您的位置:首页 > 运维架构 > Linux

《Linux程序设计》--读书笔记---第十三章进程间通信:管道

2013-08-09 17:27 253 查看
管道:进程可以通过它交换更有用的数据。

我们通常是把一个进程的输出通过管道连接到另一个进程的输入;

对shell命令来说,命令的连接是通过管道字符来完成的;

cmd1 | cmd2

shell负责安排两个命令的标准输入和标准输出

cmd1的标准输入来自键盘

cmd1的标准输出传递给cmd2,作为它的标准输入

cmd2的标准输出连接到终端屏幕

shell所做的工作实际上是对标准输入和标准输出进行了重新连接,使数据流从键盘输入通过两个最终输出屏幕上。

一、进程管道

可能最简单的在两个程序之间传递数据的方法就是使用popen和pclose函数了,它们的原型:

#include <stdio.h>

FILE *popen(const char *command,const char *open_mode);

int pclose(FILE *stream_to_close);

1、popen函数

popen()函数允许一个程序将另一个程序作为新进程来启动,并可以传递数据给它或者通过它接收数据。

函数参数:

command字符串表示要运行的程序名和相应的参数;

open_mode:必须是“r”或者“w”

如果open_mode是“r”,被调用程序的输出就可以给调用程序使用,调用程序利用popen函数返回的FILE*文件流指针,就可以通过fread函数来读取被调用程序的输出;

如果open_mode是“w”,调用程序就可以调用fwrite向被调用程序发送数据,而被调用程序可以在标准输入上读取这些数据;

这就好比fopen函数一样,只不过fopen是打开一个文件指针,popen是打开一个程序输出的指针;

被调用的程序通常不会意识到自己正从另一个进程读取数据,他只是在标准输入流上读取数据,然后做出相应操作。

注:

每个popen()调用都必须指定“r”或者“w”,在popen函数的标准中不支持任何其他选项;这意味我们不能调用另一个程序并同时对它进行读写操作;popen函数调用失败时返回一个空指针,如果想通过管道实现双向通信,最普通的解决方式是使用两个管道每个管道负责一个方向的数据流。

2、pclose函数

使用pclose关闭popen启动的文件流;

pclose调用只在popen启动的进程结束之后才返回,如果调用pclose时仍在运行,pclose调用将等待该进程的结束。

pclose调用的返回值通常是它所关闭的文件流所在进程的退出码。如果调用进程在调用pclose之前执行了一个wait语句,被调用进程的退出状态就会丢失,因为被调用进程已结束,此时pclose将返回-1并设置errno为ECHILD。

例程一:

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#define BUFSIZE 1024

int main(int argc,char *argv[])

{

FILE *read_fp;

char buffer[BUFSIZE + 1];

int chars_read;

memset(buffer,'\0',sizeof(buffer));

read_fp = popen("uname -a","r");

if(read_fp != NULL)

{

chars_read = fread(buffer,sizeof(char),BUFSIZE,read_fp);

if(chars_read > 0)

{

printf("Output was:-\n%s\n",buffer);

}

pclose(read_fp);

exit(EXIT_SUCCESS);

}

exit(EXIT_FAILURE);

}

打印输出:

Output was:-

Linux ubuntu 3.5.0-17-generic #28-Ubuntu SMP Tue Oct 9 19:32:08 UTC 2012 i686 i686 i686 GNU/Linux

程序分析:

命令uname -a的作用是打印系统信息:包括计算机型号、操作系统名称、版本和发行号、以及计算机的网络名。

完成程序的初始化之后,打开一个链接到uname命令的管道,把管道设置为可读方式并让read_fp指向该命令输出,最后关闭read_fp指向的管道。

例程二:将输出送往popen

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

#define BUFSIZE 1024

int main(int argc,char *argv[])

{

FILE *write_fp;

char buffer[BUFSIZE + 1];

sprintf(buffer,"Once upon a time,there was ....\n");

write_fp = popen("od -c","w");

if(write_fp != NULL)

{

fwrite(buffer,sizeof(char),strlen(buffer)-1,write_fp);

pclose(write_fp);

exit(EXIT_SUCCESS);

}

exit(EXIT_FAILURE);

}

打印输出:

0000000 O n c e u p o n a t i m e

0000020 , t h e r e w a s . . . .

0000037

3、传送更多的数据:

有时,希望能以块的方式传送数据,或者根本不知道输出数据的长度,为了避免定义一个非常大的缓冲区,可以用多个fread或者fwrite调用来将数据分为几部分处理。

为了避免定义一个很大的缓冲区,可以用多个fread或者fwrite调用来将数据分为几部分处理。

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

#define BUFSIZE 2048

int main(int argc,char *argv[])

{

FILE *read_fp;

char buffer[BUFSIZE + 1];

int chars_read;

memset(buffer,'\0',sizeof(buffer));

read_fp = popen("ps ax","r");

if(read_fp != NULL)

{

chars_read = fread(buffer,sizeof(char),BUFSIZE,read_fp);

while(chars_read > 0)

{

buffer[chars_read -1] = '\0';

printf("Reading %d:-\n%s\n",chars_read,buffer);

chars_read = fread(buffer,sizeof(char),BUFSIZE,read_fp);

}

pclose(read_fp);

exit(EXIT_SUCCESS);

}

exit(EXIT_FAILURE);

}

打印输出:

Reading 2048:-

PID TTY STAT TIME COMMAND

1 ? Ss 0:01 /sbin/init

2 ? S 0:00 [kthreadd]

3 ? S 0:00 [ksoftirqd/0]

6 ? S 0:00 [migration/0]

7 ? S 0:00 [watchdog/0]

8 ? S< 0:00 [cpuset]

9 ? S< 0:00 [khelper]

10 ? S 0:00 [kdevtmpfs]

11 ? S< 0:00 [netns]

12 ? S 0:00 [sync_supers]

13 ? S 0:00 [bdi-default]

14 ? S< 0:00 [kintegrityd]

15 ? S< 0:00 [kblockd]

16 ? S< 0:00 [ata_sff]

17 ? S 0:00 [khubd]

18 ? S< 0:00 [md]

21 ? S 0:00 [khungtaskd]

22 ? S 0:00 [kswapd0]

23 ? SN 0:00 [ksmd]

24 ? SN 0:00 [khugepaged]

25 ? S 0:00 [fsnotify_mark]

26 ? S 0:00 [ecryptfs-kthrea]

27 ? S< 0:00 [crypto]

36 ? S< 0:00 [kthrotld]

38 ? S 0:00 [kworker/u:2]

39 ? S 0:00 [scsi_eh_0]

40 ? S 0:00 [scsi_eh_1]

41 ? S 0:00 [kworker/u:3]

43 ? S< 0:00 [binder]

62 ? S< 0:00 [deferwq]

63 ? S< 0:00 [charger_manager]

64 ? S< 0:00 [devfreq_wq]

185 ? S< 0:00 [mpt_poll_0]

186 ? S< 0:00 [mpt/0]

202 ? S 0:00 [scsi_eh_2]

217 ? S 0:00 [jbd2/sda1-8]

218 ? S< 0:00 [ext4-dio-unwrit]

316 ? S 0:00 upstart-udev-bridge --daemon

320 ? Ss 0:00 /sbin/udevd --daemon

456 ? Ss 0:00 dbus-daemon --system --fork

486 ? S< 0:00 [ttm_swap]

494 ? Ss 0:00 /usr/sbin/bluetoothd

501 ? Sl 0:00 rsyslogd -c5

508 ? S 0:00 avahi-daemon: running [ubuntu.local]

509 ? S 0:00 avahi-daemon: chroot helper

529 ? S< 0:00 [krfcommd]

546 ? S 0:00 /sbin/udevd --daemon

547 ? S 0:00 /sbin/udevd --daemon

552 ? Ss 0:00 /usr/sb

Reading 2048:-

n/cupsd -F

582 ? Sl 0:00 /usr/lib/i386-linux-gnu/colord/colord

674 ? S< 0:00 [kpsmoused]

752 ? S 0:00 [flush-8:0]

837 ? S 0:00 upstart-socket-bridge --daemon

883 ? Ss 0:00 tpvmlpd2

956 ? Ss 0:00 /usr/sbin/sshd -D

979 ? Ss 0:00 /usr/sbin/modem-manager

988 ? Ssl 0:00 NetworkManager

992 ? Sl 0:00 /usr/lib/policykit-1/polkitd --no-debug

1058 tty4 Ss+ 0:00 /sbin/getty -8 38400 tty4

1068 tty5 Ss+ 0:00 /sbin/getty -8 38400 tty5

1074 tty2 Ss+ 0:00 /sbin/getty -8 38400 tty2

1075 tty3 Ss+ 0:00 /sbin/getty -8 38400 tty3

1077 tty6 Ss+ 0:00 /sbin/getty -8 38400 tty6

1101 ? Ss 0:00 anacron -s

1102 ? Ss 0:00 acpid -c /etc/acpi/events -s /var/run/acpid.socket

1104 ? Ss 0:00 cron

1105 ? Ss 0:00 atd

1107 ? SLsl 0:00 lightdm

1119 ? Ssl 0:00 whoopsie

1144 tty7 Rs+ 1:40 /usr/bin/X :0 -core -auth /var/run/lightdm/root/:0 -nolisten tcp vt7 -novtswitch -background none

1311 tty1 Ss+ 0:00 /sbin/getty -8 38400 tty1

1335 ? Sl 0:00 /usr/lib/accountsservice/accounts-daemon

1375 ? Sl 0:00 /usr/sbin/console-kit-daemon --no-daemon

1749 ? Ssl 0:00 /usr/sbin/vmware-vmblock-fuse -o subtype=vmware-vmblock,default_permissions,allow_other /var/run/vmblock-fuse

1774 ? S 0:04 /usr/sbin/vmtoolsd

1783 ? Sl 0:00 lightdm --session-child 12 21

1841 ? Sl 0:00 /usr/lib/upower/upowerd

1853 ? SNl 0:00 /usr/lib/rtkit/rtkit-daemon

2083 ? Sl 0:00 /usr/bin/gnome-keyring-daemon --daemonize --login

2094 ? Ssl 0:00 gnome-session --session=ubuntu

2129 ? Ss 0:00 /usr/bin/ssh-agent /usr/bin/dbus-launch --exit-with-session gnome-session --session=ubuntu

2132 ? S 0:00 /usr/bin/dbus-launch --exit-with-session gnome-session --session=ubuntu

2133 ? Ss 0:00 //bin/dbus-daemon --fork --print-pid 5 -

Reading 2048:-

print-address 7 --session

2142 ? Sl 0:02 /usr/lib/gnome-settings-daemon/gnome-settings-daemon

2152 ? Sl 0:00 /usr/lib/gvfs/gvfsd

2156 ? Sl 0:00 /usr/lib/gvfs//gvfsd-fuse -f /run/user/hangma/gvfs

2163 ? Sl 0:29 compiz

2174 ? S<l 0:00 /usr/bin/pulseaudio --start --log-target=syslog

2177 ? S 0:00 /usr/lib/pulseaudio/pulse/gconf-helper

2179 ? S 0:00 /usr/lib/i386-linux-gnu/gconf/gconfd-2

2183 ? Sl 0:00 /usr/lib/dconf/dconf-service

2187 ? Sl 0:01 nautilus -n

2188 ? Sl 0:00 bluetooth-applet

2189 ? Sl 0:00 /usr/lib/policykit-1-gnome/polkit-gnome-authentication-agent-1

2190 ? Sl 0:00 nm-applet

2192 ? Sl 0:00 /usr/lib/gnome-settings-daemon/gnome-fallback-mount-helper

2194 ? Sl 0:05 /usr/lib/vmware-tools/sbin32/vmtoolsd -n vmusr --blockFd 3

2212 ? Sl 0:00 /usr/lib/gvfs/gvfs-udisks2-volume-monitor

2218 ? Sl 0:00 /usr/lib/udisks2/udisksd --no-debug

2232 ? Sl 0:00 /usr/lib/gvfs/gvfs-afc-volume-monitor

2237 ? Sl 0:00 /usr/lib/gvfs/gvfs-gphoto2-volume-monitor

2249 ? Sl 0:00 /usr/lib/gvfs/gvfsd-trash --spawner :1.5 /org/gtk/gvfs/exec_spaw/0

2268 ? Sl 0:00 /usr/lib/gvfs/gvfsd-burn --spawner :1.5 /org/gtk/gvfs/exec_spaw/1

2295 ? Sl 0:00 /usr/lib/bamf/bamfdaemon

2304 ? Ss 0:00 /bin/sh -c /usr/bin/gtk-window-decorator

2305 ? Sl 0:00 /usr/bin/gtk-window-decorator

2316 ? Sl 0:00 /usr/lib/unity/unity-panel-service

2318 ? Sl 0:00 /usr/lib/indicator-appmenu/hud-service

2337 ? Sl 0:00 /usr/lib/indicator-messages/indicator-messages-service

2338 ? Sl 0:00 /usr/lib/indicator-datetime/indicator-datetime-service

2340 ? Sl 0:00 /usr/lib/indicator-sound/indicator-sound-service

2342 ? Sl 0:00 /usr/lib/indicator-session/indicator-session-service

2344 ? Sl 0:00 /usr/lib/indicator-printers/indi

Reading 2048:-

ator-printers-service

2346 ? Sl 0:00 /usr/lib/i386-linux-gnu/indicator-application-service

2372 ? Sl 0:00 /usr/lib/evolution/evolution-source-registry

2386 ? Sl 0:00 /usr/lib/unity-lens-applications/unity-applications-daemon

2387 ? Sl 0:00 /usr/lib/unity-lens-files/unity-files-daemon

2388 ? Sl 0:00 /usr/lib/gwibber/unity-gwibber-daemon

2389 ? Sl 0:00 /usr/lib/i386-linux-gnu/unity-music-daemon

2392 ? Sl 0:00 /usr/lib/i386-linux-gnu/unity-shopping-daemon

2393 ? Sl 0:00 /usr/bin/python3 /usr/lib/unity-lens-photos/unity-lens-photos

2395 ? Sl 0:00 /usr/bin/python /usr/lib/unity-lens-video/unity-lens-video

2435 ? Sl 0:00 /usr/lib/geoclue/geoclue-master

2450 ? Sl 0:00 /usr/bin/zeitgeist-daemon

2466 ? Sl 0:00 /usr/lib/ubuntu-geoip/ubuntu-geoip-provider

2471 ? Sl 0:00 /usr/lib/zeitgeist/zeitgeist-fts

2473 ? Sl 0:00 zeitgeist-datahub

2482 ? S 0:00 /bin/cat

2505 ? Sl 0:00 telepathy-indicator

2507 ? Sl 0:00 /usr/bin/python3 /usr/lib/unity-lens-files/unity-scope-gdocs

2512 ? Ss 0:00 /bin/sh -c gnome-terminal

2515 ? Sl 0:11 gnome-terminal

2530 ? Sl 0:00 /usr/lib/telepathy/mission-control-5

2539 ? Sl 0:00 /usr/bin/signon-ui

2541 ? Sl 0:00 /usr/bin/python /usr/lib/unity-scope-video-remote/unity-scope-video-remote

2543 ? S 0:00 gnome-pty-helper

2544 pts/0 Ss 0:00 bash

2609 ? Sl 0:00 /usr/lib/i386-linux-gnu/unity-musicstore-daemon

2640 ? Sl 0:00 update-notifier

2652 ? S 0:00 /usr/bin/python /usr/lib/system-service/system-service-d

2656 ? SNl 0:03 /usr/bin/python3 /usr/bin/update-manager --no-update --no-focus-on-map

2682 ? Sl 0:00 /usr/lib/i386-linux-gnu/deja-dup/deja-dup-monitor

2698 ? S 0:00 /bin/sh -c run-parts --report /etc/cron.daily

2699 ? S 0:00 run-parts --

Reading 629:-

eport /etc/cron.daily

2705 ? S 0:00 /bin/sh /etc/cron.daily/apt

2779 pts/2 Ss 0:00 bash

3019 ? S 0:00 apt-get -qq -y update

3022 ? S 0:00 /usr/lib/apt/methods/http

3023 ? S 0:00 /usr/lib/apt/methods/http

3024 ? S 0:00 /usr/lib/apt/methods/http

3050 ? S 0:00 [kworker/0:0]

3062 pts/0 S+ 0:01 vim popen3.c

3077 ? S 0:00 [kworker/0:1]

3107 ? S 0:00 [kworker/0:2]

3129 ? S 0:00 [kworker/u:0]

3170 pts/2 S+ 0:00 ./popen3

3171 pts/2 S+ 0:00 sh -c ps ax

3172 pts/2 R+ 0:00 ps ax

注意:

虽然ps命令的执行可能要花费一些时间,但Linux会安排好进程间的调度,让两个 程序在可以运行;如果杜金城popen3没有数据可读,它将被挂载到直到有数据到达;如果写进程ps产生的输出超过了可用缓冲区的长度,它也会被挂起直到读进程读取了一些数据。

4、popen的优缺点

优点:在linux系统中,所有的参数扩展都是由shell来完成的,所以,在启动程序之前先启动shell来分析命令字符串,就可以使各种shell扩展(例如对*.c进行扩展)在程序启动之前就全部完成;

这个功能非常有用,它允许我们通过popen启动非常复杂的shell命令,而其他一些创建进程的函数(如execl)调用就复杂的多,因为调用进程必须自己去完成shell扩展。

缺点:针对每个popen调用,不仅要启动一个被请求的程序,还要启动一个shell,即每个popen调用将多启动两个进程;从节省系统资源的角度来看,popen函数的调用成本略高,而且对目标命令的调用比正常方式要慢一些。

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <string.h>

#define BUFSIZE 1024

int main(int argc,char *argv[])

{

FILE *read_fp = NULL;

char buffer[BUFSIZE + 1];

int chars_read;

memset(buffer,'\0',sizeof(buffer));

read_fp = popen("wc -l popen*.c","r");

while(read_fp != NULL)

{

chars_read = fread(buffer,sizeof(char),BUFSIZE,read_fp);

while(chars_read > 0)

{

buffer[chars_read] = '\0';

printf("reading is :\n%s\n",buffer);

chars_read = fread(buffer,sizeof(char),BUFSIZE,read_fp);

}

pclose(read_fp);

exit(EXIT_SUCCESS);

}

exit(EXIT_FAILURE);

}

打印输出:

reading is :

26 popen1.c

24 popen2.c

32 popen3.c

29 popen4.c

111 total

二、pipe调用

pipe函数:通过这个函数在两个程序之间传送数据不需要启动一个shell来解释请求的命令,它同时还提供了对读写数据的更多控制。

#include <unistd.h>

int pipe(int file_descriptor[2]);

pipe函数的参数:是一个由两个整型类型的文件描述符组成的数组的指针;该函数在数组中填上两个新的文件描述符后返回0;如果失败则返回-1并设置errno来表明失败的原因;

错误原因:

EMFILE:进程中使用的文件描述符太多

ENFILE:系统文件表已满

EFAULT:文件描述符无效

两个文件描述符以一种特殊的方式连接起来,写到file_descriptor[1]中的所有的数据都可以从file_descriptor[0]中读出来,并且数据基于先进先出的原则,即每次写入的内容都是添加到管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

注意:由于这里使用的文件描述符而不是文件指针,所以必须使用底层的write和read函数来访问数据,而不是使用fread和fwrite来访问数据流。

特点:

(1)管道两端是固定了任务的,即一端只能用于读,由描述字file_descriptor[0]表示,称为管道读端;一端只能用于写,由描述字file_descriptor[1]来表示,称为管道写端。

(2)管道是半双工的,数据只能向一个方向流动,需要双方通信时,需要建立起两个管道

(3)只能用于父子进程或者兄弟进程之间

(4)单独构成一种独立的文件系统

(5)管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,他不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中;

例程:

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#define BUFSIZE 1024

int main(int argc,char *argv[])

{

int data_processed;

int file_pipes[2];

const char some_data[] = "12323483u24uy32984129874389dsjfndsmnfkjsfjdsnnv";

char buffer[BUFSIZE + 1];

memset(buffer,'\0',sizeof(buffer));

if(pipe(file_pipes) == 0)

{

data_processed = write(file_pipes[1],some_data,strlen(some_data));

printf("write %d bytes\n",data_processed);

data_processed = read(file_pipes[0],buffer,BUFSIZE);

printf("read %d bytes:%s\n",data_processed,buffer);

exit(EXIT_SUCCESS);

}

exit(EXIT_FAILURE);

}

打印输出:

write 3 bytes

read 3 bytes:123

注意:管道有一些内置的缓存区,他在write和read调用之间保存数据。

管道的真正优势在于:两个进程之间的数据传递。

例程:跨越fork调用管道:

#include <unistd.h>

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#define BUFSIZE 1024

int main(int argc, char *argv[])

{

int data_processed;

int file_descriptor[2];

const char some_data[] = "1233324";

char buffer[BUFSIZE + 1 ];

pid_t fork_result;

memset(buffer,'\0',sizeof(buffer));

if(pipe(file_descriptor) == 0)

{

fork_result = fork();

if(fork_result == -1)

{

printf("fork failure\n");

exit(EXIT_FAILURE);

}

else if(fork_result == 0)

{

data_processed = write(file_descriptor[1],some_data,strlen(some_data));

printf("write %d bytes\n",data_processed);

}

else

{

data_processed = read(file_descriptor[0],buffer,BUFSIZE);

printf("read %d bytes:%s\n",data_processed,buffer);

}

exit(EXIT_SUCCESS);

}

exit(EXIT_FAILURE);

}


打印输出:

hangma@ubuntu:~/test/test/pipe_test$ ./pipe2

read 7 bytes:1233324

hangma@ubuntu:~/test/test/pipe_test$ write 7 bytes

说明:如果父进程在子进程之前退出,就会看到两部分输出之间有shell提示符。

如果不想这样,可以在父进程中使用:int stat_val;wait(&stat_val);

这样打印输出为:

hangma@ubuntu:~/test/test/pipe_test$ ./pipe2

write 7 bytes

the stat_val is :0

read 7 bytes:1233324

6、父进程和子进程

pipe3.c

#include <stdio.h>

#include <string.h>

#include <unistd.h>

#include <string.h>

#define BUFSIZE 1024

int main(int argc,char *argv[])

{

int data_processed;

const char some_data[] = "132343dsfdsf";

int file_pipes[2];

char buffer[BUFSIZE + 1];

pid_t fork_result;

if(pipe(file_pipes) == 0)

{

fork_result = fork();

if(fork_result == (pid_t)-1)

{

printf("fork failure\n");

exit(EXIT_FAILURE);

}

if(fork_result == 0)

{

sprintf(buffer,"%d",file_pipes[0]);

(void)execl("pipe4","pipe4",buffer,(char *)0);

exit(EXIT_FAILURE);

}

else

{

data_processed = write(file_pipes[1],some_data,strlen(some_data));

printf("%d-wrote %d bytes\n",getpid(),data_processed);

}

}

}

pipe4.c

#include <unistd.h>

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#define BUFSIZE 1024

int main(int argc,char *argv[])

{

int data_processed;

char buffer[BUFSIZE + 1];

int file_descriptor;

memset(buffer,'\0',sizeof(buffer));

sscanf(argv[1],"%d",&file_descriptor);

data_processed = read(file_descriptor,buffer,BUFSIZE);

printf("%d-read %d bytes:%s\n",getpid(),data_processed,buffer);

exit(EXIT_SUCCESS);

}

打印输出:

hangma@ubuntu:~/test/test/pipe_test$ ./pipe3

6007-wrote 12 bytes

hangma@ubuntu:~/test/test/pipe_test$ 6008-read 12 bytes:132343dsfdsf

表明父进程先于子进程退出,如果不想出现shell命令提示符:则在父进程中使用wait()函数。

打印输出:

hangma@ubuntu:~/test/test/pipe_test$ ./pipe3

6036-wrote 12 bytes

6037-read 12 bytes:132343dsfdsf

三、管道关闭后的读操作

如果管道写端已经关闭或者没有进程打开管道写端写数据,这时read调用就会阻塞,但是这样的阻塞不是我们想要的,我们想要的是:对一个已关闭写数据的管道使用read调用时返回0而不是阻塞,这样才能使读进程能够像检测文件结束一样,对管道进行检测并作出相应的动作。

注意:这与读取一个无效的文件描述符不同,read把无效的文件描述符看做一个错误并返回-1;

如果跨越fork调用管道,就会有两个不同的文件描述符可以用于向管道写数据,一个在父进程中,一个在子进程中,只有把父子进程中的针对管道的写文件描述符都关闭,管道才会被认为是关闭了,对管道的read调用才会失败。

(1)把管道作为标准输入和标准输出

用管道连接两个进程更简洁的方法:

把其中一个管道文件描述设置为一个已知值,一般是标准输入0或者标准输出1;这样做的最大好处就是:我们可以调用标准程序,即那些不需要以文件描述符为参数的程序。

#include <unistd.h>

int dup(int file_descriptor)

int dup2(int file_descriptor_one,int file_descriptor_two)

dup调用的目的是打开一个新的文件描述符,这与open调用有点类似,不同之处是,dup调用创建的新文件描述符与作为它的参数那个文件描述符指向同一个文件(或管道)。

对dup函数来说,新的文件描述符总是取最小的可用值。

对dup2来说,它所创建的新文件描述符或者与参数file_descriptor_two相同,或者是第一个大于该参数的可用值。

dup是如何帮助我们在进程之间传递数据的呢?

标准输入的文件描述符总是0,而dup返回的新的文件描述符又总是使用最小可用的数组;因此,我们首先关闭文件描述符0,然后调用dup,那么新的文件描述符就是就是数字0;因为新的文件描述符是复制一个已有的文件描述符,所以标准输入就会指向dup函数文件描述符对应的文件或者管道;

即将标准输入指向管道或者文件描述符对应的文件。

例程:

#include <unistd.h>

#include <string.h>

#include <stdio.h>

#include <stdlib.h>

#define BUFSIZE 1024

int main(int argc,char *argv[])

{

int data_processed;

int file_pipes[2];

const char some_data[] = "12334jredjfsdkl";

pid_t fork_result;

if(pipe(file_pipes) == 0 )

{

fork_result = fork();

if(fork_result == (pid_t)-1)

{

printf("fork erro\n");

exit(EXIT_FAILURE);

}

else if(fork_result == (pid_t)0)

{

close(0);

dup(file_pipes[0]);

close(file_pipes[0]);

close(file_pipes[1]);

execlp("od","od","-c",(char *)0);

exit(EXIT_FAILURE);

}

else

{

close(file_pipes[0]);

data_processed = write(file_pipes[1],some_data,strlen(some_data));

close(file_pipes[1]);

wait(0);

printf("%d-wrote %d bytes\n",(int)getpid(),data_processed);

}

exit(EXIT_SUCCESS);

}

}

打印输出:

hangma@ubuntu:~/test/test/pipe_test$ ./pipe5

0000000 1 2 3 3 4 j r e d j f s d k l

0000017

7958-wrote 15 bytes

例程分析:

首先:创建一个管道,此时父子进程都可以访问管道的文件描述符,一个用于读数据,一个用于写数据,所以总共有4个打开的文件描述符。

其次,子进程先关闭它的标准输入,然后调用dup把与管道的读取端关联的文件描述符复制为文件描述符0,即使标准输入指向管道读端;然后,子进程关闭原先用来从管道读取数据的文件描述符file_pipes[0],同时关闭子进程的管道读端file_pipes[1]; 此时,子进程只有一个与管道关联的文件描述符,即文件描述符,它的标准输入。

再次,子进程exec启动任何从标准输入读取数据的程序。

最后,父进程首先关闭管道的读取端file_pipes[0],因为它不会从管道读取数据;接着它向管道写入数据,当所有数据都写完后,父进程关闭管道的写入端并退出;



四、命名管道

上述管道是在相关的进程之间传递数据,即这些进程是由一个共同的祖先启动的;这些管道都是没有名字的,因此它们被称为匿名管道,但对于文件系统而言,匿名管道是不可见的,它的作用仅限于在父进程和子进程两个进程之间进行通信;

如果想在不相关的进程之间传递数据,可以用FIFO文件来完成这项工作,通常也称为命名管道(named pipe);命令管道是一个可见的文件,因此可以用于任何两个进程之间的通信,不管这两个进程是不是父子进程,也不管这两个进程之间是否有关系。

命名管道是一种特殊的文件(LINUX中所有事物都是文件),它在文件系统中以文件名的形式存在,但它的行为却和我们已经见过的没有名字的管道很相似。

(1)命令行上创建管道:

mkfifo filename

(2)在程序中创建命名管道

#include <sys/types.h>

#include <sys/stat.h>

int mkfifo(const char *name,mode_t mode);

参数:const char *name:将要在文件系统中创建一个专用文件;

mode_t mode:用来规定FIFO的读写权限。

返回值:如果调用成功,返回值为0;否则返回-1;

FIFO文件与匿名管道的区别:

FIFO是以命名文件的形式存在,而不是打开的文件描述符,所以再对它进行读写操作之前必须先打开它。FIFO管道也用open和close函数打开和关闭,这与对一般文件的操作一样,但是对于FIFO来说,传递给open调用的是文件路径名,而不是一个正常的文件。

#include <unistd.h>

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/stat.h>

int main(int argc,char *argv[])

{

int res = mkfifo("/tmp/my_fifo",0777);

if(res == 0)

printf("fifo create\n");

exit(EXIT_SUCCESS);

}

打印输出:

hangma@ubuntu:~/test/test/pipe_test$ ./fifo1

fifo create

hangma@ubuntu:~/test/test/pipe_test$ ls -l /tmp/my_fifo

prwxrwxr-x 1 hangma hangma 0 Aug 9 10:39 /tmp/my_fifo

hangma@ubuntu:~/test/test/pipe_test$ ls -lF /tmp/my_fifo

prwxrwxr-x 1 hangma hangma 0 Aug 9 10:39 /tmp/my_fifo|

(3)使用open打开FIFO文件

打开FIFO的一个主要限制是:不能以O_RDWR模式打开FIFO文件进行读写操作,这个限制时有道理的,因为我们通常使用FIFO 只是为了单向传递数据,所以没有必要使用O_RDWR;如果确实需要进程之间双向传递数据,最好用一对FIFO或者匿名管道,一个方向使用一个,或者采用先关闭再重新打开FIFO的方法来明确改变数据流的方向。

对于FIFO来说,除非写入方主动打开管道的写入端,否则读取方是无法打开命名管道的,open调用执行后,读取方将被阻塞,直到写入方出现为止。

O_RDONLY、O_WRONLY和O_NONBLOCK共有四种组合:

open(const char *path,O_RDONLY);

在这种情况下,open调用将被阻塞,除非另有进程以写入方式打开这个FIFO,否则它不会返回。

open(const char *path,O_WRONLY)

在这种情况下,open调用将被阻塞,直到有一个进程以读入的方式打开这个FIFO为止

open(const char *path,O_RDONLY | O_NONBLOCK)

即使没有进程写入这个FIFO,这个open调用也立即成功立刻返回

open(const char *path,O_WRONLY | O_NONBLOCK)

这个函数调用总是立刻返回,但是如果没有进程以只读方式打开FIFO文件,open调用将返回一个 错误-1并且FIFO也不会打开;如果确实有一个进程以读方式打开FIFO文件,那么就可以通过它返回的文件描述符来对这个FIFO进行写操作。

注意:O_NONBLOCK分别搭配O_RDONLY和O_WRONLY在效果上不同,如果没有进程以读方式打开管道,非阻塞写方式将失败;但是非阻塞读方式的open调用总是成功;同时close调用的行为并不受O_NONBLOCK标志的影响。

例程2:

#include <stdio.h>

#include <unistd.h>

#include <string.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#define FIFO_NAME "/tmp/my_fifo"

int main(int argc,char *argv[])

{

int res;

int open_mode = 0;

int i;

if(argc < 2)

{

fprintf(stderr,"usage:%s < some combination of\

O_RDONLY O_WRONLY O_NONBLOCK >\n",*argv);

exit(EXIT_FAILURE);

}

for(i = 1;i < argc; i++)

{

if(strncmp(*++argv,"O_RDONLY",8) == 0)

open_mode |= O_RDONLY;

if(strncmp(*argv,"O_WRONLY",8) == 0)

open_mode |= O_WRONLY;

if(strncmp(*argv,"O_NONBLOCK",10) == 0)

open_mode |= O_NONBLOCK;

}



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

{

res = mkfifo(FIFO_NAME,0777);

if(res != 0 )

{

fprintf(stderr,"could not create fifo:%s\n",FIFO_NAME);

exit(EXIT_FAILURE);

}

}

printf("process %d opening FIFO\n",getpid());

res = open(FIFO_NAME,open_mode);

printf("Process %d result %d\n",getpid(),res);

sleep(5);

if(res != 1)

(void)close(res);

printf("the %d process finished\n",getpid());

exit(EXIT_SUCCESS);

}

打印输出:

hangma@ubuntu:~/test/test/pipe_test$ ./fifo2 O_RDONLY O_NONBLOCK

process 8334 opening FIFO

Process 8334 result 3

the 8334 process finished

hangma@ubuntu:~/test/test/pipe_test$ ./fifo2 O_WRONLY O_NONBLOCK

process 8336 opening FIFO

Process 8336 result -1

the 8336 process finished

hangma@ubuntu:~/test/test/pipe_test$ ./fifo2 O_WRONLY

process 8339 opening FIFO

^C

hangma@ubuntu:~/test/test/pipe_test$ ./fifo2 O_RDONLY

process 8409 opening FIFO

^C

分析:

对一个空的FIFO以O_RDONLY无阻塞的方式打开时,立刻返回0

当FIFO以O_WRONLY无阻塞的方式打开时,返回错误

当FIFO以O_RDONLY或者O_WRONLY方式打开时,均被阻塞。

命名管道最常见的用法:它允许先启动读进程,并在open调用中等待,当第二个程序打开FIFO文件时,两个程序继续进行,注意,读进程和写进程在open调用中取得同步。

当一个linux进程被阻塞时,它并不消耗CPU资源,所以这种进程的同步方式对CPU来说是非常有效率的。

(4)对FIFO读写操作的规则:

对一个空的、阻塞的FIFO(即O_RDONLY)进行read调用将等待,直到有数据可读才继续进行;

对一个空的、非阻塞的FIFO(即O_RDONLY/O_NONBLOCK)进行调用将立刻返回0字节。

对一个完全阻塞的FIFO进行write调用(即O_WRONLY)将等待,知道数据可以被写入时才继续执行,如果写入的数据长度小于PIPE_BUF,那么或者全部写入,或者一个字节都不写入;

对一个非阻塞的FIFO进行write调用(即O_WRONLY和O_NONBLOCK),如果FIFO不能接收所有写入的数据,它将按下面的规则执行:

如果请求写入的数据的长度小于等于PIPE_BUF字节,调用失败,数据不能写入;

如果请求写入的数据长度大于PIPE_BUF字节,将写入部分数据,返回实际写入的字节数,返回值也可能是0;



注意:FIFO的 长度是需要考虑的一个重要因素,系统在任何时刻在一个FIFO中可以存在的数据长度是有限制的,它由#define PIPE_BUF语句定义,通常可以在头文件limits.h中找到。

例程:使用FIFO实现进程间通信:

fifo3.c内容:生产者程序:即写端程序

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <limits.h>

#define FIFO_NAME "/tmp/my_fifo"

#define BUFFER_SIZE PIPE_BUF

#define TEN_MEG (1024*1024*10)

int main(int argc, char *argv[])

{

int pipe_fd;

int res;

int open_mode = O_WRONLY;

int bytes_sent = 0;

char buffer[BUFFER_SIZE + 1];

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

{

res = mkfifo(FIFO_NAME,0777);

if(res == -1)

{

printf("fifo create failure\n");

exit(EXIT_FAILURE);

}

}

printf("Process %d opening FIFO O_WRONLY\n",getpid());

pipe_fd = open(FIFO_NAME,open_mode);

printf("Process %d result %d\n",getpid(),pipe_fd);

if(pipe_fd != -1)

{

while(bytes_sent < TEN_MEG)

{

res = write(pipe_fd,buffer,BUFFER_SIZE);

if(res == -1)

{

fprintf(stderr,"write error on pipe\n");

exit(EXIT_FAILURE);

}

bytes_sent+=res;

}

(void)close(pipe_fd);

}

else

{

exit(EXIT_FAILURE);

}

printf("Process %d finished\n",getpid());

exit(EXIT_SUCCESS);

}

fifo4.c:消费者程序:即读端程序

#include <sys/types.h>

#include <sys/stat.h>

#include <unistd.h>

#include <string.h>

#include <stdio.h>

#include <stdlib.h>

#include <limits.h>

#include <fcntl.h>

#define FIFO_NAME "/tmp/my_fifo"

#define BUFFER_SIZE PIPE_BUF

int main(int argc,char *argv[])

{

int pipe_fd;

int res;

int open_mode = O_RDONLY;

char buffer[BUFFER_SIZE + 1];

int bytes_read = 0;

memset(buffer,'\0',sizeof(buffer));

printf("Process %d opening FIFO O_RDONLY\n",getpid());

pipe_fd = open(FIFO_NAME,open_mode);

printf("Process %d result pipe:%d\n",getpid(),pipe_fd);

if(pipe_fd != -1)

{

do

{

res = read(pipe_fd,buffer,BUFFER_SIZE);

bytes_read += res;

}while(res > 0);

}

else

{

exit(EXIT_FAILURE);

}

printf("Process %d finished,%d bytes read\n",getpid(),bytes_read);

exit(EXIT_SUCCESS);

}

打印输出:

hangma@ubuntu:~/test/test/pipe_test$ ./fifo3 &

[1] 10258

hangma@ubuntu:~/test/test/pipe_test$ Process 10258 opening FIFO O_WRONLY

time ./fifo4

Process 10261 opening FIFO O_RDONLY

Process 10258 result 3

Process 10261 result pipe:3

Process 10258 finished

Process 10261 finished,10485760 bytes read

[1]+ Done ./fifo3

real 0m0.079s

user 0m0.020s

sys 0m0.016s

注意:在命令行后面加 &,表示后台运行,这样在shell运行的结果就是创建完进程后直接返回shell命令行;如果不加&,创建完进程后等待子进程结束后再返回shell命令行。

分析:

两个程序使用 的都是阻塞模式的FIFO;我们首先启动fifo3(写进程/生产者),它将阻塞以等待读进程打开这个FIFO;fifo4(消费者)启动以后,写进程解除阻塞并开始向管道写数据;同时读进程也开始从管道中读取数据:

linux会安排好这两个进程之间的调度,使它们可以运行的时候就运行,不能运行的时候就阻塞;因此写进程在管道满时就阻塞,读进程在管道空时就阻塞。

time命令的输出显示,读进程只运行了不到0.1s,却读取的10M的数据,这说明管道在程序之间传递数据的效率还是很高的。

五、使用FIFO的客户/服务器应用 程序

服务器端:

#include "client.h"

#include <ctype.h>

int main(int argc,char *argv[])

{

int server_fifo_fd;

int client_fifo_fd;

struct data_to_pass_st my_data;

int read_res;

char client_fifo[256];

char *tmp_char_ptr;

mkfifo(SERVER_FIFO_NAME,0777);

server_fifo_fd = open(SERVER_FIFO_NAME,O_RDONLY);

if(server_fifo_fd == -1)

{

fprintf(stderr,"open failure\n");

exit(EXIT_FAILURE);

}

sleep(10);

do

{

read_res = read(server_fifo_fd,&my_data,sizeof(my_data));

if(read_res > 0)

{

tmp_char_ptr = my_data.some_data;

while(*tmp_char_ptr)

{

*tmp_char_ptr = toupper(*tmp_char_ptr) ;

tmp_char_ptr++;

}

sprintf(client_fifo,CLIENT_FIFO_NAME,my_data.client_pid);

client_fifo_fd = open(client_fifo,O_WRONLY);

if(client_fifo_fd != -1)

{

write(client_fifo_fd,&my_data,sizeof(my_data));

close(client_fifo_fd);

}

}

}while(read_res > 0);

close(server_fifo_fd);

unlink(SERVER_FIFO_NAME);

exit(EXIT_SUCCESS);

}

客户端程序:

#include "client.h"

#include <ctype.h>

int main(int argc,char *argv[])

{

int server_fifo_fd,client_fifo_fd;

struct data_to_pass_st my_data;

int times_to_end;

char client_fifo[256];

server_fifo_fd = open(SERVER_FIFO_NAME,O_WRONLY);

if(server_fifo_fd == -1)

{

fprintf(stderr,"sorry,no server\n");

exit(EXIT_FAILURE);

}

my_data.client_pid = getpid();

sprintf(client_fifo,CLIENT_FIFO_NAME,my_data.client_pid);

if(mkfifo(client_fifo,0777) == -1)

{

fprintf(stderr,"sorry,can't make %s\n",client_fifo);

exit(EXIT_FAILURE);

}

for(times_to_end = 0;times_to_end < 5;times_to_end++)

{

sprintf(my_data.some_data,"Hello from %d",my_data.client_pid);

printf("%d sent %s,",my_data.client_pid,my_data.some_data);

write(server_fifo_fd,&my_data,sizeof(my_data));

client_fifo_fd = open(client_fifo,O_RDONLY);

if(client_fifo_fd != -1)

{

fprintf(stderr,"sorry,can't make %s\n",client_fifo);

exit(EXIT_FAILURE);

}

for(times_to_end = 0;times_to_end < 5;times_to_end++)

{

sprintf(my_data.some_data,"Hello from %d",my_data.client_pid);

printf("%d sent %s,",my_data.client_pid,my_data.some_data);

write(server_fifo_fd,&my_data,sizeof(my_data));

client_fifo_fd = open(client_fifo,O_RDONLY);

if(client_fifo_fd != -1)

{

if(read(client_fifo_fd,&my_data,sizeof(my_data)) > 0 )

{

printf("received:%s\n",my_data.some_data);

}

close(client_fifo_fd);

}

}

close(server_fifo_fd);

unlink(client_fifo);

exit(EXIT_SUCCESS);

}



打印输出:

root@ubuntu:~/test/test/pipe_test# ./server &

[1] 11631

root@ubuntu:~/test/test/pipe_test# for i in 1 2 3 4 5 ; do ./client & done

[2] 11632

[3] 11633

[4] 11634

[5] 11635

[6] 11636

root@ubuntu:~/test/test/pipe_test# 11635 sent Hello from 11635,received:HELLO FROM 11635

11635 sent Hello from 11635,received:HELLO FROM 11635

11634 sent Hello from 11634,received:HELLO FROM 11634

11635 sent Hello from 11635,received:HELLO FROM 11635

11634 sent Hello from 11634,received:HELLO FROM 11634

11633 sent Hello from 11633,received:HELLO FROM 11633

11635 sent Hello from 11635,received:HELLO FROM 11635

11634 sent Hello from 11634,received:HELLO FROM 11634

11633 sent Hello from 11633,received:HELLO FROM 11633

11633 sent Hello from 11633,received:HELLO FROM 11633

11633 sent Hello from 11633,received:HELLO FROM 11633

11633 sent Hello from 11633,received:HELLO FROM 11633

11634 sent Hello from 11634,received:HELLO FROM 11634

11635 sent Hello from 11635,received:HELLO FROM 11635

11632 sent Hello from 11632,received:HELLO FROM 11632

11634 sent Hello from 11634,received:HELLO FROM 11634

11632 sent Hello from 11632,received:HELLO FROM 11632

11632 sent Hello from 11632,received:HELLO FROM 11632

11636 sent Hello from 11636,received:HELLO FROM 11636

11632 sent Hello from 11632,received:HELLO FROM 11632

11636 sent Hello from 11636,received:HELLO FROM 11636

11632 sent Hello from 11632,received:HELLO FROM 11632

11636 sent Hello from 11636,received:HELLO FROM 11636

11636 sent Hello from 11636,received:HELLO FROM 11636

11636 sent Hello from 11636,received:HELLO FROM 11636

分析:不同的客户请求交错在一起,客户请求的的顺序是随机的,服务器接收到的客户请求的顺序随机器的不同而不同。

(1)服务器以只读模式创建它的FIFO并阻塞,直到第一个客户以写方式打开一个FIFO来建立连接为止;此时,服务器进程解除阻塞并执行sleep语句,这使得来自客户的数据排队等候。在实际应用程序中,应该立刻把sleep语句删除,这里只是为了演示当有多个客户的请求同时到达时,程序正确的操作方法。

(2)客户打开服务器FIFO后,它创建一个唯一的一个命名管道来读取服务器返回的数据。完成这些工作后,客户发送数据给服务器(如果管道满或者服务器仍在休眠中就阻塞),然后阻塞在对自己的FIFO的read调用上,等待服务器的响应。

(3)接收到来自客户的请求后,服务器处理它,然后以写入的方式打开客户管道并将处理后的数据返回,这将解除客户的阻塞状态;客户被解除阻塞后,它既可从自己的管道中读取服务器返回的数据。

整个过程不断重复,直到最后一个客户关闭服务器管道为止,这将使服务器的read调用失败,因为已经没有进程以写方式打开服务器管道了;

如果这是一个真正的服务器进程,它还需要继续等待客户的请求,我们就需要对它进行修改,有两种方法:

第一:对它自己的服务器管道打开一个文件描述符,这样read调用将总是阻塞而不是返回0;

第二:当read调用返回0时,关闭重新打开的服务器管道,使服务器进程在open调用处以等待客户的到来。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: