1. 程式人生 > >Linux(高階程式設計)8————程序間通訊4(共享記憶體)

Linux(高階程式設計)8————程序間通訊4(共享記憶體)

共享記憶體是什麼?
因為程序之間是相互獨立的,他們有各自程序地址空間,那麼他們需要通訊時就要藉助核心來為他們建立橋樑,像之前我們瞭解的管道、訊息佇列就是核心做的工作來為程序間通訊架的橋樑。共享記憶體也是核心為程序間通訊駕的一座橋樑,只不過這座橋樑比其他橋樑更優,共享記憶體是核心為需要通訊的程序開闢了一塊公共的空間(記憶體),而這些需要通訊的程序只需將這塊空間對映到自己的程序地址空間即可通過操作這塊共享記憶體,便可以通訊了。
畫一幅圖可以加深對共享記憶體的理解:
在這裡插入圖片描述
通過這幅,可以讓我們對共享記憶體有了一個初步瞭解。
關於這幅圖,我們就知道了,為什麼共享記憶體是程序間通訊的最快方式了:


原因是管道、訊息佇列都需要將資料從使用者態拷到核心態。而共享記憶體則不需要這一操作。
在這裡插入圖片描述
管道通訊現將資料寫入使用者態,然後再拷到核心。而共享記憶體則少了這一動作(從使用者態到核心)。
** 共享記憶體**

  • 共享記憶體特性:共享記憶體是直接將一塊記憶體對映到程序地址空間中,進行資料傳輸相較於其他通訊方式少了兩次資料拷貝,少了使用者與核心之間資料的拷貝。
  • 共享記憶體的操作步驟:
    1.建立共享記憶體
    2.對映共享記憶體到虛擬地址空間。
    3.資料拷貝。
    4.通訊結束:a.解除對映。b.刪除共享記憶體。
  • 關於共享記憶體各個介面的介紹:
    1.共享記憶體的建立:
	 int shmget(key_t key, size_t size, int shmflg);

引數:
key:核心對共享記憶體的標識。
size:需要共享記憶體的大小(位元組數)。
shmflg:許可權標誌位
IPC_CREAT 如果不存在則建立。
IPC_EXCL 如果存在則報錯返回。
IPC_PRIVATE 標識只能用於具有親緣關係的程序間通訊。
如:IPC_CREAT|IPC_EXCL|0664
返回值:成功返回操作控制代碼;失敗返回-1

2.建立對映:

      void *shmat(int shmid, const void *shmaddr, int shmflg);

引數:
shmid:操作控制代碼
shmaddr:需要對映的起始地址(一般用NULL,讓作業系統為我們做)。
shmflg:SHM_RDONLY 標識為只讀。否則為讀寫
返回值:成功返回共享記憶體首地址(void*)。失敗返回-1。
3.解除對映:

 int shmdt(const void *shmaddr);

shmaddr:需要解除對映的共享記憶體的首地址。
4.共享記憶體控制:

 int shmctl(int shmid, int cmd, struct shmid_ds *buf);

shmid:操作控制代碼
cmd:需要的操作,常用操作如下:
IPC_RMID刪除
IPC_SET 設定共享記憶體
buf:描述共享記憶體的資訊,關心則接收,不關心則可以忽略。
**注意:**共享並不是cmd使用了IPC_RMID就會立刻刪除共享記憶體,而是這塊共享記憶體的連線數會建1,只有當這塊共享記憶體的連線減為0的時候才會真正的刪除這塊共享記憶體。
下面是關於共享記憶體使用的一個小案例:
還是關於server&client的小程式
server.c:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define KEY 0x123456

int main()
{
        //1.建立共享記憶體
        int shmid = shmget(KEY,1024,IPC_CREAT|0664);
        if(shmid<0)
        {
                perror("shmget");
                exit(-1);
        }
        //建立連線
        void* shm_start = shmat(shmid,NULL,0);
        while(1)
        {
                sleep(5);
                printf("client say: %s",shm_start);
                printf("\n");
                
        }
        //解除連線
        if(shmdt(shm_start) == 0)
                printf("解除完成!");
        shmctl(shmid,IPC_RMID,NULL);
        return 0;
}

client.c:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define KEY 0x123456

int main()
{
        //1.建立或開啟共享記憶體
        int shmid = shmget(KEY,1024,IPC_CREAT|0664);
        if(shmid<0)
        {
                perror("shmget");
                exit(-1);
        }
        //建立連線
        void* shm_start = shmat(shmid,NULL,0);
        while(1)
        {
                printf("client enter# ");
                fflush(stdout);
                scanf("%s",shm_start);
        }
        //解除連線
        if(shmdt(shm_start) == 0)
                printf("解除完成!");
        shmctl(shmid,IPC_RMID,NULL);
        return 0;
}

效果展示server:
在這裡插入圖片描述
client:
在這裡插入圖片描述
我們會發現這小程式存在一些缺點,它只能夠單向通訊,但是我們知道共享記憶體是雙向通訊的,那麼這是為什麼?因為共享記憶體直接對映到程序地址空間的那麼只要映射了這塊共享記憶體就可以操作這塊空間了,勢必會造成資料安全問題,那麼對於資料安全問題,我們可以配合訊號量對這塊空間訪問進行保護,在這裡暫時還不行,下面我們介紹了訊號量就可以對這個程式進行優化了。