在计算机的浩瀚宇宙中,进程无疑是最为璀璨的星辰之一。它是程序在计算机中的运行状态,是从程序加载到执行这一过程的具象化体现。
进程不仅是计算机系统中最基本的调度单元,它的管理与调度涉及到操作系统的核心机制。理解进程的定义、生命周期、以及如何与之交互,不仅能够帮助我们更好地掌控计算机资源,还能让我们对操作系统的运作有更深刻的洞察。就如同宇宙中的每颗星辰都有自己的轨迹与生命,进程也有其独特的运行方式和生命周期。那么,在这一系列的交响乐中,进程如何诞生、成长、消亡呢?又是如何在操作系统的指挥下,演奏出那首和谐的生命之歌?
进程是计算机中一个正在执行的程序实例。每当一个程序被加载到计算机内存中并开始运行时,它便成为了一个进程。进程不仅包含程序代码,还包括执行状态、程序计数器、寄存器内容以及内存等相关信息。可以说,进程是程序的一个动态表现,代表了程序在特定时刻的运行状态。
**与程序的静态性不同,进程是动态的,它会经历从创建、运行、暂停到销毁的整个生命周期。**每一个进程都是操作系统资源管理的基本单元,操作系统通过对进程的管理来实现任务的调度与分配。
尽管进程和线程都与程序的执行密切相关,但它们是两个不同的概念。一个进程可以包含多个线程,而线程是操作系统进行调度的最小单元。
进程和线程是操作系统调度的两种不同的级别,操作系统通过调度进程来进行多任务处理,而通过调度线程来提高执行效率。
一个操作系统可能可以同时进行多个进程,比如我们可以让多个程序同时进行,我们的电脑可以同时跑多个软件,为了避免进程执行起来互相干扰,所以我们要对进程进行管理
一般进行管理的过程就是:先描述+再组织
所以我们要进行进程描述:任何一个进程,在被加载到内存,形成真正的进程时,操作系统都要先创建描述进程的结构体对象——PCB,也叫做进程控制块
PCB可以理解为进程属性的集合,操作系统是C语言写的,所以PCB一定是一个struct结构体,PCB中会包含进程如下的重要信息:
此外为了方便管理进程,处理进程与进程之间的关系,进程在内存中是以队列的形式存在的,具体点来讲就是链表(双链表),进程在内存中的存在形式可以抽象为下图:

由于PCB中包含着进程的所有信息,所以对进程管理的本质其实就是对进程的PCB做管理,进程在操作系统又通过队列进行链接,所以对进程的管理,其实就是对链表的增删改查
这里的PCB是针对所有操作系统而言的,在我们的Linux中我们往往习惯称呼这个概念为task struct
在上面我们讲到进程的许多属性,包括进程编号、进程状态等等许多内容
首先我们可以通过查看/proc/文件,来查看我们目前正在执行的全部进程:
ls /proc/
这些数字就是进程的PID,每个进程都会有一个对应的PID,PID就是我们上面所说的进程ID,也叫做进程标识符,我们可以通过这些进程标识符来查看每个进程具体的信息,比如查看1号进程
ls /proc/1
除了上面的这个方法外,我们还可以通过下面这个指令,不仅可以看到所有的进程,还可以看到它们的进程的属性信息:
ps axj
我们节选一部分: 执行结果的第一行就是我们的进程属性信息的列名,下面就是每个进程对应的属性信息,我们可以只打印出一行来看一下进程属性的内容(需要借助之前的知识:管道 | 和打印行数head)
ps axj | head -1
对于这些属性信息中,我们先记住前两个就行了,PPID指的是父进程标识符,PID知道是当前进程标识符
目前我们自己创建的可执行文件有test
对于这些属性信息中,我们先记住前两个就行了,PPID指的是父进程标识符,PID知道是当前进程标识符 目前我们自己创建的可执行文件有code.c

我们可以查看下我们自己创建的这个进程的相关信息(注意只有当我们的程序在跑着的时候它才叫进程,所以我们可以将我们的程序写成一个死循环,然后让它执行起来)
ps axj | head -1 && ps axj | grep test
观察这个执行结果,我们可以发现有两个相关进程,会出现第二条的原因就是执行查找test进程的命令本身也会成为一个进程,而这个进程中含test,所以会把自身也带上
如果不想要,可以在后面加上 | grep -v grep,这个-v选项我们在前面讲指令的时候是讲过的,是反向匹配的意思
ps axj | head -1 && ps axj | grep test | grep -v grep除了上面获取进程标识符的方法外,我们还可以通过系统调用的方式来获取表示符,系统接口为getpid和getppid,我们可以通过man手册来查看这个接口
man 2 getpid
具体实例代码如下:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("pid: %d\n", getpid());
printf("ppid: %d\n", getppid());
return 0;
}多次执行这个程序,我们会发现pid一直在变化,而ppid一直不变,也就是说子进程编号一直在变化,而父进程一直没变,为什么会出现这个现象呢?
这是因为,我们在打开Linux时,会首先创建一个bash进程,形成对话框,这个bash进程也是其它所有进程的父进程,所以一般代码重新运行时,它的子进程编号会变,而父进程编号不会变
我们可以创建一个监视窗口方便观察(了解):
while :; do ps axj | head -1 ; ps axj | grep test | grep -v grep;
echo "----------------------------"; sleep 1 ; done我们可以通过fork手动创建进程,我们可以通过man手册查看一下fork
man fork
我们先来看下面的一个小程序:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
printf("before test");
fork();
printf("after test");
return 0;
}运行结果:

我们注意到在fork()函数之后的第二行打印语句执行了两次,说明在fork()之后一个进程变成了两个进程
此外,fork函数还有一个重要知识就是它是有两个整形返回值的,这点与我们之前所学的C语言中的函数差别很大,因为我们之前所学的函数都是只有一个返回值。
fork的两个整形返回值中,大于0代表父进程,等于0是子进程
我们下面来看这样一个程序来验证一下:
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<unistd.h>
4
5 int main()
6 {
7 pid_t id=fork();
8 if(id>0)
9 {
10 //父进程
11 printf("I am parent process, pid:%d, ppid:%d\n",getpid(),getppid());
12 }
13 else if(id==0)
14 {
15 //子进程
16 printf("I am child process, pid:%d, ppid:%d\n",getpid(),getppid());
17 }
18 printf("hello linux\n");
19 return 0;
20 }执行结果如下:

我们可以看到子进程的ppid就是父进程的pid,所以也印证了它们的父子关系,而且最后一个打印代码父子进程都执行了
相信不少同学对上面的问题已经有了很大的疑惑了,比如fork为什么要给子进程返回0,给父进程返回子进程pid呢?
其实这就是为了区分父子进程,让不同的执行流执行不同的代码
Linux进程,作为一个个自我独立又相互依赖的生命体,它们承载着操作系统的使命,在无数的指令中流转、变换、消失。这些进程不仅仅是计算机科学中的抽象概念,它们有着属于自己的生死循环,有着情感般的律动,活跃在操作系统的每一个角落。
进程的诞生,继承与消逝,宛如一场浩瀚的交响乐,不同的进程在这场音乐的演奏中各司其职,恰如其分地展示着属于 Linux 系统的那份优雅与力量。
本篇关于进程的介绍就暂告段落啦,希望能对大家的学习产生帮助,欢迎各位佬前来支持斧正!!!