您的位置 首页 芯闻

详细分析Linux内核态的抢占机制

详细分析Linux内核态的抢占机制-非抢占式内核是由任务主动放弃CPU的使用权。非抢占式调度法也称作合作型多任务,各个任务彼此合作共享一个CPU。异步事件还是由中断服务来处理。中断服务可以使一个高优先级的任务由挂起状态变为就绪状态。但中断服务以后控制权还是回到原来被中断了的那个任务,直到该任务主动放弃CPU的使用权时,那个高优先级的任务才能获得CPU的使用权。

  本文首要介绍非抢占式内核(Non-Preemptive Kernel)和可抢占式内核(PreempTIve Kernel)的差异。接着剖析Linux下有两种抢占:用户态抢占(User PreempTIon)、内核态抢占(Kernel PreempTIon)。然后剖析了在内核态下:怎么判别能否抢占内核(什么是可抢占的条件);何时触发从头调度(何时设置可抢占条件);抢占产生的机遇(何时查看可抢占的条件);什么时候不能抢占内核。最终剖析了2.6kernel中怎么支撑抢占内核。

  1 非抢占式和可抢占式内核的差异

  为了简化问题,我运用嵌入式实时体系uC/OS作为比方。首要要指出的是,uC/OS只需内核态,没有用户态,这和Linux不一样。

  多使命体系中,内核担任办理各个使命,或许说为每个使命分配CPU时刻,而且担任使命之间的通讯。内核供给的根本服务是使命切换。调度(Scheduler),英文还有一词叫dispatcher,也是调度的意思。这是内核的主要职责之一,便是要决议该轮到哪个使命运转了。大都实时内核是依据优先级调度法的。每个使命依据其重要程度的不同被赋予必定的优先级。依据优先级的调度法指,CPU总是让处在安排妥当态的优先级最高的使命先运转。可是,终究何时让高优先级使命把握CPU的运用权,有两种不同的状况,这要看用的是什么类型的内核,是不行掠夺型的仍是可掠夺型内核。

  非抢占式内核

  非抢占式内核是由使命自动抛弃CPU的运用权。非抢占式调度法也称作协作型多使命,各个使命互相协作同享一个CPU。异步事情仍是由间断服务来处理。间断服务能够使一个高优先级的使命由挂起状况变为安排妥当状况。但间断服务今后控制权仍是回到本来被间断了的那个使命,直到该使命自动抛弃CPU的运用权时,那个高优先级的使命才干取得CPU的运用权。非抢占式内核如下图所示。

  非抢占式内核的长处有:

  间断呼应快(与抢占式内核比较);

  答应运用不行重入函数;

  简直不需求运用信号量维护同享数据。运转的使命占有CPU,不用忧虑被其他使命抢占。这不是肯定的,在打印机的运用上,仍需求满意互斥条件。

  非抢占式内核的缺陷有:

  使命呼应时刻慢。高优先级的使命现已进入安排妥当态,但还不能运转,要比及当时运转着的使命开释CPU。

  非抢占式内核的使命级呼应时刻是不确定的,不知道什么时候最高优先级的使命才干拿到CPU的控制权,彻底取决于应用程序什么时候开释CPU。

  抢占式内核

  运用抢占式内核能够确保体系呼应时刻。最高优先级的使命一旦安排妥当,总能得到CPU的运用权。当一个运转着的使命使一个比它优先级高的使命进入了安排妥当态,当时使命的CPU运用权就会被掠夺,或许说被挂起了,那个高优先级的使命马上得到了CPU的控制权。假设是间断服务子程序使一个高优先级的使命进入安排妥当态,间断完结时,间断了的使命被挂起,优先级高的那个使命开端运转。抢占式内核如下图所示。

  抢占式内核的长处有:

  运用抢占式内核,最高优先级的使命什么时候能够履行,能够得到CPU的运用权是可知的。运用抢占式内核使得使命级呼应时刻得以最优化。

  抢占式内核的缺陷有:

  不能直接运用不行重入型函数。调用不行重入函数时,要满意互斥条件,这点能够运用互斥型信号量来完结。假设调用不行重入型函数时,低优先级的使命CPU的运用权被高优先级使命掠夺,不行重入型函数中的数据有或许被损坏。

  2 Linux下的用户态抢占和内核态抢占

  Linux除了内核态外还有用户态。用户程序的上下文归于用户态,体系调用和间断处理例程上下文归于内核态。在2.6 kernel曾经,Linux kernel只支撑用户态抢占。

  2.1 用户态抢占(User PreempTIon)

  在kernel回来用户态(user-space)时,而且need_resched标志为1时,scheduler被调用,这便是用户态抢占。当kernel回来用户态时,体系能够安全的履行当时的使命,或许切换到别的一个使命。当间断处理例程或许体系调用完结后,kernel回来用户态时,need_resched标志的值会被查看,假设它为1,调度器会挑选一个新的使命并履行。间断和体系调用的回来途径(return path)的完结在entry.S中(entry.S不只包含kernel entry code,也包含kernel exit code)。

  2.2 内核态抢占(Kernel Preemption)

  在2.6 kernel曾经,kernel code(间断和体系调用归于kernel code)会一向运转,直到code被完结或许被堵塞(体系调用能够被堵塞)。在 2.6 kernel里,Linux kernel变成可抢占式。当从间断处理例程回来到内核态(kernel-space)时,kernel会查看是否能够抢占和是否需求从头调度。kernel能够在任何时刻点上抢占一个使命(由于间断能够产生在任何时刻点上),只需在这个时刻点上kernel的状况是安全的、可从头调度的。

  3 内核态抢占的规划

  3.1 可抢占的条件

  要满意什么条件,kernel才能够抢占一个使命的内核态呢?

  没持有。锁是用于维护临界区的,不能被抢占。

  Kernel code可重入(reentrant)。由于kernel是SMP-safe的,所以满意可重入性。

  怎么判别当时上下文(间断处理例程、体系调用、内核线程等)是没持有锁的?Linux在每个每个使命的thread_info结构中增加了preempt_count变量作为preemption的计数器。这个变量初始为0,当加锁时计数器增一,当解锁时计数器减一。

  3.2 内核态需求抢占的触发条件

  内核供给了一个need_resched标志(这个标志在使命结构thread_info中)来标明是否需求从头履行调度。

  3.3 何时触发从头调度

  set_tsk_need_resched():设置指定进程中的need_resched标志

  clear_tsk need_resched():铲除指定进程中的need_resched标志

  need_resched():查看need_ resched标志的值;假设被设置就回来真,不然回来假

  什么时候需求从头调度:

  时钟间断处理例程查看当时使命的时刻片,当使命的时刻片耗费完时,scheduler_tick()函数就会设置need_resched标志;

  信号量、比及行列、completion等机制唤醒时都是依据waitqueue的,而waitqueue的唤醒函数为default_wake_function,其调用try_to_wake_up将被唤醒的使命更改为安排妥当状况并设置need_resched标志。

  设置用户进程的nice值时,或许会使高优先级的使命进入安排妥当状况;

  改动使命的优先级时,或许会使高优先级的使命进入安排妥当状况;

  新建一个使命时,或许会使高优先级的使命进入安排妥当状况;

  对CPU(SMP)进行负载均衡时,当时使命或许需求放到别的一个CPU上运转;

  3.4 抢占产生的机遇(何时查看可抢占条件)

  当一个间断处理例程退出,在回来到内核态时(kernel-space)。这是隐式的调用schedule()函数,当时使命没有自动抛弃CPU运用权,而是被掠夺了CPU运用权。

  当kernel code从不行抢占状况变为可抢占状况时(preemptible again)。也便是preempt_count从正整数变为0时。这也是隐式的调用schedule()函数。

  一个使命在内核态中显式的调用schedule()函数。使命自动抛弃CPU运用权。

  一个使命在内核态中被堵塞,导致需求调用schedule()函数。使命自动抛弃CPU运用权。

  3.5 禁用/使能可抢占条件的操作

  对preempt_count操作的函数有add_preempt_count()、sub_preempt_count()、inc_preempt_count()、dec_preempt_count()。

  使能可抢占条件的操作是preempt_enable(),它调用dec_preempt_count()函数,然后再调用preempt_check_resched()函数去查看是否需求从头调度。

  禁用可抢占条件的操作是preempt_disable(),它调用inc_preempt_count()函数。

  在内核中有许多函数调用了preempt_enable()和preempt_disable()。比方spin_lock()函数调用了preempt_disable()函数,spin_unlock()函数调用了preempt_enable()函数。

  3.6 什么时候不答应抢占

  preempt_count()函数用于获取preempt_count的值,preemptible()用于判别内核是否可抢占。

  有几种状况Linux内核不该该被抢占,除此之外,Linux内核在恣意一点都可被抢占。这几种状况是:

  内核正进行间断处理。在Linux内核中进程不能抢占间断(间断只能被其他间断间断、抢占,进程不能间断、抢占间断),在间断例程中不答应进行进程调度。进程调度函数schedule()会对此作出判别,假设是在间断中调用,会打印犯错信息。

  内核正在进行间断上下文的Bottom Half(间断的下半部)处理。硬件间断回来前会履行软间断,此刻依然处于间断上下文中。

  内核的代码段正持有spinlock自旋锁、writelock/readlock读写锁等锁,处干这些锁的维护状况中。内核中的这些锁是为了在SMP体系中短时刻内确保不同CPU上运转的进程并发履行的正确性。当持有这些锁时,内核不该该被抢占,不然由于抢占将导致其他CPU长时间不能取得锁而死等。

  内核正在履行调度程序Scheduler。抢占的原因便是为了进行新的调度,没有理由将调度程序抢占掉再运转调度程序。

  内核正在对每个CPU“私有”的数据结构操作(Per-CPU date structures)。在SMP中,关于per-CPU数据结构未用spinlocks维护,由于这些数据结构隐含地被维护了(不同的CPU有不一样的per-CPU数据,其他CPU上运转的进程不会用到另一个CPU的per-CPU数据)。可是假设答应抢占,但一个进程被抢占后从头调度,有或许调度到其他的CPU上去,这时界说的Per-CPU变量就会有问题,这时应禁抢占。

  4 Linux内核态抢占的完结

  4.1 数据结构

  [cpp] view plain copy

  struct thread_info {

  struct task_struct *task; /* main task structure */

  struct exec_domain *exec_domain; /* execution domain */

  /**

  * 假设有TIF_NEED_RESCHED标志,则有必要调用调度程序。

  */

  unsigned long flags; /* low level flags */

  /**

  * 线程标志:

  * TS_USEDFPU:表明进程在当时履行过程中,是否运用过FPU、MMX和XMM寄存器

  */

  unsigned long status; /* thread-synchronous flags */

  /**

  * 可运转进程地点运转行列的CPU逻辑号。

  */

  __u32 cpu; /* current CPU */

  __s32 preempt_count; /* 0 =》 preemptable, 《0 =》 BUG */

  mm_segment_t addr_limit; /* thread address space:

  0-0xBFFFFFFF for user-thead

  0-0xFFFFFFFF for kernel-thread

  */

  struct restart_block restart_block;

  unsigned long previous_esp; /* ESP of the previous stack in case

  of nested (IRQ) stacks

  */

  __u8 supervisor_stack[0];

  };

  4.2 代码流程

  禁用/使能可抢占条件的函数

  [cpp] view plain copy

  #ifdef CONFIG_DEBUG_PREEMPT

  extern void fastcall add_preempt_count(int val);

  extern void fastcall sub_preempt_count(int val);

  #else

  # define add_preempt_count(val) do { preempt_count() += (val); } while (0)

  # define sub_preempt_count(val) do { preempt_count() -= (val); } while (0)

  #endif

  #define inc_preempt_count() add_preempt_count(1)

  #define dec_preempt_count() sub_preempt_count(1)

  /**

  * 在thread_info描述符中挑选preempt_count字段

  */

  #define preempt_count() (current_thread_info()-》preempt_count)

  #ifdef CONFIG_PREEMPT

  asmlinkage void preempt_schedule(void);

  /**

  * 使抢占计数加1

  */

  #define preempt_disable() \

  do { \

  inc_preempt_count(); \

  barrier(); \

  } while (0)

  /**

  * 使抢占计数减1

  */

  #define preempt_enable_no_resched() \

  do { \

  barrier(); \

  dec_preempt_count(); \

  } while (0)

  #define preempt_check_resched() \

  do { \

  if (unlikely(test_thread_flag(TIF_NEED_RESCHED))) \

  preempt_schedule(); \

  } while (0)

  /**

  * 使抢占计数减1,并在thread_info描述符的TIF_NEED_RESCHED标志被置为1的状况下,调用preempt_schedule()

  */

  #define preempt_enable() \

  do { \

  preempt_enable_no_resched(); \

  preempt_check_resched(); \

  } while (0)

  #else

  #define preempt_disable() do { } while (0)

  #define preempt_enable_no_resched() do { } while (0)

  #define preempt_enable() do { } while (0)

  #define preempt_check_resched() do { } while (0)

  #endif

  设置need_resched标志的函数

  [cpp] view plain copy

  static inline void set_tsk_need_resched(struct task_struct *tsk)

  {

  set_tsk_thread_flag(tsk,TIF_NEED_RESCHED);

  }

  static inline void clear_tsk_need_resched(struct task_struct *tsk)

  {

  clear_tsk_thread_flag(tsk,TIF_NEED_RESCHED);

  }

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/news/xinwen/90149.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部