Linux——进程控制1|再谈fork()|fork常规用法和调用失败原因|进程终止|main函数返回值|exit和_exit|相同点|不同点 |总结 |进程等待(下)

简介: 笔记

进程等待


子进程退出,父进程不管子进程,子进程处于僵尸状态——若不回收会导致内存泄漏


父进程如何得知子进程状况?


上面这些问题都需要进程等待来完成


进程等待的必要性:


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



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



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



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




wait验证

1.png

子进程跑完后进入僵尸状态,没有被回收


我们使用wait回收它,wait是等待一个进程直到这个进程的状态发生变化(如有R或S变为Z),如果回收成功,返回子进程PID,失败了返回-1


参数 status 保存着子进程退出时的一些状态(包括 task_struct、thread_info及内核栈等)它是一个指向 int 类型的指针;如果不在意子进程的结束状态值,只想把这个僵尸进程消灭掉(实际上,大多数时候都是这样做的),则可以将这个参数设为 NULL,即

pid = wait(NULL);        // 不管子进程的结束状态,直接杀死进程

2.png


使用wait,由于前面子进程sleep5秒钟,这个时候父进程调用wait是在阻塞式的等待

3.png



通过对比我们发现,子进程已经被回收了,子进程由S切换到Z,立马被回收

进程的一般写法就是fork+wait/wait pid

4.png

wait pid

5.png

第一个参数:某进程的PID,若是-1,则代表任意一个进程,与wait等效,若是0,表示等待指定进程

第二个参数:输出型参数和wait的status一模一样


第三个参数:默认为0,表示阻塞等待


返回值>0代表等待成功,<0代表等待失败


waitpid(pid,NULL,0)=wait(NULL),验证这一结论,我们发现结果和上面一致6.png


7.png

获取子进程退出的结果

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。



如果传递NULL,表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

8.png

exit(15),status是3840

9.png

status并不是按照整数来整体使用的。而是按照比特位的方式,将32个比特位进行划分,其中我们学习的是低16个


status构成:


status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特

位):

10.png

15-8叫次低8位表示子进程退出时的退出码


如果想得到子进程退出结果,右移8位,然后和1111 1111按位与,此时拿到了退出码15

11.png12.png

父进程可以通过子进程的退出码来查看子进程的具体情况


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


操作系统如何杀掉?


本质是通过发送信号的方式!下图都是信号

13.png14.png15.png16.png


其实没有0信号,如果收到的信号是0,说明我们的进程是正常跑完的,测试异常

17.png18.png

8号信号是,8号信号是浮点数错误,这里的0代表退出码无意义,因为我们的退出码是15


如果我们用kill -9 ,子进程也会收到9号信号


程序异常,不仅是内部代码有问题,也可能是外部杀掉的


总结


父进程通过wait/waitpid可以拿到子进程的退出结果(退出码和推出信号), 不能用全局变量来代替退出码


如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。


使用wait或waitpid,当子进程退出的时候,父进程还存在,从某种意义上说wait/waitpid可以让进程退出具有一定的顺序性,这让设置的目的让父进程将来可以有更多的收尾工作


僵尸进程:至少要保留该进程的PCB信息,task_struct里面保留了任何进程退出时的退出结果信息。wait和waitpid本质是读取子进程的task_struct的结构

19.png

wait和waitpid有权利访问是task_struct(内核数据结构)因为wait和waitpid是系统调用的


waitpid详解


20.png

如果用位运算获取进程编号和退出码会比较麻烦,系统给我们提供了宏,我们可直接拿来使用


WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)

WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

21.png22.png

当waitpid()第三个参数设置为WNOHANG的时候 ,父进程就成了非阻塞状态


Linux是用C语言写的->系统调用接口->OS自己提供的接口->就是C语言函数->系统提供的一般大写的标记位如WNOHANG,其实就是宏


#define WNOHANG 1


WNOHANG其实就是wait no hang(夯住了),夯住了在系统层面上就是这个进程没被CPU调度。此时要么是在阻塞队列中,要么等待被调度。


非阻塞等待:如果父进程检测子进程的退出状态,发现子进程没有退出,我们的父进程通过调用waitpid来进行等待,如果子进程没有退出,waitpid这个系统调用,立马返回。


阻塞等待一般都是在内核中阻塞,阻塞队列会等待被唤醒,一般一个进程被阻塞了就会被切换


伪代码:

23.png

非阻塞等待会有一个基于非阻塞调用的轮询检测方案(就是不停的进行访问,当条件不满足时会做其他事情)


#include <iostream>
#include <vector>
#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.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;
}

24.png


相关文章
|
5月前
|
监控 Linux 应用服务中间件
linux查看日志文件tail -f用法
在 Linux 中,查看和监控日志文件是系统管理员和开发者常用的操作之一。tail 命令就是用来查看文件内容的,它默认显示文件的最后部分。tail -f 是 tail 命令的一个非常有用的选项,用于实时查看和跟踪日志文件的更新,尤其是在监控运行中的服务时非常有用。
767 0
|
6月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
140 0
|
6月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
211 0
|
6月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
137 0
|
Shell 程序员 Linux
|
3月前
|
Linux 应用服务中间件 Shell
二、Linux文本处理与文件操作核心命令
熟悉了Linux的基本“行走”后,就该拿起真正的“工具”干活了。用grep这个“放大镜”在文件里搜索内容,用find这个“探测器”在系统中寻找文件,再用tar把东西打包带走。最关键的是要学会使用管道符|,它像一条流水线,能把这些命令串联起来,让简单工具组合出强大的功能,比如 ps -ef | grep 'nginx' 就能快速找出nginx进程。
485 1
二、Linux文本处理与文件操作核心命令
|
3月前
|
Linux
linux命令—stat
`stat` 是 Linux 系统中用于查看文件或文件系统详细状态信息的命令。相比 `ls -l`,它提供更全面的信息,包括文件大小、权限、所有者、时间戳(最后访问、修改、状态变更时间)、inode 号、设备信息等。其常用选项包括 `-f` 查看文件系统状态、`-t` 以简洁格式输出、`-L` 跟踪符号链接,以及 `-c` 或 `--format` 自定义输出格式。通过这些选项,用户可以灵活获取所需信息,适用于系统调试、权限检查、磁盘管理等场景。
329 137
|
3月前
|
安全 Ubuntu Unix
一、初识 Linux 与基本命令
玩转Linux命令行,就像探索一座新城市。首先要熟悉它的“地图”,也就是/根目录下/etc(放配置)、/home(住家)这些核心区域。然后掌握几个“生存口令”:用ls看周围,cd去别处,mkdir建新房,cp/mv搬东西,再用cat或tail看文件内容。最后,别忘了随时按Tab键,它能帮你自动补全命令和路径,是提高效率的第一神器。
771 57
|
2月前
|
存储 安全 Linux
Linux卡在emergency mode怎么办?xfs_repair 命令轻松解决
Linux虚拟机遇紧急模式?别慌!多因磁盘挂载失败。本文教你通过日志定位问题,用`xfs_repair`等工具修复文件系统,三步快速恢复。掌握查日志、修磁盘、验重启,轻松应对紧急模式,保障系统稳定运行。
573 2
|
3月前
|
缓存 监控 Linux
Linux内存问题排查命令详解
Linux服务器卡顿?可能是内存问题。掌握free、vmstat、sar三大命令,快速排查内存使用情况。free查看实时内存,vmstat诊断系统整体性能瓶颈,sar实现长期监控,三者结合,高效定位并解决内存问题。
352 0
Linux内存问题排查命令详解