桂林网站建设哪家好,软文网站名称,青岛高端模板建站,wordpress vip查看插件在本文中#xff0c;我们通过对Portable Operating System Interface#xff08;POSIX#xff09;抽象的历史演变进行系统性的回顾#xff0c;提供了一个全面的视图。我们讨论了推动这些演变的一些关键因素#xff0c;并确定了在构建现代应用程序时使它们不可行的缺陷。
…在本文中我们通过对Portable Operating System InterfacePOSIX抽象的历史演变进行系统性的回顾提供了一个全面的视图。我们讨论了推动这些演变的一些关键因素并确定了在构建现代应用程序时使它们不可行的缺陷。
POSIX标准定义了类Unix操作系统的接口。Unix是由程序员编写给程序员的第一个操作系统而POSIX使开发人员能够编写可在不同的Unix操作系统变体和指令集架构上运行的可移植应用程序。Unix的主要应用场景是对存储文件系统进行多路复用并为用户提供交互式环境shell。相比之下许多当代基于POSIX的系统的主要应用场景是在数据中心的计算机上运行的服务这些服务具有数量级较低的延迟要求。由于登纳德缩放定律Dennard Scaling在2004年前后结束这些服务不能指望随着CPU时钟频率的增加而逐年运行得更快因为CPU时钟频率不再以Unix普及时期普遍存在的速度增长。此外许多人认为摩尔定律Moore’s Law正在放缓因此软件不再能指望通过硬件优化的增加来变得更快这是由于晶体管密度的增加所驱动的。随着我们迈向后摩尔定律时代的计算系统设计师开始利用快速可编程网卡NICs、专用硬件加速器和非易失性主存等设备来应对应用程序的严格延迟限制。 年份 抽象 示例接口 版本 ’69 Filesystem open, read, write V0 ’69 Processes fork V0 ’71 Processes exec V1 ’71 Virtual memory break1 V1 ’73 Pipes pipe V3 ’73 Signals signal V4 ’79 Signals kill V7 ’79 Virtual memory vfork2 3BSD ’83 Networking socket, recv, send 4.2BSD ’83 I/O multiplexing select 4.2BSD ’83 Virtual memory mmap3 4.2BSD ’83 IPC msgget, semget, shmget SRV1 ’87 I/O multiplexing poll SRV3 ’88 Virtual memory mmap SunOS 4.0 ’93 Async. I/O aio_submit POSIX.1b ’95 Threads pthread_create POSIX.1c
POSIX抽象和接口的时间线
这些抽象是在1970年代至1990年代的不同Unix变体中引入的。文件系统和进程是早期接口中的基本部分已经存在于V0中。虚拟内存在1970年代的3BSD中引入并在1980年代的4.2BSD和SunOS 4.0中完成。网络支持是在1980年代的4.2BSD中添加的。异步I/O和线程是在1990年代的POSIX标准中引入的。 break系统调用后来被重命名为brk并添加了另一个变体sbrk。现在这两个都已被弃用。 3BSD添加了对基于页面的虚拟内存的支持。它们添加了vfork系统调用以避免实现fork的写时复制。 尽管mmap是在1983年设计的但提议的设计在1986年完全实现了。
POSIX的演进
POSIX的抽象进程、文件系统、虚拟内存、套接字和线程是基于不同Unix变体在1970年代和1980年代的开发中的操作系统抽象例如Research Unix、System V、BSD、SunOS等。
它们各自时代的应用场景和硬件能力影响了这些抽象。例如早期的Unix运行在PDP-11/20上这是一台16位计算机具有单个CPU和最多248KB的主存储器。由于PDP-11/20缺乏内存保护Unix不支持虚拟内存这与当时的其它操作系统如Multics不同。虽然后来的PDP-11变体如PDP-11/70具有内存映射单元MMU但直到1970年代末VAX架构的出现Unix才添加了虚拟内存这成为当时Unix的主要架构。同样Unix直到1980年代初互联网出现后才有了网络抽象当时的4.2BSD引入了套接字抽象用于抽象TCP/IP网络协议进行远程进程通信。类似地Unix直到1990年代初多处理器机器变得更加主流时才有了线程抽象。
文件系统
文件系统是一种访问和组织存储设备上字节数据的抽象。这种抽象及其I/O接口在很大程度上起源于Multics并且被认为是Unix中最重要的抽象。然而与Unix仅支持同步I/O不同Multics还支持异步I/O这个特性最终成为了POSIX的一部分。
文件系统抽象还包括文件、目录、特殊文件以及硬链接和符号链接。文件系统中的文件是一系列字节操作系统不以任何方式解释这些字节。这使得操作系统可以将硬件设备表示为特殊文件并且操作文件的接口已经成为I/O设备的事实标准接口。
文件系统抽象可以方便地集成I/O设备。然而它可能成为快速I/O设备的瓶颈。
进程
进程是系统中执行应用程序的抽象。具体而言应用程序被表示为一个映像抽象了其执行环境其中包括程序代码文本、处理器寄存器值和打开的文件。此映像存储在文件系统中操作系统确保进程映像的执行部分驻留在内存中。进程抽象自早期的Unix开始存在并且对于共享计算和I/O资源的时间共享至关重要。
这种抽象根植于多道程序设计这是一种在1950年代中期开发的技术用于提高硬件利用率同时进行I/O操作。早期运行在PDP-7上的Unix仅支持两个进程一个用于连接到机器的每个终端后来设计为在PDP-11上运行的Unix可以将多个进程保留在内存中。
进程是一种以处理器为中心的抽象对于假设进程映像的执行仅在CPU上完成的应用程序非常有用。然而图形处理单元GPU、张量处理单元TPU和各种其它用于卸载计算的专用加速器等硬件设备的普及正在挑战这种假设。
相关视频推荐
c八股文重点网络的posix api实现原理
初识linux内核进程通信还能这么玩
8个方面讲解io_uring重塑对异步io的理解
免费学习地址c/c linux服务器开发/后台架构师
需要C/C Linux服务器架构师学习资料加qun812855908获取资料包括C/CLinuxgolang技术NginxZeroMQMySQLRedisfastdfsMongoDBZK流媒体CDNP2PK8SDockerTCP/IP协程DPDKffmpeg等免费分享
虚拟内存 虚拟内存是一种抽象可以创建一个看似与存储空间一样大的内存空间。它源于自动利用主内存速度和廉价存储容量的需要。虚拟内存的概念可以追溯到1960年代初基于页面的虚拟内存最早出现在Atlas Supervisor中1962年Multics也支持虚拟内存。
虚拟内存是在Unix中添加的大约在Unix诞生后的十年。在诞生时Unix进程地址空间被划分为三个段一个程序文本代码段在所有进程之间共享但不可写一个进程数据段可读/写但私有以及一个栈段。sbrk系统调用可以扩展和收缩进程数据段。然而由于需要运行需要比当时主存储器容量更多的存储的程序例如LispVAX-11体系结构中的MMU使基于页面的虚拟内存成为可能。
这种抽象解耦了两个相关的概念地址空间即用于寻址内存的标识符以及内存空间即用于存储数据的物理位置。从历史上看这种解耦具有三个主要目标(1通过独立于物理内存空间的地址空间促进机器的独立性(2通过允许程序员在执行时将独立的模块组合成程序来促进模块化(3实现运行大型程序的可能性这些程序无法适应物理内存例如Lisp程序。虚拟内存的其它好处包括运行任意大小的程序、运行部分加载的程序以及更改内存配置而无需重新编译程序。虚拟内存被视为一种基本的操作系统抽象但当前的硬件和应用趋势正在挑战其核心假设。
进程间通信IPC
进程间通信的抽象允许一个或多个进程相互交互。早期的Unix版本支持信号和管道。信号使程序员能够以编程方式处理硬件故障并且该机制被泛化以允许一个进程通知其它进程。例如shell进程可以使用信号来停止其它进程。管道是特殊的文件允许进程彼此交换数据。管道不允许任意进程交换数据因为两个进程之间的管道必须由它们共同的祖先进程建立。
由于管道和信号的限制BSD添加了套接字以为本地和远程进程提供统一的IPC机制即在不同主机机器上运行的进程之间的通信。套接字已成为网络的标准方式但与平台特定的本地IPC机制相比它们并未被广泛使用。
共享内存的mmap接口被设想为一种IPC机制但从未真正普及。附加的IPC机制信号量、专用于共享内存的IPC接口和消息队列在POSIX.1b中被添加于1993年发布但此后主要被供应商特定的IPC机制所取代。
线程和异步I/O
线程和异步I/O是POSIX中为满足并行性和并发性需求的后来者抽象。
传统的UNIX进程提供单个执行线程。这种无法支持并发执行线程的能力使得单个UNIX进程无法利用多个计算核心提供的并行性。利用并行性的一种方法是分叉多个进程但这要求分叉的进程使用IPC机制进行通信这反过来效率低下。
POSIX异步I/OAIO接口旨在满足对非阻塞I/O接口的需求从而改进并发性。该接口使进程能够调用异步执行的I/O操作。但在各种情况下它可能会阻塞并且每个I/O操作需要至少两个系统调用一个用于提交请求另一个用于等待其完成。
在POSIX中线程于1990年代初开始出现是为了支持多核硬件的并行性并实现应用级并发性。与进程不同线程在相同的地址空间中运行。POSIX线程可以以不同的方式实现1对1每个线程在自己的内核线程中运行N对1所有线程在单个内核线程中运行N对MN个线程在M个内核线程中运行。在用户空间中管理并行性对于高性能至关重要。然而主流的POSIX操作系统采用了1对1的线程模型理由是实现简单。尽管如此使用大量线程的应用程序体系结构如分阶段事件驱动架构SEDA由于线程开销的缘故效率低下。因此许多高性能应用程序采用每个核心一个线程的模型其中线程数量等于处理核心数量并且提供自己的并发接口。
超越POSIX
计算卸载
POSIX进程是以CPU为中心的抽象因为CPU在Unix发展的几十年中一直是中心和主要的计算资源。然而将计算从CPU卸载到特定领域的协处理器和加速器例如用于图形和并行计算的GPU以及用于卸载数据包处理的NIC已成为主流。因此CPU越来越成为协调计算资源的协调器并且CPU的计算能力越来越多地被应用程序用于仅在各种硬件资源上协调计算。
然而POSIX没有机制来处理协处理器或加速器。因此所有不是CPU的计算元素都被视为I/O设备。因此应用程序需要使用用户空间API将代码和数据上传到加速器该API通过不透明的系统调用例如fcntl(与操作系统内核集成。例如有用于GPGPU的OpenCL和CUDA以及用于图形编程的Vulkan等API。这些API必须处理诸如内存和资源管理等事物因为POSIX本身不支持这种类型的硬件。 缓存 同步 拷贝 复杂度 read/write kernel yes yes low mmap kernel yes no medium DIO user yes no medium AIO/DIO user no no high io_uring kernel/user no yes/no high
Linux中的I/O访问方法
异步I/O
异步I/O起源于Multics。然而POSIX I/O调用起源于Unix其I/O接口是同步的。因此POSIX的read/write系统调用是同步的并且它们会导致从内核页缓存中复制数据。同步接口对于快速I/O来说是一个瓶颈并且需要应用程序使用线程来实现应用级并发和并行性。相比之下mmap接口比传统的read/write更快因为它避免了系统调用开销和内核与用户空间之间的数据复制。然而使用mmap的I/O是同步的具有更复杂的错误处理。例如磁盘已满时写入将返回错误代码而基于mmap的I/O则需要处理信号。相比之下直接I/ODIO允许应用程序使用相同的read和write系统调用同时绕过页缓存。但是缓冲区管理和缓存是在用户空间中执行的。异步I/OAIO接口提供了一组新的系统调用允许用户空间应用程序异步提交I/O请求并使用io_submit系统调用轮询I/O完成。然而Linux的AIO实现存在一些问题每次系统调用会复制高达104字节的描述符和完成元数据并且系统调用有时会发生阻塞。
Linux的io_uring接口旨在解决这些缺陷并提供真正的异步I/O接口。它首次引入于Linux内核5.1版本使用两个无锁的单生产者单消费者SPSC队列在内核和用户空间之间进行通信。一个队列用于I/O提交应用程序写入内核读取而另一个队列用于I/O完成内核写入应用程序读取。根据应用场景应用程序可以配置io_uring实例以作为中断驱动、轮询或内核轮询来运行。io_uring接口允许线程提交I/O请求并在操作系统通知I/O操作完成前继续执行其它任务。
绕过POSIX I/O
POSIX I/O模型假设内核执行I/O将数据传输到用户空间进行进一步处理。然而这个模型在高到达率下不是很好扩展所以早期绕过POSIX I/O接口的一个例子是BSD数据包过滤器BPFBSD Packet filter。BPF通过在运行于内核中的伪机器内部过滤数据包来促进用户级别数据包捕获。数据包捕获应用程序首先命令内核复制到达网络接口卡NIC的数据包其中一份复制遍历网络协议栈而另一份遍历伪机器。BPF伪机器在将经过高级描述语言编译的数据包过滤代码发送到用户空间之前执行。扩展的伯克利数据包过滤器eBPFExtended Berkeley Packet Filter在BPF的基础上构建并允许应用程序在内核虚拟机中执行沙箱程序或在能够运行这些程序的硬件上执行。这使得应用程序可以将I/O活动例如网络协议处理和在用户空间中实现文件系统卸载。具体而言eBPF允许应用程序完全绕过I/O的POSIX抽象并在用户空间中实现它们。eBPF补充了现有的内核绕过方法如DPDK和SPDK它们使得应用程序可以绕过内核进行网络和存储I/O。
超越机器抽象
POSIX提供了一种可移植地编写应用程序的抽象方式可在类Unix操作系统变体和机器架构上运行。然而当代应用程序很少在单个机器上运行。它们越来越多地使用远程过程调用RPC、HTTP和REST API、分布式KV存储和数据库所有这些都是使用高级语言如JavaScript或Python实现的运行在托管的运行时上。这些托管的运行时和框架暴露了隐藏其底层POSIX抽象和接口的接口。此外它们还允许应用程序使用C之外的编程语言进行编写而C是Unix和POSIX的编程语言。因此对于许多当代系统和服务的开发者来说POSIX在很大程度上已经过时因为它的抽象是低级的并且与单个机器绑定在一起。
然而云和无服务器平台现在面临的问题与之前的POSIX操作系统相似它们的API是分散且特定于平台的这使得编写可移植的应用程序变得困难。此外这些API仍然是以CPU为中心的这使得在不使用定制解决方案的情况下难以高效地利用特殊用途的加速器和非集成硬件。例如可以认为JavaScript今天与过去的POSIX处于类似的位置它将应用程序逻辑与底层操作系统和机器架构解耦。然而JavaScript运行时仍然是以CPU为中心的这使得将JavaScript应用程序的某些部分卸载到运行在NIC或存储设备上的加速器变得困难。具体而言我们需要一种能够表达应用程序逻辑的语言使得编译器和语言运行时能够高效地利用不同硬件堆栈中新兴的大量硬件资源的能力。与此同时思考一下如果POSIX中没有以CPU为中心的特点这些设备的硬件设计会有多么不同将是一个有趣的思想实验。
结束语
多年来POSIX已经成为操作系统抽象和接口的标准。抽象设计的两个驱动因素是硬件约束和当时的应用场景。今天I/O和计算之间的速度平衡正在向I/O倾斜这在一定程度上解释了为什么协处理器和特殊用途的加速器越来越受欢迎。因此我们认为POSIX时代已经结束未来的设计需要超越POSIX并在更高的级别上重新思考抽象和接口。我们还认为操作系统接口必须改变以支持这些更高级别的抽象。
原文地址超越POSIX一个时代的终结