写给大忙人看的教程,一文形象生动地让你理解linux

Linux的知识范围广,一篇博客不可能全部讲解。因此,选取了最重要的内容,包括预处理、编译、链接,进程管理。结合实例讲解,专门写给大忙人,有图有知识点!

预处理

预处理的概念与作用

概念:预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。由预处理器对程序源代码文本进行处理,把源代码分割或处理成为特定的单位,得到的结果再由编译器核心进一步编译。这个过程并不对程序的源代码进行解析。

作用
1: 宏定义。宏定义是用一个标识符来表示一个字符串,这个字符串可以是常量、变量或表达式。在宏调用中将用该字符串代换宏名。
2:文件包含。文件包含是预处理的一个重要功能,它可用来把多个源文件连接成一个源文件进行编译,结果将生成一个目标文件。
3:条件编译。条件编译允许只编译源程序中满足条件的程序段,使生成的目标程序较短,从而减少了内存的开销并提高了程序的效率。

在Linux下预处理的命令

预处理命令:
gcc –E hello.c > hello.i
图2.2 在Linux下预处理的命令

图2.2 在Linux下预处理的命令

Hello的预处理结果解析

在这里插入图片描述

图2.3 hello.i文件

用文本编辑器打开hello.i,main函数的预处理解析结果如上图。
在main函数前出现的是stdio.h unistd.h stdlib.h头文件。
.i程序中是没有#define的,并使用了大量的#ifdef #ifndef的语句。
预处理指令会对条件值进行判断来决定是否执行包含其中的逻辑。

编译

概念与作用

  1. 编译的概念:利用编译程序从源语言编写的源程序产生目标程序的过程,用编译程序产生目标程序。 编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。
  2. 编译的作用:把高级语言变成计算机可以识别的2进制语言,词法分析、语法分析、语义检查和中间代码生成、代码优化、目标代码生成。

在Linux下编译的命令

gcc -S hello.i -o hello.s

在这里插入图片描述

图3.2 在Linux下编译的命令

Hello的编译结果解析

在linux用文本编辑器打开hello.s查看编译结果

数据段 作用
.text 已编译程序的机器代码
.rodata 只读数据
.data 已初始化的全局C变量
.bss 未初始化和初始化为0的全局C变量。在目标文件中这个节不占据实际的空间,仅仅是一个占位符
.symtab 一个符号表,它存放在程序中定义和引用的函数和全局变量的信息
.rel.text 一个.text节中位置的列表
.rel.data 被模块引用或定义的任何全局变量的重定位信息
.debug 一个调试符号表。只有以-g选项调用编译驱动程序才会得到这张表
.line 原始C源程序中的行号和.text节中机器指令之间的映射。只有以-g选项调用编译驱动程序时才会得到这张表
.strtab 一个字符串表,内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。

字符串表是以null结尾的字符串序列

3.31 数据

(1)字符串:
在这里插入图片描述

图3.311 字符串

(2)整数 sleepsecs
在这里插入图片描述

图3.312 整数 sleepsecs

3.32 赋值

(1) 全局变量sleepsecs =2
在这里插入图片描述

图3.321

(2) 局部变量i =0
在这里插入图片描述

图3.322 局部变量i =0

3.33 类型转换

隐式类型转换的是:int sleepsecs=2.5,将浮点数类型的2.5转换为int类型

3.34 算术操作
在这里插入图片描述

图3.340 算术操作符号

3.35 控制转移
在这里插入图片描述

图3.340 指令助记符

3.36 函数操作

a) int main(int argc, char *argv[])

(1)参数传递:从内核中获取命令行参数和环境变量地址

(2)函数调用:内核执行程序时调用特殊的启动例程,执行main函数

(3)函数返回:当命令行参数数量不为3时输出提示信息并调用exit(1)退出main函数;当命令行参数数量为3执行循环和getchar函数后return 0的方式退出函数。

argc: 传给main()的命令行参数个数
argv: 命令行参数字符型指针数组的首地址

b) exit()

(1)参数传递:getchar()函数无参数

(2)函数传递:main函数通过call指令调用getchar()

(3)函数返回:返回值类型为int,如果成功返回用户输入的ASCII码,出错返回-1

3.37关系操作

(1)argc!=3
在这里插入图片描述

图3.371 !=汇编代码

(2)i<10
在这里插入图片描述

图3.371 <汇编代码

汇编

概念与作用

  1. 概念:把汇编语言翻译成机器语言的过程称为汇编。在汇编语言中,用助记符代替操作码,用地址符号或标号代替地址码。通过用符号代替机器语言的二进制码,可以把机器语言变成汇编语言。
  2. 作用:将汇编语言翻译成机器语言。

编译 VS 汇编
编译:将高级语言程序变成计算机能识别的二进制语言
汇编:将汇编语言翻译成机器语言

在Linux下汇编的命令

汇编的命令:as hello.s -o hello.o
在这里插入图片描述

图4.2 在Linux下汇编的命令

4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

(1) ELF头
在这里插入图片描述

图4.311 ELF头

ELF头包括一个16字节的序列、ELF头的大小、目标文件的类型(如可重定位、可执行或共享的)、机器类型(如x86-64)、
节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。
其结构体表示:

#define EI_NIDENT 16
  typedef struct{
  unsigned char e_ident[EI_NIDENT];
  Elf32_Half e_type;
  Elf32_Half e_machine;
  Elf32_Word e_version;
  Elf32_Addr e_entry;
  Elf32_Off e_phoff;
  Elf32_Off e_shoff;
  Elf32_Word e_flags;
  Elf32_Half e_ehsize;
  Elf32_Half e_phentsize;
  Elf32_Half e_phnum;
  Elf32_Half e_shentsize;
  Elf32_Half e_shnum;
  Elf32_Half e_shstrndx;
}Elf32_Ehdr;

数据格式:在这里插入图片描述

图4.312 数据格式

(2) 节头部表:文件中出现的各个节的语义,包括节的类型、位置和大小
在这里插入图片描述

图4.32 节头部表

根据节头部表可知,当号=1,符号在.text;当号=3,符号在.data,以此类推。
三个特殊伪节:
ABS:不该被重定位的符号,如main()函数。
UND:其它文件中定义,本文件中引用的符号,如swap()函数。
COM:还未分配位置的未初始化数据目标,如buf2,它最终放在.bss。
(3) 重定位节

(a)普通重定位由以下数据结构定义:

typedef struct
{
Elf32_Addr r_offset; //指定需要重定位的项的位置
Elf32_Word r_info; //提供了符号表中的一个位置,包括重定位类型信息。
r_info == int symbol:24,type:8;
} Elf32_Rel;

(b)在ELF定义了32种不同的重定位类型,其中最基本的两种是:

R X86_ 64 PC32。 重定位一个使用32位PC相对地址的引用。一个PC相对地址就是距程序计数器(PC)的当前运行时值的偏移量。当CPU执行一条使用PC相对寻址的指令时,它就将在指令中编码的32位值加上PC的当前运行时值,得到有效地址(如call指令的目标),PC值通常是下一条指令在内存中的地址。
R X86_ 64 _32。 重定位一个使用32位绝对地址的引用。通过绝对寻址,CPU直接使用在指令中编码的32位值作为有效地址,不需要进一步修改。

©代码重定位条目放在.rel.text中。已经初始化数据的重定位条目放在.rel.data中。
main.c源文件引用了一个全局sleepsecs符号。
sleepsecs的重定位类型为相对重定位
并且由图4.33(1)可以得到:sleepsecs的r_offset : 000000000060重定位的字节处, 由图4.33(2)可以得到:sleepsecs的大小为4个字节
计算sleepsecs的重定位后的地址:Result = S-P+A
A代表加数值,S是符号表中保存的符号的值,P代表重定位的位置偏移量
在这里插入图片描述

图4.331重定位节

在这里插入图片描述

图4.332

(4) 符号表:存放着程序中定义和引用函数和全局变量的信息,不包含局部变量的条目
在这里插入图片描述

图4.34 符号表

Value:在对应节的偏移。
Size:目标大小。
Type:是数据或函数。
Bind:本地或全局。
Vis:预留。
Ndx:符号所在的节,其实是节头部表中条目的索引。
Name:符号名,为空的为链接器内部使用的本地符号,可以忽略。

4.4 Hello.o的结果解析

用命令行得到,比较hello.objdump与hello.o,进行对照分析
在这里插入图片描述

图4.40 命令行

(1) hello.objdump记录了文件格式和.text代码段:
而hello.s中除了记录了文件格式和.text代码段还包括.type .size .align以及.rodata
在这里插入图片描述

图4.41 hello.objdump与hello.s文件内容对比

(2) 分支转移:

hello.objdump跳转中地址为已确定的实际指令地址;
hello.s跳转中地址为助记符如.L2,通过使用例如.L2等的助记符进行跳转。
在这里插入图片描述
图4.42 hello.objdump与hello.s分支转移对比
(3)函数调用
在.s文件中,call的地址是函数名称,如puts@PLT,
而在反汇编程序中,call的目标地址是指令,如callq 21 <main+0x21>。因为hello.c中调用的函数都是共享库中的函数,共享库函数调用需要通过链接时重定位才能确定地址
在这里插入图片描述
图4.43 hello.objdump与hello.s函数puts调用对比
(4)全局变量访问
hello.objdump使用0+%rip访问全局变量sleepsecs,如lea 0x0(%rip),%rdi。hello.s使用段名称+%rip访问全局变量sleepsecs,如leaq .LC0(%rip), %rdi
在这里插入图片描述
图4.44 hello.objdump与hello.s全局变量sleepsecs访问对比

链接

5.1 链接的概念与作用

链接的概念:Linux 链接分两种,一种被称为硬链接(Hard Link),另一种被称为符号链接(Symbolic Link)。默认情况下,ln 命令产生硬链接。
(1)硬连接指通过索引节点来进行连接。在 Linux 的文件系统中,保存在磁盘分区中的文件不管是什么类型都给它分配一个编号,称为索引节点号(Inode Index)。在 Linux 中,多个文件名指向同一索引节点是存在的。
(2)软连接。软链接文件是一个特殊的文件。在符号连接中,文件实际上是一个文本文件,其中包含的有另一文件的位置信息。
作用:链接操作给系统中已有的某个文件指定另外一个可用于访问它的名称。我们可以为这个新的文件名指定不同的访问权限。链用户可以利用链接直接进入被链接的目录。即使删除这个链接,也不会破坏原来的目录。硬连接的作用是允许一个文件拥有多个有效路径名,用户就可以建立硬连接到重要文件,以防止“误删”的功能。

5.2 在Linux下链接的命令

使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
链接的命令:ld -o OUTPUT /lib/crt0.o hello.o –lc
链接的命令行:ld -o hello -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o
在这里插入图片描述
图5.2 在Linux下链接的命令

5.3 可执行目标文件hello的格式

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
使用命令行readelf -a hello > hello1.elf生成hello1.elf文件
节头表中包含了各段的起始地址,大小等信息。

在这里插入图片描述
图5.31 节头表
在这里插入图片描述
图5.32 节头表

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。

(1) 分析程序头部表。

PHDR:程序头表
INTERP:程序执行前需要调用的解释器
LOAD:程序目标代码和常量信息
DYNAMIC:动态链接器所使用的信息
NOTE::辅助信息
GNU_EH_FRAME:保存异常信息
GNU_STACK:使用系统栈所需要的权限信息
GNU_RELRO:保存在重定位之后只读信息的位置
VirtAddr:本段首字节的虚拟地址
PhysAddr指出本段首字节的物理地址
pFileSiz指出本段在文件中所占的字节数,可以为0
MemSiz指出本段在存储器中所占字节数,可以为0
Flags指出存取权限,Align指出对齐方式
在这里插入图片描述
图5.41 程序头部表

(2) 在edb查看hello的虚拟地址空间的各段信息
在这里插入图片描述
图5.42 hello的虚拟地址空间

(3) 程序头与Datadump的映射关系:例如PHDR对应的虚拟内存地址是0x400000—— 0x4001c0
在这里插入图片描述
图5.43 程序头与Datadump的映射关系

5.5 链接的重定位过程分析

通过命令行objdump –d –r hello > hello.txt得到反汇编文件hello.txt。

(1) hello的反汇编结果与hello.o的反汇编结果相比,hello.txt多了以下节头表:

_init 程序初始化代码
gmon_start call_gmon_start函数初始化
gmon profiling system,程序通过gprof可以输出函数调用等信息
_dl_relocate_static_pie 静态库链接
.plt 动态链接-过程链接表
Puts(等函数)@plt 动态链接各个函数
_start 编译器为可执行文件加上了一个启动例程
__libc_csu_init 程序调用libc库用来对程序进行初始化的函数,一般先于main函数执行
_fini 当程序正常终止时需要执行的代码

(2) 函数个数:在使用ld命令链接的时候,指定了动态链接器为64的/lib64/ld-linux-x86-64.so.2,crt1.o、crti.o、crtn.o中主要定义了程序入口_start、初始化函数_init,_start程序调用hello.c中的main函数,libc.so是动态链接共享库,链接器加入了以下函数printf、sleep、getchar、exit函数和_start中调用的__libc_csu_init,__libc_csu_fini,__libc_start_main。

函数调用:链接器解析重定条目时发现对外部函数调用的类型为R_X86_64_PLT32的重定位,此时动态链接库中的函数已经加入到了PLT中,.text与.plt节相对距离已经确定,链接器计算相对距离,将对动态链接库中函数的调用值改为PLT中相应函数与下条指令的相对地址,指向对应函数。
rodata引用:链接器解析重定条目时发现两个类型为R_X86_64_PC32的对.rodata的重定位(printf中的两个字符串),.rodata与.text节之间的相对距离确定,因此链接器直接修改call之后的值为目标地址与下一条指令的地址之差,指向相应的字符串。

(3) 重定位过程。hello反汇编文件中对应全局变量已通过重定位绝对引用被替换为固定地址。

5.6 hello的执行流程

(以下格式自行编排,编辑时删除)
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
ld-2.27.so!_dl_start
ld-2.27.so!_dl_init
hello!_start
libc-2.27.so!__libc_start_main
libc-2.27.so!__cxa_atexit
libc-2.27.so!__libc_csu_init
hello!_init
libc-2.27.so!_setjmp
libc-2.27.so!_sigsetjmp
libc-2.27.so!__sigjmp_save
hello!main
hello!puts@plt
hello!exit@plt
hello!printf@plt
hello!sleep@plt
hello!getchar@plt
ld-2.27.so!_dl_runtime_resolve_xsave
ld-2.27.so!_dl_fixup
ld-2.27.so!_dl_lookup_symbol_x
libc-2.27.so!exit

5.7 Hello的动态链接分析

(1)编译器无法确定动态链接库中的函数地址,因为动态链接库中的函数在程序执行的时候才会确定地址。GNU编译系统采用延迟绑定技术来解决动态库函数模块调用的问题。

(2)延迟绑定通过全局偏移量表(GOT)和过程链接表(PLT)实现。

(a)PLT是一个数组,其中每个条目是16字节代码。每个库函数都有自己的PLT条目,PLT[0]是一个特殊的条目,跳转到动态链接器中。从PLT[2]开始的条目调用用户代码调用的函数。

(b)GOT同样是一个数组,每个条目是8字节的地址,和PLT联合使用时,GOT[2]是动态链接在ld-linux.so模块的入口点,其余条目对应于被调用的函数,在运行时被解析。每个条目都有匹配的PLT条目。

(3)延迟绑定的实现步骤如下:
a.建立一个 GOT.PLT 表,用来放全局函数的实际地址
b.对每一个全局函数,链接器生成一个与之相对应的函数,如 puts@plt。
c.所有的puts都换成对 puts@plt。

(4)下面分析在dl_init调用前后,项目的内容的变化
a)dl_init调用前
在这里插入图片描述
图5.71 dl_init调用前GOT条目
b)dl_init调用后, GOT条目初始时指向其PLT条目的第二条指令的地址
在这里插入图片描述
图5.72 dl_init调用后GOT条目

进程管理

6.1 概念与作用

进程的概念:进程是正在运行的程序的实例,是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。进程是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
进程的作用:进程提供两个假象,程序独占地使用处理器和程序在独占地使用系统内存。

6.2 简述壳Shell-bash的作用与处理流程

Shell-bash的作用:shell和其他软件一样都是和内核打交道,直接服务于用户。但和其他软件不同,shell主要用来管理文件和运行程序。
处理流程:shell对命令行的处理流程
(1)读取输入的命令行。
(2)解析引用并分割命令行为各个单词,其中重定向所在的单词会被保存下来,直到扩展步骤(5)结束后才进行相关处理。
(3)检查命令行结构。
(4)对第一个单词进行别名扩展。
(5)进行各种扩展。扩展顺序为:大括号扩展;波浪号扩展;参数、变量和命令替换、算术扩展;单词拆分;文件名扩展。
(6)引号去除。
(7)搜索和执行命令。
(8)返回退出状态码。

6.3 Hello的fork进程创建过程

普通的系统调用,调用一次就返回一次,而fork()调用一次,会返回两次,一次是父进程,另一个是子进程,互不干扰,调用的先后顺序由操作系统的调度算法决定。子进程永远返回0,父进程则返回子进程的ID。

fork的进程图为:
在这里插入图片描述
图6.3 fork的进程图

6.4 Hello的execve过程

execve 函数加载并运行可执行目标文件 filename, 且带参数列表 argv 和环境变量列表 envp 。只有当出现错误时,例如找不到 filename, execve 才会返回到调用程序。所以,与 fork 一次调用返回两次不同, execve 调用一次并从不返回。

6.5 Hello的进程执行

结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。

(1)上下文及上下文切换:进程的物理实体(代码和数据等)和支持进程运行的环境。系统通过处理器调度让处理器轮流执行多个进程,实现不同进程中指令交替执行的机制称为进程的上下文切换。

(2)进程时间片:连续执行同一个进程的时间段称为时间片

(3)用户态与核心态转换:处理器通过某个控制寄存器中的一个模式位来提供限制一个应用可以执行的指令以及它可以访问的地址空间范围的功能。当设置了模式位时,进程就运行在内核模式中。没有设置模式位时,进程就运行在用户模式中。

(4)Hello进程调度的过程以及用户态与核心态的转换
调度是在进程执行的某些时刻,内核可以决定抢占当前进程并重新开始一个先前被抢占了的进程的决策。在切换的第一部分中,内核代表进程A在内核模式下执行指令。然后在某一时刻,shell加载可执行目标文件hello。在上下文切换之后,内核代表进程hello在用户模式下执行指令。之后进程hello在用户模式下运行,直到磁盘发出一个中断信号,执行一个从进程hello到进程A的上下文切换,将控制返回给进程A,进程A继续运行,直到下一次异常发生。
在这里插入图片描述
图6.5 Hello进程调度的过程以及用户态与核心态的转换

6.6 hello的异常与信号处理

hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
Hello执行过程出现的异常为:中断、故障
会产生的信号为:SIGSTP 来自终端的停止信号,SIGINT 来自键盘的中断

(1) 正常终止
在这里插入图片描述
图6.61 正常终止

(2) Ctrl + C
当按下ctrl-c之后,shell父进程收到SIGINT信号,信号处理程序结束hello,并回收hello进程。
在这里插入图片描述
图6.62 按Ctrl + C时

(3) Ctrl + Z
当按下ctrl-z之后,
(a)shell父进程收到SIGSTP信号,
(b)信号处理程序打印并将hello进程挂起,
©通过ps命令看到hello进程没有被回收,
通过jobs命令看到hello进程的号为1,
通过pstree命令可以看出:之后调用fg 1将其调到前台,执行相应命令行
在这里插入图片描述

图6.63 按Ctrl + Z时

(4) 中途乱按
中途乱按不导致异常和产生信号
在这里插入图片描述
图6.64 中途乱按时

如果有收获?希望来个两连击,给更多的人看到这篇文章

1、关注我的原创微信公众号「程序猿的进阶」,主要是IT与竞赛

2、创作不易,顺便点个赞呗,可以让更多的人看到这篇文章,激励一下我这个小白

发布了110 篇原创文章 · 获赞 154 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/JAck_chen0309/article/details/104794824