【Linux学习笔记】进程间通信之共享内存

【Linux学习笔记】进程间通信之共享内存

🔥个人主页大白的编程日记

🔥专栏Linux学习笔记


文章目录

  • 【Linux学习笔记】进程间通信之共享内存
    • 前言
    • [一. system V共享内存](#一. system V共享内存)
      • [1.1 共享内存数据结构](#1.1 共享内存数据结构)
      • [1.2 共享内存函数](#1.2 共享内存函数)
      • [1.3 共享内存实现通信](#1.3 共享内存实现通信)
      • [1.4 借助管道实现访问控制版的共享内存](#1.4 借助管道实现访问控制版的共享内存)
    • 后言

前言

哈喽,各位小伙伴大家好!上期我们讲了进程间通信之管道 今天我们讲的是进程间通信之共享内存。话不多说,我们进入正题!向大厂冲锋!

一. system V共享内存

共享内存区是最快的IPC形式。⼀旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执⾏进⼊内核的系统调⽤来传递彼此的数据

共享内存示意图

1.1 共享内存数据结构

bash 复制代码
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 */
};

1.2 共享内存函数

shmget函数

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

shmat函数

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

说明:

bash 复制代码
shmaddr为NULL,核⼼⾃动选择⼀个地址
shmaddr不为NULL且shmflg⽆SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会⾃动向下调整为SHMLBA的整数倍。
公式:shmaddr - (shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表⽰连接操作⽤来只读共享内存

shmdt函数

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

shmctl函数

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

1.3 共享内存实现通信

测试代码结构

bash 复制代码
# ls
client.c comm.c comm.h Makefile server.c
# cat Makefile
.PHONY:all
all:server client
client:client.c comm.c
gcc -o $@ $^
server:server.c comm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f client server
1

comm.h

cpp 复制代码
#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

cpp 复制代码
#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.c

cpp 复制代码
#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.c

cpp 复制代码
#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;
}

1.4 借助管道实现访问控制版的共享内存

  • Comm.hpp
cpp 复制代码
以下是提取的代码:

```cpp
#pragma once

#include <fcntl.h>

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cassert>
#include <cstdio>
#include <ctime>
#include <cstring>
#include <iostream>
#include <string>

using namespace std;

#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3
const std::string msg[] = {
    "Debug",
    "Notice",
    "Warning",
    "Error"
};

std::ostream &Log(std::string message, int level) {
    std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;
    return std::cout;
}

#define PATH_NAME "/home/hyb"
#define PROJ_ID 0x66
#define SHM_SIZE 4096  // 共享内存的大小,最好是页(PAGE: 4096)的整数倍
#define FIFO_NAME "./fifo"

class Init {
public:
    Init() {
        umask(0);
        int n = mkfifo(FIFO_NAME, 0666);
        assert(n == 0);
        (void)n;
        Log("create fifo success", Notice) << "\n";
    }

    ~Init() {
        unlink(FIFO_NAME);
        Log("remove fifo success", Notice) << "\n";
    }
};

#define READ O_RDONLY
#define WRITE O_WRONLY
int OpenFIFO(std::string pathname, int flags) {
    int fd = open(pathname.c_str(), flags);
    assert(fd >= 0);
    return fd;
}

void CloseFifo(int fd) {
    close(fd);
}

void Wait(int fd) {
    Log("等待中....", Notice) << "\n";
    uint32_t temp = 0;
    ssize_t s = read(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;
}

void Signal(int fd) {
    uint32_t temp = 1;
    ssize_t s = write(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;
    Log("唤醒中....", Notice) << "\n";
}

string TransToHex(key_t k) {
    char buffer[32];
    sprintf(buffer, sizeof buffer, "0x%x", k);
    return buffer;
}
cpp 复制代码
以下是提取的代码:

```cpp
#include "Comm.hpp"
Init init;

int main() {
    // 1. 创建公共的Key值

    key_t k = ftok(PATH_NAME, PROJ_ID);
    assert(k != -1);
    Log("create key done", Debug) << " server key : " << TransToHex(k) << endl;

    // 2. 创建共享内存 -- 建议要创建一个全新的共享内存 -- 通信的发起者
    int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid == -1) {
        perror("shmget");
        exit(1);
    }
    Log("create shm done", Debug) << " shmid : " << shmid << endl;

    // 3. 将指定的共享内存,挂接到自己的地址空间
    char *shmaddr = (char*)shmat(shmid, nullptr, 0);
    Log("attach shm done", Debug) << " << shmid << endl;

    // 4. 访问控制
    int fd = OpenFIFO(FIFO_NAME, O_RDONLY);
    while (true) {
        // 阻塞
        Wait(fd);
        // 临界区
        printf("%s\n", shmaddr);
        if (strcmp(shmaddr, "quit") == 0)
            break;
    }
    CloseFifo(fd);

    // 5. 将指定的共享内存,从自己的地址空间中去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    (void)n;
    Log("detach shm done", Debug) << " << shmid << endl;

    // 6. 删除共享内存, IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存
    n = shmctl(shmid, IPC_RMID, nullptr);
    assert(n != -1);
    (void)n;
    Log("delete shm done", Debug) << " << shmid << endl;
    return 0;
}
cpp 复制代码
以下是提取的代码:

```cpp
#include "Comm.hpp"

int main() {
    // 1. 创建公共的key值
    key_t k = ftok(PATH_NAME, PROJ_ID);
    if (k < 0) {
        Log("create key failed", Error) << " client key : " << TransToHex(k) << endl;
        exit(1);
    }
    Log("create key done", Debug) << " client key : " << TransToHex(k) << endl;

    // 2. 获取共享内存
    int shmid = shmget(k, SHM_SIZE, 0);
    if (shmid < 0) {
        Log("create shm failed", Error) << " client key : " << TransToHex(k) << endl;
        exit(2);
    }
    Log("create shm success", Error) << " client key : " << TransToHex(k) << endl;

    // 3. 挂接共享内存
    char* shmaddr = (char*)shmat(shmid, nullptr, 0);
    if (shmaddr == nullptr) {
        Log("attach shm failed", Error) << " client key : " << TransToHex(k) << endl;
        exit(3);
    }
    Log("attach shm success", Error) << " client key : " << TransToHex(k) << endl;

    // 4. 写
    int fd = OpenFIFO(FIFO_NAME, O_WRONLY);
    while (true) {
        ssize_t s = read(0, shmaddr, SHM_SIZE - 1);
        if (s > 0) {
            shmaddr[s - 1] = 0;
            Signal(fd);
            if (strcmp(shmaddr, "quit") == 0)
                break;
        }
    }
    CloseFifo(fd);

    // 5. 去关联
    int n = shmdt(shmaddr);
    assert(n != -1);

    Log("detach shm success", Error) << " client key : " << TransToHex(k) << endl;
    return 0;
}

后言

这就是进程间通信之共享内存。大家自己好好消化!今天就分享到这! 感谢各位的耐心垂阅!咱们下期见!拜拜~

相关推荐
霞姐聊IT2 分钟前
三大并发技术—进程、线程和协程
linux·运维·网络·操作系统
南境十里·墨染春水18 分钟前
linux学习进展 网络编程——HTTPS (补充)
linux·网络·学习
t5y2223 分钟前
【Linux】学习小计
linux
是喵斯特ya24 分钟前
红日内网靶场1环境搭建
笔记
老四啊laosi36 分钟前
【Linux系统】18. 基础IO(文件管理部分)
linux·文件操作·重定向
中屹指纹浏览器38 分钟前
2026浏览器插件扩展安全风险溯源与环境隔离防护规范
经验分享·笔记
mounter6251 小时前
深度解析 dmabuf/devmem:从图形渲染到 AI 与高性能网络的演进之路
linux·网络·人工智能·内存管理·kernel
吃好睡好便好1 小时前
说说损伤膝盖的行为和保护膝盖的方法
学习
北山有鸟1 小时前
RK3588利用NPU加速Lens Shading Correction参数生成
linux
宵时待雨1 小时前
回溯算法专题1:递归
数据结构·c++·笔记·算法·leetcode·深度优先