Linux设备驱动程序学习(1)
-字符设备驱动程序
一、分配设备号
1、 对字符设备的访问是通过文件系统内的设备名称进行的(/dev/ttyS0)。在内核中,
#include <linux/types.h> dev_t 用来保存设备编号——包括主设备号和次设备号。
由dev_t获得主次设备号:
MAJOR(dev_t dev);
MINOR(dev_t dev);
由主次设备号获得dev_t
MKDEV(int major, int minor);
2、分配和释放设备编号:
静态分配设备号: 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);
3、动态分配设备编号
以《ldd3》作者的观点,分配主设备号的最佳方式是:默认采用动态分配,同时保留在加载甚至是编译时指定主设备号的余地。示例驱动scull采用了如下方式:
二、与设备驱动相关的三个重要数据结构
1、struct file_operation
它用来建立设备编号与设备操作之间的连接,scull设备驱动程序的此结构初始化为如下形式:
2、struct file 文件描述符
file 结构与用户空间的FILE没有任何关联,FILE在C库中定义且不会出现在内核代码中,而struct file是一个内核结构,它不会出现在用户程序中。
3、struct inode
内核用inode结构在内部表示文件,因此它和file结构不同,后者表示打开的文件描述符。对单个文件,可能会有许多个表示打开的文件描述符的file结构,但它们都指向单个inode结构。
dev_t i_rdev; 包含真正的设备编号,unsigned int imajor(struct inode *inode); unsigned int iminor(struct inode *inode);
struct cdev *i_cdev;
三、注册字符设备
内核内部使用struct cdev结构来表示字符设备。在内核调用设备的操作之前,必须分配并注册一个或者多个上述结构。在 <linux/cdev.h>中定义了这个结构及其相关操作。
1、分配cdev结构
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
2、初始化分配到了cdev结构
void cdev_init(struct cdev *cdev, struct file_operation *fops);
my_cedv->owner = THIS_MODULE;
3、告诉内核该结构的信息
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
4、从系统中删除一个字符设备
voi d cdev_del(struct cdev *dev);
scull的设备注册如下:
四、设备的相关操作实现 ——open() release() read() write()
open方法提供给驱动程序以初始化的能力,为以后的操作作准备。应完成的工作如下:
(1)检查设备特定的错误(如设备未就绪或硬件问题);
(2)如果设备是首次打开,则对其进行初始化;
(3)如有必要,更新f_op指针;
(4)分配并填写置于filp->private_data里的数据结构。
而根据scull的实际情况,他的open函数只要完成第四步(将初始化过的struct scull_dev dev的指针传递到filp->private_data里,以备后用)就好了,所以open函数很简单。但是其中用到了定义在<linux/kernel.h>中的container_of宏,源码如下:
|
其实从源码可以看出,其作用就是:通过指针ptr,获得包含ptr所指向数据(是member结构体)的type结构体的指针。即是用指针得到另外一个指针。
release方法提供释放内存,关闭设备的功能。应完成的工作如下:
(1)释放由open分配的、保存在file->private_data中的所有内容;
(2)在最后一次关闭操作时关闭设备。
由于前面定义了scull是一个全局且持久的内存区,所以他的release什么都不做。
read和write
read和write方法的主要作用就是实现内核与用户空间之间的数据拷贝。因为Linux的内核空间和用户空间隔离的,所以要实现数据拷贝就必须使用在<asm/uaccess.h>中定义的:
|
而值得一提的是以上两个函数和
|
之间的关系:通过源码可知,前者调用后者,但前者在调用前对用户空间指针进行了检查。
五、scull的内存使用模型