IPC——共享內存

概述html

管道是OS在物理內存上開闢一段緩存空間,當進程經過read、write等API來共享讀寫這段空間時,就實現了進程間通訊。緩存

消息隊列是OS建立的鏈表,鏈表的全部節點都是保存在物理內存上的,因此消息隊列這個鏈表其實也是OS在物理內存上所開闢的緩存,當進程調用msgsnd、msgrcv等API來共享讀寫時,就實現了進程間通訊。ide

共享內存也逃不開一樣的套路。共享內存就是OS在物理內存中開闢一大段緩存空間,不過與管道、消息隊列調用read、write、msgsnd、msgrcv等API來讀寫所不一樣的是,使用共享內存通訊時,進程是直接使用地址來共享讀寫的。函數

固然無論使用那種方式,只要可以共享操做同一段緩存,就均可以實現進程間的通訊。ui

 

信號、管道、消息隊列、共享內存對比spa

信號:非精確通訊3d

管道:無名管道只用於親緣進程,命名管道克服了這一缺點。可是這兩種管道方式仍是不適合網狀通訊code

消息隊列:克服了無名管道只用於親緣進程,無名/命名管道 網狀通訊若的缺點。可是不能實現大規模數據的通訊。htm

共享內存:繼承了消息隊列的優勢,還克服了其缺點。支持大規模數據通訊。blog

爲啥共享內存比消息隊列效率高?

前面這4中IPC,其本質都是操做OS提供的一段虛擬內存(在引入虛擬內存機制的狀況下),虛擬內存最終仍是被OS映射到真實物理內存。信號、管道、消息隊列 都要調用各類API,在到達內存以前,通過了屢次函數調用,直到最後一個函數,該函數纔會經過地址去讀寫共享的緩存。層層調用勢必會嚴重下降效率。而共享內存就沒有這麼多麻煩,直接使用地址來讀寫內存,效率高,那是必須的!

 

共享內存原理

注:紫線表述有誤,共享內存不會佔據進程所有虛擬地址空間

每一個進程的虛擬內存只嚴格對應本身的那片物理內存空間,也就是說虛擬空間的虛擬地址,只和本身的那片物理內存空間的物理地址創建映射關係,和其它進程的物理內存空間沒有任何的交集,所以進程空間之間是徹底獨立的。

以兩個進程使用共享內存來通訊爲例,實現的方法就是:

(1)調用API,讓OS在物理內存上開闢出一大段緩存空間。
(2)讓各自進程空間與開闢出的緩存空間創建映射關係

創建映射關係後,每一個進程均可以經過映射後的虛擬地址來共享操做實現通訊了。

多個進程能不能映射到同一片空間,而後數據共享呢?

固然是能夠的。不過當多個進程映射並共享同一個空間時,在寫數據的時候可能會出現相互干擾,好比A進程的數據剛寫了一半沒寫完,結果切換到B進程後,B進程又開始寫,A的數據就被中間B的數據給岔開了。這時每每須要加保護措施,讓每一個進程在沒有操做時不要被別人干擾,等操做完之後,別的進程才能寫數據。

 

共享內存的使用步驟

①進程調用shmget函數建立新的或獲取已有共享內存。shm是share memory的縮寫。

②進程調用shmat函數,將物理內存映射到本身的進程空間。即讓虛擬地址和真實物理地址創建一 一對應的映射關係。創建映射後,就能夠直接使用虛擬地址來讀寫共享的內存空間了。

③shmdt函數,取消映射

④調用shmctl函數釋放開闢的那片物理內存空間和消息隊列的msgctl的功能是同樣的,只不過這個是共享內存的。

多個進程使用共享內存通訊時,建立者只須要一個,一樣的,通常都是誰先運行誰建立,其它後運行的進程發現已經被建立好了,就直接獲取共享使用,你們共享操做同一個內存,便可實現通訊。

 

API

shmget

函數原型 

#include <sys/ipc.h>
#include <sys/shm.h>                 
int shmget(key_t key, size_t size, int shmflg);

功能

建立新的,或者獲取已有的共享內存

若是key值沒有對應任何共享內存:建立一個新的共享內存,建立的過程其實就是OS在物理內存上劃出(開闢出)一段物理內存空間出來。

若是key值有對應某一個共享內存:說明以前有進程調用msgget函數,使用該key去建立了某個共享內存,既然別人以前就建立好了,那就直接獲取key所對應的共享內存。

參數

key:用於生成共享內存的標識符,使用方法參考消息隊列的key

size:指定共享內存的大小,咱們通常要求size是虛擬頁大小的整數倍。通常來講虛擬頁大小是4k(4096字節),若是你指定的大小不是虛擬頁的整數倍,也會自動幫你補成整數倍。

semflg:與消息隊列同樣。指定原始權限和IPC_CREAT,好比 0664|IPC_CREAT。只有在建立一個新的共享內存時纔會用到,否者不會用到。

返回值

成功:返回共享內存的標識符,之後續操做

失敗:返回-1,而且errno被設置。

 

shmat

函數原型

#include <sys/types.h>
#include <sys/shm.h>               
void *shmat(int shmid, const void *shmaddr, int shmflg); 

功能

將shmid所指向的共享內存空間映射到進程空間(虛擬內存空間),並返回影射後的起始地址(虛擬地址)。有了這個地址後,就能夠經過這個地址對共享內存進行讀寫操做。

參數

shmid:共享內存標識符。

shmaddr:指定映射的起始地址,有兩種設置方式

①本身指定映射的起始地址(虛擬地址)。咱們通常不會這麼作,由於咱們本身都搞不清哪些虛擬地址被用了,哪些沒被用。

②NULL:表示由內核本身來選擇映射的起始地址(虛擬地址)。這是最多見的方式,也是最合理的方式,由於只有內核本身才知道哪些虛擬地址可用,哪些不可用。

shmflg:指定映射條件。

0:以可讀可寫的方式映射共享內存,也就是說映射後,能夠讀、也能夠寫共享內存。

SHM_RDONLY:以只讀方式映射共享內存,也就是說映射後,只能讀共享內存,不能寫。

返回值

成功:則返回映射地址

失敗:返回(void *)-1,而且errno被設置。

 

shmdt

函數原型 

#include <sys/types.h>
#include <sys/shm.h>                
int shmdt(const void *shmaddr);   

功能

取消創建的映射。

參數

shmaddr:映射的起始地址(虛擬地址)。

返回值

調用成功返回0,失敗返回-1,且errno被設置。

 

shmctl

函數原型 

#include <sys/ipc.h>
#include <sys/shm.h>           
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

 功能

根據cmd的要求,對共享內存進行相應控制。

好比:

獲取共享內存的屬性信息
修改共享內存的屬性信息
刪除共享內存
等等

刪除共享內存是最多見的控制。

參數

shmid:標識符。

cmd:控制選項

①IPC_STAT:從內核獲取共享內存屬性信息到第三個參數(應用緩存)。

②IPC_SET:修改共享內存的屬性。修改方法與消息隊列相同。

③IPC_RMID:刪除共享內存,不過前提是只有當全部的映射取消後,才能刪除共享內存。刪除時,用不着第三個參數,因此設置爲NULL

buf:buf的類型爲struct shmid_ds。

①cmd爲IPC_STAT時,buf用於存儲原有的共享內存屬性,以供查看。

②cmd爲IPC_SET時,buf中放的是新的屬性設置,用於修改共享內存的屬性。

struct shmid_ds結構體 

struct shmid_ds 
{
    struct ipc_perm shm_perm;    /* Ownership and permissions:權限 */
    size_t shm_segsz;   /* Size of segment (bytes):共享內存大小 */
    time_t shm_atime;   /* Last attach time:最後一次映射的時間 */
    time_t shm_dtime;   /* Last detach time:最後一次取消映射的時間 */
    time_t shm_ctime;   /* Last change time:最後一次修改屬性信息的時間 */
    pid_t shm_cpid;    /* PID of creator:建立進程的PID */
    pid_t shm_lpid;    /* PID of last shmat(2)/shmdt(2) :當前正在使用進程的PID*/
    shmatt_t shm_nattch;  /* No. of current attaches:映射數量,標記有多少個進程空間映射到了共享內存上,每增長一個映射就+1,每取消一個映射就-1 */ 
    ...
}; 

struct ipc_perm,這個結構體咱們在講消息隊列時已經講過

struct ipc_perm 
{
    key_t          __key;    /* Key supplied to shmget(2) */
    uid_t          uid;      /* UID of owner */
    gid_t          gid;      /* GID of owner */
    uid_t          cuid;     /* UID of creator */
    gid_t          cgid;     /* GID of creator */
    unsigned short mode;     /* Permissions + SHM_DEST andSHM_LOCKED flags */
    unsigned short __seq;    /* Sequence number */
};

返回值

調用成功0,失敗則返回-1,而且errno被設置。

 

共享內存示例代碼

單向通行

p1.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <strings.h>
#include <string.h>
#include <signal.h>
#include <errno.h>

#define SHM_FILE "./shmfile"

#define SHM_SIZE 4096

int shmid = -1;    

char buf[300] = {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222\
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2222222222"};

void print_err(char *estr)
{
    perror(estr);    
    exit(-1);
}

void create_or_get_shm(void)
{
    int fd = 0;
    key_t key = -1;    

    fd = open(SHM_FILE, O_RDWR|O_CREAT, 0664);
    if(fd == -1) print_err("open fail");
    
    key = ftok(SHM_FILE, 'b');
    if(key == -1) print_err("ftok fail");
    
    shmid = shmget(key, SHM_SIZE, 0664|IPC_CREAT);
    if(shmid == -1) print_err("shmget fail");
    
    //read(fd, &shmid, sizeof(shmid));
}

int main(void)
{

    void *shmaddr = NULL;

    /* 建立、或者獲取共享內存 */
    create_or_get_shm();

    //創建映射
    shmaddr = shmat(shmid, NULL, 0);
    if(shmaddr == (void *)-1) print_err("shmat fail");    
    
    while(1)
    {
        memcpy(shmaddr, buf, sizeof(buf));
        sleep(1);
        
    }
    
    return 0;
}
View Code

p2.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <strings.h>
#include <string.h>
#include <signal.h>
#include <errno.h>

#define SHM_FILE "./shmfile"

#define SHM_SIZE 4096

int shmid = -1;    

void print_err(char *estr)
{
    perror(estr);    
    exit(-1);
}

void create_or_get_shm(void)
{
    int fd = 0;
    key_t key = -1;    

    fd = open(SHM_FILE, O_RDWR|O_CREAT, 0664);
    if(fd == -1) print_err("open fail");
    
    key = ftok(SHM_FILE, 'b');
    if(key == -1) print_err("ftok fail");
    
    shmid = shmget(key, SHM_SIZE, 0664|IPC_CREAT);
    if(shmid == -1) print_err("shmget fail");
    
    //read(fd, &shmid, sizeof(shmid));
}

int main(void)
{

    void *shmaddr = NULL;

    /* 建立、或者獲取共享內存 */
    create_or_get_shm();

    //創建映射
    shmaddr = shmat(shmid, NULL, 0);
    if(shmaddr == (void *)-1) print_err("shmat fail");    
    
    while(1)
    {
        if(strlen((char*)shmaddr)!=0)
        {
            printf("%s\n", (char *)shmaddr);
            bzero(shmaddr, SHM_SIZE);
        }
    
    }
    
    return 0;
}
View Code

  這種寫法,在Ctrl+C硬件中斷進程後,並不會刪除共享內存。下面代碼改進這一問題

p1.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <strings.h>
#include <string.h>
#include <signal.h>
#include <errno.h>



#define SHM_FILE "./shmfile"

#define SHM_SIZE 4096

int shmid = -1;
void *shmaddr = NULL;    



void print_err(char *estr)
{
    perror(estr);    
    exit(-1);
}

void create_or_get_shm(void)
{
    int fd = 0;
    key_t key = -1;    

    fd = open(SHM_FILE, O_RDWR|O_CREAT, 0664);
    if(fd == -1) print_err("open fail");
    
    key = ftok(SHM_FILE, 'b');
    if(key == -1) print_err("ftok fail");
    
    shmid = shmget(key, SHM_SIZE, 0664|IPC_CREAT);
    if(shmid == -1) print_err("shmget fail");

    //write(fd, &shmid, sizeof(shmid));
}

char buf[300] = {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222\
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2222222222"};

void signal_fun(int signo)
{
    shmdt(shmaddr);
    shmctl(shmid, IPC_RMID, NULL);
    remove("./fifo");
    remove(SHM_FILE);
    
    exit(-1);    
}

int get_peer_PID(void)
{
    int ret = -1;
    int fifofd = -1;

    /* 建立有名管道文件 */
        ret = mkfifo("./fifo", 0664); 
        if(ret == -1 && errno != EEXIST) print_err("mkfifo fail");
    
    /* 以只讀方式打開管道 */
    fifofd = open("./fifo", O_RDONLY);
        if(fifofd == -1) print_err("open fifo fail");

    /* 讀管道,獲取「讀共享內存進程」的PID */
        int peer_pid;
        ret = read(fifofd, &peer_pid, sizeof(peer_pid));
        if(ret == -1) print_err("read fifo fail");

    return peer_pid; 
}

int main(void)
{
    int peer_pid = -1;

    /* 給SIGINT信號註冊捕獲函數,用於刪除共享內存、管道、文件等 */
    signal(SIGINT, signal_fun);


    /* 使用有名管道獲取讀共享內存進程的PID */
    peer_pid = get_peer_PID();
    
    
    /* 建立、或者獲取共享內存 */
    create_or_get_shm();
    
    //創建映射
    shmaddr = shmat(shmid, NULL, 0);
    if(shmaddr == (void *)-1) print_err("shmat fail");    
    
    while(1)
    {
        memcpy(shmaddr, buf, sizeof(buf));
        kill(peer_pid, SIGUSR1);
        sleep(1);
        
    }
    
    return 0;
}
View Code

p2.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <strings.h>
#include <string.h>
#include <signal.h>
#include <errno.h>


#define SHM_FILE "./shmfile"

#define SHM_SIZE 4096

int shmid = -1;
void *shmaddr = NULL;    



void print_err(char *estr)
{
    perror(estr);    
    exit(-1);
}

void create_or_get_shm(void)
{
    int fd = 0;
    key_t key = -1;    

    fd = open(SHM_FILE, O_RDWR|O_CREAT, 0664);
    if(fd == -1) print_err("open fail");
    
    key = ftok(SHM_FILE, 'b');
    if(key == -1) print_err("ftok fail");
    
    shmid = shmget(key, SHM_SIZE, 0664|IPC_CREAT);
    if(shmid == -1) print_err("shmget fail");
    
    //read(fd, &shmid, sizeof(shmid));
}

void signal_fun(int signo)
{
    if(SIGINT == signo)
    {
        shmdt(shmaddr);
        shmctl(shmid, IPC_RMID, NULL);
        remove("./fifo");
        remove(SHM_FILE);

        exit(-1);
    }
    else if(SIGUSR1 == signo)
    {
        
    }
}

void snd_self_PID(void)
{
    int ret = -1;
    int fifofd = -1;

    /* 建立有名管道文件 */
    mkfifo("./fifo", 0664); 
    if(ret == -1 && errno != EEXIST) print_err("mkfifo fail");
    
    /* 以只寫方式打開文件 */
    fifofd = open("./fifo", O_WRONLY);
    if(fifofd == -1) print_err("open fifo fail");
    
    /* 獲取當前進程的PID, 使用有名管道發送給寫共享內存的進程 */
    int pid = getpid();
    ret = write(fifofd, &pid, sizeof(pid));//發送PID
    if(ret == -1) print_err("write fifo fail");
}

int main(void)
{

    /*給SIGUSR1註冊一個空捕獲函數,用於喚醒pause()函數 */
    signal(SIGUSR1, signal_fun);
    signal(SIGINT, signal_fun);

    /* 使用有名管道,講當前進程的PID發送給寫共享內存的進程 */
    snd_self_PID();    


    /* 建立、或者獲取共享內存 */
    create_or_get_shm();


    //創建映射
    shmaddr = shmat(shmid, NULL, 0);
    if(shmaddr == (void *)-1) print_err("shmat fail");    
    
    while(1)
    {
        pause();
        printf("%s\n", (char *)shmaddr);
        bzero(shmaddr, SHM_SIZE);
    }
    
    return 0;
}
View Code

雙向通訊

相關文章
相關標籤/搜索