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

对于Linux系统中,通常字符设备和驱动之间的函数调用关系以下图所示html

上图描述了用户空间应用程序经过系统调用来调用程序的过程。通常而言在驱动程序的设计中,会关系 struct file 和 struct inode 这两个结构体。node

用户空间使用open()系统调用函数打开一个字符设备时( int fd = open("dev/demo", O_RDWR) )大体有如下过程:linux

  1. 在虚拟文件系统VFS中的查找对应与字符设备对应 struct inode节点
  2. 遍历字符设备列表(chardevs数组),根据inod节点中的 cdev_t设备号找到cdev对象
  3. 建立struct file对象(系统采用一个数组来管理一个进程中的多个被打开的设备,每一个文件秒速符做为数组下标标识了一个设备对象)
  4. 初始化struct file对象,将 struct file对象中的 file_operations成员指向 struct cdev对象中的 file_operations成员(file->fops =  cdev->fops)
  5. 回调file->fops->open函数

1、inode结构体

         VFS inode 包含文件访问权限、属主、组、大小、生成时间、访问时间、最后修改时间等信息。它是Linux 管理文件系统的最基本单位,也是文件系统链接任何子目录、文件的桥梁。数组

        内核使用inode结构体在内核内部表示一个文件。所以,它与表示一个已经打开的文件描述符的结构体(即file 文件结构)是不一样的,咱们可使用多个file 文件结构表示同一个文件的多个文件描述符,但此时,全部的这些file文件结构所有都必须只能指向一个inode结构体数据结构

      inode结构体包含了一大堆文件相关的信息,可是就针对驱动代码来讲,咱们只要关心其中的两个域便可:app

  1. dev_t i_rdev;     表示设备文件的结点,这个域实际上包含了设备号。
  2. struct cdev *i_cdev;  struct cdev是内核的一个内部结构,它是用来表示字符设备的,当inode结点指向一个字符设备文件时,此域为一个指向inode结构的指针。

下面是源代码:ide

struct inode {  
  struct hlist_node i_hash;  
  struct list_head i_list;  
  struct list_head i_sb_list;  
  struct list_head i_dentry;  
  unsigned long  i_ino;  
  atomic_t  i_count;  
  unsigned int  i_nlink;  
  uid_t   i_uid;//inode拥有者id  
  gid_t   i_gid;//inode所属群组id  
  dev_t   i_rdev;//如果设备文件,表示记录设备的设备号  
  u64   i_version;  
  loff_t   i_size;//inode所表明大少  
#ifdef __NEED_I_SIZE_ORDERED  
  seqcount_t  i_size_seqcount;  
#endif  
  struct timespec  i_atime;//inode最近一次的存取时间  
  struct timespec  i_mtime;//inode最近一次修改时间  
  struct timespec  i_ctime;//inode的生成时间  
  unsigned int  i_blkbits;  
  blkcnt_t  i_blocks;  
  unsigned short          i_bytes;  
  umode_t   i_mode;  
  spinlock_t  i_lock;   
  struct mutex  i_mutex;  
  struct rw_semaphore i_alloc_sem;  
  const struct inode_operations *i_op;  
  const struct file_operations *i_fop;   
  struct super_block *i_sb;  
  struct file_lock *i_flock;  
  struct address_space *i_mapping;  
  struct address_space i_data;  
#ifdef CONFIG_QUOTA  
  struct dquot  *i_dquot[MAXQUOTAS];  
#endif  
  struct list_head i_devices;  
  union {  
    struct pipe_inode_info *i_pipe;  
    struct block_device *i_bdev;  
    struct cdev  *i_cdev;//如果字符设备,对应的为cdev结构  
  }; 
}; 
struct inode{...}

 inode的相关操做函数函数

/* 内核函数从inode中提取设备号 */

/* 提取主设备号 */
static inline unsigned imajor(const struct inode *inode)
{
  
return MAJOR(inode->i_rdev); } /* 提取次设备号 */ static inline unsigned iminor(const struct inode *inode) {   return MINOR(inode->i_rdev); }

  2、file 文件结构体

       在设备驱动中,这也是个很是重要的数据结构,必需要注意一点,这里的file与用户空间程序中的FILE指针是不一样的,用户空间FILE是定义在C库中,历来不会出如今内核中。而struct file,倒是内核当中的数据结构,所以,它也不会出如今用户层程序中。post

       file结构体指示一个已经打开的文件(设备对应于设备文件),其实系统中的每一个打开的文件在内核空间都有一个相应的struct file结构体,它由内核在打开文件时建立,并传递给在文件上进行操做的任何函数,直至文件被关闭。若是文件被关闭,内核就会释放相应的数据结构。ui

     在内核源码中,struct file要么表示为file,或者为filp(意指“file pointer”), 注意区分一点,file指的是struct file自己,而filp是指向这个结构体的指针。

下面是几个重要成员:

一、fmode_t f_mode;

      此文件模式经过 FMODE_READ ,  FMODE_WRITE 识别了文件为可读的,可写的,或者是两者。在open或ioctl函数中可能须要检查此域以确认文件的读/写权限,你没必要直接去检测读或写权限,由于在进行octl等操做时内核自己就须要对其权限进行检测。

二、 loff_t f_pos;

     当前读写文件的位置。为64位。若是想知道当前文件当前位置在哪,驱动能够读取这个值而不会改变其位置。对read,write来讲,当其接收到一个loff_t型指针做为其最后一个参数时,他们的读写操做便做更新文件的位置,而不须要直接执行filp ->f_pos操做。而

三、unsigned int f_flags;

     文件标志,如 O_RDONLY ,  O_NONBLOCK 以及 O_SYNC 。在驱动中还能够检查O_NONBLOCK标志查看是否有非阻塞请求。其它的标志较少使用。特别地注意的是,读写权限的检查是使用f_mode而不是f_flog。全部的标量定义在头文件中

四、struct file_operations *f_op;

    与文件相关的各类操做。当文件须要迅速进行各类操做时,内核分配这个指针做为它实现文件打开,读,写等功能的一部分。filp->f_op 其值从未被内核保存做为下次的引用,即你能够改变与文件相关的各类操做,这种方式效率很是高。

    file_operation 结构体解析以下:Linux字符设备驱动file_operations

五、 void *private_data;

      在驱动调用open方法以前,open系统调用设置此指针为NULL值。你能够很自由的将其作为你本身须要的一些数据域或者无论它,如,你能够将其指向一个分配好的数据,可是你必须记得在file struct被内核销毁以前在release方法中释放这些数据的内存空间。private_data用于在系统调用期间保存各类状态信息是很是有用的。

3、chardevs 数组

     前面对用户层open()的分析提到,经过数据结构 struct inode{...} 中的 i_cdev 成员能够找到cdev,而全部的字符设备都在 chrdevs 数组中,chrdevs具体是什么样的呢

下面先看一下 chrdevs 的定义: 

#define CHRDEV_MAJOR_HASH_SIZE 255  
static DEFINE_MUTEX(chrdevs_lock);

static struct char_device_struct {   struct char_device_struct *next; // 结构体指针   unsigned int major; // 主设备号   unsigned int baseminor; // 次设备起始号   int minorct; // 次备号个数   char name[64];   struct cdev *cdev; /* will die */ } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];// 只能挂255个字符主设备<span style="font-family: Arial, Helvetica, sans-serif; rgb(255, 255, 255);"> </span>

         能够看到全局数组 chrdevs 包含了255(CHRDEV_MAJOR_HASH_SIZE 的值)个 struct char_device_struct的元素,每个对应一个相应的主设备号。

       若是分配了一个设备号,就会建立一个 struct char_device_struct 的对象,并将其添加到 chrdevs 中;这样,经过chrdevs数组,咱们就能够知道分配了哪些设备号。

相关函数,(这些函数在上篇已经介绍过,如今回顾一下:

  register_chrdev_region( ) 分配指定的设备号范围

  alloc_chrdev_region( ) 动态分配设备范围

他们都主要是经过调用函数 __register_chrdev_region() 来实现的;要注意,这两个函数仅仅是注册设备号!若是要和cdev关联起来,还要调用cdev_add()。

  register_chrdev( )申请指定的设备号,而且将其注册到字符设备驱动模型中.

  它所作的事情为:

  1. 注册设备号, 经过调用 __register_chrdev_region() 来实现
  2. 分配一个cdev, 经过调用 cdev_alloc() 来实现
  3. 将cdev添加到驱动模型中, 这一步将设备号和驱动关联了起来. 经过调用 cdev_add() 来实现
  4. 将第一步中建立的 struct char_device_struct 对象的 cdev 指向第二步中分配的cdev. 因为register_chrdev()是老的接口,这一步在新的接口中并不须要。

4、cdev 结构体

        Linux内核中,使用 struct cdev 来描述一个字符设备

5、文件系统中对字符设备文件的访问

        下面看一下上层应用open() 调用系统调用函数的过程

        对于一个字符设备文件, 其inode->i_cdev 指向字符驱动对象cdev, 若是i_cdev为 NULL ,则说明该设备文件没有被打开.

  因为多个设备能够共用同一个驱动程序.因此,经过字符设备的inode 中的i_devices 和 cdev中的list组成一个链表

首先,系统调用open打开一个字符设备的时候, 经过一系列调用,最终会执行到 chrdev_open

  (最终是经过调用到def_chr_fops中的.open, 而def_chr_fops.open = chrdev_open. 这一系列的调用过程,本文暂不讨论)

  int chrdev_open(struct inode * inode, struct file * filp)

chrdev_open()所作的事情能够归纳以下:

  1. 根据设备号(inode->i_rdev), 在字符设备驱动模型中查找对应的驱动程序, 这经过kobj_lookup() 来实现, kobj_lookup()会返回对应驱动程序cdev的kobject.

  2. 设置inode->i_cdev , 指向找到的cdev.

  3. 将inode添加到cdev->list 的链表中.

  4. 使用cdev的ops 设置file对象的f_op

  5. 若是ops中定义了open方法,则调用该open方法

  6. 返回

执行完 chrdev_open()以后,file对象的f_op指向cdev的ops,于是以后对设备进行的read, write等操做,就会执行cdev的相应操做。

 

获得进程号:
asm/current.h
linux/sched.h
#define current get_current()

获得进程号:current->pid获得进程名:current->comm

相关文章
相关标签/搜索