之前有一篇文章寫到,使用while true 加sleep進行消息監聽操作。然而,使用while操作,其實是一種忙等狀態,會讓系統很忙。那有沒有一種不忙的操作的方式呢?
應該是有的,本文講些信號量方面的知識。讓我看看他都能做什么。
我們有過多線程編程經驗同學肯定都知道,同步鎖(如java的sychronized),等待wait,通知notify,等等。其實就是干這事。
一 信號量的分類
信號量,也叫信號燈,是一個確定的二元組(S,Q),其中S是個具有非負初值的整型變量,且S的值只能由定義在信號量上的P操作原語和V操作原語來改變,而Q是個初始狀態為空的隊列。
整型信號量(integer semaphore):信號量是整數記錄型信號量(record semaphore):每個信號量s除一個整數值s.value(計數)外,還有一個進程等待隊列s.L,其中是阻塞在該信號量的各個進程的標識二進制信號量(binary semaphore):只允許信號量取0或1值每個信號量至少須記錄兩個信息:信號量的值和等待該信號量的進程隊列。它的類型定義如下:(用類PASCAL語言表述)semaphore = recordvalue: integer;queue: ^PCB;end;其中PCB是進程控制塊,是操作系統為每個進程建立的數據結構。s.value>=0時,s.queue為空;s.value<0時,s.value的絕對值為s.queue中等待進程的個數;
P(wait)、V(signal)操作原語如下:
P(wait)原語操作過程:
1.S減1;
2.若S減1后仍大於或等於零,則調用P原語的進程繼續執行;
3.若S減1后小於零,則該進程被阻塞到與該信號相對應的隊列Q中,然后轉進程調度。
V(signal)原語操作過程:
1.S加1;
2.若相加后的S大於零,則進程繼續執行;
3.若相加后的S小於或等於零,則從該信號的等待隊列Q中喚醒一等待進程,然后再返回原進程繼續執行或轉進程調度。
二 信號量的工作原理
三 Linux的信號量機制
四 信號號相關的兩個結構體
內核為每個信號量集合設置了一個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的進程數
} ;
三 信號量的使用
#include <sys/sem.h>
int semget (key_t key, int nsem,int oflag) ;
#include <sys/sem.h>
int semop (int semid, structsembuf * opsptr, size_t nops) ;
參數opsptr是一個指針,它指向一個信號量操作數組,信號量操作由sembuf結構表示:
struct sembuf{
short sem_num; // 除非使用一組信號量,否則它為0
short sem_op; // 信號量在一次操作中需要改變的數據,通常是兩個數,
// 一個是-1,即P(等待)操作,一個是+1,即V(發送信號)操作
short sem_flg; // 通常為SEM_UNDO,使操作系統跟蹤信號,並在進程沒有釋放該信號量而終止時,
// 操作系統釋放信號量
};
#include <sys/sem.h>
int semctl (int semid, int semnum,int cmd, /*可選參數*/ ) ;
四 信號量值的初始化
semget並不初始化各個信號量的值,這個初始化必須通過以SETVAL命令(設置集合中的一個值)或SETALL命令(設置集合中的所有值) 調用semctl來完成。
SystemV信號量的設計中,創建一個信號量集並將它初始化需兩次函數調用是一個致命的缺陷。一個不完備的解決方案是:在調用semget時指定IPC_CREAT | IPC_EXCL標志,這樣只有一個進程(首先調用semget的那個進程)創建所需信號量,該進程隨后初始化該信號量。
五 例子
#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;
}
【信號量的意圖在於進程間同步,互斥鎖和條件變量的意圖則在於線程間同步。但是信號量也可用於線程間,互斥鎖和條件變量也可用於進程間。我們應該使用適合具體應用的那組原語。】

