Linux调度与进程/线程的创建

  • 在Linux里面,进程可以分成两种:一种为实时进程,需要尽快执行返回结果;另一种是普通进程。
  • 在task_struct中,有一个成员变量叫调度策略。优先级配合调度策略,优先级就是一个数值,对于实时进程,优先级的范围是0~99;对于普通进程,优先级的范围是100~139。数值越小,优先级越高。

  • 对于实时进程调度策略SCHED_FIFO、SCHED_RR、SCHED_DEADLINE:
      SCHED_FIFO:高优先级的进程可以抢占低优先级的进程,而相同优先级的进程,遵循先来先得。
      SCHED_RR:轮流调度,采用时间片,相同优先级的任务当用完时间片会被放到队列尾部,以保证公平性,而高优先级的任务也是可以抢占低优先级的任务。
      SCHED_DEADLINE:按照任务的deadline进行调度,当产生一个调度点时,调度器总是选择其deadline距离当前时间点最近的那个任务,并调度它执行。

  • 对于普通进程的调度策略SCHED_NORMAL、SCHED_BATCH、SCHED_IDLE:
      SCHED_NORMAL:普通的进程。
      SCHED_BATCH:后台进程,几乎不需要和前端进行交互,这类进程可以默默执行,不要影响需要交互的进程,可以降低它的优先级。
      SCHED_IDLE:是特别空闲的时候才跑的进程。

  • 调度策略的执行逻辑,就封装在sched_class中:
      stop_sched_class:优先级最高的任务会使用这种策略,会中断所有其他线程,且不会被其他任务打断。
      dl_sched_class:对应deadline调度策略。
      rt_sched_class:对应RR或FIFO调度策略。
      fair_sched_class:普通进程的调度策略。
      idle_sched_class:空闲进程的调度策略。

  • 对于普通进程,Linux中实现了一个基于CFS(Completely Fair Scheduling,完全公平调度)的调度算法。CFS会为每一个进程安排一个虚拟运行时间vruntime。如果一个进程在运行,随着时间的增长,进程的vruntime将不断增大,没有得到执行的进程的vruntime不变。
  • vruntime经常变化,CFS使用红黑树来对vruntime进行排序,因为红黑树在需要查询的时候,能够快速找到最小的vruntime,更新的时候也能够快速地调整排序。能够平衡查询和更新速度的数据结构是树。

  • 包含vruntime的红黑树的节点称为调度实体。运行的进程通过不断地插入操作最终都存储在以时间为顺序的红黑树中,vruntime最小的在树的左侧,vruntime最多的在树的右侧。CFS调度策略会选择红黑树最左边的叶子节点作为下一个将获得CPU的任务。这个红黑树在CPU看起来就是一个队列,不断地取下一个应该运行的进程。
  • 在每个CPU上都有一个队列rq,这个队列里面包含多个子队列,如rt_rq和cfs_rq,不同的队列有不同的实现方式,cfs_rq就是用红黑树实现的。当某个CPU需要下一个任务执行时,会按照优先级依次调用调度类,不同的调度类操作不同的队列,其中rt_sched_class先被调用,它会在rt_rq上找下一个任务,只有找不到的时候,才轮到fair_sched_class被调用,它会在cfs_rq上找下一个任务,这样保证了实时任务的优先级永远大于普通任务。

  • 调度的方式:主动式调度和抢占式调度。

  • 主动调度的过程,也即一个运行中的进程主动调用__schedule函数让出CPU。在__schedule里会做两件事情:第一是选取下一个进程,第二是进行上下文切换。上下文切换又分用户态进程空间(也即虚拟内存)的切换和内核态的切换。

  • 发生抢占式调度最常见的情况:一个进程执行时间太长,需要切换到另一个进程。另一个可能发生抢占式调度的场景:当一个进程被唤醒的时候。
  • 用户态的抢占时机:
      对于用户态的进程来讲,从系统调用中返回的那个时刻,是一个被抢占的时机;
      对于用户态的进程来讲,从中断中返回的那个时刻,也是一个被抢占的时机。

  • 内核态的抢占时机:
      在内核态的执行中,有的操作是不能被中断的,所以在进行这些操作之前,总是先关闭抢占,当再次打开的时候,就是一次内核态代码被抢占的机会;
      在内核态也会遇到中断的情况,当中断返回后,返回的仍然是内核态,此时也是一个执行抢占的时机。
  • fork是一个系统调用,使用fork创建进程的这个动作在内核里做的事情:
      A.将task_struct结构复制一份并且初始化。
      B.试图唤醒新创建的子进程。

  • 创建进程调用的系统调用是fork,在copy_process函数里面,会将五大结构files_struct(维护进程打开文件信息)、fs_struct(维护进程目录信息结构)、sighand_struct(维护信号处理函数结构)、signal_struct(维护进程信号结构)、mm_struct(进程内存空间结构)都复制一遍,父进程和子进程各用各的数据结构。

  • 创建线程的话,调用的是系统调用clone,在copy_process函数里面,五大结构仅仅是引用计数加一,也即线程共享进程的数据结构。
原文地址:https://www.cnblogs.com/kongzimengzixiaozhuzi/p/13284498.html