Linux进程通信共享内存

本文深入解析了共享内存的原理与实现,介绍了shmget、shmat、shmdt、shmctl四个关键函数,通过示例展示了如何在Linux环境下创建、连接、断开及控制共享内存,强调了共享内存作为高效进程间通信机制的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

共享内存效率最高,分析一下高在哪里:

共享内存函数由shmget、shmat、shmdt、shmctl四个函数组成。下面的表格列出了这四个函数的函数原型及其具体说明。

 

1.   shmget函数原型

shmget(得到一个共享内存标识符或创建一个共享内存对象)

所需头文件

#include <sys/ipc.h>

#include <sys/shm.h>

函数说明

得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符

函数原型

int shmget(key_t key, size_t size, int shmflg)

函数传入值

key

0(IPC_PRIVATE):会建立新共享内存对象

大于0的32位整数:视参数shmflg来确定操作。通常要求此值来源于ftok返回的IPC键值

size

大于0的整数:新建的共享内存大小,以字节为单位

0:只获取共享内存时指定为0

shmflg

0:取共享内存标识符,若不存在则函数会报错

IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符

IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个消息队列;如果存在这样的共享内存则报错

函数返回值

成功:返回共享内存的标识符

出错:-1,错误原因存于error中

附加说明

上述shmflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限

错误代码

EINVAL:参数size小于SHMMIN或大于SHMMAX

EEXIST:预建立key所指的共享内存,但已经存在

EIDRM:参数key所指的共享内存已经删除

ENOSPC:超过了系统允许建立的共享内存的最大值(SHMALL)

ENOENT:参数key所指的共享内存不存在,而参数shmflg未设IPC_CREAT位

EACCES:没有权限

ENOMEM:核心内存不足

 

在Linux环境中,对开始申请的共享内存空间进行了初始化,初始值为0x00。

如果用shmget创建了一个新的消息队列对象时,则shmid_ds结构成员变量的值设置如下:

Ÿ        shm_lpid、shm_nattach、shm_atime、shm_dtime设置为0。

Ÿ        msg_ctime设置为当前时间。

Ÿ        shm_segsz设成创建共享内存的大小。

Ÿ        shmflg的读写权限放在shm_perm.mode中。

Ÿ        shm_perm结构的uid和cuid成员被设置成当前进程的有效用户ID,gid和cuid成员被设置成当前进程的有效组ID。

2.   shmat函数原型

shmat(把共享内存区对象映射到调用进程的地址空间)

所需头文件

#include <sys/types.h>

#include <sys/shm.h>

函数说明

连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问

函数原型

void *shmat(int shmid, const void *shmaddr, int shmflg)

函数传入值

msqid

共享内存标识符

shmaddr

指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置

shmflg

SHM_RDONLY:为只读模式,其他为读写模式

函数返回值

成功:附加好的共享内存地址

出错:-1,错误原因存于error中

附加说明

fork后子进程继承已连接的共享内存地址。exec后该子进程与已连接的共享内存地址自动脱离(detach)。进程结束后,已连接的共享内存地址会自动脱离(detach)

错误代码

EACCES:无权限以指定方式连接共享内存

EINVAL:无效的参数shmid或shmaddr

ENOMEM:核心内存不足

3.   shmdt函数原型

shmat(断开共享内存连接)

所需头文件

#include <sys/types.h>

#include <sys/shm.h>

函数说明

与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存

函数原型

int shmdt(const void *shmaddr)

函数传入值

shmaddr:连接的共享内存的起始地址

函数返回值

成功:0

出错:-1,错误原因存于error中

附加说明

本函数调用并不删除所指定的共享内存区,而只是将先前用shmat函数连接(attach)好的共享内存脱离(detach)目前的进程

错误代码

EINVAL:无效的参数shmaddr

4.   shmctl函数原型

shmctl(共享内存管理)

所需头文件

#include <sys/types.h>

#include <sys/shm.h>

函数说明

完成对共享内存的控制

函数原型

int shmctl(int shmid, int cmd, struct shmid_ds *buf)

函数传入值

msqid

共享内存标识符

cmd

IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中

IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内

IPC_RMID:删除这片共享内存

buf

共享内存管理结构体。具体说明参见共享内存内核结构定义部分

函数返回值

成功:0

出错:-1,错误原因存于error中

错误代码

EACCESS:参数cmd为IPC_STAT,确无权限读取该共享内存

EFAULT:参数buf指向无效的内存地址

EIDRM:标识符为msqid的共享内存已被删除

EINVAL:无效的参数cmd或shmid

EPERM:参数cmd为IPC_SET或IPC_RMID,却无足够的权限执行

 

共享内存应用范例

1.创建共享内存

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

//./a.out .
int main(int argc, const char *argv[])
{
 char *addr;
 int shm_id;
 key_t key;

  
 if((key = ftok(argv[1],'k'))  < 0)
 {
  perror("Fail to key");
  exit(EXIT_FAILURE);
 }

 printf("key = %#x.\n",key);

    /*
  *int shmget(key_t key, size_t size, int shmflg);
  *第一个参数:
  *1.IPC_PRIVATE,每次都会获得一个新的共享内存,主要用于有亲缘关系的进程
  *2.通过ftok函数获得            
  * key_t key;
  * key = ftok(pathname,字符);
  *
  *第二个参数:
  *共享内存大小
  *
  *第三个参数:
  *IPC_CREAT,IPC_EXCL,权限
  */
 //如果对应的key的共享内存不存在,创建,然后返回其ID
 //如果对应的key的共享内存存在,直接返回ID
 if((shm_id = shmget(key,1024,IPC_CREAT | 0666)) < 0)
 {
  perror("Fail to shmget");
  exit(EXIT_FAILURE);
 }

 printf("shm_id = %d.\n",shm_id);
  
 //映射共享内存
 if( (    addr = (char *)shmat(shm_id,NULL,0)   )   == (char *)-1  )
 {
  perror("Fail to shmat");
  exit(EXIT_FAILURE);
 }
 
 while(1)
 {
  fgets(addr,1024,stdin);
  addr[strlen(addr) - 1] = '\0';
 }

 exit(EXIT_SUCCESS);;
}

 

2.写
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <signal.h>

//./a.out .
int main(int argc, const char *argv[])
{
    char *addr;
    int shm_id;
    key_t key;
    int peer_pid;

    if((key = ftok(argv[1],'k'))  < 0)
        {
        perror("Fail to key");
        exit(EXIT_FAILURE);
        }

    printf("key = %#x.\n",key);

    /*
*int shmget(key_t key, size_t size, int shmflg);
*第一个参数:
*1.IPC_PRIVATE,每次都会获得一个新的共享内存,主要用于有亲缘关系的进程
*2.通过ftok函数获得
* key_t key;
* key = ftok(pathname,字符);
*
*第二个参数:
*共享内存大小
*
*第三个参数:
*IPC_CREAT,IPC_EXCL,权限
*/
    //如果对应的key的共享内存不存在,创建,然后返回其ID
    //如果对应的key的共享内存存在,直接返回ID
    if((shm_id = shmget(key,1024,IPC_CREAT | 0666)) < 0)
        {
        perror("Fail to shmget");
        exit(EXIT_FAILURE);
        }

    printf("shm_id = %d.\n",shm_id);

    //映射共享内存
    if( (    addr = (char *)shmat(shm_id,NULL,0)   )   == (char *)-1  )
        {
        perror("Fail to shmat");
        exit(EXIT_FAILURE);
        }

    //从共享内存中读取对方PID
    peer_pid = *((int *)addr);

    while(1)
        {
        fgets(addr,1024,stdin);
        addr[strlen(addr) - 1] = '\0';

        printf("addr = %s\n",addr);

        //发信号唤醒对方
        kill(peer_pid,SIGUSR1);
        }

    exit(EXIT_SUCCESS);;
}

 

3.读

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <signal.h>

void handler_signal(int signum)
{
    printf("handler_signal\n");
    return;
}

//./a.out .
int main(int argc, const char *argv[])
{
    char *addr;
    int shm_id;
    key_t key;

    //捕捉SIGUSR1
    if(signal(SIGUSR1,handler_signal) == SIG_ERR)
        {
        perror("Fail to signal");
        exit(EXIT_FAILURE);
        }

    //先运行
    if((key = ftok(argv[1],'k'))  < 0)
        {
        perror("Fail to key");
        exit(EXIT_FAILURE);
        }

    printf("key = %#x.\n",key);

    /*
*int shmget(key_t key, size_t size, int shmflg);
*第一个参数:
*1.IPC_PRIVATE,每次都会获得一个新的共享内存,主要用于有亲缘关系的进程
*2.通过ftok函数获得
* key_t key;
* key = ftok(pathname,字符);
*
*第二个参数:
*共享内存大小
*
*第三个参数:
*IPC_CREAT,IPC_EXCL,权限
*/
    //如果对应的key的共享内存不存在,创建,然后返回其ID
    //如果对应的key的共享内存存在,直接返回ID
    if((shm_id = shmget(key,1024,IPC_CREAT | 0666)) < 0)
        {
        perror("Fail to shmget");
        exit(EXIT_FAILURE);
        }

    printf("shm_id = %d.\n",shm_id);

    //映射共享内存
    if( (    addr = (char *)shmat(shm_id,NULL,0)   )   == (char *)-1  )
        {
        perror("Fail to shmat");
        exit(EXIT_FAILURE);
        }

    //写自己PID到共享内存
    *( (int *)addr ) = getpid();

    while(1)
        {
        //等待被唤醒
        pause();
        printf("%s.\n",addr);

        if(strncmp(addr,"quit",4) == 0)
            break;
        }

    //解除映射
    if(shmdt(addr) < 0)
        {
        perror("Fail to shmdt");
        exit(EXIT_FAILURE);
        }

    //删除共享内存对象
    //真正删除:在共享内存映射次数为0的时候,才会删除
    if(shmctl(shm_id,IPC_RMID,NULL) < 0)
        {
        perror("Fail to shmctl");
        exit(EXIT_FAILURE);
        }

    exit(EXIT_SUCCESS);;
}
 
ipcs -m 打印共享内存ID信息
 
 
经过仔细分析,原来共享内存直接通过一块公共的地址来传递数据(通信)。父子进程 和 多个进程都可以,跟管道有区别(无名管道只能用于父子进程通信,有名管道用于多个进程通信)。
 
下面看详细介绍:

一 共享内存介绍

     共享内存可以从字面上去理解,就把一片逻辑内存共享出来,让不同的进程去访问它,修改它。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc分配的内存一样。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

但有一点特别要注意:共享内存并未提供同步机制。也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,例如信号量。

http://www.cnblogs.com/fangshenghui/p/4039720.html

 
本文部分参考:http://blog.csdn.net/guoping16/article/details/6584058

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

静思心远

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值