聊聊操作系統-進程與線程

工作也有兩年了,在研究很多項目時發現很多問題追根溯源都會到計算機底層的知識,也是越來越發現編程語言只是一層外殼,這個外殼需要去和操作系統協商使用後者管理的的計算機資源,包括存儲資源和計算資源。如果計算機底層知識不牢靠,遇到一些問題還真是不好分析,也很容易成爲職業上升的瓶頸。現在回想起大學時學習這麼課時是比較抽象的,那時沒有太多編程經驗,知識很難落地,造成了只知其然不知其所以然的情況,所以現在重新梳理一下計算機操作系統的一些底層知識,在這裏記錄一下。

我們說操作系統是運行在內核態的一套軟件,它的主要作用是隱藏硬件,呈現給程序或者程序員良好的、優雅的、一致的計算機抽象模型,並且負責將計算機的存儲資源和計算資源有序分配給應用程序。


這篇文章我先總結一下計算機的進程和線程相關知識。

(一)進程相關

(1)什麼是進程

我個人覺得理解進程需要從兩個角度來看,第一從動態角度講“進程”是一個正在運行程序的實例,它包括程序計數器、寄存器和變量的當前值。

第二從靜態角度講“進程”是一個容器,該容器用於聚集相關的計算機資源,線程,打開的文件等。


在單CPU系統中,CPU在多個進程之間快速來運行的,這是一種僞並行模式。所以在對進程編程時絕不能對時序做任何確定的假設。

(2)什麼時候進程被創建

  • 系統初始化:必須linux系統啓動時init進程會被創建,所有其他進程都是這個進程的子進程
  • 正在運行的進程通過系統調用創建另一個進程:比如我在這篇博客Redis持久化機制原理分析中提到的Redis主進程通過系統調用fork創建一個子進程來執行RDB
  • 用戶請求創建一個進程:如用戶在Window系統中雙擊桌面上的word快捷方式

在linux系統中進程的創建時通過系統調用fork進行創建,調用fork的進程成爲父進程,被創建的進程成爲子進程。父進程和子進程都有私有的內存地址空間,如果父進程修改了屬於自己的變量,這個修改對子進程是不可見的。

	pid = fork(); //創建成功pid>0,實際爲子進程的pid
		if(pid < 0){
			hand_error();  // pid <0 創建進程失敗
		} else if (pid > 0){
			// 父進程邏輯
		} else {
			// 子進程邏輯
		}

系統調用fork調用一次返回兩次,在父進程中返回子進程的pid,在子進程中返回0,可以通過這個返回值來判斷執行父進程代碼還是子進程代碼,系統調用fork創建一個完全與父進程相同的子進程,包括相同的文件描述符,相同的寄存器內容和其他所有內容,當然現代操作系統使用了 寫時複製Copy on write的方式來優化fork的性能,fork剛創建的子進程採用了共享的方式,只用指針指向了父進程的物理資源。當子進程真正要對某些物理資源寫操作時,纔會真正的複製一塊物理資源來供子進程使用。這樣就極大的優化了fork的性能

如果子進程想獲得自己的pid可以通過getpid系統調用。

(3)什麼時候進程退出

  • 正常運行結束程序退出(正常情況):在linux系統中程序運行結束後調用exit退出
  • 出現錯誤程序退出(正常情況):編譯一個找不到的文件等
  • 嚴重錯誤(非正常情況):引用不存在的內存、除數爲0等情況
  • 被其他用戶殺死(非正常情況):在linux系統中用戶kill命令殺死進程


(4)進程層次結構

在unix系統中當一個進程創建了另一個進程之後,我們稱前者爲父進程後者爲子進程,一個父進程有0個或多個子進程,這些進程構成了一個進程樹。

在Windows系統中進程沒有父子進程的層次結構的概念。


(二)線程相關

(1)什麼是線程

我們常說線程是cpu調用的最小單位,每個進程中至少有一個線程,當然這只是從cpu調度的角度來看線程;如果從存儲資源的角度來看線程的話,

線程解決的最大問題就是它可以很簡單地表示共享資源的問題,這裏說的資源指的是存儲器資源,資源最後都會加載到物理內存,一個進程的所有線程都是共享這個進程的同一個虛擬地址空間的,也就是說從線程的角度來說,它們看到的物理資源都是一樣的,這樣就可以通過共享變量的方式來表示共享資源,也就是直接共享內存的方式解決了線程通信的問題。

理論上說Linux內核是沒有線程這個概念的,只有內核調度實體(Kernal Scheduling Entry, KSE)這個概念。linux的線程本質上是一種輕量級的進程,是通過clone系統調用來創建的。

(2)線程的創建

在文章上面我們提到通過系統調用fork創建進程,如果說父進程是一個多線程運行模式,那麼現在面臨的一個問題是:是不是需要在新創建的子進程中創建父進程中的所有線程,如果全部創建假設父進程中一個線程正在等待輸入而阻塞,那麼等輸入完成之後數據是傳給父進程還是子進程的線程;如果不全創建,如果父進程中一個線程A持有鎖,線程B等待鎖,那麼子進程中如果沒有創建線程A,那麼線程B在子進程中將永遠阻塞。

爲了解決這個問題,Linux系統引入了一個強大的系統調用clone

pid=clone(function,stack_ptr,sharing_flags,args)

function:爲新建線程的執行入口

args:爲新創建線程執行參數

stack_ptr:爲新創建線程的私有堆棧指針

sharing_flags:這個是一個位圖不同的值可以指定,新創建線程是複製調用clone函數的線程數據結構,還是共享這部分數據結構。

具體參數值可以參考下圖:



(3)線程沒有層級結構而是對等的關係

進程採用父子結構,init進程是最頂端的父進程,其他進程都是從init進程派生出來的。這樣就很容易理解進程是如何共享內核的代碼和數據的了。

而線程採用對等結構,即線程沒有父子的概念,所有線程都屬於同一個線程組,線程組的組號等於第一個線程的線程號。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章