Linux进程间通信:管道及有名管道

ZDNet软件频道 时间:2009-11-05 作者:郑彦兴 | 巧巧读书 我要评论()
本文关键词:进程 系统进程 进程管理 Linux
本文在详细讨论了管道和有名管道的通信机制的基础上,用实例对其读写规则进行了程序验证,这样做有利于增强读者对读写规则的感性认识,同时也提供了应用范例。

  2、 有名管道概述及相关API应用

  2.1 有名管道相关的关键概念

  管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间通信,在有名管道(named pipe或FIFO)提出后,该限制得到了克服。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。

  2.2有名管道的创建

  #include

  #include

  int mkfifo(const char * pathname, mode_t mode)

  该函数的第一个参数是一个普通的路径名,也就是创建后FIFO的名字。第二个参数与打开普通文件的open()函数中的mode 参数相同。如果mkfifo的第一个参数是一个已经存在的路径名时,会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。一般文件的I/O函数都可以用于FIFO,如close、read、write等等。

  2.3有名管道的打开规则

  有名管道比管道多了一个打开操作:open。

  FIFO的打开规则:

  如果当前打开操作是为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志);或者,成功返回(当前打开操作没有设置阻塞标志)。

  如果当前打开操作是为写而打开FIFO时,如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENXIO错误(当前打开操作没有设置阻塞标志)。对打开规则的验证参见附2。

  2.4有名管道的读写规则

  从FIFO中读取数据:

  约定:如果一个进程为了从FIFO中读取数据而阻塞打开FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。

  如果有进程写打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。

  对于设置了阻塞标志的读操作说,造成阻塞的原因有两种:当前FIFO内有数据,但有其它进程在读这些数据;另外就是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论信写入数据量的大小,也不论读操作请求多少数据量。

  读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)。

  如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞。

  注:如果FIFO中有数据,则设置了阻塞标志的读操作不会因为FIFO中的字节数小于请求读的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。

  向FIFO中写入数据:

  约定:如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。

  对于设置了阻塞标志的写操作:

  当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。

  当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。

  对于没有设置阻塞标志的写操作:

  当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。

  当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写;

  对FIFO读写规则的验证:

  下面提供了两个对FIFO的读写程序,适当调节程序中的很少地方或者程序的命令行参数就可以对各种FIFO读写规则进行验证。

  程序1:写FIFO的程序

  #include

  #include

  #include

  #include

  #define FIFO_SERVER "/tmp/fifoserver"

  main(int argc,char** argv)

  //参数为即将写入的字节数

  {

  int fd;

  char w_buf[4096*2];

  int real_wnum;

  memset(w_buf,0,4096*2);

  if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))

  printf("cannot create fifoserver");

  if(fd==-1)

  if(errno==ENXIO)

  printf("open error; no reading process");

  fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);

  //设置非阻塞标志

  //fd=open(FIFO_SERVER,O_WRONLY,0);

  //设置阻塞标志

  real_wnum=write(fd,w_buf,2048);

  if(real_wnum==-1)

  {

  if(errno==EAGAIN)

  printf("write to fifo error; try later");

  }

  else

  printf("real write num is %d",real_wnum);

  real_wnum=write(fd,w_buf,5000);

  //5000用于测试写入字节大于4096时的非原子性

  //real_wnum=write(fd,w_buf,4096);

  //4096用于测试写入字节不大于4096时的原子性

  if(real_wnum==-1)

  if(errno==EAGAIN)

  printf("try later");

  }

  程序2:与程序1一起测试写FIFO的规则,第一个命令行参数是请求从FIFO读出的字节数

  #include

  #include

  #include

  #include

  #define FIFO_SERVER "/tmp/fifoserver"

  main(int argc,char** argv)

  {

  char r_buf[4096*2];

  int fd;

  int r_size;

  int ret_size;

  r_size=atoi(argv[1]);

  printf("requred real read bytes %d",r_size);

  memset(r_buf,0,sizeof(r_buf));

  fd=open(FIFO_SERVER,O_RDONLY|O_NONBLOCK,0);

  //fd=open(FIFO_SERVER,O_RDONLY,0);

  //在此处可以把读程序编译成两个不同版本:阻塞版本及非阻塞版本

  if(fd==-1)

  {

  printf("open %s for read error");

  exit();

  }

  while(1)

  {

  memset(r_buf,0,sizeof(r_buf));

  ret_size=read(fd,r_buf,r_size);

  if(ret_size==-1)

  if(errno==EAGAIN)

  printf("no data avlaible");

  printf("real read bytes %d",ret_size);

  sleep(1);

  }

  pause();

  unlink(FIFO_SERVER);

  }

  程序应用说明:

  把读程序编译成两个不同版本:

  阻塞读版本:br

  以及非阻塞读版本nbr

  把写程序编译成两个四个版本:

  非阻塞且请求写的字节数大于PIPE_BUF版本:nbwg

  非阻塞且请求写的字节数不大于PIPE_BUF版本:版本nbw

  阻塞且请求写的字节数大于PIPE_BUF版本:bwg

  阻塞且请求写的字节数不大于PIPE_BUF版本:版本bw

  下面将使用br、nbr、w代替相应程序中的阻塞读、非阻塞读

  验证阻塞写操作:

  当请求写入的数据量大于PIPE_BUF时的非原子性:

  nbr 1000

  bwg

  当请求写入的数据量不大于PIPE_BUF时的原子性:

  nbr 1000

  bw

  验证非阻塞写操作:

  当请求写入的数据量大于PIPE_BUF时的非原子性:

  nbr 1000

  nbwg

  请求写入的数据量不大于PIPE_BUF时的原子性:

  nbr 1000

  nbw

  不管写打开的阻塞标志是否设置,在请求写入的字节数大于4096时,都不保证写入的原子性。但二者有本质区别:

  对于阻塞写来说,写操作在写满FIFO的空闲区域后,会一直等待,直到写完所有数据为止,请求写入的数据最终都会写入FIFO;

  而非阻塞写则在写满FIFO的空闲区域后,就返回(实际写入的字节数),所以有些数据最终不能够写入。

  对于读操作的验证则比较简单,不再讨论。

  2.5有名管道应用实例

  在验证了相应的读写规则后,应用实例似乎就没有必要了。

  小结:

  管道常用于两个方面:(1)在shell中时常会用到管道(作为输入输入的重定向),在这种应用方式下,管道的创建对于用户来说是透明的;(2)用于具有亲缘关系的进程间通信,用户自己创建管道,并完成读写操作。

  FIFO可以说是管道的推广,克服了管道无名字的限制,使得无亲缘关系的进程同样可以采用先进先出的通信机制进行通信。

  管道和FIFO的数据是字节流,应用程序之间必须事先确定特定的传输"协议",采用传播具有特定意义的消息。

  要灵活应用管道及FIFO,理解它们的读写规则是关键。

  附1:kill -l 的运行结果,显示了当前系统支持的所有信号:

  1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL

  5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE

  9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2

  13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD

  18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN

  22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ

  26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO

  30) SIGPWR 31) SIGSYS 32) SIGRTMIN 33) SIGRTMIN+1

  34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5

  38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9

  42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13

  46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14

  50) SIGRTMAX-13 51) SIGRTMAX-12 52) SIGRTMAX-11 53) SIGRTMAX-10

  54) SIGRTMAX-9 55) SIGRTMAX-8 56) SIGRTMAX-7 57) SIGRTMAX-6

  58) SIGRTMAX-5 59) SIGRTMAX-4 60) SIGRTMAX-3 61) SIGRTMAX-2

  62) SIGRTMAX-1 63) SIGRTMAX

  除了在此处用来说明管道应用外,接下来的专题还要对这些信号分类讨论。

  附2:对FIFO打开规则的验证(主要验证写打开对读打开的依赖性)

  #include

  #include

  #include

  #include

  #define FIFO_SERVER "/tmp/fifoserver"

  int handle_client(char*);

  main(int argc,char** argv)

  {

  int r_rd;

  int w_fd;

  pid_t pid;

  if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))

  printf("cannot create fifoserver");

  handle_client(FIFO_SERVER);

  }

  int handle_client(char* arg)

  {

  int ret;

  ret=w_open(arg);

  switch(ret)

  {

  case 0:

  {

  printf("open %s error",arg);

  printf("no process has the fifo open for reading");

  return -1;

  }

  case -1:

  {

  printf("something wrong with open the fifo except for ENXIO");

  return -1;

  }

  case 1:

  {

  printf("open server ok");

  return 1;

  }

  default:

  {

  printf("w_no_r return ????");

  return 0;

  }

  }

  unlink(FIFO_SERVER);

  }

  int w_open(char*arg)

  //0 open error for no reading

  //-1 open error for other reasons

  //1 open ok

  {

  if(open(arg,O_WRONLY|O_NONBLOCK,0)==-1)

  { if(errno==ENXIO)

  {

  return 0;

  }

  else

  return -1;

  }

  return 1;

  }

  参考文献:

  UNIX网络编程第二卷:进程间通信,作者:W.Richard Stevens,译者:杨继张,清华大学出版社。丰富的UNIX进程间通信实例及分析,对Linux环境下的程序开发有极大的启发意义。

  Linux内核源代码情景分析(上、下),毛德操、胡希明著,浙江大学出版社,当要验证某个结论、想法时,最好的参考资料;

  UNIX环境高级编程,作者:W.Richard Stevens,译者:尤晋元等,机械工业出版社。具有丰富的编程实例,以及关键函数伴随Unix的发展历程。

  http://www.Linux.org.tw/CLDP/gb/Secure-Programs-HOWTO/x346.html 点明Linux下sigaction的实现基础,Linux源码../kernel/signal.c更说明了问题;

  pipe手册,最直接而可靠的参考资料

  fifo手册,最直接而可靠的参考资料

  关于作者

  郑彦兴,男,现攻读国防科大计算机学院网络方向博士学位。您可以通过电子邮件mlinux@163.com和他联系。


百度大联盟认证黄金会员Copyright© 1997- CNET Networks 版权所有。 ZDNet 是CNET Networks公司注册服务商标。
中华人民共和国电信与信息服务业务经营许可证编号:京ICP证010391号 京ICP备09041801号-159
京公网安备:1101082134