1.1硬中断和软中断
中断是指在计算机执行期间,系统内发生任何非寻常的或非预期的急需处理事件,使得CPU暂时中断当前正在执行的程序而转去执行相应的时间处理程序。待处理完毕后又返回原来被中断处继续执行或调度新的进程执行的过程。
引发中断的事件称为中断源。中断源向CPU提出处理的请求称为中断请求。发生中断时被打断程序的暂停点称为断点。CPU暂停现行程序而转为响应中断请求的过程称为中断响应。处理中断源的程序称为中断处理程序。CPU执行有关的中断处理程序称为中断处理。而返回断点的过程称为中断返回。中断的实现由软件和硬件综合完成,硬件部分叫作硬件装置,软件部分称为软件处理程序。linux
上下文
通常来讲,CPU在任什么时候刻都处于如下3种状况之一。算法
中断处理程序主动调用schedule函数让出CPU,涵盖第一和第二种状况。架构
调度算法就是从就绪队列中选一个进程。调度策略是寻找知足需求的方法,而调度算法是如何实现这个调度策略。
2.1 进程的分类
按CPU占用率分类:ide
实时进程。实时进程对调度延迟的要求最高,这些进程执行很是重要的操做,要求当即执行响应并执行。
当前Linux系统的解决方案是,对于实时进程,linux采用FIFO(先进先出)或者Round Robin(时间片轮转)的调度策略。对其它进程,则采用CFS调度器,核心是“彻底公平”。函数
2.2 调度策略
Linux系统中经常使用的几种调度策略为SCHED_NORMAL、SCHED_FIFO、SCHED_RR。
其中SCHED_NORMAL是用于普通进程的调度类,而SCHED_FIFO和SCHED_RR是用于实时进程的调度类,优先级高于SCHED_NORMAL。内核根据进程的优先级来区分普通进程与实时进程,Linux内核进程优先级为0~139,数值越高,优先级越低,0为最高优先级。实时进程的优先级取值为0~99,普通进程只具备nice值,nice值映射到优先级为100~139。学习
2.3 CFS调度算法
CFS即为彻底公平调度算法,其基本原理是基于权重的动态优先级调度算法。每一个进程使用CPU的顺序进程由已使用的CPU虚拟时间(vruntime)决定,已使用的虚拟时间越少,进程排序就越靠前,进程再次被调度执行的几率也就越高。每一个进程每次占用CPU后可以执行的时间(ideal_runtime)由进程的权重决定,而且保证在某个时间周期(_sched_period)内运行队列的因此进程都可以至少被调度执行一次。atom
3.1 进程执行环境的切换
为了控制进程的执行,内核必须有能力挂起正在CPU中运行的进程,并恢复执行之前挂起的某个进程。这种行为被称为进程切换,任务切换或进程上下文切换。进程上下文包含了进程执行须要的全部信息。idea
硬件上下文,相关寄存器的值。
在实际的代码中,每一个进程切换基本由两个步骤组成:操作系统
切换内核态堆栈和硬件上下文,由于硬件上下文提供了内核执行新进程所须要的全部信息。
线程
1.从新克隆一个menu,而后从新编译内核。
2.打开调试模式,另打开一个窗口进行gdb远程调试,配置gdb远程调试并设置断点。
context_switch关键代码部分
static inline void context_switch(struct rq *rq, struct task_struct *prev, struct task_struct *next) { ... arch_start_context_switch(prev); if (unlikely(!mm)) { //若是被切换进来的进程的mm为空切换,内核线程mm为空 next->active_mm = oldmm; //将共享切换出去的进程的active_mm atomic_inc(&oldmm->mm_count); //有一个进程共享,全部引用计数加一 enter_lazy_tlb(oldmm, next); //将per cpu变量cpu_tlbstate状态设为LAZY } else //普通mm不为空,则调用switch_mm切换地址空间 switch_mm(oldmm, mm, next); ... //这里切换寄存器状态和栈 switch_to(prev, next, prev);
switch_to关键代码部分
#define switch_to(prev, next, last) do { /* * Context-switching clobbers all registers, so we clobber * them explicitly, via unused output variables. * (EAX and EBP is not listed because EBP is saved/restored * explicitly for wchan access and EAX is the return value of * __switch_to()) */ unsigned long ebx, ecx, edx, esi, edi; asm volatile( "pushfl\n\t" //保存当前进程flags "pushl %%ebp\n\t" //当前进程堆栈基址压栈 "movl %%esp,%[prev_sp]\n\t" //保存ESP,将当前堆栈栈顶保存起来 "movl %[next_sp],%%esp\n\t" //更新ESP,将下一栈顶保存到ESP中 // 完成内核堆栈的切换 "movl $1f,%[prev_ip]\n\t" //保存当前进程的EIP "pushl %[next_ip]\n\t" //将next进程起点压入堆栈,即next进程的栈顶为起点 __switch_canary //next_ip通常为$1f,对于新建立的子进程是ret_from_fork "jmp __switch_to\n" //prve进程中,设置next进程堆栈,jmp与call不一样,是经过寄存器传递参数(call经过堆栈),因此ret时弹出的是以前压入栈顶的next进程起点 //完成EIP的切换 "1:\t" //next进程开始执行 "popl %%ebp\n\t" //restore EBP "popfl\n" //restore flags //输出量 : [prev_sp] "=m" (prev->thread.sp), //保存当前进程的esp [prev_ip] "=m" (prev->thread.ip), //保存当前进仓的eip "=a" (last), //要破坏的寄存器 "=b" (ebx), "=c" (ecx), "=d" (edx), "=S" (esi), "=D" (edi) __switch_canary_oparam //输入量 : [next_sp] "m" (next->thread.sp), //next进程的内核堆栈栈顶地址,即esp [next_ip] "m" (next->thread.ip), //next进程的eip // regparm parameters for __switch_to(): [prev] "a" (prev), [next] "d" (next) __switch_canary_iparam : //从新加载段寄存器 "memory"); } while (0)
经过本次实验,我学习到了进程调度的时机和进程切换进行,分析进程的调度时机,调度策略和算法,并跟踪schedule,pick_next_task和context_switch等函数。 我了解到进程调度是为了合理分配计算机资源,并让每一个进程都得到适当的执行机会。 而在中断处理过程(包括时钟中断、I/O中断、系统调用和异常)中,系统直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule()。 用户态进程没法实现主动调度,仅能经过陷入内核态后的某个时机点进行调度,即在中断处理过程当中进行调度。