网站首页 > 技术文章 正文
上篇中给大家介绍了 makefile如何生成image、内核自解压代码分析,下篇继续给大家分享内核启动分析:
三、VMLINUX内核启动代码分析
从下面开始就进入了真正的内核了,首先执行的代码是在
arch/arm/kernel/head.S中的汇编代码,然后就进入了内核的c代码部分
(init/Main. c),注意,此刻内核的编译链接地址不再是0x0 了,而是0xC0008000
了。
内核汇编部分:(关于该部分参考我的另一篇详细的分析)
刚刚进入内核汇编时的一些系统状态和寄存器值如下
Mmu 关闭,I Cache 和D Cache 清除并关闭,r0=0, rl=architecture ID。
从arch/arm/kernel/vmlinux. Ids文件中可以看出,解压后内核是从stext
段开始执行的。
1. 确保系统处于SVC模式并且FIR、IRQ都已经关闭、
2. 调用_lookup_processor_type函数,通过协处理器cpl5读取出系统cpu
的id,然后通过查無 [^核映像中 .proc. info, init段的所有
proc_info_list数据结构,以判断该内核是否支持该款cpu。
3. 调用_lookup_machine_type函数,检査由uboot传递进来的machine
architecture number是否被内核支持,也就是在内核
的 .arch. info, init段中查找看是否有对应 machine number的
machine_desc数据结构体存在。
下面进入start_kernel函数去执行,in init/Main. c文件
1. printk(linu:x_banner)打印内核的一些信息,版本,作者,编译器版本,日
期等信息
2. setup_arch(&command_line); /氺 in arm/kernel/setup. c */
函数原型:void —init |setup_arch|(char **cmdline』)
批注[si4]:嗯,重中之重啊
a. setup_processor()+lookup_processor_type〇 获取对应处理器 id 的
proc_info_list 结构体 list,取出 cpu_name,打印关于 epuname,id,
proc_arch 的信息,设置上 system_utsname= list->arch_name(armv4t),
elf_platform= list->elf_name (v4), elf_hwcap = list->elf_hwcap;
/* l|2|4 */,接着执行 cpu_proc_init()函数
Cpu-single. h中有如下定义
#define cpu_proc_init —cpu_fn(CPU_NAME, _proc_init)
#def ine —cpu_fn (name, x) —catify_fn (name, x)
#def ine —catify_fn (name, x) narae##x
CPU_NAME = cpu_arm920
所以 cpu_proc_init();函数实际上是函数 cpu_arm920_proc_init()函
数,在pr〇c_arm920. S文件中定义的。
b. mdesc = setup—machine (machine—arch—type)函数
取出当前系统的在内核.arch. info, init段内对应的machine_desc数据
结构体的地址list,并打印出list-name内容
c. machine_name = mdesc->name 设置全局变量 machine_name
d. tags = phys_to_virt(mdesc_>boot_params),修改默认的参数地址,使
用uboot传运进^的参数的真实地址0x30000100并将其转换成虚拟地址
OxcOOOOlOO
e. 参数解析
if (tags->hdr. tag == ATAG_C0RE) {
if (meminfo. nr—banks != 0) /* meminfo defined in setup, c */
squash_mem_tags(tags);
parse_tags(tags);
} _
static struct meminfo meminfo —initdata = { 0, };in steup. c
所以这里会调用parse_tags(tags) (in steup. c)函数。
Steup_arch ()-->parse_tags ()-->parse_tag (),
all function in steup. c
_tagtable_begin, _tagtable_end 这两个参数是在
arch/arm/kernel/vmlinux. Ids文件中确定的,这个区间存放了各种参
数的解析函数,可以直接引用
parSe_tag〇函数就是查找这个区间的各种参数头,来和传递寄来的参数
头进行匹配,找到了之后就利用其中的函数指针parse来进行参数解析,
具体怎么解析需要分析各种参数的作用了。如果匹g不到的话,会打印
信息说 Ignoring unrecognised tag 0x%08x\n,表示 uboot 传递进来了
一个内核识别不了的参数。
f. struct mm_struct init_mm = INIT_MM(init_mm);
start_code = (unsigned long) &_text;
init_mm. end_code = (unsigned long) &_etext;
init_mm. end_data - (unsigned long) &_edata;
init mm. brk = (unsigned long) &—end;
_text, _etext, _edata, _end 都是在 arch/arm/kernel/vmlinux. Ids
文件中#定的
g. parse_cmdline(cmdline_p,from)解析 uboot 传递寄来的 commond_line
字符串,通过from指针内的参数解析到command_line这个全局数组里,
然后将该数组的地址赋值给上上级传进来的参数 command_line
(start_kernerl)。这里只分析了当中的mem、initrd部分,另外一处是
start_kernel()->parse_option〇用于解析其余部分。将 mem 和 initrd
参数从原始 cormnand_line中扣出,留下的部分放在 *cmdline_p中
(start_kernel传进来的参数,第二次分析会用到)。因为接下来是建立
内存的映射页表,需要用到mem、initrd两个参数的内容,所以在这里
提前解析出来。
h. paging_init(&meminfo,mdesc);/*这部分的主要工作建立页表,初始
化内存 */ in arch/arm/imn/init. c 文件中
memtable_init (mi)为系统内存创建页表,是一个比较重要的函数,
详细的参考网址
http://bbs. sjtu. edu. cn/bbstcon, board, Embedded, reid, 1165977462.
html
的内容,需要注意的是这个函数中将中断向量表映射到了 OxffffOOOO
开始处
调用 mdesc->map_io() 函数,在 smdk2410 中位于
arch/arm/mach-s3c2410/mach-sindk2410. c文件中的 smdk2410_map_io()
函数,关于这部分的外设的静态映射分析请_考易松华老师
Linux静态映射分析,smdk2410_map_io()函数主要做了下M冗祥攀十青:
1 )、iotable_init(s3c_iodesc,ARRAY_SIZE(s3c_iodesc))建立
GPIO, IRQ, MEMCTRL, UART 的#态映射表
2)
、(cpu->map_io) (mach_desc, size)建立其他外设的静态映射表,
包括网卡,LCD,看门狗等
这里需要明确一点的是在map. h文件中的
S3C2410_ADDR(x) ((void _iomem *)0xF0000000 + (x))
表明我们处理的10和外设的寄存器被静态映射到了 OxFOOOOOOO开
始地方,其中每一种外设的空间占1M大小。
这里必须得提到的是内存的三种映射和使用方式:1.处理器的GPI0,各
种外设(IRQ, UART, MEMCRTL, WATCHDOG, USB等)的寄存器的静态映射,
也就是上面红色字体体现出来的。这是映射到内核空间内,所以用户程
序是不能访问的,只能由内核来访问。2.内核空间访问的虚拟地址(比
如是代码的地址或者是kmalloc空间的地址)都是在0x30008000开始后
的空间,也就是说这中虚拟地址是通过静态映射得到的,这部分也只能
由内核访问。phys_to_virt和virt_to_phys,3.除此之外的物理地址
都是通过mmu的规射出来的 ,TTB已"^确定在0x30004000开始的16K
空间就是一级页表的空间。通过这中方式映射的虚拟地址用户程序和内
核程序都可以访问,不过解析式通过 ramu来完成的而已。参考
arch/arm/kernel/head. S中建立的4M空间手工页表就是按照mmu的规则
建立的(只不过是按照sector的方式建立)
3) 、对时钟,Uart的简单初始化。
h.request_standard_resources(&meminfo, mdesc) 为 memory ,
kernel_text, kernel_data,video_ram在资源树种申请标准资源。它完成
实际的备源分配工作,如果参数new所描述的资源中的一部分或全部已经被
其它节点所占用,则函数返回与new相冲突的resource结构的指针。否则
就返回NULL
i. cpu_init()函数分析 in arch/arm/kernel/setup. c
打印一些关于cpu的信息,比如cpu id, cache大小等。另外重要的是
设置了 IRQ、ABT、UND三种模式的stack空间,分别都是12个字节。最后
将系统切换到svc模式。
i.用_mach_desc_SMDK2410_type结构体中特定成员来初始化系统的这么
写全局变量 init_arch_irq, init_machine, system_timer
3. sched_init()函数
初始&每个处理器的可运行队列,设置系统初始化进程即0号进程。也就是
调用了 init_idie (current, smp_processor_id〇)初始化 idle 当前进程。
4. pre empt_d i sab 1 e ()禁止抢占
5. 建立系统内存页区(zone)链表build_all_zonelists〇
6. printk(KERN_N0TICE 〃Kernel command line: %s\n〃,saved_command_line);
打印出从uboot传递过来的command_line字符串,在setup_arch函数中获
得的。
7. parse_early_param〇,这里分析的是系统能够辨别的一些早期参数(这个函数甚至可以去掉,_ s e t u p的形式的参数),而且在分析的时候并不是以
setup_arch (&command_line)传出来的 command_line 为基础,而是以最原
生态的saved_coramand_line为基础的。
8. parse_args (’’Booting kernel' command_line, —start________par am,
—stop___param - —start______par am,
&unknown_bootoption);
对于比较新的版本真正起作用的函数,与parse_early_param〇;相比,此处对解析
列表的处理范围加大了,解析列表中除了包括系统以setup定义的启动参数,还包括模
块中定义的param参数以及系统不能辨别的参数。
c〇mmand_line是setup_arch函数传递出来的值;
_start___ param是param参数的起始地址,在System, map文件中能看到
_stop___param - _start___param是参数个数
unknown_bootoption是对应与启动参数不是param的相应处理函数(查看
parse_one ()就知道怎么回事)
函数parse_one()既可以处理param参数,又可以处理_setup的形式的参
数,还可以处理不能识别的参数。当然这些都是依靠传递进来的参数进行分支处
理的。
参数的处理详情参考网络上另一篇文章:Linux启动bootargs参数分析
9. sort_main_extable ()
将放在—start___ ex_table 到—stop____ ex_table 之间的*(_ex_table)区
域中的struct exception_table_entry型全局结构变量按insn成员变量值
从小到大排序,即将可能_致缺&异常的指令按其指令二进制代码值从小到
大排序。
10.在前面的 setup_arch-■>paging_init-■> memtable_init 函数中为系统创建
页表的时候,中断向量表的虚地址 init_ maps,是用
alloc_ bootmem_ low_ pages分配的,ARM规定中断向量表的地址只能是0或
OxFFFFOOOO,#以该函数里有部分代码的作用就是映射一页到0或
OxFFFFOOOO。
trap_init函数做了一下的工作:把放在.Lcvectors处的系统8个意外入口
跳转指令搬到高端中断向量 OxffffOOOO处,再将_ stubs_start到
_S tubs_e nd之间的各种意外初始化代码搬到0xffff0200处。将系统调用的
返回句^拷贝到〇x ffff〇5〇〇处。刷新OxffffOOOO处1页范围的指令cache,
将 D0MAINJJSER 的访问权限由 DOMAIN_MANAGER 改成 D0MAIN_CLIENT 权限。
11. rcu_init 〇函数初始化当前cpu的读、复制、更新数据结构(struct rcu_data)
全局变量 per cpu rcu data 和 per cpu rcu bh data.
12. init_IRQ 函数
初始化系统中支持的最大可能中断数的中断描述结构struct irqdesc变量数
组ir(L<lesc[NR_IRQS],把每个结构变量irq_desc[n]都初始化成预先定义好
的坏中断描述结"构变量bacUrcLdesc,并初始化该中断的链表表头成员结构
变量pend。
执行init_arch_irq函数,该函数是在setup_arch函数最后初始化的一个全
局函数指针,指向了 smdk2410_init_irq 函数(inmach-smdk2410. c),实际
上是调用了 s3C 24xx_init_irq_函数在该函数中,首先清除所有的中断未决
标志,之后就初始化中断的触发方式和屏蔽位,还有中断句柄初始化,这里
不是最终用户的中断函数,而是do_level_IRQ或者do_edge_IRQ函数,在这
两个函数中都使用过_do_irq函数来找到真正最终驱动程序注册在系统中
的中断处理函数。
status = 0;
do {
if (ret == IRQ
一
HANDLED)
status |= action->flags;
retval |= ret:
action = action->next;
} while (action);
接着初始化外部中断的一些参数,最后补充初始化uart和ADC中断。
13. pidhash_init ()函数
设置系泰中每种 pidjiash表中的 hash链表数的移位值全局变量
pidhash_shift, 将 pidhash_shift 设置成min (12)。分别为每种 hash 表的
连续hash链表表头结构^请内存,把申请到的内存虚拟基址分别传给
pid_hash[n] (n=l~3),并将每种hash表中的每个hash链表表头结构struct
hlist_head中的first成员指针设置成NULL
14. init_timers ()函数
初始Ifc当前处理器的时间向量基本结构struct tveC_t_baSe_S全局变量
per_cpu_tvec_bases,初始化 per_cpu_tvec_bases 的自选锁成员变量 lock
5: void _init jllit tilTISrS (void)
1: {
) : //这个函数就是 ti rrers_nb这个结构体的 cal I函 数
>: timer_cpu_notify(& timers—nb, (unsigned long)CPU—UP—PREPARE,
1: (void * ) (long) smp_processor_id ());
l
h / /这个是用的机制和 cpuf r eq的机制是一样的,通过 not i f i er_chai n_r egi st er (&cpu_chai n, nb)注册以
): / /只不过这里的链是 cpu_chai n,而 cpufreq是其他的链
.: register_cpu_noti fier(&
timors_nb );
>
?
ji //设置软中断行动函数描述结构变量softirq_ueC[z1](系统定时器)的设置
1: //也就是设置timer■定时器到之后的处理函数
): open—softirq (TIMER—SOFTIRQ, run_timer_sof tirq , NULL);
15. softirqjnit 函数
内核的软中断机制初始化函数,void _____ init softirq_init (void)
{ 一
/*HI_SCFTI RQffl于实现 bottom hal f, TASKLET_SCFTI RQffl 于公共的 t ask 丨 et V
open_so f t irq ( TASKLET_SOFTIR〇 , tasklet_act ion , NULL );
open_sof tirq (HI_SOFTIRQJ tasklet_hi_act ion , NULL);
}
16. time_init()这个函数是用来做体系相关的timer的初始化
void ___init time init(v
〇
id)
if (
timer - >offset == NULL)
system_ timer - >offset = dummy—get t imeo f fset;
system_ timer - > i n i t ();
还记得在setup_arch最后初始化了三个全局的变量吗?下图所示
/?
x Set up uarious architecture-specific pointers
/ /运 行 rrach-smdk2410- c文件中定义的结构体 __nBch_desc_SNEK2410中的特定函数和结构 |
init_arch_irq - mdesc->init_irq;/x smdk2410_init_irq
system一 timer - mdesc- > t imer ; /? 3c24xx_tim9r ?/
inj t_machine - mdesc-> ini t_machine ;/? smdk2410_init_fs2410 x/
static void ____ init
s3c2410 timer init
(void)
{
s3c2410_timer_setup();
setup—irq ^ s3c241 D_ timer_irq );
struct sys_timer
.init =
.offset =
.resume =
s3 c2 4xx_t imer = {
s3 c2 410_t imer_ini t,
s3 c2 410_get t imeo f fset,
s3 c2 410—t imer—setup
很显然,t i m e_i n i t函数中的i f条件不成立,所以就是直接执行了函数
s3c2410_timer_init函数。可以看出,系统使用了 timer4来作为系统的定
时器。
17. console_init 函数
初始化系统的控制台结构,该函数执行后调用printk函数将log_buf中所有
符合打印级别的系统信息打印到控制台上
a. tty_register_ldisc ()注册默认的TTY线路规程
大象研究tty的驱动就会发现了在用户和硬件之间tty的驱动是分了三
层最底层当然是tty驱动程序了,主要负责从硬件接受数据,和格式化
上层发下来的数据后给硬件。在驱动程序之上就是线路规程了,他负责
把从tty核心层或者tty驱动层接受的数据进行特殊的按着某个协议的格式化,就像是Pf5p或者蓝牙协议,然后在分发出去的。在tty线路规
程之上就是tty核心层了。
b.接下来就是执行平台相关的控制台初始化代码
s3c24xx_serial_initconsole
/*
在vmlinux. Ids. S中连接脚本汇编中有这段代码
—con_initcall_start =.;
*(. con_initcall. init)
一con initcall—end =.;
因此我们再此处调用的就是C〇n_initcall. init段的代码,
将fn函数放到.con_initcall. init的输入段中,如下:
^define console_initcall(fn) \
static initcall_t —initcall_##fn \
—attribute_used_
—attribute—((—section—(〃. con_initcall. init")))=fn
j
//在我们串口驱动里面有
这么一个注册语句:console_initcall (s3c24xx_serial_initconsole);
//因此我们的控制台初始化流
程就是:start_kernel->console_init_>s3c24xx_serial_initconsole
call = 一con_initcall_start; /* console_initcall */
while (call < —con_initcall_end) {
(*call)();
call++;
18_ profile_init ()函数
/*对系&剖析做相关初始化,系统剖析用于系统调用*/
//profile是用来对系统剖析的,在系统调试的时候有用
//需要打开内核选项,并且在bootargs中有profile这一项才能开启这个功
能
/*
profile只是内核的一个调试性能的工具,这个可以通过menuconfig
中 profiling support 打开。
1. 如何使用profile:
首先确认内核支持profile,然后在内核启动时加入以下参数:
profile=l或者其它参数,新的内核支持profile=schedule 1
2.
内核启动后会创建/ proc/ profile文件,这个文件可以通过
readprofile 读取,
$口 readprofile -m / proc/kallsyms | sort -nr >
Vcur_ profile. log,
或者 readprofile _r _m / proc/kallsyms | sort - nr,
或者 readprofile _r && sleep 1 && readprofile _ m/ proc/kallsyms
猜你喜欢
- 2024-11-10 VMware中ESXI常用命令(vmware esxi使用教程)
- 2024-11-10 arm嵌入式考试题,大神精心总结(arm嵌入式知识点)
- 2024-11-10 ARM汇编教程(3): ARM指令集(arm汇编指令的基本格式)
- 2024-11-10 Cortex-A的通用寄存器和程序状态寄存器
- 2024-11-10 基于istio的mirror构建真实流量测试环境
- 2024-11-10 领先业界,中兴通讯首发两款双路4K超高清视讯终端
- 2024-11-10 arm 汇编指令 CPS(arm汇编器)
- 2024-11-10 Linux-2.6.37版:Linux内核启动全过程详解
- 2024-11-10 协处理器指令_开启ICache代码示例
- 2024-11-10 ADC触摸屏编程_定时器程序优化(触摸屏如何修改plc里时间定时值)
- 最近发表
-
- 使用Knative部署基于Spring Native的微服务
- 阿里p7大佬首次分享Spring Cloud学习笔记,带你从0搭建微服务
- ElasticSearch进阶篇之搞定在SpringBoot项目中的实战应用
- SpringCloud微服务架构实战:类目管理微服务开发
- SpringBoot+SpringCloud题目整理
- 《github精选系列》——SpringBoot 全家桶
- Springboot2.0学习2 超详细创建restful服务步骤
- SpringCloud系列:多模块聚合工程基本环境搭建「1」
- Spring Cloud Consul快速入门Demo
- Spring Cloud Contract快速入门Demo
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)