1. 程式人生 > >Linux中的System V訊號量

Linux中的System V訊號量

> 在程序同步,併發執行時,保證按序地訪問共享資源是十分重要的。因此引入了臨界區的概念,一次只能有一個執行緒進入臨界區完成他的指令。而訊號量(semaphore)的作用,類似於一個交通訊號燈,它負責程序協作,因此訊號量又稱為訊號燈。 在Linux系統中,它提供兩種訊號量: - **核心訊號量**,由核心控制路徑使用 - **使用者態程序使用的訊號量**,這種訊號量有兩種介面,```POSIX```訊號量和```SYSTEM V```訊號量。 訊號量的本質是一個計數器。一個較為常見的用法,是為每個資源都會分配一個訊號量。記訊號量為S,除了初始化之外,有兩個標準原子操作:```wait()```和```signal()```。 ## System V訊號量介面 - **semget** 建立一個新訊號量或取得一個已有訊號量 ```c int semget(key_t key, int num_sems, int sem_flags); ``` ```key```是一個整數值(唯一非零),可以理解成是訊號量的識別符號。 ```num_sems```指定了需要的訊號量數目,通常為1。 ```sem_flags```是一組標誌,當建立一個新的訊號量時,設定許可權與值```IPC_CREAT```做按位或操作。設定了```IPC_CREAT```標誌後,即使給出的鍵是一個已有訊號量的鍵,也不會產生錯誤。而```IPC_CREAT | IPC_EXCL```則可以建立一個新的,唯一的訊號量,如果訊號量已存在,返回一個錯誤。 函式成功返回一個相應訊號識別符號(非零),失敗返回```-1```。 - **semctl** 直接控制訊號量資訊 ```c int semctl(int sem_id, int sem_num, int command, ...); ``` 第二個引數是操作訊號在訊號集中的編號,第一個訊號的編號是```0```。 第三個引數```command```通常是下面兩個值中的其中一個: ```SETVAL```:用來把訊號量初始化為一個已知的值。 ```IPC_RMID```:用於刪除一個已經無需繼續使用的訊號量識別符號。 如果有第四個引數,它通常是一個```union semum```結構,定義如下: ```c union semun { int val; struct semid_ds *buf; unsigned short *arry; }; ``` - **semop** 改變訊號量的值 ```c int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops); ``` ```sem_id```是由```semget```返回的訊號量識別符號,```sembuf```結構的定義如下: ```c struct sembuf{ short sem_num;//除非使用一組訊號量,否則為0 short sem_op;//訊號量在一次操作中需要改變的資料,-1即P(等待)操作,+1即V(傳送訊號)操作。 short sem_flg;//通常為SEM_UNDO,使作業系統跟蹤訊號,並在程序沒有釋放該訊號量而終止時,作業系統釋放訊號量 }; ``` ## 程序同步例項 ##### 無訊號量例項 ```c #include #include #include int main() { pid_t pid; pid = fork(); srand(pid); if(pid > 0) // parent process { char a = 'A'; // char to print for(int i = 0; i < 10; ++i) { printf("%c", a); fflush(stdout); // flush stdout buffer sleep(1); printf("%c", a); fflush(stdout); sleep(1); } } else // child process { char b = 'B'; for(int i = 0; i < 10; ++i) { printf("%c", b); fflush(stdout); sleep(1); printf("%c", b); fflush(stdout); sleep(1); } } printf("\n%d - finished\n", getpid()); sleep(3); return 0; } ``` 執行結果 ![](https://img2020.cnblogs.com/blog/1897130/202012/1897130-20201228082302994-1255375557.png) ##### 有訊號量例項 ```c #include #include #include #include #include
#include #include #define SEMKEY 0x00002222 // set a key for semaphore union semun // union for semaphore { int val; struct semid_ds *buf; unsigned short *array; }; struct sembuf p = { 0, -1, SEM_UNDO}; struct sembuf v = { 0, +1, SEM_UNDO}; int main() { int sem_id = semget(SEMKEY, 1, 0666 | IPC_CREAT); // get semaphore union semun sem_union; sem_union.val = 1; if(semctl(sem_id, 0, SETVAL, sem_union) < 0) { perror("semctl error"); return -1; } int pid; pid = fork(); srand(pid); if(pid > 0) // parent process { char a = 'A'; // char to print for(int i = 0; i < 10; ++i) { if(semop(sem_id, &p, 1) < 0) // P operation { perror("semop p error"); return -1; } printf("%c", a); fflush(stdout); // flush stdout buffer sleep(1); printf("%c", a); fflush(stdout); if(semop(sem_id, &v, 1) < 0) // V operation { perror("semop v error"); return -1; } sleep(1); } } else // child process { char b = 'B'; // char to print for(int i = 0; i < 10; ++i) { if(semop(sem_id, &p, 1) < 0) // P operation { perror("semop p error"); return -1; } printf("%c", b); fflush(stdout); // flush stdout buffer sleep(1); printf("%c", b); fflush(stdout); if(semop(sem_id, &v, 1) < 0) // V operation { perror("semop v error"); return -1; } sleep(1); } } printf("\n%d - finished\n", getpid()); sleep(3); if (pid >
0) { system("ipcrm -S 0x00002222"); } return 0; } ``` 執行結果 ![](https://img2020.cnblogs.com/blog/1897130/202012/1897130-20201228082319831-1707094879.png) 因為設定訊號量的關係,一個執行緒在臨界區內一定會執行兩次```print()```操作,所以A或B一定成對