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

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

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 文档