[转] 结构体file_operations
原文地址: http://www.cnblogs.com/sunyubo/archive/2010/12/22/2282079.html
结构体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),但会捕捉这些操作并且告诉用户该操作不被支持。不要担心我们对读入缓冲区的数据做了什么;我们什么都没做。我们只是读入数据并输出我们已经接收到的数据的信息。
- Example 4-1. chardev.c
- /*
- * chardev.c: Creates a read-only char device that says how many times
- * you've read from the dev file
- */
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/fs.h>
- #include <asm/uaccess.h> /* for put_user */
- /*
- * Prototypes - this would normally go in a .h file
- */
- int init_module(void);
- void cleanup_module(void);
- static int device_open(struct inode *, struct file *);
- static int device_release(struct inode *, struct file *);
- static ssize_t device_read(struct file *, char *, size_t, loff_t *);
- static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
- #define SUCCESS 0
- #define DEVICE_NAME "chardev" /* Dev name as it appears in /proc/devices */
- #define BUF_LEN 80 /* Max length of the message from the device */
- /*
- * Global variables are declared as static, so are global within the file.
- */
- static int Major; /* Major number assigned to our device driver */
- static int Device_Open = 0; /* Is device open?
- * Used to prevent multiple access to device */
- static char msg[BUF_LEN]; /* The msg the device will give when asked */
- static char *msg_Ptr;
- static struct file_operations fops = {
- .read = device_read,
- .write = device_write,
- .open = device_open,
- .release = device_release
- };
- /*
- * Functions
- */
- int init_module(void)
- {
- Major = register_chrdev(0, DEVICE_NAME, &fops);
- if (Major < 0) {
- printk("Registering the character device failed with %d/n",
- Major);
- return Major;
- }
- printk("<1>I was assigned major number %d. To talk to/n", Major);
- printk("<1>the driver, create a dev file with/n");
- printk("'mknod /dev/hello c %d 0'./n", Major);
- printk("<1>Try various minor numbers. Try to cat and echo to/n");
- printk("the device file./n");
- printk("<1>Remove the device file and module when done./n");
- return 0;
- }
- void cleanup_module(void)
- {
- /*
- * Unregister the device
- */
- int ret = unregister_chrdev(Major, DEVICE_NAME);
- if (ret < 0)
- printk("Error in unregister_chrdev: %d/n", ret);
- }
- /*
- * Methods
- */
- /*
- * Called when a process tries to open the device file, like
- * "cat /dev/mycharfile"
- */
- static int device_open(struct inode *inode, struct file *file)
- {
- static int counter = 0;
- if (Device_Open)
- return -EBUSY;
- Device_Open++;
- sprintf(msg, "I already told you %d times Hello world!/n", counter++);
- msg_Ptr = msg;
- try_module_get(THIS_MODULE);
- return SUCCESS;
- }
- /*
- * Called when a process closes the device file.
- */
- static int device_release(struct inode *inode, struct file *file)
- {
- Device_Open--; /* We're now ready for our next caller */
- /*
- * Decrement the usage count, or else once you opened the file, you'll
- * never get get rid of the module.
- */
- module_put(THIS_MODULE);
- return 0;
- }
- /*
- * Called when a process, which already opened the dev file, attempts to
- * read from it.
- */
- static ssize_t device_read(struct file *filp, /* see include/linux/fs.h */
- char *buffer, /* buffer to fill with data */
- size_t length, /* length of the buffer */
- loff_t * offset)
- {
- /*
- * Number of bytes actually written to the buffer
- */
- int bytes_read = 0;
- /*
- * If we're at the end of the message,
- * return 0 signifying end of file
- */
- if (*msg_Ptr == 0)
- return 0;
- /*
- * Actually put the data into the buffer
- */
- while (length && *msg_Ptr) {
- /*
- * The buffer is in the user data segment, not the kernel
- * segment so "*" assignment won't work. We have to use
- * put_user which copies data from the kernel data segment to
- * the user data segment.
- */
- put_user(*(msg_Ptr++), buffer++);
- length--;
- bytes_read++;
- }
- /*
- * Most read functions return the number of bytes put into the buffer
- */
- return bytes_read;
- }
- /*
- * Called when a process writes to dev file: echo "hi" > /dev/hello
- */
- static ssize_t
- device_write(struct file *filp, const char *buff, size_t len, loff_t * off)
- {
- printk("<1>Sorry, this operation isn't supported./n");
- return -EINVAL;
- }
为多个版本的内核编写内核模块
系统调用,也就是内核提供给进程的接口,基本上是保持不变的。也许会添入新的系统调用,但那些已有的不会被改动。这对于向下兼容是非常重要的。在多数情况下,设备文件是保持不变的。但内核的内部在不同版本之间还是会有区别的。
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的更多相关文章
- cdev成员结构体file_operations文件操作结构的分析
struct file_operations{ struct module *owner; // 指向拥有该结构的模块的指针,避免正在操作时被卸载,一般为初始化为THIS_MODULES loff_t ...
- file_operations结构体解析 1
注:学了这么长时间了,还没有好好看看 file_operations机构体,这其中还有很多的东西,当你学着学着的时候,就会用到这里面的一些系统调用对应的函数了,我在网上搜索之后,记录如下,一边将来查看 ...
- Linux字符设备驱动结构(一)--cdev结构体、设备号相关知识机械【转】
本文转载自:http://blog.csdn.net/zqixiao_09/article/details/50839042 一.字符设备基础知识 1.设备驱动分类 linux系统将设备分为3类:字符 ...
- ARM-Linux S5PV210 UART驱动(3)----串口核心层、关键结构体、接口关系
尽管一个特定的UART设备驱动完全可以按照tty驱动的设计方法来设计,即定义tty_driver并实现tty_operations其中的成员函数,但是Linux已经在文件serial_core.c中实 ...
- Linux_Struct file()结构体
struct file结构体定义在/linux/include/linux/fs.h(Linux 2.6.11内核)中,其原型是:struct file { /* * f ...
- Linux字符设备中的两个重要结构体(file、inode)
对于Linux系统中,一般字符设备和驱动之间的函数调用关系如下图所示 上图描述了用户空间应用程序通过系统调用来调用程序的过程.一般而言在驱动程序的设计中,会关系 struct file 和 struc ...
- 字符设备驱动1:新的方式添加cdev + 在open函数中将文件私有数据指向设备结构体
本例中,驱动入口处,使用cdev_add添加驱动,这点也可与字符设备驱动0:一个简单但完整的字符设备驱动程序对比一下. 另外主要讲xx_open实现文件私有数据指向设备结构体. 引子: 偶然看到,在j ...
- Linux--struct file结构体
struct file(file结构体): struct file结构体定义在include/linux/fs.h中定义.文件结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的 ...
- 2018.5.2 file结构体
f_flags,File Status Flag f_pos,表示当前读写位置 f_count,表示引用计数(Reference Count): dup.fork等系统调用会导致多个文件描述符指向同一 ...
随机推荐
- 基于Ubuntu14.04下Suricata(一款高性能的网络IDS、IPS和网络安全监控引擎)的搭建(图文详解)(博主推荐)
为什么,要写这篇论文? 是因为,目前科研的我,正值研三,致力于网络安全.大数据.机器学习研究领域! 论文方向的需要,同时不局限于真实物理环境机器实验室的攻防环境.也不局限于真实物理机器环境实验室的大数 ...
- WPF学习09:数据绑定之 Binding to List Data
从WPF学习03:Element Binding我们可以实现控件属性与控件属性的绑定. 从WPF学习07:MVVM 预备知识之数据绑定 我们可以实现控件属性与自定义对象属性的绑定. 而以上两个功能在实 ...
- 从单机到2000万 QPS 并发的 Redis 高性能缓存实践之路
1.引言 知乎存储平台团队基于开源Redis 组件打造的知乎 Redis 平台,经过不断的研发迭代,目前已经形成了一整套完整自动化运维服务体系,提供很多强大的功能.本文作者陈鹏是该系统的负责人,本次文 ...
- json三层解析(数组解析)
里面多了数组,所以用到了JOSNArray package com.xykj.weather; import java.io.BufferedReader; import java.io.IOExce ...
- fatal: Authentication failed for 问题解决
执行以下code git config --system --unset credential.helper 参考地址: https://www.jianshu.com/p/8a7f257e07b8
- 迅为7寸工业平板电脑|人机界面|工业触摸屏|工控机|HMI|工业显示器
型号:iTOP-HMI070-C 7寸工业平板电脑特点: 1.iTOP-HMI070-C(CAN) 7寸工业触摸屏,CAN总线型触摸屏,配有2组独立的串口和一路CAN总线口: 2.串口都支持各种PLC ...
- pringBoot Controller接收参数的几种常用方式
第一类:请求路径参数1.@PathVariable 获取路径参数.即url/{id}这种形式.2.@RequestParam 获取查询参数.即url?name=这种形式例子 GEThttp://loc ...
- CAD交互绘制文字(com接口)
在cad设计时,需要绘制文字,用户可以设置设置绘制文字的高度等属性. 主要用到函数说明: _DMxDrawX::DrawText 绘制一个单行文字.详细说明如下: 参数 说明 DOUBLE dPosX ...
- DEBUG无法进入断点解决方法
18/08/17 任务栏:Tools->Options->Debugging->General->Require source files to exactly match t ...
- JAVA基础——内存流
掌握内存操作流 输入和输出都是从文件中来的,当然,也可将输出的位置设置在内存上,这就需要ByteArrayInputStream和ByteArrayOutputStream ByteArrayInpu ...