Linux(4)(上)

一.进程创建

1.1 fork初识

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

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

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


当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程,看如下程序。

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
    pid_t pid;
    printf("Before: pid is %d\n", getpid());
    if ((pid = fork()) == -1)
    {
        perror("fork()"),
        exit(1);
    }
    //这里会被子进程和父进程各执行一次
    printf("After:pid is %d, fork return %d\n", getpid(), pid);
    sleep(1);
    return 0;
}
//执行结果
/*
Before: pid is 43676
After:pid is 43676(父进程pid), fork return 43677//fork的返回值
After:pid is 43677(子进程pid), fork return 0//fork的返回值
*/

fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。

fork函数返回值:

  • 子进程返回0,
  • 父进程返回的是子进程的pid

fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。

  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数

fork调用失败的原因

  • 系统中有太多的进程

  • 实际用户的进程数超过了限制

1.2写时拷贝

通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副 本。具体见下图:

二.进程终止

进程退出场景

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

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

  • 代码异常终止

退出知识补充

  • 退出场景1和2是采用退出码(return值)来判定,可以用echo $?,来查看最近一次进程的退出码

  • 谁会关心退出码呢,其实就是该进程的父进程,父进程之所以要关心其实就是要反映给用户

  • 退出场景3此时我们不在关心退出码,而是关心异常中断后发出的信号(这个后面会讲是什么)

进程常见退出方法

正常终止:

  • 从main的return返回

  • 调用exit

  • _exit

异常退出:

  • ctrl + c

正常终止补充

return和exit的区别

  • return:表示当前函数返回

  • exit:表示进程返回

exit和_exit的区别

  • 前者是库函数后者是系统调用

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

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

因此返回只推荐使用return和exit。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。

三.进程等待

进程等待必要性

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

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

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

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

了解进程等待

  1. 是什么

    通过系统调用wait/waitpid,来进行对子进程进行状态检查和回收的功能

  2. 为什么

    解决僵尸进程,获取子进程的退出情况

进程等待的方法

1. wait方法

cpp 复制代码
// 等待任意一个子进程终止,并回收其资源
#include <sys/wait.h>

pid_t wait(int *wstatus);

// 参数:
//   wstatus - 指向整型的指针,用于接收子进程的退出状态(可为 NULL)

// 返回值:
//   成功:返回已终止子进程的 PID
//   出错(如当前无子进程):返回 -1,并设置 errno
  • wait是等待任意一个子进程退出,然后对其进行回收,随后会立即返回

  • 当子进程不退出的时候,那么父进程将保持这个状态直至子进程退出,这个情况叫做阻塞状态

  • 如果不存在子进程,则报错返回

2. waitpid方法

cpp 复制代码
// 等待指定子进程状态变化(如退出、暂停),并可选择是否阻塞
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
//返回值:
    //当正常返回的时候waitpid返回收集到的子进程的进程Pid;
    //如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
    //如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
//参数:
//    pid://只能等待当前进程的子进程,如果不是当前进程的子进程会报错

//        Pid=-1,等待任意一个子进程。与wait等效
//        Pid>0.等待与Pid相等的子进程。
        //只讲解这两个重要的

//    status:
//    	NULL://不关心子进程返回
//        &status://想获取退出码

//    options:
//    	0://阻塞方式

        //若Pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。
        //若正常结束,则返回该子进程的Pid。
//        WNOHANG://非阻塞
  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。

阻塞你可以理解,它不来你不走(父进程卡住);非阻塞可以理解,看到它一起走(返回子进程的Pid),没看到我自己走(父进程返回0)。

获取子进程status

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

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

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

  • 所以查看终止信号发现最大值是64不就刚好是用了七位

  • 因此查看status是否有异常**(status&0x7F(0111 1111))**

  • 看status的退出状态**((status>>8)&0xFF(1111 1111)))**

  • 不过不推荐上面两种写法,只推荐下面的写法

cpp 复制代码
WIFEXITED:   //若为正常终止子进程返回的状态,则为真


WEXITSTATUS: //若WIFEXITED非零,提取子进程退出码
        

WTERMSIG: //WIFEXITED非零,用于获取杀死子进程的信号编号

if (WIFEXITED(status))
{
    printf("正常退出,code = %d\n", WEXITSTATUS(status));
}
else if (WIFSIGNALED(status))
{
    printf("被信号 %d 杀死\n", WTERMSIG(status));
}

非阻塞轮询

  • 非阻塞:父进程不会等待waitpid的返回值,而是可以执行下面的代码

  • 轮询:代码采用循环,让父进程多次询问其子进程的返回值

今天的知识都是非常非常非常重点的,重要的事情说三遍,因此一定一定要记牢,要清楚进程创建用什么,进程退出用什么,进程等待用什么!

相关推荐
一个平凡而乐于分享的小比特2 小时前
Linux/Unix系统主流脚本语言--Bash语言
linux·bash·脚本语言
代码总长两年半2 小时前
Linux---配置编程环境VSCode
linux·运维·服务器
Tipriest_2 小时前
Linux 桌面(Desktop)图标的生成原理/执行流程/自己编写桌面图标的方法
linux·运维·服务器
G_H_S_3_2 小时前
【网络运维】KVM基础使用
linux·运维·网络·kvm
Lynnxiaowen2 小时前
今天我们利用Jenkins插件调用ansible
linux·运维·ansible·jenkins
_OP_CHEN2 小时前
【Linux系统编程】(十七)揭秘 Linux 进程创建与终止:从 fork 到 exit 的底层逻辑全解析
linux·运维·服务器·操作系统·shell·进程·进程创建与终止
草莓熊Lotso2 小时前
Makefile 完全指南:从入门到工程化,自动化构建不再难
linux·运维·服务器·人工智能·经验分享·后端·自动化
代码游侠2 小时前
学习笔记——网络基础
linux·c语言·网络·笔记·学习·算法
ElfBoard3 小时前
ElfBoard技术贴|如何在【RK3588】ELF 2开发板实现GPIO功能复用
linux·人工智能·单片机·嵌入式硬件·物联网·机器人