文章目录
一、内核分析之编译初体验
1.解压缩
2.配置
内核的配置有三种方法
(1)直接执行make menuconfig
,从头到尾每一条都自己配置,有成千上万的配置项,比较复杂
(2)使用默认的配置,在这个基础上进行修改
先在内核目录下进行查找:find -name "*defconfig*"
进入对应的arch/arm/configs目录,可以看到:
那么就可以直接执行make s3c2410_defconfig
进行配置
执行完之后会提示所有的配置项都被写到.config
里面,因此这条命令的执行结果是保存在.config里面的
后面再执行make menuconfig
,他肯定会去读取这个.config,然后出现一个菜单,可以在这个菜单上修改配置项
注意执行make menuconfig之前,需要安装ncrses,他是一个提供功能键定义、屏幕绘制以及基于文本终端的图形互动功能的动态库。
sudo apt-get install bc
sudo apt-get install libncurses5-dev libncursesw5-dev
sudo apt-get install zlib1g:i386
sudo apt-get install libc6-i386 lib32stdc++6 lib32gcc1 lib32ncurses5
之后执行make menuconfig就可以出现如下界面
(3)使用厂家提供的配置文件
方法就是直接将厂家的config文件复制为.config文件,然后make menuconfig进行配置
输入Y,就会被编译进内核里面
输入N,就不编译
输入M,就被做为一个模块,也就是一个驱动程序
输入*,build-in,表示编译进内核
输入M,表示被作为一个模块
3.编译
编译直接执行make命令即可
或者当我们想生成uImage,uImage就是在真正的内核前面加一个头部(uboot支持的内核有一个头部)
如果想编译一个内核给uboot用,那就执行make uImage
将编译好的UImage烧到单板上
菜单项里面的K是烧写UImage,然后打开dnw,进行发送
如下:
在源码目录下看到cmd_menu.c中命令K
他是先用usb命令来接受dnw发送的数据,收到之后就去擦除内核分区,然后再把30000000位置的东西烧写filesize大小到内核分区
4.启动内核
倒数计时之后,就会去执行bootcmd这个环境变量里面定义的两条命令,这两条命令一个是从flash中读出内核,一个是启动内核
烧写完之后可以使用b命令来进行启动,如下:
当然,我们这里没有烧写文件系统,所以最终会卡着
二、内核分析之配置
通过前面我们知道配置最终生成的是.config文件,里面有很多配置项,以DM9000为例,进行搜索可以看到:
y也就是会被编译进内核里面,继续搜索CONFIG_DM9000,可以看到如下:
1.C源码里面用到了CONFIG_DM9000,也就是一个宏,宏只能在头文件或者C文件中定义,因此是在include/linux/autoconf.h定义的
2.driver/net/Makefile,也就是子目录的Makefile里面有
对于子目录的Makefile,格式比较简单,
obj-y += xxx.o 表示xxx.c这个文件最终会被编译进内核里面
obj-m += yyy.o 表示yyy.c这个文件最终会被编译成一个可加载的模块yyy.ko
3.include/config/auto.config
对于子目录下的CONFIG_DM9000则是在include/config/auto.config中进行定义
同样,auto.config也是自动生成的,内容来源于.config
而include/config/auto.config则是被顶层的Makefile包含
4.include/generated/autoconf.h 自动生成的(make机制),里面的内容来源于.config,打开autoconf.h看时都是被定义为1的
因此我们执行make uImage的时候,发生了如下事情:
1、.config---->auto.config,这个文件是被源代码使用的
2、.config---->autoconf.h,这个文件是被顶层Makefile包含的,子目录下的Makefile来使用
三、内核分析之Makefile
首先我们从最简单的角度总结Makefile的作用:
(1)决定编译哪些文件
(2)怎么编译这些文件
(3)怎么链接这些文件,最重要的是他们的顺序如何
Linux内核Makefile文件分类如下:
1.分析子目录下的Makefile,以driver/char目录下的Makefile进行分析为例,可以看到:
如果CONFIG_SGI_SNSC这个变量在配置文件中被定义为y
,则snsc.c snsc_event.c会被编译为snsc.o snsc_event.o,最后链接到内核里去
如果被定义为m
,则snsc.c snsc_event.c会被编译为模块.ko文件
那么对于a.c和b.c两个文件
被编译进内核很简单,obj-y += a.o b.o
如果这两个要组成一个模块,则是:
obj-m += ab.o
ab-objs := a.o b.o
最终的结果就是a.c–>a.o,b.c–>b.o,然后这两者链接为ab.ko
注意到我们前面编译内核的时候使用的命令时make uImage
。搜索发现uImage的时候发现uImage是在arch/arm/Makefile
中定义
2.架构相关的Makefile
因为uImage是在这里定义,最终也会生成uImage,那么可以知道架构相关的Makefile肯定会被包含进顶层的Makefile
uImage依赖于vmlinux
,uImage = 头部 + 真正的内核
因此我们制作uImage的时候要先编译出真正的内核,真正的内核就是vmlinux
vmlinux的依赖在顶层Makefile里面
3.顶层Makefile
在顶层Makefile里面可以看到是包含了架构相关的Makefile的
同时还会包含auto.config
配置文件
分析vmlinux可以看到:
具体这里不详细介绍,可以分别进行搜索找出原材料。
为了更方便知道结果,可以指向make uImage V=1命令,V=1是将命令详细的列出来,执行这个命令来看最终是做了什么,执行了什么。
总结分析Makefile最终知道:
1.第一个文件:arch/arm/kernel/head.s
2.链接脚本:arch/arm/kernel/vmlinux.lds
四、内核分析之启动过程
从前面我们分析u-boot启动过程可以知道,先是设置参数,然后启动内核。怎么启动?执行thekernel这个函数。theKernel就是内核的入口地址
第一个参数是0
第二个参数是机器ID
第三个参数是那些参数存放的地址
那么我们的内核首先就会去处理u-boot传过来的参数
注意内核的最终目的是运行应用程序
对于pc机来说应用程序在C盘,D盘。对于Linux来说是在根文件系统,因此需要挂接根文件系统,然后启动应用程序。
知道最开始和最终目的,下面来分析中间做了什么
补充一点:对于最终生成的很大的内核,可以进行压缩
,最终得到一个很小的内核,在压缩后的内核前面加上一段自解压代码
,最后内核就从自解压代码这里开始运行,自解压代码的作用就是将压缩后的内核解压出来,然后再去执行解压缩后的内核。
首先分析arch/arm/kernel/head.s
文件
100ask/uboot-2.6/linux-2.6.22.6/arch/arm/kernel/head.s
所做的工作:
1.判断是否支持这个CPU
2.判断是否支持这个单板,也就是启动内核时传入的R1(机器ID)
3.建立页表
4.使能mmu
5.跳转执行start_kernel,这是kernel的第一个C函数,在这个C函数里面去处理启动参数
里面主要是进行一些初始化工作,前面我们已经处理了传进来的机器ID,但是启动参数还没有处理,这个函数里面的下面这两个函数就是用来处理传入的参数的
如果uboot没有传入命令行参数(也就是bootargs
这个环境变量对应的参数),那就回使用默认的命令行参数default_command_line
同时也会调用rest_init
函数,这个函数里面会去创建一个内核线程,想当于调用kernel_init这个函数
u-boot传过来的参数如下:
内核启动流程
大致调用关系:
start_kernel
setup_arch //解析uboot传入的启动参数
setup_command_line //解析uboot传入的启动参数
parse_early_param
do_early_param //从_setup_start到_setup_end,调用early函数
unknown_bootoption
obsolote_checksetup //从_setup_start到_setup_end,调用非early函数
rest_init
kernel_init
prepare_namespace
mount_root //挂接根文件系统
init_post
//执行应用程序
以命令行参数为例来进行分析:
进入u-boot可以看到:
分析代码可以发现:命令行参数最开始是被保存在某个字符串里面,对于不同的"root=",对应不同的处理函数root_dev_setup
,这两者被定义在一个结构体里面,这些很多的结构体被链接脚本放到一块,在arch/arm/kernel/vmlinux.lds里面
*(.init.setup),调用的时候就从start一直搜索到end,具体怎么调用在前面内核调用流程里面
在flash里面没有分区表,但是从命令行参数得知有root = /dev/mtdblock3
,那么整个分区怎么体现?
—>在代码里面写死