LinuxC编程——进程间通信(一)(管道)

这篇具有很好参考价值的文章主要介绍了LinuxC编程——进程间通信(一)(管道)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

复杂的编程环境通常使用多个相关的进程来执行有关操作。进程之间必须进行通信,来共享资源和信息。因此,要求内核提供必要的机制,这些机制通常称为进程间通信(InterProcess Communication, IPC)。

一、Linux平台通信方式发展史

  1. 早期通信方式:早期的Unix IPC包括管道、FIFO和信号
  2. AT&T的贝尔实验室,对Unix早期的进程间通信进行了改进和扩充,形成了“system V IPC”,其通信进程主要局限在单个计算机内。
  3. BSD(加州大学伯克利分校的伯克利软件发布中心),跳过了只能在同一计算机通信的限制,形成了基于套接字(socket)的进程间通信机制。

二、进程间通信方式⭐⭐⭐

  1. 早期通信:无名管道(pipe),有名管道(fifo)、信号(sem)
  2. system V IPC:共享内存(share memory) 、信号灯集(semaphore)、消息队列(message queue)
  3. BSD:套接字(socket)

三、无名管道

3.1 特点⭐⭐⭐

  1. 只能用于具有亲缘关系的进程间进行通信
  2. 半双工通信,具有固定的读端与写端
    (单工:只能单方面传输信息->广播
    半双工:可以双向,但是同一时间只能一个方向传输信息
    全双工:可以双向同时传输信息)
  3. 无名管道可以看作一种特殊的文件,对它的读写采用文件IO:read、write
  4. 管道是基于文件描述符通信方式。当一个无名管道创建会自动创建两个文件描述符,分别的fd[0]、fd[1],其中fd[0]固定的读端,fd[1]固定的写端

3.2 函数pipe

int pipe(int fd[2])

  • 功能:创建无名管道
  • 参数:文件描述符(fd[0]:读端 fd[1]:写端)
  • 返回值:成功 0;失败 -1
    注📢:管道要用文件I/O进行操作(read,write,close)且管道创建后,fd[0]=3,fd[1]=4
    例:
    LinuxC编程——进程间通信(一)(管道),I/O&进程&线程,linux,c语言,笔记,嵌入式开发,进程,管道
    LinuxC编程——进程间通信(一)(管道),I/O&进程&线程,linux,c语言,笔记,嵌入式开发,进程,管道

3.3 注意事项⭐⭐⭐

  1. 当管道中无数据时,读操作会阻塞;管道中无数据,将写端关闭,读操作会立即返回
  2. 管道中装满(管道大小64K)数据写阻塞,一旦有4k空间,写继续,直到写满为止
  3. 只有在管道的读端存在时,向管道中写入数据才有意义。否则,会导致管道破裂,向管道中写入数据的进程将收到内核传来的SIGPIPE信号 (通常Broken pipe错误)。(GDB调试可以查看到)

代码示例:

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    int fd[2] = {0};
    if (pipe(fd) < 0)
    {
        perror("pipe error");
        return -1;
    }
    printf("fd[0]:%d  fd[1]:%d\n", fd[0], fd[1]);

    char buf1[32] = {"hello world!"};
    char buf2[32] = {0};
    // write(fd[1],buf1,strlen(buf1)); //往管道写入buf1
    // ssize_t s = read(fd[0],buf2,32);  //从管道读取数据到buf2
    // printf("%s %d\n",buf2,s);
    // close(fd[0]);
    // close(fd[1]);
#if 0
    // 1.管道中无数据,读阻塞
    read(fd[0], buf2, 32);
#endif
#if 0
    // 2.将写端关闭,读操作会立即返回
    close(fd[1]);
    read(fd[0],buf2,32);
#endif
#if 1
    //3.1 当无名管道中写满数据64k,写阻塞
    char buf[65536] = {0};
    write(fd[1], buf, 65536);
    printf("full\n");
    write(fd[1], "a", 1);
    printf("write a ok\n");
    //至少读出4k的空间,才能继续写
    read(fd[0], buf, 4095);
    write(fd[1], 'a', 1);
    printf("write 'a' ok\n");
#endif
#if 1
    // 3.1 将读端关闭,继续写
    close(fd[0]);
    write(fd[1], "a", 1);
    printf("ok...\n");
#endif
    // close(fd[0]);
    // close(fd[1]);
    return 0;
}

第三种情况管道破裂,通过GDB调试查看到的结果如下:
LinuxC编程——进程间通信(一)(管道),I/O&amp;进程&amp;线程,linux,c语言,笔记,嵌入式开发,进程,管道

3.4 练习

父子进程实现通信,父进程循环从终端输入数据,子进程循环打印数据,输入一次打印一次,当输入quit结束,使用无名管道

/*
  练习:父子进程实现通信,父进程循环从终端输入数据,
  子进程循环打印数据,当输入quit结束,使用无名管道
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char const *argv[])
{
    char buf[32] = {0};
    int fd[2] = {0};
    if(pipe(fd)<0) //创建无名管道
    {
        perror("pipe err");
        return -1;
    }
    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork err");
        return -1;
    }
    else if(pid == 0)
    {
        while (1) //子进程循环从管道读取数据,管道为空阻塞
        {
            read(fd[0],buf,32);
            if(strcmp(buf,"quit")==0)
                exit(0);
            printf("%s\n",buf);
        }  
    }
    else
    {
        while(1)//循环从终端输入数据,循环往管道写入数据
        {
            //scanf("%s",buf);
            fgets(buf,32,stdin);
            if(buf[strlen(buf)-1] == '\n')
                buf[strlen(buf)-1] = '\0';
            write(fd[1],buf,strlen(buf)+1);
            if(strcmp(buf,"quit")==0)
                exit(0);
        }
        wait(NULL);
    }
    close(fd[0]);
    close(fd[1]);
    return 0;
}

四、有名管道

4.1 特点⭐⭐⭐

  1. 可以用于两个不相关的进程之间通信
  2. 有名管道可以通过路径名指出,在文件系统中可见,但内容存放在内存里
  3. 通过文件IO操作
  4. 遵循先进先出,故不支持lseek操作
  5. 半双工通信

4.2 函数 mkfifo

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

  • 功能:创健有名管道
  • 参数:
    • filename:有名管道文件名
    • mode:权限
  • 返回值:成功:0;失败:-1,并设置errno号
    注意对错误的处理方式:📢📢
    如果错误是file exist时,注意加判断,如:if(errno == EEXIST)
    执行如下代码:
    LinuxC编程——进程间通信(一)(管道),I/O&amp;进程&amp;线程,linux,c语言,笔记,嵌入式开发,进程,管道
    第一次运行:
    LinuxC编程——进程间通信(一)(管道),I/O&amp;进程&amp;线程,linux,c语言,笔记,嵌入式开发,进程,管道
    再次运行:
    LinuxC编程——进程间通信(一)(管道),I/O&amp;进程&amp;线程,linux,c语言,笔记,嵌入式开发,进程,管道
    处理方式:捕捉错误码,进行过滤即可👇
    LinuxC编程——进程间通信(一)(管道),I/O&amp;进程&amp;线程,linux,c语言,笔记,嵌入式开发,进程,管道
  1. 由上面的有名管道特点的第二条可以知道,写入有名管道的内容并非存放在文件中,而是存在内存,也就是说有名管道文件的大小为0,下面进行验证:
    LinuxC编程——进程间通信(一)(管道),I/O&amp;进程&amp;线程,linux,c语言,笔记,嵌入式开发,进程,管道
    在写后面加一个while死循环,运行后会等写完阻塞,不读出数据。然后在终端可以查看当前管道文件的大小,结果是大小为0👇
    LinuxC编程——进程间通信(一)(管道),I/O&amp;进程&amp;线程,linux,c语言,笔记,嵌入式开发,进程,管道

4.3 注意事项⭐⭐

  1. 只写方式,写阻塞,直到另一个进程将读打开
  2. 只读方式,读阻塞,直到另一个进程将写打开
  3. 可读可写,管道中无数据,读阻塞。

验证1,2:
创建两个c文件,一个以只读方式打开有名管道,从中读数据;另一个以只写方式打开同一个有名管道,从中写数据。
只读
LinuxC编程——进程间通信(一)(管道),I/O&amp;进程&amp;线程,linux,c语言,笔记,嵌入式开发,进程,管道
只写
LinuxC编程——进程间通信(一)(管道),I/O&amp;进程&amp;线程,linux,c语言,笔记,嵌入式开发,进程,管道
通过运行可以看到,运行了其中任意一个,会发生阻塞,只有当再运行另一个才可以解除阻塞,两个程序得以顺利执行下去。验证了只写方式,写阻塞,直到另一个进程将读打开只读方式,读阻塞,直到另一个进程将写打开。
还可以得知,上面程序并不是在read或iwrite发生阻塞,而是在open函数处发生了阻塞

补充:如果所有写进程都关闭命名管道,则只读进程的读操作会认为到达文件末尾,读操作解除阻塞并返回
验证:
以只读方式打开有名管道的程序代码1.c👇
LinuxC编程——进程间通信(一)(管道),I/O&amp;进程&amp;线程,linux,c语言,笔记,嵌入式开发,进程,管道
以只写方式打开有名管道程序代码2.c👇
LinuxC编程——进程间通信(一)(管道),I/O&amp;进程&amp;线程,linux,c语言,笔记,嵌入式开发,进程,管道
先执行1.c,会发生阻塞;再执行2.c,2.c不会往管道写入数据(保证1.c不会因为管道中有数据而解除阻塞),2.c间隔1秒关闭管道,可以看到原先阻塞的1.c也会在2.c执行1秒后解除阻塞,且read返回值为0。

4.4 练习

通过有名管道实现cp文件复制
方法一:两个c文件,一个只读管道,一个只写管道
3cp_MkfifoToDest.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    if(argc != 2)
    {
        printf("Please input %s <des>\n",argv[0]);
        return -1;
    }
    if(mkfifo("./fifo",0666) < 0)//创建有名管道
    {
        //处理文件已存在的情况
        if(errno == EEXIST)//EEXTST=17
        {
            printf("file eexist\n");
        }
        else
        {
            perror("mkfifo err");
            return -1;
        }
    }
    //打开管道和目标文件
    int fd = open("./fifo",O_RDONLY);
    //此处一定不要用可读可写的方式打开
    //若以可读可写的方式打开,管道中无数据则读阻塞
    if(fd<0)
    {
        perror("open fifo err");
        return -1;
    }
    int dest = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0666);
    if(fd<0)
    {
        perror("open dest err");
        return -1;
    }
    //循环读管道,写目标文件
    ssize_t s;
    char buf[32] = {0};
    while ((s=read(fd,buf,32)) != 0)
        write(dest,buf,s);
    close(fd);
    close(dest);
    return 0;
}

3cp_SrcToMkfifo.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    if(argc != 2)
    {
        printf("Please input %s <src>\n",argv[0]);
        return -1;
    }
    if(mkfifo("./fifo",0666) < 0)//创建有名管道
    {
        //处理文件已存在的情况
        if(errno == EEXIST)//EEXTST=17
        {
            printf("file eexist\n");
        }
        else
        {
            perror("mkfifo err");
            return -1;
        }
    }
    //打开管道和源文件
    int fd = open("./fifo",O_WRONLY);
    if(fd<0)
    {
        perror("open fifo err");
        return -1;
    }
    int src = open(argv[1],O_RDONLY);
    if(fd<0)
    {
        perror("open src err");
        return -1;
    }

    ssize_t s;
    char buf[32] = {0};
    while ((s=read(src,buf,32)) != 0)
    {
        write(fd,buf,s);
    }
    close(fd);
    close(src);
    return 0;
}

运行结果:
LinuxC编程——进程间通信(一)(管道),I/O&amp;进程&amp;线程,linux,c语言,笔记,嵌入式开发,进程,管道

方法二:单个c文件,用父子进程实现,父进程只写有名管道,子进程只读有名管道

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    if (argc != 3)
    {
        printf("Please iniput %s <src> <dest>\n", argv[0]);
        return -1;
    }
    if (mkfifo("./fifo", 0666) < 0) //创建有名管道
    {
        //处理文件已存在的情况
        if (errno == EEXIST) //EEXTST=17
        {
            printf("file eexist\n");
        }
        else
        {
            perror("mkfifo err");
            return -1;
        }
    }
    //打开管道、源文件、目标文件
    
    int src = open(argv[1], O_RDONLY);
    if (src < 0)
    {
        perror("open src err");
        return -1;
    }
    int dest = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (dest < 0)
    {
        perror("open dest err");
        return -1;
    }

    ssize_t s;
    char buf[32] = {0};

    pid_t pid = fork(); // 创建子进程
    if (pid < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (pid == 0) //子进程从有名管道中读出数据,写到目标文件中
    {
        int fd = open("./fifo", O_RDONLY); //管道设置为只读
        if (fd < 0)
        {
            perror("open fifo err");
            return -1;
        }
        while ((s = read(fd, buf, 32)) != 0)
            write(dest, buf, s);
        printf("child end...\n");
        close(fd);
        exit(0);
    }
    else //父进程从源文件读出数据,写到有名管道中
    {
        int fd = open("./fifo", O_WRONLY); //管道设置为只写
        if (fd < 0)
        {
            perror("open fifo err");
            return -1;
        }
        while ((s = read(src, buf, 32)) != 0)
            write(fd, buf, s);
        printf("parent end...\n");
        close(fd);
        wait(NULL);
    }
    close(src);
    close(dest);
    return 0;
}

注:该程序容易被怀疑最后在子进程的read(fd, buf, 32)会发生阻塞,其实不然,这里用到了上面的一个要点:如果所有写进程都关闭命名管道,则只读进程的读操作会认为到达文件末尾,读操作解除阻塞并返回

五、无名管道与有名管道对比⭐⭐

LinuxC编程——进程间通信(一)(管道),I/O&amp;进程&amp;线程,linux,c语言,笔记,嵌入式开发,进程,管道文章来源地址https://www.toymoban.com/news/detail-645868.html

到了这里,关于LinuxC编程——进程间通信(一)(管道)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 学习系统编程No.20【进程间通信之命名管道】

    北京时间:2023/4/15/10:34,今天起床时间9:25,睡了快8小时,昨天刷视屏刷了一个小时,本来12点的时候发完博客洗把脸就要睡了,可惜,看到了一个标题,说实话,现在的标题党是懂人性的,接下来就是无法自拔的一个小时快乐时光,但导致莫名间接熬夜,你说烦人不烦人!但

    2023年04月17日
    浏览(31)
  • 学习系统编程No.18【进程间通信之管道实战】

    北京时间:2023/4/11/21:17,今天的文章更新啦!但是还是没有上热榜,所以我们需要继续更文啦!我相信下一篇博客肯定是可以上热榜的,加油!并且今天晚上因为有一节体育课,所以导致现在才开始码字,体育课上教我们打羽毛球,虽然我自从高考到现在已经快一年没打了,

    2024年02月02日
    浏览(25)
  • 【Linux】进程间通信(匿名管道 & 命名管道)-- 详解

    如何理解进程间通信? 进程具有独立性,所以进程想要通信难度是比较大的,成本高。 在日常生活中,通信的本质是传递信息,但站在程序员角度来看, 进程间通信的本质:让不同的进程看到同一份资源(内存空间) 。 进程间通信就是进程之间互相传递数据,那么进程间

    2024年04月28日
    浏览(36)
  • Linux进程通信:无名管道

    (1)数据传输:进程间数据传输; (2)通知事件:一个进程向另一个或一组进程发送消息,通知某个事件的发生(如子进程终止时需通知父进程); (3)资源共享:多个进程共享资源,需要内核提供同步互斥机制; (4)进程控制:某进程需要控制另一个进程的执行(如

    2023年04月24日
    浏览(47)
  • 【Linux】进程通信 — 管道

    从本章开始,我们开始学习进程通信相关的知识,本章将来详细探讨一下管道,学习匿名管道和命名管道的原理和代码实现等相关操作。目标已经确定,接下来就要搬好小板凳,准备开讲了…🙆🙆🙆🙆 在我们之前的学习中,我们知道进程是具独立性的。但是不要以为进程

    2024年02月16日
    浏览(32)
  • Linux——进程间通信&&管道

    📘北尘_ :个人主页 🌎个人专栏 :《Linux操作系统》《经典算法试题 》《C++》 《数据结构与算法》 ☀️走在路上,不忘来时的初心 数据传输:一个进程需要把他的数据传给另外一个进程。 资源共享:多个进程之间共享同样的资源。 通知事件:一个进程需要向另一个或一组

    2024年04月09日
    浏览(31)
  • linux——进程间通信——管道

     ✅1主页::我的代码爱吃辣 📃2知识讲解:Linux——进程间通信——管道通信 ☂️3开发环境:Centos7 💬4前言:进程间通信(InterProcess Communication,IPC)是指在不同进程之间传播或交换信息。 目录 一.什么是进程间通信 二.进程间通信目的  三.进程间通信发展 四.什么是管道

    2024年02月08日
    浏览(34)
  • Linux——进程间通信、管道

    进程间的通信就是 在不同进程之间传播或交换信息。 举个例子: 古时,两军交战不斩来使; 因为两军互相是独立的,所以使节就是两军之间传话的进行传话的; 而在OS中,进程之间也是相互独立的,但某项工作并不是一个进程就可以完成,而是多个进程之间相互协助完成;

    2024年02月22日
    浏览(31)
  • Linux——进程间通信(管道)

    目录 进程通信的目的 管道 见见猪跑(举个例子) 文件描述符fd与管道的关系(深度理解管道) 什么是管道?  匿名管道 pipe函数概述 父子进程通信时与文件描述符的关系图(理解pipe函数的关键) pipe函数的使用  管道读写规则 管道的大小 自测  使用man 7 pipe查看 使用ulimit -a查看 管

    2024年02月03日
    浏览(81)
  • [Linux]进程间通信--管道

    数据传输:一个进程需要将它的数据发送给另一个进程 。 资源共享:多个进程之间共享同样的资源。 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。 进程控制:有些进程希望完全控制另一个进程的执

    2024年02月09日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包