您的位置:首页 > 运维架构 > Linux

将应用程序从 OS/2 移植到 Linux 上: 第 1 部分,线程、互斥锁、信号量

2007-10-30 11:11 591 查看

将应用程序从 OS/2 移植到 Linux 上: 第 1 部分,线程、互斥锁、信号量

POSIX、OS/2 以及如何在两者之间架起桥梁



级别: 初级

Dinakar Guniguntala, 资深软件工程师, IBM
Sathyan Doraiswamy (dsathyan@in.ibm.com), 资深软件工程师, IBM
Anand K. Santhanam (asanthan@in.ibm.com), 软件工程师, IBM
Srinivasan S. Muthuswamy (smuthusw@in.ibm.com), 软件工程师, IBM
Rinson Antony (arinson@in.ibm.com), 软件工程师, IBM
Brahmaiah Vallabhaneni (bvallabh@in.ibm.com), 软件工程师, IBM

2004 年 4 月 01 日

Linux 是新千年里最杰出的操作系统,而传统的操作系统,如 OS/2,现在正在逐渐淘汰出局。本系列文章是为那些正经受移植痛苦的开发人员撰写的,可以帮助他们将 OS/2 系统驱动和应用程序移植到 Linux 上。文中介绍了如何将各种 OS/2 调用一一映射到 Linux 上,涉及的内容包括线程、IPC、内存管理、定时器处理、文件处理等。此外,文中还介绍了一些可以从 OS/2 映射到 Linux 上的预处理器指令和编译器/链接器选项。本系列文章总共三篇,本文是其中的第一篇。
本文介绍了有关线程和同步原语映射方面的知识。本系列的后续文章将会介绍系统调用、网络调用和其他编程指令的情况。

线程

线程是在 OS/2 上的基本执行单元,也是 OS/2 进程内部的可调度单元。线程的调度及优先级是完全在内核中处理的。

Linux 内核使用的是进程模型,而不是线程模型。然而,Linux 内核为创建线程提供了一种轻量级的进程框架,而线程的真正实现是在用户空间中。目前,可用的线程库有很多种(LinuxThreads、NGPT、NPTL,等等)。本文的研究是以 LinuxThreads 库为基础进行的,但是其内容对 Rad Hat 的 Native POSIX Threading Library(本地 POSIX 线程库,NPTL)也同样适用。

本节介绍 OS/2 和 Linux 中的线程机制。内容涉及创建线程、设置线程属性以及改变线程优先级等所使用的调用。OS/2 与 Linux 中这些调用的对应关系如下表所示:

表 1. 线程映射表

OS/2 Linux
类别
_beginthread
pthread_create

pthread_attr_init

pthread_attr_setstacksize

pthread_attr_destroy
可映射的
_endthread
pthread_exit
可映射的
DosWaitThread
pthread_join

pthread_attr_setdetachstate

pthread_detach
可映射的
DosSetPriority
进程
setpriority

sched_setscheduler

sched_setparam

线程
pthread_setschedparam

pthread_setschedpolicy

pthread_attr_setschedparam

pthread_attr_setschedpolicy
特定于上下文的
类别一列表示该 OS/2 指令是可映射的,还是特定于上下文的:

可映射的:这条 OS/2 指令可以映射为一条(或者若干条)指定的 Linux 指令,这需要对类型、参数、返回代码等进行检查。

特定于上下文的:给定的 OS/2 指令在 Linux 中可能有等价的指令,也可能没有,或者是 Linux 中能够提供相似功能的指令不止一条。不论是哪种情况,我们都要根据应用程序的上下文来决定具体使用哪一条(或哪几条)Linux 指令。


Linux 入门

自从 1991 年 Linux 诞生以来,就随着它的流行而不断成长起来。Linux 的主要优势在于它是开放源代码的自由软件(有关自由软件的详细内容,请您参看本说明框底部的资源清单)

当 Linux 最新的主力版本(内核 2.6)发布时,越来越多的公司正将它们的业务过程转移到开放标准上来,并且发布了一些基于 Linux 的产品。

在大公司中,IBM 在开放源代码社团中的表现是最为活跃的。IBM 尤其关注一些特定的领域,比如如何改进 Linux 的可升级能力和稳定性,重点在企业计算方面。现在,大多数 IBM eServer 服务器都捆绑了 Linux。

作为其 Linux 计划的一部分,IBM 正积极参与到若干项目中,致力于将基于 OS/2 等遗留操作系统的产品迁移到 Linux 上。

自由软件基金会能告诉您有关 自由软件 的全部情况。

要更好地理解自由软件和开放源代码软件之间的区别,请您阅读 开放源代码规范

IBM developerWorks 上有一个页面专门为 Linux 初学者提供资料和信息。

目前,大多数 IBM eServer服务器都捆绑了 Linux。

线程的创建

在 OS/2 中,我们用
_beginthread
这个系统调用来产生线程:

int _beginthread(void (*start_address) (void *), (void *) stack,
unsigned stack_size, void *arg);


Linux 则调用 pthread 库中的
pthread_create()
来产生线程:

int pthread_create (pthread_t *thread_id, pthread_attr_t *threadAttr,
void * (*start_address)(void *), void * arg);


指定线程函数

在 OS/2 系统调用
_beginthread
中,参数
start_address
表示新创建的线程将要执行的函数的地址。这个线程函数必须用
_Optlink
链接标记进行声明和编译。

在 Linux 库调用
pthread_create()
中,参数
start_address
表示新创建的线程将要执行的函数的地址。

向线程函数传递参数

在 OS/2 中,系统调用
_beginthread()
的参数 arg 用于指定要传递给新建线程的参数。它指定了要传递给新建线程的数据项的地址。

在 Linux 中,库调用
pthread_create()
的参数 arg 用于指定要传递给新建线程的参数。

设置堆栈大小

OS/2 系统调用
call _beginthread()
的参数 stack_size 表示将要分配给新建线程的堆栈大小,单位为字节。堆栈大小是 4K 的正整数倍,最小为 8K。

在 Linux 中,堆栈大小是在属性对象中设置的;也就是说,我们将
pthread_attr_t
类型的
threadAttr
参数传递给
pthread_create()
库调用。在设置这个对象的属性之前,先要调用
pthread_attr_init()
对其进行初始化。通过调用
pthread_attr_destroy()
可以销毁这个属性对象:

int pthread_attr_init(pthread_attr_t *threadAttr);
int pthread_attr_destroy(pthread_attr_t *threadAttr);


请注意,所有形如
pthread_attr_setxxxx
的调用实现的功能都与
pthread_xxxx
这样的调用(如果存在的话)类似,不过
pthread_attr_setxxxx
只能用于在创建线程之前对将要作为参数传递给
pthread_create
的属性对象进行更新。同理,在创建好线程之后,我们可以使用任何形如
pthread_xxxx
的调用。

pthread_attr_setstacksize()
可以用于设置堆栈大小:

int pthread_attr_setstacksize(pthread_attr_t *threadAttr, int
stack_size);


线程状态

在 OS/2 中并不存在显式地维护与线程终止有关的线程状态。不过,我们可以通过调用
DosWaitThread()
,让一个线程显式地等待进程内某个特定或非特定线程的终止。

在 Linux 中,缺省情况下线程是以一种可连接(joinable)状态被创建的。在可连接状态下,其他线程可以在一个线程终止时对其进行同步,然后用
pthread_join()
函数恢复其终止代码。这个可连接线程的线程资源只有在它被插入之后才能释放。

OS/2 用
DosWaitThread()
来等待一个线程的终止:

APIRET DosWaitThread(PTID ptid, ULONG option);


Linux 用
pthread_join
完成相同的工作


int pthread_join(pthread_t *thread, void **thread_return);


在分离(detached)状态下,线程的资源在线程终止时会被立即释放。您可以通过对线程属性对象调用
pthread_attr_setdetachstate()
来设置分离状态:

int pthread_attr_setdetachstate (pthread_attr_t *attr, int
detachstate);


以可连接状态创建的线程以后还可以使用
pthread_detach()
调用来进入分离状态:

int pthread_detach (pthread_t id);


线程的退出

在 OS/2 中,系统调用
_endthread()
用于终止线程。

Linux 中的等价库调用为
pthread_exit()
retval
表示线程的返回值,其他线程可以通过调用
pthread_join()
获取这个值:

int pthread_exit(void* retval);


改变优先级

OS/2 用系统调用
DosSetPriority()
改变进程,或正在运行的进程中的线程的优先级:

int DosSetPriority(int scope, int class, int delta, int id);


在本例中,scope 是一个进程的
PRTYS_PROCESS
;一个线程级别的
PRTYS_THREAD
是该线程的优先级。下面列出可以设置的不同优先级:

不改变:
PRTYC_NOCHANGE


空闲时间:
PRTYC_IDLETIME


常规:
PRTYC_REGULAR


时间关键型:
PRTYC_TIMECRITICAL


固定高的优先级:
PRTYC_FOREGROUNDSERVER


其中,参数
delta
是应用于该进程优先级的改变量。其值必须在 -31 到 +31 之间。该值越高,优先级就越高。当前进程或线程的 id 都为 0。

Linux 中提供了很多调用,可以用来修改或改变线程的优先级。您应该根据应用程序的上下文环境选择使用不同的调用。

普通或常规的进程/线程

Linux 系统调用
setpriority()
用来设置或修改普通进程和线程的优先级。参数 scope 是
PRIO_PROCESS
。将 id 设为 0,可以修改当前进程(或线程)的优先级。与前面一样,
delta
也是一个优先级数值 —— 不过这一次 delta 的范围变成了 -20 到 20。还要注意:在 Linux 中,delta 值越小,优先级就越高。因此您可以对
IDLETIME
优先级将 delta 设置为 +20,而对
REGULAR
优先级则将 delta 设置为 0。

在 OS/2 中,优先级的范围是从 0 (优先级最低)到 31 (优先级最高)。但是在 Linux 中,普通的非实时进程优先级范围都是从 -20(优先级最高)到 +20(优先级最低)。在使用之前,必须对优先级进行映射。

int setpriority(int scope, int id, int delta);


时间关键型并且实时的进程和线程

Linux 系统调用
sched_setscheduler
可以用来修改当前正在运行的进程的调度优先级和调度策略:

int sched_setscheduler(pit_t pid, int policy, const struct sched_param *param);


参数
policy
是调度策略。policy 允许的值有
SCHED_OTHER
(用于普通的非实时调度)、
SCHED_RR
(实时的轮询策略)和
SCHED_FIFO
(实时的 FIFO 策略)。

清单 1. 修改调度优先级

struct sched_param {
...
int sched_priority;
...
};


此处,
param
是指向一个代表调度优先级的结构的指针。对于实时调度策略来说,该值的范围为从 1 到 99。对于其他策略(普通的非实时进程)来说,该值是 0。

在 Linux 中,对于一种已知的调度策略来说,我们也可以使用系统调用
sched_setparam
来只修改进程的优先级:

int sched_setparam(pit_t pid, const struct sched_param *param);


LinuxThreads
库中的
pthread_setschedparam
调用是
sched_setscheduler
的一个用于线程的版本,可以动态地修改正在运行的线程调度优先级和调度策略:

int pthread_setschedparam(pthread_t target_thread, int policy,
const struct sched_param *param);


参数
target_thread
代表要修改优先级的线程,参数
param
表示优先级。

LinuxThreads
库中的
pthread_attr_setschedpolicy
调用和
pthread_attr_setschedparam
调用可以用于在创建线程之前设置线程属性对象的调度策略和优先级:

int pthread_attr_setschedpolicy(pthread attr_t *threadAttr, int policy);

int pthread_attr_setschedparam(pthread attr_t *threadAttr, const
struct sched_param *param);


下面这个例子可以非常清楚地说明创建线程和修改优先级的实现从 OS/2 到 Linux 的映射方式。

线程的例子

在这个 OS/2 的例子中,thread1 是一个普通的线程,而 thread2 则是一个时间关键型实时线程。

清单 2. OS/2 线程代码




main () ...{




enum ...{StackSize = 120*1024}; /**//* stack size set to 120 K */




/**//* create a thread */


int thread1 = _beginThread (RegularThread, NULL, StackSize,


NULL);


int thread2 = _beginThread (CriticalThread, NULL,


StackSize, NULL);


}




/**//* Normal /Regular Priority Thread */




static void RegularThread (void *) ...{




/**//* Set the priority of the thread. 0 is passed as an


argument to identify the current thread */


int iRc = DosSetPriority(PRTYS_THREAD, PRTYC_REGULAR, 20,


0);


_endThread();


}




/**//* Realtime time critical Priority Thread */




static void CriticalThread (void *) ...{


int iRc = DosSetPriority(PRTYS_THREAD, PRTYC_TIMECRITICAL,


20, 0);


_endThread();


}



下面是与上面这段 OS/2 代码等价的 Linux 代码:

清单 3. 等价的 Linux 线程代码


#define STATIC 0


static void * RegularThread (void *);


static void * CriticalThread (void *);




/**//* Regular non-realtime Thread function */


static void * RegularThread (void *d)




...{


int priority = 10; //0 for Regular, +20 for Idletime




/**//* Set the priority - normal non-realtime priority */


int irc = setpriority(PRIO_PROCESS, 0, priority);


pthread_exit(NULL);


}




/**//* Time Critical Realtime Thread function */


static void * CriticalThread (void *d)




...{




if (STATIC == 0) ...{




/**//* change the thread priority dynamically */


struct sched_param param; // scheduling priority


int policy = SCHED_RR; // scheduling policy




/**//* Get the current thread id */


pthread_t thread_id = pthread_self();




/**//* To set the scheduling priority of the thread */


param.sched_priority = 99;


int irc = pthread_setschedparam(thread_id, policy, ¶m);


}


pthread_exit(NULL);


}


int main (void)




...{


pthread_t thread1, thread2; // thread identifiers


pthread_attr_t threadAttr1, threadAttr2; // thread attributes


struct sched_param param; // scheduling priority


int policy = SCHED_RR; // scheduling policy - real time


int irc, rc;




rc = pthread_attr_init(&threadAttr1); /**//* init the attr 1*/




rc = pthread_attr_init(&threadAttr2); /**//* init the attr 2*/




/**//* Set the stack size of the thread */


irc = pthread_attr_setstacksize(&threadAttr1, 120*1024);


irc = pthread_attr_setstacksize(&threadAttr2, 120*1024);




/**//* Set thread to detached state. No need for pthread_join*/


irc = pthread_attr_setdetachstate(&threadAttr1,


PTHREAD_CREATE_DETACHED);


irc = pthread_attr_setdetachstate(&threadAttr2,


PTHREAD_CREATE_DETACHED);




if (STATIC == 1) ...{




/**//* priority is set statically */




/**//* Set the policy of the thread to real time*/


irc = pthread_attr_setschedpolicy(&threadAttr2, policy);




/**//* Set the scheduling priority of the thread - max


priority*/


param.sched_priority = 99;


irc = pthread_attr_setschedparam(&threadAttr2, ¶m);


}




/**//* Create the threads */


irc = pthread_create(&thread1, &threadAttr1, RegularThread, NULL);


irc = pthread_create(&thread2, &threadAttr2, CriticalThread,


NULL);




/**//* Destroy the thread attributes */


irc = pthread_attr_destroy(&threadAttr1);


irc = pthread_attr_destroy(&threadAttr2);


}










回页首
互斥锁

在互斥锁的映射中,要考虑以下几点:

互斥锁的类型:OS/2 中互斥锁信号量缺省是递归的,而在 Linux 中则并非如此。

初始状态:在 OS/2 中,互斥锁在创建时就可以获得,而在 Linux 中则并不支持这种方式。要在 Linux 中达到同样的功能,在创建之后必须对互斥锁显式地加锁。

信号量:OS/2 支持有名信号量和无名信号量,而在 Linux pthreads 的当前实现中,并不支持这种功能。

超时:在 OS/2 中,在获取互斥锁时可以指定超时时间;而在 Linux 中并不支持这种功能(本文后文中会加以介绍)。

互斥锁的映射如下表所示:

表 2. 互斥锁映射表

OS/2 Linux 类别
DosCreateMutexSem

DosRequestMutexSem

DosReleaseMutexSem

DosCloseMutexSem
pthread_mutex_init

pthread_mutex_lock/pthread_mutex_trylock

pthread_mutex_unlock

pthread_mutex_destroy
可映射的
创建互斥锁

在 OS/2 中,系统调用
DosCreateMutexSem()
用来创建互斥锁:

APIRET DosCreateMutexSem (PSZ pszName, PHMTX phmtx, ULONG flAttr, BOOL32 fState);


参数
pszName
是用 ASCII 字母表示的互斥锁名字。

在 Linux 中,我们可以使用 pthread 库的
pthread_mutex_init()
调用来创建互斥锁:

int pthread_mutex_init(pthread_mutex_t *mutex, const
pthread_mutexattr_t *mutexattr);


在 Linux 中,有“三种”互斥锁。互斥锁的“类型”决定了如果一个线程试图对一个已经使用
pthread_mutex_lock
加锁过了的互斥锁再加锁会产生什么问题:

快速互斥锁:在试图使用
pthread_mutex_lock()
对互斥锁加锁时,调用线程永远挂起。

递归互斥锁:
pthread_mutex_lock()
立即返回,返回值是一个成功代码。

带错误检验的互斥锁:
pthread_mutex_lock()
立即返回,返回值是错误代码 EDEADLK。

我们可以使用两种方法来设置互斥锁的“类型”。静态的设置方法如下:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
/* For Fast mutexes */
pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
/* For recursive mutexes */
pthread_mutex_t errchkmutex =
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;/* For errorcheck mutexes */


另外一种设置互斥锁“类型”的方法是使用一个互斥锁的属性对象。要实现这种功能,就要在调用
pthread_mutexattr_init()
来初始化对象之后再调用
pthread_mutexattr_settype()
来设置互斥锁的“类型”:

int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int kind);


参数“kind”允许的值如下所示:

PTHREAD_MUTEX_FAST_NP


PTHREAD_MUTEX_RECURSIVE_NP


PTHREAD_MUTEX_ERRORCHECK_NP


属性对象可以使用
pthread_mutexattr_destroy()
销毁:

int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);


加锁或获取互斥锁

OS/2 使用
DosRequestMutexSem()
对互斥锁加锁(或称为获取互斥锁):

APIRET DosRequestMutexSem(HMTX hmtx, ULONG ulTimeout);


参数
ulTimeOut
指明该线程要被锁定的最长时间:

在 Linux 中,pthread 库调用
pthread_mutex_lock()
pthread_mutex_trylock()
都可以用来获取互斥锁:

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);


在这两个调用中,第一个
pthread_mutex_lock()
是一个阻塞调用 —— 这就是说,如果一个互斥锁已经被其他线程加锁了,那么
pthread_mutex_lock()
就会挂起产生调用的线程,直到该互斥锁被释放为止。

pthread_mutex_trylock()
则不同,如果这个互斥锁已经被其他进程加锁,那么
pthread_mutex_trylock()
就会立即返回。

注意,在 Linux 中并没有超时选项。在一个循环(该循环计数超时值)的延时期间执行一个非阻塞的
pthread_mutex_trylock()
调用,也能达到同样的效果。(示例代码请参考 清单 6)。

解锁或释放互斥锁

OS/2 使用
DosReleaseMutexSem()
来释放对一个互斥信号量的所有权:

APIRET DosReleaseMutexSem(HMTX hmtx);


Linux 使用
pthread_mutex_unlock()
来释放互斥锁:

int pthread_mutex_unlock(pthread_mutex_t *mutex);


注意,互斥函数并不是异步信号安全的,也不应该在信号处理函数中调用。通常,在一个信号处理函数中调用
pthread_mutex_lock
pthread_mutex_unlock
可能会引起调用线程的死锁。

销毁互斥锁

在 OS/2 中,
DosCloseMutexSem()
用来关闭一个互斥信号量:

APIRET DosCloseMutexSem (HMTX hmtxSem);


在 Linux 中,
pthread_mutex_destroy()
用来销毁一个互斥对象,并释放它持有的所有资源。这个调用还会检查当时这个互斥锁是否已经被释放了:

int pthread_mutex_destroy(pthread_mutex_t *mutex);


设置状态

在 OS/2 中,
DosCreateMutexSem()
调用的
fState
参数用来设置互斥锁的初始状态。
fState
允许为以下的两个值:

值 1 表示创建一个初始状态为已经拥有的互斥锁。

值 0 表示创建一个初始状态为未拥有的互斥锁。

在 Linux 中,我们并不能使用
pthread_mutex_init()
调用来设置互斥锁的状态,但是可以使用以下步骤来达到这个目的:

使用
pthread_mutex_init()
创建一个互斥锁。

使用
pthread_mutex_lock()
对这个互斥锁进行加锁(或称为获取这个互斥锁)。

互斥锁的例子

清单 4. OS/2 互斥锁代码


hmtx hmtxSem; // semaphore handle


unsigned long ulRc; // return code




/**//* Create a un named mutex semaphore */


ulRc = DosCreateMutexSem (NULL, &hmtxSem, 0, FALSE);


ulRc = DosRequestMutexSem (hmtxSem, (unsigned long)


SEM_INDEFINITE_WAIT);




/**//* Access the shared resource */


...




/**//* Release the mutex */


ulRc = DosReleaseMutexSem(hmtxSem);


...




/**//* Closes the semaphore */


ulrc = DosCloseMutexSem (hmtxSem);



请试着将这段代码与下面的 Linux 代码比较一下:

清单 5. Linux 互斥锁代码




/**//* Declare the mutex */


pthread_mutex_t mutex;




/**//* Attribute for the mutex */


pthread_mutexattr_t mutexattr;




/**//* Set the mutex as a recursive mutex */


pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE_NP);




/**//* create the mutex with the attributes set */


pthread_mutex_init(&mutex, &mutexattr);




/**//* lock the mutex */


pthread_mutex_lock (&mutex);




/**//* access the shared resource */


..




/**//* unlock the mutex */


pthread_mutex_unlock (&mutex);


...




/**//* destroy the attribute */


pthread_mutexattr_destroy(&mutexattr)




/**//* Close/destroy the semaphore */


irc = pthread_mutex_destroy (&mutex);



下面这个例子说明了在试图获取一个互斥锁时,如何模拟超时功能:

清单 6. 在 Linux 中模拟超时功能


#define TIMEOUT 100 /* 1 sec */


struct timespec delay;




/**//* Declare the mutex */


pthread_mutex_t mutex;




while (timeout < TIMEOUT ) ...{


delay.tv_sec = 0;




delay.tv_nsec = 1000000; /**//* 1 milli sec */


irc = pthread_mutex_trylock(&mutex);




if (!irc) ...{




/**//* we now own the mutex */


break;


}




else ...{




/**//* check whether somebody else has the mutex */




if (irc == EPERM ) ...{




/**//* sleep for delay time */


nanosleep(&delay, NULL);


timeout++ ;


}




else...{




/**//* error */


}


}


}










回页首
信号量

除了 pthread 条件变量之外,Linux 还提供了 POSIX 信号量来映射 OS/2 的事件信号量构造(Linux 还提供了 SVR 兼容的 semop、semctl 等类似的调用,但是本文的目的仅限于介绍 POSIX 和 LinuxThreads 实现)。它们都有自己的优点和缺点,您需要根据应用程序的逻辑来决定到底使用哪种技术。在对事件信号量进行映射时,要考虑以下几点:

信号量的类型:OS/2 支持有名和无名的事件信号量,有名信号量可以在进程间进行共享。Linux 不支持这种功能。

初始状态:在 OS/2 中,信号量可以有一个初值。在 Linux 中, 虽然 POSIX 信号量支持这种功能,但是 pthreads 并不支持这种功能。在使用 pthreads 时要考虑这个问题。

超时:OS/2 事件信号量支持超时等待。在 Linux 中,POSIX 信号量实现只支持不确定的等待(阻塞)。pthreads 实现既可以支持阻塞,也可以支持超时。
pthread_cond_timedwait()
调用提供了一个等待时的超时时间,而
pthread_cond_wait()
则用于不确定的等待。

信号:在 OS/2 中,唤醒一个信号量会唤醒正在等待这个信号量的所有线程。在 Linux 中,POSIX 线程实现一次只会唤醒一个线程,而 pthreads 实现有一个
pthread_cond_signal()
调用,它可以唤醒一个线程;还有一个
pthread_cond_broadcast()
调用,可以唤醒所有正在等待这个信号量的线程。

同步:在 OS/2 中,事件信号量是异步的。在 Linux 中,POSIX 信号量是异步的,而 pthreads 条件变量是同步的。

总之,只有在不需要超时和广播唤醒的情况下才可以考虑 POSIX 信号量。如果应用程序逻辑需要广播唤醒和超时,可以使用 pthread 条件变量。

表 3. 信号量映射表

OS/2 POSIX Linux 调用 pthread Linux 调用 类别
DosCreateEventSemsem_initpthread_cond_init上下文相关
DosPostEventSemsem_postpthread_cond_signal / pthread_cond_broadcast上下文相关
DosWaitEventSemsem_wait/sem_trywaitpthread_cond_wait / pthread_cond_timedwait 上下文相关
DosCloseEventSemsem_destroypthread_cond_destroy上下文相关
创建事件信号量

OS/2 使用
DosCreateEventSem()
调用来创建事件信号量:

APIRET DosCreateEventSem (PSZ pszName,PHEV phev, ULONG flAttr, BOOL32 fState);


此处,
pszName
是一个指向信号量的 ASCII 名的指针。如果该参数为 NULL,那么
DosCreateEventSem()
就会创建一个无名的事件信号量。fState 用来设置事件信号量的状态。如果该值为 0,那么该信号量最初就被重置;如果该值为 1,那么该信号量就被发出。

在 Linux 中,系统调用
sem_init()
用来创建一个 POSIX 信号量:

int sem_init(sem_t *sem, int pshared, unsigned int value);


其中
value
(信号量的计数)被设置为该信号量的初值。

Linux pthreads 使用
pthread_cond_init()
来创建一个条件变量:

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);


pthread_cond_t
类型的条件变量可以使用常量
PTHREAD_COND_INITIALIZER
静态地进行初始化;还可以使用
pthread_condattr_init()
进行初始化,后者会对与这个条件变量有关的属性进行初始化。系统调用
pthread_condattr_destroy()
用来销毁属性:

int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);


等待事件信号量

在 OS/2,
DosWaitEventSem()
被用来阻塞一个线程并等待事件信号量:

APIRET DosWaitEventSem (HEV hev, ULONG ulTimeOut);


此处参数
ulTimeOut
指明了超时时间。如果在这段指定的时间内,信号量还没有被释放,那么
DosWaitEventSem()
调用就会返回一个错误代码。如果指定的超时时间是
-1
,那么它就会阻塞调用线程。

Linux POSIX 信号量使用
sem_wait()
挂起调用线程,直到这个信号量的计数为非 0;然后自动减少这个信号量的计数:

int sem_wait(sem_t * sem)


在 POSIX 信号量中,没有超时功能。这可以在一个循环中执行非阻塞的
sem_trywait()
来实现,该循环可以计数超时值:

int sem_trywait(sem_t * sem);


Linux pthreads 使用
pthread_cond_wait()
来不确定地阻塞调用线程:

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);


另一方面,如果调用线程需要被阻塞一段指定的时间,就可以使用
pthread_cond_timedwait()
来阻塞线程。如果在这段指定的时间内条件变量还没有被释放,那么
pthread_cond_timedwait()
就会返回一个错误:

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t
*mutex,const struct timespec *abstime);


此处,
abstime
参数指定了一个绝对时间(具体来讲,是从格林威治标准时间 1970 年 1 月 1 日 0 时 0 分 0 秒开始经过的时间)。

唤醒事件信号量

OS/2 使用
DosPostEventSem()
来释放事件信号量,这样可以唤醒所有正在等待这个信号量的线程:

APIRET DosPostEventSem (HEV hev);


Linux POSIX 信号量使用
sem_post()
来释放事件信号量,这样可以唤醒一个正在因该信号量而阻塞的线程:

int sem_post(sem_t * sem);


pthread_cond_signal()
调用用来在 LinuxThreads 中唤醒正在等待这个条件变量的一个线程,而
pthread_cond_broadcast()
调用则用来唤醒正在等待这个条件变量的所有线程。

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);


注意,条件函数不是异步信号安全的,不应该在信号处理函数中调用。在特殊情况下,如果在信号处理函数中调用
pthread_cond_signal
pthread_cond_broadcast
,可能会导致调用线程的死锁。

销毁事件信号量

OS/2 使用
DosCloseEventSem()
来销毁信号量:

APIRET DosCloseEventSem (HEV hev);


Linux POSIX 信号量使用
sem_destroy()
来销毁信号量:

int sem_destroy(sem_t * sem);


在 Linux pthreads 中,
pthread_cond_destroy()
用来销毁条件变量:

int pthread_cond_destroy(pthread_cond_t *cond);


事件信号量的例子

清单 7. OS/2 信号量代码


HEV hevIpcInterrupt;


unsigned long ulPostCnt = 0;


unsigned long ulrc; // return code


unsigned long ulTimeout = 10 ; // timeout value




/**//* create event semaphore */


DosCreateEventSem (NULL, &hevIpcInterrupt, 0, TRUE);




/**//* In Thread A */




/**//* Wait forever for event to be posted */


DosWaitEventSem (hevIpcInterrupt, (unsigned long)


SEM_INDEFINITE_WAIT);




/**//* immediately unblocked as the semaphore is already posted */




/**//* Waits until the semaphore is posted */


DosWaitEventSem (hevIpcInterrupt, (unsigned long)


SEM_INDEFINITE_WAIT);




/**//* In Thread B */


DosPostEventSem(hevIpcInterrupt);




/**//* Close the semaphore */


ulrc = DosCloseEventSem (hevIpcInterrupt);



下面的 Linux 例子使用 pthread 条件变量,用来在两个线程 A 和 B 之间进行同步:

清单 8. Linux 条件变量


pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;


pthread_cond_t condvar = PTHREAD_COND_INITIALIZER;


struct timeval tvTimeNow; //Absolute current time


struct timezone tzTimeZone; //Timezone


struct timespec tsTimeOut; // Input for timedwait




/**//* In Thread A */


...


pthread_mutex_lock(&mutex);




/**//* signal one thread to wake up */


pthread_cond_signal(&condvar);


pthread_mutex_unlock(&mutex);




/**//* this signal is lost as no one is waiting */




/**//* In Thread B */


pthread_mutex_lock(&mutex);


pthread_cond_wait(&condvar, &mutex);


pthread_mutex_unlock(&mutex);




/**//* Thread B blocks indefinitely */




/**//* ---------------------------------------------------------------------


--------------------------------------------------------------------- */




/**//* One way of avoiding losing the signal is as follows */




/**//* In Thread B - Lock the mutex early to avoid losing signal


*/


pthread_mutex_lock (&mutex);




/**//* Do work */


.......




/**//* This work may lead other threads to send signal to


thread B */




/**//* Thread A now tries to take the mutex lock


to send the signal but gets blocked */


...


pthread_mutex_lock(&mutex);




/**//* Thread B: Get the current time */


gettimeofday (&tvTimeNow, &tzTimeZone);




/**//* Calculate the absolute end time - 10 seconds wait */


tsTimeOut.tv_sec = tvTimeNow.tv_sec + 10 ;




/**//* Thread B waits for the specified time for the signal to be


posted */


pthread_cond_timedwait (&condvar, &mutex, &tsTimeOut );




/**//* Thread A now gets the lock and can


signal thread B to wake up */


pthread_cond_signal(&condvar */


pthread_mutex_unlock(&mutex);




... /**//* Thread B


unblocks upon receipt of signal */


pthread_mutex_unlock (&mutex);



下面的清单使用 POSIX 信号量来实现对线程 A 和 B 之间的同步:

清单 9. Linux POSIX 信号量




sem_t sem; /**//* semaphore object */




int irc; /**//* return code */




/**//* Initialize the semaphore - count is set to 1*/


irc = sem_init (sem, 0,1);


...




/**//* In Thread A */




/**//* Wait for event to be posted */


sem_wait (&sem);




/**//* Unblocks immediately as semaphore initial count was set to 1 */


.......




/**//* Wait again for event to be posted */


sem_wait (&sem);




/**//* Blocks till event is posted */




/**//* In Thread B */




/**//* Post the semaphore */


...


irc = sem_post (&sem);




/**//* Destroy the semaphore */


irc = sem_destroy(&sem);










回页首
结束语

本文介绍了从 OS/2 到 Linux 的映射中有关线程、互斥锁和事件信号量的知识。在将任何程序从 OS/2 移植到 Linux 上时,您都可以参考这些内容。本文中给出的提示和警告有助于简化您移植程序的设计;有关本文中所提到的所有 Linux 调用的更详细内容,您可以参考相关的手册页。本系列文章的下一篇将介绍有关内存管理、文件处理和设备驱动接口的系统调用的映射。

参考资料

您可以参阅本文在 developerWorks 全球站点上的 英文原文.

H.M. Deitel 和 M.S. Kogan 所著的 The Design of OS/2(Addison-Wesley, 1992)仍然可以在使用书籍站点上找到;ISBN 为 0-201-54889-5。

请参考 Bradford Nichols、Dick Buttlar 和 Jacqueline Proulx Farrel 所著的 Pthreads Programming (O'Reilly & Associates, 1996)一书的在线代码示例。

对于有关 Linux 中线程编程的更详细信息,不要忘记参考 Linux 的“info”和特殊调用的手册页。

在 IBM developerWorksLinux 专区中,之前有两篇文章介绍过线程编程:Peter Seebach 的“ pthreads 的基本用法”( developerWorks,2004 年 3 月)和 Daniel Robbin 的“ POSIX 线程详解 ”( developerWorks,2000 年 7 月)。

IBM 的 OS/2 Strategy for 2004推荐了一个到 WebSphere 软件平台的转换。

大部分 IBM eServer服务器现在都捆绑了 Linux。

IBM 红皮书为那些希望切换到 Windows 或 Linux 上的 OS/2 管理员提供了一份 OS/2 Server Transition

IBM Global Services Linux Porting Service Practice提供了一个 OS/2 到 Linux 定制应用估价、移植和测试服务。

Porting OS/2 applications to Linux (in C)( developerWorks,2002 年 3 月)介绍了 Linux 团队在将 LANDP(LAN 分布式平台)从 OS/2 移植到 Linux 上时,LANDP 所碰到的一些问题。

您也可以在同一台机器上同时使用 OS/2 和 Linux。请参考南加利福尼亚州的 OS/2 用户组 的 How to use Linux and OS/2 Together资源列表,以及 SUSE LINUX 入门中的 OS/2 and Linux with the OS/2 Boot Manager对双重引导的一个简要介绍。

IBM developerWorks Linux专区可以找到为 Linux 开发人员准备的更多参考资料。

请参考 Developer Bookstore 中 Linux 部分中大量有关 Linux 的书籍。

作者简介



Dinakar Guniguntala 拥有 REC Surat 的计算机工程的工程学士学位。他从 1998 年 2 月开始,就一直就职于印度的 IBM Global Services,在这里他从事操作系统内幕的研究。他的工作包括 OS/2 内核、图形引擎、文件系统、嵌入式 Linux、Linux 内核和 Linux 线程库。可以通过 dgunigun-at-in.ibm.com与他联系。



Sathyan Doraiswamy 拥有 Bangalore 的 Visvesvaraya 大学工程学院电子与通信专业的工程学士学位。他于 1997 年 6 月加入了 IBM 印度部门。他就职于工程与技术服务部门,专攻嵌入式和实时系统。他具有在 Windows 和 Linux 以及 Vxworks 上编写设备驱动程序的丰富经验。可以通过 dsathyan-at-in.ibm.com与他联系。



Anand K. Santhanam 在印度的 Madras 大学获得了计算机科学的工程学士学位。从 1999 年 7 月以来,他一直就职于 IBM Global Services (软件实验室),在那里他从事 ARM-Linux、字符/基于 X 的设备驱动、嵌入式系统上的电源管理、PCI 设备驱动程序以及Linux 上的多线程程序的开发。他的其他兴趣包括 OS 内幕和网络。可以通过 asanthan-at-in.ibm.com与他联系。



Srinivasan S. Muthuswamy 在印度的 Coimbatore 国立科技大学获得了计算机工程的工程学士学位。从 2000 年 8 月以来,他就一直就职于印度的 IBM Global Services。他从事 Linux 上多线程应用程序的开发,以及使用 WSBCC/Java 技术/WebSphere/MQSeries 技术开发 Web 应用程序。他还从事有关 Crystal Reports 和 Lotus Domino 等方面的工作。 可以通过 smuthusw-at-in.ibm.com与他联系。



Rinson Antony 在印度的 Bombay 大学获得了计算机工程的工程学士学位。他从 2000 年 7 月以来,一直就职于印度的 IBM Global Services。他的工作包括 Linux 上开发多线程应用程序和使用 Java 技术/WebSphere/XML 开发 Web 应用程序。他的其他兴趣是 PCI 设备驱动程序和网络。可以通过 arinson-at-in.ibm.com与他联系。



Brahmaiah Vallabhaneni 在印度的 BITS, Pilani 获得了工程学士学位。从 2002 年 8 月以来,他一直就职于印度的 IBM Global Services。他的工作包括在嵌入式系统上移植 GNU C 编译器,开发字符设备驱动程序和电源管理程序,以及在 Linux 上开发 PCI 设备驱动程序和多线程程序。他的其他兴趣包括 Linux 内幕和网络。可以通过 bvallabh-at-in.ibm.com与他联系。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐