「BUAA OS Lab1」内核、Boot和prinitf

Posted by saltyfishyjk on 2022-03-28
Words 5.1k and Reading Time 23 Minutes
Viewed Times

「BUAA OS Lab1」内核、Boot和prinitf

Part 0 前言

Lab1的要求包括:从操作系统角度理解 MIPS 体系结构、掌握操作系统启动的基本流程、掌握 ELF 文件的结构和功能和完成 printf 函数的编写。实验指导书有详尽的介绍,这里不再重复造轮子,仅针对学习和实验过程中的体会和思考加以记录。

Part 1 操作系统的启动

实验的环境

区分以下几个概念是重要的:

模拟硬件环境:GXemul,这是一款计算机架构仿真器,可以模拟需要的CPU等硬件环境,这也是我们进行仿真实验的平台。

编写代码的平台:Linux。我们的跳板机的操作系统是Linux,我们输入的各种如cdls等指令就是Linux指令。

交叉编译:我们的跳板机是x86平台,其编译器服务于x86平台,但我们希望在跳板机中写出的操作系统运行于MIPS平台,因此,我们需要的是编译出可以在MIPS平台运行的程序,普通编译器做不到这一点。交叉编译器,就是在一个体系结构的平台上编译出另一个体系结构的程序的编译器。

启动操作系统

系统加电后,操作系统将要启动,这是一个复杂纠结的过程,以下简要总结思考路径。

  1. 内核代码要能够运行,必须要被CPU直接访问,排除磁盘。
  2. 内核代码需要稳定,掉电后依然不能失去,排除易失性存储器内存RAM。RAM掉电后会丢失全部数据。
  3. 排除法知,现仅剩CPU可以直接访问的非易失性存储器ROM和FLASH。
  4. 上述两种存储器的存储空间一般会被映射到CPU寻址空间的确定区域,是有限的,不能装载内核较大的一版操作系统。
  5. 我们希望一个计算机硬件可以支持多个操作系统,并且希望操作系统便于移植而不和特定硬件完全绑死。

基于以上考虑,硬件初始化工作用bootloader程序完成,有以下好处和特点:

  1. 其单独将硬件初始化工作从操作系统中抽离,实现硬件启动和软件启动的分离,因此需要在非易失性存储器中存放的硬件启动指令不多。
  2. bootloader完成硬件初始化后,为软件启动做相应准备,如,将内核镜像文件从存放的存储器(如磁盘)读取到RAM。即,我们可以在bootloader后选择加载哪一个内核镜像,达成多重开机的功能,也达成一个硬件运行多个操作系统的功能。
  3. bootloader清晰地划分了硬件启动和软件启动的边界,这样虽然也不能使操作系统不依赖硬件,但是明显提高了操作系统和硬件交互的抽象层次。

Bootloader

Bootloader大多分为stage1和stage2两部分,一下简述这两部分工作。

stage1

初始化硬件设备,包括watchdog timer、中断、时钟、内存。此时内存RAM尚未初始化完成,因此stage1直接运行在bootloader的存储设备上(如FLASH)。此外,stage1会为加载stage2准备RAM空间,将stage2的代码复制到RAM空间,设置堆栈,最后跳转到stage2的入口函数。

stage2

stage2运行在RAM中,充足的运行环境使得可以用C语言实现复杂功能。初始化stage2需要使用的硬件设备和其他功能,将内核镜像文件从存储器读到RAM中,设置内核的启动参数,将CPU指令寄存器的内容设置为内核入口函数的地址,将控制权从bootloader转交给操作系统内核。

image-20220330152009977

Part 2 Kernal

顶层Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# Main makefile
#
# Copyright (C) 2007 Beihang University
# Written by Zhu Like ( zlike@cse.buaa.edu.cn )
#

drivers_dir := drivers # 赋值符号,对变量的定义语句
boot_dir := boot
init_dir := init
lib_dir := lib
tools_dir := tools
test_dir :=
#上面定义了各种文件夹名称
vmlinux_elf := gxemul/vmlinux
#这个是我们最终需要生成的内核elf文件
link_script := $(tools_dir)/scse0_3.lds #链接用的脚本,指导连接器将多个.o文件链接成目标可执行的脚本

modules := boot drivers init lib # modules定义了内核所包含的所有模块
objects := $(boot_dir)/start.o \ # objects表示要编译出内核所依赖的所有.o文件;\表示这一行没有结束,下一行的内容和这一行是连在一起的,提高了文件的可读性
$(init_dir)/main.o \
$(init_dir)/init.o \
$(drivers_dir)/gxconsole/console.o \
$(lib_dir)/*.o \
#定义了需要生成的各种文件

.PHONY: all $(modules) clean # 表明列在其后的目标不受修改时间的约束。即,一旦该规则被调用,无视make工具编译时有关时间戳的性质,无论依赖文件是否被修改,一定保证它被执行

all: $(modules) vmlinux #我们的“最终目标”,其中列出的dependencies表明构建整个项目依赖于构建好所有的模块以及vmlinunx

vmlinux: $(modules) # vmlinux的构建依赖于所有的模块
$(LD) -o $(vmlinux_elf) -N -T $(link_script) $(objects) # 调用了链接器将之前构建的各模块产生的所有.o问年间在linker script的指导下链接到一起,产生最终的vmlinux可执行文件

##注意,这里调用了一个叫做$(LD)的程序

$(modules): # 定义了每个模块的构建方法是调用对应模块目录下的Makefile
$(MAKE) --directory=$@

#进入各个子文件夹进行make

clean: # 40-45行定义了如何清理所有被构建出来的文件
for d in $(modules); \
do \
$(MAKE) --directory=$$d clean; \
done; \
rm -rf *.o *~ $(vmlinux_elf)

include include.mk

完成lab1课下后回看这份代码,会有新的体会。

  1. tool 目录下只有 scse0_3.lds 这个 linker script 文件,我们会在下面的小节中详细讲解。
  2. gxemul 目录存放着 GXemul 仿真器启动内核文件时所需要的配置文件,另外代码编译完成后生成的名为“vmlinux”的内核文件也会放在这个目录下。
  3. boot 目录中需要注意的是 start.S 文件。这个文件中的 _start 函数是 CPU 控制权被转交给内核后执行的第一个函数,主要工作是初始化硬件相关的设置,为之后操作系统初始化做准备,最后跳转到 init/main.c 文件中定义的 main 函数。
  4. init 目录中主要有两个代码文件 main.c 和 init.c,其作用是初始化操作系统。通过分析函数调用关系,我们可以发现最终调用的函数是 init.c 文件中的 mips_init()函数。在本实验中此函数只是简单的打印输出,而在之后的实验中会逐步添加新的内核功能,所以诸如内存初始化等具体方面的初始化函数都会在这里被调用。
  5. include 目录中存放系统头文件。在本实验中需要用到的头文件是 mmu.h 文件,这个文件中有一张内存布局图,我们在填写 linker script 的时候需要根据这个图来设 置相应节的加载地址。
  6. lib 目录存放一些常用库函数,这里主要存放一些打印的函数。
  7. driver 目录中存放驱动相关的代码,这里主要存放的是终端输出相关的函数。

start.S

位置:boot/start.S

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <asm/regdef.h>
#include <asm/cp0regdef.h>
#include <asm/asm.h>


.section .data.stk
KERNEL_STACK:
.space 0x8000


.text
LEAF(_start) /*LEAF is defined in asm.h and LEAF functions don't call other functions*/

.set mips2 /*.set is used to instruct how the assembler works and control the order of instructions */
.set reorder

/* Disable interrupts */
mtc0 zero, CP0_STATUS

/* Disable watch exception. */
mtc0 zero, CP0_WATCHLO
mtc0 zero, CP0_WATCHHI

/* disable kernel mode cache */
mfc0 t0, CP0_CONFIG
and t0, ~0x7
ori t0, 0x2
mtc0 t0, CP0_CONFIG

/*
Exercise 1.4. Please complete this part.
To do:
set up stack.
hint: you can reference the memory layout in the include/mmu.h.
call main func.
*/
/* ans begin*/
li sp, 0x80400000
li t0, 0x80400000
#sw t0, mCONTEXT
jal main

/*ans end*/

loop:
j loop
nop
END(_start) /*the function defined in asm.h*/

main.c

地址:init/main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* Copyright (C) 2001 MontaVista Software Inc.
* Author: Jun Sun, jsun@mvista.com or jsun@junsun.net
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*/

#include <printf.h>
#include <pmap.h>

int main()
{
printf("main.c:\tmain is start ...\n");

mips_init();
panic("main is over is error!");

return 0;
}

init.c

地址:init/init.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <asm/asm.h>
#include <pmap.h>
#include <env.h>
#include <printf.h>
#include <kclock.h>
#include <trap.h>


void mips_init()
{
printf("init.c:\tmips_init() is called\n");


//for your degree,don't delete these.
//------------|
#ifdef FTEST
FTEST();
#endif

#ifdef PTEST
ENV_CREATE(PTEST);
#endif
//-----------|
panic("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
}

ELF

文件结构

image-20220330163247799

  1. ELF Header,包括程序的基本信息,比如体系结构和操作系统,同时也包含了Section Header Table和Program Header Table相对文件的偏移量(offset)。
  2. Program Header Table,也可以称为Segment Table,主要包含程序中各个Segment的信息,Segment的信息需要在运行时刻使用。
  3. Section Header Table,主要包含各个Section的信息,Section的信息需要在程序编译和链接的时候使用。
  4. Segments,就是各个Segment。Segment 则记录了每一段数据(包括代码等内容) 需要被载入到哪里,记录了用于指导应用程序加载的各类信息。
  5. Sections,就是各个Section。就是各个 Section。Section 记录了程序的代码段、数据段等各个段的内容,主要是链接器在链接的过程中需要使用。

Program Header Table和Section Header Table指向了同样的地方,这就说明两者所代表的内容是重合的,这意味着两者只是同一个东西的不同视图,产生这种情况的原因在于ELF文件需要在两种场合使用:

  1. 组成可重定位文件,参与可执行文件和可共享文件的链接。
  2. 组成可执行文件或者可共享文件,在运行时为加载器提供信息。

kerelf.h

地址:./readelf/kerelf.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/* This file defines standard ELF types, structures, and macros.
Copyright (C) 1995, 1996, 1997, 1998, 1999 Free Software Foundation, Inc.
This file is part of the GNU C Library.
Contributed by Ian Lance Taylor <ian@cygnus.com>.

The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.

The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with the GNU C Library; see the file COPYING.LIB. If not,
write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA. */

#ifndef _KER_ELF_H
#define _KER_ELF_H

/* ELF defination file from GNU C Library. We simplefied this
* file for our lab, removing definations about ELF64, structs and
* enums which we don't care.
*/

#include "types.h"

typedef u_int64_t uint64_t;
typedef u_int32_t uint32_t;
typedef u_int16_t uint16_t;

/* Type for a 16-bit quantity. */
typedef uint16_t Elf32_Half;

/* Types for signed and unsigned 32-bit quantities. */
typedef uint32_t Elf32_Word;
typedef int32_t Elf32_Sword;

/* Types for signed and unsigned 64-bit quantities. */
typedef uint64_t Elf32_Xword;
typedef int64_t Elf32_Sxword;

/* Type of addresses. */
typedef uint32_t Elf32_Addr;

/* Type of file offsets. */
typedef uint32_t Elf32_Off;

/* Type for section indices, which are 16-bit quantities. */
typedef uint16_t Elf32_Section;

/* Type of symbol indices. */
typedef uint32_t Elf32_Symndx;

/* The ELF file header. This appears at the start of every ELF file. */
/* ELF 文件的文件头。所有的ELF文件均以此为起始 */
#define EI_NIDENT (16)

typedef struct {
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
// 存放魔数以及其他信息
Elf32_Half e_type; /* Object file type */
// 文件类型
Elf32_Half e_machine; /* Architecture */
// 机器架构
Elf32_Word e_version; /* Object file version */
// 文件版本
Elf32_Addr e_entry; /* Entry point virtual address */
// 入口点的虚拟地址
Elf32_Off e_phoff; /* Program header table file offset */
// 程序头表所在处与此文件头的偏移
Elf32_Off e_shoff; /* Section header table file offset */
// 节头表所在处与此文件头的偏移,ELF文件头地址加上该偏移即为节头表第一项地址
Elf32_Word e_flags; /* Processor-specific flags */
// 针对处理器的标记
Elf32_Half e_ehsize; /* ELF header size in bytes */
// ELF文件头的大小(单位为字节)
Elf32_Half e_phentsize; /* Program header table entry size */
// 程序头表入口大小
Elf32_Half e_phnum; /* Program header table entry count */
// 程序头表入口数
Elf32_Half e_shentsize; /* Section header table entry size */
// 节头表入口大小
Elf32_Half e_shnum; /* Section header table entry count */
// 节头表入口数
Elf32_Half e_shstrndx; /* Section header string table index */
// 节头字符串编号
} Elf32_Ehdr;
typedef struct {
// section name
Elf32_Word sh_name;
// section type
Elf32_Word sh_type;
// section flags
Elf32_Word sh_flags;
// section addr
Elf32_Addr sh_addr;
// offset from elf head of this entry
Elf32_Off sh_offset;
// byte size of this section
Elf32_Word sh_size;
// link
Elf32_Word sh_link;
// extra info
Elf32_Word sh_info;
// alignment
Elf32_Word sh_addralign;
// entry size
Elf32_Word sh_entsize;
}Elf32_Shdr;

typedef struct {
// segment type
Elf32_Word p_type;
// offset from elf file head of this entry
Elf32_Off p_offset;
// virtual addr of this segment
Elf32_Addr p_vaddr;
// physical addr, in linux, this value is meanless and has same value of p_vaddr
Elf32_Addr p_paddr;
// file size of this segment
Elf32_Word p_filesz;
// memory size of this segment
Elf32_Word p_memsz;
// segment flag
Elf32_Word p_flags;
// alignment
Elf32_Word p_align;
}Elf32_Phdr;


/* Legal values for p_type (segment type). */

#define PT_NULL 0 /* Program header table entry unused */
#define PT_LOAD 1 /* Loadable program segment */
#define PT_DYNAMIC 2 /* Dynamic linking information */
#define PT_INTERP 3 /* Program interpreter */
#define PT_NOTE 4 /* Auxiliary information */
#define PT_SHLIB 5 /* Reserved */
#define PT_PHDR 6 /* Entry for header table itself */
#define PT_NUM 7 /* Number of defined types. */
#define PT_LOOS 0x60000000 /* Start of OS-specific */
#define PT_HIOS 0x6fffffff /* End of OS-specific */
#define PT_LOPROC 0x70000000 /* Start of processor-specific */
#define PT_HIPROC 0x7fffffff /* End of processor-specific */

/* Legal values for p_flags (segment flags). */

#define PF_X (1 << 0) /* Segment is executable */
#define PF_W (1 << 1) /* Segment is writable */
#define PF_R (1 << 2) /* Segment is readable */
#define PF_MASKPROC 0xf0000000 /* Processor-specific */

int readelf(u_char *binary,int size);

#endif /* kerelf.h */

readelf.c

地址:./readelf/readelf.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/* This is a simplefied ELF reader.
* You can contact me if you find any bugs.
*
* Luming Wang<wlm199558@126.com>
*/

#include "kerelf.h"
#include <stdio.h>
/* Overview:
* Check whether it is a ELF file.
*
* Pre-Condition:
* binary must longer than 4 byte.
*
* Post-Condition:
* Return 0 if `binary` isn't an elf. Otherwise
* return 1.
*/
int is_elf_format(u_char *binary)
{ // 对魔数判等,若不等则说明不是elf格式文件
Elf32_Ehdr *ehdr = (Elf32_Ehdr *)binary;
if (ehdr->e_ident[EI_MAG0] == ELFMAG0 &&
ehdr->e_ident[EI_MAG1] == ELFMAG1 &&
ehdr->e_ident[EI_MAG2] == ELFMAG2 &&
ehdr->e_ident[EI_MAG3] == ELFMAG3) {
return 1;
}

return 0;
}

/* Overview:
* read an elf format binary file. get ELF's information
*
* Pre-Condition:
* `binary` can't be NULL and `size` is the size of binary.
*
* Post-Condition:
* Return 0 if success. Otherwise return < 0.
* If success, output address of every section in ELF.
*/

/*
Exercise 1.2. Please complete func "readelf".
*/
int readelf(u_char *binary, int size)
{
Elf32_Ehdr *ehdr = (Elf32_Ehdr *)binary; // 强转ehdr指向文件头

int Nr;

Elf32_Shdr *shdr = NULL;

u_char *ptr_sh_table = NULL;
Elf32_Half sh_entry_count;
Elf32_Half sh_entry_size;

// check whether `binary` is a ELF file.
if (size < 4 || !is_elf_format(binary)) {
printf("not a standard elf format\n");
return 0;
}

// get section table addr, section header number and section header size
ptr_sh_table = binary + ehdr -> e_shoff;
sh_entry_count = ehdr -> e_shnum;
sh_entry_size = ehdr -> e_shentsize;
// for each section header, output section number and section addr.
for (Nr = 0; Nr < sh_entry_count; Nr++) {
shdr = (Elf32_Shdr *)(ptr_sh_table + Nr * sh_entry_size);
printf("%d:0x%x\n", Nr, shdr->sh_addr);
}
// hint: section number starts at 0.


return 0;
}

main.c

地址:./readelf/main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <stdlib.h>

extern int readelf(u_char* binary, int size);
/*
overview: input a elf format file name from control line, call the readelf function
to parse it.
params:
argc: the number of parameters
argv: array of parameters, argv[1] shuold be the file name.

*/
int main(int argc,char *argv[])
{
FILE* fp;
int fsize;
unsigned char *p;


if(argc < 2)
{
printf("Please input the filename.\n");
return 0;
}
if((fp = fopen(argv[1],"rb"))==NULL)
{
printf("File not found\n");
return 0;
}
fseek(fp,0L,SEEK_END);
fsize = ftell(fp);
p = (u_char *)malloc(fsize+1);
if(p == NULL)
{
fclose(fp);
return 0;
}
fseek(fp,0L,SEEK_SET);
fread(p,fsize,1,fp);
p[fsize] = 0;


readelf(p,fsize);
return 0;
}

MIPS内存布局

image-20220320222345638

从硬件角度讲,这四个区域的情况如下:

  1. User Space(kuseg) 0x00000000-0x7FFFFFFF(2G):这些是用户模式下可用的地址。在使用这些地址的时候,程序会通过MMU映射到实际的物理地址上。
  2. Kernel Space Unmapped Cached(kseg0) 0x80000000-0x9FFFFFFF(512MB):只需要将地址的高3位清零,这些地址就被转换为物理地址。 也就是说,逻辑地址到物理地址的映射关系是硬件直接确定,不通过MMU。因为转换很简单,我们常常把这些地址称为“无需转换的”。 一般情况下,都是通过cache 对这段区域的地址进行访问。
  3. Kernel Space Unmapped Uncached(kseg1) 0xA0000000-0xBFFFFFFF(512MB):这一段地址的转换规则与前者相似, 通过将高 3 位清零的方法将地址映射为相应的物理地址,重复映射到了低端 512MB 的物理地址。需要注意的是,kseg1 不通过cache 进行存取。
  4. Kernel Space Mapped Cached(kseg2) 0xC0000000-0xFFFFFFFF(1GB):这段地址只能在内核态下使用并且需要MMU 的转换。

需要通过MMU映射访问的地址得在建立虚拟内存机制后才能正常使用, 是由操作系统所管理的。因此,在载入内核时,我们只能选用不需要通过MMU的内存空间,因为此时尚未建立虚存机制。好了,满足这一条件的只有kseg0和kseg1了。 而kseg1是不经过cache的,一般用于访问外部设备。所以,我们的内核只能放在kseg0了。

MMU.H

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#ifndef _MMU_H_
#define _MMU_H_

#include "types.h"
/*
* This file contains:
*
* Part 1. MIPS definitions.
* Part 2. Our conventions.
* Part 3. Our helper functions.
*/

/*
* Part 1. MIPS definitions.
*/
#define BY2PG 4096 // bytes to a page
#define PDMAP (4*1024*1024) // bytes mapped by a page directory entry
#define PGSHIFT 12
#define PDX(va) ((((u_long)(va))>>22) & 0x03FF)
#define PTX(va) ((((u_long)(va))>>12) & 0x03FF)
#define PTE_ADDR(pte) ((u_long)(pte)&~0xFFF)

// page number field of address
#define PPN(va) (((u_long)(va))>>12)
#define VPN(va) PPN(va)

#define VA2PFN(va) (((u_long)(va)) & 0xFFFFF000 ) // va 2 PFN for EntryLo0/1
//$#define VA2PDE(va) (((u_long)(va)) & 0xFFC00000 ) // for context
/* Page Table/Directory Entry flags
* these are defined by the hardware
*/
#define PTE_G 0x0100 // Global bit
#define PTE_V 0x0200 // Valid bit
#define PTE_R 0x0400 // Dirty bit ,'0' means only read ,otherwise make interrupt
#define PTE_UC 0x0800 // unCached

/*
* Part 2. Our conventions.
*/

/*
o 4G -----------> +----------------------------+------------0x100000000
o | ... | kseg3
o +----------------------------+------------0xe000 0000
o | ... | kseg2
o +----------------------------+------------0xc000 0000
o | Interrupts & Exception | kseg1
o +----------------------------+------------0xa000 0000
o | Invalid memory | /|\
o +----------------------------+----|-------Physics Memory Max
o | ... | kseg0
o VPT,KSTACKTOP-----> +----------------------------+----|-------0x8040 0000-------end
o | Kernel Stack | | KSTKSIZE /|\
o +----------------------------+----|------ |
o | Kernel Text | | PDMAP
o KERNBASE -----> +----------------------------+----|-------0x8001 0000 |
o | Interrupts & Exception | \|/ \|/
o ULIM -----> +----------------------------+------------0x8000 0000-------
o | User VPT | PDMAP /|\
o UVPT -----> +----------------------------+------------0x7fc0 0000 |
o | PAGES | PDMAP |
o UPAGES -----> +----------------------------+------------0x7f80 0000 |
o | ENVS | PDMAP |
o UTOP,UENVS -----> +----------------------------+------------0x7f40 0000 |
o UXSTACKTOP -/ | user exception stack | BY2PG |
o +----------------------------+------------0x7f3f f000 |
o | Invalid memory | BY2PG |
o USTACKTOP ----> +----------------------------+------------0x7f3f e000 |
o | normal user stack | BY2PG |
o +----------------------------+------------0x7f3f d000 |
a | | |
a ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
a . . |
a . . kuseg
a . . |
a |~~~~~~~~~~~~~~~~~~~~~~~~~~~~| |
a | | |
o UTEXT -----> +----------------------------+ |
o | | 2 * PDMAP \|/
a 0 ------------> +----------------------------+ -----------------------------
o
*/

#define KERNBASE 0x80010000

#define VPT (ULIM + PDMAP )
#define KSTACKTOP (VPT-0x100)
#define KSTKSIZE (8*BY2PG)
#define ULIM 0x80000000

#define UVPT (ULIM - PDMAP)
#define UPAGES (UVPT - PDMAP)
#define UENVS (UPAGES - PDMAP)

#define UTOP UENVS
#define UXSTACKTOP (0x82000000)

#define USTACKTOP (UTOP - 2*BY2PG)
#define UTEXT 0x00400000



/*
* Part 3. Our helper functions.
*/

void bcopy(const void *, void *, size_t);
void bzero(void *, size_t);

extern char bootstacktop[], bootstack[];

extern u_long npage;

typedef u_long Pde;
typedef u_long Pte;

#define PADDR(kva) \
({ \
u_long a = (u_long) (kva); \
if (a < ULIM) \
panic("PADDR called with invalid kva %08lx", a);\
a - ULIM; \
})


// translates from physical address to kernel virtual address
#define KADDR(pa) \
({ \
u_long ppn = PPN(pa); \
if (ppn >= npage) \
panic("KADDR called with invalid pa %08lx", (u_long)pa);\
(pa) + ULIM; \
})

#define assert(x) \
do { if (!(x)) panic("assertion failed: %s", #x); } while (0)

#endif // !_MMU_H_

Linker Script

LinkerScript记录了各个section如何映射到segment,以及各个segment应该被加载到的位置。

1
2
3
.text 保存可执行文件的操作指令
.data 保存已初始化的全局变量和静态变量
.bss 保存未初始化的全局变量和静态变量

Part 3 printf

printf.c

地址:lib/printf.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/*
* Copyright (C) 2001 MontaVista Software Inc.
* Author: Jun Sun, jsun@mvista.com or jsun@junsun.net
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*/

#include <printf.h>
#include <print.h>
#include <drivers/gxconsole/dev_cons.h>



void printcharc(char ch);

void halt(void);

static void myoutput(void *arg, char *s, int l)
{
int i;

// special termination call
if ((l==1) && (s[0] == '\0')) return;

for (i=0; i< l; i++) {
printcharc(s[i]);
if (s[i] == '\n') printcharc('\n');
}
}

void printf(char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
lp_Print(myoutput, 0, fmt, ap);
va_end(ap);
}

void
_panic(const char *file, int line, const char *fmt,...)
{
va_list ap;


va_start(ap, fmt);
printf("panic at %s:%d: ", file, line);
lp_Print(myoutput, 0, (char *)fmt, ap);
printf("\n");
va_end(ap);


for(;;);
}

console.c

地址:drivers/gxconsole/console.c

Part 4 测试printf详细流程

笔者在完成Exercise1.5后,对于如何测试运行写好的printf摸索了一阵,在这里简单记录。我们假设读者目前完成了Exercise1.1-1.5全部内容。

  1. 在学号目录下,执行make clean,清除已有的编译结果,避免潜在的问题。

  2. 在学号目录下,执行make,在gxemul目录下得到vmlinux内核文件。这个阶段可能出现Error报错,可以仔细阅读报错信息。

  3. 执行/OSLAB/gxemul -E testmips -C R3000 -M 64 ./gxemul/vmlinux命令,得到测试结果。如果出现了指导书PDF83页中的内容,说明printf已经编写正确。

    值得注意的是,命令中不是gxemul,而是/OSLAB/gxemul,若使用gxemul会报错-bash: gxemul: command not found,算是对指导书的小小捉虫。

  4. ctrl+C中断模拟,quit退出仿真器,ctrl+z仿真器挂在后台,fg将GXemul作业调至前台


This is copyright.