线程调度

来源:互联网 发布:琼斯镇惨案 知乎 编辑:程序博客网 时间:2024/05/17 23:17

线程调度

   内核调度器是基于优先级实现的,他可以使应用程序线程共享CPU

相关概念

   在每一个时间点调度器都会决定执行那个线程,这个被执行的线程有个统称叫“当前线程”
   无论什么时候,只要调度器改变了当前线程的ID或者当前线程被中断服务函数打断,内核都会先保存当前线程的寄存器值,这些寄存器的值在线程被重新执行时恢复

线程状态

   一个已经是就绪状态的线程是不会被阻止其运行的,并有可能被调度器选择为当前线程
   一个线程有一个或多个因素阻止一个未就绪的线程运行,未就绪的线程是不可以被选择诶当前线程的
   下面几个因素可使一个线程为未就绪态:
   ①线程没有启动
   ②线程等待一个内核对象完成一个指定操作(比如线程在等待一个还不可用的信号量)
   ③线程在等待超时出现
   ④现场被挂起
   ⑤线程被同步终止或异步中止

线程优先级

   线程优先级是一个整数值,可以是一个负整数或者正整数。数值小的优先级比数值大的优先级要高。比如,优先级是4的线程A要比优先级7的线程B优先级高,优先级为-2的线程C要比线程A和线程B的优先级都高
   调度器通过线程优先级来区分这两类线程
   ①协作式线程优先级是一个负数。如果协作式线程变成了当前线程,它会一直占用CPU直到这个线程被某个操作指定为未就绪状态
   ②抢占式线程优先级是一个正数。如果抢占式线程变成了当前线程,任何时候他都会被就绪的协作式线程替代,或者是一个优先级大于等于它并且处于就绪态的抢占式线程替代
   线程的优先级在线程启动后可以被动态上调和下调。因此一个抢占式线程通过修改它的优先级可能会变成一个协作式线程
   内核支持一个几乎无线数量的优先级级别,配置项CONFIG_NUM_COOP_PRIORITIES和CONFIG_NUM_PREEMPT_PRIORITIES分别为两类线程指定优先级级别个数,最后的设置优先级范围情况如下
   协作式线程:(-CONFIG_NUM_COOP_PRIORITIES)到 -1
   抢占式线程:0 到 (CONFIG_NUM_PREEMPT_PRIORITIES - 1)
   例如,设置CONFIG_NUM_COOP_PRIORITIES = 5 和 CONFIG_NUM_PREEMPT_PRIORITIES = 0时,协作式线程优先级范围是 -5 到 -1,抢占式线程的优先级范围是 0 到 9

调度算法

   内核调度器会在就绪态线程中选择优先级最高的线程作为当前线程。当同时有多个相同优先级的线程处于就绪态时,调度器会选择等待时间最长的那个线程作为当前线程
   note:中断服务函数的优先级要高于线程的优先级,所以如果中断没有被关闭的话,线程在任何时候都有可能被中断服务函数打断。无论是协作式线程还是抢占式线程都是如此

协作时间片

    当一个协作式线程变成了当前线程,他会一直占用CPU到这个协作式线程变成未就绪状态。因此,如果协作式线程需要执行很长时间,其他线程有可能会因此延时一段不可接受的时间,包括那些优先级大于等于它的线程
   为了解决这个问题,协作式线程会主动让出CPU一段时间来让其他线程执行。一个线程可以通过两种方式让出CPU
   ①通过调用k_yield()把当前线程放到调度器就绪线程优先表之后,然后调用调度器。所有优先级大于等于yield线程的就绪线程在yield线程重新调用之前都有可能被执行。如果没有优先级符合的线程存在,调度器会立即重新调度yield线程,不进行上下文切换
   ②通过调用k_sleep()使线程睡眠一段指定的时间,所有的就绪线程都可以被执行。然而,这样也不会保证在sleeping线程变成就绪态之前那些优先级小的线程会被调度

抢占时间片

   当一个抢占式线程变成当前线程时,在一个优先级高的线程变成就绪态之前会一直保持为当前线程,或者线程执行一个动作使他变成未就绪状态。因此,如果抢占式线程需要执行很长时间,其他线程有可能会因此延时一段不可接收的时间,包括那些优先级相等的线程
   为了解决这个问题,抢占式线程可以执行一个协作时间片(如上面描述那样),或者通过调度器时间片让具有相同优先级的线程被调度
   调度器把时间分成一系列时间片,这个时间片是以系统时钟为基准的。时间片长短是可配置的,而且这个时间大小在程序运行时也是可变的
   在每个时间片的结尾,调度器都会查看当前线程是否是可抢占的,如果是可抢占线程,将会自动调用k_yield()。这样在当前线程被重新调度之前其他具有相同优先级的就绪线程有可能会被调度执行。如果没有相同优先级的就绪线程,那么调度器就不会做线程调度切换
   如果一个线程优先级比设置的优先级范围高的话,那么这个线程就不会有抢占时间片,所以就永远不会被相同优先级的就绪线程抢占。这样应用程序就可以在处理对时间不敏感的低优先级线程时利用抢占时间片来做一些操作(也就是说把节省下来的抢占时间片用来处理其他操作)
   note:内核时间片算法不能保证一些具有相同优先级的线程获得相等的CPU执行时间,因为内核不会测量每个线程实际执行了多长时间。比如,一个线程可能在时间片尾端变成当前线程然后立即让出CPU。然而,调度算法也不会确保线程执行时间因为没让出CPU而超过一个时间片

调度器上锁

   一个抢占式线程如果在处理一个临界操作时不想被抢占,可以通过调用k_sched_unlock()接口实现,这样调度器会把抢占式线程当做协作式线程而不被抢占。这样线程在在执行临界操作时就不会被其他线程干涉了
   当临界操作执行完毕后线程必须调用k_sched_unlock()恢复到正常的抢占式线程状态
   如果一个线程在执行k_sched_lock()后又执行了一个操作是自己变成未就绪状态,调度器会切换出上锁线程并且调度其他线程运行。当上锁线程重新变成当前线程,会保持原有的不可抢占状态

线程睡眠

   线程可以通过调用k_sleep()把当前操作延时一个指定时间段。在这段时间内会让出CPU让其他线程运行。当指定的延时时间到达时线程会变成就绪态并会被重新调度
   一个处于睡眠态的线程可被其他线程通过调用k_wakeup()唤醒。这种机制可在某些事件发生时通过二次线程来通知一个睡眠线程,而不是通过线程定义一个内核异步对象实现(比如信号量)。如果一个线程没在睡眠状态也是可以被唤醒的,只不过不会产生任何效果

忙等待

   一个线程可以通过调用k_busy_wait()执行一次忙等待、延时一个指定的时间段,在延时期间线程不会让出CPU
   当一个线程延时时间太小时就会调用线程忙等待(busy wait)而不是调用线程睡眠(sleeping),因为这个延时的时间已经比调度器从当前线程切换到其他线程再切换回来需要花费的时间还要短

推荐用法

   使用协作式线程处理设备驱动程序和其他有性能要求的工作
   使用协作式线程实现具有互斥关系且不需要内核对象的功能,这个内核对象指的是互斥锁等
   通过抢占式线程,可以给一个时间敏感的操作高优先级而给一个时间不敏感的操作一个低优先级

配置选项

   相关的配置选项如下:
  • CONFIG_NUM_COOP_PRIORITIES
  • CONFIG_NUM_PREEMPT_PRIORITIES
  • CONFIG_TIMESLICING
  • CONFIG_TIMESLICE_SIZE
  • CONFIG_TIMESLICE_PRIORITY

APIs

   下面是在kernel.h中定义的和线程调度相关的APIs
  • k_current_get()
  • k_sched_lock()
  • k_sched_unlock()
  • k_yield()
  • k_sleep()
  • k_wakeup()
  • k_busy_wait()
  • k_sched_time_slice_set()
原创粉丝点击