• Linux 系统之 inode与文件描述符

    背景

    我想很多人和我一样,虽然知道文件描述符和inode,但并不知道具体是怎么回事。作为一个系统运维,我觉得了解这些还是必要的。

    什么是inode ?

    理解inode,要从文件储存说起。文件储存在硬盘上,硬盘的最小存储单位叫做”扇区”(Sector)。每个扇区储存512字节(相当于0.5KB)。操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个”块”(block)。这种由多个扇区组成的”块”,是文件存取的最小单位。”块”的大小,最常见的是4KB,即连续八个 sector组成一个 block。

    文件数据都储存在”块”中,那么很显然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为”索引节点”。每一个文件都有对应的inode,里面包含了与该文件有关的一些信息。你可能会问,那inode和block的信息存在哪呢?  superblock里面包含了整个文件系统的inode和block信息。

    由于每个 inode 与 block 都有编号,而每个文件都会占用一个 inode ,inode 内则有文件数据放置的 block 号码。 因此,我们可以知道的是,如果能够找到文件的 inode 的话,那么自然就会知道这个文件所放置数据的 block 号码, 当然也就能够读出该文件的实际数据了。这是个比较有效率的作法,因为如此一来我们的磁盘就能够在短时间内读取出全部的数据, 读写的效能比较好啰。

    我们将 inode 与 block 区块用图解来说明一下,如下图所示,文件系统先格式化出 inode 与 block 的区块,假设某一个文件的属性与权限数据是放置到 inode 4 号(下图较小方格内),而这个 inode 记录了文件数据的实际放置点为 2, 7, 13, 15 这四个 block 号码,此时我们的操作系统就能够据此来排列磁盘的阅读顺序,可以一口气将四个 block 内容读出来! 那么数据的读取就如同下图中的箭头所指定的模样了。

    yy

    总结来说,Linux的文件系统中有三个特别重要的概念:

    inode 具体里面包括哪些?
    • 文件的字节数
    • 文件拥有者的User ID
    • 文件的Group ID
    • 文件的读、写、执行权限
    • 文件的时间戳,共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间。
    • 链接数,即有多少文件名指向这个inode
    • 文件数据block的位置

    我们可以使用stat命令查看inode节点的内容:

    %e5%b1%8f%e5%b9%95%e5%bf%ab%e7%85%a7-2016-11-03-%e4%b8%8b%e5%8d%885-04-46

    那么我们应该怎么查看superblock的内容呢?  我们可以通过dumpe2fs命令来查看。

    inode也会消耗硬盘空间,所以硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是数据区,存放文件数据;另一个是inode区(inode table),存放inode所包含的信息。

    每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode。假定在一块1GB的硬盘中,每个inode节点的大小为128字节,每1KB就设置一个inode,那么inode table的大小就会达到128MB,占整块硬盘的12.8%。

    查看每个硬盘分区的inode总数和已经使用的数量,可以使用df   -i  命令。

    inode 与 文件描述符

    这是两个完全不同的概念。每个进程在Linux内核中都有一个task_struct结构体来维护进程相关的信息,task_struct中有一个指针(struct files_struct *files; )指向files_struct结构体,称为文件描述符表,其中每个表项包含一个指向已打开的文件的指针。

    也就是说,我要想访问文件,需要从这张文件描述符表里取得这个指向打开文件的指针才可以。但是用户程序不能直接访问内核中的文件描述符表,而只能使用文件描述符表的索引 (即0、1、2、3这些数字),这些索引就称为文件描述符(File Descriptor),用int 型变量保存。文件描述符表中的指针指向file 结构体,一个已打开的文件在内核中用file 结构体表示,在file 结构体中维护File Status Flag(file 结构体的成员f_flags)和当前读写位置(file 结构体 的成员f_pos )等。

    %e5%b1%8f%e5%b9%95%e5%bf%ab%e7%85%a7-2016-11-04-%e4%b8%8a%e5%8d%8811-21-02

     

    那么我怎么操作文件呢? 每个file 结构体都指向一个file_operations 结构体,这个结构体的成员都是函数指针,指向实现 各种文件操作的内核函数。比如在用户程序中read 一个文件描述符,read 通过系统调用进入内核,然后找到这个文件描述符所指向的file 结构体,找到file 结构体所指向的file_operations 结构体,调用它的read 成员所指向的内核函数(如内核代码中实现函数可能为sys_read())以完成用户请求。在用户程序中调 用lseek 、read 、write 、ioctl 、open 等函数,最终都由内核调用file_operations 的各成员所指向的内核函数完成用户请求。file_operations 结构体中的release成员用于完成用户程序的close 请求,之所以叫release而不叫close 是因为它不一定真的关闭文件,而是减少引用计数,只有引用计 数减到0才关闭文件。对于同一个文件系统上打开的常规文件来说,read 、write 等文件操作的步骤 和方法应该是一样的,调用的函数应该是相同的,所以图中的三个打开文件的file 结构体指向同一 个file_operations 结构体。如果打开一个字符设备文件,那么它的read,write 操作肯定和常规文 件不一样,不是读写磁盘的数据块而是读写硬件设备,所以file 结构体应该指向不同 的file_operations 结构体,其中的各种文件操作函数由该设备的驱动程序实现。

    到此为止,我们终于清楚的知道文件描述符在系统中的存在了。那么inode呢? inode在系统中是怎样的形式存在的呢?他和文件描述符之间又有什么关系呢? 为了方便理解,我们还是从task_struct结构体入手,看看这之间的关系结构图

    yy

    在上图中,进程1和进程2都打开同一文件,但是对应不同的file 结构体,因此可以有不同的File Status Flag和读写位置。file 结构体中比较重要的成员还有f_count,表示引用计数(Reference Count),如dup 、fork 等系统调用会导致多个文件描述符指向同一 个file 结构体,例如有fd1 和fd2 都引用同一个file 结构体,那么它的引用计数就是2,,当close(fd1) 时并不会释放file 结构体,而只是把引用计数减到1,如果再close(fd2) ,引用计数 就会减到0同时释放file 结构体,这才真的关闭了文件。

    每个file 结构体都有一个指向dentry结构体的指针,“dentry”是directory entry(目录项)的缩写。 我们传给open 、stat 等函数的参数的是一个路径,如/home/akaedu/a ,需要根据路径找到文件 的inode。为了减少读盘次数,内核缓存了目录的树状结构,称为dentry cache,其中每个节点是一 个dentry结构体,只要沿着路径各部分的dentry搜索即可,从根目录/找到home 目录,然后找 到akaedu目录,然后找到文件a。dentry cache只保存最近访问过的目录项,如果要找的目录项 在cache中没有,就要从磁盘读到内存中。

    每个dentry结构体都有一个指针指向inode 结构体。inode 结构体保存着从磁盘inode读上来的信 息。在上图的例子中,有两个dentry,分别表示/home/akaedu/a 和/home/akaedu/b ,它们都指向同 一个inode,说明这两个文件互为硬链接。inode 结构体中保存着从磁盘分区的inode读上来信息,,例如所有者、文件大小、文件类型和权限位等。每个inode 结构体都有一个指向inode_operations结 构体的指针,后者也是一组函数指针指向一些完成文件目录操作的内核函数。
    和file_operations 不同,inode_operations所指向的不是针对某一个文件进行操作的函数,而是影响文件和目录布局的函数,例如添加删除文件和目录、跟踪符号链接等等,属于同一文件系统的 各inode 结构体可以指向同一个inode_operations结构体。 inode 结构体有一个指向super_block结构体的指针。super_block结构体保存着从磁盘分区的超级块 读上来的信息,例如文件系统类型、块大小等。super_block结构体的s_root成员是一个指 向dentry的指针,表示这个文件系统的根目录被mount 到哪里,在上图的例子中这个分区 被mount 到/home 目录下。

    file 、dentry、inode 、super_block这几个结构体组成了VFS的核心概念。

    什么是VFS?

    yy

     

    标准输入、输出、错误文件描述符

        在UNIX系统中,用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端 (Controlling Terminal),控制终端是保存在PCB(进程控制块)中的信息,而我们知 道fork 会复制PCB(进程控制块)中的信息,因此由Shell进程启动的其它进程的控制终端也是这个终端。
    默认情况 下(没有重定向),每个进程的标准输入(stdin)、标准输出(stdout)和标准错误输出(stderr)都指向控制终端,因为在程序启动时(在main 函数还 没开始执行之前)会自动把控制终端打开三次,分别赋给三个FILE *指 针stdin 、stdout和stderr,这三个文件指针是libc 中定义的全局变量,这三个文件的描述符分别是0、1、2,保存在相应的FILE 结构体中。进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写也就是输出到显示器上
    头文件unistd.h 中有如下的宏定义来表示这三个文件描述符:
    #define STDIN_FILENO 0
    #define STDOUT_FILENO 1
    #define STDERR_FILENO 2

     

    Linux系统文件描述符的调整

    一. 查看最大的打开文件数

    1. 查看系统最大打开文件数

    cat /proc/sys/fs/file-max

    2. 查看当前用户打开最大文件数

    ulimit -Hn //查看硬限制

    ulimit -Sn //查看软限制

    3. 查看当前系统已经打开的文件描述符数

     $ cat /proc/sys/fs/file-nr

    5664        0        186405

    其中第一个数表示当前系统已分配使用的打开文件描述符数,第二个数为分配后已释放的(目前已不再使用),第三个数等于file-max。

    二. 设置最大打开文件数

    1、系统级的设置

    echo “fs.file-max = 100000”  >>  /etc/sysctl.conf

    sysctl  -p

    2、用户级设

    vi /etc/security/limits.conf

    设置如下:

    1. httpd soft nofile 4096
    2. httpd hard nofile 10240

    httpd是用户,可以使用通配符*表示所有用户。
    要使 limits.conf 文件配置生效,必须要确保 pam_limits.so 文件被加入到启动文件中。
    查看 /etc/pam.d/login 文件中有:

    session required /lib/security/pam_limits.so

    使用如下命令立即生效:

    # su – httpd

    $ ulimit -Hn 10240

    $ ulimit -Sn 4096

    注意:我们在控制用户的limits相关限制的时候往往会以如下方式去做:

    如上,我们以为通过如上方式(标红)限制了每个用户可以打开的最大进程数为65535,但是却发现在linux 6.5 下是不起作用的,当我登录其他用户时依然还是系统默认的1024:

    也就是说通过在limit.conf里面限制用户可以打开的进程数是不起作用的,那么该怎么才能调整这一值呢 ?方法如下:

    如上修改上面文件中标红的值即可。

     

    参考

    http://www.ruanyifeng.com/blog/2011/12/inode.html

    http://blog.csdn.net/jnu_simba/article/details/8806654