程序人生-计算机体系大作业

王柳  论坛元老 | 2024-11-21 21:05:59 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1021|帖子 1021|积分 3063

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
摘  要

这篇是深入理解计算机体系这门课的一个总结,从hello.c这一程序从出生到消亡的全过程来概括计算机体系对一样平常程序的处理过程。本文具体报告了各个步骤中的要点,比较相似步骤之间的异同,并从中探讨计算机体系处理程序的逻辑。

关键词:编译体系;进程;linux;shell                           

第1章 概述

1.1 Hello简介

1、P2P(Program to process)
Hello程序从hello.c代码开始,颠末预处理、编译、汇编、链接,生成可执行目的文件,再被fork创建子进程、execve写进上下文,终极成为一个进程。
编译体系各步骤及其生成的文件如下图所示:

                                  
图1 编译体系

2、020(Zero to zero)
Hello程序要履历一个从无到有,再从有到无的过程。
从无到有就是指程序员编写出hello.c这一文件的过程,而颠末编译体系和利用体系的P2P利用,hello进程被创建并运行,而shell进程已经写好了wait函数等候接纳这一进程资源,包括进程号、主存上分配的所在空间,在hello进程在屏幕上输出Hello World之后,hello程序就被接纳掉了,此为从有到无。
1.2 情况与工具

硬件情况:Intel Core i7-10870H CPU, 2.21GHz,x64,16GB RAM
软件情况:Windows11,VMware17,Ubuntu22.04
开发工具:vim,edb,gcc
1.3 中央结果

hello.c :hello代码

hello.i :预处理之后的源程序

hello.s :汇编文件

hello.o :可重定位的目的文件

hello :可执行目的程序

asm.txt :反汇编文件

1.4 本章小结

本章概述了hello程序的整个运行过程,列出了实验所用的软硬件情况和开发工具。


第2章 预处理

2.1 预处理的概念与作用

预处理指在程序源代码被翻译为目的代码的过程中,生成二进制代码之前的过程。是把*.c变成*.i。
作用是把源程序中以#开头的行中引用的各种外部文件通过typedef、extern等方式引入程序文件。
2.2在Ubuntu下预处理的命令

预处理的命令行如下:

                          图2 预处理命令
2.3 Hello的预处理结果解析

运行完预处理命令,文件夹里多了一个hello.i文件:

                         图3 预处理结果
打开来看一看:

                           
                                 图4 hello.i

可以看到,原来hello.c的代码被放在了末了面,注释被删掉了,前面是从外部库引入的函数和类型界说,举例如下:
#include <stdio.h>,stdio.h中的scanf函数引入:

                            图5 scanf引入
几个类型的界说如下:

                            图6 几个类型
2.4 本章小结

本章介绍了预处理的Linux命令行,对预处理举行的利用和得到的文件hello.i与源文件hello.c的关系举行了分析。


第3章 编译

3.1 编译的概念与作用

编译是指把预处理得到的*.i转变为*.s,即汇编语言程序。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
作用是把高级语言程序转为汇编指令,便于后续翻译成呆板指令。
3.2 在Ubuntu下编译的命令


                            图7 编译指令
执行完,文件夹出现了hello.s文件。
                            图8 编译结果

3.3 Hello的编译结果解析

Hello.s全文如下:
 

                          图9 hello.s

3.3.1.
变量:
参数argc存放在-20(%rbp)的位置,argv存放在-32(%rbp)的位置。局部变量i存放在-4(%rbp)的位置。
常量:
报错用的printf的字符串存放在.LC0标志后的.string段,

                            图10 printf第一次调用
输出信息用的printf的字符串存放在.LC1标志后的.string段,


                            图11 printf第二次调用
给这个printf函数传参,用%rdi传递字符串常量,用%rsi传递本来在栈里的argv所指向的argv[1]。

3.3.2.
赋值:


                            图12 i的赋值
如图,i被赋初值0。
3.3.3.
算术利用:
for循环中出现的i++,在汇编语言文件中是这样写的,


                            图13
3.3.4.
关系利用:
判断argc!=4,汇编语言文件是这样做的,


                            图14
逻辑是相称则跳转到.L2,不相称则顺序往下。
另有一处是for循环的终止条件,实现如下,


                            图15
C代码中,用的条件是i<8,而汇编语言则用了i<=7。满足则跳转.L4继续循环,不满足则向下顺序执行跳出循环。
3.3.5.
数组利用:
由于argv指向的是一个数组。读取argv[2]、argv[3]分别用了如下利用,




                            图16
逻辑就是先把argv[0]的所在(就是argv)放进%rax寄存器,在此基础上加上想要读取的数组成员的所在偏移量,末了把%rax内容所指向的内容存进%rax,就完成了数组利用。
3.3.6.
控制转移:
如3.3.4中for循环终止条件所形貌,


                     图17
用i<=7,作为判断条件,满足则跳转.L4继续循环,不满足则向下顺序执行跳出循环。
3.3.7.
函数利用:
调用了printf函数,传参方法如3.3.1所形貌。
调用了atoi函数,


                            图18
传参用的是%rdi寄存器,传了一个argv[3],返回值又被传给了函数sleep,用的还是%rdi寄存器。


                            图19
调用了getchar函数,无参,仅作为暂停利用,返回值被忽略。


                            图20
3.4 本章小结

本章形貌了从*.i文件到*.s文件所利用的命令行,分析了编译器是以怎样的逻辑编写汇编语言的。


第4章 汇编

4.1 汇编的概念与作用

汇编程序是指把汇编语言誊写的程序翻译成与之等价的呆板语言程序的翻译程序。汇编程序输入的是用汇编语言誊写的源程序,输出的是用呆板语言表示的目的程序。
作用:把汇编程序翻译成呆板语言程序(可重定位目的文件)。
4.2 在Ubuntu下汇编的命令

命令行和执行后的文件夹内容如下图所示。


                            图21 汇编指令
4.3 可重定位目的elf格式

    可重定位目的文件由下列节构成:
.text:已编译程序的呆板代码
.rodata:只读数据
.data:已初始化的全局和静态C变量
.bss:未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量
.symtab:在程序中界说和引用的函数和全局变量信息
.rel.text:.text节重定位信息
.rel.data:被模块引用或界说的所有全局变量的重定位信息
.debug:调试符号表
.line:行号
.strlab:.symtab和.debug节中的符号表
    ELF头:

                            图22
    节头表:



                            图23
    .rel.text节:

            
                图24

    .symtab节:

 
                           图25

4.4 Hello.o的结果解析

objdump -d -r hello.o得到反汇编如下:




                        图26 hello.o反汇编
与hello.s相比,这个文本每条指令都有了十六进制所在,而hello.s文件只是指令没有所在;
这个文本没有了.L0等标志信息,而是通过PC增减的方式实现跳转;
这个文本的利用数都是十六进制表示,有别于hello.s中的十进制;
这个文本汇编语言指令与呆板指令是一一对应的双射关系。
4.5 本章小结

本章形貌了从*.s文件到*.o文件所利用的命令行,分析了汇编器是怎么处理汇编语言文件的,观察了ELF文件格式,比较了颠末汇编器处理的文件和之前的文件的区别。


第5章 链接

5.1 链接的概念与作用

链接是指把用到的零散的代码片断合成一个文件。
作用:通过链接,使得分离编译成为可能;动态绑定可使界说、实现、利用分离。
注意:这儿的链接是指从 hello.o 到hello生成过程。
5.2 在Ubuntu下链接的命令


                            图27 链接指令
5.3 可执行目的文件hello的格式

    ELF头:



                     图28
    节头表:


                            图29


                            图30
    程序头表:

                            图31
    符号表:





                            图32
5.4 hello的假造所在空间

   
   

                            图33
5.5 链接的重定位过程分析







                             图34 hello.out反汇编



    可以看出,这段文本增长了所利用的其他库中的函数的代码,体现了链接的工作。Call的内容都是所在,而且增长了*.out文件特有的init节。
5.6 本章小结

本章形貌了hello.o到hello.out的过程,分析了链接器的工作内容。

第6章 hello进程管理

6.1 进程的概念与作用

进程的概念:进程(Process)是计算机中的程序关于某数据聚集上的一次运行活动,是体系举行资源分配的基本单元,是利用体系布局的基础。在早期面向进程设计的计算机布局中,进程是程序的基本执行实体;在当代面向线程设计的计算机布局中,进程是线程的容器。程序是指令、数据及其组织形式的形貌,进程是程序的实体。
进程的作用:进步CPU的执行服从,减少因为程序等候带来的CPU空转以及其他计算机软硬件资源的浪费。
6.2 简述壳Shell-bash的作用与处理流程

处理流程:


  • 读取从键盘输入的命令;
  • 判断命令是否正确,且将命令行的参数改造为体系调用execve()内部处理所要求的形式;
  • 终端进程调用fork()来创建子进程,自身则用体系调用wait()等候子进程完成;
  • 当子进程运行时,它调用execve() 根据命令的名字指定的文件到目录中查找可行性文件,调入内存并执行这个命令;
  • 如果命令行末尾有后台命令符号&,终端进程不执行等候体系调用,而是立即发提示符,让用户输入下一条命令;如果命令末尾没有&,终端进程要不停等候。当子进程完成处理后,向父进程陈诉,此时终端进程被唤醒,做完须要的鉴别工作后,再发提示符,让用户输入新命令。
6.3 Hello的fork进程创建过程

终端调用fork函数,创建一个新的进程,父进程返回子进程PID,子进程返回0子进程得到与父进程相同的代码和数据段、堆、共享库、用户栈,但物理所在是新的。子进程和父进程PID不同,互不影响。
6.4 Hello的execve过程

创建的子进程调用execve函数,不返回,把hello进程的envp和argc、argv写进上下文。
6.5 Hello的进程执行

用户程序之间的切换是由内核完成的,内查对所有资源有同等权限。想要执行hello,要给他分配时间片,当进程出现非常或时间片用完,内核就会举行上下文切换,制止浪费资源。
6.6 hello的非常与信号处理

刚刚运行的时候会发生缺页非常,这是故障的一种,特点是如果处理程序成功完成了故障处理,会返回引起故障的指令,而不是下一条。

乱按:


                            图35
    可以看出终端表现输入流和hello是两个并发进程,没发信号就互不干扰。
    按下Ctrl+Z之后ps、jobs、pstree


                            图36
Ctrl+Z会让内核给hello进程发送3号SIGTSTP信号,进程临时挂起,但仍旧存在。执行命令fg可以重新唤起hello。


                            图37
Ctrl+C:


                            图38
这一命令会让内核给hello发2号信号SIGINT,这样该进程就被终止并接纳,不复存在了。
Kill:

                            图39
可以看出,在进程挂起的时候,也可以通过kill命令发送SIGINT信号将其终止。
6.7本章小结

本章形貌了hello作为一个进程的创建、挂起、唤起、终止的过程和其中的信号处理机制。

第7章 hello的存储管理

7.1 hello的存储器所在空间

逻辑所在:从程序的角度看到的内存单元和存储单元所在,一样平常是偏移所在,就是hello反汇编得到的所在数据。
线性所在:将逻辑所在段号和偏移量运算后得到的所在。
假造所在:假造存储器里的所在,要比物理所在大很多,可以映射到物理所在。
物理所在:真正的存储所在,也就是hello真正在主存占据的位置。
7.2 Intel逻辑所在到线性所在的变换-段式管理

将所在分为代码段、数据段,逻辑所在就可以由段号和段内偏移量构成。


                           图40 逻辑所在
有特点如下:


  • 段式管理以段为单元分配内存,每段分配一个一连的内存区。
  • 由于各段长度不等,以是这些存储区的巨细不一。
  • 同一进程包含的各段之间不要求一连。
④ 段式管理的内存分配与开释在作业或进程的执行过程中动态举行。
7.3 Hello的线性所在到物理所在的变换-页式管理

把主存和虚存都分成同样巨细的页面,每个页面是否掷中由页表来记载,为了进步页表利用服从,页表有本身的缓存TLB。

页掷中完全由硬件实现,缺页处理由软硬件共同实现。所在翻译流程如下:

①  处理器生成一个假造所在,并将其传送给MMU。



  •  MMU生成PTE所在(PTEA),并从高速缓存/主存请求得到PTE。
  •  高速缓存/主存向MMU返回PTE。
  •  MMU 将物理所在传送给高速缓存/主存。
⑤  高速缓存/主存返回所请求的数据字给处理器。

7.4 TLB与四级页表支持下的VA到PA的变换

利用多级页表是为了收缩单个所在的位数,防止由于过大的假造所在空间而使得假造所在变得太长。
四级页表布局如图所示:


                            图41 四级页表
7.5 三级Cache支持下的物理内存访问

三级Cache布局如图:


                                  图42 三级cache
7.6 hello进程fork时的内存映射

终端调用fork函数之后,子进程就有了本身的PID和所在空间,和父进程共享库、用相同的段,整体是父进程的一个副本。
7.7 hello进程execve时的内存映射

所在不变,内容改变,这个时候他就成为了与父进程区分开的另一个进程。
7.8 缺页故障与缺页停止处理

发生缺页故障,处理程序唤起对应的进程,调页竣事再执行引发缺页故障的这一条指令。
7.9本章小结

本章分析了hello对内存的利用方法。

 结论

    在读大学之前,甚至说是学习计算机体系这门课之前,我对程序运行的理解都是极其肤浅的,以为程序能跑通是理所当然。
现在学了这门课,我明白了没有任何事是本该云云的,就一个简单的hello程序,里面都蕴含了计算机科学家的智慧。我以为,深入理解计算机体系这门课,可能是我正确认识计算机的一个开始。
从这次大作业中,我学到了:对待计算机、对待科学、对待人生,一定要有严谨的态度。一步出错,连最简单的hello都会是一个困难,更不必说我们要学的复杂的知识体系,万万不可懈怠。

附件

hello.c(初始的程序代码)
hello.i(预处理后的代码)
hello.s(汇编语言文件)
hello.o(可重定位目的文件)
hello(可执行目的文件)

参考文献

[1]  https://blog.csdn.net/forcj/article/details/117967945
[2]  https://my.oschina.net/mikeowen/blog/4812888
[3]  https://blog.csdn.net/goJiaWei123/article/details/98862886
[4] https://baike.baidu.com/item/%E9%80%BB%E8%BE%91%E5%9C%B0%E5%9D%80/3283849?fr=aladdin#3
[5]  https://www.cnblogs.com/mengxiaoleng/p/11921912.html








免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

王柳

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表