第十四章进程间的通信
管道的概念
管道是Linux / UNIX 系列中比较原始的进程间通信方式,他实现数据以一种数据流的方式在进程间流动。在系统中其相当于文件系统上的一个文件,来缓存所要传输的数据。在某些特征性上有不同于文件,例如,当读出后,则管道中就没有数据了,但是文件没有这个特性。顾名思义,匿名管道在系统中是没有实名的,并不可以在文件系统中一任何方式看到管道。他只是进程的一种资源,会随进程的结束而被系统清楚。穿件一个管道时生成了两个文件描述符,但是对于管道中使用的文件描述符并没有路径名,也就是不存在任何意义上的文件,她们只是在内存中跟某一个索引节点相关的两个文件描述符。
#include<unistd.h>
Int pipe (int fd[2])
经由参数file des返回两个文件描述符: filedes[0]为读而打开,f i l ed es[ 1]为写而打开。 filedes[1 ]的输出是f i l edes[0]的输入。
有两种方法来描绘一个管道,见图 。左半图显示了管道的两端在一个进程中相互连接,
右半图则说明数据通过内核在管道中流动。
f s t a t函数(见 4 . 2 节)对管道的每一端都返回一个 F I F O 类型的文件描述符,可以用
S _ISF IFO宏来测试管道。
单个进程中的管道几乎没有任何用处。通常,调用 p i pe的进程接着调用 f or k,这样就创建了从父进程到子进程或反之的 IPC通道。图1 4-2显示了这种情况。
f ork之后做什么取决于我们想要有的数据流的方向。对于从父进程到子进程的管道,父进程关闭管道的读端(fd[ 0]) ,子进程则关闭写端(fd[1]) 。图14-3显示了描述符的最后安排。
对于从子进程到父进程的管道,父进程关闭 fd[1],子进程关闭f d[0]。
当管道的一端被关闭后,下列规则起作用:
(1) 当读一个写端已被关闭的管道时,在所有数据都被读取后, read返回0,以指示达到了文件结束处(从技术方面考虑,管道的写端还有进程时,就不会产生文件的结束。可以复制一个管道的描述符,使得有多个进程具有写打开文件描述符。但是,通常一个管道只有一个读进程,一个写进程。下一节介绍FIFO时,我们会看到对于一个单一的FIFO常常有多个写进程)
。
(2) 如果写一个读端已被关闭的管道,则产生信号 SIGPIPE。如果忽略该信号或者捕捉该信号并从其处理程序返回,则 wr ite出错返回,errno设置为EPIPE。在写管道时,常数 P IPE_BUF规定了内核中管道缓存器的大小。如果对管道进行 wr ite调用,而且要求写的字节数小于等于 P IPE_ B UF,则此操作不会与其他进程对同一管道(或 FI FO)的wr ite操作穿插进行。但是,若有多个进程同时写一个管道(或 FI FO) ,而且某个或某些进程要求写的字节数超过P IPE_BUF字节数,则数据可能会与其他写操作的数据相穿插。
管道的写入规则:
向管道中写入数据时,管道缓冲区一旦有空闲区域,写进程就会立即试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将一直被阻塞。
程序14-1创建了一个从父进程到子进程的管道,并且父进程经由该管道向子进程传送数据。
EG 14-1 :
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define BUFSIZE 512
void err_quit(char *msg)
{
printf("%s",msg);
return ;
}
int main()
{
int fd [2] ;
//写入管道的缓冲区
char buf[BUFSIZE] = "Pipe Data : Hello\n";
pid_t pid ;
int len ;
if((pipe(fd))<0)
err_quit("Pipe failed\n");
if((pid = fork())<0)
err_quit("fork failed\n");
else if( pid > 0 )
{
//父进程中关闭管道的读出端
printf("father\n");
close (fd[0]);
//父进程管道写入数据
len = write(fd[1],buf,strlen(buf));
if(len <0 )
printf("write error\n");
else{
printf("%s\n",buf);
}
exit(0);
}
else {
//子进程关闭管道的写入端
printf("child\n");
close(fd[1]);
//子进程从管道中读出数据
memset(buf,0,sizeof(buf)*BUFSIZE);
len = read(fd[0],buf,BUFSIZE);
if(len < 0 )
err_quit("process failed when read a pipe\n");
else {
printf("%s",buf);
}
}
}
EG14 -2 :这是一个只有一个进程之间的通信模式,有些地方还是要注意的
int main(void)
{
// pipe file descriptors array
int fd [2];
char str [256] ;
// printf("create the pipe failed\n");
if((pipe(fd ) ) < 0 )
{
printf("create the pipe failed\n");
return 0 ;
}
// write data into pipe writing port
// write error after closing Read Port
///close(fd[0]);
if( write(fd[1],"create the pipe sucessfully!\n",31) < 0)
printf("write error \n");
else
printf("write Ok \n");
// read the data from pipe reading port
// Read the data isn't error after closing Writing Port
close(fd[1]);
if( read(fd[0],str,sizeof(str) )< 0 )
printf("Read Error");
else
printf("Read Ok \n");
printf("%s",str);
printf("pipe file descriptors are %d , %d \n",fd[0],fd[1]);
close(fd[0]);
close(fd[1]);
return 0 ;
}
EG 14 - 3 兄弟进程之间的管道通信
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<limits.h>
#define BUFSIZE PIPE_BUF //pipe_buf ,管道默认一次读写的数据长度
void err_quit(char *msg)
{
printf("%s",msg);
return ;
}
int main( int argc ,char * argv [])
{
int fd [2] ;
char buf[BUFSIZE] = "hello my brother!\n" ; //缓冲区
pid_t pid ;
int len ;
if(pipe(fd) <0 )
{
err_quit("pipe failed\n");
}
if((pid = fork())<0)
{
err_quit("fork failed");
}
// in child process
else if( pid ==0 )
{
//关闭不使用的文件描述符
close(fd[0]);
// write message
write(fd[1],buf,strlen(buf)) ;
exit(0);
}
// second subprocess
if( (pid = fork())< 0 )
{
err_quit("fork failed\n");
}
// father process
else if( pid > 0)
{
close(fd[0]);
close(fd[1]);
exit(0);
}
else {
close(fd[1]);
len = read (fd[0],buf,BUFSIZE);
write(STDOUT_FILENO,buf,len);
exit(0);
}
return 0 ;
}
命名管道命名管道
命名管道(named pipe)也称为FIFO ,他是一种文件类型,在文件系统中可以看到它,创建一个FIFO文件类型类似于创建一个普通文件。在程序中可以通过查看文件stat结构中st_mode成员的值来判断该文件是否为FIFO
命名管道区别于管道主要在下面的两点:
1) 命名管道可以用于任何两个进程间的通信,而并不限制两个进程同源。
2) 命名管道作为一种特殊的文件存放于文件系统中,而不是像管道一样存放在内存中(使用完后消失)。当进程对命名管道的使用结束后,命名管道依然存在于文件系统中,除非对其进行删除操作,否则该命名管道不会消失。
命名管道的好处是解决了系统在应用过程中产生的大量的中间临时文件的问题。FIFO可以被shell调用使数据从一进程到另一个进程,系统不必为该中间通道而清理不必要的圾,或者去释放该通道的资源,它可以被被后来的进程使用。
命名管道的创建:
#include<sys/types.h>
#include<sys/stat.h>
Int mkfifo(const char *pathname,mode_t mode)
EG 14 -5 :
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
int main ( int argc , char* argv [])
{
mode_t mode = 0666 ;
int error ;
if(argc!=2)
{
printf("USEMSG: create _ FIFO name!\n");
exit(0);
}
if( (error = mkfifo(argv[1],mode))<0)
{
printf("failed to mkfifo !\n please chekck path or the fifofile is %d ...\n",error);
return 0 ;
}
else {
// uotput the FIFo file name
printf("you sucessfully create a FIFO name : %s \n",argv[1]);
}
return 0 ;
}
Program Output :
Program analysis:
当程序运行第二次是,由于前一次已经创建一个相同路径的fifo file了,所以系统会自动输出错误提示(基于Redhat的Linux ,Ubuntu 测试时没有给出提示)
FIFO FOLE OPEN
FIFO 的打开与其他文件的打开是不同的,FIFO的打开规则:
① 如果当前打开操作是为读而打开FIFO 时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回,否则,可能阻塞直到有相应进程为写而打开该FIFO (当前打开操作设置了阻塞标志);或者,成功返回(当前打开操作没有设置阻塞标志)。
分析:
1)当我们只运行读取端程序时,其运行结果
一直处于阻塞状态(没设置阻塞位)
如果我们接着运行写入端程序,则马上读取数据
2)如果我们把函数 read (fd,buf,BUFS)改为 read (fd,buf,BUFS|O_NONBLOCK|)
则程序马上成功返回
② 如果当前打开操作是为写而打开FIFO时,若已经有相应进程为读而打开该FIFO,则当强打开操作将成功返回;否者,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置