【Linux进程间通信】深入探索:Linux下的命名管道与System V共享内存

📝个人主页🌹:Eternity._
⏩收录专栏⏪:Linux “ 登神长阶 ”
🌹🌹期待您的关注 🌹🌹

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述


🔍前言:在Linux操作系统中,进程间通信(IPC)是一个至关重要的概念,它允许不同的进程之间进行数据交换和同步。随着现代操作系统的日益复杂,进程间通信的重要性也日益凸显。在众多IPC机制中,命名管道和System V共享内存无疑是两种最为常见且强大的工具

命名管道,又称为FIFO(First In First Out)管道,是一种在进程间传输数据的管道机制。与无名管道相比,命名管道具有更高的灵活性,因为它允许不相关的进程进行通信,而不仅仅是父子进程。这种特性使得命名管道在多种应用场景中都非常有用

另一方面,System V共享内存则是一种高效的内存共享机制。它允许多个进程共享同一块内存区域,从而可以方便地进行数据的读写操作。这种机制在需要高效数据交换的场景中特别有用,例如数据库系统、实时系统等

本文旨在深入探讨Linux进程间通信中的命名管道和System V共享内存。我们将从这两种机制的基本原理出发,逐步介绍它们的实现方式、应用场景以及相关的注意事项。通过本文的学习,你将能够深入理解Linux进程间通信的核心概念,并掌握命名管道和System V共享内存的使用方法


📒1. 命名管道

命名管道(Named Pipe),又称FIFO(First In First Out,先进先出)管道,是一种特殊类型的文件,存在于文件系统中。与匿名管道不同,命名管道可以在不相关的进程间进行数据传输,它提供了进程间通信(IPC)的一种机制


创建命名管道:

函数:int mkfifo(const char *filename,mode_t mode);

在这里插入图片描述


匿名管道与命名管道的区别

  • 匿名管道由pipe函数创建并打开
  • 命名管道由mkfifo函数创建,打开用open
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义

命名管道的打开规则

  • 如果当前打开操作是为读而打开FIFO时
  • O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
  • O_NONBLOCK enable:立刻返回成功
  • 如果当前打开操作是为写而打开FIFO时
  • O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
  • O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

📙2. 命名管道实现server&client通信

在这里插入图片描述


comm.hpp:

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

#define FILENAME ".fifo"

Makefile:

.PHONY:all
all:server client

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

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

.PHONY:clean
clean:
	rm -rf client

server:

#include "comm.hpp"

using namespace std;

bool MakeFifo()
{
    int n = mkfifo(FILENAME, 0666);
    if(n < 0)
    {
    	// 创建失败
        cerr << "errno: " << errno << ", strerrno: " << strerror(errno) << endl;
        return false;
    }

    cout << "mkfifo success ... ... read" << endl;
    return true;
}

int main()
{
Start:
	// 以读取的方式打开文件
    int rfd = open(FILENAME, O_RDONLY);
    if(rfd < 0)
    {
        cerr << "errno: " << errno << ", strerrno: " << strerror(errno) << endl;
        // 如果没有创建,则创建命名管道后重新打开
        if(MakeFifo()) goto Start;
        else
        {
            return 1;
        }

    }

    cout << "open fifo success" << endl;
    
	// To do
    char buffer[1024];
    while(true)
    {
    	// 读取
        ssize_t s = read(rfd, buffer, sizeof(buffer)-1);
        if(s > 0)
        {
            buffer[s] = 0; // 将最后一位变成'\0'
            cout << "Client say# " << buffer << endl;
        }
    }

    close(rfd);

    return 0;
}

client:

#include "comm.hpp"

using namespace std;

int main()
{
	// 以写入的方式打开文件
    int wfd = open(FILENAME, O_WRONLY);
    if(wfd < 0)
    {
        cerr << "errno: " << errno << ", strerrno: " << strerror(errno) << endl;
        return 1;
    }

    cout << "mkfifo success ... ... write" << endl;

    string msg;
    while(true)
    {
        cout << "Please Enter# ";
        // 从标准输入中读取数据
        getline(cin, msg);
		
		//写入数据
        ssize_t s = write(wfd, msg.c_str(), msg.size());
        if(s < 0)
        {
            cerr << "errno: " << errno << ", strerrno: " << strerror(errno) << endl;
            break;
        }
    }

    close(wfd);

    return 0;
}

在这里插入图片描述

命名管道实现server&client通信 代码


📚3. system V共享内存

System V共享内存的原理是由操作系统内核申请出一块物理内存空间,并将该空间的使用权移交给多个进程。这些进程通过各自的页表将这块物理内存映射到自己的进程地址空间中,从而实现对同一块内存区域的访问。这种方式避免了进程间通过内核进行数据传递的开销,提高了通信效率

共享内存示意图:
在这里插入图片描述

  • 共享内存的通信方式,不会提供同步机制,共享内存是直接裸露给所有的使用者的,一定要注意共享内存的使用安全问题
  • 共享内存是所有进程间通信,速度最快的
  • 共享内存可以提供较大的空间

📜4. 共享内存函数

ftok函数:

在这里插入图片描述

功能:用于生成唯一键值(key)的函数

参数说明:

  • pathname:指向文件路径的指针,这个文件通常是项目中的一个已知文件。这个路径需要指向一个实际存在的文件或目录,以便ftok函数能够获取其inode编号和设备号
  • proj_id:项目标识符,通常为一个字符或整数(但只使用其低8位)。它用于进一步区分同一文件路径下的不同IPC资源

shmget函数:

在这里插入图片描述

功能:在System V共享内存机制中创建一个新的共享内存段或获取一个已存在的共享内存段的系统调用

参数说明:

  • key:用于标识共享内存段的键值,通常由ftok函数生成。这个键值在系统中必须是唯一的,以确保不同进程能够访问到相同的共享内存段。
  • size:指定共享内存段的大小(以字节为单位)。这个大小通常是4096的倍数,因为System V共享内存是以页面为单位进行分配的。
  • shmflg:用于控制共享内存段的创建和访问权限的标志位。常用的标志位包括IPC_CREAT(如果共享内存段不存在则创建它)、IPC_EXCL(与IPC_CREAT一起使用时,如果共享内存段已存在则返回错误)、以及一系列的权限标志(如0666表示所有用户都有读写权限)
  • IPC_CREAT | IPC_EXCL:当这两个标志位一起使用时,系统调用将尝试创建一个新的IPC对象。如果对象已经存在,则调用失败并返回错误。这种组合通常用于确保创建的IPC对象是唯一的
  • 仅IPC_CREAT:当只使用IPC_CREAT标志位时,如果指定的IPC对象不存在,则创建一个新的对象;如果对象已经存在,则返回该对象的标识符。这种用法允许进程访问已存在的IPC对象
  • IPC_EXCL不能单独使用

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

  • shmid:应用这个共享内存的时候,我们使用shmid来进行操作共享内存
  • key:不要在应用层使用,只用来在内核中标识shm的唯一性

shmat函数:

在这里插入图片描述

功能:用于将共享内存段连接到进程的地址空间,使得进程可以通过指针来访问共享内存

参数说明:

  • shmid:由shmget函数返回的共享内存段标识符
  • shmaddr:希望连接的共享内存段在进程地址空间中的起始地址。通常设置为NULL
  • shmflg:控制连接行为的标志位。常用的标志位有0

shmdt函数:

在这里插入图片描述

功能:用于将共享内存段从进程的地址空间中分离(或断开连接)


shmctl函数:

在这里插入图片描述

功能:用于对共享内存段进行各种控制操作的接口,比如删除共享内存段、获取或设置其状态等

参数说明:

  • shmid:共享内存段的标识符,由shmget函数返回
  • cmd:控制命令,用于指定要执行的操作
  • buf:指向shmid_ds结构体的指针,用于存储或接收共享内存段的状态信息

在这里插入图片描述


📝5. 共享内存实现server&client通信

在这里插入图片描述


Makefile:

.PHONY:all
all:server client

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

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

.PHONY:clean
clean:
	rm -rf client server fifo

comm.hpp:

#pragma once

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

using namespace std;

// ftok所需指定的变量
const string pathname = "/home/pxt/109/linux/test_9_21";
const int proj_id = 0x11223344;

// 命名管道文件名
const string FileName = "fifo";

const int size = 4096;

// 获取key
key_t GetKey()
{
    key_t key = ftok(pathname.c_str(), proj_id);
    if(key < 0)
    {
        cerr << "errno: " << errno << ", strerrno: " << strerror(errno) << endl;
        exit(1);
    }

    return key;
}

// 将 key 转化为十六进制
string ToHex(int id)
{
    char buffer[1024];
    snprintf(buffer, sizeof(buffer), "0x%x", id);
    return buffer;
}

// 创建共享内存
int CreateShmHelper(key_t key, int flag)
{
    int shmid = shmget(key, size, flag);  

    if(shmid < 0)
    {
        cerr << "errno: " << errno << ", strerrno: " << strerror(errno) << endl;
        exit(1);
    }
    return shmid;
}

// 创建共享内存
int CreateShm(key_t key)
{
    return CreateShmHelper(key, IPC_CREAT|IPC_EXCL|0644);
}

// 获取共享内存
int GetShm(key_t key)
{
    return CreateShmHelper(key, IPC_CREAT);
}

// 创建命名管道
bool MakeFifo()
{
    int n = mkfifo(FileName.c_str(), 0666);
    if(n < 0)
    {
        cerr << "errno: " << errno << ", strerrno: " << strerror(errno) << endl;
        return false;
    }

    cout << "mkfifo success ... ... read" << endl;
    return true;
}

server:

#include "comm.hpp"

using namespace std;

// 定义一个类来管理共享内存
class Init
{
public:
    Init()
    {
    	// 用命名管道来起到同步的作用
        bool r = MakeFifo();
        if(!r)
        {
            return;
        }

        key_t key = GetKey();
        cout << "key: " << ToHex(key) << endl;

        int shmid = CreateShm(key);
        cout << "shmid: " << shmid << endl;

        cout << "开始将shm映射到进程的地址空间中" << endl;
        
        s = (char *)shmat(shmid, nullptr, 0);
        
        fd = open(FileName.c_str(), O_RDONLY);
    }

    ~Init()
    {
        shmdt(s);
        cout << "开始将shm从进程的地址空间中移除" << endl;
        
        shmctl(shmid, IPC_RMID, nullptr);
        cout << "开始将shm从OS中移除" << endl;
        
        close(fd);
        // unlink用来删除文件
        unlink(FileName.c_str());
    }
public:
    int shmid;
    int fd;
    char *s;
};

int main()
{
    Init init;

    while(true)
    {
        int code = 0;
        ssize_t n = read(init.fd, &code, sizeof(code));
        if(n > 0)
        {
            cout << "共享的内容: " << init.s << endl;
            sleep(1);
        }
        else if(n == 0)
        {
            break;
        }
    }

    return 0;
}

client:

#include "comm.hpp"

using namespace std;

int main()
{
    key_t key = GetKey();
    // 获取共享内存
    int shmid = GetShm(key);
    char *s = (char *)shmat(shmid, nullptr, 0);
    cout << "attach shm done" << endl;

    int fd = open(FileName.c_str(), O_WRONLY);
    if(fd < 0)
    {
        cerr << "errno: " << errno << ", strerrno: " << strerror(errno) << endl;
        return false;
    }

    char c = 'a';
    for(; c <= 'z'; c++)
    {
        s[c-'a'] = c;
        cout << "write: " << c << " done" << endl;
        sleep(1);

        int code = 1;
        write(fd, &code, sizeof(code));
    }

    shmdt(s);
    cout << "detach shm done" << endl;
    return 0;
}

在这里插入图片描述

共享内存实现server&client通信 代码


📖6. 总结

随着我们对Linux进程间通信中命名管道和System V共享内存的深入学习,不难发现,这两种机制在操作系统中扮演着举足轻重的角色。命名管道以其灵活性和易用性,成为了不同进程间进行数据交换的桥梁;而System V共享内存则以其高效性和低延迟,成为了高性能应用中的首选通信方式

在本文中,我们详细探讨了命名管道和System V共享内存的基本原理、实现方式、应用场景以及相关的注意事项。通过实例演示和代码分析,我们深入剖析了这两种机制的工作机制,并展示了它们在实际应用中的强大功能

虽然我们已经对命名管道和System V共享内存有了较为深入的了解,但操作系统中的进程间通信机制远不止于此。在未来的学习旅程中,我们还将继续探索其他IPC机制,如消息队列、信号量等,以更全面地掌握Linux进程间通信的核心技术

愿你在未来的学习之路上不断前行,取得更大的成就!

在这里插入图片描述

希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!

在这里插入图片描述

评论 192
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值