什么是操作系统和系统调用
操作系统是从硬件抽象出来的虚拟机,在该虚拟机上用户可以运行应用程序。它负责直接与硬件交互,向用户程序提供公共服务,并使它们同硬件特性隔离。因为程序不应该依赖于下层的硬件,只有这样应用程序才能很方便的在各种不同的Unix系统之间移动。
系统调用是Unix/Linux操作系统向用户程序提供支持的接口,通过这些接口应用程序向操作系统请求服务,控制转向操作系统,而操作系统在完成服务后,将控制和结果返回给用户程序。
Unix/Linux系统体系结构
一个Unix/Linux系统分为三个层次:用户、核心以及硬件。
其中系统调用是用户程序与核心间的边界,通过系统调用进程可由用户模式转入核心模式,在核心模式下完成一定的服务请求后在返回用户模式。系统调用接口看起来和C程序中的普通函数调用很相似,它们通常是通过库把这些函数调用映射成进入操作系统所需要的原语。这些操作原语只是提供一个基本功能集,而通过库对这些操作的引用和封装,可以形成丰富而且强大的系统调用库。这里体现了机制与策略相分离的编程思想——系统调用只是提供访问核心的基本机制,而策略是通过系统调用库来体现。
例:execv, execl, execlv, opendir , readdir...
Unix/Linux运行模式,地址空间和上下文
运行模式(运行态):
一种计算机硬件要运行Unix/Linux系统,至少需要提供两种运行模式:高优先级的核心模式和低优先级的用户模式。实际上许多计算机都有两种以上的执行模式。如:intel 80x86体系结构就有四层执行特权,内层特权最高。Unix只需要两层即可以了:核心运行在高优先级,称之为核心态;其它外围软件包括shell,编辑程序,Xwindow等等都是在低优先级运行,称之为用户态。
之所以采取不同的执行模式主要原因时为了保护,由于用户进程在较低的特权级上运行,它们将不能意外或故意的破坏其它进程或内核。程序造成的破坏会被局部化而不影响系统中其它活动或者进程。当用户进程需要完成特权模式下才能完成的某些功能时,必须严格按照系统调用提供接口才能进入特权模式,然后执行调用所提供的有限功能。
每种运行态都应该有自己的堆栈。在Linux中,分为用户栈和核心栈。用户栈包括在用户态执行时函数调用的参数、局部变量和其它数据结构。有些系统中专门为全局中断处理提供了中断栈,但是x86中并没有中断栈,中断在当前进程的核心栈中处理。
地址空间
采用特权模式进行保护的根本目的是对地址空间的保护,用户进程不应该能够访问所有的地址空间:只有通过系统调用这种受严格限制的接口,进程才能进入核心态并访问到受保护的那一部分地址空间的数据,这一部分通常是留给操作系统使用。另外,进程与进程之间的地址空间也不应该随便互访。这样,就需要提供一种机制来在一片物理内存上实现同一进程不同地址空间上的保护,以及不同进程之间地址空间的保护。
Unix/Linux
中通过虚存管理机制很好的实现了这种保护,在虚存系统中,进程所使用的地址不直接对应物理的存储单元。每个进程都有自己的虚存空间,每个进程有自己的虚拟地址空间,对虚拟地址的引用通过地址转换机制转换成为物理地址的引用。正因为所有进程共享物理内存资源,所以必须通过一定的方法来保护这种共享资源,通过虚存系统很好的实现了这种保护:每个进程的地址空间通过地址转换机制映射到不同的物理存储页面上,这样就保证了进程只能访问自己的地址空间所对应的页面而不能访问或修改其它进程的.
地址空间对应的页面
虚拟地址空间分为两个部分:用户空间和系统空间。在用户模式下只能访问用户空间而在核心模式下可以访问系统空间和用户空间。系统空间在每个进程的虚拟地址空间中都是固定的,而且由于系统中只有一个内核实例在运行,因此所有进程都映射到单一内核地址空间。内核中维护全局数据结构和每个进程的一些对象信息,后者包括的信息使得内核可以访问任何进程的地址空间。通过地址转换机制进程可以直接访问当前进程的地址空间(通过MMU),而通过一些特殊的方法也可以访问到其它进程的地址空间。
尽管所有进程都共享内核,但是系统空间是受保护的,进程在用户态无法访问。进程如果需要访问内核,则必须通过系统调用接口。进程调用一个系统调用时,通过执行一组特殊的指令(这个指令是与平台相关的,每种系统都提供了专门的trap命令,基于x86的Linux中是使用int 指令)使系统进入内核态,并将控制权交给内核,由内核替代进程完成操作。当系统调用完成后,内核执行另一组特征指令将系统返回到用户态,控制权返回给进程。
上下文
一个进程的上下文可以分为三个部分:用户级上下文、寄存器上下文以及系统级上下文。
用户级上下文:正文、数据、用户栈以及共享存储区;
寄存器上下文:程序寄存器(IP),即CPU将执行的下条指令地址,处理机状态寄存器(EFLAGS),栈指针,通用寄存器;
系统级上下文:进程表项(proc结构)和U区,在Linux中这两个部分被合成task_struct,区表及页表(mm_struct , vm_area_struct, pgd, pmd, pte等),核心栈等。
全部的上下文信息组成了一个进程的运行环境。当发生进程调度时,必须对全部上下文信息进行切换,新调度的进程才能运行。进程就是上下文的集合的一个抽象概念。
系统调用的功能和分类
操作系统核心在运行期间的活动可以分为两个部分:上半部分(top half)和下半部分(bottom half),其中上半部分为应用程序提供系统调用或自陷的服务,是同步服务,由当前执行的进程引起,在当前进程上下文中执行并允许直接访问当前进程的数据结构;而下半部分则是由处理硬件中断的子程序,是属于异步活动,这些子程序的调用和执行与当前进程无关。上半部分允许被阻塞,因为这样阻塞的是当前进程;下半部分不允许被阻塞,因为阻塞下半部分会引起阻塞一个无辜的进程甚至整个核心。
系统调用可以看作是一个所有Unix/Linux进程共享的子程序库,但是它是在特权方式下运行,可以存取核心数据结构和它所支持的用户级数据。系统调用的主要功能是使用户可以使用操作系统提供的有关设备管理、文件系统、进程控制进程通讯以及存储管理方面的功能,而不必要了解操作系统的内部结构和有关硬件的细节问题,从而减轻用户负担和保护系统以及提高资源利用率。
系统调用分为两个部分:与文件子系统交互的和进程子系统交互的两个部分。其中和文件子系统交互的部分进一步由可以包括与设备文件的交互和与普通文件的交互的系统调用(open, close, ioctl, create, unlink, . . . );与进程相关的系统调用又包括进程控制系统调用(fork, exit, getpid, . . . ),进程间通讯,存储管理,进程调度等方面的系统调用。