Linux 事件机制

作者:Frederic Rossi

本文是描述 Linux 新的基于事件的机制系列文章的第一篇。本文特别关注于运营商级 Linux 的这种机制的动机、需求和优势。

在 Linux 内核中支持原生事件驱动系统的工作始于 2001 年,是加拿大蒙特利尔开放系统实验室(爱立信研究,公司部门)的一个研究项目。目标是为 Linux 提供一个事件驱动的环境,在电信应用背景下,与现有解决方案相比,能够提供更好的性能。

人们对将 Linux 操作系统提升到运营商级水平的兴趣日益浓厚。例如,OSDL 运营商级 Linux 工作组 (www.osdl.org/projects/cgl) 目前正在起草一套需求,旨在将 Linux 变成下一代网络的可靠运营商级服务器。

电信应用的操作系统必须确保它们能够提供高响应率和最短的停机时间——每年少于五分钟,99.999% 的正常运行时间——包括硬件、操作系统和软件升级。除了这个目标之外,运营商级系统还必须考虑诸如可扩展性、高可用性和性能等特性。

对于此类系统,即使在高负载下,也必须并发处理数千个请求,而不会影响系统的整体性能。用户在发出请求时可以预期一定的延迟时间,但他们不愿意接受无限的响应时间。由于多种原因,此类事务不会立即处理,可能需要几毫秒或几秒钟才能回复。等待答复会降低应用程序处理其他事务的能力。

人们已经设想了许多不同的解决方案来提高 Linux 的能力,使用不同类型的软件组织,例如多线程架构,通过实现高效的 POSIX 接口或通过提高现有内核例程的可扩展性。我们认为,这些解决方案都不足以满足真正的运营商级服务器的需求。

An Event Mechanism for Linux

图 1. PSTN 和 IP 网络之间的架构和互操作性

为了理解我们的观点,本文概述了电信网络。目的是阐明运营商级操作系统的要求。在介绍之后,我们将解释原生异步事件机制的优势,以便更好地支持运营商级特性。

运营商级需求

电信涉及在两个设备之间建立电话呼叫以及通过有线链路传输语音。图 1 显示了传统的公共交换电话网络 (PSTN)。更具体地说,运营商使用术语 信令 来指示在两个设备之间建立电话呼叫。PSTN 中的信令通过 7 号信令系统 (SS7) 协议栈完成。SS7 执行呼叫路由,并通过电路构建到目标电话的路径。一旦路径建立,两个电话就会连接,并且可以传输语音。SS7 协议能够处理呼叫路由、呼叫转移和错误情况。

互联网的灵活性、成本效益和持续增长正在推动许多电信服务向其迁移。这有助于将 IP 技术确立为所有通信服务的新标准。这两种类型的网络基于不同的技术,需要利用信令和媒体网关来实现互操作。

网关执行不同类型网络之间信息的转换。例如,SS7 网关用于通过流控制传输协议 (SCTP) 在 IP 网络上封装信令。媒体网关用于编码和解码来自 PSTN 网络并进入 IP 网络的语音,反之亦然。

信令和媒体网关为来自 PSTN 网络的呼叫提供与 IP 网络的连接。信令网关必须执行协议封装,以便通过互联网协议(例如 SCTP over IP 或 UDP over IP)传输 SS7 消息的语法和语义。随着并发请求数量的增加,信令服务器必须能够根据其系统能力进行扩展。

借助媒体网关,运营商可以实现 PSTN 和 IP 网络之间流数据的传输。他们可以接受的连接数取决于他们的硬件,硬件的大小可以从一个接口到数千个接口不等。媒体网关必须实时支持大量连接。

身份验证、授权和计费 (AAA) 服务器维护用户配置文件数据库。通常,一个或两个 AAA 服务器可能包含有关属于特定网络运营商的数百万用户的信息。通常会观察到每秒数千个并发身份验证请求的峰值。这种连接数量的变化很难提前计划。AAA 服务器在控制对 IP 网络的访问方面起着至关重要的作用,并且不允许发生故障。它们需要软实时能力,以便它们可以在几毫秒内回复大多数请求。

媒体服务器为最终用户提供专门的资源和服务,例如视频会议、视频服务器、应用程序和电子邮件。这些运营商级系统的一个重要方面是可扩展性。这些平台可以接受事务数量相对于处理器、接口或带宽数量的线性增长。电信运营商谈论线性可扩展性,这意味着在扩展服务器时,每个事务或每个用户的成本不应增加。

在发生故障或计划外中断的情况下,运营商级平台可以通过网络冗余程序自动恢复或故障转移到另一台服务器。实时软件升级和硬件设备热插拔也是 99.999% 服务可用性的一部分。

多年来,Linux 已被证明是稳定和一致的,并且它已经成为运营商有吸引力的选择。然而,为了成为电信网络的关键要素,它必须通过提供这些急需的运营商级功能的组件来增强。

匹配性能和架构

在传统的编程模型中,软件组件与其他组件显式同步。当需要大量交互时,这是一种常见的模型。例如,典型的方法是使用 select() 或 poll() 来监听文件描述符。select 的通用实现会扫描整个描述符数组。这是不可扩展的,因为检测描述符上的活动所需的时间与数组的大小成正比。这会增加应用程序延迟,并导致系统整体性能下降。

扫描描述符数组或等待数据会消耗处理时间。高效算法设计中的一个常见想法是异步处理系统事件。一些为用户空间应用程序提供事件通知的机制示例包括 POSIX AIO、epoll 或 BSD kqueue(请参阅资源)。

在描述此类机制的效率时,通常会计算从内核中检测到事件到应用程序有效处理事件之间所需的平均时间。这样做的一个主要原因是,此类方法的微基准测试是不相关的。这些机制在本地可能非常高效,但当与不适应的其他机制(例如多线程架构)结合使用时,效率会降低。例如,许多 Web 服务器使用线程池,该线程池在应用程序启动时启动。典型的架构是使用一个专用线程来管理传入连接,每个事务使用一个线程。通常,这种设计对于少量传入连接是有效的,但当负载更高时效率会降低。

当争夺 CPU 的对象之间需要高并发级别时,需要多线程应用程序。众所周知的例子可以在高性能计算应用程序中找到,在这些应用程序中,每个线程的执行速度都很重要,但运行的线程数量并不多。

线程提供了一种顺序和同步的开发模型,并且当需要高并发级别时,它们已成为实现应用程序的标准方法。但是,应用程序设计中的缺陷或处理同步中的缺陷很容易导致系统争用并影响系统整体性能。J. Ousterhout 在“为什么线程是个坏主意”中指出,使用线程进行编程非常困难,并且主要导致应用程序在高负载下无法正常执行。

电信应用程序中不存在线程之间的竞争。但是,当处理公共对象(例如分布式数据结构)时,会发生并发。对于这些应用程序,需要线程来提供对共享数据的并发访问。

电信应用程序用于在同一处理器上处理每秒数千个事务和数百个并发连接。此外,还必须考虑系统事件,包括数据库访问、应用程序故障、过载通知、警报、系统组件状态更改等等。在应用程序执行期间,同一系统中可能会生成数千个事件,因此使用线程管理事件效率低下。

传统的异步机制试图通过防止应用程序不必要地等待来解决此可扩展性问题,或者像 Linux 上的 epoll 一样,它们旨在改进对活动描述符的检测。不幸的是,这些解决方案仅限于文件描述符,而文件描述符仅代表感兴趣事件的一小部分。此外,启动大量线程(Web 服务器需要这样做才能处理这些事件)会造成瓶颈并加剧情况。

基于事件的架构

复杂分布式软件架构的开发需要实现一种适合在运行时利用系统资源的机制。解决此问题更有希望的解决方案是在 Linux 中引入基于事件的机制。这种机制实现了操作系统和应用程序之间的真正协作。它们提供了能够注册事件的组件,这些事件稍后可以通过处理程序的执行来异步通知。

如果我们比较信号处理程序和事件处理程序,我们发现后者信息量更大,因为它们将数据直接带给应用程序。基本上,异步事件机制可用于实现由系统事件触发的通用用户级处理程序,或实现周期性监视组件,如计时器。如果应用程序不知道事件何时发生,则第一种情况特别有趣。当异步接收事件时,应用程序可以采取操作,而无需恢复所有必要的数据,因为它在参数中提供。

已经对快速消息传递机制进行了一些研究,这些机制基于与异步事件相同的原理。例如,活动消息(请参阅资源)在接收进程的堆栈上异步执行。在弹出线程中,为每个处理程序创建一个执行线程,而在单线程向上调用中,在每个处理器上创建一个专用线程。AEM 是一种新兴机制,它为开发需要真正异步的应用程序提供了原生环境。例如,我们使用 AEM 为 TCP 实现了原生异步套接字接口。在 AEM 中,在注册时可以选择定义一个处理程序,该处理程序在当前执行任务或新的执行线程上执行。其他一些研究项目已经提出了类似的解决方案,以提高高负载下 Web 服务器的能力(请参阅“UNIX 的可扩展和显式事件传递机制”,资源)。

事件范例的主要好处是在同一机制中集成了事件处理和线程管理。具体而言,它完全控制了资源消耗。

性能确实是基于事件的机制的目标。将事件管理与应用程序分离,可以通过利用不同的内存分配方案或影响调度程序决策来提高局部性。例如,通过根据待处理事件强制执行进程优先级来确保软实时响应能力。

与多线程架构提供的复杂性相比,这种新兴的范例提供了一种更简单、更自然的编程风格。它证明了其在多层软件架构开发中的效率,其中每一层都为上层提供服务。这种类型的架构在分布式应用程序中非常常见。

图 2 说明了一个基于事件驱动模型的典型分布式应用程序。它由许多软件组件组成,一个进程代表应用程序的一层。在分布式应用程序中,无论是在同一层还是在不同层,都会进行大量的本地和远程通信。

An Event Mechanism for Linux

图 2. 在两个处理器上运行的基于事件模型的 多层分布式应用程序。每一层都是单线程的,应用程序组件之间的通信可以是同步的(实线)或异步的(虚线)。

在许多情况下,此类应用程序必须提供在全球范围内以高性能运行的服务。至关重要的是,这些应用程序要充分利用硬件资源,并根据平台的功能进行线性扩展。

此软件的设计必须确保组件之间不可能出现死锁或竞争条件。这种设计缺陷对系统完整性的影响可能是灾难性的。当使用多线程方法时,这种情况很难解决,因为由于可能的配置数量众多,因此很难检测和纠正。基于事件的机制通过控制异步启动的线程数量来减少引入故障点的机会。更容易保证处理程序执行的原子性,因为该机制保留在内核中。

系统资源是有限的,可以启动的进程数量始终是有限的。在注册时,可以选择要执行的处理程序的类型。这使得随着负载的增加,可以生成更强大的应用程序。应用程序的主要优势是可以混合顺序代码和异步代码。然后可以设计利用两种策略的功能的应用程序。

基于事件的框架为运营商提供了动态重新配置,对系统正常运行时间的影响最小。硬件热插拔和动态软件升级必须在不重启系统的情况下成为可能。分布式应用程序由大量交互组件构建而成,升级此类软件是一项关键操作。

电信平台要求所有服务都达到 99.999% 的正常运行时间。在维护操作期间不能停止服务,因为这会影响其他服务平台和连接到它的用户请求。软件升级必须逐步执行。基于事件的机制为分布式应用程序引入了这种能力的可能性。正如我们在图 2 中看到的那样,如果通信是异步执行的,则软件层之间没有直接依赖关系。然后可以替换某些应用程序部分而不会造成重大干扰。

结论

基于事件的机制提供了一种新的编程模型,为软件开发人员提供了独特而强大的异步进程执行支持。当然,它与我们习惯的顺序编程风格截然不同,但提供了一个结构更合理的软件开发设计框架。它还简化了复杂软件组件的集成和互操作性。

这种机制的优势在于它能够将同步代码和异步代码组合在同一个应用程序中——甚至在同一个代码例程中混合这两种类型的模型。通过这种混合方法,可以根据具体情况利用它们各自的功能。这种模型尤其有利于安全软件的开发和任务关键型应用程序的长期维护。

在以后的文章中,我们将展示如何实现 AEM 以在 Linux 内核中提供这种支持,以及如何将其用于软件开发。

致谢

感谢开放系统实验室审查和批准本文的发布,感谢爱立信加拿大研究中心的 Laurent Marchand 提供的有益评论,以及舍布鲁克大学的学生 Philippe Meloche。

电子邮件:Frederic.Rossi@ericsson.ca

Frederic Rossi 是加拿大蒙特利尔爱立信研究公司部门开放系统实验室的研究员。他参与了旨在设计内核组件以推进运营商级操作系统的研究活动。可以通过电子邮件 frederic.rossi@ericsson.ca 与他联系。

加载 Disqus 评论