Archive for 2015 年 2 月

open()在Linux内核的实现(3)-“.”和“..”的处理

17 2 月, 2015

1.基本说明

open()在内核的实现过程中,有一大部分工作都是路径查找。路径查找即对用户态传入的文件路径以目录项为单位进行依次遍历。目录项包含五种类型,当目录项为”.”(LAST_DOT)或者“..”(LAST_DOTDOT),那么walk_component()将通过handle_dots()对其进行处理。

实际上,如果当前目录项为“.”,那么该函数什么也不做直接返回(返回到link_path_walk中)即可。也就是说,如果当前目录项为”.”,那么walk_component()此时的作用就是“越过”这些当前目录,而nd信息不做改变,因为所有“.”之前的普通目录项已经更新了nd。比如/home/edsionte/./././doc,目录项edsionte之后的“.”对应的nd与edsionte目录项相同,因此walk_component()在遇到“.”时直接越过他们,进而处理doc目录项。

如果当前目录项为“..”,即当前要walk的目录项为上一次已经walk的目录项的父目录,也就是需要向上获取当前目录的父目录。

2.函数实现

2.1.handle_dots()

该函数的实现比较简单,内部根据当前的walk类型做了函数分发。

static inline int handle_dots(struct nameidata *nd, int type);

如果当前搜索路径的模式位rcu,则进入follow_dotdot_rcu()的流程;否则进入follow_dotdot()的流程。

2.2.follow_dotdot_rcu()

该函数的声明如下:

static int follow_dotdot_rcu(struct nameidata *nd);

该函数是在rcu模式下获取父目录项信息,如果搜索成功,则返回0;否则,返回ECHILD,也就是说需要切换到ref-walk方式下进行搜索路径。该函数的主要处理过程如下:

1.如果有需要的话,首先通过set_root_rcu()设置当前路径的根目录信息。可以在path_init()中获知,只有搜索路径是绝对路径,nd中的root才会在一开始就被设置;否则,比如是相对路径,那么这里就必须对根目录进行设置了。因为此处是向上搜索,可能会一直找到根目录处。

2.进入循环体,向上获取当前目录项的父目录项。通过情况下,这个循环体只会被执行一次即退出,只有当父目录项为一个挂载点时才有可能不断进行循环。

3.如果退出循环体,至此已经获取到了当前目录项的上一级目录项(即“..”所代表的父目录项)。

4.如果这个父目录项是一个挂载点,那么还需做一些特殊检查。因为在特殊情况下,当前这个父目录项又被挂载了其他的文件系统,那么返回上级目录这个操作获取的应该是最新文件系统的内容而不是之前那个文件系统的内容。通过__lookup_mnt()检查父目录下挂载的文件系统是否为最新的文件系统,如果是则检查结束;否则,将继续检查;

5.更新nd中的inode,并返回;

在循环体中,可能会出现三种情况:

a.如果当前目录项恰好为根目录目录项,则直接跳出循环;比如在根目录下执行“cd ../../../”;

b.如果当前目录项既不是根目录,也不是一个挂载点,则属于最普通的情况,即直接获取当前目录项的父目录项。方法很简单,直接用当前目录项的parent(nd->path.dentry->d_parent)覆盖当前的nd结构中的path.dentry即可;

c.如果当前目录项代表的是当前文件系统的根目录,则不能通过简单的获取当前目录项parent这种方法,因为获取的parent为“/”(当然指的是当前文件系统的根目录)。此时需要通过follow_up_rcu()对nd->path进行填充。如果follow_up_rcu()返回1,则循环继续;返回0,则结束循环;

2.3.follow_up_rcu()

如果“..”所代表的目录项正好是一个挂载点时,那么需要将当前的遍历从当前的文件系统向上(follow up)切换到父文件系统。该函数的声明如下:

static int follow_up_rcu(struct path *path);

既然是向上跨越到父文件系统,那么首先将父文件系统的挂载点对应的dentry(nd->path->mnt->mnt_mountpoint)赋值给当前nd中对应的dentry(nd->path->dentry)。因为这个挂载点也就是“..”对应的目录项,挂载点本质也就是个目录。

其次,将父文件系统的vfsmount结构(nd->path->mnt->mnt_parent)复制给当前nd中对应的mnt项(nd->path->mnt)。

通过以上两个步骤,即将当前的walk位置向上移动到父文件系统中的一个目录上。

下面通过举例说明上述过程。假设存在路径/home/edsionte/work,其中fs1文件系统下有目录/home/edsionte/work,文件系统fs2挂载在work目录下,即fs2的挂载点为work。假设work下有文件w1和file,在/home/edsionte/work/file目录下,用户访问的路径为“../w1”,则“..”对应的dentry即为fs2文件系统根目录,则需要跨越到fs1文件系统中。“跨越”操作首先是将当前dentry指向work,也就是下次将要在work目录下寻找w1文件;其次,需要改变当前文件系统状态,即通过替换vfsmount来体现。

完成以上操作,将返回1。也就是说,必须再进行一次follow_dotdot_rcu()中的循环过程,因为follow_up_rcu()完成的只是文件系统的跨越,跨越完毕后必须进行follow_dotdot_rcu()中的循环体工作。

更特殊的是,跨越文件系统之后的这个目录项很可能又是一个挂载点,那么又必须进入follow_up_rcu()中执行上述操作。只有当当前文件系统的挂载点就是自己的时候,即跨越到根文件系统的时候,该函数返回0,那么返回到上级函数follow_dotdot_rcu()中的循环体时,也将结束整个循环过程。

下面再通过举例说明follow_dotdot_rcu()中循环体反复执行的例子。假设fs1文件系统存在路径/home/edsionte/work,首先将fs2文件系统挂载于work下,再将fs3挂载在work下,那么此时fs3对应的父vfsmount为fs2对应的结构;再将fs4文件系统挂载在wrok下,那么fs4指向fs3。此刻,/home/edsionte/work可以访问fs4的内容,而其他之前挂载在这里的文件系统将被隐藏。假设用户在wrok目录下执行“cd ../”,用户想得到的结果是fs1文件系统下edsionte/下的内容。而此刻work位于fs4中,那么他必须向上逐步跨越文件系统,即fs4通过follow_up_rcu()跨越到父文件系统fs3,fs3再跨越到fs2,fs2再跨越到fs1。

2.4.follow_dotdot()

follow_dotdot()和follow_dotdot_rcu()的实现方式几乎一致,只不过该函数在内部实现上使用了读写锁。具体的,主要体现在follow_up()和follow_mount()两个函数的内部,它们的实现过程中均使用了读写锁,这在rcu-walk中是不允许的。

3.总结

本文针对open()在内核中的路径查找过程进行简单说明,并集中关注了“..”这个特殊的目录项。LAST_DOTDOT类型的目录项之所以特殊是由于它向上(follow up)的查找过程与当前所位于的路径查找过程(向下,follow down)相反,同时还会涉及到文件系统挂载点。如果您想了解其他类型目录项的查找过程,可以阅读本系列其他文章。

参考资料:

1.Linux源码3.2.69;

2.Linux系统调用open七日游:http://blog.chinaunix.net/uid-20522771-id-4419666.html

3.深入理解Linux内核:http://book.douban.com/subject/2287506/;

4.深入Linux内核架构:http://book.douban.com/subject/4843567/;

5.Linux内核探秘:http://book.douban.com/subject/25817503/;

open()在Linux内核的实现(2)-路径查找

10 2 月, 2015

1.基本说明

文件的打开操作在内核中的实现思路很简单:即通过用户态传递的路径逐项查找文件;如果该文件存在,那么内核将为该文件创建file结构;同时将该file结构与files数组关联,最终返回数组的索引作为用户态的文件描述符。

路径查找是对给定的文件路径以目录项为单位进行逐级解析。主要包括以下几项内容:

1.确定路径查找的起始位置。比如,起始位置可能是current->fs->cwd或current->fs->root;

2.当前进程是否有对目录项关联的inode进行访问的权限;

3.根据当前的目录项,对下一级目录项进行查找;这里的查找可能是向下查找子文件,也可能是向上反查父目录(比如下一级目录项为“..”);

4.处理挂载点问题;当前目录项如果是挂载点,那么必须处理不同文件系统之间的跨越;

5.处理符号链接文件;如果当前目录项为一个符号链接文件,那么必须追随(follow)该文件所指向的真实文件;

6.查找并创建文件路径中所缺失的部分;比如,通过open()创建一个新文件时,那么所传递的路径中可能有部分目录项当前是不存在的;

其中,第1项是路径查找的首要工作;2~6项是在路径查找过程中,针对每个目录项进行检查确认的。

负责open系统调用基本实现的是do_sys_open(),其内部所调用的do_filp_open函数承担了大部分open的实现过程,其中就包括路径查找。

2.函数分析

2.1.do_filp_open

open操作的核心函数为do_filp_open,它解析文件路径并新建file结构。该函数内部创建nd变量,传入并调用了path_openat()。nameidata类型的nd在整个路径查找过程中充当中间变量,它既可以为当前查找输入数据,又可以保存本次查找的结果。

struct file *do_filp_open(int dfd, const char *pathname,
		const struct open_flags *op, int flags)
{
	struct nameidata nd;
	struct file *filp;

	filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU);
	if (unlikely(filp == ERR_PTR(-ECHILD)))
		filp = path_openat(dfd, pathname, &nd, op, flags);
	if (unlikely(filp == ERR_PTR(-ESTALE)))
		filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_REVAL);
	return filp;
}

在这个函数中,path_openat有可能会被调用三次。通常内核为了提高效率,会首先在RCU模式(rcu-walk)下进行文件打开操作;如果在此方式下打开失败,则进入普通模式(ref-walk)。第三次调用比较少用,目前只有在nfs文件系统才有可能会被使用。接下来将主要说明前两种调用方式。

2.2.path_openat

path_openat()其函数声明如下:

static struct file *path_openat(int dfd, const char *pathname,
		struct nameidata *nd, const struct open_flags *op, int flags);

该函数描述了整个路径查找过程的基本步骤,这里做简单说明。每个具体步骤的实现过程,将在本文以及后续文章中做详析说明。

1.首先通过get_empty_flip()分配一个新的file结构,分配前会对当前进程的权限和文件最大数进行判断;

2.path_init()对接下来的路径遍历做一些准备工作,主要用于判断路径遍历的起始位置,即通过根目录/,或当前路径(pwd),或指定路径(openat系统调用可以指定);

3.将当前进程的total_link_count置为0;

3.link_path_walk()对所打开文件路径进行逐一解析,每个目录项的解析结果都存在nd参数中;

4.根据最后一个目录项的结果,do_last()将填充filp所指向的file结构;

5.如果上一步中的filp所指为空,将说明当前文件为符号链接文件;

6.如果设置了LOOKUP_FOLLOW标志,则通过follow_link()进入符号链接文件所指文件,填充file;否则,直接返回当前符号链接文件的filp;

7.最终返回file结构;

2.3.path_init

path_init()用于设置路径搜寻的起始位置,主要体现在设置nd变量。其函数声明如下:

static struct file *path_openat(int dfd, const char *pathname,
		struct nameidata *nd, const struct open_flags *op, int flags);

如果flags设置了LOOKUP_ROOT标志,则表示该函数被open_by_handle_at函数调用,该函数将指定一个路径作为根;这属于特殊情况,这里暂不分析;接下来path_init主要分三种情况设置nd。

1.如果路径名name以/为起始,则表示当前路径是一个绝对路径,通过set_root设置nd;否则,表示路径name是一个相对路径;

2.如果dfd为AT_FDCWD,那么表示这个相对路径是以当前路径pwd作为起始的,因此通过pwd设置nd;

3.如果dfd不是AT_FDCWD,表示这个相对路径是用户设置的,需要通过dfd获取具体相对路径信息,进而设置nd;

上述步骤2和3都表示要打开的文件路径是以相对路径为起始的,但是两者稍有不同。步骤2为我们通常默认的open操作,而步骤3具体指的是openat系统调用,这一点体现在不同打开系统调用向do_sys_open中dfd参数所传递的值。

不管上述哪一种打开情况,均要设置nd变量,它是一个nameidata类型。在path_init中,nd的last_type都被默认设置成了LAST_ROOT。

在path_init中,如果为上述步骤1,则通过当前进程的fs->root字段更新nd的root字段,并且nd的path字段也指向root字段;如果为步骤2,则通当前进程fs->pwd更新nd的path字段;如果为步骤3,则先通过文件描述符dfd获取用户指定的工作目录file结构,然后通过file的f_path字段更新nd的path字段。需要注意的,步骤2和步骤3均未设置root字段。最终,nd中的inode字段均由path.dentry->d_inode更新。

2.4.link_path_walk

link_path_walk()主要用于对各目录项逐级遍历。其函数声明如下:

static int link_path_walk(const char *name, struct nameidata *nd);

该函数核心部分是通过一个循环完成的。在进入这个循环之前,如果路径name是一个绝对路径,那么该函数还对路径进行了一些处理,即过滤掉绝对路径/前多余的符号/。

在循环中,所要做的工作包含如下:

1.next为path类型的变量,指向下一个目录项;name指向被搜索的路径;this为qstr类型变量,表示当前搜索路径所处目录项的哈希值,用type指明当前目录项类型;

2.如果有必要,为当前目录项更新哈希值,并保存在this中;

3.如果当前目录项为“.”,则type为LAST_DOT;如果目录项为“..”,则type为LAST_DOTDOT;否则,type默认为LAST_NORM;

4.如果当前目录项紧邻的分隔符/有多个(比如/home///edsionte),则将其过滤,即使name指向最后一个/;

5.通过walk_component()处理当前目录项,更新nd和next;如果当前目录项为符号链接文件,则只更新next;

6.如果当前目录项为符号链接文件,则通过nested_symlink()进行处理,更新nd;

7.如果name中的目录项遍历完毕,则结束;否则进行下一轮循环;

通过上述循环,将用户所指定的路径name从头至尾进行了搜索,至此nd保存了最后一个目录项的信息,但是内核并没有确定最后一个目录项是否真的存在,这些工作将在do_last()中进行。

2.5.walk_component

walk_component()位于link_path_walk函数之中。该函数声明如下:

static inline int walk_component(struct nameidata *nd, struct path *path,
		struct qstr *name, int type, int follow)

在每次循环中,它将获取当前目录项的dentry结构以及inode结构等信息,即更新nd。如果当前目录项对应的inode不存在,那么将向用户态返回ENOENT;在该函数中,定义了变量inode,它将保存当前目录项对应的索引节点。

根据当前目录项类型的不同,对目录项的处理流程也不同。该函数的具体流程如下:

1.如果type为LAST_DOT和LAST_DOTDOT,将进入handle_dots()对当前目录项进行“walk”;

2.如果当前目录项为普通目录项,则通过do_lookup()对其进行处理;

3.如果should_follow_link()获知当前目录项为符号链接文件,则退出当前函数。具体的,如果当前walk模式为rcu,则直接返回-ECHILD,否则返回1。返回-ECHILD时候,将直接返回到do_filp_open(),进行ref-walk模式重新查找;如果返回1,则返回至上层函数link_path_walk(),进入netsted_symlink()进行符号链接目录项的处理;

也就是说,一旦当前目录项为符号链接文件,则需要通过ref-walk进行处理。这是因为处理符号链接文件需要通过具体文件的处理函数进行实现,这个过程可能会导致阻塞,这与rcu方式是违背的,因此需要先转换到ref-walk;

4.至此,如果当前目录项查找成功,则通过path_to_nameidata()更新nd;

3.总结

本文重点说明了open实现过程中的路径查找过程。open中的路径查找是针对用户所传递路径,按照目录项逐级进行遍历查找;对于路径中的每个目录项,不同类型的目录项有不同的处理方法。如果需要了解对“.”、“..”以及符号连接文件的处理方法,可以阅读本系列后续文章。

参考资料:

1.Linux源码3.2.69;

2.Linux系统调用open七日游:http://blog.chinaunix.net/uid-20522771-id-4419666.html

3.深入理解Linux内核:http://book.douban.com/subject/2287506/;

4.深入Linux内核架构:http://book.douban.com/subject/4843567/;

5.Linux内核探秘:http://book.douban.com/subject/25817503/;

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key