嵌入式Linux应用程序开发详解-第8 章 进程间通信.pdf
《嵌入式Linux应用程序开发详解-第8 章 进程间通信.pdf》由会员分享,可在线阅读,更多相关《嵌入式Linux应用程序开发详解-第8 章 进程间通信.pdf(41页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、华清远见嵌入式培训专家 http:/ 华清远见培训教材 “黑色经典”系列之嵌入式“黑色经典”系列之嵌入式 Linux 应用程序开发详解应用程序开发详解 第 8 章 进程间通信 本章目标 在上一章中,读者已经学会了如何创建进程以及如何对进程进行基本的控制,而这些都只是停留在父子进程之间的控制,本章将要学习不同的进程间进行通信的方法,通过本章的学习,读者将会掌握如下内容。掌握 Linux 中管道的基本概念 掌握 Linux 中管道的创建 掌握 Linux 中管道的读写 掌握 Linux 中有名管道的创建读写方法 掌握 Linux 中消息队列的处理 掌握 Linux 共享内存的处理 QQ:31363
2、8714http:/华清远见嵌入式培训专家 http:/ 华清远见培训教材 8.1 Linux 下进程间通信概述 在上一章中,读者已经知道了进程是一个程序的一次执行的过程。这里所说的进程一般是指运行在用户态的进程,而由于处于用户态的不同进程之间是彼此隔离的,就像处于不同城市的人们,它们必须通过某种方式来提供通信,例如人们现在广泛使用的手机等方式。本章就是讲述如何建立这些不同的通话方式,就像人们有多种通信方式一样。Linux 下的进程通信手段基本上是从 UNIX 平台上的进程通信手段继承而来的。而对UNIX 发展做出重大贡献的两大主力 AT&T 的贝尔实验室及 BSD(加州大学伯克利分校的伯克利
3、软件发布中心)在进程间的通信方面的侧重点有所不同。前者是对 UNIX 早期的进程间通信手段进行了系统的改进和扩充,形成了“system V IPC”,其通信进程主要局限在单个计算机内;后者则跳过了该限制,形成了基于套接口(socket)的进程间通信机制。而 Linux则把两者的优势都继承了下来,如图 8.1 所示。UNIX 进程间通信(IPC)方式包括管道、FIFO、信号。最初UNIX 的进程间通信 基于 Socket 进程间通信 基于 System V 进程间通信 POSIX 进程间通信 Linux 进程间通信 图 8.1 进程间通信发展历程 System V 进程间通信(IPC)包括 Sy
4、stem V 消息队列、System V 信号灯、System V共享内存区。Posix 进程间通信(IPC)包括 Posix 消息队列、Posix 信号灯、Posix 共享内存区。现在在 Linux 中使用较多的进程间通信方式主要有以下几种。(1)管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。(2)信号(Signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知接受进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一样的。QQ:
5、313638714http:/ 嵌入式 Linux 应用程序开发详解第 8 章、进程间通信 华清远见培训教材(3)消息队列:消息队列是消息的链接表,包括 Posix 消息队列 systemV 消息队列。它克服了前两种通信方式中信息量有限的缺点,具有写权限的进程可以向消息队列中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读取消息。(4)共享内存:可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种通信方式需要依靠某种同步机制,如互斥锁和信号量等。(5)信号量:主要作为进程间以及同一进程不同线程之
6、间的同步手段。(6)套接字(Socket):这是一种更为一般的进程间通信机制,它可用于不同机器之间的进程间通信,应用非常广泛。本章会详细介绍前 4 种进程通信方式,对第 5 种通信方式将会在第 10 章中单独介绍。8.2 管道通信 8.2.1 管道概述 细心的读者可能会注意到本书在第 2 章中介绍“ps”的命令时提到过管道,当时指出了管道是 Linux 中很重要的一种通信方式,它是把一个程序的输出直接连接到另一个程序的输入,这里仍以第 2 章中的“ps ef|grep ntp”为例,描述管道的通信过程,如图 8.2 所示。内核 管道 进程 ps-ef 进程 grep ntp 图 8.2 管道的
7、通信过程 管道是 Linux 中进程间通信的一种方式。这里所说的管道主要指无名管道,它具有如下特点。它只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程之间)。它是一个半双工的通信模式,具有固定的读端和写端。管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的 read、write 等QQ:313638714http:/华清远见嵌入式培训专家 http:/ 华清远见培训教材 函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。8.2.2 管道创建与关闭 1管道创建与关闭说明 管道是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符 f
8、ds0和 fds1,其中 fds0固定用于读管道,而 fd1固定用于写管道,如图 8.3 所示,这样就构成了一个半双工的通道。内核 管道 进程 ps-ef 进程 grep ntp 图 8.3 Linux 中管道与文件描述符的关系 管道关闭时只需将这两个文件描述符关闭即可,可使用普通的 close 函数逐个关闭各个文件描述符。注意 一个管道共享了多对文件描述符时,若将其中的一对读写文件描述符都删除,则该管道就失效。2管道创建函数 创建管道可以通过调用 pipe 来实现,下表 8.1 列出了 pipe 函数的语法要点。表 8.1 pipe 函数语法要点 所需头文件#include 函数原型 int
9、 pipe(int fd2)函数传入值 fd2:管道的两个文件描述符,之后就可以直接操作这两个文件描述符 成功:0 函数返回值 出错:1 QQ:313638714http:/ 嵌入式 Linux 应用程序开发详解第 8 章、进程间通信 华清远见培训教材 3管道创建实例 创建管道非常简单,只需调用函数 pipe 即可,如下所示:/*pipe.c*/#include#include#include#include int main()int pipe_fd2;/*创建一无名管道*/if(pipe(pipe_fd)0)printf(pipe create errorn);return 1;else
10、printf(pipe create successn);/*关闭管道描述符*/close(pipe_fd0);close(pipe_fd1);程序运行后先成功创建一个无名管道,之后再将其关闭。8.2.3 管道读写 1管道读写说明 用pipe函数创建的管道两端处于一个进程中,由于管道是主要用于在不同进程间通信的,因此这在实际应用中没有太大意义。实际上,通常先是创建一个管道,再通过 fork()函数创建一子进程,该子进程会继承父进程所创建的管道,这时,父子进程管道的文件描述符对应关系就如图 8.4 所示。QQ:313638714http:/华清远见嵌入式培训专家 http:/ 华清远见培训教材
11、父进程 f d0 f d1 子进程 f d0 f d1 内核 管道 图 8.4 父子进程管道的文件描述符对应关系 这时的关系看似非常复杂,实际上却已经给不同进程之间的读写创造了很好的条件。这时,父子进程分别拥有自己的读写的通道,为了实现父子进程之间的读写,只需把无关的读端或写端的文件描述符关闭即可。例如在图 8.5 中把父进程的写端 fd1和子进程的读端 fd0关闭。这时,父子进程之间就建立起了一条“子进程写入父进程读”的通道。父进程 f d0 f d1 子进程 f d0 f d1内核 管道 图 8.5 关闭父进程 fd1和子进程 fd0 同样,也可以关闭父进程的 fd0和子进程的 fd1,这
12、样就可以建立一条“父进程写,子进程读”的通道。另外,父进程还可以创建多个子进程,各个子进程都继承了相应的 fd0和 fd1,这时,只需要关闭相应端口就可以建立其各子进程之间的通道。想一想 为什么无名管道只能建立具有亲缘关系的进程之间?QQ:313638714http:/ 嵌入式 Linux 应用程序开发详解第 8 章、进程间通信 华清远见培训教材 2管道读写实例 在本例中,首先创建管道,之后父进程使用 fork 函数创建子进程,之后通过关闭父进程的读描述符和子进程的写描述符,建立起它们之间的管道通信。/*pipe_rw.c*/#include#include#include#include#i
13、nclude int main()int pipe_fd2;pid_t pid;char buf_r100;char*p_wbuf;int r_num;memset(buf_r,0,sizeof(buf_r);/*创建管道*/if(pipe(pipe_fd)0)printf(%d numbers read from the pipe is%sn,r_num,buf_r);/*关闭子进程读描述符*/close(pipe_fd0);exit(0);QQ:313638714http:/华清远见嵌入式培训专家 http:/ 华清远见培训教材 else if(pid0)/*/关闭父进程读描述符,并分两次
14、向管道中写入 Hello Pipe*/close(pipe_fd0);if(write(pipe_fd1,Hello,5)!=1)printf(parent write1 success!n);if(write(pipe_fd1,Pipe,5)!=1)printf(parent write2 success!n);/*关闭父进程写描述符*/close(pipe_fd1);sleep(3);/*收集子进程退出信息*/waitpid(pid,NULL,0);exit(0);将该程序交叉编译,下载到开发板上的运行结果如下所示:root(none)1#./pipe_rw2 parent write1
15、success!parent write2 success!10 numbers read from the pipe is Hello Pipe 3管道读写注意点 只有在管道的读端存在时向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的 SIFPIPE 信号(通常 Broken pipe 错误)。向管道中写入数据时,linux 将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读取管道缓冲区中的数据,那么写操作将会一直阻塞。父子进程在运行时,它们的先后次序并不能保证,因此,在这里为了保证父进程已经关闭了读描述符,可在子进程中调用 sl
16、eep 函数。8.2.4 标准流管道 1标准流管道函数说明 与 Linux 中文件操作有基于文件流的标准 I/O 操作一样,管道的操作也支持基于文件流的模式。这种基于文件流的管道主要是用来创建一个连接到另一个进程的管道,这里的“另一个进程”也就是一个可以进行一定操作的可执行文件,例如,用户执行“cat popen.c”或者自己编写的程序“hello”等。由于这一类操作很常用,因此标准流管道就将一系列的创建QQ:313638714http:/ 嵌入式 Linux 应用程序开发详解第 8 章、进程间通信 华清远见培训教材 过程合并到一个函数 popen 中完成。它所完成的工作有以下几步。创建一个管
17、道。fork 一个子进程。在父子进程中关闭不需要的文件描述符。执行 exec 函数族调用。执行函数中所指定的命令。这个函数的使用可以大大减少代码的编写量,但同时也有一些不利之处,例如,它没有前面管道创建的函数灵活多样,并且用 popen 创建的管道必须使用标准 I/O 函数进行操作,但不能使用前面的 read、write 一类不带缓冲的 I/O 函数。与之相对应,关闭用 popen 创建的流管道必须使用函数 pclose 来关闭该管道流。该函数关闭标准 I/O 流,并等待命令执行结束。2函数格式 popen 和 pclose 函数格式如表 8.2 和表 8.3 所示。表 8.2 popen 函
18、数语法要点 所需头文件#include 函数原型 FILE*popen(const char*command,const char*type)Command:指向的是一个以 null 结束符结尾的字符串,这个字符串包含一个 shell命令,并被送到/bin/sh 以-c 参数执行,即由 shell 来执行 函数传入值 type:“r”:文件指针连接到 command 的标准输出,即该命令的结果产生输出“w”:文件指针连接到 command 的标准输入,即该命令的结果产生输入 成功:文件流指针 函数返回值 出错:1 表 8.3 pclose 函数语法要点 所需头文件#include 函数原型 i
19、nt pclose(FILE*stream)函数传入值 stream:要关闭的文件流 成功:返回 popen 中执行命令的终止状态 函数返回值 出错:1 3函数使用实例 在该实例中,使用 popen 来执行“ps-ef”命令。可以看出,popen 函数的使用能够使程序变得短小精悍。/*popen.c*/QQ:313638714http:/华清远见嵌入式培训专家 http:/ 华清远见培训教材#include#include#include#include#define BUFSIZE 1000 int main()FILE*fp;char*cmd=ps-ef;char bufBUFSIZE;/
20、*调用 popen 函数执行相应的命令*/if(fp=popen(cmd,r)=NULL)perror(popen);while(fgets(buf,BUFSIZE,fp)!=NULL)printf(%s,buf);pclose(fp);exit(0);下面是该程序在目标板上的执行结果。root(none)1#./popen PID TTY Uid Size State Command 1 root 1832 S init 2 root 0 S keventd 3 root 0 S ksoftirqd_CPU0 4 root 0 S kswapd 5 root 0 S bdflush 6 ro
21、ot 0 S kupdated 7 root 0 S mtdblockd 8 root 0 S khubd 35 root 2104 S /bin/bash/usr/etc/rc.local 36 root 2324 S /bin/bash 41 root 1364 S /sbin/inetd 53 root 14260 S /Qtopia/qtopia-free-1.7.0/bin/qpe-qws 54 root 11672 S quicklauncher 55 root 0 S usb-storage-0 56 root 0 S scsi_eh_0 74 root 1284 S ./pop
22、en QQ:313638714http:/ 嵌入式 Linux 应用程序开发详解第 8 章、进程间通信 华清远见培训教材 75 root 1836 S sh-c ps-ef 76 root 2020 R ps ef 8.2.5 FIFO 1有名管道说明 前面介绍的管道是无名管道,它只能用于具有亲缘关系的进程之间,这就大大地限制了管道的使用。有名管道的出现突破了这种限制,它可以使互不相关的两个进程实现彼此通信。该管道可以通过路径名来指出,并且在文件系统中是可见的。在建立了管道之后,两个进程就可以把它当作普通文件一样进行读写操作,使用非常方便。不过值得注意的是,FIFO 是严格地遵循先进先出规则的
23、,对管道及 FIFO 的读总是从开始处返回数据,对它们的写则把数据添加到末尾,它们不支持如 lseek()等文件定位操作。有名管道的创建可以使用函数 mkfifo(),该函数类似文件中的 open()操作,可以指定管道的路径和打开的模式。小知识 用户还可以在命令行使用“mknod 管道名 p”来创建有名管道。在创建管道成功之后,就可以使用 open、read、write 这些函数了。与普通文件的开发设置一样,对于为读而打开的管道可在 open 中设置 O_RDONLY,对于为写而打开的管道可在open 中设置 O_WRONLY,在这里与普通文件不同的是阻塞问题。由于普通文件的读写时不会出现阻塞
24、问题,而在管道的读写中却有阻塞的可能,这里的非阻塞标志可以在 open 函数中设定为 O_NONBLOCK。下面分别对阻塞打开和非阻塞打开的读写进行一定的讨论。对于读进程 若该管道是阻塞打开,且当前 FIFO 内没有数据,则对读进程而言将一直阻塞直到有数据写入。若该管道是非阻塞打开,则不论 FIFO 内是否有数据,读进程都会立即执行读操作。对于写进程 若该管道是阻塞打开,则写进程而言将一直阻塞直到有读进程读出数据。若该管道是非阻塞打开,则当前 FIFO 内没有读操作,写进程都会立即执行读操作。2mkfifo 函数格式 表 8.4 列出了 mkfifo 函数的语法要点。表 8.4 mkfifo
25、函数语法要点 所需头文件#include#include 函数原型 int mkfifo(const char*filename,mode_t mode)函数传入值 filename:要创建的管道 续表 QQ:313638714http:/华清远见嵌入式培训专家 http:/ 华清远见培训教材 O_RDONLY:读管道 O_WRONLY:写管道 O_RDWR:读写管道 O_NONBLOCK:非阻塞 O_CREAT:如果该文件不存在,那么就创建一个新的文件,并用第三的参数为其设置权限 函数传入值 mode:O_EXCL:如果使用 O_CREAT 时文件存在,那么可返回错误消息。这一参数可测试文件
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 嵌入式Linux应用程序开发详解-第8 进程间通信 嵌入式 Linux 应用程序 开发 详解 进程 通信
限制150内