程序通訊--訊號量
ping16/article/details/6584043/" rel="nofollow,noindex" target="_blank">https://blog.csdn.net/guoping16/article/details/6584043/
https://www.cnblogs.com/fangshenghui/p/4039946.html
一 為什麼要使用訊號量
為了防止出現因多個程式同時訪問一個共享資源而引發的一系列問題,我們需要一種方法,它可以通過生成並使用令牌來授權,在任一時刻只能有一個執行執行緒訪問 程式碼的臨界區域。臨界區域是指執行資料更新的程式碼需要獨佔式地執行。而訊號量就可以提供這樣的一種訪問機制,讓一個臨界區同一時間只有一個執行緒在訪問它, 也就是說訊號量是用來調協程序對共享資源的訪問的。其中共享記憶體的使用就要用到訊號量。
二 訊號量的工作原理
由於訊號量只能進行兩種操作等待和傳送訊號,即P(sv)和V(sv),他們的行為是這樣的:
P(sv):如果sv的值大於零,就給它減1;如果它的值為零,就掛起該程序的執行
V(sv):如果有其他程序因等待sv而被掛起,就讓它恢復執行,如果沒有程序因等待sv而掛起,就給它加1.
三 訊號量的使用
1、建立訊號量
semget函式建立一個訊號量集或訪問一個已存在的訊號量集。
#include <sys/sem.h> int semget (key_t key, int nsem, int oflag);
返回值是一個稱為訊號量識別符號的整數,semop和semctl函式將使用它。
引數nsem指定集合中的訊號量數。(若用於訪問一個已存在的集合,那就可以把該引數指定為0)
引數oflag可以是SEM_R(read)和SEM_A(alter)常值的組合。(開啟時用到),也可以是IPC_CREAT或IPC_EXCL;
2、開啟訊號量
使用semget開啟一個訊號量集後,對其中一個或多個訊號量的操作就使用semop(op–operate)函式來執行。
#include <sys/sem.h> int semop (int semid, struct sembuf * opsptr, size_t nops);
引數opsptr是一個指標,它指向一個訊號量運算元組,訊號量操作由sembuf結構表示:
struct sembuf { short sem_num;// 除非使用一組訊號量,否則它為0 short sem_op; // 訊號量在一次操作中需要改變的資料,通常是兩個數, // 一個是-1,即P(等待)操作,一個是+1,即V(傳送訊號)操作 short sem_flg;// 通常為SEM_UNDO,使作業系統跟蹤訊號,並在程序沒有釋放該訊號量而終止時, // 作業系統釋放訊號量 };
引數nops規定opsptr陣列中元素個數。
sem_op值: (1)若sem_op為正,這對應於程序釋放佔用的資源數。sem_op值加到訊號量的值上。(V操作)
(2)若sem_op為負,這表示要獲取該訊號量控制的資源數。訊號量值減去sem_op的絕對值。(P操作)
(3)若sem_op為0,這表示呼叫程序希望等待到該訊號量值變成0
如果訊號量值小於sem_op的絕對值(資源不能滿足要求),則:
(1)若指定了IPC_NOWAIT,則semop()出錯返回EAGAIN。
(2)若未指定IPC_NOWAIT,則訊號量的semncnt值加1(因為呼叫程序將進 入休眠狀態),然後呼叫程序被掛起直至:①此訊號量變成大於或等於sem_op的絕對值;②從系統中刪除了此訊號量,返回EIDRM;③程序捕捉到一個信 號,並從訊號處理程式返回,返回EINTR。(與訊息佇列的阻塞處理方式 很相似)
3、訊號量是操作
semctl函式對一個訊號量執行各種控制操作。
#include <sys/sem.h> int semctl (int semid, int semnum, int cmd, union semun arg);
引數semid 訊號量集識別符號
引數semnum 訊號量集陣列上的下標,表示某一個訊號量
引數cmd指定以下10種命令中的一種,在semid指定的訊號量集合上執行此命令。
IPC_STAT讀取一個訊號量集的資料結構semid_ds,並將其儲存在semun中的buf引數中。 IPC_SET設定訊號量集的資料結構semid_ds中的元素ipc_perm,其值取自semun中的buf引數。 IPC_RMID將訊號量集從記憶體中刪除。 GETALL用於讀取訊號量集中的所有信號量的值。 GETNCNT返回正在等待資源的程序數目。 GETPID返回最後一個執行semop操作的程序的PID。 GETVAL返回訊號量集中的一個單個的訊號量的值。 GETZCNT返回這在等待完全空閒的資源的程序數目。 SETALL設定訊號量集中的所有的訊號量的值。 SETVAL設定訊號量集中的一個單獨的訊號量的值。
引數 arg
union semun { short val;/* SETVAL用的值 */ struct semid_ds* buf;/* IPC_STAT、IPC_SET用的semid_ds結構 */ unsigned short* array; /* SETALL、GETALL用的陣列值 */ struct seminfo *buf;/* 為控制IPC_INFO提供的快取 */ } arg;
四 訊號量值的初始化
semget並不初始化各個訊號量的值,這個初始化必須通過以SETVAL命令(設定集合中的一個值)或SETALL命令(設定集合中的所有值) 呼叫semctl來完成。
SystemV訊號量的設計中,建立一個訊號量集並將它初始化需兩次函式呼叫是一個致命的缺陷。一個不完備的解決方案是:在呼叫semget時指定IPC_CREAT | IPC_EXCL標誌,這樣只有一個程序(首先呼叫semget的那個程序)建立所需訊號量,該程序隨後初始化該訊號量。
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <assert.h> #include <time.h> #include <unistd.h> #include <sys/wait.h> #define MAX_SEMAPHORE 10 #define SEMKEY1 union semun { int val; struct semid_ds *buf; unsigned short *array; struct seminfo *_buf; } arg; int main() { key_t key; int semid ,ret, i; unsigned short buf[MAX_SEMAPHORE]; struct sembuf sb[MAX_SEMAPHORE]; pid_t pid; semid = semget(SEMKEY, MAX_SEMAPHORE, IPC_CREAT|0666); if (semid == -1) { fprintf(stderr, "Error in semget: %s\n", strerror(errno)); return -1; } printf("Semaphore get, ID is: %d\n",semid); for (i = 0; i < MAX_SEMAPHORE; i++) buf[i] = 0; arg.array = buf; ret = semctl(semid, 0, SETALL, arg); if (ret == -1) { fprintf(stderr, "Error in semctl: %s\n", strerror(errno)); return -2; } printf("Semaphore Init!\n"); pid = fork(); if (pid < 0) { fprintf(stderr, "Create Process Error!: %s\n",strerror(errno)); return -3; } if (pid == 0) { sleep(5); // 子程序產生訊號 printf("child wake up.\n"); for (i = 0; i < MAX_SEMAPHORE; i ++) { sb[i].sem_num = i; sb[i].sem_op = +1; sb[i].sem_flg = 0; } printf("child start to inc resource.\n"); ret = semop(semid, sb, 10); if (ret == -1) { fprintf(stderr, "子程序產生訊號失敗: %s\n", strerror(errno)); exit(-1); } printf("child exiting successfully.\n"); exit(0); } printf("parent wake up....\n"); // 此時父程序的阻塞,因為初始化為0 for (i = 0; i < MAX_SEMAPHORE; i++) { sb[i].sem_num = i; sb[i].sem_op = -1; sb[i].sem_flg = 0; } printf("parent is asking for resource...\n"); ret = semop(semid, sb, 10); //p() if (ret == 0) { printf("parent got the resource!\n"); } // 父程序等待子程序退出 waitpid(pid, NULL, 0); ret = semctl(semid, 0, IPC_RMID); if (ret == -1) { fprintf(stderr, "semaphore刪除失敗: %s\n", strerror(errno)); return -4; } printf("parent exiting.\n"); return 0; }