历时将近两个月,终于化零为整,对《Linux设备驱动程序》(3rd edition)中第三章scull驱动有了详细的了解,下面对scull的驱动进行解析,方便以后的查看。
一、创建字符驱动的步骤:
1、获得设备号:
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);
上述两个函数,前者为在确定主设备号(first)的时候,进行设备号的注册,后者为自动获得主设备号并注册(获得的设备号存入dev中)。
2、将设备操作函数与设备号联系到一起:
初始化:void cdev_init(struct cdev *dev, struct file_operations *fops);
添加到内核中:int cdev_add(struct cdev *dev, dev_t num, unsigned int count)
注意:步骤2用到的fops已经初始化完成,设计设备驱动的open,read,write,release等函数;而需要定义全局变量struct cdev my_cdev,或在驱动代码中利用kalloc对其分配空间。
二、代码例程:
可参考本书自带的例程,附件中的main.c中scull_init_module的实现。
三、scull驱动代码分析以及应用程序调用scull
1、scull驱动代码中的全局变量:struct scull_dev *scull_devices; //通过在scull_init_module中为其分配空间;
scull_major, scull_minor:设备号
2、当scull驱动编译完成,且安装到内核中后,可利用应用程序进行读取来验证。如:file1=fopen("/dev/scull0", "rb+"); stringlen = fwrite(stringBuffer, 1, sizeof(stringBuffer), file1)
3、关于fopen的实现:
a)应用程序在调用fopen库函数后,会执行底层应用函数接口int open(const char *path, int oflags),进而会执行到内核代码 long do_sys_open(int dfd, const char __user *filename, int flags, int mode) (内核代码open.c文件中)。do_sys_open实现通过filename得到struct file *f,并把f与int fd绑定,返回fd。得到的fd即为open函数的返回值。
b)scull驱动中的open函数int scull_open(struct inode *inode, struct file *filp)
struct scull_dev *dev;
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* for other methods */
在scull驱动中主要是通过filp->private_data(指向dev),进行数据的write和read。
在看这段代码时,有很多疑问,如inode->i_cdev应该是指向的之前在scull驱动中定义的scull_devices.dev部分,那么是在什么时候jiangscull_devices.dev的地址赋值给的inode->i_cdev;再有就是scull_open与内核代码中的do_sys_open之间的关系。自己的理解如下:
(1)scull_init_module调用scull_setup_cdev函数,其中scull_setup_cdev实现cdev_init和cdev_add的功能,cdev_add实现告诉内核,对应的设备代码对应特定的设备文件和操作函数(scull_devices以及其中的f_ops),即在内核中进行注册,之后通过设备号就可找到对应的scull_devices。在进行节点创建时(mknod),会用到设备号(如:mknod /dev/scull0 -C $majorNumber 0),此时创建的inode,其i_cdev已经指向了对应特定设备号的scull_devices。
(2)内核源码do_sys_open主要实现通过文件路径path得到file指针f,并与对应的句柄fd绑定。
do_sys_open调用struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);do_filp_open函数中实质性的打开工作是do_last函数。
在do_last函数中,无论open_flag中的标志位判断该文件是不是需要被创建,最后都会调用函数nameidata_to_filp,nameidata_to_filp会调用__dentry_open,在__dentry_open函数中会调用到f->f_op->open也就是scull_open。
关于文件节点是否需要创建在调用f_op 的时刻会有不同,需要创建节点时,在__open_namei_create之后调用nameidata_to_filp;不需要创建节点时,在finish_open中调用nameidata_to_filp。
(3)在scull驱动过程中,通过file->private_data来传递scull_devices的地址(主要在scull_open代码中实现),进而对scull_devices的data数据去进行读写。
4、scull_devices指向的数据空间(可参考,第三章61页的图片):
在进行scull_write和scull_read时,调用scull_follow函数,实现对数据空间的分配(kmalloc)
struct scull_dev {
struct scull_qset *data; /* Pointer to first quantum set */
int quantum; /* the current quantum size */
int qset; /* the current array size */
unsigned long size; /* amount of data stored here */
unsigned int access_key; /* used by sculluid and scullpriv */
struct semaphore sem; /* mutual exclusion semaphore */
struct cdev cdev; /* Char device structure */
};
struct scull_qset {
void **data;
struct scull_qset *next;
};
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
参考资料:
1、《LInux设备驱动程序》第三章
2、Linux内核源码
3、关于内核代码open的解析:http://www.cublog.cn/u3/119372/showart_2518539.html
4、关于file->private_data http://linux.chinaunix.net/techdoc/develop/2007/09/09/967423.shtml