Linux Kernel 2x系列主要发布记录
Linux内核开发者Willy Tarreau在邮件中透露,Linux kernel 2.4分支即将终结,将不再提供更新。他曾在2010年底称,如果在一年内没有关键性修复被合并到2.4分支,该分支的生命周期将在一年后(2011年12月)结束。现在又过去4个月了,Willy Tarreau称不能再推迟了。在去年结束该分支可能对于部分用户来说会有一些困难,但是该分支已经没有真正重要的代码合并进来了,已经到了最后的结束期限了。
对于仍在使用2.4分支的开发者,内核团队给出了一个Git树,并称在今后的一段时间内,可能会发布一些重要的修复。 内核团队建议开发者尽快将内核版本升级至2.6分支或更高的3.x分支,目前最新的稳定分支是3.3.1,可以通过官网获取。
早期的 Linux 内核并不很适合充当需要高并发度的数据库服务器。这些内核并没有随着系统上的处理器数量的增加而提供良好的扩展性,因为早期内核的开发由单处理器机器所驱动。迁移至多处理器平台时需要许多全局内核锁的支持,否则会导致串行化问题。
其中最大的锁是用于保护单个未排序的运行队列的自旋锁(spin lock)。 在单处理器环境中, 单个队列足以调度所有可运行任务。然而在SMP环境中,单个运行队列并不够,这会成为一个瓶颈。随着处理器数量的增多,在运行队列上锁竞争的可能性也增加。另外,由于该队列是未排序的,当锁被持有时调度器需要检查队列中的所有任务以便确定每个任务的良好度(每个任务被赋予一个良好度取值,用于确定哪个任务将是被调度至处理器上运行的最佳候选)。这增加了锁持有时间,从而增加了锁竞争的可能性或加剧当前的竞争条件。
在 Linux 2.5内核中, 这个唯一运行队列被删除并替换为基于 CPU的运行队列;拥有多个运行队列删除了对单个全局锁的需求并改进了整体的可扩展性。另外,每个运行队列都维护一个优先级列表,有助于调度器选择要运行的最佳任务。这种优先排序可以减少队列上的锁持有时间,并进一步减少了锁竞争。
内存管理
大型对称多处理(SymmetricalMultiprocessing,SMP)服务器上运行的场景。这种服务器常常配置了海量物理内存。系统的大部分内存都被分配给数据库缓冲区 cache区域(称为数据库缓冲区)。数据库缓冲区用于缓存从磁盘读至内存中的表和索引数据页。由于磁盘访问相对于内存访问来说是缓慢的操作,将更多内存分配给数据库缓冲区可以极大改进数据库性能。适当调优的数据库会占用大部分可用内存,而为其他正在运行的应用和操作系统只保留够用的内存大小。为数据库分配过多内存会剥夺其他应用可用的内存空间。另外过度分配内存会导致过多的交换,这对于性能极为有害。
Linux内核如何管理物理内存对于数据库服务器的性能而言也很重要。在 IA-32体系结构中, 内核基于 4KB大小的页面管理内存。 另一方面, 大多数当代的处理器都支持更大的页面(高达数兆字节)。对于许多应用来说,4KB页面大小是理想的。小页面可以减少内部分段,将数据换入和换出内存时会产生更小的开销,并确保使用中的虚存驻留于物理内存中。大部分数据库服务器对于其数据库缓冲区都使用大型共享内存段。其结果是需要大量页表项(PageTableEntrie, PTE)。这给内存管理器添加了很大的维护开销。 例如,假若某个数据库服务器使用 4MB大小的共享内存段为其缓冲区分配 1GB共享内存。使用4KB页面大小的内核需要 262 144个 PTE,这个庞大的页表数量会给内存管理器增添极大的维护开销。当然上述数据是过分简化的计算值。实际的 PTE数将远大于这个数值,因为 Linux无法对共享内存的页表进行共享。
Linux 2.6内核中提供了对更大页面的支持。进程可以从内存池中显式请求大页面。使用大页面可以减少当应用请求大型内存块时所需的 PTE数。因此,维护 PTE所需的计算成本也得以减少。对于前面的示例,如果页面大小是4MB而不是4KB,那么1GB数据库缓冲区只需要 1 024个 PTE(这个计算也被过度简化)。大页面支持特性的另一个优点是增加了 TLB(translation lookaside buffer)所覆盖的内存范围, 从而降低了TLB不命中的概率。与页表访问相比较而言,TLB访问的速度极快,因为它是 CPU缓存区域。
Linux Kernel v2.6.7 之后的 32 位模式下的虚拟内存布局方式如下:

Kernel Space(内核空间)— 存储内核和驱动程序的代码和数据;
Stack(栈区)— 存储程序执行期间的本地变量和函数的参数,从高地址向低地址生长;
Memory Mapping Segment(内存映射区)— 简称为 mmap,用来文件或其他对象映射进内存;
Heap(堆区)— 动态内存分配区域,通过 malloc、new、free 和 delete 等函数管理;
BSS segment(未初始化变量区)— 存储未被初始化的全局变量和静态变量;
DATA segment(数据区)— 存储在源代码中有预定义值的全局变量和静态变量;
TEXT segment(代码区)— 存储只读的程序执行代码,即机器指令。
其中 Heap 和 Mmap 区域是可以提供给用户程序使用的虚拟内存空间。
Heap 操作
操作系统提供了 brk () 函数,c 运行时库提供了 sbrk () 函数从 Heap 中申请内存,函数声明如下:
int brk(void *addr);
void *sbrk(intptr_t increment);
brk()通过设置进程堆的结束地址进行内存分配与释放,即可以一次性的分配或释放一整段连续的内存空间。比较适合于一次性分配大块内存的情况,如果设置的结束地址过大或过小会造成内存碎片或内存浪费的问题。
sbrk()函数通过传入的 increment 参数决定增加或减少堆空间的大小,可以动态的多次分配或释放空间达到需要多少内存就申请多少内存的效果,有效避免了内存碎片和浪费问题。
Mmap 操作
在 Linux 中提供了 mmap () 和 munmap () 函数操作虚拟内存空间,函数声明如下:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
其中 mmap 能够将文件或者其他对象映射进内存,munmap 能够删除特定地址区域的内存映射。
内存分配器
开源社区公开了很多现成的内存分配器,包括 dlmalloc、ptmalloc、jemalloc、tcmalloc......,glibc 用的就是 ptmalloc 内存分配器。
I/O 管理
数据库性能严重依赖于高效快速的I/O操作。由于有可能要处理 TB量级的数据,任何I/O瓶颈都会导致数据库服务器难以满足商业需求。 数据库管理员(DBA)通常花费大量时间和金钱对I/O子系统进行优化,以便减少I/O延迟并最大化I/O吞吐率。早期 Linux内核存在着许多阻碍 I/O性能的缺陷,也缺乏诸如原始I/O、向量I/O、异步I/O以及直接I/O等特性, 这限制了 DBA对在其他平台上已知能够改善数据库性能的技术加以利用的能力。另外,功能上的匮乏,例如回弹缓冲和单个I/O请求队列锁,增添了不必要的系统成本并引入了串行化问题。幸运的是,这些问题在Linux 2.6内核中或通过对2.4内核打补丁都已被消除。
1、回弹缓冲区
在早期的 Linux 内核(早期的 2.4及先前的内核版本)中,设备驱动程序无法直接访问高端内存中的虚址。换言之,这些设备驱动程序无法对高端内存执行直接内存访问I/O。相反,内核在低端内存中分配缓冲区,数据通过内核缓冲区在高端内存和设备驱动程序之间传输。这个内核缓冲区通常称为回弹缓冲区(bouncebuffer), 该过程被称为回弹或回弹缓冲。由于数据库服务器的 I/O密集特征,回弹过程严重地降低了数据库服务器的性能。首先,回弹缓冲区会消耗低端内存,这可能导致内存短缺问题。其次,过量的回弹操作会引起系统占用时间较长, 导致数据库系统完全变成 CPU绑定的。数据库服务器性能领域的一个主要进展即是在最近的内核中消除了回弹缓冲机制。
2、原始I/O
Linux 2.4内核中引入的块设备原始(raw)I/O接口提供了从用户空间缓冲区直接执行I/O的能力,而不必通过文件系统接口进行额外复制。 raw接口的使用避免了文件系统层次上的操作,因此能够极大改进数据库服务器的 I/O性能。然而,应该注意到,并非所有的数据库工作负荷都能够受益于 raw I/O的使用。如果需要经常访问相同数据,raw I/O就无法利用文件系统缓冲区 cache。raw I/O接口使得 DBA在针对性能目标而进行数据库设计时具有更多的灵活性。基于数据库服务器的 I/O特征, DBA可选择使用 raw接口以获得更快的 I/O操作,或者使用文件系统缓存机制并减少磁盘访问操作,或者组合使用上述两种机制,这依赖于对数据库中的表的访问方式。
3、向量I/O
Linux 2.6内核通过readv和writev接口提供了向量I/O(vectored I/O,也称scattered I/O)的完整实现。向量读操作接口 readv将磁盘上的连续页面读入内存中的非连续页面上;与之相反,向量写操作接口 writev通过单个函数调用将内存中的非连续页面写到磁盘上。这种 I/O机制有利于频繁执行大量串行 I/O操作的数据库服务器。如果没有正确的向量读写实现,则执行串行 I/O的应用会完成以下操作之一:
执行数据库页面大小的 I/O操作。
执行大型块 I/O操作,并使用 memcpy函数在读/写缓冲区与数据库缓冲区之间复制数据页面。
在扫描TB量级的数据时,这两种方法都会产生昂贵的计算开销。
4、异步I/O
异步I/O机制为应用提供了发出 I/O请求后无需阻塞并等待该 I/O完成的能力。该机制很适合数据库服务器。其他平台提供异步 I/O接口已有一段时间,但该特性在 Linux上还相对较新。在没有异步 I/O机制的情况下要提高 I/O吞吐率,数据库服务器通常创建许多专门执行 I/O的进程或线程。由于大量进程/线程都在执行 I/O活动, 数据库应用不再阻塞于单个 I/O请求上。 使用大量进程/线程方式的缺点是创建、 管理和调度这些进程/线程会产生额外的开销。
GNU C Library(GLIBC)异步 I/O接口通过用户级线程来执行阻塞 I/O操作。这种方法只是使得请求对于应用而言看起来是异步的,但与前面描述的方法并无区别,也面临着同样的性能缺陷。为了消除该问题, Linux 2.6 内核中引入了内核异步 I/O(Kernel AsynchronousI/O, KAIO)接口。 KAIO在内核而不是用户空间中以线程方式实现异步 I/O
机制,确保了真正的异步性。
5、直接I/O
直接 I/O具有可与原始 I/O相媲美的性能,又拥有文件系统的额外灵活性。因此对于希望在保持数据库维护灵活性的同时又使用文件系统作为存储介质的数据库管理员来说,直接I/O是具有吸引力的机制。
使用文件系统而不是 raw设备作为存储介质的主要吸引力是因为其易于调整数据库存储器的容量。只需添加更多的磁盘就可以增加文件系统的大小。 文件系统的另一个优点是提供了许多可用的工具,例如, DBA可以使用 fsck来帮助维护数据完整性。
将文件系统用作没有直接 I/O接口的存储介质也存在着一些缺点。其中之一就是缓冲区 cache守护进程所增加的开销,该进程会主动地将脏页面清空到磁盘上。这种活动所消耗的 CPU周期对于 CPU密集的数据库工作负荷来说是宝贵资源;另一个缺点与缓冲区cache不命中问题有关。如果数据不经常重用,其开销会很大。
对于大型数据库系统而言,会为数据库缓冲区分配内存。大型的数据库缓冲区降低了数据库应用的磁盘读写频率,也减少了文件系统缓冲区 cache可用的内存量。这会对服务器性能产生双面影响。首先, 缓冲区 cache守护进程更积极地将脏页面清空到磁盘上(页面更快地变脏)。 其次, 对于容量更小的缓冲区 cache, 数据驻留于其中的可能性要小得多,这增加了缓冲区 cache不命中的可能性 。
6、块I/O
设备驱动程序以称为块的一组字节为单位来传输数据。块大小被设置为设备的扇区大小,通常为 512B(尽管许多硬件设备及其相关驱动程序可以处理更大的传输文件)。每个块在内存中都关联着一个缓冲区头部结构。当来自应用的读请求通过read调用到达时,设备驱动程序将读缓冲区划分成扇区大小的多个块,在与每个块相关的缓冲区头部里填充来自物理设备的数据,然后再将这些块接合为原始的读缓冲区。类似地,当来自应用的一个写请求到达时,设备驱动程序将写缓冲区划分成扇区大小的块,并使用相关缓冲区头部的取值来更新磁盘上的物理数据。在 Lnux2.6内核中,原始 I/O的块大小为 4096B而不是 512B。 这个简单的变化通过减少原始 I/O操作所需的缓冲区头部数量, 改进了整体 I/O吞吐率和 CPU利用率。缓冲区头部更少的话,也可以降低维护其所需的内核开销,而大型块的使用减少了设备驱动程序针对一个 I/O请求需要执行的划分—接合操作数。
7、I/O请求锁
Linux内核为每个块设备都维护一个 I/O请求队列。 该队列以一种最大化系统性能的方式对 I/O请求进行排序。在 Lnux 2.2和早期的 2.4内核中,所有的请求队列由一个称为io_request_lock的全局自旋锁加以保护。对于在尤许多磁盘构成的 SMP机器上运行的数据库服务器来说,这个 I/O请求锁会导致严重的串行化问题。
最近的内核版本里已从 SCSI子系统中删除了这个全局锁(io_request_lock)。在其位置上,每个 I/O请求队列都由自己的 request_queue_lock锁加以保护。 I/O请求的串行化现象仍会出现,但只发生在单个请求队列上,而不会发生在 SCSI子系统中的所有请求队列上。
从 Linux 2.4版本转换至 2.6版本后出现的大量内核变化改进了数据库服务器性能。诸如多个运行队列等特性的引入通过删除系统中的串行点增强了 SMP扩展性。诸如向量I/O、异步 I/O和直接 I/O等变化所提供的内核服务已在其他平台上证明有助于提高数据库性能。这些改进特性多数已包含在 SUSE或 RHEL基于 2.4内核的发行版本中。随着Linux不断地向数据库服务器领域扩展,更多的性能瓶颈将会显现出来,从而持续推动内核开发以改进性能,并且在此过程中使得 Linux成为各类数据库应用工作负荷的一种更有吸引力的解决方案。