进程间通信---管道

进程间通信

一般而言,不同的进程看到的那一份公共资源,是由操作系统提供的,任何一个进程的全局变量在另一个进程中都看不到,所以,进程之间要交换数据就需要通过内核,在内核中开辟一块缓冲区,进程一把数据从用户空间拷贝到缓冲区,进程二再从缓冲区把数据读走,内核提供的这种机制就叫做进程间通信 (IPC)。
这里写图片描述

进程间通信的目的:

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变

进程间通信的分类:

管道
  • 匿名管道
  • 命名管道
System V IPC
  • System V 消息队列
  • System V 共享内存
  • System V 信号量
POSIX IPC
  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 读写锁
  • 条件变量

今天我们主要来谈谈管道管道是从一个进程连接到另一个进程的一个数据流

匿名管道

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

调用pipe函数时在内核中开辟一块缓冲区,称为管道,创建成功就返回0,创建失败则返回-1。
管道有一个读端一个写端,通过pipefd参数传出给用户程序两个文件描述符,pipefd[0]表示管道的读端,pipefd[1]表示管道的写端

看几张图来理解一下管道的通信过程:

1. 父进程调用pipe开辟管道,得到两个文件描述符分别指向管道的读端和写端。

这里写图片描述
2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一个管道。

这里写图片描述
3. 父进程关闭管道写端,子进程关闭管道读端。子进程可以往管道里写,父进程可以从管道里读。管道是用环形队列实现的,数据从写端流入,从读端流出,这样就实现了进程间通信。

这里写图片描述

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

int main()
{
    int fd[2] = {0};
    if(pipe(fd)==-1)
    {
        perror("pipe");
        return 1;
    }
    pid_t pid = fork();
    if(pid==-1)
    {
        perror("fork");
        return 2;
    }
    else if(pid==0)
    {//child write
        close(fd[0]);
        char *msg="father,I am child";
        int i = 5;
        while(i--)
        {
            write(fd[1],msg,strlen(msg));
            sleep(1);
        }
    }
    else
    {//father read
        close(fd[1]);
        char buf[1024];
        while(1)
        {
            ssize_t s = read(fd[0],buf,sizeof(buf));
            if(s>0)
            {
                buf[s]=0;
                printf("father: %s\n",buf);
            }
            else if(s==0)
            {
                printf("child quit!!!\n");
                break;
            }
            else
            {
                perror("read");
                return 3;
            }
        }
    }
    return 0;
}

这里写图片描述

匿名管道的特点

  • 只能用于具有亲缘关系的进程之间进行通信(常为父子)
  • 管道的生命周期随进程
  • 管道是半双工的,只能进行单向通信
  • 管道自带同步机制
  • 管道是面向字节流的

管道的读写规则

  • 写端的文件描述符关闭,读端一直读
    —–读到文件结尾返回0
  • 写端的文件描述符没关闭,但写端也不写数据,而读端一直读
    —–读端一直等待
  • 写端一直写数据,读端不读,也不关闭它的文件描述符
    —–写端写满后一直等待
  • 写端写数据,而读端的文件描述符关闭
    —–系统直接结束掉写进程(用13信号)

命名管道

匿名管道的一个不足之处是只能用于具有亲缘关系的进程间通信,而命名管道(FIFO)克服了这个缺点。
FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存储于文件系统中。命名管道是一个设备文件,因此,即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。

命名管道的创建

  1. 通过命令创建
$ mkfifo filename

这里写图片描述
2. 通过函数创建

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

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

创建成功返回0,创建失败返回-1
参数pathname代表的是你创建出的管道名称,mode是这个管道具有的权限

来看例子:

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

int main()
{
    if(mkfifo("fifo",0644)==-1)
    {
        perror("mkfifo");
        return 1;
    }
    return 0;
}

这里写图片描述
命名管道和匿名管道除了打开和创建方式不同,其他语义基本相同


用命名管道实现client/server通信

(实现用户发送消息,服务器接受消息)

直接上代码
server.c

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


int main()
{
    if(mkfifo("mypipe",0644)==-1)//创建命名管道
    {
        perror("mkfifo");
        return 1;
    }
    int rfd = 0;
    if((rfd = open("mypipe",O_RDONLY))==-1)//以只读方式打开管道
    {
        perror("open");
        return 2;
    }
    char buf[1024];
    while(1)
    {
        ssize_t s = read(rfd,buf,sizeof(buf)-1);//从管道中读数据
        if(s>0)
        {
            buf[s-1]=0;
            printf("client say:%s\n",buf);
        }
        else if(s==0)
        {
            printf("client quit!!!\n");
            break;
        }
        else
        {
            perror("read");
            return 3;
        }
    }
    return 0;
}

client.c

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


int main()
{
    int wfd = 0;
    if((wfd = open("mypipe",O_WRONLY))==-1)//以只写方式打开管道
    {
        perror("open");
        return 1;
    }
    char buf[1024];
    while(1)
    {
        printf("Please Enter:");
        //从标准输入读取数据
        fflush(stdout);
        ssize_t s = read(0,buf,sizeof(buf)-1);
        if(s>0)
        {
            buf[s]=0;
            write(wfd,buf,strlen(buf));//将读取到的数据写进管道
        }
        else if(s==-1)
        {
            perror("write");
            return 2;
        }
    }
    return 0;
}

因为这里有两个文件需要编译,我在这里编写了一个Makefile来管理这两个文件

Makefile:
这里写图片描述

测试结果:
1.编译通过后,我们先执行server创建出管道,如图:
(再开启一个终端来观察)
这里写图片描述

2.再执行client开始传输数据
这里写图片描述

这样我们就通过命名管道实现了两个毫不相关进程间的通信

Linux下管道的容量及管道的数据结构

1.查看管道的容量

这里写图片描述
2.6标准版本的linux内核,pipe缓冲区是64KB,尽管命令ulimit -a看到管道大小是8块,这个大小是内核设定的管道缓冲区的大小。因为内核动态分配最大是16缓冲条目乘64K。

那么如何查看自己电脑上面管道的大小呢?

  • 通过ulimit -a查看pipe size一次原子性写入8*512bytes=4096bytes
    这里写图片描述

  • 查看缓冲条目个数:cat /usr/src/kernels/2.6.32-431.el6.i686/include/linux/pipe_fs_i.h 文件
    这里写图片描述

接下来就可以计算自己电脑上管道容量的大小了:16*4096bytes=65536bytes
也就验证了 man 7 pipe中容量的大小:
这里写图片描述

2.管道的数据结构

在 Linux 中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file结构和 VFS 的索引节点inode。通过将两个file结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的。
有两个 file 数据结构,但它们定义文件操作例程地址是不同的,其中一个是向管道中写入数据的例程地址,而另一个是从管道中读出数据的例程地址。这样,用户程序的系统调用仍然是通常的文件操作,而内核却利用这种抽象机制实现了管道这一特殊操作。

//inode结点信息结构
struct inode {
...
    struct pipe_inode_info  *i_pipe;
... 
};
//管道缓冲区个数
#define PIPE_BUFFERS (16)

//管道缓存区对象结构
struct pipe_buffer {
    struct page *page; //管道缓冲区页框的描述符地址
    unsigned int offset, len; //页框内有效数据的当前位置,和有效数据的长度
    struct pipe_buf_operations *ops; //管道缓存区方法表的地址
};

//管道信息结构
struct pipe_inode_info {
    wait_queue_head_t wait; //管道等待队列
    unsigned int nrbufs, curbuf; 
    //包含待读数据的缓冲区数和包含待读数据的第一个缓冲区的索引
    struct pipe_buffer bufs[PIPE_BUFFERS]; //管道缓冲区描述符数组
    struct page *tmp_page; //高速缓存区页框指针
    unsigned int start;  //当前管道缓存区读的位置
    unsigned int readers; //读进程的标志,或编号
    unsigned int writers; //写进程的标志,或编号
    unsigned int waiting_writers; //在等待队列中睡眠的写进程的个数
    unsigned int r_counter; //与readers类似,但当等待写入FIFO的进程是使用
    unsigned int w_counter; //与writers类似,但当等待写入FIFO的进程时使用
    struct fasync_struct *fasync_readers; //用于通过信号进行的异步I/O通知
    struct fasync_struct *fasync_writers; //用于通过信号的异步I/O通知
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值