TLPI 第24 章 练习:Process Creation
笔记和练习博客总目录见开始读TLPI。24-1在一个程序执行以下一系列 fork() 调用之后会产生多少个新进程假设所有调用都成功fork();fork();fork();总共会有8个进程新进程有7个。如下图初始进程 P0 ├─ 第一次fork后分裂 │ P0父 │ └─ P1子1 │ ├─ 第二次fork各自分裂 │ │ P0 → P2 │ │ P1 → P3 │ │ ├─ 第三次fork全部再分裂 │ │ │ P0 → P4 │ │ │ P2 → P5 │ │ │ P1 → P6 │ │ │ P3 → P7 │ │ │ │ │ │ 全部8个进程P0 P1 P2 P3 P4 P5 P6 P724-2编写一个程序来演示在 vfork() 之后子进程可以关闭一个文件描述符例如描述符 0而不会影响父进程中的相应文件描述符。原书提供的答案如procexec/vfork_fd_test.c#define_BSD_SOURCE/* To get vfork() declaration from unistd.h in case _XOPEN_SOURCE 700 */#includetlpi_hdr.hintmain(intargc,char*argv[]){switch(fork()){case-1:errExit(vfork);case0:if(close(STDOUT_FILENO)-1)errMsg(close - child);_exit(EXIT_SUCCESS);default:break;}/* Now parent closes STDOUT_FILENO twice: only the second close should fail, indicating that the close(STDOUT_FILENO) by the child did not affect the parent. */if(close(STDOUT_FILENO)-1)errMsg(first close);if(close(STDOUT_FILENO)-1)errMsg(second close);exit(EXIT_SUCCESS);}我修改了close之后的提示这样可以明确是first还是second close。他的解题思路挺巧妙就是如果第一次close失败说明影响了否则就没影响。运行如下$ ./vfork_fd_test ERROR[EBADF Badfiledescriptor]second close我的代码如下代码模板抄自fork(2) #includesignal.h#includestdint.h#includestdio.h#includestdlib.h#includeunistd.hintmain(void){pid_tpid;if(signal(SIGCHLD,SIG_IGN)SIG_ERR){perror(signal);exit(EXIT_FAILURE);}pidvfork();switch(pid){case-1:perror(fork);exit(EXIT_FAILURE);case0:puts(Child exiting.);close(STDOUT_FILENO);exit(EXIT_SUCCESS);default:printf(Child is PID %jd\n,(intmax_t)pid);puts(Parent exiting.);exit(EXIT_SUCCESS);}}运行如下$ ./ex24-2 child - Child exiting. parent - Child is PID16238parent - Parent exiting.我的思路是子进程关闭标准输出后看父进程输出信息是否能成功。不要被题目误导如果把vfork换成fork行为是一样的。但输出顺序是不同的因为vfork保证了子进程先执行。以下是vfork换成fork后的输出$ ./ex24-2 parent - Child is PID16229parent - Parent exiting. child - Child exiting.24-3假设我们可以修改程序源代码如何在某一特定时刻获取进程的核心转储core dump同时让进程继续执行由signal(7)和core(5)可知生成core dump的方式有两种killabort代码如下#includesignal.h#includestdint.h#includestdio.h#includestdlib.h#includeunistd.h#defineTESTSIGSIGABRTintmain(void){pid_tpid;if(signal(SIGCHLD,SIG_IGN)SIG_ERR){perror(signal);exit(EXIT_FAILURE);}pidfork();switch(pid){case-1:perror(fork);exit(EXIT_FAILURE);case0:puts(child - Child exiting.);raise(TESTSIG);puts(child - Never get here.);_exit(0);default:printf(parent - Child is PID %jd\n,(intmax_t)pid);for(;;)pause();puts(parent - Never get here.);}}执行$ ./ex24-3 parent - Child is PID16416child - Child exiting. ^C验证$ coredumpctl list TIME PIDUIDGID SIG COREFILE EXE SIZE Tue2026-07-14 01:52:22 UTC1641610001000SIGABRT present /home/vagrant/tlpi-book/ex/ex24-322.5K其实这道题很简单的但还是花了近1小时原因是发现vfork和fork行为不一。这道题只能用fork而非vfork因为vfork:vfork() differs from fork(2) in that the calling thread is suspended until the child terminates (ei‐ther normally, by calling _exit(2), or abnormally, after delivery of a fatal signal), or it makes acall to execve(2). Until that point, the child shares all memory with its parent, including thestack.24-4.在其他 UNIX 系统实现环境中运行清单 24-5 所示程序fork_whos_on_first.c进行测试以此探究各类系统在调用fork()创建子进程后是如何调度父进程与子进程的。fork_whos_on_first.c在目录procexec下。代码如下#includesys/wait.h#includetlpi_hdr.hintmain(intargc,char*argv[]){intnumChildren,j;pid_tchildPid;if(argc1strcmp(argv[1],--help)0)usageErr(%s [num-children]\n,argv[0]);numChildren(argc1)?getInt(argv[1],GN_GT_0,num-children):1;setbuf(stdout,NULL);/* Make stdout unbuffered */for(j0;jnumChildren;j){switch(childPidfork()){case-1:errExit(fork);case0:printf(%d child\n,j);_exit(EXIT_SUCCESS);default:printf(%d parent\n,j);wait(NULL);/* Wait for child to terminate */break;}}exit(EXIT_SUCCESS);}运行如下parent总是优先于child调度$ ./fork_whos_on_first50parent0child1parent1child2parent2child3parent3child4parent4child以上是在OEL 9.7上的行为。其他的UNIX环境可使用FreeBSD。参见VirtualBox上安装FreeBSD在FreeBSD上编译TLPI示例代码在FreeBSD下的行为是一样的$uname-aFreeBSD freebsd14.4-RELEASE FreeBSD14.4-RELEASE releng/14.4-n273675-a456f852d145 GENERIC amd64 tlpifreebsd:~/tlpi-dist/procexec $ ./fork_whos_on_first50parent0child1parent1child2parent2child3parent3child4parent4child24-5假设在清单 24-6 的程序中子进程也需要等待父进程完成某些操作。为了强制执行这一点需要对程序进行哪些修改清单 24-6即procexec/fork_sig_sync.c。代码如下/* Listing 24-6 */#includesignal.h#includecurr_time.h/* Declaration of currTime() */#includetlpi_hdr.h#defineSYNC_SIGSIGUSR1/* Synchronization signal */staticvoid/* Signal handler - does nothing but return */handler(intsig){}intmain(intargc,char*argv[]){pid_tchildPid;sigset_tblockMask,origMask,emptyMask;structsigactionsa;setbuf(stdout,NULL);/* Disable buffering of stdout */sigemptyset(blockMask);sigaddset(blockMask,SYNC_SIG);/* Block signal */if(sigprocmask(SIG_BLOCK,blockMask,origMask)-1)errExit(sigprocmask);sigemptyset(sa.sa_mask);sa.sa_flagsSA_RESTART;sa.sa_handlerhandler;if(sigaction(SYNC_SIG,sa,NULL)-1)errExit(sigaction);switch(childPidfork()){case-1:errExit(fork);case0:/* Child *//* Child does some required action here... */printf([%s %ld] Child started - doing some work\n,currTime(%T),(long)getpid());sleep(2);/* Simulate time spent doing some work *//* And then signals parent that its done */printf([%s %ld] Child about to signal parent\n,currTime(%T),(long)getpid());if(kill(getppid(),SYNC_SIG)-1)errExit(kill);/* Now child can do other things... */_exit(EXIT_SUCCESS);default:/* Parent *//* Parent may do some work here, and then waits for child to complete the required action */printf([%s %ld] Parent about to wait for signal\n,currTime(%T),(long)getpid());sigemptyset(emptyMask);if(sigsuspend(emptyMask)-1errno!EINTR)errExit(sigsuspend);printf([%s %ld] Parent got signal\n,currTime(%T),(long)getpid());/* If required, return signal mask to its original state */if(sigprocmask(SIG_SETMASK,origMask,NULL)-1)errExit(sigprocmask);/* Parent carries on to do other things... */exit(EXIT_SUCCESS);}}此程序为父子进程设置了一个联络信号SYNC_SIG子进程做一些工作后发送联络信号而父进程则等待这个信号。运行如下$ ./fork_sig_sync[02:39:5816571]Parent about towaitforsignal[02:39:5816572]Child started - doing some work[02:40:0016572]Child about to signal parent[02:40:0016571]Parent got signal如果父进程也需要做一些工作则将之前的同步机制再使用一次即可即子进程等待父进程的同步信号而父进程在完成任务后向子进程发送同步信号。代码如下#includesignal.h#includecurr_time.h/* Declaration of currTime() */#includetlpi_hdr.h#defineSYNC_SIGSIGUSR1/* Synchronization signal */staticvoid/* Signal handler - does nothing but return */handler(intsig){}intmain(intargc,char*argv[]){pid_tchildPid;sigset_tblockMask,origMask,emptyMask;structsigactionsa;setbuf(stdout,NULL);/* Disable buffering of stdout */sigemptyset(blockMask);sigaddset(blockMask,SYNC_SIG);/* Block signal */if(sigprocmask(SIG_BLOCK,blockMask,origMask)-1)errExit(sigprocmask);sigemptyset(sa.sa_mask);sa.sa_flagsSA_RESTART;sa.sa_handlerhandler;if(sigaction(SYNC_SIG,sa,NULL)-1)errExit(sigaction);switch(childPidfork()){case-1:errExit(fork);case0:/* Child *//* Child does some required action here... */printf([%s %ld] Child started - doing some work\n,currTime(%T),(long)getpid());sleep(2);/* Simulate time spent doing some work *//* And then signals parent that its done */printf([%s %ld] Child about to signal parent\n,currTime(%T),(long)getpid());if(kill(getppid(),SYNC_SIG)-1)errExit(kill);/* Now child can do other things... */if(sigsuspend(emptyMask)-1errno!EINTR)errExit(sigsuspend);printf([%s %ld] Child got signal\n,currTime(%T),(long)getpid());_exit(EXIT_SUCCESS);default:/* Parent *//* Parent may do some work here, and then waits for child to complete the required action */printf([%s %ld] Parent about to wait for signal\n,currTime(%T),(long)getpid());sigemptyset(emptyMask);if(sigsuspend(emptyMask)-1errno!EINTR)errExit(sigsuspend);printf([%s %ld] Parent got signal\n,currTime(%T),(long)getpid());/* If required, return signal mask to its original state */if(sigprocmask(SIG_SETMASK,origMask,NULL)-1)errExit(sigprocmask);/* Parent carries on to do other things... */printf([%s %ld] Parent started - doing some work\n,currTime(%T),(long)getpid());sleep(2);/* Simulate time spent doing some work */printf([%s %ld] Child about to signal parent\n,currTime(%T),(long)getpid());if(kill(childPid,SYNC_SIG)-1)errExit(kill);exit(EXIT_SUCCESS);}}

相关新闻

JUC-II CPU微指令编码实战:从寻址方式到指令集的全流程设计与验证

JUC-II CPU微指令编码实战:从寻址方式到指令集的全流程设计与验证

1. JUC-II CPU微指令设计基础 第一次接触微指令设计时,我和许多同学一样被各种专业术语搞得晕头转向。直到在实验室熬了三个通宵调试MOV指令后,才真正理解微指令就是CPU内部的"操作手册"。JUC-II作为教学用模型机,其微指令设计遵循…

2026/7/16 9:12:15阅读更多 →
深度解密BetterGI分辨率自适应技术:从像素困境到智能适配的突破之路

深度解密BetterGI分辨率自适应技术:从像素困境到智能适配的突破之路

深度解密BetterGI分辨率自适应技术:从像素困境到智能适配的突破之路 【免费下载链接】better-genshin-impact 📦BetterGI 更好的原神 - 自动拾取 | 自动剧情 | 全自动钓鱼(AI) | 全自动七圣召唤 | 自动伐木 | 自动刷本 | 自动采集/挖矿/锄地 | 一条龙 |…

2026/7/16 9:12:15阅读更多 →
多普勒模式为归零时,运行6小时打开载波平滑伪码开关后的测距结果优于不开平滑的结果,20小时后比不开平滑的结果差,是为什么

多普勒模式为归零时,运行6小时打开载波平滑伪码开关后的测距结果优于不开平滑的结果,20小时后比不开平滑的结果差,是为什么

1 原因 这是一个非常经典且极具洞察力的工程现象!你观察到的这个“先变好,后变差”的过程,恰恰完美地揭示了载波平滑伪码(Hatch滤波器)在存在“系统误差”时的“双刃剑”特性。 简单来说:6小时时&#xff0…

2026/7/16 9:12:15阅读更多 →
电源工程师实战:调试技巧与效率优化全解析

电源工程师实战:调试技巧与效率优化全解析

1. 电源工程师的日常调试挑战作为一名从业12年的电源工程师,我每天的工作台前总少不了各种调试设备——示波器探头缠绕在隔离变压器上,电子负载的散热风扇呼呼作响,桌角堆着不同规格的功率MOSFET和磁性元件。这个场景下,最让人头疼…

2026/7/16 17:59:35阅读更多 →
OpenAI Codex与ChatGPT Work使用限制取消及开发效率提升指南

OpenAI Codex与ChatGPT Work使用限制取消及开发效率提升指南

最近在技术社区中,不少开发者在使用 OpenAI Codex 和 ChatGPT Work 时遇到了使用限制的问题,特别是 Plus、Pro 和 Business 订阅用户原本存在的 5 小时使用限制。这一限制对于需要长时间进行复杂项目开发的用户来说确实带来了不少困扰。本文将详细解析这…

2026/7/16 17:59:35阅读更多 →
Volatility实战:从零搭建CTF内存取证环境与高频命令速查手册

Volatility实战:从零搭建CTF内存取证环境与高频命令速查手册

1. 环境准备:多平台安装指南 第一次接触内存取证时,最头疼的就是工具安装。我在2018年参加DEFCON CTF时,就因为Volatility环境配置问题浪费了两小时。下面分享经过实战验证的安装方案,覆盖Kali和Windows两大主流平台。 1.1 Kali…

2026/7/16 17:59:35阅读更多 →
【微软认证AI插件深度评测】:Top 4 Excel AI工具横向对比,含响应延迟、准确率与合规红线数据

【微软认证AI插件深度评测】:Top 4 Excel AI工具横向对比,含响应延迟、准确率与合规红线数据

更多请点击: https://intelliparadigm.com 第一章:AI Excel 数据处理 现代数据分析工作流中,Excel 仍是最广泛使用的结构化数据入口之一。AI 技术正深度融入 Excel 生态,通过智能公式补全、自然语言查询、自动化清洗与预测建模&a…

2026/7/16 17:59:35阅读更多 →
CAN总线数据收发实战:从SocketCAN工具到协议帧解析

CAN总线数据收发实战:从SocketCAN工具到协议帧解析

1. CAN总线基础与SocketCAN环境搭建CAN(Controller Area Network)总线是德国Bosch公司在上世纪80年代专为汽车电子系统设计的串行通信协议,如今已广泛应用于工业控制、医疗设备等领域。它的核心优势在于多主机架构、非破坏性仲裁机制和强大的…

2026/7/16 17:59:35阅读更多 →
为什么Discord服务器都在用GiveawayBot?10个让抽奖效率提升10倍的理由

为什么Discord服务器都在用GiveawayBot?10个让抽奖效率提升10倍的理由

为什么Discord服务器都在用GiveawayBot?10个让抽奖效率提升10倍的理由 【免费下载链接】GiveawayBot 🎉 Hold giveaways quickly and easily on your Discord server! 项目地址: https://gitcode.com/gh_mirrors/gi/GiveawayBot 在当今社交媒体和…

2026/7/16 17:54:34阅读更多 →
VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异当你在VSCode中启动一个新的TypeScript项目时,第一个技术决策往往从安装方式开始。这个看似简单的选择——全局安装还是项目本地安装——实际上会深刻影响你的开发流程、团队协作和…

2026/7/16 8:28:11阅读更多 →
智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手 【免费下载链接】zhihuishu 智慧树刷课插件,自动播放下一集、1.5倍速度、无声 项目地址: https://gitcode.com/gh_mirrors/zh/zhihuishu 智慧树刷课插件是一款专为智慧树在线教育平台设计的Chrome浏…

2026/7/16 6:53:04阅读更多 →
Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL 你是否在GOG或Epic Games Store购买了心仪的游戏…

2026/7/16 12:02:41阅读更多 →
A--10 Codex Review与GitHub PR工作流实战指南:从代码审查到安全合并

A--10 Codex Review与GitHub PR工作流实战指南:从代码审查到安全合并

摘要:本文系统讲解如何利用Codex App的Review功能与GitHub PR工作流,实现从代码修改到安全合并的完整流程。涵盖Review面板深度使用、/review命令实战、GitHub Connector配置、PR描述撰写技巧,以及常见问题排查方法。通过多个实战案例和流程图,帮助开发者建立高效的AI辅助代…

2026/7/16 0:00:38阅读更多 →
遗传算法解5皇后问题:从Hello World到工业优化的进化实验室

遗传算法解5皇后问题:从Hello World到工业优化的进化实验室

1. 项目概述:为什么用遗传算法解5皇后问题,而不是直接回溯?我带过十几届算法课,也给不少初创团队做过AI架构咨询。每次讲到组合优化问题,学生和工程师的第一反应永远是“写个回溯试试”。这没错——55棋盘上找所有合法…

2026/7/16 0:00:38阅读更多 →
5.1V稳压管输出为何只有4.7V?工作电流与负载影响分析

5.1V稳压管输出为何只有4.7V?工作电流与负载影响分析

前几天调试一个简单的电源模块,用到了5.1V稳压管。电路接好,上电测试,万用表一量——输出居然只有4.7V。第一反应是稳压管坏了,换了一个新的,结果还是4.7V。这让我想起很多初学者都会遇到的困惑:明明标称5.…

2026/7/16 0:00:38阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/15 15:50:47阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/16 8:58:42阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/16 17:10:26阅读更多 →