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

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

前面的文章一系列文章有介绍了 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 是通过共享内存处理照片的,从而相机画面看起来不卡。

发布了102 篇原创文章 · 获赞 3346 · 访问量 175万+

猜你喜欢

转载自blog.csdn.net/briblue/article/details/89191565