2006.5.22
12:12
作者:magichere
将应用程序从 OS/2 移植到 Linux 上: 第 1 部分,线程、互斥锁、信号量 Dinakar Guniguntala, 资深软件工程师, IBM
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 类别
可映射的:这条 OS/2 指令可以映射为一条(或者若干条)指定的 Linux 指令,这需要对类型、参数、返回代码等进行检查。 自从 1991 年 Linux 诞生以来,就随着它的流行而不断成长起来。Linux 的主要优势在于它是开放源代码的自由软件(有关自由软件的详细内容,请您参看本说明框底部的资源清单) 当 Linux 最新的主力版本(内核 2.6)发布时,越来越多的公司正将它们的业务过程转移到开放标准上来,并且发布了一些基于 Linux 的产品。 在大公司中,IBM 在开放源代码社团中的表现是最为活跃的。IBM 尤其关注一些特定的领域,比如如何改进 Linux 的可升级能力和稳定性,重点在企业计算方面。现在,大多数 IBM eServer 服务器都捆绑了 Linux。 作为其 Linux 计划的一部分,IBM 正积极参与到若干项目中,致力于将基于 OS/2 等遗留操作系统的产品迁移到 Linux 上。
在 OS/2 中,我们用 _beginthread 这个系统调用来产生线程: int _beginthread(void (*start_address) (void *), (void *) stack, Linux 则调用 pthread 库中的 pthread_create() 来产生线程: int pthread_create (pthread_t *thread_id, pthread_attr_t *threadAttr, 指定线程函数 在 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); 请注意,所有形如 pthread_attr_setxxxx 的调用实现的功能都与 pthread_xxxx 这样的调用(如果存在的话)类似,不过 pthread_attr_setxxxx 只能用于在创建线程之前对将要作为参数传递给 pthread_create 的属性对象进行更新。同理,在创建好线程之后,我们可以使用任何形如 pthread_xxxx 的调用。 pthread_attr_setstacksize() 可以用于设置堆栈大小: int pthread_attr_setstacksize(pthread_attr_t *threadAttr, int 线程状态 在 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 以可连接状态创建的线程以后还可以使用 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 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 策略)。
此处, 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, 参数 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 下面这个例子可以非常清楚地说明创建线程和修改优先级的实现从 OS/2 到 Linux 的映射方式。 线程的例子 在这个 OS/2 的例子中,thread1 是一个普通的线程,而 thread2 则是一个时间关键型实时线程。
main () { /* Normal /Regular Priority Thread */ /* Realtime time critical Priority Thread */
#define STATIC 0
互斥锁 在互斥锁的映射中,要考虑以下几点: 互斥锁的类型:OS/2 中互斥锁信号量缺省是递归的,而在 Linux 中则并非如此。 表 2. 互斥锁映射表 OS/2 Linux 类别
在 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 在 Linux 中,有“三种”互斥锁。互斥锁的“类型”决定了如果一个线程试图对一个已经使用 pthread_mutex_lock 加锁过了的互斥锁再加锁会产生什么问题: 快速互斥锁:在试图使用 pthread_mutex_lock() 对互斥锁加锁时,调用线程永远挂起。 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 另外一种设置互斥锁“类型”的方法是使用一个互斥锁的属性对象。要实现这种功能,就要在调用 pthread_mutexattr_init() 来初始化对象之后再调用 pthread_mutexattr_settype() 来设置互斥锁的“类型”: int pthread_mutexattr_init(pthread_mutexattr_t *attr); 参数“kind”允许的值如下所示: PTHREAD_MUTEX_FAST_NP 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); 在这两个调用中,第一个 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 表示创建一个初始状态为已经拥有的互斥锁。 使用 pthread_mutex_init() 创建一个互斥锁。
hmtx hmtxSem; // semaphore handle /* Create a un named mutex semaphore */ ulRc = DosRequestMutexSem (hmtxSem, (unsigned long) /* Access the shared resource */ /* Release the mutex */ /* Closes the semaphore */
/* Declare the mutex */ /* create the mutex with the attributes set */ /* lock the mutex */ /* access the shared resource */ /* unlock the mutex */ /* destroy the attribute */
#define TIMEOUT 100 /* 1 sec */ while (timeout < TIMEOUT ) { irc = pthread_mutex_trylock(&mutex);
信号量 除了 pthread 条件变量之外,Linux 还提供了 POSIX 信号量来映射 OS/2 的事件信号量构造(Linux 还提供了 SVR 兼容的 semop、semctl 等类似的调用,但是本文的目的仅限于介绍 POSIX 和 LinuxThreads 实现)。它们都有自己的优点和缺点,您需要根据应用程序的逻辑来决定到底使用哪种技术。在对事件信号量进行映射时,要考虑以下几点: 信号量的类型:OS/2 支持有名和无名的事件信号量,有名信号量可以在进程间进行共享。Linux 不支持这种功能。
表 3. 信号量映射表 OS/2 POSIX Linux 调用 pthread Linux 调用 类别
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); 等待事件信号量 在 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 此处, 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); 注意,条件函数不是异步信号安全的,不应该在信号处理函数中调用。在特殊情况下,如果在信号处理函数中调用 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; /* create event semaphore */ /* In Thread A */ /* Waits until the semaphore is posted */ /* In Thread B */ /* Close the semaphore */
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; struct timeval tvTimeNow; //Absolute current time /* In Thread A */
pthread_mutex_lock(&mutex); pthread_cond_wait(&condvar, &mutex); pthread_mutex_unlock(&mutex);
/* --------------------------------------------------------------------- /* One way of avoiding losing the signal is as follows */
pthread_mutex_lock (&mutex); /* Do work */ ....... /* This work may lead other threads to send signal to /* Thread A now tries to take the mutex lock
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 pthread_cond_timedwait (&condvar, &mutex, &tsTimeOut ); pthread_mutex_unlock (&mutex);
sem_t sem; /* semaphore object */ /* Initialize the semaphore - count is set to 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 */ /* Destroy the semaphore */
结束语 本文介绍了从 OS/2 到 Linux 的映射中有关线程、互斥锁和事件信号量的知识。在将任何程序从 OS/2 移植到 Linux 上时,您都可以参考这些内容。本文中给出的提示和警告有助于简化您移植程序的设计;有关本文中所提到的所有 Linux 调用的更详细内容,您可以参考相关的手册页。本系列文章的下一篇将介绍有关内存管理、文件处理和设备驱动接口的系统调用的映射。
参考资料 您可以参阅本文在 developerWorks 全球站点上的 英文原文.
作者简介
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与他联系。 |
|