進程間通訊 (IPC) 方法總結(二)

進程間通訊 (IPC) 方法總結(二)

共享內存(SHARE MEMORY)

  • 使得多個進程能夠能夠直接讀寫同一塊內存空間,是最快的可用IPC形式。是針對其餘通訊機制運行效率較低而設計的。
  • 爲了在多個進程間交換信息,內核專門留出了一塊內存區,能夠由須要訪問的進程將其映射到本身的私有地址空間。進程就能夠直接讀寫這一塊內存而不須要進行數據的拷貝,從而大大提升效率。
  • 因爲多個進程共享一段內存,所以須要依靠某種同步機制(如信號量)來達到進程間的同步及互斥。

共享內存

Linux的內核支持多種共享內存方式,如mmap()系統調用,Posix共享內存,以及System V共享內存。node

mmap

mmap()系統調用使得進程之間經過映射同一個普通文件實現共享內存。普通文件被映射到進程地址空間後,進程能夠像訪問普通內存同樣對文件進行訪問,沒必要再調用read(),write()等操做。數組

注:實際上,mmap()系統調用並非徹底爲了用於共享內存而設計的。它自己提供了不一樣於通常對普通文件的訪問方式,進程能夠像讀寫內存同樣對普通文件的操做。而Posix或System V的共享內存IPC則純粹用於共享目的,固然mmap()實現共享內存也是其主要應用之一。數據結構

mmap示意圖

mmap的使用
#include <sys/mman.h>
void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset);
  • 返回:成功:返回建立的映射區首地址;失敗:MAP_FAILED宏
  • 參數:
    • addr:創建映射區的首地址,由Linux內核指定。使用時,直接傳遞NULL
    • length:欲建立映射區的大小
    • prot: 映射區權限PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
    • flags:標誌位參數(經常使用於設定更新物理區域、設置共享、建立匿名映射區)
      • MAP_SHARED: 會將映射區所作的操做反映到物理設備(磁盤)上。
      • MAP_PRIVATE: 映射區所作的修改不會反映到物理設備。
    • fd:用來創建映射區的文件描述符
    • offset:映射文件的偏移(4k的整數倍)

同malloc函數申請內存空間相似的,mmap創建的映射區在使用結束後也應調用相似free的函數來釋放。函數

int munmap(void *addr, size_t length);  //成功:0; 失敗:-1

eg.設計

// 寫共享內存
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

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

int main(int argc, char** argv)
{
    if(argc < 2)
    {
         fprintf(stderr,"Usage: %s filename \n", argv[0]);
         return -1;
    }
    people* p_map;
    char temp = 'a';
    
    int fd = open(argv[1], O_CREAT|O_RDWR|O_TRUNC, 00777);
    if (-1 == fd)
    {
        printf("open file error = %s\n", strerror(errno));
        return -1;
    }
    ftruncate(fd, sizeof(people)*10);
    p_map = (people*)mmap(NULL, sizeof(people)*10, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == p_map)
    {
        printf("mmap file error = %s\n", strerror(errno));
        return -1;
    }
    
    for(int i = 0; i < 10; i++)
    {
        memcpy( (*(p_map+i)).name, &temp, 1);
        (*(p_map+i)).name[1] = 0;
        (*(p_map+i)).age = 20+i;
        temp += 1;
    }
    printf("initialize over\n");
        
    close(fd);
    munmap(p_map, sizeof(people)*10);
    printf("umap ok \n");
    return 0;
}

// 讀共享內存
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>

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

int main(int argc, char** argv)
{
    if(argc < 2)
    {
         fprintf(stderr,"Usage: %s filename \n", argv[0]);
         return -1;
    }
    people* p_map;
    struct stat filestat;
    
    int fd = open(argv[1], O_CREAT|O_RDWR, 00777);
    if (-1 == fd)
    {
        printf("open file error = %s\n", strerror(errno));
        return -1;
    }
    fstat(fd, &filestat);
    p_map = (people*)mmap(NULL, filestat.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == p_map)
    {
        printf("mmap file error = %s\n", strerror(errno));
        return -1;
    }
    
    for(int i = 0; i < 10; i++)
    {
        printf("name = %s, age = %d\n",(*(p_map+i)).name, (*(p_map+i)).age);
    }
    
    close(fd);
    munmap(p_map, sizeof(people)*10);
    printf("umap ok \n");
    return 0;
}

運行結果:3d

執行./mmap_w people.txt指針

mmap_w

執行./mmap_r people.txtcode

mmap_w

POSIX共享內存

POSIX共享內存使用方法有如下兩個步驟:對象

  1. 經過shm_open建立或打開一個POSIX共享內存對象
  2. 調用mmap將它映射到當前進程的地址空間

和經過內存映射文件進行通訊的使用上差異在於mmap描述符參數獲取方式不同:經過open或shm_open。POSIX共享內存和POSIX消息隊列,有名信號量同樣都是具備隨內核持續性的特色。blog

POSIX共享內存的使用
#include <sys/mman.h>
#include <sys/stat.h>        /* For mode constants */
#include <fcntl.h>           /* For O_* constants */

// 打開一個共享內存的文件句柄
int shm_open(const char *name, int oflag, mode_t mode);
//注意 這裏的名字具備形式 /somename,即必須以 / 爲開頭,由於POSIX共享內存對應的文件是位於/dev/shm這個特殊的文件系統內。

// 刪除一個共享內存的名字,但只有全部程序都關閉,纔會真的刪除
int shm_unlink(const char *name);

eg.

寫共享內存

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

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

main(int argc, char** argv)
{
    if(argc < 2)
    {
         fprintf(stderr,"Usage: %s /filename \n", argv[0]);
         return -1;
    }
    people* p_map;
    char temp = 'a';
    
    int fd = shm_open(argv[1], O_CREAT|O_RDWR, 00777);
    if (-1 == fd)
    {
        printf("open file error = %s\n", strerror(errno));
        return -1;
    }
    ftruncate(fd, sizeof(people)*10);
    p_map = (people*)mmap(NULL, sizeof(people)*10, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == p_map)
    {
        printf("mmap file error = %s\n", strerror(errno));
        return -1;
    }
    
    for(int i = 0; i < 10; i++)
    {
        memcpy( ( *(p_map+i) ).name, &temp, 1);
        ( *(p_map+i) ).name[1] = 0;
        ( *(p_map+i) ).age = 20+i;
        temp += 1;
    }
    printf("initialize over\n");
        
    close(fd);
    munmap(p_map, sizeof(people)*10);
    printf("umap ok \n");
    return 0;
}

讀共享內存

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

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

main(int argc, char** argv)
{
    if(argc < 2)
    {
         fprintf(stderr,"Usage: %s /filename \n", argv[0]);
         return -1;
    }
    people* p_map;
    struct stat filestat;
    
    int fd = shm_open(argv[1], O_CREAT|O_RDWR, 00777);
    if (-1 == fd)
    {
        printf("open file error = %s\n", strerror(errno));
        return -1;
    }
    fstat(fd, &filestat);
    p_map = (people*)mmap(NULL, filestat.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == p_map)
    {
        printf("mmap file error = %s\n", strerror(errno));
        return -1;
    }
    
    for(int i = 0; i < 10; i++)
    {
        printf("name = %s, age = %d\n",(*(p_map+i)).name, (*(p_map+i)).age);
    }
    
    close(fd);
    munmap(p_map, sizeof(people)*10);
    printf("umap ok \n");
    return 0;
}

刪除共享內存

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

main(int argc, char** argv)
{
    if(argc < 2)
    {
         fprintf(stderr,"Usage: %s /filename \n", argv[0]);
         return -1;
    }
    int ret = shm_unlink(argv[1]);
    if (-1 == ret)
    {
        printf("unlink shm error = %s\n", strerror(errno));
        return -1;
    }
    
    printf("unlink ok \n");
    return 0;
}

運行結果:

[root@rocket ipc]# g++ -g -o ipc_posix_mmap_writer ipc_posix_mmap_writer.cpp -lrt
[root@rocket ipc]# ./ipc_posix_mmap_writer /shm_from_mem.txt
initialize over
umap ok
[root@rocket ipc]# g++ -g -o ipc_posix_mmap_reader ipc_posix_mmap_reader.cpp -lrt
[root@rocket ipc]# ./ipc_posix_mmap_reader /shm_from_mem.txt
name = a, age = 20
name = b, age = 21
name = c, age = 22
name = d, age = 23
name = e, age = 24
name = f, age = 25
name = g, age = 26
name = h, age = 27
name = i, age = 28
name = j, age = 29
umap ok
[root@rocket ipc]# ./ipc_posix_mmap_unlink /shm_from_mem.txt
unlink ok
[root@rocket ipc]# ./ipc_posix_mmap_unlink /shm_from_mem.txt
unlink shm error = No such file or directory
[root@rocket ipc]# ll /dev/shm/|grep mem
[root@rocket ipc]#

System V共享內存

系統調用mmap()經過映射一個普通文件實現共享內存。System V則是經過映射特殊文件系統shm中的文件實現進程間的共享內存通訊。也就是說,每一個共享內存區域對應特殊文件系統shm中的一個文件(這是經過shmid_kernel結構聯繫起來的)。進程間須要共享的數據被放在一個叫作IPC共享內存區域的地方,全部須要訪問該共享區域的進程都要把該共享區域映射到本進程的地址空間中去。System V共享內存經過shmget得到或建立一個IPC共享內存區域,並返回相應的標識符。內核在保證shmget得到或建立一個共享內存區,初始化該共享內存區相應的shmid_kernel結構注同時,還將在特殊文件系統shm中,建立並打開一個同名文件,並在內存中創建起該文件的相應dentry及inode結構,新打開的文件不屬於任何一個進程(任何進程均可以訪問該共享內存區)。全部這一切都是系統調用shmget完成的。

每個共享內存區都有一個控制結構struct shmid_kernel,shmid_kernel是共享內存區域中很是重要的一個數據結構,它是存儲管理和文件系統結合起來的橋樑,定義以下:

struct shmid_kernel /* private to the kernel */
{       
    struct kern_ipc_perm shm_perm;
    struct file* shm_file;
    int id;
    unsigned long shm_nattch;
    unsigned long shm_segsz;
    time_t shm_atim;
    time_t shm_dtim;
    time_t shm_ctim;
    pid_t shm_cprid;
    pid_t shm_lprid;
};

該結構中最重要的一個域應該是shm_file,它存儲了將被映射文件的地址。每一個共享內存區對象都對應特殊文件系統shm中的一個文件,通常狀況下,特殊文件系統shm中的文件是不能用read()、write()等方法訪問的,當採起共享內存的方式把其中的文件映射到進程地址空間後,可直接採用訪問內存的方式對其訪問。
System V共享內存內核結構
內核經過數據結構struct ipc_ids shm_ids維護系統中的全部共享內存區域。上圖中的shm_ids.entries變量指向一個ipc_id結構數組,而每一個ipc_id結構數組中有個指向kern_ipc_perm結構的指針。到這裏讀者應該很熟悉了,對於系統V共享內存區來講,kern_ipc_perm的宿主是shmid_kernel結構,shmid_kernel是用來描述一個共享內存區域的,這樣內核就可以控制系統中全部的共享區域。同時,在shmid_kernel結構的file類型指針shm_file指向文件系統shm中相應的文件,這樣,共享內存區域就與shm文件系統中的文件對應起來。

在建立了一個共享內存區域後,還要將它映射到進程地址空間,系統調用shmat()完成此項功能。因爲在調用shmget()時,已經建立了文件系統shm中的一個同名文件與共享內存區域相對應,所以,調用shmat()的過程至關於映射文件系統shm中的同名文件過程,原理與mmap()大同小異。

System V共享內存的使用
#include <sys/ipc.h>
#include <sys/shm.h>

// 獲取共享內存區域
int shmget(key_t key, size_t size, int shmflg);

// 鏈接共享內存區域
void *shmat(int shmid, const void *shmaddr, int shmflg);

// 斷開共享內存區域
int shmdt(const void *shmaddr);

// 對共享內存區域進行控制
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

// 將path和proj_id轉換成System V IPC key
key_t ftok(const char *pathname, int proj_id);

eg.

寫共享內存

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

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

int main(int argc, char** argv)
{
    int shm_id,i;
    key_t key;
    people* p_map;
    char temp = 'a';
    
    const char* name = "/dev/shm/my_systemv_shm1";
    key = ftok(name,0);
    if (key == -1)
    {
        perror("ftok error");
        return -1;
    }
    shm_id=shmget(key, 4096, IPC_CREAT);
    if(shm_id == -1)
    {
        perror("shmget error");
        return -1;
    }
    p_map=(people*)shmat(shm_id,NULL,0);
    
    for(int i = 0; i < 10; i++)
    {
        memcpy( ( *(p_map+i) ).name, &temp, 1);
        ( *(p_map+i) ).name[1] = 0;
        ( *(p_map+i) ).age = 20+i;
        temp += 1;
    }
    printf("initialize over\n");
    
    if(shmdt(p_map) == -1)
    {
        perror(" detach error ");
        return -1;
    }
    
    return 0;
}

讀共享內存

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

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

int main(int argc, char** argv)
{
    int shm_id,i;
    key_t key;
    people* p_map;
    
    const char* name = "/dev/shm/my_systemv_shm1";
    key = ftok(name,0);
    if (key == -1)
    {
        perror("ftok error");
        return -1;
    }
    shm_id=shmget(key, 4096, IPC_CREAT);
    if(shm_id == -1)
    {
        perror("shmget error");
        return -1;
    }
    p_map=(people*)shmat(shm_id,NULL,0);
    
    for(int i = 0; i < 10; i++)
    {
        printf( "name:%s, ",(*(p_map+i)).name );
        printf( "age %d\n",(*(p_map+i)).age );
    }
    
    if(shmdt(p_map) == -1)
    {
        perror(" detach error ");
        return -1;
    }
    
    return 0;
}

運行結果:

[root@rocket ipc]# g++ -g -o ipc_systemv_mmap_writer ipc_systemv_mmap_writer.cpp
[root@rocket ipc]# touch /dev/shm/my_systemv_shm1
[root@rocket ipc]# ./ipc_systemv_mmap_writer
initialize over
[root@rocket ipc]# g++ -g -o ipc_systemv_mmap_reader ipc_systemv_mmap_reader.cpp
[root@rocket ipc]# ./ipc_systemv_mmap_reader
name:a, age 20
name:b, age 21
name:c, age 22
name:d, age 23
name:e, age 24
name:f, age 25
name:g, age 26
name:h, age 27
name:i, age 28
name:j, age 29

觀察一下共享內存:
[root@rocket ipc]# ./get_ipc_key /dev/shm/my_systemv_shm1
key = 1084739
[root@rocket ipc]# ipcs
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status     
0x00000000 0          gdm        600        393216     2          dest        
0x00000000 32769      gdm        600        393216     2          dest        
0x00000000 65538      gdm        600        393216     2          dest        
0x00000000 98307      gdm        600        393216     2          dest        
0x00108d43 131076     root       0          4096       0   
 
看到咱們新建的共享內存了吧?刪除也很簡單:
[root@rocket ipc]# ipcrm -m 131076
[root@rocket ipc]# ipcs
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status     
0x00000000 0          gdm        600        393216     2          dest        
0x00000000 32769      gdm        600        393216     2          dest        
0x00000000 65538      gdm        600        393216     2          dest        
0x00000000 98307      gdm        600        393216     2          dest
相關文章
相關標籤/搜索