Linux的管道通信
进程通信的实用例子之一是Unix系统的管道通信。Unix系统从SystemV开始提供有名管道和无名管道两种数据通信方式。无名管道为建立管道的进程及其子孙提供一条以比特流方式传送消息的通信 。该管道在逻辑上被看作管道文件,在物理上则由文件系统的高速缓冲区构成,而很少启动外设。有名管道可用于两个无关的进程之间的通信。
管道是Linux支持的最初Unix IPC形式之一,在Linux中是一种使用非常频繁的通信机制。从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现为:   
·限制管道的大小。实际上,管道是一个固定大小的缓冲区。在Linux中,该缓冲区的大小为1页,即4K字节,使得它的大小不象文件那样不加检验地增长。使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。   
·读取进程也可能工作得比写进程快。当所有当前进程数据已被读取时,管道变空。当这种情
况发生时,一个随后的read()调用将默认地被阻塞,等待某些数据被写入,这解决了read()调用返回文件结束的问题。
注意:从管道读数据是一次性操作,数据一旦被读,它就从管道中被抛弃,释放空间以便写更多的数据。
管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起。写进程在管道的尾端写入数据,读进程在管道的首端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。管道提供了简单的流控制机制。进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。
管道的创建
#include <unistd.h>
int pipe(int fd[2])
函数int pipe(int fd[2])创建一个管道,管道两端可分别用描述字fd[0]以及fd[1]来描述。需要注意的是,管道的两端是固定了任务的。即一端只能用于读,由描述字fd[0]表示,称其为管道读端;另一端则只能用于写,由描述字fd[1]来表示,称其为管道写端。如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生。一般文件的I/O函数都可以用于管道,如close、read、write等等。
使用管道通信时,可关闭某些不需要的读或写描述符,建立起单向的读或写管道,然后用read和write像操作文件一样去操作它:
close(pipe_fd[0]);/*关闭读管道*/
close(pipe_fd[1]);/*关闭写管道*/
发送进程利用文件系统的系统调用write( fd[1], buf, size),把buf中长度为size字节的字符消息送入管道入口(即写入端)fd[1],接收进程则使用系统调用read( fd[0], buf, size )从管道出口(即读出端)fd[0]读出size字节的字符消息放到buf中。这里,管道按FIFO方式传送消息,且只能单向传送消息。进程间通信管道
管道应用实例
1:管道可用于输入输出重定向,它将一个命令的输出直接定向到另一个命令的输入。比如,当在某个shell程序(Bourneshell或C shell等)键入who│wc -l后,相应shell程序将创建who以及wc两个进程和这两个进程间的管道。考虑下面的命令行:
$kill -l显示了当前系统支持的所有信号
$kill -l | grep SIGRTMIN
2使用系统调用pipe()建立一条管道线,两个子进程p1和p2分别向管道各写一句话:child1 is sending a message!和child2 is sending a message!,父进程则从管道中读出来自子进程的信息,并显示在屏幕上。
#include <unistd.h>
#include <stdio.h>
main()
{    int fd[2];
int pid1,pid2;
    char OutPipe[100], InPipe[100];
    pipe(fd);
    while((pid1=fork())==-1);
    if(pid1==0){
    printf(“child process1 %d\n”,getpid());
    lockf(fd[1],1,0);    /*加锁锁定写入端*/
    sprintf(OutPipe, “child1 is sending a message!”);
    write(fd[1], OutPipe, 50);    /*将buf中的50个字符写入管道*/
    sleep(5);                    /*睡眠5秒,暂时放弃CPU*/
    lockf(fd[1],0,0);            /*解锁释放写入端*/
    exit(0);                    /*结束进程pid1 */
}else{
        while((pid2=fork())==-1);
        if(pid2==0){
        printf(“child process2 %d\n”,getpid()”);
        lockf(fd[1],1,0);
        sprintf(OutPipe, “child2 is sending a message!”);
        write(fd[1], OutPipe, 50);
        sleep(5);
        lockf(fd[1],0,0);
        exit(0);
}else{
    printf(“parent process %d\n”,getpid());
    wait(0);
    read(fd[0],InPipe,50);
    printf(“%s\n”,InPipe);
    wait(0);
    read(fd[0],InPipe,50);
    printf(“%s\n”,InPipe);
    exit(0);
}
}
}
这里,用到了文件锁函数lockf以实现互斥。其函数原型为:
#include <unistd.h>
int lockf(int files, int function, long size);
功能:用作锁定文件的某些段或整个文件
参数:
files是文件描述符;
function是锁定和解锁,1表示锁定,0表示解锁;
size是锁定和解锁的字节数,表示自fd文件的当前位置开始处理size个相连字节,若size值为0,则表示从调用lockf后开始锁定,锁定范围从文件的当前位置到文件尾。
思考:程序例2中,文件锁是否是必需的?
3:两个进程,如子进程向父进程发送数据,即使用子进程的fd[1]和父进程的fd[0],同时关闭子进程的fd[0]和父进程的fd[1]。
#include<sys/types.h> 
#include<unistd.h> 
#include<stdio.h> 
#include<stdlib.h> 
#include<errno.h> 
#include<memory.h> 
int main() 
  char *msg="I am child process!";    /*子进程发送的数据*/ 
  pid_t pid; 
  char buf[100];                    /*用于读取*/ 
  int pi;                            /*创建管道时的返回值*/ 
  int fd[2];                        /*创建管道的参数*/ 
 
  memset(buf,0,sizeof(buf));            /*设置buf数组全为0,需*/ 
  pi=pipe(fd);                            /*要引入#include<memory.h>*/   
  if(pi<0) 
  {        perror("pipe() error!"); 
        exit(0); 
  }   
  if((pid=fork())==0)            /*child process*/ 
  {        close(fd[0]);                  /*关闭读管道*/ 
      if(write(fd[1],msg,20)!=-1)      /*写入管道*/ 
              printf("child process write success!\n"); 
      close(fd[1]);                  /*关闭写管道*/ 
   } 
  else if(pid>0)                    /*parent process*/ 
  {        close(fd[1]);            /*关闭写管道*/ 
      sleep(2);                /*休眠一下等待数据写入*/ 
      if(read(fd[0],buf,100)>0)/*写入管道*/ 
                  printf("Message from the pipe is:%s\n",buf);           
      close(fd[0]);/*关闭读管道*/ 
        waitpid(pid,NULL,0);/*待pid进程退出,此处pid为子进程*/ 
      exit(0); 
  } 
  else 
  {        perror("fork() error!"); 
        exit(0); 
  }   
管道的局限性
管道的主要局限性正体现在它的特点上:只支持单向数据流;只能用于具有亲缘关系的进程之间;没有名字;管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小);管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等。
有名管道
普通管道只能用于一个进程家族之间的通信,如父子,兄弟之间,而命名管道是有“名字”的管道,另外的进程可以看到并使用。普通管道在内存中,随着进程的结束而消失,命名管道在磁盘上,作为一个特殊的设备文件而存在,进程结束不消失。值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。
有名管道可用于两个无关的进程之间的通信。它的实现函数是:
#include <sys/types.h>
#include <sys/stat.h>

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。