Operating System,OS:指控制和管理整个计算机系统的硬件与软件资源,合理的组织、调度计算机的工作与资源的分配,并且可以为用户和应用软件提供系统调用和运行环境的程序集合
并发(Concurrence)
指两个或多个事件在同一时间间隔内发生。计算机系统中“同时”运行着多个程序,这些程序宏观上看是同时运行着的,而微观上看是交替运行的
共享(Sharing)
共享即资源共享,是指系统中的资源可供内存中多个并发执行的进程共同使用
互斥共享方式:系统中的某些资源,虽然可以提供给多个进程使用,但一个时间段内只允许一个进程访问该资源
同时共享方式:系统中的某些资源,允许一个时间段内由多个进程“同时”对它们进行访问
并发和共享互为存在条件
没有并发和共享,就谈不上虚拟和异步,因此并发和共享是操作系统的两个最基本的特征
虚拟(Virtual)
虚拟是指把一个物理实体变为若干个逻辑上的实体。物理实体是实际存在的,而逻辑上的实体是用户感受到的
异步(Asynchronism)
异步是指,在多道程序环境下,允许多个程序并发执行,但由于资源有限,进程的执行不是一贯到底的,而是走走停停,以不可预知的速度向前推进,这就是进程的异步性
手工操作阶段(1946-1950年)
这个阶段还没有操作系统
单道批处理系统(50年代末-60年代中)
引入脱机输入/输出技术(用外围控制机+磁带完成),并由监督程序(操作系统的雏形)负责从磁带上调入一道程序进入内存运行
多道批处理系统(60年代中-70年代中)
标志着操作系统的出现
分时操作系统(70年代中-至今)
多个用户分时共享同一台计算机,响应时间短,人机交互性好
操作系统对各个用户都是完全公平的,循环地为每个用户服务一个时间片,不区分任务的紧急性,所以缺点是不能优先处理一些紧急任务
时间片轮转的系统,其吞吐量和周转时间均不如批处理系统
时间片的选择很重要,若时间片大到让一个进程足以完成其全部工作,则这种算法退化为先来先服务算法;若时间片太小,则处理器在进程之间的转换工作太频繁,系统开销增大
实时操作系统
适于处理紧急任务,响应快,可靠性高,但资源利用率低
在实时操作系统的控制下,计算机系统接收到外部信号后及时进行处理,并且要在严格的时限内处理完事件。实时操作系统的主要特点是及时性和可靠性
硬实时操作系统:必须在绝对严格的规定时间内完成处理(如导弹控制系统、自动驾驶系统)
软实时操作系统:能接受偶尔的延时处理(如订票系统)
其他类型的操作系统
网络操作系统:伴随计算机网络的发展而诞生的,能把网络中各个计算机有机地结合起来,实现数据传送等功能,实现网络中各种资源的共享(如文件共享)和各台计算机之间的通信。如Windows NT就是一种典型的网络操作系统,网站服务器使用
分布式操作系统:主要特点是分布性和并行性。系统中的各台计算机地位相同,任何工作都可以分布在这些计算机上,由它们并行、协同完成这个任务
个人计算机操作系统:如Windows XP、MacOS等,此外还有嵌入式操作系统、智能手机操作系统(Android、iOS)
UNIX中,write()这种函数叫系统调用封装函数,是系统级IO函数。它是对系统调用的封装,而编程语言提供的库函数或操作系统提供的API函数是对系统级IO函数的封装
系统调用(system call)是操作系统内核实现的对硬件操作的函数,然后向上层用户或应用软件提供的调用接口
系统调用是用户或上层应用程序使用操作系统及底层硬件的入口,是操作系统内核代码的入口
每一个系统调用内都有一条软件中断(trap自陷)的代码(如int 80)
操作系统把中断作为用户态进入核心态的唯一方式,利用中断处理程序来实施各种检查,让上层应用只能按照操作系统规定的格式来使用系统
int80的中断描述符(其他中断描述符不一定)的DPL为3,所以能通过特权级检查
通过中断描述符找到中断处理程序的CS段选择子(此时CPL被置为0)和偏移地址,通过GDT+段选择子找到段描述符获得基址,再拼接上偏移地址就找到了中断处理程序入口,然后就开始执行系统调用的实现了
宏内核提供内存分配功能的服务过程举例
微内核提供内存分配功能的服务过程举例
综合了宏内核与微内核优点的方案
单向依赖:最底层是硬件,最高层是用户接口,每层可调用紧邻的更低一层的功能
模块之间独立,各模块间通过接口通信
BIOS是针对具体主板设计的,与操作系统无关。BIOS中包含了各种基本设备的驱动程序,通过指向BIOS,这些基本驱动程序以中断服务程序的形式被加载到内存中,以提供基本IO系统调用。一旦进入保护模式,就不再使用BIOS
加电后PC被初始化为0xFFFF0(具体来说,CS置0xFFFF,IP置0x0000),CPU启动(开始取指执行),这个入口地址处是个跳转指令,跳转到0xFE05B地址处执行。此时执行代码的功能是:先硬件自检(Power-on Self Test)、初始化,再建立BIOS中断向量表和填写相应中断例程。在这之后,BIOS调用中断服务例程会读取存储设备的0盘0道1扇区的内容(共512B),如果验证了末尾两个字节是0x55和0xaa,那么BIOS就找到了主引导记录(启动区),然后就把这512B的内容从磁盘加载到内存0x7c00的位置,之后就是一条跳转指令,跳到0x7c00处执行。此时,BIOS的工作就完成了
主引导记录(Linux0.11的bootsect.s为例)被加载到内存0x7c00处后,然后自己把自己移动到内存0x90000处(读入操作系统核心代码腾出空间),然后将ds和cs设置为了0x9000,方便之后程序访问代码和数据。并且,将栈顶地址ss:sp设置在了离代码的位置0x9000足够遥远的 0x9FF00,保证栈向下发展不会轻易覆盖掉已有的代码。再然后bootsect.s继续读入操作系统的setup.s文件,从硬盘的第2个扇区开始,把数据加载到内存0x90200处,共加载4个扇区,再然后,把操作系统的核心system模块从硬盘第6个扇区开始往后的240个扇区,加载到内存0x10000处。至此,整个操作系统的全部代码,就已经全部从硬盘加载到内存中了。然后又通过一个段间跳转指令jmpi0,0x9020,跳转到0x90200处,就是硬盘第二个扇区开始处的内容(setup.s)
setup做三件事,先获取(使用BIOS中断)硬件(内存、硬盘、显卡)的一些信息存储在0x90000处开始的地方(具体位置如下图),接下来把内存地址0x10000处开始往后一直到 0x90000的内容,复制到内存的最开始的0位置,最后,就要从实模式转变为保护模式(打开A20地址线,初始化GDT表,设置GDTR寄存器,然后将CR0寄存器第0位置1,就从实模式切换到保护模式(启动保护模式的实质是启动MMU来完成运行时重定位)了,然后进行长跳转(跳转到0地址执行))
此时0地址处存储着操作系统全部核心代码(如下图),在进入C语言写的main.c之前,要先执行head.s。head.s做三件事:设置中断表和IDTR寄存器(将system从0x10000挪到0地址处时,BIOS中断就没法使用了,此时IDT表项没实在的内容,等到后面给各个模块初始化时,才会设置相应例程的入口地址到各个表项中)、重新设置GDT表和GDTR寄存器(这里重新设置在head程序中如第二幅图,这块内存不会被其他程序用到并且覆盖,并且最后还预留了252项的空间,这些空间后面会用来放置任务状态段描述符TSS和局部描述符表LDT)、最后设置页表(页表存放在0地址处开始的5个长度为4kb的内存中,覆盖了开头的已经执行过的system代码,四个页目录项将前16M的线性地址空间与16M的物理地址空间一一对应起来),设置页表寄存器CR3,设置CR0的最高位为1(启动分页机制),然后跳转到main函数执行(此时内存如图1.29),main是绝对不会返回的,否则就会到一个死循环(死机)
main函数里是各个模块的初始化函数;再之后的fork函数会创建一个新的进程,最终会启动一个shell程序与用户进行交互,这标志着操作系统建立完毕,达到了一个用户可用的状态