跳到主要内容
Linux 进程间通信详解:管道、共享内存与内核机制 | 极客日志
C++
Linux 进程间通信详解:管道、共享内存与内核机制 Linux 进程间通信(IPC)是不同进程间交换信息的技术方法。深入解析 IPC 机制,涵盖匿名管道与命名管道的创建及原理,System V 共享内存、消息队列与信号量的接口函数及内核数据结构。通过代码示例演示进程池实现及同步问题解决方案,解决文件描述符泄漏等 BUG。最后探讨内核如何通过命名空间和权限控制隔离 IPC 资源,呈现进程间通信的全景视角。
GitMaster 发布于 2026/2/9 更新于 2026/5/28 23 浏览一、进程间通信介绍
1. 什么是进程间通信
进程间通信 (Inter-Process Communication, IPC) 是指在不同进程之间传播或交换信息的技术方法。由于操作系统中的进程通常拥有独立的地址空间,一个进程不能直接访问另一个进程的变量或数据结构,因此需要专门的机制来实现进程间的数据共享和通信。
进程间通信的本质:是让不同的进程先看到同一份资源(内存),然后才有通信的条件。
2. 进程间通信的发展和分类(简单介绍)
(1)早期 IPC:管道 (Pipe) 是最早的 IPC 机制之一。
匿名管道 (无名管道):单向通信,只能用于有亲缘关系的进程 (如父子进程),通过 pipe() 系统调用创建。
命名管道 (FIFO):有名称的管道文件,可用于无亲缘关系的进程间通信,通过 mkfifo() 创建。
(2)System V IPC
三种主要机制:消息队列、信号量、共享内存
使用键值 (Key) 来标识 IPC 对象,需要显式删除 IPC 对象,否则会一直存在于系统中。
权限控制通过类似文件权限的机制
(3)POSIX IPC:对 System V IPC 的改进和标准化
主要包括以下组件:消息队列、共享内存、信号量、互斥量、条件变量、读写锁。
(4)发展对比
特性 管道 System V IPC POSIX IPC 标准化 Unix 传统 System V Unix POSIX 标准 对象标识 文件描述符 (匿名管道) 键值 (Key) 文件系统路径名 生命周期 随进程结束 需显式删除 可配置为随进程结束 访问控制 文件权限 IPC 权限 文件权限 跨平台性 有限 有限 较好 性能 中等 高 (特别是共享内存) 高
3. 进程间通信的目的
• 数据传输:一个进程需要将它的数据发送给另一个进程。
• 资源共享:多个进程之间共享同样的资源。
• 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
• 进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
二、管道
1. 什么是管道?
管道(Pipe)是 Unix 系统最古老的进程间通信(IPC)方式,其核心思想是:
将一个进程的输出数据流(stdout)直接连接到另一个进程的输入数据流(stdin),形成一个单向的 、基于字节流 (无消息边界,二进制传输)的通信通道(内核级)。
2. 匿名管道
(1)核心特点
• 单向通信:数据只能从写端流向读端(半双工)
• 仅限亲缘进程:通过 fork() 创建的父子/兄弟进程间使用
• 内存级通信:由内核管理缓冲区,不依赖磁盘文件
• 随进程销毁:当所有相关进程终止时,管道自动释放
(2)创建匿名管道
#include <unistd.h>
;
int
pipe
(int pipefd[2 ])
参数:pipefd[2] 表示一个长度为 2 的整型数组,用于存储管道的两个文件描述符:
• pipefd[0]:管道的读端(用于从管道读取数据)。
• pipefd[1]:管道的写端(用于向管道写入数据)。
注:调用 pipe 创建管道不要文件路径,没有文件名。是内存级的,被 OS 单独设计,称之为匿名管道。
(3)fork 共享管道原理 单个进程中的管道几乎没有任何用处,通常,进程会先调用 pipe(),接着调用 fork(),从而创建父进程到子进程的 IPC 通道。
我们怎么保证两个进程打开的是同一个管道文件?fork 之后,子进程会继承父进程的管道文件描述符,父子进程通过 fd 访问同一管道,内核确保数据同步和生命周期管理。
fork 之后做什么取决于我们想要的数据流的方向。对于父进程到子进程的管道,父进程关闭管道的读端(fd[0]),子进程关闭写端(fd[1])。
(4)内核角度 - 管道本质 管道在内核中通过 inode 表示,不同于磁盘文件的 inode,它关联的是内存中的 pipe_inode_info(管道核心)。
管道是通过 文件描述符→file→inode→pipe_inode_info→数据页 的链式关系实现的,多个进程通过不同层级的共享(file 结构独立,但底层 pipe_inode_info 共享)完成通信。
匿名管道的'匿名性'体现在其 inode 不关联文件系统,仅存于内存。
(5)管道代码样例 创建一个管道,用于父子进程间单向通信,子进程每隔 1 秒向管道写入数据,父进程从管道读取数据并打印。
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <string.h>
void ChildWrite (int wfd) {
char buffer[1024 ];
int cnt = 0 ;
while (true ) {
snprintf (buffer, sizeof (buffer), "I am child, pid: %d, cnt: %d" , getpid (), cnt++);
write (wfd, buffer, strlen (buffer));
sleep (1 );
}
}
void FatherRead (int rfd) {
char buffer[1024 ];
while (true ) {
buffer[0 ] = 0 ;
size_t n = read (rfd, buffer, sizeof (buffer)-1 );
if (n > 0 ) {
buffer[n] = 0 ;
std::cout << "Child say: " << buffer << std::endl;
}
}
}
int main () {
int fds[2 ] = {0 };
int n = pipe (fds);
if (n < 0 ) {
std::cerr << "pipe error!" << std::endl;
return -1 ;
}
pid_t id = fork();
if (id == 0 ) {
close (fds[0 ]);
ChildWrite (fds[1 ]);
close (fds[1 ]);
exit (0 );
}
close (fds[1 ]);
FatherRead (fds[0 ]);
waitpid (id, nullptr , 0 );
close (fds[0 ]);
return 0 ;
}
(6)管道文件的特点
• 管道通常由父进程创建,然后调用 fork(),使父子进程共享管道的文件描述符。
• 非亲缘关系的进程无法直接使用管道通信(但可以通过其他方式,如命名管道 FIFO,后面讲)。
• 管道是字节流(stream),没有消息边界,数据以字节序列的形式传输。
• 不同于消息队列(如 msgqueue),不会自动分隔消息,需要应用层自行处理(如用 \n 分隔)。
• 管道的生命周期依赖于进程,当所有引用该管道的进程都关闭文件描述符后,管道会被内核回收。
• 如果父进程先退出,子进程仍可继续使用管道,但若所有进程都关闭管道,数据会丢失。
• 同步 (Synchronization):
当管道空时,读端 read() 会阻塞,直到有数据写入。
当管道满时(默认缓冲区大小通常为 64KB),写端 write() 会阻塞,直到有空间可写。
• 互斥 (Mutual Exclusion):
内核保证多个进程同时读写管道时不会发生数据竞争(read 和 write 是原子的 )。
例如:如果两个进程同时写管道,内核会确保数据不会交错(一次 write() 不会被另一个 write() 打断)。如果两个进程同时读管道,内核会确保数据不会被重复读取(每个 read() 获取不同的数据)。
• 半双工(Half-Duplex):数据只能单向传输(要么父写子读,要么子写父读)。
• 如果需要双向通信(全双工),必须建立两个管道
• 在 Linux 中,管道的默认缓冲区大小通常是 64KB (PIPE_BUF,定义在 <limits.h>)。
• 如果写入的数据超过 PIPE_BUF,write() 可能会部分写入或阻塞,取决于是否设置 O_NONBLOCK。
情况 读端 read() 写端 write() 管道空 阻塞 (直到有数据)正常写入 管道满 正常读取 阻塞 (直到有空间)所有写端关闭 read() 返回 0(EOF)- 所有读端关闭 - SIGPIPE
(7)基于匿名管道 --- 进程池 这是一个基于 C++ 实现的简单进程池系统,主要用于管理多个子进程并通过管道进行任务分发。
① Task.hpp ● TaskManager 类(任务管理器):管理可执行任务
• 关键特性:
使用函数指针数组存储任务
支持任务注册 (Register)
随机选择任务 (Code)
执行指定任务 (Execute)
• 任务类型:typedef void (*task_t)() 定义的无参数无返回值函数
#pragma once
#include <iostream>
#include <vector>
#include <ctime>
typedef void (*task_t ) () ;
void PrintLog () { std::cout << "我是一个打印日志的任务" << std::endl; }
void DownLoad () { std::cout << "我是一个下载的任务" << std::endl; }
void Upload () { std::cout << "我是一个上传的任务" << std::endl; }
class TaskManager {
private :
std::vector<task_t > _tasks;
public :
TaskManager () { srand (time (nullptr )); }
void Register (task_t t) { _tasks.push_back (t); }
int Code () { return rand () % _tasks.size (); }
void Execute (int code) {
if (code >= 0 && code < _tasks.size ()) {
_tasks[code]();
}
}
~TaskManager () {}
};
② ProcessPool.hpp ● Channel 类(通道/管道):封装了父子进程间的单向通信管道
• 关键成员:
_wfd:管道写端文件描述符
_subid:子进程 PID
_name:通道名称(用于标识)
● ChannelManager 类(通道管理器):集中管理所有 Channel 对象
• 关键特性:
使用 vector 存储 Channel 对象
采用轮询 (round-robin) 方式选择 Channel
● ProcessPool 类(进程池):主管理类,整合上述组件
• 工作流程:
初始化时注册任务
启动时创建指定数量的子进程
通过 Run() 方法分发任务
#pragma once
#include <iostream>
#include <vector>
#include <unistd.h>
#include <cstdlib>
#include <string>
#include <sys/wait.h>
#include "Task.hpp"
class Channel {
private :
int _wfd;
pid_t _subid;
std::string _name;
public :
Channel (int wfd, int subid) : _wfd(wfd), _subid(subid) {
_name = "channel- " + std::to_string (wfd) + " - " + std::to_string (subid);
}
int Fd () { return _wfd; }
pid_t Subid () { return _subid; }
void Send (int code) {
int n = write (_wfd, &code, sizeof (code));
(void )n;
}
void Close () { close (_wfd); }
void Wait () { waitpid (_subid, nullptr , 0 ); }
~Channel () {};
};
class ChannelManager {
private :
std::vector<Channel> _channels;
int _next;
public :
ChannelManager () : _next(0 ) {};
void InsertChannel (int wfd, pid_t subid) {
_channels.emplace_back (wfd, subid);
}
Channel &Select () {
auto &c = _channels[_next];
_next++;
_next %= _channels.size ();
return c;
}
void StopSubProcess () {
for (auto &channel : _channels) {
channel.Close ();
}
}
void WaitSubProcess () {
for (auto &channel : _channels) {
channel.Wait ();
}
}
~ChannelManager () {};
};
const int defaultnum = 5 ;
class ProcessPool {
private :
ChannelManager _cm;
int _process_num;
TaskManager _tm;
public :
ProcessPool (int num) : _process_num(num) {
_tm.Register (PrintLog);
_tm.Register (DownLoad);
_tm.Register (Upload);
}
void Work (int rfd) {
while (true ) {
int code = 0 ;
ssize_t n = read (rfd, &code, sizeof (code));
if (n > 0 ) {
std::cout << "子进程 [" << getpid () << "] 收到一个任务码:" << code << std::endl;
_tm.Execute (code);
} else if (n == 0 ) {
break ;
} else {
break ;
}
}
}
bool Start () {
for (int i = 0 ; i < _process_num; i++) {
int pipefd[2 ] = {0 };
int n = pipe (pipefd);
if (n < 0 ) return false ;
pid_t subid = fork();
if (subid < 0 ) return false ;
else if (subid == 0 ) {
close (pipefd[1 ]);
Work (pipefd[0 ]);
close (pipefd[0 ]);
exit (0 );
} else {
close (pipefd[0 ]);
_cm.InsertChannel (pipefd[1 ], subid);
}
}
return true ;
}
void Run () {
int taskcode = _tm.Code ();
auto &c = _cm.Select ();
c.Send (taskcode);
}
void Stop () {
_cm.StopSubProcess ();
_cm.WaitSubProcess ();
}
~ProcessPool () {};
};
③ Main.cc #include "ProcessPool.hpp"
int main () {
ProcessPool pp (defaultnum) ;
pp.Start ();
int cnt = 5 ;
while (cnt--) {
pp.Run ();
sleep (1 );
}
pp.Stop ();
return 0 ;
}
④ Makefile process_pool:Main.cc
g++ -o $@ $^
.PHONY :clean
clean:
rm -f process_pool
⑤ BUG --- 文件描述符的继承和泄漏 如果我们用整合后的 StopAndWait() 代替 WaitSubProcess() 和 StopAndWait() 呢。会发生什么?
文件描述符继承:每次 fork() 时,子进程会复制父进程的文件描述符表,包括之前创建的管道描述符。
描述符泄漏:父进程虽然关闭了当前管道的写端(pipefd[1]),但之前子进程继承的冗余描述符未被关闭。
waitpid() 阻塞:由于子进程仍有未关闭的描述符,它的 read() 可能未检测到 EOF,导致子进程无法退出。
方案 1:在执行 StopAndWait() 时,倒着关闭文件描述符表。
void StopAndWait () {
for (int i = _channels.size () - 1 ; i >= 0 ; i--) {
_channels[i].Close ();
_channels[i].Wait ();
}
}
void CloseAll () {
for (auto &channel : _channels) {
channel.Close ();
}
}
3. 命名管道 命名管道(Named Pipe),也称为 FIFO(First In First Out)文件,是一种特殊的文件类型,用于在不相关进程(无父子关系)之间进行通信(特性) 。与匿名管道(pipe())不同,命名管道在文件系统中有一个可见的路径名 ,任何进程只要知道该路径,都可以访问它。
(1)创建命名管道
① 命令行创建 $ mkfifo fifo
$ ll total 8
prw-rw-r-- 1 zyt zyt 0 May 22 16:24 fifo|
命名管道(FIFO)是一种 同步通信机制,写入操作(>)会 阻塞,直到另一端有进程打开管道并开始读取。
$ echo "hello fifo" >fifo
$ cat < fifo
hello fifo
② 系统调用函数 #include <sys/types.h>
#include <sys/stat.h>
int mkfifo (const char *pathname, mode_t mode) ;
(2)实例
① 示例:简单实现一个客户端与服务端的通信 #include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string>
#include <iostream>
#include <unistd.h>
#include "comm.hpp"
int main () {
umask (0 );
int n = mkfifo (FIFO_FILE, 0666 );
if (n < 0 ) {
std::cerr << "mkfifo error" << std::endl;
return 1 ;
}
int fd = open (FIFO_FILE, O_RDONLY);
if (fd < 0 ) {
std::cerr << "open fifo error" << std::endl;
return 2 ;
}
while (true ) {
char buffer[1024 ];
int number = read (fd, buffer, sizeof (buffer)-1 );
if (number > 0 ) {
buffer[number] = 0 ;
std::cout << "client say: " << buffer << std::endl;
} else if (number == 0 ) {
break ;
} else {
break ;
}
}
unlink (FIFO_FILE);
return 0 ;
}
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string>
#include <iostream>
#include <unistd.h>
#include "comm.hpp"
int main () {
int fd = open (FIFO_FILE, O_WRONLY);
if (fd < 0 ) {
std::cerr << "open fifo error" << std::endl;
return 1 ;
}
std::string message;
int cnt = 1 ;
pid_t id = getpid ();
while (true ) {
std::cout << "Please Enter# " ;
std::getline (std::cin, message);
message += ", message number " + std::to_string (cnt++) + ", [" + std::to_string (id) + "]" ;
write (fd, message.c_str (), message.size ());
}
close (fd);
return 0 ;
}
② 优化:用类封装
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FIFO_FILE "fifo"
#define PATH "."
#define FILENAME "fifo"
#define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); } while (0)
struct Namedfifo {
public :
Namedfifo (const std::string path, const std::string name) : _path(path), _name(name) {
_fifoname = _path + "/" + _name;
umask (0 );
int n = mkfifo (_fifoname.c_str (), 0666 );
if (n < 0 ) ERR_EXIT ("mkfifo" );
}
~Namedfifo () {
unlink (_fifoname.c_str ());
}
private :
std::string _path;
std::string _name;
std::string _fifoname;
};
class FileOper {
public :
FileOper (const std::string &path, const std::string &name) : _path(path), _name(name), _fd(-1 ) {
_fifoname = _path + "/" + _name;
}
void OpenForRead () {
_fd = open (_fifoname.c_str (), O_RDONLY);
if (_fd < 0 ) ERR_EXIT ("open" );
}
void OpenForWrite () {
_fd = open (_fifoname.c_str (), O_WRONLY);
if (_fd < 0 ) ERR_EXIT ("open" );
}
void Write () {
std::string message;
int cnt = 1 ;
pid_t id = getpid ();
while (true ) {
std::cout << "Please Enter# " ;
std::getline (std::cin, message);
message += ", message number " + std::to_string (cnt++) + ", [" + std::to_string (id) + "]" ;
write (_fd, message.c_str (), message.size ());
}
}
void Read () {
while (true ) {
char buffer[1024 ];
int number = read (_fd, buffer, sizeof (buffer) - 1 );
if (number > 0 ) {
buffer[number] = 0 ;
std::cout << "client say: " << buffer << std::endl;
} else if (number == 0 ) {
break ;
} else {
ERR_EXIT ("read" );
}
}
}
void Close () { if (_fd > 0 ) close (_fd); }
~FileOper () {}
private :
std::string _path;
std::string _name;
std::string _fifoname;
int _fd;
};
三、System V System V IPC 是 Unix/Linux 系统中一种经典的进程间通信(IPC)标准,包含以下三种核心机制(共享内存,信号量,消息队列),均在内核中维护全局唯一的标识符和数据结构。
1. System V 共享内存 共享内存允许两个或多个进程共享一个给定的存储区。因为数据不需要在客户进程和服务器进程之间复制,所以这是最快的一种 IPC。
映射之后读写直接被对方看到。
不需要进行系统调用获取或写入内容,以指针地址的方式。
(1)原理
① 物理内存共享
内核分配一块物理内存:通过系统调用(如 shmget())在内核中申请一块物理内存区域,称为共享内存段 。
② 虚拟地址映射
进程通过 shmat() 将共享内存段映射 到自己的虚拟地址空间。
③ 直接访问
④ 工作流程
(2)内核数据结构 共享内存的机制依赖于内核数据结构 和物理内存 的紧密结合。
① struct shmid_ds(用户可见的元信息) struct shmid_ds {
struct ipc_perm shm_perm ;
int shm_segsz;
__kernel_time_t shm_atime;
__kernel_time_t shm_dtime;
__kernel_time_t shm_ctime;
__kernel_ipc_pid_t shm_cpid;
__kernel_ipc_pid_t shm_lpid;
unsigned short shm_nattch;
};
② struct shmid_kernel(内核实际使用的扩展结构) struct shmid_kernel {
struct shmid_ds u ;
struct file *shm_file ;
unsigned long shm_nattch;
};
③ struct file 与物理页帧 shm_file->f_mapping:指向共享内存的物理页帧集合(struct address_space)。
④ 操作系统如何跟踪共享内存的使用状态? 操作系统通过内核数据结构 和引用计数机制 精确跟踪共享内存是否被进程使用。
(3)标识符和键
① 标识符 shmid 每个内核中的 IPC 结构,都用一个非负整数的标识符加以引用。
② 键 key 每个 IPC 对象都与一个键 key 相关联,将这个键作为该对象的外部名,key 值是由用户层构建并传入的。
ftok() :基于文件路径和项目 ID 计算产生一个 key。
#include <sys/types.h>
key_t ftok (const char *path, int id) ;
③ 总结 问题 判断依据 共享内存是否存在? shmget(key, 0, 0) 是否成功是否同一共享内存? 两个进程是否使用相同的 key
(4)共享内存接口函数
① shmget() #include <sys/ipc.h>
#include <sys/shm.h>
int shmget (key_t key, size_t size, int shmflg) ;
② shmctl() #include <sys/ipc.h>
#include <sys/shm.h>
int shmctl (int shmid, int cmd, struct shmid_ds *buf) ;
常用 cmd 控制命令:IPC_STAT, IPC_SET, IPC_RMID.
③ shmat() 将共享内存段挂接(attach)到当前进程的地址空间。
#include <sys/shm.h>
void *shmat (int shmid, const void *shmaddr, int shmflg) ;
④ shmdt() 从调用进程的地址空间中分离位于 shmaddr 的共享内存段。
#include <sys/shm.h>
int shmdt (const void *shmaddr) ;
(5)共享内存使用全流程
① comm.hpp #pragma once
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <iostream>
#include <string>
#include <cstdio>
#include <unistd.h>
#define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); } while (0)
const int defaultid = -1 ;
const int gsize = 4096 ;
const std::string pathname = "." ;
const int projid = 0x66 ;
class Shm {
public :
Shm () : _shmid(defaultid), _size(gsize), _start_mem(nullptr ) {}
void Create () {
key_t k = ftok (pathname.c_str (), projid);
_shmid = shmget (k, _size, IPC_CREAT | IPC_EXCL);
}
void Destory () {
if (_shmid == defaultid) return ;
shmctl (_shmid, IPC_RMID, nullptr );
}
void Attach () {
_start_mem = shmat (_shmid, nullptr , 0 );
}
void *VirtualAddr () { return _start_mem; }
~Shm () {}
private :
int _shmid;
int _size;
void *_start_mem;
};
② server.cc #include "comm.hpp"
int main () {
Shm shm;
shm.Create ();
sleep (3 );
shm.Attach ();
shm.VirtualAddr ();
sleep (3 );
shm.Destory ();
return 0 ;
}
(6)基于共享内存实现进程间通信
① 无同步机制的进程间通信
#pragma once
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <iostream>
#include <string>
#include <cstdio>
#include <unistd.h>
#define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); } while (0)
const int defaultid = -1 ;
const int gsize = 4096 ;
const std::string pathname = "." ;
const int projid = 0x66 ;
const int gmode = 0666 ;
#define CREATER "creater"
#define USER "user"
class Shm {
private :
void CreateHelper (int flag) {
_shmid = shmget (_key, _size, flag);
}
void Create () { CreateHelper (IPC_CREAT | IPC_EXCL | gmode); }
void Attach () { _start_mem = shmat (_shmid, nullptr , 0 ); }
void Destory () { shmctl (_shmid, IPC_RMID, nullptr ); }
public :
Shm (const std::string &pathname, int projid, const std::string &usertype) : _shmid(defaultid), _size(gsize), _start_mem(nullptr ), _usertype(usertype) {
_key = ftok (pathname.c_str (), projid);
if (_usertype == CREATER) Create ();
else if (_usertype == USER) CreateHelper (IPC_CREAT);
Attach ();
}
void *VirtualAddr () { return _start_mem; }
~Shm () { if (_usertype == CREATER) Destory (); }
private :
int _shmid;
key_t _key;
int _size;
void *_start_mem;
std::string _usertype;
};
#include "shm.hpp"
int main () {
Shm shm (pathname, projid, CREATER) ;
char * mem = (char *)shm.VirtualAddr ();
while (true ) {
printf ("%s\n" , mem);
sleep (1 );
}
return 0 ;
}
#include "shm.hpp"
int main () {
Shm shm (pathname, projid, USER) ;
char *mem = (char *)shm.VirtualAddr ();
int index = 0 ;
for (char c = 'A' ; c <= 'Z' ; index += 2 ) {
mem[index] = c;
sleep (1 );
mem[index + 1 ] = c;
sleep (1 );
}
return 0 ;
}
② 共享内存 + 管道 #pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FIFO_FILE "fifo"
#define PATH "."
#define FILENAME "fifo"
#define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); } while (0)
struct Namedfifo {
public :
Namedfifo (const std::string path, const std::string name) : _path(path), _name(name) {
_fifoname = _path + "/" + _name;
umask (0 );
int n = mkfifo (_fifoname.c_str (), 0666 );
if (n < 0 ) ERR_EXIT ("mkfifo" );
}
~Namedfifo () { unlink (_fifoname.c_str ()); }
private :
std::string _path;
std::string _name;
std::string _fifoname;
};
class FileOper {
public :
FileOper (const std::string &path, const std::string &name) : _path(path), _name(name), _fd(-1 ) {
_fifoname = _path + "/" + _name;
}
void OpenForRead () { _fd = open (_fifoname.c_str (), O_RDONLY); }
void OpenForWrite () { _fd = open (_fifoname.c_str (), O_WRONLY); }
void Wakeup () { char c = 'c' ; write (_fd, &c, 1 ); }
bool Wait () { char c; int number = read (_fd, &c, 1 ); return number > 0 ; }
void Close () { if (_fd > 0 ) close (_fd); }
~FileOper () { unlink (_fifoname.c_str ()); }
private :
std::string _path;
std::string _name;
std::string _fifoname;
int _fd;
};
#include "shm.hpp"
#include "Fifo.hpp"
int main () {
Shm shm (pathname, projid, CREATER) ;
Namedfifo fifo (PATH, FILENAME) ;
FileOper readerfile (PATH, FILENAME) ;
readerfile.OpenForRead ();
char *mem = (char *)shm.VirtualAddr ();
while (true ) {
if (readerfile.Wait ()) {
printf ("%s\n" , mem);
} else break ;
}
readerfile.Close ();
return 0 ;
}
#include "shm.hpp"
#include "Fifo.hpp"
int main () {
FileOper writerfile (PATH, FILENAME) ;
writerfile.OpenForWrite ();
Shm shm (pathname, projid, USER) ;
char *mem = (char *)shm.VirtualAddr ();
int index = 0 ;
for (char c = 'A' ; c <= 'Z' ; index += 2 ) {
mem[index] = c;
sleep (1 );
mem[index + 1 ] = c;
sleep (1 );
mem[index + 2 ] = 0 ;
writerfile.Wakeup ();
}
writerfile.Close ();
return 0 ;
}
2. System V 消息队列
(1)基本概念 消息队列是一种进程间通信(IPC)机制,它允许不同进程通过发送和接收消息来进行异步通信。
接收模式 参数设置 行为 先进先出 (FIFO) msgtyp=0读取队列中最早的消息 指定类型 msgtyp>0读取队列中第一个该类型消息 类型阈值 msgtyp<0读取类型≤
(2)接口函数
① msgget() #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget (key_t key, int msgflg) ;
② msgctl() int msgctl (int msqid, int cmd, struct msqid_ds *buf) ;
③ msgsnd() int msgsnd (int msqid, const void *msgp, size_t msgsz, int msgflg) ;
④ msgrcv() ssize_t msgrcv (int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg) ;
3. System V 信号量
(1)并发编程,概念铺垫 • 多个执行流(进程),能看到的同一份公共资源:共享资源
• 被保护起来的资源叫做临界资源
• 保护的方式常见:互斥与同步
(2)原子性 • 原子性 是指 一个操作(或一系列操作)要么完全执行,要么完全不执行,不会被其他线程/进程打断。
(3)什么是信号量? 信号量本质是一个计数器 ,用于表明临界资源中资源数量的多少。
操作 名称 行为 P (Proberen)等待/获取 尝试减少信号量值。若值 ≥ 1,则减 1 并继续;否则进程阻塞。 V (Verhogen)释放/唤醒 增加信号量值。若有进程在等待该信号量,则唤醒其中一个。
(4)信号量接口函数
① semget() #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget (key_t key, int nsems, int semflg) ;
② semctl() #include <sys/ipc.h>
#include <sys/sem.h>
int semctl (int semid, int semnum, int cmd, ... ) ;
③ semop() #include <sys/ipc.h>
#include <sys/sem.h>
int semop (int semid, struct sembuf *sops, size_t nsops) ;
(5)内核信号量集属性 struct semid_ds {
struct ipc_perm sem_perm ;
time_t sem_otime;
time_t sem_ctime;
unsigned short sem_nsems;
};
四、内核是如何组织管理 IPC 资源的? 内核通过 统一权限模型(kern_ipc_perm)+ 分类资源管理(信号量/消息队列/共享内存)+ 命名空间隔离 实现 IPC 资源的高效组织。
(1)核心数据结构物理布局 struct ipc_namespace {
struct ipc_ids ids [3];
struct shmem_info shm_info ;
};
struct ipc_ids {
struct kern_ipc_perm *entries ;
int in_use;
unsigned short seq;
struct rw_semaphore rwsem ;
};
struct kern_ipc_perm {
key_t key;
uid_t uid;
gid_t gid;
mode_t mode;
int id;
};
(2)内核源代码关系图示 相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online