linux进程(下)

目录

[一、进程创建:fork 函数](#一、进程创建:fork 函数)

[1.1 fork 函数初识](#1.1 fork 函数初识)

[​1.2 fork 内核执行流程](#1.2 fork 内核执行流程)

[1.3 写时拷贝(Copy-On-Write)](#1.3 写时拷贝(Copy-On-Write))

[1.4 fork 常规用法](#1.4 fork 常规用法)

[1.5 fork 调用失败原因](#1.5 fork 调用失败原因)

二、进程终止:退出场景与三种退出方式

[2.1 进程退出三大场景](#2.1 进程退出三大场景)

[2.2 进程正常退出方法](#2.2 进程正常退出方法)

[2.3 异常退出](#2.3 异常退出)

[2.4 退出码与 ?](#2.4 退出码与 ?)

三、进程等待:避免僵尸进程,回收子进程资源

[3.1 为什么需要进程等待?](#3.1 为什么需要进程等待?)

[3.2 进程等待两个核心](#3.2 进程等待两个核心)

[(1)wait 函数](#(1)wait 函数)

[(2)waitpid 函数](#(2)waitpid 函数)

[3.3 status 位图解析](#3.3 status 位图解析)

[3.4 阻塞 vs 非阻塞等待](#3.4 阻塞 vs 非阻塞等待)

[四、进程程序替换:exec 函数族](#四、进程程序替换:exec 函数族)

[4.1 替换原理](#4.1 替换原理)

[4.2 exec 函数族 6 个接口](#4.2 exec 函数族 6 个接口)

[4.3 命名规律(一秒记住)](#4.3 命名规律(一秒记住))

[4.4 函数特性](#4.4 函数特性)

[五、综合实践:动手实现微型 Shell](#五、综合实践:动手实现微型 Shell)

[5.1 Shell 底层原理](#5.1 Shell 底层原理)

[5.2 微型 Shell 代码实现](#5.2 微型 Shell 代码实现)

[Linux 进程控制 面试题](#Linux 进程控制 面试题)

[1. fork 为什么给子进程返回 0,父进程返回子 PID?](#1. fork 为什么给子进程返回 0,父进程返回子 PID?)

[2. 什么是写时拷贝(COW)?好处是什么?](#2. 什么是写时拷贝(COW)?好处是什么?)

[3. exit 和 _exit 有什么区别?](#3. exit 和 _exit 有什么区别?)

[4. 什么是僵尸进程?危害?如何解决?](#4. 什么是僵尸进程?危害?如何解决?)

[5. 进程等待的作用是什么?](#5. 进程等待的作用是什么?)

[6. wait 和 waitpid 区别?](#6. wait 和 waitpid 区别?)

[7. exec 函数族调用成功为什么不返回?](#7. exec 函数族调用成功为什么不返回?)

[8. Shell 底层是怎么执行命令的?](#8. Shell 底层是怎么执行命令的?)


上篇博客我们讲解了进程的一下额基础概念,作为Linux 系统最核心的抽象概念,一切运行中的程序都以进程的形式存在。掌握进程创建、终止、等待、程序替换。

一、进程创建:fork 函数

1.1 fork 函数初识

fork 是 Linux 中创建子进程的核心系统调用,从已存在进程(父进程)中克隆出新进程(子进程)。

复制代码
#include <unistd.h>
pid_t fork(void);

返回值规则

  • 子进程中返回 0
  • 父进程中返回 子进程 PID
  • 创建失败返回 -1

1.2 fork 内核执行流程

当进程调用 fork 后,内核会完成四件事:

  1. 为子进程分配新的内存块与内核数据结构(PCB)
  2. 拷贝父进程部分数据结构到子进程
  3. 将子进程加入系统进程列表
  4. fork 返回,由调度器决定父子谁先执行
    关键结论:fork 之前父进程单独执行,fork 之后父子两个执行流分开运行,谁先执行由调度器决定

1.3 写时拷贝(Copy-On-Write)

fork 后父子进程默认代码共享,数据在不修改时也共享。

  • 任意一方尝试写入数据时,操作系统会以写时拷贝的方式,为写入方复制一份独立副本
  • 优势:节约内存,提升 fork 执行效率

1.4 fork 常规用法

  1. 父进程复制自身,让父子执行不同代码分支(如服务端父进程监听,子进程处理请求)
  2. 子进程 fork 后调用 exec 执行全新程序

1.5 fork 调用失败原因

  • 系统中进程总数达到上限
  • 当前用户可创建的进程数超过限制

二、进程终止:退出场景与三种退出方式

2.1 进程退出三大场景

  1. 代码运行完毕,结果正确
  2. 代码运行完毕,结果不正确
  3. 代码异常终止(如段错误、被信号杀死)

2.2 进程正常退出方法

Linux 提供三种正常退出方式,行为差异极大:

退出方式 核心行为 特点
return main 函数中 return n 等价于 exit(n),最常用
exit 先清理,再终止 执行用户清理函数、冲刷缓冲区、关闭流
_exit 直接终止进程 不处理缓冲区,不做任何清理

代码示例:exit 与 _exit 区别

cpp 复制代码
// exit:会输出 hello
int main() {
    printf("hello");
    exit(0);
}

// _exit:无输出,缓冲区未冲刷
int main() {
    printf("hello");
    _exit(0);
}

2.3 异常退出

  • 终端:Ctrl + C
  • 信号终止(如 kill -9 PID

2.4 退出码与 $?

  • 进程退出码仅低 8 位有效,范围 0~255
  • 终端使用 echo $? 查看上一个进程的退出码

三、进程等待:避免僵尸进程,回收子进程资源

3.1 为什么需要进程等待?

  1. 子进程退出后,父进程不回收会产生僵尸进程,造成内存泄漏
  2. 僵尸进程无法被 kill -9 杀死(进程已死,仅保留 PCB)
  3. 父进程需要获取子进程的退出结果(成功 / 失败 / 异常)

3.2 进程等待两个核心

(1)wait 函数
cpp 复制代码
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int* status);
  • 作用:阻塞等待任意一个子进程退出
  • 返回值:成功返回子进程 PID,失败返回 -1
  • status:输出型参数,获取子进程退出状态,不关心可传 NULL
(2)waitpid 函数
cpp 复制代码
pid_t waitpid(pid_t pid, int *status, int options);

参数说明

  • pid = -1:等待任意子进程(等价于 wait)
  • pid > 0:等待指定 PID 的子进程
  • options = WNOHANG非阻塞等待,子进程未退出返回 0

返回值

  • 正常返回:子进程 PID
  • 非阻塞模式无子进程退出:返回 0
  • 调用失败:返回 -1

3.3 status 位图解析

status 不能当作普通整数,是低 16 位位图

  • 正常终止:高 8 位存储退出状态码
  • 异常终止:低 7 位存储终止信号

宏函数使用

  • WIFEXITED(status):判断是否正常退出
  • WEXITSTATUS(status):提取正常退出码

3.4 阻塞 vs 非阻塞等待

  • 阻塞等待:父进程挂起,直到子进程退出,简单但无法处理其他任务
  • 非阻塞等待:父进程轮询检查,子进程未退出可执行其他逻辑

四、进程程序替换:exec 函数族

4.1 替换原理

fork 创建的子进程默认和父进程执行相同代码,使用 exec 函数族可让进程执行全新的程序

  • 调用 exec 后,不创建新进程,PID 不变
  • 进程用户空间的代码、数据被新程序完全替换

4.2 exec 函数族 6 个接口

cpp 复制代码
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

4.3 命名规律(一秒记住)

  • l:list,参数以列表形式传递
  • v:vector,参数以数组形式传递
  • p:自动搜索 PATH 环境变量,无需写全程序路径
  • e:自定义环境变量,不使用系统默认环境变量

核心:只有 execve 是真正的系统调用,其余 5 个都是库函数,最终都会调用 execve。

4.4 函数特性

  • 调用成功:不返回,直接执行新程序
  • 调用失败:返回 -1(只有出错返回值,没有成功返回值)

五、综合实践:动手实现微型 Shell

5.1 Shell 底层原理

我们日常使用的 bash,本质就是一个进程控制程序,执行逻辑:

  1. 读取用户输入的命令
  2. 解析命令为参数列表
  3. fork 创建子进程
  4. 子进程调用 exec 执行命令
  5. 父进程 wait 等待子进程退出,回到命令行

5.2 微型 Shell 代码实现

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

#define MAX_CMD 1024
char command[MAX_CMD];

// 打印命令行提示符,获取输入
int do_face() {
    memset(command, 0, MAX_CMD);
    printf("minishell$ ");
    fflush(stdout);
    if (scanf("%[^\n]%*c", command) == 0) {
        getchar();
        return -1;
    }
    return 0;
}

// 解析命令为参数列表
char **do_parse(char *buff) {
    static char *argv[32];
    int argc = 0;
    char *ptr = buff;

    while (*ptr != '\0') {
        if (!isspace(*ptr)) {
            argv[argc++] = ptr;
            while (!isspace(*ptr) && *ptr != '\0') ptr++;
            *ptr = '\0';
        }
        ptr++;
    }
    argv[argc] = NULL;
    return argv;
}

// 创建子进程并执行命令
int do_exec(char *buff) {
    char **argv = do_parse(buff);
    if (argv[0] == NULL) return -1;

    pid_t pid = fork();
    if (pid == 0) {
        execvp(argv[0], argv);
        exit(-1);
    } else {
        waitpid(pid, NULL, 0);
    }
    return 0;
}

int main() {
    while (1) {
        if (do_face() < 0) continue;
        do_exec(command);
    }
    return 0;
}

Linux 进程控制 面试题

1. fork 为什么给子进程返回 0,父进程返回子 PID?

答案

  • 子进程只需要知道自己是子进程,不需要知道父 PID
  • 父进程需要管理多个子进程,必须拿到 PID 才能区分、等待、回收

2. 什么是写时拷贝(COW)?好处是什么?

答案 :fork 后父子进程共享同一份数据和代码,只有在写入时才复制一份

优点:

  • 节省内存
  • 让 fork 执行更快
  • 避免不必要的拷贝

3. exit 和 _exit 有什么区别?

答案

  • exit:会冲刷缓冲区、关闭文件、执行清理函数,再调用 _exit
  • _exit:直接进入内核终止进程,不处理缓冲区

4. 什么是僵尸进程?危害?如何解决?

答案

  • 子进程退出、父进程没 wait,就会变成僵尸进程
  • 危害:占用 PCB,造成内存泄漏
  • 解决:父进程调用 wait /waitpid 回收

5. 进程等待的作用是什么?

答案

  1. 防止僵尸进程
  2. 回收子进程资源
  3. 获取子进程退出状态

6. wait 和 waitpid 区别?

答案

  • wait:阻塞等待任意子进程
  • waitpid:可以指定 PID、支持非阻塞等待(WNOHANG),更灵活

7. exec 函数族调用成功为什么不返回?

答案 :因为进程的代码段、数据段已经被完全替换,原来的代码已经不存在了。

8. Shell 底层是怎么执行命令的?

答案

  1. 读取命令
  2. 解析命令
  3. fork 子进程
  4. exec 替换执行命令
  5. 父进程 wait 等待子进程结束
相关推荐
瀚高PG实验室2 小时前
瀚高数据库安全版4.5.8系列使用pg_cron定时任务
服务器·数据库·瀚高数据库
汉克老师2 小时前
GESP2023年12月认证C++三级( 第一部分选择题(1-8))
c++·string·字符数组·gesp三级·gesp3级
俺不要写代码2 小时前
lambda表达式理解
c++·算法
澈2072 小时前
动态内存管理:从基础到实战详解
c++·算法
想唱rap2 小时前
C++11之包装器
服务器·开发语言·c++·算法·ubuntu
wuminyu2 小时前
专家视角看Java的线程是如何run起来的过程
java·linux·c语言·jvm·c++
REDcker2 小时前
C++ std::move实现原理与vector扩容移动语义
开发语言·c++·c
脱氧核糖核酸__2 小时前
LeetCode热题100——48.旋转图像(题解+答案+要点)
c++·算法·leetcode
宵时待雨2 小时前
优选算法专题2:滑动窗口
数据结构·c++·笔记·算法