linux课程设计.docx
- 文档编号:25560457
- 上传时间:2023-06-09
- 格式:DOCX
- 页数:13
- 大小:43.79KB
linux课程设计.docx
《linux课程设计.docx》由会员分享,可在线阅读,更多相关《linux课程设计.docx(13页珍藏版)》请在冰豆网上搜索。
linux课程设计
Linux操作系统进程调度源码分析报告
学院计算机工程学院
专业计算机科学与技术
年级班别09计算机科学与技术2班
学号2009404010219
学生姓名
指导教师李永
2012年06月11日
前言
Linux的问世和开源码的流行,很多大型公司企业都开始使用Linux系统,这个时代不再是微软的独霸天下。
Linux的兴起正是他的源码的开放性,使人们可以更深入的了解操作系统。
Internet的普及使热心于Linux的开发者们能进行高效、快捷的交流,从而为Linux创造了一个优良的分布式开发环境。
Linux具有很强的适应性,能适应各种不同的硬件平台。
Linux的版本更新很快。
在短短的七年时间里,其版本已升至2.1.x。
这里之所以用"x"表示,是因为x的值变化太快,很难准确地定位它的值。
这也从侧面反映了从事Linux的研究者之多。
不过,Linux用得最多的版本还是2.0.30,许多商品化的操作系统都以它为核心
Linux的最重要特征之一就是支持多种文件系统。
支持进程的并发与并行。
进程的创建是一个十分复杂的过程,通常的做法需为子进程重新分配物理空间,并把父进程空间的内容全盘复制到子进程空间中,其开销非常大。
为了降低进程创建的开销,Linux采用了Copyonwrite技术,即不拷贝父进程的空间,而是拷贝父进程的页表,使父进程和子进程共享物理空间,并将这个共享空间的访问权限置为只读。
当父进程和子进程的某一方进行写操作时,Linux检测到一个非法操作,这时才将要写的页进行复制。
这一做法免除了只读页的复制,从而降低了开销。
Linux目前尚未提供用户级线程,但提供了核心级线程,核心线程的创建是在进程创建的基础上稍做修改,使创建的子进程与父进程共享虚存空间。
从这一意义上讲,核心线程更像一个共享进程组。
本文将重点解释Linux操作系统进程的调度,并做出相应的结构逻辑图,其中选择了goodness(),SMP,等程序代码段去进行研究,方便了我们对Linux进程整体理解。
目录
1设计与需求1
1.1设计内容1
1.2设计目的与要求1
1.2.1设计目的1
1.2.2设计要求2
1.3设计环境、原理与说明2
1.3.1设计环境2
1.3.2设计原理与说明2
2程序代码与程序分析4
2.1时钟中断4
2.2进程可运行程度的衡量(goodness())5
2.3进程调度的实现7
3总结11
1设计与需求
1.1设计内容
本次课程设计,主要分析了进程调度的基本原理,以及基本的调度算法。
进程运行需要各种各样的系统资源,如内存、文件、打印机和最宝贵的CPU等等,所以说呢,调度的实质就是资源的分配。
系统通过不同的调度算法(SchedulingAlgorithm)来实现这种资源的分配。
通常来说,选择什么样的调度算法取决于的资源分配的策略(SchedulingPolicy),我们不准备在这里详细说明各种调度算法,只说明与Linux调度相关的几种算法及这些算法的原理。
进程调度有四种调度:
1时间片(TimeSlice)就是分配给进程运行的一段时间。
2.优先权调度算法为了照顾到紧迫型进程在进入系统后便能获得优先处理,引入了最高优先权调度算法。
当将该算法用于进程调度时,系统将把处理机分配给运行队列中优先权最高的进程。
3.多级反馈队列调度其本质是:
综合了时间片轮转调度和抢占式优先权调度的优点,即:
优先权高的进程先运行给定的时间片,相同优先权的进程轮流运行给定的时间片。
4.实时调度系统对外部事件有求必应、尽快响应。
在实时系统中存在有若干个实时进程或任务,它们用来反应或控制某个(些)外部事件,往往带有某种程度的紧迫性,因而对实时系统中的进程调度有某些特殊要求。
设计主要是对Linux源代码进行分析,画出自己所分析的源代码的结构图、程序流程图,阐述自己所分析的源代码的操作系统的原理,并对所有程序写出注释性文档,最后整理形成源码分析报告。
1.2设计目的与要求
1.2.1设计目的
熟悉Linux3.2操作系统原理,熟悉C语言代码的精华,提高个人编程能力并学习补充C语言和操作系统的代码分析。
学习常见函数的设计方法,学习进程调度和进程管理等编程技。
理解Linux3.2内核部分代码的设计思路和操作系统原。
为以后的工作提前做好实践工作。
增加。
1.2.2设计要求
对Linux内核3.2.8版本的源代码中的某个文件夹下的源文件进行分析。
画出分析的源代码的结构图、程序流程图,阐述自己所分析的源代码的操作系统的原理,并对所有程序写出注释性文档,最后整理形成源码分析报告。
这次设计主要要求可以从中学到很多的计算机的底层知识,如后面将讲到的系统的引导和硬件提供的中断机制等;其它,象虚拟存储的实现机制,多任务机制,系统保护机制等等,这些都是非都源码不能体会的。
1.3设计环境、原理与说明
1.3.1设计环境
Linux是基于标准的操作系统,符合POSIX标准,能够向其他的商业性UNIX系统一样直接连接软件。
与大部分其他的UNIX不同的,Linux在设备上运行时,可以用于移动设备才使用的gumstix平台,也可以用在IBMz系列主机,甚至排名前500的超机计算机上运行。
这次设计选用Windows操作系统下的VC++6.0、文本编辑器等,分析Linux内核代码。
1.3.2设计原理与说明
调度程序运行时,要在所有可运行状态的进程中选择最值得运行的进程投入运行。
选择进程的依据是什么呢?
在每个进程的task_strUCt结构中有以下四项:
policy、priority、counter、rt_priority。
这四项是选择进程的依据。
其中,policy是进程的调度策略,用来区分实时进程和普通进程,实时进程优先于普通进程运行;priority是进程(包括实时和普通)的静态优先级;counter是进程剩余的时间片,它的起始值就是priority的值;由于counter在后面计算一个处于可运行状态的进程值得运行的程度goodness时起重要作用,因此,counter也可以看作是进程的动态优先级。
rt_priority是实时进程特有的,用于实时进程间的选择。
Linux用函数goodness()来衡量一个处于可运行状态的进程值得运行的程度。
该函数综合了以上提到的四项,还结合了一些其他的因素,给每个处于可运行状态的进程赋予一个权值(weight),调度程序以这个权值作为选择进程的唯一依据。
Linux根据policy从整体上区分实时进程和普通进程,因为实时进程和普通进程度调度是不同的,它们两者之间,实时进程应该先于普通进程而运行,然后,对于同一类型的不同进程,采用不同的标准来选择进程。
对于普通进程,Linux采用动态优先调度,选择进程的依据就是进程counter的大小。
进程创建时,优先级priority被赋一个初值,一般为0~70之间的数字,这个数字同时也是计数器counter的初值,就是说进程创建时两者是相等的。
对于实时进程,Linux采用了两种调度策略,即FIFO(先来先服务调度)和RR(时间片轮转调度)。
因为实时进程具有一定程度的紧迫性,所以衡量一个实时进程是否应该运行,Linux采用了一个比较固定的标准。
实时进程的counter只是用来表示该进程的剩余时间片,并不作为衡量它是否值得运行的标准。
实时进程的counter只是用来表示该进程的剩余时间片,并不作为衡量它是否值得运行的标准,这和普通进程是有区别的。
上面已经看到,每个进程有两个优先级,实时优先级就是用来衡量实时进程是否值得运行的。
2程序代码与程序分析
2.1时钟中断
在时钟中断返回时,要调用函数ret_from_sys_call(),前面我们已经讨论过这个函数,在这个函数中有如下几行:
ret_from_sys_call:
movl_current,%eax
cmpl_task,%eax#task[0]cannothavesignals
je3f
cmpw$0x0f,CS(%esp)#wasoldcodesegmentsupervisor?
jne3f
cmpw$0x17,OLDSS(%esp)#wasstacksegment=0x17?
jne3f
movlsignal(%eax),%ebx
movlblocked(%eax),%ecx
notl%ecx
andl%ebx,%ecx
bsfl%ecx,%ecx
je3f
btrl%ecx,%ebx
movl%ebx,signal(%eax)
incl%ecx
pushl%ecx
call_do_signal
popl%ecx
testl%eax,%eax
jne2b#seeifweneedtoswitchtasks,ordomoresignals
3:
popl%eax
popl%ebx
popl%ecx
popl%edx
addl$4,%esp#skiporig_eax
pop%fs
pop%es
pop%ds
iret
这几行的意思很明显:
检测need_resched标志,如果此标志为非0,那么就转到reschedule处调用调度程序schedule()进行进程的选择。
调度程序schedule()会根据具体的标准在运行队列中选择下一个应该运行的进程。
当从调度程序返回时,如果发现又有调度标志被设置,则又调用调度程序,直到调度标志为0,这时,从调度程序返回时由RESTORE_ALL恢复被选定进程的环境,返回到被选定进程的用户空间,使之得到运行。
2.2进程可运行程度的衡量(goodness())
函数goodness()就是用来衡量一个处于可运行状态的进程值得运行的程度。
该函数综合使用了上面我们提到的五项,给每个处于可运行状态的进程赋予一个权值(weight),调度程序以这个权值作为选择进程的唯一依据。
函数主体如下(为了便于理解,笔者对函数做了一些改写和简化,只考虑单处理机的情况):
staticinlineintgoodness(structtask_struct*p,structmm_struct*this_mm)
{intweight;/*权值,作为衡量进程是否运行的唯一依据*
weight=-1;
if(p->policy&SCHED_YIELD)
gotoout;/*如果该进程愿意“礼让(yield)”,则让其权值为-1*/
switch(p->policy)
{
/*实时进程*/
caseSCHED_FIFO:
caseSCHED_RR:
weight=1000+p->rt_priority;
/*普通进程*/
caseSCHED_OTHER:
{weight=p->counter;
if(!
weight)
gotoout
/*做细微的调整*/
if(p->mm=this_mm||!
p->mm)
weight=weight+1;
weight+=20-p->nice;
}
}
out:
returnweight;/*返回权值*/
}
其中,在sched.h中对调度策略定义如下:
#defineSCHED_OTHER0
#defineSCHED_FIFO1
#defineSCHED_RR2
#defineSCHED_YIELD0x10
这个函数比较很简单。
首先,根据policy区分实时进程和普通进程。
实时进程的权值取决于其实时优先级,其至少是1000,与conter和nice无关。
普通进程的权值需特别说明两点:
(1)为什么进行细微的调整?
如果p->mm为空,则意味着该进程无用户空间(例如内核线程),则无需切换到用户空间。
如果p->mm=this_mm,则说明该进程的用户空间就是当前进程的用户空间,该进程完全有可能再次得到运行。
对于以上两种情况,都给其权值加1,算是对它们小小的奖励。
(2)进程的优先级nice是从早期Unix沿用下来的负向优先级,其数值标志“谦让”的程度,其值越大,就表示其越“谦让”,也就是优先级越低,其取值范围为-20~+19,因此,(20-p->nice)的取值范围就是0~40。
可以看出,普通进程的权值不仅考虑了其剩余的时间片,还考虑了其优先级,优先级越高,其权值越大。
有了衡量进程是否应该运行的标准,选择进程就是轻而易举的事情了,弱肉强食,谁的权值大谁就先运行。
根据进程调度的依据,调度程序就可以控制系统中的所有处于可运行状态的进程并在它们之间进行选择。
2.3进程调度的实现
调度程序在内核中就是一个函数,为了讨论方便,我们同样对其进行了简化,略其对SMP的实现部分。
asmlinkagevoidschedule(void)
{
structtask_struct*prev,*next,*p;/*prev表示调度之前的进程,
next表示调度之后的进程*/
structlist_head*tmp;
intthis_cpu,c;
if(!
current->active_mm)BUG();/*如果当前进程的的active_mm为空,出错*/
need_resched_back:
prev=current;/*让prev成为当前进程*/
this_cpu=prev->processor;
if(in_interrupt()){/*如果schedule是在中断服务程序内部执行,
就说明发生了错误*/
printk("Schedulingininterrupt\n");
BUG();
}
release_kernel_lock(prev,this_cpu);/*释放全局内核锁,
并开this_cpu的中断*/
spin_lock_irq(&runqueue_lock);/*锁住运行队列,并且同时关中断*/
if(prev->policy==SCHED_RR)/*将一个时间片用完的SCHED_RR实时
gotomove_rr_last;进程放到队列的末尾*/
move_rr_back:
switch(prev->state){/*根据prev的状态做相应的处理*/
caseTASK_INTERRUPTIBLE:
/*此状态表明该进程可以被信号中断*/
if(signal_pending(prev)){/*如果该进程有未处理的
信号,则让其变为可运行状态*/
prev->state=TASK_RUNNING;
break;
}
default:
/*如果为可中断的等待状态或僵死状态*/
del_from_runqueue(prev);/*从运行队列中删除*/
caseTASK_RUNNING:
;/*如果为可运行状态,继续处理*/
}
prev->need_resched=0;
/*下面是调度程序的正文*/
repeat_schedule:
/*真正开始选择值得运行的进程*/
next=idle_task(this_cpu);/*缺省选择空闲进程*/
c=-1000;
if(prev->state==TASK_RUNNING)
gotostill_running;
still_running_back:
list_for_each(tmp,&runqueue_head){/*遍历运行队列*/
p=list_entry(tmp,structtask_struct,run_list);
if(can_schedule(p,this_cpu)){/*单CPU中,该函数总返回1*/intweight=goodness(p,this_cpu,prev->active_mm);
if(weight>c)
c=weight,next=p;
}
}
/*如果c为0,说明运行队列中所有进程的权值都为0,也就是分配给各个进程的
时间片都已用完,需重新计算各个进程的时间片*/
if(!
c){
structtask_struct*p;
spin_unlock_irq(&runqueue_lock);/*锁住运行队列*/
read_lock(&tasklist_lock);/*锁住进程的双向链表*/
for_each_task(p)/*对系统中的每个进程*/
p->counter=(p->counter>>1)+NICE_TO_TICKS(p->nice);
read_unlock(&tasklist_lock);
spin_lock_irq(&runqueue_lock);
gotorepeat_schedule;
}
spin_unlock_irq(&runqueue_lock);/*对运行队列解锁,并开中断*/
if(prev==next){/*如果选中的进程就是原来的进程*/
prev->policy&=~SCHED_YIELD;
gotosame_process;
}
/*下面开始进行进程切换*/
kstat.context_swtch++;/*统计上下文切换的次数*/
{
structmm_struct*mm=next->mm;
structmm_struct*oldmm=prev->active_mm;
if(!
mm){/*如果是内核线程,则借用prev的地址空间*/
if(next->active_mm)BUG();
next->active_mm=oldmm;
}else{/*如果是一般进程,则切换到next的用户空间*/
if(next->active_mm!
=mm)BUG();
switch_mm(oldmm,mm,next,this_cpu);
}
if(!
prev->mm){/*如果切换出去的是内核线程*/
prev->active_mm=NULL;/*归还它所借用的地址空间*/
mmdrop(oldmm);/*mm_struct中的共享计数减1*/
}
}
switch_to(prev,next,prev);/*进程的真正切换,即堆栈的切换*/
__schedule_tail(prev);/*置prev->policy的SCHED_YIELD为0*/
same_process:
reacquire_kernel_lock(current);/*针对SMP*/
if(current->need_resched)/*如果调度标志被置位*/
gotoneed_resched_back;/*重新开始调度*/
return;
}
以上就是调度程序的主要内容,为了对该程序形成一个清晰的思路,我们对其再给出进一的解释:
·如果当前进程既没有自己的地址空间,也没有向别的进程借用地址空间,那肯定出错。
另外,如果schedule()在中断服务程序内部执行,那也出错.
·对当前进程做相关处理,为选择下一个进程做好准备。
当前进程就是正在运行着的进程,可是,当进入schedule()时,其状态却不一定是TASK_RUNNIG,例如,在exit()系统调用中,当前进程的状态可能已被改为TASK_ZOMBE;又例如,在wait4()系统调用中,当前进程的状态可能被置为TASK_INTERRUPTIBLE。
因此,如果当前进程处于这些状态中的一种,就要把它从运行队列中删除。
·从运行队列中选择最值得运行的进程,也就是权值最大的进程。
·如果已经选择的进程其权值为0,说明运行队列中所有进程的时间片都用完了(队列中肯定没有实时进程,因为其最小权值为1000),因此,重新计算所有进程的时间片,其中宏操作NICE_TO_TICKS就是把优先级nice转换为时钟滴答。
·进程地址空间的切换。
如果新进程有自己的用户空间,也就是说,如果next->mm与next->active_mm相同,那么,switch_mm()函数就把该进程从内核空间切换到用户空间,也就是加载next的页目录。
如果新进程无用户空间(next->mm为空),也就是说,如果它是一个内核线程,那它就要在内核空间运行,因此,需要借用前一个进程(prev)的地址空间,因为所有进程的内核空间都是共享的,因此,这种借用是有效的。
3总结
其实操作系统我们早在大三上学期就学了,只不过当时对编程语言还不熟加上这门课本身就比较枯燥无味,理论性很强,当时学的时候就没怎么认真的学。
总觉得学的他和我们用的操作系统没什么多大的关系,进程呀,线程呀,只是在概念上似乎明白是这回事。
但真正的来解析它,却怎么也摸不着头老,不知道从什么地方下手。
这个学期学习了linux,这才进一步的了解了操作系统。
Linux是开源的系统这就为我们学习带来了方便,我们可以更深层次的来了研究一下操作系统的源码,了解进程到底是怎么在内存中运行的,cpu又是怎么来设计的。
现在才发觉什么东西如果只是看看,永远也学不好,只有自己不断的去研究才能挖掘其中的知识。
正好古人说的那样:
纸上得来终觉浅,觉知此事要躬行。
分析源码也许是痛苦的但从中获得的成就感好是无与伦比的。
在这里必须感谢李老师一学期的教导。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- linux 课程设计