本文主要叙述了目前 Linux 环境需要提供健壮和实时同步机制的必要性,并提出了实现的一个方案。这种同步机制对于 linux 进一步开拓服务器市场是非常重要的,尤其是电信市场。
1: 背景
自从多线程编程的概念出现在 Linux 中以来,Linux 多线程应用的发展总是与两个问题脱不开干系:兼容性、效率。 早期的 Linuxthreads 在兼容性和效率上都存在了严重的问题,特别是兼容性上的问题,严重阻碍了Linux上的跨平台应用(如Apache)采用多线程设计,从而使得 Linux 上的线程应用一直保持在比较低的水平。在 Linux 社区中,后来它被RedHat公司牵头研发的NPTL(Native Posix Thread Library)而替代。在技术实现上,NPTL仍然采用1:1的线程模型,并配合glibc和Linux内核在信号处理、线程同步、存储管理等多方面进行了优化。和LinuxThreads不同,NPTL没有使用管理线程,核心线程的管理直接放在核内进行,这也带了性能的优化。
虽然 NPTL 无论在兼容性还是在效率上面都大大优于Linuxthread。但是它并没有完全的符合POSIX规范,并不能提供实时的同步机制,而实时的同步机制对于服务器尤其是电信级别服务器是至关重要的。因为如果没有实时的同步机制,就非常容易引起优先级逆转的问题。这对于需要优先级服务的电信服务器来说是致命的。因此提供实时性的同步机制是完善Linux的一个重要环节。
另外在电信服务器上,如果只提供实时的同步机制是远远不够的。因为这些服务器需要提供99.9999% 的高可用性,这也就需要同步原语即使在异常情况下也能够提供高可用性。比如下面有两个线程A和B,它们分别用mutex来同步共享资源。 图1:传统的多线程编程
这里线程 B 先于线程 A 进入了临界区,线程 A 看到已经有人抢占了共享资源,它只好进入睡眠状态等待被 B 唤醒。然而不幸的是,线程 B 在领界区程序的执行过程中遇到了异常的情况,导致线程 B 的退出。这样线程 A 就只能沉睡下去永远无法被唤醒。如果线程 A 是一个有时间限制的客户服务,那就肯定无法按时完成客户的请求了。
鉴于以上的分析,我们认为为了让 Linux 在服务器市场上大展手脚,它至少需要提供实时并且健壮的同步机制。Intel OTC 的 Robust Mutex 正是为了提供这种服务而设立的一个项目。它不仅为 Linux 同步机制提供了实时的特性避免了优先级逆转,也提供了健壮的同步原语,另外也引入了动态死锁的检测模块。在技术上,整个项目包括两个部分:Linux 内核补丁即 Fusyn 和 Glibc 即 RTNPTL。以下的章节将介绍它们的基本实现原理。
2: Fusyn
Fusyn 是 Linux 内核的一个补丁,它在内核层次上解决了以上的一些问题,主要提供了以下几个特性:
1. 用优先级继承(PI)和优先级保护(PP)的策略来避免优先级逆转
2. 基于优先级提供了实时的唤醒机制
3. 提供了健壮性的同步机制
4. 同时也提供了死锁的检测机制
为了能够和 NPTL 更好的相处,Fusyn 采用了层次化的设计原则,在第一层上是Fuqueue。它和NPTL的等待队列 wait queue 类似,不同的是它提供了基于优先级排列的等待队列,这样就为提供优先级的唤醒机制奠定了基础。基于 Fuqueue,Fusyn 建立了 Fulock 层次,这个层次记录了每个同步锁的拥有者的信息,为提供锁的健壮性打下了基础。
2.1 Fuqueue
Fuqueue 和 Linux 内核中的等待队列类似,不同于普通的等待队列是基于 FIFO 策略的,而 Fuqueue 如下图所示,它是一个基于优先级排列的等待队列。
图2:优先级队列
因此当要唤醒下一个线程的时候,就会根据优先级进行唤醒,这样就提供了一些实时的特性。为了提供这种功能,结构体的定义如下:
图3:结构体
在定义中,成员 wlist 是一个按优先级排列的队列,而 fuqueue_ops 为一些 fuqueue相关的函数指针,如改变优先级函数 fuqueue_waiter_chprio() 和取消等待函数fuqueue_waiter_cancel() 等。为了使用优先级等待队列,可以先用 fuqueue_init() 函数进行初始化,当需要根据优先级插入到等待队列睡眠的时候,可以利用 fuqueue_wait() 函数。如果想根据优先级唤醒下一个线程,fuqueue_wake 则可以实现这种功能。另外为了能够提供 fulock 避免优先级逆转的功能,也实现了fuqueue_waiter_chprio() 函数,这个函数能够设置某一个线程的优先级并重新排序等待队列。而 fuqueue_waiter_cancel 则用来取消等待唤醒等待线程。
为了让 fuqueue 的功能也能够直接在用户空间得到应用,我们需要为它增加数据结构来记录用户空间同步锁和 fuqueue 队列的对应信息,在 Fusyn 的实现中,我们定义了以下结构体:
图4:结构体
在这里 vlocator 被用来在内核空间代表用户空间的同步锁,也即每一把用户空间的同步锁都在内核中存在了一个 vlocator 结构体,这样 ufuqueue 结构体就可以容易的把 fuqueue 等待队列和用户空间的同步锁直接联系起来。那么在 Linux 的同步原语的实现中,同步锁在内核中怎么标志呢?在 NPTL 的实现当中,线程间的同步锁用锁的虚拟地址表示,而进程间的同步锁则需要借助于文件系统来表示。因此结构体 vl_key 的定义如下:
如果您对本文有任何疑问或者建议,请到讨论区发表您的意见:
>>
论坛入口 <<
上一页12 3 下一页
上一篇:Linux内核配置选项 下一篇:Linux 初始 RAM 磁盘(initrd)概述
|