Linux课程学习总结报告

进程调度

程序员编写的程序要想获得运行,必须首先把静态的程序变成一个个动态的进程,进程因创建而产生,因调度而执行,因撤销而消亡,这便是一个进程的一个生命周期。在电脑的内存中,有着成千上万的进程,而cpu仅仅只有一个,那该如何管理这些进程完成进程的调度呢?

首先从进程的管理谈起,进程由三部分构成——进程控制块PCB,数据段和代码段,操作系统正是通过管理进程的PCB来实现管理进程的。进程的PCB本质上是一个结构体数组,包含有这个进程的所有信息,操作系统可以对外提供相应的函数去改变进程PCB中相应的域。进程的调度和PCB中的很多域相关,比如关于时间片计数器counter,进程优先级priority,进程调度策略policy以及指向可运行队列的下一个和前一个PCB的指针*next_run和*prev_run.

linux进程的调度依赖于一个数据结构——可运行队列,本质上也是一个结构体数组,其组成结构如下图所示:

image-20200708204656587

先来解释下这个可运行队列,可运行队列包含有活动进程可运行队列active和过期可运行队列expired,他们分别指向一个prio_array_t结构体类型变量的地址,这个prio_array_t结构体包含有三部分,分别是代表队列中PCB数量的nr_active,代表每一个优先级队列中是否有PCB的bitmap,代表140个优先级队列的queue。

linux的进程调度算法具有优先级、时间片、可剥夺、先来先服务四大特点。下面依次来谈。

  • 优先级:从可运行队列的图可以看出,linux把进程分为了140个优先级,其中实时性的进程占据0-99号优先级,普通进程占据100-139号优先级,优先级的等级越低,表示优先级越高,也就越容易被cpu调度。
  • 时间片:linux的优先级采用的动态优先级,也就是说一个进程被创建后,会分配一个静态优先级,随着进程的执行,此进程的优先级会不断提高。而进程分配到的时间片恰恰跟进程创建之初的静态优先级有关系,静态优先级越高,所分配到的时间片也就越多,也就是说0号优先级队列上的每一个进程分配到的时间片最多。
  • 可剥夺:当有优先级更高的进程到来时,会剥夺正在运行的进程的cpu。其中实时性任务总是会剥夺普通任务。
  • 先来先服务:每一个优先级队列上的进程调度,按照先来先服务原则进行调度。

实时进程和普通进程采取的linux调度算法是不同的。

  • 实时进程:
    • 先进先出算法,实时进程的执行不会被剥夺,总是先来的实时进程先执行完。
    • 时间片轮转算法,实时任务时间片用完之后,会重新回到该优先级队列的尾部等待再一次的调度,仅仅可被同等优先级的进程剥夺cpu。
  • 普通进程:
    • 可剥夺的动态优先级的时间片轮转算法。
      • 交互类进程,时间片用完之后仍然会在active指向的活动进程可运行队列。(有特殊情况,不讨论)
      • 批处理类进程,时间片用完之后会回到expired指向的过期进程可运行队列。

有了上面知识的铺垫,接下来谈谈具体的linux进程调度过程:

  1. 查看下可运行队列active指向的活动进程可运行队列的nr_active的值,如果为0,交换active和expired两个指针,活动进程可运行队列变为过期进程可运行队列,过期进程可运行队列变为活动进程可运行队列,进行下一步。
  2. 查看active指向的活动进程可运行队列的bitmap,找到第一个不为0相应的优先级队列是几号。
  3. 找到相应的优先级队列开始按照先来先服务的原则调度该优先级队列。
  4. 如果有更高优先级的进程到来,剥夺正在运行的进程的cpu
  5. 当一个进程时间片用完后,把该进程放到相应的优先级队列中,参考实时进程和普通进程采取的linux调度算法。

中断和异常

linux操作系统除了能够正常的调度进程的执行外,还应该具备有响应中断事件的能力。为了解决cpu的高速运行和I/O设备的低速处理之间的矛盾,linux提供了中断机制:当对外部设备发出读或者写命令后,cpu不用等待外部设备准备数据,而是继续调度执行就绪进程,只有当外部设备准备好数据后,才会向cpu发出中断信号,cpu此时就会改变程序流,立即去响应并处理中断事件,过程如图所示:

image-20200709134149987

在linux中,每个中断和异常由0~255之间的一个数(8位)来标识,称之为中断向量。其中0~31号的中断向量分配给了异常处理,128号中断向量分配给了系统调用,剩下的中断向量分配给了外部中断。中断和异常是广义上中断的两个分类,此时狭义上的中断指的是外部中断,由硬件触发的中断,比如时钟中断,I/O中断等。异常是由软件触发的中断,比如缺页异常,除0异常等。

linux处理外部中断过程如下:

  1. 在每一条指令执行周期结束后cpu都会查看cpu的INTR引脚上是否有中断信号,如果电平发生变化,说明有中断产生;

  2. cpu读取「中断控制器」中数据端口中的「中断向量」;

  3. cpu读取「IDTR寄存器」,找到「中断描述符表IDT」,根据中断向量号找到相应的表项;

  4. 根据表项中相关位上的数据进行安全检查,查看此次中断是否合法;

  5. 检查完后,若合法,进行硬件级别的保护现场工作。

    • 关中断

    • 将ss,esp,eflags,cs,eip寄存器中的值依次进入「被中断进程的内核栈」。

    • 开中断

  6. 进行软件级别的保护现场工作

    • 将中断向量号入栈
    • 执行SAVE_ALL,按照pt_regs结构保存现场
  7. 调用do_IRQ函数,执行中「断处理程序」;

    • 中断处理程序会执行多个「中断服务例程」
  8. 执行ret_from_intr函数,进行中断返回和恢复现场。

linux处理异常的过程如下:

  1. 当发生异常时,进行硬件级别的保护现场

    • 关中断

    • 将ss,esp,eflags,cs,eip寄存器中的值依次进入「被中断进程的内核栈」。

    • 开中断

  2. 将出错码入栈和相应的c函数地址入栈

  3. 执行error_code函数,按照pt_regs结构进一步保存现场

  4. 把堆栈地址中的do_handler_name()函数的地址装入edi寄存器,并在这个位置写入fs值

  5. 执行call *%edi指令

  6. 执行ret_from_exception函数,进行中断返回和恢复现场。

在进行异常和中断处理时,保护现场时的内核栈情况如下入所示:

image-20200709143031993
posted @ 2020-07-09 20:45  如魚飲水  阅读(292)  评论(0编辑  收藏  举报