Linux操作系统分析-lab1-HowtheComputerWorks
2013-05-14 17:09
225 查看
学号:sa****340 姓名:**钰
实验一:计算机是怎样工作的?
实验要求:请使用Example的C代码分别生成.cpp,.s,.o和ELF可执行文件,并加载运行,分析.s汇编代码的CPU上的执行过程。
实验报告要求:通过实验解释单任务计算机是怎样工作的,并在此基础上讨论分析多任务计算机是怎样工作的。
1 example.c源程序
计算机执行程序的过程是将源代码编译生成二进制文件,计算机最终能处理的只有二进制文件。编译器的工作过程如下:
(1)预处理(Pre-Processing)
(2)编译(Compiling)
(3)汇编(Assembling)
(4)连接(Linking)
2 具体执行过程
2.1 预处理
gcc -E -o example.cpp example.c
生成example.cpp文件
宏的替换和常量标识符的替换
还有注释的消除
还有找到相关的库文件,将源文件中以”include”格式包含的文件复制到编译的源文件中
如果源代码中有预处理指令(如#if),那么预处理程序将先判断条件,再相应的修改源代码
用编辑器打开example.cpp会发现有很多很多代码,但是看最后部分就会发现,预处理做了宏的替换,还有注释的消除,可以理解为无关代码的清除。
在这个预处理阶段,源文件把include<stdio.h>代码编译进来,生成扩展的源文件example.cpp,这一步对程序员来说是透明的,预处理完成之后会自动将example.cpp进行编译。
2.2 编译
gcc -S -o example.s example.c
生成example.s文件
在上述文件中多处出现以.cfi开头的命令,虽然通过查阅一定资料,但是还是不是很理解,可以参考这里。
程序执行过程中,内存的变化如下:
正向调用过程如下:
当函数调用结束时回复到调用函数的下一条指令的地方,这时候内存的变化,%ebp和%esp我没有画图显示。
从上面的.s文件可以看出,每一个函数的开头总是:
pushl %ebp ;保存old ebp,以便下次返回用
movl %esp, %ebp ;将esp的值赋给ebp,使得ebp指向新的一段活动记录
把ebp压入栈中,是为了在函数返回的时候便于恢复以前的ebp值。而之所以要保存一些寄存器,在于编译器可能要求某些寄存器在调用前后保持不变,那么函数就可以在函数开始时将这些寄存器的值压入栈中,在结束时取出。所以在函数返回时,所进行的标准结尾与标准开头正好相反。
movl %ebp,%esp
pop %ebp
2.3 汇编
gcc -c -o example.o example.c
as -o example.o example.s
生成example.o文件
汇编阶段把编译阶段生成的example.s文件转换为二进制目标文件,也就是翻译成机器语言指令,把这些指令打包成可重定为目标程序的格式。
2.4 连接
gcc -o example example.o
生成可执行文件example,没有后缀名。
在终端可以执行:./example
可以用file 命令查看example的属性。
终端执行命令:file example
显示如下:
songzeyu@ubuntu:~/Linux system/lab1$ file example
example: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, not stripped
其中的executable指出该文件为ELF中的可执行文件类型。
3 计算机工作模式:单任务和多任务的浅析
3.1 单任务的工作模式
计算机在同一时间只能运行一个应用程序,计算机首先从外存中加载程序到内存,然后依次执行程序指令,完全执行完毕之后才可以加载、执行下一个程序。具体的执行过程如上面所分析得一样。
单任务的缺点,由于CPU资源十分昂贵,如果我们需要优先运行级别较高的程序,单任务是不能满足要求的。
3.2 多任务的工作模式
计算机在同一时间可以运行多个应用程序,现在的操作系统基本上都是多任务工作模式的。同时运行的多个程序之前不会相互干扰,但是让CPU同时运行多个程序,必须使用并行技术,最容易理解的就是“时间片轮转进程调度算法”。
4 实验小结
PS:这次实验咋一看也许觉得很简单,但是当我真正去做的时候,发现其中的很多细节的知识点很模糊。计算机是如何工作这本身就不是一个简单的问题,就算是科班出生的同学也并不一定能正确的解释它的运行机制。所以从实验的前期准备到开始做实验我都很认真。
之前尽管知道程序运行的过程为:预处理、编译、汇编、连接,但是对其中的每一步是如何实现的,如何进行命名的操作都不是很清楚,平时运行程序只要一行命令就可以完成:gcc example -o example.c。通过这次实验我掌握了分别进行操作。在这之前我查阅了一些资料用于这次实验:
编译器的工作模式:
0、 gcc -o example example.c 不加 -c -S -E 参数,编译器将执行预处理/编译/汇编/链接操作直接生成可执行代码。
1、 gcc -c -o example.o example.c的作用
-c 参数将对源程序example.c进行预处理,编译,汇编操作,生成example.o文件。去掉指定输出选项"-o example.o"自动输出为example.o,所以这里-o加不加都可以。
2、 gcc -S -o example.s example.c的作用
-S参数将对源程序example.c进行预处理,编译,生成example.s文件。-o选项同上。
3、 gcc -E -o example.i example.c的作用
-E 参数将对源程序example.c进行预处理,生成 example.i文件(不同版本不宜样),就是将#include ,#define 等进行文件插入及宏扩展等操作。
4、 gcc -v -o example example.c的作用
加上-v 参数,显示编译时的详细信息,编译器的版本,编译过程等。
5、gcc -Wall -o example example.c
-Wall 选项打开了所有需要注意的警告信息,像在声明之前就使用的函数,声明后却没有使用的变量等。
6、gcc -Ox -o example example.c
虽然这些都是基本知识,可是在实验之前我并不了解。
另一方面通过这次实验我对栈的理解有了深一步的理解和掌握。
5 参考资料
5.1 《程序员的自我修养—链接、装载与库》
这是我目前看到的将这方面的知识比较好的一本书,我主要看了第十章“内存”,里面图文并茂的讲解给这次实验帮了不少的忙。
5.2 《深入理解计算机系统》
我主要看了第三章“程序的机器级表示”,核心的知识点讲了程序在堆栈中的表示。
实验一:计算机是怎样工作的?
实验要求:请使用Example的C代码分别生成.cpp,.s,.o和ELF可执行文件,并加载运行,分析.s汇编代码的CPU上的执行过程。
实验报告要求:通过实验解释单任务计算机是怎样工作的,并在此基础上讨论分析多任务计算机是怎样工作的。
1 example.c源程序
#include <stdio.h> int g(int x) { return x+3; } int f(int x) { return g(x); } int main(void) { return f(8)+1; }
计算机执行程序的过程是将源代码编译生成二进制文件,计算机最终能处理的只有二进制文件。编译器的工作过程如下:
(1)预处理(Pre-Processing)
(2)编译(Compiling)
(3)汇编(Assembling)
(4)连接(Linking)
2 具体执行过程
2.1 预处理
gcc -E -o example.cpp example.c
生成example.cpp文件
...
extern int fileno (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ; extern int fileno_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ; # 870 "/usr/include/stdio.h" 3 4 extern FILE *popen (__const char *__command, __const char *__modes) ; extern int pclose (FILE *__stream); extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__)); # 910 "/usr/include/stdio.h" 3 4 extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)); extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ; extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)); # 940 "/usr/include/stdio.h" 3 4 # 2 "example.c" 2 int g(int x) { return x+3; } int f(int x) { return g(x); } int main(void) { return f(8)+1; }预处理的作用:
宏的替换和常量标识符的替换
还有注释的消除
还有找到相关的库文件,将源文件中以”include”格式包含的文件复制到编译的源文件中
如果源代码中有预处理指令(如#if),那么预处理程序将先判断条件,再相应的修改源代码
用编辑器打开example.cpp会发现有很多很多代码,但是看最后部分就会发现,预处理做了宏的替换,还有注释的消除,可以理解为无关代码的清除。
在这个预处理阶段,源文件把include<stdio.h>代码编译进来,生成扩展的源文件example.cpp,这一步对程序员来说是透明的,预处理完成之后会自动将example.cpp进行编译。
2.2 编译
gcc -S -o example.s example.c
生成example.s文件
.file "example.c" .text .globl g .type g, @function g: .LFB0: .cfi_startproc pushl %ebp ;保存old ebp,以便下次返回用 .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp ;将esp的值赋给ebp,使得ebp指向新的一段活动记录 .cfi_def_cfa_register 5 movl 8(%ebp), %eax ;ebp +8地址中的参数赋给寄存器eax addl $3, %eax ;eax寄存器的值加3 popl %ebp ;从栈中弹出ebp,使得ebp回复到原来的位置 .cfi_def_cfa 4, 4 .cfi_restore 5 ret ;返回eip,回到原来调用函数的下一条指令 .cfi_endproc .LFE0: .size g, .-g .globl f .type f, @function f: .LFB1: .cfi_startproc pushl %ebp ;保存old ebp,以便下次返回用 .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp ;将esp的值赋给ebp,使得ebp指向新的一段活动记录 .cfi_def_cfa_register 5 subl $4, %esp ;esp向栈底开辟4个字节的空间 movl 8(%ebp), %eax ;ebp +8地址中的参数赋给寄存器eax movl %eax, (%esp) ;将寄存器eax中的值赋给esp所指向的空间 call g ;调用函数g leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret ;返回eip,回到原来调用函数的下一条指令 .cfi_endproc .LFE1: .size f, .-f .globl main .type main, @function main: .LFB2: .cfi_startproc pushl %ebp ;保存old ebp,以便下次返回用 .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp ;将esp的值赋给ebp,使得ebp指向新的一段活动记录 .cfi_def_cfa_register 5 subl $4, %esp ;esp向栈底开辟4个字节的空间 movl $8, (%esp) ;ebp +8地址中的参数赋给寄存器eax call f ;调用函数f addl $1, %eax ;eax寄存器的值加1 leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret ;返回eip,回到原来调用函数的下一条指令 .cfi_endproc .LFE2: .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3" .section .note.GNU-stack,"",@progbits
在上述文件中多处出现以.cfi开头的命令,虽然通过查阅一定资料,但是还是不是很理解,可以参考这里。
程序执行过程中,内存的变化如下:
正向调用过程如下:
当函数调用结束时回复到调用函数的下一条指令的地方,这时候内存的变化,%ebp和%esp我没有画图显示。
从上面的.s文件可以看出,每一个函数的开头总是:
pushl %ebp ;保存old ebp,以便下次返回用
movl %esp, %ebp ;将esp的值赋给ebp,使得ebp指向新的一段活动记录
把ebp压入栈中,是为了在函数返回的时候便于恢复以前的ebp值。而之所以要保存一些寄存器,在于编译器可能要求某些寄存器在调用前后保持不变,那么函数就可以在函数开始时将这些寄存器的值压入栈中,在结束时取出。所以在函数返回时,所进行的标准结尾与标准开头正好相反。
movl %ebp,%esp
pop %ebp
2.3 汇编
gcc -c -o example.o example.c
as -o example.o example.s
生成example.o文件
汇编阶段把编译阶段生成的example.s文件转换为二进制目标文件,也就是翻译成机器语言指令,把这些指令打包成可重定为目标程序的格式。
2.4 连接
gcc -o example example.o
生成可执行文件example,没有后缀名。
在终端可以执行:./example
可以用file 命令查看example的属性。
终端执行命令:file example
显示如下:
songzeyu@ubuntu:~/Linux system/lab1$ file example
example: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, not stripped
其中的executable指出该文件为ELF中的可执行文件类型。
3 计算机工作模式:单任务和多任务的浅析
3.1 单任务的工作模式
计算机在同一时间只能运行一个应用程序,计算机首先从外存中加载程序到内存,然后依次执行程序指令,完全执行完毕之后才可以加载、执行下一个程序。具体的执行过程如上面所分析得一样。
单任务的缺点,由于CPU资源十分昂贵,如果我们需要优先运行级别较高的程序,单任务是不能满足要求的。
3.2 多任务的工作模式
计算机在同一时间可以运行多个应用程序,现在的操作系统基本上都是多任务工作模式的。同时运行的多个程序之前不会相互干扰,但是让CPU同时运行多个程序,必须使用并行技术,最容易理解的就是“时间片轮转进程调度算法”。
4 实验小结
PS:这次实验咋一看也许觉得很简单,但是当我真正去做的时候,发现其中的很多细节的知识点很模糊。计算机是如何工作这本身就不是一个简单的问题,就算是科班出生的同学也并不一定能正确的解释它的运行机制。所以从实验的前期准备到开始做实验我都很认真。
之前尽管知道程序运行的过程为:预处理、编译、汇编、连接,但是对其中的每一步是如何实现的,如何进行命名的操作都不是很清楚,平时运行程序只要一行命令就可以完成:gcc example -o example.c。通过这次实验我掌握了分别进行操作。在这之前我查阅了一些资料用于这次实验:
编译器的工作模式:
0、 gcc -o example example.c 不加 -c -S -E 参数,编译器将执行预处理/编译/汇编/链接操作直接生成可执行代码。
1、 gcc -c -o example.o example.c的作用
-c 参数将对源程序example.c进行预处理,编译,汇编操作,生成example.o文件。去掉指定输出选项"-o example.o"自动输出为example.o,所以这里-o加不加都可以。
2、 gcc -S -o example.s example.c的作用
-S参数将对源程序example.c进行预处理,编译,生成example.s文件。-o选项同上。
3、 gcc -E -o example.i example.c的作用
-E 参数将对源程序example.c进行预处理,生成 example.i文件(不同版本不宜样),就是将#include ,#define 等进行文件插入及宏扩展等操作。
4、 gcc -v -o example example.c的作用
加上-v 参数,显示编译时的详细信息,编译器的版本,编译过程等。
5、gcc -Wall -o example example.c
-Wall 选项打开了所有需要注意的警告信息,像在声明之前就使用的函数,声明后却没有使用的变量等。
6、gcc -Ox -o example example.c
虽然这些都是基本知识,可是在实验之前我并不了解。
另一方面通过这次实验我对栈的理解有了深一步的理解和掌握。
5 参考资料
5.1 《程序员的自我修养—链接、装载与库》
这是我目前看到的将这方面的知识比较好的一本书,我主要看了第十章“内存”,里面图文并茂的讲解给这次实验帮了不少的忙。
5.2 《深入理解计算机系统》
我主要看了第三章“程序的机器级表示”,核心的知识点讲了程序在堆栈中的表示。
相关文章推荐
- Linux操作系统分析-How the computer works
- Linux操作系统分析(1)- How program works
- Linux 内核分析 之一:How Computer Works 实验
- Linux操作系统分析(1)- How program works
- How the Computer Works
- How the Computer Works (based on X86/Linux)
- How the Linux Kernel initcall Mechanism Works
- how to check the computer is 32 bit or 64bit in linux
- The Linux Bootdisk HOWTO 中译版
- Linux操作系统分析(5)- 计时器
- How JavaScript works: an overview of the engine, the runtime, and the call stack
- How to Share a Theme With All Users in a Windows 7 Computer?
- 一个操作系统的实现(1):分析linux下如何运行一个执行文件
- How to count the number of threads in a process on Linux
- Object-Oriented JavaScript Part 2: How the prototype works
- 【Linux操作系统分析】进程——进程切换,进程的创建和撤销
- How the Procedural Map Generation Works in Reactor Heart
- Linux内和分析(二)操作系统是如何工作的
- Linux操作系统分析之计算机工作原理
- linux驱动—input输入子系统—The simplest example(一个最简单的实例)分析(1)