在unix早期的代碼中,schedule和swap兩個核心任務都是由0號進程來負責的,這個樸實的設計就是unix系統最最原始的設計,由於unix在開始設計的時候十分清楚進程應該作什麼不該該作什麼,應該作它本職的工做,而諸如調度和置換之類的任務不該該由用戶進程負責,可是linux後來顛覆了這個想法,畢竟頻繁的切換帶來的開銷已經基本抵消了分工設計帶來的優雅,因而就將調度工做分擔給了各個進程自己,而置換工做仍舊由 內核進程來完成可是卻不是0號進程,而0號進程最終退化成了一個idle進程。linux
咱們從原始的unix 6的代碼中能夠看到schedule的實現,該實現中一共進行了兩次切換而不是像linux中的那樣僅僅一次,起初切換到0號進程,實際上就是切換了一下堆棧,核心堆棧切換到了0號進程就能夠說當前的進程是0號進程了,代碼執行流依舊往下走,切換堆棧並無修改pc寄存器的值,所以進行了第一次切換之後的代碼就成了0號進程執行的了,0號進程接下來作什麼呢?其實很簡單,就是在全部被換進內存的進程中找一個最值得運行的,這就涉及到了具體的調度策略。待0號進程選擇好了下一個進程以後,最後進行第二次切換,也就是切換到這個被選中的進程,切換的過程是簡單的,就是核心棧的切換,而後就是切換頁表,實際上那個時候的頁表被叫作區表,系統尚未實現徹底的虛擬內存管理,PDP-11機器使用寄存器來實現相似X86頁表的功能,在切換的過程當中,將負責「虛擬頁面」和物理頁面映射的寄存器修改一下就能夠了,由於一個時刻只有一個進程是運行態,因此進程的核心棧所在的位置是一個肯定的位置,unix的這個設計很是好,系統變得簡單,只須要將這個肯定位置的「虛擬內存」地址指向新進程的物理內存地址就能夠了,物理內存由malloc分配,注意和標準c庫的malloc的區別和聯繫,區別是功能不一樣,聯繫是實現思想相同。這裏不深談其它的地址映射,實際上內核空間的代碼是共享的,而用戶空間的不一樣,PDP-11機器分別用兩組寄存器來進行內核空間和用戶空間的地址映射,因而可知內核空間的映射是相同的,而且和後來的linux同樣,也用了一一的線性映射。這裏能夠看出代碼和數據的區別,雖然在調度函數裏面執行的是一個函數的代碼,可是倒是不一樣的3個進程在執行,因此正是數據將進程區別了開來,而不是代碼,數據又被分爲堆棧等等,所以切換了堆棧就是切換了進程,固然這裏的堆棧是核心堆棧。這個設計被後來的windows所採用,一直到如今的Windows NT都直接體現了古老的unix的這一思想,將核心堆棧等等一些核心的數據結構放置於一個固定的內存地址,而linux卻不是這樣,這得益於linux中task_struct這一精巧結構的實現,在windows中和unix同樣也是將進程的控制塊分爲了相似U區和proc結構,而且unix自從設計之初,優先級的概念就很重要,後來windows的IRQL也是借鑑了這一思想,說說看,linux是類unix系統嗎?除了API同樣以外幾乎徹底顛覆了unix的一切,倒不如說windows的某些設計和unix很類似,固然這裏不談微內核和大內核。windows
通觀unix 6代碼的fork和newproc函數的實現,簡直叫一個妙啊,很簡單,很明瞭,在父進程中直接返回子進程的pid,而子進程直接掛入進程隊列,待到系統進行調度的時候也就是switch的時候,子進程會返回switch的返回值,也就是0,switch的返回值簡直就是一石三鳥啊!unix在當時其實尚未徹底實現虛擬存儲,僅僅是很樸素的虛擬內存的概念,所以並無後來複雜的請求調頁機制,而是整個進程的換入換出,負責這件事的就是0號進程最後的那個無限大循環。在linux的請求調頁實現中,專門有一個內核線程負責頁面的換入和換出,固然必須處理的就是缺頁異常,linux靠缺頁處理以及kswapd實現了請求調頁。數據結構
總之,若是你想深刻理解操做系統原理,那麼務必看一下《萊昂氏UNIX源代碼分析》這一本書,這本書的份量能夠任何講操做系統的書都要重,從中你能夠獲得操做系統最精髓的部分,比現現在動則五六百頁的書要簡單得多,簡單就是美。只求理解精髓!10000行左右的代碼居然詮釋瞭如此一個龐然大物,而且做爲後面幾乎全部操做系統的藍本一直髮展壯大着,直到如今。ide