信號量是一種不同進程或不同線程間的同步方法,有System V信號量和Posix信號量。
本文介紹System V 信號量,其在內核中維護,可用於進程間或線程間的同步,本文只介紹進程間同步。
信號量一般有兩種,二值信號量(binary semaphore)和計數信號量(counting semaphore)。
二值信號量: 顧名思義,其值只有兩種0或1,相當於互斥量,當值為1時資源可用;而當值為0時,資源被鎖住,進程阻塞無法繼續執行。
計數信號量: 其值是在0到某個限制值之間的信號量。
System V信號量是指的計數信號量集(set of counting semaphores),是一個或多個信號量的集合,其中每個都是計數信號量。(注:System V 信號量是計數信號量集,Posix 信號量是單個計數信號量。)
System V 信號量函數頭文件及相關函數原型:
#include <sys/sem.h>
int semget(key_t key, int nsems, int oflag);
功能:創建一個信號量集或訪問一個已經存在的信號量集
返回值:成功返回非負的標識符,出錯返回-1
參數:key是信號量的鍵值,多個進程可以通過這個鍵值訪問同一個信號量;nsems參數指定信號量集合中的信號量數,一般設為1,如果不創建新的信號量集,只是訪問一個已經存在的集合,可以把該參數設為0,一旦創建完一個信號量集,就不能改變其中的信號量數;oflag同open()權限位,IPC_CREAT標示創建新的信號量,如果或上IPC_EXCL,若信號量已存在則出錯,如果沒有或上IPC_EXCL,若信號量存在也不會出錯。
int semctl(int semid, int semnum, int cmd, ... /*union semun arg */);
功能: 信號量控制操作。
返回值:若成功,根據cmd不同返回不同的值,IPC_STAT,IPC_SETVAL,IPC_RMID返回0,IPC_GETVAL返回信號量當前值;出錯返回-1.
參數:semid標示操作的信號量集;semnum標示該信號量集內的某個成員(0,1等,直到nsems-1),semnum值僅僅用於GETVAL,SETVAL,GETNCNT,GETZCNT,GETPID,通常取值0,也就是第一個信號量;cmd:指定對單個信號量的各種操作,IPC_STAT,IPC_GETVAL,IPC_SETVAL,IPC_RMID;arg: 可選參數,取決了第三個參數cmd。
int semop(int semid, struct sembuf *opstr, size_t nops);
功能:操作信號量,P,V 操作
返回值:成功返回信號量標識符,出錯返回-1
參數:semid:信號量集標識符;nops是opstr數組中元素數目,通常取值為1;opstr指向一個結構數組
struct sembuf{
short sem_num;
short sem_op;
short sem_flg;
}
一般編程步驟:
1. 創建信號量或獲得在系統中已存在的信號量
1). 調用semget().
2). 不同進程使用同一個信號量鍵值來獲得同個信號量
2. 初始化信號量
1).使用semctl()函數的SETVAL操作
2).當使用二維信號量時,通常將信號量初始化為1
3.進行信號量PV操作
1). 調用semop()函數
2). 實現進程之間的同步和互斥
4.如果不需要該信號量,從系統中刪除
1).使用semctl()函數的IPC_RMID操作
代碼舉例,父子進程間的同步sem_test.c
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/types.h>
- #include <unistd.h>
- #include <sys/sem.h>
- #include <sys/ipc.h>
- #define USE_SYSTEMV_SEM 1
- #define DELAY_TIME 2
- union semun {
- int val;
- struct semid_ds *buf;
- unsigned short *array;
- };
- // 將信號量sem_id設置為init_value
- int init_sem(int sem_id,int init_value) {
- union semun sem_union;
- sem_union.val=init_value;
- if (semctl(sem_id,0,SETVAL,sem_union)==-1) {
- perror("Sem init");
- exit(1);
- }
- return 0;
- }
- // 刪除sem_id信號量
- int del_sem(int sem_id) {
- union semun sem_union;
- if (semctl(sem_id,0,IPC_RMID,sem_union)==-1) {
- perror("Sem delete");
- exit(1);
- }
- return 0;
- }
- // 對sem_id執行p操作
- int sem_p(int sem_id) {
- struct sembuf sem_buf;
- sem_buf.sem_num=0;//信號量編號
- sem_buf.sem_op=-1;//P操作
- sem_buf.sem_flg=SEM_UNDO;//系統退出前未釋放信號量,系統自動釋放
- if (semop(sem_id,&sem_buf,1)==-1) {
- perror("Sem P operation");
- exit(1);
- }
- return 0;
- }
- // 對sem_id執行V操作
- int sem_v(int sem_id) {
- struct sembuf sem_buf;
- sem_buf.sem_num=0;
- sem_buf.sem_op=1;//V操作
- sem_buf.sem_flg=SEM_UNDO;
- if (semop(sem_id,&sem_buf,1)==-1) {
- perror("Sem V operation");
- exit(1);
- }
- return 0;
- }
- int main() {
- pid_t pid;
- #if USE_SYSTEMV_SEM
- int sem_id;
- key_t sem_key;
- sem_key=ftok(".",'A');
- printf("sem_key=%x\n",sem_key);
- //以0666且create mode創建一個信號量,返回給sem_id
- sem_id=semget(sem_key,1,0666|IPC_CREAT);
- printf("sem_id=%x\n",sem_id);
- //將sem_id設為1
- init_sem(sem_id,1);
- #endif
- if ((pid=fork())<0) {
- perror("Fork error!\n");
- exit(1);
- } else if (pid==0) {
- #if USE_SYSTEMV_SEM
- sem_p(sem_id); // P操作
- #endif
- printf("Child running...\n");
- sleep(DELAY_TIME);
- printf("Child %d,returned value:%d.\n",getpid(),pid);
- #if USE_SYSTEMV_SEM
- sem_v(sem_id); // V操作
- #endif
- exit(0);
- } else {
- #if USE_SYSTEMV_SEM
- sem_p(sem_id); // P操作
- #endif
- printf("Parent running!\n");
- sleep(DELAY_TIME);
- printf("Parent %d,returned value:%d.\n",getpid(),pid);
- #if USE_SYSTEMV_SEM
- sem_v(sem_id); // V操作
- waitpid(pid,0,0);
- del_sem(sem_id);
- #endif
- exit(0);
- }
- }
運行結果:
$ ./a.out
sem_key=41015bb5
sem_id=70004
Parent running!
Parent 17759,returned value:17760.
Child running...
Child 17760,returned value:0.
可以看到是父進程執行完后才執行子進程。
如果取消System V 信號量(把條件編譯宏設為0 “#define USE_SYSTEMV_SEM 0”),運行結果為
$ ./a.out
Parent running!
Child running...
Parent 17772,returned value:17773.
Child 17773,returned value:0.
可以看到父子進程之間存在競爭
注意: sem_key=ftok(".",'A');
語句中ftok的第一個參數必須是存在的文件名,如果不存在的文件名,例如sem_key=ftok("xxxxx",'A'); 則運行結果出錯:
$ ./a.out
sem_key=ffffffff
sem_id=ffffffff
Sem init: Invalid argument
關於PV操作 p操作和v操作是不可中斷的程序段,稱為原語。P,V原語中P是荷蘭語的Passeren,相當於英文的pass, 意為通過,V是荷蘭語的Verhoog,相當於英文中的incremnet,意為釋放。 |