Linux进程通信之共享内存与信号量

发布时间:2024年01月15日

(。・?・)ノ゙嗨!你好这里是ky233的主页:这里是ky233的主页,欢迎光临~icon-default.png?t=N7T8https://blog.csdn.net/ky233?type=blog

点个关注不迷路?'?'?

目录

一、共享内存

1.原理

2.建立

3.shmget和ftok

4.ftok的用法

5.shmctl的用法

6.shmat的用法

7.通信代码

8.结论

9.用管道来帮助共享内存进行访问控制

二、信号量

1.临界资源、临界区、互斥

2.原子性


一、共享内存

1.原理

共享内存顾名思义,就是在内存中堆区和栈区相对,中间还有一个共享内存区,两个进程同时访问,即可完成进程之间的通信!

这里要注意!

我们开辟的共享内存是在虚拟内存上开辟然后在,然后映射到真是的物理内存的,所以双方要进行进程通信直接用内存级别的读写就可以了,这是在堆栈之间开辟的,无需经过系统调用,而管道本质上还是经过文件,这是要有对应的内核数据结构,所以管道要用操作系统来调用!?

2.建立

首先共享内存的提供者是操作系统,共享内存也是要经过OS管理的还是经过先描述在组织

所以共享内存=共享内存块+对应的共享内存的内核数据结构

3.shmget和ftok

int shmget(key_t key,size_t size,int shmflg)
  • 参数1:两个进程只要是同一个key就可以看到同一个共享内存
  • 参数2:需要的共享内存有多大
  • 参数3:两个选项,IPC_CREAT(创建内存,如果底层已经存在,就直接获取并返回),IPC_EXCL(单独使用无意义,合起来使用如果底层不存在,则创建并返回,如存在则出错返回,则一定创建一个全新的共享内存)
  • 返回值:类似于fd
key_t ftok(const char *pathname,int proi id)

用此函数来形成key值

  • 参数1:路径
  • 参数2:0~255的任意数

只要两个进程使用同样的pathname,和id那么就会生成唯一key

4.ftok的用法

Server:

int main()
{
    key_t k = ftok(PATH_NAME,PROJ_ID);
    Log("create key done",Debug)<<"server key:"<<k<<endl;
    return 0;
}

Client:?

int main()
{
    key_t k = ftok(PATH_NAME,PROJ_ID);
    Log("create key done",Debug)<<"client key:"<<k<<endl;
    return 0;
}

?由此我们可以看见两个进程获取的是同一个key值,而且是一个随机值

5.shmctl的用法

当进程运行结束,我们的共享内存还存在!system V IPC资源的生命周期是随我们内核的!

要想删除两种办法

  • 1:手动删除
  • 2:代码删除

手动删除太麻烦,所以我们可以用shmctl来删除

int shmctl(int shmid,int cmd,struct shmid_ds *buf)
  • 参数一:就是shmget创建的id
  • 参数二:设置一些属性,如删除
  • 参数三:今天我们只是删除,所以设为空即可

6.shmat的用法

我们创建完共享内存之后需要挂接到我们需要的进程的地址空间中,所以我们用这个函数

void *shmat(int shmid,const void *shmaddr,int shmflg)
  • ?参数一:对应共享内存的id
  • 参数二:所要指定的虚拟地址,但一般不建议自己设置一般设为nullptr
  • 参数三:一些挂接方式,如只读、或其他方式,今天这里设为0
  • 返回值:成功为虚拟地址,失败为-1

7.shmdt的用法

int shmdt(const void *shmaddr)
  • ?参数一:shmat的返回值
  • 返回值:成功0,失败-1;

7.通信代码

shmServer:

#include "comm.hpp"
#include "Log.hpp"

string TransToHex(key_t &k)
{
    char buffer[32];
    snprintf(buffer, sizeof buffer, "0x%x", k);
    return buffer;
}

int main()
{
    // 创建key值
    key_t k = ftok(PATH_NAME, PROJ_ID);
    assert(k != -1);
    Log("create key done", Debug) << "server key:" << TransToHex(k) << endl;

    // 创建共享内存
    int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid == -1)
    {
        perror("shmget");
        exit(1);
    }
    Log("create shm done", Debug) << "shmid:" << shmid << endl;

    // 将指定的共享内存挂接到自己的地址空间
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    Log("attach shm done", Debug) << " shmid : " << shmid << endl;

    // 进行通信
    for (;;)
    {
        cout << shmaddr << endl;
        if(strcmp(shmaddr,"quit")==0)
            break;
        sleep(1);
    }

    // 将共享内存去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    (void)n;
    Log("detach shm done", Debug) << " shmid : " << shmid << endl;

    // 删除共享内存
    n = shmctl(shmid, IPC_RMID, nullptr);
    assert(n != -1);
    (void)n;
    Log("delete shm done", Debug) << " shmid : " << shmid << endl;
    return 0;
}

shmClient:

#include "comm.hpp"
#include "Log.hpp"

int main()
{
    key_t k = ftok(PATH_NAME, PROJ_ID);
    if (k < 0)
    {
        Log("create key failed", Error) << " client key : " << k << endl;
        exit(1);
    }
    Log("create key done", Debug) << " client key : " << k << endl;

    // 获取共享内存
    int shmid = shmget(k, SHM_SIZE, 0);
    if (shmid < 0)
    {
        Log("create shm failed", Error) << " client key : " << k << endl;
        exit(2);
    }
    Log("create shm success", Error) << " client key : " << k << endl;

    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    if (shmaddr == nullptr)
    {
        Log("attach shm failed", Error) << " client key : " << k << endl;
        exit(3);
    }
    Log("attach shm success", Error) << " client key : " << k << endl;

    // 使用
    // 看做char类型的buffer
    char a = 'a';
    for (; a < 'z'; a++)
    {
        snprintf(shmaddr, SHM_SIZE - 1, "你好,这是我的pid:%d,inc:%c", getpid(), a);
        sleep(2);
    }
    strcpy(shmaddr,"quit");

    // 去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    Log("detach shm success", Error) << " client key : " << k << endl;

    return 0;
}

由此可见,我们的两个进程利用共享内存来完成了通信!!!

8.结论

  • 只要是通信双方使用共享内存,乙方向共享内存中写入数据,另一方,就可以立马看到,共享内存是所有进程间通信速度最快的,这是因为不需要过多的拷贝!不需要将数据交给操作内存!!
  • 共享内存缺乏访问控制!无论是否有数据,读端一直在读!甚至读取和写入放都不知道对方的存在!会带来并发问题。

9.用管道来帮助共享内存进行访问控制

我们可以封装一个管道,以此来进行控制!

comm.hpp:

#pragma once

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"

using namespace std;

#define PATH_NAME "/home/ky"
#define PROJ_ID 0x66
#define SHM_SIZE 4096

#define FIFO_NAME "./fifo"

class Init
{
public:
    Init()
    {
        umask(0);
        int n = mkfifo(FIFO_NAME, 0666);
        assert(n == 0);
        (void)n;
        Log("create fifo success", Notice) << "\n";
    }
    ~Init()
    {
        unlink(FIFO_NAME);
        Log("remove fifo success", Notice) << "\n";
    }
};

#define READ O_RDONLY
#define WRITE O_WRONLY

int OpenFIFO(std::string pathname, int flags)
{
    int fd = open(pathname.c_str(), flags);
    assert(fd >= 0);
    return fd;
}

void Wait(int fd)
{
    Log("等待中....", Notice) << "\n";
    uint32_t temp = 0;
    ssize_t s = read(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;
}

void Signal(int fd)
{
    uint32_t temp = 1;
    ssize_t s = write(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;
    Log("唤醒中....", Notice) << "\n";
}

void CloseFifo(int fd)
{
    close(fd);
}

shmClient:

#include "comm.hpp"
#include "Log.hpp"

int main()
{
    key_t k = ftok(PATH_NAME, PROJ_ID);
    if (k < 0)
    {
        Log("create key failed", Error) << " client key : " << k << endl;
        exit(1);
    }
    Log("create key done", Debug) << " client key : " << k << endl;

    // 获取共享内存
    int shmid = shmget(k, SHM_SIZE, 0);
    if (shmid < 0)
    {
        Log("create shm failed", Error) << " client key : " << k << endl;
        exit(2);
    }
    Log("create shm success", Error) << " client key : " << k << endl;

    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    if (shmaddr == nullptr)
    {
        Log("attach shm failed", Error) << " client key : " << k << endl;
        exit(3);
    }
    Log("attach shm success", Error) << " client key : " << k << endl;

    //打开管道文件
    int fd = OpenFIFO(FIFO_NAME, WRITE);

    // 使用
    // 看做char类型的buffer
    // char a = 'a';
    // for (; a < 'z'; a++)
    // {
    //     snprintf(shmaddr, SHM_SIZE - 1, "你好,这是我的pid:%d,inc:%c", getpid(), a);
    //     sleep(2);
    // }

    // strcpy(shmaddr,"quit");

    while(1)
    {
        ssize_t s = read(0, shmaddr, SHM_SIZE-1);
        if(s > 0)
        {
            shmaddr[s-1] = 0;
            Signal(fd);
            if(strcmp(shmaddr,"quit") == 0) break;
        }
    }

    CloseFifo(fd);
    // 去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    Log("detach shm success", Error) << " client key : " << k << endl;

    return 0;
}

shmServer:

#include "comm.hpp"
#include "Log.hpp"

Init init; 

string TransToHex(key_t &k)
{
    char buffer[32];
    snprintf(buffer, sizeof buffer, "0x%x", k);
    return buffer;
}

int main()
{
    // 创建key值
    key_t k = ftok(PATH_NAME, PROJ_ID);
    assert(k != -1);
    Log("create key done", Debug) << "server key:" << TransToHex(k) << endl;

    // 创建共享内存
    int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid == -1)
    {
        perror("shmget");
        exit(1);
    }
    Log("create shm done", Debug) << "shmid:" << shmid << endl;

    // 将指定的共享内存挂接到自己的地址空间
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    Log("attach shm done", Debug) << " shmid : " << shmid << endl;


    //打开管道文件
    int fd = OpenFIFO(FIFO_NAME, READ);
    // 进行通信
     for(;;)
    {
        //进行等待
        Wait(fd);

        // 临界区
        printf("%s\n", shmaddr);
        if(strcmp(shmaddr, "quit") == 0) break;
        // sleep(1);
    }

    // 将共享内存去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    (void)n;
    Log("detach shm done", Debug) << " shmid : " << shmid << endl;

    // 删除共享内存
    n = shmctl(shmid, IPC_RMID, nullptr);
    assert(n != -1);
    (void)n;
    Log("delete shm done", Debug) << " shmid : " << shmid << endl;

    CloseFifo(fd);
    return 0;
}

这样我们就利用管道完成了对内存共享的访问控制!!!

二、信号量

1.临界资源、临界区、互斥

我们为了让进程间通信,本质上都是解决同一个问题,那就是让不同的进程看到同一份资源!

但这就会造成一些问题,比如共享内存带来了一些时序问题,造成数据不一致问题!

  • 临界资源:我们把多个进程(执行流)看到的公共的一份资源叫做临界资源
  • 临界区:我们把自己的进程,访问临界资源的代码叫做临界区

所以当我们的多个执行流运行互相干扰的时候,主要是我们不加保护的访问了同样的资源(临界资源),所以在非临界区我们的多个执行流是不影响的!!!?

  • 互斥:为了更好的进行对临界区的保护,可以让任何时刻,都只能有一个进程进入临界区!这种行为我们称之为互斥

2.原子性

我们的临界资源可能会分有好几个部分,当每一个进程想要进入临界资源的是偶,访问临界资源的一部分,不能让进程直接去使用临界资源,要先去申请信号量

  • 信号量本质上是一个计数器
  • 申请信号量的本质就是让信号量计数器--
  • 只要申请信号量成功,临界资源内部一定给你预留了想要的资源,本质上申请信号量是对临界资源的一种预定机制
  • 如所有的临界资源都被申请,那么如果再有进程想要申请临界资源的话就会进入阻塞状态等待其他进程访问完毕,释放信号量之后才能进入!

但是把信号量本质上是计数器但是当作一个计数器是不准确的,今日姑且这样理解,因为两个进程根本没办法看到同一个全局变量,即便看到了,也是不可以的

首先cpu执行指令的时候,会先将内存的数据加遭到cpu内的寄存器中,然后执行指令,将cpu修改完毕的数据写回内存。但是执行流在执行的时候,在任何时刻都可能被切换,寄存器只有一套,被所有的执行流共享,但是寄存器里面的数据属于每一个执行流,是属于该执行流的上下文数据!

那么即便可以看到同一个全局变量,但是因为时序、并发问题,n会有中间状态,会导致不一致,是不安全的。

所以,如果n--的汇编代码只有一行,我们就称之为原子的

  • 原子性:要么不做,要么做完,没有中间状态就成为原子性!?

所以,所以要想多个进程同时看到以及保证不会被切换,所以这个变量必须要放到内存空间中!也必须是原子的!!

信号计数器

申请信号量->计数器--?->p操作->必须是原子的

释放信号量->计数器++?->v操作->必须是原子

所以信号量是对临界资源的预定机制!!!

文章来源:https://blog.csdn.net/ky233/article/details/134274547
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。