• 【Linux】第十章 进程间通信(管道+system V共享内存)


    🏆个人主页企鹅不叫的博客

    ​ 🌈专栏

    ⭐️ 博主码云gitee链接:代码仓库地址

    ⚡若有帮助可以【关注+点赞+收藏】,大家一起进步!

    💙系列文章💙


    【Linux】第一章环境搭建和配置

    【Linux】第二章常见指令和权限理解

    【Linux】第三章Linux环境基础开发工具使用(yum+rzsz+vim+g++和gcc+gdb+make和Makefile+进度条+git)

    【Linux】第四章 进程(冯诺依曼体系+操作系统+进程概念+PID和PPID+fork+运行状态和描述+进程优先级)

    【Linux】第五章 环境变量(概念补充+作用+命令+main三个参数+environ+getenv())

    【Linux】第六章 进程地址空间(程序在内存中存储+虚拟地址+页表+mm_struct+写实拷贝+解释fork返回值)

    【Linux】第七章 进程控制(进程创建+进程终止+进程等待+进程替换+min_shell)

    【Linux】第八章 基础IO(open+write+read+文件描述符+重定向+缓冲区+文件系统管理+软硬链接)

    【Linux】第九章 动态库和静态库(生成原理+生成和使用+动态链接)



    💎一、进程通信介绍

    🏆1.进程通信概念

    进程间通信(IPC,Interprocess communication)是一组编程接口,让不同进程相互传递、交换信息

    🏆2.进程通信目的

    • 数据传输:一个进程需要将它的数据发送给另一个进程
    • 资源共享:多个进程之间共享同样的资源。
    • 通知事件:一个进程需要向另一个或一组进程发送消息,通知某些或某个进程发生了某种事件(如进程终止时要通知父进程)。
    • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变

    🏆3.进程通信本质

    让不同的进程看到同一份资源。各个进程是独立的,进程间通信是借助第三方资源写入或读取数据

    🏆4.进程通信分类

    管道

    • 匿名管道
    • 命名管道

    System V IPC

    • System V 消息队列
    • System V 共享内存
    • System V 信号量

    POSIX IPC

    • 消息队列
    • 共享内存
    • 信号量
    • 互斥量
    • 条件变量
    • 读写锁

    💎二、管道

    🏆1.概念

    管道是Unix中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的数据流称为一个“管道”。特点是单向传输数据,管道的本质是内核中的缓冲区。

    🏆2.匿名管道

    概念:匿名管道用于进程间通信,这两个进程需要具有亲缘关系(父子进程等)

    匿名管道原理

    进程通信首先让不同的进程看到同一份资源,使用匿名管道的原理是,让父子两个进程看到同一份文件,然后父进程就可以对文件操作读或写,实现父子进程间通信。

    • 父进程分别打开读和写,为了让子进程继承,这样子进程不用再打开了
    • 父进程要关闭对应的读写,管道必须是单向通信的
    • 用户决定关闭父子读写
    • 父子进程看到的是同一份文件资源,当父子进程对该文件进行写入操作时,该文件缓冲区当中的数据并不会进行写时拷贝

    pipe函数

    #include 
    int pipe(int pipefd[2])
    
    • 1
    • 2

    功能: 创建一个匿名管道

    参数:
    pipefd:文件描述符数组,这是一个输出型参数,pipefd[0]表示管道端文件描述符,fdpipefd1]表示管道端文件描述符
    返回值:
    创建管道成功返回0,失败返回-1

    匿名管道使用

    1.父进程调用pipe函数创建管道

    在这里插入图片描述

    2.fork()生成子进程

    在这里插入图片描述

    3.父进程关闭读端,子进程关闭写端

    在这里插入图片描述

    例如:父进程向匿名管道当中写入5行数据,子进程从匿名管道当中将数据读出

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    int main()
    {
     //1.创建管道
    	int pipefd[2] = { 0 };
    	if (pipe(pipefd) < 0){ //使用pipe创建匿名管道
    		perror("pipe");
    		return 1;
    	}
     //2.创建子进程
    	pid_t id = fork(); //使用fork创建子进程
    	if (id == 0){
    		//child
    		close(pipefd[1]); //子进程关闭写端
    		//子进程读数据
    		char buff[1024];
         while (1){
             ssize_t s = read(pipefd[0], buff, sizeof(buff));
             //读入成功
             if (s > 0){
                 buff[s] = '\0';
                 cout<<"子进程向父进程发送"<<buff<<endl;
             }
             //读取完成
             else if (s == 0){
                 cout<<"子进程读取完成"<<endl;
                 break;
             }
             //读取失败
             else{
                 cout<<"读取错误"<<endl;
                 break;
             }
         }
    	    close(pipefd[0]); //子进程读取完毕,关闭文件
         exit(0);
    	}
    	//father
    	close(pipefd[0]); //父进程关闭读端
    	//父进程写数据
     const char* msg = "hello child, I am father...";
     int count = 5;
     while (count--){
         write(pipefd[1], msg, strlen(msg));
         sleep(1);
     }
     close(pipefd[1]); //父进程写入完毕,关闭文件
     cout<<"父进程写入成功"<<endl;
     exit(0);
    
    	pid_t ret = waitpid(id, nullptr, 0);
     if(ret>0)
     {
         cout<<"等待子进程退出成功"<<endl;
     }
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    结果:

    [Jungle@VM-20-8-centos:~/lesson25]$ ./mypipe
    子进程向父进程发送hello child, I am father...
    子进程向父进程发送hello child, I am father...
    子进程向父进程发送hello child, I am father...
    子进程向父进程发送hello child, I am father...
    子进程向父进程发送hello child, I am father...
    子进程读取完成
    父进程写入成功
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    匿名管道的特点

    • 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信,一般是父子进程
    • 管道提供流式服务。也就是你想往管道里读写多少数据是根据自身来定的
    • 管道是半双工的,数据只能向一个方向流动

    🏆3.用匿名管道实现派发任务

    父子进程通过管道通信,父进程给管道写入命令,指派给子进程,所以现在建立多个管道,由父进程通过多个管道和多个子进程建立联系

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    typedef void (*functor)();//函数指针
    
    vector<functor> functors; // 方法集合
    // for debug
    unordered_map<uint32_t, string> info;
    
    // int32_t: 进程pid, int32_t: 该进程对应的管道写端fd
    typedef std::pair<int32_t, int32_t> elem;
    int processNum = 3;//子进程数量
    
    //三个任务
    void f1()
    {
     cout << "这是一个处理日志的任务, 执行的进程 ID [" << getpid() << "]"
          << "执行时间是[" << time(nullptr) << "]\n" << endl;
     //
    }
    void f2()
    {
     cout << "这是一个备份数据任务, 执行的进程 ID [" << getpid() << "]"
          << "执行时间是[" << time(nullptr) << "]\n" << endl;
    }
    void f3()
    {
     cout << "这是一个处理网络连接的任务, 执行的进程 ID [" << getpid() << "]"
          << "执行时间是[" << time(nullptr) << "]\n" << endl;
    }
    
    void loadFunctor()
    {
     info.insert({functors.size(), "处理日志的任务"});
     functors.push_back(f1);
    
     info.insert({functors.size(), "备份数据任务"});
     functors.push_back(f2);
    
     info.insert({functors.size(), "处理网络连接的任务"});
     functors.push_back(f3);
    }
    
    //子进程工作-读
    void work(int blockFd)
    {
     cout << "进程[" << getpid() << "]" << " 开始工作" << endl;
     // 子进程核心工作的代码
     while (true)
     {
         // a.阻塞等待  b. 获取任务信息
         uint32_t operatorCode = 0;
         //如果有数据就读取,没有就阻塞等待
         ssize_t s = read(blockFd, &operatorCode, sizeof(uint32_t));
         if(s == 0) break;//子进程结束
    
         // c. 处理任务,如果数组访问没有越界
         if(operatorCode < functors.size()) functors[operatorCode]();//函数指针
     }
     cout << "进程[" << getpid() << "]" << " 结束工作" << endl;
    }
    
    // [子进程的pid, 子进程的管道fd]
    void blanceSendTask(const vector<elem> &processFds)
    {
     srand((long long)time(nullptr));
     while(true)
     {
         sleep(1);
         // 选择一个进程, 选择进程是随机的,没有压着一个进程给任务
         // 较为均匀的将任务给所有的子进程 --- 负载均衡
         uint32_t pick = rand() % processFds.size();
    
         // 选择一个任务
         uint32_t task = rand() % functors.size();
    
         // 把任务给一个指定的进程
         write(processFds[pick].second, &task, sizeof(task));
    
         // 打印对应的提示信息
         cout << "父进程指派任务->" << info[task] << "给进程: " << processFds[pick].first << " 编号: " << pick << endl;
     }
    }
    
    int main()
    {
     //加载任务列表
     loadFunctor();
     vector<elem> assignMap;
     // 创建processNum个进程
     for (int i = 0; i < processNum; i++)
     {
         // 定义保存管道fd的对象
         int pipefd[2] = {0};
         // 创建管道
         pipe(pipefd);
         // 创建子进程
         pid_t id = fork();
         //子进程
         if (id == 0)
         {
             // 子进程读取, r -> pipefd[0]
             close(pipefd[1]);
             // 子进程执行
             work(pipefd[0]);
             close(pipefd[0]);
             exit(0);
         }
         //父进程
         else
         {
             //父进程做的事情, pipefd[1]
             close(pipefd[0]);
             elem e(id, pipefd[1]);
             assignMap.push_back(e);
         }
     }
     cout << "create all process success!" << std::endl;
     // 父进程, 派发任务
     blanceSendTask(assignMap);
    
     // 回收资源
     for (int i = 0; i < processNum; i++)
     {
         //assignMap[i]对应的子进程,first-进程PID
         if (waitpid(assignMap[i].first, nullptr, 0) > 0)//成功
             cout << "wait for: pid=" << assignMap[i].first << " wait success!"
                  << "number: " << i << "\n";
         close(assignMap[i].second);
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142

    结果:父进程通过管道控制三个子进程,给子进程派发任务

    [Jungle@VM-20-8-centos:~/lesson26]$ ./pipe
    create all process success!
    进程[9453] 开始工作
    进程[9452] 开始工作
    进程[9454] 开始工作
    父进程指派任务->处理日志的任务给进程: 9452 编号: 0
    这是一个处理日志的任务, 执行的进程 ID [9452]执行时间是[1667614684]
    
    父进程指派任务->备份数据任务给进程: 9453 编号: 1
    这是一个备份数据任务, 执行的进程 ID [9453]执行时间是[1667614685]
    
    父进程指派任务->处理日志的任务给进程: 9453 编号: 1
    这是一个处理日志的任务, 执行的进程 ID [9453]执行时间是[1667614686]
    
    父进程指派任务->备份数据任务给进程: 9452 编号: 0
    这是一个备份数据任务, 执行的进程 ID [9452]执行时间是[1667614687]
    
    父进程指派任务->处理网络连接的任务给进程: 9454 编号: 2
    这是一个处理网络连接的任务, 执行的进程 ID [9454]执行时间是[1667614688]
    
    c父进程指派任务->备份数据任务给进程: 9453 编号: 1
    这是一个备份数据任务, 执行的进程 ID [9453]执行时间是[1667614689]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    🏆3.命名管道

    概念

    命名管道就是一种特殊类型的文件,两个进程通过命名管道的文件名打开同一个管道文件,此时这两个进程也就看到了同一份资源,进而就可以进行通信了

    创建命名管道

    函数创建

    #include 
    #include 
    int mkfifo(const char *pathname, mode_t mode);
    
    • 1
    • 2
    • 3

    功能: 创建一个命名管道
    参数:
    pathname: 管道名称,默认创建在当前路径下
    mode: 权限,一般设置0666,记得设置umask(文件默认掩码)
    返回值: 创建成功返回0,失败返回-1

    命令行创建

    [Jungle@VM-20-8-centos:~/lesson25]$ mkfifo fifo
    [Jungle@VM-20-8-centos:~/lesson25]$ ll
    prw-r--r-- 1 Jungle root     0 113 15:44 fifo
    
    • 1
    • 2
    • 3

    使用命名管道通信

    创建两个文件,server.c,client.c,用两个进程来模拟客户端和服务端进行通信,客户端往管道发消息,服务端读消息,共用头文件comm

    comm.h

    头文件当中提供这个共用的命名管道文件的文件名,客户端和服务器打开同一个命名管道文件,进而进行通信了

    #pragma once
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define IPC_PATH "./.fifo"//让客户端和服务端使用同一个命名管道
    
    using namespace std;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    server.c

    //读
    #include "comm.h"
    
    int main ()
    {
     umask(0); //将文件默认掩码设置为0
     if(mkfifo(IPC_PATH, 0600) != 0)//使用mkfifo创建命名管道文件
     {
         perror(".fifo");
    		return 1;
     }
     //打开文件
     int pipeFd = open(IPC_PATH, O_RDONLY);
     if(pipeFd < 0)
     {
         cerr << "open fifo error" << endl;
         return 2;
     }
     //通信过程
     char buff[1024];
     while(1)
     {
         //以读的方式打开命名管道文件
         ssize_t s = read(pipeFd, buff, sizeof(buff)-1);
         //正常通信
         if(s>0)
         {
             buff[s] = '\0';//预留\0 方便输出
             cout<<"client:"<< buff<<endl;
         }
         //客户端停止
         else if(s==0)
         {
             cout<<"client quit"<<endl;
             break;
         }
         //出错
         else
         {
             cout<<"read error"<<endl;
             break;
         }
     }
     //关闭文件
     close(pipeFd);
     cout<<"server quit"<<endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    client.c

    因为服务端运行起来后命名管道文件就已经被创建了,所以客户端只需以写的方式打开该命名管道文件,之后客户端就可以将通信信息写入到命名管道文件当中,进而实现和服务端的通信

    //写
    #include "comm.h"
    
    int main ()
    {
     //以写的方式打开命名管道文件
     int pipeFd = open(IPC_PATH, O_WRONLY);
     if(pipeFd < 0)
     {
         cerr << "open: " << strerror(errno) << endl;
         return 1;
     }
     //通信过程
     char buff[1024];
     while(1)
     {
         cout<<"Please Enter:";//提示客户端输入
         fflush(stdout);
         //从客户端的标准输入流读取信息
    		ssize_t s = read(0, buff, sizeof(buff)-1);
         if (s > 0){
    			buff[s - 1] = '\0';
    			//将信息写入命名管道
    			write(pipeFd, buff, strlen(buff));
    		}
     }
     //关闭文件
     close(pipeFd);
     cout<<"client quit"<<endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    运行:

    //服务器端接收
    [Jungle@VM-20-8-centos:~/lesson26]$ ./server
    client:123
    client:qwer
    client:你好呀
    //客户端输入
    [Jungle@VM-20-8-centos:~/lesson26]$ ./client
    Please Enter:123
    Please Enter:qwer
    Please Enter:你好呀
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    🏆4.匿名管道和命名管道的区别

    • 匿名管道由pipe函数创建并打开。
    • 命名管道由mkfifo函数创建,打开用open
    • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同

    🏆5.命令行当中的管道

    利用管道“|”同时使用cat命令和grep命令

    下面同时实现打开file.txt文件和只选取带有123的数据

    [Jungle@VM-20-8-centos:~/lesson26]$ cat file.txt
    hello 1
    hello 12
    hello 123
    [Jungle@VM-20-8-centos:~/lesson26]$ cat file.txt | grep 123
    hello 123
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在命令行当中的管道“|”是**匿名管道**,有亲缘关系的进程,他们之间的进程都是同一个父进程

    💎三、system V共享内存

    🏆1.共享内存原理

    共享内存是在物理内存上申请一块空间,再让两个进程各自在页表建立虚拟地址和这块空间的映射关系。这样两个进程看到的就是同一份资源,这一份资源就叫做共享内存。匿名管道,约定使用同一个管道,共享内存,约定使用同一个唯一的Key

    在这里插入图片描述

    共享内存数据结构

    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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    为了让要实现通信的进程能够看到同一个共享内存,我们通过一个Key值,标识共享内存,而这个key值用于标识系统中共享内存的唯一性,通过Key我们可以知道对应的共享内存是否存在

    🏆2.共享内存创建和释放

    ftok-获取标识符key

    #include 
    #include 
    key_t ftok(const char *pathname, int proj_id);
    
    • 1
    • 2
    • 3

    功能: 获取一个共享内存的唯一标识符key(IPC键值)
    函数参数:

    • pathname:可以传入任何文件名,必须存在且可存取
    • proj_id:只有是一个非0的数都可以

    返回值:
    成功返回key值,失败返回-1

    shmget-创建共享内存

    #include 
    #include 
    int shmget(key_t key, size_t size, int shmflg);
    
    • 1
    • 2
    • 3

    功能: 创建共享内存
    函数参数

    • key:共享内存在系统当中的唯一标识
    • size:共享内存的大小(页(4kb)的整数倍)
    • shmflg:权限,由9个权限标准构成

    这里介绍两个选项

    • IPC_CREAT: 如果底层存在这个标识符的共享内存空间,就返回标识符,不存在就创建
    • IPC_EXCL: 如果底层存在这个标识符的共享内存空间,就出错返回,不存在就创建
    • 使用组合IPC_CREAT,一定会获得一个共享内存的标识符,但无法确认该共享内存是否是新建的共享内存。
    • 使用组合IPC_CREAT | IPC_EXCL,只有shmget函数调用成功时才会获得共享内存的标识符,并且该共享内存一定是新建的共享内存。

    两个选项合起来用就可以穿甲一个权限的共享内存空间
    返回值
    成功返回共享内存标识码符(给用户看的),失败返回-1

    创建共享内存

    使用ftok和shmget函数创建一块共享内存

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define PATH_NAME "/home/Jungle/lesson27"//路径
    #define PROJ_ID 0x666//ID
    #define MEM_SIZE 4096//共享内存大小
    
    using namespace std;
    
    int main()
    {
     // 先通过ftok函数 利用pathname和proj_id来生成一个共享内存标识符key,用来标识共享内存(给OS看的)
     key_t key = ftok(PATH_NAME, PROJ_ID); 
    	if (key < 0){
    		perror("ftok");
    		return 1;
    	}
     cout<<"key:"<< key <<endl;
     // 创建内存空间
     // IPC_CREAT 要创建的共享内存如果存在,就打开返回,不存在就创建
     // IPC_EXCL  如果底层共享内存已经存在就出错返回
     // 结合使用可以创建一个全新的共享内存
     int shm = shmget(key, MEM_SIZE, IPC_CREAT | IPC_EXCL); //创建新的共享内存
    	if (shm < 0){
    		perror("shmget");
    		return 2;
    	}
     cout<<"shm:"<<shm<<endl;
     return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    结果:

    [Jungle@VM-20-8-centos:~/lesson27]$ ./test
    key:1711351259
    shm:39
    
    • 1
    • 2
    • 3

    ipcs 命令

    • -q:列出消息队列相关信息。
    • -m:列出共享内存相关信息。
    • -s:列出信号量相关信息。
    • -a 针对所有资源的操作
    标题含义
    key共享内存唯一标识符
    shmid共享内存的用户层id
    owner共享内存的拥有者
    perms共享内存的权限
    bytes共享内存的大小
    nattch共享内存的进程数
    status共享内存的状态

    ipcs -m和ipcrm -m shmid和ipcrm -a

    指令ipcs -m查看ipc资源

    [Jungle@VM-20-8-centos:~/lesson27]$ ipcs -m
    
    ------------ 共享内存段 --------------
    键        shmid      拥有者  权限     字节     nattch     状态      
    0x660125db 39         Jungle     0          4096       0    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ipcrm -m shmid删除共享内存,IPC的声明周期随内核

    [Jungle@VM-20-8-centos:~/lesson27]$ ipcrm -m 39
    [Jungle@VM-20-8-centos:~/lesson27]$ ipcs -m
    
    ------------ 共享内存段 --------------
    键        shmid      拥有者  权限     字节     nattch     状态      
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ipcrm -a:删除所有进程间通信资源

    shmctl**-控制共享内存**

    #include 
    #include 
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    
    • 1
    • 2
    • 3

    功能: 控制共享内存
    参数

    • shmid:共享内存标识符
    • cmd:具体的控制动作
    • buf:获取或设置所控制共享内存的数据结构

    第二个参数传入的选项:

    IPC_STAT: 获取共享内存的当前关联值
    IPC_SET: 将共享内存的当前关联值设置为buf所指的数据结构中的值
    IPC_RMID:删除共享内存段

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

    释放共享内存

    释放共享内存,将上面test.cpp文件修改为以下代码,创建共享内存10s后释放共享内存

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define PATH_NAME "/home/Jungle/lesson27"//路径
    #define PROJ_ID 0x666//ID
    #define MEM_SIZE 4096//共享内存大小
    
    using namespace std;
    
    int main()
    {
     // 先通过ftok函数 利用pathname和proj_id来生成一个共享内存标识符key,用来标识共享内存(给OS看的)
     key_t key = ftok(PATH_NAME, PROJ_ID); 
    	if (key < 0){
    		perror("ftok");
    		return 1;
    	}
     cout<<"key:"<< key <<endl;
     // 创建内存空间
     // IPC_CREAT 要创建的共享内存如果存在,就打开返回,不存在就创建
     // IPC_EXCL  如果底层共享内存已经存在就出错返回
     // 结合使用可以创建一个全新的共享内存
     int shm = shmget(key, MEM_SIZE, IPC_CREAT | IPC_EXCL); //创建新的共享内存
    	if (shm < 0){
    		perror("shmget");
    		return 2;
    	}
     cout<<"shm:"<<shm<<endl;
    
     sleep(10);
    	shmctl(shm, IPC_RMID, NULL); //释放共享内存
    	sleep(2);
     return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    命令行监控脚本:

    while :; do ipcs -m; echo "#####################"; sleep 1;done;
    
    • 1

    结果:

    #####################
    
    ------------ 共享内存段 --------------
    键        shmid      拥有者  权限     字节     nattch     状态      
    0x660125db 40         Jungle     0          4096       0                       
    
    #####################
    
    ------------ 共享内存段 --------------
    键        shmid      拥有者  权限     字节     nattch     状态    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    🏆3.关联共享内存和去关联

    shmat-共享内存关联进程

    #include 
    #include 
    void *shmat(int shmid, const void *shmaddr, int shmflg);
    
    • 1
    • 2
    • 3

    功能: 将共享内存空间关联到进程地址空间
    参数

    • shmid:共享内存标识符
    • shmaddr:定共享内存映射到进程地址空间的某一地址,一般取nullptr。
    • shmfig:表示关联共享内存时设置的某些属性

    返回值: 成功返回一个指针(虚拟地址空间中共享内存的地址,是一个虚拟地址),失败返回-1
    说明:
    shmaddr为NULL,核心自动选择一个地址
    shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
    shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整> 数倍。公式:shmaddr -(shmaddr % SHMLBA)
    shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

    关联共享内存

     char *str = (char *)shmat(shm, nullptr, 0);//关联共享内存,记得强转
    
    • 1

    设置权限

    int shm = shmget(key, MEM_SIZE, IPC_CREAT | IPC_EXCL | 0666); //创建新的共享内存
    
    • 1

    结果:关联该共享内存的进程数由0变成了1,共享内存的权限显示是我们设置的666权限

    ------------ 共享内存段 --------------
    键                     shmid      拥有者     权限          字节     nattch     状态      
    0x660125db       40         Jungle     666          4096        1      
    
    • 1
    • 2
    • 3

    shmdt-取消关联

    #include 
    #include 
    int shmdt(const void *shmaddr);
    
    • 1
    • 2
    • 3

    功能: 取消共享内存空间和进程地址空间的关联
    参数:
    shmaddr:共享内存的起始地址(shmat获取的指针)
    返回值: 成功返回0,失败返回-1

    取消共享内存关联

    将进程和这块共享内存关联起来,取消关联共享内存空间

     char *str = (char*)shmat(shm, nullptr, 0);//关联共享内存
     cout<<"attach shm : " << shm << " success\n";
    
     shmdt(str);//取消关联
     cout << "detach shm : " << shm << " success\n";
    
    • 1
    • 2
    • 3
    • 4
    • 5

    结果:共享内存的关联数由1变为0的过程,即取消了共享内存与该进程之间的关联

    ------------ 共享内存段 --------------
    键                     shmid      拥有者     权限          字节     nattch     状态      
    0x660125db       40         Jungle     666          4096        1      
    ------------ 共享内存段 --------------
    键                     shmid      拥有者     权限          字节     nattch     状态      
    0x660125db       40         Jungle     666          4096        0      
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    🏆4.使用共享内存实现进程通信

    client.c和server.c两个文件,还有一个comm.h一个头文件,公共的pathname和proj_id,两个进程就可以得到相同的共享内存唯一标识符,使用服务端创建共享内存,然后连接到共享内存,让客户端也连接上这块共享内存,客户端写数据,服务端不断读

    comm.h

    #pragma once
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define PATH_NAME "/home/Jungle/lesson27"
    #define PROJ_ID 0x666
    #define MEM_SIZE 4096
    
    #define FIFO_FILE ".fifo"//文件名
    
    #define READER O_RDONLY
    #define WRITER O_WRONLY
    
    using namespace std;
    
    key_t  CreateKey()
    {
     // 先通过ftok函数 利用pathname和proj_id来生成一个共享内存标识符key,用来标识共享内存(给OS看的)
     key_t key = ftok(PATH_NAME, PROJ_ID); 
    	if (key < 0){
    		perror("ftok");
    		exit(1);
    	}
     return key ;
    }
    
    void CreateFifo()
    {
     umask(0);//默认遮掩码
     if(mkfifo(FIFO_FILE, 0666) < 0)//创建失败
     {
         perror("mkfifo");
         exit(2);
     }
    }
    
    //选择读或写
    int Open(const string &filename, int flags)
    {
     return open(filename.c_str(), flags);
    }
    
    int Wait(int fd)
    {
     uint32_t values = 0;
     ssize_t s = read(fd, &values, sizeof(values));
     return s;
    }
    
    //信号
    int Signal(int fd)
    {
     uint32_t cmd = 1;
     write(fd, &cmd, sizeof(cmd));
    }
    
    //关闭文件描述符
    int Close(int fd, const string filename)
    {
     close(fd);
     unlink(filename.c_str());//删除指定文件
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    server.c

    #include "comm.h"
    
    using namespace std;
    
    int main()
    {
     CreateFifo();//创建管道
     int fd = Open(FIFO_FILE, READER);//打开文件
    
     // 先通过ftok函数 利用pathname和proj_id来生成一个共享内存标识符key,用来标识共享内存(给OS看的)
     key_t key = CreateKey();
     cout<<"key:"<< key <<endl;
     // 创建内存空间
     // IPC_CREAT 要创建的共享内存如果存在,就打开返回,不存在就创建
     // IPC_EXCL  如果底层共享内存已经存在就出错返回
     // 结合使用可以创建一个全新的共享内存
     int shm = shmget(key, MEM_SIZE, IPC_CREAT | IPC_EXCL | 0666); //创建新的共享内存
    	if (shm < 0){
    		perror("shmget");
    		return 2;
    	}
    
     //关联共享内存
     char *str = (char*)shmat(shm, nullptr, 0);
     //cout<<"attach shm : " << shm << " success"<
    
     // 服务端每隔一秒在显示器上刷新共享内存段中的数据
     while(1)
     {
         // 让读端进行等待
         if(Wait(fd) <= 0) break; 
         printf("%s\n",str);
         sleep(1);
     };
    
     //取消关联
     shmdt(str);
     //cout << "detach shm : " << shm << " success"<
    
     //释放共享内存
    	shmctl(shm, IPC_RMID, nullptr); 
     //cout<< "delete shm : " << shm << " success" << endl;;
    
     //关闭文件描述符
     Close(fd, FIFO_FILE);
    
     return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    client.c

    #include "comm.h"
    #include 
    using namespace std;
    
    int main()
    {
     int fd = Open(FIFO_FILE, WRITER);//写入数据
    
     key_t key = CreateKey();
     //cout<<"key:"<< key <
     // 创建内存空间
     // IPC_CREAT 要创建的共享内存如果存在,就打开返回,不存在就创建
     // IPC_EXCL  如果底层共享内存已经存在就出错返回
     // 结合使用可以创建一个全新的共享内存
     int shm = shmget(key, MEM_SIZE, IPC_CREAT); //创建新的共享内存
    	if (shm < 0){
    		perror("shmget");
    		return 2;
    	}
    
     //关联共享内存
     char *str = (char*)shmat(shm, nullptr, 0);
    
     // 客户端在共享内存写数据
     while(1)
     {
         printf("Please Enter: ");
         fflush(stdout);
         ssize_t s = read(0, str, MEM_SIZE);
         if(s > 0)
         {
             str[s] = '\0';
         }
         Signal(fd);//写完数据就通知
     }
    
     //取消关联
     shmdt(str);
    
     return 0;
    }
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    结论: 共享内存底层不提供任何同步与互斥的机制,共享内存是所有进程中速度最快的


  • 相关阅读:
    比尔盖茨:人工智能将彻底改变人们使用电脑的方式并颠覆软件行业
    SwiftUI SQLite 教程之 构建App本地数据库实现创建、读取、更新和删除(教程含完成项目源码)
    诺亚财富 X Hologres : 统一OLAP分析引擎,全面打造金融数字化分析平台
    UE4 回合游戏项目 08- 攻击界面UI的点击事件
    C#ref和out关键字的有什么区别?
    华为汪涛:5.5G时代UBB目标网,跃升数字生产力
    组件的自定义事件②
    ElasticSearch7.3学习(二十二)----Text字段排序、Scroll分批查询场景解析
    来看这份小微风控中的税票数据(含衍生等字段)
    C8051F020 SMBus一直处于busy状态解决办法
  • 原文地址:https://blog.csdn.net/YQ20210216/article/details/127716838