0


Linux——进程间通信

一.理解进程间通信

进程是一个独立的个体,但是近处也需要某种协同,而协同的前提就是进程间的通信。

进程间通信的前提是

先让不同的进程看到同一份操作系统提供的资源(“一段内存”),一定是某个进程先需要通信,让OS创建一个共享资源,所以OS必须提供很多的系统调用。

OS创建的共享资源的不同,系统调用接口的不同,说明进程间通信会有不同的种类。

进程通信的常见方式

  1. 消息队列
  2. 管道
  3. 共享内存
  4. 信号量

二.管道

1.匿名管道实现

匿名管道是父子进程之间进行通信的一种方式,父子进程通过对同一份文件进程读写操作,会同时得到文件的文件描述符,通过读写分离的操作,在文件的内核缓冲区——即管道中进行通信,而缓冲区里的数据不需要刷新的磁盘中

** 命令行中使用的“|”,就是匿名管道**。

**#include<unistd.h> **

//创建匿名管道

int pipe(int pipefd[2]);//输出型参数,0号下标->r,1号下标->w,创建成功返回0,失败返回-1.

#include<iostream>
#include<cerrno>
#include<cstring>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
using namespace std;

const int size = 1024;

string GetOtherMessage()
{
    static int cnt = 0;
    string messageid = to_string(cnt);
    cnt++;
    pid_t self_id = getpid();
    string stringpid = to_string(self_id);
    string message = "messageid:";
    message += messageid;
    message += "my pid is:";
    message += stringpid;
    return message;
}
void SubProcessRead(int rfd)
{
    char inbuffer[size];
    while(true)
    {
        ssize_t n = read(rfd,inbuffer,sizeof(inbuffer) - 1);//子进程收到父进程发来的消息
        if(n > 0)
        {
            inbuffer[n] = 0;
            cout << "child get message:" << inbuffer << endl;
        }
    }
}

void FatherProcessWrite(int wfd)
{
    string message = "I am father.";
    while(true)
    {
        string info = message + GetOtherMessage();//父进程发给子进程的消息
        write(wfd,info.c_str(),info.size());//写入管道时,没有写入/0
        sleep(1);
    }
}
int main()
{
    //创建管道
    int pipefd[2];
    int n = pipe(pipefd);
    if(n != 0)
    {
        cerr << "errno:" << errno << ":" 
        << "errstring:" << strerror(errno) << endl; 
    }

    //创建子进程
    pid_t id = fork();
    if(id == 0)
    {
        sleep(1);
        //子进程读
        close(pipefd[1]);
        SubProcessRead(pipefd[0]);
        close(pipefd[0]);
        exit(0);
    }
    sleep(1);
    //父进程写
    close(pipefd[0]);
    FatherProcessWrite(pipefd[1]);
    close(pipefd[1]);
    pid_t rid = waitpid(id,nullptr,0);
    if(rid > 0)
    {
        cout << "wait child process done." << endl;
    }
    return 0;
}

2.管道的五种特征

  1. 匿名管道:只用来进行具有血缘关系的进程之间 进行通信,常用于父子进程之间的通信。
  2. 管道内部,自带进程之间的同步机制。多执行流执行代码时,具有明显的顺序性。
  3. 管道文件的生命周期是随进程的。
  4. 管道文件在通信的时候,是面向字节流的。write的次数和read的次数不是一一对应的。
  5. 管道的通信模式,是一种特殊的半双工模式。

3.管道的四种情况

  1. 如果管道内部是空并且write fd没有关闭,读取条件不具备,读进程会被阻塞。可以通过使用wait函数,等待读取条件具备时再进行写入数据。
  2. 管道被写满并且read fd不读且没有关闭,管道会被写满,写进程会被阻塞。同样使用wait函数,等待数据被读取,等写条件具备再进行写入。
  3. 管道一直在读并且写端关闭了wfd,读端read返回值会读到0,表示读到了文件末尾。
  4. rfd直接关闭,写段wfd一直在进行写入,写端进程会被操作系统直接用13号信号关掉,相当于进程出现了异常。

4.命名管道

两个毫不相干的进程,可以通过文件路径来打开同一个文件,此文件路径即为命名管道

**mkfifo指令:创建一个管道文件 **

#include<sys/types.h>

#include<sys/stat.h>

*int mkfifo(const char pathname,mode_t mode)函数:创建代码级管道文件

#include<unistd.h>

*int unlink(const char pathname)函数:删除管道文件


1 .client

#include"namedPipe.hpp"

//write
int main()
{
    NamedPipe fifo(comm_path,User);
    if(fifo.OpenForWrite())
    {
        while(true)
        {
            cout << "Please Enter: ";
            string massage;
            getline(cin,massage);
            fifo.WriteNamedPipe(massage);
        }
    }
    return 0;
}

2.server

#include"namedPipe.hpp"

//read 管理命名管道的整个生命周期
int main()
{
    NamedPipe fifo(comm_path,Creater);
    if(fifo.OpenForRead())
    {
        while(true)
        {
            string massage;
            int n = fifo.ReadNamedPipe(&massage);
            if(n > 0)
            {
                cout << "Client Say: " << massage << endl;
            }
            else if(n == 0)
            {
                cout << "Client quit, Server Too!" << endl;
                break;
            }
            else
            {
                cout << "fifo.ReadNamedPipe Error" << endl;
                break;
            }
        }
    }

    return 0;
}

3. namesPipe

#pragma once

#include<iostream>
#include<cerrno>
#include<cstdio>
#include<string>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
using namespace std;

const string comm_path = "./myfifo";
#define Creater 1
#define User 2
#define DefaultFd -1
#define Read O_RDONLY
#define Write O_WRONLY
#define BaseSize 4096

class NamedPipe
{
private:
    bool OpenNamedPipe(int mode)
    {
        _fd = open(_fifo_path.c_str(),mode);
        if(_fd < 0)
            return false;
        return true;
    }
public:
    NamedPipe(const string &path,int who)
    :_fifo_path(path),_id(who),_fd(DefaultFd)
    {
        if(_id == Creater)
        {
            int res = mkfifo(_fifo_path.c_str(),0666);
            if(res != 0)
            {
                perror("mkfifo");
            }
            cout << "creater create named pipe." << endl;
        }

    }

    ~NamedPipe()
    {
        sleep(5);
        if(_id == Creater)
        {
            int res = unlink(_fifo_path.c_str());
            if(res != 0)
            {
                perror("unlink");
            }
            cout << "creater free named pipe." << endl;
        }
        if(_fd != DefaultFd) close(_fd);
    }

    bool OpenForRead()
    {
        return OpenNamedPipe(Read);
    }

    bool OpenForWrite()
    {
        return OpenNamedPipe(Write);
    }

    int ReadNamedPipe(string *out)
    {
        char buffer[BaseSize];
        int n = read(_fd,buffer,sizeof(buffer));
        if(n > 0)
        {
            buffer[n] = 0;
            *out = buffer;
        }
        return n;
    }

    int WriteNamedPipe(string &in)
    {
        return write(_fd,in.c_str(),in.size());
    }
private:
    const string _fifo_path;
    int _id;
    int _fd;
};

三.共享内存

共享内存是由OS在物理内存中申请一块空间,通过页表映射到不同进程的虚拟地址空间中,从而使不同的进程之间可以进行通信

深入理解共享内存

1.上述共享内存的各种操作,都是由OS完成的。

2.OS需要提供必要的系统调用,供用户来进行调用。

3.共享内存可以在系统中同时存在多份,供不同个数,不同对进程同时进行通信。

4.OS要对所有的共享内存进行管理,要有对应的数据结构和匹配算法。

5.共享内存 = 内存空间(数据) + 共享内存的属性。


1.共享内存实现

共享内存创建函数

#include<sys/ipc.h>

#include<sys/shm.h>

int shmget(key_t key,size_t size,int shmflg);

参数:

key,由用户给出,标识该共享内存的唯一性。

size,共享内存的大小。

shmflg,位图,拥有两种写法IPC_CREAT和IPC_EXCL。

返回值:

创建成功返回共享内存的标识符,失败返回-1。

该返回值称为shmid,是内核给用户返回的一个标识符,用来进行用户级对共享内存进行管理的id值。

IPC_CREAT:如果你创建的共享内存不存在,就创建,如果存在,获取该共享内存并返回。

IPC_EXCL:单独使用没有意义,只有和IPC_CREAT组合使用才有意义。

IPC_CREAT|IPC_EXCL:如果你要创建的共享内存不存在,就创建,如果存在,就出错返回,如果成功返回,就说明创建一个全新的共享内存。

共享内存共享函数

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

#include<sys/ipc.h>

*key_t ftok(const char pathname,int proj_id);

参数:

pathname:已存在的路径名,用于生成key值,用户随机定义。

proj_id:项目ID,用户随机定义即可。

返回值:

key:属于用户形成,内核使用的一个字段,用户不能使用key来进行shm的管理,是内核进行区分shm的唯一性标识。

** 多个不同的进程通过调用该函数,传入完全相同的参数,从而得到相同的返回值key,再作为shmgat函数的参数传入,即可完成共享内存的创建**。

共享内存控制(删除)函数

#include<sys/ipc.h>

#include<sys/shm.h>

*int shmctl(int shmid,int cmd,struct shmid_ds buf);

参数:

shmid:即共享内存唯一标识符。

cmd:指令集,其中IPC_RMID为删除共享内存,IPC_STAT为获取共享内存属性。

buf:输出型参数,获取共享内存的属性。

共享内存挂接函数

#include<sys/types.h>

#include<sys/shm.h>

**void shmat(int shmid,const void shmaddr,int shmflg):为进程挂接共享内存。

*int shmdt(const void shmaddr):为进程取消挂接共享内存。

参数:

shmid:即共享内存唯一标识符。

shmaddr:共享内存要挂接的地址,不使用置为nullptr。

shmfig:共享内存访问权限。

返回值:

viod *:成功返回共享内存在地址空间中的起始地址,失败返回nullptr。

** 共享内存的注意事项:**

共享内存不随着进程的结束而自动释放,会一直存在,直到系统重启,或手动进行释放。

共享内存是所有进程通信方式中,速度最快的,因为大大减少了数据的拷贝次数。

共享内存本身不保护共享内存数据,可以借助管道进行保护


2.IPC指令

ipcs -m:查看当前所有的共享内存

ipcrm -m + shmid:删除某共享内存


四.信号量

1.五个概念

  1. 多个执行流能看到的一份资源,称为共享资源。
  2. 被保护起来的资源称为临界资源,用互斥的方式保护的共享资源,也是临界资源。
  3. 互斥:任何时刻只能有一个进程在访问共享资源。
  4. 资源要被程序员通过代码访问。代码 = 访问共享资源的代码 + 不访问共享资源的代码
  5. 所谓对共享资源进行保护,本质是对访问共享资源的代码进行保护。

信号量的本质,就是用来保护临界资源的一个计数器,一个公共资源

对信号量的操作即PV操作,P为申请,V为释放

信号量获取函数

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key,int nsems,int shmflg);

** 信号量控制函数:**

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl(int seqid,int semnum, int cmd,...);

信号量操作函数:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

*int semop(int semid,struct sembuf sops,size_t nsops);

信号量的这些接口与共享内存类似


五.消息队列

消息队列的通信方式为:一个进程向另一个进程发送数据块的方式,所有的数据块由队列管理。

消息队列创建函数

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int shmget(key_t key,size_t size,int shmflg);

消息队列控制函数:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

*int msgctl(int msqid, int cmd, struct msqid_ds buf);

消息队列收/发函数:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

*int msgsnd(int msqid, const void msgp, size_t msgsz, int msgflg);//发送

*ssize_t msgrcv(int msqid, void msgp, size_t msgsz, long msgtyp,int msgflg);//接收

消息队列的这些接口同样与共享内存类似

标签: linux 运维 服务器

本文转载自: https://blog.csdn.net/2303_78442132/article/details/140525590
版权归原作者 很楠不爱 所有, 如有侵权,请联系我们删除。

“Linux——进程间通信”的评论:

还没有评论