linux網路編程之System V 訊號量(三):基於生產者-消費者模型實現先進先出的共用記憶體段

來源:互聯網
上載者:User

生產者消費者問題:該問題描述了兩個共用固定大小緩衝區的進程——即所謂的“生產者”和“消費者”——在實際運行時會發生的問題。生產者的主要作用是產生一定量的資料放到緩衝區中,然後重複此過程。與此同時,消費者也在緩衝區消耗這些資料。該問題的關鍵就是要保證生產者不會在緩衝區滿時加入資料,消費者也不會在緩衝區中空時消耗資料。

我們可以用訊號量解決生產者消費者問題,如:


定義3個訊號量,sem_full 和 sem_empty 用於生產者進程和消費者進程之間同步,即緩衝區為空白才能生產,緩衝區不為空白才能消費。由於共用同一塊緩衝區,在生產一個產品過程中不能生產/消費產品,在消費一個產品的過程中不能生產/消費產品,故再使用一個
sem_mutex 訊號量來約束行為,即進程間互斥。


下面基於生產者消費者模型,來實現一個先進先出的共用記憶體段:


如所示,定義兩個結構體,shmhead 是共用記憶體段的頭部,儲存了塊大小,塊數,讀寫索引。shmfifo 儲存了共用記憶體頭部的指標,承載的起始地址,建立的共用記憶體段的shmid,以及3個訊號量。

下面來封裝幾個函數:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include "shmfifo.h"
#include <assert.h>

shmfifo_t *shmfifo_init(int key, int blksize, int blocks)
{
    shmfifo_t *fifo = (shmfifo_t *)malloc(sizeof(shmfifo_t));
    assert(fifo != NULL);
    memset(fifo, 0, sizeof(shmfifo_t));

    int shmid;
    shmid = shmget(key, 0, 0);
    int size = sizeof(shmhead_t) + blksize * blocks;
    if (shmid == -1)
    {
        fifo->shmid = shmget(key, size, IPC_CREAT | 0666);
        if (fifo->shmid == -1)
            ERR_EXIT("shmget");

        fifo->p_shm = (shmhead_t *)shmat(fifo->shmid, NULL, 0);
        if (fifo->p_shm == (shmhead_t *) - 1)
            ERR_EXIT("shmat");

        fifo->p_payload = (char *)(fifo->p_shm + 1);

        fifo->p_shm->blksize = blksize;
        fifo->p_shm->blocks = blocks;
        fifo->p_shm->rd_index = 0;
        fifo->p_shm->wr_index = 0;

        fifo->sem_mutex = sem_create(key);
        fifo->sem_full = sem_create(key + 1);
        fifo->sem_empty = sem_create(key + 2);

        sem_setval(fifo->sem_mutex, 1);
        sem_setval(fifo->sem_full, blocks);
        sem_setval(fifo->sem_empty, 0);
    }
    else
    {
        fifo->shmid = shmid;
        fifo->p_shm = (shmhead_t *)shmat(fifo->shmid, NULL, 0);
        if (fifo->p_shm == (shmhead_t *) - 1)
            ERR_EXIT("shmat");

        fifo->p_payload = (char *)(fifo->p_shm + 1);

        fifo->sem_mutex = sem_open(key);
        fifo->sem_full = sem_open(key + 1);
        fifo->sem_empty = sem_open(key + 2);
    }

    return fifo;
}

void shmfifo_put(shmfifo_t *fifo, const void *buf)
{
    sem_p(fifo->sem_full);
    sem_p(fifo->sem_mutex);

    memcpy(fifo->p_payload + fifo->p_shm->blksize * fifo->p_shm->wr_index,
           buf, fifo->p_shm->blksize);
    fifo->p_shm->wr_index = (fifo->p_shm->wr_index + 1) % fifo->p_shm->blocks;
    sem_v(fifo->sem_mutex);
    sem_v(fifo->sem_empty);
}

void shmfifo_get(shmfifo_t *fifo, void *buf)
{
    sem_p(fifo->sem_empty);
    sem_p(fifo->sem_mutex);

    memcpy(buf, fifo->p_payload + fifo->p_shm->blksize * fifo->p_shm->rd_index,
           fifo->p_shm->blksize);
    fifo->p_shm->rd_index = (fifo->p_shm->rd_index + 1) % fifo->p_shm->blocks;
    sem_v(fifo->sem_mutex);
    sem_v(fifo->sem_full);
}

void shmfifo_destroy(shmfifo_t *fifo)
{
    sem_d(fifo->sem_mutex);
    sem_d(fifo->sem_full);
    sem_d(fifo->sem_empty);

    shmdt(fifo->p_shm);
    shmctl(fifo->shmid, IPC_RMID, 0);
    free(fifo);
}

1、shmfifo_init:先分配shmfifo 結構體的記憶體,如果嘗試開啟共用記憶體失敗則建立,建立的共用記憶體段大小 = shmhead大小 + 塊大小×塊數目,然後shmat將此共用記憶體段映射到進程地址空間,然後使用sem_create 建立3個訊號量集,每個訊號集只有一個訊號量,即上面提到的3個訊號量,設定每個訊號量的資源初始值。如果共用記憶體已經存在,則直接sem_open
開啟即可。sem_xxx 系列封裝函數參考這裡。

2、shmfifo_put:參照第一個生產者消費者的圖,除去sem_p,sem_v 操作之外,中間就將buf 的內容memcpy 到對應緩衝區塊,然後移動wr_index。

3、shmfifo_get:與shmfifo_put 類似,執行的是相反的操作。

4、shmfifo_destroy:刪除3個訊號量集,將共用記憶體段從進程地址空間剝離,刪除共用記憶體段,釋放shmfifo 結構體的記憶體。


下面是生產者程式和消費者程式:

shmfifo_send.c

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "shmfifo.h"

typedef struct stu
{
    char name[32];
    int age;
} STU;
int main(void)
{
    shmfifo_t *fifo = shmfifo_init(1234, sizeof(STU), 3);

    STU s;
    memset(&s, 0, sizeof(STU));
    s.name[0] = 'A';
    int i;

    for (i = 0; i < 5; i++)
    {
        s.age = 20 + i;
        shmfifo_put(fifo, &s);
        s.name[0] = s.name[0] + 1;

        printf("send ok\n");
    }

    return 0;
}

shmfifo_recv.c

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "shmfifo.h"

typedef struct stu
{
    char name[32];
    int age;
} STU;

int main(void)
{
    shmfifo_t *fifo = shmfifo_init(1234, sizeof(STU), 3);

    STU s;
    memset(&s, 0, sizeof(STU));
    int i;

    for (i = 0; i < 5; i++)
    {
        shmfifo_get(fifo, &s);
        printf("name = %s age = %d\n", s.name, s.age);
    }

    shmfifo_destroy(fifo);

    return 0;
}

先運行生產者進程,輸出如下:

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v/shmfifo$ ./shmfifo_send 
send ok
send ok
send ok


因為共用記憶體只有3塊block,故發送了3次後再次P(semfull)就阻塞了,等待消費者讀取資料,現在運行消費者進程

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v/shmfifo$ ./shmfifo_recv 
name = A age = 20
name = B age = 21
name = C age = 22
name = D age = 23
name = E age = 24

因為生產者已經建立了一塊共用記憶體,故消費者只是開啟而已,當讀取了第一塊資料之後,生產者會再次寫入,依次輸出後兩個 send ok,可以推論的是D是重新寫到共用記憶體開始的第一塊,E是第二塊,類似環形隊列。

從輸出可以看出,的確實現了資料的先進先出。


參考:《UNP》

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.