linux下的进程通信

进程通信

  • 进程为什么需要通信呢?
    • 进程通信的技术背景
    • 进程通信本质
  • 进程通信分类
    • 管道
      • 匿名管道pipe
      • 匿名管道原理
      • 管道特点
    • 命名管道
      • 创建命名管道
      • 命名管道原理
    • System V IPC
      • 管道与 System V的区别
      • 共享内存函数
        • ftok()
        • shmget()
      • shmat()
      • shmdt()
      • shmctl()
      • 删除共享内存
      • System V 代码演示
      • 管道和共享内存总结

进程为什么需要通信呢?

虽然进程都有相对独立性,但是还是需要进行互相通信的,比如说QQ发消息、一个进程想要给另一个进程发送数据、几个进程之间想共享一份数据等等,这些都需要进程进行通信。、

进程通信的技术背景

  • 进程是有独立性的。虚拟地址空间+页表 保证进程运行的独立性(进程内核数据结构+进程的代码和数据)
  • 由于独立性的原因,通信成本会比较高

进程通信本质

  • 进程通信的前提,首先需要让不同的进程看到同一块“内存”(特定的结构组织的)
  • 同一块“内存”,不属于任意进程,是在进程的共享代码段。

进程通信分类

管道

我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。

# | 命令就是连接两个进程之间的管道命令
# who命令输出两行数据,交给wc -l 进程,统计行数,输出。
who | wc -l 

在这里插入图片描述

匿名管道pipe

功能:创建一个无名管道
int pipe(int fd [2])
参数:
fd:文件描述符数组,其中fd[0],为读端;fd[1]为写端。
返回值:成功返回0,失败返回错误代码。

使用fork(创建父子进程进行通信)来验证管道的原理:
fork之后:子进程会创建一份新的PCB,并且会复制一份父进程的文件描述符数组;像打开文件的信息,管道等都只共享的一份。

下面这段代码实现了,子进程写入管道,父进程从管道读出这一功能:

#include<iostream>
#include<unistd.h>
//#include <string.h>
#include<cstring>
#include<cerrno>
#include<cassert>
#include<cstdlib>
#include<sys/wait.h>
#include<sys/types.h>
int main()
{//利用fork让两个不的进程看到同一份资源int pipefd[2] = {0};//1.创建匿名管道int n = pipe(pipefd);if(n<0)   //创建管道失败{std::cout<<"pipe error, "<<errno<<" : "<<strerror(errno)<<std::endl;return 1;}std::cout<<"pipefd[0]: "<<pipefd[0]<<std::endl;    //0读端std::cout<<"pipefd[1]: "<<pipefd[1]<<std::endl;   //1写端//2.  创建子进程pid_t id = fork();assert(id!=-1);if(id == 0)  //子进程{//3.关掉不需要得fd,实现让父进程读,子进程写得功能close(pipefd[0]);//4.开始通信 --结合某种场景int cnt = 0;while(true){char x = 'X';write(pipefd[1],&x,1);std::cout<<"Cnt: "<<cnt++<<std::endl;sleep(1);}close(pipefd[1]);exit(0);}//父进程//3. 关闭不需要的fd,让父进程读,子进程写close(pipefd[1]);//4.开始通信char buffer[1024];int cnt = 0;while(true){int n = read(pipefd[0], buffer, sizeof(buffer)-1);if(n>0){buffer[n]='\0';std::cout<<"我是父进程,child give me messages:"<<buffer<<std::endl;}else if(n==0){std::cout<<"我是父进程,读到了文件结尾"<<std::endl;break;}else{std::cout<<"我是父进程,读管道异常"<<std::endl;break;}sleep(2);if(cnt++>5) break;}close(pipefd[0]);int status = 0;waitpid(id, &status, 0);std::cout<<" sig: "<<(status &0x7f) << std::endl;sleep(20);return 0;
}

匿名管道原理

fork之后,子进程会复制一份父进程的文件描述符,指向父进程已经打开的文件资源等等。而用父进程创建匿名管道后,会在内存中,开辟一份空间,为父子进程之间提供通信,而父进程创建的这份资源会以fd的形式存在,父进程的打开文件表中。子进程复制后,子进程也能访问。
如下图:
在这里插入图片描述

管道特点

  1. 管道是用来进行具有血缘关系的进程进行进程见通信。—常用于父子通信
  2. 管道具有通过让进程间协同,提供了访问控制。
  • 写快,读慢,写满了不能再写了。
  • 写慢,读快,管道中没有数据的时候,读必须等待
  • 写关,读0,标识读到了文件结尾。
  • 读关,写继续写,OS终止写进程。
  1. 管道提供的是面向流式的通信服务,----面向字节流
  2. 管道是基于文件的,文件的生命周期是随进程的,管道的生命周期是随进程的
  3. 管道是单向通信,属于半双工通信的特殊情况,如果要进行全双工通信,需要创建两个管道。

命名管道

管道(匿名管道)应用的一个限制是只能在具有共同祖先的进程间通信。
但是,如果想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,这就是命名管道(一种特殊类型的文件)。

创建命名管道

  • 可以从命令行中创建管道:p开头即为管道文件

mkfifo [filename]

在这里插入图片描述

  • 可以使用函数创建命名管道:

int mkfifo(const char* filename, mode_t mode)
第一个参数是创建管道的路径
第二个参数是管道读写的权限,一般是0666

命名管道原理

首先命名管道是一个特殊的文件,因此它可以被打开到内存,但是不会将内存数据刷新到磁盘,该文件在系统中具有唯一路径,因此进程可以通过该路径找到管道文件,进行通信。
下面实现一段功能:父进程创建管道文件,然后从管道读消息,子进程从管道写消息。

//1.comm.hpp
#ifndef _COMM_H_
#define _COMM_H_#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<cstring>
#include "Log.hpp"
using namespace std;#define MODE 0666
#define SIZE 128string ipcPath = "./fifo.ipc";#endif//2. Log.hpp
#ifndef _LOG_H_
#define _LOG_H_#include<iostream>
#include<ctime>#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3const 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;
}#endif//3.client.cxx
#include "comm.hpp"int main()
{//1.获取管道文件int fd = open(ipcPath.c_str(), O_WRONLY);if(fd<0){perror("open");exit(1);}//2.ipc过程string buffer;while (true){cout<<"Please Enter Message Line :>";std::getline(std::cin, buffer);write(fd, buffer.c_str(), buffer.size());}//3.关闭文件close(fd);return 0;
}//4. server.cxx
#include "comm.hpp"
#include<sys/wait.h>static void getMessage(int fd)
{char buffer[SIZE];while(true){memset(buffer, '\0', sizeof(buffer));ssize_t s = read(fd, buffer, sizeof(buffer)-1);if(s > 0){cout<<"[ "<<getpid()<<" ]"<<"client sat>:"<<buffer<<endl;}else if(s == 0){//end of filecerr<<"[ "<<getpid()<<" ]"<<"read end of file, client quit, server quit too!"<<endl;break;}else{//read errorperror("read");break;}}
}int main()
{//1.创建管道文件if(mkfifo(ipcPath.c_str(), MODE)<0){perror("mkfifo");exit(1);}Log("管道文件创建成功", Debug)<<" step1"<<endl;//2.正常文件操作int fd = open(ipcPath.c_str(), O_RDONLY);if(fd<0){perror("open");exit(2);}Log("打开管道文件成功",Debug)<<" step 2"<<endl;int nums = 3;for(int i = 0; i < nums; i++){pid_t id = fork();if(id == 0){//3.编写正常的通信代码getMessage(fd);exit(1);}}for(int i = 0; i<nums;i++){waitpid(-1, nullptr, 0);}//4.关闭文件close(fd);Log("关闭管道文件成功",Debug)<<" step 3"<<endl;unlink(ipcPath.c_str());   //通信完毕,删除管道文件Log("删除管道文件成功", Debug)<<" step 4"<<endl;return 0;
}

System V IPC

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程地址空间,这些进程间的数据传递不再涉及到内核,换句话说,进程不再通过执行进入内核的系统调用来传递彼此的数据。
如下图是 System V的通信方式:
在这里插入图片描述
该方式直接在内存里建立一块空间,供进程通信(进程AB都会在自己的页表上,建立好虚拟地址到物理地址的映射),因此只需访问自己空间的地址,就可以实现进程间通信。

管道与 System V的区别

管道对应的公共资源是文件,而文件是OS内核对应的数据结构,需要操作系统维护,因此需要系统调用来实现。而System V只需要在物理内存上申请一块空间,而内存是用户空间里的内容,用户可以不经过系统调用直接进行访问,直接进行内存级的读写即可。
但是共享内存的提供者是OS,因为OS要管理共享内存,因为OS要先描述再组织->共享内存 = 共享内存快+对应的共享内存的内核数据结构。 申请需要OS管理,但是申请完了之后,用户可以直接访问。

共享内存函数

ftok()

第0步
功能:生成一个唯一的key,供shmget使用生成共享内存段。
int ftok(const char* pathname, int proj_id);
参数:
pathname:必须是存在的、可访问的文件路径
proj_id:至少是8bit的非0数字(自己给定)
返回值:若生成成功,返回唯一的key值。失败返回-1

shmget()

1 .第一步
功能:用来创建共享内存
int shmget(key_t key, size_t size, int shmflg);
参数:
key:这个共享内存段的名字(唯一id)
size:共享内存的大小
shmfg:由九个标志权限构成,他们的用法和创建文件open使用的mode模式标志一样
返回值:成功返回一个非负整数(该段共享内存段的标识码,类似于fd);失败返回-1

  • 说明shmfg参数的具体解释:
  • IPC_CREAT:创建共享内存,如果底层已经存在,获取已存在的id,并且返回;如果不存在,创建共享内存,并返回
  • IPC_EXCL: 单独使用无意义,和上面一起使用:若底层不存在,创建它并返回;若底层存在,出错返回。–>若返回成功的一定是一个全新的shm

shmat()

2.第二步
功能:将共享内存段连接到进程地址空间(在页表上生成映射)
void shmat(int shmid, const void shmaddr, int shmflg)
参数:
shmid:共享内存标识
shmaddr:指定连接的地址(一般填nullptr)
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存的第一个地址;失败返回-1

  • 说明shmflg:
  • shmaddr:为NULL,核心自动选择一个地址
  • shmaddr:不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址
  • shmadd:不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
  • shmflg=SHM_RDONLY,标识该连接操作用作只读共享内存

shmdt()

第3步
功能:将共享内存点与当前进程脱离
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

  • 说明:cmd
  • IPC_STAT:把shmid_ds结构中的数据结构设置为共享内存的关联值
  • IPC_SET:在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中的值
  • IPC_RMID:删除共享内存段

删除共享内存

首先这种内存共享方式,当我们程序结束后,如果代码不主动删除,那么该内存不会被释放!
我们可以使用代码删除、也可以手动删除,手动删除使用如下命令即可。
注意:key只有在创建的时候才有,其他时候访问共享内存都是用shmid

在这里插入图片描述

System V 代码演示

该功能为:Server创建共享内存,并删除。Cilentt使用该共享内存传输数据。


// Log.hpp
#ifndef _LOG_H_
#define _LOG_H_#include<iostream>
#include<ctime>#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3const 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;
}#endif//comm.hpp
#pragma once#include<iostream>
#include<cstdio>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<cassert>
#include"Log.hpp"using namespace std; //不推荐#define PATH_NAME "/home/xty"
#define PROJ_ID 0x66
#define SHM_SIZE 4096    //共享内存大小,最好是4096的整数倍//shmClient.cc
#include "comm.hpp"int main()
{key_t k = ftok(PATH_NAME, PROJ_ID);if(k<0){Log("creat key failed", Error)<<" client key : "<< k <<endl;exit(-1);}Log("creat key done", Debug)<<" client key : "<< k <<endl;//获取共享内存int shmid = shmget(k, SHM_SIZE, 0);if(shmid < 0){Log("create shm failed", Error) <<" client key : "<< k <<endl;exit(2);}Log(" create shm success", Debug) <<" client key : " << k <<endl;sleep(10);char* shmaddr = (char *)shmat(shmid, nullptr, 0);if(shmaddr == nullptr){Log(" attach shm failed", Error) <<" client key : "<< k<<endl;exit(3);}Log(" attach shm success", Debug) <<" client key : "<< k<<endl;sleep(10);// 使用//去关联int n = shmdt(shmaddr);assert(n!=-1);Log("detach shm success", Debug) << "client key : "<< k <<endl;sleep(10);// client 要不要chmctl删除呢?不需要return 0;
}//shmServer.cc
#include "comm.hpp"string TransToHex(key_t k)
{char buffer[32];snprintf(buffer, sizeof(buffer), "0x%x", k);return buffer;
}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;sleep(10);//3.将指定的共享内存,挂接到自己的地址空间char * shmaddr = (char *)shmat(shmid, nullptr, 0);Log("attach shm done", Debug) << " shmid : " << shmid <<endl;sleep(10);//通信的逻辑...//4.将指定的共享内存,从自己的地址空间中 去 关联int n = shmdt(shmaddr);assert(n!=-1);(void)n;Log("deatch shm done", Debug) << " shmid : " << shmid <<endl;sleep(10);//5. 删除共享内存, IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存n = shmctl(shmid, IPC_RMID, nullptr);assert( n!=-1);(void)n;Log("delete shm done", Debug) << " shmid : " <<shmid<<endl;return 0;
}//makefile
.PHONY:allall:shmClient shmServershmClient:shmClient.ccg++ -o $@ $^ -std=c++11
shmServer:shmServer.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f shmClient shmServer

管道和共享内存总结

管道通信的过程:由键盘->自己定义的缓冲区->进程A->write给内核缓冲区->内核缓冲区给管道文件->管道给内核缓冲区->read读到进程B处->自己定义的缓冲区->打印到屏幕。

共享内存通信:由键盘->自己定义的缓冲区->进程A->直接写入共享内存->进程B读贡献内存->到自己定义的缓冲区->屏幕。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux系统(ubuntu)调用科大讯飞SDK实现语音识别

1. 科大讯飞官网 登录注册实名制 2. 点击控制台&#xff0c;创建应用 点击左侧的语音听写&#xff0c;右边下滑选择Linux&#xff0c;点击下载 选择Linux平台&#xff0c;普通版本&#xff0c;语音听写&#xff0c;SDK下载 此时将得到一个压缩包&#xff0c;选择的功能不…

【漏洞复现】泛微OA E-Cology portalTsLogin文件读取漏洞

漏洞描述&#xff1a; 泛微E-Cology是一款面向中大型组织的数字化办公产品&#xff0c;它基于全新的设计理念和管理思想&#xff0c;旨在为中大型组织创建一个全新的高效协同办公环境。泛微OA E-Cology portalTsLogin存在任意文件读取漏洞&#xff0c;允许未经授权的用户读取服…

【经验分享】图片自适应窗口大小css;CSS实现背景图片全屏铺满自适应的方式

目录 设置背景颜色和边距 设置背景图片 调整背景图片尺寸和位置 完整代码 使用效果如下&#xff08;展示&#xff09; 网页版图片效果展示 手机版图片效果展示 如何使用 CSS 创建网页背景效果 在网页设计中&#xff0c;背景是一个重要的视觉元素&#xff0c;它可以为网…

SEO之为什么研究关键词(一)

初创企业需要建站的朋友看这篇文章&#xff0c;谢谢支持&#xff1a; 我给不会敲代码又想搭建网站的人建议 新手上云 初做网站的人很容易犯的最大错误之一是&#xff0c;脑袋一拍就贸然进入某个领域&#xff0c;跳过竞争研究&#xff0c;没规划好目标关键词就开始做网站。这样做…

Jmeter中线程组介绍

1.线程数的意义 Jmeter采用了线程来模拟用户&#xff0c;即1个线程代表1个用户&#xff0c;线程可以简单理解为计算机处理任务时的一个具体执行人。 一个任务可以由多个人&#xff08;线程&#xff09;共同完成&#xff0c;也可以由一个人&#xff08;线程&#xff09;来完成&a…

美股市场恒生指数冲刺19000点关口 地产股大涨

查查配5月10日电(中新财经记者 谢艺观)5月10日,港股现强势行情,恒生指数盘中一度冲至18993.28点,距离19000点关口仅一步之遥。 美港通证券以其专业的服务和较低的管理费用在市场中受到不少关注。该平台提供了实盘交易、止盈止损、仓位控制等功能,旨在为投资者提供更为全面的投…

C# 统计代码运行时长

using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms;namespace Sci {/// <summary>/// 统计代码…

如何在计算机上安装两个系统并引导?这里有详细步骤

序言 大多数计算机附带一个操作系统&#xff0c;但你可以在一台电脑上安装多个操作系统。安装两个操作系统&#xff0c;并在启动时在它们之间进行选择。 谷歌和微软终止了英特尔的双启动Windows和Android PC计划&#xff0c;但你可以在安装Windows 7的同时安装Windows 8.1&am…

673. 最长递增子序列的个数(Leetcode)

文章目录 前言一、题目描述二、解题步骤1.小demo介绍2.动态规划1.状态表示2.状态转移方程3.初始化4.填表顺序5.返回值 三、代码编写总结 前言 在本篇文章中&#xff0c;我们将会讲到leetcode中673. 最长递增子序列的个数&#xff0c;我们将会用动态规划方式解决这道问题&#…

Star-CCM+绘制网格-全局网格定义(网格类型选择、薄体网格、网格重置)

前言 绘制网格是有限体积法仿真中必不可少的环节。目前Star-CCM+新版本(2304版)导入面网格只可以导入到部件中。网格类型也只能在操作中完成。零部件导入部件后,选中参与计算的全部部件→右键选择“将部件分配给区域”。此处需要注意的是,只有分配给区域后的部件才能进行网…

HNCTF-PWN

1.ez_pwn 直接看危险函数&#xff0c;不能溢出&#xff0c;只能覆盖ebp。 后面紧接的又是leave,ret 很明显是栈迁移&#xff0c;通过printf打印出ebp&#xff0c;通过偏移计算出栈地址。 通过gdb调试&#xff0c;偏移是0x38 以下是payload&#xff1a; from pwn import * #i…

HR人才测评,表达能力与岗位胜任力素质测评

什么是表达能力&#xff1f; 表达能力指的就是在语言能力基础之上发展形成的一种语用能力&#xff0c;可以结合自己所掌握的语言来实现交际的目的&#xff0c;能正确且灵活的把语言材料组合成为语言并且表达出想要表达的内容。 在百度百科中有如此定义&#xff0c;表达能力…