【Linux课程学习】第十九弹---深入理解进程间通信---匿名管道,命名管道,多匿名管道的BUG
我们知道,进程是具有独立性的,那么我们想要让一个进程从另外一个进程得到信息,我们要怎么做呢?这就有了进程间通信。进程间通信的本质是让不同的进程看到同一份资源。这块内存的特点:由操作系统(OS)创建,是内存级空间(没必要到达磁盘这一级去)。管道式一种古老的通信方式,它设计不要专门去设计一套通信,而是用文件进行修改就能通信的方式。我们把一个进程连接到另一个进程的数据流称为管道。管道的特性是面向字节流的
🎁个人主页:我们的五年
🔍系列专栏:Linux课程学习
🌷追光的人,终会万丈光芒
🎉欢迎大家点赞👍评论📝收藏⭐文章
Linux学习笔记:
https://blog.csdn.net/djdjiejsn/category_12669243.html
前言:
从本篇开始,就要来谈谈进程间通信。方式有很多,设计的模式也不一样。根据后面网络的编程的需求,某些进程间通信可能用的更广。文章会从进程间通信的介绍,匿名管道,命名管道,system V 共享内存讲解,system V可能会放在下面文章讲解。
目录
一.进程间通信介绍
我们知道,进程是具有独立性的,那么我们想要让一个进程从另外一个进程得到信息,我们要怎么做呢?这就有了进程间通信。
进程间通信的本质是:让不同的进程看到同一份资源。
这块内存的特点:由操作系统(OS)创建,是内存级空间(没必要到达磁盘这一级去)。
1.1进程间通信的目的:
🥬1.数据传输:一个进程需要把它的数据发生给另外一个进程。
🥬2.资源共享:多个进程直接共享同样的资源。
🥬3.通知事件:一个进程向另一个进程或者一组进程发生消息,通知它们发生某种事件。
🥬4.进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有陷入和异常,并且能及时知道它的状态信息。
上面所有的目的都是因为进程间通信,可以让不同进程间传递数据。
1.2进程间通信的发展:
管道 |
System V进程间通信 |
POXIS进程间通信 |
1.3进程间通信的分类:
下面是本地通信的分类,除了本地通信,还有网络通信。
管道:匿名管道(pipe),命名管道(FIFO)。
System V:System 消息队列,System V共享内存,System V 信号量。
POSXI IPC:消息队列,共享内存,信号量,互斥量,条件变量,读写锁。
二.匿名管道
2.1管道的介绍:
管道式一种古老的通信方式,它设计不要专门去设计一套通信,而是用文件进行修改就能通信的方式。
我们把一个进程连接到另一个进程的数据流称为管道。
管道的特性是面向字节流的。单向的。
在创建多管道的时候,会有BUG。父子进程能进程同样的文件描述符,但在创建躲管道的时候,这也是他缺点。
匿名管道也是只能用于有亲缘关系之间的进程进行通信。因为他们要进行继承父进程的文件描述符,才能让他们看到同样的空间。
在Ubuntu系统中,管道的大小一般是64KB。
2.2匿名管道创建:
2.2.1函数介绍:
头文件:
#include <unistd.h>函数原型:
int pipe(int pipefd[2]);
2.2.2函数解读:
🎒功能:创建一无名管道。
🎒参数:传入的fd表示的int数组类型的指针。如果成功,fd[0]中表示读端的文件描述符,fd[1]表示的写端的文件表述符。
🎒返回值:成功返回0,错误返回错误代码。
2.3匿名管道实现通信实现:
下面的代码实现的是,打开一个匿名管道。然后返回的是文件描述符。通过创建子进程,子进程继承父进程的文件描述符。这样就让父子进程看到同一个文件。因为要保证单向性,最好就是把没有用的端关闭。这样尽量保证安全性。
2.3.1关闭相应的端口:
比如父进程只进行读取数据,那么他就把写入端关闭。子进程进行写入,那么子进程就把读端关闭。
2.3.2写端关闭,读端如何反应?
另外如果管道没有东西,读端就阻塞住,等待写端写入数据。如果写端关闭,那么读端把管道里的数据读完以后,就会读到空。返回的就是0。这样就可以判断可以关闭读端了。
2.3.3读端关闭,写端如何反应?
读端关闭,写端进程会发生错误,管道里的数据也会被处理。匿名管道读端关闭后,写端进程继续写入数据会面临错误返回或者收到终止信号等情况,并且管道内剩余未读的数据也会被相应处理掉。
OS通过信号杀死进程。通过kill -13杀死进程。IPC!!!
2.3.4其他情况:
管道为空&&管道正常,那么read阻塞。
管道为满&&管道正常,那么write阻塞。
写入的数据量不大于PIPE-BUF时,Linux保证写入的原子性。
写入的数据大于PIPE-BUF时,Linux保证写入的原子性。
#include <iostream>
#include <unistd.h>
// child send message to father
int main()
{
int fd[2];
int n = pipe(fd);
// 匿名管道创建失败,成功返回0,失败返回非0
if (n != 0)
{
std::cerr << "pipe error!\n"
<< std::endl;
return 1;
}
// 创建子进程,并且父子进程关闭对应的文件fd。
int id = fork();
if (id < 0)
{
std::cerr << "fork error!\n"
<< std::endl;
}
// child 关闭读端
if (id == 0)
{
::close(fd[0]);
int num = 0;
while (1)
{
std::string s;
std::cin >> s;
num += ::write(fd[1], s.c_str(), s.size()-1);
std::cout << "num:" << num << std::endl;
}
}
// father 关闭写端
else
{
::close(fd[1]);
while (1)
{
char buf[1024];
int end = ::read(fd[0], buf, sizeof(buf));
if (end > 0)
{
buf[end] = '\0';
}
else if (end == 0)
{
close(fd[0]);
break;
}
printf("father:%s\n\n", buf);
}
}
return 0;
}
2.4多个匿名管道产生的BUG
在我们pipe创建多个管道的时候,在第一次把一个管道创建好以后,我们会进行把某个进程的读端打开,某个进程的写端关闭。然后再去创建下一个匿名管道。
这个时候我们fork出来的子进程,还会继承父进程的一系列的文件描述符,其中就包括了指向上一个匿名管道的读端或者写端。所以我们要进行一一的关闭。
创建一个管道,对应要关一次,创建两个匿名管道,就要关两次了,以此类推。
三.命名管道
3.1命名管道的介绍:
命名管道不再受血液的限制,可以让任意两个进程进行通信。命名管道是一种特殊的管道。命名管道会有一个真实的文件在指定文件夹中。
命名管道其实就是一个文件,只是这个文件被OS经过特殊的处理,只占用内核的空间,不会去进行磁盘级的操作。
它和匿名管道的区别主要体现在:
1.匿名有pipe函数创建并且打开。
2.命名管道由mkfifo创建,由open打开。
除了这些打开方式的区别以外,匿名管道和命令管道的用法基本相同。
3.2如何创建管道?
3.2.1在命令行中创建一个命名管道:
在命令行中输入下面的命令:
mkfifo share
mkfifo表示创建一个命名管道,后面的是命名管道的名称。
3.2.2通过函数创建:
头文件:
#include <sys/types.h>
#include <sys/stat.h>
函数原型:
int mkfifo(const char *pathname, mode_t mode);
函数解读:
pathname表示的是创建命名管道的路径,可以是./fifofile这样的路径,然后就会在当前路径创建命名管道。命名管道其实就是一个文件,只是这个文件被OS经过特殊的处理,只占用内核的空间,不会去进行磁盘级的操作。
但命令管道被创建,先打开的是写,而读端还没启动,那么就会阻塞住。不然直接就是写端有,读端没有,就会被OS杀死。所以在这一点,还是进行了特殊处理的。
四.关于Makefile文件的编写:
BIN表示的是目标程序,SRC是源文件,OBJ是经过预处理,编译和汇编以后的文件。
CC表示所用的编译器,FLAGS和LDFLAGS是选项。
test是查看所有的.o和.cc。
BIN=pipe
SRC=$(wildcard *.cc)
OBJ=$(SRC:.cc=.o)
CC=g++
FLAGS=-c -Wall -std=c++11
LDFLAGS=-o
$(BIN):$(OBJ)
$(CC) $(LDFLAGS) $@ $^
%.o:%.cc
$(CC) $(FLAGS) $<
.PHONY:clean
clean:
rm -rf $(BIN) $(OBJ)
.PHONY:test
test:
@echo $(SRC)
@echo $(OBJ)
文章已经接近尾声,后续会进行更新System V共享内存,可以关注一下新的博客。如果看懂文章欢迎到下面进行投票。你的支持是我前进的最大动力。
🌷🌷🌷🌷🌷🌷🌷🌷🌷🌷🌷🌷🌷🌷🌷🌷🌷🌷🌷🌷🌷🌷🌷🌷
更多推荐
所有评论(0)