1. 并發編程概念鋪墊
> 多個執行流【進程】看到同一份資源:共享資源。
> 被保護起來的資源叫做臨界資源。
> 在進程中,涉及臨界資源的程序段叫做臨界區。【說人話就是程序中訪問共享資源的代碼】
> 什么是互斥:任何時刻,只允許一個執行流訪問資源叫叫作互斥。【比如,一個進程在訪問臨界資源的時候,另一個進程無法訪問,直到前者訪問結束解鎖,將鎖交給后者,后者才能訪問臨界區!】
> 什么是同步:多個執行流,訪問臨界資源的時候,具有一定的順序性,叫做同步。
> 所謂對共享資源的保護,本質上是對共享資源代碼的保護【即對臨界區的保護】。
2. 認識并理解信號量
什么是信號量呢?信號量本質就是計數器,用來描述臨界資源中資源的多少。
我們的進程在訪問臨界資源時,并不是說,你想訪問就訪問。其實,臨界資源也是分一塊一塊的,每個進程如果成功訪問臨界資源,那么這塊臨界資源就勢必會被占用。一旦臨界資源被占滿了,后來的進程就無法再訪問臨界資源了。系統又是如何做到這一點的呢?所有進程在訪問臨界資源之前,都必須要先申請信號量【我們現在可以簡單把它理解為一個整形變量的計數器,用來描述臨界資源的多少】,一旦申請成功,計數器減減。至此,該進程就可以隨時訪問系統為其分配的臨界資源。但是,如果資源不夠,那么該進程就會被阻塞掛起。所以,信號量的本質是對資源的預定機制!
細節一:信號量需要被所有進程訪問,那么信號量本身就是共享資源。信號量需要被保護起來,那么對于信號量的操作必須是要原子性的。我們把申請信號量,計數器++叫做p操作,進程結束訪問臨界資源,計數器--叫做v操作。所以,信號量通過PV操作來完成資源的預定機制。【而這兩個操作都必須是要原子性的,至于如何保證原子性后面再說】
細節二:只有1和0兩態的信號量叫做二元信號量【也就是互斥】。
細節三:信號量和通信有什么關系呢?為什么信號量會被歸為進程間通信的范疇呢?
首先,進程對資源的訪問,都必須要先申請信號量,所以進程看到了同一份資源。其次,不只是傳遞數據才是進程間通信,同步互斥通知也是進程間通信。比如:在進程a申請信號量時,計數器為0,那么進程a申請信號量失敗,進程a則阻塞掛起到等待隊列中。有朝一日,進程b訪問臨界資源結束,釋放資源,計數器++,進程a看到后被喚醒申請臨界資源。在上面這個例子中,進程b通過信號量完成了對進程a的控制。
3. 系統如何管理組織ipc
我們前面一直說,系統內部有描述共享內存|消息隊列|信號量的結構體對象,我們也可以理解。但是,系統內部是如何管理組織這些結構體對象呢???
不急,我們先來看看一些有關消息隊列和信號量的接口:
信號量創建接口:
信號量控制接口:
消息隊列創建接口:
?消息隊列控制接口:
通過觀察以上的接口,我們發現描述這些ipc的結構體對象都有一個共性:就是它們都命名為struct xxxid_ds,并且第一個成員類型都是struct ipc_perm,而且ipc_perm中的第一個成員都是key。
因此,我們可以得出一個結論:在Linux內核中,共享內存,消息隊列,信號量都用key來唯一區分!并且它們被系統當做了同一種資源。?
事實上,在Linux內核中,有一種全局的數據結構來管理組織ipc,就是struct ipc_ids。其中,entries指針指向ipc_id_ary,ipc_id_ary中有一個結構叫柔性數組!
?而該柔性數組中則管理了內核中的所有ipc,無論是共享內存,消息隊列還是信號量。但是,系統又是如何區分不同的結構體呢【畢竟柔性數組若是沒有相應的類型,我們就這能獲得它指向元素的第一個成員】??我們可以這樣理解,在創建控制不同類型的ipc時,我們使用的系統調用是不同的,內核中對柔性數組可以因此來區分不同類型的結構體,到時用得到的類型進行強轉即可。至此,我們也終于明白了,xxxid為什么在不斷的增長,因為他就是柔性數組的下標,我們也不用擔心數組下標越界。當下標到達最大值時,管理柔性數組的結構體會對其下標進行回繞。
下面是部分內核圖幫我們理解: