Linux 进程间通信之 管道

为什么进程间要通信

  1. 数据传输 进程需要把数据传递给其他进程
  2. 资源共享 多个进程之间有时需要共享一份资源
  3. 通知事件 比如子进程要把自己的退出信息交给父进程
  4. 进程控制 比如Debug进程需要控制待调试进程的所有陷入和异常 ,
    并且知道他现在的状态。

因为每个进程本来是拥有自己的虚拟地址空间 用页表映射到每个进程自己的物理内存上的 每个进程自己视为独占系统资源 各个进程的地址空间 为了安全期间并不关联 相对封闭 所以需要一些机制来保证进程间能够通信

学习进程间通信必须了解临界资源的概念
1. 临界资源:把多个进程(执行流)能够看到访问的共同资源叫临界资源,管道是一种临界资源。
2 . 临界区:把访问临界资源的代码叫临界区。
进程间通讯的本质是二进程共享资源(不同进程看到公共资源),一进程以读方式一进程以写方式打开同一份文件
linux 下一切接文件,管道也是一种特殊的文件。
3. 互斥:在任意一个时刻只能有一个“人”访问临界资源,采用原子性原则访问。
4. 原子性操作:狭义的原子性操作指该操作的汇编代码只有一句(该操作不可分),广义的原子操作指一个操作正在执行 时决不会被切出。原子操作要么做了,要么没做,不存在正在做。

System V 的进程间通信机制有三种
1. 消息队列
2. 信号量
3. 共享内存

这四个机制的共同点是 让两个或几个要通信的进程 能够按照一定的规则访问同一内核内存空间 该资源也叫临界资源。 临界资源架起了两个进程之间的桥梁 。

1. 管道

管道是最古老的unix进程间通信方式 大概的思想是 让两个进程打开并访问到同一个文件 看到同一份内存 一个进程往文件里面写数据 另一个进程从文件里面读数据
这个文件于是就想链接两个进程的管道一样。

1.1匿名管道

匿名管道的创建

    #include <unistd.h>
       int pipe(int pipefd[2]);
       int pipe2(int pipefd[2], int flags);

系统调用pipe 的参数是一个文件描述符数组 有两个数组元素
f[0] 表示文件的读端 f[1]表示文件的写端 返回值 成功返回0 失败返回 -1 并置errno 错误代码。

flag 表示 读写方式
pipe()系统调用默认读写方式阻塞

具体怎么实现通信呢?
这里写图片描述

这张图做了说明 父进程调用pipe() 创建了一个匿名管道(在内核空间)
并且默认打开这个管道文件的读端写端 f[0] f[1]文件描述符
之后 fork() 创建子进程 子进程继承了父进程的文件描述符表 故也默认打开f[0] f[1] 两个文件描述符

之后父进程关闭读端文件描述符 子进程关闭写端文件描述符 父进程往文件里写 子进程从文件里读 。 这样父子进程实现了通信

这是对于想要父写 子读的 也可以子写父读 父关f[1] 子关f[0]

代码实现 :

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

int main()
{
    int _pipe[2];
    int ret = pipe(_pipe);
    if(-1 == ret){
        printf(" Create pipe error!, error code id %d",ret);
    }
    pid_t pid = fork();
    if(pid < 0){
        perror("fork");
    }else if(0 ==  pid ){
        //Child
        close(_pipe[0]);
        char* masg = NULL;
        int i = 0;
        while( i < 10){
            masg = "I am Child!";
            write(_pipe[1],masg,strlen(masg) + 1);
            sleep(1); 
        }
    }
     else if( 0 < pid){
        //Father
        close(_pipe[1]);
        char masg_buf[20];  
        int i = 0;
        while( i < 10){
            memset(masg_buf, '\0',sizeof(masg_buf));
            read(_pipe[0],masg_buf,sizeof(masg_buf));
            printf("Child said :%s\n",masg_buf);
            +`````
i;
        }
    }
    return 0;
}

这里写图片描述

从内核角度看进程间通信

两个进程的进程控制块 task_struct——-> file_struct———–>file 结构体——–>dentry 结构体——–> inode结构体

同时指向同一个inode结构体 一个file结构体的f_op ——>file_operators 为读 一个为写
这里写图片描述
这一段看不懂请看:http://blog.csdn.net/x__016meliorem/article/details/78825904

管道的读写规则:

和 pipe2() 函数的flag设置有关
没有数据可读时:
1. O_NONBLOCK diabale read()调用阻塞 进程暂停执行 直到有数据写进去才继续开始读
2. O_NONBLOCK enable read调用返回-1, errno值设置为 EAGAIN

当管道被写满的时候:
1. O_NONBLOCK disanble write()调用阻塞 进程暂停执行 直到有数据被读走才开始继续写。
2. O_NONBLOCK enable write()调用返回-1, errno至设置为 EAGAIN。

如果所有管道的对应写端文件描述符被关闭 ,read()返回0。
如果所有管道的对应读端文件描述符被关闭 , write()操作会产生信号SIGPIPE 该信号可能导致write()进程退出。(因为没有人读 写是没有意义的)

当写入数据量不大的 PIPE_BUF时 linux保证写入的原子操作。
当写入数据量很大的 PIPE_BUF时 linux保不证写入的原子操作。

管道的特点:
1. 生命周期随进程 当进程退出时 管道的内存空间被释放 。
2. 只用于有亲源关系的进程之间通信 , 因为有相同亲源的进程从共同祖先那里继承了一份文件描述符表 表中有同一管道的读写 端文件描述符
这样才能让不同进程看到同一管道
3. 半双工 数据只朝一个方向流动 要实现双向通信 就必须建立两个管道
4 管道通信是面向字节流的 一次写的数据不一定要一次都出来 ,一次读的数据不一定要一次写进去。
5. 内核对管道的操作提供同步与互斥保证

什么是同步与互斥
同步:多人多进程要完成最终目标就一定要按照一定次序协同完成 , 否则无法完成或效率底下。
互斥:任意时刻只能有一个“人” 访问临界资源 采用原子操作访问。

管道的同步与互斥是指:
同步:写满了就不再写 读完了就不再读
互斥:不可以两个进程同时读 或者写管道

命名管道fifo

如果想在两个并不相关的进程之间实现通信 就得使用FIFO命名管道
FIFO的意思是先进先出 mkfifo创建的文件 内核提供机制让 先写入的数据被先读到。

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

创建命名管道的函数

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

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

该函数的第一个参数是 要创建的命名管道文件的路径名 第二个参数mode是拥有者,所属组 , 和其他三种“人” 的权限。

命名管道的实质是创建一个文件, 两个进程以open方式打开这个文件 一个写一个读

命名管道与之前的匿名管道的区别
只在于 创建方式不同 可以在非亲元关系的进程

根本都是内核提供一块内存让 两个进程通过文件描述符 读写公共资源
在内核角度都是让两个进程的file结构体指向同一个inode节点

命名管道实现 client —–server 通信
client.c

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

int main()
{
    int ret  = mkfifo("./myfifo", 0666);
    if(ret < 0){
        perror("mkfifo");
        return 1;
    }
    int fd = open("./myfifo", O_RDONLY);
    if(fd < 0){
        perror("open");
        return 2;
    }
    char buf[1024] = {0};
    while(1){
        ssize_t read_size = read(fd, buf, sizeof(buf) - 1);
        if(read_size > 0){
            buf[read_size - 1] = 0;
            printf("client say : %s\n", buf);
        }
        else if(read_size == 0){
            printf("client quit!, quit now\n");
            return 4;
        }else if(read_size < 0){
            perror("read");
            return 3; 
        }
    }
    close(fd);
    return 0;
}

server.c

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
    int ret = mkfifo("./myfifo", 0666);
    if(ret == 0){
        perror("mkfifo");
        return 1;
    }
    int fd = open("./myfifo", O_WRONLY);
    if(fd < 0){
        perror("open");
        return 2;
    }
    while(1){
        char buf[1024] = {0};
        printf("plase enter!:");
        fflush(stdout);
        ssize_t read_size = read(0, buf, sizeof(buf) - 1);
        if(read_size > 0){
            buf[read_size] = 0;
            write(fd, buf, strlen(buf));
        }
        if(read_size <= 0){
            perror("read");
            return 3;
        }
    }
    close(fd);
    return 0;
}

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值