概述
6.1.4 Linux字符设备驱动的组成
在Linux中,字符设备驱动由如下几个部分组成。
1.字符设备驱动模块加载与卸载函数
在字符设备驱动模块加载函数中实现设备号的申请和cdev(字符设备)的注册,在卸载函数中实现设备号的释放和cdev的注销。Linux内核的编码习惯是为设备定义一个设备相关的结构体,这个结构体包含设备所涉及的cdev、私有数据及锁等信息。常见的设备结构体、模块加载和卸载函数形式如代码清单6.5所示。
代码清单6.5 字符设备驱动模块加载与卸载函数模板
1/* 设备结构体 */
2struct xxx_dev_t {
3 struct cdev cdev;
4 ...
5} xxx_dev;
6/* 设备驱动模块加载函数 */
7static int _ _init xxx_init(void)
8{
9 ...
10 cdev_init(&xxx_dev.cdev, &xxx_fops); /* 初始化 cdev */
11 xxx_dev.cdev.owner = THIS_MODULE;
12 /* 获取字符设备号 */
13 if (xxx_major) {
14 register_chrdev_region(xxx_dev_no, 1, DEV_NAME); // 静态分配设备号
15 } else {
16 alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME); // 动态分配设备号
17 }
18
19 ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1); /* 注册设备 */
20 ...
21}
22/* 设备驱动模块卸载函数 */
23static void _ _exit xxx_exit(void)
24{
25 unregister_chrdev_region(xxx_dev_no, 1); /* 释放占用的设备号 */
26 cdev_del(&xxx_dev.cdev); /* 注销设备 */
27 ...
28}
2.字符设备驱动的file_operations结构体中的成员函数
file_operations结构体中的成员函数是字符设备驱动与内核虚拟文件系统的接口,是用户空间对Linux进行系统调用最终的实现者。多数字符设备驱动会实现read()、write()和ioctl()函数,常见的字符设备驱动的这3个函数的形式如代码清单6.6所示。
代码清单6.6 字符设备驱动读、写、I/O控制函数模板
1 /* 读设备: 从设备中读取数据 */
2 ssize_t xxx_read(struct file *filp, char __user *buf, size_t count,
3 loff_t*f_pos)
4 {
5 ...
6 copy_to_user(buf, ..., ...); /* 把内核空间中的数据拷贝到用户空间 */
7 ...
8 }
9 /* 写设备:向设备中写入数据 */
10 ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count,
11 loff_t *f_pos)
12 {
13 ...
14 copy_from_user(..., buf, ...); /* 把用户空间中的数据写入到内核空间 */
15 ...
16 }
17 /* ioctl 函数:命令控制函数 */
18 long xxx_ioctl(struct file *filp, unsigned int cmd,
19 unsigned long arg)
20 {
21 ...
22 switch (cmd) {
23 case XXX_CMD1:
24 ...
25 break;
26 case XXX_CMD2:
27 ...
28 break;
29 default:
30 /* 不能支持的命令 */
31 return - ENOTTY;
32 }
33 return 0;
34 }
设备驱动的读函数中,filp是文件结构体指针,buf是用户空间内存的地址,该地址在内核空间不宜直接读写,count是要读的字节数,f_pos是读的位置相对于文件开头的偏移。
设备驱动的写函数中,filp是文件结构体指针,buf是用户空间内存的地址,该地址在内核空间不宜直接读写,count是要写的字节数,f_pos是写的位置相对于文件开头的偏移。
因为用户空间不能直接访问内核空间的内存,借助函数copy_from_user()完成用户空间缓冲区到内核空间的复制,以及copy_to_user()完成内核空间到用户空间缓冲区的复制。
完成内核空间和用户空间内存复制的copy_from_user()和copy_to_user()的原型分别为:
unsigned long copy_from_user(void *to, const void _ _user *from, unsigned long count);
unsigned long copy_to_user(void _ _user *to, const void *from, unsigned long count);
上述函数均返回不能被复制的字节数,如果完全复制成功,返回值为0。如果复制失败,则返回负值。
如果要复制的内存是简单类型,如char、int、long等,可以使用简单的put_user()和get_user(),如:
int val; /* 内核空间整型变量 */
...
get_user(val, (int *) arg); /* 用户→内核, arg 是用户空间的地址 */
...
put_user(val, (int *) arg); /* 内核→用户, arg 是用户空间的地址 */
读和写函数中的__user是一个宏,表明其后的指针指向用户空间,这个宏定义为:
#ifdef _ _CHECKER_ _
#define _ _user _ _attribute_ _((noderef, address_space(1)))
#else
#define _ _user
#endif
内核空间虽然可以访问用户空间的缓冲区,但是在访问之前,一般需要先检查其合法性。通过access_ok(type,addr,size)进行判断,以确定传入的缓冲区的确属于用户空间,例如:
static ssize_t read_port(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
unsigned long i = *ppos;
char __user *tmp = buf;
if (!access_ok(VERIFY_WRITE, buf, count))
return -EFAULT;
while (count-- > 0 && i < 65536) {
if (__put_user(inb(i), tmp) < 0)
return -EFAULT;
i++;
tmp++;
}
*ppos = i;
return tmp-buf;
}
上述代码中引用的__put_user()与前文讲解的put_user()的区别在于前者不进行类似access_ok()的检查,而后者会进行这一检查。在本例中,不使用put_user()而使用__put_user()的原因是在__put_user()调用之前,已经手动检查了用户空间缓冲区(buf指向的大小为count的内存)的合法性。get_user()和__get_user()的区别也相似。
注意:在内核空间与用户空间的界面处,内核检查用户空间缓冲区的合法性显得尤其必要,Linux内核的安全漏洞都是因为遗忘了这一检查造成的,非法侵入者可以伪造一片内核空间的缓冲区地址传入系统调用的接口,让内核对这个evil指针指向的内核空间填充数据。
copy_from_user()、copy_to_user()内部也进行了这样的检查:在<asm/uaccess.h>中定义
static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
{
if (access_ok(VERIFY_READ, from, n))
n = __copy_from_user(to, from, n);
else /* security hole - plug it */
memset(to, 0, n);
return n;
}
static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
{
if (access_ok(VERIFY_WRITE, to, n))
n = __copy_to_user(to, from, n);
return n;
}
在字符设备驱动中,需要定义一个file_operations的实例,并将具体设备驱动的函数赋值给file_operations的成员,如代码清单6.7所示。
代码清单6.7 字符设备驱动文件操作结构体模板
1struct file_operations xxx_fops = {
2 .owner = THIS_MODULE,
3 .read = xxx_read,
4 .write = xxx_write,
5 .unlocked_ioctl= xxx_ioctl,
6 ...
7};
上述xxx_fops在代码清单6.5第10行的cdev_init(&xxx_dev.cdev,&xxx_fops)的语句中建立与cdev的连接。
图6.1所示为字符设备驱动的结构、字符设备驱动与字符设备以及字符设备驱动与用户空间访问该设备的程序之间的关系。
最后
以上就是细腻毛巾为你收集整理的第6章 字符设备驱动之Linux字符设备驱动结构的全部内容,希望文章能够帮你解决第6章 字符设备驱动之Linux字符设备驱动结构所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复