进程等待-

进程终止时,os做了些什么??

释放进程申请的相关内核数据结构和对应的数据和代码。(本质是释放系统资源)

进程终止的常见方式:

a、代码跑完,且结果正确

b、代码跑完,结果不正确

c、代码未运行完成,程序崩溃。

在此引出一个问题,关于main函数的返回值

首先main函数的返回值意义是什么??return 0 又有什么意义??为啥总是0???

返回意义:返回给上一级进程,用来评判该进程执行结果用的,

返回值是进程的退出码,并不总是0,也有可能是其他的,不同的数字有不同的意义

我们自己可以使用这些退出码和含义,但是,如果你想自己定义,也可以自己设计一套退出方案!(小明考试)

在main函数内return语句就是终止进程的!!!return 退出码

例如

0 代表success

非0 代表的是运行的结果不正确。非0 的值有无数个,不同的非0 值就标识着不同的错误原因!!!

可以通过下面的代码查看Linux下的错误码以及对应的错误。

for(int number = 0; number < 150; number++) { printf("%d: %s\n", number, strerror(number)); }

根据进程的退出码,给我们的程序运行结束之后,结果不正确,方便定位错误。

值得关注的是:程序崩溃的时候,退出码将毫无意义!!一般而言,奔溃的时候,退出码对应的return 语句没有被执行!!!

进程正常终止我们可以使用 echo $?查看退出码。

如何正常终止,1.从main函数返回2.调用exit3._exit。

异常退出,ctrl + c 信号终止。

exit()、_exit()

这两个函数有什么区别???

#include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> int main() { printf("hello world"); sleep(3); exit(0); //_exit(0); return 0; }

运行结果很清楚,由于我们的printf函数里面没有/n,所以数据没有第一时间刷新,而是呆在了缓存区,exit的话会将缓存区的数据刷新,然后再终止程序。而_exit的话,是直接终止程序,没有其他操作。

从这里我们也窥见了一些东西。

printf - \n数据是保存在”缓冲“中的,请问这个”缓冲区,在哪里???指的是什么缓冲区???谁维护??

首先我们明确的是,一定不在系统内部。如果是操作系统维护的,缓冲区_exit也能刷新出来! ! !

其实是C标准库给我们维护的! ! !

两者的差别和联系

在这个过程中,我们还可以看出,缓冲区的数据被写入。联想到printf 与\n。

数据保存在缓冲区,这个缓冲区是c标准库给我们提供维护的,而且一定不在操作系统内部,因为是操作系统维护的,缓冲区_exit()也能刷新出来。

_exit函数

#include <unistd.h> void _exit(int status); 参数:status 定义了进程的终止状态,父进程通过wait来获取该值

说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值 是255。

exit函数

#include <unistd.h> void exit(int status);

exit最后也会调用exit, 但在调用exit之前,还做了其他工作:

1. 执行用户通过 atexit或on_exit定义的清理函数。

2. 关闭所有打开的流,所有的缓存数据均被写入

3. 调用_exit

当退出信号为 0时,则代表正常跑完的,注意力放在退出码上

当退出信号不为 0,则退出码也就没有意义了。

进程等待

为啥要进行进程等待???

子进程退出,父进程不管,子进程就会处于僵尸状态———导致内存泄露

创建进程的初衷是想让子进程执行代码(做事),需要关注子进程的完成状态以及进度,

这里要明白一点,这里导致的内存泄漏和我们语言层面上的内存泄漏不是一个东西。

这里是系统层面上的。我们在语言层面C/C++上的new、malloc 出来的空间,如果没有被释放,那么在这个进程结束之后,操作系统就会自动回收释放。这里的空间是堆上的空间。而我们的僵尸进程是该进程的代码跑完了,然后该进程的代码和数据在该程序执行完之后就释放了。就剩下了进程控制块PCB(task_struct),这个进程控制块是占据系统资源的。

官方一点的说法

子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。

另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法 杀死一个已经死去的进程。

最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对, 或者是否正常退出。

父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

wait 函数

#include<sys/types.h> #include<sys/wait.h> pid_t wait(int*status); 返回值: 成功返回被等待进程pid,失败返回-1。 参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

int status = 0;

status :输出型参数!!

如果传递NULL,表示不关心子进程的退出状态信息。

waitpid( pid ,NULL,0) == wait(NULL);

status 并不是按照整数来整体使用的!!!而是按照比特位的方式,将32位比特位进行划分,

退出码>(status >> 8)&0xFF > WEXITSTATUS(status)

退出信号>status & 0x7F

core dump------>>>> gdb调试崩溃程序信号

这里值得注意的是在wait_pid里面提到了两个宏定义。有一个是退出码,另一个 WIFEXITED(status)为判断子进程终止返回的状态。

进程异常退出,或者崩溃,本质是操作系统杀掉了你的进程!!

操作系统如何杀掉呢?本质是通过发送信号的方式!

waitpid函数

pid_ t waitpid(pid_t pid, int *status, int options); 返回值: 当正常返回的时候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。

int status = 0;

status :输出型参数!!

如果传递NULL,表示不关心子进程的退出状态信息。

waitpid( pid ,NULL,0) == wait(NULL)

// 父进程 //int status = 0; 只有子进程退出的时候,父进程才会waitpid函数,进行返回!![父进程依旧还活着呢!!] waitpid/wait 可以在目前的情况下,让进程退出具有一定的顺序性! 将来可以让父进程进行更多的收尾工作. id > 0 : 等待指定进程 id == -1: 等待任意一个子进程退出, 等价于wait(); options: 默认为0,代表阻塞等待, WNOHANG: 叫做非阻塞等待 pid_t result = waitpid(id, &status, 0); // 默认是在阻塞状态去等待子进程状态变化(就是退出!) if(result > 0) { // 可以不这么检测 //printf("父进程等待成功, 退出码: %d, 退出信号: %d\n", (status>>8)&0xFF, status & 0x7F); if(WIFEXITED(status)) { //子进程是正常退出的 printf("子进程执行完毕,子进程的退出码: %d\n", WEXITSTATUS(status)); } else { printf("子进程异常退出: %d\n", WIFEXITED(status)); } }

options :默认为0,表示阻塞等待!!

options: WNOHANG选项,代表父进程非阻塞等待!

#define WNOHANG 1

Wait No HANG(夯住了)

夯住了通俗为系统中,这个进程没有被CPU调度。这个进程要么在阻塞队列中,要么是等待被调度!!

1 代表为非阻塞等待

0 代表阻塞等待

阻塞等待和非阻塞等待(张三打电话)

阻塞等待,一般都是在内核中阻塞,等待被唤醒。阻塞一般伴随着被切换!

下面是代码

#include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> int main() { pid_t id = fork(); if(id < 0) { perror("fork"); exit(1); //标识进程运行完毕,结果不正确 } else if(id == 0) { //子进程 int cnt = 5; while(cnt) { printf("cnt: %d, 我是子进程, pid: %d, ppid : %d\n", cnt, getpid(), getppid()); sleep(1); //cnt--; //int * p = NULL; //*p = 100; // int a = 10; // a /= 0; } exit(15); } else { //父进程 printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid()); //sleep(7); //pid_t ret = wait(NULL); //阻塞式的等待! int status = 0; pid_t ret = waitpid(id, &status, 0); //阻塞式的等待! if(ret > 0) { // 0x7F -> 0000.000 111 1111 printf("等待子进程成功, ret: %d, 子进程收到的信号编号: %d,子进程退出码: %d\n",\ ret, status & 0x7F ,(status >> 8)&0xFF); //0xff --> 0000...000 1111 1111 printf("code: %d\n", code); } //基本没有意义的,只是为了对比演示 //while(1) //{ // printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid()); // sleep(1); //} } }


阻塞等待和非阻塞等待

阻塞等待一般都是在内核中阻塞,等待被唤醒。在这个过程中,一般伴随着被切换!!!该进程就不运行了,就相当于调用卡住了---->>对于上层来说,好像软件卡住了。就比如咱们的cin 、scanf。这些语言层面的接口底层封装着系统调用。

夯住了,对于系统来说,这个进程没有被CPU调度。此时,这个进程要么是在阻塞队列中,或者等待被调度!

非阻塞等待

我们的父进程通过调用waitpid来进行等待,如果子进程没有退出我们waitpid这个系统调用,立马返回!

#include <iostream> #include <vector> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <unistd.h> // //typedef void (*handler_t)(); //函数指针类型 // //std::vector<handler_t> handlers; //函数指针数组 // //void fun_one() //{ // printf("这是一个临时任务1\n"); //} //void fun_two() //{ // printf("这是一个临时任务2\n"); //} // 设置对应的方法回调 以后想让父进程闲了执行任何方法的时候,只要向Load里面注册,就可以让父进程执行对应的方法喽! //void Load() //{ // handlers.push_back(fun_one); // handlers.push_back(fun_two); //} // //int main() //{ // pid_t id = fork(); // if(id == 0) // { // // 子进程 // int cnt = 5; // while(cnt) // { // printf("我是子进程: %d\n", cnt--); // sleep(1); // } // // exit(11); // 11 仅仅用来测试 // } // else // { // int quit = 0; // while(!quit) // { // int status = 0; // pid_t res = waitpid(-1, &status, WNOHANG); //以非阻塞方式等待 // if(res > 0) // { // //等待成功 && 子进程退出 // printf("等待子进程退出成功, 退出码: %d\n", WEXITSTATUS(status)); // quit = 1; // } // else if( res == 0 ) // { // //等待成功 && 但子进程并未退出 // printf("子进程还在运行中,暂时还没有退出,父进程可以在等一等, 处理一下其他事情??\n"); // if(handlers.empty()) Load(); // for(auto iter : handlers) // { // //执行处理其他任务 // iter(); // } // } // else // { // //等待失败 // printf("wait失败!\n"); // quit = 1; // } // sleep(1); // } // return 0; //}


1.父进程通过wait/waitpid可以拿到子进程的退出结果,为什么要用wait/waitpid函数呢?直接全局变量不行吗??

进程具有独立性,那么数据就要发生写时拷贝,父进程无法拿到,况且,信号呢??

2.既然进程是具有独立性的,进程退出码,不也是子进程的数据吗?父进程有凭什么拿到呢? ?(wait/waitpid究竟干了什么呢??)

僵尸进程:至少要保留该进程的PCB信息! task_struct里面保留了任何进程退出时的退出结果信息!!---------->>>

wait(/waitpid有这个权利吗?系统调用啊!!!,不就是操作系统吗!!!task_struct是内核数据结构对象!!------->>


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值