這兩個Fast I/O例程有相同的接口:
typedef BOOLEAN (*PFAST_IO_QUERY_ABSIC_INFO)(
IN struct _FILE_OBJECT *FileObject,
IN BOOLEAN Wait,
OUT PFILE_BASIC_INFORMATION Buffer,
OUT PIO_STATUS_BLOCK IoStatus,
IN struct _DEVICE_OBJECT *DeviceObject);
Wait參數表示調用者是否阻塞以等待獲取信息的返回,如果設置為FALSE,則調用要么立刻完成,要么返回FALSE,如果返回FALSE,則會生成一個包含整個操作必要的上下文件的IRP。有趣的是在NT3.51中當Wait設置為FALSE時這兩個例程將不被調用,當然這在以后的版本中會修改。
這兩個例程一但被調用,它們會查詢文件對象第一次打開時保存的信息,這些信息也可能被實時變化,例如,文件的最近訪問時間屬性會被文件系統設置的當前的系統時間,當然設置這些屬性取決于文件系統的實現。
FastIoLock,FastIoUnLockSingle,FastIoUnLockAll,and FastIoUnLockAllByKey
這些例程被用來控制特殊文件的加鎖狀態。對文件的加鎖以字節為單位,所以可以對文件的多個字節進行加密。標準的NT文件系統使用文件系統運行時開發包(FsRtl函數)所提供的通用代碼來驗證鎖權限并存儲文件的加鎖范圍。鎖狀態通過調用NT API函數NtLockFile和NtUnLockFile來控制。
在Windows NT中有兩種鎖,一種是排它鎖,是寫鎖,說明加鎖的地放要進行修改;另一種是共享鎖,是讀鎖,說明加鎖的地放用來的讀的。多個共享鎖在重疊操作中可以被授權,并且一直保存到釋放為止。將各種加鎖的信息存儲起來在訪問這些信息的時候會提高速度。
FastIoLock的原型:
Typedef BOOLEAN (*PFAST_IO_LOCK)(
IN struct _FILE_OJBECT *FileObject,
IN PLARGE_INTEGER FileOffset,
IN PLARGE_INTEGER Length,
PEPROCESS ProcessId,
ULONG Key,
BOOLEAN FailImmediately,
BOOLEAN ExclusiveLock,
OUT PIO_STATUS_BLOCK IoStatus,
IN struct _DEVICE_OBJECT *DeviceObject
);
FileOffset 和Length參數對應加鎖的范圍,ProcessId標識加鎖的進程,如果進行退出,鎖將會清除。Key參數提供一個非透明的值,用來關聯多個鎖,例如調用FastIoUnLockAllByKey可以快速訪問多個鎖。FailImmediately用來標識當鎖無效時是立刻返回失敗還是阻塞直到鎖可用。對于FsRtl函數,如果是無效的鎖則忽略FailImmediately參數,函數返回FALSE。ExclusiveLock參數用來標識是排它鎖還是共享鎖。
FastUnlockSingle例程被用來釋放對文件的加鎖,原型如下:
Typedef BOOLEAN (*PFAST_IO_UNLOCK_SINGLE)(
IN struct _FILE_OBJECT *FileObject,
IN PLARGE_INTEGER FileOffset,
IN PLARGE_INTEGER Length,
PEPROCESS ProcessId,
ULONG Key,
OUT PIO_STATUS_BLOCK IoStatus,
IN struct _DEVICE_OBJECT *DeviceObject
);
對大多文件系統來說,如果文件沒有加鎖,此例程總是返回TRUE,即使朝無效的鎖,操作也會完成,因為用IRP來操作也同樣會產生相同的錯誤。
如果這個解鎖操作成功,那么FileOffset,Length,ProcessId,和Key必須和相應的鎖信息匹配,否則操作會返回錯誤STATUS_RANGE_NOT_LOCKED。FastIoUnlockAll例程用來釋放特殊文件所有的鎖,函數原型如下:
typedef BOOLEAN(*PFAST_IO_UNLOCK_ALL)(
IN struct _FILE_OBJECT *FileObject,
PEPROCESS ProcessId,
OUT PIO_STATUS_BLOCK IoStatus,
IN struct _DEVICE_OBJECT *DeviceObject
);
在這種情況下,Fast I/O例程查找進程ProcessId所操作的文件所有的鎖,并刪除,無論是排它鎖還是共享鎖。這個例程常用在當系統由于關掉程序或終止程序而調用NtCloseFile時。
FastIoUnlockAllByKey操作用來通過一些特殊的鍵值來刪除一系列鎖。原型如下:
Typedef BOOLEAN (*PFAST_IO_UNLOCK_ALL_BY_KEY)(
IN struct _FILE_OBJECT *FileObject,
PVOID ProcessId,
ULONG Key,
OUT PIO_STATUS_BLOCK_IoStatus,
IN struct _DEVICE_OBJECT *DeviceObject
);
提供這個例程是為了便于文件服務如SRV。在NT3.51的I/O管理器中沒有出現這個調用。鍵值用來文件服務給遠程客戶機文件加鎖時分配的。因為許多遠程客戶端,僅有ProcessId是遠遠不夠的。同樣,對于多文件服務器,僅使用鍵值也會在其它的文件服務器釋放時導致錯誤。二者同時使用以確保正確操作并允許遠程加鎖。
4.設備棧,過濾,文件系統的感知
前邊都在介紹文件系統驅動的結構,卻還沒講到我們的過濾驅動如何能捕獲所有發給文件系統驅動的irp,讓我們自己來處理?前面已經解釋過了設備對象。現在來解釋一下設備棧。
任何設備對象都存在于某個設備棧中。設備棧自然是一組設備對象。這些設備對象是互相關聯的,也就是說,如果得到一個DO指針,你就可以知道它所處的設備棧。
任何來自應用的請求,最終被windows io mgr翻譯成irp的,總是發送給設備棧的頂端那個設備。
原始irp irp irp irp
--------------> ------> -------> ----->
DevTop Dev2 ... DevVolumne ... ???
<-------------- <------ <------- <-----
原始irp(返回) irp irp irp
上圖向右的箭頭表示irp請求的發送過程,向左則是返回。可見irp是從設備棧的頂端開始,逐步向下發送。DevVolumue表示我們實際要過濾的Volume設備,DevTop表示這個設備棧的頂端。我們只要在這個設備棧的頂端再綁定一個設備,那發送給Volume的請求,自然會先發給我們的設備來處理。
有一個系統調用可以把我們的設備綁定到某個設備的設備棧的頂端。這個調用是IoAttachDeviceToDeviceStack,這個調用2000以及以上系統都可以用(所以說到這點,是因為還有一個IoAttachDeviceToDeviceStackSafe,是2000所沒有的。這常常導致你的filter在2000下不能用。)
我自己寫了一個函數來幫我實現綁定功能:
//----------------------wdf.h中的內容----------------------------------
// 這個例程把源設備綁定到目標設備的設備棧中去,并返回源設備所直
// 接綁定的設備。注意源設備未必直接綁定在目標設備上。它應綁定在
// 目標設備的設備棧的頂端。
_inline wd_stat wd_dev_attach(in wd_dev *src,
in wd_dev *dst,
in out wd_dev **attached)
{
*attached = dst;
*attached = IoAttachDeviceToDeviceStack(src,dst);
if(*attached == NULL)
return wd_stat_no_such_dev;
return wd_stat_suc;
}
到這里,我們已經知道過濾對Volume的請求的辦法。比如“C:”這個設備,我已經知道符號連接為“C:”,不難得到設備名。得到設備名后,又不難得到設備。這時候我們IoCreateDevice()生成一個Device Object,然后調用wd_dev_attach綁定,不是一切ok嗎?所有發給“C:”的irp,就必然先發送給我們的驅動,我們也可以捕獲所有對文件的操作了!
這確實是很簡單的處理方法。我得到的FileMon的代碼就是這樣處理的,如果不想處理動態的Volume,你完全可以這樣做。但是我們這里有更高的要求。當你把一個U盤插入usb口,一個“J:”之類的Volume動態誕生的時候,我們依然要捕獲這個事件,并生成一個Device來綁定它。
一個新的存儲媒質被系統發現并在文件系統中生成一個Volume的過程稱為Mounting.其過程開始的時候,FS的CDO將得到一個IRP,其Major Function Code為IRP_MJ_FILE_SYSTEM_CONTROL,Minor Function Code為IRP_MN_MOUNT。換句話說,如果我們已經生成了一個設備綁定文件系統的CDO,那么我們就可以得到這樣的IRP,在其中知道一個新的Volume正在Mount.這時候我們可以執行上邊所說的操作。
現在的問題是如何知道系統中有那些文件系統,還有就是我應該在什么時候綁定它們的控制設備。
IoRegisterFsRegistrationChange()是一個非常有用的系統調用。這個調用注冊一個回調函數。當系統中有任何文件系統被激活或者是被注銷的時候,你注冊過的回調函數就會被調用。
//----------------------wdf.h中的內容----------------------------------
wd_stat wdff_reg_notify(
in wd_drv *driver,
in wdff_notify_func func
)
{
return IoRegisterFsRegistrationChange(driver,func);
}
你有必要為此寫一個回調函數。
//-------------------我的回調處理函數----------------------------------
wd_void my_fs_notify(
in wd_dev *dev,
in wd_bool active)
{
wd_wchar name_buf[wd_dev_name_max_len];
wd_ustr name;
wd_ustr_init_em(&name,name_buf,wd_dev_name_max_len);
// 如果注冊了,就應該得到通知
wd_printf0("notify: a file sys have been acitved!!! rn");
// 得到文件系統對象的名字,然后打印出來
wd_obj_get_name(dev,&name);
wd_printf0("notify : file sys name = %wZrn",&name);
if(active)
{
wd_printf0("notify: try to attach.rn");
// ... 請在這里綁定文件系統的控制設備
}
else
{
wd_printf0("notify: unactive.rn");
// ...
}
}
應該如何綁定一個文件系統CDO?我們在下面的章節再詳細描述。
現在我們應該再在wd_main函數中加上下邊的內容:
if(wdff_reg_notify(driver,my_fs_notify) != wd_stat_suc)
{
wd_printf0("error: reg notify failed.rn");
wd_fio_disp_release(driver);
wd_dev_del(g_cdo);
g_cdo = wd_null;
return wd_stat_insufficient_res;
};
wd_printf0("success: reg notify ok.n");
我們再次回顧一下,wd_main中,應該做哪些工作。
a.生成一個控制設備。當然此前你必須給控制設置指定名稱。
b.設置Dispatch Functions.
c.設置Fast Io Functions.
d.編寫一個my_fs_notify回調函數,在其中綁定剛激活的FS CDO.
e.使用wdff_reg_notify調用注冊這個回調函數。
5.綁定FS CDO,文件系統識別器,設備擴展
上一節講到我們打算綁定一個剛剛被激活的FS CDO.前邊說過簡單的調用wd_dev_attach可以很容易的綁定這個設備。但是,并不是每次my_fs_notify調用發現有新的fs激活,我就直接綁定它。
首先判斷是否我需要關心的文件系統類型。我用下面的函數來獲取設備類型。
// ------------------wdf.h中的內容-------------------
_inline wd_dev_type wd_dev_get_type(in wd_dev *dev)
{
return dev->DeviceType;
}
文件系統的CDO的設備類型有下邊的幾種可能,你的過濾驅動可能只對其中某些感興趣。
enum {
wd_dev_disk_fs = FILE_DEVICE_DISK_FILE_SYSTEM,
wd_dev_cdrom_fs = FILE_DEVICE_CD_ROM_FILE_SYSTEM,
wd_dev_network_fs = FILE_DEVICE_NETWORK_FILE_SYSTEM
};
你應該自己寫一個函數來判斷該fs是否你所關心的。
// -------------一個函數,判斷是否我所關心的fs---------------
wd_bool my_care(wd_ulong type)
{
return (((type) == wd_dev_disk_fs) ||
((type) == wd_dev_cdrom_fs) ||
((type) == wd_dev_network_fs));
}
下一個問題是我打算跳過文件系統識別器。文件系統識別器是文件系統驅動的一個很小的替身。為了避免沒有使用到的文件系統驅動占據內核內存,windows系統不加載這些大驅動,而代替以該文件系統驅動對應的文件系統識別器。當新的物理存儲媒介進入系統,io管理器會依次的嘗試各種文件系統對它進行“識別”。識別成功,立刻加載真正的文件系統驅動,對應的文件系統識別器則被卸載掉。對我們來說,文件系統識別器的控制設備看起來就像一個文件系統控制設備。但我們不打算綁定它。
分辨的方法是通過驅動的名字。凡是文件系統識別器的驅動對象的名字(注意是DriverObject而不是DeviceObject!)都為“FileSystem\Fs_Rec”.
//-------------------用這些代碼來跳過文件系統識別器----------------------
wd_wchar name_buf[wd_dev_name_max_len];
wd_ustr name,tmp;
wd_ustr_init_em(&name,name_buf,wd_dev_name_max_len);
wd_ustr_init(&tmp,L"\FileSystem\Fs_Rec");
// 我不綁定識別器。所以如果是識別器,我直接返回成功。查看是否是識別
// 器的辦法是看是否是FileSystemFs_Rec的設備。
wd_obj_get_name(wd_dev_drv(fs_dev),&name);
if(wd_ustr_cmp(&name,&tmp,wd_true) == 0)
{
wd_printf0("attach fs dev:is a recogonizer.rn");
return wd_stat_suc;
}
wd_printf0("attach fs dev: not a recogonizer.rn");
接下來我將要生成我的設備。這里要提到設備擴展的概念。設備對象是一個數據結構,為了表示不同的設備,里邊將有一片自定義的空間,用來給你記錄這個設備的特有信息。我們為我們所生成的設備確定設備擴展如下:
// 文件過濾系統驅動的設備擴展
typedef struct _my_dev_ext
{
// 我們綁定的文件系統驅動
wd_dev * attached_to;
// 上邊這個設備的設備名。
wd_ustr dev_name;
// 這是上邊的unicode字符串的緩沖區
wd_wchar name_buf[wd_dev_name_max_len];
} my_dev_ext;
之所以如此簡單,是因為我們現在還沒有多少東西要記錄。只要記得自己綁定在哪個設備上就好了。如果以后需要更多的信息,再增加不遲。擴展空間的大小是在wdf_dev_create(也就是這個設備生成)的時候指定的。得到設備對象指針后,我用下面這個函數來獲取設備擴展指針:
// --------------wdf.h中的內容------------------
_inline wd_void * wd_dev_ext(wd_dev *dev)
{
return (dev->DeviceExtension);
}
生成設備后,為了讓系統看起來,你的設備和原來的設備沒什么區別,你必須設置一些該設備的標志位與你所綁定的設備相同。
_inline wd_void wd_dev_copy_flag(wd_dev *new_dev,
wd_dev *old_dev)
{
if(old_dev->Flags & DO_BUFFERED_IO)
new_dev->Flags &= DO_BUFFERED_IO;
if(old_dev->Flags & DO_DIRECT_IO)
new_dev->Flags &= DO_DIRECT_IO;
if (old_dev->Characteristics & FILE_DEVICE_SECURE_OPEN)
new_dev->Characteristics &= FILE_DEVICE_SECURE_OPEN;
}
DO_BUFFERED_IO,DO_DIRECT_IO這兩個標志的意義在于外部向這些設備發送讀寫請求的時候,所用的緩沖地址將有所不同。這點以后在過濾文件讀寫的時候再討論。現在一切事情都搞完,你應該去掉你的新設備上的DO_DEVICE_INITIALIZING標志,以表明的的設備已經完全可以用了。
// --------------wdf.h中的內容------------------
_inline wd_void wd_dev_clr_init_flag(wd_dev *dev)
{
dev->Flags &= ~DO_DEVICE_INITIALIZING;
}
現在我寫一個函數來完成以上的這個過程。你只要在上一節中提示的位置調用這個函數,就完成對文件系統控制設備的綁定了。
//-----------綁定一個文件系統驅動設備-------------------------
wd_stat my_attach_fs_dev(wd_dev *fs_dev)
{
wd_wchar name_buf[wd_dev_name_max_len];
wd_ustr name,tmp;
wd_dev *new_dev;
wd_stat status;
my_dev_ext *ext;
wd_ustr_init_em(&name,name_buf,wd_dev_name_max_len);
wd_ustr_init(&tmp,L"\FileSystem\Fs_Rec");
// 如果不是我關心的類型,我直接返回成功
if(!my_care(wd_dev_get_type(fs_dev)))
{
wd_printf0(("attach fs dev:not a cared type.rn"));
return wd_stat_suc;
}
wd_printf0("attach fs dev: is my cared type.rn");
// 我不綁定識別器。所以如果是識別器,我直接返回成功。查看是否是識別
// 器的辦法是看是否是FileSystem\Fs_Rec的設備。
wd_obj_get_name(wd_dev_drv(fs_dev),&name);
if(wd_ustr_cmp(&name,&tmp,wd_true) == 0)
{
wd_printf0("attach fs dev:is a recogonizer.rn");
return wd_stat_suc;
}
wd_printf0("attach fs dev: not a recogonizer.rn");
// 現在來生成一個設備用來綁定
status = wd_dev_create(g_drv,sizeof(my_dev_ext),NULL,
wd_dev_get_type(fs_dev),
0,wd_false,&new_dev);
if(!wd_suc(status))
{
wd_printf0("attach fs dev: dev create failed.rn");
return status;
}
wd_printf0("attach fs dev: create dev success.rn");
// 接著設置設備的各種標志與之要綁定的標志一致
wd_dev_copy_flag(new_dev,fs_dev);
ext = (my_dev_ext *)wd_dev_ext(new_dev);
wd_printf0("begin to attach.rn");
status = wd_dev_attach(new_dev,fs_dev,&ext->attached_to);
wd_printf0("attach over.status = %8xrn",status);
if(!wd_suc(status))
{
wd_printf0("attach fs dev: dev attach failed.rn");
UNREFERENCED_PARAMETER(new_dev);
wd_dev_del(new_dev);
return status;
}
wd_printf0("attach fs dev: attach %wZ succeed.rn",&name);
wd_ustr_init_em(&ext->dev_name,ext->name_buf,wd_dev_name_max_len);
wd_ustr_copy(&ext->dev_name,&name);
wd_dev_clr_init_flag(new_dev);
return status;
}
6.IRP的傳遞,File System Control Dispatch
我們現在不得不開始寫dispatch functions.因為你的設備已經綁定到文件系統控制設備上去了。windows發給文件系統的請求發給你的驅動。如果你不能做恰當的處理,你的系統的就會崩潰。
最簡單的處理方式是把請求不加改變的傳遞到我們所綁定的設備上去。如何獲得我們所綁定的設備?上一節已經把該設備記錄在我們的設備擴展里。
//------------我用這個函數快速得到我所綁定的設備-----------
// 得到綁定的設備
_inline wd_dev *my_dev_attached(wd_dev *dev)
{
return ((wdff_dev_ext *)wd_dev_ext(dev))->attached_to;
}
如何傳遞請求?使用IoCallDriver,該調用的第一個參數是設備對象指針,第二個參數是IRP指針。
一個IRP擁有一組IO_STACK_LOCATION.前面說過IRP在一個設備棧中傳遞。IO_STACK_LOCATION是和這個設備棧對應的。用于保存IRP請求在當前設備棧位置中的部分參數。如果我要把請求往下個設備傳遞,那么我應該把當前IO_STATCK_LOCATION復制到下一個。
我寫了一些函數來處理IO_STACK_LOCATION,另外wd_irp_call用來包裝IoCallDriver的功能。
//---------------------wdf.h中的內容----------------------------
typdef wd_irpsp PIO_STACK_LOCAION;
_inline wd_irpsp *wd_cur_io_stack(wd_irp *irp)
{
return IoGetCurrentIrpStackLocation(irp);
}
_inline wd_void wd_skip_io_stack(wd_pirp irp)
{
IoSkipCurrentIrpStackLocation(irp);
}
_inline wd_void wd_copy_io_stack(wd_irp *irp)
{
IoCopyCurrentIrpStackLocationToNext(irp);
}
_inline wd_stat wd_irp_call(wd_dev *dev,wd_pirp irp)
{
return IoCallDriver(dev,irp);
}
有了上邊這些,我現在可以寫一個默認的Dispatch Functions.
// 默認的處理很簡單,忽略當前調用棧,直接發送給綁定設備
wd_stat my_disp_default(in wd_dev *dev,in wd_pirp irp)
{
wd_dev *attached_dev;
if(!is_my_dev(dev))
return wd_irp_failed(irp,wd_stat_invalid_dev_req);
if(is_my_cdo(dev))
return wd_irp_failed(irp,wd_stat_invalid_dev_req);
attached_dev = my_dev_attached(dev);
if(!attached_dev)
return wd_irp_failed(irp,wd_stat_invalid_dev_req);
wd_skip_io_stack(irp);
return wd_irp_call(attached_dev,irp);
}
上邊有一個函數is_my_dev來判斷是否我的設備。這個判斷過程很簡單。通過dev可以得到DriverObject指針,判斷一下是否我自己的驅動即可。is_my_cdo()來判斷這個設備是否是我的控制設備,不要忘記在wd_main()中我們首先生成了一個本驅動的控制設備。實際這個控制設備還不做任何事情,所以對它發生的任何請求也是非法的。返回錯誤即可。wd_irp_failed這個函數立刻讓一個irp失敗。其內容如下:
// 這個函數可以立刻失敗掉一個irp
_inline wd_stat wd_irp_failed(wd_pirp irp,wd_stat status_error)
{
irp->IoStatus.Status = status_error;
irp->IoStatus.Information = 0;
return wd_irp_over(irp);
}
如此一來,本不改發到我的驅動的irp,就立刻返回錯誤非法請求。但是實際上這種情況是很少發生的。
如果你現在想要你的驅動立刻運行,讓所有的dispacth functions都調用my_disp_default.這個驅動已經可以綁定文件系統的控制設備,并輸出一些調試信息。但是還沒有綁定Volume.所以并不能直接監控文件讀寫。
對于一個綁定文件系統控制設備的設備來說,其他的請求直接調用上邊的默認處理就可以了。重點需要注意的是上邊曾經掛接IRP_MJ_FILE_SYSTEM_CONTROL的dispatch處理的函數my_disp_file_sys_ctl().
IRP_MJ_FILE_SYSTEM_CONTROL這個東西是IRP的主功能號。每個主功能號下一般都有次功能號。這兩個東西標示一個IRP的功能。
主功能號和次功能號是IO_STACK_LOCATION的開頭兩字節。
//----------------我重新定義的次功能號-------------------
enum {
wd_irp_mn_mount = IRP_MN_MOUNT_VOLUME,
wd_irp_mn_load_filesys = IRP_MN_LOAD_FILE_SYSTEM,
wd_irp_mn_user_req = IRP_MN_USER_FS_REQUEST
};
enum {
wdf_fsctl_dismount = FSCTL_DISMOUNT_VOLUME
};
要得到功能號,要先得到當前的IO_STACK_LOCATION,這個上邊已經有函數wd_cur_io_stack,相信這個不能難倒你。
當有Volumne被Mount或者dismount,你寫的my_disp_file_sys_ctl()就被調用。具體的判斷方法,就見如下的代碼了:
// 可以看到分發函數中其他的函數處理都很簡單,但是file_sys_ctl的
// 處理會比較復雜。我們已經在notify函數中綁定了文件系統驅動的控
// 制對象。當文件系統得到實際的介質的時候,會生成新的設備對象,
// 這種設備稱為卷(Volume),而這種設備是在file_sys中的mount中生
// 成的,而且也是unmount中注銷掉的。我們捕獲這樣的操作之后,就必
// 須生成我們的設備對象,綁定在這樣的“卷”上,才能綁定對這個卷
// 上的文件的操作。
wd_stat my_disp_file_sys_ctl(in wd_dev *dev,in wd_pirp irp)
{
wd_dev *attached_dev;
wd_io_stack *stack = wd_cur_io_stack(irp);
if(!is_my_dev(dev))
return wd_irp_failed(irp,wd_stat_invalid_dev_req);
switch(wd_irpsp_minor(stack))
{
case wd_irp_mn_mount:
// 在這里,一個Volume正在Mount
return my_fsctl_mount(dev,irp);
case wd_irp_mn_load_filesys:
return my_fsctl_load_fs(dev,irp);
case wd_irp_mn_user_req:
{
switch(wd_irpsp_fs_ctl_code(stack))
{
case wdf_fsctl_dismount:
// 在這里,一個Volume正dismount
return my_fsctl_dismount(dev,irp);
}
}
}
wd_skip_io_stack(irp);
attached_dev = my_dev_attached(dev);
return wd_irp_call(attached_dev,irp);
}
你發現你又得開始寫兩個新的函數,my_fsctl_mount()和my_fsctl_dismount(),來處理卷的Mount和Dismount.顯然,你應該在其中生成設備或者刪除,綁定或者解除綁定。很快,你就能完全監控所有的卷了。
這樣做是動態監控所有的卷的完美的解決方案。
如果是在xp以上,有一個調用可以獲得一個文件系統上已經被Mount的卷。但是2000下不能使用。所以我們沒有使用那個方法。何況僅僅得到已經Mount的卷也不是我想要的。
這里另外還有一個my_fsctl_load_fs函數。發生于IRP_MN_LOAD_FILESYS。這個功能碼我只做一點點解釋:當一個文件識別器(見上文)決定加載真正的文件系統的時候,會產生一個這樣的irp。
你現在可以修改你的驅動,使插入拔出u盤的時候,在Volume加載卸載時候輸出調試信息。回首一下我們的脈絡:
a.生成一個控制設備。當然此前你必須給控制設置指定名稱。
b.設置Dispatch Functions.
c.設置Fast Io Functions.
d.編寫一個my_fs_notify回調函數,在其中綁定剛激活的FS CDO.
e.使用wdff_reg_notify調用注冊這個回調函數。
f.編寫默認的dispatch functions.
e.處理IRP_MJ_FILE_SYSTEM_CONTROL,在其中監控Volumne的Mount和Dismount.
f.下一步自然是綁定Volumne了,請聽下回分解。
7.IRP完成函數,中斷級,如何超越中斷級別的限制
先討論一下Volumne設備是如何得到的.首先看以下幾個函數:
// ------------------wdf.h 中的內容 -------------------------
typedef VPB wd_vpb;
_inline wd_vpb * wd_dev_vbp(wd_dev *dev)
{
return dev->Vpb;
}
_inline wd_dev * wd_vbp_dev(wd_vpb *vpb)
{
return vpb->DeviceObject;
}