第5章系统调用(一)

在现代操作系统中,内核提供了用户进程与内核交互的一组接口。这些接口让应用程序受限地访问硬件设备,提供了创建新进程并与已有进程进行通信的机制,也提供了申请操作系统其它资源的能力。这些接口在应用程序和内核之间扮演了使者的角色,应用程序发出各种请求,而内核负责满足这些请求。

5.1 与内核通信

系统调用在用户空间进程和硬件设备之间添加了一个中间层。该层主要作用有三个。首先,为用户空间提供一种硬件的抽象接口。例如,当读文件时,应用程序可以不管磁盘类型和介质,甚至不用去管文件所在的文件系统是哪种类型。第二,系统调用保证系统的稳定性和安全。作为硬件设备和应用程序之间的中间人,内核可以基于权限、用户类型和其他一些规则对需要进行的访问进行裁决。例如,这样可以避免应用程序不正确地使用硬件设备,窃取其他进程资源,或做出其他危害系统的事情。第三,每个进程都运行在虚拟系统中,而在用户空间和系统的其余部分提供这样一层公共接口,也是出于这种考虑。如果应用程序可以随意访问硬件而内核一无所知的话,几乎就没法实现多任务和虚拟内存,也不可能实现良好的稳定性和安全性。在Linux中,系统调用是用户空间访问内核的唯一手段;除异常和陷入外,是内核唯一的合法入口。实际上,其他的像设备文件和/proc之类的方式,最终也是通过系统调用进行访问的。

5.2 API、POSIX(可移植操作系统接口)和C库

一般情况下,应用程序通过在用户空间实现的应用编程接口来编程。因为应用程序使用的这种编程接口实际上并不需要和内核提供的系统调用对应。一个API定义了一组应用程序使用的编程接口。它们可以实现成一个系统调用,也可以通过调用多个系统调用来实现,而完全不使用任何系统调用也不存在问题。API可以在各种不同的操作系统上实现,给应用程序提供完全相同的接口,而它们本身在这些系统上的实现却可能不同。图5-1给出POSIX、API、C库以及系统调用之间的关系。

在Unix中,应用编程接口是基于POSIX(可移植操作系统接口)标准的。从技术角度来看,POSIX是由IEEE的一组标准组成,其目标是提供一套大体上基于Unix的可移植操作系统标准。POSIX是说明API和系统调用之间关系的一个极好例子。在大多数Unix系统上,根据POSIX定义的API函数和系统调用之间有着直接关系。

Linux的系统调用像大多数Unix系统一样,作为C库的一部分提供。C库实现了Unix系统的主要API,包括标准C库函数和系统调用接口。所有的C程序都可以使用C库,此外,C库提供了POSIX的绝大部分API。

从程序员角度来看,系统调用无关紧要,只需要跟API打交道就可以了。内核只跟系统调用打交道;库函数及应用程序怎么使用系统调用,不是内核关心的。关于Unix的接口是提供机制而不是策略。Unix的系统调用抽象出了用于完成某种确定的目的函数。至于这些函数怎么用完全不需要内核去关心。

5.3系统调用

要访问系统调用,通常通过C库中定义的函数调用来进行。通常都需要定义0个、1个或几个参数(输入)而且可能产生一些副作用。系统调用还会通过一个long类型的返回值来表示成功或者错误。通常,但也不绝对,用一个负的返回值来表明错误。返回一个0值通常表明成功。系统调用在出现错误的时候C库会把错误码写入errno全局变量。通过调用perror()库函数,可以把该变量翻译成用户可以理解的错误字符串。

系统调用具有一种明确的操作。例如getpid()系统调用,根据定义会返回当前进程的PID。内核中的实现如下:

kernel/timer.c

/**
 * sys_getpid - return the thread group id of the current process
 *
 * Note, despite the name, this returns the tgid not the pid.  The tgid and
 * the pid are identical unless CLONE_THREAD was specified on clone() in
 * which case the tgid is the same in all threads of the same group.
 *
 * This is SMP safe as current->tgid does not change.
 */

SYSCALL_DEFINE0(getpid)
{
        return task_tgid_vnr(current);
}

注意:SYSCALL_DEFINE0只是一个宏,定义一个无参数的系统调用(因此这里数字为0),展开后的代码如下:

include/linux/syscalls.h

asmlinkage long sys_getpid(void);//函数原型声明

如何定义系统调用。首先,函数声明中的asmlinkage限定词,是一个编译指令,通知编译器仅从栈中提取该函数的参数。其次,函数返回long。为了保证32位和64位系统的兼容,系统调用在用户空间和内核空间有不同的返回值类型,在用户空间为int,在内核空间为long。最后,系统调用getpid()在内核中被定义为sys_getpid()。这是Linux中所有系统调用都应该遵守的命名规则,系统调用bar()在内核中也实现为sys_bar()函数。

猜你喜欢

转载自blog.csdn.net/xiezhi123456/article/details/81134363