進程間通訊之信號量ide
1、什麼是信號量函數
爲了防止出現因多個程序同時訪問一個共享資源而引起的一系列問題,咱們須要一種方法,它能夠經過生成並使用令牌來受權,在任一時刻只能有一個執行線程訪問代碼的臨界區域。臨界區域是指執行數據更新的代碼須要獨佔式地執行。而信號量就能夠提供這樣的一種訪問機制,讓一個臨界區同一時間只有一個線程在訪問它,也就是說信號量是用來調協進程對共享資源的訪問的。測試
信號量是一個特殊的變量,程序對其訪問都是原子操做,且只容許對它進行等待(即P(信號變量))和發送(即V(信號變量))信息操做。最簡單的信號量是隻能取0和1的變量,這也是信號量最多見的一種形式,叫作二進制信號量(二元信號量)。而能夠取多個正整數的信號量被稱爲通用信號量。這裏主要討論二進制信號量。spa
2、信號量的工做原理操作系統
因爲信號量只能進行兩種操做等待和發送信號,即P(sv)和V(sv),他們的行爲是這樣的:線程
P(sv):若是sv的值大於零,就給它減1;若是它的值爲零,就掛起該進程的執行設計
V(sv):若是有其餘進程因等待sv而被掛起,就讓它恢復運行,若是沒有進程因等待sv而掛起,就給它加1。orm
舉個例子:接口
兩個進程共享信號量sv,一旦其中一個進程執行了P(sv)操做,它將獲得信號量,並能夠進入臨界區,使sv減1。而第二個進程將被阻止進入臨界區,由於當它試圖執行P(sv)時,sv爲0,它會被掛起以等待第一個進程離開臨界區域並執行V(sv)釋放信號量,這時第二個進程就能夠恢復執行。進程
3、Linux的信號量機制
Linux提供了一組精心設計的信號量接口來對信號進行操做,它們不僅是針對二進制信號量,下面將會對這些函數進行介紹,但請注意,這些函數都是用來對成組的信號量值進行操做的。它們聲明在頭文件sys/sem.h中。
一、semget函數
它的做用是建立一個新信號量或取得一個已有信號量,原型爲:
int semget(key_t key, int num_sems, int sem_flags);
第一個參數key是整數值(惟一非零),不相關的進程能夠經過它訪問一個信號量,它表明程序可能要使用的某個資源,程序對全部信號量的訪問都是間接的,程序先經過調用semget函數並提供一個鍵,再由系統生成一個相應的信號標識符(semget函數的返回值),只有semget函數才直接使用信號量鍵,全部其餘的信號量函數使用由semget函數返回的信號量標識符。若是多個程序使用相同的key值,key將負責協調工做。
第二個參數num_sems指定須要的信號量數目,它的值幾乎老是1。
第三個參數sem_flags是一組標誌,當想要當信號量不存在時建立一個新的信號量,能夠和值IPC_CREAT作按位或操做。設置了IPC_CREAT標誌後,即便給出的鍵是一個已有信號量的鍵,也不會產生錯誤。而IPC_CREAT | IPC_EXCL則能夠建立一個新的,惟一的信號量,若是信號量已存在,返回一個錯誤。
semget函數成功返回一個相應信號標識符(非零),失敗返回-1.
二、semop函數
它的做用是改變信號量的值,原型爲:
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
sem_id是由semget返回的信號量標識符,sembuf結構的定義以下:
struct sembuf{
short sem_num; //除非使用一組信號量,不然它爲0
short sem_op; //信號量在一次操做中須要改變的數據,一般是兩個數,
//一個是-1,即P(等待)操做,
//一個是+1,即V(發送信號)操做。
short sem_flg; //一般爲SEM_UNDO,使操做系統跟蹤信號,
//並在進程沒有釋放該信號量而終止時,操做系統釋放信號量
};
num_sem_ops是信號操做結構的數量,恆大於或等於1。
三、semctl函數
該函數用來直接控制信號量信息,它的原型爲:
int semctl(int sem_id, int sem_num, int command, ...);
若是有第四個參數,它一般是一個union semum結構,定義以下:
union semun{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
sem_id是由semget返回的信號量標識符。sem_num參數是操做信號在信號集中的編號,第一個信號的編號是0。command一般是下面兩個值中的其中一個
SETVAL:用來把信號量初始化爲一個已知的值。p 這個值經過union semun中的val成員設置,其做用是在信號量第一次使用前對它進行設置。
IPC_RMID:用於刪除一個已經無需繼續使用的信號量標識符。
舉個例子:
下面這個例子由兩個進程(父子進程)同時向屏幕輸出字符,父進程每次輸出兩個‘B’,子進程每次輸出兩個‘A’,若是不加信號量控制的話,則輸出的字符並無規律。
此時利用一個二元信號量加以控制,則輸出的字符變得有規律了。
程序組成:
comm.h:封裝所需的函數聲明
comm.c:函數的實現
test_sem.c:測試代碼
代碼:
comm.h
#pragma once #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <unistd.h> #include <errno.h> #define _PROJ_NAME_ "./tmp" #define _PROJ_ID_ 0x6666 //struct sembuf //{ // unsigned short sem_num; /* semaphore number */ // short sem_op; /* semaphore operation */ // short sem_flg; /* operation flags */ //}; int creat_sem_set(int sems); int init_sem_set(int msg_id, int which, int val); int get_sem_set(); int destory_sem_set(int msg_id); int P(int sem_id, int which); int V(int sem_id, int which);
comm.c
#include "comm.h" static int comm_sem_set(int sems, int flag) { key_t _key = ftok(_PROJ_NAME_, _PROJ_ID_); if(_key < 0) { perror("ftok"); return -1; } int sem_id = semget(_key, sems, flag); if(sem_id < 0) { perror("semget"); return -2; } return sem_id; } int create_sem_set(int sems) { int flag = IPC_CREAT | IPC_EXCL | 0644; return comm_sem_set(sems, flag); } int init_sem_set(int msg_id, int which, int val) { int ret = semctl(msg_id, which, SETVAL, val); if(ret < 0) { perror("semctl"); return -1; } return 0; } int get_sem_set() { int flag = IPC_CREAT; return comm_sem_set(0, flag); } int destory_sem_set(int sem_id) { int ret = semctl(sem_id, 0, IPC_RMID); if(ret < 0) { perror("semctl"); return -1; } return 0; } static int op(int sem_id, int which, int op) { struct sembuf _sem; _sem.sem_num = which; _sem.sem_op = op; _sem.sem_flg = 0; int ret = semop(sem_id, &_sem, 1); if(ret < 0) { perror("semop"); return -1; } return 0; } int P(int sem_id, int which) { return op(sem_id, which, -1); } int V(int sem_id, int which) { return op(sem_id, which, +1); }
test_sem.c
#include "comm.h" int main() { int sem_id = create_sem_set(1); init_sem_set(sem_id, 0, 1); pid_t id = fork(); if(id == 0) //child { int c_sem_id = get_sem_set(); while(1) { P(c_sem_id, 0); printf("A"); fflush(stdout); usleep(rand()%123456); printf("A"); fflush(stdout); usleep(rand()%12345); V(c_sem_id, 0); } } else // father { while(1) { P(sem_id, 0); printf("B"); fflush(stdout); usleep(rand()%123459); printf("B"); fflush(stdout); usleep(rand()%12344); V(sem_id, 0); } wait(NULL); destory_sem_set(sem_id); } return 0; }
總結
信號量是一個特殊的變量,程序對其訪問都是原子操做,且只容許對它進行等待(即P(信號變量))和發送(即V(信號變量))信息操做。咱們一般經過信號來解決多個進程對同一資源的訪問競爭的問題,使在任一時刻只能有一個執行線程訪問代碼的臨界區域,也能夠說它是協調進程間的對同一資源的訪問權,也就是用於同步進程的。