分析基于内核版本2.6.12.6

 

在什么情况下,会触发调度?

Linux进程的调度主要分为主动调度和被动调度两大类。

◆主动调度

主动调度就是进程自己缺少相应的所申请的资源,显示调用schedule,让出处理器。

◆被动调度

在整个linux运行过程中,被动调度又可细分为两种:

●用户态抢占调度

●内核态抢占调度

 

下面就结合内核代码分析上述各种调度时机的详细情况。

被动调度

整个linux运行过程中,被动调度分为用户态抢占调度和内核态抢占调度。

用户态抢占调度

用户态抢占调度发生在当系统调用、中断处理、异常处理等返回用户态时,或者进程的时间片用完时。

系统调用返回

当一个进程由于系统调用进入内核态,在系统调用处理完,返回用户态时,是一个调度点。这个时候会检测有没有设置TIF_NEED_RESCHED标志。下面分析下内核中系统调用返回相关的代码片段。

 

ENTRY(system_call /*系统调用入口*/

 CFI_STARTPROC     /*用在每个函数的开始用于初始化一些内部数据结构*/

 swapgs             /*当处理器进入或者离开内核时,会使用swapg命令在gs寄存器的内核与用户值之间切换*/

 movq %rsp,%gs:pda_oldrsp  /*将用户态栈指针压入gs寄存器指向的x8664_pda结构体的oldrsp字段*/

 movq %gs:pda_kernelstack,%rsp  /*将内核栈帧赋给rsp*/

 sti      /*开中断*/ 

 SAVE_ARGS 8,1    /*将一些寄存器压栈*/

 movq  %rax ,ORIG_RAX-ARGOFFSET(%rsp  /*120-48=72   8*9(%rsp)*/

 movq  %rcx ,RIP-ARGOFFSET(%rsp)         /*128-48=80  8*10(%rsp)*/

 GET_THREAD_INFO(%rcx            /*获取当前进程的thread_info结构的地址*/

 testl $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SECCOMP),threadinfo_flags(%rcx 

 jnz tracesys  

 cmpq $__NR_syscall_max ,%rax    /*判断系统调用号是否大于最大值*/

ja badsys     

 movq %r10 ,%rcx  

call *sys_call_table(,%rax,8)  # XXX:  rip relative  /*rax为系统调用号*/

 movq %rax ,RAX-ARGOFFSET(%rsp 

 

上面代码片段是系统调用的入口。这个系统调用的入口是在syscall_init函数里调用wrmsrl(MSR_LSTAR, system_call)设置的,通过一个MSR寄存器来保存系统调用的地址,而不再像I386架构下面是通过中断。

用户进程和内核都使用gs段寄存器访问状态数据。用户进程使用这个寄存器保存每个线程的数据,内核使用这个寄存器管理每个处理器的数据。当处理器进入或者离开内核时,都会使用swapgs命令在gs寄存器的内核和用户态值之间切换。

 

/* Per processor datastructure. %gs points to it while the kernel runs */  

struct x8664_pda  

 struct task_struct *pcurrent;  /* Current process */  

 unsigned long data_offset;  /* Per cpu data offset from linker address */  

 struct x8664_pda *me;       /* Pointer to itself */   

 unsigned long kernelstack;  /* top of kernel stack for current */  

 unsigned long oldrsp;       /* user rsp for system call */  

 unsigned long irqrsp;     /* Old rsp for interrupts. */  

         int irqcount;           /* Irq nesting counter. Starts with -1 */       

 int cpunumber;          /* Logical CPU number */  

 char *irqstackptr;     /* top of irqstack */  

 unsigned int __softirq_pending 

 unsigned int __nmi_count  /* number of NMI on this CPUs */  

 struct mm_struct *active_mm 

 int mmu_state;      

 unsigned apic_timer_irqs 

____cacheline_aligned 

 

x8664_pda数据结构的kernelstack字段指向当前cpu的内核栈顶,oldrsp字段存储的是由系统调用进入内核态的用户进程的栈帧rsp。在进入系统调用入口后,就会保存用户进程的栈帧,并恢复当前cpu的栈帧到rsp

 

SAVE_ARGS宏主要是将一些寄存器压栈。

下面接着分析系统调用代码:

 

.globl ret_from_sys_call  

ret_from_sys_call:  

 movl $_TIF_ALLWORK_MASK,%edi  

 /*edi: flagmask */  

sysret_check:    

 GET_THREAD_INFO(%rcx 

 cli     /*关中断*/

 movl threadinfo_flags(%rcx),%edx  

 andl %edi,%edx          /*检测是否还有其它工作需要完成*/

jnz  sysret_careful    /*有工作完成则跳转到sysret_careful */

 movq RIP-ARGOFFSET(%rsp),%rcx  

 RESTORE_ARGS 0,-ARG_SKIP,1  

 movq %gs:pda_oldrsp,%rsp  

 swapgs  

 sysretq  

 

 /* Handle reschedules */  

 /*edx: workedi: workmask */   

sysret_careful:  

 bt $TIF_NEED_RESCHED,%edx     /*检测是否有设置TIF_NEED_RESCHED 标志*/

 jnc sysret_signal              /*没有设置TIF_NEED_RESCHED标志,跳转到sysret_signal*/

 sti  

 pushq %rdi  

 call schedule     

 popq  %rdi  

 jmp sysret_check  

 

上面代码是系统调用后的处理部分。在处理完系统调用返回用户态前,首先检查是否还有其他工作需要完成,如果有其他工作,则跳转到sysret_careful标签处执行。如果没有其他工作,则将之前压栈的寄存器出栈,调用swapgs命令,切换gs寄存器的内核和用户态值,最后调用sysretq指令退出系统调用。

 

sysret_careful开始就检测是否有设置TIF_NEED_RESCHED标志,如果没有设置此标志,则跳转到sysret_signal标签处执行。如果有设置此标志,则首先开中断,然后调用schedule函数。

 

下面接着分析sysret_signal标签处的代码:

 

sysret_signal:

sti       /*开中断*/

testl $(_TIF_SIGPENDING|_TIF_NOTIFY_RESUME|_TIF_SINGLESTEP),%edx  /*检测是否有信号需要处理*/

jz    1f

 

/* Really a signal */

/* edx:work flags (arg3) */

leaq do_notify_resume(%rip),%rax               /*do_notify_resume函数的地址赋给rax寄存器*/

leaq -ARGOFFSET(%rsp),%rdi # &pt_regs -> arg1   /* do_notify_resume函数参数1*/

xorl %esi,%esi # oldset -> arg2                 /* do_notify_resume函数参数2*/

call ptregscall_common

1:movl $_TIF_NEED_RESCHED,%edi

jmp sysret_check

 

上面代码片段是处理信号部分代码。首先检测是否有信号需要处理,有信号处理的情况下,将do_notify_resume函数的地址赋给rax寄存器,然后准备好do_notify_resume函数需要使用的3个参数,通过寄存器rdi,esiedx三个寄存器传递。然后跳转到ptregscall_common函数处执行。在ptregscall_common中会调用call *%rax,进入do_notify_resume函数执行。

 

下面是上面分析的系统调用相关代码的流程图。

 

-systemcall流程图

 

 

-ret_from_sys_call流程图

 

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐