嵌入式系统启动流程分析
- 目的:通过分析启动流程,了解各类嵌入式系统的启动方法、启动步骤、代谢文件、达到什么目的、逐步掌握分析内核源码的能力
Linux 启动流程
- Bootloader (Uboot):启动引导代码
-
Bootloader 是 与内核不能同时存在的,在内核 启动完成后Bootloader 会关闭
-
Bootloader 会初始化内核所运行需要的硬件环境, 比如初始化CPU、 串口,其实是对硬件的初始化
-
为什么刚开始不是用C语言?
-
以为C语言需要用到栈 、堆,刚开始没有栈、堆
-
-
CPU初始化
-
内存初始化
-
硬盘初始化
-
代码搬移
-
各类硬件的初始化
-
进行参数设定 ---->struct tag_ist struct tag . 形成的 tsg_list 会存放在固定的内存里
-
-
Bootloader 怎么给 内核传递参数?
-
Bootloader设置参数的格式是与内核解析 的参数的格式是一致的
-
Bootloader 会把初始化的参数 放在一个固定的 内存里, 内核去 固定的内存里找就可以
-
可以修改传递的 一些命令,来达到内核启动后要执行的一些功能
struct tag{
struct tag_header hdr;
union{
struct tag_core core;
struct tag_mem_range mem_range;
struct tag_cmdline cmdline; //命令
struct tag_clock clock;
struct tag_ethernet ethernet
}u;
};
-
启动内核 、
-
0.11内核代码里 的boot 文件下有下面三个文件
-
-
其中 bootsect.s 放在磁盘的第一个扇区里,类型nor flash前4k 内存一样
-
setup .s 是 内核解析当前BootLoader 传递进来的参数,并且赋给某些全局变量
-
head.s 是执行当前 内核的头,从内核的头开始执行,从 main函数 开始运行
-
完成内存划分
-
完成各模块的启动功能(main.c)
#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)
//系统时间
static void time_init(void)
{
struct tm time;
//从rtc芯片中读取开机的时间
do {
time.tm_sec = CMOS_READ(0);
time.tm_min = CMOS_READ(2);
time.tm_hour = CMOS_READ(4);
time.tm_mday = CMOS_READ(7);
time.tm_mon = CMOS_READ(8);
time.tm_year = CMOS_READ(9);
} while (time.tm_sec != CMOS_READ(0));
//时间进制的转化
BCD_TO_BIN(time.tm_sec);
BCD_TO_BIN(time.tm_min);
BCD_TO_BIN(time.tm_hour);
BCD_TO_BIN(time.tm_mday);
BCD_TO_BIN(time.tm_mon);
BCD_TO_BIN(time.tm_year);
time.tm_mon--;
//计算开机时间
startup_time = kernel_mktime(&time);
}
static long memory_end = 0;
static long buffer_memory_end = 0;
static long main_memory_start = 0;
struct drive_info { char dummy[32]; } drive_info;
void main(void) /* This really IS void, no error here. */
{ /* The startup routine assumes (well, ...) this */
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
//设置操作系统的根文件
ROOT_DEV = ORIG_ROOT_DEV;
// 设置操作系统驱动参数
drive_info = DRIVE_INFO;
// 解析setup.s代码后获取系统内存参数
//做内存镜像的映射
memory_end = (1<<20) + (EXT_MEM_K<<10);
// 4K取整
memory_end &= 0xfffff000;
// 当前Linux内核最大支持内存为16M
if (memory_end > 16*1024*1024)
memory_end = 16*1024*1024;
//确定高速缓冲区的内存大小
if (memory_end > 12*1024*1024)
buffer_memory_end = 4*1024*1024;
else if (memory_end > 6*1024*1024)
buffer_memory_end = 2*1024*1024;
else
buffer_memory_end = 1*1024*1024;
main_memory_start = buffer_memory_end;
#ifdef RAMDISK
main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endif
// 进行用户内存的初始化
mem_init(main_memory_start,memory_end);
// 系统中断初始化(已讲)
trap_init();
//进行块设备驱动的初始化,加载块设备驱动
blk_dev_init();
// 进行字符型设备驱动的初始化,加载字符型设备驱动
chr_dev_init();
//进行控制台设备的初始化,加载显示和传输设备的驱动
tty_init();
//初始化系统时钟
time_init();
//进行进程调度初始化(已讲)
sched_init();
//进行缓冲区初始化(已讲)
buffer_init(buffer_memory_end);
//进行硬盘设备的初始化,加载硬盘驱动
hd_init();
//进行软盘设备的初始化,加载软盘驱动
floppy_init();
// 开启中断
sti();
// 从内核的初始化状态切换到用户模式
move_to_user_mode();
//创建0号进程 运行最初的应用程序
if (!fork()) { /* we count on this going ok */
init();
}
/*
* NOTE!! For any other task 'pause()' would mean we have to get a
* signal to awaken, but task0 is the sole exception (see 'schedule()')
* as task 0 gets activated at every idle moment (when no other tasks
* can run). For task0 'pause()' just means we go check if some other
* task can run, and if not we return here.
*/
for(;;) pause();
}
static int printf(const char *fmt, ...)
{
va_list args;
int i;
va_start(args, fmt);
write(1,printbuf,i=vsprintf(printbuf, fmt, args));
va_end(args);
return i;
}
static char * argv_rc[] = { "/bin/sh", NULL };
static char * envp_rc[] = { "HOME=/", NULL };
static char * argv[] = { "-/bin/sh",NULL };
static char * envp[] = { "HOME=/usr/root", NULL };
void init(void)
{
int pid,i;
//运行setup.s程序,进行配置
//获取由setup程序进行解析的参数
setup((void *) &drive_info);
//open标准输入的文件句柄
(void) open("/dev/tty0",O_RDWR,0); //打开标准输入控制台
(void) dup(0); //打开标准输出控制台
(void) dup(0); //打开标准错误控制台
//make
mkdir("/proc",755);
mknod("/proc/sysinfo", S_IFPROC,8 );
printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,
NR_BUFFERS*BLOCK_SIZE);
printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);
//创建1号进程
if (!(pid=fork())) {
close(0);
if (open("/etc/rc",O_RDONLY,0))
_exit(1);
execve("/bin/sh",argv_rc,envp_rc); //如果成功启动则阻塞
_exit(2);
}
if (pid>0)
while (pid != wait(&i)) //做循环等待
//如果启动不正常,出错状态
/* nothing */;
while (1) {
if ((pid=fork())<0) {
printf("Fork failed in init\r\n");
continue;
}
if (!pid) {
close(0);close(1);close(2);
setsid();
(void) open("/dev/tty0",O_RDWR,0);
(void) dup(0);
(void) dup(0);
_exit(execve("/bin/sh",argv,envp));
}
while (1)
if (pid == wait(&i))
break;
printf("\n\rchild %d died with code %04x\n\r",pid,i);
sync();
}
_exit(0); /* NOTE! _exit, not exit() */
}
- 切换运行态
- fork 多进程进行第一个进程的创建,名字为 init ,PID=1
- 获得标准输出 标准错误 标准输入
Openwrt 的启动流程
- 1.openwrt 启动的第一个进程为 /etc/preinit (/package/base-files/files/etc/preinit)
- 我们打开此文件看,它是个shell脚本
- 里面的 $PREINIT 刚开始没有被定义
- 2.启动 /sbin/init,进行环境变量设置 、文件系统的挂接、进行所有内核模块的加载,创建两个进程
- /sbin/init 会执行这个进程,它做了初始化的工作,如挂载
- 挂接 /etc/fstab
-
<file system> <mount point> <type> <options> <dump> <pass>
- 3./sbin/procd(/build_dir/target-i386_pentium4_mus1-1.1.16/procd-2017-08-08-66be6a23) PREINIT变量进行设置
- 4./sbin/kmodloader 加载内核模块
- 5./etc/inittab 初始化文件系统
- /etc/init.d/ 系统的开机启动脚本,各类软件的开机启动脚本都会放在这