01-计算机系统漫游

编译过程分为四个阶段:预处理、编译、汇编、链接
-w1201

gcc -E hello.c -o hello.i   //预处理
gcc -S hello.i -o hello.s   //编译
gcc -c hello.s -o hello.o   //汇编
gcc hello.o -o hello        //生成可执行文件

hello.c为例子:

#include <stdio.h>
#define ANSWER 42
int main() {
    int obj = ANSWER;
    return 0;
}

预编译实际上是进行头文件、宏定义的替换和组织,执行上述预编译命令可查看其内容(展示部分结果):

# 1 "hello.c"
# 1 "<built-in>"
# 1 "<command-line>"
//...省略
# 216 "/usr/lib/gcc/x86_64-redhat-linux/8/include/stddef.h" 3 4
typedef long unsigned int size_t;
# 34 "/usr/include/stdio.h" 2 3 4
//...省略
typedef __builtin_va_list __gnuc_va_list;
# 37 "/usr/include/stdio.h" 2 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 int __sig_atomic_t;
# 39 "/usr/include/stdio.h" 2 3 4
# 1 "/usr/include/bits/types/__fpos_t.h" 1 3 4




# 1 "/usr/include/bits/types/__mbstate_t.h" 1 3 4
# 13 "/usr/include/bits/types/__mbstate_t.h" 3 4
typedef struct
{
  int __count;
  union
  {
    unsigned int __wch;
    char __wchb[4];
  } __value;
} __mbstate_t;
# 6 "/usr/include/bits/types/__fpos_t.h" 2 3 4

//...省略

extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 864 "/usr/include/stdio.h" 3 4
extern int __uflow (FILE *);
extern int __overflow (FILE *, int);
# 879 "/usr/include/stdio.h" 3 4

# 2 "hello.c" 2


# 3 "hello.c"
int main() {
    int obj = 42;
    return 0;
}

我们发现预编译生成的.i文件中已经不存在宏ANSWER了,其值被替换成42。编译阶段主要是对代码进行词法分析、语法分析、语义分析、中间代码优化等等。

然后是通过gcc -S选项进行编译,编译生成的.s是汇编程序,结果如下:

	.file	"hello.c"
	.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	$42, -4(%rbp)
	movl	$0, %eax
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (GNU) 8.4.1 20200928 (Red Hat 8.4.1-1)"
	.section	.note.GNU-stack,"",@progbits

接着就是汇编,将汇编程序转换为ELF(Executable and Linkable Format)格式的目标.o程序,可通过gcc -c的方式,或直接调用as进行汇编:as -c hello.s -o hello.o

当拿到.o文件后就可以进行链接或者直接生成可执行程序,链接的话需要加载链接库,链接库有动态链接库和静态链接库。以上就是代码编译系统的过程。

那为什么要理解一个程序的编译过程呢?要理解编译系统的原因:

  • 理解编译系统可以优化程序的性能
  • 理解链接时出现的错误
  • 避免安全漏洞

接下来站在全局的角度大致了解硬件架构图,以便于我们了解程序执行的流程:
-w957

其中CPU(Central Processing Unit)中央处理单元包括:PC(Program Count)、寄存器堆(Register file)、ALU(Arithmatic/logic Unit)三部分。

那么执行一个hello程序,计算机内部会发生什么呢?(通常用户是在shell终端上执行代码的)
-w883
-w829
-w828

上图就是程序执行流程,概述如下:

  • IO设备键盘输入字符串"./hello",shell程序将输入字符读入寄存器,处理器会把hello字符通过系统总线和内存总线加载至内存(此时还没有按下回车)。
  • 当按下回车执行时,shell就知道我们已经完成命令输入,然后shell会执行一系列指令加载可执行文件hello。
  • 加载的过程实际上是将代码所需要的数据从磁盘拷贝至内存。拷贝的过程被称为DMA(Direct Memory Access),DMA技术可将数据不经过处理器,从磁盘直接加载至内存,我们知道CPU时间是非常宝贵的,磁盘读取本身就很慢,通过DMA技术,拷贝操作不经过处理器,这样就不会剥夺占用CPU时间,达到更加高效的作用(对计算密集型主机来说特别有帮助)。
  • 当可执行文件hello中的代码和数据被加载至内存中后,处理器就开始执行函数代码,那么一个程序运行起来就变为了进程。

那么上面说到了shell程序,其实更准确的说法应该是shell进程,进程是正在运行中的程序,当用户在shell中运行hello进程的时候,其实shell会发生中断,系统会保存其上下文信息,然后转而运行hello进程,当hello进程运行完毕后,又恢复shell进程上下文信息。大致形势如下:
-w795

在计算机系统中,每个进程都对应了4GB的虚拟内存地址,操作系统将实际硬件上的物理地址通过内存映射方式,将物理地址映射为连续大小4GB的虚拟内存地址空间,这4GB空间中,由低地址向高地址的3GB空间划分为用户空间,然后高地址部分的1GB划分为内核空间,专门用于保存系统级别数据信息。那么用户对一个程序的操作实际上是在用户空间进行的,划分如下:
-w719

关于系统加速,有以下三种定律:

  • 阿姆达尔定律
    -w1155
    -w796

  • 古斯塔法森定律
    -w1163

  • 孙-倪定律
    -w1163
    -w1141

三种模型关系:
-w867

关于并发、并行,首先什么是并发?什么是并行?
并行parallelise)同时刻(某点),即并行是在某一时刻上有多个任务在执行。
并发concurrency)同时间(某段),即并发是在某一时段内有多个任务在执行。

如何获得更高的计算力:

  • 线程级并发:增加CPU核心数提高系统行呢哥
  • 指令级并行:流水指令集
  • 单指令多数据并行:SIMD指令加速
posted @ 2023-07-13 19:38  miseryjerry  阅读(33)  评论(0编辑  收藏  举报