1 / 34

Windows 内 核 对 象

Windows 内 核 对 象. 第二章 Windows 内核对象. 2.1 Windows 内核对象 2.2 创建 Windows 内核对象 2.3 关闭内核对象 2.4 内核对象状态 2.5 等待内核对象 2.6 跨越进程共享内核对象. 2.1 Windows 内核对象. 内核对象 操作系统内部分配的一个内存块,是一种数据结构,其成员负责维护该对象的各种信息。 内核对象可以供系统和应用程序使用,来管理各种各样的资源,比如进程、线程、文件等。

tarmon
Download Presentation

Windows 内 核 对 象

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Windows 内 核 对 象

  2. 第二章 Windows内核对象 • 2.1 Windows 内核对象 • 2.2 创建Windows内核对象 • 2.3 关闭内核对象 • 2.4 内核对象状态 • 2.5 等待内核对象 • 2.6 跨越进程共享内核对象

  3. 2.1 Windows 内核对象 • 内核对象 • 操作系统内部分配的一个内存块,是一种数据结构,其成员负责维护该对象的各种信息。 • 内核对象可以供系统和应用程序使用,来管理各种各样的资源,比如进程、线程、文件等。 • 作为Windows软件开发人员,需要经常创建、打开和操作各种内核对象。系统也要创建和操作若干类型的内核对象 • 内核对象的数据结构只能被内核访问,应用程序无法直接访问及修改,只能通过操作系统提供的函数对内核对象进行操作。 • 常见内核对象 • 存取符号对象、事件对象、文件对象、文件映象对象、I/O完成端口对象、作业对象、信箱对象、互斥对象、管道对象、进程对象、信标对象、线程对象和等待计时器对象

  4. 主要的内核对象 • 进程(Process) • 线程(Thread) • 作业(Job) • 可等待定时器(Timer) • 文件(File) • 信标(Semphore,Event) • 互斥对象(Mutex) • 控制台输入(Console Input,Output)

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

  6. 2.2 创建Windows内核对象 • 使用Windows内核函数来创建和操作内核对象。 • 每个内核对象只是内核创建的一个内存块,并只能由该内核访问,该内存块是一种数据结构,他的成员负责维护该对象的各种信息。 • 例:一个进程对象有一个进程ID、一个基本优先级和退出代码,而文件对象有一个字节位移,一个共享模式和一个打开模式。

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

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

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

  10. 内核对象创建-1 HANDLE CreateThread( LPSECURITY_ATTRIBUTESlpThreadAttributes, // 安全性,缺省NULL SIZE_TdwStackSize, // initial stack size,0表示和调用线程一样大小 LPTHREAD_START_ROUTINElpStartAddress, // thread function LPVOIDlpParameter, // threadargument,传递给线程的参数,NULL则不传参 DWORDdwCreationFlags, // creation option,用于确定线程创建后是否立即运行,0则立即运行 LPDWORDlpThreadId // [out] thread identifier,NULL则线程标识不返回 ); 自定义的线程入口函数(函数名可以自定义) DWORD WINAPI ThreadProc( LPVOIDlpParameter // thread data );

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

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

  13. 内核对象创建-2 • CreateMutex • The CreateMutex function creates or opens a named or unnamed mutex object. • HANDLE CreateMutex( • LPSECURITY_ATTRIBUTESlpMutexAttributes, // SD • BOOLbInitialOwner, // initial owner • LPCTSTRlpName // object name • ); [in] Pointer to a SECURITY_ATTRIBUTES structure that determines whether the returned handle can be inherited by child processes. If lpMutexAttributes is NULL, the handle cannot be inherited. [in] Specifies the initial owner of the mutex object. If this value is TRUE and the caller created the mutex, the calling thread obtains ownership of the mutex object. Otherwise, the calling thread does not obtain ownership of the mutex.

  14. 创建互斥量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);

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

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

  17. 2.5 等待内核对象 • 等待单个内核对象--WaitForSingleObject • 函数原型 WaitForSingleObject( HANDLE hHandle, // 内核对象句柄 DWORD dwMilliseconds //等待时间 ) WaitForSingleOjbect函数可以等待一个内核对象,直到该内核对象的状态变为已通知状态,或者指定的等待时间已经到了, 否则等待的线程处于等待态,是不占用CPU的,当等待的时间到了,函数返回WAIT_TIMEOUT,如果是等待的对象变成已通知,则返回WAIT_OBJECT_0。

  18. 例: hThreadHandle= CreateThread(NULL,0,ThreadFunc, NULL, 0, NULL); DWORD dwRet= WaitForSingleObject(hThreadHandle, 500); if ( dwRet== WAIT_TIMEOUT) { … } else if ( dwRet== WAIT_OBJECT_0 ) { … } else { // 错误处理 } 只有当hThreadHandle对应的线程函数返回的时候,hThreadHandle的状态才变成已通知,WaitForSingleObject函数才能返回。 TimeOut也可以指定为INFINITE,表示无限等待

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

  20. 例:线程A需要等待N种Event,则一个基本的等待方式是:例:线程A需要等待N种Event,则一个基本的等待方式是: HANDLE g_ahThreadEvent[N]; // 这是内核对象数组,事先被创建好 DWORD WINAPI ThreadFunc(PVOIDpParam) // 线程的主函数 { 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: // 错误处理 } } // 做一些释放资源等的善后工作 }

  21. #include <windows.h>//因为要访问Windows提供的API函数 #include <iostream.h>//C++的标准I/O文件 //函数原型声明 DWORD WINAPI Fun1Proc( LPVOID lpParameter); DWORD WINAPI Fun2Proc(LPVOID lpParameter); inttickets=100;//机票数 void main()//主线程入口函数,在该线程中,可以创建新线程 { HANDLE hThread1; HANDLE hThread2; hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL); hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL); CloseHandle(hThread1);//关闭线程句柄 CloseHandle(hThread2); Sleep(5000);//主线程睡眠,以使子线程有机会运行完成 } • 创建两个线程销售机票

  22. //函数实现 DWORD WINAPI Fun1Proc(LPVOID lpParameter) { while(TRUE) { if(tickets>0) { cout<<"thread1 sell ticket : "<<tickets--<<endl; } else break; } return 0; } DWORD WINAPI Fun2Proc(LPVOID lpParameter) { while(TRUE) { if(tickets>0) { cout<<"thread2 sell ticket : "<<tickets--<<endl; } else break; } return 0; }

  23. 对共享资源tickets的访问存在隐患,可能会一张票卖给两个人,而且这种错误是不可再现的。对共享资源tickets的访问存在隐患,可能会一张票卖给两个人,而且这种错误是不可再现的。

  24. //函数实现 DWORD WINAPI Fun1Proc(LPVOID lpParameter) { while(TRUE) { if(tickets>0) { Sleep(1); //当前线程睡眠,以切换到线程2 cout<<"thread1 sell ticket : "<<tickets--<<endl; } else break; } return 0; } DWORD WINAPI Fun2Proc(LPVOID lpParameter) { while(TRUE) { if(tickets>0) { Sleep(1); //当前线程睡眠,以切换到线程1 cout<<"thread2 sell ticket : "<<tickets--<<endl; } else break; } return 0; }

  25. #include <windows.h>//因为要访问Windows提供的API函数 #include <iostream.h>//C++的标准I/O文件 //函数原型声明 DWORD WINAPI Fun1Proc( LPVOID lpParameter); DWORD WINAPI Fun2Proc(LPVOID lpParameter); inttickets=100;//机票数 HANDLE hMutex; //保存(共享的)互斥对象句柄 void main()//主线程入口函数,在该线程中,可以创建新线程 { HANDLE hThread1; HANDLE hThread2; hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL); hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL); CloseHandle(hThread1);//关闭线程句柄 CloseHandle(hThread2); hMutex=CreateMutex(NULL,FALSE,NULL); //创建(匿名的)互斥对象 Sleep(5000);//主线程睡眠,以使子线程有机会运行完成 } FALSE: 创建该互斥对象时,没有线程拥有该互斥对象,操作系统就将该互斥对象设为已通知状态,即有信号状态(signaled)

  26. DWORD WINAPI Fun1Proc(LPVOID lpParameter) { while(TRUE) { WaitForSingleObject(hMutex,INFINITE); if(tickets>0) { Sleep(1); //当前线程睡眠,以切换到线程2 cout<<"thread1 sell ticket : "<<tickets--<<endl; } else break; ReleaseMutex(hMutex); } return 0; } 当线程请求到互斥对象,OS就将互斥对象的线程ID设为该线程的线程ID,并将该互斥对象设为未通知nonsignaled状态。 OS将互斥对象的线程ID设为0,并将该互斥对象设为已通知signaled状态

  27. DWORD WINAPI Fun2Proc(LPVOID lpParameter) { while(TRUE) { WaitForSingleObject(hMutex,INFINITE); if(tickets>0) { Sleep(1); //当前线程睡眠,以切换到线程1 cout<<"thread2 sell ticket : "<<tickets--<<endl; } else break; ReleaseMutex(hMutex); } return 0; }

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

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

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

  31. • 进程A创建一个名字为“JeffMutex”的Mutex hMutexA= CreateMutex(NULL, FALSE,”JeffMutex”); • 进程B创建一个同名的Mutex hMutexB= CreateMutex(NULL, FALSE, “JeffMutex”); • 如果同名对象存在,则hMutexB指向了和hMutexA所指的同一个Mutex,GetLastError会返回ERROR_ALREADY_EXITS. • 否则系统会创建一个新的内核对象。 • hMutexA!= 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不能被创建出来

  32. The end

More Related