操作系统的目标

实际上要运行一个程序,操作系统并不是必须的,完全可以把二进制程序交给硬件直接运行。但这样做是不明智的,会有许多麻烦的问题无可避免,这也就是操作系统之所以存在的原因。总的来说操作系统有如下几个目标(作用):

  • 硬件抽象。对低级硬件资源的抽象并提供高层级的接口,可以方便应用开发并提供更好的移植性;

  • Multiplex。允许多个应用程序之间共用硬件资源,程序之间互不干扰;

  • 隔离性。操作系统可以同时运行多个程序,同时保证程序之间互不干扰,例如其中一个程序出现故障,其他程序不会受到影响。主要通过两种方式实现:1)硬件,user/kernel/machine mode,2)虚拟内存;

  • 共享性。在隔离性的基础上,也需要提供程序间共享数据的接口,例如进程间通信方式;

  • 高性能。虽然操作系统对硬件做了抽象,但不应该阻碍硬件的性能发挥,而是帮助应用程序获得最高性能,例如充分发挥CPU的多核性能;

  • 多场景。支持不同类型的应用程序,这里想强调的是,一个强大的操作系统应该支持不同的用户场景,例如编辑、游戏、服务器、桌面等等。

操作系统的结构

一个典型的操作系统(宏内核)从底向上结构如下:

  • 硬件。操作系统要抽象的对象;

  • 内核。操作系统抽象硬件的方法和手段,涉及到内存管理、进程管理和文件系统,三者共同作用下实现对硬件的抽象,向用户提供使用接口;

  • 用户。操作系统的用户利用操作系统提供的接口(通常是系统调用)来使用底层的硬件资源。

操作系统隔离性的实现

两个方面:

  • 硬件上提供三种模式:user model,kernel model,machine model;

  • 虚拟内存,通过软件层面的页表和硬件层面的MMU实现。

用户空间和内核空间的切换

程序在:

  • 执行系统调用时;

  • 出现page fault、除0 等错误时;

  • 需要响应设备中断时

会发生用户空间到内核空间的切换。这样的切换本质上通过一个硬件指令(int、syscall、ecall等)来进行CPU状态信息的切换,例如保存和恢复寄存器,将mode寄存器改成kernel mode,修改堆栈寄存器指向内核堆栈,程序计数器指向内核代码等等,具体的过程可以看之前系统调用相关文章。

页表

页表是实现虚拟内存的手段,而虚拟内存是一个现代操作系统做到隔离性的不二法门。页(page)是针对内存而说的,它是操作系统管理内存的最基本单位,通常是4096(4k)字节。当CPU想要访问某个物理地址上的数据时,它就会首先寄希望于找到该数据所在的物理页,而找到物理页的前提是找到物理页的地址,找到物理页的地址就需要先通过虚拟地址(CPU直接直到的地址)找到其页表项。也就是说需要通过页表来把虚拟地址对应到一个具体的页表项(当然也可以通过TLB缓存),根据页表项的地址找到物理页的地址,然后通过虚拟地址的偏移量具体定位到物理页的数据。虚拟地址空间比实际物理地址空间大得多,通常使用三级页表,具体的过程可以看之前的关于进程的方方面面

进程管理

进程是一个程序运行起来的示例,操作系统通过虚拟内存为进程提供一个独立统一的内存地址空间,同时为进程的创建和使用提供优化。

缺页(page fault)

通过缺页可以实现lazy allocation、写时拷贝、demand paging等特性,在操作系统分配内存、创建新进程、执行代码时,我们不完全分配内存、拷贝进程数据、加载执行代码,而是等到实际要用到具体页的数据时,发生一个缺页异常,这是才分配、拷贝、加载指令。这样做的好处是可以避免对用不到的数据进行操作,提升系统进程管理的效率。

多线程和锁

操作系统提供对多线程(进程)支持的原因是用户对同时(或者看起来同时)执行多个认为的需求,同时,也是为了满足操作系统高效性的需求,即充分利用CPU的多核性能。关键之处在于实现在同一个CPU下多个线程之间的切换,其本质上是维护每个线程的状态信息(有点像trap),线程与线程之间的切换(调度)由定时器中断驱动。

锁是为了避免多线程并发带来的条件竞争问题而产生的,操作系统中,借由硬件提供的test and set(compare and exchange)指令,可以实现自旋锁,从而对可能发生条件竞争的地方进行同步,注意锁的粒度会严重影响系统的效率。同时,可以使用Sleep&Wakeup(wait&notify)机制对两个进程进行同步。

文件系统

关于文件系统的话题可以看

微内核与宏内核