20242817李臻《Linux⾼级编程实践》第四周

20242817李臻《Linux⾼级编程实践》第4周

一、AI对学习内容的总结

第5章 Linux进程管理

5.1 进程基本概念

进程与程序的区别

  • 程序:静态的二进制文件(如/bin/ls),存储在磁盘中,不占用运行资源。
  • 进程:程序的动态实例,拥有独立的地址空间、资源(CPU、内存、文件句柄等),生命周期包括创建、运行、阻塞、终止。
    关键差异
  • 程序是“菜谱”,进程是“烹饪过程”。
  • 同一程序可产生多个进程(如同时运行多个ls命令)。

进程类型

  • 交互进程:由用户通过Shell启动(如./a.out)。前台进程占用终端(用户需等待完成),后台进程加&(如./a.out &)。
  • 批处理进程:无交互任务序列(如cron定时任务)。
  • 守护进程(Daemon):系统启动时加载(如sshdhttpd),脱离终端,长期运行。

进程状态

  • 运行(Running):占用CPU执行指令。
  • 就绪(Ready):等待调度器分配CPU时间片。
  • 睡眠(Sleeping):等待I/O或信号唤醒。
  • 停止(Stopped):收到SIGSTOP信号暂停。
  • 僵尸(Zombie):已终止但未被父进程回收。

执行模式

  • 用户模式:运行用户程序,权限受限(无法直接访问硬件)。
  • 内核模式:执行系统调用或中断处理,可操作特权指令(如修改内存页表)。
    切换场景
  • 系统调用(如read())。
  • 硬件中断(如时钟中断)。

5.2 进程创建与命令执行

fork()系统调用

核心机制

  • 写时复制(Copy-On-Write):父子进程共享物理内存,仅在实际修改时复制。
  • 复制父进程的代码段、数据段、堆栈、文件描述符表、信号处理等。
    返回值
  • 父进程返回子进程PID(>0)。
  • 子进程返回0。
  • 失败返回-1(如进程数超限)。
    示例分析
pid_t pid = fork();
if (pid == 0) {// 子进程代码
} else if (pid > 0) {// 父进程代码
} else {perror("fork failed");
}

exec系列函数

功能:替换当前进程的代码段为新程序(“换脑”)。
常用变体

函数参数传递方式路径查找方式
execl列表(arg0, arg1, ..., NULL需完整路径
execv数组(argv[]需完整路径
execlp列表通过PATH环境变量
execvp数组通过PATH环境变量
关键特性
  • 成功调用后不返回,失败返回-1。
  • 保留原进程的PID、文件描述符、信号掩码等属性。
    文件描述符继承
  • 父子进程共享打开的文件描述符,文件偏移量同步更新。
    潜在问题
  • 并发写入冲突(需使用O_APPEND或文件锁)。
    示例:父进程读取文件后fork(),子进程写入时继承同一偏移量。

5.3 进程退出

退出方式

正常退出

  • exit(int status):刷新缓冲区,调用atexit()注册的函数。
  • _exit(int status):直接终止进程,不清理缓冲区。
    异常退出
  • 收到信号(如SIGKILLSIGSEGV)。

守护进程创建步骤

详细流程

  1. fork()后父进程退出:子进程成为孤儿进程,由init接管。
  2. setsid()创建新会话:脱离原控制终端,成为会话组长。
  3. chdir("/")修改工作目录:避免占用挂载点。
  4. umask(0)重置文件掩码:取消权限限制。
  5. 关闭文件描述符:
for (int i = 0; i < sysconf(_SC_OPEN_MAX); i++) close(i);
  1. 重定向标准I/O(可选):
open("/dev/null", O_RDWR); // stdin
dup(0); // stdout
dup(0); // stderr

僵尸进程处理

产生原因:子进程终止后,父进程未调用wait()/waitpid()
解决方法

  • 阻塞等待:父进程调用wait(&status)
  • 非阻塞等待:使用waitpid(pid, &status, WNOHANG)
  • 信号处理:捕获SIGCHLD信号,异步回收子进程。
signal(SIGCHLD, [](int sig) {while (waitpid(-1, NULL, WNOHANG) > 0);
});

wait()waitpid()对比

  • wait():等待任一子进程结束,阻塞父进程。
  • waitpid()
    • 可指定子进程PID(如pid=-1等待所有子进程)。
    • 支持选项(如WNOHANG非阻塞轮询)。

5.4 进程开发实例

简易Shell实现

核心逻辑

while (1) {printf("> ");fgets(cmd, sizeof(cmd), stdin);if (strcmp(cmd, "exit\n") == 0) break;pid_t pid = fork();if (pid == 0) {// 子进程解析命令并执行execvp(args[0], args);perror("exec failed");exit(1);} else {wait(NULL); // 等待子进程结束}
}

扩展功能

  • 支持管道(pipe() + dup2())。
  • 支持重定向(open() + dup2())。

多进程同步

父子进程协作示例:父进程等待所有子进程完成。

pid_t child1 = fork();
if (child1 == 0) { /* 输出10个B */ exit(0); }
pid_t child2 = fork();
if (child2 == 0) { /* 输出10个C */ exit(0); }
waitpid(child1, NULL, 0);
waitpid(child2, NULL, 0);
// 父进程输出A

总结表格

主题关键点
fork()写时复制、父子进程独立执行、返回值区分角色。
exec系列替换进程映像、参数传递方式(列表/数组)、路径查找(完整路径/PATH)。
守护进程setsid()脱离终端、chdir("/")umask(0)、关闭文件描述符。
僵尸进程父进程未调用wait()、通过信号或非阻塞等待回收。
Shell实现fork()+execvp()+wait()循环,支持命令解析与执行。

对AI总结的反思与补充

1.反思

原内容存在问题

5.1 进程基本概念

  1. 守护进程类型描述不完整
    • 原文描述:“系统启动时加载”
    • 修正:守护进程分为两类:系统引导时启动的常驻型(如sshd)和按需启动的任务型(如crond)。建议补充说明"守护进程可通过systemd等机制按需启动"。
  2. 进程状态术语偏差
    • 原文使用"睡眠(Sleeping)"状态
    • 修正:Linux官方术语中分为"阻塞态(Blocked)“和"睡眠态(Sleep)”:
      • 阻塞态:被动等待I/O或资源(如read()系统调用)
      • 睡眠态:主动调用sleep()进入挂起

5.2 进程创建与命令执行

  1. exec系列函数的文件描述符继承
    • 原文描述:“父子进程共享打开的文件描述符”
    • 修正:“若文件描述符设置了FD_CLOEXEC标志,则exec时会自动关闭该描述符”。可通过fcntl(fd, F_SETFD, FD_CLOEXEC)设置。

5.3 进程退出

  1. 僵尸进程处理方案补充
    • 原文未提及SIGCHLD信号特性
    • 修正:需补充说明"若父进程注册了SIGCHLD信号处理函数,应使用循环while(waitpid(-1, NULL, WNOHANG) > 0)避免信号丢失"。
  2. 守护进程创建步骤优化
    • 原文代码示例直接关闭所有文件描述符
    • 修正:更安全的做法是遍历/proc/self/fd目录关闭文件描述符,避免漏关新增fd:
    DIR *dir = opendir("/proc/self/fd");  
    while ((entry = readdir(dir)) != NULL) {  int fd = atoi(entry->d_name);  if (fd > STDERR_FILENO) close(fd);  
    }  
    closedir(dir);  
    ```。
    

5.4 进程开发实例

  1. Shell实现的重定向漏洞
    • 原文未处理重定向竞争条件
    • 修正:在fork()前完成文件打开操作,避免父子进程并发操作:
    int fd = open(file, O_RDWR);  
    if (fork() == 0) {  dup2(fd, STDOUT_FILENO);  // 子进程重定向  close(fd);                // 避免fd泄漏  
    }  
    

2.补充


5.1 进程基本概念

进程控制块(PCB)
  • 数据结构:每个进程对应一个task_struct结构体,包含:
    • 进程标识符(PID/PPID):唯一标识进程及其父进程
    • 进程状态:R(运行)、S(休眠)、D(不可中断)、Z(僵尸)等
    • 调度信息:优先级、时间片、调度策略
    • 内存管理:虚拟内存地址空间、页表指针
    • 文件系统:打开的文件描述符表(FD Table)
    • 信号处理:信号掩码、未决信号队列
进程状态扩展
  • 不可中断睡眠(D):等待磁盘I/O等硬件操作,无法被信号唤醒
  • 僵尸进程处理
    • SIGCHLD信号处理:通过signal(SIGCHLD, handler)注册信号处理器,异步回收子进程。
    • waitpid选项WNOHANG非阻塞模式轮询僵尸进程。

5.2 进程创建与命令执行

exec函数族扩展
  • 环境变量控制
    • execle:可自定义环境变量数组(char *envp[])。
    • execve:系统级底层函数,支持完整参数和环境变量控制。
      // execle示例:传递自定义环境变量
      char *env[] = {"PATH=/usr/bin", "LANG=en_US", NULL};
      execle("/bin/ls", "ls", "-l", NULL, env);
      
  • 文件描述符继承
    • 默认继承:所有打开的文件描述符(除O_CLOEXEC标记)。
    • 关闭技巧fcntl(fd, F_SETFD, FD_CLOEXEC)标记在exec时自动关闭。
高级进程创建
  • 双重fork技巧:防止守护进程意外获取控制终端。
    if (fork() > 0) exit(0); // 第一次fork
    setsid(); // 创建新会话
    if (fork() > 0) exit(0); // 第二次fork
    

5.3 进程退出

守护进程优化
  • 资源清理
    • 关闭所有文件描述符
      for(int i=sysconf(_SC_OPEN_MAX); i>=0; i--) close(i);
      
    • 重定向标准I/Oopen("/dev/null", O_RDWR)后执行dup2()
  • 快速创建:通过nohup command &直接创建守护进程。
僵尸进程深度处理
  • 多子进程回收:在信号处理函数中使用循环while(waitpid(-1, NULL, WNOHANG) > 0)防止信号丢失。
    void sigchld_handler(int sig) {while(waitpid(-1, NULL, WNOHANG) > 0);
    }
    signal(SIGCHLD, sigchld_handler);
    

5.4 进程开发实例

进程间通信(IPC)
机制特点适用场景
管道单向通信,血缘进程间使用父子进程简单数据传递
共享内存零拷贝高效,需同步机制大数据量高频次通信
信号量计数器实现同步资源竞争控制
消息队列结构化消息,支持优先级异步通信
// 共享内存示例
int shm_id = shmget(IPC_PRIVATE, size, 0666);
char *ptr = shmat(shm_id, NULL, 0);
sprintf(ptr, "Hello from PID %d", getpid());
shmdt(ptr);
进程替换特性
  • 属性保留
    • PID不变:exec后仍保持原进程ID。
    • 信号处理:已忽略的信号保持忽略,其他重置为默认动作。
    • 资源限制:继承原进程的rlimit设置。

总结

补充主题新增关键点
PCB结构task_struct包含进程所有元数据,如内存映射、文件表、信号处理等。
双重fork防止守护进程重新获得控制终端的关键技术。
信号量IPC通过semget/semctl实现进程间同步,解决资源竞争问题。
环境变量传递execle允许自定义环境变量,适用于特殊执行环境需求。
非阻塞waitpidWNOHANG选项实现异步子进程状态查询,避免父进程阻塞。

三、学习思维导图

第五章

在这里插入图片描述

mindmaproot((Linux进程管理))1 进程基本概念1.1 进程定义- 动态执行的程序实例- 包含程序代码、数据、栈及运行状态1.2 进程类型- 交互进程(Shell启动,前后台运行)- 批处理进程(按指定方式执行的进程序列)- 守护进程(系统启动时运行,后台服务)1.3 执行模式- 用户模式(运行用户程序代码)- 内核模式(执行系统调用或处理中断)2 进程创建和命令执行2.1 fork系统调用- 创建子进程,复制父进程资源- 返回值区分父子进程(0给子,PID给父)- 示例代码及输出分析2.2 exec家族调用- 替换进程代码执行新程序- 包括execl、execv、execlp、execvp等- 参数传递方式及查找路径区别2.3 fork与exec结合- 父进程创建子进程后,子进程调用exec执行新任务- 实现命令执行的典型模式3 进程退出3.1 正常退出- 程序运行结束自动退出- 调用exit函数刷新缓冲区并清理资源- _exit与exit区别(是否刷新缓冲区及调用清理函数)3.2 异常退出- 接收外部信号终止执行(如kill -9)3.3 守护进程- 脱离终端后台运行的服务进程- 创建步骤:fork子进程、setsid脱离终端、改变工作目录、修改文件权限掩码、关闭文件描述符- 示例代码及运行验证3.4 僵尸进程- 子进程结束但父进程未回收其资源- 使用wait系统调用避免僵尸进程- wait与waitpid区别(等待任意或指定子进程)4 进程开发实例4.1 myshell程序- 模拟Shell功能,等待用户输入命令- 父进程处理用户输入,子进程执行命令- 使用fork、exec、wait等系统调用实现命令执行流程4.2 实例运行模型- 父进程循环等待输入,子进程执行命令后退出- 展示了进程创建、执行、等待、退出的完整生命周期

四、基于AI的学习

1、关于符进程与程序

  • 我:
    我在学习Linux进程与程序知识点,请你以苏格拉底的方式对我进行提问,一次一个问题
  • AI:
    很好!让我们从基础开始。请告诉我,你认为什么是Linux中的“程序”?
  • 我:
    在Linux系统中,程序可以有很多种形式。比如,有些程序是直接在命令行里运行的,你输入一个命令,它就开始工作;还有一些程序是图形界面的,就像你平时用的软件一样,点点鼠标、按按按钮就能操作。简单来说,程序就是让计算机帮你做事的工具。
  • AI:
    很好的描述!那么,既然你已经对程序有了一定的理解,我想进一步问你:当一个程序在Linux系统中运行时,它变成了什么?或者说,程序和进程之间有什么关系?
  • 我:
    程序就是一堆写好的指令,告诉计算机要做什么事情,它是一个静态的东西。
    当我们在Linux系统中运行一个程序的时候,程序就变成了一个进程。进程是程序在运行时的“活生生”的状态。它包含了程序的代码、程序运行时需要的数据,以及程序运行时的状态
  • AI:
    非常清晰的解释!既然你已经理解了程序和进程的区别,那么让我继续追问:当一个进程在Linux系统中运行时,操作系统是如何管理和跟踪它的?具体来说,操作系统会为进程分配哪些资源?
  • 我:
    当一个进程在Linux系统里运行的时候,操作系统会像“管家”一样管理它。操作系统会给进程分配一些资源,让它能顺利运行。
    操作系统会给进程分配内存。内存就像是进程的“工作台”,进程需要在这个地方存储自己的代码和数据。每个进程都有自己的一块独立的内存空间,这样它们才不会互相干扰。
  • AI:
    很好!你提到了内存分配,这是非常重要的一个方面。那么,既然操作系统为进程分配了内存,我想进一步探讨的是:操作系统是如何区分和管理不同进程的?或者说,每个进程在系统中是否有唯一的标识?
  • 我:
    当一个进程在Linux系统中运行时,操作系统会创建这个进程,并给它分配一个独一无二的PID。这个PID在整个系统运行期间都是唯一的,不会和其他进程的PID重复。操作系统用PID来跟踪每个进程的状态,比如它正在运行、暂停,还是已经结束。
  • AI:
    非常准确!既然你已经知道PID(进程ID)是操作系统用来区分和管理进程的关键,那么让我们深入一点:除了PID,操作系统还会为每个进程维护哪些关键信息?这些信息在进程管理中起到了什么作用?
  • 我:
    你的理解是什么?

  • AI:
    我的理解是,除了PID,操作系统还会为每个进程维护以下关键信息:
  1. PPID(父进程ID):标识创建当前进程的父进程,帮助操作系统管理进程的层次结构。
  2. 进程状态:如运行、就绪、阻塞等,帮助操作系统调度进程。
  3. CPU寄存器状态:保存进程的上下文,以便在进程切换时恢复执行。
  4. 内存分配信息:记录进程使用的内存区域,确保内存的合理分配和回收。
  5. 文件描述符表:跟踪进程打开的文件和I/O资源。
    这些信息帮助操作系统有效地管理、调度和监控进程,确保系统的稳定性和效率。现在,你的理解是什么呢?

  • 我:
    针对我该知识点,我理解了吗?我的回答结束了,请对我的回答进行评价总结。

  • AI:
    你对Linux进程与程序的基础知识已经有了较好的理解!以下是对你回答的总结和评价:

优点:

  1. 程序与进程的区别:你清楚地解释了程序是静态的指令集合,而进程是程序在运行时的动态实体。这种区分非常重要,表明你理解了核心概念。
  2. PID的作用:你准确地指出了PID是操作系统用来唯一标识和管理进程的关键信息,这显示你对进程管理的基本机制有清晰的认识。
  3. 资源分配:你提到操作系统为进程分配内存,并强调了内存空间的独立性,这表明你理解了进程运行时的资源管理。

改进建议:

  1. 深入进程信息:虽然你提到了PID和内存分配,但对其他关键信息(如PPID、进程状态、CPU寄存器、文件描述符等)的讨论可以更深入。这些信息在进程管理和调度中同样重要。
  2. 操作系统的调度机制:你可以进一步探讨操作系统如何利用这些信息来调度进程,例如进程状态如何影响CPU的分配。
  3. 实例与场景:通过具体的例子或场景来说明这些概念,可以帮助你更好地理解和记忆。例如,可以思考在多任务环境中,操作系统如何通过PID和进程状态来管理多个进程。

总结:

你对Linux进程与程序的基础知识掌握得不错,已经理解了核心概念。接下来,可以通过深入探讨进程的其他关键信息、操作系统的调度机制,以及结合实际场景来进一步巩固和扩展你的知识。继续努力,你的理解会越来越全面!

2、关于僵尸进程处理

  • 我:
    我在学习僵尸进程知识点,请你以苏格拉底的方式对我进行提问,一次一个问题
  • AI:
    好的,让我们从基础开始。你知道什么是进程吗?
  • 我:
    进程是程序的运行实例,是操作系统进行资源分配和调度的基本单位。
  • AI:
    很好!既然你理解进程的概念,那么你能告诉我进程在终止时,通常会发生什么吗?
  • 我:
    进程终止时,通常会释放占用的资源,如内存、文件句柄等;操作系统会清理其数据结构,并通知父进程其终止状态。
  • AI:
    非常清晰!既然进程终止时会通知父进程,那么如果一个父进程没有接收到子进程的终止状态,你认为会发生什么情况?
  • 我:
    如果父进程没有接收子进程的终止状态,子进程会变成僵尸进程,占用系统资源,直到父进程读取其状态信息。
  • AI:
    没错!那么,现在你已经知道僵尸进程的产生原因,你能想到哪些方法可以避免或处理僵尸进程吗?

  • 我:
    避免或处理僵尸进程的方法如下:
    父进程调用 wait 或 waitpid:父进程通过调用 wait 或 waitpid 函数回收子进程的状态。
    捕获 SIGCHLD 信号:在父进程中捕获 SIGCHLD 信号,并在信号处理函数中调用 wait 或 waitpid。
    使用双重 fork 技术:通过双重 fork 使子进程成为孤儿进程,由 init 进程接管并回收。
    设置信号忽略:在父进程中设置 signal(SIGCHLD, SIG_IGN),忽略 SIGCHLD 信号,让子进程直接被 init 进程接管。
    处理僵尸进程的方法
    杀死父进程:找到产生僵尸进程的父进程并杀死它,僵尸进程会被 init 进程接管并清理。
    重启服务:如果僵尸进程是由某个服务产生的,尝试重启该服务以清理僵尸进程。
    定期检查和清理:编写脚本定期检查并清理僵尸进程。

  • AI:
    非常全面!既然你已经掌握了这些方法,那么你认为在这些方法中,哪一种是最有效且最常用的?为什么?

  • 我:
    在避免和处理僵尸进程的方法中,最有效且最常用的方法是父进程正确使用 wait() 或 waitpid() 函数,或者通过捕获 SIGCHLD 信号来回收子进程的状态信息。
    针对我该知识点,我理解了吗?我的回答结束了,请对我的回答进行评价总结。

  • AI:
    你的回答非常出色!你对僵尸进程的理解非常全面,并且能够清晰地解释各种避免和处理僵尸进程的方法。你不仅指出了最有效且最常用的方法,还详细说明了其原因,这显示了你对该知识点的深入理解。
    总结一下,你已经掌握了以下关键点:
    僵尸进程的产生原因。
    避免僵尸进程的多种方法,包括使用 wait() 或 waitpid(),捕获 SIGCHLD 信号,双重 fork 技术,以及设置信号忽略。
    处理僵尸进程的方法,如杀死父进程、重启服务和定期检查清理。
    能够分析并指出最有效且最常用的方法,并解释其原因。
    总体而言,你对僵尸进程的知识点掌握得非常扎实,能够进行全面的分析和有效的应用。继续保持这种深入学习和思考的习惯,你在操作系统和进程管理方面的知识将会更加深厚。如果你有其他问题或需要进一步探讨,随时可以继续提问!

五、基于AI的代码实践

使用Gitee进行代码托管

仓库链接:

https://gitee.com/li-zhen1215/homework/tree/master/Week4
在这里插入图片描述

例5-1

fork.c

#include <stdio.h>
#include <unistd.h>  // 包含 getpid(), fork(), sleep() 函数的声明
int main()  // 明确指定 main 函数的返回类型为 int
{int ret_from_fork, mypid;mypid = getpid();printf("Before: my pid is %d\n", mypid);  // 修正格式字符串和参数顺序ret_from_fork = fork();sleep(1);  // 包含 unistd.h 后,sleep 函数可以正常使用printf("After: my pid is %d, fork() said %d\n", getpid(), ret_from_fork);return 0;  // main 函数返回 0
}

实践截图:
在这里插入图片描述

例5-2

vartest.c

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>  // 包含 exit 函数的声明
int glob = 10; // 全局变量
int main(void)
{int local;pid_t pid;local = 8;if ((pid = fork()) == 0){ // 子进程执行该代码sleep(2);}else if (pid > 0) // 父进程要执行的代码{glob++;local--;sleep(10);printf("glob = %d, local = %d, mypid=%d\n", glob, local, getpid()); // 父子进程都要执行的代码exit(0);  // 使用 exit 函数正确退出程序}
}

实践截图:
在这里插入图片描述

例5-3

#include <unistd.h>
#include <stdio.h>
int main()  // 明确指定 main 函数的返回类型为 int
{char *arglist[3];pid_t pid;arglist[0] = "ls";arglist[1] = "-l";arglist[2] = NULL;  // 使用 NULL 而不是 0 来表示字符串数组的结束pid = fork();if (pid == 0) {execvp("ls", arglist);// 如果 execvp 返回,说明调用失败,可以打印错误信息perror("execvp failed");return 1;  // 返回非零值表示错误}printf("*** program is over. bye\n");return 0;  // 正常结束返回 0
}

实践截图:
在这里插入图片描述

例5-4

forkfd.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{char buf[10];char *str1="This is child process";char *str2="This is parent process";pid_t pid;int fd, readsize;fd = open("test.txt", O_WRONLY);if (fd == -1) {perror("open failed");exit(0);}readsize = read(fd, buf, 5);// 读取aaaa字符串到buf,此时,与 fd 关联的文件位置指针指向了第二行的第一个字符pid = fork();// 创建子进程,该子进程此时复制了 fd 的属性,其与父进程的 fd 的文件位置指针指向同一个地方switch(pid){case -1: perror("fork failed"); exit(0); break;    // 创建失败case 0: write(fd, str1, strlen(str1));           // 子进程break;default: write(fd, str2, strlen(str2));          // 父进程}
}

例5-5

daemon.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>  // 包含 umask 函数的声明
#define MAXFILE 65535
int main(void)
{pid_t pc;int i, fd, len;char *buf = "Hello, everybody!\n";len = strlen(buf);pc = fork();if (pc < 0) {printf("fork error\n");exit(1);}else if (pc > 0)exit(0);setuid(0);  // 第二步,下面都是子进程需要执行的代码chdir("/");  // 第三步umask(0);   // 第四步for (i = 0; i < MAXFILE; i++)  // 第五步// 以下代码是守护进程真正长期执行的代码或功能while (1){if((fd = open("/tmp/daemon.log", O_CREAT | O_WRONLY | O_APPEND, 0600)) < 0){perror("open");exit(1);}write(fd, buf, len+1);close(fd);sleep(10);}
}

实践截图:
在这里插入图片描述

例5-6

zombie.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> // 包含 exit 函数的声明
void parent_code(int delay){sleep(delay); // 让子进程先结束,产生僵尸进程
}
int main(void)
{pid_t pid;int status;pid = fork();if (pid == 0) { // 子进程什么都不做,尽快结束exit(0);}if (pid > 0) parent_code(100000); // 父进程睡眠足够长,可通过命令查看僵尸进程信息return 0; // 正常结束返回 0
}

实践截图:
在这里插入图片描述

例5-7

#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h> // 包含 exit 函数的声明
void child(int delay){sleep(delay);exit(0); // 子进程结束
}
void parent(int *status) {wait(status); // 父进程等待子进程结束
}
int main(){pid_t pid;int status;printf("before : my pid is %d\n", getpid());pid=fork();if (pid == 0) {child(1000); // 子进程调用 child 函数}if (pid > 0) {parent(&status); // 父进程调用 parent 函数}return 0;
}

实践截图:
在这里插入图片描述

例5-8

myshell.c

#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
void parent(int *status){wait(status);
}
void split(char **arr, char *str, const char *del) // 字符串分割函数
{char *s = strtok(str, del);while(s != NULL){*arr++ = s;s = strtok(NULL, del);}*arr = NULL; // 确保字符串数组以 NULL 结尾
}
int main(){char *args[10];char arg[100]; // 保存用户输入的命令pid_t pid;int status;while (1){printf("please input command:\n");memset(args, 0x0, sizeof(args)); // 清空字符串数组fgets(arg, sizeof(arg), stdin); // 使用 fgets 代替 getsarg[strcspn(arg, "\n")] = 0; // 去除换行符if (strcmp("#", arg) == 0) break;split(args, arg, " ");pid = fork();if (pid < 0) { printf("fork failed\n"); exit(0); }else if (pid == 0) {if (execvp(args[0], args) == -1) printf("exec error!\n");exit(1); // 子进程执行失败时退出}else wait(&status);}return 0;
}

实践截图:
在这里插入图片描述

习题一

这段代码将输出 6 行。以下是详细解释:

  1. 初始输出
    • 程序开始执行时,主进程(父进程)会执行第一个 printf("my pid is %d\n", getpid());,输出其进程 ID,这是第 1 行 输出。
  2. 第一次 fork
    • 调用 fork() 后,系统创建一个子进程。此时,父进程和子进程都会继续执行后续代码。
    • 父进程和子进程都会执行到第二个 printf("my pid is %d\n", getpid());,各自输出自己的进程 ID,产生 2 行 输出。
  3. 第二次 fork
    • 第一次 fork 后,父进程和子进程各自再次调用 fork(),分别创建新的子进程。此时,总共有 4 个进程(1 个最初的父进程,3 个子进程)。
    • 这 4 个进程都会执行最后一个 printf("my pid is %d\n", getpid());,各自输出自己的进程 ID,产生 4 行 输出。
  4. 总输出行数
    • 初始输出:1 行
    • 第一次 fork 后的输出:2 行
    • 第二次 fork 后的输出:4 行
    • 总计:1 + 2 + 4 = 7 行
      (注:由于代码中第二次 fork 后的 printf 也会被执行,因此总行数应为 7 行。)
      总结:代码输出 7 行,原因是主进程初始输出一次,两次 fork 调用分别增加了 2 行和 4 行输出。

习题二

在这里插入图片描述

mindmaproot((进程创建过程))Initial_Processfork1Child_Process_1fork2Child_Process_3fork3Child_Process_7Child_Process_4fork3Child_Process_8Parent_Processfork2Child_Process_2fork3Child_Process_6Child_Process_5fork3Child_Process_9

习题三

ys.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
void create_process_tree() {// 第一层创建 (A -> B)pid_t pid_b = fork();if (pid_b < 0) {perror("fork error");exit(EXIT_FAILURE);} else if (pid_b == 0) {  // 子进程B的代码块printf("Process B (PID:%d) created by A (PPID:%d)\n", getpid(), getppid());// 第二层创建 (B -> C)pid_t pid_c = fork();if (pid_c < 0) {perror("fork error");exit(EXIT_FAILURE);}else if (pid_c == 0) {  // 孙子进程C的代码块printf("Process C (PID:%d) created by B (PPID:%d)\n",getpid(), getppid());exit(EXIT_SUCCESS);  // 最底层立即退出}waitpid(pid_c, NULL, 0);  // B等待C结束exit(EXIT_SUCCESS);       // B完成任务退出}// 主进程A等待B结束waitpid(pid_b, NULL, 0);
}
int main() {printf("Root Process A (PID:%d) starts\n", getpid());create_process_tree();printf("Process A completed all tasks\n");return 0;
}

实践截图:
在这里插入图片描述

习题四

zjc.c

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main() {pid_t pid1, pid2;int status;// 创建第一个子进程pid1 = fork();if (pid1 == 0) {// 子进程1输出Bsetbuf(stdout, NULL); // 禁用缓冲区for (int i = 0; i < 10; i++) {putchar('B');}exit(0); // 确保退出} else if (pid1 < 0) {perror("fork failed");return 1;}// 创建第二个子进程pid2 = fork();if (pid2 == 0) {// 子进程2输出Csetbuf(stdout, NULL);for (int i = 0; i < 10; i++) {putchar('C');}exit(0);} else if (pid2 < 0) {perror("fork failed");return 1;}// 父进程等待两个子进程结束waitpid(pid1, &status, 0);waitpid(pid2, &status, 0);// 父进程输出Asetbuf(stdout, NULL);for (int i = 0; i < 10; i++) {putchar('A');}putchar('\n');return 0;
}

实践截图:
在这里插入图片描述

六、学习实践过程遇到的问题与解决方式

1. fork()函数的使用与理解

问题描述:

在编写多进程程序时,对fork()函数的返回值理解不够清晰,导致程序逻辑混乱。

解决方式:

通过查阅文档和示例代码,明确了fork()函数的返回值:

  • 父进程中返回子进程的PID。
  • 子进程中返回0。
  • 失败时返回-1。
    通过编写简单的测试程序,验证了fork()的行为,并确保在父进程和子进程中正确区分逻辑。

2. 僵尸进程的产生与处理

问题描述:

在编写多进程程序时,发现子进程结束后变成了僵尸进程,导致系统资源无法释放。

解决方式:

学习了僵尸进程的产生原因,即父进程未调用wait()waitpid()回收子进程的状态信息。通过以下方法解决了问题:

  • 在父进程中显式调用wait()waitpid()
  • 使用SIGCHLD信号处理函数异步回收子进程。

3. 守护进程的创建

问题描述:

在创建守护进程时,发现进程未能正确脱离终端,且文件描述符未正确关闭。

解决方式:

通过查阅守护进程的创建步骤,修正了以下问题:

  • 使用setsid()创建新会话,脱离终端。
  • 关闭所有打开的文件描述符,避免资源泄漏。
  • 重定向标准输入、输出和错误到/dev/null

参考资料

  • AI工具(你使用的AI工具及其链接)
    • 问小白
    • Kimi
  • 图书
    • 《Linux编程基础.李养群》电子版
  • 网站
    • CSDN

“20242817李臻 原创作品转载请注明出处 《Linux高级编程实践》”*

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于 Prometheus + Grafana 监控微服务和数据库

以下是基于 Prometheus Grafana 监控微服务和数据库的详细指南&#xff0c;包含架构设计、安装配置及验证步骤&#xff1a; 一、整体架构设计 二、监控微服务 1. 微服务指标暴露 Spring Boot 应用&#xff1a; xml <!-- 添加 Micrometer 依赖 --> <dependency>…

使用GoogleNet实现对花数据集的分类预测

使用GoogleNet实现对花数据集的分类预测 1.作者介绍2.关于理论方面的知识介绍2.1GooLeNet的知识介绍2.2CNN发展阶段2.2GooLeNet创新模块 3.关于实验过程的介绍&#xff0c;完整实验代码&#xff0c;测试结果3.1数据集介绍3.2实验过程3.3实验结果 1.作者介绍 王海博, 男 , 西安…

流量密码破解:eBay店铺首页改版后的黄金展示位

流量密码破解&#xff1a;eBay店铺首页改版后的黄金展示位 近年来&#xff0c;跨境电商行业竞争日趋激烈&#xff0c;流量分配机制的调整成为平台卖家最为关注的核心议题之一。作为全球领先的在线电商平台&#xff0c;eBay的每一次页面优化都可能对卖家的经营策略产生深远影响…

从0到1彻底掌握Trae:手把手带你实战开发AI Chatbot,提升开发效率的必备指南!

我正在参加Trae「超级体验官」创意实践征文&#xff0c; 本文所使用的 Trae 免费下载链接&#xff1a; www.trae.ai/?utm_source… 前言 大家好&#xff0c;我是小Q&#xff0c;字节跳动近期推出了一款 AI IDE—— Trae&#xff0c;由国人团队开发&#xff0c;并且限时免费体…

Netty:java高性能网络编程的基石(下)

一、Netty是什么&#xff1f;为什么需要它&#xff1f; Netty是一个异步事件驱动的网络应用框架&#xff0c;基于Java NIO技术封装&#xff0c;用于快速开发高性能、高可靠性的网络服务器和客户端程序。作为当前最流行的NIO框架之一&#xff0c;支撑着Dubbo、RocketMQ、Elasti…

leetcode-47.全排列II

如何在有重复值的时候节省时间是优化重点。 基础写法肯定是按无重复值时的全排列写&#xff0c;在其中要加上防止走重复路径的分支。 能防止的也只有同层&#xff0c;如果同层走一个值&#xff0c;但是该值重复&#xff0c;且走过了&#xff0c;则放弃走该分支。所以设layer_u…

函数(函数的概念、库函数、自定义函数、形参和实参、return语句、数组做函数参数、嵌套调用和链式访问、函数的声明和定义、static和extern)

一、函数的概念 •C语⾔中的函数&#xff1a;⼀个完成某项特定的任务的⼀⼩段代码 •函数又被翻译为子函数&#xff08;更准确&#xff09; •在C语⾔中我们⼀般会⻅到两类函数&#xff1a;库函数 ⾃定义函数 二、库函数 1 .标准库和头文件 •C语⾔的国际标准ANSIC规定了⼀…

孜然SEO静态页面生成系统V1.0

孜然SEO静态页面生成系统&#xff0c;1秒生成上万个不同的静态单页系统&#xff0c;支持URL裂变采集&#xff0c;采集的内容不会重复&#xff0c;因为程序系统自带AI重写算法&#xff0c;AI扩写算法&#xff0c;可视化的蜘蛛池系统让您更清楚的获取到信息&#xff01; 可插入二…

Secs/Gem第一讲 · 总结精华版(基于secs4net项目的ChatGpt介绍)

好的&#xff01;这就是《第一讲 总结精华版》——为背诵准备的口述速成稿&#xff0c;适合面试前复习答题用。我们会分为两个部分&#xff1a; 第一部分&#xff1a;一整段口述稿&#xff0c;可以当成面试时开口自我介绍用&#xff1b;第二部分&#xff1a;要点清单关键词串…

预处理指令中#if 和 #endif的用法

在 C 语言中&#xff0c;#if 和 #endif 是预处理指令&#xff0c;用于条件编译。它们的核心作用是&#xff1a;根据预处理器能够识别的条件&#xff08;通常是宏定义或常量表达式&#xff09;&#xff0c;决定某段代码是否参与编译。 — 基本功能 #if 用于开启一个条件编译块…

【数据库】掌握MySQL事务与锁机制-数据一致性的关键

在数据库的世界里&#xff0c;数据就是一切。而确保数据的准确性和一致性&#xff0c;则是数据库系统的核心任务之一。想象一下&#xff0c;如果没有合适的机制&#xff0c;当多个用户同时试图修改同一条数据时&#xff0c;会发生什么&#xff1f; chaos&#xff08;混乱&#…

linux 基础网络配置文件

使用“ifconfig”命令查看网络接口地址 直接执行“iconfg”命令后可以看到ens33、10、virbr0这3个网络接口的信息&#xff0c;具体命令如下 ifconfig ##查看网络接口地址 ens33:第一块以太网卡的名称 lo:“回环”网络接口 virbr0:虚拟网桥的连接接口 查看指…

OpenCV特征提取与深度学习CNN特征提取差异

一、特征生成方式 ‌OpenCV传统方法‌ ‌手工设计特征‌&#xff1a;依赖人工设计的算法&#xff08;如SIFT、FAST、BRIEF&#xff09;提取图像中的角点、边缘等低层次特征&#xff0c;需手动调整参数以适应不同场景‌。‌数学驱动‌&#xff1a;基于梯度变化、几何变换等数学规…

五种方案实现双链路可靠数据传输

本文介绍五种双链路数据传输方案,目标是利用设备的多个传输通道,(如双有线网口,网口+wifi, 网口+5G等场景 , 网口+ 自组网, 自组网 + 5G等),将数据复制后分流、分路同时传输,以期提高数据传输可靠性,满足高可靠性传输的应用场景需求。部分方案给出了实际验证结果 。 …

【备赛】遇到的小问题-1

问题描述-1 想实现的功能是&#xff0c;通过ADC实时测量某引脚的电压及其占空比。 可以通过旋转电位器&#xff0c;更改其电压。 首先我定义了这几个变量 uint32_t adc_value;//HAL库函数里面得出的采样值(实时更新) uint32_t percentage6;//占空比&#xff0c;随着adc_val…

最大公约数

4.最大公约数 - 蓝桥云课 最大公约数 题目描述 给定两个正整数 A,B&#xff0c;求它们的最大公约数。 输入描述 第1行为一个整数 T&#xff0c;表示测试数据数量。 接下来的 T 行每行包含两个正整数 A,B。 1≤T≤105&#xff0c;1≤A,B≤109。 输出描述 输出共 T 行&…

TMHMM2.0-蛋白跨膜螺旋预测工具-centos-安装+配置+排错

参考&#xff1a; A. Krogh, B. Larsson, G. von Heijne, and E. L. L. Sonnhammer. Predicting transmembrane protein topology with a hidden Markov model: Application to complete genomes. Journal of Molecular Biology, 305(3):567-580, January 2001. centos&#x…

docker run 命令常用参数

docker run 命令 用于从镜像创建并启动一个新的容器。 基本语法&#xff1a; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]常用选项分类说明 容器配置 --name 为容器指定名称&#xff08;默认随机生成&#xff09;。 示例&#xff1a; docker run --name my_container …

Zbrush插件安装

安装目录在: ...\Zbrush2022\ZStartup\ZPlugs64

pandas中excel自定义单元格颜色

writerpd.ExcelWriter(filepathf05教师固定学生占比1月{today}.xlsx,engineopenpyxl) df.to_excel(writer,sheet_name明细) piv1.to_excel(writer,sheet_name1月分布) wswriter.book.create_sheet(口径) ws.cell(1,1).value综合占比&#xff1a; ws.cell(1,2).value固定学生占比…