作業系統常見面試題

三分惡發表於2021-10-02

1.程式的常見狀態?以及各種狀態之間的轉換條件?

  • 就緒:程式已處於準備好執行的狀態,即程式已分配到除CPU外的所有必要資源後,只要再獲得CPU,便可立即執行。
  • 執行:程式已經獲得CPU,程式正在執行狀態。
  • 阻塞:正在執行的程式由於發生某事件(如I/O請求、申請緩衝區失敗等)暫時無法繼續執行的狀態。

          

2.程式同步

程式同步的主要任務:是對多個相關程式在執行次序上進行協調,以使併發執行的諸程式之間能有效地共享資源和相互合作,從而使程式的執行具有可再現性。

  同步機制遵循的原則:

  (1)空閒讓進;

  (2)忙則等待(保證對臨界區的互斥訪問);

  (3)有限等待(有限代表有限的時間,避免死等);

  (4)讓權等待,(當程式不能進入自己的臨界區時,應該釋放處理機,以免陷入忙等狀態)。

3.程式的通訊方式有哪些?

  程式通訊,是指程式之間的資訊交換(資訊量少則一個狀態或數值,多者則是成千上萬個位元組)。因此,對於用訊號量進行的程式間的互斥和同步,由於其所交換的資訊量少而被歸結為低階通訊。

  所謂高階程式通訊指:使用者可以利用作業系統所提供的一組通訊命令傳送大量資料的一種通訊方式。作業系統隱藏了程式通訊的實現細節。或者說,通訊過程對使用者是透明的。

  高階通訊機制可歸結為三大類:

  (1)共享儲存器系統(儲存器中劃分的共享儲存區);實際操作中對應的是“剪貼簿”(剪貼簿實際上是系統維護管理的一塊記憶體區域)的通訊方式,比如舉例如下:word程式按下ctrl+c,在ppt程式按下ctrl+v,即完成了word程式和ppt程式之間的通訊,複製時將資料放入到剪貼簿,貼上時從剪貼簿中取出資料,然後顯示在ppt視窗上。

  (2)訊息傳遞系統(程式間的資料交換以訊息(message)為單位,當今最流行的微核心作業系統中,微核心與伺服器之間的通訊,無一例外地都採用了訊息傳遞機制。應用舉例:郵槽(MailSlot)是基於廣播通訊體系設計出來的,它採用無連線的不可靠的資料傳輸。郵槽是一種單向通訊機制,建立郵槽的伺服器程式讀取資料,開啟郵槽的客戶機程式寫入資料。

  (3)管道通訊系統(管道即:連線讀寫程式以實現他們之間通訊的共享檔案(pipe檔案,類似先進先出的佇列,由一個程式寫,另一程式讀))。實際操作中,管道分為:匿名管道、命名管道。匿名管道是一個未命名的、單向管道,通過父程式和一個子程式之間傳輸資料。匿名管道只能實現本地機器上兩個程式之間的通訊,而不能實現跨網路的通訊。命名管道不僅可以在本機上實現兩個程式間的通訊,還可以跨網路實現兩個程式間的通訊。

  • 管道管道是單向的、先進先出的、無結構的、固定大小的位元組流,它把一個程式的標準輸出和另一個程式的標準輸入連線在一起。寫程式在管道的尾端寫入資料,讀程式在管道的道端讀出資料。資料讀出後將從管道中移走,其它讀程式都不能再讀到這些資料。管道提供了簡單的流控制機制。程式試圖讀空管道時,在有資料寫入管道前,程式將一直阻塞。同樣地,管道已經滿時,程式再試圖寫管道,在其它程式從管道中移走資料之前,寫程式將一直阻塞。

          注1:無名管道只能實現父子或者兄弟程式之間的通訊,有名管道(FIFO)可以實現互不相關的兩個程式之間的通訊。

     注2:用FIFO讓一個伺服器和多個客戶端進行交流時候,每個客戶在向伺服器傳送資訊前建立自己的讀管道,或者讓伺服器在得到資料後再建立管道。使用客戶的程式號(pid)作為管道名是一種常用的方法。客戶可以先把自己的程式號告訴伺服器,然後再到那個以自己程式號命名的管道中讀取回復。

  • 訊號量訊號量是一個計數器,可以用來控制多個程式對共享資源的訪問。它常作為一種鎖機制,防止某程式正在訪問共享資源時,其它程式也訪問該資源。因此,主要作為程式間以及同一程式內不同執行緒之間的同步手段。

  • 訊息佇列是一個在系統核心中用來儲存消  息的佇列,它在系統核心中是以訊息連結串列的形式出現的。訊息佇列克服了訊號傳遞資訊少、管道只能承載無格式位元組流以及緩衝區大小受限等缺點

  • 共享記憶體:共享記憶體允許兩個或多個程式訪問同一個邏輯記憶體。這一段記憶體可以被兩個或兩個以上的程式對映至自身的地址空間中,一個程式寫入共享記憶體的資訊,可以被其他使用這個共享記憶體的程式,通過一個簡單的記憶體讀取讀出,從而實現了程式間的通訊。如果某個程式向共享記憶體寫入資料,所做的改動將立即影響到可以訪問同一段共享記憶體的任何其他程式。共享記憶體是最快的IPC方式,它是針對其它程式間通訊方式執行效率低而專門設計的。它往往與其它通訊機制(如訊號量)配合使用,來實現程式間的同步和通訊。

  • 套接字:套接字也是一種程式間通訊機制,與其它通訊機制不同的是,它可用於不同機器間的程式通訊。

4.上下文切換

對於單核單執行緒CPU而言,在某一時刻只能執行一條CPU指令。上下文切換(Context Switch)是一種將CPU資源從一個程式分配給另一個程式的機制。從使用者角度看,計算機能夠並行執行多個程式,這恰恰是作業系統通過快速上下文切換造成的結果。在切換的過程中,作業系統需要先儲存當前程式的狀態(包括記憶體空間的指標,當前執行完的指令等等),再讀入下一個程式的狀態,然後執行此程式。

5.程式與執行緒的區別和聯絡?

  • 程式是具有一定獨立功能的程式關於某個資料集合上的一次執行活動,程式是系統進行資源分配和排程的一個獨立單位。
  • 執行緒是程式的一個實體,是CPU排程和分派的基本單位,它是比程式更小的能獨立執行的基本單位。

程式和執行緒的關係

(1)一個執行緒只能屬於一個程式,而一個程式可以有多個執行緒,但至少有一個執行緒。執行緒是作業系統可識別的最小執行和排程單位。

(2)資源分配給程式,同一程式的所有執行緒共享該程式的所有資源。 同一程式中的多個執行緒共享程式碼段(程式碼和常量),資料段(全域性變數和靜態變數),擴充套件段(堆儲存)。但是每個執行緒擁有自己的棧段,棧段又叫執行時段,用來存放所有區域性變數和臨時變數。

(3)處理機分給執行緒,即真正在處理機上執行的是執行緒。

(4)執行緒在執行過程中,需要協作同步。不同程式的執行緒間要利用訊息通訊的辦法實現同步。

程式與執行緒的區別?

(1)程式有自己的獨立地址空間,執行緒沒有

(2)程式是資源分配的最小單位,執行緒是CPU排程的最小單位

(3)程式和執行緒通訊方式不同(執行緒之間的通訊比較方便。同一程式下的執行緒共享資料(比如全域性變數,靜態變數),通過這些資料來通訊不僅快捷而且方便,當然如何處理好這些訪問的同步與互斥正是編寫多執行緒程式的難點。而程式之間的通訊只能通過程式通訊的方式進行。)

(4)程式上下文切換開銷大,執行緒開銷小

(5)一個程式掛掉了不會影響其他程式,而執行緒掛掉了會影響其他執行緒

(6)對程式程式操作一般開銷都比較大,對執行緒開銷就小了 

 為什麼程式上下文切換比執行緒上下文切換代價高?

程式切換分兩步:

1.切換頁目錄以使用新的地址空間

2.切換核心棧和硬體上下文

對於linux來說,執行緒和程式的最大區別就在於地址空間,對於執行緒切換,第1步是不需要做的,第2是程式和執行緒切換都要做的。

切換的效能消耗:

1、執行緒上下文切換和程式上下問切換一個最主要的區別是執行緒的切換虛擬記憶體空間依然是相同的,但是程式切換是不同的。這兩種上下文切換的處理都是通過作業系統核心來完成的。核心的這種切換過程伴隨的最顯著的效能損耗是將暫存器中的內容切換出。

2、另外一個隱藏的損耗是上下文的切換會擾亂處理器的快取機制。簡單的說,一旦去切換上下文,處理器中所有已經快取的記憶體地址一瞬間都作廢了。還有一個顯著的區別是當你改變虛擬記憶體空間的時候,處理的頁表緩衝(processor's Translation Lookaside Buffer (TLB))或者相當的神馬東西會被全部重新整理,這將導致記憶體的訪問在一段時間內相當的低效。但是線上程的切換中,不會出現這個問題。

 

轉自知乎:程式和執行緒的區別

連結:https://www.zhihu.com/question/25532384/answer/81152571

首先來一句概括的總論:程式和執行緒都是一個時間段的描述,是CPU工作時間段的描述。

下面細說背景
CPU+RAM+各種資源(比如顯示卡,光碟機,鍵盤,GPS, 等等外設)構成我們的電腦,但是電腦的執行,實際就是CPU和相關暫存器以及RAM之間的事情。

一個最最基礎的事實:CPU太快,太快,太快了,暫存器僅僅能夠追的上他的腳步,RAM和別的掛在各匯流排上的裝置完全是望其項背。那當多個任務要執行的時候怎麼辦呢?輪流著來?或者誰優先順序高誰來?不管怎麼樣的策略,一句話就是在CPU看來就是輪流著來。

一個必須知道的事實:執行一段程式程式碼,實現一個功能的過程介紹 ,當得到CPU的時候,相關的資源必須也已經就位,就是顯示卡啊,GPS啊什麼的必須就位,然後CPU開始執行。這裡除了CPU以外所有的就構成了這個程式的執行環境,也就是我們所定義的程式上下文。當這個程式執行完了,或者分配給他的CPU執行時間用完了,那它就要被切換出去,等待下一次CPU的臨幸。在被切換出去的最後一步工作就是儲存程式上下文,因為這個是下次他被CPU臨幸的執行環境,必須儲存。

串聯起來的事實:前面講過在CPU看來所有的任務都是一個一個的輪流執行的,具體的輪流方法就是:先載入程式A的上下文,然後開始執行A,儲存程式A的上下文,調入下一個要執行的程式B的程式上下文,然後開始執行B,儲存程式B的上下文。。。

========= 重要的東西出現了========

程式和執行緒就是這樣的背景出來的,兩個名詞不過是對應的CPU時間段的描述,名詞就是這樣的功能。

  • 程式就是包換上下文切換的程式執行時間總和 = CPU載入上下文+CPU執行+CPU儲存上下文

執行緒是什麼呢?
程式的顆粒度太大,每次都要有上下的調入,儲存,調出。如果我們把程式比喻為一個執行在電腦上的軟體,那麼一個軟體的執行不可能是一條邏輯執行的,必定有多個分支和多個程式段,就好比要實現程式A,實際分成 a,b,c等多個塊組合而成。那麼這裡具體的執行就可能變成:

程式A得到CPU =》CPU載入上下文,開始執行程式A的a小段,然後執行A的b小段,然後再執行A的c小段,最後CPU儲存A的上下文。

這裡a,b,c的執行是共享了A的上下文,CPU在執行的時候沒有進行上下文切換的。這裡的a,b,c就是執行緒,也就是說執行緒是共享了程式的上下文環境,的更為細小的CPU時間段。

到此全文結束,再一個總結:


程式和執行緒都是一個時間段的描述,是CPU工作時間段的描述,不過是顆粒大小不同。

 

  • 程式(process)與執行緒(thread)最大的區別是程式擁有自己的地址空間,某程式內的執行緒對於其他程式不可見,即程式A不能通過傳地址的方式直接讀寫程式B的儲存區域。程式之間的通訊需要通過程式間通訊(Inter-process communication,IPC)。與之相對的,同一程式的各執行緒間之間可以直接通過傳遞地址或全域性變數的方式傳遞資訊

  • 程式作為作業系統中擁有資源和獨立排程的基本單位,可以擁有多個執行緒。通常作業系統中執行的一個程式就對應一個程式。在同一程式中,執行緒的切換不會引起程式切換。在不同程式中進行執行緒切換,如從一個程式內的執行緒切換到另一個程式中的執行緒時,會引起程式切換。相比程式切換,執行緒切換的開銷要小很多。執行緒於程式相互結合能夠提高系統的執行效率。

執行緒可以分為兩類:

  • 使用者級執行緒(user level thread):對於這類執行緒,有關執行緒管理的所有工作都由應用程式完成,核心意識不到執行緒的存在。在應用程式啟動後,作業系統分配給該程式一個程式號,以及其對應的記憶體空間等資源。應用程式通常先在一個執行緒中執行,該執行緒被成為主執行緒。在其執行的某個時刻,可以通過呼叫執行緒庫中的函式建立一個在相同程式中執行的新執行緒。使用者級執行緒的好處是非常高效,不需要進入核心空間,但併發效率不高。

  • 核心級執行緒(kernel level thread):對於這類執行緒,有關執行緒管理的所有工作由核心完成,應用程式沒有進行執行緒管理的程式碼,只能呼叫核心執行緒的介面。核心維護程式及其內部的每個執行緒,排程也由核心基於執行緒架構完成。核心級執行緒的好處是,核心可以將不同執行緒更好地分配到不同的CPU,以實現真正的平行計算。

事實上,在現代作業系統中,往往使用組合方式實現多執行緒,即執行緒建立完全在使用者空間中完成,並且一個應用程式中的多個使用者級執行緒被對映到一些核心級執行緒上,相當於是一種折中方案。


6.程式排程

排程種類

  • 高階排程:(High-Level Scheduling)又稱為作業排程,它決定把後備作業調入記憶體執行;

  • 低階排程:(Low-Level Scheduling)又稱為程式排程,它決定把就緒佇列的某程式獲得CPU;

  • 中級排程:(Intermediate-Level Scheduling)又稱為在虛擬儲存器中引入,在內、外存對換區進行程式對換。

非搶佔式排程與搶佔式排程

  • 非搶佔式:分派程式一旦把處理機分配給某程式後便讓它一直執行下去,直到程式完成或發生程式排程程式排程某事件而阻塞時,才把處理機分配給另一個程式。

  • 搶佔式:作業系統將正在執行的程式強行暫停,由排程程式將CPU分配給其他就緒程式的排程方式。

排程策略的設計

  • 響應時間: 從使用者輸入到產生反應的時間

  • 週轉時間: 從任務開始到任務結束的時間

CPU任務可以分為互動式任務批處理任務,排程最終的目標是合理的使用CPU,使得互動式任務的響應時間儘可能短,使用者不至於感到延遲,同時使得批處理任務的週轉時間儘可能短,減少使用者等待的時間。

排程演算法:

FIFO或First Come, First Served (FCFS)先來先服務

  • 排程的順序就是任務到達就緒佇列的順序。

  • 公平、簡單(FIFO佇列)、非搶佔、不適合互動式。

  • 未考慮任務特性,平均等待時間可以縮短。

Shortest Job First (SJF)

  • 最短的作業(CPU區間長度最小)最先排程。

  • SJF可以保證最小的平均等待時間。

Shortest Remaining Job First (SRJF)

  • SJF的可搶佔版本,比SJF更有優勢。

  • SJF(SRJF): 如何知道下一CPU區間大小?根據歷史進行預測: 指數平均法。

優先權排程

  • 每個任務關聯一個優先權,排程優先權最高的任務。

  • 注意:優先權太低的任務一直就緒,得不到執行,出現“飢餓”現象。

Round-Robin(RR)輪轉排程演算法

  • 設定一個時間片,按時間片來輪轉排程(“輪叫”演算法)

  • 優點: 定時有響應,等待時間較短;缺點: 上下文切換次數較多;

  • 時間片太大,響應時間太長;吞吐量變小,週轉時間變長;當時間片過長時,退化為FCFS。

多級佇列排程

  • 按照一定的規則建立多個程式佇列

  • 不同的佇列有固定的優先順序(高優先順序有搶佔權)

  • 不同的佇列可以給不同的時間片和採用不同的排程方法

  • 存在問題1:沒法區分I/O bound和CPU bound;

  • 存在問題2:也存在一定程度的“飢餓”現象;

多級反饋佇列

  • 在多級佇列的基礎上,任務可以在佇列之間移動,更細緻的區分任務。

  • 可以根據“享用”CPU時間多少來移動佇列,阻止“飢餓”。

  • 最通用的排程演算法,多數OS都使用該方法或其變形,如UNIX、Windows等。

多級反饋佇列排程演算法描述:

clipboard.png

  • 程式在進入待排程的佇列等待時,首先進入優先順序最高的Q1等待。

  • 首先排程優先順序高的佇列中的程式。若高優先順序中佇列中已沒有排程的程式,則排程次優先順序佇列中的程式。例如:Q1,Q2,Q3三個佇列,只有在Q1中沒有程式等待時才去排程Q2,同理,只有Q1,Q2都為空時才會去排程Q3。

  • 對於同一個佇列中的各個程式,按照時間片輪轉法排程。比如Q1佇列的時間片為N,那麼Q1中的作業在經歷了N個時間片後若還沒有完成,則進入Q2佇列等待,若Q2的時間片用完後作業還不能完成,一直進入下一級佇列,直至完成。

  • 在低優先順序的佇列中的程式在執行時,又有新到達的作業,那麼在執行完這個時間片後,CPU馬上分配給新到達的作業(搶佔式)。

一個簡單的例子
假設系統中有3個反饋佇列Q1,Q2,Q3,時間片分別為2,4,8。現在有3個作業J1,J2,J3分別在時間 0 ,1,3時刻到達。而它們所需要的CPU時間分別是3,2,1個時間片。

    • 時刻0 J1到達。 於是進入到佇列1 ,執行1個時間片 ,時間片還未到,此時J2到達。

    • 時刻1 J2到達。 由於時間片仍然由J1掌控,於是等待。J1在執行了1個時間片後,已經完成了在Q1中的2個時間片的限制,於是J1置於Q2等待被排程。現在處理機分配給J2。

    • 時刻2 J1進入Q2等待排程,J2獲得CPU開始執行。

    • 時刻3 J3到達,由於J2的時間片未到,故J3在Q1等待排程,J1也在Q2等待排程。

    • 時刻4 J2處理完成,由於J3,J1都在等待排程,但是J3所在的佇列比J1所在的佇列的優先順序要高,於是J3被排程,J1繼續在Q2等待。

    • 時刻5 J3經過1個時間片,完成。

    • 時刻6 由於Q1已經空閒,於是開始排程Q2中的作業,則J1得到處理器開始執行。 J1再經過一個時間片,完成了任務。於是整個排程過程結束。

      7.死鎖的條件?以及如何處理死鎖問題?

      定義:如果一組程式中的每一個程式都在等待僅由該組程式中的其他程式才能引發的事件,那麼該組程式就是死鎖的。或者在兩個或多個併發程式中,如果每個程式持有某種資源而又都等待別的程式釋放它或它們現在保持著的資源,在未改變這種狀態之前都不能向前推進,稱這一組程式產生了死鎖。通俗地講,就是兩個或多個程式被無限期地阻塞、相互等待的一種狀態。

      產生死鎖的必要條件:

    • 互斥條件(Mutual exclusion):資源不能被共享,只能由一個程式使用。

    • 請求與保持條件(Hold and wait):已經得到資源的程式可以再次申請新的資源。

    • 非搶佔條件(No pre-emption):已經分配的資源不能從相應的程式中被強制地剝奪。

    • 迴圈等待條件(Circular wait):系統中若干程式組成環路,該環路中每個程式都在等待相鄰程式正佔用的資源。

    • 如何處理死鎖問題:

    • 忽略該問題。例如鴕鳥演算法,該演算法可以應用在極少發生死鎖的的情況下。為什麼叫鴕鳥演算法呢,因為傳說中鴕鳥看到危險就把頭埋在地底下,可能鴕鳥覺得看不到危險也就沒危險了吧。跟掩耳盜鈴有點像。

    • 檢測死鎖並且恢復。

    • 仔細地對資源進行動態分配,使系統始終處於安全狀態以避免死鎖

    • 通過破除死鎖四個必要條件之一,來防止死鎖產生。

  8.臨界資源

  • 在作業系統中,程式是佔有資源的最小單位(執行緒可以訪問其所在程式內的所有資源,但執行緒本身並不佔有資源或僅僅佔有一點必須資源)。但對於某些資源來說,其在同一時間只能被一個程式所佔用。這些一次只能被一個程式所佔用的資源就是所謂的臨界資源。典型的臨界資源比如物理上的印表機,或是存在硬碟或記憶體中被多個程式所共享的一些變數和資料等(如果這類資源不被看成臨界資源加以保護,那麼很有可能造成丟資料的問題)。

  • 對於臨界資源的訪問,必須是互斥進行。也就是當臨界資源被佔用時,另一個申請臨界資源的程式會被阻塞,直到其所申請的臨界資源被釋放。而程式內訪問臨界資源的程式碼被成為臨界區。

   9.一個程式從開始執行到結束的完整過程(四個過程)

  • 1、預處理:條件編譯,標頭檔案包含,巨集替換的處理,生成.i檔案。

    2、編譯:將預處理後的檔案轉換成組合語言,生成.s檔案

    3、彙編:彙編變為目的碼(機器程式碼)生成.o的檔案

    4、連結:連線目的碼,生成可執行程式

    連結

    10.記憶體池、程式池、執行緒池。(c++程式設計師必須掌握)

        首先介紹一個概念“池化技術 ”。池化技術就是:提前儲存大量的資源,以備不時之需以及重複使用。池化技術應用廣泛,如記憶體池,執行緒池,連線池等等。記憶體池相關的內容,建議看看Apache、Nginx等開源web伺服器的記憶體池實現。
        由於在實際應用當做,分配記憶體、建立程式、執行緒都會設計到一些系統呼叫,系統呼叫需要導致程式從使用者態切換到核心態,是非常耗時的操作。因此,當程式中需要頻繁的進行記憶體申請釋放,程式、執行緒建立銷燬等操作時,通常會使用記憶體池、程式池、執行緒池技術來提升程式的效能。

        執行緒池:執行緒池的原理很簡單,類似於作業系統中的緩衝區的概念,它的流程如下:先啟動若干數量的執行緒,並讓這些執行緒都處於睡眠狀態,當需要一個開闢一個執行緒去做具體的工作時,就會喚醒執行緒池中的某一個睡眠執行緒,讓它去做具體工作,當工作完成後,執行緒又處於睡眠狀態,而不是將執行緒銷燬。

        程式池與執行緒池同理。

        記憶體池:記憶體池是指程式預先從作業系統申請一塊足夠大記憶體,此後,當程式中需要申請記憶體的時候,不是直接向作業系統申請,而是直接從記憶體池中獲取;同理,當程式釋放記憶體的時候,並不真正將記憶體返回給作業系統,而是返回記憶體池。當程式退出(或者特定時間)時,記憶體池才將之前申請的記憶體真正釋放。

11.動態連結庫與靜態連結庫的區別

靜態庫

  • 靜態庫是一個外部函式與變數的集合體。靜態庫的檔案內容,通常包含一堆程式設計師自定的變數與函式,其內容不像動態連結庫那麼複雜,在編譯期間由編譯器與連結器將它整合至應用程式內,並製作成目標檔案以及可以獨立運作的可執行檔案。而這個可執行檔案與編譯可執行檔案的程式,都是一種程式的靜態建立(static build)。

動態庫

  • 靜態庫很方便,但是如果我們只是想用庫中的某一個函式,卻仍然得把所有的內容都連結進去。一個更現代的方法則是使用共享庫,避免了在檔案中靜態庫的大量重複。

  • 動態連結可以在首次載入的時候執行(load-time linking),這是 Linux 的標準做法,會由動態連結器ld-linux.so 完成,比方標準 C 庫(libc.so) 通常就是動態連結的,這樣所有的程式可以共享同一個庫,而不用分別進行封裝。

  • 動態連結也可以在程式開始執行的時候完成(run-time linking),在 Linux 中使用 dlopen()介面來完成(會使用函式指標),通常用於分散式軟體,高效能伺服器上。而且共享庫也可以在多個程式間共享。

  • 連結使得我們可以用多個物件檔案構造我們的程式。可以在程式的不同階段進行(編譯、載入、執行期間均可),理解連結可以幫助我們避免遇到奇怪的錯誤

  •  

區別:

  1. 使用靜態庫的時候,靜態連結庫要參與編譯,在生成執行檔案之前的連結過程中,要將靜態連結庫的全部指令直接連結入可執行檔案中。而動態庫提供了一種方法,使程式可以呼叫不屬於其可執行程式碼的函式。函式的可執行程式碼位於一個.dll檔案中,該dll包含一個或多個已被編譯,連結並與使用它們的程式分開儲存的函式。
  2. 靜態庫中不能再包含其他動態庫或靜態庫,而在動態庫中還可以再包含其他動態或者靜態庫。
  3. 靜態庫在編譯的時候,就將庫函式裝在到程式中去了,而動態庫函式必須在執行的時候才被裝載,所以使用靜態庫速度快一些。

連結

12.虛擬記憶體?優缺點?

定義:具有請求調入功能和置換功能,能從邏輯上對記憶體容量加以擴充得一種儲存器系統。其邏輯容量由記憶體之和和外存之和決定。

與傳統儲存器比較虛擬儲存器有以下三個主要特徵:

  • 多次性,是指無需在作業執行時一次性地全部裝入記憶體,而是允許被分成多次調入記憶體執行。
  • 對換性,是指無需在作業執行時一直常駐記憶體,而是允許在作業的執行過程中,進行換進和換出。
  • 虛擬性,是指從邏輯上擴充記憶體的容量,使使用者所看到的記憶體容量,遠大於實際的記憶體容量。

虛擬記憶體的實現有以下兩種方式:

  • 請求分頁儲存管理。
  • 請求分段儲存管理。

13.頁面置換演算法

作業系統將記憶體按照頁面進行管理,在需要的時候才把程式相應的部分調入記憶體。當產生缺頁中斷時,需要選擇一個頁面寫入。如果要換出的頁面在記憶體中被修改過,變成了“髒”頁面,那就需要先寫會到磁碟。頁面置換演算法,就是要選出最合適的一個頁面,使得置換的效率最高。頁面置換演算法有很多,簡單介紹幾個,重點介紹比較重要的LRU及其實現演算法。

一、最優頁面置換演算法

最理想的狀態下,我們給頁面做個標記,挑選一個最遠才會被再次用到的頁面調出。當然,這樣的演算法不可能實現,因為不確定一個頁面在何時會被用到。

二、先進先出頁面置換演算法(FIFO)及其改進

這種演算法的思想和佇列是一樣的,該演算法總是淘汰最先進入記憶體的頁面,即選擇在記憶體中駐留時間最久的頁面予淘汰。實現:把一個程式已調入記憶體的頁面按先後次序連結成一個佇列,並且設定一個指標總是指向最老的頁面。缺點:對於有些經常被訪問的頁面如含有全域性變數、常用函式、例程等的頁面,不能保證這些不被淘汰。

三、最近最少使用頁面置換演算法LRU(Least Recently Used)

根據頁面調入記憶體後的使用情況做出決策。LRU置換演算法是選擇最近最久未使用的頁面進行淘汰。

1.為每個在記憶體中的頁面配置一個移位暫存器。(P165)定時訊號將每隔一段時間將暫存器右移一位。最小數值的暫存器對應頁面就是最久未使用頁面。

2.利用一個特殊的棧儲存當前使用的各個頁面的頁面號。每當程式訪問某頁面時,便將該頁面的頁面號從棧中移出,將它壓入棧頂。因此,棧頂永遠是最新被訪問的頁面號,棧底是最近最久未被訪問的頁面號。

連結:分頁記憶體管理(把虛擬記憶體空間和實體記憶體空間均劃分為大小相同的頁面等內容)

連結:分段記憶體管理

14.中斷與系統呼叫

所謂的中斷就是在計算機執行程式的過程中,由於出現了某些特殊事情,使得CPU暫停對程式的執行,轉而去執行處理這一事件的程式。等這些特殊事情處理完之後再回去執行之前的程式。中斷一般分為三類:

  • 由計算機硬體異常或故障引起的中斷,稱為內部異常中斷

  • 由程式中執行了引起中斷的指令而造成的中斷,稱為軟中斷(這也是和我們將要說明的系統呼叫相關的中斷);

  • 由外部裝置請求引起的中斷,稱為外部中斷。簡單來說,對中斷的理解就是對一些特殊事情的處理。

與中斷緊密相連的一個概念就是中斷處理程式了。當中斷髮生的時候,系統需要去對中斷進行處理,對這些中斷的處理是由作業系統核心中的特定函式進行的,這些處理中斷的特定的函式就是我們所說的中斷處理程式了。

另一個與中斷緊密相連的概念就是中斷的優先順序。中斷的優先順序說明的是當一箇中斷正在被處理的時候,處理器能接受的中斷的級別。中斷的優先順序也表明了中斷需要被處理的緊急程度。每個中斷都有一個對應的優先順序,當處理器在處理某一中斷的時候,只有比這個中斷優先順序高的中斷可以被處理器接受並且被處理。優先順序比這個當前正在被處理的中斷優先順序要低的中斷將會被忽略。

典型的中斷優先順序如下所示:

  • 機器錯誤 > 時鐘 > 磁碟 > 網路裝置 > 終端 > 軟體中斷

 

在講系統呼叫之前,先說下程式的執行在系統上的兩個級別:使用者級和核心級,也稱為使用者態和系統態(user mode and kernel mode)

           使用者空間就是使用者程式所在的記憶體區域,相對的,系統空間就是作業系統佔據的記憶體區域。使用者程式和系統程式的所有資料都在記憶體中。處於使用者態的程式只能訪問使用者空間,而處於核心態的程式可以訪問使用者空間和核心空間。

使用者態切換到核心態的方式如下:

  • 系統呼叫:程式的執行一般是在使用者態下執行的,但當程式需要使用作業系統提供的服務時,比如說開啟某一裝置、建立檔案、讀寫檔案(這些均屬於系統呼叫)等,就需要向作業系統發出呼叫服務的請求,這就是系統呼叫。

  • 異常:當CPU在執行執行在使用者態下的程式時,發生了某些事先不可知的異常,這時會觸發由當前執行程式切換到處理此異常的核心相關程式中,也就轉到了核心態,比如缺頁異常。

  • 外圍裝置的中斷:當外圍裝置完成使用者請求的操作後,會向CPU發出相應的中斷訊號,這時CPU會暫停執行下一條即將要執行的指令轉而去執行與中斷訊號對應的處理程式,如果先前執行的指令是使用者態下的程式,那麼這個轉換的過程自然也就發生了由使用者態到核心態的切換。比如硬碟讀寫操作完成,系統會切換到硬碟讀寫的中斷處理程式中執行後續操作等。

使用者態和核心態(核心態)之間的區別是什麼呢?

       許可權不一樣。

  • 使用者態的程式能存取它們自己的指令和資料,但不能存取核心指令和資料(或其他程式的指令和資料)

  • 核心態下的程式能夠存取核心和使用者地址某些機器指令是特權指令,在使用者態下執行特權指令會引起錯誤。在系統中核心並不是作為一個與使用者程式平行的估計的程式的集合。

15.C++多執行緒,互斥,同步

同步和互斥

當有多個執行緒的時候,經常需要去同步(注:同步不是同時刻)這些執行緒以訪問同一個資料或資源。例如,假設有一個程式,其中一個執行緒用於把檔案讀到記憶體,而另一個執行緒用於統計檔案中的字元數。當然,在把整個檔案調入記憶體之前,統計它的計數是沒有意義的。但是,由於每個操作都有自己的執行緒,作業系統會把兩個執行緒當作是互不相干的任務分別執行,這樣就可能在沒有把整個檔案裝入記憶體時統計字數。為解決此問題,你必須使兩個執行緒同步工作。

所謂同步,是指在不同程式之間的若干程式片斷,它們的執行必須嚴格按照規定的某種先後次序來執行,這種先後次序依賴於要完成的特定的任務。如果用對資源的訪問來定義的話,同步是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源。

所謂互斥,是指散佈在不同程式之間的若干程式片斷,當某個程式執行其中一個程式片段時,其它程式就不能執行它們之中的任一程式片段,只能等到該程式執行完這個程式片段後才可以執行。如果用對資源的訪問來定義的話,互斥某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。

多執行緒同步和互斥有幾種實現方法

執行緒間的同步方法大體可分為兩類:使用者模式和核心模式。顧名思義,核心模式就是指利用系統核心物件的單一性來進行同步,使用時需要切換核心態與使用者態,而使用者模式就是不需要切換到核心態,只在使用者態完成操作。

使用者模式下的方法有:原子操作(例如一個單一的全域性變數),臨界區。

核心模式下的方法有:事件,訊號量,互斥量。

1、臨界區:通過對多執行緒的序列化來訪問公共資源或一段程式碼,速度快,適合控制資料訪問。 
2、互斥量:為協調共同對一個共享資源的單獨訪問而設計的。 
3、訊號量:為控制一個具有有限數量使用者資源而設計。 
4、事 件:用來通知執行緒有一些事件已發生,從而啟動後繼任務的開始。

16.邏輯地址 Vs 實體地址 Vs 虛擬記憶體

  • 所謂的邏輯地址,是指計算機使用者(例如程式開發者),看到的地址。例如,當建立一個長度為100的整型陣列時,作業系統返回一個邏輯上的連續空間:指標指向陣列第一個元素的記憶體地址。由於整型元素的大小為4個位元組,故第二個元素的地址時起始地址加4,以此類推。事實上,邏輯地址並不一定是元素儲存的真實地址,即陣列元素的實體地址(在記憶體條中所處的位置),並非是連續的,只是作業系統通過地址對映,將邏輯地址對映成連續的,這樣更符合人們的直觀思維

  • 另一個重要概念是虛擬記憶體。作業系統讀寫記憶體的速度可以比讀寫磁碟的速度快幾個量級。但是,記憶體價格也相對較高,不能大規模擴充套件。於是,作業系統可以通過將部分不太常用的資料移出記憶體,“存放到價格相對較低的磁碟快取,以實現記憶體擴充套件。作業系統還可以通過演算法預測哪部分儲存到磁碟快取的資料需要進行讀寫,提前把這部分資料讀回記憶體。虛擬記憶體空間相對磁碟而言要小很多,因此,即使搜尋虛擬記憶體空間也比直接搜尋磁碟要快。唯一慢於磁碟的可能是,記憶體、虛擬記憶體中都沒有所需要的資料,最終還需要從硬碟中直接讀取。這就是為什麼記憶體和虛擬記憶體中需要儲存會被重複讀寫的資料,否則就失去了快取的意義。現代計算機中有一個專門的轉譯緩衝區(Translation Lookaside Buffer,TLB),用來實現虛擬地址到實體地址的快速轉換。

與記憶體/虛擬記憶體相關的還有如下兩個概念:
1) Resident Set

  • 當一個程式在執行的時候,作業系統不會一次性載入程式的所有資料到記憶體,只會載入一部分正在用,以及預期要用的資料。其他資料可能儲存在虛擬記憶體,交換區和硬碟檔案系統上。被載入到記憶體的部分就是resident set。

2) Thrashing

  • 由於resident set包含預期要用的資料,理想情況下,程式執行過程中用到的資料都會逐步載入進resident set。但事實往往並非如此:每當需要的記憶體頁面(page)不在resident set中時,作業系統必須從虛擬記憶體或硬碟中讀資料,這個過程被稱為記憶體頁面錯誤(page faults)。當作業系統需要花費大量時間去處理頁面錯誤的情況就是thrashing。

參考連結:https://blog.csdn.net/newcong0123/article/details/52792070

17.內部碎片與外部碎片

在記憶體管理中,內部碎片是已經被分配出去的的記憶體空間大於請求所需的記憶體空間。

外部碎片是指還沒有分配出去,但是由於大小太小而無法分配給申請空間的新程式的記憶體空間空閒塊。

固定分割槽存在內部碎片,可變式分割槽分配會存在外部碎片;

頁式虛擬儲存系統存在內部碎片段式虛擬儲存系統,存在外部碎片

為了有效的利用記憶體,使記憶體產生更少的碎片,要對記憶體分頁,記憶體以頁為單位來使用,最後一頁往往裝不滿,於是形成了內部碎片。

為了共享要分段,在段的換入換出時形成外部碎片,比如5K的段換出後,有一個4k的段進來放到原來5k的地方,於是形成1k的外部碎片。

18.同步和互斥的區別

        當有多個執行緒的時候,經常需要去同步這些執行緒以訪問同一個資料或資源。例如,假設有一個程式,其中一個執行緒用於把檔案讀到記憶體,而另一個執行緒用於統計檔案中的字元數。當然,在把整個檔案調入記憶體之前,統計它的計數是沒有意義的。但是,由於每個操作都有自己的執行緒,作業系統會把兩個執行緒當作是互不相干的任務分別執行,這樣就可能在沒有把整個檔案裝入記憶體時統計字數。為解決此問題,你必須使兩個執行緒同步工作。

      所謂同步,是指散步在不同程式之間的若干程式片斷,它們的執行必須嚴格按照規定的某種先後次序來執行,這種先後次序依賴於要完成的特定的任務。如果用對資源的訪問來定義的話,同步是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源。

      所謂互斥,是指散佈在不同程式之間的若干程式片斷,當某個程式執行其中一個程式片段時,其它程式就不能執行它們之中的任一程式片段,只能等到該程式執行完這個程式片段後才可以執行。如果用對資源的訪問來定義的話,互斥某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。

19.什麼是執行緒安全

如果多執行緒的程式執行結果是可預期的,而且與單執行緒的程式執行結果一樣,那麼說明是“執行緒安全”的。

20.同步與非同步

同步:

  • 同步的定義:是指一個程式在執行某個請求的時候,若該請求需要一段時間才能返回資訊,那麼,這個程式將會一直等待下去,直到收到返回資訊才繼續執行下去。
  • 特點:
  1. 同步是阻塞模式;
  2. 同步是按順序執行,執行完一個再執行下一個,需要等待,協調執行;

非同步:

  • 是指程式不需要一直等下去,而是繼續執行下面的操作,不管其他程式的狀態。當有訊息返回時系統會通知程式進行處理,這樣可以提高執行的效率。
  • 特點:
  1. 非同步是非阻塞模式,無需等待;
  2. 非同步是彼此獨立,在等待某事件的過程中,繼續做自己的事,不需要等待這一事件完成後再工作。執行緒是非同步實現的一個方式。

同步與非同步的優缺點:

  • 同步可以避免出現死鎖,讀髒資料的發生。一般共享某一資源的時候,如果每個人都有修改許可權,同時修改一個檔案,有可能使一個讀取另一個人已經刪除了內容,就會出錯,同步就不會出錯。但,同步需要等待資源訪問結束,浪費時間,效率低。
  • 非同步可以提高效率,但,安全性較低。

21.系統呼叫與庫函式的區別

  • 系統呼叫(System call)是程式向系統核心請求服務的方式。可以包括硬體相關的服務(例如,訪問硬碟等),或者建立新程式,排程其他程式等。系統呼叫是程式和作業系統之間的重要介面。

  • 庫函式:把一些常用的函式編寫完放到一個檔案裡,編寫應用程式時呼叫,這是由第三方提供的,發生在使用者地址空間

  • 移植性方面,不同作業系統的系統呼叫一般是不同的,移植性差;而在所有的ANSI C編譯器版本中,C庫函式是相同的。

  • 呼叫開銷方面,系統呼叫需要在使用者空間和核心環境間切換,開銷較大;而庫函式呼叫屬於“過程呼叫”,開銷較小。

22.守護、殭屍、孤兒程式的概念

  • 守護程式:執行在後臺的一種特殊程式,獨立於控制終端並週期性地執行某些任務

  • 殭屍程式:一個程式 fork 子程式,子程式退出,而父程式沒有wait/waitpid子程式,那麼子程式的程式描述符仍儲存在系統中,這樣的程式稱為殭屍程式。

  • 孤兒程式:一個父程式退出,而它的一個或多個子程式還在執行,這些子程式稱為孤兒程式。(孤兒程式將由 init 程式收養並對它們完成狀態收集工作)

23.Semaphore(訊號量) Vs Mutex(互斥鎖)

  • 當使用者創立多個執行緒/程式時,如果不同執行緒/程式同時讀寫相同的內容,則可能造成讀寫錯誤,或者資料不一致。此時,需要通過加鎖的方式,控制臨界區(critical section)的訪問許可權。對於semaphore而言,在初始化變數的時候可以控制允許多少個執行緒/程式同時訪問一個臨界區,其他的執行緒/程式會被堵塞,直到有人解鎖。

  • Mutex相當於只允許一個執行緒/程式訪問的semaphore。此外,根據實際需要,人們還實現了一種讀寫鎖(read-write lock),它允許同時存在多個閱讀者(reader),但任何時候至多隻有一個寫者(writer),且不能於讀者共存。

24.IO多路複用

IO多路複用是指核心一旦發現程式指定的一個或者多個IO條件準備讀取,它就通知該程式。IO多路複用適用如下場合:

  • 當客戶處理多個描述字時(一般是互動式輸入和網路套介面),必須使用I/O複用。

  • 當一個客戶同時處理多個套介面時,而這種情況是可能的,但很少出現。

  • 如果一個TCP伺服器既要處理監聽套介面,又要處理已連線套介面,一般也要用到I/O複用。

  • 如果一個伺服器即要處理TCP,又要處理UDP,一般要使用I/O複用。

  • 如果一個伺服器要處理多個服務或多個協議,一般要使用I/O複用。

  • 與多程式和多執行緒技術相比,I/O多路複用技術的最大優勢是系統開銷小,系統不必建立程式/執行緒,也不必維護這些程式/執行緒,從而大大減小了系統的開銷。

25.執行緒安全

如果你的程式碼所在的程式中有多個執行緒在同時執行,而這些執行緒可能會同時執行這段程式碼。如果每次執行結果和單執行緒執行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是執行緒安全的。或者說:一個類或者程式所提供的介面對於執行緒來說是原子操作或者多個執行緒之間的切換不會導致該介面的執行結果存在二義性,也就是說我們不用考慮同步的問題。

執行緒安全問題都是由全域性變數靜態變數引起的。

若每個執行緒中對全域性變數、靜態變數只有讀操作,而無寫操作,一般來說,這個全域性變數是執行緒安全的;若有多個執行緒同時執行寫操作,一般都需要考慮執行緒同步,否則的話就可能影響執行緒安全。

26.執行緒共享資源和獨佔資源問題

參考連結

一個程式中的所有執行緒共享該程式的地址空間,但它們有各自獨立的(/私有的)棧(stack),Windows執行緒的預設堆疊大小為1M。堆(heap)的分配與棧有所不同,一般是一個程式有一個C執行時堆,這個堆為本程式中所有執行緒共享,windows程式還有所謂程式預設堆,使用者也可以建立自己的堆。 
用作業系統術語,執行緒切換的時候實際上切換的是一個可以稱之為執行緒控制塊的結構(TCB),裡面儲存所有將來用於恢復執行緒環境必須的資訊,包括所有必須儲存的暫存器集,執行緒的狀態等。

堆: 是大家共有的空間,分全域性堆和區域性堆。全域性堆就是所有沒有分配的空間,區域性堆就是使用者分配的空間。堆在作業系統對程式初始化的時候分配,執行過程中也可以向系統要額外的堆,但是記得用完了要還給作業系統,要不然就是記憶體洩漏。

棧:是個執行緒獨有的,儲存其執行狀態和區域性自動變數的。棧線上程開始的時候初始化,每個執行緒的棧互相獨立,因此,棧是 thread safe的。作業系統在切換執行緒的時候會自動的切換棧,就是切換 SS/ESP暫存器。棧空間不需要在高階語言裡面顯式的分配和釋放。

參考:https://www.cnblogs.com/inception6-lxc/p/9073983.html

相關文章