Linux之进程间通信——system V(共享内存、消息队列、信号量等)(下)

简介: Linux之进程间通信——system V(共享内存、消息队列、信号量等)(下)

二、实现进程间通信(代码)

文件comm.hpp

#ifndef __COMM_HPP_
#define __COMM_HPP_
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
#define PATHNAME "."
#define PROJ_JD 0x66
#define MAX_SIZE 4096
key_t getkey()
{
    key_t k = ftok(PATHNAME,PROJ_JD);
    if(k <0)
    {
        cerr<<errno<<":"<<strerror(errno)<<endl;
        exit(1);
    }
    return k;
}
int getShmHelper(key_t k,int flags)
{
    //k是要shmget,设置进入共享内存属性中的,用来标识
    //该共享难内存在内核中的唯一性
    //shmid与key:
    //fd     inode
    int shmid = shmget(k,MAX_SIZE,flags);
    if(shmid<0)
    {
        cerr<<errno<<":"<<strerror(errno)<<endl;
        exit(2);
    }
    return shmid;
} 
//获取
int getShm(key_t k)
{
    return getShmHelper(k,IPC_CREAT);
}
//创建
int createShm(key_t k)
{
    return getShmHelper(k,IPC_CREAT | IPC_EXCL|0600);
}
void delShm(int shmid)
{
    if(shmctl(shmid,IPC_RMID,nullptr)==-1)
    {
        cerr<<errno<<":"<<strerror(errno)<<endl;
    }
}
void * attachShm(int shmid)
{
    void*mem = shmat(shmid,nullptr,0);
    if((long long)mem==-1L)//64位系统,8个字节,L表示数字类型
    {
        cerr<<errno<<"shmat:"<<strerror(errno)<<endl;
        exit(3);
    }
    return mem;
}
void detachShm(void * start)
{
    if(shmdt(start)==-1)
    {
        cerr<<"shmdt:"<<errno<<":"<<strerror(errno)<<endl;
    }
}
#endif

文件server.cc

#include "comm.hpp"
#include <unistd.h>
using namespace std;
int main()
{
    key_t k = getkey();
    printf("key:%0x%x\n",k);
    int shmid = createShm(k);
    printf("shmid:%d\n",shmid);
    //sleep(5);
    char*start = (char*)attachShm(shmid);
    printf("attach success,address start:%p\n",start);
    //使用
    while(true)
    {
        printf("client say:%s\n",start);
        struct shmid_ds ds;
        shmctl(shmid,IPC_STAT,&ds);
        printf("获取属性:size:%d,pid:%d,myself:%d",ds.shm_segsz,ds.shm_cpid);
        sleep(1);
    }
    //去关联
    detachShm(start);
    sleep(10);
    //删除共享内存
    delShm(shmid);
    return 0;
}

文件client.cc

#include "comm.hpp"
#include <unistd.h>
using namespace std;
int main()
{
    key_t k = getkey();
    printf("key:%0x%x\n",k);
    int shmid = getShm(k);
    printf("shmid:%d\n",shmid);
    char*start = (char*)attachShm(shmid);
    printf("attach success,address start:%p\n",start);
    const char*message = "hello server,我是另一个进程,正在和你通信";
    pid_t id = getpid();
    int count = 1;
    //char buffer[1024];
    while(true)
    {
       sleep(5);
       snprintf(start,MAX_SIZE,"%s[pid:%d][消息编号:%d]",message,id,count++);
       // snprintf(buffer,sizeof(buffer),"%s[pid:%d][消息编号:%d]",message,id,count++);
      //  memcpy(start,buffer,strlen(buffer)+1);
    }
    detachShm(start);
    return 0;
}

三、共享内存的特点

  1. 共享内存的生命周期是随OS的,而不是随进程,这是所有system V进程间通信的共性。
  2. 共性内存是所有进程间通信速度最快的,因为共享内存是被双方所共享,只要一方有写入,另一方就会立即看到,这样可以大大减少数据的拷贝次数。(优点
  3. 综合考虑管道和共享内存:
    管道:
    写入端进程:需要通过键盘输入到自己定义的缓冲区char buffer[],将数据拷贝到buffer中,再调用write接口将buffer中的数据拷贝到管道中。
    读取端进程:也定义了buffer缓冲区,调用read接口将数据从管道拷贝到buffer中,再将数据显示到显示屏上。
    共享内存:
    通过映射,直接从输入到共享内存,从共享内存到输出。
  4. 共享内存不给我们提供同步和互斥的操作,无法对数据进行保护。客户端和服务端没有做保护,如果想要保护数据,需要用到信号量,对共享内存进行保护,写完通过读端读取。(缺点)

四、消息队列(了解)

1.概念

消息队列是OS提供的内核级队列,消息队列提供了推广从一个进程想另一个进程发送一块数据的方法。每个数据块都被认为是有一个类型,而接收者进程接收的数据块可以是不同的类型值。

2.消息队列数据结构

struct msqid_ds{
  struct ipc_perm msg_perm;
  time_t msg_stime;
  time_t msg_rtime;
  time_t msg_ctime;
  unsigned long __msg_cbytes;
  msgqnum_t msg_qnum;
  msglen_t msg_qbytes;
  pid_t msg_lspid;
  pid_t msg_lrpid;

消息队列数据结构的第一个成员是msg_perm,它和shm_perm是同一个类型的结构体变量——ipc_perm结构体。

3.消息队列的相关函数

msgget:获取消息队列

参数

keyftok函数生成的一个key值,它作为msgget的第一个参数;

msgflg:与创建共享内存用的函数shmget的第三个参数相同;

返回值

返回一个有效的消息队列标识符。

msgctl:控制消息队列

它的参数与之前类似接口的参数相同。

msgsnd:发数据

参数

msqid:表示消息队列的用户级标识符;

msgp:表示待发送的数据块;

msgsz:表示待发送的数据块的大小;

msgflg:表示发送数据块的方式,一般默认为0。

返回值

发送成功返回0,发送失败返回-1.

msgrcv:读取消息队列

参数

mspid:表示消息队列的用户级标识符;

msgp:表示获取到的数据块(它是一个输出型参数);

msgsz:表示要获取的数据块的大小;

msgtyp:表示要接收的数据块的类型;

msgflg:表示发送数据块的方式,一般默认为0。

返回值

成功返回实际获取到的mtext数组中的字节数,失败返回-1。

五、信号量

1.概念

信号量的本质是一个计数器,通常用来表示公共资源中资源数多少的问题。信号量主要是用于同步和互斥操作的。

公共资源:能被多个进程同时访问的资源。

  1. 访问没有被保护的公共资源,会存在数据不一致的问题。(要让不同的进程看到同一份资源是为了进程间通信,通信是为了实现进程的协同,但是,不同进程访问同一份资源会导致该资源数据被修改,进而导致数据不一致的问题)
  2. 被保护的公共资源称为临界资源,进程要访问资源,一定是进程中有对应的代码来访问这份资源,访问临界资源的代码就称为临界区。有临界区,自然就有非临界区。多进程访问一份临界资源的情况属于少数情况,大部分情况下进程都是申请自己独立的资源,不访问公共资源的代码就是非临界区

为了避免数据不一致的问题,我们需要对公共资源进行保护,那么该如何保护呢?

答:互斥和同步。

  1. 互斥:由于有各个进程要求共享资源的情况,并且有些资源需要互斥使用,因此各进程间需要竞争使用这些资源。进程的这种关系称为互斥
  2. 原子性:做一件事,只有做完和不做两态(即,要么做要么不做,不能做一半)。

为什么不用全局的整数来作为信号量?

因为全局的整数,有血缘关系的父子进程都不能同时看到(一旦一方修改,就会进行写时拷贝),而不同的进程更加不能看到。因此进程间想看到同一个计数器(可能会发生修改),就不能用全局的整数。

为什么需要信号量?

当我们想要申请某项共享资源时,我们需要通过信号量来预测该共享资源是否被使用。共享资源的使用方式:1.作为一个整体被整个使用(一个打印机,信号量是打印顺序,同一时间只能打印一份文件);2.被划分为一个一个小的资源部分(电影院的座位,信号量是电影票,凭电影票进去看电影,同一场电影可以被多个人同时观看)。进程要访问某些共享资源时,要先申请信号量,申请成功就相当于预定了共享资源,即允许访问该共享资源。

2.信号量数据结构

3.信号量的原子操作(P/V操作)

所有进程在访问公共资源之前,都需要申请sem信号量,而申请信号量的前提是进程必须先看到同一个信号量,所以信号量本身就是一个公共资源。同时信号量的操作必须保证自身是安全的,因此++/–是原子性的。

特殊的:如果信号量的初始值为1,则表示该公共资源是作为一个整体来进行申请、使用、释放的。这种二院信号量是具有互斥功能的。

4.信号量的相关函数

semget:申请信号量

参数

key:使用ftok函数生成的key值,可以唯一表示共享内存;

nsems:表示创建信号量的个数;

semflg:与穿个件共享内存时使用的shmget函数的第三个参数相同。

返回值

信号量集创建成功时返回一个有效到的信号量集标识符。

semctl:信号量的删除

semop:信号量的操作

六、总结

我们发现:共享内存、消息队列、信号量的接口相似度都很高(参数很多都是相同的),获取和删除都是system V标准的进程间通信的操作。

OS的管理本质都是 先描述,再组织,对于共享内存、消息队列、信号量等的第一个成员都是结构体ipc_perm的变量。

虽然它们内部的属性差别很大,但是维护它们的结构的第一个成员是一样的,都可以用key值来标识唯一性。这样设计的好处:在操作系统中可以只定义一个struct ipc_perm结构体类型的数组,每当申请一个IPC资源就在该数组中多开辟一个这样的结构体变量的空间((struct shmid_ds*)perms[0],像这样进行强转强转,此时就能访问其他的属性)


总结

以上就是今天要讲的内容,本文介绍了进程间通信的system V的相关概念。本文作者目前也是正在学习Linux相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。

最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!

相关文章
|
7天前
|
消息中间件 存储 网络协议
从零开始掌握进程间通信:管道、信号、消息队列、共享内存大揭秘
本文详细介绍了进程间通信(IPC)的六种主要方式:管道、信号、消息队列、共享内存、信号量和套接字。每种方式都有其特点和适用场景,如管道适用于父子进程间的通信,消息队列能传递结构化数据,共享内存提供高速数据交换,信号量用于同步控制,套接字支持跨网络通信。通过对比和分析,帮助读者理解并选择合适的IPC机制,以提高系统性能和可靠性。
63 14
|
13天前
|
消息中间件 Linux
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
75 20
|
2月前
|
消息中间件 存储 Linux
|
3月前
|
消息中间件 Linux API
Linux c/c++之IPC进程间通信
这篇文章详细介绍了Linux下C/C++进程间通信(IPC)的三种主要技术:共享内存、消息队列和信号量,包括它们的编程模型、API函数原型、优势与缺点,并通过示例代码展示了它们的创建、使用和管理方法。
53 0
Linux c/c++之IPC进程间通信
|
3月前
|
Linux C++
Linux c/c++进程间通信(1)
这篇文章介绍了Linux下C/C++进程间通信的几种方式,包括普通文件、文件映射虚拟内存、管道通信(FIFO),并提供了示例代码和标准输入输出设备的应用。
44 0
Linux c/c++进程间通信(1)
|
6月前
|
消息中间件 C语言 RocketMQ
消息队列 MQ操作报错合集之出现"Connection reset by peer"的错误,该如何处理
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
|
6月前
|
消息中间件 Java C语言
消息队列 MQ使用问题之在使用C++客户端和GBase的ESQL进行编译时出现core dump,该怎么办
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
|
2月前
|
消息中间件 存储 Kafka
MQ 消息队列核心原理,12 条最全面总结!
本文总结了消息队列的12个核心原理,涵盖消息顺序性、ACK机制、持久化及高可用性等内容。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
4月前
|
消息中间件
手撸MQ消息队列——循环数组
队列是一种常用的数据结构,类似于栈,但采用先进先出(FIFO)的原则。生活中常见的排队场景就是队列的应用实例。在数据结构中,队列通常用数组实现,包括入队(队尾插入元素)和出队(队头移除元素)两种基本操作。本文介绍了如何用数组实现队列,包括定义数组长度、维护队头和队尾下标(front 和 tail),并通过取模运算解决下标越界问题。此外,还讨论了队列的空与满状态判断,以及并发和等待机制的实现。通过示例代码展示了队列的基本操作及优化方法,确保多线程环境下的正确性和高效性。
62 0
手撸MQ消息队列——循环数组
|
5月前
|
消息中间件 存储 缓存
一个用过消息队列的人,竟不知为何要用 MQ?
一个用过消息队列的人,竟不知为何要用 MQ?
207 1

热门文章

最新文章