win7怎样使用kernel streaming
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了win7怎样使用kernel streaming相关的知识,希望对你有一定的参考价值。
内核流(Kernel Streaming)驱动模型在多媒体方面应用的比较多,支持内核流模型的驱动能够向系统报告它的性能,以及能够将数据高效,通用的传递。通俗的说,就是可以将摄像头的数据直接传递到显卡中显示,而不需要通过应用层.它可以避免数据在应用层和内核层之间的传递,但是这对于上层来说是透明的;并且采用WDM 内核流模型还可以实现设备无关性,使得程序有很好的移植性和通用性。一般来说,QQ摄像头就是使用基于WDM内核流的组件来实现的。所以可以在打开摄像头的时候轻易的更换为给对方播放影音文件(在上层使用相同的组件和流程,仅仅更换了source filter)。在这里需要指明的是,minidriver一般是可以和硬件设备相关,但是也不一样会和硬件设备相关,它在内核层同样可以调用其他的组件,例如可以实现虚拟摄像头之类的应用。
一般来说,硬件设备会提供一个KsProxy组件,这个组件能够完成一些相应的扩展功能,同时,也可以将数据进行不同类别的传送。上层应用程序能够控制底层数据的流向,而不是将数据拷贝到应用层,然后再传递给内核层处理(这个和DirectX的处理有相似的地方,因为DirectShow曾经也是DirectX的一员)。
虽然现在微软对于流内核结构进行了调整,新的流类型采用的是AVStream(下一次在叙述AVStream框架)。但是从目前来看,很多情况下仍然采用目前的方式来处理数据。
下面通过源代码和数据类型的形式来讲解一下这个驱动程序的框架结构。会尽量屏蔽代码中关于具体设备的细节,专注于描述stream class的流程和结构:
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1. 驱动程序的入口点:
NTSTATUS DriverEntry(__in PDRIVER_OBJECT DriverObject, __in PUNICODE_STRING
RegistryPath)
HW_INITIALIZATION_DATA HwInitData;
RtlZeroMemory( &HwInitData, sizeof(HW_INITIALIZATION_DATA) );
HwInitData.HwInitializationDataSize = sizeof(HwInitData); // 结构大小
HwInitData.HwInterrupt = NULL; // 硬件回调
HwInitData.HwReceivePacket = MyReceivePacket; // 控制回调
HwInitData.HwCancelPacket = MyCancelOnePacket; // 取消回调
HwInitData.HwRequestTimeoutHandler = MyTimeoutHandler; // 超时回调
HwInitData.DeviceExtensionSize = sizeof(MY_EXTENSION); // 设备扩展
HwInitData.PerStreamExtensionSize = sizeof(STREAMEX); // 流扩展
HwInitData.PerRequestExtensionSize = sizeof(IRB); // SRB大小
HwInitData.FilterInstanceExtensionSize = 0; // 安装大小
HwInitData.BusMasterDMA = FALSE; // 总线DMA
HwInitData.Dma24BitAddresses = FALSE; // DMA地址
HwInitData.BufferAlignment = sizeof(ULONG) - 1; //
HwInitData.TurnOffSynchronization = TRUE;
HwInitData.DmaBufferSize = 0;
// 注册流端口驱动和回调函数
return (StreamClassRegisterAdapter(DriverObject, RegistryPath, &HwInitData));
这里可以看得出,驱动入口点基本上只是向stream class注册回调函数和信息。此处的设备
扩展和流扩展需要我们自己定义。
超时回调函数和取消回调函数本身并没有做太过于特殊的事情,关键在于控制命令回调函数,它是接收上层,也就是stream class 发送的控制包,下面会详细的讲解控制命令回调函数:
2. MyReceivePacket函数:
VOID
MyReceivePacket(IN PHW_STREAM_REQUEST_BLOCK pSrb)
PIO_STACK_LOCATION IrpStack;
PMY_EXTENSION pDevExt = (PMY_EXTENSION) pSrb->HwDeviceExtension;
PAGED_CODE();
pSrb->Status = STATUS_SUCCESS;
switch (pSrb->Command)
case SRB_INITIALIZE_DEVICE: // 初始化设备
break;
case SRB_INITIALIZATION_COMPLETE: // 初始化设备完成
break;
case SRB_GET_STREAM_INFO:// 获取设备信息
break;
case SRB_OPEN_STREAM: // 打开流
break;
case SRB_CLOSE_STREAM: // 关闭流
break;
case SRB_SURPRISE_REMOVAL: // 移除设备
break;
case SRB_UNKNOWN_DEVICE_COMMAND: // 未知的命令
break;
case SRB_UNINITIALIZE_DEVICE: // 卸载设备
break;
case SRB_GET_DATA_INTERSECTION: // 获取格式和范围
break;
case SRB_CHANGE_POWER_STATE: // 改变电源状态
break;
case SRB_GET_DEVICE_PROPERTY: // 获取设备属性
break;
case SRB_SET_DEVICE_PROPERTY: // 设置设备属性
break;
case SRB_PAGING_OUT_DRIVER: // ?
break;
default:
pSrb->Status = STATUS_NOT_IMPLEMENTED;
break;
StreamClassDeviceNotification(DeviceRequestComplete, pSrb->HwDeviceExtension,
pSrb);
可以看出来的是,上层会通过向这个函数发送命令包,来控制设备的行为。这个端口驱动需要
自己决定从什么拷贝来数据,或者怎么向上层回复。
这里stream class的命令中,需要关注地方并不多,由于设备可能会是USB设备/1394设备/网络
组件/图像采集卡,所以很难统一的给出一份具体的代码.但是通过下面的几个命令的讲解,大家
应该很容易的构建出具体设备的代码来:
2.1 初始化命令: 在设备的初始化阶段,stream class 会依次发送下面的命令
SRB_INITIALIZE_DEVICE->SRB_GET_STREAM_INFO->SRB_INITIALIZATION_COMPLETE
一般来说,SRB_INITIALIZE_DEVICE命令主要是初始化设备扩展和属性结构的初始化,
SRB_GET_STREAM_INFO命令则是向注册表写入自己的属性,并提供相应的另一组回调函数给
Stream class,便于接受更微观的控制;SRB_INITIALIZATION_COMPLETE命令一般是一个完成回调
的方式。
下面的代码会揭示在SRB_GET_STREAM_INFO命令时候,一般会进行的处理:
typedef struct _HW_STREAM_HEADER
ULONG NumberOfStreams; // 支持的流的数目
ULONG SizeOfHwStreamInformation; // 结构大小
ULONG NumDevPropArrayEntries; // 支持的属性数组大小
PKSPROPERTY_SET DevicePropertiesArray; // 属性数组
ULONG NumDevEventArrayEntries; // 支持的事件数组大小
PKSEVENT_SET DeviceEventsArray; // 事件数组
PKSTOPOLOGY Topology; //
PHW_EVENT_ROUTINE DeviceEventRoutine; // 超时时间
ULONG Reserved[2]; // 保留
HW_STREAM_HEADER, *PHW_STREAM_HEADER;
typedef struct _HW_STREAM_INFORMATION
ULONG NumberOfPossibleInstances; // 设备支持的流的数量
KSPIN_DATAFLOW DataFlow; // 数据流的方向
BOOLEAN DataAccessible; // 数据释放是否能够被看到
ULONG NumberOfFormatArrayEntries; // 支持的属性信息
PKSDATARANGE* StreamFormatsArray; // 属性信息数组
PVOID ClassReserved[4];
ULONG NumStreamPropArrayEntries; // 流媒体的支持属性数组的下标
PKSPROPERTY_SET StreamPropertiesArray;// 属性数组
ULONG NumStreamEventArrayEntries;
PKSEVENT_SET StreamEventsArray;
GUID* Category; // Pin范围
GUID* Name; // Pin的名字
ULONG MediumsCount;
const KSPIN_MEDIUM* Mediums; // 媒体类型
BOOLEAN BridgeStream; // 允许流进行桥接?
ULONG Reserved[2];
HW_STREAM_INFORMATION, *PHW_STREAM_INFORMATION;
VOID MyGetStreamInfo(IN PHW_STREAM_REQUEST_BLOCK Srb)
PHW_STREAM_HEADER StreamHeader = &(Srb->CommandData.StreamBuffer->StreamHeader);
PMY_EXTENSION pDevExt = (PMY_EXTENSION) Srb->HwDeviceExtension;
PHW_STREAM_INFORMATION StreamInfo = &(Srb->CommandData.StreamBuffer->StreamInfo);
PAGED_CODE();
ASSERT (Srb->NumberOfBytesToTransfer >=
sizeof (HW_STREAM_HEADER) +
sizeof (HW_STREAM_INFORMATION));
RtlZeroMemory(StreamHeader,
sizeof (HW_STREAM_HEADER) +
sizeof (HW_STREAM_INFORMATION));
StreamHeader->NumberOfStreams = 1;
StreamHeader->SizeOfHwStreamInformation = sizeof(HW_STREAM_INFORMATION);
StreamHeader->NumDevPropArrayEntries = pDevExt->ulPropSetSupported;
StreamHeader->DevicePropertiesArray = &pDevExt->VideoProcAmpSet;
StreamInfo->NumberOfPossibleInstances = 1;
StreamInfo->DataFlow = KSPIN_DATAFLOW_OUT;
StreamInfo->DataAccessible = TRUE;
StreamInfo->NumberOfFormatArrayEntries = pDevExt->ModeSupported;
StreamInfo->StreamFormatsArray = &pDevExt->MyStrmModes[0];
StreamInfo->NumStreamPropArrayEntries = NUMBER_VIDEO_STREAM_PROPERTIES;
StreamInfo->StreamPropertiesArray = (PKSPROPERTY_SET) VideoStreamProperties;
StreamInfo->Name = (GUID *) &PINNAME_VIDEO_CAPTURE;
StreamInfo->Category = (GUID *) &PINNAME_VIDEO_CAPTURE;
Srb->CommandData.StreamBuffer->StreamHeader.Topology = &Topology;
Srb->Status = STATUS_SUCCESS;
2.2 打开和关闭流: SRB_OPEN_STREAM/SRB_CLOSE_STREAM 命令,此处需要注意的就是一个协商的
过程了,因为此处上层和下层需要来协商进行哪种数据类型的传递。
下面的代码片段屏蔽了硬件的具体相关细节,主要描述和stream class相关的部分:
VOID MyOpenStream(IN PHW_STREAM_REQUEST_BLOCK pSrb)
PIRB Irb;
ULONG nSize;
PMY_EXTENSION pDevExt;
PSTREAMEX pStrmEx;
PKS_DATAFORMAT_VIDEOINFOHEADER pKSDataFormat =
(PKS_DATAFORMAT_VIDEOINFOHEADER) pSrb->CommandData.OpenFormat;
PKS_VIDEOINFOHEADER pVideoInfoHdrRequested =
&pKSDataFormat->VideoInfoHeader;
PAGED_CODE();
Irb = (PIRB) pSrb->SRBExtension;
pDevExt = (PMY_EXTENSION) pSrb->HwDeviceExtension;
pStrmEx = (PSTREAMEX)pSrb->StreamObject->HwStreamExtension;
// 缓存流扩展
pDevExt->pStrmEx = pStrmEx;
pSrb->Status = STATUS_SUCCESS;
// 确定哪些编号流被打开了。这些编号表明在流信息结构的偏移数组中被调用的
// 流信息适配器
//
// So:
// 0 - Video data from camera
//
// 0 - 从硬件出来的视频数据
switch (pSrb->StreamObject->StreamNumber)
case 0:
// 检查设备是否在使用
// 找出格式,他们正试图打开第一格式,此处一般采用的是循环对比的方式
// 来找到合适的媒体类型。
if (!AdapterVerifyFormat (pDevExt->ModeSupported, pDevExt->MyStrmModes, pKSDataFormat, pSrb->StreamObject->StreamNumber))
pDevExt->pStrmEx = NULL;
pSrb->Status = STATUS_INVALID_PARAMETER;
return;
// 初始化流扩展
InitializeStreamExtension(pDevExt, pSrb->StreamObject, pStrmEx);
// 使用我们的安全版本
if (!NT_SUCCESS(RTL_SAFE_KS_SIZE_VIDEOHEADER(pVideoInfoHdrRequested, &nSize)))
pSrb->Status = STATUS_INTEGER_OVERFLOW;
return;
pStrmEx->pVideoInfoHeader = ExAllocatePoolWithTag(NonPagedPool, nSize, \'macd\');
if (pStrmEx->pVideoInfoHeader == NULL)
ASSERT(pStrmEx->pVideoInfoHeader != NULL);
pDevExt->pStrmEx = NULL;
pSrb->Status = STATUS_INSUFFICIENT_RESOURCES;
return;
// 拷贝媒体信息头
RtlCopyMemory(
pStrmEx->pVideoInfoHeader,
pVideoInfoHdrRequested,
nSize);
// 分配硬件需要的资源
pSrb->Status = MyAllocateIsochResource(pDevExt, pSrb->SRBExtension, TRUE);
if (pSrb->Status)
ExFreePool(pStrmEx->pVideoInfoHeader);
pStrmEx->pVideoInfoHeader = NULL;
pDevExt->pStrmEx = NULL;
pSrb->Status = STATUS_INSUFFICIENT_RESOURCES;
return;
// 提交控制回调/数据回调函数
pSrb->StreamObject->ReceiveDataPacket = (PHW_RECEIVE_STREAM_DATA_SRB) MyReceiveDataPacket;
pSrb->StreamObject->ReceiveControlPacket = (PHW_RECEIVE_STREAM_CONTROL_SRB) MyReceiveCtrlPacket;
if(pDevExt->bDevRemoved || pDevExt->bStopIsochCallback)
pDevExt->bStopIsochCallback = FALSE;
pDevExt->bDevRemoved = FALSE;
// 初始化流扩展句柄信息
break;
default:
ASSERT(FALSE);
pDevExt->pStrmEx = NULL;
pSrb->Status = STATUS_INVALID_PARAMETER;
return;
pSrb->StreamObject->HwClockObject.ClockSupportFlags = 0;
// 我们不使用DMA方式
pSrb->StreamObject->Dma = FALSE;
pSrb->StreamObject->StreamHeaderMediaSpecific = sizeof(KS_FRAME_INFO);
// PIO 必须设置为mini驱动缓冲区使用逻辑寻址,我们不打算控制这部分缓冲区
pSrb->StreamObject->Pio = FALSE;
// 将最后保存配置
SetCurrentDevicePropertyValues(pDevExt, (PIRB) pSrb->SRBExtension);
ASSERT(pSrb->Status == STATUS_SUCCESS);
VOID MyCloseStream(IN PHW_STREAM_REQUEST_BLOCK pSrb)
PMY_EXTENSION pDevExt;
PSTREAMEX pStrmEx;
PIRB pIrb;
PAGED_CODE();
pSrb->Status = STATUS_SUCCESS;
pDevExt = (PMY_EXTENSION) pSrb->HwDeviceExtension;
ASSERT(pDevExt);
// 等待所有的未决工作完成
KeWaitForSingleObject( &pDevExt->PendingWorkItemEvent, Executive, KernelMode, FALSE, NULL );
pStrmEx = (PSTREAMEX)pDevExt->pStrmEx;
ASSERT(pStrmEx);
if(!pStrmEx )
StreamClassDeviceNotification(DeviceRequestComplete, pSrb->HwDeviceExtension, pSrb);
return;
// pDevExt->Irb可能被释放了,在HwUninitialize()中
// 由于某个原因,所以必须使用下面的
pIrb = (PIRB) pSrb->SRBExtension;
// 保存设备扩展信息
MySetPropertyValuesToRegistry(pDevExt);
// 释放硬件资源
MyFreeIsochResource (pDevExt, pIrb, TRUE);
if(pStrmEx->pVideoInfoHeader)
ExFreePool(pStrmEx->pVideoInfoHeader);
pStrmEx->pVideoInfoHeader = NULL;
pStrmEx->hMasterClock = 0;
// 如果输入读,那么取消掉它们
if(pDevExt->PendingReadCount > 0)
if( InterlockedExchange((PLONG)&pStrmEx->CancelToken, 1 ) == 0 )
MyCancelAllPackets(
pDevExt,
&pDevExt->PendingReadCount
);
pDevExt->pStrmEx = 0;
StreamClassDeviceNotification(DeviceRequestComplete, pSrb->HwDeviceExtension, pSrb);
2.3 属性设置: 属性设置这一部分实际上都是通过特定的属性表来实现的,它和硬件的相关性很大,一般采用DEFINE_KSPROPERTY_TABLE宏来实现对于属性的封装,这一部分可以查阅相应的资料即可实现。 参考技术A 个是Winamp播放器生成的一个文件!好像和某个插件有关!你看看同目录下是不是有音频文件或播放列表!一打开Winamp就会生成这个的,不用担心!!! 求采纳
获取/检查进程 win32 的内部 kernel32 状态(为了安全使用 TerminateThread )
【中文标题】获取/检查进程 win32 的内部 kernel32 状态(为了安全使用 TerminateThread )【英文标题】:get/check inner kernel32 state for process win32 (for safe usage of TerminateThread ) 【发布时间】:2020-08-03 04:27:49 【问题描述】:我为用户可用的线程编写了一个带有终止选项的线程池。如中所述
Documentation of API terminateThread()
,
如果目标线程在终止时正在执行某些 kernel32 调用,则线程进程的 kernel32 状态可能不一致。
我可以自己验证这个问题:在这种情况下终止线程会导致内存分配问题(以及其他问题),但修复该情况同时解决了问题。
问题
所以,我想在每次使用 terminateThread()
后检查这个内部状态。如果terminateThread()
导致 kernel32.dll 中进程的内部状态出现问题,我想引发异常 - 并在登录到用户后终止进程(除非仍然可以修复内部状态)。
这可行吗?也许通过找到相关状态变量的地址(或类似的东西 - 通过匹配 kernel32 的 pdb 文件或其他方式)?这种情况对我来说很糟糕——如果我无法解决它,我要么必须省略 threapool 的终止选项,要么只将线程留给自己。任何提示将不胜感激!
还有其他win32函数会导致类似问题吗?
一个。当一个线程调用了一个绝对不会返回的阻塞 kernel32 函数时,为它自己留下一个线程是否安全?
b.如果win32函数返回,lambda函数被销毁了怎么办?
我为什么要问这个? (补充资料)
我的项目中有一个自定义线程池,我在其中调用了一些有时可能会永远阻塞的 win32 API。因此,我使用超时来调用它们。当达到该超时时,我调用terminateThread()
并让我的线程池返回“不成功的调用状态”。
有时,我当前的应用程序会遇到死锁。我发现这个死锁发生在线程池中,所以我正在寻找terminateThread()
的替代方案(例如按照我在问题中描述的那样离开线程)或尝试修复内部状态,或者至少验证是否terminateThread()
是我陷入僵局的根源。
我也想在其他项目中重用这个线程池,所以我应该确保它安全。
更新:问题已解决。
我在我的应用程序中发现了错误:
当我的线程池中的超时时间已经很低(大约 200 毫秒)时,它实际上是对terminateThread()
的调用。
线程在它没有阻塞的时候被杀死(即,如果有更长的超时时间,它会工作并正确返回)。
从内核堆栈跟踪中,我发现在内核模式下,线程终止时互斥锁被锁定,而线程退出时,其他线程已经在等待该互斥锁。
通过将最小超时时间增加到 1000 毫秒,问题似乎首先消失了,但我对此并不满意:
我的解决方案是在达到超时时在堆上创建 lambda,将 lambda 和线程留给自己而不终止,并将其添加到_ToTerminateThreads
的列表中。
列表每 10 分钟终止一次(等待 10 分钟,复制列表,再等待一分钟,然后终止线程并删除 lambda)。
不过,经过测试和数小时的调试,我还是遇到了堆损坏。 最后我发现了以下内容: 我留待删除的线程 写入用户函数已使用的内存(已传递给线程池) - 因为线程池已经返回,他们被释放了。 这导致了最终的问题,因此最终的解决方案是将超时增加到安全的数量。
我建议所有需要此类功能的人将其部署到子进程,并终止该进程而不是使用线程。
我没有回答这个问题,因为主要的 4 个问题尚未得到解答。对于我的问题,我不再需要他们的答案,但他们可能对 *** 的其他成员很感兴趣。
【问题讨论】:
更多阅读 - devblogs.microsoft.com/oldnewthing/?p=91811 当你终止一个线程时,你不能再做任何有意义的事情了。进程已进入无法恢复的状态。事后试图检查情况不会成为解决方案。也许您应该询问您正在尝试解决的真正问题。很明显,您的代码存在与线程池无关的问题。 您在寻找halting problem 的解决方案吗?至于验证您的代码是导致死锁的原因,这很简单。您的流程处于无法向前推进的状态。此时,启动任务管理器,并让它收集一个小型转储。在调试器(如 WinDbg)中加载它,并检查等待周期。 WinDbg 为此提供了方便的!locks
扩展名。
还要确保考虑到这一点:Must be This Tall to Write Multi-Threaded Code(链接到article)。
@emaditaj - 阅读带有少量大写字母和很长句子的问答并不容易。我选择提出修改建议。请检查重新编写的版本是否仍能正确描述您的问题并正确报告(我相信确实如此,但您永远不知道)。我希望这有助于其他读者使用此问答。
【参考方案1】:
我的问题已解决,尽管它与帖子中的 3 个问题无关。 我尝试以相反的顺序回答它们:
ad 3.b.) 如果外部函数返回并且您的本地 lambda 已被删除,cpu 将不知道这一点,并将尝试将该偏移处的字节处理为CPU 指令。这肯定会搞砸你,所以永远不要那样做!
ad 3.a.) 是的,如果你 100% 确定外部函数永远不会返回,那么离开是安全的(否则返回时会弄乱你的应用程序
-
如果您按照 b. 中说明的相同方式删除了其余代码
如果您没有删除 lambda 或者它是一个全局函数,它将运行其余函数,这些函数可能正在编辑已被释放并导致堆损坏的动态分配内存(堆,而不是堆栈),或者只是编辑一些全局变量)。
ad 2.) 我搜索了危险的 winapi 函数,除了TerminateThread()
之外没有找到任何结果。
如果你知道一个,请添加评论或写另一个答案。
ad 1.) 我没有任何解决方案来检查/修复 Microsoft 所指进程的内部 kernel32 状态。 我认为读过 kernel32.dll 源代码的微软人应该回答这个问题。
除了这个 kernel32 状态之外,TerminateThread()
会导致许多其他问题(如资源/堆分配、互斥锁、泄漏等),因此除非您 100% 确定自己在做什么,否则切勿使用它。
阅读文章@RichardCritten 链接在 cmets:TerminateThread()
我的代码中有什么错误?
我正在呼叫TerminateThread()
,超时时间很短(300 毫秒)。
随机当机器资源不足时,该功能仍在运行(我的意思是非阻塞调用!)。
我终止了该函数,从而导致内核互斥锁被锁定。
这个锁定的互斥体使所有其他线程等待 - 当它们返回时不会退出。
备注
在没有收到任何答案后,我根据发现的内容回答了我自己的问题。因此,它可能包含一些错误信息。如有错误请指正。
【讨论】:
你可以accept your own answer。 @RitaHan-MSFT 做到了,请您为我的问题投票,我无法创建任何新问题,因为包括此在内的一些旧问题的投票为零或反对票 您可以参考this 和this 来了解“提供一两个更优质的答案并获得支持”或“编辑以改进您的问题”。 "How do I ask a good question?"希望这会有所帮助。以上是关于win7怎样使用kernel streaming的主要内容,如果未能解决你的问题,请参考以下文章
无法在 DLL 'kernel32.dll' 中找到名为 'InterlockedIncrement' 的入口点 - VS2005@Win7 64 位
win7下delphi7编译时提示: Debugger Kernel BORDBK70.DLL is missing or not registered.