Linux进程间通信

简介: Linux进程间通信

因为进程间具有独立性,你们想用进行进程间通信,难度还是比较大的。

进程间通信的本质就是让不同的进程看到同一份资源

为什么要进行进程间通信——交互数据、控制、通知等目标

进程间通信的技术背景

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

进程间通信的本质理解

  1. 进程间通信的前提是让不同的进程看到同一块“内存”
  2. 所谓的同一块“内存”,不隶属任何一个进程,而是更强调共享

对于Linux原生提供的管道是匿名管道

对于管道,有入口,有出口,有一个入口,有一个出口,管道都是单向传输内容的,管道中传输的资源就是数据。管道只支持单向通信,这是设计的原因


  • 在同步的提现中,若管道所有写段关闭,则从管道中读取完所有数据后,继续read会返回0,不再阻塞;若所有读端关闭,则继续write写入会触发异常导致进程退出

创建管道的过程:

  1. 分别以读写的形式打开同一个内存
  2. fork创建子进程
  3. 双方进程各自关闭自己不需要的文件描述符

匿名管道

pipe的用法

#include <unistd.h>

int pipe(int fd[2]);

该功能是创建一个无名管道

对于参数来说:fd文件描述符,fd[0]表示读端

对于返回值来说,成功返回0,失败返回错误代码

一个简单的使用例子:

#include <iostream>
#include <cassert>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;

int main()
{
    int pipearry[2]={0};
    int s=pipe(pipearry);

    assert(s==0);
    (void)s;
    //因为12行在release中没有用
    //为了防止在运行的时候出现警告信息用了第13行的代码
#ifdef DEBUGE
    cout<<"pipearry[0]"<<pipearry[0]<<endl;
    cout<<"pipearry[1]"<<pipearry[1]<<endl;

#endif
    pid_t pid=fork();
    if(pid==0)//child,关闭写端——pipearry[1]
    {
        //接受消息
        close(pipearry[1]);
        char buffer[1000];
        while(true)
        {
            ssize_t  ret=read(pipearry[0],buffer,sizeof(buffer));
            if(ret>0){
                buffer[ret]=0;
                cout<<"接收成功:接收的数据为->"<<buffer<<endl;
            }
            else if(ret==0){
                cout<<"父进程退出,子进程马上退出"<<endl;
                break;
            }
            else{
                cout<<"异常错误"<<endl;
                break;
            }
        }
        exit(0);

    }
    //father,关闭读端——pipearry[0]
    //写信息
    close(pipearry[0]);
    char buffer[]="我是父进程";
    int count=0;
    while(true){
        ssize_t ret = write(pipearry[1],buffer,sizeof(buffer));
        cout<<buffer<<':'<<count++<< "子进程pid"<<pid<< "父进程pid"<<getpid()<<endl;
        sleep(1);
        if(count==5){
            cout<<"父进程退出"<<endl;
            break;
        }
    }
    close(pipearry[1]);
    pid_t n= waitpid(pid,nullptr,0);
    cout<<"pid:"<<pid<<"n:"<<n<<endl;

    return 0;
}

总结管道的特点

  1. 管道是用来进行血缘关系的进程进行进程间的通信——常用于父子通信
  2. 管道具有通过让进程间协同,提供了访问控制!
  3. 写快,读慢,写满就不能在写了
  4. 写慢,读快,管道没有数据的时候,读必须等待
  5. 写关,读0,标识读到了文件结尾
  6. 读关,写继续写,os将中止写进程
  1. 管道提供的是面向流式的通信服务——面向字节流——协议
  2. 管道是基于文件的,文件的生命周期是随进程的,管道的生命周期是随进程的
  3. 管道是单向通信的,就是半双工通信是一种特殊情况

命名管道

命名管道说明该管道是有名字的,它和匿名管道的区别就是,匿名管道是内存基本的,在磁盘上没有存储,而命名管道在磁盘是存在的,但是里面是没有内容的。

这样双方进程就可以看见同一份资源了

管道的本质是内核中的缓冲区,命名管道文件是缓冲区的标识

管道的生命周期随进程,本质是内核中的缓冲区,命名管道文件只是标识,用于让多个进程找到同一块缓冲区,删除后,之前已经打开管道的进程依然可以通信。

创建一个命名管道

命令行创建

mkfifo 文件名

创建:

发送:

接受:

上面就是在命令行中完成的操作

在程序中创建

我们用下面这个函数

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);
/*
pathname是路径名
mode是权限
返回值:成功返回0,错误返回-1、并设置错误码
*/

下面用命名管道实现server&client通信

Makefile

自动化编译

.PHONY:all
all:server client

server:server.cpp
  g++ -o $@ $^ -std=c++11
client:client.cpp
  g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
  rm -f server client

头文件包含相应的头:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

#define FILEPATH "./namepipe"
#define MODE 0666
#define SIZE 512

server.cpp

创建namepipe匿名管道

接收来自用户端发来的消息

#include "common.hpp"

void getmessage(int fd)
{
    char buffer[SIZE]={0};
    while(true){
        ssize_t ret = read(fd,buffer,sizeof buffer);
        if(ret>0){
            std::cout<<"读取成功:pid为:"<<getpid()<<"内容为"<<buffer<<std::endl;
        }
        else if(ret==0){
            std::cout<<"读取完毕"<<"进程pid:"<<getpid()<<"退出"<<std::endl;
            break;
        }
        else{
            perror("读取错误");
            exit(1);
        }
    }
}

int main()
{
    if(mkfifo(FILEPATH,MODE)<0){
        perror("创建命名管道失败");
        exit(1);
    }
    //创建成功,打开文件
    int fd=open(FILEPATH,O_RDONLY);
    if (fd < 0)
    {
        perror("open");
        exit(1);
    }
    //创建3个子进程
    int n=3;
    for(int i=0;i<n;i++){
        pid_t pid=fork();
        if(pid==0){
            // 接收客户端发送的消息
            getmessage(fd);
            exit(3);
        }
    }
    //等待子进程退出
    for(int i=0;i<n;i++){
        waitpid(-1,nullptr,0);
    }
    close(fd);
    //删除命名管道
    std::cout<<"删除命名管道"<<std::endl;
    unlink(FILEPATH);
    return 0;
}

client.cpp

向服务器端发送消息

#include "common.hpp"


int main()
{
    int fd=open(FILEPATH,O_WRONLY);
    if(fd<0){
        perror("open");
        exit(1);
    }
    char buffer[SIZE]={0};
    while(true){
        std::cout<<"请输入你发送的信息";
        std::cin>>buffer;
        ssize_t ret = write(fd,buffer,sizeof buffer);
        if(ret<0){
            std::cout<<"写入数据错误"<<std::endl;
            break;
        }
    }
    close(fd);
    return 0;
}

删除文件

下面这个删除文件本质上是删除的该文件的链接个数

#include <unistd.h>

int unlink(const char *pathname);

system V共享内存

共享内存区是最快的IPC(进程间通信)形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核 ,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。共享内存是在用户空间中,可以直接像使用数组一样使用该空间


共享内存在哪个区域呢?——堆栈相对生长的区域就是共享内存的区域

既然是共享内存,需要os进行管理,那么就应该有它的数据结构


共享内存没有进行同步与互斥(共享内存缺乏访问控制),所以可能会出现,写入的数据的时候,还没有读取的时候就进行再一次的写入,就会读取不到原来的数据;或者是没有写完就进行读取,会导致读取的数据不完整


共享内存的数据结构为:

共享内存的创建和使用

先介绍几个函数

#include <sys/shm.h>
//用来创建共享内存
int shmget(key_t key, size_t size, int shmflg);
/*
key:共享内存段的名字,key_t 是int类型
size:共享内存大小

shmflg:用法和创建mode模式的标志一样
IPC_CREAT——>如果存在获取并返回,如果不存在,创建并返回
IPC_CREAT | IPC_EXCL——>如果底层不存在,创建并返回;如果存在,出错并返回
还可以再加权限。

返回值:成功返回非负整数,表示该共享内存的标识码,失败返回-1
*/
#include <sys/shm.h>
//将共享内存段连接到进程地址空间
void *shmat(int shmid, const void *shmaddr, int shmflg);
/*
shmid:共享内存的标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND(0)和SHM_RDONLY(非0)
返回值:
成功返回一个指针,指向共享内存第一个节;失败返回-1

参数的使用:
shmaddr为null,核心自动选择一个地址
shmaddr不为null且shmflg无SHM_RND标记,则以shmaddr为连接地址
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - 
(shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存。
*/
#include <sys/shm.h>
//将共享内存段与当前进程脱离
int shmdt(const void *shmaddr);
/*
shmaddr:由shmat所返回的指针
返回值:成功返回0,失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
*/
#include <sys/shm.h>
//控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
/*
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作——>IPC_STAT,IPC_SET,IPC_RMID
IPC_RMID表示的是删除共享内存段
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
就是共享内存数据结构的指针
返回值:成功返回0;失败返回-1
*/

server&client演示

我们想要client和server打开同一个共享内存,那么就要保证key一致,除了我们手动写死一个key的方式外,我们还可以用下面这个函数生成。

#include <sys/ipc.h>
//将路径名和项目标识符转换成system V的key
key_t ftok(const char *pathname, int proj_id);
/*  
pathname:路径名
proj_id:项目标识符
返回值:成功返回一个key_t值,失败时返回-1.
*/

Makefile

.PHONY:all
all:shmserver shmclient

shmserver:shmserver.cpp
  g++ -o $@ $^ -std=c++11
shmclient:shmclient.cpp
  g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
  rm -f shmserver shmclient

common.hpp

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


#define FILEPATH "/home/lighthouse/studycode/"
#define FILENAME "./controlpipe"
// #define PROJID 0x888
#define PROJID 0x66
#define MODE 0666
#define SIZE 4096

using namespace std;
//创建命名管道,用于控制共享内存
class Init
{
public:
    Init()
    {
        int ret = mkfifo(FILENAME,MODE);
        if(ret<0){
            perror("命名管道");
            exit(-1);
        }
        cout<<"创建命名管道成功,用来对共享内存进行控制"<<endl;

    }
    ~Init()
    {
        unlink(FILENAME);
        cout<<"删除命名管道"<<endl;
    }
};
#define READ O_RDONLY
#define WRITE O_WRONLY
int OpenGFile(int flag)
{
    int fd = open(FILENAME, flag);
    if (fd < 0)
    {
        perror("open error");
        exit(-1);
    }
    return fd;
}
void Wait(int fd)
{
    cout<<"等待读取中……"<<endl;
    int datatemp=0;
    read(fd,&datatemp,sizeof(int));
    
}
void Wakeup(int fd)
{
    cout<<"读取中……"<<endl;
    int datatemp=0;
    write(fd,&datatemp,sizeof(int));
    
}
void Close(int fd)
{
    close(fd);
    cout<<"文件关闭成功"<<endl;
}

shmserver.cpp

#include "common.hpp"

void getmessage(int fd)
{
    char buffer[SIZE]={0};
    while(true){
        ssize_t ret = read(fd,buffer,sizeof buffer);
        if(ret>0){
            std::cout<<"读取成功:pid为:"<<getpid()<<"内容为"<<buffer<<std::endl;
        }
        else if(ret==0){
            std::cout<<"读取完毕"<<"进程pid:"<<getpid()<<"退出"<<std::endl;
            break;
        }
        else{
            perror("读取错误");
            exit(1);
        }
    }
}
Init init;

int main()
{
    //得到共享内存段的名字
    key_t key = ftok(FILEPATH,PROJID);
    if(key<0){
        perror("key");
        exit(1);
    }
    std::cout<<"得到共享内存段的名字key:"<<key<<std::endl;

    //创建一个全新的共享内存
    //MODE是添加的权限
    int shmid = shmget(key,SIZE,IPC_CREAT|IPC_EXCL|MODE);
    if(shmid<0){
        perror("create share memory");
        exit(2);
    }
    std::cout<<"创建一个全新的共享内存shmid:"<<shmid<<std::endl;
    //链接进程地址
    char* shmaddr = (char*)shmat(shmid,NULL,SHM_RND);
    if(shmaddr<0){
        perror("shmaddr error");
        exit(3);
    }
    std::cout<<"链接进程地址shmaddr:"<<(void*)shmaddr<<std::endl;
    
    //通信代码,接收信息,有信息才进行读取
    int fd = OpenGFile(READ);
    while(true)
    {
        Wait(fd);
        cout<<shmaddr<<endl;
        if(strcmp(shmaddr,"quit")==0)
        break;
    }
    Close(fd);
    // for(int i=0;i<5;i++){
    //     cout<<shmaddr<<endl;
    //     sleep(3);
    // }

    //断开与进程的连接
    int ret = shmdt(shmaddr);
    if(ret<0){
        perror("shmdt error");
        exit(4);
    }
    std::cout<<"断开与进程的连接"<<std::endl;

    //删除共享内存空间
    ret = shmctl(shmid,IPC_RMID,NULL);
    if (ret < 0)
    {
        perror("shmctl error");
        exit(5);
    }
    std::cout<<"删除共享内存空间"<<std::endl;
    return 0;
}

客户端不需要删除共享内存

shmclient.cpp

#include "common.hpp"


int main()
{
    key_t key = ftok(FILEPATH, PROJID);
    if (key < 0)
    {
        perror("key");
        exit(1);
    }
    std::cout << "得到共享内存段的名字key:" << key << std::endl;

    //不需要创建,获得共享内存的标识符即可
    int shmid = shmget(key, SIZE, 0);//0只是为了获得shmid
    if (shmid < 0)
    {
        perror("create share memory");
        exit(2);
    }
    std::cout << "得到共享内存shmid:" << shmid << std::endl;

    // 链接进程地址
    char* shmaddr = (char*)shmat(shmid, NULL, SHM_RND);
    if (shmaddr < 0)
    {
        perror("shmaddr error");
        exit(3);
    }
    std::cout << "链接进程地址shmaddr:" << (void*)shmaddr << std::endl;
    // 通信代码
    int fd = OpenGFile(WRITE);

    while(true)
    {

        cin>>shmaddr;
        Wakeup(fd);
        if (strcmp(shmaddr, "quit") == 0)
            break;
    }
    Close(fd);

    // for(int i=0;i<5;i++){
    //     cin>>shmaddr;
    // }
    // 断开与进程的连接
    int ret = shmdt(shmaddr);
    if (ret < 0)
    {
        perror("shmdt error");
        exit(4);
    }
    std::cout << "断开与进程的连接" << std::endl;

    //客户端不需要删除共享内存

    return 0;
}

查看共享内存的信息。

ipcs -m

删除共享内存资源

ipcrm -m shmid编号——>这是通过命令行的形式来删除的查看共享内存的信息。

ipcs -m

删除共享内存资源

ipcrm -m shmid编号——>这是通过命令行的形式来删除的

相关文章
|
1月前
|
资源调度 Linux 调度
Linux c/c++之进程基础
这篇文章主要介绍了Linux下C/C++进程的基本概念、组成、模式、运行和状态,以及如何使用系统调用创建和管理进程。
38 0
|
21天前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
82 4
linux进程管理万字详解!!!
|
12天前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
53 8
|
9天前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
|
20天前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
58 4
|
21天前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
23天前
|
消息中间件 存储 Linux
|
29天前
|
运维 Linux
Linux查找占用的端口,并杀死进程的简单方法
通过上述步骤和命令,您能够迅速识别并根据实际情况管理Linux系统中占用特定端口的进程。为了获得更全面的服务器管理技巧和解决方案,提供了丰富的资源和专业服务,是您提升运维技能的理想选择。
39 1
|
1月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
【10月更文挑战第9天】本文将深入浅出地介绍Linux系统中的进程管理机制,包括进程的概念、状态、调度以及如何在Linux环境下进行进程控制。我们将通过直观的语言和生动的比喻,让读者轻松掌握这一核心概念。文章不仅适合初学者构建基础,也能帮助有经验的用户加深对进程管理的理解。
26 1
|
1月前
|
消息中间件 Linux API
Linux c/c++之IPC进程间通信
这篇文章详细介绍了Linux下C/C++进程间通信(IPC)的三种主要技术:共享内存、消息队列和信号量,包括它们的编程模型、API函数原型、优势与缺点,并通过示例代码展示了它们的创建、使用和管理方法。
32 0
Linux c/c++之IPC进程间通信
下一篇
无影云桌面