前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux进程状态与优先级

Linux进程状态与优先级

作者头像
利刃大大
发布2023-04-12 14:28:00
2.7K0
发布2023-04-12 14:28:00
举报

Ⅰ. OS进程状态的概念

进程状态反映进程执行过程的变化。这些状态随着进程的执行和外界条件的变化而转换。

  • 三态模型中,进程状态分为 运行态,就绪态,阻塞态
  • 五态模型中,进程状态分为 新建态、终止态,运行态,就绪态,阻塞态
  • 七态模型中,进程状态分为 挂起就绪态、挂起等待态、新建态、终止态,运行态,就绪态,阻塞态

系统操作原理:进程的状态和转换(五态模型)

在普遍的操作系统中,我们所遇到的进程状态有:运行、新建、就绪、挂起、阻塞、停止、挂机、死亡…等等,但是我们并不懂它们(学了等于没学),因为这是操作系统层面的说法,它的理论放到哪个操作系统中都对。所以我们要学习一个具体的操作系统来理解进程状态,而这里我们使用的当然就是Linux

进程的状态有多种,本质都是为了满足不同的运行场景!

1、运行状态

​ 常见的外设有键盘、显示器、网卡、磁盘…等,操作系统也要对这些外设进行管理。那操作系统怎么管理呢? ​ 答案是:先描述,再管理。所以,在操作系统内都包含了每一个硬件的 struct 结构体,这里比如 struct div_keybord… 等,这些结构体都包含了该硬件的所有属性,这些属性可以找到驱动上该硬件的所有匹配的方法,然后操作系统可以对硬件进行各种操作。

​ 根据冯诺依曼体系结构,所有的数据结构和内部属性数据都在内存当中,因为操作系统开机就被加载到了内存中

​ 假设磁盘中有一个 bin.exe 文件要运行,它先被加载到内存里,操作系统要对该进程进行管理,要管理就要先描述,这里PCB假设是 struct task_struct {};这个进程想要在 CPU 上运行起来,就必须加入到 运行队列(runqueue) 中,这个运行队列是 CPU 为管理进程而产生的,是内核给 CPU 准备的。为什么进程运行要进行排队?因为 CPU 的资源是有限的

​ 这里假设只有单核的 CPU (一个CPU),不考虑多核的情况,一个 CPU 只有一个运行队列进程想要运行就必须加入到运行队列中,这里的运行队列假设是 struct runqueue{},这个运行队列包括了 struct task_struct head* 和 其他属性。

让进程入队列,本质是:将该进程的 task_struct 结构体对象放入到运行队列中

​ 🔴 注意:进队列排队的是 task_struct 结构体(也就是PCB),并不是让可执行程序去排队,这个 task_struct 结构体包含了该进程的代码和所有属性,运行队列中有一个头指针 struct task_struct head*,head 指针指向第一个进程,第一个进行的属性中又包含了下一个进程的指针,保证可以链接到下一个进程,依次往后就可以找到所有进程。

​ 举个栗子:比如你要找工作了,你是把你的简历投到公司的邮箱里,而不是把你自己投到邮箱里。公司有自己的简历池,假设有一万份简历,HR 对这些投递的简历进行排序,HR 觉得你的简历不错,通过简历池选出你进行面试,HR 再把你的简历扔给面试官。你的简历上有你的电话、姓名、邮箱…等等,通过这个简历里面的方法可以找到你这个人。这个面试官相当于 CPUHR 就相当于操作系统的调度算法,你的简历就相当于 PCB 结构体 task_struct。面试官拿到的是你的简历,简历上有你的全部数据和属性,通过简历上的属性可以找到你这个人,PCB 结构体 task_struct 也是如此,它有进程的全部数据和属性,通过该进程的属性可以找到该进程所对应的代码和数据等等。

​ 下面给出结构图,帮助理解,结合上面的文字理解:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d9MjeJbU-1672113443040)(../../img/image-20221207112603670.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d9MjeJbU-1672113443040)(../../img/image-20221207112603670.png)]

​ 总所周知,CPU 虽然很笨,但是他很快,很快意味着 CPU 很快轮转一遍进程(进程切换再详谈),运行队列里的进程随时随刻都要准本好,让 CPU 随时调度运行,此时在运行队列里的一个个进程就叫做运行状态(running)

区分一个概念:一个进程正在 CPU 上运行,它一定是运行状态,但是一个进程没有在 CPU 上运行,但他已经在运行队列里面了,这个进程也是运行状态 !

​ 状态是进程的内部属性,进程的全部属性又在 PCB 结构体 task_struct 中,所以状态就是在 PCB 结构体里面,这个状态可以用整数 int 进行表示,比如 int(1:run 2:stop 3:dead …)用整数表示一个具体的状态,整数是几,就意味着状态是什么。

2、阻塞状态

​ 根据冯诺依曼体系结构,CPU 很快,但是外设(显示屏,磁盘,键盘…)相较于 CPU 是很慢的,而且这些外设也是只有少量的。进程或多或少都要访问这些外部设备,比如有十几个进程想要访问磁盘,但是磁盘被 进程A 用着,进程B 和 进程C 又来了,它们也要访问磁盘,还有进程D、E、F… 要访问磁盘,磁盘没有准备好,进程B后面的进程也只能进行等待

🚜 总结:不要只以为你的进程只会等待(占用)CPU 资源 ,你的进程也可能随时随地会访问外设资源

假设有十几个进程想要访问磁盘,磁盘该怎么办呢?操作系统对外设进行管理也是先描述再组织,操作系统里面有管理每个外设的结构体,每个外设的结构体都有一个等待队列,这些结构体可以对访问该外设的进程进行管理,假设等待队列为 task_struct wait_queue* ,那么此时这些在等待外设的进程就会加入到 wait_queue ,变成阻塞状态,等待资源!

这个过程也是对该进程的 PCB 结构体对象放到不同的队列中,并不是拿该进程的数据和代码

磁盘忙完了,发现进程A 在等待磁盘,此时磁盘是空闲的,磁盘就告诉操作系统,进程A可以运行了,然后操作系统就走进程A,把进程A的阻塞状态改为运行状态,然后放入 CPU 的运行队列里,进程A 就等待 CPU 运行

🐰 结论:所谓的进程不同的状态,本质是进程在不同的队列中,等待某种资源

3、挂起状态

​ 场景假设:假设大量的进程处于阻塞状态,这些进程都在等待某种资源资源,进程B 是这些进程里的一个进程,也是处于阻塞状态

​ 在这个过程中,进程B 也短时间内不会被调度,也就是不会被 CPU执行,该进程的 代码和数据 短期内不会被使用。

​ 假设此时的内存空间不够了,又有新的程序加载到内存里,操作系统最大的特点就是为我们节省内存空间。这时操作系统发现进程B 短时间内不会被调度,进程B 的代码和数据短时间内也不会被执行,内存空间却一直被你进程B 占用,操作系统说:这不是占着茅坑不拉屎吗?

​ 这时操作系统就做了一件事,把 进程B 的代码和数据暂时保存到磁盘中,为其他进程腾出内存空间,腾出的空间可以被其他进程使用

我们把 一个进程的代码和数据暂时换出到磁盘的这个过程叫做该进程被挂起,该状态就是挂起状态

🔺 注意该进程B只是数据和代码被换出到磁盘,进程B 内存中的内核数据结构还存在,也就是 task_struct 依旧存在

阻塞状态和挂起状态有什么区别? 答:阻塞不一定挂起,挂起一定阻塞

所以小结一下,操作系统中的进程状态的概念是这样子的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-56Fwy9Is-1672113443041)(../../img/image-20221207114427102.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-56Fwy9Is-1672113443041)(../../img/image-20221207114427102.png)]

其他状态在下面结合linux谈!

Ⅱ. Linux中的进程状态

为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)这里我们具体谈一下Linux操作系统中的进程状态,下面的状态在 kernel 源代码里定义:

代码语言:javascript
复制
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
    "R (running)", /* 0 */
    "S (sleeping)", /* 1 */
    "D (disk sleep)", /* 2 */
    "T (stopped)", /* 4 */
    "t (tracing stop)", /* 8 */
    "X (dead)", /* 16 */
    "Z (zombie)", /* 32 */
};

Linux 的运行状态由一个指针数组构成,task_state_array[0] 就代表运行状态,其他也是如此。

Linux 状态总览图:

而在 Linux 中查看进程的状态用的指令是 ps ajx 或者 ps aux

在这里插入图片描述
在这里插入图片描述

♐️ 并且我们可以发现,Linux 中并没有所谓的就绪状态、挂起状态等等说法,这是因为 OS 其实主要是为了提供一个总体概念的说法,而具体到某个 OS 上面操作的时候,不同的 OS 的进程状态的设定是不一样的,但是都是基于总体概念的,而 Linux 就有 Linux 独特的进程状态说法!

1、运行状态R(Running)

​ 🐎 定义:并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里

运行状态上面的 遍操作系统层面的进程状态(宏观)已经谈过了,自己往上翻阅

接下来我们看一下一个具体运行状态

测试代码:

代码语言:javascript
复制
#include<stdio.h>    
    
int main()    
{    
    int a = 0;    
    while(1)    
    {    
        a += 2;                                                      
    }    
    return 0;    
}

运行后我们用 ps ajx 来查看一下进程状态,这里的 STAT 代表的就是进程状态,这里的 R+ 就是运行状态,至于 + 号是什么意思,下面会解释!

2、睡眠状态S(Sleeping)

​ 🐎 定义:意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))

下面在 Linux 测试一下该状态:

代码语言:javascript
复制
#include<stdio.h>    
    
int main()    
{    
    int a = 0;    
    while(1)    
    {    
        a += 2;    
        printf("当前值为:%d\n", a);                                     
    }    
    return 0;    
} 

运行并查看进程,当前进程状态处于 S,叫做睡眠状态,睡眠状态也是阻塞状态的一种。因为 printf 要访问显示器,显示器是外设的一种,外设有个特点就是相较于 CPU 比较慢,慢就要等待显示器就绪,等待就要花较长的时间。我们这个程序可能只有万分之一的时间在运行,其它时间都在休眠,站在用户的角度它是 R,但是对于操作系统来说它不一定是 R它有可能在队列中等待调度

​ 🚗并不是每次测试的时候打印状态都能打出 S,只不过这个概率很高,因为这个状态是瞬间的,有可能那个瞬间是在执行代码而不是在等 IO就绪 ,所以有时候测试得到的状态可能是 R

3、磁盘休眠状态 D(Disk sleep)

​ 🐎 定义:有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。

上面的 S 状态也叫 浅度睡眠,进程可以被杀掉,这里的 D 状态叫做深度睡眠,表示该进程不会被杀掉,即便是操作系统也不行,只有该进程自动醒来才可以恢复或者给机器断电

​ 假设场景:进程A需要向磁盘写入10万条用户数据,这些数据对用户很重要。进程A 访问磁盘,等待磁盘写入数据,进程A 等待磁盘返回一个结果,数据是否写入成功,此时进程A 处于休眠状态S;此时突然内存空间不足了挂起也无法解决内存空间不足的问题,操作系统会自主杀掉一些进程(特别是内存资源不用的,比如进程A),操作系统就把进程A 给杀掉了,造成了磁盘写入数据失败,磁盘给进程A 返回结果,发现进程A 没有应答,磁盘只能把这些数据丢弃,然后磁盘继续为其他进程提供服务。结果,这重要的 10万条数据皆丢失了。

​ 为了防止这种情况的发生,Linux 给进程设置了深度睡眠 (D) 状态,处于深度睡眠状态的进程既不能被用户杀掉,也不能被操作系统杀掉,只能通过断电,或者等待进程自己醒来

深度睡眠状态一般很难见到,一般在企业中做高并发或高IO的时候会遇到,这里就不演示了

注:一旦机器大量进程处于 D状态,说明机器已经处于崩溃的边缘了

4、停止状态 T(stopped)

​ 🐎 定义:可以通过发送 SIGSTOP (signal stop)信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT (signal continue)信号让进程继续运行。

而这两个信号是在 kill 指令中的,我们可以用下面的指令来查看 kill 的指令选项:

代码语言:javascript
复制
kill -l

今天介绍的两个信号为 18、19, 18信号是继续,19信号是暂停

测试代码:

代码语言:javascript
复制
#include<stdio.h>    
int main()    
{    
    int a = 0;    
    while(1)    
    {    
        a += 2;    
        printf("当前值为:%d\n", a);                                     
    }    
    return 0;    
}    

进程运行了,给进程发送 19号信号(暂停)

代码语言:javascript
复制
kill -19 进程的PID
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jCpZtgDP-1672113443041)(../../img/image-20221207194319976.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jCpZtgDP-1672113443041)(../../img/image-20221207194319976.png)]

再给进程发送 18号信号(继续),进程又继续运行起来,这里的 +不见了,后面解释,这时候进程就无法用 [Ctrl]+c 杀掉了,只能用 9号信号杀掉进程

代码语言:javascript
复制
kill -18 进程的PID
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ifn6KYk-1672113443042)(../../img/image-20221207194747527.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ifn6KYk-1672113443042)(../../img/image-20221207194747527.png)]

可以看出 T 状态也是阻塞状态的一种,但是有没有挂起完全不知道,这是由操作系统自己决定的!

补充:前台和后台进程

  • 前台进程:可以被 [Ctrl]+c 杀掉的进程,命令行在这个终端可以起作用,S+ 的 + 号就是前台进程的意思
  • 后台进程:无法被 [Ctrl]+c 杀掉的进程,命令行在这个终端也可以起作用,这个时候没有 + 号

详细的测试自主尝试(用循环配合进程状态即可看到效果)~~

5、追踪停止状态 t(tracing stop)

​ 🐎 追踪暂停状态t 是也是一种暂停状态,不过它是一种特殊暂停状态,表示该进程正在被追踪。比如在使用 gdb 调试打断点的时候,程序暂停下来的状态就是 t状态,这里就不细谈了!

6、死亡状态 X(dead)

​ 🐎 这个状态只是一个返回状态,你不会在任务列表里看到这个状态

死亡状态只是一个返回状态,当一个进程的退出信息被读取后,该进程所申请的资源就会立即被释放,该进程也一瞬间就不存在了,所以你几乎不会在任务列表当中看到死亡状态

7、僵尸状态 Z(zombie)

我们创建一个进程的目的是为了让其帮我们完成某种任务,而既然是完成任务,进程在结束前就应该返回任务执行的结果,供父进程或者操作系统读取;所以进程退出的时候,不能立即释放该进程的资源,该进程要保存一段时间,让父进程或操作系统读取该进程的执行结果(保存一段时间是对于CPU 而言)

​ 🐎 僵尸状态 Z 就是进程退出时,该进程的资源不能立即被释放,该进程要保留一段时间,等待父进程或操作系统读取该进程结果的过程就叫僵尸状态

  • 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
  • 只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

​ 比如,有一个人在跑步中倒下了,没有了呼吸,这个人已经离开这个世界了(进程退出),警察来到现场对首先这个人退出的原因进行调查(父进程或操作系统读取该进程的执行结果),而不是立即进行对这个人进行火葬(资源清理)

7.1 僵尸进程

​ 🐎 处于僵尸状态的进程,我们就称之为僵尸进程

​ 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码

下面用例子测试僵尸进程

测试代码:

代码语言:javascript
复制
#include<stdio.h>    
#include<unistd.h>    
#include<stdlib.h>    
    
int main()    
{    
    int id = fork();    
    if(id == 0)    
    {    
        printf("child, pid=%d, ppid=%d\n", getpid(), getppid());    
        sleep(5);    
        exit(1);    
    }    
    else    
    {    
        while(1)    
        {
        	printf("parent, pid=%d, ppid=%d\n", getpid(), getppid());           
            sleep(2);    
        }    
    }    
    return 0;    
}    

运行代码后,可以通过以下监控脚本查看:

代码语言:javascript
复制
while :; do ps axj | head -1 && ps axj | grep myproc | grep -v grep;echo "################";sleep 1;done

当子进程退出后,子进程的状态就变成了僵尸状态 !

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CFp0wY0f-1672113443042)(../../img/image-20221207205222989.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CFp0wY0f-1672113443042)(../../img/image-20221207205222989.png)]

僵尸进程危害

  1. 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,子进程就一直处于Z状态
  2. 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在 task_struct(PCB)中,换句话说, Z状态一直不退出, PCB一直都要维护
  3. 那一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费。因为数据结对象本身就要占用内存,比如C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
  4. 会造成内存泄漏

这里只讲了僵尸状态是一个问题,没有谈怎么解决,后面进程控制谈解决。

7.2 孤儿进程

前面子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态,该进程就为僵尸进程,如果反过来。父进程先退出,子进程还在执行,这是什么进程?

​ 🐎 定义父进程先退出,子进程就称之为“孤儿进程”,孤儿进程会被1号 init进程(有些版本高的叫做systemd)领养,由 init进程回收,1号进程就是操作系统

下面用例子测试孤儿进程(其实就是将exit()放到了父进程中)

测试代码:

代码语言:javascript
复制
#include<stdio.h>    
#include<unistd.h>    
#include<stdlib.h>    
    
int main()    
{    
    int id = fork();    
    if(id == 0)    
    {    
        while(1)
        {
        	printf("child, pid=%d, ppid=%d\n", getpid(), getppid());    
        	sleep(5);
        }
    }    
    else    
    {    
        printf("parent, pid=%d, ppid=%d\n", getpid(), getppid());           
        sleep(2);    
        exit(1); 
    }    
    return 0;    
}    

运行代码后,同样可以通过监控脚本查看过程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zowM9vUP-1672113443042)(../../img/image-20221207221026990.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zowM9vUP-1672113443042)(../../img/image-20221207221026990.png)]

可以看到 5 秒前有 2 个进程,5 秒后父进程死亡了(这里没有被僵尸的原因是父进程也有父进程 25369 -> bash),只有 1 个子进程。这里我们称没有父进程的子进程为孤儿进程也就是 1127此时孤儿进程会被 1号进程领养,它是 systemd(操作系统),此时操作系统就可以直接对 1127 回收资源。 且进程状态会由前台转换为后台,后台进程可以使用 kill -9 来结束进程。

这种现象是一定存在的,如果不对子进程进行领养,对应的僵尸进程便没有人能回收了。如果是前台进程创建子进程,如果子进程变孤儿了,子进程会自动变成后台进程

7.3 1号进程

操作系统启动之前是有 0号进程的,只不过完全启动成功后,0号进程就被1号进程取代了,具体的取代方案,后面学习 进程替换时再谈。可以看到 pid 排名靠前的进程都是由 root 来启动的。注意在 Centos7.6 下,它的 1号进程 叫做 systemd,而 Centos6.5 下,它的 1号进程 叫做 initd

面试题 :什么样的进程杀不死 ❓ D状态进程 和 Z状态进程。因为一个是在深度休眠,操作系统都得叫大哥,一个是已经死了。

Ⅲ. Linux中的优先级

1、优先级的基本概念

  • cpu资源分配的先后顺序,就是指进程的优先权(priority)。
  • 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的 linux 很有用,可以改善系统性能。
  • 还可以把进程运行到指定的 CPU 上,这样一来,把不重要的进程安排到某个 CPU,可以大大改善系统整体性能。
  • 存在优先级的本质就是资源太少,想要合理的管理!

2、查看系统优先级

linux 或者 unix 系统中,用 ps –l 命令则会类似输出以下几个内容:

我们很容易注意到其中的几个重要信息,有下:

  • UID(user id) : 代表执行者的身份
  • PID (process id): 代表这个进程的代号
  • PPID (parent process id):代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
  • TTY:可以理解为终端设备
  • CMD:是当前进程的命令。
  • PRI(priority) :代表这个进程可被执行的优先级,其值越小越早被执行
  • NI (nice):代表这个进程的nice值

下面我们就要对 PRI 和 NI 进行详细讲解:

♐️ PRI and NI

  • PRI 比较好理解,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,PRI 值越小进程的优先级别越高
  • NI 就是 nice 值,其表示进程可被执行的优先级的修正数值,其==取值范围是 [-20, 19]==,一共 40 个级别。
  • PRI 值越小越快被执行,那么加入 nice 值后,将会使得 PRI 变为:PRI(new) = PRI(old) + nice (注意旧的 PRI 永远都是80)
  • 这样,当 nice 值为负值的时候,那么该程序将会优先级值将变小,其优先级会变高,则其越快被执行
  • 所以在 Linux 下,调整进程优先级就是调整进程 nice 值

🐰 PRI vs NI

  • 需要强调一点的是,进程的 nice 值不是进程的优先级,他们不是一个概念,但是进程 nice 值会影响到进程的优先级变化。
  • 可以理解 nice 值是进程优先级的修正数据。

3、调整优先级

使用 top命令 配合 ‘r’ 可以调整已存在的进程的 nice 值:

现在我们按下 r 键,并输入对应进程的 PID 即可进行 nice 值的修改!

🔺 注意: 一般来说需要 sudo 一下才能修改 nice 值!

🐇 其他概念

  • 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
  • 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
  • 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
  • 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为 并发

那我们如何去理解这个进程切换的过程呢,下面我们来详细讨论一下:

Ⅳ. 进程切换

首先我们先要知道,CPU 中是有大量的寄存器的,比如说常见的BPSP等等,如下图:

而寄存器一直都是在做三件事情:①取指令 ②分析指令 ③执行指令

这个时候如果有一个进程需要我们加载到内存的话,那么就会有寄存器指向该进程的结构体PCB,如下图:

比如当我们在执行该进程的某条指令的时候,会有一个寄存器叫做 pc/eip 指向 当前正在执行指令的下一条指令的地址

♻️ 要注意的是,当我们的进程在运行的时候,一定会产生非常多的临时数据(比如返回值等等),这些数据都只属于当前线程

🐎 换句话说,CPU内部虽然只有一套寄存器硬件,但是寄存器里面保存的数据,只属于当前进程,而不属于其他进程!

​ 还有一个点:寄存器硬件 != 寄存器内的数据

​ 虽然进程在运行的时候占用 CPU,但是该进程并不是一直要占用 CPU 直到进程结束的!比如说,我们用 while(1) 写一个死循环,要是按照前面的说法,进程要是一直占用 CPU 直到进程结束才释放,那么结果就会导致一旦我们写了死循环,我们就无法操作其他进程了,其它进程也就卡死了。

​ 但是真实的情况是就算我们写了死循环,一般也是不会卡死的,我们还是可以操作其他的进程的,比如在 VS 写了死循环后,我们还是照样可以上 qq,打游戏等等。这是 因为进程在运行的时候,都有各自的时间片

那什么是进程的时间片呢?

🌛 其实非常好理解,也就是每个进程轮流占用 CPU 的时间。比如说当前有 100 个进程正在运行队列中,假设进程的时间片是 10ms,也就是每个进程会分别被轮流执行 10ms,那么一秒之内每个进程都会被执行一次。

​ *️⃣ 而我们要关注的重点不只是进程的执行时间,还有进程每次被调度执行的时候,有可能这个进程还没有跑完指令,它的时间片就到了,那么当下次又轮到该进程被调度执行的时候,那些当时没跑完的指令和数据是我们怎么办呢?

​ 我们先举个例子,比如说张三大一的时候挂了一门学科,但是他因为身体好想去义务征兵,于是在没有告知老师和学校的情况下,和同学吃顿散伙饭就走了,等到当兵回来发现,自己已经挂了二十多门课了。这种情况的发生就是因为张三走的时候没有告知老师和学校,他留在学校的一堆数据还没有得到处理,进而导致了这种后果。所以为了避免这种后果,张三在离开学校的时候需要向学校告知,让学校保存档案数据!

​ 和上面的例子对比一下,其实张三就是进程,而学校就是CPU,兵队就是运行队列或其他队列,当进程在CPU执行的时间片到了之后,进程会重新回到运行队列中排队,相当于张三征兵;而进程离开CPU的时候,按我们所知的,为了避免留在CPU的数据被误处理,每次进程离开CPU的时候都要将数据保存和带走(至于数据存放到哪去我们不关心,可以看作数据暂时放回了PCB中,但实际上是操作系统在管的这些数据),这样子才能避免数据被误处理,这个过程叫做 数据的上下文保护

​ 而当张三回到学校的时候,也就是重新轮到该进程被CPU调度的时候,那么张三就需要先去通知学校,让学校安排复学手续,相当于进程回到CPU调度的时候要将原来的数据带回来恢复,继续往下执行,这个过程叫做 数据的上下文恢复

在这里插入图片描述
在这里插入图片描述

总的来说,也就是 当进程在切换的时候,要进行进程的上下文保护;当进程在恢复运行的时候,要进行上下文的恢复!

在任何时刻,CPU里面的寄存器中的数据,看起来是在寄存器上,但是其实寄存器中的数据,只属于当前运行的进程!所以可以看出寄存器被所有进程共享,寄存器内的数据,是每个进程各自私有的-----上下文数据

♻️ 总结: 当一个进程需要切换到另一个进程时,操作系统会执行以下几个步骤:

  1. 保存当前进程的状态,包括它的寄存器值、程序计数器和栈指针等。
  2. 将 CPU 的控制权交给新的进程。
  3. 恢复新进程的状态,将它的寄存器值、程序计数器和栈指针等恢复到之前保存的状态。
  4. 开始执行新进程。

进程切换是操作系统内部自动完成的,用户不需要关心这个过程。不过,进程切换会消耗一定的时间,因此,操作系统会根据实际情况来决定是否执行进程切换,以便尽量降低进程切换对性能的影响。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-12-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Ⅰ. OS进程状态的概念
    • 1、运行状态
      • 2、阻塞状态
        • 3、挂起状态
        • Ⅱ. Linux中的进程状态
          • 1、运行状态R(Running)
            • 2、睡眠状态S(Sleeping)
              • 3、磁盘休眠状态 D(Disk sleep)
                • 4、停止状态 T(stopped)
                  • 补充:前台和后台进程
                    • 5、追踪停止状态 t(tracing stop)
                      • 6、死亡状态 X(dead)
                        • 7、僵尸状态 Z(zombie)
                          • 7.1 僵尸进程
                          • 僵尸进程危害
                          • 7.2 孤儿进程
                          • 7.3 1号进程
                      • Ⅲ. Linux中的优先级
                        • 1、优先级的基本概念
                          • 2、查看系统优先级
                            • ♐️ PRI and NI
                              • 🐰 PRI vs NI
                                • 3、调整优先级
                                  • 🐇 其他概念
                                    • ❓ 那什么是进程的时间片呢?
                                • Ⅳ. 进程切换
                                领券
                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档