深入理解linux内核架构第二章 2.5-2.8

2.5 调度器的实现

schedule函数是理解调度操作的起点(实际进行调度的函数)
linux完全公平调度器没有时间片概念,只考虑等待时间,等待时间最长的优先运行
在这里插入图片描述
所有的可运行进程都按时间在一个红黑树中排序,所谓时间即其等待时间。等待CPU时间最长的
进程是最左侧的项,调度器下一次会考虑该进程。等待时间稍短的进程在该树上从左至右排序。
调度器使用虚拟时钟估测每个进程在完美并发的CPU上应该运行多少时间(虚拟时间流逝的速度是实际时间的1/N, N是当前进程数)
红黑树上的节点,使用fair_clock - wait_time排序

两种方法激活调度

1)进程睡眠或者yield
2)周期性调度(时钟中断)

优先级

task_struct中有关优先级的字段

  • static_prio: 程序启动时的优先级
  • normal_prio:根据static_prio计算出的动态优先级
  • prio:实际优先级,用于某些情况下内核需要提升优先级
  • rt_priority:实时进程优先级

内核不是根据进程调度,而是根据**可调度实体(sched_entity)**为单位调度,这样可以实现组调度。每个被调度的单位内嵌一个sched_entity
policy:当前进程的调度策略

  • CFS: normal,batch,idle
  • 实时:rr,fifo
    每个调度器都实现一个调度器类sched_class(实际是一个接口)
struct sched_class { 
	const struct sched_class *next; 
	void (*enqueue_task) (struct rq *rq, struct task_struct *p, int wakeup); 
	void (*dequeue_task) (struct rq *rq, struct task_struct *p, int sleep); 
	void (*yield_task) (struct rq *rq); 
	void (*check_preempt_curr) (struct rq *rq, struct task_struct *p); 
	struct task_struct * (*pick_next_task) (struct rq *rq); 
	void (*put_prev_task) (struct rq *rq, struct task_struct *p); 
	void (*set_curr_task) (struct rq *rq); 
	void (*task_tick) (struct rq *rq, struct task_struct *p); 
	void (*task_new) (struct rq *rq, struct task_struct *p); 
};

每个CPU都有一个就绪队列实例(struct rq)

struct rq { 
	unsigned long nr_running; 
	#define CPU_LOAD_IDX_MAX 5 
	unsigned long cpu_load[CPU_LOAD_IDX_MAX]; 
	... 
	struct load_weight load; 
	struct cfs_rq cfs; 
	struct rt_rq rt; 
	struct task_struct *curr, *idle; 
	u64 clock; 
... 
};

系统的所有就绪队列都在runqueues数组中,该数组的每个元素分别对应于系统中的一个CPU。
调度实体

struct sched_entity { 
	struct load_weight load; /* 用于负载均衡 */ 
	struct rb_node run_node; 
	unsigned int on_rq; 
	u64 exec_start; 
	u64 sum_exec_runtime; 
	u64 vruntime; 
	u64 prev_sum_exec_runtime; 
	... 
}
处理优先级

在用户空间可以通过nice命令设置进程的静态优先级(-20到19之间)
值越低,表明优先级越高
内核使用一个简单些的数值范围,从0到139(包含),用来表示内部优先级。同样是值越低,优
先级越高。从0到99的范围专供实时进程使用。nice值[-20, +19]映射到范围100到139,
进程每降低一个nice值,则多获得10%的CPU时间,每升高一个nice值,则放
弃10%的CPU时间。内核将优先级转换为权重值

static const int prio_to_weight[40] = { 
/* -20 */ 88761, 71755, 56483, 46273, 36291, 
/* -15 */ 29154, 23254, 18705, 14949, 11916, 
/* -10 */ 9548, 7620, 6100, 4904, 3906, 
/* -5 */ 3121, 2501, 1991, 1586, 1277, 
/* 0 */ 1024, 820, 655, 526, 423, 
/* 5 */ 335, 272, 215, 172, 137, 
/* 10 */ 110, 87, 70, 56, 45, 
/* 15 */ 36, 29, 23, 18, 15, 
};

实时进程的权重是普通进程的两倍
在这里插入图片描述

核心调度器

周期性调度器在scheduler_tick中实现,他不能直接切换进程,而要设置当前进程的TIF_NEED_RESCHED标志,随后在内核的许多地方,会检查这个标志(例如从系统调用返回之后) 如果这个标志置位,则内核调用核心调度器(schedule)切换进程并清空标志位。

与fork的交互

fork会调用sched_fork让调度器初始化这个进程(可以理解为向调度器注册这个新进程)
然后调用wake_up_new_task时会将新进程加入就绪队列中

上下文切换

由context_switch函数实现,它会调用所需的特定于体系结构的方法
主要两件事情:
1)switch_mm更换通过task_struct->mm描述的内存管理上下文。加载页表、刷出地址转换后备缓冲器(部分或全部)、向内存管理单元(MMU)提供新的信息
2)switch_to切换处理器寄存器内容和内核栈由于用户空间进程的寄存器内容在进入核心态时保存在内核栈上(更多细节请参见第14章),在上下文切换期间无需显式操作。而因为每个进程首先都是从核心态开始执行(在调度期间控制权传递到新进程),在返回用户空间时,会使用内核栈上保存的值自动恢复寄存器数据。
但要记住,内核线程没有自身的用户空间内存上下文,可能在某个随机进程地址空间的上部执行。其task_struct->mm为NULL。从当前进程“借来”的地址空间记录在active_mm中

2.6 完全公平调度类

完全公平调度算法依赖于虚拟时钟,用以度量等待进程在完全公平系统中所能得到的CPU时间。
内核在很多地方调用update_curr更新物理运行时间和虚拟运行时间
完全公平调度器的真正关键点是,红黑树的排序过程是根据下列键进行的:

se->vruntime -cfs_rq->min_vruntime;

键值较小的结点,排序位置就更靠左,因此会被更快地调度
在进程运行时,其vruntime稳定地增加,它在红黑树中总是向右移动的
如果进程进入睡眠,则其vruntime保持不变。
睡眠进程醒来后,在红黑树中的位置会更靠左。此时醒来的进程vruntime可能比其他进程小很多,为了防止它一直运行,调度器会把他的vruntime设为比min_vruntime小一点点的值,确保他马上运行的同时又不会让他一直运行

延迟跟踪

调度器会保证每sysctl_sched_latency的时间里每个进程都运行一次。默认为20ms,会随进程数上升而线性上升。

注意,就绪队列中不保存当前正在运行的进程,进程运行完也有可能被放入另一个cpu的队列中。进程从就绪队列取出时,vruntime要减去当前队列的min_vruntime, 加入队列时又要加上当前队列的min_vruntime, 这是因为每个队列的min_vruntime不同,防止一个进程换了一个cpu后一直被调度。

2.7 实时调度类

实时进程的特点在于其优先级比普通进程高,其static_prio值总是比普通进程低
实时进程与普通进程有一个根本的不同之处:如果系统中有一个实时进程且可运行,那么调度器
总是会选中它运行,除非有另一个优先级更高的实时进程

  • 循环进程(SCHED_RR)有时间片,其值在进程运行时会减少,就像是普通进程
  • 先进先出进程(SCHED_FIFO)没有时间片,在被调度器选择执行后,可以运行任意长时间。

具有相同优先级的所有实时进程都保存在一个链表中,表头为active.queue[prio],而
active.bitmap位图中的每个比特位对应于一个链表,凡包含了进程的链表,对应的比特位则置位
所有计算的单位都是实际时间,不需要虚拟时间。

2.8 调度器增强

SMP调度
  • CPU负荷必须尽可能公平地在所有的处理器上共享。
  • 进程与系统中某些处理器的亲合性(affinity)必须是可设置的。
  • 内核必须能够将进程从一个CPU迁移到另一个。
    使用load_balance方法在rq之间移动进程

分为调度器迁移或rq主动迁移

内核为每个就绪队列提供了一个迁移线程,可以接收迁移请求,这些请求保存在链表migration_queue中。(请求是指把进程从当前队列中移出)

调度域和控制组

组调度: 进程置于不同的组中,调度器首先在这些组之间保证公平,然后在组中的所有进程之间保证公平
把进程按用户分组不是唯一可能的做法,内核提供了 控制组(control group),通过
特殊文件系统cgroups可以创建任意的进程集合,甚至可以分为多个层次。

内核抢占

为系统提供更平滑的体验
需要在编译内核时启用对内核抢占的支持,使得不仅用户空间应用程序可以被中断,内核也可以被中断
注意内核进入临界区时是不能中断的

低延迟

即使没有开启内核抢占,linux也能做到低延迟。在一些需要大量读写操作的函数中,内核会在循环里调用cond_resched主动让出cpu,以保证相应速度

参考

深入理解linux内核架构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值