部分摘自《Linux System Programming 》作者: Robert Love
刘建文略译(http://blog.csdn.net/keminlau)
过去的Unix编程是没有系统不系统之分的。
即便是开发 X Window也是在系统级(system-level)编程,看到系统的全部API。现代的操作系统编程有所谓[系统级编程],使用与[应用编程]不同的API(System programming API) 。
从编程的形式和耗费心力上。系统编程与应用编程没有本质差别,这也意味着一个经验丰富的应用程序猿转向系统编程难度不大。
只是,这并不能说或者预言系统编程的末日的到来。由于,有人用Javascript或C#写应用就要有人写它的解释器和执行时。
此外,操作系统代码仅仅能使用系统级编程。
本书的一些核心问题:
系统级接口(system-level interface)究竟是什么?又怎样编写Linux的系统级应用?
内核和C库详细提供了什么给我们?
怎样编写优质代码?Linux又有什么已知的陷阱(tricks )?
Linux的系统调用是怎样实现的?
What neat system calls are provided in Linux compared to other Unix variants?
How does it all work? Those questions are at the center of this book.
[系统调用]就是用户空间与内核之间的函数接口,目的是为了给用户空间的程序请求内核服务和资源。与其他非常多操作系统相比。Linux实现的系统调用少非常多。比方,Linux为I386体系实现了300个左右的系统调用。而 Microsoft Windows据说实现数以千计的系统调用。
Linux内核的不同平台实如今系统调用上存在差异,只是90%的系统调用是同样的。
Invoking system calls
出于安全性等因素,应用代码是不可以直接调用系统调用的。必须使用特殊的[陷入](trap)机制。一种“知会”内核进行工作的函数调用形式(KEMIN:证明两“系统”的耦合度较弱,比直接调用方式要弱。以系统论的角度考究syscall也非常有意思)。陷入机制的详细实现也是因不同的体系而有所不同的。
比方I386体系,应用代码通过触发软中断指令(int 0x80)来调用syscall。那0x80是什么呢?软件中断向量号吗?回答否。
应用代码必须通过处理器的寄存器向告诉内核向量号和调用參数。比方。假设应用代码调用open(),它得置eax值5,然后把參数放在另外的五个寄存器:ebx, ecx, edx, esi, 和edi(所以系统调用至多使用五个參数),这些寄存器保存实用户空间的地址,也就是參数数据所在。
作为一位系统程序猿,你一般不需干涉系统调用的过程,由于调用过程由体系定义。而且由C库和C编译器自己主动处理。
C库是全部UINX应用的核心。由于不管你使用什么语言,你的代码终于还是调用C库。其他高级语言的库都是基于C库构建的,或者说是这些库是对C库的包装。
如今的Linux,使用的C库是GNU libc。行话glibc。
glibc不不过个程序语言库,比方C标准库。它还是一个系统库,并且是一个现代操作系统库,函数涵盖了对系统调用的包装、线程支持、网络支持等。
Linux的C编译器是gcc,过去gcc代表GNU C Compiler,是cc的在GNU项目的实现;如今gcc代表GNU Compiler Collection,只是gcc 仍然是C编译器的入口。
Unix系统(包含Linux)使用的编译器与系统编程是高度相关的。由于编译器负责实现了C标准和系统ABI。
无人不希望自己写的程序具有非常好的移植性(portability)。能够执行在不同软件平台(如操作系统或应用框架)、硬件平台(如处理器体系及载板),甚至跨平台的开发版本号执行。
有多种因素影响着程序的可移植性。其中就有两组不同的[系统接口]影响程序的可移植性:第一组是应用编程接口(API),还有一组是应用二进制接口(ABI)。
APIs
API是两支软件在源码级的接口。通过这个标准的接口(一般以函数形式实现),客户代码(一般称高级别的软件代码)能够调用服务代码(低级别的软件代码)。API本身是抽象的,它仅定义了一个接口,不涉入应用程序怎样实现的细节。
系统论里的接口范畴
接口或者port是两子系统边界[信息交换]的规格或约定方式,用通俗的理解就是。信息是什么样的。接口是信息的格式。
应用编程接口。就是软件系统不同组成部分衔接的约定。
程序设计的实践中,编程接口的设计首先要使系统的职责得到合理划分。良好的接口设计能够减少系统各部分的相互依赖。提高组成单元的内聚性,减少组成单元间的耦合程度,从而提高系统的维护性和扩展性。
因为API是抽象的,必须清晰差别接口定义与接口的实现。比方[C标准库]是API,uclibc是一个实现;POSIX 是API,glibc是一个实现。
那么API一般涵盖什么样的函数呢? 这是一个非常有意思的问题。比方C标准库是一种语言库,它必须非常通用。所以接口函数不能依赖软件或硬件特性;相反POSIX 是操作系统标准,它相对没那么的通用。
ABIs
API是源码级别接口,是逻辑约定;而ABI是二进制级别接口,定义的在特定的架构上两个软件模块之间的接口的物理实现方式。这样的[物理实现约定]保证二进制代码兼容,也就是保证一段目标代码可以在不论什么具有相同ABI的系统上都正常运作,不须要又一次编译源码。
ABI([物理实现约定]) 的内容包含调用约定(calling conventions)、字节序(byte ordering)、寄存器使用(register use)、系统调用实现方式、对象链接、库行为和二进制格式。以调用规则为例,它规定了函数怎样被调用。參数怎样传递,哪些寄存器被保留和哪些会被破坏,以及调用者怎样提取返回的结果。
虽然以前尝试着为特定架构下不同的操作系统(特别是i386上的Unix操作系统)定义唯一的ABI,然而到眼下为止还没有取得成效。相反,包含Linux在内的操作系统都尝试定义各自独立的ABI,这些ABI和架构紧密相连。
大部分的ABI涉及了机器级别的概念,如特定的寄存器或者汇编指令。
因此。在Linux系统中。每个机器架构都有自己的ABI集合,其实,我们以机器架构的名称来称呼这些ABI,比如alpha x86-64等。
全部的Unix系统,包含Linux系统。都提供了一个共同的抽象和接口集合,这个共同点定义了Unix。
如对文件和进程的抽象、管道和套接字管理的接口等等,都是Unix的核心内容。
综上,个人理解,系统编程与应用编程的不同在于:调用的接口不同。