一. Linux内存管理子系统
1. 内存管理模型(Memory Management)
内存管理子系统主要作用为: (实现代码不要求掌握)
一,完成虚拟地址与物理地址的映射;
二,进行物理内存的分配
2. 虚拟地址与物理地址的映射
2.1 虚拟地址空间分布
在Linux系统中用到的都是虚拟地址,32位Linux系统能访问到的虚拟地址空间为4GB。其中0~3G是用户空间,里面存放的用户应用程序,每个程序都享有3GB的虚拟地址空间;3GB~4GB的空间为内核空间,其中包括:直接映射区(最高为896MB,根据硬件的物理地址大小来定)、vmalloc区、永久内核映射区和固定映射区。
直接映射区为物理内存直接映射到物理内存的区域。
固定映射区用来访问特殊的寄存器。
2.2 虚拟地址幻化为物理地址
3.物理地址分配管理
malloc函数只是分配了一个虚拟地址,并没有立刻分配物理地址空间,用户当访问这个虚拟地址时,才会产生一个缺页异常,系统才会从空闲页框中真正分配一个物理地址与之建立映射关系。
kmalloc函数通过slab管理器,分配的虚拟地址已经跟物理地址建立映射关系。
二. Linux进程管理子系统
1. Linux进程要素
1.1 程序与进程
程序:存放在磁盘上的一系列代码和数据的可执行映像,是一个静止的尸体。
进程:是一个执行中的程序,它是动态的实体。
1.2 进程的四要素
1.有一段程序供其执行。这段程序不一定是某个进程所专有,可以与其他进程共用。
2.有进程专用的内核空间堆栈。
3.在内核中有一个task_struct数据结构,即通常所说的“进程控制块”。有了这个数据结构,进程才能成为内核调度的一个基本单位,接受内核的调度。
4.有独立的用户空间。如果只是有共享的用户空间,那是用户线程,没有,那是内核线程。
1.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),所处的状态。
1.4 进程描述结构
在Linux内核代码中,线程、进程都是用结构体task_struct(sched.h)来表示,它包含了大量描述进程、线程的信息,其中比较重要的有:
pid_t pid; //进程号
long state; //进程状态
int prio; //进程优先级
2.Linux进程调度
从就绪的进程中选出最适合的一个执行。
2.1 调度策略
SCHED_NORMAL(SCHED_OTHER):普通的分时进程
SCHED_FIFO:先入先出的实时进程
SCHED_RR:时间片轮转的实时进程
SCHED_BATCH:批处理进程
SCHED_IDLE:只在系统空闲时才能够被调度执行的进程
2.2 调度时机
系统发生进程调度的时候,会调用schedule()函数。
2.2.1 主动式调度
在内核中直接调用schedule(),当进程需要等待资源而暂时停止运行时,会把自己的状态至于挂起(睡眠)状态,并主动请求调度,让出CPU。
范例:1.current->state = TASK_INTERRUPTIBLE;
2.schedule();
2.2.2 被动式调度(抢占式调度)
分为用户态抢占(Linux2.4、Linux2.6)和内核态抢占(Linux2.6)。
用户态抢占发生在:从系统调用返回用户空间的时候,或者从中断处理程序返回用户空间时。内核即将返回用户空间时,如果need_resched标志被设置,会导致schedule()被调用,即发生用户态抢占。need_resched标志被设置的概况可能发生在:当某个进程耗尽它的时间片时,或者当一个优先级更高的进程进入可执行状态的时候。
用户态抢占的缺陷在于,进程或线程一旦运行到内核态,就可以一直执行,直到它主动放弃或时间片耗尽为止。这样会导致一些紧急的进程或线程将长时间得不到运行,降低整个系统的实时性。为了改进这缺陷,Linux2.6允许系统在内核态也支持抢占,更高优先级的进程或线程可以抢占正在内核态运行的低优先级的进程或线程。
内核态抢占有可能发生在:中断处理程序完成后,返回内核空间之前,或者当内核代码再一次具有可抢占性的时候,例如解锁或使能软中断等。
在支持内核态抢占的系统中,某些情况下是不允许抢占的,例如一下情况:
1)内核正在运行中断处理;
2)内核正在进行中断上下文的Bootom Half(中断的低半部)处理。硬件中断返回前回执行软中断,此时仍然处于中断上下文中。
3)进程正持有spinlock自选所、writelock或readlock读写锁等。当持有这些锁时,不应该被抢占,否则将有可能导致其他进程长期得不到锁,而让系统处于死锁状态。
4)内核正在执行调度程序scheduler。抢占的原因就是为了进行新的调度,没有理由将调度程序抢占掉再运行调度程序。
为保证Linux内核在以上几种情况不会被抢占,抢占式内核使用一个变量preempt_count,称为内核抢占计数。这一变量被设置在进程的thread_info结构中,每当内核要进入以上几种状态是,变量preempt_count就+1,提示内核不允许抢占。每当内核从以上几种状态退出时,变量preempt_count就-1,同时进行可抢占的判断与调度。
2.3 调度步骤
schedule函数工作流程如下:
1)清理当前运行中的进程;
2)选择下一个要运行的进程;
3)设置新进程的运行环境;
4)进程的上下文切换。