源码学习:文件描述符

在进程描述学习中,扯到了max_fds,接着就联想到了日常运维中常见的ulimit参数、sysctl内核参数,原来以为max_fds与这些个关联性比较强,但经过一早上折腾以后,发现其实还是有一些差距的。但是在学习过程中,却是东拉西扯折腾出了很多其他东西。

现在尝试把这些东西都统一消化一下。

文件描述符File Descriptor

fd 是 File descriptor 的缩写,中文名叫做:文件描述符。文件描述符是一个非负整数,本质上是一个索引值。

fd是索引值!fd是索引值!fd是索引值!

重要的事情说三遍。

什么时候需要fd

当打开一个文件时,内核向进程返回一个文件描述符( open 系统调用得到 ),后续 read、write 这个文件时,则只需要用这个文件描述符来标识该文件,将其作为参数传入 read、write 。

如同task_struct内的struct files_struct *files

cs 复制代码
struct task_struct {
    // ...
    /* Open file information: */
    struct files_struct     *files;
    // ...
    }

此处的files 是一个指针,指向一个为 struct files_struct 的结构体。这个结构体就是用来管理该进程打开的所有文件的管理结构。

struct task_struct 是进程的抽象封装,标识一个进程。当创建一个进程,其实也就是 new 一个 struct task_struct 出来;

上面代码通过进程结构体引出了 struct files_struct 这个结构体。这个结构体管理某进程打开的所有文件的管理结构,核心代码如下:

cs 复制代码
struct files_struct {
....
    struct fdtable __rcu *fdt;
    struct fdtable fdtab;  //动态数组
....
    struct file __rcu * fd_array[NR_OPEN_DEFAULT]; //静态数组
....
};

files_struct 结构体是用来管理所有打开的文件的。通过数组进行管理,所有打开的文件结构都在数组里。struct files_struct内有两个数组,一个静态数组,一个动态数组。

由于大部分进程只会打开少量的文件,所以静态数组就够了,这样就不用另外分配内存。如果超过了静态数组的阈值,那么就动态扩展,使用动态数组。

静态数组

cs 复制代码
#define NR_OPEN_DEFAULT BITS_PER_LONG

这是一个静态数组,随着 files_struct 结构体分配出来的,在 64 位系统上,静态数组大小为 64;

定义代码如下,BITS_PER_LONG在64位系统上为64:

cs 复制代码
#define NR_OPEN_DEFAULT BITS_PER_LONG

动态数组

struct fdtable

这个是数组管理结构,封装用来管理 fd 的结构体,代码如下

cs 复制代码
struct fdtable {
    unsigned int max_fds;
    struct file __rcu **fd;      /* current fd array */
    unsigned long *close_on_exec;
    unsigned long *open_fds;
    unsigned long *full_fds_bits;
    struct rcu_head rcu;
};

fdtable.fd 这个字段是一个二级指针,指向 fdtable.fd 是一个指针字段,指向的内存地址还是存储指针的(元素指针类型为 struct file * )。换句话说,fdtable.fd 指向一个数组,数组元素为指针(指针类型为 struct file *)。其中 max_fds 指明数组边界。

file_struct 本质上是用来管理所有打开的文件的,内部的核心是由一个静态数组和动态数组管理结构实现。而文件描述符fd 就是这个数组的索引,也就是数组的槽位编号已。 通过非负数 fd 就能拿到对应的 struct file 结构体的地址。

日常运维与fd擦边的参数

1. file-max

/proc/sys/fs/file-max

这个文件决定了系统级别所有进程可以打开的文件描述符的数量限制,尝试分配超过file-max值的文件描述符将通过printk报告,查找包含"VFS: file-max limit reached"的信息。

root@linux-kernel:/# sysctl -a | grep -i file-max fs.file-max = 65535

修改方式

echo "fs.file-max = 65535000" >> /etc/sysctl.conf

sysctl -p

2. file-nr

这个是一个状态指示的文件,一共三个值,第一个代表全局已经分配的文件描述符数量,第二个代表自由的文件描述符(待重新分配的),第三个代表总的文件描述符的数量。

Linux 2.6 版本始终将空闲文件句柄数量报告为0 ------ 这不是错误,而是表示已分配的文件句柄数量恰好等于已使用的文件句柄数量。

root@linux-kernel:/# cat /proc/sys/fs/file-nr

1248 0 9223372036854775807

3. nr_open

表示一个进程可以分配的最大文件句柄数量。默认值是1024*1024(1048576),对大多数机器来说应该足够了。实际限制取决于RLIMIT_NOFILE资源限制。

cs 复制代码
unsigned int sysctl_nr_open __read_mostly = 1024*1024;
unsigned int sysctl_nr_open_min = BITS_PER_LONG;

4. RLIMIT_NOFILE

RLIMIT_NOFILE是一个用于限制单个进程可以打开的文件描述符数量的资源限制。它定义了当前进程能够同时打开的最大文件描述符数量。这个限制是针对每个进程的,而不是系统整体的。通过调整这个限制,可以控制每个进程能够同时处理的文件数量,从而对系统的资源利用进行管理和优化。

可以通过ulimit -n 配置(重启后失效) 或者配置/etc/security/limits.conf永久生效。openEuler默认为1024

其中nofile为number of open file, 为打开文件数量,针对单个进程和用户。

cs 复制代码
# /etc/security/limits.conf //将可打开数量设置为65535 
* soft nofile 65535 
* hard nofile 65535

对应limit默认值配置源码如下,用户空间的NR_OPEN配置为1024.

另外可以通过程序接口 setrlimit()、getrlimit() 进行设置

cs 复制代码
int do_prlimit(struct task_struct *tsk, unsigned int resource,
        struct rlimit *new_rlim, struct rlimit *old_rlim)
{
    struct rlimit *rlim;
    int retval = 0;
    ...
    if (new_rlim) {
        if (new_rlim->rlim_cur > new_rlim->rlim_max)
            return -EINVAL;
        if (resource == RLIMIT_NOFILE &&
                new_rlim->rlim_max > sysctl_nr_open)
            return -EPERM;
    }
    ...
}

在/proc/sys/fs下三个参数的加载源码

cs 复制代码
static struct ctl_table fs_table[] = {
   ...
    {
        .procname   = "file-nr",
        .data       = &files_stat,
        .maxlen     = sizeof(files_stat),
        .mode       = 0444,
        .proc_handler   = proc_nr_files,
    },
    {
        .procname   = "file-max",
        .data       = &files_stat.max_files,
        .maxlen     = sizeof(files_stat.max_files),
        .mode       = 0644,
        .proc_handler   = proc_doulongvec_minmax,
        .extra1     = &zero_ul,
        .extra2     = &long_max,
    },
    {
        .procname   = "nr_open",
        .data       = &sysctl_nr_open,
        .maxlen     = sizeof(unsigned int),
        .mode       = 0644,
        .proc_handler   = proc_dointvec_minmax,
        .extra1     = &sysctl_nr_open_min,
        .extra2     = &sysctl_nr_open_max,
    }
    ...

nr_open、file-max与RLIMIT_NOFILE的对比

范围:

  • nr_open:单个进程的文件描述符最大值。
  • file-max:系统范围内所有进程的文件描述符总数。
  • RLIMIT_NOFILE:每个进程的文件描述符软限制和硬限制。

层级:

  • nr_open 和 RLIMIT_NOFILE 作用于进程级别,控制单个进程可以打开的文件数量。
  • file-max 作用于系统级别,控制整个系统可以打开的文件数量。

限制机制:

  • nr_open 提供了一个系统范围内的进程级硬限制。
  • RLIMIT_NOFILE 可以通过用户或管理员动态调整,灵活性较高。
  • file-max 确保系统不会超出总的文件描述符资源,避免资源枯竭。

关联性:

在 Linux 系统中,NR_OPEN 是一个内核中定义的常量,它表示整个系统可以同时打开的文件描述符的最大数量。一旦这个常量在内核中设置为某个值(比如1024),那么系统级别的文件描述符总数就会受到这个限制。

用户进程可以通过 RLIMIT_NOFILE 这个资源限制来控制其自身可以打开的文件描述符数量。这个限制是针对每个进程的,与 NR_OPEN 不同,它影响的是单个进程的能力,而不是整个系统。

如果 NR_OPEN 被设置为 1024,这意味着整个系统在任何时刻都不会超过 1024 个打开的文件描述符。但是,每个单独的进程可以通过 RLIMIT_NOFILE 设置自己的最大文件描述符数量。默认情况下,如果不显式设置,RLIMIT_NOFILE 的值通常会比较大,远超过 NR_OPEN 的设置,允许进程在其自身的限制内操作。

因此,即使 NR_OPEN 设置为 1024,用户进程仍然可以通过设置适当的 RLIMIT_NOFILE 来打开更多的文件描述符,只要它们不超过其自身的限制。这种设置允许操作系统在系统级别保持资源的控制,同时让每个进程有足够的灵活性来管理自己的资源使用。

在设置和检查文件描述符时,内核会结合这几个参数来确保资源使用在合理范围内。例如,当一个进程请求打开新的文件描述符时,内核会检查 RLIMIT_NOFILE 和 nr_open,同时在全局范围内检查是否超过 file-max。

通过这些参数的组合,Linux 内核能够灵活而有效地管理文件描述符资源,确保系统稳定和高效运行。

相关推荐
单音GG12 分钟前
推荐一个基于协程的C++(lua)游戏服务器
服务器·c++·游戏·lua
Shepherd061940 分钟前
【Jenkins实战】Windows安装服务启动失败
运维·jenkins
shitian08111 小时前
用轻量云服务器搭建一个开源的商城系统,含小程序和pc端
服务器·小程序·开源
Biomamba生信基地1 小时前
Linux也有百度云喔~
linux·运维·服务器·百度云
米饭是菜qy1 小时前
TCP 三次握手意义及为什么是三次握手
服务器·网络·tcp/ip
yaoxin5211231 小时前
第十九章 TCP 客户端 服务器通信 - 数据包模式
服务器·网络·tcp/ip
new_abc1 小时前
Ubuntu 22.04 ftp搭建
linux·运维·ubuntu
flying robot2 小时前
RPM的使用
linux
鹿鸣天涯2 小时前
‌华为交换机在Spine-Leaf架构中的使用场景
运维·服务器·网络