作者:GWD 时间:2019.7.31
一、初始化代码
1、起点:磁盘引导程序,需要将内核等移入内存进行运行,并初始化多种模块和硬件
2、终点:运行第一个应用程序——系统的根文件系统
3、程序一开始运行就不断的在创建新的进程,但是如果有别的驱动之类的发生了中断就会去执行别的,执行完中断再回来执行这个初始化,子进程执行shell,父进程等待回收。
二、void init(void)分析
1、
void init(void)
{
int pid,i;
// 读取硬盘参数包括分区表信息并建立虚拟盘和安装根文件系统设备。
// 该函数是在25 行上的宏定义的,对应函数是sys_setup(),在kernel/blk_drv/hd.c。
setup((void *) &drive_info);
(void) open("/dev/tty0",O_RDWR,0); // 用读写访问方式打开设备“/dev/tty0”,
// 这里对应终端控制台。
// 返回的句柄号0 -- stdin 标准输入设备,也就是写。
(void) dup(0); // 复制句柄,产生句柄1 号-- stdout 标准输出设备。
(void) dup(0); // 复制句柄,产生句柄2 号-- stderr 标准出错输出设备。
// 下面fork()用于创建一个子进程(子任务)。对于被创建的子进程,fork()将返回0 值,
// 对于原(父进程)将返回子进程的进程号。所以if (!(pid=fork())) {...} 内是子进程执行的内容。
// 该子进程关闭了句柄0(stdin),以只读方式打开/etc/rc 文件,并执行/bin/sh 程序,所带参数和
// 环境变量分别由argv_rc 和envp_rc 数组给出。参见后面的描述
//创建了1号进程 如果在0号父进程创建进程成功则 fork函数返回0 ,如果在子进程中fork则返回父进程PID
// 如果fork返回值为0,则其在新进程中执行,如果返回值不为0 返回值为子进程的进程号则在父进程中进行,在前面章节提到过,调用一次fork有两个返回值。
if (!(pid=fork())) {
//在1号进程中进行执行
close(0); //关闭了0号进程创建的标准输入输出
if (open("/etc/rc",O_RDONLY,0)) //打开了系统配置文件,挂接文件系统,开启shell
//脚本,为什么要读取这个文件呢?因为这里有很多//用户的配置参数,配置参数里面比如要显示图片,挂//接某些程序都会被读出来,放在环境变量里面
_exit(1);
// 如果打开文件失败,则退出(/lib/_exit.c);
execve("/bin/sh",argv_rc,envp_rc); // 装入/bin/sh 程序并执行。(/lib/execve.c)
_exit(2); // 若execve()执行失败则退出(出错码2,“文件或目录不存在”)。
//正常情况是不会执行到这里的
}
// 下面是父进程执行的语句。wait()是等待子进程停止或终止,其返回值应是子进程的
// 进程号(pid)。这三句的作用是父进程等待子进程的结束。&i 是存放返回状态信息的
// 位置。如果wait()返回值不等于子进程号,则继续等待。
if (pid>0)//如果执行失败则等待子进程关闭
while (pid != wait(&i))
{ /* nothing */;}
// --
// 如果执行到这里,说明刚创建的子进程的执行已停止或终止了。下面循环中首先再创建
// 一个子进程,如果出错,则显示“初始化程序创建子进程失败”的信息并继续执行。对
// 于所创建的子进程关闭所有以前还遗留的句柄(stdin, stdout, stderr),新创建一个
// 会话并设置进程组号,然后重新打开/dev/tty0 作为stdin,并复制成stdout 和stderr。
// 再次执行系统解释程序/bin/sh。但这次执行所选用的参数和环境数组另选了一套(见上面)。
// 然后父进程再次运行wait()等待。如果子进程又停止了执行,则在标准输出上显示出错信息
// “子进程pid 停止了运行,返回码是i”,
// 然后继续重试下去…,形成“大”死循环。
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);
//执行shell脚本
_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() */
}
三、操作系统移植的过程分为2步:
- 进程操作系统初始化的适配,能够让main在你的板卡上跑起来;
- 进程驱动的移植;
四、接下来看一看3.4.2内核的引导程序主线
1、首先我们查找start_kernel这个函数被哪个函数调用了:
在mmap中被调用了,然后,mmap被head.s调用
2、BOOTLOADER的启动内核代码
创建
void (theKernel)(int zero, int arch, uint params);
把指针移到ih_ep上去,Linux的启动入口
theKernel = (void ()(int, int, uint))ntohl(hdr->ih_ep);
执行Linux并传入参数
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
bd->bi_arch_number 称为process id CPU的架构号
bd->bi_boot_params 成为参数地址
3、在bootloader启动内核是如何进行的?thekernel函数指针
4、比对当前板子的CPU是否支持Linux,如果不支持怎不启动直接退出,如果支持则继续进行
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
THUMB( it eq ) @ force fixup-able long branch encoding
beq __error_p @ yes, error ‘p’
5、设置物理内存的页面
adr r3, 2f
ldmia r3, {r4, r8}
sub r4, r3, r4 @ (PHYS_OFFSET - PAGE_OFFSET)
add r8, r8, r4 @ PHYS_OFFSET
6、验证参数是否完整(tag_list)参数
bl __vet_atags
7、进程虚拟内存映射表的创建
bl __create_page_tables
8、把函数mmap_switched的地址装载进链接寄存器(调用某个函数后执行完后再返回这个函数的地址),mmap_switch函数是拷贝内核的关键因素,虚拟内存到物理内存的交叉。
ldr r13, =__mmap_switched @ address to jump to after
9、进程初始化C函数的调用,start_kernel中有各种初始化
asmlinkage void __init start_kernel(void)
在rest_init()中;创建了kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND)进程这句话相当于0.11中的fork init;找一下这个进程的开始和结束;
//打开标准输入控制台、拷贝标准输出控制台和标准错误控制台
/* Open the /dev/console on the rootfs, this should never fail */
if (sys_open((const char __user *) “/dev/console”, O_RDWR, 0) < 0)
printk(KERN_WARNING “Warning: unable to open an initial console.\n”);
(void) sys_dup(0);
(void) sys_dup(0);
最后又执行了:
init_post();
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");