笔记:Linux Device Driver 3 第三章 字符设备SCULL
笔记原创:
山涛
联系邮件:
epost_guo@126.com
说明:本文档供学习交流使用,我是学习者,文档属于自己的体会,看本文档一分为二。希望大家共同交流,共同进步!
一,
SCULL:Simple Character Utility for Loading
Localities
SCULL驱动模块操作PC上的一块内存,它是一个char设备。
二,
SCULL的主设备号和次设备号:
主设备号标志着一个与设备相关的驱动;现代Linux内核允许多个驱动共享一个主设备号,但是绝大多数的设备仍然遵循着“一个驱动对应一个主设备号”的原则。
次设备号被内核使用,用于区分具体的逻辑设备。
(个人理解,对一个设备而言,由于设备有不同的功能和配置,按功能和配置划分的话,一个设备相当于若干个子设备的集合;驱动实现了所有这些子设备的功能,但是当内核被告知使用其中的某一个子设备时,就需要使用次设备号来区分)
设备号,类型是:dev_t,它由“主设备号+次设备号”组成(dev_t类型32位,12位用于主设备号,20位用于次设备号)。
分配一个设备号:
静态分配设备号函数原型:
int
register_chrdev_region(dev_t first, unsigned int count, char *name);
动态分配设备号函数原型:
int
alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count,
char *name);
释放设备号函数原型:
void
unregister_chrdev_region(dev_t first, unsigned int count);
建议使用动态分配设备函数;使用该函数的缺点是不能预先建立设备节点(在/dev下),因为你不知道动态分配的号到底是多少。不过,这个缺点很容易解决,即当驱动加载成功后,你通过读取/proc/devices得知该驱动所分配到的设备号。
三,
一些重要的结构(这些结构均只能在内核中使用)
file_operations:表示文件操作结构。
file:表示一个打开的文件。
inode:表示一个文件。
file与inode的区别在于:一个文件可以被打开多次,就会有多个file结构;而一个文件只对应一个inode结构。
四,
内核使用struct
cdev来表示内部的字符设备。在一般的字符设备程序中,cdev结构被嵌入到特定的设备结构中,成为其中的一个域。Cdev结构的初始化使用下面的函数原型:
void
cdev_init (struct cdev *cdev, struct file_operations *fops);
当cdev域被初始化后,最后的一步就是将它的信息告知内核(这就是所谓的注册),调用如下的函数原型:
int
cdev_add(struct cdev *dev, dev_t num, unsigned int count);
在调用cdev_add()之前,要确保驱动的初始化完成,对设备的操作已经完全准备好。
从内核释放字符设备的函数原型如下:
void
cdev_del(struct cdev *dev);
五,
open方法的功能:
(1)检查设备相关的错误;
(2)第一次打开设备时,初始化设备;
(3)如果需要,更新f_op指针;
(4)分配和填充设备使用的数据结构,将它存入filp->privat_data域中。
Open方法的函数原型:
int (*open)
(struct inode *inode, struct file *filp);
release方法的功能:
(1)释放任何在filp->private_data中所分配的数据结果;
(2)关闭设备。
Release方法的函数原型:
int
(*release)(struct inode *inode, struct file *filp);
六,
SCULL的内存使用
SCULL以一片系统内存作为一个字符设备,对其进行操作。SCULL的内存使用是很有趣的。
代表SCULL设备的数据结构是scull_dev。scull_dev通过一个链表来管理内存,链表的具体结构为scull_qset,每个scull_qset指向一块内存区域:这块内存区域被分成1000份,每份4000个字节。所以,每份称为quantum,份数成为quantum set,其实份数就是指针了。 SCULL设备的规划如下图:
这个内存使用模型是以scull_qset结构为单位的,当你向SCULL中写入一个byte的数据时,SCULL需要分配给你一个scull_qet结构,这至少需要1000个指针(占用4000字节或8000字节,这取决于平台是32位还是64位)和一个quantum(4000字节)。所以写一个字节对于这样一个内存使用模型的开销是很大的。SCULL内存使用模型并没有限制最大使用内存的数目,你可以用完你的机器上所有的实际内存。SCULL只限制了一个scull_qset可以容纳4000x1000大小的内存。
七,
读方法函数原型:
ssize_t
read(struct file* filp, char __user *buff, size_t count, loff_t
*offp);
写方法函数原型:
ssize_t
write(struct file* filp, const char __user *buff, size_t count, loff_t
*offp);
内核读/写方法需要与用户进行数据交换的函数,原因是上面的原型中的buff参数是用户空间的地址,内核空间不能直接访问用户空间的数据变量。因此,内核和用户空间交换数据时,需要两个辅助函数,原型如下:
unsigned long
copy_to_user(void __user *to, const void *from, unsigned long count);
unsigned long
copy_from_user(void *to, const void __user *from, unsinged long
count);
这两个函数会首先检查用户空间指针是否有效。
读方法实现的功能
读方法是系统调用sys_read()的具体实现。读方法的返回值由调用read系统调用的应用程序来进行解释。
(1)如果返回值与count参数的值相同,则说明被要求传输的数据完全接收完毕。这是最理想的情况;
(2)如果返回值是正值,但是小于count值,说明仅有一部分数据被传输。这种情况的发生有多种原因,依赖于具体的设备。通常情况下,应用程序会尝试重新读取。
(3)如果返回值为0,表示到了文件尾(即无数据可读)。
(4)如果返回值是负值,表示发生了错误。
(5)还有一种情况,read系统调用可以阻塞,这意味着“现在没有数据,但是等会就来”。
写方法实现的功能:
写方法,是系统调用sys_write()的具体实现。写方法的返回值同样由调用write系统调用的应用程序来解释。
(1)如果返回值与count值相等,表明按要求传输的数据发送完毕。
(2)如果返回值为正值,但小于count值,表明一部分数据发送成功。应用程序通常会尝试重新发送剩余的数据。
(3)如果返回值为0,表明没有发送数据,这不是个错误。再一次,标准数据库会重新调用write系统调用。
(4)如果返回值为负值,表明发送数据出错。