linux進程間通訊-信號量(semaphore)

一 爲何要使用信號量

爲了防止出現因多個程序同時訪問一個共享資源而引起的一系列問題,咱們須要一種方法,它能夠經過生成並使用令牌來受權,在任一時刻只能有一個執行線程訪問 代碼的臨界區域。臨界區域是指執行數據更新的代碼須要獨佔式地執行。而信號量就能夠提供這樣的一種訪問機制,讓一個臨界區同一時間只有一個線程在訪問它, 也就是說信號量是用來調協進程對共享資源的訪問的。其中共享內存的使用就要用到信號量

二 信號量的工做原理

因爲信號量只能進行兩種操做等待和發送信號,即P(sv)和V(sv),他們的行爲是這樣的:
P(sv):若是sv的值大於零,就給它減1;若是它的值爲零,就掛起該進程的執行
V(sv):若是有其餘進程因等待sv而被掛起,就讓它恢復運行,若是沒有進程因等待sv而掛起,就給它加1.
 
舉個例子,就是 兩個進程共享信號量sv,一旦其中一個進程執行了P(sv)操做,它將獲得信號量,並能夠進入臨界區,使sv減1。而第二個進程將被阻止進入臨界區,由於 當它試圖執行P(sv)時,sv爲0,它會被掛起以等待第一個進程離開臨界區域並執行V(sv)釋放信號量,這時第二個進程就能夠恢復執行。

三 Linux的信號量機制

Linux提供了一組精心設計的信號量接口來對信號進行操做,它們不僅是針對二進制信號量,下面將會對這些函數進行介紹,但請注意,這些函數都是用來對成組的信號量值進行操做的。它們聲明在頭文件sys/sem.h中。

四 信號號相關的兩個結構體

內核爲每一個信號量集合設置了一個semid_ds結構數組

struct semid_ds {
struct ipc_permsem_perm ;
structsem* sem_base ; //信號數組指針
ushort sem_nsem ; //此集中信號個數
time_t sem_otime ; //最後一次semop時間
time_t sem_ctime ; //最後一次建立時間
} ;
每一個信號量由一個無名結構表示,它至少包含下列成員: (這個是什麼意思??)
struct {
ushort_t semval ; //信號量的值
short sempid ; //最後一個調用semop的進程ID
ushort semncnt ; //等待該信號量值大於當前值的進程數(一有進程釋放資源 就被喚醒)
ushort semzcnt ; //等待該信號量值等於0的進程數
} ;

三 信號量的使用

一、建立信號量
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 ;
 
二、打開信號量
使用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。(與消息隊列的阻塞處理方式 很類似)
 三、信號量是操做
semctl函數對一個信號量執行各類控制操做。
#include <sys/sem.h>
int semctl (int semid, int semnum, int cmd, /*可選參數*/ ) ;
第四個參數是可選的,取決於第三個參數cmd。
參數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      設置信號量集中的一個單獨的信號量的值。

四 信號量值的初始化

semget並不初始化各個信號量的值,這個初始化必須經過以SETVAL命令(設置集合中的一個值)或SETALL命令(設置集合中的全部值) 調用semctl來完成。函數

SystemV信號量的設計中,建立一個信號量集並將它初始化需兩次函數調用是一個致命的缺陷。一個不完備的解決方案是:在調用semget時指定IPC_CREAT | IPC_EXCL標誌,這樣只有一個進程(首先調用semget的那個進程)建立所需信號量,該進程隨後初始化該信號量。spa

 五 例子

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/sem.h>
 
union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *arry;
};
 
static int sem_id = 0;
 
static int set_semvalue();
static void del_semvalue();
static int semaphore_p();
static int semaphore_v();
 
int main(int argc, char *argv[])
{
     char message = 'X';
     int i = 0;
 
    /* 建立信號量 */
    sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
 
    if(argc > 1)
    {
        /* 程序第一次被調用,初始化信號量 */
        if(!set_semvalue())
        {
            fprintf(stderr, "Failed to initialize semaphore\n");
            exit(EXIT_FAILURE);
        }
        /* 設置要輸出到屏幕中的信息,即其參數的第一個字符 */
        message = argv[1][0];
        sleep(2);
    }

    for(i = 0; i < 10; ++i)
    {
         /* 進入臨界區 */
        if(!semaphore_p())
        {
            exit(EXIT_FAILURE);
        }
        /* 向屏幕中輸出數據 */
        printf("%c", message);
        /* 清理緩衝區,而後休眠隨機時間 */
        fflush(stdout);
        sleep(rand() % 3);
        /* 離開臨界區前再一次向屏幕輸出數據 */
        printf("%c", message);
        fflush(stdout);
        /* 離開臨界區,休眠隨機時間後繼續循環 */
        if(!semaphore_v())
        {
            exit(EXIT_FAILURE);
        }
        sleep(rand() % 2);
    }
    sleep(10);
    printf("\n%d - finished\n", getpid());
 
    if(argc > 1)
    {
        /* 若是程序是第一次被調用,則在退出前刪除信號量 */
        sleep(3);
        del_semvalue();
    }
    exit(EXIT_SUCCESS);
}
static int set_semvalue() { /* 用於初始化信號量,在使用信號量前必須這樣作 */ union semun sem_union; sem_union.val = 1; if(semctl(sem_id, 0, SETVAL, sem_union) == -1) { return 0; } return 1; } static void del_semvalue() { /* 刪除信號量 */ union semun sem_union; if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1) { fprintf(stderr, "Failed to delete semaphore\n"); } }
static int semaphore_p() { /* 對信號量作減1操做,即等待P(sv)*/ struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = -1;//P() sem_b.sem_flg = SEM_UNDO; if(semop(sem_id, &sem_b, 1) == -1) { fprintf(stderr, "semaphore_p failed\n"); return 0; } return 1; } static int semaphore_v() { /* 這是一個釋放操做,它使信號量變爲可用,即發送信號V(sv)*/ struct sembuf sem_b; sem_b.sem_num = 0; sem_b.sem_op = 1;//V() sem_b.sem_flg = SEM_UNDO; if(semop(sem_id, &sem_b, 1) == -1) { fprintf(stderr, "semaphore_v failed\n"); return 0; } return 1; }

六 信號量集合的例子

#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 FILE_NAME "test2.c"
 
union semun{
    int val ;
    struct semid_ds *buf ;
    unsigned short *array ;
    struct seminfo *_buf ;
}arg;
struct semid_ds sembuf; int main() { key_t key ; int semid ,ret,i; unsigned short buf[MAX_SEMAPHORE] ; struct sembuf sb[MAX_SEMAPHORE] ; pid_t pid ; pid = fork() ; if(pid < 0) { /* Create process Error! */ fprintf(stderr,"Create Process Error!:%s\n",strerror(errno)); exit(1) ; }

if(pid > 0) { /* in parent process !*/ key = ftok(FILE_NAME,'a') ; if(key == -1) { /* in parent process*/ fprintf(stderr,"Error in ftok:%s!\n",strerror(errno)); exit(1) ; } semid = semget(key,MAX_SEMAPHORE,IPC_CREAT|0666); //建立信號量集合 if(semid == -1) { fprintf(stderr,"Error in semget:%s\n",strerror(errno)); exit(1) ; } printf("Semaphore have been initialed successfully in parent process,ID is :%d\n",semid); sleep(2) ; printf("parent wake up....\n"); /* 父進程在子進程獲得semaphore的時候請求semaphore,此時父進程將阻塞直至子進程釋放掉semaphore*/ /* 此時父進程的阻塞是由於semaphore 1 不能申請,於是致使的進程阻塞*/ for(i=0;i<MAX_SEMAPHORE;++i) { sb[i].sem_num = i ; sb[i].sem_op = -1 ; /*表示申請semaphore*/ 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); printf("parent exiting .. \n"); exit(0) ;
}
else { /* in child process! */ key = ftok(FILE_NAME,'a') ; if(key == -1) { /* in child process*/ fprintf(stderr,"Error in ftok:%s!\n",strerror(errno)); exit(1) ; } semid = semget(key,MAX_SEMAPHORE,IPC_CREAT|0666); if(semid == -1) { fprintf(stderr,"Error in semget:%s\n",strerror(errno)); exit(1) ; } printf("Semaphore have been initialed successfully in child process,ID is:%d\n",semid); for(i=0;i<MAX_SEMAPHORE;++i) { /* Initial semaphore */ buf[i] = i + 1; }
arg.array
= buf; ret = semctl(semid , 0, SETALL,arg); if(ret == -1) {
fprintf(stderr,
"Error in semctl in child:%s!\n",strerror(errno)); exit(1) ; } printf("In child , Semaphore Initailed!\n"); /* 子進程在初始化了semaphore以後,就申請得到semaphore*/ for(i=0;i<MAX_SEMAPHORE;++i) { sb[i].sem_num = i ; sb[i].sem_op = -1 ; sb[i].sem_flg = 0 ; }
ret
= semop(semid , sb , 10);//信號量0被阻塞 if( ret == -1 ) { fprintf(stderr,"子進程申請semaphore失敗:%s\n",strerror(errno)); exit(1) ; }
printf(
"child got semaphore,and start to sleep 3 seconds!\n"); sleep(3) ; 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 release the resource...\n"); ret = semop(semid, sb ,10) ; if(ret == -1) { fprintf(stderr,"子進程釋放semaphore失敗:%s\n",strerror(errno)); exit(1) ; }
ret
= semctl(semid ,0 ,IPC_RMID); if(ret == -1) { fprintf(stderr,"semaphore刪除失敗:%s!\n",strerror(errno)); exit(1) ; }
printf(
"child exiting successfully!\n"); exit(0) ;
}
return 0;
}

【信號量的意圖在於進程間同步,互斥鎖和條件變量的意圖則在於線程間同步。可是信號量也可用於線程間,互斥鎖和條件變量也可用於進程間。咱們應該使用適合具體應用的那組原語。】操作系統

相關文章
相關標籤/搜索