文件的打开、共享与复制

2016-4-22 chenhui 文件系统

在我们创建一个进程时,系统会根据创建时的参数来决定共享或复制一些文件给新进程。

进程的创建可参考:进程的创建


在了解文件的共享和复制之前,需要先了解进程是怎么打开文件的,而了解进程是怎么打开文件的之前,需要了解进程是怎么管理打开的文件的。


进程描述符(task_struct)中,有一个类型为 files_struct 的 files 字段,这个字段用来管理进程打开的所有文件,其定义如下:


struct files_struct { 
		
		...
		
        int max_fds; 
        int max_fdset; 
        int next_fd;
        struct file ** fd;    
        fd_set *close_on_exec;
        fd_set *open_fds;
        fd_set close_on_exec_init;
        fd_set open_fds_init;
        struct file * fd_array[NR_OPEN_DEFAULT];
};


files_struct 的 fd 字段,指向的是一个 file * 数组,这个数组中保存了所有已打开的文件,我们知道,一个已打开的文件,由 file 描述符来描述,而 open 返回的句柄值,就是 file 描述符在数组内的下标。


在一开始,fd 指针指向另一个字段:fd_array。fd_array 这个数组的长度是 32,这当然不是意味着我们只能打开 32 个文件,在我们打开的文件不超过 32 个时,fd 指向 fd_array,并把 file 描述符存入这个数组。一旦打开的文件超过 32,那么系统就会另外申请一个 file * 数组。


有时候,我们需要使用数组中的一个元素来保存 file 描述符,但有时候又释放掉他,这样这个数组元素就空出来了。但我们怎么知道哪个位置空出来了呢?这就要用到 open_fds 字段,他一开始指向 open_fds_init,open_fds_init 是一个位图,位图中的一个位对应数组一个元素,1 为已打开,0为空闲。根据查找这个位图,可以得知哪个位置可用。


一个进程在刚创建时,他只是父进程的复制体,所以他需要共享或复制父进程打开的文件。这种情况一直到进程载入可执行文件之后才结束。当进程载入可执行文件后,他就成为了真正意义上独立的进程,这时候进程一般情况下要舍弃掉所有继承下来的文件,但是内核允许保留一部分文件。close_on_exec 字段和 open_fds 相似,他一开始指向 open_fds_init,他也是一个对应文件列表的位图,当位图里的某个位为一时,说明他对应的文件要在进程载入可执行文件后被抛弃,如果为零,则保留。


然后再来看文件的共享和复制,这个操作在 do_fork 调用的 copy_process 进行,共有两个函数:


if ((retval = copy_files(clone_flags, p)))
	goto bad_fork_cleanup_semundo;

if (retval = copy_fs(clone_flags, p)) 	
	goto bad_fork_cleanup_files;


copy_files 的作用是复制或继承父进程打开的文件

copy_fs 的作用是复制或继承父进程的文件系统信息


clone_flags 就是创建子进程时传入的参数。


先来看 copy_files:


static int copy_files(unsigned long clone_flags, struct task_struct * tsk)
{
	struct files_struct *oldf, *newf;
	struct file **old_fds, **new_fds;
	int open_files, size, i, error = 0, expand;

	// 当前进程(父进程)的 files_struct
	oldf = current->files; 
	if (!oldf)
		goto out;

	if (clone_flags & CLONE_FILES) {
		//如果设置了CLONE_FILES,就表示子进程和父进程共享文件描述符
		
		atomic_inc(&oldf->count);
		goto out;
	}

	/* 没有设置 CLONE_FILES,子进程不共享父进程的文件,开始复制 */
	
	tsk->files = NULL;
	error = -ENOMEM;
	
	// 创建一个 files_struct,并初始化
	newf = kmem_cache_alloc(files_cachep, SLAB_KERNEL);
	if (!newf) 
		goto out;

	atomic_set(&newf->count, 1);
	spin_lock_init(&newf->file_lock);
	newf->next_fd	    = 0;
	newf->max_fds	    = NR_OPEN_DEFAULT;
	newf->max_fdset	    = __FD_SETSIZE;
	newf->close_on_exec = &newf->close_on_exec_init;
	newf->open_fds	    = &newf->open_fds_init;
	newf->fd	    = &newf->fd_array[0];
	spin_lock(&oldf->file_lock);
	open_files = count_open_files(oldf, oldf->max_fdset);
	
	...

	old_fds = oldf->fd;
	new_fds = newf->fd;
	
	// 复制父进程的 open_fds 和 close_on_exec 到子进程
	memcpy(newf->open_fds->fds_bits, oldf->open_fds->fds_bits, open_files/8);
	memcpy(newf->close_on_exec->fds_bits, oldf->close_on_exec->fds_bits, open_files/8);
	
	for (i = open_files; i != 0; i--) {
		//遍历父进程已经打开的所有的文件
		
		//取出当前已打开的文件
		struct file *f = *old_fds++;
		
		
		if (f) //当前文件存在,递增他的引用计数,因为相对于子进程也打开了他
			get_file(f);
		else  //当前文件不存在,则清楚他对应的位
			FD_CLR(open_files - i, newf->open_fds);
		
		// 存入 fd 数组
		*new_fds++ = f;
	}

	spin_unlock(&oldf->file_lock);
	
	...

	// files_struct生效
	tsk->files = newf;
	error = 0;
out:
	return error;

out_release:
	free_fdset (newf->close_on_exec, newf->max_fdset);
	free_fdset (newf->open_fds, newf->max_fdset);
	free_fd_array(newf->fd, newf->max_fds);
	kmem_cache_free(files_cachep, newf);
	goto out;
}


对于共享父进程打开的文件,那很简单,就直接使用父进程的 files_struct 即可。

对于复制父进程打开的文件,也很简单,申请一个新的,然后把父进程已打开的文件的 file 描述符保存到自己的 files_struct 即可。


接下来就是 copy_fs,他处理的是文件系统信息,他也有共享和复制,只不过从 files_struct 变成了 fs_struct 

这个函数相对来说非常简单,对于复制,不过就是把父进程的工作目录和根目录拿过来罢了。


static inline int copy_fs(unsigned long clone_flags, struct task_struct * tsk)
{
	if (clone_flags & CLONE_FS) {
		atomic_inc(&current->fs->count);
		return 0;
	}
	
	申请一个fs_struct描述符并拷贝父进程的文件系统信息
	tsk->fs = __copy_fs_struct(current->fs);

	if (!tsk->fs)
		return -ENOMEM;
	
	return 0;
}


继续看:


static inline struct fs_struct *__copy_fs_struct(struct fs_struct *old)
{

	// 分配一个 fs_struct 描述符。
	struct fs_struct *fs = kmem_cache_alloc(fs_cachep, GFP_KERNEL);
	if (fs) {
		
		atomic_set(&fs->count, 1);
		rwlock_init(&fs->lock);
		fs->umask = old->umask;
		read_lock(&old->lock); 

		// 得到父进程的根目录
		fs->rootmnt = mntget(old->rootmnt); 
		fs->root = dget(old->root);		
		
		// 得到父进程的工作目录
		fs->pwdmnt = mntget(old->pwdmnt);
		fs->pwd = dget(old->pwd);

		if (old->altroot) {
			fs->altrootmnt = mntget(old->altrootmnt);
			fs->altroot = dget(old->altroot);
		} else {
			fs->altrootmnt = NULL;
			fs->altroot = NULL;
		}
		
		read_unlock(&old->lock);
	}
	return fs;
}



.






发表评论:

Copyright ©2015-2016 freehui All rights reserved