信號量是一個計數器,用于為多個進程提供對共享數據對象的訪問。
在信號量上只有三種操作可以進行,初始化、遞增和增加,這三種操作都是原子操作。遞減操作可以用于阻塞一個進程,增加操作用于解除阻塞一個進程。
為了獲得共享資源,需要測試信號量,若信號量為正,則進程可以使用該資源,這時信號量值減一。否則信號量值為0,進程進入休眠狀態。當進程不再使用由一個信號量控制的共享資源時,信號量值加一。如果有正在休眠的進程,則喚醒它們。常用的信號量的形式為二元信號量。即是原子性的。
信號量的功能 :負責數據操作的互斥、同步等功能。本質上是一種數據操作鎖。
我們為什么要使用信號量呢?為了防止出現因多個程序同時訪問一個共享資源而引發的一系列問題,我們需要一種方法,它可以通過生成并使?用令牌來授權,在任一時刻只能有一個執行線程訪問代碼的臨界區域。臨界區域是指執行數據更新的代碼需要獨占式地執行。而信號量就可以提供這樣的一種訪問機制,讓一個臨界區同一時間只有一個線程在訪問它,也就是說信號量是用來協調進程對共享資源的訪問的。其中共享內存的使用就要用到信號量。
信號量只能進行兩種操作等待和發送信號,PV操作 ,即P(sv)和V(sv),P申請資源,則將可用資源數-1,V釋放資源,則將可用資源數+1。
內核為每個信號量集合維護著一個semid_ds結構:
struct semid_ds
{struct ipc_perm sem_perm;unsigned short sem_nsems; time_t sem_otime;time_t sem_ctime;
};
每個信號量都有一個無名的結構:
unsigned short semval; unsigned short semzcnt; unsigned short semncnt; pid_t sempid;
當我們想使用信號量時,首先要通過調用函數semget來獲得一個信號量ID:
#include<sys/sem.h>
int semget(key_t key,int nsems,int flag);
semctl函數包含了多種信號量操作
int semctl(int semid,int semnum,int cmd,...);
union semun
{int val; struct semid_ds *buf; unsigned short *array ;
};
我們通常使用的:IPC_RMID:從系統中刪除該信號量集合
SETVAL:設置成員semnum的semval值,該值由arg.val指定
函數semop自動執行信號量集合上的操作數組。
int semop(int semid,struct sembuf semoparray[],size_t nops);
struct sembuf
{unsigned short sem_num; short sem_op; short sem_flg;
};
對集合中每個成員的操作由相應的sem_op值規定。此值可以為負值,0,正值。最易于處理的是為正值,說明需要釋放資源,則sem_op的值會加到信號量值上,如果指定了undo標志,則從此信號量調整之后的值上減去sem_op;如果sem_op是負值,說明需要申請資源,則信號量會減去sem_op的絕對值,如果指定undo標志,則sem_op的絕對值也加到信號量的調整值上。sem_op為0,表示調用進程希望等待到該信號量值為0。
對于信號量調整,如果在進程終止時,它占用了經由信號量分配的資源,那么就會成為一個問題。無論何時只要為信號量操作指定了SEM_UNDO標志,然后分配資源(sem_op< 0),那么內核就會記住該特定信號量,分配給調用進程多少資源。對每個操作都指定SEM_UNDO,以處理在未釋放資源條件下進程終止的情況。
信號量主要解決互斥與同步問題,下面舉個栗子:(實現父子進程輸出成對AA或BB)
#ifndef _COMM_H_
#define _COMM_H_ #include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h> #define PATHNAME "."
#define PROJ_ID 0X6666
union semun {int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf; };int CreateSem(int nums);
int GetSem(int nums);
int DestroySem(int semid);
int initSem(int semid,int nums,int initval);
int SemP(int semid,int who);
int SemV(int semid,int who);#endif //_COMM_H_
static int CommSemSet(int nsems,int flags)
{key_t key = ftok(PATHNAME,PROJ_ID);if (key < 0 ){//printf ("%d \n" , key);perror("ftok" );return -1 ;}int semid = semget (key,nsems,flags);if (semid < 0 ){perror("semget" );return -2 ;}return semid;
}int CreateSemSet(int nums)
{return CommSemSet(nums,IPC_CREAT|IPC_EXCL|0666 );
}int GetSem(int nums)
{return CommSemSet(nums,IPC_CREAT);
}
int DestroySem(int semid)
{if (semctl (semid,0 ,IPC_RMID) < 0 ){perror("semctl" );return -1 ;}return 0 ;
}int initSem(int semid,int nums,int initval)
{union semun _un;_un.val = initval;if (semctl (semid,nums,SETVAL,_un) < 0 ){perror("semctl" );return -1 ;}return 0 ;
}static int CommPV(int semid,int who,int op)
{struct sembuf _sem;_sem.sem_num = who;_sem.sem_op = op;_sem.sem_flg = 0 ;if (semop (semid,&_sem,1 ) < 0 ){perror("semop" );return -1 ;} return 0 ;
}
int SemP(int semid,int who)
{return CommPV(semid,who,-1 );
}
int SemV(int semid,int who)
{return CommPV(semid,who,1 );
}
#include"comm.h" int main()
{int semid = CreateSemSet(1 );initSem(semid,0 ,1 ); pid_t id = fork();if (id == 0 ){int _semid = GetSem(0 );while (1 ){SemP(_semid,0 );printf ("A" );fflush(stdout);usleep(123456 );printf ("A" );fflush(stdout);usleep(345678 ); SemV(_semid,0 );}}else {while (1 ){SemP(semid,0 );printf ("B" );fflush(stdout);usleep(234567 );printf ("B" );fflush(stdout);usleep(456789 );SemV(semid,0 );}wait(NULL);}DestroySem(semid);printf ("sem quit!\n" );return 0 ;
}
//Makefile
sem: sem.c comm.c gcc -o $@ $^
.PHONY: clean
clean: rm -f sem
運行結果: