Unix/Linux系统相关异常
Unix/Linux系统相关异常主要封装了操作系统调用失败时的错误信息,是连接底层系统错误与上层应用逻辑的桥梁。以下从核心概念、常见类型、实战应用到最佳实践,系统讲解这类异常:
层级关系
std::runtime_error
└─ UnixError (封装errno,系统调用通用异常)
├─ FileError (文件操作异常)
│ ├─ FileNotFoundException (文件不存在)
│ ├─ FileExistsError (文件已存在)
│ ├─ FilePermissionError (权限不足)
│ └─ FileUnexpectedEOFError (文件读取不完整)
├─ IOError (输入输出异常)
│ ├─ ReadError (读取失败)
│ └─ WriteError (写入失败)
├─ MemoryError (内存操作异常)
│ ├─ MmapError (内存映射失败)
│ └─ AllocationError (内存分配失败)
└─ ProcessError (进程操作异常)
├─ ForkError (fork失败)
└─ ExecError (执行程序失败)
一、Unix/Linux系统异常的核心概念
1. 系统错误的源头:errno
- 定义:
errno是Unix/Linux系统中的全局整数变量(线程局部存储,每个线程独立),当系统调用(如open、read)失败时,会被设置为特定错误码,对应具体错误类型。 - 查看错误信息:通过
strerror(errno)可将错误码转为可读字符串,例如:errno = ENOENT; // 文件不存在 printf("%s\n", strerror(errno)); // 输出 "No such file or directory"
2. 异常封装的意义
系统调用通常返回-1表示失败,但上层应用需要更清晰的错误类型和上下文。通过封装errno到异常中,可以:
- 区分不同错误类型(如文件不存在、权限不足、磁盘满)。
- 传递错误上下文(如操作的文件名、函数名)。
- 统一错误处理流程,避免重复编写
if (ret == -1)判断。
二、常见Unix/Linux系统异常类型
1. 基础系统异常类
定位:封装所有 Unix/Linux 系统调用的错误(如 open、read、lseek、fork 等)。
#include <stdexcept>
#include <cstring> // strerror
class UnixError : public std::runtime_error {
public:
// 无参构造:直接使用errno的错误信息
UnixError() : std::runtime_error(strerror(errno)) {}
// 带参数构造:添加自定义错误描述
UnixError(const std::string& msg)
: std::runtime_error(msg + ": " + strerror(errno)) {}
};
2. 文件操作相关异常
定位:专门处理与文件操作相关的错误(如文件打开失败、读写错误)。
class FileError : public UnixError {
public:
// 封装文件操作的具体错误(打开/读写/关闭)
FileError(const std::string& path, const std::string& operation)
: UnixError("File " + operation + " failed for " + path) {}
};
// 使用示例
if (open("data.db", O_RDONLY) == -1) {
throw FileError("data.db", "open");
}
3. I/O操作异常
class IOError : public UnixError {
public:
IOError(const std::string& operation, int fd) {
char buf[100];
snprintf(buf, sizeof(buf), "IO %s failed on fd %d",
operation.c_str(), fd);
what_str_ = buf;
}
private:
std::string what_str_;
const char* what() const noexcept override {
return what_str_.c_str();
}
};
4. 内存操作异常
class MemoryError : public UnixError {
public:
MemoryError(const std::string& operation)
: UnixError("Memory " + operation + " failed") {}
};
// 示例:mmap失败时抛出
void* ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (ptr == MAP_FAILED) {
throw MemoryError("mmap");
}
三、系统异常的实战应用场景
1. 文件I/O操作中的异常处理
void read_file(int fd, char* buffer, size_t size) {
ssize_t bytes_read = read(fd, buffer, size);
if (bytes_read == -1) {
throw IOError("read", fd);
} else if (bytes_read < size) {
throw std::runtime_error("Partial read, expected " +
std::to_string(size) + " bytes, got " +
std::to_string(bytes_read));
}
}
2. 进程操作异常
pid_t pid = fork();
if (pid == -1) {
throw UnixError("fork failed");
} else if (pid == 0) {
// 子进程逻辑
} else {
// 父进程逻辑
}
3. 网络套接字异常
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
throw UnixError("socket creation failed");
}
struct sockaddr_in addr;
// 初始化addr...
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
throw UnixError("bind failed");
}
四、系统异常处理的最佳实践
1. 保存errno避免竞争
void safe_write(int fd, const char* data, size_t size) {
int original_errno = errno; // 先保存errno,避免多线程修改
if (write(fd, data, size) != size) {
original_errno = errno; // 若write失败,errno已被设置为具体错误
throw UnixError("write failed");
}
errno = original_errno; // 恢复errno(可选,视场景而定)
}
2. 区分可恢复与不可恢复错误
- 可恢复错误:如临时文件锁冲突(
EAGAIN),可重试。 - 不可恢复错误:如文件不存在(
ENOENT),需上层处理。
try {
// 尝试打开文件
int fd = open("data.db", O_RDONLY);
} catch (const UnixError& e) {
if (errno == ENOENT) {
// 文件不存在,创建新文件
fd = open("data.db", O_RDWR | O_CREAT, 0666);
} else {
// 其他错误,无法恢复
throw;
}
}
3. 错误码与异常类型映射
对高频错误码,可封装为特定异常类,便于针对性处理:
class FileNotFoundException : public FileError {
public:
FileNotFoundException(const std::string& path)
: FileError(path, "open") {
// 确保errno是ENOENT
errno = ENOENT;
}
};
// 使用
try {
open_file("non_existent.txt");
} catch (const FileNotFoundException& e) {
std::cout << "File not found, creating new one..." << std::endl;
create_new_file("non_existent.txt");
}
4. 资源管理与异常安全
使用RAII(资源获取即初始化)模式,确保异常抛出时资源自动释放:
class FileGuard {
public:
FileGuard(const std::string& path, int flags) {
fd_ = open(path.c_str(), flags);
if (fd_ == -1) {
throw FileError(path, "open");
}
}
~FileGuard() {
if (fd_ != -1) {
close(fd_);
}
}
int get_fd() const { return fd_; }
private:
int fd_ = -1;
};
// 使用
void process_file(const std::string& path) {
FileGuard file(path, O_RDWR);
// 操作file.get_fd(),异常时自动关闭文件
}
五、常见Unix错误码与场景
| 错误码 | 宏定义 | 含义 | 典型场景 |
|---|---|---|---|
| 2 | ENOENT | 文件或目录不存在 | open("nonexist.txt") |
| 13 | EACCES | 权限不足 | 访问受保护的文件 |
| 28 | ENOSPC | 磁盘空间不足 | 写入文件时磁盘满 |
| 9 | EBADF | 无效的文件描述符 | 使用已关闭的fd读写 |
| 11 | EAGAIN | 资源暂时不可用(可重试) | 非阻塞I/O操作时无数据可读 |
| 22 | EINVAL | 无效参数 | lseek使用非法whence参数 |
| 12 | ENOMEM | 内存不足 | malloc/mmap分配失败 |
总结
Unix/Linux系统相关异常的核心是对errno的封装与抽象,通过将系统错误码转换为语义化的异常类型,使上层应用能更清晰地处理不同错误场景。在数据库等系统级项目中,合理设计系统异常体系可以:
- 区分底层系统错误与业务逻辑错误。
- 提供丰富的错误上下文,加速问题定位。
- 结合RAII模式保证异常安全,避免资源泄漏。
掌握这些异常的使用,能显著提升代码的健壮性和可维护性,尤其是在处理文件I/O、进程管理、内存映射等关键操作时。

浙公网安备 33010602011771号