Linux系统编程之管道:匿名管道pipe与命名管道fifo

一、进程间通信

每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。如下图所示。


二、管道是一种最基本的IPC机制:

匿名管道
1、概念:
   管道是linux进程间通信的一种方式,其是利用管道“文件”作为不同进程之间的传输数据的媒介,而实现进程间的数据交换。
    而无名管道pipe则是利用内核虚拟出来的管道“文件”来作为不同进程间数据传输通道,而并非实际存在真正意义上的文件。  
   特点:
            1、 无名管道只能用于具有亲缘关系的进程之间的通信,并且要由父进程来创建。(eg:父子进程间通信)
           2、通信方式是单工的,意味着要实现两个进程间的双向通信,必须建立两个管道。
           3、 其存在于内核为其分配的内核空间在。
管道读写注意事项:
1.必须在系统调用fork()中调用pipe(),否则子进程将不会继承文件描述符;
2.当使用半双工管道时,任何关联的进程都必须共享一个相关的祖先进程。
2、匿名管道的用法:

#include <unistd.h>
int pipe(int filedes[2]);

1.pipe()会建立管道,并将文件描述词由参数filedes数组返回。

2. filedes[0]为管道里的读取端
    filedes[1]则为管道的写入端。

3.若成功则返回零,否则返回-1,错误原因存于errno中。

 错误代码: 
         EMFILE 进程已用完文件描述词最大量
         ENFILE 系统已无文件描述词可用。
         EFAULT 参数 filedes 数组地址不合法。

调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或者write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区。pipe函数调用成功返回0,调用失败返回-1。


开辟了管道之后如何实现两个进程间的通信呢?比如可以按下面的步骤通信。




#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
int main(int argc, char *argv[])
{
    int pipefd[2];
    if (pipe(pipefd) == -1)
        perror("pipe error");

    pid_t pid;
    pid = fork();
    if (pid == -1)
        perror("fork error");

    if (pid == 0)
    {
        close(pipefd[0]);
        write(pipefd[1], "hello", 5);
        close(pipefd[1]);
        exit(EXIT_SUCCESS);
    }

    close(pipefd[1]);
    char buf[10] = {0};
    read(pipefd[0], buf, 10);
    printf("buf=%s\n", buf);

    return 0;
}
1. 父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。
2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
3. 父进程关闭管道写端,子进程关闭管道读端。子进程可以往管道里写,父进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。


使用管道有一些限制:

两个进程通过一个管道只能实现单向通信,比如最上面的例子,父进程读子进程写,如果有时候也需要子进程读父进程写,就必须另开一个管道。

管道的读写端通过打开的文件描述符来传递,因此要通信的两个进程必须从它们的公共祖先那里继承管道文件描述符。上面的例子是父进程把文件描述符传给子进程之后父子进程之间通信,也可以父进程fork两次,把文件描述符传给两个子进程,然后两个子进程之间通信,总之需要通过fork传递文件描述符使两个进程都能访问同一管道,它们才能通信。



进程间通信必须通过内核提供的通道,而且必须有一种办法在进程中标识内核提供的某个通道,前面讲过的匿名管道是用打开的文件描述符来标识的。如果要互相通信的几个进程没有从公共祖先那里继承文件描述符,它们怎么通信呢?内核提供一条通道不成问题,问题是如何标识这条通道才能使各进程都可以访问它?文件系统中的路径名是全局的,各进程都可以访问,因此可以用文件系统中的路径名来标识一个IPC通道。

FIFO和UNIX Domain Socket这两种IPC机制都是利用文件系统中的特殊文件来标识的。

FIFO文件在磁盘上没有数据块,仅用来标识内核中的一条通道.

2.命名管道

1、概念:
   与无名管道的不同之处在于,有名管道fifo的管道文件是真正意义上的文件,其由用户自定义分配。    
    特点:
            1、 有名管道fifo可以用于任意不同进程间的通信,不仅仅局限于具有亲缘关系的进程之间。
           2、 其存储路径是用户定义的,有名管道文件是真正意义上的文件。
2、命名管道的用法:
命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
$ mkfifo filename或者
    1、创建有名管道:  
int mkfifo(const char *pathname, mode_t mode);
    2、打开管道      
int open(const char *pathname, int flags);
    3、读写、关闭有名管道与操作无名管道一致。
3、创建有名管道(mkfifo)时错误返回信息:
       EACCES One  of  the  directories in pathname did not allow search (exe‐
              cute) permission.
        EEXIST pathname already exists.  This includes the case where  pathname
              is a symbolic link, dangling or not.
       ENAMETOOLONG
              Either the total length of pathname is greater than PATH_MAX, or
              an individual filename  component  has  a  length  greater  than
              NAME_MAX.  In the GNU system, there is no imposed limit on over‐
              all filename length, but some file systems may place  limits  on
              the length of a component.
       ENOENT A  directory  component  in pathname does not exist or is a dan‐
              gling symbolic link.
     以上只列出几种,其中需要稍加注意第2种,其用于判断创建的管道是否已经存在.

命名管道的打开规则

如果当前打开操作是为读而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
O_NONBLOCK enable:立刻返回成功
如果当前打开操作是为写而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

需要注意的是打开的文件描述符默认是阻塞的.

注意:

系统加于管道和FIFO的唯一限制是:

1、OPEN_MAX 一个进程在任意时刻打开的最大描述符数。可以通过调用sysconf函数查询。

2、PIPE_BUF 可原子地写往一个管道或FIFO的最大数据量。Posix任务它是一个路径名变量,它的值可以随指定的路径名而变化,因为不同的路径名可以落在不同文件系统上,而这些文件系统可能有不同的特征。所以PIPE_BUF可通过pathconf函数取得。

pipeconf.c

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
  
int  
main(int argc, char **argv)  
{  
    if(argc != 2)  
        printf("usage:pipeconf <pathname>");  
    printf("PIPE_BUF = %ld, OPEN_MAX = %ld\n",  
            pathconf(argv[1], _PC_PIPE_BUF), sysconf(_SC_OPEN_MAX));  
    exit(0);  
}  

对管道或FIFO能以两种方式设置成非阻塞:

1、调用open时可指定O_NONBLOCK标志。

writefd = open(FIFO1, O_WRONLY | O_NONBLOCK, 0);

2、如果一个描述符已经打开,可调用fcntl启用O_NONBLOCK标志。对于管道来说,必须使用这种技术,因为管道没哟open调用,在pipe调用中也无法指定O_NONBLOCK标志。使用fcntl时,先使用F_GETFL命令取得当前文件状态标志,将它与O_NONBLOCK标志按位或,再使用F_SETFL命令存储这些文件状态标志。

int  flags;

if((flags = fcntl(fd, F_GETFL, 0) < 0))
		err_sys("F_GETEFL error");
flags |= O_NONBLOCK;
if((flags = fcntl(fd, F_SETFL, 0) < 0))
		err_sys("F_SETEFL error");


下面用一个简单程序,演示FIFO IPC的用法。

该程序分为2端:

server程序创建一个FIFO,并从FIFO读取字符,转换成大写后输出到屏幕。

#include <stdio.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#define FIFO_PATH "/tmp/myfifo"  
int main()  
{  
    int ret;  
    int fd;  
    char buffer;  
    int nread;  
    int i;  
    /*建立FIFO*/  
    ret = mkfifo(FIFO_PATH, 0777);  
    /*打开FIFO*/  
    fd = open(FIFO_PATH, O_RDONLY);  
    if(-1 == fd)  
    {  
        printf("error/n");  
        return -1;  
    }  
    while(1)  
    {  
        nread = read(fd, &buffer, 1);  
        if(nread > 0)   
        {  
            buffer = toupper(buffer); //将字符c转换为大写英文字母.如果c为小写英文字母.
                                      //则返回对应的大写字母;否则返回原来的值。
            printf("%c", buffer);  
        }  
    }  
}  


运行server后,可看到创建了文件/tmp/myfifo,这是mkfifo函数指定的命名管道的路径(名字)。

当然,系统不会真的在磁盘上创建这个文件。


client程序读取用户输入并写入FIFO。

#include <stdio.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#define FIFO_PATH "/tmp/myfifo"  
int main()  
{  
    int fd;  
    int ret;  
    char c;  
    fd = open(FIFO_PATH, O_WRONLY);  
    if(-1 == fd)  
    {  
        printf("error/n");  
        return -1;  
    }  
    while(c = getchar())  
    {  
        write(fd, &c, 1);  
    }  
}  

先启动server程序,再运行client,随便输入些字符。

server端将在屏幕上显示转换为大写后的输入字符。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值