进程和线程(修改优先级 Windows IPC 应用移植到 Linux) | |
| |
来源: ChinaUnix博客 日期: 2008.08.20 15:13 (共有条评论) 我要评论 | |
当前,很多全球商务和服务都正在趋于开源 —— 业界的所有主要参与者都在争取实现此目标。这一趋势催生了一个重要的迁移模式:为不同平台(Windows、OS2、Solaris 等)维持的现有产品将被移植到开放源代码的 Linux 平台。 很多应用程序在设计时并未考虑到需要将它们移植到 Linux。这有可能使移植成为一件痛苦的事情,但并非绝对如此。本系列文章的目的是,帮助您将涉及到 IPC 和线程原语的复杂应用程序从 Windows 迁移到 Linux。我们与您分享迁移这些关键应用程序的经验,包括要求线程同步的多线程应用程序以及要求进程间同步的多进程应用程序。 简言之,可以将此系列文章看作是一个映射文档 —— 它提供了与线程、进程和进程间通信元素(互斥体、信号量等等)相关的各种 Windows 调用到 Linux 调用的映射。我们将那些映射分为三个部分:
进程 Windows 中和 Linux 中的基本执行单位是不同的。在 Windows 中,线程是基本执行单位,进程是一个容纳线程的容器。 在 Linux 中,基本执行单位是进程。Windows API 所提供的功能可以直接映射到 Linux 系统调用: 表 1. 进程映射 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。 例子 下面的例子解释了我们在本节中讨论的内容。 清单 1. Windows 进程代码 //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 清单 2. 相应的 Linux 进程代码 #include int main(int argc,char *argv[]) { //Parameters Declaration/Definition char PathName[255]; char *Argptr[20]; int rc; char *EnvValue,*lpszVariable; if(argc > 1) { printf(" Wrong parameters !!"); exit(EXIT_FAILURE); } //Get and display an environment variable PATH EnvValue = getenv("PATH"); if(EnvValue == NULL) { printf("Invalid environment variable passed as param !!"); }else { lpszVariable = EnvValue; while (*lpszVariable) putchar(*lpszVariable++); putchar('\n'); } rc = fork(); //variable rc's value on success would be process ID in the parent //process, and 0 in the child's thread of execution. switch(rc) { case -1: printf("Fork() function failed !!"); ret = -1; break; case 0: printf("Child process..."); setpgid(0,0); //Change the parent grp ID to 0 ret = execv(PathName,Argptr); // there are other flavours of exec available, // u can use any of them based on the arguments. if(ret == -1) { kill(getpid(),0); } break; default: // infinitely waits for child process to die Waitpid(rc,&status,WNOHANG); //Note RC will have PID returned since this is parent process. break; } exit(EXIT_SUCCESS); } 回页首 线程 在 Windows 中,线程是基本的执行单位。在进程的上下文中会有一个或多个线程在运行。调度代码在内核中实现。没有单独的“调度器(scheduler)”模块或例程。 Linux 内核使用的是进程模型,而不是线程模型。Linux 内核提供了一个轻量级进程框架来创建线程;实际的线程在用户空间中实现。在 Linux 中有多种可用的线程库(LinuxThreads、NGPT、NPTL 等等)。本文中的资料基于 LinuxThreads 库,不过这里的资料也适用于 Red Hat 的 Native POSIX Threading Library(NPTL)。 本节描述 Windows 和 Linux 中的线程。内容涵盖了创建线程、设置其属性以及修改其优先级。 表 2. 线程映射 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(较低的优先级)。在使用前必须对此进行映射。 例子 下面的清单阐述了本节中的概念。 清单 3. Windows 线程示例 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); } 清单 4. Linux 相应的线程代码 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 IPC 应用移植到 Linux)
最新推荐文章于 2023-12-24 17:54:52 发布