By: 潘云登
Date: 2009-5-25
Email: intrepyd@gmail.com
Homepage: http://blog.csdn.net/intrepyd
Copyright: 该文章版权由潘云登所有。可在非商业目的下任意传播和复制。
对于商业目的下对本文的任何行为需经作者同意。
写在前面
1. 本文内容对应《linux设备驱动程序》第三章。
2. 希望本文对您有所帮助,也欢迎您给我提意见和建议。
字符设备的生命周期
1. 分配设备编号
在内核中,dev_t类型(32位)用来保存设备编号,包括主设备号(12位)和次设备号(20位)。主设备号标识设备对应的驱动程序。次设备号由内核和驱动程序使用,用于正确确定设备文件所指的设备。主设备号和设备名称的对应关系记录在/proc/devices中。
MAJOR(dev_t dev); MINOR(dev_t dev); MKDEV(int major, int minor); |
分配设备编号的方式有两种:一是根据Documentation/devices.txt文件,选取尚未分配的设备号,进行静态分配。
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); |
2. 实例化设备对象
即为描述设备的结构分配内存并初始化。在内核中,cdev是表示字符设备的内部结构,包含字符设备的共有信息,如设备号dev_t和文件操作file_operations。具体设备相关的个性信息可以存放在自定义的设备结构中,如scull_dev,其中包含cdev结构。
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 */ }; |
3. 初始化并注册设备
在内核调用设备的操作之前,必须将设备cdev与文件操作关联起来,并向内核注册设备结构cdev。注意,内核只认识字符设备的共有信息cdev,而不关心与具体设备相关的信息,如scull_dev。
void cdev_init(struct cdev *cdev, struct file_operations *fops); int cdev_add(struct cdev *dev, dev_t num, unsigned int count); |
注册完成后,设备的操作就会被内核调用。在此之前,必须完全准备好处理设备上的操作。
4. 创建设备文件
通过手工执行mknod命令,或者通过mknod系统调用创建设备文件。在内核中,由inode结构表示,其中包含了设备编号dev_t和字符设备结构cdev。将设备虚拟化为文件,可以统一应用程序访问设备的接口,保持系统调用的精简。
设备号dev_t,设备cdev以及设备文件inode之间是一一对应关系。
5. 操作字符设备
应用程序通过系统调用,执行驱动程序定义的文件操作,从而完成对设备的访问。这些操作并不直接访问设备文件inode,而是访问一个称为文件描述符的file结构。该结构在打开设备文件时创建。可以有多个文件描述符file对应同一个设备文件inode,从而实现多个进程对单个设备的共享。
int (*open) (struct inode *, struct file *); int (*release) (struct inode *, struct file *); ssize_t (*read) (struct file *, char _ _user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char _ _user *, size_t, loff_t *); |
因为在对设备进行读写操作时没有传入inode,所以需要在打开设备时,即open操作中,将cdev的指针或者将包含设备个性信息的自定义结构指针赋给文件描述符file中的private_data字段。当然,也可以通过file->dentry->inode->cdev访问。
int scull_open(struct inode *inode, struct file *filp) { struct scull_dev *dev; /* device information */
dev = container_of(inode->i_cdev, struct scull_dev, cdev); filp->private_data = dev; /* for other methods */ ……. } |
6. 卸载字符设备
当字符设备的生命走到尽头时,应当以与创建设备相反的顺序,先向内核注销设备结构,然后释放设备编号。
void cdev_del(struct cdev *dev); void unregister_chrdev_region(dev_t first, unsigned int count); |
主要数据结构的关系