实验任务
请利用 C/C++ 语言,编写程序通过实验确定电脑的操作系统可并发运行的进程的最大数目是多少?
实验思路
首先很容易想到的一个思路就是编写一个C代码不断创建子进程,直到资源耗尽导致创建失败,则其创建的进程数可以作为系统并发运行的最大进程数的参考。这里要注意的是子进程创建后应使其一直处于休眠状态,否则会它会抢占CPU资源,导致父进程fork()速度越来越慢。
程序源代码如下:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int i = 1;
int pid;
while (1)
{
pid = fork();
if (pid == 0)
break;
else if (pid == -1)
{
perror("error reason:");
break;
}
printf("the %d process created!\n", i++);
}
while (1)
{
sleep(1);
}
return 0;
}
这里的
max_process.c
程序的主要思路是使用一个
while
循环来不断创建新的进程,直到创建失败,并记录当前建立的进程数量。
实验结果如下:
当然,我们很容易想到这种方法的缺陷所在,即我们并没有考虑系统中其他正在运行的进程,这使得我们计算出来的结果会比实际值偏小,并不够精确,因此改进方向也很明确,就是实时监控系统资源,查看系统总进程数量,很幸运的是,Linux为我们提供了这样的命令:
top
通过
top -d 1
命令监控系统资源
Linux进程含义开头的部分显示了系统任务、CPU占用、内存占用等信息,相关信息的含义如下:
系统任务(Task)信息 :total ,总进程数; running,正在运行的进程数;sleeping,休眠的进程数;stopped,中止的进程数;zombie,僵死无响应的进程数。
CPU占用信息:us,用户占用;sy,内核占用;ni,优先级调度占用;id,空闲CPU;wa,I/O等待占用;hi,硬件终端占用;si,软件中断占用;st,虚拟化占用。要了解空闲的CPU百分比,主要看 %id 部分。
内存占用(MEM)信息:total,总内存空间;free,空闲内存;used,已用内存;buff / cache,物理内存和交换内存的缓冲区总和。
交换空间(swap) 占用:total,总交换空间;free,空闲交换空间;used,已用交换空间;avail Mem,可用物理空间。
进程各列信息的含义为:
列名 |
含义 |
---|---|
PID | 进程id |
USER | 进程所有者的用户名 |
PR | 优先级 |
NI | nice值。负值表示高优先级,正值表示低优先级 |
VIRT | 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES |
RES | 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA |
SHR | 共享内存大小,单位kb |
S | 进程状态。D=不可中断的睡眠状态 R=运行 S=睡眠 T=跟踪/停止 Z=僵尸进程 |
%CPU | 上次更新到现在的CPU时间占用百分比 |
%MEM | 进程使用的物理内存百分比 |
TIME+ | 进程使用的CPU时间总计,单位1/100秒 |
COMMAND | 命令名/命令行 |
由此可以得知,此时系统总进程数量为301个
接着使用命令
./max_process
运行程序:
可以看到程序最多同时创建了9798个进程,再次观察系统资源,可以看到此时系统总进程数为10100个,因此我们有理由推断出系统最多可并发运行的进程数大约为10100个(301+9798+1)。
那么理论值到底该如何计算呢?
通过
ulimit -a
命令可以查看系统的所有限制
可以看到此系统的
max user processes
理论上最大为15258
发现的问题
回顾以下到现在为止,我们做了什么。为了将系统其他进程算进来,我们使用了
top -d 1
命令实时监控系统进程,显示的进程数应该能够反映出当前系统的总进程数量而非只是
max_pid
所创建的进程数量,因此也确实与真实的进程数更近了一步,但是似乎和
ulimit -u
得出来的数字15258仍然有着较大差别,这是为什么?
经查阅资料,我们发现
ulimit -u
这条命令实际上是查询系统限制下某用户最多可以运行多少进程,并且其默认值为系统最大线程数的一半,使用命令
cat /proc/sys/kernel/threads-max
,如下图所示,不难看出最大线程数为30517,刚好是进程数的两倍。
因此
max user processes
这个参数在系统资源不足以创建这个数量的进程时便没有参考价值了,当然,最大进程数的大小还会受到全局的
pid_max
限制,不过这个值一般都很大,可以不予考虑,具体数值如下:
另外,
max user processes
也是可以修改的,我们只需要修改
/etc/security/limits.conf
文件中
nproc
值的大小,便可以对所有用户改变
max user processes
的数值。
具体操作如下:
在
/etc/security/limits.conf
文件最下方增加两行代码
* soft nproc 7000
* hard nproc 7000
重启后再次查看相关配置,发现
max user processes
已经变成了7000
再次运行代码,查看系统资源,发现总进程量减少到了7000左右:
至于为什么会比7000要大,个人的想法是max user process应该只是规定了用户可创建的最大进程数量,有可能还有一些系统自身的进程,并没有被考虑进来。
到这也许应该结束了,因为现在基本上可以得知此时10100这个数字基本上就只是受限于硬件虚拟内存不足,而非软件上的一些限制,但是我还是想要知道到底为什么会是10100,它的理论值到底是怎么得出来的。在经过多次尝试无果之后,我最后下载了
ubuntu20.04
的源代码,尝试阅读
fork()
相关的内容:
可以看到此处对线程数做了一定的限制,整理一下就是:
max_threads = (nr_pages * PAGE_SIZE )/ (8 * THREAD_SIZE);
其中nr_pages是机器的物理页面个数, THREAD_SIZE=8 K, 也就是说:
max user process = max_threads / 2 = (nr_pages * PAGE_SIZE) / ( 2 * 8 *THREAD_SIZE ) = free_memory/128 K;
即当前可创建的最大进程数为:空闲内存容量/128 k
另外也可以清楚地看到默认NPROC与max_threads之间的关系:
那让我们最后来一遍:
首先使用
grep MemFree /proc/meminfo
命令查看当前空闲内存容量,并计算出理论最大进程数:
运行
./max_pid
,并使用
top -d 1
命令查看总进程数量
我们发现这时的理论值与实际值相差就很小了,这里也有内存空闲值在不断变化的缘故,而且我想系统也应该会留下一小部分系统资源,以避免系统卡死。