C++ 操作系统与系统编程
一、进程管理
⭐ 进程的创建与终止
cpp
#include <unistd.h>
#include <sys/wait.h>
void ForkExample() {
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
} else if (pid == 0) {
// 子进程
std::cout << "Child PID: " << getpid() << "\n";
execlp("ls", "ls", "-la", nullptr); // 替换为 ls 程序
_exit(1); // exec 失败才会到这里
} else {
// 父进程
std::cout << "Parent PID: " << getpid() << ", Child: " << pid << "\n";
int status;
waitpid(pid, &status, 0); // 等待子进程结束
if (WIFEXITED(status)) {
std::cout << "Child exited with code: " << WEXITSTATUS(status) << "\n";
}
}
}
⭐ 进程 vs 线程选择
| 特性 | 多进程 | 多线程 |
|---|---|---|
| 隔离性 | ✅ 强(崩溃不互相影响) | ❌ 弱(一个崩全部崩) |
| 通信 | IPC(较复杂) | 共享内存(简单) |
| 创建开销 | 大 | 小 |
| 调试 | 较难 | 较容易 |
| 适用场景 | 高可靠性(如 Nginx) | 高性能(如线程池) |
二、进程间通信(IPC)
⭐ IPC 方式概览
⭐ 管道(Pipe)
cpp
#include <unistd.h>
void PipeExample() {
int fd[2]; // fd[0] 读端, fd[1] 写端
pipe(fd);
pid_t pid = fork();
if (pid == 0) {
// 子进程:写入
close(fd[0]); // 关闭读端
const char* msg = "Hello from child";
write(fd[1], msg, strlen(msg));
close(fd[1]);
_exit(0);
} else {
// 父进程:读取
close(fd[1]); // 关闭写端
char buf[256]{};
read(fd[0], buf, sizeof(buf));
std::cout << "Received: " << buf << "\n";
close(fd[0]);
wait(nullptr);
}
}
⭐ 共享内存(最快的 IPC)
cpp
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
// POSIX 共享内存
void SharedMemoryExample() {
const char* name = "/my_shm";
const int SIZE = 4096;
// 创建/打开共享内存
int fd = shm_open(name, O_CREAT | O_RDWR, 0666);
ftruncate(fd, SIZE);
// 映射到进程地址空间
void* ptr = mmap(nullptr, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// 写入数据
sprintf((char*)ptr, "Hello, shared memory!");
// 读取数据(另一个进程也可以读取同一块内存)
std::cout << (char*)ptr << "\n";
// 清理
munmap(ptr, SIZE);
shm_unlink(name);
}
⭐ mmap 内存映射文件
cpp
#include <sys/mman.h>
#include <fcntl.h>
// 将文件映射到内存(零拷贝读取)
std::string ReadFileWithMmap(const std::string& path) {
int fd = open(path.c_str(), O_RDONLY);
if (fd < 0) return "";
struct stat sb;
fstat(fd, &sb);
size_t size = sb.st_size;
void* addr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
if (addr == MAP_FAILED) return "";
std::string content((char*)addr, size);
munmap(addr, size);
return content;
}
三、信号(Signal)
⭐ 常见信号
| 信号 | 编号 | 含义 | 默认动作 |
|---|---|---|---|
SIGINT | 2 | Ctrl+C 中断 | 终止 |
SIGTERM | 15 | 终止请求 | 终止 |
SIGKILL | 9 | 强制终止 | 终止(不可捕获) |
SIGSEGV | 11 | 段错误 | 终止+core |
SIGCHLD | 17 | 子进程终止 | 忽略 |
SIGPIPE | 13 | 写已关闭的管道 | 终止 |
SIGUSR1/2 | 10/12 | 用户自定义 | 终止 |
信号处理
cpp
#include <signal.h>
#include <atomic>
std::atomic<bool> g_running{true};
// 信号处理函数(注意:只能调用异步信号安全函数)
void SignalHandler(int sig) {
if (sig == SIGINT || sig == SIGTERM) {
g_running.store(false); // 优雅退出
}
}
void SetupSignals() {
struct sigaction sa{};
sa.sa_handler = SignalHandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, nullptr);
sigaction(SIGTERM, &sa, nullptr);
// 忽略 SIGPIPE(网络编程必需)
signal(SIGPIPE, SIG_IGN);
}
int main() {
SetupSignals();
while (g_running) {
DoWork();
}
Cleanup();
return 0;
}
四、文件描述符与 I/O
⭐ 文件描述符
┌──────────────────────────────────────────┐
│ 进程的文件描述符表 │
│ │
│ fd 0 ──→ stdin (标准输入) │
│ fd 1 ──→ stdout (标准输出) │
│ fd 2 ──→ stderr (标准错误) │
│ fd 3 ──→ 打开的文件 / socket / pipe │
│ fd 4 ──→ ... │
│ ... │
└──────────────────────────────────────────┘
⭐ 非阻塞 I/O
cpp
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
// 设置非阻塞
void SetNonBlocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
// 非阻塞读取
ssize_t NonBlockingRead(int fd, char* buf, size_t size) {
ssize_t n = read(fd, buf, size);
if (n < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return 0; // 暂时没有数据,不是错误
}
return -1; // 真正的错误
}
return n;
}
五种 I/O 模型
五、定时器
⭐ 常用定时器方案
cpp
// 1. sleep / usleep(简单但不精确)
sleep(1); // 秒
usleep(1000); // 微秒
// 2. timerfd(推荐,配合 epoll)
#include <sys/timerfd.h>
int CreateTimer(int interval_ms) {
int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
itimerspec ts{};
ts.it_value.tv_sec = interval_ms / 1000;
ts.it_value.tv_nsec = (interval_ms % 1000) * 1000000;
ts.it_interval = ts.it_value; // 重复
timerfd_settime(tfd, 0, &ts, nullptr);
return tfd; // 可以放入 epoll 监听
}
// 3. C++ 标准库计时
#include <chrono>
#include <thread>
void PreciseSleep(int ms) {
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}
六、守护进程(Daemon)
⭐ 创建守护进程的标准步骤
cpp
void Daemonize() {
pid_t pid = fork();
if (pid > 0) _exit(0); // 父进程退出
if (pid < 0) _exit(1);
setsid(); // 新会话
pid = fork(); // 第二次 fork
if (pid > 0) _exit(0);
if (pid < 0) _exit(1);
chdir("/");
umask(0);
// 关闭标准 fd
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// 重定向到 /dev/null
open("/dev/null", O_RDWR); // fd 0
dup(0); // fd 1
dup(0); // fd 2
}
七、系统调用与性能
⭐ 减少系统调用的技巧
| 优化 | 说明 | 效果 |
|---|---|---|
| 缓冲 I/O | fread/fwrite 内部缓冲 | 减少 read/write 次数 |
| mmap | 内存映射替代 read/write | 零拷贝 |
| sendfile | 内核态直接传文件到 socket | 零拷贝网络传输 |
| writev/readv | 散布/聚集 I/O | 合并多次 write |
| epoll | 事件驱动替代轮询 | 高并发 |
| io_uring (Linux 5.1+) | 异步 I/O 新接口 | 极致性能 |
sendfile 零拷贝
cpp
#include <sys/sendfile.h>
// 将文件直接发送到 socket(不经过用户态)
void SendFileToSocket(int file_fd, int socket_fd, size_t size) {
off_t offset = 0;
sendfile(socket_fd, file_fd, &offset, size);
// 数据路径:磁盘 → 内核缓冲区 → socket(不经过用户空间)
}
八、总结知识图谱
📑 文章目录
💬 评论