Linux内核源码阅读之打开文件篇

Linux虚拟文件系统--文件路径名的解析(1)--整体过程 文件路径名的解析是VFS中最基本也是最频繁用到的一个部分,它的代码实现还是十分繁杂的,主要是因为除了普通文件名的解析,内核还要考虑各种可能出现的情况,如一个目录下挂载了多个文件系统,路径中的符号链接等等……后面我会分几次将整个过程进行一个尽量仔细的分析,其中所涉及到的各种数据结构在ULK等相关内核书籍上都有比较详细的介绍,就不列出来了        文件路径名的解析路口函数为path_looku 阅读详情

1.          引言       从事Linux环境工作2年有余,一直懵懵懂懂,1年前拜读了《莱昂氏UNIX源代码分析》一书,感觉自己的学习道路漫漫且修远。最近受chinaunix的精华文帖启发,拟将近来的部分内核调用分析笔记拿出来与各前辈先进共同探讨学习,以壮个人学习之路。       本部分主要讲述的是文件I/O操作的2.6.11内核版本实现,包括了主要的数据结构、宏定义和函数流程。以下分别讲述open,create,close,read,write,lseek系统调用。 2.          主要参考 《莱昂氏UNIX源代码分析》 《UNIX环境高级编程》     www.kernel.org 3.          主要数据结构 3.1.          FD         对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。       当读、写一个文件时,用open或creat返回的文件描述符fd标识该文件,将其作为参数传送给read或write。在POSIX.1应用程序中,文件描述符为常数0、1和2分别代表STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO,意即标准输入,标准输出和标准出错输出,这些常数都定义在头文件<unistd.h>;中。 文件描述符的范围是0~OPEN_MAX,在目前常用的linux系统中,是32位整形所能表示的整数,即65535,64位机上则更多。 3.2.          File struct file {      struct list_head          f_list; //文件链表指针     struct dentry                  *f_dentry; // 文件对应的目录结构     struct vfsmount           *f_vfsmnt; // 虚拟文件系统挂载点     struct file_operations          *f_op; // 文件操作函数指针     atomic_t                  f_count;      unsigned int                   f_flags;      mode_t                          f_mode; // 文件模式     int                          f_error;      loff_t                          f_pos; // 文件offset      struct fown_struct          f_owner; //文件owner 结构     unsigned int                  f_uid, f_gid;      struct file_ra_state          f_ra; // 跟踪上次文件操作状态的结构指针     size_t                          f_maxcount; // 文件大小     unsigned long                  f_version;      void                          *f_security; // hook 文件操作的security结构指针     void                          *private_data; // tty 驱动器所需数据 #ifdef CONFIG_EPOLL      struct list_head          f_ep_links; // EPOLL 机制检测所需链表结构     spinlock_t                  f_ep_lock; // 兼容早期gcc bug 的标志 #endif /* #ifdef CONFIG_EPOLL */      struct address_space          *f_mapping; // 地址映射表 } 3.3.          File_struct File_struct结构保存了进程打开的所有文件表数据。 struct files_struct {      atomic_t count; // 自动增量     spinlock_t file_lock; // 低位成员保护标识     int max_fds; // 最大文件句柄数目     int max_fdset; // 最大的fd集合容量     int next_fd; // 下一个空闲fd      struct file ** fd; // 当前fd对应的文件结构指针列表     fd_set *close_on_exec; // 可执行close的fd集合     fd_set *open_fds; // 打开的fd集合     fd_set close_on_exec_init; //      fd_set open_fds_init;      struct file * fd_array[NR_OPEN_DEFAULT]; // 默认打开的fd队列 }; 4.          open 函数 4.1.          原型与参数      int open(const char * pathname,    int oflag, .../*, mode_t mode * / )    -1代表错误。      这里的oflag是一个整形,主要供open 函数使用,部分fcntl函数也会使用。详细的说明请用 man 2 open就可以看到了。以下列出了2.6内核定义的open和fcntl函数所使用的flag宏定义,说明的格式如宏定义名称<实际常数值>;: 描述。 O_ACCMODE          <0003>;: 读写文件操作时,用于取出flag的低2位。 O_RDONLY<00>;: 只读打开 O_WRONLY<01>;: 只写打开 O_RDWR<02>;: 读写打开 O_CREAT<0100>;: 文件不存在则创建,需要mode_t,not fcntl O_EXCL<0200>;: 如果同时指定了O_CREAT,而文件已经存在,则出错, not fcntl O_NOCTTY<0400>;: 如果pathname指终端设备,则不将此设备分配作为此进程的控制终端。O_TRUNC<01000>;: 如果此文件存在,而且为只读或只写成功打开,则将其长度截短为0。      O_APPEND<02000>;: 每次写时都加到文件的尾端 O_NONBLOCK<04000>;: 如果p a t h n a m e指的是一个F I F O、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I / O操作设置非阻塞方式。 O_NDELAY<O_NONBLOCK>;; O_SYNC<010000>;: 使每次write都等到物理I/O操作完成。 FASYNC<020000>;: 兼容BSD的fcntl同步操作 O_DIRECT<040000>;: 直接磁盘操作标识 O_LARGEFILE<0100000>;: 大文件标识 O_DIRECTORY<0200000>;:          必须是目录 O_NOFOLLOW<0400000>;: 不获取连接文件 O_NOATIME<01000000>;: 暂无 当新创建一个文件时,需要指定mode 参数,以下说明的格式如宏定义名称<实际常数值>;: 描述。      S_IRWXU<00700>;:文件拥有者有读写执行权限      S_IRUSR (S_IREAD)<00400>;:文件拥有者仅有读权限      S_IWUSR (S_IWRITE)<00200>;:文件拥有者仅有写权限      S_IXUSR (S_IEXEC)<00100>;:文件拥有者仅有执行权限      S_IRWXG<00070>;:组用户有读写执行权限      S_IRGRP<00040>;:组用户仅有读权限      S_IWGRP<00020>;:组用户仅有写权限      S_IXGRP<00010>;:组用户仅有执行权限      S_IRWXO<00007>;:其他用户有读写执行权限      S_IROTH<00004>;:其他用户仅有读权限      S_IWOTH<00002>;:其他用户仅有写权限      S_IXOTH<00001>;:其他用户仅有执行权限 4.2.         实现分析 4.2.1.         主要函数调用关系图     sys_open( 见4.2.2 节)     | ----------- getname( 见4.2.3 节 )     | ----------- filp_open( 见4.2.4节 )     |                         | ------------ open_namei( 见4.2.4.1节 )     |                         |                   | ----------- may_open     |                         | ------------ dentry_open( 见4.2.4.2节 ) 4.2.2.         主调用函数sys_open,函数定义在/fs/open.c文件 asmlinkage long sys_open(const char __user * filename, int flags, int mode){          char * tmp;          int fd, error;

// 如果不是32位处理器,则增加大文件标识 #if BITS_PER_LONG != 32          flags |= O_LARGEFILE; #endif          // 为了提高使用效率,在使用之前先将文件名拷贝到内核数据区。见3.2.2说明          tmp = getname(filename);          // 获取到返回值,如果出错,则返回,否则执行打开操作。          fd = PTR_ERR(tmp);          if (!IS_ERR(tmp)) {                  // 从进程的文件表中找出一个空闲的文件表指针,如果出错,则返回                  fd = get_unused_fd();                  if (fd >= 0) {                          // 执行打开操作。见3.2.3说明                          struct file *f = filp_open(tmp, flags, mode);                          // 获取返回结果,如果出错,则跳转至out_error,否则执行fd_install                          error = PTR_ERR(f);                          if (IS_ERR(f))                                  goto out_error;                          // 添加打开的文件表 f 到当前进程的文件表队列中。见3.2.4说明                          fd_install(fd, f);                  } out:                  // 释放getname分配的内存空间                  putname(tmp);          }          return fd; out_error:                  // 将文件表指针返回到进程的文件表中,并返回错误代码。          put_unused_fd(fd);          fd = error;          goto out; } 4.2.3.         sys_open子函数getname getname函数主要功能是在使用文件名之前将其拷贝到内核数据区,正常结束时返回内核分配的空间首地址,出错时返回错误代码。其调用了函数do_getname来实现。 static inline int do_getname(const char __user *filename, char *page){          int retval;          unsigned long len = PATH_MAX; // 内核允许的最大路径长度

         // 如果进程的地址限制是否和KERNEL_DS相等,则检查文件名是否小于用户进程空间          if (!segment_eq(get_fs(), KERNEL_DS)) {                  // 文件名地址大于用户进程空间,则返回错误-EFAULT                  if ((unsigned long) filename >= TASK_SIZE)                          return -EFAULT;                  // 获取较小的地址长度                  if (TASK_SIZE - (unsigned long) filename < PATH_MAX)                          len = TASK_SIZE - (unsigned long) filename;          }

         // 将filename拷贝len长度到page中,返回实际拷贝长度          retval = strncpy_from_user(page, filename, len);          if (retval >0) {                  // 如果retval大于等于len,则返回-ENAMETOOLONG                  if (retval < len)                          return 0;                  return -ENAMETOOLONG;          } else if (!retval)                  // filename 为空,则返回-ENOENT                  retval = -ENOENT;          return retval; }

char * getname(const char __user * filename){          char *tmp, *result;

         result = ERR_PTR(-ENOMEM);          // 从内核缓存中分配空间,如果成功,则执行do_getname          tmp = __getname();          if (tmp)   {                  // 执行文件名拷贝操作                  int retval = do_getname(filename, tmp);

                 result = tmp;                  if (retval < 0) {                          // do_getname出错,则释放空间,并返回错误代码                          __putname(tmp);                          result = ERR_PTR(retval);                  }          }          // 如果前面操作成功,且audit_context不为空,则将当前文件名添加到audit列表中          if (unlikely(current->;audit_context) && !IS_ERR(result) && result)                  audit_getname(result);          // 返回处理结果          return result; } 4.2.4.         sys_open子函数filp_open 在较高版本的内核里函数名改为do_filp_open,但是函数内容一样。 这后面的函数使用了一个nameidata的数据结构来描述文件相关的操作数据。 struct nameidata {          struct dentry         *dentry; // 目录数据          struct vfsmount *mnt; // 虚拟文件挂载点数据          struct qstr         last; // hash值          unsigned int         flags; // 文件操作标识          int                 last_type; // 类型          unsigned         depth;          char *saved_names[MAX_NESTED_LINKS + 1];          union {                  struct open_intent open;          } intent; // 专用数据 }; struct file *filp_open(const char * filename, int flags, int mode){          int namei_flags, error;          struct nameidata nd;

         namei_flags = flags;          if ((namei_flags+1) & O_ACCMODE)                  namei_flags++; // 如果flags有O_WRONLY,则增加O_RDONLY          if (namei_flags & O_TRUNC)                  namei_flags |= 2; // 如果有O_TRUNC,则增加O_RDWR

         error = open_namei(filename, namei_flags, mode, &nd); // 如3.2.3.1 描述          if (!error)                  return dentry_open(nd.dentry, nd.mnt, flags); // 如3.2.3.2描述

         return ERR_PTR(error); // 返回错误代码 } 4.2.4.1.         filp_open子函数open_namei open_namei函数主要执行文件操作的inode部分的打开等操作。 int open_namei(const char * pathname, int flag, int mode, struct nameidata *nd){          int acc_mode, error = 0;          struct dentry *dentry;          struct dentry *dir;          int count = 0;

         acc_mode = ACC_MODE(flag); // 取出低2位操作标识

         if (flag & O_APPEND) // 取出O_APPEND操作标识                  acc_mode |= MAY_APPEND;

         //赋值open函数的专用数据          nd->;intent.open.flags = flag;          nd->;intent.open.create_mode = mode;

         // 如果不需要创建文件,则在进程目录文件表搜索已有文件,并把结果拷贝到nd中          if (!(flag & O_CREAT)) {                  error = path_lookup(pathname, lookup_flags(flag)|LOOKUP_OPEN, nd);                  if (error) // 错误代码有ENOENT,ENOTDIR,EAGAIN,ESTALE,                          return error;                  goto ok; // 否则执行打开函数,更新inode数据          }

// 在进程文件表中搜索该文件,如果不存在,则创建,结果由nd保存          error = path_lookup(pathname, LOOKUP_PARENT|LOOKUP_OPEN|LOOKUP_CREATE, nd);          if (error)                  return error;

         // 检测nd的结果是否是一个目录文件,是则返回          error = -EISDIR;          if (nd->;last_type != LAST_NORM || nd->;last.name[nd->;last.len])                  goto exit;          // 获取文件的相关目录数据,结果返回到dentry中。          dir = nd->;dentry;          nd->;flags &= ~LOOKUP_PARENT;          down(&dir->;d_inode->;i_sem);          dentry = __lookup_hash(&nd->;last, nd->;dentry, nd);

do_last:          // 如果dentry是一个错误值,则返回          error = PTR_ERR(dentry);          if (IS_ERR(dentry)) {                  up(&dir->;d_inode->;i_sem);                  goto exit;          }

         // 如果dentry不存在,则创建他          if (!dentry->;d_inode) {                  if (!IS_POSIXACL(dir->;d_inode))                          mode &= ~current->;fs->;umask;                  error = vfs_create(dir->;d_inode, dentry, mode, nd); // 创建inode                  up(&dir->;d_inode->;i_sem);                  dput(nd->;dentry);                  nd->;dentry = dentry;                  if (error)                          goto exit;                  acc_mode = 0;                  flag &= ~O_TRUNC;                  goto ok;          }

up(&dir->;d_inode->;i_sem);

         error = -EEXIST; // 如果指定了O_EXCL和O_CREAT,文件存在时,出错          if (flag & O_EXCL)                  goto exit_dput;

         if (d_mountpoint(dentry)) { // 检测文件是否是连接文件                  error = -ELOOP;                  if (flag & O_NOFOLLOW) // 如果指定不遍历连接文件,则返回                          goto exit_dput;                  // 检测dentry挂载点                  while (__follow_down(&nd->;mnt,&dentry) && d_mountpoint(dentry));          }          error = -ENOENT;          if (!dentry->;d_inode) // inode 不存在,则返回                  goto exit_dput;          if (dentry->;d_inode->;i_op && dentry->;d_inode->;i_op->;follow_link)                  goto do_link; // 允许遍历连接文件,则手工找到连接文件对应的文件                  // 将处理后的dentry复制到nd结构中,并判断其是否是目录,是则返回错误          dput(nd->;dentry);          nd->;dentry = dentry;          error = -EISDIR;          if (dentry->;d_inode && S_ISDIR(dentry->;d_inode->;i_mode))                  goto exit; ok:          error = may_open(nd, acc_mode, flag); // 打开文件,返回处理结果代码。如3.2.3.1.1描述          if (error)                  goto exit;          return 0;

exit_dput:          dput(dentry); // 释放dentry exit:          path_release(nd); // 释放nd结构          return error; // 返回错误代码

do_link:          error = -ELOOP;          if (flag & O_NOFOLLOW)                  goto exit_dput; // 不允许遍历连接文件,则返回错误                  // 以下代码是手工找到连接文件对应的文件dentry数据          nd->;flags |= LOOKUP_PARENT;          error = security_inode_follow_link(dentry, nd);          if (error)                  goto exit_dput;          error = __do_follow_link(dentry, nd);          dput(dentry);          if (error)                  return error;          nd->;flags &= ~LOOKUP_PARENT;          if (nd->;last_type == LAST_BIND) {                  dentry = nd->;dentry;                  goto ok;          }          error = -EISDIR;          if (nd->;last_type != LAST_NORM)                  goto exit;          if (nd->;last.name[nd->;last.len]) {                  putname(nd->;last.name);                  goto exit;          }          error = -ELOOP;          if (count++==32) {                  putname(nd->;last.name);                  goto exit;          }          dir = nd->;dentry;          down(&dir->;d_inode->;i_sem);          dentry = __lookup_hash(&nd->;last, nd->;dentry, nd);          putname(nd->;last.name);          goto do_last; } 4.2.4.1.1.         filp_open子函数may_open 在较高版本的内核里作为open_namei的子函数。。。 may_open执行权限检测和文件打开,和truncate的操作。 int may_open(struct nameidata *nd, int acc_mode, int flag){          struct dentry *dentry = nd->;dentry;          struct inode *inode = dentry->;d_inode;          int error;

         if (!inode) return -ENOENT; // inode为空,则返回错误

         if (S_ISLNK(inode->;i_mode)) // 连接文件,返回错误                  return -ELOOP;                  if (S_ISDIR(inode->;i_mode) && (flag & FMODE_WRITE))                  return -EISDIR; // 是目录且仅有写权限,返回错误

         error = permission(inode, acc_mode, nd); // 见擦inode的accmode          if (error)                  return error;

         if (S_ISFIFO(inode->;i_mode) || S_ISSOCK(inode->;i_mode)) {                      flag &= ~O_TRUNC; // 如果是FIFO文件,则不允许truncate          } else if (S_ISBLK(inode->;i_mode) || S_ISCHR(inode->;i_mode)) {                  if (nd->;mnt->;mnt_flags & MNT_NODEV)                          return -EACCES; // 如果是设备,则不允许truncate,否则返回错误                  flag &= ~O_TRUNC;          } else if (IS_RDONLY(inode) && (flag & FMODE_WRITE))                  return -EROFS; 如果flag标识和inode权限冲突,则返回错误                  // 如果inode只允许append方式写入,则不允许truncate和非append写入方式。 if (IS_APPEND(inode)) {                  if   ((flag & FMODE_WRITE) && !(flag & O_APPEND))                          return -EPERM;                  if (flag & O_TRUNC)                          return -EPERM;          }

         // O_NOATIME方式仅在inode用户是文件拥有者或者超级用户情况下才被允许          if (flag & O_NOATIME)                  if (current->;fsuid != inode->;i_uid && !capable(CAP_FOWNER))                          return -EPERM;

         // 检查是否有其他进程在使用该文件          error = break_lease(inode, flag);          if (error)                  return error;

         if (flag & O_TRUNC) {                  error = get_write_access(inode); // 获取一次inode写操作权限                  if (error)                          return error;

                 // 锁定inode                  error = locks_verify_locked(inode);                  if (!error) {                          DQUOT_INIT(inode); // 对inode执行配额初始化                                                  error = do_truncate(dentry, 0); // truncate dentry                  }                  put_write_access(inode); // 释放当前写操作权限                  if (error)                          return error;          } else                  if (flag & FMODE_WRITE) // 如果有写标识,则对inode执行配额初始化                          DQUOT_INIT(inode);

         return 0; } 4.2.4.2.         open_namei子函数dentry_open dentry_open函数主要实现文件表的对应打开等操作,返回文件指针。 struct file *dentry_open(struct dentry *dentry, struct vfsmount *mnt, int flags){          struct file * f;          struct inode *inode;          int error;

         error = -ENFILE;          f = get_empty_filp(); // 从进程文件表中获取一个未使用的文件结构指针,空则出错返回          if (!f)                  goto cleanup_dentry;          // 设置文件的flags和mode标识          f->;f_flags = flags;          f->;f_mode = ((flags+1) & O_ACCMODE) | FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE;          inode = dentry->;d_inode;          if (f->;f_mode & FMODE_WRITE) {                  error = get_write_access(inode); // 获取一次inode写操作权限                  if (error)                          goto cleanup_file;          }          // 初始化文件结构          f->;f_mapping = inode->;i_mapping;          f->;f_dentry = dentry;          f->;f_vfsmnt = mnt;          f->;f_pos = 0;          f->;f_op = fops_get(inode->;i_fop);          file_move(f, &inode->;i_sb->;s_files);

         // 调用文件驱动模块初始化物理磁盘          if (f->;f_op && f->;f_op->;open) {                  error = f->;f_op->;open(inode,f);                  if (error)                          goto cleanup_all;          }          f->;f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);

         // 初始化上次读取状态          file_ra_state_init(&f->;f_ra, f->;f_mapping->;host->;i_mapping);

         // 如果设置了O_DIRECT,则检测文件结构中是否有驱动的操作函数指针          if (f->;f_flags & O_DIRECT) {                  if (!f->;f_mapping->;a_ops || !f->;f_mapping->;a_ops->;direct_IO) {                          fput(f);                          f = ERR_PTR(-EINVAL);                  }          }

         return f; // 返回文件结构

cleanup_all: // 出错,则释放资源并返回          fops_put(f->;f_op);          if (f->;f_mode & FMODE_WRITE)                  put_write_access(inode);          file_kill(f);          f->;f_dentry = NULL;          f->;f_vfsmnt = NULL; cleanup_file:          put_filp(f); cleanup_dentry:          dput(dentry);          mntput(mnt);          return ERR_PTR(error); } 4.3.         总结 open函数的主要操作就是为文件初始化inode,初始化文件结构,刷新进程文件链表。 5.         creat 函数 asmlinkage long sys_creat(const char __user * pathname, int mode){          return sys_open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode); }          从上面的简短的实现代码可以看出,creat的系统调用直接以O_CREAT|O_WRONLY|O_TRUNC标识调用open函数实现的。 文章出处:飞诺网(www.diybl.com):http://www.diybl.com/course/6_system/linux/Linuxjs/20101230/550127.html

 
 
Linux中打开文件是通过open系统调用实现,其函数中调用了do_sys_open()函数完成打开功能,所以下面主要分析do_sys_open()函数,首先先看下open系统调用的入口函数,再具体看do_sys_open()函数:

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;

}

 

 

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 = get_unused_fd_flags(flags);

                   if (fd >= 0) {

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

                            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()函数*/

                                     fsnotify_open(f->f_path.dentry);

                                     /*将文件指针安装在fd数组中*/

                                     fd_install(fd, f);

                            }

                   }

                   /*释放放置从用户空间拷贝过来的文件名的存储空间*/

                   putname(tmp);

         }

         return fd;

}

 

接下来即将进入到打开功能的真正实现功能的函数do_filp_open()函数:

struct file *do_filp_open(int dfd, const char *pathname,

                   int open_flag, int mode, int acc_mode)

{

         /*

         *…若干变量声明

         */

         /*改变参数flag的值,具体做法是flag+1*/

         int flag = open_to_namei_flags(open_flag);

         int force_reval = 0;

 

         /*根据__O_SYNC标志来设置O_DSYNC 标志,用以防止恶意破坏程序*/

         if (open_flag & __O_SYNC)

                   open_flag |= O_DSYNC;

         /*设置访问权限*/

         if (!acc_mode)

                   acc_mode = MAY_OPEN | ACC_MODE(open_flag);

 

         /*根据 O_TRUNC标志设置写权限 */

         if (flag & O_TRUNC)

                   acc_mode |= MAY_WRITE;

 

         /* 设置O_APPEND 标志*/

         if (flag & O_APPEND)

                   acc_mode |= MAY_APPEND;

 

         /*如果不是创建文件*/

         if (!(flag & O_CREAT)) {

                   /*返回特定的file结构体指针*/

                   filp = get_empty_filp();

 

                   if (filp == NULL)

                            return ERR_PTR(-ENFILE);

                   /*填充nameidata 结构*/

                   nd.intent.open.file = filp;

                   filp->f_flags = open_flag;

                   nd.intent.open.flags = flag;

                   nd.intent.open.create_mode = 0;

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

                                               lookup_flags(flag)|LOOKUP_OPEN, &nd);

                   if (IS_ERR(nd.intent.open.file)) {

                            if (error == 0) {

                                     error = PTR_ERR(nd.intent.open.file);

                                     /*减少dentry和vsmount得计数*/

                                     path_put(&nd.path);

                            }

                   } else if (error)

                            /*如果查找失败则释放一些资源*/

                            release_open_intent(&nd);

                   if (error)

                            return ERR_PTR(error);

                   goto ok;

         }

 

         /*到此则是要创建文件*/

reval:

         /* path-init为查找作准备工作,path_walk真正上路查找,这两个函数联合起来根据一段路径名找到对应的dentry */

         error = path_init(dfd, pathname, LOOKUP_PARENT, &nd);

         if (error)

                   return ERR_PTR(error);

         if (force_reval)

                   nd.flags |= LOOKUP_REVAL;

/*这个函数相当重要,就如源代码注释的那样,是整个NFS的名字解析函数,其实也是NFS得以构筑的函数。这里作一重点分析。这里先作一个综述。该函数采用一个for循环,对name路径根据目录的层次,一层一层推进,直到终点或失败。在推进的过程中,一步步建立了目录树的dentry和对应的inode */

         error = path_walk(pathname, &nd);

         if (error) {

                   if (nd.root.mnt)

                            path_put(&nd.root);

                   return ERR_PTR(error);

         }

         if (unlikely(!audit_dummy_context()))

                   /*保存inode节点信息*/

                   audit_inode(pathname, nd.path.dentry);

 

         /*父节点信息*/

         error = -EISDIR;

         if (nd.last_type != LAST_NORM || nd.last.name[nd.last.len])

                   goto exit_parent;

 

         error = -ENFILE;

         /*获取文件指针*/

         filp = get_empty_filp();

         if (filp == NULL)

                   goto exit_parent;

         /*填充nameidata 结构*/

         nd.intent.open.file = filp;

         filp->f_flags = open_flag;

         nd.intent.open.flags = flag;

         nd.intent.open.create_mode = mode;

         dir = nd.path.dentry;

         nd.flags &= ~LOOKUP_PARENT;

         nd.flags |= LOOKUP_CREATE | LOOKUP_OPEN;

         if (flag & O_EXCL)

                   nd.flags |= LOOKUP_EXCL;

         mutex_lock(&dir->d_inode->i_mutex);

         /*从哈希表中查找nd对应的dentry*/

         path.dentry = lookup_hash(&nd);

         path.mnt = nd.path.mnt;

 

do_last:

         error = PTR_ERR(path.dentry);

         if (IS_ERR(path.dentry)) {

                   mutex_unlock(&dir->d_inode->i_mutex);

                   goto exit;

         }

 

         if (IS_ERR(nd.intent.open.file)) {

                   error = PTR_ERR(nd.intent.open.file);

                   goto exit_mutex_unlock;

         }

 

         /*如果此dentry结构没有对应的inode节点,说明是无效的,应该创建文件节点 */

         if (!path.dentry->d_inode) {

                   /*write权限是必需的*/

                   error = mnt_want_write(nd.path.mnt);

                   if (error)

                            goto exit_mutex_unlock;

                   /*按照namei格式的flag open*/

                   error = __open_namei_create(&nd, &path, flag, mode);

                   if (error) {

                            mnt_drop_write(nd.path.mnt);

                            goto exit;

                   }

                   /*根据nameidata 得到相应的file结构*/

                   filp = nameidata_to_filp(&nd);

                   /*放弃写权限*/

                   mnt_drop_write(nd.path.mnt);

                   if (nd.root.mnt)

                            /*计数减一*/

                            path_put(&nd.root);

                   if (!IS_ERR(filp)) {

                            error = ima_file_check(filp, acc_mode);

                            if (error) {

                                     fput(filp);

                                     filp = ERR_PTR(error);

                            }

                   }

                   return filp;

         }

 

         /*要打开的文件已经存在*/

         mutex_unlock(&dir->d_inode->i_mutex);

         /*保存inode节点*/

         audit_inode(pathname, path.dentry);

 

/*

         *省略若干flag标志检查代码

         */

 

         /*路径装化为相应的nameidata 结构*/

         path_to_nameidata(&path, &nd);

         error = -EISDIR;

         /*如果是文件夹*/

         if (S_ISDIR(path.dentry->d_inode->i_mode))

                   goto exit;

ok:

         /*检测是否截断文件标志*/

         will_truncate = open_will_truncate(flag, nd.path.dentry->d_inode);

         if (will_truncate) {

         /*要截断的话就要获取写权限*/

                   error = mnt_want_write(nd.path.mnt);

                   if (error)

                            goto exit;

         }

         //may_open执行权限检测、文件打开和truncate的操作

         error = may_open(&nd.path, acc_mode, flag);

         if (error) {

                   if (will_truncate)

                            mnt_drop_write(nd.path.mnt);

                   goto exit;

         }

         filp = nameidata_to_filp(&nd);

         if (!IS_ERR(filp)) {

                   error = ima_file_check(filp, acc_mode);

                   if (error) {

                            fput(filp);

                            filp = ERR_PTR(error);

                   }

         }

         if (!IS_ERR(filp)) {

                   if (acc_mode & MAY_WRITE)

                            vfs_dq_init(nd.path.dentry->d_inode);

 

                   if (will_truncate) {

                            //处理截断

                            error = handle_truncate(&nd.path);

                            if (error) {

                                     fput(filp);

                                     filp = ERR_PTR(error);

                            }

                   }

         }

         //安全的放弃写权限

         if (will_truncate)

                   mnt_drop_write(nd.path.mnt);

         if (nd.root.mnt)

                   path_put(&nd.root);

         return filp;

 

exit_mutex_unlock:

         mutex_unlock(&dir->d_inode->i_mutex);

exit_dput:

         path_put_conditional(&path, &nd);

exit:

         if (!IS_ERR(nd.intent.open.file))

                   release_open_intent(&nd);

exit_parent:

         if (nd.root.mnt)

                   path_put(&nd.root);

         path_put(&nd.path);

         return ERR_PTR(error);

//允许遍历连接文件,则手工找到连接文件对应的文件

do_link:

         error = -ELOOP;

         if (flag & O_NOFOLLOW)

                   //不允许遍历连接文件,返回错误

                   goto exit_dput;

         /*

          以下是手工找到链接文件对应的文件dentry结构代码

          */

         // 设置查找LOOKUP_PARENT标志

         nd.flags |= LOOKUP_PARENT;

         //判断操作是否安全

         error = security_inode_follow_link(path.dentry, &nd);

         if (error)

                   goto exit_dput;

         // 处理符号链接

         error = __do_follow_link(&path, &nd);

         path_put(&path);

         if (error) {

         

                   release_open_intent(&nd);

                   if (nd.root.mnt)

                            path_put(&nd.root);

                   if (error == -ESTALE && !force_reval) {

                            force_reval = 1;

                            goto reval;

                   }

                   return ERR_PTR(error);

         }

         nd.flags &= ~LOOKUP_PARENT;

         // 检查最后一段文件或目录名的属性情况

         if (nd.last_type == LAST_BIND)

                   goto ok;

         error = -EISDIR;

         if (nd.last_type != LAST_NORM)

                   goto exit;

         if (nd.last.name[nd.last.len]) {

                   __putname(nd.last.name);

                   goto exit;

         }

         error = -ELOOP;

         // 出现回环标志: 循环超过32次

         if (count++==32) {

                   __putname(nd.last.name);

                   goto exit;

         }

         dir = nd.path.dentry;

         mutex_lock(&dir->d_inode->i_mutex);

         // 更新路径的挂接点和dentry

         path.dentry = lookup_hash(&nd);

         path.mnt = nd.path.mnt;

         __putname(nd.last.name);

         goto do_last;

}

分析完上述主要函数以后,我们来看一下整个打开流程是如何做到的:

在内核中要打开一个文件,首先应该找到这个文件,而查找文件的过程在vfs里面是由do_path_lookup或者path_lookup_open函数来完成的。这两个函数将用户传进来的字符串表示的文件路径转换成一个dentry结构,并建立好相应的inode和file结构,将指向file的描述符返回用户。用户随后通过文件描述符,来访问这些数据结构。

基本函数流程及调用方式如下所示:

打开过程首先是open系统调用访问SYSCALL_DEFINE3函数,然后调用do_sys_open 函数完成主要功能,再调用函数do_filp_open完成主要的打开功能,下面详细看下do_filp_open中调用的do_path_lookup主要过程:

staic int  do_path_lookup(int dfd,const char

*name,unsigned int flags,strucy nameidata *nd)

{

           int retval=path_init(dfd,name,flags,nd);

//设置nd->root=根路径(绝对地址)或者当前工作目录(相对地址) 。

//这一步做完了后,内核会建立一些数据结构(dentry,inode)来初始化查找的起点

           if(!retval)

                       retval = path_walk(name,nd);

//path_walk,会遍历路径的每一份量,也就是用“/”分隔开的每一部分,

//最中找到name指向的文件,walk的意思就是walk path的每一个组分(component)

        }

 

我们进一步看看path_walk

int path_walk(const char *name,struct nameidata *nd)

{

            return link_path_walk(name,nd);

//path_walk其实相当于直接调用link_path_walk来完成工作

}

link_path_walk的主要工作是有其内部函数__link_path_walk 来完成的

          result = __link_path_walk(name,nd)

         

至此我们转向最重要的代码__link_walk_path,该函数把传进来的字符串name,也就是用户指定的路径,按路径分隔符分解成一系列小的component。比如用户说,我要找/path/to/dest这个文件,那么我们的文件系统就会按path,to,dest一个一个来找,知道最后一个分量是文件或者查找完成。他找的时候,会先用path_init初始化过的根路径去找第一个分量,也就是path。然后用path的dentry->d_inode去找to,这样循环到最后一个。注意,内核会缓存找到的路径分量,所以往往只有第一次访问一个路径的时候,才会去访问磁盘,后面的访问会直接从缓存里找,下面会看到,很多与页告诉缓存打交道的代码。但不管怎样,第一遍查找总是会访问磁盘的。

static int __link_path_walk(const char *name,strucy

nameidata *nd)

{

}

至此,按照每一个component查找完成之后,就会找到相应的文件,然后相应的打开工作就基本完成了。


 

 

 

open函数的流程 1.          引言       从事Linux环境工作2年有余,一直懵懵懂懂,1年前拜读了《莱昂氏UNIX源代码分析》一书,感觉自己的学习道路漫漫且修远。最近受chinaunix的精华文帖启发,拟将近来的部分内核调用分析笔记拿出来与各前辈先进共同探讨学习,以壮个人学习之路。       本部分主要讲述的是文件I/O操作的2.6.11内核版本实现,包括了主要的数据结构、宏定义和函数流程 阅读详情

相关推荐

SourceInsight:Linux内核源码阅读器

Source Insight是一个面向项目开发的程序编辑器和代码浏览器,它拥有内置的对C/C++, C#和Java等程序的分析。SourceInsight是基于windows操作系统的源码阅读器,在阅读Linux内核源码之前要先把它放到windows操作系统下。

qq_35713097的博客 2146

Linux文件系统IO:直接IO原理与实现:缓存I/O、直接I/O

目录 缓存I/O 缓存I/O的优缺点 直接I/O 直接I/O实现 -direct_IO(),brw_kiovec() 推荐阅读 缓存I/O 一般来说,当调用open()系统调用打开文件时,如果不指定O_DIRECT标志,那么就是使用缓存I/O来对文件进行读写操作。我们先来看看open()系统调用的定义: int open(const char *pathname, int flags, ... /*, mode_t mode */ ); 下面说明一下各个参数的作用:...

RToax 4299

Linux内核基础——Linux源码阅读工具Source Insight4.0

Linux内核源码阅读工具——source insight4.0Source insight4.0工具的使用入门一、Souce insight建立工程、导入源码二、遍历所有源码文件建立符号索引 Source insight4.0工具的使用入门 本篇博客为学习同步更新博客,学习如何使用source insight查看linux内核源码以及工具的使用技巧。 一、Souce insight建立工程、导入...

SuperBoy_Zjc 4717

Linux内核文件操作

Linux内核文件操作前言一、文件操作结构体二、VFS之file_operations对象1.文件打开filp_open2.文件关闭filp_close3.文件读取vfs_read4.文件写入vfs_write4.注意点驱动模块实例 前言 Linux系统中的文件系统由两层结构进行构建:第一层为虚拟文件系统(VFS),第二层则是各种不同的具体的文件系统。VFS则是将各种具体的文件系统的公共部分抽取出来,从而形成一个抽象层,是Linux系统内核的一部分,它位于用户程序和具体的文件系统之间,对用户提供了标准的

年少生而为人 1817

VSCode+clangd阅读linux内核源码

VSCode+clangd阅读linux内核源码

Jaylen.sun的嵌入式之旅 1万+

linux 应用程序中的open 操作与硬件驱动之间的联系浅析

第一节 open 函数的系统操作过程,在fs/open.c 文件中 read、write 函数的系统操作过程,在fs/read_write.c 文件中 对于linux 下所有的系统调用函数,均可找到与其对应的内核函数sys_xxx().比如此处的open函数,对应内核函数就是sys_open。只不过,在此内核版本中,它的定义是如下这种形式 SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int,mode)。 这个SYSCALL_DEFINE3 就是个宏定义,如果展开,到最后,其实上面这个函数就是 asmlinkage long sys_open(const char __user filename,int flags,int mode)。 那么,就是上面说过的,open 函数,对应的内核函数,就是sys_open,这种形式。

文件系统的系统调用过程

<br />有关文件系统的系统调用中有好几个,这里分析了其中的系统调用open、sys_read。其它的系统调用参阅内核源代码。 系统调用open <br />系统调用open是由函数sys_open(fs/open.c)实现的。函数sys_open最终通过具体文件系统的节点的lookup函数,用 文件路径名查找得到路径名对应的dentry结构,通过dentry得到对应的inode结构,分配文件描述符,用fd文件描述符作为file的索引,用 dentry结构中数据填充这个file结构。以后对这个

lcw_202的专栏 1701

机器学习-深嵌交叉验证(Nested Cross-Validation)

Section I: Code and Analyses 第一部分:代码 from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC from...

Santorinisu Blogs 2716

使用 Source Insight 阅读 Linux 内核源码

在后面开发驱动程序时,驱动程序中用到的函数都是来自内核,所以可以先在 Windows 下创建内核的Source Insight 工程。 如果你不想学习驱动开发,那么可以不创建内核的工程。但是以后学习大型 APP 时,也可以使用 Source Insight 来阅读、编写代码,可以借鉴本节讲解的 Source Insight 用法。 7.4.1 Source Insight 简介 Source Insight 是 Source Dynamics 公司出品的源代码编辑器。Source Insight 提供语法突

韦东山嵌入式专栏 8061

ubuntu18.04美化主题(完整版)

和网上的美化一样,但是我当初跟着博客美化的时候遇到一些问题,按博客的做法无法解决。 所以我自己也写一篇关于ubuntu18.04美化主题的博客。 第一步:安装主题工具:GNOME Tweaks sudo apt-get update sudo apt-get install gnome-tweak-tool 然后安装完成后我们打开Tweaks(截图如下): 第二步,修改窗口按钮位...

B丶atty的博客 7万+

i2c转串口驱动移植

i2c转串口驱动移植欢迎使用Markdown编辑器新的改变思路与使用方式合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入 欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用M

mars_Vessalius的博客 2276

【Linux 内核】Linux 内核源码结构 ( 下载 Linux 内核源码 | 使用 VSCode 阅读 Linux 内核源码 )

一、下载 Linux 内核源码、 二、使用 VSCode 阅读 Linux 内核源码

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 5523

网易数智CoreAgent实战:5步教你打造企业级AI数字员工(附避坑指南)

本文以网易数智CoreAgent智能体协作中枢为核心,提供了一份企业级AI数字员工的实战部署指南。文章详细拆解了从环境准备、智能体配置、业务集成、安全合规到效果评估的五个关键步骤,并分享了部署过程中的常见“避坑”经验,旨在帮助企业技术团队高效、安全地将AI智能体落地于实际业务场景,解决AI落地难题。

rice5的博客 953

Linux内核源码阅读以及工具详解

接上篇Linux内核源码下载方法 这篇总结了如何利用source insight对Linux内核代码进行阅读和学习(资料来源于网络) 随着linux的逐步普及,现在有不少人对于Linux的安装及设置已经比较熟悉了。与Linux 的蓬勃发展相适应,想深入了解Linux的也越来越多。而要想深入了解Linux,就需要阅读和分析linux内核的源代码。 Linux的内核源代码可以从很多途径得到。一般来讲...

CodeAllen嵌入式 4537

如何使用Agent框架来模拟群体智能?

Agent 框架作为人工智能领域的重要工具,可以模拟复杂的群体智能行为,广泛应用于社会科学、经济学和行为学等领域。

335

Winform利用动画控制实现各种特效

动画绘制控制类代码 using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Threading; using System.Windows.Forms; namespace Mengliao.CSharp.A14 { #region 动画类型枚举 // 本例为了简单起见没有使用见名知意的名称,如用于实际可将这些枚举成员更名

LilyCoder的博客 2502

linux 内核代码阅读工具,linux内核源码阅读工具

一 linux内核源码阅读工具windows下当然首选source insight, 但是linux下就没有sourceinsight这么优秀的工具了,但是也有不少的替代品,但觉绝对部分人会选择vim+ctags+cscope的组合,还有部分人或选择wine中的sourceinsight或选择navigatror,当然对于代码阅读来说vim+ctags+cscope的组合还是比较好的一个选择方案,...

weixin_42306002的博客 1991

VTK实战完整教程介绍

该专栏以VTK提供的Example为基础,讲解VTK中的相关接口和使用方法。订阅该专栏,带你了解所有VTK的功能,以便VTK更好的解决工作中遇到的问题。一起学习起来吧!

qq_40041064的博客 2506

Linux内核源码阅读入门(全网最详细教程)

通过阅读Linux内核,你能深入理解Linux底层运行机制,对操作系统有深入理解,会对整个计算机体系都有一个更深刻的认识。

猪猪和超超的博客 1万+

YOLOv8交通管理 详细解释及代码完整示例

YOLOv8在交通管理中的应用及代码实现 摘要:本文详细介绍了YOLOv8在交通管理领域的应用场景,包括车辆检测、违规行为识别和行人安全预警等。文章提供了完整的Python代码示例,展示了如何使用YOLOv8进行交通对象检测和车辆计数统计。代码包含模型加载、对象检测、结果可视化等功能模块,并给出了性能优化和功能扩展建议。该实现基于Ultralytics框架,支持多种YOLOv8模型选择,可根据实际需求调整模型大小和检测参数。

u010117029的博客 153

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

vscode+clangd阅读Linux内核源码

运行上述命令后,在内核源码根目录生成了compile_commands.json文件。至此,配置已经完成。打开任意c文件,点击函数以后,就能自动跳转到其定义处。添加下面的选项参数到clangd server。[在这里插入图片描述]t。

Yuke的博客 1907

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

如何阅linux内核源码,阅读 Linux 内核源码的正确姿势

首页 专栏 linux 文章详情0 阅读 Linux 内核源码的正确姿势 Constantine3 发布于 2 月 28 日引言动机最近有阅读 Linux 内核源码的需求,而现在主力机是 Mac,所以 Windows 环境下传统的 Source Insight 工具不能继续使用了。那么,只好祭出宇宙最强编辑器——VSCode!效果先看最终效果图:代码语法高亮也不会有讨厌的红色波浪线报错。同时支持跳...

weixin_35997546的博客 1061

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

搞会linux内核源码,linux内核源码阅读工具

一 linux内核源码阅读工具windows下当然首选source insight, 但是linux下就没有sourceinsight这么优秀的工具了,但是也有不少的替代品,但觉绝对部分人会选择vim+ctags+cscope的组合,还有部分人或选择wine中的sourceinsight或选择navigatror,当然对于代码阅读来说vim+ctags+cscope的组合还是比较好的一个选择方案,...

weixin_42174098的博客 186

linux内核源码怎么读,linux内核源码阅读工具

一 linux内核源码阅读工具windows下当然首选source insight, 但是linux下就没有sourceinsight这么优秀的工具了,但是也有不少的替代品,但觉绝对部分人会选择vim+ctags+cscope的组合,还有部分人或选择wine中的sourceinsight或选择navigatror,当然对于代码阅读来说vim+ctags+cscope的组合还是比较好的一个选择方案,...

weixin_35725389的博客 244
上一篇: kobject,kset,子系统层次结构 、platform_device platform_driver
下一篇: 关于platform_driver 是如何匹配 platform_device的和如何调用到platform_driver中的probe函数的研究
ADC0809608
博客等级 码龄17年 44粉丝 10原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值