信號量是一種用於提供不同進程間或一個給定進程的不同線程件同步手段的原語.信號量是一個特殊的整數值,主要用來控制多個進程對臨界資源的互斥訪問,進程根據信號量來判斷是否有 訪問的資源。
信號量是一個計數器,可用於同步多進程對共享數據對象得訪問,為了獲得共享資源,進程需要執行以下操作:
1、測試控制該資源的信號量
2、若此信號量的值為正,則進程可以使用該資源,進程將信號量值減1,表示它使用了一個資源單位
3、若此信號量的值為0,則進程進入睡眠狀態,直至信號量值大於0。當進程被喚醒後,它返回至第1步。
常用的信號量一般初始值為1,只控制單個資源,有時也稱互斥鎖,但是,信號量得初值可以是任意一正值,該值說明有多少個共享資源單位可供共享應用,信號量有以下3個特性:
1、信號量並非是一個非負值,而必須將信號量定義為含有一個或多個信號量值得集合,當創建一個信號量時,要指定該集合中的各個值。
sreuct sem
{
ushort_t semvl;
short sempid;
ushort semncnt;
ushort semzcnt;
};
2、創建信號量對其賦初值分開,這是一個致命弱點,因為不能原子地創建一個信號量集合,並且對該集合中的所有值賦初值。
3、即使沒有進程使用,但他們仍然存在,因此必須考慮在進程終止時有沒有釋放得信號量。
以上的三個特性就導致了信號使用的復雜性。
信號量的值通過P、V原語來進行操作改變的。(p為減操作,v為加操作)
在Linux中,系統提供了信號量的操作函數,主要有以下函數:
1、key_t ftok(char *pathname, char proj);
根據參數pathname和proj 來創建一個關鍵字,成功時返回與路徑pathname相對應的一個鍵值,具有唯一性,失敗時返回值為-1.
2、int semget (key_t key, int nsems , int semflg);
創建一個新信號量或者取得一個現有的信號量,key是一個關鍵字,是用ftok()函數創建。nsems表明創建的信號量個數,semflg是設置信號量的訪問權限標志,函數調用成功時返回信號量ID,失敗則返回-1.
3、int semop (int semid, struct sembuf *spos, int nspos);
對信號量進行操作的函數,用於改變信號量的鍵值,semid是信號量的標志,spos是指向一個結構體數組的指針,表明要進行什麼操作,nspos表明數組的元素個數,調用成功則返回0,失敗則返回-1.
4、semctl(int semid , int semnum , int cmd , …/* union semun arg */);
semid是信號量標志,semnum是指該信號量集合內的某個成員(用下標表示0-nsems-1)。semnum只用於GETVAL、SETVAL、GETNCNT、GETZCNT、GETPID命令。參數cmd是可選的,它支持以下命令:
GETVAL:把semval的當前值返回。
SETVAL:設置senval值
GETPID:把當前sempid當作函數返回值
GETNCNT:把semncnt的當前值當作函數的返回值
GETZCNT:把semzcnt的當前值當作函數的返回值
IPC_RMID:把由sem_id指定的信號量從系統中刪除
IPC_SET:設定指定信號量集合的semid_ds結構中的三個成員
IPC_STAT:返回所指定信號量當前的semid_ds結構
SETALL:設定所指定信號量集合中每個成員的semval值
GETALL:返回所指定信號量集合中每個成員的當前semval的值
Struct sembuf
{
Unsigned short sem_num; /*sem index in array*/
Short sem_op; /* sem operation */
Short sem_flg;/* operation flags */ sem_flg&IPC_RND 0
};
其中,如果sem_op大於0,那麼操作值加入到信號量的值中,並喚醒等待信號增加的進程,如果sem_op為0,當信號量的值是0的時候,函數返回,否則阻塞直到信號量的值為0,如果sem_op小於0,函數判斷信號量的值加上這個負值,如果結果為0喚醒等待信號量為0的進程,如果小於0函數阻塞,如果大於0,那麼從信號量裡面減去這個值並返回。
4、int semctl (int semid, int semnum, int cmd, union semun arg);
該函數得作用是對信號量進行一系列得控制,semid是要操作得信號量標志,semnum是信號量得下標,cmd是操作的命令,經常用的兩個命令是:SETVAL、IPC_RMID,arg用於設置或返回信號量信息。
Union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
void *__pad;
}
下面是信號量簡單應用的小程序:
1、semcreate.cpp:創建一個信號量
#include "utili.h"
int main(int argc, char *argv[])
{
//通過ftok函數構造唯一的鍵值,路徑為所傳參數,構造完並檢驗鍵值是否構造成功
key_t key_id, sem_id;
key_id = ftok(argv[1], 0xFF);
if(key_id == -1){
printf("ftok error.\n");
exit(1);
}
//鍵值構造成功後,用所獲得的鍵值創建一個信號量
sem_id = semget(key_id, 1, IPC_CREAT|IPC_EXCL|0666);//信號量的權限為要麼創建,要麼返回EEXIST錯誤,所屬用戶、所屬組、其他用戶都可讀可寫
if(sem_id == -1){ //判斷信號量創建是否成功
printf("semget error.\n");
exit(1);
}
//創建成功
printf("semget ok. sem id = %d\n", sem_id);
return 0;
}
2、semgetvalue.cpp:得到信號量的值
#include "utili.h"
int main(int argc, char *argv[])
{
key_t key_id, sem_id;
key_id = ftok(argv[1], 0xFF); //獲得在semcreate.cpp中產生的鍵值
sem_id = semget(key_id, 0, 0); //利用該建值來獲得semcreate.cpp中創建的信號量
int sem = semget(sem_id, 0, GETVAL); //利用semget函數得到信號量的值
printf("sem value = %d\n", sem);
return 0;
}
3、semsetvalue.cpp
#include "utili.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 */
void *__pad;
};
int main(int argc, char *argv[])
{
//與semgetvalue.cpp中一樣,為了獲得已創建的信號量的sem_id。
key_t key_id, sem_id;
key_id = ftok(argv[1], 0xFF);
sem_id = semget(key_id, 0, 0);
if(sem_id == -1){
printf("semget error.\n");
exit(1);
}
//定義一個semun類型的共用體,將傳進來的第二個參數當作內部的val值
union semun init;
init.val = atoi(argv[2]);
//通過控制函數將init.val值賦給該信號量,命令SETVAL表明該函數現在被用來執行設定值的操作
int res = semctl(sem_id, 0, SETVAL, init);
//判斷是否設置成功,失敗返回-1.
if(res == -1){
printf("set value fail.\n");
exit(1);
}
printf("set value ok.\n");
return 0;
}
4、semopde.cpp:信號量的減操作
#include "utili.h"
int main(int argc, char *argv[])
{
//與semgetvalue.cpp中一樣,為了獲得已創建的信號量的sem_id。
key_t key_id, sem_id;
key_id = ftok(argv[1], 0xFF);
sem_id = semget(key_id, 0, 0);
//定義一個sembuf類型的結構體P,並生定它內部的成員的值
struct sembuf p;
p.sem_num = 0; //表明該操作只對高信號量集合中0下標的信號量進行操作
p.sem_op = -1; //表明執行一次該操作,所對應信號量加-1
p.sem_flg = 0; //信號量的標志
//通過semop函數對某個信號量集執行一組數組(上面結構體p)的操作
int res = semop(sem_id, &p, 1);
if(res == -1){
printf("operator sem decreament fail.\n");
}
printf("operator sem decreament ok.\n");
return 0;
}
5、semopin.cpp:執行信號量集合的加操作
#include "utili.h"
int main(int argc, char *argv[])
{
//與semgetvalue.cpp中一樣,為了獲得已創建的信號量的sem_id。
key_t key_id, sem_id;
key_id = ftok(argv[1], 0xFF);
sem_id = semget(key_id, 0, 0);
//定義一個sembuf類型的結構體v,並生定它內部的成員的值
struct sembuf v;
v.sem_num = 0; //表明該操作只對高信號量集合中0下標的信號量進行操作
v.sem_op = 1; //表明執行一次該操作,所對應信號量加1
v.sem_flg = 0; //信號量的標志
//通過semop函數對某個信號量集執行一組數組(上面結構體p)的操作
int res = semop(sem_id, &v, 1);
if(res == -1){
printf("operator increament fail.\n");
}
printf("operator increament ok.\n");
return 0;
}
6、semrmid.cpp:刪出信息量集
#include "utili.h"
int main(int argc, char *argv[])
{
//與semgetvalue.cpp中一樣,為了獲得已創建的信號量的sem_id
key_t key_id, sem_id;
key_id = ftok(argv[1], 0xFF);
sem_id = semget(key_id, 0, 0);
//通過semctl函數執行IPC_RMID命令刪除所創建的信號量
semctl(sem_id, 0, IPC_RMID);
printf("remove ok. sem id = %d\n", sem_id);
return 0;
}
7、Makefile文件:【Makefile文件中每個執行語句前的空格必須用tale鍵,否則會報錯】
all:semcreate semsetvalue semgetvalue semopin semopde semrmid
semcreate:semcreate.cpp
g++ -o semcreate semcreate.cpp
semsetvalue:semsetvalue.cpp
g++ -o semsetvalue semsetvalue.cpp
semgetvalue:semgetvalue.cpp
g++ -o semgetvalue semgetvalue.cpp
semopin:semopin.cpp
g++ -o semopin semopin.cpp
semopde:semopde.cpp
g++ -o semopde semopde.cpp
semrmid:semrmid.cpp
g++ -o semrmid semrmid.cpp
.PHONY:clean
clean:
rm semcreate semsetvalue semgetvalue semopin semopde semrmid
8、utili.h:頭文件
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define ID 0xFF
上面只是信號量的簡單程序的實現,並沒有很好的體現信號量的同步機制。後面會繼續整理相關內容。