跟着iMX28x开发套件学linux-067
Posted diskiii
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了跟着iMX28x开发套件学linux-067相关的知识,希望对你有一定的参考价值。
七、linux应用编程之五:管道
进程间通信有多种方式,管道是其中一种。管道分为匿名管道和命名管道,匿名管道仅用于父子进程之间通信,没有实际文件。而命名管道可以实现任意进程间的通信,在系统中需要创建一个fifo文件作为管道。
管道的理解:无论是匿名管道还是命名管道,都可以把管道看做一个文件,进程A给这个文件写数据,进程B从这个文件读数据,那么进程A就可以给进程B传输数据了。而重点在于,匿名管道没有实际文件要怎么实现,还有如何保证进程A,B之间的通信同步(即进程A发送数据的时候,是不是进程B需要这个数据的时候)。
匿名管道
1) 创建管道:匿名管道没有实际的文件,想象出一个虚拟的文件,父进程给这个文件写数据,子进程从这个文件读数据,完成父子进程间的通信。既然想象成文件,那读写数据就需要文件描述符,但是这个文件又是虚拟的,无法通过open()函数获得文件描述符。实际上系统只需要知道哪个是文件描述符即可,文件描述符的内容是什么没有影响。所以可以int声明文件描述符,然后调用int pipe(int pipefd[2]);函数对这个文件描述符进行注册即可,当然要检查注册是否成功,所以有以下代码,创建管道:
int pipefd[2]; if(pipe(pipefd) <0 ){ fprintf(stderr, "creat pipe error\\n"); exit(-1); } else{ printf("creat pipe succeed\\n"); }
PS:代码中文件描述符用了int pipefd[2],一般文件描述符都是int fd,实际上pipefd[2]并不是两个文件的意思,而是一个文件的两个端口,读端口和写端口。linux系统定义pipefd[1]是写端口,pipefd[0]是读端口,匿名管道实际模型更加接近于下图:
2) 将数据写入匿名管道:父子进程都可以是数据的接收方或者数据的发送方,但是不能同时是数据的发送方和接收方,所以匿名通道实际上是一个半双工通信。 假设父进程时发送方,那父进程应当关闭pipefd[0],然后在pipefd[1]写入要发送的数据。父进程发送数据代码如下:
else if(cpid > 0){ //parent process close(pipefd[0]); write(pipefd[1], argv[1], strlen(argv[1])); close(pipefd[1]); if(wait(NULL) != cpid){ printf("p:exit child process error\\n"); exit(-1); } else{ printf("p:exit child process succeed\\n"); exit(0); } }
代码说明:在父进程中先关闭输入端口pipefd[0],接着在pipefd[1]写入要发送的数据,这里的数据是运行程序时的第二个参数。发送完成之后关闭输出端口pipefd[1],最后给子进程发送wait()。
3) 从匿名管道中读取数据:上面程序中父进程已经把数据写入了匿名通道,子进程应当把管道中的数据读取出来,完成一次进程通信。子进程中先关闭匿名通道的输出端口pipe[1],然后读取匿名通道中的内容,判断是否读取完毕,然后将读取到的内容打印到屏幕上,观察与运行程序时的第二个参数是否相同。代码如下:
else if(cpid == 0){ //child process close(pipefd[1]); while(read(pipefd[0], &read_buf[count++], 1) > 0 ); printf("c:%s\\n", read_buf); close(pipefd[0]); exit(0); }
代码说明:在读取数据的时候,应该一个字节一个字节的读,当read()函数返回-1的时候代表数据已经全部读取。
4) 通信同步问题:有四种特殊情况需要注意。
① 写端开启,读端开启,当读端读出数据时,若匿名管道内没有数据,读端进程将在read()处阻塞。
② 写段关闭,读端开启,当读端读出数据时,匿名管道中的数据被全部读出,读端进程的read()将会返回0
③ 写端开启,读端关闭,若写端进程试图运行write()函数时,写端进程将会收到信号SIGPIPE,并终止。
④ 写端开启,读端开启,但是读端没有进行read(),而写端一直运行write(),等到匿名管道被写满之后,写端进程将在write()处阻塞。
5) 匿名管道完整实验代码:
#include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main(int argc, char *argv[]){ pid_t cpid; int pipefd[2]; char read_buf[100] = {0}; int count = 0; if(argc != 2){ fprintf(stderr, "Usage:%s<filename>", argv[0]); exit(-1); } if(pipe(pipefd) <0 ){ fprintf(stderr, "creat pipe error\\n"); exit(-1); } else{ printf("creat pipe succeed\\n"); } cpid = fork(); if(cpid < 0){ fprintf(stderr, "creat child process error\\n"); return -1; } else if(cpid == 0){ //child process close(pipefd[1]); while(read(pipefd[0], &read_buf[count++], 1) > 0 ); printf("c:%s\\n", read_buf); close(pipefd[0]); exit(0); } else if(cpid > 0){ //parent process close(pipefd[0]); write(pipefd[1], argv[1], strlen(argv[1])); close(pipefd[1]); if(wait(NULL) != cpid){ printf("p:exit child process error\\n"); exit(-1); } else{ printf("p:exit child process succeed\\n"); exit(0); } } return -1; }
程序运行结果:
命名管道
1) 创建通道:命名通道是有具体文件的fifo,可以时间无关进程间的通信,所以创建命名通道的过程跟创建文件是一样的。在创建命名通道之前,要用acess(fifo_name, F_OK);函数检查通道文件是否存在,若存在返回0,不存在返回-1,根据返回的结果决定是否创建命名管道。创建过程代码如下:
if(access(fifo_name, F_OK) < 0){ ret = mkfifo(fifo_name, 0777); if(ret < 0){ perror("mkfifo error"); exit(EXIT_FAILURE); } }
2) 写端将数据写进命名管道:跟写数据进文件一样,写入数据之前要先打开文件。需要注意的是,当使用只写(O_WRONLY)模式打开命名管道时,若没有进程使用只读(O_RDONLY)模式打开命名管道,则用只写模式打开命名管道的进程将会阻塞,直到有进程使用只读模式打开命名管道。当然,如果一个进程使用读写(O_RDWR)模式打开命名管道时,不会阻塞。打开命名管道以及写数据进管道的代码如下:
pipefd = open(fifo_name, O_WRONLY); ret = write(pipefd, buffer, bytes); if(ret < 0){ fprintf(stderr, "write data to pipe error\\n"); exit(EXIT_FAILURE); }
3) 读端从命名管道读取数据:与从文件读数据一样,读取数据之前要先打开文件,然后写入,代码如下:
pipefd = open(fifoname, O_RDONLY); bytes = read(pipefd, buffer, BUFSIZE); if(bytes < 0){ fprintf(stderr, "read data error\\n"); exit(EXIT_FAILURE); }
4) 完整实验代码:进程A创建命名管道,并复制一个文件的内容到管道。进程B从管道中读取数据,然后写入到另一个文件中。最后通过MD5校验查看两个代码的内容是否相同。
进程A代码:
#include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <limits.h> #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <string.h> #define BUFSIZE 1024 int main(int argc, char *argv[]){ char fifo_name[] = "./fifo"; int pipefd, datafd; int bytes, ret; char buffer[BUFSIZE] = {0}; if(argc != 2){ fprintf(stderr, "Usage:%s<filename>\\n", argv[0]); exit(EXIT_FAILURE); } if(access(fifo_name, F_OK) < 0){ ret = mkfifo(fifo_name, 0777); if(ret < 0){ perror("mkfifo error"); exit(EXIT_FAILURE); } } pipefd = open(fifo_name, O_WRONLY); datafd = open(argv[1], O_RDONLY); if(!(pipefd>0 && datafd>0)){ fprintf(stderr, "file open error\\n"); exit(EXIT_FAILURE); } //printf("A:Process A sleep 5s\\n"); //sleep(5); bytes = read(datafd, buffer, BUFSIZE); if(bytes < 0){ fprintf(stderr, "read data from file error\\n"); exit(EXIT_FAILURE); } while(bytes > 0){ ret = write(pipefd, buffer, bytes); if(ret < 0){ fprintf(stderr, "write data to pipe error\\n"); exit(EXIT_FAILURE); } bytes = read(datafd, buffer, BUFSIZE); if(bytes < 0){ fprintf(stderr, "read data from file error\\n"); exit(EXIT_FAILURE); } } close(pipefd); close(datafd); printf("A:read from file , write to pipe succeed\\n"); //printf("A:Process A sleep 5s\\n"); //sleep(5); while(1); return 0; }
进程B代码:
#include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <limits.h> #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <string.h> #define BUFSIZE 1024 int main(int argc, char* argv[]) { char fifoname[] = "./fifo"; int pipefd, datafd; int bytes, ret; char buffer[BUFSIZE]; if(argc != 2){ fprintf(stderr, "Usage:%s<filename>", argv[0]); exit(EXIT_FAILURE); } pipefd = open(fifoname, O_RDONLY); datafd = open(argv[1], O_WRONLY|O_CREAT, 0666); if(!(pipefd>0 && datafd>0)){ fprintf(stderr, "file open error\\n"); exit(EXIT_FAILURE); } printf("B:i want to read\\n"); bytes = read(pipefd, buffer, BUFSIZE); if(bytes < 0){ fprintf(stderr, "read data error\\n"); exit(EXIT_FAILURE); } while(bytes > 0){ ret = write(datafd, buffer, bytes); if(ret < 0){ fprintf(stderr, "write data error\\n"); exit(EXIT_FAILURE); } bytes = read(pipefd, buffer, BUFSIZE); if(bytes < 0){ fprintf(stderr, "read data error\\n"); exit(EXIT_FAILURE); } } close(pipefd); close(datafd); printf("ok\\n"); return 0; }
程序运行结果,先运行进程A,再运行进程B:
说明:运行进程A之前先创建进程A要用到的文件a.bin(在程序中文件的打开方式没写O_CREAT),然后运行进程A,注意用的指令是./processA a.bin &,表示程序后台运行,因为以只写模式打开命名管道时,进程会阻塞,如果不后台运行,这个终端将无法操作。接着运行进程B,运行进程B之后,进程A结束阻塞状态,从a.bin中读取数据,然后写到管道中,进程B从管道中读取数据,然后写到b.bin中。运行结束后,运行md5sum指令,查看a.bin和b.bin的MD5码是否一致。
以上是关于跟着iMX28x开发套件学linux-067的主要内容,如果未能解决你的问题,请参考以下文章
#盲盒+码##跟着小白一起学鸿蒙#如何编译OpenHarmony自带APP