Linux fork子进程,进程退出与等待


title:
date: 2024-07-07 12:57:17
tags: fork Linux
cover: https://picbed0521.oss-cn-shanghai.aliyuncs.com/blogpic/PixPin_2024-07-25_22-26-59.png


原文见我的网站: www.supdriver.top

认识fork()

头文件<unistd.h>提供的fork()函数用于从已有的原进程创建一个新的子进程,而原进程在关系式称为父进程

fork()的返回值

#include <unistd.h>
pid_t id = fork();

父子进程中fork()函数的返回值(此处用变量id储存)是不同的:

父进程id的值为子进程的PID,其值>0;子进程id值固定为0

  • id > 0 父进程
  • id == 0 子进程
  • id < 0 fork()失败

分流

利用父子进程中fork()返回值的不同,可以用if...else...进行分流,让父子进程执行不同的代码

fork()的过程

进程调用fork,当控制转移到内核中的fork代码后,内核

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

当一个进程调用fork之后,就有一对二进制代码相同的父子进程。而且它们都运行到相同的地方。但每个进程都可以独立地继续运行代码,并按代码分流至不同的代码段

但这两个进程谁先执行完全由调度器决定,而谁先结束,则由实际执行情况和调度器决定

拷贝的过程 与 写时拷贝

fork时,子进程会将父进程虚拟内存的内容都复制一份在自己的虚拟内存中,但通过页表,二者映射到了同一块物理内存的区域,这样在二者都没有写入行为时,减少了物理内存中冗余的拷贝行为,有效提高了运行效率

但当父子进程中有一方发生写入行为时,就会触发写时拷贝,此时物理内存中发生拷贝行为,但只拷贝进程映射的数据段,而由于不发生进程替换时父子进程的代码段一定相同,物理内存中,代码段映射部分并不发生拷贝

进程退出

进程退出的场景

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

进程常见退出方法

  • main函数返回
  • exit退出
  • _exit退出

异常退出: ctrl + c

查看退出码

在终端使用命令echo $?可以查看退出码

尽管子进程返回的是int,父进程只取退出码的最低8位,所以以下三种情况

  • main函数返回-1
  • exit(-1)
  • _exit(-1)

在终端输echo $?可得退出码255

exit_eixt 辨析

main函数返回就不惜说了,来辨析一下头文件<stdlib.h>提供的exit_exit函数

_exit()

_exit直接终止进程并返回状态码,而不会执行用户定义的清理函数,也不会清理缓冲区

exit()

exit实际上最后也会调用_exit,但它会先执行一系列善后工作,顺序如下:

  1. 执行用户通过 atexit或on_exit定义的清理函数
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit

return退出: 执行return n等同于执行exit(n)

进程等待

进程等待的必要性

  • 子进程退出后需要父进程来回收僵尸进程,防止产生其引发内存泄漏等问题
  • 僵尸进程难以处理,kill -9也清理不掉
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

进程等待的方法

wait方法

#include<sys/types.h>
#include<sys/wait.h>

pid_t wait(int*status);

参数:显然status是输出型参数,获取子进程的退出状态,若不关心,可传参NULL

返回值: 若成功,则返回被等待进程的pid,若失败,则返回-1

waitpid方法

返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:
pid
pid == -1,等待任一个子进程。与wait等效。
pid > 0.等待其进程ID与pid相等的子进程。
status:
输出型参数传参&status: 将子进程的状态码存入status
以下两个宏函数用于处理状态码:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
0: 阻塞等待指定pid的进程

阻塞等待

option == 0waitpid采用阻塞等待,父进程会阻塞等待到子进程退出

示例代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main()
{
    int status;
    pid_t id = fork();
    if(id == 0)
    {
        for(int i = 1;i<=5;i++)
        {
            printf("child running [%d]s\n",i);
            sleep(1);
        }
        printf("child exited\n");
        exit(0);
    }
    else
    {
        waitpid(id,&status,0);
        if(WIFEXITED(status))
            printf("father wait success, exit code: %d\n",WEXITSTATUS(status));
        else
            printf("child failed to exit\n");
    }
    return 1;
}
非阻塞轮询

option == WNOHANG时,waitpid采用非阻塞等待,若等不到子进程退出,就会继续执行后面的代码,所以一般加上while等循环用于轮询,二者共同构成非阻塞轮询

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

//非阻塞轮询
int main()
{
    int status;
    pid_t id = fork();
    if(id == 0)
    {
        sleep(5);
        printf("child exited\n");
        exit(0);
    }
    else
    {
        while(1)//轮询
        {
            pid_t rid = waitpid(id,&status,WNOHANG);
            if(rid > 0)
            {
                if(WIFEXITED(status))
                    printf("father wait success, exit code: %d\n",WEXITSTATUS(status));
                else
                    printf("child failed to exit\n");

                break;
            }
            else if(rid == 0)
            {
                printf("等待子进程中...\n");
                sleep(1);
            }
            else//rid<0
            {
                printf("wait failed\n");
                break;
            }
        }
    }
    return 1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值