當前位置:編程學習大全網 - 源碼下載 - “將文本與Linux進程、線程和文件描述符相結合”的基本原則

“將文本與Linux進程、線程和文件描述符相結合”的基本原則

十年開發經驗總結,阿裏架構師寫的彈簧靴原理實用文檔。

建築師阿裏的文案:Redis核心原理及應用實踐,帶妳撕Redis。

Tomcat結構原理詳解

說到進程,面試中最常見的問題恐怕就是線程和進程的關系了,所以我先說壹下答案:在Linux系統中,進程和線程幾乎沒有區別。

Linux中的進程實際上是壹個數據結構。順便了解壹下文件描述符、重定向和管道命令的底層工作原理。最後,我們會從操作系統的角度來看為什麽線程和進程基本沒有區別。

首先,抽象地說,我們的計算機是這個東西:

這個大矩形代表計算機的內存空間,其中小矩形代表進程,左下角的圓圈代表磁盤,右下角的圖形代表壹些輸入輸出設備,比如鼠標、鍵盤、顯示器等。另外註意,內存空間分為兩塊,上半部分代表用戶空間,下半部分代表內核空間。

用戶空間包含用戶進程需要使用的資源。例如,如果在程序代碼中打開壹個數組,這個數組中必須有用戶空間。內核空間存儲了內核進程需要加載的系統資源,壹般不允許用戶訪問。但是註意有些用戶進程會* * *享受壹些內核空間資源,比如壹些動態鏈接庫等等。

我們用C語言編寫壹個hello程序,編譯得到壹個可執行文件,在命令行運行並打印壹個hello world,然後程序退出。在操作系統層面,創建壹個新的進程,把我們編譯的可執行文件讀入內存空間,然後執行,最後退出。

妳編譯的可執行程序只是壹個文件,不是壹個進程。在可執行文件真正運行之前,必須將其加載到內存中並打包到壹個進程中。進程是由操作系統創建的,每個進程都有其固有的屬性,如進程號(PID)、進程狀態、打開的文件等。進程創建後,讀取妳的程序,妳的程序就會被系統執行。

那麽,操作系統是如何創建進程的呢?對於操作系統來說,進程是壹個數據結構。我們直接看Linux的源代碼:

Task_struct是Linux內核對壹個進程的描述,也可以稱為“進程描述符”。源代碼比較復雜,這裏我截取了比較常見的壹小部分。

我們主要講mm指針和files指針。Mm是指進程的虛擬內存,即加載資源和可執行文件的地方;文件指針指向壹個數組,該數組包含由進程打開的所有文件的指針。

讓我們從文件開始,它是壹個文件指針數組。壹般來說,進程將從文件[0]中讀取輸入,將輸出寫入文件[1],並將錯誤信息寫入文件[2]。

比如從我們的角度來看,C語言的printf函數是將字符打印到命令行,但從進程的角度來看,是將數據寫入文件[1];類似地,scanf函數是進程試圖從文件files[0]中讀取數據。

每個進程創建時,文件的前三位用默認值填充,分別指向標準輸入流、標準輸出流和標準錯誤流。我們常說的“文件描述符”是指這個文件指針數組的索引,所以程序的文件描述符默認為0作為輸入,1作為輸出,2作為錯誤。

我們可以畫壹張新的圖:

對於壹般的計算機來說,輸入流是鍵盤,輸出流是顯示器,錯誤流也是顯示器,所以現在這個進程和內核是三線連接的。因為硬件是由內核管理的,所以我們的進程需要通過“系統調用”讓內核進程訪問硬件資源。

PS:別忘了Linux裏的壹切都被抽象成了文件,設備也是文件,可以讀寫。

如果我們寫的程序需要其他資源,比如打開壹個文件讀寫,也很簡單。進行系統調用讓內核打開文件,文件會放在files的第四個位置,對應文件描述符3:

明白了這個原理,輸入重定向就好理解了。當壹個程序要讀取數據時,它會去files[0]讀取數據,所以只要我們把files[0]指向壹個文件,程序就會從這個文件讀取數據,而不是從鍵盤讀取數據:

類似地,輸出重定向是指將files[1]指向壹個文件,因此程序的輸出不會寫入顯示器,而是寫入這個文件:

錯誤重定向也壹樣,就不贅述了。

實際上,管道符號也有同樣的效果。壹個進程的輸出流和另壹個進程的輸入流連接成壹個“管道”,數據在其中傳輸。不得不說這個設計思路真的很巧妙:

在這壹點上,妳可能也看到了“Linux中的壹切都是壹個文件”的設計思路很高明。無論是壹個設備、另壹個進程、壹個套接字還是壹個真實的文件,它們都可以被讀寫並放入壹個簡單的文件數組中。進程通過簡單的文件描述符訪問相應的資源,具體細節交給操作系統,有效解耦,美觀高效。

首先要明確,多進程多線程是並發的,可以提高處理器的利用效率,所以現在的關鍵是多進程和多進程的區別是什麽。

為什麽Linux中線程和進程基本沒有區別?因為從Linux內核的角度來說,線程和進程是沒有區別對待的。

我們知道系統調用fork()可以創建壹個新的子進程,函數pthread()可以創建壹個新的線程。但是線程和進程都是用task_struct結構表示的,唯壹的區別就是* * *享受不同的數據區域。

換句話說,壹個線程看起來和壹個進程沒什麽區別,只不過線程的壹些數據區是由其父進程共享的,而子進程是壹個副本,而不是* * *。例如,mm結構和files結構在線程中都是* * *共享的。我畫兩張圖,妳們就明白了:

所以我們的多線程程序要使用鎖機制,防止多個線程同時向同壹個區域寫數據,否則可能會造成數據混亂。

那麽妳可能會問,既然進程類似於線程,多進程數據是不共享的,也就是不存在數據混淆的問題,為什麽多線程比多進程更常用呢?

因為數據共享的並發在現實中比較常見,比如十個人同時從壹個賬戶裏取十塊錢。我們希望這個賬戶的余額正確減少壹百元,而不是希望每個人拿到壹個賬戶的副本,每個副本賬戶減少十元。

當然,必須註意的是,只有Linux系統把線程看作是享受數據的* * *進程,並沒有特殊對待。許多其他操作系統對線程和進程的處理是不同的,線程有自己獨特的數據結構。個人認為這種設計不如Linux簡單,增加了系統的復雜度。

在Linux中創建新線程和進程的效率非常高。對於創建新進程時復制內存區域的問題,Linux采用了寫時復制的策略優化,即不真正復制父進程的內存空間,而是等到需要壹次寫操作時再復制。因此,在Linux中創建新進程和新線程非常快。

  • 上一篇:《Steam》找回密碼方法教程
  • 下一篇:廣州數控928TC系統指令有那些
  • copyright 2024編程學習大全網