分类: C/C++
2011-11-09 17:55:40
转载http://blog.csdn.net/elijah5748/article/details/2326714
随着开发者将原本普遍的 Windows® 应用迁移到 Linux™ 平台,正在进行的向开源迁移的浪潮有可能引发极大的移植问题。这个由三部分构成的系列文章提供一个映射指南,并附有例子,能够简化从 Windows 到 Linux 的转变。第 1 部分介绍了进程和线程。
当前,很多全球商务和服务都正在趋于开源 —— 业界的所有主要参与者都在争取实现此目标。这一趋势催生了一个重要的迁移模式:为不同平台(Windows、OS2、Solaris 等)维持的现有产品将被移植到开放源代码的 Linux 平台。
很多应用程序在设计时并未考虑到需要将它们移植到 Linux。这有可能使移植成为一件痛苦的事情,但并非绝对如此。本系列文章的目的是,帮助您将涉及到 IPC 和线程原语的复杂应用程序从 Windows 迁移到 Linux。我们与您分享迁移这些关键应用程序的经验,包括要求线程同步的多线程应用程序以及要求进程间同步的多进程应用程序。
简言之,可以将此系列文章看作是一个映射文档 —— 它提供了与线程、进程和进程间通信元素(互斥体、信号量等等)相关的各种 Windows 调用到 Linux 调用的映射。我们将那些映射分为三个部分:
Windows 中和 Linux 中的基本执行单位是不同的。在 Windows 中,线程是基本执行单位,进程是一个容纳线程的容器。
在 Linux 中,基本执行单位是进程。Windows API 所提供的功能可以直接映射到 Linux 系统调用:
Windows | Linux | 类别 |
CreateProcess() CreateProcessAsUser() | fork() setuid() exec() | 可映射 |
TerminateProcess() | kill() | 可映射 |
SetThreadpriority() GetThreadPriority() | Setpriority() getPriority() | 可映射 |
GetCurrentProcessID() | getpid() | 可映射 |
Exitprocess() | exit() | 可映射 |
Waitforsingleobject() Waitformultipleobject() GetExitCodeProcess() | waitpid() Using Sys V semaphores, Waitforsingleobject/multipleobject 不能实现 | 与上下文相关 |
GetEnvironmentVariable SetEnvironmentVariable | getenv() setenv() | 可映射 |
“类别”一列(解释了本文中所使用的分类结构)表明了 Windows 结构是否 可映射 或者 与上下文相关:
在 Windows 中,您可以使用 CreateProcess() 来创建一个新的进程。 CreateProcess() 函数创建一个新的进程及其主线程,如下:
BOOL CreateProcess( LPCTSTR lpApplicationName, // name of executable module LPTSTR lpCommandLine, // command line string LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD BOOL bInheritHandles, // handle inheritance option DWORD dwCreationFlags, // creation flags LPVOID lpEnvironment, // new environment block LPCTSTR lpCurrentDirectory, // current directory name LPSTARTUPINFO lpStartupInfo, // startup information LPPROCESS_INFORMATION lpProcessInformation // process information ) |
bInheritHandles 确定了子进程是否要继承父进程的句柄。lpApplicationName 和 lpCommandLine 给出了将要被启动的进程的名称与路径。lpEnvironment 定义了进程可使用的环境变量。
在 Linux 中,exec* 家族函数使用一个新的进程映像取代当前进程映像(如下所示):
int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg , ..., char * const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); |
exec* 的这些版本只是内核函数 execve() (int execve(const char *filename, char *const argv [], char *const envp[]))的各种调用接口。在这里,argv 是包含有参数 list 的指针,envp 是包含有环境变量列表(主要是 key=value 对)的指针。
它必须与 fork() 命令一起使用,所以父进程和子进程都在运行: pid_t fork(void)。fork() 会创建一个子进程,与父进程相比只是 PID 和 PPID 不同;实际上,资源利用设为 0。
默认情况下,exec() 继承父进程的组和用户 ID,这就使得它会依赖于父进程。可以使用以下方法来改变:
CreateProcessAsUser() 函数与 CreateProcess() 类似,只是新进程是在用户通过 hToken 参数描述的安全上下文中运行。在 Linux 中,没有与此函数惟一对应的函数,但是可以使用下面的逻辑来实现对它的复制:
在 Windows 中,您可以使用 TerminateProcess() 强制终止一个运行中的进程。
BOOL TerminateProcess( HANDLE hProcess, // handle to the process UINT uExitCode // exit code for the process ); |
这个函数终止运行中的进程及其相关线程。只是在非常极端的场合才会使用这个函数。
在 Linux 中,您可以使用 kill() 来强行杀死一个进程: int kill(pid_t pid, int sig)。这个系统调用会终止 id 为 PID 的进程。您也可以使用它向任何组或者进程发出信号。
在子进程依赖于父进程的情况下,您可以在父进程中使用等待函数来等待子进程的终止。在 Windows 中,您可以使用 WaitForSingleObject() 函数调用来实现此功能。
您可以使用 WaitForMultipleObject() 函数来等待多个对象。
DWORD WaitForMultipleObjects( DWORD nCount, // number of handles in array CONST HANDLE *lpHandles, // object-handle array BOOL bWaitAll, // wait option DWORD dwMilliseconds // time-out interval ); |
您可以向对象句柄数组(object-handle array)中填充很多需要等待的对象。根据 bWaitALL 选项,您既可以等待所有对象被信号通知,也可以等待其中任意一个被信号通知。
在这两个函数中,如果您想等待有限的一段时间,则可以在第二个参数中指定时间间隔。如果您想无限制等待,那么使用 INFINITE 作为 dwMilliseconds 的值。将 dwMilliseconds 设置为 0 则只是检测对象的状态并返回。
在 Linux 中,如果您希望无限期等待进程被杀死,则可以使用 waitpid()。在 Linux 中,使用 waitpid() 调用无法等待限定的时间。
在这段代码中:pid_t waitpid(pid_t pid, int *status, int options),waitpid() 会无限期等待子进程的终止。在 Windows 和 Linux 中,等待函数会挂起当前进程的执行,直到它完成等待,不过,在 Windows 中可以选择在指定的时间后退出。使用 System V 信号量,您可以实现类似于 WaitForSingleObject() 和WaitForMultipleObject() 的限时等待或者 NO WAIT 功能,在本系列的第 2 部分中将讨论此内容。本系列的第 3 部分将深入讨论等待函数。
退出进程指的是优雅(graceful)地退出进程,并完成适当的清除工作。在 Windows 中,您可以使用ExitProcess() 来执行此操作。
VOID ExitProcess( UINT uExitCode // exit code for all threads ); |
ExitProcess() 是在进程结束处执行的方法。这个函数能够干净地停止进程。包括调用所有链接到的动态链接库(DLL)的入口点函数,给出一个值,指出这个进程正在解除那个 DLL 的链接。
Linux 中与 ExitProcess() 相对应的是 exit():void exit(int status);。
exit() 函数会令程序正常终止,并将 &0377 状态值返回给父进程。 C 语言标准规定了两个定义(EXIT_SUCCESS 和 EXIT_FAILURE),可以被传递到状态参数,以说明终止成功或者不成功。
每个进程都拥有关联到它的一组环境,其中主要是 name=value 对,指明进程可以访问的各种环境变量。尽管我们可以在创建进程时指定环境,不过也有特定函数可以在进程创建后设置和获得环境变量。
在 Windows 中,您可以使用 GetEnvironmentVariable() 和 SetEnvironmentVariable() 来获得和设置环境变量。
DWORD GetEnvironmentVariable( LPCTSTR lpName, // environment variable name LPTSTR lpBuffer, // buffer for variable value DWORD nSize // size of buffer ); |
如果成功,则此函数返回值缓存的大小,如果指定的名称并不是一个合法的环境变量名,则返回 0。SetEnvironmentVariable() 函数为当前进程设置指定的环境变量的内容。
BOOL SetEnvironmentVariable( LPCTSTR lpName, // environment variable name LPCTSTR lpValue // new value for variable ); |
如果函数成功,则返回值非零。如果函数失败,则返回值为零。
在 Linux 中,getenv() 和 setenv() 系统调用提供了相应的功能。
char *getenv(const char *name); int setenv(const char *name, const char *value, int overwrite); |
getenv() 函数会在环境列表中搜索与名称字符串相匹配的字符串。这个函数会返回一个指向环境中的值的指针,或者如果不匹配则返回 NULL。setenv() 函数将变量名和值添加到环境中,如果那个名称并不存在。如果环境中已经存在那个名称,而且如果 overwrite 非零,则它的值会被修改为 value。如果 overwrite 为零,则name 的值不会被改变。如果成功,则 setenv() 会返回零,如果环境中空间不足,则返回 -1。
下面的例子解释了我们在本节中讨论的内容。
//Sample Application that explain process concepts //Parameters Declaration/Definition int TimetoWait; STARTUPINFO si; PROCESS_INFORMATION pi; LPTSTR lpszCurrValue,LPTSTR lpszVariable; TCHAR tchBuf[BUFSIZE]; BOOL fSuccess; if(argc > 2) { printf("InvalidArgument"); ExitProcess(1); //Failure } //Get and display an environment variable PATH lpszCurrValue = ((GetEnvironmentVariable("PATH",tchBuf, BUFSIZE) > 0) ? tchBuf : NULL); lpszVariable = lpszCurrValue; //Display the environment variable while (*lpszVariable) putchar(*lpszVariable++); putchar('/n'); //Initialise si and pi ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); ZeroMemory( &pi, sizeof(pi) ); //Create a childProcess if( !CreateProcess( NULL, // No module name (use command line). "SomeProcess", // Command line. NULL, // Process handle not inheritable. NULL, // Thread handle not inheritable. FALSE, // Set handle inheritance to FALSE. 0, // No creation flags. NULL, // Use parent's environment block. NULL, // Use parent's starting directory. &si, // Pointer to STARTUPINFO structure. &pi ) // Pointer to PROCESS_INFORMATION structure. ) { printf( "CreateProcess failed." ); } // Wait until child process exits. if(argc == 2) { TIMEOUT = atoi(argv[1]); ret = WaitForSingleObject( pi.hProcess, TIMEOUT ); if(ret == WAIT_TIMEOUT) { TerminateProcess(pi.hProcess); } } else { WaitForSingleObject( pi.hProcess, INFINITE ); ... } ExitProcess(0); //Success |
#include |
|
在 Windows 中,线程是基本的执行单位。在进程的上下文中会有一个或多个线程在运行。调度代码在内核中实现。没有单独的“调度器(scheduler)”模块或例程。
Linux 内核使用的是进程模型,而不是线程模型。Linux 内核提供了一个轻量级进程框架来创建线程;实际的线程在用户空间中实现。在 Linux 中有多种可用的线程库(LinuxThreads、NGPT、NPTL 等等)。本文中的资料基于 LinuxThreads 库,不过这里的资料也适用于 Red Hat 的 Native POSIX Threading Library(NPTL)。
本节描述 Windows 和 Linux 中的线程。内容涵盖了创建线程、设置其属性以及修改其优先级。
Windows | Linux | 类别 |
CreateThread | pthread_create pthread_attr_init pthread_attr_setstacksize pthread_attr_destroy | 可映射 |
ThreadExit | pthread_exit | 可映射 |
WaitForSingleObject | pthread_join pthread_attr_setdetachstate pthread_detach | 可映射 |
SetPriorityClass SetThreadPriority | setpriority sched_setscheduler sched_setparam pthread_setschedparam pthread_setschedpolicy pthread_attr_setschedparam pthread_attr_setschedpolicy | 与上下文相关 |
在 Windows 中,您可以使用 CreateThread() 来创建线程,创建的线程在调用进程的虚拟地址空间中运行。
HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD SIZE_T dwStackSize, // initial stack size LPTHREAD_START_ROUTINE lpStartAddress, // thread function LPVOID lpParameter, // thread argument DWORD dwCreationFlags, // creation option LPDWORD lpThreadId // thread identifier ); |
lpThreadAttributes 是指向线程属性的指针,决定了线程句柄是否能由子进程继承。
Linux 使用 pthread 库调用 pthread_create() 来派生线程:
int pthread_create (pthread_t *thread_id, pthread_attr_t *threadAttr, void * (*start_address)(void *), void * arg); |
注意:在 Windows 中,受可用虚拟内存的限制,一个进程可以创建的线程数目是有限的。默认情况下,每个线程有一兆栈空间。因此,您最多可以创建 2,028 个线程。如果您减小默认栈大小,那么可以创建更多线程。在 Linux 中,使用 ULIMIT -a(limits for all users)可以获得每个用户可以创建的线程的最大数目,可以使用ULIMIT -u 来修改它,不过只有在登录时才可以这样做。 /usr/Include/limit.h 和 ulimit.h 下的头文件定义了这些内容。您可以修改它们并重新编译内核,以使其永久生效。对于 POSIX 线程限制而言,local_lim.h 中定义的THREAD_THREADS_MAX 宏定义了数目的上限。
CreateThread() 中的 lpStartAddress 参数是刚创建的线程要执行的函数的地址。
pthread_create() 库调用的 start_address 参数是刚创建的线程要执行的函数的地址。
在 Windows 中,系统调用 CreateThread() 的参数 lpParameter 指定了要传递给刚创建的线程的参数。它指明了将要传递给新线程的数据条目的地址。
在 Linux 中,库调用 pthread_create() 的参数 arg 指定了将要传递给新线程的参数。
在 Windows 中,CreateThread() 的参数 dwStackSize 是将要分配给新线程的以字节为单位的栈大小。栈大小应该是 4 KB 的非零整数倍,最小为 8 KB。
在 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_xxxx,来更新将要作为参数传递给 pthread_create 的属性对象。同时,您在创建线程之后的任意时候都可以使用 pthread_xxxx。
使用调用 pthread_attr_setstacksize() 来设置栈大小: int pthread_attr_setstacksize(pthread_attr_t *threadAttr, int stack_size);。
在 Windows 中,系统调用 ExitThread() 会终止线程。 dwExitCode 是线程的返回值,另一个线程通过调用GetExitCodeThread() 就可以得到它。
VOID ExitThread( DWORD dwExitCode // exit code for this thread ); |
Linux 中与此相对应的是库调用 pthread_exit()。 retval 是线程的返回值,可以在另一个线程中通过调用pthread_join() 来获得它: int pthread_exit(void* retval);。
在 Windows 中,没有保持关于线程终止的显式线程状态。不过,WaitForSingleObject() 让线程能够显式地等待进程中某个指定的或者非指定的线程终止。
在 Linux 中,默认以可连接(joinable)的状态创建线程。在可连接状态中,另一个线程可以同步这个线程的终止,使用函数 pthread_join() 来重新获得其终止代码。可连接的线程只有在被连接后才释放线程资源。
Windows 使用 WaitForSingleObject() 来等待某个线程终止:
DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds ); |
其中:
Linux 使用 pthread_join() 来完成同样的功能: int pthread_join(pthread_t *thread, void **thread_return);。
在分离的状态中,线程终止后线程资源会立即被释放。通过对线程属性对象调用pthread_attr_setdetachstate() 可以设置分离状态: int pthread_attr_setdetachstate (pthread_attr_t *attr, int detachstate);。以可连接状态创建的线程,稍后可以被转为分离状态,方法是使用 pthread_detach() 调用:int pthread_detach (pthread_t id);。
在 Windows 中,线程的优先级由其进程的优先级等级以及进程优先级等级中的线程优先级层次决定。在 Linux 中,线程本身就是一个执行单位,有其自己的优先级。它与其进程的优先级没有依赖关系。
在 Windows 中,您可以使用 SetPriorityClass() 来设置特定进程的优先级等级:
BOOL SetPriorityClass( HANDLE hProcess, // handle to the process DWORD dwPriorityClass // Priority class ); |
dwPriorityClass 是进程的优先级等级,它可以设置为下列值中的任意一个:
一旦设置了进程的优先级等级,就可以使用 SetThreadPriority() 在进程的优先级等级内部设置线程的优先级层次:
BOOL SetThreadPriority( HANDLE hThread, int nPriority ); |
nPriority 是线程的优先级值,它被设置为下列之一;
|
为了结束这一期文章,让我们来看下面类型的进程和线程的一些例子:
使用 Linux 系统调用 setpriority() 来设置或者修改普通进程和线程的优先级层次。参数的范围是PRIO_PROCESS。将 id 设置为 0 来修改当前进程(或线程)的优先级。此外,delta 是优先级的值 —— 这一次是从 -20 到 20。另外,要注意在 Linux 中较低的 delta 值代表较高的优先级。所以,使用 +20 设置 IDLETIME 优先级,使用 0 设置 REGULAR 优先级。
在 Windows 中,常规线程的优先级的范围是从 1(较低的优先级)到 15(较高的优先级)。不过,在 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(实时 round-robin 策略)和 SCHED_FIFO(实时 FIFO 策略)。
在此,param 是指向描述调度优先级结构体的指针。它的范围是 1 到 99,只用于实时策略。对于其他的(普通的非实时进程),它为零。
在 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); |
在 Windows 中,实时线程的优先级范围是从 16(较低的优先级)到 31(较高的优先级)。在 Linux 中,实时线程的优先级范围是从 99(较高的)到 1(较低的优先级)。在使用前必须对此进行映射。
下面的清单阐述了本节中的概念。
Main Thread enum stackSize = 120 * 1024 ; // create a thread normal and real time thread DWORD normalTId, realTID; HANDLE normalTHandle, realTHandle; normalTHandle = CreateThread( NULL, // default security attributes stackSize, // 120K NormalThread, // thread function NULL, // argument to thread function 0, // use default creation flags &normalTId); // returns the thread identifier // Set the priority class as "High priority" SetPriorityClass(pHandle, HIGH_PRIORITY_CLASS); normalTHandle = CreateThread( NULL, // default security attributes stackSize, // 120K NormalThread, // thread function NULL, // argument to thread function 0, // use default creation flags &normalTId); // returns the thread identifier CloseHandle(threadHandle); ... ... // Thread function DWORD WINAPI NormalThread ( LPVOID lpParam ) { HANDLE tHandle,pHandle; pHandle = GetCurrentProcess(); tHandle = GetCurrentThread(); // Set the priority class as "High priority" SetPriorityClass(pHandle, HIGH_PRIORITY_CLASS); // increase the priority by 2 points above the priority class SetThreadPriority(tHandle,THREAD_PRIORITY_HIGHEST); // perform job at high priority ... ... ... // Reset the priority class as "Normal" SetPriorityClass(pHandle, NORMAL_PRIORITY_CLASS); // set the priority back to normal SetThreadPriority(tHandle,THREAD_PRIORITY_NORMAL); // Exit thread ExitThread(0); } // Thread function DWORD WINAPI RealTimeThread ( LPVOID lpParam ) { HANDLE tHandle, pHandle ; pHandle = GetCurrentProcess(); tHandle = GetCurrentThread (); // Set the priority class as "Real time" SetPriorityClass(pHandle, REALTIME_PRIORITY_CLASS); // increase the priority by 2 points above the priority class SetThreadPriority(tHandle,THREAD_PRIORITY_HIGHEST); // do time critical work ... ... ... // Reset the priority class as "Normal" SetPriorityClass(pHandle, NORMAL_PRIORITY_CLASS); // Reset the priority back to normal SetThreadPriority(tHandle,THREAD_PRIORITY_NORMAL); ExitThread(0); } |
static void * RegularThread (void *); static void * CriticalThread (void *); // Main Thread pthread_t thread1, thread2; // thread identifiers pthread_attr_t threadAttr; struct sched_param param; // scheduling priority // initialize the thread attribute pthread_attr_init(&threadAttr); // Set the stack size of the thread pthread_attr_setstacksize(&threadAttr, 120*1024); // Set thread to detached state. No need for pthread_join pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED); // Create the threads pthread_create(&thread1, &threadAttr, RegularThread, NULL); pthread_create(&thread2, &threadAttr, CriticalThread,NULL); // Destroy the thread attributes pthread_attr_destroy(&threadAttr); ... ... // Regular non-realtime Thread function static void * RegularThread (void *d) { int priority = -18; // Increase the priority setpriority(PRIO_PROCESS, 0, priority); // perform high priority job ... ... // set the priority back to normal setpriority(PRIO_PROCESS, 0, 0); pthread_exit(NULL); } // Time Critical Realtime Thread function static void * CriticalThread (void *d) { // Increase the priority 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 = 90; pthread_setschedparam(thread_id, policy, ¶m); // Perform time critical task ... ... // set the priority back to normal param.sched_priority = 0; policy = 0; // for normal threads pthread_setschedparam(thread_id, policy, ¶m); .... .... pthread_exit(NULL); } |
在 Windows 上,同步是使用等待函数中的同步对象来实现的。同步对象可以有两种状态:有信号(signaled)状态和无信号(non-signaled)状态。当在一个等待函数中使用同步对象时,等待函数就会阻塞调用线程,直到同步对象的状态被设置为有信号为止。
下面是在 Windows 上可以使用的一些同步对象:
在 Linux 中,可以使用不同的同步原语。Windows 与 Linux 的不同之处在于每个原语都有自己的等待函数(所谓等待函数就是用来修改同步原语状态的函数);在 Windows 中,有一些通用的等待函数来实现相同的目的。以下是 Linux 上可以使用的一些同步原语:
通过使用上面列出的这些原语,各种库都可以用于 Linux 之上,以提供同步机制。
Windows | Linux —— 线程 | Linux —— 进程 |
互斥 | 互斥 - pthread 库 | System V 信号量 |
临界区 | 互斥 - pthread 库 | 不适用,因为临界区只用于同一进程的不同线程之间 |
信号量 | 具有互斥的条件变量 - pthreads POSIX 信号量 | System V 信号量 |
事件 | 具有互斥的条件变量 - pthreads | System V 信号量 |
|
Windows 信号量是一些计数器变量,允许有限个线程/进程访问共享资源。Linux POSIX 信号量也是一些计数器变量,可以用来在 Linux 上实现 Windows 上的信号量功能。
在对进程进行映射时,我们需要考虑以下问题:
Windows | Linux 线程 | Linux 进程 | 类别 |
CreateSemaphore | sem_init | semget semctl | 与上下文相关 |
OpenSemaphore | 不适用 | semget | 与上下文相关 |
WaitForSingleObject | sem_wait sem_trywait | semop | 与上下文相关 |
ReleaseSemaphore | sem_post | semop | 与上下文相关 |
CloseHandle | sem_destroy | semctl | 与上下文相关 |
在 Windows 中,可以使用 CreateSemaphore() 创建或打开一个有名或无名的信号量。
HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName ); |
在这段代码中:
这个函数创建信号量,并返回这个信号量的句柄。它还将初始值设置为调用中指定的值。这样就可以允许有限个线程来访问某个共享资源。
在 Linux 中,可以使用 sem_init() 来创建一个无名的 POSIX 信号量,这个调用可以在相同进程的线程之间使用。它还会对信号量计数器进行初始化:int sem_init(sem_t *sem, int pshared, unsigned int value)。在这段代码中:
这里要注意的是,最大值基于 demaphore.h 头文件中定义的 SEM_VALUE_MAX。
在 Linux 中,semget() 用于创建 System V 信号量,它可以在不同集成的线程之间使用。可以用它来实现与 Windows 中有名信号量相同的功能。这个函数返回一个信号量集标识符,它与一个参数的键值关联在一起。当创建一个新信号量集时,对于与 semid_ds 数据结构关联在一起的信号量,semget() 要负责将它们进行初始化,方法如下:
用来创建 System V 信号量使用的代码是:int semget(key_t key, int nsems, int semflg)。下面是对这段代码的一些解释:
注意,在 System V 信号量中,key 被用来惟一标识信号量;在 Windows 中,信号量是使用一个名称来标识的。
为了对信号量集数据结构进行初始化,可以使用 IPC_SET 命令来调用 semctl() 系统调用。将 arg.buf 所指向的 semid_ds 数据结构的某些成员的值写入信号量集数据结构中,同时更新这个结构的 sem_ctime member 的值。用户提供的这个 arg.buf 所指向的 semid_ds 结构如下所示:
调用进程的有效用户 ID 应该是超级用户,或者至少应该与这个信号量集的创建者或所有者匹配: int semctl(int semid, int semnum, int cmd = IPC_SET, ...)。在这段代码中:
最大计数器的值是根据在头文件中定义的 SEMVMX 来决定的。
在 Windows 中,我们使用 OpenSemaphore() 来打开某个指定信号量。只有在两个进程之间共享信号量时,才需要使用信号量。在成功打开信号量之后,这个函数就会返回这个信号量的句柄,这样就可以在后续的调用中使用它了。
HANDLE OpenSemaphore( DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName ) |
在这段代码中:
在 Linux 中,可以调用相同的 semget() 来打开某个信号量,不过此时 semflg 的值为 0:int semget(key,nsems,0)。在这段代码中:
在 Windows 中,等待函数提供了获取同步对象的机制。可以使用的等待函数有多种类型;在这一节中,我们只考虑 WaitForSingleObject()(其他类型将会分别进行讨论)。这个函数使用一个信号量对象的句柄作为参数,并会一直等待下去,直到其状态变为有信号状态或超时为止。
DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds );
在这段代码中:
在 Linux 中,sem_wait() 用来获取对信号量的访问。这个函数会挂起调用线程,直到这个信号量有一个非空计数为止。然后,它可以原子地减少这个信号量计数器的值:int sem_wait(sem_t * sem)。
在 POSIX 信号量中并没有超时操作。这可以通过在一个循环中执行一个非阻塞的 sem_trywait() 实现,该函数会对超时值进行计算:int sem_trywait(sem_t * sem)。
在使用 System V 信号量时,如果通过使用 IPC_SET 命令的 semctl() 调用设置初始的值,那么必须要使用semop() 来获取信号量。semop() 执行操作集中指定的操作,并阻塞调用线程/进程,直到信号量值为 0 或更大为止:int semop(int semid, struct sembuf *sops, unsigned nsops)。
函数 semop() 原子地执行在 sops 中所包含的操作 —— 也就是说,只有在这些操作可以同时成功执行时,这些操作才会被同时执行。sops 所指向的数组中的每个 nsops 元素都使用 struct sembuf 指定了一个要对信号量执行的操作,这个结构包括以下成员:
要获取信号量,可以通过将 sem_op 设置为 -1 来调用 semop();在使用完信号量之后,可以通过将 sem_op 设置为 1 来调用 semop() 释放信号量。通过将 sem_op 设置为 -1 来调用 semop(),信号量计数器将会减小 1,如果该值小于 0(信号量的值是不能小于 0 的),那么这个信号量就不能再减小,而是会让调用线程/进程阻塞,直到其状态变为有信号状态为止。
sem_flg 中可以识别的标记是 IPC_NOWAIT 和 SEM_UNDO。如果某一个操作被设置了 SEM_UNDO 标记,那么在进程结束时,该操作将被取消。如果 sem_op 被设置为 0,那么 semop() 就会等待 semval 变成 0。这是一个“等待为 0” 的操作,可以用它来获取信号量。
记住,超时操作在 System V 信号量中并不适用。这可以在一个循环中使用非阻塞的 semop()(通过将 sem_flg设置为 IPC_NOWAIT)实现,这会计算超时的值。
在 Windows 中,ReleaseSemaphore() 用来释放信号量。
BOOL ReleaseSemaphore( HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount ); |
在这段代码中:
这个函数会将信号量计数器的值增加在 lReleaseCount 中指定的值上,然后将这个信号量的状态设置为有信号状态。
在 Linux 中,我们使用 sem_post() 来释放信号量。这会唤醒对这个信号量进行阻塞的所有线程。信号量的计数器同时被增加 1。要为这个信号量的计数器添加指定的值(就像是 Windows 上一样),可以使用一个互斥变量多次调用以下函数:int sem_post(sem_t * sem)。
对于 System V 信号量来说,只能使用 semop() 来释放信号量:int semop(int semid, struct sembuf *sops, unsigned nsops)。
函数 semop() 原子地执行 sops 中包含的一组操作(只在所有操作都可以同时成功执行时,才会将所有的操作同时一次执行完)。sops 所指向的数组中的每个 nsops 元素都使用一个 struct sembuf 结构指定了一个要对这个信号量执行的操作,该结构包含以下元素:
要释放信号量,可以通过将 sem_op 设置为 1 来调用 semop()。通过将 semop() 设置为 1 来调用 semop(),这个信号量的计数器会增加 1,同时用信号通知这个信号量。
在 Windows 中,我们使用 CloseHandle() 来关闭或销毁信号量对象。
BOOL CloseHandle( HANDLE hObject ); |
hObject 是指向这个同步对象句柄的指针。
在 Linux 中,sem_destroy() 负责销毁信号量对象,并释放它所持有的资源: int sem_destroy(sem_t *sem)。对于 System V 信号量来说,只能使用 semctl() 函数的 IPC_RMID 命令来关闭信号量集:int semctl(int semid, int semnum, int cmd = IPC_RMID, ...)。
这个命令将立即删除信号量集及其数据结构,并唤醒所有正在等待的进程(如果发生错误,则返回,并将 errno设置为 EIDRM)。调用进程的有效用户 ID 必须是超级用户,或者可以与该信号量集的创建者或所有者匹配的用户。参数 semnum 会被忽略。
下面是信号量的几个例子。
HANDLE hSemaphore; LONG lCountMax = 10; LONG lPrevCount; DWORD dwRetCode; // Create a semaphore with initial and max. counts of 10. hSemaphore = CreateSemaphore( NULL, // no security attributes 0, // initial count lCountMax, // maximum count NULL); // unnamed semaphore // Try to enter the semaphore gate. dwRetCode = WaitForSingleObject( hSemaphore, // handle to semaphore 2000L); // zero-second time-out interval switch (dwRetCode) { // The semaphore object was signaled. case WAIT_OBJECT_0: // Semaphore is signaled // go ahead and continue the work goto success: break; case WAIT_TIMEOUT: // Handle the time out case break; } Success: // Job done, release the semaphore ReleaseSemaphore( hSemaphore, // handle to semaphore 1, // increase count by one NULL) // not interested in previous count // Close the semaphore handle CloseHandle(hSemaphore); |
// Main thread #define TIMEOUT 200 /* 2 secs */ // Thread 1 sem_t sem ; // Global Variable int retCode ; // Initialize event semaphore retCode = sem_init( sem, // handle to the event semaphore 0, // not shared 0); // initially set to non signaled state while (timeout < TIMEOUT ) { delay.tv_sec = 0; delay.tv_nsec = 1000000; /* 1 milli sec */ // Wait for the event be signaled retCode = sem_trywait( &sem); // event semaphore handle // non blocking call if (!retCode) { /* Event is signaled */ break; } else { /* check whether somebody else has the mutex */ if (retCode == EPERM ) { /* sleep for delay time */ nanosleep(&delay, NULL); timeout++ ; } else{ /* error */ } } } // Completed the job, // now destroy the event semaphore retCode = sem_destroy( &sem); // Event semaphore handle // Thread 2 // Condition met // now signal the event semaphore sem_post( &sem); // Event semaphore Handle |
// Process 1 #define TIMEOUT 200 //Definition of variables key_t key; int semid; int Ret; int timeout = 0; struct sembuf operation[1] ; union semun { int val; struct semid_ds *buf; USHORT *array; } semctl_arg,ignored_argument; key = ftok(); // Generate a unique key, U can also supply a value instead semid = semget(key, // a unique identifier to identify semaphore set 1, // number of semaphore in the semaphore set 0666 | IPC_CREAT // permissions (rwxrwxrwx) on the new // semaphore set and creation flag ); //Set Initial value for the resource semctl_arg.val = 0; //Setting semval to 0 semctl(semid, 0, SETVAL, semctl_arg); //Wait for Zero while(timeout < TIMEOUT) { delay.tv_sec = 0; delay.tv_nsec = 1000000; /* 1 milli sec */ //Call Wait for Zero with IPC_NOWAIT option,so it will be non blocking operation[0].sem_op = -1; // Wait until the semaphore count becomes 0 operation[0].sem_num = 0; operation[0].sem_flg = IPC_NOWAIT; ret = semop(semid, operation,1); if(ret < 0) { /* check whether somebody else has the mutex */ if (retCode == EPERM ) { /* sleep for delay time */ nanosleep(&delay, NULL); timeout++ ; } else { printf("ERROR while wait "); break; } } else { /*semaphore got triggered */ break; } } //Close semaphore iRc = semctl(semid, 1, IPC_RMID , ignored_argument); } // Process 2 key_t key = KEY; // Process 2 should know key value in order to open the // existing semaphore set struct sembuf operation[1] ; //Open semaphore semid = semget(key, 1, 0); operation[0].sem_op = 1; // Release the resource so Wait in process 1 will // be triggered operation[0].sem_num = 0; operation[0].sem_flg = SEM_UNDO; //Release semaphore semop(semid, operation,0); } |
|
在 Windows 中,事件对象是那些需要使用 SetEvent() 函数显式地将其状态设置为有信号状态的同步对象。事件对象来源有两种类型:
事件对象有两种状态,有信号(signaled)状态 和 无信号(non-signaled)状态。对事件对象调用的等待函数会阻塞调用线程,直到其状态被设置为有信号状态为止。
在进行平台的迁移时,需要考虑以下问题:
还有几点非常重要,需要说明一下:
Windows | Linux 线程 | Linux 进程 | 类别 |
CreateEvent OpenEvent | pthread_cond_init sem_init | semget semctl | 与上下文相关 |
SetEvent | pthread_cond_signal sem_post | semop | 与上下文相关 |
ResetEvent | N/A | N/A | 与上下文相关 |
WaitForSingleObject | pthread_cond_wait pthread_cond_timedwait sem_wait sem_trywait | semop | 与上下文相关 |
CloseHandle | pthread_cond_destroy sem_destroy | semctl | 与上下文相关 |
在 Windows 中,我们使用 CreateEvent() 来创建事件对象。
HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName ) |
在这段代码中:
这个函数创建一个手工重置或自动重置的事件对象,同时还要设置改对象的初始状态。这个函数返回事件对象的句柄,这样就可以在后续的调用中使用这个事件对象了。
OpenEvent() 用来打开一个现有的有名事件对象。这个函数返回该事件对象的句柄。
HANDLE OpenEvent( DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName ) |
在这段代码中:
在 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_INITIALIZER 常量静态地对 pthread_cond_t 类型的条件变量进行初始化,也可以使用 pthread_condattr_init() 对其进行初始化,这个函数会对与这个条件变量关联在一起的属性进行初始化。可以调用 pthread_condattr_destroy() 用来销毁属性:
int pthread_condattr_init(pthread_condattr_t *attr) int pthread_condattr_destroy(pthread_condattr_t *attr) |
在 Windows 中,等待函数提供了获取同步对象的机制。我们可以使用不同类型的等待函数(此处我们只考虑WaitForSingleObject())。这个函数会使用一个互斥对象的句柄,并一直等待,直到它变为有信号状态或超时为止。
DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds ); |
在这段代码中:
Linux POSIX 信号量使用 sem_wait() 来挂起调用线程,直到信号量的计数器变成非零的值为止。然后它会自动减小信号量计数器的值: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 秒到现在所经过的时间。)
函数 SetEvent() 用来将事件对象的状态设置为有信号状态。对一个已经设置为有信号状态的事件对象再次执行该函数是无效的。
BOOL SetEvent( HANDLE hEvent ) |
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() 可能会导致调用线程的死锁。
在 Windows 中,ResetEvent() 用来将事件对象的状态重新设置为无信号状态。
BOOL ResetEvent( HANDLE hEvent ); |
在 Linux 中,条件变量和 POSIX 信号量都是自动重置类型的。
在 Windows 中,CloseHandle() 用来关闭或销毁事件对象。
BOOL CloseHandle( HANDLE hObject ); |
在这段代码中,hObject 是指向同步对象句柄的指针。
在 Linux 中, sem_destroy()/ pthread_cond_destroy() 用来销毁信号量对象或条件变量,并释放它们所持有的资源:
int sem_destroy(sem_t *sem) int pthread_cond_destroy(pthread_cond_t *cond) |
在 Linux 中,进程之间有名事件对象所实现的功能可以使用 System V 信号量实现。System V 信号量是计数器变量,因此可以实现 Windows 中事件对象的功能,信号量的计数器的初始值可以使用 semctl() 设置为 0。
要将某个事件的状态修改为有信号状态,可以使用 semop(),并将 sem_op 的值设置为 1。要等待某个事件,则可以使用 semop() 函数,并将 sem_op 的值设置为 -1,这样就可以阻塞调用进程,直到它变为有信号状态为止。
可以通过使用 semctl() 将信号量计数器的初始值设置为 0 来获得信号量。在使用完共享资源之后,可以使用semop() 将信号量计数设置为 1。关于每个 System V 信号量的原型,请参阅本文中有关信号量一节的内容。
下面几个例子可以帮助您理解我们在这一节中所讨论的内容。
// Main thread HANDLE hEvent; // Global Variable // Thread 1 DWORD dwRetCode; // Create Event hEvent = CreateEvent( NULL, // no security attributes FALSE, // Auto reset event FALSE, // initially set to non signaled state NULL); // un named event // Wait for the event be signaled dwRetCode = WaitForSingleObject( hEvent, // Mutex handle INFINITE); // Infinite wait switch(dwRetCode) { case WAIT_OBJECT_O : // Event is signaled // go ahead and proceed the work default : // Probe for error } // Completed the job, // now close the event handle CloseHandle(hEvent); // Thread 2 // Condition met for the event hEvent // now set the event SetEvent( hEvent); // Event Handle |
// Main thread sem_t sem ; // Global Variable // Thread 1 int retCode ; // Initialize event semaphore retCode = sem_init( sem, // handle to the event semaphore 0, // not shared 0); // initially set to non signaled state // Wait for the event be signaled retCode = sem_wait( &sem); // event semaphore handle // Indefinite wait // Event Signaled // a head and proceed the work // Completed the job, // now destroy the event semaphore retCode = sem_destroy( &sem); // Event semaphore handle // Thread 2 // Condition met // now signal the event semaphore sem_post( &sem); // Event semaphore Handle |
// Main thread pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t condvar = PTHREAD_COND_INITIALIZER; // Thread 1 ... 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 // Thread 1 now tries to take the mutex lock // to send the signal but gets blocked ... pthread_mutex_lock(&mutex); // Thread 1 now gets the lock and can // signal thread 2 to wake up pthread_cond_signal(&condvar); pthread_mutex_unlock(&mutex); // Thread 2pthread_mutex_lock(&mutex); pthread_cond_wait(&condvar, &mutex); pthread_mutex_unlock(&mutex); // Thread 2 blocks indefinitely // One way of avoiding losing the signal is as follows // In Thread 2 - 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 2 // Thread 2 waits for indefinitely for the signal to be posted pthread_cond_wait (&condvar, &Mutex ); // Thread 2 unblocks upon receipt of signal pthread_mutex_unlock (&mutex); |
// Process 1 DWORD dwRetCode; HANDLE hEvent; // Local variable // Create Event hEvent = CreateEvent( NULL, // no security attributes FALSE, // Auto reset event FALSE, // initially set to non signaled state "myEvent"); // un named event // Wait for the event be signaled dwRetCode = WaitForSingleObject( hEvent, // Mutex handle INFINITE); // Infinite wait switch(dwRetCode) { case WAIT_OBJECT_O : // Event is signaled // go ahead and proceed the work default : // Probe for error } // Completed the job, // now close the event handle CloseHandle(hEvent); // Process 2 HANDLE hEvent; // Local variable // Open the Event hEvent = CreateEvent( NULL, // no security attributes FALSE, // do not inherit handle "myEvent"); // un named event // Condition met for the event hEvent // now set the event SetEvent( hEvent); // Event Handle // completed the job, now close the event handle CloseHandle(hEvent); |
// Process 1 int main() { //Definition of variables key_t key; int semid; int Ret; int timeout = 0; struct sembuf operation[1] ; union semun { int val; struct semid_ds *buf; USHORT *array; } semctl_arg,ignored_argument; key = ftok(); /Generate a unique key, U can also supply a value instead semid = semget(key, // a unique identifier to identify semaphore set 1, // number of semaphore in the semaphore set 0666 | IPC_CREAT // permissions (rwxrwxrwx) on the new // semaphore set and creation flag ); if(semid < 0) { printf("Create semaphore set failed "); Exit(1); } //Set Initial value for the resource - initially not owned semctl_arg.val = 0; //Setting semval to 0 semctl(semid, 0, SETVAL, semctl_arg); // wait on the semaphore // blocked until it is signaled operation[0].sem_op = -1; operation[0].sem_num = 0; operation[0].sem_flg = IPC_WAIT; ret = semop(semid, operation,1); // access the shared resource ... ... //Close semaphore iRc = semctl(semid, 1, IPC_RMID , ignored_argument); } // Process 2 int main() { key_t key = KEY; //Process 2 shd know key value in order to open the // existing semaphore set struct sembuf operation[1] ; //Open semaphore semid = semget(key, 1, 0); // signal the semaphore by incrementing the semaphore count operation[0].sem_op = 1; operation[0].sem_num = 0; operation[0].sem_flg = SEM_UNDO; semop(semid, operation,0); } |