信號量的相關概念:
信號量
信號量本質上是一個計數器(不設置全局變量是因爲進程間是相互獨立的,而這不一定能看到,看到也不能保證++引用計數爲原子操作),用於多進程對共享數據對象的讀取,它和管道有所不同,它不以傳送數據爲主要目的,它主要是用來保護共享資源(信號量也屬於臨界資源),使得資源在一個時刻只有一個進程獨享。
在瞭解信號量之前,我們先來看幾個概念:
臨界資源:兩個進程看到的同一個公共的資源,但是同時只能被一個進程所使用的的資源叫做臨界資源(互斥資源)
臨界區:在晉城中涉及到互斥資源的程序段叫臨界區
信號量主要用於同步和互斥,下面我們來看看什麼是同步和互斥。
互斥:各個進程都要訪問共享資源,但共享資源是互斥的,同時只能有一個進程使用。因此,各個進程之間競爭使用這些資源,將這種關係稱爲互斥。
同步:多個進程需要相互配合共同完成一項任務。
信號量的工作機制
簡單說一下信號量的工作機制,可以直接理解成計數器,信號量會有初值(>0),每當有進程申請使用信號量,通過一個P操作來對信號量進行-1操作,當計數器減到0的時候就說明沒有資源了,其他進程要想訪問就必須等待,當該進程執行完這段工作(我們稱之爲臨界區)之後,就會執行V操作來對信號量進行+1操作。
P:如果sv的值大於零,就給它減1;如果它的值爲零,就掛起該進程的執行
V:如果有其他進程因等待sv而被掛起,就讓它恢復運行,如果沒有進程因等待sv而掛起,就+1。
在信號量進行PV操作時都爲原子操作(因爲它需要保護臨界資源)
信號量的相關函數:
信號量結構體
struct semaphore
{
int value;
pointer_PCB queue;
}
P原語
P(s)
{
s.value = s.value--;
if (s.value < 0)
{
該進程狀態置爲等待狀態
將該進程的PCB插入相應的等待隊列s.queue末尾
}
}
V原語V(s)
{
s.value = s.value++;
if (s.value < =0)
{
喚醒相應等待隊列s.queue中等待的一個進程
改變其狀態爲就緒態
並將其插入就緒隊列
}
}
信號量集結構
struct semid_ds {
struct ipc_perm sem_perm; /* Ownership and permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned short sem_nsems; /* No. of semaphores in set */
};
信號集函數:
semget函數:
功能:⽤用來創建和訪問⼀一個信號量集
原型
int semget(key_t key, int nsems, int semflg);
參數
key: 信號集的名字
nsems:信號集中信號量的個數
semflg: 由九個權限標誌構成,它們的⽤用法和創建⽂文件時使⽤用的mode模式標誌是⼀一樣的
返回值:成功返回⼀一個⾮非負整數,即該信號集的標識碼;失敗返回-1
shmctl函數:
功能:⽤用於控制信號量集
原型
int semctl(int semid, int semnum, int cmd, ...);
參數
semid:由semget返回的信號集標識碼
semnum:信號集中信號量的序號
cmd:將要採取的動作(有三個可取值)
最後⼀一個參數根據命令不同⽽而不同
返回值:成功返回0;失敗返回-1
semop函數:
功能:⽤用來創建和訪問⼀一個信號量集
原型
int semop(int semid, struct sembuf *sops, unsigned nsops);
參數
semid:是該信號量的標識碼,也就是semget函數的返回值
sops:是個指向⼀一個結構數值的指針
nsops:信號量的個數
返回值:成功返回0;失敗返回-1
說明:
sembuf結構體:
struct sembuf {
short sem_num;
short sem_op;
short sem_flg;
};
sem_num是信號量的編號。
sem_op是信號量⼀一次PV操作時加減的數值,⼀一般只會⽤用到兩個值:
⼀一個是“-1”,也就是P操作,等待信號量變得可⽤用;
另⼀一個是“+1”,也就是我們的V操作,發出信號量已經變得可⽤用
sem_flag的兩個取值是IPC_NOWAIT或SEM_UNDO
同消息隊列類似,這裏也可以使用ipcs -s查看IPC資源,使用ipcrm -s刪除IPC資源:
信號量實例:
應用以上函數,編寫一段代碼:
創建Makefile:
test_sem:comm.c test_sem.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f test_sem
comm.h
#ifndef _COMM_H_
#define _COMM_H_
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union semun
{
int val; /* value for setval */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO*/
};
int createSemSet(int nums);
int initSem(int semid, int nums, int initval);
int getSemSet(int nums);
int P(int semid, int who);
int V(int semid, int who);
int destorySemSet(int semid);
#endif
comm.c
#include "comm.h"
static int commSemSet(int nums, int flags)
{
key_t key = ftok("/tmp", 0x6666);
if(key < 0)
{
perror("ftok");
return -1;
}
int semid = semget(key, nums, flags);
if(semid < 0)
{
perror("semget");
return -2;
}
return semid;
}
int createSemSet(int nums)
{
return commSemSet(nums, IPC_CREAT|IPC_EXCL|0666);
}
int getSemSet(int nums)
{
return commSemSet(nums, IPC_CREAT);
}
int initSemSet(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 _sf;
_sf.sem_num = who;
_sf.sem_op = op;
_sf.sem_flg = 0;
if(semop(semid, &_sf, 1) < 0)
{
perror("semop");
return -1;
}
return 0;
}
int P(int semid, int who)
{
return commPV(semid, who, -1);
}
int V(int semid, int who)
{
return commPV(semid, who, 1);
}
int destorySemSet(int semid)
{
if(semctl(semid, 0, IPC_RMID) < 0)
{
perror("semctl");
return -1;
}
return 0;
}
test_sem.c
#include "comm.h"
int main()
{
int semid = createSemSet(1);
initSemSet(semid, 0, 1);
pid_t pid = fork();
if(pid == 0)
{
//child
int _semid = getSemSet(0);
while(1)
{
P(_semid, 0);
printf("A");
fflush(stdout);
usleep(123456);
printf("A ");
fflush(stdout);
usleep(123456);
V(_semid, 0);
}
}
else
{
//parent
while(1)
{
P(semid, 0);
printf("B");
fflush(stdout);
usleep(123456);
printf("B ");
fflush(stdout);
usleep(123456);
V(semid, 0);
}
wait(NULL);
}
destorySemSet(semid);
return 0;
}
未使用PV操作時結果:
加入PV操作時的結果:
我們可以看到所有的AB都是成對出現的,不會出現交叉的情況。這是因爲P、V操作實現過程中具有原子性,能夠實現對臨界區的管理,它的執行是不會受其他進程的影響。