参考
主要参考了:陈向群操作系统, 程序员的自我修养
Gcc 编译的背后
GCC编程四个过程:预处理-编译-汇编-链接
helloworld程序的运行过程
1 2 3 4 5 6 7 8
| #include <stdio.h> int main(int argc, char *argv[]) { puts("hello world"); return 0; }
|
- 用户告诉操作系统执行helloworld程序
- 操作系统:找到helloworld程序的相关信息,检查其类型是否是
可执行文件
;并通过程序首部信息,确定代码和数据在可执行文件中的位置并计算出对应的磁盘块地址
- 操作系统:创建一个新的进程,并将helloworld可执行文件映射到该进程结构,表示由该进程执行helloworld程序
- 操作系统:为helloworld程序设置CPU上下文环境,并跳到程序开始处
- 执行helloworld程序的第一条指令,发生缺页异常
- 操作系统:分配一页物理内存,并将代码从磁盘读入内存,然后继续执行helloworld程序
- helloworld程序执行puts函数(系统调用),在显示器上写一字符串
- 操作系统:找到要将字符串送往的显示设备,通常设备是由一个进程控制的,所以,操作系统将要写的字符串送给该进程
- 操作系统:控制设备的进程告诉设备的窗口系统它要显示字符串,窗口系统确定这是一个合法的操作,然后将字符串转换成像素,将像素写入设备的存储映像区
- 视频硬件将像素转换成显示器可接收的一组控制/数据信号
- 显示器解释信号,激发液晶屏
- OK!!!我们在屏幕上看到了“hello world”
操作系统的5大功能(从资源管理的角度)
1 进程/线程管理
(CPU管理)
进程线程状态、控制、同步互斥、通信、调度、……
2 存储管理
分配/回收、地址转换、存储保护、内存扩充、……
3 文件管理
文件目录、文件操作、磁盘空间、文件存取控制、……
4 设备管理
设备驱动、分配回收、缓冲技术、……
5 用户接口
系统命令、编程接口
操作系统的主要特征
1 并发
2 共享
3 虚拟
4 随机/异常
linux内核组件
可执行文件的装载与进程
可执行文件:
[root@localhost bin]# file cgdb
cgdb: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs)
for GNU/Linux 2.6.32, BuildID[sha1]=0x7e94e0eb5975bd6a1eb4c7a8c0af6b77475f4362, not stripped
[root@localhost bin]# file ShadowsocksR-dotnet2.0.exe
ShadowsocksR-dotnet2.0.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly,for MS Windows
进程的建立:
- 创建一个独立的虚拟地址空间(页目录)
虚拟空间和物理内存的映射关系
- 读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系
虚拟空间和可执行文件的映射关系
- 将CPU的指令寄存器设置成可执行文件的入口地址,启动运行
可执行文件在装载时实际是被映射的虚拟空间,所以可执行文件很多时候又被叫做映像文件
gcc编译的四步
1 2 3 4 5 6 7 8 9 10
| #include "stdio.h"
int main() { puts("hello, it,s me!"); return 0; }
|
预处理阶段
头文件的包含,宏定义的扩展,条件编译的选择
gcc -E test.c -o test.i
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
| # 1 "test.c" # 1 "<built-in>" # 1 "<命令行>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 1 "<命令行>" 2 # 1 "test.c" # 1 "/usr/include/stdio.h" 1 3 4 # 27 "/usr/include/stdio.h" 3 4 # 1 "/usr/include/features.h" 1 3 4 # 375 "/usr/include/features.h" 3 4 # 1 "/usr/include/sys/cdefs.h" 1 3 4 # 392 "/usr/include/sys/cdefs.h" 3 4 # 1 "/usr/include/bits/wordsize.h" 1 3 4 # 393 "/usr/include/sys/cdefs.h" 2 3 4 # 376 "/usr/include/features.h" 2 3 4 # 399 "/usr/include/features.h" 3 4 # 1 "/usr/include/gnu/stubs.h" 1 3 4 # 10 "/usr/include/gnu/stubs.h" 3 4 # 1 "/usr/include/gnu/stubs-64.h" 1 3 4 # 11 "/usr/include/gnu/stubs.h" 2 3 4 # 400 "/usr/include/features.h" 2 3 4 # 28 "/usr/include/stdio.h" 2 3 4
# 1 "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/include/stddef.h" 1 3 4 # 212 "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/include/stddef.h" 3 4 typedef long unsigned int size_t; # 34 "/usr/include/stdio.h" 2 3 4
# 1 "/usr/include/bits/types.h" 1 3 4 # 27 "/usr/include/bits/types.h" 3 4 # 1 "/usr/include/bits/wordsize.h" 1 3 4 # 28 "/usr/include/bits/types.h" 2 3 4
typedef unsigned char __u_char; typedef unsigned short int __u_short; typedef unsigned int __u_int; typedef unsigned long int __u_long;
typedef signed char __int8_t; typedef unsigned char __uint8_t; typedef signed short int __int16_t; typedef unsigned short int __uint16_t; typedef signed int __int32_t; typedef unsigned int __uint32_t;
typedef signed long int __int64_t; typedef unsigned long int __uint64_t;
---------------省略一大部分内容------------------------------
---------------省略一大部分内容------------------------------
# 2 "test.c" 2
int main() { puts("hello, it,s me!"); return 0; }
|
编译阶段
词法分析,语法分析,将源文件代码转为中间汇编代码
gcc -S test.i -o test.s
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
| .file "test.c" .section .rodata .LC0: .string "hello, it,s me!" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl $.LC0, %edi call puts movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-36)" .section .note.GNU-stack,"",@progbits
|
汇编阶段
将汇编代码翻译为目标代码(机器代码),即二进制文件
gcc -c test.s -o test.o
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
|
链接阶段
重定位是将符号引用与符号定义进行链接的过程。因此链接是处理可重定位文件,把它们的各种符号引用和符号定义转换为可执行文件中的合适信息(一般是虚拟内存地址)的过程。
链接又分为静态链接和动态链接,前者是程序开发阶段程序员用 ld(gcc 实际上在后台调用了 ld)静态链接器手动链接的过程,而动态链接则是程序运行期间系统调用动态链接器(ld-linux.so)自动链接的过程。
gcc test.o -o test
[root@localhost test]# ldd test
linux-vdso.so.1 => (0x00007fff22f44000)
libc.so.6 => /lib64/libc.so.6 (0x00007f80760ec000)
/lib64/ld-linux-x86-64.so.2 (0x00007f80764c2000)
函数库一般分为静态库和动态库两种。静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为”.a”。动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为”.so”,如前面所述的libc.so.6就是动态库。gcc在编译时默认使用动态库。
ending