linux VFS 之六:进程与文件系统的关联

时间:2021-02-08 05:14:16

一、进程与vfs对象之间的关系很重要


linux VFS 之六:进程与文件系统的关联

1、、文件对象是在文件被打开的时候,由进程创建的,由一个file结构来描述,文件结构也仅仅存在于内存中。

2、文件对象中存了一个重要信息:文件指针(文件当前位置),几个进程可能同时访问同一文件,因此文件指针必须存放在文件对象,而非索引节点。 这也是为什么一个文件被打开一次,就要创建一次文件对象。
3、使用dup(), dup2(), fcntl() 两个文件描述符可以指向同一个打开的文件对象


二、进程与文件系统通过如下结构关联


struct task_struct {

   。。。。。。。。


  /* filesystem information */
struct fs_struct *fs;                             //文件系统信息
  /* open file information */
struct files_struct *files;      //打开的文件对象信息
  /* namespaces */
struct nsproxy *nsproxy;      //合名空间


  。。。。。。。


进程利用fs_struct结构体的当前的工作目录,和它自已的根目录与文件系统关联起来。如下

struct fs_struct {
int users;
spinlock_t lock;
seqcount_t seq;
int umask;
int in_exec;
struct path root, pwd;     //进程当前的工作目录,和它自已的根目录。

/*

struct path {
struct vfsmount *mnt;
struct dentry *dentry;
}; */
};


进程利用files_struct结构体与打开的文件对象进行关联,如下:
/*
 * Open file table structure
 */
struct files_struct {
atomic_t count;    
struct fdtable __rcu *fdt;   //文件描述符表指针,指向fdtab
struct fdtable fdtab;       //文件描述符表

spinlock_t file_lock ____cacheline_aligned_in_smp;   //文件锁
int next_fd;            // 下一个分配的文件描述符:所分配的文件描述符加1,或者是释放的文件描述符。

//注:next_fd 并不一定是指下一个可分配的文件描述符啊!!下面再会再继续说明它的作用!
unsigned long close_on_exec_init[1];   
unsigned long open_fds_init[1];
struct file __rcu * fd_array[NR_OPEN_DEFAULT]; //文件对象指针数组,

            //这里存放的就是文件对象指针,由文件描述符fd作为索引 

                          //index查找文件对象,数组大小默认32
};


struct fdtable {
unsigned int max_fds;  //文件对象最大数,默认32。如果超过32,

  //重新申请分配一个更大的文件对象指针数组fd_array, 并更新max_fds
struct file __rcu **fd;     //指向文件对象指针数组的指针,即指向 fd_array
unsigned long *close_on_exec;
unsigned long *open_fds;        //指向打开开文件上描述符的指针
struct rcu_head rcu;
struct fdtable *next;
};


下面举为init task进程定义的全局init_files,说明该结构体中几个指针的初始化:

struct files_struct init_files = {
.count = ATOMIC_INIT(1),       
.fdt = &init_files.fdtab,               //指向fdtab
.fdtab = {
.max_fds = NR_OPEN_DEFAULT,
.fd = &init_files.fd_array[0],              //指向fd_array
.close_on_exec= init_files.close_on_exec_init,
.open_fds = init_files.open_fds_init,             //指向open_fds_init
},
.file_lock = __SPIN_LOCK_UNLOCKED(init_task.file_lock),
};


注:其他进程的files_struct应该是fork时copyinit_files而来。看如下调用关系:

do_fork=》copy_process=》copy_files=》dup_fd


三、重点参考代码文件及函数注释


/kernel/fs/file.c 

/*分配一个文件描述符fd : start从哪开始,一般是0 */
int alloc_fd(unsigned start, unsigned flags)
{
struct files_struct *files = current->files;   //当前进程中获取结构体files_struct数据
unsigned int fd;
int error;
struct fdtable *fdt;


spin_lock(&files->file_lock);    //锁住files_struct
repeat:
fdt = files_fdtable(files);       //取出files_struct中的fdt指针
fd = start;         //一般这时fd=start=0;
if (fd < files->next_fd)
fd = files->next_fd;         //新的fd先设置为下一个分配的文件描述符:next_fd
if (fd < fdt->max_fds)   //如果next_fd仍然小于的最大分配数目max_fds。

fd = find_next_zero_bit(fdt->open_fds, fdt->max_fds, fd);// 从next_fd到max_fds开始找open_fds为0的bit
        //注:
如果next_fd仍然小于的最大分配数目max_fds, 则肯定可以找到一个未分配的fd,否则说明上次fd已经分配到max_fds,下面就不要再查了,要进行扩展。

//

error = expand_files(files, fd);     //判断是否要进行扩展fdtable,或进行fdtable扩展

/*  注:expand_files函数里面通过下面这句话对前面已经设置、查找到的fd与max_fds进行比较,

确定是否需要进行扩展!

/* Do we need to expand? */
if (nr < fdt->max_fds)
return 0;

*/

/*
* If we needed to expand the fs array we
* might have blocked - try again.
*/
if (error)     //如果expand_files返非0,说明进行了fdtable扩展,需要再进行确认一遍。
goto repeat;     


if (start <= files->next_fd)          
files->next_fd = fd + 1;      //fd已经分配成功,更新下一个分配描述符next_fd 。
// 如果next_fd 小于max_fds,则next_fd指下一个可分配的fd。

//如果next_fd已经等于max_fds,则next_fd并不是可分配的fd。

// close 系统调用关闭文件时会回收文件描述符fd,因此next_fd位置可能是0--max_fds之间任意位置。

//close回收文件对象占用的fd的代码如下:(fs/open.c)

static void __put_unused_fd(struct files_struct *files, unsigned int fd)
{
struct fdtable *fdt = files_fdtable(files);
__clear_open_fd(fd, fdt);
if (fd < files->next_fd)   //如果fd小于next_fd,则更新next_fd。
files->next_fd = fd;   //next_fd 的位置可能0--max_fds随机啦!

}

*/

__set_open_fd(fd, fdt);   //更新fd在open_fds所对应的bit。

           error = fd;        //new fd

out:
spin_unlock(&files->file_lock);       //释放锁

return error;        //最后返回新分配的fd
}

好了,有函数alloc_fd的注释,看一下面几个用到的函数就很容易理解了。

int expand_files(struct files_struct *files, int nr)

static int expand_fdtable(struct files_struct *files, int nr)

static struct fdtable * alloc_fdtable(unsigned int nr)

struct files_struct *dup_fd(struct files_struct *oldf, int *errorp)


注:使用dup(), dup2(), fcntl() 两个文件描述符可以指向同一个打开的文件对象。即:

  数组fd_array的两个元素可能指向同一个文件对象。下面看看dup是如何实现的:

fs/fcntl.c

SYSCALL_DEFINE1(dup, unsigned int, fildes)
{
int ret = -EBADF;
struct file *file = fget_raw(fildes);   //通过fd查到从fdtable中查找已有的文件对象file

if (file) {
ret = get_unused_fd();   //重新申请一个新的文件描述符fd
if (ret >= 0)
fd_install(ret, file);     // 当前进程的文件对象file与新的fd关联起来。
else
fput(file);
}
return ret;
}

/kernel/fs/file_table.c

void __init files_init(unsigned long mempages)

struct file *fget(unsigned int fd)

void fput(struct file *file)

struct file *alloc_file(struct path *path, fmode_t mode,const struct file_operations *fop)

struct file *get_empty_filp(void)



好啦,相信这些对理解VFS是非常有意义的!