三、字符设备驱动(基于北京迅为电子)
一、概述
字符设备的驱动包括设备号、字符设备结构体、自动创建设备节点、fops结构体
二、知识点
- 设备号:一个32bit的数据,高12bit表示主设备号,低20bit表示次设备号
- 宏定义:
MAJOR(dev),MINOR(dev),MKDEV(ma,mi) - 分配设备号:
register_chrdev_region、alloc_chrdev_region前者静态分配设备号,后者动态分配设备号 cat /proc/devices查看已经分配的设备号- 分配设备号
register_chrdev_region(dev_t, unsigned, const char*); // 起始设备号、数量、名称
alloc_chrdev_region(dev_t*, unsigned, unsigned, const char*); // 保存申请到的设备号、起始次设备号、数量、名称
unregister_chrdev_region(dev_t, unsigned); // 要释放的设备号、要释放的设备号的数量
- 注册一个字符设备
struct cdev {
struct kobject kobj; // 父对象
struct module *owner; // 所有者
const struct file_operations *ops; // fops
struct list_head list; // 链表头
dev_t dev; // 设备号
unsigned int count; // 数量
} __randomize_layout;
void cdev_init(struct cdev *, const struct file_operations *); // 初始化cdev结构体并关联fops
int cdev_add(struct cdev *, dev_t, unsigned); // 关联设备号和数量,并注册cdev
void cdev_del(struct cdev *); // 注销cdev
- 文件操作函数结构体
struct file_operations ;
应用层的open、read、write、close、ioctl函数均有驱动层的相应函数对应,这些函数指针都放在file_operation结构体中
- 创建设备节点
手动创建设备节点 mknod /dev/xxx c 250 0
struct class *cls = class_create(owner, name);
void class_destroy(struct class *cls);
device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
void device_destroy(struct class *cls, dev_t devt);
- 用户空间和内核空间
通过系统调用、软中断、硬件中断,进程由用户空间陷入内核空间。两个空间拷贝数据的API:copy_from_user和copy_to_user - file结构体的私有数据,一般驱动空间中会由一个自定义的结构体,这样在open时设置file的private_data为该结构体实例对象的指针,在后续的read/write函数中通过访问file的private_data就可以得到有用数据
container_of(type, struct, name);通过结构体成员的地址得到结构体的地址- 错误处理:使用goto完成不同阶段的错误处理退出,使用IS_ERR判断指针是否有效,通过PTR_TO_ERR将错误指针返回错误
三、杂项设备
- 杂项设备是无法归类的一系列设备,主设备号都是10,用来避免主设备号的浪费
- 杂项设备不需要手动设置设备节点,实现相应的miscdevice结构体,填充name、minor和fops即可(minor使用MISC_DYNAMIC_MINOR表示动态申请次设备号)
- 实现miscdevice结构体之后,调用
misc_register就可以注册一个杂项设备并自动生成设备节点 - 调用
misc_unregister注销杂项设备
四、字符设备的驱动框架
- 定义cdev结构体
- 动态分配一个设备号
alloc_chrdev_region - 初始化cdev结构体并添加到系统中
cdev_init cdev_add - 设置自动创建设备节点
class_create device_create cdev_init时需要传入fops,因此需要实现fops的几个函数,open、read、write等等- 出口函数中需要反过来一步一步地注销
五、原子操作
- 原子变量,对原子变量的初始化、加减等
- 原子位操作,对指定内存对应的变量进行位操作
六、自旋锁
- 自旋锁的初始化、加锁、解锁
- 自旋锁一般在中断中使用,进入临界区加锁,出临界区解锁
- 自旋锁死锁的情况一:任务A获取自旋锁后在临界区使系统进入休眠状态,同时另一个任务B也要获取自旋锁,导致死锁
- 自旋锁死锁的情况二:任务A获取自旋锁后在临界区由于中断进行中断服务程序,中断服务程序中也要获取自旋锁,导致死锁
- 解决方法:在临界区不调用引起系统休眠的函数,同时进入临界区使用
spin_lock_irqsave,出临界区使用spin_unlock_irqrestore
七、信号量
- 信号量的值不能小于0,可以大于1
- 信号量具有休眠特性,不能用在中断函数中,适合长时间占用资源的场合,控制同时访问共享资源的线程数
- 信号量的初始化、获取信号量、释放信号量
八、互斥锁
- 和值为1的信号量类似,但是效率更高
- 自旋锁的初始化、加锁、解锁
九、IO模型
- 阻塞IO、非阻塞IO、信号驱动IO、IO多路复用、异步IO
- 阻塞IO,如果数据没有准备好,则阻塞等待直到数据准备好
- 非阻塞IO,没有数据返回ERR,有数据则即刻返回
- IO多路复用,等待多个描述符,内核负责检查多个描述符,有一个有效的就返回
- 信号驱动IO,注册数据处理函数,当IO数据产生后,内核发送信号,调用信号处理函数
十、驱动调试
- printk
- dump_stack,打印函数调用关系

浙公网安备 33010602011771号