资讯详情

2021-06-27

本文通过hello程序从头到尾的生命周期,对计算机系统各章节的内容进行了回顾和串联。探究了hello通过预处理、编译、汇编、链接等步骤,程序源代码成为可执行程序,并通过流程管理、存储管理IO在操作系统中运行和结束管理等系统机制的整个过程 。体现了hello程序的“P2P”(From Program to Process)和“O2O”(From Zero-0 to Zero-0)。

Hello程序;预处理;编译;汇编;链接;流程管理;存储管理;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.1 Hello简介

从源文件到目标文件的转化是由编译器驱动程序完成的,用高级语言编写hello.c文件,GCC编译器驱动程序读取源文件hello.c,并把它编译成一个可执行目标文件hello。这个编译可分为四个阶段完成。首先,预处理器(cpp)根据以字符#开头的命令,修改原始的C程序,将hello.c文件转化为hello.i;而后通过编译器(ccl)将文本文件hello.i翻译成为文本文件hello.s汇编程序,它包含一个汇编语言程序;再用汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在目标文件hello.o中;最后,由于hello程序调用了printf等函数(属于标准C库的一个函数),因此要将含printf等函数的printf.o等用链接器(ld)合并到我们hello.o程序中,结果得到hello文件(可执行文件)。

Linux系统中通过内置命令行解释器shell加载运行hello程序,为hello程序fork进程,至此,hello.c完成了P2P的过程。

shell通过execve在fork产生的子进程中加载hello,先删除当前虚拟地址的用户部分已存在的数据结构,为hello的代码段、数据、bss以及栈区域创建新的区域结构,然后映射虚拟内存,设置程序计数器,使之指向代码区域的入口点,进入程序入口后程序开始载入物理内存,而后进入main函数,CPU为hello分配时间片执行逻辑控制流。hello通过Unix I/O管理来控制输出。hello执行完成后shell父进程会回收hello进程,并且内核会从系统中删除hello所有痕迹,至此,hello完成O2O的过程。

1.2 环境与工具

硬件环境:Intel i5-9300H @ 2.40GHz X64 CPU , 8G RAM

软件环境:Windows 10 64位 ,Vmware 15 ,Ubuntu 64 位

开发工具:gcc , gedit , Codeblocks , gdb , edb ,

1.3 中间结果

hello.i

预处理之后文本文件

hello.s

编译之后的汇编文件

hello.o

汇编之后的可重定位目标执行

hello

链接之后的可执行目标文件

hello.elf

Hello的ELF格式

1.4 本章小结

本章对Hello程序P2P和O2O的概念进行了简单的描述,对于实验的环境与工具进行了简单的说明,对于实验中的中间产物进行了罗列。

第2章 预处理

2.1 预处理的概念与作用

概念:

在嵌入式系统编程中不管是内核的驱动程序还是应用程序的编写,涉及到大量的预处理与条件编译,这样做的好处主要体现在代码的移植性强以及代码的修改方便等方面。因此引入了预处理与条件编译的概念。

在C语言的程序中可包括各种以符号#开头的编译指令,这些指令称为预处理命令。预处理命令属于C语言编译器,而不是C语言的组成部分。通过预处理命令可扩展C语言程序设计的环境。

作用:

在集成开发环境中,编译,链接是同时完成的。其实, C语言编译器在对源代码

编译之前,还需要进一步的处理: 预编译。预编译的主要作用如下:

●将源文件中以“include”格式包含的文件复制到编译的源文件中。

●用实际值替换用"#define" 定义的字符串。

●根据"#if”后面的条件决定需要编译的代码。

2.2在Ubuntu下预处理的命令

命令:gcc -m64 -no-pie -fno-PIC -E hello.c -o hello.i

2.3 Hello的预处理结果解析

经过预处理之后,hello.c转化为hello.i文件,打开该文件可以发现,程序的内容增加,程序的主要部分被保留成最简单的格式,删去了所有注释,位于.i文件的末尾且仍为可以阅读的C语言程序文本文件。且对原文件中的宏进行了宏展开,头文件中的内容被包含进该文件中。例如声明函数、定义结构体、定义变量、定义宏等内容。另外,如果代码中有#define命令还会对相应的符号进行替换。

2.4 本章小结

本章解释了预处理的概念和作用,并在Ubuntu中通过将hello.c实际预处理为了hello.i,展示了预处理后的文件,通过浏览hello.i 文件更好地了解了预处理的作用, 例如实现将定义的宏进行符号替换、引入头文件的内容、根据指令进行选择性编译等。

第3章 编译

3.1 编译的概念与作用

概念:

编译,就是把代码转化为汇编指令的过程,把预处理完的文件进行一系列语法分析及优化后生成相应的汇编文件。将.i形成的简单直接的c语言码经过分析并优化,翻译成一份汇编语言代码,得到的结果为.s文件。

作用:

扫描,语法分析,语义分析,源代码优化,目标代码生成,目标代码优化再生成汇编代码,再汇总符号,最后生成.s文件。

3.2 在Ubuntu下编译的命令

命令:gcc -S hello.i -o hello.s

3.3 Hello的编译结果解析

.file:声明源文件

.text:代码节

.global:声明全局变量 sleepsecs,main

.align:数据或者指令的地址对其方式

.type:声明一个符号是数据类型还是函数类型

.size 声明大小

.section:

.rodata:只读代码段

.string:声明字符串

.long 长整型数据

3.3.1 数据

1.常量

以十进制数字的形式存在。

字符串常量:“Usage: Hello 学号 姓名!\n"和"Hello %s %s\n”

在.LC0、.LC1段声明的字符串常量,且都是在.rodata只读数据节中

2.全局变量

1.变量名:sleepsecs

类型:int

操作:在汇编文件中,使用语句sleepsecs(%rip)进行引用。

赋值:在文件开头,为其赋初值,因为是int型整数,将初值小数位舍去,赋初值为2。

2.变量名:main

类型:函数地址

2.局部变量

1.变量名:i

类型:int

操作;i储存在以%rbp中值减4为地址的内存中,使用语句-4(%rbp)进行引用

赋值:未赋初值,使用movl $0, -4(%rbp)语句,赋值0;

2.变量名:argc

类型:int

操作:刚开时储存在%edi寄存器中,然后将其放置在以%rbp中值减去20为地址的内存中,使用语句-20(%rbp)进行引用。

3.变量名:argv

类型:char **

操作:刚开始储存在%rsi寄存器中,然后将其放置在以%rbp中值减去32 为地址的内存中,使用语句-32(%rbp)进行引用。

3.3.2 赋值

还以局部变量i的赋值为例,直接向栈的寄存器中存入32位整型(对应movl的l后缀),即int i = 0。

3.3.3 类型转换

sleepsecs存在2次类型转换。

一个是赋初值时,将double型的常数舍去小数赋值给sleepsecs

一个实在作为sleep函数的参数时,直接将其值作为unsigned int类型,作为参数传递给sleep函数。

3.3.4  算术操作

++: 使用语句:addl $1, -4(%rbp)对int型变量i加1,并将结果储存在i中

3.3.5  关系操作

1. argc!=3 :

使用cmpl $3, -20(%rbp)向对argc值和3进行比较,并设置条件位,再使用je .L2语句,如果b==a则跳转到.L2处,执行.L2处的语句;如果不想等,则不跳转,执行je L2后面的语句,也就是!=的情况下,要执行的语句。

2. i<10 :

在这里为刚开始赋值为0,然后增大,所以编译器实际上是执行<=的操作,使用语句cmpl $9, -4(%rbp),对i和9进行比较,并设置条件位,再使用语句jle .L4,如果i<=9则跳转到.L4处,继续循环,否则跳出循环。

3.3.6  数组/指针/结构

1.指针:argv是一个指针,argv所指向的数组中的每一个元素也是指针。

字符串也表示为一个地址值。用如:.LC0,进行应用。

对于指针的所指向值得引用是语句(指针),表示指针所指向地址处内存中的值。

2.数组:argv[]是一个数组,对于数组元素的引用,是按照数组的首地址argv加 上一个所要引用的元素在数组中地址的偏移量(一般是索引值*元素类型的字节数),得到元素的首地址,再使用(首地址)进行引用获得其值。

3.3.7  控制转移

1.if (if(argc!=3)):

使用je .L2语句,如果不条件成立则跳转到.L2处,执行.L2处的语句;如果成立,则不跳转,执行je L2后面的语句,也就是if后面的要执行的语句。

2.for (for(i=0;i<10;i++)):

在循环刚开始,给i赋值为0(movl $0, -4(%rbp)),然后跳转到.L3处进行比较,如果符合i<10的条件,则跳转到.L4(jle.L4),执行循环中的语句,在循环的最后,会给i加上1,.L3紧跟在.L4后,所以执行完循环体.L4中的语句后,会顺序执行.L3中的内容,进行条件判断。

3.3.8  函数操作

1.main函数:

参数传递:%edi存储着argc的值,是参数变量的个数;作为第一个参数

%rsi储存值argv的值,作为一个地址,指向参数字符串数组的首地址.作为第二个参数

函数调用:主函数,第一个执行的函数

函数返回:返回值0,使用movl $0, %eax语句,将%eax中值作为返回值,使用指令ret进行返回

2.printf函数:

参数传递:第一次 printf 将%rdi 设置为“Usage: Hello 学号 姓名! \n”字符串的首地址。第二次 printf 设置%rdi 为“Hello %s %s\n” 的首地址,设置%rsi 为 argv[1],%rdx 为 argv[2]。

函数调用:第一次 printf 因为只有一个字符串参数,所以 call puts;第二次 printf 使用 call printf。

3.exit函数:

参数传递:将1放置在寄存器%edi中(movl $1, %edi),做为参数传递

函数调用:使用指令call进行函数调用

函数返回:无返回值,不返回

4.sleep函数:

参数传递:将sleepsecs的值作为参数,通过%eax赋值给%edi,%edi中值作为第一个参数

函数调用:使用指令call进行函数调用

函数返回:返回值存储在%eax,使用ret指令返回

5.getchar函数:

参数传递:无参数

函数调用:使用指令call进行函数调用

函数返回:返回值存储在%eax中,使用ret指令返回.

3.4 本章小结

本章内容,对汇编的概念和作用进行了阐述,并通过对文本文件hello.c的编译以及编译结果演示再现了编译的过程。

在以上的分析过程中,解释了hello.c文件与hello.s文件间的映射关系,详细的分析了编译器是怎么处理C语言的各个数据类型以及各类操作的,按照不同的数据类型和操作格式c代码到汇编代码的翻译方式。

第4章 汇编

4.1 汇编的概念与作用

概念:

汇编器(as)将汇编文件hello.s翻译成机器语言指令,并把这些指令打包成可重定位目标程序的格式,并将结果保存在二进制文件中hello.o。

作用:

将汇编代码转为机器指令,使其在链接后能被机器识别并执行。。

4.2 在Ubuntu下汇编的命令

gcc -m64 -no-pie -fno-PIC -c hello.s -o hello.o

4.3 可重定位目标elf格式

使用readelf -a hello.o > hello.elf 指令获得hello.o文件的ELF格式。

4.3.1  elf头

以一个16字节序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序,剩下的信息帮助链接器语法分析和解释目标文件的信息。包括ELF头大小,目标文件类型,机器类型,节头部表的文件偏移以及节头部表中条目的大小和数量等。

4.3.2  节头

记录了各节名称、类型、地址、偏移量、大小、全体大小、旗标、连接、信息、对齐信息。

4.3.3  重定位节

文件中有一些内存地址或引用,这些地方在链接前是待定的,需要视链接的情况指定确切的地址。因此,需要对这些地址进行重定位。每个代码段或数据段都对应一个重定位表,记录了段中的这些位置,方便对它们进行查找和操作。

4.3.4  符号表

.symtab存放着程序中定义和引用函数和全局变量的信息。且不包含局部变量的条目。重定位中的符号类型全在该表中有声明。

4.4 Hello.o的结果解析

将反汇编的代码与hello.s比较,指令并没有太大差异,只是反汇编代码所显示的不仅是汇编代码,还有机器代码。机器指令由操作码和操作数构成,每一条汇编语言操作码都可以用机器二进制数据来表示,进而可以将所有的汇编语言(操作码和操作数)和二进制机器语言建立一一映射的关系,因此可以将汇编语言转化为机器语言,通过对机器代码的分析可以看出一下不同的地方。

  

(1) 机器语言中的操作数使用的是十六进制格式;而汇编语言则是十进制

(2)分支转移:反汇编的跳转指令用的不是段名称比如.L3,二是用的确定的地址,因为,因为段名称只是在汇编语言中便于编写的助记符,所以在汇编成机器语言之后显然不存在,而是确定的地址。

(3)函数调用:在.s 文件中,函数调用之后直接跟着函数名称,而在反汇编程 序中,call的目标地址是当前下一条指令。这是因为 hello.c 中调用的函数 都是共享库中的函数,最终需要通过动态链接器才能确定函数的运行时执行地址,在汇编成为机器语言的时候,对于这些不确定地址的函数调用,将其call指令后的相对地址设置为全0(目标地址正是下一条指令),然后在.rela.text 节中为其添加重定位条目,等待静态链接的进一步确定。

4.5 本章小结

本章对hello.s进行了汇编,生成了hello.o可重定位目标文件,并且分析了可重定位文件的ELF头、节头部表、符号表和可重定位节,比较了hello.s和hello.o反汇编代码的不同之处,分析了从汇编语言到机器语言的一一映射关系,并分析了反汇编代码与.s文件不同之处。

第5章 链接

5.1 链接的概念与作用

概念:

链接是结合多个不同的可重定位目标文件、得到具有统一内存地址,能够运行的可执行文件的过程。这个文件可被加载到内存并执行。

作用:

链接可以执行于编译时,也就是在源代码被编译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至于运行时,也就是由应用程序来执行。链接是由叫做链接器的程序执行的。链接器使得分离编译成为可能。

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

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

首先是ELF 头,如图发现可执行目标文件格式类似于可重定位目标文件格式。Type类型为EXEC表明hello是一个可执行目标文件,有27个节。ELF描述文件总体格式,发现它还包括了程序的入口点,即程序运行时要执行的第一条指令的地址。

再看节头,在 ELF 格式文件中,节头对 hello 中所有的节信息进行了声明,其中包括大小以及在程序中的偏移量,因此根据节头中的信息我们就可以用定位各个节所占的区间(起始位置,大小)。其中 Address 是程序被载入到虚拟地址的起始地址。

除此之外,.text、.rodata、.data节与可重定位目标文件的节是相似的,这些节已经被重定位到他们最终的运行时内存地址。

5.4 hello的虚拟地址空间

通过查看edb,看出hello的虚拟地址空间开始于0x401000,结束与0x402000,如图  

根据节头部表图,可以通过edb找到代码段各个节的信息

例如.txt节,虚拟地址开始于0x4010d0,大小为0x135.

也可以查看整个内存映像各段的信息

由上到下依次为只读代码段,读写段,运行时堆,用户栈等

5.5 链接的重定位过程分析

使用命令:objdump -d -r hello

分别获得hello  hello.o的反汇编代码。

hello.o的objdump

hello的objdump

分析hello与hello.o的差异。可以发现以下不同的地方:

1.在hello.o中,main函数地址从0开始,即hello.o中保存的都是相对偏移地址;而在hello中main函数0x401105开始,即hello中保存的是虚拟内存地址,对hello.o中的地址进行了重定位。

2.ELF描述文件总体格式,发现它还包括了程序的入口点,即程序运行时要执行的第一条指令的地址。除此之外,由于可执行文件是完全链接的,故也没有了rel节。

3.hello可执行目标文件中多出了.init节和.plt段。.init节定义了一个小函数叫做_init,程序的初始化代码会用到,用于初始化程序执行环境;.plt段是程序执行时的动态链接。所有的重定位条目都被修改为了确定的运行时内存地址。

4.在hello中链接加入了在hello.c中用到的函数,如exit、printf、sleep、getchar等函数

链接过程:

链接就是链接器ld将各个目标文件组装在一起,就是把.o文件中的各个函数段按照一定规则累积在一起,指定了动态链接器为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。链接器将上述函数加入。

重定位:

在这个步骤中,将合并输入模块。并为每个符号分配运行时的地址。重定位由两步组成:重定位节与符号定义、重定位节中的符号引用。

在重定位节与符号定义这一步中,链接器将所有相同类型的节合并为同一类型的新的聚合节,而后,链接器将运行时内存地址赋值给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。

在重定位节中的符号引用中,链接器修改代码节和数据节中对每个符号的引用,使得他们指向正确的运行地址,这一步依赖hello.o中的重定位条目。

除此之外重定位类型分为两种,分别为R_X86_64_PC32与R_X886_64_32,这两种分别为PC相对寻址与绝对寻址。对于hello.o中使用PC相对寻址的指令使用R_X86_64_PC32类型进行重定位,而对hello.o直接引用地址的指令,采用R_X886_64_32类型进行重定位。

5.6 hello的执行流程

_dl_start_user

_dl_init

_start

_libc_start_main

_cxa_atexit

_libc_csu_init

_init

_setjmp

_sigsetjmp

_sigjmp_save

main

(main后)

puts@plt

exit@plt

printf@plt

atoi@plt

sleep@plt

getchar@plt_dl_runtime_resolve_xsave

_dl_fixup

_dl_lookup_symbol_x

Exit

5.7 Hello的动态链接分析

动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。虽然动态链接把链接过程推迟到了程序运行时,但是在形成可执行文件时(注意形成可执行文件和执行程序是两个概念),还是需要用到动态链接库。比如我们在形成可执行程序时,发现引用了一个外部的函数,此时会检查动态链接库,发现这个函数名是一个动态链接符号,此时可执行程序就不对这个符号进行重定位,而把这个过程留到装载时再进行。

在调用共享库函数时,编译器没有办法预测这个函数的运行时地址,因为定义它的共享模块在运行时可以加载到任意位置。正常的方法是为该引用生成一条重定位记录,然后动态链接器在程序加载的时候再解析它。GNU编译系统使用延迟绑定(lazybinding),将过程地址的绑定推迟到第一次调用该过程时。

延迟绑定是通过GOT和PLT实现的。GOT是数据段的一部分,而PLT是代码段的一部分。两表内容分别为:

PLT:PLT是一个数组,其中每个条目是16字节代码。PLT[0]是一个特殊条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。

GOT:GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT[O]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。GOT[2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目。

根据hello的ELF文件可知GOT起始表位置为0x404000

GOT表位置在调用dl_init之前0x404008后的16个字节均为0:

调用_start之后发生改变,0x404008后的两个8个字节分别变为:0x7ff78e2b4190、0x7ff78e29dbb0,其中GOT[O]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。GOT[2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,改变后的GOT表如下: ​

GOT[2]对应部分是共享库模块的入口点,如下:

5.8 本章小结

本章详细地讨论了链接,对hello.o文件的链接和执行流程进行了解释。阐述了hello.o是怎么链接成为一个可执行目标文件的,详细介绍了hello.o的ELF格式和各个节的含义并分析了hello的虚拟地址空间、重定位过程、执行流程、动态链接过程。

链接实现多个文件的合并,最终创建一个可执行的完整的程序,到此,一个可执行程序的建立就已完成.

第6章 hello进程管理

6.1 进程的概念与作用

概念:进程是计算机科学中对深刻最成功的概念之一,进程是操作系统对一个正在运行的程序的一种抽象,进程的经典定义就是一个执行中程序的实例。系统中每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的沉痼的代码和数据,它的栈、通用目的寄存器的内容,程序计数器、环境变量以及打开文件描述符的集合。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。

作用:它提供一个假象,好像我们的程序是系统当前运行的唯一的程序一样。我们的程序好像是独占的使用处理器和内存。处理器好像无间断的一条接一条的执行我们程序中的指令。最后,我们程序中的代码和数据好像是系统内存中唯一的对象。

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

Linux系统中,Shell是一个交互型应用级程序,代表用户运行其他程序(是命令行解释器,以用户态方式运行的终端进程)。Shell执行一系列的读/求值步骤,然后终止。读步骤读取来自用户的一个命令行。求值步骤解析命令行,代表用户运行程序。

其基本功能是解释并运行用户的指令,重复如下处理过程:

(1)终端进程读取用户由键盘输入的命令行。

(2)分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量

(3)检查第一个(首个、第0个)命令行参数是否是一个内置的shell命令

(4)如果不是内部命令,调用fork( )创建新进程/子进程

(5)在子进程中,用步骤2获取的参数,调用execve( )执行指定程序。

(6)如果用户没要求后台运行(命令末尾没有&号)否则shell使用waitpid(或wait…等待作业终止后返回。

(7)如果用户要求后台运行(如果命令末尾有&号),则shell返回;

6.3 Hello的fork进程创建过程

根据shell的处理流程,输入命令执行当前目录下的可执行文件hello,由于命令行参数不是一个内置的shell命令,所以父进程通过调用fork函数创建一个新的运行的子进程。

新创建的子进程几乎但不完全与父进程相同,子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程还获得父进程打开任何文件描述符相同的副本,这意味着当父进程调用fork函数时,子进程可以读写父进程中任何打开的文件。父进程与子进程之间最大的区别在于它们拥有不同的 PID。

Fork函数制备调用一次,却返回两次;一次是在调用父进程中,一次是在创建新的子进程中。在父进程中,fork返回子进程的PID。在子进程中,fork返回0.因为子进程的pid总是非0,返回值就提供一个明确的方法来分辨程序是在父进程还是在子进程中进行。

在子进程执行期间,父进程默认选项是显示等待子进程的完成。

接下来 hello 将在 fork 创建的子进程中执行。

6.4 Hello的execve过程

当创建了一个子进程之后,子进程调用exceve函数在当前子进程的上下文加载并运行一个新的程序即hello程序,加载并运行需要以下几个步骤:

(1)删除已存在的用户区域。删除当前进程虚拟地址的用户部分中已存在的区域结构。

(2)映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些区域结构都是私有的,写时复制的。虚拟地址空间的代码和数据区域被映射为hello文件的.txt和.data区。bss区域是请求二进制零的,映射匿名文件,其大小包含在hello文件中。栈和堆区域也是请求二进制零的,初始长度为零。如图6.4

(3)映射共享区域。如果hello程序与共享对象链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域。

(4)设置程序计数器(PC)。exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。下一次调用这个进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。

6.5 Hello的进程执行

为了使操作系统内核提供一个无懈可击的进程抽象,处理器必须提供一种机制,限制一个应用可以执行的指令以及它可以访问的地址空间范围。处理器通常使用某个控制寄存器的一个模式位提供两种模式的区分,该寄存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中,用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。

操作系统内核使用一中称为上下文切换的较高层形式的异常控制流来实现多任务:内核为每个进程维持一个上下文,上下文就是内核重新启动一个被抢占的进程所需的状态,它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地址空间的页表、包含有关当前进程信息的进程表,以及包含进程一打开文件的信息的文件表。

内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程。这种调度决策,是由内核中

标签: 2cr3z集成电路cxa1587s集成电路

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台