Linux 进程间通信详解:管道、共享内存与内核机制
Linux 进程间通信(IPC)是不同进程间交换信息的技术方法。深入解析 IPC 机制,涵盖匿名管道与命名管道的创建及原理,System V 共享内存、消息队列与信号量的接口函数及内核数据结构。通过代码示例演示进程池实现及同步问题解决方案,解决文件描述符泄漏等 BUG。最后探讨内核如何通过命名空间和权限控制隔离 IPC 资源,呈现进程间通信的全景视角。

Linux 进程间通信(IPC)是不同进程间交换信息的技术方法。深入解析 IPC 机制,涵盖匿名管道与命名管道的创建及原理,System V 共享内存、消息队列与信号量的接口函数及内核数据结构。通过代码示例演示进程池实现及同步问题解决方案,解决文件描述符泄漏等 BUG。最后探讨内核如何通过命名空间和权限控制隔离 IPC 资源,呈现进程间通信的全景视角。

进程间通信 (Inter-Process Communication, IPC) 是指在不同进程之间传播或交换信息的技术方法。由于操作系统中的进程通常拥有独立的地址空间,一个进程不能直接访问另一个进程的变量或数据结构,因此需要专门的机制来实现进程间的数据共享和通信。
进程间通信的本质:是让不同的进程先看到同一份资源(内存),然后才有通信的条件。
(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 权限 | 文件权限 |
| 跨平台性 | 有限 | 有限 | 较好 |
| 性能 | 中等 | 高 (特别是共享内存) | 高 |
• 数据传输:一个进程需要将它的数据发送给另一个进程。 • 资源共享:多个进程之间共享同样的资源。 • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。 • 进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
管道(Pipe)是 Unix 系统最古老的进程间通信(IPC)方式,其核心思想是: 将一个进程的输出数据流(stdout)直接连接到另一个进程的输入数据流(stdin),形成一个单向的、基于字节流(无消息边界,二进制传输)的通信通道(内核级)。
• 单向通信:数据只能从写端流向读端(半双工) • 仅限亲缘进程:通过 fork() 创建的父子/兄弟进程间使用 • 内存级通信:由内核管理缓冲区,不依赖磁盘文件 • 随进程销毁:当所有相关进程终止时,管道自动释放
#include <unistd.h>
int pipe(int pipefd[2]); // 成功:返回 0,并填充 pipefd
// 失败:返回 -1,并设置 errno 表示错误原因
参数:pipefd[2] 表示一个长度为 2 的整型数组,用于存储管道的两个文件描述符: • pipefd[0]:管道的读端(用于从管道读取数据)。 • pipefd[1]:管道的写端(用于向管道写入数据)。
注:调用 pipe 创建管道不要文件路径,没有文件名。是内存级的,被 OS 单独设计,称之为匿名管道。
单个进程中的管道几乎没有任何用处,通常,进程会先调用 pipe(),接着调用 fork(),从而创建父进程到子进程的 IPC 通道。
我们怎么保证两个进程打开的是同一个管道文件?fork 之后,子进程会继承父进程的管道文件描述符,父子进程通过 fd 访问同一管道,内核确保数据同步和生命周期管理。
fork 之后做什么取决于我们想要的数据流的方向。对于父进程到子进程的管道,父进程关闭管道的读端(fd[0]),子进程关闭写端(fd[1])。
管道在内核中通过 inode 表示,不同于磁盘文件的 inode,它关联的是内存中的 pipe_inode_info(管道核心)。
管道是通过 文件描述符→file→inode→pipe_inode_info→数据页 的链式关系实现的,多个进程通过不同层级的共享(file 结构独立,但底层 pipe_inode_info 共享)完成通信。
匿名管道的'匿名性'体现在其 inode 不关联文件系统,仅存于内存。
管道的本质也是文件!
创建一个管道,用于父子进程间单向通信,子进程每隔 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;
}
① 只能用于具有共同祖先的进程(亲缘关系进程)
• 管道通常由父进程创建,然后调用 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 |
这是一个基于 C++ 实现的简单进程池系统,主要用于管理多个子进程并通过管道进行任务分发。
● 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() {}
};
● 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() {};
};
#include "ProcessPool.hpp"
int main() {
ProcessPool pp(defaultnum);
pp.Start();
int cnt = 5;
while (cnt--) {
pp.Run();
sleep(1);
}
pp.Stop();
return 0;
}
process_pool:Main.cc
g++ -o $@ $^
.PHONY:clean
clean:
rm -f process_pool
如果我们用整合后的 StopAndWait() 代替 WaitSubProcess() 和 StopAndWait() 呢。会发生什么?
问题根源:
解决问题
方案 1:在执行 StopAndWait() 时,倒着关闭文件描述符表。
void StopAndWait() {
for (int i = _channels.size() - 1; i >= 0; i--) {
_channels[i].Close();
_channels[i].Wait();
}
}
方案 2:在子进程中关闭无关描述符。
void CloseAll() {
for (auto &channel : _channels) {
channel.Close();
}
}
命名管道(Named Pipe),也称为 FIFO(First In First Out)文件,是一种特殊的文件类型,用于在不相关进程(无父子关系)之间进行通信(特性)。与匿名管道(pipe())不同,命名管道在文件系统中有一个可见的路径名,任何进程只要知道该路径,都可以访问它。
$ mkfifo fifo
$ ll total 8
prw-rw-r-- 1 zyt zyt 0 May 22 16:24 fifo|
命名管道(FIFO)是一种 同步通信机制,写入操作(>)会 阻塞,直到另一端有进程打开管道并开始读取。
# 终端 1(写入端)
$ echo "hello fifo">fifo
# 终端 2(读取端)
$ cat < fifo
hello fifo
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
server.cc
#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;
}
client.cc
#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;
}
// comm.hpp
#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 IPC 是 Unix/Linux 系统中一种经典的进程间通信(IPC)标准,包含以下三种核心机制(共享内存,信号量,消息队列),均在内核中维护全局唯一的标识符和数据结构。
共享内存允许两个或多个进程共享一个给定的存储区。因为数据不需要在客户进程和服务器进程之间复制,所以这是最快的一种 IPC。
- 映射之后读写直接被对方看到。
- 不需要进行系统调用获取或写入内容,以指针地址的方式。
内核分配一块物理内存:通过系统调用(如 shmget())在内核中申请一块物理内存区域,称为共享内存段。
进程通过 shmat() 将共享内存段映射到自己的虚拟地址空间。
进程通过指针操作共享内存,无需系统调用。
以两个进程共享内存为例。
共享内存的机制依赖于内核数据结构和物理内存的紧密结合。
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_ds u;
struct file *shm_file;
unsigned long shm_nattch;
};
shm_file->f_mapping:指向共享内存的物理页帧集合(struct address_space)。
操作系统通过内核数据结构和引用计数机制精确跟踪共享内存是否被进程使用。
每个内核中的 IPC 结构,都用一个非负整数的标识符加以引用。
每个 IPC 对象都与一个键 key 相关联,将这个键作为该对象的外部名,key 值是由用户层构建并传入的。
ftok() :基于文件路径和项目 ID 计算产生一个 key。
#include <sys/types.h>
key_t ftok(const char *path, int id);
| 问题 | 判断依据 |
|---|---|
| 共享内存是否存在? | shmget(key, 0, 0) 是否成功 |
| 是否同一共享内存? | 两个进程是否使用相同的 key |
创建或获取一个共享内存段。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
控制共享内存段的系统调用。
#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.
将共享内存段挂接(attach)到当前进程的地址空间。
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
从调用进程的地址空间中分离位于 shmaddr 的共享内存段。
#include <sys/shm.h>
int shmdt(const void *shmaddr);
#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;
};
#include "comm.hpp"
int main() {
Shm shm;
shm.Create();
sleep(3);
shm.Attach();
shm.VirtualAddr();
sleep(3);
shm.Destory();
return 0;
}
// Shm.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;
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;
};
server.cc
#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;
}
client.cc
#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;
}
Fifo.hpp
#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;
};
server.cc
#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;
}
client.cc
#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;
}
消息队列是一种进程间通信(IPC)机制,它允许不同进程通过发送和接收消息来进行异步通信。
| 接收模式 | 参数设置 | 行为 |
|---|---|---|
| 先进先出 (FIFO) | msgtyp=0 | 读取队列中最早的消息 |
| 指定类型 | msgtyp>0 | 读取队列中第一个该类型消息 |
| 类型阈值 | msgtyp<0 | 读取类型≤ |
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
• 多个执行流(进程),能看到的同一份公共资源:共享资源 • 被保护起来的资源叫做临界资源 • 保护的方式常见:互斥与同步
• 原子性是指 一个操作(或一系列操作)要么完全执行,要么完全不执行,不会被其他线程/进程打断。
信号量本质是一个计数器,用于表明临界资源中资源数量的多少。
P/V 操作本质:
| 操作 | 名称 | 行为 |
|---|---|---|
| P (Proberen) | 等待/获取 | 尝试减少信号量值。若值 ≥ 1,则减 1 并继续;否则进程阻塞。 |
| V (Verhogen) | 释放/唤醒 | 增加信号量值。若有进程在等待该信号量,则唤醒其中一个。 |
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ... /* union semun arg */);
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
struct semid_ds {
struct ipc_perm sem_perm;
time_t sem_otime;
time_t sem_ctime;
unsigned short sem_nsems;
};
内核通过 统一权限模型(kern_ipc_perm)+ 分类资源管理(信号量/消息队列/共享内存)+ 命名空间隔离 实现 IPC 资源的高效组织。
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;
};
(此处省略内核源码关系图,参考原文档结构)

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online