正在上传…重新上传取消
题 目 程序人生-Hello’s P2P
专 业 人工智能(未来技术)
学 号 7203610615
班 级 2036016
学 生 张佳欣
指 导 教 师 史先俊
本论文旨在研究hello在linux系统下的整个生命周期。结合CSAPP课本,通过gcc,edb实验等工具,从而实施和整合教科书知识,通过程序深入挖掘知识点,对学生对课程的理解和知识的升华有很大的帮助。
预处理;编译;汇编;链接;过程;存储;IO管理
1.1 Hello简介... - 4 -
1.2 环境与工具... - 4 -
1.3 中间结果... - 4 -
1.4 本章小结... - 4 -
2.1 预处理的概念和作用... - 5 -
2.2在Ubuntu下一个预处理命令... - 5 -
2.3 Hello预处理结果分析... - 5 -
2.4 本章小结... - 5 -
3.1 编译的概念与作用......................................................................................... - 6 -
3.2 在Ubuntu下编译的命令............................................................................. - 6 -
3.3 Hello的编译结果解析.................................................................................. - 6 -
3.4 本章小结......................................................................................................... - 6 -
4.1 汇编的概念与作用......................................................................................... - 7 -
4.2 在Ubuntu下汇编的命令............................................................................. - 7 -
4.3 可重定位目标elf格式................................................................................. - 7 -
4.4 Hello.o的结果解析...................................................................................... - 7 -
4.5 本章小结......................................................................................................... - 7 -
5.1 链接的概念与作用......................................................................................... - 8 -
5.2 在Ubuntu下链接的命令............................................................................. - 8 -
5.3 可执行目标文件hello的格式.................................................................... - 8 -
5.4 hello的虚拟地址空间.................................................................................. - 8 -
5.5 链接的重定位过程分析................................................................................. - 8 -
5.6 hello的执行流程.......................................................................................... - 8 -
5.7 Hello的动态链接分析.................................................................................. - 8 -
5.8 本章小结......................................................................................................... - 9 -
6.1 进程的概念与作用....................................................................................... - 10 -
6.2 简述壳Shell-bash的作用与处理流程..................................................... - 10 -
6.3 Hello的fork进程创建过程..................................................................... - 10 -
6.4 Hello的execve过程................................................................................. - 10 -
6.5 Hello的进程执行........................................................................................ - 10 -
6.6 hello的异常与信号处理............................................................................ - 10 -
6.7本章小结....................................................................................................... - 10 -
7.1 hello的存储器地址空间............................................................................ - 11 -
7.2 Intel逻辑地址到线性地址的变换-段式管理............................................ - 11 -
7.3 Hello的线性地址到物理地址的变换-页式管理....................................... - 11 -
7.4 TLB与四级页表支持下的VA到PA的变换............................................. - 11 -
7.5 三级Cache支持下的物理内存访问.......................................................... - 11 -
7.6 hello进程fork时的内存映射.................................................................. - 11 -
7.7 hello进程execve时的内存映射.............................................................. - 11 -
7.8 缺页故障与缺页中断处理........................................................................... - 11 -
7.9动态存储分配管理....................................................................................... - 11 -
7.10本章小结..................................................................................................... - 12 -
8.1 Linux的IO设备管理方法.......................................................................... - 13 -
8.2 简述Unix IO接口及其函数....................................................................... - 13 -
8.3 printf的实现分析........................................................................................ - 13 -
8.4 getchar的实现分析.................................................................................... - 13 -
8.5本章小结....................................................................................................... - 13 -
1.1 Hello简介
P2P指的是Program To(two) Process,是指从程序向进程的转变过程。以hello.c为例,在gcc编译器中,hello.c经过预处理器cpp预处理生成hello.i,hello.i传入编译器ccl生成汇编语言程序hello.s,hello.s经过汇编器as生成可重定位目标程序hello.o,hello.o通过链接器ld完成静态或动态链接后,生成可执行文件hello.o。在shell中键入启动命令,shell为hello进行fork子进程,hello实现了From Program to Process。
O2O过程指zero to zero。Shell为 hello的代码、数据、bss和栈区域创建新的区域结构,然后映射共享区域,设置程序计数器,映射虚拟内存,然后加载物理内存。完成后进入main函数,执行目标代码,CPU为运行的hello分配时间片执行逻辑控制流。当程序结束,shell的父进程负责回收hello进程,内核删除相关数据结构,这就是hello的020过程。
1.2 环境与工具
VMware15.5.7
Gcc version 9.4.0
Visual studio 2022
CodeBlocks
Gedit+gcc
1.3 中间结果
hello.i :预处理产生的文件
hello.o :汇编后产生的可重定位的目标文件
hello.s :预处理后产生的文件
hello.objdump:hello的反汇编代码文件
hello.bin :读取hello.o的ELF格式文件
hello.txt:hello.o的反汇编文件
hello.o:经过链接后的可执行目标文件
正在上传…重新上传取消
1.4 本章小结
本章首先介绍了P2P,020模式,列出了用于实验的硬件软件环境,以及相关调试工具,最后列出来过程中的中间文件
2.1 预处理的概念与作用
概念:预处理器(cpp)根据以字符#开头的命令(宏定义、条件编译),修改原始的C程序,读取头文件的内容,并将其字节插入程序文本中,结果会得到另一个C程序,通常是以.i作为文件拓展名。具体操作主要有:对#include的文件进行包含;对代码中#define定义的常量进行替换;对代码中的所有注释进行删除。
作用:头文件经常#include其他头文件。包含多个头文件是普遍的,一个头文件也会被多次包含进同一个源文件。这个时候要保证编译的时候多次包含同一头文件不会引起该头文件定义的类和对象被多次定义,这个时候就需要预处理器,保证编译过程中只会包含定义一次。在编写头文件之前,我们需要引入一些额外的预处理器设施。预处理器允许自定义变量。
2.2在Ubuntu下预处理的命令
输入命令:gcc hello.c -E -o hello.i
正在上传…重新上传取消
2.3 Hello的预处理结果解析
正在上传…重新上传取消
会发现在预编译文件的前面,所有头文件中的函数被写入预编译文件,展开宏,同时去掉了重复的部分。最后部分,main函数和hello.c文件中的形式没有太大区别。
2.4 本章小结
本章主要介绍了预处理的概念和预处理的功能,以及Ubuntu下预处理的命令,并对预处理结果hello.i进行了分析,发现预处理会进行头文件的展开、宏替换、清除注释、条件编译等操作。
3.1 编译的概念与作用
概念:程序编译,是用户使用编译程序对其个人编制的源程序进行编译的过程。
作用:编译后生成的.s文本文件是汇编语言程序,更容易让计算机理解,编译是将程序转换为机器可直接识别处理执行的机器指令的中间过程。
编译过程中的操作包括词法分析、语法分析、语义检查与中间代码生成、中间代码优化、目标代码生成几个部分。
许多汇编程序为程序开发、汇编控制、辅助调试提供了额外的支持机制。有的汇编语言编程工具经常会提供宏,它们也被称为宏汇编器。
3.2 在Ubuntu下编译的命令
编译命令:gcc -S hello.i -o hello.s
正在上传…重新上传取消
3.3 Hello的编译结果解析
正在上传…重新上传取消
正在上传…重新上传取消
用文本编辑器查看hello.s内容
3.3.1数据
(1)宏
.file |
声明源文件 |
.text |
声明代码段 |
.data |
声明数据段 |
(2)字符串
在第六行和第八行分别有一个字符串
正在上传…重新上传取消
这个位置表示只读数据。同时,在代码中发现对这两个字符串的使用分别在26和41行
正在上传…重新上传取消
(3)变量
代码中使用的变量有局部变量i和参数argc,以变量i为例,在代码中声名如下:
正在上传…重新上传取消
正在上传…重新上传取消
main函数中声明了局部变量i,将其放在地址为 -4(%rbp)的堆栈中。
3.3.2赋值
在汇编语言中,赋值操作主要由mov指令完成,movl指令的意思是传送双字。
正在上传…重新上传取消
此处movl将变量i赋值为0。
3.3.3类型转换
Atoi函数将字符串转换为整型数据,对应C语言中的getchar语句
正在上传…重新上传取消
3.3.4算术操作
Hello.s的汇编代码中使用了addl操作,对应C语言中的语句i++
正在上传…重新上传取消
3.3.5逻辑/位操作
正在上传…重新上传取消
3.3.6关系操作
汇编代码中有两处关系操作,均为比较大小,je表示相等跳转,jle表示大于等于跳转。对应C语言代码分别为argc!=4和i<8
正在上传…重新上传取消
正在上传…重新上传取消
正在上传…重新上传取消
3.3.7数组
汇编代码中数组部分的循环如图所示:
正在上传…重新上传取消
argv[2]保存在寄存器%rdx中,因此可推断argv[2]地址为-0x16(%rbp);argv[1]保存在寄存器%rsi中,因此可推断argv[1]地址为-0x2A(%rbp)中,数组首地址位于-0x32(%rbp) ,以上所占字节数为8。
3.3.8控制转移
程序中涉及的控制转移有:
- if (argv != 3):当argv不等于3的时候执行程序段中的代码。如图3.6,对于if判断,编译器使用跳转指令实现,首先cmpl比较argv和3,设置条件码,使用je判断ZF标志位,如果为0,说明argv - 3 = 0,即argv = 3,则不执行if中的代码直接跳转到.L2,否则顺序执行下一条语句,即执行if中的代码。
- 正在上传…重新上传取消
2. for (i = 0; i < 10; i++):使用计数变量i循环10次。如图3.7,编译器的编译逻辑是,首先无条件跳转到位于循环体.L4之后的比较代码,使用cmpl进行比较,如果i <= 9,则跳入.L4中的for循环体执行,否则说明循环结束,顺序执行for之后的逻辑。
3.3.8函数操作
(1).print函数
正在上传…重新上传取消
(2).exit
正在上传…重新上传取消
(3). sleep函数
正在上传…重新上传取消(4).getchar函数
正在上传…重新上传取消
(5).atoi函数
正在上传…重新上传取消
3.4 本章小结
本章主要阐述了编译器是如何处理C语言的各个数据类型以及各类操作的,基本都是先给出原理然后结合hello.c,C程序到hello.s汇编代码之间的映射关系作出合理解释。
编译器将.i的拓展程序编译为.s的汇编代码。经过编译之后,我们的hello自C语言解构为更加低级的汇编语言。
4.1 汇编的概念与作用
汇编器(as)将.s汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在.o目标文件中,.o文件是一个二进制文件,它包含程序的指令编码。这个过程称为汇编,亦即汇编的作用。
4.2 在Ubuntu下汇编的命令
正在上传…重新上传取消
4.3 可重定位目标elf格式
使用readelf -a hello.o > hello.elf指令获取hello的elf格式文件
正在上传…重新上传取消
ELF头:
ELF头(ELF header)以一个16字节的序列开始,这个序列描述了生成该文件的系统的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。如下图所示:
正在上传…重新上传取消
节头部表: 节头部表描述了不同节的位置和大小,其中目标文件中每个节都有一个固定大小的条目。具体的描述包括节的名称、类型、地址和偏移量等。夹在ELF头和节头部表之间的都是节。如下图所示:
正在上传…重新上传取消
rel.text节:
一个.text节中位置的列表,包含.text节中需要进行重定位的信息,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。
下图的重定位节中有8条重定位信息,分别为对第一个字符串内容(.L0)、puts函数、exit函数、第二个字符串内容(.L1)、printf函数、atoi函数、sleep函数、getchar函数的重定位声明。
正在上传…重新上传取消
symtab节: 一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。每个可重定位目标文件在.symtab中都有一张符号表。如图所示:
正在上传…重新上传取消
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,
正在上传…重新上传取消
使用objdump -d -r hello.o > hello.objdump获得反汇编代码。
总体观察图4.6后发现,除去显示格式之外两者差别不大,主要差别如下:
1. 分支转移:反汇编代码跳转指令的操作数使用的不是段名称如.L3,因为段名称只是在汇编语言中便于编写的助记符,所以在汇编成机器语言之后显然不存在,而是确定的地址。
2. 函数调用:在.s文件中,函数调用之后直接跟着函数名称,而在反汇编程序中,call的目标地址是当前下一条指令。这是因为hello.c中调用的函数都是共享库中的函数,最终需要通过动态链接器才能确定函数的运行时执行地址,在汇编成为机器语言的时候,对于这些不确定地址的函数调用,将其call指令后的相对地址设置为全0(目标地址正是下一条指令),然后在.rela,.text节中为其添加重定位条目,等待静态链接的进一步确定。
3. 全局变量访问:在.s文件中,访问rodata(printf中的字符串),使用段名称+%rip,在反汇编代码中0+%rip,因为rodata中数据地址也是在运行时确定,故访问也需要重定位。所以在汇编成为机器语言时,将操作数设置为全0并添加重定位条目。
4.5 本章小结
本章介绍了hello从hello.s到hello.o的汇编过程,通过查看hello.o的elf格式和使用objdump得到反汇编代码与hello.s进行比较的方式,间接了解到从汇编语言映射到机器语言汇编器需要实现的转换。
5.1 链接的概念与作用
链接阶段:
链接(linking)是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时,也就是在源代码被翻译成机器代码时;也可以执行于加载时,也就是在程序被加载器(loader)加载到内存并执行时;甚至于运行时,也就是由应用程序来执行。在现代系统中,链接是由叫做链接器(linker)的程序自动执行的。链接器在软件开发中扮演着一个关键的角色,因为它们使得分离编译成为可能。
5.2 在Ubuntu下链接的命令
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
执行命令即可生成可执行程序hello
正在上传…重新上传取消
5.3 可执行目标文件hello的格式
Elf文件一般包含如下内容:
正在上传…重新上传取消
夹在ELF头和节头部表之间的都是节。一个典型的ELF可重定位目标文件包含下面几个节: .text:已编译程序的机器代码。 .rodata: 只读数据,比如printf语句中的格图7-3 典型的ELF可重定位目标文件式串和开关语句的跳转表。 .data:已初始化的全局和静态C变量。局部C变量在运行时被保存在栈中,既不出现在.data节中,也不出现在.bss节中。 .bss:未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。目标文件格式区分已初始化和未初始化变量是为了空间效率:在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。运行时,在内存中分配这些变量,初始值为0。 .symtab: 一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。一些程序员错误地认为必须通过-g选项来编译-一个程序,才能得到符号表信息。实际上,每个可重定位目标文件在.symtab中都有一-张符号表(除非程序员特意用STRIP命令去掉它)。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的条目。 .rel.text: 一个.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。注意,可执行目标文件中并不需要重定位信息,因此通常省略,除非用户显式地指示链接器包含这些信息。 .rel.data:被模块引用或定义的所有全局变量的重定位信息。一般而言,任何已初始化的全局变量,如果它的初始值是-一个全局变量地址或者外部定义函数的地址,都需要被修改。 .debug: 一个调试符号表, 其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C源文件。只有以-g选项调用编译器驱动程序时,才会得到这张表。 .line: 原始C源程序中的行号和.text节中机器指令之间的映射。只有以-g选项调用编译器驱动程序时,才会得到这张表。 .strtab: 一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。字符串表就是以null结尾的字符串的序列。
使用readelf -a hello > hello.elf命令生成hello程序的ELF格式文件。
在ELF格式文件中,Section Headers对hello中所有的节信息进行了声明,其中包括大小Size以及在程序中的偏移量Offset,因此根据Section Headers中的信息我们就可以用HexEdit定位各个节所占的区间(起始位置,大小)。其中Address是程序被载入到虚拟地址的起始地址。
正在上传…重新上传取消
5.4 hello的虚拟地址空间
使用edb打开hello程序,通过edb的Data Dump窗口查看加载到虚拟地址中的hello程序。
在0x400000~0x401000段中,程序被载入,自虚拟地址0x400000开始,自0x400fff结束,这之间每个节(开始~ .eh_frame节)的排列即开始结束同图5.2中 Address中声明。
如图5.3,查看ELF格式文件中的Program Headers,程序头表在执行的时候被使用,它告诉链接器运行时加载的内容并提供动态链接的信息。每一个表项提供了各段在虚拟地址空间和物理地址空间的大小、位置、标志、访问权限和对齐方面的信息。在下面可以看出,程序包含8个段:
1. PHDR保存程序头表。
2. INTERP指定在程序已经从可执行文件映射到内存之后,必须调用的解释器(如动态链接器)。
3. LOAD表示一个需要从二进制文件映射到虚拟地址空间的段。其中保存了常量数据(如字符串)、程序的目标代码等。
4. DYNAMIC保存了由动态链接器使用的信息。
5. NOTE保存辅助信息。
6. GNU_STACK:权限标志,标志栈是否是可执行的。
7. GNU_RELRO:指定在重定位结束之后那些内存区域是需要设置只读。
正在上传…重新上传取消通过Data Dump查看虚拟地址段0x600000~0x602000,在0~fff空间中,与0x400000~0x401000段的存放的程序相同,在fff之后存放的是.dynamic~.shstrtab节。
5.5 链接的重定位过程分析
使用objdump -d -r hello > hello.objdump获得hello的反汇编代码。
与hello.o反汇编文本hello.o.objdump相比,在hello.objdump中多了许多节,如下。
节名称 |
描述 |
.interp |
保存 ld.so 的路径 |
.note,.ABI-tag |
Linux下特有的section |
.hash |
符号的哈希表 |
.gnu.hash |
GNU 拓展的符号的哈希表 |
.dynsym |
运行时/动态符号表 |
.dynstr |
存放.dynsym节中的符号名称 |
.gnu.version |
符号版本 |
.gnu.version_r |
符号引用版本 |
.rela.dyn |
运行时/动态重定位表 |
.rela.plt |
.plt 节的重定位条目 |
.init |
程序初始化需要执行的代码 |
.plt |
动态链接-过程链接表 |
.fini |
当程序正常终止时需要执行的代码 |
.eh_frame |
contains exception unwinding and source language information. |
.dynamic |
存放被 ld.so 使用的动态链接信息 |
.got |
动态链接-全局偏移量表-存放变量 |
.got.plt |
动态链接-全局偏移量表-存放函数 |
.data |
初始化了的数据 |
.comment |
一串包含编译器的NULL-terminated字符串 |
通过比较hello.objdump和helloo.objdump了解链接器。
1. 函数个数:在使用ld命令链接的时候,指定了动态链接器为64的/lib64/ld-linux-x86-64.so.2,crt1.o、crti.o、crtn.o中主要定义了程序入口_start、初始化函数_init,_start程序调用hello.c中的main函数,libc.so是动态链接共享库,其中定义了hello.c中用到的printf、sleep、getchar、exit函数和_start中调用的__libc_csu_init,__libc_csu_fini,__libc_start_main。链接器将上述函数加入。
2. 函数调用:链接器解析重定条目时发现对外部函数调用的类型为R_X86_64_PLT32的重定位,此时动态链接库中的函数已经加入到了PLT中,.text与.plt节相对距离已经确定,链接器计算相对距离,将对动态链接库中函数的调用值改为PLT中相应函数与下条指令的相对地址,指向对应函数。对于此类重定位链接器为其构造.plt与.got.plt。
3. .rodata引用:链接器解析重定条目时发现两个类型为R_X86_64_PC32的对.rodata的重定位(printf中的两个字符串),.rodata与.text节之间的相对距离确定,因此链接器直接修改call之后的值为目标地址与下一条指令的地址之差,指向相应的字符串。这里以计算第一条字符串相对地址为例说明计算相对地址的算法(算法说明同4.3节):
refptr = s + r.offset = Pointer to 0x40054A
refaddr = ADDR(s) + r.offset= ADDR(main)+r.offset=0x400532+0x18=0x40054A
*refptr = (unsigned)(ADDR(r.symbol) + r.addend-refaddr) = ADDR(str1) + r.addend - refaddr = 0x400644 + (-0x4) - 0x40054A = (unsigned)0xF6。
观察反汇编验证计算:
正在上传…重新上传取消
其他.rodata引用,函数调用原理类似。
5.6 hello的执行流程
使用edb执行hello,观察函数执行流程,将过程中执行的主要函数列在下面:
程序名称 |
程序地址 |
ld-2.27.so!_dl_start |
0x7fce 8cc38ea0 |
ld-2.27.so!_dl_init |
0x7fce 8cc47630 |
hello!_start |
0x400500 |
libc-2.27.so!__libc_start_main |
0x7fce 8c867ab0 |
-libc-2.27.so!__cxa_atexit |
0x7fce 8c889430 |
-libc-2.27.so!__libc_csu_init |
0x4005c0 |
hello!_init |
0x400488 |
libc-2.27.so!_setjmp |
0x7fce 8c884c10 |
-libc-2.27.so!_sigsetjmp |
0x7fce 8c884b70 |
--libc-2.27.so!__sigjmp_save |
0x7fce 8c884bd0 |
hello!main |
0x400532 |
hello!puts@plt |
0x4004b0 |
hello!exit@plt |
0x4004e0 |
*hello!printf@plt |
—— |
*hello!sleep@plt |
—— |
*hello!getchar@plt |
—— |
ld-2.27.so!_dl_runtime_resolve_xsave |
0x7fce 8cc4e680 |
-ld-2.27.so!_dl_fixup |
0x7fce 8cc46df0 |
--ld-2.27.so!_dl_lookup_symbol_x |
0x7fce 8cc420b0 |
libc-2.27.so!exit |
0x7fce 8c889128 |
5.7 Hello的动态链接分析
动态链接是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序。虽然动态链接把链接过程推迟到了程序运行时,但是在形成可执行文件时,还是需要用到动态链接库。比如我们在形成可执行程序时,发现引用了一个外部的函数,此时会检查动态链接库,发现这个函数名是一个动态链接符号,此时可执行程序就不对这个符号进行重定位,而把这个过程留到装载时再进行。在dl_init调用之前,GOT存放PLT函数调用的下一条指令地址。
PLT函数调用的下一条指令地址。
正在上传…重新上传取消 |
图41 ELF文件中.got.plt指向的内存地址 |
正在上传…重新上传取消 |
图41 执行dl_init前 |
正在上传…重新上传取消 |
图42 执行dl_init后 |
在本章中主要介绍了链接的概念与作用、hello的ELF格式,分析了hello的虚拟地址空间、重定位过程、执行流程、动态链接过程。
6.1 进程的概念与作用
进程(process)的经典定义就是一个执行中程序的实例。系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。每次用户通过向shell输入一个可执行目标文件的名字,运行程序时,shell就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件。应用程序也能够创建新进程,并且在这个新进程的上下文中运行它们自己的代码或其他应用程序。我们将关注进程提供给应用程序的关键抽象:
一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器。
一个私有的地址空间,它提供一个假象,好像我们的程序独占地使用内存系统。
6.2 简述壳Shell-bash的作用与处理流程
Shell的作用:Shell是一个用C语言编写的程序,他是用户使用Linux的桥梁。Shell是指一种应用程序,Shell应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。
处理流程:
1. 从终端读入输入的命令。
2. 将输入字符串切分获得所有的参数。
3. 如果是内置命令则立即执行。
4. 否则调用相应的程序为其分配子进程并运行。
5. shell应该接受键盘输入信号,并对这些信号进行相应处理。
6.3 Hello的fork进程创建过程
终端Gnome-Terminal中键入./hello 7203610615zjx,运行的终端程序会对输入的命令行进行解析,因为hello不是一个内置的shell命令所以解析之后终端程序判断./hello的语义为执行当前目录下的可执行目标文件hello,之后终端程序首先会调用fork函数创建一个新的运行的子进程,新创建的子进程几乎但不完全与父进程相同,子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,这就意味着,当父进程调用fork时,子进程可以读写父进程中打开的任何文件。父进程与子进程之间最大的区别在于他们的PID不同。
父进程与子进程是并发运行的独立进程,内核能够以任意方式交替执行它们的逻辑控制流的指令。在子进程执行期间,父进程默认选项是显示等待子进程的完成。
简单进程图如下:
正在上传…重新上传取消
6.4 Hello的execve过程
当fork之后,子进程调用execve函数(传入命令行参数)在当前进程的上下文中加载并运行一个新程序即hello程序,execve调用驻留在内存中的被称为启动加载器的操作系统代码来执行hello程序,加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零,通过将虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码和数据段被初始化为可执行文件中的内容。最后加载器设置PC指向_start地址,_start最终调用hello中的main函数。除了一些头部信息,在加载过程中没有任何从磁盘到内存的数据复制。直到CPU引用一个被映射的虚拟页时才会进行复制,这时,操作系统利用它的页面调度机制自动将页面从磁盘传送到内存。
加载器创建的内存映像如下:
正在上传…重新上传取消
6.5 Hello的进程执行
1. 逻辑控制流:一系列程序计数器PC的值的序列叫做逻辑控制流,进程是轮流使用处理器的,在同一个处理器核心中,每个进程执行它的流的一部分后被抢占(暂时挂起)然后轮到其他进程。
2. 时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。
3. 用户模式和内核模式:处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中,用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。
4. 上下文信息:上下文就是内核重新启动一个被抢占的进程所需要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。
简单看hello sleep进程调度的过程:当调用sleep之前,如果hello程序不被抢占则顺序执行,假如发生被抢占的情况,则进行上下文切换,上下文切换是由内核中调度器完成的,当内核调度新的进程运行后,它就会抢占当前进程,并进行
(1)保存以前进程的上下文。