【多任务编程-windows内核对象】

Window多线程编程

在介绍Window多线程编程之前,必须介绍一个重要的概念:
Windows内核对象

内核对象可以供系统和应用程序使用,来管理各种各样的资源,比如进程、线程、文件等。
作为Windows软件开发人员,需要经常创建、打开、和操作各种内核对象。系统也要创建和操作若干类型的内核对象, 
常见内核对象
存取符号对象、事件对象、文件对象、文件映象对象、I/O完成端口对象、作业对象、信箱对象、互斥对象、管道对象、进程对象、信标对象、线程对象和等待计时器对象

主要的内核对象

  • 进程 (Process)
  • 线程(Thread)
  • 作业(Job)
  • 可等待定时器 (Timer)
  • 文件(File)
  • 信标(Semphore,Event)
  • 互斥对象(Mutex)
  • 控制台输入 (ConsoleInput,Output)

内核对象和用户对象

除了内核对象,应用程序也可以使用其他类型的对象。 
菜单,窗口,鼠标光标,刷子和字体等属于用户对象或者GDI对象而不是内核对象
内核对象创建的函数中必须有安全描述符( PSECURITY_ATTRIBUTES )作为参数,用户对象则不需要。
安全描述符PSECURITY_ATTRIBUTES描述了谁创建该内核对象,谁能够访问,谁无权访问等信息。
大多数程序的安全描述符可以制定为NULL

创建Windows内核对象

使用windows内核函数来创建和操作内核对象。每个内核对象只是内核创建的一个内存块,并只能由该内核访问,该内存块是一种树据结构,他的成员负责维护该对象的各种信息。

例,一个进程对象有一个进程ID、一个基本优先级和退出代码,而文件对象有一个字节位移,一个共享模式和一个打开模式。 

内核对象的句柄 

内核对象的句柄,当调用一个创建内核对象的函数时,该函数返回一个句柄,用来标志这个内核对象。
这个句柄值是和进程密切相关的,只能在这个进程中使用,如果在其他的进程中使用这个句柄值操作句柄,会失败。
在这个进程中的其他线程,可以使用这个句柄值 

内核对象的使用计数 

内核对象是由内核拥有的,而不是由进程拥有。如果你的进程创建了一个内核对象的句柄,然后你的进程中止运行,那么,内核对象不一定会被销毁。
内核对象的存在时间可以比创建该内核对象的进程长很多。
内核需要知道多少个进程正在使用某个内核对象,所以内核对象有一个使用计数。使用计数是内核对象常用的数据成员 

进程的内核对象句柄表 

当一个进程初始化的时候,系统为他分配一个句柄表。该句柄表只维护内核对象,不用于用户对象或者GDI对象。
创建一个内核对象,系统返回一个内核对象的句柄,这个句柄实际上是在这个句柄表中的一个索引值。
在创建一个内核对象的时候,系统可能返回一个NULL(0),或者 INVALID_HANDLE_AVLUE(-1),所以,不要单纯使用INVALID_HANDLE_AVLUE来判断一个内核创建函数的返回值是否有效。 

内核对象创建

例创建Windows线程内核对象

     HANDLE hThreadHandle = CreateThread(NULL, 0, TaskMain,
                         (LPVOID)this, 0, 
                        &dwThreadID);
    判断这个线程是否创建成功,通常判断的方式是
        if (NULL == hThreadHandle ) {
            // 创建失败, 处理错误。
            。。。。
        }
    完全的判断方式是
         if (NULL == hThreadHandle || 
            INVALID_HANDLE_AVLUE == hThreadHandle) {
            // 创建失败, 处理错误。
            。。。。
        } 
创建互斥量Mutex内核对象

     HANDLE hThreadHandle = CreateMutex(NULL, 0, TaskMain,
                         (LPVOID)this, 0, 
                        &dwThreadID);
    
创建信号量Semaphore内核对象

     HANDLE hThreadHandle = CreateSemaphore(NULL, 0, TaskMain,
                         (LPVOID)this, 0, 
                        &dwThreadID);
    
创建事件Event内核对象

     HANDLE hThreadHandle = CreateEvent(NULL, 0, TaskMain,
                         (LPVOID)this, 0, 
                        &dwThreadID);


关闭内核对象 

无论怎样创建内核对象,都要调用系统函数CloseHandle来结束对该对象的操作。
函数 BOOL CloseHandle(HANDLE hObj)
这个函数会检查该内核对象的使用计数,如果使用计数是0,该内核便从内存中销毁这个内核对象。
(虽然系统会为您的遗漏做一些弥补的工作,比如在进程退出的时候扫描内核对象句柄表,关闭那些使用计数是0的内核对象,但是正常关闭一个您创建的内核对象,是称职的程序员所应该做的。 ) 

内核对象状态

内核对象都有两种状态:已通知和未通知,包括Thread,Event,Semaphore,Mutex等内核对象都有这两种状态。
当一个线程等待一个未通知状态的内核对象,该线程将进入等待状态,线程不占用CPU
等待内核对象使用WaitForSingleObject函数
对于线程的内核对象,总是在未通知的状态下创建的,当线程终止运行的时候,线程的内核对象的状态变为已通知 
这样的特点可以使我们知道一个线程是何时终止运行的,我们只需要等待线程的内核对象从未通知变成已通知,则可以知道线程退出了。

等待单个内核对象 WaitForSingleObject

WaitForSingleObject(HANDLE hHandle,// 内核对象句柄
             DWORD dwMilliseconds )// 等待时间
WaitForSingleOjbect函数可以等待一个内核对象,直到该内核对象的状态变为已通知状态,或者指定的等待时间已经到了, 否则等待的线程处于等待态,是不占用CPU的,当等待的时间到了,函数返回WAIT_TIMEOUT ,如果是等待的对象变成已通知,则返回WAIT_OBJECT_0 。
例:
hThreadHandle = CreateThread(ThreadFunc, 0, 0, NULL);
DWORD dwRet = WaitForSingleObject(hThreadHandle, 500);
// 注:TimeOut也可以指定为INFINITE,表示无限等待
if ( dwRet == WAIT_TIMEOUT) {
    …
} else if ( dwRet == WAIT_OBJECT_0  ) {
    …
} else {
    // 错误处理
}

只有当hThreadHandle对应的线程函数返回的时候,hThreadHandle的状态才变成已通知,WaitForSingleObject函数才能返回。


等待多个内核对象WaitForMultipleObjects

也可以一次等待多个内核对象,尤其在一个线程等待多个事件(Event)的情况下
DWORD WaitForMultipleObjects( 
DWORD         nCount,     // 等待内核对象的个数
CONST HANDLE*lpHandles,      // 存储内核对象的数组指针
BOOL     fWaitAll,     // 等到全部内核对象才退出标志
DWORD         dwMilliseconds );     // TimeOut
fWaitAll标志设置为TRUE,表示只有等待的全部内核对象都变成已通知状态才返回,FALSE表示有一个内核对象变成已通知状态就返回。
在fWaitAll为FALSE情况下,如果第一个内核对象变成已通知状态,则返回WAIT_OBJECT_0, 如果第二个内核对象变成已通知状态,则返回WAIT_OBJECT_1, 以此类推。

等待多个内核对象WaitForMultipleObjects 例子程序

线程A需要等待N种Event,则一个基本的等待方式是:
HANDLE g_ahThreadEvent[N]; // 这是内核对象数组,事先被创建好
DWORD WINAPI ThreadFunc(PVOID pParam)  // 线程的主函数
{
    BOOL bRunFlag=TRUE;
    while(bRunFlag) {
DWORD dwRet = WaitForMultipleObjects(N, 
g_ahThreadEvent,FALSE, INFINITE);
Switch(dwRet- WAIT_OBJECT_0){
    case 0:    // 线程要退出
        bRunFlag = FALSE;        
        break;
    case 1:
        processEvent1();
        break;
    case N:
        processEvent2();
        break;
    default:
        // 错误处理
}
    }
    // 做一些释放资源等的善后工作
}

注:这只是一个例子,并不是一个风格良好的代码

跨越进程共享内核对象

许多情况下,不同进程之间需要共享内核对象。
文件映射对象能使你在同一机器的两个进程之间共享数据块
互斥对象,信标和事件使不同进程中的线程能够同步它们的连续运行
跨进程共享内核对象通过对象句柄的继承性,命名对象等方式实现。

对象句柄的继承性

只有当进程具有父子关系时,才能使用对象句柄的继承性
父进程创建子进程,为子进程赋予对父进程内核对象对象的访问权
内核对象句柄如果能被继承,必须在创建内核对象的时候必须把安全描述符的成员赋值:bInherritHandle=TRUE
父进程创建子进程的时候CreateProcess函数的bInheritHandle参数必须指定为TRUE
通常把内核对象句柄作为进程启动命令行参数(CreateProcess的pszCommondLine)传递给子进程

命名对象

共享跨进程边界的内核对象的另外一种方法是给对象命名
CreateMutex,CreateEvent, CreateSemaphore,CreateWaitableTime,CreateFileMapping函数都有一个参数pszName作为这个内核对象的名称
为pszName传递NULL参数,则系统创建匿名的内核对象
如果指定了同名的内核对象名称,则系统返回同一个内核对象(Handle值不同,Handle是属于进程的)

命名对象例

进程A创建一个名字为”JeffMutex”的Mutex
    hMutexA = CreateMutex(NULL, FALSE,”JeffMutex”);
进程B创建一个同名的Mutex
    hMutexB = CreateMutex(NULL, FALSE, “JeffMutex”);如果同名对象存在,则hMutexB指向了和hMutexA所指的同一个Mutex,
     GetLastError会返回ERROR_ALREADY_EXITS.否则系统会创建一个新的内核对象。
hMutex != hMutexB
也可以使用Open函数打开一个对象,例
     hMutexB = OpenMutex(MUTEX_ALL_ACCESS , FALSE, “JeffMutex”);
    当同名的Mutex不存在时,GetLasteError会返回ERROR_FILE_NOT_FOUND
不同类型之间的内核对象,使用同名会导致错误
    hMutexA = CreateMutex(NULL,FALSE,”JeffMutex”);
    hSemaphoreA = CreateSemaphore(NULL,1,0,”JeffMutex”);
    则hSemaphoreA 不能被创建出来

你可能感兴趣的:(windows,经验分享)