专栏首页Frank909Linux 多进程通信开发(六): 共享内存

抱歉,你查看的文章已删除

Linux 多进程通信开发(六): 共享内存

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/briblue/article/details/89191565

这会是一系列文章,讲解的内容也很简单,文章的目的是让自己的知识固话和文档化,以备自己不时的复习,同时也希望能够给予初学者一些帮助。

前面的文章一系列文章有介绍了 linux 下常见的 IPC 机制,如管道、消息队列、信号量,今天这篇文章介绍一下最核心的机制,那就是共享内存,因为它是最高效的 IPC 方式。

什么是共享内存?

共享内存其实很容易理解,不同的进程共享一块内存。

我们都知道,进程间通信的难处就在于进程本身,每一个进程拥有独立的空间,所以数据交互就不方便,需要借助第三方,比如管道其实就是一个文件,消息队列也是因为放在内核中,它们都充当了中间人的角色。

共享内存也差不多,但是它有个特别的地方就是,它可以动态依附到进程空间当中,也可以动态卸载。

理解这一点是非常重要的。当一个进程加载了共享内存的时候,这一块内存就像是本身进程的一部分,和 malloc 分配一般。

共享内存的创建

和其他 IPC 手段类似,共享内存通过 shmget 获取。

#include <sys/shm.h>
int shmget (key_t key, size_t size, int shmflg)
  • key 是有 ftok() 获取到的文件描述符
  • size 是要分配的共享内存的大小
  • shmflg 操作标志,取值有 IPC_CREAT 和 IPC_EXCL
  • IPC_CREATE 如果共享内存不存在则新建,否则返回共享内存标志符
  • IPC_EXCL 和 IPC_CREATE 配合使用,如果共享内存已经存在的话就返回-1,错误码 EEXIST

共享内存的添加与删除

前面讲到共享内存可以动态地添加到一个进程,也能动态卸载它。

映射

#include <sys/shm.h>

void *shmat (int shmid, const void *shmaddr, int shmflg)
  • shmid 前面通过 shmget 得到的 id
  • shmaddr 是共享内存的附加地址,一般为 NULL
  • shmflg 操作标志位,配合 shmaddr 使用,可能取值 SHM_RAND

如果 shmaddr 为 NULL,那么 shmat 会自主选择一块空闲的内核空间,然后将地址返回

如果 shmaddr 不为 NULL, shmflg 指定为 SHM_RAND,那么共享内存的地址会附加到由 shmaddr 指定的地址的一个低端边界地址

如果 shmaddr 不为 NULL,shmflg 没有指定为 SHM_RAND,那么共享地址就是 shmaddr

shmat 的返回值就是处理后的共享内存的地址

卸载

#include <sys/shm.h>
int shmdt (const void *shmaddr)
  • shamaddr 是由 shmat 返回的共享内存的地址

shmdt 返回值为 0 ,代表调用成功,返回值为 -1 代表调用失败。

共享内存的操作

#include <sys/shm.h>

int shmctl (int shmid, int cmd, struct shmid_ds *buf)
  • shmid 共享内存的 id
  • cmd 操作命令,取值有 IPC_RMID、IPC_SET、IPC_STAT 3 种
  • buf 当 cmd 取值为 IPC_SET 时,将 buf 中的内容设置到共享内存的 shmid_ds 结构体中

cmd 取值 IPC_RMID 目的是删除内核中的共享内存 cmd 取值 IPC_SET 是为了设置共享内存的 shmid_ds 结构体 cmd 取值 IPC_STAT 查看共享内存的 shmid_ds 结构体内容

每一块共享内存都有一个对应的 shmid_ds 结构体。

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 */
};
  • shm_segsz 内存的大小
  • shm_nattch 多少个进程连接了这个内存

共享内存示例

一块共享内存可以供多个进程使用,但是使用不当的话,也很危险,因为共享内存本身是没有做同步控制的。

所以,经常就需要用信号量和共享内存配合使用

下面是示例代码.

需要编写 2 个代码文件,分别用于演示发送和接收。

testshmwrite.cpp

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>

using namespace std;


void P(int semid)
{
    struct sembuf buf = {0,-1,0};

    semop(semid,&buf,1);
}

void V(int semid)
{
    struct sembuf buf = {0,1,0};

    semop(semid,&buf,1);
}

int get_sem_id()
{
    key_t keyid = ftok("testforsem",102);

    if (keyid < 0)
    {
        cerr << " get key failed" << endl;
        return -1;
    }

    int semid = semget(keyid,1,IPC_CREAT|0660);
    if (semid < 0)
    {
        cerr << "get semphore failed" << endl;
        return -1;
    }

    return semid;
}


int main(int argc,char** argv)
{
    key_t keyid = ftok ("./forshm",1);

    if (keyid < 0)
    {
        cerr << "get keyid failed !" << endl;
        return -1;
    }

    int shmid = shmget(keyid,1024,IPC_CREAT|0660);

    if (shmid < 0)
    {
        cerr << "get share memory error!" << endl;
        return -1;
    }

    cout << "shmmem keyid " << keyid;
    cout << " share memory id: " << shmid << endl;

    void* addr = shmat(shmid,(char*)0,0);
    if (addr == (void*)-1)
    {
        perror("shmat");
    }

    cout << "shared memory addr :" << addr << endl;

    int semid = get_sem_id();
    if (semid < 0)
    {
        return -1;
    }

     if (semctl(semid,0,SETVAL,1) < 0)
    {
        cerr << "Inital sem val failed" << endl;
        return -1;
    }

    P(semid);
    cout << "I'm pid " << getpid() << " ready for writing a msg " << endl;

    char* msg = "Hello shared memory!";
    char* p = (char*)addr;
    strcpy(p,msg);
    //模拟耗时操作
    sleep(10);
    V(semid);

    cout << "I'm pid " << getpid() << " writed a msg " << endl;

    shmdt(addr);
    
    return 0;
}

下面的是接收代码

testshmread.cpp

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>

using namespace std;


void P(int semid)
{
    struct sembuf buf = {0,-1,0};

    semop(semid,&buf,1);
}

void V(int semid)
{
    struct sembuf buf = {0,1,0};

    semop(semid,&buf,1);
}

int get_sem_id()
{
    key_t keyid = ftok("testforsem",102);

    if (keyid < 0)
    {
        cerr << " get key failed" << endl;
        return -1;
    }

    int semid = semget(keyid,1,IPC_CREAT|0660);
    if (semid < 0)
    {
        cerr << "get semphore failed" << endl;
        return -1;
    }

    return semid;
}


int main(int argc,char** argv)
{
    key_t keyid = ftok ("./forshm",1);

    if (keyid < 0)
    {
        cerr << "get keyid failed !" << endl;
        return -1;
    }

    int shmid = shmget(keyid,1024,IPC_CREAT|0660);

    if (shmid < 0)
    {
        cerr << "get share memory error!" << endl;
        perror("get shmid");
        return -1;
    }

    cout << "shmmem keyid " << keyid;
    cout << " share memory id: " << shmid << endl;

    char* addr = (char*)shmat(shmid,NULL,0);

    if (addr == (char*)-1)
    {
        perror("shmat");
    }

    int semid = get_sem_id();
    if (semid < 0)
    {
        return -1;
    }

    P(semid);

    cout << "I’m pid " << getpid() << " read msg : " << addr << endl;
    
    V(semid);

    shmdt(addr);


    shmctl(shmid,IPC_RMID,NULL);

    semctl(semid,0,IPC_RMID);

    return 0;
}

需要注意的是,我在当前目录新建了两个空文件 testforsem 和 forshm ,这是为了提供给 ftok 用的,用于获取信号量和共享内存的描述符

touch testfoorsem
touch forshm

最后,我们可以编译代码并执行

g++ testshmwrite.cpp -o shmwriter

g++ testshmread.cpp -o shmreader

./shmwriter &

./shmreader

最终结果如下:

[1] 9899
shmmem keyid 17307152 share memory id: 126156879
shared memory addr :0x7f0b165ad000
I'm pid 9899 ready for writing a msg
./shmreader
shmmem keyid 17307152 share memory id: 126156879
I'm pid 9899 writed a msg
I’m pid 10031 read msg : Hello shared memory!
[1]+  已完成               ./shmwriter

可以看到,两个进程顺利通信了。

代码很简单,就不一一讲解了,如有疑问,可以在下方评论。

共享内存的用处

共享内存用到的地方很多,比较适合在进程间处理容量较大的数据。

在 Linux 终端运行 ipcs -m命令,你就可以查看目前系统现存的共享内存。

ipcs -m

------------ 共享内存段 --------------
键        shmid      拥有者  权限     字节     连接数  状态
0x00000000 1507328    frank      600        524288     2          目标
0x00000000 163841     frank      600        524288     2          目标
0x00000000 82345986   frank      600        393216     2          目标
0x00000000 425987     frank      600        524288     2          目标
0x00000000 819204     frank      600        524288     2          目标
0x00000000 622597     frank      600        524288     2          目标
0x00000000 720902     frank      600        524288     2          目标
0x00000000 983047     frank      700        16472      2          目标

我几年前曾在某小型手机厂商上班,当时负责 Android 系统的 Camera 应用。

系统是阿里的 YunOS,当时有一个需求,就是 Camera 要实现拍立淘的功能,这个是要调用阿里的 SDK 的。

Android 系统有自己的 Binder 通信机制,但是实时性不强,所以我当时记得阿里的拍立淘 SDK 是通过共享内存处理照片的,从而相机画面看起来不卡。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 神奇的 ViewDragHelper,让你轻松定制拥有拖拽能力的 ViewGroup

    相信这种效果大家都见过吧?我第一次见到这样的效果时,心里也痒痒的,急于想实现这种功能,后来因为拖延症的问题,就一直没有去弄这件事。现在这段时间,工作比较轻...

    Frank909
  • 针对 CoordinatorLayout 及 Behavior 的一次细节较真

    我一直对 Material Design 很感兴趣,每次在官网上阅读它的相关文档时,我总会有更进一步的体会。当然,Material Design 并不是仅仅针对...

    Frank909
  • OpenCV检测篇(二):笑脸检测

    上篇分享了如何做猫脸检测,本文与之具有知识上的连贯性,所以建议没读过前一篇的先去阅读一下前一篇。这篇主要给大家介绍下如何使用OpenCV进行笑脸检测。

    刘潇龙
  • hdu1065

    @坤的
  • BZOJ2440: [中山市选2011]完全平方数(莫比乌斯+容斥原理)

    Description 小 X 自幼就很喜欢数。但奇怪的是,他十分讨厌完全平方数。他觉得这些 数看起来很令人难受。由此,他也讨厌所有是完全平方数的正整数倍的数。...

    attack
  • BZOJ2580: [Usaco2012 Jan]Video Game(AC自动机)

    attack
  • 为何 Go 的声明语法有点怪?(语法比较)

    Go 语法对第一次接触 Go 的新手来有点怪,因为大家习惯了类 C 语法将类型放在前面的方式,对 Go 将类型放在参数后面有点不习惯,刚开始感觉很别扭,那 Go...

    Dylan Liu
  • 洛谷P1966 火柴排队(逆序对)

    首先要保证权值最小,不难想到一种贪心策略,即把两个序列中rank相同的数放到同一个位置

    attack
  • 【算法】老鼠走迷宫

    老鼠走迷官(一) 说明老鼠走迷宫是递回求解的基本题型,我们在二维阵列中使用2表示迷宫墙壁,使用1来表 示老鼠的行走路径,试以程式求出由入口至出口的路径。 解法老...

    赵腰静

扫码关注云+社区

领取腾讯云代金券