进程(6)进程切换,Linux中的进程组织,Linux进程调度算法

本章目标

1.进程切换

2.Linux中的进程组织

3.Linux进程调度算法

1.进程切换

前面几节中,我们讨论过进程pcb中上下文数据,它保存的是当前进程中产生的临时数据.

我们这个小节讲述的东西和它有很大的关系.

在,我们这里讨论的进程切换,实际上就是cpu的上下文切换.也可以叫做任务切换.当cpu执行多任务的的时候,内核决定从当前进程转而去执行其他进程,就需要吧当前进程产生的临时数据拿出来保存到自己的pcb当中.也就是保存它当前的运行状态.因为这些内容是保存在堆栈的.当下一个进程准备开始执行的时候要将自己的pcb中的上下文数据拿出来载入到cpu的寄存器当中.这个过程我们叫做进程切换.

我们可以给出Linux0.11内核中的源码

这是task_struct中的内容.在更高版本的内核中,上下文呢数据这一部分被从task_struct中提出来了,但仍然可以通过间接的方式找到它.

2.Linux中的进程组织

在前面我们说过一个进程要将自身的属性进行描述而产生了pcb,具体到Linux当中就是task_struct,而对于一款os来说,一个模块只将自身的属性描述清楚是不行的.仍然需要数据结构来进行管理.

而在Linux中我们采用双向链表的方式对Linux的进程进行管理组织.但是这种链表与我们平常所见的链表并不相同

这是我们常见的链表,它由数据域和指针域所构成,但是这种链表存在弊端.

1,它只能存储固定类型.

2.它的扩展性不足.

3,它不够泛用.

在我介绍Linux中的链表结构前我们要先提出两个语法知识

因为Linux操作系统是用汇编和c语言来进行构造的.所以描述一个结构的信息自然是结构体.

对于一个结构体.整个结构体的地址是与第一个元素的首地址是相同的

我们在这里简单画一个内核地址空间.

对于栈区它是从高地址向地址生长的.

但是对于一个结构体的内部元素来说,它是根据声明的顺序从下往上依此创建的.

也就是我们对这个结构体取地址它是等于我们对结构体内部的a成员取地址的

这点放在堆空间同样适用.

c 复制代码
&abj==&(abj.a)

在c语言当中对一个变量的地址数字是在众多开辟字节当中地址最小的那个

举个例子我们对一个数组取地址,我们拿到的地址数字是这个数组首元素的第一个字节的地址.

第二个,我们知道一个结构体成员的地址,我们想知道整个结构体的地址,我们改如何获取.
我们可以通过将0进行强转成结构体类型然后访问c成员,对这个c进行取地址.我们现在拿到的就是该成员在结构体相对于整个结构体起始地址的偏移量

我们对这个已知的结构体地址减去这个偏移量.我们就拿到了整个结构体的地址.

有了整个结构体的地址我们就可以去访问在整个结构体里面的所有成员,

在标准库中,提供了offsetof这个宏来去获取特定成员在整个结构体当中的偏移量.

c 复制代码
#include <stdio.h>
#include <stddef.h>

struct Example {
    int a;
    char b;
    double c;
};

int main() {
    printf("Offset of a: %zu\n", offsetof(struct Example, a)); // 通常为0
    printf("Offset of b: %zu\n", offsetof(struct Example, b)); // 通常为4(取决于对齐)
    printf("Offset of c: %zu\n", offsetof(struct Example, c)); // 通常为8(取决于对齐)

    return 0;
}

有了上面东西的铺垫我们可以去讲在Linux中的链表结构了

在Linux中的task_struct,中有一个list_head类型的结构体.这个就是我们的链表

在这个链表当中有两个自身类型的指针.

我们可以画个图对比以下两种结构

我们可以看到,在Linux当中我们将指针域进行了封装,这样当我们可以在外面拿整个头指针,指向第一个pcb当中的list_head,如果想访问下一个链表就去通过当前结构里拿到.

想去访问pcb当中的内容,可以通过自身的地址(自身的地址就是next指针的地址,因为整个结构体的地址等于结构体内声明第一个元素的地址)去减去偏移量拿到整个pcb起始地址就可以访问其他data成员了.

这就引出了1个问题

1.为什么Linux在设计的时候要多此一举,对指针域进行封装?

这样做是为了方便维护与扩展.

因为当需要这个结构的时候就可以只将其这个list_head结构进行包含.

我们在进行管理的时候就不需要对data去考虑了.只需要这个list_head就能够完成

增删改查.

2.可以让同一份数据属于不同的结构.

我们同样可以封装出一个RBtree或者hash的结构,然后对其种的数据也将它放出来.

这样同一个数据就可以属于多个结构

在Linux中就是这么做的.我们可以看一下源码

这是task_struct中的结构.在这里同一份pcb属于多个结构当中有的是管理父进程的,管理子进程的,还要很多.我在这里只是表明一种观点.这种方法大大增加了代码的可复用性.在之前的结构当中.我们想要分别对同一份数据通过不同方式进行管理,往往会在重复定义这些数据.这样就造成了空间的浪费以及空间的冗余.在当下一个地址空间只有4gb的i情况下,这种方法能大大节约开销

这种方法还还可以在同一个结构上去链接不同不同的结构体虽然这么做的意义并不大

3.Linux进程调度算法

在前面我们在Linux的进程状态的小节的时候我们讨论了过每一个cpu都有一个运行队列.这种队列是遵循先进先出的规则.在早期的操作系统或者是单片机控制这种实时操作系统是能够完成需求的但是对于现在的我们使用的操作系统需要满足一个内核多任务同时推进的需求就衍生了时间片轮转和优先级的概念.

在Linux中,我们仍然采用上述的链表结构去模拟这个调度队列

这个每个cpu的调度队列在最外面这层结构,我们需要关注的只有红框和篮筐的东西和上面active和expired两个指针.

其他的都是这个cpu的调度队列的属性信息.我们暂时不需要关注.

红框和篮筐的东西是一样的,他们被成为活跃队列和过期队列.

其中用用来保存进程pcb就是这两个140的队列它的具体结构式这样的

它本本质是一个struct list head类型的数组,其中这个list_head是用来模拟队列的,

根据优先级,我们的进程被划分为140个等级,其种1-99是属于实时操作系统的.

我们不关系,100-140的内容是属于分时操作系统的.我们的进程根据old_pri和nice值算出自身的优先级,然后通过+40的方式映射到100-140的内容当中.

在这里我们可以得出一个结论,优先级数字本质上是一个数组下标

也就是是说,在这一层结构中,它本质上是被当中一个hash表.根据优先级选择一个进程的过程实际上是一个hash的过程.

一旦确定是哪个优先级队列的,剩下的就是fifo的过程,因为先进入的进程永远都是处于前面的,第一个永远都是最合适的,因为从100到140这个数字是常数级别的,这个查找的时间复杂度是o(1)的,因为调取一个进程的过程都是从该队列的头部获取,这个过程也是o(1)的,所以它的整体时间复杂度是o(1)的,

但是为了提高查找速度.我们采用位图结构,一个long类型的整型在当时是4个字节,一个字节是32个bit位,我们只需要5个整型变量就能够完全知道该调度队列的所有优先级是否有进程.这个过程在算法中被成为二进制枚举.,我们只需要检测该变量是否为0,0我们就跳过,不是0就根据比特位去查找,这样就能够将我们的查找速度进一步进行压缩,变为十几次

我们还维护了一个nr_active的变量用来记录该调度队列中有多少进程.

当一个进程执行完自身的时间片或者是任务就让这个变量--,有增加进入队列的进程就让它++.

我们在前面指出过,我们这样的队列一共有两个,分别通过active和expired两个指针来区分.这个两个队列是被放在一个数组当中.

这个类型的数组当中.当一个队列中的进程全部执行完,或者说是任务全部执行完.

这个nr_acitve的变量成为0就会让这两个active和expired指针互相交换内容,让原先的活跃队列变为过期队列,让过期队列成为新的活跃队列.

我们新创建的进程如果不需要去根据优先级进行抢占的话,一般是入过期队列的,时间片执行完任务却没有结束的任务同样进入过期队列.

这么做有一个好处是能够防止进程饥饿的问题.

想想一下,我们的进程是根据优先级去进行调度的有一个优先级为60的进程,而我们只有一个调度队列的话.然后有一堆调度任务他们的优先级为61,那么60的进程只要任务没有完成,它就会持续进入队列,永远被最先调度.这样我们的调度算法就成为了一个线性串行执行的了.如果增加一个队列让时间片用完的或者是新进入进程进入过期队列,当活跃队列调度完,交换指针.这样就能完美的解决进程饥饿的问题

相关推荐
业精于勤的牙2 小时前
最长特殊序列(三)
算法
皮卡蛋炒饭.2 小时前
前缀和与差分
算法
Jelly-小丑鱼2 小时前
Linux搭建SQLserver数据库和Orical数据库
linux·运维·数据库·sqlserver·oracal·docker容器数据库
CAU界编程小白2 小时前
Linux编程系列之权限理解和基础开发工具的使用(下)
linux
Run_Teenage2 小时前
Linux:进程等待
linux·运维·服务器
Trouvaille ~2 小时前
【Linux】从磁盘到文件系统:深入理解Ext2文件系统
linux·运维·网络·c++·磁盘·文件系统·inode
wdfk_prog2 小时前
[Linux]学习笔记系列 -- [fs]file
linux·笔记·学习
0x7F7F7F7F2 小时前
算法竞赛数学知识大全
算法
___波子 Pro Max.2 小时前
Linux ps命令-ef参数详解
linux