内存管理 进程 线程


进程、线程、内存管理是一个内核最基本的服务,也是一个内核最基本的组成部分。这几方面的知识是一个软件开发者必须掌握的基础知识。尽管一个人不懂这些知识也能编写简单的程序。但这种程序仅仅能算是皮毛。掌握了进程、线程和内存管理方面的知识,就行充分利用操作系统内核提供的服务,提高你编写的软件的运行效率、更节省资源、更健壮。顺便说一下,在Windows CE.net下能够执行用Visual Studio.net开发的.net平台上执行的软件,但这种软件是最上层的软件。离操作系统内核太远了。

不但执行效率相对较低,并且还要把.net 框架加到内核中。所以在大多数情况下,EVC仍然是第一选择。




图一 CE内核结构

  一、进程和线程

  1、概念:

  Windows CE.NET是一个抢占多任务操作系统,抢占多任务又被称为调度。

在调度过程中,内核的调度系统包括一个当前全部进程中线程的优先级列表,并对全部的线程按优先级排列顺序。

其中断发生时。调度系统又一次安排全部线程的排列顺序。

  一个进程是一个正运行的应用程序的实例。

它由两个部分组成:一个是操作系统用来管理这个进程的内核对象。还有一个是这个进程拥有的地址空间。这个地址空间包括应用程序的代码段、静态数据段、堆、栈,非XIP(Execute In Place)DLL。

从运行角度方面看,一个进程由一个或多个线程组成。

一个线程是一个运行单元,它控制CPU运行进程中某一段代码段。一个线程能够訪问这个进程中全部的地址空间和资源。

一个进程最少包括一个线程来运行代码,这个线程又叫做主线程。

  2、进程:

  Windows CE.NET最多支持32个进程同一时候执行。这是由整个系统分配给全部进程的总地址空间决定的。

低于Windows CE 4.0版本号(也就是低于.NET的版本号)的CE操作系统。总进程空间从0x0000 0000到0x4200 0000 。每32MB地址空间为一个槽(Slot),共33个槽。

当一个进程启动时,内核选择一个没有被占用的槽作为这个进程的地址空间。当中0x0000 0000到0x01FF FFFF这个槽称为Slot 0。

每一个进程在即将得到CPU控制权时,将整个地址映射到Slot 0。

这个进程在帮助文档中称为当前执行进程(currently running process)。

分配一个槽后,内核在这个槽内按由低地址到高地址顺序为代码段、静态数据段分配足够的地址空间,然后是堆、栈。栈之后的空间为全部 DLL保留,包含XIP和非XIP DLL。注意Slot 0最底部64KB是永远保留的。从Slot 1 到 Slot32 为进程使用。前几个槽一般为系统程序使用。如filesys.exe、device.exe、gwes.exe等。

  Windows CE.NET与低版本号操作系统略有不同。这一点是从MSDN 的"Technical Articles"和"Knowledge Base"的文章中找到的。这的确让我费了一番功夫。在Windows CE.NET的帮助文件里仅仅能找到和早期版本号同样的说法,而"Technical Articles"和"Knowledge Base"中有几篇文章清楚的说明了Windows CE.NET 和低版本号操作系统的不同。

在低版本号操作系统中,的确如上所说分为33个槽。Slot 0用于当前执行进程,共支持32个进程同一时候执行。并且全部DLL都载入到进程的地址空间。但Windows CE.NET下 Slot 1也用于当前进程(Slot 1仅仅用于载入全部XIP DLL)。那么一个进程就不是占有32MB地址空间了,而是64MB。在解说内存管理时我会详细解说。

  创建一个进程的API函数例如以下:

BOOL CreateProcess(LPCTSTR lpApplicationName, LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation );

  Windows CE.NET不支持安全性、当前文件夹、继承性。所以这个函数非常多參数都必须设为0或FALSE。详细第3、4、7、8、9设为0,第5设为FALSE。第1參数为应用程序名称,这个參数不能为NULL。假设仅仅传递应用程序名称而没有指定路径,那么系统将先搜索\Windows文件夹,接着搜索OEM指定的搜索路径。第2參数用于传递启动參数,必须为UNICODE码。第6參数为创建标志。

能够为0(创建一个常规进程)、CREATE_SUSPENDED(启动后挂起)、DEBUG_PROCESS(用于创建这个进程的父进程调试用)、DEBUG_ONLY_THIS_PROCESS(不调试子进程)、CREATE_NEW_CONSOLE(控制台进程)。

第10參数传递给它一个PROCESS_INFORMATION结构变量的地址。返回进程和主线程的句柄和ID。

  终止一个进程最好是由WinMain函数返回。

在主线程中调用ExitThread函数也能够。在当前进程终止还有一个进程使用TerminateProcess函数。CE下的TerminateProcess函数要比其它Windows下TerminateProcess函数功能强大。

TerminateThread函数的危急在于,此函数一旦成功运行,指定的线程会立马终止运行,那么可能有非常多的结束处理工作还没来得及进行,可是此线程载入的DLL会被通知结束运行。要调用TerminateThread函数就必须先了解要终止的线程的详细情况。另外假设指定的线程正在运行系统API调用时,TerminateThread函数不能立马终止此线程的运行,而是在API函数运行完成后才退出。所以使用TerminateThread前必须对线程的详细情况要了解。



  3、线程:

  线程除了可以訪问进程的资源外,每一个线程还拥有自己的栈。

栈的大小是可以调整的。最小为1KB或4KB(也就是一个内存页。内存页的大小取决于CPU),一般默觉得64KB,但栈顶端永远保留2KB为防止溢出。假设要改变栈初始时大小,在EVC"Project"-"Settings"-"Link"链接选项"/STACK"后的參数中指定大小。

当中參数1为默认大小,參数2为一个内存页大小,都用十六进制表示。假设将栈的初始值设置太小,非常easy导致系统訪问非法并马上终止进程。



  线程有五中状态,分别为执行、挂起、睡眠、堵塞、终止。当所有线程所有处于堵塞状态时,内核处于空暇模式(Idle mode)。这时对CPU的电力供应将减小。

  创建一个线程的API函数例如以下:

HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId );

  Windows CE.NET 不支持安全性所以參数1必须设置为0。假设參数5为STACK_SIZE_PARAM_IS_A_RESERVATION,那么參数2能够指定栈的大小,内核将依照參数2的数值来为此线程拥有的栈保留地址空间。假设參数5不为STACK_SIZE_PARAM_IS_A_RESERVATION,那么參数2必须设置为0。參数3为运行路径的首地址,也就是函数的地址。參数4用来向线程中传递一个參数。

參数5除了上面说明外,还能够为0、CREATE_SUSPENDED。CREATE_SUSPENDED表示这个线程在创建后一直处于挂起状态。直到用ResumeThread函数来恢复。

最后一个參数保存函数返回的创建的线程ID。

  退出一个线程同退出一个进程有类似的方法。最好是由函数返回。在线程中调用ExitThead函数也能够。



  Windows CE.NET不像其它Windows操作系统将进程分为不同的优先级类,Windows CE.NET仅仅将线程分为256个优先级。

0优先级最高,255最低,0到248优先级属于实时性优先级。0到247优先级一般分配给实时性应用程序、驱动程序、系统程序。249到255优先级中,251优先级(THREAD_PRIORITY_NORMAL)是正常优先级。255优先级(THREAD_PRIORITY_IDLE)为空暇优先级。249优先级(THREAD_PRIORITY_HIGHEST)是高优先级。

248到255优先级一般分配给普通应用程序线程使用。详细分段见下表:

优先级范围 分配对象
0-96 高于驱动程序的程序
97-152 基于Windows CE的驱动程序
153-247 低于驱动程序的程序
248-255 普通的应用程序
 
  Windows CE.NET操作系统具有实时性。所以调度系统必须保证高优先级线程先执行,低优先级线程在高优先级线程终止后或者堵塞时才干得到CPU时间片。并且一旦发生中断。内核会暂停低优先级线程的执行,让高优先级线程继续执行,直到终止或者堵塞。具有同样优先级的线程平均占有CPU时间片。当一个线程使用完了CPU时间片或在时间片内堵塞、睡眠,那么其他同样优先级的线程会占有时间片。这里提到的CPU时间片是指内核限制线程占有CPU的时间,默觉得100ms。OEM能够更改这个值,甚至设置为0。

假设为0,当前线程将一直占有CPU。直到更高优先级线程要求占有CPU。这个调度算法好像是非常有效、非常完美,但却存在着一种情况,当这样的情况发生时程序会死锁。举例来说:一个应用程序包括两个线程。线程1是高优先级,线程2是低优先级。当线程1执行过程中处于堵塞时,线程2得到时间片,线程2这次进入了一个临界区,我们都知道临界区内的资源是不会被其他线程訪问的,当线程2正执行时。线程1已经从堵塞状态转变为执行状态,而这次线程1却要訪问线程2的资源。这个资源却被临界区锁定,那么线程1仅仅能等待。等待线程2从临界区中执行结束并释放资源的独占权。可是线程2却永远不会得到时间片。由于CE保证高优先级线程会先执行。这时程序就会处于死锁状态。

当然系统不会死锁。由于还有更高优先级的线程、驱动程序在执行。对于这样的情况,CE採取优先级转换的办法来解决。就是当发生这样的情况时。内核将线程2的优先级提高到线程1的优先级水平。这样线程2就能够执行完临界区代码了,线程1也就能够訪问资源了。然后内核再恢复线程2原来的优先级。

  挂起一个线程使用SuspendThread函数。

參数仅仅有一个――线程的句柄。

要说明的是假设要挂起的线程正调用一个内核功能。这时运行此函数可能会失败。须要多次调用此函数直到函数返回值不为0xFFFFFFFF。说明挂起成功。恢复线程使用ResumeThread函数。參数也仅仅有一个――线程的句柄。

posted @ 2017-08-20 12:16  jhcelue  阅读(995)  评论(0编辑  收藏  举报