Linux多进程介绍及使用
- 1.相关的概念:
- 2.linux提供跟进程有关的接口函数
-
- (1)进程的创建(创建子进程)
-
- fork函数的特点:
-
- ①一次调用,两次返回,分别表示父子进程
- 示例代码:fork返回值的理解
- [② fork创建子进程的时候,会给子进程分配独立的内存空间,同时子进程会复制父进程的所有资源(会复制fork之前的变量(全局和局部))](#② fork创建子进程的时候,会给子进程分配独立的内存空间,同时子进程会复制父进程的所有资源(会复制fork之前的变量(全局和局部)))
- 示例代码:子进程创建成功会复制父进程的资源
- fork底层原理分析:
- (2)进程的退出和回收
- (3)获取进程的id以及获取父进程的id
1.相关的概念:
进程:
一个正在运行的程序,只要你运行了程序,那么系统中都会产生一个对应的进程,动态的概念
程序:
用编译器编译得到的二进制文件,静态的概念
相关的shell命令:
ps -elf 查看当前系统进程的状态信息
ps英文全称process state
进程的组成:
代码块(程序代码)+数据块(程序中用到的数据)+进程控制块
进程控制块(PCB-->process contrl block):指的是系统中定义的一个结构体
系统相关的头文件/usr/src/linux-hwe-5.15-headers-5.15.0-136/include/linux/sched.h
c
struct task_struct
{
//当./程序名运行一个程序的时候,系统里面产生对应名字的进程,并且把该进程在运行时候的状态参数全部存放到一个结构体变量中
//这个结构体叫做struct task_struct(进程控制块 )
//存放了跟当前进程有关的一些状态信息(比如:进程运行占用cpu百分比,内存占用情况,打开的文件信息)
long state //保存进程的运行状态
-1表示没有运行
0表示正在运行
>0表示暂停
int pid; //进程的ID号
long prority //进程的优先级
}
父进程:
\quad 哪个主函数调用fork创建子进程,该程序就是父进程
c
int main() //父进程
{
fork(); //子进程
}
孤儿进程(僵尸进程):
\quad 父进程优先于子进程退出了,导致子进程的资源没有回收,这种现象就叫做孤儿进程
2.linux提供跟进程有关的接口函数
(1)进程的创建(创建子进程)
c
#include <unistd.h>
pid_t fork();
返回值: >0 表示父进程,此时返回值中存放的是子进程的id号
==0 表示子进程
-1 表示失败
fork函数的特点:
①一次调用,两次返回,分别表示父子进程
示例代码:fork返回值的理解
c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
printf("程序开始运行\n");
pid = fork();
if (pid > 0) //父进程
{
printf("父进程中的ID: %d\n",pid);
while(1)
{
printf("父进程循环\n");
sleep(1);
}
}
else if (pid == 0) //子进程
{
printf("子进程中的ID: %d\n",pid);
while(1)
{
printf("子进程循环\n");
sleep(1);
}
}
else
{
printf("fork失败\n");
return -1;
}
return 0;
}
/*
执行结果:
程序开始运行
父进程中的ID: 3404
父进程循环
子进程中的ID: 0
子进程循环
父进程循环
子进程循环
父进程循环
子进程循环
...
*/
② fork创建子进程的时候,会给子进程分配独立的内存空间,同时子进程会复制父进程的所有资源(会复制fork之前的变量(全局和局部))
示例代码:子进程创建成功会复制父进程的资源
c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
/*
子进程创建成功,子进程拥有独立的内存空间
讨论研究:
子进程从它父进程那里里拷贝了什么,拷贝的特点
特点1:全局变量,子进程会备份一个,跟父进程的全局变量同名,但是相互独立
特点2:局部变量,子进程会备份一个,跟父进程的局部变量同名,但是相互独立
*/
// 定义全局变量
int g_num = 10;
int main()
{
pid_t pid;
int m = 100;
printf("程序开始运行\n");
pid = fork();
if (pid > 0) //父进程
{
// 父进程修改全局变量,子进程不会受影响
g_num = 20;
m = 200;
printf("父进程中的ID: %d\n",pid);
while(1)
{
printf("父进程循环 全局变量,g_num=%d\n", g_num);
printf("父进程循环 局部变量,m=%d\n", m);
sleep(1);
}
}
else if (pid == 0) //子进程
{
// 子进程修改全局变量,父进程不会受影响
g_num = 30;
m = 300;
printf("子进程中的ID: %d\n",pid);
while(1)
{
printf("子进程循环 全局变量,g_num=%d\n", g_num);
printf("子进程循环 局部变量,m=%d\n", m);
sleep(1);
}
}
else
{
printf("fork失败\n");
return -1;
}
return 0;
}
/*
执行结果:
程序开始运行
父进程中的ID: 3548
父进程循环 全局变量,g_num=20
父进程循环 局部变量,m=200
子进程中的ID: 0
子进程循环 全局变量,g_num=30
子进程循环 局部变量,m=300
父进程循环 全局变量,g_num=20
父进程循环 局部变量,m=200
子进程循环 全局变量,g_num=30
子进程循环 局部变量,m=300
*/
fork底层原理分析:



(2)进程的退出和回收
c
结束进程:
void exit(int status);
void _exit(int status);
exit和_exit的区别:
- exit结束进程的时候会刷新缓冲区,但是_exit()不会刷新缓冲区
- 正常情况下:\n和return语句都能刷新缓冲区
示例代码:exit和_exit的区别
c
#include "myhead.h"
/*
区别:
exit会刷新缓冲区
_exit不会刷新缓冲区
相同:都可以结束某个进程
*/
int main()
{
printf("hello world!");
//exit(0); //结束当前进程,会刷新缓冲区,因此打印语句可以见到
_exit(0); //结束当前进程,不会刷新缓冲区,因此打印语句不能见到
}
return和exit的区别:
- 区别一:return 是关键字,exit()是函数
- 区别二:return结束函数,返回返回值
exit()结束整个进程
示例代码:return和exit的区别
c
#include "myhead.h"
/*
区别:
exit是个函数
return是个关键字
exit结束整个进程
return结束当前函数调用,返回某个结果给到调用者
*/
int fun()
{
printf("fun这个函数被调用了!\n");
//exit(0); //结束整个进程 ,当执行在exit函数时整个进程退出了,不会执行后面的语句
return 0; //fun有个返回值,是0
}
int main()
{
printf("主函数开始运行!\n");
fun();
printf("调用fun完毕,主函数准备退出!\n");
return 0;
}
进程的回收wait
c
#include <sys/wait.h>
pid_t wait(int *stat_loc);
返回值:成功 回收到的那个子进程的id号
失败 -1
参数(重点):stat_loc --》存放进程退出时候的状态信息
进程的退出值(exit/_exit圆括号里面的整型参数)仅仅只是状态信息的一部分,状态信息还包含其它内容(子进程是正常退出还是异常退出,是被哪个信号终止的)
特点:阻塞父进程,等待子进程的退出
补充:
- kill -9 进程的ID号 //给指定的进程发送9号信号
- kill -l //罗列linux系统中所有的信号
示例代码:进程的退出与回收wait
c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int status;
printf("程序开始运行\n");
pid = fork(); // 创建子进程
if (pid > 0) //父进程
{
printf("父进程执行,父进程id=%d,子进程id=%d\n", getpid(), pid);
}
else if (pid == 0) //子进程
{
printf("子进程执行,子进程id=%d\n",getpid());
exit(5); //退出子进程 ,如不加exit退出子进程,运行会提示子进程异常退出
}
else
{
perror("fork失败\n");
return -1;
}
printf("父进程准备退出\n");
// 回收新建的子进程
pid_t ret = wait(&status);
//判断子进程是正常退出还是异常退出
if(WIFEXITED(status))
{
printf("子进程是正常退出的,退出值是: %d!\n",WEXITSTATUS(status));
printf("%d\n",status);
}
else
printf("子进程是异常退出的!\n");
printf("父进程回收子进程成功,回收的子进程ID是: %d\n",ret);
return 0;
}
/*
执行结果:
// 屏蔽exit()函数的情况
程序开始运行
父进程执行,父进程id=3323,子进程id=3324
父进程准备退出
子进程执行,子进程id=3324
父进程准备退出
子进程是异常退出的!
父进程回收子进程成功,回收的子进程ID是: -1
子进程是正常退出的,退出值是: 0!
0
父进程回收子进程成功,回收的子进程ID是: 3324
// 未屏蔽exit()函数的情况
程序开始运行
父进程执行,父进程id=3339,子进程id=3340
父进程准备退出
子进程执行,子进程id=3340
子进程是正常退出的,退出值是: 5!
1280
父进程回收子进程成功,回收的子进程ID是: 3340
*/
进程的回收waitpid
c
pid_t waitpid(pid_t pid, int *stat_loc, int options);
返回值:成功 回收到的那个子进程的id号
失败 -1
参数:pid --》 < -1 回收进程组id是pid绝对值中的某个进程
比如: waitpid(-10000,) 回收进程组ID是10000的这个组里面的某个进程
== -1 回收任意一个进程
比如: waitpid(-1,);
== 0 回收本进程组中的某个进程-跟主函数在同一组内的进程
比如: waitpid(0)
> 0 指定回收进程id是pid的这个进程
比如: waitpid(10000,); 回收id是10000的这个进程
options --》 WNOHANG 非阻塞等待,父进程在退出的时候,如果子进程还没有退出,那么父进程不会阻塞,也不会去回收子进程,直接退出
0 阻塞等待
补充:进程组就是多个进程组成的一个集合
linux允许多个进程加入到某个组里面,形成了一个小团队--》进程组
示例代码:waitpid指定回收子进程
c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
/*
waitpid指定回收子进程
*/
int main()
{
pid_t id,id1,id2;
pid_t ret;
int i;
int status;
//先生第一个
id=fork();
if(id>0) //父进程
{
//生第二个
id1=fork();
if(id1==0) //第二个子进程
{
printf("扫描漏洞!\n");
while(1)
{
// 此时父进程会阻塞在这里,等待子进程结束
}
exit(2);
}
else if(id1>0) //父进程
{
//生第三个子进程
id2=fork();
if(id2==0) //第三个子进程
{
printf("清理垃圾!\n");
exit(3);
}
}
}
else if(id==0) //第一个子进程
{
printf("杀毒!\n");
exit(1);
}
printf("父进程执行软件升级!\n");
//回收三个子进程-->假设我按照 2 3 1来回收
ret=waitpid(id1,&status,0); // 0:阻塞等待 WNOHANG:非阻塞
printf("我目前回收的子进程是: %d,退出值: %d\n",ret,(status&0xff00)>>8);
ret=waitpid(id2,&status,0);
printf("我目前回收的子进程是: %d,退出值: %d\n",ret,(status&0xff00)>>8);
ret=waitpid(id,&status,0);
printf("我目前回收的子进程是: %d,退出值: %d\n",ret,(status&0xff00)>>8);
return 0;
}
/*
执行结果:
杀毒!
父进程执行软件升级!
扫描漏洞!
清理垃圾!
// 此时父进程会阻塞在这里,等待子进程结束
*/
示例代码:waitpid非阻塞等待
c
#include "myhead.h"
/*
waitpid非阻塞等待
*/
int main()
{
pid_t id,id1,id2;
pid_t ret;
int i;
int status;
//先生第一个
id=fork();
if(id>0) //父进程
{
//生第二个
id1=fork();
if(id1==0) //第二个子进程
{
printf("扫描漏洞!\n");
sleep(2);
exit(2);
}
else if(id1>0) //父进程
{
//生第三个子进程
id2=fork();
if(id2==0) //第三个子进程
{
printf("清理垃圾!\n");
exit(3);
}
}
}
else if(id==0) //第一个子进程
{
printf("杀毒!\n");
exit(1);
}
printf("父进程执行软件升级!\n");
//回收三个子进程-->假设我按照 2 3 1来回收
ret=waitpid(id1,&status,WNOHANG);
printf("我目前回收的子进程是: %d,退出值: %d\n",ret,(status&0xff00)>>8);
ret=waitpid(id2,&status,0);
printf("我目前回收的子进程是: %d,退出值: %d\n",ret,(status&0xff00)>>8);
ret=waitpid(id,&status,0);
printf("我目前回收的子进程是: %d,退出值: %d\n",ret,(status&0xff00)>>8);
return 0;
}
具体宏定义如下:

(3)获取进程的id以及获取父进程的id
c
获取当前进程的id号
pid_t getpid(void);
c
获取父进程的id
pid_t getppid(void);
示例代码:创建多个子进程1
c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
pid_t pid1, pid2;
int ret, status;
//创建两个子进程
//错误写法1: 实际结果创建出来的不止两个子进程
//fork();
//fork();
//错误写法2: 实际结果创建出来的不止两个子进程
//for(int i=0; i<2; i++)
//fork();
pid1 = fork();
if (pid1 > 0) //父进程
{
printf("This is parent process1\n");
// 创建第二个子进程
pid2 = fork();
if (pid2 > 0) //父进程
{
printf("This is parent process2\n");
}
else if (pid2 == 0) //子进程2
{
printf("This is child process 2\n");
exit(2);
}
}
else if( pid1 == 0) //子进程1
{
printf("This is child process 1\n");
exit(1);
}
printf("父进程执行!\n");
//回收两个子进程
ret=wait(&status);
// printf("目前回收的子进程是: %d,退出值: %d\n",ret,(status&0xff00)>>8);
printf("目前回收的子进程是: %d,退出值: %d\n",ret, WEXITSTATUS(status));
ret=wait(&status);
// printf("目前回收的子进程是: %d,退出值: %d\n",ret,(status&0xff00)>>8);
printf("目前回收的子进程是: %d,退出值: %d\n",ret,WEXITSTATUS(status));
return 0;
}
/*
执行结果:
This is parent process1
This is parent process2
父进程执行!
This is child process 1
This is child process 2
目前回收的子进程是: 4978,退出值: 1
目前回收的子进程是: 4979,退出值: 2
*/
示例代码:创建多个子进程2
c
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#define N 5
int main() {
pid_t children[N]; // 记录子进程 PID,以便精确回收
for (int i = 0; i < N; i++) {
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
} else if (pid == 0) {
// 子进程工作
printf("子进程 %d (PID=%d) 工作中...\n", i+1, getpid());
sleep(1); // 模拟工作
exit(i+1); // 退出码为 i+1
} else {
children[i] = pid; // 父进程保存子进程 PID
}
}
// 父进程回收所有子进程,并收集退出状态
for (int i = 0; i < N; i++) {
int status;
pid_t pid = waitpid(children[i], &status, 0);
if (WIFEXITED(status)) {
printf("子进程 %d (PID=%d) 正常退出,退出码 %d\n",
i+1, pid, WEXITSTATUS(status));
}
}
printf("所有子进程已回收,父进程结束。\n");
return 0;
}
/*
执行结果:
子进程 1 (PID=3057) 工作中...
子进程 3 (PID=3059) 工作中...
子进程 2 (PID=3058) 工作中...
子进程 5 (PID=3061) 工作中...
子进程 4 (PID=3060) 工作中...
子进程 1 (PID=3057) 正常退出,退出码 1
子进程 2 (PID=3058) 正常退出,退出码 2
子进程 3 (PID=3059) 正常退出,退出码 3
子进程 4 (PID=3060) 正常退出,退出码 4
子进程 5 (PID=3061) 正常退出,退出码 5
所有子进程已回收,父进程结束。
*/