2.6.4 程序接口(系统调用)
程序接口是操作系统专门为用户程序设置取得操作系统服务的唯一途径。程序接口通常由各种类型的系统调用组成,因而,也可以说,系统调用提供了用户程序和操作系统之间的接口。所谓系统调用,本质上是应用程序请求操作系统内核完成某功能时的一种过程调用,它与一般的过程调用的关键差别是:一般的过程调用运行在用户态,系统调用运行在系统态。系统调用的主要目的是使用户可以使用操作系统提供的有关输入/输出管理、文件系统和进程控制、通信以及存储管理等方面的功能,而不必了解系统内部程序的结构和有关硬件细节,从而保护系统、减轻用户负担、提高资源利用率。
1.系统调用类型
每个操作系统提供的系统调用数量和类型有所不同,但是它们的概念是类似的,种类也大致相同。下面以UNIX操作系统为例,介绍主要几类系统调用。
(1)有关设备管理的系统调用
用户使用这些系统调用对有关设备进行读写和控制。例如,系统调用read和write用来对指定设备进行读和写,系统调用open和close用来打开和关闭某一指定设备。
(2)有关文件系统的系统调用
这一类系统调用是用户使用最为频繁的系统调用,也是种类较多的系统调用,包括文件的打开(open)、关闭(close)、读(read)、写(write)、创建(create)和删除(unlink)等,还包括文件的执行(execl)、控制(fnctl)、加解锁(flock)、文件状态获取(stat)和安装文件系统(mount)等。
(3)有关进程控制的系统调用
进程控制是操作系统的核心任务,系统也允许用户进行必要的进程控制,有关进程控制的系统调用很多,常用的有创建进程(fork())、阻塞当前执行进程(wait())、终止进程(exit())、获得进程标识符(getpid())、获得进程优先级(getpriority())、暂停进程(pause())、管道调用(pipe())等,还有两个常用的实用程序睡眠(sleep(n))和互斥(lockf(fd,mode,size)),睡眠(sleep(n))使当前执行进程睡眠n秒,互斥(lockf(fd,mode,size))将文件fd指定的区域进行加锁或解锁,以解决临界资源的竞争问题。
(4)有关进程通信的系统调用
主要包括套接字的建立、连接、控制、删除以及进程间通信的消息队列、同步机制的建立、连接、控制、删除等。
(5)有关存储管理的系统调用
主要包括获取内存现有空间大小、检查内存中现有进程以及内存区的保护和改变堆栈的大小等。
(6)管理用的系统调用
例如,设置和读取日期和时间,获取用户和主机的标识符等系统调用。
系统提供的系统调用越多,功能就越强,用户使用起来就越方便灵活。
2.系统调用使用方式
一般系统调用以标准实用子程序形式提供给用户在编程中使用,从而减少用户程序设计和编程的难度和时间。目前很多用户编程时使用的一些库函数,就是简化了系统调用的接口,但是库函数也要通过系统调用获得操作系统的服务。
用户在不同层次编程,使用系统调用的方式也是不同的。底层编程,使用汇编语言时,系统调用是作为汇编语言的指令使用的,系统调用会在程序员手册中列出;使用一般高级语言进行顶层应用编程时,系统调用通常以函数调用的形式出现(如C语言、PASCAL语言);而在使用面向对象编程语言时,系统调用都封装为类的方法。这里给出一个C语言使用系统调用的例子。
【例2-5】使用C语言,调用操作系统的系统调用fork()创建进程,并且打印活动进程号。
以下是程序片段。
该C程序直接调用系统调用fork()。fork()的功能是创建新进程,如果创建成功,分配处理机,其返回值有以下3个。
·—1:若创建进程失败,返回—1;
·0:创建进程成功,处理机分配给新创建的子进程;
·0:创建进程成功,处理机分配给当前运行的父进程。
各个进程的进程号是调用系统调用getppid()获得的。
3.系统调用的实现机制
尽管系统调用形式与普通的函数调用相似,但是系统调用的实现与一般过程调用的实现相比有很大差异。这主要是系统调用实现机制造成的。该机制由“中断与陷入硬件机构”和“中断与陷入处理程序”两部分组成。
(1)中断与陷入硬件机构
先看什么是中断与陷入,中断是指CPU对系统发生某件事时的一种响应,中断发生时,CPU暂停正在执行的进程,保护现场后转去执行该事件的中断处理程序,执行完成后返回被中断的程序继续执行。中断分为内中断和外中断,外中断指外部设备引起的中断,如打印机中断、时钟中断;而内中断指由CPU内部事件引起的中断,如程序出错等,内中断又称陷入(trap)。中断与陷入的硬件机构完成暂停执行进程、保护现场、转入中断或陷入程序一系列工作。系统调用通过一条陷入指令实现,该指令是一条机器硬件指令,其操作数部分对应于系统调用号。
(2)中断与陷入处理程序
中断与陷入处理程序就是系统调用的处理程序,又称为系统调用子程序,系统调用的功能主要由它来实现。系统调用子程序由系统调用号来标明入口,在系统中有一张系统调用入口表,用来指示各系统调用处理程序的入口地址,从而,只要把系统调用的编号与系统调用入口表中处理程序入口地址对应起来,当用户调用系统调用时,系统就可以通过陷入指令而找到并执行有关的处理程序,以完成系统调用的功能。
4.系统调用实现过程
系统调用实现过程分为以下3步。
首先,中断与陷入硬件机构将处理机状态由用户态转为系统态,保护被中断进程的CPU环境,然后将用户定义的参数传送到指定的地方保存起来。
其次,分析系统调用类型,按照陷入号,根据系统调用入口表,转入系统调用子程序(中断与陷入处理程序),系统调用子程序执行,完成相应功能。
最后,在系统调用子程序执行完后,返回被中断进程或新进程,继续执行。
【例2-6】在C程序中执行一个向已打开文件写一批数据的任务,显然C程序中要执行系统调用。
…
r f lag=read(fd,buf,count);
…
这条语句被编译后形成的汇编指令如下。
trap 4
参数1
参数2
参数3
k1:…
其中参数1、2、3分别对应C语句中的文件描述符fd,读出数据存放地址指针buf,读出字节数count。完成这个系统调用的步骤如下。
①CPU执行到trap 4指令时,产生陷入事件,硬件做出中断响应:保留该进程现场;程序控制转向一段核心码,即中断与陷入处理程序,将进程状态由用户态改为系统态。
②根据系统调用号4查找系统调用入口表,得到相应系统调用子程序入口地址。
③转入文件系统。根据文件描述符fd,查找目录,找到文件所在物理设备。
④启动设备驱动程序,将设备上文件fd读入缓冲区。
⑤启动CPU调度进程,如果选择本例的读文件进程执行,则恢复该进程现场,继续执行。否则执行新进程。
整个过程如图2-45所示。
图2-45 系统调用执行过程