什么是操作系统?Linus Torvalds这样解释道
To kind to explain what Linux is, you have to explain what an Operating System is. And… the thing about Operating System is: you have never ever supposed to see it.nobody really use an Operating System, people use… program… on their computer.And the only mission in life of an operating system is to help those programs run. So an operationg system never does anything on its own.It’s only wating for the programs to ask for certain resources, or ask for certain files on the disk, or ask for the program to connect to the outside world. And then the operating system comes, steps in and then tries to make it easy for people to write programs.
怎么学习操作系统?
Learn OS concepts by coding them!绝知此事要躬行
操作系统是帮助应用程序操作计算机硬件的软件系统,主要帮助我们进行cpu管理,内存管理,终端管理,磁盘管理,文件管理等等,是应用程序和计算机硬件的中间层。
计算机的运行模式: 计算机是从图灵机到通用图灵机再到计算机的一步一步发展出来的。
图灵机是1936年,英国数学家A.C.图灵提出的一种计算模型,是一种用自动化设备模拟人的计算过程。用控制器模拟人的大脑,用读写程序模拟人的眼睛和笔,用纸带模拟纸。在做计算的时候只要在纸带上写上操作数和+号,控制器挨个读入后经过计算就得到结果打印到纸带上。但是这种模型缺乏通用性,控制器只能处理加法计算。 在此基础上图灵又提出了通用图灵机,在图灵机的基础上控制器具备了读懂”菜谱”的能力。能够设置/修改控制器,然后控制器开始工作读入数据处理得到结果。再读入新的控制器动作(程序),进行下一个计算。 在1946年,冯诺依曼提出了存储程序思想,存储程序的主要思想:将程序和数据存放到计算机内部的存储器(内存)中,然后把程序载入控制器(cpu)中解释执行,取指执行。在冯诺依曼架构中的计算机的五大组成部件: 输入设备,输出设备,存储器,运算器,控制器
打开电源
打开电源后操作系统就开始工作了,那么从打开电源开始计算机执行的第一条指令是什么?从冯诺依曼体系出发思考的话,也就是最开始的时候PC指针指向的是什么内容?
以x86为例,刚开机的时候CS=0xFFFF;IP=0x0000. 此时cpu是处于实模式,和保护模式相对应,实模式的寻址CS:IP(CS左移4位+IP)。因此刚开机的时候是寻址0xFFFF0,是指向ROM BIOS 映射区。
BIOS是英文“BasicInputOutputSystem”的缩略语,直译过来后中文名称就是“基本输入输出系统”。它的全称应该是ROM-BIOS,意思是只读存储器基本输入输出系统。其实,它是一组固化到计算机内主板上一个ROM芯片上的程序,它保存着计算机最重要的基本输入输出的程序、系统设置信息、开机上电自检程序和系统启动自举程序。
刚开机系统内存RAM都是没有内容的,只有在ROM中有固化的启动程序,随后开始检查RAM,键盘,显示器,软硬磁盘等等。
随后BIOS会将磁盘0磁道0扇区(一个扇区512k)读入0x7c00处,这个是操作系统的引导扇区,随后设置cs=0x7c00,ip=0x0000
http://www.ruanyifeng.com/blog/2013/02/booting.html 为什么将引导扇区加载到0x7c00处? http://www.ruanyifeng.com/blog/2015/09/0x7c00.html
bootsect.s
引导扇区代码是一段汇编代码 bootsect.s, 用汇编代码可以对整个流程进行完整的控制,防止高级语言在编译过程中产生的不确定性。
.globl begtext,begdata,begbss,endtext,enddata,endbss
.text //文本段 //.text等是伪操作符,告诉编译器产生文本段,.text用于标识文本段的开始位置。此处的.text、.data、.bss表明这3个段重叠,不分段
begtext:
.data //数据段
begdata:
.bss //未初始化数据段
begbss:
entry start //关键字entry告诉链接器“程序入口”
start:
mov ax, #BOOTSEG mov ds, ax // !此条语句就是0x7c00处存放的语句!
mov ax, #INITSEG mov es, ax
mov cx, #256 // 移动计数值=256字,1 word = 2byte = 16bit
sub si, si sub di,di
rep movw // 将0x07c0:0x0000处的256个字移动到0x9000:0x0000处, 256字是512字节,是将bios读入的第一个扇区的数据移动到了0x9000:0x0000处。这么做的目的是为了腾出空间
jmpi go, INITSEG // jmpi 是将go复制给IP,将INITMSEG复制给CS,go是下面一段代码的标号,因为这段代码被移动到了0x9000:0x0000处,那么下面要跳转到0x9000:go处执行
BOOTSEG = 0x07c0
INITSEG = 0x9000
SETUPSEG = 0x9020
mov ds, ax 是将ax内容赋给ds
movw: 将DS:SI的内容送至ES:DI,note! 是复制过去,原来的代码还在,每次移动一个字
rep 重复执行后面一句操作,并递减cx的值,知道cx=0停止
go: mov ax,cs //cs=0x9000
mov ds,ax mov es,ax mov ss,ax mov sp,#0xff00
load_setup: //载入setup模块
mov dx,#0x0000 mov cx,#0x0002 mov bx,#0x0200
mov ax,#0x0200+SETUPLEN int 0x13 //BIOS中断
jnc ok_load_setup
mov dx,#0x0000
mov ax,#0x0000 //复位
int 0x13j load_setup //重读
0x13是BIOS读磁盘扇区的中断: ah=0x02-读磁盘,al=扇区数量(SETUPLEN=4),ch=柱面号,cl=开始扇区,dh=磁头号,dl=驱动器号,es:bx=内存地址
cl是cx的低八位,因此是从02分区开始读,读入al=4个扇区,读到bootsect的上面,bootsect从90000开始,占用了512字节,因此内存16进制的位置是90200.所以es=0x9000,bx=0x0200
启动盘布局
在读入setup区域之后最后再跳转到setup代码段执行,bootsect代码段主要做的事就是从磁盘将setup和操作系统代码段的程序读入
jmpi 0, SETUPSEG // SETUPSEG=0x9020
setup.s
setup将完成OS启动前的设置,获取系统硬件信息,将system代码移动到0地址
start: mov ax,#INITSEG mov ds,ax mov ah,#0x03
xor bh,bh int 0x10 //取光标位置dx 取出光标位置(包括其他硬件参数)到0x90000处
mov [0],dx
mov ah,#0x88 int 0x15 mov [2],ax ... // 0x15 是获取物理内存的大小,获取出的值放在ax中,在复制给[2](间接寻址,写入0x90002)
cli ///不允许中断
mov ax,#0x0000 cld
do_move: mov es,ax add ax,#0x1000
cmp ax,#0x9000 jz end_move
mov ds,ax sub di,di
sub si,si
mov cx,#0x8000
rep // 将system代码移到0地址
movsw
jmp do_move
0x90000光标位置
0x90002扩展内存数
0x901FC根设备号
0x9000C显卡参数
在setup执行结束之后执行了以下代码
mov ax, #0x0001 mov cr0, ax
jmpi 0,8
在实模式中jmpi 0,8 是将0赋给ip,8赋给cs,得到的是跳转80,但是这里其实应该跳转到0地址执行。所以这里寻址方式已经发生了改变。
CS左移4位+ip的方式,最多可以进行20位寻址,最多只能访问1M,要切换到32位模式才能访问更大的内存空间。通过以上第一行代码,实现了寻址模式的切换, cr0是一个寄存器,上面代码将cr0置为1,进入了保护模式
实模式下:cs左移4+ip,保护模式下:根据cs查表+ip,真正的段的基址是放在表项中,相同的也有一个IDT(中断处理)
https://blog.csdn.net/ice__snow/article/details/50654629 IDT 和 GDT详解
所以在setup中会初始化gdt表
gdt: .word 0,0,0,0 // 0
.word 0x07FF, 0x0000, 0x9A00, 0x00C0 // 8, 寻址是以byte为单位
.word 0x07FF, 0x0000, 0x9200, 0x00C0 // 16
每个word是16位,一个表项有4个word,一个表项是64位。
将.word 0x07FF, 0x0000, 0x9A00, 0x00C0 放入gdt表
63-48位 | 47-32 |
---|---|
0x00C0 | 0x9A00 |
0x0000 | 0x07FF |
31-16 | 15-0 |
所以8所对应的表项的段基址是15..0, 23..16, 31..24,组成起来就是0x00000000,段基址是32位,所以jmpi 0,8 实际调到了内存0地址
system
从上面可以看到,引导盘的磁盘布局是固定的,boot -> setup -> system,操作系统编译通过Makefile控制Image的布局
操作系统Makefile
disk: Image
dd bs=8192 if=Image of=/dev/PS0
Image: boot/bootsect boot/setup tools/system tools/build
tools/build boot/bootsect boot/setup tools/system> Image
tools/system: boot/head.o init/main.o $(DRIVERS) ...
$(LD) boot/head.oinit/main.o $(DRIVERS) ... -o tools/systemsystem
head.s 是进入操作系统模块后执行的第一块代码,在setup中建立的gdt表只是为了能jmpi 到操作系统的head处,gdt表并没有建好,所以在head.s 中会重新初始化idt和gdt表。并且进入保护模式后,使用32位汇编的模式。在执行完初始化逻辑后,调用main.c 进入
after_page_tables:
pushl $0 pushl $0 pushl $0 pushl $L6
pushl $_main jmp set_paging
L6: jmp L6
setup_paging: //设置页表ret
这段代码是首先进行压栈,压入的是main函数的参数,最后压入了main函数的地址,压入之后跳转到set_paging执行,set_paginge执行结束后,执行ret后会执行main函数,ret指令会从栈顶出栈赋值给ip,也就是把main函数的地址赋值给ip,执行main函数。进入main函数之后的栈为0,0,0,L6,main函数的三个参数是0,0,0。main函数是不会退出的,退出后会无限循环L6。在main函数中完成一些内存,中断,设备,时钟,cpu等内容的初始化。
实际上在启动阶段就做了两件事,第一步将操作系统从磁盘读入内存,读入内存之后,cpu才可以开始取指执行。第二步是进行初始化的工作,针对计算机硬件的信息完成初始化