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

时间:2022-10-15 10:23:15

管道:进程可以通过它交换更有用的数据。


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

对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],因为它不会从管道读取数据;接着它向管道写入数据,当所有数据都写完后,父进程关闭管道的写入端并退出;      

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





四、命名管道

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

                 如果想在不相关的进程之间传递数据,可以用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
sys0m0.016s



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


分析:

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

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

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



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