欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

《深入linux内核架构》C2 进程管理与调度

程序员文章站 2022-05-29 09:11:32
...

前言

         第二次阅读此书,给人的感觉是:经典的Linux内核书籍,越读越心醉。因而,不写点什么真对不住自己。

开门见山

         现代操作系统(Linux、windows)基本都能处理多项任务(多任务系统),因而,此多任务的管理和调度是内核的基本功能,第二章就围绕此主体展开。相关主题如下:

进程优先级

Linux支持实时进程和非实时进程(普通进程)。一般的进程都是普通进程,而实时进程强调希望得到系统的快速响应与处理,因而有较高的优先级。应用程序可以通过nice系统调用修改程序的优先级

#include <unistd.h>
int nice(int inc);

进程的生命周期

         进程的生命周期大体可分为:运行、等待、睡眠等阶段,在内核中使用进程状态表示:

  1. R (TASK_RUNNING):可执行状态&运行状态(在run_queue队列里的状态);
  2. S (TASK_INTERRUPTIBLE):可中断的睡眠状态,可处理signal;
  3. D (TASK_UNINTERRUPTIBLE):不可中断的睡眠状态,可处理signal,有延迟,不能有外部信号唤醒,只能由内核亲自唤醒;
  4. T (TASK_STOPPED or TASK_TRACED):暂停状态或跟踪状态,不可处理signal,因为根本没有时间片运行代码;
  5. Z (TASK_DEAD - EXIT_ZOMBIE):退出状态,进程成为僵尸进程。不可被kill, 即不响应任务信号, 无法用SIGKILL杀死.

以上状态在内核中用各种宏定义(sched.h),在调试嵌入式程序时非常有用。如查看系统mysqld守护进程的状态

《深入linux内核架构》C2 进程管理与调度

其外,文章介绍了Linux的抢占式多任务处理:

        进程的两种状态选项:用户态、内核态。内核态具有无限的权利,用户态受到各种限制。用户态切换到内核态的两种方法:系统调用、中断。系统调用是应用程序有意的,中断是自动触发的。

内核抢占:紧急情况下切换到另一个进程,甚至当前是处于内核态执行系统调用(中断不能被抢占)。

进程的表示

  • 内核使用tast_struck结构体表示进程(线程),内容太长,略。
  • 命名空间namespace

       这里要重点提下,现阶段热门的docker等虚拟性技术大多基于Linux的命名空间(还一种技术是Control group),其为应用程序提供源生态的内核虚拟化支持。目前,内核支持6中命名空间:

UTS:UNIX timesharing system;
IPC:进程间通信;
mnt:文件系统装载;
PID:进程id管理;
user:用户管理
net:网络命名空间。

命名空间为层级关系,父空间能查看所有子空间的内容,反过来不行。

  • 进程PID管理

       内核有一套完整的PID管理与分配方案,加入了PID 命名空间后,又存在全局PID和局部PID之分,此部分相当复杂,建议深入阅读分析,此处从简。

进程管理相关的系统调用

         用户态程序可以使用fork、clone、vfork等系统调用产生新的进程/线程,然后使用exec函数族进行进程替换(ELF文件,替换原有进程的各个段)。

调度器的实现

    进程的调度**有两种方式:周期性调度器主调度器。其中,周期性调度器由系统时钟中断触发,定期扫描调度队列上是否有需要运行的程序;主调度器则采用schedule函数主动放弃当前进程的运行,使CPU选择新的进程运行。

在调度顺序上,所有可运行的进程按照时间在一个红黑树中排序,最左侧的节点等待时间最长,最先被得到调度。红黑树是内核的一种标准数据结构,读者可以在rbtree.c中查看。

       此外,内核采用模块化的设计分离调度器与调度类,调度器只负责选择下一个待运行的进程,而真正的进程管理功能由调度器类实现。内核支持两种调度器类(4.14源码中有5个调度器类):完全公平调度类、实时调度类。不同的调度类又有不同的调度策略,如下:

调度类

描述

对应调度策略

stop_sched_class

优先级最高的线程,会中断所有其他线程,且不会被其他任务打断作用

1.发生在cpu_stop_cpu_callback 进行cpu之间任务migration

2.HOTPLUG_CPU的情况下关闭任务

……

dl_sched_class

采用EDF最早截至时间优先算法调度实时进程

……

rt_sched_class

采用提供 Roound-Robin算法或者FIFO算法调度实时进程,具体调度策略由进程的task_struct->policy指定

SCHED__RR:循环进程

SCHED_FIFO:先进先出

fair_sched_clas

采用CFS算法调度普通的非实时进程

SCHED_BATCH

SCHED_NORMAL

idle_sched_class

采用CFS算法调度idle进程, 每个cup的第一个pid=0线程:swapper,是一个静态线程。调度类属于:idel_sched_class,所以在ps里面是看不到的。一般运行在开机过程和cpu异常的时候做dump

……

调度器类由sched_class表示,通过指针相互串联。如:

const struct sched_class fair_sched_class = {

         .next                            = &idle_sched_class,

应用程序可以调用:sched_getscheduler、sched_setscheduler等系统调用查看和修改进程调度方式与调度策略。

  • 完全公平调度类:fair_sched_class
  • 实时调度类:rt_sched_class

调度器增强

         调度器增强讲诉在SMP(Symmetrical Multi-Processing)系统上关于调度系统的扩展,它涉及CPU间的负载均衡CPU间的进程迁移、按照调度域或控制组整体调度等。Linux系统为每个CPU创建一个内核线程用于进程迁移,如下:

《深入linux内核架构》C2 进程管理与调度

一个简单的应用程序

         以下写个简单的代码,查看/修改应用程序自身的调度类与调度策略,以便对相关调度API有个大致的认识

ULONG cvtest_Test4_Sched()
{
    INT iSched;
    INT iRet;
    struct sched_param stSched;
    memset(&stSched, 0, sizeof(stSched));
    
    iSched = sched_getscheduler(0);
    printf("Before : %d \n", iSched);

    /* set to FIFI sched, must be root */
    stSched.__sched_priority = 12;
    iRet = sched_setscheduler(0, SCHED_FIFO, &stSched);
    if (0 != iRet)
    {
        return ERROR_FAILED;
    }
    
    iSched = sched_getscheduler(0);
    memset(&stSched, 0, sizeof(stSched));
    sched_getparam(0, &stSched);
    printf("After : %d, %d \n", iSched, stSched.__sched_priority);
    return ERROR_SUCCESS;
}
  • 程序分析:
  1. 程序首先通过sched_getscheduler获取自身的调度策略;
  2. 之后,构建sched_param结构,通过sched_setscheduler修改自身的调度策略为FIFO(实时进程),且优先级为12;
  3. 最后,再次调用sched_getscheduler查看自身的调查策略。
  • 程序执行结果(需要root权限):

《深入linux内核架构》C2 进程管理与调度

相关数字(0 和 1)即表示内核中调度策略:

/* Scheduling algorithms.  */
#define SCHED_OTHER		0
#define SCHED_FIFO		1
#define SCHED_RR		2
#ifdef __USE_GNU
# define SCHED_BATCH		3
# define SCHED_IDLE		5

总结

         进程调度是OS的基础之基础,本文只是通过书本讲诉简要的描述了调度的框架,可谓冰山一角。读者若感兴趣,可以阅读内核相关源码。很多博主对此类主题也有深刻的解析,可以参阅,如:

Linux进程调度器的设计--Linux进程的管理与调度(十七)

  感谢---完。

相关标签: kernel