前言
打怪升级:第53天 |
---|
进程是什么
进程:一个可执行程序的代码和数据被加载到内存,并且操作系统对该程序的pcb进行管理。
各个进程之间是独立的,一个进程执行的情况大致可以分为两种:执行成功 和 执行失败(因为各种情况被中断 eg:kill -9)
执行成功也分为两种情况:①执行成功后结果正确,②执行成功后结果错误
执行失败后会返回信号 来说明失败的原因;
小故事:小明今天要参加数学考试,考试可能有写完了 和 没写完两种情况,
写完了:他是满分吗?①满分 – 不错不错 ②没有满分 --为什么没有满分,哪些题是不该错的
没写完:为什么没有写完? 是题目太时间不够,还是考试作弊被抓到啦?
也就是说,当我们进程执行成功并且结果正确的情况下我们不会去查看“他为什么成功了”,
但是当它执行成功但是结果不对(进程退出码) 或者 **执行都没有执行完(信号)**的情况下我们就需要了解一下相关的情况了。
在进程中:进程正确执行会返回0,如果执行失败会有返回错误信号,
正确执行后会有对应进程退出码(类似函数返回值),我们自己写的程序加载到内存后也会成为进程,
进程中的其他函数返回表示该函数执行结束,当main函数返回时表示整个进程结束。
一、创建子进程
创建子进程的函数:fork,
头文件:#include<unistd.h>
函数原型:pid_t fork();
返回值:子进程返回0, 父进程返回子进程pid,创建失败则返回-1.
至于为什么一个函数可以有两个返回值 以及 一个地址可以同时存储两个整数的问题感兴趣的朋友可以取看一看我的前面两篇文章,分别有详细讲解,这里就不再说明。
#include<stdio.h>
#include<unistd.h> // sleep(); // getpid(); getppid();
#include<sys/types.h>
#include<assert.h> // assert(); 断言
void Test02()
{
int num = 100;
pid_t ret = fork();
assert(ret != -1);
if(ret == 0)
{
while(1)
{
// 子进程
printf("这是子进程,pid = %d, ppid = %d, num = %d, &num = %p\n", getpid(), getppid(), num, &num);
sleep(1);
num = 20;
}
}
else
{
while(1)
{
// 父进程
printf("这是父进程,pid = %d, ppid = %d, num = %d, &num = %p\n", getpid(), getppid(), num, &num);
sleep(1);
}
}
}
int main()
{
//Test01();
Test02();
return 0;
}
- fork使用场景
- 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子
进程来处理请求; - 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
- fork调用失败的原因
- 系统中有太多的进程
- 实际用户的进程数超过了限制
echo $?
下面我们直接来见识一个操作指令:
echo $?
作用:查看上一个进程的退出码
#include<stdio.h>
int JudgeEqual(int num)
{
if(num == 100) // 判断num 等于 100
return 1;
else
return 0;
}
int main()
{
return 107;
// int ret = JudgeEqual(5);
//
// if(ret == 1)
// printf("The result is right\n");
// else
// printf("The result is wrong\n");
// return 0;
}
进程的创建:OS 创建对应的PCB 后将可执行程序的代码和数据拉取到内存并与进程的虚拟地址空间建立映射(先创建PCB)。
二、进程终止
-
正常终止(可以通过echo $? 查看进程退出码)
main函数return;
库函数:exit();
系统调用:_exit(); -
异常终止(不需要查看退出码:考试作弊了,查看就没有查看成绩的必要)
Ctrl + c、信号(kill -9) -
exit
库函数头文件 : <stdlib.h>
函数原型: void exit(int status);
-
_exit
系统调用头文件 : <usistd.h>
函数原型: void _exit(int status);
exit 与 exit 的区别:
- return退出
return是一种更常用的退出进程的方式,使用return n;
退出等同于 调用exit(n);
因为调用main的运行时函数会将main的返回值当做 exit的参数。
三、进程等待
- 什么是进程等待?
父进程创建子进程,并且给子进程分配任务,此时父进程需要等待子进程完成对应的任务。 - 为什么要有进程等待?
父进程给子进程分配任务后需要知道子进程有没有完成任务,以及完成的好不好。 - 进程等待的必要性?
上面提到,父进程需要获得子进程任务完成的情况,但是有时候我们“只是不想让它闲着,想要给它找一些事情做做”,既然我们并不关心做的结果如何,那这就不是进程等待的必要原因,
进程等待最根本的目的是:回收子进程资源
子进程完成任务后不管结果如何都会去向父进程汇报情况,如果父进程不进行等待,子进程占用的资源始终不会被释放,此时我们称子进程进入了:僵尸状态,进而造成内存泄漏,进入僵尸状态的进程会变得“刀枪不入”,即使是 kill -9 也杀不死它,因为我们无论如何也杀不死一个已经死掉的进程。
所以,进程等待就相当于“收尸”。
所以父进程通过进程等待的方式回收子进程资源,获取子进程退出信息。
头文件:<sys/types.h>、<sys/wait.h>
函数原型:
pid_t wait(int* status);
pid_t waitpid(pid_t pid, int* status, int options);
1.wait
pid_t wait(int* status);
返回值为:如果正确,返回子进程pid,如果错误,返回-1,
参数:status 是一个输出型参数(可以在wait函数中进行更改 – status为一个整形,此处传递的是它的地址)
status是用来记录子进程退出的状态的,如果不需要可以直接传空指针。
#include<stdio.h>
#include<unistd.h> // sleep(); // getpid(); getppid();
#include<sys/types.h>
#include<assert.h> // assert(); 断言
#include<sys/wait.h>
void Test02()
{
pid_t ret = fork();
assert(ret != -1);
if(ret == 0)
{
int cnt = 5;
while(cnt--)
{
// 子进程
printf("这是子进程,pid = %d, ppid = %d\n", getpid(), getppid());
sleep(1);
}
}
else
{
pid_t id = wait(NULL);
printf("子进程退出成功, 子进程id = %d\n", id);
while(1)
{
// 父进程
printf("这是父进程,pid = %d, ppid = %d\n", getpid(), getppid());
sleep(1);
}
}
}
int main()
{
Test02();
return 0;
}
上面是子进程正常执行结束,那如果异常退出会是什么情况呢?
我们把子进程改为死循环,之后使用kill命令杀死它。
我们看到,不论是进程正常结束还是异常退出(使用kill杀死 – 信号),父进程都没有反应,他只知道子进程退出了,
而如果我们想要知道子进程的执行情况的话就需要使用上面的 status参数来获取。
所谓进程退出码就是:main函数的返回值 以及 exit 中的内容
所谓信号:就是kill、Ctrl + c等强制进程中断导致进程没有正常退出时会发出的信息(下方会展示出来)
那么我们想要分别查看进程退出码 和 信号 就可以使用位运算来进行提取:
#include<stdio.h>
#include<unistd.h> // sleep(); // getpid(); getppid();
#include<stdlib.h>
#include<sys/types.h>
#include<assert.h> // assert(); 断言
#include<sys/wait.h>
void Test02()
{
pid_t ret = fork();
assert(ret != -1);
if(ret == 0)
{
int cnt = 5;
while(cnt--)
{
// 子进程
printf("这是子进程,pid = %d, ppid = %d\n", getpid(), getppid());
sleep(1);
}
exit(1);
}
else
{
int status = 0;
pid_t id = wait(&status);
printf("子进程退出成功, 子进程id = %d\n", id);
printf("子进程退出码code = %d, 信号single = %d\n", status>>8 & 0xFF, status & 0x7F);
while(1)
{
// 父进程
printf("这是父进程,pid = %d, ppid = %d\n", getpid(), getppid());
sleep(1);
}
}
}
int main()
{
Test02();
return 0;
}
上面是程序正常执行,下面我们来见一见程序中断的情况:
演示查看进程退出码和信号
将特定数字转换为错误提示字符串:
头文件:<string.h>
函数原型:char* strerror(int errnum);
#include<stdio.h>
#include<string.h>
int main()
{
for(int i=0; i<200; ++i)
printf("%d -> %s\n", i, strerror(i));
return 0;
}
2.waitpid
pid_t waitpid(pid_t pid, int* status, int options);
演示使用:
我们来详细了解一下各个参数的含义:
我们约朋友一起出去玩儿时,如果我们去的早,朋友此时还没有到,那么我们此时会做什么?
– 1、一直等他,什么事情也不做,就瞪大眼睛看着马路对面等他出现。
– 2、打一会儿游戏,或者给他打打电话,或者和朋友聊聊天等。
第一种情况就是wait 以及 waitpid( pid, status, 0); 最后一个参数设为0的waitpid,都表示父进程阻塞,此时什么也不做,就一直等待子进程,
而 waitpid(pid, status, WNOHANG); 表示父进程非阻塞,既然等不到子进程那就先去做一些别的事情。
#include<stdio.h>
#include<unistd.h> // sleep(); // getpid(); getppid();
#include<stdlib.h>
#include<sys/types.h>
#include<assert.h> // assert(); 断言
#include<sys/wait.h>
void Test02()
{
pid_t ret = fork();
assert(ret != -1);
if(ret == 0)
{
int cnt = 5;
while(cnt--)
{
// 子进程
printf("这是子进程,pid = %d, ppid = %d\n", getpid(), getppid());
sleep(1);
}
}
else
{
int status = 0;
pid_t id;
// 父进程
while(1)
{
id = waitpid(ret, &status, WNOHANG);
assert(id >= 0);
if(id == 0)
{
printf("我是父进程,子进程还没有结束,我先做一些其他事情。。。\n");
sleep(1);
}
else
{
printf("子进程退出成功, 子进程id = %d\n", id);
printf("子进程退出码code = %d, 信号single = %d\n", status>>8 & 0xFF, status & 0x7F);
printf("这是父进程,pid = %d, ppid = %d\n", getpid(), getppid());
sleep(1);
}
}
}
}
int main()
{
Test02();
return 0;
}
- 补充一点:
判断进程的是否接收到信号 以及 进程的退出码我们也有两个宏:WIFEXITED(status), WEXITSTATUS(status)
#include<stdio.h>
#include<unistd.h> // sleep(); // getpid(); getppid();
#include<stdlib.h>
#include<sys/types.h>
#include<assert.h> // assert(); 断言
#include<sys/wait.h>
void Test01()
{
pid_t id = fork();
assert(id >= 0);
if(id == 0)
{
printf("我是子进程,pid = %d, ppid = %d\n", getpid(), getppid());
exit(107); // 子进程正常退出
}
else
{
int status = 0;
waitpid(-1, &status, 0); // 此时完全等同于 wait -- 等待任意进程退出,并且等待期间父进程阻塞
printf("我是父进程,pid = %d, ppid = %d\n", getpid(), getppid());
if(WIFEXITED(status))
{
printf("wait seccess, 子进程exit code = %d\n", WEXITSTATUS(status));
}
else
{
printf("wait seccess, 子进程exit error\n");
}
}
}
int main()
{
Test01();
return 0;
}
总结一下进程等待
- wait 和 waitpid 函数的使用,
wait函数必须阻塞等待,等待子进程,而waitpid的最后一个整形参数设置为 WNOHANG, 父进程就可以不阻塞;
2.宏 WEXITED(status) 判断是否收到信号 – 收到信号就为假,表明运行出错;
宏 WEXITSTATUS(status)提取进程退出码。