<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">\documentclass[a4paper]{ctexart}
\input{mypreamble}
\renewcommand{\mylabname}{系统软件启动过程}
\renewcommand{\mydate}{2024年3月26日}

\begin{document}
    \mytitle
    \begin{enumerate}
        \myitem{完成相关实验内容后，回答以下问题：}{
            \questionandanswer[]{
                为何要开启A20？ uCore OS是如何开启A20的？
            }{
                如果不开启A20会导致地址线第21位永远为0，无法完整进行内存寻址。uCore OS通过向8042键盘控制器的命令端口和数据端口发送命令和数据来开启A20。
            }
            \questionandanswer[]{
                试分析段描述符表（GDT ）的结构，说明段描述符中每个字段的含义以及作用。uCore OS是如何初始化GDT表的？
            }{
                % 段选择子的每个元素为16位，前13位是INDEX，表示这个段选择子在GDT数组或LDT数组的索引号；第14位是Table Indicator，这个值为0表示查找GDT，1则查找LDT；最后两位是Request Privilege Level，表示以什么样的权限去访问段。
                \begin{center}
                    \includegraphics[width=1\linewidth]{imgs/2024-03-26-21-17-25.png}
                \end{center}
                uCore OS初始化了一个空的表项、一个内核代码段、一个内核数据段。
            }
            \questionandanswer[]{
                实模式和保护模式有何不同？uCore OS是如何使能和进入保护模式的？
            }{
                实模式的寻址范围不超过1M，并且没有分段机制等，而保护模式的寻址范围则没有1M的限制，并且可以实现分段机制等。uCore OS通过把控制寄存器的0位设置成1来使能和进入保护模式。
            }
            \questionandanswer[]{
                Bootloader是如何利用ELF文件头的相关属性加载和运行uCore OS Kernel的？
            }{
                首先判断文件头的e\_magic是否等于ELF\_MAGIC，之后根据e\_phoff找到程序头表的位置，在程序头表中找到分段数和各个分段的偏移位置、大小，接着把各个分段加载到内存中，最后跳转到e\_entry入口开始执行。
            }
            \questionandanswer[]{
                分析中断描述符表（IDT）的结构，说明中断描述符中每个字段的含义以及作用。 uCore OS是如何实现中断机制的？
            }{
                中断描述符表中的每个表项称为中断描述符，它可以确定相应的中断处理程序的位置，中断描述符的结构和每个字段的含义以及作用如下：
                \begin{center}
                    \includegraphics[width=1\linewidth]{imgs/IDT1.png}
                    \includegraphics[width=1\linewidth]{imgs/IDT2.png}
                \end{center}
                它确定了段选择子、段内偏移、该段是否已调入内存、该中断的特权级、中断类型（中断门或者陷阱门）等。

                uCore OS将每个中断门都初始化为带中断号参数的调用（最后调用trap.c中的内容），之后把系统调用的中断门的特权级设置为用户态，最后加载中断描述符表寄存器。
            }
        }
        \myitem{程序设计与实现的基本思路}{
            \item 大部分实现的功能在trap.c文件中，首先从实验视频中可以得知，在触发中断后会进入trap.c的执行流程，之后根据不同的中断号执行不同的流程，由于题目要求通过键盘中断实现计时器，因此主要关注键盘中断和时钟中断；
            \item 根据题意，在键盘按下S时开始，P暂停，E停止，C继续，A正计时，B倒计时。那么首先考虑键盘中断，这里很明显很适合用switch语句，根据键盘中断后得到的字符不同，执行不同的流程；
            \item 这里还需要注意整个功能是存在不同的状态的（其实是因为单线程才会有这么多状态，如果多线程都不需要考虑状态，事件驱动就行），首先很明显可以知道有正在计时状态和停止状态。然后我们考虑这两个状态会在什么时候互相转化，从停止状态转到开始状态可以是按下S或者C，从开始状态转到停止状态可以是按下P或E，或者是倒计时到0了。而且题目需要实现正计时和倒计时，这是两种不同的模式，因此还需要一个状态变量记录当前的模式，通过按A和B切换；
            \item 还要注意，当按了B后进入倒计时，此时要输入时间，但是这时候还没有gets等函数的实现，需要自己通过键盘中断进行输入，那么在键盘中断的时候就不仅需要捕获字母，也需要捕获数字，但我们不希望在输入数字的时候不小心输入了字母就打断了数字输入，这时候就需要增加状态了，需要一个状态表示正在输入数字，在按下B后切换到这个状态，当按下Enter后切换到计时状态或停止状态；
            \item 当倒计时到0的时候，应该切换到停止状态，并且还需要输出一条信息表示当前计时到0了，但是这里就要注意，从计时状态切换到停止状态时不应该输出这条信息，因此这里需要多加一个中间状态，用来表示倒计时结束的准备输出，这个状态在下一次时钟中断（可以类比数字逻辑电路中，同步时序电路，在时钟有效边沿到来时）转换到停止状态，并且输出这条信息；
            \item 关于检测频率，原先的计时器在每次时钟中断时执行ticks++，而时钟中断频率在clock.c文件中设置的是时钟频率除以100，也就是每0.01秒触发一次，尝试过改成除以1000，但会导致走时变慢，应该是因为触发中断太频繁了时间开销太大，也尝试过改成除以10，又会导致走时过快，看来还是原始的除以100最准时；
            \item 既然每0.01秒触发一次中断，那么如果是每TICK\_NUM次中断打印一次时间，直观感受就是精度为 TICK\_NUM * 0.01 秒，按照题目要求那么TICK\_NUM应该取10，当然为了提升精度也可以取更小，比如1；
            \item 当键入退格时字符为$\backslash$b，用于在输入数字时进行退格；当键入回车时字符为$\backslash$n，用于确定倒计时的时间；循环打印时间时需要使用$\backslash$r，表示将光标回到当前行的开头，这样再打印时间就会把之前的时间覆盖掉；
            \item 当然还存在一些bug，比如从10.000倒计时到9.990时就会无法覆盖最后一个0，导致看起来是9.9900；还有时间精度还是不准的问题；欢迎给出修复建议（可以通过Issue的方式，下面会给出网址）。
        }
        \myitem{代码}{
            \item \url{https://gitea.shuishan.net.cn/10213903403/os_kernel_lab}
            \item 也可以看上传的附件。
        }
        注：\mycircle{1} 要求实验报告以及代码以附件形式提交。
            \mycircle{2} 实验报告提交的截止期为4月1日。
    \end{enumerate}
\end{document}</pre></body></html>