字符设备文件的打开操作(1)

作为例子,这里假定前面对应于/dev/demodev设备节点的驱动程序在自己的代码里实现了如下的struct file_operations对象fops:

    static struct file_operations fops = {  
   .open = demoopen,  
   .read = demoread,  
   .write = demowrite,  
   .ioctl = demoioctl,  
   };  

用户空间open函数的原型为:

int open(const char *filename, int flags, mode_t mode); 

这个函数如果成功,将返回一个文件描述符,否则返回-1。函数的第一个参数filename表示要打开的文件名,第二个参数flags用于指定文件的打开或者创建模式,本书在后续"字符设备的高级操作"一章中会讨论其中一些常见取值对驱动程序的影响,最后一个参数mode只在创建一个新文件时才使用,用于指定新建文件的访问权限,比如可读、可写及可执行等权限。
位于内核空间的驱动程序中open函数的原型为:

    <include/linux/fs.h> 
   struct file_operations {  
   …  
   int (*open) (struct inode *, struct file *);  
   …  
   };  

两者相比差异很大。接下来我们将描述从用户态的open是如何一步一步调用到驱动程序提供的open函数(在我们的例子中,它的具体实现是demoopen)的。如同设备文件节点的生成一样,透彻了解这里的每一个步骤也需要掌握全面的Linux下文件系统的技术细节。从设备驱动程序员的角度,我们依然将重点放在两者如何建立联系的关键点上。
用户程序调用open函数返回的文件描述符,本文用fd表示,这是个int型的变量,会被用户程序后续的read、write和ioctl等函数所使用。同时可以看到,在驱动程序中的demodev_read、demodev_write和demodev_ioctl等函数其第一个参数都是struct file *filp。显然内核需要在打开设备文件时为fd与filp建立某种联系,其次是为filp与驱动程序中的fops建立关联。
用户空间程序调用open函数,将发起一个系统调用,通过sys_open函数进入内核空间,其中一系列关键的函数调用关系如图2-8所示:
222143282.jpg

do_sys_open函数首先通过get_unused_fd_flags为本次的open操作分配一个未使用过的文件描述符fd :

    <fs/open.c> 
   long do_sys_open(int dfd, const char __user *filename, int flags, int mode)  
   {  
   …  
   fd = get_unused_fd_flags(flags);  
   …  
   }  

get_unused_fd_flags实际上是封装了alloc_fd的一个宏,真正分配fd的操作发生在alloc_fd函数中,后者会涉及大量文件系统方面的细节,这不是本书的主题。读者这里只需知道alloc_fd将会为本次的open操作分配一个新的fd。
do_sys_open随后调用do_filp_open函数,后者会首先查找"/dev/demodev"设备文件所对应的inode。在Linux文件系统中,每个文件都有一个inode与之对应。从文件名查找对应的inode这一过程,同样会涉及大量文件系统方面的细节。
do_filp_open在成功查找到"/dev/demodev"设备文件对应的inode之后,接着会调用函数get_empty_filp,后者会为每个打开的文件分配一个新的struct file类型的内存空间(本书将把指向该结构体对象的内存指针简写为filp):

    <fs/namei.c> 
   struct file *do_filp_open(int dfd, const char *pathname,  
   const struct open_flags *op, int flags)  
   {  
   struct nameidata nd;  
   struct file *filp;  
   
   filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU);  
   …  
   return filp;  
   }  

内核用struct file对象来描述进程打开的每一个文件的视图,即使是打开同一文件,内核也会为之生成一个新的struct file对象,用来表示当前操作的文件的相关信息,其定义为:

    <include/linux/fs.h> 
   struct file {  
   union {  
   struct list_head    fu_list;  
   struct rcu_head     fu_rcuhead;  
   } f_u;  
   struct path     f_path;  
   #define f_dentry    f_path.dentry  
   #define f_vfsmnt    f_path.mnt  
   const struct file_operations    *f_op;  
   spinlock_t      f_lock;  
   atomic_long_t       f_count;  
   unsigned int        f_flags;  
   fmode_t         f_mode;  
   loff_t          f_pos;  
   struct fown_struct  f_owner;  
   const struct cred   *f_cred;  
   struct file_ra_state    f_ra;  
   
   u64         f_version;  
   #ifdef CONFIG_SECURITY  
   void            *f_security;  
   #endif  
   /* needed for tty driver, and maybe others */  
   void            *private_data;  
   
   #ifdef CONFIG_EPOLL  
   /* Used by fs/eventpoll.c to link all the hooks to this file */  
   struct list_head    f_ep_links;  
   #endif /* #ifdef CONFIG_EPOLL */  
   struct address_space    *f_mapping;  
   };