Linux系统——进程间通信之system V

💁‍♂️个人主页:进击的荆棘

👇作者其它专栏:

《数据结构与算法》《算法》《C++起始之路》《Linux》


目录

1.进程间通信介绍

2.管道

3.匿名管道

4.命名管道

5.system V共享内存

6.system V消息队列

7.system V信息量

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


5.system V共享内存

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

5.1共享内存示意图

5.2共享内存数据结构

cpp 复制代码
truct 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 currentattaches */
    unsigned short shm_unused; /* compatibility */
    void shm_unused2; / ditto - used byDIPC */
    void shm_unused3; / unused */
};

5.3共享内存函数

shmget函数

cpp 复制代码
功能:⽤来创建共享内存
原型
    int shmget(key_t key, size_t size, int shmflg);
参数
    key:这个共享内存段名字
    size:共享内存⼤⼩
    shmflg:由九个权限标志构成,它们的⽤法和创建⽂件时使⽤的mode模式标志是⼀样的
        取值为IPC_CREAT:共享内存不存在,创建并返回;共享内存已存在,获取并返回。
        取值为IPC_CREAT | IPC_EXCL:共享内存不存在,创建并返回;共享内存已存在,
出错返回。
返回值:成功返回⼀个⾮负整数,即该共享内存段的标识码;失败返回-1

shmat函数

cpp 复制代码
功能:将共享内存段连接到进程地址空间
原型
    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,表示连接操作用来只读共享内存

shmdt函数

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

shmctl函数

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

实例1-共享内存实现进程间通信

Makefile

cpp 复制代码
.PHONY:all
all:server client
server:server.cc
	g++ -o $@ $^
client:client.cc
	g++ -o $@ $^

.PHONY:clean
clean:
	rm -f server client

comm.h

cpp 复制代码
#ifndef _COMM_H_
#define _COMM_H_

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

# define PATHNAME "."
# define PROJ_ID 0x6666

int createShm(int size);
int destroyShm(int shmid);
int getShm(int size);

# endif

comm.c

cpp 复制代码
#include "comm.h"

static int commShm(int size, int flags)
{
    key_t key = ftok(PATHNAME, PROJ_ID);
    if(key < 0){
        perror("ftok");
        return -1;
    }
    int shmid = 0;
    if( (shmid = shmget(key, size, flags)) < 0){
        perror("shmget");
        return -2;
    }
    return shmid;
}

int destroyShm(int shmid)
{
    if(shmctl(shmid, IPC_RMID, NULL) < 0){
        perror("shmctl");
        return -1;
    }
    return 0;
}

int createShm(int size)
{
    return commShm(size, IPC_CREAT|IPC_EXCL|0666);
}

int getShm(int size)
{
    return commShm(size, IPC_CREAT);
}

server.cc

cpp 复制代码
#include "comm.h"

int main()
{
    int shmid = createShm(4096);
    char *addr = shmat(shmid, NULL, 0);
    sleep(2);
    int i = 0;
    while(i++<26){
        printf("client# %s\n", addr);
        sleep(1);
    }
    shmdt(addr);
    sleep(2);
    destroyShm(shmid);
    return 0;
}

client.cc

cpp 复制代码
#include "comm.h"

int main()
{
    int shmid = getShm(4096);
    sleep(1);
    char *addr = shmat(shmid, NULL, 0);
    sleep(2);
    int i = 0;
    while(i<26){
        addr[i] = 'A'+i;
        i++;
        addr[i] = 0;
        sleep(1);
    }
    shmdt(addr);
    sleep(2);
    return 0;
}

运行结果

ctrl+c终止进程,再次启动

bash 复制代码
# ./server
shmget: File exists
# ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x66026a25 688145 root 666 4096 0

# ipcrm -m 688145 #删除shm ipc资源,注意,不是必须通过⼿动来删除,这⾥只为演⽰相关指
令,删除IPC资源是进程该做的事情

注:共享内存没有进行同步与互斥。共享内存缺乏访问控制,会带来并发问题

实例2-借助管道实现访问控制版的进程通信

comm.hpp

cpp 复制代码
#pragma once

#include <cstdio>
#include <cstdlib>

#define ERR_EXIT(n) \
do{ \
    perror(n); \
    exit(EXIT_FAILURE); \
}while(0) \

fifo.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string>
#include "Comm.hpp"

#define PATH "."
#define FIFONAME "fifo"

class FileFifo{
public:
    FileFifo(const std::string &path,const std::string &name)
        :_path(path),_name(name)
    {
        _pathname=_path+"/"+_name;
        umask(0);
        //创建管道文件
        int n=mkfifo(_pathname.c_str(),0666);
        if(n<0){
            std::cerr<<"mkfifo error"<<std::endl;
            ERR_EXIT("mkfifo");
        }
    }
    ~FileFifo(){
        //删除管道文件
        int n=unlink(_pathname.c_str());
        if(n==0){
            std::cout<<"unlink success"<<std::endl;
        }
        else{
            std::cerr<<"remove fifo failed"<<std::endl;
            ERR_EXIT("unlink");
        }
    }
private:
    std::string _path;
    std::string _name;
    std::string _pathname;
};

class FifoOper{
public:
    FifoOper(const std::string &path,const std::string &name)
        :_path(path),_name(name),_fd(-1)
    {
        _pathname=_path+"/"+_name;
    }
    void OpenForRead(){
        //打开文件
        //当write没有执行open时,read会在open内部进行阻塞
        //直到有人把管道文件打开了,open才会返回
        _fd=open(_pathname.c_str(),O_RDONLY);
        if(_fd<0){
            std::cerr<<"open mkfifo failed"<<std::endl;
            ERR_EXIT("open");
        }
        std::cout<<"open fifo success"<<std::endl;
    }
    void OpenForWrite(){
        //打开文件
        _fd=open(_pathname.c_str(),O_WRONLY);
        if(_fd<0){
            std::cerr<<"open mkfifo failed"<<std::endl;
            ERR_EXIT("open");
        }
        std::cout<<"open fifo success"<<std::endl;
    }
    bool Wait(){
        //读取文件内容
        char c;
        int number=read(_fd,&c,1);
        if(number>0){
            printf("醒来:%d\n",number);
            return true;
        }
        return false;
    }
    void WakeUp(){
        //向文件写入内容
        char c='c';
        int n=write(_fd,&c,1);
        printf("尝试唤醒:%d\n",n);
    }
    void Close(){
        if(_fd>0)
            close(_fd);
    }
    ~FifoOper(){}
private:
    std::string _path;
    std::string _name;
    std::string _pathname;
    int _fd;
};

shm.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <sys/shm.h>
#include <sys/ipc.h>
#include "Comm.hpp"

const int gdefult = -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:
    // 不同的用户需要不同的权限,比如CREATER,我希望它有创建shm和读的权限,但不希望它有写的权限
    // 所以,让一些方法私有,禁止用户直接访问
    void CreateHelper(int flg)
    {
        printf("key is 0x%x\n", _key);
        // 需要给读写执行权限,否则,创建好共享内存后,可能无法读写等
        _shmid = shmget(_key, _size, flg);
        if (_shmid < 0)
        {
            ERR_EXIT("shmget");
        }
        printf("shemget success,id:%d\n", _shmid);
    }
    void Creat()
    {
        CreateHelper(IPC_CREAT | IPC_EXCL | gmode);
    }
    void Get()
    {
        CreateHelper(IPC_CREAT);
    }
    void Attch()
    {
        _start_mm = shmat(_shmid, nullptr, 0);
        if ((long long)_start_mm < 0)
        {
            ERR_EXIT("shmat");
        }
        std::cout<<"attach success"<<std::endl;
    }
    void Detach()
    {
        int n = shmdt(_start_mm);
        if (n < 0)
        {
            ERR_EXIT("shmdt");
        }
        std::cout << "detach success" << std::endl;
    }

    void Destory()
    {
        Detach();
        if (_usertype == CREATER)
        {
            int n = shmctl(_shmid, IPC_RMID, nullptr);
            if (n < 0)
            {
                ERR_EXIT("shmctl");
            }
            std::cout << "delete shm:"<<_shmid<<" successful" << std::endl;
        }
    }

public:
    Shm(const std::string& pathname,int projid,const std::string& usertype)
        : _size(gsize), _shmid(gdefult), _start_mm(nullptr), _usertype(usertype)
    {
        _key = ftok(pathname.c_str(), projid);
        if (_key < 0)
        {
            ERR_EXIT("ftok");
        }

        if (_usertype == CREATER)
        {
            Creat();
        }
        else if (_usertype == USER)
        {
            Get();
        }
        else
        {
        }
        Attch();
    }

    void *VirtualAddr()
    {
        printf("virtual addr:%p\n",_start_mm);
        return _start_mm;
    }
    //输出共享内存的各种属性
    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);
    }
    int Size()
    {
        return _size;
    }

    ~Shm()
    {
        Destory();
    }

private:
    int _size;
    key_t _key;
    int _shmid;
    void *_start_mm;
    std::string _usertype;
};

server.cc

cpp 复制代码
#include "fifo.hpp"
#include "shm.hpp"

int main(){
    Shm sh(pathname,projid,CREATER);
    sh.Attr();

    //因为虚拟内存没有保护机制,它写的数据会直接被读出
    //所以我们使用管道来保护它的数据完整性
    FileFifo fifo(PATH,FIFONAME);

    FifoOper readerfile(PATH,FIFONAME);
    readerfile.OpenForRead();

    //读写共享内存,不用使用系统调用,使用它就像使用我们从堆栈上申请的一样
    char *mem=(char*)sh.VirtualAddr();
    while(true){
        if(readerfile.Wait()){
            printf("%s\n",mem);
        }
        else break;
    }
    readerfile.Close();
    
    return 0;
}

client.cc

cpp 复制代码
#include "fifo.hpp"
#include "shm.hpp"

int main(){
    FifoOper writerfile(PATH,FIFONAME);
    writerfile.OpenForWrite();

    Shm sh(pathname,projid,USER);

    char * mem=(char*)sh.VirtualAddr();
    int index=0;

    for(char c='A';c<='Z';c++){
        sleep(1);
        mem[index]=c;
        mem[index+1]=c;
        sleep(1);
        mem[index+2]=0;
        index+=2;

        writerfile.WakeUp();
    }

    writerfile.Close();
    return 0;
}

6.system V消息队列

●消息队列提供了一个从一个进程向另一个进程发送一块数据的方法

●每个数据块都被认为有一个类型,接收者进程接收的数据块可以有不同的类型值

●特性

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

7.system V信号量

信号量主要用于同步和互斥

7.1并发编程-前导

●多个执行流(进程),能看到的同一份公共资源:共享资源

●被保护起来的共享资源叫做临界资源

●保护的方式常见:互斥和同步

●任何时刻,只允许一个执行流访问资源,叫做互斥

●多个执行流,访问临界资源的时候,具有一定的顺序性,就叫做同步

●系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源

●在进程中涉及到互斥资源的程序段叫临界区。我们写的代码=访问临界区资源的代码(临界区)+不访问临界区资源的代码(非临界区)

●所谓对共享资源进行保护,本质是对访问共享资源的代码进行保护(约束)

7.2信号量

特性

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

理解

●信号量是一个计数器

作用

●保护临界区

本质

●信号量本质是对资源的预定机制

操作

●申请资源,计数器--,P操作

●释放资源,计数器++,V操作

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

●参考Linux2.6.18源码

相关推荐
水饺编程12 小时前
第2章:Linux 基础,切换到超级用户
linux·c语言·汇编
便利店102413 小时前
系统活动一抓留存!深入 Linux C/C++ 调试工具之 sysdig
linux·运维·服务器·sysdig
qetfw13 小时前
Bash 变量:export、位置参数与默认值展开验证
linux·开发语言·chrome·bash
其实防守也摸鱼14 小时前
Cylab在线靶场——CTF中的通用技能
linux·数据库·学习·算法·靶场·ctf·学习网络安全
toooooop814 小时前
CentOS 7 内核 3.10.0-1160.119.1 升级指南
linux·运维·centos
码少女15 小时前
Linux——UDP协议和TCP协议
linux·网络·udp·tcp
GeW15 小时前
新质生产力发展路线图Linux、数据库、AI 一个都不能少
linux
kkkkkkkkkk_Z15 小时前
新手自学嵌入式 | 学习日记:Linux驱动复习之系统移植(U-Boot篇)
linux·运维·学习
Rabitebla15 小时前
【系统性学习笔记】Linux 权限篇(二):文件属性、权限修改与目录权限的底层逻辑
linux·c++·笔记·学习·算法