关于file_operations结构体【转】

 

转自:http://blog.csdn.net/zkx1982/archive/2008/06/12/2540401.aspx

结构体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[1]设备文 件访问。主设备号告诉你哪些驱动模块是用来操纵哪些硬件设备的。从设备号是驱动模 块自己使用来区别它操纵的不同设备,当此驱动模块操纵不只一个设备时。

将内核驱动模块加载入内核意味着要向内核注册自己。这个工作是和驱动模块获 得主设备号时初始化一同进行的。你可以使用头文件 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统调用建立设备文件并且调用 rm 删除该设备 文件在驱动模块调用函数cleanup_module前。

注销一个设备
即使时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的内核。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zkx1982/archive/2008/06/12/2540401.aspx

你可能感兴趣的:(struct,File,user,Module,buffer,linux内核)