Windows核心编程 第五章 作业(上)
第5章 作 業
????通常,必須將一組進程當作單個實體來處理。例如,當讓 Microsoft Developer Studio為你創建一個應用程序項目時,它會生成 C l . e x e,C l . e x e則必須生成其他的進程(比如編譯器的各個函數傳遞) 。如果用戶想要永遠停止該應用程序的創建,那么 Developer Studio必須能夠終止C l . e x e和它的所有子進程的運行。在 Wi n d o w s中解決這個簡單(和常見的)的問題是極其困難的,因為Wi n d o w s并不維護進程之間的父/子關系。即使父進程已經終止運行,子進程仍然會繼續運行。
????當設計一個服務器時,也必須將一組進程作為單個進程組來處理。例如,客戶機可能要求服務器執行一個應用程序(這可以生成它自己的子應用程序) ,并給客戶機返回其結果。由于可能有許多客戶機與該服務器相連接,如果服務器能夠限制客戶機的要求,即用什么手段來防止任何一個客戶機壟斷它的所有資源,那么這是非常有用的。這些限制包括:可以分配給客戶機請求的最大C P U時間,最小和最大的工作區的大小,防止客戶機的應用程序關閉計算機,以及安全性限制等。
????Microsoft Windoss 2000提供了一個新的作業內核對象,使你能夠將進程組合在一起,并且創建一個“沙框” ,以便限制進程能夠進行的操作。最好將作業對象視為一個進程的容器。但是,創建包含單個進程的作業是有用的,因為這樣一來,就可以對該進程加上通常情況下不能加的限制。
下面的S t a r t R e s t r i c t e d P r o c e s s函數,將一個進程放入一個作業,以限制該進程進行某些操作的能力。
Windows 98 Windows 98不支持作業的操作。
TIP: 先說一下運行上面的代碼的時候我遇到的問題,上面的代碼是書上的代碼(略微改動),我照著寫了下,比較簡單很好理解,但是遇到了幾個問題:
1.CreateProcess這個函數,如果你在vs2012上直接按照書上那么寫直接編譯不過去了因為vs2012上通常CreateProcess是CreateProcessW(現在幾乎都是默認寬字節了),如果是CreateProcessW那么第二個cmdline參數就不能直接寫常量,而CreateProcessA可以寫,原因是*A最后會轉換成*W函數去執行,而這個轉換過程會定義一個變量,這樣即使我們傳遞錯的參數(常量),在*A上也不會崩潰,而在*W上會,又因為默認是*W所以書上的代碼就直接崩潰了,我上面的那個代碼是改過的了。
?
雖然看上去*A的也不能用常量,但是用常量是可以的(上面解釋了)
2.CreateProcessA這個函數我調用notepad的時候發現了一個奇怪的現象,就是Getlasterror顯示的是(),調用cmd也是,但是返回值是teue并且我們的東西可以正常被啟動,但是直接調用QQ就沒事了。
?
?
3.第三個問題是,按照書上的代碼,AssignProcessToJobObject這個函數執行會失敗,返回的結果是拒絕訪問,期初我第一反應就是可能需要管理員權限,于是就提高了自己程序的權限:
?
? ? 然并卵,然后就是找了好久,最后找到的解決方案,是在CreateProcess函數里面增加一個參數
?
? ? 這個參數的MSDN是這么描述的:
?
? ? 在實際調試的時候我發現,如果不加這個參數,首先在把進程放到作業里的時候會失敗并且顯示權限不足,同時在放失敗之后我們CreateProcess創建的進程還是有的(此時主線程沒有起來,因為創建的時候掛起了主線程),然后后續在激活主線程,這個創建的程序就跑起來了,但是如果增加了這個參數的話,如果我們因為某種原因,在把進程加到作業里失敗之后,這個進程將會被關閉掉。之后再激活主線程也就沒意義了。為了模擬出來這種場景,我是直接在創建作業的時候把作業的最大進程數寫成0,這樣第一個進程就會加入失敗。
?然后繼續。。。解釋一下S t a r t R e s t r i c t e d P r o c e s s函數是如何工作的。首先,調用下面的代碼,創建一個新作業內核對象:
?
? ?與所有的內核對象一樣,它的第一個參數將安全信息與新作業對象關聯起來,并且告訴系統,是否想要使返回的句柄成為可繼承的句柄。最后一個參數用于給作業對象命名,使它可以供另一個進程通過下面所示的O p e n J o b O b j e c t函數進行訪問。
?
? ?與平常一樣,如果知道你將不再需要訪問代碼中的作業對象,那么就必須通過調用C l o s e H a n d l e來關閉它的句柄??梢栽?S t a r t R e s t r i c t e d P r o c e s s函數的結尾處看到這個代碼的情況。應該知道,關閉作業對象并不會迫使作業中的所有進程終止運行。該作業對象實際上做上了刪除標記,只有當作業中的所有進程全部終止運行之后,該作業對象才被自動撤消。
????注意,關閉作業的句柄后,盡管該作業仍然存在,但是該作業將無法被所有進程訪問。請看下面的代碼:
? ?
5.1 對作業進程的限制
進程創建后,通常需要設置一個沙框(設置一些限制) ,以便限制作業中的進程能夠進行的操作??梢越o一個作業加上若干不同類型的限制:
? 基本限制和擴展基本限制,用于防止作業中的進程壟斷系統的資源。
? 基本的U I限制,用于防止作業中的進程改變用戶界面。
? 安全性限制,用于防止作業中的進程訪問保密資源(文件、注冊表子關鍵字等) 。
通過調用下面的代碼,可以給作業加上各種限制:
?
? ?第一個參數用于標識要限制的作業。第二個參數是個枚舉類型,用于指明要使用的限制類型。第三個參數是包含限制設置值的數據結構的地址,第四個參數用于指明該結構的大小(用于確定版本) 。表5 - 1列出了如何來設置各種限制條件。
?
? ? 在S t a r t R e s t r i c t e d P r o c e s s函數中,我只對作業設置了一些最基本的限制。指定了一個J O B _ O B J E C T _ B A S I C _ L I M I T _ I N F O R M AT I O N結構,對它進行了初始化,然后調用S e t I n f o r m a t i o n J o b O b j e c t函數。J O B _ O B J E C T _ B A S I C _ L I M I T _ I N F O R M AT I O N結構類似下面的樣子:
?
表5 - 2簡單地描述了它的各個成員的情況。
?
????關于這個結構的某些問題在 Platform SDK文檔中并沒有說清楚,因此在這里作一些說明。你在 L i m i t F l a g s成員中設置了一些信息,來指明想用于作業的限制條件。我設置了J O B _ O B J E C T _ L I M I T _ P R I O R I T Y _ C L A S S和J O B _ O B J E C T _ L I M I T _ J O B _ T I M E這兩個標志。這意味著它們是我用于該作業的唯一的兩個限制條件。我沒有對C P U的親緣關系、工作區的大小、每個進程占用的C P U時間等作出限制。當作業運行時,它會維護一些統計信息,比如作業中的進程已經使用了多少 C P U時間。每次使用J O B _ O B J E C T _ L I M I T _ J O B _ T I M E標志來設置基本限制時,作業就會減去已經終止運行的進程的C P U時間的統計信息。這顯示當前活動的進程使用了多少 C P U時間。如果想改變作業運行所在的C P U的親緣關系,但是沒有重置C P U時間的統計信息,那將要如何處理呢?為了處理這種情況,必須使用JOB_OBJECT_LIMIT_AFFINITY 標志來設置新的基本限制條件,并且必須退出J O B _ O B J E C T _ L I M I T _ J O B _ T I M E標志的設置。這樣一來, 就告訴作業, 不再想要使用C P U的時間限制。這不是你想要的。
????你想要的是改變C P U親緣關系的限制,保留現有的C P U時間限制。你只是不想減去已終止運行的進程的 C P U時間的統計信息。為了解決這個問題,可以使用一個特殊標志,即J O B _ O B J E C T _ L I M I T _ P R E S E RV E _ J O B _ T I M E。這個標志與J O B _ O B J E C T _ L I M I T _ J O B _ T I M E標志是互斥的。J O B _ O B J E C T _ L I M I T _ P R E S E RV E _ J O B _ T I M E標志表示你想改變限制條件,而
不減去已經終止運行的進程的C P U時間的統計信息。
????現在介紹一下J O B O B J E C T _ B A S I C _ L I M I T _ I N F O R M AT I O N結構的S c h e d u l i n g C l a s s成員。假如你有兩個正在運行的作業,你將兩個作業的優先級類都設置為 N O R M A L _ P R I O R I T Y _C L A S S。但是你還想讓一個作業中的進程獲得比另一個進程多的 C P U時間??梢允褂?/span>S c h e d u l i n g C l a s s成員來改變擁有相同優先級的作業的相對調度關系??梢栽O置一個 0至9之間的值(包括0和9) ,5是默認值。在Windows 2000上,如果這個設置值比較大,那么系統就會給某個作業的進程中的線程提供較長的C P U時間量。如果設置的值比較小,就減少該線程的 C P U時間量。
? ? 例如,我有兩個擁有正常優先級類的作業。每個作業包含一個進程,每個進程只有一個(擁有正常優先級的)線程。在正常環境下,這兩個線程將按循環方式進行調度,每個線程獲得相同的C P U時間量。但是,如果將第一個作業的 S c h e d u l i n g C l a s s成員設置為3,那么,當該作業中的線程被安排C P U時間時,它得到的時間量將比第二個作業中的線程少。
? 如果使用S c h e d u l i n g C l a s s成員,應該避免使用大數字即較大的時間量,因為較大的時間量會降低系統中的其他作業、進程和線程的總體響應能力。另外,我只是介紹了 Windows 2000中的情況。M i c r o s o f t計劃在將來的Wi n d o w s版本中對線程調度程序進行更重要的修改,因為它認為操作系統應該為作業、進程和線程提供更寬松的線程調度環境。
? 需要特別注意的最后一個限制是 J O B _ O B J E C T _ L I M I T _ D I E _ O N _ U N H A N D L E D _E X C E P T I O N限制標志。這個限制可使系統為與作業相關的每個進程關閉“未處理的異常情況”對話框。系統通過調用S e t E r r o r M o d e函數,將作業中的每個進程的S E M _ N O G P FA U LT E R R O R B O X標志傳遞給它。作業中產生未處理的異常情況的進程會立即終止運行,不顯示任何用戶界面。對
于服務程序和其他面向批處理的作業來說,這是個非常有用的限制標志。如果沒有這個標志,作業中的進程就會產生一個異常情況,并且永遠不會終止運行,從而浪費了系統資源。
? ? 除了基本限制外,還可以使用J O B O B J E C T _ E X T E N D E D _ L I M I T _ I N F O R M AT I O N結構對作業設置擴展限制:
?
? ?如你所見,該結構包含一個J O B O B J E C T _ B A S I C _ L I M I T _ I N F O R M AT I O N結構,它構成了基本限制的一個超集。這個結構有一點兒特殊,因為它包含的成員與設置作業的限制毫無關系。首先,I o I n f o成員保留不用,無論如何不能訪問它。本章后面將要介紹如何查詢I / O計數器信息。此外,P a c k P r o c e s s M e m o r y U s e d和P a c k J o b M e m o r y U s e d成員是只讀成員,分別告訴你作業中的任何一個進程和所有進程需要使用的已確認的內存最大值。
? ? 另外兩個成員P r o c e s s M e m o r y L i m i t和J o b M e m o r y L i m i t分別用于限制作業中的任何一個進程和所有進程使用的已確認的內存量。若要設置這些限制值,可以在 L i m i t F l a g s成員中分別設定J O B _ O B J E C T _ L I M I T _ J O B _ M E M O RY和J O B _ O B J E C T _ L I M I T _ P R O C E S S _ M E M O RY兩個標志。
? ? 現在看一下可以對作業設置的另一些限制。下面是J O B O B J E C T _ B A S I C _ U I _ R E S T R I C T I O NS結構的樣子:
?
? 這個結構只有一個數據成員,即 U I R e s t r i c t i o n s C l a s s,它用于存放表5 - 3中簡單描述的一組位標志。
?
? ? 最后一個標志J O B _ O B J E C T _ U I L I M I T _ H A N D L E S是特別有趣的。這個限制意味著作業中沒有一個進程能夠訪問該作業外部的進程創建的 U S E R對象。因此,如果試圖在作業內部運行Microsoft Spy++,那么除了S p y + +自己創建的窗口外,你看不到任何別的窗口。圖 5 - 1顯示的S p y + +中打開了兩個M D I子窗口。注意,Threads 1的窗口包含一個系統中的線程列表。這些線程中只有一個線程,即000006AC SPYXX似乎創建了一些窗口。這是因為我是在它自己的作業中運行S p y + +的,并且限制了它對 U I句柄的使用。在同一個窗口中,可以看到 M S D E V和E X P L O R E R兩個線程,但是看來它們尚未創建任何窗口。可以保證,這些線程肯定創建了窗口,但是S p y + +無法訪問它們。在對話框的右邊,可以看到 Windows 3窗口,在這個窗口中,S p y + +顯示了桌面上存在的所有窗口的層次結構。注意,它只有一個項目,即 0 0 0 0 0 0 0 0。S p y + +必須將它作為占位符放在這里。
?
? ? 注意,這個U I限制是單向的。這就是說,作業外部的進程能夠看到作業內部的進程創建的U S D R對象。例如,如果我在一個作業中運行 N o t e p a d,并在作業的外部運行S p y + +,那么,如果N o t e p a d所在的作業設定了 J O B _ O B J E C T _ U I L I M I T _ H A N D L E S標志,S p y + +將能夠看到N o t e p a d的窗口。同樣,如果S p y + +在它自己的作業中,那么它也可以看到 N o t e p a d的窗口,只要它設定了J O B _ O B J E C T _ U I L I M I T _ H A N D L E S標志。
? ? 如果想為作業進程的操作創建真正的沙框,那么限制 U I句柄是可怕的。但是,如果作為作業組成部分的一個進程要與作業外部的進程進行通信,就可以使用這種限制。
? ? 實現這個目的有一個簡便的方法,那就是使用窗口消息,但是,如果作業的進程不能訪問U I句柄,那么作業中的進程就無法將窗口消息發送或顯示在作業外部的進程創建的窗口中。不過,可以使用下面這個新函數來解決這個問題:
?
? ? h U s e r O b j參數用于指明一個U S E R對象,可以為作業中的進程賦予或者撤消對該對象的訪問權。它幾乎總是一個窗口句柄,但是它可以是另一個 U S E R對象,比如桌面、掛鉤、圖標或菜單。最后兩個參數h j o b和f G r a n t用于指明你賦予或撤消對哪個作業的訪問權。注意,如果從h j o b標識的作業中的一個進程來調用該函數,該函數的運行就會失敗 — 這可以防止作業中的進程總是為它自己賦予訪問一個對象的權限。
? ? 對作業施加的最后一種限制類型與安全性相關(注意,一旦使用這種限制,就無法取消安全性限制) 。J O B O B J E C T _ S E C U R I T Y _ L I M I T _ I N F O R M AT I O N的結構類似下面的形式:
?
表5 - 4簡單地描述了它的各個成員。
?
? ? 你為該函數傳遞作業的句柄(就像你對 S e t I n f o r m a t i o n J o b O b j e c t操作時那樣) ,這些句柄包括用于指明你想要的限制信息的枚舉類型,函數要進行初始化的數據結構的地址,以及包含該結構的數據塊的長度。最后一個參數是 p d w R e t u r n L e n g t h,用于指向該函數填寫的 D W O R D,它告訴你有多少字節放入了緩存。如果你愿意的話,可以(并且通常)為該參數傳遞 N U L L。
? ? 注意 作業中的進程可以調用Q u e r y I n f o r m a t i o n J o b O b j e c t,以便通過為作業的句柄參數傳遞N U L L,獲取關于該進程所屬的作業的信息。由于它使進程能夠看到已經對它實施了哪些限制,所以這個函數非常有用。但是,如果為作業句柄參數傳遞 N U L L,那么SetInformationJobObject函數運行就會失敗,因為這將允許進程刪除對它實施的限制。
5.2 將進程放入作業
? ? 上面介紹的是設置和查詢限制方面的信息?,F在回到S t a r t R e s t r i c t e d P r o c e s s這個函數的操作上來。當對作業實施一些限制之后,通過調用 C r e a t e P r o c e s s,生成了一個進程,我想將它放入作業。但是,注意,當調用C r e a t e P r o c e s s時,我使用了C R E AT E _ S U S P E N D E D標志。這樣,創建了一個新進程,但是不允許它執行任何代碼。由于 S t a r t - R e a t r i c t e d P r o c e s s函數是從不屬于作業組成部分的進程來執行的,因此子進程也不屬于作業的組成部分。如果準備立即允許子進程開始執行代碼,那么它將跑出我的沙框,并且能夠成功地執行我想限制它做的工作。因此,當創建子進程之后,在我允許它開始運行之前,我必須顯式地將該進程放入我新創建的作業,方法是調用下面的代碼:
?
? ? 該函數告訴系統,將該進程(由 h P r o c e s s標識)視為現有作業(由 h J o b標識)的一部分。注意,該函數只允許將尚未被賦予任何作業的進程賦予一個作業。一旦進程成為一個作業的組成部分,它就不能轉到另一個作業,并且不能是無作業的進程。另外,當作為作業的一部分的進程生成另一個進程的時候,新進程將自動成為父作業的組成部分。不過可以用下面的方法改變它的行為特性:
? 打開J O B O B J E C T _ B A S I C _ L I M I T _ I N F O R M AT I O N的L i m i t F l a g s成員中的J O B _ O B J E C T _B R E A K AWAY _ O K標志,告訴系統,新生成的進程可以在作業外部運行。若要做到這一點,必須用新的C R E AT E _ B R E A K AWAY _ F R O M _ J O B標志來調用C r e a t e P r o c e s s。如果用C R E AT E _ B R E A K AWAY _ F R O M _ J O B標志調用C r e a t e P r o c e s s函數,但是該作業并沒有打開C R E AT E _ B R E A K AWAY _ F R O M _ J O B這個標志,那么C r e a t e P r o c e s s函數運行就會失敗。
? ? 如果新生成的進程也能控制作業,那么這個機制是有用的。
? 打開J O B O B J E C T _ B A S I C _ L I M I T _ I N F O R M AT I O N的L i m i t F l a g s成員中的J O B _ O B J E C T _S I L E N T _ B R E A K AWAY _ O K標志。該標志也告訴系統,新生成的進程不應該是作業的組成部分。但是沒有必要將任何其他標志傳遞給 C r e a t e P r o c e s s。實際上,該標志將使新進程不能成為作業的組成部分。該標志可以用于原先對作業對象一無所知的進程。
? ? 至于S t a r t R e s t r i c t e d P r o c e s s函數,當調用A s s i g n P r o c e s s To J o b O b j e c t后,新進程就成為受限制的作業的組成部分。然后調用R e s u m e T h r e a d,這樣,進程的線程就可以在作業的限制下執行代碼。這時,也可以關閉線程的句柄,因為不再需要它了。
5.3 終止作業中所有進程的運行
? ? 當然,想對作業進行的最經常的操作是撤消作業中的所有進程。本章開頭講過, D e v e l o p e rS t u d i o沒有配備任何便于使用的方法,來停止進程中的某個操作,因為它不知道哪個進程是由第一個進程生成的(這非常復雜。我在 Microsoft Systems Journal期刊1 9 9 8年9月號上Win32 問與答欄中介紹了Developer Studio是如何做到這一點的) 。我認為,Developer Studio的將來版本
? ? 將會改用作業來進行操作,因為代碼的編寫要容易得多,可以用它做更多的工作。
? ? 若要撤消作業中的進程,只需要調用下面的代碼:
?
? ? 這類似為作業中的每個進程調用 Te r m i n a t e P r o c e s s函數,將它們的所有退出代碼設置為u E x i t C o d e。
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的Windows核心编程 第五章 作业(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Intel汇编程序设计-整数算术指令(上
- 下一篇: Windows核心编程 第五章 作业(下