首先,linux内核的open函数是这么定义的SYSCALL_DEFINE3(open, ...),可以查到的宏定义为

#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

然后由:

  1. #ifdef CONFIG_FTRACE_SYSCALLS
  2. #define SYSCALL_DEFINEx(x, sname, ...) \
  3. static const char *types_##sname[] = { \
  4. __SC_STR_TDECL##x(__VA_ARGS__) \
  5. }; \
  6. static const char *args_##sname[] = { \
  7. __SC_STR_ADECL##x(__VA_ARGS__) \
  8. }; \
  9. SYSCALL_METADATA(sname, x); \
  10. __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
  11. #else
  12. #define SYSCALL_DEFINEx(x, sname, ...) \
  13. __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
  14. #endif

转换为:

__SYSCALL_DEFINEx(3,_open,__VA_ARGS__)

紧接着再次由:

  1. #ifdef CONFIG_HAVE_SYSCALL_WRAPPERS
  2. #define SYSCALL_DEFINE(name) static inline long SYSC_##name
  3. #define __SYSCALL_DEFINEx(x, name, ...) \
  4. asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__)); \
  5. static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__)); \
  6. asmlinkage long SyS##name(__SC_LONG##x(__VA_ARGS__)) \
  7. { \
  8. __SC_TEST##x(__VA_ARGS__); \
  9. return (long) SYSC##name(__SC_CAST##x(__VA_ARGS__)); \
  10. } \
  11. SYSCALL_ALIAS(sys##name, SyS##name); \
  12. static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__))
  13. #else /* CONFIG_HAVE_SYSCALL_WRAPPERS */
  14. #define SYSCALL_DEFINE(name) asmlinkage long sys_##name
  15. #define __SYSCALL_DEFINEx(x, name, ...) \
  16. asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))
  17. #endif /* CONFIG_HAVE_SYSCALL_WRAPPERS */

转换为:

  1. asmlinkage long sys_open(__SC_DECL3(__VA_ARGS__))
  2. (t3 a3,__SC_DECL2(__VA_ARGS__))
  3. (t3 a3,t2 a2,t1 a1)
  4. static inline long SYSC_open(t3 a3,t2 a2,t1 a1);
  5. asmlinkage long SyS_open()
  6. {
  7. __SC_TEST(t3);
  8. __SC_TEST(t2);
  9. __SC_TEST(t1);
  10. return (long) SYSC_open((t3) a3,(t2) a2,(t1) a1);
  11. }

不再一一展开。总之最后变为

do_sys_open(AT_FDCWD, filename, flags, mode)

现在来看看里面具体的过程,现在假设当前带入的flags为O_CREAT和O_RDWR(不同的标志组合实在太多)

首先是由build_open_flags(flags, mode, &op);处理打开文件时所附带的标志。

别的不多说,就说一个在这个函数里进行的操作:

op->intent = flags & O_PATH ? 0 : LOOKUP_OPEN;

去查了一下O_PATH标志并不是经常使用,而使用该标志一般意味着 to perform operations that act purely at the file descriptor level. The file itself is not opened。也就是说使用该标志并不会真正打开该文件。所以正常情况下op->intent = LOOKUP_OPEN

  1. ...
  2. tmp = getname(filename);
  3. ...
  4. fd = get_unused_fd_flags(flags);

首先是从用户空间把文件名拷贝到内核空间,其中还对文件名长度接近PATH_MAX的情况做了特殊处理,保证能拷贝到小于等于PATH_MAX的文件名(之所以做特殊处理是因为当文件名远小于4096时,该函数做了一些优化)。取出文件名后,就需要从系统的fd集合中取出尚未被使用的fd(file descriptor)。即进入到了int __alloc_fd(struct files_struct *files,unsigned start, unsigned end, unsigned flags)函数。

  1. fd = start;
  2. if (fd < files->next_fd)
  3. fd = files->next_fd;
  4. if (fd < fdt->max_fds)
  5. fd = find_next_zero_bit(fdt->open_fds, fdt->max_fds, fd);
  6. ...
  7. if (start <= files->next_fd)
  8. files->next_fd = fd + 1;

从上面这段代码可以看出,files->next_fd记录的是当前可以使用的最小的fd。注意find_next_zero_bit是从fd的基础上开始找的第一个可用的fd。所以如果start这次小于files->next_fd,就说明这次的fd是从files->next_fd开始找的,那么下次可用的最小的files->next_fd自然是这次分配到的fd加1(当然不保证一定可用,需要find_next_zero_bit进行验证)。

fd分配成功后,就要真正进行打开文件的操作了:

struct file *f = do_filp_open(dfd, tmp, &op);

可以看到在该函数内部创建了一个file结构体,以便为了后来表示打开的文件:

  1. struct file *filp;
  2. filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU);

上述标志中增加了一个LOOKUP_RCU,似乎RCU机制可以加快操作。后文将默认使用RCU机制

file = get_empty_filp(); 

上行代码从filp_cachep中获得一个空的已分配空间的file结构体。因为我的标志当中没有O_TMPFILE所以进入到error = path_init(dfd, pathname->name, flags, nd)

  1. <span style="white-space:pre"> </span>nd->last_type = LAST_ROOT; /* if there are only slashes... */
  2. nd->flags = flags | LOOKUP_JUMPED | LOOKUP_PARENT;
  3. nd->depth = 0;
  4. nd->base = NULL;

如果给的路径名只是斜杠的话,那么就是打开根目录,进行相应的处理。这里不考虑这种情况。

  1. if (*name=='/') {
  2. ...
  3. }
  4. else if (dfd == AT_FDCWD)
  5. ...
  6. }

路径名分两种情况,一种是绝对路径,一种是相对路径。这里都给出了处理分支。解释一下struct nameidata *nd这个结构体。这个结构体从这开始贯穿整个open函数,代表着当前正在操作或即将操作的dentry及其他一些相关信息。假设路径名为相对路径,那么可见nd->path = fs->pwd;。即被赋值为当前目录。这些处理完后,就可以进入link_path_walk(name, nd);该函数遍历路径各个分支,直到取到最后一个dentry或出错啥的。

  1. link_path_walk(name,nd){
  2. for(;;){
  3. may_lookup(nd);{//权限检查
  4. inode_permisssion(nd->inode,MAY_EXEC|MAY_NOT_BLOCK);
  5. }
  6. type = LAST_NORM;
  7. if(name[0] == '.'){
  8. //如果碰上了".."或".",那么type=LAST_DOTDOT或LAST_DOT
  9. }
  10. if(type == LAST_NORM){
  11. struct dentry *parent = nd->path.dentry;
  12. nd->flags &= ~LOOKUP_JUMPED;
  13. }
  14. nd->last.hash_len = hash_len;
  15. nd->last.name = name;
  16. nd->last_type = type;
  17. name += hashlen_len(hash_len);
  18. ...//经过一番处理,walk刚刚得到的dentry
  19. walk_component(truct nameidata *nd, struct path *path,int follow){
  20. if (nd->last_type != LAST_NORM)
  21. return handle_dots(nd, nd->last_type);
  22. err = lookup_fast(nd, path, &inode);{
  23. //首先在dentry_hashtable内部进行一次快速查找,从这次查找可以看出在该parent下其子dentry组成了一个hash_list
  24. dentry = __d_lookup_rcu(parent, &nd->last, &seq);
  25. path->mnt = mnt;
  26. path->dentry = dentry;
  27. return 0;
  28. //当然刚才也可能出现在缓存的dentry_hashtable找不到的情况。
  29. unlazy_walk(nd, dentry);//rcu to ref I think it's fast change to slow
  30. ...
  31. return 1;//need_lookup
  32. }
  33. if (err){
  34. err = lookup_slow(nd, path);
  35. }
  36. if (!inode || d_is_negative(path->dentry)) //当然也可能存在所要求的dentry不存在的情况:因为创建文件或恶意操作?
  37. goto out_path_put;
  38. ...//follow link?
  39. path_to_nameidata(path, nd);
  40. nd->inode = inode;
  41. return 0;
  42. }
  43. if (!d_can_lookup(nd->path.dentry)) { //如果该dentry不是目录
  44. err = -ENOTDIR;
  45. break;
  46. }
  47. }
  48. terminate_walk(nd);//如果是RCU过程,释放RCU_LOCK
  49. }

上面这段代码去掉了link_path_walk一些不太重要的部分,并且对有些比较重要的函数调用直接附在该函数调用后面进行解释,同时走的是RCU路径,。通过上面代码,如果不发生意外错误,一般都会使得nd的dentry为路径名的最后一部分的父目录,并且nd->name为路径名的最后一部分。

做完这些操作后,进入do_last(nd, &path, file, op, &opened, pathname);其中opend=0。如函数名提示,开始进入函数的最后一部分了。

  1. static int do_last(struct nameidata *nd, struct path *path,
  2. struct file *file, const struct open_flags *op,
  3. int *opened, struct filename *name){
  4. if (nd->last_type != LAST_NORM) { //如果是.或..
  5. error = handle_dots(nd, nd->last_type);
  6. goto finish_open;
  7. }
  8. if (!(open_flag & O_CREAT)) { //处理非创建的情况
  9. error = lookup_fast(nd, path, &inode);
  10. }
  11. else{
  12. error = complete_walk(nd);
  13. ...
  14. }
  15. retry_lookup:
  16. if (op->open_flag & (O_CREAT | O_TRUNC | O_WRONLY | O_RDWR)) {<span style="white-space:pre"> </span>//创建操作需要当前挂载的文件系统具有写权限
  17. error = mnt_want_write(nd->path.mnt);
  18. }
  19. error = lookup_open(nd, path, file, op, got_write, opened);{
  20. dentry = lookup_dcache(&nd->last, dir, nd->flags, &need_lookup);
  21. if (!need_lookup && dentry->d_inode)
  22. goto out_no_open;
  23. if ((nd->flags & LOOKUP_OPEN) && dir_inode->i_op->atomic_open) {
  24. return atomic_open(nd, dentry, path, file, op, got_write,
  25. need_lookup, opened);{
  26. if (((open_flag & (O_CREAT | O_TRUNC)) ||
  27. (open_flag & O_ACCMODE) != O_RDONLY) && unlikely(!got_write))//看清楚这个逻辑判断操作,先或操作再且操作,主要是希望有写权限,但是可能没有got_write
  28. }
  29. }
  30. }
  31. }

因为标志是O_CREAT,所以会进入else分支,然后接下来处理创建的一些工作。其中一般都会进入atomic_open部分。该函数的注释说如果返回0,代表此次创建成功,且file结构体成功关联到此次创建的文件,如果返回1,则此次只做了查找操作,具体的打开文件操作仍然要交给调用者。别的情况就返回错误。

最后一些权限检查后,基本就完成了open函数的操作。

这其中对元数据查询的最关键的部分应该是在link_path_walk

O_CREAT和O_RDWR进入linux系统调用open函数相关推荐

  1. linux 系统函数调用脚本文件,Linux系统调用fsync函数详解

    Linux系统调用fsync函数详解 发布时间:2013-11-14 19:55:10   作者:佚名   我要评论 Linux fsync函数主要用于将同步内存中所有已修改的文件数据到储存设备,多用 ...

  2. linux 系统调用 open函数使用

    函数介绍 本文仅仅将open系统调用的使用简单总结一下,关于其实现原理大批的大佬分享可以自行学习.open系统调用主要用于打开或者创建一个文件,并返回文件描述符. 头文件 #include <f ...

  3. linux磁盘同步函数,Linux系统调用fsync函数详解

    功能描述: 同步内存中所有已修改的文件数据到储存设备. 用法: #include int fsync(int fd); 参数: fd:文件描述词. 返回说明: 成功执行时,返回0.失败返回-1,err ...

  4. uname命令 linux,Linux系统调用--uname()函数及系统下的uname命令

    [uname系统调用] 功能描述: 获取当前内核名称和其它信息. 用法: #include /* Put information about the system in NAME.  */ exter ...

  5. Linux系统调用--getrusage函数详解

    功能描述:    获得进程的相关资源信息.如:用户开销时间,系统开销时间,接收的信号量等等; 用法:   #include <sys/types.h>   #include <sys ...

  6. Linux系统调用之lseek函数

    前言 如果,想要深入的学习Linux系统调用函数lseek了话,还是需要去阅读Linux系统中的帮助文档的. 具体输入命令: man 2 lseek 即可查阅到完整的资料信息. lseek函数 lse ...

  7. Linux系统调用之execve函数与标准C库exec函数族(有关于进程方面的函数族)

    前言 如果,想要深入的学习Linux系统调用里面的execve函数与标准C库中的exec函数族,还是需要去自己阅读Linux系统中的帮助文档. 具体输入命令: man 2 execve man 3 e ...

  8. linux ioctl root权限,Linux系统调用设备的ioctl函数

    Linux系统调用设备的ioctl函数 在命令行调用设备的ioctl函数.在Linux系统中,似乎对设备的直接操作只有ioctl函数了.他接受的参数不是太多,而且都是一一对应的. blockdev - ...

  9. linux中recvfrom读取速度,Linux系统调用-- recv/recvfrom 函数详解

    Linux系统调用-- recv/recvfrom函数详解 功能描述: 从套接字上接收一个消息.对于recvfrom,可同时应用于面向连接的和无连接的套接字.recv一般只用在面向连接的套接字,几乎等 ...

最新文章

  1. leetcode 8. 字符串转换整数 (atoi)
  2. html表格颜色sql,如何用SQL语句操作Table
  3. 埋在 MySQL 数据库应用中的17个关键问题!
  4. 密码技术--非对称加密算法及Go语言应用
  5. vim windows版本_大概是篇Vim入门教程(1): 基本的一些东西
  6. junit单元测试报错InvalidTestClassError: Invalid test class
  7. activity7 拖不动_Activiti7相关问题汇总
  8. ABAP如何调用OCX
  9. Mysql中explain命令查看语句执行概况
  10. 在张学友演唱会的6万观众中,AI锁定了一名逃犯
  11. leetcode讲解--693. Binary Number with Alternating Bits
  12. 引导界面滑动导航 + 大于等于1页时无限轮播 + 各种切换动画轮播效果
  13. Hbase API实现倒序查询
  14. CNN图片分类(Pytorch)
  15. libmodbus 手册翻译
  16. We‘re sorry but XX doesn‘t work properly without JavaScript enabled. Please enable it to continue
  17. 研发思维09----嵌入式智能产品开发经过思考
  18. Android 音频系统
  19. 【C语言练习——打印空心下三角及其变形】
  20. Springmvc 控制器的作用

热门文章

  1. 从0到1搭建大数据平台之数据存储
  2. Python degrees() 函数
  3. HashMap实现的原理,hashmap怎么存的值?看了就懂了
  4. 配置K8S出现以下错误“/proc/sys/net/ipv4/ip_forward contents are not set to 1”
  5. 性能测试-工具篇:jmeter - Header管理器
  6. 计算机专业工作紧张熬夜吗,性别:男,年龄:35,合肥,从事工作:计算机,性格内向,生活和工作压力大,人好紧张焦虑,前几年总是加.....
  7. ROS-Control专题:PR2的六个概念【6】
  8. 中创 | 云服务市场竞争加剧,全国增值电信业务经营许可企业达14万家
  9. deviance resolve达芬奇15 mac 破解教程
  10. MacBook Pro M1 安装 Docker