日志标签 ‘文件系统’

open()在Linux内核的实现(4)-普通目录项的处理

2015年3月3日

1.基本说明

在open()的路径查找过程中,walk_component()将对路径中的每个目录项进行遍历,当目录项为普通目录项时,将通过do_lookup()对其进行查找。在路径查找过程中,普通目录项算是比较常见的,因此更应该采取高效的方法节省查找时间。

为了提高查找效率,内核通过rcu-walk方式避免ref-walk由于锁机制而引入的阻塞问题;其次,内核通过dentry缓存的方式将目录项以哈希表的方式组织起来,以便提高查找效率。这两种方式在一定程度上可以提高路径查找的效率,不过它们并不是适合所有情况。比如,rcu-walk方式不能使用可能会引起阻塞的函数,有时候要查找的目录项并不位于缓存当中而只能从磁盘上获取信息。

由于上述提高查找效率的方式可能会查找失败,因此内核一般会经过多次目录项的查找。也就是说,内核会先进行高效的查找方式,如果在这种模式下查找失败,那么再进行传统的查找方式,保证查找工作的正常进行。具体的,内核会先进行rcu-walk方式,如果查找失败,则进行ref-walk方式;内核会优先在目录项缓存中进行快速的查找,如果查找失败,则通过访问磁盘进行慢速的查找。

2.函数实现

2.1.do_lookup()

内核通过do_lookup()进行普通目录项的查找工作。该函数声明如下:

tatic int do_lookup(struct nameidata *nd, struct qstr *name,
	struct path *path, struct inode **inode);

这个函数主要通过区分不同的情景来选择对应的查找方式。总体来说,先以rcu-walk方式在目录项缓存中查找,如果成功,则查找结束返回;否则,将rcu-walk切换到ref-walk模式;ref-walk模式下会先进行目录项缓存的查找,如果查找成功,则返回;否则,进行ref-walk下的磁盘查找;如果成功,则返回,否则,查找失败并返回错误码ENOENT;该错误码对应到用户态即为“No such file or directory”。

如果以rcu-walk模式进入该函数,则主要的查找步骤如下:

1.如果当前是rcu-walk模式(设置了LOOKUP_RCU),则先通过__d_lookup_rcu在目录项缓存中查找;

2.如果查找失败,则跳入unlazy标号处;如果查找成功,则继续;

3.通过follow_mount_rcu()判断当前的目录项是否为挂载点,如果是,则跨越挂载点;否则继续;

4.如果rcu-walk方式下查找缓存成功,则返回0;否则,进入unlazy标号;

5.unlazy标号是将当前的rcu-walk切换成ref-walk模式;如果切换成功,则继续;否则,返回ECHILD。即返回到do_filp_open()处,重新进行ref模式的查找;

6.当前查找模式为ref-walk,并且此时在缓存中并未找到对应的目录项;那么接下来必须试图在磁盘上进行查找了。不过在进行磁盘查找之前,还是会再次调用d_lookup()进行一次内存查找,因为retry标号下的代码有互斥锁,很可能该函数再此处会阻塞,而在阻塞阶段就目标目录项就有可能被载入内存,这样就可以省去在磁盘上查找目录项的工作;

7.如果d_lookup()查找成功,则继续;否则,通过d_alloc_and_lookup()分配并在磁盘上查找dentry;具体的,调用当前文件系统的lookup钩子函数;如果磁盘查找失败,则错误返回;否则,继续;

8.此时,已获取到要查找的目录项;则再通过follow_managed()对当前目录项进行检查,比如,检查当前目录项是否为挂载点;

9.获取当前目录项对应的inode信息;

10.返回;

如果以ref-walk模式进入该函数,则首先会通过__d_lookup()在目录项缓存中查找;如果查找成功,则接下来的步骤与rcu-walk步骤8-10相同;如果查找失败,则接下来的步骤与rcu-walk步骤6-10相同。

3.总结

普通目录项的查找工作其实并不复杂,无非是在内存中查找或在磁盘中查找。只不过,内核为了提高路径查找效率同时引入了rcu-walk方式,从而增加了代码分析的复杂度。如果当前目录项为符号链接文件,则内核的处理方式又是另一种方式,具体的实现过程可参见本系列的下一篇文章。

参考资料:

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/;

Linux内核中通过文件描述符获取绝对路径

2014年3月19日

背景

在Linux内核中,已知一个进程的pid和其打开文件的文件描述符fd,如何获取该文件的绝对路径?基本思路是先获取该文件在内核中的file结构体,再通过d_path()获取到整个文件的绝对路径。

方法一

如果理解了进程和文件系统数据结构之间的关系,那么这种方法可以采用。基本的方法如下:

1.通过进程pid获取进程描述符task_struct;

2.通过task_struct获取该进程打开文件结构files_struct,从而获取文件描述符表;

3.以fd为索引在文件描述符表中获取对应文件的结构体file;

4.通过file获取对应path结构,该结构封装当前文件对应的dentry和挂载点;

5.通过内核函数d_path()获取该文件的绝对路径;

通过进程pid获取进程描述符demo:

struct task_struct *get_proc(pid_t pid)
{
	struct pid *pid_struct = NULL;
	struct task_struct *mytask = NULL;

	pid_struct = find_get_pid(pid);
	if (!pid_struct)
		return NULL;
	mytask = pid_task(pid_struct, PIDTYPE_PID);
	return mytask;
}

通过fd以及d_path()获取绝对路径demo:

int get_path(struct task_struct *mytask, int fd)
{
        struct file *myfile = NULL;
        struct files_struct *files = NULL;
        char path[100] = {'\0'};
        char *ppath = path;

        files = mytask->files;
        if (!files) {
                printk("files is null..\n");
                return -1;
        }
        myfile = files->fdt->fd[fd];
        if (!myfile) {
                printk("myfile is null..\n");
                return -1;
        }
        ppath = d_path(&(myfile->f_path), ppath, 100);

        printk("path:%s\n", ppath);
        return 0;
}

从上面的代码可以看出,从fd到file结构的获取均通过各个数据结构之间的指向关系获取。

方法二

与方法一的思路相同,但是可以直接使用内核提供的函数fget()进行fd到file的获取。这种方法使用比较简单,程序更加安全,不过就是少了对数据结构关系的思考过程。其实也可以将fget()函数的实现过程作为参考,欣赏内核中代码实现的严谨性。

 

Linux内核文件系统挂载分析

2014年2月25日

本文将针对内核版本3.2.0中的mount系统调用实现过程进行简单说明。

1.数据结构

下面将对文件系统挂载过程中涉及到的两个主要数据结构vfsmount和path进行节本说明。

1.1 struct vfsmount

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

struct list_head mnt_hash;

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

struct list_head mnt_mounts;

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

struct list_head mnt_child;

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

struct vfsmount *mnt_parent;

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

struct dentry *mnt_mountpoint;

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

struct dentry *mnt_root;

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

struct super_block *mnt_sb;

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

struct list_head mnt_list;

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

struct mnt_namespace *mnt_ns;

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

1.2 struct path

path结构由vfsmount结构和dentry结构组成。该结构在挂载文件系统时表示目的文件系统的vfsmount结构和挂载点dentry。

2.函数调用关系图

do_mount

3.实现

3.1 mount系统调用服务例程

mount()系统调用服务例程为:

SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name, char __user *, type, unsigned long, flags, void __user *, data)

其内部实现主要是将用户态的参数依次复制到内核态,接着调用内核函数do_mount()。

3.2 do_mount()

该函数内部首先通过kern_path()获取目的文件系统的path结构,即挂载点目录项以及目的文件系统的vfsmount结构;接着,通过检查flags对挂载操作进行不同目的的分发。这里我们只讨论最普通的情形,即将一个文件系统挂载在一个新的挂载点中,这种情况调用do_new_mount()。

3.3 do_new_mount()

这个函数描述的是挂载一个新文件系统最普遍的情形,主要包括以下几点:

1.文件系统类型、操纵权限检查等;

2.通过do_kern_mount()获取源文件系统的vfsmount结构;

3.通过do_add_mount()将源文件系统增加到目的文件系统中;

3.4 do_add_mount()

1.flags参数合法性检查;

2.检查指定的目的文件系统是否为当前文件系统。如果是,则失败;

3.检查源文件系统的根inode是否为链接文件。如果是,则失败;

4.通过graft_tree()将源文件系统装载到目的文件系统中。其内部graft又封装了attach_recursive_mnt();

3.5 attach_recursive_mnt()

该函数的主要作用是设置父子文件系统的映射关系。具体操作为:

1.通过mnt_set_mountpoint()将子vfsmount中的mnt_parent指向父vfsmount,将子vfsmount的mnt_mountpoint指向位于父文件系统中的挂载点dentry;

2.通过commit_tree()将子文件系统添加到内核的文件系统哈希表中,并将子文件系统添加到父文件系统对应的子文件系统链表中;

3.6 commit_tree()

1.将当前文件系统的名字空间设置为父名字空间,父vfsmount通过当前vfsmount中的mnt_parent获取;再将其连接到父名字空间链表中。

2.将当前vfsmount加入到对应哈希值的冲突链表当中,哈希值通过hash()计算。其中,mnt_hash作为链表元素。

3.将当前vfsmount加入到父vfsmount对应的子文件系统链表mnt_mounts中。其中,mnt_child作为链表元素。

从整个挂载的处理流程上看,挂载的本质就是将源文件系统的vfsmount结构连接到目的文件系统对应的vfsmount结构中,即具体涉及到两个vfsmount中字段的指向问题。两个vfsmount具体父子等级关系,这也对应着内核中目录树的父子等级关系。

参考资料:

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

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

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

文件操作函数在VFS层的实现

2012年11月23日

虚拟文件系统(Virtual Filesystem Switch,VFS)为各种文件系统提供了一个通用的接口,它使得上层进程在进行与文件系统相关的操作时可以使用同一组系统调用,但是系统调用在内核中可以根据不同的文件系统执行不同的操作。

与文件相关的基本操作函数有:open、read、write和close,本文将结合内核源代码分析这些函数在虚拟文件系统中的实现。

1.open()的实现

open系统调用的作用是打开或创建一个文件,并且返回该文件的文件描述符。在内核中,open系统调用主要完成的工作是为此次打开的文件创建file对象,该对象在fd_array数组中的索引值为返回用户空间的文件描述符。

open系统调用对应的系统调用服务例程为sys_open,不过目前内核已经统一使用SYSCALL_DEFINEn这种方式对系统调用服务例程进行定义。在open系统调用服务例程中又直接调用了do_sys_open函数,它是打开动作的主体函数。

long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{
        char *tmp = getname(filename);
        int fd = PTR_ERR(tmp);

        if (!IS_ERR(tmp)) {
                fd = get_unused_fd_flags(flags);
                if (fd >= 0) {
                        struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);
                        if (IS_ERR(f)) {
                                put_unused_fd(fd);
                                fd = PTR_ERR(f);
                        } else {
                                fsnotify_open(f->f_path.dentry);
                                fd_install(fd, f);
                        }
                }
                putname(tmp);
        }
        return fd;
}

用户进程使用open打开文件时将传递文件路径filename,因此该函数第一步先通过getname函数从用户空间读取文件路径到内核空间,暂存到tmp。通过get_unused_fd_flags函数在当前进程的fd_array数据中找到一个何时的位置,并返回其索引。

接下来通过do_filp_open函数执行打开文件的核心操作:根据系统调用中的标志参数flags和访问模式mode设置相应的局部变量以便后续使用;根据要打开文件的路径tmp寻找其inode节点,如果该inode节点不存在并且设置了O_CREATE标志则在磁盘上创建一个新的磁盘索引节点;分配一个新的文件对象,并根据系统调用传递的标志和访问模式设置文件对象的f_flags和f_mode字段;使用索引节点的i_fop字段初始化文件对象的f_op字段;将该文件对象插入到超级块指向的打开文件链表中;如果文件对象操作函数集中的open函数被定义则调用它;最后返回这个文件对象;

如果这个文件对象创建成功,则通过fd_install函数将该文件对象赋值到fd_array数组的第fd个元素中。

2.read()的实现

读文件系统调用read()的作用是根据文件描述符fd读取指定长度size的数据到缓冲区buf中。该系统调用的实现涉及了内核中对I/O进行处理的各个层次,但是对于VFS层来说实现方法比较清晰。

SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
        struct file *file;
        ssize_t ret = -EBADF;
        int fput_needed;
        
        file = fget_light(fd, &fput_needed);
        if (file) {
                loff_t pos = file_pos_read(file);
                ret = vfs_read(file, buf, count, &pos);
                file_pos_write(file, pos);
                fput_light(file, fput_needed);
        }

        return ret;
}

在read系统调用对应的服务例程中,首先使用fget_light函数通过fd获取对应的文件对象;再通过file_pos_read函数获取读文件的起始偏移量,即文件对象的f_pos字段的值;接着通过vfs_read函数进行读操作;通过file_pos_write函数更新文件当前的偏移量;通过fput_light函数释放文件对象;最终返回vfs_read函数的返回值ret,该值则为实际读取数据的长度。

read系统服务例程中最核心的函数即为vfs_read,它的主要工作是选择一个具体的读操作函数。如果当前文件对象操作函数集中的read钩子函数(file->f_op->read)被实现(通常在驱动程序中实现),则调用它。否则使用内核默认的读函数do_sys_read。

ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
        ssize_t ret;

        if (!(file->f_mode & FMODE_READ))
                return -EBADF;
        if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
                return -EINVAL;
        if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
                return -EFAULT;

        ret = rw_verify_area(READ, file, pos, count);
        if (ret >= 0) {
                count = ret;
                if (file->f_op->read)
                        ret = file->f_op->read(file, buf, count, pos);
                else
                        ret = do_sync_read(file, buf, count, pos);
                if (ret > 0) {
                        fsnotify_access(file->f_path.dentry);
                        add_rchar(current, ret);
                }
                inc_syscr(current);
        }

        return ret;
}

事实上,do_sys_read函数在内部调用钩子函数aio_read(file->f_op->aio_read),该钩子函数一般指向内核实现的通用读函数generic_file_aio_read。这个通用函数已经不属于我们本文所述的VFS层的实现范畴。

3.write函数的实现

write系统调用在VFS层的实现流程与read类似,只不过在出现read的地方将其相应的置换为write。

SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
                size_t, count)
{
        struct file *file;
        ssize_t ret = -EBADF;
        int fput_needed;

        file = fget_light(fd, &fput_needed);
        if (file) {
                loff_t pos = file_pos_read(file);
                ret = vfs_write(file, buf, count, &pos);
                file_pos_write(file, pos);
                fput_light(file, fput_needed);
        }

        return ret;
}

当然最终实现写文件操作的函数也是file->f_op->write或者内核中通用的写操作generic_file_aio_write。

4.close()的实现

close系统调用对应的服务例程中,它首先通过fd在文件对象数组中获取文件对象,接着则将fd处的文件对象清空。接下来的大部分工作都通过filp_close函数完成,它主要的工作是调用flush钩子函数将页高速缓存中的数据全部写回磁盘,释放该文件上的所有锁,通过fput函数释放该文件对象。最后返回0或者一个错误码。

inotify机制在用户态下的使用方法

2011年9月24日

1.inotify是什么?

inotify机制用于监控文件系统,通过它可以监控一个或多个文件,如果该文件发生了指定事件,比如打开,读或写等,该机制会异步的响应用程序发出通知(或称为警告),应用程序根据文件系统发生的事件类型做出相应的反应。

2.inotify可以监控的事件

inotify使用一组宏来表示文件可以被监控的事件,这些宏在稍候介绍的inotify_add_watch()中使用。在没有特别说明的情况下,下面解释中的文件均指被监控的文件,并且即可以是普通文件又可以是目录文件。

IN_ACCESS:文件被访问,如果是目录文件,则指目录中的文件名被访问。

IN_MODIFY:文件被修改,如果是目录文件,则指目录中的文件名被修改。

IN_ATTRIB:文件属性被修改,比如使用chmod命令。

IN_CLOSE_WRITE:可写的文件被关闭。

IN_CLOSE_NOWRITE:不可写文件被关闭。

IN_CLOSE:文件被关闭,它等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)的效果。

IN_OPEN:文件被打开。

IN_MOVED_FROM:文件被移出监控区,比如使用mv命令将一个文件移出监控目录。

IN_MOVED_TO:文件(这个文件既可以是受监控的又可以是未受监控的)被移入监控区,比如使用mv和cp命令。

IN_MOVE:文件被移动,它等同于(IN_MOVED_FROM | IN_MOVED_TO)的作用效果。

IN_CREATE:在目录中创建一个新文件,比如touch或mkdir命令。

IN_DELETE:文件被删除,比如使用rm命令。

IN_DELETE_SELF:自删除,即一个可执行文件在执行时删除自己。

IN_MOVE_SELF:自移动,即一个可执行文件在执行时移动自己。

IN_UNMOUNT:宿主文件系统被 umount。

另外,IN_ISDIR宏用来判断被监控的文件是否为目录文件,该宏可以在应用程序对监控文件作监控处理时应用。

3.inotify用户态使用概述

inotify机制属于Linux在2.6.13之后增加的一个新特性,它属于dnotify机制的升级版。要使用inotify机制监控文件系统,那么必须先创建一个inotify的实例。由于Linux中的一切皆为文件,可以将inotify实例理解为一个“inotify类型的文件”,因此该实例会对应一个文件描述符,这也属于inotify优于dnotify的一大特性。

inotify机制的另一大特性即为监控程序对文件的监控不必轮询去查看,一旦监控的文件有指定的事件发生,它会异步通知监控程序,监控程序收到警告后会立马做出相应的响应。而在没有发生监控事件的时候,监控程序则一直处于阻塞状态。

这里的阻塞通过read()即可完成。当没有监控时间发生时,inotify实例中没有数据则read()阻塞;当有监控事件发生时,监控事件将被写入inotify实例中,此时read函数被唤醒读取该事件,监控程序根据读取的数据做出相应处理。这里的事件其实是通过字节流发送到inotify实例中的,因此可以通过read()函数来读取。为此,专门有一个数据结构来存储监控事件,即为struct inotify_event:

 struct inotify_event
struct inotify_event {
	__s32		wd;		/* watch descriptor */
	__u32		mask;		/* watch mask */
	__u32		cookie;		/* cookie to synchronize two events */
	__u32		len;		/* length (including nulls) of name */
	char		name[0];	/* stub for possible name */
}; 

该结构的定义位于用户态文件目录include/linux/inotify.h中,每个字段代表的含义如下:

wd:一个监视器(watch)的描述符,所谓监视器就是一个二元组(监视文件,事件掩码),其中事件掩码包含该文件被监视的所有事件。wd是通过inotify_add_watch()返回的,wd在此结构中与一个监视事件关联,即说明wd监视器上发生了当前inotify_event这个事件。

mask:该事件的类型即为当前结构中的mask,它是wd中所指定mask的一个子集。

len:表示当前结构中name的长度,但有时候name为了字节对齐会填充若干个0,因此len会大于等于name的长度。

name:表示监控文件的路径,这里通过GNU C中的0长度数组来表示变长的文件路径。

对inotify机制的典型使用方法如下:

1.创建并初始化一个inotify的实例,通过inotify_init()即可实现,该函数返回一个文件描述符。

2.添加一个或多个监控文件,即监视器,通过inotify_add_watch()即可实现,该函数就返回一个监视器的文件描述符。

3.循环等待监控事件的发生,通过循环read()inotify实例的fd即可实现。

4.如果有监控事件发生,则将fd中的字节流读取到inotify_event结构中,监控程序随之作适当处理,处理完毕后返回继续等待。

5.当不需要继续监控或收到某个代表监控结束的信号时,关闭inotify实例的文件描述符。

关于基本的使用流程还可以参考下图:

4.inotify用户态API

inotify的API都使用文件描述符,这样可以将监控粒度控制到单个文件,而dnotify机制的控制粒度则为单个目录。使用文件描述符更大的优势在于对inotify的操作也可以使用read()、close()、select()等这些传统的文件操作函数。

1.int inotify_init (void)

创建并初始化一个inotify实例,该函数返回一个文件描述符。可以认为这个函数是打开一个inotify类型的文件并返回该类型文件的描述符。

2.int inotify_add_watch (int __fd, const char *__name, uint32_t __mask)

增加监视文件(监视器),fd用于指明该文件被添加于哪个inotify实例,name用于指名该文件的路径,mask则指明了该文件所有的监控事件。该函数调用成功后返回一个监视器的描述符。

3.int inotify_rm_watch (int __fd, int __wd)

从fd中删除一个监视器,wd指名具体的监视器。

关于上述函数的详细的使用方法以及错误返回值等内容可以参考man手册。

参考:

IBM Developer Works:http://www.ibm.com/developerworks/cn/linux/l-ubuntu-inotify/index.html

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