老大让我学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中管道并没有使用专门的数据结构来实现,而是借助了文件系统的file和VFS的索引节点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 这一篇好像是某本书里面关于管道这一章
作者:Aga.J
出处:http://www.cnblogs.com/aga-j
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
个人学习笔记仅供本人记录知识所用,不属发表性文章。