學習於:bilibili.com/video/av29

進程在內核中都有一個進程式控制制塊(PCB)來維護進程相關的信息,linux內核的進程式控制制塊是task_struct結構體。也就是說,linux在創建一個進程的時候,就會創建一個task_struct結構體來維護這個進程。

讓我們來了解一下這個結構體里的信息:

1.進程id。系統中每個進程有唯一的id,在c語言鎮南關用pid_t(typedef出來的)類型表示,其實就是一個非負整數。使用ps aux指令,如下,pid即此進程id。

2.進程的狀態,這個和書里的不一樣,linux的進程有:運行,掛起,停止,殭屍等狀態。後面進行詳解,挺重要的。

3.進程切換時需要保存和恢復的一些cpu寄存器。

4.描述虛擬地址空間的信息。

5當前工作目錄

6.umask掩碼

7.文件描述符表,包含很多指向file結構體的指針。

8.和信號相關的信息。

9.用戶id和組id。

10.控制終端、session和進程組

11.進程可以使用的資源上下限。

ulimit -a 可以大致查看這個資源上下限制

或者通過 cat /proc/self/limits 查看:

比如 openfiles這項,soft limits是軟限制,hard limits是硬限制,units是單位。即可以打開的文件數目的限制。 通過 ulimit -n 8888可以對它進行修改,普通用戶修改不能超過hard limit. root用戶則可以超過hard root。

然後我們來講一個重點:linux進程的虛擬地址。(這裡討論32位的linux,32位的linux分配給進程的是4G虛擬內存,64位的是2的48次方)

linux給每個進程分配了4G的虛擬內存,也就是說,每個進程有4G的可操作內存空間。高1G是內核空間,低3G是用戶空間。也就是說3~4g是內核空間。1~3g是用戶空間。 用戶是不能操作這裡面的內核空間的。

如下圖: 比如實際物理內存只有2g的大小。進程1的代碼段為code1,進程2的代碼段為code2,實際上都是存儲在物理內存2g當中。這裡,比如code1,在進程1中的地址可能是0x00444000,而在物理內存當中的地址可能是0x00666999;這裡存在著一個地址映射關係,在操作系統內部維護了一張虛擬內存映射表,這張表記錄了每個進程的虛擬地址對應的物理內存地址是多少。 當然,進程裡面不只有代碼段,還有數據段,堆,棧之類的,也是和這個代碼段一樣,分別指向物理內存的不同地址。

然後再繼續看下圖: 我們注意內核空間,也就是kernel,多個進程的虛擬內存的kernel映射的是同一個物理內存地址,這就和我們前面說的用戶空間的不一樣了。也就是說各進程共用一個kernel。那這樣不會出問題嗎? 不會。因為進程只能讀寫用戶空間,所以不會出現意想不到的錯誤。

然後大家猜PCB是放在用戶空間呢,還是放在內核空間呢? 答案是放在進程的內核空間里。對於用戶來說,是看不到內核空間里的東西的,而對於系統而言,這些PCB都是放在實際物理內存的kernel空間里,是可見透明的。

然後說一下操作系統是怎麼管理這些進程內存的。它是有一個page的單位,1 page=4096 byte ,也就是4kb的大小。這是它操作這些內存的最小單位,並不是位元組。這意味著什麼呢? 當進程1使用malloc申請1000個位元組的空間時,到物理內存是直接給你這個進程1個page的大小,也就是4kb。當你再申請300個位元組的時候,操作系統發現給你的這個page還沒用完,所以直接操作原來的那個page。可是如果你申請3097個位元組的話,那就沒辦法,只能再給你一個新的page。所以,了解這些底層的東西之後,我們在寫程序的時候就可以對數據的大小有個認識,會盡量申請page的倍數或者小於page的大小。比如你創建一個數組2000位元組,再創建一個數組3000位元組。這樣就不太好。操作系統在申請頁面的時候就會有這個調度的開銷。

另外,每個page頁面都可以設置它的RW讀寫屬性,所以,當你把一個頁面設置成了只讀屬性,那麼就不能寫,否則就會報錯。

講到這裡就需要說一下intel處理器的工作級別。如下圖:0級是最高級別,3級是最低的。linux在使用的時候只使用到了兩個級別,就是ring0和ring3。 0級是內核態,3級是用戶態。這是什麼意思呢?當一個程序開始運行,變成一個進程,當它在跑裡面的代碼段,操作棧和堆時,cpu都是處於3級,也就是只能操作用戶空間,也就是0到3g的用戶空間。當你調用驅動函數的時候,就是操作硬體之類的時候,cpu就會進入0級,這個時候要做的第一件事就是把cpu從3級切換到0級,處於0級的cpu才有資格去訪問3~4g的進程的內核空間。也就是說,在設置讀寫屬性時,就是把0~3g的用戶空間和3~4g的內核空間分別設置為0級和3級的讀寫屬性,比如內核空間就是不允許3級的cpu進行讀寫訪問。 那麼3級怎麼切換成0級呢?這是通過一個叫做系統調用的東西實現的。

然後來看看啥是系統調用呢?如下圖:

用戶態應用程序執行到printf函數,然後printf又調用系統的write函數,write函數實際上在intel上是執行一條彙編指令 int 0x80,也就是產生一個軟中斷,在arm上就是swi指令,也是產生一個軟中斷,然後cpu就變成0級,然後去執行內核態的代碼。把數據列印到顯示屏上。(如果玩過單片機就很好理解這個了,調函數,寫底層驅動,無非就是驅動那幾根三級管之類的發光工具嘛) 注意啊:系統調用的過程中,cpu寄存器需要先保存在用戶態運行時的相關信息,比如運行位置之類的。接著為了運行內核態的代碼,cpu寄存器要存放內核態指令的運行位置,然後再跳到內核態去運行代碼。 結束運行內核態的代碼後,又是類似的原理返回到用戶態,只是此時不需要保存內核態的運行位置。所以,這裡的一次系統調用有兩次的cpu上下文切換!!!

最後把視頻中的圖片截取下來:總結和記憶。


推薦閱讀:
相关文章