概述
一、前言
1、在从事嵌入式软件的相关工作中,也许会碰到许多编程技巧,比如,进程间的通信就算一种。大家可以用书本上的知识去指导下实践,反过来也可以用实践去检验书本上知识的正确性(6人还可以更深入地修改、完善参考书上的知识)。当然,笔者只是想整理和巩固下相关知识,并以项目的实际应用,让大家好对进程间的通信知识点重要性的定位,都是些基础知识,大神可以直接略过。在某网络编程之IPC参考书上,源码把许多宏和函数的定义都放在某一个文件中,当运行程序时需要依赖某些库文件;运笔者利用开源的思想,把每个案例所用到宏和函数的定义都提取出来,让大家最终都能看到标准的系统调用,单独编译一个主程序即可运行调试。
2、 首先,介绍下我碰到的实际应用案列。我开发嵌入式设备的需求:看上去有点类似手机的拍照功能,但不同的是运行的是Linux操作系统。首先,ARM的显存(Frame Buffer 0和Frame Buffer 1)有两个部分:一个是用来显示菜单部分的(Qt界面),另一个用来显示CCD图像,然后通过ARM 的叠加IP利用Overlay技术(Arm的 硬件模块,根据手册直接调用驱动接口即可实现),将两层进行叠加显示,大家就可以看到菜单界面的照相机了。
3、 那么Qt在Linux是一个进程,我的理解即单独运行的一个main函数,然后另一个进程就是CCD的预览程序(用到了V4L2显示架构的标准应用调用、DMA传输、线程同步、管道通信等技术)。如果想实现一个拍照工程,那么就在Qt菜单上进行拍照操作时,然后告知CCD预览进程并把要拍照的路径名字传递过去,在这里就用到了FIFO(有名管道),CCD预览程序就会抓一帧底层的数据,再通过ARM 的JPEG模块把拿到的一帧数据编码成图片。
4、 另外,加入在Qt界面还有其他的大量数据(从其他模块得到),比如GPS、日期、天气信息。然后我在拍照时,需要把这些数据添加到CCD照片中(以前听说只要你上传你的照片,就能查出你这张照片在何时何地拍出来的,这也没什么好惊讶的。照片包括了RGB图像数据和一些地理、日期的信息字节流数据,不明白的可以去看https://blog.csdn.net/psy6653/article/details/79658144)。这时又要用到进程间的通信,那么就不是用FIFO了,而是用的共享内存。
二、管道的特性以及开发环境
1、本节主要让大家了解下管道(有名和无名管道),个别函数使用的介绍(fork、watipid ),以及无名管道的演示。后续章节笔者会针对FIFO、锁和共享内存部分进行分析,我会用结合项目的代码进行讲解,并给大家共享源码。
无名管道和有名管道(FIFO)在一般的Linux系统中都是半双工通道,从一边写,从另一边读取,结构图如下,
无名管道一般用在有亲缘关系的父进程和子进程间(一个main函数中调用fork()创建子进程)的通信,而FIFO一般用于无亲缘关系的进程间(Linux操作系统下运行的两个main函数程序)的通信,FIFO往往是是用得最多的。
以下是有亲缘关系的父、子进程利用管道进行通信的结构图,用两根管道把它们连接起来,某著名参考书上管道2的数据流好像画反了。。。
管道1:父进程写数据(fd1[1]),子进程读数据(fd1[0]):
管道2:父进程读数据(fd2[0]),子进程写数据(fd2[1]):
所以fd1[1]和fd2[0]运行在父进程,fd1[0]和fd2[1]运行在子进程
平台:X86 PC
系统:LInux 内核3.1.0(Fedora16)
编译器:X86 gcc
绘图软件:Microsoft Office Visio
截图软件:FastStone_Capture
三、源码的分析
以下是管道通信的源代码
如果想查找某函数的头文件以及函数功能、参数的详细说明(标准系统调用),直接在终端用
`man 函数名`
客户端的源码程序运行在父进程,从管道1的fd1[1]端写数据,从管道2的fd2[0]端读数据,
void client(int readfd, int writefd)
{
size_t len;
ssize_t n;
char buff[MAXLINE];
#ifdef RUN_STEP_DEBUG
printf("[4]从控制台(stdin)读入输入路径n");
#endif
//等待终端输入文件的绝对路径
fgets(buff, MAXLINE, stdin);/*stdin在<stdio.h>头文件声明*/
len = strlen(buff);/* fgets()保证以字符串空(即null,ASCII值为0)结束*/
if (buff[len-1] == 'n')
len--;/* 删除输入路径中的换行符(即‘n’,ASCII值为10)*/
#ifdef RUN_STEP_DEBUG
printf("[7]把路径写入进程间管道(fd1[1])n");
#endif
write(writefd, buff, len);
#ifdef RUN_STEP_DEBUG
printf("[8]从进程间管道(fd2[0])读出内容,并输出到标准输出控制台n");
#endif
while ( (n = read(readfd, buff, MAXLINE)) > 0)
write(STDOUT_FILENO, buff, n);/*STDOUT_FILENO在<unistd.h>头文件声明*/
}
客户端需要用的strlen和open函数,man strlen命令即可查到,查找其他的函数类似,
服务端的源码程序运行在子进程,从管道1的fd1[0]端读数据,从管道2的fd2[1]端写数据,源码如下,
void server(int readfd, int writefd){
int fd;
ssize_t n;
char buff[MAXLINE+1];
#ifdef RUN_STEP_DEBUG
printf("[6]从进程间管道(fd1[0])读入输入路径n");
#endif
if ( (n = read(readfd, buff, MAXLINE)) == 0)
buff[n] = '