当前位置: 首页 > news >正文

Linux系统入门:System V进程间通信

 目录

一.system V共享内存

一).共享内存原理

二).共享内存函数

1.shmget函数:用来创建共享内存

2.shmat函数:将共享内存段连接到进程地址空间

3.shmdt函数:将共享内存段与当前进程脱离

4.shmctl函数:用于控制共享内存

三).模拟实现共享内存实现通信

二.system V消息队列(了解即可)

三.system V信号量

一).并发编程

二).信号量介绍

四.内核是如何组织管理IPC资源的


一.system V共享内存

专门设计了一个IPC模块,让不同的进程看到同一份资源。

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

共享内存,消息队列和信号量三种IPC资源的生命周期随内核的生命周期,IPC资源必须调用命令或者调用系统调用进行删除,否则不会自动清除,除非重启。

一).共享内存原理

在物理内存空间开辟一块内存空间,将这块内存空间分别映射到两个进程虚拟内存空间的共享区中,进程就可以通过访问自己进程的虚拟地址空间进行进程间通信了

我们的系统中有很多的共享内存,那么这些共享内存需要被管理吗?需要的,那怎么管理,先描述再组织!也就是进程和共享内存的关系!共享内存一定有对应的内核数据结构

共享内存的内核数据结构

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

二).共享内存函数

1.shmget函数:用来创建共享内存

参数:

  • key:这个共享内存段名字
  • size:共享内存大小
  • shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的取值为IPC_CREAT:共享内存不存在,创建并返回;共享内存已存在,获取并返回。取值为IPC_CREAT | IPC_EXCL:共享内存不存在,创建并返回;共享内存已存在,出错返回

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


注意:key是共享内存唯一性的标志,这个参数需要由用户传入,当两个进程要使用同一块共享内存进行通信时,该参数填为一样的值。

生成key值:

2.shmat函数:将共享内存段连接到进程地址空间

参数:

  • shmid: 共享内存标识
  • shmaddr:指定连接的地址
  • shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY

返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1


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

但一般情况下 shmaddr 参数为 nullptr,shmflg 参数为 0。不用管。

3.shmdt函数:将共享内存段与当前进程脱离

参数:

  • shmaddr: 由shmat所返回的指针

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


注意:将共享内存段与当前进程脱离不等于删除共享内存段

4.shmctl函数:于控制共享内存

参数:

  • shmid:由shmget返回的共享内存标识码
  • cmd:将要采取的动作(有三个可取值)
  • buf:指向一个保存着共享内存的模式状态和访问权限的数据结构

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


Bash命令:

ipcs -m 查看共享内存

ipcrm -m [shmid] 命名,删除共享内存标识为shmid的共享内存

三).模拟实现共享内存实现通信

使用共享内存技术进行进程间通信,因为共享内存没有进行同步与互斥! 共享内存缺乏访问控制!会带来并发问题,所以使用命名管道对其进程间做一定的访问控制。

代码实现目标:服务端依次写入AA,BB,CC.....,客户端依次读到AA,AABB,AABBCC,.....

//Makefile.PHONY:all
all:client server
client:client.ccg++ -o $@ $^ -std=c++11
server:server.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f client server
// Comm.hpp#include <cstdio>
#include <stdlib.h>#define ERR_EXIT(m)\
do\
{\perror(m);\exit(EXIT_FAILURE);\
} while (0);
// Fifo.hpp#pragma once#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "Comm.hpp"#define PATH "."
#define NAME "fifo"class NameFifo
{
public:NameFifo(const std::string path, const std::string name): _path(path), _name(name){// 创建管道文件_filename = _path + "/" + _name;umask(0);int n = mkfifo(_filename.c_str(), 0664);if (n < 0){ERR_EXIT("mkfifo");}else{std::cout << "管道创建成功" << std::endl;}}~NameFifo(){int n = unlink(_filename.c_str());// 删除成功if (n == 0){perror("unlink");// ERR_EXIT("unlink");// 如果使用ERR_EXIT进行退出,服务端会直接调用exit函数进行退出,而不会调用shm的析构函数}else // 删除失败{std::cout << "删除管道失败" << std::endl;}}private:std::string _path;std::string _name;std::string _filename;
};class FileOperation
{
public:FileOperation(const std::string path, const std::string name): _path(path), _name(name), _fd(-1){_filename = _path + "/" + _name;}void OpenForRead(){_fd = open(_filename.c_str(), O_RDONLY);if (_fd < 0) {ERR_EXIT("open");}else if (_fd > 0){std::cout << "打开成功" << std::endl;}}void OpenForWrite(){_fd = open(_filename.c_str(), O_WRONLY);if (_fd < 0) {ERR_EXIT("open");}else if (_fd > 0){std::cout << "打开成功" << std::endl;}}//  服务端进行读void Read(){char buf[1024];int n;while (true){n = read(_fd, buf, sizeof(buf) - 1);buf[n] = 0;if (n > 0){std::cout << "Client say # " << buf << std::endl;}else if (n == 0){std::cout << "Client quit! me too !" << std::endl;break;}else{// TODO}}}//  客户端进行写void Write(){std::string message;while (true){std::cin >> message;int n = 0;write(_fd, message.c_str(), message.size());}}bool Wait(){char c;int number = read(_fd, &c, 1);if (number > 0){return true;}return false;}void Wakeup(){char c = 'c';write(_fd, &c, 1);}void Close(){close(_fd);}private:std::string _path;std::string _name;std::string _filename;int _fd;
};

#pragma once#include <iostream>
#include <cstdio>
#include <string>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "Comm.hpp"
#include <unistd.h>const int gdefaultid = -1;
const int gsize = 4096;
const std::string pathname = ".";
const int projid = 0x66;
const int gmode = 0666;
#define CREATER "creater"
#define USER "user"//  描述共享内存的类
class Shm
{
private:void Createhelp(int flg){key_t key=ftok(pathname.c_str(),projid);if(key<0){ERR_EXIT("ftok");}//创建共享内存_shmid=shmget(key,_size,flg);if(_shmid<0){ERR_EXIT("shmget");}}//  创建共享内存资源void Create(){Createhelp(IPC_CREAT | IPC_EXCL | gmode);}//  关联共享内存资源void Attach(){_start_mem = shmat(_shmid, nullptr, 0);if ((long long)_start_mem < 0){ERR_EXIT("shmat");}std::cout << "关联共享内存资源成功" << std::endl;}//  取消关联void Detach(){int n = shmdt(_start_mem);if (n == 0){printf("取消关联成功\n");}}//  获取共享内存资源void Get(){Createhelp(IPC_CREAT);}//  释放共享内存资源void Destroy(){shmctl(_shmid, IPC_RMID, nullptr);}public:// pathname 和 projid 用于构建 key 值,usertype用于标记使用者身份Shm(const std::string &pathname, const int projid, const std::string &usertype): _shmid(gdefaultid), _size(gsize), _start_mem(nullptr), _user_type(usertype){if (_user_type == CREATER){Create();printf("1111111");}else if (_user_type == USER){Get();}else{}Attach();}// 取消共享内存的关联对象,如果使用者身份为创建者,则释放共享内存资源~Shm(){std::cout << _user_type << std::endl;if (_user_type == CREATER){Destroy();}}// 获取起始虚拟地址void* VirtualAddr(){printf("virtual address: %p\n", _start_mem);return _start_mem;}//  获取共享内存大小int size(){return _size;}//  获取共享内存信息void Attr(){struct shmid_ds ds;int n = shmctl(_shmid, IPC_STAT, &ds);printf("shm_segsz: %ld\n", ds.shm_segsz);printf("key: 0x%x\n", ds.shm_perm.__key);}private:int _shmid; //共享内存id号key_t _key; //共享内存的key值,标识唯一性int _size;  //共享内存的空间大小void *_start_mem;   //共享内存的起始虚拟地址std::string _user_type; //共享内存的使用者身份
};

//server.cc#include "Comm.hpp"
#include "Shm.hpp"
#include "Fifo.hpp"int main()
{Shm shm(pathname, projid, CREATER);NameFifo nf(PATH, NAME);FileOperation fo(PATH, NAME);fo.OpenForRead();// 使用命名管道的方式,让数据的读取按照特定的格式读取char *mem = (char *)shm.VirtualAddr();while (true){if (fo.Wait()){printf("%s\n", mem);}else{break;}}fo.Close();return 0;
}
//client.cc#include "Comm.hpp"
#include "Shm.hpp"
#include "Fifo.hpp"int main()
{Shm shm(pathname, projid, USER);FileOperation fo(PATH,NAME);fo.OpenForWrite();char* mem = (char*)shm.VirtualAddr();int index = 0;for (char c = 'A'; c <= 'Z'; c++, index+=2){sleep(1);mem[index] = c;sleep(1);mem[index+1] = c;fo.Wakeup();}fo.Close();return 0;
}

上述代码分为服务端和客户端。当服务端启动的时候,创建共享内存并将共享内存和该进程进行关联,创建命名管道并以读方式打开命名管道,此时服务端进程会阻塞在Wait()函数的read()函数处,等待客户端启动;当客户端启动时,获取和服务端相同的共享内存资源并关联该共享内存,并且以写方式打开命名管道,然后向共享内存中依次写入AA BB CC ...,当同一个字母写了两次后,向命名管道中写入一个标记字符c,唤醒服务端,服务端收到该进程发送的标记字符,结束等待,从共享内存资源中读取数据。


二.system V消息队列(了解即可)

  • 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法。
  • 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。
  • 特性方面,IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核。

三.system V信号量

信号量主要用于同步和互斥的,下面先来看看什么是同步和互斥。

一).并发编程

  • 进程间通讯的前提条件:使不同的进程看到同一份资源。
  • 数据不一致:当不同的进程访问同一份资源的时候,需要有“保护”机制。例如让读端以某一种形式对写端的传输的数据进行读取。如上述的共享内存,当两个进程之间没有“保护”机制的时候,写端一直向共享内存写入数据,而读端没有按照 AA BB..的形式读取,就会造成数据不一致的情况。
  • 数据不一致产生情况:访问共享资源的代码段没有进行约束或者保护,意思就是两个进程对同一份资源访问的时候没有一定的顺序性。
  • 临界区:当一份代码中某一段代码会对共享资源进行访问时,该代码段叫做临界区。同理没有对共享资源进行访问的代码段,叫做非临界区。
  • 临界资源:系统中某些资源一次只允许一个进程访问,称这样的资源为临界资源或互斥资源。也就是,没有被保护的资源是共享资源,被保护的资源是临界资源。那么要如何保护?同步或互斥
  • 同步:多个执行流访问临界(共享)资源时,具有一定的顺序性叫做同步。
  • 互斥:任何一个时刻,至于性能一个执行流访问临界资源,叫做互斥。同步和互斥是最常见的对共享资源进行保护的方式。

没有对临界资源访问的部分叫做非临界区,对临界资源访问的部分叫做临界区,通过加锁的方式,可以对共享资源进行保护。对共享资源进行保护,本质是对访问共享资源的代码进行保护

二).信号量介绍

特性方面

  • IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核

理解方面

  • 信号量是一个计数器,用来表明临界资源中,资源的数量

作用方面

  • 保护临界区

本质方面

  • 所有进程访问临界资源的一小块,就必须申请信号量。如果失败,进程阻塞挂起。申请信号量本质是对资源的预定机制

操作方面

  • 申请资源,计数器--,P操作
  • 释放资源,计数器++,V操作

那么信号量和通信有什么关系?

  • 访问信号量,每个进程都得先看到同一份资源。
  • 不是传递数据才是通信IPC,通知,互斥也算。

四.内核是如何组织管理IPC资源的

在Linux内核中,System V标准的三种资源 -- 共享内存、消息队列、信号量,是进行统一管理的。

在内核中,有一个 struct ipc_ids sem_ids 结构体,里面包含了一指针 *entries,指向一个 ipc_id_ary 结构体,而 ipc_id_ary 结构体中维护了一个柔性数组,数组的每一个元素存储着指向上述申请的各种资源的指针。

问题是,为什么不同的结构体能被相同的指针所指向?原因是,三种共享资源的结构体中,第一个成员对象都是 kern_ipc_perm 结构体,所以在柔性数组中,存放的是 kern_ipc_perm类型的指针,使其能够指向不同类型的结构体。本质上,是C语言的一种多态形式。当需要进行访问将指针强转为对应类型即可进行访问。

http://www.dtcms.com/a/528283.html

相关文章:

  • 第一章 蓝图篇 - 全景认知与项目设计
  • mormot.net.server.pas源代码分析
  • 丹阳网站建设价位php网站搭建
  • 【工具分享】另一个免费开源的远程桌面服务-Apache Guacamole
  • RabbitMQ TTL机制详解
  • XSL-FO 对象:深度解析与实际应用
  • 在JavaScript / Node.js / 抖音小游戏中,使用tt.request通信
  • 两学一做网站源码wordpress 柚子皮下载
  • Go slog 日志打印最佳实践指南
  • Go的垃圾回收
  • 珠海网站管理公司国际公司名字
  • 自动化模型学习器——autoGluon
  • 长沙网站建设招聘外贸做那种网站有哪些
  • 浏览器卡顿内存高?傲游浏览器三核加速,网页加载效率提升60%
  • 研发部门验收流程
  • 贪心算法 with Gemini
  • 掌握 Rust:从内存安全到高性能服务的完整技术图谱
  • [Java]重学Java-Java平台
  • Bash Shell 脚本编程入门详解
  • 打造高清3D虚拟世界|零基础学习Unity HDRP高清渲染管线(第七天)
  • 营销型网站建立费用手机端网站开发页
  • 网页模板免费资源搜索引擎排名优化技术
  • 2025年9月电子学会全国青少年软件编程等级考试(Python四级)真题及答案
  • hot 100 技巧题
  • Evaluating Long Context (Reasoning) Ability
  • 乐器基础知识学习
  • 做英语手抄报 什么网站中铁建设集团有限公司分公司
  • Java自动化测试之数据库的操作
  • 算法:并行课程II
  • 信阳住房和城乡建设厅网站企业vi设计说明