您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 企业文化 > 嵌入式linux应用程序开发技术详解 8
华清远见——嵌入式培训专家应用开发班培训教材“黑色经典”系列之《嵌入式Linux应用程序开发详解》第8章进程间通信本章目标在上一章中,读者已经学会了如何创建进程以及如何对进程进行基本的控制,而这些都只是停留在父子进程之间的控制,本章将要学习不同的进程间进行通信的方法,通过本章的学习,读者将会掌握如下内容。掌握Linux中管道的基本概念掌握Linux中管道的创建掌握Linux中管道的读写掌握Linux中有名管道的创建读写方法掌握Linux中消息队列的处理掌握Linux共享内存的处理华清远见——嵌入式培训专家下进程间通信概述在上一章中,读者已经知道了进程是一个程序的一次执行的过程。这里所说的进程一般是指运行在用户态的进程,而由于处于用户态的不同进程之间是彼此隔离的,就像处于不同城市的人们,它们必须通过某种方式来提供通信,例如人们现在广泛使用的手机等方式。本章就是讲述如何建立这些不同的通话方式,就像人们有多种通信方式一样。Linux下的进程通信手段基本上是从UNIX平台上的进程通信手段继承而来的。而对UNIX发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间的通信方面的侧重点有所不同。前者是对UNIX早期的进程间通信手段进行了系统的改进和扩充,形成了“systemVIPC”,其通信进程主要局限在单个计算机内;后者则跳过了该限制,形成了基于套接口(socket)的进程间通信机制。而Linux则把两者的优势都继承了下来,如图8.1所示。•UNIX进程间通信(IPC)方式包括管道、FIFO、信号。最初UNIX的进程间通信基于Socket进程间通信基于SystemV进程间通信POSIX进程间通信Linux进程间通信图8.1进程间通信发展历程•SystemV进程间通信(IPC)包括SystemV消息队列、SystemV信号灯、SystemV共享内存区。•Posix进程间通信(IPC)包括Posix消息队列、Posix信号灯、Posix共享内存区。现在在Linux中使用较多的进程间通信方式主要有以下几种。(1)管道(Pipe)及有名管道(namedpipe):管道可用于具有亲缘关系进程间的通信,有名管道,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。(2)信号(Signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知接受进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一样的。《嵌入式Linux应用程序开发详解》——第8章、进程间通信华清远见嵌入式Linux应用开发班培训教材(3)消息队列:消息队列是消息的链接表,包括Posix消息队列systemV消息队列。它克服了前两种通信方式中信息量有限的缺点,具有写权限的进程可以向消息队列中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读取消息。(4)共享内存:可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种通信方式需要依靠某种同步机制,如互斥锁和信号量等。(5)信号量:主要作为进程间以及同一进程不同线程之间的同步手段。(6)套接字(Socket):这是一种更为一般的进程间通信机制,它可用于不同机器之间的进程间通信,应用非常广泛。本章会详细介绍前4种进程通信方式,对第5种通信方式将会在第10章中单独介绍。8.2管道通信8.2.1管道概述细心的读者可能会注意到本书在第2章中介绍“ps”的命令时提到过管道,当时指出了管道是Linux中很重要的一种通信方式,它是把一个程序的输出直接连接到另一个程序的输入,这里仍以第2章中的“ps–ef|grepntp”为例,描述管道的通信过程,如图8.2所示。内核管道进程ps-ef进程grepntp图8.2管道的通信过程管道是Linux中进程间通信的一种方式。这里所说的管道主要指无名管道,它具有如下特点。•它只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程之间)。•它是一个半双工的通信模式,具有固定的读端和写端。•管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等华清远见——嵌入式培训专家应用开发班培训教材函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。8.2.2管道创建与关闭1.管道创建与关闭说明管道是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符fds[0]和fds[1],其中fds[0]固定用于读管道,而fd[1]固定用于写管道,如图8.3所示,这样就构成了一个半双工的通道。内核管道进程ps-ef进程grepntp图8.3Linux中管道与文件描述符的关系管道关闭时只需将这两个文件描述符关闭即可,可使用普通的close函数逐个关闭各个文件描述符。注意一个管道共享了多对文件描述符时,若将其中的一对读写文件描述符都删除,则该管道就失效。2.管道创建函数创建管道可以通过调用pipe来实现,下表8.1列出了pipe函数的语法要点。表8.1pipe函数语法要点所需头文件#includeunistd.h函数原型intpipe(intfd[2])函数传入值fd[2]:管道的两个文件描述符,之后就可以直接操作这两个文件描述符成功:0函数返回值出错:-1《嵌入式Linux应用程序开发详解》——第8章、进程间通信华清远见嵌入式Linux应用开发班培训教材3.管道创建实例创建管道非常简单,只需调用函数pipe即可,如下所示:/*pipe.c*/#includeunistd.h#includeerrno.h#includestdio.h#includestdlib.hintmain(){intpipe_fd[2];/*创建一无名管道*/if(pipe(pipe_fd)0){printf(pipecreateerror\n);return-1;}elseprintf(pipecreatesuccess\n);/*关闭管道描述符*/close(pipe_fd[0]);close(pipe_fd[1]);}程序运行后先成功创建一个无名管道,之后再将其关闭。8.2.3管道读写1.管道读写说明用pipe函数创建的管道两端处于一个进程中,由于管道是主要用于在不同进程间通信的,因此这在实际应用中没有太大意义。实际上,通常先是创建一个管道,再通过fork()函数创建一子进程,该子进程会继承父进程所创建的管道,这时,父子进程管道的文件描述符对应关系就如图8.4所示。华清远见——嵌入式培训专家[0]fd[1]子进程fd[0]fd[1]内核管道图8.4父子进程管道的文件描述符对应关系这时的关系看似非常复杂,实际上却已经给不同进程之间的读写创造了很好的条件。这时,父子进程分别拥有自己的读写的通道,为了实现父子进程之间的读写,只需把无关的读端或写端的文件描述符关闭即可。例如在图8.5中把父进程的写端fd[1]和子进程的读端fd[0]关闭。这时,父子进程之间就建立起了一条“子进程写入父进程读”的通道。父进程fd[0]fd[1]子进程fd[0]fd[1]内核管道图8.5关闭父进程fd[1]和子进程fd[0]同样,也可以关闭父进程的fd[0]和子进程的fd[1],这样就可以建立一条“父进程写,子进程读”的通道。另外,父进程还可以创建多个子进程,各个子进程都继承了相应的fd[0]和fd[1],这时,只需要关闭相应端口就可以建立其各子进程之间的通道。想一想为什么无名管道只能建立具有亲缘关系的进程之间?《嵌入式Linux应用程序开发详解》——第8章、进程间通信华清远见嵌入式Linux应用开发班培训教材2.管道读写实例在本例中,首先创建管道,之后父进程使用fork函数创建子进程,之后通过关闭父进程的读描述符和子进程的写描述符,建立起它们之间的管道通信。/*pipe_rw.c*/#includeunistd.h#includesys/types.h#includeerrno.h#includestdio.h#includestdlib.hintmain(){intpipe_fd[2];pid_tpid;charbuf_r[100];char*p_wbuf;intr_num;memset(buf_r,0,sizeof(buf_r));/*创建管道*/if(pipe(pipe_fd)0){printf(pipecreateerror\n);return-1;}/*创建一子进程*/if((pid=fork())==0){printf(\n);/*关闭子进程写描述符,并通过使父进程暂停2秒确保父进程已关闭相应的读描述符*/close(pipe_fd[1]);sleep(2);/*子进程读取管道内容*/if((r_num=read(pipe_fd[0],buf_r,100))0){printf(%dnumbersreadfromthepipeis%s\n,r_num,buf_r);}/*关闭子进程读描述符*/close(pipe_fd[0]);exit(0);}华清远见——嵌入式培训专家(pid0){/*/关闭父进程读描述符,并分两次向管道中写入HelloPipe*/close(pipe_fd[0]);if(write(pipe_fd[1],Hello,5)!=-1)printf(parentwrite1success!\n);if(write(pipe_fd[1],Pipe,5)!=-1)printf(parentwrite2success!\n);/*关闭父进程写描述符*/close(pipe_fd[1]);sleep(3);/*收集子进程退出信息*/waitpid(pid,NULL,0);exit(0);}}将该程序交叉编译,下载到开发板上的运行结果如下所示:[root@(none)1]#./pipe_rw2parentwrite1success!parentwrite2success!10numbersreadfromthepipeisHelloPipe3.管道读写注意点•只有在管道的读端存在时向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIFPIPE信号(通常Brokenpipe错误)。•向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读取管道缓冲区中的数据,那么写操作将会一直阻塞。•父子进程在运行时,它们的先后次序并不能保证,因此,在这里为了保证父进程已经关闭了读描述符,可在子进程中调用sleep函数。8.2.4标准流管道1.标准流管道函数说明与Linux中文件操作有基于文件流的标准I/O操作一样,管道的操作也支持基于文件流的模式。这种基于文件流的管道主要是用来创建一个连接到另一个进程的管道,这里的“另一个进程”也就是一个可以进行一定操作的可执行文件,例如,用户执行“catpopen.c”或者自己编写的程序“hello”等。由于这一类操作很常用,因此标准流管道就将一系列的创建《嵌入式Linux应用程序开发详解》——第8章、进程间通信华清远见嵌入式Linux应用开发班培训教材过程合并到一个函数popen中完成。它所完成的工作有以下几步。•创建一个管道。•fork
本文标题:嵌入式linux应用程序开发技术详解 8
链接地址:https://www.777doc.com/doc-4805474 .html