前两天微博上的@王关胜同学问了个问题:
#ulimit问题# 关于nproc设置:centos6,内核版本是2.6.32. 默认情况下,ulimit -u的值为1024,是/etc/security/limits.d/90-nproc.conf的值限制;注释掉这个限制后,值为95044;手工设置90-nproc.conf文件,值为新设置的值。想请 问这个95044是怎么来的?
这个问题挺有意思的,这里面有二个信息点:
1. 为什么limit配置文件是 /etc/security/limits.d/90-nproc.conf 而不是其他?
2. 为什么是nproc的值95044,而不是其他。
我们来简单的做下实验:
1
2
3
4
5
6
7
8
9
<code>$</code><code>cat</code>
<code>/etc/security/limits</code><code>.d</code><code>/90-nproc</code><code>.conf </code>
<code>* soft nproc 8933</code>
<code>$</code><code>ulimit</code>
<code>-u</code>
<code>8933</code>
<code>/etc/security/limits</code><code>.d</code><code>/90-nproc</code><code>.conf </code><code>#注释掉</code>
<code>#* soft nproc 8933</code>
<code>385962</code>
我们可以看出就是说当注释掉限制的话,不同的机器值是不同的。
我们先来回答第一个问题:为什么limit配置文件是 /etc/security/limits.d/90-nproc.conf 而不是其他
我们一步步来看这个问题,首先看下 谁在使用 90-nproc.conf 这个文件:
<code>t.stp</code>
<code>probe syscall.</code><code>open</code><code>.</code><code>return</code>
<code>{</code>
<code> </code><code>filename = user_string($filename)</code>
<code> </code><code>if</code>
<code>(!isinstr(filename,</code><code>"90-nproc.conf"</code><code>)) next;</code>
<code> </code><code>printf</code><code>(</code><code>"%s %d\n"</code><code>, execname(), pid());</code>
<code>}</code>
<code>$</code><code>sudo</code>
<code>stap t.stp</code>
<code>sshd 24844</code>
运行脚本后,开个ssh终端上去,就马上知道sshd在使用这个文件, 同时也验证了配置是即刻生效的。
我们都知道linux下这个limit限制是由pam_limits来执行的。
10
11
12
<code>$</code><code>grep</code>
<code>-rin pam_limit</code><code>/etc/pam</code><code>.d</code>
<code>/etc/pam</code><code>.d</code><code>/sudo-i</code><code>:6:session required pam_limits.so</code>
<code>/etc/pam</code><code>.d</code><code>/smartcard-auth-ac</code><code>:16:session required pam_limits.so</code>
<code>/etc/pam</code><code>.d</code><code>/smartcard-auth</code><code>:16:session required pam_limits.so</code>
<code>/etc/pam</code><code>.d</code><code>/system-auth-ac</code><code>:20:session required pam_limits.so</code>
<code>/etc/pam</code><code>.d</code><code>/fingerprint-auth</code><code>:16:session required pam_limits.so</code>
<code>/etc/pam</code><code>.d</code><code>/sudo</code><code>:6:session required pam_limits.so</code>
<code>/etc/pam</code><code>.d</code><code>/runuser</code><code>:4:session required pam_limits.so</code>
<code>/etc/pam</code><code>.d</code><code>/password-auth-ac</code><code>:19:session required pam_limits.so</code>
<code>/etc/pam</code><code>.d</code><code>/password-auth</code><code>:19:session required pam_limits.so</code>
<code>/etc/pam</code><code>.d</code><code>/system-auth</code><code>:20:session required pam_limits.so</code>
<code>/etc/pam</code><code>.d</code><code>/fingerprint-auth-ac</code><code>:16:session required pam_limits.so</code>
瞄几下modules/pam_limits/pam_limits.c就知道 限制如何执行的:
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<code>/* now the session stuff */</code>
<code>PAM_EXTERN</code><code>int</code>
<code>pam_sm_open_session (pam_handle_t *pamh,</code><code>int</code> <code>flags UNUSED,</code>
<code> </code><code>int</code>
<code>argc,</code><code>const</code> <code>char</code> <code>**argv)</code>
<code>[...]</code>
<code> </code><code>retval = init_limits(pamh, pl, ctrl);</code>
<code> </code><code>if</code>
<code>(retval != PAM_SUCCESS) {</code>
<code> </code><code>pam_syslog(pamh, LOG_WARNING,</code><code>"cannot initialize"</code><code>);</code>
<code> </code><code>return</code>
<code>PAM_ABORT;</code>
<code> </code><code>}</code>
<code> </code><code>retval = parse_config_file(pamh, pwd->pw_name, pwd->pw_uid, pwd->pw_gid, ctrl, pl);</code>
<code>(retval == PAM_IGNORE) {</code>
<code> </code><code>D((</code><code>"the configuration file ('%s') has an applicable '<domain> -' entry"</code><code>, CONF_FILE));</code>
<code>PAM_SUCCESS;</code>
<code>(retval != PAM_SUCCESS || pl->conf_file != NULL)</code>
<code> </code><code>/* skip reading limits.d if config file explicitely specified */</code>
<code> </code><code>goto</code>
<code>out;</code>
<code> </code><code>/* Read subsequent *.conf files, if they exist. */</code>
<code> </code><code>/* set the LC_COLLATE so the sorting order doesn't depend </code>
<code> </code><code>on system locale */</code>
<code> </code><code>oldlocale =</code><code>setlocale</code><code>(LC_COLLATE,</code><code>"C"</code><code>);</code>
<code> </code><code>glob_rc = glob(LIMITS_CONF_GLOB, GLOB_ERR, NULL, &globbuf);</code>
<code>(oldlocale != NULL)</code>
<code> </code><code>setlocale</code>
<code>(LC_COLLATE, oldlocale);</code>
<code>(!glob_rc) {</code>
<code> </code><code>/* Parse the *.conf files. */</code>
<code> </code><code>for</code>
<code>(i = 0; globbuf.gl_pathv[i] != NULL; i++) {</code>
<code> </code><code>pl->conf_file = globbuf.gl_pathv[i];</code>
<code> </code><code>retval = parse_config_file(pamh, pwd->pw_name, pwd->pw_uid, pwd->pw_gid, ctrl, pl);</code>
<code> </code><code>if</code>
<code> </code><code>D((</code><code>"the configuration file ('%s') has an applicable '<domain> -' entry"</code><code>, pl->conf_file));</code>
<code> </code><code>globfree(&globbuf);</code>
<code> </code><code>return</code>
<code> </code><code>}</code>
<code> </code><code>if</code>
<code>(retval != PAM_SUCCESS)</code>
<code> </code><code>goto</code>
<code> </code><code>}</code>
<code>out:</code>
分析这段代码可以知道先读/etc/security/limits.conf,如果/etc/security/limits.d/目录下还有配置文件的话,也读进来,一起分析。
这就意味/etc/security/limits.d/里面的文件里面的配置会覆盖/etc/security/limits.conf的配置。
我们看下这行:glob_rc = glob(LIMITS_CONF_GLOB, GLOB_ERR, NULL, &globbuf);
读取/etc/security/limits.d/目录下文件的函数,从名字就可以猜出,是遍历,文件名的数字起到顺序的作用。
到此就解释了文件名90-nproc.conf的作用。
接着看第二个问题: 为什么是nproc的值95044, 而不是其他。
通过阅读process_limit函数只是看到 nproc的最大值限制,没有看到其他的,那我们就很容易联想,如果用户不设置nproc的话,那么这个值应该是由内核自己来决定。
我们看下内核代码 2.6.18:
<code>$</code><code>pwd</code>
<code>/home/chuba/linux-2</code><code>.6.18.x86_64</code><code>/kernel</code>
<code>-rin nproc .</code>
<code>.</code><code>/sys</code><code>.c:896: current->signal->rlim[RLIMIT_NPROC].rlim_cur &&</code>
<code>.</code><code>/fork</code><code>.c:176: init_task.signal->rlim[RLIMIT_NPROC].rlim_cur = max_threads</code><code>/2</code><code>;</code>
<code>.</code><code>/fork</code><code>.c:177: init_task.signal->rlim[RLIMIT_NPROC].rlim_max = max_threads</code><code>/2</code><code>;</code>
<code>.</code><code>/fork</code><code>.c:179: init_task.signal->rlim[RLIMIT_NPROC];</code>
<code>.</code><code>/fork</code><code>.c:1130: p->signal->rlim[RLIMIT_NPROC].rlim_cur) {</code>
<code>.</code><code>/cpuset</code><code>.c:69: int cnt; /* unprocessed events count */</code>
<code>.</code><code>/cpuset</code><code>.c:1140: * Limit the count of unprocessed events to FM_MAXCNT, so as to avoid</code>
<code>.</code><code>/user</code><code>.c:181: * new uid over his NPROC rlimit? We can check this now</code>
一下子就看出来了 默认值是 max_threads/2. 打开fork.c分析下:
//fork_init(num_physpages); //void __init fork_init(unsigned long mempages) /* * The default maximum number of threads is set to a safe * value: the thread structures can take up at most half * of memory. */ max_threads = mempages / (8 * THREAD_SIZE / PAGE_SIZE);
其中mempages是机器的物理页面个数, THREAD_SIZE=8K, 也就是说:
default_nproc = max_threads / 2 = (mempages * PAGE_SIZE) / ( 2 * 8 *THREAD_SIZE ) = total_memory/128K;
我们来验证下:
<code>/proc/meminfo</code> <code>|</code><code>grep</code>
<code>MemTotal</code>
<code>MemTotal: 49421024 kB</code>
<code>$</code><code>echo</code>
<code>"49421024 / 128"</code><code>|</code>
<code>bc</code>
<code>386101</code>
算出来default_nproc =386101 是不是和实际的很接近?
因为物理页面会内存用作一些关键数据,所以实际的比计算出来的要小点。
小结: 源码说话!
祝玩的开心!