在计算机科学的演进历程中,进程概念的出现标志着操作系统设计从批处理系统向多任务系统的重大飞跃。进程作为现代操作系统资源分配的基本单位,其创建、执行与终止构成了计算机程序生命周期管理的核心框架。Linux作为开源操作系统的典范,其进程管理机制体现了Unix哲学的简洁与优雅。本文旨在深入剖析Linux系统中进程创建的关键技术fork函数、进程终止的多种方式及其内在原理,为读者呈现一幅进程生命周期管理的清晰画卷。理解这些底层机制不仅是系统编程的基石,更是洞察操作系统设计思想的窗口。
在Linux中fork是一个非常重要的函数,它从已存在的进程中创建一个新的进程 。新的进程为子进程,而原来的进程为父进程。
#include <unistd.h>//包含的头文件
pid_t fork(void);
返回值:子进程中返回0,⽗进程返回⼦进程id,出错返回-1
总而言之: 进程调用fork,当控制转移到内核中的fork代码后,内核做: • 分配新的内存块和内核数据节后给子进程 • 将父进程部分数据结构内容拷贝到子进程中 • 添加子进程到系统进程列表当中 • fork返回,开始调度器调度

我们来看一段代码:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main( void )
{
pid_t pid;
printf("Before: pid is %d\n", getpid());
if ((pid=fork()) == -1 )
{
perror("fork()");
exit(1);
}
printf("After:pid is %d, fork return %d\n", getpid(), pid);
sleep(1);
return 0;运行结果:


结论:所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。
我们先说结论:子进程返回
0,父进程返回子进程的pid所以我们可以利用这个结论来让父进程和子进程执行不同的执行流(代码块)
观察如下代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
printf("i am parent,pid: %d, ppid %d\n\n", getpid(), getppid());
pid_t id = fork();
if(id == 0)
{
while(1)
{
printf("i am child,pid: %d, ppid %d\n", getpid(), getppid());
sleep(1);
}
}
else if(id > 0)
{
while(1)
{
printf("i am parent,pid: %d, ppid %d\n", getpid(), getppid());
sleep(1);
}
}
else
{
peeor("fork()");
exit(1);
}
return 0;
}运行结果如下:

关于写实拷贝,我们有在进程概念(六)中简单提到过:【点击进入】

写时拷贝是操作系统为优化进程创建而设计的内存管理策略。当父进程创建子进程时,操作系统并不立即复制父进程的数据段,而是让父子进程共享同一物理内存页,仅将相关页表项标记为只读权限。
因为有写时拷贝技术的存在,所以父子进程得以彻底分离离!完成了进程独立性的技术保证! 写时拷贝是⼀种延时申请技术,可以提高整机内存的使用率。
fork函数创建子进程也可能会失败,有以下两种情况:
我们看一段代码:
#include <stdio.h>
int main()
{
printf("hello world\n");
return 0;
}我们从最开始学习C语言的时候结尾都是return 0,那么有没有考虑过为什么是return 0呢?
当我们用echo $?查看最近一次进程返回的退出码的时候发现也是0,这是为什么呢,有没有可能返回1,2,3等等?

其实0是程序执行成功后返回的退出码,除0以外的任何返回的退出码都是都说明有错误,以下图片介绍一下常见的退出码及其含义:

0 表⽰命令执⾏⽆误,这是完成命令的理想状态。1我们也可以将其解释为 “不被允许的操作”。例如在没有 sudo 权限的情况下使⽤yum;再例如除以 0 等操作也会返回错误码 1 ,对应的命令为 let a = 1/0strerror函数来获取退出码对应的描述strerror函数可以用来获取退出码的对应的描述。
#include <stdio.h>
#include <string.h>
int main()
{
int i = 0;
for(; i < 200; i++)
{
printf("%d: %s\n", i, strerror(i));
}
return 0;
}
这里我们可以看到从0~133个错误码,其实具体个数取决于内核版本和配置。
由于上述的原理加上我们知道error储存最近一次的错误码我们可以得出一个结论:程序运行中发生错误时,可以通过
errno获取系统错误码,并使用strerror()将其转换为可读信息。
代码如下:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{
int ret = 0;
char* daitou = (char*)malloc(4000000000);
if(daitou == NULL)
{
ret = errno;
printf("%d: %s\n",errno, strerror(errno));
}
else
{
printf("malloc success\n");
}
return ret;
}结果:

如此我们便能够看到通过 errno 获取系统错误码12并且映射到合适的退出码,并使用 strerror() 将其转换为可读信息:无法分配内存。
正常终止(可以通过echo $?查看进程退出码)
main返回exit_exit异常退出:
5. ctrl + c,信号终止
return和exit的区别

区别一:
我们观看图片可以看到exit是包含在 stdlib.h 的头文件,然后参数status如下:
exit(0); // 表示成功(最常用)
exit(1); // 表示失败
exit(42); // 自定义退出码(0-255之间)代码示例如下:
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("hello linux\n");
//exit(17);
//return 7;
}结果如下:

我们可以发现除了0和1,其他status是在0~255之间随机。
区别二:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void show()
{
printf("hello linux, begin\n");
printf("hello linux, begin\n");
printf("hello linux, begin\n");
//return;
//exit(11)
printf("hello linux, end\n");
printf("hello linux, end\n");
printf("hello linux, end\n");
}
int main()
{
show();
printf("hello linux\n");
return 0;
}
即任意位置exit被调用都表示进程直接退出,但是return在其它函数中被调用只表示当前函数返回。

exit的代码现象上述已经展示过,我们来看看_exit的
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
printf("hello linux");
exit(17);
_exit(17);
}
关于缓冲区的位置,我们在接下来的内容进行讲解!
Linux进程管理机制的精髓在于平衡效率与安全性,兼顾资源利用与进程隔离。从fork函数的写时拷贝优化到exit系列函数的缓冲区处理,每一个设计决策都体现了操作系统设计者对性能与稳定性的深刻思考。进程退出码体系不仅为程序间通信提供了标准化接口,更是构建可靠软件系统的重要保障。通过深入理解进程创建与终止的内在机制,开发者能够编写出更加健壮、高效的系统程序,在资源受限的环境中实现最优的性能表现。这些底层知识构成了系统编程的坚实基础,也是通往高级操作系统理解的必经之路。