Skip to content

Commit e45c36a

Browse files
update sched and mm
1 parent 7f26caf commit e45c36a

File tree

4 files changed

+227
-7
lines changed

4 files changed

+227
-7
lines changed

kernel/mm/mm_pagetable.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@
129129
* **[翻译后缓冲器(translation lookaside buffer,TLB)](https://en.wikipedia.org/wiki/Translation_lookaside_buffer)**——为了加快从虚拟内存中页面到物理内存中对应地址的搜索,多数体系结构都实现了一个将虚拟地址映射到物理地址的硬件缓存。
130130
* 当访问一个虚拟地址时,CPU的MMU先检查TLB中是否缓存了该虚拟地址到物理地址的映射,如果在缓存中直接命中,物理地址立刻返回。
131131
* 否则,通过页表搜索需要的物理地址。
132-
* TLB中缓冲的是 **页表条目(PTE)**,而不是物理页。因此TLB命中返回PTE给MMU后,节省了MMU去查询页表的时间,仍然需要通过cache/memory去获取内容。
132+
* TLB中缓冲的是映射关系,而不是物理页。因此如果 TLB 命中直接将结果返回给MMU后,节省了MMU去查询页表的时间,仍然需要通过cache/memory去获取内容。
133133
* **[MMU](https://en.wikipedia.org/wiki/Memory_management_unit)** 是一种负责处理中央处理器(CPU)的内存访问请求的计算机硬件。它的功能包括:
134134
* 虚拟地址到物理地址的转换(即虚拟内存管理)
135135
* 内存保护、CPU cache的控制

kernel/sched/sched_cfs-1.md

+125-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

2-
### CFS调度器相关的`sched_latency_ns``sched_min_granularity_ns` `sched_wakeup_granularity_ns`
2+
## CFS调度器相关的`sched_latency_ns``sched_min_granularity_ns``sched_wakeup_granularity_ns`参数
33

4+
### 初始化
45
* 与 CFS 调度器工作密切相关的几个参数值在不同机器上看到的值可能会不一样,但是看内核源代码,内核的`.config``/etc/sysctl.conf``/etc/sysctl.d/*`以及其他能想到会配置`sysctl`的地方却找不到有哪里修改了它们。
56
* 这几个变量在内核中的缺省值:
67
* kernel/sched/fair.c
@@ -54,6 +55,7 @@ kernel.sched_wakeup_granularity_ns = 4000000
5455
* 为什么不采用固定值,或者随着 CPU 数目线性增长,而是将`log(ncpus)`作为因子?
5556
* 简单的说,就是随着 CPU 性能的增多,调度的“有效延迟”肯定会减小,但减小的幅度却不可能是线性的。
5657
* 可以想象,到后来即使加入更多的 CPU,调度因此而获得的收益会愈不明显,所以也就没有必要再返回更大的 factor 了。
58+
* kernel/sched/fair.c
5759
```c
5860
/*
5961
* Increase the granularity value when there are more CPUs,
@@ -110,3 +112,125 @@ start_kernel()
110112
-> update_sysctl()
111113
-> get_update_sysctl_factor()
112114
```
115+
116+
### 设置
117+
118+
* 而设置这几个参数则通过回调函数`sched_proc_update_handler()`修改
119+
```c
120+
int sched_proc_update_handler(struct ctl_table *table, int write,
121+
void __user *buffer, size_t *lenp,
122+
loff_t *ppos)
123+
{
124+
int ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
125+
unsigned int factor = get_update_sysctl_factor();
126+
127+
if (ret || !write)
128+
return ret;
129+
130+
sched_nr_latency = DIV_ROUND_UP(sysctl_sched_latency,
131+
sysctl_sched_min_granularity);
132+
133+
#define WRT_SYSCTL(name) \
134+
(normalized_sysctl_##name = sysctl_##name / (factor))
135+
WRT_SYSCTL(sched_min_granularity);
136+
WRT_SYSCTL(sched_latency);
137+
WRT_SYSCTL(sched_wakeup_granularity);
138+
#undef WRT_SYSCTL
139+
140+
return 0;
141+
}
142+
#endif
143+
...__```
144+
```
145+
* 注意,全局变量`sched_nr_latency`除了初始化外,唯一被赋值的地方就是这里,公式为:
146+
```c
147+
sched_nr_latency = ceil( sysctl_sched_latency / sysctl_sched_min_granularity )
148+
```
149+
150+
### 微调
151+
* 关于调节`sched_latency_ns``sched_min_granularity_ns`会对系统,或者说抢占,造成的影响,主要考察`sched_latency_ns`的计算公式和以下几个函数:
152+
```c
153+
check_preempt_tick()
154+
-> sched_slice()
155+
-> __sched_period()
156+
```
157+
#### 计算调度周期__sched_period
158+
* 首先是看计算 **调度周期**(也叫 *目标延迟* 或者 *调度延迟*)的函数`__sched_period()`
159+
* 该延迟使得 CFS 不必每个 tick 都去检查是否需要调度切换,而是延迟到一定程度再去检查
160+
* 在调度延迟这个时间片内,`cfs_rq`中的每个进程以求优先级为权重瓜分时间
161+
```c
162+
/*
163+
* The idea is to set a period in which each task runs once.
164+
*
165+
* When there are too many tasks (sched_nr_latency) we have to stretch
166+
* this period because otherwise the slices get too small.
167+
*
168+
* p = (nr <= nl) ? l : l*nr/nl
169+
*/
170+
static u64 __sched_period(unsigned long nr_running)
171+
{
172+
if (unlikely(nr_running > sched_nr_latency))
173+
return nr_running * sysctl_sched_min_granularity;
174+
else
175+
return sysctl_sched_latency;
176+
}
177+
```
178+
* `nr_running`通常是就绪队列上的进程数,由此可以看到,调度周期同时受`nr_running`,`sysctl_sched_min_granularity`,`sysctl_sched_latency`三者影响,`sched_nr_latency`是根据后二者计算出来的
179+
* 我们将这两个分支称为 **条件1** 和 **条件2**
180+
1. **条件1**: `nr_running > sched_nr_latency`:排队的就绪进程较多,每个进程分得的时间按权重比例划分`nr_running * sysctl_sched_min_granularity`
181+
2. **条件2**:`nr_running > sched_nr_latency`:排队的就绪进程较少,每个进程分得的时间按权重比例划分`sysctl_sched_latency`
182+
183+
```c
184+
/*
185+
* We calculate the wall-time slice from the period by taking a part
186+
* proportional to the weight.
187+
*
188+
* s = p*P[w/rw]
189+
*/
190+
static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)
191+
{
192+
u64 slice = __sched_period(cfs_rq->nr_running + !se->on_rq);
193+
194+
for_each_sched_entity(se) {
195+
struct load_weight *load;
196+
struct load_weight lw;
197+
198+
cfs_rq = cfs_rq_of(se);
199+
load = &cfs_rq->load;
200+
201+
if (unlikely(!se->on_rq)) {
202+
lw = cfs_rq->load;
203+
204+
update_load_add(&lw, se->load.weight);
205+
load = &lw;
206+
}
207+
slice = __calc_delta(slice, se->load.weight, load);
208+
}
209+
return slice;
210+
}
211+
...*```
212+
```
213+
* `sched_slice()`是按权重比例划分调度周期为时间片的过程
214+
* 注意,这里的时间片是墙上时间(实际时间),不是虚拟时间
215+
* `check_preempt_tick()`详见 [周期性调度检查check_preempt_tick](#sched_cfs.md#周期性调度检查check_preempt_tick)
216+
217+
#### `sysctl_sched_latency`不变,只减小`sysctl_sched_min_granularity`
218+
* 根据公式,`sched_nr_latency`会比较大,因此容易进入 **条件2**
219+
* 有可能用更长的调度周期`sysctl_sched_latency`,而不是`nr_running * sysctl_sched_min_granularity`
220+
* 虽然调度最小粒度比较小,但比改动前的理想运行时间会更长
221+
*`check_preempt_tick()`时会因`if (delta_exec > ideal_runtime)` 不易达成变得不易被抢占
222+
#### `sysctl_sched_min_granularity`不变,只减小`sysctl_sched_latency`
223+
* 根据公式,`sched_nr_latency`会比较小,因此容易进入 **条件1**
224+
* 以调度最小粒度换算后进行调度
225+
*`check_preempt_tick()`时,会使检查调度最小粒度的条件不容易通过,因此变得易被抢占
226+
```c
227+
if (delta_exec < sysctl_sched_min_granularity)
228+
return;
229+
```
230+
#### 同时减小`sysctl_sched_latency``sysctl_sched_min_granularity`
231+
* 根据公式,`sched_nr_latency`不变,因此容易进入 **条件1****条件2** 的机会和调整之前想当
232+
* 然而,不论是进入哪个条件,调度周期都变短了,见`__sched_period()`
233+
* 因此,理想运行时间也变短了,见`sched_slice()`
234+
*`check_preempt_tick()`时,
235+
* `if (delta_exec > ideal_runtime)` 容易达成,易被抢占
236+
* `if (delta_exec < sysctl_sched_min_granularity)` 不易达成,易被抢占

kernel/sched/sched_cfs.md

+9-5
Original file line numberDiff line numberDiff line change
@@ -534,9 +534,10 @@ wakeup_preempt_entity(struct sched_entity *curr, struct sched_entity *se)
534534
return 0;
535535
}
536536
```
537-
*`wakeup_preempt_entity()`返回**1**时,`check_preempt_wakeup()`会设置`TIF_NEED_RESCHED`标志允许新进程抢占当前进程。
538-
* 注释中的s1,s2,s3是新进程/调度实体的三种情况,横座标轴为`vruntime`
537+
*`wakeup_preempt_entity()`返回 **1** 时,`check_preempt_wakeup()`会设置`TIF_NEED_RESCHED`标志允许新进程抢占当前进程。
538+
* 注释中的 s1,s2,s3 是新进程/调度实体的三种情况,横座标轴为`vruntime`。仅当 s3 情况,`se-vruntime``curr->vruntime`的值小且超过`gran`,可以抢占
539539
* 为了让进程切换不会过于频繁,这里不会因为新进程的`vruntime`较小就立即切换,而是“缓冲”一下,与`wakeup_gran()`计算结果进行比较后再决定。
540+
* 注意参数的顺序,该函数判断`se`能否抢占`curr`
540541

541542
### 唤醒时粒度的计算wakeup_gran
542543
* `wakeup_gran()`函数用于计算根据`sysctl_sched_wakeup_granularity`**新进程的权重**转换得到的虚拟时间。
@@ -1177,8 +1178,8 @@ pick_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *curr)
11771178
/*
11781179
* Prefer last buddy, try to return the CPU to a preempted task.
11791180
*/
1180-
/*尝试把CPU还给被抢占的进程,条件是,last进程的vruntime和left进程的vruntime相差不大
1181-
(具体可以回去看wakeup_preempt_entity()的实现)。这是为了提高cache的利用。
1181+
/*尝试把CPU还给被抢占的进程,条件是,last 进程的 vruntime 和 left 进程的 vruntime
1182+
相差不大(具体可以回去看wakeup_preempt_entity()的实现)。这是为了提高cache的利用。
11821183
如果last进程的vruntime比left进程的vruntime大很多,说明left进程已经积累的较大的不
11831184
公平,需要及时被调度。
11841185
*/
@@ -1199,7 +1200,10 @@ pick_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *curr)
11991200
```
12001201
* `cfs_rq->rb_leftmost`会在`__enqueue_entity()`或者`__dequeue_entity()`操作时顺带缓存起来,再用到的时候就无需重新搜索红黑树了。
12011202
* `cfs_rq->skip`通常会在`yield_task_fair()`的时候被设置,表明该实体会失去一次被调度的机会。
1202-
* 在调用`wakeup_preempt_entity()``cfs_rq->next``cfs_rq->last`比较的时候传的是`left`,以及为什么这几个检查的顺序要这么排列,关系到次序问题,见该函数的注释。
1203+
* 在调用`wakeup_preempt_entity()``cfs_rq->next``cfs_rq->last`比较时用的是`left`
1204+
* `left`记录的是当前红黑树上和当前进程中`vruntime`最小的调度实体
1205+
* 该调度实体会按照注释所说的顺序分别与`skip``last``next`实体的`vruntime`进行比较
1206+
* 最后一个给`se`赋值的被选中,因此实现时的顺序与列举的顺序相反
12031207
12041208
### 标记选出的进程set_next_entity
12051209
* 进程选出来后还需要有一些与CFS运行队列相关的后续工作,由`set_next_entity()`完成

kernel/sched/sched_lb.md

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
```c
2+
start_kernel()
3+
-> rest_init()
4+
-> kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
5+
6+
kernel_init()
7+
-> kernel_init_freeable()
8+
-> smp_init()
9+
-> idle_threads_init()
10+
-> sched_init_smp()
11+
-> sched_init_numa()
12+
-> init_sched_domains(cpu_active_mask)
13+
-> alloc_sched_domains(ndoms_cur = 1)
14+
-> build_sched_domains(doms_cur[0], NULL)
15+
-> __visit_domain_allocation_hell()
16+
-> __sdt_alloc(cpu_map)
17+
-> register_sched_domain_sysctl()
18+
-> run_init_process()
19+
```
20+
21+
* kernel/sched/core.c
22+
```c
23+
struct sd_data {
24+
struct sched_domain **__percpu sd;
25+
struct sched_domain_shared **__percpu sds;
26+
struct sched_group **__percpu sg;
27+
struct sched_group_capacity **__percpu sgc;
28+
};
29+
30+
struct sched_domain_topology_level {
31+
sched_domain_init_f init;
32+
sched_domain_mask_f mask;
33+
int flags;
34+
int numa_level;
35+
struct sd_data data;
36+
};
37+
...
38+
/*
39+
* Topology list, bottom-up.
40+
*/
41+
static struct sched_domain_topology_level default_topology[] = {
42+
#ifdef CONFIG_SCHED_SMT
43+
{ sd_init_SIBLING, cpu_smt_mask, },
44+
#endif
45+
#ifdef CONFIG_SCHED_MC
46+
{ sd_init_MC, cpu_coregroup_mask, },
47+
#endif
48+
#ifdef CONFIG_SCHED_BOOK
49+
{ sd_init_BOOK, cpu_book_mask, },
50+
#endif
51+
{ sd_init_CPU, cpu_cpu_mask, },
52+
{ NULL, },
53+
};
54+
55+
static struct sched_domain_topology_level *sched_domain_topology = default_topology;
56+
57+
#define for_each_sd_topology(tl) \
58+
for (tl = sched_domain_topology; tl->mask; tl++)
59+
...**```
60+
```
61+
* 每个级别的调度域和调度组会在`__sdt_alloc()``kzalloc_node()`分配出来
62+
* `register_sched_domain_sysctl()` 建立如下 `sysctl` 控制项
63+
```
64+
proc/sys/kernel/sched_domain/
65+
├── cpu0
66+
│   ├── domain0
67+
│   │   ├── busy_factor
68+
│   │   ├── busy_idx
69+
│   │   ├── cache_nice_tries
70+
│   │   ├── flags
71+
│   │   ├── forkexec_idx
72+
│   │   ├── idle_idx
73+
│   │   ├── imbalance_pct
74+
│   │   ├── max_interval
75+
│   │   ├── min_interval
76+
│   │   ├── name
77+
│   │   ├── newidle_idx
78+
│   │   └── wake_idx
79+
│   └── domain1
80+
├── cpu1
81+
│   ├── domain0
82+
│   └── domain1
83+
├── cpu2
84+
│   ├── domain0
85+
│   └── domain1
86+
├── cpu3
87+
│   ├── domain0
88+
│   └── domain1
89+
└── cpu4
90+
   ├── domain0
91+
   └── domain1
92+
```

0 commit comments

Comments
 (0)