LINUX进程调度机制及其堆栈切换分析.docx

上传人:b****3 文档编号:2147744 上传时间:2022-10-27 格式:DOCX 页数:23 大小:30.84KB
下载 相关 举报
LINUX进程调度机制及其堆栈切换分析.docx_第1页
第1页 / 共23页
LINUX进程调度机制及其堆栈切换分析.docx_第2页
第2页 / 共23页
LINUX进程调度机制及其堆栈切换分析.docx_第3页
第3页 / 共23页
LINUX进程调度机制及其堆栈切换分析.docx_第4页
第4页 / 共23页
LINUX进程调度机制及其堆栈切换分析.docx_第5页
第5页 / 共23页
点击查看更多>>
下载资源
资源描述

LINUX进程调度机制及其堆栈切换分析.docx

《LINUX进程调度机制及其堆栈切换分析.docx》由会员分享,可在线阅读,更多相关《LINUX进程调度机制及其堆栈切换分析.docx(23页珍藏版)》请在冰豆网上搜索。

LINUX进程调度机制及其堆栈切换分析.docx

LINUX进程调度机制及其堆栈切换分析

LINUX2.4.18进程调度机制及其堆栈切换分析

张华强

2004-08

1LINUX调度机制概述

LINUX的进程调度机制的设计主要是从下面三个方面来考虑的。

调度的时机,LINUX的进程调度分为主动调度和被动的调度。

其中主动的调度在内核空间通过调用schedule()函数来启动一次调度。

在用户空间则是通过调用延时、睡眠、退出等主动放弃运行权的系统调用接口进入内核空间并最终通过调用schedule()函数来是启动一次调度。

而被动的调度则发生每次处理中断或异常后从系统空间切换到用户空间的前夕,或者经过其他的系统调用返回到用户空间的前夕。

调度的策略,为了适应各种不同的应用,LINUX在从UNIX上继承下来的基于优先级的调度策略的基础上实现了三种调度策略:

SCHED_FIFO、SCHED_RR和SCHED_OTHER。

其中的SCHE_FIFO和SCHED_RR都是实时调度的策略,他们在系统中被赋予了至少1000的优先权值。

二者不同的地方在于对待同等优先权值的进程的处理方式上的差别。

当系统中有多个同等优先级的SCHED_FIFO进程就绪时,按照先入先出的原则只有当前进程运行到自动放弃CPU的权限时才会让同等优先级的另一个进程调度起来运行。

而对于SCHED_RR的进程,则会在每次时钟中断到来时检查先前进程的时间片是否用完,如果用完则剥夺当前进程的CPU使用权调度出同等优先级的另一个SCHED_RR进程运行。

SCHED_OTHER则为传统的调度策略,主要用于交互式应用。

调度的方式:

LINUX内核的调度采用有条件的可剥夺方式,当进程在用户空间运行时,一旦有必要,例如发生了外部中断或者进程的时间片到时,内核都可以剥夺当前进程的运行权而调度其他的进程运行,因而说这种调度方式是可剥夺的,但当进入内核态时,即使内核知道应该调度了,但这种调度并不会立即发生,只有当前进程回到用户空间的前夕这种调度才会发生。

根据上面的分析可以得出以下几点结论:

1、LINUX的进程调度与中断是密不可分的(即使是系统调用也是通过软件中断INT0x80来实现的)。

2、由于不论是主动的调度还是被动的调度最终都是通过在内核空间中调用schedule函数来实现的,因此LINUX的进程切换一定是在系统空间中完成的,在下面的分析中还可以看到这个过程实际上就是在schedule函数中完成的。

2LINUX2.4.18调度时的堆栈切换分析

基于以上两点我们假设系统中目前有两个进程,进程1和进程2,进程1正在运行。

下面我们就分别分析在被动调度(中断发生了)和主动调度(调用nanosleep)时系统的堆栈切换情况和进程的调度实现。

2.1被动调度时的堆栈切换分析

首先我们分析被动调度的情况。

假设进程1正在用户空间运行的过程中来了一个外设的中断请求。

根据CPU对中断的响应规则,CPU会在进程的当前指令执行完毕后相应中断。

CPU响应中断的过程如下描述。

由于在LINUX中只使用了CPU的0和3这两个运行级别,因此在以下的讨论中涉及到CPU的运行级别时只考虑这两个运行级。

首先CPU从中断控制器中取得中断向量,然后根据该向量重IDT中取出一个初始化好的中断门并试图穿过该中断门进入相应得中断服务程序中。

在穿过中断门的过程中,根据INTELIA-32体系结构中设计的硬件机制,CPU将作如下工作:

首先CPU将其当前运行级别与中断门中要求的运行级别进行比较,如果发现CPU的当前运行级别低于中断门的描述符运行级别(即CPL=3,中断门的DPL=0)时,CPU首先会将由TR寄存器选择的TSS段中的SS0和ESP0(运行级别0的堆栈)装入CPU的SS和ESP中,从后面的分析可知,SS0和ESP0一定对应的是当前运行任务的系统堆栈指针。

通过这样就完成了用户空间堆栈到系统空间堆栈的切换。

堆栈切换完成之后,CPU紧接着就将当前进程的用户空间堆栈的SS和ESP值压入新的堆栈中,然后在将当前的EFLAGS值和CS、EIP值,这些就对应当前任务被中断时的CPU标志寄存器值和中断处理完成后返回当前进程的地址。

如果发现CPU的当前运行级和描述符的运行级一致则不进行堆栈切换而直接将EFLAGS、CS、EIP的值压入堆栈中。

不管上述哪种情况,穿过中断门后的堆栈都会是当前任务的系统空间堆栈。

由于中断发生在用户空间,因此中断发生时的CPL=3,因而必然会发生堆栈切换。

因此进入中断服务入口处时系统堆栈情况如图1所示。

返回进程1被中断处的指令地址

用户空间

堆栈指针

用户空间SS

用户空间ESP

EFLAGS

用户空间CS

用户空间EIP

系统空间堆栈当前ESP

 

图1进入中断服务程序入口处的系统堆栈

从后面的分析可知由于CPU每次使用内核堆栈时对其所作的操作都是均衡的,因此每次从用户空间堆栈切换到系统空间堆栈时,该堆栈都是空的。

穿过中断门后系统进入中断服务程序的入口。

在LINUX系统中所有中断服务程序的总入口都是由gcc预生成的,具有如下形式:

asmlinkagevoidIRQ0xYY_interrupt();

__asm__(

"\n"

“IRQ_0xYY_interrupt:

\n\t"

"pushl$"#nr"-256\n\t"\

"jmpcommon_interrupt");

上述代码中的YY代表外部中断0-254的16进制值(其中系统调用0x80除外,它是由系统另外单独初始化的)。

根据上述代码可以看出所有的外部中断都是统一进入common_interrup的地方进行处理的。

同样该段由gcc预处理出来的代码如下:

asmlinkagevoidcall_do_IRQ(void);

__asm__(

"\n"__ALIGN_STR"\n"

"common_interrupt:

\n\t"

SAVE_ALL

"call_do_IRQ:

\n\t"

"calldo_IRQ"\n\t"

"jmpret_from_intr\n");

上述代码的主要作用就是通过SAVE_ALL宏将CPU的当前寄存器内容入栈。

SAVE_ALL宏的定义如下:

#defineSAVE_ALL\

"cld\n\t"\

"pushl%es\n\t"\

"pushl%ds\n\t"\

"pushl%eax\n\t"\

"pushl%ebp\n\t"\

"pushl%edi\n\t"\

"pushl%esi\n\t"\

"pushl%edx\n\t"\

"pushl%ecx\n\t"\

"pushl%ebx\n\t"\

"movl$"STR(__KERNEL_DS)",%edx\n\t"\

"movl%edx,%ds\n\t"\

"movl%edx,%es\n\t"

保护完CPU的寄存器,就通过一条CALL指令调用do_IRQ进入真正的中断服务程序中。

这样CPU在进入中断服务程序do_IRQ时系统的堆栈情况如图2所示:

从do_IRQ()返回到

common_interrupt的

jmpret_from_intr处的地址

orig_eax

返回进程1被中断处的指令地址

用户空间

堆栈指针

用户空间SS

用户空间ESP

EFLAGS

用户空间CS

用户空间EIP

系统空间

堆栈当前ESP

中断号-256

中断前的CPU寄存器ES-EBX

系统空间EIP

 

图2进入中断服务程序do_IR时的中断

从图2可知,当系统处理完中断从do_IRQ()函数返回时将跳转到ret_from_intr处开始执行代码。

(由于系统设计使得在中断服务过程中不会调用schedule()函数产生任务切换。

因而中断服务结束后一定会返回到图2中的返回点)。

 

orig_eax

返回进程1被中断处的指令地址

用户空间

堆栈指针

用户空间SS

用户空间ESP

EFLAGS

用户空间CS

用户空间EIP

系统空间

堆栈当前ESP

中断号-256

中断前的CPU寄存器ES-EBX

 

图3中断返回后的堆栈

其中的ret_from_intr处的代码如下:

ret_from_intr:

GET_CURRENT(%ebx)

ret_from_exception:

movlEFLAGS(%esp),%eax#mixEFLAGSandCS

movbCS(%esp),%al

testl$(VM_MASK|3),%eax#returntoVM86modeornon-supervisor?

jneret_from_sys_call

jmprestore_all

在上述代码中,系统首先取出当前进程(进程1)的task_struct结构指针放入ebx寄存器中。

然后在判断中断是发生在用户空间还是系统空间,如果发生在系统空间,则直接跳转到restore_all处中断返回。

由于本例中中断发生在进程1的用户空间中,因而程序跳转到ret_from_syscall处执行,该段代码如下:

ret_from_sys_call:

cli#need_reschedandsignalsatomictest

cmpl$0,need_resched(%ebx)

jnereschedule

cmpl$0,sigpending(%ebx)

jnesignal_return

restore_all:

RESTORE_ALL

到达ret_from_syscall后,系统将判断当前进程(进程1)的need_resched字段值是否为零,如果非零则调用schedule()函数进行任务切换。

在这里我们假设在中断服务程序中唤醒了进程2,则从中断服务程序do_IRQ返回时进程1的need_resched字段值即为1。

因而系统将跳转到reschedule处运行,该段代码如下:

reschedule:

callSYMBOL_NAME(schedule)#test

jmpret_from_sys_call

由此可见系统在此处将直接调用schedule()函数进行进程切换。

Schedule()函数的实现入下:

asmlinkagevoidschedule(void)

{

structschedule_data*sched_data;

structtask_struct*prev,*next,*p;

structlist_head*tmp;

intthis_cpu,c;

if(!

current->active_mm)BUG();

need_resched_back:

prev=current;

this_cpu=prev->processor;

if(unlikely(in_interrupt())){

printk("Schedulingininterrupt\n");

BUG();

}

sched_data=&aligned_data[this_cpu].schedule_data;

if(unlikely(prev->policy==SCHED_RR))

if(!

prev->counter){

prev->counter=NICE_TO_TICKS(prev->nice);

move_last_runqueue(prev);

}

switch(prev->state){

caseTASK_INTERRUPTIBLE:

if(signal_pend

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 农林牧渔 > 林学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1