open系统调用源码剖析

1.简介

需要前备知识,对教程的task_struct结构体有一定的了解,尤其是要对其中的struct file有一定了解,明白物理内存与虚拟内存的映射关系,明白文件系统的分层,最最重要的一点是会使用工具追偿源码,现在就来看一看open系统调用。

#define AT_FDCWD		-100    /* Special value used to indicate
                                    openat should use the current working directory. */
//linux-4.13.16\fs\open.c
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{   ...
	return do_sys_open(AT_FDCWD, filename, flags, mode);
}

略去一些细节性的东西(话说看内核源码,最忌讳深入追求细节),AT_FDCWD根据注释应该是指示在当前文件夹下查找吧,接下来调用do_sys_open函数:

long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
    .../*相关数据结构定义与参数检查*/
	fd = get_unused_fd_flags(flags);//不论我文件存在与否,都要一个fd绑定struct file
	if (fd >= 0) {
		struct file *f = do_filp_open(dfd, tmp, &op);//依据tmp结构获取文件的struct file
		if (IS_ERR(f)) {//失败了走这儿。。。
			put_unused_fd(fd);
			fd = PTR_ERR(f);
		} else {//找到了走这儿。。。
			fsnotify_open(f);
			fd_install(fd, f);
		}
	}
	putname(tmp);
	return fd;//为open函数返回的fd
}

关于get_unused_fd_flags获取空闲fd,其实不算太复杂,先找到进程对应的struct file结构体数组,找到还没有被使用的即可,而且剖析源码会发现,使用了好多自旋锁,原因无他,当前进程不想被阻塞。所以主要看do_filp_open看它怎样找到一个struct file结构。

2.敢问我的file结构体在何方

首先认识一个关于保存文件路径等相关信息用于起辅助作用的结构体:

//linux-4.13.16\fs\namei.c
struct nameidata {
	struct path	path; //该结构体标识了文件路径相关的信息
	struct qstr	last;
	struct path	root;
	struct inode	*inode; /* path.dentry.d_inode */
	unsigned int	flags;
	unsigned	seq, m_seq;
	int		last_type;
	unsigned	depth;
	int		total_link_count;
	struct saved {
		struct path link;
		struct delayed_call done;
		const char *name;
		unsigned seq;
	} *stack, internal[EMBEDDED_LEVELS];
	struct filename	*name;
	struct nameidata *saved;
	struct inode	*link_inode;
	unsigned	root_seq;
	int		dfd;
};
//其初始化方式
static void set_nameidata(struct nameidata *p, int dfd, struct filename *name)
{
	struct nameidata *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;//为何要挂在进程上?
}

do_filp_open:

struct file *do_filp_open(int dfd, struct filename *pathname,
		const struct open_flags *op)
{
	struct nameidata nd;
	int flags = op->lookup_flags;
	struct file *filp;

	set_nameidata(&nd, dfd, pathname);//初始化了nd变量,将路径名等信息均保存在该结构
	filp = path_openat(&nd, op, flags | LOOKUP_RCU);//加载文件至内存,返回struct file
	...//失败了怎么处理
	restore_nameidata();
	return filp;
}

其实到此处就应该明白一点,任何东西都不是大白菜,平时文件在磁盘上,想要操作它显然直接从磁盘读取是不合适的,内核设计的文件系统,内存关联设计原理告诉我们它应该会将文件先映射在内存里面,然后初始化我们分配好的struct file 结构体,这上面会有丰富的信息包括文件的inode节点等。故现在我们就来看一看它是如何串联的?

static struct file *path_openat(struct nameidata *nd,
			const struct open_flags *op, unsigned flags)
{
	const char *s;
	struct file *file;
	int opened = 0;
	int error;

	file = get_empty_filp();
    ...
	file->f_flags = op->open_flag;//初始化f_flags
    ...
	s = path_init(nd, flags);//继续初始化struct nameidata结构,返回输入的路径名
    ...//下面link_path_walk进行路径名的划分
	while (!(error = link_path_walk(s, nd)) &&
		(error = do_last(nd, file, op, &opened)) > 0) {...}
	//先解析路径前面的部分,然后调用do_last解析路径最后面的部分
	terminate_walk(nd);
    。。。
	return file;
}

do_last分两种情况解析处理,若在文件路径上找到了要查找的文件的对应的dentry项则返回,否则就调用相关函数在kmem_cache里面分配一个dentry对象:

static int do_last(struct nameidata *nd,
		   struct file *file, const struct open_flags *op,
		   int *opened){
    ...
    error = lookup_fast(nd, &path, &inode, &seq);//在缓存中找dentry
    ...
        error = lookup_open(nd, &path, file, op, got_write, opened);
        //此函数处理dentry没有找到的情况
        //先分配dentry,然后调用inode所在文件系统的lookip_open函数在文件系统中查找dentry
        //查找到就好,若是仍旧没有查找到,在通过看flag是否有O_CREAT标记确定是否是创建文件操作
        //是则创建之,否则返回错误
    error = vfs_open(&nd->path, file, current_cred());
    //这处最后调用vfs_open真正的打开文件		       
}

3.总结

总的来说,这个open系统调用是比较复杂的。

  • 通过task_struct 找到一个空闲文件描述符
  • 根据输入的路径名在dentry对应的高速缓存中找到要打开文件对应的dentry数据结构
  • 若没有找到则调用相关函数在dentry对应的kmem_cache中创建新的dentry对象,然后去fs查找
  • 根据dentry结构找到对应的inode对象用来初始化file结构体
  • 最后调用vfs_open函数真正打开文件

附注

  • 硬盘文件系统由一个个的inode块和普通数据块组成
  • inode结构我们又将它叫做元数据,保存文件好多类型的信息以及具体的数据块
  • ext2与ext3文件系统定义一个大小为15的数组,前面12项直接保存具体的块,后面3项依次为1,2,3级索引。
  • ext4引入了一种叫做extens的数据结构组织形式,它可以放置一个extent_header与4个普通的索引或者叶子节点exten,这样就组成了一个多叉树,扩展性大大提高,文件的容量也大大提高
  • 我们使用inode位图与块位图来组织和管理磁盘上的空闲块。
  • 这样我们就可以串起来了,调用open函数,若确实有这个文件就直接打开就好,若没有且设置了O_CREAT标记,就调用表示这个文件的dentry的inode的create函数创建之,在创建的时候其实就是调用文件系统的相关函数,从位图中找到空闲的inode块
  • 受限于块位图与大量的块以及inode位图与inode块索引的磁盘容量太小的缘故,我们将这样一个组合叫做一个块组,这样一个个块组就构成了我们的文件系统。
  • 对于文件系统中的每一个块组我们有一个超级快,记录了一些相关信息,包括此块组有多少块多少inode等。。。
  • 软硬链接的根本区别就是对inode的使用方式不同,硬链接与文件共享inode而软连接有自己的inode只不过其数据块里面指向的是真正的文件而已。最后,软链接可以跨文件系统,而硬链接不可以。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章