扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
部分摘自《Linux System Programming 》作者: Robert Love
刘建文略译
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本身是抽象的,它仅定义了一个接口,不涉入应用程序如何实现的细节。
系统论里的接口范畴
接口或者端口是两子系统边界[信息交换]的规格或约定方式,用通俗的理解就是,信息是什么样的。接口是信息的格式。
应用编程接口,就是软件系统不同组成部分衔接的约定。由于近年来软件的规模日益庞大,常常会需要把复杂的系统划分成小的组成部分,编程接口的设计十分重要。程序设计的实践中,编程接口的设计首先要使系统的职责得到合理划分。良好的接口设计可以降低系统各部分的相互依赖,提高组成单元的内聚性,降低组成单元间的耦合程度,从而提高系统的维护性和扩展性。
由于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等。(nakeman: http://blog.csdn.net/keminlau/archive/2009/11/26/4874732.aspx)
1.4 Linux编程概念、POSIX和系统库
所有的Unix系统,包括Linux系统,都提供了一个共同的抽象和接口集合,这个共同点定义了Unix 。如对文件和进程的抽象、管道和套接字管理的接口等等,都是Unix的核心内容。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者