操作系统笔记之进程调用API中的getpid、fork、wait、exec补充

操作系统笔记之进程调用API中的getpid、fork、wait、exec补充

code review!

效果图

—— 杭州 2024-03-17 夜

文章目录

  • 操作系统笔记之进程调用API中的getpid、fork、wait、exec补充
    • 1.getpid()
    • 2.fork()
    • 3.wait()
    • 4.exec()
    • 5.通常,exec() 调用与 fork() 调用一起使用,为什么?
      • `fork()` 的作用
      • `exec()` 的作用
      • `fork()` 和 `exec()` 一起使用的原因
      • 实例
    • 6.fork() 和 exec() 一起使用时,子进程的调用会返回吗?

1.getpid()

getpid() 是一个在 Unix-like 系统(比如 Linux 和 macOS)中常用的系统调用函数,它用于获取当前进程的进程标识符(Process ID,简称 PID)。该函数定义在 <unistd.h> 头文件中,属于 POSIX 标准的一部分。

每个运行中的进程都有一个唯一的 PID,这是一个非负整数。这个标识符可以用于控制进程,比如发送信号给进程来终止它或者查询进程的状态。

函数原型

getpid() 函数的原型如下:

#include <unistd.h>

pid_t getpid(void);

这里 pid_t 通常是 int 类型的别名,用于表示进程 ID。

返回值

getpid() 函数返回调用进程的 PID。因为每个进程都有一个唯一的 PID,并且 getpid() 不会失败,所以它没有失败的返回值。

示例代码

以下是一个简单的示例,演示了如何在 C 程序中调用 getpid() 函数:

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

int main() {
    pid_t pid = getpid(); // 调用 getpid() 获取当前进程的 PID
    printf("The Process ID (PID) is: %d\n", pid);
    return 0;
}

当你编译并运行这个程序时,它会输出当前进程的 PID。

应用场景

getpid() 在多种情况下可能会被使用:

  • 日志记录:在进行系统日志记录时,记录当前进程的 PID 可以帮助在出现问题时追踪到具体的进程。
  • 进程管理:脚本或程序可能需要知道其自身的 PID,以便于创建锁文件,防止多个实例同时运行。
  • 调试:在调试多进程程序时,知道不同进程的 PID 可以帮助区分它们的输出或行为。
  • 信号处理:发送信号给特定进程时需要知道其 PID。

注意事项

  • 在 Unix-like 系统中,PID 1 通常是初始化进程(init 或 systemd),它是所有其他用户空间进程的祖先。
  • PID 是一个有限资源,在长时间运行的系统中可能会耗尽。不过,系统会在 PID 耗尽时回收和重用旧的、不再使用的 PID。
  • 在多线程程序中,所有线程共享同一个 PID,因为它们运行在同一个进程上下文中。如果你需要获取线程的唯一标识符,应该使用 pthread_self() 或其他相关函数。

2.fork()

fork() 是一个用于创建进程的系统调用。理解 fork() 的关键是要知道它会创建一个与原始(父)进程几乎完全相同的新进程(子进程)。让我们用一个更详细的方式来说明这个过程:

  1. 调用 fork(): 当一个进程(我们称之为父进程)执行到 fork() 系统调用时,它会请求操作系统创建一个新的进程。

  2. 创建子进程: 操作系统会复制父进程的整个状态到子进程中。这意味着子进程将获得父进程数据段、代码段和堆栈的副本。在许多操作系统中,这种复制是通过写时复制(Copy-on-Write, COW)机制实现的,以提高效率。实际的内存页只有在其中一个进程尝试写入时才会被复制。

  3. 区分父子进程: fork() 调用在父进程中返回子进程的 PID,在子进程中返回 0。这是父进程和子进程的代码可以区分两个进程的关键所在。

  4. 独立执行: 一旦 fork() 完成,两个进程(父进程和子进程)都将从 fork() 调用之后的指令开始独立执行。这两个进程有各自独立的地址空间,所以一个进程对内存的改变不会影响另一个进程。

  5. 资源共享: 子进程会继承父进程的文件描述符。这些文件描述符指向相同的文件表项,意味着父子进程可以共享打开的文件等资源。

  6. 独立生命周期: 子进程有自己的生命周期,它可以独立于父进程执行,也可以执行不同的代码。通常,子进程会调用 exec() 系列函数来替换自己的内存空间,包括代码和数据,以运行一个新的程序。

简单的 fork() 示例

让我们看一个 fork() 的示例,以便更好地理解这个过程:

在这里插入图片描述

运行:
在这里插入图片描述

代码

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

int main() {
    printf("Before fork()\n");

    pid_t pid = fork();

    if (pid == -1) {
        // fork失败
        perror("fork");
        return 1;
    } else if (pid > 0) {
        // 父进程
        printf("I am the parent process. My PID is %d and my child's PID is %d.\n", getpid(), pid);
    } else {
        // 子进程
        printf("I am the child process. My PID is %d.\n", getpid());
    }

    printf("This is the end of the process with PID %d.\n", getpid());

    return 0;
}

在这个程序中,我们首先打印 “Before fork()”,然后调用 fork()。在 fork() 之后,我们有两个独立的进程:父进程和子进程。每个进程都会执行相应的 if 分支,并且都会打印 “This is the end of the process with PID …”。因此,你会看到 “Before fork()” 只打印一次,而 “This is the end of the process with PID …” 会打印两次,一次用父进程的 PID,一次用子进程的 PID。

3.wait()

wait() 系统调用在 UNIX 和类 UNIX 操作系统中用于使一个父进程等待其子进程结束或改变状态。当子进程结束或停止时,父进程可以通过 wait() 系统调用来收集子进程的退出状态信息。这是一种进程间通信的方式,也是父进程管理子进程生命周期的一种方法。

功能

wait() 提供以下功能:

  • 收集子进程状态: 父进程可以获取子进程的终止状态,例如子进程的退出代码。
  • 回收资源: 当子进程结束时,操作系统会保留一些资源(如进程描述符和统计信息),直到父进程通过 wait() 调用释放。这一步骤被称为 “回收” 子进程。
  • 同步: wait() 可以用来确保父进程在子进程结束之前不会继续执行,实现父子进程间的同步。

使用方法

wait() 调用通常在父进程中这样使用:

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

int main() {
    pid_t pid = fork();

    if (pid == -1) {
        // fork失败
        perror("fork");
        return 1;
    } else if (pid > 0) {
        // 父进程
        int status;
        waitpid(pid, &status, 0); // 父进程等待子进程结束

        if (WIFEXITED(status)) {
            printf("Child exited with status %d\n", WEXITSTATUS(status));
        }
    } else {
        // 子进程
        // 执行一些工作...
        _exit(42); // 子进程结束,返回42作为退出状态
    }

    return 0;
}

参数和返回值

wait() 函数的原型如下:

pid_t wait(int *status);
  • status: 这是一个指向整数的指针,用于存储子进程的退出状态。通过宏(比如 WIFEXITEDWEXITSTATUS)可以分析这个状态值。
  • 返回值: 返回子进程的 PID,或在错误时返回 -1。

状态宏

wait() 通过 status 参数传递的状态值可以用下列宏进行分析:

  • WIFEXITED(status): 如果子进程正常结束,则此宏返回真(非0值)。
  • WEXITSTATUS(status): 如果 WIFEXITED 非零,返回子进程的退出状态(即 main() 的返回值或 _exit 的参数)。
  • WIFSIGNALED(status): 如果子进程因为信号而结束,则此宏返回真。
  • WTERMSIG(status): 如果 WIFSIGNALED 非零,返回导致子进程终止的信号编号。
  • WIFSTOPPED(status): 如果子进程处于停止状态,则此宏返回真。
  • WSTOPSIG(status): 如果 WIFSTOPPED 非零,返回导致子进程停止的信号编号。

注意事项

  • 如果父进程没有调用 wait(),而子进程已经结束,子进程将变成僵尸进程(Zombie),直到其父进程结束或为其调用 wait()
  • 如果父进程结束而子进程仍在运行,子进程将被 init 进程(PID 为 1)收养,init 将负责调用 wait() 收集状态信息。

wait() 还有几个相关函数,如 waitpid()waitid()wait3()/wait4(),它们提供了更多控制选项,比如非阻塞等待或等待特定的子进程。

4.exec()

当你运行一个程序时,操作系统为该程序创建了一个进程。每个进程都有自己的内存空间,其中包含了运行程序所需的指令和数据。exec() 系统调用的功能是在一个已经存在的进程中启动一个新的程序。这意味着 exec() 实际上是用一个全新的程序来替换当前进程的内存空间内容。

exec() 是一组函数,不仅仅只有一个。这组函数包括 execl(), execv(), execlp(), execvp(), execle(), execve() 等。这些函数的区别在于它们如何接收参数(比如直接传递参数列表或是通过数组传递参数),以及它们是否搜索系统的 PATH 环境变量来找到可执行文件。

以下是 exec() 函数族中一个函数的典型用法:

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

int main() {
    char *args[] = {"/bin/ls", "-l", NULL}; // 定义了要执行的命令和参数列表

    execv("/bin/ls", args); // 使用 execv 来执行/bin/ls程序

    // 如果execv执行成功,以下的代码不会被执行,
    // 因为当前进程的内存已经被ls程序替换。
    perror("execv"); // 如果 execv 失败,则打印错误消息
    return 1;
}

在这个例子中,execv() 被用来在当前进程中运行 /bin/ls 命令。如果 execv() 成功执行,当前的程序(这个示例中的 C 程序)就会停止运行,因为它的内存空间被 ls 命令的代码和数据所替换。因此,程序中 execv() 调用之后的代码(在这里是 perror()return 1)不会被执行。

如果 execv() 调用失败了(比如,如果 /bin/ls 不是一个有效的可执行文件),则 execv() 会返回 -1,并且 errno 会被设置为描述错误的代码。在这种情况下,perror() 会被执行,它会根据 errno 的值打印一条错误消息。

通常,exec() 调用与 fork() 调用一起使用,这样可以先通过 fork() 创建一个新的子进程,然后在子进程中使用 exec() 来替换为另一个程序。父进程可以继续执行其他任务,或者等待子进程完成。

5.通常,exec() 调用与 fork() 调用一起使用,为什么?

fork() 的作用

  • 创建进程: fork() 创建了一个新的子进程,这个子进程几乎是父进程的完整副本。它有自己的进程ID,并且复制了父进程的内存布局。
  • 独立运行: 一旦 fork() 成功,你就有了两个几乎相同的进程:一个父进程和一个子进程。

exec() 的作用

  • 替换程序: exec() 家族的函数用于在一个进程中启动一个新程序。它替换当前进程的内存空间,包括代码和数据。
  • 执行新任务: exec() 通常在 fork() 创建的子进程中调用,以确保子进程执行的是与父进程不同的新任务。

fork()exec() 一起使用的原因

  • 保护父进程: 如果只使用 exec(),你将失去当前正在运行的程序,因为它会被新程序替换。fork() 允许父进程继续运行,同时子进程可以去执行新任务。
  • 执行并发任务: 使用 fork()exec(),你可以让父进程和子进程同时(并发地)执行不同的任务。

实例

想象你有一个程序,就像一个简单的命令行界面。用户输入一个命令,比如 ls

  1. 程序调用 fork() 创建一个新的子进程。
  2. 子进程使用 exec() 来执行 ls 命令。
  3. 父进程等待子进程执行完毕。

在这个过程中:

  • 如果没有 fork(),原有的程序就会停止运行来执行 ls
  • 如果只有 fork() 而没有 exec(),子进程将会做和父进程完全相同的事情,这不是我们想要的。

结合使用 fork()exec() 允许父进程继续运行自己的代码,而子进程则运行一个全新的程序。这是多任务操作系统中常见的操作,它允许系统同时处理多个任务。

6.fork() 和 exec() 一起使用时,子进程的调用会返回吗?

fork()exec() 被一起使用时,子进程的行为取决于 exec() 调用是否成功:

  1. 如果 exec() 调用成功

    • 子进程的 exec() 调用不会返回,因为子进程的原始程序代码已经被新程序替换。子进程从此开始执行新程序的代码,之前的执行上下文(包括调用 exec() 的代码)不复存在。
    • 子进程将继续作为新程序运行,直到该程序结束或遇到错误。
  2. 如果 exec() 调用失败

    • 子进程中的 exec() 调用会返回 -1,并且 errno 会被设置为描述错误的代码。在这种情况下,子进程通常会执行一些错误处理的代码,例如打印出错信息,并且随后通常会立即退出。
    • 子进程在 exec() 调用失败后通常会调用 exit()_exit() 函数来结束自身,因为子进程的正常逻辑是执行另一个程序,如果这一步骤失败了,通常就没有理由继续执行原来的程序代码了。

简而言之,如果 exec() 成功,子进程不会返回到原程序代码;如果 exec()失败,子进程会返回一个错误,而且通常会紧接着退出。父进程可以通过 wait()waitpid()调用来监测子进程的退出状态,以了解子进程是正常结束还是遇到了错误。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/465204.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CentOS 7 编译安装 Git

CentOS 7 编译安装 Git 背景来源删除旧版本 Git安装依赖包下载 Git 源代码检验相关依赖&#xff0c;设置安装路径编译安装添加 Git 环境变量重新加载配置文件查看版本号参考文献 背景来源 为什么要安装新版本呢&#xff1f; 因为无聊&#xff0c;哈哈哈&#xff0c;其实也不是…

论文阅读——SpectralGPT

SpectralGPT: Spectral Foundation Model SpectralGPT的通用RS基础模型&#xff0c;该模型专门用于使用新型3D生成预训练Transformer&#xff08;GPT&#xff09;处理光谱RS图像。 重建损失由两个部分组成&#xff1a;令牌到令牌和频谱到频谱 下游任务&#xff1a;

DevOps 环境预测测试中的机器学习

在当今快节奏的技术世界中&#xff0c;DevOps 已成为软件开发不可或缺的一部分。它强调协作、自动化、持续集成&#xff08;CI&#xff09;和持续交付&#xff08;CD&#xff09;&#xff0c;以提高软件部署的速度和质量。预测测试是这一领域的关键组成部分&#xff0c;其中机器…

基于深度学习LSTM+NLP情感分析电影数据爬虫可视化分析推荐系统(深度学习LSTM+机器学习双推荐算法+scrapy爬虫+NLP情感分析+数据分析可视化)

文章目录 基于深度学习LSTMNLP情感分析电影数据爬虫可视化分析推荐系统&#xff08;深度学习LSTM机器学习双推荐算法scrapy爬虫NLP情感分析数据分析可视化&#xff09;项目概述深度学习长短时记忆网络&#xff08;Long Short-Term Memory&#xff0c;LSTM&#xff09;机器学习协…

【解读】保障软件供应链安全:SBOM推荐实践指南(含指南获取链接)

2023年11底&#xff0c;美国NSA&#xff08;National Security Agency&#xff09;、CISA&#xff08;Cybersecurity and Infrastructure Security Agency&#xff09;等多个政府机构部门组成的ESF&#xff08;Enduring Security Framework&#xff0c;持久安全框架&#xff09…

C++ 特殊类及单例模式

文章目录 1. 前言2. 不能被拷贝的类3. 不能被继承的类4. 只能在堆上创建对象的类5. 只能在栈上创建对象的类6. 只能创建一个对象的类&#xff08;单例模式&#xff09; 1. 前言 在实际场景中&#xff0c;我们在编写类的过程中总会遇到一些特殊情况&#xff0c;比如设计一个类不…

06.共享内存

1.内存映射&#xff08;mmap&#xff09; 我们在单片机中首先接触到了映射的概念 将一个寄存器的地址映射到了另外的一个存储空间中 内存映射: 内存映射&#xff08;Memory Mapping&#xff09;是一种在计算机科学中使用的技术&#xff0c;它允许将文件或其他设备的内容映射…

Vue项目的搭建

Node.js 下载 Node.js — Download (nodejs.org)https://nodejs.org/en/download/ 安装 测试 winR->cmd执行 node -v配置 在安装目录下创建两个子文件夹node_cache和node_global,我的就是 D:\nodejs\node_cache D:\nodejs\node_global 在node_global文件下再创建一个…

【SQL】1174. 即时食物配送 II (窗口函数row_number; group by写法;对比;定位错因)

前述 推荐学习&#xff1a; 通俗易懂的学会&#xff1a;SQL窗口函数 题目描述 leetcode题目&#xff1a;1174. 即时食物配送 II 写法一&#xff1a;窗口函数 分组排序&#xff08;以customer_id 分组&#xff0c;按照order_date 排序&#xff09;&#xff0c;窗口函数应用。…

kubernetes-有状态和无状态服务

kubernetes-有状态和无状态服务 kubernetes-有状态和无状态服务1.有状态的应用1.1、理解1.2、特点 2、无状态应用2.1、理解2.2、特点 3、玩一下3.1、启动一个nginx无状态的业务3.2、启动一个nginx有状态的业务 4、无头服务4.1、无头服务的特点&#xff1a;4.2、无头服务的用途&…

力扣每日一题 最小高度树 BFS 双向

Problem: 310. 最小高度树 思路 &#x1f468;‍&#x1f3eb; 参考地址 复杂度 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( n ) O(n) O(n) Code import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Queue;publ…

企业数据流动安全管理软件(深度解析文章)

企业数据重要性不言而喻&#xff0c;而同时数据的流动和共享也带来了安全风险&#xff0c;如何确保企业数据在流动过程中的安全性&#xff0c;也成为了企业需要面临的重要问题。 企业数据流动安全管理软件的主要功能是监控和管理企业数据的流动过程。 它能够对企业内部的数据…

Ps:直接选择工具

直接选择工具 Direct Selection Tool可用于选择和调整路径或形状中的锚点和路径线段。 快捷键&#xff1a;A 直接选择工具的指针形状为白箭头。当需要调整锚点、方向调杆、路径线段以及对选中的多个锚点子路径进行移动、变换&#xff08;缩放、旋转、扭曲、斜切、变形等&#x…

蓝桥杯刷题(十)

1.翻转 代码 输入数据&#xff0c;每组数据进行比较&#xff0c;j的范围掐头去尾&#xff0c;若a[j]b[j]&#xff0c;继续&#xff0c;若出现010,101子串则改成000,111&#xff0c;遍历完后比较a是否等于b&#xff0c;相同则输出次数&#xff0c;不同则输出-1。 for _ in ran…

智慧城市新篇章:数字孪生的力量与未来

随着信息技术的迅猛发展和数字化浪潮的推进&#xff0c;智慧城市作为现代城市发展的新模式&#xff0c;正在逐步改变我们的生活方式和社会结构。在智慧城市的构建中&#xff0c;数字孪生技术以其独特的优势&#xff0c;为城市的规划、管理、服务等方面带来了革命性的变革。本文…

目标检测---IOU计算详细解读(IoU、GIoU、DIoU、CIoU、EIOU、Focal-EIOU、WIOU)

常见IoU解读与代码实现 一、✒️IoU&#xff08;Intersection over Union&#xff09;1.1 &#x1f525;IoU原理☀️ 优点⚡️缺点 1.2 &#x1f525;IoU计算1.3 &#x1f4cc;IoU代码实现 二、✒️GIoU&#xff08;Generalized IoU&#xff09;2.1 GIoU原理☀️优点⚡️缺点 2…

【Spark编程基础】RDD 编程初级实践(附源代码)

目录 一、实验目的二、实验平台三、实验内容1.spark-shell 交互式编程2.编写独立应用程序实现数据去重3.编写独立应用程序实现求平均值问题 一、实验目的 1、熟悉 Spark 的 RDD 基本操作及键值对操作&#xff1b; 2、熟悉使用 RDD 编程解决实际具体问题的方法 二、实验平台 …

百科源码生活资讯百科门户类网站百科知识,生活常识

百科源码生活资讯百科门户类网站百科知识,生活常识 百科源码安装环境 支持php5.6&#xff0c;数据库mysql即可&#xff0c;需要有子目录权限&#xff0c;没有权限的话无法安装 百科源码可以创建百科内容&#xff0c;创建活动内容。 包含用户注册&#xff0c;词条创建&#xff…

VScode(8)之阅读大型CC++工程

VScode(8)之阅读大型CC工程(Linux内核)代码 Author&#xff1a;Once Day Date&#xff1a;2023年4月25日/2024年3月17日 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章请查看专栏: VScode开发_Once-Day的博客-CSDN博客 参考文档: 1. 历史包袱 由于上世纪70-80年代的…

综合知识篇08-数据库系统考点(2024年软考高级系统架构设计师冲刺知识点总结系列文章)

专栏系列文章: 2024高级系统架构设计师备考资料(高频考点&真题&经验)https://blog.csdn.net/seeker1994/category_12593400.html案例分析篇00-【历年案例分析真题考点汇总】与【专栏文章案例分析高频考点目录】(2024年软考高级系统架构设计师冲刺知识点总结-案例…