我是靠谱客的博主 友好发卡,最近开发中收集的这篇文章主要介绍Unix网络编程学习笔记_进程间通信IPC之管道通信二、管道的特性以及开发环境,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

一、前言

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] = '';/*路径以null结束 */

        if ( (fd = open(buff, O_RDONLY)) < 0) {
                #ifdef RUN_STEP_DEBUG
                        printf("[9]如果打开输入路径失败,向管道(fd2[1])写入提示原因告知客户端n");
                #endif
                snprintf(buff + n, sizeof(buff) - n, ": can't open, %sn",strerror(errno));/*errno在<errno.h>头文件声明*/
                n = strlen(buff);
                write(writefd, buff, n);

        } else {
                #ifdef RUN_STEP_DEBUG
                        printf("[9]如果打开输入路径成功,向管道(fd2[1])写入整个文件内容n");
                #endif
                while ( (n = read(fd, buff, MAXLINE)) > 0)
                        write(writefd, buff, n);
                close(fd);
        }
}

子进程服务端需要用的snprintf()函数需要的头文件如下,其作用是把数据按照一定的格式放在buff中
这里写图片描述
main函数的源码如下:

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

#define MAXLINE (4096)
#define RUN_STEP_DEBUG

void    client(int, int), server(int, int); /*客户端(主进程)和服务端(子进程)函数声明*/

int main(int argc, char **argv){
        int             fd1[2], fd2[2];
        pid_t   pid;/*pid_t类型在<sys/types.h>头文件声明*/

#ifdef RUN_STEP_DEBUG
        printf("[1]创建2个管道n");
#endif
        pipe(fd1);
        pipe(fd2);

#ifdef RUN_STEP_DEBUG
        printf("[2]创建一个子进程n");
#endif
        pid = fork();
        if (pid < 0){
                printf("error in fork!");

        }else if ( pid == 0) {
                close(fd1[1]);
                close(fd2[0]);
                #ifdef RUN_STEP_DEBUG
                        printf("[5]子进程执行服务端函数n");
                #endif
                server(fd1[0], fd2[1]);
                exit(0);
        }else{
                close(fd1[0]);
                close(fd2[1]);

#ifdef RUN_STEP_DEBUG
                printf("[3]执行客户端函数n");
#endif
                client(fd2[0], fd1[1]);

                waitpid(pid, NULL, 0);/* 等待子进程结束 */
                exit(0);
        }
}

以下是pipe、fork、waitpid函数需要的头文件信息。
这里写图片描述
这里写图片描述
其中fork函数的使用方法,
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
这里写图片描述
waitpid函数在次是阻塞当前进程,直到子进程结束,接收子进程的结束状态值。根据某参考书上,在此解释下waitpid函数的作用,在子进程调用exit(0)终止后(fork的子进程),但父进程仍然在运行,内核会马上给父进程产生一个SIGCHLD信号,而父进程并没有捕捉该信号(默认是被忽略的),此时已终止的子进程称为僵尸进程。当父进程的client函数从管道读入最终数据返回,再调用waitpid函数获取该僵尸进程的状态。如果父进程没有调用该函数,那么子进程将托孤给init进程(操作系统的守护进程,一直都在运行)的孤儿进程,内核将向init进程发送另外一个SIGCHLD信号,让守护进程取得该终止子进程的终止状态。

makefile

OBJS=mainpipe.o
%.o:%.c
        gcc -c $< -o $@
all:$(OBJS)
        gcc $(OBJS) -o mainpipe
clean:
        rm *.o mainpipe

四、程序的编译测试

这里写图片描述
运行失败的结果
这里写图片描述
运行权限的问题
这里写图片描述
修改文件夹wwww无读、写、执行权限;
这里写图片描述

下图是程序正常运行的结果:
在这里插入图片描述
免费分享演示代码链接: https://pan.baidu.com/s/1JmuZGE0MFxC_aCFEHbuWHA 提取码: dsje
在这里插入图片描述
在这里插入图片描述

最后

以上就是友好发卡为你收集整理的Unix网络编程学习笔记_进程间通信IPC之管道通信二、管道的特性以及开发环境的全部内容,希望文章能够帮你解决Unix网络编程学习笔记_进程间通信IPC之管道通信二、管道的特性以及开发环境所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(49)

评论列表共有 0 条评论

立即
投稿
返回
顶部