1. 程式人生 > >windows 核心情景分析 --- 網路通訊

windows 核心情景分析 --- 網路通訊

典型的基於tcpip協議套接字方式的網路通訊模組層次:

應用程式

socket api

WS2_32.dll

socket irp

Afd.sys

tdi irp

Tcpip.sys

回撥函式介面

各Ndis中間層過濾驅動

回撥函式介面

小埠驅動

中斷互動操作

網絡卡

應用程式呼叫WS2_32.dll中的socket api,socket api在內部生成socket irp發給afd.sys這個中間輔助驅動層,afd.sys將socket irp轉換成tdi irp發給tcpip協議驅動,協議驅動通過註冊的回撥函式與小埠驅動(中間可能穿插N箇中間層過濾驅動),小埠驅動最終通過中斷與網絡卡互動,操作硬體。

其中,協議驅動、中間層驅動、小埠驅動三者之間的互動是通過ndis.sys這個庫函式模組實現的,或者說ndis.sys提供了ndis框架,協議驅動、中間層驅動、小埠驅動三者都得遵循這個框架。

為什麼網路通訊需要這麼複雜的分層?答案是為了減輕開發維護管理工作的需要,分層能夠提供最大的靈活性。各層的設計人員只需專注自身模組的設計工作,無需擔心其他模組是怎麼實現的,只需保持介面一致即可。

如應用程式可以呼叫socket api就可以實現網路通訊,而不管底層是如何實現的。使用socket api還可以使得windows上能相容執行Unix系統上的網路通訊程式,ws2_32.dll這個模組中實現了socket介面。

Afd.sys實際上是一個適配層,他可以適配N種協議驅動。

Tcpip.sys是一種協議驅動(其實是一個協議棧驅動),它內部實現了一套協議棧,決定了如何解析從網絡卡接收到的包,以及以什麼格式將應用程式資料發到網絡卡。只不過tcpip.sys將收到的包按鏈路層、網路層、傳輸層分層三層逐層解析。事實上我們可以完全可以自定義、自編寫一個協議驅動,按照我們自己的協議來發包、收包(我們的這個自定義協議驅動可以採用分層機制,也可以採用簡單的單層機制),這樣在傳送方電腦和接收方電腦都安裝我們的自定義協議驅動後,傳送方就可以按照自定義協議發包,接收方就按照約定的格式解包。

如果不考慮中間驅動,協議驅動是直接與小埠驅動互動的。協議驅動從小埠驅動收包,協議驅動發包給小埠驅動,這就是二者之間的互動。他們之間的互動通過ndis框架預約的一套回撥函式介面來實現。

下面我們看各層驅動的實現:

一個協議驅動需要在DriverEntry中將自己註冊為一個協議驅動,向ndis框架登記、宣告自己的協議特徵。

一個協議特徵記錄了協議的名稱以及它提供的各個回撥函式

4.0版本的ndis協議特徵結構如下定義:

typedef struct _NDIS40_PROTOCOL_CHARACTERISTICS

{

  UCHAR MajorNdisVersion;

  UCHAR MinorNdisVersion;

  __MINGW_EXTENSION union {

    UINT Reserved;

    UINT Flags;

  };

  OPEN_ADAPTER_COMPLETE_HANDLER OpenAdapterCompleteHandler;//繫結完成回撥函式

  CLOSE_ADAPTER_COMPLETE_HANDLER CloseAdapterCompleteHandler;//解除繫結完成回撥函式

  SEND_COMPLETE_HANDLER SendCompleteHandler;//傳送完成回撥函式

  TRANSFER_DATA_COMPLETE_HANDLER TransferDataCompleteHandler;//轉移資料完成回撥函式

  RESET_COMPLETE_HANDLER ResetCompleteHandler;

  REQUEST_COMPLETE_HANDLER RequestCompleteHandler;//ndis請求完成回撥函式

  RECEIVE_HANDLER ReceiveHandler;//接收函式

  RECEIVE_COMPLETE_HANDLER ReceiveCompleteHandler;//接收完成回撥函式

  STATUS_HANDLER StatusHandler;//狀態變換通知回撥函式

  STATUS_COMPLETE_HANDLER StatusCompleteHandler;//狀態變換完成通知回撥函式

  NDIS_STRING Name;//協議名

  RECEIVE_PACKET_HANDLER ReceivePacketHandler;//接收包函式

  BIND_HANDLER BindAdapterHandler;//繫結通知回撥函式

  UNBIND_HANDLER UnbindAdapterHandler;//解除繫結通知回撥函式

  PNP_EVENT_HANDLER PnPEventHandler;//Pnp事件回撥函式

  UNLOAD_PROTOCOL_HANDLER UnloadHandler;//協議驅動的解除安裝例程

} NDIS40_PROTOCOL_CHARACTERISTICS;

下面的函式用於將一個驅動註冊為ndis協議驅動

VOID

NdisRegisterProtocol(

    OUT PNDIS_STATUS                    Status,//返回狀態

    OUT PNDIS_HANDLE                    NdisProtocolHandle,//返回註冊的協議驅動控制代碼

    IN  PNDIS_PROTOCOL_CHARACTERISTICS  ProtocolCharacteristics,

    IN  UINT                            CharacteristicsLength)

{

  PPROTOCOL_BINDING Protocol;

  NTSTATUS NtStatus;

  UINT MinSize;

  PNET_PNP_EVENT PnPEvent;

  *NdisProtocolHandle = NULL;

  switch (ProtocolCharacteristics->MajorNdisVersion)

    {

    case 0x03:

      MinSize = sizeof(NDIS30_PROTOCOL_CHARACTERISTICS);

      break;

    case 0x04:

      MinSize = sizeof(NDIS40_PROTOCOL_CHARACTERISTICS);

      break;

    case 0x05:

      MinSize = sizeof(NDIS50_PROTOCOL_CHARACTERISTICS);

      break;

    default:

      *Status = NDIS_STATUS_BAD_VERSION;

      return;

    }

  if (CharacteristicsLength < MinSize) //結構體的長度必須與宣告的ndis版本一致

    {

      *Status = NDIS_STATUS_BAD_CHARACTERISTICS;

      return;

}

  //協議驅動控制代碼實際上是一個PROTOCOL_BINDING結構體指標

  Protocol = ExAllocatePool(NonPagedPool, sizeof(PROTOCOL_BINDING));//一個協議驅動描述符

  RtlZeroMemory(Protocol, sizeof(PROTOCOL_BINDING));

  RtlCopyMemory(&Protocol->Chars, ProtocolCharacteristics, MinSize);//關鍵。記錄協議特徵

  KeInitializeSpinLock(&Protocol->Lock);

  InitializeListHead(&Protocol->AdapterListHead);//該協議驅動繫結的網絡卡列表初始為空

  *NdisProtocolHandle = Protocol;//返回協議驅動的控制代碼

  ndisBindMiniportsToProtocol(Status, Protocol);//關鍵。剛一註冊就在此繫結所有現有網絡卡

  PnPEvent = ProSetupPnPEvent(NetEventBindsComplete, NULL, 0);//構造一個所有繫結完成事件

  if (PnPEvent)

  {

      if (Protocol->Chars.PnPEventHandler)

          NtStatus = (*Protocol->Chars.PnPEventHandler)(NULL,PnPEvent);

  }

  if (*Status == NDIS_STATUS_SUCCESS)

 {

      ExInterlockedInsertTailList(&ProtocolListHead, &Protocol->ListEntry, &ProtocolListLock);//插入全域性的協議驅動連結串列

  } 

}

上面最主要的工作便是登記協議特徵到驅動描述符中,然後附帶繫結現有的已有網絡卡。下面的函式就是用來繫結所有現有網絡卡的。

VOID  ndisBindMiniportsToProtocol(OUT PNDIS_STATUS Status, IN PPROTOCOL_BINDING Protocol)

{

    HANDLE DriverKeyHandle = NULL;

    PKEY_VALUE_PARTIAL_INFORMATION KeyInformation = NULL;

    PNDIS_PROTOCOL_CHARACTERISTICS ProtocolCharacteristics = &Protocol->Chars;

    RegistryPathStr = ExAllocatePoolWithTag(PagedPool, sizeof(SERVICES_KEY) + ProtocolCharacteristics->Name.Length + sizeof(LINKAGE_KEY), NDIS_TAG + __LINE__);

    wcscpy(RegistryPathStr, SERVICES_KEY);

    wcsncat(RegistryPathStr, ((WCHAR *)ProtocolCharacteristics->Name.Buffer), ProtocolCharacteristics->Name.Length / sizeof(WCHAR));

    RegistryPathStr[wcslen(SERVICES_KEY)+ProtocolCharacteristics->Name.Length/sizeof(WCHAR)] = NULL;

    wcscat(RegistryPathStr, LINKAGE_KEY);

    //經過上面的拼湊,RegistryPathStr最終拼成…\Services\協議名\Linkage

    RtlInitUnicodeString(&RegistryPath, RegistryPathStr);

    InitializeObjectAttributes(&ObjectAttributes, &RegistryPath, OBJ_CASE_INSENSITIVE, NULL, NULL);

    NtStatus = ZwOpenKey(&DriverKeyHandle, KEY_READ, &ObjectAttributes);//開啟Linkage鍵

    ExFreePool(RegistryPathStr);

    if(!NT_SUCCESS(NtStatus))

      {

        *Status = NDIS_STATUS_FAILURE;

        return;

      }

  {

    UNICODE_STRING ValueName;

    ULONG ResultLength;

    RtlInitUnicodeString(&ValueName, L"Bind");

    NtStatus = ZwQueryValueKey(DriverKeyHandle, &ValueName, KeyValuePartialInformation, NULL, 0, &ResultLength);

KeyInformation = ExAllocatePoolWithTag(PagedPool, sizeof(KEY_VALUE_PARTIAL_INFORMATION) + ResultLength, NDIS_TAG + __LINE__);

//查詢Linkage鍵下的Bind值(多個網絡卡裝置物件名稱組成的一條字串)

    NtStatus = ZwQueryValueKey(DriverKeyHandle, &ValueName, KeyValuePartialInformation, KeyInformation,sizeof(KEY_VALUE_PARTIAL_INFORMATION) + ResultLength, &ResultLength);

    ZwClose(DriverKeyHandle);

  }

  *Status = NDIS_STATUS_SUCCESS;

  //遍歷每個網絡卡

  for (DataPtr = (WCHAR *)KeyInformation->Data;

          *DataPtr != 0;   DataPtr += wcslen(DataPtr) + 1)

    {

      VOID *BindContext = NULL;

      NDIS_STRING DeviceName;

      NDIS_STRING RegistryPath;

      WCHAR *RegistryPathStr = NULL;

      ULONG PathLength = 0;

      // DeviceName為‘\Device\小埠裝置物件名稱’形式

      RtlInitUnicodeString(&DeviceName, DataPtr);

      if (!MiniLocateDevice(&DeviceName))//if 那個網絡卡尚未啟動

          continue;

      if (LocateAdapterBindingByName(Protocol, &DeviceName)) //if 本協議已綁定了那塊網絡卡

          continue;

      PathLength = sizeof(SERVICES_KEY) +                               

          wcslen( DataPtr + 8 ) * sizeof(WCHAR) +

          sizeof(PARAMETERS_KEY) +                                     

          ProtocolCharacteristics->Name.Length + sizeof(WCHAR);

      RegistryPathStr = ExAllocatePool(PagedPool, PathLength);

      wcscpy(RegistryPathStr, SERVICES_KEY);

      wcscat(RegistryPathStr, DataPtr + 8 );

      wcscat(RegistryPathStr, PARAMETERS_KEY);

      wcsncat(RegistryPathStr, ProtocolCharacteristics->Name.Buffer, ProtocolCharacteristics->Name.Length / sizeof(WCHAR) );

      RegistryPathStr[PathLength/sizeof(WCHAR) - 1] = 0;

      RtlInitUnicodeString(&RegistryPath, RegistryPathStr);

      //RegistryPath最終變成\Services\小埠裝置物件名 \Parameters\協議名 形式

        {

          BIND_HANDLER BindHandler = ProtocolCharacteristics->BindAdapterHandler;

          if(BindHandler) //關鍵,通知協議驅動繫結網絡卡列表中的每塊網絡卡

             BindHandler(Status, BindContext, &DeviceName, &RegistryPath, 0);

    }

   ExFreePool(KeyInformation);

}

一個驅動註冊為協議驅動後,ndis內部會為這個驅動建立一個協議驅動描述符,返回的控制代碼就是這個結構指標。Typedef PVOID NDIS_HANDLE,可見ndis控制代碼其實就是一個指標。

typedef struct _PROTOCOL_BINDING {  //協議驅動描述符

    LIST_ENTRY                    ListEntry;        用來掛入全域性協議驅動連結串列

    KSPIN_LOCK                    Lock;             

    NDIS_PROTOCOL_CHARACTERISTICS Chars;            //關鍵。本協議驅動的特徵

    WORK_QUEUE_ITEM               WorkItem;         

    LIST_ENTRY                    AdapterListHead;  //本協議驅動繫結的所有網絡卡

} PROTOCOL_BINDING, *PPROTOCOL_BINDING;

同樣:小埠驅動也需要在其DriverEntry中將自己註冊為一個ndis小埠驅動。

Struct NDIS40_MINIPORT_CHARACTERISTICS  //4.0版的小埠驅動特徵結構

{

  UCHAR  MajorNdisVersion; 

  UCHAR  MinorNdisVersion; 

  UINT  Reserved; 

  W_CHECK_FOR_HANG_HANDLER  CheckForHangHandler; 

  W_DISABLE_INTERRUPT_HANDLER  DisableInterruptHandler;//禁用來自特定網絡卡的中斷

  W_ENABLE_INTERRUPT_HANDLER  EnableInterruptHandler; //啟用來自特定網絡卡的中斷

  W_HALT_HANDLER  HaltHandler; 

  W_HANDLE_INTERRUPT_HANDLER  HandleInterruptHandler;//isr的後半部 

  W_INITIALIZE_HANDLER  InitializeHandler; //IRP_MN_START_DEVICE中呼叫的啟動初始化函式

  W_ISR_HANDLER  ISRHandler; //我們的isr 

  W_QUERY_INFORMATION_HANDLER  QueryInformationHandler;//處理查詢請求的函式 

  W_RECONFIGURE_HANDLER  ReconfigureHandler; 

  W_RESET_HANDLER  ResetHandler; 

  W_SEND_HANDLER  SendHandler; //傳送函式

  W_SET_INFORMATION_HANDLER  SetInformationHandler;//處理設定請求的函式

  W_TRANSFER_DATA_HANDLER  TransferDataHandler;//處理協議驅動發下來的轉移資料請求的函式

  W_RETURN_PACKET_HANDLER  ReturnPacketHandler; //歸還包函式

  W_SEND_PACKETS_HANDLER  SendPacketsHandler;//傳送包函式

  W_ALLOCATE_COMPLETE_HANDLER  AllocateCompleteHandler;

}

typedef struct _NDIS_M_DRIVER_BLOCK     //小埠驅動描述符、控制代碼

    LIST_ENTRY                      ListEntry;                //用來掛入全域性小埠驅動連結串列

    KSPIN_LOCK                      Lock;                     

    NDIS_MINIPORT_CHARACTERISTICS   MiniportCharacteristics;  //特徵

    WORK_QUEUE_ITEM                 WorkItem;                 

    PDRIVER_OBJECT                  DriverObject;             //小埠驅動物件

    LIST_ENTRY                      DeviceList;               //本驅動中建立的所有介面卡裝置

    PUNICODE_STRING                 RegistryPath;             //本驅動的服務鍵路徑

} NDIS_M_DRIVER_BLOCK, *PNDIS_M_DRIVER_BLOCK;

下面的函式用於將一個驅動註冊為ndis小埠驅動

NDIS_STATUS

NdisMRegisterMiniport(

    IN  NDIS_HANDLE                     NdisWrapperHandle,//小埠驅動控制代碼

    IN  PNDIS_MINIPORT_CHARACTERISTICS  MiniportCharacteristics,

    IN  UINT                            CharacteristicsLength)

{

  UINT MinSize;

  PNDIS_M_DRIVER_BLOCK Miniport = (PNDIS_M_DRIVER_BLOCK)NdisWrapperHandle;

  PNDIS_M_DRIVER_BLOCK *MiniportPtr;

  NTSTATUS Status;

  switch (MiniportCharacteristics->MajorNdisVersion)

    {

      case 0x03:

        MinSize = sizeof(NDIS30_MINIPORT_CHARACTERISTICS);

        break;

      case 0x04:

        MinSize = sizeof(NDIS40_MINIPORT_CHARACTERISTICS);

        break;

      case 0x05:

        MinSize = sizeof(NDIS50_MINIPORT_CHARACTERISTICS);

        break;

      default:

        return NDIS_STATUS_BAD_VERSION;

    }

  if (CharacteristicsLength < MinSize)

        return NDIS_STATUS_BAD_CHARACTERISTICS;

  //這三個回撥函式在任何ndis版本都必須提供

  if ((!MiniportCharacteristics->HaltHandler) ||

       (!MiniportCharacteristics->InitializeHandler)||

       (!MiniportCharacteristics->ResetHandler))

    {

      return NDIS_STATUS_BAD_CHARACTERISTICS;

    }

  if (MiniportCharacteristics->MajorNdisVersion < 0x05)

  {

      if ((!MiniportCharacteristics->QueryInformationHandler) ||

          (!MiniportCharacteristics->SetInformationHandler))

      {

           return NDIS_STATUS_BAD_CHARACTERISTICS;

      }

  }

  else

  {

      if (((!MiniportCharacteristics->QueryInformationHandler) ||

           (!MiniportCharacteristics->SetInformationHandler)) &&

           (!MiniportCharacteristics->CoRequestHandler))

      {

           return NDIS_STATUS_BAD_CHARACTERISTICS;

      }

  }

  if (MiniportCharacteristics->MajorNdisVersion == 0x03)

    {

      if (!MiniportCharacteristics->SendHandler)

          return NDIS_STATUS_BAD_CHARACTERISTICS;

    }

  else if (MiniportCharacteristics->MajorNdisVersion == 0x04)

    {

      if ((!MiniportCharacteristics->SendHandler) &&

          (!MiniportCharacteristics->SendPacketsHandler))

        {

          return NDIS_STATUS_BAD_CHARACTERISTICS;

        }

    }

  else if (MiniportCharacteristics->MajorNdisVersion == 0x05)

    {

      if ((!MiniportCharacteristics->SendHandler) &&

          (!MiniportCharacteristics->SendPacketsHandler) &&

          (!MiniportCharacteristics->CoSendPacketsHandler))

        {

          return NDIS_STATUS_BAD_CHARACTERISTICS;

        }

    }

  //關鍵。記錄該小埠驅動的特徵到驅動描述符中

  RtlCopyMemory(&Miniport->MiniportCharacteristics, MiniportCharacteristics, MinSize);

  Status = IoAllocateDriverObjectExtension(Miniport->DriverObject, 'NMID',

                                           sizeof(PNDIS_M_DRIVER_BLOCK), &MiniportPtr);

  *MiniportPtr = Miniport;//驅動擴充套件指向小埠驅動描述符

  //這些irp派遣函式都被ndis託管了。如果我們在註冊小埠前設定了這些派遣函式,將會被覆蓋。

如果在註冊小埠後再設定,可以hook ndis內部設定的那些派遣函式。(I表示Internal內部未匯出函式)

  Miniport->DriverObject->MajorFunction[IRP_MJ_CREATE] = NdisICreateClose;

  Miniport->DriverObject->MajorFunction[IRP_MJ_CLOSE] = NdisICreateClose;

  Miniport->DriverObject->MajorFunction[IRP_MJ_PNP] = NdisIDispatchPnp;

  Miniport->DriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = NdisIShutdown;

  Miniport->DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = NdisIDeviceIoControl;

//關鍵。Ndis內部託管了AddDevice,它會在內部建立小埠裝置物件,繫結在硬體pdo上

  Miniport->DriverObject->DriverExtension->AddDevice = NdisIAddDevice; 

  return NDIS_STATUS_SUCCESS;

}

協議驅動通過NdisRegisterProtocol,小埠驅動通過NdisMRegisterMiniport向ndis框架註冊了自己的回撥函式後,協議驅動就可以與小埠驅動在ndis框架下通過這兩組回撥函式進行互動通訊了。Ndis.sys起著橋樑中介的作用,除此之外,ndis.sys模組還提供了大量的ndis執行庫函式。因此又可以說ndis.sys是一個函式庫。

在NdisMRegisterMiniport之前,需要一個ndis小埠驅動控制代碼(實際上是一個驅動描述符結構),下面的巨集就是用來建立一個小埠驅動控制代碼的。

#define NdisMInitializeWrapper  NdisInitializeWrapper

VOID

NdisInitializeWrapper(

    OUT PNDIS_HANDLE    NdisWrapperHandle, //返回建立的小埠驅動控制代碼

    IN  PVOID           SystemSpecific1,//必須傳DriverObject

    IN  PVOID           SystemSpecific2,//必須傳RegisterPath

    IN  PVOID           SystemSpecific3)//無用

{

  PNDIS_M_DRIVER_BLOCK Miniport;

  PUNICODE_STRING RegistryPath;

  WCHAR *RegistryBuffer;

  *NdisWrapperHandle = NULL;

  //建立一個小埠驅動描述符,也即控制代碼

  Miniport = ExAllocatePool(NonPagedPool, sizeof(NDIS_M_DRIVER_BLOCK));

  RtlZeroMemory(Miniport, sizeof(NDIS_M_DRIVER_BLOCK));

  KeInitializeSpinLock(&Miniport->Lock);

  Miniport->DriverObject = (PDRIVER_OBJECT)SystemSpecific1;

  RegistryPath = ExAllocatePool(PagedPool, sizeof(UNICODE_STRING));

  RegistryPath->Length = ((PUNICODE_STRING)SystemSpecific2)->Length;

  RegistryPath->MaximumLength = RegistryPath->Length + sizeof(WCHAR)

  RegistryBuffer = ExAllocatePool(PagedPool, RegistryPath->MaximumLength);

  RtlCopyMemory(RegistryBuffer, ((PUNICODE_STRING)SystemSpecific2)->Buffer, RegistryPath->Length);

  RegistryBuffer[RegistryPath->Length/sizeof(WCHAR)] = 0;

  RegistryPath->Buffer = RegistryBuffer;

  Miniport->RegistryPath = RegistryPath;//記錄這個小埠驅動的服務鍵路徑

  InitializeListHead(&Miniport->DeviceList);//初始為空

  //將本小埠驅動掛入全域性連結串列(貌似在NdisMRegisterMiniport中做這項工作更合理)

  ExInterlockedInsertTailList(&MiniportListHead, &Miniport->ListEntry, &MiniportListLock);

  *NdisWrapperHandle = Miniport;//返回建立的小埠驅動控制代碼給使用者

}

這樣,在小埠驅動的描述符中有一個指標指向其驅動物件,而在驅動物件的標準擴充套件部中也有一個指標指向了小埠驅動的描述符。二者互相指向。

前面說過:ndis內部設定的AddDevice即NdisIAddDevice函式會在內部自動建立一個小埠裝置物件,然後加入堆疊。我們看:

NTSTATUS

NdisIAddDevice(  //中間的I表示Internal,即ndis.sys內部使用,未匯出的函式

    IN PDRIVER_OBJECT DriverObject,//ndis小埠驅動物件

    IN PDEVICE_OBJECT PhysicalDeviceObject)//代表網絡卡的硬體pdo

{

  static const WCHAR ClassKeyName[] = {'C','l','a','s','s','\\'};

  static const WCHAR LinkageKeyName[] = {'\\','L','i','n','k','a','g','e',0};

  MiniportPtr = IoGetDriverObjectExtension(DriverObject, (PVOID)'NMID');

  Miniport = *MiniportPtr;//獲得小埠驅動描述符

  //獲取該硬體pdo的驅動鍵屬性

  Status = IoGetDeviceProperty(PhysicalDeviceObject, DevicePropertyDriverKeyName,

                               0, NULL, &DriverKeyLength);

  LinkageKeyBuffer = ExAllocatePool(PagedPool, DriverKeyLength +

                                    sizeof(ClassKeyName) + sizeof(LinkageKeyName));

  Status = IoGetDeviceProperty(PhysicalDeviceObject, DevicePropertyDriverKeyName,

                               DriverKeyLength, LinkageKeyBuffer +

                               (sizeof(ClassKeyName) / sizeof(WCHAR)),&DriverKeyLength);

  RtlCopyMemory(LinkageKeyBuffer, ClassKeyName, sizeof(ClassKeyName));

  RtlCopyMemory(LinkageKeyBuffer + ((sizeof(ClassKeyName) + DriverKeyLength) /

                sizeof(WCHAR)) - 1, LinkageKeyName, sizeof(LinkageKeyName));

  // LinkageKeyBuffer最終為:‘Class\DriverKeyName\Linkage’

  RtlZeroMemory(QueryTable, sizeof(QueryTable));

  RtlInitUnicodeString(&ExportName, NULL);

  QueryTable[0].Flags = RTL_QUERY_REGISTRY_REQUIRED | RTL_QUERY_REGISTRY_DIRECT;

  QueryTable[0].Name = L"Export";

  QueryTable[0].EntryContext = &ExportName;

  //查詢該硬體pdo的ExportName,作為其埠裝置物件名稱

  Status = RtlQueryRegistryValues(RTL_REGISTRY_CONTROL, LinkageKeyBuffer,

                                  QueryTable, NULL, NULL);

  //關鍵。Ndis內部自動為其建立了小埠裝置

  Status = IoCreateDevice(Miniport->DriverObject, sizeof(LOGICAL_ADAPTER),

            &ExportName, FILE_DEVICE_PHYSICAL_NETCARD,0, FALSE, &DeviceObject);

  //關鍵。Ndis為我們建立的小埠裝置物件使用標準的LOGICAL_ADAPTER結構裝置擴充套件

  Adapter = (PLOGICAL_ADAPTER)DeviceObject->DeviceExtension;

  KeInitializeSpinLock(&Adapter->NdisMiniportBlock.Lock);

  InitializeListHead(&Adapter->ProtocolListHead);//初始為空

  Status = IoRegisterDeviceInterface(PhysicalDeviceObject,&GUID_DEVINTERFACE_NET,

                                     NULL,&Adapter->NdisMiniportBlock.SymbolicLinkName);

  Adapter->NdisMiniportBlock.DriverHandle = Miniport;

  Adapter->NdisMiniportBlock.MiniportName = ExportName;//小埠裝置物件名

  Adapter->NdisMiniportBlock.DeviceObject = DeviceObject;

  Adapter->NdisMiniportBlock.PhysicalDeviceObject = PhysicalDeviceObject;//該網絡卡的硬體pdo

  //關鍵。Ndis內部自動建立一個相應的小埠裝置,並加入堆疊。(這些操作對使用者透明)

  Adapter->NdisMiniportBlock.NextDeviceObject =

  IoAttachDeviceToDeviceStack(Adapter->NdisMiniportBlock.DeviceObject,PhysicalDeviceObject);

  Adapter->NdisMiniportBlock.OldPnPDeviceState = 0;

  Adapter->NdisMiniportBlock.PnPDeviceState = NdisPnPDeviceAdded;//標記已建立裝置加入堆疊

  KeInitializeTimer(&Adapter->NdisMiniportBlock.WakeUpDpcTimer.Timer);

  KeInitializeDpc(&Adapter->NdisMiniportBlock.WakeUpDpcTimer.Dpc, MiniportHangDpc, Adapter);

  DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

  return STATUS_SUCCESS;

}

總之,Ndis內部託管的AddDevice會為我們自動建立小埠裝置,加入堆疊。但是協議驅動的AddDevice就沒有了,因此協議驅動沒有形式堆疊到裝置棧中。協議驅動與小埠驅動之間斷層了,irp只能最終下發到協議驅動這一層就再也傳不下去了,協議驅動與小埠驅動之間的互動就不能使用傳統的irp方式,而只能藉助ndis框架和回撥函式進行通訊。

Ndis內部的小埠裝置物件的裝置擴充套件結構

typedef struct _LOGICAL_ADAPTER  //標準的小埠裝置擴充套件

{

    //這個欄位內部有一個自定義小埠裝置擴充套件,使用者設定的自定義裝置擴充套件就放在那裡

    NDIS_MINIPORT_BLOCK         NdisMiniportBlock;      

    PNDIS_MINIPORT_WORK_ITEM    WorkQueueHead;          /* Head of work queue */

    PNDIS_MINIPORT_WORK_ITEM    WorkQueueTail;          /* Tail of work queue */

    LIST_ENTRY                  ListEntry;              //用來掛入全域性的小埠裝置連結串列

    LIST_ENTRY                  MiniportListEntry;      //用來掛入本驅動中的小埠裝置連結串列

    LIST_ENTRY                  ProtocolListHead;       //繫結著本小埠裝置的所有協議驅動

    ULONG                       MediumHeaderSize;       //鏈路層頭部長度(即鏈路層型別)

    HARDWARE_ADDRESS            Address;                //實體地址(乙太網卡為MAC)

    ULONG                       AddressLength;          //實體地址長度(乙太網卡為6B)

    PMINIPORT_BUGCHECK_CONTEXT  BugcheckContext;        

} LOGICAL_ADAPTER, *PLOGICAL_ADAPTER;

當小埠驅動載入執行了DriverEntryAddDevice後,系統就會發出一個IRP_MN_START_DEVICEpnp irp來啟動裝置。Pnp  irp 派遣函式也被ndis託管了,固定為:

NdisIDispatchPnp。我們看他是如何處理pnp irp的

NTSTATUS

NdisIDispatchPnp(IN PDEVICE_OBJECT DeviceObject,PIRP Irp)

{

  PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);

  PLOGICAL_ADAPTER Adapter = (PLOGICAL_ADAPTER)DeviceObject->DeviceExtension;

  NTSTATUS Status;

  switch (Stack->MinorFunction)

    {

      case IRP_MN_START_DEVICE:

        Status = NdisIForwardIrpAndWait(Adapter, Irp);//向下層轉發直至完成

        if (NT_SUCCESS(Status) && NT_SUCCESS(Irp->IoStatus.Status))

      Status = NdisIPnPStartDevice(DeviceObject, Irp);//執行通用的裝置啟動操作

        Irp->IoStatus.Status = Status;

        IoCompleteRequest(Irp, IO_NO_INCREMENT);

        break;

       …

    }

  return Status;

}

實際的裝置啟動工作由NdisIPnPStartDevice完成。這是一個通用函式,用來完成一些通用的ndis網絡卡裝置的啟動工作。

NTSTATUS  NdisIPnPStartDevice(IN PDEVICE_OBJECT DeviceObject,PIRP Irp)

{   …

    //加入全域性的介面卡列表

ExInterlockedInsertTailList(&AdapterListHead, &Adapter->ListEntry, &AdapterListLock);

//關鍵。回撥使用者自己提供的啟動初始化函式,執行使用者自定義的初始化工作

    NdisStatus = (*Adapter->NdisMiniportBlock.DriverHandle->MiniportCharacteristics.InitializeHandler)

(

    &OpenErrorStatus, &SelectedMediumIndex, &MediaArray[0],

MEDIA_ARRAY_SIZE, Adapter, (NDIS_HANDLE)&WrapperContext

);

Adapter->NdisMiniportBlock.MediaType = MediaArray[SelectedMediumIndex];//記錄介質型別

  //加入所屬小埠驅動內部的介面卡連結串列

  ExInterlockedInsertTailList(&Adapter->NdisMiniportBlock.DriverHandle->DeviceList, &Adapter->MiniportListEntry, &Adapter->NdisMiniportBlock.DriverHandle->Lock);

  //關鍵。新網絡卡啟動初始化完成後,通知所有協議驅動進行繫結

  CurrentEntry = ProtocolListHead.Flink;

  while (CurrentEntry != &ProtocolListHead)

  {

      ProtocolBinding = CONTAINING_RECORD(CurrentEntry, PROTOCOL_BINDING, ListEntry);

      ndisBindMiniportsToProtocol(&NdisStatus, ProtocolBinding);

      CurrentEntry = CurrentEntry->Flink;

}

  …

}

小埠驅動都必須提供一個自定義的啟動初始化回撥函式,用來在網絡卡啟動時執行某些初始化工作。下面是一個典型的啟動初始化函式,我們看看一般要做哪些初始化工作。(注意下文所說的示例函式都來自於ne2000.sys這個通用的乙太網卡小埠驅動)

//示例函式(這個示例回撥函式做了初始化硬體、註冊中斷向量 等工作)

NDIS_STATUS NTAPI MiniportInitialize(

    OUT PNDIS_STATUS    OpenErrorStatus,

    OUT PUINT           SelectedMediumIndex,//返回該網絡卡的介質型別

    IN  PNDIS_MEDIUM    MediumArray,

    IN  UINT            MediumArraySize,

    IN  NDIS_HANDLE     MiniportAdapterHandle,//實際上就是內建的標準小埠裝置擴充套件

    IN  NDIS_HANDLE     WrapperConfigurationContext)//硬體pdo的一些屬性資訊和配置鍵

{

    UINT *RegNetworkAddress = 0;

    UINT RegNetworkAddressLength = 0;

for (i = 0; i < MediumArraySize; i++)

{

        if (MediumArray[i] == NdisMedium802_3)

            break;

    }

    if (i == MediumArraySize) 

        return NDIS_STATUS_UNSUPPORTED_MEDIA;

    *SelectedMediumIndex = i;//返回介質型別

    //分配一個自定義的小埠裝置物件擴充套件

    Status = NdisAllocateMemory(&Adapter,sizeof(NIC_ADAPTER),0,HighestAcceptableMax);

    NdisZeroMemory(Adapter, sizeof(NIC_ADAPTER));

Adapter->MiniportAdapterHandle  = MiniportAdapterHandle;//記錄標準裝置擴充套件

//下面是預設的資源配置

    Adapter->IoBaseAddress          = DRIVER_DEFAULT_IO_BASE_ADDRESS;//即port基地址

    Adapter->InterruptLevel         = DRIVER_DEFAULT_INTERRUPT_NUMBER;

    Adapter->InterruptVector        = DRIVER_DEFAULT_INTERRUPT_NUMBER;

    Adapter->InterruptShared        = DRIVER_DEFAULT_INTERRUPT_SHARED;

    Adapter->InterruptMode          = DRIVER_DEFAULT_INTERRUPT_MODE;

    Adapter->MaxMulticastListSize   = DRIVER_MAX_MULTICAST_LIST_SIZE;

    Adapter->InterruptMask          = DRIVER_INTERRUPT_MASK;

    Adapter->LookaheadSize          = DRIVER_MAXIMUM_LOOKAHEAD;//負載前視區長度

    //查詢系統為該網絡卡分配的資源(irq、port),記錄到自定義裝置擴充套件中

    MiQueryResources(&Status, Adapter, WrapperConfigurationContext);

    //如果分配失敗或查詢失敗,就從登錄檔中配置該網絡卡需要的資源

    if (Status != NDIS_STATUS_SUCCESS)

    {

        PNDIS_CONFIGURATION_PARAMETER ConfigurationParameter;

        UNICODE_STRING Keyword;

        //開啟配置鍵

        NdisOpenConfiguration(&Status, &ConfigurationHandle, WrapperConfigurationContext);

        if (Status == NDIS_STATUS_SUCCESS)

        {

            //查詢irq

            NdisInitUnicodeString(&Keyword, L"Irq");

            NdisReadConfiguration(&Status, &ConfigurationParameter, ConfigurationHandle, &Keyword, NdisParameterHexInteger);

            if(Status == NDIS_STATUS_SUCCESS)

            {

                Adapter->InterruptLevel =

                Adapter->InterruptVector = ConfigurationParameter->ParameterData.IntegerData;

            }

            //查詢port

            NdisInitUnicodeString(&Keyword, L"Port");

            NdisReadConfiguration(&Status, &ConfigurationParameter, ConfigurationHandle, &Keyword, NdisParameterHexInteger);

            if(Status == NDIS_STATUS_SUCCESS)

                Adapter->IoBaseAddress = ConfigurationParameter->ParameterData.IntegerData;

            NdisCloseConfiguration(ConfigurationHandle);

        }

    }

    //關鍵。設定自定義裝置擴充套件

 NdisMSetAttributes(MiniportAdapterHandle,

        (NDIS_HANDLE)Adapter,//記錄這個自定義的小埠裝置擴充套件 到 標準裝置擴充套件內部

        FALSE,NdisInterfaceIsa);

    Status = NdisMRegisterIoPortRange(&Adapter->IOBase,MiniportAdapterHandle,

             Adapter->IoBaseAddress,0x20);

if (Status != NDIS_STATUS_SUCCESS) 。。。

    Adapter->IOPortRangeRegistered = TRUE;

#ifndef NOCARD

    Status = NICInitialize(Adapter);//初始化網絡卡內部的硬體暫存器

    if (Status != NDIS_STATUS_SUCCESS) 。。。

    NdisOpenConfiguration(&Status, &ConfigurationHandle, WrapperConfigurationContext);

    if (Status == NDIS_STATUS_SUCCESS)

    {    //從登錄檔中讀取軟配置的MAC地址

         NdisReadNetworkAddress(&Status, (PVOID *)&RegNetworkAddress, &RegNetworkAddressLength, ConfigurationHandle);

         if(Status == NDIS_STATUS_SUCCESS && RegNetworkAddressLength == 6)

         {

             for(i = 0; i < 6; i++)

                 Adapter->StationAddress[i] = RegNetworkAddress[i];

         }

         NdisCloseConfiguration(ConfigurationHandle);

    }

    if (Status != NDIS_STATUS_SUCCESS || RegNetworkAddressLength !=6)

    {

        for (i = 0; i < 6; i++)  //使用固定的MAC地址

             Adapter->StationAddress[i] = Adapter->PermanentAddress[i];

    }

    。。。

    NICSetup(Adapter); //設定網絡卡內部的硬體暫存器

#endif

    //註冊中斷向量

    Status = NdisMRegisterInterrupt(&Adapter->Interrupt, MiniportAdapterHandle,

        Adapter->InterruptVector,Adapter->InterruptLevel,FALSE,

        Adapter->InterruptShared,Adapter->InterruptMode);

    if (Status != NDIS_STATUS_SUCCESS) 。。。

    Adapter->InterruptRegistered = TRUE;

#ifndef NOCARD

    NICStart(Adapter); //設定網絡卡內部的硬體暫存器

#endif

    NdisMRegisterAdapterShutdownHandler(MiniportAdapterHandle, Adapter, MiniportShutdown);

    Adapter->ShutdownHandlerRegistered = TRUE;

    InsertTailList(&DriverInfo.AdapterListHead, &Adapter->ListEntry);

    return NDIS_STATUS_SUCCESS;

}

如上,可以看出,一塊網絡卡的啟動初始化工作是比較複雜的。上面的示例函式分配了一個自定義的小埠裝置物件擴充套件,初始化網絡卡內部的硬體暫存器,註冊中斷向量,最後返回網絡卡的介質型別告訴給ndis框架(前3工作是可選的,最後的告訴工作是必須的)

事實上,一般的網絡卡在啟動初始化時都要做這些工作:【硬體、注斷、自擴充套件】

硬體:指初始化硬體

注斷:註冊中斷isr

自擴充套件:在標準小埠裝置擴充套件之外再另行分配一個自定義裝置擴充套件

題外話:

為什麼要分配一個自定義裝置擴充套件呢? 我們知道,ndis內部提供託管的AddDevice會為我們自動建立一個小埠裝置物件,而這個裝置物件的裝置擴充套件是ndis內部預置的一個結構。以往我們手動呼叫IoCreateDevice時都是自己定義的裝置擴充套件來儲存自定義資訊,但現在被ndis託管了,如果我們希望仍舊儲存一些自定義資訊怎麼辦?Ndis框架不傻,那個預置的小埠裝置擴充套件內部就提供了一個欄位(即介面卡上下文),用來存放使用者自定義的裝置擴充套件。使用者只需分配一個裝置擴充套件,然後呼叫NdisMSetAttributes設定一下即可。

到時候ndis呼叫我們的回撥函式時,會傳入這個自定義裝置擴充套件的。

如上,我們說了,在網絡卡啟動初始化階段,一般需要註冊一箇中斷向量,下面的函式就是幹這個的。

NDIS_STATUS

NdisMRegisterInterrupt(

    OUT PNDIS_MINIPORT_INTERRUPT    Interrupt,//返回

    IN  NDIS_HANDLE                 MiniportAdapterHandle,

    IN  UINT                        InterruptVector,

    IN  UINT                        InterruptLevel,

    IN  BOOLEAN                  RequestIsr,

    IN  BOOLEAN                     SharedInterrupt,

    IN  NDIS_INTERRUPT_MODE         InterruptMode)

{

  NTSTATUS Status;

  ULONG MappedIRQ;

  KIRQL DIrql;

  KAFFINITY Affinity;

  PLOGICAL_ADAPTER Adapter = (PLOGICAL_ADAPTER)MiniportAdapterHandle;

  RtlZeroMemory(Interrupt, sizeof(NDIS_MINIPORT_INTERRUPT));

  KeInitializeSpinLock(&Interrupt->DpcCountLock);

  // HandleDeferredProcessing為DPC

  KeInitializeDpc(&Interrupt->InterruptDpc, HandleDeferredProcessing, Adapter);

  KeInitializeEvent(&Interrupt->DpcsCompletedEvent, NotificationEvent, FALSE);

  Interrupt->SharedInterrupt = SharedInterrupt;

  Interrupt->IsrRequested = RequestIsr;

  Interrupt->Miniport = &Adapter->NdisMiniportBlock;

  MappedIRQ = HalGetInterruptVector(Adapter->NdisMiniportBlock.BusType, Adapter->NdisMiniportBlock.BusNumber,InterruptLevel, InterruptVector, &DIrql,&Affinity);

  //關鍵。註冊中斷向量。Isr為ServiceRoutine,是ndis自己內部提供的isr

  Status = IoConnectInterrupt(&Interrupt->InterruptObject, ServiceRoutine, Interrupt, &Interrupt->DpcCountLock, MappedIRQ,DIrql, DIrql, InterruptMode, SharedInterrupt, Affinity, FALSE);

  if (NT_SUCCESS(Status)) {

      Adapter->NdisMiniportBlock.Interrupt = Interrupt;

      Adapter->NdisMiniportBlock.RegisteredInterrupts++;

      return NDIS_STATUS_SUCCESS;

  }

  return NDIS_STATUS_FAILURE;

}

這樣,一旦有中斷髮生,就會進入ServiceRoutine這個isr。這個isr是ndis內部自己提供的,我們看它做了什麼

BOOLEAN  ServiceRoutine(IN  PKINTERRUPT Interrupt,  IN  PVOID  ServiceContext)

{

  BOOLEAN InterruptRecognized = FALSE;

  BOOLEAN QueueMiniportHandleInterrupt = FALSE;

  PNDIS_MINIPORT_INTERRUPT NdisInterrupt = ServiceContext;

  PNDIS_MINIPORT_BLOCK NdisMiniportBlock = NdisInterrupt->Miniport;

  if (NdisInterrupt->IsrRequested)//是否要執行isr

  {

      //呼叫我們註冊小埠特徵時登記的isr,簡稱我們的isr

      (*NdisMiniportBlock->DriverHandle->MiniportCharacteristics.ISRHandler)(

          &InterruptRecognized,

&QueueMiniportHandleInterrupt,  //返回是否要執行isr的後半部

          NdisMiniportBlock->MiniportAdapterContext);

  }

 else if (NdisMiniportBlock->DriverHandle->MiniportCharacteristics.DisableInterruptHandler)

 {

      (*NdisMiniportBlock->DriverHandle->MiniportCharacteristics.DisableInterruptHandler)(

          NdisMiniportBlock->MiniportAdapterContext);

       QueueMiniportHandleInterrupt = TRUE;

       InterruptRecognized = TRUE;

  }

  if (QueueMiniportHandleInterrupt) //執行HandleDeferredProcessing這個DPC,即isr的後半部

      KeInsertQueueDpc(&NdisInterrupt->InterruptDpc, NULL, NULL);

  return InterruptRecognized;

}

//我們的isr。(這是一個示例函式)

VOID NTAPI MiniportISR(

    OUT PBOOLEAN    InterruptRecognized,

    OUT PBOOLEAN    QueueMiniportHandleInterrupt,//返回時

    IN  NDIS_HANDLE MiniportAdapterContext)

{

    //遮蔽來自這個網絡卡的後續中斷。注意與cli指令不一樣。

NICDisableInterrupts((PNIC_ADAPTER)MiniportAdapterContext); 

    *InterruptRecognized          = TRUE;

    *QueueMiniportHandleInterrupt = TRUE;

}

如上,我們編寫的這個示例isr很簡單,它僅僅暫時遮蔽來自這個網絡卡的後續中斷,然後,QueueMiniportHandleInterrupt置為TRUE,表示將實質的中斷處理工作納入到DPC中去執行。由於DPC都是在開中斷的條件下執行的,所以必須先遮蔽掉來自同一網絡卡的其它後續中斷,防止巢狀。而這個DPC就是ndis內部自己提供的下面函式,我們看它做了什麼工作。

VOID  HandleDeferredProcessing(

    IN  PKDPC   Dpc,

    IN  PVOID   DeferredContext,

    IN  PVOID   SystemArgument1,

    IN  PVOID   SystemArgument2)

{

  PLOGICAL_ADAPTER Adapter = GET_LOGICAL_ADAPTER(DeferredContext);

//關鍵。呼叫使用者自己註冊小埠時提供的HandleInterruptHandler例程(*Adapter->NdisMiniportBlock.DriverHandle->MiniportCharacteristics.HandleInterruptHandler)(

      Adapter->NdisMiniportBlock.MiniportAdapterContext);  

//重新啟用中斷後執行這個函式

if(Adapter->NdisMiniportBlock.DriverHandle->MiniportCharacteristics.EnableInterruptHandler)

  (*Adapter->NdisMiniportBlock.DriverHandle->MiniportCharacteristics.EnableInterruptHandler)

(Adapter->NdisMiniportBlock.MiniportAdapterContext);

}

如上,使用者自己註冊小埠時提供的HandleInterruptHandler例程其實就是我們isr的後半部。當ndis框架回調執行了isr的後半部後,所有中斷處理工作都處理完畢了,然後就可以開啟來自這個網絡卡的後續中斷了,也即撤銷遮蔽。

下面就是一個示例HandleInterruptHandler,來自於ne2000驅動,我們看看那個驅動的isr後半部工作到底做了什麼。

VOID NTAPI MiniportHandleInterrupt(IN  NDIS_HANDLE MiniportAdapterContext)//自定義裝置擴充套件

{

    UCHAR ISRValue;

    UCHAR ISRMask;

    UCHAR Mask;

    PNIC_ADAPTER Adapter = (PNIC_ADAPTER)MiniportAdapterContext;

    UINT i = 0;

ISRMask = Adapter->InterruptMask;//一般為0xFF

//所有網絡卡內部都配備有一箇中斷狀態暫存器,即PG0_ISR

    NdisRawReadPortUchar(Adapter->IOBase + PG0_ISR, &ISRValue);//讀取當前網絡卡的狀態

    Adapter->InterruptStatus |= (ISRValue & ISRMask);

    Mask = 0x01;//mask表示位置掩碼

while (Adapter->InterruptStatus != 0x00 && i++ < INTERRUPT_LIMIT)

 {

        if (ISRValue != 0x00) {

            NdisRawWritePortUchar(Adapter->IOBase + PG0_ISR, ISRValue);

            Mask = 0x01;//重新回到最低位

        }

        //逐位向高位掃描

        while (((Adapter->InterruptStatus & Mask) == 0) && (Mask < ISRMask))

            Mask = (Mask << 1);

        switch (Adapter->InterruptStatus & Mask)

 {

        case ISR_OVW://每當晶片中的接收緩衝區溢位時會觸發這種中斷

            Adapter->BufferOverflow = TRUE;

       if(Adapter->MiniportAdapterHandle)

      HandleReceive(Adapter); //接出所有幀,提交給上層

            Adapter->InterruptStatus &= ~ISR_OVW;

            break;

        case ISR_RXE://每當收到一個錯誤幀時觸發這種中斷

            NICUpdateCounters(Adapter);

            Adapter->ReceiveError = TRUE;

        case ISR_PRX://每當晶片收到一個乙太網幀時,觸發這種中斷

       if(Adapter->MiniportAdapterHandle)

       HandleReceive(Adapter); //接出所有幀,提交給上層

            Adapter->InterruptStatus &= ~(ISR_PRX | ISR_RXE);

            break;

        case ISR_TXE://每當晶片傳送一幀失敗時,觸發這種中斷

            NICUpdateCounters(Adapter);

            Adapter->TransmitError = TRUE;

        case ISR_PTX://每當晶片中的傳送緩衝區變空時觸發這種中斷

            HandleTransmit(Adapter);

            Adapter->InterruptStatus &= ~(ISR_PTX | ISR_TXE);

            break;

        case ISR_CNT://每當晶片中的計數器溢位時觸發這種中斷

            NICUpdateCounters(Adapter);

            Adapter->InterruptStatus &= ~ISR_CNT;

            break;

        default:

            Adapter->InterruptStatus &= ~Mask;

            break;

        }

        Mask = (Mask << 1);

        NdisRawReadPortUchar(Adapter->IOBase + PG0_ISR, &ISRValue);

        Adapter->InterruptStatus |= (ISRValue & ISRMask);//狀態可能又變了,讀取最新的狀態

    }

}

如上,網絡卡的物理狀態發生上述變化時,都會觸發一次中斷,同時記錄在狀態暫存器對應的位。

我們的isr每得到一次中斷時,都要掃描狀態暫存器中的所有狀態位,一一處理(因為我們在處理中斷時,遮蔽了來自這個網絡卡的中斷,因此會造成中斷累積。所以必須在每次中斷的處理函式中處理所有可能發生的狀態)

網絡卡晶片中有一個硬體傳送緩衝區和一個硬體接收緩衝區。當從網路電纜來到一幀時,就會存放到晶片內部的接收緩衝區。晶片會將內部的傳送緩衝區中的幀注入到電纜上,這個過程也比較費時。晶片與電纜的資料交換速度受網絡卡製造工藝限制,一般的網絡卡也不過是百兆、千兆頻寬而已。即使交換速度慢,但是如果應用程式收幀的速度沒有幀從網路電纜抵達網絡卡的速度快的話,網絡卡內部的接收緩衝區就會逐漸變滿而溢位,從而導致觸發中斷。同理,當網絡卡終於把內部發送緩衝區中的資料發出到網路電纜後,傳送緩衝區變成空閒時,也會觸發中斷。

Ne2000乙太網卡的小埠驅動提供了HandleReceive這個函式,用於從網絡卡內部的接收緩衝區中讀出所有幀,提交給上層(實際上是提交給繫結著這塊網絡卡的所有協議驅動)。HandleReceive這個函式內部使用了NdisMEthIndicateReceive巨集完成提交工作。這個巨集實際上呼叫了下面的函式來做提交工作

VOID

EthFilterDprIndicateReceive(

    IN PETH_FILTER Filter,

    IN NDIS_HANDLE MacReceiveContext,

    IN PCHAR       Address,

    IN PVOID       HeaderBuffer,

    IN UINT        HeaderBufferSize,

    IN PVOID       LookaheadBuffer,

    IN UINT        LookaheadBufferSize,

    IN UINT        PacketSize)

{

    MiniIndicateData((PLOGICAL_ADAPTER)((PETHI_FILTER)Filter)->Miniport,

     MacReceiveContext,HeaderBuffer,HeaderBufferSize,

     LookaheadBuffer,LookaheadBufferSize,PacketSize);

}

VOID

MiniIndicateData(       //向上提交部分幀

    PLOGICAL_ADAPTER    Adapter,//目標網絡卡裝置的標準裝置擴充套件

    NDIS_HANDLE         MacReceiveContext,

    PVOID               HeaderBuffer,//幀頭

    UINT                HeaderBufferSize,//幀頭長度

    PVOID               LookaheadBuffer,//負載部分的前N位元組,又叫前視區

    UINT                LookaheadBufferSize,// 前視區長度

    UINT                PacketSize)//負載部分的總長 

{

  KIRQL OldIrql;

  PLIST_ENTRY CurrentEntry;

  PADAPTER_BINDING AdapterBinding;

  MiniDisplayPacket2(HeaderBuffer, HeaderBufferSize, LookaheadBuffer, LookaheadBufferSize);

  KeAcquireSpinLock(&Adapter->NdisMiniportBlock.Lock, &OldIrql);

  {

      CurrentEntry =