linux open系统调用完全剖析 下载本文

内容发布更新时间 : 2024/6/24 2:55:46星期一 下面是文章的全部内容请认真阅读。

Linux系统是如何区分普通文件与设备驱动文件的研究一

文件的打开

一般来说对于文件或者是设备的操作都是从open开始的,我们首先要打开这个设备节点或者是普通文件,才可以对这个文件进行read、write、ioctl、mmap等操作。所以一切的起源于open。我们首先从open开始研究。

在linux系统进程当中,分为内核空间和用户空间。当我们在用户空间通过open之后,会产生一个软中断,然后通过系统调用陷入内核空间。通过系统调用号,我们可以跳转到该中断例程的入口地址,我们接着看内核源码的实现。

1、在arch/x86/include/asm/unistd_32.h中定义了系统调用号 #define __NR_restart_syscall 0 #define __NR_exit 1 #define __NR_fork 2 #define __NR_read 3 #define __NR_write 4 #define __NR_open 5 #define __NR_close 6 #define __NR_waitpid 7 。。。。。。。。

2、当产生系统调用的时候,会进入到下面这个函数:

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode) { long ret; if (force_o_largefile()) flags |= O_LARGEFILE; ret = do_sys_open(AT_FDCWD, filename, flags, mode); /* avoid REGPARM breakage on x86: */ asmlinkage_protect(3, ret, filename, flags, mode); return ret; }

3、最终会调用到do_sys_open(),这个函数首先分配一个可用的文件描述符。并且通过调用do_filp_open()通过传进来的文件名查找到inode信息,并且根据这些信息创建一个file对象,并且将inode和file的关系关联起来。

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

/*获取文件名称,由getname()函数完成,其内部首先创建存取文件名称的空间, 然后*从用户空间把文件名拷贝过来*/ char *tmp = getname(filename); int fd = PTR_ERR(tmp); if (!IS_ERR(tmp)) {

/*获取一个可用的fd,此函数调用alloc_fd()函数从

fd_table中获取一个可用fd,并做些简单初始化,此函数内部实现比较简单,

此次分析不细看,注意,对于文件描述符fd来讲,它只对本进程有效, 也即它只在该进程中可见而在其它进程中代表着完全不同的文件。 */ fd = get_unused_fd_flags(flags); if (fd >= 0) {

/*fd获取成功则开始打开文件,此函数是主要完成打开功能的函数,在此先放一放,下面详细分析*/

//如果分配fd成功,则创建一个file对象 struct file *f = do_filp_open(dfd, tmp, flags, mode, 0); if (IS_ERR(f)) { /*打开失败,释放fd*/ put_unused_fd(fd); fd = PTR_ERR(f); } else {

/*文件如果已经被打开了,调用fsnotify_open()函数,根据inode所指定的信息进行打开 函数(参数为f)将该文件加入到文件监控的系统中。该系统是用来监控文件被打开,创建, 读写,关闭,修改等操作的*/ fsnotify_open(f->f_path.dentry); /*将文件指针安装在fd数组中

将struct file *f加入到fd索引位置处的数组中。如果后续过程中,有对该文件描述符的 操作的话,就会通过查找该数组得到对应的文件结构,而后在进行相关操作。*/ fd_install(fd, f); } } /*释放放置从用户空间拷贝过来的文件名的存储空间*/ putname(tmp); } return fd; }

4、do_filp_open函数的一个重要作用就是根据传递近来的权限进行分析,并且分析传递近来的路径名字,根据路径名逐个解析成dentry,并且通过dentry找到inode,inode就是记录着该文件相关的信息, 包括文件的创建时间和文件属性所有者等等信息,根据这些信息就可以找到对应的文件操作方法。在这个过程当中有一个临时的结构体用于保存在查找过程中的相关信息,就是

struct nameidata { struct path path;//当前目录的dentry数据结构 struct qstr last;//这个结构体也是临时性的,主要用来保存当前目录的名称,杂凑值。 unsigned int flags; int last_type; unsigned depth;//连接文件的深度(可能一个连接文件跟到最后还是一个了连接文件) //用来保存连接文件的一些信息,下标表示连接文件的深度 char *saved_names[MAX_NESTED_LINKS + 1];

/* Intent data */ union { struct open_intent open; } intent; };

struct file *do_filp_open(int dfd, const char *pathname, int open_flag, int mode, int acc_mode) {

/*当内核要访问一个文件的时候,第一步要做的是找到这个文件,而查找文件的过程在vfs 里面是由path_lookup或者path_lookup_open函数来完成的。这两个函数将用户传进来的字符串表示的文件路径转换成一个dentry结构,并建立好相应的inode和file结构,将指向file 的描述符返回用户。用户随后通过文件描述符,来访问这些数据结构当函数正确返回的时候,已经将inode和dentry的结构体创建好,并且绑定好*/ error = do_path_lookup(dfd, pathname, LOOKUP_PARENT, &nd); /*

这个函数根据 struct nameidata 结构返回一个 struct file。可以看到 struct file 是在使用了 __dentry_open() 函数后被填充的,且使用的第

一个参数是 nameidata->dentry,这也是为什么我们要获得 struct nameidata 的一个主要原因,其目的就是为了得到 struct dentry 结构。 在此时,已经将inode和dentry的结构体创建好,并且绑定好*/ filp = nameidata_to_filp(&nd, open_flag); mnt_drop_write(nd.path.mnt); return filp; }

struct file *do_filp_open(int dfd, const char *pathname, int open_flag, int mode, int acc_mode) {

...................

/*当内核要访问一个文件的时候,第一步要做的是找到这个文件,而查找文件的过程在vfs 里面是由path_lookup或者path_lookup_open函数来完成的。这两个函数将用户传进来的字符串

表示的文件路径转换成一个dentry结构,并建立好相应的inode和file结构,将指向file 的描述符返回用户。用户随后通过文件描述符,来访问这些数据结构

当函数正确返回的时候,已经将inode和dentry的结构体创建好,并且绑定好*/ error = do_path_lookup(dfd, pathname, LOOKUP_PARENT, &nd); /*

这个函数根据 struct nameidata 结构返回一个 struct file。可以看到

struct file 是在使用了 __dentry_open() 函数后被填充的,且使用的第

一个参数是 nameidata->dentry,这也是为什么我们要获得 struct nameidata 的一个主要原因,其目的就是为了得到 struct dentry 结构。 在此时,已经将inode和dentry的结构体创建好,并且绑定好*/ filp = nameidata_to_filp(&nd, open_flag); ................... }

static int do_path_lookup(int dfd, const char *name, unsigned int flags, struct nameidata *nd) {

...................

// 注意:这个函数才真正的分解路径,调用实际文件系统的操作。 // 它本身也是个简单封状,实际是使用 __link_path_walk() 函数 // 完成操作。 retval = path_walk(name, nd); ................... }

//分装的一个路径搜索路径

static int path_walk(const char *name, struct nameidata *nd) { current->total_link_count = 0; return link_path_walk(name, nd); }

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

//对路径名字进行解析 result = __link_path_walk(name, nd); }

static int __link_path_walk(const char *name, struct nameidata *nd) {

//对路径的名name进行逐一分析出一个dentry项,分析完路径名之后再进行查找inode ..............................

// 从缓存或调用实际文件系统函数获取 inode 信息

到这里才是查找对应 struct dentry 的具体操作,此函数首先从缓存中尝试获取 struct dentry 结构。如果获取失败,则调用 real_lookup() 函数使用实际文件 系统方法来读取 inode 信息。这里要明确 struct dentry 中包含了 struct inode 信息。*/

//真正的开始查找了,do_lookup(),首先现在内存中的杂凑表中查找,如果没有找到 //那么就要到磁盘或者相应的块设备中去查找,并在内存建立相应的数据结构

//next是struct path类型的结构体,定义于/linux/fs/namei.c,用于保存从磁盘或者其他块设备

上查找的信息 err = do_lookup(nd, &this, &next); ..................................... }

static int do_lookup(struct nameidata *nd, struct qstr *name, struct path *path) { struct vfsmount *mnt = nd->path.mnt;

// 从 hlist 中获取 struct dentry 结构,hlist 代表的是 // 一个 inode 的缓存即是一个 HASH 表 struct dentry *dentry = __d_lookup(nd->path.dentry, name);

//在缓冲区中没有找到指定名字的dentry,调用real_lookup在磁盘中查找

/* 到这里才是查找对应 struct dentry 的具体操作,此函数首先从缓存中尝试获取 struct dentry 结构。如果获取失败,则调用 real_lookup() 函数使用实际文件 系统方法来读取 inode 信息。这里要明确 struct dentry 中包含了 struct inode 信息。 */

// 如果没有找到则会调用 real_lookup() 实际文件系统方法 // 从磁盘中获取 if (!dentry) goto need_lookup; if (dentry->d_op && dentry->d_op->d_revalidate) goto need_revalidate;

done:// 如果从缓存中找到,则设置 struct path 并返回 path->mnt = mnt; path->dentry = dentry; __follow_mount(path); return 0;

need_lookup:

// 从磁盘中获取// 使用实际文件系统方法,从磁盘中获得 inode 信息 dentry = real_lookup(nd->path.dentry, name, nd); ......................... }

//当所有查找dentry的方法都失效的时候,只有调用real_lookup()到磁盘中查找inode static struct dentry * real_lookup(struct dentry * parent, struct qstr * name, struct nameidata *nd) { /*

在分析 real_lookup() 函数前,我们先来看一下 ext3 文件系统的 inode 结构。很明显可以看出 lookup 指向了 ext3_lookup() 函数。 存放在 /fs/ext3/namei.c

struct inode_operations ext3_dir_inode_operations = {