当前位置:首页 > 嵌入式 > 嵌入式教程
[导读]本书在第2章中介绍“ps”的命令时提到过管道,当时指出了管道是Linux中一种很重要的通信方式,它是把一个程序的输出直接连接到另一个程序的输入,这里仍以第2章中的“ps –ef | grep ntp”为例,描述管道的通信过程,如图8.2所示

8.2管道8.2.1管道概述

本书在第2章中介绍“ps”的命令时提到过管道,当时指出了管道是Linux中一种很重要的通信方式,它是把一个程序的输出直接连接到另一个程序的输入,这里仍以第2章中的“ps–ef|grepntp”为例,描述管道的通信过程,如图8.2所示。

图8.2管道的通信过程

管道是Linux中进程间通信的一种方式。这里所说的管道主要指无名管道,它具有如下特点。

n 它只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程之间)。

n 它是一个半双工的通信模式,具有固定的读端和写端。

n 管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的read()和write()等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内核的内存空间中。

8.2.2管道系统调用1.管道创建与关闭说明

管道是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符fds[0]和fds[1],其中fds[0]固定用于读管道,而fd[1]固定用于写管道,如图8.3所示,这样就构成了一个半双工的通道。

图8.3Linux中管道与文件描述符的关系

管道关闭时只需将这两个文件描述符关闭即可,可使用普通的close()函数逐个关闭各个文件描述符。

注意

当一个管道共享多对文件描述符时,若将其中的一对读写文件描述符都删除,则该管道就失效。

2.管道创建函数

创建管道可以通过调用pipe()来实现,表8.1列出了pipe()函数的语法要点。

表8.1 pipe()函数语法要点

所需头文件

#include<unistd.h>

函数原型

intpipe(intfd[2])

函数传入值

fd[2]:管道的两个文件描述符,之后就可以直接操作这两个文件描述符

函数返回值

成功:0

出错:-1

3.管道读写说明

用pipe()函数创建的管道两端处于一个进程中,由于管道是主要用于在不同进程间通信的,因此这在实际应用中没有太大意义。实际上,通常先是创建一个管道,再通过fork()函数创建一子进程,该子进程会继承父进程所创建的管道,这时,父子进程管道的文件描述符对应关系如图8.4所示。

此时的关系看似非常复杂,实际上却已经给不同进程之间的读写创造了很好的条件。父子进程分别拥有自己的读写通道,为了实现父子进程之间的读写,只需把无关的读端或写端的文件描述符关闭即可。例如在图8.5中将父进程的写端fd[1]和子进程的读端fd[0]关闭。此时,父子进程之间就建立起了一条“子进程写入父进程读取”的通道。

图8.4父子进程管道的文件描述符对应关系图8.5关闭父进程fd[1]和子进程fd[0]

同样,也可以关闭父进程的fd[0]和子进程的fd[1],这样就可以建立一条“父进程写入子进程读取”的通道。另外,父进程还可以创建多个子进程,各个子进程都继承了相应的fd[0]和fd[1],这时,只需要关闭相应端口就可以建立其各子进程之间的通道。

想一想

为什么无名管道只能在具有亲缘关系的进程之间建立?

4.管道使用实例

在本例中,首先创建管道,之后父进程使用fork()函数创建子进程,之后通过关闭父进程的读描述符和子进程的写描述符,建立起它们之间的管道通信。

/*pipe.c*/

#include<unistd.h>

#include<sys/types.h>

#include<errno.h>

#include<stdio.h>

#include<stdlib.h>

#defineMAX_DATA_LEN256

#defineDELAY_TIME1

intmain()

{

pid_tpid;

intpipe_fd[2];

charbuf[MAX_DATA_LEN];

constchardata[]="PipeTestProgram";

intreal_read,real_write;

memset((void*)buf,0,sizeof(buf));

/*创建管道*/

if(pipe(pipe_fd)<0)

{

printf("pipecreateerrorn");

exit(1);

}

/*创建一子进程*/

if((pid=fork())==0)

{

/*子进程关闭写描述符,并通过使子进程暂停1s等待父进程已关闭相应的读描述符*/

close(pipe_fd[1]);

sleep(DELAY_TIME*3);

/*子进程读取管道内容*/

if((real_read=read(pipe_fd[0],buf,MAX_DATA_LEN))>0)

{

printf("%dbytesreadfromthepipeis'%s'n",real_read,buf);

}

/*关闭子进程读描述符*/

close(pipe_fd[0]);

exit(0);

}

elseif(pid>0)

{

/*父进程关闭读描述符,并通过使父进程暂停1s等待子进程已关闭相应的写描述符*/

close(pipe_fd[0]);

sleep(DELAY_TIME);

if((real_write=write(pipe_fd[1],data,strlen(data)))!=-1)

{

printf("Parentwrote%dbytes:'%s'n",real_write,data);

}

/*关闭父进程写描述符*/

close(pipe_fd[1]);

/*收集子进程退出信息*/

waitpid(pid,NULL,0);

exit(0);

}

}

将该程序交叉编译,下载到开发板上的运行结果如下所示:

$./pipe

Parentwrote17bytes:'PipeTestProgram'

17bytesreadfromthepipeis'PipeTestProgram'

5.管道读写注意点

n 只有在管道的读端存在时,向管道写入数据才有意义。否则,向管道写入数据的进程将收到内核传来的SIGPIPE信号(通常为Brokenpipe错误)。

n 向管道写入数据时,Linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读取管道缓冲区中的数据,那么写操作将会一直阻塞。

n 父子进程在运行时,它们的先后次序并不能保证,因此,在这里为了保证父子进程已经关闭了相应的文件描述符,可在两个进程中调用sleep()函数,当然这种调用不是很好的解决方法,在后面学到进程之间的同步与互斥机制之后,请读者自行修改本小节的实例程序。

8.2.4标准流管道1.标准流管道函数说明

与Linux的文件操作中有基于文件流的标准I/O操作一样,管道的操作也支持基于文件流的模式。这种基于文件流的管道主要是用来创建一个连接到另一个进程的管道,这里的“另一个进程”也就是一个可以进行一定操作的可执行文件,例如,用户执行“ls-l”或者自己编写的程序“./pipe”等。由于这一类操作很常用,因此标准流管道就将一系列的创建过程合并到一个函数popen()中完成。它所完成的工作有以下几步。

n 创建一个管道。

n fork()一个子进程。

n 在父子进程中关闭不需要的文件描述符。

n 执行exec函数族调用。

n 执行函数中所指定的命令。

这个函数的使用可以大大减少代码的编写量,但同时也有一些不利之处,例如,它不如前面管道创建的函数那样灵活多样,并且用popen()创建的管道必须使用标准I/O函数进行操作,但不能使用前面的read()、write()一类不带缓冲的I/O函数。

与之相对应,关闭用popen()创建的流管道必须使用函数pclose()来关闭该管道流。该函数关闭标准I/O流,并等待命令执行结束。

2.函数格式

popen()和pclose()函数格式如表8.2和表8.3所示。

表8.2 popen()函数语法要点

所需头文件

#include<stdio.h>

函数原型

FILE*popen(constchar*command,constchar*type)

函数传入值

command:指向的是一个以null结束符结尾的字符串,这个字符串包含一个shell命令,并被送到/bin/sh以-c参数执行,即由shell来执行

type:

“r”:文件指针连接到command的标准输出,即该命令的结果产生输出
“w”:文件指针连接到command的标准输入,即该命令的结果产生输入

函数返回值

成功:文件流指针

出错:-1

表8.3 pclose()函数语法要点

所需头文件

#include<stdio.h>

函数原型

intpclose(FILE*stream)

函数传入值

stream:要关闭的文件流

函数返回值

成功:返回由popen()所执行的进程的退出码

出错:-1

3.函数使用实例

在该实例中,使用popen()来执行“ps-ef”命令。可以看出,popen()函数的使用能够使程序变得短小精悍。

/*standard_pipe.c*/

#include<stdio.h>

#include<unistd.h>

#include<stdlib.h>

#include<fcntl.h>

#defineBUFSIZE1024

intmain()

{

FILE*fp;

char*cmd="ps-ef";

charbuf[BUFSIZE];

/*调用popen()函数执行相应的命令*/

if((fp=popen(cmd,"r"))==NULL)

{

printf("Popenerrorn");

exit(1);

}

while((fgets(buf,BUFSIZE,fp))!=NULL)

{

printf("%s",buf);

}

pclose(fp);

exit(0);

}

下面是该程序在目标板上的执行结果。

$./standard_pipe

PIDTTYUidSizeStateCommand

1root1832Sinit

2root0S[keventd]

3root0S[ksoftirqd_CPU0]

……

74root1284S./standard_pipe

75root1836Ssh-cps-ef

76root2020Rps–ef

8.2.5FIFO1.有名管道说明

前面介绍的管道是无名管道,它只能用于具有亲缘关系的进程之间,这就大大地限制了管道的使用。有名管道的出现突破了这种限制,它可以使互不相关的两个进程实现彼此通信。该管道可以通过路径名来指出,并且在文件系统中是可见的。在建立了管道之后,两个进程就可以把它当作普通文件一样进行读写操作,使用非常方便。不过值得注意的是,FIFO是严格地遵循先进先出规则的,对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾,它们不支持如lseek()等文件定位操作。

有名管道的创建可以使用函数mkfifo(),该函数类似文件中的open()操作,可以指定管道的路径和打开的模式。

小知识

用户还可以在命令行使用“mknod管道名p”来创建有名管道。

在创建管道成功之后,就可以使用open()、read()和write()这些函数了。与普通文件的开发设置一样,对于为读而打开的管道可在open()中设置O_RDONLY,对于为写而打开的管道可在open()中设置O_WRONLY,在这里与普通文件不同的是阻塞问题。由于普通文件的读写时不会出现阻塞问题,而在管道的读写中却有阻塞的可能,这里的非阻塞标志可以在open()函数中设定为O_NONBLOCK。下面分别对阻塞打开和非阻塞打开的读写进行讨论。

(1)对于读进程。

n 若该管道是阻塞打开,且当前FIFO内没有数据,则对读进程而言将一直阻塞到有数据写入。

n 若该管道是非阻塞打开,则不论FIFO内是否有数据,读进程都会立即执行读操作。即如果FIFO内没有数据,则读函数将立刻返回0。

(2)对于写进程。

n 若该管道是阻塞打开,则写操作将一直阻塞到数据可以被写入。

n 若该管道是非阻塞打开而不能写入全部数据,则读操作进行部分写入或者调用失败。

2.mkfifo()函数格式

表8.4列出了mkfifo()函数的语法要点。

表8.4 mkfifo()函数语法要点

所需头文件

#include<sys/types.h>
#include<sys/state.h>

函数原型

intmkfifo(constchar*filename,mode_tmode)

函数传入值

filename:要创建的管道

函数传入值

mode:

O_RDONLY:读管道

O_WRONLY:写管道

O_RDWR:读写管道

O_NONBLOCK:非阻塞

函数传入值

mode:

O_CREAT:如果该文件不存在,那么就创建一个新的文件,并用第三个参数为其设置权限

O_EXCL:如果使用O_CREAT时文件存在,那么可返回错误消息。这一参数可测试文件是否存在

函数返回值

成功:0

出错:-1

表8.5再对FIFO相关的出错信息做一归纳,以方便用户查错。

表8.5 FIFO相关的出错信息

EACCESS

参数filename所指定的目录路径无可执行的权限

EEXIST

参数filename所指定的文件已存在

ENAMETOOLONG

参数filename的路径名称太长

ENOENT

参数filename包含的目录不存在

ENOSPC

文件系统的剩余空间不足

ENOTDIR

参数filename路径中的目录存在但却非真正的目录

EROFS

参数filename指定的文件存在于只读文件系统内

3.使用实例

下面的实例包含了两个程序,一个用于读管道,另一个用于写管道。其中在读管道的程序里创建管道,并且作为main()函数里的参数由用户输入要写入的内容。读管道的程序会读出用户写入到管道的内容,这两个程序采用的是阻塞式读写管道模式。

以下是写管道的程序:

/*fifo_write.c*/

#include<sys/types.h>

#include<sys/stat.h>

#include<errno.h>

#include<fcntl.h>

#include<stdio.h>

#include<stdlib.h>

#include<limits.h>

#defineMYFIFO"/tmp/myfifo"/*有名管道文件名*/

#defineMAX_BUFFER_SIZEPIPE_BUF/*定义在于limits.h中*/

intmain(intargc,char*argv[])/*参数为即将写入的字符串*/

{

intfd;

charbuff[MAX_BUFFER_SIZE];

intnwrite;

if(argc<=1)

{

printf("Usage:./fifo_writestringn");

exit(1);

}

sscanf(argv[1],"%s",buff);

/*以只写阻塞方式打开FIFO管道*/

fd=open(MYFIFO,O_WRONLY);

if(fd==-1)

{

printf("Openfifofileerrorn");

exit(1);

}

/*向管道中写入字符串*/

if((nwrite=write(fd,buff,MAX_BUFFER_SIZE))>0)

{

printf("Write'%s'toFIFOn",buff);

}

close(fd);

exit(0);

}

以下是读管道程序:

/*fifo_read.c*/

(头文件和宏定义同fifo_write.c)

intmain()

{

charbuff[MAX_BUFFER_SIZE];

intfd;

intnread;

/*判断有名管道是否已存在,若尚未创建,则以相应的权限创建*/

if(access(MYFIFO,F_OK)==-1)

{

if((mkfifo(MYFIFO,0666)<0)&&(errno!=EEXIST))

{

printf("Cannotcreatefifofilen");

exit(1);

}

}

/*以只读阻塞方式打开有名管道*/

fd=open(MYFIFO,O_RDONLY);

if(fd==-1)

{

printf("Openfifofileerrorn");

exit(1);

}

while(1)

{

memset(buff,0,sizeof(buff));

if((nread=read(fd,buff,MAX_BUFFER_SIZE))>0)

{

printf("Read'%s'fromFIFOn",buff);

}

}

close(fd);

exit(0);

}

为了能够较好地观察运行结果,需要把这两个程序分别在两个终端里运行,在这里首先启动读管道程序。读管道进程在建立管道之后就开始循环地从管道里读出内容,如果没有数据可读,则一直阻塞到写管道进程向管道写入数据。在启动了写管道程序后,读进程能够从管道里读出用户的输入内容,程序运行结果如下所示。

终端一:

$./fifo_read

Read'FIFO'fromFIFO

Read'Test'fromFIFO

Read'Program'fromFIFO

……

终端二:

$./fifo_writeFIFO

Write'FIFO'toFIFO

$./fifo_writeTest

Write'Test'toFIFO

$./fifo_writeProgram

Write'Program'toFIFO

……

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

为了满足日益增长的数据处理需求,铁威马NAS推出了全新的性能巅峰2024年旗舰之作F4-424 Pro,并搭载了最新的操作系统--TOS 6。这款高效办公神器的问世,无疑将为企业和专业人士带来前所未有的便捷与效率。

关键字: 存储 Linux 服务器

Windows Embedded Compact 7(简称WinCE)是一种专为嵌入式系统设计的操作系统,具有体积小、效率高、可定制性强的特点。在WinCE中设置自动运行软件,通常是为了满足设备在启动后自动执行特定任务的...

关键字: 嵌入式系统 软件 操作系统

双系统将是下述内容的主要介绍对象,通过这篇文章,小编希望大家可以对双系统的相关情况以及信息有所认识和了解,详细内容如下。

关键字: 双系统 Windows Linux

今天,小编将在这篇文章中为大家带来Windows 11系统的有关报道,通过阅读这篇文章,大家可以对Windows 11系统具备清晰的认识,主要内容如下。

关键字: Windows 操作系统

全新随插即用方案简化虚拟化实时IIoT平台的设置

关键字: 计算机模块 IIoT 操作系统

目前,HarmonyOS NEXT星河预览版已经正式面向开发者开放申请,面向鸿蒙原生应用及元服务开发者提供的集成开发环境——DevEco Studio也迎来功能更细化的4.1版本。

关键字: HarmonyOS 操作系统

华为P40是一款备受关注的高端智能手机,搭载了华为自研的鸿蒙操作系统。鸿蒙系统作为华为自主研发的操作系统,具有高度的可定制性和扩展性,能够为用户带来全新的使用体验。本文将详细介绍华为P40鸿蒙系统的升级方法,帮助用户更好...

关键字: 华为P40 智能手机 操作系统

安装Linux操作系统并不复杂,下面是一个大致的步骤指南,以帮助您完成安装。1. 下载Linux发行版:首先,您需要从Linux发行版官方网站下载最新的ISO镜像文件。

关键字: Linux 操作系统 ISO镜像

计算机是由一堆硬件组成的,为了有限的控制这些硬件资源,于是就有了操作系统的产生,操作系统是软件子系统的一部分,是硬件基础上的第一层软件。

关键字: Linux 操作系统 计算机

Linux操作系统是一套免费使用和自由传播的类Unix操作系统,通常被称为GNU/Linux。它是由林纳斯·托瓦兹在1991年首次发布的,并基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。Lin...

关键字: Linux 操作系统
关闭
关闭