Linux读核日记

今天开始我的读核罹难记.第一次读内核,实在难度很大。

面对近 123 M 的源码,困惑是难免的所以我决定先从大面上把握,再在某一些具体的点上切入.这样一来linux 的启动过程便十分重要,因此我先用dmesg命令察看一下linux启动时打出的消息.

(我想源文件应在/usr/src/linux/init/main.c)

内核的启动最后是到 start_kernel ( in /init/main.c )也就是说启动的过程是从 head.S ( arch/i386/boot/ ) 一直运行到 main.c(start_kernel) .它的作用是完成开机后的设置与内核的初始化,然后,系统究竟入一个无限的循环中等待用户的输入,调用fork来产生子进程.从而达到交互式操作系统的设计要求.

启动系统

PC机加电开始启动时,80X86的处理器(CPU)在实模式下自检,开始执行物理地址 0xFFFF0ROM-BIOS的起始地址处的代码。PC机的BIOS进行系统自检,初始化中断向量表到物理地址0x0。然后把引导设备的第一个扇区加 载到地址0x 7C 00,执行此处的指令。到这里与linux无关,x86系列的硬件设置如此.

linux的内核本身是不能自举的,所以liloloadlin的作用就是加载系统内核.有关lilo的原理可以参考liloreadme.从加电到内核加载的过程是:加电->执行BIOS->加载第一扇区->lilo->加载内核

Linux内核的最初部分代码是用汇编语言写的(文件是boot/bootsect.s)。它首先把自身这部分代码移到绝对地址0x90000,把下面的2K代码从引导设备加载到地址0x90200上,内核的其余部分加载到地址 0x10000处。在加载系统时显示“loading...”. 然后,程序控制权交给另一个实模式汇编程序(boot/Setup.S)。接下来,此程序把整个系统从地址0x10000移到地址0x1000,进入保护 模式。程序控制转给系统的其余部分  即地址0x1000

下一个步骤是系统内核的解压过程,这部分代码在地址0x1000(文件/Boot/head.S),该段程序初始 化寄存器,然后执行decompress_kernel(),这个函数源于zBoot/inflate.czBoot/unzip.c zBoot/misc.c三个文件

                             Loading ....[ bootsect.S ]             

  uncompress .....[ decompress_kernel() ] 

  main.c ---> start_kernel() 开始.       

  开始 printk(banner);                    

 

由于我主要负责bootsect.S部分,所以重点分析它。这个程序是linuxkernel的第一个程序,包括了linux自己的bootstrap程序,但是在说明这个程序前,必须先说明一般IBMPC开机时的动作(此处的开机是指"打开PC的电源" ): 
  一般PC在电源一开时,是由内存中地址FFFF:0000开始执行(这个地址一定在ROMBIOS中,ROMBIOS一般是在FEOOOhFFFFFh),而此处的内容则是一个jump指令,jump到另一个位于ROMBIOS中的位置,开始执行一系列的动作,包括了检查RAMkeyboard,显示器,软硬磁盘等等,这些动作是由系统测试码(systemtestcode)来执行的,随着制作BIOS厂商的不同而会有些许差异,但都是大同小异。 
  紧接着系统测试码之后,控制权会转移给ROM中的启动程序(ROMbootstraproutine) ,这个程序会将磁盘上的零道零扇区读入内存中(这就是一般所谓的bootsector,因为我曾接触过电脑病毒,所以很早以前就听过它的大名),至于被读到内存的哪里呢?--绝对位置 07C 0:0000( 07C 00h),这是IBM系列PC的特性。而位在linux开机磁盘的bootsector上的正是linuxbootsect程序,也就是说,bootsect是第一个被读入内存中并执行的程序。现在,可以开始来看看到底bootsect做了什么。 

第一步 
  首先,bootsect将它"自己"从被ROMBIOS载入的绝对地址0x 7C 00处搬到0x90000处,然后利用一个jmpi(jumpindirectly)的指令,跳到新位置的jmpi的下一行去执行,关键的assemblycode如下:  
                  
                    (搬移bootsect本身
                  
                  
                   jmpigo,INITSEC 
                   go: 
                  
                   .  

                                .                                              
  表示将跳到CS0x9000IPoffset"go"的位置(CS:IP=0x9000:offsetgo),其中INITSEC=0x9000定义于程序开头的部份,而go这个label则恰好是下一行指令所在的位置。 

第二步 
  接着,将其它segmentregisters包括DSESSS都指向0x9000这个位置,与CS看齐。另外将SPDX指向一任意位移地址(offset),这个地址等一下会用来存放磁盘参数表  (diskpara-metertable) 
  提到磁盘参数表,就必须提到BIOS中断1Eh。先简单的介绍一下BIOS的中断服务:80x86将内存最低的256*4byte保留给256个中断向量(每个interruptvector大小为4byte,所以一共有256*4=1024byte),而其中的第1Eh个向量指向"磁盘参数表",这个表会告诉电脑如何去读取磁盘机,而我们所要做的事是搬移磁盘参数表到刚才所设定的任意地址。 
  接着,改变搬移来的参数表的参数,以符合我们的需要。再将中断向量1Eh指向我们所修改过的磁盘参数表,然后呼叫BIOSinterruptint13h(function0,即AH=0)重置磁盘控制卡及磁盘驱动器,之后磁盘机就会照我们的意思动作了。如果你曾traceDOS kernel,你会发现,上述的动作在DOS中也有类似的对应流程。 
   
现在来看看关键的程序码:.  
 
              .              
              push#0         
              popfs         
              movbx,#0x78 
              .          

(使GS:SI=FS:BX,指向磁盘参数表, 再将GS:SI所指地址的内容搬移6wordES:DI所指的地址


  此段程序是将FS:BX调整成0000:0078,接着再将GS:SI的内容设成与FS:BX相同,此处0x78h即为int1Eh的起始位置(7*16+8=120,(1*16+14)*4=120)。调整ES:DI为刚才所设定的任意地址,从GS:SI搬移6word(12byte)ES:DI所指的位置,显然磁盘参数表的长度就是6word(不过事实上,磁盘参数表的确实长度是11byte)。关于磁盘参数表 ,有兴趣的读者可自行参阅讲述BIOSinterruptservices的技术手册,会有详细的说明。 
 
  我用了debug自行观察自家机器上DOS的磁盘参数表的起始位置(int 1Eh的内容)。以下是我做的实验的情形(我使用的系统是MSDOS6.2): 

C:>debug 
       -d0000:0000 
       0000: 00008A 101601F 4067000-1600CB 04F 4067000......p.......p. 
       0000: 0010F 40670000301790E-43EB 00F 0EBEA 00F 0..p...y.C....... 
       0000:002004108E 340C 118E34-5700CB 046F 00CB04...4...4W...o... 
       0000:00308700CB0408079433-B700CB 04F 4067000.......3......p. 
       0000: 00400C 01790E4DF 800F 0 -41F 800F 0BA 165F 06..y.M...A....._. 
       0000:005039E 700F 01B01790E-70118E341201790E9.....y.p..4..y. 
       0000:006000E 000F 085175F 06-6EFE 00F 0EE067000......_.n.....p. 
       0000:007053FF 00F 0A 4F 000F 0-220500003E 4600C 0S......."...>F.. 
 
  
由上图中可知,在DOS中磁盘参数表的起始位置(int1Eh的内容)0000:0522。接着观察DOS中位置0000:0522开始的11byte,也就是磁盘参数表的内容 
     

C:>debug 
      -d0000: 0520l 10 
      0000:05204D53DF022502121B-FF 54F 60F 08000000MS..%....T...... 
 
   
11byte即为磁盘参数表的内容(分别是byte00h0Ah) 
  在程序中我们所更动的是第五个byte(byte04h),改为18h(在上图例子中为12h),这 
byte的功能是定义磁轨上一个磁区的资料笔数。关键的程序码如下:  
               .                             
               movb4(di),*18        
               .                           

第三步 
  接着利用BIOS中断服务int13h的第0号功能,重置磁盘控制器,使得刚才的设定发挥功能。  
               .                  
               xorah,ah       
               xordl,dl        
               int0x13         
               .                  

第四步 
  完成重置磁盘控制器之后,bootsect就从磁盘上读入紧邻着bootsectsetup程序,也就是以后将会介绍的setup.S,此读入动作是利用BIOS中断服务int13h的第2号功能。setupimage将会读入至程序所指定的内存绝对地址0x90200处,也就是在内存中紧邻着bootsect所在的位置。待setupimage读入内存后,利用BIOS中断服务int13h的第8号功能读取目前磁盘机的参数。 

第五步 
  再来,就要读入真正linuxkernel了,也就是你可以在linux的根目录下看到的"v mlinuz"。在读入前,将会先呼叫BIOS中断服务int10h的第3号功能,读取游标位置,之后再呼叫BIOS中断服务int10h的第13h号功能,在萤幕上输出字符串"Loading",这个字符串在bootlinux时都会首先被看到,相信大家应该觉得很眼熟吧。 
  linuxkernel将会被读入至内存绝对地址0x10000处,键关的程序码如下:  

           .                                  
           movax,#SYSSEG        
           moves,ax                      
           callread_it                     
           callkill_motor                
           .                                  
 
  其中SYSSEG于程序开头时定义为0x1000,先将ES内容设为0x1000,接着在read_it这个子程序便以ES为目的地的节地址,将kernel读入内存中。

 

第六步 
  接下来做的事是检查rootdevice,之后就仿照一开始的方法,利用indirectjump跳至刚刚已读入的setup部份,程序码如下
           jmpi0,SETYPSEG 
  其中SETUPSEG已在先前定义为0x9020,所以CS:IP会设定为9020:0000,即跳到绝对地址为0x90200,也就是setup的起点。而bootsect也大功告成了。 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值