一、进程终止

代码运行完毕,结果正确。

代码运行完毕,结果不正确。

代码异常终止。(退出码无意义)

子进程也是进程->父进程创建->子进程执行的结果

进程一旦出现异常,一般是进程收到了信号。

return 本质就是将返回值放到寄存器。

即使不写return 0,返回值也是0的原因:无非就是少了将 ret 放到寄存器这一句指令,寄存器默认存的值为0,拿到的程序退出码(语言层面)还是0。

int main(); main函数返回值,通常表明程序的执行情况。

echo $? 打印最近一个程序退出时的退出码->进程退出码->写到进程的task_struct内部的

结果正确,退出码为0。

结果不正确,退出码非0,{1,2,3,...},为了通过错误码得知错误信息。

strerror,<string.h>,根据错误码返回对应的字符串。

[0,133],一共有134种错误码。

errno:整形变量,<errno.h>,记录上一个错误码。

测试代码:

#include <stdio.h>    
#include <string.h>    
#include <errno.h>    
    
int main()    
{    
    FILE* pf = fopen("test.txt","r");    
    if(pf==NULL)    
    {                                                                                                                                                
        printf("strerror:%s\n",strerror(errno));    
        return errno;                               
    }                                               
    fclose(pf);                                     
    return 0;                                       
}   

进程退出的方式(重点) 

main函数结束,表示进程结束。

其他函数结束,只表示自己函数调用完成,返回。

main:(return)

exit:(status)

任何地方调用exit,表示进程结束!并返回给父进程,子进程的退出码。

exit(C)vs _exit(系统):

_exit()和exit()是上下层的关系,exit()封装了系统调用_exit()。

我们之前讨论的缓冲区。应该在哪里?

一定不是操作系统内部的缓冲区,否则_exit退出会导致内存泄漏。

库缓冲区,C语言提供的缓冲区。

易错点1:

fork创建子进程就是在内核中通过调用clone实现。 

易错点2:

fork函数功能是通过复制父进程,创建一个新的子进程。

信号相关信息各自独立,并不会复制。 

易错点3:

不算 main 这个进程自身,到底创建了多少个进程啊?

int main(int argc, char* argv[])
{
   fork();
   fork() && fork() || fork();
   fork();
}

19个。

二、进程等待(重点) 

进程等待的必要性 

为什么要等待?

1)防止造成“僵尸进程”的问题,进而导致内存泄漏。

2)父进程派给子进程的任务完成的情况(结果对,结果不对,异常退出),父进程要知道。

总结:父进程通过进程等待的方式,回收子进程资源(重要)获取子进程退出信息 。

进程等待的方式

wait,<sys/types.h>,<sys/wait.h>

pid_t wait(int *status); status为输出型参数,输出等待子进程的进程退出码。

等待成功,返回子进程pid,等待失败返回-1。

wait是阻塞调用,父进程会一直阻塞。

waitpid的使用(重点)

pid_t waitpid(pid_t pid, int *status, int options);

pid为指定等待子进程的pid(特别的,pid为-1,等待任意一个子进程,pid<-1,等这个pid绝对值的子进程)

status为输出型参数,输出等待子进程的进程状态码

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

options为选项,可以选择等待的方式为 阻塞等待 或 非阻塞等待。(默认为0,阻塞等待)

WNOHANG: pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。(非阻塞等待)
返回值:
返回值大于0,等待成功,返回值为回收的子进程pid
返回值等于0,表示还有子进程需要回收,但这次没有等待成功(WNOHANG)
返回值小于0,表示没有子进程了。

waitpid(-1,NULL,0); 等同于 wait(NULL);

其中NULL,表示父进程不关心子进程退出信息。

测试代码如下:

#include <stdio.h>    
#include <stdlib.h>    
#include <sys/types.h>    
#include <sys/wait.h>    
#include <unistd.h>    
    
int main()    
{    
    pid_t pid = fork();    
    if(pid==0)    
    {    
        //子进程    
        int cnt = 5;    
        while(cnt--)    
        {    
            sleep(1);    
            printf("子进程pid:%d\n",getpid());    
        }    
        exit(1);    
    }    
    //pid_t ret = wait(NULL);    
    int status;    
    pid_t ret = waitpid(-1,&status,0);    
    printf("等待的子进程pid:%d,status:%d\n",ret,status);    
    return 0;    
}    

POSIX标准定义
status的位操作需严格遵循从0开始的编号方式。例如:

终止信号编号:存储在第0~7位(即低8位),通过 status & 0x7F 提取。

退出码:存储在第8~15位(即高8位),通过 (status >> 8) & 0xFF 提取。

core dump标志:第7位(从0开始计数,即 status & 0x80 的非零值)。

异常判断:

1)低7个比特位,0

2)一旦低7个比特位不为0,异常退出,退出码无意义!

main函数返回值要写到PCB里,PCB里有对应的exit_code退出码,exit_signal信号码,父进程通过系统调用waitpid获取子进程的状态码,getpid()和getppid()同理,PCB里有存。

为什么?

因为子进程结束后,进入僵尸状态,等待父进程回收,其中进程地址空间、代码和数据以及页表可以释放,但是子进程的PCB(task_struct)需要保留,

WNOHANG 子进程还没退出就立即返回,非阻塞调用。

返回值:

大于0:等待结束

等于0:调用结束,但子进程没有退出

小于0:等待失败

非阻塞调用:可以让等待者,做自己的事情,充分利用资源。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐