计算机与操作系统
计算机逻辑架构
CPU包括三个部分,运算单元、数据单元和控制单元。
运算单元计算的数据如果每次都要经过总线,到内存里面现拿,这样就太慢了,所以就有了数据单元。数据单元包括CPU内部的缓存和寄存器组,空间很小,但是速度飞快,可以暂时存放数据和运算结果。
控制单元是一个统一的指挥中心,它可以获得下一条指令,然后执行这条指令。
计算机位数指的是总线位数,包括地址总线、数据总线。
X86 CPU组件逻辑架构图:
其中IP寄存器为指令指针寄存器指向代码段中下一条指令位置。
果需要切换进程呢?每个进程都分代码段和数据段,为了指向不同进程的地址空间,有四个16位的段寄存器,分别是CS、DS、SS、ES。
CS就是代码段寄存器(Code Segment Register),通过它可以找到代码在内存中的位置;DS是数据段的寄存器,通过它可以找到数据在内存中的位置。SS是栈寄存器(Stack Register)。
32位的cpu CS、SS、DS、ES仍然是16位的,但是不再是段的起始地址。段的起始地址放在内存的某个地方。这个地方是一个表格,表格中的一项一项是段描述符(Segment Descriptor)。这里面才是真正的段的起始地址。而段寄存器里面保存的是在这个表格中的哪一项,称为选择子(Selector)。
将一个从段寄存器直接拿到的段起始地址,就变成了先间接地从段寄存器找到表格中的一项,再从表格中的一项中拿到段起始地址。
32位CPU-逻辑架构
BIOS
当我们没有操作系统时启动计算机,在主板上,有一个东西叫ROM(Read Only Memory,只读存储器)。这和咱们平常说的内存RAM(Random Access Memory,随机存取存储器)不同。咱们平时买的内存条是可读可写的,这样才能保存计算结果。而ROM是只读的,上面早就固化了一些初始化的程序,也就是BIOS(Basic Input and Output System,基本输入输出系统)。
bootloader
操作系统在哪儿呢?一般都会在安装在硬盘上,在BIOS的界面上。你会看到一个启动盘的选项。启动盘有什么特点呢?它一般在第一个扇区,占512字节,而且以0xAA55结束。这是一个约定,当满足这个条件的时候,就说明这是一个启动盘,在512字节以内会启动相关的代码。
这些代码是谁放在这里的呢?在Linux里面有一个工具,叫Grub2,全称Grand Unified Bootloader Version 2。顾名思义,就是搞系统启动的。
你可以通过grub2-mkconfig -o /boot/grub2/grub.cfg来配置系统启动的选项。
grub2第一个要安装的就是boot.img。它由boot.S编译而成,一共512字节,正式安装到启动盘的第一个扇区。这个扇区通常称为MBR(Master Boot Record,主引导记录/扇区)。
BIOS完成任务后,会将boot.img从硬盘加载到内存中的0x7c00来运行。
由于512个字节实在有限,boot.img做不了太多的事情。它能做的最重要的一个事情就是加载grub2的另一个镜像core.img。
引导扇区就是你找到的门卫,虽然他看着档案库的大门,但是知道的事情很少。他不知道你的宝典在哪里,但是,他知道应该问谁。门卫说,档案库入口处有个管理处,然后把你领到门口。
core.img就是管理处,它们知道的和能做的事情就多了一些。core.img由lzma_decompress.img、diskboot.img、kernel.img和一系列的模块组成,功能比较丰富,能做很多事情。
从实模式切换到保护模式
实模式寻址空间只有1m。切换到保护空间可以获取更大的寻址空间和总线位数有关。
切换到保护模式要干很多工作,大部分工作都与内存的访问方式有关。
第一项是启用分段,就是在内存里面建立段描述符表,将寄存器里面的段寄存器变成段选择子(这个是cpu逻辑架构转变导致的),指向某个段描述符,这样就能实现不同进程的切换了。
第二项是启动分页。能够管理的内存变大了,就需要将内存分成相等大小的块。
切换到保护模式后对kernel.img解压缩对应的代码是startup.S以及一堆c文件,在startup.S中会调用grub_main,这是grub kernel的主函数。
如果是正常启动,grub_main最后会调用grub_command_execute (“normal”, 0, 0),最终会调用grub_normal_execute()函数。在这个函数里面,grub_show_menu()会显示出让你选择的那个操作系统的列表。
最后会启动内核初始化。
内核初始化
调用函数start_kernel()开始。在init/main.c文件中,start_kernel相当于内核的main函数。
init_task创建0号进程。
trap_init() 处理各种中断。响应用户各种需求。
vfs_caches_init() 初始化rootfs。在VFS虚拟文件系统里面注册了一种类型,我们定义为struct file_system_type rootfs_fs_type。
文件系统是我们的项目资料库,为了兼容各种各样的文件系统,我们需要将文件的相关数据结构和操作抽象出来,形成一个抽象层对上提供统一的接口,这个抽象层就是VFS(Virtual File System),虚拟文件系统。
rest_init()
rest_init的第一大工作是,用kernel_thread(kernel_init, NULL, CLONE_FS)创建第二个进程,这个是1号进程。
此时需要做权限区分。
将能够访问关键资源的代码放在Ring0,我们称为内核态(Kernel Mode);将普通的程序代码放在Ring3,我们称为用户态(User Mode)。
用户态不能执行访问内核代码,例如:
当一个用户态的程序运行到一半,要访问一个核心资源,例如访问网卡发一个网络包,就需要暂停当前的运行,调用系统调用,接下来就轮到内核中的代码运行了。
首先,内核将从系统调用传过来的包,在网卡上排队,轮到的时候就发送。发送完了,系统调用就结束了,返回用户态,让暂停运行的程序接着运行。
其中内存是用来保存程序运行时候的中间结果的,现在要暂时停下来,这些中间结果不能丢,因为再次运行的时候,还要基于这些中间结果接着来。另外就是,当前运行到代码的哪一行了,当前的栈在哪里,这些都是在寄存器里面的。
用户态-系统调用-保存寄存器-内核态执行系统调用-恢复寄存器-返回用户态,然后接着运行。
1号进程是所有用户态进程的祖先
创建2号进程
rest_init第二大事情就是第三个进程,就是2号进程。2号进程统一管理内核态进程。
使用kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES)函数创建。
从内核态来看,无论是进程,还是线程,我们都可以统称为任务(Task),都使用相同的数据结构,平放在同一个链表中。