1.共享内存:进程间通信(IPC)的本质就是让不同进程看到同一份资源,而共享内存是最快的IPC形式,当使用共享内存时,IPC就不会涉及到内核,共享内存也没有涉及到访问与控制(同步与互斥)

        1.释放共享内存:1.去关联2.释放共享内存,这些都由操作系统来做(需求方->系统调用->执行方)

        2.设置标志key来让不同进程看到同一块共享内存,和观察内存的存在性

        3.创建IPC-shmget,传地址,大小,操作,创建一个新的共享内存段,或者获取一个已存在的共享内存段:

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

参数分析

                1.key:共享内存段的标识符。用于命名共享内存段,在服务器和客户端之间共享

                2.size:共享内存段的大小,建议是页大小(一般是4096字节)的整数倍

                3.shmflg:权限标志和控制标志,可以组合使用:(如何实现操作?

                4.IPC_CREAT:如果共享内存段不存在,则创建它;如果存在,则返回其标识符

                5.IPC_CREAT | IPC_EXCL:如果共享内存段不存在则创建它;如果已存在,则返回错误

                6.权限标志:(如文件权限)给出访问权限(如 0666 表示用户、组、其他都可读写)

返回值:成功返回一个非负整数,即共享内存段的标识码(shmid);失败返回-1

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
 
int main() {
    key_t key = 1234; // 任意选定一个key
    int shmid;
 
    // 创建共享内存段
    shmid = shmget(key, 4096, 0644 | IPC_CREAT);
    if (shmid == -1) {
        perror("shmget failed");
        exit(1);
    }
 
    printf("共享内存创建成功,ID: %d\n", shmid);
    return 0;
}

  

        4.创建key(ftok):用文件路径和项目ID创建一个System V IPC key

key_t ftok(const char *pathname, int proj_id);

 参数分析:

                1.pathnane:路径名,指向一个存在且可访问的文件

                2.proj_id:项目ID,通常为小整数

返回值:成功返回生成的 key;失败返回 -1

#include <stdio.h>
#include <sys/ipc.h>
#include <stdlib.h>
 
int main() {
    key_t key;
 
    // 使用 ftok 生成一个唯一的key
    key = ftok("/root/mmgd", 'R');
    if (key == -1) {
        perror("ftok failed");
        exit(1);
    }
 
    printf("ftok 生成的key: %d\n", key);
    return 0;
}

                

关于key:

1.key是一个数字。这个数字是几,不重要。关键在于它必须在内核中具有唯一性,能够让不同的进程进行唯一性标识

2.第一个进程可以通过 key 创建共享内存,第二个之后的进程,只要拿着同一个 key 就可以和第一个共享内存

3.对于一个已经创建好的共享内存,key 在哪?key 在共享内存的描述对象中!(保存信物)

4.key--类似(理念)--路径--唯一

第一次创建的时候,必须有一个 key 了. 怎么有?

ftok是一套算法,pathname 和 proj_id 进行了数值计算即可,由用户自己指定

key 为什么要用户来生成?自己设完之后还有可能出现冲突

用户约定的,原理定义时提到过(命名管道:同路径下同一个文件名=路径+文件名),借用 ftok 并不会冲突

共享内存被删除后,则其他线程直接无法通信是错误的,共享内存的删除操作并非直接删除,而是拒绝后续映射,只有在当前映射链接数为0时,表示没有进程访问了,才会真正被删除

shmid与key

key保证了操作系统内的唯一性,shmid 只在你的进程内,用来表示资源的唯一性

只有 shmget 时候用key,大部分情况用户访问共享内存,都用的是shmid

        5.shmat(连接):将共享内存段连接到进程地址空间

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

参数分析:

        1.shmid:共享内存段标识符

        2.shmaddr:连接地址,若为NULL,系统自动选择地址;若为非NULL,按提供的地址连接

        3.shmflg:0(默认连接方式),SHM_RDONLY(只读方式),SHM_RND(将地址向下取整为某个整数倍)

返回值:成功返回指向共享内存段的指针;失败返回 -1

存取返回值,调用实现写入

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
 
int main() {
    key_t key = 1234;
    int shmid;
    char *data;
 
    // 获取共享内存段
    shmid = shmget(key, 4096, 0644 | IPC_CREAT);
    if (shmid == -1) {
        perror("shmget failed");
        exit(1);
    }
 
    // 将共享内存段连接到当前进程
    data = (char *)shmat(shmid, NULL, 0);
    if (*data == -1) {
        perror("shmat failed");
        exit(1);
    }
 
    // 写入数据
    strncpy(data, "Hello, Shared Memory!", 4096);
    printf("数据已写入共享内存: %s\n", data);
 
    return 0;
}

         

        6.shmdt(脱离):共享内存段与当前进程脱离

int shmdt(const void *shmaddr);

参数分析:

        1.shmaddr:由 shmat 返回的指向共享内存的指针

返回值:成功返回0;失败返回-1

经典格式:获取,修改,查看共享内存

        7.shmct(控制删除):控制共享内存段

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

参数分析:

        1.shmid:共享内存段标识符,由 shmget 返回。内核当中获取共享内存的属性

        2.cmd:

                1.IPC_STAT:获取共享内存段的当前关联值

                2.IPC_SET:设置共享内存段的当前关联值(需要足够权限)

                3.IPC_RMID:删除共享内存段

        3.buf:指向shmid_ds结构的指针,struct shmid_ds buf;创建一下

返回值:成功返回0;失败返回-1

        8.总结:

                1.ftok测试代码生成一个唯一的key

                2.shmget测试代码创建一个共享内存段

                3.shmat测试代码将共享内存段连接到当前进程并写入数据

                4.shmdt测试代码将共享内存段与当前进程脱离

                5.shmctl测试代码获取共享内存段的状态,并最终删除它

        9.代码示例

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
#define SHM_SIZE 4096
int main()
{
    key_t key;
    int shmid;
    char* data;
    struct shmid_ds buf;
    //ftok
    printf("make key\n");
    key=ftok("/home/lvy",'R');
    if(key==-1){
        perror("ftok failed");
        exit(1);
    }
    printf("ftok 生成的 key:%d\n",key);
    //creat. shmget
    shmid=shmget(key,SHM_SIZE,0644 | IPC_CREAT);
    if(shmid==-1){
        perror("shmget failed");
        exit(1);
    }
    printf("shmget,ID:%d\n",shmid);
    //shmat
    data=(char*)shmat(shmid,NULL,0);
    if (data == (char *)(-1)) {
        perror("shmat failed");
        exit(1);
    }
    printf("共享内存段连接成功。\n");
    //write
    strncpy(data, "Hello, Shared Memory!", SHM_SIZE);
    printf("数据已写入共享内存: %s\n", data);
    //shmdt
    // 将共享内存段与当前进程脱离
    printf("将共享内存段与当前进程脱离...\n");
    if (shmdt(data) == -1) {
        perror("shmdt failed");
        exit(1);
    }
    printf("共享内存已脱离。\n");
 
    // shmctl
    printf("获取共享内存段的状态...\n");
    if(shmctl(shmid,IPC_STAT,&buf)==-1){
        perror("shmctl (IPC_STAT) failed");
        exit(1);
    }
    //打印结构中内容
    printf("共享内存大小: %zu\n", buf.shm_segsz);
    printf("最后访问时间: %ld\n", buf.shm_atime);
    printf("最后分离时间: %ld\n", buf.shm_dtime);
 
    //shmctl
    printf("删除共享内存段...\n");
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        perror("shmctl (IPC_RMID) failed");
        exit(1);
    }
    printf("共享内存段已删除。\n");
    return 0;
}

展示了如何使用 shmgetshmat、和 shmdt 来创建、连接、和脱离共享内存段,以及使用 ftok 生成唯一的 key 

        10.实验:

堆栈空间在使用时会多搞一点空间备用,就是cookie

上面有没有通信呢?没有

下面来进行通信,直接用。一旦有了共享内存,挂接到自己的地址空间中,你直接把他当成你的内存空间来用即可!不需要调用系统调用

我们来尝试在小项目中使用共享内存,思路:5 步操作存储 shmaddr(=shmat return) 后,对共享地址 read 和 write~

Makefile

comm.hpp

#ifndef __COMM_HPP__
#define __COMM_HPP__
 
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/stat.h>
 
#include "log.hpp"
 
using namespace std;
Log log;
// 共享内存的大小一般建议是4096的整数倍
// 4097,实际上操作系统给你的是4096*2的大小
const int size = 4096; 
const string pathname="/root/myipc";
const int proj_id = 0x6666;
key_t GetKey()
{
    key_t k=ftok(pathname.c_str(),proj_id);
    if(k<0)
    {
         log(Fatal, "ftok error: %s", strerror(errno));
        exit(1);
    }
    log(Info, "ftok success, key is : 0x%x", k);
    return k;
}
 
int GetShareMemHelper(int flag)
{
    key_t k = GetKey();
    int shmid = shmget(k, size, flag);
    if(shmid < 0)
    {
        log(Fatal, "create share memory error: %s", strerror(errno));
        exit(2);
    }
    log(Info, "create share memory success, shmid: %d", shmid);
 
    return shmid;
}
 
int CreateShm()
{
    return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}
 
int GetShm()
{
    return GetShareMemHelper(IPC_CREAT); 
}
 
#define FIFO_FILE "./myfifo"
#define MODE 0664
 
enum
{
    FIFO_CREATE_ERR = 1,
    FIFO_DELETE_ERR,
    FIFO_OPEN_ERR
};
 
class Init
{
public:
    Init()
    {
        // 创建管道
        int n = mkfifo(FIFO_FILE, MODE);
        if (n == -1)
        {
            perror("mkfifo");
            exit(FIFO_CREATE_ERR);
        }
    }
    ~Init()
    {
 
        int m = unlink(FIFO_FILE);
        if (m == -1)
        {
            perror("unlink");
            exit(FIFO_DELETE_ERR);
        }
    }
};
#endif

 processa.cc

#include "comm.hpp"
 
extern Log log;
 
int main()
{
    Init init;
    int shmid = CreateShm();
    char *shmaddr = (char*)shmat(shmid, nullptr, 0);
 
    // ipc code 在这里!!
    // 一旦有人把数据写入到共享内存,其实我们立马能看到了!!
    // 不需要经过系统调用,直接就能看到数据了!
 
    int fd = open(FIFO_FILE, O_RDONLY); // 等待写入方打开之后,自己才会打开文件,向后执行, open 阻塞了!
    if (fd < 0)
    {
        log(Fatal, "error string: %s, error code: %d", strerror(errno), errno);
        exit(FIFO_OPEN_ERR);
    }
    struct shmid_ds shmds;
    while(true)
    {
        char c;
        ssize_t s = read(fd, &c, 1);
        if(s == 0) break;
        else if(s < 0) break;
 
        cout << "client say@ " << shmaddr << endl; //直接访问共享内存
        sleep(1);
 
        shmctl(shmid, IPC_STAT, &shmds);
        cout << "shm size: " << shmds.shm_segsz << endl;
        cout << "shm nattch: " << shmds.shm_nattch << endl;
        printf("shm key: 0x%x\n",  shmds.shm_perm.__key);
        cout << "shm mode: " << shmds.shm_perm.mode << endl;
    }
 
    shmdt(shmaddr);
    shmctl(shmid, IPC_RMID, nullptr);
 
    close(fd);
    return 0;
}

processb.cc

#include "comm.hpp"
 
int main()
{
    int shmid = GetShm();
    char *shmaddr = (char*)shmat(shmid, nullptr, 0);
 
    int fd = open(FIFO_FILE, O_WRONLY); // 等待写入方打开之后,自己才会打开文件,向后执行, open 阻塞了!
    if (fd < 0)
    {
        log(Fatal, "error string: %s, error code: %d", strerror(errno), errno);
        exit(FIFO_OPEN_ERR);
    }
    // 一旦有了共享内存,挂接到自己的地址空间中,你直接把他当成你的内存空间来用即可!
    // 不需要调用系统调用
    // ipc code
    while(true)
    {
        cout << "Please Enter@ ";
        fgets(shmaddr, 4096, stdin);
 
        write(fd, "c", 1); // 通知对方
    }
 
    shmdt(shmaddr);
 
    close(fd);
    return 0;
}

log.hpp

#pragma once
 
#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
 
#define SIZE 1024
 
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
 
#define Screen 1
#define Onefile 2
#define Classfile 3
 
#define LogFile "log.txt"
 
class Log
{
public:
    Log()
    {
        printMethod = Screen;
        path = "./log/";
    }
    void Enable(int method)
    {
        printMethod = method;
    }
    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }
 
    void logmessage(int level, const char *format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
 
        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);
 
        // 格式:默认部分+自定义部分
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);
 
        // 打印日志
        printLog(level, logtxt);
    }
    void printLog(int level, const std::string &logtxt)
    {
        switch (printMethod)
        {
        case Screen:
            std::cout << logtxt;
            break;
        case Onefile:
            printOneFile(LogFile, logtxt);
            break;
        case Classfile:
            printClassFile(level, logtxt);
            break;
        default:
            break;
        }
    }
    void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
        if (fd < 0) {
            perror("open");
            return;
        }
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }
    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
        printOneFile(filename, logtxt);
    }
 
    ~Log() {}
 
    void operator()(int level, const char *format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
 
        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);
 
        // 格式:默认部分+自定义部分
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);
 
        // 打印日志
        printLog(level, logtxt);
    }
 
private:
    int printMethod;
    std::string path;
};
 
int main() {
    Log logger;
 
    // 设置日志输出方法
    logger.Enable(Screen);
    logger.logmessage(Info, "This is an information message.");
    logger(Warning, "This is a warning message.");
 
    // 切换到单文件输出方法
    logger.Enable(Onefile);
    logger(Debug, "This is a debug message written to a file.");
 
    // 切换到按级别分类输出方法
    logger.Enable(Classfile);
    logger(Fatal, "This is a fatal error message written to separate class files.");
 
    return 0;
}

        11.ipc指令

ipcs -m shmid #实现查看ipc资源
ipcrm   #删除共享内存

        12.复习

cp的高级用法:cp ../../XXX . #来实现拷贝到当前路径下

位段,大小端和处理器架构

位段:位段是C语言中用于在结构体或联合中指定数据成员的位大小的一种方式。它们允许您指定哪些位应该用于存储特定的数据,从而节省内存空间。位段的布局取决于编译器和目标平台的字节序

字节序:字节序描述了多字节数据类型(如整数、浮点数等)在内存中的存储方式。有两种常见的字节序

        1.大端字节序:在这种字节序中,最高有效字节(Most Significant Byte)位于内存地址的最小位置

        2.小端字节序:在这种字节序中,最低有效字节(Least Significant Byte)位于内存地址的最小位置

处理器架构:不同的处理器架构默认支持不同的字节序。例如,x86架构通常使用小端字节序,而ARM架构则通常使用大端字节序。x64是x86架构的64位扩展,它也支持小端字节序

缺页中断和报错:在计算机操作系统中,缺页中断和越界 是两种不同的概念,它们在不同的上下文中发生

缺页中断:

        1.定义:缺页中断是在虚拟内存系统中发生的一种异常,当程序试图访问一个不存在的内存页时触发

        2.原因:可能是因为程序试图访问一个未分配或已释放的内存页(信息存储在os的结构体中),或者是因为程序试图访问一个受保护的内存区域

        3.处理:操作系统会处理缺页中断,它会检查页面是否已经在物理内存中,如果是,则重新加载页面;如果不在,则会从磁盘加载页面到物理内存中,并更新内存管理表(如页表)

越界:

        1.定义:越界是指程序尝试访问内存地址范围之外的内存区域

        2.原因:越界通常是由于程序错误地处理了内存地址,例如,访问数组的越界元素,或者访问了一个不存在的内存地址

        3.处理:越界通常由程序本身处理,例如,在数组访问中,如果访问的索引超出了数组的大小,程序会触发一个越界错误。越界错误不会由操作系统处理,而是由程序的错误处理机制处理

        4.处理:

                1.触发条件:缺页中断是由于尝试访问不存在的内存页,而越界是由于尝试访问内存地址范围之外的内存区域

                2.处理机制:缺页中断由操作系统处理,而越界错误由程序本身处理

                3.后果:缺页中断可能导致程序暂停或重新启动,而越界错误可能导致程序崩溃或执行不正确。在实际编程中,程序员需要确保处理越界错误,以防止程序崩溃或产生不正确的结果。操作系统则负责处理缺页中断,以保持系统的稳定性和性能

不存在的内存页和内存地址之外的内存区是怎么区分的?

        1.映射关系:每个内存地址都映射到一个内存页。当程序访问一个内存地址时,操作系统会通过页表查找对应的内存页

        2.访问单位:在编程中,内存地址是操作的单位,但在内存管理中,内存页是分配和管理的单位

        3.虚拟内存与物理内存的桥梁:内存页在虚拟内存和物理内存之间架起了一座桥梁,它允许操作系统将虚拟内存的地址空间映射到物理内存或磁盘上的交换文件

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐