当前位置:首页 > 嵌入式 > 嵌入式教程
[导读]本书在第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

……

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

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭