Linux进程控制【进程创建终止和等待】
创建进程之后,还需要对其进行管理,本文就来讲讲程控制中的,进程创建、进程终止和进程等待
1. 进程创建
进程的创建需要用到fork
函数
1.1 fork函数
fork
函数的作用是在当前进程下,创建一个子进程
#include <usistd.h>
pid_t fork(void);
fock
后内核会做的操作
- 分配新的内存块和内核数据结构(PCB)给子进程
- 将父进程部分数据结构内容拷贝给子进程,同时还会继承父进程中的环境变量表
- 将子进程添加到系统进程列表中
- fork返回开始调度器调度
fork
调用失败的原因
- 系统中有太多的进程
- 实际用户的进程数超过了上限
fork
函数返回类型为pid_t
,相当于typedef int
,只是专门用于进程,它拥有两个返回值
- 进程创建失败,返回
-1
- 进程创建成功,给子进程返回
0
,给父进程返回子进程的PID
进程具有独立性,fork
创建子进程后,父子进程拥有各自的 PCB
,二者独立运行
来试用一下
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <assert.h>
int main()
{
pid_t id = fork(); //获取返回值
assert(id != -1); //创建失败的情况
if(id == 0) //子进程
{
printf("我是子进程, 我的PID是: %d, PPID是: %d\n", getpid(), getppid());
}
else if(id > 0) //父进程
{
printf("我是父进程, 我的PID是: %d, PPID是: %d\n", getpid(), getppid());
}
return 0;
}
这里我们会发现后创建的进程反而先运行,这是因为fork
创建进程后,先执行哪个进程是取决于调度器
1.2 写时拷贝
上文中已经讲到了写时拷贝机制,这里我就做些补充,不演示现象了
通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本,不会对原数据造成影响,如图
补充
- 写时拷贝本质就是一种按需申请资源的机制
- 写时拷贝可以发生在栈区、堆区、只读的数据段等
2. 进程终止
进程退出的场景
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码异常终止
2.1 进程退出码
使用指令echo $?
,可以查看最近一次子进程运行的退出码
echo $? //查看退出码
比如我们main
函数最后一条常用的return 0
中,0
就是退出码,表示正常退出
main
函数退出,表示整个程序退出,程序中的函数退出,仅表示该函数运行结束
进程退出后,OS
会释放进程的内核数据结构 + 代码和数据
- 退出码是给父进程看的,用于判断子进程运行状态
- 子进程运行失败或异常终止,会出现终止信号,无退出码,运行成功,返回退出码,但可能出现结果错误的情况
2.2 进程退出方法
之前讲过进程的外部终止方法,ctrl + c
和kill -9 PID
,进程的内部终止常用exit(-1)
的方式来终止程序运行
进程常用退出方法
- main函数中return返回
- 调用exit()函数
- 调用_exit函数
void exit(int status);
void _exit(int status);
我们通过同一段代码来看看exit()和_exit()的区别
#include <stdio.h>
#include <stdlib.h> //exit()
#include <unistd.h> //_exit()
int main()
{
printf("Can you see me");
//exit(-1);
_exit(-1);
return 0;
}
使用exit()
的退出结果
使用_exit()
退出的结果
我们发现使用exit()
会打印语句,而使用_exit()
不会
推荐使用exit()
,来看看exit()
函数和_exit()
函数的区别
_exit()
函数就是单纯的退出程序,exit()
是对_exit()
封装实现的exit()
函数执行用户定义的清理函数关闭所有打开的流,所有缓存器均被写入
exit()
在退出时,会先冲刷缓冲区,再调用_exit()
3. 进程等待
之前讲到过,子进程运行结束后,父进程没有等待并接收其退出码和退出状态,OS
无法释放进程的内核数据结构 + 代码和数据,就会导致僵尸进程,会造成内存泄漏等问题,进程等待就是用来解决这一问题的
3.1 等待的必要性
父进程可以通过函数等待子进程运行结束,来避免僵尸进程的出现
==进程等待的必要性==
- 僵尸进程可能会造成内存泄漏
- 回收子进程资源,获取子进程退出信息
- 进程的退出状态是必要的,进程的执行结果是非必要的
3.2 进程等待方法
使用系统提供的wait()
函数和waitpid()
函数来进行进程等待,其中waitpid()
比较常用
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int* status);
pid_t waitpid(pid_t pid, int* status, int options);
status
是int*
类型,指向的是一个int
大小的空间,32
个比特位,其中从右向左数,第7-15
次低八位表示退出状态,第0-6
低七位位表示终止信号,第7位这个比特位表示core dump
标志位,高十六位不用管
wait()
函数
- 成功返回等待进程
PID
,失败则返回-1
;参数不关心则可以设置NULL
wait()
使用演示
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
int main()
{
pid_t id = fork(); //获取返回值
assert(id != -1); //创建失败的情况
if(id == 0) //子进程
{
printf("我是子进程, 我的PID是: %d, PPID是: %d\n", getpid(), getppid());
exit(0); //子进程退出
}
wait(0); //等待子进程退出
printf("我是父进程, 我的PID是: %d, PPID是: %d\n", getpid(), getppid());
return 0;
}
waitpid()
函数
返回值:
- 当正常返回的时候
waitpid
返回收集到的子进程的进程ID
- 如果设置了选项
WNOHANG
,而调用中waitpid
发现没有已退出的子进程可收集,则返回0
- 如果调用中出错,则返回
-1
,这时errno
会被设置成相应的值以指示错误所在
参数:
pid:
pid= -1
,等待任一个子进程。与wait
等效pid > 0
,等待其进程ID
与pid
相等的子进程status:
WIFEXITED(status)
: 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)WEXITSTATUS(status)
: 若WIFEXITED
非零,提取子进程退出码。(查看进程的退出码)options:
WNOHANG
: 若pid
指定的子进程没有结束,则waitpid()
函数返回0
,不予以等待。若正常结束,则返回该子进程的ID
waitpid()
使用演示,获取进程的退出码和终止信号
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork(); //创建子进程
if(id == 0) //子进程
{
int count = 0;
while(1)
{
if(count == 5)
{
break;
}
printf("我是子进程,我已经运行了%d秒,PID:%d,PPPID:%d\n", count, getpid(), getppid());
count++;
sleep(1);
}
exit(12); //子进程退出
}
//父进程
int status = 0; //状态
pid_t f_id = waitpid(id, &status, 0);
printf("我是父进程,PID: %d,PPID:%d,f_id: %d,子进程退出码: %d,子进程终止信号: %d\n", getpid(), getppid(), f_id, (status >> 8) & 0xFF, status & 0x7F);
return 0;
}
程序正常终止
程序异常终止
觉得(status >> 8) & 0xFF
和 (status & 0x7F)
这两个位运算有些麻烦的话,系统还提供了两个宏来获取退出码
WIFEXITED
判断进程退出情况,宏为真,表示进程正常退出WEXITSTATUS 相当于
(status >> 8) & 0xFF`,直接获取退出码
举个例子
waitpid(id, &status, WNOHANG);
如果子进程没有退出,那么父进程在wait的时候,是在干什么呢?
- 在子进程没有退出的时候,父进程只有一直在调用
waitpid
进程等待,这种等待就是阻塞等待
3.3 非阻塞等待
如果不想父进程在waitpid
处卡住,而是让他去做别的事情,这个就叫非阻塞等待
父进程可以通过设置 options
参数,进程解除夯
(阻塞)状态,让父进程变成等待轮询
状态,不断获取子进程状态,如果子进程没退出,就可以处理别的任务
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
int count = 0;
while(1)
{
if(count == 3)
{
break;
}
printf("我是子进程,我已经运行了%d秒,PID:%d,PPPID:%d\n", count + 1, getpid(), getppid());
count++;
sleep(1);
}
exit(12);
}
//父进程
while(1)
{
int status = 0;
pid_t f_id = waitpid(id, &status, WNOHANG);
if(f_id < 0)
{
printf("err\n");
exit(-1);
}
else if(f_id == 0)
{
printf("子进程还没有退出,我先干点儿别的事儿\n");
sleep(1);
continue;
}
else
{
printf("我是父进程,PID: %d,PPID: %f_id:%d,子进程退出码: %d,子进程终止信号: %d\n", getpid(), getppid(), f_id, (status >> 8) & 0xFF, status & 0x7F);
break;
}
return 0;
}
}
当然也可以使用宏来获取退出码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
int count = 0;
while(1)
{
if(count == 3)
{
break;
}
printf("我是子进程,我已经运行了%d秒,PID:%d,PPPID:%d\n", count + 1, getpid(), getppid());
count++;
sleep(1);
}
exit(12);
}
//父进程
while(1)
{
int status = 0;
pid_t f_id = waitpid(id, &status, WNOHANG);
if(f_id < 0)
{
printf("err\n");
exit(-1);
}
else if(f_id == 0)
{
printf("子进程还没有退出,我先干点儿别的事儿\n");
sleep(1);
continue;
}
else
{
if(WIFEXITED(status)) //收到信号
{
printf("等待成功,退出码是: %d\n", WEXITSTATUS(status));
}
else
{
printf("等待成功,退出信号是: %d\n", status & 0x7F);
}
break;
}
return 0;
}
}
Linux进程控制—进程的创建、终止和等待,到这里就介绍结束了,本篇文章对你由帮助的话,期待大佬们的三连,你们的支持是我最大的动力!
文章有写的不足或是错误的地方,欢迎评论或私信指出,我会在第一时间改正