open系统调用:创建file结构体,(指针)放入进程打开文件表,返回表下标(文件描述符)

转自:http://blog.csdn.net/qiang81020/archive/2010/06/20/5681481.aspx

我们常常使用系统调用open来打开一个文件,例如:

fd = open( "/mnt/data/myfile",O_RDWR|O_CREAT);

下面来看看Linux是如何完成的,首先是系统调用的代码:

sys_open的源程序

view plaincopy to clipboardprint?
asmlinkage long sys_open(const char * filename, int flags, int mode)  
{  
        char * tmp;  
        int fd, error;  
#if BITS_PER_LONG != 32  
        flags |= O_LARGEFILE;  
#endif  
        tmp = getname(filename);  
        fd = PTR_ERR(tmp);  
        if (!IS_ERR(tmp)) {  
                fd = get_unused_fd();/*<-----------------------------------(1) */ 
                if (fd >= 0) {  
                        struct file *f = filp_open(tmp, flags, mode);/*<---(2) */ 
                        error = PTR_ERR(f);  
                        if (IS_ERR(f))  
                                goto out_error;  
                        fd_install(fd, f);/*<------------------------------(3) */ 
                }  
out:  
                putname(tmp);  
        }  
        return fd;  
out_error:  
        put_unused_fd(fd);  
        fd = error;  
        goto out;  

asmlinkage long sys_open(const char * filename, int flags, int mode)
{
        char * tmp;
        int fd, error;
#if BITS_PER_LONG != 32
        flags |= O_LARGEFILE;
#endif
        tmp = getname(filename);
        fd = PTR_ERR(tmp);
        if (!IS_ERR(tmp)) {
                fd = get_unused_fd();/*<-----------------------------------(1) */
                if (fd >= 0) {
                        struct file *f = filp_open(tmp, flags, mode);/*<---(2) */
                        error = PTR_ERR(f);
                        if (IS_ERR(f))
                                goto out_error;
                        fd_install(fd, f);/*<------------------------------(3) */
                }
out:
                putname(tmp);
        }
        return fd;
out_error:
        put_unused_fd(fd);
        fd = error;
        goto out;
}

这里面完成的几个工作

1.注意到返回一个整数fd,所以在(1)的位置获得一个整数fd。
2.根据路径/mnt/data/myfile找到(或创建)文件,并且创建一个结构file(见(2))。
3.将整数fd和file结构指针f联系起来(见(3))。

注意:
结构file实际上是系统打开文件表中的表项,目的是实现不同进程共享文件的读写指针的情形。(另外一种文件共享是不同的进程有各自的文件读写指针的情形)。

文件打开过程的总结:

系统在进程打开文件表中找到一个未使用的fd;
根据open中提供的路径字符串找到(创建)对应的dentry(从而找到inode);
创建系统打开文件表中的一个打开文件表项(创建一个file结构,使用指针filp指向file结构),并使该表项指向dentry(file的字段之一就是f_dentry,指向被打开文件在其目录中的表项dentry);
在fd所指示的进程打开文件表表项中设置指向file结构的指针;
系统使用filp->f_dentry->d_inode的方式访问磁盘inode,将其读入内存中,创建对应的vnode。因此,可以认为系统打开文件表表项有指针指向vnode结构
进一步了解:

Linux 2.6.11内核文件IO系统调用

Linux 2.6.11内核文件IO系统调用
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返回的文件描述符标识该文件,将其作为参数传送给read或write。在POSIX.1应用程序中,文件描述符为常数0、1和2分别代表STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO,意即标准输入,标准输出和标准出错输出,这些常数都定义在头文件

;中。
文件描述符的范围是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指终端设备,则不将此设备分配作为此进程的控制终端。not fcntl O_TRUNC<01000>;: 如果此文件存在,而且为只读或只写成功打开,则将其长度截短为0。not fcntl
    O_APPEND<02000>;: 每次写时都加到文件的尾端
   O_NONBLOCK<04000>;: 如果p a t h n a m e指的是一个F I F O、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I / O操作设置非阻塞方式。
O_NDELAY;;
   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( 见4.2.4.1.1节 )
   |                        | ------------ dentry_open( 见4.2.4.2节 )
4.2.2.        主调用函数sys_open
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;

}

从open系统调用的源码看文件的打开过程相关推荐

  1. dlsym 如何查看一个so里面的_Android so 文件进阶二 从dlsym()源码看android 动态链接过程...

    0x00  前言 这篇文章其实是我之前学习elf文件关于符号表的学习笔记,网上也有很多关于符号表的文章,怎么说呢,感觉像是在翻译elf文件格式的文档一样,千篇一律,因此把自己的学习笔记分享出来.dls ...

  2. LwIP 之一 源码目录文件详解及移植说明

       lwIP 是 TCP/IP 协议套件的一个小型独立实现.lwIP TCP/IP 实现的重点是减少 RAM 使用同时仍然有一个完整的 TCP. 这使得 lwIP 适合使用在具有数 10 千字节的可 ...

  3. codeblock socket 编译错误_从Linux源码看Socket(TCP)Client端的Connect

    从Linux源码看Socket(TCP)Client端的Connect 前言 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情. 今天笔者就来从Linux源码的 ...

  4. linux源码_从linux源码看epoll及epoll实战揭秘

    从linux源码看epoll 前言 在linux的高性能网络编程中,绕不开的就是epoll.和select.poll等系统调用相比,epoll在需要监视大量文件描述符并且其中只有少数活跃的时候,表现出 ...

  5. ip受限 linux_从linux源码看epoll及epoll实战揭秘

    从linux源码看epoll 前言 在linux的高性能网络编程中,绕不开的就是epoll.和select.poll等系统调用相比,epoll在需要监视大量文件描述符并且其中只有少数活跃的时候,表现出 ...

  6. 从linux源码看epoll及epoll实战揭秘

    从linux源码看epoll 前言 在linux的高性能网络编程中,绕不开的就是epoll.和select.poll等系统调用相比,epoll在需要监视大量文件描述符并且其中只有少数活跃的时候,表现出 ...

  7. 从linux源码看epoll

    从linux源码看epoll 前言 在linux的高性能网络编程中,绕不开的就是epoll.和select.poll等系统调用相比,epoll在需要监视大量文件描述符并且其中只有少数活跃的时候,表现出 ...

  8. FatFs 之一 R0.13c版源码目录文件、函数、全配置项详解及移植说明

      FatFs 是用于小型嵌入式系统的通用 FAT/exFAT 文件系统模块.FatFs 模块的编写符合 ANSI C(C89),并与磁盘 I/O 层完全分离,因此它独立于硬件平台. 它可以集成到资源 ...

  9. linux内核线程socket,从Linux源码看Socket(TCP)的accept

    从Linux源码看Socket(TCP)的accept 前言 笔者一直以为若是能知道从应用到框架再到操做系统的每一处代码,是一件Exciting的事情. 今天笔者就从Linux源码的角度看下Serve ...

最新文章

  1. MySQL分库分表使用Snowflake全局ID生成器(3rd)
  2. [导入]XML for Analysis(XMLA)开发详解-(3)各版本的SQL Server及Windows下配置XMLA over HTTP访问SASS(OLAP)的文档合集...
  3. Building Shops dp 预处理距离
  4. P3375 【模板】KMP字符串匹配 (KMP模板)
  5. 初玩PLSQL连接 Oracle
  6. ExtJS + Gears
  7. 【python】用正则表达式进行文字局部替换
  8. React开发(269):事件的绑定-addEventListener
  9. 序列化(串行化)- 使用BinaryFormatter进行序列化
  10. CodeForces 589J Cleaner Robot
  11. 【Qt串口调试助手】1.8 - 修改Qt应用图标和窗口图标
  12. 预防医学的曙光 | 微软要用AI构建免疫系统图谱
  13. emu8086的入门使用
  14. Ceph OSD简介
  15. v u e + ts excel表的导入解析转换 加下载excel表格式(个人笔记)
  16. 无法卸载creative cloud桌面应用程序
  17. 6个在线正则表达式工具
  18. Spring/Boot/Cloud系列知识:SpringMVC进行HTTP信息接收和发送的过程(1)
  19. Linux格式化sd卡博客,Linux下格式化U盘或者SD卡
  20. 如何卸载Ulead VideoStudio 10 (绘声绘影)

热门文章

  1. 保存一波集合框架的介绍
  2. PHP自学4——通过函数将数组数据输出到html的Table标签中(使用函数的例子)
  3. MySQL性能调优与架构设计——第5章 备份与恢复
  4. 关于字符串排序的别的规则
  5. ASP.NET MVC 5 学习教程:Details 和 Delete 方法详解
  6. 关于“SEO五条金律”的解说
  7. du,df,fdisk,mkfs.ext3命令详解
  8. Android ExpandableListView几个特殊的属性
  9. step5 . day6 网络编程 基于TCP协议的多并发模式(使用多进程、多线程、select函数分别实现)...
  10. 6.Django与Ajax