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

前言

         第二次阅读此书,给人的感觉是:经典的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的抢占式多任务处理:

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

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

进程的表示

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

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

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

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

扫描二维码关注公众号,回复: 12690910 查看本文章
  • 进程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创建一个内核线程用于进程迁移,如下:

一个简单的应用程序

         以下写个简单的代码,查看/修改应用程序自身的调度类与调度策略,以便对相关调度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权限):

相关数字(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进程的管理与调度(十七)

  感谢---完。

猜你喜欢

转载自blog.csdn.net/zhaogang1993/article/details/92012405