报表:
bash
=======================================================================
Linux select() 返回值 / errno 对照表
=======================================================================
FD_SETSIZE = 1024
sizeof(fd_set) = 128 字节
sizeof(long) = 8 字节
已确保 RLIMIT_NOFILE >= 2048
-----------------------------------------------------------------------
场景 A:缓冲区足够大(堆分配覆盖 maxfd),fd 打开且可读
-----------------------------------------------------------------------
select(nfds=1023, fd=1022 open & readable) | ret = 1 | errno = 0 (Success)
select(nfds=1024, fd=1023 open & readable) | ret = 1 | errno = 0 (Success)
select(nfds=1025, fd=1024 open & readable) | ret = 1 | errno = 0 (Success)
select(nfds=1026, fd=1025 open & readable) | ret = 1 | errno = 0 (Success)
-----------------------------------------------------------------------
场景 B:缓冲区只有 128 字节,nfds = fd+1 (fd >= 1024)
用 mmap + guard page 让内核越界读立刻失败
-----------------------------------------------------------------------
select(nfds=1025) with only 128B buffer at page end | ret = -1 | errno = 14 (Bad address)
select(nfds=1026) with only 128B buffer at page end | ret = -1 | errno = 14 (Bad address)
-----------------------------------------------------------------------
场景 C:fd_set 里放了一个已关闭的 fd
-----------------------------------------------------------------------
select(nfds=4) with closed fd=3 in set | ret = -1 | errno = 9 (Bad file descriptor)
-----------------------------------------------------------------------
场景 D:nfds 非法
-----------------------------------------------------------------------
select(nfds=-1, NULL, NULL, NULL, {0,0}) | ret = -1 | errno = 22 (Invalid argument)
select(nfds=0, NULL, NULL, NULL, {0,0}) | ret = 0 | errno = 0 (Success)
-----------------------------------------------------------------------
场景 E:所有 fd_set 位为 0,nfds 取 1024 / 1025 / 1026
-----------------------------------------------------------------------
select(nfds=1024, all-zero, {0,0}) | ret = 0 | errno = 0 (Success)
select(nfds=1025, all-zero, {0,0}) | ret = 0 | errno = 0 (Success)
select(nfds=1026, all-zero, {0,0}) | ret = 0 | errno = 0 (Success)
-----------------------------------------------------------------------
场景 F:nfds=1026,fd_set 里只有 fd=1025 就绪,超时 500ms
-----------------------------------------------------------------------
select(nfds=1026, fd=1025 ready, 500ms) | ret = 1 | errno = 0 (Success)
=======================================================================
结论
-----------------------------------------------------------------------
A) 缓冲区够大、nfds = maxfd+1 时,fd = 1024 / 1025 完全正常:
ret = 就绪 fd 数,errno = 0。不会返回 EINVAL 或 EBADF。
B) 缓冲区只有标准 128 字节但 nfds > 1024 时,内核拷贝 fd_set
越界读,若缓冲区紧邻 guard page -> ret = -1, errno = EFAULT(14)。
C) fd_set 中含已关闭的 fd -> ret = -1, errno = EBADF(9)。
D) nfds < 0 -> ret = -1, errno = EINVAL(22)。
nfds = 0 且所有位为 0 -> ret = 0。
E) 所有 fd_set 位为 0 时,nfds 即使传 1024/1025/1026,
只要缓冲区够大,select 直接按 timeout 超时返回 0,errno = 0。
F) 标准 fd_set 下 FD_SET(1024, &set) / FD_SET(1025, &set)
是用户态越界写,属于未定义行为,不会产生 errno。
=======================================================================
代码:
cpp
// select_errno_demo.cpp
//
// 专门测试 Linux select() 在各种边界下返回什么、errno 是什么。
//
// 编译:
// g++ -std=c++17 -O0 -g -Wall -Wextra -o select_errno_demo select_errno_demo.cpp
// 运行:
// ./select_errno_demo
//
//#define _GNU_SOURCE
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cerrno>
#include <cstdint>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/resource.h>
/* ===================================================================== */
/* 工具函数 */
/* ===================================================================== */
static bool raise_nofile(int need)
{
struct rlimit rl;
if (::getrlimit(RLIMIT_NOFILE, &rl) != 0) return false;
if (static_cast<int>(rl.rlim_cur) >= need) return true;
rlim_t want = static_cast<rlim_t>(need);
if (rl.rlim_max != RLIM_INFINITY && want > rl.rlim_max)
want = rl.rlim_max;
rl.rlim_cur = want;
if (::setrlimit(RLIMIT_NOFILE, &rl) != 0) return false;
if (::getrlimit(RLIMIT_NOFILE, &rl) != 0) return false;
return static_cast<int>(rl.rlim_cur) >= need;
}
/* 统一格式打印结果,方便对照 */
static void line(const char *desc, int ret, int err)
{
if (ret < 0) {
std::printf(" %-56s | ret = %3d | errno = %-3d (%s)\n",
desc, ret, err, std::strerror(err));
} else {
std::printf(" %-56s | ret = %3d | errno = %-3d (%s)\n",
desc, ret, err, err ? std::strerror(err) : "Success");
}
}
/* 创建一个编号 >= min_fd 且立刻"可读"的 fd */
static int make_readable_fd_at_least(int min_fd, int *wfd_out)
{
int pfd[2];
if (::pipe(pfd) < 0) { std::perror("pipe"); std::exit(EXIT_FAILURE); }
int rfd = ::fcntl(pfd[0], F_DUPFD, min_fd);
if (rfd < 0) { std::perror("fcntl(F_DUPFD)"); std::exit(EXIT_FAILURE); }
::close(pfd[0]);
char c = 'x';
if (::write(pfd[1], &c, 1) != 1) { std::perror("write"); std::exit(EXIT_FAILURE); }
*wfd_out = pfd[1];
return rfd;
}
/* 精确拿到编号为 target 的 fd;失败返回 -1 */
static int dupfd_exact(int fd, int target)
{
if (target < 0) return -1;
int got = ::fcntl(fd, F_DUPFD, target);
if (got < 0) return -1;
if (got != target) { ::close(got); return -1; }
return got;
}
/* 在堆上分配一个足以覆盖 [0, maxfd] 的 fd_set 缓冲区 */
static fd_set *fdset_alloc(int maxfd)
{
const size_t bits = static_cast<size_t>(maxfd) + 1;
const size_t bytes = (bits + 7) / 8;
size_t nsets = (bytes + sizeof(fd_set) - 1) / sizeof(fd_set);
if (nsets == 0) nsets = 1;
fd_set *p = static_cast<fd_set *>(std::calloc(nsets, sizeof(fd_set)));
if (!p) { std::perror("calloc"); std::exit(EXIT_FAILURE); }
return p;
}
/* ===================================================================== */
/* 主程序 */
/* ===================================================================== */
int main(void)
{
std::printf("=======================================================================\n");
std::printf(" Linux select() 返回值 / errno 对照表\n");
std::printf("=======================================================================\n");
std::printf(" FD_SETSIZE = %d\n", FD_SETSIZE);
std::printf(" sizeof(fd_set) = %zu 字节\n", sizeof(fd_set));
std::printf(" sizeof(long) = %zu 字节\n\n", sizeof(long));
if (!raise_nofile(2048)) {
std::printf(" [警告] 无法把 RLIMIT_NOFILE 提升到 2048\n");
} else {
std::printf(" 已确保 RLIMIT_NOFILE >= 2048\n\n");
}
/* ------------------------------------------------------------------ */
/* 准备 fd:1022 / 1023 / 1024 / 1025,均打开且可读 */
/* ------------------------------------------------------------------ */
struct FdPair { int wanted; int rfd; int wfd; };
FdPair fds[] = {
{ 1022, -1, -1 },
{ 1023, -1, -1 },
{ 1024, -1, -1 },
{ 1025, -1, -1 },
};
const int N = static_cast<int>(sizeof(fds) / sizeof(fds[0]));
for (int i = 0; i < N; ++i) {
int w = -1;
int r = make_readable_fd_at_least(fds[i].wanted, &w);
if (r != fds[i].wanted) {
int exact = dupfd_exact(r, fds[i].wanted);
if (exact != fds[i].wanted) {
std::printf(" [错误] 无法把 fd 精确调到 %d\n", fds[i].wanted);
::close(r); ::close(w); fds[i].rfd = -1; continue;
}
::close(r); r = exact;
}
fds[i].rfd = r;
fds[i].wfd = w;
}
struct timeval tv;
/* ================================================================== */
/* 场景 A:缓冲区足够大,fd 打开且可读 */
/* ================================================================== */
std::printf("-----------------------------------------------------------------------\n");
std::printf(" 场景 A:缓冲区足够大(堆分配覆盖 maxfd),fd 打开且可读\n");
std::printf("-----------------------------------------------------------------------\n");
for (int i = 0; i < N; ++i) {
if (fds[i].rfd < 0) continue;
const int fd = fds[i].rfd;
fd_set *rfds = fdset_alloc(fd);
FD_SET(fd, rfds);
tv.tv_sec = 0; tv.tv_usec = 0;
errno = 0;
const int ret = ::select(fd + 1, rfds, nullptr, nullptr, &tv);
const int e = errno;
char desc[128];
std::snprintf(desc, sizeof(desc),
"select(nfds=%d, fd=%d open & readable)", fd + 1, fd);
line(desc, ret, e);
std::free(rfds);
}
std::printf("\n");
/* ================================================================== */
/* 场景 B:缓冲区只有 sizeof(fd_set),nfds 却 = fd+1 */
/* 内核 copy_from_user 越界读 -> EFAULT */
/* ================================================================== */
std::printf("-----------------------------------------------------------------------\n");
std::printf(" 场景 B:缓冲区只有 %zu 字节,nfds = fd+1 (fd >= 1024)\n",
sizeof(fd_set));
std::printf(" 用 mmap + guard page 让内核越界读立刻失败\n");
std::printf("-----------------------------------------------------------------------\n");
{
const long page = ::sysconf(_SC_PAGESIZE);
char *base = static_cast<char *>(
::mmap(nullptr, static_cast<size_t>(page) * 2,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
if (base == MAP_FAILED) { std::perror("mmap"); return 1; }
if (::mprotect(base + page, static_cast<size_t>(page), PROT_NONE) < 0) {
std::perror("mprotect"); return 1;
}
/* 把标准 fd_set 紧贴在 guard page 之前,保证越界读立刻 EFAULT */
fd_set *rfds = reinterpret_cast<fd_set *>(base + page - sizeof(fd_set));
std::memset(rfds, 0, sizeof(fd_set));
for (int i = 0; i < N; ++i) {
if (fds[i].rfd < 0) continue;
const int fd = fds[i].rfd;
if (fd < FD_SETSIZE) continue; /* 只测 fd >= 1024 */
tv.tv_sec = 0; tv.tv_usec = 0;
errno = 0;
const int ret = ::select(fd + 1, rfds, nullptr, nullptr, &tv);
const int e = errno;
char desc[128];
std::snprintf(desc, sizeof(desc),
"select(nfds=%d) with only %zuB buffer at page end",
fd + 1, sizeof(fd_set));
line(desc, ret, e);
}
::munmap(base, static_cast<size_t>(page) * 2);
}
std::printf("\n");
/* ================================================================== */
/* 场景 C:fd_set 里放了一个已经关闭的 fd -> EBADF */
/* ================================================================== */
std::printf("-----------------------------------------------------------------------\n");
std::printf(" 场景 C:fd_set 里放了一个已关闭的 fd\n");
std::printf("-----------------------------------------------------------------------\n");
{
int pfd[2];
if (::pipe(pfd) < 0) { std::perror("pipe"); return 1; }
int closed_fd = pfd[0];
::close(pfd[0]);
::close(pfd[1]);
fd_set *rfds = fdset_alloc(closed_fd);
FD_SET(closed_fd, rfds);
tv.tv_sec = 0; tv.tv_usec = 0;
errno = 0;
const int ret = ::select(closed_fd + 1, rfds, nullptr, nullptr, &tv);
const int e = errno;
char desc[128];
std::snprintf(desc, sizeof(desc),
"select(nfds=%d) with closed fd=%d in set",
closed_fd + 1, closed_fd);
line(desc, ret, e);
std::free(rfds);
}
std::printf("\n");
/* ================================================================== */
/* 场景 D:nfds 非法 */
/* ================================================================== */
std::printf("-----------------------------------------------------------------------\n");
std::printf(" 场景 D:nfds 非法\n");
std::printf("-----------------------------------------------------------------------\n");
{
tv.tv_sec = 0; tv.tv_usec = 0;
errno = 0;
int ret = ::select(-1, nullptr, nullptr, nullptr, &tv);
line("select(nfds=-1, NULL, NULL, NULL, {0,0})", ret, errno);
errno = 0;
ret = ::select(0, nullptr, nullptr, nullptr, &tv);
line("select(nfds=0, NULL, NULL, NULL, {0,0})", ret, errno);
}
std::printf("\n");
/* ================================================================== */
/* 场景 E:nfds 合法,但所有位为 0 -> 超时返回 0 */
/* ================================================================== */
std::printf("-----------------------------------------------------------------------\n");
std::printf(" 场景 E:所有 fd_set 位为 0,nfds 取 1024 / 1025 / 1026\n");
std::printf("-----------------------------------------------------------------------\n");
{
const int nfds_list[] = { 1024, 1025, 1026 };
for (int nfds : nfds_list) {
fd_set *rfds = fdset_alloc(nfds - 1);
tv.tv_sec = 0; tv.tv_usec = 0;
errno = 0;
const int ret = ::select(nfds, rfds, nullptr, nullptr, &tv);
const int e = errno;
char desc[128];
std::snprintf(desc, sizeof(desc),
"select(nfds=%d, all-zero, {0,0})", nfds);
line(desc, ret, e);
std::free(rfds);
}
}
std::printf("\n");
/* ================================================================== */
/* 场景 F:nfds=1026,fd_set 里只有 fd=1025 就绪,超时 500ms */
/* ================================================================== */
std::printf("-----------------------------------------------------------------------\n");
std::printf(" 场景 F:nfds=1026,fd_set 里只有 fd=1025 就绪,超时 500ms\n");
std::printf("-----------------------------------------------------------------------\n");
{
if (fds[3].rfd >= 0) {
const int fd = fds[3].rfd; /* 1025 */
fd_set *rfds = fdset_alloc(fd);
FD_SET(fd, rfds);
tv.tv_sec = 0; tv.tv_usec = 500000;
errno = 0;
const int ret = ::select(fd + 1, rfds, nullptr, nullptr, &tv);
const int e = errno;
char desc[128];
std::snprintf(desc, sizeof(desc),
"select(nfds=%d, fd=%d ready, 500ms)", fd + 1, fd);
line(desc, ret, e);
std::free(rfds);
}
}
std::printf("\n");
/* ---------- 收尾 ---------- */
for (int i = 0; i < N; ++i) {
if (fds[i].rfd >= 0) ::close(fds[i].rfd);
if (fds[i].wfd >= 0) ::close(fds[i].wfd);
}
std::printf("=======================================================================\n");
std::printf(" 结论\n");
std::printf("-----------------------------------------------------------------------\n");
std::printf(" A) 缓冲区够大、nfds = maxfd+1 时,fd = 1024 / 1025 完全正常:\n");
std::printf(" ret = 就绪 fd 数,errno = 0。不会返回 EINVAL 或 EBADF。\n");
std::printf("\n");
std::printf(" B) 缓冲区只有标准 128 字节但 nfds > 1024 时,内核拷贝 fd_set\n");
std::printf(" 越界读,若缓冲区紧邻 guard page -> ret = -1, errno = EFAULT(14)。\n");
std::printf("\n");
std::printf(" C) fd_set 中含已关闭的 fd -> ret = -1, errno = EBADF(9)。\n");
std::printf("\n");
std::printf(" D) nfds < 0 -> ret = -1, errno = EINVAL(22)。\n");
std::printf(" nfds = 0 且所有位为 0 -> ret = 0。\n");
std::printf("\n");
std::printf(" E) 所有 fd_set 位为 0 时,nfds 即使传 1024/1025/1026,\n");
std::printf(" 只要缓冲区够大,select 直接按 timeout 超时返回 0,errno = 0。\n");
std::printf("\n");
std::printf(" F) 标准 fd_set 下 FD_SET(1024, &set) / FD_SET(1025, &set)\n");
std::printf(" 是用户态越界写,属于未定义行为,不会产生 errno。\n");
std::printf("=======================================================================\n");
return 0;
}