
文章目录
- 💐专栏导读
- 💐文章导读
- 🌷进程是什么
- 🌷进程的描述——PCB
- 🌷进程的组织
- 🌷如何查看进程
- 🌷如何通过系统调用查看进程PID
- 🌷通过系统调用创建进程
- 🌺认识fork
- 🌺重点来啦!!!
💐专栏导读
🌸作者简介:花想云 ,在读本科生一枚,致力于 C/C++、Linux 学习。
🌸专栏简介:本文收录于 C语言初阶专栏,本专栏主要内容为本专栏主要内容为Linux的系统性学习,专为小白打造的文章专栏。
🌸相关专栏推荐:C语言初阶系列、C语言进阶系列 、C++系列、数据结构与算法。
💐文章导读
本章我们正式进入进程的学习。本章的主要内容有进程的概念、PCB说明、进程的先描述再组织、如何查看进程、以及学习getpid、getppid、fork等系统调用如何使用~

🌷进程是什么
开门见山一句话——
进程=内核关于进程的相关数据结构+当前代码的内容和数据;
🍁如何理解?
当我们写好一段代码进行编译、链接等一系列过程之后,生成了可执行程序。此时的可执行程序是一个文件,被存放到硬盘中。当我们运行该可执行程序后,该程序的代码和数据会被预先加载到内存中(至于原因上一章我们已经做了很好的铺垫),同时操作系统会对该进程建模,提取该进程有关的状态信息等所有的属性并将这些信息保存到一个叫PCB的结构体中,并将该PCB的指针保存到某种例如链表一样的数据结构当中,以进行对进程的增删查改一系列操作。
简单归纳,“程序被加载到内存中就成为了进程”这就话并不准确,关键是该进程相关的信息有没有被操作系统所管理。就如同,诺大的校园中,如何证明你是学校的学生呢?你说你能从学校的大门进到学校里来,这显然不能说明你就是这个学校的学生,而是当学校的档案系统里记录了你的信息,才能证明你是这个学校的学生。
所以,以前我们任何将程序启动并运行的行为本质上是操作系统将该程序转化为进程从而完成特定的任务。
上文中还有一个很中还要的点需要我们来理解——PCB是什么?有什么作用?
🌷进程的描述——PCB
操作系统如何管理进程?还记得上一章中最重要的六个字吗?——先描述,再组织。PCB就是对进程的一种描述。
- 进程信息被放在一个叫做
进程控制块的数据结构中,可以理解为进程属性的集合; - 课本上称之为
PCB(process control block),Linux操作系统下的PCB是:task_struct。
总结为一句话就是——在Linux中描述进程的结构体叫做task_struct。task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
🍁task_struct包含以下内容:
标示符: 描述本进程的唯一标示符,用来区别其他进程;状态: 任务状态,退出代码,退出信号等;优先级: 相对于其他进程的优先级;程序计数器: 程序中即将被执行的下一条指令的地址;内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针;上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器;I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表;记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等;其他信息;
🌷进程的组织
所有的进程都会以task_struct结构体的形式被描述起来。而这些task_struct结构体都会被链接起来,生成一个task_struct类型的链表。操作系统对进程的控制就会转化为对task_struct的增删查改。
🌷如何查看进程
首先我们写一个简单的程序(命名为myprocess),并运行:
#include<stdio.h>
int main()
{
while(1)
{
printf("我是一个进程\n");
sleep(1);
}
return 0;
}
$ gcc myprocess.c -o myprocess
$ ./myprocess
在分屏的情况下,输入指令查看进程:
ps axj | head -1 && ps axj | grep myprocess | grep -v grep
我们暂且不需要管这条指令的具体指令有什么作用,只需要知道它能显示当前myprocess进程的信息。如图所示:

虽然我们现在可能看不到每个标识符代表什么意思,但是其它先不用管,只需要知道PID是当前进程的ID,就如同我们的学号。
🍁第二种方式
还有一种查看进程的方式:
- 进程的信息可以通过
/proc系统文件夹查看;
让我们再以myprocess为例。
- 进入
/proc目录; ls查看目录下的文件;
🍁myprocess未运行时

我们观察到有很多数字命名的文件,其实猜测一下就能大致猜出这些数字应该就是每个进程对应的PID。
- 运行
myprocess;
🍁myprocess运行时

如图所示,该目录下的确有该进程的相关文件。
🌷如何通过系统调用查看进程PID
除了上述以指令的方式输出PID外,我们还可以用系统调用getpid()获取当前进程的pid,getppid()获取当前进程的父进程的PID。
首先通过man手册查询这两个函数如何使用:
$ man getpid

注意,返回值类型未pid_t其实就是size_t。通过一下代码进行测试:
#include<stdio.h>
#include<sys/types.h>
int main()
{
while(1)
{
printf("我是一个进程,我的PID是:%d,我的父进程是:%d\n",getpid(),getppid());
sleep(1);
}
return 0;
}
运行结果为:

当我们尝试不断地启动程序和结束程序:

我们可以发现一个规律:
- 每次启动时,该进程的
PID都不同,但是PPID好像每次都一样。
那么PID是什么呢?为什么会不变呢?当你在命令行中启动任何一个进程,它的父进程的PID都是它,即这些进程都有一个共同的父进程——bash(命令行解释器)。如图所示:

🌷通过系统调用创建进程
接下来的内容为本章的重点内容——认识fork与如何通过fork创建子进程。
🌺认识fork
首先我们通过man手册认识一下fork;
$ man fork


简单说明就是**fork用来创建子进程,在父进程中,fork的返回值为子进程的PID;在子进程中返回值为0**。
话不多说,我们来进行测试:
#include<stdio.h>
#include<sys/types.h>
int main()
{
printf("我是fork调用之前的内容.....\n");
fork();
printf("我是fork调用之后的内容.....\n");
return 0;
}

很显然,这里fork调用之后的内容打印了两遍。原因是当调用fork之后,子进程被创建,父进程与子进程同时在运行,于是父进程打印了一遍fork调用后的内容,子进程也打印了一遍fork调用后的内容。
如何证明呢?修改代码,在打印内容的同时打印父子进程各自的PID与PPID:
#include<stdio.h>
#include<sys/types.h>
int main()
{
printf("我是fork调用之前的内容.....我的PID是:%d,我的父进程是:%d\n",getpid(),getppid());
fork();
printf("我是fork调用之后的内容.....我的PID是:%d,我的父进程是:%d\n",getpid(),getppid());
return 0;
}

🌺重点来啦!!!
fork的功能很强大,我们一般需要与if配合使用进行分流。还记得上面提到的fork的返回值吗?子进程返回值为0,父进程返回值为子进程PID,可以此作为分流的依据。例如:
#include<stdio.h>
#include<sys/types.h>
int main()
{
pid_t ret = fork();
if(ret==0)
{
// 子进程
while(1)
{
printf("我是子进程,我的PID是:%d,我的父进程是:%d\n",getpid(),getppid());
sleep(1);
}
}
else
{
// 父进程
while(1)
{
printf("我是父进程,我的PID是:%d,我的父进程是:%d\n",getpid(),getppid());
sleep(1);
}
}
return 0;
}

如图所示,此刻父子进程都在运行。那么问题来了——
- 请问为什么此时
if与else竟然能够同时执行?也就是fork为什么会有两个返回值? - 又问为什么此时的
ret竟然会同时存在两个不同的值?
以上的问题确实有些颠覆初学者的三观。在本章节我们只能够试着解决第一个问题,至于第二个问题,我们在后续进程地址空间中会解释明白。
首先,我们得清楚一个基本概念:
进程间是互相独立的。
例如qq与微信同时运行,两个并无关联,互不影响。
其次我们要明白子进程是如何创建的。
我们知道进程=内核数据结构(PCB)+代码和数据。当子进程创建时,操作系统会为子进程创建一个PCB记录子进程的状态信息等。同时子进程会与父进程共同使用一份代码和数据,若有任意一个执行流(只父子进程)修改数据时,操作系统会为该进程将代码和数据拷贝一份,再进行修改,此动作我们称之为写时拷贝,写时拷贝同样为非常重要的知识,但是现在我们只做了解即可。
最后,因为进程具有独立性,同样父子进程也具有独立性,且由于写时拷贝的存在,父进程中调用fork会返回子进程的PID,所以else执行了,这是父进程的行为;子进程中会fork会返回0,所以if执行了,这是子进程的行为。父子进程互不影响。
本章的内容就到这里了。关于fork我们还需要学习很多,本章我们就先简单认识一下即可。

点击下方个人名片,可添加博主的个人QQ,交流会更方便哦~
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓













![[Pandas] 设置DataFrame的index索引起始值为1](https://img-blog.csdnimg.cn/13b38f0022924625865792731c8872d8.png)





