[转] 结构体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),但会捕捉这些操作并且告诉用户该操作不被支持。不要担心我们对读入缓冲区的数据做了什么;我们什么都没做。我们只是读入数据并输出我们已经接收到的数据的信息。

  1. Example 4-1. chardev.c
  2. /*
  3. *  chardev.c: Creates a read-only char device that says how many times
  4. *  you‘ve read from the dev file
  5. */
  6. #include <linux/kernel.h>
  7. #include <linux/module.h>
  8. #include <linux/fs.h>
  9. #include <asm/uaccess.h> /* for put_user */
  10. /*
  11. *  Prototypes - this would normally go in a .h file
  12. */
  13. int init_module(void);
  14. void cleanup_module(void);
  15. static int device_open(struct inode *, struct file *);
  16. static int device_release(struct inode *, struct file *);
  17. static ssize_t device_read(struct file *, char *, size_t, loff_t *);
  18. static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
  19. #define SUCCESS 0
  20. #define DEVICE_NAME "chardev" /* Dev name as it appears in /proc/devices   */
  21. #define BUF_LEN 80  /* Max length of the message from the device */
  22. /*
  23. * Global variables are declared as static, so are global within the file.
  24. */
  25. static int Major;  /* Major number assigned to our device driver */
  26. static int Device_Open = 0; /* Is device open?
  27. * Used to prevent multiple access to device */
  28. static char msg[BUF_LEN]; /* The msg the device will give when asked */
  29. static char *msg_Ptr;
  30. static struct file_operations fops = {
  31. .read = device_read,
  32. .write = device_write,
  33. .open = device_open,
  34. .release = device_release
  35. };
  36. /*
  37. * Functions
  38. */
  39. int init_module(void)
  40. {
  41. Major = register_chrdev(0, DEVICE_NAME, &fops);
  42. if (Major < 0) {
  43. printk("Registering the character device failed with %d/n",
  44. Major);
  45. return Major;
  46. }
  47. printk("<1>I was assigned major number %d.  To talk to/n", Major);
  48. printk("<1>the driver, create a dev file with/n");
  49. printk("‘mknod /dev/hello c %d 0‘./n", Major);
  50. printk("<1>Try various minor numbers.  Try to cat and echo to/n");
  51. printk("the device file./n");
  52. printk("<1>Remove the device file and module when done./n");
  53. return 0;
  54. }
  55. void cleanup_module(void)
  56. {
  57. /*
  58. * Unregister the device
  59. */
  60. int ret = unregister_chrdev(Major, DEVICE_NAME);
  61. if (ret < 0)
  62. printk("Error in unregister_chrdev: %d/n", ret);
  63. }
  64. /*
  65. * Methods
  66. */
  67. /*
  68. * Called when a process tries to open the device file, like
  69. * "cat /dev/mycharfile"
  70. */
  71. static int device_open(struct inode *inode, struct file *file)
  72. {
  73. static int counter = 0;
  74. if (Device_Open)
  75. return -EBUSY;
  76. Device_Open++;
  77. sprintf(msg, "I already told you %d times Hello world!/n", counter++);
  78. msg_Ptr = msg;
  79. try_module_get(THIS_MODULE);
  80. return SUCCESS;
  81. }
  82. /*
  83. * Called when a process closes the device file.
  84. */
  85. static int device_release(struct inode *inode, struct file *file)
  86. {
  87. Device_Open--;  /* We‘re now ready for our next caller */
  88. /*
  89. * Decrement the usage count, or else once you opened the file, you‘ll
  90. * never get get rid of the module.
  91. */
  92. module_put(THIS_MODULE);
  93. return 0;
  94. }
  95. /*
  96. * Called when a process, which already opened the dev file, attempts to
  97. * read from it.
  98. */
  99. static ssize_t device_read(struct file *filp, /* see include/linux/fs.h   */
  100. char *buffer, /* buffer to fill with data */
  101. size_t length, /* length of the buffer     */
  102. loff_t * offset)
  103. {
  104. /*
  105. * Number of bytes actually written to the buffer
  106. */
  107. int bytes_read = 0;
  108. /*
  109. * If we‘re at the end of the message,
  110. * return 0 signifying end of file
  111. */
  112. if (*msg_Ptr == 0)
  113. return 0;
  114. /*
  115. * Actually put the data into the buffer
  116. */
  117. while (length && *msg_Ptr) {
  118. /*
  119. * The buffer is in the user data segment, not the kernel
  120. * segment so "*" assignment won‘t work.  We have to use
  121. * put_user which copies data from the kernel data segment to
  122. * the user data segment.
  123. */
  124. put_user(*(msg_Ptr++), buffer++);
  125. length--;
  126. bytes_read++;
  127. }
  128. /*
  129. * Most read functions return the number of bytes put into the buffer
  130. */
  131. return bytes_read;
  132. }
  133. /*
  134. * Called when a process writes to dev file: echo "hi" > /dev/hello
  135. */
  136. static ssize_t
  137. device_write(struct file *filp, const char *buff, size_t len, loff_t * off)
  138. {
  139. printk("<1>Sorry, this operation isn‘t supported./n");
  140. return -EINVAL;
  141. }

为多个版本的内核编写内核模块

系统调用,也就是内核提供给进程的接口,基本上是保持不变的。也许会添入新的系统调用,但那些已有的不会被改动。这对于向下兼容是非常重要的。在多数情况下,设备文件是保持不变的。但内核的内部在不同版本之间还是会有区别的。

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的内核。

时间: 2024-10-25 06:13:21

[转] 结构体file_operations的相关文章

使用结构体 file_operations封装驱动设备的操作

最近学习到了Linux驱动章节的课程,对设备的对应驱动的注册有些困惑,看了下发现是把设备的所有操作方法封装到结构体 file_operations 中,这个结构体为所有的设备文件都提供了统一的操作函数接口.然后把这个结构体连同设备的主设备号.名字(没啥用)一起,通过函数 register_chrdev(0, "first_drv", &first_drv_fops) 注册驱动模块到内核的字符设备数组chrdev的第xx项,具体注册和使用方法和裸机LCD及LCD控制器的一模一样!

cdev成员结构体file_operations文件操作结构的分析

struct file_operations{ struct module *owner; // 指向拥有该结构的模块的指针,避免正在操作时被卸载,一般为初始化为THIS_MODULES loff_t (*llseek) (struct file *, loff_t, int); // llseek用来修改文件当前的读写位置,返回新位置 // loff_t为一个”长偏移量”.当此函数指针为空,seek调用将会以不可预期的方式修改file结构中的位置计数器. ssize_t (*read) (st

file_operations结构体解析 1

注:学了这么长时间了,还没有好好看看 file_operations机构体,这其中还有很多的东西,当你学着学着的时候,就会用到这里面的一些系统调用对应的函数了,我在网上搜索之后,记录如下,一边将来查看..... 前沿:这些东西估计对你有用 linux驱动程序中最重要的涉及3个重要的内核数据结构,分别为file_operations,file和inode. 在linux中inode结构用于表示文件,而file结构则表示打开的文件的描述,因为对于单个文件而言可能会有许多个表示打开的文件的描述符,因而

Linux字符设备中的两个重要结构体(file、inode)

对于Linux系统中,一般字符设备和驱动之间的函数调用关系如下图所示 上图描述了用户空间应用程序通过系统调用来调用程序的过程.一般而言在驱动程序的设计中,会关系 struct file 和 struct inode 这两个结构体. 用户空间使用open()系统调用函数打开一个字符设备时( int fd = open("dev/demo", O_RDWR) )大致有以下过程: 在虚拟文件系统VFS中的查找对应与字符设备对应 struct inode节点 遍历字符设备列表(chardevs

Linux--struct file结构体

struct file(file结构体): struct file结构体定义在include/linux/fs.h中定义.文件结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的 struct file. 它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数.在文件的所有实例都关闭后,内核释放这个数据结构.在内核创建和驱动源码中, struct file的指针通常被命名为file或filp.其有两个非常重要的字段:文件描述符和缓冲区.  文件描述符fd: fd只是一个

驱动学习之register_chrdev_region函数和cdev结构体

1:register_chrdev_region int register_chrdev_region(dev_t from, unsigned count, const char *name) {     struct char_device_struct *cd;     dev_t to = from + count;     dev_t n, next;     for (n = from; n < to; n = next) {         next = MKDEV(MAJOR(n

文件系统及其相关结构体

fd指向打开的文件描述符列表,开始的时候指向fd_array,当大小超过max_fds时,重新分配地址 file结构体代表一个打开的文件 f_op其中包含着与文件关联的操作 进程内核栈与进程描述符之间的关系 task_struct结构体注释详解可参考http://www.educity.cn/linux/518072.html dentry的中文名称是目录项,是Linux文件系统中某个索引节点(inode)的链接.这个索引节点可以是文件,也可以是目录. struct dentry {      

inode结构体

inode分为内存中的inode和文件系统中的inode,为了避免混淆,我们称前者为VFS inode, 而后者以EXT2为代表,我们称为Ext2 inod.这里说明的是VFS inode. 重要成员: 1. struct cdev *i_cdev; 若是字符设备,为其对应的cdev结构体指针. 2. struct block_device *i_bdev; 若是块设备,为其对应的block_device结构体指针 3. dev_t i_rdev; 若是设备文件,此成员记录设备的设备号 1 st

cdev结构体及其相关函数

一.在Linux2.6内核中一个字符设备用cdev结构来描述,其定义如下: 1 struct cdev { 2 struct kobject kobj; 3 struct module *owner; //所属模块 4 const struct file_operations *ops; //文件操作结构,在写驱动时,其结构体内的大部分函数要被实现 5 struct list_head list; 6 dev_t dev; //设备号,int 类型,高12位为主设备号,低20位为次设备号 7 u