• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            小默

            (轉載)Windows文件系統過濾驅動開發教程 作者,楚狂人自述 (3)

            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用來記錄上下相關參數.(這是個空指針,你可以只想你任何想要的參數類型).

            一般這個任務會自動執行,但是有時我們也想不插入隊列,我們自己執行它.那么調用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.rn");
            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!!!rn");
            UNREFERENCED_PARAMETER(dev);
            UNREFERENCED_PARAMETER(irp);
            // 判斷當前中斷級
            if(wd_get_cur_irql() > wd_irql_passive)
            {
            wd_printf0("fsctl_comp:into quque!!!rn");
            // 如果在passive更低的中斷級別,必須插入延遲隊列中運行
            wd_work_queue((wd_work_item *)context,wd_work_delay);
            }
            else
            {
            // 否則可以直接執行
            wd_printf0("fsctl_comp:run directly!!!rn");
            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的綁定.

            希望你沒有被弄昏頭:),我們下回再分解.



            8 終于綁定了Volume,讀操作的捕獲與分析

            上文已經講到綁定Volume之前的關鍵操作.我們一路逢山開路,逢水架橋,相信你從中也學到了驅動開發的基本方法.以后的工作,無非靈活運用這些方法而已.前邊已經舉出過綁定FS CDO的操作.那么現在綁定Volume,無非照貓畫虎,而以后的教程中,我也不會逐一詳盡的列舉出細節的代碼了.

            但是綁定Volume的過程中,還是有些地方需要稍微注意:

            1.獲得Storage Device Object的問題.前邊已經說過,為了得到Vbp,我要先得到Storage Device Object,方法如下:

            wd_irpsp *irpsp = wd_cur_io_stack(irp);
            wd_dev *storage_dev = wd_irpsp_mount_storage(irpsp);

            這是在Irp完成之前,這樣調用是對的.但是到完成函數中,情況就不同了.因為這個irpsp下保存的storage_dev可能被修改掉(這并非我自己調試的結果,而是閱讀sfilter代碼中的注釋而得到的信息).既然有這個可能,我們只好先把storage_dev這個東西保存下來.實際上,可以在Device擴展增加一個項目storage_dev.在irp完成之前,生成我要綁定的設備(只是不綁定),并把這個設備指針傳入完成函數上下文.

            這樣在完成函數中,我就能得到這個storage_dev,最終找到Volmue設備的指針,此時進行綁定就可以了.

            2.綁定的過程,未必一次就能成功.因為Volmue設備的Initilize標記被清除之前,我的綁定是不能成功的.對這種情況,我抄襲了sfilter中的方法,就是延時500毫秒,然后再嘗試.一共嘗試8次.

            我包裝了一個函數用來延時:

            _inline wd_void wd_delay_milli_se(wd_ulong milli_sec)
            {
            wd_llong delay = milli_sec*(-10)*1000;
            wd_lgint interval;
            interval.QuadPart = delay;
            KeDelayExecutionThread(KernelMode,FALSE,&interval);
            }

            這個函數的參數是毫秒,但是我并不清楚有多小的精度.

            其他的就不說了,祝愿你的運氣足夠的好.現在我們處理IRP_MJ_READ,如果你已經綁定了Volume,那么顯然,發送給Volume的請求就會先發送給你.處理IRP_MJ_READ,能捕獲文件的讀操作.

            進入你的my_disp_read()函數(假設你注冊了這個函數來處理IRP_MJ_READ,請見前面關于分發函數的講述),首先判斷這個Dev是不是綁定Volume的設備.如果是,那么就是一個讀文件的操作.

            如何判斷?記得我們先綁定Volume的時候,在我們的設備擴展中設置了storage_dev,如果不是(比如是FS CDO,我們沒設置過),那么這么判斷即可:

            if(is_my_dev(dev))
            {
            my_dev_ext *ext = (my_dev_ext *)wd_dev_ext(dev);
            if(ext->storage_dev)
            {
            // ... 到這里說明是對文件的讀操作
            }
            }

            其他的情況不需要捕獲,請直接傳遞到下層.

            讀請求的IRP情況非常復雜,請有足夠的心理準備.并不要過于依賴幫助,最好的辦法就是自己打印IRP的各個細節,親自查看文件讀操作的完成過程.而我現在所說的東西,換另一我未嘗試過的版本的windows是否還正確,我也無法下斷言.

            不過基本的東西是不會變的.

            首先關心被讀寫的文件.IRP下有一個FileObject指針.這個東西指向一個文件對象.你可以得到文件對象的名字,這個名字是沒有盤符的文件全路徑.有人要問那么盤符如何獲得?因為你已經知道了Volume,前邊已經說過盤符不過是Volume的符號連接名,那么想要真正的全路徑問題應該也不大了.

            我現在偷一個懶,我現在只打印沒有盤符的路徑名.先寫一個函數,從IRP得到FileObject.

            _inline wd_file *wd_irp_file(wd_irpsp *irpsp)
            {
            return irpsp->FileObject;
            }

            然后寫一個函數來獲得文件名.這個函數參考的是FileMon的代碼.

            wd_void wd_file_get_name(in wd_file *file,
            in out wd_ustr *name)
            {
            if( file->FileName.Buffer &&
            !(file->Flags & FO_DIRECT_DEVICE_OPEN) )
            RtlCopyUnicodeString(name,&file->FileName);
            }

            接下來有必要得到讀文件的偏移量.和vxd的文件系統驅動不同,2000下文件系統得到的偏移量似乎都是從文件起始位置開始計算的.偏移量是一個LARGE_INTEGER.因為現在確實有些文件已經超過了長整型所能表示的大小.

            以下函數用來得到偏移量.wd_lgint是經過重定義的LARGE_INTEGER.

            _inline wd_lgint wd_irp_read_offset(wd_irpsp *irpsp)
            {
            return irpsp->Parameters.Read.ByteOffset;
            }

            注意以上的參數不是irp.是當前IO_STACK_LOCATION,也就是我的wd_irpsp.前面已經講述過如何獲取當前irpsp.

            此外我還希望能得到我所讀到的數據.這要注意,我們捕獲這個請求的時候,這個請求還沒有完成.既然沒有完成,當然無數據可讀.如果要獲取,那就設置完成函數,在完成函數中完成請求.

            完成Irp的時候忽略還是拷貝當前IO_STACK_LOCATION,返回什么STATUS,以及完成函數中如何結束Irp,是不那么容易搞清楚的一件事情.我想做個總結如下:

            1.如果對irp完成之后的事情無興趣,直接忽略當前IO_STACK_LOCATION,(對我的程序來說,調用wd_ship_cur_io_stack),然后向下傳遞請求,返回wd_irp_call()所返回的狀態.

            2.不但對irp完成之后的事情無興趣,而且我不打算繼續傳遞,打算立刻返回成功或失敗.那么我不用忽略或者拷貝當前IO_STACK_LOCATION,填寫參數后調用IoCompleteRequest,并返回我想返回的結果.

            3.如果對irp完成之后的事情有興趣,并打算在完成函數中處理,應該首先拷貝當前IO_STACK_LOCATION(wd_copy_cur_io_stack()),然后指定完成函數,并返回wd_irp_call()所返回的status.完成函數中,不需要調用IoCompleteRequest!直接返回Irp的當前狀態即可.

            4.同3的情況,有時候,會把任務塞入系統工作者線程或者希望在另外的線程中去完成Irp,那么完成函數中應該返回wd_stat_more_processing,此時完成Irp的時候應該調用IoCompleteRequest.另一種類似的情況是在dispatch函數中等待完成函數中設置事件,那么完成函數返回wd_stat_more_processing,dispatch函數在等待結束后調用IoCompleteRequest.

            前邊已經提到過設備的DO_BUFFERED_IO,DO_DIRECT_IO這兩個標記.情況是3種:要么是兩個標記中其中一個,要么是一個都沒有.Volume設備出現DO_BUFFERED的情況幾乎沒有,我碰到的都是一個標記都沒有.DO_DIRECT_IO表示數據應該返回到Irp->MdlAddress所指向的MDL所指向的內存.在無標記的情況下,表明數據讀好,請返回到Irp->UseBuffer中即可.

            UseBuffer是一個只在當前線程上下文才有效的地址.如果你打算按這個地址獲得數據,你最好在當前線程上下文中.完成函數與my_disp_read并非同一個線程.所以在完成函數中按這個地址去獲取數據是不對的.如何回到當前線程?我采用簡單的辦法.在my_disp_read中設置一個事件,調用wd_irp_call(即ddk中的IoCallDriver)之后開始等待這個事件.而在完成函數中設置這個事件.這樣等待結束的時候,剛好Irp已經完成,我也回到了我的my_disp_read原來的線程.

            wd_stat my_disp_read(in wd_dev *dev,in wd_pirp irp)
            {
            my_dev_ext *ext;
            wd_dev *attached_dev;
            wd_irpsp *irpsp = wd_cur_io_stack(irp);
            wd_stat status;
            wd_file *file = wd_irp_file(irpsp);
            wd_lgint offset = wd_irp_read_offset(irpsp);
            wd_size length = wd_irp_read_length(irpsp);
            wd_wchar name_buf[512];
            wd_ustr name;
            wd_event event;

            // 檢查是否我的設備
            if(!is_my_dev(dev))
            return wd_irp_failed(irp,wd_stat_invalid_dev_req);

            ext = (wdff_dev_ext *)wd_dev_ext(dev);
            attached_dev = wdff_dev_attached(dev);

            // 到這里判斷得到這是對一個被綁定了的卷的讀操作
            if(ext->storage_dev == NULL)
            {
            wd_skip_io_stack(irp);
            return wd_irp_call(attached_dev,irp);
            }

            // 到了這里,確認是對文件的讀
            wd_ustr_init_em(&name,name_buf,512);
            if(file)
            wd_file_get_name((wd_void *)file,&name);
            else
            {
            wd_skip_io_stack(irp);
            return wd_irp_call(attached_dev,irp);
            }

            wd_printf1("xxx irp flag = %xrn",wd_irp_flags(irp));
            wd_printf1("xxx file read: %wZ rn",&name);
            wd_printf1("xxx read offset = %ld ",offset);
            wd_printf1("xxx read length = %ldrn",length);

            // 以上我已經打印了讀請求的相關參數,下面我希望得到讀出的內容
            wd_event_init(&event);
            // 先拷貝當前io_stack,然后再指定完成例程

            wd_copy_io_stack(irp);
            wd_irp_set_comp(irp,my_disp_read_comp,&event);

            // 對實際設備呼叫irp
            status = wd_irp_call(attached_dev,irp);
            if(status == wd_stat_pending)
            wd_event_wait(&event);

            wd_printf1("test read end:status = %x rn",status);
            // 如果此時status = 0,那么內容應該就在Irp->UserBuffer中,請自己打印...
            wd_printf1("test read end:read length = %ldrn",wd_irp_infor(irp));

            return wd_irp_over(irp);
            }

            然后是my_disp_read_comp的內容,可以看見只是簡單的設置事件,然后返回wd_stat_more_processing.

            wd_stat my_disp_read_comp(in wd_dev *dev,
            in wd_irp *irp,
            in wd_void *context)
            {
            wd_event * event=
            (wd_event *)context;
            UNREFERENCED_PARAMETER(dev);
            UNREFERENCED_PARAMETER(irp);
            wd_event_set(event);
            return wd_stat_more_processing;
            }

            盡管已經寫了很多,盡管我們得到了讀過程的所有參數和結果,我們依然不知道如果自己寫一個文件系統,該如何完成讀請求,或者過濾驅動中,如何修改讀請求等等.我們下一節繼續討論讀操作.



            9 完成讀操作

            除非是一個完整的文件系統,完成讀操作似乎是不必要的。過濾驅動一般只需要把請求交給下層的實際文件系統來完成。但是有時候比如加解密操作,我希望從下層讀到數據,解密后,我自己來完成這一IRP請求。

            這里要談到IRP的minor function code.以前已經討論到如果major function code 是IRP_MJ_READ則是Read請求。實際上有些主功能號下面有一些子功能號,如果是IRP_MJ_READ,檢查其MINOR,應該有幾種情況:IRP_MN_NORMAL,IRP_MN_MDL,IRP_MN_MDL|IRP_COMPLETE(這個其實就是IRP_MN_MDL_COMPLETE).還有其他幾種情況,資料上有解釋,但是我沒自己調試過,也就不胡說了。只拿自己調試過的幾種情況來說說。

            先寫個函數,得到次功能號。

            _inline wd_uchar wd_irpsp_minor(wd_io_stack *irpsp)
            {
            return irpsp->MinorFunction;
            }

            enum {
            wd_mn_mdl = IRP_MN_MDL,
            wd_mn_mdl_comp = IRP_MN_MDL_COMPLETE,
            wd_mn_normal = IRP_MN_NORMAL
            };

            希望你喜歡我重新定義過的次功能號碼。

            wd_mn_normal的情況完全與上一節同(讀者可能要罵了,上一節明明說就是這樣的,結果還有其他的情況,那不是騙人么?但是微軟的東西例外就是多,我也實在拿他沒辦法... ).

            注意如上節所敘述,wd_mn_normal的情況,既有可能是在Irp->MdlAddress中返回數據,也可能是在Irp->UserBuffer中返回數據,這個取決于Device的標志.

            但是如果次功能號為wd_mn_mdl則完全不是這個意思。這種irp一進來看數據,就赫然發現Irp->MdlAddress和Irp->UserBuffer都為空。那你得到數據后把數據往哪里拷貝呢?

            wd_mn_mdl的意思是請自己分配一個mdl,然后把mdl指向你的數據所在的空間,然后返回給上層。自然mdl是要釋放的,換句話說事業使用完畢要歸還,所以又有wd_mn_mdl_comp,意思是一個mdl已經使用完畢,可以釋放了。

            mdl用于描述內存的位置。據說和NDIS_BUFFER用的是同一個結構。這里不深究,我寫一些函數來分配和釋放mdl,并把mdl指向內存位置或者得到mdl所指向的內存:

            // 釋放mdl
            _inline wd_void wd_mdl_free(wd_mdl *mdl)
            {
            IoFreeMdl(mdl);
            }

            // 這個這個東西分配mdl,緩沖必須是非分頁的。可以在dispatch level跑。
            _inline wd_mdl *wd_mdl_alloc(wd_void* buf,
            wd_ulong length)
            {
            wd_mdl * pmdl = IoAllocateMdl(buf,length,wd_false,wd_false,NULL);
            if(pmdl == NULL)
            return NULL;
            MmBuildMdlForNonPagedPool(pmdl);
            return pmdl;
            }

            // 這個函數分配一個mdl,并且帶有一片內存
            _inline wd_mdl *wd_mdl_malloc(wd_ulong length)
            {
            wd_mdl *mdl;
            wd_void *point = wd_malloc(wd_false,length);
            if(point == NULL)
            return NULL;
            mdl = wd_mdl_alloc(point,length);
            if(mdl == NULL)
            {
            wd_free(point);
            return NULL;
            }
            return mdl;
            }

            // 這個函數釋放mdl并釋放mdl所帶的內存。
            _inline wd_void wd_mdl_mfree(wd_mdl *mdl)
            {
            wd_void *point = wd_mdl_vaddr(mdl);
            wd_mdl_free(mdl);
            wd_free(point);
            }

            // 得到地址。如果想往一個MdlAddress里邊拷貝數據 ...
            _inline wd_void *wd_mdl_vaddr(wd_mdl *mdl)
            {
            return MmGetSystemAddressForMdlSafe(mdl,NormalPagePriority);
            }

            另外我通過這兩個函數來設置和獲取Irp上的mdl.

            _inline wd_mdl *wd_irp_mdl(wd_irp *irp)
            {
            return irp->MdlAddress;
            }

            _inline wd_void wd_irp_mdl_set(wd_irp *irp,
            wd_mdl *mdl)
            {
            irp->MdlAddress = mdl;
            }

            一個函數獲得UserBuffer.
            _inline wd_void * wd_irp_user_buf(wd_irp *irp)
            {
            return irp->UserBuffer;
            }

            要完成請求還有一個問題。就是irp->IoStatus.Information.在這里你必須填上實際讀取得到的字節數字。不然上層不知道有多少數據返回。這個數字不一定與你的請求的長度等同(其實我認為幾乎只要是成功,就應該都是等同的,唯一的例外是讀取到文件結束的地方,長度不夠了的情況)。我用下邊兩個函數來獲取和設置這個數值:

            _inline wd_void wd_irp_infor_set(wd_irp *irp,
            wd_ulong infor)
            {
            irp->IoStatus.Information = infor;
            }

            _inline wd_ulong wd_irp_infor(wd_irp *irp)
            {
            return irp->IoStatus.Information;
            }


            也許你都煩了,但是還有事情要做。作為讀文件的情況,如果你是自己完成請求,不能忘記移動一下文件指針。否則操作系統會不知道文件指針移動了而反復讀同一個地方永遠找不到文件尾,我碰到過這樣的情況。

            一般是這樣的,如果文件讀取失敗,請保持原來的文件指針位置不要變。如果文件讀取成功,請把文件指針指到“讀請求偏移量+成功讀取長度”的位置。

            這個所謂的指針是指Irp->FileObject->CurrentByteOffset.

            我跟蹤過正常的windows文件系統的讀行為,我認為并不一定是向我上邊說的這樣做。情況很復雜,有時動,有時不動(說復雜當然是因為我不理解),但是按我上邊說的方法來完成,我還沒有發現過錯誤。

            我用以下函數來設置和獲取這個。

            _inline wd_void wd_file_offset_add(wd_file *file,wd_ulong len)
            {
            file->CurrentByteOffset.QuadPart += len;
            }

            _inline wd_void wd_file_offset_set(wd_file *file,wd_lgint offset)
            {
            file->CurrentByteOffset = offset;
            }

            _inline wd_lgint wd_file_offset(wd_file *file)
            {
            return file->CurrentByteOffset;
            }


            現在看看怎么完成這些請求,假設我已經有數據了。現在假設本設備緩沖標記為0,即正常情況采用Irp->UserBuffer返回數據. 這些當然都是在my_disp_read中或者是其他想完成這個irp的地方做的(希望你還記得我們是如何來到這里),假設其他必要的判斷都已經做了:

            wd_irpsp *irpsp = wd_irp_cur_io_stack(irp);
            switch(wd_irpsp_minor(irpsp))
            {
            // 我先保留文件的偏移位置
            case wd_mn_normal:
            {
            wd_void *point = wd_irp_user_buf(irp);

            // 如果有數據,就往point ...里邊拷貝 ...

            wd_irp_infor_set(irp,length);
            wd_irp_status_set(irp,wd_suc);
            wd_file_offset_set(wd_irp_file(irp),offset+length);

            return wd_irp_over(irp);
            }
            case wd_mn_mdl:
            {
            wd_void *mdl = wd_mdl_malloc(length); // 情況比上邊的復雜,請先分配mdl
            if(mdl == NULL)
            {
            // ... 返回資源不足 ...
            }

            wd_irp_mdl_set(irp,mdl);
            wd_irp_infor_set(irp,length);
            wd_irp_status_set(irp,wd_suc);

            wd_file_offset_set(wd_irp_file(irp),offset+length);

            return wd_irp_over(irp);

            }
            case wd_mn_mdl_comp:
            {
            // 沒有其他任務,就是釋放mdl
            wd_mdl_mfree(wd_irp_mdl(irp));
            wd_irp_mdl_set(irp,0);
            wd_irp_infor_set(irp,0);
            wd_irp_status_set(irp,wd_status_suc);

            return wd_irp_over(irp);
            }
            default:
            {
            // 我認為其他的情況不過濾比較簡單 ...
            }
            }

            重要提醒:wd_mn_mdl的情況,需要分配一個mdl,并且這個mdl所帶有的內存是有一定長度的,這個長度必須與后來的irp->IoStatus.Information相同!似乎上層并不以irp->IoStatus.Information返回的長度為準。比如明明只讀了50個字節,但是你返回了一個mdl指向內存長度為60字節,則操作系統則認為已經讀了60個字節!這非常糟糕。

            最后提一下文件是如何結尾的。如果到某一處,返回成功,但是實際讀取到的數據沒有請求的數據長,這時還是返回wd_status_suc(STATUS_SUCCESS),但是此后操作系統會馬上發irp來讀最后一個位置,此時返回長度為0,返回狀態STATUS_FILE_END即可。



            10 自己發送Irp完成讀請求

            關于這個有一篇文檔解釋得很詳細,不過我認為示例的代碼有點太簡略了,這篇文檔在IFS所附帶的OSR文檔中,請自己尋找。

            為何要自己發送Irp?在一個文件過濾驅動中,如果你打算讀寫文件,可以試用ZwReadFile.但是這有一些問題。Zw系列的Native API使用句柄。而句柄是有線程環境限制的。此外也有中斷級別的限制。再說,Zw系列函數來讀寫文件,最終還是要發出Irp,又會被自己的過濾驅動捕獲到。結果帶來判斷上的困難。對資源也是浪費。那么最應該的辦法是什么呢?當然是直接對卷設備發Irp了。

            但是Irp是非常復雜的數據結構,而且又被微軟所構造的很多未空開的部件所處理。所以自己發irp并不是一件簡單的事情。

            比較萬能的方法是IoAllocateIrp,分配后自己逐個填寫。問題是細節實在太多,很多無文檔可尋。有興趣的應該看看我上邊所提及的那篇文章“Rolling Your Own”。

            有意思的是這篇文章后來提到了捷徑,就是利用三個函數:

            IoBuildAsynchronousFsdRequest(...)
            IoBuildSynchronousFsdRequest(...)
            IoBuildDeviceIoControlRequest(...)

            于是我參考了他這方面的示例代碼,發現運行良好,程序也很簡單。建議怕深入研究的選手就可以使用我下邊提供的方法了。

            首先的建議是使用IoBuildAsynchronousFsdRequest(),而不要使用同步的那個。使用異步的Irp使irp和線程無關。而你的過濾驅動一般很難把握當前線程(如果你開一個系統線程來專門讀取文件那例外)。此時,你可以輕松的在Irp的完成函數中刪除你分配過的Irp,避免去追究和線程相關的事情。

            但是這個方法有局限性。文檔指出,這個方法僅僅能用于IRP_MJ_READ,IRP_MJ_WRITE,IRP_MJ_FLUSH_BUFFERS,和IRP_MJ_SHUTDOWN.剛好我這里僅僅要求完成文件讀。

            用Irp完成文件讀需要一個FILE_OBJECT.FileObject是比Zw系列所用的句柄更好的東西。因為這個FileObject是和線程無關的。你可以放心的在未知的線程中使用他。

            自己要獲得一個FILE_OBJECT必須自己發送IRP_MJ_CREATE的IRP.這又不是一件輕松的事情。不過我跳過了這個問題。因為我是文件系統過濾驅動,所以我從上面發來的IRP中得到FILE_OBJECT,然后再構造自己的IRP使用這個FILE_OBJECT,我發現運行很好。

            但是又出現一個問題,如果IRP的irp->Flags中有IRP_PAGING(或者說是Cache管理器所發來的IRP)標記,則其FileObject我獲得并使用后,老是返回錯誤。閱讀網上的經驗表明,帶有IRP_PAGINGE的FileObject不可以使用.于是我避免使用這時的FileObject.我總是使用不帶IRP_PAGING的Irp(認為是用戶程序發來的讀請求)的FileObject。

            好,現在廢話很多了,現在來看看構造irp的代碼:

            _inline wd_irp *wd_irp_fsd_read_alloc(wd_dev *dev,
            wd_void *buf,
            wd_ulong length,
            wd_lgint *offset,
            wd_io_stat_block *io_stat)
            {
            return IoBuildAsynchronousFsdRequest(IRP_MJ_READ,dev,
            buf,length,
            offset,
            io_stat);
            }

            io_stat我不知道是做何用,我一般填寫NULL.類型是PIO_STATUS_BLOCK.buf則是緩沖。在Irp中被用做UserBuffer接收數據。offset是這次讀的偏移量。

            以上函數構造一個讀irp.請注意,此時您還沒有設置FileObject.實際上我是這樣發出請求的:

            irp = wd_irp_fsd_read_alloc(dev,buf,len,&start,NULL);
            if(irp == NULL)
            return;
            irpsp = wd_irp_next_sp(irp);
            wd_irpsp_file_set(irpsp,file);
            wd_irp_comp(irp,my_req_comp,context);

            請注意wd_irp_next_sp,我是得到了這個irp的下一個IO_STACK_LOCATION,然后我設置了FileObject.接下來應該設置了完成后,我就可以發送請求了!請求發送完畢后,一旦系統完成請求就會調用你的my_req_comp.

            再看看my_req_comp如何收場:

            wd_stat my_req_comp(in wd_dev *dev,
            in wd_irp *irp,
            in wd_void *context)
            {

            // 請注意,無論成功還是失敗,我都得在這里徹底銷毀irp
            wd_irp_send_by_myself_free(irp);

            // 返回這個,僅僅是因為irp我已經銷毀,不想讓系統再次銷毀它而已。
            return wd_stat_more_processing;
            }

            wd_stat_more_prcessing就是STATUS_MORE_PROCESSING_REQUIRED。之所以返回這個,是因為我已經把irp給刪除了。我不想windows系統再對這個irp做什么。所以干脆說我還要繼續處理,這樣避免了io管理器再去動用這個irp的可能。

            最后是那個irp刪除函數的代碼:

            _inline wd_void wd_irp_send_by_myself_free(wd_irp *irp)
            {
            if (irp->MdlAddress)
            {
            MmUnmapLockedPages(MmGetSystemAddressForMdl(irp->MdlAddress),
            irp->MdlAddress);
            MmUnlockPages(irp->MdlAddress);
            IoFreeMdl(irp->MdlAddress);
            }
            IoFreeIrp(irp);
            };

            因為我自己沒有分配過MdlAddress下去過。所以如果下邊返回了MDL,我得附帶清理它。

            好了,祝愿你心情愉快。如果你何我一樣懶惰的話,不妨就用上邊的簡單方法自己發irp來讀文件了。



            11.文件和目錄的生成打開,關閉與刪除

            我們已經分析了讀,寫與讀類似。文件系統還有其他的操作。比如文件或目錄的打開(打開已經存在的或者創建新的),關閉。文件或目錄的移動,刪除。

            實際上FILE_OBJECT并不僅僅指文件對象。在windows文件系統中,目錄和文件都是用FileObject來抽象的。這里產生一個問題,對于一個已經有的FileObject,我如何判斷這是一個目錄還是一個文件呢?

            對于一個已經存在的FileObject,我沒有找到除了發送IRP來向卷設備詢問這個FileObject的信息之外更好的辦法。自己發送IRP很麻煩。不是我很樂意做的那種事情。但是FileObject都是在CreateFile的時候誕生的。在誕生的過程中,確實有機會得到這個即將誕生的FileObject,是一個文件還是一個目錄。

            Create的時候,獲得當前IO_STACK_LOCATION,假設為irpsp,那么irpsp->Parameters.Create的結構為:

            struct {
            PIO_SECURITY_CONTEXT SecurityContext;
            ULONG Options;
            USHORT FileAttributes;
            USHORT ShareAccess;
            ULONG EaLength;
            };

            這個結構中的參數是與CreateFile這個api中的參數對應的,請自己研究。我先寫一些函數包裝來方便讀取irpsp.

            _inline wd_ulong wd_irpsp_file_opts(wd_irpsp *irpsp)
            {
            return irpsp->Parameters.Create.Options;
            }

            _inline wd_ushort wd_irpsp_file_attrs(wd_irpsp *irpsp)
            {
            return irpsp->Parameters.Create.FileAttributes;
            }

            enum {wd_file_opt_dir = FILE_DIRECTORY_FILE};
            enum {wd_file_attr_dir = FILE_ATTRIBUTE_DIRECTORY};

            然后我們搞清上邊Options和FileAttributes的意思。是不是Options里邊有FILE_DIRECTORY_FILE標記就表示這是一個目錄?實際上,CreateOpen是一種嘗試性的動作。無論如何,我們只有當CreateOpen成功的時候,判斷FileObject才有意義。否則是空談。

            成功有兩種可能,一是已經打開了原有的文件或者目錄,另一種是新建立了文件或者目錄。Options里邊帶有FILE_DIRECTORY_FILE表示打開或者生成的對象是一個目錄。那么,如果在Create的完成函數中,證實生成或者打開是成功的,那么返回得到的FILE_OBJECT,確實應該是一個目錄。

            當我經常要使用我過濾時得到的文件或者目錄對象的時候,我一般在Create成功的的時候捕獲他們,并把他們記錄在一個“集合”中。這時你得寫一個用來表示“集合”的數據結構。你可以用鏈表或者數組,只是注意保證多線程安全性。因為Create的時候已經得到了屬性表示FileObject是否是目錄,你就沒有必要再發送IRP來詢問FileObject的Attribute了。

            對了上邊有FileAttributes。但是這個東西并不可靠。因為在生成或者打開的時候,你只需要設置Options。我認為這個字段并無法說明你打開的文件對象是目錄。

            這你需要設置一下Create的完成函數。如果設置這里不再重復,請參考上邊對文件讀操作。

            wd_stat my_create_comp(in wd_dev *dev,
            in wd_irp *irp,
            in wd_void *context)
            {
            wd_irpsp *irpsp = wd_irp_cur_sp(irp);
            wd_file *file = wd_irpsp_file(irpsp);

            UNREFERENCED_PARAMETER(dev);

            if(wd_suc(wd_irp_status(irp))
            {
            // 如果成功了,把這個FileObject記錄到集合里,這是一個
            // 剛剛打開或者生成的目錄
            if(file &&
            (wd_irpsp_file_opts(irpsp) & wd_file_opt_dir))
            add_obj_to_set(file);
            }
            return wd_irp_status(irp);
            }

            這里順便解釋一下UNREFERENCED_PARAMETER宏。我曾經不理解這個宏的意思。其實就是因為本函數傳入了三個參數,這些參數你未必會用到。如果你不用的話,大家知道c編譯器會發出一條警告。一般認為驅動應該去掉所有的警告,所以用了這個宏來“使用”一下沒有用到過的參數。你完全可以不用他們。

            現在所有的目錄都被你記錄。那么得到一個FileObject的時候,判斷一下這個FileObject在不在你的集合里,如果在,就說明是目錄,反之是文件。

            當這個FileObject被關閉的時候你應該把它從你的集合中刪除。你可以捕獲Close的IRP來做這個。記得本教程很早以前,我們已經安裝過my_close函數的來處理IRP(請回憶或者翻閱第3節的內容),那么很簡單了,在該函數中從你的集合中刪除該FileObject即可。作為保險的做法,應該觀察一下關閉是否成功。如果成功的話,再進行你的從集合中刪除元素工作。

            因為判斷FileObject是文件還是目錄的問題,我們已經見識了文件的打開和關閉工作?,F在看一下文件是如何被刪除的。

            刪除的操作,第一步是打開文件,打開文件的時候必須設置為可以刪除。如果打開失敗,則直接導致無法刪除文件。第二步設置文件屬性為用于刪除,第三步關閉文件即可。關閉的時候,文件被系統刪除。

            不過請注意這里的“刪除”并非把文件刪除到回收站。如果要測試,你必須按住shift徹底刪除文件。文件刪除到回收站只是一種改名操作。改名操作我們留到以后再討論。

            第一步是打開文件,我應該可以在文件被打開的時候,捕獲到的irpsp的參數,記得前邊的參數結構,中間有:

            PIO_SECURITY_CONTEXT SecurityContext;

            相關的結構如下:

            typedef struct _IO_SECURITY_CONTEXT {
            PSECURITY_QUALITY_OF_SERVICE SecurityQos;
            PACCESS_STATE AccessState;
            ACCESS_MASK DesiredAccess;
            ULONG FullCreateOptions;
            } IO_SECURITY_CONTEXT, *PIO_SECURITY_CONTEXT;

            注意其中的DesiredAccess,其中必須有DELETE標記,才可以刪除文件。

            第二步是設置為”關閉時刪除”。這是通過發送一個IRP(Set Information)來設置的。捕獲主功能碼為IRP_MJ_SET_INFORMATION的IRP后:
            首先,IrpSp->Parameters.SetFile.FileInformationClass應該為FileDispositionInformation。

            然后,Irp->AssociatedIrp.SystemBuffer指向一個如下的結構:

            typedef struct _FILE_DISPOSITION_INFORMATION {
            BOOLEAN DeleteFile;
            } FILE_DISPOSITION_INFORMATION;

            如果DeleteFile為TRUE,那么這是一個刪除文件的操作。文件將在這個FileObject Close的時候被刪除。

            以上的我都未實際調試,也不再提供示例的代碼。有興趣的讀者請自己完成。

            posted on 2009-12-24 20:52 小默 閱讀(592) 評論(0)  編輯 收藏 引用 所屬分類: Windows

            導航

            統計

            留言簿(13)

            隨筆分類(287)

            隨筆檔案(289)

            漏洞

            搜索

            積分與排名

            最新評論

            閱讀排行榜

            久久精品国产亚洲7777| 久久人人爽人爽人人爽av| 人妻久久久一区二区三区| 大香网伊人久久综合网2020| 国产精品久久久久久久app| 91久久香蕉国产熟女线看| 日韩精品久久久久久久电影蜜臀| 精品国产乱码久久久久久郑州公司| 香蕉99久久国产综合精品宅男自 | 久久久这里只有精品加勒比| 国产亚洲精品美女久久久| 久久精品无码av| 久久精品国产精品亚洲毛片| 性做久久久久久久| 久久久久人妻一区二区三区| 香蕉久久久久久狠狠色| 久久本道伊人久久| 国产精品永久久久久久久久久| 久久久久亚洲AV无码观看| 欧美激情精品久久久久久久| 2021精品国产综合久久| 久久99精品久久久久久野外| 欧美色综合久久久久久 | 久久久亚洲精品蜜桃臀| 九九精品99久久久香蕉| 国产成人精品综合久久久久| 久久99精品国产麻豆宅宅| 久久91精品综合国产首页| 狠狠色丁香婷婷久久综合不卡| 久久天天躁狠狠躁夜夜不卡| 国产香蕉久久精品综合网| 久久亚洲天堂| 欧美粉嫩小泬久久久久久久| 久久久久亚洲av成人无码电影| 国产精品成人久久久久久久| 久久综合久久综合久久综合| 久久天天日天天操综合伊人av| 国产综合成人久久大片91| 久久久这里有精品中文字幕| 久久精品国产亚洲Aⅴ香蕉 | 狠狠久久综合|