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

Linux 孤儿进程与进程的优先级和切换和调度

一.孤儿进程

1.概念引入

孤儿进程,简单来说就是在它自己的任务执行完之前,父进程先挂掉,它就变成了孤儿进程。孤儿进程貌似变成了“无人看管的状态”。

2.实践证明

我们用下面的代码模拟孤儿进程产生的过程。

#include<stdio.h>2 #include<sys/types.h>3 #include<unistd.h>4 int main(){5     pid_t id=fork();6     if(id==0)7     {8         //child9         while(1){10 11             printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());12             sleep(1);13         }14     }15     else{16         //father17         int count=5;18         while(count--){19             printf("我是一个父进程,pid:%d,ppid:%d\n",getpid(),getppid());20             sleep(1);21         }22     }23     return 0;                                                                                                                                                                                                 24 }

生成可执行程序之后,我们用指令查看进程状态。

ps ajx | grep myprocess

可以看到,父进程确实先退出了,到后面只有子进程在一直执行。

此时我们发现一个有趣的情况:这时的子进程用Ctrl+c是杀不掉的。那是因为:此时子进程变成了孤儿进程,自动会把当前的可执行程序切换到后台执行,我们需要用指令kill -9才能把这个子进程杀掉。

父进程退出后,子进程不能称为没有父进程的子进程,它会被1号进程领养,这个子进程就叫孤儿进程。

那么1号进程是谁?systemd(可以看作操作系统)。由它创建bash进程。

为什么要领养?我们需要回答它的反面问题。如果不领养会怎样?

子进程进入僵尸状态?无人回收。

那一个进程的父进程为什么推出后不会孤儿也不会僵尸?因为由bash创建,会被bash回收。

二.进程的优先级和切换和调度

1.进程优先级

我们将从三个方面讲解这个问题。

是什么:什么是优先级?

为什么:为什么要设计优先级?

怎么办:如何设计优先级?

1.排队的本质:确认优先级,自己什么时候能打到饭(得到某种资源的先后顺序)。

2.高峰期的时候才需要排队。也就是说,目标资源稀缺时,进程优先级才有意义。很多进程都在竞争有限的cpu资源。

优先级和权限:优先级决定的是能得到这个资源,不过是先后问题;权限决定的是是否能得到这个资源。

3.优先级,就是一种数字。是task_struct中的属性。数字值越低,优先级越高。

现代计算机操作系统:基于时间片的分时操作系统。时间片:这里只做简单比喻,只给30秒打饭。这类的操作系统需要考虑公平性

示例:我们修改上面的代码,让父子进程一直跑。

#include<stdio.h>2 #include<sys/types.h>3 #include<unistd.h>4 int main(){5     pid_t id=fork();6     if(id==0)7     {8         //child9         while(1){10 11             printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());12             sleep(1);13         }14     }15     else{16         //father17         while(1){18             printf("我是一个父进程,pid:%d,ppid:%d\n",getpid(),getppid());19             sleep(1);20         }21     }                                                                                              22     return 0;23 }   

生成可执行程序,并查看进程状态。

ps -al|head -1 && ps -al|grep myprocess


我们需要关注一些特别的进程属性。

1.UID:user Identify,标识一个用户的唯一身份。在Linux文件系统中我就可以看到UID,只不过平时都向用户展示用户名。

wujiahao@VM-12-14-ubuntu:~/process_test$ ls -ln
total 28
-rw-rw-r-- 1 1002 1003    77 Sep 20 20:32 Makefile
-rwxrwxr-x 1 1002 1003 17664 Sep 21 15:54 myprocess
-rw-rw-r-- 1 1002 1003   450 Sep 21 15:52 myprocess.c

tips:我们(用户)在用指令访问文件时,本质上是一个创建了一个进程访问。一个进程启动时,就会被UID标识,同样的文件也会标识UID。在执行操作时对比两者值即可实现权限管理。

可以说,Linux中一切皆文件,Linux中的而一切访问资源方式都是由进程访问,进程就是用户的化身

2.PRI和Ni:上面也说到了,进程的优先级其实就是一个数字。PRI即进程的优先级,默认为80;NI即进程优先级的偏移值,也就是nice值。

真实的优先级=PRI(默认)+NI。接下来我们通过两种方式修改进程的优先级。

方法1:

先修改代码

#include<stdio.h>2 #include<sys/types.h>3 #include<unistd.h>4 int main(){5     while(1){6         printf("我是一个父进程,pid:%d,ppid:%d\n",getpid(),getppid());                             7         sleep(1);8     }9    // pid_t id=fork();10    // if(id==0)11    // {12    //     //child13    //     while(1){14 15    //         printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());16    //         sleep(1);17    //     }18    // }19    // else{20    //     //father21    //     while(1){22    //         printf("我是一个父进程,pid:%d,ppid:%d\n",getpid(),getppid());23    //         sleep(1);24    //     }25    // }26     return 0;27 }
~                                                                                                      
~               

启动进程之后,使用top命令——>r命令——>输入当前要修改的优先级的进程的pid。

输入要修改的值。

之后再去查看进程的状态。发现进程的优先级已经成功被修改。

wujiahao@VM-12-14-ubuntu:~$ ps -al|head -1 && ps -al|grep myprocess
F S   UID     PID    PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1002 1041409 1031091  0  90  10 -   694 hrtime pts/0    00:00:00 myprocess

需要注意的一点是,进程的优先级都是基于PRI的默认值80修改的,这个设计的用意我们下面就会说。

方法2:使用nice/renice指令

优先级的极值问题:如果优先级跨度过大,会有人恶意修改自己的进程优先执行,优先级低的进程长时间的不到cpu资源,引发进程饥饿。

我们用renice指令修改进程优先级,分别让他们-100,+100,看看结果如何。

最终发现nice值的区间为[-20,19],也就是说我们进程的优先级会控制在[60,99]的范围。

2.进程的切换

本节将讲解进程切换和进程调度原理,在此之前我们需要补充一些前置知识。

1.竞争性:系统进程数目众多,而CPU资源有限,所以进程之间有竞争属性。为了高效完成任务,更合理竞争相关资源,于是有了优先级的概念。

2.独立性:进程=内核数据结构(task_struct)+代码和数据,多进程运行独享资源,并且运行期间互不干扰。其中一个进程挂了不会影响其他进程。

3.并发:多个进程在一个CPU运行,采用进程切换的方式分时运行多个进程。

4.并行:多个进程在多个CPU分别同时运行,是真正同时运行。

我们也可以通过指令查看我们当前的云服务器的CPU配置(不重要)。

cat /proc/cpuinfo

wujiahao@VM-12-14-ubuntu:~/process_test$ cat /proc/cpuinfo
processor	: 0
vendor_id	: AuthenticAMD
cpu family	: 25
model		: 1
model name	: AMD EPYC 7K83 64-Core Processor
stepping	: 0
microcode	: 0x1000065
cpu MHz		: 2545.216
cache size	: 512 KB
physical id	: 0
siblings	: 2
core id		: 0
cpu cores	: 1
apicid		: 0
initial apicid	: 0
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm rep_good nopl cpuid extd_apicid tsc_known_freq pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm cmp_legacy cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw topoext ibpb vmmcall fsgsbase bmi1 avx2 smep bmi2 erms rdseed adx smap clflushopt sha_ni xsaveopt xsavec xgetbv1 arat fsrm
bugs		: fxsave_leak sysret_ss_attrs null_seg spectre_v1 spectre_v2 spec_store_bypass srso ibpb_no_ret
bogomips	: 5090.43
TLB size	: 1024 4K pages
clflush size	: 64
cache_alignment	: 64
address sizes	: 48 bits physical, 48 bits virtual
power management:processor	: 1
vendor_id	: AuthenticAMD
cpu family	: 25
model		: 1
model name	: AMD EPYC 7K83 64-Core Processor
stepping	: 0
microcode	: 0x1000065
cpu MHz		: 2545.216
cache size	: 512 KB
physical id	: 0
siblings	: 2
core id		: 0
cpu cores	: 1
apicid		: 1
initial apicid	: 1
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm rep_good nopl cpuid extd_apicid tsc_known_freq pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm cmp_legacy cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw topoext ibpb vmmcall fsgsbase bmi1 avx2 smep bmi2 erms rdseed adx smap clflushopt sha_ni xsaveopt xsavec xgetbv1 arat fsrm
bugs		: fxsave_leak sysret_ss_attrs null_seg spectre_v1 spectre_v2 spec_store_bypass srso ibpb_no_ret
bogomips	: 5090.43
TLB size	: 1024 4K pages
clflush size	: 64
cache_alignment	: 64
address sizes	: 48 bits physical, 48 bits virtual
power management:

要讲解进程切换,我们从三个方面入手。

1.死循环进程如何运行?

2.谈谈CPU和寄存器

3.进程切换原理

1.死循环如何运行

一旦一个进程占有CPU,会把自己的代码跑完吗?不会,系统分配时间片,时间片一到就会进程切换。所以死循环系统不会打死系统,不会一直占有CPU。

2.cpu与寄存器

代码和数据由CPU处理的时候,不会一下子全放进去,而是一条一条存在CPU的寄存器中。

CPU中有很多类别的寄存器,它们各自有各自独特的功能。

寄存器起到一个临时存储的作用,它会存储一个正在运行的进程的临时数据。

我们需要明确的一点是:寄存器是一个空间,它只有一份;而寄存器内的数据是临时数据,可能下一秒就会有不同的内瓤存入。

3.进程切换原理

要深入讲解进程切换原理,我们首先来讲一个例子。

比如你正在上大学,准备应征入伍,成为光荣的军人。但是在此之前,学校需要将你的学籍保留在一个文件袋,因为我们很容易想到一种糟糕的情况——如果没有保留学籍,一年没有上课没有考试,当然会挂很多科,要补起来就很难。于是保留学籍后,就可以入伍了。退伍之后,学校就可以把你的学籍恢复,跟着新一届的同学一起学习。

那么这个过程中:整个学校,导员和你就组成了一个完整的调度过程。

我们接着讲真实的进程切换。

进程A被分配的时间片用完之后,要把A切走;我们需要把寄存器中的所有值都带走(拷贝出来,在哪?)此时直接A进程换到running Queue队尾,进程B上去运行,数据直接覆盖。一定时间后,进程B的时间片也用完,复刻进程A的操作,然后进程A继续上去运行,恢复历史位置,继续运行。

也就是说,进程切换的最核心问题就是

保存当前进程的上下文数据

当前可能还有一个大家比较好奇的问题:被拷贝的寄存器数据存放到哪里了?

进程的task_struct中的TSS字段(任务状态段)。TSS中会定义一系列寄存器变量存储值。

TSS字段内容

3.进程的调度

在此之前,我们要明白一件事:一个内核级的指针,永远指向当前运行的进程

struct task_struct *current

Linux真实的调度算法讲解

当前讲解情况:一个CPU一个运行队列。

分时操作系统——互联网,服务器

0-99这个区间的优先级我们目前暂时不考虑,只考虑后四十个队列。这里就可以看到,这个区间实际上就是上面的进程优先级区间

实时操作系统——基于实际情况实时生成任务,使用于制造生产

这里的queue[140],实际上就是一个指针数组;而这个优先级队列,实际上就是一个哈希表。优先级,就是一个key,我们要根据一定的hash函数映射到这个指针数组中。

调度器如何快速挑选一个进程?

bitmap[5]:位图,unsigned类型。这里bitmap的比特位,跟queue是一一对应的(32*50=160),比特位的内容:0或1,是否有进程。

调度器调度队列:

1.挑队列:查看位图,近乎O(1)的调度算法。

2.挑进程:nr:当前队列中有多少进程。

调度过程:先查nr,nr>0,再查bitmap确认下标,直接索引,找到目标队列然后从队列头部移除,拿出来后把当前进程的PCB用current指针管理,然后执行切换算法,让current指针的进程放到CPU执行。

但这样设计还是无法完成一个优秀的调度算法。


比如现在60号进程在运行,时间片到了先切换下去,它最多会回到它的优先级队列的尾部。如果单纯这样设计,CPU会永远把前面的进程跑完才会跑第二个,第三个....如果出现一个死循环的进程,就会造成其他进程的饥饿。

因此又有一个队列,跟上面讲过的一模一样的队列。就是上面的一蓝一红

定义两个指针,一个是 struct rqueue_elem *active =&prio_array[0]

另一个是struct rqueue_elem *expired =&prio_array[1]

选队列时,只从active里找,如果发生了上面的切换问题,此时进程不能再放回active里,要放回expired里(执行完了就完了)。active中的进程会越来越少,而expired中的进程会越来越多。一定要把active的队列全部调度完成,再进行下一步。这样原本active中优先级较低的一定会被调到,不会发生饥饿。

此时把active中的队列全部调度完,执行一个操作:

swap(&active,&expired);

交换指针内容,这样active就又执行一个满的队列,而expired管理一个空的队列,重复执行以上流程。

这样就是大O(1)调度算法的设计。

新进程来了,是a队列还是e队列?放在e队列,可以看作是就绪队列。

链到a队,就会自动根据优先级插队了。

其他内容讲解:

cpu_load:实际的运行中会优先把新进程插在负载较低的队列中,保证负载均衡

因此我们能理解之前讲解的优先级算法,其实是为了配套这个调度算法设计的。

http://www.dtcms.com/a/394424.html

相关文章:

  • QML学习笔记(七)QML的基本数据类型
  • 基于51单片机电子钟闹钟12/24小时制LCD显示
  • 小程序开发全解析:从结构到API
  • 异步方法在C#中的使用
  • js时间戳转换日期格式 yyyy-mm-dd
  • 信号处理方法概述
  • 固定收益理论(五)债券投资归因模型
  • 【论文速递】2025年第18周(Apr-27-May-03)(Robotics/Embodied AI/LLM)
  • 3D视觉——求出目标物体在相机坐标系下的位姿信息
  • 固态和机械硬盘损坏后的不同
  • Linux 基础IO
  • pandawiki ai 无法生成摘要
  • m语言可视化log中的变量信息
  • MySQL:库操作和常用数据类型
  • uniapp实现view块级元素横竖屏切换
  • 【编号74】河北地理基础数据(道路、水系、四级行政边界、地级城市、DEM等)
  • Python: 将wxauto发布为接口,并部署为Windows服务
  • 2025年度SEO优化公司
  • 基于Markdown的静态网站生成器完全指南
  • hot100——第十一周
  • 嵌入式(2)——HAL_GetTick()
  • 《第18课——C语言结构体:从Java的“豪华别墅“到C的“集装箱宿舍“——内存对齐、位域抠门与指针破门的底层狂欢》
  • 旅游线路预约小程序怎么搭建?景区售票团购小程序怎么做?
  • Redis未来发展趋势:技术演进与生态展望
  • 怎么重新映射windows终端的按键的功能
  • 【秋招笔试】2025.09.20哔哩哔哩秋招笔试真题
  • string 容器
  • MySQL零基础学习Day1——安装与配置
  • mysql重启,服务器计划重启,如何优雅地停止MySQL?
  • 源码加密知识产权(二) JS压缩和加密——东方仙盟元婴期