进程—进程调度(1)

进程—进程调度(1)


上下文切换

进程可以调度,但必须保证每个进程都可以顺序的执行,而一个进程执行所需的全部信息可由进程的PCB(task_struct)维护,所以在进程发生切换的时候可以将当前进程的运行状态信息(快照)保存到它的PCB中(这样就能在下一次调度程序选择到它时接着上一状态继续执行),将马上要执行的进程的运行状态信息(在PCB中)恢复,这样就可以合理的完成调度,这个过程就叫上下文切换。

中断

上下文切换是在内核中完成的,对用户透明,所以在上下文切换的时候必须先陷入内核(一般是通过时钟中断和系统调用)。上下文的切换需要硬件的支持。当前进程正在运行,当中断发生时,中断硬件将程序计数器、程序状态字、有时还有一个或多个寄存器(进程的运行状态信息)压入当前进程的内核堆栈中,PC随即跳转到中断服务程序入口(根据硬件向量法或软件查询法得到中断服务程序入口地址)去执行中断服务程序。注意,这些工作都是硬件完成的,在这些工作完成的同时完成了一次堆栈切换(从进程的用户堆栈切换到进程的内核堆栈,由中断硬件完成)。然后,PC的控制权转移到了软件(中断服务程序),一般地,中断服务程序有一个自己的中断堆栈(就像进程有自己的内核堆栈一样),为了不破坏内核堆栈,在执行中断服务程序的过程中又会发生一次堆栈切换(从内核堆栈切换到中断堆栈,这次的切换是软件完成的),进入到中断上下文之中,随后中断服务程序会调用处理特定中断请求的中断处理例程,让中断处理例程运行在中断上下文之中。中断处理例程完成中断处理之后返回到中断服务程序,返回的过程又会涉及到一次堆栈切换(由中断堆栈切换到内核堆栈,这次的切换也是软件完成的),最后,中断服务程序执行中断返回指令,由中断硬件将内核堆栈中的状态信息恢复到相应的寄存器中,在恢复寄存器的同时清空内核堆栈中保存的状态信息,系统从内核态返回到用户态,这里又发生了一次堆栈切换(从内核堆栈切换到用户堆栈,由中断硬件完成)。

发生上下文切换的中断过程

中断服务程序调用中断处理例程,中断处理例程在执行的过程中调用了调度程序进行调度,这时,调度程序会检查是否需要发生上下文切换(不是每次中断都会发生上下文的切换,例如某个时钟中断就可能不会导致上下文切换),发现需要发生上下文切换。接下来,要完成不同进程之间的内核栈的切换。为什么要切换内核栈呢?因为restore_all需要从内核栈中恢复中断现场,每个进程都有一个内核堆栈,它们互不相同,里面保存了各自的中断现场,所以要想恢复进程A的中断现场就得先切换到进程A的内核堆栈中去。

参考《Linux内核代码情景分析》第三章

ENTRY(ret_from_intr)
  ...
 jne ret_with_reschedule //需要返回用户态
  ...

ret_with_reschedule:
 cmpl $0,need_resched(%ebx)
 jne reschedule
 cmpl $0,sigpending(%ebx)
 jne signal_return  
restore_all:
  RESTORE_ALL

reschedule:
   call SYMBOL_NAME(schedule)//完成任务的调度以及内核堆栈的切换
   ...
   jmp ret_from_intr  




#define RESTORE_ALL \

popl %ebx; \
popl %ecx; \
popl %edx; \
popl %esi; \
popl %edi; \
popl %ebp; \
popl %eax; \
1: popl %ds; \
2: popl %es; \
addl $4,%esp; \
3: iret; \

/*
忽略信号处理等流程,可以看到,中断返回用户态的流程大概是
ret_from_intr-->[jne]ret_with_reschedule-->[jne]reschedule-->[call]schedule
-->[jmp]ret_from_intr-->[jne]ret_with_reschedule-->restore_all。
除了schedule是用call指令被调用之外,其他的流程都是通过跳转指令到达,所以不会返回。

在第一次执行ret_from_intr处的代码时,跳转条件满足,流程跳到ret_with_reschedule,一直
到[call]schedule,因为schedule是函数调用,所以会在内核堆栈中构造返回地址,调整%ebp,
%esp。这样,在schedule返回时,pc能跳到返回地址【call SYMBOL_NAME(schedule)的下一条指
令】,内核堆栈恢复到调用schedule之前的状态,流程回到call SYMBOL_NAME(schedule)的下一
条指令,跳来跳出,最后会跳到restore_all,恢复中断现场。
*/

内核堆栈的切换由内联汇编代码switch_to完成。

参考《深入理解Linux内核》中文第三版.第三章和第七章

static inline
task_t * context_switch(runqueue_t *rq, task_t *prev, task_t *next)
{
 struct mm_struct *mm = next->mm;
 struct mm_struct *oldmm = prev->active_mm;

  /*
  内核进程的task_struct->mm为NULL, task_struct->active_mm指向之前进程的active_mm
  用户进程的task_struct->mm不为空,task_struct->active_mm指向自己的mm

  如果next是一个内核进程,那么他就是用prev的active_mm,不进行cr3的切换,使用prev进程
  的页目录。

  如果next是一个用户进程,那么他需要将cr3切换,使cr3寄存器指向自己的页目录
  */

  /*
  可以去include\asm-i386\mmu_context.h中查看enter_lazy_tlb函数和switch_mm函数
  */
 if (unlikely(!mm)) {    //next是一个内核进程
     next->active_mm = oldmm;
     atomic_inc(&oldmm->mm_count);
     enter_lazy_tlb(oldmm, next);
 } else  //next是一个用户进程
     switch_mm(oldmm, mm, next);
  /*
  static inline void switch_mm(struct mm_struct *prev,
                            struct mm_struct *next,
                            struct task_struct *tsk)
     ...
         //Re-load page tables 
         load_cr3(next->pgd); //切换cr3
     ...
 }   
   */

...

 /* Here we just switch the register state and the stack. */
 switch_to(prev, next, prev); //完成内核堆栈的切换

 return prev;
}

#define switch_to(prev,next,last) do {                   \

 unsigned long esi,edi;                      \
 asm volatile("pushfl\n\t"                   \
          "pushl %%ebp\n\t"                  \
          "movl %%esp,%0\n\t"    /* save ESP */      \
          "movl %5,%%esp\n\t"    /* restore ESP */   \
          "movl $1f,%1\n\t"     /* save EIP */      \
          "pushl %6\n\t"         /* restore EIP */   \
          "jmp __switch_to\n"                \
          "1:\t"                     \
          "popl %%ebp\n\t"                   \
          "popfl"                        \
          :"=m" (prev->thread.esp),"=m" (prev->thread.eip),  \
           "=a" (last),"=S" (esi),"=D" (edi)         \
          :"m" (next->thread.esp),"m" (next->thread.eip),    \
           "2" (prev), "d" (next));              \
} while (0)
//这个函数使用寄存器传参: %eax<-->prev_p, %edx<-->next_p
struct task_struct fastcall * __switch_to(struct task_struct *prev_p, struct task_struct *next_p)
{
 struct thread_struct *prev = &prev_p->thread,
              *next = &next_p->thread;
 int cpu = smp_processor_id();
 struct tss_struct *tss = &per_cpu(init_tss, cpu);

 /* never put a printk in __switch_to... printk() calls wake_up*() indirectly */

 __unlazy_fpu(prev_p);

 /*
  * Reload esp0, LDT and the page table pointer:
  */
 load_esp0(tss, next);

 /*
  * Load the per-thread Thread-Local Storage descriptor.
  */
 load_TLS(next, cpu);

 /*
  * Save away %fs and %gs. No need to save %es and %ds, as
  * those are always kernel segments while inside the kernel.
  */
 asm volatile("movl %%fs,%0":"=m" (*(int *)&prev->fs));
 asm volatile("movl %%gs,%0":"=m" (*(int *)&prev->gs));

 /*
  * Restore %fs and %gs if needed.
  */
 if (unlikely(prev->fs | prev->gs | next->fs | next->gs)) {
     loadsegment(fs, next->fs);
     loadsegment(gs, next->gs);
 }

 /*
  * Now maybe reload the debug registers
  */
 if (unlikely(next->debugreg[7])) {
     loaddebug(next, 0);
     loaddebug(next, 1);
     loaddebug(next, 2);
     loaddebug(next, 3);
     /* no 4 and 5 */
     loaddebug(next, 6);
     loaddebug(next, 7);
 }

 if (unlikely(prev->io_bitmap_ptr || next->io_bitmap_ptr))
     handle_io_bitmap(next, tss);

 return prev_p;//pre_p-->%eax
}

switch_to(prev,next,last)并不是一个函数,只是一个宏定义,现在我把它放到调度函数的环境下:

asmlinkage void __sched schedule(void)
{
     task_t *prev, *next;
     runqueue_t *rq;
  ···  
         prev = context_switch(rq, prev, next);          
     /*  
         static inline
         task_t * context_switch(runqueue_t *rq, 
                                 task_t *prev, 
                                 task_t *next)
     {
     ...
         //直接展开switch_to(prev, next, prev):
         do {                    \
         unsigned long esi,edi;                      \
         asm volatile("pushfl\n\t"                   \
          "pushl %%ebp\n\t"                  \
          "movl %%esp,%0\n\t"    // save ESP     \

          //切换内核堆栈
          "movl %5,%%esp\n\t"    // restore ESP  \

          "movl $1f,%1\n\t"     // save EIP         \
          "pushl %6\n\t"         // restore EIP  \
          "jmp __switch_to\n"                \
          "1:\t"                     \
          "popl %%ebp\n\t"                   \
          "popfl"                        \
          :"=m" (prev->thread.esp),"=m" (prev->thread.eip),  \
           "=a" (last),"=S" (esi),"=D" (edi)         \
          :"m" (next->thread.esp),"m" (next->thread.eip),    \
           "2" (prev), "d" (next));              \
         } while (0);        
         ...         
         }       
         */

     barrier();
         finish_task_switch(prev);
         ···
         prev = current;
  ···
}

图示:

方框中的%x表示寄存器,进程A是prev,进程B是next。图中%eap是指%eax,%edp是指%edx,标注错了。

下图是cpu刚执行完eip所指的指令movl %%esp, %0\n\t后的情况,注意,我忽略了堆栈中的局部变量unsigned long esi,edi;movl %%esp, %0\n\t表示:ebp_A==> A->thread.esp


p144 内核栈切换_brefore

下图展示了切换内核堆栈之后的情况。注意,这时候cpu用来索引局部变量的寄存器%ebp还没有切换到B的内核堆栈,所以代码中对局部变量的引用仍然来自A的内核堆栈。


p144 内核栈切换_切换

切换堆栈之后,将1赋值到A->thread.eip。


p144 内核栈切换_middle1

注意,函数__switch_to不是通过call指令调用的,而是直接jmp过去的。所以不会在B的内核堆栈中构造返回地址,不过,cpu执行pushl %6\n\t指令时将一个保存在B中的指令地址B->thread.eip压入了B的内核堆栈中,这相当于手动为__switch_to构造了一个返回地址,所以在__switch_to返回时[ret],cpu会调转到B->thread.eip处执行,即跳转到1处。


p144 内核栈切换_middle2

执行指令popl %%ebp\n\t ,切换%ebp寄存器到B内核堆栈:ebp_B==>%ebp


p144 内核栈切换_切换

最后更改B内核堆栈中的局部变量prev的值,使之指向A。因为%ebp已经切换到B的内核堆栈上了,所以对局部变量last【宏名称,预处理阶段会被替换成prev】的引用会更改B内核堆栈中的局部变量prev的值。


p144 内核栈切换_切换prev

通过上面一系列的过程,内核完成将内核堆栈由A[prev]切换到B[next]的工作,而且,现在,B通过current宏能够获得自己的task_struct,同时拥有到A的task_struct的引用prev[B内核堆栈中的局部变量prev]。

那为什么将A调度出去了还要在B中拥有对A的引用呢?

因为在流程回到schedule之后,虽然控制流掌握在了B的手中,但是在B返回用户态之前,内核在schedule的后续工作中还得用到A的task_struct。

内核还需要调用函数finish_task_switch(prev)对进程A进行后续地处理。

最后,schedule的任务完成,返回到 call SYMBOL_NAME(schedule)的下一条指令接着执行,内核堆栈恢复到调用schedule之前的状态。最终,内核执行到restore_all,恢复中断现场,进程B返回到被中断之前的状态,在用户态接着执行,仿佛从未发生过中断一样。

Linux进程类别

1.实时进程:高优先级,响应快,优先级范围[0,99]。
2.普通进程:分为交互式进程(I/O消耗型)和批处理进程(CPU消耗型),优先级低于实时进程,范围为[100,139]。

在Linux中,调度算法可以明确地确认所有实时进程的身份,但却不能区分交互式进程和批处理进程。Linux2.6调度程序实现了基于进程过去行为的启发式算法,以确定进程此刻应该被当作交互式进程还是批处理进程。与批处理进程相比,交互式进程的调度优先级要更高一些。

Linux中与调度相关的系统调用

系统调用说明
nice()改变一个普通进程的静态优先级
getpriority()获得一组普通进程的最大静态优先级
setpriority()设置一组普通进程的静态优先级
sched_getscheduler()获得一个进程的调度策略
sched_setscheduler()设定一个进程的调度策略和实时优先级
sched_getparam()获得一个进程的实时优先级
sched_setparam()设置一个进程的实时优先级
sched_yield()自愿放弃处理器而不阻塞
sched_get_priority_min()获得一种策略的最小实时优先级
sched_rr_get_interval()获得时间片轮转策略的时间片值
sched_setaffinity()设置进程的CPU亲和力掩码
sched_getaffinity()获得进程的CPU亲和力掩码

几个优先级字段

nice
static_prio

nice和static_prio是普通进程会用到的两个优先级字段,用轮转策略的实时进程也会用到它们,只是用来重新计算轮转时间片长度。

rt_priority

rt_priority是实时进程用到的优先级字段。

prio

这才是决定进程调度优先级的字段,也就是说,进程在哪个就绪队列中由这个字段决定。

Linux进程调度策略

用户可以调用系统调用sched_setscheduler()设置调度策略。

unsigned int policy;

Linux的进程调度是抢占式的,允许高优先级进程抢占低优先级进程,这是下面几个调度策略对应的算法都必须确保的基本机制。另外,调度只会发生在位于就绪队列中的进程之间(ranqueue)。

实时进程的调度优先级只由实时优先级(rt_priority,范围为[0,MAX_RT_PRIO-1])静态的决定,从一开始由用户指定后,就不再动态地改变,不受nice值的影响(nice值只影响调度优先级在[100,139]范围内的普通进程的调度优先级)。实时优先级的调度优先级(prio)通过其实时优先级(rt_priority)计算(prio = MAX_RT_PRIO-1 - rt_priority)得到,范围在0~MAX_RT_PRIO-1间。默认MAX_RT_PRIO为100,所以默认的实时进程的调度优先级(prio)范围是[0,99],实时进程的调度优先级虽然不会自己动态的改变,但是可以由用户使用系统调用sched_setparam()和sched_setscheduler()来重新设置它的实时优先级,进而改变调度优先级。实时进程位于高优先级队列(明确地说,优先权在[0~MAX_RT_PRIO-1]区间内的优先级队列)中,而且总是位于活动运行队列中,所以只要有实时进程存在,普通进程永远也别想运行。

普通进程的调度优先级根据静态优先级static_prio和平均睡眠时间sleep_avg动态的来计算,static_prio是根据nice值得到的,两者可以相互转换。详细的说明请见task_struct代码注释。默认的普通进程的调度优先级(prio)范围是[100,139]。

1.SCHED_FIFO

值为1-实时进程-用基于优先权的先进先出算法。基于SCHED_FIFO调度机制的实时进程在运行时会一直占用CPU,除非就绪队列中有优先级更高的实时进程,或自愿调用阻塞原语(如sleep_on_interruptible()),或停止,或被杀死,或通过调用sched_yield()自动放弃CPU。

2.SCHED_RR

值为2-实时进程-用基于优先权的轮转法。一旦当前进程自愿调用阻塞原语(如sleep_on_interruptible()),或停止,或被杀死,或通过调用sched_yield()自动放弃CPU或一个时间片消耗完毕,或就绪队列中有优先级更高的实时进程,则会在中断返回时发生调度。基于SCHED_RR调度机制的调度程序将该进程置于同优先级队列的末尾,然后运行该优先级队列中的下一个实时进程。这是分时系统实现良好交互性的基础算法,又名时间片轮转调度算法。

3.SCHED_NORMAL

值为0-非实时进程-用基于优先权的多级反馈队列轮转法,根据进程过去的执行情况动态的调整调度优先级,一般还会将执行完轮转时间片的进程放入过期可运行队列中。兼顾了作业的周转时间和交互性,并且防止了饿死。

普通进程的调度策略:SCHED_NORMAL

普通进程的调度优先级(prio)由2个因素决定,一个是进程的静态优先级(static_prio,又叫基本优先级,默认值是120),另一个是进程的平均睡眠时间(sleep_avg)。static_prio的范围为[100,139],新进程总是继承其父进程的静态优先级。

static_prio

static_prio通过nice(范围为[-20,19])计算得到(static_prio = 120 + nice),所以用户可以通过系统调用nice()和setpriority()来设置nice值进而改变自己拥有的进程的静态优先级(也只能通过这两个系统调用改变静态优先级,否则静态优先级不会发生变化,尽管动态优先级会发生变化)。

普通进程每次执行的时候都有一个有限的轮转时间片,这是进程在被抢占前能够连续占用CPU的时间片长度,如果没有高优先级进程抢断,当前进程会可以一直占用CPU直到用完它的轮转时间片。静态优先级用来计算普通进程的轮转时间片,基本规则是静态优先级越高,static_prio越小,轮转时间片越长。所以对于普通进程,静态优先级越高的进程获得的连续执行CPU的时间片越长,也可以说nice值唯一决定了普通进程能获得的轮转时间片长度。具体的计算规则可以参考../kernel/sched.c 中的task_timeslice(),这个函数计算并返回一个进程轮转时间片长度,或者参考《深入理解Linux内核》第七章中普通进程的调度章节下的基本时间片一节。

p126 static_prio

prio

task_struct中的这个字段是进程的调度优先级,用来决定进程在哪个优先级队列中,进而实现O(1)调度。对于普通进程,这个字段被称为它的动态优先级,通过静态优先级和平均睡眠时间计算得到,范围是[100,139]。

普通进程的调度策略(SCHED_NORMAL)的主要思想是通过进程过去的执行情况来衡量这个进程是属于I/O消耗型还是CPU消耗型,具体的衡量指标是一个进程的平均睡眠时间sleep_avg。如果一个进程的sleep_avg大,则该进程更加趋向于I/O消耗型,优先级就越高,prio就越小。

动态优先级: prio = max (100, min (static_prio - bonus,139) )

bonus = 10*sleep_avg/HZ - 5,范围在[-5,5]之间。(sleep_avg 范围为[0,HZ]),所以sleep_avg = HZ/2时,进程调度优先级保持不变;当HZ > sleep_avg > HZ/2时,进程调度优先级会提高;当0 < sleep_avg < HZ/2时,进程调度优先级会降低。

对于交互性强的进程,它不会一次性的就将轮转时间片给用光(这一般是CPU消耗型进程干的事),而是用一小会儿之后,进行一次I/O操作,放弃掉CPU,这使得它可以长时间的位于活期可执行队列中,不至于用完时间片了就被重新计算时间片,然后放到过期可执行队列中,久久得不到执行机会。而且经常性的执行I/O操作正是交互型进程的一大特点,这也是交互型进程之所以为交换型进程的根本原因,系统给予这种进程以较高的bonus,使其调度优先级更高,有更多的被调度的机会。

实时进程的调度策略

参考task_struct代码注释和Linux进程调度策略的前言部分。

进程调度时机

调度时机来临时,内核或驱动将调用schedule(),在Linux中调度的时机主要有:
一、current的状态从running转换为其它状态时,如:
1)进程终止。exit()在最后调用schedule()。
2)进程因某种原因进入等待状态(随后会从就绪队列中删除,被插入到等待队列中)。
比较常见的就是进程调用nanosleep()或者wait系列的系统调用。此外,在设备驱动程序中,最常见的原因就是驱动程序引发一次I/O操作后,为等待I/O操作的结束而进入等待状态。多数情况下,驱动程序会直接调用schedule()。

二、当前进程的时间片用完时。
时间片是否用完,由时钟中断处理程序进行判断,若用完,就将current进程的need_resched位置1。在中断将要返回用户态时,如果current的need_resched位为1,则调用schedule()。

三、进程从中断、异常、系统调用状态(即内核态)返回时。
每次从内核返回到用户态时都会检查need_resched标记,若在中断、异常、系统调用中,current的need_resched被置1,就会导致进程调度,时钟中断属于此类。

计算调度优先级:effective_prio(p)

计算进程p的调度优先级,在更新进程调度优先级时会被recalc_task_prio()调用。

//../kernel/sched.c

/*
* return the priority that is based on the static
* priority but is modified by bonuses/penalties.
   *
* We scale the actual sleep average [0 .... MAX_SLEEP_AVG]
* into the -5 ... 0 ... +5 bonus/penalty range.
   *
   */
  static int effective_prio(task_t *p)
  {
  int bonus, prio;

  if (rt_task(p))//如果是实时进程
    return p->prio; //返回实时进程的调度优先级

  //如果是普通进程

  bonus = CURRENT_BONUS(p) - MAX_BONUS / 2; // 10*(sleep_avg/HZ) - 5,值在[-5,5]之间。

  prio = p->static_prio - bonus; 
  if (prio < MAX_RT_PRIO)
    prio = MAX_RT_PRIO;
  if (prio > MAX_PRIO-1)
    prio = MAX_PRIO-1;
  return prio; //普通进程的调度优先级
   //prio = max (100, min (static_prio - bonus, 139))
  }

//下面的代码段是一些宏定义,用来参考。
//../include/linux/sched.h

#define MAX_USER_RT_PRIO    100
#define MAX_RT_PRIO     MAX_USER_RT_PRIO  //100
#define MAX_PRIO        (MAX_RT_PRIO + 40) //140

#define rt_task(p)      (unlikely((p)->prio < MAX_RT_PRIO)) //prio<100?

//../kernel/sched.c

#define USER_PRIO(p)        ((p)-MAX_RT_PRIO)  //p-100

#define MAX_USER_PRIO       (USER_PRIO(MAX_PRIO)) //40
#define PRIO_BONUS_RATIO     25

#define MAX_BONUS       (MAX_USER_PRIO * PRIO_BONUS_RATIO / 100) //10

#define DEF_TIMESLICE       (100 * HZ / 1000)

#define MAX_SLEEP_AVG       (DEF_TIMESLICE * MAX_BONUS)  //HZ

#define NS_TO_JIFFIES(TIME) ((TIME) / (1000000000 / HZ))
#define MAX_SLEEP_AVG       (DEF_TIMESLICE * MAX_BONUS)   //HZ 

#define CURRENT_BONUS(p) \
    (NS_TO_JIFFIES((p)->sleep_avg) * MAX_BONUS / \
        MAX_SLEEP_AVG)              // 10*sleep_avg/HZ  值在[0,10]之间

参考

task_struct

//---------------------------------------  Linux 2.6 进程调度相关信息  -----------------------------------------
  long                              nice; //进程的初始优先级,范围[-20,+19],默认0,nice值越大优先级越低,分配的
  //时间片可能更少。能通过系统调用nice()可以修改nice值。
  int                               static_prio;//静态优先级。范围为[MAX_RT_PRIO, MAX_RT_PRIO+39],默认情况
  //[100,139]。Normal进程使用静态优先级static_prio和平均睡眠时间sleep_avg动态的计算进程的调度优先级prio。
   /*
    static_prio= MAX_RT_PRIO + nice + 20

    在../kernel/sched.c中有两个宏实现nice值和static_prio值之间的转换
    #define NICE_TO_PRIO(nice)  (MAX_RT_PRIO + (nice)+ 20)
    #define PRIO_TO_NICE(prio)  ((prio) - MAX_RT_PRIO- 20)
  */

  unsigned int                      rt_priority; //实时优先级,[0,MAX_RT_PRIO-1],默认情况下范围[0,99],在
  //setscheduler()中设置,且一经设定就不再改变。Real_time进程使用实时优先级rt_priority静态的计算进程的调度优先级prio。
   /*
    0-99 -> realtime
   */

  int                               prio; //存放"调度程序"要用到的优先级,对应优先级位图中的相应优先级位。数值越
  //大,表示进程优先级越小。
  /*
    0-99 -> Realtime process
    100-139 -> Normal process

   for Realtime process: prio = MAX_RT_PRIO-1 – rt_priority
   for Normal process: prio = max (100, min (static_prio - bonus, 139))
   其中bonus在[-5,5]之间。bonus越大,prio越小,优先级越高。
  */ 

  unsigned long                     sleep_avg;//这个字段的值用来支持调度程序对进程的类型(I/O消耗型 or CPU消耗型)
  //进行判断,值越大表示睡眠的时候越多,更趋向于I/O消耗型,系统调度时,会给该进程更多奖励以便该进程有更多的机会能够执行,反
  //之,更趋向于CPU消耗型,会给该进程更多惩罚。sleep_avg 的范围是 0~MAX_SLEEP_AVG。

  unsigned long long                timestamp;//进程最近插入运行队列的时间,或涉及本进程的最近一次进程切换发生的时
 //间。
  unsigned long long                last_ran;//最近一次替换本进程的进程切换发生的时间。

  cputime_t                         utime;//该进程在用户态的cpu的使用时间。
  cputime_t                         stime;//该进程在内核态的cpu的使用时间。
  unsigned long                     sleep_time; //进程的睡眠时间

  unsigned int                      time_slice;//进程剩余时间片,当一个普通进程(或者基于时间片轮转策略的实时进程)
  //的时间片用完之后,要根据任务的静态优先级static_prio重新计算时间片。task_timeslice()为给定的任务返回一个新的时间
  //片。对于交互性强的进程,时间片用完之后,它会被再放到活动数组而不是过期数组,该逻辑在scheduler_tick()中实现。

  unsigned int                      first_time_slice;//如果进程肯定不会用完其时间片,就把该标志设置为1

  const struct sched_class          *sched_class;  //与调度相关的函数
  struct sched_entity               se;  //调度实体
  struct sched_rt_entity            rt;  //实时任务调度实体
#ifdef CONFIG_PREEMPT_NOTIFIERS 
  /*list of struct preempt_notifier:*/
  struct hlist_head                 preempt_notifiers;  //与抢占有关
#endif 
#if defined(CONFIG_SCHEDSTATS)||define(CONFIG_TASK_DELAY_ACCT)  
  unsigned int                      policy;  //表示该进程的进程调度策略。调度策略有:
//SCHED_NORMAL 0, 非实时进程, 用基于优先权的轮转法。
//SCHED_FIFO 1, 实时进程, 用先进先出算法。
//SCHED_RR 2, 实时进程, 用基于优先权的轮转法

  struct sched_info                 sched_info;  //调度相关的信息,如进程在cpu上运行的时间/在队列中等待的时间...
#endif

  struct list_head                  tasks;  //任务队列,通过这个寄宿于PCB(task_struct)中的字段构成的双向循环链表
  //将宿主PCB链接起来。

  volatile long                     need_resched; //调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到
  //用户态或从中断返回时,会发生调度。当进程的时间片耗尽时,scheduler_tick()会设置这个标识,当一个优先级高的进程进入可执
  //行状态的时候,try_to_wake_up()会设置这个标识。

  struct list_head                  run_list; //该进程所在的运行队列。这个队列有一个与之对应的优先级k,所有位于这个
  //队列中的进程的优先级都是k,这些k优先级进程之间使用轮转法进行调度。k的取值是0~139。这个位于宿主PCB中的struct 
  //list_head类型的run_list字段将构成一个优先级为k的双向循环链表,像一条细细的绳子一样,将所有优先级为k的处于可运行状态
  //的进程的PCB(task_struct)链接起来。

  prio_array_t                      *array; //指向当前进程所在CPU的就绪进程链表。

  cpumask_t                         cpus_allowed//能执行进程的CPU的位掩码

  struct thread_info                *thread_info;
/*
thread_info中与调度相关字段:
__u32                       flags;//存放TIF_NEED_RESCHED标志,如果必须调用调度程序,则设置该标志
__u32                       cpu;//运行进程所在运行队列的CPU逻辑号 

*/

arch\i386\kernel\entry.s

    # system call handler stub
ENTRY(system_call)
    pushl %eax          # save orig_eax
    SAVE_ALL
    GET_THREAD_INFO(%ebp)
                    # system call tracing in operation
    testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)
    jnz syscall_trace_entry
    cmpl $(nr_syscalls), %eax
    jae syscall_badsys
syscall_call:
    call *sys_call_table(,%eax,4)
    movl %eax,EAX(%esp)     # store the return value
syscall_exit:
    cli             # make sure we don't miss an interrupt
                    # setting need_resched or sigpending
                    # between sampling and the iret
    movl TI_flags(%ebp), %ecx
    testw $_TIF_ALLWORK_MASK, %cx  # current->work
    jne syscall_exit_work
restore_all:
    RESTORE_ALL
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
进程控制是操作系统的重要功能之一,它负责管理计算机系统中运行的各个进程进程控制通过创建、终止、挂起、激活等操作来管理进程的生命周期,保证系统的稳定性和高效性。同时,进程控制还包括进程间通信和同步机制的管理,以确保不同进程之间能够协同工作并共享资源。 在操作系统中,进程调度是保证系统资源充分利用、提高系统吞吐量的一个重要功能。进程调度算法是决定在多个就绪态进程中,系统应该选择哪一个进程来执行的策略。常见的调度算法包括先来先服务(FCFS)、最短作业优先(SJF)、优先级调度、轮转法等。不同的调度算法适用于不同的场景,对系统的性能影响也不同。 进程控制和调度是操作系统的核心内容之一,对系统的性能和用户体验有着直接的影响。合理的进程控制和调度策略可以有效地提高系统的并发处理能力和资源利用率,减少进程之间的竞争和冲突。同时,进程控制和调度也涉及到系统的安全性和稳定性,因此需要不断优化和改进策略,以适应不断变化的应用需求。 在实际的操作系统开发和应用中,进程控制和调度是需要开发人员和系统管理员高度重视的内容。只有通过合理的管理和优化,才能保证系统的稳定性、高效性和安全性,满足用户对系统性能和服务质量的期望。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值