Linux里档案file结构和inode结构

内核编译和嵌入式产品的设计与开发
回复
兰晓成
帖子: 28
注册时间: 2007-05-09 19:59

Linux里档案file结构和inode结构

#1

帖子 兰晓成 » 2007-08-09 9:54

在Linux里,每一个档案都有一个file结构和inode结构,inode结构是用来让Kernel做管理的,而file结构则是我们平常对档案读写或开启,关闭所使用的。当然,从user的观点来看是看不出什么的。在Linux里,档案的观念应用的蛮广泛的,甚至是写一个driver你也只要提供一组的file operations就可以完成了。我们现在来看看File结构的内容。
  
  

代码: 全选

struct file { 
  struct file *f_next,**f_pprev; 
  struct dentry *f_dentry; 
  struct file_operations *f_op; 
  mode_t f_mode; 
  loff_t f_pos; 
  unsigned int f_count,f_flags; 
  unsigned long f_reada,f_ramax,f_raend,f_ralen,f_rawin; 
  struct fown_struct f_owner; 
  unsigned long f_version; 
  void *private_data; 
  }; 
  
  比起super_block和inode结构,file结构就显得小多了,file结构也是用串行来管理,f_next会指到下一个file结构,而f_pprev则是会指到上一个file结构地址的地址,不过,这个字段的用法跟一般指到前一个file结构的用法不太一样,有机会再跟各位讨论,f_dentry会记录其inode的dentry地址,f_mode为档案存取的种类,f_pos则是目前档案的offset,每次读写都从offset记录的位置开始读写,f_count是此file结构的reference cout,f_flags则是开启此档案的模式,f_reada,f_ramax,f_raend,f_ralen,f_rawin则是控制read ahead的参数,f_owner记录了要接收SIGIO和SIGURG的行程ID或行程群组ID,private_data则是tty driver所使用的字段。最后,我们来看看f_op这个字段。这个字段记录了一组的函式是专门用来使用档案的。
  
  ? llseek(file,offset,where)
  
  我们写程序会呼叫lseek()系统呼叫设定从档案那个位置开始读写,这个函式你可以不用提供,因为系统已经有一个写好的,但是系统提供的llseek()没有办法让你将where设为SEEK_END,因为系统不知道你的档案长度是多少,所以没办法提供这样的服务。如果你不提供llseek()的话,那系统会直接使用它已经有的llseek()。llseek()必须要将file->offset的值做改新。
  
  ? read(file,buf,buflen,poffset)
  
  当我们读取一个档案时,最终就是会呼叫read()这个函式来读取档案内容。这些参数VFS会替我们准备好,至于poffset则是offset的指针,这是要告诉read()从那里开始读,读完之后必须更新poffset的内容。请注意,在这里buf是一个地址,而且是一个位于user space的地址。
  
  ? write(file,buf,buflen,poffset)
  
  write()的动作就跟read()是相反的,参数也都一样,buf依然是位于user space的地址。
  
  ? readdir(file,dirent,filldir)
  
  这是用来读取目录的下一个direntry的,其中file是file结构地址,dirent则是一个readdir_callback结构,这个结构里包含了使用者呼叫readdir()系统呼叫时所传过去的dirent结构地址,filldir则是一个函式指针,这个函式在VFS已经有提供了,这个函式其实是增加了kernel在读取dirent方面的弹性。当档案系统的readdir()被呼叫时,在它把下一个dirent取出来之后,应该要呼叫filldir(),让它把所需的资料写到user space的dirent结构里,也许还会多做些处理。有兴趣的朋友可以参考的filldir()函式。
  
  ? poll(file,poll_table)
  
  之前的Kernel版本本来是在file_operations结构里有select()函式而不是poll()函式的。但是,这并不代表Linux不提供select()系统呼叫,相反的,Linux仍然提供select()系统呼叫,只不过select()系统呼叫implement的方式是使用poll()函式来做的。
  
  ? ioctl(inode,file,cmd,arg)
  
  ioctl()这个函式其实有很大的用途,尤其它可以做为user space的程序对Kernel的一个沟通管道。那ioctl()是什么时候被呼叫呢? 还记得平常写程序时偶而会用到ioctl()系统呼叫来直接控制档案或device吗? 是的,ioctl()系统呼叫最后就是把命令交给档案的f_op->ioctl()来执行。f_op->ioctl()要做的事很简单,只要根据cmd的值,做出适当的行为,并传回值即可。但是,ioctl()系统呼叫其实是分几个步骤的,第一,系统有几个内定的command它自己可以处理,在这种情形下,它不会呼叫f_op->ioctl()来处理。如果user指定的command是以下的一种,那VFS会自己处理。
  
  o FIONCLEX
  
  清除档案的close-on-exec位。
  
  o FIOCLEX
  
  设定档案的close-on-exec位。
  
  o FIONBIO
  
  如果arg传过来的值为0的话,就将档案的O_NONBLOCK属性去掉,但是如果不等于0的话,就将O_NONBLOCK属性设起来。
  
  o FIOASYNC
  
  如果arg传过来的值为0的话,就将档案的O_SYNC属性去掉,但是如果不等于0的话,就将O_SYNC属性设起来。只是在Kernel 2.2.1时,这个属性的功能还没完成。
  
  如果cmd的值不是以上数种,而且如果file所代表的不是普通的档案的话,像是device之类的特殊档案,VFS会直接呼叫f_op->ioctl()去处理。但是如果file代表普通档案的话,那VFS会呼叫file_ioctl()做另外的处理。何谓另外的处理呢? file_ioctl()会再过?一次cmd的值,如果是以下数种,它会先做些处理,然后再呼叫f_op->ioctl(),不管怎么样,file_ioctl()最后都会再呼叫f_op->ioctl()去处理。
  
  o FIBMAP
  
  先将arg指到的档案block number取出来,并呼叫f_op->bmap()计算出其disk上的block number,最后再将计算出来的block number放到arg参数里。
  
  o FIGETBSZ
  
  先取得档案系统block的大小并放入arg的参数里。
  
  o FIONREAD
  
  将档案剩下尚未读取的长度写到arg里。比方说档案大小是1000,而f_op->offset的值是300,表示还有700个byte尚未读取,所以,将700写到arg参数里。
  
  ? mmap(file,vmarea)
  
  这个函式是用来将档案的部分内容映像到内存中的,file是指要被映像的档案,而vmarea则是用来描述到映像到内存的那里。
  
  ? open(inode,file)
  
  当我们呼叫open()系统呼叫来开启档案时,open()会把所有的事都做好,最后则会呼叫f_op->open()看档案系统是否要做些什么事,一般来讲,VFS已经把事做好了,所以很多系统事实上根本不提供这个函式,当然,你要提供也可以,比方说,你可以在这个函式里计算这个档案系统的档案被使用过多少次等。
  
  ? flush(file)
  
  这个函式也是新增加的,这个函式是在我们呼叫close()系统呼叫来关闭档案时所呼叫的。只要你呼叫close()系统呼叫,那close()就会呼叫flush(),不管那个时候f_count的值是否为0。这个函式我不是很确定在做什么的,事实上,在Ext2里也没有提供这么一个函式,也许是在关闭档案之前,VFS允许档案系统先做些backup的动作吧。
  
  ? release(inode,file)
  
  这个函式也是在close()系统呼叫里使用的,当然,不尽在close()中使用,在别的地方也是有使用到。基本上,这个函式的定位跟open()很像,不过,当我们对一个档案呼叫close()时,只有当f_count的值归0时,VFS才会呼叫这个函式做处理。一般来讲,如果你在open()时配置了一些东西,那应该在release()时将配置的东西释放掉。至于f_count的值则是不用在open()和release()中控制,VFS已经在fget()和fput()中增减f_count了。
  
  
  ? fsync(file,dentry)
  
  fsync()这个函式主要是由buffer cache所使用,它是用来跟file这个档案的资料写到disk上。事实上,Linux里有两个系统呼叫,fsync()和fdatasync(),都是呼叫f_op->fsync()。它们几乎是一模一样的,差别在于fsync()呼叫f_op->fsync()之前会使用semaphore将f_op->fsync()设成critical section,而fdatasync()则是直接呼叫f_op->fsync()而不设semaphore。
  
  ? fasync(fd,file,on)
  
  当我们呼叫fcntl()系统呼叫,并使用F_SETFL命令来设定档案的参数时,VFS就会呼叫fasync()这个函式,而当读写档案的动作完成时,行程会收到SIGIO的讯息。
  
  ? check_media_change(dev)
  
  这个函式只对可以使用可移动的disk的block device有效而已,像是MO,CDROM,floopy disk等等。为什么对这些可以把disk随时抽取的需要提供这么一个函式呢? 其实,从字面上我们大概可以知道,这是用来检查disk是否换过了,以CDROM为例,每一个光盘片都代表一个档案系统,如果今天我们把光盘片换掉了,那表示这个档案系统不存在了,如果user此时去读取这个档案系统的资料,那会发生什么事? 很有可能系统就这么出事了。所以,对于这种的device,每当在mount时,我们就必须检查其中的disk是否换过了,如何检查呢? 当然只有档案系统本身才知道,所以,档案系统必须提供此函式。
  
  ? revalidate(dev)
  
  这个函式跟上面的check_media_change()有着相当的关系。当user执行mount要挂上一个档案系统时,mount会先呼叫里的check_disk_change(),如果档案系统所属的device有提供这个函式的话,那check_disk_change()会先呼叫f_op->check_media_change()来检查是否其中的disk有换过,如果有则呼叫invalidate_inodes()和invalidate_buffers()将跟原本disk有关的buffer或inode都设为无效,如果档案系统所属的device还有提供revalidate()的话,那就再呼叫revalidate()将此device的资料记录好。
  
  ? lock(file,cmd,file_lock)
  
  这个函式也是新增加的,在Linux里,我们可对一

Inode结构:基本资料
inode的基本资料蛮多,在此我很简略的跟各位介绍一下
  
  unsigned long i_ino;
  
  每一个inode都有一个序号,经由super block结构和其序号,我们可以很轻易的找到这个inode。
  
  unsigned int i_count;
  
  在Kernel里,很多的结构都会记录其reference count,以确保如果某个结构正在使用,它不会被不小心释放掉,i_count就是其reference count。
  
  kdev_t i_dev; /* inode所在的device代码 */
  umode_t i_mode; /* inode的权限 */
  nlink_t i_nlink; /* hard link的个数 */
  uid_t i_uid; /* inode拥有者的id */
  gid_t i_gid; /* inode所属的群组id */
  kdev_t i_rdev; /* 如果inode代表的是device的话,
  那此字段将记录device的代码 */
  off_t i_size; /* inode所代表的档案大小 */
  time_t i_atime; /* inode最近一次的存取时间 */
  time_t i_mtime; /* inode最近一次的修改时间 */
  time_t i_ctime; /* inode的产生时间 */
  unsigned long i_blksize; /* inode在做IO时的区块大小 */
  unsigned long i_blocks; /* inode所使用的block数,一个block为512 byte*/
  unsigned long i_version; /* 版本号码 */
  unsigned long i_nrpages; /* inode所使用的page个数 */
  struct page *i_pages;
  /* inode使用的page会被放在串行里,这个字段记录着此串行的开头 */
  struct super_block *i_sb; /* inode所属档案系统的super block */
  unsigned long i_state;
  /* inode目前的状态,可以是I_DIRTY,I_LOCK和 I_FREEING的OR组合 */
  unsigned int i_flags; /* 记录此inode的参数 */
  unsigned char i_pipe; /* 用来记录此inode是否为pipe */
  unsigned char i_sock; /* 用来记录此inode是否为socket */
  unsigned int i_attr_flags; /* 用来记录此inode的属性参数 */
  struct file_lock *i_flock; /* 用来做file lock */


Inode结构
在Linux里,我们可以利用mmap()将档案或device的某个区块映像到记体里使用。在inode里这两个字段就是跟它有关的:
  
  struct vm_area_struct *i_mmap;
  
  int i_writecount;
  
  i_writecount这个字段的值是用来记录目前有多少个行程是以可写入的模式开启此档案的。为什么需要这个值呢? 因为系统没办法支持可以对一个档案写入,而又同时将这个档案映像为MAP_DENYWRITE的模式,所以,用这个字段来代表目前有多个行程可对此inode做写入的动作或是有多少个行程将它映像成MAP_DENYWRITE的模式。它的值有以下三种情形:
  
  0: 没有行程将它开启为可写入,也没有行程对它做MAP_DENYWRITE的映像
  
  < 0: 有-i_writecount个行程对它做MAP_DENYWRITE的映像。
  
  > 0: 有i_writecount个行程将它开启为可写入模式。
  
  至于i_mmap这个字段就是用来做内存映像的字段。
回复