96SEO 2026-02-19 17:46 0
当一个进程调用fork后就会创建出一个新进程新进程是子进程原来的进程是父进程但是却可能执行不同的代码

上面的结果可以充分证明父子进程执行不同代码的事实id值0的就是子进程id值0(即返回子进程的pid)的是父进程
结合地址空间的知识一个父进程一个子进程各自都有地址空间都有页表所以用的虚拟地址相同而物理内存其实是被映射到不同区域的所以同一个变量id会有两个不同的值所以父子进程会执行不同的代码
父子进程的代码通常是共享的父子进程不写入时数据也是共享的而当任意一方试图写入的时候便以写时拷贝的方式再拷贝一份出来。
正是有写时拷贝技术的存在所以父子进程得以彻底的分离保证了进程的独立性
首先在进程终止时操作系统会释放进程申请的相关内核数据结构(PCB结构体、页表...)、对应的数据和代码
0这个0是什么这个返回值的意义是什么必须要是0吗其他值比如5可以吗
其实这里的0是进程退出码返回值如果是0就说明是成功运行且结果正确而如果是非0则说明运行的结果是不正确的
而这里的非0值是有无数个的不同的非0值就可以表示不同的错误原因这样就可以在我们的程序运行结束后如果结果不正确就能清楚的知道错误的原因
main函数返回值的意义是返回给上一级进程用于判断进程执行结果对还是不对
我们使用退出码验证结果是否正确从1加到3结果应该是6所以判断一下如果结果不等于6则改变退出码ret变为1否则就是正常返回0
其中strerror就是将一个整数转化成字符串描述所以可以查看每一个非0值表示的意义
我们平时ls打印文件信息如果ls后面的文件不存在就会报这样的错误提示
而我们刚刚打印出来的退出码的含义其中2后面对应的错误信息就是这个所以我们执行一下echo
还需要注意的是如果进程终止是第三个情况程序崩溃而终止这时的退出码无意义这种情况一般都是退出码对应的return语句没有被执行
执行到return语句前面就终止了并且查看退出码也是5结果正确
而return在普通函数中表示函数调用结束在main函数中才代表进程退出
可以发现退出码并不是调用的add函数中的return值而是main函数中的return值
可以发现在调用add函数中最后的exit(10)而退出码也是10说明exit在任何地方调用都表示直接终止进程
正常情况下由于我们的hello后面没有\n换行所以程序是将hello先放到缓冲区中去执行后面的sleep(2)和exit(5)这时看下面的结果
我们可以发现如果是_exit终止进程前并没有打印出缓冲区的内容
exit终止进程前会执行用户清理函数打印缓冲区的内容关闭流...
所以上面的代码exit等看到打印的结果而_exit则看不到结果
父进程创建出子进程是要子进程完成任务的那么子进程是否完成又该如何处理是进程等待需要做的
如果子进程结束父进程不管子进程就可能会造成僵尸进程造成内存泄漏的问题
首先运行fork函数创建出子进程并且子进程执行三次就退出而父进程继续执行
还没运行可执行程序proc时用ps指令查看进程发现只有grep进程因为我们在执行这个命令
可以发现在刚开始运行时子进程还没有终止时进程状态是S而子进程运行三次终止后只剩父进程执行时再观察进程信息可以看到子进程的进程状态变为了Z即僵尸状态后面也有defunct表示它是无效的
先执行一次父进程的printf语句然后sleep7秒由于子进程执行每次循环都sleep1秒执行三次就退出所以三秒后子进程就是僵尸进程
我们设置父进程sleep7秒再执行下面的代码前三秒父子进程都正常三秒后子进程变成僵尸进程7秒后父进程执行wait就可以清楚观察到子进程变成僵尸进程后父进程执行wait所造成的结果是什么
一共用ps指令查看了三次第一次是子进程还没有执行完三次父子进程正常运行
第二次是子进程执行完三次已经退出而父进程还在sleep没有执行wait的进程状态
可以看到第一次pid为25490的子进程的进程状态是S父子进程正常运行
第二次pid为25490的子进程的进程状态为Z子进程已经退出进程变为僵尸进程父进程正常运行
第三次父进程执行完wait后可以发现pid为25490的子进程已经被父进程回收所以进程列表看不见pid为25490的子进程了
上面就是执行完wait后的情况所以之后编写多进程都会使用fork
同样包含在头文件sys/types.h和sys/wait.h中
status是一个输出型参数我们通过waitpid获得子进程的退出结果想用status来标识子进程退出的结果是什么如果不关心可以为NULL
下面将使用wait时的代码做以改变将wait改为waitpid
其中框住的部分第三个参数为0是父进程默认在阻塞状态去等待子进程状态变化即等待子进程退出
与wait一样刚开始父子进程正常执行时都是S子进程执行完毕退出状态变为Z即僵尸进程然后父进程执行waitpid回收子进程资源僵尸进程被回收
用waitpid也完成了进程等待这就是回收僵尸进程的两种方法wait、waitpid
下面详细说下status就status的构成来说status不是按照整数来整体使用的而是按照比特位的方式将32位进行划分我们只学习低16位
而这低16位中次低的8位表示的是进程的退出码所以如果想打印出子进程退出码需要将status的先右移8位将次低的8位移到低位的8位然后按位与0xFF就可以得到这低16位中次低8位的值即进程的退出码
所以父进程就可以通过子进程的退出码是0还是非0来确定子进程运行有没有成功结果是否正确如果失败错误码是什么表示什么原因失败的
进程终止的情况三是进程异常退出或是崩溃其实本质就是操作系统杀掉了进程
而我们上面说到进程退出码是status的次低8位比特位而终止信号则是最低7个比特位倒数第八个比特位是core
我们只需要关注前31个普通信号其中第9个我们曾经还用于终止进程
代码中加了红框框起来的由于最低7位是终止信号所以status与0x7F按位与得到的就是最低7位的值0x7F即0000......0111
接下来尝试一下异常情况子进程直接崩溃即一个数除0时观察子进程的信号编号情况
编译时给了一个警告warning分母不能为0但不是报错依然可以运行在子进程循环第一次的时候就终止程序了程序崩溃可以看到报的信号编号为8
这时子进程收到的信号编号为8不为0表示不正常进程崩溃了这时的退出码0无意义
子进程收到的信号编号为11不为0同样表示不正常进程崩溃了这时的退出码0同样无意义
我们将子进程while循环的num不--就会导致子进程死循环这时父进程等不到子进程结束于是使用外力kill直接杀掉进程如下
28223相当于直接杀死进程这时的信号编号是9即我们经常用到的SIGKILL
而kill杀掉了进程子进程代码无法确定是否运行完毕所以退出码同样没有任何意义
我们其实不用每次都是使用这种按位与或者右移再按位与的方法对status进行二进制处理系统中给我们提供了宏
WIFEXITED(status)若正常终止子进程则为真检测是否正常退出
WEXITSTATUS(status)若WIFEXITED为真则提取子进程退出码查看子进程退出码
经过上面知识的铺垫我们就可以理解父进程通过wait/waitpid拿到子进程的退出结果而不用全局变量是因为进程是具有独立性的改变数据时会发生写时拷贝父进程是无法拿到的并且还有上面说到的子进程的信号编号通过全局变量也是无法做到的
并且还有一个问题进程是具有独立性的那子进程退出了子进程的退出码也是子进程的数据父进程为什么可以拿到其中的wait/waitpid是怎么做到的
其实很简单在子进程变为僵尸进程后父进程回收子进程资源其中子进程的PCB结构体是保留下来的而这个PCB结构体中是保留了子进程的退出结果信息的而wait/waitpid就是系统调用接口相当于操作系统所以是有权限调用PCB结构体中的信息的所以本质其实就是父进程通过wait/waitpid系统调用接口读取了子进程的PCB结构体里的信息因此父进程可以拿到子进程的退出码就可以很好地解释了
options是waitpid的第三个参数默认为0代表阻塞等待
夯住了本质就是这个进程没有被调度也就是要么是在阻塞队列中要么是等待被调度
之前的阻塞等待时子进程如果没有运行结束父进程是会一直等待子进程运行结束的而现在的非阻塞等待改变代码如下
观察结果可知非阻塞等待中在子进程还没有退出时父进程不像阻塞等待那样什么都不干父进程是可以边执行任务边等待子进程的
前面说到fork()后父子会各自执行父进程代码的部分父子代码共享数据写时拷贝那么子进程想执行一个全新的程序时就要引入进程程序替换的概念了
程序替换是通过特定的的接口加载磁盘上全新的程序(代码数据)进而加载到调用程序的地址空间中从而让子进程执行其他程序
而进程替换这个过程原本的PCB结构体、地址空间等内核数据结构都不发生改变只是将新的磁盘上的程序加载到内存中并和当前进程的页表建立映射关系即可所以进程替换并没有创建新的子进程
而程序替换的原因就是是和应用场景有关的有时候我们必须要程序替换
第二个参数arg及后面的...我们在Linux命令行上怎么填就在这里怎么填最后一个必须是NULL表示参数传递完毕
使用which查看ls路径是/usr/bin/ls所以在代码做以修改
execl第一个参数传入ls的路径第二个参数及后面的参数按命令行的写法顺序写入即ls--colorauto-a-l最后以NULL结尾其中--colorauto是ls打印时所带的颜色
这是因为execl是程序替换在调用该函数成功后会将当前进程的所有代码和数据都进行替换其中包括已经执行的和没有执行的
根据这个性质我们可以得到execl不需要的返回值的理由如果有返回值调用成功的时候会将当前进程所有代码都替换包括返回值这时的返回值无意义
如果调用失败我们只需要在execl下面写exit即可这样成功了不会执行exit失败才执行
如下面的例子path里传入一个完全不对的路径在下面加上exit(6)
这时调用失败就不会再执行下面的代码了并且查看退出码是我们设置的6
上面进程替换的例子是不创建子进程时的例子下面都是在创建子进程的前提下的例子
创建子进程是为了不会影响父进程我们需要父进程执行读取数据、解析数据、指派进程执行的功能如果在进程替换时不创建子进程那么替换的就是父进程所以需要创建子进程
execl函数以l结尾可以看做list我们需要将参数一个一个传入
而execv函数以v结尾可以看做vector我们则需要将所用参数传入一个指针数组argv中然后将argv当做参数传入execv函数中
通过观察execlp和execl的第一个参数一个是path一个参数是file
这个execlp函数结尾是p可以看做它会自己在环境变量PATH中进行查找不需要告诉它要执行的程序的路径
这里的execvp与execlp都是以p结尾所以也不需要带路径
如果我们当前文件夹中有两个文件Makefile如何书写可以做到make时生成两个可执行如下所示
我们当前有两个文件exec.c和test.cMakefile改变如下
这时当Makefile从上往下被扫描时需要生成的目标文件第一个遇到的就是all而all依赖的是exec和test所以就会往下扫描将exec与test的可执行程序执行完all的依赖条件具备后想执行all的依赖方法发现all没有依赖方法所以Makefile就结束了在clean中也删除两个可执行文件
以后如果还想make生成更多的可执行文件只需在all后面空格为间隔继续跟即可
main函数后面的参数argc、argv叫做命令行参数因为我们生成的可执行程序是test假设执行的操作是test
所以这里的第一个参数argc是2表示test是一个-a或-b是第二个
因此第一个if语句就是与argc有关判断是否为2为2再往下执行不为2直接exit退出进程
我们的test.c程序写好了那么如何在exec.c中让子进程执行我自己写的C/C代码例如让exec的子进程执行test.c
里面的/home/fcy/lesson3/test是绝对路径的表示也可以使用相对路径表示即./test
使用execl第一参数传入可执行程序test的路径第二个参数及后面的参数按命令行的形式填入即test
成功在exec可执行程序中执行我们自己写的代码test.c的可执行程序
这里的execle比上面说到过的execl多了一个e这里的e可以看做环境变量
我们想要在exec的子进程中调用test所以在test中实现我们自己的环境变量MY_VAR之后由exec的子进程调用即可
由execle的第三个参数类型可以看出传入的环境变量类型也是一个指针数组
所以在exec.c中自己创建一个环境变量MY_VAR在指针数组env中接着在子进程中调用execle函数最后一个参数传入env第一个参数中的路径是可执行程序test
所以到这里就可以明白之前说到过的环境变量可以被子进程继承具有全局属性的原因main函数的命令行参数三个分别是int
env[]其中第三个参数env就是环境变量所以在子进程执行execle时最后一个参数传入env就可以继承父进程的环境变量
经过上面的例子可以非常清楚看到execvpe就只是比execvp多了一个环境变量参数这个参数的用法具体参照execle就不具体说明了
可以发现execve是2号即系统调用而上面的六个函数都是3号是系统提供的基本封装
也可以说上面六个的函数底层就是execve传入数据根据不同的情况传入这几种不同的函数中然后经过处理变为execve的三个参数最终调用的都是execve
之所以提供这些封装就是因为我们遇到的场景不一样为了满足这些不同的调用场景
//第二次调用strtok如果还是解析原字符串传入NULL50
//因为子进程被替换后会exit退出再次回到循环最开始的地方53
//路径并没有发生改变所以这种需要父进程执行的就是内置命令54
//如果cd后面有命令则调用函数chdir改变路径然后continue58
printf(进程退出码%d\n,WEXITSTATUS(status));78
实际执行的和我们命令行上的操作基本类似底层使我们自己刚刚模拟实现的简易的shell能够执行简单的指令如下
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback