资源描述
从实践中学嵌入式Linux应用程序开发华清远见嵌入式学院第四章 嵌入式LINUX进程间通信4.1 Linux下进程间通信概述通过前面的学习,读者已经知道了进程是一个程序的一次执行,是系统资源分配的最小单元。这里所说的进程一般是指运行在用户态的进程,而由于处于用户态的不同进程间是彼此隔离的,就像处于不同城市的人们,他们必须通过某种方式来进行通信,例如人们现在广泛使用的手机等方式。本章将讲述如何建立这些不同的通信方式,就像人们有多种通信方式一样。Linux下的进程通信手段基本上是从UNIX平台上的进程通信手段继承而来的。而对UNIX发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间的通信方面的侧重点有所不同。前者是对UNIX早期的进程间通信手段进行了系统的改进和扩充,形成了“System V IPC”,其通信进程主要局限在单个计算机内;后者则跳过了该限制,形成了基于套接口(Socket)的进程间通信机制。而Linux则把两者的优势都继承下来,如图4.1所示。图4.1 进程间通信发展历程UNIX进程间通信(IPC)方式包括管道、FIFO及信号。System V进程间通信(IPC)包括System V消息队列、System V信号量及System V共享内存区。Posix 进程间通信(IPC)包括Posix消息队列、Posix信号量及Posix共享内存区。现在在Linux中使用较多的进程间通信方式主要有以下几种。管道(Pipe)及有名管道(Named Pipe):管道可用于具有亲缘关系进程间的通信。有名管道除具有管道所具有的功能外,还允许无亲缘关系进程间的通信。信号(Signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一样的。消息队列(Messge Queue):消息队列是消息的链接表,包括Posix消息队列和System V消息队列。它克服了前两种通信方式中信息量有限的缺点,具有写权限的进程可以按照一定的规则向消息队列中添加新消息;对消息队列有读权限的进程则可以从消息队列中读取消息。共享内存(Shared Memory):可以说这是最有效的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种通信方式需要依靠某种同步机制,如互斥锁和信号量等。信号量(Semaphore):主要作为进程之间及同一进程的不同线程之间的同步和互斥手段。套接字(Socket):这是一种更为通用的进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。本章将详细介绍前5种进程通信方式。4.2 管道通信4.2.1 管道简介管道是Linux中进程间通信的一种方式,它把一个程序的输出直接连接到另一个程序的输入。Linux的管道主要包括两种:无名管道和有名管道。1.无名管道无名管道是Linux中管道通信的一种原始方法,如图4.2(左)所示,它具有如下特点:它只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程之间)。它是一个半双工的通信模式,具有固定的读端和写端。管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的read()、write()等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。2.有名管道(FIFO)有名管道是对无名管道的一种改进,如图4.2(右)所示,它具有如下特点:它可以使互不相关的两个进程实现彼此通信。该管道可以通过路径名来指出,并且在文件系统中是可见的。在建立了管道之后,两个进程就可以把它当做普通文件一样进行读写操作,使用非常方便。FIFO严格地遵循先进先出规则,对管道及FIFO的读总是从开始处返回数据,对它们的写则是把数据添加到末尾,它们不支持如lseek()等文件定位操作。图4.2 无名管道(左)和有名管道(右)4.2.2 无名管道系统调用1.管道创建与关闭说明管道是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符fd0和fd1,其中fd0固定用于读管道,而fd1固定用于写管道,如图4.3所示,这样就构成了一个半双工的通道。图4.3 无名管道的读写机制管道关闭时只需将这两个文件描述符关闭即可,可使用普通的close()函数逐个关闭各个文件描述符。2.管道创建函数创建管道可以通过调用pipe()来实现。表4.1列出了pipe()函数的语法要点。表4.1 pipe()函数语法要点3.管道读写说明用pipe()函数创建的管道两端处于一个进程中,由于管道是主要用于在不同进程间通信的,因此在实际应用中没有太大意义。实际上,通常先是创建一个管道,再调用fork()函数创建一个子进程,该子进程会继承父进程所创建的管道,这时,父子进程管道的文件描述符对应关系如图4.4所示。图4.4 父子进程管道的文件描述符对应关系此时的关系看似非常复杂,实际上却已经给不同进程之间的读写创造了很好的条件。父子进程分别拥有自己的读写通道,为了实现父子进程之间的读写,只需把无关的读端或写端的文件描述符关闭即可。例如在图4.5中将父进程的写端fd1和子进程的读端fd0关闭。此时,父子进程之间就建立起了一条“子进程写入父进程读取”的通道。图4.5 关闭父进程fd1和子进程fd0同样,也可以关闭父进程的fd0和子进程的fd1,这样就可以建立一条“父进程写入子进程读取”的通道。另外,父进程还可以创建多个子进程,各个子进程都继承了相应的fd0和fd1。这时,只需关闭相应端口就可以建立其各子进程间的通道。4.管道读写注意点管道读写需注意以下几点:只有在管道的读端存在时,向管道写入数据才有意义。否则,向管道写入数据的进程将收到内核传来的SIGPIPE信号(通常为Broken pipe错误)。向管道写入数据时,Linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读取管道缓冲区中的数据,那么写操作将会一直阻塞。父子进程在运行时,它们的先后次序并不能保证。因此,为了保证父子进程已经关闭了相应的文件描述符,可在两个进程中调用sleep()函数。当然这种调用不是很好的解决方法,在后面学到进程之间的同步机制与互斥机制后,请读者自行修改本小节的实例程序。5.使用实例在本例中,首先创建管道,之后父进程使用fork()函数创建子进程,最后通过关闭父进程的读描述符和子进程的写描述符,建立起它们之间的管道通信。/* pipe.c */#include #include #include #include #include #define MAX_DATA_LEN 256#define DELAY_TIME 1int main() pid_t pid; int pipe_fd2; char bufMAX_DATA_LEN; const char data = Pipe Test Program; int real_read, real_write; memset(void*)buf, 0, sizeof(buf); if (pipe(pipe_fd) 0) printf(%d bytes read from the pipe is %sn, real_read, buf); close(pipe_fd0); /* 关闭子进程读描述符 */ exit(0); else if (pid 0) /* 父进程关闭读描述符,并通过使父进程暂停1s 等待子进程已关闭相应的写描述符 */ close(pipe_fd0); sleep(DELAY_TIME); if(real_write = write(pipe_fd1, data, strlen(data) != -1) printf(Parent wrote %d bytes : %sn, real_write, data); close(pipe_fd1); /* 关闭父进程写描述符 */ waitpid(pid, NULL, 0); /* 收集子进程退出信息 */ exit(0); 将该程序交叉编译,下载到开发板上的运行结果如下:$ ./pipeParent wrote 17 bytes : Pipe Test Program17 bytes read from the pipe is Pipe Test Program4.2.4 有名管道(FIFO)有名管道的创建可以使用函数mkfifo(),该函数类似于文件中的open()操作,可以指定管道的路径和打开的模式。用户还可以在命令行使用“mknod 管道名 p”来创建有名管道。在创建管道成功后,就可以使用open()、read()和write()这些函数了。与普通文件的开发设置一样,对于为读而打开的管道可在open()中设置O_RDONLY,对于为写而打开的管道可在open()中设置O_WRONLY,在这里与普通文件不同的是阻塞问题。由于普通文件在读写时不会出现阻塞问题,而在管道的读写中却有阻塞的可能,这里的非阻塞标志可以在open()函数中设定为O_NONBLOCK。下面分别对阻塞打开和非阻塞打开的读写进行讨论。对于读进程:若该管道是阻塞打开,且当前FIFO内没有数据,则对读进程而言将一直阻塞到有数据写入。若该管道是非阻塞打开,则不论FIFO内是否有数据,读进程都会立即执行读操作。即如果FIFO内没有数据,则读函数将立刻返回0。对于写进程:若该管道是阻塞打开,则写操作将一直阻塞到数据可以被写入。若该管道是非阻塞打开而不能写入全部数据,则读操作进行部分写入或者调用失败。表4.4列出了mkfifo()函数的语法要点。表4.4 mkfifo()函数语法要点表4.5再对FIFO相关的出错信息进行归纳,以方便用户查错。表4.5 FIFO相关的出错信息下面的实例包含两个程序,一个用于读管道,另一个用于写管道。其中在读管道的程序中创建管道,并且作为main()函数里的参数由用户输入要写入的内容;读管道的程序会读出用户写入到管道的内容。这两个程序采用的是阻塞式读写管道模式。写管道的程序如下:/* fifo_write.c */#include #include #include #include #include #include #include #define MYFIFO /tmp/myfifo /* 有名管道文件名 */#define MAX_BUFFER_SIZE PIPE_BUF /* 定义在limits.h 中 */int main(int argc, char * argv) /* 参数为即将写入的字符串 */ int fd; char buffMAX_BUFFER_SIZE; int nwrite; if(argc 0) printf(Write %s to FIFOn, buff); close(fd); exit(0);读管道程序如下:/* fifo_read.c */(头文件和宏定义同fifo_write.c)int main() char buffMAX_BUFFER_SIZE; int fd; int nread; /* 判断有名管道是否已存在,若尚未创建,则以相应的权限创建 */ if (access(MYFIFO, F_OK) = -1) if (mkfifo(MYFIFO, 0666) 0) printf(Read %s from FIFOn, buff); close(fd); exit(0);为了能够较好地观察运行结果,需要把这两个程序分别在两个终端里运行。在这里首先启动读管道程序。读管道进程在建立管道后就开始循环地从管道里读出内容,如果没有数据可读,则一直阻塞到写管道进程向管道写入数据。在启动了写管道程序后,读进程能够从管道里读出用户的输入内容,程序运行结果如下。终端一:$ ./fifo_readRead FIFO from FIFORead Test from FIFORead Program from FIFO终端二:$ ./fifo_write FIFOWrite FIFO to FIFO$ ./fifo_write TestWrite Test to FIFO$ ./fifo_write ProgramWrite Program to FIFO4.3 信号通信4.3.1 信号概述信号是在软件层次上对中断机制的一种模拟。在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。它可以在任何时候发给某一进程,而无须知道该进程的状态。如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它为止;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。 信号是进程间通信机制中唯一的异步通信机制,可以看做是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过Posix实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。信号事件的发生有两个来源:硬件来源(如我们按下了键盘上的按钮或者出现其他硬件故障);软件来源,最常用发送信号的系统函数有kill()、raise()、alarm()、setitimer()和sigqueue()等,软件来源还包括一些非法运算等操作。 进程可以通过3种方式来响应一个信号。1. 忽略信号 忽略信号即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILL和SIGSTOP。2. 捕捉信号 定义信号处理函数,当信号发生时,执行相应的处理函数。3. 执行默认操作 Linux对每种信号都规定了默认操作,如表4.6所示。表4.6 常见信号的含义及其默认操作一个完整的信号生命周期可以分为3个重要阶段,这3个阶段由4个重要事件来刻画的:信号产生、信号在进程中注册、信号在进程中注销、执行信号处理函数。相邻两个事件的时间间隔构成信号生命周期的一个阶段。这里信号的产生、注册、注销等是指信号的内部实现机制,而不是信号的函数实现。因此,信号注册与否与发送信号函数(如kill()等)及信号安装函数(如signal()等)无关,只与信号值有关。 信号处理有多种方式,一般是由内核完成的,当然也可以由用户进程来完成。 信号的处理包括信号的发送、捕获及信号的处理,它们有各自对应的函数。发送信号的函数:kill()、raise()。捕捉信号的函数:alarm()、pause()。处理信号的函数:signal()、sigaction()。4.4 信号量4.4.1 信号量概述在多任务操作系统环境下,多个进程会同时运行,多个进程可能为了完成同一个任务相互协作,这就形成了进程间的同步关系。而在不同进程间,为了争夺有限的系统资源(硬件或软件资源)会进入竞争状态,这就是进程间的互斥关系。进程间的同步与互斥关系存在的根源在于临界资源。临界资源是在同一个时刻只允许有限个(通常只有一个)进程可以访问(读)或修改(写)的资源,通常包括硬件资源(处理器、内存、存储器及其他外围设备等)和软件资源(共享代码段、共享结构和变量等)。 访问临界资源的代码叫做临界区,临界区本身也会成为临界资源。信号量是用来解决进程间的同步与互斥问题的一种进程间通信机制,包括一个称为信号量的变量和在该信号量下等待资源的进程等待队列,以及对信号量进行的两个原子操作(P操作和V操作)。其中信号量对应于某一种资源,取一个非负的整型值。信号量值指的是当前可用的该资源的数量,若等于0则意味着目前没有可用的资源。最简单的信号量只能取0和1两种值,这种信号量叫做二维信号量。P/V原子操作的具体定义如下。P操作:如果有可用的资源(信号量值0),则占用一个资源(给信号量值减1,进入临界区代码);如果没有可用的资源(信号量值=0),则被阻塞,直到系统将资源分配给该进程(进入等待队列,一直等到资源轮到该进程)。V操作:如果在该信号量的等待队列中有进程在等待资源,则唤醒一个阻塞进程;如果没有进程等待它,则释放一个资源(给信号量值加1)。常见的使用信号量访问临界区的伪代码如下:/* 设R为某种资源,S为资源R的信号量 */INIT_VAL(S); /* 对信号量S进行初始化 */非临界区;P(S); /* 进行P操作 */临界区(使用资源R); /* 只有有限个(通常只有一个)进程被允许进入该区 */V(S); /* 进行V操作 */非临界区;4.5 共享内存共享内存是一种最为高效的进程间通信方式,因为进程可以直接读写内存,不需要任何数据的复制。为了在多个进程间交换信息,内核专门留出了一块内存区,这段内存区可以由需要访问的进程将其映射到自己的私有地址空间。因此,进程就可以直接读写这一内存区而不需要进行数据的复制,从而大大提高了效率。当然,由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等。其原理示意图如图4.7所示。图4.7 共享内存原理示意图共享内存的实现分为两个步骤:第一步是创建共享内存,这里用到的函数是shmget(),也就是从内存中获得一段共享内存区域;第二步是映射共享内存,也就是把这段创建的共享内存映射到具体的进程空间中,这里使用的函数是shmat()。到这里,就可以使用这段共享内存了,也就是可以使用不带缓冲的I/O读写命令对其进行操作。除此之外,还有撤销映射的操作,其函数为shmdt()。表4.20 shmget()函数语法要点表4.21 shmat()函数语法要点表4.22 shmdt()函数语法要点以下实例说明了如何使用基本的共享内存函数。首先创建一个共享内存区(采用的共享内存的键值为IPC_PRIVATE,是因为本实例中创建的共享内存是父子进程间的共用部分)。然后创建子进程,在父子两个进程中将共享内存分别映射到各自的进程地址空间中。父进程先等待用户输入,然后将用户输入的字符串写入到共享内存,之后向共享内存的头部写入“WROTE”字符串表示父进程已成功写入数据。子进程一直等到共享内存的头部字符串为“WROTE”,然后将共享内存的有效数据(在父进程中用户输入的字符串)在屏幕上打印。父子两个进程在完成以上工作后,分别解除与共享内存的映射关系。最后在子进程中删除共享内存。因为共享内存自身并不提供同步机制,所以应额外实现不同进程间的同步(如信号量)。为了简单起见,在本实例中用标志字符串来实现非常简单的父子进程间的同步。这里要介绍的一个命令是ipcs,用于报告进程间通信机制状态,它可以查看共享内存、消息队列等各种进程间通信机制的情况,这里使用了system()函数调用shell命令“ipcs”。程序源代码如下:/* shmem.c */#include #include #include #include #include #include #define BUFFER_SIZE 2048int main() pid_t pid; int shmid; char *shm_addr; char flag = WROTE; char *buff; /* 创建共享内存 */ if (shmid = shmget(IPC_PRIVATE, BUFFER_SIZE, 0666) 0) perror(shmget); exit(1); else printf(Create shared-memory: %dn,shmid); /* 显示共享内存情况 */ system(ipcs -m); pid = fork(); if (pid = -1) perror(fork); exit(1); else if (pid = 0) /* 子进程处理 */ /* 映射共享内存 */ if (shm_addr = shmat(shmid, 0, 0) = (void*)-1) perror(Child: shmat); exit(1); else printf(Child: Attach shared-memory: %pn, shm_addr); system(ipcs -m); /* 通过检查在共享内存的头部是否有标志字符串“WROTE”来确认父进程已经向共享内存写入有效数据 */ while (str ncmp(shm_addr, flag, strlen(flag) printf(Child: Wait for enable data.n); sleep(5); /* 获取共享内存的有效数据并显示 */ strcpy(buff, shm_addr + strlen(flag); printf(Child: Shared-memory :%sn, buff); /* 解除共享内存映射 */ if (shmdt(shm_addr) 0) perror(shmdt); exit(1); else printf(Child: Deattach shared-memoryn); system(ipcs -m); /* 删除共享内存 */ if (shmctl(shmid, IPC_RMID, NULL) = -1) perror(Child: shmctl(IPC_RMID)n); exit(1); else printf(Delete shared-memoryn); system(ipcs -m); else /* 父进程处理 */ /* 映射共享内存 */ if (shm_addr = shmat(shmid, 0, 0) = (void*)-1) perror(Parent: shmat); exit(1); else printf(Parent: Attach shared-memory: %pn, shm_addr); sleep(1); printf(nInput some string:n); fgets(buff, BUFFER_SIZE, stdin); strncpy(shm_addr + strlen(flag), buff, strlen(buff); strncpy(shm_addr, flag, strlen(flag); /* 解除共享内存映射 */ if (shmdt(shm_addr) 0) perror(Parent: shmdt); exit(1); else printf(Parent: Deattach shared-memoryn); system(ipcs -m); waitpid(pid, NULL, 0); printf(Finishedn); exit(0);下面是运行结果,从该结果中可以看出,nattch的值随着共享内存状态的变化而变化,共享内存的值根据不同的系统会有所不同。$ ./shmemCreate shared-memory: 753665/* 在刚创建共享内存时(尚未有任何地址映射)共享内存的情况 */- Shared Memory Segments -key shmid owner perms bytes nattch status0x00000000 753665 david 666 2048 0Child: Attach shared-memory: 0xb7f59000 /* 共享内存的映射地址 */Parent: Attach shared-memory: 0xb7f59000/* 在父子进程中进行共享内存的地址映射后共享内存的情况 */- Shared Memory Segments -key shmid owner perms bytes nattch status0x00000000 753665 david 666 2048 2Child: Wait for enable data.Input some string:Hello /* 用户输入字符串“Hello” */Parent: Deattach shared-memory/* 在父进程中解除共享内存的映射关系后共享内存的情况 */- Shared Memory Segments -key shmid owner perms bytes nattch status0x00000000 753665 david 666 2048 1/* 在子进程中读取共享内存的有效数据并打印 */Child: Shared-memory :helloChild: Deattach shared-memory/* 在子进程中解除共享内存的映射关系后共享内存的情况 */- Shared Memory Segments -key shmid owner perms bytes nattch status0x00000000 753665 david 666 2048 0Delete shared-memory/* 在删除共享内存后共享内存的情况 */- Shared Memory Segments -key shmid owner perms bytes nattch statusFinished4.6 消息队列顾名思义,消息队列就是一些消息的列表,用户可以在消息队列中添加消息和读取消息等。从这点上看,消息队列具有一定的FIFO特性,但是它可以实现消息的随机查询,比FIFO具有更大的优势。同时,这些消息又是存在于内核中的,由“队列ID”来标识。消息队列的实现包括创建或打开消息队列、添加消息、读取消息和控制消息队列4种操作:创建或打开消息队列使用的函数是msgget(),这里创建的消息队列的数量会受到系统消息队列数量的限制;添加消息使用的函数是msgsnd(),它把消息添加到已打开的消息队列末尾;读取消息使用的函数是msgrcv(),它把消息从消息队列中取走,与FIFO不同的是,这里可以取走指定的某一条消息;控制消息队列使用的函数是msgctl(),它可以完成多项功能。 表4.23 msgget()函数语法要点表4.24 msgsnd()函数语法要点表4.25 msgrcv()函数语法要点表4.26 msgctl()函数语法要点下面的实例体现了如何使用消息队列进行两个进程(发送端和接收端)之间的通信,包括消息队列的创建、消息发送与读取、消息队列的撤销和删除等多种操作。消息发送端进程和消息接收端进程间不需要额外实现进程间的同步。在该实例中,发送端发送的消息类型设置为该进程的进程号(可以取其他值),因此接收端根据消息类型来确定消息发送者的进程号。注意这里使用了ftok()函数,它可以根据不同的路径和关键字产生标准的key。linux ftok()函数 系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。ftok原型如下:key_t ftok( char * fname, int id )fname-指定的文件名(该文件必须是存在而且可以访问的),id-子序号,8bit(0-255)。当成功执行的时候,一个key_t值将会被返回,否则 -1 被返回。 在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。例如:指定文件的索引节点号为65538,换算成16进制为 0x010002,而指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。 查询文件索引节点号的方法是: ls -i消息队列发送端的代码如下:/* msgsnd.c */#include #include #include #include #include #include #include #define BUFFER_SIZE 512struct message long msg_type; char msg_textBUFFER_SIZE; int main() int qid; key_t key; struct message msg; /* 根据不同的路径和关键字产生标准的key */if (key = ftok(., a) = -1) perror(ftok); exit(1); /* 创建消息队列 */ if (qid = msgget(key, IPC_CREAT|0666) = -1) perror(msgget); exit(1); printf(Open queue %dn,qid); while(1) printf(Enter some message to the queue:); if (fgets(msg.msg_text, BUFFER_SIZE, stdin) = NULL) puts(no message); exit(1); msg.msg_type = getpid(); /* 添加消息到消息队列 */ if (msgsnd(qid, &msg, strlen(msg.msg_text), 0) 0) perror(message posted); exit(1); if (strncmp(msg.msg_text, quit, 4) = 0) break; exit(0);消息队列接收端的代码如下:/* msgrcv.c */#include #include #include #include #include #include #include #define BUFFER_SIZE 512struct message long msg_type; char msg_textBUFFER_SIZE;int main() int qid; key_t key; struct message msg; /* 根据不同的路径和关键字产生标准的key */ if (key = ftok(., a) = -1) perror(ftok); exit(1); /* 创建消息队列 */ if (qid = msgget(key, IPC_CREAT|0666) = -1) perror(msgget); exit(1); printf(Open queue %dn, qid); do /* 读取消息队列 */ memset(msg.msg_text, 0, BUFFER_SIZE); if (msgrcv(qid, (void*)&msg, BUFFER_SIZE, 0, 0) 0) perror(msgrcv); exit(1); printf(The message from process %d : %s, msg.msg_type, msg.msg_text); while(strncmp(msg.msg_text, quit, 4); /* 从系统内核中移走消息队列 */ if (msgctl(qid, IPC_RMID, NULL) 0) perror(msgctl); exit(1); exit(0);以下是程序的运行结果,输入“quit”则两个进程都将结束。$ ./msgsndOpen queue 327680Enter some message to the queue:first messageEnter some message to the queue:second messageEnter some message to the queue:quit$ ./msgrcvOpen queue 327680The message from process 6072 : first messageThe message from process 6072 : second messageThe message from process 6072 : quit4.8 本章小结进程间通信是嵌入式Linux应用开发中很重要的高级议题,本章讲解了管道、信号、信号量、共享内存及消息队列等常用的通信机制,并添加了经典实例代码。其中,管道通信又分为有名管道和无名管道。信号通信中要着重掌握如何对信号进行适当的处理,如采用信号集等方式。信号量是用于实现进程间的同步和互斥的进程间通信机制。消息队列和共享内存也是很好的进程间通信的手段,其中消息队列解决使用管道时出现的一些问题;共享内存具有很高的效率,并经常以信号量作为同步机制。
展开阅读全文