大连密封材料 发表于 2024-6-27 03:25:28

【Linux】System V 共享内存

https://img-blog.csdnimg.cn/direct/1d2aa81c868944a58925e91fe6ba910b.gif#pic_center
共享内存区是最快的 IPC 形式。一旦如许的内存映射到共享它的历程的地址空间,这些历程间数据传递不再涉及到内核,换句话说是历程不再通过执行进入内核的系统调用来传递相互的数据。
1. 共享内存表示图

https://img-blog.csdnimg.cn/direct/62d79c1c72de4d8ea3dbe41584c8670d.png#pic_center
2. 共享内存数据结构

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 */
};
3. 共享内存函数

shmget

功能:用来创建共享内存
原型
        int shmget(key_t key, size_t size, int shmflg);
参数
        key:这个共享内存段名字
        size:共享内存大小
        shmflg:由九个权限标志构成,它们的用法和创建文件时使用的 mode 模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-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
https://img-blog.csdnimg.cn/direct/f8175b6b632d4645b2fd1bd2ac94888e.png#pic_center
4. 实例代码测试共享内存



[*]利用共享内存通讯,一定是一个历程创建新的 shm,另一个直接获取共享内存即可。
代码结构:
$ ls
Comm.hppFifo.hppMakefileShmClient.ccShmServer.cc

$ cat Makefile
.PHONY:all
all:shm_client shm_server

shm_server:ShmServer.cc
        g++ -o $@ $^ -std=c++11
shm_client:ShmClient.cc
        g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
        rm -f shm_client shm_server
Fifo.hpp:
#ifndef __COMM_HPP__
#define __COMM_HPP__

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cassert>

using namespace std;

#define Mode 0666
#define Path "./fifo"

class Fifo
{
public:
    Fifo(const string &path = Path)
      : _path(path)
    {
      umask(0);
      int n = mkfifo(_path.c_str(), Mode);
      if (n == 0)
      {
            cout << "mkfifo success" << endl;
      }
      else
      {
            cerr << "mkfifo failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
      }
    }

    ~Fifo()
    {
      int n = unlink(_path.c_str());
      if (n == 0)
      {
            cout << "remove fifo file " << _path << " success" << endl;
      }
      else
      {
            cerr << "remove failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;
      }
    }

private:
    string _path; // 文件路径 + 文件名
};

class Sync
{
public:
    Sync()
      : rfd(-1), wfd(-1)
    {
    }

    void OpenReadOrDie()
    {
      rfd = open(Path, O_RDONLY);
      if (rfd < 0)
            exit(1);
    }

    void OpenWriteOrDie()
    {
      wfd = open(Path, O_WRONLY);
      if (wfd < 0)
            exit(1);
    }

    bool Wait()
    {
      bool ret = true;
      uint32_t c = 0;
      ssize_t n = read(rfd, &c, sizeof(uint32_t));
      if (n == sizeof(uint32_t))
      {
            std::cout << "server wakeup, begin read shm..." << std::endl;
      }
      else if (n == 0)
      {
            ret = false;
      }
      else
      {
            return false;
      }
      return ret;
    }

    void Wakeup()
    {
      uint32_t c = 0;
      ssize_t n = write(wfd, &c, sizeof(c));
      assert(n == sizeof(uint32_t));

      std::cout << "wakeup server..." << std::endl;
    }

    ~Sync()
    {
    }

private:
    int rfd;
    int wfd;
};

#endif
Comm.hpp:
#pragma once

#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <string>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>

using namespace std;

const char *pathname = "/home/ubuntu";
const int proj_id = 0x66;

// 在内核中,共享内存的大小是以4KB为基本单位的,你只能用你申请的大小。建议申请大小是N*4KB
const int defaultsize = 4096; // 单位是字节

std::string ToHex(key_t k)
{
    char buffer;
    snprintf(buffer, sizeof(buffer), "0x%x", k);
    return buffer;
}

key_t GetShmKeyOrDie()
{
    key_t k = ftok(pathname, proj_id);
    if (k < 0)
    {
      std::cerr << "ftok error, errno: " << errno << ", error string: " << strerror(errno) << std::endl;
      exit(1);
    }
    return k;
}

int CreateShmOrDie(key_t key, int size, int flag)
{
    int shmid = shmget(key, size, flag);
    if (shmid < 0)
    {
      std::cerr << "shmget error, errno: " << errno << ", error string: " << strerror(errno) << std::endl;
      exit(2);
    }
    return shmid;
}

int CreateShm(key_t key, int size)
{
    // IPC_CREAT: 不存在就创建,存在就获取
    // IPC_EXCL: 没有意义
    // IPC_CREAT | IPC_EXCL: 不存在就创建,存在就出错返回
    return CreateShmOrDie(key, size, IPC_CREAT | 0666);
}

int GetShm(key_t key, int size)
{
    return CreateShmOrDie(key, size, IPC_CREAT);
}

void DeleteShm(int shmid)
{
    int n = shmctl(shmid, IPC_RMID, nullptr);
    if (n < 0)
    {
      std::cerr << "shmctl error" << std::endl;
    }
    else
    {
      std::cout << "shmctl delete shm success, shmid: " << shmid << std::endl;
    }
}

void ShmDebug(int shmid)
{
    struct shmid_ds shmds;
    int n = shmctl(shmid, IPC_STAT, &shmds);
    if (n < 0)
    {
      std::cerr << "shmctl error" << std::endl;
      return;
    }
    std::cout << "shmds.shm_segez: " << shmds.shm_segsz << std::endl;
    std::cout << "shmds.shm_nattch: " << shmds.shm_nattch << std::endl;
    std::cout << "shmds.shm_ctime: " << shmds.shm_ctime << std::endl;
    std::cout << "shmds.shm_perm.__key: " << ToHex(shmds.shm_perm.__key) << std::endl;
}

void *ShmAttach(int shmid)
{
    // 核心自动选择一个地址
    void *addr = shmat(shmid, nullptr, 0);
    if ((long long int)addr == -1)
    {
      std::cerr << "shmat error" << std::endl;
      return nullptr;
    }
    return addr;
}

void ShmDetach(void *addr)
{
    int n = shmdt(addr);
    if (n < 0)
    {
      std::cerr << "shmdt error" << std::endl;
    }
}
ShmServer:
#include "Comm.hpp"
#include "Fifo.hpp"

int main()
{
    // 1. 获取key
    key_t key = GetShmKeyOrDie();
    std::cout << "key: " << ToHex(key) << std::endl;

    // 2. 创建共享内存
    int shmid = CreateShm(key, defaultsize);
    std::cout << "shmid: " << shmid << std::endl;

    // 4. 将共享内存和进程进行挂接(关联)
    char *addr = (char *)ShmAttach(shmid);
    std::cout << "Attach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;

    // 0. 先引入管道
    Fifo fifo;
    Sync syn;
    syn.OpenReadOrDie();

    // 可以进行通信了
    for (;;)
    {
      if (!syn.Wait())
            break;
      std::cout << "shm content: " << addr << std::endl;
    }

    ShmDetach(addr);
    std::cout << "Detach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;

    // 3. 删除共享内存
    DeleteShm(shmid);

    return 0;
}
ShmClient:
#include "Comm.hpp"
#include "Fifo.hpp"

int main()
{
    key_t key = GetShmKeyOrDie();
    std::cout << "key: " << ToHex(key) << std::endl;

    int shmid = GetShm(key, defaultsize);
    std::cout << "shmid: " << shmid << std::endl;

    char *addr = (char *)ShmAttach(shmid);
    std::cout << "Attach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;

    memset(addr, 0, defaultsize);
    Sync syn;
    syn.OpenWriteOrDie();

    // 可以进行通信了
    for (char c = 'A'; c <= 'Z'; c++) // shm没有使用系统调用
    {
      addr = c;
      syn.Wakeup();
      sleep(1);
    }

    ShmDetach(addr);
    std::cout << "Detach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;
    sleep(5);

    return 0;
}
结果演示:
https://img-blog.csdnimg.cn/direct/ee6ea60b5e9048789dd941fa5e9f0627.png#pic_center
https://img-blog.csdnimg.cn/direct/2bb11db5580743a5aa89279edab5a4b4.png#pic_center
5. 共享内存相关命令



[*]共享内存,如果历程结束,我们没有自动开释它,则共享内存一直存在。
[*]共享内存的生命周期是随内核的,只有重启系统它才会自行销毁。
[*]为了更好地控制共享内存,我们要会利用命令删除它。
[*]不管是指令还是代码,想对共享内存举行控制,都必要利用 shmid ,shmid 是共享内存的唯一性标识!
# 查看共享内存
ipcs -m

# 删除指定共享内存
ipcrm -m
# 实机演示
$ ipcs -m

------ Shared Memory Segments --------
key      shmid      owner      perms      bytes      nattch   status      
0x00000000 0          root       644      80         2                     
0x00000000 1          root       644      16384      2                     
0x00000000 2          root       644      280      2                     
0x6602fc97 10         ubuntu   666      4096       1

$ ipcrm -m 10        # 指定shmid即可删除该共享内存资源
留意:不是必须通过手动来删除,这里只是为了演示相关指令,删除共享内存资源是历程该做的变乱。
留意:共享内存没有历程同步与互斥!
6. System V 消息队列(了解)



[*]消息队列提供了从一个历程向另外一个历程发送一块数据的方法。
[*]每个数据块都被认为是有一个范例,接收者历程接收的数据块可以有差异的范例值。
[*]IPC 资源必须删除,否则不会自动清除,除非重启,以是 System V IPC 资源的生命周期随内核。
7. System V 信号量(了解)



[*]信号量重要用于同步和互斥。
[*]由于各历程要求共享资源,而且有些资源必要互斥利用,因此各历程间竞争利用这些资源,历程的这种关系为历程的互斥。
[*]系统中某些资源一次只允许一个历程利用,称如许的资源为临界资源或互斥资源。
[*]在历程中涉及到互斥资源的步伐段叫临界区。
   END
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 【Linux】System V 共享内存