收藏 分享(赏)

linux内核设计与实现 第二章.doc

上传人:dzzj200808 文档编号:2641178 上传时间:2018-09-24 格式:DOC 页数:14 大小:115KB
下载 相关 举报
linux内核设计与实现 第二章.doc_第1页
第1页 / 共14页
linux内核设计与实现 第二章.doc_第2页
第2页 / 共14页
linux内核设计与实现 第二章.doc_第3页
第3页 / 共14页
linux内核设计与实现 第二章.doc_第4页
第4页 / 共14页
linux内核设计与实现 第二章.doc_第5页
第5页 / 共14页
点击查看更多>>
资源描述

1、Chapter 2. Getting Started with the KernelIn this chapter, we introduce some of the Basics of the Linux kernel: where to get its source, how to compile it, and how to install the new kernel. We then go over some kernel assumptions, differences between the kernel and user-space programs, and common m

2、ethods used in the kernel.The kernel has some intriguing differences over other beasts, but certainly nothing that cannot be tamed. Lets tackle it.第二章 从内核出发在这一章,我们介绍 Linux内核一些基本常识:从何处获取源码,如何编译它,又如何安装新内核。那么,让我们考察一下内核的一些状态、内核程序与用户空间程序的差异,以及内核所用一般函数的特点。 内核像性格怪异的猛兽,但并非不可驯服。让我们来驾驭它。Obtaining the Kernel S

3、ourceThe current Linux source code is always available in both a complete tarball and an incremental patch from the official home of the Linux kernel, http:/www.kernel.org.Unless you have a specific reason to work with an older version of the Linux source, you always want the latest code. The reposi

4、tory at kernel.org is the place to get it, along with additional patches from a number of leading kernel developers.2.1获取内核源码在 Linux内核官方网站 http:/www.kernel.org,可以随时获取当前版本的 Linux源代码,可以是完整的压缩形式,也可以是增量补丁形式。除非特殊情况下需要 Linux源码的旧版本,一般都希望拥有最新的代码。kernel.org是源码的库存之处,那些领导潮流的内核开发者所发布的增量补丁也放在这里。Installing the Ke

5、rnel SourceThe kernel tarball is distributed in both GNU zip (gzip) and bzip2 format. Bzip2 is the default and preferred format, as it generally compresses quite a bit better than gzip. The Linux kernel tarball in bzip2 format is named linux-x.y.z.tar.bz2, where x.y.z is the version of that particul

6、ar release of the kernel source. After downloading the source, uncompressing and untarring it is simple. If your tarball is compressed with bzip2, run$ tar xvjf linux-x.y.z.tar.bz2If it is compressed with GNU zip, run$ tar xvzf linux-x.y.z.tar.gzThis uncompresses and untars the source to the directo

7、ry linux-x.y.z.2.1.1安装内核源代码内核压缩以 GNU zip(gzip)和 bzip2两种形式发布。bzip2 是缺省和首选形式,因为它在压缩上比 gzip有相当的优势。以 bzip2形式发布的 Linux内核叫做 linux-x.y.z.tar.bz2,这里 x.y.z是内核源码的具体版本。下载了源代码之后,就可以轻而易举地对其解压。如果压缩形式是 bzip2,则运行:$ tar xvjf linux-x.y.z.tar.bz2如果压缩形式是 GNU的 zip,则运行$ tar xvzf linux-x.y.z.tar.gz解压后的源代码位于 linux-x.y.z.目录

8、下。何处安装源码内核源码一般安装在/usr/src/linux 目录下。但请注意,不要把这个源码树用于开发。相反,编译你的 C库所用的内核版本就链接到这颗树。此外,不要以 root身份对内核进行修改,而应当是,建立自己的主目录,仅以 root身份安装新内核。即使在安装新内核时,/usr/src/linux目录都应当原封不动。Where to Install and Hack on the SourceThe kernel source is typically installed in/usr/src/linux. Note that you should not use this sourc

9、e tree for development. The kernel version that your C library is compiled against is often linked to this tree. Besides, you do not want to have to be root to make changes to the kernelinstead, work out of your home directory and use root only to install new kernels. Even when installing a new kern

10、el, /usr/src/linux should remain untouched.2.2 2 使用补丁在 Linux内核社区中,补丁是通用语。你可以以补丁的形式发布对代码的修改,也可以以补丁的形式接收其他人所做的修改。时间流失,内核版本在不断更新,增量补丁可以作为版本转移的桥梁。你不再需要下载内核源码的全部压缩,而只需给旧版本打上一个增量补丁,让其旧貌换新颜。这不仅节约了带宽,还省了时间。要应用增量补丁,从你的内部源码树开始,只是运行:$ patch p1 some_other_file一旦你需要查看编译的输出信息,你可以查看这个文件。不过,因为错误和警告都会在屏幕上显示,所以你需要看这个

11、文件的可能性不大。事实上,我只不过敲入如下命令$ make /dev/null 这就把无用的输出信息重定向到永无返回值的黑洞/dev/null。which redirects all the worthless output to that big ominous sink of no return, /dev/null.衍生多个编译作业 make(1)程序能把编译过程拆分成多个作业。其中的每个作业独立并发地运行,这有助于极大地提高多处理器系统上的编译过程,也有利于改善处理器的利用率,因为编译大型源代码树也包括 I/O等待所花费的时间(也就是处理器空下来等待 I/O请求完成所花费的时间)。缺省

12、情况下,make(1)只衍生一个作业。Makefiles 时常把文件之间的依赖弄乱。对于不正确的依赖,多个作业可能互相踩踏,会导致编译过程出错。当然,内核的 Makefiles没有这样的编码错误。为了以多个作业编译内核,使用以下命令:$ make -jn这里,n 是要衍生的作业数,在实际中,每个处理器上一般衍生一个或者两个作业。例如,在一个双处理器上,你可以输入如下命令:$ make j4利用出色的 distcc(1) 或者 ccache(1)工具,也可以动态地改善内核的编译时间。Spawning Multiple Build JobsThe make(1) program provides

13、a feature to split the build process into a number of jobs. Each of these jobs then runs separately and concurrently, significantly speeding up the build process on multiprocessing systems. It also improves processor utilization because the time to build a large source tree also includes some time s

14、pent in I/O wait (time where the process is idle waiting for an I/O request to complete).By default, make(1) spawns only a single job. Makefiles all too often have their dependency information screwed up. With incorrect dependencies, multiple jobs can step on each others toes, resulting in errors in

15、 the build process. The kernels Makefiles, naturally, have no such coding mistakes. To build the kernel with multiple jobs, use$ make -jnwhere n is the number of jobs to spawn. Usual practice is to spawn one or two jobs per processor. For example, on a dual processor machine, one might do$ make j4Us

16、ing utilities such as the excellent distcc(1) or ccache(1) can also dramatically improve kernel build time.Installing the Kernel安装内核在内核编译好之后,你还需要安装它。怎么安装就和体系结构以及启动引导工具(boot loader)息息相关了查阅启动引导工具的说明,按照它的指导将内核映像拷贝到合适的位置,并且按照启动要求安装它。一定要保证随时有一个或两个可以启动的内核,以防新编译的内核出现问题。举个例子,在使用 grub的 X86系统上,你可能需要把 arch/i38

17、6/boot/bzImage拷贝到/boot目录下,把它命名为像 vmlinuz-version,并且编辑/etc/grub/grub.conf 文件,为新内核建立一个新的启动项 。使用 LILO启动的系统应当编辑/etc/lilo.conf,然后运行lilo(8)。所幸,模块的安装是自动的,也是独立于体系结构的。以 root身份,只要运行:% make modules_install就可以把所有已编译的模块安装到正确的主目录/lib 下。Systems using LILO to boot would instead edit /etc/lilo.conf and then rerun.In

18、stalling modules, thankfully, is automated and architecture-independent. As root, simply run% make modules_installto install all the compiled modules to their correct home in /lib.The build process also creates the file System.map in the root of the kernel source tree. It contains a symbol lookup ta

19、ble, mapping kernel symbols to their start addresses. This is used during debugging to translate memory addresses to function and variable names.编译时也会在内核代码树的根目录下创建一个 System.map文件。这是一份符号对照表,用以将内核符号和它们的起始地址对应起来。调试的时候,如果需要把内存地址翻译成容易理解的函数名以及变量名,这就会很有用。1.6 内核开发的特点相对于用户空间内应用程序的开发,内核开发有很大的不同。这种差异给开发内核带来了特别

20、的挑战,但这并不意味着开发内核一定比开发应用程序难很多。这种差异使内核成了一只性格迥异的猛兽。一些常用的准则被颠覆了,而又必须建立许多全新的准则。尽管有许多差异一目了然(人人都知道内核可以做它想做的任何事) ,但还是有一些差异晦暗不明。最重要的差异包括以下几种:* 内核编程时不能访问 C库。* 内核编程时必须使用 GNU C。* 内核编程时缺乏像用户空间那样的内存保护机制。* 内核编程时浮点数很难使用。* 内核只有一个很小的定长堆栈。* 由于内核支持异步中断、抢占和 SMP,因此必须时刻注意同步和并发。* 要考虑可移植性的重要性。让我们仔细考察一下这些要点,所有这些东西在内核开发中必须时刻牢记

21、。1.6.1 没有 libc库与用户空间的应用程序不同,内核不能链接使用标准 C函数库(其他的那些库也不行) 。造成这种情况的原因有许多,其中就包括先有鸡还是先有蛋这个驳论。不过最主要的原因还是在于速度和大小。对内核来说,完整的 C库太大了即便是从中抽取一个合适的子集大小和效率都不能被接受。别着急,大部分常用的 C库函数在内核中都已经得到实现了。比如说操作字符串的函数组就位于 lib/string.c文件中。只要包含头文件,就可以使用它们。头文件当我在本书的任何地方谈及头文件时,都指的是组成内核源代码树的内核头文件。内核源代码文件不能包含外部头文件,就像它们不能用外部库一样。Header Fi

22、lesWhen I talk about header files here or elsewhere in this book I am referring to the kernel header files that are part of the kernel source tree. Kernel source files cannot include outside headers, just as they cannot use outside libraries.在所有没有实现的函数中,最著名的就数 printf()函数了。内核代码虽然无法调用printf(),但它可以调用 p

23、rintk()函数。printk()函数负责把格式化好的字符串拷贝到内核日志缓冲区上,这样,syslog 程序就可以通过读取该缓冲区来获取内核信息。printk()的用法很像 printf():printk(“Hello world! A string: %s and an integer: %dn“, a_string, an_integer);printk()和 printf()之间的一个显著区别在于 printk()允许你通过指定一个标志来设置优先级。Syslog 会根据这个优先级标志来决定在什么地方显示这条系统消息。下面是一个使用这种优先级标志的例子:printk(KERN_ERR “

24、this is an error!n“);我们在这本书里很多地方都要用到 printk()。在后续章节中有关于 printk()函数的更详细的信息。1.6.2 GNU C像所有自视清高的 Unix内核一样,Linux 内核是用 C语言编写的。让人略感惊讶的是,内核并不完全符合 ANSI C标准。实际上,只要有可能,内核开发者总是要用到 gcc提供的许多语言扩展部分。 (gcc 是多种 GNU编译器的集合,它包含的 C编译器既可以编译内核,也可以编译 Linux系统上用 C写的其他代码。 )内核开发者使用的 C语言涵盖了 ISO C995标准和 GNU C扩展特性。这其中的种种变化把 Linux

25、内核推向了 gcc的怀抱。尽管目前出现了一些新的编译器如 Intel C,已经支持了足够多的 gcc扩展特性,完全可以用来编译 Linux内核了。Linux 内核用到的 ISO C995标准的扩展没有什么特别之处,而且 C991作为 C语言官方标准的修订本,不可能有大的或是激进的变化。让人感兴趣的,与标准 C有区别,而通常也是人们不熟悉的那些变化,多数集中在 GNU C上。就让我们研究一下内核代码中所使用到的 C语言扩展中让人感兴趣的那部分吧。1. 内联(inline)函数GNU的 C编译器支持内联函数。Inline 这个名称(译者注:inline 翻译成内联似乎并不贴切,直译应该是“在字里行

26、间展开”的意思,不过约定俗成,我们也把它翻译成“内联”)就可以反映出它的工作方式,函数会在它所调用的位置上展开。这么做可以消除函数调用和返回所带来的开销(寄存器存储和恢复) ,而且,由于编译器会把调用函数的代码和函数本身放在一起进行优化,所以也有进一步优化代码的可能。不过,这么做是有代价的(天下没有免费的午餐) ,代码会变长,这也就意味着占用更多的内存空间或者占用更多的指令缓存。内核开发者通常把那些对时间要求比较高,而本身长度又比较短的函数定义成内联函数。如果你把一个大块头的程序做成了内联函数,却并不需要争分夺秒,反而反复调用它,又怎能不让人叹气呢?1ISO C99 是 ISO C 的最新修订

27、版。 C99 相对于前一个修订版作了许多加强,ISO C90 引入了命名结构体初始化和 complex 数据类型。后者你不能在内核内安全地使用。定义一个内联函数的时候,你需要使用 static作为关键字,并且用 inline限定它。比如:static inline void dog(unsigned long tail_size)内联函数必须在使用之前就定义好,否则编译器就没法把这个函数展开。实践中一般在头文件中定义内联函数。由于使用了 static作为关键字进行限制,所以编译时不会为内联函数单独建立一个函数体。如果一个内联函数仅仅在某个源文件中使用,那么也可以把它定义在该文件开始的地方。在内

28、核中,为了类型安全起见,优先使用内联函数而不是复杂的宏。In the kernel, using inline functions is preferred over complicated macros for reasons of type safety.2. 内联汇编gcc编译器支持在 C函数中嵌入汇编指令。当然,在内核编程的时候,只有知道对应的体系结构,才能使用这个功能。Linux的内核混合使用了 C和汇编语言。在偏近体系结构的底层或对执行时间要求严格的地方,一般使用的是汇编语言。而内核其他部分的大部分代码是 C语言写的。3. 分支声明对于条件选择语句,gcc 内建了一条指令用于优化,

29、在一个条件经常出现,或者该条件很少出现的时候,编译器可以根据这条指令对条件分支选择进行优化。内核把这条指令封装成了宏,比如 likely()和 unlikely(),这样使用起来比较方便。例如,下面是一个条件选择语句:if (foo) /* */如果想要把这个选择标记成绝少发生的分支:/* 我们认为 foo绝大多数时间都会为 0 */if (unlikely(foo) /* */相反,如果我们想把一个分支标记为通常为真的选择:/* 我们认为 foo通常都不会为 0 */if (likely(foo) /* */在你想要对某个条件选择语句进行优化之前,你一定要搞清楚其中是不是存在这么一个条件,在

30、绝大多数情况下都会成立。这点十分重要:如果你的判断正确,确实是这个条件占压倒性的地位,那么性能会得到提升,如果你搞错了,性能反而会下降。在对一些错误条件进行判断的时候,常常用到 unlikely()和 likely()。你可以猜到,unlikely()在内核中得到广泛使用,因为 if语句往往判断一种特殊情况。As one might expect, unlikely() finds much more use in the kernel because if statements tend to indicate a special case.1.6.3 没有内存保护机制如果一个用户程序试图进

31、行一次非法的内存访问,内核会发现这个错误,发送 SIGSEGV,并结束整个进程。然而,如果是内核自己非法访问了内存,那后果就很难控制了。 (毕竟,有谁能照顾内核呢?)内核中发生的内存错误会导致 oops,这是内核中出现的最常见的一类错误。在内核中,你不应该去做访问非法的内存地址,引用空指针之类的事情,否则它可能会死掉,却根本不知会你一声在内核里,风险常常会比外面大一些。此外,内核中的内存都不分页。也就是说,你每用掉一个字节,物理内存就减少一个字节。所以,在你想往内核里加入什么新功能的时候,要记住这一点。1.6.4 不要轻易在内核中使用浮点数在用户空间的进程内进行浮点操作的时候,内核会完成从整数

32、操作到浮点数操作的模式转换。在执行浮点指令时到底会做些什么,因体系结构不同,内核的选择也不同,但是,内核通常捕获陷阱并做相应处理。和用户空间进程不同,内核并不能完美地支持浮点操作,因为它本身不能陷入。 在内核中使用浮点数时,除了要人工保存和恢复浮点寄存器,还有其他一些琐碎的事情要做。如果要直截了当的回答,那就是:别这么做了,不要在内核中使用浮点数。1.6.5 容积小而固定的栈用户空间的程序可以从栈上分配大量的空间来存放变量,甚至巨大的结构体或者是包含许多数据项的数组都没有问题。之所以可以这么做,是因为用户空间的栈本身比较大,而且还能动态的增长(年长的开发者回想一下 DOS那个年代,这种缺少智能

33、的操作系统即使在用户空间也只有固定大小的栈)。developers of older, less intelligent operating systems say, DOS might recall a time when even user-space had a fixed-sized stack)。内核栈的准确大小随体系结构而变。在 x86上,栈的大小在编译时配置,可以是 4KB也可以是 8KB。从历史上说,内核栈的大小是两页,这就意味着,32 位机的内核栈是 8KB,而64位机是 16KB,这是固定不变的。每个处理器都有自己的栈。The exact size of the kerne

34、ls stack varies by architecture. On x86, the stack size is configurable at compile-time and can be either 4 or 8KB. Historically, the kernel stack is two pages, which generally implies that it is 8KB on 32-bit architectures and 16KB on 64-bit architecturesthis size is fixed and absolute. Each proces

35、s receives its own stack.关于内核栈的更多内容,会在后面的章节中讨论。1.6.6 同步和并发内核很容易产生竞争条件。和单线程的用户空间程序不同,内核的许多特性都要求能够并发的访问共享数据,这就要求有同步机制保证不出现竞争条件,特别是:* Linux is a preemptive multi-tasking operating system. Processes are scheduled and rescheduled at the whim of the kernels process scheduler. The kernel must synchronize b

36、etween these tasks.* Linux是抢占多任务操作系统。内核的进程调度程序即兴对进程进行调度和重新调度。内核必须对这些任务同步。* Linux内核支持多处理器系统。所以,如果没有适当的保护,在两个或两个以上的处理器上运行的代码很可能会同时访问共享的同一个资源。* 中断是异步到来的,完全不顾及当前正在执行的代码。也就是说,如果不加以适当的保护,中断完全有可能在代码访问共享资源的当间到来,这样,中段处理程序就有可能访问同一资源。* Linux内核可以抢占。所以,如果不加以适当的保护,内核中一段正在执行代码可能会被另外一段代码抢占,从而有可能导致几段代码同时访问相同的资源。常用的解

37、决竞争的办法是自旋锁和信号量。我们将在后面的章节中详细讨论同步和并发执行。1.6.7 可移植性的重要性尽管用户空间的应用程序不太注意移植问题,然而 Linux却是一个可移植的操作系统,并且要一直保持这种特点。也就是说,大部分 C代码应该与体系结构无关,在许多不同体系结构的计算机上都能够编译和执行,因此,必须把体系结构相关的代码从内核代码树的特定目录中适当地分离出来。and that architecture-dependent code must be properly segregated in system-specific directories in the kernel source

38、 tree。诸如保持字节序、64 位对齐、不假定字长和页面长度等一系列准则都有助于移植性。移植性的深度讨论将在后面的章节进行。A handful of rules such as remain endian neutral, be 64-bit clean, do not assume the word or page size, and so on go a long way. Portability is discussed in extreme depth in a later chapter.1.7 小结内核的确是一头独一无二的猛兽:没有内存保护,没有靠得住的 libc,小的堆栈,庞大

39、的源码树。Linux 内核遵循它自己的游戏规则,以大人物的架势运行, 运行足够长的时间后才停止,打破了我们惯以为常的习俗。尽管如此,内核不外乎就是一个程序,它与我们司空见惯的程序没有多大区别。不必望而生畏:直面它、呼唤它、摆布它。 意识到内核并不像咋看起来那样使人畏惧,这就是良好的开端。不过,要梦想成真,你必须全身心地投入,阅读源码、剖析源码,并毫不气馁。我希望第一章的介绍和本章的基础知识为贯穿本书其余部分的学习打下良好的基础。在后续的章节中,我们将着眼于介绍内核各个子系统详细而具体的概念。So Here We AreThe kernel is indeed a unique and inim

40、itable beast: No memory protection, no tried-and-true libc, a small stack, a huge source tree. The Linux kernel plays by its own rules, running with the big boys and stopping just long enough to break the customs with which we are familiar. Despite this, however, the kernel is just a program. It is

41、not very different from the usual, the accustomed, the status quo. Do not be afraid: Stand up to it, call it names, push it around.Realizing that the kernel is not as daunting as first appearances might suggest is the first step on the road to having everything just make sense. To reach that utopia,

42、 however, you have to jump in, read the source, hack the source, and not be disheartened.The introduction in the previous chapter and the basics in this chapter willI hopelay the foundation for the monument of knowledge we will construct throughout the rest of this book. In the following chapters, we will look at specific concepts of and subsystems in the kernel.

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 大学课件

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报