Linux system V共享内存函数shmget怎么用?

 更新时间:2026年10月30日 08:47:31   作者:进击的荆棘  
还在为进程间通信效率低而烦恼吗?共享内存作为最快的IPC方式,能让你绕过内核直接传递数据,本文深入讲解共享内存的实现原理、关键函数用法,以及如何借助管道和信号量解决同步互斥问题,帮你避免并发陷阱,提升系统性能

共享内存区是最快的IPC形式。

一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,即进程不再通过执行进入内核的系统调用来传递彼此的数据。

共享内存示意图

共享内存数据结构

truct 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 currentattaches */
    unsigned short shm_unused; /* compatibility */
    void shm_unused2; / ditto - used byDIPC */
    void shm_unused3; / unused */
};

共享内存函数

shmget函数

功能:⽤来创建共享内存
原型
    int shmget(key_t key, size_t size, int shmflg);
参数
    key:这个共享内存段名字
    size:共享内存⼤⼩
    shmflg:由九个权限标志构成,它们的⽤法和创建⽂件时使⽤的mode模式标志是⼀样的
        取值为IPC_CREAT:共享内存不存在,创建并返回;共享内存已存在,获取并返回。
        取值为IPC_CREAT | IPC_EXCL:共享内存不存在,创建并返回;共享内存已存在,
出错返回。
返回值:成功返回⼀个⾮负整数,即该共享内存段的标识码;失败返回-1

shmat函数

功能:将共享内存段连接到进程地址空间
原型
    void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
    shmid: 共享内存标识
    shmaddr:指定连接的地址
    shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回⼀个指针,指向共享内存第⼀个节;失败返回-1

说明

  • shmaddr为NULL,核心自动选择⼀个地址
  • shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
  • shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
  • shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

shmdt函数

功能:将共享内存段与当前进程脱离
原型
    int shmdt(const void *shmaddr);
参数
    shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数

功能:⽤于控制共享内存
原型
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
    shmid:由shmget返回的共享内存标识码
    cmd:将要采取的动作(有三个可取值)
    buf:指向⼀个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

实例1-共享内存实现进程间通信

Makefile

.PHONY:all
all:server client
server:server.cc
	g++ -o $@ $^
client:client.cc
	g++ -o $@ $^

.PHONY:clean
clean:
	rm -f server client

comm.h

#ifndef _COMM_H_
#define _COMM_H_

# include <stdio.h>
# include <sys/types.h>
# include <sys/ipc.h>
# include <sys/shm.h>

# define PATHNAME "."
# define PROJ_ID 0x6666

int createShm(int size);
int destroyShm(int shmid);
int getShm(int size);

# endif

comm.c

#include "comm.h"

static int commShm(int size, int flags)
{
    key_t key = ftok(PATHNAME, PROJ_ID);
    if(key < 0){
        perror("ftok");
        return -1;
    }
    int shmid = 0;
    if( (shmid = shmget(key, size, flags)) < 0){
        perror("shmget");
        return -2;
    }
    return shmid;
}

int destroyShm(int shmid)
{
    if(shmctl(shmid, IPC_RMID, NULL) < 0){
        perror("shmctl");
        return -1;
    }
    return 0;
}

int createShm(int size)
{
    return commShm(size, IPC_CREAT|IPC_EXCL|0666);
}

int getShm(int size)
{
    return commShm(size, IPC_CREAT);
}

server.cc

#include "comm.h"

int main()
{
    int shmid = createShm(4096);
    char *addr = shmat(shmid, NULL, 0);
    sleep(2);
    int i = 0;
    while(i++<26){
        printf("client# %s\n", addr);
        sleep(1);
    }
    shmdt(addr);
    sleep(2);
    destroyShm(shmid);
    return 0;
}

client.cc

#include "comm.h"

int main()
{
    int shmid = getShm(4096);
    sleep(1);
    char *addr = shmat(shmid, NULL, 0);
    sleep(2);
    int i = 0;
    while(i<26){
        addr[i] = 'A'+i;
        i++;
        addr[i] = 0;
        sleep(1);
    }
    shmdt(addr);
    sleep(2);
    return 0;
}

运行结果

ctrl+c终止进程,再次启动

# ./server
shmget: File exists
# ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x66026a25 688145 root 666 4096 0

# ipcrm -m 688145 #删除shm ipc资源,注意,不是必须通过⼿动来删除,这⾥只为演⽰相关指
令,删除IPC资源是进程该做的事情

注:共享内存没有进行同步与互斥。共享内存缺乏访问控制,会带来并发问题

实例2-借助管道实现访问控制版的进程通信

comm.hpp

#pragma once

#include <cstdio>
#include <cstdlib>

#define ERR_EXIT(n) \
do{ \
    perror(n); \
    exit(EXIT_FAILURE); \
}while(0) \

fifo.hpp

#pragma once

#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string>
#include "Comm.hpp"

#define PATH "."
#define FIFONAME "fifo"

class FileFifo{
public:
    FileFifo(const std::string &path,const std::string &name)
        :_path(path),_name(name)
    {
        _pathname=_path+"/"+_name;
        umask(0);
        //创建管道文件
        int n=mkfifo(_pathname.c_str(),0666);
        if(n<0){
            std::cerr<<"mkfifo error"<<std::endl;
            ERR_EXIT("mkfifo");
        }
    }
    ~FileFifo(){
        //删除管道文件
        int n=unlink(_pathname.c_str());
        if(n==0){
            std::cout<<"unlink success"<<std::endl;
        }
        else{
            std::cerr<<"remove fifo failed"<<std::endl;
            ERR_EXIT("unlink");
        }
    }
private:
    std::string _path;
    std::string _name;
    std::string _pathname;
};

class FifoOper{
public:
    FifoOper(const std::string &path,const std::string &name)
        :_path(path),_name(name),_fd(-1)
    {
        _pathname=_path+"/"+_name;
    }
    void OpenForRead(){
        //打开文件
        //当write没有执行open时,read会在open内部进行阻塞
        //直到有人把管道文件打开了,open才会返回
        _fd=open(_pathname.c_str(),O_RDONLY);
        if(_fd<0){
            std::cerr<<"open mkfifo failed"<<std::endl;
            ERR_EXIT("open");
        }
        std::cout<<"open fifo success"<<std::endl;
    }
    void OpenForWrite(){
        //打开文件
        _fd=open(_pathname.c_str(),O_WRONLY);
        if(_fd<0){
            std::cerr<<"open mkfifo failed"<<std::endl;
            ERR_EXIT("open");
        }
        std::cout<<"open fifo success"<<std::endl;
    }
    bool Wait(){
        //读取文件内容
        char c;
        int number=read(_fd,&c,1);
        if(number>0){
            printf("醒来:%d\n",number);
            return true;
        }
        return false;
    }
    void WakeUp(){
        //向文件写入内容
        char c='c';
        int n=write(_fd,&c,1);
        printf("尝试唤醒:%d\n",n);
    }
    void Close(){
        if(_fd>0)
            close(_fd);
    }
    ~FifoOper(){}
private:
    std::string _path;
    std::string _name;
    std::string _pathname;
    int _fd;
};

shm.hpp

#pragma once

#include <iostream>
#include <sys/shm.h>
#include <sys/ipc.h>
#include "Comm.hpp"

const int gdefult = -1;
const int gsize = 4096;
const std::string pathname = ".";
const int projid = 0x66;
const int gmode = 0666;

#define CREATER "creater"
#define USER "user"

class Shm
{
private:
    // 不同的用户需要不同的权限,比如CREATER,我希望它有创建shm和读的权限,但不希望它有写的权限
    // 所以,让一些方法私有,禁止用户直接访问
    void CreateHelper(int flg)
    {
        printf("key is 0x%x\n", _key);
        // 需要给读写执行权限,否则,创建好共享内存后,可能无法读写等
        _shmid = shmget(_key, _size, flg);
        if (_shmid < 0)
        {
            ERR_EXIT("shmget");
        }
        printf("shemget success,id:%d\n", _shmid);
    }
    void Creat()
    {
        CreateHelper(IPC_CREAT | IPC_EXCL | gmode);
    }
    void Get()
    {
        CreateHelper(IPC_CREAT);
    }
    void Attch()
    {
        _start_mm = shmat(_shmid, nullptr, 0);
        if ((long long)_start_mm < 0)
        {
            ERR_EXIT("shmat");
        }
        std::cout<<"attach success"<<std::endl;
    }
    void Detach()
    {
        int n = shmdt(_start_mm);
        if (n < 0)
        {
            ERR_EXIT("shmdt");
        }
        std::cout << "detach success" << std::endl;
    }

    void Destory()
    {
        Detach();
        if (_usertype == CREATER)
        {
            int n = shmctl(_shmid, IPC_RMID, nullptr);
            if (n < 0)
            {
                ERR_EXIT("shmctl");
            }
            std::cout << "delete shm:"<<_shmid<<" successful" << std::endl;
        }
    }

public:
    Shm(const std::string& pathname,int projid,const std::string& usertype)
        : _size(gsize), _shmid(gdefult), _start_mm(nullptr), _usertype(usertype)
    {
        _key = ftok(pathname.c_str(), projid);
        if (_key < 0)
        {
            ERR_EXIT("ftok");
        }

        if (_usertype == CREATER)
        {
            Creat();
        }
        else if (_usertype == USER)
        {
            Get();
        }
        else
        {
        }
        Attch();
    }

    void *VirtualAddr()
    {
        printf("virtual addr:%p\n",_start_mm);
        return _start_mm;
    }
    //输出共享内存的各种属性
    void Attr()
    {
        struct shmid_ds ds;
        int n=shmctl(_shmid,IPC_STAT,&ds);
        printf("shm_segsz:%ld\n",ds.shm_segsz);
        printf("key:0x%x\n",ds.shm_perm.__key);
    }
    int Size()
    {
        return _size;
    }

    ~Shm()
    {
        Destory();
    }

private:
    int _size;
    key_t _key;
    int _shmid;
    void *_start_mm;
    std::string _usertype;
};

server.cc

#include "fifo.hpp"
#include "shm.hpp"

int main(){
    Shm sh(pathname,projid,CREATER);
    sh.Attr();

    //因为虚拟内存没有保护机制,它写的数据会直接被读出
    //所以我们使用管道来保护它的数据完整性
    FileFifo fifo(PATH,FIFONAME);

    FifoOper readerfile(PATH,FIFONAME);
    readerfile.OpenForRead();

    //读写共享内存,不用使用系统调用,使用它就像使用我们从堆栈上申请的一样
    char *mem=(char*)sh.VirtualAddr();
    while(true){
        if(readerfile.Wait()){
            printf("%s\n",mem);
        }
        else break;
    }
    readerfile.Close();
    
    return 0;
}

client.cc

#include "fifo.hpp"
#include "shm.hpp"

int main(){
    FifoOper writerfile(PATH,FIFONAME);
    writerfile.OpenForWrite();

    Shm sh(pathname,projid,USER);

    char * mem=(char*)sh.VirtualAddr();
    int index=0;

    for(char c='A';c<='Z';c++){
        sleep(1);
        mem[index]=c;
        mem[index+1]=c;
        sleep(1);
        mem[index+2]=0;
        index+=2;

        writerfile.WakeUp();
    }

    writerfile.Close();
    return 0;
}

system V消息队列

消息队列提供了一个从一个进程向另一个进程发送一块数据的方法

每个数据块都被认为有一个类型,接收者进程接收的数据块可以有不同的类型值

特性:

IPC资源必须删除,否则不会自动清除,除非重启(因为system V IPC资源的生命周期随内核

system V信号量

信号量主要用于同步和互斥

并发编程-前导

  • 多个执行流(进程),能看到的同一份公共资源:共享资源
  • 被保护起来的共享资源叫做临界资源
  • 保护的方式常见:互斥和同步
  • 任何时刻,只允许一个执行流访问资源,叫做互斥
  • 多个执行流,访问临界资源的时候,具有一定的顺序性,就叫做同步
  • 系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源
  • 在进程中涉及到互斥资源的程序段叫临界区。我们写的代码=访问临界区资源的代码(临界区)+不访问临界区资源的代码(非临界区)
  • 所谓对共享资源进行保护,本质是对访问共享资源的代码进行保护(约束)

信号量

特性

IPC资源必须删除,否则不会自动清除,除非重启,因为system V IPC资源的生命周期随内核

理解

信号量是一个计数器

作用

保护临界区

本质

信号量本质是对资源的预定机制

操作

申请资源,计数器--,P操作

释放资源,计数器++,V操作

内核是如何组织管理IPC资源的

参考Linux2.6.18源码

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • expect实现Linux自动登陆远程机器脚本实例

    expect实现Linux自动登陆远程机器脚本实例

    expect 是由Don Libes基于Tcl(Tool Command Language )语言开发的,主要应用于自动化交互式操作的场景,借助Expect处理交互的命令,可以将交互过程如:ssh登录,ftp登录等交互过程,写到Shell脚本里以实现一些自动化操作。
    2022-12-12
  • Linux检查Swap交换空间的五个命令小结

    Linux检查Swap交换空间的五个命令小结

    这篇文章主要给大家介绍了关于Linux中检查Swap交换空间的五个命令的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用linux具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-10-10
  • linux网络服务搭建sftp指南

    linux网络服务搭建sftp指南

    这篇文章主要介绍了linux网络服务搭建sftp全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2026-06-06
  • Linux文件搜索神器之Find命令的使用完全指南

    Linux文件搜索神器之Find命令的使用完全指南

    find命令是Linux系统中最强大的文件搜索工具,可以按名称、大小、时间等多种维度筛选文件,它支持基础和高级用法,下面小编就和大家详细介绍一下吧
    2026-04-04
  • Linux中FTP服务器搭建与安全配置方式

    Linux中FTP服务器搭建与安全配置方式

    这篇文章主要介绍了Linux中FTP服务器搭建与安全配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-02-02
  • 如何在CentOS 7上搭建GitLab服务器(完整指南)

    如何在CentOS 7上搭建GitLab服务器(完整指南)

    本文详解在CentOS7上搭建GitLab企业级服务器的完整流程,包括环境准备、安装配置、关键设置、故障排查及实操截图,提供国内镜像加速和常见错误解决方案,感兴趣的朋友一起看看吧
    2025-08-08
  • Linux使用System V实现内存共享的最佳实践

    Linux使用System V实现内存共享的最佳实践

    System V的共享内存是Unix操作系统中一种进程间通信机制,它允许不同的进程通过共享一块物理内存区域来交换数据,共享内存提供了高效的进程间通信方式,因为进程可以直接读写共享区域,本文给大家介绍了在Linux上实现内存共享的最佳实践,需要的朋友可以参考下
    2025-07-07
  • Linux使用iptables限制多个IP访问你的服务器

    Linux使用iptables限制多个IP访问你的服务器

    iptables是一个管理netfilter的工具。这篇文章主要介绍了Linux使用iptables限制多个IP访问你的服务器的相关知识,需要的朋友可以参考下
    2018-10-10
  • Linux文件管理使用详解

    Linux文件管理使用详解

    这篇文章主要为大家详细介绍了Linux文件管理的使用方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-04-04
  • Linux系统中ll命令显示内容日期格式方式

    Linux系统中ll命令显示内容日期格式方式

    本文介绍了在Linux系统中使用`ll`命令查看文件日期格式的方法,并提供了两种修改日期格式的命令,第一种是临时修改,使用`export`命令;第二种是永久修改,需要在配置文件中添加相关设置
    2025-02-02

最新评论