操作系统笔记

296 阅读10分钟

操作系统基础

1.什么是操作系统

操作系统(OS)是计算机硬件和应用之间的一层软件。 image.png image.png 五个深色为基本部分,上层应用使用OS提供的接口来使用计算机硬件。

2.打开操作系统

  • 计算机说到底就是一个计算模型,从图灵机到通用图灵机,再到冯诺依曼计算机结构。
  • 计算机执行的过程就是:取指执行。因此要将OS从磁盘/硬盘中,读到内存中,这样才可以正常运行。 image.png
  • (Intel结构即×86结构)对Intel这个PC机来说其内存中有一部分是固化的(固化在0xFFFF0中),叫ROM BIOS(basic in out sysytem)
  • 开机之后,计算机执行的第一句指令:刚开始在FFFF0处,在BIOS区执行固定的程序。检查RAM,键盘显示器磁盘...
  • 将0磁道0扇区的东西(操作系统的引导扇区,一个扇区512字节)读到0x7c00处
  • 跳到引导扇区执行启动程序。(cs<<4+IP=0x7c00) image.png
  • 引导扇区代码bootsect.s,是汇编代码
  • 地址都是段地址:偏移地址 int 0x13是关键,BIOS开始读磁盘扇区的中断。为setup读四个扇区,system模块继续读。 读了setup之后,进入ok_load_setup,int 0x10是关键,BIOS中断,显示字符。 Lecture3:操作系统启动 将操作系统读入内存+完成初始化。
  1. setup模块,setup.s int 0x15BIOS中断,获得物理内存的大小,放在ax中,然后送到90002保存。方便之后管理内存。 获得显卡参数、设备号等等信息,放在内存中,方便之后管理。 移动system模块到0位置。整个内存中,操作系统一直放在0地址处开始的位置。 最后的gdt:xxxxx,初始化gdt表。

setup最后执行一个jmpi 0,8,因为已经进入了保护模式,寻址方式进行改变,这个程序跳到零地址处,即system模块。 传统jmpi 0,8,即0给ip,8给cs,偏移地址为cs左移4位+ip,这样最多能表示20位,即1M的地址,太少了。需要改变寻址方式,切换到32位模式(保护模式),启动32位寻址模式(4G)。 cr0最后一位如果为1,进入保护模式。

使用gdt得到地址。现在cs叫做选择子,存放的不再是地址,而是表中的下标,通过cs在表中(即gdt全局描述表)查找,再+ip,得到地址。 gdt是通过硬件实现的,速度很快。 保护模式下的中断,也变为在IDT找,找中断处理函数的入口地址。 2. system模块 system模块的第一部分的代码为head.s。

head.s又做了许多事情,在保护模式下,与前面的16位汇编代码不同。

main永不停止,不会返回,main如果返回了,就进入L6,死循环。

main中执行各种初始化的函数。 比如,mem_init执行内存初始化。

初始mem_map数组,每次解4k内存,4k作为一页。数组表格中保存哪些是内存是使用的,哪些没用。 end_mem,即为总的内存,可以求出数组的尺寸大。 Lecture4:操作系统接口(OS Interface) 操作系统的接口:连接上层用户和操作系统软件,屏蔽了实现的细节。

用户使用计算机:通过命令行、图形按钮、应用程序。这是总体的使用,并不是真正的接口。

使用命令行的时候:命令对应的是程序,编译完就是可执行文件,shell命令将可执行文件执行(申请CPU执行,使用一些函数)。

图形按钮(鼠标点击、键盘按下):图形界面就是一个包括画图的C程序。硬件系统实现一个消息队列,当鼠标/键盘点下之后,通过中断送入消息队列;应用程序要实现一个消息循环,getmessage,来拿出内部的这些消息,通过消息处理函数,完成该消息对应的功能。

==关键在于调用的重要函数,这就是操作系统的接口。== 称为==系统调用==。

Lecture5:系统调用的实现 上层的函数,不能直接调用内核中的信息,不能直接jmp/mov,需要使用系统调用提供的接口才可以进入到内核中,得到信息,这样更加安全。

使用一种硬件设计,来将内核程序和用户程序进行隔离,用户态和内核态。 DPL描述目标内存段的特权级,0表示是内核段。CPL描述当前的特权级。当前特权级比目标更小(即当前特权更高),才可以访问。

硬件提供了唯一的主动进入内核的方法,即中断。 系统调用的核心,执行int 0x80指令,中断处理函数处理中断,根据该中断执行相应代码完成进入内核的操作。

宏syscall3,展开汇编指令,把NR_write置给eax,int 0x80调用中断,进入内核。 int 0x80:

到IDT表中找到中断处理程序入口位置(使用system call函数),跳到那里执行,处理之后再回来。 会将DPL设为3,这样CPL=DPL,就可以进入内核了。

Lecture6:操作系统的历史 IBSYS监控系统(1955-1965) OS/360(1965-1980) MULTICS(1965-1980) UNIX(1980-1990) Linux(1990-2000)

Lecture7:我们的任务

Lecture8:CPU管理的直观想法 操作系统在管理CPU的时候引出了多进程图像。

CPU的工作原理:

自动的取指执行。PC存放指令地址,取到地址,CPU执行指令,PC会自动更改为下一个指令的地址。 那么最简单的管理CPU方法,就是设置好PC的初值即可。 由于I/O指令比计算指令所需要的时间多得多,近似是10 ^ 6倍,因此简单的设置PC初值,一个一个指令顺序执行,I/O执行的时间,CPU需要等待,因此效率非常低。 让多道程序,交替执行,可以重复利用CPU,提高效率。在一个CPU上交替执行多个程序,即并发。 如何做到并发:

我们需要控制PC,在CPU等待的时候,切换到其他程序,等待结束还要可以切换回来。 同时切换回来的时候,要恢复原来的执行状态(这些信息在切换出去的时候需要记录下来)。 进程:

进程是执行中的程序,记录程序运行的样子。 进程有开始、结束、程序没有。 进程会走走停停,走停对程序无意义。 进程需要记录ax,bx等,程序不需要。 Lecture9:多进程图像 启动了的程序就是进程,多个进程推进;操作系统只需要将这些进程记录好,要按照合理的次序推进(分配资源、进行调度),这就是多进程图像。

多进程图像从启动开始,到关机结束。是操作系统的核心图像。

利用PCB这种数据结构来记录进程信息。

操作系统有序地维护这个进程状态图。 PCB描述进程,队列中排队,状态表示进程的状态 多进程的交替切换(队列操作+调度+切换):

schedule()函数,完成切换,从就绪队列中找到下一个进程(PCB),切换到下一个进程。 getNext()函数,进行进程调度,即找到下一个进程。简单的方法有FIFO,引入优先级。 switch_to()函数,进行切换,当前进程的一些信息保存到PCB,然后把下一个进程的PCB加载,就切换到下一个进程了。 多个进程可能会相互影响(因为都在内存中,可能会导致进程2直接访问了进程1的地址,导致进程1出错),因此可以限制对之前进程地址的读写,进行多进程的地址空间分离,进程之间地址互不影响,这是内存管理的主要内容。 多进程合作,核心在于进程同步(合理的推进顺序)。 Lecture10:用户级线程(多个进程如何切换)

将资源和指令执行分开,只需要切换PC,不需要切换内存中的映射表,则为线程。 线程是有价值的,许多程序需要共享资源,因此不需要切换资源,使用线程即可。

create创造第一次切换时的样子,yield进行切换(切换出去再切换回来)。

==每个线程自己一个栈,只有yield可以切换栈==。TCB为线程控制块(Thread Control Block,TCB),与上面的PCB类似。 204是在调用yield时候压栈的,因此在yield结束的时候弹出204,因此不需要jmp 204,yield结束之后会弹出204,执行204的},弹出104,返回到104,yield只需要切换栈即可。按照原本的jmp,会多执行一次204。

yield都是用户级线程,只是在用户态切换,不进入内核。如果某个线程需要使用硬件,那么需要经过内核,那么等待的时候,内核看不到该线程还有其他的线程一起运行,会直接切换到其他的进程。这边应该执行的线程也会停止运行。 核心级线程schedule(内核级线程)可以进入内核,并发性更好。由操作系统完成,用户不可见。

Lecture11:内核级线程

多处理器跟多核有区别,多处理器是每个处理器自己一套cache,MMU(相关映射),多核是多个CPU共用一套。 因此多核可以快速进行多内核级线程,因为共用MMU,不需要切换资源。注意只有内核级线程,OS才可以分配硬件,才能用到多核,核是OS管理的。

两套栈,每个线程有一套栈(用户栈和内核栈)。 进入内核的时候(中断,进入内核),用到内核栈。

INT中断,启用内核栈,通过指针与用户栈相连,内核栈存放用户栈当前执行到的位置,方便之后退回。 SS:SP即用户栈栈顶地址,CS:IP即用户程序开始的地址。 进入内核之后:

在内核中也会切换线程,switch_to。 比如S和T两个线程,首先进入S的内核(通过中断),需要切换时,在S的内核栈找到T的TCB,切换栈,到T的内核栈,使用iret中断返回指令,再切换到T的用户栈。

如果是进程切换,还需要切换地址映射表(内存管理)。

Lecture12:内核级线程实现 核心是栈的切换。

fork系统调用,引起中断。在A中执行,遇到fork。 遇到 0x80,中断(找到当前内核栈,将SS,SP送入内核栈,然后执行system_call)

将用户态的现场情况,保存到内核栈中。 内核态中执行sys_fork,即中断函数的功能。 在中断函数执行过程中,发现可能需要等待,则就需要进行切换。 即cmp比较,状态state如果不为0,说明该线程阻塞,则schedule切换到其他线程。 cmp比较,counter不为0,则时间片用光了,也需要调度切换。 结束之后,进行中断返回函数,从内核栈回到用户态。

next即找到切换线程/进程的TCB,switch_to完成切换,next具体算法后面讲。

图里是基于TSS的切换,效率较慢;现在都使用内