linux应用编程:进程间通信(三) 共享内存

一、概述

  • 共享内存区是IPC中最快的一种通信方式,也是最受欢迎的一种方式。
  • 一旦内存区映射到共享它的进程的地址空间,这些进程间数据的传递不再涉及内核:
    其他IPC数据传递流程:
    在这里插入图片描述
    共享内存数据传递流程:
    在这里插入图片描述
  • 多个进程共享同一块内存区,需要某种形式的同步,如:互斥锁、条件变量、读写锁、记录锁、信号量等

二、内存映射

2.1 相关函数

mmap()

  • 用来将某个文件内容映射到内存中,对该内存区域的存取即是直接对该文件内容的读写。通过这样可以加快文件访问速度
 #include <sys/mman.h> 
 
 void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
  • 返回值:成功:返回被映射的起始地址;失败:返回MAP_FAILED
  • addr: 指向欲对应的内存起始地址,通常设为 NULL,代表让系统自动选定地址,对应成功后该地址会返回。
  • length: 代表将文件中多大的部分对应到内存。
  • prot:代表映射区域的保护方式有下列组合:

     PROT_EXEC 映射区域可被执行
     PROT_READ 映射区域可被读取
     PROT_WRITE 映射区域可被写入
     PROT_NONE 映射区域不能存取

  • flags: flags 会影响映射区域的各种特性:

    MAP_FIXED 如果参数 start 所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此
    旗标。
    MAP_SHARED 对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
    MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
    MAP_ANONYMOUS 建立匿名映射。此时会忽略参数 fd,不涉及文件,而且映射区域无法和
    其他进程共享。
    MAP_DENYWRITE 只允许对映射区域的写入操作,而不能对 fd 指向的文件进行读写,对该文件直接写入的操
    作将会被拒绝。
    MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。
    在调用 mmap()时必须要指定 MAP_SHARED 或 MAP_PRIVATE。

  • fd: 为 open()返回的文件描述词,代表欲映射到内存的文件
  • offset:为文件映射的偏移量,通常设置为 0,代表从文件最前方开始对应,offset 必须是分页大小的整数倍
    在这里插入图片描述
  • mmap()返回成功,fd参数可以关闭,关闭fd对后续操作无影响。

munmap()

  • 删除映射区与用户进程空间的联系
 #include <sys/mman.h> 
 
 int munmap(void *addr, size_t length);
  • 返回值:成功 0 失败 -1
  • addr:映射内存起始地址
  • length:欲取消的内存大小

msync()

  • 内存映射区(在内存中),内存映射的文件(一般在硬盘上),正常情况下系统定时将内存映射区的数据刷新到内存映射文件中,而msync()则是实现手动实时刷新
 #include <sys/mman.h> 
 
 int msync(void *addr, size_t length, int flags);
  • 返回值:成功0,失败-1
  • addr:内存映射区起始地址
  • length:长度
  • flags

    MS_ASYNC:执行异步写MS_SYNC:执行同步写MS_INVALIDATE:
    在这里插入图片描述在这里插入图片描述

三、system V 共享内存区

3.1 相关函数

shmget()

  • 创建一个新的共享内存区,或访问一个已经存在的共享内存区
 #include <sys/ipc.h>
 #include <sys/shm.h>
 
 int shmget(key_t key, size_t size, int shmflg);
  • 返回值:共享内存区标识符,失败为-1
  • key:IPC_PRIVATE,也可以是ftok()的返回值
  • size:单位指定内存区的大小(当实际操作访问一个已经存在的共享内存区,size应为0)
  • shmflg:同open()的权限,如:0666,也可以与IPC_CREAT或IPC_RCEAT|IPC_EXCL按位或

shmat()

  • 创建或打开一个共享内存区后,通过该函数将该内存区映射到调用进程的地址空间
 #include <sys/types.h>
 #include <sys/shm.h>
 
 void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 返回值:指定共享内存区在调用进程内的起始地址,失败:(void *)-1
  • shmid:共享内存区标识符,即shmget的返回值
  • shmaddr:将共享内存区映射到指定的位置(shmflg未指定SHM_RND的情况),不过通常为NULL,表示让系统自己选择位置
  • shmflg:SHM_RDONLY 访问只读,0 访问可读写

shmdt()

  • 当调用进程完成共享内存区的使用,通过该函数断开内存区与进程地址空间的映射
 #include <sys/types.h>
 #include <sys/shm.h>
 
 int shmdt(const void *shmaddr);
  • 返回值:成功 0, 失败 -1
  • shmaddr:shmat的返回值
  • 注:本函数调用并不删除所指定的共享内存区,只是断开进程地址空间与内存区的关联,删除工作靠shmctl()

shmctl ()

  • 提供了对共享内存区的多种操作
 #include <sys/ipc.h>
 #include <sys/shm.h>

 int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 返回值:成功 0, 失败 -1
  • shmid:共享内存区标识符,即shmget的返回值
  • cmd:将共享内存区映射到指定的位置(shmflg未指定SHM_RND的情况)

    ① IPC_RMID 删除由shmid标识的共享内存区并拆除它(删除指使其标识符失效,拆除指的是释放或回收与它对应的数据结构,包括删除其上的数据)
    ② IPC_SET 经所指定的共享内存区设置struct shmid_ds结构体的成员:shm_perm.uid、shm_perm.gid、shm_perm.mode; shm_ctime的值也可以用于当前时间替换
    ③ IPC_STAT 通过buf参数,向调用者返回所指定共享内存区当前的shmid_ds结构

  • buf
struct shmid_ds {
	struct ipc_perm		shm_perm;	/* operation perms */
	int					shm_segsz;	/* size of segment (bytes) */
	__kernel_time_t		shm_atime;	/* last attach time */
	__kernel_time_t		shm_dtime;	/* last detach time */
	__kernel_time_t		shm_ctime;	/* last change time */
	__kernel_ipc_pid_t	shm_cpid;	/* pid of creator */
	__kernel_ipc_pid_t	shm_lpid;	/* pid of last operator */
	unsigned short		shm_nattch;	/* no. of current attaches */
	unsigned short 		shm_unused;	/* compatibility */
	void 			*shm_unused2;	/* ditto - used by DIPC */
	void			*shm_unused3;	/* unused */
};

ftok()

  • 把一个已经存在的路径和一个整数标识符转换成一个key_t 值,即IPC键
 #include <sys/types.h>
 #include <sys/ipc.h>
 
 key_t ftok(const char *pathname, int proj_id);
  • 返回值:成功 IPC键,可以用该关键字关联一个共享内存段 ; 失败 -1
  • pathname:为一个全路径文件名,并且该文件必须可访问。
  • proj_id:一非 0 字符
3.2 例程
3.2.1 非亲缘进程间通信

(1) 写数据 shm_process1.c

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

#define SHMBUFF_SIZE  1024
#define KEY  43000

int main(int argc, char **argv)
{
    int shmid;
    
    shmid = shmget(KEY, SHMBUFF_SIZE, IPC_CREAT|0666);
    if (shmid < 0) {
        printf("shmget failed\r\n");
        exit(-1);
    }
    printf("shmid = %d\r\n", shmid);

    char *shm_ptr = (char *)shmat(shmid, NULL, 0);
    if (shm_ptr == (char *)-1) {
        printf("shmat failed\r\n");
        exit(-1);
    }
    printf("shm_ptr = %p\r\n", shm_ptr);

    memset(shm_ptr, 0, SHMBUFF_SIZE);
    memcpy(shm_ptr, "hello world", SHMBUFF_SIZE);
    
    if (shmdt(shm_ptr) < 0) {
        printf("shmdt failed\r\n");
    }

    /* if (shmctl(shmid, IPC_RMID, NULL) < 0) {
         printf("shmctl failed\r\n");
         exit(-1);
    } */
    exit(0);
}

(1) 读数据 shm_process2.c

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

#define SHMBUFF_SIZE  1024
#define KEY  43000

int main(int argc, char **argv)
{
    int shmid;
    
    shmid = shmget(KEY, SHMBUFF_SIZE, IPC_CREAT|0666);
    if (shmid < 0) {
        printf("shmget failed\r\n");
        exit(-1);
    }
    printf("shmid = %d\r\n", shmid);

    char *shm_ptr = (char *)shmat(shmid, NULL, 0);
    if (shm_ptr == (char *)-1) {
        printf("shmat failed\r\n");
        exit(-1);
    }
    printf("shm_ptr = %s\r\n", shm_ptr);


    if (shmdt(shm_ptr) < 0) {
        printf("shmdt failed\r\n");
    }
    
    /* if (shmctl(shmid, IPC_RMID, NULL) < 0) {
         printf("shmctl failed\r\n");
         exit(-1);
    } */
    exit(0);
}


(3) 结果显示
在这里插入图片描述

  • 用ftok()函数创建key键,在shmget时失败
  • 两进程间通信,谨慎使用shmctl(shmid, IPC_RMID, NULL);
3.2.2 亲缘进程间通信
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

#define SHMBUFF_SIZE  1024

int main(int argc, char **argv)
{
    int shmid;
    
    shmid = shmget(IPC_PRIVATE, SHMBUFF_SIZE, IPC_CREAT|0666);
    if (shmid < 0) {
        printf("shmget failed\r\n");
        exit(-1);
    }
    //printf("shmid = %d\r\n", shmid);

    pid_t pid = fork();
    if (pid < -1) {
        printf("fork failed\r\n");
    } else if (pid == 0) {
        char *shm_ptr = (char *)shmat(shmid, NULL, 0);
        if (shm_ptr < (char *)0) {
            printf("shmat failed\r\n");
            exit(-1);
        }
        memset(shm_ptr, 0, SHMBUFF_SIZE);
        memcpy(shm_ptr, "hello father process!!!, I am child process", SHMBUFF_SIZE);

        if (shmctl(shmid, IPC_RMID, NULL) < 0) {
            printf("shmctl failed\r\n");
            exit(-1);
        }
        exit(0);

    } else {
       char *shm_ptr = (char *)shmat(shmid, NULL, 0);
        if (shm_ptr < (char *)0) {
            printf("shmat failed\r\n");
            exit(-1);
        }
        sleep(2);
        printf("father recv: shm_ptr = %s\r\n", shm_ptr);
        exit(0);
    }

    /* if (shmdt(shm_ptr) < 0) {
        printf("shmdt failed\r\n");
    } */

    
}

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值