操作系统实验报告_ucore_lab1

在大三上学期接触了操作系统这门课程,经过了小半个学期的理论讲解,我对操作系统的局部有了粗浅的了解。

紧接而来的这个实验是清华大学的操作系统实验课程,有一定难度,需要去学习很多课本中没有详谈的问题,需要通过自己的一次次尝试去了解操作系统开发实验环境,进一步熟悉命令行方式的编译、调试工程,同时还要求我们熟悉C语言编程和指针的概念。

本篇报告,详细地记录了我的整个实验过程,包括遇到新概念和软件后的学习笔记,也会记录在本文中,可以完整地反映我的学习过程。

实验环境配置

本次实验需要在LINUX环境下完成。
实验指导书中给出了多个配置环境的方法,我在这里选择了VMware中的Ubuntu 64位 虚拟机,版本为16.04.5

Ubuntu是图形界面友好和易操作的linux发行版,有时只需执行几条简单的指令就可以完成繁琐的鼠标点击才能完成的操作,拥有强大的命令行操作。

编程开发调试的基本工具

1. gcc

  • 安装gcc
$ sudo apt-get install gcc

安装完毕后是4:5.3.1-1ubuntu1的版本

更新:
sudo apt-get update
卸载:
sudo apt-get install <软件名>-

  • 用gcc编译文件
$ gcc -Wall <代码文件名称> -o <可执行文件名称>
  • 执行文件
$ ./<可执行文件名称>

实验一 系统软件启动过程

BIOS

Basic Input Output System,即基本输入/输出系统,实际上是一个被固化在计算机ROM(只读存储器)芯片上的特殊的软件,为上层软件提供最底层的、最直接的硬件控制与支持。
BIOS做完计算机硬件自检和初始化后,会选择一个启动设备,并且读取该设备的第一扇区,到内存一个特定的地址处,进一步的工作交给了ucore的bootloader。

BootLoader

在嵌入式操作系统中,BootLoader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。

我通过网络上的资料对Bootloader这个软件进行了大概的了解,我把它理解为一个【导入管】。
计算机从开机到操作系统进入一个类似while(1)的永不退出的循环中,需要一个导入过程——在加电后执行第一段代码,在它完成CPU和相关硬件的初始化之后,再将操作系统映像或固化的嵌入式应用程序装在到内存中,然后跳转到操作系统所在的空间,启动操作系统运行。

实验中给的bootloader可以切换到X86保护模式,能够读磁盘并加载ELF执行文件格式。

我们再来看看实验指导书中对Bootloader这个软件的学习要求:

  • List item
  • 编译运行bootloader的过程
  • 调试bootloader的方法
  • PC启动bootloader的过程
  • ELF执行文件的格式和加载
  • 外设访问:读硬盘,在CGA上显示字符串

以下是头文件<asm.h>中的内容:

#ifndef __BOOT_ASM_H__

#ifndef 的意思是:如果_BOOT_ASM_H_这个标识未被定义,则编译下面的程序段。

接下来就是正式定义<asm.h>的内容,大致的操作含义是利用汇编宏(Assembler macros)定义了X86下的常规和应用程序的段类型,例如 STA_X为可执行段(Executable segment),STA_C为代码段(Conforming code segment)。

#define __BOOT_ASM_H__

/* Assembler macros to create x86 segments */

/* Normal segment */
#define SEG_NULLASM                                             \
    .word 0, 0;                                                 \
    .byte 0, 0, 0, 0
#define SEG_ASM(type,base,lim)                                  \
    .word (((lim) >> 12) & 0xffff), ((base) & 0xffff);          \
    .byte (((base) >> 16) & 0xff), (0x90 | (type)),             \
        (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff)


/* Application segment type bits */
#define STA_X       0x8     // Executable segment
#define STA_E       0x4     // Expand down (non-executable segments)
#define STA_C       0x4     // Conforming code segment (executable only)
#define STA_W       0x2     // Writeable (non-executable segments)
#define STA_R       0x2     // Readable (executable segments)
#define STA_A       0x1     // Accessed

#endif /* !__BOOT_ASM_H__ */

接下来是bootasm.S中的内容,大致的操作是:

  • 启动CPU:切换到32位保护模式,并跳转到C;
  • BIOS将这段代码从硬盘的第一个扇区,装入到物理地址为0x7c00处的内存,并以实模式开始执行。

实模式 是早期CPU运行的工作模式,这个模式下段基地址必须是16的整数倍。

保护模式 则是现代CPU运行的模式,可以实现更大空间的,更灵活的内存访问。
现代CPU在运行boot loader时仍旧要先进入实模式,是为了实现软件的向后兼容性。

#include <asm.h>

# Start the CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00.

.set PROT_MODE_CSEG,        0x8                     # kernel code segment selector
.set PROT_MODE_DSEG,        0x10                    # kernel data segment selector
.set CR0_PE_ON,             0x1                     # protected mode enable flag

# start address should be 0:7c00, in real mode, the beginning address of the running bootloader
.globl start
start:
.code16                                             # Assemble for 16-bit mode
    cli                                             # Disable interrupts
    cld                                             # String operations increment

    # Set up the important data segment registers (DS, ES, SS).
    xorw %ax, %ax                                   # Segment number zero
    movw %ax, %ds                                   # -> Data Segment
    movw %ax, %es                                   # -> Extra Segment
    movw %ax, %ss                                   # -> Stack Segment

    # Enable A20:
    #  For backwards compatibility with the earliest PCs, physical
    #  address line 20 is tied low, so that addresses higher than
    #  1MB wrap around to zero by default. This code undoes this.
seta20.1:
    inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).
    testb $0x2, %al
    jnz seta20.1

    movb $0xd1, %al                                 # 0xd1 -> port 0x64
    outb %al, $0x64                                 # 0xd1 means: write data to 8042's P2 port

seta20.2:
    inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).
    testb $0x2, %al
    jnz seta20.2

    movb $0xdf, %al                                 # 0xdf -> port 0x60
    outb %al, $0x60                                 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1

    # Switch from real to protected mode, using a bootstrap GDT
    # and segment translation that makes virtual addresses
    # identical to physical addresses, so that the
    # effective memory map does not change during the switch.
    lgdt gdtdesc
    movl %cr0, %eax
    orl $CR0_PE_ON, %eax
    movl %eax, %cr0

    # Jump to next instruction, but in 32-bit code segment.
    # Switches processor into 32-bit mode.
    ljmp $PROT_MODE_CSEG, $protcseg

.code32                                             # Assemble for 32-bit mode
protcseg:
    # Set up the protected-mode data segment registers
    movw $PROT_MODE_DSEG, %ax                       # Our data segment selector
    movw %ax, %ds                                   # -> DS: Data Segment
    movw %ax, %es                                   # -> ES: Extra Segment
    movw %ax, %fs                                   # -> FS
    movw %ax, %gs                                   # -> GS
    movw %ax, %ss                                   # -> SS: Stack Segment

    # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00)
    movl $0x0, %ebp
    movl $start, %esp
    call bootmain

    # If bootmain returns (it shouldn't), loop.
spin:
    jmp spin

# Bootstrap GDT
.p2align 2                                          # force 4 byte alignment
gdt:
    SEG_NULLASM                                     # null seg
    SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff)           # code seg for bootloader and kernel
    SEG_ASM(STA_W, 0x0, 0xffffffff)                 # data seg for bootloader and kernel

gdtdesc:
    .word 0x17                                      # sizeof(gdt) - 1
    .long gdt                                       # address gdt

练习1:理解通过make生成执行文件的过程。

Makefile 是一种常用于编译的脚本语言。它可以更好更方便的管理你的项目的代码编译,节约编译时间

使用GCC的命令进行程序编译时,当程序是单个文件时编译是比较方便的,但当工程中的文件数目增多,甚至非常庞大,并且目录结构关系复杂时,便需要通过makefile来进行程序的编译。

1.1 操作系统镜像文件ucore.img是如何一步一步生成的?(需要比较详细地解释Makefile中每一条相关命令和命令参数的含义,以及说明命令导致的结果)

我们先在Makefile文件中,找到ucore.img的部分

# create ucore.img
UCOREIMG        := $(call totarget,ucore.img)

$(UCOREIMG): $(kernel) $(bootblock)
        $(V)dd if=/dev/zero of=$@ count=10000
        $(V)dd if=$(bootblock) of=$@ conv=notrunc
        $(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc

$(call create_target,ucore.img)

从第3行开始,代码表明了:
生成ucore需要kernelbootblock文件;
将/dev/zero复制进一个新创建的地址为10000的块中去;
bootblock复制到同一个位置(不截短输出文件 );
kernel拷贝到同一位置(从输出文件开头跳过1个块后再开始拷贝 )

接下来,再看看kernelbootblock的部分:

# create kernel target
kernel = $(call totarget,kernel)

$(kernel): tools/kernel.ld

$(kernel): $(KOBJS)
        @echo + ld $@
        $(V)$(LD) $(LDFLAGS) -T tools/kernel.ld -o $@ $(KOBJS)
        @$(OBJDUMP) -S $@ > $(call asmfile,kernel)
        @$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel)

$(call create_target,kernel)

# create bootblock
bootfiles = $(call listf_cc,boot)
$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc))

bootblock = $(call totarget,bootblock)

$(bootblock): $(call toobj,$(bootfiles)) | $(call totarget,sign)
        @echo + ld $@
        $(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 $^ -o $(call toobj,bootblock)
        @$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock)
        @$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock
  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实验一 Linux系统的安装及用户界面的使用 一.实验目的 1. 了解Linux系统的安装、熟悉系统的启动过程和使用环境。 2. 掌握Linux环境下vi编辑器的使用方法。 3. 掌握Linux系统中编辑、编译、调试、运行一个C语言程序的全过程。 二.实验内容 1、实验要求 1. 在VMWare虚拟机环境或真实物理机器上,安装一个Linux操作系统。 2. 体验Linux操作系统中X Windows系统的使用。 3. 尝试Linux系统键盘命令的使用,并熟练掌握常用的基本命令。 4. 掌握命令行方式下vi编辑器的使用。 5. 编写一段C程序,使用系统调用fork()创建两个子进程。各进程显示不同的信息,如父进程显示字符“a”,子进程分别显示字符“b”和“c”。多次运行观察显示结果,并分析产生这种执行效果的原因。 实验二 Linux进程控制 一.实验目的 1. 掌握进程的概念,明确进程和程序的区别。 2. 认识和了解并发执行的实质。 二.实验内容 1、实验要求 1. 编写一段程序,使用系统调用fork()创建两个子进程。各进程显示不同的信息,如父进程显示字符“a”,子进程分别显示字符“b”和“c”。多次运行观察显示结果,并分析产生这种执行效果的原因。 2. 修改上面编写的程序,将每个进程的输出由单个字符改为循环输出一句话,如父进程显示:“parent:”加上进程ID,子进程分别显示:“Child1:”(或“Child2:”)加上自己的进程ID。再观察程序执行时屏幕上出现的现象,并分析原因。 3.一个父进程创建一个子进程,子进程通过exec系统调用执行另一个文件。各自的代码中显示不同的信息,观察其运行结果,分析两个进程并发执行的效果。 4. 编写程序创建如图所示的进程树,在每个进程中显示当前进程ID和父进程ID。 实验三 Linux进程间通信 一.实验目的 (1) 分析进程争用临界资源的现象,学习解决进程互斥的方法; (2) 学习如何利用进程的“软中断”、管道机制进行进程间的通信,并加深对上述通信机制的理解; (3) 了解系统调用pipe( )、msgget( )、msgsnd( )、msgrcv( )、msgctl( )、shmget( )、shmat( )、shmdt( )、shmctl( )的功能和实现过程,利用共享存储区机制进行进程间通信。 二、实验内容 1、实验要求 (1) 进程的控制 修改已编制的程序,将每个进程输出一个字符修改为每个进程输出一句话,再观察程序执行时屏幕上出现的现象,并分析出现问题的原因,进一步理解各个进程争夺临界资源的情况。 如果在程序中使用系统调用locking( )来给每一个进程加锁,可以实现进程之间的互斥,试观察并分析出现的现象。 (2) 进程的软中断通讯 编制一段程序,实现进程的软中断通讯:使用系统调用fork( )创建两个子进程;再使用系统调用signal( )让父进程捕捉键盘上来的中断信号(即按Del键);在捕捉到中断信号后,父进程用系统调用kill( )向两个子进程发信号;子进程捕捉到信号后分别输出下列信息后终止: Child process1 is killed by parent! Child process2 is killed by parent! 父进程等待两个子进程都终止以后,输出如下信息后终止: Parent process in killed! (3) 进程的管道通讯 编制一段程序,实现进程的管道通讯:使用系统调用pipe( )建立一条管道线;两个子进程分别循环向这条管道写一句话: Child 1 is sending a message! Child 2 is sending a message! 而父进程则循环从管道中读出信息,显示在屏幕上。 实验报告 内含源代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值