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

[Linux]进程间通信(system V共享内存 | system V信号量)

在这里插入图片描述

文章目录

  • system V共享内存
    • 共享内存示意图
    • 共享内存数据结构
    • 共享内存函数
    • 创建共享内存
    • 共享内存底层细节
      • 共享内存大小
      • 共享内存属性的数据结构
    • 共享内存实现server&client通信
  • system V信号量
    • 临界资源
    • 临界区
    • 原子性
    • 互斥
    • 信号量

system V共享内存

共享内存区是最快的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 */
};

共享内存函数

shmget函数

#include <sys/ipc.h>
#include <sys/shm.h>
功能:用来创建共享内存
原型
	 int shmget(key_t key, size_t size, int shmflg);
参数
	 key:这个共享内存段名字
	 size:共享内存大小
	 shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

size:
共享内存的大小尽量保持在4kb的整数倍

shmflg标识标志:

在这里插入图片描述
如果shmflg的值为IPC_CREAT或0,如果key对应的共享内存段不存在,则创建,如果已经存在,则直接返回已经存在的共享内存。
如果shmflg的值为IPC_CREAT|IPC_EXCL,如果key对应的共享内存段不存在,则创建,如果已经存在,则返回错误,这样就保证了调用sheget()申请的共享内存一定是未使用过的。


如果获取key值呢?调用ftok()接口生成

ftok函数

#include <sys/types.h>
#include <sys/ipc.h>
功能:用来获取key值
原型
	key_t ftok(const char *pathname, int proj_id);
参数
	pathname:自定义路径名
	proj_id:自定义项目id

如何保证不同进程访问的是同一块共享内存呢?

通过自定义路径名+自定义项目id生成key的算法是一样的,就会形成同一个key值,访问的就是同一块共享内存块。key值会设置进内核关于共享内存的数据结构中。


shmat函数

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

功能:将共享内存段连接到进程地址空间
原型
	 void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
	 shmid: 共享内存标识
	 shmaddr:指定连接的地址
	 shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个字节;失败返回-1

说明:

shmaddr为NULL,核心自动选择一个地址。
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。
公式:shmaddr - (shmaddr % SHMLBA)。
shmflg = SHM_RDONLY,表示连接操作用来只读共享内存。
  • 用户在访问共享内存时,不需要调用read() or write()系统调用,因为共享内存一旦创建好,就会映射到进程的进程地址空间中,该进程就可以直接访问共享内存,就如同malloc()一样,不需要系统调用接口访问,所以共享内存是所有进程间通信速度最快的。
  • 在一个进程没有向共享内存写数据时,另一个进程并不会等待该进程写入之后在读数据,共享内存不提供同步和互斥机制,需要程序员自行维护数据安全。

shmdt函数

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

功能:将共享内存段与当前进程脱离
原型
 	int shmdt(const void *shmaddr);
参数
 	shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数

#include <sys/ipc.h>
#include <sys/shm.h>

功能:用于控制共享内存
原型
	 int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
	 shmid:由shmget返回的共享内存标识码
	 cmd:将要采取的动作(有三个可取值)
	 buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

在这里插入图片描述


创建共享内存

comm.h:

#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>

#define PATH_NAME "./"
#define PROJ_ID 0x6666
#define SIZE 4096

server.c:

#include "comm.h"

// 创建共享内存

int main()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if(key < 0){
        perror("ftok");
        return 1;
    }

    int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL);
    if(shmid < 0){
        perror("shmget");
        return 1;
    }

    printf("key: %u, shmid: %d\n", key, shmid);

    return 0;
}

运行结果:
[cwx@VM-20-16-centos shared_memory]$ ./server 
key: 1711343725, shmid: 12
[cwx@VM-20-16-centos shared_memory]$ ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x6601086d 12         cwx        0          4096       0    

我们可以发现运行server进程后,进程运行结束,但是进程创建的共享内存并没有被释放,systemV的IPC资源生命周期是随内核的,需要程序员显式的释放或者操作系统重启。

可以通过ipcs -m查看共享内存详情,用ipcrm -m shmid释放共享内存:

[cwx@VM-20-16-centos shared_memory]$ ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x6601086d 12         cwx        0          4096       0                       

[cwx@VM-20-16-centos shared_memory]$ ipcrm -m 12
[cwx@VM-20-16-centos shared_memory]$ ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status   

或者通过shmctl系统调用删除共享内存:

shmctl(shmid, IPC_RMID, NULL);

key VS shmid:

key:key是在系统层面用来标识共享内存唯一性的值,不能用来管理共享内存。
shmid:shmid是操作系统给用户返回的id,在用户层用于管理共享内存。


共享内存底层细节

共享内存大小

shmget函数的size

int shmget(key_t key, size_t size, int shmflg);
参数
	 size:共享内存大小

创建共享内存时,建议size设置为4096字节的整数倍,共享内存在内核中申请的基本单位是内存页(4KB),操作系统为了提高内存和硬盘之间交换数据的速度,交换数据是以4KB为单位的,如果申请4097字节的共享内存,内核会给你4096*2字节的空间,但是用户可见的也只是4097个字节。


共享内存属性的数据结构

man shmctl查看存储共享内存属性的数据结构:

在这里插入图片描述

man msgctl查看存储消息队列属性的数据结构:

在这里插入图片描述

man semctl查看存储信号量属性的数据结构:

在这里插入图片描述

总结:

  • 共享内存、消息队列和信号量的接口类似
  • 共享内存、消息队列和信号量的数据结构的第一个数据的数据类型是一样的:struct ipc_perm

在内核中,所有的IPC资源都是由数组组织起来的,但是共享内存、消息队列和信号量的数据结构类型完全不一样,操作系统是怎么组织的呢?

所有的System V标准的IPC资源,XXXid_ds结构体的第一个数据类型都是ipc_prem,在内核中,有存放IPC资源的数组ipc_id_ary,ipc_id_ary数组的类型为(struct ipc_prem*),由于XXXid_ds结构体的第一个数据类型都是ipc_prem,存储时只需把结构体指针类型强转成(ipc_prem*)就可以以相同的视角看待不同类型的IPC资源,如若需要使用IPC资源的其他成员,只需要把数组成员强转回原来的类型,解引用就可以得到所需成员,这与C++的切片原理类似,是用C语言实现的切片技术。

在这里插入图片描述


共享内存实现server&client通信

comm.h:

#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>

#define PATH_NAME "./"
#define PROJ_ID 0x6666
#define SIZE 4096

Makefile文件:

.PHONY:all
all:client server

client:client.c
	gcc -o $@ $^

server:server.c
	gcc -o $@ $^

.PHONY:clean
clean:
	rm -f client server

server.c:

#include "comm.h"

// 创建共享内存

int main()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if(key < 0){
        perror("ftok");
        return 1;
    }

    int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if(shmid < 0){
        perror("shmget");
        return 1;
    }

    char* mem = shmat(shmid, NULL, 0);
    printf("server process attaches shared memory success\n");

    while(1)
    {
        sleep(1);
        printf("%s\n", mem);
    }

    shmdt(mem);
    printf("server process detaches shared memory success\n");

    shmctl(shmid, IPC_RMID, NULL);


    printf("key: %u shmid: %d delete success\n", key, shmid);


    return 0;
}

client.c:

#include "comm.h"

int main()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if(key < 0){
        perror("ftok");
        return 1;
    }

    int shmid = shmget(key, SIZE, IPC_CREAT);
    if(shmid < 0){
        perror("shmget");
        return 1;
    }

    char* mem = shmat(shmid, NULL, 0);
    printf("client process attaches success\n");

    char ch = 'A';
    while(ch <= 'Z'){
        mem[ch-'A'] = ch;
        ch++;
        mem[ch-'A'] = 0;
        sleep(1);
    }

    shmdt(mem);
    printf("client process detaches success\n");

    return 0;
}

运行结果:
在这里插入图片描述


system V信号量

信号量主要用于同步和互斥的。先了解概念,多线程会深入探讨。

临界资源

系统中被多个执行流同时访问的资源就是临界资源。比如同时向显示器打印消息,显示器就是一个临界资源,管道、共享内存、消息队列等都是临界资源。

临界区

进程的代码中用来访问临界资源的代码就是临界区。比如命名管道的代码:

#include "comm.h"

int main()
{
    int fd = open(MY_FIFO, O_WRONLY);

    while(1){
        printf("请输入# ");
        fflush(stdout);
        char buffer[64] = {0};
        ssize_t s = read(0, buffer, sizeof(buffer)-1);

        // 临界区
        if(s > 0){
            buffer[s-1] = 0; // 去掉回车

            write(fd, buffer, strlen(buffer));
        }
    }
    close(fd);

    return 0;
}

原子性

原子性是指 一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着"同生共死"的感觉。

互斥

是指散布在不同进程之间的若干程序片段,当某个进程执行其中的一个程序片段时,其他进程就不能运行它们之中的任一程序片段,只能等到该进程运行完之后才可以继续运行。

信号量

管道、共享内存和消息队列等进程间通信的方式都是以传输数据为目的的,信号量不是以传输数据为目的,而是通过资源"共享"的方式,来达到多个进程同步和互斥的目的,信号量是用来衡量临界资源数目的。


相关文章:

  • 分布式事务及其实现方案
  • Flink系列-背压(反压)
  • 随机森林实战(分类任务+特征重要性+回归任务)(含Python代码详解)
  • 面向对象编程原则(02)——单一职责原则
  • C++面向对象程序设计(第2版)第二章(类和对象的特性)知识点总结
  • 学习springboot杂乱无章的笔记
  • java计算机毕业设计红河旅游信息服务系统源码+数据库+系统+lw文档+mybatis+运行部署
  • Pytorch 实战 LESSON 16 深度学习视觉入门 上
  • 10.VScode下载---Windows64x
  • java计算机毕业设计互联网保险网站源码+数据库+系统+lw文档+mybatis+运行部署
  • Linux14 NAT网络配置原理 查看网络ip和网关 修改ip地址 指定ip方法 主机名与hosts映射 主机名解析过程
  • SPDK Vhost在线恢复:让I/O飞一会儿
  • 如何判断一个低代码平台是否专业?
  • 达利欧《原则》读书思考笔记
  • C语言动态内存管理、柔性数组(超详细版)
  • 2019.2.20 c++ 知识梳理
  • cookie和session
  • docker-consul
  • ES6 学习笔记(一)let,const和解构赋值
  • iOS帅气加载动画、通知视图、红包助手、引导页、导航栏、朋友圈、小游戏等效果源码...
  • java 多线程基础, 我觉得还是有必要看看的
  • MySQL的数据类型
  • Python 基础起步 (十) 什么叫函数?
  • vue-cli3搭建项目
  • 每天一个设计模式之命令模式
  • 前端设计模式
  • 如何编写一个可升级的智能合约
  • 首页查询功能的一次实现过程
  • 数组大概知多少
  • 小程序01:wepy框架整合iview webapp UI
  • 新手搭建网站的主要流程
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • 2017年360最后一道编程题
  • Salesforce和SAP Netweaver里数据库表的元数据设计
  • 阿里云ACE认证之理解CDN技术
  • ​ArcGIS Pro 如何批量删除字段
  • ​LeetCode解法汇总1410. HTML 实体解析器
  • ​如何使用ArcGIS Pro制作渐变河流效果
  • #{}和${}的区别?
  • $(function(){})与(function($){....})(jQuery)的区别
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (173)FPGA约束:单周期时序分析或默认时序分析
  • (ibm)Java 语言的 XPath API
  • (python)数据结构---字典
  • (八)Spring源码解析:Spring MVC
  • (附源码)springboot炼糖厂地磅全自动控制系统 毕业设计 341357
  • (附源码)ssm捐赠救助系统 毕业设计 060945
  • (四)TensorRT | 基于 GPU 端的 Python 推理
  • (四)汇编语言——简单程序
  • (转)ABI是什么
  • (转)清华学霸演讲稿:永远不要说你已经尽力了
  • (转载)Linux 多线程条件变量同步
  • ***详解账号泄露:全球约1亿用户已泄露
  • .NET Core WebAPI中使用Log4net 日志级别分类并记录到数据库
  • .NET Core跨平台微服务学习资源