30 Apr 2022
上一篇文件描述符简明介绍中有介绍fd是什么,那么linux中是如何管理它的呢?
根据上篇文章,我们可知,fd的实际使用是绑定到每个进程上的。
因为linux上创建进程,其实是通过拷贝的方式来实现的,区别只是在于PID、PPID、某些资源和统计量(例如挂起的信号量),大部分的task_struct(fd表所在的数据结构)中的数据没有变化,相当于是直接继承了,所以fd的限制理论上,是直接继承的父进程的fd的限制。
但是也会有其他的管理工具或者方法可以让子进程改变继承的fd限制。总结来讲有以下几种方式:
除了默认的从父进程继承的形式之外,其他所有改变fd限制的方式底层都是通过相关的几个系统调用(后面细讲)来实现的。
只有root才可以调高hard limit,其他的用户只可以调整soft limit(不可以超过hard limit)
┌────────────┐ ┌────────┐ ┌─────┐ ┌────┐ ┌─────┐ ┌─────┐ ┌─────────┐
│init/systemd│ │services│ │shell│ │sshd│ │crond│ │login│ │container│
└────────┬───┘ └────┬───┘ └───┬─┘ └──┬─┘ └──┬──┘ └───┬─┘ └────┬────┘
│ │ (opti│onal) └──────┴────────┤ │
│ │ └─────────────┐ │ │
│ │ │ PAM │
│ ├─sysvinit scripts──────┤ │ │
│ │ (optional) │ │ │
│ │ │ │ │
│ ├─systemd units─┐ │ │ │
│ │ │ │ │ │
│ ┌──▼───┐ ┌───────▼─┐ ┌───▼──┐ ┌───────▼──────┐ ┌───▼────────┐
│ │ hard │ │ systemd │ │ulimit│ │ limits.conf │ │ container │
└──────►│ code │ │ │ │(cmd) │ │ pam_limit.so │ │ management │
└──┬───┘ └────┬────┘ └───┬──┘ └───────┬──────┘ └──────┬─────┘
│ │ │ │ │
└────────────┴──────────┴───┬────────┴───────────────┘
SYSTEM│CALL
DEFAULT │
┌─────────────────────────┬──────────────────▼──────┐
│ │ │
│ inherit from │ getrlimit prlimit │
│ parent process │ setrlimit rlimit │
│ │ │
├─────────────────────────┴─────────────────────────┤
│ │
│ KERNEL │
│ │
└───────────────────────────────────────────────────┘
和资源限制(包含fd)系统调用相关的数据结构:rlimit(resource limit)
和资源限制(包含fd)相关的系统调用
// 获取rlimit int getrlimit(int resource, struct rlimit *rlim); // 设定rlimit int setrlimit(int resource, const struct rlimit *rlim); // 设定其他进程的rlimit int prlimit(pid_t pid, int resource, const struct rlimit *new_limit, struct rlimit *old_limit);
准备一个简单的测试c程序limit.c,为排除ulimit的影响,修改父进程的rlimit值为和ulimit不同的值,看子进程是否继承
#include <stdio.h> #include <unistd.h> #include <sys/resource.h> #include <errno.h> #include<sys/wait.h> int main() { pid_t c_pid; struct rlimit p_rlim, c_rlim; if (fork()==0) printf("fork done\n"); else c_pid = wait(NULL); if (c_pid != 0) { // change parent pid's rlimit p_rlim.rlim_cur = 512; p_rlim.rlim_max = 512; if(setrlimit(RLIMIT_NOFILE, &p_rlim) == -1) fprintf(stderr, "%s\n", strerror(errno)); if( getrlimit(RLIMIT_NOFILE, &p_rlim) == 0) { printf("Parent limits -> soft limit= %ld \t hard limit= %ld \n", p_rlim.rlim_cur, p_rlim.rlim_max); printf("Parent pid = %d\n", getpid()); } else { fprintf(stderr, "%s\n", strerror(errno)); } } else { if( getrlimit(RLIMIT_NOFILE, &c_rlim) == 0) { printf("Child limits -> soft limit= %ld \t hard limit= %ld \n", c_rlim.rlim_cur, c_rlim.rlim_max); printf("Child pid = %d\n", getpid()); } else { fprintf(stderr, "%s\n", strerror(errno)); } } return 0; }
# 为排除ulimit的影响,我们先设定ulimit的限制为和父进程的rlimit不同的值 ulimit -n 1024 # compile测试程序并运行 gcc limit.c -o limit ./limit fork done Child limits -> soft limit= 1024 hard limit= 1024 Child pid = 246 Parent limits -> soft limit= 512 hard limit= 512 Parent pid = 245
可以看出,limit(子)的fd限制继承自limit(父),然后limit(父)可以通过hard code把fd限制手动修改
注意:程序设定的值不可以超过父进程bash的值,也就是ulimit命令设定的hard limit的值,否则会有如下报错
strace ./limit ...... setrlimit(RLIMIT_NOFILE, {rlim_cur=1025, rlim_max=1025}) = -1 EPERM (Operation not permitted) --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x80cde390} --- +++ killed by SIGSEGV +++ Segmentation fault
详细内容见:ulimit工具简明介绍:2.3
/etc/security/limits.conf中的配置限制,前提是在pam中针对crond配置了pam_limits.so模块的对应配置详细内容见:systemd 系统资源限制