`
yuanjinxiu
  • 浏览: 657838 次
文章分类
社区版块
存档分类
最新评论

reactos操作系统实现(86)

 
阅读更多

创建完成端口需要调用Windows APICreateIoCompletionPort

HANDLE CreateIoCompletionPort(
HANDLE FileHandle,
HANDLE ExistingCompletionPort,
DWORD CompletionKey,
DWORD NumberOfConcurrentThreads
);

创建一个完成端口时,通常对参数ExistingCompletionPort赋值NULLNumberOfConcurrentThreads参数定义了在完成端口上同时允许执行的线程数量。如果有文件句柄传递给FileHandle参数,则该文件与完成端口关联在了一起。当这个文件上的I/O请求完成时,一个完成通知包将被投递到完成端口消息队列中。另外一个APIGetQueuedCompletionStatus是用来获取排队完成状态,它使调用线程挂起,直到收到一个完成通知包。

BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort,
LPDWORD lpNumberOfBytesTransferred,
LPDWORD CompletionKey,
LPOVERLAPPED* lpOverlapped,
DWORD dwMiillisecondTimeout
);

完成端口实际上是在管理一个线程池,它会记录当前活动(即没有被I/O等事件阻塞)的线程数。当有完成通知包到达该端口时,在该端口上等待的线程按照后进先出(LIFO)的次序被唤醒,因此最近(most recently)被阻塞的线程就是获得下一个完成通知包的线程。那些长时间得不到响应的的线程的堆栈将会被从内存调到磁盘交换区去等待,当与一个端口关联的线程太多超过了当前的处理能力时,就可以将长时间阻塞的线程占用的内存减到最少。

服务器应用程序往往通过网络端点来接受客户请求,而这些网络端点是由文件句柄来表示的。这样的例子包括Windows Sockets 2(Winsock2)套接字或者命名管道。当服务器创建它的通信端点时,它将这些通信端点与一个完成端口关联起来,并且它的线程通过调用GetQueuedCompletionStatus来等待此端口上进来的完成通知。当一个线程在此完成端口上得到一个I/O完成通知包时,它便不再等待,开始处理I/O结果数据,从而变成一个活动的线程。一个线程在处理过程中可能将阻塞很多次,比如当它需要从磁盘上的文件读取数据时,或者当它需要与其他的线程同步时。Windows NT检测到这些活动,并且识别出该完成端口上至少已经有一个活动线程。因此,当活动线程由于I/O请求而阻塞时,如果在队列中存在一个包,则唤醒另一个正在此完成端口上等待的线程提供处理服务。

微软的指导原则是,将并发值设置成大约等于该系统中处理器的数目。但是要注意,一个完成端口上实际活动线程数量有可能超过设置的并发值。考虑并发值被设置为1的情况,一个客户请求进来了,某个线程因为被调度来处理该请求而变成活动的。下一个请求到达时,正在该端口上等待的另一个线程却不允许执行,因为活动的线程数已经达到了设置的并发上限值。然后,当活动线程需要等待I/O而阻塞时,等待的线程将被激活,当它尚在活动时,上一个线程的I/O完成了,这使得它继续保持活动状态(继续执行数据处理服务)。此刻,一直到两个线程中有一个被阻塞,并发值始终是2,高于设置的并发上限值1。大多数时候,活动线程数将维持在设置的并发限制值上,或者超过一点。

应用程序通过调用PostQueuedCompletionStatus这个API向完成端口投递一个自定义的完成通知包。服务器一般通过该函数发送消息通知线程有外部事件发生,例如需要温和的关机。

完成端口内部机制

当传递NULL值给ExistingCompletionPort参数来调用CreateIoCompletionPort来创建完成端口时,将调用同名的NtCreateIoCompletion系统服务。实质上,IoCompletion对象是建立在一个称为队列的内核同步对象基础上。系统创建一个完成端口的同时,在完成端口所分配到的内存中初始化一个队列对象(指向完成端口的指针同时指向了此队列对象,因为队列对象位于完成端口对象内存的开始处)。当一个线程调用CreateIoCompletionPort来创建完成端口时,第四个参数NumberOfConcurrentThreads即为队列的并发值。NtCreateIoCompletion函数将调用KeInitializeQueue系统服务来初始化该端口的消息队列。

当应用程序再次调用CreateIoCompletionPort时,将调用NtSetInformationFile服务来使参数一(文件句柄)与参数二(一个已有的完成端口)关联起来。完成通知包FileCompletionInformation包含的信息:CreateIoCompletionPort的参数二ExistingCompletionPort(已有的完成端口句柄)和参数三CompletionKey(完成键)。NtSetInformationFile通过解引用操作从该文件句柄获得对应的文件对象,并且申请一个记录完成上下文的数据结构。这个数据结构在NTDDK.H定义如下:

typedef struct _IO_COMPLETION_CONTEXT {
PVOID Port;
ULONG Key;
} IO_COMPLETION_CONTEXT, *PIO_COMPLETION_CONTEXT;

最后,将调用NtSetInformationFile系统服务设置文件对象中CompletionContext域的值。当一个异步I/O在一个文件对象上完成时,系统内部执行具有I/O管理功能的IopCompleteRequest系统服务,检查文件对象中的CompletionContext域是否为非NULL。如果是,则I/O管理器生成一个完成通知包,通过调用KeInsertQueue系统服务将完成通知包投递到完成端口队列(注意,完成端口对象和队列对象是同义的)。

当一个服务器线程调用GetQueuedCompletionStatus时,它将调用NtRemoveIoCompletion系统服务。在验证参数后,并且将完成端口句柄转换成一个指向该端口的指针后,NtRemoveIoCompletion调用KeRemoveQueue

正如你所看到的,KeRemoveQueueKeInsertQueue是完成端口模型的两个引擎级函数,它们决定阻塞在完成端口上等待I/O完成通知包的线程什么时候被唤醒。在系统内部,队列对象维护了完成端口上当前活动线程的计数值,以及最大的并发活动线程的数量。当一个线程调用KeRemoveQueue并且当前活动线程数大于或等于并发数上限时,那么该线程将被投放到一个阻塞线程队列(按LIFO顺序)中,等待系统调度来获取并处理完成通知包。此线程列表挂在队列对象的外面,线程的控制块数据结构中有一个指针引用了一个与之相关的队列对象;如果这个指针为NULL,则该线程没有与队列关联。

Windows依赖与线程控制块中的队列指针来跟踪和记录那些“由于被阻塞在除了完成端口之外的其他事情上而变成不活动”的线程。那些有可能会导致一个线程阻塞的调度例程(例如KeWaitForSingleObjectKeDelayExecutionThread等等)要检查该线程的队列指针。如果该指针不为NULL,则这些函数调用KiActivateWaiterQueue一个与队列相关的函数,它会递减与该队列相关联的活动线程的计数值。如果计数值递减到小于设置的并发值,并且此时至少有一个完成通知包在该队列中,那么处于该队列的线程列表最前面的那个线程被唤醒,并且把最老的(the oldest)完成通知包交给它处理。相反,无论何时,与一个队列相关联的线程在阻塞之后被唤醒时,调度程序执行KiUnwaitThread函数来增加该队列上活动线程的计数值。

最后,PostQueuedCompletionStatus这个Windows API将调用NtSetIoCompletion服务。该函数只是简单的调用KeInsertQueue将自定义的完成通知包插入到完成端口的队列中。

没有公开的

Windows NT的完成端口API提供了一种易于使用和高效的方法最大限度地发挥服务器的性能——最大限度的减少上下文切换的同时最大限度的提高系统并发量。这些API使我们能够调用I/O管理器和内核提供的一些服务功能。队列对象可以被设备驱动程序调用(这些接口尽管没有公开,但还是很容易查询到的),不过完成端口的API没有提供相关访问功能。但是,如果队列接口被继承,我们完全可以通过编写队列处理程序并通过手动设置CompletionContext的值来模拟完成端口模型。

创建完成端口的函数,如下:

#001 NTSTATUS

#002 NTAPI

#003 NtCreateIoCompletion(OUT PHANDLE IoCompletionHandle,

#004 IN ACCESS_MASK DesiredAccess,

#005 IN POBJECT_ATTRIBUTES ObjectAttributes,

#006 IN ULONG NumberOfConcurrentThreads)

#007 {

#008 PKQUEUE Queue;

#009 HANDLE hIoCompletionHandle;

#010 KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();

#011 NTSTATUS Status = STATUS_SUCCESS;

#012 PAGED_CODE();

#013

检查是否用户模式调用,如果是就使用SEH机制。

#014 /* Check if this was a user-mode call */

#015 if (PreviousMode != KernelMode)

#016 {

#017 /* Wrap probing in SEH */

#018 _SEH2_TRY

#019 {

#020 /* Probe the handle */

#021 ProbeForWriteHandle(IoCompletionHandle);

#022 }

#023 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)

#024 {

#025 /* Get the exception code */

#026 Status = _SEH2_GetExceptionCode();

#027 }

#028 _SEH2_END;

#029

#030 /* Fail on exception */

#031 if (!NT_SUCCESS(Status)) return Status;

#032 }

#033

创建一个I/O对象。

#034 /* Create the Object */

#035 Status = ObCreateObject(PreviousMode,

#036 IoCompletionType,

#037 ObjectAttributes,

#038 PreviousMode,

#039 NULL,

#040 sizeof(KQUEUE),

#041 0,

#042 0,

#043 (PVOID*)&Queue);

#044 if (NT_SUCCESS(Status))

#045 {

实始化N个线程的队列。

#046 /* Initialize the Queue */

#047 KeInitializeQueue(Queue, NumberOfConcurrentThreads);

#048

把队列放到对象管理器。

#049 /* Insert it */

#050 Status = ObInsertObject(Queue,

#051 NULL,

#052 DesiredAccess,

#053 0,

#054 NULL,

#055 &hIoCompletionHandle);

#056 if (NT_SUCCESS(Status))

#057 {

#058 /* Protect writing the handle in SEH */

#059 _SEH2_TRY

#060 {

返回I/O句柄给应用程序。

#061 /* Write the handle back */

#062 *IoCompletionHandle = hIoCompletionHandle;

#063 }

#064 _SEH2_EXCEPT(ExSystemExceptionFilter())

#065 {

#066 /* Get the exception code */

#067 Status = _SEH2_GetExceptionCode();

#068 }

#069 _SEH2_END;

#070 }

#071 }

#072

#073 /* Return Status */

#074 return Status;

#075 }

下面函数设置I/O完成端口,如下:

#001 NTSTATUS

#002 NTAPI

#003 NtSetIoCompletion(IN HANDLE IoCompletionPortHandle,

#004 IN PVOID CompletionKey,

#005 IN PVOID CompletionContext,

#006 IN NTSTATUS CompletionStatus,

#007 IN ULONG CompletionInformation)

#008 {

#009 NTSTATUS Status;

#010 PKQUEUE Queue;

#011 PAGED_CODE();

#012

#013 /* Get the Object */

#014 Status = ObReferenceObjectByHandle(IoCompletionPortHandle,

#015 IO_COMPLETION_MODIFY_STATE,

#016 IoCompletionType,

#017 ExGetPreviousMode(),

#018 (PVOID*)&Queue,

#019 NULL);

#020 if (NT_SUCCESS(Status))

#021 {

#022 /* Set the Completion */

#023 Status = IoSetIoCompletion(Queue,

#024 CompletionKey,

#025 CompletionContext,

#026 CompletionStatus,

#027 CompletionInformation,

#028 TRUE);

#029

#030 /* Dereference the object */

#031 ObDereferenceObject(Queue);

#032 }

#033

#034 /* Return status */

#035 return Status;

#036 }

现在来看看GetQueuedCompletionStatus函数是通过调用下面函数来实现相关的功能,如下:

#001 NTSTATUS

#002 NTAPI

#003 NtRemoveIoCompletion(IN HANDLE IoCompletionHandle,

#004 OUT PVOID *KeyContext,

#005 OUT PVOID *ApcContext,

#006 OUT PIO_STATUS_BLOCK IoStatusBlock,

#007 IN PLARGE_INTEGER Timeout OPTIONAL)

#008 {

#009 LARGE_INTEGER SafeTimeout;

#010 PKQUEUE Queue;

#011 PIOP_MINI_COMPLETION_PACKET Packet;

#012 PLIST_ENTRY ListEntry;

#013 KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();

#014 NTSTATUS Status = STATUS_SUCCESS;

#015 PIRP Irp;

#016 PVOID Apc, Key;

#017 IO_STATUS_BLOCK IoStatus;

#018 PAGED_CODE();

#019

检查是否用户模式下调用,如果是就使用SEH机制来设置。

#020 /* Check if the call was from user mode */

#021 if (PreviousMode != KernelMode)

#022 {

#023 /* Protect probes in SEH */

#024 _SEH2_TRY

#025 {

#026 /* Probe the pointers */

#027 ProbeForWritePointer(KeyContext);

#028 ProbeForWritePointer(ApcContext);

#029

#030 /* Probe the I/O Status Block */

#031 ProbeForWriteIoStatusBlock(IoStatusBlock);

#032 if (Timeout)

#033 {

#034 /* Probe and capture the timeout */

#035 SafeTimeout = ProbeForReadLargeInteger(Timeout);

#036 Timeout = &SafeTimeout;

#037 }

#038 }

#039 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)

#040 {

#041 /* Get the exception code */

#042 Status = _SEH2_GetExceptionCode();

#043 }

#044 _SEH2_END;

#045

#046 /* Fail on exception */

#047 if (!NT_SUCCESS(Status)) return Status;

#048 }

#049

打开所创建的I/O对象。

#050 /* Open the Object */

#051 Status = ObReferenceObjectByHandle(IoCompletionHandle,

#052 IO_COMPLETION_MODIFY_STATE,

#053 IoCompletionType,

#054 PreviousMode,

#055 (PVOID*)&Queue,

#056 NULL);

#057 if (NT_SUCCESS(Status))

#058 {

从队列里删除IO请求包,如果没有完成的,就会挂起线程运行。

#059 /* Remove queue */

#060 ListEntry = KeRemoveQueue(Queue, PreviousMode, Timeout);

#061

#062 /* If we got a timeout or user_apc back, return the status */

#063 if (((NTSTATUS)ListEntry == STATUS_TIMEOUT) ||

#064 ((NTSTATUS)ListEntry == STATUS_USER_APC))

#065 {

#066 /* Set this as the status */

#067 Status = (NTSTATUS)ListEntry;

#068 }

#069 else

#070 {

#071 /* Get the Packet Data */

#072 Packet = CONTAINING_RECORD(ListEntry,

#073 IOP_MINI_COMPLETION_PACKET,

#074 ListEntry);

#075

#076 /* Check if this is piggybacked on an IRP */

#077 if (Packet->PacketType == IopCompletionPacketIrp)

#078 {

#079 /* Get the IRP */

#080 Irp = CONTAINING_RECORD(ListEntry,

#081 IRP,

#082 Tail.Overlay.ListEntry);

#083

#084 /* Save values */

#085 Key = Irp->Tail.CompletionKey;

#086 Apc = Irp->Overlay.AsynchronousParameters.UserApcContext;

#087 IoStatus = Irp->IoStatus;

#088

#089 /* Free the IRP */

#090 IoFreeIrp(Irp);

#091 }

#092 else

#093 {

#094 /* Save values */

#095 Key = Packet->KeyContext;

#096 Apc = Packet->ApcContext;

#097 IoStatus.Status = Packet->IoStatus;

#098 IoStatus.Information = Packet->IoStatusInformation;

#099

#100 /* Free the packet */

#101 IopFreeMiniPacket(Packet);

#102 }

#103

通过SEH机制返回请求包给应用程序。

#104 /* Enter SEH to write back the values */

#105 _SEH2_TRY

#106 {

#107 /* Write the values to caller */

#108 *ApcContext = Apc;

#109 *KeyContext = Key;

#110 *IoStatusBlock = IoStatus;

#111 }

#112 _SEH2_EXCEPT(ExSystemExceptionFilter())

#113 {

#114 /* Get the exception code */

#115 Status = _SEH2_GetExceptionCode();

#116 }

#117 _SEH2_END;

#118 }

#119

#120 /* Dereference the Object */

#121 ObDereferenceObject(Queue);

#122 }

#123

#124 /* Return status */

#125 return Status;

#126 }

分享到:
评论

相关推荐

    ReactOS-0.4.13-release-14-g2494cfc-iso.zip

    ReactOS项目致力于为大家开发一个免费而且完全兼容 Microsoft Windows XP 的操作系统。ReactOS 旨在通过使用类似构架和提供完整公共接口实现与 NT 以及 XP 操作系统二进制下的应用程序和驱动设备的完全兼容。 ...

    Windows 内核情景分析--采用开源代码ReactOS (上册) part02

    本书通过分析ReactOS的源代码介绍了Windows内核各个方面的结构、功能、算法与具体实现。全书从“内存管理”、“进程”、“进程间通信”、“设备驱动”等多个方面进行分析介绍,所有的分析都有ReactOS的源代码(以及...

    Windows 内核情景分析--采用开源代码ReactOS (上册) part01

    本书通过分析ReactOS的源代码介绍了Windows内核各个方面的结构、功能、算法与具体实现。全书从“内存管理”、“进程”、“进程间通信”、“设备驱动”等多个方面进行分析介绍,所有的分析都有ReactOS的源代码(以及...

    JS-OS:Web上的统一操作系统

    JS操作系统 Web上的统一操作系统。 使用的技术: HTML CSS JavaScript React-JS 如何在本地运行: 克隆仓库git clone https://github.com/NJACKWinterOfCode/JS-OS.git 进入JS-OS光盘进入JS-OS cd JS-OS 安装...

    漫谈兼容内核.zip

    漫谈兼容内核之一:ReactOS怎样实现系统调用 漫谈兼容内核之二:关于kernel-win32的对象管理 漫谈兼容内核之三:Kernel-win32的文件操作 漫谈兼容内核之四:Kernel-win32的进程管理 漫谈兼容内核之五:Kernel-win32...

    LKM-光纤:高级操作系统和虚拟化项目(20172018)课程在罗马大学萨皮恩扎市举行

    在本文档中,我们以Windows NT和ReactOS提供的Fibers实现为参考,介绍了可加载内核模块(LKM)实现。 虽然用户空间实现通常因其开销少且易于调试而成为首选,但是内核空间实现允许更深入地了解内核子系统的工作方式...

    漫谈Linux兼容内核

    01:ReactOS怎样实现系统调用.pdf 02:关于kernel -win32的对象管理.pdf 03:关于kernel-win32的文件操作.pdf 04:Kernel-win32的进程管理.pdf 05:Kernel-win32的系统调用机制.pdf 06:二进制映像的类型识别.pdf 07...

    harmonyos2-reactive-extra:流星React额外包

    React式对象实现。 结帐 用法 var obj = new ReactiveObject ( { 'foo' : '1' } ) ; obj . defineProperty ( 'bar' , 2 ) ; obj . foo = '2' ; obj . undefineProperty ( 'foo' ) ; // Don't use 'delete obj.foo' ...

    DLL注入之远线程方式

    远线程注入 每个进程都有自己的虚拟地址空间,对32位进程来说,这个地址空间的大小为4GB。...如下摘自ReactOS 3.14的代码所示,CreateRemoteThread实际实现的功能就是调用NtCreateThread创建一个属于目标进程的线程。

    Windows之漫谈兼容内核

    漫谈兼容内核之一:ReactOS怎样实现系统调用 漫谈兼容内核之二:关于kernel-win32的对象管理 漫谈兼容内核之三:Kernel-win32的文件操作 漫谈兼容内核之四:Kernel-win32的进程管理 漫谈兼容内核之五:Kernel-win32...

    winampify::high_voltage:具有操作系统外观的界面和经典音频播放器Winamp的重新实现的Spotify Web客户端

    艺术家,专辑和曲目都以文件和文件夹的形式呈现,并且可以在Winamp重新实现轻松地进行操作和播放。现场环境动机构建软件应该保持乐趣。 这个项目主要是一个沙箱,我可以尝试并提高对React和TypeScript的了解。 这也...

    React Native 的 UIDevice 类包装器

    isIpad() 设备型号为 iPad Device.isIphone() 设备型号为 iPhone Properties Device.model 设备型号,如 iPhone 或 iPad Device.deviceName 设备名称,如 John Smith 的 iPhone Device.systemName设备操作系统名称...

    CredBandit

    内存转储是通过使用NTFS事务完成的,NTFS事务使我们可以将转储写入内存,并且MiniDumpWriteDump API已被ReactOS的MiniDumpWriteDump实现改编所取代。 然后,BOF使用base64对内存中的数据进行编码,将其分块,然后...

    漫谈兼容内核.7z

    谈兼容内核之一:ReactOS怎样实现系统调用.pdf 漫谈兼容内核之二:关于kernel -win32的对象管理.pdf 漫谈兼容内核之三:关于kernel-win32的文件操作.pdf 漫谈兼容内核之四:Kernel-win32的进程管理.pdf 漫谈兼容内核...

    phoebe:菲比

    在许多情况下,操作员习惯于处理遥测,实时图表,警报等,这可以帮助他们确定有问题的机器并做出React以解决任何潜在的问题。 但是,一个问题浮现在脑海:如果机器可以自动调整自身并为用户提供自我修复功能,那...

    hyperglass:超级玻璃是试图使互联网变得更好的网络外观玻璃

    功能,主题,UI / API文本,错误消息,命令内置支持: Arista EOS 鸟思科IOS-XR 思科IOS / IOS-XE 思科NX-OS FRRouting 了华为瞻博JunOS 米克罗蒂克诺基亚SR OS TNSR 虚拟操作系统对任何其他配置支持(可选)通过SSH...

    sentinel-crawler:Xenomorph Crawler, a Concise, Declarative and Observable Distributed Crawler(Node Go Java Rust) For Web, RDB, OS, also can act as a Monitor(with Prometheus) or ETL for Infrastructure 多语言执行器,分布式爬虫

    xe-crawlerxe-crawler 是遵循声明式、可监测理念的分布式爬虫,其计划提供 Node.js、Go、Python 多种实现,能够对于静态 Web 页面、动态 Web 页面、关系型数据库、操作系统等异构多源数据进行抓取。xe-crawler 希望...

    图像dct变换matlab代码-SJTU-IE307-hw1:ConductedbyProf.XieRong

    实现分块功能可以采用手动循环的对每个块依次操作,也可以使用Matlab提供的分块处理功能blkproc。 选择两张大小相同的图像,分别进行DFT变换后,置换两幅图像的幅度和相位信息后再作反变换,观察并分析结果。 主要...

Global site tag (gtag.js) - Google Analytics