💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 🍂 🌿 🍄🍝 🍛 🍤
📃個人主頁 :阿然成長日記 👈點擊可跳轉
📆 個人專欄: 🔹數據結構與算法🔹C語言進階🔹C++🔹Liunx
🚩 不能則學,不知則問,恥于問人,決無長進
🍭 🍯 🍎 🍏 🍊 🍋 🍒 🍇 🍉 🍓 🍑 🍈 🍌 🍐 🍍
文章目錄
- 前言:
- 一、什么是臨界區和臨界資源?
- 二、SystemV信號量引出
- 三、什么是SystemV信號量?
- 四、 SystemV信號量的創建和控制以及操作
- 1.semget()函數:
- 2.semctl()函數:
- 3.semop()函數:
前言:
上一篇博客講解了System V共享內存,在最后說它的缺點時提到,他沒有提供進程同步機制,那么為了彌補這個缺點,所以引入了信號量sem機制。
一、什么是臨界區和臨界資源?
-
臨界資源:多個進程共同使用的一份資源,例如共享內存就是一個臨界資源
-
臨界區:不同進程內部,訪問臨界資源的那段代碼
二、SystemV信號量引出
- 打個比方:這場電影一共有五十個位置,設置一個信號量n,n = 50;看電影的人必須買票,買一張票信號量n就會減一,當n=0時,也就代表資源已經耗盡,沒有位置了。但是如果有人退票的話,n會加1。買了票才能進入觀影。
相應的,每一個進程想進入臨界資源,訪問臨界資源的一部分,不能讓進程直接去使用臨界資源(不能讓用戶直接去電影院搶占座位),而是先得申請 信號量(先得買票)。
這樣說的話,我們只需要一個int型的變量就可做到計數器的功能了,那還大費周章的提出一個信號量干嘛呢?
1.不是局部變量
- 這個變量肯定不是局部變量,那么使用一個全局變量可以嗎?
2.不是全局變量
- 如果使用全局變量,父子進程在申請信號量時,會發生寫時拷貝,導致這個變量父子進程各自一份,也不太行。
3.不在共享內存中
- 那就使用最近剛剛學習的共享內存不就好了嗎。仔細思考也不可以。
- 首先共享內存也就是說變量直接存儲在內存中。我們創建的幾個程序執行時是需要將指令放入cpu中進行執行。
- 假設只看這個共享區的變量n,它的隨著一個進程執行過程如下
1.將內存中的數據n加載到cpu的寄存器
2.n–(分析&&執行指令)
3.將cpu修改完畢的n寫回內存。 - 一個進程執行流在執行的時候,在任何時刻都可能被切換 , 被切換的時候,會帶走自己的上下文數據(包括n),然后再被切回來的時候,再把自己的上下文數據寫入到cpu的寄存器中,繼續執行。
- 這樣就有了一個問題:
假設有10個進程,分別為1,2,3,4…,9,10,而臨界資源一共只有5份,所以信號量n也為5.
假設1先申請信號量,但運氣不好執行完第一步,就被切走了,然后2,3,4,5,6都正常申請了信號量,此時信號量已經為0,后面7到10的進程都不能再申請了。
但此時1號又開始繼續執行,由于第一次進來時n是5,繼續執行第2,3步,n–為4,然后再寫回到內存,此時n從0變成了4,這不就差了嗎,明明都沒有資源了,結果信號量成了4,后面的進程又可以繼續申請,這樣肯定就出錯了。
所以n減減時,因為時序問題導致n有中間狀態,可能導致數據不一致、但如果n只有一行匯編,那么該操作就是原子的!
4.提出System V信號量
所以就提出了信號量機制
三、什么是SystemV信號量?
信號量的本質是:是一個描述臨界資源的計數器
System V信號量是一種在操作系統中提供的進程間通信(IPC)機制,用于實現進程之間的同步和互斥。它通過對計數器進行操作來控制資源的訪問。
System V信號量由一個整型的標識符(semaphore identifier)來標識,每個標識符對應著一個信號量集合
(semaphore set)。信號量集合中可以包含多個單獨的信號量,每個信號量都有一個非負整數值。
四、 SystemV信號量的創建和控制以及操作
分別對一個semget()、semctl()和semop()
頭文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
1.semget()函數:
首先回顧一下ftok():
ftok()函數生成key
頭文件:
#include <sys/types.h>
#include <sys/ipc.h>
格式:
key_t ftok(const char *pathname, int proj_id);
- 參數:
pathname指針
:一個字符串,用于標識一個文件的路徑名。通常會選擇一個已經存在的文件,因為 ftok() 函數將使用該文件的inode編號和 proj_id 參數通過算法來生成鍵值key。
proj_id
:一個整數,作為用于生成鍵的項目標識號。該參數通常取一個非負整數。 - 返回值:成功則返回生成的鍵值,否則返回-1。
格式:
int semget(key_t key, int nsems, int semflg);
參數:
-
key:唯一key值,用于標識要創建或獲取的信號量集合。
key值可以使用ftok()
獲取 -
nsems
:指定信號量集合中信號量的數量
。你想創建幾個信號量。可以想象成一個數組。 -
semflg:標志參數,用于指定信號量的創建方式和訪問權限。有下面兩個
選項
IPC_CREATE | 創建共享內存,如果底層已經存在,則獲取并返回;如果不存在,則創建共享內存然后再返回, |
---|---|
IPC_CREATE | IPC_EXCL | 如果底層不存在,則創建共享內存并返回;如果底層存在,則出錯返回。言外之意,如果返回成功,那么一定是一個全新的內存塊! |
使用:
通過指定一個鍵值和其他參數,調用semget()函數可以創建一個新的信號量集合,或者獲取一個已經存在的信號量集合。
返回值:
返回值是一個信號量標識符
(semaphore identifier),它用于后續對信號量集合的控制和操作
注意理清一個概念:semget()可以一次申請多個信號量(n1,n2.n3,),其中一個信號量n1就是一個計數器
2.semctl()函數:
int semctl(int semid, int semnum, int cmd, ...);
參數:
- semid:信號量標識符,用于指定要操作的信號量集合。
- semnum:注意與seget()區分清楚。指定具體的信號量在集合中的
索引
,用于標識要操作的信號量。 - cmd:執行的控制命令,用于指定具體的操作。
- arg:根據不同的命令,需要提供的參數。
下面是semctl函數cmd形參說明表
命令 | 解 釋 |
---|---|
IPC_STAT | 從信號量集上檢索semid_ds結構,并存到semun聯合體參數的成員buf的地址中 |
IPC_SET | 設置一個信號量集合的semid_ds結構中ipc_perm域的值,并從semun的buf中取出值 |
IPC_RMID | 從內核中刪除信號量集合 |
GETALL | 從信號量集合中獲得所有信號量的值,并把其整數值存到semun聯合體成員的一個指針數組中 |
GETNCNT | 返回當前等待資源的進程個數 |
GETPID | 返回最后一個執行系統調用semop()進程的PID |
GETVAL | 返回信號量集合內單個信號量的值 |
GETZCNT | 返回當前等待100%資源利用的進程個數 |
SETALL | 與GETALL正好相反 |
SETVAL | 用聯合體中val成員的值設置信號量集合中單個信號量的值 |
用法:
semctl()函數用于控制和管理信號量集合。
可以通過指定不同的控制命令(cmd)來實現不同的操作,例如設置
信號量的初始值
、獲取或改變
信號量的值,以及刪除信號量集合
等。
具體的參數(如arg)根據不同的命令而有所不同。
3.semop()函數:
int semop(int semid, struct sembuf *sops, unsigned nsops);
參數:
- semid:信號量標識符,用于指定要操作的信號量集合。由seget()獲取
- sops:指向一個sembuf結構體數組的指針,包含了一組操作。此結構的具體說明如下:(
里面的注釋很重呀
)
struct sembuf {short semnum; 指定要操作的信號量在集合中的索引,從0開始計數。short op;指定要執行的操作。
如果sem_op的值大于0,則表示進行V(釋放)操作,即增加信號量的值。
如果sem_op的值小于0,則表示進行P(等待)操作,即減少信號量的值。
如果sem_op的值等于0,則表示進行Z(零)操作,如果信號量的值為0,則等待。該操作通常用于同步操作,以等待某個特定條件的發生。short flag; 用于指定操作的標志。
IPC_NOWAIT:如果無法進行操作(例如信號量的值為0且sem_op為負數),則立即返回,不進行等待。
SEM_UNDO:系統在進程意外終止時,會自動撤銷該進程對信號量的操作,以避免死鎖。};
- nsops:指定操作的數量。
作用: 用于對指定信號量集
【semid】當中的指定數量的信號量
【nsops:常常設置為1】,對它做操作
【sembuf *sops】