Skip to content

Latest commit

 

History

History
284 lines (186 loc) · 15 KB

File metadata and controls

284 lines (186 loc) · 15 KB

一、系统编程导论

本章是对系统编程的介绍,探讨了一系列主题,从最初的定义到它是如何随着系统演化而随时间变化的。本章提供一些基本概念和 Unix 及其资源的概述,包括内核和应用程序编程接口API)。这些概念中的许多在这里定义,并在本书的其余部分中使用。

本章将介绍以下主题:

  • 什么是系统编程?
  • 应用程序编程接口
  • 了解保护环的工作原理
  • 系统调用概述
  • POSIX 标准

技术要求

如果您在 Linux 上,本章不要求您安装任何特殊软件。

如果您是 Windows 用户,您可以为 Linux 安装Windows 子系统WSL。按照以下步骤安装 WSL:

  1. 以管理员身份打开 PowerShell 并运行以下操作:
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
  1. 出现提示时重新启动计算机。
  2. 从 Microsoft 应用商店安装您喜爱的 Linux 发行版。

从系统编程开始

多年来,IT 领域发生了巨大的变化。挑战冯·诺依曼机器、互联网和分布式系统的多核 CPU 只是过去 30 年中发生的一些变化。那么,系统编程在这种情况下处于什么地位呢?

软件换软件

让我们先从标准教科书的定义开始。

系统编程(或系统编程)是对计算机系统软件进行编程的活动。与应用程序编程相比,系统编程的主要区别在于,应用程序编程旨在生成直接向用户提供服务的软件(例如,字处理器),鉴于系统编程旨在生成为其他软件提供服务的软件和软件平台,并设计为在性能受限的环境中工作,例如操作系统、计算科学应用程序、游戏引擎和 AAA 视频游戏、工业自动化和软件即服务应用程序。

该定义强调了系统应用程序的两个主要概念,如下所示:

  • 其他软件使用的软件,而不是最终用户直接使用的软件。
  • 软件是硬件感知的(它知道硬件如何工作),并且面向性能。

这使得人们可以轻松地将操作系统内核、硬件驱动程序、编译器和调试器识别为系统软件,而不是系统软件、聊天客户端或文字处理器。

历史上,系统程序是使用汇编和 C 语言创建的。然后出现了 shell 和脚本语言,用于将系统程序提供的功能结合在一起。系统语言的另一个特点是控制内存分配。

语言与系统演化

在过去的十年中,脚本语言越来越流行,一些脚本语言的性能有了显著的提高,整个系统都是用它们构建的。例如,让我们考虑一下 JavaScript 的 V8 引擎和 Python 的 PyPy 实现,它们极大地改变了这些语言的性能。

其他语言(如 Go)证明了垃圾收集和性能并不是相互排斥的。特别是,Go 设法用 Go 在 1.5 版中编写的本机版本替换了自己用 C 编写的内存分配器,将其性能提高到了可比的程度。

与此同时,系统开始被分发,应用程序开始被装入容器中,由其他系统软件(如Kubernetes)协调。这些系统旨在维持巨大的吞吐量,并通过两种主要方式实现:

  • 通过扩展来增加托管系统的计算机的数量或资源
  • 通过优化软件以提高资源效率

系统编程与软件工程

在构建分布式系统时,系统编程的一些实践(例如,将应用程序绑定到硬件、面向性能以及在资源受限的环境中工作)也是一种有效的方法,其中,限制资源使用可以减少所需的实例数。看起来系统编程是解决一般软件工程问题的一种好方法。

这意味着,在构建任何类型的应用程序时,学习有关有效使用机器资源(从内存使用到文件系统访问)的系统编程概念将非常有用。

应用程序编程接口

API 是用于构建软件的系列子例程定义、通信协议和工具。API 最重要的方面是它提供的功能,以及它的文档,这有助于用户在另一个软件中使用和实现软件本身。API 可以是允许应用软件使用系统软件的接口。

API 通常有一个特定的发布策略,用于特定的收件人组。这可以是以下内容:

  • 私人及仅供内部使用
  • 合作伙伴,仅可由确定的组使用。这可能包括希望将服务与其集成的公司
  • 公共和可供每个用户使用

API 的类型

我们将看到有几种类型的 API,从用于使不同应用程序软件协同工作的 API,到操作系统向其他软件公开的内部 API。

操作系统

API 可以指定如何连接应用程序和操作系统。例如,Windows、Linux 和 macOS 都有一个接口,使操作文件系统和文件成为可能。

图书馆和框架

与软件库相关的 API 描述并规定(提供如何使用它的说明)每个元素的行为,包括最常见的错误场景。API 的行为和接口通常被称为库规范,而库是该规范中描述的规则的实现。库和框架通常是语言绑定的,但是有一些工具可以使用不同语言的库。您可以使用 CGO 在 Go 中使用 C 代码,在 Python 中可以使用 CPython。

远程 API

这使得使用特定的通信标准来操作远程资源成为可能,这些标准允许不同的技术协同工作,而不考虑语言或平台。一个很好的例子是Java 数据库连接JDBC)API,它允许使用相同的函数集查询许多不同类型的数据库,或者 Java 远程方法调用 API(Java RMI),它允许像使用本地函数一样使用远程函数。

Web API

Web API 是定义一系列关于所用协议、消息编码和可用端点及其预期输入和输出值的规范的接口。这种 API-REST 和 SOAP 有两种主要模式:

  • REST API 具有以下特征:
    • 他们将数据视为一种资源。
    • 每个资源都由一个 URL 标识。
    • 操作类型由 HTTP 方法指定。
  • SOAP 协议具有以下特征:
    • 它们由 W3C 标准定义。
    • XML 是用于消息的唯一编码。
    • 他们使用一系列 XML 模式来验证数据。

理解保护环

保护环又称层次保护域,是用于保护系统不发生故障的机制。它的名称来源于其权限级别的层次结构,由同心环表示,当移动到外圈时,权限会减少。每个环之间都有特殊的门,允许外圈以受限的方式访问内圈资源。

建筑差异

环的数量和顺序取决于 CPU 体系结构。它们通常以递减权限进行编号,使环 0 成为最具权限的环。这适用于使用四个环(从环 0 到环 3)的 i386 和 x64 体系结构,但不适用于使用相反顺序(从 EL3 到 EL0)的 ARM。大多数操作系统没有使用所有四个级别;它们最终使用两级层次结构用户/应用程序(环 3)和内核(环 0)。

内核空间和用户空间

在操作系统下运行的软件将在用户(环 3)级别执行。为了访问机器资源,它必须与操作系统内核(在环 0 上运行)交互。下面列出了 ring 3 应用程序无法执行的一些操作:

  • 修改确定当前环的当前段描述符
  • 修改页表,防止一个进程看到其他进程的内存
  • 使用 LGDT 和 LIDT 指令,防止它们注册中断处理程序
  • 使用 I/O 指令,如 in 和 out,可以忽略文件权限并直接从磁盘读取

例如,对磁盘内容的访问将由内核进行中介,内核将验证应用程序是否具有访问数据的权限。这种协商可以提高安全性并避免失败,但会带来影响应用程序性能的重要开销。

有些应用程序可以设计为直接在硬件上运行,而无需操作系统提供的框架。这对于实时系统来说是正确的,在响应时间和性能方面没有任何妥协。

潜入系统调用

系统调用是操作系统为应用程序提供对资源的访问的方式。它是由内核实现的用于安全访问硬件的 API。

提供的服务

我们可以使用一些类别来划分操作系统提供的众多功能。其中包括对正在运行的应用程序及其流的控制、文件系统访问和网络。

过程控制

这类服务包括load,将程序添加到内存中,并在将控制权传递给程序本身之前准备执行;或execute,在预先存在的进程上下文中运行可执行文件。属于此类别的其他操作如下所示:

  • endabort-第一个要求应用程序退出,第二个强制退出。
  • CreateProcess,在 Unix 系统中也称为fork,在 Windows 系统中也称为NtCreateProcess
  • 终止进程。
  • 获取/设置进程属性。
  • 等待时间、等待事件或信号事件。
  • 分配和释放内存。

文件管理

文件和文件系统的处理属于文件管理系统调用。有创建删除文件,可以在文件系统中添加或删除条目;有openclose操作,可以控制文件以执行读写操作。还可以读取和更改文件属性。

设备管理

设备管理处理除文件系统以外的所有其他设备,如帧缓冲区或显示。它包括从设备请求开始的所有操作,包括与设备之间的通信(读、写、寻道)及其释放。它还包括更改设备属性以及逻辑附加和分离设备属性的所有操作。

信息维护

读取和写入系统日期和时间属于信息维护类别。此类别还负责其他系统数据,如环境。属于这里的另一组重要操作是进程、文件和设备属性的请求和操作。

表达

从处理套接字到接受连接的所有网络操作都属于通信类。这包括连接的创建、删除和命名,以及s结束和接收消息。

操作系统之间的差异

Windows 有一系列不同的系统调用,涵盖所有内核操作。其中许多与 Unix 等价物完全对应。以下是一些重叠系统调用的列表:

| | 视窗 | Unix | | 过程控制 | CreateProcess() ExitProcess() WaitForSingleObject() | fork() exit() wait() | | 文件操作 | CreateFile() ReadFile() WriteFile() CloseHandle() | open() read() write() close() | | 文件保护 | SetFileSecurity() InitializeSecurityDescriptor() SetSecurityDescriptorGroup() | chmod() umask() chown() | | 设备管理 | SetConsoleMode() ReadConsole() WriteConsole() | ioctl() read() write() | | 信息维护 | GetCurrentProcessID() SetTimer() Sleep() | getpid() alarm() sleep() | | 通讯 | CreatePipe() CreateFileMapping() MapViewOfFile() | pipe() shmget() mmap() |

理解 POSIX 标准

为了确保操作系统之间的一致性,IEEE 正式制定了一些操作系统标准。以下各节将对其进行说明。

POSIX 标准和特性

Unix 的便携式操作系统接口POSIX)代表了一系列操作系统接口标准。第一个版本可以追溯到 1988 年,涵盖了一系列主题,如文件名、shell 和正则表达式。

POSIX 定义了许多特性,它们被组织在四个不同的标准中,每个标准都关注 Unix 遵从性的不同方面。它们都被命名为 POSIX,后跟一个数字。

POSIX.1–核心服务

POSIX.1 是 1988 年的原始标准,最初命名为 POSIX,但后来重新命名,以便在不放弃名称的情况下为该系列添加更多标准。它定义了以下功能:

  • 过程创建和控制
  • 信号:
  • 浮点异常
  • 分段/内存冲突
  • 非法指令
  • 总线错误
  • 计时器
  • 文件和目录操作
  • C 库(标准 C)
  • I/O 端口接口和控制
  • 进程触发器

POSIX.1b 和 POSIX.1c–实时和线程扩展

POSIX.1b 专注于实时应用程序和需要高性能的应用程序。它着重于以下几个方面:

  • 优先级调度
  • 实时信号
  • 钟表
  • 信号量
  • 消息传递
  • 共享内存
  • 异步和同步 I/O
  • 内存锁定接口

POSIX.1c 引入了多线程范例,并定义了以下内容:

  • 线程创建、控制和清理
  • 线程调度
  • 线程同步
  • 信号处理

POSIX.2–壳牌和公用设施

POSIX.2 将命令行解释器和实用程序的标准指定为cdechols

操作系统依从性

并非所有的操作系统都与 POSIX 兼容。例如,Windows 是在标准之后诞生的,它不符合标准。从认证的角度来看,macOS 比 Linux 更兼容,因为后者使用另一种构建在 POSIX 之上的标准。

Linux 和 macOS

大多数 Linux 发行版遵循Linux 标准库LSB),这是另一个包含 POSIX 和更多内容的标准,重点是维护不同 Linux 发行版之间的互兼容性。由于开发人员没有进入认证过程,因此它不被认为是正式合规的。

然而,macOS 在 2007 年与雪豹发行版完全兼容,并从那时起通过 POSIX 认证。

窗户

Windows 不符合 POSIX 标准,但有许多尝试使其符合 POSIX 标准。有一些开源项目,如 Cygwin 和 MinGW,它们提供了一个不太符合 POSIX 的开发环境,并使用 Microsoft Visual C 运行时库支持 C 应用程序。微软自己也在 POSIX 兼容性方面做了一些尝试,比如微软 POSIX 子系统。微软最新推出的兼容层是 Windows Linux 子系统,这是一个可选功能,可以在 Windows 10 中激活,并且受到了开发人员(包括我自己)的欢迎。

总结

在本章中,我们了解了系统编程意味着编写具有一些严格要求的系统软件,例如与硬件绑定、使用低级语言以及在资源受限的环境中工作。当构建通常需要优化资源使用的分布式系统时,它的实践可能非常有用。我们讨论了 API,即允许其他软件使用软件的定义,并列出了不同类型的 API—操作系统中的 API、库和框架,以及远程 API 和 web API。

我们分析了在操作系统中,如何在称为保护环的分层级别上安排对资源的访问,以防止不受控制的使用,从而提高安全性并避免应用程序出现故障。Linux 模型将这个层次结构简化为两个级别,分别称为用户内核空间。所有的应用程序都在用户空间中运行,为了访问机器的资源,它们需要内核进行干预。

然后我们看到了一种称为系统调用的特定类型的 API,它允许应用程序向内核请求资源,并调解进程控制、文件访问和管理以及设备和网络通信。

我们概述了定义 Unix 系统互操作性的 POSIX 标准。在定义的功能中,还有 C API、CLI 实用程序、shell 语言、环境变量、程序退出状态、正则表达式、目录结构、文件名和命令行实用程序 API 约定。

在下一章中,我们将探讨 Unix 操作系统资源,如文件系统和 Unix 权限模型。我们将了解什么是流程,它们如何相互通信,以及它们如何处理错误。

问题

  1. 应用程序和系统编程之间的区别是什么?
  2. 什么是 API?为什么 API 如此重要?
  3. 你能解释一下保护环是怎么工作的吗?
  4. 你能举例说明在用户空间中不能做的事情吗?
  5. 什么是系统调用?
  6. Unix 中使用哪些调用来管理进程?
  7. 为什么 POSIX 有用?
  8. Windows POSIX 兼容吗?