迅为RK3568开发板第二期:字符设备驱动开发指南

迅为RK3568开发板第二期:字符设备驱动开发指南

最近跟着迅为RK3568开发板的教程完成了字符设备驱动的系统学习,结合课程中的三张核心图示,梳理出从「用户态-内核态交互逻辑」到「完整驱动开发流程」再到「简化版杂项设备驱动」的全链路知识点,彻底搞懂了Linux字符设备驱动的核心设计思路。

一、字符设备驱动核心交互逻辑

课程中的核心图示直接点明了字符设备驱动的本质:
应用层通过open/read/write等系统调用操作/dev/xxx设备文件时,内核会通过file_operations结构体,将这些用户态操作映射到驱动层对应的函数(如xxx_open/xxx_read/xxx_write),最终实现应用程序对硬件的控制。

file_operations是连接应用层与驱动层的核心“桥梁”——它是一套接口映射表,只要驱动中实现了该结构体的关键成员函数,就能让应用程序通过标准文件操作“调用”驱动逻辑。
​​​​在这里插入图片描述

二、字符设备驱动标准开发流程

字符设备驱动的完整开发流程分为4个核心步骤,也是本次学习的重点:

1. 注册字符设备

(1)分配设备号

设备号是字符设备的唯一标识,分为主设备号(标识驱动类型)和次设备号(标识同一驱动下的不同设备):

  • 静态分配register_chrdev_region
    手动指定主设备号,优点是直观,缺点是易与系统现有设备号冲突,不推荐;
  • 动态分配alloc_chrdev_region
    由内核自动分配未被占用的主设备号,避免冲突,是工业级开发首选;
  • 设备号操作函数
    • MAJOR(dev_t dev):从设备号中拆分主设备号;
    • MINOR(dev_t dev):从设备号中拆分次设备号;
    • MKDEV(int major, int minor):合并主/次设备号为dev_t类型。
(2)初始化&注册cdev

struct cdev是内核描述字符设备的核心结构体,需完成初始化与注册:

  • cdev_init(&cdev, &fops):将cdev结构体与file_operations绑定,告知内核该设备的操作函数集;
  • cdev_add(&cdev, dev_num, count):将cdev注册到内核,完成“设备号→驱动函数”的映射。

2. 构建file_operations结构体

file_operations是驱动的“功能实现区”,需实现应用层对应的核心操作:

成员函数触发时机核心逻辑
open应用执行open("/dev/xxx")初始化设备硬件/私有数据,绑定设备资源到file->private_data
read应用执行read(fd, buf, len)copy_to_user将内核态数据拷贝到用户态缓冲区;
write应用执行write(fd, buf, len)copy_from_user将用户态数据拷贝到内核态缓冲区(禁止直接访问用户态内存);
release应用执行close(fd)释放设备资源、私有数据内存,避免内存泄漏;

注意:copy_to_user/copy_from_user是内核态与用户态数据交互的安全函数,直接访问用户态指针会导致内核崩溃。

3. 生成设备节点

应用层需通过/dev/xxx设备文件访问驱动,需手动/自动生成节点:

  • 自动生成(推荐)
    class_create(THIS_MODULE, "class_name"):在/sys/class下创建设备类;
    device_create(class, NULL, dev_num, NULL, "dev_name"):基于设备类生成/dev/xxx节点;
  • 手动生成
    mknod命令:mknod /dev/xxx c 主设备号 次设备号c表示字符设备)。

4. 驱动卸载(反向释放资源)

驱动卸载时需按“反向顺序”释放资源,避免内存泄漏/资源残留:
unregister_chrdev_region(释放设备号)→ cdev_del(删除cdev结构体)→ device_destroy(销毁设备节点)→ class_destroy(销毁设备类)。
在这里插入图片描述

三、简化版:杂项设备驱动

课程中还讲解了字符设备的简化实现——杂项设备驱动,适合快速开发简单设备:

核心优势

  • 省去手动分配设备号、生成设备节点的步骤,内核自动处理;
  • 代码量大幅减少,只需实现核心功能函数。
    在这里插入图片描述

关键步骤

  1. 构建struct miscdevice结构体:指定设备名、绑定file_operations
  2. 注册杂项设备:misc_register(&misc_dev)
  3. 卸载杂项设备:misc_deregister(&misc_dev)

局限性

杂项设备的主设备号固定为10,仅适用于功能简单、无需自定义设备号的场景。

四、文件私有数据(private_data)

  1. 私有数据的核心作用
    struct file结构体中的 private_data指针,是内核留给驱动的 “专属存储空间”,核心价值:
    绑定设备资源:将全局设备结构体(如struct device_test)绑定到每个打开的文件,让read/write函数快速访问设备资源;
    独立上下文管理:每个打开的/dev/xxx文件拥有独立的私有数据(如读写计数、缓冲区、状态标记),多进程访问时互不干扰;
    解耦代码逻辑:无需依赖全局变量,驱动扩展性更强(如支持多设备时,仅需绑定不同设备结构体即可)。

总结

本次学习的核心是理解“用户态-内核态”的交互桥梁——file_operations,字符设备驱动的本质是将标准文件操作映射到硬件控制逻辑。标准字符设备驱动流程完整、灵活性高,适合复杂设备;杂项设备驱动简化了开发流程,适合快速验证功能。基于RK3568开发板的实战,也让我掌握了从驱动编写、编译、加载到应用层测试的全流程,为后续硬件驱动开发打下了基础。

注:图片来自迅为b站视频讲解,本次记录作为学习记录,如侵权将及时删除。

### 环境搭建与代码获取 在Linux环境下进行RK3568开发的第一步是搭建合适的开发环境。首先需要准备一个支持的Linux发行版,如Ubuntu 20.04 LTS或更高版本[^1]。接着安装必要的软件包和工具链,这包括但不限于build-essential、libncurses5-dev、flex、bison、libssl-dev等。 访问Rockchip的GitHub页面来获取内核源代码是一个好的起点。在这个仓库中可以找到针对RK3568的具体配置和补丁集,这对于理解硬件特性和如何正确初始化外设至关重要[^1]。 ### 驱动开发基础 对于驱动程序的开发,《iTOP-RK3568开发板驱动开发指南》提供了详细的指导,特别是关于I²C和SPI接口的部分。这些文档不仅介绍了如何编写基本的驱动模块,还涵盖了更高级的主题,比如DMA传输、中断处理以及电源管理功能的实现[^2]。 为了开始编写第一个驱动程序,开发者应该熟悉Linux设备模型,并了解platform_device和platform_driver结构体的作用及其注册过程。此外,还需要掌握字符设备注册方法及文件操作函数集(file_operations)的定义。 ### 镜像构建与烧录 当完成了驱动程序或其他内核模块的开发后,下一步就是创建可部署的镜像文件。可以通过几种方式获得适用于RK3568的目标镜像:从每日构建版本下载预编译好的固件;或者克隆官方源码仓库并自行编译生成所需的镜像文件。具体来说,在路径`out/rk3568/packages/phone/images`里拷贝生成的镜像是一种常见做法[^3]。 一旦有了正确的镜像文件,就可以使用专门的工具以Loader模式将其烧写到目标设备上。确保按照官方提供的步骤准确无误地执行每一步操作,以免造成硬件损坏或数据丢失。 ### 示例代码 - GPIO控制 以下是一个简单的示例代码片段,演示了如何通过sysfs接口在用户空间直接操控GPIO引脚: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> int main() { const char *gpio_export_path = "/sys/class/gpio/export"; int gpio_number = 17; // 假设要使用的GPIO编号 FILE *fp = fopen(gpio_export_path, "w"); if (!fp) { perror("无法打开 gpio export 节点"); exit(EXIT_FAILURE); } fprintf(fp, "%d", gpio_number); fclose(fp); char gpio_direction_path[50]; snprintf(gpio_direction_path, sizeof(gpio_direction_path), "/sys/class/gpio/gpio%d/direction", gpio_number); fp = fopen(gpio_direction_path, "w"); if (!fp) { perror("无法打开 gpio direction 节点"); exit(EXIT_FAILURE); } fprintf(fp, "out"); fclose(fp); while (1) { char gpio_value_path[50]; snprintf(gpio_value_path, sizeof(gpio_value_path), "/sys/class/gpio/gpio%d/value", gpio_number); fp = fopen(gpio_value_path, "w"); if (!fp) { perror("无法打开 gpio value 节点"); exit(EXIT_FAILURE); } fprintf(fp, "1"); // 设置为高电平 fclose(fp); sleep(1); // 等待一秒 fp = fopen(gpio_value_path, "w"); if (!fp) { perror("无法打开 gpio value 节点"); exit(EXIT_FAILURE); } fprintf(fp, "0"); // 设置为低电平 fclose(fp); sleep(1); // 再次等待一秒 } return 0; } ``` 此程序展示了如何动态导出指定GPIO并通过修改其方向属性设置为输出模式,然后循环改变该GPIO的状态从而点亮连接于此引脚上的LED灯。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值