Mac下的进程与线程10.docx
《Mac下的进程与线程10.docx》由会员分享,可在线阅读,更多相关《Mac下的进程与线程10.docx(33页珍藏版)》请在冰点文库上搜索。
![Mac下的进程与线程10.docx](https://file1.bingdoc.com/fileroot1/2023-5/28/fd15ff91-2b4f-448c-9e26-836e44a18ad5/fd15ff91-2b4f-448c-9e26-836e44a18ad51.gif)
Mac下的进程与线程10
Mac下的进程与线程
Version1.00
RevisionHistory
Version
Date
Originator
Description
1.00
2007/11/9
张永峰
1.引言
MacOSX是一个抢占式多任务环境,并且支持实时处理应用程序的能力。
MacOSX的内核提供了很多增强机制,包括强占式、增强内存保护、改进的网络机制、支持Macintosh(标准和扩展)和非Macintosh(UFS,ISO9660等)文件系统、面向对象API等等。
在这些特性中,抢占式和内存保护机制使系统的运行更加安全高效。
Mach作为MacOSX的内核部分提供了一个小的简单而强大的抽象集,这些主要内核抽象有:
●任务(Tasks):
也称为进程,是资源联系的单元。
每个任务由一个虚拟地址空间、一个端口和一个或多个线程等组成。
●线程(Threads):
在一个任务内CPU执行的单元。
●地址空间(Addressspace):
与内存管理器相关联。
●端口(Ports):
安全、独立的通信通道,具有发送和接收对象的能力。
●进程间通信(IPC):
消息队列,远程过程调用,通知,信号量和锁设置。
●时间(Time):
锁、定时器和等待。
Mach的任务(tasks)和线程(threads)分别实现了MacOSX的进程和POSIX的线程(pthreads)。
一个任务为它所包含的线程提供资源,而线程主要是为了实现并行运算和资源共享。
在UNIX中,一个process包括了一个执行中的程序和一些他所需的系统资源,诸如档案描述表和地址空间等。
但是在Mach中,一个task却只包括了一些系统资源,而由thread掌握了所有的执行活动。
一个Machtask可能有任意多个thread,而Mach系统中所有的thread均属于一些task。
属于同一个task的所有thread共享该task所拥有的系统资源。
因此,thread实质上就是一个程序计数器、一个堆栈再加上一组缓存器。
UNIX的一个process可以看成是一个只有一个thread的Machtask。
Machthread让程序设计师们能很方便的做出执行于单一或多重处理器环境下同时执行的程序,而不需要考虑处理器多寡的问题,能直接得到多重处理的效能(如果有多的处理器的话)。
但在这一方面Linux的支持目前并不是很完美。
本文档以下部分主要介绍Mac系统中的进程和线程。
注:
由于在Mac官方的文档习惯于将进程称作任务,所以本文对这一术语将以官方参考资料为准。
2.Mac进程
2.1.概述
任务(tasks)是相当消耗资源的实体,其是以一个资源集合而存在。
任务中的所有线程共享任务内的任何资源。
任务没有它自己的活动生命——只有线程执行指令。
通常称“任务X在处理Y事件”,其实质是“一个在任务X中的线程在处理Y事件”。
2.2.进程间通信(IPC)
进程间通信(IPC)是一组编程接口,让程序员能够协调不同的程序进程,使之能在一个操作系统里同时运行。
这使得一个程序能够在同一时间里处理许多用户的要求。
即使只有一个用户使用系统,也可能在一个操作系统中有多个进程的运行,所以进程之间有时需要互相通信,而IPC接口就提供了这种功能。
每种IPC方法均有它自己的优点和局限性。
在Mach中任务间通信是一个重要的元素。
Mach提供了一个客户/服务(client/server)系统结构,一个任务(clients)在通信通道中给另一个任务(servers)通过以发送消息的方式来请求服务。
Mach提供的IPC有以下形式:
●消息队列(MessageQueues)
●信号量(semaphores)
●通知(notifications)
●锁设置(locks)
●远程过程调用(RPC)
IPC是传递情报的工具。
在早期的系统中不是所有的IPC类型都可以被实现。
2.2.1.MessageQueues
最初,在Mach中的进程间通信唯一的方式是消息队列。
仅有一个任务可以拥有接收端口指示的消息队列的权利。
这一个任务被允许接收(read)消息从端口列。
多个任务有权发送消息(write)给相应端口列。
通过建立包含数据元素集合的数据结构,并且给拥有发送权的端口发送消息操作的方式来实现任务之间的通信。
在这之后,有端口接收权限的任务将会接收到消息。
一个消息可能由下面的部分或全部组成:
●纯数据
●内存列的拷贝
●端口权限
●内核暗含的属性,例如:
发送者的安全标记(securitytoken)。
消息传输是一个异步操作。
接收任务内的多线程可能试图从给定的端口接收消息,但仅有一个线程可以接收到发送的消息。
如果使用MultiprocessingServicesAPI实现线程,消息队列为线程通信提供了抽象。
messagequeue是以先进先出(FIFO)的方式来管理线程数据出入的。
一个线程可以有输入和输出队列。
输入队列包含有线程需要去完成的工作,而输出队列包含处理完成后的结果。
虽然消息队列提供了一个简单和方便的抽象,但是其与其它类型的通信方式相比性能却较慢。
如果使用它们传输较小的消息,性能的消耗可以忽略,但若在一秒内传输成百上千个消息其消耗将是相当可观的。
由于消息队列采用双队列来实现的,在同步的程序设计中将会带来很多的不便和性能上的差距,一般都采用通知(Notifications)机制而很少使用消息队列。
2.2.2.Semaphores
信号量对象支持等待和信号量计数等,如果没有线程当前等待在信号量的等待队列中则信号量设置的值会被保存(即计数)。
MacOSX使用传统的计数信号量而不是二元信号量(锁的本质)。
当一个线程通过一个信号量被唤醒时并不是立即执行,因为可能还有其它的线程也在等待同一资源。
Carbon通过MultiprocessingServicesAPI支持信号量。
Cocoa不能直接地支持信号量,但也可以间接地使用MultiprocessingServices的信号量来代替。
以下是MultiprocessingServices中有关信号量的方法:
●MPCreateSemaphore创建信号量
●MPDeleteSemaphore删除信号量
●MPSignalSemaphore发出信号量。
表示所占用资源释放,进行加1操作。
●MPWaitOnSemaphore等待信号量。
如果信号量的值大于0,那么将做处理并之后进行减1操作。
否则,进程将被阻塞等待直到信号量大于0或超时。
2.2.3.Notifications
在Mac中使用通知(notifications)对象时除了系统预先注册的通知对象外可以自定义通知,并绑定相关的函数,设置(post)操作会触发这个函数执行一些相关操作。
这部分将在线程章节作详细介绍。
2.2.4.Locks
锁是一个提供排它访问有争议性部分的对象。
锁主要使用于事物处理,在事物处理时线程获取锁,从事物处理中返回时释放锁。
这部分将在线程章节作详细介绍。
2.2.5.RemoteProcedureCall(RPC)Objects
RPC对象是为推动和优化远程对象调用而设计的。
RPC对象也主要使用于面向事物处理。
当RPC对象被创建时会定义一个参数块格式集。
当一个客户产生了一个RPC(一个发送对象)时会创建一个预先定义格式的消息并且加入队列中,并最终传给服务(接收者)。
当服务从事物处理中返回时,将给发送者发送回复。
Mach试图通过使用客户端的资源执行服务来优化事物处理,这也被称为线程移植。
由于RPC对象在应用程序中很少使用,因此将不做详细介绍了。
2.3.进程管理
在Cocoa中使用NSTask类,程序可以启动并运行另一个程序,并将其作为它的一个子进程来监视它的执行。
一个NSTask对象可创建一个独立的可执行实体,但其不能与创建它的进程共享内存空间。
一个进程的操作环境是被当前的数个条目决定的,例如:
当前目录,标准输入/输出/错误和其它的环境变量。
NSTask对象缺省的从启动它的线程继承环境。
如果有不同的环境需要改变,例如当前的目录,则必须在进程启动之前改变其设置,进程的环境不能在启动运行之后改变。
进程的参数可以具体化设置,但其参数则不能接收shell的扩展,例如$PWD,这在程序中是不能被识别的。
2.3.1.创建和启动NSTask
有两种方式可以创建NSTask对象。
如果从创建进程的那个环境运行任务是高效的,则可以使用launchedTaskWithLaunchPath:
arguments:
方法。
这个方法即可以创建也可执行(启动)任务。
如果需要改变任务的运行环境,则使用alloc和init方法,用set方法改变环境部分,之后使用启动方法启动任务。
例如,下面的任务用输入文件和输出文件作为参数,其在启动任务之前读取这些参数和文本域的当前目录等。
-(void)runTask:
(id)sender
{
NSTask*aTask=[[NSTaskalloc]init];
NSMutableArray*args=[NSMutableArrayarray];
/*setarguments*/
[argsaddObject:
[[inputFilestringValue]lastPathComponent]];
[argsaddObject:
[outputFilestringValue]];
[aTasksetCurrentDirectoryPath:
[[inputFilestringValue]
stringByDeletingLastPathComponent]];
[aTasksetLaunchPath:
[taskFieldstringValue]];
[aTasksetArguments:
args];
[aTasklaunch];
}
如果使用这种方式创建NSTask对象,则必须确定使用setLaunchPath设置可执行的名字,而且因为在Mac下的应用程序是以.app为后缀的,如果以这个.app所在目录为进程的启动路径是不行的,必须以.app的内部真正的执行文件为启动路径,否则将出现NSInvalidArgumentException异常。
2.3.2.终止NSTask
通常地,任务启动后会运行直到结束完成。
当任务退出时,相应的NSTask对象给缺省的消息中心发送NSTaskDidTerminateNotification消息,也可以在程序中使用terminate:
方法来结束进程。
添加一个定制的对象作为消息监听者并在方法中核查任务的退出状态。
下面是一段用来测试程序退出的例子:
-(id)init
{
self=[superinit];
[[NSNotificationCenterdefaultCenter]addObserver:
self
selector:
@selector(checkATaskStatus:
)
name:
NSTaskDidTerminateNotification
object:
nil];
returnself;
}
-(void)checkATaskStatus:
(NSNotification*)aNotification
{
intstatus=[[aNotificationobject]terminationStatus];
if(status==ATASK_SUCCESS_VALUE)
NSLog(@"Tasksucceeded.");
else
NSLog(@"Taskfailed.");
}
如果需要强迫结束执行的任务,发送一个终止消息给NSTask对象。
NSTask对象已经被释放,而NSTaskDidTerminateNotification将不会发送出去,因为发送消息的端口作为任务释放的一部分已经被释放了。
2.3.3.进程间的管道数据传输
在启动的进程之前可以通过绑定一个或多个NSPipe对象给进程的标准输入/输出/错误设备进行通信。
在关联的进程之间管道是一个单向的通信通道,一个进程写数据而另一个读数据。
通过管道传递的数据是带缓冲的;其大小由所在的操作系统决定。
一个管道对象由两个端点表示。
NSPipe对象的端点是一个NSFileHandle实例。
从适当的NSFileHandle对象读写数据可作为进程的输出/输入数据。
多任务可以通过在一个任务的标准输出和另一个任务的标准输入之间帮定NSPipe对象,这样就将二者连接起来,从第一个任务的输出将自动发送到第二个任务输入。
任务的标准输入/输出/错误设备可以直接地绑定到NSFileHandle对象,这样使输入数据来自一个文件,而输出到另一文件。
每个管道(pipe)的两端是一个文件描述符——NSFileHandle对象表示。
使用NSFileHandle消息读或写管道(pipe)数据。
父进程创建NSPipe对象并占据它的一端,并将创建的NSPipe对象的另一端设置为其它进程的标准输入/输出/错误。
这样两个进程间就可以传递数据进行通信了,一个进程的输入正好是另一个进程的输出。
下面的例子是在一个进程程序的readTaskData:
方法中设置管道连接的具体实现:
-(void)readTaskData:
(id)sender
{
NSTask*pipeTask=[[NSTaskalloc]init];
NSPipe*newPipe=[NSPipepipe];
NSFileHandle*readHandle=[newPipefileHandleForReading];
NSData*inData=nil;
//writehandleisclosedtothisprocess
[pipeTasksetStandardOutput:
newPipe];
[pipeTasksetLaunchPath:
[NSHomeDirectory()
stringByAppendingPathComponent:
@"PipeTask.app/Contents/MacOS/PipeTask"]];
[pipeTasklaunch];
while((inData=[readHandleavailableData])&&[inDatalength])
{
[selfprocessData:
inData];
}
[pipeTaskrelease];
}
而在PipeTask类中有如下的代码,这表示在进程PipeTask向标准的输出传送字符串str。
fprintf(stdout,str);
在这个例子中启动的进程(PipeTask)必须输出数据或写数据(使用NSFileHandle的writeData:
方法或NSFileHandle的fileHandleWithStandardOutput方法)给它的标准输出设备。
当进程没有更多的数据通过管道通信的时候,写进程应该简单的发送closeFile给NSFileHandle最后点,这导致使用“read”NSFileHandle的进程接收到一个空的NSData对象——标志数据结束。
如果父进程使用init方法创建NSPipe对象则应该在之后释放它。
2.3.4.Carbon进程API
启动一个应用程序,其属于ProcessManager(官方不推荐使用)
OSErrLaunchApplication(
LaunchPBPtrLaunchParams
);
参数说明:
LaunchParams
一个LaunchParamBlockRec指针,其是关于应用程序启动的具体信息
ReturnValue
应用程序结果码
注意:
LaunchApplication函数从具体的文件启动应用程序,如果成功的话则返回进程的序列码,最适合的和最小的分区大小。
如果启动了另一个应用程序而没有终止此应用程序,已启动的应用程序将不会准确的执行直到做出一个后来的调用——WaitNextEvent或EventAvail。
如果想让应用程序在启动具体应用程序之后继续运行,则设置launchContinue标志在启动参数块的相应域。
如果没有设置这个标志,LaunchApplication将终止应用程序在启动其它应用程序之后,即使启动失败。
3.Mac线程
3.1.概述
线程是轻量级的实体,它可以廉价的创建并且有低级的操作。
线程有很少的状态信息(大多是它的注册状态)。
其所属的任务担负资源管理。
在一个多处理器计算机上,一个任务中的多个线程可能执行在并行方式下。
多线程有个优点是可以用在同步化程序中,而代替了试图用单线程同步程序提供的多样服务。
线程(threads)的基本特点有:
●是进程中一个控制流点
●可访问所属任务内的元素(资源)
●与其它线程并行执行
●拥有最小的状态信息
线程是基本的计算实体。
一个线程属于一个并且仅有一个任务定义它的地址空间,它影响的是地址空间的结构或参照资源而不是地址空间。
Mach为线程提供了一个相应的框架——调度策略(schedulingpolicies)。
MacOSX早期的版本既支持共享时间也支持固定优先权策略。
一个共享时间的线程优先权的高低是与其它共享时间线程在资源消耗方面比较并均衡后得到的。
固定的优先权线程是在某个时间量(段)内执行的,在执行完之后将在平等优先权线程队列的末尾继续排队。
设置固定优先线程的量级无限大后,除非阻塞它将一直运行。
高级权限的时实线程通常是固定优先级。
MacOSX另外为实时平台提供了时间限制的调度。
这种调度方式允许线程在某个时间段内获得某个时间量,在这个时间量内来允许线程的执行。
事实上,在MacOSX中除了CarbonThreadManager外,其它的所有线程都是抢占式的。
这个特例的调度方式是协作式的,协作式线程执行在给定的时间段,相当于时间片调度。
这样做是为了保证早期的MacOS应用程序在移植到MacOSX的时候不会崩溃。
在MacOSX中,每一个进程(应用程序)由一个或多个线程组成。
每一个线程代表应用程序代码的一个单独的可执行流。
每个应用程序用一个单线程启动,它被用来运行应用程序的主函数。
应用程序可以产生或增加线程。
当一个应用程序产生一个新的线程后,这个线程将变成应用程序进程空间内的独立实体。
每一个线程用它自己的执行堆栈和内核独立调度的运行时。
线程可以与其它的线程和进程通信、I/O操作和任何需要做的操作。
因为它们是在同一个进程空间内,所以在独立的应用程序内的所有线程共享同一虚拟空间并且具有进程相同的访问权限。
MacOSX提供了数个创建和管理线程的API。
虽然每个API的底层实现技术是相同的,但它们提供了不同的管理和创建线程方式。
Cocoa环境提供了一个面向对象的线程模型。
Carbon环境为熟悉早期MacOS的开发者提供了一个接口集。
Darwin层提供了POSIX接口,其可最大化的控制线程。
同其它操作系统上的一样,MAC上的线程在它的生命期内也分为四种状态:
●运行:
当前正在执行它的代码。
●阻塞:
为了执行而等待或正在等待锁等资源的释放
●准备:
线程没有运行,但只要处理时机允许将能够运行。
●终止:
线程已经完成了处理并且等待资源被收回
3.2.线程包
MacOSX为创建应用级线程提供了数个API。
从行为表现上来说,一个API创建的线程与其它API创建的本质上标识是一样的。
应用程序的类型(Carbon,Cocoa,Darwin)决定了使用那种API来创建线程。
但是在其也各有优略,例如Cocoa线程可简单方便的使用但其适应性和兼容方面不如POSIX线程。
3.2.1.MachThreads
Mach线程(有时也称内核线程)提供系统上基本的线程实现。
所有其它的线程API都来自于Mach线程。
应用程序开发者很少直接使用Mach线程。
Mach线程被用在内核级的程序中,例如内核扩展。
应用程序应该使用其它高级层的线程API。
3.2.2.POSIXThreads
POSIX线程(通常参照于“pthread”)是一个围绕Mach线程被用于用户级进程的轻量级包。
POSIX线程是所有应用层线程包的基础。
包括Cocoa线程,CarbonThreadManager线程和多处理服务(MultiprocessingServices)线程。
POSIX线程API是基于C并且提供可理解的创建和访问线程信息的控制。
这个扩展的控制意味着程序必须启动和维持线程,在这一方面超过了其它线程API所需要做的。
例如不像其它线程包那样,POSIX线程不能自动的协调与应用程序的运行循环(runloop(后面将会讲到))。
因为其支持更多的线程行为控制,所以POSIX线程与其它线程包相比支持最直接的执行方法。
POSIX线程另一个优点是在很多不同平台访问用的API是相同的。
所以使用POSIX线程代码可以被用在大多数的UNIX(Windows目前还不支持)系统中。
.
3.2.3.CocoaThreads
Cocoa线程(被包含于NSThread类中)提供了一个创建和管理线程的高层抽象。
Cocoa线程可以给应用程序设置适当的关于线程情报的Cocoa通知(notifications)。
虽然NSThread类使用POSIX线程来实现它的线程行为,但这种线程提供了较少管理的选择。
这些特性足够实现大多数高级的多线程程序。
后面的章节将重点以MAC本地的Cocoa线程为主来详细讲解。
3.2.4.CarbonMultiprocessingServices
CarbonMultiprocessingServices是一个管理Carbon应用程序中抢占式线程的API。
这个模型基于MacOS9系统的MPTaskAPI,MPTaskAPI是由CarbonThreadManager支持的,其可以创建协作式线程模型。
在MultiprocessingServicesAPI下,一个应用程序可以创建多个抢占执行的且具有在多处理器上运行的线程。
如果是在多处理器下,MultiprocessingServices将分离任务给所有的处理器以使效率最大化(这一技术通常称为“均衡多处理”)。
如果是在单处理器下,MultiprocessingServices将简单的调度可用的任务以确保每个任务都被注意到。
对于Carbon应用程序,MultiprocessingServices是创建线程的常用方式。
其API创建的线程同POSIX线程和NSThread一样。
它也提供线程同步,通知,定时器,远程过程调用,异常获取,信号量等功能或操作。
3.2.5.CarbonThreadManager
CarbonThreadManager继承自应用Carbon程序管理协作式线程的API。
其