存档在 2015年1月

open()在Linux内核的实现(1)-基本实现

2015年1月4日

1.基本说明

在用户态使用open()时,必须向该函数传入文件路径和打开权限。这两个参数传入内核后,内核首先检查这个文件路径存在的合法性,同时还需检查使用者是否有合法权限打开该文件。如果一切顺利,那么内核将对访问该文件的进程创建一个file结构。

在用户态,通常open()在操作成功时返回的是一个非负整数,即所谓的文件描述符(fd,file descriptor);并且,用户态后续对文件的读写操作等都是通过fd来完成的。由此可见fd与file结构在内核中有一定的关联。

具体的,内核使用进程描述符task_struct来描述一个进程,而该进程所有已打开文件对应的file结构将形成一个数组files(其为files_struct结构),内核向用户返回的fd便是该数组中具体file结构的索引。默认情况下,每个进程创建后都已打开了标准输入文件、标准输出文件、标准错误文件,因此他们的文件描述符依次为0、1和2。

2.函数分析

2.1.do_sys_open

明白了上述原理,那么open系统调用在内核中的基本实现过程就很清晰。根据系统调用入口函数的命名规则,open系统调用的入口函数应该为sys_open。不过,目前内核统一使用SYSCALL_DEFINEn宏来描述系统调用入口函数,因此可以在open.c文件中找到该入口函数,具体如下所示:

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode);

该函数内部直接调用了do_sys_open函数,具体声明如下:

long do_sys_open(int dfd, const char __user *filename, int flags, int mode);

这个函数的参数基本上与open系统调用的参数一致。

该函数可以简单概括open系统调用的功能:

1.通过build_open_flags()将用户态的flags和mode转换成对应的内核态标志;

2.由于filename是用户态的内存缓冲区(使用了__user修饰),因此通过getname()将文件名从用户态拷贝至内核态;

3.get_unused_fd_flags()为即将打开的文件分配文件描述符;也就是在当前进程的files数组中寻找一个未使用的位置;

4.通过do_filp_open()为文件创建file结构体;

5.如果创建file成功,则通过fd_install()将fd和file进行关联;如果创建file失败,通过put_unused_fd()将已分配的fd返回至系统,并且根据file生成错误的fd;

6.通过putname()释放在内核分配的路径缓冲区;

7.返回fd;

当open系统调用执行完毕后,fd返回用户态,内核态新建了与其关联的file;对于每个进程而言,通过files_struct来记录其所打开的文件,具体通过fd_array数据保存fd和file的对应关系,fd本质为该数组的索引。

3.总结

至此,open的基本实现过程已经分析完毕。不过do_sys_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/;

open()在Linux内核的实现-准备工作

2015年1月4日

1.基本说明

“open()在Linux内核的实现”系列文章将分析open系统调用在Linux内核中的实现过程。本系列文章分为六篇,每篇文章都描述 open()实现的一部分内容,与前后的系列文章保持相对独立。本文属于前序文章,集中说明后续文章涉及到的基本原理和基本数据结构,并且对整个分析过程进行Q&A。

本系列文章参考Linux内核源码版本为3.2.69。

2.数据结构

dentry结构

对于打开文件这个操作来说,它是通过路径名查找对应文件inode的过程,这里用户直面的是文件路径,而内核关注的inode。在文件路径和inode之间通过目录项(dentry)缓存进行关联,dentry缓存加快了vfs对文件的查找。所有的目录项通过散列表进行组织,这样可以快速对dentry进行查找;此外,内核将常用的dentry通过LRU算法进行组织,这样可以快速查到最近一段时间经常使用的dentry。

下面将对dentry中的部分字段进行说明。

d_inode:该字段指向目录项所关联的文件。如果该字段为空,则说明当前目录项指向的是一个并不存在的文件。

d_name:该字段表示目录项名称(并不是整个路径名),但它并不是单纯的字符串,而是将字符串文件名、字符串长度和散列值封装成qstr(quick string)结构,这样可以加速目录项的查找工作;

d_iname:当目录项名称长度小于DNAME_INLINE_LEN时,则该字符串名称则直接通过该字段进行存储;

d_parent:一个路径中的目录项形成层级结构。该字段指向当前目录项的父目录dentry实例;特别的,对于根目录项来说,这个字段指向自己;

d_subdirs:当前目录项如果代表目录,则该目录下的所有文件对应的dentry将形成d_subdirs链表(表头);

d_child:这个字段是父目录dentry中d_subdirs链表中的结点;

d_alias:一个文件可能有多个名称(硬链接),即多个dentry,则一个文件的所有目录项则形成一个链表,这个链表头位于该文件inode中的i_dentry字段,d_alias充当的该链表中的结点;

vfsmount结构

每个挂载在内核目录树中的文件系统都将对应一个vfsmount结构,下面将对该结构中的部分字段进行说明。假设设备/dev/sdc为ntfs文件系统,现需要将其挂载在文件系统为ext3的/home/edsionte/work下。因此,/home/edsionte/work可以被称为ntfs文件系统的挂载点,并且称ntfs文件系统与ext3文件系统形成父子文件文件系统关系。同时ntfs也可称为源文件系统,而ext3也可称为目的文件系统。

mnt_hash:内核将系统内所有已挂载的文件系统通过散列表的形式进行组织,每个vfsmount将处于其对应哈希值的冲突链表当中。mnt_hash字段则为具体冲突链表的元素。

mnt_mounts:如果当前文件系统下挂载了其他的子文件系统,那么这些子文件系统将通过自身vfsmount中的mnt_child字段组成一个链表,该链表头为父文件系统中的mnt_mounts字段。

mnt_child:当前文件系统将通过该字段与其他父文件系统下的子文件系统组成一个链表。

mnt_parent:该字段指向父文件系统对应的vfsmount结构。即指向ext3文件系统对应的vfsmount结构。

mnt_mountpoint:该字段表示源文件系统在目的文件系统中挂载点对应的dentry结构。/home/edsionte/work为挂载点,则该字段指向目录项work。

mnt_root:指向当前文件系统的根目录项。对于源文件系统ntfs来说,根目录项相对为/,但在整个系统目录树中,根目录项为work。

mnt_sb:每个文件系统都将对应一个super_block结构,该字段指向/dev/sdc设备上文件系统对应的超级块。

mnt_list:所有处于一个名字空间的文件系统通过mnt_list字段链接在一起,而该链表的表头为该名字空间结构中的list字段。

mnt_ns:该字段表示当前vfsmount所对应的名字空间结构。

nameidata结构

文件路径是由各级别的目录项组成的,因此路径的查找过程是对目录项的逐级查找。nameidata结构是路径查找过程中的核心数据结构,在每一级目录项查找过程中,它向查找函数输入参数,并且保存本次查找的结果,因此它是不断变化的。

下面对nameidata结构中的部分字段进行说明。

path:该字段用于保存当前目录项。该字段是path结构,该结构将目录项和该目录项所关联的vfsmount结构进行封装。

last:该字段为qstr结构,表示当前目录项的名称。

root:该字段为path结构,表示根目录。

last_type:表示当前目录项的类型。

inode:表示当前目录项对应的inode,它的取值来自于path.dentry.d_inode。

depth:表示符号链接当前的嵌套级别,最大不能超过MAX_NESTED_LINKS;

saved_names:该字符串数组表示符号链接每个嵌套级别的名称;

目录项的类型包括以下几种情况:

LAST_NORM:普通目录项;

LAST_ROOT:当前目录项为/;

LAST_DOT:当前目录项为.;

LAST_DOTDOT:当前目录项为..;

LAST_BIND:当前目录项为符号链接文件;

3.基本原理

rcu机制

写时拷贝(rcu,Read-Copy-Update)是Linux内核的一种锁机制,它是一种改良的rwlock(但并不能代替),适合读者多写者少的情景,可以保证读写者操作同时进行。

对于读者而言,rcu机制可以保证多个读者在不申请锁的情况下直接对临界区资源进行访问。对于写者而言,它之所以可以与读者同时访问共享资源,是因为在读者读取原始数据的同时它修改的是原始数据的备份。当所有读者都退出访问该共享资源时,写着将用修改后的新数据替换原始数据。同时,rcu中的回收机制将对原始数据进行回收。

与rwlock相比,在读多写少的情况下,rcu的效率会高很多。因为rcu所提供的拷贝技术使读写者可以同时访问共享资源,因此免去了读写者申请锁时所花费的开销。

由于rcu机制的自身特点,它所使用的上下文必须是不可睡眠的。因为,写者在替换原始数据之前会等待所有读者退出临界区,而此时如果读者处于阻塞状态,那么系统将进入死锁状态。

rcu-walk和ref-walk

内核中的路径查找提供两种模式:ref-walk和rcu-walk。前者是内核中传统的路径查找方式,而ref-walk是基于rcu所机制的一种路径查找模式。由于路径查找正好是一个读多写少的情景,基于rcu机制快速高效的特点,该模式可以高效的进行路径查找。不过,rcu-walk并不是万能的,如果路径查找过程中需要睡眠,那么必须将查找模式由rcu-walk切换到ref-walk。

4.总结

本篇对open()在内核实现中所涉及的数据结构和原理进行实现说明,并且针对open()实现过程的一些问题进行Q&A。可以在阅读open()内核源码之前阅读本文,也可在阅读之后再次阅读本文。

参考资料:

1.Linux源码3.2.69;

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

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

4.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