深入了解linux系统—— 进程间通信之管道

article/2025/6/29 18:33:28

前言

本篇博客所涉及到的代码一同步到本人gitee:testfifo · 迟来的grown/linux - 码云 - 开源中国

一、进程间通信

什么是进程间通信

在之前的学习中,我们了解到了进程具有独立性,就算是父子进程,在修改数据时也会进行写时拷贝;

而进程间通信(IPC)就是指在同一个计算机或者不同的计算机上的不同进程之间进行数据交换和通信的计数。

为什么要进程间通信

每一个进程都有自己的进程地址空间,它们无法访问彼此的数据,也就是进程具有独立性;那为什么要存在通信呢?

因为我们进程之间要进行数据传输、资源共享、通知事件等一系列操作。

  • 数据传输:一个进程要将它的数据发送到另一个进程。
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个/一组进程法信息,通知它们发生了某种事情(例如:进制终止时要通知父进程)
  • 进程控制:一些进程希望可以控制另一个进程的执行(Debug控制进程),此时控制进程就希望它可以拦截另一个进程的所有陷入和异常,并且能及时知道它的改变。

在现实生活中,我们在使用微信进行聊天时,微信在我们的系统中运行就是一个进程,我们发送信息给对方本质上就是我们的微信进程将信息发送到对方系统中的微信进程。这不就是两个进程之间的数据传输吗。

如何通信

我们知道进程是具有独立性的,就算父子进程,在对数据做修改时也会进行写时拷贝;

那进程之间如何进行通信呢?

这里进程之间要进行通信,那前提是这些进程要看到同一块资源啊,总不能这个进程在这一块内存中度写数据,另一个进程在另一块内存读写数据啊。

所以我们进程之间要像进行通信,就先有一块所有进程都能看到的资源,而这一块资源从哪来呢?

只能由OS提供

  • 管道: 建立一个单向或双向的通道来传递信息。
  • 共享内存: 开辟一块双方都能访问的内存区域,直接在上面读写。
  • 消息队列: 把消息放到一个队列里,对方按需取走。
  • 信号/信号量: 发送一个简单的通知或控制指令(如“开始”、“停止”、“资源已释放”)。
  • 套接字: 即使在不同机器上也能通信(虽然通常 IPC 指同一台机器,但套接字也可用于本机通信)。

二、管道

这里我们来了解管道,对于管道,之前在第一次迈入Linux学习的大门——学习指令时,我们曾用过管道;

但是管道它究竟是什么呢?

在这里插入图片描述

如上图所示ps -axj | head -1ps -axj指令(进程)通过管道将自己的执行结果输出给head -1指令(进程)。

这里的|就是管道(匿名管道)。

管道又分为匿名管道和命名管道。

管道原理

那管道可以用来进行进程间通信,那又是如何支持的呢?

要实现进程间通信,就要让不同的进程看到同一份资源;

我们知道,文件是可以被多个进程打开的,那也就是说:一个文件可以被多个进程打开,这样不同的进程都能看到同一个文件

所以可以说,管道是基于文件实现的

在进程打开文件的角度理解:

我们知道,每一个进程都有自己的task_struct,都有一份自己的文件描述符表struct file_struct

那我们不同的进程打开同一个文件时,在每一个进程中都会存在一个文件描述符,那我们的进程不就看到了同一份资源吗。

所以,管道就是基于文件实现的进程间通信

在这里插入图片描述

不同的进程看到的同一份资源,就是文件;至于这个文件从哪里来,肯定是通过操作系统提供的系统调用接口创建的文件,或者现有的文件。

而管道又分为匿名管道和命名管道

匿名管道

管道本质上呢就是文件,只不过这个文件是OS提供的;

现在来了解通过管道来实现进程间通信的操作。

匿名管道,从这个名字上我们可以想象到:不同进程看到的同一份资源(同一个文件),这个文件是没有名字的。

在之前对于文件的操作中,都是使用文件名来对文件进行相关操作,那匿名管道文件没有文件名,如何创建这个匿名管道文件呢?

系统调用

很显然,匿名管道这个文件只能由OS来提供,所以OS也提供了相关的系统调用。

int pipe(int pipefd[2]);

在这里插入图片描述

pipe系统调用的作用就是:创建一个用于进程间通信的单向数据通道。

返回值:

当调用pipe创建管道文件成功时,返回值为0;创建失败时,返回值为-1

参数:

先来看pipe系统调用的参数pipefd[2],这是一个输出型参数;

在调用pipe创建管道文件成功时。fd[0]表示r(以读方式打开)该文件的文件描述符、fd[1]表示w(以写方式打开)该文件的文件描述符。

#include <stdio.h>
#include <unistd.h>
int main()
{int fd[2];int n = pipe(fd);if(n<0){perror("pipe");exit(1);}printf("fd[0] : %d\n",fd[0]);printf("fd[1] : %d\n",fd[1]);return 0;
}

在这里插入图片描述

匿名管道实现原理

  • 当父进程创建子进程时,操作系统会将父进程的task_structmm_structfile_struct等的数据拷贝给子进程,然后在子进程中修改部分数据。
  • 那当我们父进程调用pipe创建了管道文件,此时父进程的文件描述符表中就存在了wr方式打开管道文件的文件描述符;而这时再创建子进程,OS就会将父进程的文件描述符表拷贝给子进程,那这样在子进程的文件描述符表中,不也就存在了以wr方式打开的管道文件的文件描述符了吗。
  • 那这样父子进程不就可以看到同一个资源(文件)了吗,那父进程写(或者子进程写),子进程读(或者父进程读);这样不就可以实现父子进程之间的通信了吗。

在这里插入图片描述

所以,在父进程创建子进程时,子进程的文件描述符表等信息都来源于父进程,OS也会为子进程创建新的struct file

在子进程中,可以通过文件描述符表找到对应的struct file,然后根据struct file就可以找到该文件。

从上图中我们也可以看出,匿名管道它和文件系统没有关系。

当父进程创建子进程时,子进程就会继承(拷贝)父进程的文件描述符表;这样子进程和父进程就看到了同一份资源(文件),就可以进程进程间的通信

在这里插入图片描述

这样在创建子进程之后,再关闭父子进程不用的文件描述符,不就可以实现进程之间的单向通信了吗。

父子进程间通信

了解了匿名管道的实现原理,那我们就应该明白匿名管道只能实现具有血缘关系的进程间的通信(父子进程),因为是通过文件描述符让不同进程看到同一份资源的。

了解了匿名管道实现通信的原理,现在来通过代码实现父子进程的通信。

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
void Write(int fd)
{char buff[1024];snprintf(buff, sizeof(buff), "I am chile, pid : %d, ppid : %d", getpid(), getppid());write(fd, buff, strlen(buff));
}
void Read(int fd)
{char buff[1024];int n = read(fd, buff, sizeof(buff) - 1);if (n < 0) // 读取失败{perror(read);exir(1);}buff[n] = 0;printf("read : %s\n", buff);
}
int main()
{int fd[2];int n = pipe(fd);//创建管道文件if(n<0) exit(1);int id = fork();if(id < 0)  eixt(2);else if(id == 0){//child -> wclose(fd[0]);//关闭不用的文件描述符Write(fd[1]);//向fd[1]文件中写入close(fd[1]);//写入完成后关闭fd[1]exit(1);//子进程退出}//parent -> rclose(fd[1]);//关闭不用的文件描述符Read(fd[0]);//在文件fd[0]中读数据close(fd[0]);//读完成后关闭fd[0]wait(NULL);//父进程等待子进程退出return 0;
}

如上述代码,父进程创建匿名管道文件后,创建子进程;

这里实现子进程写,父进程读;关闭多余的文件描述符,子进程关闭fd[0]、父进程关闭fd[1]

然后子进程向fd[1]中写入,父进程在fd[0]中读取。

子进程在写入完成后关闭fd[1]然后退出;父进程读取完成后关闭fd[0]然后等待子进程退出。

在这里插入图片描述

可以看到,父进程读取到了子进程向匿名管道中写入的数据,并输出到显示器上。

管道通信的四种情况

在上述实现父子进程通信中,子进程只写入了一条信息就退出了,而父进程也只读了一条信息就退出了。

那如果这里的子进程一直中写入信息,父进程也在一直读信息。

管道通信就有四种情况:

  • 数据读的快,写入的慢:读端就会在read阻塞,等待写端写入。
  • 数据读的慢,写入的快:写端一直在写,直到匿名管道被写满;写端等读端读取数据后再写入。
  • 读端正常,写端退出了:读端read就会返回0,表示写端已经退出了。
  • 读端退出了,写端正常OS就会通过信号杀掉正在写入的进程。

1. 读的快,写的慢

当读取管道文件中数据比写入的快时,读端就会阻塞在read处。

void Write(int fd)
{sleep(3);char buff[1024];snprintf(buff, sizeof(buff), "I am chile, pid : %d, ppid : %d", getpid(), getppid());write(fd, buff, strlen(buff));
}
void Read(int fd)
{printf("read begin:\n");char buff[1024];int n = read(fd, buff, sizeof(buff) - 1);if (n < 0) // 读取失败{perror("read");exit(1);}buff[n] = 0;printf("read end\n");printf("read : %s\n", buff);
}

在这里插入图片描述

2. 读的慢,写的快

当我们写端写入的非常快,读端读取数据很慢,写端就会一直写入,直到将匿名管道写满,然后等待读端读取,然后继续进行写入。

这里就对WriteRead函数进行修改,让读取每读取一次就sleep一秒钟;写端就一直写入。

void Write(int fd)
{int cnt = 0;while (1){char buff[1024];snprintf(buff, sizeof(buff), "I am chile, pid : %d, ppid : %d", getpid(), getppid());printf("cnt = %d\n",cnt++);write(fd, buff, strlen(buff));}
}
void Read(int fd)
{// printf("read begin:\n");while (1){sleep(1);char buff[1024];int n = read(fd, buff, sizeof(buff) - 1);if (n < 0) // 读取失败{perror("read");exit(1);}buff[n] = 0;// printf("read end\n");printf("read : %s\n", buff);printf("----------------------------------------------------------\n");//分割每一次读取的数据}
}

在这里插入图片描述

3. 写端退出,读端正常

但写端退出时,读端的read的返回值就为0,表示写端已经退出了。

void Write(int fd)
{int cnt = 0;while (1){char buff[1024];snprintf(buff, sizeof(buff), "I am chile, pid : %d, ppid : %d", getpid(), getppid());printf("cnt = %d\n", cnt++);write(fd, buff, strlen(buff));break;}
}
void Read(int fd)
{while (1){sleep(1);char buff[1024];int n = read(fd, buff, sizeof(buff) - 1);if (n < 0) // 读取失败{perror("read");exit(1);}else if (n == 0){printf("read return value : %d\n", n);continue;}buff[n] = 0;printf("read : %s\n",buff);}
}

这里写端写入一次之后就退出,读端每一秒读取一次。

在这里插入图片描述

一般情况下,当写端退出时,读端也应该要退出了(read返回值为0

4. 写端正常,读端退出

当我们读端退出,而写端正常时,操作系统就会通过信号杀掉写端进程。

也就是说,当一个管道文件没有读端时(读端全部关闭),操作系统就会杀掉所有的写端。

这就好比,你和别人在聊天,别人都不看你发的信息了,你再发就没有任何意义了。

当读端退出时,写端再进行写入就没有任何意义了,并且还会浪费内存空间;操作系统不允许这种情况出现,所以就会通过信号杀掉写端进程。

测试代码:

void Write(int fd)
{int cnt = 0;while (1){char buff[1024];snprintf(buff, sizeof(buff), "I am chile, pid : %d, ppid : %d", getpid(), getppid());printf("cnt = %d\n", cnt++);write(fd, buff, strlen(buff));sleep(1);}
}
void Read(int fd)
{while (1){char buff[1024];int n = read(fd, buff, sizeof(buff) - 1);if (n < 0) // 读取失败{perror("read");exit(1);}else if (n == 0){printf("read return value : %d\n", n);continue;}buff[n] = 0;printf("read : %s\n",buff);sleep(2);break;}
}int main()
{int fd[2];int n = pipe(fd); // 创建管道文件if (n < 0){perror("pipe");exit(1);}int id = fork();if (id < 0){perror("fork");exit(2);}else if (id == 0){// child -> wclose(fd[0]); // 关闭不用的文件描述符Write(fd[1]); // 向fd[1]文件中写入close(fd[1]); // 写入完成后关闭fd[1]exit(1);      // 子进程退出}// parent -> rclose(fd[1]); // 关闭不用的文件描述符Read(fd[0]);  // 在文件fd[0]中读数据close(fd[0]); // 读完成后关闭fd[0]sleep(1);     //父进程sleep两秒,子进程处于僵尸状态int status;waitpid(id, &status, 0);printf("child exit : %d\n",status & 0x7F);//输出子进程退出时的退出信号sleep(2);return 0;
}

在这里插入图片描述

命名管道

匿名管道是通过文件描述符表让父子进程看到同一个文件,所以匿名管道就只能用来完成父子进程(存在血缘关系的进程)之间的通信;那如果两个毫不相干的进程想要进行通信呢?

而两个进程想要通信就要先看到同一份资源,那我们让不同的进程打开同一个文件不就行了;但是如果进程打开的是普通文件,在进程退出时操作系统就会将文件缓冲区的内容刷新到磁盘中,而进程间通信这里是不需要将内容刷新到缓冲区的。

所以,两个进程就不能打开普通文件,而是管道文件来进行通信;那这个管道文件也是一个文件啊,进程要打开这个文件就要有文件名啊,那这个文件就是一个命名文件也就是命名管道。

创建/删除管道文件

我们可以使用mkfifo命令来创建一个管道文件:

mkfifo filename

在这里插入图片描述

创建成功管道文件之后,我们就可以在程序中打开进程文件然后进行写入和读取操作。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{//打开命名管道文件printf("open fifo\n");open("fifo",O_RDONLY);//read or writeprintf("open fifo success\n");return 0;
}

在这里插入图片描述

我们可以发现,当我们在程序中试图打开管道文件时,它阻塞在了open函数中;

这是因为:管道文件是用来实现进程间通信的,这里我们只有一个进程要打开这个管道文件,OS就会让进程在open函数中阻塞住,直到有其他进程也打开这个管道文件。

当然可以创建管道文件也可以删除:

删除一个管道文件可以使用命令rm也可以使用命令unlink

进程自己创建管道文件

我们可以使用mkfifo指令来创建管道文件,这样在程序还没有运行时,就要先创建好管道文件;那程序在运行时可不可以自己创建管道文件实现通信呢?

当然是可以的:

在这里插入图片描述

程序可以通过调用mkfifo函数,在程序运行时创建管道文件。

参数:

  • 第一个参数pathname表示要创建管道文件的文件路径(包含文件名)
  • 第二个参数mode,表示权限;指创建管道文件的默认权限(文件权限 = 默认权限 & (~umask))。

我们可以使用umask函数设置局部的umask码。

返回值

  • 如果创建管道文件成功就返回0
  • 如果失败就返回-1,并且错误码被设置

删除管道文件

程序在运行时可以创建管道文件,当然也可以删除管道文件。

程序可以通过调用unlink函数来删除一个管道文件。

在这里插入图片描述

利用命名管道实现文件的拷贝

这里呢简单使用一下管道文件,实现文件的拷贝;

原理呢非常简单,本质上就是进程读取一个文件的内容,然后通过管道传给另一个文件,然后该进程再将接受到的数据写入到另一个文件当中。

  • 进程1(process1)以读方式打开文件src.txt;进程2(process2)以写发生打开文件dest.txt
  • 进程2创建管道文件,并以读方式打开管道文件;进程1以写方式打开管道文件
  • 进程1将读取文件src.txt的内容,写入到管道文件;进程2读取管道文件的内容,然后将内容写入到文件dest.txt

至此,就完成了文件的拷贝。

//process1.c
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#define _FIFO_FILE_ "fifo"
#define _SRC_FILE_ "src.txt"int main()
{//以w方式打开管道文件int wf = open(_FIFO_FILE_, O_WRONLY);if(wf < 0)  exit(1);//以r方式打开src.txtint rs = open(_SRC_FILE_, O_RDONLY);if(rs < 0)  exit(2);//读取src文件内容,写入到管道文件中char buff[1024];int n = 0;while(n = read(rs,buff,sizeof(buff)-1))//当n等于0时表示读到文件末尾{//将文件内容写入到管道文件中buff[n] = 0;write(wf, buff,strlen(buff));}printf("read src.txt and write fifo success\n");return 0;
}
//process2.c
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>#define _FIFO_FILE_ "fifo"
#define _DEST_FILE_ "dest.txt"int main()
{//创建管道文件int mf = mkfifo(_FIFO_FILE_, 0666);if(mf < 0)  exit(1);//以读方式打开管道文件int rf = open(_FIFO_FILE_, O_RDONLY);if(rf < 0)   exit(2);//以写方式打开dest.txt文件int wd = open(_DEST_FILE_, O_WRONLY | O_TRUNC);if(wd < 0)  exit(3);//从管道文件中读取char buff[1024];int n = 0;while(n = read(rf, buff,sizeof(buff)-1)){buff[n] = 0;//将读取到的内容写入到dest.txt文件中write(wd, buff,strlen(buff));}printf("read fifo and write dest.txt success\n");return 0;
}

在这里插入图片描述

通过命名管道实现进程间通信

在上述过程中,利用管道文件实现了文件的拷贝,这是非常简单的,使用一个进程以r方式打开src.txt文件,以w方式打开dest.txt文件也是可以完成文件的拷贝的。

那我们想要两个进程之间进行通信,如何完成呢?

现在模拟一个场景,客户端进程client向服务端进程server发送信息。

  • 服务端进程server:创建管道文件,并以读r方式打开管道文件。
  • 客户端进程client: 以w方式打开管道文件,向管道文件中发送信息。

1. 服务端server

这里服务端进程server要接受客户端进程client发送的信息;管道文件也就由服务端进程创建。

  1. 创建管道文件
  2. 以读方式打开管道文件,读取管道文件内容
  3. 数据传输结束,关闭并删除管道文件
#include <iostream>
#include <cstdio>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#define _FIFO_FILE_ "fifo"int main()
{//创建管道文件int mf = mkfifo(_FIFO_FILE_, 0666);if(mf < 0){perror("mkfifo");exit(1);}int rf = open(_FIFO_FILE_, O_RDONLY);if(rf < 0){perror("open");exit(2);} //等待客户端发送信息while(1){char buff[1024];int n = read(rf, buff, sizeof(buff));if(n < 0){perror("read");exit(3);}else if(n == 0){printf("client exit, server exit too\n");break;}buff[n] = 0;printf("Client : %s\n",buff);}//关闭并删除管道文件close(rf);unlink(_FIFO_FILE_);return 0;
}

2. 客户端client

服务端进程server创建管道文件成功,客户端就只需以w方式打开管道文件,然后向管道文件写入内容即可。

  1. w方式打开管道文件,将传输内容写入到管道文件
  2. 数据传输结束,关闭管道文件
#include <iostream>
#include <string>
#include <cstdio>
#include <fcntl.h>
#include <unistd.h>
#define _FIFO_FILE_ "fifo"int main()
{//w打开管道文件int wf = open(_FIFO_FILE_, O_WRONLY);if(wf < 0){perror("open");exit(1);}while(1){std::string massage;std::cout<<"Enter : ";std::getline(std::cin, massage);write(wf,massage.c_str(),massage.size());}close(wf);return 0;
}

3. 效果演示

在这里插入图片描述

4. 代码优化

在上述代码中,虽然说使用了C++语法,但是整个代码的过程还是面向过程化的,现在我们对其进行修改;

将整个代码面向对象化。

对于服务端进程server和客户端进程client,通过管道进行通信的本质就是文件操作。

这里就设计出一个文件类,而在服务端进程中要进行管道文件的创建和删除,这里就再设计一个管道文件类。

管道文件fifofile

我们要创建一个管道文件,就要有路径和文件名,所以在fifofile类中就要有成员变量_path路径和_name文件名。

为了方便操作,再增加一个_filename成员变量,表示管道文件的路径+文件名。

server进程中,要创建管道文件,也要删除文件;而有时异常退出就可能没有删除管道文件;

所以这里在fifofile的构造函数中创建管道文件,在析构函数中删除这个管道文件。

成员变量:

  • _path,表示路径
  • _name :表示管道文件的文件名
  • _fifoname 表示管道文件的(路径+文件名)

成员方法:

  • 构造函数:在构造函数中创建管道文件。
  • 析构函数:在析构函数中删除管道文件。
class fifofile
{
public:fifofile(const std::string &path, const std::string &name): _path(path), _name(name){// 创建管道文件_fifoname = path + '/' + name;int n = mkfifo(_fifoname.c_str(), 0666);if (n < 0){std::cerr << "mkfifo failed" << std::endl;exit(1);}//std::cout << "mkfifo success" << std::endl;}~fifofile(){unlink(_fifoname.c_str());}private:std::string _path;std::string _name;std::string _fifoname;
};

文件操作file

打开管道文件之后,进程直接的通信就变成了对文件的写入和读取,这里设计一个file类,实现对文件的一系列操作。

成员变量:

  • _path:文件路径
  • _name:文件名
  • filename:文件路径 + 文件名

成员函数:

  • 构造函数、析构函数
  • _readopen_writeopen:以读/写方式打开文件
  • _read_write:文件的读取/写入
  • _close:关闭文件
class file
{
public:file(const std::string &path, const std::string &name): _path(path), _name(name), _fd(-1){_filename = path + '/' + name;}~file(){_close();}bool _readopen(){_fd = open(_filename.c_str(), O_RDONLY);if (_fd < 0){std::cerr << "open failed" << std::endl;return false;}std::cout << "readopen succcess" << std::endl;return true;}bool _writeopen(){_fd = open(_filename.c_str(), O_WRONLY);if (_fd < 0){std::cerr << "open failed" << std::endl;return false;}std::cout << "write open success" << std::endl;return true;}int _read(char *buff, int n){if (_fd < 0)-1;int x = read(_fd, buff, sizeof(buff) - 1);if (x < 0){std::cerr << "read failed" << std::endl;exit(1);}else if (x == 0)return 0;buff[x] = 0;return x;}int _write(std::string &str){if (_fd < 0)return -1;int n = write(_fd, str.c_str(), str.size());if (n < 0){std::cerr << "read failed" << std::endl;exit(1);}return n;}void _close(){if (_fd >= 0)close(_fd);}
private:std::string _path;std::string _name;std::string _filename;int _fd;
};

这里实现的_read_write都是一次读取/写入;

如果读取失败就直接终止,写端退出就返回0,成功读取就返回读取到的字节数。

如果写入是吧就直接终止,写入成功就返回实际写入的字节数。

针对上述实现的fifofilefile类,这里也写出了测试方法,实现的效果和面向过程实现进程间通信是一样的,这里就演示了

fifo.hpp:

//fifo.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#define _FIFO_PATH_ "."
#define _FIFO_NAME_ "fifo"
//fifofile类
//fife类

server.cc:

#include "fifo.hpp"int main()
{// 创建管道文件fifofile ff(_FIFO_PATH_, _FIFO_NAME_);// 打开管道文件file rf(_FIFO_PATH_, _FIFO_NAME_);rf._readopen();char buff[1024];while (1){int n = rf._read(buff, sizeof(buff));if (n == 0){std::cout << "client exit, me too" << std::endl;break;}buff[n] = 0;std::cout << "client : " << buff << std::endl;}// 自动调用析构函数,删除管道文件return 0;
}

client.cc:

#include "fifo.hpp"int main()
{file wf(_FIFO_PATH_, _FIFO_NAME_);wf._writeopen();while(1){std::string str;std::getline(std::cin, str);wf._write(str);}return 0;
}

到这里本篇文件内容就结束了,感谢各位的支持!!!

到这里本篇文件内容就结束了,感谢各位的支持!!!

简单总结:

  1. 进程间通信IPC
  2. 管道的原理:管道是基于文件系统实现的进程间通信
  3. 匿名管道:父子进程通过文件描述符表看到同一个文件
  4. 命名管道:通过文件名看到同一个文件
  5. pipe创建匿名管道
  6. mkfifo命名先创建命名管道再运行程序;先运行程序,在运行时通过mkfifo函数创建命名管道

http://www.hkcw.cn/article/BppxrLlJWi.shtml

相关文章

电脑使用VPN后直接关机,再次打开后无法上网的问题

出现这种问题&#xff0c;都是在使用VPN后&#xff0c;以前自己都是通过杀毒软件的网络修复工具进行解决的。 但现在有了一个更简单的方法&#xff1a; 打开设置&#xff0c;找到网络中的代理,然后关闭即可。

【Linux】线程控制

&#x1f4dd;前言&#xff1a; 这篇文章我们来讲讲Linux——线程控制 &#x1f3ac;个人简介&#xff1a;努力学习ing &#x1f4cb;个人专栏&#xff1a;Linux &#x1f380;CSDN主页 愚润求学 &#x1f304;其他专栏&#xff1a;C学习笔记&#xff0c;C语言入门基础&#xf…

CppCon 2014 学习:Hardening Your Code

“Hardening Your Code” 是指增强代码的健壮性、安全性、可维护性和可测试性&#xff0c;确保在各种边界条件、错误场景甚至恶意输入下&#xff0c;代码依然稳定运行&#xff0c;不崩溃、不泄露资源&#xff0c;也不产生未定义行为。 什么是“Hardening Your Code”&#xff…

【js逆向_AES】某专业技术人员继续教育平台登录分析及模拟实践

目标&#xff1a;account&#xff0c;password加密 网址&#xff1a;aHR0cHM6Ly93d3cuZ3N6eGp5cHguY24vd2ViL2luZGV4 请求载荷加密方式 账号加密&#xff1a; 网页调试输出&#xff1a; python代码&#xff1a; from Cryptodome.Cipher import AES import base64 from Crypto…

《信号与系统》--期末总结V1.0

《信号与系统》–期末总结V1.0 学习链接 入门&#xff1a;【拯救期末】期末必备&#xff01;8小时速成信号与系统&#xff01;【拯救期末】期末必备&#xff01;8小时速成信号与系统&#xff01;_哔哩哔哩_bilibili 精通&#xff1a;2022浙江大学信号与系统&#xff08;含配…

可视化大屏通用模板Axure原型设计案例

本文将介绍一款基于Axure设计的可视化大屏通用模板&#xff0c;适用于城市、网络安全、园区、交通、社区、工业、医疗、能源等多个领域。 模板概述 这款Axure可视化大屏通用模板集成了多种数据展示模块和组件&#xff0c;旨在为用户提供一个灵活、可定制的数据展示平台。无论…

AI来敲门:我们该如何与焦虑共舞

最近一份覆盖国内上万职场人的调研报告像一颗深水炸弹&#xff0c;在职场圈激起层层涟漪——85.53%的人担心AI会抢走自己的饭碗&#xff0c;67.57%的人认为这会在五年内发生。更令人意外的是&#xff0c;这些焦虑的职场人中&#xff0c;高达34.13%出现了抑郁症状&#xff0c;这…

单调栈(打卡)

本篇基于b站灵茶山艾府。 下面是灵神上课讲解的题目与课后作业&#xff0c;课后作业还有三道实在写不下去了&#xff0c;下次再写。 739. 每日温度 给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其中 answer[i] 是…

【C语言入门级教学】冒泡排序和指针数组

文章目录 1.冒泡排序2.⼆级指针3.指针数组4.指针数组模拟⼆维数组 1.冒泡排序 冒泡排序的核⼼思想&#xff1a;两两相邻的元素进⾏⽐较。 //⽅法1 void bubble_sort(int arr[], int sz)//参数接收数组元素个数 { int i 0;for(i0; i-1; i) { int j 0; for(j0; j-1; j) { …

源码解析(三):Stable Diffusion

原文 技术博客 &#x1f600; Stable Diffusion是一种基于扩散模型&#xff08;Diffusion Model&#xff09;的生成式AI技术&#xff0c;通过逐步去噪过程将随机噪声转化为高质量图像。其核心优势在于开源免费、支持本地部署&#xff0c;且能通过文本提示&#xff08;prompt&am…

洛雪音乐+多种音源同步更新,附带安装教程 -【PC端/安卓端】音乐软件

今天&#xff0c;就为大家介绍一款全网免费听歌神器——‌洛雪音乐‌&#xff01; &#x1f3b6; 洛雪音乐&#xff1a;&#xff08;文末获取软件&#xff09; 一、软件亮点 全平台支持‌&#xff1a;无论是Windows系统还是安卓手机&#xff0c;洛雪音乐都能随时伴你左右&am…

【CATIA的二次开发18】根对象Application涉及用户交互相关方法

在CATIA VBA开发中&#xff0c;对根对象Application涉及用户交互相关方法进行详细总结&#xff0c;并且用不同形式展示出来。供大家后续开发全面了解Application对象的方法&#xff0c;以便在开发过程中快速查找和使用&#xff1a; 一、Application常用方法分类 1、基础控制与…

密码学:解析Feistel网络结构及实现代码

概述 Feistel网络是由IBM密码学家Horst Feistel在20世纪70年代提出的对称加密结构&#xff0c;已成为现代分组密码的核心框架。DES、Blowfish、RC5等经典加密算法均基于此结构。其核心思想是将输入明文分组分成左右两半&#xff0c;通过多轮迭代操作实现加密&#xff0c;每轮使…

JavaSE知识总结(集合篇) ~个人笔记以及不断思考~持续更新

目录 集合 List List的各种接口API List的五种遍历方式 List的删除是内部是怎么做的&#xff1f; ArrayList和LinkedList的区别 Vetor和Stack是什么&#xff1f; Set Set的特点 HashSet TreeSet LinkedHashSet Map HashMap LinkedHashMap TreeMap 集合 在Java…

Linux中的mysql备份与恢复

一、安装mysql社区服务 二、数据库的介绍 三、备份类型和备份工具 一、安装mysql社区服务 这是小编自己写的&#xff0c;没有安装的去看看 Linux换源以及yum安装nginx和mysql-CSDN博客 二、数据库的介绍 2.1 数据库的组成 数据库是一堆物理文件的集合&#xff0c;主要包括…

也说字母L:柔软的长舌

英语单词 tongue&#xff0c;意为“舌头” tongue n.舌&#xff0c;舌头&#xff1b;语言 很显然&#xff0c;“语言”是引申义&#xff0c;因为语言是抽象的&#xff0c;但舌头是具象的&#xff0c;根据由简入繁的原则&#xff0c;tongue显然首先是象形起义&#xff0c;表达…

【机器学习】决策树

目录 一、引言 二、决策树的构造 三、决策树的ID3算法 四、决策树的C4.5算法 五、决策树的CART算法 六、动手实现决策树C4.5的算法详解步骤以及Python完整代码实现 一、引言 在机器学习中,有一种与神经网络并行的非参数化模型——决策树模型及其变种。顾名思义,决…

美提高钢铝关税至50% 欧盟深表遗憾 谈判进程加速

6月2日,欧盟委员会新闻发言人对美国宣布将钢铁和铝关税从25%提高至50%表示遗憾,认为这一决定加剧了大西洋两岸的经济不确定性。发言人提到谈判仍在继续,双方已同意加快谈判进程,并计划本周举行会谈。欧盟贸易专员塞夫科维奇将于6月4日在法国巴黎会见美国贸易代表格里尔。美…

基于ubuntu和树莓派环境对游戏进行移植

目录 一、在Ubuntu中对波斯王子游戏进行移植 1.1修改Ubuntu系统的仓库镜像网站为国内网站 1.2安装mininim 软件所依赖的库 1.3 编译mininim 软件 二、在树莓派中对波斯王子游戏移植 2.1安装相关环境 2.3编译mininim 软件 三、使用树莓派实现流水灯 一、在Ubuntu中对波…

设计模式——备忘录设计模式(行为型)

摘要 备忘录设计模式是一种行为型设计模式&#xff0c;用于在不破坏封装性的前提下&#xff0c;捕获对象的内部状态并在需要时恢复。它包含三个关键角色&#xff1a;原发器&#xff08;Originator&#xff09;、备忘录&#xff08;Memento&#xff09;和负责人&#xff08;Car…