三、字符设备驱动(基于北京迅为电子)

一、概述

字符设备的驱动包括设备号、字符设备结构体、自动创建设备节点、fops结构体

二、知识点

  1. 设备号:一个32bit的数据,高12bit表示主设备号,低20bit表示次设备号
  2. 宏定义:MAJOR(dev),MINOR(dev),MKDEV(ma,mi)
  3. 分配设备号:register_chrdev_region、alloc_chrdev_region前者静态分配设备号,后者动态分配设备号
  4. cat /proc/devices 查看已经分配的设备号
  5. 分配设备号
register_chrdev_region(dev_t, unsigned, const char*);      // 起始设备号、数量、名称
alloc_chrdev_region(dev_t*, unsigned, unsigned, const char*);  // 保存申请到的设备号、起始次设备号、数量、名称
unregister_chrdev_region(dev_t, unsigned);                // 要释放的设备号、要释放的设备号的数量
  1. 注册一个字符设备
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    
  1. 文件操作函数结构体
struct file_operations ;
应用层的open、read、write、close、ioctl函数均有驱动层的相应函数对应,这些函数指针都放在file_operation结构体中
  1. 创建设备节点
手动创建设备节点 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);
  1. 用户空间和内核空间
    通过系统调用、软中断、硬件中断,进程由用户空间陷入内核空间。两个空间拷贝数据的API:copy_from_usercopy_to_user
  2. file结构体的私有数据,一般驱动空间中会由一个自定义的结构体,这样在open时设置file的private_data为该结构体实例对象的指针,在后续的read/write函数中通过访问file的private_data就可以得到有用数据
  3. container_of(type, struct, name); 通过结构体成员的地址得到结构体的地址
  4. 错误处理:使用goto完成不同阶段的错误处理退出,使用IS_ERR判断指针是否有效,通过PTR_TO_ERR将错误指针返回错误

三、杂项设备

  1. 杂项设备是无法归类的一系列设备,主设备号都是10,用来避免主设备号的浪费
  2. 杂项设备不需要手动设置设备节点,实现相应的miscdevice结构体,填充name、minor和fops即可(minor使用MISC_DYNAMIC_MINOR表示动态申请次设备号)
  3. 实现miscdevice结构体之后,调用misc_register就可以注册一个杂项设备并自动生成设备节点
  4. 调用misc_unregister注销杂项设备

四、字符设备的驱动框架

  1. 定义cdev结构体
  2. 动态分配一个设备号alloc_chrdev_region
  3. 初始化cdev结构体并添加到系统中cdev_init cdev_add
  4. 设置自动创建设备节点class_create device_create
  5. cdev_init时需要传入fops,因此需要实现fops的几个函数,open、read、write等等
  6. 出口函数中需要反过来一步一步地注销

五、原子操作

  1. 原子变量,对原子变量的初始化、加减等
  2. 原子位操作,对指定内存对应的变量进行位操作

六、自旋锁

  1. 自旋锁的初始化、加锁、解锁
  2. 自旋锁一般在中断中使用,进入临界区加锁,出临界区解锁
  3. 自旋锁死锁的情况一:任务A获取自旋锁后在临界区使系统进入休眠状态,同时另一个任务B也要获取自旋锁,导致死锁
  4. 自旋锁死锁的情况二:任务A获取自旋锁后在临界区由于中断进行中断服务程序,中断服务程序中也要获取自旋锁,导致死锁
  5. 解决方法:在临界区不调用引起系统休眠的函数,同时进入临界区使用spin_lock_irqsave,出临界区使用spin_unlock_irqrestore

七、信号量

  1. 信号量的值不能小于0,可以大于1
  2. 信号量具有休眠特性,不能用在中断函数中,适合长时间占用资源的场合,控制同时访问共享资源的线程数
  3. 信号量的初始化、获取信号量、释放信号量

八、互斥锁

  1. 和值为1的信号量类似,但是效率更高
  2. 自旋锁的初始化、加锁、解锁

九、IO模型

  1. 阻塞IO、非阻塞IO、信号驱动IO、IO多路复用、异步IO
  2. 阻塞IO,如果数据没有准备好,则阻塞等待直到数据准备好
  3. 非阻塞IO,没有数据返回ERR,有数据则即刻返回
  4. IO多路复用,等待多个描述符,内核负责检查多个描述符,有一个有效的就返回
  5. 信号驱动IO,注册数据处理函数,当IO数据产生后,内核发送信号,调用信号处理函数

十、驱动调试

  1. printk
  2. dump_stack,打印函数调用关系
posted @ 2025-11-04 23:24  gramming  阅读(14)  评论(0)    收藏  举报