1.字符设备驱动概述
Linux用户空间与内核空间之间的虚拟内存是分开的,用户空间不能直接写入内核空间,如果用户空间和内核空间需要传递数据就需要字符设备驱动。
2.结构体以及函数介绍
(1)字符设备空间结构体:
1 static const struct file_operations chrdev_fops = 2 { 3 .owner = This_MODULE, 4 .read = chrdev_read, 5 .write = chrdev_write, 6 .open = chrdev_open, 7 .release = chrdev_release 8 };View Code
(2)打开和释放函数:
1 static int chrdev_open(struct inode *inode, struct file *filp) 2 { 3 printk(KERN_INFO "chrdev opened\n"); 4 5 return 0; 6 }View Code
1 static int chrdev_release(struct inode *inode, struct file *filp) 2 { 3 printk(KERN_INFO "chrdev released\n"); 4 5 return 0; 6 }View Code
(3) 读写函数:
1 static ssize_t chrdev_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) 2 { 3 printk(KERN_INFO "return EOF\n"); 4 5 return 0; 6 }View Code
1 static ssize_t chrdev_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) 2 { 3 printk(KERN_INFO "got %ld bytes\n", count); 4 5 return count; 6 }View Code
对于这两个方法,filp是文件指针,count是请求的数据传输的大小。buf参数指向保存要写的数据的用户缓冲区,或者指向应该放置新读取数据的空缓冲区。最后,ppos是指向 “long offset type” 对象的指针,该对象指示用户正在访问的文件位置。返回值是一个“signed size type”。这是最基本的函数结构,但是如果这个函数只是一个空壳,并不能实现读写。
就数据传输而言,与这两种设备方法相关的主要问题是需要在内核地址空间和用户地址空间之间传输数据。该操作不能以通常的方式通过指针执行,也不能通过memcpy执行。由于一些原因,用户空间地址不能直接在内核空间中使用。
内核空间地址和用户空间地址之间的一个重要区别是,用户空间中的内存可以交换出去。当内核访问一个用户空间指针时,相关的页面可能不会出现在内存中,并且会生成一个页面错误。即使在CPU在内核空间执行时,也能正确地处理页面错误。
另外,有趣的是,Linux 2.0的x86端口为用户空间和内核空间使用了完全不同的内存映射。因此,用户空间指针根本不能从内核空间取消引用。如果目标设备是一个扩展板而不是RAM,那么同样的问题就会出现,因为驱动程序必须在用户缓冲区和内核空间之间(也可能在内核空间和I/O内存之间)复制数据。
在Linux中,跨空间副本是通过中定义的特殊函数来执行的。这种复制可以通过通用(类似于memcpy)的函数执行,也可以通过针对特定数据大小(char、short、int、long)进行优化的函数执行。
scull中的读写代码需要将整个数据段复制到用户地址空间或从用户地址空间复制数据段。该功能由以下内核函数提供,这些函数复制任意的字节数组,并位于每次读写实现的核心:
1 unsigned long copy_to_user(void *to, const void *from, unsigned long count);View Code
1 unsigned long copy_from_user(void *to, const void *from, unsigned long count);View Code
参考链接:https://www.oreilly.com/library/view/linux-device-drivers/0596000081/ch03s08.html