一.概述 System V信號量與System V消息隊列不同。它不是用來在進程間傳遞數據。它主要是來同步進程的動作。1.一個信號量是一個由內核維護的整數。其值被限製為大於或等於0。2.可以在信號量上加上或減去一個數量。3.當一個減操作把信號量減到小於...
一.概述
System V信號量與System V消息隊列不同。它不是用來在進程間傳遞數據。它主要是來同步進程的動作。
1.一個信號量是一個由內核維護的整數。其值被限製為大於或等於0。
2.可以在信號量上加上或減去一個數量。
3.當一個減操作把信號量減到小於0時,內核會阻塞調用進程。直到另一操作把信號恢復,阻塞才會解除。
4.常用的信號量是二進位信號量。即操作0和1來控制臨界區。
二.函數介面
1.創建或打開一個信號量
1 #include <sys/sem.h> 2 3 int semget(key_t key, int nsems, int semflg);
key和semflg跟消息隊列一樣,這裡不再介紹,上面已給出消息隊列的文章連接。
nsems:指定信號量數目,一般都是1。
2.控制信號量
1 #include <sys/sem.h> 2 3 int semctl(int semid, int semnum, int cmd, ...);
semid:semget()返回的信號量標識符。
semnum:信號量編號,如果是成組的信號量,就要用到它,否則是0。
cmd:控制信號量的命令。SETVAL初始化一個值,IPC_RMID刪除信號量。
第四個參數是一個union semun結構。如果有的Linux版本頭文件沒有這個結構,需要自己定義:
1 union semun { 2 int val; /* Value for SETVAL */ 3 struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ 4 unsigned short *array; /* Array for GETALL, SETALL */ 5 struct seminfo *__buf; /* Buffer for IPC_INFO 6 (Linux-specific) */ 7 };
3.改變信號量的值
1 #include <sys/sem.h> 2 3 int semop(int semid, struct sembuf *sops, size_t nsops);
sops:指向一個sembuf結構,該結構成員如下:
unsigned short sem_num:信號量編號,如果不是一組信號,一般都為0。
short sem_op:對信號量加減操作,如:-1,+1,1。
short sem_flg:通常設置為SEM_UNDO。如果進程終止時沒有釋放該信號量,內核會釋放它。
三.簡單例子
我們封裝一個簡單的二進位信號量,寫2個小程式,一個只列印基數,一個只列印偶數,通過二進位信號量來控制它們按順序列印1-10。
1.封裝二進位信號量
1 /** 2 * @file binary_sem.h 3 */ 4 5 #ifndef _BINARY_SEM_H_ 6 #define _BINARY_SEM_H_ 7 8 union semun { 9 int val; /* Value for SETVAL */ 10 struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ 11 unsigned short *array; /* Array for GETALL, SETALL */ 12 struct seminfo *__buf; /* Buffer for IPC_INFO 13 (Linux-specific) */ 14 }; 15 16 /* 設置信號量 */ 17 int sem_set(int sem_id); 18 /* 增大信號量 */ 19 int sem_up(int sem_id); 20 /* 減小信號量 */ 21 int sem_down(int sem_id); 22 /* 刪除信號量 */ 23 int sem_delete(int sem_id); 24 25 #endif
1 /** 2 * @file binary_sem.c 3 */ 4 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #include <sys/sem.h> 9 10 #include "binary_sem.h" 11 12 static union semun sem_union; 13 static struct sembuf sem_buf; 14 15 16 /* 設置信號量 */ 17 int sem_set(int sem_id) 18 { 19 sem_union.val = 1; 20 if (semctl(sem_id, 0, SETVAL, sem_union) == -1) 21 return -1; 22 return 0; 23 } 24 25 /* 改變信號量為1 */ 26 int sem_up(int sem_id) 27 { 28 sem_buf.sem_num = 0; 29 sem_buf.sem_flg = SEM_UNDO; 30 sem_buf.sem_op = 1; 31 if (semop(sem_id, &sem_buf, 1) == -1) 32 return -1; 33 return 0; 34 } 35 36 /* 信號量減1 */ 37 int sem_down(int sem_id) 38 { 39 sem_buf.sem_num = 0; 40 sem_buf.sem_flg = SEM_UNDO; 41 sem_buf.sem_op = -1; 42 if (semop(sem_id, &sem_buf, 1) == -1) 43 return -1; 44 return 0; 45 } 46 47 /* 刪除信號量 */ 48 int sem_delete(int sem_id) 49 { 50 if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1) 51 return -1; 52 return 0; 53 }
2.列印基數的程式
1 /** 2 * @file sem1.c 3 */ 4 5 #include <stdio.h> 6 #include <string.h> 7 #include <stdlib.h> 8 #include <sys/sem.h> 9 #include <unistd.h> 10 11 #include "binary_sem.h" 12 13 #define SEM_KEY 7788 14 15 void err_exit(const char *err_msg) 16 { 17 printf("error: %s\n", err_msg); 18 exit(1); 19 } 20 21 int main(int argc, const char *argv[]) 22 { 23 int sem_id, i; 24 25 /* 創建信號 */ 26 if ((sem_id = semget(SEM_KEY, 1, 0666 | IPC_CREAT)) == -1) 27 err_exit("semget()"); 28 29 /* 初始化信號為1 */ 30 if (sem_set(sem_id) == -1) 31 err_exit("sem_set()"); 32 33 for (i = 1; i < 10; i += 2) 34 { 35 /* 信號減一 */ 36 if (sem_down(sem_id) == -1) 37 err_exit("sem_down()"); 38 39 sleep(3); 40 printf("%s:%d\n", argv[0], i); 41 42 /* 信號加一 */ 43 if (sem_up(sem_id) == -1) 44 err_exit("sem_up()"); 45 46 sleep(1); 47 } 48 return 0; 49 }
對信號量加減操作之間的代碼就是臨界區。程式第39行的sleep()是為了有足夠的時間來允許第二個程式,46行的sleep()是儘量讓其他程式來獲取信號量。
3.只列印偶數的程式
1 /** 2 * @file sem2.c 3 */ 4 5 #include <stdio.h> 6 #include <string.h> 7 #include <stdlib.h> 8 #include <unistd.h> 9 #include <sys/sem.h> 10 11 #include "binary_sem.h" 12 13 #define SEM_KEY 7788 14 15 void err_exit(const char *err_msg) 16 { 17 printf("error: %s\n", err_msg); 18 exit(1); 19 } 20 21 int main(int argc, const char *argv[]) 22 { 23 int sem_id, i; 24 25 /* 創建信號 */ 26 if ((sem_id = semget(SEM_KEY, 1, 0666 | IPC_CREAT)) == -1) 27 err_exit("semget()"); 28 29 for (i = 2; i <= 10; i += 2) 30 { 31 /* 信號減一 */ 32 if (sem_down(sem_id) == -1) 33 err_exit("sem_down()"); 34 35 printf("%s:%d\n", argv[0], i); 36 37 /* 信號加一 */ 38 if (sem_up(sem_id) == -1) 39 err_exit("sem_up()"); 40 41 sleep(1); 42 } 43 return 0; 44 }
四.實驗
1.為了測試方便,上面2個程式的key統一用一個值為7788的巨集來指定。
2.兩個程式執行的流程:sem1初始化一個為1的信號量,進入迴圈後對信號執行減一,並進入臨界區。休眠3秒的時間我執行第二個程式sem2,sem2進入迴圈後也執行減一操作,但此時信號已被sem1減為0,此時內核會阻塞sem1的操作,因為再減就小於0了。回到sem1,3秒過後,列印基數,並把信號重置為1,離開臨界區,休眠1秒。此時內核發現信號是1,可以執行減操作,內核會對sem2放行,sem2進入臨界區列印偶數。剛剛休眠1秒的sem1再次進入臨界區,發現信號量被sem2減到了0,此時被內核阻塞,直到sem2重置信號量。如此反覆!!!
最後:其實System V信號量的語義和API都比較複雜,有機會再探討比它簡單的POSIX信號量。