計算機作業系統處理機排程讀後感—–關於程序概念的剖析。從RING3到RING0(32位作業系統)
計算機作業系統處理機排程讀後感:
筆者在看作業系統西安電子科技大學那本書的時候,初次感覺本科教的不會太難,所以沒有認真的看,但是隨後這本書講的重新整理了我的世界觀。這本書居然是ring0級別的,這時不禁吐槽一下。。如果沒除錯過程式,沒接觸過ring0的同學,這本書就和馬原一樣。全背完還不知道學了啥。
由於筆者之前做過逆向工程。而除錯的大都是ring3級別的,這本書是ring0級別的。我必須要把這些知識和之前學的連線起來,以便以後接觸ring0的時候能更輕鬆一些。
1、建立程序。
在這個模組我會從一個建立程序的函式開始,將我知道的程序執行緒等核心物件與目前看的這本書聯絡起來。
ps:當你雙擊一個桌面一個exe執行檔案的時候,實際上是一個名叫explorer.exe的程序幫你去建立一個程序。如果你把這個程序終結了的話你會發現圖示及工作列全沒了。剛剛啟動的視窗卻還在。沒了父程序的我們一般稱作孤兒程序。(笑)
這是win32api給出的建立程序的方法。下面是對於建立程序需要的引數。
1、作業排程引數
IpApplicationName 一般填入檔案路徑。指向一個NULL結尾的、用來指定可執行模組的字串。說白了告訴程式要從哪把PE檔案拽入記憶體中(這個過程一般稱為作業排程)。
lpCommandLine指向一個以NULL結尾的字串,該字串指定要執行的命令列。 可以把他理解為main方法中的引數argv[]。
dwCreationFlags一般設定程序的優先順序,和其他細節操作。有很多引數網上都有。沒啥用。
2、程序通訊引數
lpProcessAttributesh、lpThreadAttributesh和bInheritHandles
這三個引數分別是子程序是否繼承父程序的控制代碼表。如果繼承了的話,子類可以訪問其他父類建立的程序。話說回來我覺得這個就是屬於作業系統程序通訊中的共享儲存器系統 。通過共享控制代碼表,來進行子程序和父程序的通訊。
而執行緒在筆者心中和程序一樣,都是屬於核心物件。原因很簡單,他們都有自己的控制代碼。而且在程式中都叫Handle。很多人認為執行緒比程序要矮一級,筆者認為這是錯的。程序是提供資源的,例如:PE檔案在裝入記憶體後(作業排程)在記憶體中分配各種程式碼段,資料段,堆疊,還有各種其他資訊。而執行緒則是執行這些程式碼段,享用這些堆疊的。
一個程序如果沒了執行緒就會被殺死,因為程序的存在是沒有意義的,空佔記憶體!
3、各種作業排程的各種細節函式(沒啥用,一般給NULL就行)
lpEnvironment
指向一個新程序的環境塊。如果此引數為空,新程序使用呼叫程序的環境。
lpCurrentDirectory
指定子程序的工作路徑。
lpStartupInfo
啟動資訊。
lpProcessInformation
程序資訊
2、程序排程
1、虛擬記憶體
筆者這裡需要引入一個虛擬記憶體的概念,這個虛擬記憶體不同於這本書後面的虛擬記憶體儲存器,(那個虛擬記憶體為了解決有的作業比較大,記憶體一時不夠用了而建立的一種分配機制)。
這裡借用一下0day安全那本書的一張圖
雖然每個程序都“相信”自己擁有 4GB 的空間,但實際上它們執行時真正能用到的空間根本沒有那麼多。記憶體管理器只是分給程序了一片“假地址”,或者說是“虛擬地址”,讓程序們“認為”這些“虛擬地址”都是可以訪問的。如果程序不使用這些“虛擬地址”,它們對程序來說就只是一筆“無形的數字財富”;當需要進行實際的記憶體操作時,記憶體管理器才會把“虛 擬地址”和“實體地址”聯絡起來
2、而為什麼是4GB的虛擬記憶體呢?
這裡我給出計算虛擬記憶體的方法
我們日常的作業系統是32位的,這個4GB的虛擬記憶體就是32位作業系統的特性。
32位最小的地址是0000 0000 最大是 FFFF FFFF 這裡是十六進位制表示的!!
可以看到十進位制是4,294,967,295 加上0000 0000這個地址,總共就是4294967296
而4294967296 代表的是可以存的Byte位元組。1024B=1KB
1024KB=1M 1024M=1G
根據這個我們可以算出結果位4GB。所以這不是定義的,而是算出來的。
3、高2G和低2G
在記憶體中7FFFF FFFF之前的我們稱之為低2G也就是所謂的ring3使用者態
而從8000 0000開始就是所謂的ring0核心級了,普通偵錯程式例如Ollydbg是無法對ring0進行除錯的。
而ring0就儲存了處理機的現場資訊,作業系統核心程式碼、裝置驅動程式、裝置I/O快取記憶體、非頁面記憶體池的分配、頁表等一切後面的章節所講的知識。
(筆者對於之前的自大有點羞愧,之前認為本科教材很簡單,思想是有點不準確的)
4、程序排程(原書3.3)
下面就是完完全全的ring0級別的了,是普通程式設計師在開發時完全體會不到的,因為無論是windows程式設計還是Java,Linux程式設計那些都是應用層面的開發。ring0級別的是完全透明的無法感知的。
因為我這次是以我最熟悉的windows的平臺進行的講解。而windows屬於多使用者多工型作業系統,程序排程相對複雜,而微軟的封閉性導致我學習的困難,因此我對程序排程的學習還是以書上的相對簡單的實現方式進行講解。(這本書關於演算法講的也太簡單了。連個資料結構都沒有,差評。)
(0)PCB程序控制塊的介紹:
(1)程序排程的任務(原書3.3.1)
1.儲存處理機的現場資訊。—這個比較好理解,把各種結構體壓進系統堆疊(push)中即可實現儲存。
2.按照某種演算法選取程序。—-筆者認為這裡是個坑。因為沒有資料結構哪來的演算法,所以還是往後再深究。
3.把處理器分配給程序。—-筆者也不知道如何分配的,書上也沒有具體介紹,姑且理解為彈棧(pop)的過程把。
三、程序排程演算法
在這裡,我主要介紹倆種程序排程演算法的實現、輪轉法和優先順序排程演算法。
一、輪轉法:
一.輪轉法的基本原理:
根據先來先服務的原則,將需要執行的所有程序按照到達時間的大小排成一個升序的序列,每次都給一個程序同樣大小的時間片,在這個時間片內如果程序執行結束了,那麼把程序從程序佇列中刪去,如果程序沒有結束,那麼把該程序停止然後改為等待狀態,放到程序佇列的尾部,直到所有的程序都已執行完畢
二.程序的切換
時間片夠用:意思就是在該時間片內,程序可以執行至結束,程序執行結束之後,將程序從程序佇列中刪除,然後啟動新的時間片
時間片不夠用:意思是在該時間片內,程序只能完成它的一部分任務,在時間片用完之後,將程序的狀態改為等待狀態,將程序放到程序佇列的尾部,等待cpu的呼叫
三.關於時間片大小的選擇
時間片過小,則程序頻繁切換,會造成cpu資源的浪費
時間片過大,則輪轉排程演算法就退化成了先來先服務演算法
二、優先順序排程演算法:
一、優先順序排程演算法的基本原理:
優先順序程序排程演算法,是把處理機分配給就緒佇列種優先順序最高的程序。即根據優先順序來確定排名的演算法。
二、根據新的更高優先順序程序能否搶佔正在執行的程序,可將該排程演算法分為:
- 非剝奪式優先順序排程演算法。當某一個程序正在處理機上執行時,即使有某個更為重要或緊迫的程序進入就緒佇列,仍然讓正在執行的程序繼續執行,直到由於其自身的原因而主動讓出處理機時(任務完成或等待事件),才把處理機分配給更為重要或緊迫的程序。
- 剝奪式優先順序排程演算法。當一個程序正在處理機上執行時,若有某個更為重要或緊迫的程序進入就緒佇列,則立即暫停正在執行的程序,將處理機分配給更重要或緊迫的程序。
三、根據程序建立後其優先順序是否可以改變,可以將程序優先順序分為以下兩種:
- 靜態優先順序。優先順序是在建立程序時確定的,且在程序的整個執行期間保持不變。確定靜態優先順序的主要依據有程序型別、程序對資源的要求、使用者要求。
- 動態優先順序。在程序執行過程中,根據程序情況的變化動態調整優先順序。動態調整優先順序的主要依據為程序佔有CPU時間的長短、就緒程序等待CPU時間的長短。
下面就是筆者使用Java語言對倆種排程演算法的實現:
package process; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.PriorityQueue; import java.util.Queue; import java.util.Scanner; public class ProcessHandling { /** * @author godoforange * @PCB資料結構 */ class PCB { PCB(int id, int priority, int timeuse, int timeround) { this.id = id; this.priority = priority; this.timeuse = timeuse; this.timeround = timeround; } int id = 0;// 程序id int priority = 0;// 優先順序 int timeuse = 0;// 需要的執行的時間為 int timeround = 0;// 輪轉時間片數為 int cpuuse = 0; @Override public String toString() { return "[+]程序ID:" + id + " 優先順序為:" + priority + " 需要執行的時間:" + timeuse + " 輪轉時間片數為:" + timeround + " 已經佔用CPU:" + cpuuse; } } /** * @author godoforange * @優先順序比較器 */ class PriorityComparator implements Comparator<PCB> { @Override public int compare(PCB o1, PCB o2) { if (o1.priority < o2.priority) { return 1; } else if (o1.priority > o2.priority) { return -1; } else { return 0; } } } /** * @author godoforange * @param N 總數 * @return 包含了PCB的連結串列 */ public List<PCB> createProcess(int N) { System.out.println("[+]建立程序中。。。"); List<PCB> allPCB = new LinkedList<>(); for (int i = 0; i < N; i++) { int p = (int) (1 + Math.random() * 10); int c = (int) (1 + Math.random() * 10); int b = (int) (1 + Math.random() * 10); PCB pcb = new PCB(i, p, c, b); System.out.println("[+]建立第" + i + "個程序,優先順序為:" + p + " 需要執行的時間為:" + c + "輪轉時間片數為:" + b); allPCB.add(pcb); } return allPCB; } /** * @author godoforange * @輪轉法 * @param N 程序數 */ public void round(int N) { List<PCB> allPCB = createProcess(N); System.out.println("[+]輪轉法排程執行中"); System.out.println("[+]建立佇列"); Queue<PCB> que = new LinkedList<>(); for(PCB pcb : allPCB) { que.offer(pcb); } System.out.println("[+]就緒佇列建立完畢"); while(!que.isEmpty()) { PCB pcb = que.element(); pcb.timeuse--; pcb.cpuuse++; try { Thread.sleep(500); } catch (InterruptedException e) { } System.out.println("[+]"+pcb.id + "運行了"+pcb.cpuuse+"次"+"輪轉時間片數為"+pcb.timeround); if (pcb.timeuse < 1) { System.out.println("[+]"+pcb.id + "執行時間到被移除"); que.remove(); }else if(pcb.cpuuse%pcb.timeround==0) { que.offer(que.remove()); } } } /** * @author godoforange * @優先權法 * @param N 程序數 */ public void priority(int N) { List<PCB> allPCB = createProcess(N); Collections.sort(allPCB, new PriorityComparator()); System.out.println("[+]優先權排程執行中"); System.out.println("[+]建立佇列:"); for (PCB pcb : allPCB) { System.out.println(pcb.toString()); } System.out.println("[+]就緒佇列建立完畢"); while (!allPCB.isEmpty()) { PCB pcb = allPCB.get(0); pcb.priority -= 3; pcb.timeuse--; pcb.cpuuse++; try { Thread.sleep(500); } catch (InterruptedException e) { } System.out.println("[+]"+pcb.id + "運行了"+pcb.cpuuse+"次"+"優先順序為"+pcb.priority); if (pcb.timeuse < 1) { System.out.println("[+]程序"+pcb.id + "執行時間到被移除"); allPCB.remove(pcb); } Collections.sort(allPCB, new PriorityComparator()); } System.out.println("[-]優先順序排程結束"); } public static void main(String[] args) { System.out.print("[+]請輸入要建立的程序數:"); int N;// 程序數 Scanner sc = new Scanner(System.in); N = sc.nextInt(); System.out.println("[+]需要建立:" +N+ "個程序"); System.out.print("[+]輸入 分別執行優先順序排程和輪轉法排程 [Y/N]:"); if(sc.nextLine().equals("Y")||sc.nextLine().equals("y")) { new ProcessHandling().priority(N); }else{ new ProcessHandling().round(N); } sc.close(); } }
執行結果如下:
四、總結
實話來說、這本書其實並不適合讓大家真正去理解何為作業系統,因為這裡面涉及到的知識很多很多,很多語言十分生澀,即便是有多年經驗的開發者也難以對其進行總結。還是希望本科教育能夠重視起來,真正的讓學生去理解何為作業系統,而非只是生澀的去介紹這些知