当前位置: 首页 > article >正文

Linux进程创建与终止

目录

一、进程创建

1. fork函数初识

2. fork函数返回值的意义

3. 写时拷贝(Copy-On-Write, COW)

4.fork的常规用法

5. fork调用失败的原因

二、进程终止

1.  终止的本质

2. main函数的return返回值

3. 进程退出状态的含义

4. 父进程为什么会得到子进程的退出码?

5. 异常退出与信号

6.  进程常见终止情况

7. exit 和 _exit 的区别

8. return与exit的区别


一、进程创建

1. fork函数初识

        在Linux系统中,fork()函数是创建进程的核心工具。它通过复制现有进程(父进程)来创建新进程(子进程)。

函数声明:

#include <unistd.h>

pid_t fork(void);

返回值:

  • 子进程中返回0

  • 父进程中返回子进程的PID(进程ID)。

  • 如果出错,返回-1


fork函数的工作原理:

进程调用fork时,当控制转移到内核中的fork代码后,内核会执行以下操作:

  1. 分配新的内存块和内核数据结构给子进程。

  2. 将父进程的部分数据结构内容拷贝至子进程。

  3. 将子进程添加到系统进程列表中。

  4. fork返回后,调度器开始调度。

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

示例代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    pid_t pid;
    printf("Before: pid is %d\n", getpid());  // 打印当前进程ID
    if ((pid = fork()) == -1) 
    {
        perror("fork()");  // 出错处理
        exit(1);
    }
    printf("After: pid is %d, fork return %d\n", getpid(), pid);
    sleep(1);  // 延迟1秒

    return 0;
}

运行结果:

这里看到了三行输出,一行Before,两行After。进程61200先打印Before消息,然后它又打印After。另一个After消息是由61201打印的。我们注意到进程61201没有打印Before,为什么呢?如下图所示:

分析:

  • 父进程(PID=61200)先打印Before消息,然后调用fork创建子进程(PID=61201)。

  • 父进程和子进程各自打印After消息。子进程没有打印Before消息,因为它是在fork之后才创建的。

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


经典面试题连续调用两次fork会创建多少进程?

fork();
fork();

答案:总共会创建3个子进程(2^n-1公式)

2. fork函数返回值的意义

  • 子进程返回0:子进程需要知道自己是子进程,返回0是一个约定。

  • 父进程返回子进程的PID:父进程需要管理子进程(如等待子进程),PID是唯一标识符。

PID管理示意图

父进程
└── 子进程1(PID=100)
    └── 子进程2(PID=101)

3. 写时拷贝(Copy-On-Write, COW)

        父子进程在fork之后,虽然共享代码段和数据段,但在写入时会触发写时拷贝机制。即,当父子进程中的任意一方试图修改共享数据时,系统会为修改方创建一份独立的副本,以避免数据冲突。具体见下图:

内存管理机制

  1. 初始时父子共享物理内存页

  2. 当任一进程尝试写入时触发缺页异常

  3. 内核复制该内存页供修改进程专用

示例验证

int global_val = 100;

int main()
{
    int local_val = 200;
    pid_t pid = fork();
    
    if (pid == 0) 
    {
        global_val++;
        local_val++;
        printf("Child: %d, %d\n", global_val, local_val);
    } 
    else 
    {
        sleep(2);  // 确保子进程先执行
        printf("Parent: %d, %d\n", global_val, local_val);
    }
    return 0;
}

运行结果:

4.fork的常规用法

  • 父进程复制自己,使父子进程同时执行不同代码段:例如,父进程等待客户端请求,生成子进程来处理请求。
  • 子进程执行不同的程序:子进程从fork返回后,调用exec函数执行新程序。

5. fork调用失败的原因

  • 系统中已有太多进程。

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

二、进程终止

1.  终止的本质

进程终止时,系统会执行以下操作:

  • 释放代码和数据占用的空间。

  • 释放内核数据结构(如task_struct)。

2. main函数的return返回值

  • main函数的return返回值表示进程的退出状态。

  • 该值会传递给父进程(如Bash shell),父进程可以通过echo $?来获取这个退出状态。

3. 进程退出状态的含义

  • 退出码(Exit Code):

    • 0 表示成功。

    • 非 0 表示失败,具体值可自定义,用于表示不同的失败原因。

  • 退出信号(Exit Signal):

    • 如果进程因接收到信号而终止,退出码可能没有意义。

    • 退出信号可以通过特定方式(如 wait 函数)获取,以判断进程异常原因。

示例分析:

int main() 
{
    int *ptr = NULL;
    *ptr = 10;  // 访问空指针,触发段错误(SIGSEGV)
    return 0;
}

运行时,进程会因段错误被操作系统终止,此时通过 echo $? 获取的值是 139。这是因为 SIGSEGV 的信号编号是 11,而退出状态码是 128 + 11 = 139

4. 父进程为什么会得到子进程的退出码?

父进程需要知道子进程的退出情况,以便:

  • 判断子进程是否成功完成任务。

  • 如果失败,可以通过退出码或信号得知失败原因,从而进行相应的处理(如重试、记录错误日志等)。

5. 异常退出与信号

  • 异常退出原因:

    • 进程做了非法操作,如访问非法内存、除以零等。

  • 信号(Signal):

    • 操作系统会发送一个信号(如 SIGSEGV SIGFPE 等)来终止进程。

    • 退出信号可以用 wait 和相关宏(如 WIFSIGNALEDWTERMSIG)来获取。

6.  进程常见终止情况

  1. 正常终止:

    • main函数返回: return语句的返回值表示进程的退出码。

    • 调用exit函数: 可以在代码的任意位置调用exit终止进程。

    • 调用_exit系统调用: 直接终止进程,不执行清理操作。

  2. 异常终止:

    • 收到操作系统发送的信号(如SIGKILLSIGTERM)。

    • 例如,用户按下Ctrl+C会发送SIGINT信号。

7. exit 和 _exit 的区别

exit系列相关函数:

#include <unistd.h>

void exit(int status);        // 调用库函数,执行清理操作后调用_exit
void _exit(int status);       // 系统调用,直接终止进程
  • exit

    • 调用时会执行清理操作,如调用用户注册的清理函数(通过atexiton_exit)、刷新流缓冲区等。

    • 最后调用 _exit 来终止进程。

  • _exit

    • 系统调用,直接终止进程,不执行清理操作。

示例代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void cleanup() 
{
    printf("Cleanup function called!\n");
}

int main() 
{
    cleanup();  
    printf("hello");
    exit(0);  // exit会调用清理函数并刷新缓冲区
}


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void cleanup() 
{
    printf("Cleanup function called!\n");
}

int main() 
{
    cleanup();  
    printf("hello");
    _exit(0);  // _exit不会调用清理函数,也不会刷新缓冲区
}

8. return与exit的区别

  • return:

    • 用于从main函数返回,等同于调用exit

    • 返回值会被父进程获取。

  • exit:

    • 显式终止进程,可以指定退出码。

    • 会刷新缓冲区并执行清理操作。


http://www.kler.cn/a/543402.html

相关文章:

  • 小白学网络安全难吗?需要具备哪些条件?
  • Vue3(2)
  • 微信小程序医院挂号系统
  • 基于Ubuntu2404搭建k8s-1.31集群
  • Spring:Spring实现AOP的通俗理解(有源码跟踪)
  • Linux系统安装Nginx详解(适用于CentOS 7)
  • Amazon Keyspaces (for Apache Cassandra)
  • 未来趋势系列 篇一(加更四):DeepSeek题材解析和股票梳理
  • CF Round 997 记录 题解 (div. 2 A - E)
  • SpringBoot中的Javaconfig
  • KRR(知识表示与推理,Knowledge Representation and Reasoning)
  • 冒泡排序
  • 多租户数据源隔离
  • kindle.cn 无法接收邮件
  • pnpm的使用
  • 【工业安全】-CVE-2022-35555- Tenda W6路由器 命令注入漏洞
  • Java入门进阶
  • 日语学习-日语知识点小记-构建基础-JLPT-N4&N5阶段(4):~てもいいです & ~てはいきません征求许可
  • 在Mac M1上面安装Miniconda
  • 名词解释:npm,cnpm,yarn,vite,vue,electron
  • PySpark查找Dataframe中的非ASCII字符并导出Excel文件
  • 07贪心 + 动态规划(D1_基础学习)
  • 蓝桥杯试题:归并排序
  • PySide (PyQt)的视图(QGraphicsView)和场景(QGraphicsScene)
  • 深入理解现代前端框架:Vue.js 的进阶探秘
  • bash shell笔记——循环结构