当前位置: 首页 > news >正文

进程控制(1)

在 Linux 操作系统中,进程是资源分配与调度的基本单位,而进程控制则是操作系统编程的核心能力 —— 它涵盖了进程的创建、终止、资源回收与程序替换,甚至是 Linux Shell 这类交互工具的底层实现。

进程创建

fork函数初识

在 linux 中 fork 函数是⾮常重要的函数,它从已存在进程中创建⼀个新进程。新进程为⼦进程,

⽽原进程为⽗进程。

#include <unistd.h>
pid_t fork(void);

返回值:⼦进程中返回0,⽗进程返回⼦进程id,出错返回-1

1. 为何子进程返回 0,父进程返回子进程 pid?

  • 父进程需通过子进程 pid 管理多个子进程(如用wait/waitpid回收资源、获取退出状态),满足多子进程场景需求(如服务器、Shell)。

  • 子进程无需跟踪其他进程,返回 0 可快速标识自身角色(代码中用pid==0判断子进程逻辑),且 0 是系统保留标识,不与其他进程 pid 冲突;子进程若需父进程 pid,可调用getppid()

2. 为何 fork 函数有两个返回值?

父进程调用 fork 后,内核为子进程分配内核数据结构、复制父进程数据并将子进程加入进程列表。完成后,父子进程从 fork 返回指令处并发执行,内核分别为二者设置返回值:父进程获子进程 pid,子进程获 0,导致一次调用产生两个返回值。

3. 为何一个 id 既等于 0 又大于 0?

并非同一 id 兼具两值,而是 fork 返回的两个值分属父子进程。fork 后父子进程数据独立(写时拷贝),父进程中pid变量存子进程 pid(大于 0),子进程中pid变量存 0,是不同进程的独立变量值,代码中通过条件判断区分,无矛盾。

进程调⽤ fork ,当控制转移到内核中的 fork 代码后,内核做:

分配新的内存块和内核数据结构给⼦进程

将⽗进程部分数据结构内容拷⻉⾄⼦进程

添加⼦进程到系统进程列表当中

fork 返回,开始调度器调度

当⼀个进程调⽤fork之后,就有两个⼆进制代码相同的进程。⽽且它们都运⾏到相同的地⽅。但每个进程都将可以开始它们⾃⼰的旅程,看如下程序。

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;
}

运行结果:

Before: pid is 43676
After:pid is 43676, fork return 43677
After:pid is 43677, fork return 0
  1. 父进程先执行:

    • 首先输出Before: pid is 43676,说明当前只有父进程(PID=43676)在运行

    • 调用fork()创建子进程,内核为子进程分配 PID=43677

  2. fork()后的并发执行:

    • 父进程中,fork()返回子进程 PID(43677),因此输出After:pid is 43676, fork return 43677

    • 子进程中,fork()返回 0,因此输出After:pid is 43677, fork return 0

  3. 关键结论:

    • fork()会复制父进程创建新进程,父子进程代码共享但数据独立

    • 子进程从fork()返回处开始执行,不会重复执行之前的代码(如Before行)

    • sleep(1)确保父子进程都能完成输出,避免父进程提前退出导致结果不完整

写时拷贝

通常,⽗⼦代码共享,⽗⼦再不写⼊时,数据也是共享的,当任意⼀⽅试图写⼊,便以写时拷⻉的⽅式各⾃⼀份副本。具体⻅下图:

因为有写时拷⻉技术的存在,所以⽗⼦进程得以彻底分离离!完成了进程独⽴性的技术保证!

写时拷⻉,是⼀种延时申请技术,可以提⾼整机内存的使⽤率

fork 的常见用法与失败场景

fork的核心应用场景分为两类:

  1. 父子分工:父进程负责监听请求(如服务器监听端口),子进程处理具体任务(如处理客户端连接),避免父进程阻塞;

  2. 程序替换:子进程通过fork复制父进程后,调用exec函数替换为新程序(如 Shell 执行ls命令时,会fork子进程并exec /bin/ls)。

fork调用失败的常见原因:

        系统进程数超过内核限制(可通过/proc/sys/kernel/pid_max查看上限);

  • 普通用户的进程数超过ulimit限制(可通过ulimit -u查看)。

进程终⽌

进程终⽌的本质是释放系统资源,就是释放进程申请的相关内核数据结构和对应的数据和代码

进程退出场景

代码运⾏完毕,结果正确

代码运⾏完毕,结果不正确

代码异常终⽌

进程常⻅退出⽅法

正常终⽌(可以通过 echo $? 查看进程退出码):

1. 从main返回

2. 调⽤exit

3. _exit

异常退出:

ctrl + c,信号终⽌

退出码

退出码(退出状态)可以告诉我们最后⼀次执⾏的命令的状态。在命令结束以后,我们可以知道命令是成功完成的还是以错误结束的。其基本思想是,程序返回退出代码 0 时表⽰执⾏成功,没有问题。代码 1 或 0 以外的任何代码都被视为不成功。

Linux Shell 中的主要退出码:

进程等待

进程等待必要性

之前讲过,⼦进程退出,⽗进程如果不管不顾,就可能造成‘僵⼫进程’的问题,进⽽造成内存

泄漏。另外,进程⼀旦变成僵⼫状态,那就⼑枪不⼊,“杀⼈不眨眼”的kill -9 也⽆能为⼒,因为谁也没有办法杀死⼀个已经死去的进程。

最后,⽗进程派给⼦进程的任务完成的如何,我们需要知道。如,⼦进程运⾏完成,结果对还是不对,或者是否正常退出。

⽗进程通过进程等待的⽅式,回收⼦进程资源,获取⼦进程退出信息

进程等待的⽅法

wait⽅法

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取⼦进程退出状态,不关⼼则可以设置成为NULL
  • 特性:阻塞等待 —— 若没有子进程终止,父进程会暂停执行,直到有子进程终止。

waitpid⽅法

pid_ t waitpid(pid_t pid, int *status, int options);

返回值:

当正常返回的时候waitpid返回收集到的⼦进程的进程ID;

如果设置了选项WNOHANG,⽽调⽤中waitpid发现没有已退出的⼦进程可收集,则返回0;

如果调⽤中出错,则返回-1,这时errno会被设置成相应的值以指⽰错误所在;

参数:

pid:

Pid=-1,等待任⼀个⼦进程。与wait等效。

Pid>0.等待其进程ID与pid相等的⼦进程。

status: 输出型参数

WIFEXITED(status): 若为正常终⽌⼦进程返回的状态,则为真。(查看进程

是否是正常退出)

WEXITSTATUS(status): 若WIFEXITED⾮零,提取⼦进程退出码。(查看进程

的退出码)

options:默认为0,表⽰阻塞等待

WNOHANG: 若pid指定的⼦进程没有结束,则waitpid()函数返回0,不予以等

待。若正常结束,则返回该⼦进程的ID。

返回值规则

  • 成功:返回被等待子进程的 PID;

  • 非阻塞时子进程未终止:返回 0;

  • 失败:返回 - 1(如无该子进程)。

获取⼦进程status

wait和waitpid,都有⼀个status参数,该参数是⼀个输出型参数,由操作系统填充。如果传递NULL,表⽰不关⼼⼦进程的退出状态信息。否则,操作系统会根据该参数,将⼦进程的退出信息反馈给⽗进程。status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16⽐特位):

#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main( void )
{
pid_t pid;
if ( (pid=fork()) == -1 )
perror("fork"),exit(1);
if ( pid == 0 ){
sleep(20);
exit(10);
} else {
int st;
int ret = wait(&st);
if ( ret > 0 && ( st & 0X7F ) == 0 ){ // 正常退出
printf("child exit code:%d\n", (st>>8)&0XFF);
} else if( ret > 0 ) { // 异常退出
printf("sig code : %d\n", st&0X7F );
}
}
}

输出结果:

测试结果:
# ./a.out #等20秒退出
child exit code:10
# ./a.out #在其他终端kill掉
sig code : 9

status并非简单的整数,而是一个16 位的位图,存储了 “退出码” 和 “信号编号” 两类信息(仅需关注低 16 位):

  • 低 7 位:子进程终止的信号编号(若为 0,表示正常终止);

  • 高 8 位:子进程的退出码(仅当低 7 位为 0 时有效,即正常终止);

  • 第 8 位:core dump标志(若为 1,表示进程异常终止时生成了核心转储文件)。


文章转载自:

http://5nJoC763.snnwx.cn
http://PpJHvSqp.snnwx.cn
http://59IsMXKJ.snnwx.cn
http://UZlBbwUL.snnwx.cn
http://FQlTIs71.snnwx.cn
http://328Tu81j.snnwx.cn
http://1T70TCTX.snnwx.cn
http://ZoQ1y9Yf.snnwx.cn
http://CMCp6hdC.snnwx.cn
http://BTfPZ1Vo.snnwx.cn
http://lJtfVyVX.snnwx.cn
http://ery06yy7.snnwx.cn
http://zyK7YvKm.snnwx.cn
http://LqoakpLL.snnwx.cn
http://qSXKXWFa.snnwx.cn
http://x5EpjlDF.snnwx.cn
http://H5cGdEPF.snnwx.cn
http://Ry6FnQDE.snnwx.cn
http://gPS4kaPw.snnwx.cn
http://r001Ya2K.snnwx.cn
http://W7fUWy7l.snnwx.cn
http://vJQw4VT4.snnwx.cn
http://BFrorx33.snnwx.cn
http://zsXsZ2sA.snnwx.cn
http://HT4megp8.snnwx.cn
http://yk9FJaCJ.snnwx.cn
http://bS9VCziT.snnwx.cn
http://VwQlIr1r.snnwx.cn
http://qHNtM4u9.snnwx.cn
http://thvahFmu.snnwx.cn
http://www.dtcms.com/a/378374.html

相关文章:

  • 操作系统进程管理——同步与互斥的基本概念
  • 灰色关联分析笔记
  • CAD文件坐标系未知?用Bigemap Pro自动计算中央子午线,准确定位!
  • 项目管理核心八项(软件篇)
  • 创新驱动:医养照护与管理实训室建设方案构建
  • C++ 之 cli窗口交互程序测试DLL
  • openEuler系统远程管理方案:cpolar实现安全高效运维
  • Spring常用注解介绍
  • 《秋鳞小故事——编译器》
  • 【前端Vue】如何优雅地在vue中引入ace-editor编辑器
  • 架构深解:英伟达Rubin CPX如何通过专用预填充加速器与解耦架构重塑AI推理效率与成本
  • 线下小店悄然增长:两个关键模式与它们的运营启示
  • 开发安全利器:detect-secrets 敏感信息扫描工具实战指南
  • 中间件架构设计与实践:构建高性能分布式系统的核心基石
  • 错误于make.names(vnames, unique = TRUE): invalid multibyte string 9 使用 R 语言进行数据处理时
  • 前端基础标签
  • 深度学习基本模块:ConvTranspose2D 二维转置卷积层
  • 多模态数据治理新范式:衡石Agentic BI如何统一结构化与非结构化数据?
  • Gopeed下载器本地部署指南:cpolar实现远程任务管理
  • App 苹果 上架全流程解析 iOS 应用发布步骤、App Store 上架流程
  • unity UGUI 鼠标画线
  • ALBEF(Align Before Fuse)
  • redis 集群——redis cluster(去中心化)
  • k8s部署kafka三节点集群
  • 11.ImGui-加载字体和中文
  • 大模型推理革命
  • 项目-sqlite类的实现
  • 物联网领域中PHP框架的最佳选择有哪些?
  • ARM1.(ARM体系结构)
  • Linux开机启动设置全攻略