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

news2024/11/28 5:34:48

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

​ 🌈专栏

  • C语言初阶和进阶
  • C项目
  • Leetcode刷题
  • 初阶数据结构与算法
  • C++初阶和进阶
  • 《深入理解计算机操作系统》
  • 《高质量C/C++编程》
  • Linux

⭐️ 博主码云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.进程通信概念
    • 🏆2.进程通信目的
    • 🏆3.进程通信本质
    • 🏆4.进程通信分类
  • 💎二、管道
    • 🏆1.概念
    • 🏆2.匿名管道
      • 匿名管道原理
      • pipe函数
      • 匿名管道使用
      • 匿名管道的特点
    • 🏆3.用匿名管道实现派发任务
    • 🏆3.命名管道
      • 概念
      • 创建命名管道
      • 使用命名管道通信
    • 🏆4.匿名管道和命名管道的区别
    • 🏆5.命令行当中的管道
  • 💎三、system V共享内存
    • 🏆1.共享内存原理
    • 🏆2.共享内存创建和释放
      • ftok-获取标识符key
      • shmget-创建共享内存
      • 创建共享内存
      • ipcs 命令
      • ipcs -m和ipcrm -m shmid
      • shmctl**-控制共享内存**
      • 释放共享内存
    • 🏆3.关联共享内存和去关联
      • shmat-共享内存关联进程
      • 关联共享内存
      • shmdt-取消关联
      • 取消共享内存关联
    • 🏆4.使用共享内存实现进程通信


💎一、进程通信介绍

🏆1.进程通信概念

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

🏆2.进程通信目的

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

🏆3.进程通信本质

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

🏆4.进程通信分类

管道

  • 匿名管道
  • 命名管道

System V IPC

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

POSIX IPC

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

💎二、管道

🏆1.概念

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

🏆2.匿名管道

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

匿名管道原理

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

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

pipe函数

#include <unistd>
int pipe(int pipefd[2])

功能: 创建一个匿名管道

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

匿名管道使用

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

在这里插入图片描述

2.fork()生成子进程

在这里插入图片描述

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

在这里插入图片描述

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

#include <cstdio>
#include <iostream>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
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;
}

结果:

[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...
子进程读取完成
父进程写入成功

匿名管道的特点

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

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

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

#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
#include <unordered_map>
#include <ctime>
#include <cstdlib>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <cassert>

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);
 }
}

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

[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]

🏆3.命名管道

概念

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

创建命名管道

函数创建

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

功能: 创建一个命名管道
参数:
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

使用命名管道通信

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

comm.h

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

#pragma once

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

#define IPC_PATH "./.fifo"//让客户端和服务端使用同一个命名管道

using namespace std;

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;
}

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;
}

运行:

//服务器端接收
[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:你好呀

🏆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

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

💎三、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 */
};

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

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

ftok-获取标识符key

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);

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

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

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

shmget-创建共享内存

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

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

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

这里介绍两个选项

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

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

创建共享内存

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

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

#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;
}

结果:

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

ipcs 命令

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

ipcs -m和ipcrm -m shmid

指令ipcs -m查看ipc资源

[Jungle@VM-20-8-centos:~/lesson27]$ ipcs -m

------------ 共享内存段 --------------
键        shmid      拥有者  权限     字节     nattch     状态      
0x660125db 39         Jungle     0          4096       0    

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

[Jungle@VM-20-8-centos:~/lesson27]$ ipcrm -m 39
[Jungle@VM-20-8-centos:~/lesson27]$ ipcs -m

------------ 共享内存段 --------------
键        shmid      拥有者  权限     字节     nattch     状态      

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

#include <sys/types.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

功能: 控制共享内存
参数

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

第二个参数传入的选项:

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

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

释放共享内存

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

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

#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;
}

命令行监控脚本:

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

结果:

#####################

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

#####################

------------ 共享内存段 --------------
键        shmid      拥有者  权限     字节     nattch     状态    

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

shmat-共享内存关联进程

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);

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

  • 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);//关联共享内存,记得强转

设置权限

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

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

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

shmdt-取消关联

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);

功能: 取消共享内存空间和进程地址空间的关联
参数:
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变为0的过程,即取消了共享内存与该进程之间的关联

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

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

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

comm.h

#pragma once

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <fcntl.h>

#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());//删除指定文件
}

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"<<endl;

 // 服务端每隔一秒在显示器上刷新共享内存段中的数据
 while(1)
 {
     // 让读端进行等待
     if(Wait(fd) <= 0) break; 
     printf("%s\n",str);
     sleep(1);
 };

 //取消关联
 shmdt(str);
 //cout << "detach shm : " << shm << " success"<<endl;;

 //释放共享内存
	shmctl(shm, IPC_RMID, nullptr); 
 //cout<< "delete shm : " << shm << " success" << endl;;

 //关闭文件描述符
 Close(fd, FIFO_FILE);

 return 0;
}

client.c

#include "comm.h"
#include <unistd.h>
using namespace std;

int main()
{
 int fd = Open(FIFO_FILE, WRITER);//写入数据

 key_t key = CreateKey();
 //cout<<"key:"<< key <<endl;
 // 创建内存空间
 // 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;
}



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


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/3769.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

工作流的例子

工作流的例子目录概述需求&#xff1a;设计思路实现思路分析1.配置bean2.examples3.no bean4.activiti-api-basic-process-example5.taskspringweb参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c…

C++ 多态类型

多态 C在面向对象中&#xff0c;多态就是不同对象收到相同消息&#xff0c;执行不同的操作。在程序设计中&#xff0c;多态性是名字相同的函数&#xff0c;这些函数执行不同或相似的操作&#xff0c;这样就可以用同一个函数名调用不同内容的函数。简而言之“一个接口&#xff…

2022 国赛postgresql

安装postgresql配置postgresql [root@linux3 ~]# postgresql-setup --initdb //初始化数据库Initializing database in ‘/var/lib/pgsql/data’Initialized, logs are in /var/lib/pgsql/initdb_postgresql.log[root@linux3 ~]# systemctl enable postgresql.service Created …

澳洲最热门职业,护士排第一,医生竟然不如程序员?

2022澳洲最新的职业紧缺名单出炉了&#xff0c;令人惊讶的是护士竟然排行第一名&#xff0c;可见澳洲的医疗人力资源紧缺的问题。 既然人力资源紧缺&#xff0c;那么首当其冲的医生作为高学历且同属医疗行业的代表理应收到重视&#xff0c;然而令人意外的是&#xff0c;通过榜单…

Linux一篇入门(以Ubuntu为例)

一、Linux与Windows区别 Linux&#xff1a;无盘符&#xff0c;只有一个根目录&#xff08;/&#xff09; Windows&#xff1a;有盘符 二、目录相关常见命令 Linux命令格式&#xff1a; cmd -option parameter cdm命令&#xff0c;就是一个操作 parameter一般是要做的对象…

韩国程序员面试考什么?

大家好&#xff0c;我是老三&#xff0c;在G站闲逛的时候&#xff0c;从每日热门上&#xff0c;看到一个韩国的技术面试项目&#xff0c;感觉有点好奇&#xff0c;忍不住点进去看看。 韩国的面试都考什么&#xff1f;有没有国内的卷呢&#xff1f; 可以看到&#xff0c;有8.…

抽象类和接口

文章目录 前言 一、今日回顾 1.《高等数学》 2.阅读&#xff1a; 3.英语&#xff1a; 二、编程的那些事 1.引入库 2.读入数据 总结 前言 一、今日回顾 1.《高等数学》 2.阅读&#xff1a; 3.英语&#xff1a; 二、编程的那些事 1.抽象类的描述 在java中&#xff0…

一次函数与二次函数的联系

首先&#xff0c;无论是一次函数还是二次函数&#xff0c;都是函数&#xff0c;所以便可以从表达式&#xff0c;图像&#xff0c;函数的四个性质&#xff08;即有界性&#xff0c;单调性&#xff0c;奇偶性&#xff0c;周期性&#xff09;去看他们之间的联系 一次函数与二次函…

2022第8届中国大学生程序设计竞赛CCPC桂林站, 签到题4题

文章目录A. LilyM.Youth FinaleC.Array ConcatenationE.Draw a triangleA. Lily A. Lily time limit per test1 second memory limit per test512 megabytes inputstandard input outputstandard output They serve the purpose of changing hydrogen into breathable oxygen,…

MySQL数据库 -- 库和表的操作

关于数据库方面&#xff0c;还是需要多多练习的&#xff0c;否则很多指令不容易记住&#xff0c;所以大家也要在自己的电脑上多写写&#xff0c;熟悉熟悉~ 目录 库的操作 创建数据库 操纵数据库 查看数据库 显示创建语句 修改数据库 数据库的删除 数据库备份和恢复 …

重学数据库基础

幸福找到我&#xff0c;幸福说&#xff1a;“瞧这个诗人&#xff0c;他比我本人还要幸福” 一、数据库相关概念 数据库 存储数据的仓库&#xff0c;数据是有组织的进行存储英文&#xff1a;DataBase&#xff0c;简称 DB 数据库管理系统 管理数据库的大型软件英文&#xff1a;Da…

CSI室内指纹定位——相关通信名词解释

目录 1、无线信道 2、时域与频域 3、信道频率响应&#xff08;Channel Frequency Response,CFR&#xff09; 4、信道冲激响应&#xff08;Channel Impulse Response, CIR&#xff09; 5、信道带宽 6、带宽 7、子载波 9、波长 10、频率 11、振幅 12、相位 13、相位差…

高数值孔径(NA)物镜的聚焦分析

1. 摘要 高NA物镜广泛用于光刻&#xff0c;显微等技术。因此&#xff0c;聚焦仿真中考虑光的矢量性质至关重要。VirtualLab可以非常便捷地对此类镜头进行光线追迹和场追迹分析。通过场追迹&#xff0c;可以清楚地观察由于矢量效应引起的聚焦光斑失对称现象。利用相机探测器和电…

第十四届蓝桥杯(Web应用开发)模拟赛1期-大学组

数据类型检测 请看这篇数据类型检测 渐变色背景生成器 html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name&…

java面试官:程序员,请你告诉我是谁把公司面试题泄露给你的?

前情提要&#xff1a; 面试官&#xff1a;你好&#xff01;请先做一下自我介绍&#xff01; 程序员&#xff1a;balabalabala... 前戏先过了.... 面试官&#xff1a;先介绍SpringCloud核心组件及其作用 程序员&#xff1a;SpringCloud由以下5个核心组件构成...另外&#x…

MySQL事务基本操作(方式1)

在观看本文前 你需要了解什么事事务 如果不太了解 可以先查看我的文章 MySQL事务基本概念 首先 我们这里有一张 staff 用户表 然后来一波 减岁交易大法 赵敏买个了 黄飞鸿十年时光 那么就是 先查询确认赵敏加上十岁不会过百 将赵敏年龄加十岁 确认黄飞鸿减去十岁不会小于零 然…

Java项目(三)-- SSM开发社交网站(9)--后台图书管理功能

后台图书管理功能 富文本编辑器wangEditor 基于javascript与css开发是Web富文本编辑器&#xff0c;轻量、简洁、易用、开源免费。 代码演示 我们在test.ftl中添加富文本编辑器演示下 <!DOCTYPE html> <html lang"en"> <head><meta charset&…

CMake中string的使用

CMake中的string命令用于字符串操作,其格式如下&#xff1a; Search and Replacestring(FIND <string> <substring> <out-var> [...])string(REPLACE <match-string> <replace-string> <out-var> <input>...)string(REGEX MATCH &l…

【数据库Redis】Redis五种基本数据结构以及三种配置方式——默认配置、运行配置、配置文件启动

文章目录一、初识Redis1.1 了解Redis1.2 Redis特性1.3 Redis使用场景Redis不适合场景1.4 用好Redis的建议1.5 正确安装并启动Redis在Linux上安装Redis在Windows上安装Redis配置、启动、操作、关闭Redis1)、启动Redis2)、Redis命令行客户端3)、停止Redis服务1.6 Redis重大版本一…

JVM(十四)—— StringTable

JVM&#xff08;十四&#xff09;—— StringTableString的基本特性String的内存分配字符串拼接intern方法常见面试题&#xff1a;到底创建了几个String对象String的基本特性 作为一名Java程序员肯定少不了和 String打交道&#xff0c;使用方法就是将字符串用一对""…