1.8 驱动程序中对应的函数接口
在NPF中,提供了NPF_Write、NPF_BufferedWrite与NPF_IoControl函数,实现把数据包传递给NDIS层,最终调用NdisSend函数把数据包发送出去。
1.8.1 发送单个数据包的接口实现
1.8.1.1 NPF_Write函数
库packet.dll的
PacketSendPacket
函数执行
WriteFile
系统
调用时,该函数被调用(响应
IRP_MJ_WRITE
)。
DriverObject->MajorFunction[IRP_MJ_WRITE] = NPF_Write;
由
NPF_Write
函数执行数据包的发送,根据
Open->Nwrites
的值执行发送次数,该数值在
NPF_Open()
函数中设置为默认值为
1
,通过
NPF_IoControl
函数可修改该值。
函数
NPF_Write
的
原型如下:
NTSTATUS NPF_Write(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
参数DeviceObject指向用户所使用的设备驱动对象,参数Irp指向包含用户请求的IRP。
函数返回操作的状态。
具体代码实现如下:
NTSTATUS NPF_Write(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
POPEN_INSTANCE Open;
PIO_STACK_LOCATION IrpSp;
PNDIS_PACKET pPacket;
NDIS_STATUS Status;
ULONG NumSends;
ULONG numSentPackets;
/*
获得调用者在给定
IRP
中的堆栈位置
*/
IrpSp = IoGetCurrentIrpStackLocation(Irp);
/*
获得
POPEN_INSTANCE
的实例
open*/
Open=IrpSp->FileObject->FsContext;
/*
获得重复发送的次数
*/
NumSends = Open->Nwrites;
/*
验证重复发送次数的有效性,必须大于
0
,否则函数返回
*/
if (NumSends == 0)
{
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
/*
验证输入参数的有效性
:*/
//1.
数据包的大小应该大于
0
//2.
小于等于数据链路层最大帧的大小,并且
//3.
最大帧大小不应该为
0
。
//
检查用户提供的缓冲区不为空
if(IrpSp ->Parameters.Write.Length == 0 ||
//
检查
MaxFrameSize
被正确初始化
Open->MaxFrameSize == 0 ||
Irp->MdlAddress == NULL ||
//
检查帧大小小于等于
MTU
IrpSp->Parameters.Write.Length > Open->MaxFrameSize)
{
//
输入参数的有效性检查失败,否则函数返回
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_UNSUCCESSFUL;
}
/*
如果可能,增加绑定句柄的引用计数
*/
if(NPF_StartUsingBinding(Open) == FALSE)
{
//
适配器没有被绑定,不能发送数据包,函数返回
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_INVALID_DEVICE_REQUEST;
}
/*
获取保护
WriteInProgress
变量的自旋锁
*/
NdisAcquireSpinLock(&Open->WriteLock);
if(Open->WriteInProgress)
{
//
另一个写操作当前正在处理中,
//
释放保护
WriteInProgress
变量的自旋锁,函数返回
NdisReleaseSpinLock(&Open->WriteLock);
NPF_StopUsingBinding(Open);
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_UNSUCCESSFUL;
}
else
{
//
无正在处理中的写操作,可开始写操作,并设置为正在执行写操作的状态
Open->WriteInProgress = TRUE;
//
复位
NdisWriteCompleteEvent
事件
NdisResetEvent(&Open->NdisWriteCompleteEvent);
}
/*
释放保护
WriteInProgress
变量的自旋锁
*/
NdisReleaseSpinLock(&Open->WriteLock);
/*
复位挂起在等待
SendComplete
的数据包个数为
0*/
Open->TransmitPendingPackets = 0;
/*
复位同步多个写进程的事件
WriteEvent*/
NdisResetEvent(&Open->WriteEvent);
numSentPackets = 0;
//
已发数据包次数初始化为
0
/*
进入重复发送的
while
循环中
*/
while( numSentPackets < NumSends )
{
//
分配并初始化一个数据包描述符,
//
由
pPacket
返回所分配的数据包描述符
NdisAllocatePacket(
&Status,
&pPacket,
Open->PacketPool
);
if (Status == NDIS_STATUS_SUCCESS)
{
//
缓冲池中有空闲数据包可用,准备用
NdisSend
发送该数据包
//
如果有要求,为该数据包设置
SkipSentPackets
标志
//
目前,我们只在禁止接受回环数据包时设置该标志,
//
比如,拒收由我们自己发送的数据包
if(Open->SkipSentPackets)
{
NdisSetPacketFlags(
pPacket,
g_SendPacketFlags);
}
//
数据包没有一个缓冲区,不需要每次单个写操作后执行内存释放
RESERVED(pPacket)->FreeBufAfterWrite = FALSE;
//
把写缓冲区附加给该数据包
NdisChainBufferAtFront(pPacket,Irp->MdlAddress);
//
递增挂起待发数据包的数目
InterlockedIncrement(
&Open->TransmitPendingPackets);
//
复位
NdisWriteCompleteEvent
事件
NdisResetEvent(&Open->NdisWriteCompleteEvent);
//
向低层的
MAC
层请求数据包发送
NdisSend(&Status,Open->AdapterHandle,pPacket);
if (Status != NDIS_STATUS_PENDING)
{
//
数据包的发送没有被挂起,立即调用完成句柄函数
NPF_SendComplete(Open,pPacket,Status);
}
numSentPackets ++;
//
已发数据包增加
1
个
}
else
{
//
传输池中没有空闲数据包可用,需要等待一段时间。
//
当至少一半
TX
数据包缓冲池是可用的时候,
//Open->WriteEvent
事件获得通知,
//NPF_SendComplete
完成句柄函数可发送该通知。
NdisWaitEvent(&Open->WriteEvent,1);
}
}
//
当程序运行到此位置时,所有的数据包已在
NdisSend
中排队等待发送了,
//
我们仅仅需要通过
SendComplete
//
完成句柄函数等待所有的数据包发送结束
//
(如果任何一个
NdisSend
请求返回
STATUS_PENDING
)。
NdisWaitEvent(&Open->NdisWriteCompleteEvent, 0);
//
所有的数据包被发送,释放适配器的绑定
NPF_StopUsingBinding(Open);
//
没有写操作正在处理
NdisAcquireSpinLock(&Open->WriteLock);
Open->WriteInProgress = FALSE;
NdisReleaseSpinLock(&Open->WriteLock);
//
完成该
Irp
并返回成功
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information =
IrpSp->Parameters.Write.Length;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
NPF_Write
函数的主要正常流程如下为:
首先获取各个必要参数,检查输入参数的有效性;
开始使用适配器的绑定;
检测是否可执行写操作;
执行一次或多次数据包发送;
等待发送结束;
停止使用绑定;
设置为可执行写操作状态( Open->WriteInProgress = FALSE;)
函数结束,返回Irp的信息;