`
gmd03gmd
  • 浏览: 18883 次
最近访客 更多访客>>
社区版块
存档分类
最新评论

关于file_operations结构体

 
阅读更多

关于file_operations结构体
2010年12月22日
  结构体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  #include  #include  #include  /* 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 I was assigned major number %d. To talk to\n", Major); printk("the driver, create a dev file with\n"); printk("'mknod /dev/hello c %d 0'.\n", Major); printk("Try various minor numbers. Try to cat and echo to\n"); printk("the device file.\n"); printk("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 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("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结构体解析.doc

    file_operations结构体解析

    linux文件系统结构体

    这是关于linux的文件系统的file_operations结构体描述

    字符设备驱动程序的基本步骤

    详细介绍字符设备驱动程序的基本步骤 ,并结合实例代码讲解,定义并初始化file_operations结构体,字符设备的注册,移除字符设备,.注销设备号.

    【嵌入式Linux驱动开发】五、LED驱动完善 – 面向对象·上下分层·左右分离

    字符设备驱动程序抽象出一个 file_operations 结构体; 我们写的程序针对硬件部分抽象出 led_operations 结构体。 上下分层, 比如我们前面写的 LED 驱动程序就分为 2 层: ① 上层实现硬件无关的操作,比如注册字符...

    Linux下驱动的应用

    .............\4.13.5字符设备驱动的file_operations 结构体中成员.c .............\4.14.1头文件、宏及设备结构体.c .............\4.14.2加载与卸载设备驱动.c .............\4.14.3读写函数.c ................

    字符设备驱动.doc

    file_operations 把系统调用和驱动程序关联起来的关键数据结构 结构的每一个成员都对应着一个系统调用 读取file_operation中相应的函数指针,接着把控制权转交给函数,从而完成了Linux设备驱动程序的工作 中定义...

    Android驱动开发权威指南

    6.1.3 file_operations结构体 6.1.4 Linux字符设备驱动的组成 6.2一个字符设备驱动例子——virtualchar 6.2.1头文件、宏及设备结构体 6.2.2加载与卸载设备驱动 6.2.3驱动函数实现 6.2.4驱动设备私有数据 6.3对...

    Linux字符设备驱动(转载)

    概括的说,字符设备驱动主要要做三件事:1、定义一个结构体static struct file_operations变量,其内定义一些设备的打开、关闭、读、写、控制函数;2、在结构体外分别实现结构体中定义的这些函数;3、向内核中注册或...

    Linux 字符设备驱动模板

    在Linux内核里面,设备(device)主要分为字符设备,块设备,网络设备,字符设备驱动是Linux驱动基础,在看《Linux 设备驱动开发...const struct file_operations xxx_fops = { .owner = THIS_MODULE, .read = xxx_r

    Android的日志系统分层与logcat使用

    android的日志系统有典型的android层次结构。本文指出路径,分析层次但不分析代码,这里还介绍logcat的使用和log_bg服务。 日志系统分层 ... 分析代码的话跟踪结构体static const struct file_operations l

    学ARM和学单片机一样简单12

    (2)、file_operations结构体介绍 5、GPIO字符驱动程序(130分钟)(第八讲) (1)、驱动程序编写 (2)、快照的使用 (3)、内核配置 (4)、内核编译 (5)、演示实验 6、中断字符驱动程序(52分钟)...

    学ARM和学单片机一样简单15

    (2)、file_operations结构体介绍 5、GPIO字符驱动程序(130分钟)(第八讲) (1)、驱动程序编写 (2)、快照的使用 (3)、内核配置 (4)、内核编译 (5)、演示实验 6、中断字符驱动程序(52分钟)...

    学ARM和学单片机一样简单4

    (2)、file_operations结构体介绍 5、GPIO字符驱动程序(130分钟)(第八讲) (1)、驱动程序编写 (2)、快照的使用 (3)、内核配置 (4)、内核编译 (5)、演示实验 6、中断字符驱动程序(52分钟)...

    学ARM和学单片机一样简单3

    (2)、file_operations结构体介绍 5、GPIO字符驱动程序(130分钟)(第八讲) (1)、驱动程序编写 (2)、快照的使用 (3)、内核配置 (4)、内核编译 (5)、演示实验 6、中断字符驱动程序(52分钟)...

    学ARM和学单片机一样简单9

    (2)、file_operations结构体介绍 5、GPIO字符驱动程序(130分钟)(第八讲) (1)、驱动程序编写 (2)、快照的使用 (3)、内核配置 (4)、内核编译 (5)、演示实验 6、中断字符驱动程序(52分钟)...

    学ARM和学单片机一样简单2

    (2)、file_operations结构体介绍 5、GPIO字符驱动程序(130分钟)(第八讲) (1)、驱动程序编写 (2)、快照的使用 (3)、内核配置 (4)、内核编译 (5)、演示实验 6、中断字符驱动程序(52分钟)...

    学ARM和学单片机一样简单11

    (2)、file_operations结构体介绍 5、GPIO字符驱动程序(130分钟)(第八讲) (1)、驱动程序编写 (2)、快照的使用 (3)、内核配置 (4)、内核编译 (5)、演示实验 6、中断字符驱动程序(52分钟)...

    学ARM和学单片机一样简单7

    (2)、file_operations结构体介绍 5、GPIO字符驱动程序(130分钟)(第八讲) (1)、驱动程序编写 (2)、快照的使用 (3)、内核配置 (4)、内核编译 (5)、演示实验 6、中断字符驱动程序(52分钟)...

    学ARM和学单片机一样简单5

    (2)、file_operations结构体介绍 5、GPIO字符驱动程序(130分钟)(第八讲) (1)、驱动程序编写 (2)、快照的使用 (3)、内核配置 (4)、内核编译 (5)、演示实验 6、中断字符驱动程序(52分钟)...

    学ARM和学单片机一样简单13

    (2)、file_operations结构体介绍 5、GPIO字符驱动程序(130分钟)(第八讲) (1)、驱动程序编写 (2)、快照的使用 (3)、内核配置 (4)、内核编译 (5)、演示实验 6、中断字符驱动程序(52分钟)...

Global site tag (gtag.js) - Google Analytics