1. 驱动的设计
- 本章目标:设计驱动scull操作内存,就像操作一个设备
- 驱动的第一步是定义驱动将要提供给用户程序的能力
- 4个设备:scull0~scull3,每个由一个全局永久的内存区组成,可共享,数据不丢失
- 4个FIFO设备:scullpipe0~scullpipe3,一个进程读来自另一个进程写,多个设备会竞争数据
- scullpipe内部有阻塞和非阻塞读写的具体实现
- 以下这些设备与scull0相似:
- scullsingle:一次只允许一个进程使用驱动
- scullpriv:对每个虚拟终端(或x终端会话)是私有的
- sculluid:可多次打开,但是一次只能是一个用户,另一用户锁着设备,返回设备忙错误
- scullwuid:可多次打开,但是一次只能是一个用户,另一用户锁着设备,实现阻塞打开
2. 主次编号
- 设备文件存位于 /dev
- 字符设备标志是 c,快设备的标志是 b
- 主设备号标识设备相连的驱动,linux允许多个驱动共享主编号
- 次编号被用来决定引用哪个设备
- dev_t 用来表示设备号(包括主次设备号,主12bit,次20bit)
- <linux/kdev_t.h>定义的宏表示主次设备号,MAJOR(dev_t dev),MINOR(dev_t dev)
- 若直接有主次编号,需要调用 MKDEV(int major, int minor)
分配和释放设备编号
- 获取设备编号来使用,在 <linux/fs.h>中声明:
int register_chrdev_region(dev_t first, unsigned int count, char *name);
- first 是起始设备编号,first的次设备标号通常是0
- count 是请求链接设备编号的总数
- name 是应当来链接到这个设备的设备名字,它出现在 /proc/devices 和 sysfs 中
- 成功返回0,错误返回错误码
- 动态分配一个主编号:
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned count, char *name);
- dev 在函数成功返回分配的第一个数
- firstminor 请求的第一个要用的次编号,通常是0
- count 和 name 同上一个函数
- 设备编号的释放:
void unregister_chrdev_region(dev_t first, unsigned int count);
- 该函数通常在模块cleanup的函数内
重要的数据结构file_operation
- file_operation 结构,定义在 <linux/fs.h>,是一个函数指针的集合,负责实现系统调用,有open、read等
- 常遇到 __user,是一种文档形式,可以被外部检查软件使用来找出对用户空间地址的错误使用
- struct module *owner; // 是一个指向拥有这个结构的模块的指针,用来在它正在被使用时阻止模块被卸载
- // 几乎都被初始化为 THIS_MODULE,一个定义在 <linux/module.h> 中的宏
- loff_t (*llseek) (struct file *, lott_t, int); // 用作改变文件中的当前读/写位置,新位置(正)作为返回值,lott_t 类型是 long offset(32位系统至少有64位宽)
- // 错误返回负数,若该函数指针时NULL,seek调用会无法预知地改变file结构中的位置计数器
- ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
- // 用作从设备中读取数据,若该指针为NULL,则read系统调用以 -EINVAL 失败
- // 非负的返回值,代表成功读取的字节数(signed size类型,目标平台的整数类型)
- size_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
- // 初始化一个异步读,可能在函数返回前不结束读操作,若为NULL,所有读操作会被read(同步读)代替
- ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
- // 发送数据给设备,若为NULL,write系统调用会返回 -EINVAL
- // 非负的返回值,代表成功写的字节数
- ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t *);
- // 初始化设备上一个异步写
- int (*readdir) (struct file *, void *, filldir_t); // 对于设备文件这个成员应当为NULL,用它来读取目录,并且对文件系统有用
- unsigned int (*poll) (struct file *, struct poll_table_struct *);
- //