信號量學習 & 共享內存同步


剛剛這篇文章學習了共享內存:http://www.cnblogs.com/charlesblc/p/6142139.html

里面也提到了共享內存,自己不進行同步,需要其他手段比如信號量來進行。那么現在就學習信號量咯。

共享內存實際編程中,
應該使用信號量,
或通過傳遞消息(使用管道或IPC消息),
或生成信號
的方法來提供讀寫之間的更有效的同步機制。
 
方法一、利用POSIX有名信號燈實現共享內存的同步
方法二、利用POSIX無名信號燈實現共享內存的同步
 
方法三、利用System V的信號燈實現共享內存的同步
方法四、利用信號實現共享內存的同步
 

 

信號燈(semaphore),也叫信號量。它是不同進程間或一個給定進程內部不同線程間同步的機制。信號燈包括posix有名信號燈、 posix基於內存的信號燈(無名信號燈)和System V信號燈(IPC對象)

 

方法一、利用POSIX有名信號燈實現共享內存的同步

有名信號量既可用於線程間的同步,又可用於進程間的同步。

兩個進程,對同一個共享內存讀寫,可利用有名信號量來進行同步。一個進程寫,另一個進程讀,利用兩個有名信號量semr, semw。semr信號量控制能否讀,初始化為0。 semw信號量控制能否寫,初始為1。

讀共享內存的程序示例代碼如下

semr = sem_open("mysem_r", O_CREAT | O_RDWR , 0666, 0);
        if (semr == SEM_FAILED)
        {
                printf("errno=%d\n", errno);
                return -1;
        }

        semw = sem_open("mysem_w", O_CREAT | O_RDWR, 0666, 1);
        if (semw == SEM_FAILED)
        {
                printf("errno=%d\n", errno);
                return -1;
        }

        if ((shmid = shmget(key, MAXSIZE, 0666 | IPC_CREAT)) == -1)
        {
                perror("semget");
                exit(-1);
        }

        if ((shmadd = (char *)shmat(shmid, NULL, 0)) == (char *)(-1))
        {
                perror("shmat");
                exit(-1);
        }

        while (1)
        {
                sem_wait(semr);
                printf("%s\n", shmadd);
                sem_post(semw);
        }

寫共享內存的程序示例代碼如下

。。。。。。
        //同讀的程序
        while (1)
        {
                sem_wait(semw);
                printf(">");
                fgets(shmadd, MAXSIZE, stdin);
                sem_post(semr);
        }

 

方法二、利用POSIX無名信號燈實現共享內存的同步

POSIX無名信號量是基於內存的信號量,可以用於線程間同步也可以用於進程間同步。若實現進程間同步,需要在共享內存中來創建無名信號量

因此,共享內存需要定義以下的結構體。

typedef struct
        {
                sem_t semr;
                sem_t semw;
                char buf[MAXSIZE];
        }SHM;

 

無名信號量的數據類型是:sem_t;

(1)初始化函數:

int sem_init(sem_t *sem, int pshared, unsigned value);

該函數將sem引用的無名信號量初始化為value,該參數表示擁有資源的個數,不能為負數。pshared指定為0,表示信號量只能由初始化這個信號量的進程使用,不能在進程間使用。一個無名信號量在被使用前必須先初始化。

該函數如果不成功將返回-1並設置errno。

 

(2)銷毀函數:

int sem_destroy(sem_t *sem);

該函數用來銷毀一個已經被初始化過的無名信號量。

如果不成功返回-1並設置errno。

 

(3)信號量操作函數:

intsem_wait(sem_t *sem);

該函數用來獲取資源,如果信號量為0,表示這時沒有相應資源空閑,那么調用線程就將掛起,直到有空閑資源可以獲取。如果信號量不為0,那么表示這時有相應資源可用,那么將信號量減1,並返回,表示獲取一個資源。

如果成功返回0,如果不成功,函數返回-1,並設置errno。值得注意的是,該函數是信號可中斷的,當正在等待資源的線程收到信號(可捕捉信號)時,該函數返回-1並把errno設置為EINTR。所以,必須在被信號中斷后重新啟動該函數,簡單代碼如下:

while((-1==sem_wait(&sem))&&(EINTR==errno));

 

sem_trywait(sem_t *sem);

該函數試圖獲取資源,當信號量為0時,它不阻塞,直接返回-1並將errno置為EAGAIN。

sem_post(sem_t *sem);

該函數實現了信號量的signal操作,如果沒有線程阻塞在該sem上,表示沒有線程等待該資源,這時該函數就對信號量的值進行增1操作,表示同類資源多增加了一個。如果至少有一個線程阻塞在該sem上,表示有線程等待資源,信號量為0,這時該函數保持信號量為0不變,並使某個阻塞在該sem上的線程從sem_wait函數中返回,表示有一個可用資源到達,並被某個線程占有,所以信號量還是為0。

 

讀、寫程序流程如下圖所示。

 

 

 

 

方法三、利用System V的信號燈實現共享內存的同步

System V的信號燈是一個或者多個信號燈的一個集合。其中的每一個都是單獨的計數信號燈。而Posix信號燈指的是單個計數信號燈

System V 信號燈由內核維護,主要函數semget,semop,semctl 。

一個進程寫,另一個進程讀,信號燈集中有兩個信號燈,下標0代表能否讀,初始化為0。 下標1代表能否寫,初始為1。

程序流程如下:

 

寫的流程和前邊的類似。

 

方法四、利用信號實現共享內存的同步

信號是在軟件層次上對中斷機制的一種模擬,是一種異步通信方式。利用信號也可以實現共享內存的同步。

思路:

reader和writer通過信號通信必須獲取對方的進程號,可利用共享內存保存雙方的進程號。

reader和writer運行的順序不確定,可約定先運行的進程創建共享內存並初始化。

利用pause, kill, signal等函數可以實現該程序(流程和前邊類似)。

 

還有這篇:

http://blog.csdn.net/ljianhui/article/details/10243617

 

信號量的工作原理
由於信號量只能進行兩種操作等待和發送信號,即P(sv)和V(sv),他們的行為是這樣的:
P(sv):如果sv的值大於零,就給它減1;如果它的值為零,就掛起該進程的執行
V(sv):如果有其他進程因等待sv而被掛起,就讓它恢復運行,如果沒有進程因等待sv而掛起,就給它加1.
 
它們聲明在頭文件sys/sem.h中。
 
1、semget函數
它的作用是創建一個新信號量或取得一個已有信號量,原型為:
int semget(key_t key, int num_sems, int sem_flags);  
第一個參數key是整數值(唯一非零),不相關的進程可以通過它訪問一個信號量,它代表程序可能要使用的某個資源,程序對所有信號量的訪問都是間接的,程序先通過調用semget函數並提供一個鍵,再由系統生成一個相應的信號標識符(semget函數的返回值),只有semget函數才直接使用信號量鍵,所有其他的信號量函數使用由semget函數返回的信號量標識符。如果多個程序使用相同的key值,key將負責協調工作。
 
第二個參數num_sems指定需要的信號量數目,它的值幾乎總是1。
 
第三個參數sem_flags是一組標志,當想要當信號量不存在時創建一個新的信號量,可以和值IPC_CREAT做按位或操作。設置了IPC_CREAT標志后,即使給出的鍵是一個已有信號量的鍵,也不會產生錯誤。而IPC_CREAT | IPC_EXCL則可以創建一個新的,唯一的信號量,如果信號量已存在,返回一個錯誤。
 
semget函數成功返回一個相應信號標識符(非零),失敗返回-1.

 

2、semop函數
它的作用是改變信號量的值,原型為:
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);  

sem_id是由semget返回的信號量標識符,sembuf結構的定義如下:

struct sembuf{  
    short sem_num;//除非使用一組信號量,否則它為0  
    short sem_op;//信號量在一次操作中需要改變的數據,通常是兩個數,一個是-1,即P(等待)操作,  
                    //一個是+1,即V(發送信號)操作。  
    short sem_flg;//通常為SEM_UNDO,使操作系統跟蹤信號量,  
                    //並在進程沒有釋放該信號量而終止時,操作系統釋放信號量  
};  

 

3、semctl函數

該函數用來直接控制信號量信息,它的原型為:

int semctl(int sem_id, int sem_num, int command, ...);  

 

如果有第四個參數,它通常是一個union semum結構,定義如下:

union semun{  
    int val;  
    struct semid_ds *buf;  
    unsigned short *arry;  
};  
前兩個參數與前面一個函數中的一樣,command通常是下面兩個值中的其中一個
SETVAL:用來把信號量初始化為一個已知的值。p 這個值通過union semun中的val成員設置,其作用是在信號量第一次使用前對它進行設置。
IPC_RMID:用於刪除一個已經無需繼續使用的信號量標識符。
 
 
四、進程使用信號量通信
下面使用一個例子來說明進程間如何使用信號量來進行通信,這個例子是兩個相同的程序同時向屏幕輸出數據,我們可以看到如何使用信號量來使兩個進程協調工作,使同一時間只有一個進程可以向屏幕輸出數據。
注意,如果程序是第一次被調用(為了區分,第一次調用程序時帶一個要輸出到屏幕中的字符作為一個參數),則需要調用set_semvalue函數初始化信號並將message字符設置為傳遞給程序的參數的第一個字符,同時第一個啟動的進程還負責信號量的刪除工作。
如果不刪除信號量,它將繼續在系統中存在, 即使程序已經退出,它可能在你下次運行此程序時引發問題,而且信號量是一種有限的資源。

 

代碼如下:

#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;
}

 

上面程序,起兩個進程,X和O總是成對出現的,也就是說sleep的時候沒有被打斷。

 

六、信號量的總結
信號量是一個特殊的變量,程序對其訪問都是原子操作,且只允許對它進行等待(即P(信號變量))和發送(即V(信號變量))信息操作。
 
 

 

 
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM