转至繁体中文版     | 网站首页 | 图文教程 | 资源下载 | 站长博客 | 图片素材 | 武汉seo | 武汉网站优化 | 
最新公告:     敏韬网|教学资源学习资料永久免费分享站!  [mintao  2008年9月2日]        
您现在的位置: 学习笔记 >> 图文教程 >> 数据库 >> MySql >> 正文
走向 Linux 2.6----走进下一个新内核的精彩之作         ★★★★

走向 Linux 2.6----走进下一个新内核的精彩之作

作者:闵涛 文章来源:闵涛的学习笔记 点击数:1411 更新时间:2009/4/22 20:45:57
即将发布的新的稳定的内核支持更多类型的处理器,并且可靠性和可扩展性得到了提高,因而将推动 Linux 得到更广泛的应用。在这里我们将重点介绍一些不同程度的变化,并给出部分代码样例。

Linux 内核的开发已经经历了一个漫长的过程,最初是 Linus Torvalds 于1991年发布的原始的0.1版本,这个版本中包括一个基本的调度器、IPC(进程间通信)和内存管理算法。而现在它已经是一个以往操作系统的实用的替代品,在市场上表现出了强大的竞争力。越来越多的政府机构和IT巨头的注意力正在转向 Linux。从最小的嵌入式设备到 S/390,从手表到大型企业服务器,Linux 现在几乎可以用于所有的地方。

Linux 2.6 是 Linux 开发周期中的下一个主要版本,它包括了一些强有力的特性,这些特性旨在改进高端企业服务器的性能和支持越来越多的嵌入式设备(要了解更详细的关于 Linux 2.6 对大型的、小型的以及多处理器系统支持问题的分析,请参阅参考资料一节中到 Joseph Pranevich 的“Linux 的精彩世界”的链接)。

本文为关注 Linux 的用户分析了 Linux 2.6 的一些重要特性,并且讨论了驱动程序开发人员可能会感兴趣的多方面的变化。

Linux 2.6亮点
无论是对于企业服务器还是对于嵌入式系统,Linux 2.6 都是一个巨大的进步。对高端的机器来说,新特性针对的是性能改进、可扩展性、吞吐率,以及对 SMP 机器 NUMA 的支持。对于嵌入式领域,添加了新的体系结构和处理器类型——包括对那些没有硬件控制的内存管理方案的 MMU-less 系统的支持。并且,和往常一样,为了满足桌面用户群的需要,添加了一整套新的音频和多媒体驱动程序。

在本文中,我们分析了 Linux 2.6的一些最引人关注的特性,但是仍有很多值得关注的变化,包括增强的内核核心转储、快速互斥支持、改进的I/O子系统,等等,在这里我们不能全部讨论。在侧栏中总结了其中一些,其余的我们在参考资料一节中给出了链接。

新的调度器
2.6版本的 Linux 内核使用了由 Ingo Molnar 开发的新的调度器算法,称为O(1)算法,它在高负载的情况下执行得极其出色,并且当有很多处理器时也可以很好地扩展。

在2.4版本的调度器中,时间片重算算法要求在所有的进程都用尽它们的时间片以后,它们的新时间片才会被重新计算。这样的话在一个有很多处理器的系统中,当进程用完它们的时间片以后得等待重算(以得到新的时间片),从而导致大部分的处理器处于空闲状态;这将影响SMP的效率。除此之外,当空闲的处理器开始执行那些时间片尚未用尽的处于等待状态的进程(如果它们自己的处理器忙),会导致进程开始在处理器之间“跳跃”。当一个高优先级进程或者交互式进程发生跳跃时,整个系统的性能就会受到影响。

新的调度器解决上述问题的方法是,基于每个 CPU 来分布时间片,并且取消了全局同步和重算循环。调度器使用了两个优先级数组,即活动数组和过期数组,可以通过指针来访问它们。活动数组中包含了所有映射到某个CPU而且时间片尚未用尽的任务。过期数组中包含了一个时间片已经用尽的所有任务的有序列表。如果所有活动任务的时间片都已用尽,那么指向这两个数组的指针互换,过期数组(包含了准备运行的任务)成为活动数组,而空的活动数组成为包含过期任务的新数组。数组的索引存储在一个64位的位图中,找到最高优先级的任务是很容易的。

新的调度器现在不再有大的 runqueue_lock。它维持每个处理器的运行队列/锁机制,以使得两个不同处理器上的两个进程可以完全并行地休眠、唤醒和上下文切换。重算循环(为进程重新计算时间片)和 goodness 循环已经被取消,O(1)算法用于 wakeup() 和 schedulee()。

新调度器的主要好处包括:

  • SMP效率:如果有工作需要完成,那么所有处理器都会工作。
  • 等待进程:没有进程需要长时间地等待处理器;同时,没有进程会无端地占用大量的CPU时间。
  • SMP进程映射:进程只映射到一个CPU而且不会在CPU之间跳跃。
  • 优先级:不重要的任务的优先级低(反之亦然)。
  • 负载平衡:调度器会降低那些超出处理器负载能力的进程的优先级。
  • 交互性能:使用新的调度器,即便是在非常高负载的情况下,系统花费很长时间来响应鼠标点击或者键盘输入的情况将不会再发生。

内核抢占
内核抢占补丁在2.5系列中就已经被打上,接下来在2.6中也会打。这将显著地降低用户交互式应用程序、多媒体应用程序等类似应用程序的延迟。这一特性对实时系统和嵌入式系统来说特别有用。

2.5的内核抢占模块的工作由 Robert Love 完成。在先前的内核版本中(包括2.4内核),不允许抢占以内核模式运行的任务(包括通过系统调用进入内核模式的用户任务),直到它们自己主动释放 CPU。

在内核2.6中,内核是可抢占的。一个内核任务可以被抢占,为的是让重要的用户应用程序可以继续运行。这样做最主要的优势在于,可以极大地增强系统的用户交互性,用户将会觉得鼠标点击和击键的事件得到了更快速的响应。

当然,不是所有的内核代码段都可以被抢占。可以锁定内核代码的关键部分,不允许抢占。锁定可以确保每个 CPU 的数据结构和状态始终受到保护而不被抢占。

以下的代码片断显示了每个 CPU 的数据结构问题(在SMP系统中):

清单 1. 存在内核抢占问题的代码

          int arr[NR_CPUS];

          arr[smp_processor_id()] = i;
          /* kernel preemption could happen here */
          j = arr[smp_processor_id()]   /* i and j are not equal as
   smp_processor_id() may not be the same */

在这种情形下,如果在特定点发生了内核抢占,任务将会由于重新调度而被分配到其他处理器——smp_processor_id() 将返回一个不同的值。

这种情形应该通过锁定来进行保护。

FPU 模式是另外一种CPU应该被保护起来不被抢占的情形。当内核在执行浮点指令时,FPU 状态不被保存。如果这时发生了抢占,由于重新调度,FPU 状态就会与抢占前完全不同。所以 FPU 代码必须始终被锁定,以防止内核抢占。

锁定可以这样来实现,在关键部分禁止抢占,在之后再激活抢占。以下是在2.6内核中禁止和激活抢占的定义:

  • preempt_enable() -- 抢占计数器减1
  • preempt_disable() -- 抢占计数器加1
  • get_cpu() -- 先后调用 preempt_disable() 和 smp_processor_id()
  • put_cpu() -- 重新激活preemption()

使用这些定义,清单 1可以重写成这样:

清单 2. 使用防抢占锁的代码

        int cpu, arr[NR_CPUS];

         arr[get_cpu()] = i;  /* disable preemption */
         j = arr[smp_processor_id()];
         /* do some critical stuff here */
         put_cpu()    /* re-enable preemption */

注意 preempt_disable()/enable()调用是可以嵌套的。也就是说,preempt_disable() 可以被调用 n 次,只有当第 n 次 preempt_enable() 被调用后,抢占才被重新激活。

当使用自旋锁时,抢占是被隐式地禁止的。例如,一个 spin_lock_irqsave() 调用会隐式地通过调用 preempt_disable() 来防止抢占;spin_unlock_irqrestroe() 通过调用 preempt_enable() 来重新激活抢占。

改进的线程模型以及对 NPTL 的支持
在2.5内核中已经做了很多的改进线程性能的工作。在2.6中改进的线程模型仍然是由 Ingo Molnar 来完成的。它基于一个1:1的线程模型(一个内核线程对应一个用户线程),包括内核内在的对新的 NPTL(Native Posix Threading Library)的支持,这个新的 NPTL 是由 Molnar 和 Ulrich Drepper 合作开发的。

2.6内核中其他引人注目的变化 

  • 文件系统
    对 ext2/ext3 文件系统做了改进,包括对扩展属性和POSIX访问控制列表的支持。NTFS的驱动程序也已经重写,可以支持(reentrant safe)SMP,大于4KB的簇,等等。同时2.6也支持 IBM 的 JFS(journaling file system) 和 SGI 的 XFS。
  • 音频
    对桌面用户而言,一个更令人期待的特性是称为 ALSA(Advanced Linux Sound Architecture) 的新的 Linux 音频体系结构,它取代了缺陷很多的旧的 OSS (Open Sound System)。新的声音体系结构支持USB音频和MIDI设备,全双工重放,等等。在桌面上播放 MP3 和其他音频文件再也不会像以前那样了!
  • 总线
    SCSI/IDE子系统经过大幅度的重写,一些驱动程序仍然处于测试阶段或者收尾阶段。
  • 电源管理
    支持 ACPI(高级电源配置管理界面,Advanced Configuration and Power Interface),用于调整 CPU(可以在不同的负载下使CPU工作于不同的时钟频率以节电)和软件挂起(这一特性仍在测试中)。
  • 联网和IPSec
    内核中加入了对 IPSec (IP安全)的支持,因此也支持 IP 有效负载压缩等各种 RFC。删除了原来内核内置的HTTP服务器 kttpd。IPSec 特性使用了内核提供的新的加密 API。这个加密API中包含了各种流行的算法,比如 MD4,MD5 DES,等等。加入了对新的 NFSv4 (网络文件系统)客户机/服务器的支持。
  • 用户界面层
    2.6内核重写了帧缓冲/控制台层。这将意味着需要更新各种用户空间帧缓冲工具,如 fbset 和 fbdesl。人机界面层还加入了对近乎所有可接入设备的支持,从触摸屏到盲人用的设备,到各种各样的鼠标。

线程操作可以提高速度;2.6内核现在可以处理任意数目的线程,PID最大可以到20亿(IA32上)。

另外一个变化是引入了 TLS(Thread Local Storage)系统调用,这个调用允许分配一个或多个 GDT(Global Descriptor Table)条目,作为线程注册表。每个 CPU 有一个 GDT,每个条目对应一个线程。这样就可以实现一个不受创建的线程数限制的1:1线程模型(因为每一个新的内核线程都是为一个用户线程而创建)。2.4内核中每个处理器最多只能支持8,192个线程。

系统调用 clone 被扩展,以优化线程的创建。如果 CLONE_PARENT_SETID 标志被设置,内核会把线程ID存储在一个给定的内存位置,如果当线程结束时 CLONE_CLEARID 标志被设置,内核就会把那个内存位置清空。这有助于用户级的内存管理去识别没有使用的内存块。同样,对线程注册表的信号安全加载的支持也已经融入到这个体系中。当 pthread_join 发生时由内核根据线程ID来完成 Futex(fast user space mutex)。(要了解futex的更多信息,请参阅参考资料).

POSIX信号处理在内核空间中完成。一个信号会传递给进程中一个可用的线程;销毁信号会终止整个进程。停止和继续信号也会影响整个进程,这样就可以实现对多线程进程的工作控制。

引入了退出系统调用的一个变种,叫做 exit_group(),这个系统调用终止整个进程和它的线程。此外,退出处理通过引入O(1)算法得到了改进,从而可以在两秒内终止一个具有成千上万个线程的进程(而在2.4内核中完成同样的事情需要15分钟)。

修改了 proc 文件系统,不再报告所有的线程而只是报告原始的线程。这样就避免了 /proc 报告速度的下降。内核保证原始的线程在所有其他线程终止之前不会终止。

虚拟内存的变化
从虚拟内存的角度来看,新内核融合了 Rik van Riel 的 r-map (反向映射,reverse mapping)技术,将显著改善虚拟内存 在一定程度负载下的性能。

为了理解反向映射技术,让我们来首先简单了解 Linux 虚拟内存系统的一些基本原理。

Linux 内核工作于虚拟内存模式:每一个虚拟页对应一个相应的系统内存的物理页。虚拟页和物理页之间的地址转换由硬件的页表来完成。对于一个特定的虚拟页,根据一条页表记录可以找到对应的物理页,或者是页无法找到的提示(说明存在一个页错误)。但是这种"虚拟到物理"的页映射不是总是一一对应的:多个虚拟页(被不同的进程共享的页)有可能指向同一个物理页。在这种情况下,每个共享进程的页记录将有指向对应物理页的映射。如果有类似这样的情况,当内核想要释放特定的物理页时,事情会变得复杂,因为它必须遍历所有的进程页表记录来查找指向这个物理页的引用;它只能在引用数达到0时才能释放这个物理页,因为它没有别的办法可以知道是不是还存在实际指向这个页的引用。这样当负载较高时会让虚拟内存变得非常慢。

反向地址映射补丁通过在结构页引入一个叫做 pte_chain 的数据结构(物理页结构)来解决这一问题。pte_chain 是一个指向页的 PTE 的简单链接列表,可以返回特定的被引用页的 PTE 列表。页释放一下子变得非常简单了。 不过,在这种模式中存在一个指针开销。系统中的每一个结构页都必须有一个额外的用于 pte_chain 的结构。在一个256M内存的系统中,有64K个物理页,这样就需要有 64KB * (sizeof(struct pte_chain)) 的内存被分配用于 pte_chain 的结构――一个很可观的数字。

有一些可以解决这个问题的技术,包括从结构页中删掉 wait_queue_head_t 域(用于对页的独占访问)。因为这个等待队列极少用到,所以在 rmap 补丁中实现了一个更小的队列,通过哈希队列来找到正确的等待队列。

尽管如此,rmap 的性能――尤其是处于高负载的高端系统――相对于2.4内核的虚拟内存系统还是有了显著的提高。

Linux 2.6的驱动程序移植
2.6内核给驱动程序开发人员带来了一系列非常有意义的变化。本节重点介绍将驱动程序从2.4内核移植到2.6内核的一些重要方面。

首先,相对于2.4来说,改进了内核编译系统,从而获得更快的编译速度。加入了改进的图形化工具:make xconfig(需要Qt库)和make gconfig(需要GTK库)。

以下是2.6编译系统的一些亮点:

  • 当使用make时自动创建 arch-zImage 和模块
  • 使用 make -jN 可以进行并行的 make
  • make 默认的不是冗余方式(可以通过设置 KBUILD_VERBOSE=1 或者使用 make V=1来设置为冗余方式)
  • make subdir/ 将编译 subdir/ 及其子目录下的所有文件
  • make help 将提供 make 目标支持
  • 在任何一个阶段都不需要再运行 make dep

内核模块加载器也在2.5中完全被重新实现,这意味着模块编译机制相对于2.4有了很大不同。需要一组新的模块工具来完成模块的加载和缷载 (他们的下载链接可以在参考资料中找到),原来的2.4所用的 makefile 在2.6下不能再用。

新的内核模块加载器是由 Rusty Russel 开发的。它使用内核编译机制,产生一个 .ko(内核目标文件,kernel object)模块目标文件而不是一个 .o 模块目标文件。内核编译系统首先编译这些模块,并将其连接成为 vermagic.o。这一过程在目标模块创建了一个特定部分,以记录使用的编译器版本号,内核版本号,是否使用内核抢占等信息。

现在让我们来看一个例子,分析一下新的内核编译系统如何来编译并加载一个简单的模块。这个模块是一个“hello world”模块,代码和2.4模块代码基本类似,只是 module_init 和 module_exit 要换成 init_module 和 cleanup_module (内核2.4.10模块已经使用这种机制)。这个模块命名为 hello.c,Makefile 文件如下:

清单 3. 驱动程序 makefile 文件示例

           KERNEL_SRC = /usr/src/linux
           SUBDIR = $(KERNEL_SRC)/drivers/char/hello/
           all: modules

           obj-m := module.o
           hello-objs := hello.o

           EXTRA_FLAGS += -DDEBUG=1

           modules:
                $(MAKE) -C $(KERNEL_SRC) SUBDIR=$(SUBDIR) modules

makefile 文件使用内核编译机制来编译模块。编译好的模块将被命名为 module.ko,并通过编译 hello.c 和连接 vermagic 而获得。KERNEL_SRC 指定内核源文件所在的目录,SUBDIR 指定放置模块的目录。EXTRA_FLAGS 指定了需要给出的编译期标记。

一旦新模块(module.ko)被创建,它可以被新的模块工具加载或缷载。2.4中的原有模块工具不能用来加载或缷载2.6的内核模块。这个新的模块加载工具会尽量减少在一个设备仍在使用的情况下相应的模块却被缷载的冲突发生,而是在确认这些模块已经没有任何设备在使用后再缷载它。产生这种冲突的原因之一是模块使用计数是由模块代码自己来控制的(通过MOD_DEC/INC_USE_COUNT)。

在2.6中,模块不再需要对引用计数进行加或减,这些工作将在模块代码外部进行。任何要引用模块的代码都必须调用 try_module_get(&module),只有在调用成功以后才能访问那个模块;如果被调用的模块已经被缷载,那么这次调用会失败。相应的,可以通过使用 module_put() 来释放对模块的引用。

内存管理的变化
在2.5的开发过程中,加入了内存池,以满足无间断地进行内存分配。其思想是预分配一个内存池,并保留到真正需要的时候。一个内存池由 mempool_create() 调用来创建(应该包含头文件 linux/mempool.h)。

mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn,
mempool_free_t *free_fn, void *pool_data);

在这里 min_nr 是需要预分配对象的数目,alloc_fn 和 free_fn 是指向内存池机制提供的标准对象分配和回收例程的指针。他们的类型是:

typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data);
typedef void (mempool_free_t)(void *element, void *pool_data);

pool_data 是分配和回收函数用到的指针,gfp_mask 是分配标记。只有当 __GFP_WAIT 标记被指定时,分配函数才会休眠。

在池中分配和回收对象是由以下程序完成的:

void *mempool_alloc(mempool_t *pool, int gfp_mask);
void mempool_free(void *element, mempool_t *pool);

mempool_alloc() 用来分配对象;如果内存池分配器无法提供

[1] [2]  下一页


[C语言系列]C# 和 Linux 时间戳转换  [Web开发]PHP flock文件锁介绍
[Web开发]flock() Linux下的文件锁  [电脑应用]Linux下的六个免费的虚拟主机管理系统介绍
[电脑应用]Linux数据库大比拚  [操作系统]在Windows中玩转Linux操作系统
[办公软件]在RedHat Linux 9里安装gaim0.80  [办公软件]掌握 Linux 调试技术
[办公软件]理解 Linux 配置文件  [聊天工具]Real10 & Xpdf installation on Linux Box
教程录入:mintao    责任编辑:mintao 
  • 上一篇教程:

  • 下一篇教程:
  • 【字体: 】【发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
      注:本站部分文章源于互联网,版权归原作者所有!如有侵权,请原作者与本站联系,本站将立即删除! 本站文章除特别注明外均可转载,但需注明出处! [MinTao学以致用网]
      网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)

    同类栏目
    · Sql Server  · MySql
    · Access  · ORACLE
    · SyBase  · 其他
    更多内容
    热门推荐 更多内容
  • 没有教程
  • 赞助链接
    更多内容
    闵涛博文 更多关于武汉SEO的内容
    500 - 内部服务器错误。

    500 - 内部服务器错误。

    您查找的资源存在问题,因而无法显示。

    | 设为首页 |加入收藏 | 联系站长 | 友情链接 | 版权申明 | 广告服务
    MinTao学以致用网

    Copyright @ 2007-2012 敏韬网(敏而好学,文韬武略--MinTao.Net)(学习笔记) Inc All Rights Reserved.
    闵涛 投放广告、内容合作请Q我! E_mail:admin@mintao.net(欢迎提供学习资源)

    站长:MinTao ICP备案号:鄂ICP备11006601号-18

    闵涛站盟:医药大全-武穴网A打造BCD……
    咸宁网络警察报警平台