管道相关函数
1 pipe
- 是 Unix/Linux 系统中的一个系统调用,用于创建一个匿名管道
#include <unistd.h>
int pipe(int pipefd[2]);
参数说明:
pipefd[2]:一个包含两个整数的数组,用于存储管道的文件描述符:
pipefd[0]:管道的读端(用于从管道读取数据)巧记:用嘴巴口型(o)读
pipefd[1]:管道的写端(用于向管道写入数据)巧记:用笔(1)写
返回值:
成功时返回 0
失败时返回 -1 并设置 errno
2 error
errno
是 C 和 C++ 中用于报告错误的全局变量(或宏),全称为 "error number"。它由系统或标准库函数在操作失败时设置,用于指示具体的错误原因。代码出错时我们更想知道出错原因,就可以用error
常见 errno
错误码
错误码宏 | 值 | 含义 |
---|---|---|
EPERM | 1 | 操作无权限 |
ENOENT | 2 | 文件或目录不存在 |
EINTR | 4 | 系统调用被中断 |
EIO | 5 | 输入/输出错误 |
EBADF | 9 | 错误的文件描述符 |
EAGAIN | 11 | 资源暂时不可用 |
ENOMEM | 12 | 内存不足 |
EACCES | 13 | 权限不足 |
EFAULT | 14 | 非法内存访问 |
EEXIST | 17 | 文件已存在 |
EDOM | 33 | 数学参数超出定义域 |
ERANGE | 34 | 结果超出范围 |
一般和和strerror配合一起使用
#include <iostream>
#include <cerrno>
#include <cstring>
int main() {
errno = 0; // 先重置 errno
double x = sqrt(-1.0); // 尝试计算负数的平方根
if (errno == EDOM) { // EDOM 是域错误宏
std::cerr << "Error: " << std::strerror(errno) << "\n";
}
}
输出:
Error: Numerical argument out of domain
3 strerror
- 是 C 标准库中的一个函数,用于将错误代码(errno 值)转换为可读的错误描述字符串。下面我会详细解释它的用法和实际应用场景。
#include <string.h>
char *strerror(int errnum);
参数说明:
errnum:错误编号(通常是 errno 的值)
返回值:
返回指向错误描述字符串的指针(静态分配的字符串,不可修改)
不会失败(永远返回有效指针)
4 推荐使用 #include <cerrno>
而不是 #include <errno.h>
1. 符合 C++ 标准库的命名规范
C++ 标准库对 C 标准库的头文件进行了重新封装,采用无 .h
后缀的形式(如 <cstdio>
、<cstdlib>
、<cerrno>
),以区别于 C 的传统头文件(如 <stdio.h>
、stdlib.h
、errno.h>
)。
<cerrno>
是 C++ 标准化的头文件,明确属于 C++ 标准库。<errno.h>
是 C 风格的头文件,虽然 C++ 兼容它,但不推荐在新代码中使用。
2. 潜在的命名空间管理
理论上,<cerrno>
将相关名称(如 errno
、EDOM
、ERANGE
)放入 std
命名空间,而 <errno.h>
直接将它们暴露在全局命名空间。虽然实际实现中(由于兼容性要求):
errno
仍然是全局宏(无法放入std
)。EDOM
、ERANGE
等宏通常在全局命名空间也可用。
但使用 <cerrno>
能更清晰地表达“这是 C++ 代码”的意图,并可能在未来的标准中更好地支持命名空间隔离。
注意事项
errno
仍是全局宏:即使使用<cerrno>
,errno
也不会变成std::errno
(因为它是宏)。- 错误码宏(如
EDOM
):大多数实现仍允许全局访问,但理论上可以额外通过std::EDOM
访问(尽管实践中很少需要)。
5 fork()
系统调用详解
fork()
是 Unix/Linux 系统中的一个重要系统调用,用于创建一个新的进程(子进程)
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回值:
父进程:返回子进程的 PID(进程ID,> 0)。
子进程:返回 0。
出错时:返回 -1(并设置 errno)。
6 exit
- 是一个标准库函数,用于终止当前进程,并返回一个状态码给操作系统。它是进程正常退出的标准方式
#include <stdlib.h>
void exit(int status);
参数:
status:进程的退出状态码:
0 或 EXIT_SUCCESS:表示成功退出。
非零值(通常 EXIT_FAILURE=1):表示失败退出(具体含义由程序定义)
exit()
的运行机制
(1) 进程终止流程
当调用 exit()
时,操作系统会按顺序执行以下操作:
-
调用
atexit()
注册的函数(按注册的逆序执行)。 -
刷新所有标准 I/O 缓冲区(如
printf
未输出的内容会被强制写入)。 -
关闭所有打开的文件描述符。
-
释放进程占用的内存和其他资源。
-
向父进程发送状态码(可通过
wait()
或$?
获取)。
(2) exit()
vs _exit()
函数 | 说明 |
---|---|
exit() | 标准 C 库函数,会执行清理(刷新缓冲区、调用 atexit() 等)。 |
_exit() | 系统调用(<unistd.h> ),直接终止进程,不执行任何清理。 |
7 snprintf
snprintf
是 C 标准库中的一个格式化输出函数,用于安全地格式化字符串并写入缓冲区,比传统的 sprintf
更安全,因为它可以防止缓冲区溢出(Buffer Overflow)
#include <stdio.h>
int snprintf(
char *str, // 目标缓冲区
size_t size, // 缓冲区大小(最多写入 size-1 个字符 + '\0')
const char *format, // 格式化字符串(类似 printf)
... // 可变参数(要格式化的数据)
);
返回值:
成功:返回理论写入的字符数(不包括结尾的 \0),即使缓冲区不够。
错误:返回负值(如编码错误)。
8 getpid()
getppid()
getpid()
是 Unix/Linux 系统编程中的一个基础系统调用,用于获取当前进程的进程ID(PID)getppid()
是 Unix/Linux 系统调用,用于获取当前进程的父进程 PID(Process ID)
#include <unistd.h> // 必须包含的头文件
pid_t getpid(void); // 返回当前进程的 PID
返回值:
成功:返回当前进程的 PID(正整数)
不会失败(无错误码)
#include <unistd.h> // 必须包含的头文件
pid_t getppid(void); // 返回父进程的 PID
返回值:
成功:返回父进程的 PID(正整数)
不会失败(无错误码)
9 sizeof
sizeof
是 C/C++ 中的一个编译时运算符(不是函数!),用于计算变量、类型或表达式所占的内存大小(字节数)。它是静态计算的,不会在运行时影响程序性能
sizeof(变量或类型)
返回值:
size_t 类型的无符号整数(通常是 unsigned int 或 unsigned long)。
计算时机:在编译时确定,不会执行括号内的代码(如果传入表达式)
语法规则
操作对象 | 示例 | 是否必须加括号 | 备注 |
---|---|---|---|
变量名 | sizeof a | 可选 | 更简洁,但可能降低可读性 |
类型名 | sizeof(int) | 必须 | 不加括号会导致编译错误 |
表达式 | sizeof(a + b) | 必须 | 表达式需用括号包裹 |
示例
int arr[10];
变量(括号可选)
size_t s1 = sizeof arr; // 计算数组总大小
size_t s2 = sizeof(arr); // 等效写法
类型(括号必须)
size_t s3 = sizeof(int); // 计算 int 类型大小
表达式(括号必须)
size_t s4 = sizeof(arr[0]); // 计算数组元素大小
结构体/类成员的大小
struct S { int x; double y; };
size_t s = sizeof(S::x); // C++ 中合法,计算成员大小
创建管道实操
makefile
mypipe:mypipe.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -rf mypipe
mypipe.cc
#include <iostream>
#include <string>
#include <cerrno>
#include <cassert>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
int pipefd[2] = {0};
int n = pipe(pipefd);
if(n < 0)
{
std::cout << "pipe error, " << errno << ": " << strerror(errno) << std::endl;
return 1;
}
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
close(pipefd[0]);
int cnt = 0;
while(true)
{
char x = 'X';
write(pipefd[1], &x, 1);
std::cout << "Cnt: " << cnt++<<std::endl;
sleep(1);
}
close(pipefd[1]);
exit(0);
}
close(pipefd[1]);
char buffer[1024];
int cnt = 0;
while(true)
{
int n = read(pipefd[0], buffer, sizeof(buffer) - 1);
if(n > 0)
{
buffer[n] = '\0';
std::cout << "我是父进程, child give me message: " << buffer << std::endl;
}
else if(n == 0)
{
std::cout << "我是父进程, 读到了文件结尾" << std::endl;
break;
}
else
{
std::cout << "我是父进程, 读异常了" << std::endl;
break;
}
sleep(1);
if(cnt++ > 5) break;
}
close(pipefd[0]);
int status = 0;
waitpid(id, &status, 0);
std::cout << "sig: " << (status & 0x7F) << std::endl;
sleep(100);
return 0;
}
mypipe
:目标文件(可执行文件)名称mypipe.cc
:依赖文件(源代码文件)g++ -o $@ $^ -std=c++11
:编译命令$@
表示目标文件(mypipe)$^
表示所有依赖文件(这里只有 mypipe.cc)-std=c++11
指定使用 C++11 标准
.PHONY:clean
:声明 clean 是一个伪目标(不是实际文件)rm -rf mypipe
:删除生成的可执行文件
父进程管理多个子进程实现管道通信实操
Makefile
ctrlProcess:ctrlProcess.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -rf ctrlProcess
Task.hpp
#pragma once
#include <iostream>
#include <vector>
#include <unistd.h>
typedef void (*fun_t)();
void a() { std::cout << "a任务正在执行...\n" << std::endl; }
void b() { std::cout << "b任务正在执行...\n" << std::endl; }
void c() { std::cout << "c任务正在执行...\n" << std::endl; }
#define A 0
#define B 1
#define C 2
class Task
{
public:
Task()
{
funcs.push_back(a);
funcs.push_back(b);
funcs.push_back(c);
}
void Execute(int command)
{
if (command >= 0 && command < funcs.size()) funcs[command]();
}
public:
std::vector<fun_t> funcs;
};
ctrlProcess.cc
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "Task.hpp"
using namespace std;
const int gnum = 3;
Task t;
class EndPoint
{
private:
static int number;
public:
pid_t _c_id;
int _w_fd;
string processname;
public:
EndPoint(int id, int fd) :_c_id(id), _w_fd(fd)
{
//process-0[pid:fd]
char namebuffer[64];
snprintf(namebuffer, sizeof(namebuffer), "process-%d[%d:%d]", number++, _c_id, _w_fd);
processname = namebuffer;
}
string name() const { return processname; }
};
int EndPoint::number = 0;
void WaitCommand()
{
while(1)
{
int command = 0;
int n = read(0, &command, sizeof(command));
if (n == sizeof(int)) t.Execute(command);
else if (n == 0)
{
std::cout << "父进程关闭了写端" << getpid() << std::endl;
break;
}
else break;
}
}
void createProcesses(vector<EndPoint> *end_points)
{
vector<int> fds;
for(int i = 0; i < gnum; ++i)
{
int pipefd[2] = {0};
int n = pipe(pipefd);
assert(n == 0); (void)n;
pid_t id = fork();
assert(id != -1);
if (id == 0)
{
for(auto &fd : fds) close(fd);
close(pipefd[1]);
dup2(pipefd[0], 0);
WaitCommand();
close(pipefd[0]);
exit(0);
}
close(pipefd[0]);
end_points->push_back(EndPoint(id, pipefd[1]));
fds.push_back(pipefd[1]);
}
}
int ShowBoard()
{
std::cout << "##########################################" << std::endl;
std::cout << "| 0. 执行日志任务 1. 执行数据库任务 |" << std::endl;
std::cout << "| 2. 执行请求任务 3. 退出 |" << std::endl;
std::cout << "##########################################" << std::endl;
std::cout << "请选择# ";
int command = 0;
std::cin >> command;
return command;
}
void ctrlProcess(const vector<EndPoint> &end_points)
{
int cnt = 0;
while(true)
{
int command = ShowBoard();
if (command == 3) break;
if (command < 0 || command > 2) continue;
int index = cnt++;
cnt %= end_points.size();
string name = end_points[index].name();
cout << "选择了进程: " << name << " | 处理任务: " << command << endl;
write(end_points[index]._w_fd, &command, sizeof(command));
sleep(1);
}
}
void waitProcess(const vector<EndPoint> &end_points)
{
for(int i = 0; i < end_points.size(); ++i)
{
std::cout << "父进程让子进程退出:" << end_points[i]._c_id << std::endl;
close(end_points[i]._w_fd);
waitpid(end_points[i]._c_id, nullptr, 0);
std::cout << "父进程回收了子进程:" << end_points[i]._c_id << std::endl;
}
}
// #define A 0
// #define B 1
// #define C 2
int main()
{
vector<EndPoint> end_points;
createProcesses(&end_points);
ctrlProcess(end_points);
waitProcess(end_points);
return 0;
}