首页 > 其他分享 >希冀 操作系统 实验lab1 内核、Boot和printf(保姆级教程 建议点赞收藏 )

希冀 操作系统 实验lab1 内核、Boot和printf(保姆级教程 建议点赞收藏 )

时间:2024-11-23 19:06:03浏览次数:7  
标签:int fmt arg Boot length lab1 num 点赞 buf

这次我把所有代码都写出来方便大家复制(可以直接复制我的代码粘贴到终端执行)

开始之前首先先拉取lab1的内容(一定先干这个,不然做不了实验!!!!!!!!!!!!!!!!)

先切换到这个目录下

cd 20221105894-lab

变为:

输入

git pull
git checkout lab1

 之后刷新一下你的 学号-lab文件夹,如果改变了就好了,如果没有变为lab1的数据就换成

lab1实验需要Exercise都做!!!!!!

lab1实验需要Exercise都做!!!!!!

lab1实验需要Exercise都做!!!!!!

lab1实验需要Exercise都做!!!!!!

lab1实验需要Exercise都做!!!!!!

一.提取文档内容 

  1. 操作系统启动与内核
    • 启动流程与硬件软件关系:操作系统启动是一个复杂过程,涉及硬件和软件相互依存。计算机由硬件和软件组成,操作系统管理硬件资源。硬件需软件控制,软件依赖硬件载入,早期工程师将此纠结过程称为 “bootstrapping”。操作系统内核是核心部分,需与硬件交互,其代码不能在磁盘或易失性内存中,通常置于非易失性存储器(如 ROM 或 FLASH)。但存在存储空间有限、限制多操作系统启动和不利于移植等问题123。
    • Bootloader 的作用与阶段:为解决上述问题,引入 Bootloader。它分为 stagel 和 stage2 两部分,stagel 初始化硬件设备,在 ROM 或 FLASH 上运行,为 stage2 准备 RAM 空间并复制代码、设置堆栈后跳转;stage2 在 RAM 中运行,用 C 语言实现复杂功能,包括初始化硬件、加载内核镜像到 RAM、设置启动参数并将控制权转交给内核456。
    • gxemul 中的简化流程:在 gxemul 仿真器中,启动流程简化,它提供了 bootloader 功能,可直接加载 elf 格式内核,将内核加载到内存后跳转至入口即可完成启动78。
  2. 编译链接相关知识
    • Makefile 解读:Makefile 指导程序构建,通过阅读可了解源代码生成可执行文件的过程。实验代码顶层 Makefile 定义了变量、规则等,如 modules 定义内核模块,objects 表示编译依赖的.o 文件。它还包含了构建项目的规则、依赖关系以及清理文件的方法,引用的 include.mk 文件定义了交叉编译相关变量9102。
    • ELF 文件结构与功能:ELF 是 Unix 常用文件格式,包括可重定位、可执行和共享对象文件。其结构包含 ELF Header、Program Header Table、Section Header Table、Segments 和 Sections 等部分。连接器通过目标文件中的信息(记录在 ELF 文件中)连接多个目标文件,可通过阅读解析程序了解其详细结构,如完成 readelf.c 中代码以输出 elf 文件的 section header 信息。最终生成的内核为 ELF 格式,由模拟器载入,通过 Linker Script 可控制各段加载地址111213。
  3. MIPS 架构相关内容
    • 内存布局与内核位置:32 位 MIPS CPU 程序地址空间分为 4 个区域,包括 User Space、Kernel Space Unmapped Cached、Kernel Space Unmapped Uncached 和 Kernel Space Mapped Cached。载入内核时,因未建立虚存机制,只能选用无需 MMU 的内存空间,即 kseg0 或 kseg1,而内核通常放在 kseg0,具体位置可在 include/mmu.h 中查看141516。
    • 汇编与 C 语言关系:在操作系统编程中,MIPS 汇编与 C 语言联系紧密。循环和判断语句在 MIPS 汇编中通过判断加跳转实现,函数调用时,编译器在函数前后添加压栈和弹栈操作保存函数状态,调用函数将参数存于 a0 - a3 寄存器,返回值存于 v0 - v1 寄存器。同时,MIPS 有 32 个通用寄存器,遵循一定使用约定,如 s0 - s7 和 fp(或 s8)寄存器在函数调用前后值不变(fp 在调用位置无关函数时有特例),ra 寄存器存放函数返回地址。此外,还有特殊的 PC 寄存器,可用于调试内核171819。

Exercise 

Exercise 1.1

请修改 include.mk 文件,使交叉编译器的路径正确。之后执行 make指令,如果配置一切正确,则会在 gxemul 目录下生成 vmlinux 的内核文件。

在 20221105894-lab目录下找到include.mk 文件  把CROSS_COMPILE 后面的路径改成我下面的

# Common includes in Makefile
#
# Copyright (C) 2007 Beihang University
# Written By Zhu Like ( zlike@cse.buaa.edu.cn )


CROSS_COMPILE :=  /OSLAB/compiler/usr/bin/mips_4KC-
CC			  := $(CROSS_COMPILE)gcc
CFLAGS		  := -O -G 0 -mno-abicalls -fno-builtin -Wa,-xgot -Wall -fPIC
LD			  := $(CROSS_COMPILE)ld

然后 在20221105894-lab目录下输入

make

会在 gxemul 目录下生成 vmlinux 的内核文件

Exercise1.2

阅读./readelf 文件夹中 kerelf.h、readelf.c 以及 main.c 三个文件中的代码,并完成 readelf.c 中缺少的代码,readelf 函数需要输出 elf 文件的所有 sectionheader 的序号和地址信息,对每个 section header,输出格式为:”%d:0x%x\n”,两个标识符分别代表序号和地址。

打开readelf.c   直接复制我的代码 覆盖即可

/* 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)
{
        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.
 */
int readelf(u_char *binary, int size)
{
        Elf32_Ehdr *ehdr = (Elf32_Ehdr *)binary;

        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.
        shdr=(Elf32_Shdr *)(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){
            printf("%d:0x%x\n",Nr,shdr -> sh_addr);
            shdr++;
        }
        return 0;
}

Exercise 1.3

填写 tools/scse0 3.lds 中空缺的部分,将内核调整到正确的位置上。

OUTPUT_ARCH(mips)
/*
Set the architecture to mips.
*/
ENTRY(_start)
/*
Set the ENTRY point of the program to _start.
*/
SECTIONS
{

/*To do:
  fill in the correct address of the key section
  such as text data bss ...
*/
    . = 0x80010000;
    .text : {
        *(.text)               /* 包含所有 .text 段的数据 */
    }   
    .data : {
        *(.data)               /* 包含所有 .data 段的数据 */
    }
    .bss : {
        *(.bss)                /* 包含所有 .bss 段的数据 */
    }
end = . ;
}

Exercise 1.4

完成 boot/start.S 中空缺的部分。设置栈指针,跳转到 main 函数。使用/OSLAB/gxemul -E testmips -C R3000 -M 64 elf-fle 运行(其中 elf-fle 是你编译生成的 vmlinux 文件的路径)。 

#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
	
/*To do: 
  set up stack 
  you can reference the memory layout in the include/mmu.h
*/
li sp,0x80400000
jal main
nop			
loop:
	j	loop
	nop
END(_start)            /*the function defined in asm.h*/

 Exercise 1.5

阅读相关代码和下面对于函数规格的说明,补全 lib/print.c中Ip_Print()函数中缺失的部分来实现字符输出。

/*
 * 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	<print.h>

/* macros */
#define		IsDigit(x)	( ((x) >= '0') && ((x) <= '9') )
#define		Ctod(x)		( (x) - '0')

/* forward declaration */
extern int PrintChar(char *, char, int, int);
extern int PrintString(char *, char *, int, int);
extern int PrintNum(char *, unsigned long, int, int, int, int, char, int);

/* private variable */
static const char theFatalMsg[] = "fatal error in lp_Print!";

/* -*-
 * A low level printf() function.
 */
void
lp_Print(void (*output)(void *, char *, int), 
	 void * arg,
	 char *fmt, 
	 va_list ap)
{

#define 	OUTPUT(arg, s, l)  \
  { if (((l) < 0) || ((l) > LP_MAX_BUF)) { \
       (*output)(arg, (char*)theFatalMsg, sizeof(theFatalMsg)-1); for(;;); \
    } else { \
      (*output)(arg, s, l); \
    } \
  }
    
    char buf[LP_MAX_BUF];

    char c;
    char *s;
    long int num;

    int longFlag;
    int negFlag;
    int width;
    int prec;
    int ladjust;
    char padc;

    int length;
for(;;) {
        {
	    /* scan for the next '%' */
        while((*fmt)!='\0'&&(*fmt)!='%')
        {
			OUTPUT(arg,fmt,1);
        	fmt ++;
        }
	    /* flush the string found so far */

	    /* are we hitting the end? */
        if((*fmt)=='\0')
            break;
	}

	/* we found a '%' */
	fmt++;
	/* check for long */

	/* check for other prefixes */

	/* check format flag */
    if(*fmt=='-'){
    longFlag=1;
        fmt++;
    }
    else if(*fmt=='0')
	{
		padc='0';
		fmt ++;
	}
    for(;IsDigit(*fmt);fmt ++)width =width*10+Ctod(*fmt);
    if(*fmt=='.'){
        fmt ++;
        for(;IsDigit(*fmt);fmt++)
            prec=prec*10+Ctod(*fmt);
    }
    if(*fmt=='1'){
        longFlag=1;
            fmt++;
    }
        longFlag=0;
        width=0;
        ladjust=0;
        prec=0;
        padc=' ';
	negFlag = 0;
	switch (*fmt) {
	 case 'b':
	    if (longFlag) { 
		num = va_arg(ap, long int); 
	    } else { 
		num = va_arg(ap, int);
	    }
	    length = PrintNum(buf, num, 2, 0, width, ladjust, padc, 0);
	    OUTPUT(arg, buf, length);
	    break;

	 case 'd':
	 case 'D':
	    if (longFlag) { 
		num = va_arg(ap, long int);
	    } else { 
		num = va_arg(ap, int); 
	    }
	    if (num < 0) {
		num = - num;
		negFlag = 1;
	    }
	    length = PrintNum(buf, num, 10, negFlag, width, ladjust, padc, 0);
	    OUTPUT(arg, buf, length);
	    break;

	 case 'o':
	 case 'O':
	    if (longFlag) { 
		num = va_arg(ap, long int);
	    } else { 
		num = va_arg(ap, int); 
	    }
	    length = PrintNum(buf, num, 8, 0, width, ladjust, padc, 0);
	    OUTPUT(arg, buf, length);
	    break;

	 case 'u':
	 case 'U':
	    if (longFlag) { 
		num = va_arg(ap, long int);
	    } else { 
		num = va_arg(ap, int); 
	    }
	    length = PrintNum(buf, num, 10, 0, width, ladjust, padc, 0);
	    OUTPUT(arg, buf, length);
	    break;
	    
	 case 'x':
	    if (longFlag) { 
		num = va_arg(ap, long int);
	    } else { 
		num = va_arg(ap, int); 
	    }
	    length = PrintNum(buf, num, 16, 0, width, ladjust, padc, 0);
	    OUTPUT(arg, buf, length);
	    break;

	 case 'X':
	    if (longFlag) { 
		num = va_arg(ap, long int);
	    } else { 
		num = va_arg(ap, int); 
	    }
	    length = PrintNum(buf, num, 16, 0, width, ladjust, padc, 1);
	    OUTPUT(arg, buf, length);
	    break;

	 case 'c':
	    c = (char)va_arg(ap, int);
	    length = PrintChar(buf, c, width, ladjust);
	    OUTPUT(arg, buf, length);
	    break;

	 case 's':
	    s = (char*)va_arg(ap, char *);
	    length = PrintString(buf, s, width, ladjust);
	    OUTPUT(arg, buf, length);
	    break;

	 case '\0':
	    fmt --;
	    break;

	 default:
	    /* output this char as it is */
	    OUTPUT(arg, fmt, 1);
        break;
	}	/* switch (*fmt) */

	fmt ++;
    }		/* for(;;) */

    /* special termination call */
    OUTPUT(arg, "\0", 1);
}


/* --------------- local help functions --------------------- */
int
PrintChar(char * buf, char c, int length, int ladjust)
{
    int i;
    
    if (length < 1) length = 1;
    if (ladjust) {
	*buf = c;
	for (i=1; i< length; i++) buf[i] = ' ';
    } else {
	for (i=0; i< length-1; i++) buf[i] = ' ';
	buf[length - 1] = c;
    }
    return length;
}

int
PrintString(char * buf, char* s, int length, int ladjust)
{
    int i;
    int len=0;
    char* s1 = s;
    while (*s1++) len++;
    if (length < len) length = len;

    if (ladjust) {
	for (i=0; i< len; i++) buf[i] = s[i];
	for (i=len; i< length; i++) buf[i] = ' ';
    } else {
	for (i=0; i< length-len; i++) buf[i] = ' ';
	for (i=length-len; i < length; i++) buf[i] = s[i-length+len];
    }
    return length;
}

int
PrintNum(char * buf, unsigned long u, int base, int negFlag, 
	 int length, int ladjust, char padc, int upcase)
{
    /* algorithm :
     *  1. prints the number from left to right in reverse form.
     *  2. fill the remaining spaces with padc if length is longer than
     *     the actual length
     *     TRICKY : if left adjusted, no "0" padding.
     *		    if negtive, insert  "0" padding between "0" and number.
     *  3. if (!ladjust) we reverse the whole string including paddings
     *  4. otherwise we only reverse the actual string representing the num.
     */

    int actualLength =0;
    char *p = buf;
    int i;

    do {
	int tmp = u %base;
	if (tmp <= 9) {
	    *p++ = '0' + tmp;
	} else if (upcase) {
	    *p++ = 'A' + tmp - 10;
	} else {
	    *p++ = 'a' + tmp - 10;
	}
	u /= base;
    } while (u != 0);

    if (negFlag) {
	*p++ = '-';
    }

    /* figure out actual length and adjust the maximum length */
    actualLength = p - buf;
    if (length < actualLength) length = actualLength;

    /* add padding */
    if (ladjust) {
	padc = ' ';
    }
    if (negFlag && !ladjust && (padc == '0')) {
	for (i = actualLength-1; i< length-1; i++) buf[i] = padc;
	buf[length -1] = '-';
    } else {
	for (i = actualLength; i< length; i++) buf[i] = padc;
    }
	    

    /* prepare to reverse the string */
    {
	int begin = 0;
	int end;
	if (ladjust) {
	    end = actualLength - 1;
	} else {
	    end = length -1;
	}

	while (end > begin) {
	    char tmp = buf[begin];
	    buf[begin] = buf[end];
	    buf[end] = tmp;
	    begin ++;
	    end --;
	}
    }

    /* adjust the string pointer */
    return length;
}

 在 20221105894-lab目录下 输入

make
gxemul -E testmips -C R3000 -M 64 gxemul/vmlinux

 输出这个即可

最后记得提交

git add .
git commit -m '这个谁便写,不一定写我的作业写完了'
git push

最后出现这个就可以

 

Think

Thinking 1.1

也许你会发现我们的 readelf 程序是不能解析之前生成的内核文件(内核文件是可执行文件)的,而我们之后将要介绍的工具 readelf 则可以解析,这是为什么呢?(提示:尝试使用 readelf -h,观察不同)

答:

两者对 ELF 文件格式的处理方式或支持程度不同。通过使用 readelf -h 命令查看文件头信息,可能会发现内核文件的某些属性或格式与我们编写的 readelf 程序预期不匹配。例如,内核文件可能采用了特殊的 ELF 文件格式版本、包含了特定的节(section)或段(segment),而我们的程序没有正确处理这些情况。系统自带的 readelf 工具则具备更全面的兼容性和对各种 ELF 文件变体的正确解析能力。

Thinking 1.2

main 函数在什么地方?我们又是怎么跨文件调用函数的呢?

答:

  • main 函数位置:在 C 语言程序中,main 函数是程序的入口点。在操作系统启动过程中,经过一系列的初始化操作后,最终会跳转到 main 函数开始执行用户空间的程序逻辑。具体来说,在文档所涉及的实验环境中,通过 bootloader 的工作(如在 gxemul 仿真器中,其启动流程简化后会加载内核到内存并跳转执行),最终会将控制权交给内核中的 main 函数(通常位于 init.c 或类似文件中)。
  • 跨文件调用函数:在 C 语言中跨文件调用函数是通过函数声明和链接来实现的。在一个文件中调用另一个文件中的函数时,需要在调用文件中包含被调用函数的声明(可以通过头文件引入声明),以便编译器知道函数的参数和返回值类型等信息。在链接阶段,连接器会将各个目标文件(.o 文件)中定义的函数和变量进行链接,解析函数调用关系,将调用指令与函数的实际实现地址关联起来。例如,如果在一个文件中调用了另一个文件中定义的函数foo,在编译时会生成对foo函数的调用指令,链接器会在链接过程中找到foo函数的定义所在的目标文件,并将调用指令中的地址修正为foo函数的实际入口地址,从而实现跨文件的函数调用。同时,对于函数调用过程中参数的传递和返回值的获取,遵循一定的规则,如在 MIPS 体系结构中,参数通常通过寄存器(a0 - a3 等)传递,返回值通过寄存器(v0 - v1 等)返回,并且在函数调用前后,编译器会根据函数调用约定进行寄存器值的保存和恢复等操作,以确保函数调用的正确性和程序状态的一致性。

标签:int,fmt,arg,Boot,length,lab1,num,点赞,buf
From: https://blog.csdn.net/m0_74172897/article/details/143845361

相关文章

  • 网络管理 主机资源监控系统项目搭建 (保姆级教程 建议点赞 收藏)
    目录教大家实现图里面的2个仪表盘其他自行根据2个仪表盘的模式进行补充 一.首先准备实验环境需要环境python3.8.8下载完成之后开始配置虚拟环境二.开始编写代码先新建前提文件好了前提工作好了接下来就是写代码了  先写python代码 然后是main.html的代码......
  • springboot家居商城-计算机毕业设计源码02059
    摘要随着互联网技术的不断发展和家居行业的数字化转型,建立一套高效的家居商城系统成为家居企业提升竞争力和满足消费者需求的重要举措。本研究旨在设计并实现了一套功能丰富的家居商城系统。通过对系统功能模块的详细分析和设计,包括普通用户和管理员的功能需求,系统实现了用户......
  • springboot列星药膳管理系统-计算机毕业设计源码05345
     摘 要身处互联网+时代,互联网无形中影响着人们的吃穿住行,人们享受着不出门便可购物的便利,网络购物在当今社会工作生活节奏飞快的今天备受欢迎,让人们购物不再受时间、地点的制约,高效快速。药膳作为一种传统的中医养生方式受到了广泛关注。然而,传统的药膳管理方式存在一些问......
  • 基于SpringBoot + Vue + Uniapp框架的旧衣回收小程序(源码+数据库+文档+部署讲解等)
    文章目录1.前言2.详细视频演示3.文档参考3.1论文参考3.2流程设计图3.3数据库表结构设计3.4系统测试部分4.项目运行截图5.技术框架5.1后端采用SpringBoot框架5.2前端框架Vue6.选题推荐毕设案例8.系统测试8.1系统测试的目的8.2系统功能测试9.代码参考10......
  • springboot毕设 黔西南旅游景点信息交流平台 程序+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景黔西南地区,以其独特的自然风光、丰富的民族文化、悠久的历史遗迹而闻名遐迩,吸引着越来越多的国内外游客前来探索与体验。然而,随着旅游业的快速发展,信......
  • springboot毕设 青岛黄海学院听课督导系统 程序+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在高等教育日益重视教学质量与教学管理的背景下,青岛黄海学院作为一所充满活力与创新精神的高校,始终致力于提升教学质量与学生满意度。然而,传统的听课......
  • springboot毕设 企业OA系统程序+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着信息技术的飞速发展和企业管理的日益精细化,企业办公自动化(OA)系统已成为提升工作效率、优化管理流程的重要手段。在全球化竞争日益激烈的今天,企业......
  • springboot毕设 配音爱好者交流网站 程序+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着互联网技术的飞速发展,人们对于娱乐和兴趣爱好的追求方式也在不断变化。配音作为一种独特的艺术形式,近年来受到了越来越多爱好者的追捧。从动画、......
  • 基于Java+SpringBoot+Mysql在线简单拍卖竞价拍卖竞拍系统功能设计与实现三
    一、前言介绍:免费学习:猿来入此1.1项目摘要主要源于互联网技术的快速发展和电子商务的普及。随着网络技术的不断进步,人们越来越依赖于互联网进行购物、交易和沟通。电子商务的兴起为在线拍卖提供了广阔的市场和便利的条件。在线拍卖系统通过搭建一个虚拟的拍卖平台,将传统的拍卖......
  • 基于Java+SpringBoot+Mysql在线简单拍卖竞价拍卖竞拍系统功能设计与实现四
    一、前言介绍:免费学习:猿来入此1.1项目摘要主要源于互联网技术的快速发展和电子商务的普及。随着网络技术的不断进步,人们越来越依赖于互联网进行购物、交易和沟通。电子商务的兴起为在线拍卖提供了广阔的市场和便利的条件。在线拍卖系统通过搭建一个虚拟的拍卖平台,将传统的拍卖......