KEY:系统论 系统编程
System Programming
过去的Unix编程是没有系统不系统之分的。
即便是开发 X Window也是在系统级(system-level)编程,看到系统的全部API。现代的操作系统编程有所谓[系统级编程],使用与[应用编程]不同的API(System programming API) 。
从编程的形式和耗费心力上。系统编程与应用编程没有本质差别,这也意味着一个经验丰富的应用程序猿转向系统编程难度不大。
系统编程与应用编程的不同在于:
- 第一,系统编程更接近硬件。系统程序猿必须熟悉硬件环境和操作系统环境;相对的,应用程序猿很多其它是熟悉应用的环境;
- 第二,系统编程使用的函数库和库函数调用方法与应用编程有一些不同。比方,在调用系统调用(syscall)时使用所谓的陷入方式,也就是软中断方式。
近年来,随计算应用深化,应用编程有远离系统编程的趋向。
只是,这并不能说或者预言系统编程的末日的到来。由于,有人用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系统编程须要熟悉三大块内容:系统调用、C库和C编译器。
System Calls
[系统调用]就是用户空间与内核之间的函数接口,目的是为了给用户空间的程序请求内核服务和资源。与其他非常多操作系统相比。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编译器自己主动处理。
The C Library
C库是全部UINX应用的核心。由于不管你使用什么语言,你的代码终于还是调用C库。其他高级语言的库都是基于C库构建的,或者说是这些库是对C库的包装。
如今的Linux,使用的C库是GNU libc。行话glibc。
glibc不不过个程序语言库,比方C标准库。它还是一个系统库,并且是一个现代操作系统库,函数涵盖了对系统调用的包装、线程支持、网络支持等。
The C Compiler
Linux的C编译器是gcc,过去gcc代表GNU C Compiler,是cc的在GNU项目的实现;如今gcc代表GNU Compiler Collection,只是gcc 仍然是C编译器的入口。
Unix系统(包含Linux)使用的编译器与系统编程是高度相关的。由于编译器负责实现了C标准和系统ABI。
APIs and ABIs
无人不希望自己写的程序具有非常好的移植性(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等。
1.4 Linux编程概念、POSIX和系统库
全部的Unix系统,包含Linux系统。都提供了一个共同的抽象和接口集合,这个共同点定义了Unix。
如对文件和进程的抽象、管道和套接字管理的接口等等,都是Unix的核心内容。
综上,个人理解,系统编程与应用编程的不同在于:调用的接口不同。