前言:
承接上文中对进程的内部属性及在操作系统层面的组织方式、系统接口调用、task_struct等方面的介绍,今天我们将从进程的相关属性出发,继续探讨进程的创建过程。
进程的创建承接上文。
上文提到,进程的创建是通过调用系统接口fork实现的,因此有时对子进程理解不深的人可能会编写如下代码:
while(1){ printf("aaa\n"); fork(); printf("...\n"); }
这段代码本身并无错误,但运行时会发现打印的结果并非单一的父子进程输出。因为子进程会执行父进程的后续代码,导致子进程也会进入死循环,进而创建更多的子进程,形成指数级增长。因此第二个printf语句的输出会呈1、2、4、8的模式递增,这容易让人感到困惑。
接下来,我们来看另一个场景,编写了如下代码:
printf("I am a child process, my pid is %d\n", getpid()); printf("I am a parent process, my pid is %d\n", getppid());
运行这段代码时,你会发现pid会变化,这是正常的现象。但为什么ppid保持不变呢?这也是正常的吗?
当我们使用指令ps -xaj打开任务管理器界面时,会发现2878进程是bash(注意,2878不一定是bash,具体取决于./test,每个进程的pid都会变化)。
那么,什么是bash呢?
当我们运行:
bash会提示我们./是个目录,无法运行。
实际上,bash就是命令行解释器。我们刚才运行的./test就像是公司的实习生,而bash则是公司的boss,管理着所有的实习生(子进程)。
为什么我们要创建子进程呢?
根据前面的代码,我们知道子进程会继承父进程的后续所有代码。但我们创建子进程的目的是为了执行这些代码吗?父进程也可以执行啊,为什么要创建子进程呢?这时候,前面埋下的伏笔就起作用了。这里有一个容易让人迷惑的问题:一个函数的返回值可以返回几次?
大多数初学者可能都会回答一次,但fork的奇异之处就在这里:
当我们man fork后,在手册中查询返回值的描述时,翻译过来就是:成功创建子进程,那么子进程的Pid就会返回给父进程,0返回给子进程,失败了则返回-1给父进程。也就是说,成功时会有两个返回值,我们就可以编写如下代码:
pid_t id = fork(); if(id == 0) { printf("hehehe\n"); } else { printf("hahahha\n"); }
最后的打印结果是:
按照传统C语言的逻辑,只会打印一个,但这里却颠覆了我们的代码观念。
奇怪的是,明明变量只有一个id,为什么会有两份呢?
实际上,在fork的时候,fork的实现部分,return id是代码吧?在fork的函数体内,已经创建了子进程,所以会返回两份id,因为父进程也会进入fork函数,因此有了两份id。至于为什么会有“两份”,我们先埋个伏笔。
由此得出结论,子进程的创建是为了执行不同的任务,而不是为了执行与父进程相同的工作。
那么,我们如何创建指定数量的进程呢?
如果我们采用最开始的指数级别创建方式,显然不好控制。我们可以利用fork的返回值来帮助我们创建指定数量的进程。因为创建成功时,会返回0给子进程,我们可以让这个子进程进入一个函数体中不再出来,这样就不会导致子进程创建子进程的情况:
void Run() { while(1) { printf("My pid is %d, ppid is %d\n", getpid(), getppid()); sleep(1); } } int main() { for(int i = 0; i < 5; i++) { pid_t id = fork(); if(id == 0) { Run(); break; } } Run(); return 0; }
来看一下结果。
不出所料,我们创建了6个进程,主函数是第一个进程,后面通过for循环创建了5个子进程,且5个子进程之间没有关联。通过它们的pid我们发现5个子进程的ppid都是父进程的12607。我们大胆预测父进程的ppid 4335是bash进程,这一点我们就不验证了。
现在我们已经基本熟悉了进程的创建,那么如何查看进程的大部分信息呢?
首先引入一个问题,指令是进程吧?那么我们运行指令后,指令的工作路径在哪里呢?这个问题可能有点让人摸不着头脑,我们用文件举例,如果我们编写如下代码:
int main(){ chdir("/home/whb/111"); FILE *fp = fopen("log.txt", "w"); (void)fp; // 忽略警告 fclose(fp); while(1) { printf("I am a process, pid: %d\n", getpid()); sleep(1); } return 0; }
那么log.txt文件默认会在哪里创建呢?应该是当前代码的工作路径吧?
在VS中就像这样,同理,Linux中的进程工作时也应该有自己的工作路径,我们从哪里查看工作路径呢?
在根目录的proc中,proc是process的缩写。
此时,我们就可以看到proc中的信息了。如果我们要查看某个具体进程的信息,只需要:
同时运行其他两条指令,我们在27847父进程中可以注意以下事项:
本文首先关注两个属性,一个是cwd,一个是exe,cwd是current working directory,当前工作目录的意思,exe则记录对应进程中可执行文件的路径。
当你对某个进程进行cd操作时,发现可以cd进去,说明进程本质上就是一个个的目录,目录中记录了进程的所有信息。此时,对Linux中“一切皆文件”的理解又加深了一层!
感谢阅读!
以上就是初识Linux · 进程(3)的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号