操作系统—启动与接口
1 操作系统概述
1.1 从屏幕上输出"hello",
(1)首先CPU向内存条发出指令,“hello”在内存的300位置,0X68就是h的ASCII码等,将其放在显存里地址为777;同理e到778位置等;
(2)实际上经过封装,只需要printf(“hello!”)就可以,而正是**操作系统的作用!**在计算机硬件之上,包装一层软件,使得使用更加方便;
1.2 什么是操作系统
(1)课程中主要将前5个,分别是CPU管理,内存管理,终端管理,磁盘管理,文件管理;
1.3 学习操作系统的层次
1.4 斯坦福实验
2 操作系统的启动
2.1 从打开电源开始
(1)从图灵机到通用图灵机,设置控制器动作(加法/乘法),输入控制器状态,处理数据对象;
设置控制动作的就是“程序”;
(2)冯‘诺依曼存储程序思想,1946年提出;
(3)主要思想:是将程序和数据存放在计算机内部的存储器中,计算机在程序的控制下一步一步进行处理;----!!取指执行!!
(4)计算机有五大部件组成:输入设备,输出设备,存储器,运算器,控制器’;
2.1.1 打开电源,计算机执行的第一条指令,PC=?
(1)x86 PC中有一段内存是固化的,称为ROM BIOS映射区,
BIOS(basic input output system),基本输入输出系统,固化程序在地址0xFFFF0;此时CS=0xFFFF;IP=0X0000;
(2)实模式的寻址=CS左移4位+IP;0xFFFF0+0=0xFFFF0
(3)这段固化程序用来检查RAM,键盘,显示器,软硬磁盘;
(4)一个扇区=512字节;0磁道0扇区就是操作系统的引导扇区;将操作系统的第一段代码读入到0x7c00处,
(5)然后设置CS=0x07c0,IP=0x0000;正好指向0X7c00处;
2.1.2 引导扇区(0x7c00处存放的代码)
2.1.3 引导扇区代码:bootsect.s(s就是汇编)
(1)move 目标操作数 源操作数;ds=7c0;es=9000;**这个是段寄存器,需要段内偏移才能形成地址;**sub si si,(si=0),sub di di;(di=0),因此地址变为07c00和90000;
(2)rep movw 将0x07c0:0x0000处的256个字移动到0x9000:0x0000处;
2.1.4 jmpi go ,INITSEG读取setup的四个扇区
(1)在读入boot引导扇区之后,要开始读之后的扇区,es:bx=90000:200=90200=内存地址
(2)重点在于int 0x13//BIOS中断,0x13是BIOS读磁盘扇区的中断;在读入引导扇区并执行之后要开始读其他扇区,使用13号中断进行读取;cx=0X0002代表从第2个扇区开始读,al是扇区数量;
(3)cx寄存器(16位)可分为ch和cl,ch(高八位),cl(低八位); cx由ch,cl拼接构成;ax由ah,al拼接构成;cl=开始扇区=02;al=扇区数量=4,也就是从第2个扇区开始读,读4个扇区;
(4)将setup的4个扇区读到boot的上面,一个扇区512字节,就是90000->90200;
2.1.5 读入setup模块后:ok_load_setup
(1)注意:**此处为0x10中断号,将bp的数据显示到屏幕光标位置上,**msg1的数据如图;
(2)call read_it就是继续读取system模块(OS代码);
(3)如果要更改开机页面,将Loading system替换,并且要更换mov cx #24,更改24到真实输出的字符数;
2.1.6 read_it//读入system模块
2.2 操作系统启动setup
(1)从磁盘载入到内存中;这个工作是由第一段代 码,由操作系统的引导扇区boostscet.s开始;先读setup在屏幕上打出开机Logo,然后再一次读入system扇区…
2.2.1 setup模块,即setup.s
(1)启动需要精细控制,因此要用汇编语言,精准完成操作指令;控制内存;
(2)int 0x15是15号中断,要获得物理内存的大小;获取的值放在ax中,ax的值又被放在[2]中;[2]是间接寻址;默认与一个段寄存器指向9000,首先要将9000左移4位,然后再+2;在表中看到0x90002中可以看到扩展内存数;通常将1M之后的内存称为扩展内存数;
(3)操作系统要管理内存,首先就要知道内存的多少,建立对计算机内存的认知;包括还要知道光标位置、显卡参数,根设备号等,通过相应的数据结构来存储信息,来管理计算机;
(4)操作系统的开机,做了两件事分别是读入系统bootsect.s和初始化setup.s;
(5)从do_move开始,es=0x0000,ds=0x9000;ds:si和es:di
(6)将所有操作系统移动到0地址处,setup获取内存参数,挪动操作系统sysytem代码到0地址后以后system就一直处于那个地方,开始退出;操作系统不能断,因此最后一个事就是jmpi 0,8;进入保护模式;
2.2.2 进入保护模式—jmpi 0,8
(1)jmpi 0,8 就是将0赋给IP,8赋给cs;cs左移4位+IP->80位置;80处会死机,非法指令;此处寻址方式会发生改变;很重要,cs和ip都是16位寄存器;cs左移4位+ip的寻址方式最多只能达到20位的地址,就是1M;
(2)转变寻址模式,将16位机转为32位机;32位模式又称为保护模式;
(3)cr0寄存器的最后一位控制,1为保护模式(32位),move cr0, ax就是将其置为保护模式;
2.2.3 保护模式下的地址翻译和中断处理
(1)**gdt:global description table **全局描述符表;cs从table中选出一个表象,根据表象产生基址,gdt是由硬件实现的,因为速度快;
(2)cs:ip模式**,cs取出基址+ip偏移**,**在保护模式下,cs和ip都是32位;**可以访问4G空间;
(3)保护模式下中断处理函数入口,IDT表象中存放的中断处理函数入口;
(4)表象中必须有内容!setup要做一个事就是初始化gdt;下面就是表,每个表象有4个(word)16位,因此是64位,而且是寻址以字节为单位,因此从第一个为0,第二个就是8;
2.2.4 jmpi 0,8 //gdt中的8
(1)cs是8,就到GDT中寻找,8对应第二行(寻址按照字节,每一行16*4=64位=8字节),然后根据硬件规定的取址方式,可以得出基址就是0x0000,ip也是0,因此就是调到0地址;setup工作到此结束;
(2)总结:setup的主要三大作用:
- 读取硬件参数;
- 把system挪动到00地址处,操作系统的核心代码就一直存储在00地址;
- 启动保护模式,应用了一条高级32位汇编指令,跳到00地址处;
2.3 跳到system模块执行
(1)**操作系统要正常运行,必须是boot,setup,system的依次排列,**否则会死机,必须是BOIS读bootsect,bootsect读set up;set up读system,且跳到system的第一部分代码;
(2)**编写操作系统一切都需要自己控制;**除了要写源码之外,还要写操作系统的控制代码linux/Makefile;操作系统编写出来的样子(boot+setup+system)称为image可以放在任意磁道扇区,一般都是0磁道0扇区;Image是树结构;
(3)head.s是system的第一个文件;
2.3.1 head.s//一段在保护模式下运行的代码、
(1)又一次初始化了idt表,和gdt表;call setup idt;和call setup gdt;
(2)注意进入保护模式之后,movl $0x10 ,%eax是源操作数在目的操作数之前,与之前的16位汇编不一样;
2.3.2 汇编不同
2.3.3 after_page_tables//设置了页表之后,从汇编.s到main.c
**(1)head.s执行完就要去执行main.c;**需要从汇编调到c文件;
(2)C执行func(p1,p2,p3);首先对三个参数进行压栈,第一个参数先入栈,还要将func的返回地址压入栈中,func后面一条语句的地址;对应左侧的pushl $0-pushl $_main;ret开始弹出;先弹出main;main返回时会进入L6死循环,所以main永远不会弹出;
(3)运行时,栈是从高地址拓展到低地址的,所以是从上往下压栈;
2.3.4 进入main函数
(1)main函数中都是初始化函数,其中展开来说一下mem.init;
2.3.5 看一下mem_init(内存的初始化)
(1)end_mem>>=12;右移12位,就是除以2的12次方,就是解4K;4K作为一页;
(2)橙色部分表示使用的,存放的操作系统的程序,下面的绿色是未使用的;总的长度取决于传入的参数end mem就是总的内存大小;setup那里读取内存大小放进这里;
(3)整个流程回顾如下;—将操作系统读入内存+初始化—
-
boot:将操作系统从磁盘上读进来;
-
setup:读取硬件参数;挪动操作系统代码到00地址;启动保护模式;
-
head:初始化了gdt表,和页表;head.s是system的第一段代码;
-
main:一堆初始化;
3 操作系统的接口
(1)上层应用怎么穿过边界到达操作系统,就是操作系统的接口;
(2)接口:连接两个东西,信号转换,屏蔽细节;
3.1 操作系统接口概念
(1)用户使用计算机主要通过:命令行;window下的图形按钮;应用程序中的操作;
3.1.1 命令行
(1)下面方框就是shell程序;调用fork()和exec(cmd)实现对CPU的使用,调用scanf()实现对键盘使用;printf()实现对屏幕显示器的使用;、
(2)命令行就是程序只是加了重要的函数对计算机硬件的调用
3.1.2 图形按钮
(1)使用普通的C语言函数+调用重要的系统函数进行实现;
3.1.3 再回到那个问题,什么是操作系统接口?
(1)接口就表现为函数调用,又由操作系统提供的函数就是操作系统的接口,称为系统调用system_call;操作系统接口==系统调用
(2)具体的一些系统调用:POSIX是统一的接口标准/系统调用;
3.2 系统调用(接口)的实现
(1)比如想要调用一个放在内存地址100的字符串,不可以直接调用printf显示吗。答案是不可以;
**不可以直接访问操作系统内核;不能随意的调用数据,不能随意的jmp;**否则操作系统不安全!!
3.2.1 阻止用户随意访问和jumpi的机制–硬件设计
(1)操作系统分为内核/用户态,内核/用户段,操作系统的内存是按段使用;
(2)用到两个段寄存器分别是CPL(CS)和RPL(DS),DPL用来描述目标内存段的特权级;Destination privilege level;要调往/访问的目标段的特权级;Current privilege level当前的特权级;其中0是内核态,3是用户态;
3.2.2 硬件提供了“主动进入内核的方法”–中断
(1)中断是进入系统内核的唯一方式,中断指令int;
(2)系统调用的核心过程:
- 用户程序中包含一段包含int指令的代码;
- 操作系统写中断处理,获取想调程序的编号;
- 操作系统根据编号执行相应代码;
3.2.3 系统调用的实现
(1)系统提供的唯一的中断入口0x80;int 0x80;中断指令;
(2)调用printf()->库函数printf()->库函数write()->通过宏来系统调用write(),使用int 0x80中断指令实现;宏展开为一段包含中断指令的汇编代码;
3.2.4 从_syscall3这段宏开始
(1)内嵌汇编的格式就是->代码:输出:输入;其中"int 0x80"就是代码,“=a”(_res)就是输出," "(_NR##name),“b”(long(a))一直到“d”(long(c))都是输入;
(2)解释一些type,这里被用来**定义宏参数,也就是说参数类型可以被替换,**这样就使得宏函数的定义变得灵活,算是Linux早起编程使用的一个trick;
(3)#define _syscall3(type,name,atype,a,btype,b,ctype ,c)分别与上一个调用对应,_syscall3(int,write,int,fd,const char*buf,off_t,count);下面的type name(atype a,bytpe b,ctype c)与上面接收的参数atype=int,btype const char**,ctypr=off_t对应;
(4)中心代码就是_asm_ volatile()这个执行中断语句;核心代码就是int 0x80;,输出为eax,输入没有默认也是eax;也就是将__NR__##name==——NR——##write;置给eax
(5)“b”((long)(a))将a置给b;对应上面参数,就是将fd置给ebx;后面是将b置给ecx;将c置给edx;
(6)–NR–write是系统调用号,放在eax,大家中断都是从0x80进来的,因此需要系统调用号来进行区分;
(7)过程梳理:内嵌汇编–代码–输出–输入
- 输入:——NR__write给eax,a给ebx,b给ecx;c给edx;
- 代码:int 0X80;执行中断程序;
- 输出:eax置给__res;
(8)syscall3()中3的含义就是3个参数;核心代码都是int 0x80;
3.2.5 int 0x80中断的处理
之前是查找gdt表来确定jumpi跳转地址;int指令也需要查idt表来确定中断转到哪个地方去执行;
(1)通过sched_init进行初始化,调用set_system_call中断处理门函数,核心就是对中断表idt进行初始化设置值,set_system_gate用来设置0x80的中断处理;
(2)#define set_system_gate(n,addr)可以看到对应,n就是0x80中断号,addr就是&system_call中断处理函数地址;set_gate中&idt就是全局变量idt的初始地址,找到n=80对应的表象;3对应就传到了下方的dpl;dpl=3;设置目的特权级=3;从movl红色部分开始也是内嵌汇编代码;代码+输出+输入;
(3)用int 0x80展开后,会将DPL=3,此时就可以进入内核;
**(4)CS=8;IP=system_call;CS的最后两位就是00,因此CPL=0;**因此在中断中进入中断处理函数时,CPL=0;特权级就变成0;初始化时DPL=3是为了让用户代码进入,进入之后CPL就变成0了就进入内核进行操作;
3.2.6 中断处理程序:system_call
(1)将0X10置给edx,将dx置给ds和es;
(2)4*代表每个系统调用占4个字节,32位系统,函数指针为4个字节;——sys——call——table就是一个函数表;
3.2.7 —sys—call—table
(1)可以看到,sys—call—table[]就是一个函数指针数组;第4个位置上放置sys_write;
(2)现在是从软件进入操作系统的接口,具体操作系统内部write调用,之后再讲;
3.2.8 总结
(1)左侧:为什么不能直接jmpi进入呢,因为操作系统会变得不安全;通过硬件设计用户态CPL=3,内核态DPL=0;阻止用户直接访问内核;
**(2)右侧:**根据系统调用号eax=72;(这里是72);唯一通道就是中断0x80;通过int 0x80指令才能穿过接口到达操作系统;接口首先设置DPL=3允许用户程序进入,之后CPL=0;访问内核开始system_call:通过查表,执行call_whoami,再执行操作系统中的sys_whoami,调用Printk内核版本,访问存储在地址100的字符串;
4 操作系统历史
4.1 1955-1965
(1)监控系统称为批处理操作系统,一个任务完成或出错改变PC指针指向下一个地方;
4.2 从IBSYS到OS/360(1965-1980)
4.3 从OS/360到MULTICS(1965-1980)
4.4 从MULTICS到UNIX(1980-1990)
4.5 从UNIX到Linux(1990-2000)
4.6 总结历史:IBSYS->OS/360->MULTICS->Unix->Linux
4.7 历史是多线条的:PC与DOS
4.8 从QDOS到MS-DOS
4.9 从MS-DOS到Windows
4.10 总结历史:CP/M->QDOS->MS-DOS->Windows
Unix->System->Mac OS->iOS
5 我们的任务
5.1 什么是操作系统?温故
(1)操作系统:管理计算机硬件的软件;
(2)硬件:CPU,内存,显示器,键盘,打印机,IO设备,磁盘;
(3)管理CPU和内存,就是多进程图像;管理IO设备,磁盘产生出文件,合在一起就是文件图像;
(4)上层应用切入操作系统;
5.2 知新—管理硬件资源
5.3 再具体一些,学什么?