Linux 中open系统调用实现原理【转】

转自:http://blog.chinaunix.net/uid-25968088-id-3426026.html

目录

OPEN系统调用过程

Open在内核里面的入口函数时sys_open

Sys_open函数内容

do_sys_open(AT_FDCWD, filename, flags, mode)

1.      找到一个本进程没有使用的文件描述符fd(int型)

2.      分配一个全新的struct file结构体

3.      根据传人的pathname查找或建立对应的dentry

4.      建立fd到这个struct file结构体的联系

 

WORD里面的目录复制过来似乎不能直接用。。还是放在这里当主线看吧..

用户空间的Open函数在内核里面的入口函数是sys_open

    通过grep open /usr/include/asm/unistd_64.h查找到的

#define __NR_open                               2

__SYSCALL(__NR_open, sys_open)

    观察unistd_64.h,我们可以猜测用户空间open函数最终调用的系统调用号是2来发起的sys_open系统调用(毕竟glibc一般的做法都会做,用户空间的函数名字和内核空间中调用的很像,如果需要得到非常准确的,请查看glibc源码找到对应的系统调用号,再和内核里面的系统调用号去一一对比)。这里我们不纠结。

 

Sys_open函数内容

 

通过前面的Linux 编程中的API函数和系统调用的关系 我们得知,sys_open实际就是下面这个函数(fs/open.c中)

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, 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;

}

其中先调用force_o_largefile()来判断是否需要设置大文件标识,然后调用do_sys_open来完成具体的工作。其中

force_o_largefile()

         在IA64系统中 arch/ia64/include/asm/fcntl.h,定义如下

#define _ASM_IA64_FCNTL_H

#define force_o_largefile()  (personality(current->personality) != PER_LINUX32)

#include

#endif /* _ASM_IA64_FCNTL_H */

 

而其余的在include/linux/fcntl.h中

#ifndef force_o_largefile

#define force_o_largefile() (BITS_PER_LONG != 32)

#endif

所以,在非32位的OS上,force_o_largefile()都为true,而32位的OS则为false

另外,我们可以查看我们的OS位数:

# grep CONFIG_64BIT /boot/config-2.6.32-220.el6.x86_64

CONFIG_64BIT=y //64位

 

#ifdef CONFIG_64BIT

#define BITS_PER_LONG 64

#else

#define BITS_PER_LONG 32

#endif /* CONFIG_64BIT */

 

所以:只有在32位的OS上此处才为false(这里不考虑IA64架构,我们考虑的是x86架构),所以64位的系统上flags会自动加上O_LARGEFILE,32位的则没有,所以文件最大大小受索引节点中表示文件大小的32位的i_size的影响,只能访问2的32次方字节,即4GB(实际高位一般不用,所以通常只有2G)。加上O_LAGEFILE之后启用索引节点的i_dir_acl字段也可以一起表示文件的大小了,这样位数就变成了64位,2的64位就4GB*4GB,单个文件这么大已经很大了16T了

 

 

我们重点来看do_Sys_open函数

do_sys_open(AT_FDCWD, filename, flags, mode)

函数原型long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)。

这个函数这里(我们讲述最主要的内容)执行过程

 

1.    找到一个本进程没有使用的文件描述符fd(int型)

 

(1)在当前进程打开的文件位图表中,找到第一个为0的位,返回这个位在位图表里面的下标,这个下标就是将用分配的未使用的文件描述符fd

 

(2)把当前进程的文件表扩展一个文件(即尝试添加一个struct file到当前

进程的文件列表中),进程task_struct-> files_struct-> fd_array[NR_OPEN_DEFAULT]是一个struct file 数组,而NR_OPEN_DEFAULT在64位系统中等于64(因为一般进程打开的文件数大多都用这个数组就可以直接放下了),如果扩展操作导致当前进程的这个存放struct file的数组放不下了,如要装第65个struct flie结构体,那么将重新分配一片内存区专门用来存放struct file结构体,并且这个内存区的大小为128个struct file结构体,然后将当前进程的task_struct->files_struct->fdtable->fd指针指向这片内存的首地址,然后把之前数组里面的内容复制到这片内存区里面来。下次添加如果超过了128个了,则分配256个大小直到256个也装满,超过256则分配512,依次类推,总是2的幂次方,且会把之前的复制到新分配的内存里面去

 

注意:这里只是更新了进程的这个file table,新的进程描述符对应的struct file还没有生成进去。

 

(3)设置进程的文件位图中新分配的这个文件描述符位为(1)中找到的下标,并更新下一次该分配的进程描述符起点

 

2.    分配一个全新的struct file结构体

Struct file =kmem_cache_zalloc(filp_cachep, GFP_KERNEL);

 

3.    根据传人的pathname查找或建立对应的dentry

并设置此dentry对应的inode。内核做这个事情借助于一个nameidata数据结构

(1)    如果pathname中第一个字符是“/”,那么说明使用绝对路径,设置nameidata为更目录对应的dentry和当前目录的inode,mount点等

 

(2)    如果不是“/”,则使用相对路径,设置nameidata为当前目录对应的dentry,inode,mount点等

 

(3)    一层一层往下查找,直到找到最终的那个文件或者目录分量,注意:如”/usr/bin/make”,先找“/”(这是3.1就做了的),再找“/”下面的usr,再找bin,最后找make。

 

这里细说一下第一层怎么在“/“下面找到”usr“的:

第一层查找先找“/”下面的usr对应的dentry,内核通过“/”对应的dentry和”usr”字符串两个参数进行hash运算获取一个dentry的链表

然后逐个看这个链表里面有没有parent dentry为“/”对应的dentry的,以及dentry对应的名字的hash值是否与“usr”对应的hash值相同

前面条件都满足这里再看一下parent dentry是否有DCACHE_OP_COMPARE标识,如果有此标识且文件系统实现了dentry->dentry_operations->d_compare函数,那么就调用文件系统的这个函数来判断

如果条件都符合,那么说明内存中usr对应的这个dentry是存在的,如果这个dentry->d_flags中包含DCACHE_OP_REVALIDATE,那么就会调用此dentry->dentry_operatoin->d_revalidate来进行一次核对(网络文件系统此函数都实现了,以便于远程的便跟,在这里会得到更新)

如果最终usr对应的dentry不存在,那么内核就在内存中直接分配一个dentry结构体并且把dentry的name和“usr”对应起来,并且设置这个dentry的parent为“/”对应的dentry,然后还要调用”/”对应的dentry->d_inode这个inode的inode_operation->lookup(“/”的inode,新建的dentry,flags),如果返回了新的dentry,那么就把dentry结构体指针指向新返回的dentry,否则还是返回刚刚新创建的那个dentry。(一般的文件系统都实现了inode_operation->lookup,我猜他们会在这个函数里面如果/usr存在则把dentry对应的inode给设置好。。如果/usr不存在则返回一个NULL之类的,以一个错误跳出整个路径执行)

到这里,无论是dentry已经存在于内存中找到的,还是新创建的dentry,总之,对应于“usr”结构的dentry在内存中已经存在了。然后调用follow_managed()函数找到“usr”最新的vfsmount(这里有一点点麻烦,后续会专门讲,这里只需要指定如果”/dev/sda” mount 到了/mnt,/dev/sdb 也mount到了/mnt,那么这里返回的是最新的这条/dev/sdb mount到/mnt这个vfsmount)。

然后把这个已经找到的或创建的dentry(已经存在于内存中的dentry已经有了inode和它绑定,新建立的dentry也通过inode_operation->lookup建立起来了inode和dentry的联系(此函数会在操作真正的磁盘介质吧inode读出来))和这个最新的vfsmount存到struct path中

然后把这个含有dentry,vfsmount的path结构体存入nameidata数据结构中,到这里,“usr“对应的dentry,inode,vfsmount都准备好了,且存到了nameidata中了

 

(4)    接着(3)里面,一层一层的往下找,依次会找到usr,bin,最后到了”make”

这里就不调用一层一层往下找的函数了,进入另外一个函数do_last()函数来

处理。在dolast,在dolast里面如果此dentry不存在则创建它,如果有O_CREATE

标识则创建这个文件的inode(这里会调用vfs_create函数,继续调用dentry->inode_operation->create来建立inode,文件系统实现的此函数会操作正在的磁盘介质去创建inode),并且建立inode和dentry的联系,并且建立”make”对应的vfsmount为最新的mount结构,至此,“/usr/bin/make”中最后一个分量“make”的dentry,inode,vfsmount都存到nameidata中去了。

接着还会把2中分配的file结构体的path(包含dentry和vfsmount)的dentry分量设置为nameidata的这个dentry(dentry结构体中已经有inode的指针),vfsmount也设置为nameidata的vfsmount,并且设置file结构体的file->f_mapppin为nameidata中dentry的inode的i_mapping.并且设置file->f_pos指针为0。

至此,make对应的新分配的这个struct file结构体中的dentry,inode,vfsmount都为nameidata中的了,并且struct file映射到内存的address_space也设置为了inode对应的address_space,struct file的当前位置指针设置为了0,“make”分量的这个struct file结构体准备好了。

接着还会把这个struct file结构体加入其inode对应的super_block超级块的s_files链表中,即struct file结构体会加入其自身inode所在超级块的所有文件链表中。

并且如果自身inode的file_operations不为空则还会设置这个struct file的file_operation等于inode的这个file_operations,即公用inode的file的操作方法。如果inode的file_operations没有实现,则设置为空。

设置此文件标识符为FILE_OPENED.

 

4.    建立fd到这个struct file结构体的联系

 

调用fd_install(fd,f)来把1中分配的文件描述符和3中的struct file建立联系。

过程简单描述一下,先获取当前进程的fdtable(简单可理解为进程的关联的所有文件数组)的所有文件数组fdtable=current->files->fdt,(current为当前进程task_struct),设置fdtable->fd[fd]=file,(下标fd即新分配的文件描述符,file即为3中创建的struct file结构体)。

这样,进程和文件描述符,struct file,dentry,inode,vfsmount就全部关联起来了。

 

附图片:完整的内核调用我画的visio学习图,欢迎纠正理解,图片需要放很大才能看清。。
。。汗。。图片有4M多,上传不了。。

参考:kernel 3.6.7源码

时间: 2024-09-11 13:27:15

Linux 中open系统调用实现原理【转】的相关文章

Linux VFS中write系统调用实现原理【转】

转自:http://blog.chinaunix.net/uid-28362602-id-3425881.html 目录 用户空间的write函数在内核里面的服务例程为sys_write Vfs_write函数实现原理   WORD里面的目录复制过来似乎不能直接用..还是放在这里当主线看吧..   用户空间的write函数在内核里面的服务例程为sys_write root@syslab ~]# grep write /usr/include/asm/unistd_64.h #define __N

Linux中brk()系统调用,sbrk(),mmap(),malloc(),calloc()的异同【转】

转自:http://blog.csdn.net/kobbee9/article/details/7397010 brk和sbrk主要的工作是实现虚拟内存到内存的映射.在GNUC中,内存分配是这样的:       每个进程可访问的虚拟内存空间为3G,但在程序编译时,不可能也没必要为程序分配这么大的空间,只分配并不大的数据段空间,程序中动态分配的空间就是从这一块分配的.如果这块空间不够,malloc函数族(realloc,calloc等)就调用sbrk函数将数据段的下界移动,sbrk函数在内核的管理

浅析Linux中的时间编程和实现原理(一) Linux应用层的时间编程

引子 我们都生活在时间中,但却无法去思考它.什么是时间呢?似乎这是一个永远也不能被回答的问题.然而作为一个程序员,在工作中,总有那么几次我必须思考什么是时间.比如,需要知道一段代码运行了多久:要在 log 文件中记录事件发生时的时间戳:再比如需要一个定时器以便能够定期做某些计算机操作.我发现,在计算机世界中,时间在不同场合也往往有不同的含义,让试图思考它的人感到迷茫.但值得庆幸的是,Linux 中的时间终究是可以理解的.因此我打算讨论一下有关时间的话题,尝试着深入理解 Linux 系统中 C 语

Linux中应用程序如何使用系统调用syscall

最近在做Android,其中一个任务是写一个能在Linux命令行运行的测试AP,运行这个AP就能关闭设备电源,即Power Off. 在 Linux内核中已经找到了关闭电源的函数kernel_power_off(),然后也知道了在sys_reboot()函数中调用 kernel_power_off()的,但是linux的应用程序怎么调用sys_reboot()呢? 经过1天的研究,终于搞明白了 这样的函数属于linux的系统调用函数(System call),需要用system call的方式调

Linux调试器的工作原理(一):基础篇

这是调试器工作原理系列文章的第一篇,我不确定这个系列会有多少篇文章,会涉及多少话题,但我仍会从这篇基础开始. 这一篇会讲什么 我将为大家展示 Linux 中调试器的主要构成模块 - ptrace 系统调用.这篇文章所有代码都是基于 32 位 Ubuntu 操作系统.值得注意的是,尽管这些代码是平台相关的,将它们移植到其它平台应该并不困难. 缘由 为了理解我们要做什么,让我们先考虑下调试器为了完成调试都需要什么资源.调试器可以开始一个进程并调试这个进程,又或者将自己同某个已经存在的进程关联起来.调

Linux中线程和进程的区别

Linux中线程和进程的区别 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,它是系统进行资源分配和调度的一个独立单位.例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格.内存空间.磁盘空间.I/O设备等,然后该进程被放入到进程的就绪队列,进程调度程序选中它,为它分配CPU及其他相关资源,该进程就被运行起来. 线程是进程的一个实体,是CPU调度和分配的基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器.一组寄存器和栈),但

在Linux中使用线程

我并不假定你会使用Linux的线程,所以在这里就简单的介绍一下.如果你之前有过多线程方面的编程经验,完全可以忽略本文的内容,因为它非常的初级. 首先说明一下,在Linux编写多线程程序需要包含头文件pthread.h.也就是说你在任何采用多线程设计的程序中都会看到类似这样的代码: #include 当然,进包含一个头文件是不能搞定线程的,还需要连接libpthread.so这个库,因此在程序连接阶段应该有类似这样的指令: gcc program.o -o program -lpthread.h>

Linux中查看进程及杀死进程命令

Linux中想杀死fcitx进程,然后再重启它. root@www.linuxidc.com:/home/zhangbin# ps -e | grep 'fcitx'  3405 ?        00:00:00 fcitx <defunct>  3415 ?        00:00:02 fcitx   不显示标题 root@www.linuxidc.com:/home/zhangbin# ps -h Warning: bad ps syntax, perhaps a bogus '-'

Linux中wait用法

Linux中wait用法: 系统中的僵尸进程都要由wait系统调用来回收. 函数原型: #include<sys/types.h> #include<sys/wait.h> pid_t wait(int *status); 进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回:如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为