专题6-Linux内核子系统
第1课-Linux内存管理子系统
1. 内存管理模型
(1)子系统简介
System Call Interface(SCI)系统调用接口 |
|
Process Management(PM) |
Virtual File System(VFS)虚拟文件系统 |
Memory Management(MM) |
Network Stack |
Arch 体系结构相关 |
Device Drivers(DD) |
Linux内核这七个子系统之间都是有所联系的,我们这阶段学习的是驱动子系统。它和内存管理子系统、进程管理子系统、VFS子系统之间都是联系密切的。我们这一个专题要研究的就是内存和进程这两个子系统。
内存和CPU是运行系统中十分中要的组成部分,对于内存来说,它虽然是有效的,但是它也是有限的。
(2)管理模型
上面的是一幅经典的内存分配的图,我们用线框起来部分的作用是物理内存的分配,其他部分的功能是地址映射。
从而我们也能明白内存管理子系统的职能是:
- 虚拟地址与物理地址的映射。
- 物理内存的分配。
2. 地址映射管理
地址映射管理主要体现在:虚拟地址空间分布和虚拟地址转化为物理地址的方法上。
(1)虚拟地址空间分布
我们前面学习的裸机开发,从芯片手册上看到的GPIO地址等等,这些都是物理地址。我们最后要访问到硬件,那么一定是要访问物理地址的。但是在linux系统当中,我们都不用物理地址,我们用的是虚拟地址,也就是程序当中出现的地址。例如我们用啊malloc函数分配出来的内存,就是虚拟地址。
我们linux系统支持的虚拟地址空间是由它的硬件来决定的。例如,我们32的处理器,地址总线是32位,它能访问的虚拟地址空间是232也就是4G。
这4G的空间又被分成了两个部分,0G-3G部分是用户空间,放的是用户程序也就是应用程序。3G-4G使我们的内核空间,其中被分为了4个部分。
- 直接映射区:3G-3G.896MB,最高到896MB
- vmalloc区
- 永久内核映射区
- 固定映射区
我们之所以做这样的划分,是因为它们到物理内存的映射关系是不同的。所由的关系在上面的图上都能看出来。
(2)虚拟地址如何转化为物理地址
我们将管理模型图进行简化,如下:
我们的cr3寄存器作为基地址,我们将32位的虚拟地址的高10位取出来作为一个偏移,这样就能从我们的页目录里面找到一个地址。页目录里面存放的是页表的基地址,加上我们32位虚拟地址的中间地址,能在页表里面找到一个地址。这个地址是指向我们物理页的基地址,加上32位虚拟地址的前12位就能找到我们物理页(也叫页帧,一个页帧一般4Kb)。
- 对于直接映射区域,被称为低端内存,直接体现,它的虚拟区域与对应的内存单元有着直接的对应关系:这个区域的虚拟地址就是真实的物理地址,加上3G。
- vmalloc区:既可以访问低端的映射区,也可也访问高端的映射区。这个区域的虚拟地址与物理地址没有直接线性的对应关系。
- 永久内存映射区:就是用来访问高端内存的。
- 固定映射区:这个区域的虚拟地址都与我们的一些固定的寄存器有着对应的关系的。
3. 物理地址分配管理
我们现在思考一个问题,究竟什么时候分配物理内存?有人说是运行new和malloc函数的时候。这在其他的系统中可能对,但是在linux系统中不对。linux实行的是虚拟地址管理的机制。
当我们用函数申请了虚拟地址,它不会直接就给分配对应的物理地址,为了减少浪费,只有我们访问虚拟地址的时候,虚拟地址才会被分配物理地址。
我们在上图中可以看到,只有Kmalloc函数是直接申请物理内存区域的。其他的函数都是在页面异常(也就是访问)的时候才会申请物理内存的。
第2课-Linux进程管理子系统
大纲
Linux进程要素:程序与进程、进程4要素、Linux进程状态、进程描述结构
Linux进程调度:调度策略、调度时机、调度步骤
一.Linux进程管理子系统
1. 进程与程序
程序:
存放在磁盘上的一系列代码和数据的可执行映像,是一个静止的实体。
进程:
是一个执行中的程序,它是动态的实体。
2. 进程的四要素(必背)
- 有一段程序供其执行。这段程序不一定是某个进程所专有专有,可以与其他进程共有。
- 有进程专用的内核空间堆栈。
- 在内核中有一个task_struct数据结构,即通常所说的“进程控制块”。有了这个数据结构,进程才能成为内核调度的基本单位接收内核的调度。
- 有独立的用户空间。
3. Linux进程状态
三态图:
linux细分的三态图:
- (1)TASK_RUNNING
进程正在被CPU执行,或者已经准备就绪,随时可以执行。当一个进程刚被创建时,就处于TASK_RUNNING状态。
- (2)TASK_INTERRUPTIBLE(阻塞态,可以唤醒)
处于等待中的进程,待等待条件为真时被唤醒,也可以被信号或者中断唤醒。
- (3)TASK_UNINTERRUPTIBLE (不可以被打断)
处于等待中的进程,待资源有效时唤醒,但不可以由其它进程通过信号(signal)或中断唤醒。
- (4)TASK_KILLABLE
Linux2.6.25新引入的进程睡眠状态,原理类似于TASK_UNINTERRUPTIBLE,但是可以被
致命信号(SIGKILL)唤醒。
- (5)TASK_TRACED
正处于被调试状态的进程。
- (6)TASK_DEAD
进程退出时(调用do_exit),所处的状态。
4. Linux进程描述
在Linux内核代码中,线程、进程都使用结构task_struct(sched.h)来表示,它包含了大量描述进程/线程的信息,其中比较重要的有:
v pid_t pid; //进程号
v vlong state; //进程状态
v vint prio; //进程优先级
v
二.进程调度
1. 什么是调度
从就绪的进程中选出最适合的一个来执行。学习调度需要掌握哪些知识点?
- (1)调度策略
- (2)调度时机
- (3)调度步骤
2. 调度策略
SCHED_NORMAL(SCHED_OTHER):普通的分时进程。
vSCHED_FIFO :先入先出的实时进程。
SCHED_RR:时间片轮转的实时进程(一个进行运行了一个片轮,下个进程运行)。
SCHED_BATCH:批处理进程。
SCHED_IDLE: 只在系统空闲时才能够被调度执行的进程。
实时进程的优先级别比普通的进程的优先级别高,调度的时候,优先调用。
3. 调度机制
什么时候发生调度?即schedule()函数什么时候被调用?
(1)主动式:
在内核中直接调用schedule()。当进程需要等待资源等而暂时停止运行时,会把自己的状态置于挂起(睡眠),并主动请求调度,让出CPU。
范例:
current->state = TASK_INTERRUPTIBLE; (current是个宏,是task_struct类型的指针,state记录的是一个状态,TASK_INTERRUPTIBLE表示阻塞态)
schedule();
(2)被动式:
被动式调度又名:抢占式调度。分为:用户态抢占(Linux2.4、Linux2.6)和内核态抢占 (Linux2.6)。
用户态抢占:
用户抢占发生在:
- 从系统调用返回用户空间。
- 从中断处理程序返回用户空间。
内核即将返回用户空间的时候,如果need_resched标志被设置,会导致schedule()被调用,即发生用户抢占。
- 当某个进程耗尽它的时间片时,会设置need_resched标志。
- 当一个优先级更高的进程进入可执行状态的时候,也会设置need_resched标志。
内核态抢占:
用户态抢占缺陷
进程/线程一旦运行到内核态,就可以一直执行,直到它主动放弃或时间片耗尽为止。这样会导致一些非常紧急的进程或线程将长时间得不到运行,降低整个系统的实时性。
改进方式
允许系统在内核态也支持抢占,更高优先级的进程/线程可以抢占正在内核态运行的低优先级进程/线程。
内核抢占可能发生在:
- 中断处理程序完成,返回内核空间之前。
- 当内核代码再一次具有可抢占性的时候,如解锁及使能软中断等
注意,在支持内核抢占的系统中,某些特例下是不允许抢占的:
v 内核正在运行中断处理。
v 内核正在进行中断上下文的Bottom Half(中断的底半部)处理。硬件中断返回前会执行软中断,此时仍然处于中断上下文中。
v 进程正持有spinlock自旋锁、writelock/readlock读写锁等,当持有这些锁时,不应该被抢占,否则由于抢占将可能导致其他进程长期得不到锁,而让系统处于死锁状态。
v 内核正在执行调度程序Scheduler。抢占的原因就是为了进行新的调度,没有理由将调度程序抢占掉再运行调度程序
(3)抢占计数:
为保证Linux内核在以上情况下不会被抢占,抢占式内核使用了一个变量preempt_count,称为内核抢占计数。这一变量被设置在进程的thread_info结构中。每当内核要进入以上几种状态时,变量preempt_count就加1,指示内核不允许抢占。每当内核从以上几种状态退出时,变量preempt_count就减1,同时进行可抢占的判断与调度。
调度步骤--Schedule函数工作流程如下:
v 清理当前运行中的进程;
v 选择下一个要运行的进程;
v 设置新进程的运行环境;
v 进程上下文切换。