可移植操作系统接口-POSIX
2022-08-25 14:32:18 阿炯

POSIX(Portable Operating System Interface for Unix,可移植操作系统接口)是一个由 IEEE(电气和电子工程师协会)制定的标准,旨在确保不同的 Unix 系统能够具有统一的接口,使得程序能够在不同的系统之间移植,而无需进行大量修改。POSIX 标准为 Unix 操作系统及其兼容系统提供了一套应用程序编程接口(API),它定义了系统调用、命令行工具和脚本编程规范等内容。是IEEE为要在各种UNIX操作系统上运行软件而定义API的一系列互相关联的标准的总称,其正式称呼为IEEE Std 1003,而国际标准名称为ISO/IEC 9945。此标准源于一个大约开始于1985年的项目。POSIX这个名称是由理查德·斯托曼(RMS)应IEEE的要求而提议的一个易于记忆的名称。它基本上是Portable Operating System Interface(可移植操作系统接口)的缩写,而X则表明其对Unix API的传承。

POSIX 可移植操作系统接口:Portable Operating System Interface of UNIX。



Linux基本上逐步实现了POSIX兼容,但并没有参加正式的POSIX认证。微软的Windows NT声称部分实现了POSIX标准。

POSIX是由IEEE指定的一系列标准,用于澄清和统一Unix操作系统提供的应用程序编程接口(以及辅助问题,如命令行shell实用程序)。当编写程序以依赖POSIX标准时,可以非常轻松地将它们移植到大量的Unix衍生产品系列中(包括Linux,但不限于此)。如果使用的某些Linux API没有标准化为Posix的一部分,那么将该程序或库移植到其他泛Unix系统(例如MacOSX)就会有一些难度。POSIX标准定义了操作系统应该为应用程序提供的接口标准;调用了符合POSIX标准的API的应用程序可以确保在不同的系统上使用;POSIX则是操作系统为应用程序提供系统调用的接口规范。

设计目标

跨平台兼容性
通过统一的接口,POSIX 使得程序能够在不同的 Unix 系统上运行,而无需做太多修改。比如,一个遵循 POSIX 标准的程序,可以在 Linux、macOS 或其他 POSIX 兼容的系统中运行。

提高可移植性
POSIX 标准的目的是让程序员不必考虑底层操作系统的实现细节,只需要遵循该规范,就能确保应用在不同的操作系统上行为一致。

系统一致性
POSIX 使得不同操作系统之间的行为趋同,这为开发人员提供了一个一致的开发环境,从而减少了不同操作系统的差异带来的复杂性。

UNIX中最通用的操作系统API基于POSIX标准(Portable Operating System Interface of UNIX 可移植操作系统接口)操作系统API通常以C库的方式提供,C库封装了这些符合POSIX标准的系统调用接口。在UNIX世界里,最通用的操作系统API基于POSIX标准;C POSIX library是C语言的POSIX系统下的标准库。包含了一些在C语言标准库之外的函数。

为了OS之间的可移植性,POSIX标准规定了一些标准的接口。而这些接口标准的集合就是POSIX库。该标准的目的是定义了标准的基于UNIX操作系统的系统接口和环境来支持源代码级的可移植性。现标准主要提供了依赖C语言的一系列标准服务,再将来的版本中,标准将致力于提供基于不同语言的规范。

驱动层对于硬件层来说是硬件接口的使用者;这些硬件接口往往被叫作硬件规格;硬件的规格有很多;相应的驱动程序就要写很多,来适配不同的硬件;这样的话很麻烦,所以一般来说硬件生产厂商要根据一定的接口和规范生产硬件;这样同一套驱动程序可以在不同的硬件上编写;当然根据实际情况还要编写特定的驱动程序。

另外操作系统开发者也为驱动程序提供了一系列接口和框架;按照该接口和框架开发的驱动程序就可以使得操作系统正常在该硬件上运行;硬件也被操作系统抽象了成了一系列的概念:磁盘被抽象成了文件系统;图形硬件被抽象成了GDI;声音和多媒体设备被抽象成了DirectX对象等;程序员从硬件细节中解放出来,关注应用程序开发本身,繁琐的硬件细节则交给操作系统。

POSIX(Portable Operating System Interface for Computing Systems)是由IEEE 和ISO/IEC 开发的一簇标准。该标准是基于现有的UNIX 实践和经验,描述了操作系统的调用服务接口,用于保证编制的应用程序可以在源代码一级上在多种操作系统上移植运行。

目前POSIX已经成为类UNIX(Unix-like)操作系统编程的通用接口,极大方便了类UNIX环境下应用程序源码级的可移植性。Glibc(GNU C Library)即C运行库,是Linux系统中最底层的API,它就是完全按照POSIX标准编写的。

System V 曾经也被称为 AT&T System V,是Unix操作系统众多版本中的一支。其最初由 AT&T 开发,在1983年第一次发布。一共发行了4个 System V 的主要版本:版本1、2、3 和 4。

System V Release 4简称为SVR4,是最成功的版本,成为一些UNIX共同特性的源头。例如 “SysV 初始化脚本” (/etc/init.d),用来控制系统启动和关闭,System V Interface Definition (SVID) 是一个System V 如何工作的标准定义。

照上面所说的System V和POSIX是一种应用于系统的接口协议,POXIS相对于System V可以说是比较新的标准,语法相对简单。在linux/unix系统编程中支持System V和POSIX。常见的一个名词就是POSIX IPC和System V IPC。IPC的全称是Inter-process Comminication,就是进程间通信。

重要组成

1.系统接口

系统接口(System Interface)这部分定义了应用程序如何与操作系统交互。主要包括系统调用(如创建进程、文件操作、进程间通信等)以及信号机制、内存管理、文件描述符等。

文件系统接口:包括打开、关闭、读取、写入、删除文件的操作。POSIX 标准定义了文件的基本操作,确保在不同的系统上行为一致。
进程管理接口:涉及创建、终止、控制进程的操作,尤其是进程间的同步、信号的传递等。
线程管理接口:POSIX 还规定了多线程编程的标准接口(称为 POSIX 线程或 pthreads),使得多线程程序在不同的系统上能统一运行。
输入输出(I/O)接口:POSIX 定义了对设备的访问和数据传输的标准方式,包括标准输入输出(stdin, stdout, stderr)和文件操作接口。

2.命令行接口

POSIX 定义了一个标准的命令行接口,确保用户可以通过 shell 脚本和命令行工具操作操作系统。例如,ls(列出目录)、cp(复制文件)、mv(移动文件)等标准工具,都属于该规范的一部分。

Shell 和脚本:POSIX 定义了一个标准的 shell 环境,标准化了脚本的编写方式,确保脚本可以在不同的系统上执行。POSIX Shell 定义了基本的命令、流程控制、变量等标准。

3.网络接口

POSIX 定义了网络编程的接口,包括套接字(socket)API,它是实现网络通信的重要工具。通过 POSIX 网络接口,程序可以建立、接收、发送网络连接,使得跨平台的网络应用变得可能。

4.进程间通信

POSIX 提供了进程间通信(IPC)的机制,包括信号、管道、共享内存、消息队列、信号量等,帮助不同进程之间交换数据和同步。

5.线程和同步

POSIX 定义了线程的创建、管理和同步机制,称为 POSIX 线程(pthreads)。它使得程序能够创建多线程应用,处理并发任务,且能够在支持 POSIX 的操作系统上实现。


当前的POSIX主要分为四个部分:Base Definitions、System Interfaces、Shell and Utilities和Rationale。

POSIX 1.1标准

1003.0
管理POSIX开放式系统环境(OSE)。IEEE在1995年通过了这项标准。ISO的版本是ISO/IEC 14252:1996。
1003.1
被广泛接受、用于源代码级别的可移植性标准。1003.1提供一个操作系统的C语言应用编程接口(API)。IEEE和ISO已经在1990年通过了这个标准,IEEE在1995年重新修订了该标准。
1003.1b
一个用于实时编程的标准(以前的P1003.4或POSIX.4)。这个标准在1993年被IEEE通过,被合并进ISO/IEC 9945-1。
1003.1c
一个用于线程(在一个程序中当前被执行的代码段)的标准。以前是P1993.4或POSIX.4的一部分,这个标准已经在1995年被IEEE通过,归入ISO/IEC 9945-1:1996。
1003.1g
一个关于协议独立接口的标准,该接口可以使一个应用程序通过网络与另一个应用程序通讯。1996年,IEEE通过了这个标准。

1003.2
一个应用于shell和工具软件的标准,它们分别是操作系统所必须提供的命令处理器和工具程序。1992年IEEE通过了这个标准。ISO也已经通过了这个标准(ISO/IEC 9945-2:1993)。

1003.2d
改进的1003.2标准。

1003.5
一个相当于1003.1的Ada语言的API。在1992年,IEEE通过了这个标准。并在1997年对其进行了修订。ISO也通过了该标准。

1003.5b
一个相当于1003.1b(实时扩展)的Ada语言的API。IEEE和ISO都已经通过了这个标准。ISO的标准是ISO/IEC 14519:1999。

1003.5c
一个相当于1003.1q(协议独立接口)的Ada语言的API。在1998年,IEEE通过了这个标准。ISO也通过了这个标准。

1003.9
一个相当于1003.1的FORTRAN语言的API。在1992年,IEEE通过了这个标准,并于1997年对其再次确认。ISO也已经通过了这个标准。

1003.10
一个应用于超级计算应用环境框架(Application Environment Profile,AEP)的标准。在1995年,IEEE通过了这个标准。

1003.13
一个关于应用环境框架的标准,主要针对使用POSIX接口的实时应用程序。在1998年,IEEE通过了这个标准。

1003.22
一个针对POSIX的关于安全性框架的指南。

1003.23
一个针对用户组织的指南,主要是为了指导用户开发和使用支持操作需求的开放式系统环境(OSE)框架

2003
针对指定和使用是否符合POSIX标准的测试方法,有关其定义、一般需求和指导方针的一个标准。在1997年,IEEE通过了这个标准。

2003.1
这个标准规定了针对1003.1的POSIX测试方法的提供商要提供的一些条件。在1992年,IEEE通过了这个标准。

2003.2
一个定义了被用来检查与IEEE 1003.2(shell和工具API)是否符合的测试方法的标准。在1996年,IEEE通过了这个标准。

除了1003和2003家族以外,还有几个其它的IEEE标准,例如1224和1228,它们也提供开发可移植应用程序的API。

主页:http://get.posixcertified.ieee.org/index.html


在 POSIX 出现之前,不同 Unix 系统(如BSD、System V等)在实现细节上存在显著差异,导致软件开发人员需要为每种系统编写特定的代码来处理这些差异。POSIX 通过定义一套统一的 API 标准,使程序代码可以在不同操作系统之间更容易地移植和复用,避免了操作系统之间的兼容性问题。POSIX标准包含多个部分,涵盖了操作系统的核心功能。主要包括以下几个部分(上文有述):

文件与目录操作:文件创建、删除、读取、写入、权限管理等。

进程控制:进程的创建、终止、同步、通信等。

线程管理:多线程编程接口(如pthread库),包括线程的创建、同步、锁等。

内存管理:共享内存、内存映射(mmap)等。

信号处理:对信号的捕获、屏蔽和处理。

网络通信:套接字(sockets)等网络通信接口。

输入/输出:通用的I/O操作标准,包括标准输入、输出和错误流。

实用工具:标准命令和工具的实现规范(如awk、grep等)。


在此通过系统回顾可移植操作系统接口 (Portable Operating System Interface,POSIX) 抽象的历史演变,提供对它的全面了解。我们讨论了推动演变的一些关键因素,并找出了在构建现代应用程序时导致它们不可行的缺陷。

POSIX 标准[1]定义了类 Unix 操作系统的接口。Unix 是第一个由程序员为程序员编写的操作系统,POSIX 使开发人员能够编写可在不同的 Unix 操作系统变体和指令集体系结构上运行的可移植应用程序。Unix 的主要用例是多路复用存储(文件系统)并为人类提供交互式环境(shell)[2]、[3]。相比之下,许多当代基于 POSIX 的系统的主要用例是在数据中心的机器上运行的服务,这些机器对延迟的要求要低几个数量级。这些服务不能指望随着 CPU 时钟频率的增加而逐年加快,因为 Dennard 缩放定律在 2004 年左右的结束意味着 CPU 时钟频率不再以 Unix 商品化期间普遍存在的速度增加。此外,许多人认为摩尔定律正在放缓,因此软件不能再指望通过增加晶体管密度来推动硬件优化而变得更快。随着我们迈向后摩尔定律计算时代,系统设计人员开始利用快速可编程 NIC、专用硬件加速器和非易失性主存储器等设备来解决应用程序的严格延迟限制。

年份
  抽象示例接口版本
'69文件系统打开、读取、写入V0
'69进程forkV0
'71进程执行V1
'71虚拟内存break1V1
'73管道管道V3
'73信号信号V4
'79信号V7
'79虚拟内存vfork23BSD
'83网络套接字,接收,发送4.2BSD
'83I/O 多路复用select4.2BSD
'83虚拟内存mmap34.2BSD
'83IPCmsgget、semget、shmgetSRV1
'87I/O 多路复用轮询SRV3
'88虚拟内存mmapSunOS 4.0
'93异步 I/Oaio_submitPOSIX.1b
'95线程pthread_createPOSIX.1c

POSIX 抽象和接口的时间线。

这些抽象是在 20 世纪 70 年代到 90 年代之间在不同的 Unix 变体中引入的。文件系统和进程是 V0 中已经存在的基本接口。虚拟内存在 20 世纪 70 年代末的 3BSD 中引入,并在 20 世纪 80 年代的 4.2BSD 和 SunOS 4.0 中完成。20 世纪 80 年代,4.2BSD 添加了网络支持。异步 I/O 和线程在 20 世纪 90 年代的 POSIX 标准中引入。

break系统调用后来重命名为 brk,并添加了另一个变体 sbrk。它们现在都已弃用。

3BSD添加了对基于分页的虚拟内存的支持。他们添加了 vfork 系统调用以避免为 fork 实现写时复制[4]。尽管 mmap 是在 1983 年设计的,但提议的设计在 1986 年才得到了完整实现[5]。

POSIX 的演进

POSIX 的抽象(进程、文件系统、虚拟内存、套接字和线程)基于 20 世纪 70 年代至 80 年代开发的不同 Unix 变体的操作系统抽象,例如 Research Unix、System V、BSD、SunOS 等。

各自时代的用例和硬件功能影响了抽象。例如,早期的 Unix 运行在 PDP-11/20 上,这是一台 16 位计算机,只有一个 CPU,主内存高达 248KB [6]。由于 PDP-11/20 缺乏内存保护,因此与 Multics 等当时的当代操作系统不同,Unix 不支持虚拟内存。尽管后来的 PDP-11 变体(如 PDP-11/70)具有内存映射单元(MMU)[7],但直到 20 世纪 70 年代后期 VAX 架构出现后,虚拟内存才被添加到 Unix 中[4],VAX 架构当时成为 Unix 的主要架构。同样,直到 20 世纪 80 年代初互联网出现后,Unix 才有了网络抽象,当时 4.2BSD 引入了用于远程进程间通信的套接字抽象来抽象 TCP/IP 网络协议。同样,直到 20 世纪 90 年代初期,当多处理器机器成为主流时,Unix 才有了线程抽象[8]。

文件系统

文件系统是访问和组织存储设备上的数据字节的抽象。此抽象及其 I/O 接口主要源自 Multics [9],它被认为是 Unix 中最重要的抽象[2]、[10]。然而,与仅支持同步 I/O 的 Unix 不同,Multics 还支持异步 I/O [11]、[12],这一功能最终成为 POSIX 的一部分。

文件系统抽象还包括文件、目录、特殊文件 [2] 以及硬链接和符号链接[13]。文件系统中的文件是操作系统无法以任何方式解释的字节序列 [2]。这使得操作系统能够将硬件设备表示为特殊文件,而操作文件的接口已成为 I/O 设备的事实接口。

文件系统抽象使 I/O 设备的集成变得容易。然而,它可能成为快速 I/O 设备的瓶颈[14]、[15]、[16]、[17]。

进程

进程是系统中应用程序执行的抽象。具体来说,应用程序表示为一个映像,该映像抽象了其执行环境,其中包括程序代码(文本)、处理器寄存器值和打开的文件[2] 。此映像存储在文件系统中,操作系统确保进程映像的执行部分驻留在内存中。进程抽象自早期 Unix [2]以来就已存在,它对于计算和 I/O 资源的分时共享至关重要。

这种抽象的根源在于多道程序设计,这是 20 世纪 50 年代中期为提高执行 I/O 时的硬件利用率而开发的一种技术[18]。在 PDP-7 上运行的早期 Unix 仅支持两个进程,每个连接到机器的终端一个进程[19];后来设计为在 PDP-11 上运行的 Unix 版本可以在内存中保留多个进程。

进程是一个以处理器为中心的抽象概念,对于假设进程映像的执行仅在 CPU 上完成的应用程序非常有用。然而,图形处理单元 (GPU)、张量处理单元 (TPU) 等硬件设备的普及,以及用于卸载计算的各种其他专用加速器,正在挑战这一假设。

虚拟内存

虚拟内存是一种抽象,它创造了一个与存储空间一样大的内存空间的假象[20]。它源于自动利用主存储器速度和廉价存储容量的需求。虚拟内存的概念可以追溯到 20 世纪 60 年代初:基于页面的虚拟内存于 1962 年在 Atlas Supervisor [21]中首次引入,Multics 也支持虚拟内存[22]。

虚拟内存于 20 世纪 70 年代末加入 Unix,此时距离其诞生已过去了近 10 年。在 Unix 诞生之初,进程地址空间被分为三段:所有进程共享但不可写的程序文本(代码)段、可读写但私有的进程数据段和堆栈段。sbrk 系统调用可以增大和缩小进程数据段。然而,由于需要运行当时需要比主内存容量更大的存储空间的程序(例如 Lisp),VAX-11 架构中的 MMU 使基于分页的虚拟内存成为可能[4] , [23]。

这种抽象将两个相关的概念解耦:地址空间,即用于寻址内存的标识符,和内存空间,即存储数据的物理位置。从历史上看,这种解耦有三个主要目标:(1)通过独立于物理内存空间的地址空间促进机器独立性,(2)通过允许程序员由在执行时链接在一起的独立模块编写程序来促进模块化,(3)可以运行物理内存无法容纳的大型程序(例如,Lisp 程序)。虚拟内存的其他好处包括运行任意大小的程序、运行部分加载的程序以及无需重新编译程序即可更改内存配置。虚拟内存被认为是一种基本的操作系统抽象,但当前的硬件和应用程序趋势正在挑战其核心假设。

进程间通信 (IPC)

进程间通信的抽象使一个或多个进程能够相互交互。早期版本的 Unix 支持信号和管道[2]。信号使程序员能够以编程方式处理硬件故障,并且此机制被推广为允许进程通知其他进程。例如,shell 进程可以使用信号来停止进程。管道是允许进程之间交换数据的特殊文件。管道不允许任意进程交换数据,因为两个进程之间的管道必须由它们的共同祖先设置。

由于管道和信号的限制,BSD 中加入了套接字,为本地和远程进程(即在不同主机上运行的进程)提供统一的 IPC 机制。套接字已成为联网的标准方式,但它们的使用并不像针对本地 IPC 的平台特定 IPC 机制那样广泛[24]。

共享内存的 mmap 接口被设想为一种 IPC 机制[25]、[26],但从未真正流行起来。1993 年发布的 POSIX.1b 中添加了其他 IPC 机制(信号量、共享内存的 IPC 特定接口和消息队列),但此后已被供应商特定的 IPC 机制大量取代[24]。

线程和异步 I/O

线程和异步 I/O 是 POSIX 中用于满足并行和并发需求的后来者抽象。

传统的 UNIX 进程只提供单线程执行。由于无法支持并发执行线程,单个 UNIX 进程无法充分利用多个计算核心提供的并行性。充分利用并行性的一种方法是分叉多个进程,但这要求分叉的进程使用 IPC 机制相互通信,而这又效率低下。

POSIX 异步 I/O (AIO) 接口旨在满足日益增长的非阻塞 I/O 接口需求,该接口可用于提高并发性。此接口允许进程调用异步执行的 I/O 操作。但是,它可能会在各种情况下阻塞,并且每个 I/O 至少需要两个系统调用:一个用于提交请求,另一个用于等待请求完成。
在 POSIX 中,线程出现于 20 世纪 90 年代初,源于支持多核硬件并行性和实现应用程序级并发性的需求[8]、[27]。与进程不同,线程在同一个地址空间中运行。POSIX 线程可以通过不同的方式实现;

1-on-1:每个线程都在自己的内核线程中运行;

N-on-1:所有线程都在一个内核线程中运行;
N-on-M:N 个线程在 M 个内核线程中运行[27]、[28]、[29]。

管理用户空间的并行性对于高性能至关重要[27]。然而,主流 POSIX 操作系统采用了 1-on-1 线程模型,理由是实现简单[30]、[31]。无论如何,使用大量线程的应用程序架构,如分阶段事件驱动架构(SEDA)[32],由于线程开销而效率低下[33]。因此,许多高性能应用程序采用每核线程模型,其中线程数等于处理核心数,并提供自己的并发接口[34],[35]。

超越 POSIX

卸载计算

POSIX 进程是一个以 CPU 为中心的抽象,因为在 Unix 数十年的演进过程中,CPU 一直是核心和主要的计算资源。然而,将计算从 CPU 卸载到特定领域的协处理器和加速器(如用于图形和并行计算的 GPU 以及用于卸载数据包处理的 NIC)已成为主流[36]。因此,CPU 越来越成为协调这些资源之间计算的协调者,而应用程序越来越多地将 CPU 的计算能力仅用于协调各种硬件资源之间的计算。

但是,POSIX 没有处理协处理器或加速器的机制。因此,所有非 CPU 的计算元素都被视为 I/O 设备。因此,应用程序需要使用用户空间 API 将代码和数据上传到加速器,该 API 通过不透明的系统调用(如 fcntl())与操作系统内核集成。例如,有用于 GPGPU 的 OpenCL 和 CUDA 等 API,以及用于图形编程的 Vulkan 等 API [37]。这些 API 必须处理内存和资源管理等问题,因为 POSIX 本身并不支持这种类型的硬件。

 CachingSyncCopiesComplexity
read/writekernelyesyeslow
mmapkernelyesnomedium
DIOuseryesnomedium
AIO/DIOusernonohigh
io_uringkernel/usernoyes/nohigh

Linux 中的 I/O 访问方法

异步 I/O

异步 I/O 源于 Multics [11]、[12]。但是,POSIX I/O 调用源于 Unix,其 I/O 接口是同步的。因此,POSIX 读/写系统调用是同步的,它们会导致从内核页面缓存复制数据。同步接口是快速 I/O 的瓶颈,它们要求应用程序使用线程实现应用程序级并发和并行。mmap 接口比传统的读/写更快,因为它避免了系统调用开销并在内核和用户空间之间进行复制。但是,使用 mmap 的 I/O 是同步的,并且具有更复杂的错误处理。例如,在磁盘已满时,写入将返回错误代码,而基于 mmap 的 I/O 则需要处理信号。相比之下,直接 I/O (DIO) 允许应用程序使用相同的读写系统调用,同时绕过页面缓存。但是缓冲区管理和缓存是在用户空间中执行的。异步 I/O (AIO) 接口提供了一组新的系统调用,允许用户空间应用程序使用 io_submit 系统调用异步提交 I/O,并使用 io_getevents 系统调用轮询 I/O 完成情况。但是,Linux 的 AIO 实现存在一些问题:它在每个系统调用中复制最多 104 个字节的描述符和完成元数据,并且系统调用有时会阻塞[38]。

Linux 的 io_uring 接口旨在解决这些缺点,并提供真正的异步 I/O 接口[38]。它首次出现在 Linux 内核 5.1 版中,它使用两个无锁单生产者单消费者 (SPSC) 队列用于内核和用户空间之间的通信[38]。一个队列用于 I/O 提交,由应用程序写入并由内核读取,而另一个队列用于 I/O 完成,该队列由内核写入并由应用程序读取。根据使用情况,应用程序可以将 io_uring 实例配置为中断驱动、轮询或内核轮询。io_uring 接口允许线程提交 I/O 请求并继续执行其他任务,直到操作系统通知它 I/O 操作已完成。

绕过(Bypassing) POSIX I/O

POSIX I/O 模型假设内核执行 I/O,将数据传输到用户空间进行进一步处理。然而,该模型对于高到达率的扩展性不是很好,因此绕过 POSIX I/O 接口的早期示例之一是 BSD 数据包过滤器 (BPF)。BPF 通过在内核中运行的伪机器内过滤数据包来促进用户级数据包捕获 [39]。数据包捕获应用程序首先命令内核复制到达网络接口卡 (NIC) 的数据包:一份副本穿过网络协议栈,另一份副本穿过伪机器。BPF 伪机器执行从高级描述语言编译的数据包过滤代码,然后将过滤后的数据包发送到用户空间。扩展伯克利数据包过滤器 (eBPF) 建立在 BPF 之上,允许应用程序在内核虚拟机或能够运行程序的硬件上执行沙盒程序[40]。这使得应用程序能够卸载 I/O 活动,例如网络协议处理和在用户空间中实现文件系统。具体来说,eBPF 使应用程序能够完全绕过 I/O 的 POSIX 抽象并在用户空间中实现它们。eBPF 补充了现有的内核旁路方法,例如 DPDK 和 SPDK,使应用程序能够绕过内核进行网络和存储 I/O [41]、[42]。

超越机器抽象

POSIX 提供了以可移植方式在类 Unix 操作系统变体和机器架构之间编写应用程序的抽象。然而,当代应用程序很少在一台机器上运行。它们越来越多地使用远程过程调用 (RPC)、HTTP 和 REST API、分布式键值存储和数据库,所有这些都是用 JavaScript 或 Python 等高级语言实现的,在托管运行时上运行。这些托管运行时和框架公开了隐藏其底层 POSIX 抽象和接口细节的接口。此外,它们还允许使用除 C(Unix 和 POSIX 的语言)以外的编程语言编写应用程序。因此,对于许多当代系统和服务的开发人员来说,POSIX 基本上已经过时了,因为它的抽象是低级的并且与单台机器绑定。

然而,云和无服务器平台现在面临着 POSIX 之前的操作系统所面临的一个问题:它们的 API 是碎片化的和平台特定的,这使得编写可移植的应用程序变得困难。此外,这些 API 仍然主要是以 CPU 为中心的,这使得很难有效地利用专用加速器和分解的硬件,除非诉诸定制解决方案。例如,JavaScript 今天的地位可能与 POSIX 过去的地位相似:它将应用程序逻辑与底层操作系统和机器架构分离。然而,JavaScript 运行时仍然以 CPU 为中心,这使得很难将 JavaScript 应用程序的某些部分卸载到 NIC 或存储设备上的加速器上运行。具体来说,我们需要一种语言来表达应用程序逻辑,使编译器和语言运行时能够有效地利用硬件堆栈不同部分中出现的大量硬件资源的功能。同时,思考如果没有 POSIX 中的 CPU 中心主义,这些设备的硬件设计会有什么不同,这将是一个有趣的思想实验。

结束语

几十年来,POSIX 已成为操作系统抽象和接口的标准。硬件约束和当时的用例是抽象设计的两个驱动因素。如今,I/O 和计算之间的速度平衡正在向 I/O 倾斜,这也是协处理器和专用加速器越来越主流的原因之一。因此,我们认为 POSIX 时代已经结束,未来的设计需要超越 POSIX,在更高层次上重新思考抽象和接口。我们还认为,操作系统接口必须改变以支持这些更高层次的抽象。

致谢

作者要感谢 Kirk McKusick、Babak Falsafi、Matt Fleming、Rik Farrow 以及 OSDI '20、EuroSys '21、HotOS XVIII、ApSys '21 和 ASPLOS '22 的匿名审阅者提出的深刻意见,使本文更加完善。

引用来源:
[1] The Open Group. The Open Group Base Specifications Issue 7, 2018 edition, 2018.
[2] Dennis M. Ritchie and Ken Thompson. The UNIX Time-sharing System. Communications of the ACM, 17(7):365-375, July 1974.
[3] Peter H. Salus. A Quarter Century of UNIX. ACM Press/Addison-Wesley Publishing Co., 1994.
[4] Özalp Babaoğlu and William Joy. Converting a Swap-Based System to Do Paging in an Architecture Lacking Page-Referenced Bits. In Proceedings of the Eighth ACM Symposium on Operating Systems Principles, SOSP '81, pages 78-86, 1981.
[5] Robert A. Gingell, Joseph P. Moran, and William A. Shannon. Virtual Memory Architecture in SunOS. In Proceedings of USENIX Summer Conference, pages 81-94, 1987.
[6] Computer History Wiki: PDP-11/20. http://gunkies.org/wiki/PDP-11/20. [Online; accessed 2022-09-01].
[7] Computer History Wiki: PDP-11/70. http://gunkies.org/wiki/PDP-11/70. [Online; accessed 2022-09-01].
[8] M. L. Powell, Steve R. Kleiman, Steve Barton, Devang Shah, Dan Stein, and Mary Weeks. SunOS Multi-thread Architecture. In Proceedings of the Usenix Winter 1991 Conference, pages 65-80, 1991
[9] Corbató, F. J. and Vyssotsky, V. A. Introduction and overview of the multics system. In Proceedings of the November 30-December 1, 1965, Fall Joint Computer Conference, Part I, AFIPS '65, pages 185-196, 1965.
[10] Daniel Cooke, Joseph Urban, and Scott Hamilton. Unix and beyond: An interview with Ken Thompson. Computer, (5):58-64, 1999.
[11] J. F. Ossanna, L. E. Mikus, and S. D. Dunten. Communications and Input/Output Switching in a Multiplex Computing System. In Proceedings of the November 30-December 1, 1965, Fall Joint Computer Conference, Part I, AFIPS '65, pages 231-241, 1965.
[12] R. J. Feiertag and E. I. Organick. The Multics Input/Output System. In Proceedings of the Third ACM Symposium on Operating Systems Principles, SOSP '71, pages 35-41, 1971.
[13] William Joy, Eric Cooper, Robert Fabry, Samuel Leffler, Kirk McKusick, and David Mosher. 4.2BSD System Manual. Technical report, University of California, Berkeley, 1983.
[14] Ying Wang, Dejun Jiang, and Jin Xiong. Caching or not: Rethinking virtual file system for Non-Volatile main memory. In 10th USENIX Workshop on Hot Topics in Storage and File Systems (HotStorage 18), Boston, MA, July 2018. USENIX Association.
[15] Sangjin Han, Scott Marshall, Byung-Gon Chun, and Sylvia Ratnasamy. MegaPipe: A New Programming Interface for Scalable Network I/O. In Proceedings of the 10th USENIX Conference on Operating Systems Design and Implementation, OSDI'12, pages 135-148, 2012.
[16] Xiaofeng Lin, Yu Chen, Xiaodong Li, Junjie Mao, Jiaquan He, Wei Xu, and Yuanchun Shi. Scalable kernel tcp design and implementation for short-lived connections. In Proceedings of the Twenty-First International Conference on Architectural Support for Programming Languages and Operating Systems, ASPLOS '16, page 339–352, New York, NY, USA, 2016. Association for Computing Machinery.
[17] Bojie Li, Tianyi Cui, Zibo Wang, Wei Bai, and Lintao Zhang. Socksdirect: Datacenter sockets can be fast and compatible. In Proceedings of the ACM Special Interest Group on Data Communication, SIGCOMM '19, page 90–103, New York, NY, USA, 2019. Association for Computing Machinery.
[18] E.F. Codd. Multiprogramming. Advances in Computers, 3:77-153, 1962.
[19] Dennis Ritchie. The Evolution of the Unix Time-Sharing System. In Lecture Notes in Computer Science #79: Language Design and Programming Methodology, pages 25-36. Springer-Verlag, 1980.
[20] Peter J. Denning. Virtual Memory. ACM Computing Surveys, 2(3):153-189, 1970.
[21] T. Kilburn, R. B. Payne, and D. J. Howarth. The Atlas Supervisor. In Proceedings of the December 12-14, 1961, Eastern Joint Computer Conference: Computers - Key to Total Systems Control, AFIPS '61 (Eastern), pages 279-294, 1961.
[22] A. Bensoussan, C. T. Clingen, and R. C. Daley. The Multics Virtual Memory: Concepts and Design. Commun. ACM, 15(5):308-318, May 1972.
[23] Marshall Kirk McKusick, Keith Bostic, Michael J. Karels, and John S. Quarterman. The Design and Implementation of the 4.4BSD Operating System. Addison Wesley Longman Publishing Co., Inc., Redwood City, CA, USA, 1996.
[24] Vaggelis Atlidakis, Jeremy Andrus, Roxana Geambasu, Dimitris Mitropoulos, and Jason Nieh. POSIX Abstractions in Modern Operating Systems: The Old, the New, and the Missing. In Proceedings of the Eleventh European Conference on Computer Systems, EuroSys '16, pages 1-17, 2016.
[25] Samuel J. Leffler, William N. Joy, and Robert S. Fabry. 4.2BSD Networking Implementation Notes (Revised July, 1983). Technical Report UCB/CSD-83-146, EECS Department, University of California, Berkeley, Jul 1983.
[26] M McKusick and M Karels. A New Virtual Memory Implementation for Berkeley UNIX. In Proceedings of the European UNIX Users Group Meeting, pages 451-460, 1986.
[27] Thomas E. Anderson, Brian N. Bershad, Edward D. Lazowska, and Henry M. Levy. Scheduler Activations: Effective Kernel Support for the User-level Management of Parallelism. In Proceedings of the Thirteenth ACM Symposium on Operating Systems Principles, SOSP '91, pages 95-109, 1991.
[28] Robert A. Alfieri. An Efficient Kernel-Based Implementation of POSIX Threads. In USENIX Summer 1994 Technical Conference, Boston, Massachusetts, USA, June 6-10, 1994, Conference Proceeding, pages 59-72, 1994.
[29] Frank Mueller. A Library Implementation of POSIX Threads under UNIX. In Proceedings of the Usenix Winter 1993 Technical Conference, San Diego, California, USA, January 1993, pages 29-42, 1993.
[30] Sun Microsystems. Multithreading in the Solaris Operating Environment. https://web.archive.org/web/20090327002504/http://www.sun.com/software/w..., 2002. [Online; accessed 2022-07-30].
[31] Ulrich Drepper and Ingo Molnar. The Native POSIX Thread Library for Linux. https://www.cs.utexas.edu/~witchel/372/lectures/POSIX_Linux_Threading.pdf, 2003. [Online; accessed 2019-07-30].
[32] Matt Welsh, David Culler, and Eric Brewer. SEDA: An Architecture for Well-conditioned, Scalable Internet Services. ACM SIGOPS Operating Systems Review, 35(5):230-243, October 2001.
[33] Matt Welsh. A Retrospective on SEDA. http://matt-welsh.blogspot.com/2010/07/retrospective-on-seda.html, 2010. [Online; accessed 2022-07-30].
[34] Seastar. http://www.seastar-project.org/. [Online; accessed 2022-07-30].
[35] Hyeontaek Lim, Dongsu Han, David G. Andersen, and Michael Kaminsky. MICA: A Holistic Approach to Fast in-Memory Key-Value Storage. In Proceedings of the 11th USENIX Conference on Networked Systems Design and Implementation, NSDI'14, pages 429-444, 2014.
[36] Jeffrey C. Mogul. TCP Offload is a Dumb Idea Whose Time Has Come. In Proceedings of the 9th Conference on Hot Topics in Operating Systems - Volume 9, HOTOS'03, pages 5-5, Berkeley, CA, USA, 2003. USENIX Association.
[37] Nicola Capodieci, Roberto Cavicchioli, and Andrea Marongiu. A taxonomy of modern gpgpu programming methods: On the benefits of a unified specification. IEEE Transactions on Computer-Aided Design of Integrated Circuits and Systems, 41(6):1649-1662, 2022.
[38] Jonathan Corbet. Ringing in a new asynchronous I/O API. https://lwn.net/Articles/776703/, 2019. [Online; accessed 2022-05-26].
[39] Steven McCanne and Van Jacobson. The BSD Packet Filter: A New Architecture for User-level Packet Capture. In USENIX Winter 1993 Conference, San Diego, CA, January 1993. USENIX Association.
[40] Jakub Kicinski and Nicolaas Viljoen. eBPF Hardware Offload to SmartNICs: cls bpf and XDP. Proceedings of netdev, 1, 2016.
[41] Data plane development kit. https://www.dpdk.org/. [Online; accessed 2022-07-30].
[42] Z. Yang, J. R. Harris, B. Walker, D. Verkamp, C. Liu, C. Chang, G. Cao, J. Stern, V. Verma, and L. E. Paul. SPDK: A Development Kit to Build High Performance Storage Applications. In 2017 IEEE International Conference on Cloud Computing Technology and Science (CloudCom), pages 154-161, 2017.


POSIX file system

POSIX is the Portable Operating System Interface standard, IEEE Std 1003.1-1988 and related. These standards, based on the Unix operating system, define a set of programming and command interfaces. Programs and scripts following these standards are supposed to be easily portable between operating system platforms providing these interfaces.

The POSIX standards imply a model for file system organization: POSIX file systems are organized as directories (folders) containing files (documents). The POSIX programming API defined a number of operations to work with files, directories, and entire file systems:
mount, umount the file system
open, close file descriptor
write, read to/from file descriptor
mkdir, rmdir, creat, unlink, link, symlink
fcntl (byte range locks, etc.)
stat, utimes, chmod, chown, chgrp
Path names are case sensitive, components are separated with forward slash (/).

Essentially, this is the API that evolved for Unix and adopted by Linux.

POSIX file systems are treated as a sequence of bytes. However, internally the data content of a file is stored as logical sequence of file system blocks:
Each block is a fixed number of bytes. The last block might not be full.
Within a block, all the bytes are sequential. However, within a file, the blocks might not reside sequentially on the disk.
Underlying storage systems are usually organized as blocks, and ideally file system blocks are aligned with the storage system’s blocks.


File systems also contain metadata, i.e., "data about the data":
There is an inode for each file or directory, providing:
Locations of the data blocks for the file.
Attributes about the file, like "time last accessed" or "owner of the file".
The inode table records where each inode is located, indexed by number.
Directories are special files listing the names and inode numbers of files under the folder.

The POSIX standard describes how system calls must behave. One particular section of the standard defines the semantics (behavior) of a POSIX compatible file system. Today, many of the UNIX systems that existed back in the 80s and 90s have become irrelevant. On the other hand, Linux became ubiquitous; one of the reasons being the POSIX compatibility. Being POSIX compatible made it easier to move from a UNIX to LINUX.

For a file system to be compatible with POSIX, it must:
Implement strong consistency. For example, if a write happened before a read, the read must return the data written.
Have atomic writes, where a read either returns all data written by a concurrent write or none of the data but is not an incomplete write.
Implement certain operations, like random reads, writes, truncate, or fsync.
Control access to files using permissions (see here) and implement calls like chmod, chown, and so on to modify them.

As you can see in the diagram above, your application makes system calls such as open, read, and write to communicate with the Linux kernel. The semantics, i.e., the behavior, of the system calls is defined by POSIX.

The Linux Kernel hands the file system-related system calls to the Virtual File System (VFS) layer, which abstracts from the underlying file system implementation, which includes local file systems but also distributed file systems like Quobyte.


Because of POSIX compatibility, you are no longer tied to a single file system. Suppose you decide to start using a specific file system, and all of a sudden, the creator of that file system gets incarcerated, and the file system becomes irrelevant (as random as this may sound, this actually happened with ReiserFS and its creator!). Even in a very random case like the one mentioned before, you don’t have to worry because you can easily swap out the file system without worrying about your application having problems with the new file system.

POSIX and Linux differences

Linux is seen as "partially POSIX compatible" when it comes to the file system, mainly because it doesn't provide the isolation feature that POSIX requires for IO operations. Instead, reads and writes are atomic on a page level (4kB), but a read might return some pages of a concurrent write operation. The decision not to implement atomicity is mainly due to performance. Instead, applications have to use file locking if they expect concurrent read/write access and require isolation between them, e.g., to avoid reading incomplete writes.

POSIX 是为了确保 Unix 系统之间的兼容性和可移植性而设计的一个标准,它通过定义系统调用、文件管理、进程控制、线程管理等接口,帮助开发者编写可以跨平台运行的程序。通过遵循 POSIX,程序可以避免针对不同操作系统进行大量修改,提高软件的移植性和可维护性。