代码改变世界

老大让我学Linux之管道

2011-07-22 09:55  Aga.J  阅读(3070)  评论(0编辑  收藏  举报

    什么是管道?管道,很形象的一个词,描述的是linux系统中进程间是如何通信的。可以将一个程序的输出直接连接到另一个程序的输入,常说的管道多为无名管道,无名管道只能用于具有亲缘关系的进程之间。那么非亲缘间的进程怎么通信呢,这就通过named pipe(命名管道)来完成了。不管是半双工的匿名管道还是命名管道,它们都是利用FIFO排队模型来指挥进程间的通信

    例如我们使用ls –l来列出当前文件夹下有什么文件的信息,我们可以使用管道,将这些结果传递到另一个地方去(这里ls –l估计默认的隐含的管道是指向bash shell,所以我们使用ls –l时总可以看到shell上显示文件信息),我们试试 ls –l | wc –l ,可以得到ls –l所得到的行数,这是因为ls –l的标准输出被设定为第二个进程的标准输入了。这里看到的是命令见的管道连接,应用程序之间也可以如此连接。

    在上面的例子中我们可以这样理解,管道在两个进程间的通信中架起了一座桥梁,它能够缓存输入到管道中的信息,并让其他进程进行读取,所以从本质上来说管道是一种缓冲区,是一种特殊的file。但是和普通file不同的是,linux下的管道是由一定的大小限制的,所以我们一定要小心写管道时管道已满的情况发生,这样写操作会被阻塞。当然读操作也可能被阻塞。(注意管道读操作是一次性操作,数据一旦读取就会被抛弃)。linux中管道并没有使用专门的数据结构来实现,而是借助了文件系统的fileVFS的索引节点inode两个file结构指向同一个临时的VFS索引节点,然后这个VFS索引节点又指向一个物理页面。

    下面说说管道的具体操作方法:

    管道写函数pipe_write会将待写入的字节复制到VFS索引节点所指向的物理内存,这是完成数据写入,然后管道读函数pipe_read则通过从VFS索引节点所指向物理内存内复制字节,读取出数据,很明显,管道也不是什么神奇的东西,就是一个缓冲区,只是实现方式比较巧妙。

    开发经验告诉我们,凡是涉及到IO的东西,都会跟同步异步有关!!!那么如何解决管道的同步问题? linux kernel使用了“锁”,“等待队列”,“信号”!!!千年不变的真理!!就是这样解决这些同步异步的问题的。

    下面以一个写的过程来分析管道的IO细节:

    当写进程向管道中写入时,它使用的是标准的库函数write,系统会根据库函数传递进来的文件描述符找到该文件的file结构,前面已经说到file结构会指向一个临时的VFS索引节点,我们知道这个写入地址后,kernel就调用写函数完成写操作,在写入函数在向内存中写入数据前,必须检查VFS索引节点中的信息,只有

(1) 内存中有足够空间容纳要写入的数据

(2) 内存没有被读程序锁定

    同时满足这两个情况时才可以进行实际的内存复制工作,这时写入函数首先会“锁定”那部分内存,然后从写进程的地址空间中复制数据到内存,如果上面两个条件没满足的话,写进程就会休眠在VFS索引节点的等待队列中。等啊等,大家都知道内核会调用调度程序来调度其他进程,当条件满足的时候(也就是有空间或者读进程解锁)这时候读进程会唤醒写进程,写进程将收到“信号”,然后写入完成后,内存自然会被解锁,这次轮到写进程来唤醒所有读进程了。

实例练手:

#include<stdio.h>

#include<unistd.h>

int main()

{

int n , fd[2];     //fd保存开辟的pipe所关联的两个文件描述符

pid_t pid;         //子进程id

char line[100];

if ( pipe(fd) <0 )     //创建匿名管道

                           //named pipe使用函数int mkfifo(const char *filename, mode_t mode); 来创建

    printf(“pipe create error\n”);

if ( (pid=fork()) < 0 )

    printf(“fork error\n”);

else if ( pid > 0)

{

  close(fd[0]);

  write(fd[1],”hello world\n”,11);

}

else

{

  close(fd[0]);

  n = read ( fd[0],line,100);

  write( STDOUT_FILENO,line,n);

}

}

插个小曲:按照这个例子,凭着大二学操作系统时做linux作业的一点点印象和自己对linux系统和命令的使用方法的猜测,成功编译了文件,一开始在vi中写完文件后文件保存为testPipe,使用gcc来编译发现没办法编译,马上想到应该给个.c的后缀吧,虽然linux内很多文件都不给后缀,然后就使用前面刚学的cp将testPipe 复制到 testPipe.c,其实也可以使用文件重命名的命令。然后使用gcc编译,发现没有什么反应,man一下gcc,找到需要添加-o来指定输出文件,试试输出到a,然后使用./a来执行,运行成功。

接下来是shell中使用管道

基本过程:

  (1)shellA 创建named pipe 读取管道内容,没有内容则阻塞等待

  (2)shellB 将内容输入到管道

  (3)shellA 被唤醒并输出读取的内容

shellA:

  mkfifo –m 0644 /tmp/cmd_pipe            //创建(named-pipe)有名管道 ,我们平时所用的 | 实际上就是匿名管道

  cat /tmp/cmd_pipe

shellB:

  echo Hi > /tmp/cmd_pipe

shellA:

  Hi

最后贴上两篇好文章:

http://blogold.chinaunix.net/u/28197/showart_2532171.html       这一篇分析了管道的相关源码

http://www.pcdog.com/edu/linux/13/11/y237288.html        这一篇好像是某本书里面关于管道这一章