Linux进程间通信

简介: Linux进程间通信

因为进程间具有独立性,你们想用进行进程间通信,难度还是比较大的。

进程间通信的本质就是让不同的进程看到同一份资源

为什么要进行进程间通信——交互数据、控制、通知等目标

进程间通信的技术背景

  1. 进程是具有独立性的。虚拟地址空间+页表 保证进程运行的独立性(进程内核数据结构+进程的代码和数据)
  2. 通信成本会比较高

进程间通信的本质理解

  1. 进程间通信的前提是让不同的进程看到同一块“内存”
  2. 所谓的同一块“内存”,不隶属任何一个进程,而是更强调共享

对于Linux原生提供的管道是匿名管道

对于管道,有入口,有出口,有一个入口,有一个出口,管道都是单向传输内容的,管道中传输的资源就是数据。管道只支持单向通信,这是设计的原因


  • 在同步的提现中,若管道所有写段关闭,则从管道中读取完所有数据后,继续read会返回0,不再阻塞;若所有读端关闭,则继续write写入会触发异常导致进程退出

创建管道的过程:

  1. 分别以读写的形式打开同一个内存
  2. fork创建子进程
  3. 双方进程各自关闭自己不需要的文件描述符

匿名管道

pipe的用法

#include <unistd.h>

int pipe(int fd[2]);

该功能是创建一个无名管道

对于参数来说:fd文件描述符,fd[0]表示读端

对于返回值来说,成功返回0,失败返回错误代码

一个简单的使用例子:

#include <iostream>
#include <cassert>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;

int main()
{
    int pipearry[2]={0};
    int s=pipe(pipearry);

    assert(s==0);
    (void)s;
    //因为12行在release中没有用
    //为了防止在运行的时候出现警告信息用了第13行的代码
#ifdef DEBUGE
    cout<<"pipearry[0]"<<pipearry[0]<<endl;
    cout<<"pipearry[1]"<<pipearry[1]<<endl;

#endif
    pid_t pid=fork();
    if(pid==0)//child,关闭写端——pipearry[1]
    {
        //接受消息
        close(pipearry[1]);
        char buffer[1000];
        while(true)
        {
            ssize_t  ret=read(pipearry[0],buffer,sizeof(buffer));
            if(ret>0){
                buffer[ret]=0;
                cout<<"接收成功:接收的数据为->"<<buffer<<endl;
            }
            else if(ret==0){
                cout<<"父进程退出,子进程马上退出"<<endl;
                break;
            }
            else{
                cout<<"异常错误"<<endl;
                break;
            }
        }
        exit(0);

    }
    //father,关闭读端——pipearry[0]
    //写信息
    close(pipearry[0]);
    char buffer[]="我是父进程";
    int count=0;
    while(true){
        ssize_t ret = write(pipearry[1],buffer,sizeof(buffer));
        cout<<buffer<<':'<<count++<< "子进程pid"<<pid<< "父进程pid"<<getpid()<<endl;
        sleep(1);
        if(count==5){
            cout<<"父进程退出"<<endl;
            break;
        }
    }
    close(pipearry[1]);
    pid_t n= waitpid(pid,nullptr,0);
    cout<<"pid:"<<pid<<"n:"<<n<<endl;

    return 0;
}

总结管道的特点

  1. 管道是用来进行血缘关系的进程进行进程间的通信——常用于父子通信
  2. 管道具有通过让进程间协同,提供了访问控制!
  3. 写快,读慢,写满就不能在写了
  4. 写慢,读快,管道没有数据的时候,读必须等待
  5. 写关,读0,标识读到了文件结尾
  6. 读关,写继续写,os将中止写进程
  1. 管道提供的是面向流式的通信服务——面向字节流——协议
  2. 管道是基于文件的,文件的生命周期是随进程的,管道的生命周期是随进程的
  3. 管道是单向通信的,就是半双工通信是一种特殊情况

命名管道

命名管道说明该管道是有名字的,它和匿名管道的区别就是,匿名管道是内存基本的,在磁盘上没有存储,而命名管道在磁盘是存在的,但是里面是没有内容的。

这样双方进程就可以看见同一份资源了

管道的本质是内核中的缓冲区,命名管道文件是缓冲区的标识

管道的生命周期随进程,本质是内核中的缓冲区,命名管道文件只是标识,用于让多个进程找到同一块缓冲区,删除后,之前已经打开管道的进程依然可以通信。

创建一个命名管道

命令行创建

mkfifo 文件名

创建:

发送:

接受:

上面就是在命令行中完成的操作

在程序中创建

我们用下面这个函数

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

int mkfifo(const char *pathname, mode_t mode);
/*
pathname是路径名
mode是权限
返回值:成功返回0,错误返回-1、并设置错误码
*/

下面用命名管道实现server&client通信

Makefile

自动化编译

.PHONY:all
all:server client

server:server.cpp
  g++ -o $@ $^ -std=c++11
client:client.cpp
  g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
  rm -f server client

头文件包含相应的头:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

#define FILEPATH "./namepipe"
#define MODE 0666
#define SIZE 512

server.cpp

创建namepipe匿名管道

接收来自用户端发来的消息

#include "common.hpp"

void getmessage(int fd)
{
    char buffer[SIZE]={0};
    while(true){
        ssize_t ret = read(fd,buffer,sizeof buffer);
        if(ret>0){
            std::cout<<"读取成功:pid为:"<<getpid()<<"内容为"<<buffer<<std::endl;
        }
        else if(ret==0){
            std::cout<<"读取完毕"<<"进程pid:"<<getpid()<<"退出"<<std::endl;
            break;
        }
        else{
            perror("读取错误");
            exit(1);
        }
    }
}

int main()
{
    if(mkfifo(FILEPATH,MODE)<0){
        perror("创建命名管道失败");
        exit(1);
    }
    //创建成功,打开文件
    int fd=open(FILEPATH,O_RDONLY);
    if (fd < 0)
    {
        perror("open");
        exit(1);
    }
    //创建3个子进程
    int n=3;
    for(int i=0;i<n;i++){
        pid_t pid=fork();
        if(pid==0){
            // 接收客户端发送的消息
            getmessage(fd);
            exit(3);
        }
    }
    //等待子进程退出
    for(int i=0;i<n;i++){
        waitpid(-1,nullptr,0);
    }
    close(fd);
    //删除命名管道
    std::cout<<"删除命名管道"<<std::endl;
    unlink(FILEPATH);
    return 0;
}

client.cpp

向服务器端发送消息

#include "common.hpp"


int main()
{
    int fd=open(FILEPATH,O_WRONLY);
    if(fd<0){
        perror("open");
        exit(1);
    }
    char buffer[SIZE]={0};
    while(true){
        std::cout<<"请输入你发送的信息";
        std::cin>>buffer;
        ssize_t ret = write(fd,buffer,sizeof buffer);
        if(ret<0){
            std::cout<<"写入数据错误"<<std::endl;
            break;
        }
    }
    close(fd);
    return 0;
}

删除文件

下面这个删除文件本质上是删除的该文件的链接个数

#include <unistd.h>

int unlink(const char *pathname);

system V共享内存

共享内存区是最快的IPC(进程间通信)形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核 ,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。共享内存是在用户空间中,可以直接像使用数组一样使用该空间


共享内存在哪个区域呢?——堆栈相对生长的区域就是共享内存的区域

既然是共享内存,需要os进行管理,那么就应该有它的数据结构


共享内存没有进行同步与互斥(共享内存缺乏访问控制),所以可能会出现,写入的数据的时候,还没有读取的时候就进行再一次的写入,就会读取不到原来的数据;或者是没有写完就进行读取,会导致读取的数据不完整


共享内存的数据结构为:

共享内存的创建和使用

先介绍几个函数

#include <sys/shm.h>
//用来创建共享内存
int shmget(key_t key, size_t size, int shmflg);
/*
key:共享内存段的名字,key_t 是int类型
size:共享内存大小

shmflg:用法和创建mode模式的标志一样
IPC_CREAT——>如果存在获取并返回,如果不存在,创建并返回
IPC_CREAT | IPC_EXCL——>如果底层不存在,创建并返回;如果存在,出错并返回
还可以再加权限。

返回值:成功返回非负整数,表示该共享内存的标识码,失败返回-1
*/
#include <sys/shm.h>
//将共享内存段连接到进程地址空间
void *shmat(int shmid, const void *shmaddr, int shmflg);
/*
shmid:共享内存的标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND(0)和SHM_RDONLY(非0)
返回值:
成功返回一个指针,指向共享内存第一个节;失败返回-1

参数的使用:
shmaddr为null,核心自动选择一个地址
shmaddr不为null且shmflg无SHM_RND标记,则以shmaddr为连接地址
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - 
(shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存。
*/
#include <sys/shm.h>
//将共享内存段与当前进程脱离
int shmdt(const void *shmaddr);
/*
shmaddr:由shmat所返回的指针
返回值:成功返回0,失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
*/
#include <sys/shm.h>
//控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
/*
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作——>IPC_STAT,IPC_SET,IPC_RMID
IPC_RMID表示的是删除共享内存段
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
就是共享内存数据结构的指针
返回值:成功返回0;失败返回-1
*/

server&client演示

我们想要client和server打开同一个共享内存,那么就要保证key一致,除了我们手动写死一个key的方式外,我们还可以用下面这个函数生成。

#include <sys/ipc.h>
//将路径名和项目标识符转换成system V的key
key_t ftok(const char *pathname, int proj_id);
/*  
pathname:路径名
proj_id:项目标识符
返回值:成功返回一个key_t值,失败时返回-1.
*/

Makefile

.PHONY:all
all:shmserver shmclient

shmserver:shmserver.cpp
  g++ -o $@ $^ -std=c++11
shmclient:shmclient.cpp
  g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
  rm -f shmserver shmclient

common.hpp

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstring>


#define FILEPATH "/home/lighthouse/studycode/"
#define FILENAME "./controlpipe"
// #define PROJID 0x888
#define PROJID 0x66
#define MODE 0666
#define SIZE 4096

using namespace std;
//创建命名管道,用于控制共享内存
class Init
{
public:
    Init()
    {
        int ret = mkfifo(FILENAME,MODE);
        if(ret<0){
            perror("命名管道");
            exit(-1);
        }
        cout<<"创建命名管道成功,用来对共享内存进行控制"<<endl;

    }
    ~Init()
    {
        unlink(FILENAME);
        cout<<"删除命名管道"<<endl;
    }
};
#define READ O_RDONLY
#define WRITE O_WRONLY
int OpenGFile(int flag)
{
    int fd = open(FILENAME, flag);
    if (fd < 0)
    {
        perror("open error");
        exit(-1);
    }
    return fd;
}
void Wait(int fd)
{
    cout<<"等待读取中……"<<endl;
    int datatemp=0;
    read(fd,&datatemp,sizeof(int));
    
}
void Wakeup(int fd)
{
    cout<<"读取中……"<<endl;
    int datatemp=0;
    write(fd,&datatemp,sizeof(int));
    
}
void Close(int fd)
{
    close(fd);
    cout<<"文件关闭成功"<<endl;
}

shmserver.cpp

#include "common.hpp"

void getmessage(int fd)
{
    char buffer[SIZE]={0};
    while(true){
        ssize_t ret = read(fd,buffer,sizeof buffer);
        if(ret>0){
            std::cout<<"读取成功:pid为:"<<getpid()<<"内容为"<<buffer<<std::endl;
        }
        else if(ret==0){
            std::cout<<"读取完毕"<<"进程pid:"<<getpid()<<"退出"<<std::endl;
            break;
        }
        else{
            perror("读取错误");
            exit(1);
        }
    }
}
Init init;

int main()
{
    //得到共享内存段的名字
    key_t key = ftok(FILEPATH,PROJID);
    if(key<0){
        perror("key");
        exit(1);
    }
    std::cout<<"得到共享内存段的名字key:"<<key<<std::endl;

    //创建一个全新的共享内存
    //MODE是添加的权限
    int shmid = shmget(key,SIZE,IPC_CREAT|IPC_EXCL|MODE);
    if(shmid<0){
        perror("create share memory");
        exit(2);
    }
    std::cout<<"创建一个全新的共享内存shmid:"<<shmid<<std::endl;
    //链接进程地址
    char* shmaddr = (char*)shmat(shmid,NULL,SHM_RND);
    if(shmaddr<0){
        perror("shmaddr error");
        exit(3);
    }
    std::cout<<"链接进程地址shmaddr:"<<(void*)shmaddr<<std::endl;
    
    //通信代码,接收信息,有信息才进行读取
    int fd = OpenGFile(READ);
    while(true)
    {
        Wait(fd);
        cout<<shmaddr<<endl;
        if(strcmp(shmaddr,"quit")==0)
        break;
    }
    Close(fd);
    // for(int i=0;i<5;i++){
    //     cout<<shmaddr<<endl;
    //     sleep(3);
    // }

    //断开与进程的连接
    int ret = shmdt(shmaddr);
    if(ret<0){
        perror("shmdt error");
        exit(4);
    }
    std::cout<<"断开与进程的连接"<<std::endl;

    //删除共享内存空间
    ret = shmctl(shmid,IPC_RMID,NULL);
    if (ret < 0)
    {
        perror("shmctl error");
        exit(5);
    }
    std::cout<<"删除共享内存空间"<<std::endl;
    return 0;
}

客户端不需要删除共享内存

shmclient.cpp

#include "common.hpp"


int main()
{
    key_t key = ftok(FILEPATH, PROJID);
    if (key < 0)
    {
        perror("key");
        exit(1);
    }
    std::cout << "得到共享内存段的名字key:" << key << std::endl;

    //不需要创建,获得共享内存的标识符即可
    int shmid = shmget(key, SIZE, 0);//0只是为了获得shmid
    if (shmid < 0)
    {
        perror("create share memory");
        exit(2);
    }
    std::cout << "得到共享内存shmid:" << shmid << std::endl;

    // 链接进程地址
    char* shmaddr = (char*)shmat(shmid, NULL, SHM_RND);
    if (shmaddr < 0)
    {
        perror("shmaddr error");
        exit(3);
    }
    std::cout << "链接进程地址shmaddr:" << (void*)shmaddr << std::endl;
    // 通信代码
    int fd = OpenGFile(WRITE);

    while(true)
    {

        cin>>shmaddr;
        Wakeup(fd);
        if (strcmp(shmaddr, "quit") == 0)
            break;
    }
    Close(fd);

    // for(int i=0;i<5;i++){
    //     cin>>shmaddr;
    // }
    // 断开与进程的连接
    int ret = shmdt(shmaddr);
    if (ret < 0)
    {
        perror("shmdt error");
        exit(4);
    }
    std::cout << "断开与进程的连接" << std::endl;

    //客户端不需要删除共享内存

    return 0;
}

查看共享内存的信息。

ipcs -m

删除共享内存资源

ipcrm -m shmid编号——>这是通过命令行的形式来删除的查看共享内存的信息。

ipcs -m

删除共享内存资源

ipcrm -m shmid编号——>这是通过命令行的形式来删除的

相关文章
|
8天前
|
消息中间件 存储 缓存
【嵌入式软件工程师面经】Linux系统编程(线程进程)
【嵌入式软件工程师面经】Linux系统编程(线程进程)
20 1
|
12天前
|
Linux Shell C语言
【Linux】进程终止
【Linux】进程终止
|
1天前
|
Linux Shell C语言
Linux进程控制——Linux进程程序替换
Linux进程控制——Linux进程程序替换
|
1天前
|
Linux 调度
Linux进程控制——Linux进程等待
Linux进程控制——Linux进程等待
|
1天前
|
存储 缓存 Linux
Linux进程控制——Linux进程终止
Linux进程控制——Linux进程终止
|
1天前
|
安全 Linux 编译器
Linux进程——进程地址空间
Linux进程——进程地址空间
|
1天前
|
Linux Shell 编译器
Linux进程——Linux环境变量
Linux进程——Linux环境变量
|
1天前
|
Linux 调度
Linux进程——Linux进程间切换与命令行参数
Linux进程——Linux进程间切换与命令行参数
|
1天前
|
Linux 调度
Linux进程——Linux进程与进程优先级
Linux进程——Linux进程与进程优先级
|
1天前
|
Linux Shell 调度
Linux进程——Linux下常见的进程状态
Linux进程——Linux下常见的进程状态