CSAPP–第八章–异常(下下)

Aki 发布于 2023-01-14 238 次阅读


回收子进程

当一个进程由于某种原因终止时,内核并不是立即把它从系统中删除,它仍然消耗系统资源,保持在一种已终止的状态中,这种进程被称作僵死进程。

当父进程执行回收,内核将子进程的退出状态传递给父进程,抛弃已终止的子进程后,此进程才不存在了。

如果父进程没有回收它的僵死进程就终止了,那么内核会安排init进程(PID=1)去回收它们。长时间运行的进程应当主动回收它们的僵死进程。

我们可以调用waitpid函数来等待它的子进程终止或者停止。默认情况下(options=0时),waitpid挂起调用进程,直到它的等待集合一个子进程终止。如果等待集合中的一个进程在刚调用的时候就已经终止了,那么waitpid就立即返回。waitpid返回的值是导致其返回的已终止的子进程的PID。

pid_t waitpid(pid_t pid, int &status, int options)
  • pid
    • pid > 0:等待集合是一个单独的PID=pid的进程。
    • pid = -1:所有子进程。
    • pid < -1:|pid|进程组中任何子进程。
    • pid = 0:当前进程同一进程组中任何子进程。
  • option
    • WNOHANG:若无子进程结束也会返回(返回值为0),不会挂起当前进程。
    • WUNTRACED:若等待集合中一个进程被停止也返回。
    • WCONTINUED:若等待集合中一个进程收到SIGCONT从停止重新开始也返回。

注意,程序不会按照特定的顺序回收子进程。

wait()函数:

我们应当知道的是,在用fork创建子进程后,父子进程的执行的先后顺序是不定的,这时,我们可以用wait函数,wait()会暂停当前进程的执行,直到有信号到来或者子进程结束。总的来说,wait()的作用就是阻塞父进程,等待子进程。

我们是在父进程中使用wait(),可以不让父进程先于其产生的子进程结束,因为如果父进程结束了,而子进程还没有结束,那这个进程就会变成一个“孤儿进程”,他会被init进程收养(进程号为1),并由init进程对它们完成状态收集工作。

当然如果子进程结束了,但是父进程没有调用wait或者waitpid(),那么子进程的资源就无法得到收集释放,这时候的子进程被称为“僵尸进程”,会占用系通资源,是非常不好的,危害很大。

函数原型:pid_t wait(int *status)

函数参数:

①status作为一个整形值,用来保存子进程退出时的状态;

②如果status为NULL,表示忽略子进程退出时的状态;

③如果status不为空,wait函数会将子进程退出的状态存入status中,另外,子进程退出时的状态可以通过linux中的特定的宏(macro)来进一步测定退出状态。

返回值:   

成功,返回子进程的进程号;失败,返回-1

 加载并运行程序

int execve(char *filename, char *argv[], char *envp[])

execve函数加载并运行可执行目标文件filename,且带参数列表argv和环境变量envp

execve函数覆盖当前进程的代码、数据、栈,有相同的PID,继承已打开的文件描述符和信号上下文。

execve函数调用一次从不返回,除非出现错误例如找不到filename,才会返回到调用程序。

execve调用失败返回-1,成功不返回。

#include<iostream>
using namespace std;
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdio.h>

int main()
{
    fork();
    char* argv[] = {"/usr/bin/ls","-lh","/root",nullptr};
    char*envp[] = {0,nullptr};
    if(execve(argv[0],argv,envp) < 0)
    {
        cout << "error" << endl;
        exit(0);
    }

    return 0;
}

上述代码执行的功能是创建一个子进程,然后子进程和父进程都使用命令 /usr/bin/ls -lh /root