选定某版本的Linux内核源码,研读进程管理和内存管理部分相关源码。在此基础上:1)对Linux进程调度机制及算法进行分析,2)对Linux内存管理机制及算法进行分析,并撰写实验报告总结以上两部分内容。
Linux是一个功能强大的操作系统,同时也是一个自由、免费、开源的软件。源代码的阅读和理解是一项重要的也是必要的基本功。在阅读过程中可以学习到很多编程方法和技巧,对于提高自己的编程水平有很大帮助。通过研读Linux源码,能够掌握操作系统中的一些底层知识知识,也能更加深刻地理解操作系统原理。在本次实验中,主要对Linux源代码的内存管理和进程管理部分做了一些简单的研究。通过对Linux进程调度机制及算法进行分析,以及对Linux内存管理机制及算法进行分析,进一步了解操作系统的内存管理机制以及进程管理机制,对操作系统课程有更加深入的理解。
环境:Fedora 64位
Linux版本:version 2.6.21-1.3194.fc7
1、 内存管理概述
Linux内存管理是内核最复杂的任务之一,主要是因为它用到许多CPU提供的功能,而且和这些功能密切相关。Linux内存管理有各种机智,如内存的初始化机制,地址映射机制,企业机制交换机制,内存分配和回收机制,缓存和刷新机制以及内存共享机制。而且Linux内存管理设计充分利用了计算机系统的虚拟存储技术,实现了虚拟存储器管理。实验中使用的Linux是Intel 386,具有段机制和页机制。
在Linux系统中全部内存分布情况为:
2、 虚拟内核空间到物理空间的映射
内核空间中存放的是内核代码和数据和进程的用户控件中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,他们都处于虚拟内存中。
Include/asm-i386/page.h中有对内核空间中地址映射的说明:
如果物理内存大于950mb,那么在编译内核设计需要加两个选项。如果物理内存小于950mb,则对于内核空间而言给定一个虚地址x,其物理地址为“x-PAGE_OFFSET”,给定一个物理地址x其虚地址为“x+PAGE_OFFSET”。
Include/asm-i386/page.h中有对页面初始化的说明:
由代码可以看出,页机制的零页,即empty_zero_page页,存放的是系统的启动参数和命令行参数。
4、 物理页面的数据结构
对物理页面的描述在/include/linux/mm_types.h
源码的注释中对这个数据结构给出了一定的说明。当页面的数据来自一个文件,index代表该页面中的数据在文件中的偏移量。系统中的每个物理页都有一个page结构。系统在初始化阶段根据内存的大小建立起一个page结构的数组mem_map。数组的下标就是内存中物理页面的序号。
在struct page描述一个页框时,我们比较关注的成员变量有unsigned long flags、struct list_head lru和atomic_t _count。
flags:包含有很多信息,包括此页框属于的node结点号,此页框属于的zone号和此页框的属性。
lru:用于将此页描述符放入相应的链表,比如伙伴系统或者每CPU页框高速缓存。
_count:代表页框的引用计数,-1代表此页框空闲,大于0代表此页框分配给了多少个进程使用(共享)。
即flags存放页的状态,如该页是不是脏页。 _count域表示该页的使用计数,如果该页未被使用,就可以在新的分配中使用它。
5、 物理页面的数据结构以及获得页
为了对页面管理机制做出初步准备,Linux使用一种叫bootmem分配器的机制。这种机制仅仅用在系统引导时,为整个物理内存建立一个页面位图。建立这个位图的目的就是弄清楚哪一些物理页面是可以动态分配的,用来存放位图的数据结构为boot_data。
在路径/linux-2.6.21/include/asm-i386/page.h中,规定了页面大小:
其中PAGE_SHIFT指定了偏移量offset字段的位数为12,因此表示页面大小为4096个字节。(2^12=4096)
PAGE_SIZE为页的大小。
pte_t表示页表项,pmd_t表示页上级目录项,pgd_t表示页全局目录项,pgprot_t表示与一个单独表项相关的保护标志。
获得页使用的接口是alloc_pages函数,源码位于linux/gfp.h中。
该函数返回值是指向page结构体的指针,参数gfp_mask是一个标志,简单来讲就是获得页所使用的行为方式。order参数规定分配多少页面,该函数分配2的order次方个连续的物理页面。返回的指针指向的是第一page页面。
获得页的函数最终调用的都是alloc_pages函数。alloc_pages函数是获得页的核心函数。
6、 释放页
当我们不再需要某些页时可以使用下面的函数释放它们:
__free_pages(struct page *page, unsigned int order)
__free_page
free_pages
free_page(unsigned long addr, unsigned int order)
这些接口都在linux/gfp.h中
释放页的时候一定要小心谨慎,内核中操作不同于在用户态,若是将地址写错,或是顺序写错,那么都可能会导致系统的崩溃。
7、总结
在这里我主要是分析了Linux内核源码物理页面内存管理的部分源码。对于物理内存来说,系统都是以页框作为最小的分配单位,而分配时必定是要通过管理区分配器进行分配的,在管理区分配器中又必定是通过伙伴系统或每CPU页框分配器进行分配的。
五、 实验:Linux进程管理机制及算法分析
1、Linux调度程序—Schedule()概述
进程的合理调度是一个非常复杂的工作。它取决于可执行程序的类型,调度的策略,以及操作系统所追求的目标。进程运行需要各种各样的资源,系统资源调度的实质就是资源的分配。系统通过不同的调度算法来实现这样的资源分配。
Linux中主要的调度算法有:
(1) 时间片轮转算法
(2) 优先权调度算法
(3) 多级反馈队列调度
(4) 实时调度
Schedule()流程大致如下:
1、拿到当前cpu的队列rq
2、进行一些与操作处理
3、选取下一个进程
4、堆栈帧context_switch 切换
5、是否需要重新调度
2、进程调度的依据
调度程序运行时,要在所有处于可运行状态进程之中选择最值得运行的进程,投入运行选择的进程的依据在每个进程的task_struct结构中。Task_struct定义在linux-2.6.21/include/linux/sched.h文件,以下是结构体中部分重要的项。
(1) volatile long state 进程状态
(2) unsigned long flags 进程标识
(3) int sigpending 进程上是否有待处理的信号
(4) need_resched 在调度时机来临时,检测这个域的值,如果为1,调用schedule()
(5) counter:进程处于运行状态时所剩余的时钟滴答数,每次时钟中断来临时,这个值就减一。当这个域的值变得越来越小,直到0时,把need_resched置1。所以这个也叫做进程的动态优先级。
(6) nice:进程的静态优先级
(7) rt_priority:实时进程的优先级
(8) policy:区分实时进程和普通进程,实时进程应该优先于普通进程执行。
3、进程的状态
这一部分同样在内核源码的sched.h文件中。
TASK_RUNNING 表示进程正在执行或进程已进入运行队列等待被执行。
TASK_INTERRUPTIBLE 表示进程处于睡眠状态,等待事件被唤醒。
TASK_UNINTERRUPTIBLE 表示进程处于深度睡眠状态,等待事件被唤醒,除了发送信号及信号无法唤醒字状态的进程。
TASK_ZOMBIE 表示进程已死亡,但其进程task_struct结构(进程PCB)未被释放。
TASK_STOPPED 主要用于调试,进程收到一个SIGSTOP信号后就将运行状态改为TASK_STOPPED而进入挂起状态,然后在接收倒一个SIGCONT信号时又恢复运行。
4、进程调度算法
进程调度算法内核源码在linux-2.6.21/include/linux/sche.h文件中。
Linux在2.6版本中将公平的的调度概念引入了调度程序,代替之前的调度器,称为CFS算法(完全公平调度算法)。CFS的做法是:在所有可运行进程的总数上计算出一个进程应该运行的时间,nice值不再作为时间片分配的标准,而是用于处理计算获得的处理器使用权重。CFS中用于记录进程运行时间的数据结构为“调度实体”,这个结构体同样被定义在linux/sched.h。
SCHED_NORMAL用于普通进程的调度, SCHED_BATCH为SCHED_NORMAL的优化版本,采用分时策略,根据动态优先级分配CPU资源。两者都通过调度器CFS实现,均用来调度普通的非实时进程。这类进程比实时进程的优先级低,也就是说,在有实时进程存在时,实时进程优先调度。这种调度算法适合于成批处理的工作。
SCHED_FIFO和SCHED_RR分别为先进先出调度、时间片轮转调度算法,通过调度器RT实现,用来调度实时进程。SCHED_FIFO的调度策略是,先进入就绪队列的进程先执行,直到进程主动放弃CPU,才轮到下一个进程执行。SCHED_RR的调度策略是,预先确定时间片的大小,每个进程运行一段时间,时间片一用完,即产生中断,轮到下一个进程占用CPU。
定义了结构体sched_param,用来记录调度的优先级sched_priority。
六、 实验总结
Linux内核源码多而复杂,阅读起来有许多困难。现有的Linux版本大都在5.3.0以上,所以在本次实验中我选择了版本较低的Linux-2.6.21,虽然微小,但是麻雀虽小五脏俱全,足够体现Linux的整体思维。通过分析Linux内核源码,可以更深刻的理解了操作系统的原理。一般操作系统只讲述了操作系统的实现原理,很难接触到内核代码,而Linux系统平台容易建立内核源码容易获得,而且Linux的实现采用了大量的数据结构。
在阅读Linux源码的过程中,我增强了自己的动手能力以及代码的分析能力,同时对操作系统也有更加深入的理解。本次实验虽然只分析了Linux2.6.21系统的进程管理和内存管理机制的部分代码,研究工作的深入性和全面性仍有不足之处,但是相信经过学习能更好的理解Linux的内存管理机制以及进程管理机制。