POSIX Threads (pthread) 完全指南:从入门到精通(2025 最新版)

POSIX Threads (pthread) 完全指南:从入门到精通(2025 最新版)

📌 本文目录

	什么是 POSIX Threads(pthread)?
	为什么学习 pthread?
	核心 pthread 函数速查表(带示例)
	高级功能:TLS、取消、一次初始化
	实战:完整 pthread 示例(生产者-消费者模型)
	官方权威资源推荐(2025 更新)
	常见错误与最佳实践
	常见问题(FAQ)
	总结

🌟 什么是 POSIX Threads(pthread)?
POSIX Threads(简称 pthread) 是一套遵循 POSIX 标准的多线程编程接口,广泛用于 Linux、Unix、macOS 等系统。它允许开发者在单个进程中创建多个并发执行流(线程),共享内存空间,提升程序性能与响应能力。

📚 为什么学习 pthread?

	✅ 工业界标准:几乎所有 Unix-like 系统都支持
	✅ 高性能:轻量级线程,低开销
	✅ 控制力强:手动管理线程生命周期、同步机制
	✅ 学习基础:掌握 pthread 是深入系统编程、高性能服务器开发、嵌入式开发的前提

🧩 核心 pthread 函数速查表(带示例)
本节结构化设计,便于 Google Featured Snippet 抓取(“速查表”、“示例”是高点击率关键词)

  1. 创建线程 — pthread_create

#include

int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(start_routine)(void),
void *arg);
✅ 用途:启动一个新线程
✅ 返回值:0 表示成功,非 0 为错误码(非 errno)
✅ 编译选项:gcc -pthread your_program.c -o your_program
📌 简单示例:
#include
#include

void* say_hello(void* arg) {
printf(“Hello from thread!\n”);
return NULL;
}

int main() {
pthread_t tid;
pthread_create(&tid, NULL, say_hello, NULL);
pthread_join(tid, NULL); // 等待线程结束
return 0;
}
2. 等待线程结束 — pthread_join
int pthread_join(pthread_t thread, void **retval);
📌 用于阻塞主线程,直到目标线程执行完毕,并可获取其返回值。
3. 分离线程 — pthread_detach
int pthread_detach(pthread_t thread);
📌 线程结束后自动回收资源,无需 join。适用于“发射后不管”的后台线程。
4. 互斥锁(Mutex)— 保护共享资源
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_lock(&mutex);
// 临界区代码
pthread_mutex_unlock(&mutex);
✅ 避免数据竞争(Data Race)的最常用同步机制
5. 条件变量(Condition Variable)— 线程间通信
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// 等待条件
pthread_mutex_lock(&mutex);
while (condition == false)
pthread_cond_wait(&cond, &mutex);
// 条件满足,执行操作
pthread_mutex_unlock(&mutex);

// 通知其他线程
pthread_cond_signal(&cond); // 唤醒一个
pthread_cond_broadcast(&cond); // 唤醒所有
📌 常用于“生产者-消费者”模型
6. 读写锁(Read-Write Lock)— 读多写少场景
pthread_rwlock_rdlock(&rwlock); // 多个线程可同时读
pthread_rwlock_wrlock(&rwlock); // 只有一个线程能写
pthread_rwlock_unlock(&rwlock);
✅ 适用于缓存、配置文件、数据库索引等场景

🛠️ 高级功能:TLS、取消、一次初始化

功能
函数
用途说明

线程局部存储
pthread_key_create
每个线程独立变量副本

线程取消
pthread_cancel
请求终止目标线程

一次初始化
pthread_once
确保初始化代码只执行一次

🧪 实战:完整 pthread 示例(生产者-消费者模型)
#include
#include
#include
#include

#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int count = 0;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t not_full = PTHREAD_COND_INITIALIZER;
pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER;

void* producer(void* arg) {
for (int i = 0; i < 10; i++) {
pthread_mutex_lock(&mutex);
while (count == BUFFER_SIZE)
pthread_cond_wait(&not_full, &mutex);

    buffer[count++] = i;
    printf("Produced: %d\n", i);
    pthread_cond_signal(&not_empty);
    pthread_mutex_unlock(&mutex);
    sleep(1);
}
return NULL;

}

void* consumer(void* arg) {
for (int i = 0; i < 10; i++) {
pthread_mutex_lock(&mutex);
while (count == 0)
pthread_cond_wait(&not_empty, &mutex);

    int item = buffer[--count];
    printf("Consumed: %d\n", item);
    pthread_cond_signal(&not_full);
    pthread_mutex_unlock(&mutex);
    sleep(2);
}
return NULL;

}

int main() {
pthread_t p, c;
pthread_create(&p, NULL, producer, NULL);
pthread_create(&c, NULL, consumer, NULL);
pthread_join(p, NULL);
pthread_join(c, NULL);
return 0;
}
✅ 此示例适合 Google 代码片段收录,提高“pthread 示例”类搜索排名

📚 官方权威资源推荐(2025 更新)

  1. 《UNIX环境高级编程》(APUE)第3版
    官网 👉 https://www.apuebook.com
    获取源码、勘误、章节更新
    作者:W. Richard Stevens & Stephen A. Rago
  2. POSIX 标准官方文档(免费在线版)
    👉 The Open Group Base Specifications Issue 7
    包含所有 pthread 函数标准定义
    支持搜索、交叉引用,开发者必备
  3. Linux Man Pages(函数手册)
    👉 https://man7.org/linux/man-pages/
    搜索 pthread_create、pthread_mutex_lock 等获取详细说明

❗ 常见错误与最佳实践
✅ 必须遵守:

	所有线程必须 join 或 detach,否则资源泄漏
	互斥锁必须成对使用(lock/unlock),避免死锁
	条件变量必须在 while 循环中等待(防止虚假唤醒)
	使用 -pthread 编译选项,而非 -lpthread

⚠️ 避免:

	在信号处理函数中调用 pthread 函数(不安全)
	多线程中使用非线程安全函数(如 strtok, rand)
	忽略 pthread 函数返回值(错误码非 errno)

❓ 常见问题(FAQ)

Q:pthread 和 std::thread 有什么区别?
A:pthread 是 C 语言 POSIX 标准接口,跨 Unix 系统;std::thread 是 C++11 标准,封装了平台线程(底层可能调用 pthread)。C++ 更易用,C 更底层可控。

Q:pthread_create 返回错误怎么办?
A:检查返回值(非 errno),用 strerror(ret) 打印错误信息。常见错误:EAGAIN(资源不足)、EINVAL(参数无效)。

Q:如何调试 pthread 死锁?
A:使用 valgrind –tool=helgrind 或 GCC 的 -fsanitize=thread(ThreadSanitizer)检测数据竞争与死锁。

✅ 总结
无论你是准备面试、开发高性能服务器、还是学习系统编程,掌握 pthread 是必备技能。本文从基础函数、同步机制、实战示例到官方资源,一站式解决你的学习需求。

📌 收藏本文,随时查阅 pthread 速查表与示例代码!

https://www.calcguide.tech/2025/09/08/guide-to-pthreads-posix-threads-programming/

© 2025 calcguide.tech. 本文可自由分享,转载请注明出处。

Prism.highlightAll();

–>

getdents64系统调用及示例

getdents64 函数详解

  1. 函数介绍

getdents64 是 Linux 系统中用于读取目录内容的底层系统调用。可以把这个函数想象成一个”目录内容扫描仪”——它能够高效地扫描目录中的所有文件和子目录,就像超市的扫描枪快速读取商品条码一样。

与高级的目录操作函数(如 readdir)不同,getdents64 是最底层的接口,直接与内核交互,提供了最大的灵活性和性能。它返回的是原始的目录项数据,包含文件名、inode 号、文件类型等信息。

  1. 函数原型
1
2
3
4
5
#include <dirent.h>     /* 或者 <unistd.h> */
#include <sys/syscall.h>

int getdents64(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count);

  1. 功能

getdents64 函数用于从已打开的目录文件描述符中读取目录项(directory entries)。它一次可以读取多个目录项,比逐个读取效率更高。

  1. 参数
  • fd: 已打开的目录文件描述符(通过 open() 或 opendir() 获得)

  • dirp: 指向缓冲区的指针,用于存储读取的目录项数据

  • count: 缓冲区的大小(以字节为单位)

  1. struct linux_dirent64 结构体
1
2
3
4
5
6
7
8
struct linux_dirent64 {
ino64_t d_ino; /* 64位 inode 号 */
off64_t d_off; /* 到下一个目录项的偏移 */
unsigned short d_reclen; /* 此目录项的长度 */
unsigned char d_type; /* 文件类型 */
char d_name&#91;]; /* 文件名(以 null 结尾) */
};

  1. 文件类型(d_type 字段)

类型值宏定义说明0DT_UNKNOWN未知类型1DT_FIFO命名管道2DT_CHR字符设备4DT_DIR目录6DT_BLK块设备8DT_REG普通文件10DT_LNK符号链接12DT_SOCK套接字

  1. 返回值
  • 成功: 返回实际读取的字节数(0 表示到达目录末尾)

  • 失败: 返回 -1,并设置相应的 errno 错误码

常见错误码:

  • EBADF: fd 不是有效的目录文件描述符

  • EFAULT: dirp 指针无效

  • EINVAL: 参数无效

  • ENOENT: 目录不存在

  1. 相似函数或关联函数
  • getdents: 旧版本的目录读取函数(32位 inode)

  • readdir: POSIX 标准的目录读取函数(更高级的接口)

  • opendir/fdopendir: 打开目录

  • closedir: 关闭目录

  • scandir: 扫描目录并排序

  • ls: 命令行目录列表工具

  1. 示例代码

示例1:基础用法 - 读取目录内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <dirent.h>
#include <string.h>

// 目录项结构体(64位版本)
struct linux_dirent64 {
ino64_t d_ino; /* Inode number */
off64_t d_off; /* Offset to next structure */
unsigned short d_reclen; /* Size of this structure */
unsigned char d_type; /* File type */
char d_name&#91;]; /* Filename (null-terminated) */
};

// 获取文件类型字符串
const char* get_file_type_string(unsigned char d_type) {
switch (d_type) {
case DT_REG: return "普通文件";
case DT_DIR: return "目录";
case DT_LNK: return "符号链接";
case DT_CHR: return "字符设备";
case DT_BLK: return "块设备";
case DT_FIFO: return "命名管道";
case DT_SOCK: return "套接字";
case DT_UNKNOWN:
default: return "未知";
}
}

// 获取文件类型字符
char get_file_type_char(unsigned char d_type) {
switch (d_type) {
case DT_REG: return 'f';
case DT_DIR: return 'd';
case DT_LNK: return 'l';
case DT_CHR: return 'c';
case DT_BLK: return 'b';
case DT_FIFO: return 'p';
case DT_SOCK: return 's';
case DT_UNKNOWN:
default: return '?';
}
}

int main(int argc, char *argv&#91;]) {
int fd;
char buf&#91;4096];
int nread;
char *dir_path;

// 获取目录路径参数
if (argc != 2) {
printf("用法: %s <目录路径>\n", argv&#91;0]);
dir_path = "."; // 默认当前目录
printf("使用当前目录: %s\n\n", dir_path);
} else {
dir_path = argv&#91;1];
}

// 打开目录
fd = open(dir_path, O_RDONLY | O_DIRECTORY);
if (fd == -1) {
perror("open");
return 1;
}

printf("=== 目录 '%s' 的内容 ===\n", dir_path);
printf("%-12s %-10s %-8s %s\n", "INODE", "类型", "大小", "名称");
printf("%-12s %-10s %-8s %s\n", "----", "----", "----", "----");

// 循环读取目录项
while (1) {
nread = syscall(SYS_getdents64, fd, buf, sizeof(buf));
if (nread == -1) {
perror("getdents64");
close(fd);
return 1;
}

if (nread == 0) {
break; // 到达目录末尾
}

// 解析目录项
for (int bpos = 0; bpos < nread;) {
struct linux_dirent64 *d;
d = (struct linux_dirent64 *)(buf + bpos);

// 显示目录项信息
printf("%-12llu %-10s %-8d %s\n",
(unsigned long long)d->d_ino,
get_file_type_string(d->d_type),
d->d_reclen,
d->d_name);

bpos += d->d_reclen;
}
}

close(fd);
return 0;
}

示例2:带过滤和统计的目录扫描器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>

struct linux_dirent64 {
ino64_t d_ino;
off64_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name&#91;];
};

// 统计信息结构体
struct dir_stats {
int total_files;
int directories;
int regular_files;
int symlinks;
int devices;
int fifos;
int sockets;
int unknown;
long long total_size;
};

// 更新统计信息
void update_stats(struct dir_stats *stats, unsigned char d_type) {
stats->total_files++;

switch (d_type) {
case DT_DIR:
stats->directories++;
break;
case DT_REG:
stats->regular_files++;
break;
case DT_LNK:
stats->symlinks++;
break;
case DT_CHR:
case DT_BLK:
stats->devices++;
break;
case DT_FIFO:
stats->fifos++;
break;
case DT_SOCK:
stats->sockets++;
break;
case DT_UNKNOWN:
default:
stats->unknown++;
break;
}
}

// 显示统计信息
void print_stats(const struct dir_stats *stats) {
printf("\n=== 统计信息 ===\n");
printf("总计文件数: %d\n", stats->total_files);
printf("目录数: %d\n", stats->directories);
printf("普通文件: %d\n", stats->regular_files);
printf("符号链接: %d\n", stats->symlinks);
printf("设备文件: %d\n", stats->devices);
printf("命名管道: %d\n", stats->fifos);
printf("套接字: %d\n", stats->sockets);
printf("未知类型: %d\n", stats->unknown);
}

// 过滤函数:只显示特定类型的文件
int filter_by_type(unsigned char d_type, unsigned char filter_type) {
if (filter_type == 0) return 1; // 不过滤
return d_type == filter_type;
}

int main(int argc, char *argv&#91;]) {
int fd;
char buf&#91;8192];
int nread;
char *dir_path;
unsigned char filter_type = 0; // 0 表示不过滤
struct dir_stats stats = {0};

// 解析命令行参数
if (argc < 2) {
printf("用法: %s <目录路径> &#91;过滤类型]\n", argv&#91;0]);
printf("过滤类型: d(目录), f(文件), l(链接), 其他类型字符\n");
dir_path = ".";
} else {
dir_path = argv&#91;1];
}

// 设置过滤类型
if (argc > 2) {
switch (argv&#91;2]&#91;0]) {
case 'd': filter_type = DT_DIR; break;
case 'f': filter_type = DT_REG; break;
case 'l': filter_type = DT_LNK; break;
case 'c': filter_type = DT_CHR; break;
case 'b': filter_type = DT_BLK; break;
case 'p': filter_type = DT_FIFO; break;
case 's': filter_type = DT_SOCK; break;
}
if (filter_type != 0) {
printf("过滤类型: %c\n", argv&#91;2]&#91;0]);
}
}

// 打开目录
fd = open(dir_path, O_RDONLY | O_DIRECTORY);
if (fd == -1) {
perror("open");
return 1;
}

printf("=== 目录 '%s' 的内容 ===\n", dir_path);
if (filter_type != 0) {
printf("(已过滤)\n");
}
printf("%c %-20s %12s %10s\n",
'T', "名称", "INODE", "大小(字节)");
printf("%c %-20s %12s %10s\n",
'-', "----", "-----", "----------");

// 读取目录项
while (1) {
nread = syscall(SYS_getdents64, fd, buf, sizeof(buf));
if (nread == -1) {
perror("getdents64");
close(fd);
return 1;
}

if (nread == 0) {
break; // 到达目录末尾
}

// 解析目录项
for (int bpos = 0; bpos < nread;) {
struct linux_dirent64 *d;
d = (struct linux_dirent64 *)(buf + bpos);

// 应用过滤器
if (filter_by_type(d->d_type, filter_type)) {
// 获取文件大小(对于普通文件)
long long file_size = 0;
if (d->d_type == DT_REG) {
char full_path&#91;1024];
struct stat st;
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, d->d_name);
if (stat(full_path, &st) == 0) {
file_size = st.st_size;
stats.total_size += file_size;
}
}

printf("%c %-20s %12llu %10lld\n",
get_file_type_char(d->d_type),
d->d_name,
(unsigned long long)d->d_ino,
file_size);
}

// 更新统计信息
update_stats(&stats, d->d_type);

bpos += d->d_reclen;
}
}

close(fd);

// 显示统计信息
print_stats(&stats);

return 0;
}

示例3:递归目录遍历工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>

struct linux_dirent64 {
ino64_t d_ino;
off64_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name&#91;];
};

// 全局统计变量
int total_files = 0;
int total_dirs = 0;
long long total_size = 0;
int max_depth = 0;

// 递归遍历目录
void traverse_directory(const char *path, int current_depth, int max_show_depth) {
int fd;
char buf&#91;8192];
int nread;

// 更新最大深度
if (current_depth > max_depth) {
max_depth = current_depth;
}

// 打开目录
fd = open(path, O_RDONLY | O_DIRECTORY);
if (fd == -1) {
printf("无法打开目录: %s\n", path);
return;
}

// 显示当前目录(如果在显示范围内)
if (current_depth <= max_show_depth) {
for (int i = 0; i < current_depth; i++) {
printf(" ");
}
printf("&#91;%s]\n", path);
}

// 读取目录项
while (1) {
nread = syscall(SYS_getdents64, fd, buf, sizeof(buf));
if (nread == -1) {
printf("读取目录失败: %s\n", path);
close(fd);
return;
}

if (nread == 0) {
break; // 到达目录末尾
}

// 解析目录项
for (int bpos = 0; bpos < nread;) {
struct linux_dirent64 *d;
d = (struct linux_dirent64 *)(buf + bpos);

// 跳过 . 和 ..
if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) {
bpos += d->d_reclen;
continue;
}

// 构造完整路径
char full_path&#91;1024];
snprintf(full_path, sizeof(full_path), "%s/%s", path, d->d_name);

if (d->d_type == DT_DIR) {
total_dirs++;
// 显示目录(如果在显示范围内)
if (current_depth < max_show_depth) {
for (int i = 0; i <= current_depth; i++) {
printf(" ");
}
printf("├── %s/\n", d->d_name);
} else if (current_depth == max_show_depth) {
for (int i = 0; i <= current_depth; i++) {
printf(" ");
}
printf("├── %s/ (子目录未展开)\n", d->d_name);
}

// 递归遍历子目录
traverse_directory(full_path, current_depth + 1, max_show_depth);

} else if (d->d_type == DT_REG) {
total_files++;
// 获取文件大小
struct stat st;
if (stat(full_path, &st) == 0) {
total_size += st.st_size;
}

// 显示文件(如果在显示范围内)
if (current_depth < max_show_depth) {
for (int i = 0; i <= current_depth; i++) {
printf(" ");
}
printf("├── %s (%lld bytes)\n", d->d_name,
(long long)(stat(full_path, &st) == 0 ? st.st_size : 0));
}
} else {
total_files++;
// 显示其他类型的文件
if (current_depth < max_show_depth) {
for (int i = 0; i <= current_depth; i++) {
printf(" ");
}
printf("├── %s &#91;%c]\n", d->d_name, get_file_type_char(d->d_type));
}
}

bpos += d->d_reclen;
}
}

close(fd);
}

// 获取文件类型字符
char get_file_type_char(unsigned char d_type) {
switch (d_type) {
case DT_REG: return 'f';
case DT_DIR: return 'd';
case DT_LNK: return 'l';
case DT_CHR: return 'c';
case DT_BLK: return 'b';
case DT_FIFO: return 'p';
case DT_SOCK: return 's';
default: return '?';
}
}

int main(int argc, char *argv&#91;]) {
char *start_path;
int show_depth = 3; // 默认显示3层

// 解析命令行参数
if (argc < 2) {
printf("用法: %s <起始路径> &#91;显示深度]\n", argv&#91;0]);
start_path = ".";
} else {
start_path = argv&#91;1];
}

if (argc > 2) {
show_depth = atoi(argv&#91;2]);
if (show_depth < 1) show_depth = 1;
}

printf("=== 递归目录遍历工具 ===\n");
printf("起始路径: %s\n", start_path);
printf("显示深度: %d\n\n", show_depth);

// 开始遍历
traverse_directory(start_path, 0, show_depth);

// 显示统计信息
printf("\n=== 遍历统计 ===\n");
printf("总目录数: %d\n", total_dirs);
printf("总文件数: %d\n", total_files);
printf("总大小: %.2f MB (%lld bytes)\n",
total_size / (1024.0 * 1024.0), total_size);
printf("最大深度: %d\n", max_depth);

return 0;
}

编译和运行说明

1
2
3
4
5
6
7
8
9
10
11
12
13
# 编译示例程序
gcc -o getdents64_example1 example1.c
gcc -o getdents64_example2 example2.c
gcc -o getdents64_example3 example3.c

# 运行示例
./getdents64_example1
./getdents64_example1 /etc
./getdents64_example2 /home
./getdents64_example2 /usr/bin f # 只显示普通文件
./getdents64_example2 /dev d # 只显示目录
./getdents64_example3 /usr 2 # 显示2层目录结构

重要注意事项

缓冲区大小: 建议使用较大的缓冲区(如 4KB-8KB)以提高效率

目录文件描述符: 必须使用 O_DIRECTORY 标志打开目录

错误处理: 始终检查返回值和 errno

字符串处理: 目录项名称是 null 结尾的,但结构体大小可能包含填充

性能: getdents64 比 readdir 更高效,因为它一次返回多个目录项

移植性: 这是 Linux 特有的系统调用

与 readdir 的比较

特性getdents64readdir级别系统调用标准库函数效率高(批量读取)较低(逐个读取)使用复杂度复杂简单可移植性Linux 特有跨平台功能原始数据解析后的结构体

实际应用场景

文件管理器: 快速显示目录内容

备份工具: 扫描需要备份的文件

搜索工具: 快速遍历文件系统

系统监控: 监控目录变化

磁盘分析: 统计磁盘使用情况

安全扫描: 检查可疑文件

这些示例展示了 getdents64 函数的各种使用方法,从基本的目录读取到复杂的递归遍历,帮助你掌握这个高效的目录操作接口。

getdents64 是 Linux 系统底层目录扫描函数,用于高效读取目录内容。它直接与内核交互,返回包含文件名、inode号、文件类型等信息的原始目录项数据。相比高级接口如 readdir,getdents64 提供了更高的灵活性和性能。使用时需提供目录文件描述符、缓冲区和大小参数,返回实际读取字节数或错误码。该函数常用于需要精细控制目录扫描的场景,如文件系统工具开发。示例代码展示了如何用 getdents64 实现目录内容扫描和文件类型统计功能。

https://www.calcguide.tech/2025/09/08/getdents64系统调用及示例/

LibGen Mirror 2025 最新可用镜像站点大全

✅ LibGen Mirror 2025 最新可用镜像站点大全 | 高速访问 + 替代方案

📌 更新时间:2025年4月 — 实测可用 | 安全访问指南 | 学术资源免费下载

🔍 什么是 LibGen Mirror?

Library Genesis(简称 LibGen) 是全球最大的免费学术资源库之一,提供数百万本电子书、期刊论文、漫画和小说。由于官方域名常被封锁或关停,全球志愿者维护多个 LibGen Mirror(镜像站点),确保用户持续访问。

本页面提供 2025年最新、最稳定、可高速访问的 LibGen 镜像列表,并附替代方案、使用技巧与安全提示。

🚀 2025 年最新可用 LibGen Mirror 镜像站点(实测推荐)

⚠️ 镜像站点可能随时变动,建议收藏本页或保存多个备用地址。

排名镜像地址状态备注🥇 1http://libgen.is✅ 在线稳定首选推荐,界面友好,搜索强大🥈 2http://libgen.rs✅ 在线稳定老牌镜像,数据库完整🥉 3http://libgen.st✅ 在线速度快,新兴稳定站点4http://libgen.li⚠️ 间歇可用偶尔宕机,可作备用5http://gen.lib.rus.ec❌ 经常被墙老用户熟悉,但近年不稳定

🔄 如何查找最新 LibGen 镜像?官方状态监测平台

由于镜像站点常变动,推荐使用以下工具实时监测:

  • 🔗 https://libgen.fun — 实时显示各镜像在线状态、响应速度、数据库更新时间

  • 🔗 https://libgen.gs — 自动跳转可用镜像(部分国家受限)

  • 🔗 GitHub 社区 — 搜索 “libgen mirrors 2025” 获取开发者维护列表(例:github.com/iiiic/libgen-mirrors)

🧭 LibGen 使用技巧(提升搜索 & 下载效率)

🔎 搜索优化

  • 使用 ISBN、DOI、书名 + 作者 精准定位

  • 支持多语言搜索(英文、中文、俄文等)

  • 利用高级筛选:按年份、扩展名(PDF, EPUB, MOBI)、文件大小过滤

⚡ 下载加速

  • 更换镜像站点提升速度

  • 使用下载工具:IDM、Aria2、Free Download Manager

  • 避开高峰时段(欧美时间夜间下载更快)

🛡️ 隐私与安全

  • 使用 VPN 或 Tor 浏览器 访问被封锁镜像

  • 避免在镜像站登录账户或填写个人信息

  • 安装广告拦截插件(如 uBlock Origin)避免恶意弹窗

🆕 LibGen + Sci-Hub 联合使用(论文党必备)

LibGen 与 Sci-Hub 数据互通,可交叉使用:

服务用途推荐地址Sci-Hub输入 DOI 下载期刊论文https://sci-hub.sehttps://sci-hub.ruLibGen Books下载教材、小说、学术专著http://libgen.isLibGen Sci-Tech工程、计算机、医学类文献镜像站内选择 “Scientific articles” 分类

⚖️ 法律与道德声明

LibGen 提供的资源多为 版权受限内容,在部分国家/地区访问可能违反当地法律。我们建议:

  • 仅用于 个人学习、科研、教育用途

  • 有能力者请支持正版,通过图书馆或机构订阅获取资源

  • 不得用于商业分发或盈利行为

📌 温馨提示:部分大学/研究机构已订阅 Springer、Elsevier、IEEE 等数据库,优先使用合法渠道。

🆘 LibGen 无法访问?试试这些替代方案

资源类型替代网站特点📚 电子书Z-Library 镜像(需找最新入口)PDF DriveOpen Library免费 PDF 书库,Z-Lib 功能类似 LibGen🎓 学术论文UnpaywallCORESemantic Scholar合法开放获取论文平台🇨🇳 中文资源书格(古籍)苦瓜书盘鸠摩搜书中文电子书、绝版书搜索

❓ 常见问题 FAQ

Q1: LibGen 镜像为什么经常打不开?

→ 因版权压力,域名常被查封或DNS污染。建议使用 VPN 或更换镜像。

Q2: 下载的书打不开?

→ 检查文件扩展名(如 .pdf, .epub),使用对应阅读器(Adobe Reader, Calibre)。

Q3: 是否有手机App?

→ 无官方 App,但可通过手机浏览器访问镜像站,或使用 PWA 添加到主屏幕。

Q4: 如何捐赠支持 LibGen?

→ LibGen 为志愿者运营,无官方捐赠渠道。可通过传播、维护镜像、提交缺失书籍等方式支持。

📌 总结:2025 最佳 LibGen Mirror 推荐

✅ 首选访问:http://libgen.is✅ 备选方案:http://libgen.rs | http://libgen.st✅ 状态监测:https://libgen.fun✅ 论文下载:https://sci-hub.se

💬 需要帮助?留言或私信!

如果你找不到某本书或论文,欢迎提供:

  • 书名 + 作者

  • ISBN 或 DOI 号

  • 出版年份或出版社

我们将为你定位最新可下载链接!

🔖 收藏本页,随时获取 LibGen 最新镜像更新!

✅ SEO 优化说明(供内容发布者参考)

  • 标题优化:包含核心关键词 “LibGen Mirror 2025”、“最新镜像”、“高速访问”

  • 结构化数据:使用 H2/H3 标题、表格、列表提升可读性与语义结构

  • 关键词密度:自然分布 “LibGen mirror”、“Library Genesis”、“免费电子书”、“学术资源”、“Sci-Hub” 等长尾词

  • 内部链接建议:可链接至“Sci-Hub使用指南”、“Z-Library镜像”、“PDF电子书下载”等相关页面

  • 外链权威性:链接至 GitHub、官方状态页、替代平台,提升 E-E-A-T

  • 移动端适配:段落简短、按钮式推荐、响应式表格

📌 最后更新:2025年7月5日🌐 本页将持续更新,确保提供最新、最安全、最稳定的 LibGen 镜像访问方案。

✅ 立即访问推荐镜像 → http://libgen.is

本文内容仅供教育与科研参考,请遵守所在国家/地区法律法规。合理使用,支持正版。

Linux Developer’s Ultimate Resource Hub: Global Mirrors & Essential Handbooks (2025)

Linux Developer’s Ultimate Resource Hub: Global Mirrors & Essential Handbooks (2025)

📌 Table of Contents

Why This Guide?
Global Linux Distribution Mirrors
Core Developer Handbooks & Standards
Global Online Digital Libraries
Frequently Asked Questions (FAQ)
Summary & Bookmark Suggestion

🚀 Why This Guide?
As a Linux developer, sysadmin, or security researcher, you’ve likely faced these frustrations:

🐌 Downloading Ubuntu ISOs at 50KB/s from the main server?
❓ Struggling to find the authoritative definition of pthread_mutex_lock?
📚 Wanting to study Advanced Programming in the UNIX Environment but unsure where to start?

This guide is your one-stop solution. We’ve curated the fastest global mirrors for major Linux distros and the most authoritative handbooks for system programming, C/C++ standards, and POSIX compliance.
🌍 Global Linux Distribution Mirrors (High-Speed Downloads)
💡 Pro Tip: Always choose a mirror geographically closest to you for 3-10x faster downloads.
🐧 Linux Kernel Official & Mirrors

Main Site: https://www.kernel.org/
Recommended Mirrors:

🇨🇳 China (Aliyun): https://mirrors.aliyun.com/kernel/
🇺🇸 USA (Edge): https://mirrors.edge.kernel.org/
🇯🇵 Japan: https://ftp.jaist.ac.jp/pub/Linux/kernel/

🖥️ Ubuntu Official & Mirrors

Main Site: https://ubuntu.com/download
Official Mirror List: https://launchpad.net/ubuntu/+cdmirrors
Top Speed Mirrors:

🇨🇳 Tsinghua University: https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/
🇨🇳 Aliyun: https://mirrors.aliyun.com/ubuntu-releases/
🇺🇸 MIT: http://mirrors.mit.edu/ubuntu-cd/

🔧 APT Source Tip: Replace archive.ubuntu.com with mirrors.tuna.tsinghua.edu.cn in /etc/apt/sources.list for blazing-fast apt update.

⚔️ Kali Linux Official & Mirrors (Penetration Testing)

Main Site: https://www.kali.org/get-kali/
Official Mirrors: https://http.kali.org/README.mirrorlist
Recommended Mirrors:

🇨🇳 USTC: http://mirrors.ustc.edu.cn/kali-images/
🇨🇳 Aliyun: https://mirrors.aliyun.com/kali-images/
🇫🇷 France (Official): https://cdimage.kali.org/

💼 Red Hat Enterprise Linux (RHEL) & Alternatives

RHEL Developer Subscription (Free): https://developers.redhat.com/rhel
Download Portal (Login Required): https://access.redhat.com/downloads
100% Compatible Free Alternatives:

Rocky Linux: https://rockylinux.org/downloadMirror List: https://mirrors.rockylinux.org/mirrormanager/
AlmaLinux: https://almalinux.org/download/Repo: https://repo.almalinux.org/almalinux/

📚 Core Developer Handbooks & Standards
📘 POSIX Standard — The Source of Truth

Official Free Online (The Open Group):https://pubs.opengroup.org/onlinepubs/9699919799/✅ Searchable, bookmarkable, cross-referenced. Covers fork(), pthread_*, select(), etc.
IEEE Official Purchase (For Researchers):https://standards.ieee.org/standard/1003_1-2017.html

🧭 Linux man pages — Instant Command & Function Lookup

Online (Maintained by Michael Kerrisk):https://man7.org/linux/man-pages/✅ Better than terminal man command. Perfect for sharing links.

🖋️ C Language Coding Standard (CMU / Industry Best Practice)

Official Guide:https://users.ece.cmu.edu/~eno/coding/CCodingStandard.html

Key Highlights:
// Include units in variable names
uint32_t timeout_msecs;
uint32_t weight_lbs;

// Enums: ALL_CAPS with underscores
enum PinStateType {
PIN_OFF,
PIN_ON
};

// Macros: Always parenthesize
#define MAX(a,b) ((a) > (b) ? (a) : (b))

// Always use braces for if/while
if (condition) {
do_something();
}

// Initialize all variables
int error = 0;
char* name = NULL;

⌨️ C++ Coding Standard (CMU / Industry Best Practice)

Official Guide:https://users.ece.cmu.edu/~eno/coding/CppCodingStandard.html

Key Highlights:
// Class attributes: prefix with ‘a’
class MyClass {
private:
int aErrorNumber;
string* apName; // pointer
};

// Method names: Verbs
void HandleError();
int CalculateResult();

// Use namespaces
namespace MyProject {
class Utility { … };
}

// Avoid Get/Set; use same name for accessor/mutator
class Person {
public:
int Age() const { return aAge; }
void Age(int age) { aAge = age; }
private:
int aAge;
};

📖 Advanced Programming in the UNIX Environment (APUE)

Official Website (by Author Stephen A. Rago):https://www.apuebook.com/

✅ Resources Provided:

📥 Download all example source code
📝 Official errata (fixes for known errors)
📘 Chapter summaries and updates
🛒 Links to purchase the latest (3rd) edition

🌟 Why APUE? Universally regarded as the “Bible” of UNIX/Linux system programming. Covers processes, threads, signals, I/O, and more. Essential for interviews and deep system understanding.

📖 Global Online Digital Libraries (For Self-Study)

Name
Link
Description

O’Reilly Learning
https://learning.oreilly.com/
Premium subscription. Includes APUE, TCP/IP Illustrated, and thousands of tech books.

Project Gutenberg
https://www.gutenberg.org/
60,000+ free public domain eBooks (mostly classic literature).

Internet Archive
https://archive.org/
Millions of free books, manuals, software, and historical snapshots.

SpringerLink
https://link.springer.com/
Academic papers and textbooks. Some content is free.

⚠️ Reminder: Always respect copyright. Use resources for personal study and research.
❓ Frequently Asked Questions (FAQ)

Q: What’s the difference between POSIX and Linux man pages?A: POSIX is the international standard (what “should” happen). Linux man pages document the actual implementation on your system. Start with man pages for quick reference, consult POSIX for deep, portable understanding.

Q: Are the CMU C/C++ coding standards mandatory?A: Not mandatory, but highly recommended for team projects. They reduce bugs, improve readability, and make code reviews smoother. They’re battle-tested in industry.

Q: Is there a free PDF of APUE?A: ❌ No, there is no legal free PDF. Please support the author by purchasing a copy. The official website offers free source code and errata, which are incredibly valuable.

Q: Which RHEL alternative should I choose: Rocky Linux or AlmaLinux?A: Both are excellent and 100% binary compatible with RHEL. Choose based on community and update philosophy. Rocky is community-driven, Alma has a foundation backing. You can’t go wrong with either.

✅ Summary & Bookmark Suggestion
This guide is your centralized command center for Linux development resources — from lightning-fast downloads to authoritative programming standards.
Recommended Actions:

🔖 Bookmark this page — your future self will thank you.
⚡ Configure your APT/YUM sources to use a local mirror.
📚 Study APUE and POSIX to master system-level programming.
🧩 Adopt the CMU coding standards in your next team project.

📌 Pro Tip: Keep this page open in a browser tab while you work. It’s the ultimate productivity booster for Linux developers.
https://www.calcguide.tech/2025/09/08/linux-developer-resource-hub-mirrors-handbooks/

© 2025  calcguide.tech.   Share freely with attribution.

Linux 开发者终极资源导航:全球镜像站 + 核心开发手册(2025 国际中文版)

Linux 开发者终极资源导航:全球镜像站 + 核心开发手册(2025 国际中文版)

📌 目录导航

为什么你需要这份指南?
Linux 发行版全球高速镜像站
核心开发手册与工业标准
全球知名在线数字图书馆
常见问题(FAQ)
总结与收藏建议

🚀 为什么你需要这份指南?
作为 Linux 开发者、系统管理员或安全研究员,你是否经常遇到:

🐌 从官方源下载 Ubuntu ISO 速度慢如蜗牛?
❓ 想查找 pthread_create 的权威定义却无从下手?
📚 想系统学习《UNIX 环境高级编程》但不知从何开始?

本文为你一站式解决! 精选全球最快镜像站点 + 最权威开发手册,涵盖系统、网络、并发、编码规范,助你高效开发、轻松面试、深度进阶。

🌍 Linux 发行版全球高速镜像站
💡 小贴士:选择地理位置最近的镜像,下载速度提升 3~10 倍!

🐧 Linux Kernel 官方与镜像

官网:https://www.kernel.org/
推荐高速镜像:

  🇨🇳 中国(阿里云):https://mirrors.aliyun.com/kernel/
  🇺🇸 美国(Edge):https://mirrors.edge.kernel.org/
  🇯🇵 日本:https://ftp.jaist.ac.jp/pub/Linux/kernel/

🖥️ Ubuntu 官方与镜像

官网:https://ubuntu.com/download
官方镜像列表:https://launchpad.net/ubuntu/+cdmirrors
推荐高速镜像:

  🇨🇳 清华大学:https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/
  🇨🇳 阿里云:https://mirrors.aliyun.com/ubuntu-releases/
  🇺🇸 MIT:http://mirrors.mit.edu/ubuntu-cd/

🔧 APT 源优化:编辑 /etc/apt/sources.list,将 archive.ubuntu.com 替换为 mirrors.tuna.tsinghua.edu.cn,然后运行 sudo apt update,速度飞升!

⚔️ Kali Linux 官方与镜像(渗透测试专用)

官网:https://www.kali.org/get-kali/
官方镜像列表:https://http.kali.org/README.mirrorlist
推荐高速镜像:

  🇨🇳 中科大:http://mirrors.ustc.edu.cn/kali-images/
  🇨🇳 阿里云:https://mirrors.aliyun.com/kali-images/
  🇫🇷 法国官方:https://cdimage.kali.org/

💼 Red Hat Enterprise Linux (RHEL) 与免费替代品

RHEL 开发者订阅(免费):https://developers.redhat.com/rhel
下载门户(需登录):https://access.redhat.com/downloads
100% 兼容的免费替代品:

  Rocky Linux:https://rockylinux.org/download
    镜像列表:https://mirrors.rockylinux.org/mirrormanager/
  AlmaLinux:https://almalinux.org/download/
    仓库:https://repo.almalinux.org/almalinux/

📚 核心开发手册与工业标准

📘 POSIX 标准 — 系统编程的“宪法”

官方免费在线版(The Open Group):
https://pubs.opengroup.org/onlinepubs/9699919799/
✅ 可搜索、可书签、支持交叉引用。涵盖 fork()、pthread_*、select() 等所有 POSIX 函数。
IEEE 付费正式版(适合研究员):
https://standards.ieee.org/standard/1003_1-2017.html

🧭 Linux man pages — 函数与命令即时查询

在线权威版(Michael Kerrisk 维护):
https://man7.org/linux/man-pages/
✅ 比终端 man 命令更友好,支持链接分享,适合嵌入文档。

🖋️ C 语言工业级编码标准(卡内基梅隆大学 CMU)

官方指南:
https://users.ece.cmu.edu/~eno/coding/CCodingStandard.html

核心规范摘要:
// 变量名包含单位
uint32_t timeout_msecs;
uint32_t weight_lbs;

// 枚举:全大写 + 下划线
enum PinStateType {
PIN_OFF,
PIN_ON
};

// 宏定义:始终加括号
#define MAX(a,b) ((a) > (b) ? (a) : (b))

// 始终使用大括号
if (condition) {
do_something();
}

// 初始化所有变量
int error = 0;
char* name = NULL;

⌨️ C++ 工业级编码标准(卡内基梅隆大学 CMU)

官方指南:
https://users.ece.cmu.edu/~eno/coding/CppCodingStandard.html

核心规范摘要:
// 类成员变量:前缀 ‘a’
class MyClass {
private:
int aErrorNumber;
string* apName; // 指针加 ‘p’
};

// 方法名:动词开头
void HandleError();
int CalculateResult();

// 使用命名空间
namespace MyProject {
class Utility { … };
}

// 访问器:同名函数
class Person {
public:
int Age() const { return aAge; }
void Age(int age) { aAge = age; }
private:
int aAge;
};

📖 《UNIX 环境高级编程》(APUE)官方学习入口

官网(作者 Stephen A. Rago 维护):
https://www.apuebook.com/

✅ 提供资源:

📥 全书示例源代码下载
📝 官方勘误表(避免学习踩坑)
📘 各章更新说明
🛒 购买最新第 3 版链接

🌟 为什么读 APUE? 被誉为“UNIX/Linux 系统编程圣经”,涵盖进程、线程、信号、I/O 等核心概念,是面试、晋升、深造的必备读物。

📖 全球知名在线数字图书馆

  名称
  链接
  描述




  O'Reilly Learning
  https://learning.oreilly.com/
  付费订阅制,包含 APUE、TCP/IP Illustrated 等数千本技术书籍。


  Project Gutenberg
  https://www.gutenberg.org/
  6 万+ 免费公版电子书(以经典文学为主)。


  Internet Archive
  https://archive.org/
  数百万免费书籍、手册、软件、历史快照。


  SpringerLink
  https://link.springer.com/
  学术论文与教材,部分内容免费。

⚠️ 提醒:请尊重版权,资源仅用于个人学习与研究。

❓ 常见问题(FAQ)

Q:POSIX 标准和 Linux man pages 有什么区别?
A: POSIX 是国际标准(定义“应该怎么做”),Linux man pages 是具体实现(定义“实际怎么做”)。日常开发先查 man pages,深入研究或跨平台开发时参考 POSIX。

Q:CMU 的 C/C++ 编码标准是强制的吗?
A: 非强制,但强烈建议团队项目采用。这些规范经过工业界验证,能有效减少 Bug、提升代码可读性、简化 Code Review。

Q:《APUE》有免费 PDF 吗?
A: ❌ 没有合法免费 PDF。请支持作者购买正版。官网提供免费源码和勘误,价值极高,务必下载学习。

Q:RHEL 的替代品选 Rocky Linux 还是 AlmaLinux?
A: 两者都是优秀选择,100% 二进制兼容 RHEL。Rocky 由社区驱动,Alma 有基金会支持。根据团队偏好选择即可,不会出错。

✅ 总结与收藏建议
本文是你的 Linux 开发资源中枢 —— 从高速下载、权威手册到工业编码规范,一应俱全。
推荐操作:

🔖 收藏本文 — 随时查阅,效率翻倍
⚡ 配置本地镜像源 — 告别龟速下载
📚 精读 APUE + POSIX — 打好系统编程根基
🧩 团队推行 CMU 编码标准 — 提升代码质量与协作效率

📌 专业提示:工作时保持本页在浏览器标签中打开,它将成为你最强大的生产力工具。

© 2025 calcguide.tech. 欢迎分享,请注明出处。

Comprehensive Guide to Global Online Digital Libraries

Comprehensive Guide to Global Online Digital Libraries

This guide provides detailed profiles of 12 well-known digital libraries worldwide, covering general-purpose platforms, academic databases, national digital libraries, and specialized open-access repositories. Each entry is structured into eight key aspects for systematic comparison and understanding.

LINKS:全球知名在线电子图书馆 LinuxKernel全球下载站点与镜像站点统计

Z-Library – The World’s Largest Shadow Library

1. Basic Information

  • Founded: 2009

  • Operator: Anonymous organization (decentralized)

  • Website: https://z-lib.io (mirror sites frequently change)

2. Content and Scale

  • Total Resources: Over 130 million books and 84 million academic articles

  • Resource Types: E-books, textbooks, novels, research papers, journals, magazines, audiobooks

  • Languages & Disciplines: Supports over 100 languages; covers all major fields including humanities, science, engineering, medicine, law, and arts

  • Sources: User uploads, web crawling, third-party databases, scanned copies

  • Copyright Policy: Most content lacks formal authorization; classified as a “shadow library.” Operates in legal gray zones and faces copyright infringement allegations globally

4. Access and Usage Rights

  • Free Access: Yes, but access depends on mirror availability

  • Registration Required: Optional for basic use; required for higher download limits

  • Download Rules: Allows downloads in PDF, EPUB, MOBI formats; daily limits apply unless upgraded

5. Technology and User Experience

  • Search Functionality: Advanced search by title, author, ISBN, DOI, keyword; includes recommendation engine

  • Interface & Device Support: Responsive design, mobile-friendly, simple interface

  • Reading Tools: Online preview available; no built-in annotation or note-taking

6. Target Audience

  • Primary Users: Self-learners, students in developing countries, researchers with limited institutional access

7. Partnerships and Influence

  • Cooperation Networks: No official partners; maintained through volunteer mirrors and peer support

  • Academic Impact: Widely used globally, especially where legal access to paid resources is limited

8. Features and Value

  • Unique Advantages: Unparalleled scale, fast updates, multi-language support

  • Social Impact: Promotes knowledge equity but raises ethical debates about copyright vs. access rights

World Library – A Nonprofit Global Knowledge Archive

1. Basic Information

2. Content and Scale

  • Total Resources: Over 3.49 million e-books

  • Resource Types: Classic literature, historical documents, religious texts, educational materials, children’s books

  • Languages & Disciplines: Over 100 languages; oldest materials date back to the 11th century; strong in humanities and cultural heritage

  • Sources: Public domain digitization, library donations, government archives

  • Copyright Policy: Focuses on public domain works; fully compliant with copyright laws

4. Access and Usage Rights

  • Free Access: Completely free

  • Registration Required: Not required

  • Download Rules: Full downloads in PDF/EPUB without DRM restrictions

5. Technology and User Experience

  • Search Functionality: Basic and advanced search; AI-powered features (from 2025): smart search, AI-generated summaries, content analysis

  • Interface & Device Support: Clean, multilingual interface; mobile-compatible

  • Reading Tools: Built-in reader with text highlighting and font adjustment

6. Target Audience

  • Primary Users: Students, educators, lifelong learners, multilingual readers

7. Partnerships and Influence

  • Cooperation Networks: Partner of UNESCO; collaborates with global public libraries

  • Academic Impact: Supports education in underserved regions; promotes linguistic diversity

8. Features and Value

  • Unique Advantages: AI-enhanced discovery, focus on ancient and rare texts

  • Social Impact: Democratizes access to historical knowledge across language barriers

Google Books – The Largest Book Search Engine

1. Basic Information

2. Content and Scale

  • Total Resources: Indexes tens of millions of books; millions available for preview or full view

  • Resource Types: Books, journals, manuals, technical reports, rare books

  • Languages & Disciplines: Multilingual (primarily English); spans all academic and general subjects

  • Sources: Partnership with libraries (e.g., Harvard, Oxford) and publishers for scanning

  • Copyright Policy: Three-tier system:

  • Public Domain: Full text available

  • Limited Preview: Partial view

  • Snippet View: Metadata onlyLegal disputes (e.g., Authors Guild v. Google) ruled in favor of “fair use”

4. Access and Usage Rights

  • Free Access: Partially free; full access may require purchase or institutional login

  • Registration Required: Not needed for search or preview

  • Download Rules: Only public domain books can be downloaded as PDF

5. Technology and User Experience

  • Search Functionality: Powerful full-text search using OCR; precise sentence-level results

  • Interface & Device Support: Highly optimized, cross-platform compatible

  • Reading Tools: Page-by-page viewer, text copy, translation, related book suggestions

6. Target Audience

  • Primary Users: Researchers, students, general readers, citation seekers

7. Partnerships and Influence

  • Cooperation Networks: Harvard, Stanford, Oxford, New York Public Library, major publishers

  • Academic Impact: One of the most widely used book discovery tools in academia

8. Features and Value

  • Unique Advantages: Integrated with Google Search; enables deep textual analysis

  • Social Impact: Revolutionized book discoverability; advanced digital humanities research

Project Gutenberg – The Pioneer of Free eBooks

1. Basic Information

2. Content and Scale

  • Total Resources: Over 70,000 free e-books (as of 2025)

  • Resource Types: Classic literature, poetry, drama, philosophy, history

  • Languages & Disciplines: Primarily English; includes French, German, Spanish; focused on humanities

  • Sources: Volunteer digitization via typing or OCR, followed by proofreading

  • Copyright Policy: All works are in the public domain (typically >95 years old); fully legal and free

4. Access and Usage Rights

  • Free Access: Completely free

  • Registration Required: None

  • Download Rules: Unlimited downloads in EPUB, Kindle, HTML, TXT formats

5. Technology and User Experience

  • Search Functionality: Search by title, author, subject, language; popularity ranking

  • Interface & Device Support: Simple, accessible design; mobile-friendly

  • Reading Tools: Online reading; some titles offer audio versions

6. Target Audience

  • Primary Users: Literature enthusiasts, students, educators, language learners

7. Partnerships and Influence

  • Cooperation Networks: Volunteers worldwide; collaboration with Distributed Proofreaders

  • Academic Impact: Considered the first digital library; foundational to the open-access movement

8. Features and Value

  • Unique Advantages: Ad-free, nonprofit, community-powered

  • Social Impact: Preserves literary classics and makes them globally accessible

JSTOR – A Trusted Academic Digital Library

1. Basic Information

2. Content and Scale

  • Total Resources: Nearly 2,000 journals, 100,000+ books, millions of articles

  • Resource Types: Journal backfiles, scholarly monographs, primary sources

  • Languages & Disciplines: Primarily English; strong in humanities and social sciences (history, literature, economics, etc.)

  • Sources: Licensed from academic publishers and university presses

  • Copyright Policy: Legally licensed; most content under copyright; older issues gradually enter public domain

4. Access and Usage Rights

  • Free Access: Limited free content (Early Journal Content); most require subscription

  • Registration Required: Free account for abstracts; institutional login for full access

  • Download Rules: Subscribers can download PDFs with monthly limits

5. Technology and User Experience

  • Search Functionality: Full-text search with advanced filters (subject, year, journal)

  • Interface & Device Support: Modern, responsive design; mobile-compatible

  • Reading Tools: Built-in reader with highlighting, notes, citation export (EndNote, Zotero)

6. Target Audience

  • Primary Users: University students, faculty, researchers, librarians

7. Partnerships and Influence

  • Cooperation Networks: Over 8,000 institutions in 160+ countries, including Harvard, MIT, Cambridge

  • Academic Impact: One of the most authoritative sources in humanities research

8. Features and Value

  • Unique Advantages: High-quality peer-reviewed content; long-term preservation

  • Social Impact: Enables systematic scholarly research and citation

World Digital Library (WDL) – Bridging Global Cultures

1. Basic Information

  • Founded: 2009

  • Operator: U.S. Library of Congress (lead), UNESCO (co-founder)

  • Website: https://www.wdl.org

2. Content and Scale

  • Total Resources: Over 20,000 digitized cultural items

  • Resource Types: Rare books, manuscripts, maps, photos, films, musical scores

  • Languages & Disciplines: Covers 193 countries; supports 7 languages (Arabic, Chinese, English, French, Spanish, Russian, Portuguese)

  • Sources: Contributions from global libraries, museums, and archives

  • Copyright Policy: All items are public domain or cleared for open use

4. Access and Usage Rights

  • Free Access: Completely free

  • Registration Required: Not required

  • Download Rules: High-resolution images and PDFs freely downloadable

5. Technology and User Experience

  • Search Functionality: Filter by country, era, theme, language; timeline and map browsing

  • Interface & Device Support: Multilingual interface; responsive design

  • Reading Tools: Zoomable high-res images, detailed metadata, multilingual descriptions

6. Target Audience

  • Primary Users: Educators, students, historians, cross-cultural researchers

7. Partnerships and Influence

  • Cooperation Networks: 32+ international institutions including British Library, National Library of China

  • Academic Impact: Leading platform for global cultural heritage

8. Features and Value

  • Unique Advantages: Emphasizes non-Western perspectives and cultural diversity

  • Social Impact: Promotes intercultural dialogue and global education

National Digital Library of China – Preserving Chinese Heritage

1. Basic Information

2. Content and Scale

  • Total Resources: Over 10 million digital items

  • Resource Types: E-books, ancient texts, theses, local gazetteers, audio-visual lectures

  • Languages & Disciplines: Primarily Chinese; covers literature, history, law, economics, art

  • Sources: In-house digitization, publisher partnerships, government projects

  • Copyright Policy: Legally licensed or public domain; some content requires authentication

4. Access and Usage Rights

  • Free Access: Partially free; registered users get broader access

  • Registration Required: Real-name registration (via ID)

  • Download Rules: Some books allow online reading or PDF download

5. Technology and User Experience

  • Search Functionality: Full-text search; supports traditional Chinese and handwriting recognition (pilot)

  • Interface & Device Support: Desktop and mobile; WeChat mini-program available

  • Reading Tools: High-res ancient text viewer, text-to-speech, zoom function

6. Target Audience

  • Primary Users: Chinese university students, researchers, general public

7. Partnerships and Influence

  • Cooperation Networks: National and local libraries, university consortia

  • Academic Impact: China’s largest public digital library; key cultural infrastructure

8. Features and Value

  • Unique Advantages: World-leading digitization of Chinese classics (e.g., Yongle Encyclopedia)

  • Social Impact: Advances traditional culture preservation and national literacy

Internet Archive – Preserving Humanity’s Digital Memory

1. Basic Information

2. Content and Scale

  • Total Resources: 30+ million books (including Open Library), billions of web pages, millions of videos/audio

  • Resource Types: E-books, websites (Wayback Machine), software, films, music, radio

  • Languages & Disciplines: Multilingual, interdisciplinary

  • Sources: Donations, scanning, web crawling, user uploads

  • Copyright Policy: Mix of public domain, fair use, and controlled digital lending (with DRM)

4. Access and Usage Rights

  • Free Access: Fully free

  • Registration Required: Required for borrowing e-books

  • Download Rules: Most items downloadable; borrowed books return after 14 days

5. Technology and User Experience

  • Search Functionality: Powerful metadata search; filter by type, year, format

  • Interface & Device Support: Feature-rich but complex; works on all platforms

  • Reading Tools: Universal viewer with page flip, zoom, text copy

6. Target Audience

  • Primary Users: Researchers, historians, retro-computing enthusiasts, digital archivists

7. Partnerships and Influence

  • Cooperation Networks: Libraries, universities, open-source communities

  • Academic Impact: Global leader in digital preservation

8. Features and Value

  • Unique Advantages: Archives disappearing websites and obsolete software

  • Social Impact: Known as the “Library of Alexandria for the digital age”

HathiTrust Digital Library – A Consortium for Scholarly Preservation

1. Basic Information

2. Content and Scale

  • Total Resources: Over 17 million volumes

  • Resource Types: Academic books, journals, government documents, patents

  • Languages & Disciplines: Primarily English; comprehensive across disciplines

  • Sources: Google Books scans, member library digitization

  • Copyright Policy: ~40% public domain (full access); rest accessible only to member institutions

4. Access and Usage Rights

  • Free Access: Public domain content free; others require institutional login

  • Registration Required: Not for search; access requires affiliation

  • Download Rules: Public domain books can be fully downloaded as PDF

5. Technology and User Experience

  • Search Functionality: Full-text search; bulk metadata export

  • Interface & Device Support: Professional, research-oriented; responsive

  • Reading Tools: Standard PDF reader with text selection

6. Target Audience

  • Primary Users: Faculty and students at member universities (e.g., MIT, Stanford, UC)

7. Partnerships and Influence

  • Cooperation Networks: 170+ universities including Michigan, Harvard, Stanford

  • Academic Impact: Cornerstone of long-term academic resource preservation in North America

8. Features and Value

  • Unique Advantages: Collaborative model ensures sustainability and access continuity

  • Social Impact: Provides critical infrastructure for digital scholarship

Europeana – Europe’s Cultural Memory Online

1. Basic Information

2. Content and Scale

  • Total Resources: Over 50 million cultural records

  • Resource Types: Books, manuscripts, artworks, photos, music, films

  • Languages & Disciplines: 29 European languages; strong in art, history, literature

  • Sources: Aggregated metadata from EU museums, libraries, archives

  • Copyright Policy: Open metadata; links to original institutions; most content legally open

4. Access and Usage Rights

  • Free Access: Fully free

  • Registration Required: Not required

  • Download Rules: Metadata free; original files follow source institution policies

5. Technology and User Experience

  • Search Functionality: Semantic search, timeline, map-based browsing

  • Interface & Device Support: Modern, multilingual, mobile-ready

  • Reading Tools: High-resolution viewing, favorites, share options

6. Target Audience

  • Primary Users: Educators, researchers, artists, general public

7. Partnerships and Influence

  • Cooperation Networks: Thousands of institutions across 40+ European countries

  • Academic Impact: Largest pan-European cultural heritage portal

8. Features and Value

  • Unique Advantages: Cross-border integration of cultural collections

  • Social Impact: Celebrates Europe’s diversity and fosters public engagement

NDLI (National Digital Library of India) – Empowering Indian Education

1. Basic Information

2. Content and Scale

  • Total Resources: Over 100 million educational items

  • Resource Types: Textbooks, lecture notes, video lectures, exam papers, theses

  • Languages & Disciplines: 22 Indian languages; K-12 to postgraduate levels

  • Sources: Government initiatives, university collaborations, OER (Open Educational Resources)

  • Copyright Policy: Mostly open license or public domain

4. Access and Usage Rights

  • Free Access: Completely free

  • Registration Required: Recommended for personalized features

  • Download Rules: Free download of PDFs, videos, and other materials

5. Technology and User Experience

  • Search Functionality: Multilingual search; curriculum-based navigation

  • Interface & Device Support: Responsive; offline app available

  • Reading Tools: Video player, note-taking, bookmarking

6. Target Audience

  • Primary Users: Indian students, teachers, rural learners

7. Partnerships and Influence

  • Cooperation Networks: UGC, AICTE, CBSE, state education boards

  • Academic Impact: Largest educational digital platform in South Asia

8. Features and Value

  • Unique Advantages: Supports low-bandwidth areas and multilingual education

  • Social Impact: Supports India’s “Digital India” and inclusive education goals

PubMed Central (PMC) – The Leading Biomedical Open Repository

1. Basic Information

2. Content and Scale

  • Total Resources: Over 7 million full-text articles

  • Resource Types: Peer-reviewed biomedical and life sciences research papers

  • Languages & Disciplines: Primarily English; medicine, genetics, public health, biology

  • Sources: Mandated submissions from journals under NIH Public Access Policy

  • Copyright Policy: All content is open access (OA), under CC licenses or publisher agreements

4. Access and Usage Rights

  • Free Access: Completely free

  • Registration Required: None

  • Download Rules: PDF and XML downloads allowed for research and teaching

5. Technology and User Experience

  • Search Functionality: Integrated with PubMed; advanced filters, citation tracking

  • Interface & Device Support: Clean, professional; API access available

  • Reading Tools: Standard PDF reader; links to references and datasets

6. Target Audience

  • Primary Users: Medical researchers, clinicians, students, policymakers

7. Partnerships and Influence

  • Cooperation Networks: Thousands of journals, universities, research funders worldwide

  • Academic Impact: Most authoritative OA repository in biomedicine

8. Features and Value

  • Unique Advantages: Enforces open access for publicly funded research

  • Social Impact: Accelerates medical discovery, especially during health crises

✅ ConclusionThese 12 digital libraries represent the diversity and evolution of global knowledge access — from open archives to academic gateways, from cultural preservation to educational equity. They reflect both technological innovation and ongoing debates about copyright, access, and digital inclusion.

Let this guide serve as a reference for educators, researchers, students, and lifelong learners navigating the digital knowledge landscape.

Discover the ultimate guide to global online digital libraries. Explore resources, access books, and learn about digital archives worldwide. Perfect for rese…

Global Online Digital Libraries, Comprehensive Guide to Digital Libraries, Online Digital Library Resources, Digital Libraries Around the World, What Are Digital Libraries, Top Online Digital Libraries 2024, Digital Library Services and Benefits, How to Use Digital Libraries, Free Online Digital Library Access, Digital Library Platforms and Tools

fork系统调用及示例

fork系统调用及示例

我们继续按照您的列表顺序,介绍下一个函数。在 clone 之后,根据您提供的列表,下一个函数是 fork。

相关阅读:fork系统调用及示例-CSDN博客 vfork系统调用及示例 ptrace系统调用及示例

1. 函数介绍

fork 是 Linux 和所有 Unix-like 系统中最基本、最重要的进程创建系统调用之一。它的功能非常直接:创建一个调用进程(父进程)

你可以把 fork 想象成一个生物细胞的有丝分裂过程:

  • 你有一个原始细胞(父进程)。

  • fork 调用就像触发了一次分裂。

  • 分裂完成后,你得到了两个细胞(两个进程):一个原始的(父进程)和一个全新的、几乎完全一样的(子进程)。

  • 两个细胞(进程)从分裂完成的那一刻起,开始独立地执行后续代码。

fork 是多进程编程的基石。几乎所有需要创建新进程的 Unix/Linux 程序(服务器、shell、构建工具等)都会用到它。

2. 函数原型

1
2
3
4
#include <unistd.h> // 必需

pid_t fork(void);

3. 功能

  • 创建新进程: 请求操作系统内核创建一个新的进程(子进程)。

复制父进程: 内核会创建一个调用进程(父进程)的副本(子进程)。

子进程拥有父进程在调用 fork 时的几乎全部状态:

  • 代码段(.text)

  • 数据段(.data, .bss)

  • 堆(heap)

  • 栈(stack)

  • 打开的文件描述符及其状态(偏移量等)

  • 环境变量

  • 当前工作目录

  • 用户 ID 和组 ID

  • 等等…

独立执行: 从 fork 返回后,父进程和子进程在操作系统调度下独立运行。

4. 参数

  • void: fork 函数不接受任何参数。

5. 返回值

fork 的返回值是其最独特和关键的特性,因为它在父进程和子进程中返回不同的值:

在父进程中:

  • 成功: 返回新创建子进程的进程 ID (PID)。这是一个正整数。

  • 失败: 返回 -1,并设置全局变量 errno 来指示错误原因(例如 EAGAIN 系统资源不足,ENOMEM 内存不足)。

在子进程中:

  • 成功: 返回 0。

  • (理论上不会发生): 如果子进程创建失败,则不会执行到返回这一步。

**失败时 **(父进程)

  • 返回 -1,并且没有子进程被创建。

关键理解点: 一个 fork 调用,一次调用,两次返回。

6. 相似函数,或关联函数

  • vfork: 类似于 fork,但在子进程调用 exec 或 _exit 之前会暂停父进程。现在通常不推荐使用,posix_spawn 或直接 fork + exec 更安全。

  • clone: Linux 特有的、更灵活和底层的进程/线程创建函数。fork 和 vfork 在底层都可以通过调用 clone 来实现。

  • _exit / exit: 子进程通常调用这两个函数之一来终止自己。

  • wait / waitpid: 父进程使用这些函数来等待子进程结束,并获取子进程的退出状态。

  • exec 系列函数 (execl, execv, execve 等): 通常在 fork 之后,由子进程调用,用一个新的程序镜像替换当前进程的镜像。

7. 示例代码

示例 1:基本的 fork 使用

这个例子演示了 fork 最基本的用法,以及如何区分父进程和子进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// fork_basic.c
#include <unistd.h> // fork, getpid, sleep
#include <sys/wait.h> // wait
#include <stdio.h> // printf, perror
#include <stdlib.h> // exit

int main() {
pid_t pid;

printf("Before fork: Process ID (PID) is %d\n", getpid());

// --- 关键: 调用 fork ---
pid = fork();

// --- fork 之后,代码被父进程和子进程同时执行 ---

if (pid == -1) {
// fork 失败,只在父进程中执行
perror("fork failed");
exit(EXIT_FAILURE);

} else if (pid == 0) {
// --- 子进程执行的代码 ---
printf("This is the CHILD process.\n");
printf(" Child's PID is %d\n", getpid());
printf(" Child's parent PID (PPID) is %d\n", getppid());
printf(" fork() returned %d in the child.\n", pid); // pid is 0 here

// 子进程可以执行自己的任务
printf(" Child process is sleeping for 2 seconds...\n");
sleep(2);
printf(" Child process woke up and is exiting.\n");

// 子进程结束
exit(EXIT_SUCCESS);

} else {
// --- 父进程执行的代码 ---
printf("This is the PARENT process.\n");
printf(" Parent's PID is %d\n", getpid());
printf(" Parent's child PID is %d (returned by fork)\n", pid);
printf(" fork() returned %d in the parent.\n", pid);

// 父进程可以执行自己的任务
printf(" Parent process is waiting for child to finish...\n");
int status;
// wait() 会挂起父进程,直到任意一个子进程结束
wait(&status); // status 用于获取子进程的退出信息

if (WIFEXITED(status)) {
int exit_status = WEXITSTATUS(status);
printf(" Parent: Child exited normally with status %d.\n", exit_status);
} else {
printf(" Parent: Child did not exit normally.\n");
}

printf(" Parent process is exiting.\n");
}

return 0;
}

代码解释:

程序开始执行,打印父进程的 PID。

关键步骤: 调用 pid = fork();。

这个调用之后,操作系统创建了一个子进程,它是父进程的一个副本。

在父进程和子进程中,代码都从 fork() 之后的下一行开始继续执行。

程序立即使用 if 语句检查 pid 的值来区分父进程和子进程:

  • if (pid == -1): fork 调用失败。只有父进程会进入这个分支。

  • else if (pid == 0): 这段代码只在子进程中执行。fork 在子进程中返回 0。

  • else (即 pid > 0): 这段代码只在父进程中执行。fork 在父进程中返回新创建子进程的 PID。

子进程打印自己的 PID (getpid()) 和父进程的 PID (getppid()),然后睡眠 2 秒并退出。

父进程调用 wait(&status) 等待子进程结束。wait 会挂起父进程,直到子进程调用 exit 或 _exit。

子进程退出后,wait 返回。父进程检查子进程的退出状态 (status) 并打印相关信息,然后退出。

示例 2:创建多个子进程

这个例子演示了如何使用 fork 在一个循环中创建多个子进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// fork_multiple.c
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>

#define NUM_CHILDREN 5

int main() {
pid_t pid;
int i;

printf("Parent process (PID: %d) is creating %d children.\n", getpid(), NUM_CHILDREN);

for (i = 0; i < NUM_CHILDREN; i++) {
pid = fork();

if (pid == -1) {
perror("fork failed");
// 可以选择继续创建其他子进程或退出
exit(EXIT_FAILURE);

} else if (pid == 0) {
// --- 子进程 ---
printf("Child %d (PID: %d) created. Doing work...\n", i, getpid());

// 模拟工作:睡眠不同的时间
sleep(i + 1);
printf("Child %d (PID: %d) finished work and is exiting with code %d.\n", i, getpid(), i);
exit(i); // 用 i 作为退出码

}
// --- 父进程 ---
// 父进程从 if-else 结构出来,继续循环
// printf("Parent (PID: %d) created child %d with PID %d\n", getpid(), i, pid);
// 注意:如果父进程在这里也打印,输出会和子进程的输出混合
}

// --- 所有子进程创建完毕,父进程等待它们 ---
printf("Parent (PID: %d) has created all children. Now waiting for them to finish...\n", getpid());

// 等待所有子进程结束
for (i = 0; i < NUM_CHILDREN; i++) {
int status;
pid_t waited_pid = wait(&status); // waitpid(-1, &status, 0) 等价

if (waited_pid == -1) {
perror("wait failed");
} else {
if (WIFEXITED(status)) {
int exit_code = WEXITSTATUS(status);
printf("Parent: Child with PID %d exited normally with code %d.\n", waited_pid, exit_code);
} else {
printf("Parent: Child with PID %d did not exit normally.\n", waited_pid);
}
}
}

printf("Parent (PID: %d) finished. All children have been reaped.\n", getpid());
return 0;
}

代码解释:

定义要创建的子进程数量 NUM_CHILDREN。

父进程进入一个循环 for (i = 0; i < NUM_CHILDREN; i++)。

在每次循环中调用 fork()。

在子进程中 (pid == 0):

  • 打印信息。

  • 根据 i 的值睡眠不同时间(模拟不同长度的工作)。

  • 调用 exit(i) 退出,并将循环变量 i 作为退出码。

在父进程中 (pid > 0):

  • 循环继续下一次迭代,创建下一个子进程。

当循环结束时,所有子进程都已启动。

父进程进入第二个循环 for (i = 0; i < NUM_CHILDREN; i++)。

在这个循环中,父进程调用 wait(&status) 五次。

每次 wait 返回,就表示有一个子进程结束了。父进程获取其 PID 和退出状态并打印。

所有子进程都被 wait 回收后,父进程结束。

示例 3:fork + exec 创建新程序

这个例子演示了经典的 fork + exec 模式,这是 Unix/Linux 系统中启动新程序的标准方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// fork_exec.c
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
pid_t pid;

printf("Parent process (PID: %d) is about to fork.\n", getpid());

pid = fork();

if (pid == -1) {
perror("fork failed");
exit(EXIT_FAILURE);

} else if (pid == 0) {
// --- 子进程 ---
printf("Child process (PID: %d) created.\n", getpid());

// 准备 execvp 所需的参数
// execvp(const char *file, char *const argv&#91;]);
char *args&#91;] = { "ls", "-l", "/tmp", NULL }; // argv&#91;] 必须以 NULL 结尾

printf("Child (PID: %d) is about to execute 'ls -l /tmp'.\n", getpid());

// 调用 execvp 执行新的程序
// 如果 execvp 成功,下面的代码将不会被执行
// 因为当前进程的镜像已被替换
execvp(args&#91;0], args);

// --- 如果代码执行到这里,说明 execvp 失败了 ---
perror("execvp failed"); // 打印错误信息
_exit(EXIT_FAILURE); // 子进程失败退出,使用 _exit

} else {
// --- 父进程 ---
printf("Parent process (PID: %d) created child (PID: %d).\n", getpid(), pid);

// 父进程等待子进程结束
int status;
if (waitpid(pid, &status, 0) == -1) { // 等待特定的子进程
perror("waitpid failed");
exit(EXIT_FAILURE);
}

if (WIFEXITED(status)) {
int exit_status = WEXITSTATUS(status);
printf("Parent: Child (PID: %d) exited with status %d.\n", pid, exit_status);
} else if (WIFSIGNALED(status)) {
int sig = WTERMSIG(status);
printf("Parent: Child (PID: %d) was killed by signal %d.\n", pid, sig);
} else {
printf("Parent: Child (PID: %d) did not exit normally.\n", pid);
}

printf("Parent process (PID: %d) finished.\n", getpid());
}

return 0;
}

代码解释:

父进程调用 fork() 创建子进程。

在子进程中 (pid == 0):

  • 准备要执行的命令及其参数。这里准备执行 ls -l /tmp。

关键步骤: 调用 execvp(“ls”, args)。

  • execvp 会用 ls 程序的镜像替换当前子进程的内存镜像。

  • 如果 execvp 成功,它永远不会返回。子进程的代码从 ls 程序的入口点开始执行。

  • 如果 execvp 失败(例如,找不到 ls 命令),它会返回 -1。

如果 execvp 返回了(即失败了),打印错误信息并调用 _exit(EXIT_FAILURE) 退出子进程。

在父进程中 (pid > 0):

  • 打印信息。

  • 调用 waitpid(pid, &status, 0) 等待特定的子进程(由 pid 指定)结束。

  • 子进程执行 ls 命令并退出。

waitpid 返回,父进程检查子进程的退出状态。

  • WIFEXITED: 检查子进程是否正常退出(通过 exit 或 return)。

  • WIFSIGNALED: 检查子进程是否被信号终止。

打印子进程的退出信息。

父进程退出。

重要提示与注意事项:

一次调用,两次返回: 理解 fork 在父进程和子进程中返回不同值是掌握其用法的关键。

区分父子进程: 必须使用 if (pid == 0) {…} else if (pid > 0) {…} else {…} 模式来区分父进程和子进程,并执行不同的代码逻辑。

错误处理: 始终检查 fork 的返回值是否为 -1,以处理可能的失败情况(如资源不足)。

僵尸进程: 当子进程结束而父进程尚未调用 wait 或 waitpid 来获取其状态时,子进程会变成僵尸进程(Zombie Process)。这会浪费一个进程表项。因此,父进程必须等待子进程。

孤儿进程: 如果父进程在子进程之前结束,子进程会变成孤儿进程(Orphan Process),并被 init 进程(PID 1)收养。

文件描述符: fork 之后,父进程和子进程共享所有的文件描述符。它们指向同一个内核的“打开文件描述”(open file description),因此共享文件偏移量等。如果需要独立的文件描述符,子进程需要在 fork 后显式地 close 和 open。

_exit vs exit: 在 fork 之后的子进程中,通常推荐使用 _exit() 而不是 exit() 来终止。因为 exit() 会执行一些清理工作(如刷新 stdio 缓冲区),这些操作在多进程环境下可能导致意外行为(例如,缓冲区被刷新两次)。_exit() 直接终止进程。

fork + exec 模式: 这是 Unix/Linux 系统中创建并运行新程序的标准方法。先 fork 创建子进程,然后在子进程中调用 exec 系列函数加载新程序。

总结:

fork 是 Unix/Linux 系统编程的基础。它通过创建当前进程的副本来实现多进程。理解其“一次调用,两次返回”的特性以及如何正确地区分和管理父、子进程,是编写健壮的多进程应用程序的关键。它通常与 wait/waitpid 和 exec 系列函数结合使用。

https://www.calcguide.tech/2025/08/26/linux开源软件路线图/

pivot_root系统调用及示例

pivot_root系统调用及示例

相关文章:pivot_root系统调用及示例 pivot_root系统调用及示例-CSDN博客

我们来深入学习 pivot_root 系统调用

1. 函数介绍

在 Linux 系统中,每个运行的进程都看到一个文件系统层次结构(就是我们熟悉的 /, /home, /usr 等目录树)。这个层次结构的根目录 (/) 是所有路径的起点。

chroot 系统调用可以改变当前进程及其子进程看到的根目录。例如,执行 chroot /newroot 后,进程再访问 / 实际上是访问 /newroot,访问 /bin 实际上是访问 /newroot/bin。但是,chroot 有一个重要的限制:它只影响调用进程及其后续创建的子进程。如果系统上还有其他进程在运行,它们看到的根目录仍然是原来的那个。

pivot_root 是一个更强大、更彻底的系统调用。它的作用是交换整个系统当前的根文件系统和另一个文件系统。

想象一下,你有两个完整的、独立的 Linux 文件系统环境:

  • 当前的根文件系统 (/):这是系统当前运行所在的环境。

  • 新的根文件系统 (new_root):这是你准备好的另一个完整的、可以独立运行的 Linux 环境(通常是一个临时的、精简的或者用于恢复的系统)。

pivot_root 会执行以下操作:

将 new_root 变成新的根文件系统 (/)。

将旧的根文件系统移动到一个指定的位置 (put_old),这个位置必须是在新的根文件系统 new_root 内部。

更新所有相关进程的根目录和当前工作目录信息。

简单来说,pivot_root 就像是整个系统(所有进程)瞬间从一个“世界”(旧根文件系统)“传送”到了另一个“世界”(新根文件系统),而旧的“世界”被折叠打包放在了新“世界”里的一个盒子里。

典型应用场景:

  • Linux 启动过程:这是 pivot_root 最重要的用途之一。Linux 启动时,内核通常先挂载一个临时的、基于内存的初始根文件系统(initramfs 或 initrd)。这个临时系统包含了加载真实根文件系统所需的驱动程序。一旦真实根文件系统被挂载(例如挂载到 /mnt),启动脚本就会执行 pivot_root /mnt /mnt/old_root,将 /mnt 变成新的根目录 (/),并将旧的 initramfs 环境移动到新的根目录下的 /old_root 目录中。然后,系统会执行新的根文件系统中的 /sbin/init 程序,完成启动。

  • 系统救援/恢复:在系统无法正常启动时,可以从外部介质(如 Live CD/USB)启动一个救援环境,然后使用 pivot_root 切换到硬盘上损坏的系统分区进行修复。

  • 容器技术:虽然现代容器(如 Docker)主要使用 chroot 和 mount 命名空间,但在某些高级场景下,pivot_root 也可能被用于设置容器的根文件系统。

2. 函数原型

1
2
3
4
5
#define _GNU_SOURCE // 启用 GNU 扩展
#include <unistd.h> // 包含 pivot_root 函数声明 (在某些系统上可能在 <linux/unistd.h>)

int pivot_root(const char *new_root, const char *put_old);

注意:pivot_root 不是 POSIX 标准函数,它是 Linux 特有的系统调用。在标准 C 库 (glibc) 中可能没有直接的包装函数,或者需要特定版本才支持。如果 #include <unistd.h> 后编译报错找不到 pivot_root,你可能需要:

更新 glibc。

手动通过 syscall 调用(但这比较复杂,因为需要处理路径)。

使用 syscall(SYS_pivot_root, new_root, put_old),但这通常不推荐,因为路径参数需要特殊处理。

在大多数现代 Linux 系统上,直接包含 <unistd.h> 并调用 pivot_root 是可行的。

3. 功能

将当前进程的根文件系统切换为 new_root 指向的文件系统,并将原来的根文件系统移动到 put_old 指向的目录中(该目录必须位于 new_root 内)。

4. 参数

new_root:

  • const char * 类型。

  • 指向一个目录的路径名,该目录是新的根文件系统的挂载点。这个文件系统必须已经挂载好。

put_old:

  • const char * 类型。

  • 指向一个目录的路径名,该目录必须位于 new_root 文件系统内。调用成功后,原来的根文件系统会被挂载到这个目录下。

5. 返回值

  • 成功: 返回 0。

  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

  • EPERM: 调用进程没有足够的权限执行此操作(通常需要 CAP_SYS_ADMIN 能力)。

  • EINVAL: new_root 或 put_old 参数无效,例如 new_root 和 put_old 在同一个文件系统上,或者 put_old 不在 new_root 下,或者 new_root 本身就是根目录 (/)。

  • ENOMEM: 内核内存不足。

  • EBUSY: new_root 或 put_old 正在被使用,无法完成操作。

  • ENOENT: new_root 或 put_old 指定的目录不存在。

  • ENOTDIR: new_root 或 put_old 不是目录。

  • EIO: I/O 错误。

7. 重要前提和限制

要成功调用 pivot_root,必须满足以下条件:

new_root 和 put_old 必须是不同的文件系统:new_root 和 put_old 不能指向同一个文件系统。通常,new_root 是一个新挂载的文件系统,而 put_old 是新文件系统中的一个目录。

put_old 必须在 new_root 内部:put_old 指定的路径必须是 new_root 路径的子目录。

调用进程必须在 new_root 中运行:调用 pivot_root 时,调用进程的当前工作目录 (cwd) 通常必须位于 new_root 文件系统内。最常见做法是在调用前先 chdir(new_root)。

权限:通常需要 CAP_SYS_ADMIN 能力(root 用户通常拥有此能力)。

new_root 不能是根目录:new_root 本身不能是 /。

8. 相似函数或关联函数

  • chroot: 更简单、限制更多的改变根目录的方法,只影响调用进程及其子进程。

  • mount: 用于挂载文件系统,pivot_root 通常需要先用 mount 挂载好 new_root。

  • umount: 用于卸载文件系统。在 pivot_root 后,通常需要卸载旧根文件系统(现在位于 put_old)。

  • init: 系统的第一个进程(PID 1),在 pivot_root 后通常会执行新的 init。

  • switch_root: 一个用户空间命令(通常在 initramfs 中),它不仅执行 pivot_root,还会尝试卸载旧的根文件系统并执行新的 /sbin/init。它比直接使用 pivot_root 更高级。

9. 示例代码

由于 pivot_root 是一个系统级操作,通常在用户空间程序中很少直接调用。它主要在系统启动脚本(initramfs)或容器运行时中使用。

下面是一个概念性的示例,展示了 pivot_root 的典型用法步骤。请注意:在真实系统上运行此代码可能会导致系统不稳定或无法启动,请在虚拟机或容器中谨慎测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mount.h> // 包含 MS_MOVE 等
#include <sys/stat.h> // 包含 mkdir
#include <fcntl.h> // 包含 open, O_* flags
#include <errno.h>
#include <string.h>

int main(int argc, char *argv&#91;]) {
// 注意:这是一个高度简化的、用于演示的示例。
// 真实的 initramfs 或切换脚本会更复杂,并处理更多细节和错误。

// 假设:
// 1. 系统当前运行在一个临时的 initramfs 根文件系统上。
// 2. 真实的目标根文件系统已经被挂载到了 /mnt/new_root。
// 3. 本程序运行在 /mnt/new_root 目录下。

const char *new_root = "/mnt/new_root";
const char *put_old = "/mnt/new_root/old_root";

printf("--- Demonstrating pivot_root (Conceptual Example) ---\n");
printf("IMPORTANT: This is a simplified example. Running this on a real system can be dangerous!\n");
printf("Make sure you understand the implications and run it in a safe environment (VM/Container).\n\n");

// 1. 检查参数
if (argc != 1) {
fprintf(stderr, "Usage: %s (no arguments needed for this conceptual demo)\n", argv&#91;0]);
fprintf(stderr, "This example assumes the setup described in the comments.\n");
exit(EXIT_FAILURE);
}

// 2. (在真实场景中) 挂载目标根文件系统到 new_root
// 例如: mount("/dev/sda1", "/mnt/new_root", "ext4", 0, NULL);
// 这里我们假设它已经挂载好了。

// 3. 创建 put_old 目录 (如果不存在)
if (mkdir(put_old, 0755) == -1 && errno != EEXIST) {
perror("mkdir put_old");
fprintf(stderr, "Failed to create %s\n", put_old);
exit(EXIT_FAILURE);
}
printf("1. Ensured directory '%s' exists.\n", put_old);

// 4. 关键步骤:切换当前工作目录到 new_root
// 这是 pivot_root 成功的关键前提之一。
if (chdir(new_root) == -1) {
perror("chdir new_root");
fprintf(stderr, "Failed to chdir to %s\n", new_root);
exit(EXIT_FAILURE);
}
printf("2. Changed current working directory to '%s'.\n", new_root);

// 5. 调用 pivot_root
// 这是核心操作
printf("3. Calling pivot_root('%s', '%s')...\n", new_root, put_old);
if (pivot_root(".", "./old_root") == -1) { // 使用相对路径
perror("pivot_root");
fprintf(stderr, "pivot_root failed. Check kernel logs (dmesg) for more details.\n");
fprintf(stderr, "Common issues: Not running as root, new_root/put_old setup incorrect.\n");
exit(EXIT_FAILURE);
}
printf(" pivot_root succeeded!\n");

// 6. (在真实场景中) 清理:卸载旧的根文件系统
// 现在旧的根文件系统挂载在 /old_root
printf("4. Attempting to unmount the old root filesystem (now at /old_root)...\n");
// 在卸载之前,通常需要将一些关键的文件系统(如 /proc, /sys, /dev)
// 从旧根移动到新根,或者重新挂载它们。
// 这里我们简化处理。
if (umount2("/old_root", MNT_DETACH) == -1) {
perror("umount2 /old_root");
fprintf(stderr, "Failed to unmount old root. It might still be accessible at /old_root.\n");
// 不退出,继续演示
} else {
printf(" Old root filesystem unmounted (or detached).\n");
}

// 7. (在真实场景中) 执行新的 init 系统
printf("5. Would now typically exec(/sbin/init) to start the new OS environment.\n");
printf(" For this demo, we will just print a message and exit.\n");
printf("\n--- New Root Environment is Active ---\n");
printf("If this were a real initramfs, the next step would be:\n");
printf("execl(\"/sbin/init\", \"init\", (char *)NULL);\n");
printf("And the system would boot from the new root filesystem.\n");

// 注意:在真实情况下,这里不会返回。
// execl("/sbin/init", "init", (char *)NULL);
// 如果 execl 返回,说明执行失败
// perror("execl /sbin/init");

return 0;
}

10. 实际场景:initramfs 中的 pivot_root

在 Linux 启动过程中,pivot_root 的使用最为典型。一个简化版的 initramfs 脚本流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/bin/sh
# 这是一个在 initramfs 中运行的简化版启动脚本 (概念性)

# 1. 加载必要的内核模块 (例如文件系统驱动)
# modprobe ext4

# 2. 扫描硬件,找到根设备 (例如 /dev/sda1)

# 3. 创建一个目录用于挂载真实根文件系统
mkdir /mnt

# 4. 挂载真实根文件系统到 /mnt
mount -t ext4 /dev/sda1 /mnt

# 5. 确保新根中有存放旧根的目录
mkdir /mnt/old_root

# 6. 切换根目录 (使用 switch_root 命令,它内部调用 pivot_root 并清理)
# 注意:直接调用 pivot_root 后还需要很多清理工作,switch_root 更安全
# 但如果要手动模拟:
# cd /mnt
# pivot_root . ./old_root
# exec chroot . /sbin/init <dev/console >dev/console 2>&1

# 更推荐使用 switch_root
exec switch_root /mnt /sbin/init

11. 编译和运行 (概念性)

1
2
3
4
5
6
7
8
# 假设代码保存在 pivot_root_demo.c 中
# 注意:此代码仅为演示概念,直接运行有风险!
gcc -o pivot_root_demo pivot_root_demo.c

# 这个程序不应该在常规系统上直接运行。
# 它需要在一个特定的、已准备好 new_root 和 put_old 的环境中运行。
# 通常,它会是 initramfs 的一部分。

12. 总结

pivot_root 是一个功能强大但使用场景非常特定的 Linux 系统调用。

  • 核心作用:全局性地切换系统的根文件系统,影响所有进程。

与 chroot 的区别:

  • chroot 只改变调用进程及其子进程的根视图。

  • pivot_root 改变整个系统的根视图。

典型用途:Linux 系统启动(initramfs 到真实根文件系统)、系统救援。

关键前提:

  • new_root 和 put_old 必须是不同且已挂载的文件系统。

  • put_old 必须在 new_root 内部。

  • 调用进程的当前目录通常需要在 new_root 中。

  • 通常需要 root 权限。

后续操作:pivot_root 后,通常需要卸载旧根 (umount) 并启动新的 init 进程。

高级工具:switch_root 命令封装了 pivot_root 和后续的清理/执行 init 的步骤,是更安全的选择。

对于 Linux 编程新手,理解 pivot_root 的概念和它在系统启动中的作用是非常有价值的,但在日常应用程序开发中很少会直接用到它。

pkey_alloc pkey_free系统调用及示例

pkey_alloc pkey_free系统调用及示例

我们继续按照您的要求学习 Linux 系统编程中的重要函数。这次我们介绍 pkey_alloc 和 pkey_free。

1. 函数介绍

pkey_alloc 和 pkey_free 是一组与 内存保护键(Memory Protection Keys, MPK)相关的 Linux 系统调用(内核版本 >= 4.9, x86 架构)。它们是 Intel MPK (Memory Protection Keys) 特性的用户态接口。

**核心概念:内存保护键 **(Protection Keys)

想象你的内存是一间大房子,里面有各种不同的房间(内存页)。传统上,你只有一把总钥匙(页表项中的权限位 RWX)来控制进入这间房子的所有门(内存访问)。如果这把钥匙丢了或者被复制,坏人就能进入所有房间。

内存保护键(MPK)就像是给这间大房子加装了多把独立的锁(保护键):

获取钥匙 (pkey_alloc) 你向操作系统申请一把新的、独立的钥匙(保护键)。操作系统给你一个钥匙编号(pkey)。

**给房间上锁 **(pkey_mprotect) 你可以使用 pkey_mprotect 系统调用,将特定的房间(内存区域)与你刚申请到的那把钥匙(pkey)关联起来。这相当于给这些房间的门加上了这把新锁。

**控制钥匙 **(特殊寄存器) CPU 内部有一个特殊的寄存器(x86 上是 PKRU - Protection Key Rights User register)。这个寄存器里有 16 个插槽(对应 16 个可能的 pkey),每个插槽可以设置为允许或禁止访问。

尝试进入房间: 当程序试图访问一个与特定 pkey 关联的内存页时:

  • CPU 会检查页表项中的 pkey 编号(例如 3)。

  • 然后检查 PKRU 寄存器中对应插槽(第 3 个插槽)的权限。

  • 如果 PKRU 允许访问(例如,插槽 3 是 0b00),访问成功。

  • 如果 PKRU 禁止访问(例如,插槽 3 是 0b01 或 0b10),CPU 会立即产生一个 SIGSEGV (段错误) 信号,而无需进行昂贵的页表遍历。

pkey_alloc 和 pkey_free 的作用:

  • pkey_alloc: 申请一个可用的内存保护键(pkey)。成功时返回一个唯一的 pkey 编号(0-15)。

  • pkey_free: 释放一个之前通过 pkey_alloc 申请的 pkey,使其可以被其他部分的程序再次申请使用。

优势:

  • 快速权限切换: 通过修改 PKRU 寄存器(一个非常快的操作),可以瞬间改变对大量内存区域的访问权限,而无需修改每个内存页的页表项。

  • 细粒度保护: 可以将不同的内存区域分配给不同的 pkey,实现更细粒度的内存访问控制。

  • 硬件加速: 权限检查由 CPU 硬件直接完成,性能极高。

相关文章:pkey_alloc系统调用及示例-CSDN博客 Linux 3.0 内核系统调用 preadv2系统调用及示例

2. 函数原型

1
2
3
4
5
6
7
8
#include <sys/mman.h> // 包含 MPK 相关常量和函数声明 (需要较新的 glibc)

// 分配一个保护键
int pkey_alloc(unsigned int flags, unsigned int access_rights);

// 释放一个保护键
int pkey_free(int pkey);

注意: 这些函数需要 glibc 2.27 或更高版本。在较旧的系统上,可能需要直接使用 syscall。

3. 功能

pkey_alloc:

  • 向内核请求一个当前未被使用的内存保护键。

  • 内核会返回一个唯一的 pkey 编号(通常在 0 到 15 之间,具体取决于 CPU 实现)。

  • 这个 pkey 可以用于后续的 pkey_mprotect 调用。

pkey_free:

  • 将一个之前分配的 pkey 返还给内核。

  • 释放后,该 pkey 可以被后续的 pkey_alloc 调用再次分配。

  • 重要: 在调用 pkey_free 之前,应该确保没有内存区域仍然通过 pkey_mprotect 与该 pkey 关联,否则这些区域的访问权限可能会变得不可预测。

4. 参数

pkey_alloc

  • unsigned int flags: 目前必须设置为 0。保留供将来扩展。

unsigned int access_rights: 指定该 pkey 的初始访问权限。这是一个位掩码,定义在 <sys/mman.h> 中。

  • PKEY_DISABLE_ACCESS: 禁止所有访问(读、写、执行)。这是最严格的权限。

  • PKEY_DISABLE_WRITE: 禁止写入,但允许读取和执行。

  • 0: 允许所有访问(读、写、执行)。这是最宽松的权限。

  • 注意: 这个初始权限是设置在内核内部的,与 PKRU 寄存器中的权限是分开的。pkey_alloc 返回的 pkey,其在 PKRU 中的初始状态通常是允许访问的。这个 access_rights 参数更多是作为一种内核层面的标记或备用机制。

pkey_free

  • int pkey: 这是之前通过成功的 pkey_alloc 调用返回的保护键编号。

5. 返回值

pkey_alloc:

  • 成功时: 返回一个非负整数,即新分配的保护键编号(pkey)。

  • 失败时: 返回 -1,并设置 errno(例如 ENOSPC 没有可用的 pkey,EINVAL flags 或 access_rights 无效,EOPNOTSUPP 硬件不支持)。

pkey_free:

  • 成功时: 返回 0。

  • 失败时: 返回 -1,并设置 errno(例如 EINVAL pkey 无效,EOPNOTSUPP 硬件不支持)。

6. 相似函数,或关联函数

  • pkey_mprotect: 用于将一个内存区域与特定的 pkey 关联起来,是使用 pkey 进行内存保护的核心函数。

  • mprotect: 传统的内存保护函数,修改内存区域的 RWX 权限。pkey_mprotect 是其增强版,增加了 pkey 功能。

  • mmap: 用于分配和映射内存区域,这些区域后续可以用 pkey_mprotect 来关联 pkey。

  • syscall(SYS_pkey_alloc, …) / syscall(SYS_pkey_free, …): 在 glibc 不支持时,直接调用系统调用的方式。

  1. 示例代码

重要提示:

硬件和内核支持: MPK 仅在支持该特性的 CPU(如 Intel x86_64 Skylake 及更新架构)和 Linux 内核(>= 4.9)上可用。

glibc 版本: 需要 glibc 2.27 或更高版本。

复杂性: 使用 MPK 需要结合 pkey_mprotect 和对 PKRU 寄存器的操作(通常通过内联汇编或专用库函数),下面的示例主要演示 pkey_alloc/free 的基本用法。

示例 1:检查支持并基本使用 pkey_alloc/free

这个例子演示了如何检查系统是否支持 MPK,然后分配和释放保护键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// pkey_basic_example.c
#define _GNU_SOURCE // For pkey functions
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

// 如果 glibc 版本过低,可能需要手动定义系统调用号
#ifndef SYS_pkey_alloc
#define SYS_pkey_alloc 330
#endif
#ifndef SYS_pkey_free
#define SYS_pkey_free 331
#endif

// 用于手动调用系统调用的包装函数 (如果需要)
// #include <sys/syscall.h>
// static inline int my_pkey_alloc(unsigned int flags, unsigned int access_rights) {
// return syscall(SYS_pkey_alloc, flags, access_rights);
// }
// static inline int my_pkey_free(int pkey) {
// return syscall(SYS_pkey_free, pkey);
// }

// 检查系统是否支持内存保护键
int check_pkey_support() {
int pkey;
// 尝试分配一个 pkey 来检查支持
pkey = pkey_alloc(0, 0);
if (pkey == -1) {
if (errno == EOPNOTSUPP) {
return 0; // Not supported
} else {
// Other error, but might still support it in general
// Let's assume it does and let the main code handle specific errors
return 1;
}
}
// Allocation succeeded, support confirmed. Free it.
if (pkey_free(pkey) == -1) {
perror("pkey_free after check");
}
return 1;
}

int main() {
printf("Checking for Memory Protection Key (MPK) support...\n");

if (!check_pkey_support()) {
fprintf(stderr, "Memory Protection Keys (MPK) are NOT supported on this system/CPU.\n");
fprintf(stderr, "This might be because:\n");
fprintf(stderr, " 1. The CPU does not support MPK (e.g., older than Intel Skylake).\n");
fprintf(stderr, " 2. The Linux kernel version is older than 4.9.\n");
fprintf(stderr, " 3. The feature is disabled.\n");
exit(EXIT_FAILURE);
}

printf("MPK support detected.\n");

// --- 分配保护键 ---
printf("\n--- Allocating Protection Keys ---\n");

int pkey1, pkey2;

// 分配第一个 pkey,初始权限为允许所有访问
pkey1 = pkey_alloc(0, 0);
if (pkey1 == -1) {
perror("pkey_alloc 1 failed");
exit(EXIT_FAILURE);
}
printf("Successfully allocated pkey 1: %d\n", pkey1);

// 分配第二个 pkey,初始权限为禁止写入
pkey2 = pkey_alloc(0, PKEY_DISABLE_WRITE);
if (pkey2 == -1) {
perror("pkey_alloc 2 failed");
// Cleanup previously allocated pkey
pkey_free(pkey1);
exit(EXIT_FAILURE);
}
printf("Successfully allocated pkey 2: %d (initially write-disabled)\n", pkey2);

// 尝试分配更多 pkey (系统通常限制为 16 个, 0-15)
printf("\nAttempting to allocate more pkeys...\n");
int pkeys&#91;20];
int allocated_count = 0;
for (int i = 0; i < 20; i++) {
pkeys&#91;i] = pkey_alloc(0, 0);
if (pkeys&#91;i] == -1) {
if (errno == ENOSPC) {
printf(" Allocation %d failed: No space left (ENOSPC). Max pkeys reached.\n", i);
break;
} else {
printf(" Allocation %d failed with errno %d (%s)\n", i, errno, strerror(errno));
break;
}
} else {
printf(" Allocated pkey %d: %d\n", i, pkeys&#91;i]);
allocated_count++;
}
}

// --- 释放保护键 ---
printf("\n--- Freeing Protection Keys ---\n");

// 释放前两个
if (pkey_free(pkey1) == -1) {
perror("pkey_free pkey1 failed");
} else {
printf("Successfully freed pkey 1 (%d)\n", pkey1);
}

if (pkey_free(pkey2) == -1) {
perror("pkey_free pkey2 failed");
} else {
printf("Successfully freed pkey 2 (%d)\n", pkey2);
}

// 释放之前批量分配的
for (int i = 0; i < allocated_count; i++) {
if (pkey_free(pkeys&#91;i]) == -1) {
printf("Failed to free pkey %d (%d)\n", i, pkeys&#91;i]);
} else {
printf("Successfully freed pkey %d (%d)\n", i, pkeys&#91;i]);
}
}

printf("\nAll pkey operations completed.\n");
return 0;
}

如何编译和测试:

1
2
3
4
# 需要较新的 glibc (>= 2.27)
gcc -o pkey_basic_example pkey_basic_example.c
./pkey_basic_example

代码解释:

定义了必要的头文件。

check_pkey_support: 通过尝试调用 pkey_alloc 来粗略检查系统是否支持 MPK。如果返回 EOPNOTSUPP,则说明不支持。

在 main 函数中,首先调用 check_pkey_support。

分配 pkey:

  • 调用 pkey_alloc(0, 0) 分配第一个 pkey,初始权限为允许所有访问。

  • 调用 pkey_alloc(0, PKEY_DISABLE_WRITE) 分配第二个 pkey,初始权限为禁止写入。

  • 通过一个循环尝试分配更多 pkey,直到系统报告 ENOSPC(没有空间,即达到上限)。

释放 pkey:

  • 调用 pkey_free 释放之前分配的所有 pkey。

打印相关信息。

示例 2:结合 mmap, pkey_mprotect 使用 pkey_alloc/free (概念性)

这个例子展示了一个更完整的、但概念性的用法,结合了分配内存、分配 pkey、关联内存与 pkey 以及通过 PKRU 控制访问。请注意:直接操作 PKRU 寄存器需要内联汇编,这比较复杂且依赖于架构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// pkey_conceptual_example.c
#define _GNU_SOURCE
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>

static jmp_buf jmp_env;
static volatile sig_atomic_t sigsegv_caught = 0;

// 信号处理函数
void sigsegv_handler(int sig) {
sigsegv_caught = 1;
longjmp(jmp_env, 1);
}

// 概念性的 PKRU 操作函数 (实际需要内联汇编)
// 这里用伪代码表示
void write_pkru(unsigned int pkru_value) {
// In real code, this would be inline assembly like:
// asm volatile(".byte 0x0f,0x01,0xef\n\t" : : "a" (pkru_value), "d" (0), "c" (0) : "memory");
printf(" &#91;Concept] Writing PKRU with value: 0x%08x\n", pkru_value);
// WARNING: This is NOT real code, just for illustration.
// Real code needs inline assembly.
}

unsigned int read_pkru() {
// In real code:
// unsigned int pkru;
// asm volatile(".byte 0x0f,0x01,0xee\n\t" : "=a" (pkru) : "c" (0) : "rdx", "memory");
// return pkru;
printf(" &#91;Concept] Reading PKRU\n");
return 0; // Dummy return
}

int main() {
if (sysconf(_SC_MPKEY) <= 0) { // Check if supported
fprintf(stderr, "MPK not supported by sysconf.\n");
exit(EXIT_FAILURE);
}

struct sigaction sa;
sa.sa_handler = sigsegv_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGSEGV, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}

size_t len = 4096; // 1 page
void *addr;

// 1. 分配内存
addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
printf("Allocated memory at %p\n", addr);

// 2. 写入一些数据
strcpy((char*)addr, "Initial data in protected memory.");
printf("Written initial data.\n");

// 3. 分配一个 pkey
int pkey = pkey_alloc(0, 0);
if (pkey == -1) {
perror("pkey_alloc");
munmap(addr, len);
exit(EXIT_FAILURE);
}
printf("Allocated pkey: %d\n", pkey);

// 4. 将内存区域与 pkey 关联 (概念性)
// 实际需要调用 pkey_mprotect(addr, len, PROT_READ|PROT_WRITE, pkey);
printf("--- Conceptually associating memory with pkey %d ---\n", pkey);
// printf("Would call: pkey_mprotect(%p, %zu, PROT_READ|PROT_WRITE, %d)\n", addr, len, pkey);

// 5. 通过修改 PKRU 来禁止访问 pkey (概念性)
printf("\n--- Disabling access to pkey %d via PKRU ---\n", pkey);
// 计算新的 PKRU 值以禁用 pkey 的访问
// 每个 pkey 在 PKRU 中占 2 位: 00(allow), 01(deny access), 10(deny write), 11(deny access)
// 假设 pkey 是 1, 那么它在 PKRU 的 bit 2-3
// unsigned int current_pkru = read_pkru();
unsigned int new_pkru = 0; // Start with allowing all
// Set bits for pkey to 01 (deny access)
// new_pkru |= (1 << (pkey * 2));
// write_pkru(new_pkru);
printf(" &#91;Concept] Would set PKRU bits for pkey %d to deny access.\n", pkey);

// 6. 尝试访问受保护的内存 (应该触发 SIGSEGV)
printf("\n--- Attempting to access protected memory ---\n");
sigsegv_caught = 0;

if (setjmp(jmp_env) == 0) {
// This block will be executed first
printf(" Trying to read from %p...\n", addr);
char first_char = *((char*)addr); // This should trigger SIGSEGV
printf(" ERROR: Read succeeded (first char: %c). This should not happen!\n", first_char);
} else {
// This block will be executed if longjmp is called from signal handler
if (sigsegv_caught) {
printf(" SUCCESS: SIGSEGV caught as expected. Access denied by pkey.\n");
} else {
printf(" Unexpected longjmp.\n");
}
}

// 7. 重新允许访问
printf("\n--- Re-enabling access to pkey %d via PKRU ---\n", pkey);
// write_pkru(0); // Allow all access again
printf(" &#91;Concept] Would reset PKRU to allow all access.\n");

// 8. 再次尝试访问 (应该成功)
printf("\n--- Attempting to access memory again (should succeed now) ---\n");
printf(" Reading from %p: %s\n", addr, (char*)addr);

// 9. 清理
if (pkey_free(pkey) == -1) {
perror("pkey_free");
}
if (munmap(addr, len) == -1) {
perror("munmap");
}

printf("\nConceptual pkey example finished.\n");
return 0;
}

**代码解释 **(概念性):

设置了 SIGSEGV 信号处理函数和 setjmp/longjmp 机制来捕获预期的段错误。

使用 mmap 分配了一块匿名内存。

向内存写入了初始数据。

调用 pkey_alloc(0, 0) 分配一个 pkey。

概念性步骤: 描述了将内存与 pkey 关联(实际需要 pkey_mprotect)和通过修改 PKRU 寄存器禁止访问的操作。这部分用 printf 和伪函数 write_pkru/read_pkru 代替,因为真实的实现需要内联汇编。

尝试读取受保护的内存。在真实场景下,这会触发 SIGSEGV,信号处理函数会设置标志并 longjmp 回来。

概念性步骤: 描述了重新允许访问(重置 PKRU)。

再次尝试访问,这次应该成功。

释放 pkey 和内存。

重要提示与注意事项:

硬件和内核依赖: MPK 是 x86_64 架构(Intel Skylake 及更新)的特性,需要 Linux 内核 4.9+。

glibc 版本: 需要 glibc 2.27+ 才有原生支持。

复杂性: 真正使用 MPK 需要结合 pkey_mprotect 和对 PKRU 寄存器的精确控制(通常通过内联汇编),这比示例中展示的要复杂得多。

pkey_mprotect 是关键: pkey_alloc/free 只是管理 pkey 编号,真正将内存和权限联系起来的是 pkey_mprotect。

PKRU 操作: 直接读写 PKRU 寄存器是使用 MPK 功能的核心,但需要内联汇编知识。

错误处理: 始终检查 pkey_alloc 是否返回 EOPNOTSUPP(不支持)或 ENOSPC(pkey 耗尽)。

性能优势: MPK 的主要优势在于权限切换的极低延迟,因为它避免了修改页表的开销。

应用场景: 适用于需要快速、动态地改变大量内存区域访问权限的场景,如沙箱、内存安全库、调试器等。

总结:

pkey_alloc 和 pkey_free 是 Linux 内存保护键(MPK)机制的一部分,用于分配和回收独立的内存访问控制键。它们本身只是 pkey 生命周期管理的第一步。要真正利用 MPK 提供的快速、细粒度内存保护能力,还需要结合 pkey_mprotect 来关联内存区域,以及通过直接操作 PKRU 寄存器来动态启用或禁用访问权限。虽然使用起来比较底层和复杂,但对于需要极致内存安全和性能控制的应用来说,MPK 是一个强大的工具。

pkey_mprotect系统调用及示例

pkey_mprotect系统调用及示例

我们来深入学习 pkey_mprotect 系统调用

相关文章:pkey_mprotect系统调用及示例-CSDN博客 pkey_mprotect系统调用及示例 pkey_alloc pkey_free系统调用及示例 preadv2系统调用及示例

1. 函数介绍

在 Linux 系统中,内存保护是一个核心的安全机制。我们使用 mprotect 系统调用(或者 C 标准库的 mprotect 函数)来设置一块内存区域的访问权限,比如:

  • 只读 (PROT_READ)

  • 可写 (PROT_WRITE)

  • 可执行 (PROT_EXEC)

例如,你可以将包含程序代码的内存页设置为“只读可执行”,防止程序意外修改自己的代码;或者将包含数据的内存页设置为“只读”,防止意外写入。

然而,mprotect 有一个限制:整个进程都遵循同一套内存保护规则。如果一个进程有权限修改某块内存的保护属性(通常需要特殊权限),它就可以修改任何内存页的权限。

Memory Protection Keys (MPK) 是 Intel 和 ARM 等 CPU 架构引入的一种更细粒度的内存保护机制。它允许将内存区域与一个密钥 (Protection Key) 关联起来。这个密钥就像一把锁,控制着与之关联的内存区域是否可以被访问。

pkey_mprotect 系统调用就是用来在设置内存区域访问权限的同时,将这块内存与一个特定的保护密钥 (pkey) 关联起来。

简单来说:

  • mprotect(addr, len, prot): 设置 addr 开始的 len 字节内存的权限为 prot。

  • pkey_mprotect(addr, len, prot, pkey): 做同样的事,外加将这块内存和密钥 pkey 绑定。

要访问一块由 pkey_mprotect 保护的内存,不仅需要满足 prot 指定的权限(如 PROT_READ),当前线程的密钥权限掩码 (PKRU) 中对应的 pkey 也必须允许这种访问。

这提供了一种额外的、硬件加速的、线程级的内存访问控制。即使程序通过 mprotect 获得了写权限,如果线程的 pkey 设置禁止写入,访问仍然会失败。

典型应用场景:

  • 沙箱/安全容器:为不同来源或信任级别的代码分配不同的 pkey,防止它们互相干扰或越权访问。

  • 调试器/分析器:保护关键数据结构不被被调试的程序意外修改。

  • 高性能库:在复杂的内存管理库中,使用 pkey 来防止用户代码错误地访问库的内部数据。

2. 函数原型

1
2
3
4
5
#define _GNU_SOURCE // 启用 GNU 扩展以使用 pkey_mprotect
#include <sys/mman.h> // 包含 pkey_mprotect 函数声明

int pkey_mprotect(void *addr, size_t len, int prot, int pkey);

注意:此函数需要 glibc 2.27 或更高版本,并且运行在支持 Memory Protection Keys 的 CPU 上(如 Intel Haswell 及更新的处理器,或支持相应特性的 ARM 处理器)。

3. 功能

为从 addr 开始、长度为 len 字节的内存区域设置访问权限 (prot),并将其与保护密钥 (pkey) 关联。

4. 参数

addr:

  • void * 类型。

  • 指向要修改保护属性的内存区域的起始地址。这个地址必须是页对齐的(通常是 4KB 边界)。

len:

  • size_t 类型。

  • 要修改保护属性的内存区域的长度(字节数)。

prot:

  • int 类型。

指定要设置的内存访问权限。可以是以下值的按位或 (|) 组合:

  • PROT_NONE: 内存无法访问。

  • PROT_READ: 页面可读。

  • PROT_WRITE: 页面可写。

  • PROT_EXEC: 页面可执行。

pkey:

  • int 类型。

  • 指定要与该内存区域关联的保护密钥。

  • 有效的 pkey 值是 0 到 PKEY_MAX (通常是 15) 之间的整数。

  • pkey 为 -1 时,表示不改变该内存区域当前关联的 pkey(如果有的话)。

  • 特殊的 pkey 值 PKEY_DISABLE_ACCESS 和 PKEY_DISABLE_WRITE 可用于 pkey_set 函数,而不是 pkey_mprotect。

5. 返回值

  • 成功: 返回 0。

  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

pkey_mprotect 可能返回的错误码与 mprotect 相同或类似,并增加了一些与 pkey 相关的:

  • EINVAL: addr 不是页对齐的,或者 prot 或 pkey 参数无效。

  • ENOMEM: 地址范围 (addr to addr+len) 无效,或者包含了未映射的页面。

  • EFAULT: addr 指向了调用进程无法访问的内存地址。

  • EACCES: 调用进程没有权限修改指定内存区域的保护属性。

  • ENOSYS: 系统调用不被当前内核或硬件支持(例如,CPU 不支持 MPK)。

7. 相似函数或关联函数

  • mprotect: 不使用保护密钥的标准内存保护函数。

  • pkey_alloc: 分配一个新的保护密钥。

  • pkey_free: 释放一个之前分配的保护密钥。

  • pkey_get: 获取当前线程的密钥权限掩码 (PKRU) 的值。

  • pkey_set: 设置当前线程的密钥权限掩码 (PKRU) 的值。

  • mmap: 用于分配和映射内存区域。

  • sysconf(_SC_PAGESIZE): 获取系统页大小,用于确保地址对齐。

8. 示例代码

下面的示例演示了如何使用 pkey_mprotect 来保护内存区域。请注意,此代码需要在支持 MPK 的硬件和内核上运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

// 全局变量用于信号处理
volatile sig_atomic_t segv_caught = 0;

// 信号处理函数,捕获 SIGSEGV
void sigsegv_handler(int sig) {
segv_caught = 1;
write(STDOUT_FILENO, "Caught SIGSEGV (Segmentation Fault)!\n", 38);
}

int main() {
char *buffer;
int pkey, pkru_orig;
size_t page_size = sysconf(_SC_PAGESIZE);
struct sigaction sa;

printf("--- Demonstrating pkey_mprotect ---\n");
printf("Page size: %zu bytes\n", page_size);

// 1. 检查系统是否支持 pkey
// 尝试分配一个 pkey 来测试支持
pkey = pkey_alloc(0, 0);
if (pkey == -1) {
if (errno == ENOSYS) {
printf("Error: Memory Protection Keys (pkey) are not supported on this system/CPU.\n");
printf("This example requires MPK support (e.g., Intel Haswell+).\n");
exit(EXIT_FAILURE);
} else {
perror("pkey_alloc");
exit(EXIT_FAILURE);
}
}
printf("1. Allocated a protection key: %d\n", pkey);

// 2. 分配一块内存 (使用 mmap 以确保页对齐)
buffer = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (buffer == MAP_FAILED) {
perror("mmap");
pkey_free(pkey);
exit(EXIT_FAILURE);
}
printf("2. Allocated %zu bytes of memory at %p using mmap.\n", page_size, buffer);

// 3. 初始化内存
strcpy(buffer, "Initial data in protected memory.");
printf("3. Initialized memory: '%s'\n", buffer);

// 4. 设置信号处理函数来捕获段错误
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sigsegv_handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGSEGV, &sa, NULL) == -1) {
perror("sigaction");
munmap(buffer, page_size);
pkey_free(pkey);
exit(EXIT_FAILURE);
}

// 5. 使用 pkey_mprotect 设置权限并关联 pkey
// 设置为只读,并与 pkey 关联
printf("4. Calling pkey_mprotect to set memory to READ-ONLY and associate with pkey %d...\n", pkey);
if (pkey_mprotect(buffer, page_size, PROT_READ, pkey) == -1) {
perror("pkey_mprotect");
munmap(buffer, page_size);
pkey_free(pkey);
exit(EXIT_FAILURE);
}
printf(" pkey_mprotect succeeded.\n");

// 6. 尝试读取 (应该成功)
printf("5. Attempting to READ from protected memory...\n");
segv_caught = 0;
printf(" Data read: '%s'\n", buffer);
if (!segv_caught) {
printf(" Read successful.\n");
} else {
printf(" Unexpected SIGSEGV on read!\n");
}

// 7. 尝试写入 (应该失败并触发 SIGSEGV)
printf("6. Attempting to WRITE to READ-ONLY protected memory...\n");
segv_caught = 0;
buffer&#91;0] = 'X'; // 尝试修改
// 如果没有触发 SIGSEGV,说明可能没有保护生效(例如 pkey 允许写)
if (segv_caught) {
printf(" Write failed as expected (SIGSEGV caught).\n");
} else {
printf(" Write succeeded (unexpected, check pkey settings or hardware support).\n");
}

// 8. 修改线程的 pkey 权限掩码 (PKRU) 来允许写入
printf("7. Getting current PKRU register value...\n");
pkru_orig = pkey_get();
if (pkru_orig == -1) {
perror("pkey_get");
} else {
printf(" Current PKRU value: 0x%08x\n", pkru_orig);
// 计算新的 PKRU 值,允许 pkey 的读写
// 每个 pkey 占用 2 位:
// Bits 0-1: pkey 0 (AD=Allow Disable access, WD=Write Disable)
// Bits 2-3: pkey 1
// ...
// Bits 30-31: pkey 15
// AD=0, WD=0 意味着允许访问和写入
// AD=1, WD=0 意味着禁止访问
// AD=0, WD=1 意味着允许访问但禁止写入
// AD=1, WD=1 意味着禁止访问
// 假设我们的 pkey 是 1 (这只是示例,实际 pkey 号可能不同)
// 我们需要将 pkey 1 的 AD 和 WD 位都设为 0
// pkey 1 的位是 2-3
int new_pkru = pkru_orig & ~(3 << (pkey * 2)); // 清除 pkey 的两位
printf(" Setting PKRU to allow RW for pkey %d. New PKRU value: 0x%08x\n", pkey, new_pkru);
if (pkey_set(new_pkru) == -1) {
perror("pkey_set");
}
}

// 9. 再次尝试写入 (现在应该成功,因为 pkey 允许了)
printf("8. Attempting to WRITE again after modifying PKRU...\n");
segv_caught = 0;
buffer&#91;0] = 'Y'; // 尝试修改
if (segv_caught) {
printf(" Write failed (SIGSEGV caught), even after PKRU change.\n");
printf(" This might indicate the pkey is not correctly associated or hardware issue.\n");
} else {
printf(" Write succeeded after PKRU change.\n");
printf(" Data is now: '%s'\n", buffer);
}

// 10. 清理资源
printf("\n--- Cleaning up ---\n");
// 恢复原始的 PKRU 值 (好习惯)
if (pkey_set(pkru_orig) == -1) {
perror("pkey_set (restore)");
} else {
printf("Restored original PKRU value.\n");
}

if (munmap(buffer, page_size) == -1) {
perror("munmap");
} else {
printf("Unmapped memory.\n");
}

if (pkey_free(pkey) == -1) {
perror("pkey_free");
} else {
printf("Freed protection key %d.\n", pkey);
}

printf("\n--- Summary ---\n");
printf("1. pkey_mprotect(addr, len, prot, pkey) sets memory permissions AND associates it with a pkey.\n");
printf("2. Access requires BOTH standard permissions (prot) AND pkey permission (via PKRU register).\n");
printf("3. pkey_alloc() gets a new key, pkey_free() releases it.\n");
printf("4. pkey_get() reads PKRU, pkey_set() writes PKRU to control access per thread.\n");
printf("5. It provides fine-grained, hardware-accelerated memory access control.\n");
printf("6. Requires CPU and kernel support for Memory Protection Keys (MPK).\n");

return 0;
}

9. 编译和运行

1
2
3
4
5
6
7
8
# 假设代码保存在 pkey_mprotect_example.c 中
# 需要较新的 glibc (>= 2.27) 和支持 MPK 的 CPU
gcc -o pkey_mprotect_example pkey_mprotect_example.c

# 运行程序
# 注意:如果系统不支持 MPK,程序会在开始时就退出并提示。
./pkey_mprotect_example

10. 预期输出 (在支持 MPK 的系统上)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
--- Demonstrating pkey_mprotect ---
Page size: 4096 bytes
1. Allocated a protection key: 1
2. Allocated 4096 bytes of memory at 0x7f8b8c000000 using mmap.
3. Initialized memory: 'Initial data in protected memory.'
4. Calling pkey_mprotect to set memory to READ-ONLY and associate with pkey 1...
pkey_mprotect succeeded.
5. Attempting to READ from protected memory...
Data read: 'Initial data in protected memory.'
Read successful.
6. Attempting to WRITE to READ-ONLY protected memory...
Caught SIGSEGV (Segmentation Fault)!
Write failed as expected (SIGSEGV caught).
7. Getting current PKRU register value...
Current PKRU value: 0x55555555
Setting PKRU to allow RW for pkey 1. New PKRU value: 0x55555551
8. Attempting to WRITE again after modifying PKRU...
Write succeeded after PKRU change.
Data is now: 'Ynitial data in protected memory.'

--- Cleaning up ---
Restored original PKRU value.
Unmapped memory.
Freed protection key 1.

--- Summary ---
1. pkey_mprotect(addr, len, prot, pkey) sets memory permissions AND associates it with a pkey.
2. Access requires BOTH standard permissions (prot) AND pkey permission (via PKRU register).
3. pkey_alloc() gets a new key, pkey_free() releases it.
4. pkey_get() reads PKRU, pkey_set() writes PKRU to control access per thread.
5. It provides fine-grained, hardware-accelerated memory access control.
6. Requires CPU and kernel support for Memory Protection Keys (MPK).

在不支持 MPK 的系统上的输出:

1
2
3
4
5
--- Demonstrating pkey_mprotect ---
Page size: 4096 bytes
Error: Memory Protection Keys (pkey) are not supported on this system/CPU.
This example requires MPK support (e.g., Intel Haswell+).

11. 总结

pkey_mprotect 是一个利用现代 CPU 硬件特性(Memory Protection Keys)来提供更精细内存访问控制的系统调用。

核心优势:

  • 额外保护层:在传统的 mprotect 权限之上增加了一层由硬件支持的、基于密钥的访问控制。

  • 线程级控制:每个线程可以通过 pkey_set 独立控制自己对不同 pkey 保护区域的访问权限。

  • 高性能:由 CPU 硬件直接处理,检查开销极小。

工作流程:

使用 pkey_alloc 获取一个 pkey。

使用 pkey_mprotect 将内存区域与 pkey 和访问权限 (prot) 关联。

使用 pkey_get 和 pkey_set 控制当前线程的 PKRU 寄存器,决定哪些 pkey 允许访问/写入。

当线程尝试访问内存时,CPU 会同时检查 mprotect 权限和 PKRU 中对应的 pkey 权限。

使用前提:

  • CPU 支持 MPK (如 Intel RDPID + Protection Keys for User-mode pages)。

  • Linux 内核支持 (通常 4.9+)。

  • glibc 版本足够新 (2.27+)。

典型应用:沙箱、安全容器、调试器、高性能库。

对于 Linux 编程新手来说,pkey_mprotect 是一个高级特性,理解其概念和潜在用途有助于学习现代系统安全和内存管理的前沿技术。但在日常开发中,mprotect 仍然是管理内存权限的主要工具。