Linux系统初始化

x33g5p2x  于2022-04-11 转载在 Linux  
字(3.7k)|赞(0)|评价(0)|浏览(372)

学习Linux内核的原因

我个人是软件工程专业的学生,但是对计算机是怎么运行的一直都有疑惑。 为什么一通电计算机就能运行了,处理各种任务。 如果对计算机底层知识的不熟悉和不理解的话,这就造成了在了解各种中间件或者软件的底层原理的时候,会比较吃力,对于程序运行的具体情况也不太清楚。 我个人目前认为只有对程序在计算机中的具体执行过程有着深入的了解,才能写出性能更好、更健壮性的代码,所以开始学习Linux内核。

Linux内核学习方法

资料
《趣谈Linux内核 》 刘超 极客时间专栏
Linux源码

路线
学习Linux内核切记不要太过深入死扣代码,而是要先在宏观上了解Linux的运行原理,然后再深入了解具体实现原理

Linux初始化

相信很多人都有这样的疑惑:为什么通电后,按下启动按钮,操作系统就运行了?

BIOS初始化
当通电后,CPU就开始执行指令了,执行的是什么指令呢?主板上有一块ROM,ROM是只读存储器,里面存储了BIOS程序。CPU会执行BIOS程序来加载操作系统。
BIOS的全称是 basic input and output system,也就是基本输入输出系统。

上述图片是bios时期的CPU地址空间的映射布局。

此时CPU处于实模式,寄存器的位数是16位,然后地址线是20位,CPU的寻址空间是2^20,也就是1MB,即使是更先进的CPU,支持64位寄存器或者超过20根的地址线,同样只能使用寄存器中的16位和其中的20根地址线。

地址的转换规则为 DS寄存器左移4位 + IP寄存器。此时CPU处于ring0特权,可以执行所有指令,操作硬件。

其中0X F0000 到 0X FFFFF 这 64kb的地址空间映射到 ROM中。

当CPU通电后,会做一些重置操作,讲DS寄存器赋值为0X FFFF , IP寄存器为 0X 0000,所以访问地址就对应着 0X FFFF0,地址0X FFFF0对应的空间 处于ROM的空间中,也就是开始执行bios程序。

1、初始化中断向量表等数据,因为用户可以通过鼠标、键盘等操作方式来操作BIOS程序,所以
2、初始化硬件资源:显示器、内存、磁盘等资源
3、加载操作系统,也就是进入BootLoader阶段

BootLoader阶段
BootLoader阶段,BIOS程序会记载操作系统内核,怎么加载呢?

检测存储设备的第一个扇区是不是以0xAA55结尾的。 如果是以0xAA55结尾就说明这是一个操作系统的启动区。我们都知道一个扇区的大小一般是512字节,一个操作系统编译后的代码一般都是好几百MB,第一个扇区是无法装下操作系统所有的代码的,所以只会将操作系统的启动代码存储到第一个扇区内,这个扇区也叫做引导扇区,存储的内容叫做boot.img。

boot.img是grub2工具写入到磁盘中的。

bios程序会将boot.img 加载到 内存中的 0x 7C00,然后利用jump指令,跳转到boot.img,至此BIOS程序就算功德圆满了,接下来就看grub2的了。

grub2加载内核过程

grub2是Grand Unified Bootloader Version 2,是一个多系统启动程序,用来一个硬盘上存在多个系统的启动,多个系统的相关配置信息被写入到了一个配置文件grub.cfg中。grub.cfg中写入了操作系统的一些配置,包括系统的版本、系统镜像的位置等信息

menuentry 'CentOS Linux (3.10.0-862.el7.x86_64) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-3.10.0-862.el7.x86_64-advanced-b1aceb95-6b9e-464a-a589-bed66220ebee' {
	load_video
	set gfxpayload=keep
	insmod gzio
	insmod part_msdos
	insmod ext2
	set root='hd0,msdos1'
	if [ x$feature_platform_search_hint = xy ]; then
	  search --no-floppy --fs-uuid --set=root --hint='hd0,msdos1'  b1aceb95-6b9e-464a-a589-bed66220ebee
	else
	  search --no-floppy --fs-uuid --set=root b1aceb95-6b9e-464a-a589-bed66220ebee
	fi
	linux16 /boot/vmlinuz-3.10.0-862.el7.x86_64 root=UUID=b1aceb95-6b9e-464a-a589-bed66220ebee ro console=tty0 console=ttyS0,115200 crashkernel=auto net.ifnames=0 biosdevname=0 rhgb quiet 
	initrd16 /boot/initramfs-3.10.0-862.el7.x86_64.img
}

grub2的程序主要包括 boot.img core.img

boot.img会加载core.img,core.img的一个扇区中存的是disk.img。

disk.img用来加载core.img中的其他模块,比如lzma_decompress.img、kernel.img等。这里的kernel是指grub2的内核,而不是操作系统的。

disk.img 加载lzma_decompress.img,lzma_decompress.img会对kernel.img进行解压,实模式的支持的内容空间不到1MB,而kernel.img占的内存比较大,所以要从实模式切换到保护模式。

保护模式的做法:
1、打开Gate20地址线,CPU可以使用其他的地址线了,意味着更大的寻址空间
2、打开分段和分页,之前的映射方式是地址的翻译方式为 DS寄存器左移4位 + IP寄存器,这种访问方式是不安全的,一个进程可以访问另一个进程的数据。所以引入了逻辑地址的方法,通过页表将逻辑地址转化为物理地址,而页表由操作系统内核维护,可以做到进程之间的安全隔离。

切换到保护模式后,lzma_decompress.img会执行kernel.img开始真正的加载操作系统。

kernel.img会调用grub_load_config() 方法来解析grub.cfg, 里面有一个grub_show_menu()会让用户选择操作系统

选择完操作系统后,就会根据grub.cfg里面的配置信息来加载操作系统啦。

初始化Linux

1、初始化中断向量表

2、初始化内存管理模块

3、初始化进程调度模块

4、初始化VFS,虚拟文件系统

5、 初始化进程

  1. 创建0号进程
    0号进程是所有进程的祖先,通过
    struct task_struct init_task = INIT_TASK(init_task) 来创建
  2. 创建1号进程
    1号进程是第一个用户态进程,是所有用户态进程的祖先,通过
    kernel_thread(kernel_init, NULL, CLONE_FS) 创建

kernel_init的大概定义如下,是要执行init文件,来进行初始化工作

if (!try_to_run_init_process("/sbin/init") ||
	    !try_to_run_init_process("/etc/init") ||
	    !try_to_run_init_process("/bin/init") ||
	    !try_to_run_init_process("/bin/sh"))
		return 0;

init文件是存储在Linux的根文件系统中的,根文件系统是一个内存文件系统。因为市面上由太多的文件系统,无法将所有的文件系统的驱动程序都加入到内核中,所以就引入了一个根文件内存系统,然后在1号进程中系统调用开始初始化内核。

在执行1号进程之前,会将CPU从内核态切换到用户态。

void
start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp)
{
set_user_gs(regs, 0);//切换到用户态
//设置寄存器的值
regs->fs	= 0;
regs->ds	= __USER_DS;
regs->es	= __USER_DS;
regs->ss	= __USER_DS;
regs->cs	= __USER_CS;
regs->ip	= new_ip;
regs->sp	= new_sp;
regs->flags	= X86_EFLAGS_IF;
force_iret();
}
EXPORT_SYMBOL_GPL(start_thread);
  1. 创建2号进程
    2号进程是第一个内核进程,是所有内核进程的祖先。

相关文章