Linux系统启动那些事—基于Linux 3.10内核【转】

转自:http://www.javashuo.com/article/p-wfpjbhku-hy.htmlphp

Linux系统启动那些事—基于Linux 3.10内核html


csdn 个人空间的下载地址 ,有些作的效果网页上没了,感兴趣建议去上面地址下载pdf版的node

下载地址 http://download.csdn.net/detail/shichaog/8054005   shichaog@126.com
启动流程概述
也许你会好奇Linux是如何启动的?本文围绕Linux如何启动起来展开一些介绍。本文涉及grub、uboot、initrd、根文件系统、设备树、以及Linux内核编译等内容。linux

对那些好奇系统是如何启动的人本文很是适合,固然对于因为涉及操做系统的方方面面,bsp的开发人员也有点价值,可是这里没有对应用作介绍;本文讨论两种平台下的启动方式,由于它们均是对应体系架构下的典型。ios

一、    通用的PC平台架构(X86)git

二、    嵌入式平台架构(ARM,S3C2440)shell

本文可能一些实验性的操做,如grub、内核编译等,建议这些动手也作一下,可以加深对这一过程的理解。bootstrap

对于存储器、磁盘、CPU具备必定的了解会比较好,不过这不是这里的重点,而且会稍微说起一下,因此没必要担忧成为障碍。 对于uboot、BIOS、grub、vmlinuz、initrd有点认识会更好,可是对操做系统是须要有个概念性的认识,不少基本概念性的没有在这里讲解了。固然,若是你还不会C语言,那就别向下看了,赶忙学学C吧,有点汇编、编译、连接的知识会更好,对Makefile、Kconfig、shell若是也知道那就更好。vim

PC下个人Linux系统:api

Fedora9, 桌面版 内核版本2.6.25;VMware9下虚拟机。

www.kerenel.org下载Linux-3.10.0,这里将使用Linux-3.10.0升级Fedora 9内核。

全部的故事从升级Linux内核开始,首先,让咱们来配置Linux内核,解压Linux内核,我解压的目录为/usr/src/下,不过我可不建议你解压到该目录,Linux内核的Header文件连接到该目录下,尽管该版本下不会带来大的问题,可是建议不要“污染”了这个目录。

步骤一:而后使用menuconfig,生成配置文件。

     cd/usr/src/linux-3.X

步骤二:编译内核目标文件。

     make {O=/home/name/build/kernel}menuconfig

步骤三:编译module和内核。

     make {O=/home/name/build/kernel}

步骤四:安装module和内核以及启动必备文件

     sudo make {O=/home/name/build/kernel}modules_install install

通过如上四个步骤,以root权限reboot系统,进入系统,若是在编译和连接时想看看详细的执行过程能够在步骤三中使用makeV=1 all该命令让编译时输出更多的过程信息。

   后面会将启动代码和启动输出的信息进行关联分析,这样的认识会更深入些;若是您手头资源有限或者时间等缘由没能完成这个步骤,那么还有一个补救措施,笔者将上述的2.6和3.10内核的两个版本的启动显示信息录制了一个视频,该视频下载网址:XXX

若是您没有本身升级,那么我强烈建议您看一下,总时间不到十分钟,您就能够看完2.6和3.10两个版本启动的过程了。编译命令中大括号里的内容可选,这里就是输出路径设置,能够不关心,这和启动没有关系,升级完该放下它,后面会再说起它的,bios可能你们都据说过,PC启动的最早代码执行的就是这里的代码,第二个执行的代码是grub或者lilo,grub会将initrd和内核拷贝到内存中,而后进行解压并执行内核。

首先看一下在2.6内核版本下启动时和Linux相关的一些文件。

[root@shichaog boot]# ls -lt

total 6282

drwxr-xr-x 2 root root    1024 2012-09-18 04:34 grub

drwxr-xr-x 3 root root    1024 2012-09-18 04:06 efi

drwx------ 2 root root   12288 2012-09-18 03:55 lost+found

-rw------- 1 root root 3312447 2012-09-1720:52 initrd-2.6.25-14.fc9.i686.img

-rw-r--r-- 1 root root   86348 2008-05-01 18:34config-2.6.25-14.fc9.i686

-rw-r--r-- 1 root root  892575 2008-05-01 18:34System.map-2.6.25-14.fc9.i686

-rwxr-xr-x 1 root root 2088288 2008-05-0118:34 vmlinuz-2.6.25-14.fc9.i686

grub启动加载程序,当系统按下电源键开机时,第一条指令对应的逻辑地址(段:偏移)FFFF:0000,也就是物理地址的FFFF0H,这个在8086时代就这么决定了,这个地址通常存放第一条BIOS的指令,这条指令通常又是个长跳转指令,由于8086时代地址线只有20根,因此寻址只能是1M范围内的空间,而到了酷睿或者i5系列早已经是32位或者64位了,因此Bios的大小和物理地址都进行了扩充,因此该指令通常跳转到bios执行指令。Bios存放在ROM中掉电不会失去。

efi bios的升级方案, 您能够将其理解为功能和bios差很少,可是运行的是32为指令而且地址有了突破,此外和pc操做系统的接口也有一点区别就能够了。这里仍是以bios为主。

lost+found存放修复或者损坏的文件,正常的启动过程用不到。

Initrd 这个是initial RAM disk的简称,系统在运行时创建在一个存储系统上的,initrd使用软件模拟磁盘系统,它就是个文件系统,通常的嵌入式系统的文件系统就是它,可是在pc环境下initrd只在启动过程当中起做用。

config文件内核的配置,和make menuconfig生成的文件同样,是关于当前系统的配置状况,如处理器类型、mmu、cgroup功能是否支持等。

system.map,这个文件是内核映像文件vmlinux使用nm导出的符号表,vmlinux是ELF文件格式,含有一些信息,nm命令导出该文件的信息,该文件对于分析内核映像文件仍是有必定帮助的。vmlinuz,加工过的vmlinux,加工内容包括压缩、去除ELF文件信息,而且添加了bst(bootstrap)部分代码。

这里也看一下升级后boot目录下的文件内容,注意黄色加深部分,至于没有着色的部分也许你也看到了系统有两个,是的,没看错,这个以前我也编译并安装过另外一个2.6.25版本的内核了。

drwxr-xr-x 2 root root    1024 2014-08-17 14:43 grub

-rw------- 1 root root 3377030 2014-08-17 14:43 initrd-3.10.0.img

lrwxrwxrwx 1 root root      232014-08-17 14:40 System.map -> /boot/System.map-3.10.0

-rw-r--r-- 1 root root 1398825 2014-08-17 14:40 System.map-3.10.0

lrwxrwxrwx 1 root root      202014-08-17 14:40 vmlinuz -> /boot/vmlinuz-3.10.0

-rw-r--r-- 1 root root 3012160 2014-08-17 14:40 vmlinuz-3.10.0

drwxr-xr-x 3 root root    1024 2014-07-23 04:53 efi

drwx------ 2 root root   12288 2014-07-23 04:45 lost+found

-rw------- 1 root root 3311461 2014-07-2221:55 initrd-2.6.25-14.fc9.i686.img

-rw-r--r-- 1 root root   86348 2008-05-01 18:34config-2.6.25-14.fc9.i686

-rw-r--r-- 1 root root  892575 2008-05-01 18:34System.map-2.6.25-14.fc9.i686

-rwxr-xr-x 1 root root 2088288 2008-05-0118:34 vmlinuz-2.6.25-14.fc9.i686

-rwxr-xr-x 1 root root  822228 2008-04-26 01:25xen-syms-2.6.25-2.fc9.i686.xen

-rw-r--r-- 1 root root   86316 2008-04-26 01:18config-2.6.25-2.fc9.i686.xen

-rw-r--r-- 1 root root  907057 2008-04-26 01:18System.map-2.6.25-2.fc9.i686.xen

-rwxr-xr-x 1 root root 2495757 2008-04-2601:18 vmlinuz-2.6.25-2.fc9.i686.xen

-rw-r--r-- 1 root root  373850 2008-04-26 01:10xen.gz-2.6.25-2.fc9.i686.xen

PC启动流程简介
对于X86PC系统上电后,会执行特定地址上的一条指令,而目前这条指令就是一个长跳转指令,该指令跳转至真正的bios入口地址执行bios,bios程序会进行加电自检(POST),其次会本地设备初始化,并按照启动顺序搜索能够引导的设备,这里咱们从硬盘引导咱们的系统,这是bios将硬盘的0磁道0柱面1扇区的内容拷贝至内存中运行,之因此拷贝至内存中,由于内存的存储速率远远高于ROM或者硬盘,不过最近的固态盘速率提升的很快,好多pc开始采用固态盘(SSD)做为PC的主存储器了。一个扇区是512字节,当这些512字节拷贝至内存后,bios会将跳转至该内存里的程序(grub)继续执行,grub将负责实际的内核加载,即将内核加载到内存中,而后将控制权交给内核,内核会解压缩自身的内核映像到特定的地址,而后运行该内核,内核会启动分页机制,内存管理、设备初始化等一些的任务,执行init进程,建立三个内核线程,其中一个线程会建立内核守护进程如swap进程,还有一个线程会启动shell进程,提供操做系统登陆这登陆。

Bios会将0磁道0柱面1扇区(sector)的512字节内容(grub stage1)拷贝至0x7c00所在的物理内存处,而且将控制权交给grub,该512B中只有446B是引导代码,剩下的是分区表信息。grub的第二个阶段代码能够能够跨越一个或者多个sector。因此实际上grub大部分的工做是由stage2来完成的,其会被stage1 阶段的代码拷贝至内存运行。

 

图1.1.1 Linux PC(X86)启动流程

先来看看grub的配置文件的内容(/boot/grub/grub.conf)【该版本的grub是0.97,有stage1.5,grub2部分见2.1节】:

title Fedora (3.10.0)

       root (hd0,0)

       kernel /vmlinuz-3.10.0 ro root=/dev/VolGroup00/LogVol00 rhgb quiet

       initrd /initrd-3.10.0.img

title Fedora (2.6.25-14.fc9.i686)

       root (hd0,0)

       kernel /vmlinuz-2.6.25-14.fc9.i686 ro root=UUID=aeca4624-c3e9-45af-b01b-125b2bcbec7arhgb quiet

       initrd /initrd-2.6.25-14.fc9.i686.img

root (hd0,0) root系统文件目录,第一块硬盘的第一个分区(主分区)。

kernel /vmlinuz-3.10.0 内核映像文件

ro只读

root=/dev/VolGroup00/LogVol00 根分区用户

rhgb quiet 图形redhat graphic boot ,不显示dmesg信息

initrd 启动时的文件系统。

device.map文件(boot/grub/):

(hd0)     /dev/sda

PC的1M内容以下:Documentation/x86/boot.txt


图1.1.2内存布局

grub执行完毕后就会将cpu转交给Linux内核,内核的bzImage格式是通过压缩的内核格式,首先进行解压缩,而后才是真正意义上的启动内核。这一解压缩和启动的过程对应于下图所示。

这一段信息中Decompressing Linux… done意味着内核刚解压完毕,紧接着启动内核,即图中显示的Booting the kernel。

 

 

图1.1.3内核解压缩

在Linux内核booting后期,会调用start_kernel()函数,该函数会建立内核守护进程, init进程,内核守护进程用于例行性管理类任务,如swapd等, /sbin/init会读取/etc/inittab,根据不一样的运行级别去初始化相关的服务。

</etc/inittab>

# Default runlevel. The runlevels used are:

#   0- halt (Do NOT set initdefault to this)

#   1- Single user mode

#   2- Multiuser, without NFS (The same as 3, if you do not have networking)

#   3- Full multiuser mode

#   4- unused

#   5- X11

#   6- reboot (Do NOT set initdefault to this)

#

id:5:initdefault:

接下来init进程会调用/etc/rc.sysinit脚本初始化不少换环境参数,如PATH、网络的设定等。加载内核模块。一样是依赖于inittab中的运行级别,执行对应运行级别初始化脚本(/etc/rc0.d~/etc/rc6.d目录下),最后还有一个/etc/rc.local目录下脚本也会运行,这里用户能够定制一些本身的服务在里面,这样每次系统启动就能够运行一些本身定制化的东东。至此全部的准备工做已经作好了,就是让用户登陆了,调用脚本/bin/login,给出用户登陆界面。登陆进shell。

这一过程直观上的感受就是启动时下面会出现的启动过程显示1.1.4图,这是一张启动截屏图。

 

图1.1.4:启动过程截图

 

1.1.5运行脚本rc5.d目录下内容。

图中划横线的部分就是在启动过程显示的服务的由来。至此整个过程有了清楚的认识了。

http://www.linuxhomenetworking.com/wiki/index.php/Quick_HOWTO_:_Ch07_:_The_Linux_Boot_Process#.VCa1mOOSwU0

ARM启动流程简介
对于嵌入式平台ARM平台,说说其NANDFlash的启动过程,请先看图2.2描述的NAND flash中的程序布局,上电时,首先cpu会自动将自动从NAND flash中拷贝必定代码到内存中执行,这是任何支持nand方式启动必须支持的,通常我见到的有2K还有4K的,这部分的代码咱们将其称为bootstrap,这个有点相似MBR中的执行代码,那部分代码是grub的stage1代码,bootstrap而后会拷贝bootloader到内存中,这个就相似PC中的grub的stage2,这部分代码大小是有限的,遇到过32K,128K的bootloade。Bootstrap会将32K或者128K的空间里的东西拷贝到内存中而无论实际的bootloader大小,而后将控制权移交给bootloader,相比bootstrap而言,bootloader发挥的空间较大了,它会读取一个叫ptb里的内容,这里存的是分区信息,根据nand的存储特色记录app,kernel…bootstrap的大小,其实各部分的block,page的信息,而后bootloader将这些内容通通拷贝到内存中,至于拷贝到内存中的地址各个平台的差别性就比较大了,不像PC加载内核到内存中的地址是固定的。

 

图1.2.1嵌入式ARM启动流程

Uboot主要仍是为kernel服务的,因此它准备好外部环境后将控制权交给kernel,其实转交过程就是jump,而且会将相关的信息传递给内核,好比设备树表的地址,kernel部分的代码而后开始执行,主要系统初始化工做仍是在startkernel中完成的,例如会解析命令,而后还有一个重要动做,那就是解析设备树,并使用一个链表将其串接在一块,在后续驱动注册的probe方法中会用到这个链表,3.10的内核是这么处理设备的。接着一系列的初始化,初始化的工做交给一个内核线程完成,该线程会执行/sbin/init的内容,这个内容就涉及到根文件系统了,在PC状况下就是initrd了,ARM下initramfs了,这里须要说明的是init并不属于内核,为了保持内核的精简,底层服务搭建好了之后,初始化任务仍是交给了用户空间去完成了,最后启动shell,至此PC下的任务完成了,嵌入式下会把须要的应用,即app里的内容放在init里去加载,或者init下调用另外一个去加载。至此整个加载过程就完毕了。

 

图1.2.2NAND Flash中全部的布局

本文会讲解两种架构下的启动流程,各个阶段对用的概念是同样的,如grub等价于uboot,PC下的initrd对应于嵌入式的根文件系统等,到Linux启动时,他们的代码概念上都是同样的,如创建页表、中断等。

MBR& grub
Linux内核启动时的一些信息就是MBR和bootloader给的,那么就先将讲的MBR的分区信息吧,bootloader和内核都会依赖分区,由于分区信息指明了代码在存储介质上的分布信息,一些操做依赖于该分区信息,好比,内核在哪里,通常在/boot分区,这些分区信息在安装操做系统格式化磁盘时就会存在,或者新的硬盘的格式化,也会有分区信息存在里面。这里使用的两个命令查看了硬盘的分区状况,在MBR中有该分区信息。

 

图2.1.1 分区信息


由fdisk命令能够知道系统共两个分区一个硬盘,一般第一块硬盘被称为sda,第二块硬盘被称为sdb,表示个人系统有一块硬盘,两个分区,/sda1和/sda2,其中/sda1是boot分区(星号),/sda2是LVM类型的分区,一个逻辑管理,适用于须要动态调整大小的场合。从df命令能够看出/boot分区的确挂在/dev/sda1下。前面的device.map文件(boot/grub/):下的(hd0)    /dev/sda,也就不难理解了,/sda表示整个第一块硬盘。若是有兴趣可使用dd if=/dev/sda1 of=XXX bs=512 count=1, od-x XXX 查看第一个扇区的内容。最后一个必然是aa55,由于这是一个合法的mbr的必然信息,前面是grub的stage1,后面是分区信息,每一个分区16个字节,因为个人盘只有两个分区,因此aa55的前面会有那么多零是正常的,关于mbr的分析,若是感兴趣本身查查,这里讲篇幅留给其它更有意义的部分吧。

 

2.1.2MBR内容

好了,接下来正式进入grub2,grub在2014年就一直没有跟新过了,但uboot更新可真是勤快啊,建议按照这个步骤先作一下,有点成就感,这样会有信心往下走,并且PC应该不难找到,找个虚拟机就能够试,在弄个snapshot就不用担忧系统坏掉了。先来一个grub2的启动界面:

 

2.1.3 grub启动界面

一、  首先去gnu的官网下载一个grub-1.97.2.tar.gz源码包,地址

ftp://alpha.gnu.org/gnu/grub/

二、  解压 tar xvzfgrub.1.97.2.tar.gz

三、  进入解压后目录 cdgrub-1.97.2

四、  配置grub, ./configure

五、  编译 make

六、  安装grub-install/dev/sda

七、  更新grub grub2-mkconfig-o /boot/grub/grub.cfg,这里的grub.cfg和原来grub.conf的做用是同样的,该命令会根据/boot下的kernel image和initrd信息生成启动信息。若是是Ubuntu下,须要使用update-grub/dev/sda命令

八、  reboot重启能够看到该系统启动grub的信息。

下面就来讲说grub的启动步骤吧:

一、Bios将MBR中的512B东西拷贝到0x7c00处,而后跳至该处执行,而后装载start模块。这部分代码参看/boot/i386/pc/boot.S

二、start模块加载剩余的grub stage2部分,参看boot/i386/pc/diskboot.S。

三、 grub stage引导CPU保护模式,自解压并释放到0x10000开始内存处,解压完成后再拷贝回原来位置,而后调用grub_main,见kern/main.c,grub_main初始化系统,加载模块,并进入normal或者rescue模式。GRUB将根据配置文件grub.cfg或者用户输入,加载操做系统并执行操做系统。

在这里就赘述Makefile以及镜像文件的连接关系等相关的内容了,这方面的知识会在内核映像生成过程当中体现出来。针对愈来愈大的硬盘存储容量,开始出现了GTP取代MBR的趋势,GTP特性须要EFI特性的支持。

 uboot
一个bootloader应当提供一下功能,

1. Setup and initialise the RAM.

2. Initialise one serial port.

3. Detect the machine type.

4. Setup the kernel tagged list or bdt.

5. Call the kernel image.

对其的介绍和使用这里就不写了,网上针对2440的文章可谓泛滥了,本身找找动手实践一下。

Linux内核映像生成过程
连接的语法能够参看:http://sourceware.org/binutils/docs-2.21/ld/

也能够查看精简版的连接脚本的书写格式。

http://www.slac.stanford.edu/comp/unix/package/rtems/doc/html/ld/ld.info.Scripts.html

内核镜像通常包括两个部分,一个部分是针对特定处理器、特定平台的启动代码,这部分一般被称为boot代码,另外一部分就是Linux系统自己,这包括内存管理、进程调度、文件系统、进程间通信、网络子系统等部分。

若是你查下Linux的代码会发现,就arch/(x86,arm)/boot下的内容差异就很大,arch/arm/boot目录下的内容以下:

 

图2.3.1 ARM启动代码目录

arch/x86/boot下的内容以下:

 


图2.3.2 X86启动代码目录

从上面就能够看出X86的启动代码会比arm的启动多一些,可是上述关于arm的boot代码在arch/arm下还有一个板级的初始化,如s3c2440板级一些工做被放在了,arch/arm/mach-s3c24xx下了。一个内核映像的组成至少包括boot部分和kernel部分,咱们暂且这么定,而且后面无特殊说明,也会boot指cpu和板级的初始化代码,kernel指脱离了硬件差异的启动代码。

两种平台启动时,其中一个重要的差异是文件系统,由于arm一般用于嵌入式系统,其内存和Flash相对PC而受限,比较出名的嵌入式文件系统有yaffs二、jffs二、cramfs,因为工做的关系这里咱们就说ubifs文件系统了,该文件系统是新一代为NAND Flash设计的文件系统,但其自己相对也较大,8M左右。PC架构采用ext3文件系统。

另外一差异在对硬件的处理上,arm采用了设备树,arch/arm/boot/dts,而且uboot如今也能够采用设备方法来解析硬件了。嵌入式设备对硬件的处理(依赖设备树)较PC差别较大。这里先综述PC下的启动流程。

首先来看一个缩略的内核镜像vmlinux(kernel部分,非boot)输入文件编译过程,使用的连接脚本是内核源码文件/arch/x86/kernel/vmlinux.lds。

[root@ge linux-3.10]# make vmlinux
HOSTCC scripts/basic/fixdep
HOSTCC arch/x86/tools/relocs_32.o
HOSTCC arch/x86/tools/relocs_64.o
HOSTCC arch/x86/tools/relocs_common.o
HOSTLD arch/x86/tools/relocs
CHK include/generated/uapi/linux/version.h
CHK include/generated/utsrelease.h
CC kernel/bounds.s
GEN include/generated/bounds.h
CC arch/x86/kernel/asm-offsets.s
GEN include/generated/asm-offsets.h
CALL scripts/checksyscalls.sh
CC scripts/mod/empty.o
HOSTCC scripts/mod/mk_elfconfig
MKELF scripts/mod/elfconfig.h
CC scripts/mod/devicetable-offsets.s
GEN scripts/mod/devicetable-offsets.h
HOSTCC scripts/mod/file2alias.o
HOSTCC scripts/mod/modpost.o
HOSTCC scripts/mod/sumversion.o
HOSTLD scripts/mod/modpost
HOSTCC scripts/selinux/genheaders/genheaders
HOSTCC scripts/selinux/mdp/mdp
HOSTCC scripts/kallsyms
HOSTCC scripts/pnmtologo
HOSTCC scripts/conmakehash
HOSTCC scripts/sortextable
CC init/main.o
CHK include/generated/compile.h
CC init/version.o
CC init/do_mounts.o
CC init/do_mounts_initrd.o
LD init/mounts.o
CC init/initramfs.o
CC init/calibrate.o
CC init/init_task.o
LD init/built-in.o
HOSTCC usr/gen_init_cpio
GEN usr/initramfs_data.cpio
AS usr/initramfs_data.o
LD usr/built-in.o
LD arch/x86/crypto/built-in.o
CC arch/x86/kernel/process_32.o
CC arch/x86/kernel/signal.o
AS arch/x86/kernel/entry_32.o
CC arch/x86/kernel/traps.o

CHK include/generated/uapi/linux/version.h
UPD include/generated/uapi/linux/version.h
CHK include/generated/utsrelease.h
UPD include/generated/utsrelease.h
CC kernel/bounds.s
GEN include/generated/bounds.h
CC arch/x86/kernel/asm-offsets.s
GEN include/generated/asm-offsets.h
CALL scripts/checksyscalls.sh
CC scripts/mod/empty.o
HOSTCC scripts/mod/mk_elfconfig
MKELF scripts/mod/elfconfig.h
CC scripts/mod/devicetable-offsets.s
GEN scripts/mod/devicetable-offsets.h
HOSTCC scripts/mod/file2alias.o
HOSTCC scripts/mod/modpost.o
HOSTCC scripts/mod/sumversion.o
HOSTLD scripts/mod/modpost
HOSTCC scripts/selinux/genheaders/genheaders
HOSTCC scripts/selinux/mdp/mdp
HOSTCC scripts/kallsyms
HOSTCC scripts/pnmtologo
HOSTCC scripts/conmakehash
HOSTCC scripts/sortextable
CC init/main.o
CHK include/generated/compile.h
UPD include/generated/compile.h
CC init/version.o
CC init/do_mounts.o
CC init/do_mounts_initrd.o
CC init/do_mounts_md.o
LD init/mounts.o
CC init/initramfs.o
CC init/calibrate.o
CC init/init_task.o
LD init/built-in.o
HOSTCC usr/gen_init_cpio
GEN usr/initramfs_data.cpio
AS usr/initramfs_data.o
LD usr/built-in.o
CC arch/x86/kernel/process_32.o
CC arch/x86/kernel/signal.o
AS arch/x86/kernel/entry_32.o
CC arch/x86/kernel/traps.o
CC arch/x86/kernel/irq.o
CC arch/x86/kernel/irq_32.o
CC arch/x86/kernel/dumpstack_32.o
CC arch/x86/kernel/time.o
CC arch/x86/kernel/ioport.o
CC arch/x86/kernel/ldt.o
CC arch/x86/kernel/dumpstack.o
CC arch/x86/kernel/nmi.o
CC arch/x86/kernel/setup.o
CC arch/x86/kernel/x86_init.o
CC arch/x86/kernel/i8259.o
CC arch/x86/kernel/irqinit.o
CC arch/x86/kernel/jump_label.o
CC arch/x86/kernel/irq_work.o
CC arch/x86/kernel/probe_roms.o
CC arch/x86/kernel/i386_ksyms_32.o
CC arch/x86/kernel/syscall_32.o
CC arch/x86/kernel/bootflag.o
CC arch/x86/kernel/e820.o
CC arch/x86/kernel/pci-dma.o
CC arch/x86/kernel/quirks.o
CC arch/x86/kernel/topology.o
CC arch/x86/kernel/kdebugfs.o
CC arch/x86/kernel/alternative.o
CC arch/x86/kernel/i8253.o
CC arch/x86/kernel/pci-nommu.o
CC arch/x86/kernel/hw_breakpoint.o
CC arch/x86/kernel/tsc.o
CC arch/x86/kernel/io_delay.o
CC arch/x86/kernel/rtc.o
CC arch/x86/kernel/pci-iommu_table.o
CC arch/x86/kernel/process.o
CC arch/x86/kernel/i387.o
CC arch/x86/kernel/xsave.o
CC arch/x86/kernel/ptrace.o
CC arch/x86/kernel/tls.o
CC arch/x86/kernel/step.o
CC arch/x86/kernel/stacktrace.o
CC arch/x86/kernel/acpi/boot.o
CC arch/x86/kernel/acpi/sleep.o
CC arch/x86/kernel/acpi/cstate.o
LD arch/x86/kernel/acpi/built-in.o
CC arch/x86/kernel/apic/apic.o
CC arch/x86/kernel/apic/apic_noop.o
CC arch/x86/kernel/apic/ipi.o
CC arch/x86/kernel/apic/hw_nmi.o
CC arch/x86/kernel/apic/io_apic.o
CC arch/x86/kernel/apic/probe_32.o
LD arch/x86/kernel/apic/built-in.o
CC arch/x86/kernel/cpu/intel_cacheinfo.o
MKCAP arch/x86/kernel/cpu/capflags.c
CC arch/x86/kernel/cpu/capflags.o
CC arch/x86/kernel/cpu/common.o
CC arch/x86/kernel/cpu/match.o
CC arch/x86/kernel/cpu/bugs.o
CC arch/x86/kernel/cpu/intel.o
CC arch/x86/kernel/cpu/amd.o
CC arch/x86/kernel/cpu/transmeta.o
CC arch/x86/kernel/cpu/perf_event.o
CC arch/x86/kernel/cpu/perf_event_amd.o
CC arch/x86/kernel/cpu/perf_event_amd_uncore.o
CC arch/x86/kernel/cpu/perf_event_p6.o
CC arch/x86/kernel/cpu/perf_event_knc.o
CC arch/x86/kernel/cpu/perf_event_p4.o
CC arch/x86/kernel/cpu/perf_event_intel_lbr.o
CC arch/x86/kernel/cpu/perf_event_intel_ds.o
CC arch/x86/kernel/cpu/perf_event_intel.o
CC arch/x86/kernel/cpu/perf_event_intel_uncore.o
CC arch/x86/kernel/cpu/mcheck/mce.o
CC arch/x86/kernel/cpu/mcheck/mce_intel.o
CC arch/x86/kernel/cpu/mcheck/mce_amd.o
CC arch/x86/kernel/cpu/mcheck/threshold.o
CC arch/x86/kernel/cpu/mcheck/therm_throt.o
LD arch/x86/kernel/cpu/mcheck/built-in.o
CC arch/x86/kernel/cpu/mtrr/main.o
CC arch/x86/kernel/cpu/mtrr/if.o
CC arch/x86/kernel/cpu/mtrr/generic.o
CC arch/x86/kernel/cpu/mtrr/cleanup.o
CC arch/x86/kernel/cpu/mtrr/amd.o
CC arch/x86/kernel/cpu/mtrr/cyrix.o
CC arch/x86/kernel/cpu/mtrr/centaur.o
LD arch/x86/kernel/cpu/mtrr/built-in.o
CC arch/x86/kernel/cpu/perfctr-watchdog.o
CC arch/x86/kernel/cpu/perf_event_amd_ibs.o
LD arch/x86/kernel/cpu/built-in.o
CC arch/x86/kernel/reboot.o
CC arch/x86/kernel/msr.o
CC arch/x86/kernel/cpuid.o
CC arch/x86/kernel/early-quirks.o
CC arch/x86/kernel/smp.o
CC arch/x86/kernel/smpboot.o
CC arch/x86/kernel/tsc_sync.o
CC arch/x86/kernel/setup_percpu.o
CC arch/x86/kernel/mpparse.o
CC arch/x86/kernel/machine_kexec_32.o
CC arch/x86/kernel/crash.o
CC arch/x86/kernel/crash_dump_32.o
CC arch/x86/kernel/module.o
CC arch/x86/kernel/doublefault_32.o
CC arch/x86/kernel/vm86_32.o
CC arch/x86/kernel/early_printk.o
CC arch/x86/kernel/amd_nb.o
CC arch/x86/kernel/microcode_core_early.o
CC arch/x86/kernel/microcode_intel_early.o
CC arch/x86/kernel/microcode_intel_lib.o
CC arch/x86/kernel/microcode_core.o
CC arch/x86/kernel/microcode_intel.o
CC arch/x86/kernel/microcode_amd.o
LD arch/x86/kernel/microcode.o
CC arch/x86/kernel/check.o
CC arch/x86/kernel/perf_regs.o
LD arch/x86/kernel/built-in.o
AS arch/x86/kernel/head_32.o
CC arch/x86/kernel/head32.o
CC arch/x86/kernel/head.o
LD arch/x86/built-in.o
….
CC kernel/fork.o
CC kernel/exec_domain.o
CC kernel/panic.o
CC kernel/printk.o
CC kernel/cpu.o

CC mm/filemap.o
CC mm/mempool.o
CC mm/oom_kill.o
CC mm/fadvise.o

CC mm/migrate.o
LD mm/built-in.o
CC fs/open.o
CC fs/read_write.o
CC fs/file_table.o
CC fs/super.o

CC fs/drop_caches.o
LD fs/built-in.o
CC ipc/util.o
CC ipc/msgutil.o
CC ipc/msg.o
CC ipc/sem.o
CC ipc/shm.o
CC ipc/ipcns_notifier.o
CC ipc/syscall.o
CC ipc/ipc_sysctl.o
CC ipc/mqueue.o
CC ipc/namespace.o
CC ipc/mq_sysctl.o
LD ipc/built-in.o
CC security/keys/gc.o

CC security/capability.o
CC security/lsm_audit.o
LD security/built-in.o
CC crypto/api.o
CC crypto/cipher.o
CC crypto/compress.o

CC arch/x86/lib/usercopy_32.o
AR arch/x86/lib/lib.a
LINK vmlinux
LD vmlinux.o
MODPOST vmlinux.o
GEN .version
CHK include/generated/compile.h
UPD include/generated/compile.h
CC init/version.o
LD init/built-in.o
KSYM .tmp_kallsyms1.o
KSYM .tmp_kallsyms2.o
LD vmlinux
SORTEX vmlinux
SYSMAP System.map
对于boot部分,依赖于arch/x86/boot/setup.ld,其编译过程以下,
CC arch/x86/boot/a20.o
AS arch/x86/boot/bioscall.o
CC arch/x86/boot/cmdline.o
AS arch/x86/boot/copy.o
HOSTCC arch/x86/boot/mkcpustr
CPUSTR arch/x86/boot/cpustr.h
CC arch/x86/boot/cpu.o
CC arch/x86/boot/cpucheck.o
CC arch/x86/boot/early_serial_console.o
CC arch/x86/boot/edd.o
VOFFSET arch/x86/boot/voffset.h
LDS arch/x86/boot/compressed/vmlinux.lds
AS arch/x86/boot/compressed/head_32.o
CC arch/x86/boot/compressed/misc.o
CC arch/x86/boot/compressed/string.o
CC arch/x86/boot/compressed/cmdline.o
CC arch/x86/boot/compressed/early_serial_console.o
OBJCOPY arch/x86/boot/compressed/vmlinux.bin
RELOCS arch/x86/boot/compressed/vmlinux.relocs
GZIP arch/x86/boot/compressed/vmlinux.bin.gz
HOSTCC arch/x86/boot/compressed/mkpiggy
MKPIGGY arch/x86/boot/compressed/piggy.S
AS arch/x86/boot/compressed/piggy.o
LD arch/x86/boot/compressed/vmlinux
ZOFFSET arch/x86/boot/zoffset.h
AS arch/x86/boot/header.o
CC arch/x86/boot/main.o
CC arch/x86/boot/mca.o
CC arch/x86/boot/memory.o
CC arch/x86/boot/pm.o
AS arch/x86/boot/pmjump.o
CC arch/x86/boot/printf.o
CC arch/x86/boot/regs.o
CC arch/x86/boot/string.o
CC arch/x86/boot/tty.o
CC arch/x86/boot/video.o
CC arch/x86/boot/video-mode.o
CC arch/x86/boot/version.o
CC arch/x86/boot/video-vga.o
CC arch/x86/boot/video-vesa.o
CC arch/x86/boot/video-bios.o
LD arch/x86/boot/setup.elf
OBJCOPY arch/x86/boot/setup.bin
OBJCOPY arch/x86/boot/vmlinux.bin
HOSTCC arch/x86/boot/tools/build
BUILD arch/x86/boot/bzImage
上述文件中总共着色了红、橙、绿、蓝、紫,(紫色的是和根文件系统相关的部分,暂时先不谈。)

剩下的着色部分在启动时会依上述顺序执行,好了到这里了,该稍微总结一下了,根据Makefile规则,能够生成上述的启动和内核文件。这基本上是一个可以启动的内核了,可是还确实根文件系统,也就是紫色部分会用到的,这部分会留到后面讲述,这里仍之内核镜像的生成和使用过程为主剖析。先来看一张图,而后再将映像文件是如何连接起来的。


图2.4.3 启动过程图

这张图的有半部分在1.1节就见过了,是内存中代码的架构,其中X = 0x001000 + grub的大小,由于grub大小在编译时才肯定,grub将上图中红色的512字节内容代码拷贝到右图红色箭头所指的地址处并将控制权较给这部分代码,至此Linux内核代码正式开始登场接管后续操做系统的启动工做。而后将实模式代码拷贝到墨绿色箭头处执行,这部分代码被称为setup代码,开始的代码是上图中黄色和绿色的两个代码,实模式跳转到保护模式开始执行,是蓝色箭头表示的。实际的过程和上面讲的还请有点区别,由于内核时通过压缩的,由/arch/x86/boot/compressed/head_32.S调用misc.c中的decompress_kernel()函数解压到0x100000地址处的,因此是整个一次性有grub拷到内存中的,而后会进行自解压等,这里就简化了这一处理过程,不过压缩的内核代码会被grub加载1M+的地方,这些地址的在连接时就肯定了的。

当内核跳到0x100000处时,控制权由bst转交给了真正意义上的kernel,这就是vmlinux的入口(可以使用make vmlinux生成的),这时/arch/x86/kernel/head32.c中的i386_start_kerne()将会被调用,该函数会调用start_kernel()函数。该函数调用若干函数和脚本创建Linux的核心环境,start_kernel()以后调用init(),建立系统级线程,比较重要的如do_basic_setup()完成外设及驱动的加载,不少设备和驱动的源头就来自这里,同时这里也会完成根文件系统的挂载工做。

完成上述工做后,init()会打开/dev/console设备,重定向stdin、stdout、stderr,最后使用execve()系统调用执行init程序。到这里能够说引导工做结束了。

init()进程开启后,系统核心环境已经准备好了,接着init()读取其配置文件/etc/inittab。

上述中setup大小是18K,这表示的是个人机器上是这样的,也许你的会有差别,具体查看和计算方法以下:

[root@ge boot]# od -j 0x01f1 -N1/boot/vmlinuz-3.10.0
0000761 000036
0000762
0000036*512/1024=18K
连接过程就是将若干个输入文件连接成一个文件,这编写经典的helloword程序时,你可能使用了printf或者printk,这里就涉及到了库文件和动态仍是静态编译了,暂时先整个内核的连接过程按照上述也分为boot和kernel两个部分,接下来就来看看这两个部分,若是细心,就会发现有不少built-in.o,如ipc/built-in.o,而且它们的得到方式不是cc而是ld,就是将对应目录下文件连接成一个文件,如:

CC security/capability.o
CC security/lsm_audit.o
LD security/built-in.o
最后这些built-in.o文件和连接脚本一块儿做为输入链接器的文件,最终将生成vmlinuz。

setup连接脚本,相关的注释使用了华文隶书:

1 /*
2 * setup.ld
3 *
4 * Linker script for the i386setup code
5 */
6OUTPUT_FORMAT("elf32-i386", "elf32-i38 6","elf32-i386")
7OUTPUT_ARCH(i386)
8ENTRY(_start)
9
ENTRY表示程序的入口,在head.S文件的274行定义以下。

272 # offset 512, entry point
273
274 .globl _start
275 _start:
276 # Explicitly enter this asbytes, or the assembler
277 # tries to generate a 3-bytejump here, which causes
278 # everything else to push offto the wrong offset.
279 .byte 0xeb # short (2-byte) jump
280 .byte start_of_setup-1f

10SECTIONS
11 {
12 . = 0;
13 .bstext : { *(.bstext) }
下面显示了为何是bstext段的内容放在这里了,在header.S文件中已经制定的生成的段类型了。.o类型文件的全部段的段类型使用对应平台的readelf命令能够查看到。

./boot/setup.ld:  .bstext                : {*(.bstext) }

Binary file ./boot/header.o matches

./boot/header.S: .section ".bstext", "ax"

下面同理

14 .bsdata : { *(.bsdata) }
15
16 . = 495;
17 .header : { *(.header) }
18 .entrytext : { *(.entrytext)}
19 .inittext : { *(.inittext) }
./boot/setup.ld: .inittext : { *(.inittext)}
./boot/tty.c: * These functions are in.inittext so they can be used to signal
./boot/tty.c:static void__attribute__((section(".inittext"))) serial_putchar(int ch)
./boot/tty.c:static void__attribute__((section(".inittext"))) bios_putchar(int ch)
./boot/tty.c:void__attribute__((section(".inittext"))) putchar(int ch)
./boot/tty.c:void __attribute__((section(".inittext")))puts(const char *str)

20 .initdata : { *(.initdata) }
21 __end_init = .;
22
23 .text : { *(.text) }
./kernel/entry_32.S: .section.entry.text, "ax"
24 .text32 : { *(.text32) }
./boot/pmjump.S: .section ".text32","ax"
25
26 . = ALIGN(16);
27 .rodata : { *(.rodata*) }
28
29 .videocards : {
30 video_cards = .;
31 *(.videocards)

./boot/video.h:#define __videocard structcard_info __attribute__((section(".videocards")))
32 video_cards_end = .;
33 }
34
35 . = ALIGN(16);
36 .data : { *(.data*) }
37
38 .signature : {
39 setup_sig = .;
40 LONG(0x5a5aaa55)
41 }
42
43
44 . = ALIGN(16);
45 .bss :
46 {
47 __bss_start = .;
48 *(.bss)
49 __bss_end = .;
50 }
51 . = ALIGN(16);
52 _end = .;
53
54 /DISCARD/ : { *(.note*) }
DISCARD 该段不会出如今输出文件中,一般用于检查映像生成的正确性
55
56 /*
57 * The ASSERT() sink to . is intentional, for binutils 2.14compatibility:
58 */
59 . = ASSERT(_end <= 0x8000, "Setup too big!");
Setup部分的实模式代码,结尾处不能超过0x8000,这样图2.4.1中的x+0x08000才有意义。
60 . = ASSERT(hdr == 0x1f1, "The setup header has the wrongoffset!");
在前面技术计算setup代码大小时用到的这个数字0x1f1,该处存放的是setup的大小信息。其里存放的内容为buf[0x1f1] = setup_sectors-1;这就不能理解512存在的缘由了。

61 /* Necessary for the very-old-loader check to work... */
62 . = ASSERT(__end_init <= 5*512, "init sections too big!");
判断init段大小,应在2.5K如下,才正确
63
64 }
好了上面就是图2.4.1中关于bootsector和setup的内容了。下面看看kernel的连接脚本,有点长,这里略去了64位总线PC状况,只留下32的SECTIONS定义。注释方法同上。

首先来看看大致的连接成什么样子,

# vmlinux
# ^
# |
# +-< $(KBUILD_VMLINUX_INIT)
# | +--< init/version.o + more
# |
# +--< $(KBUILD_VMLINUX_MAIN)
# | +--< drivers/built-in.omm/built-in.o + more
# |
# +-< ${kallsymso} (see description in KALLSYMS section)
上面深红色KBUILD_VMLINUX_INIT表示的内容就是kernel image最开始存放的代码了,接下来存放kernel的主要内容,一些核心、库文件、驱动以及网络代码通通放在这个部分,想知道这里KBUILD_VMLINUX_INIT的内容存放什么内容, 看下面的Makefile脚本。
<Makefile>
755 # Externally visible symbols (used by link-vmlinux.sh)
756export KBUILD_VMLINUX_INIT:= $(head-y)$(init-y)
757export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y) $(drivers-y) $(net-y)
758export KBUILD_LDS :=arch/$(SRCARCH)/kernel/vmlinux.lds
763 vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT)$(KBUILD_VMLINUX_MAIN)
其中756-758行很关键,前两个指定了在kernel前两部分放置的内容,后一个则指定了连接的脚本文件的存放路径,该连接脚本指导连接的整个过程,后面会简单注释如下。
771 vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
从上述的Makefile文件能够看出导出符号KBUILD_VMLINUX_INIT 依赖于head-y和init-y符号表示的内容, :=是脚本一种赋值方法。再看head-y表示的是什么。
<arch/x86/Makefile>
160 # Kernel objects
161
162 head-y := arch/x86/kernel/head_$(BITS).o
163 head-y += arch/x86/kernel/head$(BITS).o
164 head-y += arch/x86/kernel/head.o
$(BITS)=32
能够看到依赖三个文件,这里BITS直接给出32,由于咱们的系统是X86_32的,将全部符号带入可获得:

head-y  :=  head_32.S head32.c  head.c

关于init-y 的内容相似,而且init-y部分很是中要,不少系统开机的设置均由这里代码完成的,因此这部分会放在Linux系统启动来说,若是想抢先看,状况init/main.c 下的start_kernel()函数,该函数作了很是多的系统初始化工做。下面就给出arch/X86/kernel/vmlinux.lds的注释。

程序加载地址和运行地址是对应的两种地址,连接脚本使用两种地址来表示(虚拟/运行地址VMA,加载地址LMA),LMA地址由 AT指定。

#include <asm/boot.h>
ENTRY(phys_startup_32)
SECTIONS
{
. = LOAD_OFFSET + LOAD_PHYSICAL_ADDR;
//.是一个位置计数符号,记录当前位置在目标文件中的虚拟地址(VMA),自增; 初始值为LOAD_OFFSET + LOAD_PHYSICAL_ADDR,前者是咱们熟知的内核虚拟地址空间起始地址0xC0000000,LOAD_PHYSICAL_ADDR是内核image加载的物理地址,由CONFIG_PHYSICAL_START计算获得。
./kernel/vmlinux.lds.S: #define LOAD_OFFSET __PAGE_OFFSET
#define __PAGE_OFFSET _AC(CONFIG_PAGE_OFFSET,UL)
.config CONFIG_PAGE_OFFSET=0xC0000000
这里LOAD_OFFSET就是3G,
<asm/boot.h>
/* Physical address where kernel should be loaded. */
#define LOAD_PHYSICAL_ADDR ((CONFIG_PHYSICAL_START \
+(CONFIG_PHYSICAL_ALIGN - 1)) \
&~(CONFIG_PHYSICAL_ALIGN - 1))

# CONFIG_KEXEC_JUMP is not set
CONFIG_PHYSICAL_START=0x1000000
CONFIG_PHYSICAL_ALIGN=0x1000000
上面是作了边界对齐。综合上面可获得 .= 0xC0000000 + 0x1000000 ;对于0x1000000在图2.4.1中找找看看在内存中的那个位置,这样就能够知道上述虚拟地址3G~4G部分是留给内核的。

phys_startup_32 = startup_32 - LOAD_OFFSET;
这里暂且知道startup_32地址等于__HEAD,就是得到物理地址,就是程序的加载地址。
arch/x86/kernel/head_32.S
__HEAD
ENTRY(startup_32)

/* Text and read-only data */
.text : AT(ADDR(.text) -LOAD_OFFSET) {
return the absolute address (the VMA) of the named section.。
建立第一段,其它段类推,段名.text, 加载地址AT指定的地址,加载地址地址为LOAD_PHYSICAL_ADDR
_text = .;
/* bootstrapping code */
HEAD_TEXT //bootsector对应的字段
./include/asm-generic/vmlinux.lds.h:#defineHEAD_TEXT *(.head.text)
. = ALIGN(8);
_stext = .;
TEXT_TEXT
#define TEXT_TEXT \
ALIGN_FUNCTION(); \
*(.text.hot) \
*(.text) \
*(.ref.text) \
DEV_KEEP(init.text) \
DEV_KEEP(exit.text) \
CPU_KEEP(init.text) \
CPU_KEEP(exit.text) \
MEM_KEEP(init.text) \
MEM_KEEP(exit.text) \
*(.text.unlikely)
其它的就不一一列举了,init段的内容在启动和驱动加载时可能会用到,这部分比较特殊一点。后续段内容参看./include/asm-generic/vmlinux.lds.h
SCHED_TEXT
LOCK_TEXT
KPROBES_TEXT
ENTRY_TEXT
IRQENTRY_TEXT
*(.fixup)
*(.gnu.warning)
/* End of text section */
_etext = .;
} :text = 0x9090
对section中的空隙用0x9090进行填充。0x90是汇编指令NOP的机器码,故至关于在不连续代码间填充空操做。
NOTES :text :note

EXCEPTION_TABLE(16) :text = 0x9090
/* Data */
.data : AT(ADDR(.data) - LOAD_OFFSET) {
/* Start of data section */
_sdata = .;

/* init_task */
INIT_TASK_DATA(THREAD_SIZE)

/* 32 bit has nosave before_edata */
NOSAVE_DATA
PAGE_ALIGNED_DATA(PAGE_SIZE)
CACHELINE_ALIGNED_DATA(L1_CACHE_BYTES)
DATA_DATA
CONSTRUCTORS
/* rarely changed data like cpumaps */
READ_MOSTLY_DATA(INTERNODE_CACHE_BYTES)
/* End of data section */
_edata = .;
} :data
/* Init code and data - will be freedafter init */
. = ALIGN(PAGE_SIZE);
.init.begin : AT(ADDR(.init.begin) - LOAD_OFFSET) {
__init_begin = .; /* pairedwith __init_end */
}
INIT_TEXT_SECTION(PAGE_SIZE)
INIT_DATA_SECTION(16)
.x86_cpu_dev.init : AT(ADDR(.x86_cpu_dev.init) - LOAD_OFFSET) {
__x86_cpu_dev_start = .;
*(.x86_cpu_dev.init)
__x86_cpu_dev_end = .;
}

/*
* start address and size of operations which during runtime
* can be patched with virtualization friendly instructions or
* baremetal native ones. Think page table operations.
* Details in paravirt_types.h
*/
. = ALIGN(8);
.parainstructions : AT(ADDR(.parainstructions) - LOAD_OFFSET) {
__parainstructions = .;
*(.parainstructions)
__parainstructions_end = .;
}

/*
* struct alt_inst entries. From the header (alternative.h):
* "Alternative instructions for different CPU types orcapabilities"
* Think locking instructions on spinlocks.
*/
. = ALIGN(8);
.altinstructions : AT(ADDR(.altinstructions) - LOAD_OFFSET) {
__alt_instructions = .;
*(.altinstructions)
__alt_instructions_end = .;
}
/*
* struct iommu_table_entry entries are injected in this section.
* It is an array of IOMMUs which during run time gets sorted depending
* on its dependency order. After rootfs_initcall is complete
* this section can be safely removed.
*/
.iommu_table : AT(ADDR(.iommu_table) - LOAD_OFFSET) {
__iommu_table = .;
*(.iommu_table)
__iommu_table_end = .;
}

. = ALIGN(8);
.apicdrivers : AT(ADDR(.apicdrivers) - LOAD_OFFSET) {
__apicdrivers = .;
*(.apicdrivers);
__apicdrivers_end = .;
}

. = ALIGN(8);
/*
* .exit.text is discard at runtime, not link time, to deal with
* references from.altinstructions and .eh_frame
*/
.exit.text : AT(ADDR(.exit.text) - LOAD_OFFSET) {
EXIT_TEXT
}

.exit.data : AT(ADDR(.exit.data) - LOAD_OFFSET) {
EXIT_DATA
}

#if !defined(CONFIG_X86_64) ||!defined(CONFIG_SMP)
PERCPU_SECTION(INTERNODE_CACHE_BYTES)
#endif

. = ALIGN(PAGE_SIZE);
/* freed after init ends here */
.init.end : AT(ADDR(.init.end) - LOAD_OFFSET) {
__init_end = .;
}

/*
* smp_locks might be freed after init
* start/end must be page aligned
*/
. = ALIGN(PAGE_SIZE);
.smp_locks : AT(ADDR(.smp_locks) - LOAD_OFFSET) {
__smp_locks = .;
*(.smp_locks)
. = ALIGN(PAGE_SIZE);
__smp_locks_end = .;
}

/* BSS */
. = ALIGN(PAGE_SIZE);
.bss : AT(ADDR(.bss) - LOAD_OFFSET) {
__bss_start = .;
*(.bss..page_aligned)
*(.bss)
. = ALIGN(PAGE_SIZE);
__bss_stop = .;
}

. = ALIGN(PAGE_SIZE);
.brk : AT(ADDR(.brk) - LOAD_OFFSET) {
__brk_base = .;
. += 64 * 1024; /* 64k alignment slop space */
*(.brk_reservation) /* areas brk users have reserved */
__brk_limit = .;
}
_end = .;

STABS_DEBUG
DWARF_DEBUG

/* Sections to be discarded */
DISCARDS
/DISCARD/ : { *(.eh_frame) }
}
从上面的连接脚本你会发现图2.4.3左边的镜像缺乏一部分,正确的形式以下,之因此上下没有对齐,是由于地址啦,内核的VMA地址不是从零开始的,而是0xC0000000 + 0x1000000:

 

图2.4.4内核镜像和启动内存布局


initramfs
本节对PC下的根文件系统作个简单的过程浏览,深刻的文件系统的部分放在嵌入式根文件系统的制做上。Initrd是initial ramdisk的简称,initramfs是initial ram filesystem的简称,是一种cpio格式的内存文件系统,在pc下init-3.10.0.img就是一个cpio格式的,目前initrd类型的启动方式少见了。

PC下的initramfs
先来看看/boot/grub/grub.cfg文件里的内容,下图只截图了3.10内核启动用到的部分,有这么一行:

Initrd  /init-3.10.0.img

若是其在启动配置文件grub.cfg里,那么能够说该文件比较重要了,由于系统启动须要使用它,实际上也能够不使用initrd,只须要将上面那行改为no initrd就能够了,大多数状况下系统仍然能够正常启动,可是对于服务器多半会启动不了,这取决于具体的硬件配置。

 

 


图2.4.1.1  grub配置信息

早期的Linux内核中是没有initrd的,在Linux 0.11版本中,系统启动时直接挂载floppy(软盘),而后启动系统的init进程,该进程在任何Linux系统中都存在,到目前是必不可少的,0.11将init存储在floppy中,可是随着时代的变迁新的存储器也出现了,好比scsi硬盘,sd、usb存储设备等相继出现,这时若是将这些存储设备的驱动编译进内核,能够想象,内核的将会很是大,Linux内核的一个宗旨就是简单,为了保持内核代码简洁,就将这些设备的驱动程序放在了文件系统里。这就意味着要想得到init程序须要先挂载设备的驱动,可是驱动代码在文件系统里,因此系统要先挂载设备的驱动,而后从设备中找到对应的init,这个加载驱动和启动init的工做就放在了initrd中来完成了,下面就来揭开initrd的神秘面纱,首先咱们看看initrd-3.10.0.img的内容,注意该文件为cpio格式的,请按照下述方式操做。

cp  /boot/initrd-3.10.0.img   initrd.gz

lsinitrd  initrd.gz

到这已经能够看到initrd的内容,也可使用下述命令去详细的看看该文件的组织结构。

gzip  –d  initrd.gz

cpio  –ivdm  <  initrd.gz

cat  init

文件输出以下:

#!/bin/nash

mount -t proc /proc /proc
setquiet
echo Mounting proc filesystem
echo Mounting sysfs filesystem
mount -t sysfs /sys /sys
echo Creating /dev
mount -o mode=0755 -t tmpfs /dev /dev
mkdir /dev/pts
mount -t devpts -o gid=5,mode=620 /dev/pts /dev/pts
mkdir /dev/shm
mkdir /dev/mapper
echo Creating initial device nodes
mknod /dev/null c 1 3
mknod /dev/zero c 1 5
mknod /dev/systty c 4 0
mknod /dev/tty c 5 0
mknod /dev/console c 5 1
mknod /dev/ptmx c 5 2
mknod /dev/tty0 c 4 0
mknod /dev/tty1 c 4 1
mknod /dev/tty2 c 4 2
mknod /dev/tty3 c 4 3
mknod /dev/tty4 c 4 4
mknod /dev/tty5 c 4 5
mknod /dev/tty6 c 4 6
mknod /dev/tty7 c 4 7
mknod /dev/tty8 c 4 8
mknod /dev/tty9 c 4 9
mknod /dev/tty10 c 4 10
mknod /dev/tty11 c 4 11
mknod /dev/tty12 c 4 12
mknod /dev/ttyS0 c 4 64
mknod /dev/ttyS1 c 4 65
mknod /dev/ttyS2 c 4 66
mknod /dev/ttyS3 c 4 67
echo Setting up hotplug.
hotplug
echo Creating block device nodes.
mkblkdevs
echo "Loading ehci-hcd module"
modprobe -q ehci-hcd
echo "Loading ohci-hcd module"
modprobe -q ohci-hcd
echo "Loading uhci-hcd module"
modprobe -q uhci-hcd
mount -t usbfs /proc/bus/usb /proc/bus/usb
echo "Loading ext3 module"
modprobe -q ext3
echo "Loading scsi_mod module"
modprobe -q scsi_mod
echo "Loading sd_mod module"
modprobe -q sd_mod
echo "Loading scsi_transport_spi module"
modprobe -q scsi_transport_spi
echo "Loading mptbase module"
modprobe -q mptbase
echo "Loading mptscsih module"
modprobe -q mptscsih
echo "Loading mptspi module"
modprobe -q mptspi
echo "Loading dm-mod module"
modprobe -q dm-mod
echo "Loading dm-mirror module"
modprobe -q dm-mirror
echo "Loading dm-zero module"
modprobe -q dm-zero
echo "Loading dm-snapshot module"
modprobe -q dm-snapshot
echo Making device-mapper control node
mkdmnod
mkblkdevs
echo Scanning logical volumes
lvm vgscan --ignorelockingfailure
echo Activating logical volumes
lvm vgchange -ay --ignorelockingfailure VolGroup00
resume /dev/VolGroup00/LogVol01
echo Creating root device.
mkrootdev -text3 -o defaults,ro /dev/VolGroup00/LogVol00
echo Mounting root filesystem.
mount /sysroot
echo Setting up other filesystems.
setuproot
loadpolicy
echo Switching to new root and running init.
switchroot
echo Booting has failed.
sleep -1
上述文件能够看出,该文件的做用就是建立一些系统运行的依赖目标,而后挂载了几个存储设备的驱动,挂载了根文件系统,这里的文件系统是真正操做系统起来后使用的文件系统,最后启动init进程就结束了。

若是看cpio命令后的目录下内容,黑色的那两个文件不是cpio解压出来的。主要包括启动的程序、启动工具和初始化服务,如网络、终端、设备驱动的加载以及加载其它文件系统。

 

图2.4.1.2 cpio文件解压内容

在来看看bin目录下的内容:


图2.4.1.3 PC根文件系统bin内容

这里能够看到好多命令都在这里了,可是对于嵌入式环境看到的也许有点不一样哦,因为嵌入式环境的存储空间是很是珍贵的,多以这里看到的这么多命令在嵌入式环境就变成busybox了,全部的命令均是连接到busybox,由busybox解析命令。

这里在总结一下这个过程,首先grub根据配置文件grub.cfg解析是否时能initrd,若是时能将内核镜像和initrd拷贝到物理内存中,而后grub将控制权交给内核,内核加载initrd,解压并将内容拷贝到/dev/ram0中,解压后的内容就是一个文件系统了,该文件系统在RAM上,这就是说内存盘(ramdisk)中存在一个文件系统了,内核这是会将其挂载为rootfs(root file system)根文件系统,而后内核建立线程执linuxrc(嵌入式系统),完成后会写在initrd并进行最后的启动,进程这是使用initrd去初始化一些系统资源,挂载根文件系统,调用init进程初始化系统资源并启动shell。至此对用户而言系统可用了。

对于嵌入式设备,若是实际的根设备(非易失性存储器)存在/initrd目录,Linux挂载并将其作为最终的根文件系统,若是不存在initrd的镜像将被丢弃。可是若是在内核命令行中指定了root=/dev/ram0之类,linuxrc的执行将被跳过而且也不会尝试挂载其它文件系统作为最终的根文件系统。

接下来会讲嵌入式系统下根文件系统的制做,再也不讲述PC下initrd的制做,有兴趣能够本身尝试制做一个,嵌入式下的根文件系统制做基于busybox,而且我的以为嵌入式下的根文件系统的制做更能让人理解根文件系统的方方面面。

busybox嵌入式根文件系统的制做:
http://busybox.net/

一、  解压busybox-1.22.1.tar.gz

二、  配置源码 makemenuconfig ARCH=arm CROSS_COMPILE=arm-linux-

三、  make

四、  make install

 

图2.4.2.1 安装busybox过程

五、  进入上一步生成的_install目录,该目录下会生成如下几个文件

 

图2.4.2.2 安装生成内容

六、  添加其它目录mkdirdev etc mnt proc var tmp sys root lib

至于根文件系统的各个目录的做用能够参看FHS,《filesystem Hierarchy Standard》

/dev 设备文件目录,应用程序和驱动程序的桥梁,udev支持的热插拔设备自动建立。

/etc 配置文件,许多系统启动的配置选项均在此,PC下和嵌入式下配置文件的内容差异比较大,有兴趣本身比较。

/mnt 挂载目录点,可挂载其它文件系统类型的存储设备。

/proc 虚拟设备文件系统,一些内核参数由此导出,通常不容许更改权限,驱动程序的一些辅助调试接口一般导出到该目录下。

/lib 存放动态、静态库等文件,命令以及应用程序会使用该库文件,glibc库必须有,不然基本的ls、cd命令可能没法运行。

/sys虚拟文件系统,用于设备和驱动的管理,驱动的class接口位于此,udev自动建立设备节点的功能依赖于该文件系统。

/root根用户登陆目录

七、  添加动态库cp –a$(TOOLCHIAN)arm-none-linux-gnueabi/sys-root/lib/*so* ./lib/

该目录下还有静态库,系统命令会调用该库里的文件。

八、  添加系统文件

[root@ge _install]# cd etc/

[root@ge etc]# vim inittab

[root@ge etc]# vim fstab

[root@ge etc]# Vim passwd

[root@ge etc]# mkdir init.d

[root@ge etc]# cd init.d/

[root@ge init.d]# vim rcS

[root@ge init.d]# chmod +x rcS

[root@ge init.d]# vim profile

这里的文件都是在/etc目录下的文件,主要用于Linux系统启动阶段,inittab属于内核标准文件,有其编写标准,该文件是Linux系统启动时该目录下第一个使用到的文件。

Inittab内容以下:

#this is run first except when bootinginsingle-user mode.
::sysinit:/etc/init.d/rcS
# /bin/sh invocations on selected ttys

# Start an "askfirst" shell ontheconsole (whatever that may be)
ttySAC0::askfirst:-/bin/sh

# Stuff to do when restarting the initprocess
::restart:/sbin/init

#Stuff to do before rebooting
::ctrlaltdel:/sbin/reboot
/sbin/init --mount the root file system
能够参看相关的语法讲解,sysinit具备最高优先级,该行表示,运行/etc/init.d/rcS,关于该文件后面再说,ttySAC0表示终端运行的shell,嵌入式通常是/bin/sh,前面的-表示当该终端打开时会调用/etc/profile脚本,若是没有-,则会运行对应用户目录下的~/.profile,ctrlaltdel表示当ctrl + Alt + Del 三键同时按下时的动做,运行/sbin/reboot命令即重启,restart行表示重启时运行/sbin/init,这里能够看出和冷启动(断电再上电)的区别。
Fstab文件内容以下:

#device   mount-point     type options     dump       fsch order

proc          /proc         proc             defaults         0       0

sysfs         /sys sysfs         defaults   0      0

该文件一样是内核标准文件,能够没有,在rcS下建立上述设备目录。上述表示文件系统的挂载相关信息,如挂载点、文件系统类型等。

rcS的内容以下:

#!/bin/sh

#This is the firstscript called by initprocess

/bin/mount –a

一般这里的内容会比较多,通常会调用其它的脚本启动一些常驻的应用程序,为shell脚本,内容差异可能很是大。

Profile文件内容

#!/bin/sh

exportHOSTNAME=ge

export USER=root

export HOME=root

#exportPS1="[$USER@$HOSTNAME\W]#"

PATH=/bin:/sbin:/usr/bin:/usr/sbin

LD_LIBRARY_PATH=lib:/usr/lib:$LD_LIBRARY_PATH

export PATHLD_LIBRARY_PATH

该文件设置一些环境参数。

在passwd文件中添加以下行:

root::0:0:root:/root:/bin/sh,即只保存与root相关项

九、  建立设备节点

mknod dev/console c 5 1

mknod dev/null c 1 3

一般必须有console这个目录,null目录也经常须要,若是没有console目录,启动会致使失败。到此busybox部分工做完成了,由于选择的是动态编译方式,因此ls等命令依赖的glibc库文件须要添加到/lib目录下,可是真正的根文件系统制做完毕还有/lib没有完成,实际使用中一般会进行压缩,通常两种方式,一种.tar.gz,还有一种一种就是cpio格式。在根目录下生成cpio格式的压缩文件,使用的以下命令:

Find . | cpio–quiet –o –H newc | gzip > ./rootfs.cpio.gz

嵌入式文件系统深刻
上面已经构建了一个可使用的根文件系统了,ls、cd命令已经能够正确运行,如今给两个题目,接下来就这两个题目想阐述如何针对项目需求去定制一个根文件系统。

题目一:物联网发展趋势下,之后的嵌入式设备会联网,如何添加防火墙的功能呢?

题目二:据说具备加密功能的ssh可以为在网络上传输提供一层保障,该如何实现该功能呢?

对于题目一使用开源软件包iptables,交叉编译iptables-1.4.18.tar.gz软件包,

./configure CC=arm-linux-gcc  --enable-static –disable-shared–host=arm-linux –prefix=$(YOUR INSTALL DIRECTORY)

Make

Make install

这样就编译好了,能够把install目录下的相关内容拷贝到上述对应的目录中,可执行文件到/sbin 、库文件到/lib中,这样在shell下就可使用iptables命令了。

题目二比题目一稍微复杂一点,ssh功能的支持包是dropbear,可是呢编译该时它还依赖zlib这个压缩包提供的功能,因此呢,须要首先编译zlib包,然后才是编译dropbear包。

zlib-1.2.8.tar.gz编译以下:

export CC=arm-linux-gcc

./configure –prefix=/$(YOUR INSTALLDIRECTORY)

Make && make install

 

Dropbear-2013.5.58 的编译命令以下

./configrure –with-zlib=$(YOUR zlib INSTALLDIRECTORY) cc=arm-linux –host=arm-linux

Make && make install

一样将生成的可执行文件以及库文件复制到对应的根文件系统的目录中,这样就可使用该功能,若是对configure不太懂,./configure –-help命令能够查看。

经过上面的两个例子,来总结一下如何向根文件系统添加新功能的思路,首先根据要提供的功能,(通常多是/bin、/sbin之类下的命令,多是/lib下的库(应用程序使用)文件,抑或二者兼有),查找有没有现成的开源包,若是有,那么交叉编译,拷贝,若是没有那就本身写,而后交叉编译,拷贝。一切都是那么的顺利成章啊~!

最后须要提醒一下上文提到的udev机制在也是须要编安装和拷贝的。

根文件系统的加载
这里的加载指的是操做系统启动时的加载过程,在系统启动时start_kernel()->vfs_cache_init()->mnt_init()->init_rootfs(),该函数会挂载rootfs做为根文件系统,可使用cat /proc/mounts查看,而后会解压用gzip压缩的归档文件到rootfs目录,接着执行解压后的init程序,一旦该程序运行,那么就称其为init进程,并将控制权移交给init进程,这也是系统由内核到用户态的标志,若是在解压的rootfs目录下找不到init程序,那么会根据解析的cmdline参数,找“root=XXX”,并挂载,该参数有devicetree的chosen节点提供。在Linux内核编译时CONFIG_INITRAMFS_SOURCE指明intramfs的位置。一般一个系统可用的文件系统在/etc/fstab文件中,可是嵌入式环境下一般不用。

设备树
对于Linux系统,一般启动分为bld(bootloader),bst(bootstrap),vmlinux(这里的vmlinux不包括bst,可是一般Linux内核源码包,编译出来的最终vmlinux是包含bst,这里将其分开讲解了)。当你在Linux-3.10根目录下使用make vmlinux时,你会发现确实有该目标,但是这里所说的vmlinux是用来生成vmlinuz的,因此概念上仍是有点区别的,区别在于前一个vmlinux并不包括bst部分,而最终的vmlinuz是包括bst的。这个关于Makefile的分析就略过了,关键的执行步骤会在下面展示出来的。

这里先提一下,因为当/sbin/init进程运行起来之后,其读取/etc/inittab配置文件,根据配置信息肯定运行级别,笔者对应的运行级别为5,即图形界面方式,在启动时的视频中就显示了该信息。Init进程会执行系统初始化脚本/etc/rc.d/rc.sysinit,对系统进行配置,以读写方式挂载根文件系统,至此,系统基本运行起来了,接着运行/etc/rc.d/rc,该文件会判断配置文件中的启动运行级别,并定义了服务启动的顺序,并启动/etc/rc.d/rc5.d,该目录下的内容连接至/etc/init.d目录下,后面会启动虚拟终端/sbin/mingetty,在运行级别5上启动xwindow,这是就是你们看到的登陆界面了,用户输入用户名和密码,而后/etc/passwd完成密码验证,登陆了。这一过程的文档确实不少,因此这里打算深刻两个细节分析一下,这也是笔者在设备驱动的道路上吃过苦头才认认真真看了源码的。

网上分析Linux内核启动的文章不少了,就算不是针对3.10版本的内核,也能够参考,并结合3.10版的代码进行分析,这里打算重点讲下嵌入式下的device tree的解析和设备的构建,将来嵌入式系统解析设备信息将会采用设备树的方式,这也是为何这里会详细讨论该解析过程了,可是这是和嵌入式处理器息息相关的,不一样的处理器的设备树的构建和解析也是有区别的,因此Linux内核会将这部分的解析代码放在setup_arch()函数中完成。

devicetree设备树
因为3.10版本的嵌入式环境下大多数使用的是device tree的方法来描述设备,不只如此,uboot也能够而且也正在采用device tree的方法,因此这里会花点篇幅介绍它,而后才是Linux系统的启动。

为何先介绍device tree:

在2.3节中bootloader功能的提到以下两个步骤:

4. Setup the kernel tagged list or dtb.

5. Call the kernel image.

对于3.10版本的内核使用dtb(device tree block)方法来描述设备拓扑,因此上面的4里的tagged list并无被使用,而是使用了dtb,对于上面的5,call内核前将前三个寄存器按以下方式设定。

- CPU register settings

  r0= 0,

  r1= machine type number.

  r2= physical address of tagged list in system RAM, or

      physical address of device tree block (dtb) in system RAM

对于r2寄存器的值为RAM中dtb的地址。

内核在启动时会查r2地址为的4偏移处的值,若是r2地址偏移零处的值为零,那么表示没有taglist或者dtb被传递过来,若是4的偏移处为0xd00dfeed,那么表示传递过来的是dtb。

同时也会传递系统内存和根文件系统。这是经过device tree的chosen节点实现的。关于device tree的语法参看:

http://www.devicetree.org/Device_Tree_Usage

关于device tree 的用法能够参看:

https://wiki.freebsd.org/FlattenedDeviceTree

在操做系统源码中/arch/arm/boot/dts/目录下常见以.dts和.dtsi为后缀的文件,一般来讲.dtsi通常是cpu级别的描述,而.dts则是板级的描述,在编译生成dtb是一般.dts会包含.dtsi,以构成完整的设备树,当还有一个skeleton.dts的一般会被包含。

这里给出一个

/proc/device-tree 是device tree的一个调试入口,

Documentation/devicetree/booting-without-of.txt.

devicetree的信息获取
先来看看devicetree的解析过程:

首先看arch/arm/kernel/head.S,注意和arch/arm/boot/compressed下的head.S区别,后者属于bst的部分, arch/arm/boot/compressed下的内容,最终会生成bst部分的代码,这里从内核的head.S开始讲起。该函数调用须要知足时各个寄存器存储的内容以下:

R0:内核所在地址  R1 : machine number

R2: dtb or atags     R8: phys_offset      R9:cupid         R10:procinfo

head.S
79 ENTRY(stext)
119 bl __vet_atags
135 ldr, r13=__mmap_switched
142 b __enable_mmu
415 __enable_mmu:
441 b __turn_mmu_on
460 ENTRY(__turn_mmu_on)
467 mov r3, r13
468 mov pc, r3
上述119行跳转地址定义位于/arch/arm/kernel/head-common.S
18 #ifdef CONFIG_CPU_BIG_ENDIAN
19#define OF_DT_MAGIC 0xd00dfeed
20#else
21#define OF_DT_MAGIC 0xedfe0dd0 /* 0xd00dfeed in big-endian */
22#endif
46__vet_atags:
47 tst r2, #0x3 @ aligned?
48 bne 1f
49
50 ldr r5, [r2, #0]
51#ifdef CONFIG_OF_FLATTREE
52 ldr r6, =OF_DT_MAGIC @ is it a DTB?
53 cmp r5, r6
54 beq 2f
55#endif
56 cmp r5, #ATAG_CORE_SIZE @ is first tag ATAG_CORE?
57 cmpne r5, #ATAG_CORE_SIZE_EMPTY
58 bne 1f
59 ldr r5, [r2, #4]
60 ldr r6, =ATAG_CORE
61 cmp r5, r6
62 bne 1f
63
642: mov pc, lr @ atag/dtb pointer isok
65
661: mov r2, #0
67 mov pc, lr
68ENDPROC(__vet_atags)
46行判断r2寄存器内容(dtb或者atags的地址)是否四字对齐,若是不是那么就是atags的地址能够直接返回,将首地址内容赋值为零,返回head.S函数的调用处。若是是四字对齐的,那么多是dtb了,须要进一步判断。50行获取四字对齐的地址的起始内容,若是是dtb,那么这里应该放的就是devicetree的魔数了。52行存储设备树的魔数,定义见18~21行。56-62处理ATAG_CORE的状况。
arch/arm/kernel/head.S的135~468行,跳转到arch/arm/kernel/head-common.S执行,代码以下:
80__mmap_switched:
81 adr r3, __mmap_switched_data
82
83 ldmia r3!, {r4, r5, r6, r7}
84 cmp r4, r5 @ Copy datasegment if needed
85 1: cmpne r5, r6
86 ldrne fp, [r4], #4
87 strne fp, [r5], #4
88 bne 1b
89
90 mov fp, #0 @Clear BSS (and zero fp)
91 1: cmp r6, r7
92 strcc fp, [r6],#4
93 bcc 1b
94
95 ARM( ldmia r3, {r4, r5, r6, r7, sp})
96 THUMB( ldmia r3, {r4, r5, r6, r7} )
97 THUMB( ldr sp, [r3, #16] )
98 str r9, [r4] @Save processor ID
99 str r1, [r5] @Save machine type
100 str r2, [r6] @ Save atags pointer
101 cmp r7, #0
102 bicne r4, r0, #CR_A @ Clear 'A' bit
103 stmneia r7, {r0, r4} @Save control register values
104 b start_kernel
105 ENDPROC(__mmap_switched)
106
107 .align 2
108 .type __mmap_switched_data,%object
109 __mmap_switched_data:
110 .long __data_loc @ r4
111 .long _sdata @ r5
112 .long __bss_start @ r6
113 .long _end @ r7
114 .long processor_id @ r4
115 .long __machine_arch_type @ r5
116 .long __atags_pointer @ r6
117 #ifdef CONFIG_CPU_CP15
118 .long cr_alignment @ r7
119 #else
120 .long 0 @ r7
121 #endif
122 .long init_thread_union +THREAD_START_SP @ sp
123 .size __mmap_switched_data, . -__mmap_switched_data
         其中100行完成将__atags_pointer设置为dtb的地址。104行跳转到start_kernel函数执行。该函数定义init/main.c中,网上对start_kernel()的分析很是多,该函数会间接调用不少子系统的代码该start_kernel()函数的501行以下:

setup_arch(&command_line);

上述函数会解析dtb。

    该函数最终将解析的根节点存放于of_allnodes中,在后续设备注册时,会扫描该节点,和该节点匹配上才进行实际的注册,这里设备分为两种状况,举个例子若是U盘想要工做,通常嵌入式Soc会集成usb控制器,这里usb控制器和u盘都会用到of_allnodes,所不一样的是usb控制器的初始化会在start_kernel()中的do_basic_setup()中完成,而U盘的初始化则会延迟到U盘插入,热插拔机制会启动相应的支持。

在setup_arch配置完成以后还会有一个board的初始化,arch/arm/match-XXX/目录下的一些平台相关的代码在这里会被调用,好比该目录下的init_irq、init_machine函数就会被调用。

在init_machine函数中会调用of_platform_populate()注册平台总线和平台设备。对于像i2c、spi之类设备则会在early_platform_add_devices()完成注册。

开发中的Linux内核源码,git.kernel.org/cgit/linux/kernel/git/Torvalds/linux.git

上述的vmlinux是没压缩过的内核映像,真正使用时会将内核压缩省以减少内核映像的大小和以及节约映像从Flash或磁盘拷贝到内存的时间。去了压缩连接的过程,这一过程在ARM的嵌入式平台和PC平台均存在,可是由上面的基础来看压缩部分的应该不难了。X86部分的压缩实现见arch/x86/boot/compressed/。

最后来点收尾的,你可能发如今1.1节中说起Linux内核升级的时候讲到了make modules 和make modules_install,这里一直未提到该内容,从该命令能够看出来是和module有关系的,config时会选择编译成module仍是编译进内核抑或不编译,若是选择编译成module,这里两个命令就是用来处理那部分代码的,简单点说就是作以下的工做(注意在现有ext3文件系统上操做,系统启动时只有根文件系统可有):

cp -f/opt/linux-3.10/modules.order    /lib/modules/3.10.0/

cp -f/opt/linux-3.10/modules.builtin   /lib/modules/3.10.0/

该文件夹下的modules.dep,描述模块的依赖关系,当有热插拔事件发生时,内核会将处理过程推到用户态处理,主要关系udev实现机制,该机制会建立必要的设备节点和加载对应的驱动须要的模块,模块的依赖关系就在modules.dep里描述了。这里就能够看出来为何和make vmlinux分开处理了,由于一个编译生成可执行文件,一个是在现有文件系统中插入一部分“module”。

devicetree函数的解析
在为了让上面setup_arch()函数所表示的启动过程更具备真实性,这里咱们举个devicetree的例子,首先知道一般嵌入式下使用的处理器被称为SOC 而不是cpu,由于该芯片一般集成了许多控制器,因此假设咱们的cpu是cotex-A9的,而且假设其集成有apb总线,在ahb总线上挂有以太网控制器,apb总线挂有i2c控制器,在实际的电路板上还有一个i2c控制的4951的音频芯片。

这里讲Soc具备的东西写成.dtsi的文件,将板上其它的外设写成.dts文件,这样在.dts文件中include该文件就能够了,这样可具备良好的可移植性。

ABCD.dtsi文件,文件在内核中的位置,arch/arm/boot/dts文件下,

/{
compatible = “ABCD,exp”;
cpus {
#address-cells=<1>;
#size-cells =<0>;
cpu0{
device_type=”cpu”;
compatible=“ABCD,cortex-a9”;
reg = <0>;
};
ahb@e0000000{
compatible=”simple-bus”;
mac0:ethernet@e000d0000{
compatible=” ABCD,eth”;
#address-cells=<1>;
#size-cells=<0>;
reg =<0xe0000000 0x3000>;
};
};
};
dts文件为:
Include ABCD.dtsi

/ {
mode = “ABCD corp Board”;
compatible = “ABCD,exp”;
chosen = {
bootargs = “console=ttySA0init=/linuxrc root=…”
};
apb@d0000000{
i2c0:i2cd0004000{
ak4951:code@5{
compatible=”A,ak4951”;
reg =<0x12>;
};
};
};
好了,设备树构建完毕了,这里省去内存信息,中断等不少其它内容,关于devicetree内容参看,http://www.devicetree.org/Main_Page,接着向下看,能够说devicetree的三分之一的内容体如今这里。

 


图2.7.1 setup_arch()函数调用过程

Setup_arch函数里调用的第一个函数,也就是这里惟一出现等号的地方,这里mdesc,这就是和devicetree相关的一个很是重要的东西,

DT_MACHINE_START(exp_DT, “Aexp(Flattened Device Tree)”)

         .restart_mode = ABCD _map_io,

.init_early= ABCD _init_early,

.init_irq=irqchip_init,

.init_machine=ABCD_init_machine,

.dt_compat= exp_dt_board_compat;

首先使用DT_MACHINE_START定义一个体系结构相关的函数,后续的的一些初始化函数将调用这里,这就有一个问题,Linux如何知道是这个呢,根据dt_compat,其指向一个compatible属性的字段,结合上面dts文件的定义,定义以下的exp_dt_board_compat;

Static const char *const exp_dt_board_compat= {

         “ABCD,exp”,

         NULL,

}

直觉告诉咱们,这二者匹配上了,可是具体的匹配过程是什么呢?耐心往下看,

ach/arm/include/asm/mach/arch.h

#defineDT_MACHINE_START(_name, _namestr)           \

static conststruct machine_desc __mach_desc_##_name   \

 __used                                                           \

 __attribute__((__section__(".arch.info.init")))= {        \

         .nr             =~0,                                     \

         .name                =_namestr,

#endif

能够看到该宏定义的,将放在.arch.info.init段中,再来看setup_arch中得到的对应代码,这里咱们使用该宏定义一个和上述devicetree相关的结构体出来,

         for_each_machine_desc(mdesc){

                   score= of_flat_dt_match(dt_root, mdesc->dt_compat);

                   if(score > 0 && score < mdesc_score) {

                            mdesc_best= mdesc;

                            mdesc_score= score;

                   }

         }

一看到到for就该笑了,就是从上面那个段,遍历一遍查看字段是否相等就能够了,为了验证咱们的想法,向下看:

#define for_each_machine_desc(p)                           \

         for(p = __arch_info_begin; p < __arch_info_end; p++)

这里用了一个很是巧妙的方法,__arch_info_begin和__arch_info_end在lds脚本里定义的,在连接时肯定的,参看脚本arch/arm/kernel/vmlinux.lds.S的180行。

       到此咱们得到了体系结构相关的初始化代码,接着看of_get_flat_dt_root(),该函数得到设备树的根节点,这很好办,由于前面话了不少篇幅说的r2就是设备树的地址,可是若是看代码,可能会以为很复杂,这是由于设备树在编译时会加上头信息,此外还要进行align操做。

setup_machine_fdt里其它函数的意义从字面上就能够看出来,这里就是针对该machine解析一些devicetree的字段,由于它们的优先级比较高,unflatten_device_tree()函数,中解析设备树,而且前面提到的ofallnode的信息来自于此。那咱们看看那些优先级不是很高的设备树字段是如何使用的,这也是个初始化的过程哦,看前面设备树中的mac字段,do_basic_setup()中调用驱动初始化相关函数,mac字段是以太网信息的表示,因此这里会进行以太网相关的初始化的工做了,整个devicetree对设备的处理都是按下面的方式处理的。

设备驱动的框架通常是,写一个driver结构体,里面包括了设备的操做方法集,而后注册该结构,该结构调用driver的probe方法。

module_platform_driver(ABCD_drvier);
struct staticplatform_drver ABCD_drvier={
.probe = ABCD_drv_probe;
.remove =
.drver ={
.name = “ABCD-eth”,
.own = THIS_MODULE,
.of_match_table=ABCD_eth_dt_ids,
},
};
static conststrut of_device_id ABCD_eth_dt_ids[]={
.compatible=”ABCD,eth”,
{ }
};
ABCD_probe(struct platform_device *pdev),该函数的参数中的pdev,该函数中有一个得到devicetree的方法,如pdev->dev.of_node,这是匹配上,那就得到上了该设备相关的信息,后面的过程相似之前的处理了,注册向下进行了。

 linux310启动
由2.5.3知道了初始化主要在start_kernel中完成,

 

图2.6.1 start_kernel初始化

上图中能够知道,内存子系统已经初始化完毕了,后面初始化其它子系统时就能够直接申请内存了。

 


图2.6.2 rest_init初始化流程

rest_init首先调用kernel_thread建立连个进程,Linux内核规定init进程的ID等于1,因此先建立kernel_init,可是进程号等于一的进程,可是ID号等于1的进程在结束时会建立内核守护进程,守护进程相关的初始化会由后面的kernel_thread(kthread…)建立,这里使用了competition机制在kernel_init得到ID号1后,该进程处于等待状态,等待kernel_thread(kthread…)初始化完毕。

 

接下来的do_basic_setup()中很是重要的两个函数driver_init()和do_initcalls(),


前者完成总线等子系统的初始化,然后这则是根据以下initcall的优先级调用。

#define pure_initcall(fn) __define_initcall(fn,0)
#define core_initcall(fn) __define_initcall(fn,1)
#define core_initcall_sync(fn) __define_initcall(fn,1s)
#define postcore_initcall(fn) __define_initcall(fn,2)
#define postcore_initcall_sync(fn) __define_initcall(fn,2s)
#define arch_initcall(fn) __define_initcall(fn,3)
#define arch_initcall_sync(fn) __define_initcall(fn,3s)
#define subsys_initcall(fn) __define_initcall(fn,4)
#define subsys_initcall_sync(fn) __define_initcall(fn,4s)
#define fs_initcall(fn) __define_initcall(fn,5)
#define fs_initcall_sync(fn) __define_initcall(fn,5s)
#define rootfs_initcall(fn) __define_initcall(fn,rootfs)
#define device_initcall(fn) __define_initcall(fn,6)
#define device_initcall_sync(fn) __define_initcall(fn,6s)
#define late_initcall(fn) __define_initcall(fn,7)
#define late_initcall_sync(fn) __define_initcall(fn,7s)
根据initcall级别,调用相应的函数,以下:


后面调用2.4介绍的文件系统执行/sbin/init,该/init会执行/etc下的配置文件,内容在2.4节介绍过了,init最终启动shell给用户。

最后一行的init_idle_bootup_task(current);是建立ideal进程,能够知道该函数的ID等于0。

相关文章
相关标签/搜索