linux内核VFS之open

linux内核VFS之open

Posted by lwk on January 11, 2021

很久没有写文章了,这是2021年的第一篇文章,主题还是linux内核相关的,接着之前的VFS继续介绍open相关内容。内容比较简单,没有很详细&深入介绍每一个内容。

Linux的open也是通过系统调用进入到内核态,其中内核里的定义入口文件在fs/open.c,具体如下:


SYSCALL_DEFINE3(open, const char __user *,filename, int, flags, umode_t, mode)
{
       if(force_o_largefile())
              flags|= O_LARGEFILE;
 
       returndo_sys_open(AT_FDCWD, filename, flags, mode);
}
long do_sys_open(int dfd, const char __user*filename, int flags, umode_t mode)
{
       structopen_flags op;
       intfd = build_open_flags(flags, mode, &op);//根据flags、mode设置op的属性
       structfilename *tmp;
 
       if(fd) // build_open_flags永远返回是0,所以if永远是false
              returnfd;
 
       tmp= getname(filename);
       if(IS_ERR(tmp))
              returnPTR_ERR(tmp);
 
       fd= get_unused_fd_flags(flags); // 分配未使用的fd
       if(fd >= 0) {
              structfile *f = do_filp_open(dfd, tmp, &op);//打开文件,获取file结构体,文件操作的函数指针等
              if(IS_ERR(f)) {
                     put_unused_fd(fd);
                     fd= PTR_ERR(f);
              }else {
                     fsnotify_open(f);
                     fd_install(fd,f);//文件描述符与文件结构体(真正的文件操作函数等)关联
              }
       }
       putname(tmp);
       returnfd;
}

int get_unused_fd_flags(unsigned flags)
{
       return__alloc_fd(current->files, 0, rlimit(RLIMIT_NOFILE), flags);
}

int __alloc_fd(struct files_struct *files,
              unsigned start, unsigned end, unsignedflags)
{
       unsignedint fd;
       interror;
       structfdtable *fdt;
 
       spin_lock(&files->file_lock);
repeat:
       fdt= files_fdtable(files);
       fd= start;
       if(fd < files->next_fd) // 下一个将被分配的文件描述符号
              fd= files->next_fd;
 
       if(fd < fdt->max_fds)
              fd= find_next_fd(fdt, fd); // 查找下一个未被使用的文件描述符
 
       /*
        * N.B. For clone tasks sharing a filesstructure, this test
        * will limit the total number of files thatcan be opened.
        */
       error= -EMFILE;
       if(fd >= end)
              gotoout;
 
       error= expand_files(files, fd);//扩展文件描述符表(因为刚开始系统没有分配sysctl_nr_open的文件描述符表,因为虽然设置了最大使用的文件描述符表,
                                                               //但可能进程根本就用不到这么,所以,为了节省内存,刚开始的文件描述符表比较小,当达到了限制后,再根据实际情况
                                                               //进行扩展(若满足扩展条件,则内核进行扩展,但最大也不会超过sysctl_nr_open,超过则报错)
       if(error < 0)
              gotoout;
 
       /*
        * If we needed to expand the fs array we
        * might have blocked - try again.
        */
       if(error)
              gotorepeat;
 
       if(start <= files->next_fd)
              files->next_fd= fd + 1;
 
       __set_open_fd(fd,fdt);
       if(flags & O_CLOEXEC)
              __set_close_on_exec(fd,fdt);
       else
              __clear_close_on_exec(fd,fdt);
       error= fd;
#if 1
       /*Sanity check */
       if(rcu_access_pointer(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);
       returnerror;
}

find_next_fd()函数主要是查找下一个未被使用的文件描述符(unsigned int类型),并分给待打开的文件作为fd标识。内核使用文件描述符表表示可以分配的描述符信息。在前面的文章

我们看到进程里打开文件的结构表示:


struct files_struct {
  /*
   *read mostly part
   */
       atomic_tcount;// 共享该表的进程数目
       boolresize_in_progress;
       wait_queue_head_tresize_wait;
 
       structfdtable __rcu *fdt;
       structfdtable fdtab; //文件描述符表
  /*
   *written part on a separate cache line in SMP
   */
       spinlock_tfile_lock ____cacheline_aligned_in_smp;
       unsignedint next_fd;//所分配的最大文件描述符加1
       unsignedlong close_on_exec_init[1];//执行exec() 时需要关闭的文件描述符的集合
       unsignedlong open_fds_init[1];//文件描述符的初值集合
       unsignedlong full_fds_bits_init[1];
       structfile __rcu * fd_array[NR_OPEN_DEFAULT];//文件对象指针的初始化数组
};

其中fdt就是文件描述符表:


struct fdtable {
       unsignedint max_fds;
       structfile __rcu **fd; //打开的文件描述符指针数组     /* current fd array */
       unsignedlong *close_on_exec;
       unsignedlong *open_fds;//指向打开的文件描述符指针
       //full_fds_bits每1bit代表的是一个64位的数组,也就是说代表了64位描述符;
       //内核中的位图是一片连续的内存空间,最低bit表示数值0,下一比特表示1,依次类推;
       //full_fds_bits每1bit只有0和1两个值,0表示有该组有可用的文件描述符,1表示没有可用的文件描述符,
       //例如位图bit 0代表的是0-63共64个文件描述符,bit1代表的是64-127共64个文件描述符,假如0-63文件描述符都被使用了,
       //那么位图bit0则应该标记为1,如果64-127中有一个未使用的文件描述符,则bit1被标记为0,当64-127中的所有文件描述符都被使用的时候,才标记为1。
       unsignedlong *full_fds_bits;
       structrcu_head rcu;
};

static unsigned int find_next_fd(structfdtable *fdt, unsigned int start)
{
       unsignedint maxfd = fdt->max_fds; // 文件描述符表最大文件描述符
       unsignedint maxbit = maxfd / BITS_PER_LONG; // 最大文件描述符组(一组文件描述符包含64个文件描述符,例如0-63为一组)
       unsignedint bitbit = start / BITS_PER_LONG; // 第几组文件描述符,从0开始。例如起始查找文件描述符所在组(64个文件描述符为一组,我们要从文件描述符65开始查找,可知,65文件描述符在65/64 = 1组,因此我们从第1组开始查找即可)
 
       bitbit= find_next_zero_bit(fdt->full_fds_bits, maxbit, bitbit) * BITS_PER_LONG;//  查找下一个可用文件描述符组,结果乘以BITS_PER_LONG,即得到该组起始文件描述符
       if(bitbit > maxfd)
              returnmaxfd; // 可用文件描述符起始值大于最大文件描述符,直接返回最大文件描述符,表示文件描述符需要扩展。
       if(bitbit > start)
              start= bitbit; // 可用起始文件描述符大于参数传递的起始查找文件描述符,将开始查找的值从真正有效的值开始,避免做无效的查找
       returnfind_next_zero_bit(fdt->open_fds, maxfd, start); // 从文件描述符表中查找可用的文件描述符(之前是查找可用组,以64位为大小查找,提高效率,这次的查找范围缩小的组内了,即最多只需要查找64次了)。
}

expand_files()函数主要是做文件描述符扩展使用,具体如下:

static int expand_files(struct files_struct*files, unsigned int nr)
       __releases(files->file_lock)
       __acquires(files->file_lock)
{
       structfdtable *fdt;
       intexpanded = 0;
 
repeat:
       fdt= files_fdtable(files);//获取当前的fdt
 
       /*Do we need to expand? */
       if(nr < fdt->max_fds)
              returnexpanded;
 
       /*Can we expand? */
       if(nr >= sysctl_nr_open) // 大于当前系统的最大文件描述符数
              return-EMFILE;
 
       if(unlikely(files->resize_in_progress)) {
              spin_unlock(&files->file_lock);
              expanded= 1;
              wait_event(files->resize_wait,!files->resize_in_progress);
              spin_lock(&files->file_lock);
              gotorepeat;
       }
 
       /*All good, so we try */
       files->resize_in_progress= true;
       expanded= expand_fdtable(files, nr); // 扩展文件描述符表
}

expand_fdtable()函数如下:

static int expand_fdtable(structfiles_struct *files, unsigned int nr)
       __releases(files->file_lock)
       __acquires(files->file_lock)
{
       structfdtable *new_fdt, *cur_fdt;
 
       spin_unlock(&files->file_lock);
       new_fdt= alloc_fdtable(nr);//分配新的fdt
 
       /*make sure all __fd_install() have seen resize_in_progress
        * or have finished their rcu_read_lock_sched()section.
        */
       if(atomic_read(&files->count) > 1)
              synchronize_rcu();
 
       spin_lock(&files->file_lock);
       if(!new_fdt)
              return-ENOMEM;
       /*
        * extremely unlikely race - sysctl_nr_opendecreased between the check in
        * caller and alloc_fdtable().  Cheaper to catch it here...
        */
       if(unlikely(new_fdt->max_fds <= nr)) {
              __free_fdtable(new_fdt);
              return-EMFILE;
       }
       cur_fdt= files_fdtable(files);//获取旧的fdt
       BUG_ON(nr< cur_fdt->max_fds);
       copy_fdtable(new_fdt,cur_fdt); // 将旧的fdt拷贝到新的fdt
       rcu_assign_pointer(files->fdt,new_fdt);
       if(cur_fdt != &files->fdtab)
              call_rcu(&cur_fdt->rcu,free_fdtable_rcu); // 释放旧的fdt
       /*coupled with smp_rmb() in __fd_install() */
       smp_wmb();
       return1;
}

其中fdt是使用bit map表示的,如下图所示: image full_fds_bits是0~N,full_fds_bits中bit对应64个bit位(64位操作系统),bit位如果是1表示对应的fd已被占用,为0表示未被使用。系统启动时,默认给fdt->max_fds分配的是64个可用文件句柄(0-63),即full_fds_bits的位数是1,对应的分组号是0,则fd[0]指向64bit的连续内存空间的数组。 上面详细分析了get_unused_fd_flags()函数是如何分配文件描述符的,接下来重点看下do_filp_open()函数。

struct file *do_filp_open(int dfd, structfilename *pathname,
              conststruct open_flags *op)
{
       structnameidata nd;
       intflags = op->lookup_flags;
       structfile *filp;
 
       set_nameidata(&nd,dfd, pathname);
       filp= path_openat(&nd, op, flags | LOOKUP_RCU);//打开文件
       if(unlikely(filp == ERR_PTR(-ECHILD)))
              filp= path_openat(&nd, op, flags);
       if(unlikely(filp == ERR_PTR(-ESTALE)))
              filp= path_openat(&nd, op, flags | LOOKUP_REVAL);
       restore_nameidata();
       returnfilp;
}

static struct file *path_openat(structnameidata *nd,
                     conststruct open_flags *op, unsigned flags)
{
       structfile *file;
       interror;
 
       file= alloc_empty_file(op->open_flag, current_cred());//创建struct file,主要是分配内存;current_cred()当前进程的凭证信息(例如SYS_ADMIN)
       if(IS_ERR(file))
              returnfile;
 
       if(unlikely(file->f_flags & __O_TMPFILE)) {
              error= do_tmpfile(nd, flags, op, file);
       }else if (unlikely(file->f_flags & O_PATH)) {
              error= do_o_path(nd, flags, file);
       }else {
              constchar *s = path_init(nd, flags);// path_init为nd的path和inode赋值,为path->dentry赋值. static int link_path_walk 为nd->last 赋值
              while(!(error = link_path_walk(s, nd)) &&
                     (error= do_last(nd, file, op)) > 0) { // link_path_walk一级一级解析文件路径
                     nd->flags&= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
                     s= trailing_symlink(nd);
              }
              terminate_walk(nd);
       }
       fput(file);
}

// set_nameidata主要是保护当前进程的现场信息,主要是

static void set_nameidata(struct nameidata*p, int dfd, struct filename *name)
{
       structnameidata *old = current->nameidata;
       p->stack= p->internal;
       p->dfd= dfd;
       p->name= name;
       p->total_link_count= old ? old->total_link_count : 0;
       p->saved= old;
       current->nameidata= p;
}
struct nameidata {

       structpath     path; //记录路径查找的结果

       structqstr      last; //路径名最后一个分量

       structpath     root; //路径查找的根目录信息

       structinode   *inode; /* path.dentry.d_inode */

       unsignedint   flags;

       unsignedseq, m_seq;

       int          last_type; //记录当前查找到目录的类别Normal/Dot/DotDot/Root/Bind

       unsigneddepth; //查找过程中跨越的符号链接深度

       int          total_link_count; //查找过程中经过的符号链接总数

       structsaved {

              structpath link;

              structdelayed_call done;

              constchar *name;

              unsignedseq;

       }*stack, internal[EMBEDDED_LEVELS];//用来记录查找过程中碰到的符号链接

       structfilename      *name;

       structnameidata *saved;

       structinode   *link_inode;

       unsignedroot_seq;

       int          dfd;

} __randomize_layout;

struct file *alloc_empty_file(int flags,const struct cred *cred)

{

       staticlong old_max;

       structfile *f;

 

       /*

        * Privileged users can go above max_files

        */

        //get_nr_files函数返回percpu_counter_read_positive(&nr_files)的值

       if(get_nr_files() >= files_stat.max_files && !capable(CAP_SYS_ADMIN)){ //超过最大文件数限制且不具备CAP_SYS_ADMIN权限

              /*

               * percpu_counters are inaccurate.  Do an expensive check before

               * we go and fail.

               */

              if(percpu_counter_sum_positive(&nr_files) >= files_stat.max_files)

                     gotoover;

       }

 

       f= __alloc_file(flags, cred);//分配文件对象

       if(!IS_ERR(f))

              percpu_counter_inc(&nr_files);

 

       returnf;

 

over:

       /*Ran out of filps - report that */

       if(get_nr_files() > old_max) {

              pr_info("VFS:file-max limit %lu reached\n", get_max_files());

              old_max= get_nr_files();

       }

       returnERR_PTR(-ENFILE);

}

static struct file *__alloc_file(int flags,const struct cred *cred)

{

       structfile *f;

       interror;

 

       f= kmem_cache_zalloc(filp_cachep, GFP_KERNEL);//分配文件对象所用内存并清零

       if(unlikely(!f))

              returnERR_PTR(-ENOMEM);

 

       f->f_cred= get_cred(cred);//获取凭证

       error= security_file_alloc(f);

       if(unlikely(error)) {

              file_free_rcu(&f->f_u.fu_rcuhead);

              returnERR_PTR(error);

       }

 

       atomic_long_set(&f->f_count,1);

       rwlock_init(&f->f_owner.lock);

       spin_lock_init(&f->f_lock);

       mutex_init(&f->f_pos_lock);

       eventpoll_init_file(f);

       f->f_flags= flags;

       f->f_mode= OPEN_FMODE(flags);

       /*f->f_version: 0 */

 

       returnf;

}

kmem_cache_zalloc()会调用kmem_cache_alloc(),kmem_cache_alloc()又调用slab_alloc(),slab_alloc()函数再调用slab_alloc_node(),slab_alloc_node()函数在之前的文章kmalloc机制有说过,主要是slub(slab)内存分配机制,这里不再赘述。

do_last()函数

int vfs_open(const struct path *path,struct file *file)

{

       file->f_path= *path;

       returndo_dentry_open(file, d_backing_inode(path->dentry), NULL);//do_dentry_open会调用file对应的文件系统的file_operations(具体文件系统的open)

}

关于文件系统file_operations,在之前的文件VFS系统篇有阐述过,这里不再赘述。

最后,有了fd和file之后,接下来执行fd和file的关联操作。

void fd_install(unsigned int fd, structfile *file)
{

       __fd_install(current->files,fd, file);

}

__fd_install()函数执行将当前进程的fdt[fd]指向file,这样fd和file就关联起来了。

以上就是linux kernel vfs的open过程