调度时机分析之被动调度(之系统调用返回)

分析基于内核版本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: work, edi: 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,esi和edx三个寄存器传递。然后跳转到ptregscall_common函数处执行。在ptregscall_common中会调用call *%rax,进入do_notify_resume函数执行。

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

图-systemcall流程图

图-ret_from_sys_call流程图

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-11 07:51:13

调度时机分析之被动调度(之系统调用返回)的相关文章

第一次作业:Linux 2.6.32的进程模型与调度器分析

1.前言 本文分析的是Linux 2.6.32版的进程模型以及调度器分析.在线查看 源码下载 本文主要讨论以下几个问题: 什么是进程?进程是如何产生的?进程都有那些? 在操作系统中,进程是如何被管理以及它们是怎样被调用的? 2.进程模型 2.1进程的概念 在我的理解中,一个程序就相当于一个进程,程序的启动意味着产生了一个新的进程,程序的关闭也就意味着一个进程的消亡. 那么专业定义应该是: 在计算中,进程是正在执行的计算机程序的一个实例. 它包含程序代码及其当前活动. 根据操作系统(OS),一个进

Hadoop 三大调度器分析

如要转载,请注上作者和出处. 须知: 我们下载的是hadoop-2.7.3-src 源码. 这个版本默认调度器是Capacity调度器. 在2.0.2-alpha版本的时候,有人汇报了一个fifo调度器的bug,社区把默认调度器从原来的fifo切换成capacity了. 在Hadoop中,调度器是一个可插拔的模块,用户可以根据自己的实际应用要求设计调度器,然后在配置文件中指定相应的调度器,这样,当Hadoop集群启动时,便会加载该调度器.当前Hadoop自带了几种调度器,分别是FIFO(默认调度

linux内核情景分析之强制性调度

从系统调用返回到用户空间是否调度,从ret_with_reschedule可看出,是否真正调度,取决于当前进程的pcb中的need_resched是否设置为1,那如何设置为1取决于以下几种情况: 时间中断处理程序,发现当前进程运行时间过长:每次发生时间中断,都要递减该进程的时间片,一旦count为0,强制调度,剥夺当前进程运行 void update_process_times(int user_tick) { struct task_struct *p = current; int cpu =

第一次作业:Linux 2.6.28进程模型与CFS调度器分析

第一次作业 1.摘要 本文主要针对Linux Kernel 2.6.28内核版本,描述了进程的概念以及调用过程. Linux Kernel源码查阅地址:https://elixir.bootlin.com/linux/v4.6/source/include/linux/types.h 2. 何谓进程 2.1 进程的概念 进程的一种官方定义: 进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,也是操作系统进行资源分配和调度的一个独立单位. 简而言之,进程是操作系统为正在运行的程序所建

ORACLE调度之基于时间的调度(一)【weber出品】

一.调度的概述 这里我看到一篇对调度的概述觉得描述的比我好,但仅限于概述部分,其他部分我觉得我讲的比他好,于是发生以下事情: ************************华丽的转载************************************************************************* 在Oracle中任务调度指某一执行程序在特定的时间被周期性的执行.Oracle把任务调度称为job.而一个基本的job由两方面组成program和schedule.其中

Linux0.11内核--进程调度分析之2.调度

上一篇说到进程调度归根结底是调用timer_interrupt函数,在system_call.s中: #### int32 -- (int 0x20) 时钟中断处理程序.中断频率被设置为100Hz(include/linux/sched.h,5), # 定时芯片8253/8254 是在(kernel/sched.c,406)处初始化的.因此这里jiffies 每10 毫秒加1. # 这段代码将jiffies 增1,发送结束中断指令给8259 控制器,然后用当前特权级作为参数调用 # C 函数do

第一次作业:关于Linux进程模型及CFS调度器分析

第一次作业内容 挑选一个开源的操作系统,深入源码分析其进程模型,具体包含如下内容: 操作系统是怎么组织进程的 进程状态如何转换(给出进程状态转换图) 进程是如何调度的 谈谈自己对该操作系统进程模型的看法 1. 前言 本文基于Linux Kernel 2.6.28 的源代码,分析本版本linux的进程模型和CFS调度器的基本算法. 源码浏览地址:https://elixir.bootlin.com/linux/v2.6.28/source 2. 进程 2.1 进程的定义 <计算机操作系统>这门课

【转】struts2的ActionInvocation分析(action调度者)

一个ActionInvocation实例代表一个action的执行状态,持有拦截器和将要执行的action的实例. defaultActionInvocation是其默认实现.下面是定义在该类中的部分成员变量 1 public class DefaultActionInvocation implements ActionInvocation { 2 protected Object action; 3 protected ActionProxy proxy; 4 protected List<P

20135201李辰希 《Linux内核分析》第四周 扒开系统调用的“三层皮”

李辰希无转载 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.用户态.内核态和中断处理过程 1.我们与系统调用打交道是通过库函数的方式 2.一般现代CPU都有几种不同的指令执行级别 因为如果所有程序员写的代码都可以有特权指令的话,系统就会很容易崩溃. 3.区别: 在高级别的状态下,代码可以执行特权指令,访问任意的物理地址. 在相应的低级别执行状态下,代码的掌控范围会受到限制. Intel x86 CPU有四