Linux系统是怎样初始化的

前言:建议阅读上一篇文章《计算机体系结构变迁》了解体系结构,实模式与保护模式的区别和由来。需要向下衍生的一个知识点是体系结构中的存储器包括内存和外存。内存除了经常讲的RAM之外,还有ROM(只读存储器)。

一、从BIOS讲起

通电伊始,系统处在实模式之下只有1M的寻址空间,其中最上面的64K会被用作去映射到ROM中去,而ROM中存放的是我们经常挂在嘴上的BIOS,接下来CPU就会依次执行BIOS中设置的指令,主要做一下几件事:

  • 检查系统的硬件
  • 建立中断向量表以及对应的中断服务程序
  • 加载在启动扇区(MBR)的boot.img镜像文件,开始操作系统安装工作(其中boot.img是boot.S编译生成,并由grub2程序放在启动扇区的)
  • boot.img尝试加载core.img相关的文件
  • real_to_prot 尝试切换到保护模式

通常来说,boot.img文件主要做的事情就是加载grub2的另一个镜像core.img。core.img由诸多模块组成,包括diskboot.img,lzma_decompress.img,kernel.img等等。所以说若一切顺利的话boot.img所谓的加载core.img就是首先加载diskboot.img,然后diskboot.img会将剩余部分全部加载进来。在进行这些工作的时候1M的地址空间还是够使用的,然而接下来,我们就要使用lzma_decompress.img进行对相关文件进行解压缩,考虑到也许会发生地址空间不够用的情况,所以lzma_decompress.img还会尝试进行real_to_prot操作,即切换到保护模式去,这样就可以使用更多的地址空间了。
从实模式切换到保护模式发生了什么

  • 启用分段与分页机制。(这一块剖析过内存管理模块应该特别熟悉了)
  • 打开Gate A20,即第21根地址线的控制线
  • 接下来执行kernel.img对应的代码startup.S及其他一堆c文件。
  • 经过startup.S的一系列骚操作就会慢慢的设置好我们的系统运行环境,真正的启动内核。
二、内核初始化

需要掌握0,1,2号进程基本意义及设计思想。

内核初始化工作起始于init/main.c中的start_kernel函数:

//"__init"仅告诉kernel,此函数仅在初始化阶段使用,使用后所占用的内存资源会释放 
asmlinkage __visible void __init start_kernel(void)
{//linux-4.13.16\init\main.c
    ...
    set_task_stack_end_magic(&init_task);//该函数去创建0号进程
    ...
	trap_init();//中断
	mm_init();  //内存管理
    sched_init();//调度
    time_init();
    kmem_cache_init_late();
    vfs_caches_init();//初始化基于内存的文件系统rootfs
    //other_init...
    rest_init();//其他初始化,创建1和2号进程
}
static noinline void __ref rest_init(void)
{//linux-4.13.16\init\main.c
	struct task_struct *tsk;
	int pid;
	......
	pid = kernel_thread(kernel_init, NULL, CLONE_FS);//1号init进程,用户态进程祖先
	......
	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);//2号进程,内核态祖先
......

可以看到start_kernel首先会使用set_task_stack_end_magic创建0号进程,这也是唯一一个内核不使用fork/kernel_thread函数创建的进程。然后是各种各样的初始化,作用注释中已给出。

(1)一号进程的意义

用户态进程的实现基石是在保护模式之上的,须知x86对资源提供了分层管理权限,分为4个ring,其中越往里权限越高,内核是ring 0,用户态进程是ring 4。保护模式的含义有2,第一是拥有了更大的寻址空间,第二是实现了对资源的保护,当用户态进程企图去访问核心资源时就会被禁止。

(2)从内核态返回用户态

由上面的讲述可知在执行rest_init函数之时仍旧处在内核态,那如何在执行kernel_thread函数创建用户态祖先进程之后进入用户态模式哩?我们所熟知的函数调用逻辑是“用户态函数——系统调用——保护进程上下文——内核态执行系统调用——恢复进程上下文——系统调用返回——用户态函数”,现在我们想要从内核态返回用户态其实只要做后半段工作时机就可以了。具体实现的时候其实都知道当创建了一个进程之后必须要做的事情就是调用do_execve系统调用加载elf文件,在这个过程中会调用load_elf_binary在这之后会进行上下文设置,进而陷入用户态。

(3)ramdisk(基于内存的文件系统)

为什么会有ramdisk?
由第(2)问知道1号进程想要返回用户态就要调用do_execve系统调用加载elf文件,这个elf文件可以是任意的文件系统上的某个特定文件,如“/sbin/init” ,“/etc/init ” ,"/bin/init" ,"/bin/sh"但正是由于存在在文件系统上,Linux若想要在访问就需要将所有的驱动程序也要加载到内核里面,这在存储系统越来越多的情况下若都放在内核里面,内核就太大了。故提出了这样一种折中的方法,先调用ramdisk/init进入用户态,然后此/init程序会加载对应存储文件系统的的驱动这样就可以设置真正的根文件系统了,此后就会调用对应根文件系统的/init程序。
在这之后,就是各种系统的初始化工作开始展开。如启动系统服务,启动控制台等。

(4)fork与kernel_thread的区别
//linux-4.13.16\kernel\fork.c
SYSCALL_DEFINE0(fork)
{
	return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
}
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
	return _do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
		(unsigned long)arg, NULL, NULL, 0);
}

emmmm,显然调用的都是相同的底层函数,本质上没有区别。只是kernel_thread可以仔细的设置创建进程的属性罢了,在Linux中类似的设计还有signal与signaction。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值