Linux——文件描述符与文件管理结构

Linux中的文件

文件:

  Linux内核将一切视为文件,那么Linux中文件是什么呢?其既可以是事实上的真正的物理文件,也可以是设备,管道,甚至可以是一块内存,狭义的文件是指文件系统中的物理文件,广义上的文件可以是Linux管理的所有对象。这些广义的文件利用VFS机制,以文件系统的形式挂载在Linux内核中,对外提供一系列的文件操作接口。

文件描述符:

对于linux而言,所有对设备和文件的操作都使用文件描述符来进行的。文件描述符是一个非负的整数,它是一个索引值,指向内核中每个进程打开文件的记录表。当打开一个现存文件或创建一个新文件时,内核就向进程返回一个文件描述符;当需要读写文件时,也需要把文件描述符作为参数传递给相应的函数。
通常,一个进程启动时,都会打开3个文件:标准输入、标准输出和标准出错处理。这3个文件分别对应文件描述符为0、1 和 2

   从数值上看,文件描述符是一个非负整数,其实质就是   一个句柄,所以可以认为文件描述符就是一个文件句柄,那何为句柄呢?一切对于用户透明的返回值,即可视为句柄。用户空间利用文件描述符与内核进行交互,而内核拿到文件 描述符之后,可以通过它得到用于管理文件的真正的数据结构。

使用文件描述符的好处:

1、为了增加安全性, 句柄类型对用户完全透明,用户无法通过任何hacking的方式,更改句柄对应内部结果,Linux内核的文件描述符,只有内核才能通过该值得到对应的文件结构;
2、增加了可扩展性,用户的代码只依赖于句柄的值,这样实际结构的类型就可以随时改变,与句柄的映射关系也可以随时改变,这些变化都不会影响任何现有的用户代码。

Linux的每个进程都会维护一个文件表,以便维护该进程打开文件的信息,包括打开的文件个数,每个打开文件的偏移量等信息。

文件表的实现:
内核中进程对应的结构是task_struct,进程的文件表保存在task_struct—>files中,结构代码:

struct files_struct 
 {
    atomic_t count;       //引用计数
    struct fdtable _rcu *fdt;
    struct fdtable fdtab;
    /* 为什么会有两个 fdtable?  这是内核的一种优化策略,fdt为指针,fdtab为普通变量,一般情况下,fdt是指向fdtab的,当需要它时,才会动态的开辟内存空间,因为默认大小的文件表足以满足大多数情况,因此这样就避免了频繁的申请空>    间,在创建时,使用普通的变量或数组,然后让指针指向他,作为默认情况,当进程使用量超过默认值时,才会动态申请内存 */
    int next_fd;  
     //用于查找下一个空闲的fd
	struct embedded_fd_set open_fds_init;
	//保存打开文件描述符的位图
    struct embedded_fd_set close_on_exec_init;
    //保存执行exec需要关闭的文件描述符的位图
    struct files_struct  __rcu* fd_array[NR_OPEN_DEFAULT]; 
    //fd_array是一个固定大小的file的结构数组,struct file是内核用于文件管理的结构,这里使用了默认大小的数组,就是为了涵盖大多数情况,避免动态分配
 };
    

在初始状态下,files_struct 与 fdtable、files的关系为:
这里写图片描述
文件表,文件描述符表以及文件结构的关系图如上所示

这里写图片描述

我们通常这样描述,”打开一个文件“,那么这个所谓的打开究竟打开了什么呢?内核在这个过程中,又做了什么事情?
我们来跟踪内核open源码open–>do_sys_open 来剖析

long do_sys_open(int dfd,const char __user *filename,int flags,int mode)
{
   struct open_flags op;  //flags为用户层传递的参数,内核会对flags进行合法性检查,并根据mode生成新的flags值赋给lookup
   int lookup = build_open_flags(flags,mode,&op);
   //将用户空间的文件名参数复制到内核空间
   char* tmp = getname(filename);
   int fd  = PTR_ERR(tmp);
   if(!IS_ERR(tmp))
   {
       //为出错则申请新的文件描述符
      fd = get_unused_fd_flags(flags);
      if(fd>=0)
      {
           //申请新的文件管理结构file
           struct file* f=  do_filp_open(dfd,tmp,&op,lookup);
           if(IS_ERR(f))
           {
              put_unused_fd(fd);
              fd = PTR_ERR(f);
           }
           else
           {
              fsnotity_open(f);// 产生文件打开的通知事件
              fd_install(fd,f);//将文件描述符fd与文件管理结构file对应起来,即安装
           }
      }
      putname(tmp);
   }
   return fd;
}

//从do_sys_open 可以看出,打开文件时,内核主要消耗了两种资源,文件描述符与内核管理文件结构file

文件描述符的选择:

根据POSIX标准,当获取一个新的文件描述符时,要返回最低的未使用的文件描述符。Linux是如何实现这一标准的呢?在Linux中,通过do_sys_open->get_unused_fd_flags->alloc_fd(0, (flags))来选择文件描述符,代码如下:

int alloc_fd(unsigned start, unsigned flags)  
{  
    struct files_struct *files = current->files;  
    unsigned int fd;  
    int error;  
    struct fdtable *fdt;  
    /* files为进程的文件表,下面需要更改文件表,所以需要先锁文件表 */  
    spin_lock(&files->file_lock);  
repeat:  
    /* 得到文件描述符表 */  
    fdt = files_fdtable(files);  
    /* 从start开始,查找未用的文件描述符。在打开文件时,start为0 */  
    fd = start;  
    /* files->next_fd为上一次成功找到的fd的下一个描述符。使用next_fd,可以快速找到未用的文  
    件描述符;*/  
    if (fd < files->next_fd)  
        fd = files->next_fd;  
 
    /*  
    当小于当前文件表支持的最大文件描述符个数时,利用位图找到未用的文件描述符。  
    如果大于max_fds怎么办呢?如果大于当前支持的最大文件描述符,那它肯定是未  
    用的,就不需要用位图来确认了。  
    */  
    if (fd < fdt->max_fds)  
        fd = find_next_zero_bit(fdt->open_fds->fds_bits,  
            fdt->max_fds, fd);  
    /* expand_files用于在必要时扩展文件表。何时是必要的时候呢?比如当前文件描述符已经超过了当  
    前文件表支持的最大值的时候。 */  
    error = expand_files(files, fd);  
    if (error < 0)  
        goto out;  
 
    /*  
    * If we needed to expand the fs array we  
    * might have blocked - try again.  
    */  
    if (error)  
        goto repeat;  
 
    /* 只有在start小于next_fd时,才需要更新next_fd,以尽量保证文件描述符的连续性。*/  
    if (start <= files->next_fd)  
        files->next_fd = fd + 1;  
 
    /* 将打开文件位图open_fds对应fd的位置置位 */  
    FD_SET(fd, fdt->open_fds);  
    /* 根据flags是否设置了O_CLOEXEC,设置或清除fdt->close_on_exec */  
    if (flags & O_CLOEXEC)  
        FD_SET(fd, fdt->close_on_exec);  
    else  
        FD_CLR(fd, fdt->close_on_exec);  
    error = fd;  
#if 1  
    /* Sanity check */  
    if (rcu_dereference_raw(fdt->fd[fd]) != NULL) {  
        printk(KERN_WARNING "alloc_fd: slot %d not NULL!\n", fd);  
        rcu_assign_pointer(fdt->fd[fd], NULL);  
    }  
#endif  
out:  
    spin_unlock(&files->file_lock);  
    return error;  
} 

文件描述符fd与文件管理结构file
内核使用fd_install 将文件管理结构file与fd组合起来,具体操作如下:

void fd_install(unsignet int fd,struct file* file)
{
    struct files_struct *files = current->files;
    struct fdtable *fdt;
    spin_lock(&files->file_lock);
    //  得到文件描述符表
    fdt = files_fdtable(files);
    BUG_ON(fdt->fd[fd] != NULL);
    //将文件描述符表中的file类型的指针数组中  对应fd的项  指向  file,这样文件描述符fd就与file建立了对应关系
    rcu_assign_pointer(fdt->fd[fd],file);
    spin_unlock(&files->file_lock);
    
}


//  得到:当用户使用fd与内核交时,内核可以用fd从 fdt->fd[fd]中得到内部管理文件的结构struct file

当新建一个程序,因为进程启动时,打开了标准输入、标准输出和标准出错处理三个文件,因此返回的文件描述符fd 为3,程序测试:
这里写图片描述

这里写图片描述

c语言中,文件类型指针:

在缓冲文件系统中,关键概念是“”文件指针“”,每个被使用的文件都在内存中开辟一个区,用来存放文件的信息(如文件的名字,文件的状态,文件当前的位置等),这些信息是保存在一个结构体变量中,该结构体类型是由系统定义的,为 FILE ,文件类型声明为

typedef struct
{
  short level; //缓冲区“满”或“空”的程度
  unsigned flags;   //文件状态标志
  char fd;         //文件描述符
  unsigned char hold; //如无缓冲区不读取字符
  short  bsize;   //缓冲区的大小
  unsigned char* buffer  //数据缓冲区位置
  unsigned char* curp  //指针,当前的指向
  unsigned istemp //临时文件,指示器
  short token //用于有效性检测
}FILE;
FILE  f[5]
 
 //定义了一个结构体数组,他有5个元素,可以用来存放5个文件信息
FILE *   fp

//fp是一个指向FILE 类型结构体的指针变量,可以使fp指向某一个文件的结构体变量,从而通过该结构体变量中的文件信息能够访问该文件,也就是说,通过文件指针能找到与他相关的文件,如果有n个文件,一般对应n个指针变量,使他们分别指向n个文件,以实现对其访问。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值