结构体file_operations在头文件 linux/fs.h中定义,用来存储驱动内核模块提供的对设备进行各种操作的函数的指针。该结构体的每个域都对应着驱动内核模块用来处理某个被请求的 事务的函数的地址。
举个例子,每个字符设备需要定义一个用来读取设备数据的函数。结构体 file_operations中存储着内核模块中执行这项操作的函数的地址。一下是该结构体 在内核2.6.5中看起来的样子:
struct file_operations {
struct module *owner;
loff_t(*llseek) (struct file *, loff_t, int);
ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *);
ssize_t(*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area) (struct file *, unsigned long,
unsigned long, unsigned long,
unsigned long);
};
驱动内核模块是不需要实现每个函数的。像视频卡的驱动就不需要从目录的结构 中读取数据。那么,相对应的file_operations重的项就为 NULL。
gcc还有一个方便使用这种结构体的扩展。你会在较现代的驱动内核模块中见到。新的使用这种结构体的方式如下:
struct file_operations fops = {
read: device_read,
write: device_write,
open: device_open,
release: device_release
};
同样也有C99语法的使用该结构体的方法,并且它比GNU扩展更受推荐。我使用的版本为 2.95为了方便那些想移植你的代码的人,你最好使用这种语法。它将提高代码的兼容性:
struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
这种语法很清晰,你也必须清楚的意识到没有显示声明的结构体成员都被gcc初始化为NULL。
指向结构体struct file_operations的指针通常命名为fops。
关于file结构体
每一个设备文件都代表着内核中的一个file结构体。该结构体在头文件linux/fs.h定义。注意,file结构体是内核空间的结构体, 这意味着它不会在用户程序的代码中出现。它绝对不是在glibc中定义的FILE。 FILE自己也从不在内核空间的函数中出现。它的名字确实挺让人迷惑的。它代表着一个抽象的打开的文件,但不是那种在磁盘上用结构体inode表示的文件。
指向结构体struct file的指针通常命名为filp。你同样可以看到struct file file的表达方式,但不要被它诱惑。
去看看结构体file的定义。大部分的函数入口,像结构体struct dentry没有被设备驱动模块使用,你大可忽略它们。这是因为设备驱动模块并不自己直接填充结构体file:它们只是使用在别处建立的结构体file中的数据。
注册一个设备
如同先前讨论的,字符设备通常通过在路径/dev下的设备文件进行访问。主设备号告诉你哪些驱动模块是用来操纵哪些硬件设备的。从设备号是驱动模块自己使用来区别它操纵的不同设备,当此驱动模块操纵不只一个设备时。
将内核驱动模块加载入内核意味着要向内核注册自己。这个工作是和驱动模块获得主设备号时初始化一同进行的。你可以使用头文件linux/fs.h中的函数register_chrdev来实现。
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
其中unsigned int major是你申请的主设备号,const char *name是将要在文件/proc/devices中显示的名称,struct file_operations *fops是指向你的驱动模块的file_operations表的指针。负的返回值意味着注册失败。注意注册并不需要提供从设备号。内核本身并不在意从设备号。
现在的问题是你如何申请到一个没有被使用的主设备号?最简单的方法是查看文件 Documentation/devices.txt从中挑选一个没有被使用的。这不是一劳永逸的方法因为你无法得知该主设备号在将来会被占用。最终的方法是让内核为你动态分配一个。
如果你向函数register_chrdev传递为0的主设备号,那么返回的就是动态分配的主设备号。副作用就是既然你无法得知主设备号,你就无法预先建立一个设备文件。有多种解决方法:第一种方法是新注册的驱动模块会输出自己新分配到的主设备号,所以我们可以手工建立需要的设备文件。第二种是利用文件/proc/devices新注册的驱动模块的入口,要么手工建立设备文件,要么编一个脚本去自动读取该文件并且生成设备文件。第三种是在我们的模块中,当注册成功时,使用mknod系统调用建立设备文件并且在驱动模块调用函数cleanup_module前,调用rm删除该设备文件。
注销一个设备
即使是root也不能允许随意卸载内核模块。当一个进程已经打开一个设备文件时我们卸载了该设备文件使用的内核模块,我们此时再对该文件的访问将会导致对已卸载的内核模块代码内存区的访问。幸运的话我们最多获得一个讨厌的错误警告。如果此时已经在该内存区加载了另一个模块,倒霉的你将会在内核中跳转执行意料外的代码。结果是无法预料的,而且多半是不那么令人愉快的。
平常,当你不允许某项操作时,你会得到该操作返回的错误值(一般为负值)。但对于无返回值的函数cleanup_module这是不可能的。然而,却有一个计数器跟踪着有多少进程正在使用该模块。你可以通过查看文件 /proc/modules的第三列来获取这些信息。如果该值非零,则卸载就会失败。你不需要在你模块中的函数cleanup_module中检查该计数器,因为该项检查由头文件linux/module.c中定义的系统调用sys_delete_module完成。你也不应该直接对该计数器进行操作。你应该使用在文件linux/modules.h定义的宏来增加,减小和读取该计数器:
try_module_get(THIS_MODULE): Increment the use count.
try_module_put(THIS_MODULE): Decrement the use count.
保持该计数器时刻精确是非常重要的;如果你丢失了正确的计数,你将无法卸载模块,那就只有重启了。不过这种情况在今后编写内核模块时也是无法避免的。
chardev.c
下面的代码示范了一个叫做chardev的字符设备。你可以用cat输出该设备文件的内容(或用别的程序打开它)时,驱动模块会将该设备文件被读取的次数显示。目前对设备文件的写操作还不被支持(像echo "hi" > /dev/hello),但会捕捉这些操作并且告诉用户该操作不被支持。不要担心我们对读入缓冲区的数据做了什么;我们什么都没做。我们只是读入数据并输出我们已经接收到的数据的信息。
为多个版本的内核编写内核模块
系统调用,也就是内核提供给进程的接口,基本上是保持不变的。也许会添入新的系统调用,但那些已有的不会被改动。这对于向下兼容是非常重要的。在多数情况下,设备文件是保持不变的。但内核的内部在不同版本之间还是会有区别的。
Linux内核分为稳定版本(版本号中间为偶数)和试验版本(版本号中间为奇数)。试验版本中可以试验各种各样的新而酷的主意,有些会被证实是一个错误,有些在下一版中会被完善。总之,你不能依赖这些版本中的接口(这也是我不在本文档中支持它们的原因, 它们更新的太快了)。在稳定版本中,我们可以期望接口保持一致,除了那些修改代码中错误的版本。
如果你要支持多版本的内核,你需要编写为不同内核编译的代码树。可以通过比较宏 LINUX_VERSION_CODE和宏KERNEL_VERSION在版本号为a.b.c 的内核中,该宏的值应该为 2^16×a+2^8×b+c
在上一个版本中该文档还保留了详细的如何向后兼容老内核的介绍,现在我们决定打破这个传统。 对为老内核编写驱动感兴趣的读者应该参考对应版本的LKMPG,也就是说,2.4.x版本的LKMPG对应 2.4.x的内核,2.6.x版本的LKMPG对应2.6.x的内核。
分享到:
相关推荐
"file_operations 结构体解析" 文件操作结构体(file_operations)是 Linux 驱动程序中最重要的涉及 3 个重要的内核数据结构之一,分别为 file_operations、file 和 inode。在 Linux 中,inode 结构用于表示文件,...
在编程时,驱动开发人员需要实现file_operations结构体中声明的各个操作函数,并填充相应的函数指针。通过这种方式,内核知道如何调用驱动程序提供的服务,实现对设备的操作。 Linux设备驱动程序的编写通常涉及内核...
在Linux操作系统中,文件操作是通过一种结构化的接口来实现的,这个接口就是`file_operations`结构体。它是内核中的核心组件之一,用于定义文件对象的各种操作,如读、写、打开、关闭等。本篇文章将深入探讨`file_...
`file_operations`结构体是Linux内核中用于定义文件操作接口的关键元素,它在设备驱动程序和用户空间应用程序之间架起了一座桥梁。本教程将深入探讨`file_operations`在Linux嵌入式环境中的应用,以及如何通过它实现...
在Linux内核中,每个设备驱动都会关联一个file_operations结构体,这个结构体包含了处理特定设备文件操作的函数指针。当用户空间程序通过标准的文件I/O操作与设备交互时,实际上是在调用这些预定义的设备驱动函数。 ...
- **struct file_operations结构体**:解释在打开字符设备节点时,内核中对应的struct file结构体,以及如何关联到驱动程序提供的struct file_operations结构体。 #### 2.3 驱动程序编写步骤 - **主设备号与file_...
触摸屏驱动程序是基于Linux系统的,使用file_operations结构体来定义触摸屏驱动程序的操作函数,并使用TS_DEV结构体来记录触摸屏运行的各种状态。TS_RET结构体用来存储触摸屏的返回值,而wait_queue_head_t结构体...
- 定义自己的file_operations结构体,这是驱动程序的核心,其中包含了指向驱动程序提供的各种操作函数的指针,如open、read、write、release等。 - 实现对应的驱动程序操作函数,如open、read、write等,这些函数会...
cdev数据结构是Linux内核中用于表示字符设备的关键结构,它包含了设备的主次设备号以及file_operations结构体的指针。file_operations结构体定义了一系列操作函数指针,如read、write、ioctl等,这些函数指针对应着...
#### file_operations结构体概述 在嵌入式Linux系统中,字符驱动是极其重要的组成部分,它们负责与硬件交互并提供给用户空间应用程序访问硬件资源的能力。file_operations结构体则是连接用户空间与内核空间的关键...
### 文件结构体 (`struct file`) 与 索引节点结构体 (`struct inode`) #### 一、`struct file` 结构体 `struct file` 是 Linux 内核中的一个重要数据结构,它用来描述内核空间中每一个打开的文件。这个结构体在 `...
2. **自定义file_operations结构体**:另一种方法是实现自己的file_operations结构体,并指定相应的读写操作函数。此外还需要实现seq_operations结构体中的函数(start, next, stop, show),以支持更复杂的序列迭代...
// 对file_operations结构体进行赋值 } return 0; } ``` 这里首先创建了一个名为 `SCSI_TARGET` 的 `proc` 目录,然后在该目录下创建了一个名为 `TARGET_PROC` 的文件,并将该文件的 `file_operations` 结构体...
数据结构方面,file_operations结构体定义了驱动提供的操作集,而file结构体代表一个打开的文件,由内核在open时创建,并在后续的设备交互中使用。此外,可能还会用到dev_struct(设备结构体)来表示硬件设备的基本...
文件操作头文件位于 Linux-2.6.29/include/linux 目录下的 fs.h 文件中,定义了 struct file_operations 结构体,包括 llseek、read、write、aio_read、aio_write 等函数。 5. 其他的头文件 在 Linux 系统中,还有...
混杂设备的原理是内核维护一个misc设备链表,当设备被访问时,内核会根据次设备号来查找对应的misc设备,并调用其file_operations结构体中注册的函数进行操作。开发者编写misc设备驱动主要就是填充file_operations...
### Linux 文件系统结构体:file_operations 在 Linux 内核中,`file_operations` 结构体扮演着极其重要的角色,它定义了一组操作方法,这些方法用于与文件系统中的文件进行交互。`file_operations` 结构体是 Linux...
Linux 内核使用一个 file 结构来标识设备文件,并通过一个称为 file_operations 的结构体来组织对设备操作的具体实现函数。 file_operations 结构体中定义了多个函数,例如 open、read、write、close 等,这些函数...
Linux 字符设备驱动结构提供了一种通用的接口 для character 设备的驱动程序,通过 cdev 结构体和 file_operations 结构体,设备驱动程序可以提供给虚拟文件系统的接口函数,实现字符设备的注册和注销。