【Linux系统编程】进程 进程描述符/进程的产生/消亡和释放
一,进程学习大致分析
1,进程描述符pid
2,进程的产生 fork()和vfork()
3,进程的消亡和释放资源
4,exec函数族
5,用户权限和组权限
6,观摩课:解释器文件
7,system( )函数
8,进程会记
9,进程时间
10,守护进程
11,系统日志文件
二,进程描述符pid
1,进程描述符的类型
对于进程描述符,这个进程描述符pid的类型是pid_t,从传统定义上来讲,进程描述符的类型是16位整形,转换过来就是3万个进程,但是对于每一个系统里,这个类型是不固定的,是不一样的,所以并不是所有的进程描述符pid的类型都是16位
3万个进程够不够用,现在目前来说,由于虚拟技术的发展,那么3万个进程偶尔是不够用的,但是对于目前来说是够用的
进程描述符是顺次向下使用的,并不是优先使用范围内最小的进程描述符
2,ps指令
对于学习进程,我们需要掌握终端上的ps指令的使用,因为这个是我们用来查询我们写的程序的进程的用的
参数 英文全称 核心功能 a all with tty 显示所有与终端相关的进程(包括其他用户的进程),不包括无终端的守护进程 x without tty 显示无控制终端的进程(如系统服务、守护进程),与 a组合可显示所有进程m threads 以进程为主线,在进程下方显示其所属线程(轻量级进程),清晰区分进程与线程关系 f forest/tree 以树形结构展示进程,通过缩进和特殊符号(如├─、└─)直观呈现父子进程层级关系 1. ps axm
整体作用:显示系统中所有进程及其线程,以进程分组、线程缩进的方式呈现,用于分析线程的
2. ps axf
整体作用:以树形结构显示所有进程的父子关系,清晰展示进程创建的层级链,用于分析进程的
3,ps ax -L
整体作用:执行后会多出两个线程专属列,这是核心:PID:进程 ID(所有线程共享同一个 PID)LWP:线程 ID(每个线程唯一的编号)NLWP:当前进程总共有多少个线程
3,获取进程pid函数
getpid是返回当前进程的pid号,getppid返回的是当前进程的父进程的pid号
这些函数总是会成功,所以这个函数就没有任何的报错信息
三,进程的产生
1,fork函数()
进程的产生是通过这个进程来创建子进程的
对于这个函数的返回值:
成功:成功将返回父进程中返回子进程的pid号,再子进程中返回值为0
失败:在父进程中返回-1这个函数是通过复制当前的进程到创建的进程,所以可以理解为用这个fork创建出来的子进程是与父进程是一样的,父进程与子进程一样
但是还是有以下几点不一样的:
1,未决信号与文件锁不继承
2,资源利用量清0
3,ppid和pid不一样,还有fork的返回值不一样重要知识:init进程为所有进程的祖先进程
2,实例1:试用fork()函数
#include<stdio.h> #include<stdlib.h> #include<unistd.h> int main(){ pid_t pid; printf("[%d]:begin\n", getpid()); pid = fork(); if(pid < 0){ perror("fork()"); exit(1); } if(pid == 0){ printf("[%d]:child is working\n", getpid()); } else{ printf("[%d]:parent is working\n", getpid()); } printf("[%d]:end\n", getpid()); exit(0); }
这个执行的过程是这样的,这个是ubuntu来执行之后产生的结果,不难看到这里是先调用了父进程,然后再调用了子进程,这个是根据不同的操作系统拥有不同的调度策略来进行的,但是这个程序还有一个问题,会被我们很容易忽视掉当我们把这个程序重定向输出到文件里面时
不难看到这个是输出了两次的begin,但是我们上面终端显示的是只输出了一个begin,这是为什么呢?这是因为fork它是直接复制当前进程给进程的,然后输出到文件里面默认是全缓冲模式,然而终端是行缓冲模式,当我们把这个程序的begin的换行符\n给去掉之后,就会产生这个
也会出现两个begin,这是应为终端是行缓冲模式,所以这个程序我们要怎么修改呢?#include<stdio.h> #include<stdlib.h> #include<unistd.h> int main(){ pid_t pid; printf("[%d]:begin\n", getpid()); fflush(NULL); pid = fork(); if(pid < 0){ perror("fork()"); exit(1); } if(pid == 0){ printf("[%d]:child is working\n", getpid()); } else{ printf("[%d]:parent is working\n", getpid()); } printf("[%d]:end\n", getpid()); exit(0); }
这里我们加上强制刷新全部的缓冲区,这样不难看到这是只出现了一个begin的,而不是出现了两个begin的所以我们再使用fork的时候,一定要再前面加上fflush进行刷新,因为在使用fork的时候,这个begin还没有来的及写到文件里面就进行fork了,这样就会导致begin出现两次
3,实例2:把质数找出来
当只有一个进程处理的时候,这个程序的书写实例
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #define LEFT 30000000 #define RIGHT 30000200 int main(){ pid_t pid; printf("[%d]:begin\n", getpid()); fflush(NULL); for(int i = LEFT; i < RIGHT; i++){ int mask = 1; for(int j = 2; j <= i/2; j++){ if(i % j == 0) // 能够整除,不是质数 { mask = 0; break; } } if(mask == 1){ printf("%d is primer\n", i); } } printf("[%d]:end!\n", getpid()); exit(0); }
我们都知道,fork有个特点就是将当前进程复制到它的子进程里面,这样就形成了一个新的进程,那么我们这个外层循环和内层循环都是复制到了它的子进程里面的,那么就是下面这样了
就像这样子,那么就是200的阶层了,那么这样的话,那不就是成为了要消耗很多很多进程,肯定会爆的,这样的话只好重启了,那么我们就知道我们在子进程执行完属于它所需要的任务的时候,要及时地进行释放
这个是消耗地时间,不难发现其实这个时间是存在问题地
我们当前设置地处理器地数量是2个,处理器地内核数量是2个,那么当我们排完这么多地200个进程,为什么是两百个?因为进程pid是按照顺序进行地,所以这里最多时间除以4,也是在0.1s附近,可是这时间居然减少了很多,这个后面会解释这样地现象,因为这个知识点是在后面4,init进程(孤儿进程)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #define MIN 3000000 #define MAX 3000200 // 求MIN到MAX之间的质数 int main() { pid_t pid; printf("[%d]: Begin!\n", getpid()); fflush(NULL); int i,j; int primer_flag; for(i=MIN; i<=MAX; i++) { pid = fork(); if(pid > 0) // 父进程 { continue; } else if(pid == 0) // 子进程执行判断操作 { primer_flag = 1; for(j = 2; j <= i/2; j++) { if(i % j ==0) // 能够整除,不是质数 { primer_flag = 0; break; } } if(primer_flag == 1) { printf("%d is primer\n", i); } sleep(1000); // 子进程最后退出,父进程先退出 exit(0); // 子进程执行结束后,exit退出进程,否则也将执行循环语句 } } printf("[%d]: End!\n", getpid()); // 如果父进程sleep子进程执行完后没有sleep,则子进程先执行完,且由于未被父进程回收称为僵尸进程,僵尸进程将占用进程号 // 如果父进程没有sleep直接执行完,而子进程sleep,则父进程先执行结束退出,子进程由init进程接管,称为s态 // 如果父子进程都执行sleep,则通过ps axf查看进程情况,是shell进程创建父进程,父进程创建子进程 exit(0); }当我们在子进程里面加上sleep的话,那么就一定要让父进程先结束,子进程更慢
这里不难看到,这都是顶格写,顶格写就意味着这个进程的父进程是init进程
因为我的子进程是要比父进程执行要慢的,那么当父进程都死了,那么我的子进程就没有了,这个时候就是当前的子进程就变成了孤儿进程,那么init祖先进程看到了自己的子子孙孙变成了孤儿进程,就把这些孤儿收了到自己旗下了,所以就产生了这样的现象5,僵尸进程
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #define MIN 3000000 #define MAX 3000200 // 求MIN到MAX之间的质数 int main() { pid_t pid; printf("[%d]: Begin!\n", getpid()); fflush(NULL); int i,j; int primer_flag; for(i=MIN; i<=MAX; i++) { pid = fork(); if(pid > 0) // 父进程 { continue; } else if(pid == 0) // 子进程执行判断操作 { primer_flag = 1; for(j = 2; j <= i/2; j++) { if(i % j ==0) // 能够整除,不是质数 { primer_flag = 0; break; } } if(primer_flag == 1) { printf("%d is primer\n", i); } // sleep(1000); // 子进程最后退出,父进程先退出 exit(0); // 子进程执行结束后,exit退出进程,否则也将执行循环语句 } } sleep(1000); printf("[%d]: End!\n", getpid()); // 如果父进程sleep子进程执行完后没有sleep,则子进程先执行完,且由于未被父进程回收称为僵尸进程,僵尸进程将占用进程号 // 如果父进程没有sleep直接执行完,而子进程sleep,则父进程先执行结束退出,子进程由init进程接管,称为s态 // 如果父子进程都执行sleep,则通过ps axf查看进程情况,是shell进程创建父进程,父进程创建子进程 exit(0); }
当我们将sleep放到下面的时候,不难看到这些都成为了僵尸进程,这个僵尸进程是怎么产生的呢?因为当子进程先执行完,没有及时释放这些子进程,这些子进程就会变成僵尸进程有两种情况就是父进程在等所有的子进程全部弄完一键杀除,还有就是等着init来杀除
6,vfork函数
这个函数就是防止一种情况用的
这个是父进程来指向一个逻辑地址,然后这个逻辑地址指向实际的物理地址,这个是fork的示意图,但是为了防止这样浪费内存,就比如我父进程有2G的数据,然后我的子进程就只需要打印一个hello,用不到这个数据,那么就会导致浪费内存,那么就有了vfork
vfork就是将这个指针指向与父进程相同的区域但是后面加入一个写实拷贝技术到fork函数里面
就是当还是指向同一个地方,但是这个区域是只读的,不可以写,当有一个要写的时候,就会在别的地方开辟一个地方提供相同的数据进行书写,不会玷污之前的数据,所以vfork就慢慢没有用了
四,进程的消亡和释放资源
1、
wait()函数核心作用
阻塞等待任意一个子进程终止,回收其资源,并获取子进程的退出状态。是最基础的子进程回收函数。
函数原型
pid_t wait(int *wstatus);参数说明
wstatus:指向int类型的指针,用于存储子进程的退出状态信息。
- 传入
NULL:表示不关心子进程的退出状态,仅回收资源。- 传入有效指针:可通过宏(如
WIFEXITED、WEXITSTATUS)解析出子进程的退出原因和退出码。返回值
- 成功:返回已终止子进程的 PID。
- 失败:返回
-1,并设置errno(常见原因:没有可等待的子进程,或调用被信号中断)。特点
- 会阻塞父进程,直到有子进程终止。
- 只能等待任意子进程,无法指定特定子进程。
- 等价于
waitpid(-1, &wstatus, 0)。2、
waitpid()函数核心作用
比
wait()更灵活的子进程等待函数:可以指定等待的子进程、支持非阻塞模式,还能监听子进程暂停 / 继续的状态。函数原型
pid_t waitpid(pid_t pid, int *wstatus, int options);参数说明
pid:指定要等待的子进程范围
pid > 0:等待 PID 等于pid的子进程。pid = -1:等待任意子进程(与wait()行为一致)。pid = 0:等待与父进程同进程组的任意子进程。pid < -1:等待进程组 ID 等于|pid|的任意子进程。
wstatus:与wait()完全相同,用于存储子进程状态。
options:控制等待行为的位掩码(可组合使用)
WNOHANG:非阻塞模式 —— 若无子进程状态变化,立即返回0。WUNTRACED:同时监听 ** 被暂停(Stopped)** 的子进程。WCONTINUED:同时监听 ** 被继续(Continued)** 的子进程。返回值
- 成功:
- 若设置
WNOHANG且无状态变化:返回0。- 否则:返回状态变化子进程的 PID。
- 失败:返回
-1,并设置errno(如pid无效、调用被信号中断)。特点
- 可精准指定等待的子进程 / 进程组。
- 支持非阻塞,避免父进程无限阻塞。
- 是
wait()的功能超集,实际开发中更常用。3、
waitid()函数核心作用
更精细的等待接口:支持按 PID / 进程组 / 会话等类型筛选,且通过
siginfo_t结构体返回更详细的子进程状态信息(如终止信号、退出码等)。函数原型
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);参数说明
idtype:指定id的类型
P_PID:id表示子进程 PID。P_PGID:id表示子进程组 ID。P_ALL:等待所有子进程(id被忽略)。
id:根据idtype传入对应的 PID / 进程组 ID 等。
infop:指向siginfo_t结构体的指针,用于存储详细状态信息(如si_pid子进程 PID、si_status退出码 / 信号、si_code状态原因)。
options:必须至少指定一个状态类型,可组合:
WEXITED:等待子进程终止。WSTOPPED:等待子进程被暂停。WCONTINUED:等待子进程被继续。WNOHANG:非阻塞模式(同waitpid())。WNOWAIT:保留子进程状态,可再次等待(不真正回收)。返回值
- 成功:
- 若设置
WNOHANG且无状态变化:返回0。- 否则:返回
0(与wait()/waitpid()不同,成功始终返回0)。- 失败:返回
-1,并设置errno。特点
- 提供最详细的子进程状态信息。
- 筛选粒度更细(支持会话、进程组等)。
- 接口更复杂,多用于需要精细控制的场景(如调试、监控)。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #define MIN 3000000 #define MAX 3000200 // 求MIN到MAX之间的质数 int main() { pid_t pid; printf("[%d]: Begin!\n", getpid()); fflush(NULL); int i,j; int primer_flag; for(i=MIN; i<=MAX; i++) { pid = fork(); if(pid > 0) // 父进程 { continue; } else if(pid == 0) // 子进程执行判断操作 { primer_flag = 1; for(j = 2; j <= i/2; j++) { if(i % j ==0) // 能够整除,不是质数 { primer_flag = 0; break; } } if(primer_flag == 1) { printf("%d is primer\n", i); } // sleep(1000); // 子进程最后退出,父进程先退出 exit(0); // 子进程执行结束后,exit退出进程,否则也将执行循环语句 } } // sleep(1000); for(i=MIN; i<=MAX; i++) { wait(NULL); // 只wait一次,能回收所有子进程吗 不行,wait是有一个子进程状态发生变化就返回,如果有n个子进程结束,就要调用n次wait // 如果wait时子进程还未结束,则一直阻塞等子进程结束 } printf("[%d]: End!\n", getpid()); // 如果父进程sleep子进程执行完后没有sleep,则子进程先执行完,且由于未被父进程回收称为僵尸进程,僵尸进程将占用进程号 // 如果父进程没有sleep直接执行完,而子进程sleep,则父进程先执行结束退出,子进程由init进程接管,称为s态 // 如果父子进程都执行sleep,则通过ps axf查看进程情况,是shell进程创建父进程,父进程创建子进程 exit(0); }代码示例
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


















所有评论(0)