4.进程通信篇(2--管道)-2020物联网_Linux高级程序设计全套教程(通俗易懂)

5.01无名管道概述

1、管道概述

管道又称为无名管道,无名管道是特殊的文件,在应用层体现为两个打开的文件描述符。

内核空间是所有进程所共有的。

无名管道:就是创建在内核中的,多个进程知道同一个无名管道的空间,就可以利用它来进行通信。

无名管道虽然是在内核空间创建的,但是会给当前用户进程两个文件描述符,一个负责执行读,一个负责写

 

管道是古老的UNIX ipc方式,其特点是:

1、半双工,fd[0]读,fd[1]写;数据在同一时刻只能在一个方向三流动

2、数据只能从管道的一端写入,另一端读出

3、写入管道中的数据先入先出

4、传送的数据是无格式的,要求管道的读出方与写入方必须实现约定好数据格式

5、管道不是普通的文件,不属于某个文件系统,其只存在内存中

6、管道内存中对应一个缓冲区,不同系统大小不一定相同

7、从管道读数据时一次性操作,一旦读走,它就从管道中被抛弃

8、管道没有名字,只能实现父子进程通信

 

2、无名管道的创建------pipe函数

 

#include <unistd.h>

int pipe(int fd[2]);

功能:经由参数fd[2]返回两个文件描述符

参数:fd为int型数组,存放管道文件描述符fd[0],fd[1]

成功0,失败-1

#include<stdio.h>
#include<stdlib.h>
#include<unstd.h>

int main(int argc, char **argv)
{
    int fd_pipe[2];
    if(pipe(fd_pipe) == -1)
    {
        perror();
        exit(1);
    }
    printf("%d\n", fd_pipe[0]);
    printf("%d\n", fd_pipe[1]);
    
    //接下来就是对两个文件描述的使用了
    if(write(fd_pipe[0], "hello world") == -1)
    {
        exit(1);
    }
    

    buf[32] = "";
    if(read(fd_pipe[1], buf) == -1)
    {
        exit(1);
    }
    
    //可以用ssize_t类型的数据接收返回值,用%ld来打印
    //如果第一次读了数据,那么会产生文件偏移量,这个值是不会变化的
    printf("%s\n", buf);
    
    return 0;
}

//可以用ssize_t类型的数据接收返回值,用%ld来打印

多次写入的数据会直接在文件中追加

//如果第一次读了数据,那么会产生文件偏移量,这个值是不会变化的

管道中如果没有数据了,read就会阻塞,毕竟这个是阻塞函数

 

 

3、无名管道实现进程通信

 

注意:利用无名管道实现进程间通信,都是父进程创建无名管道,然后再创建子进程,子进程继承父进程的无名管道的文件描述符,然后父子进程实现通信;

无名管道使用范围比较小,只有具有亲缘关系的进程之间。

父进程先创建无名管道,然后在fork,这样就可以实现通信了,

要想实现全双工通信还得要创建2个管道,否则两个进程同时写的话会出错

 

#include<stdio.h>
#include<stdlib.h>
#include<unstd.h>
#include<string.h>

int main(int argc, char **argv)
{ 
    pid_t pid;
    int pipe_fd[2];
    if(pipe(pipe_fd) == -1)
    {
        exit(1);
    }
    
    if((pid = fork()) < 0)
    {exit(1);}
    
    else if(pi > 0)  //父进程,父进程只负责发送数据,子进程负责读数据
    {
        char buf[128]={};
        while(1)
        {
            fgets(buf,sizeof(buf), stdin);
            buf[strlen(buf) - 1] = '\0';
            
            if(write(pipe_fd[1], buf, sizeof(buf)) == -1)
            {
                exit(1);
            }
        }   
    }
    else  //子进程只负责读
    {
        char buf[128]= "";
        while(1)
        {
            if(read(pipd_fd, buf, sizeof(buf) == -1)) //
            {
                exit(1);
            }
        }
        printf(" son process:%s\n", buf);        
    }
    return 0;
}

 

4、无名管道的读写规律

 

从管道中读数据的特点:

①默认用read函数从管道中读取数据时阻塞的

②用write函数向管道中写入数据,当缓冲区满了write会阻塞

③通信中,读关闭,写数据进程会收到(SIGPIPE)退出!

 

几种情况需要考虑:

①读写端都存在,只读不写:

②读写端都存在,只写不读

③只有读端:

④只有写端:写数据进程会收到(SIGPIPE)退出!

实例1:读写端都存在,只读不写

#include<stdio.h>
#include<stdlib.h>
#include<unstd.h>
#include<string.h>

int main(int argc, char **argv)
{ 
    pid_t pid;
    int pipe_fd[2];
    if(pipe(pipe_fd) == -1)
    {
        exit(1);
    }
    
    //读写端都存在,只读不写会怎么样?
    write(pipe_fd[1], "hello world", 11);
    char buf[128] = "";
             if(read(pipd_fd, buf, sizeof(buf) == -1)) //
            {
                exit(1);
            }
    printf("%s\n", buf);
             if(read(pipd_fd, buf, sizeof(buf) == -1)) //
            {
                exit(1);
            }    
     printf("%s\n", buf);   
    
    return 0;
}

 

实例2:读写端都存在,只写不读

缓冲区写满之后,也会阻塞,默认最大为64K字节

#include<stdio.h>
#include<stdlib.h>
#include<unstd.h>
#include<string.h>

int main(int argc, char **argv)
{ 
    int pipe_fd[2];
    if(pipe(pipe_fd) == -1)
    {
        exit(1);
    }
    int num = 0;
        while(1)
        {     
            if(write(pipe_fd[1], "666", 1024) == -1)
            {
                exit(1);
            }
            num++;
            printf("%d\n", num);
        }   
    return 0;
}

 

 

实例3:只有读端:

#include<stdio.h>
#include<stdlib.h>
#include<unstd.h>
#include<string.h>

int main(int argc, char **argv)
{ 
    int pipe_fd[2];
    if(pipe(pipe_fd) == -1)
    {
        exit(1);
    }
    
    write(pipe_fd[1], "hello world", 11);
    close(piped_fd[1]);
    char buf[128] ="";
    ssize_t bytes;
    if((bytes = read(pipd_fd[0], buf, sizeof(buf))) == -1)//
    {exit(1);}
    
    printf("bytes = %ld\n", bytes);
    printf("buf = %s\n", buf);
    
    memset(buf, 0, sizeof(buf));
    if((bytes = read(pipd_fd[0], buf, sizeof(buf))) == -1)//
    {
        exit(1);
    }
    
    printf("bytes = %ld\n", bytes);
    printf("buf = %s\n", buf);
    return 0;
}

 

实例4:只有写端:管道破裂信号

 

   ......
    close(pipe_fd[0]);//读端关闭,一直写
    int num = 0
        while(1)
        {     
            if(write(pipe_fd[1], "666", 1024) == -1)
            {
                exit(1);
            }
            num++;
            printf("%d\n", num);
        }   
        return 0;

只有写端,没有读端,一旦执行写操作就会产生一个信号SIGPIPE(管道破裂),只要执行就产生,不管以后没有满。

我们可以自己定义处理函数(先注册)

   void handler(int sig)
   {
     printf("AAAAAAAAAAAAAAAAAAAAA\n");  
   }
   
    int main(int argc, char *argv[])
    {
         signal(SIGPIPE, handler);//要先注册
         int pipefd[2];
         if(pipe(pipefd) == -1)
         {
             exit(1);
         }                            
        close(pipe_fd[0]);//读端关闭,一直写
        int num = 0
        while(1)
        {     
            if(write(pipe_fd[1], "666", 1024) == -1)
            {
                exit(1);
            }
            num++;
            printf("%d\n", num);
         }   
         return 0;
    } 

 

5、通过fcntl函数设置文件的阻塞特性(设置非阻塞的目的什么????)

 

编程时可以通过fcntl函数设置文件的阻塞属性:

设置为阻塞:fcntl(fd, F_SETFL,0)

设置为非阻塞:fcntl(fd, F_SETFL, O_NONBLOCK)

默认就是就是阻塞的(0)

#include<stdio.h>
#include<stdlib.h>
#include<unstd.h>

int main(int argc, char **argv)
{ 
    int pipefd[2];
    char buf[] = "hello world";
    if(pipe(pipefd) == -1)
    {
         exit(1);
    } 
    
    pid_t pid;
    if((pid = fork()) < 0)
    {
        exit(1);
    }
    if(pid == 0)
    {
        while(1)
        {
            sleep(5);
            write(fd_pipe[1], buf, strlen(buf);
        }
    }
    else
    {
        while(1)
        {
            //如果将fd_pipe[0]设置为非阻塞
            fcntl(fd_pipe[0], F_SETPL, O_NONBLOCK);
            
            memset(buf, 0, sizeof(buf));
            read(fd_pipe[0], buf, sizeof(buf));
            printf("buf=[%s]\n", buf);
            sleep(1);
        }
    }

    return 0;
}

 

6、文件描述符的概念

(需要好好补充,文件描述符信息很多)

 

7、文件描述符的复制---dup函数

dup和dup2是两个非常有用的系统调用,都是用来复制一个文件描述符的,使用新的文件描述符也标识旧的文件描述符所标识的文件;

int dup(int oldfd);

int dup2(int oldfd, int newfd)

dup和dup2经常用来重定向进程stdin stdout stderr 

#include<unistd.h>

int dup(int oldfd);

功能:复制old文件描述符,并分配一个新的文件描述符,新的文件描述符调用进程文件描述符中最小的可用的文件描述符

参数:要复制的旧的文件

返回新的文件描述符,失败-1

返回新的文件符。这个新的是系统中文件描述符可用的最小值。

 

 

int main(void)
{
    int fd; 
    fd = dup(1);
    printf("%d\n", fd);
    可以直接往fd中写数据,就是往1(标准输出)中写数据
    
    return 0
}

也可以通过dup实现输出重定向

关闭1,然后dup(1),要想重新实现输出到标准输出,只需要把1给关闭就行

 

int fd_file;
fd_file = open("test.txt", O_WRONLY | O_CREAT | O_TRUNK, 0664)
close(1);
int fd=dup(fd_file);
printf("AAAAAAAAA");

 

8、文件描述符的复制---dup2函数

 

int dup2(int oldfd, int newfd);

功能:复制一份打开的文件描述符oldfd,并分配一个新的文件描述符newfd, 两者标识同一个文件

注意:newfd是小于文件描述符最大允许的非负整数,如果newfd是一个已经打开的文件描述符,这首先关闭该文件,然后再复制;

参数:oldfd newfd

成功返回newfd,失败返回-1

这个函数比dup函数更加方便!newfd如果是一个已经打开的文件描述符,那么会选择先把它关闭。

同样也可以实现输出重定向和再恢复

fd1 = open("a.txt", O_CREATE | O_WRONLY, 0664)

    if(fd <0).....

fd2 = dup2(fd1, 1);

 

注意:如果使用dup2函数,则应该在声明的时候先把第二个参数赋值一个初始化的值,否则会直接赋值0,就不能标准输入了

 

{
    int fd1;
    int fd2 = 3;
    dup2(1, fd2) ;  //这个地方要注意呀
    printf("fd2 = %d\n", fd2);
    
    fd1 = open("test.txt", O_CREAT | O_RDWD, 0664)
    dup2(fd1, 1);
    printf("hello world\n");
    
    dup2(fd2, 1);
    printf("AAAAAAAAAAAAAAAAAAA")'
    
    return 0;
}

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
1. 实验目的 1) 加深对进程概念的理解,明确进程程序的区别。 2) 进一步认识并发执行的实质。 3) 分析进程争用资源的现象,学习解决进程互斥的方法。 4) 学习解决进程同步的方法。 5) 了解Linux系统中进程通信的基本原理。   进程是操作系统中最重要的概念,贯穿始终,也是学习现代操作系统的关键。通过本次实验,要求理解进程的实质和进程管理的机制。在Linux系统下实现进程从创建到终止的全过程,从中体会进程的创建过程、父进程和子进程之间的关系、进程状态的变化、进程之间的互斥、同步机制、进程调度的原理和以管道为代表的进程间的通信方式的实现。 2. 内容及要求:   这是一个设计型实验,要求自行编制程序。   使用系统调用pipe()建立一条管道,两个子进程分别向管道写一句话:   Child process1 is sending a message!   Child process2 is sending a message!   父进程管道读出来自两个子进程的信息,显示在屏幕上。   要求: 1) 父进程先接收子进程1发来的消息,然后再接收子进程2发来的消息。 2) 实现管道的互斥使用,当一个子进程正在对管道进行写操作时,另一子进程必须等待。使用系统调用lockf(fd[1],1,0)实现对管道的加锁操作,用lockf(fd[1],0,0)解除对管道的锁定。 3) 实现父子进程的同步,当子进程把数据写入管道后,便去睡眠等待;当父进程试图从一空管道中读取数据时,也应等待,直到子进程将数据写入管道后,才将其唤醒。 3.相关的系统调用 1) fork() 用于创一个子进程。 格式:int fork(); 返回值:在子进程中返回0;在父进程中返回所创建的子进程的ID值;当返回-1时,创建失败。 2) wait() 常用来控制父进程与子进程的同步。 在父进程中调用wait(),则父进程被阻塞,进入等待队列,等待子进程结束。当子进程结束时,父进程从wait()返回继续执行原来的程序。 返回值:大于0时,为子进程的ID值;等于-1时,调用失败。 3) exit() 是进程结束时最常调用的。 格式:void exit( int status); 其中,status为进程结束状态。 4) pipe() 用于创建一个管道 格式:pipe(int fd); 其中fd是一个由两个数组元素fd[0]和fd[1]组成的整型数组,fd[0]是管道的读端口,用于从管道读出数据,fd[1] 是管道的写端口,用于向管道写入数据。 返回值:0 调用成功;-1 调用失败。 5) sleep() 调用进程睡眠若干时间,之后唤醒。 格式:sleep(int t); 其中t为睡眠时间。 6) lockf() 用于对互斥资源加锁和解锁。在本实验中,该调用的格式为: lockf(fd[1],1,0);/* 表示对管道的写入端口加锁。 lockf(fd[1],0,0);/* 表示对管道的写入端口解锁。 7) write(fd[1],String,Length) 将字符串String的内容写入管道的写入口。 8) read(fd[0],String,Length) 从管道的读入口读出信息放入字符串String中。 4.程序流程 父进程: 1) 创建管道; 2) 创建子进程1; 3) 创建子进程2; 4) 等待从管道中读出子进程1写入的数据,并显示在屏幕上; 5) 等待从管道中读出子进程2写入的数据,并显示在屏幕上; 6) 退出。 子进程: 1) 将管道的写入口加锁; 2) 将信息“Child process n is sending message!”输入到变量OutPipe中,n=1,2; 3) 将OutPipe中信息写入管道; 4) 睡眠等待; 5) 将管道的写入口解锁; 6) 退出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LuckyDog0623

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值