首頁 > 軟體

Linux進程間通訊-共用記憶體深入理解

2020-06-16 16:53:32

繼前三篇分析了進程間通訊機制管道,命名管道(FIFO),訊息佇列後,本文將介紹最後一種進程間通訊機制,也是進程間通訊機制效率最高的一種-共用記憶體

1、共用記憶體

考慮前三種進程間通訊機制,一個客戶-伺服器檔案複製程式將設計到一下步驟:

(1)伺服器從輸入檔案讀取。該檔案的資料由核心讀入自己的記憶體空間,然後從核心複製到伺服器進程。

(2)伺服器往管道、FIFO和訊息佇列以一條訊息的形式寫入這些資料。這些IPC形式需要把進程中的資料複製到核心。

(3)用戶端從這些IPC通道中讀取資料,需要把核心資料複製到進程的地址空間。

(4)用戶端將進程空間內的資料再寫入核心,由核心寫入檔案。

 這些IPC形式的問題在於,共用資料需要多次經過核心。

共用記憶體的方式是讓同一塊實體記憶體被對映到進程A、B各自的進程地址空間。進程A可以即時看到進程B對共用記憶體中資料的更新,反之亦然。由於多個進程共用同一塊記憶體區域,必然需要某種同步機制(Linux同步的機制系列文章)。同樣是上面的例子,共用記憶體只需經過如下 流程:

這種形式的通訊資料只需複製兩次:一次從輸入檔案到共用記憶體,另一次是從共用記憶體到輸出檔案。

2、共用記憶體基本操作

2.1 開啟/建立一個共用記憶體物件

#include <sys/mman.h>

int shm_open(const char* name, int oflag, mode_t mode);

成功返回非負描述符,若失敗返回-1

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>

int main(int argc, char* argv[])
{
    int flag = O_RDWR | O_CREAT | O_EXCL;
    int mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
    int fd = 0;
    fd = shm_open("/shm_test", flag, mode);
    if (-1 == fd)
    {
        printf("open shm failed!n");
        return 1;
    }
    ftruncate(fd, 1024);

    mmap(NULL,1024,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    return 0;
}

2.2 刪除一個共用物件的名字

#include <sys/mman.h>

int shm_unlink(const char* name);

成功返回0,失敗返回-1

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>

int main(int argc, char* argv[])
{
    shm_unlink("/shm_test");
    return 0;
}

2.3 ftruncate 和 fstat

處理mmap的時候,普通檔案或共用記憶體物件的大小可以通過ftruncate函數修改:

#include <unistd.h>

int ftruncate(int fd, off_t length);

成功返回0,出錯返回-1

 

當開啟一個已存在的共用記憶體區物件時,我們可呼叫fstat來獲取有關該物件的資訊:

#include <sys/types.h>

#include <sys/stat.h>

int fstat(int fd, struct stat* stat);

成功返回0,失敗返回-1

對於普通檔案stat結構可以獲得12個以上的成員資訊,然而當fd指代一個共用記憶體區物件時,只有四個成員含有資訊。

struct stat{

...

mode_t st_mode;

uid_t st_uid;

gid_t st_gid;

off_t st_size;

};

 2.4 共用記憶體的讀寫

read端程式碼如下:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>

int main(int argc, char* argv[])
{
    int flag = O_RDONLY;
    int mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
    int fd = 0;
    unsigned char* ptr, c;
    fd = shm_open("/shm_test", flag, mode);
    if (-1 == fd)
    {
        printf("open shm failed!n");
        return 1;
    }
    struct stat stat;
    fstat(fd,&stat);
    ptr = mmap(NULL, stat.st_size, PROT_READ, MAP_SHARED, fd, 0);
    close(fd);

    for (int i = 0; i < stat.st_size; i++)
    {
        if((c = *ptr++) != (i%256))
        {
            printf("read error!n");
            exit(-1);
        }
        else{
            printf("%cn",c);
        }
    }

    return 0;
}

write端程式碼如下:

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>

int main(int argc, char* argv[])
{
    int flag = O_RDWR;
    int mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
    int fd = 0;
    unsigned char* ptr, c;
    fd = shm_open("/shm_test", flag, mode);
    if (-1 == fd)
    {
        printf("open shm failed!n");
        return 1;
    }
    struct stat stat;
    fstat(fd,&stat);
    ptr = mmap(NULL, stat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);

    for (int i = 0; i < stat.st_size; i++)
    {
        *ptr++ = i % 256;
        printf("write %dn",i);
    }
    return 0;
}

這裡與mqueue不同的是,read端讀取的時候並不會阻塞等待,如果沒有提前write內容到共用記憶體裡,read讀取到的內容為空。

3、共用記憶體的起始地址

同一共用記憶體區物件對映到不同進程的地址空間,起始地址可以不一樣。

#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdio.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>

int main(int argc, char* argv[])
{
    int fd1,fd2;
    unsigned char *ptr1, *ptr2;
    pid_t pid;
    int mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
    fd1 = shm_open("/shm_test",O_RDWR,mode);
    if (fd1 == -1)
    {
        printf("Message : %sn", strerror(errno));
        return 1;
    }

    struct stat stat;
    fstat(fd1, &stat);

    fd2 = open("/dev/shm/shm_test1",O_RDWR | O_CREAT,mode);
    if (fd2 == -1)
    {
        printf("Message : %sn", strerror(errno));
        return 1;
    }
   
    pid = fork();
    if (pid == 0)
    {
        ptr2 = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd2,0);
        ptr1 = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd1, 0);
        printf("child: shm ptr=%p, mmf ptr=%pn",ptr1, ptr2);
        sleep(5);
        printf("share memory integer:%d", *ptr1);
    }
    else if (pid > 0)
    {
        ptr1 = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd1, 0);
        ptr2 = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd2,0);
        printf("parent: shm ptr=%p, mmf ptr=%pn",ptr1, ptr2);
        *ptr1 = 128;
        waitpid(0, NULL, 0);
    }
    else{
       
    }

    return 0;
}

程式碼執行結果如下:

可見,在不同的進程中,相同的共用記憶體區物件起始地址可以不一樣。

一、進程間通訊-管道 https://www.linuxidc.com/Linux/2018-04/151680.htm

二、進程間通訊-命名管道 https://www.linuxidc.com/Linux/2018-04/151681.htm

三、進程間通訊-訊息佇列 https://www.linuxidc.com/Linux/2018-04/151682.htm

本文永久更新連結地址https://www.linuxidc.com/Linux/2018-04/151683.htm


IT145.com E-mail:sddin#qq.com