欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 名人名企 > 【Linux篇】操作系统揭秘:进程创建、等待与终止的无缝衔接

【Linux篇】操作系统揭秘:进程创建、等待与终止的无缝衔接

2025/4/11 13:55:57 来源:https://blog.csdn.net/braveact/article/details/146966711  浏览:    关键词:【Linux篇】操作系统揭秘:进程创建、等待与终止的无缝衔接

进程生命周期揭秘:从启动到替换的完整过程

  • 一. 进程创建
    • 1.1 基本概念
    • 1.2 创建新进程的方法
      • 1.2.1 初识fork函数
      • 1.2.2 fork函数返回值
      • 1.2.3 写时拷⻉
      • 1.2.4 fork调用失败原因
  • 二. 进程终止
    • 2.1 基本概念
    • 2.2 进程退出场景
    • 2.3 进程常见退出方法
      • 2.3.1 退出码
      • 2.3.2 _exit函数和exit函数区别
  • 三. 进程等待
    • 3.1 基本概念
    • 3.2 为什么有进程等待
    • 3.3 进程等待方法
      • 3.3.1 wait系统调用
      • 3.3.2 waitpid

本文将介绍进程的创建、终止、等待和程序替换四个关键过程,帮助读者深入理解操作系统如何管理进程生命周期。进程创建涉及操作系统如何为新进程分配资源并初始化环境,进程终止则描述了操作系统如何清理资源并回收内存。等待过程讲解了父子进程之间的同步机制,以及如何通过进程调度实现资源共享和任务协调。程序替换则涉及操作系统如何在内存不足时通过交换技术,确保多个进程得以高效运行。这些过程共同作用,保证了多任务环境中的进程安全与高效运行,为操作系统的核心功能提供了支持。

💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力!
👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗?别忘了点赞、收藏并分享给更多的小伙伴哦!你们的支持是我不断进步的动力!
🚀 分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对Linux OS感兴趣的朋友,让我们一起进步!

一. 进程创建

1.1 基本概念

进程创建是操作系统中启动一个新进程的过程。通常,进程创建由现有进程发起,称为父进程。创建新进程时,操作系统会分配资源(如内存、文件句柄等),并初始化进程控制块(PCB)。父进程通过调用系统调用(如UNIX中的fork()或Windows中的CreateProcess())来创建子进程。子进程通常会继承父进程的一些资源和属性,但也可以根据需要进行修改。进程创建后,子进程会与父进程并行执行,可能有自己的执行路径,也可能通过exec()等系统调用加载新程序。

1.2 创建新进程的方法

1.2.1 初识fork函数

fork()函数用于创建一个新的子进程,子进程是父进程的副本,拥有独立的进程ID。返回值区分父进程(返回子进程ID)和子进程(返回0)。
在这里插入图片描述
进程调用fork后,当控制转移到内核中的fork代码后,内核做:

  1. 分配新的内存块和内核数据结构给⼦进程
  2. 将⽗进程部分数据结构内容拷⻉⾄⼦进程
  3. 添加⼦进程到系统进程列表当中
  4. fork返回,开始调度器调度

fork之前⽗进程独⽴执⾏,fork之后,⽗⼦两个执⾏流分别执⾏。注意,fork之后,谁先执⾏完全由调度器决定。

1.2.2 fork函数返回值

  • ⼦进程返回0
  • ⽗进程返回的是⼦进程的pid。

1.2.3 写时拷⻉

通常,⽗⼦代码共享,⽗⼦再不写⼊时,数据也是共享的,当任意⼀⽅试图写⼊,便以写时拷⻉的⽅式各⾃⼀份副本(让权限出错,不一致)。具体⻅下图:
在这里插入图片描述

  • 因为有写时拷⻉技术的存在,所以⽗⼦进程得以彻底分离离!完成了进程独⽴性的技术保证!
  • 写时拷⻉,是⼀种延时申请技术,可以提⾼整机内存的使⽤率

1.2.4 fork调用失败原因

  • 系统资源不足:系统可能没有足够的内存或进程表项来创建新的进程。每个进程都需要一定的内存和操作系统资源。如果系统资源有限,fork()会失败。
  • 进程数限制:操作系统对每个用户或系统中可创建的最大进程数有一个限制。如果达到该限制,fork()会返回失败。
  • 权限不足:如果调用进程没有足够的权限(例如,普通用户没有权限创建新进程),fork()可能会失败。

地址空间限制:在某些系统中,fork()会复制父进程的内存空间(例如,使用写时复制机制),如果内存地址空间过大或不允许复制,fork()可能会失败。

  • 内核限制:操作系统的内核可能有某些限制,特别是对于系统进程和守护进程。内核可能会限制某些类型进程的创建。
  • 文件描述符限制:每个进程可以打开的文件描述符是有限的。如果打开的文件描述符数超过了限制,fork()可能会失败。
  • 调用参数错误:fork()函数本身没有太多参数错误的情况,但调用环境不正确或程序存在其他逻辑错误也可能导致fork()失败。

二. 进程终止

2.1 基本概念

进程终止是指一个进程的执行结束,操作系统回收该进程所占用的资源并从进程表中移除。进程终止通常有两种方式:正常终止和异常终止。正常终止发生在进程完成其预定任务后,通常通过调用exit()系统调用或返回main()函数完成。异常终止则发生在进程出现错误时,如访问非法内存、除零错误等,通常由操作系统或运行时环境通过发送信号来处理,如SIGSEGV信号(段错误)。进程在终止时,会将退出状态码返回给父进程,父进程可以通过wait()或waitpid()等系统调用获取子进程的退出状态,并处理相关的清理工作。进程的终止不仅意味着其自身的消失,还会触发资源的释放,包括文件句柄、内存、CPU时间等,保证操作系统的资源得到有效管理。进程终止后,系统会在进程表中留下一个“僵尸”状态,等待父进程回收。

2.2 进程退出场景

  • 代码运⾏完毕,结果正确
  • 代码运⾏完毕,结果不正确
  • 代码异常终⽌

2.3 进程常见退出方法

正常终⽌(可以通过 echo $? 查看进程退出码):打印最近一次程序的退出状态。

  1. 正常退出:进程通过调用exit()函数来正常退出,操作系统会回收该进程的资源并从进程表中移除。exit()可以传递一个退出状态码,供父进程通过wait()获取。
  2. 返回main()函数:对于从main()函数启动的进程,执行完main()函数后会自动退出,操作系统会回收资源。
  3. 调用_exit():_exit()是一个系统调用,类似于exit(),但它不会调用标准库的清理工作(如文件流的刷新)。通常用于在子进程中调用,以避免父进程的标准库清理操作影响。
  4. 父进程终止:如果父进程调用exit()或发生崩溃,子进程会被标记为孤儿进程,由系统的init进程收养并终止。

异常退出:退出码无意义

  1. 异常退出:进程在执行过程中遇到未捕获的错误(如段错误、除零错误等)时会异常终止,操作系统会发出信号(如SIGSEGV),导致进程退出。
  2. 调用abort():abort()函数用于立即终止进程,且不进行任何清理工作,进程的退出状态通常是异常终止。
  3. 接收到终止信号:操作系统通过发送特定信号(如SIGTERM或SIGKILL)请求进程退出,进程收到信号后执行相应的退出操作。

2.3.1 退出码

退出码(Exit Code),也称为返回值或退出状态码,是一个整数值,用于表示进程的执行结果。操作系统通过退出码来判断进程是否成功执行以及执行过程中的错误类型。

常见的退出码定义如下:

在这里插入图片描述

  1. 0:表示进程正常退出,即没有发生任何错误。成功执行是进程退出时常用的退出码。
  2. 非零值:表示进程异常退出,具体值用于指示不同的错误类型。常见的非零退出码有:

1:一般性错误,表示发生了某些问题,但未明确具体类型。

2:命令行使用错误或错误的参数传递。

127:命令未找到,通常在尝试执行一个不存在的命令时返回。

128:无效的命令退出,通常是由信号引起的异常退出。

130:通常由Ctrl+C(SIGINT信号)终止的进程。

137:表示进程由于接收到SIGKILL信号被强制终止。

  1. 信号退出码:如果进程是由于接收到信号终止的,退出码会以128加上信号编号的方式表示。例如,进程因SIGSEGV(段错误)退出时,退出码可能是139(128+ 11,11是SIGSEGV信号的编号)。
  2. 自定义退出码:用户可以根据程序的实际需求定义不同的退出码,用来表示具体的错误或状态,特别是在脚本和应用程序中。

2.3.2 _exit函数和exit函数区别

void exit(int status); void _exit(int status);

  • exit():exit()是标准库函数,会做一些清理工作。

在调用exit()时,标准库会执行以下操作:

  • 调用所有注册的atexit()函数(如果有的话)。
  • 刷新所有打开的输出流(如标准输出、文件流等),确保未写入的数据被保存。
  • 关闭所有打开的文件描述符。
  • 执行其他标准库的清理任务。
  • exit()通常用于正常退出进程,并执行进程的资源清理。

_exit():_exit()是一个系统调用,它不会执行标准库的清理工作。

  • 当调用_exit()时,它直接终止进程,立即回收资源并退出,不会刷新输出缓冲区,也不会调用atexit()函数。
  • _exit()通常用于子进程的退出,特别是在fork()后,因为在子进程中执行exit()可能会影响父进程的资源清理工作。

总结:

  • exit()用于正常退出进程,进行标准库清理操作。
  • _exit()用于立即退出进程,不执行任何标准库的清理,适用于子进程或者需要立即退出的情况。

在这里插入图片描述
示例代码:

int main()
{
printf("hello");
exit(0);
}
运⾏结果:
[root@localhost linux]# ./a.out
hello[root@localhost linux]#
int main()
{
printf("hello");
_exit(0);
}
运⾏结果:
[root@localhost linux]# ./a.out
[root@localhost linux]#

三. 进程等待

3.1 基本概念

进程等待是指一个进程在执行过程中暂停,等待另一个进程完成某些任务或条件。通常,父进程会等待子进程的终止,以回收子进程的资源。进程等待是进程间同步的一部分,确保进程的协调和资源的有效管理。在操作系统中,父进程通过调用wait()或waitpid()等系统调用,阻塞自己,直到子进程终止,并获取子进程的退出状态。进程等待有助于防止“僵尸进程”的产生,因为父进程会获取子进程的退出信息并回收资源。如果父进程不调用wait(),子进程会处于“僵尸”状态,操作系统仍会为其保留资源,直到父进程处理它。进程等待也常用于处理同步问题,例如多个进程需要按顺序完成任务。

3.2 为什么有进程等待

进程等待的主要原因是确保进程间的协调和资源管理。当一个进程创建子进程后,父进程需要等待子进程完成任务,以回收子进程的资源,避免产生僵尸进程。如果父进程不等待,子进程的退出信息无法被回收,导致系统资源浪费。此外,进程等待用于进程间同步,确保任务按照特定顺序执行,避免数据竞争或不一致。例如,一个进程可能需要等待另一个进程的结果或某些条件满足才能继续执行。进程等待还能避免多进程环境中因过度并发而导致的资源冲突,提高系统稳定性和效率。

3.3 进程等待方法

3.3.1 wait系统调用

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取⼦进程退出状态,不关⼼则可以设置成为NULL

3.3.2 waitpid

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词