Windows檔案系統過濾驅動開發教程 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;
}
VPB是Volume parameter block.一個資料結構.它的主要作用是把實際儲存媒介裝置對象和檔案系統上的卷裝置對象聯絡起來.
wd_dev_vbp可以讓你從一個Storage Device Object得到一個VPB,而wd_vbp_dev這個函數可以得到這個VPB所對應的Volmue裝置.
現在首先要得到Storage Device Object.實際上這個東西儲存在當前IO_STACK_LOCATION中.
// ------------------wdf.h 中的內容 -----------------------
_inline wd_dev *wd_irpsp_mount_storage(wd_io_stack *irpsp)
{
return irpsp->Parameters.MountVolume.Vpb->RealDevice;
};
那麼,從irp出發,我最終可以通過以下的方式得到Volumue裝置:
wd_irpsp *irpsp = wd_cur_io_stack(irp);
wd_dev *storage_dev = wd_irpsp_mount_storage(irp);
wd_vpb *vpb = wd_dev_vbp(storage_dev);
wd_dev *volume_dev = wd_vbp_dev(vpb);
不過實際情況並不這麼簡單.這裡的IRP是一個MOUNT請求.而volume裝置對象實際上是這個請求完成之後的返回結果.因此,在這個請求還沒有完成之前,我們就試圖去獲得Volume裝置對象,當然是竹籃打水一場空了.
這裡,你可以直接拷貝當前IO_STACK_LOCATION,然後向下發送請求,但在此之前,要先給irp分配一個完成函數.irp一旦完成,你的完成函數將被調用.這樣的話,你可以在完成函數中得到Volume裝置,並實施你的綁定過程.
這裡要討論一下中斷層級的問題.常常碰到人問某函數只能在Passive Level調用是什麼意思.總之我們的任何代碼執行的時候,總是處在某個當前的中斷級之中.某些系統調用只能在低層級中斷級中執行.請注意,如果一個調用 可以在高處運行,那麼它能在低處運行,反過來則不行.
我們需要知道的只是我們關心Passive Level和Dispatch Level.而且Dispatch Level的中斷級較高.一般ddk上都會標明,如果註明irq level>=dispatch,那麼你就不能在passive level的代碼中調用它們了.
那麼你如何判斷當前的代碼在哪個中斷層級中呢?我一般是這麼判斷的:如果你的代碼執行是由於應用程式(或者說上層)的調用而引發的,那麼應該在Passive Level.如果你的代碼執行是由於下層硬體而引發的,那麼則可能在dispatch level.
希望不要機械的理解我的話!以上只是極為粗略的便於記憶的理解方法.實際的應用應該是這樣的:所有的dispatch functions由於是上層發來的irp而導致的調用,所以應該都是Passive Level,在其中你可以調用絕大多數系統調用.而如網卡的OnReceive,硬碟讀寫完畢,返回而導致的完成函數,都有可能在Dispatch級.注 意都是有可能,而不是絕對是.但是一旦有可能,我們就應該按就是考慮.
好,現在我們發現,我們已經註冊了完成函數,並且這個函數執行中可能是dispatch level.
現在面臨的問題是,我們已經決定在完成函數中調用 IoAttachDeviceToDeviceStack來綁定Volume.而DDK說明有:Callers of IoAttachDeviceToDeviceStack must be running at IRQL <= DISPATCH_LEVEL.
實際上前邊說過有IoAttachDeviceToDeviceStackSafe,這個調用可以在Dispatch level進行.無奈這個調用僅僅出現在Xp以上的系統中.
超越中斷層級的限制有幾種方法.第一種是自己產生一個系統線程來完成此事.系統線程將保證在Passive Level中運行.另一種方法就是把自己的任務插入Windows工作者線程,這會使你的任務遲早得到執行.如果你的任務比較小,可以實行第二種方法.對 系統來說比較省事,對程式員來說則反正都是麻煩.
我做了以下幾個函數專門來插入任務到工作者線程.
//---------------wdf.h 中的內容 ------------------------
typedef WORK_QUEUE_ITEM wd_work_item;
typedef PWORKER_THREAD_ROUTINE wd_work_func;
// 任務的初始化
_inline wd_void wd_work_init(wd_work_item *item,
wd_work_func worker,
wd_void *context)
{
ExInitializeWorkItem(item,worker,context);
}
// 三種任務隊列
typedef enum _wd_work_quque_type{
wd_work_crit = CriticalWorkQueue,
wd_work_delay = DelayedWorkQueue,
wd_work_hyper = HyperCriticalWorkQueue
} wd_work_queue_type;
_inline wd_void wd_work_queue(in wd_work_item *item,
in wd_work_queue_type type)
{
ExQueueWorkItem(item,(WORK_QUEUE_TYPE)type);
}
_inline wd_void wd_work_run(in wd_work_item *item)
{
(item->WorkerRoutine)(item->Parameter);
}
任務是一個資料結構,已經被我重定義為wd_work_item,wd_work_init能初始化它.初始化的時候你只需要填寫一個你的任務的函數.同時一個context用來記錄上下相關參數.(這是個null 指標,你可以只想你任何想要的參數類型).
一般這個任務會自動執行,但是有時我們也想不插入隊列,我們自己執行它.那麼調用wd_work_run即可.
然後調用wd_work_queque插入工作者隊列,之後會被執行.插入類型這裡選擇wd_work_delay.
希望你沒有被這一串東西搞糊塗.現在我會寫一個"設定完成函數"的函數.執行後,自動在Passive Level級執行你的完成函數.希望不會把你搞得暈頭轉向的:).
// 完成常式上下文。好幾個fsctl需要註冊完成常式。而常式中的工作可能
// 只能在passive level中運行,因此不得不加入一個work_item,把任務塞
// 入背景工作執行緒等待完成
typedef struct _my_fsctl_comp_con
{
wd_work_item work;
wd_dev *dev;
wd_irp *irp;
wd_dev *new_dev; // 這個元素僅僅用於mount的時候。因為我
// 們要產生一個新裝置來綁定vdo.
} my_fsctl_comp_con;
wd_bool my_fsctl_set_comp(wd_dev *dev,
wd_irp *irp,
wd_dev *new_dev,
wd_irp_comp_func complete,
wd_work_func work_complete)
{
my_fsctl_comp_con *context;
context = (wdff_fsctl_comp_con *)wd_malloc(wd_false,
sizeof(wdff_fsctl_comp_con));
if(context == NULL)
{
wd_printf0("fsctl set comp: failed to malloc context.\r\n");
return wd_false;
}
// 初始化工作細節
wd_work_init(&context->work,
work_complete,
context);
context->dev = dev;
context->irp = irp;
context->new_dev = new_dev;
// 設定irp完成常式
wd_irp_comp(irp,complete,context);
return wd_true;
}
// 以下函數作為以上complete的參數被使用
wd_stat my_fsctl_comp(in wd_dev *dev,
in wd_irp *irp,
in wd_void *context)
{
wd_printf0("fsctl_comp: come in!!!\r\n");
UNREFERENCED_PARAMETER(dev);
UNREFERENCED_PARAMETER(irp);
// 判斷當前中斷級
if(wd_get_cur_irql() > wd_irql_passive)
{
wd_printf0("fsctl_comp:into quque!!!\r\n");
// 如果在passive更低的中斷層級,必須插入延遲隊列中運行
wd_work_queue((wd_work_item *)context,wd_work_delay);
}
else
{
// 否則可以直接執行
wd_printf0("fsctl_comp:run directly!!!\r\n");
wd_work_run((wd_work_item *)context);
}
return wd_stat_more_processing;
}
我想以上的過程應該已經可以理解了!註冊了基本的完成曆程complete函數(也就是我最後寫的函數my_fsctl_comp後),irp執行完畢回 調my_fsctl_comp,而我事先已經把已經做好的任務(wd_work_item)寫在上下文指標中(context)中.一回調這個函數,我就 wd_work_queque插入隊列.結果wd_work_item中記錄的work_complete函數顯然會在Passive level中執行.我們的系統也將保持穩定.
work_complete函數將從context上下文指標中得到足夠的參數,來完成對Volume的綁定.
希望你沒有被弄昏頭:),我們下回再分解.