北航操作系统课程-第三次作业-系统引导与程序装载

北航操作系统课程-第三次作业-系统引导与程序装载


北京航空航天大学计算机学院-2020春操作系统课程
题目作者为北航计算机学院操作系统课程组,答案为博主原创。水平有限,无法保证作答正确性,如有错误敬请批评指正。部分作答源自百度谷歌等其他资料,如有侵权联系删除


1 假设有一个简单的计算机硬件系统,CPU 是同学们自己设计的,有内存和硬盘(可以没有MMU),请设计一个尽量简单的启动过程。(要求:列出必要的硬件支持、启动软件的基本功能和启动过程,只要将控制权交给操作系统镜像就算完成启动)

首先假设我们同学自己设计的简单硬件系统如下:

  • MIPS体系结构的CPU
  • Bootloader存放在flash上
  • 没有MMU作地址转换,没有cache和多级存储
  • 内存分区简单划分为内核区和用户区
  • 有外设总线可以用于连接少部分外设

在此基础上讨论其启动过程。系统的启动过程大致如下所述:

  1. 系统加电,开始运行flash上的汇编代码,包括初始化向量表、协处理器;设置CPU、总线和内存的工作频率;设置堆栈等。最终跳转至C语言入口,开始执行flash上的C代码。
  2. 利用C语言做一些更多设备和软件的初始化,包括时钟、环境变量、串口、控制台的初始化,显示一些Bootloader的启动信息,初始化整个内存。至此,串口和内存完成了启动。
  3. 将内存划分为用户和内核区,设置内核堆栈,然后将flash上的代码搬到内存RAM上继续运行,在此步骤也完成flash和RAM地址转换重定位。
  4. 初始化malloc、外设总线及所连接的外设,将硬盘上的操作系统镜像拷贝到内存的内核区(操作系统第一条指令所在的物理地址处),将控制权交给操作系统。

2 编写一段程序,分别输出属于该程序代码段、数据段、堆和栈的地址。(提示:不要求一定要输出各个段的首地址,不要编一个ELF 解析程序,只要输出不同类型变量的地址就可以。)

在Linux环境下,可执行文件(ELF文件)在没有调入内存执行时拥有3个部分,分别是text、data和BSS段,而堆栈段是在程序装入内存后由系统分配的。在C语言中,各个段的内容如下:

段名 内容
Text segment 可执行代码、常量数据
Data segment 已初始化的全局变量、已初始化的全局静态变量、局部静态变量
BSS segment 未初始化的全局变量
Heap 动态内存分配
Stack 局部变量、函数参数

其中常量之所以归入了Text段,依我个人见解,有可能是在编译过程中常量优化将其直接装载进代码中。根据C语言的特性,我们可以编写如下的代码,通过不同类型的变量来输出不同段的地址:

/*
 * @Name: 03-02.c
 * @Description: Operating System course by SCSE of Beihang University.
 * @Description: To show the address of each segment when running C program on Linux(Ubuntu16.04).
 * @Version: 1.0
 * @Author: JeremyZhao1998
 * @Date: 2020-03-11 16:28:09
 * @LastEditors: JeremyZhao1998
 * @LastEditTime: 2020-03-11 18:59:18
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Text segment: code, constants
void code(){}
const char constants[] = "constants";

// Data segment: initialized global variables
char initialized_global[] = "initialized_global";
static char initialized_global_static[] = "initialized_global_static";

// Bss segment: uninitialized global variables
char uninitialized_global[20];

int main()
{
	strcpy(uninitialized_global, "uninitialized_global");
	
	// Data segment: local static variables
	static char local_static[] = "local_static";

	// Heap: dynamic memory allocation
	char* dynamic_memory_allocation;
	dynamic_memory_allocation = (char*)malloc(sizeof(char)*10);

	// Stack: local variables
	char local[] = "local";

	printf("\n===================================================================\n\n");

	printf("Text segment: code, constants\n");
	printf("Function address:                           %p\n", &code);
	printf("Constant address:                           %p\n", &constants);
	printf("-------------------------------------------------------------------\n");
	printf("Data segment: initialized global variables\n");
	printf("Initialized global variable address:        %p\n", &initialized_global);
	printf("Initialized static global variable address: %p\n", &initialized_global_static);
	printf("Local static variable address:              %p\n", &local_static);
	printf("-------------------------------------------------------------------\n");
	printf("Bss segment: uninitialized global variables\n");
	printf("Uninitialized global variable address:      %p\n", &uninitialized_global);
	printf("-------------------------------------------------------------------\n");
	printf("Heap: dynamic memory allocations\n");
	printf("Dynamic memory allocation pointer address:  %p\n", dynamic_memory_allocation);
	printf("-------------------------------------------------------------------\n");
	printf("Stack: local variables\n");
	printf("Local variable address:                     %p\n", &local);

	printf("\n===================================================================\n\n");

	return 0; 
}

其中代码的解释如注释,不再赘述。上面的代码运行在Linux环境下(Ubuntu16.04),其运行结果如下图:
在这里插入图片描述
可以看出,常量确实和函数一起并入Text段,而Text、Data、BSS和堆依次在低地址空间向上延伸,栈空间从高地址向下延伸,与本次作业第四题Linux进程的内存分布相符。

我们可以通过objdump命令来查看可执行文件各个段的大小内容等信息。下图分别是.o文件和最终.out文件dump出的信息:
在这里插入图片描述在这里插入图片描述
其中,Size一列是各个段的大小,File off一列是该段的起始地址,每个段的第二行显示了一些段的信息。可以看到链接重定位前后各个段的大小和起始地址都是有差异的,而通过.out文件右起第二列的相对起始地址也可以看出,各个段在可执行文件中和运行在内存时的相对地址也基本是吻合的。

参考博客:https://blog.csdn.net/yangcunbiao/article/details/83020443


3 举例说明,ELF格式的程序在重定位时对函数和全局变量的不同计算方法。

以课堂讲授的32位MIPS体系结构为例,在MIPS中,不同的重定位类型有着不同的重定位地址计算方法,如下图:
在这里插入图片描述
其中A和AHL分别是指令和地址的附加值,主要用于解决MIPS中少量的变长指令或没有对齐的地址的问题;R_MIPS_26中Local指的是部分带有特殊局部标记的符号,External指的是全局或外部符号;R_MIPS_HI16和R_MIPS_LO16是将32位数据的高16位与低16位分开进行重定位。以具体的例子来看,下图是一段C代码编译过后的.out文件,通过反汇编和ReadELF工具dump出的符号表:
在这里插入图片描述
符号表中符号名称可以看出其类型,read_something是全局函数,而some_global_variable是全局变量。可以从该符号表中看出,函数的重定位类型是R_MIPS_26,其重定位计算方法是处理对齐后右移两位;全局变脸的重定位被拆分为高16位和低16位,其计算方法分别是取高16位和取低16位后处理地址对齐。


4 描述Linux系统中一个可执行文件加载到内存后地址空间的布局,包括代码段、数据段、堆和栈等。(用图和文字描述)

在Linux中,每一个进程都被抽象为task_struct结构体,称为进程描述符,存储着进程各方面的信息,例如打开的文件,信号以及内存等等。task_struct的一个属性mm_struct管理着进程的所有虚拟内存,称为内存描述符。在mm_struct结构体中,存储着进程各个内存段的开始以及结尾,如下图:
在这里插入图片描述
可以看出,进程装载进内存后,从低地址到高地址依次排布着Text段、Data段、BSS段,随后是动态分配的堆、内存映射段以及从高地址向低地址延申的栈。

参考博客:https://www.linuxprobe.com/linux-memory-layout.html

发布了12 篇原创文章 · 获赞 2 · 访问量 484

猜你喜欢

转载自blog.csdn.net/JeremyZhao1998/article/details/104810014
今日推荐