mirror of
https://github.com/gatieme/LDD-LinuxDeviceDrivers.git
synced 2026-08-19 01:22:36 +08:00
鏇存柊浜嗗唴瀛樼鐞嗕箣鍐呭瓨鎻忚堪(鍥?--椤靛抚struct page璇﹁В...
This commit is contained in:
@@ -148,7 +148,7 @@ linux进程的调度算法其实经过了很多次的演变, 但是其演变主
|
||||
| O(1)调度器 | linux-2.5 |
|
||||
| CFS调度器 | linux-2.6~至今 |
|
||||
|
||||
|
||||
关于调度器发展的详细内容, 可以阅读[Linux进程调度策略的发展和演变--Linux进程的管理与调度(十六)](http://blog.csdn.net/gatieme/article/details/51701149)
|
||||
|
||||
#4 Linux的调度器设计
|
||||
-------
|
||||
@@ -287,6 +287,8 @@ linux针对实时进程实现了Roound-Robin, FIFO和Earliest-Deadline-First(EDF
|
||||
##4.2 进程的调度
|
||||
-------
|
||||
|
||||
|
||||
|
||||
首先,我们需要清楚,什么样的进程会进入调度器进行选择,就是处于TASK_RUNNING状态的进程,而其他状态下的进程都不会进入调度器进行调度。
|
||||
系统发生调度的时机如下
|
||||
|
||||
@@ -312,24 +314,193 @@ linux针对实时进程实现了Roound-Robin, FIFO和Earliest-Deadline-First(EDF
|
||||
|
||||
系统并不是每时每刻都允许调度的发生,当处于硬中断期间的时候,调度是被系统禁止的,之后硬中断过后才重新允许调度。而对于异常,系统并不会禁止调度,也就是在异常上下文中,系统是有可能发生调度的。
|
||||
|
||||
##4.3 抢占标识TIF_NEED_RESCHED
|
||||
|
||||
##周期性调度器
|
||||
内核在检查need_resched标识TIF_NEED_RESCHED的值判断是否需要抢占当前进程, 内核在thread_info的flag中设置了一个标识来标志进程是否需要重新调度, 即重新调度need_resched标识TIF_NEED_RESCHED, 内核在即将返回用户空间时会检查标识TIF_NEED_RESCHED标志进程是否需要重新调度
|
||||
|
||||
系统中每个进程都有一个特定于体系结构的struct thread_info结构, 用户层程序被调度的时候会检查struct thread_info中的need_resched标识TLF_NEED_RESCHED标识来检查自己是否需要被重新调度.
|
||||
|
||||
如果内核检查进程的抢占标识被设置, 则会在一个关键的时刻, 调用调度器来完成调度和抢占的工作
|
||||
|
||||
|
||||
|
||||
##4.4 内核抢占和用户抢占
|
||||
-------
|
||||
|
||||
而根据进程抢占发生的时机, 抢占可以分为内核抢占和用户抢占, **内核抢占**就是指一个在内核态运行的进程, 可能在执行内核函数期间被另一个进程取
|
||||
|
||||
一般来说,用户抢占发生几下情况:
|
||||
|
||||
* 从系统调用返回用户空间;
|
||||
|
||||
* 从中断(异常)处理程序返回用户空间
|
||||
|
||||
内核抢占发生的时机,一般发生在:
|
||||
|
||||
* 当从中断处理程序正在执行,且返回内核空间之前。当一个中断处理例程退出,在返回到内核态时(kernel-space)。这是隐式的调用schedule()函数,当前任务没有主动放弃CPU使用权,而是被剥夺了CPU使用权。
|
||||
|
||||
* 当内核代码再一次具有可抢占性的时候,如解锁(spin_unlock_bh)及使能软中断(local_bh_enable)等, 此时当kernel code从不可抢占状态变为可抢占状态时(preemptible again)。也就是preempt_count从正整数变为0时。这也是隐式的调用schedule()函数
|
||||
|
||||
* 如果内核中的任务显式的调用schedule(), 任务主动放弃CPU使用权
|
||||
|
||||
* 如果内核中的任务阻塞(这同样也会导致调用schedule()), 导致需要调用schedule()函数。任务主动放弃CPU使用权
|
||||
|
||||
|
||||
内核抢占采用同抢占标识的类似方法被实现, linux内核在thread_info结构中添加了一个自旋锁标识preempt_count, 称为**抢占计数器(preemption counter)**.
|
||||
|
||||
```c
|
||||
struct thread_info
|
||||
{
|
||||
/* ...... */
|
||||
int preempt_count; /* 0 => preemptable, <0 => BUG */
|
||||
/* ...... */
|
||||
}
|
||||
```
|
||||
|
||||
| preempt_count值 | 描述 |
|
||||
| ------- |:-------:|
|
||||
| >0 | 禁止内核抢占, 其值标记了使用preempt_count的临界区的数目 |
|
||||
| 0 | 开启内核抢占 |
|
||||
| <0 | 锁为负值, 内核出现错误 |
|
||||
|
||||
内核自然也提供了一些函数或者宏, 用来开启, 关闭以及检测抢占计数器preempt_coun的值, 这些通用的函数定义在[include/asm-generic/preempt.h](http://lxr.free-electrons.com/source/include/asm-generic/preempt.h?v=4.6#L8), 而某些架构也定义了自己的接口, 比如x86架构[/arch/x86/include/asm/preempt.h](http://lxr.free-electrons.com/source/arch/x86/include/asm/preempt.h?v=4.6)
|
||||
|
||||
|
||||
>详细内容请参见 [Linux用户抢占和内核抢占详解(概念, 实现和触发时机)--Linux进程的管理与调度(二十)](http://blog.csdn.net/gatieme/article/details/51872618)
|
||||
|
||||
|
||||
##4.5 周期性调度器scheduler_tick
|
||||
-------
|
||||
|
||||
|
||||
##核心调度器
|
||||
**周期调度器**
|
||||
|
||||
周期性调度器scheduler_tick由内核时钟中断周期性的触发, 周期性调度器以固定的频率激活负责当前进程调度类的周期性调度方法, 以保证系统的并发性, 周期性调度器通过调用进程所属调度器类的task_tick操作完成周期性调度的通知和配置工作, 通过resched_curr函数(早期的resched_task函数)设置抢占标识TIF_NEED_RESCHED来通知内核在必要的时间由主调度函数完成真正的调度工作, 此种做法称之为延迟调度策略
|
||||
|
||||
>关于周期性调度器的详细信息, 参见[Linux核心调度器之周期性调度器scheduler_tick--Linux进程的管理与调度(十八)](http://blog.csdn.net/gatieme/article/details/51872561)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
##4.6 主调度器schedule
|
||||
-------
|
||||
|
||||
**主调度器**
|
||||
|
||||
schedule就是主调度器的工作函数, 在内核中的许多地方, 如果要将CPU分配给与当前活动进程不同的另一个进程, 都会直接调用主调度器函数schedule或者其子函数__schedule.
|
||||
|
||||
**__schedule完成抢占**
|
||||
|
||||
* 完成一些必要的检查, 并设置进程状态, 处理进程所在的就绪队列
|
||||
|
||||
* 调度全局的pick_next_task选择抢占的进程
|
||||
|
||||
如果当前cpu上所有的进程都是cfs调度的普通非实时进程, 则直接用cfs调度, 如果无程序可调度则调度idle进程
|
||||
|
||||
否则从优先级最高的调度器类sched_class_highest(目前是stop_sched_class)开始依次遍历所有调度器类的pick_next_task函数, 选择最优的那个进程执行
|
||||
|
||||
* context_switch完成进程上下文切换
|
||||
|
||||
调用switch_mm(), 把虚拟内存从一个进程映射切换到新进程中
|
||||
|
||||
调用switch_to(),从上一个进程的处理器状态切换到新进程的处理器状态。这包括保存、恢复栈信息和寄存器信息
|
||||
|
||||
>主调度器的详细信息, 可以参考 [Linux进程核心调度器之主调度器--Linux进程的管理与调度(十九)](http://blog.csdn.net/gatieme/article/details/51872594)
|
||||
|
||||
|
||||
##4.7 进程上下文切换context_switch
|
||||
-------
|
||||
|
||||
|
||||
##linux内核抢占与用户抢占
|
||||
**context_switch流程**
|
||||
|
||||
context_switch其实是一个分配器, 他会调用所需的特定体系结构的方法
|
||||
|
||||
* 调用switch_mm(), 把虚拟内存从一个进程映射切换到新进程中
|
||||
|
||||
switch_mm更换通过task_struct->mm描述的内存管理上下文, 该工作的细节取决于处理器, 主要包括加载页表, 刷出地址转换后备缓冲器(部分或者全部), 向内存管理单元(MMU)提供新的信息
|
||||
|
||||
* 调用switch_to(),从上一个进程的处理器状态切换到新进程的处理器状态。这包括保存、恢复栈信息和寄存器信息
|
||||
|
||||
switch_to切换处理器寄存器的呢内容和内核栈(虚拟地址空间的用户部分已经通过switch_mm变更, 其中也包括了用户状态下的栈, 因此switch_to不需要变更用户栈, 只需变更内核栈), 此段代码严重依赖于体系结构, 且代码通常都是用汇编语言编写.
|
||||
|
||||
|
||||
**为什么switch_to需要3个参数**
|
||||
|
||||
|
||||
|
||||
在新进程被选中执行时, 内核恢复到进程被切换出去的点继续执行, 此时内核只知道谁之前将新进程抢占了, 但是却不知道新进程再次执行是抢占了谁, 因此底层的进程切换机制必须将此前执行的进程(即新进程抢占的那个进程)提供给context_switch. 由于控制流会回到函数的该中间, 因此无法通过普通函数的返回值来完成. 因此使用了一个3个参数, 但是逻辑效果是相同的, 仿佛是switch_to是带有两个参数的函数, 而且返回了一个指向此前运行的进程的指针.
|
||||
|
||||
```c
|
||||
switch_to(prev, next, last);
|
||||
|
||||
即
|
||||
|
||||
prev = last = switch_to(prev, next);
|
||||
```
|
||||
其中返回的prev值并不是做参数的prev值, 而是prev被再次调度的时候抢占掉的那个进程last.
|
||||
|
||||
详情请参见, [Linux进程上下文切换过程context_switch详解--Linux进程的管理与调度(二十一)](http://blog.csdn.net/gatieme/article/details/51872659)
|
||||
|
||||
|
||||
##4.8 处理进程优先级
|
||||
-------
|
||||
内核使用一些简单的数值范围0~139表示内部优先级, 数值越低, 优先级越高。
|
||||
|
||||
从0~99的范围专供实时进程使用, nice的值[-20,19]则映射到范围100~139
|
||||
|
||||
动态优先级 静态优先级 实时优先级
|
||||
|
||||
其中task_struct采用了三个成员表示进程的优先级:prio和normal_prio表示动态优先级, static_prio表示进程的静态优先级.
|
||||
|
||||
|
||||
此外还用了一个字段rt_priority保存了实时进程的优先级
|
||||
|
||||
|字段 | 描述 |
|
||||
|:-------:|:-------:|
|
||||
| static_prio | 用于保存静态优先级, 是进程启动时分配的优先级, ,可以通过nice和sched_setscheduler系统调用来进行修改, 否则在进程运行期间会一直保持恒定 |
|
||||
| prio | 保存进程的动态优先级 |
|
||||
| normal_prio | 表示基于进程的静态优先级static_prio和调度策略计算出的优先级. 因此即使普通进程和实时进程具有相同的静态优先级, 其普通优先级也是不同的, 进程分叉(fork)时, 子进程会继承父进程的普通优先级 |
|
||||
| rt_priority | 用于保存实时优先级, 实时进程的优先级用实时优先级rt_priority来表示 |
|
||||
|
||||
静态优先级static_prio(普通进程)和实时优先级rt_priority(实时进程)是计算的起点
|
||||
|
||||
因此他们也是进程创建的时候设定好的, 我们通过nice修改的就是普通进程的静态优先级static_prio
|
||||
|
||||
首先通过静态优先级static_prio计算出普通优先级normal_prio, 该工作可以由nromal_prio来完成, 该函数定义在[kernel/sched/core.c#L861](http://lxr.free-electrons.com/source/kernel/sched/core.c?v=4.6#L861)
|
||||
|
||||
内核通过ffective_prio设置动态优先级prio, 计算动态优先级的流程如下
|
||||
|
||||
* 设置进程的普通优先级(实时进程99-rt_priority, 普通进程为static_priority)
|
||||
|
||||
* 计算进程的动态优先级(实时进程则维持动态优先级的prio不变, 普通进程的动态优先级即为其普通优先级)
|
||||
|
||||
最后, 我们综述一下在针对不同类型进程的计算结果
|
||||
|
||||
| 进程类型 | 实时优先级rt_priority | 静态优先级static_prio | 普通优先级normal_prio | 动态优先级prio |
|
||||
| ------- |:-------:|:-------:|:-------:|
|
||||
| EDF调度的实时进程 | rt_priority | 不使用 | MAX_DL_PRIO-1 | 维持原prio不变 |
|
||||
| RT算法调度的实时进程 | rt_priority | 不使用 | MAX_RT_PRIO-1-rt_priority | 维持原prio不变 |
|
||||
| 普通进程 | 不使用 | static_prio | static_prio | static_prio |
|
||||
| 优先级提高的普通进程 | 不使用 | static_prio(改变) | static_prio | 维持原prio不变 |
|
||||
|
||||
>关于进程优先级的详细信息请参见[Linux进程优先级的处理--Linux进程的管理与调度(二十二)](http://blog.csdn.net/gatieme/article/details/51719208)
|
||||
|
||||
|
||||
|
||||
##4.9 唤醒抢占
|
||||
-------
|
||||
|
||||
|
||||
|
||||
##处理进程优先级
|
||||
-------
|
||||
当在try_to_wake_up/wake_up_process和wake_up_new_task中唤醒进程时, 内核使用全局check_preempt_curr看看是否进程可以抢占当前进程可以抢占当前运行的进程.
|
||||
|
||||
每个调度器类都因应该实现一个check_preempt_curr函数, 在全局check_preempt_curr中会调用进程其所属调度器类check_preempt_curr进行抢占检查, 对于完全公平调度器CFS处理的进程, 则对应由check_preempt_wakeup函数执行该策略.
|
||||
|
||||
新唤醒的进程不必一定由完全公平调度器处理, 如果新进程是一个实时进程, 则会立即请求调度, 因为实时进程优先极高, 实时进程总会抢占CFS进
|
||||
|
||||
|
||||
##linux进程上下文切换
|
||||
-------
|
||||
参见[Linux唤醒抢占----Linux进程的管理与调度(二十三)](http://blog.csdn.net/gatieme/article/details/51872831)
|
||||
|
||||
>内核为了实现完全公平, 对一些交互式进程有补偿机制, 这些交互式进程多数情况下属于睡眠状态, 只有在接收到信号以后被唤醒, 比如vim在接收了键盘录入的信号后被唤醒, 完成工作后又进入睡眠态, 因此我们需要对唤醒的进程做一些补偿, 关于补偿的内容我们会在各个调度器类的设计中讲解.
|
||||
|
||||
@@ -89,63 +89,6 @@ linux把进程区分为实时进程和非实时进程, 其中非实时进程进
|
||||
并且每个调度器包括两个内容:**调度框架**(其实质就是两个函数框架)及**调度器类**
|
||||
|
||||
|
||||
**抢占标识TIF_NEED_RESCHED**
|
||||
内核在检查need_resched标识TIF_NEED_RESCHED的值判断是否需要抢占当前进程, 内核在thread_info的flag中设置了一个标识来标志进程是否需要重新调度, 即重新调度need_resched标识TIF_NEED_RESCHED, 内核在即将返回用户空间时会检查标识TIF_NEED_RESCHED标志进程是否需要重新调度
|
||||
|
||||
**周期调度器**
|
||||
|
||||
周期性调度器scheduler_tick由内核时钟中断周期性的触发, 周期性调度器以固定的频率激活负责当前进程调度类的周期性调度方法, 以保证系统的并发性, 周期性调度器通过调用进程所属调度器类的task_tick操作完成周期性调度的通知和配置工作, 通过resched_curr函数(早期的resched_task函数)设置抢占标识TIF_NEED_RESCHED来通知内核在必要的时间由主调度函数完成真正的调度工作, 此种做法称之为延迟调度策略
|
||||
|
||||
>关于周期性调度器的详细信息, 参见[Linux核心调度器之周期性调度器scheduler_tick--Linux进程的管理与调度(十八)](http://blog.csdn.net/gatieme/article/details/51872561)
|
||||
|
||||
**主调度器**
|
||||
|
||||
schedule就是主调度器的工作函数, 在内核中的许多地方, 如果要将CPU分配给与当前活动进程不同的另一个进程, 都会直接调用主调度器函数schedule或者其子函数__schedule.
|
||||
|
||||
**__schedule完成抢占**
|
||||
|
||||
* 完成一些必要的检查, 并设置进程状态, 处理进程所在的就绪队列
|
||||
|
||||
* 调度全局的pick_next_task选择抢占的进程
|
||||
|
||||
如果当前cpu上所有的进程都是cfs调度的普通非实时进程, 则直接用cfs调度, 如果无程序可调度则调度idle进程
|
||||
|
||||
否则从优先级最高的调度器类sched_class_highest(目前是stop_sched_class)开始依次遍历所有调度器类的pick_next_task函数, 选择最优的那个进程执行
|
||||
|
||||
* context_switch完成进程上下文切换
|
||||
|
||||
调用switch_mm(), 把虚拟内存从一个进程映射切换到新进程中
|
||||
|
||||
调用switch_to(),从上一个进程的处理器状态切换到新进程的处理器状态。这包括保存、恢复栈信息和寄存器信息
|
||||
|
||||
>主调度器的详细信息, 可以参考 [Linux进程核心调度器之主调度器--Linux进程的管理与调度(十九)](http://blog.csdn.net/gatieme/article/details/51872594)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**内核抢占和用户抢占**
|
||||
|
||||
而根据进程抢占发生的时机, 抢占可以分为内核抢占和用户抢占, **内核抢占**就是指一个在内核态运行的进程, 可能在执行内核函数期间被另一个进程取
|
||||
|
||||
一般来说,用户抢占发生几下情况:
|
||||
|
||||
* 从系统调用返回用户空间;
|
||||
|
||||
* 从中断(异常)处理程序返回用户空间
|
||||
|
||||
内核抢占发生的时机,一般发生在:
|
||||
|
||||
* 当从中断处理程序正在执行,且返回内核空间之前。当一个中断处理例程退出,在返回到内核态时(kernel-space)。这是隐式的调用schedule()函数,当前任务没有主动放弃CPU使用权,而是被剥夺了CPU使用权。
|
||||
|
||||
* 当内核代码再一次具有可抢占性的时候,如解锁(spin_unlock_bh)及使能软中断(local_bh_enable)等, 此时当kernel code从不可抢占状态变为可抢占状态时(preemptible again)。也就是preempt_count从正整数变为0时。这也是隐式的调用schedule()函数
|
||||
|
||||
* 如果内核中的任务显式的调用schedule(), 任务主动放弃CPU使用权
|
||||
|
||||
* 如果内核中的任务阻塞(这同样也会导致调用schedule()), 导致需要调用schedule()函数。任务主动放弃CPU使用权
|
||||
|
||||
|
||||
>详细内容请参见 [Linux用户抢占和内核抢占详解(概念, 实现和触发时机)--Linux进程的管理与调度(二十)](http://blog.csdn.net/gatieme/article/details/51872618)
|
||||
|
||||
##2.2 6种调度策略
|
||||
-------
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
enum node_states {
|
||||
N_POSSIBLE, /* The node could become online at some point */
|
||||
N_ONLINE, /* The node is online */
|
||||
N_NORMAL_MEMORY, /* The node has regular memory */
|
||||
#ifdef CONFIG_HIGHMEM
|
||||
N_HIGH_MEMORY, /* The node has regular or high memory */
|
||||
#else
|
||||
N_HIGH_MEMORY = N_NORMAL_MEMORY,
|
||||
#endif
|
||||
#ifdef CONFIG_MOVABLE_NODE
|
||||
N_MEMORY, /* The node has memory(regular, high, movable) */
|
||||
#else
|
||||
N_MEMORY = N_HIGH_MEMORY,
|
||||
#endif
|
||||
N_CPU, /* The node has one or more cpus */
|
||||
NR_NODE_STATES
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,177 @@
|
||||
服务器体系与共享存储器架构
|
||||
=======
|
||||
|
||||
| 日期 | 内核版本 | 架构| 作者 | GitHub| CSDN |
|
||||
| ------- |:-------:|:-------:|:-------:|:-------:|:-------:|
|
||||
| 2016-06-14 | [Linux-4.7](http://lxr.free-electrons.com/source/?v=4.7) | X86 & arm | [gatieme](http://blog.csdn.net/gatieme) | [LinuxDeviceDrivers](https://github.com/gatieme/LDD-LinuxDeviceDrivers) | [Linux内存管理](http://blog.csdn.net/gatieme/article/category/6225543) |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
##参照
|
||||
-------
|
||||
|
||||
| 链接 |
|
||||
|:-------:|
|
||||
| [内存管理(一)内存模型之Node](http://biancheng.dnbcw.info/linux/387391.html) |
|
||||
| [Linux 内存管理 重要结构体](http://blog.chinaunix.net/uid-26009500-id-3078986.html) |
|
||||
| [Bootmem机制](http://blog.csdn.net/samssm/article/details/25064897) |
|
||||
| [Linux-2.6.32 NUMA架构之内存和调度](http://www.cnblogs.com/zhenjing/archive/2012/03/21/linux_numa.html) |
|
||||
| [Linux 用户空间与内核空间——高端内存详解](http://blog.csdn.net/tommy_wxie/article/details/17122923) |
|
||||
| [探索 Linux 内存模型](http://www.ibm.com/developerworks/cn/linux/l-memmod/) |
|
||||
| [Linux内存管理](http://blog.chinaunix.net/uid/21718047/cid-151509-list-2.html) |
|
||||
| [内存管理-之内核内存管理-基于linux3.10](http://blog.csdn.net/shichaog/article/details/45509917) |
|
||||
| [内存管理(一)](http://www.cnblogs.com/openix/p/3334026.html) |
|
||||
| [Linux内存管理原理](http://www.cnblogs.com/zhaoyl/p/3695517.html) |
|
||||
| [第 15 章 内存映射和 DMA](http://www.embeddedlinux.org.cn/ldd3/ch15.html) |
|
||||
| [ 内存管理(二)struct page ](http://blog.chinaunix.net/uid-30282771-id-5176971.html) |
|
||||
|
||||
|
||||
#1 前景回顾
|
||||
-------
|
||||
|
||||
前面我们讲到[服务器体系(SMP, NUMA, MPP)与共享存储器架构(UMA和NUMA)](http://blog.csdn.net/gatieme/article/details/52098615)
|
||||
|
||||
#1.1 UMA和NUMA两种模型
|
||||
-------
|
||||
|
||||
共享存储型多处理机有两种模型
|
||||
|
||||
* 均匀存储器存取(Uniform-Memory-Access,简称UMA)模型
|
||||
|
||||
将可用内存以连续方式组织起来,
|
||||
* 非均匀存储器存取(Nonuniform-Memory-Access,简称NUMA)模型
|
||||
|
||||
##1.2 UMA模型
|
||||
-------
|
||||
|
||||
传统的多核运算是使用SMP(Symmetric Multi-Processor )模式:将多个处理器与一个集中的存储器和I/O总线相连。所有处理器只能访问同一个物理存储器,因此SMP系统有时也被称为一致存储器访问(UMA)结构体系,一致性意指无论在什么时候,处理器只能为内存的每个数据保持或共享唯一一个数值。
|
||||
|
||||
|
||||
|
||||
>物理存储器被所有处理机均匀共享。所有处理机对所有存储字具有相同的存取时间,这就是为什么称它为均匀存储器存取的原因。每台处理机可以有私用高速缓存,外围设备也以一定形式共享。
|
||||
|
||||
很显然,SMP的缺点是可伸缩性有限,因为在存储器和I/O接口达到饱和的时候,增加处理器并不能获得更高的性能,与之相对应的有AMP架构,不同核之间有主从关系,如一个核控制另外一个核的业务,可以理解为多核系统中控制平面和数据平面。
|
||||
|
||||
##1.3 NUMA模型
|
||||
-------
|
||||
|
||||
|
||||
NUMA模式是一种分布式存储器访问方式,处理器可以同时访问不同的存储器地址,大幅度提高并行性。 NUMA总是多处理器计算机,系统的哪个CPU都有本地内存, 可支持快速的访问, 各个处理器之前通过总线链接起来, 以支持堆其他CPU的本地内存的访问, 当然访问要比本地内存慢.
|
||||
|
||||
|
||||
NUMA模式下,处理器被划分成多个"节点"(node), 每个节点被分配有的本地存储器空间。 所有节点中的处理器都可以访问全部的系统物理存储器,但是访问本节点内的存储器所需要的时间,比访问某些远程节点内的存储器所花的时间要少得多。
|
||||
|
||||
>其访问时间随存储字的位置不同而变化。其共享存储器物理上是分布在所有处理机的本地存储器上。所有本地存储器的集合组成了全局地址空间,可被所有的处理机访问。处理机访问本地存储器是比较快的,但访问属于另一台处理机的远程存储器则比较慢,因为通过互连网络会产生附加时延。
|
||||
|
||||
NUMA 的主要优点是伸缩性。NUMA 体系结构在设计上已超越了 SMP 体系结构在伸缩性上的限制。通过 SMP,所有的内存访问都传递到相同的共享内存总线。这种方式非常适用于 CPU 数量相对较少的情况,但不适用于具有几十个甚至几百个 CPU 的情况,因为这些 CPU 会相互竞争对共享内存总线的访问。NUMA 通过限制任何一条内存总线上的 CPU 数量并依靠高速互连来连接各个节点,从而缓解了这些瓶颈状况。
|
||||
|
||||
|
||||
|
||||
#2 (N)UMA模型中linux内存的机构
|
||||
-------
|
||||
|
||||
|
||||
Linux适用于各种不同的体系结构, 而不同体系结构在内存管理方面的差别很大. 因此linux内核需要用一种体系结构无关的方式来表示内存.
|
||||
|
||||
Linux内核通过插入一些兼容层, 使得不同体系结构的差异很好的被隐藏起来, 内核对一致和非一致内存访问使用相同的数据结构
|
||||
|
||||
|
||||
#2.1 (N)UMA模型中linux内存的机构
|
||||
-------
|
||||
|
||||
|
||||
|
||||
非一致存储器访问(NUMA)模式下
|
||||
|
||||
* 处理器被划分成多个"节点"(node), 每个节点被分配有的本地存储器空间. 所有节点中的处理器都可以访问全部的系统物理存储器,但是访问本节点内的存储器所需要的时间,比访问某些远程节点内的存储器所花的时间要少得多
|
||||
|
||||
|
||||
* 内存被分割成多个区域(BANK,也叫"簇"),依据簇与处理器的"距离"不同, 访问不同簇的代码也会不同. 比如,可能把内存的一个簇指派给每个处理器,或则某个簇和设备卡很近,很适合DMA,那么就指派给该设备。因此当前的多数系统会把内存系统分割成2块区域,一块是专门给CPU去访问,一块是给外围设备板卡的DMA去访问
|
||||
|
||||
>在UMA系统中, 内存就相当于一个只使用一个NUMA节点来管理整个系统的内存. 而内存管理的其他地方则认为他们就是在处理一个(伪)NUMA系统.
|
||||
|
||||
|
||||
|
||||
#2.2 Linux物理内存的组织形式
|
||||
-------
|
||||
|
||||
Linux把物理内存划分为三个层次来管理
|
||||
|
||||
| 层次 | 描述 |
|
||||
|:----:|:----:|
|
||||
| 存储节点(Node) | CPU被划分为多个节点(node), 内存则被分簇, 每个CPU对应一个本地物理内存, 即一个CPU-node对应一个内存簇bank,即每个内存簇被认为是一个节点 |
|
||||
| 管理区(Zone) | 每个物理内存节点node被划分为多个内存管理区域, 用于表示不同范围的内存, 内核可以使用不同的映射方式映射物理内存 |
|
||||
| 页面(Page) | 内存被细分为多个页面帧, 页面是最基本的页面分配的单位 |
|
||||
|
||||
为了支持NUMA模型,也即CPU对不同内存单元的访问时间可能不同,此时系统的物理内存被划分为几个节点(node), 一个node对应一个内存簇bank,即每个内存簇被认为是一个节点
|
||||
|
||||
* 首先, 内存被划分为结点. 每个节点关联到系统中的一个处理器, 内核中表示为`pg_data_t`的实例. 系统中每个节点被链接到一个以NULL结尾的`pgdat_list`链表中<而其中的每个节点利用`pg_data_tnode_next`字段链接到下一节.而对于PC这种UMA结构的机器来说, 只使用了一个成为contig_page_data的静态pg_data_t结构.
|
||||
|
||||
* 接着各个节点又被划分为内存管理区域, 一个管理区域通过struct zone_struct描述, 其被定义为zone_t, 用以表示内存的某个范围, 低端范围的16MB被描述为ZONE_DMA, 某些工业标准体系结构中的(ISA)设备需要用到它, 然后是可直接映射到内核的普通内存域ZONE_NORMAL,最后是超出了内核段的物理地址域ZONE_HIGHMEM, 被称为高端内存. 是系统中预留的可用内存空间, 不能被内核直接映射.
|
||||
|
||||
|
||||
在一个单独的节点内,任一给定CPU访问页面所需的时间都是相同的。然而,对不同的CPU,这个时间可能就不同。对每个CPU而言,内核都试图把耗时节点的访问次数减到最少这就要小心地选择CPU最常引用的内核数据结构的存放位置.
|
||||
|
||||
对于UMA体系的,系统中只有一个node
|
||||
|
||||
在LINUX中引入一个数据结构`struct pglist_data` ,来描述一个node,定义在[`include/linux/mmzone.h`](http://lxr.free-electrons.com/source/include/linux/mmzone.h#L630) 文件中。(这个结构被typedef pg_data_t)。
|
||||
|
||||
|
||||
* 对于NUMA系统来讲, 整个系统的内存由一个[node_data](http://lxr.free-electrons.com/source/arch/s390/numa/numa.c?v=4.7#L23)的pg_data_t指针数组来管理。(因为可能有多个node),系统中的每个节点链接到一个以NULL结尾的[pgdat_list](http://lxr.free-electrons.com/source/arch/ia64/include/asm/numa.h#L27)链表中,而其中的每个节点利用pd_data_tnode_next字段链接到下一个节点。
|
||||
|
||||
|
||||
* 对于PC这样的UMA系统,使用struct pglist_datacontig_page_data ,作为系统唯一的node管理所有的内存区域。(UMA系统中中只有一个node)
|
||||
|
||||
|
||||
|
||||
|
||||
##内存节点node
|
||||
-------
|
||||
|
||||
>CPU被划分为多个节点(node), 内存则被分簇, 每个CPU对应一个本地物理内存, 即一个CPU-node对应一个内存簇bank,即每个内存簇被认为是一个节点
|
||||
>
|
||||
>系统的物理内存被划分为几个节点(node), 一个node对应一个内存簇bank,即每个内存簇被认为是一个节点
|
||||
|
||||
* 首先, 内存被划分为结点. 每个节点关联到系统中的一个处理器, 内核中表示为`pg_data_t`的实例. 系统中每个节点被链接到一个以NULL结尾的`pgdat_list`链表中<而其中的每个节点利用`pg_data_tnode_next`字段链接到下一节.而对于PC这种UMA结构的机器来说, 只使用了一个成为contig_page_data的静态pg_data_t结构.
|
||||
|
||||
|
||||
内存中的每个节点都是由pg_data_t描述,而pg_data_t由struct pglist_data定义而来, 该数据结构定义在[include/linux/mmzone.h, line 615](http://lxr.free-electrons.com/source/include/linux/mmzone.h#L615)
|
||||
|
||||
|
||||
在分配一个页面时, Linux采用节点局部分配的策略, 从最靠近运行中的CPU的节点分配内存, 由于进程往往是在同一个CPU上运行, 因此从当前节点得到的内存很可能被用到
|
||||
|
||||
|
||||
在内存中,每个簇所对应的node又被分成的称为管理区(zone)的块,它们各自描述在内存中的范围。一个管理区(zone)由[struct zone](http://lxr.free-electrons.com/source/include/linux/mmzone.h#L326)结构体来描述,在linux-2.4.37之前的内核中是用[`typedef struct zone_struct zone_t `](http://lxr.free-electrons.com/source/include/linux/mmzone.h?v=2.4.37#L47)数据结构来描述)
|
||||
|
||||
管理区的类型有如下几种
|
||||
* ZONE_DMA
|
||||
* ZONE_NORMAL
|
||||
* ZONE_HIGHMEM这三种类型
|
||||
|
||||
不同的管理区的用途是不一样的,ZONE_DMA类型的内存区域在物理内存的低端,主要是ISA设备只能用低端的地址做DMA操作。ZONE_NORMAL类型的内存区域直接被内核映射到线性地址空间上面的区域(line address space),ZONE_HIGHMEM将保留给系统使用,是系统中预留的可用内存空间,不能被内核直接映射。
|
||||
|
||||
对于x86机器,管理区(内存区域)类型如下分布
|
||||
|
||||
| 类型 | 区域 |
|
||||
| :------- | ----: |
|
||||
| ZONE_DMA | 0~16MB |
|
||||
| ZONE_NORMAL | 16MB~896MB |
|
||||
| ZONE_HIGHMEM | 896MB~物理内存结束 |
|
||||
|
||||
##内存页page
|
||||
-------
|
||||
|
||||
大多数内核(kernel)的操作只使用ZONE_NORMAL区域,系统内存由很多固定大小的内存块组成的,这样的内存块称作为“页”(PAGE),
|
||||
|
||||
x86体系结构中,page的大小为4096个字节。
|
||||
|
||||
每个物理的页由一个`struct page`的数据结构对象来描述。页的数据结构对象都保存在`mem_map`全局数组中,该数组通常被存放在ZONE_NORMAL的首部,或者就在小内存系统中为装入内核映像而预留的区域之后。从载入内核的低地址内存区域的后面内存区域,也就是ZONE_NORMAL开始的地方的内存的页的数据结构对象,都保存在这个全局数组中。
|
||||
|
||||
|
||||
|
||||
##高端内存
|
||||
-------
|
||||
|
||||
由于能够被Linux内核直接访问的ZONE_NORMAL区域的内存空间也是有限的,所以LINUX提出了高端内存(High memory)的概念,并且允许对高端内存的访问
|
||||
Reference in New Issue
Block a user