1. 信号量的基本概念
信号量主要用于同步與互斥的,先簡單的說幾個概念吧
- 原子性:表示一個事件的兩種狀态,要麼做了,要麼沒有做,沒有第三種狀态;
- 同步:在多道程式環境下,程序是并發執行的,不同程序之間存在着不同互相制約的關系;
- 互斥:在一段時間内,資源隻允許被一個程序通路;
- 臨界資源:像列印機這類一次隻允許一個程序使用的資源;
- 臨界區:多個程序通路臨界資源的那一段代碼。
那麼什麼是信号量呢,我們可以簡單的了解成計數器(當然實際上并沒有那麼簡單)描述資源的多少,它本身不具備資料交換的功能,而是通過控制臨界資源來實作程序間通信。
簡單說下信号量的工作機制,我麼說信号量可以了解為一個計數器,它有一個初值(>0),每當有程序申請使用信号量時,通過P操作來對信号量進行-1;當計數器減到0的時候,其他程序想要通路資源,就需要挂起等待,直到該程序執行完操作,通過V操作對信号量+1釋放資源。是以,我們說信号量也是程序間通信的一種方式,比如互斥鎖的簡單實作就是二進制信号量,一個程序在使用互斥鎖時,通知其他程序,阻止他們的通路挂起等。舉一個例子:假如我們一趟火車隻有一張火車票了,但是有兩個人都要買,其中一個人先申請到了資源,買到了票,那麼另一人隻能等,等到如果有人退票或者改簽,他就可以買到票了。
2.信号量的操作函數
信号量的操作函數與消息隊列也是十分類似的:
semget
- 功能:用來建立和通路一個信号
- 原型:
int semget(key_t key, int nsems, int semflg);
- 參數:
- key:信号集的名字,與消息隊列類似,不在詳細的說了
- nsems:信号集中信号量的個數,我們使用semget這個函數建立的是一個信号集,包括了好多信号量的, 當然可以選擇隻建立一個
- semflg: 兩個參數IPC_CREAT和IPC_EXCL,與之前的消息隊列也類似-
- 傳回值:成功傳回⼀一個⾮非負整數,即該信号集的辨別碼;失敗傳回-1
semctl
- 功能:⽤用于控制信号量集
- 原型:
int semctl(int semid, int semnum, int cmd, ...);
- 參數:
- semid:由semget傳回的信号集辨別碼
- semnum:信号集中信号量的序号,序号從0開始
- cmd:将要采取的動作(有三個可取值)
- 最後⼀一個參數根據指令不同⽽而不同
-
傳回值:成功傳回0;失敗傳回-1
這個函數,我們初始化和删除都會用到它,當第三個參數為IPC_RMID時,用于删除信号集;當第三個參數設定為SETVAL時,可以用于信号量的初始化,但此時就需要第四個參數了;第四個參數是這樣的,它需要加入一個聯合體:
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 */
}
semop
- 功能:⽤來實作PV操作的,這個函數還用得到了一個結構體
- 原型:
int semop(int semid, struct sembuf *sops, unsigned nsops);
- 參數:
- semid:是該信号量的辨別碼,semget函數的傳回值
- sops:指向一個結構體的指針
- nsops:信号量的個數
- 傳回值:成功傳回0;失敗傳回-1
這個結構體是這樣的:
struct sembuf
{
short sem_num;//sem_num是信号量的編号
short sem_op;//sem_op是信号量⼀次PV操作時加減的數值,一般隻會⽤用到兩個值:一個是“-1”,也就是P操作,等待信号量變得可⽤;另⼀個是“+1”,也就是我們的V操作,發出信号量已經變得可⽤
short sem_flg;//sem_flag的兩個取值是IPC_NOWAIT或SEM_UNDO
};
同消息隊列類似,這裡也可以使用ipcs -s檢視IPC資源,使用ipcrm -s删除IPC資源。
這裡需要注意的是:信号量與信号并不一樣,有的同學可能會把這兩個概念搞混了,信号量是用來作業系統程序間同步通路共享資源。信号是用來通知程序發生了異步事件,雖然二者名字上很相似,但本質上相差很大。
3.執行個體
下面我們就用一段很經典的代碼使用這些函數:
先寫一個Makefile
sem_test:sem_test.c comm.c
gcc -o [email protected] $^
.PHONY:clean
clean:
rm -f sem_test
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", x6666);
if(key < )
{
perror("ftok");
return -;
}
int semid = semget(key, nums, flags);
if(semid < )
{
perror("semget");
return -;
}
return semid;
}
int createSemSet(int nums)
{
return commSemSet(nums, IPC_CREAT|IPC_EXCL|666);
}
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) < )
{
perror("semctl");
return -;
}
return ;
}
static int commPV(int semid, int who, int op)
{
struct sembuf _sf;
_sf.sem_num = who;
_sf.sem_op = op;
_sf.sem_flg = ;
if(semop(semid, &_sf, ) < )
{
perror("semop");
return -;
}
return ;
}
int P(int semid, int who)
{
return commPV(semid, who, -);
}
int V(int semid, int who)
{
return commPV(semid, who, );
}
int destorySemSet(int semid)
{
if(semctl(semid, , IPC_RMID) < )
{
perror("semctl");
return -;
}
return ;
}
test.c
#include "comm.h"
int main()
{
int semid = createSemSet();
initSemSet(semid, , );
pid_t pid = fork();
if(pid == )
{
//child
int _semid = getSemSet();
while()
{
P(_semid, );
printf("A");
fflush(stdout);
usleep();
printf("A ");
fflush(stdout);
usleep();
V(_semid, );
}
}
else
{
//parent
while()
{
P(semid, );
printf("B");
fflush(stdout);
usleep();
printf("B ");
fflush(stdout);
usleep();
V(semid, );
}
wait(NULL);
}
destorySemSet(semid);
return ;
}
看上面的測試程式,使用fork()建立了兩個程序,但是兩個程序交替執行,是以如果沒有使用PV操作的話,列印出來的是醬紫的:
但是如果我們加了PV操作,這樣就可以保證A和B是成都存在的了