6.1 Linux字符设备驱动结构
-
cdev结构体
- Linux中用cdev结构体描述字符设备,主要成员变量:所属模块、file_operations结构体、dev_t 类型的设备号
- 成员变量:
- 设备号dev_t为u32类型,前12位为主设备号,后20位为次设备号,以下宏定义可获取:
- MAJOR(dev_t dev)
- MINOR(dev_t dev)
- MKDEV(int major, int minor)
- file_operations:字符设备驱动提供给虚拟文件系统的接口
- 设备号dev_t为u32类型,前12位为主设备号,后20位为次设备号,以下宏定义可获取:
- 对cdev的操作:
- void cdev_init(struct cdev *, struct file_operations *); 初始化cdev成员,与file_operations建立连接
- struct cdev *cdev_alloc(void); 动态申请一个cdev内存
- void cdev_put(struct cdev *p);
- int cdev_add(struct cdev *, dev_t, unsigned);
- void cdev_del(struct cdev *);
- Linux中用cdev结构体描述字符设备,主要成员变量:所属模块、file_operations结构体、dev_t 类型的设备号
-
分配和释放设备号
-
cdev_add()之前应该向系统申请设备号;在cdev_del()之后应该释放设备号
-
设备号申请:
-
静态申请:register_chrdev_region();
-
动态申请:alloc_chrdev_region();
-
- 设备号释放:unregister_chrdev_region();
-
-
file_operations结构体(主要成员分析)
-
llseek()修改一个文件的当前读写位置,返回新位置
-
read()函数用来从设备中读取数据,成功时函数返回读取的字节数
-
write()函数向设备发送数据,成功时该函数返回写入的字节数。
-
unlocked_ioctl()提供设备相关控制命令的实现
-
mmap()函数将设备内存映射到进程的虚拟地址空间中
-
驱动程序可以不实现open(),在这种情况下,设备的打开操作永远成功。与open()函数对应的是release()函数。
-
poll()函数一般用于询问设备是否可被非阻塞地立即读写。当询问的条件未触发时,用户空间进行select()和poll()系统调用将引起进程的阻塞。
-
aio_read()和aio_write()函数分别对与文件描述符对应的设备进行异步读、写操作。
-
-
Linux字符设备驱动的组成
- 字符设备驱动模块加载与卸载函数
- 设备号的申请与释放
- cdev结构体的注册与注销
- Linux习惯定义一个设备结构体,包含设备信息
- 字符设备驱动的file_operations结构体中成员函数
- 字符设备驱动模块加载与卸载函数
- 用户空间不能直接访问内核空间内存
- copy_from_user();
- copy_to_user();
- 若是简单类型,可以使用:put_user()和get_user()
- 在访问用户空间缓冲区时,必须检查合法性,使用access_ok()函数进行判断,上述四个函数已经进行了合法性检查。
6.2 globalmem虚拟设备实例描述
- ioctl()命令生成可防止命令码污染
-
I/O控制命令的组成
设备类型
序列号
方向
数据尺寸
8位“幻数” 8位 2位(无、读、写、双向) 13/14位 -
用宏定义生成I/O命令:
-
_IO、_IOR、_IOW、_IORW
-
eg: #define MEM_CLEAR _IO('g', 0x1) 其中’g‘为幻数,0x1为序号
-
内核中预定义了一些I/O控制命令,如果某设备驱动中包含了与预定义命令一样的命令码,这些命令会作为预定义命令被内核处理而不是被设备驱动处理
-
- 使用文件私有数据:
- 将文件私有数据private_data指向设备结构体,再用其他函数通过private_data访问设备结构体。
- container_of()可以通过结构体成员的指针找到对应结构体的指针,形式参数分别为第1个参数是结构体成员的指针,第2个参数为整个结构体的类型,第3个参数为传入的第1个参数即结构体成员的类型,返回值为整个结构体的指针