Linux——进程间通信(管道)

目录

1、进程间通信介绍

进程通信的目的

2、管道

什么是管道

3、匿名管道

用fork来共享管道原理

代码实例

匿名管道特点

匿名管道的读写规则

4、命名管道

创建一个命名管道

匿名管道与命名管道的区别

代码实例


1、进程间通信介绍

进程通信的目的

数据传输:一个进程需要将它的数据发送给另一个进程

资源共享:多个进程之间共享同样的资源

通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态。


2、管道

什么是管道

管道式Unix中最古老的进程间通信的形式

我们把从一个进程到另一个进程的一个数据流称为一个"管道"


3、匿名管道

#include <unistd.h>

功能:创建一无名管道

原型

int pipe(int fd[2]);

参数

fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端

返回值:成功返回0,失败返回错误代码

用fork来共享管道原理

父进程对于管道的读写打开之后,fork出子进程,子进程是对父进程内容的一份拷贝,因此子进程对于管道的通道也是打开的,这就使父进程与子进程通过管道连接在了一起。

这里的管道其实属于半双工通信(下面介绍特点会讲),因此只能让一端读,一端写,所以上图还有点小缺陷,这里我们选择父进程读,子进程写,改进后的图如下:

像这样有着亲子关系连接的管道就是匿名管道。

代码实例
cpp 复制代码
  //实现父进程向子进程输入"i am father"
  1 #include <stdio.h>
  2 #include <unistd.h>                                                                                                                                                                                          
  3 #include <sys/wait.h>
  4 #include <stdlib.h>
  5 #include <string.h>
  6 #include <sys/types.h>
  7 
  8 int main()
  9 {
 10   int fd[2] = {0};
 11   if(pipe(fd) < 0)
 12   {
 13     perror("pipe");
 14     exit(-1);
 15   }
 16 
 17   pid_t id = fork();
 18   if (id == 0)
 19   {
 20     //child
 21     close(fd[1]); //子进程关闭写
 22   char buffer[64];
 23   while(1)
 24   {
 25     ssize_t s = read(fd[0], buffer, sizeof(buffer));
 26     if(s > 0)
 27     {
 28       buffer[s] = '\0';
 29       printf("father send to child: %s\n", buffer);
 30     }
 31     else if (s == 0)
 32     {
 33       printf("read file end!\n");
 34       break;
 35     }
 36     else 
 37     {
 38       printf("read error!\n");
 39       break;
 40     }
 41   }
 42   }
 43   //father
 44   close(fd[0]); //父进程关闭读
 45   const char* msg = "i am father";
 46   write(fd[1], msg, strlen(msg));
 47 
 48   waitpid(id, NULL, 0);
 49 
 50   return 0;
 51 }
匿名管道特点

智能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

管道提供流式服务

一般而言,进程退出,管道释放,所以管道的生命周期随进程

一般而言,内核会对管道操作进行同步于互斥

管道式半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

匿名管道的读写规则

1、当没有数据可读时

O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到由数据来到为止。

O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

2、当管道满的时候

O_NONBLOCK disable:write调用阻塞,直到有进程读走数据。

O_NONBLOCK enable:调用返回-1,errno值为EAGAIN。

3、如果所有管道写端对应的文件描述符被关闭,则read返回0。

4、如果管道读端对应的描述文件符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出。

5、当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。

6、当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。


4、命名管道

管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。

如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。

命名管道式一种特殊类型的文件。

创建一个命名管道

命名管道可以从命令行上创建,命令行方法是使用下面这个指令:

$ mkfifo filename

再用一段代码来测试一下命名管道的功能

可以看到,我们在第一个终端执行命令向fifo中写入,第二个终端执行cat命令从fifo中读取,这两个命令的进程是没有关系的

这里需要注意的是,如果先关闭了读进程,那么写就没有意义,写的进程会被直接kill掉。

命名管道也可以从程序里创建,相关的函数为:

int mkfifo(const char *filename, mode_t mode);

创建命名管道:

int main(int argc, char *argv[])

{

mkfifo("p2", 0644);

return 0;

}

匿名管道与命名管道的区别

匿名管道由pipe函数创建并打开。

命名管道由mkfifo函数创建,打开用open。

FIFO与pipe之间唯一的区别在于它们创建与打开的方式不同,一旦这些工作完成之后,它们具有相同的语义。

代码实例
cpp 复制代码
//comm.h
#pragma once
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define FILE_NAME "myfifo"
cpp 复制代码
//server.c
#include "comm.h"

int main()
{
    if(mkfifo(FILE_NAME, 0644) < 0)
    {
        perror("myfifo");
        return 1;
    }

    int fd = open(FILE_NAME, O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        return 2;
    }

    char msg[128];
    while(1)
    {
        msg[0] = 0;
        ssize_t s = read(fd, msg, sizeof(msg)-1);
        if(s > 0)
        {
            msg[s] = 0;
            printf("client# %s\n", msg);
        }
        else if(s == 0)
        {
            printf("client quit!\n");
            break;
        }
        else 
        {
            printf("read error!\n");
            break;
        }
        close(fd);

        return 0;
    }
}
cpp 复制代码
//client.c
#include "comm.h"

int main()
{
    int fd = open(FILE_NAME, O_WRONLY);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    char msg[128];
    while(1)
    {
        msg[0] = 0;
        printf("Please Enter$ ");
        fflush(stdout);
        ssize_t s = read(0, msg, sizeof(msg));
        if(s > 0)
        {
            msg[s] = 0;
            write(fd, msg, strlen(msg));
        }
    }

    close(fd);
    return 0;
}

相关推荐
2301_810746316 分钟前
CKA冲刺40天笔记 - day20-day21 SSL/TLS详解
运维·笔记·网络协议·kubernetes·ssl
❀͜͡傀儡师12 分钟前
docker 部署 komari-monitor监控
运维·docker·容器·komari
物联网软硬件开发-轨物科技38 分钟前
【轨物方案】软硬件一体赋能,开启矿山机械远程智慧运维新篇章
运维
月熊40 分钟前
在root无法通过登录界面进去时,通过原本的普通用户qiujian如何把它修改为自己指定的用户名
linux·运维·服务器
大江东去浪淘尽千古风流人物1 小时前
【DSP】向量化操作的误差来源分析及其经典解决方案
linux·运维·人工智能·算法·vr·dsp开发·mr
打码人的日常分享2 小时前
智慧城市一网统管建设方案,新型城市整体建设方案(PPT)
大数据·运维·服务器·人工智能·信息可视化·智慧城市
赖small强2 小时前
【Linux驱动开发】NOR Flash 技术原理与 Linux 系统应用全解析
linux·驱动开发·nor flash·芯片内执行
风掣长空3 小时前
Google Test (gtest) 新手完全指南:从入门到精通
运维·服务器·网络
luback3 小时前
前端对Docker简单了解
运维·docker·容器
0思必得03 小时前
[Web自动化] 开发者工具应用(Application)面板
运维·前端·python·自动化·web自动化·开发者工具