linux 字符设备——驱动框架(二)
字符设备驱动框架
(前言)
前面编写了一个简单的[linux字符设备驱动](https://www.cnblogs.com/gulan-zmc/p/11518002.html"linux driver 1")。接下来简单地来看一下字符设备驱动的框架。
一、重要的数据结构
在linux 字符设备里,有三个比较重要的数据结构,分别是:struct file_operations,struct file 和 struct inode
下面来简单的说明一下
1、struct file_operations
这个结构体是相当于设备的驱动了,实现了字符设备的相关操作函数
/* 只介绍部分struct file_operations结构体的成员 */
struct file_operations {
/* struct module 这个成员不是一个操作; 它是一个指向拥有这个结构体的模块指针。
* 这个成员用来在它还被使用时阻止模块被卸载。一般初始化为 THIS_MODULE,
*/
struct module *owner;
/* llseek 函数用来改变文件中当前的读/写的位置,如果这个函数指针是NULL,内核中
* 缺省的实现通过修改filp->f_pos进行移位
*/
loff_t (*llseek) (struct file *, loff_t, int);
/* read函数用来从设备中获取数据。如果这个函数指针是NULL,以 -EINVAL 失败返回给
* read 系统调用,一个非负返回值代表了成功读取的字节数。
*/
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
/* write函数用来发送数据给设备。如果这个函数指针是NULL,以 -EINVAL 失败返回给
* write 系统调用,一个非负返回值代表了成功读取的字节数。
*/
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
/* poll 函数是: poll, epoll 和 select 这三个系统调用的后端。这三个系统调用都用作查询对一个
* 或者多个文件描述符的读或写是否会阻塞。poll函数应返回一个位掩码指示是否非阻塞的读或
* 者写是可能的。并且给内核提供信息,用来使调用的的进程睡眠,直到读写操作变为可能。
* 如果一个驱动的 poll 指针为 NULL, 设备假定为不阻塞地可读可写。
*/
unsigned int (*poll) (struct file *, struct poll_table_struct *);
/* ioctl 函数 为 ioctl() 系统调用提供了发出设备特定命令的方法
* 如果一个驱动的 ioctl 指针为 NULL, 对于任何未事先定义的请
* 求返回 -ENOTTY, 系统调用返回一个错误
*/
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
/* mmap 用来请求将设备内存映射到进程的地址空间,
* 如果这个指针是 NULL, mmap 系统调用返回 -ENODEV.
*/
int (*mmap) (struct file *, struct vm_area_struct *);
/* open成员是 open 系统调用的后端,通常是对设备文件进行的第一个操作
* 如果这个指针是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.
*/
int (*open) (struct inode *, struct file *);
/* release成员是 close系统调用的后端,在文件结构被释放时引用这个操作。
* 如同open,release成员也可以为NULL。
*/
int (*release) (struct inode *, struct file *);
/* fasync这个操作用来通知设备它的 FASYNC 标志的改变(异步通知)
* 如果这个成员是 NULL, 驱动不支持异步通知。
*/
int (*fasync) (int, struct file *, int);
};
2、struct file
这个结构体描述的是打开的一个文件,每打开一个文件就会有一个struct file结构体来进行管理
/* 只介绍部分struct file结构体的成员 */
struct file {
/* 文件模式确定文件是可读的或者是可写的(或者都是),
* 通过位 FMODE_READ 和FMODE_WRITE 描述
*/
mode_t f_mode;
/* 文件当前的读写位置. 如果驱动需要知到文件中的当前位置,可以读这个值,但是不应该改变它。
* 在fops 的 read 和 write 以最后的一个参数传入,来代替直接使用f_pos成员,在fops的llseek方法中
* 通过改变f_pos 成员来改变,文件的读写位置
*/
loff_t f_pos;
/* 文件的标志。例如 O_REONLY, O_NONBLOCK, 和 O_SYNC。驱动通过检查O_NONBLOCK标志
* 来查看是否请求非阻塞操作,其他标志一般比较少用。
*/
unsigned int f_flags;
/* 和文件关联的操作*/
const struct file_operations *f_op;
/* 这个成员通常用来指向分配的数据,通常在fops->open 成员函数里面指定。
* 如果使用了这个成员必须在fops->release 成员中释放。
*/
void *private_data;
};
3、struct inode
inode 结构用来描述磁盘上一个文件。inode 结构包含大量关于文件的信息,但这个结构只有 2 个成员对于编写驱动代码有用
/* 只介绍部分struct inode结构体的成员 */
struct inode {
/* 对于代表设备文件的节点, 这个成员包含实际的设备编号. */
dev_t i_rdev;
union {
struct pipe_inode_info *i_pipe;
/* 当节点指的是一个块设备文件时,代表块设备。*/
struct block_device *i_bdev;
/* 当节点指的是一个字符设备文件时,代表字符设备。*/
struct cdev *i_cdev;
};
}
二、linux 源代码框架分析
系统调用的到内核驱动的调用过程如下(以 open 系统调用为例,其他的系统调用类似)
(系统调用的入口函数在 kernel/calls.S 这个文件里面)
sys_open
do_sys_open
fd = get_unused_fd(); //找到一个本进程没有使用的文件描述符
struct file *f = do_filp_open(dfd, tmp, flags, mode); // 构造和设置struct file 结构体
nameidata_to_filp(&nd, flags);
__dentry_open(nd->dentry, nd->mnt, flags, filp, NULL);
if (!open && f->f_op)//open是一个函数指针作为__dentry_open 的第五个参数
open = f->f_op->open; //获取fops的open成员
if (open) {
error = open(inode, f); //调用函数open函数
}
三、框架图
<念念有余>这位博主有一张比较详细的框架图,所以直接引用过来了