《深入linux内核架构》C2 进程管理与调度
前言
第二次阅读此书,给人的感觉是:经典的Linux内核书籍,越读越心醉。因而,不写点什么真对不住自己。
开门见山
现代操作系统(Linux、windows)基本都能处理多项任务(多任务系统),因而,此多任务的管理和调度是内核的基本功能,第二章就围绕此主体展开。相关主题如下:
进程优先级
Linux支持实时进程和非实时进程(普通进程)。一般的进程都是普通进程,而实时进程强调希望得到系统的快速响应与处理,因而有较高的优先级。应用程序可以通过nice系统调用修改程序的优先级。
#include <unistd.h>
int nice(int inc);
进程的生命周期
进程的生命周期大体可分为:运行、等待、睡眠等阶段,在内核中使用进程状态表示:
- R (TASK_RUNNING):可执行状态&运行状态(在run_queue队列里的状态);
- S (TASK_INTERRUPTIBLE):可中断的睡眠状态,可处理signal;
- D (TASK_UNINTERRUPTIBLE):不可中断的睡眠状态,可处理signal,有延迟,不能有外部信号唤醒,只能由内核亲自唤醒;
- T (TASK_STOPPED or TASK_TRACED):暂停状态或跟踪状态,不可处理signal,因为根本没有时间片运行代码;
- 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:网络命名空间。
命名空间为层级关系,父空间能查看所有子空间的内容,反过来不行。
- 进程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;
}
- 程序分析:
- 程序首先通过sched_getscheduler获取自身的调度策略;
- 之后,构建sched_param结构,通过sched_setscheduler修改自身的调度策略为FIFO(实时进程),且优先级为12;
- 最后,再次调用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进程的管理与调度(十七)
感谢---完。
上一篇: kernel(八)PWM 蜂鸣器
下一篇: Linux2.6内存布局