linux进程间通讯-共享内存

一、什么是共享内存
    顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。因此,采用共享内存的通信方式效率是非常高的。
二、共享内存的分类
    1) POSIX共享内存
        posix提供了两种在无亲缘关系进程间共享内存区的方法:
        (1)内存映射文件:先用open函数打开,然后调用mmap函数把得到的描述符映射到当前进程地址空间中。
             访问速度:慢于内存区对象,因为需要内核同步或异步更新到文件系统中,而内存区对象是直接操作内存的。
             持续性:随文件,即进程重启或内核自举后不丢失,除非显示rm掉文件才会丢失。
        (2)共享内存对象:先用shm_open打开一个Posix IPC名字(也可以是文件系统中的一个路径名),然后调用mmap将返回的描述符映射到当前进程的地址空间。
             访问速度:非常快,因为 /dev/shm 是tmpfs的文件系统,可以看成是直接对内存操作的,速度当然是非常快的。
             持续性:随内核,即进程重启共享内存中数据不会丢失,内核自举或者调用shm_unlink或者rm掉文件后丢失。
    2) SYSTEM V共享内存
        共享内存创建后,执行ipcs命令,会打印出相应的信息,比如下面所示,key为申请时分配的,可以执行ipcrm -M 0x12345678 删除,nattch字段为1表示有一个进程挂载了该内存。
        ------ Shared Memory Segments --------
        key        shmid      owner      perms      bytes      nattch     status     
        0x12345678 32769      root      644        10         1
        访问速度:非常快,可以理解为全内存操作。
        持续性: 随内核,即进程重启共享内存中数据不会丢失,内核自举或调用shmdt或使用ipcrm删除后丢失。
    说明:(a)SYSTEM V的共享内存区对象的大小是在调用shmget创建时固定下来的,而POSIX共享内存区大小可以在任何时刻通过ftruncate修改。(b)Sytem V方式读写速度快于POSIX方式,而POSIX 共享内存和文件映射方式相差不大, 共享内存性能略优。
三、 POSIX共享内存
    1)函数说明:
        A、void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off);
        内存映射文件和共享内存对象这两种方法都需要调用mmap函数,差别在于作为mmap的参数之一的描述符的获取手段。所以先对mmap函数进行介绍。该函数需包含头文件#include
        功能:把一个文件或一个Posix共享内存区对象映射到调用进程的地址空间。
        返回值:如果mmap成功则返回映射首地址,如果出错则返回常数MAP_FAILED。
        参数说明:
        addr:指向映射存储区的起始地址。如果addr参数为NULL,内核会自己在进程地址空间中选择合适的地址建立映射。如果addr不是NULL,则给内核一个提示,应该从什么地址开始映射,内核会选择addr之上的某个合适的地址开始映射。建立映射后,真正的映射首地址通过返回值可以得到。
        len:映射的字节数。
        prot:对映射存储区的保护要求。prot参数有四种取值:
            PROT_EXEC表示映射的这一段可执行,例如映射共享库
            PROT_READ表示映射的这一段可读
            PROT_WRITE表示映射的这一段可写
            PROT_NONE表示映射的这一段不可访问
        flag:标志位。参数有很多种取值,这里只讲两种:
            MAP_SHARED多个进程对同一个文件的映射是共享的,一个进程对映射的内存做了修改,另一个进程也会看到这种变化。
            MAP_PRIVATE多个进程对同一个文件的映射不是共享的,一个进程对映射的内存做了修改,另一个进程并不会看到这种变化,也不会真的写到文件中去。
        filedes:要被映射文件的描述符。
        off:要映射字节在文件中的起始偏移量。
    
        B、int munmap(caddr_t addr,size_t len)
        当进程终止时,该进程的映射内存会自动解除,也可以调用munmap解除映射。
        功能:解除存储映射。再次访问这些地址导致向调用进程产生一个SIGSEGV信号。如果被映射区是使用 MAP_PRIVATE标志映射的,那么调用进程对它所作的变动都被丢弃掉。
        返回值:成功返回0,出错返回-1。
        参数说明:
        addr:指向映射存储区的起始地址,就是由mmap返回的地址。
        len:映射的字节。

        C、int shm_open(const char *name, int oflag, mode_t mode)
        功能:打开或创建一个共享内存区
        返回值:是一个整数描述字,它随后用作mmap的第五个参数
        参数:    
        name:共享内存区的名字
        oflag:标志位。oflag参数必须含有O_RDONLY和O_RDWR标志,还可以指定如下标志:O_CREAT,O_EXCL或O_TRUNC
        mode:权限位,它指定O_CREAT标志的前提下使用
    
        D、int shm_unlink(const char *name)
        功能:函数删除一个共享内存区对象的名字,删除一个名字仅仅防止后续的open,mq_open或sem_open调用取得成功。
        返回值:成功返回0,出错返回-1
        参数:     
        name:共享内存区的名字
    
        E、int msync(void *addr,size_t len,int flags);
        内核的虚存算法保持内存映射文件(一般在硬盘上)与内存映射区(在内存中)的同步(前提它是MAP_SHARED内存区)。这就是说,如果我们修改了内存映射到某个文件的内存区中某个位置的内容,那么内核将在稍后某个时刻相应地更新文件。然而有时候我们希望确信硬盘上的文件内容与内存映射区中的文件内容一致,于是调用msync来执行这种同步。
        功能:同步文件到存储器
        返回值:若成功则返回0,若出错则返回-1
        参数:
        addr:指向映射存储区的起始地址
        len:映射的字节
        flags:参数为MS_ASYNC(执行异步写),MS_SYNC(执行同步写),MS_INVALIDATE(使高速缓存的数据实效)。其中MS_ASYNC和MS_SYNC这两个常值中必须指定一个,但不能都指定。它们的差别是,一旦写操作已由内核排入队列,MS_ASYNC即返回,而MS_SYNC则要等到写操作完成后才返回。如果还指定了MS_INVALIDATE,那么与其最终拷贝不一致的文件数据的所有内存中拷贝都失效。后续的引用将从文件取得数据。
    
        F、int ftruncate(int fd,off_t length)
        功能:调整文件或共享内存区大小
        返回值:成功返回0,出错返回-1
        参数:
        fd:描述符     
        length:大小
         
        G、int stat(const char *file_name,struct stat *buf);
        功能:获得文件或共享内存区的信息。对于普通文件stat结构可以获得12个以上的成员信息,然而当fd指代一个共享内存区对象时,只有四个成员含有信息。
        返回值:成功返回0,出错返回-1
        参数:
        file_name:文件名
        buf:stat结构
    2)例子

        A、posix内存映射文件例子

        posix_file.c文件

#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
  
#define FILE_MODE (S_IRUSR | S_IWUSR)  
  
const char shmfile[] = "./tmp.shm";  
const int size = 100;  
  
struct namelist {  
    int  id;   
    char name[20];  
};  

int main(void)  
{  
    int fd, pid, status;   
    int *ptr;  
    struct stat stat;   
    int flags = O_RDWR | O_CREAT;  
    
    fd = open(shmfile, flags, FILE_MODE);
      
    ftruncate(fd, size);  
    ptr = (int *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);   
    
    close(fd);
    
    pid = fork();  
    if (pid == 0) { // child process  
        printf("Child %d: start\n", getpid());  

        fd = open(shmfile, flags, FILE_MODE);  
        fstat(fd, &stat);    
        ptr = (int *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);   
        close(fd);  
        struct namelist tmp;  

        // store total num in ptr[0];  
        *ptr = 3;  

        ptr++;  
        struct namelist *cur = (struct namelist *)ptr;  

        // store items  
        tmp.id = 1;  
        strcpy(tmp.name, "Nellson");  
        *cur++ = tmp;  
        tmp.id = 2;  
        strcpy(tmp.name, "Daisy");  
        *cur++ = tmp;  
        tmp.id = 3;  
        strcpy(tmp.name, "Robbie");  
        *cur++ = tmp;  

        exit(0);  
    } else { // parent process  
        sleep(1);  
        struct namelist tmp;  
        int i;
        int total = *ptr;  
        printf("There is %d item in the shm\n", total);   

        ptr++;  
        struct namelist *cur = (struct namelist *)ptr;  

        for (i = 0; i< total; i++) {  
            tmp = *cur;  
            printf("%d: %s\n", tmp.id, tmp.name);  
            cur++;  
        }  

        printf("\n");  
        waitpid(pid, &status, 0);  
    }  
    
    printf("Parent %d get child status:%d\n", getpid(), status);  
    return 0;  
}

        Makefile文件

CC       = gcc
WORKDIR  = 
INCLUDES = 
LIBS     =
LINKS    =
TARGET   = posix_file

src=$(wildcard *.c ./callback/*.c)
C_OBJS=$(patsubst %.c, %.o,$(src))
#C_OBJS=$(dir:%.c=%.o)

compile:$(TARGET)
	
$(C_OBJS):%.o:%.c
	$(CC) $(CFLAGS) $(INCLUDES) -o $*.o -c $*.c
	
$(TARGET):$(C_OBJS)
	$(CC) -o $(TARGET) $^ $(LIBS) $(LINKS) 

	@echo 
	@echo Project has been successfully compiled.
	@echo
	
install: $(TARGET)
	cp $(TARGET) $(INSTALL_PATH)

uninstall:
	rm -f $(INSTALL_PATH)/$(TARGET)

rebuild: clean compile

clean:
	rm -rf *.o  $(TARGET) *.log *~

        B、posix共享内存对象例子

        这个例子是两个没有亲缘关系的进程,分成两个文件server.c和client.c,两个进程间使用信号量进行同步。

        server.c文件

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define FILE_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)

//计数器结构体
struct shmstruct {
    int count;
};

//同步有名信号量
sem_t  *mutex;

int main(int argc,char *argv[])
{
    int     fd;
    struct  shmstruct *ptr;
    
    if(argc != 3) {
        printf("usage: server  .\n");
        exit(0);
    }
    
    //防止所需共享内存区对象已经存在
    shm_unlink(argv[1]);
    
    //创建一个新的共享内存区对象
    if((fd = shm_open(argv[1], O_RDWR | O_CREAT | O_EXCL, FILE_MODE)) == -1) {
        perror("shm_open error");
        exit(-1);
    }
    
    //指定新创建的共享内存区对象的大小
    ftruncate(fd, sizeof( struct shmstruct));
    
    //将新创建的共享内存区映射到调用进程的地址空间
    if((ptr = mmap(NULL, sizeof(struct shmstruct), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
        perror("mmap error");
        exit(-1);
    }
    
    //关闭对象描述符
    close(fd);
    
    //防止所需的信号量已经存在
    sem_unlink(argv[2]);
    
    //创建有名信号量,作为互斥锁用
    if((mutex = sem_open(argv[2], O_CREAT|O_EXCL, FILE_MODE, 1)) == SEM_FAILED) {
        perror("sem_open error");
        exit(-1);
    }
    
    //关闭信号量
    sem_close(mutex);
    
    exit(0);
}

        Makefile文件

CC       = gcc
WORKDIR  = 
INCLUDES = 
LIBS     =
LINKS    = -lrt
TARGET   = server

src=$(wildcard *.c ./callback/*.c)
C_OBJS=$(patsubst %.c, %.o,$(src))
#C_OBJS=$(dir:%.c=%.o)

compile:$(TARGET)
	
$(C_OBJS):%.o:%.c
	$(CC) $(CFLAGS) $(INCLUDES) -o $*.o -c $*.c
	
$(TARGET):$(C_OBJS)
	$(CC) -o $(TARGET) $^ $(LIBS) $(LINKS) 

	@echo 
	@echo Project has been successfully compiled.
	@echo
	
install: $(TARGET)
	cp $(TARGET) $(INSTALL_PATH)

uninstall:
	rm -f $(INSTALL_PATH)/$(TARGET)

rebuild: clean compile

clean:
	rm -rf *.o  $(TARGET) *.log *~

        client.c文件

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define FILE_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)

struct shmstruct {
    int count;
};

sem_t  *mutex;

int main(int argc,char *argv[])
{
    int     fd,i,nloop;
    pid_t   pid;
    struct shmstruct *ptr;
    
    if(argc != 4) {
        printf("usage: posix_mem1   .\n");
        exit(0);
    }
    
    nloop = atoi(argv[3]);
    
    //打开共享内存区
    if((fd = shm_open(argv[1], O_RDWR, FILE_MODE)) == -1) {
        perror("shm_open error\n");
        exit(0);
    }
    
    //将共享内存区映射到进程地址空间
    if((ptr = mmap(NULL, sizeof(struct shmstruct), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
        perror("mmap error\n");
        exit(-1);
    }
    close(fd);
    
    //打开信号量
    
    if((mutex = sem_open(argv[2], 0)) == SEM_FAILED) {
        printf("open sem:%s error\n", argv[2]);
        exit(-1);
    }
    
    pid = getpid();
    
    sem_wait(mutex); //锁住信号量
    for(i=0;icount++);        
    }
    sem_post(mutex); //释放信号量
    
    exit(0);
}

        Makefile文件

CC       = gcc
WORKDIR  = 
INCLUDES = 
LIBS     =
LINKS    = -lrt
TARGET   = client

src=$(wildcard *.c ./callback/*.c)
C_OBJS=$(patsubst %.c, %.o,$(src))
#C_OBJS=$(dir:%.c=%.o)

compile:$(TARGET)
	
$(C_OBJS):%.o:%.c
	$(CC) $(CFLAGS) $(INCLUDES) -o $*.o -c $*.c
	
$(TARGET):$(C_OBJS)
	$(CC) -o $(TARGET) $^ $(LIBS) $(LINKS) 

	@echo 
	@echo Project has been successfully compiled.
	@echo
	
install: $(TARGET)
	cp $(TARGET) $(INSTALL_PATH)

uninstall:
	rm -f $(INSTALL_PATH)/$(TARGET)

rebuild: clean compile

clean:
	rm -rf *.o  $(TARGET) *.log *~

        执行结果如下图,一个进程先打印完另一个进程再打印,如果不使用信号量同步,打印将会是交错在一起的!


linux进程间通讯-共享内存_第1张图片

四、 SYSTEM V共享内存

    1)函数说明
        A、int shmget(key_t key, size_t size, int shmflg)
        函数说明:
        第一个参数,与信号量的semget函数一样,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.
        不相关的进程可以通过该函数的返回值访问同一共享内存,它代表程序可能要使用的某个资源,程序对所有共享内存的访问都是间接的,程序先通过调用shmget函数并提供一个键,再由系统生成一个相应的共享内存标识符(shmget函数的返回值),只有shmget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。
        第二个参数,size以字节为单位指定需要共享的内存容量
        第三个参数,shmflg是权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。共享内存的权限标志与文件的读写权限一样,举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。
        
        B、void *shmat(int shm_id, const void *shm_addr, int shmflg)
        功能:
        第一次创建完共享内存时,它还不能被任何进程访问,shmat函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。
        返回值:
        调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1
        参数:
        第一个参数,shm_id是由shmget函数返回的共享内存标识。
        第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
        第三个参数,shm_flg是一组标志位,通常为0。

        C、int shmdt(const void *shmaddr)
        功能:
        该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。
        返回值:
        调用成功时返回0,失败时返回-1
        参数:
        参数shmaddr是shmat函数返回的地址指针

        D、int shmctl(int shm_id, int command, struct shmid_ds *buf)
        功能:
        与信号量的semctl函数一样,用来控制共享内存。
        返回值:
        调用成功时返回0,失败时返回-1
        参数:
        第一个参数,shm_id是shmget函数返回的共享内存标识符。
        第二个参数,command是要采取的操作,它可以取下面的三个值 :
            IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
            IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
            IPC_RMID:删除共享内存段
        第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。
        shmid_ds结构至少包括以下成员:
        struct shmid_ds  
        {  
            uid_t shm_perm.uid;  
            uid_t shm_perm.gid;  
            mode_t shm_perm.mode;  
        };
    
    2)例子

        例子是两个没有亲缘关系的进程,使用互斥锁进行同步,分成两个文件,一个负责读一个负责写。

        共用的头文件sm_common.h

#ifndef __SM_COMMON_H__  
#define __SM_COMMON_H__  
  
#include   
  
#define SM_BUF_SIZE 1024  
#define SM_ID 0x1122  
  
struct sm_msg {  
    int flag;  
    pthread_mutex_t sm_mutex;  
    char buf[SM_BUF_SIZE];  
};  
  
#endif  

        shm_read.c文件

#include   
#include   
#include   
#include   
#include  
#include "sm_common.h"  
  
  
int main(void)  
{  
    int shm_id = -1;  
    int ret = -1;  
    int key = -1;  
    int running = 1;  
    struct sm_msg *msg = NULL;  
    void *shared_memory = NULL;  
    pthread_mutexattr_t attr;  
  
    pthread_mutexattr_init(&attr);  
    pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);  
    
    //key = ftok( "./sm_common.h", 1 );  
    //printf("key: %d\n", key);  
    
    shm_id = shmget((key_t)SM_ID, sizeof(struct sm_msg), 0666|IPC_CREAT);  
    if(shm_id < 0) {  
        printf("fail to shmget\n");  
        exit(1);  
    }  
        
    shared_memory = shmat(shm_id, NULL, 0);  
    if (shared_memory == NULL) {  
        printf("Failed to shmat\n");  
        exit(1); 
    }  
  
    msg = (struct sm_msg *)shared_memory;  
    msg->flag = 0;  
    
    pthread_mutex_init(&msg->sm_mutex, &attr);  
  
    while (running) {  
        pthread_mutex_lock(&msg->sm_mutex);  
        if (msg->flag == 1) {  
            printf("Read message: %s\n", msg->buf);  
            msg->flag = 0;  
            pthread_mutex_unlock(&msg->sm_mutex);  
            if (strncmp(msg->buf, "exit", 4) == 0) {  
                running = 0;  
            }  
        } else {  
            printf("No available data to read, sleep...\n");  
            pthread_mutex_unlock(&msg->sm_mutex);  
            sleep(2);  
        } 
    }  
  
    ret = shmdt(shared_memory);  
    if (ret < 0) {  
        printf("Failed to shmdt\n");  
        exit(1);  
    }  
  
    if(shmctl(shm_id, IPC_RMID, 0) < 0) {  
        printf("failed to shmctl\n");  
        exit(1);  
    }  
 
    return 0;  
}
        Makefile文件

CC       = gcc
WORKDIR  = 
INCLUDES = 
LIBS     =
LINKS    = -lpthread
TARGET   = shm_read

src=$(wildcard *.c ./callback/*.c)
C_OBJS=$(patsubst %.c, %.o,$(src))
#C_OBJS=$(dir:%.c=%.o)

compile:$(TARGET)
	
$(C_OBJS):%.o:%.c
	$(CC) $(CFLAGS) $(INCLUDES) -o $*.o -c $*.c
	
$(TARGET):$(C_OBJS)
	$(CC) -o $(TARGET) $^ $(LIBS) $(LINKS) 

	@echo 
	@echo Project has been successfully compiled.
	@echo
	
install: $(TARGET)
	cp $(TARGET) $(INSTALL_PATH)

uninstall:
	rm -f $(INSTALL_PATH)/$(TARGET)

rebuild: clean compile

clean:
	rm -rf *.o  $(TARGET) *.log *~
        shm_write.c文件

#include 
#include   
#include   
#include   
#include   
  
#include "sm_common.h"  
  
  
int main(void)  
{  
    int shm_id = -1;  
    int ret = -1;  
    int key = -1;  
    int running = 1;  
    struct sm_msg *msg = NULL;  
    void *shared_memory = NULL;
      
    //key = ftok( "./sm_common.h", 1 );  
    //printf("key: %d\n", key);  
    
    shm_id = shmget((key_t)SM_ID, sizeof(struct sm_msg), 0666|IPC_CREAT);  
    if(shm_id < 0) {  
        perror("fail to shmget");  
        exit(1);  
    }  
  
    shared_memory = shmat(shm_id, NULL, 0);  
    if (shared_memory == NULL) {  
        perror("Failed to shmat");  
        exit(1);  
    }  
  
    msg = (struct sm_msg *)shared_memory;  
  
    char buf[32]; 
     
    while (running) {  
        printf("wait lock\n");  
        pthread_mutex_lock(&msg->sm_mutex);  
        printf("get lock\n");  
        if(msg->flag == 1) {  
            printf("Wait for other process's reading\n");  
            pthread_mutex_unlock(&msg->sm_mutex);  
            sleep(2);  
        } else {  
            printf("Please input data\n");  
            fgets(buf, 32, stdin);  
            printf("Write msg: %s\n", buf);  
            strncpy(msg->buf, buf, sizeof(buf));       
            msg->flag = 1;  
            if (strncmp(msg->buf, "exit", 4) == 0) {  
                running = 0;  
            }  
            pthread_mutex_unlock(&msg->sm_mutex);  
        }  
    }  
  
    ret = shmdt(shared_memory);  
    if (ret < 0) {  
        perror("Failed to shmdt");  
        exit(1);  
    }  
      
#if 0 //Do this in server.  
    if(shmctl(shm_id, IPC_RMID, 0) < 0)  
    {  
        perror("failed to shmctl");  
        exit(1);  
    }  
#endif  
    return 0;  
}  

        Makefile文件

CC       = gcc
WORKDIR  = 
INCLUDES = 
LIBS     =
LINKS    = -lpthread
TARGET   = shm_write

src=$(wildcard *.c ./callback/*.c)
C_OBJS=$(patsubst %.c, %.o,$(src))
#C_OBJS=$(dir:%.c=%.o)

compile:$(TARGET)
	
$(C_OBJS):%.o:%.c
	$(CC) $(CFLAGS) $(INCLUDES) -o $*.o -c $*.c
	
$(TARGET):$(C_OBJS)
	$(CC) -o $(TARGET) $^ $(LIBS) $(LINKS) 

	@echo 
	@echo Project has been successfully compiled.
	@echo
	
install: $(TARGET)
	cp $(TARGET) $(INSTALL_PATH)

uninstall:
	rm -f $(INSTALL_PATH)/$(TARGET)

rebuild: clean compile

clean:
	rm -rf *.o  $(TARGET) *.log *~


你可能感兴趣的:(linux进程通讯,linux进程通讯,linux共享内存,共享内存)