Linux進程通訊 之 信號燈(semphore)(System V && POSIX)

一. 信號燈簡介linux

信號燈與其餘進程間通訊方式不大相同,它主要提供對進程間共享資源訪問控制機制。api

至關於內存中的標誌,進程能夠根據它斷定是否可以訪問某些共享資源,同時,進程數組

也能夠修改該標誌。除了用於訪問控制外,還可用於進程同步。安全

信號燈有如下兩種類型:函數

二值信號燈:最簡單的信號燈形式,信號燈的值只能取0或1,相似於互斥鎖。 post

注:二值信號燈可以實現互斥鎖的功能,但二者的關注內容不一樣。信號燈強調共享資源,測試

只要共享資源可用,其餘進程一樣能夠修改信號燈的值;互斥鎖更強調進程,佔用資源spa

的進程使用完資源後,必須由進程自己來解鎖。線程

計算信號燈:信號燈的值能夠取任意非負值(固然受內核自己的約束)。設計

 

系統V信號燈是隨內核持續的,只有在內核重起或者顯示刪除一個信號燈集時,該信號

燈集纔會真正被刪除。

 

二. 信號燈的基本操做

對信號燈的操做無非有下面三種類型:

一、打開或建立信號燈 

二、信號燈值操做 

    linux能夠增長或減少信號燈的值,相應於對共享資源的釋放和佔有。具體參見後面的

    semop系統調用。

三、得到或設置信號燈屬性: 

    系統中的每個信號燈集都對應一個struct sem_array結構,該結構記錄了信號燈集

    的各類信息,存在於系統空間。爲了設置、得到該信號燈集的各類信息及屬性,在用戶

    空間有一個重要的聯合結構與之對應,即union semun。

 

3、系統V信號燈API

系統V消息隊列API只有三個,使用時須要包括幾個頭文件:

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

 

1)int semget(key_t key, int nsems, int semflg) 

參數key是一個鍵值,由ftok得到,惟一標識一個信號燈集.

參數nsems指定信號燈集包含信號燈的數目;

semflg參數是一些標誌位。

該調用返回與健值key相對應的信號燈集id

調用返回:成功返回信號燈集描述字,不然返回-1。 

 

2)int semop(int semid, struct sembuf *sops, unsigned nsops); 

semid是信號燈集ID,sops數組的每個sembuf結構都刻畫一個在特定信號燈上的操做。

nsops爲sops數組的大小。 sembuf結構以下:

struct sembuf {

unsigned short  sem_num;/* semaphore index in array */

shortsem_op;/* semaphore operation */

shortsem_flg;/* operation flags */

};

sem_num對應集合中的信號燈,0對應第一個信號燈, 以此類推...

sem_flg可取IPC_NOWAIT以及SEM_UNDO兩個標誌。若是設置了SEM_UNDO標誌,

那麼在進程結束時,相應的操做將被取消,這是比較重要的一個標誌位。若是設置了該標

志位,那麼在進程沒有釋放共享資源就退出時,內核將代爲釋放。若是爲一個信號燈設置

了該標誌,內核都要分配一個sem_undo結構來記錄它,爲的是確保之後資源可以安全釋

放。事實上,若是進程退出了,那麼它所佔用就釋放了,但信號燈值卻沒有改變,此時,

信號燈值反映的已經不是資源佔有的實際狀況,在這種狀況下,問題的解決就靠內核來完

成。這有點像殭屍進程,進程雖然退出了,資源也都釋放了,但內核進程表中仍然有它的

記錄,此時就須要父進程調用waitpid來解決問題了。 

sem_op的值大於0,等於0以及小於0肯定了對sem_num指定的信號燈進行的三種操做。

這裏須要強調的是semop能夠同時操做多個信號燈,在實際應用中,對應多種資源的申請

或釋放。semop保證操做的原子性,這一點尤其重要。尤爲對於多種資源的申請來講,要

麼一次性得到全部資源,要麼放棄申請,要麼在不佔有任何資源狀況下繼續等待,這樣,

一方面避免了資源的浪費;另外一方面,避免了進程之間因爲申請共享資源形成死鎖。 

也許從實際含義上更好理解這些操做:信號燈的當前值記錄相應資源目前可用數目;sem_op>0對應相應進程要釋放sem_op數目的共享資源;sem_op=0能夠用於對共享資

源是否已用完的測試;sem_op<0至關於進程要申請-sem_op個共享資源。再聯想操做的

原子性,更不難理解該系統調用什麼時候正常返回,什麼時候睡眠等待。 

調用返回:成功返回0,不然返回-1。

 

3) int semctl(int semid,int semnum,int cmd,union semun arg) 

該系統調用實現對信號燈的各類控制操做,參數semid指定信號燈集,參數cmd指定具體的

操做類型;參數semnum指定對哪一個信號燈操做,只對幾個特殊的cmd操做有意義;arg用

於設置或返回信號燈信息。 

該系統調用詳細信息請參見其手冊頁,這裏只給出參數cmd所能指定的操做。

IPC_STAT獲取信號燈信息,信息由arg.buf返回;

IPC_SET設置信號燈信息,待設置信息保存在arg.buf中.

GETALL返回全部信號燈的值,結果保存在arg.array中,參數sennum被忽略;

GETNCNT返回等待semnum所表明信號燈的值增長的進程數,至關於目前有多少

                進程在等待semnum表明的信號燈所表明的共享資源;

GETPID返回最後一個對semnum所表明信號燈執行semop操做的進程ID;

GETVAL返回semnum所表明信號燈的值;

GETZCNT返回等待semnum所表明信號燈的值變成0的進程數;

SETALL經過arg.array更新全部信號燈的值;同時,更新與本信號集相關的

                 semid_ds結構的sem_ctime成員;

SETVAL設置semnum所表明信號燈的值爲arg.val;

調用返回:調用失敗返回-1,成功返回與cmd相關:

Cmdreturn value

GETNCNTSemncnt

GETPIDSempid

GETVALSemval

GETZCNTSemzcnt

 

semctl函數使用到的結構體: 

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 */   //test!!

void *__pad;

};

struct  seminfo {

int semmap;

int semmni;

int semmns;

int semmnu;

int semmsl;

int semopm;

int semume;

int semusz;

int semvmx;

int semaem;

};

 

 

4、範例

這個範例使用信號燈來同步共享內存的操做, 程序建立一塊共享內存, 而後父子進程共同

修改共享內存. 父子進程採用信號燈來同步操做.

 

 

C代碼   收藏代碼
  1. #include <stdio.h>  
  2. #include <sys/types.h>  
  3. #include <sys/ipc.h>  
  4. #include <sys/sem.h>  
  5.   
  6. #define SHM_KEY 0x33  
  7. #define SEM_KEY 0x44  
  8.   
  9. union semun {  
  10.     int val;  
  11.     struct semid_ds *buf;  
  12.     unsigned short *array;  
  13. };  
  14.   
  15. int P(int semid)  
  16. {  
  17.     struct sembuf sb;  
  18.     sb.sem_num = 0;  
  19.     sb.sem_op = -1;  
  20.     sb.sem_flg = SEM_UNDO;  
  21.       
  22.     if(semop(semid, &sb, 1) == -1) {  
  23.         perror("semop");  
  24.         return -1;  
  25.     }  
  26.     return 0;  
  27. }  
  28.   
  29. int V(int semid)  
  30. {  
  31.     struct sembuf sb;  
  32.     sb.sem_num = 0;  
  33.     sb.sem_op = 1;  
  34.     sb.sem_flg = SEM_UNDO;  
  35.       
  36.     if(semop(semid, &sb, 1) == -1) {  
  37.         perror("semop");  
  38.         return -1;  
  39.     }  
  40.     return 0;  
  41. }  
  42.   
  43. int main(int argc, char **argv)  
  44. {  
  45.     pid_t pid;  
  46.     int i, shmid, semid;  
  47.     int *ptr;  
  48.     union semun semopts;  
  49.   
  50.     /* 建立一塊共享內存, 存一個int變量 */  
  51.     if ((shmid = shmget(SHM_KEY, sizeof(int), IPC_CREAT | 0600)) == -1) {  
  52.         perror("msgget");  
  53.     }  
  54.   
  55.     /* 將共享內存映射到進程, fork後子進程能夠繼承映射 */  
  56.     if ((ptr = (int *)shmat(shmid, NULL, 0)) == (void *)-1) {  
  57.         perror("shmat");  
  58.     }  
  59.     *ptr = 0;  
  60.   
  61.     /* 建立一個信號量用來同步共享內存的操做 */  
  62.     if ((semid = semget(SEM_KEY, 1, IPC_CREAT | 0600)) == -1) {  
  63.         perror("semget");  
  64.     }  
  65.   
  66.     /* 初始化信號量 */  
  67.     semopts.val = 1;  
  68.     if (semctl(semid, 0, SETVAL, semopts) < 0) {  
  69.         perror("semctl");  
  70.     }  
  71.   
  72.     if ((pid = fork()) < 0) {  
  73.         perror("fork");  
  74.     } else if (pid == 0) {      /* Child */  
  75.         /* 子進程對共享內存加1 */  
  76.         for (i = 0; i < 100000; i++) {  
  77.             P(semid);  
  78.             (*ptr)++;  
  79.             V(semid);  
  80.             printf("child: %d\n", *ptr);  
  81.         }  
  82.     } else {                    /* Parent */  
  83.         /* 父進程對共享內存減1 */  
  84.         for (i = 0; i < 100000; i++) {  
  85.             P(semid);  
  86.             (*ptr)--;  
  87.             V(semid);  
  88.             printf("parent: %d\n", *ptr);  
  89.         }  
  90.         waitpid(pid);  
  91.         /* 若是同步成功, 共享內存的值爲0 */  
  92.         printf("finally: %d\n", *ptr);  
  93.     }  
  94.   
  95.     return 0;  
  96. }  

 

五. 區別 System V信號量和Posix信號量
信號量有兩種實現:傳統的System V信號量和新的POSIX信號量。它們所提供的函數很容易被區分:對於全部System V信號量函數,在它們的名字裏面沒有劃線。例如,應該是semget()而不是sem_get()。然而,全部的的POSIX信號量函數都有一個下劃線。下面列出了它們提供的全部函數清單:
Systm V POSIX
semctl() sem_getvalue()
semget() sem_post()
semop() sem_timedwait()
  sem_trywait()
  sem_wait()
   
  sem_destroy()
  sem_init()
   
  sem_close()
  sem_open()
  sem_unlink()


另一個區別是,對於POSIX信號量,你能夠有命名的信號量,例如,信號量有一個文件
關聯它們,
對於最後三個函數,被用來建立,關閉和刪除這樣一個命名的信號量。
而sem_init()和sem_destroy()僅僅供非命名信號量使用。
他們是有關信號量的兩組程序設計接口函數。POSIX信號量來源於POSIX技術規範的實時
擴展方案(POSIX Realtime Extension),經常使用於線程;system v信號量,經常使用於進程的同步。
這二者很是相近,但它們使用的函數調用各不相同。前一種的頭文件爲semaphore.h,函數
調用爲sem_init(),sem_wait(),sem_post(),sem_destory()等等。後一種頭文件爲<sys/sem.h>,
函數調用爲semctl(),semget(),semop()等函數。
 
更詳細地請看 man sem_overview
 
總結:
System V的信號量通常用於進程同步, 且是內核持續的, api爲
semget
semctl
semop
Posix的有名信號量通常用於進程同步, 有名信號量是內核持續的. 有名信號量的api爲
sem_open
sem_close
sem_unlink

Posix的無名信號量通常用於線程同步, 無名信號量是進程持續的, 無名信號量的api爲

sem_init

sem_destroy

 

下面一個範例使用Posix的有名信號量來同步父子進程的共享內存操做:

 

 

C代碼   收藏代碼
    1. #include <stdio.h>  
    2. #include <sys/types.h>  
    3. #include <sys/ipc.h>  
    4. #include <semaphore.h>  
    5. #include <fcntl.h>           /* For O_* constants */  
    6. #include <sys/stat.h>        /* For mode constants */  
    7. #include <stdlib.h>  
    8.   
    9. #define SHM_KEY 0x33  
    10.   
    11. int main(int argc, char **argv)  
    12. {  
    13.     pid_t pid;  
    14.     int i, shmid;  
    15.     int *ptr;  
    16.     sem_t *sem;  
    17.   
    18.     /* 建立一塊共享內存, 存一個int變量 */  
    19.     if ((shmid = shmget(SHM_KEY, sizeof(int), IPC_CREAT | 0600)) == -1) {  
    20.         perror("msgget");  
    21.     }  
    22.   
    23.     /* 將共享內存映射到進程, fork後子進程能夠繼承映射 */  
    24.     if ((ptr = (int *)shmat(shmid, NULL, 0)) == (void *)-1) {  
    25.         perror("shmat");  
    26.     }  
    27.     *ptr = 0;  
    28.   
    29.     /* posix的有名信號量是kernel persistent的 
    30.      * 調用sem_unlink刪除之前的信號量 */  
    31.     sem_unlink("/mysem");  
    32.   
    33.     /* 建立新的信號量, 初值爲1, sem_open會建立共享內存 
    34.      * 因此信號量是內核持續的 */  
    35.     if ((sem = sem_open("/mysem", O_CREAT, 0600, 1)) == SEM_FAILED) {  
    36.         perror("sem_open");  
    37.     }  
    38.   
    39.     if ((pid = fork()) < 0) {  
    40.         perror("fork");  
    41.     } else if (pid == 0) {      /* Child */  
    42.         /* 子進程對共享內存加1 */  
    43.         for (i = 0; i < 100000; i++) {  
    44.             sem_wait(sem);  
    45.             (*ptr)++;  
    46.             sem_post(sem);  
    47.             printf("child: %d\n", *ptr);  
    48.         }  
    49.     } else {                    /* Parent */  
    50.         /* 父進程對共享內存減1 */  
    51.         for (i = 0; i < 100000; i++) {  
    52.             sem_wait(sem);  
    53.             (*ptr)--;  
    54.             sem_post(sem);  
    55.             printf("parent: %d\n", *ptr);  
    56.         }  
    57.         waitpid(pid);  
    58.         /* 若是同步成功, 共享內存的值爲0 */  
    59.         printf("finally: %d\n", *ptr);  
    60.         sem_unlink("/mysem");  
    61.     }  
    62.   
    63.     return 0;  
    64. }  
相關文章
相關標籤/搜索