【Linux】进程控制(1):进程创建&&进程终止

目录

[一 进程创建](#一 进程创建)

[1 知识点回顾](#1 知识点回顾)

(1)fork函数初识

(2)fork函数的返回值

(3)写时拷贝

(4)fork的常用用法

(5)fork调用失败的原因

[2 写时拷贝](#2 写时拷贝)

[(1) 写时拷贝的原理--->部分](#(1) 写时拷贝的原理--->部分)

(2)为什么要有写时拷贝

[3 关于进程创建的实际应用场景](#3 关于进程创建的实际应用场景)

[二 进程终止](#二 进程终止)

[1 进程终止的几种情况](#1 进程终止的几种情况)

(1)怎么知道代码跑完了,结果正不正确?

(2)代码没跑完,异常了,怎么去衡量异常?

(3)结论

[2 进程常见的退出方法](#2 进程常见的退出方法)

[(1)echo ? 命令](#(1)echo ? 命令)

(2)退出码

(3)_exit函数

(4)exit函数

(5)exit和return有什么区别

(6)总结总结)


一 进程创建

我们先来引入两个结论

(1)进程 = 内核数据结构(task_struct z + mm_struct + vm_area_struct) +代码和数据

其中:内核数据结构更侧重于进程管理 ,代码和数据侧重于进程完成任务

(2)进程创建的本质:就是系统内多了一个进程

进程是具有独立性:内核各自独立,代码和数据也各自独立!

1 知识点回顾

(1)fork函数初识

在 Linux 中,fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

bash 复制代码
#include <unistd.h>
pid_t fork(void);
返回值:⼦进程中返回0,⽗进程返回⼦进程id,出错返回-1
1. 为什么要给⼦进程返回0,⽗进程返回⼦进程pid?
2. 为甚⼀个函数fork会有两个返回值?
3. 为什么⼀个id即等于0,⼜⼤于0?

进程调用 fork,当控制转移到内核中的 fork 代码后,内核会执行以下操作:

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

・将父进程部分数据结构内容拷贝至子进程

・添加子进程到系统进程列表当中

・fork 返回,开始由调度器进行调度

当一个进程调用 fork 之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程,看如下程序。

bash 复制代码
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;
} 
运⾏结果:
[root@localhost linux]# ./a.out
Before: pid is 43676
After:pid is 43676, fork return 43677
After:pid is 43677, fork return 0

这里看到了三行输出,一行 before,两行 after。进程 43676 先打印 before 消息,然后它又打印 after。

另一个 after 消息是 43677 打印的。注意到进程 43677 没有打印 before,为什么呢?如下图所示

所以,fork 之前父进程独立执行,fork 之后,父子两个执行流分别执行。注意,fork 之后,谁先执行完全由调度器决定。

(2)fork函数的返回值

子进程返回0

父进程返回的是子进程的pid

(3)写时拷贝

通常,父子代码共享,父子在不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图:

qu

因为有写时拷贝技术的存在,所以父子进程得以彻底分离!完成了进程独立性的技术保证!

写时拷贝,是一种延时申请技术,可以提高整机内存的使用效率

(4)fork的常用用法

一个父进程希望复制自己,使父子进程同时执行不同的代码段。

例如,父进程等待客户端请求,生成子进程来处理请求。

一个进程要执行一个不同的程序。

例如子进程从fork返回后,调用exec函数。

(5)fork调用失败的原因

系统中有太多的进程

实际用户的进程数超过了限制

2 写时拷贝

(1) 写时拷贝的原理--->部分

当我们的权限是只读,如果这个时候写入数据,就会转化失败。操作系统会介入,识别到子进程要访问共享数据,操作系统会触发写时拷贝,修改页表·。父子进程就可以正常访问各自数据

系统fork 之后,是通过修改页表权限,来触发报错,让 操作系统 介入,完成写时拷贝的!

(2)为什么要有写时拷贝

区分问题:写入!= 覆盖写入,修改也是写入!count = 10; count++;

因为有写时拷贝技术的存在,所以父子进程得以彻底分离!完成了进程独立性的技术保证!

写时拷贝,是一种延时申请技术,可以提高整机内存的使用率。

写时拷贝可以满足各种不同的写入场景

3 关于进程创建的实际应用场景

使用fork+写时拷贝技术,进行安全备份!!!


二 进程终止

进程终止本质就是系统中少了一个进程

1 进程终止的几种情况

代码运行完毕,结果正确
代码运行完毕,结果不正确
代码异常终止

前两种是正常情况,最后一种是异常情况

(1)怎么知道代码跑完了,结果正不正确?

我们先来引入一个概念:

cs 复制代码
int main()
{
  //.....
  return 0;
}

这里的return 值,有什么含义吗?

这里的return值,表示的是退出码,通常0表示成功

退出码是给谁的?为什么要有它?
1 退出码是给父进程bash或者自定义父进程的

2 退出码是为了让父进程知道子进程任务完成的怎么样,因为0标识成功,非0标识失败

父进程要知道是什么原因失败的,1,2,3,4,5分别表示不同的失败原因。所以我们需要有一种机制,把0,1,2....转化成对应的描述,我们就可以用到strerror

strerror:把错误码转化成字符串

(2)代码没跑完,异常了,怎么去衡量异常?

直接输出一个结论:收到了信号,导致进程异常了

kill - l:1-31是普通信号,但是没有0号信号

(3)结论

衡量一个进程运行结果是否 "可信",可以用两个数字来表示:
exit code(退出码)
signal number(信号编号)

这两个数字在 Linux/Unix 系统中,通常可以通过 waitpid() 等系统调用获取,对应变量如:

bash 复制代码
long exit_state;
int exit_code, exit_signal;

重要细节:
如果一个进程异常退出(被信号终止),那么它的退出码( exit code) 是无意义的,只有 signal number 才有参考价值。

进程退出的时候,会把自己的退出信息维护起来,僵尸状态。这俩个数字(exit code 和 signal number)会被维护在僵尸进程的PCB内部

2 进程常见的退出方法

(1)echo $? 命令

在 Linux/Unix 系统中,每个命令或程序执行结束后,都会返回一个退出码 (0-255 之间的整数):

0:表示程序 / 命令执行成功

非 0:表示执行失败(不同的非 0 值对应不同的错误类型)
$? 是系统内置的环境变量,专门存储上一个执行的命令 / 程序的退出码

我们来看一下echo $?的使用场景:

bash 复制代码
# 执行一个不存在的命令(故意制造失败)
ls non_exist_file

# 查看退出码
echo $?
# 输出:2 (表示文件不存在,不同错误对应不同非0值)

(2)退出码

退出码是 Unix/Linux 系统中程序 / 命令执行完毕后返回给操作系统的一个整数(范围 0-255),本质是程序向调用者(如 Shell、其他程序)传递「执行结果」的标准化方式

退出码只有正确和·错误两种表示:

0 = 执行成功 (无任何错误);
1-255 = 执行失败(不同数值代表不同类型的错误,是行业通用约定,而非强制规则)

• 退出码0 表示命令执行无误,这是完成命令的理想状态。

• 退出码1 我们也可以将其解释为"不被允许的操作"。例如在没有sudo权限的情况下使用yum;再例如除以0等操作也会返回错误码1,对应的命令为let a=1/0。

130 (SIGINT或^C)和143 (SIGTERM)等终止信号是非常典型的,它们属于128+n信号,其中n代表终止码。

• 可以使用strerror函数来获取退出码对应的描述。

所以我们可以通过退出码判断程序是否正确退出,也可以在shell脚本中根据退出码进行逻辑判断

main函数返回值称为进程退出码

(3)_exit函数

bash 复制代码
#include <unistd.h>
void _exit(int status);
参数:status 定义了进程的终⽌状态,⽗进程通过wait来获取该值

说明:虽然status是int,但是仅有低8位可以被父进程所⽤。所以_exit(-1)时,在终端执⾏$?发现
返回值是255。

(4)exit函数

bash 复制代码
#include <unistd.h>
void exit(int status);

exit最后也会调用_exit(底层是_exit()封装的),但在调用_exit之前,还做了其他工作:

  • 执行用户通过atexit或on_exit定义的清理函数。
  • 关闭所有打开的流,所有的缓存数据均被写入。
  • 调用_exit。

实例:

bash 复制代码
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]# 

(5)exit和return有什么区别

return只有在main函数中才表示进程结束,exit在任意地方都表示进程结束

exit和_exit的头文件不同,且_exit是系统调用,但是exit是库函数

exit和_exit最大的区别:在进程结束的时候,一般会主动冲刷缓冲区,但是_exit不会冲刷缓冲区,它直接杀死进程

我们在这里埋一个种子:这个缓冲区应该由谁提供?---->一定不是操作系统提供的

最佳实践用exit

库函数和系统调用是上下级的关系,其实exit在底层就调用了_eixt

所以进程属于操作系统内被管理对象,杀掉进程,就是释放进程内PCB和数据,但是不可能用户自己释放,要系统调用释放

(6)总结


相关推荐
顺顺 尼2 小时前
linux第一个系统程序-进度条
linux
vpk1122 小时前
Docker 安装与常用命令
运维·docker·容器
liurunlin8882 小时前
httpslocalhostindex 配置的nginx,一刷新就报404了
运维·nginx
开源盛世!!2 小时前
3.19-3.21
linux·服务器·前端
Maverick062 小时前
Oracle PDB 概念与架构
运维·数据库·oracle
小民AI实战笔记2 小时前
htop安装不了怎么解决
linux·运维
pillowss2 小时前
SSH 登录服务器后 Backspace 失效?Ghostty + TERM 踩坑完整解决方案
服务器·ssh·github
源远流长jerry3 小时前
RDMA 技术深度解析:从原理到实践
linux·网络·tcp/ip·架构·ip
ken22323 小时前
在ubuntu终端里, 怎样让历史不要记录本条命令:禁止记录包含密码之类的命令
linux·运维·ubuntu