⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 4.txt

📁 Linux内核源代码分析/电子书
💻 TXT
📖 第 1 页 / 共 3 页
字号:
图4-1   描述用缓存这个if程序块为记录表格分配内存,并把所有项都清零。注意到如果prof_shift是0(默认值),那么记录功能就被关掉了,if程序块不再被执行,也不为表格分配空间。19846:内核通过调用sti(UP版本的13104行,注意该主题在第6章中有更详细的介绍)开始接收硬件中断。首先需要激活定时器中断,以便后来对calibrate_delay(19654行)的调用可以计算机器的BogoMIPS的值(在下一节“BogoMIPS”中介绍)。因为一些设备驱动程序需要BogoMIPS的值,所以内核必需在大部分硬件、文件系统等等初始化之前计算出这个值来。19876:测试该CPU的各种缺陷,比如Pentium F00F缺陷(请参见第8章),记录检测到的缺陷,以便于内核的其他部分以后可以使用它们工作。(为了节省空间起见,我们省略掉了check_bugs函数。)19882:调用smp_init(19787行),它又调用了其他的函数来激活SMP系统中的其他CPU:在x86的平台上,smp_boot_cpus(4614行)初始化一些内核数据结构,这些数据结构跟踪检测另外的CPU并简单的将其改为保持模式;最后smp_commence(4195行)使这些CPU继续执行。19883:把init函数作为内核线程终止,这比较复杂;请参阅本章后面有关init的讨论。19885:增加idle进程的need_resched标志,这样做的原因此时可能还比较模糊。当读完了第5、6、7章以后,就会有个清楚的概念;但是,在下一个定时器中断结束之前(在第6章中讨论),system_call(171行,在第5章中讨论)函数中会注意到idle进程的need_resched标志增加了,并且调用schedule(26686行,第7章)释放CPU,并将其赋给更应该获取CPU的进程。19886:已经完成了内核初始化的工作—或者不管怎样,已经把需要完成的少量责任传递给了init,所剩余的工作不过是进入idle循环以消耗空闲的CPU时间片。因此,本行调用cpu_idle(2014行)—idle循环。正如你可以从cpu_idle本身可以发现的一样,该函数从不返回。然而,当有实际工作要处理时,该函数就会被抢占。注意到cpu_idle只是反复调用idle系统调用(下一章将讨论系统调用),它通过sys_idle(2064行)实现真正的idle循环—2014行对应UP版本,2044行针对SMP版本。它们通过执行hlt(对应“halt”)指令把CPU转入低功耗的“睡眠”状态。只要没有实际的工作处理,CPU都将转入这种状态。4.2.1   BogoMIPSBogoMIPS的数字由内核计算并在系统初始化的时候打印。它近似给出了每秒钟CPU可以执行一个短延迟循环的次数。在内核中,这个结果主要用于需要等待非常短周期的设备驱动程序—例如,等待几微秒并查看设备的某些信息是否已经可用。由于没有正确理解BogoMIPS的含义,BogoMIPS在各处都被滥用,就仿佛它可以满足人类最原始、最深层次的需求:把所有计算机性能的信息简化为一个数字。“BogoMIPS”中的“Bogo”部分来源于“伪(bogus)”,就正是为了防止这种用法:虽然这个数字比大多数基准测试数大,但是它仍然是不准确的、容易引起误解的、无用的和不真实的,根本不适合将它用于机器间差别的对比。但是这个数字仍然非常吸引人,这也正是我们在这里讨论这个问题的原因。(BogoMIPS 中“MIPS”部分是“millions of instructions per second(百万条指令每秒)”的缩写,这是cpu基准测试中的一个常用单位。) calibrate_delay19654:calibrate_delay是近似计算BogoMIPS数字的内核函数。19622:作为第一次估算,calibrate_delay计算出在每一秒内执行多少次__delay循环(6866行),也就是每个定时器滴答(timer tick)—百分之一秒—内延时循环可以执行多少次。19664:计算一个定时器滴答内可以执行多少次循环需要在滴答开始时就开始计数,或者应该尽可能与它接近。全局变量jiffies(16588行)中存储了从内核开始保持跟踪时间开始到现在已经经过的定时器滴答数;第6章中将介绍它的实现方式。jiffies保持异步更新,在一个中断内—每秒一百次,内核暂时挂起正在处理的内容,更新变量,然后继续刚才的工作。如果不这样处理,下一行的循环就永远不可能退出。从而,如果jiffies不声明为volatile—简单地说,这个值变化的原因对于编译器是透明的,gcc仍然可能对该循环进行优化,并引起该循环进入不能退出的状态。虽然目前的gcc还没有如此高的智能,然而它的维护者应该完全能够为它实现这种智能。19669:定时器又前移了一个滴答,因此又产生一个新的滴答。下一步是要等待loops_per_sec延时循环调用定时器循环,接着检测是否最少有一个完整的滴答已经完成。如果是这样,就退出首次近似估算循环;如果没有,就把loops_per_sec的值加倍,然后重新启动这个过程。这个循环的正确性依赖于如下的事实:现有的机器在任何地方都不能每秒执行232次延时循环(对于64位机来说则远低于每秒264次),虽然这只是一个微不足道的问题。19677:现在内核已经清楚loops_per_sec循环调用延时循环在这台机器上要花费超过百分之一秒的时间才能完成,因此,内核将重新开始进行估算。为了提高效率,内核使用折半查找算法计算loops_per_sec的实际值,我们假定开始的时候,实际值在现在计算结果和其一半之间—实际值不可能比现在计算值还大,但是可以(而且可能)稍微小一点。19681:和前面使用的方式一样,calibrate_delay查看这个loops_per_sec已经减小了的值是否还是比较大,而需要耗费一个完整的定时器间隔。如果还是相当大,实际值应该小于当前计算值或者就是当前值,因此,使用更小的值继续查询;如果不够大,就使用一个更大的值继续查询。19691:内核有一种很好的方法来计算一个定时器滴答中执行延时循环的次数。这个数字乘以一秒内滴答的数量就得到了每秒内可以执行的延时循环的次数。这种计算只是一种估算,乘法也累积了误差,因此结果并不能精确到纳秒。但是这个数字供内核使用已经足够精确了。19693:为了让用户感到激动,内核打印出这个数字。注意这里明显省略了%f的格式限定—内核尽量避免浮点数运算。这个计算过程中最有用的常量是500 000;它是用一百万除以2得来,理由是每秒钟执行一百万条指令,而每个delay循环的核心是2条指令(decl和一条跳转指令)。4.2.2   分析内核选项parse_options函数分析由内核引导程序发送给内核的启动选项,在初始化过程中按照某些选项运行,并将剩余部分传送给init进程(在本章后面部分提到)。这些选项可能已经存储在配置文件中了,也可能是由用户在系统启动时敲入的—内核并不关心这些。类似的细节全部是内核引导程序应该关注的内容。1. parse_options19707:参数已经收集在一条长的命令行中,内核被赋给指向该命令行头部的一个指针;内核引导程序在前面已经将该行存储在一个指定地址中。19718:中断下一个参数,保持指向下一个参数的指针以供下一次循环使用。注意系统使用空格而不是通常的空白来分隔内核参数;制表符并不能把当前参数和下一个参数分隔开。如果发现了分隔字符空格,下一行就使用字节0覆盖,这样line可以作为包含有唯一一个内核选项的标准C字符串来使用了。如果没有发现空格,就该函数关心的内容而言,其余的部分都具有相同的属性—这只有在处理line中最后一个选项的情况下才会发生,循环就会在下次开始时结束。注意该代码不会跳过多个空格。假设line值如下所述(两个空格):rw  debug这会被当作三个选项:“rw”、“”(空字符串)和“debug”。因为空字符串不是有效的内核选项,它将会被传递到初始化的过程(这一点随后就可以看到)—这当然不是用户所希望的。因此,内核引导程序应该负责对多个空格进行压缩。LILO通过忽略用户多敲的空格,完美地解决了这个问题。19721:现在开始解释这些选项。最前面的两个选项ro和rw指明内核要装载根文件系统,也就是根目录( / 目录)所在的位置,而分别处于只读和读/写模式。19729:第三种可能性,debug,增加了调试信息的数量;这些调试信息要通过调用do_syslog打印出来(25724行)。19733:开始几个选项是简单的独立标志,它们并不使用参数。内核也可以辨认形为option=value的选项。本行就是一个例子,这里内核引导程序定义了一个命令来代替init运行;它使用init=/some/other/program的形式。这里的代码舍弃了init= 部分,为随后init的使用而把剩余部分在execute_command中保存起来(20044行)。和其他大部分参数的处理方法不同,本处功能不能在checksetup(19612行)中实现,这是因为它改变了该函数的局部变量。很快,你就可以看到前面三个选项之所以也在这里处理,而不是在checksetup中处理的原因。19745:大部分内核选项都是由checksetup函数分析的。如果checksetup处理了某个选项,就返回真值,循环继续进行。19750:否则,line中没有已经被辨认的内核选项。在这种情况下,它被作为一个供init进程使用的选项或者环境变量来处理—如果其形式为envar=value,就作为环境变量处理;否则,就作为选项处理。如果argv_init和envp_init(分别见19057和19059行)数组中有足够的空间,选项和环境变量就存储在里面供以后init函数使用。

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -