linux内核启动流程及busybox分析
来源:互联网 发布:店面招牌设计软件 编辑:程序博客网 时间:2024/05/21 07:05
内核版本:linux-3.4
启动流程:
1、比较机器ID
2、解析u-boot传入的启动参数
3、挂接根文件系统、执行第一个应用程序
1、比较机器ID
打开 vmlinux.lds.S 里面可以找到内核把初始化参数的段定义为了”.arch.info.init”
.init.arch.info : { __arch_info_begin = .; *(.arch.info.init) __arch_info_end = .;}
在内核源码的目录下可以输入指令查找一下谁调用了这个段:grep “.arch.info.init” * -nR
搜索到如下内容:
#define MACHINE_START(_type,_name) \ static const struct machine_desc __mach_desc_##_type \ /* ##是连词符号 */ __used \ __attribute__((__section__(".arch.info.init"))) = { \ .nr = MACH_TYPE_##_type, \ .name = _name, #define MACHINE_END \ };
定义了一个宏来指定,可以接着在 sourceinsight 工程里面去搜索 MACHINE_START 和 MACHINE_END 这两个宏,看下具体谁使用了它,搜索结果如下 :
MACHINE_START(SUNXI, "sun8i") .atag_offset = 0x100, .init_machine = sunxi_dev_init, .init_early = sunxi_init_early, .map_io = sunxi_map_io,#ifndef CONFIG_OF .init_irq = sun8i_gic_init,#endif .handle_irq = gic_handle_irq, .restart = sun8i_restart, .timer = &sunxi_timer, .dt_compat = NULL, .reserve = sun8i_reserve, .fixup = sun8i_fixup, .nr_irqs = NR_IRQS,#ifdef CONFIG_SMP .smp = smp_ops(sunxi_smp_ops),#ifdef CONFIG_ARCH_SUN8IW6 .smp_init = smp_init_ops(sun8i_smp_init_ops),#endif#endifMACHINE_END
MACHINE_START 和 MACHINE_END 这两个就是定义一个结构体,这个结构体被强制的设置为一个属性,把它的段设置为 “.arch.info.init”,那么如果哪个文件下面有这些结构体的话,这些结构体就会被 vmlinux.lds.S 这个链接脚本组合在一起,我们可以把这段定义带入到刚才搜索到的那段 MACHINE_START 和 MACHINE_END 的宏里面,代入后他们就被置换成了如下内容:
static const struct machine_desc __mach_desc_SUNXI \ __used \ __attribute__((__section__(".arch.info.init"))) = { \ .nr = MACH_TYPE_SUNXI, \ .name = "sun8i", .atag_offset = 0x100, .init_machine = sunxi_dev_init, .init_early = sunxi_init_early, .map_io = sunxi_map_io, #ifndef CONFIG_OF .init_irq = sun8i_gic_init, #endif .handle_irq = gic_handle_irq, .restart = sun8i_restart, .timer = &sunxi_timer, .dt_compat = NULL, .reserve = sun8i_reserve, .fixup = sun8i_fixup, .nr_irqs = NR_IRQS, #ifdef CONFIG_SMP .smp = smp_ops(sunxi_smp_ops), #ifdef CONFIG_ARCH_SUN8IW6 .smp_init = smp_init_ops(sun8i_smp_init_ops), #endif };
上面的属性就是配置了struct machine_desc 这个结构体,和结构体内容一一对应,从内核得出这些内容后我们就可以和 uboot 传入的参数进行对比,uboot 传数据给内核是从 uboot源码里面的 arch/arm/lib/bootm.c 这个文件的 do_boota_linux 函数,具体调用以下函数:
kernel_entry(0, bd->bi_arch_number, bd->bi_boot_params);
这个函数传入的参数刚好和上面的 MACHINE_START(machine_desc结构体) 刚好对应起来,然后内核启动的时候就会从初始化的 begin 开始读到 end :
.init.arch.info : { __arch_info_begin = .; *(.arch.info.init) __arch_info_end = .;}
把uboot传入的ID和内核的ID做比较,如果吻合的话表示支持这个单板,然后还会接着比较后面的参数
2、解析u-boot传入的启动参数
上一章通过分析 linux 源码的 Makefile 和脚本后发现内容启动的第一个程序是:arch/arm/kernel/head.S
这里截取关键的代码如下:
__lookup_processor_type /* 判断是否支持这个CPU处理器 */ __create_page_tables /* 创建页表:因为链接脚本的起始地址并不对应于真实存在的地址,因此需要创建页表来启动MMU */__enable_mmu /* 使能MMU */__mmap_switched /* 当MMU使能之后会跳到这个switch里面去,复制数据段、清BSS段、设置栈指针、保存processor_id变量、保存机器类型ID */ start_kernel /* 启动内核,这是第一个C函数,在里面处理启动参数 */ init.... /* 一系列的初始化 */ setup_arch(&command_line); /* 这两句解析uboot传递过来的参数,把参数从 machine_desc 结构体取出来保存 */ setup_command_line(command_line); ... parse_early_param /* 早期参数初始化 */ parse_early_options do_early_param /* 从 __setup_start 到 __setup_end 这是在 vmlinux.lds.S 里面定义的 INIT_SETUP 段 作用是调用那些用early来标识的函数,但是我们函数传递进来的early是0,所以这里用不着 */ unknown_bootoption obsolete_checksetup /* 处理从 __setup_start 到 __setup_end 这个段调用的非early的函数 */ ... rest_init(); kernel_init sys_open((const char __user *) "/dev/console", O_RDWR, 0) /* 打开终端,标准输出 */ (void) sys_dup(0); /* dup是复制的意思,这里代表标准输入 */ (void) sys_dup(0); /*标准错误 ,打开的这三个文件都指向console这个设备,这个设备会给busybox应用 */ prepare_namespace saved_root_name[0] /* 从uboot设置好的root名读取保存(root_dev_setup函数)到saved_root_name里面, */ /* 如何定义 saved_root_name 的 */ __setup("root=", root_dev_setup); /*这个宏定义了一个结构体,这个结构体里面有两个成员,其实就是一个字符串对应一个处理函数*/ #define __setup(str, fn) \ __setup_param(str, fn, fn, 0) /* 第四个参数是early,这里被设置为0,所以上面的 parse_early_param 可以忽略*/ #define __setup_param(str, unique_id, fn, early) \ static const char __setup_str_##unique_id[] __initconst \ __aligned(1) = str; \ static struct obs_kernel_param __setup_##unique_id \ __used __section(.init.setup) \ /* 这个结构体有个属性,这个属性被强制设备为(.init.setup)这个段, 这个段在链接脚本里面有定义(INIT_SETUP)里面可以找到__setup_start*/ __attribute__((aligned((sizeof(long))))) \ = { __setup_str_##unique_id, fn, early } /* 查看它被谁使用,就知道命令行是怎么调用的了,搜索 __setup_start 查看谁调用了,有如下两个函数调用了:*/ obsolete_checksetup /* 被 unknown_bootoption 调用 */ do_early_param /* 被 parse_early_param 调用 */ mount_root(); /* 挂接根文件系统 */ /* 开始执行应用程序 */ init_post /* 一旦执行了以下任意一个应用程序就是一个死循环,不会再执行后面的了 */ run_init_process(execute_command); /* 搜索关键字execute_command可以发现init_setup调用了它 */ __setup("init=", init_setup) /* init是一个从uboot命令行参数,uboot如果设置了init,则 execute_command 等于设置的值 */ run_init_process("/sbin/init"); //执行应用程序,如果不成功则依次往下执行 ...
3、挂接根文件系统、执行第一个应用程序
在上面代码中,先是调用了挂接根文件系统的函数,然后使用函数 run_init_process 执行应用程序,我们可以在 uboot 参数里面定义 init 变量来告诉内核第一个应用程序执行哪个文件,如果没有设置第一个是执行 /sbin/bin,而一般情况下我们第一个程序是 busybox,我们可以先启动系统然后使用 ps 指令查看下当前运行的进程,但是并不会发现有 ls 、cd、cp 等这些程序,但我们一启动就能使用这些命令,那这些程序哪里来的呢?就来源于 busybox 这个应用程序,我们可以使用指令来验证一下:
# ls -l /bin/lslrwxrwxrwx 1 root root 7 Oct 18 2017 /bin/ls -> busybox
可以看出 ls 软链接到了 busybox,所以执行 ls 就等于执行 busybox 里面的 ls,busybox 这个应用存放于 /bin 这个目录。
我的内容在 uboot 中是将 init 设置为了 /init,即 init = /init ,它其实也是链接到了 busybox:
# ls /init -llrwxrwxrwx 1 root root 11 Dec 14 2017 /init -> bin/busybox
而且如果我们没定义 init ,它就会根据我们上面分析的代码去执行 /sbin/init ,而 /sbin/init 也是链接到了 busybox:
# ls /sbin/init -llrwxrwxrwx 1 root root 14 Oct 18 2017 /sbin/init ->../bin/busybox
既然第一个执行的程序是 busybox,下面可以来简单分析一下,看下 init 程序做了些什么
busybox 里面的 init 程序主要执行了以下的一些内容:
1、读取配置文件 –> 这个配置文件指定了后续要读取哪些程序
2、解析配置文件
3、根据配置文件来启动执行“用户程序”
busybox 源码分析:
init_main parse_inittab file = fopen(INITTAB, "r"); /* 打开"/etc/inittab"这个配置文件 */ new_init_action /* 根据配置文件调用这个函数,创建一个init_action结构,填充结构体,把结构放入 init_action_list 链表 */ new_init_action(ASKFIRST, bb_default_login_shell, VC_2) 为例分析 new_init_action const char bb_default_login_shell[] ALIGN1 = LIBBB_DEFAULT_LOGIN_SHELL; #define LIBBB_DEFAULT_LOGIN_SHELL "-/bin/sh" # define VC_2 "/dev/tty2" 带入即等于: new_init_action(ASKFIRST,"-/bin/sh","/dev/tty2") for (a = last = init_action_list; a; a = a->next) /* 创建一个init_action结构,填充结构体,把结构放入链表 */ run_actions(SYSINIT); /* 运行 SYSINIT 这一类型的动作 */ waitfor(a, 0); /* 执行应用程序,等待它执行完毕 */ delete_init_action(a); /* 从 init_action_list 链表里删除 */ run_actions(WAIT); /* 运行 WAIT 这一类型的动作,过程同上 */ run_actions(ONCE); /* 运行 ONCE 这一类型的动作 */ while(1){ /* 这个循环用于命令行运行应用程序使用,结束一个线程就等待用户重新输入 */ run_actions(RESPAWN); if (a->pid == 0){ /* 一开始PID是等于0的 */ a-pid = run(a); } run_actions(ASKFIRST); if (a->pid == 0){ a-pid = run(a); /* 等待回车,创建子线程 */ 打印"\nPlease press Enter to activate this console. " } wpid = wait(NULL); /* 等待子进程退出 */ while (wpid > 0) { a->pid = 0; /* 退出后,就设置 pid=0,重新回到while循环继续执行上面已退出的子进程 */ } }
上面的 /etc/inittab 配置文件的书写格式如下:
<id>:<runlevels>:<action>:<process>
id : /dev/idrunlevels : 忽略action : 何时执行,有以下选项 # <action>: Valid actions include: sysinit, respawn, askfirst, wait, once, # restart, ctrlaltdel, and shutdown.process : 应用程序或脚本
从 busybox 可以看出最小根文件系统至少需要什么东西:
1. /dev/console 或 /dev/null(如果没有定义console,stdout/in/err 就定位到null去)2. /etc/inintab3. 配置文件里指定的应用程序4. C库5. init本身,即busybox
- linux内核启动流程及busybox分析
- Linux内核启动流程分析
- linux内核启动流程分析
- linux内核启动流程分析
- Linux内核启动流程分析
- linux启动流程分析-内核解压缩过程
- Linux内核启动流程分析(一)
- Linux内核启动流程分析(一)
- Linux内核启动流程分析(二)
- linux启动流程分析-内核解压缩过程
- Linux内核移植及启动流程概述
- 内核启动流程分析
- 内核启动流程分析
- 内核启动流程分析
- linux内核启动到busybox命令实现
- [转载] linux启动流程分析(1)---bootloader启动内核过程
- linux启动流程分析(1)---bootloader启动内核过程
- linux启动流程分析(2)---内核启动地址的确定
- java实现简单的学生信息管理系统
- 日常2.1--关于EasyUI的坑(datagrid_2)
- on条件与where条件的区别
- Maven构建SpringBoot项目
- 惭愧呀!工作这么多年了,才知道“性能优化”是这么一回事!
- linux内核启动流程及busybox分析
- Android进程间通信(IPC)机制Binder简要介绍和学习计划
- canvas简单的粒子效果的实现
- SpringBoot集成jsp
- 代数方程解的存在性和唯一性(if and only if)
- windows 下虚拟机安装mac os
- 响应式表格
- unittest框架实战(一)
- 单列模式