转载声明:http://blog.sina.com.cn/s/blog_72605ba50102xk9r.html
1.简介
一般而言在驱动程序的设计中,会关系 struct file 和 struct inode 这两个结构体。
用户空间使用open()系统调用函数打开一个字符设备时( int fd = open(“dev/demo”, O_RDWR) )大致有以下过程:
- 1.在虚拟文件系统VFS中的查找对应与字符设备对应 struct inode节点;
2.遍历字符设备列表(chardevs数组),根据inode节点中的 cdev_t设备号找到cdev对象;
3.创建struct file对象(系统采用一个数组来管理一个进程中的多个被打开的设备,每个文件秒速符作为数组下标标识了一个设备对象);
4.初始化struct file对象,将 struct file对象中的 file_operations成员指向 struct cdev对象中的 file_operations成员(file->fops = cdev->fops);
5.回调file->fops->open函数;
2.inode结构体
VFS inode 包含文件访问权限、属主、组、大小、生成时间、访问时间、最后修改时间等信息。它是Linux 管理文件系统的最基本单位,也是文件系统连接任何子目录、文件的桥梁。
- 内核使用inode结构体在内核内部表示一个文件。因此,它与表示一个已经打开的文件描述符的结构体(即file文件结构)是不同的,我们可以使用多个file文件结构表示同一个文件的多个文件描述符,但此时,所有的这些file文件结构全部都必须只能指向一个inode结构体。 inode结构体包含了一大堆文件相关的信息,但是就针对驱动代码来说,我们只要关心其中的两个域即可:
dev_t i_rdev; 表示设备文件的结点,这个域实际上包含了设备号。
struct cdev i_cdev; struct cdev是内核的一个内部结构,它是用来表示字符设备的,当inode结点指向一个字符设备文件时,此域为一个指向inode结构的指针。
3.file 文件结构体
-
在设备驱动中,这也是个非常重要的数据结构,必须要注意一点,这里的file与用户空间程序中的FILE指针是不同的,用户空间FILE是定义在C库中,从来不会出现在内核中。而struct
file,却是内核当中的数据结构,因此,它也不会出现在用户层程序中。file结构体指示一个已经打开的文件(设备对应于设备文件),其实系统中的每个打开的文件在内核空间都有一个相应的struct file结构体,它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数,直至文件被关闭。如果文件被关闭,内核就会释放相应的数据结构。
在内核源码中,struct file要么表示为file,或者为filp(意指“file pointer”), 注意区分一点,file指的是struct file本身,而filp是指向这个结构体的指针。
下面是几个重要成员:
- 1、fmode_t f_mode;
此文件模式通过 FMODE_READ , FMODE_WRITE识别了文件为可读的,可写的,或者是二者。在open或ioctl函数中可能需要检查此域以确认文件的读/写权限,你不必直接去检测读或写权限,因为在进行octl等操作时内核本身就需要对其权限进行检测。 - 2、 loff_t f_pos;
当前读写文件的位置。为64位。如果想知道当前文件当前位置在哪,驱动可以读取这个值而不会改变其位置。对read,write来说,当其接收到一个loff_t型指针作为其最后一个参数时,他们的读写操作便作更新文件的位置,而不需要直接执行filp ->f_pos操作。 - 3、unsigned int f_flags;
文件标志,如 O_RDONLY , O_NONBLOCK 以及 O_SYNC 。在驱动中还可以检O_NONBLOCK标志查看是否有非阻塞请求。其它的标志较少使用。特别地注意的是,读写权限的检查是使用f_mode而不是f_flog。所有的标量定义在头文件中 - 4、struct file_operations *f_op;
与文件相关的各种操作。当文件需要迅速进行各种操作时,内核分配这个指针作为它实现文件打开,读,写等功能的一部分。filp->f_op 其值从未被内核保存作为下次的引用,即你可以改变与文件相关的各种操作,这种方式效率非常高。 - 5、 void *private_data;
在驱动调用open方法之前,open系统调用设置此指针为NULL值。你可以很自由的将其做为你自己需要的一些数据域或者不管它,如,你可以将其指向一个分配好的数据,但是你必须记得在file struct被内核销毁之前在release方法中释放这些数据的内存空间。private_data用于在系统调用期间保存各种状态信息是非常有用的。
4.chardevs 数组
前面对用户层open()的分析提到,通过数据结构 struct inode{…} 中的 i_cdev 成员可以找到cdev,而所有的字符设备都在 chrdevs 数组中,chrdevs具体是什么样的呢
下面先看一下 chrdevs 的定义:
#define CHRDEV_MAJOR_HASH_SIZE 255
static DEFINE_MUTEX(chrdevs_lock);
static struct char_device_struct {
struct char_device_struct *next; // 结构体指针
unsigned int major; // 主设备号
unsigned int baseminor; // 次设备起始号
int minorct; // 次备号个数
char name[64];
struct cdev *cdev;
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];// 只能挂255个字符主设备
-
可以看到全局数组 chrdevs 包含了255(CHRDEV_MAJOR_HASH_SIZE 的值)个 struct
char_device_struct的元素,每一个对应一个相应的主设备号。如果分配了一个设备号,就会创建一个 struct char_device_struct 的对象,并将其添加到 chrdevs 中;这样,通过chrdevs数组,我们就可以知道分配了哪些设备号。
相关函数,
register_chrdev_region( ) 分配指定的设备号范围
alloc_chrdev_region( ) 动态分配设备范围
他们都主要是通过调用函数 __register_chrdev_region() 来实现的;要注意,这两个函数仅仅是注册设备号!如果要和cdev关联起来,还要调用cdev_add()。
register_chrdev( )申请指定的设备号,并且将其注册到字符设备驱动模型中.
它所做的事情为:
- 注册设备号, 通过调用 __register_chrdev_region() 来实现 分配一个cdev,
- 通过调用cdev_alloc() 来实现 将cdev添加到驱动模型中, 这一步将设备号和驱动关联了起来.
- 通过调用 cdev_add() 来实现 将第一步中创建的 struct char_device_struct 对象的 cdev 指向第二步中分配的cdev.由于register_chrdev()是老的接口,这一步在新的接口中并不需要。
5.cdev 结构体
使用 struct cdev 来描述一个字符设备
struct cdev {
struct kobject kobj; //内嵌的内核对象.
struct module *owner; //该字符设备所在的内核模块(所有者)的对象指针,一般为THIS_MODULE主要用于模块计数
const struct file_operations *ops; //该结构描述了字符设备所能实现的操作集(打开、关闭、读/写、...),是极为关键的一个结构体
struct list_head list; //用来将已经向内核注册的所有字符设备形成链表
dev_t dev; //字符设备的设备号,由主设备号和次设备号构成(如果是一次申请多个设备号,此设备号为第一个)
unsigned int count; //隶属于同一主设备号的次设备号的个数
...
};
6.文件系统中对字符设备文件的访问
-
下面看一下上层应用open() 调用系统调用函数的过程
对于一个字符设备文件, 其inode->i_cdev 指向字符驱动对象cdev, 如果i_cdev为 NULL ,则说明该设备文件没有被打开.
由于多个设备可以共用同一个驱动程序.所以,通过字符设备的inode 中的i_devices 和 cdev中的list组成一个链表
系统调用open打开一个字符设备的时候, 通过一系列调用,最终会执行到 chrdev_open
(最终是通过调用到def_chr_fops中的.open, 而def_chr_fops.open = chrdev_open. 这一系列的调用过程,本文暂不讨论)
int chrdev_open(struct inode * inode, struct file * filp)
chrdev_open()所做的事情可以概括如下:
1. 根据设备号(inode->i_rdev), 在字符设备驱动模型中查找对应的驱动程序, 这通过kobj_lookup() 来实现, kobj_lookup()会返回对应驱动程序cdev的kobject.
2. 设置inode->i_cdev , 指向找到的cdev.
3. 将inode添加到cdev->list 的链表中.
4. 使用cdev的ops 设置file对象的f_op
5. 如果ops中定义了open方法,则调用该open方法
6. 返回
执行完 chrdev_open()之后,file对象的f_op指向cdev的ops,因而之后对设备进行的read, write等操作,就会执行cdev的相应操作