互斥:每一個資源要麼已經分配給了一個進程,要麼就是可用的。css
佔有和等待:已經獲得了某個資源的進程能夠再請求新的資源。java
不可搶佔:已經分配給一個進程的資源不能強制性地被搶佔,它只能被佔有它的進程顯式地釋放。程序員
環路等待:有兩個或者兩個以上的進程組成一條環路,該環路中的每一個進程都在等待下一個進程所佔有的資源。面試
主要有如下四種方法:算法
鴕鳥策略編程
死鎖檢測與死鎖恢復緩存
死鎖預防安全
死鎖避免性能優化
把頭埋在沙子裏,僞裝根本沒發生問題。bash
由於解決死鎖問題的代價很高,所以鴕鳥策略這種不採起任務措施的方案會得到更高的性能。
當發生死鎖時不會對用戶形成多大影響,或發生死鎖的機率很低,能夠採用鴕鳥策略。
大多數操做系統,包括 Unix,Linux 和 Windows,處理死鎖問題的辦法僅僅是忽略它。
不試圖阻止死鎖,而是當檢測到死鎖發生時,採起措施進行恢復。
上圖爲資源分配圖,其中方框表示資源,圓圈表示進程。資源指向進程表示該資源已經分配給該進程,進程指向資源表示進程請求獲取該資源。
圖 a 能夠抽取出環,如圖 b,它知足了環路等待條件,所以會發生死鎖。
每種類型一個資源的死鎖檢測算法是經過檢測有向圖是否存在環來實現,從一個節點出發進行深度優先搜索,對訪問過的節點進行標記,若是訪問了已經標記的節點,就表示有向圖存在環,也就是檢測到死鎖的發生。
上圖中,有三個進程四個資源,每一個數據表明的含義以下:
E 向量:資源總量
A 向量:資源剩餘量
C 矩陣:每一個進程所擁有的資源數量,每一行都表明一個進程擁有資源的數量
R 矩陣:每一個進程請求的資源數量
進程 P1 和 P2 所請求的資源都得不到知足,只有進程 P3 能夠,讓 P3 執行,以後釋放 P3 擁有的資源,此時 A = (2 2 2 0)。P2 能夠執行,執行後釋放 P2 擁有的資源,A = (4 2 2 1) 。P1 也能夠執行。全部進程均可以順利執行,沒有死鎖。
算法總結以下:
每一個進程最開始時都不被標記,執行過程有可能被標記。當算法結束時,任何沒有被標記的進程都是死鎖進程。
尋找一個沒有標記的進程 Pi,它所請求的資源小於等於 A。
若是找到了這樣一個進程,那麼將 C 矩陣的第 i 行向量加到 A 中,標記該進程,並轉回 1。
若是沒有這樣一個進程,算法終止。
利用搶佔恢復
利用回滾恢復
經過殺死進程恢復
在程序運行以前預防發生死鎖。
例如假脫機打印機技術容許若干個進程同時輸出,惟一真正請求物理打印機的進程是打印機守護進程。
一種實現方式是規定全部進程在開始執行前請求所須要的所有資源。
給資源統一編號,進程只能按編號順序來請求資源。
在程序運行時避免發生死鎖。
圖 a 的第二列 Has 表示已擁有的資源數,第三列 Max 表示總共須要的資源數,Free 表示還有可使用的資源數。從圖 a 開始出發,先讓 B 擁有所需的全部資源(圖 b),運行結束後釋放 B,此時 Free 變爲 5(圖 c);接着以一樣的方式運行 C 和 A,使得全部進程都能成功運行,所以能夠稱圖 a 所示的狀態時安全的。
定義:若是沒有死鎖發生,而且即便全部進程忽然請求對資源的最大需求,也仍然存在某種調度次序可以使得每個進程運行完畢,則稱該狀態是安全的。
安全狀態的檢測與死鎖的檢測相似,由於安全狀態必需要求不能發生死鎖。下面的銀行家算法與死鎖檢測算法很是相似,能夠結合着作參考對比。
一個小城鎮的銀行家,他向一羣客戶分別承諾了必定的貸款額度,算法要作的是判斷對請求的知足是否會進入不安全狀態,若是是,就拒絕請求;不然予以分配。
上圖 c 爲不安全狀態,所以算法會拒絕以前的請求,從而避免進入圖 c 中的狀態。
上圖中有五個進程,四個資源。左邊的圖表示已經分配的資源,右邊的圖表示還須要分配的資源。最右邊的 E、P 以及 A 分別表示:總資源、已分配資源以及可用資源,注意這三個爲向量,而不是具體數值,例如 A=(1020),表示 4 個資源分別還剩下 1/0/2/0。
檢查一個狀態是否安全的算法以下:
查找右邊的矩陣是否存在一行小於等於向量 A。若是不存在這樣的行,那麼系統將會發生死鎖,狀態是不安全的。
倘若找到這樣一行,將該進程標記爲終止,並將其已分配資源加到 A 中。
重複以上兩步,直到全部進程都標記爲終止,則狀態時安全的。
若是一個狀態不是安全的,須要拒絕進入這個狀態。
虛擬內存的目的是爲了讓物理內存擴充成更大的邏輯內存,從而讓程序得到更多的可用內存。
爲了更好的管理內存,操做系統將內存抽象成地址空間。每一個程序擁有本身的地址空間,這個地址空間被分割成多個塊,每一塊稱爲一頁。這些頁被映射到物理內存,但不須要映射到連續的物理內存,也不須要全部頁都必須在物理內存中。當程序引用到不在物理內存中的頁時,由硬件執行必要的映射,將缺失的部分裝入物理內存並從新執行失敗的指令。
從上面的描述中能夠看出,虛擬內存容許程序不用將地址空間中的每一頁都映射到物理內存,也就是說一個程序不須要所有調入內存就能夠運行,這使得有限的內存運行大程序成爲可能。例若有一臺計算機能夠產生 16 位地址,那麼一個程序的地址空間範圍是 0~64K。該計算機只有 32KB 的物理內存,虛擬內存技術容許該計算機運行一個 64K 大小的程序。
內存管理單元(MMU)管理着地址空間和物理內存的轉換,其中的頁表(Page table)存儲着頁(程序地址空間)和頁框(物理內存空間)的映射表。
一個虛擬地址分紅兩個部分,一部分存儲頁面號,一部分存儲偏移量。
下圖的頁表存放着 16 個頁,這 16 個頁須要用 4 個比特位來進行索引定位。例如對於虛擬地址(0010 000000000100),前 4 位是存儲頁面號 2,讀取表項內容爲(110 1),頁表項最後一位表示是否存在於內存中,1 表示存在。後 12 位存儲偏移量。這個頁對應的頁框的地址爲 (110 000000000100)。
在程序運行過程當中,若是要訪問的頁面不在內存中,就發生缺頁中斷從而將該頁調入內存中。此時若是內存已無空閒空間,系統必須從內存中調出一個頁面到磁盤對換區中來騰出空間。
頁面置換算法和緩存淘汰策略相似,能夠將內存當作磁盤的緩存。在緩存系統中,緩存的大小有限,當有新的緩存到達時,須要淘汰一部分已經存在的緩存,這樣纔有空間存放新的緩存數據。
頁面置換算法的主要目標是使頁面置換頻率最低(也能夠說缺頁率最低)。
OPT, Optimal replacement algorithm
所選擇的被換出的頁面將是最長時間內再也不被訪問,一般能夠保證得到最低的缺頁率。
是一種理論上的算法,由於沒法知道一個頁面多長時間再也不被訪問。
舉例:一個系統爲某進程分配了三個物理塊,並有以下頁面引用序列:
開始運行時,先將 7, 0, 1 三個頁面裝入內存。當進程要訪問頁面 2 時,產生缺頁中斷,會將頁面 7 換出,由於頁面 7 再次被訪問的時間最長。
LRU, Least Recently Used
雖然沒法知道未來要使用的頁面狀況,可是能夠知道過去使用頁面的狀況。LRU 將最近最久未使用的頁面換出。
爲了實現 LRU,須要在內存中維護一個全部頁面的鏈表。當一個頁面被訪問時,將這個頁面移到鏈表表頭。這樣就能保證鏈表表尾的頁面是最近最久未訪問的。
由於每次訪問都須要更新鏈表,所以這種方式實現的 LRU 代價很高。
NRU, Not Recently Used
每一個頁面都有兩個狀態位:R 與 M,當頁面被訪問時設置頁面的 R=1,當頁面被修改時設置 M=1。其中 R 位會定時被清零。能夠將頁面分紅如下四類:
R=0,M=0
R=0,M=1
R=1,M=0
R=1,M=1
當發生缺頁中斷時,NRU 算法隨機地從類編號最小的非空類中挑選一個頁面將它換出。
NRU 優先換出已經被修改的髒頁面(R=0,M=1),而不是被頻繁使用的乾淨頁面(R=1,M=0)。
FIFO, First In First Out
選擇換出的頁面是最早進入的頁面。
該算法會將那些常常被訪問的頁面也被換出,從而使缺頁率升高。
FIFO 算法可能會把常用的頁面置換出去,爲了不這一問題,對該算法作一個簡單的修改:
當頁面被訪問 (讀或寫) 時設置該頁面的 R 位爲 1。須要替換的時候,檢查最老頁面的 R 位。若是 R 位是 0,那麼這個頁面既老又沒有被使用,能夠馬上置換掉;若是是 1,就將 R 位清 0,並把該頁面放到鏈表的尾端,修改它的裝入時間使它就像剛裝入的同樣,而後繼續從鏈表的頭部開始搜索。
Clock
第二次機會算法須要在鏈表中移動頁面,下降了效率。時鐘算法使用環形鏈表將頁面鏈接起來,再使用一個指針指向最老的頁面。
虛擬內存採用的是分頁技術,也就是將地址空間劃分紅固定大小的頁,每一頁再與內存進行映射。
下圖爲一個編譯器在編譯過程當中創建的多個表,有 4 個表是動態增加的,若是使用分頁系統的一維地址空間,動態增加的特色會致使覆蓋問題的出現。
分段的作法是把每一個表分紅段,一個段構成一個獨立的地址空間。每一個段的長度能夠不一樣,而且能夠動態增加。
程序的地址空間劃分紅多個擁有獨立地址空間的段,每一個段上的地址空間劃分紅大小相同的頁。這樣既擁有分段系統的共享和保護,又擁有分頁系統的虛擬內存功能。
對程序員的透明性:分頁透明,可是分段須要程序員顯示劃分每一個段。
地址空間的維度:分頁是一維地址空間,分段是二維的。
大小是否能夠改變:頁的大小不可變,段的大小能夠動態改變。
出現的緣由:分頁主要用於實現虛擬內存,從而得到更大的地址空間;分段主要是爲了使程序和數據能夠被劃分爲邏輯上獨立的地址空間而且有助於共享和保護。
盤面(Platter):一個磁盤有多個盤面;
磁道(Track):盤面上的圓形帶狀區域,一個盤面能夠有多個磁道;
扇區(Track Sector):磁道上的一個弧段,一個磁道能夠有多個扇區,它是最小的物理儲存單位,目前主要有 512 bytes 與 4 K 兩種大小;
磁頭(Head):與盤面很是接近,可以將盤面上的磁場轉換爲電信號(讀),或者將電信號轉換爲盤面的磁場(寫);
制動手臂(Actuator arm):用於在磁道之間移動磁頭;
主軸(Spindle):使整個盤面轉動。
讀寫一個磁盤塊的時間的影響因素有:
旋轉時間(主軸轉動盤面,使得磁頭移動到適當的扇區上)
尋道時間(制動手臂移動,使得磁頭移動到適當的磁道上)
實際的數據傳輸時間
其中,尋道時間最長,所以磁盤調度的主要目標是使磁盤的平均尋道時間最短。
FCFS, First Come First Served
按照磁盤請求的順序進行調度。
優勢是公平和簡單。缺點也很明顯,由於未對尋道作任何優化,使平均尋道時間可能較長。
SSTF, Shortest Seek Time First
優先調度與當前磁頭所在磁道距離最近的磁道。
雖然平均尋道時間比較低,可是不夠公平。若是新到達的磁道請求老是比一個在等待的磁道請求近,那麼在等待的磁道請求會一直等待下去,也就是出現飢餓現象。具體來講,兩端的磁道請求更容易出現飢餓現象。
SCAN
電梯老是保持一個方向運行,直到該方向沒有請求爲止,而後改變運行方向。
電梯算法(掃描算法)和電梯的運行過程相似,老是按一個方向來進行磁盤調度,直到該方向上沒有未完成的磁盤請求,而後改變方向。
由於考慮了移動方向,所以全部的磁盤請求都會被知足,解決了 SSTF 的飢餓問題。
如下是一個 hello.c 程序:
#include <stdio.h>
int main()
{
printf("hello, world ");
return 0;
}
複製代碼
在 Unix 系統上,由編譯器把源文件轉換爲目標文件。
gcc -o hello hello.c
複製代碼
這個過程大體以下:
預處理階段:處理以 # 開頭的預處理命令;
編譯階段:翻譯成彙編文件;
彙編階段:將彙編文件翻譯成可重定向目標文件;
連接階段:將可重定向目標文件和 printf.o 等單獨預編譯好的目標文件進行合併,獲得最終的可執行目標文件。
靜態連接器以一組可重定向目標文件爲輸入,生成一個徹底連接的可執行目標文件做爲輸出。連接器主要完成如下兩個任務:
符號解析:每一個符號對應於一個函數、一個全局變量或一個靜態變量,符號解析的目的是將每一個符號引用與一個符號定義關聯起來。
重定位:連接器經過把每一個符號定義與一個內存位置關聯起來,而後修改全部對這些符號的引用,使得它們指向這個內存位置。
可執行目標文件:能夠直接在內存中執行;
可重定向目標文件:可與其它可重定向目標文件在連接階段合併,建立一個可執行目標文件;
共享目標文件:這是一種特殊的可重定向目標文件,能夠在運行時被動態加載進內存並連接;
靜態庫有如下兩個問題:
當靜態庫更新時那麼整個程序都要從新進行連接;
對於 printf 這種標準函數庫,若是每一個程序都要有代碼,這會極大浪費資源。
共享庫是爲了解決靜態庫的這兩個問題而設計的,在 Linux 系統中一般用 .so 後綴來表示,Windows 系統上它們被稱爲 DLL。它具備如下特色:
在給定的文件系統中一個庫只有一個文件,全部引用該庫的可執行目標文件都共享這個文件,它不會被複制到引用它的可執行文件中;
在內存中,一個共享庫的 .text 節(已編譯程序的機器代碼)的一個副本能夠被不一樣的正在運行的進程共享。
關於Android面試的題庫,我花了一個多月的時間整理出來的學習資料,但願能幫助那些想學習Android開發,卻又不知道怎麼開始學習的同窗。幫助再金三銀四季尚未找到合適工做的同窗,若是你依然在編程的世界裏迷茫,不知道本身的將來規劃,能夠加入Android高級架構羣:點擊連接加入羣聊【騰訊@Android高級架構】:(包括java基礎與原理,自定義控件、NDK、架構設計、混合式開發(Flutter,Weex)、性能優化、完整商業項目開發等系統的高級技術)