1.溫故知新
? ? ? ? 上一篇博客,我們又知道了一種進程間通信的方案:共享內存。它是在物理內存中用系統調用給我們在物理內存開辟一個共享內存,可以由多個進程的頁表進行映射,共享內存不和管道一樣,它的生命周期是隨內核的,而且它的效率高,這是因為共享內存創建出來不需要系統調用,而我們的管道等都需要系統調用進行通信,系統調用由成本,所以共享內存效率比較高,不知道我們學C++的時候還知道內存池嗎?SLT它為了提高效率,防止小塊內存一直調系統調用,它會提前開辟出一大塊內存池讓我們的STL去使用,減少內存碎片的問題。new,malloc底層就是系統調用,我們用戶肯定無法開辟空間,得讓我們的系統幫助我們用戶去開辟空間,為什么,系統是管理硬件和軟件的軟件。共享內存不提供互斥和同步,所以我們用戶必須手動來保證訪問資源的同步和互斥。
這里簡單介紹一下,互斥是什么?顧名思義,就是互斥的,我一個資源在同一時間只能有一個線程來訪問,同步就是讓不同的線程按照一定的順序來訪問我們的資源,同步必須保證互斥,只不過是有順序了,所以同步=互斥+順序。
2.并發編程
? ? ? ? 介紹信號之前,先了解一個概念叫做并發編程。
? ? ? ? 多個執行流,能看到同一份資源,如果我們并發訪問一份共享資源,會導致數據不安全,我們需要協調我們多執行流,就是保護我們的共享資源,一般就是共享和互斥。
? ? ? ? 被保護起來的資源,一般是全局變量等全局的,叫做臨界資源。
? ? ? ? 保護的方式一般就是我們上面說的互斥和同步,讓多個進程在同一時間只能有一個進程來訪問這個資源,同步就是協調進程,讓它們按照一定的順序來訪問,避免饑餓問題。
? ? ? ? 多個執行流,在訪問臨界資源的時候,具有一定的順序性,叫做同步。同步保證了互斥,在保證一個執行流訪問共享資源的時候同時保證了一定的順序性。
? ? ? ? 系統中某些資源同一時間只允許一個執行流訪問,這樣的資源叫做臨界資源。
? ? ? ? 在執行流中涉及訪問互斥資源的程序叫做臨界區,你寫的代碼=訪問臨界資源的代碼(臨界區)+不訪問臨界資源的代碼(非臨界區)。
? ? ? ? 所謂的對共享資源的保護,其實就是對臨界區代碼的保護。歸根到底我們是寫代碼來保護資源的,對代碼的保護就間接實現了對共享資源的保護。
????????
????????
????????
3.信號量
? ? ? ? ? ? ? ?信號量就是一個計數器。我們知道我們有的時候會去看電影,假如我們電影是一個資源,我們一個電影院一般要容納很多人進去看電影,所以我們這個時候很多人進去看電影就是抽象為資源分給很多執行流使用,而如果我們一個VIP影院只允許一個人去看,抽象出來就是我們就是把電影院資源作為一個整體去使用了。
? ? ? ? 我們電影院的經營者要進行賣票,我只要買了票,那個位置就是我的,就是對資源的預定。我們電影院是不能將一個位置的票重復出售的。
? ? ? ? 未來當我們的所有執行流想要訪問一個 公共資源,要先對資源進行申請,也就是買票,相當于對資源的預定機制。
? ? ? ? 但是所有進程想要看到信號量,信號量本身就是一個共享資源,那么誰來保證信號量的安全嗯?答案是把信號量設計為原子的,不可被中斷的。
? ? ? ? 所以我們的信號量實際就是對一個資源的預定機制,允許多個執行流去訪問一個共享資源,所以未來執行流想要訪問臨界資源就要先申請信號量,對資源進行預定。
? ? ? ? 我們的二元信號量就是互斥,多元信號量就是同步,說白了我們二元信號量就是只0和1只允許一個執行流申請,多元信號量允許多個執行流對資源進行申請。二元就是把電影院的資源當整體使用,只允許一個人進去看。多元就允許我們多個執行流對資源進行預定,然后進去訪問了。多元信號量允許多個線程對資源進行并發訪問,如果我們里面有共享資源還要搭配鎖來保證我們的線程安全。
信號量可以限制我們執行流訪問共享內存的個數,當它為1時,它只允許一個執行流去訪問我們的共享內存,會保護我們的共享內存的安全。
??4.信號
1.什么是信號
? ? ? ? 講完了信號量,我們來講一講信號,有人問,信號量和信號一字之差有啥聯系嗎?答案是沒有聯系,就和老婆餅和老婆沒有半毛錢關系一樣。
? ? ? ? 信號有信號產生,信號保存,信號處理三個階段。現實生活中我們看到紅燈信號就知道不能走,信號在發出之前我們就知道對應的應對方法了。相似的,我們計算機中,我們進程內部都已經內置了對信號的處理方法和處理機制了。我們能識別這些信號是因為被教育過,進程能識別過這些信號是已經被寫好了。
? ? ? ? 還有信號到來時,我們可能正在做更重要的事,不會立即去處理信號,比如快遞到達了, 我還正在打游戲,那得等我打完游戲再去取快遞吧。
? ? ? ? 進程對于信號的處理有默認動作,顧名思義,就是默認處理,還有就是忽略,忽略它不管它,還有就是我們自定義捕捉,OS會提供對應的函數幫助我們捕捉信號并對它的處理進行修改。
? ? ? ? 我們知道前面講過我們在shell中啟動一個命令,如果我們想讓它停下來,可以按crtl+c,就可以終止掉這個進程,當我們按下這個鍵的同時,我們就會想這個進程發送2號信號,讓它終止下來,我們看到的就是這個進程被我們終止了。
? ? ? ? 結論1:我們通過crtl+c這個案例可以明白我們的鍵盤可以想進程發送信號。
? ? ? ? 結論2:kill命令可以殺死一個進程。
? ? ? ? 我們先來說一下crtl+c的詳細,就是我們只有前臺進程可以通過鍵盤獲取信號,任何時刻,一個登錄系統只允許一個進程在前臺運行,其他進程都在后臺。
? ? ? ? 我們fork之后,父進程先退出,讓子進程變孤兒進程,它就會自動變為后臺進程了。
? ? ? ? 我們有一個signal可以對捕捉的信號做自定義處理可以驗證我的說法,有興趣可以去Linux上試一下。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>// 自定義信號處理函數
void signal_handler(int signum) {printf("自定義信號處理:捕獲信號 %d\n", signum);
}int main() {// 注冊信號處理函數signal(SIGINT, signal_handler);printf("程序運行中,按下 Ctrl+C 觸發信號...\n");printf("進程ID: %d\n", getpid());// 無限循環以保持程序運行while(1) {sleep(1);}return 0;
}
2.信號的本質
我們說是信號信號的,其實就是一個宏,我們發現我們的信號是沒有0號信號的。我們前面說過信號分為大致下面:信號產生,信號保存,信號處理,我們信號產生不會去立即處理,而是在合適的時候處理,那我就得把我的信號保存下來啊,保存下來我才知道他來過啊!那么記錄在哪里呢?怎么記錄呢?
下面依次回答:記錄在結構體力,結構體里各種變量,怎么記錄呢?通過位圖,我們一個有這么多信號,我們一個整型剛好32個比特位,一個比特位表示一個信號,可以保存所有產生的信號。
????????
下面我有2個問題是:如何理解給進程發信號?有了上面的認識,給進程發信號不就把一個比特位由0變1嗎?我們查一下比特位我們就知道這個信號來過了,它就被保存下來了。
? ? ? ? 如何識別信號呢?這也是我們看它在位圖的哪個比特位,看它對應比特位是0還是1我們就可以知道在這個信號在哪里,是否產生過。
但是總而言之,我們發信號是通過操作系統發送的,無論發送的信號方式有多少種,我們用戶是無法發送信號的,本質還是我們操作系統去發送信號的。
不知道還記得我們以前介紹過的task struct結構體嗎?每個進程創建出來這個結構體里面就描述它的屬性,我們的信號存儲就是存儲在我們的task_struct里了。task_struct本質是內核的數據結構,誰有權利去內核改數據,只有操作系統,所以我們得出上面的結論,既然我們的信號是通過位圖在我們的task_struct(描述進程屬性的結構體)里存儲的,我們修改里面的一個變量可以保存我們的信號,那么就只有操作系統本身可以去內核修改數據,就只有操作系統才可以給我們的進程發送信號!!!
????????