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

📄 核心源码的物理布局.txt

📁 献给ARM初学者
💻 TXT
📖 第 1 页 / 共 3 页
字号:
核心源码的物理布局
       
       
      第十六章    核心源码的物理布局
       
       
       
      到目前为止,我们从写设备驱动程序的角度讨论了Linux核心。但一旦你开始研究核心,
      你会发现你想“全面理解它”。事实上,你可能整天在浏览源码,搜索源码树,目的只
      是想搞清楚核心不同部分之间的关系。
       
      这种“沉重的搜索”是我在家里专门设一台计算机的任务之一,并且这是从源码中获取
      信息的一个有效的办法。然而,在坐在你喜欢的shell提示符之前若能得到一些知识基础
      将会很有帮助。本章基于版本2.0.x,对Linux核心源文件提供一个快速的概览。文件布
      局在版本之间的改变并不大,尽管我不能保证将来会不会变。因此下面的信息对浏览核

      心的其它版本应该也很有用,即使它不是权威。
       
      在本章中,每个给出的路径名都是相对于源码的根(通常是/usr/src/linux),而没有
      目录部分的文件名一般假设它居于“当前”目录----即正在讨论的哪个。头文件(当以
      角括弧的形式命名时----<和>)是相对于源码树的include目录给出的。我不想介绍Docu
      mantation目录,因为它的作用应该很清楚。
       
       
       
      引导核心
       
      看一个程序的一般方法是从执行开始的地方着手。至于Linux,很难说执行是从那里开始
      的----它取决于你是如何定义“开始”的。
       
      体系结构相关的开始点是start_kernel,在init/main.c中。这个函数是从体系结构特定
      的代码中被调用的,但它并不返回到那里。它掌管着转动轮子,因此可以被认为是“所
      有函数的母亲”,计算机生命的第一次呼吸。在start_kernel之前是一片混沌。
       
      在start_kernel被调用时,处理器已经被初始化了,保护模式(如果有)也被激活了,
      处理器在最高的优先级执行(通常被称为“管理员模式”),中断被关闭了。start_ker
      nel函数负责初始化所有的核心数据结构。这个通过调用外部函数来执行子任务,因为每
      个设置函数都在合适的核心子系统定义。start_kernel也调用parse_options(也在init/

      main.c文件中)来对从用户或引导系统的程序处传来的命令行进行解码。
       
      命令行(与memory_start,memory_end一道)用setup_arch从计算机内存中获取。setup
      _arch,如它的名字提示,是体系结构特定的代码。
       
      init/main.c中的代码主要由#ifdefs组成。这是因为初始化是按步发生的,很多步可能
      被运行或跳过,这依赖于核心的编译时配置。命令行的解释也严重地依赖于条件,因为
      很多参数只有在被编译的核心含有特定的驱动程序时才有意义。
       
      由start_kernel调用的初始化函数有两种风格。一些函数没有参数,返回void;而另一
      些需要两个unsigned long参数,并返回另一个unsigned long值。其参数是memory_star
      t和 memory_end的当前值,即未分配的物理内存的边界。返回值是新的memory_start值
      (如你所已知的,核心用unsigned long表达内存地址)。这个技术允许子系统在物理内
      存的开始处分配一个持续的(和连续的)内存区域,如在第七章“把握内存”中“playi
      ng dirty”一节中提到的。这种技术的最大的缺点是它只能在引导时使用,对那些需要
      用于DMA的巨大内存区段的模块并不可用。
       
      初始化完成后,start_kernel打印出旗帜字符串,包括Linux版本号和编译时间,接着通
      过调用kernel_thread派生出(fork)一个init进程。
       
      start_kernel函数接着以任务0(所谓的“空闲”任务)的形式继续,并调用cpu_idle,
      它是一个调用idle的无限循环。在这一点,SMP(对称多处理器)的工作方式略有不同,但

      我不打算讲述这个不同。idle函数的真正行为是体系结构相关的,对源码的简单搜索可
      以把你带到可以研究其功能的位置。
       
       
       
      引导之前
       
      在前一节,我把start_kernel看作第一个核心函数。不过,你可能对这点之前发生的事
      情感兴趣。
       
      在start_kernel之前运行的代码是低级的,包含汇编码,因此你可能对其细节不感兴趣
      。不过,我将介绍一下固件(在PC世界称为BIOS)将控制交给Linux后计算机中发生了什
      么。
       
      如果你对钻研低级代码没有兴趣,你可以直接跳到“Init进程”。下面提供了关于Intel
      ,Alpha,Sparc引导代码的一些提示,因为这是我能访问的仅有的系统。(如果有人肯
      捐一些硬件,我将在下一版覆盖更多的平台)。
       
       
       
      设置X86处理器
       

      个人计算机是基于一个老的设计,后向兼容性一直有很高的优先级。因此,PC固件还是
      以一种老方式引导操作系统。一旦引导设备被选择,它的第一个扇区被加载到内存的0x7
      C00处,然后让出控制。
       
      刚加电的处理器处于实模式(也就是说,它象8086)并只能寻址物理内存的前640KB。其
      中一部分已经被固件管理的数据表格占用了。由于核心要比这个大,Linux的开发者必须
      找到一个不一般的方法将核心影响加载到内存。结果就是zImage,即核心的压缩映象,
      它可以被装入底端内存(但愿如此),并在进入保护模式后自解压缩到高端内存。
       
      这样引导扇区发现它面对着五百字节的代码,和半兆字节的空闲内存。引导代码真正做
      的依赖于系统是如何引导的。引导扇区可以是第一个核心扇区(如果你直接从软盘上引
      导zImage)或者lilo。如果Linux由loadlin引导,则没有引导扇区什么事,因为在loadl
      in运行时,系统已经被引导了。
       
       
       
      引导一个bare-bones zImage核心
       
      如果被引导的系统是软盘上的核心映象,在引导扇区执行的代码是arch/i386/boot/boot
      ..S(一个实模式的汇编文件)。它将自己移到地址0x90000,从可引导设备上加载另外几
      个扇区,把它们放在紧挨自己的后面(也就是0x90200)。接着核心映象的其余部分被加
      载到地址0x10000(64KB:固件数据空间之后)。

       
      位于0x90200的代码是所谓的“设置”代码(arch/i386/boot/setup.S和arch/i386/boot
      /video.S),它负责各种硬件的初始化,以及对视频板子的初步检测,以便可以切换到
      不同的文本模式分辨率。这些任务在实模式中进行(使用loadlin时则是在VM86模式),
      因此可以使用BIOS调用,避免处理硬件特定的细节。
       
      setup.S接着把整个核心从0x10000(64KB)移到0x1000(4KB);这样在核心代码之前只有一
      页被浪费了----这页其实并没有真的浪费;它在系统中自有它的用处。代码的这种来回
      复制是为了摆脱被BIOS强加的内存布局,还能不至于覆盖重要的数据。最后setup.S进入
      保护模式,跳转到0x1000。
       
      arch/i386/boot/compressed/head.S(用gas写成,因为我们已经在保护模式了)设置栈。
      接着调用decompress_kernel,它把已解开的代码放在地址0x100000(1M)并跳转到那里。
       
      arch/i386/kernel/head.S是被解压缩核心的头;它建立最后的处理器设置(与硬件换页
      有关的寄存器处理)并调用start_kernel。这就是所有需要的----已经完成了。
       
       
       
      引导一个bare-bones bzImage核心
       
      随着越来越多的驱动程序为Linux核心开发出来,一个全特征的压缩核心不再能放入低端

      内存。例如,这种情况对安装核心就可能发生,因为它为了能适应各种配置,塞满了不
      同的驱动程序。因此,必须设计另一种加载方法。bzImage就是大的zImage,它在不能放
      入低端内存时也可以被加载。
       
      有几种加载bzImage的办法,这取决于使用的引导加载程序(boot loader)。核心负责
      每种情况,现在我打算从原始软盘上是如何引导的。
       
      一个bzImage核心的引导扇区不能简单地将所有的压缩数据加载到低端内存,所以它必须
      欺骗(如多数实模式x86程序所做的那样)。如果被加载的映象是大的,引导扇区象往常
      一样加载“设置”扇区,但在主引导循环的每次叠代都有一个“助手”例程被调用。助
      手例程在setup.S中定义,因为引导扇区太小无法放下它。这个例程用一个BIOS调用将数
      据从低端移到高端内存,一次移动64KB,它还要重置目的地址,引导扇区用来从盘上传
      送下一次的数据。这样,在bootsect.S中的一般加载例程就不会用尽低端内存。
       
      在核心被加载后,setup.S象往常一样被调用。它除了改变上一个跳转指令的目的地址外
      ,并不做任何特殊的工作。由于我们加载了一个大映象,处理器通过使用一台特殊的机
      器指令(它允许386在实模式段使用32位偏移)跳到0x100000而不是0x1000。
       
      解压缩和往常一样工作,但输出不能放在0x100000(1M),因为压缩的映象已经在那儿
      了。解压的数据被写到低端的内存直到用尽;接着被写到越过压缩映象的地方。这两个
      解压的片段通过执行另外的内存移动在0x100000处装配起来。但复制例程也居于高端内
      存,它必须首先将自己复制到低端内存已防止被覆盖;然后它把整个映象移到0x100000

       
      到这儿,游戏就结束了。但kernel/head.S并没有注意到发生的额外工作,所有事情照常
      进行。
       
       
       
      使用lilo
       
      lilo,Linux加载程序,居于引导扇区----或者是主引导扇区,或者是磁盘分区的第一个
      扇区。它使用BIOS调用从一个文件系统中加载核心。
       
      这个程序与核心映象面对同样的问题:在机器引导时,仅有半KB的代码被装入内存,而
      且只用几打的指令解码一个文件系统结构也是不可能的。lilo通过在安装时构造一个磁
      盘映射来解决这个问题。它用这个映射告诉BIOS从正确的地方获取每个核心块。这个技
      术很有效,但你在替换或者重写一个核心映象后必须重新安装lilo----你必须调用lilo
      命令,用一个新的核心块表来重新安装引导加载程序。
       
      实际上,lilo扩展了加载机制,它允许用户在引导时选择加载哪个映象。这个选择是通
      过一个映象的安装定义表来做到的。它用从不同的分区中取出的引导扇区代替它自己的
      引导扇区来实现。
       

      lilo比一个barebone引导的最大好处(除了能从硬驱直接引导外)是它允许用户象核心
      传递一个命令行。这个命令行可以在lilo配置文件中指定,也可以在引导时交互给出。l
      ilo把命令行放在零页(我们将其在boot/head.S之前保持空闲)的后一半。这一页以后
      由setup_arch(在arch/i386/kernel/setup.c中定义)取得。
       
      lilo的最近版本(18版本甚至更新)可以加载bzImage,而老的发布是不能的。较新的版
      本可以用BIOS调用将数据加载到高端内存,象bootsect.S做的那样。
       
      当lilo完成加载,它跳到setup.S,事情就象我们以前看到的那样继续进行。
       
       
       
      使用loadlin
       
      loadlin用来将Linux从一个实模式操作系统中引导起来。与lilo类似,都是加载数据,
      传递命令行,跳至setup.S。但它有一个优点就是它可以在FAT分区中从一个指定的文件
      名加载核心,而不需要一个块的映射。这使得它比较稳定。如果你想加载bzImage,你需
      要loadlin的版本1.6或更新*。有趣的是注意到loadlin可能需要玩一些脏活才能加载整
      个核心,同时又不至于搞乱宿主操作系统。只有在核心的所有部分都被加载了,loadlin
      才能在合适的地址重新装配它,并调用它的入口点。
       
      其它引导方式

       
      还有一些程序可以引导Linux核心。其中的两个是Etherboot和syslinux,当然还有很多
      。不过我不打算在这里讲述它们,因为它们与我已经讲过的类似,至少与核心相关的部
      分如此。
       
      但要注意,引导一个Linux核心并不是象我说的这么简单。要进行大量的检测,版本号经
      常出现在特别的地方,以抓住用户的错误,并友好的回复。意思是如果发生了什么问题
      ,系统可以在挂起前打印一条信息。局限在x86实模式的执行环境下很难完全避免发生错
      误时挂起,打印一条消息总比什么都没有强。
       
       
       
      设置Alpha处理器
       
      让一个Alpha到达能运行start_kernel这一点要比Intel处理器容易的多,因为在Alpha上
      不需要和实模式或内存限制做斗争。而且,Alpha工作站通常配有比PC好的固件,可以从
      文件系统装载一个完整的文件。我不想讨论装载一个文件时的实际步骤,因为这个代码
      没有随Linux发布,这样你无法检查它----我也不能,因此就无法谈论它。
       
      milo(迷你加载程序)程序是引导的一般选择。milo比固件要聪明,因为它理解Linux和
      它的文件系统,但又比核心笨,因为它不能运行进程。milo由固件从FAT分区执行,可以
      从ext2或ISO9660块设备上加载核心。象lilo和loadlin,milo也向核心传递一个命令行

      。在Linux被加载到内存中正确的虚地址后,milo转向核心,自己消失。
       
      milo的有些特征依赖于核心源码,因为它需要访问设备,理解文件系统布局。配有驱动
      程序和文件系统类型,它可以根据文件名从硬盘或CD-ROM上取得核心映象。这个设计后
      面的想法与loadlin类似,只是milo使用Linux核心的代码,而不是取自别的操作系统环
      境。
       
      在Alpha上引导Linux并不总是可用milo。如果你的系统有SRM固件,就不能安装milo。相
      反,你可以使用arch/alpha/boot中的原始加载程序。这个加载程序很简单,能从硬盘或
      软驱中读取一个顺序区域,这与PC上zImage前面的引导扇区所做的工作一样。使用原始
      加载程序要求核心映象必须(在任何文件系统之外)被复制到磁盘上的连续区域。
       
      如果不考虑系统是如何引导的,控制被传递给arch/alpha/kernel/head.S,但Linus说:
      “没什么需要我们做的了”。源码只是设置几个指针,然后就跳到start_kernel。
       
       
       
      设置Sparc处理器
       
      Sparc计算机用一个称为silo的程序引导Linux。与lilo,milo命名方法类似,只是用“s

⌨️ 快捷键说明

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