struct files_struct和struct fdtable
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;
};
在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),
};
下图展示了上述代码初始化后各结构成员之间的关系:
我们知道,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,
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;
...
}