struct files_struct和struct fdtable

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/metersun/article/details/80513702
struct files_struct和struct fdtable的初始化
我们先来列出struct files_struct和struct fdtable的定义,为了讨论方面,下面的定义中略去了很少一部分的锁成员,下面的代码均摘自linux 2.6.24。
struct files_struct在<include/linux/fdtable.h>中定义如下:
struct files_struct {
    atomic_t count;
    struct fdtable *fdt;
    struct fdtable  fdtab;
   
    int next_fd;
    struct embedded_fd_set close_on_exec_init;
    struct embedded_fd_set open_fds_init;
    struct file * fd_array[NR_OPEN_DEFAULT];
};


struct fdtable定义如下:
struct fdtable {
    unsigned int max_fds;
    struct file ** fd; /* 当前fd_array */
    fd_set *close_on_exec;
    fd_set *open_fds;
    struct rcu_head rcu;
    struct files_struct *free_files;
    struct fdtable *next;
};
在struct files_struct中包括一个struct fdtable变量实例和一个struct fdtable类型指针,而struct files_struct中的成员变量close_on_exec,open_fds,fd又分别指向struct files_struct中的成员close_on_exec_init,open_fds_init和fd_array。即一个结构中嵌套了另一个结构,被嵌套结构中的成员又反过来引用了属主的成员,这样看起来有些重复和让人迷惑。
在files_struct结构的初始化时,能更清晰的看出这种重复。如内核第一个进程(即进程init)的files_struct静态初始化:
struct files_struct init_files = {
    .count      = ATOMIC_INIT(1),
    .fdt        = &init_files.fdtab,
    .fdtab      = {
        .max_fds        = NR_OPEN_DEFAULT,
        .fd               = &init_files.fd_array[0],
        .close_on_exec  = (fd_set *)&init_files.close_on_exec_init,
        .open_fds       = (fd_set *)&init_files.open_fds_init,
        .rcu            = RCU_HEAD_INIT,
    },
    .file_lock  = __SPIN_LOCK_UNLOCKED(init_task.file_lock),
};

下图展示了上述代码初始化后各结构成员之间的关系:

可以看出,files_struct的成员fdt在这里初始化为其内嵌的struct fdtable变量实例(成员fdtab)地址,内嵌的struct fdtable实例变量的fd,close_on_exec,open_fds又指向属主的相应变量。那么这个fdtab存在的必要意义是什么呢(在此处看不出来,即使去除了该变量,也足以表达files_struct)?
我们知道,linux系统中,一个进程打开的文件数是有初步限制的,即其文件描述符数初始时有最大化定量,即一个进程一般只能打开NR_OPEN_DEFAULT个文件,该值在32位机上为32个,在64位机上为64个。上面的files_struct这种初始化正是体现了进程初步所能打开文件的内核结构描述。这里需要说明的是,这不仅仅限于类似上面的静态初始化,当init进程fork一个子进程时,也是如此(此时是files_struct是动态分配的)。
见下面的代码:
static struct files_struct *alloc_files(void)
{
    struct files_struct *newf;
    struct fdtable *fdt;
    newf = kmem_cache_alloc(files_cachep, GFP_KERNEL);
    if (!newf)
        goto out;
    atomic_set(&newf->count, 1);
    spin_lock_init(&newf->file_lock);
    newf->next_fd = 0;
    fdt = &newf->fdtab;
    fdt->max_fds = NR_OPEN_DEFAULT;
    fdt->close_on_exec = (fd_set *)&newf->close_on_exec_init;
    fdt->open_fds = (fd_set *)&newf->open_fds_init;
    fdt->fd = &newf->fd_array[0];
    INIT_RCU_HEAD(&fdt->rcu);
    fdt->next = NULL;
    rcu_assign_pointer(newf->fdt, fdt);
out:
    return newf;
}

函数alloc_files的被调用流程是:
do_fork------>copy_process
    |------>dup_task_struct------>alloc_task_struct
    |------>copy_files------>dup_fd------>alloc_files
即先调用alloc_task_struct分配一个task_struct结构实例,然后使用alloc_files来分配并初始化files_struct变量实例。
上面的 files_struct初始化和上面例子中的静态初始化并无本质差别:
语句fdt = &newf->fdtab取出newf的struct fdtable实例变量fdtab的指针,然后通过rcu_assign_pointer函数将其赋值给newf->fdt,那么newf->fdt还是指向其自身中的struct fdtable实例变量,fdt的成员close_on_exec、open_fds和fd也是如此。

struct files_struct扩充
当进程打开的文件数超过NR_OPEN_DEFAULT时,就要在对上面的已初始化的struct files_struct(更准确的说是其所包含的数据对象)进行扩充。
上面对于struct files_struct和struct fdtable这种互相引用及其冲突讨论了很多,其实内核中之所以还使用了struct fdtable,根本目的是为了应对这种扩充。
当进行struct files_struct扩充时,会分配一个新的struct fdtable,为了叙述方便,下面该变量用指针用nfdt来表示。另外还分配了满足扩充要求的fd数组(即struct file数组),以及与fd相对的bitmap描述close_on_exec,open_fds的存储空间。
然后将新分配的close_on_exec,open_fds,fd空间指针赋值给nfdt->close_on_exec,nfdt->open_fds和nfdt->fd。注意,这里的close_on_exec,open_fds和上面初始化时close_on_exec_init,open_fds_init的差别:
close_on_exec,open_fds的最大值按bit来说为__FDSET_LONGS,实际值为1024位,即文件描述符的最大数为1024个。但它们也是按需分配,并和file数组的大小一致,分配的实际值同时赋值给nfdt->max_fds。
分配并初始化新的struct fdtable变量后,原先指向fdtab的struct files_struct指针成员fdt,会调整为指向新分配的struct fdtable变量。这时,struct files_struct实例变量中就包含两个struct fdtable存储区:一个是其自身的,一个新分配的,用fdt指向。
执行完上面的操作后,其关系如下图:

注意上图中同颜色的存储区,代表的是两者内容是一致的。即执行完上述的操作后,还要将的结构存储区的内容拷贝到新的存储区,这包括files_struct自身所包含的close_on_exec,open_fds,fd到新分配的close_on_exec,open_fds,fd的拷贝。
执行完上述拷贝之后,就要释放旧的struct fdtable,但这里并不执行执行该项释放操作(下面的讨论中会说明何时才进行释放操作)。

struct files_struct扩充使用内核源码中的expand_files来实现,expand_files会调用expand_fdtable:
static int expand_fdtable(struct files_struct *files, int nr)
{
    struct fdtable *new_fdt, *cur_fdt;
    new_fdt = alloc_fdtable(nr);      //分配了一个fdtable
    cur_fdt = files_fdtable(files);   //files->fdt
    if (nr >= cur_fdt->max_fds) {
        /* Continue as planned */
        copy_fdtable(new_fdt, cur_fdt);   //拷贝了其中的3个变量:fd,open_fds,close_on_exec
        rcu_assign_pointer(files->fdt, new_fdt);  //将新分配的fdtable赋值给files的fdt
          if (cur_fdt->max_fds > NR_OPEN_DEFAULT)  //注意它第一次初始化为NR_OPEN_DEFAULT
            free_fdtable(cur_fdt);
     }
    return 1;
}

上面代码中包括:
先使用函数alloc_fdtable分配一个struct fdtable变量,以及close_on_exec,open_fds,file数组
因为我们这里讨论的是扩充,所以接下来的if判断为真,那么执行旧的数据区到新分配数据区的拷贝,即函数copy_fdtable,拷贝操作上面已经进行过讨论,在此不再赘述。这里重点说明旧的数据区的释放:
if (cur_fdt->max_fds > NR_OPEN_DEFAULT)  
    free_fdtable(cur_fdt);
此处的cur_fdt是旧的struct fdtable变量指针,它的文件描述符数初始化为NR_OPEN_DEFAULT,显然if判断为假,并不执行free_fdtable来进行旧的struct fdtable释放。那么何时才进行释放呢?
我们下面来分析free_fdtable函数,该函数会引起free_fdtable_rcu的调用,简化的free_fdtable_rcu函数代码如下:
void free_fdtable_rcu(struct rcu_head *rcu)
{
    struct fdtable *fdt = container_of(rcu, struct fdtable, rcu);
    struct fdtable_defer *fddef;
 
    if (fdt->max_fds <= NR_OPEN_DEFAULT) {
          kmem_cache_free(files_cachep,
                container_of(fdt, struct files_struct, fdtab)); //这里释放的是files_struct本身
        return;
    }
    //上面是最初的分配,下面的是扩展分配(本次是再次扩展),注意扩展分配时fd,open_fds都是动态分配的,
    //所以可以用free释放掉。
    //另外,这里并没有显式的释放close_on_exec指向的内存
    if (fdt->max_fds <= (PAGE_SIZE / sizeof(struct file *))) {
        kfree(fdt->fd);
        kfree(fdt->open_fds);
        kfree(fdt);
    } else {
            .....
    }
}
上面的代码中,开头的if (fdt->max_fds <= NR_OPEN_DEFAULT)判断语句为真,此时是struct files_struct没有进行扩充,而需要释放files_struct本身时,对free_fdtable_rcu的调用。这时,会执行kmem_cache_free,以container_of(fdt, struct files_struct, fdtab)为需要释放存储空间的指针参数,来完成释放。

下一个if语句块就是扩充后struct files_struct的释放,注意此时暗含的条件是fdt->max_fds大于NR_OPEN_DEFAULT,即进行扩充后的释放,但我们前面讨论的expand_fdtable不会执行到free_fdtable_rcu(第一次扩充),而此处的fdt->max_fds大于NR_OPEN_DEFAULT表示的是二次扩充,即在我们前面讨论的扩充基础上的再次扩充,此时就需要释放上面讨论的新分配的struct fdtable,以及open_fds和file array。
这里并没有看到释放close_on_exec,其实在函数alloc_fdtable中分配open_fds和close_on_exec时,是将二者统一分配的,所以上面的kfree(fdt->open_fds)语句用来释放这个统一分配的存储区,close_on_exec的分配空间一同释放。
最后列出空间分配函数alloc_fdtable的代码:
static struct fdtable * alloc_fdtable(unsigned int nr)
{
    struct fdtable *fdt;
    char *data;

    data = alloc_fdmem(nr * sizeof(struct file *));  //这里可以看出fdt->max_fds是最大的文件描述
                                                      //符, 即分配了多少个file结构体
    fdt->fd = (struct file **)data;

    //下面乘2的意思是open_fds + close_on_exec
    data
= alloc_fdmem(max_t(unsigned int,
                 2 * nr / BITS_PER_BYTE, L1_CACHE_BYTES)); 
    fdt->open_fds = (fd_set *)data;
    data += nr / BITS_PER_BYTE;
    fdt->close_on_exec = (fd_set *)data;
    INIT_RCU_HEAD(&fdt->rcu);
    fdt->next = NULL;
    return fdt;
     ...
}






展开阅读全文

没有更多推荐了,返回首页