程序员的自我修养

链接、装载与库

程序员的自我修养:链接、装载与库 - 阅读笔记

书籍信息

  • 书名:程序员的自我修养:链接、装载与库
  • 作者:俞甲子、石凡、潘爱民
  • 出版社:电子工业出版社
  • 出版年份:2009年

内容简介

本书主要介绍了程序在编译、链接、装载过程中的原理和实现机制,是理解计算机系统底层运行机制的重要参考书籍。

阅读笔记

第一章:温故而知新

1.1 从 Hello World 说起

  • 程序从源代码到可执行文件的完整过程
  • 编译、链接、装载的基本概念

1.2 万变不离其宗

  • 计算机系统的基本组成
  • 程序运行的基本原理

第二章:编译和链接

2.1 被隐藏了的过程

  • 预编译
  • 编译
  • 汇编
  • 链接

2.2 编译器做了什么

  • 词法分析
  • 语法分析
  • 语义分析
  • 中间代码生成
  • 目标代码生成

第三章:目标文件里有什么

3.1 目标文件的格式

  • ELF 文件格式
  • 段的概念
  • 符号表

3.2 链接的接口——符号

  • 符号的定义与引用
  • 符号表
  • 符号修饰与函数签名

第四章:静态链接

4.1 空间与地址分配

  • 空间分配
  • 地址分配
  • 符号解析

4.2 符号解析与重定位

  • 符号解析
  • 重定位
  • 重定位表

第五章:动态链接

5.1 为什么要动态链接

  • 静态链接的缺点
  • 动态链接的优势

5.2 简单的动态链接例子

  • 动态链接的基本过程
  • 动态链接器的工作流程

个人感悟

这本书深入浅出地讲解了程序从源代码到可执行文件的完整过程,对于理解计算机系统底层运行机制非常有帮助。通过阅读本书,我对编译、链接、装载等过程有了更深入的理解。

推荐指数

⭐⭐⭐⭐⭐

适合人群

  • 想要深入理解计算机系统底层原理的程序员
  • 对编译原理感兴趣的读者
  • 系统程序员和底层开发者

延伸阅读

  1. 《深入理解计算机系统》
  2. 《编译原理》(龙书)
  3. 《程序员的自我修养:链接、装载与库》第二版

第十三章:调试

13.1 调试的基本概念

调试的作用

调试的主要功能:

  1. 定位错误
  2. 分析问题
  3. 验证修复
  4. 性能优化

示例代码:

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
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

// 调试宏
#define DEBUG_PRINT(fmt, ...) \
fprintf(stderr, "DEBUG: " fmt "\n", ##__VA_ARGS__)

int main() {
int x = 10;
int y = 0;

// 使用断言
assert(x > 0);

// 使用调试宏
DEBUG_PRINT("x = %d, y = %d", x, y);

// 检查除零错误
if (y == 0) {
DEBUG_PRINT("Division by zero!");
return 1;
}

int result = x / y;
printf("Result: %d\n", result);

return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译(启用调试信息)
gcc -g debug_basic.c -o debug_basic

# 运行
./debug_basic

调试工具

常用的调试工具:

  1. GDB
  2. Valgrind
  3. strace
  4. ltrace

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
// 内存分配
char *str = malloc(10);
if (!str) return 1;

// 字符串操作
strcpy(str, "Hello");
printf("String: %s\n", str);

// 内存泄漏
// str = NULL; // 注释掉这行会导致内存泄漏

// 使用已释放的内存
free(str);
// printf("String: %s\n", str); // 注释掉这行会导致段错误

return 0;
}

调试命令:

1
2
3
4
5
6
7
8
9
10
11
# 使用GDB调试
gdb ./debug_tools

# 使用Valgrind检查内存问题
valgrind --leak-check=full ./debug_tools

# 使用strace跟踪系统调用
strace ./debug_tools

# 使用ltrace跟踪库调用
ltrace ./debug_tools

13.2 调试技术

断点调试

断点调试的方法:

  1. 设置断点
  2. 单步执行
  3. 查看变量
  4. 修改变量

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <stdlib.h>

int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}

int main() {
int n = 5;
int result = factorial(n);
printf("Factorial of %d is %d\n", n, result);
return 0;
}

GDB调试命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 启动GDB
gdb ./breakpoint

# 设置断点
(gdb) break factorial
(gdb) break main

# 运行程序
(gdb) run

# 单步执行
(gdb) next
(gdb) step

# 查看变量
(gdb) print n
(gdb) print result

# 继续执行
(gdb) continue

内存调试

内存调试的方法:

  1. 内存泄漏检测
  2. 越界访问检测
  3. 使用已释放内存检测
  4. 内存对齐检查

示例代码:

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
// 内存泄漏
char *leak = malloc(10);
strcpy(leak, "leak");

// 越界访问
char *overflow = malloc(5);
strcpy(overflow, "overflow");

// 使用已释放的内存
char *use_after_free = malloc(10);
strcpy(use_after_free, "test");
free(use_after_free);
printf("%s\n", use_after_free);

// 内存对齐问题
char *unaligned = malloc(1);
int *ptr = (int *)(unaligned + 1);
*ptr = 42;

return 0;
}

Valgrind调试命令:

1
2
3
4
5
6
7
8
# 检查内存泄漏
valgrind --leak-check=full ./memory_debug

# 检查内存错误
valgrind --tool=memcheck ./memory_debug

# 检查堆使用情况
valgrind --tool=massif ./memory_debug

13.3 性能调试

性能分析

性能分析的方法:

  1. 时间分析
  2. 内存分析
  3. CPU分析
  4. I/O分析

示例代码:

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
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define SIZE 1000000

void slow_function() {
int *arr = malloc(SIZE * sizeof(int));
for (int i = 0; i < SIZE; i++) {
arr[i] = i;
}
free(arr);
}

void fast_function() {
int *arr = malloc(SIZE * sizeof(int));
for (int i = 0; i < SIZE; i += 16) {
arr[i] = i;
}
free(arr);
}

int main() {
clock_t start, end;

// 测试慢函数
start = clock();
slow_function();
end = clock();
printf("Slow function time: %f seconds\n",
(double)(end - start) / CLOCKS_PER_SEC);

// 测试快函数
start = clock();
fast_function();
end = clock();
printf("Fast function time: %f seconds\n",
(double)(end - start) / CLOCKS_PER_SEC);

return 0;
}

性能分析命令:

1
2
3
4
5
6
7
8
9
10
11
# 使用gprof分析
gcc -pg performance.c -o performance
./performance
gprof performance gmon.out

# 使用perf分析
perf record ./performance
perf report

# 使用time命令
time ./performance

性能优化

性能优化的方法:

  1. 算法优化
  2. 内存优化
  3. 缓存优化
  4. 并行优化

示例代码:

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 未优化的版本
void unoptimized_copy(char *dst, const char *src, size_t n) {
for (size_t i = 0; i < n; i++) {
dst[i] = src[i];
}
}

// 优化的版本
void optimized_copy(char *dst, const char *src, size_t n) {
// 使用内存对齐
size_t i = 0;
while (i < n && ((uintptr_t)(dst + i) & 7) != 0) {
dst[i] = src[i];
i++;
}

// 使用64位复制
while (i + 8 <= n) {
*(uint64_t *)(dst + i) = *(const uint64_t *)(src + i);
i += 8;
}

// 处理剩余字节
while (i < n) {
dst[i] = src[i];
i++;
}
}

int main() {
const size_t size = 1024 * 1024;
char *src = malloc(size);
char *dst1 = malloc(size);
char *dst2 = malloc(size);

// 初始化源数据
memset(src, 'A', size);

// 测试未优化的版本
unoptimized_copy(dst1, src, size);

// 测试优化的版本
optimized_copy(dst2, src, size);

free(src);
free(dst1);
free(dst2);

return 0;
}

实践练习

  1. 基本调试实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 创建测试程序
cat > debug_test.c << EOL
#include <stdio.h>
#include <stdlib.h>

int main() {
int *ptr = NULL;
*ptr = 42; // 段错误
return 0;
}
EOL

# 编译和调试
gcc -g debug_test.c -o debug_test
gdb ./debug_test
  1. 内存调试实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 创建内存测试程序
cat > memory_test.c << EOL
#include <stdio.h>
#include <stdlib.h>

int main() {
char *ptr = malloc(10);
ptr[10] = 'A'; // 越界访问
free(ptr);
return 0;
}
EOL

# 编译和检查
gcc memory_test.c -o memory_test
valgrind ./memory_test
  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
# 创建性能测试程序
cat > perf_test.c << EOL
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
clock_t start = clock();

// 执行一些操作
int *arr = malloc(1000000 * sizeof(int));
for (int i = 0; i < 1000000; i++) {
arr[i] = i;
}
free(arr);

clock_t end = clock();
printf("Time: %f seconds\n",
(double)(end - start) / CLOCKS_PER_SEC);
return 0;
}
EOL

# 编译和分析
gcc -pg perf_test.c -o perf_test
./perf_test
gprof perf_test gmon.out

思考题

  1. 调试的主要功能是什么?
  2. 常用的调试工具有哪些?
  3. 如何进行内存调试?
  4. 性能调试的方法有哪些?
  5. 如何优化程序性能?

参考资料

  1. 《程序员的自我修养:链接、装载与库》
  2. GDB 文档
  3. Valgrind 文档
  4. System V ABI
  5. ELF 文件格式规范

第十二章:线程库

12.1 线程的基本概念

线程的作用

线程的主要功能:

  1. 并发执行
  2. 资源共享
  3. 提高响应性
  4. 简化编程模型

示例代码:

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
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// 线程函数
void *thread_func(void *arg) {
int id = *(int *)arg;
printf("Thread %d is running\n", id);
sleep(1);
printf("Thread %d is done\n", id);
return NULL;
}

int main() {
pthread_t threads[3];
int thread_ids[3];

// 创建线程
for (int i = 0; i < 3; i++) {
thread_ids[i] = i;
pthread_create(&threads[i], NULL, thread_func, &thread_ids[i]);
}

// 等待线程结束
for (int i = 0; i < 3; i++) {
pthread_join(threads[i], NULL);
}

printf("All threads completed\n");
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc thread_basic.c -pthread -o thread_basic

# 运行
./thread_basic

线程的实现

线程的实现方式:

  1. 用户级线程
  2. 内核级线程
  3. 混合实现
  4. 线程调度

示例代码:

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
#include <stdio.h>
#include <pthread.h>
#include <sched.h>

// 线程函数
void *thread_func(void *arg) {
// 获取线程ID
pthread_t tid = pthread_self();
printf("Thread ID: %lu\n", (unsigned long)tid);

// 获取调度策略
int policy;
struct sched_param param;
pthread_getschedparam(pthread_self(), &policy, &param);

printf("Scheduling policy: %d\n", policy);
printf("Scheduling priority: %d\n", param.sched_priority);

return NULL;
}

int main() {
pthread_t thread;

// 创建线程
pthread_create(&thread, NULL, thread_func, NULL);

// 等待线程结束
pthread_join(thread, NULL);

return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc thread_impl.c -pthread -o thread_impl

# 运行
./thread_impl

12.2 线程同步

互斥锁

互斥锁的使用:

  1. 初始化锁
  2. 加锁
  3. 解锁
  4. 销毁锁

示例代码:

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
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// 共享资源
int counter = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// 线程函数
void *thread_func(void *arg) {
for (int i = 0; i < 1000000; i++) {
// 加锁
pthread_mutex_lock(&mutex);

// 访问共享资源
counter++;

// 解锁
pthread_mutex_unlock(&mutex);
}
return NULL;
}

int main() {
pthread_t threads[2];

// 创建线程
pthread_create(&threads[0], NULL, thread_func, NULL);
pthread_create(&threads[1], NULL, thread_func, NULL);

// 等待线程结束
pthread_join(threads[0], NULL);
pthread_join(threads[1], NULL);

printf("Final counter value: %d\n", counter);
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc mutex.c -pthread -o mutex

# 运行
./mutex

条件变量

条件变量的使用:

  1. 初始化条件变量
  2. 等待条件
  3. 发送信号
  4. 销毁条件变量

示例代码:

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
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// 共享资源
int data = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// 生产者线程
void *producer(void *arg) {
for (int i = 0; i < 5; i++) {
pthread_mutex_lock(&mutex);

// 生产数据
data = i + 1;
printf("Produced: %d\n", data);

// 通知消费者
pthread_cond_signal(&cond);

pthread_mutex_unlock(&mutex);
sleep(1);
}
return NULL;
}

// 消费者线程
void *consumer(void *arg) {
for (int i = 0; i < 5; i++) {
pthread_mutex_lock(&mutex);

// 等待数据
while (data == 0) {
pthread_cond_wait(&cond, &mutex);
}

// 消费数据
printf("Consumed: %d\n", data);
data = 0;

pthread_mutex_unlock(&mutex);
}
return NULL;
}

int main() {
pthread_t prod, cons;

// 创建线程
pthread_create(&prod, NULL, producer, NULL);
pthread_create(&cons, NULL, consumer, NULL);

// 等待线程结束
pthread_join(prod, NULL);
pthread_join(cons, NULL);

return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc cond_var.c -pthread -o cond_var

# 运行
./cond_var

12.3 线程池

线程池的实现

线程池的主要组件:

  1. 任务队列
  2. 工作线程
  3. 线程管理
  4. 任务调度

示例代码:

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
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

#define THREAD_COUNT 4
#define QUEUE_SIZE 10

// 任务结构
typedef struct {
void (*func)(void *);
void *arg;
} task_t;

// 线程池结构
typedef struct {
pthread_t threads[THREAD_COUNT];
task_t queue[QUEUE_SIZE];
int queue_size;
int queue_head;
int queue_tail;
pthread_mutex_t mutex;
pthread_cond_t cond;
int shutdown;
} thread_pool_t;

// 初始化线程池
thread_pool_t *pool_init() {
thread_pool_t *pool = malloc(sizeof(thread_pool_t));
if (!pool) return NULL;

pool->queue_size = 0;
pool->queue_head = 0;
pool->queue_tail = 0;
pool->shutdown = 0;

pthread_mutex_init(&pool->mutex, NULL);
pthread_cond_init(&pool->cond, NULL);

// 创建工作线程
for (int i = 0; i < THREAD_COUNT; i++) {
pthread_create(&pool->threads[i], NULL, worker, pool);
}

return pool;
}

// 工作线程函数
void *worker(void *arg) {
thread_pool_t *pool = arg;

while (1) {
pthread_mutex_lock(&pool->mutex);

// 等待任务
while (pool->queue_size == 0 && !pool->shutdown) {
pthread_cond_wait(&pool->cond, &pool->mutex);
}

if (pool->shutdown) {
pthread_mutex_unlock(&pool->mutex);
break;
}

// 获取任务
task_t task = pool->queue[pool->queue_head];
pool->queue_head = (pool->queue_head + 1) % QUEUE_SIZE;
pool->queue_size--;

pthread_mutex_unlock(&pool->mutex);

// 执行任务
task.func(task.arg);
}

return NULL;
}

// 添加任务
void pool_add_task(thread_pool_t *pool, void (*func)(void *), void *arg) {
pthread_mutex_lock(&pool->mutex);

// 等待队列有空间
while (pool->queue_size == QUEUE_SIZE) {
pthread_cond_wait(&pool->cond, &pool->mutex);
}

// 添加任务
pool->queue[pool->queue_tail].func = func;
pool->queue[pool->queue_tail].arg = arg;
pool->queue_tail = (pool->queue_tail + 1) % QUEUE_SIZE;
pool->queue_size++;

pthread_cond_signal(&pool->cond);
pthread_mutex_unlock(&pool->mutex);
}

// 销毁线程池
void pool_destroy(thread_pool_t *pool) {
pthread_mutex_lock(&pool->mutex);
pool->shutdown = 1;
pthread_cond_broadcast(&pool->cond);
pthread_mutex_unlock(&pool->mutex);

// 等待所有线程结束
for (int i = 0; i < THREAD_COUNT; i++) {
pthread_join(pool->threads[i], NULL);
}

pthread_mutex_destroy(&pool->mutex);
pthread_cond_destroy(&pool->cond);
free(pool);
}

线程池的使用

线程池的使用方法:

  1. 创建线程池
  2. 添加任务
  3. 等待任务完成
  4. 销毁线程池

示例代码:

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
#include <stdio.h>
#include <unistd.h>

// 任务函数
void task_func(void *arg) {
int id = *(int *)arg;
printf("Task %d is running\n", id);
sleep(1);
printf("Task %d is done\n", id);
}

int main() {
// 创建线程池
thread_pool_t *pool = pool_init();
if (!pool) return 1;

// 添加任务
int task_ids[10];
for (int i = 0; i < 10; i++) {
task_ids[i] = i;
pool_add_task(pool, task_func, &task_ids[i]);
}

// 等待一段时间
sleep(5);

// 销毁线程池
pool_destroy(pool);

return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc pool_usage.c thread_pool.c -pthread -o pool_usage

# 运行
./pool_usage

实践练习

  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
# 创建测试程序
cat > thread_test.c << EOL
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void *thread_func(void *arg) {
printf("Thread is running\n");
sleep(1);
printf("Thread is done\n");
return NULL;
}

int main() {
pthread_t thread;
pthread_create(&thread, NULL, thread_func, NULL);
pthread_join(thread, NULL);
return 0;
}
EOL

# 编译和运行
gcc thread_test.c -pthread -o thread_test
./thread_test
  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
# 创建同步测试程序
cat > sync_test.c << EOL
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int counter = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *thread_func(void *arg) {
pthread_mutex_lock(&mutex);
counter++;
printf("Counter: %d\n", counter);
pthread_mutex_unlock(&mutex);
return NULL;
}

int main() {
pthread_t threads[3];
for (int i = 0; i < 3; i++) {
pthread_create(&threads[i], NULL, thread_func, NULL);
}
for (int i = 0; i < 3; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
EOL

# 编译和运行
gcc sync_test.c -pthread -o sync_test
./sync_test
  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
# 创建线程池测试程序
cat > pool_test.c << EOL
#include <stdio.h>
#include <unistd.h>
#include "thread_pool.c"

void task_func(void *arg) {
int id = *(int *)arg;
printf("Task %d is running\n", id);
sleep(1);
printf("Task %d is done\n", id);
}

int main() {
thread_pool_t *pool = pool_init();
if (!pool) return 1;

int task_ids[5];
for (int i = 0; i < 5; i++) {
task_ids[i] = i;
pool_add_task(pool, task_func, &task_ids[i]);
}

sleep(6);
pool_destroy(pool);
return 0;
}
EOL

# 编译和运行
gcc pool_test.c -pthread -o pool_test
./pool_test

思考题

  1. 线程的主要功能是什么?
  2. 线程同步的方法有哪些?
  3. 什么是线程池?它有什么优点?
  4. 如何实现线程安全?
  5. 线程和进程的区别是什么?

参考资料

  1. 《程序员的自我修养:链接、装载与库》
  2. POSIX Threads 文档
  3. System V ABI
  4. ELF 文件格式规范
  5. GNU Binutils 文档

第十一章:系统调用

11.1 系统调用的基本概念

系统调用的作用

系统调用的主要功能:

  1. 进程控制
  2. 文件操作
  3. 设备管理
  4. 内存管理
  5. 网络通信

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main() {
// 进程控制
pid_t pid = getpid();
printf("Process ID: %d\n", pid);

// 文件操作
int fd = open("test.txt", O_CREAT | O_WRONLY, 0644);
if (fd != -1) {
write(fd, "Hello, World!\n", 13);
close(fd);
}

return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc syscall_basic.c -o syscall_basic

# 运行
./syscall_basic

系统调用的实现

系统调用的实现方式:

  1. 软中断
  2. 系统调用表
  3. 参数传递
  4. 返回值处理

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>

int main() {
// 直接使用系统调用号
long ret = syscall(SYS_getpid);
printf("Process ID (syscall): %ld\n", ret);

// 使用系统调用宏
ret = syscall(SYS_write, STDOUT_FILENO, "Hello\n", 6);
printf("Write bytes: %ld\n", ret);

return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc syscall_impl.c -o syscall_impl

# 运行
./syscall_impl

11.2 系统调用的使用

进程控制

进程控制相关的系统调用:

  1. fork
  2. exec
  3. wait
  4. exit

示例代码:

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
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
// 创建子进程
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}

if (pid == 0) {
// 子进程
printf("Child process (PID: %d)\n", getpid());
sleep(1);
return 42;
} else {
// 父进程
printf("Parent process (PID: %d)\n", getpid());

// 等待子进程
int status;
waitpid(pid, &status, 0);

if (WIFEXITED(status)) {
printf("Child exited with status: %d\n",
WEXITSTATUS(status));
}
}

return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc process_control.c -o process_control

# 运行
./process_control

文件操作

文件操作相关的系统调用:

  1. open
  2. read
  3. write
  4. close

示例代码:

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
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

int main() {
// 打开文件
int fd = open("test.txt", O_CREAT | O_RDWR, 0644);
if (fd == -1) {
perror("open");
return 1;
}

// 写入数据
const char *msg = "Hello, World!\n";
ssize_t n = write(fd, msg, strlen(msg));
if (n == -1) {
perror("write");
close(fd);
return 1;
}

// 定位到文件开始
lseek(fd, 0, SEEK_SET);

// 读取数据
char buf[1024];
n = read(fd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("Read: %s", buf);
}

// 关闭文件
close(fd);
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc file_ops.c -o file_ops

# 运行
./file_ops

11.3 系统调用的优化

性能优化

系统调用性能优化的方法:

  1. 减少系统调用次数
  2. 使用缓冲
  3. 批量操作
  4. 异步I/O

示例代码:

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
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

#define BUFFER_SIZE 4096

int main() {
// 打开文件
int fd = open("large.txt", O_CREAT | O_WRONLY, 0644);
if (fd == -1) {
perror("open");
return 1;
}

// 使用缓冲写入
char buf[BUFFER_SIZE];
memset(buf, 'A', BUFFER_SIZE);

// 批量写入
for (int i = 0; i < 1000; i++) {
if (write(fd, buf, BUFFER_SIZE) == -1) {
perror("write");
break;
}
}

close(fd);
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc syscall_optimize.c -o syscall_optimize

# 运行
./syscall_optimize

错误处理

系统调用错误处理的方法:

  1. 检查返回值
  2. 使用errno
  3. 错误恢复
  4. 日志记录

示例代码:

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
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

// 错误处理宏
#define CHECK_ERROR(expr, msg) \
do { \
if ((expr) == -1) { \
fprintf(stderr, "%s: %s\n", msg, strerror(errno)); \
return 1; \
} \
} while (0)

int main() {
// 打开文件
int fd = open("test.txt", O_RDONLY);
CHECK_ERROR(fd, "open failed");

// 读取数据
char buf[1024];
ssize_t n = read(fd, buf, sizeof(buf) - 1);
CHECK_ERROR(n, "read failed");

if (n > 0) {
buf[n] = '\0';
printf("Read: %s", buf);
}

// 关闭文件
CHECK_ERROR(close(fd), "close failed");

return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc error_handle.c -o error_handle

# 运行
./error_handle

实践练习

  1. 系统调用实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 创建测试程序
cat > syscall_test.c << EOL
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
printf("Process ID: %d\n", getpid());
printf("Parent Process ID: %d\n", getppid());
return 0;
}
EOL

# 编译和运行
gcc syscall_test.c -o syscall_test
./syscall_test
  1. 文件操作实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 创建文件操作测试程序
cat > file_test.c << EOL
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

int main() {
int fd = open("test.txt", O_CREAT | O_WRONLY, 0644);
if (fd != -1) {
write(fd, "Test message\n", 13);
close(fd);
}
return 0;
}
EOL

# 编译和运行
gcc file_test.c -o file_test
./file_test
  1. 错误处理实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 创建错误处理测试程序
cat > error_test.c << EOL
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

int main() {
int fd = open("nonexistent.txt", O_RDONLY);
if (fd == -1) {
printf("Error: %s\n", strerror(errno));
}
return 0;
}
EOL

# 编译和运行
gcc error_test.c -o error_test
./error_test

思考题

  1. 系统调用的主要功能是什么?
  2. 系统调用是如何实现的?
  3. 如何优化系统调用的性能?
  4. 系统调用错误处理的方法有哪些?
  5. 进程控制和文件操作的系统调用有哪些?

参考资料

  1. 《程序员的自我修养:链接、装载与库》
  2. Linux 系统调用文档
  3. System V ABI
  4. ELF 文件格式规范
  5. GNU Binutils 文档

第十章:运行库

10.1 运行库的基本概念

运行库的作用

运行库的主要功能:

  1. 提供标准C库函数
  2. 实现系统调用
  3. 管理程序入口
  4. 处理程序退出

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>

int main() {
// 使用标准C库函数
printf("Hello, World!\n");

// 使用内存分配函数
int *ptr = malloc(sizeof(int));
if (ptr) {
*ptr = 42;
printf("Value: %d\n", *ptr);
free(ptr);
}

return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc runtime_basic.c -o runtime_basic

# 运行
./runtime_basic

运行库的组成

运行库的主要组件:

  1. 启动代码
  2. 标准C库
  3. 系统调用接口
  4. 错误处理

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main() {
// 使用标准C库
FILE *fp = fopen("test.txt", "r");
if (!fp) {
// 错误处理
fprintf(stderr, "Error: %s\n", strerror(errno));
return 1;
}

// 使用系统调用接口
char buf[1024];
size_t n = fread(buf, 1, sizeof(buf), fp);
if (n > 0) {
printf("Read %zu bytes\n", n);
}

fclose(fp);
return 0;
}

10.2 运行库的实现

启动代码

启动代码的功能:

  1. 初始化环境
  2. 设置堆栈
  3. 调用main函数
  4. 处理程序退出

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdlib.h>

// 全局构造函数
__attribute__((constructor))
void init() {
printf("Global initialization\n");
}

// 全局析构函数
__attribute__((destructor))
void cleanup() {
printf("Global cleanup\n");
}

int main() {
printf("Main function\n");
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc startup_code.c -o startup_code

# 运行
./startup_code

标准C库

标准C库的实现:

  1. 字符串处理
  2. 内存操作
  3. 文件操作
  4. 数学函数

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

int main() {
// 字符串处理
char str[] = "Hello, World!";
printf("Length: %zu\n", strlen(str));

// 内存操作
void *ptr = malloc(1024);
if (ptr) {
memset(ptr, 0, 1024);
free(ptr);
}

// 数学函数
double x = 3.14;
printf("Sin(%f) = %f\n", x, sin(x));

return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc std_lib.c -lm -o std_lib

# 运行
./std_lib

10.3 运行库的优化

性能优化

性能优化的方法:

  1. 内联函数
  2. 缓存优化
  3. 内存对齐
  4. 指令优化

示例代码:

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 内联函数
static inline int add(int a, int b) {
return a + b;
}

// 内存对齐的结构
struct aligned_data {
int x;
char padding[60]; // 确保下一个成员对齐到64字节边界
int y;
} __attribute__((aligned(64)));

int main() {
// 使用内联函数
int sum = add(1, 2);
printf("Sum: %d\n", sum);

// 使用对齐的内存
struct aligned_data *data = malloc(sizeof(struct aligned_data));
if (data) {
data->x = 10;
data->y = 20;
printf("Data: %d, %d\n", data->x, data->y);
free(data);
}

return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译(启用优化)
gcc -O2 performance.c -o performance

# 运行
./performance

调试支持

调试支持的功能:

  1. 错误检查
  2. 内存检查
  3. 堆栈跟踪
  4. 日志记录

示例代码:

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
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

// 调试宏
#define DEBUG_PRINT(fmt, ...) \
fprintf(stderr, "DEBUG: " fmt "\n", ##__VA_ARGS__)

// 内存检查函数
void *checked_malloc(size_t size) {
void *ptr = malloc(size);
if (!ptr) {
DEBUG_PRINT("Memory allocation failed");
abort();
}
return ptr;
}

int main() {
// 使用断言
int x = 10;
assert(x > 0);

// 使用调试宏
DEBUG_PRINT("Value of x: %d", x);

// 使用内存检查
int *ptr = checked_malloc(sizeof(int));
*ptr = 42;
printf("Value: %d\n", *ptr);
free(ptr);

return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译(启用调试信息)
gcc -g debug_support.c -o debug_support

# 运行
./debug_support

实践练习

  1. 运行库实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建测试程序
cat > runtime_test.c << EOL
#include <stdio.h>
#include <stdlib.h>

int main() {
printf("Testing runtime library\n");
return 0;
}
EOL

# 编译和运行
gcc runtime_test.c -o runtime_test
./runtime_test
  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
# 创建性能测试程序
cat > perf_test.c << EOL
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define SIZE 1000000

int main() {
clock_t start = clock();

// 分配内存
int *arr = malloc(SIZE * sizeof(int));
if (!arr) return 1;

// 初始化数组
for (int i = 0; i < SIZE; i++) {
arr[i] = i;
}

clock_t end = clock();
printf("Time: %f seconds\n",
(double)(end - start) / CLOCKS_PER_SEC);

free(arr);
return 0;
}
EOL

# 编译和运行
gcc -O2 perf_test.c -o perf_test
./perf_test
  1. 调试实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 创建调试测试程序
cat > debug_test.c << EOL
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main() {
int *ptr = NULL;
assert(ptr != NULL); // 这会导致程序终止
return 0;
}
EOL

# 编译和运行
gcc -g debug_test.c -o debug_test
./debug_test

思考题

  1. 运行库的主要功能是什么?
  2. 启动代码的作用是什么?
  3. 如何优化运行库的性能?
  4. 运行库提供了哪些调试支持?
  5. 标准C库包含哪些主要功能?

参考资料

  1. 《程序员的自我修养:链接、装载与库》
  2. GNU C Library 文档
  3. System V ABI
  4. ELF 文件格式规范
  5. GNU Binutils 文档

第九章:内存管理

9.1 内存管理的基本概念

内存管理的目标

内存管理的主要目标:

  1. 内存分配
  2. 内存回收
  3. 内存保护
  4. 内存共享

示例代码:

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

int main() {
// 内存分配
int *ptr = malloc(sizeof(int));
if (!ptr) {
perror("malloc");
return 1;
}

// 内存使用
*ptr = 42;
printf("Value: %d\n", *ptr);

// 内存回收
free(ptr);

// 内存映射
void *mapped = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (mapped == MAP_FAILED) {
perror("mmap");
return 1;
}

// 使用映射内存
int *mapped_ptr = mapped;
*mapped_ptr = 100;
printf("Mapped value: %d\n", *mapped_ptr);

// 解除映射
munmap(mapped, 4096);

return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc memory_basic.c -o memory_basic

# 运行
./memory_basic

内存分配算法

常见的内存分配算法:

  1. 首次适应
  2. 最佳适应
  3. 最差适应
  4. 循环首次适应

示例代码:

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
#include <stdio.h>
#include <stdlib.h>

// 内存块结构
typedef struct block {
size_t size;
struct block *next;
int free;
} block_t;

// 内存分配函数
void *my_malloc(size_t size) {
// 实现首次适应算法
block_t *current = heap_start;
while (current) {
if (current->free && current->size >= size) {
// 分配内存
current->free = 0;
return (void *)(current + 1);
}
current = current->next;
}
return NULL;
}

// 内存释放函数
void my_free(void *ptr) {
if (!ptr) return;

block_t *block = (block_t *)ptr - 1;
block->free = 1;

// 合并相邻的空闲块
block_t *current = heap_start;
while (current) {
if (current->free && current->next && current->next->free) {
current->size += current->next->size;
current->next = current->next->next;
}
current = current->next;
}
}

9.2 内存管理的实现

页式管理

页式管理的特点:

  1. 固定大小的页
  2. 页表映射
  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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

#define PAGE_SIZE 4096

int main() {
// 分配一页内存
void *page = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED) {
perror("mmap");
return 1;
}

// 使用页面
int *ptr = page;
*ptr = 42;
printf("Page value: %d\n", *ptr);

// 修改页面权限
if (mprotect(page, PAGE_SIZE, PROT_READ) == -1) {
perror("mprotect");
return 1;
}

// 解除映射
munmap(page, PAGE_SIZE);

return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc page_manage.c -o page_manage

# 运行
./page_manage

段式管理

段式管理的特点:

  1. 变长段
  2. 段表映射
  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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

int main() {
// 分配代码段
void *code_seg = mmap(NULL, 4096, PROT_READ | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (code_seg == MAP_FAILED) {
perror("mmap code");
return 1;
}

// 分配数据段
void *data_seg = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (data_seg == MAP_FAILED) {
perror("mmap data");
return 1;
}

// 使用数据段
int *data = data_seg;
*data = 100;
printf("Data value: %d\n", *data);

// 解除映射
munmap(code_seg, 4096);
munmap(data_seg, 4096);

return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc segment_manage.c -o segment_manage

# 运行
./segment_manage

9.3 内存管理的优化

内存池

内存池的实现:

  1. 预分配内存
  2. 快速分配
  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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define POOL_SIZE 1024
#define BLOCK_SIZE 32

typedef struct memory_pool {
char *memory;
size_t used;
struct memory_pool *next;
} memory_pool_t;

// 创建内存池
memory_pool_t *create_pool() {
memory_pool_t *pool = malloc(sizeof(memory_pool_t));
if (!pool) return NULL;

pool->memory = malloc(POOL_SIZE);
if (!pool->memory) {
free(pool);
return NULL;
}

pool->used = 0;
pool->next = NULL;
return pool;
}

// 从内存池分配
void *pool_alloc(memory_pool_t *pool, size_t size) {
if (size > BLOCK_SIZE) return NULL;

if (pool->used + size > POOL_SIZE) {
// 创建新的内存池
memory_pool_t *new_pool = create_pool();
if (!new_pool) return NULL;

new_pool->next = pool;
pool = new_pool;
}

void *ptr = pool->memory + pool->used;
pool->used += size;
return ptr;
}

// 释放内存池
void free_pool(memory_pool_t *pool) {
while (pool) {
memory_pool_t *next = pool->next;
free(pool->memory);
free(pool);
pool = next;
}
}

内存对齐

内存对齐的实现:

  1. 计算对齐大小
  2. 调整分配大小
  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
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

// 计算对齐后的地址
void *align_ptr(void *ptr, size_t alignment) {
uintptr_t addr = (uintptr_t)ptr;
uintptr_t aligned = (addr + alignment - 1) & ~(alignment - 1);
return (void *)aligned;
}

// 分配对齐的内存
void *aligned_malloc(size_t size, size_t alignment) {
// 分配额外的空间用于对齐
void *ptr = malloc(size + alignment - 1);
if (!ptr) return NULL;

// 对齐地址
void *aligned = align_ptr(ptr, alignment);

// 存储原始指针
*((void **)aligned - 1) = ptr;

return aligned;
}

// 释放对齐的内存
void aligned_free(void *ptr) {
if (!ptr) return;

// 获取原始指针
void *original = *((void **)ptr - 1);
free(original);
}

实践练习

  1. 内存分配实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 创建测试程序
cat > mem_test.c << EOL
#include <stdio.h>
#include <stdlib.h>

int main() {
// 分配内存
int *ptr = malloc(sizeof(int));
*ptr = 42;
printf("Value: %d\n", *ptr);
free(ptr);
return 0;
}
EOL

# 编译和运行
gcc mem_test.c -o mem_test
./mem_test
  1. 内存池实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 创建内存池测试程序
cat > pool_test.c << EOL
#include <stdio.h>
#include "memory_pool.c"

int main() {
memory_pool_t *pool = create_pool();
if (!pool) return 1;

// 分配内存
int *ptr = pool_alloc(pool, sizeof(int));
*ptr = 100;
printf("Pool value: %d\n", *ptr);

free_pool(pool);
return 0;
}
EOL

# 编译和运行
gcc pool_test.c -o pool_test
./pool_test
  1. 内存对齐实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 创建对齐测试程序
cat > align_test.c << EOL
#include <stdio.h>
#include "memory_align.c"

int main() {
// 分配对齐的内存
int *ptr = aligned_malloc(sizeof(int), 16);
*ptr = 200;
printf("Aligned value: %d\n", *ptr);

aligned_free(ptr);
return 0;
}
EOL

# 编译和运行
gcc align_test.c -o align_test
./align_test

思考题

  1. 内存管理的主要目标是什么?
  2. 常见的内存分配算法有哪些?
  3. 页式管理和段式管理的区别是什么?
  4. 什么是内存池?它有什么优点?
  5. 为什么需要内存对齐?

参考资料

  1. 《程序员的自我修养:链接、装载与库》
  2. Linux 内核文档
  3. System V ABI
  4. ELF 文件格式规范
  5. GNU Binutils 文档

第八章:Linux共享库的组织

8.1 共享库的版本

共享库版本号

共享库版本号的组成:

  1. 主版本号
  2. 次版本号
  3. 发布号

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

// 版本 1.0.0
void func_v1() {
printf("Function version 1.0.0\n");
}

// 版本 1.1.0
void func_v2() {
printf("Function version 1.1.0\n");
}

// 版本 1.1.1
void func_v3() {
printf("Function version 1.1.1\n");
}

编译命令:

1
2
3
4
5
6
7
8
# 编译不同版本
gcc -shared -fPIC -Wl,-soname,libtest.so.1 libversion.c -o libtest.so.1.0.0
gcc -shared -fPIC -Wl,-soname,libtest.so.1 libversion.c -o libtest.so.1.1.0
gcc -shared -fPIC -Wl,-soname,libtest.so.1 libversion.c -o libtest.so.1.1.1

# 创建符号链接
ln -sf libtest.so.1.0.0 libtest.so.1
ln -sf libtest.so.1 libtest.so

符号版本化

符号版本化的实现:

  1. 定义版本脚本
  2. 标记符号版本
  3. 链接时指定版本

示例代码:

1
2
3
4
5
6
7
8
9
#include <stdio.h>

void func_v1() {
printf("Function version 1\n");
}

void func_v2() {
printf("Function version 2\n");
}

版本脚本:

1
2
3
4
5
6
7
8
VERSION_1.0 {
global: func_v1;
local: *;
};

VERSION_1.1 {
global: func_v2;
} VERSION_1.0;

编译命令:

1
2
# 编译带版本信息的共享库
gcc -shared -fPIC version_script.c -Wl,--version-script=version.script -o libversion.so

8.2 共享库的命名

命名规则

共享库的命名规则:

  1. lib前缀
  2. 库名
  3. .so后缀
  4. 版本号

示例代码:

1
2
3
4
5
#include <stdio.h>

void func() {
printf("Function from libname\n");
}

编译命令:

1
2
3
4
5
6
# 编译共享库
gcc -shared -fPIC libname.c -o libname.so.1.0.0

# 创建符号链接
ln -sf libname.so.1.0.0 libname.so.1
ln -sf libname.so.1 libname.so

搜索路径

共享库的搜索路径:

  1. LD_LIBRARY_PATH
  2. /etc/ld.so.conf
  3. /lib和/usr/lib

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <dlfcn.h>

int main() {
// 加载共享库
void *handle = dlopen("libname.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}

// 获取函数地址
void (*func)() = dlsym(handle, "func");
if (func) func();

dlclose(handle);
return 0;
}

编译和运行命令:

1
2
3
4
5
6
7
8
# 编译
gcc search_path.c -ldl -o search_path

# 设置搜索路径
export LD_LIBRARY_PATH=.

# 运行
./search_path

8.3 共享库的安装

安装位置

共享库的安装位置:

  1. /usr/lib
  2. /usr/local/lib
  3. 自定义目录

示例代码:

1
2
3
4
5
#include <stdio.h>

void func() {
printf("Function from installed library\n");
}

安装命令:

1
2
3
4
5
6
7
8
9
10
# 编译共享库
gcc -shared -fPIC install_lib.c -o libinstall.so.1.0.0

# 安装到系统目录
sudo cp libinstall.so.1.0.0 /usr/local/lib/
sudo ln -sf /usr/local/lib/libinstall.so.1.0.0 /usr/local/lib/libinstall.so.1
sudo ln -sf /usr/local/lib/libinstall.so.1 /usr/local/lib/libinstall.so

# 更新共享库缓存
sudo ldconfig

安装脚本

安装脚本的编写:

  1. 复制文件
  2. 创建符号链接
  3. 更新缓存

示例脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash

# 安装目录
INSTALL_DIR="/usr/local/lib"
LIB_NAME="libinstall"
VERSION="1.0.0"

# 复制共享库
cp ${LIB_NAME}.so.${VERSION} ${INSTALL_DIR}/

# 创建符号链接
ln -sf ${INSTALL_DIR}/${LIB_NAME}.so.${VERSION} ${INSTALL_DIR}/${LIB_NAME}.so.1
ln -sf ${INSTALL_DIR}/${LIB_NAME}.so.1 ${INSTALL_DIR}/${LIB_NAME}.so

# 更新共享库缓存
ldconfig

实践练习

  1. 版本管理实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 创建不同版本的共享库
cat > libversion.c << EOL
#include <stdio.h>

void func_v1() { printf("Version 1\n"); }
void func_v2() { printf("Version 2\n"); }
EOL

# 编译不同版本
gcc -shared -fPIC -Wl,-soname,libversion.so.1 libversion.c -o libversion.so.1.0.0
gcc -shared -fPIC -Wl,-soname,libversion.so.1 libversion.c -o libversion.so.1.1.0

# 创建符号链接
ln -sf libversion.so.1.0.0 libversion.so.1
ln -sf libversion.so.1 libversion.so
  1. 命名规则实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 创建共享库
cat > libtest.c << EOL
#include <stdio.h>

void test_func() {
printf("Test function\n");
}
EOL

# 编译共享库
gcc -shared -fPIC libtest.c -o libtest.so.1.0.0

# 创建符号链接
ln -sf libtest.so.1.0.0 libtest.so.1
ln -sf libtest.so.1 libtest.so
  1. 安装实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 创建安装脚本
cat > install.sh << EOL
#!/bin/bash

# 安装目录
INSTALL_DIR="/usr/local/lib"
LIB_NAME="libtest"
VERSION="1.0.0"

# 复制共享库
cp ${LIB_NAME}.so.${VERSION} ${INSTALL_DIR}/

# 创建符号链接
ln -sf ${INSTALL_DIR}/${LIB_NAME}.so.${VERSION} ${INSTALL_DIR}/${LIB_NAME}.so.1
ln -sf ${INSTALL_DIR}/${LIB_NAME}.so.1 ${INSTALL_DIR}/${LIB_NAME}.so

# 更新共享库缓存
ldconfig
EOL

# 执行安装
chmod +x install.sh
sudo ./install.sh

思考题

  1. 共享库版本号的作用是什么?
  2. 什么是符号版本化?它有什么作用?
  3. 共享库的命名规则是什么?
  4. 如何设置共享库的搜索路径?
  5. 如何正确安装共享库?

参考资料

  1. 《程序员的自我修养:链接、装载与库》
  2. 动态链接器文档
  3. ELF 文件格式规范
  4. GNU Binutils 文档
  5. System V ABI

第七章:动态链接的实现

7.1 动态链接的基本实现

动态链接器

动态链接器的主要功能:

  1. 加载共享库
  2. 符号解析
  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
#include <stdio.h>
#include <dlfcn.h>

int main() {
// 加载共享库
void *handle = dlopen("./libshared.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}

// 获取函数地址
void (*func)() = dlsym(handle, "shared_func");
if (!func) {
fprintf(stderr, "dlsym failed: %s\n", dlerror());
dlclose(handle);
return 1;
}

// 调用函数
func();

dlclose(handle);
return 0;
}

编译和运行命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 创建共享库
cat > libshared.c << EOL
#include <stdio.h>

void shared_func() {
printf("This is a shared function\n");
}
EOL

gcc -shared -fPIC libshared.c -o libshared.so

# 编译主程序
gcc dynamic_linker.c -ldl -o dynamic_linker

# 运行
./dynamic_linker

符号解析

符号解析的过程:

  1. 收集所有符号
  2. 建立符号表
  3. 解析符号引用

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <dlfcn.h>

int main() {
// 加载共享库
void *handle = dlopen("./libshared.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}

// 解析符号
void (*func1)() = dlsym(handle, "shared_func1");
void (*func2)() = dlsym(handle, "shared_func2");

if (func1) func1();
if (func2) func2();

dlclose(handle);
return 0;
}

编译和运行命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 创建共享库
cat > libshared.c << EOL
#include <stdio.h>

void shared_func1() {
printf("Function 1\n");
}

void shared_func2() {
printf("Function 2\n");
}
EOL

gcc -shared -fPIC libshared.c -o libshared.so

# 编译主程序
gcc symbol_resolve.c -ldl -o symbol_resolve

# 运行
./symbol_resolve

7.2 动态链接的优化

延迟绑定

延迟绑定的实现:

  1. 第一次调用时解析
  2. 缓存解析结果
  3. 后续调用直接使用

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <dlfcn.h>

int main() {
// 加载共享库
void *handle = dlopen("./libshared.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}

// 第一次调用(触发延迟绑定)
void (*func)() = dlsym(handle, "shared_func");
if (func) func();

// 第二次调用(使用缓存)
func = dlsym(handle, "shared_func");
if (func) func();

dlclose(handle);
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc lazy_binding.c -ldl -o lazy_binding

# 运行
./lazy_binding

共享库缓存

共享库缓存的实现:

  1. 缓存共享库路径
  2. 缓存符号解析结果
  3. 加速加载过程

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <dlfcn.h>

int main() {
// 加载共享库(使用缓存)
void *handle = dlopen("./libshared.so", RTLD_NOW);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}

// 获取函数地址
void (*func)() = dlsym(handle, "shared_func");
if (func) func();

dlclose(handle);
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc shared_cache.c -ldl -o shared_cache

# 运行
./shared_cache

7.3 动态链接的调试

调试工具

常用的调试工具:

  1. ldd
  2. nm
  3. objdump
  4. readelf

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <dlfcn.h>

int main() {
// 加载共享库
void *handle = dlopen("./libshared.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}

// 获取函数地址
void (*func)() = dlsym(handle, "shared_func");
if (func) func();

dlclose(handle);
return 0;
}

调试命令:

1
2
3
4
5
6
7
8
9
10
11
# 查看依赖
ldd debug_dl

# 查看符号表
nm libshared.so

# 查看重定位信息
objdump -R debug_dl

# 查看动态段
readelf -d debug_dl

错误处理

错误处理的方法:

  1. 检查返回值
  2. 使用dlerror
  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
#include <stdio.h>
#include <dlfcn.h>

void error_handler(const char *msg) {
fprintf(stderr, "Error: %s\n", msg);
}

int main() {
// 设置错误处理函数
dlerror();

// 加载共享库
void *handle = dlopen("./libshared.so", RTLD_LAZY);
if (!handle) {
error_handler(dlerror());
return 1;
}

// 获取函数地址
void (*func)() = dlsym(handle, "shared_func");
if (!func) {
error_handler(dlerror());
dlclose(handle);
return 1;
}

// 调用函数
func();

dlclose(handle);
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc error_handle.c -ldl -o error_handle

# 运行
./error_handle

实践练习

  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
# 创建共享库
cat > libtest.c << EOL
#include <stdio.h>

void test_func() {
printf("Test function\n");
}
EOL

gcc -shared -fPIC libtest.c -o libtest.so

# 创建主程序
cat > main.c << EOL
#include <stdio.h>
#include <dlfcn.h>

int main() {
void *handle = dlopen("./libtest.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}

void (*func)() = dlsym(handle, "test_func");
if (func) func();

dlclose(handle);
return 0;
}
EOL

# 编译和运行
gcc main.c -ldl -o main
./main
  1. 调试实验
1
2
3
4
5
6
7
8
# 查看依赖
ldd main

# 查看符号表
nm libtest.so

# 查看重定位信息
objdump -R main
  1. 错误处理实验
1
2
3
4
5
# 删除共享库
rm libtest.so

# 运行程序(应该显示错误)
./main

思考题

  1. 动态链接器的主要功能是什么?
  2. 什么是延迟绑定?它有什么作用?
  3. 如何优化动态链接的性能?
  4. 常用的动态链接调试工具有哪些?
  5. 如何处理动态链接中的错误?

参考资料

  1. 《程序员的自我修养:链接、装载与库》
  2. 动态链接器文档
  3. ELF 文件格式规范
  4. GNU Binutils 文档
  5. System V ABI

第六章:可执行文件的装载与进程

6.1 进程虚拟地址空间

虚拟地址空间的概念

虚拟地址空间是操作系统为每个进程提供的独立地址空间。主要包括:

  1. 代码段
  2. 数据段
  3. 共享库

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int global_var = 10; // 数据段
static int static_var = 20; // 数据段

int main() {
int local_var = 30; // 栈
int *heap_var = malloc(sizeof(int)); // 堆
*heap_var = 40;

printf("global_var: %p\n", &global_var);
printf("static_var: %p\n", &static_var);
printf("local_var: %p\n", &local_var);
printf("heap_var: %p\n", heap_var);
printf("main: %p\n", main);

free(heap_var);
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc virtual_space.c -o virtual_space

# 运行
./virtual_space

内存布局

进程的内存布局包括:

  1. 代码段(.text)
  2. 数据段(.data)
  3. BSS段(.bss)
  4. 堆(heap)
  5. 栈(stack)

示例代码:

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// 代码段
void func() {
printf("Function address: %p\n", func);
}

// 数据段
int global_init = 10;

// BSS段
int global_uninit;

int main() {
// 栈
int stack_var = 20;

// 堆
int *heap_var = malloc(sizeof(int));
*heap_var = 30;

printf("global_init: %p\n", &global_init);
printf("global_uninit: %p\n", &global_uninit);
printf("stack_var: %p\n", &stack_var);
printf("heap_var: %p\n", heap_var);
printf("func: %p\n", func);

free(heap_var);
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc memory_layout.c -o memory_layout

# 运行
./memory_layout

6.2 装载的方式

静态装载

静态装载的特点:

  1. 一次性加载
  2. 固定地址
  3. 无重定位

示例代码:

1
2
3
4
5
6
#include <stdio.h>

int main() {
printf("Program loaded at: %p\n", main);
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc -static static_load.c -o static_load

# 运行
./static_load

动态装载

动态装载的特点:

  1. 按需加载
  2. 地址无关
  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
#include <stdio.h>
#include <dlfcn.h>

int main() {
// 动态加载库
void *handle = dlopen("./libdynamic.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}

// 获取函数地址
void (*func)() = dlsym(handle, "func");
if (!func) {
fprintf(stderr, "dlsym failed: %s\n", dlerror());
dlclose(handle);
return 1;
}

// 调用函数
func();

dlclose(handle);
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc dynamic_load.c -ldl -o dynamic_load

# 运行
./dynamic_load

6.3 从操作系统角度看可执行文件的装载

进程的创建

进程创建的过程:

  1. 创建虚拟地址空间
  2. 读取可执行文件头
  3. 建立虚拟地址空间与可执行文件的映射关系
  4. 设置CPU指令寄存器

示例代码:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
pid_t pid = getpid();
printf("Process ID: %d\n", pid);
printf("Program loaded at: %p\n", main);
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc process_create.c -o process_create

# 运行
./process_create

页错误

页错误处理:

  1. 缺页中断
  2. 页面调入
  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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

#define PAGE_SIZE 4096

int main() {
// 分配内存
char *ptr = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
return 1;
}

// 写入数据(触发页错误)
ptr[0] = 'A';

printf("Memory allocated at: %p\n", ptr);
printf("First byte: %c\n", ptr[0]);

// 释放内存
munmap(ptr, PAGE_SIZE);
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc page_fault.c -o page_fault

# 运行
./page_fault

实践练习

  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
# 创建示例程序
cat > vaddr_test.c << EOL
#include <stdio.h>
#include <stdlib.h>

int global = 10;
static int static_var = 20;

int main() {
int local = 30;
int *heap = malloc(sizeof(int));
*heap = 40;

printf("global: %p\n", &global);
printf("static_var: %p\n", &static_var);
printf("local: %p\n", &local);
printf("heap: %p\n", heap);
printf("main: %p\n", main);

free(heap);
return 0;
}
EOL

# 编译和运行
gcc vaddr_test.c -o vaddr_test
./vaddr_test
  1. 内存布局实验
1
2
3
4
5
# 查看进程内存映射
pmap -x $$

# 查看进程虚拟内存使用情况
cat /proc/$$/maps
  1. 页错误实验
1
2
# 使用 strace 跟踪页错误
strace ./page_fault

思考题

  1. 什么是虚拟地址空间?它有什么作用?
  2. 进程的内存布局包括哪些部分?
  3. 静态装载和动态装载的区别是什么?
  4. 操作系统如何创建进程?
  5. 什么是页错误?如何处理页错误?

参考资料

  1. 《程序员的自我修养:链接、装载与库》
  2. Linux 内核文档
  3. System V ABI
  4. ELF 文件格式规范
  5. GNU Binutils 文档

第五章:动态链接

5.1 为什么要动态链接

静态链接的缺点

静态链接存在以下问题:

  1. 空间浪费
  2. 更新困难
  3. 内存占用大

示例代码:

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int add(int a, int b) {
return a + b;
}

int subtract(int a, int b) {
return a - b;
}
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

extern int add(int a, int b);
extern int subtract(int a, int b);

int main() {
printf("10 + 5 = %d\n", add(10, 5));
printf("10 - 5 = %d\n", subtract(10, 5));
return 0;
}

编译和链接命令:

1
2
3
4
5
6
7
8
9
# 创建静态库
gcc -c static_lib.c -o static_lib.o
ar rcs libstatic.a static_lib.o

# 静态链接
gcc static_main.c -L. -lstatic -o static_program

# 查看文件大小
ls -l static_program

动态链接的优势

动态链接的优点:

  1. 节省空间
  2. 便于更新
  3. 共享内存

示例代码:

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int add(int a, int b) {
return a + b;
}

int subtract(int a, int b) {
return a - b;
}
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

extern int add(int a, int b);
extern int subtract(int a, int b);

int main() {
printf("10 + 5 = %d\n", add(10, 5));
printf("10 - 5 = %d\n", subtract(10, 5));
return 0;
}

编译和链接命令:

1
2
3
4
5
6
7
8
# 创建动态库
gcc -shared -fPIC dynamic_lib.c -o libdynamic.so

# 动态链接
gcc dynamic_main.c -L. -ldynamic -o dynamic_program

# 查看文件大小
ls -l dynamic_program

5.2 动态链接的实现

动态链接的基本实现

动态链接的基本步骤:

  1. 加载动态库
  2. 符号解析
  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
#include <stdio.h>
#include <dlfcn.h>

int main() {
// 加载动态库
void *handle = dlopen("./libdynamic.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}

// 获取函数指针
int (*add)(int, int) = dlsym(handle, "add");
int (*subtract)(int, int) = dlsym(handle, "subtract");

if (!add || !subtract) {
fprintf(stderr, "dlsym failed: %s\n", dlerror());
dlclose(handle);
return 1;
}

// 调用函数
printf("10 + 5 = %d\n", add(10, 5));
printf("10 - 5 = %d\n", subtract(10, 5));

// 关闭动态库
dlclose(handle);
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc dl_demo.c -ldl -o dl_demo

# 运行
./dl_demo

动态链接的细节

动态链接的细节包括:

  1. 符号解析
  2. 重定位
  3. 延迟绑定

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

extern int add(int a, int b);
extern int subtract(int a, int b);

int main() {
// 第一次调用会触发延迟绑定
printf("First call: 10 + 5 = %d\n", add(10, 5));

// 第二次调用直接使用已解析的地址
printf("Second call: 10 + 5 = %d\n", add(10, 5));

return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc lazy_binding.c -L. -ldynamic -o lazy_binding

# 运行
./lazy_binding

动态链接的优化

动态链接的优化技术:

  1. 符号预解析
  2. 共享库缓存
  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
#include <stdio.h>
#include <dlfcn.h>

int main() {
// 使用 RTLD_NOW 进行预解析
void *handle = dlopen("./libdynamic.so", RTLD_NOW);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}

// 预解析所有符号
int (*add)(int, int) = dlsym(handle, "add");
int (*subtract)(int, int) = dlsym(handle, "subtract");

if (!add || !subtract) {
fprintf(stderr, "dlsym failed: %s\n", dlerror());
dlclose(handle);
return 1;
}

// 调用函数
printf("10 + 5 = %d\n", add(10, 5));
printf("10 - 5 = %d\n", subtract(10, 5));

dlclose(handle);
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc optimize_dl.c -ldl -o optimize_dl

# 运行
./optimize_dl

实践练习

  1. 动态库创建实验
1
2
3
4
5
6
7
8
9
10
# 创建动态库
cat > libmath.c << EOL
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
EOL

gcc -shared -fPIC libmath.c -o libmath.so

# 查看动态库信息
readelf -d libmath.so
  1. 动态链接实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 创建主程序
cat > main.c << EOL
#include <stdio.h>
extern int add(int a, int b);
extern int subtract(int a, int b);

int main() {
printf("10 + 5 = %d\n", add(10, 5));
printf("10 - 5 = %d\n", subtract(10, 5));
return 0;
}
EOL

# 编译和链接
gcc main.c -L. -lmath -o math_program

# 运行程序
LD_LIBRARY_PATH=. ./math_program
  1. 延迟绑定实验
1
2
3
4
5
# 查看延迟绑定信息
objdump -R math_program

# 使用 strace 跟踪动态链接过程
strace ./math_program

思考题

  1. 动态链接相比静态链接有哪些优势?
  2. 动态链接的基本实现步骤是什么?
  3. 什么是延迟绑定?它有什么作用?
  4. 如何优化动态链接的性能?
  5. 动态链接可能带来哪些问题?

参考资料

  1. 《程序员的自我修养:链接、装载与库》
  2. 动态链接器文档
  3. ELF 文件格式规范
  4. GNU Binutils 文档
  5. System V ABI
0%