Linux 虚存 linux2.6内核特性

时间:2022-04-13 19:44:19

一、大型页面的支持

    当代计算机体系结构大都支持多种页面大小,例如,IA-32体系结构支持4KB4MB的页面, Linux操作系统只是将大型页面用于映射实际的内核映像。大型页面的使用主要是为了改进高性能计算(HPC)以及其他内存密集型应用的性能。任何占用大量虚存的访存密集型应用程序都可以使用大型页面来改进性能(Linux使用 2MB4MB的大型页面, AIX使用 16MB的大型页面,而 Solaris使用的页面大小为 4MB)大型页面能够提高性能是因为转换查找缓冲区(Translation Lookaside bufferTLB)能够映射到更大的虚存空间(通过增加 TLB的可达范围),从而减少了 TLB不命中次数。大型页面通过消除在4KB边界上重启预取操作的需求,还改进了内存预取过程 。
    在Linux 2.6内核中,实际可用的大型页面数量可通过proc(/proc/sys/vm/nr_hugepages)接口来配置。由于大页面的实际分配情况与系统中是否存在着物理连续的内存区域有关,建议在系统启动时分配大型页面。 Linux 2.6内核中大型页面的实现核心称为 hugetlbfs,是一种基于 ramfs的伪文件系统(fs/hugetlbfs/inode.c中实现)。该实现的思想使用用大页面来备份存在于文件系统中的任何文件。实际的伪文件系统在系统启动时初始化并注册为一种内部文件系统。进程访问大型页面的方式有两种:通过 shmget()接口建立一个由大型页面 back的共享区域,或者通过对一个已在大型页面文件系统中打开的文件使用mmap()调用。要对一个使用了大型页面的文件进行操作的话, 首先应挂接一个 hugetlbfs文件系统。完成该挂接操作之后,就可以使用标准的文件操作函数调用如 open()等来设置一个文件描述符。 在一个已打开的文件上使用 mmap()系统调用时, hugetlbfs文件系统注册的 mmap()函数为该进程创建适当的 vma结构。推荐使用/proc/meminfo接口以确保能够按预期设置大型页面的容量及数量。分片操作可能会导致该分配过程无法成功地完成 。

二、页面分配与替换

    Linux 2.6内核的物理页面分配机制仍基于伙伴(buddy)算法,但该机制本身已被扩展, 从而类似于一种将合并过程加以延迟处理的懒伙伴(lazy buddy)机制。合并过程中的缓冲区释放活动包含两个步骤:首先将一个缓冲区推入空闲列表中,使得该缓冲区可用于其他请求;然后将该缓冲区的引用状态标记为空闲(通常在一个位图之中),并将该缓冲区与相邻的缓冲区加以合并(如果可行的话)。通常的 buddy系统实现在每次释放时都会执行这两个步骤。而 lazy buddy机制执行第一个步骤后延迟执行第二个步骤,是基于缓冲区的状态来执行该步骤的。下面所讨论的懒伙伴概念在 Linux 2.6中的实现采用了基于 CPU的页面列表。 这些页面集合包含了两个页面(热页面和冷页面)列表, 其中热页面是最近已被使用过的页面,因此期望这些页面仍存在于 CPUcache中。在分配时, 首先查询当前 CPU的页面集合以确定这些页面是否可用。如果可用, 则分配过程相应地继续进行。要确定应该何时释放或分配该页面集合, 则使用一种基于高低水印(low andhigh watermark)的方法。当到达低水印值时,会分配一批页面并将其置于该表中;当到达高水印值时,会同时释放一批页面。这种新型改进实现的结果是极大降低了自旋锁的开销。

三、 Slab分配器

    和其他大多数 UNIX操作系统一样, Linux也提供了一个通用的内存 slab分配器,用来完成任意空间大小的内存分配。 所有 slab分配器的基本目标都是高效地分配通常小于一个页面或者不是页面尺寸偶数倍的小型内存缓冲区。其另一个目标是对频繁分配和释放的内存对象加以操作,这个过程可能导致出现内存分片现象。 slab分配器允许对这些常用的对象(例如 sockinodemm_struct)加以缓存,从而尽量减少每次请求一个新对象时初始化和破坏这些基本实体所需的开销。 


四、VM的可调参数     所有这些参数都可以通过 proc(/proc/sys/vm)文件系统接口进行调优。下面按照与proc文件系统中相同的字母顺序列出这些参数。
  • dirty_background_ratio 参数是在尝试一个回写(writeback)操作之前所持有的脏内存比例。在 Linux 2.6内核中, pdflush内核线程池负责 VM的回写操作, 并周期性同步文件系统的元数据。若超出了这个后台回写百分比,则 pdflush守护进程异步处理回写操作。
  • dirty_expire_centisecs 参数是数据可以保持为脏状态的最大厘秒数, 这个时间段通过查询所有在内存中缓存了脏页面的文件的时间戳来确定。该参数表示一个上限值,确保最终可以将数据写到磁盘子系统。厘秒表示1/100s的时间单位。
  • dirty_ratio 参数是过量脏内存的比例。当一个任务在脏内存过多的环境中执行文件写操作时,系统必须要对脏内存页面执行写出操作,直至脏内存页面的百分比低于系统规定的阈值。这一点很重要, 因为 I/O操作被看作具有高延迟。 如果某个任务在系统能够满足一个分配请求之前需要等待回写操作完成,则该任务的性能会受到影响。这主要被看作是大型内存系统中的一个问题。
  • dirty_writeback_sentisecs 参数是 pdflush内核线程执行回写操作之间的时间间隔(以厘秒为单位)。前面讨论过的 dirty_expire_centisecs 参数控制脏内存页面的存活时间,而该参数定义了 pdflush 线程的调用频率。建议该参数值小于dirty_expire_centisecs取值(对于多数工作负荷来说, 16的比率应是合理的)。增加这个时间长度以及频率间隔(但仍遵循 16比率)可能有利于拥有内核密集型工作负荷的大型系统,因为后台回写操作可能会干扰到工作负荷自身的性能行为。
  • lower_zone_protection 参数是为内存区域回退操作的阻止因子分配的权重。Linux操作系统将物理内存分成 3个不同区域。ZONE_DMA是可由(老式)I/O设备寻址的区域, ZONE_ NORMAL是可由内核寻址的区域, 而 ZONE_HIGHMEM区域则提供给用户空间和特定的内核(临时)缓冲区。上述概念的基本原理是ZONE_DMA区域可用作与 ZONE_NORMAL相同的用途,而 ZONE_NORMAL区域可用作与 ZONE_HIGHMEM 相同的用途,但反之则不然。当ZONE_HIGHMEM区域饱和时, 可以使用 ZONE_NORMAL区域进行分配(通过一个回退操作)ZONE_NORMAL是唯一可用于保存内核数据结构的区域,对于系统而言是至关重要的。在Linux中,使用一种称为递增最小量(incremental min)的启发算法来阻止回退至重要内存区域(至少 ZONE_NORMAL)。lower_zone_protection置为默认的零值会禁止递增最小量算法,将该参数值设置为 4会使其效果增至4倍。这意味着该参数当前控制较低区域防护算法的强度。对于可能占用系统中较高区域的分配操作,lower_zone_protection这个可调参数可以提高对较低内存区域的保护程度 。
  • min_free_kbytes 参数规定了可用于诸如中断处理程序发起的紧急分配等的内存池的大小。在更大规模的系统上, 该参数会影响可用于中断时间的内存与可用于运行时分配的内存比率。在更小的内存系统上, 该参数被保持为较小值以避免分配失败。普遍共识是这个低水印参数用于确定何时拒绝用户空间分配以及何时激发页面替换场景。有可能不将该值设置为任何较大的物理内存比例。如果用户空间操作是内存子系统主要消耗者,尤其适用。
  • nr_pdflush_thread 参数规定 pdflush(回写)线程池的当前大小。VM子系统利用一种动态算法来判定实际线程的数量。重点是在低内存的情形下(其中额外线程的动态分配可能会失败)保留特定数量的 pdflush线程。下限与上限(Linux中分别称为 MIN_PDFLUSH_THREADSMAX_PDFLUSH _THREADS)各自设置为 28。提高上限值(并且重编译内核)有助于在拥有大量磁盘驱动器以及绑定寻道操作的工作负荷的环境中提高 VM I/O并发性。提高最大值会改进性能的另一种情况是与回写操作相关的 CPU绑定情形,这时系统中可提供额外处理器来处理总体工作负荷。
  • overcommit_memory 参数是一个允许内存过量使用的标志。当该标志置为默认值 0时,内核在每个 malloc()调用前都发起检查以确保存在足够的内存。若该标志置为 1,则假定系统中总是存在着足够多的内存。当该标志置为 0时,则确保只会分配页面替换基础架构所能处理的用户虚存量,并且每个页面都需要一个后备存储器。匿名内存页面需要交换空间,而文件页面则需要可以被保留的磁盘空间。将该标志置为1后会生成更有效的内存利用行为。但是,基于特定工作负荷,它可能导致某些进程被关闭的场景。概言之,将该标志置为 0会导致内核通过估测可用的内存量和明显无效的失败请求,执行启发式的内存过量使用处理操作。不幸的是,由于系统使用一种启发式而不是精确的算法来分配内存,这种设置有时会导致系统可用内存过载。将该标志置为 1会导致内核执行内存无过量使用方式的处理。这种设置会增大内存过载的可能性,但也会提高内存密集型任务(例如某些科学应用所执行的任务)的性能。另一种支持情形是将该标志置为2。在这种情况下,对于全部交换空间以及 overcommit_ratio 参数(在下面讨论)中规定的物理内存百分比之和的内存请求,内核会分配失败。 在 Linux中, 将该标志置为2可以降低内存过量使用的风险。
  • overcommit_ratio 参数指定了当(前面讨论的)overcommit_memory参数置为2时所考虑的物理内存百分比。默认值是50
  • page-cluster 参数所代表的整数值当 2作为 x的乘幂时标识了内核一次性读入的页面数目(实际的交换预读窗口)。该参数的默认值对于内存小于 16MB的系统为 2,对于内存更大的系统为3, 这些取值在多数情况下都可看作是合理的设置。 该参数可用于改进页面 I/O效率, 但如果该参数被不当指定的话, 系统可能会面临过度的I/O和内存消耗。
  • swappiness 参数指示了 VM子系统在通过解除页面映射并将其换出的方式来回收页面与只回收未被任何进程映射的页面之间的相对优先选择。实际的决策依赖于一个二元开关,当映射至进程页表中的内存百分比的一半取值与 swappiness取值之和超过 100时激活该开关。 系统在决策过程中进一步利用了某个危机因子,该因子在每次需要重试页面替换操作时都增加 2倍。如果系统难以定位可回收的未映射页面,则 VM子系统执行反向工作过程并开始对匿名内存进行页换出。如果不想对匿名内存进行页换出,则可以通过为 swappiness参数赋予较低取值来表达。如果 kswapd正在使用大量 CPU资源或者系统正在 iowait状态中耗费时间,则可以增加 swappiness参数值可以提高系统的整体性能。

五、CPU调度器
    线程调度器子系统负责将可用的 CPU资源分配到可运行的线程中。该调度器的行为及决策制定过程与线程的公平性和调度延迟直接相关。公平性可描述为所有线程不仅都能够向前执行而且都以一种均衡的方式向前执行的能力。公平性的相反情况称为饿死,在这种情形下某个特定线程无法继续向前执行。公平性常常难以评判,因为这意味着全局和局部性能间的折衷。调度延迟描述了线程进入可运行状态(TSRUN)后以及在处理器上实际运行(TSONPROC)之间的实际延迟。低效的调度延迟行为会导致应用响应时间出现明显的迟滞。高效且有效的 CPU调度器对于计算平台的运行极为重要, 决定了每个任务(线程)在何时开始运行以及运行的时间长度。 实时任务与分时任务是有区别的, 各自具有不同的目标,并通过调度器中内含的不同调度规则来实现。
    Linux为每个任务分配一个静态优先级,可以通过 nice()接口对其进行修改。 Linux拥有一组优先级类型,用以区别实时任务和分时任务。优先级的取值越低, 任务的逻辑优先级或者换句话说其综合重要性(general importance)就越高。当阐述优先级提高或降低时,在这个上下文中的讨论总是指代逻辑优先级。实时任务的优先级总是高于分时任务。
    Linux 2.6内核的调度器称为 O(1)调度器,是一个多队列调度器,为每个 CPU都分配一个实际的运行队列, 因此是一种 CPU本地调度方法。 O(1)这个标签描述了任务检索的时间复杂度,即无论系统上的工作负荷如何,都可以在常量时间内选定下一个运行任务。
    先前版本的
Linux调度器使用了优度(goodness)概念来确定下一个待执行的线程。所有可运行的任务都保持在单个运行队列上,该队列是处于 TSRUN(TASK_RUNNABLE)状态中的线程所形成的一个链表。 Linux 2.6内核中将唯一的运行队列锁替换为基于每个CPU的锁,从而确保在 SMP系统上具有更好的扩展性。该 O(1)调度器所采用的基于 CPU的运行队列机制将运行队列(按优先级顺序)分解成许多桶(bucket),并使用位图来标识拥有可运行任务的那些桶。如果要定位下一个待执行任务,需要读取该位图以标识出第一个具有可运行任务的桶并选择该桶的运行队列中的第一个任务。
    每个
CPU的运行队列由两个任务列表向量组成, 称为活跃向量(active vector)和超时向量(expired vector)。每个向量索引代表了拥有各自优先级的可运行任务的一个列表。一个任务在执行一段时间后,会从活跃列表移至超时列表,这样确保了所有可运行任务都能够获得执行机会。当该活跃数组为空时, 则通过修改指针来交换超时向量和活跃向量。
    系统有时需要执行一个负载平衡算法对各个运行队列进行平衡处理,以确保每个
CPU上的任务数量都相近。前面提过,调度器需要决定即将运行哪些任务以及任务的运行时间长度。Linux内核中的时间量子定义为系统节拍的倍数。节拍定义为两个连续定时器中断之间的固定增量(1/HZ)Linux 2.6内核中, 参数 HZ被设置为 1 000, 这表明每毫秒激活一次中断例程 scheduler_tick(),此时当前运行的任务即执行了一个节拍。将参数 HZ被设置为 1 000并不会改进系统的响应效率,因为该设置不会影响到实际的调度器时间片。对于主要在数学运算(number-crunching)模式中执行的系统来说,HZ=1 000的设置可能并不合适。在这种环境下,将参数 HZ设置为更低的取值(大约 100)有利于提高整体系统的性能。
    除了静态优先级
(static_prio)之外,每个任务都还维护一个有效优先级(prio)其区别表示基于特定任务的最*均休眠时间(sleep_avg)而得到的特定优先级奖励或处罚。平均休眠时间表示一个任务最近被取消调度的节拍数量。任务的有效优先级决定了其在运行队列的优先级列表中的位置。如果一个任务的有效优先级超出其静态优先级特定程度,则称该任务是交互式的。这种场景只能基于任务 accumulating平均休眠节拍数。Linux 2.6内核中嵌入的交互式估测器框架自动且透明地运行。在 Linux中,高优先级任务到达交互状态所需的平均休眠时间远小于低优先级任务。因为基于平均休眠时间来评估任务的交互性, I/O绑定的任务是到达交互状态的潜在候选,而 CPU绑定的任务通常不被看成是交互式的。实际的时间片被定义为一个任务在将 CPU(主动)转让给另一个任务之前可以执行的最长时间,只是正常任务的静态优先级的线性函数。 优先级本身对应于MIN_TIMESLICE(默认值为10ms)MAX_TIMESLICE(默认值200ms)之间的时间值。一个任务的优先级越高,该任务的当前时间片所减少的每个定时器节拍的时间量就越大。当时间量减至 0时,调度器再次补充时间片数据,重新计算有效优先级,并将该任务重新排队到活跃向量(若该任务被归类为交互式的)或超时向量(若该任务被看作是非交互式的)中。 此时, 系统会从活跃数组中分派另一个任务。 该场景确保了在任何超时任务获得再次运行的机会前,首先执行活跃数组中的任务。如果一个任务被取消调度的话,则在唤醒时不会补充时间片数据,但是其有效优先级可能由于任何 accumulated休眠时间而已经发生变化。如果所有可运行任务都耗尽了自己的时间片并已被移至超时列表中,则应交换超时和活跃向量。 这种技术使得该 O(1)调度器不需要遍历一个可能很长的任务列表(这是 Linux 2.4内核调度器中所需的操作)。这种新型设计的问题是,由于任何潜在的交互行为,以下场景可能会出现:活跃队列继续拥有未被移入超时列表的可运行任务。有 些任务可能因为等待 CPU时间而停止。为了避免这个问题, 首先移入超时列表中的任务要老于 STARVATION_LIMIT(10),并且应交换活跃数组和超时数组。