操作系统实验 订阅
《操作系统实验》是2004年中央电大出版社出版的图书,作者是孟庆昌。 展开全文
《操作系统实验》是2004年中央电大出版社出版的图书,作者是孟庆昌。
信息
页    数
93
作    者
孟庆昌
定    价
12.00元
书    名
操作系统实验
出版时间
2004-7
出版社
中央电大
副标题
操作系统实验
ISBN
9787304018801
接单内容简介
内容介绍《计算机应用专业系列教材·操作系统实验》以SCO UNIX为例介绍了操作系统的安装和使用,通过指导读者上机实验,使读者既加深对操作系统概念的理解,又学会对UNIX系统的一般使用。全书共分九章,内容包括:UNIX系统安装,SCO UNIX图形界面,用户帐户建立,vi使用,进程管理,存储器管理,文件系统,设备管理和简单shell编程。《计算机应用专业系列教材·操作系统实验》是中央电大的实验教材,同时也可作为广大UNIX初学者的指导书。 [1] 
收起全文
精华内容
下载资源
问答
  • 操作系统实验

    热门讨论 2008-06-17 15:56:04
    C++Builder操作系统实验,内含文档,操作系统实验,进程调度,主存分配与回收,文件管理,作业调度
  • 操作系统实验c语言

    千次阅读 2019-12-07 20:38:23
    这个学期学了操作系统,实验课是用c语言实现几个操作系统比较核心的算法,其实也只是模拟一下,照真实的操作系统所运行的程序,还差得太远,虽然很想接触硬件,接触底层,用汇编等...操作系统实验二(银行家算法)...

    这个学期学了操作系统,实验课是用c语言实现几个操作系统比较核心的算法,其实也只是模拟一下,照真实的操作系统所运行的程序,还差得太远,虽然很想接触硬件,接触底层,用汇编等实现一下操作系统,但要真正实现一个操作系统,需要付出的太多,也怕耽误了学业,有兴趣的可以去看看《30天自制操作系统》。
    下面是我整理的用c语言实现的操作系统用到的几个算法
    操作系统实验一(进程调度算法)
    操作系统实验二(银行家算法)
    操作系统实验三(动态分区分配算法)
    操作系统实验四(页面置换算法)

    展开全文
  • 基于XV6操作系统实验平台建设和哈工大操作系统实验 的搭建实验环境参加实验平台建设的第一步就是搭建操作系统平台: 说白了就是在linux系统下搭建一个虚拟机运行xv6这个操作系统 配置环境 Ubuntu:16.04 xv6 & qemu ...

    基于XV6操作系统实验平台建设和哈工大操作系统实验 的搭建实验环境

    参加实验平台建设的第一步就是搭建操作系统平台:
    说白了就是在linux系统下搭建一个虚拟机运行xv6这个操作系统

    • 配置环境
    • Ubuntu:16.04
    • xv6 & qemu
    • 注:我同学ubuntu17貌似会出现fail

    安装linux系统,这里默认已经完成

    这里写图片描述

    • 这里使用的是root用户在操作。通过sudo -s切换到root用户。

    准备安装

    如:

    # 安装git
    apt-get install -y git
    # 安装g++
    apt-get install -y g++
    # 安装gdb
    apt-get install -y gdb
    # 安装编译工具链
    apt-get install -y build-essential
    # 下载xv6的代码
    cd /对应路径/
    git clone git://github.com/mit-pdos/xv6-public.git
    /在这里注意不要上github然后download zip,这样会丢失文件,我这就卡了一下午,一定要执行clone,会有非master版本的文件/

    这里写图片描述

    安装qemu

    # 安装qemu
    apt-get install qemu
    

    这里写图片描述

    编译xv6

    cd /到对应位置/xv6-public
    make qemu
    这里写图片描述


    生成qemu环境下运行的xv6
    成功

    有其他问题欢迎交流,联系qq:2282550468

    展开全文
  • 操作系统实验

    千次阅读 2020-05-20 21:13:48
    实验课程名称 操作系统实验 成绩 实验项目名称 进程管理与进程通信 指导老师 张艳玲 实验一 进程管理与进程通信 一、实验目的 1、掌握进程的概念,明确进程的含义。 2、认识并了解进程并发执行的实质,进程的阻塞与...

    广州大学学生实验报告
    开课学院:计算机科学与网络工程学院
    实验室:计算机软件实验室 2020 年5月20 日
    学院 计算机科学与教育软件学院 年级/专业/班 计科183 姓名 张健介 学号 1806100162
    实验课程名称 操作系统实验 成绩
    实验项目名称 进程管理与进程通信 指导老师 张艳玲

    实验一 进程管理与进程通信
    一、实验目的
    1、掌握进程的概念,明确进程的含义。
    2、认识并了解进程并发执行的实质,进程的阻塞与唤醒,终止与退出的过程。
    3、熟悉进程的睡眠、同步、撤消等进程控制方法。
    4、分析进程竞争资源的现象,学习解决进程互斥的方法 。
    5、了解什么是信号,利用信号量机制熟悉进程间软中断通信的基本原理,
    6、熟悉消息传送的机理 ,共享存储机制 。

    二、实验内容
    1、编写一段程序,使用系统调用fork( )创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程并发执行,观察实验结果并分析原因。
    2、用fork( )创建一个进程,再调用exec( ),用新的程序替换该子进程的内容,利用wait( )来控制进程执行顺序,掌握进程的睡眠、同步、撤消等进程控制方法,并根据实验结果分析原因。
    3、编写一段多进程并发运行的程序,用lockf( )来给每一个进程加锁,以实现进程之间的互斥,观察并分析出现的现象及原因。
    4、编写程序:用fork( )创建两个子进程,再用系统调用signal( )让父进程捕捉键盘上来的中断信号(即按^c键);捕捉到中断信号后,父进程用系统调用kill( )向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止:
    Child process1 is killed by parent!
    Child process2 is killed by parent!
    父进程等待两个子进程终止后,输出如下的信息后终止:
    Parent process is killed!
    分析利用信号量机制中的软中断通信实现进程同步的机理。
    5、使用系统调用msgget( ),msgsnd( ),msgrev( ),及msgctl( )编制一长度为1k的消息发送和接收的程序,并分析消息的创建、发送和接收机制及控制原理。
    6、编制一长度为1k的共享存储区发送和接收的程序,并设计对该共享存储区进行互斥访问及进程同步的措施,必须保证实现正确的通信。

    三、实验原理
    1、进程创建与进程并发执行
    Linux中,进程既是一个独立拥有资源的基本单位,又是一个独立调度的基本单位。一个进程实体由若干个区(段)组成,包括程序区、数据区、栈区、共享存储区等。每个区又分为若干页,每个进程配置有唯一的进程控制块PCB,用于控制和管理进程。
    系统为每个进程配置了一张进程区表。表中,每一项记录一个区的起始虚地址及指向系统区表中对应的区表项。核心通过查找进程区表和系统区表,便可将区的逻辑地址变换为物理地址。
    进程是进程映像的执行过程,也就是正在执行的进程实体。它由三部分组成:
    (1)用户级上、下文。主要成分是用户程序;
    (2)寄存器上、下文。由CPU中的一些寄存器的内容组成,如PC,PSW,SP及通用寄存器等;
    (3)系统级上、下文。包括OS为管理进程所用的信息,有静态和动态之分。

    进程创建所涉及的系统调用:
    fork( ) 创建一个新进程。
    系统调用格式: pid=fork( )
    参数定义:int fork( )
    fork( )返回值意义如下:
    0:在子进程中,pid变量保存的fork( )返回值为0,表示当前进程是子进程。
    大于0:在父进程中,pid变量保存的fork( )返回值为子进程的id值(进程唯一标识符)。
    -1:创建失败。
    如果fork( )调用成功,它向父进程返回子进程的PID,并向子进程返回0,即fork( )被调用了一次,但返回了两次。此时OS在内存中建立一个新进程,所建的新进程是调用fork( )父进程(parent process)的副本,称为子进程(child process)。子进程继承了父进程的许多特性,并具有与父进程完全相同的用户级上下文。父进程与子进程并发执行。
    核心为fork( )完成以下操作:
    (1)为新进程分配一进程表项和进程标识符
    进入fork( )后,核心检查系统是否有足够的资源来建立一个新进程。若资源不足,则fork( )系统调用失败;否则,核心为新进程分配一进程表项和唯一的进程标识符。
    (2)检查同时运行的进程数目
    超过预先规定的最大数目时,fork( )系统调用失败。
    (3)拷贝进程表项中的数据
    将父进程的当前目录和所有已打开的数据拷贝到子进程表项中,并置进程的状态为“创建”状态。
    (4)子进程继承父进程的所有文件
    对父进程当前目录和所有已打开的文件表项中的引用计数加1。
    (5)为子进程创建进程上、下文
    进程创建结束,设子进程状态为“内存中就绪”并返回子进程的标识符。
    (6)子进程执行
    虽然父进程与子进程程序完全相同,但每个进程都有自己的程序计数器PC(注意子进程的PC开始位置),然后根据pid变量保存的fork( )返回值的不同,执行了不同的分支语句。

    2、进程的睡眠、同步、撤消等进程控制
    用fork( )创建一个进程,再调用exec( )用新的程序替换该子进程的内容,然后利用wait( )来控制进程执行顺序。
    (1)exec( )系列
    系统调用exec( )系列,也可用于新程序的运行。fork( )只是将父进程的用户级上下文拷贝到新进程中,而exec( )系列可以将一个可执行的二进制文件覆盖在新进程的用户级上下文的存储空间上,以更改新进程的用户级上下文。exec( )系列中的系统调用都完成相同的功能,它们把一个新程序装入内存,来改变调用进程的执行代码,从而形成新进程。如果exec( )调用成功,调用进程将被覆盖,然后从新程序的入口开始执行,这样就产生了一个新进程,新进程的进程标识符id 与调用进程相同。
    exec( )没有建立一个与调用进程并发的子进程,而是用新进程取代了原来进程。所以exec( )调用成功后,没有任何数据返回,这与fork( )不同。exec( )系列系统调用在UNIX系统库unistd.h中,共有execl、execlp、execle、execv、execvp五个,其基本功能相同,只是以不同的方式来给出参数。
    一种是直接给出参数的指针,如:
    int execl(path,arg0[,arg1,…argn],0);
    char *path,*arg0,*arg1,…,*argn;
    另一种是给出指向参数表的指针,如:
    int execv(path,argv);
    char *path,*argv[ ];
    具体使用可参考有关书。
    (2)exec( )和fork( )联合使用
    系统调用exec和fork( )联合使用能为程序开发提供有力支持。用fork( )建立子进程,然后在子进程中使用exec( ),这样就实现了父进程与一个与它完全不同子进程的并发执行。
    一般,wait、exec联合使用的模型为:
    int status;

    if (fork( )= =0)
    {
    …;
    execl(…);
    …;
    }
    wait(&status);
    (3)wait( )
    等待子进程运行结束。如果子进程没有完成,父进程一直等待。wait( )将调用进程挂起,直至其子进程因暂停或终止而发来软中断信号为止。如果在wait( )前已有子进程暂停或终止,则调用进程做适当处理后便返回。
    系统调用格式:
    int wait(status) 
    int *status;
    其中,status是用户空间的地址。它的低8位反应子进程状态,为0表示子进程正常结束,非0则表示出现了各种各样的问题;高8位则带回了exit( )的返回值。exit( )返回值由系统给出。
    核心对wait( )作以下处理:
    1)首先查找调用进程是否有子进程,若无,则返回出错码;
    2)若找到一处于“僵死状态”的子进程,则将子进程的执行时间加到父进程的执行时间上,并释放子进程的进程表项;
    3)若未找到处于“僵死状态”的子进程,则调用进程便在可被中断的优先级上睡眠,等待其子进程发来软中断信号时被唤醒。
    (4)exit( )
    终止进程的执行。
    系统调用格式:
        void exit(status)
       int status;
    其中,status是返回给父进程的一个整数,以备查考。
    为了及时回收进程所占用的资源并减少父进程的干预,UNIX/LINUX利用exit( )来实现进程的自我终止,通常父进程在创建子进程时,应在进程的末尾安排一条exit( ),使子进程自我终止。exit(0)表示进程正常终止,exit(1)表示进程运行有错,异常终止。
    如果调用进程在执行exit( )时,其父进程正在等待它的终止,则父进程可立即得到其返回的整数。核心须为exit( )完成以下操作:
    1)关闭软中断
    2)回收资源
    3)写记帐信息
    4)置进程为“僵死状态”

    3、多进程通过加锁互斥并发运行
    用lockf( )来给每一个进程加锁,以实现多进程之间的互斥。
    所涉及的系统调用:lockf(files,function,size),用作锁定文件的某些段或者整个文件。
    本函数的头文件为
    #include “unistd.h”
    参数定义:
    int lockf(files,function,size)
    int files,function;
    long size;
    其中:files是文件描述符;function是锁定和解锁:1表示锁定,0表示解锁。size是锁定或解锁的字节数,为0,表示从文件的当前位置到文件尾。

    4、进程间通过信号机制实现软中断通信
    (1)信号的基本概念
    每个信号都对应一个正整数常量(称为signal number,即信号编号。定义在系统头文件<signal.h>中),代表同一用户的诸进程之间传送事先约定的信息的类型,用于通知某进程发生了某异常事件。每个进程在运行时,都要通过信号机制来检查是否有信号到达。若有,便中断正在执行的程序,转向与该信号相对应的处理程序,以完成对该事件的处理;处理结束后再返回到原来的断点继续执行。实质上,信号机制是对中断机制的一种模拟,故在早期的UNIX版本中又把它称为软中断。
    信号与中断的相似点:
    1)采用了相同的异步通信方式;
    2)当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行相应的处理程序;
    3)都在处理完毕后返回到原来的断点;
    4)对信号或中断都可进行屏蔽。
    信号与中断的区别:
    1)中断有优先级,而信号没有优先级,所有的信号都是平等的;
    2)信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行;
    (3)中断响应是及时的,而信号响应通常都有较大的时间延迟。
    信号机制具有以下三方面的功能:
    1)发送信号。发送信号的程序用系统调用kill( )实现;
    2)预置对信号的处理方式。接收信号的程序用signal( )来实现对处理方式的预置;
    3)收受信号的进程按事先的规定完成对相应事件的处理。
    (2)信号的发送
    信号的发送,是指由发送进程把信号送到指定进程的信号域的某一位上。如果目标进程正在一个可被中断的优先级上睡眠,核心便将它唤醒,发送进程就此结束。一个进程可能在其信号域中有多个位被置位,代表有多种类型的信号到达,但对于一类信号,进程却只能记住其中的某一个。
    进程用kill( )向一个进程或一组进程发送一个信号。
    (3)对信号的处理
    当一个进程要进入或退出一个低优先级睡眠状态时,或一个进程即将从核心态返回用户态时,核心都要检查该进程是否已收到软中断。当进程处于核心态时,即使收到软中断也不予理睬;只有当它返回到用户态后,才处理软中断信号。对软中断信号的处理分三种情况进行:
    1)如果进程收到的软中断是一个已决定要忽略的信号(function=1),进程不做任何处理便立即返回;
    2)进程收到软中断后便退出(function=0);
    3)执行用户设置的软中断处理程序。
    (4)所涉及的中断调用
    (1)kill( )
    系统调用格式:int kill(pid,sig)
    参数定义:int pid,sig;
    其中,pid是一个或一组进程的标识符,参数sig是要发送的软中断信号。
    1)pid>0时,核心将信号发送给进程pid。
    2)pid=0时,核心将信号发送给与发送进程同组的所有进程。
    3)pid=-1时,核心将信号发送给所有用户标识符真正等于发送进程的有效用户标识号的进程。
    (2)signal( )
    预置对信号的处理方式,允许调用进程控制软中断信号。
    系统调用格式
    signal(sig,function)
    头文件为
      #include <signal.h>
    参数定义
    signal(sig,function)
    int sig;
    void (*func) ( )
    其中sig用于指定信号的类型,sig为0则表示没有收到任何信号,余者如下表:

    值 名 字 说 明
    01 SIGHUP 挂起(hangup)
    02 SIGINT 中断,当用户从键盘按c键或break键时
    03 SIGQUIT 退出,当用户从键盘按quit键时
    04 SIGILL 非法指令
    05 SIGTRAP 跟踪陷阱(trace trap),启动进程,跟踪代码的执行
    06 SIGIOT IOT指令
    07 SIGEMT EMT指令
    08 SIGFPE 浮点运算溢出
    09 SIGKILL 杀死、终止进程
    10 SIGBUS 总线错误
    11 SIGSEGV 段违例(segmentation violation),进程试图去访问其虚地址空间以外的位置
    12 SIGSYS 系统调用中参数错,如系统调用号非法
    13 SIGPIPE 向某个非读管道中写入数据
    14 SIGALRM 闹钟。当某进程希望在某时间后接收信号时发此信号
    15 SIGTERM 软件终止(software termination)
    16 SIGUSR1 用户自定义信号1
    17 SIGUSR2 用户自定义信号2
    18 SIGCLD 某个子进程死
    19 SIGPWR 电源故障

    function:在该进程中的一个函数地址,在核心返回用户态时,它以软中断信号的序号作为参数调用该函数,对除了信号SIGKILL,SIGTRAP和SIGPWR以外的信号,核心自动地重新设置软中断信号处理程序的值为SIG_DFL,一个进程不能捕获SIGKILL信号。
    function 的解释如下:
    1)function=1时,进程对sig类信号不予理睬,亦即屏蔽了该类信号;
    2)function=0时,缺省值,进程在收到sig信号后应终止自己;
    3)function为非0,非1类整数时,function的值即作为信号处理程序的指针。

    5、消息的发送与接收
    使用系统调用msgget( ),msgsnd( ),msgrev( ),及msgctl( )编制一长度为1k的消息发送和接收的程序。
    消息(message)是一个格式化的可变长的信息单元。消息机制允许由一个进程给其它任意的进程发送一个消息。当一个进程收到多个消息时,可将它们排成一个消息队列。消息使用二种重要的数据结构:一是消息首部,其中记录了一些与消息有关的信息,如消息数据的字节数;二个消息队列头表,其每一表项是作为一个消息队列的消息头,记录了消息队列的有关信息。
    (1)消息机制的数据结构
    (1)消息首部
    记录一些与消息有关的信息,如消息的类型、大小、指向消息数据区的指针、消息队列的链接指针等。
    (2)消息队列头表
    其每一项作为一个消息队列的消息头,记录了消息队列的有关信息如指向消息队列中第一个消息和指向最后一个消息的指针、队列中消息的数目、队列中消息数据的总字节数、队列所允许消息数据的最大字节总数,还有最近一次执行发送操作的进程标识符和时间、最近一次执行接收操作的进程标识符和时间等。
    (3) 消息队列的描述符
    UNIX中,每一个消息队列都有一个称为关键字(key)的名字,是由用户指定的;消息队列有一消息队列描述符,其作用与用户文件描述符一样,也是为了方便用户和系统对消息队列的访问。
    涉及的系统调用
    (1) msgget( )
    创建一个消息,获得一个消息的描述符。核心将搜索消息队列头表,确定是否有指定名字的消息队列。若无,核心将分配一新的消息队列头,并对它进行初始化,然后给用户返回一个消息队列描述符,否则它只是检查消息队列的许可权便返回。
    系统调用格式:
    msgqid=msgget(key,flag)
    该函数使用头文件如下:
    #include<sys/types.h>
    #include<sys/ipc.h>
    #include<sys/msg.h>
    参数定义
    int msgget(key,flag)
    key_t key;
    int flag;
    其中:
    key是用户指定的消息队列的名字;flag是用户设置的标志和访问方式。如 IPC_CREAT |0400 是否该队列已被创建。无则创建,是则打开;
    IPC_EXCL |0400 是否该队列的创建应是互斥的。
    msgqid 是该系统调用返回的描述符,失败则返回-1。
    (2) msgsnd()
    发送一消息。向指定的消息队列发送一个消息,并将该消息链接到该消息队列的尾部。
    系统调用格式:
    msgsnd(msgqid,msgp,size,flag)
    该函数使用头文件如下:
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    参数定义:
    int msgsnd(msgqid,msgp,size,flag)
    I int msgqid,size,flag;
    struct msgbuf * msgp;
    其中msgqid是返回消息队列的描述符;msgp是指向用户消息缓冲区的一个结构体指针。缓冲区中包括消息类型和消息正文,即
    {
    long mtype; /消息类型/
    char mtext[ ]; /消息的文本/
    }
    size指示由msgp指向的数据结构中字符数组的长度;即消息的长度。这个数组的最大值由MSG-MAX( )系统可调用参数来确定。flag规定当核心用尽内部缓冲空间时应执行的动作:进程是等待,还是立即返回。若在标志flag中未设置IPC_NOWAIT位,则当该消息队列中的字节数超过最大值时,或系统范围的消息数超过某一最大值时,调用msgsnd进程睡眠。若是设置IPC_NOWAIT,则在此情况下,msgsnd立即返回。
    对于msgsnd( ),核心须完成以下工作:
    1)对消息队列的描述符和许可权及消息长度等进行检查。若合法才继续执行,否则返回;
    2)核心为消息分配消息数据区。将用户消息缓冲区中的消息正文,拷贝到消息数据区;
    3)分配消息首部,并将它链入消息队列的末尾。在消息首部中须填写消息类型、消息大小和指向消息数据区的指针等数据;
    4)修改消息队列头中的数据,如队列中的消息数、字节总数等。最后,唤醒等待消息的进程。
    (3) msgrcv( )
    接受一消息。从指定的消息队列中接收指定类型的消息。
    系统调用格式:
    msgrcv(msgqid,msgp,size,type,flag)
    本函数使用的头文件如下:
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    参数定义:
    int msgrcv(msgqid,msgp,size,type,flag)
    int msgqid,size,flag;
    struct msgbuf *msgp;
    long type;
    其中,msgqid,msgp,size,flag与msgsnd中的对应参数相似,type是规定要读的消息类型,flag规定倘若该队列无消息,核心应做的操作。如此时设置了IPC_NOWAIT标志,则立即返回,若在flag中设置了MS_NOERROR,且所接收的消息大于size,则核心截断所接收的消息。
    对于msgrcv系统调用,核心须完成下述工作:
    1)对消息队列的描述符和许可权等进行检查。若合法,就往下执行;否则返回;
    2)根据type的不同分成三种情况处理:
    type=0,接收该队列的第一个消息,并将它返回给调用者;
    type为正整数,接收类型type的第一个消息;
    type为负整数,接收小于等于type绝对值的最低类型的第一个消息。
    3)当所返回消息大小等于或小于用户的请求时,核心便将消息正文拷贝到用户区,并从消息队列中删除此消息,然后唤醒睡眠的发送进程。但如果消息长度比用户要求的大时,则做出错返回。
    (4) msgctl( )
    消息队列的操纵。读取消息队列的状态信息并进行修改,如查询消息队列描述符、修改它的许可权及删除该队列等。
    系统调用格式:
    msgctl(msgqid,cmd,buf);
    本函数使用的头文件如下:
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    参数定义:
    int msgctl(msgqid,cmd,buf);
    int msgqid,cmd;
    struct msgqid_ds *buf;
    其中,函数调用成功时返回0,不成功则返回-1。buf是用户缓冲区地址,供用户存放控制参数和查询结果;cmd是规定的命令。命令可分三类:
    1)IPC_STAT。查询有关消息队列情况的命令。如查询队列中的消息数目、队列中的最大字节数、最后一个发送消息的进程标识符、发送时间等;
    2)IPC_SET。按buf指向的结构中的值,设置和改变有关消息队列属性的命令。如改变消息队列的用户标识符、消息队列的许可权等;
    3)IPC_RMID。消除消息队列的标识符。
    msgqid_ds 结构定义如下:
    struct msgqid_ds
    { struct ipc_perm msg_perm; /许可权结构/
    short pad1[7]; /由系统使用/
    ushort msg_qnum; /队列上消息数/
    ushort msg_qbytes; /队列上最大字节数/
    ushort msg_lspid; /最后发送消息的PID/
    ushort msg_lrpid; /最后接收消息的PID/
    time_t msg_stime; /最后发送消息的时间/
    time_t msg_rtime; /最后接收消息的时间/
    time_t msg_ctime; /最后更改时间/
    };
    struct ipc_perm
    { ushort uid; /当前用户/
    ushort gid; /当前进程组/
    ushort cuid; /创建用户/
    ushort cgid; /创建进程组/
    ushort mode; /存取许可权/
    { short pid1; long pad2;} /由系统使用/
    }

    6、进程的共享存储区通信
    编制一长度为1k的共享存储区发送和接收的程序。
    (1)共享存储区机制的概念
    共享存储区(Share Memory)是UNIX系统中通信速度最高的一种通信机制。该机制可使若干进程共享主存中的某一个区域,且使该区域出现(映射)在多个进程的虚地址空间中。另一方面,一个进程的虚地址空间中又可连接多个共享存储区,每个共享存储区都有自己的名字。当进程间欲利用共享存储区进行通信时,必须先在主存中建立一共享存储区,然后将它附接到自己的虚地址空间上。此后,进程对该区的访问操作,与对其虚地址空间的其它部分的操作完全相同。进程之间便可通过对共享存储区中数据的读、写来进行直接通信。图示列出二个进程通过共享一个共享存储区来进行通信的例子。其中,进程A将建立的共享存储区附接到自己的AA’区域,进程B将它附接到自己的BB’区域。

    进程A的虚空间 内存空间 进程B的虚空间

                A
    
                A’
    
    
    
    应当指出,共享存储区机制只为进程提供了用于实现通信的共享存储区和对共享存储区进行操作的手段,然而并未提供对该区进行互斥访问及进程同步的措施。因而当用户需要使用该机制时,必须自己设置同步和互斥措施才能保证实现正确的通信。
    

    (2)涉及的系统调用
    1)shmget( )
    创建、获得一个共享存储区。
    系统调用格式:
    shmid=shmget(key,size,flag)
    该函数使用头文件如下:
    #include<sys/types.h>
    #include<sys/ipc.h>
    #include<sys/shm.h>
    参数定义
    int shmget(key,size,flag);
    key_t key;
    int size,flag;
    其中,key是共享存储区的名字;size是其大小(以字节计);flag是用户设置的标志,如IPC_CREAT。IPC_CREAT表示若系统中尚无指名的共享存储区,则由核心建立一个共享存储区;若系统中已有共享存储区,便忽略IPC_CREAT。
    附:
    操作允许权 八进制数
    用户可读 00400
    用户可写 00200
    小组可读 00040
    小组可写 00020
    其它可读 00004
    其它可写 00002

    控制命令 值
    IPC_CREAT 0001000
    IPC_EXCL 0002000
    例:shmid=shmget(key,size,(IPC_CREAT|0400))
    创建一个关键字为key,长度为size的共享存储区
    2)shmat( )
    共享存储区的附接。从逻辑上将一个共享存储区附接到进程的虚拟地址空间上。
    系统调用格式:
    virtaddr=shmat(shmid,addr,flag)
    该函数使用头文件如下:
    #include<sys/types.h>
    #include<sys/ipc.h>
    #include<sys/shm.h>
    参数定义
    char *shmat(shmid,addr,flag);
    int shmid,flag;
    char * addr;
    其中,shmid是共享存储区的标识符;addr是用户给定的,将共享存储区附接到进程的虚地址空间;flag规定共享存储区的读、写权限,以及系统是否应对用户规定的地址做舍入操作。其值为SHM_RDONLY时,表示只能读;其值为0时,表示可读、可写;其值为SHM_RND(取整)时,表示操作系统在必要时舍去这个地址。该系统调用的返回值是共享存储区所附接到的进程虚地址viraddr。
    3)shmdt( )
    把一个共享存储区从指定进程的虚地址空间断开。
    系统调用格式:
    shmdt(addr)
    该函数使用头文件如下:
    #include<sys/types.h>
    #include<sys/ipc.h>
    #include<sys/shm.h>
    参数定义
    int shmdt(addr);
    char addr;
    其中,addr是要断开连接的虚地址,亦即以前由连接的系统调用shmat( )所返回的虚地址。调用成功时,返回0值,调用不成功,返回-1。
    4)shmctl( )
    共享存储区的控制,对其状态信息进行读取和修改。
    系统调用格式:
    shmctl(shmid,cmd,buf)
    该函数使用头文件如下:
    #include<sys/types.h>
    #include<sys/ipc.h>
    #include<sys/shm.h>
    参数定义
    int shmctl(shmid,cmd,buf);
    int shmid,cmd;
    struct shmid_ds *buf;
    其中,buf是用户缓冲区地址,cmd是操作命令。命令可分为多种类型:
    第一种:用于查询有关共享存储区的情况。如其长度、当前连接的进程数、共享区的创建者标识符等;
    第二种:用于设置或改变共享存储区的属性。如共享存储区的许可权、当前连接的进程计数等;
    第三种:对共享存储区的加锁和解锁命令;
    第四种:删除共享存储区标识符等。
    上述的查询是将shmid所指示的数据结构中的有关成员,放入所指示的缓冲区中;而设置是用由buf所指示的缓冲区内容来设置由shmid所指示的数据结构中的相应成员。

    四、实验中用到的系统调用函数(包括实验原理中介绍的和自己采用的),自己采用的系统调用函数要按照指导书中的格式说明进行介绍。
    本实验的用到的主要系统调用函数:
    Fork, exec, wait, exit, getpid, sleep, lockf, kill, signal, read, write, msgget, msgsnd, msgrcv, msgctl,shmget, shmat, shmdt, shmctl。
    五、实验步骤(要求写出实验过程和思路。)
    1.父进程创建两个子进程,然后三个进程并发执行,父进程输出a,子进程a输出b,子进程b输出c,多次运行程序,并通过结果观察其规律。

    2.父进程创建一个子进程,如果子进程创建成功,若此时是父进程正在运行,则父进程进行等待操作,将cpu教给子进程,子进程获取cpu后,开始运行,用系统中bin目录下的ls命令程序装入子进程运行的地址,即用ls命令程序代替子进程。当子进程运行完毕,父进程输出,程序运行完毕。

    3.父进程创建两个子进程,若是父进程运行,则输出parent 1-500,若子进程运行,则输出son1-500或者daughter1-500,分别用加锁和不加锁的程序进行测试。

    4.用fork( )创建两个子进程,再用系统调用signal( )让父进程捕捉键盘上来的中断信号(即按^c键);捕捉到中断信号后,父进程用系统调用kill( )向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止:
    Child process1 is killed by parent!
    Child process2 is killed by parent!
    父进程等待两个子进程终止后,输出如下的信息后终止:
    Parent process is killed!

    5.在两个终端上创建两个进程,分别是client负责发送消息,server负责接受消息。

    6.建立两个进程server和client,开辟一个共享资源区,双方都处于不断同步的状态,client等待资源区被server修改,而server修改后,也在等待client修改资源区,client察觉资源区被修改,也会继续修改,就这样进行循环。

    六、实验数据及源代码(学生必须提交自己设计的程序源代码,并有注释,源代码电子版也一并提交),包括思考题的程序。
    1)

    #include <unistd.h>
    #include <sys/types.h>
    #include <stdio.h>
    int main(int argc,char *argv[])
    {
        int pa;  //进程a
        int pb;  //进程b
        while((pa=fork())==-1); //等待子进程a建立完成
        if(pa==0)
          {
             putchar('b');
          }
        else
          {
             while((pb=fork())==-1);
             if(pb==0)
               {
                  putchar('c');
               }
             else
               {
                 putchar('a');
               }
          }
    }
    

    2)

    #include <stdlib.h>
    #include<stdio.h>
    #include<sys/wait.h>
    #include<unistd.h>
    int main(int argc,char* argv[])
    {
      int pid;
      pid=fork();
      switch(pid)
      {
        case -1:
                printf("fork fail!\n");
                exit(1);
        case 0:
                execl("/bin/ls","-1","-color",NULL);
                printf("exec fail!\n");
                exit(1);
        default:
                wait(NULL);
                printf("ls completed!\n");
                exit(0);
    
      }
    }
    

    3)

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/wait.h>
    #include<unistd.h>
    
    int main(int argc,char* argv[])
    {
         int p1,p2,i;
         p1=fork();
         p2=fork();
         if(p1==0)
           {
               lockf(1,1,0);     //加锁
               for(i=0;i<500;i++)
                   printf("parent %d\n",i);
               lockf(1,0,0);   //解锁
               wait(0);  //保证子进程终止前,父进程不终止
               exit(0);
           }
         else
          {
               if(p2==0)
                 {
                     lockf(1,1,0);
                     for(i=0;i<500;i++)
                           printf("son %d\n",i);
                     lockf(1,0,0);
                     wait(0);  
                     exit(0);
                 }
               else
                {
                      lockf(1,1,0);
                      for(i=0;i<500;i++)
                           printf("daughter %d\n",i);
                      lockf(1,0,0);
                      exit(0);
                }
          }
    }
    

    4)

    #include<stdio.h>
    #include<signal.h>
    #include<unistd.h>
    #include<sys/wait.h>
    #include<stdlib.h>
    
    int pid1,pid2;
    int EndFlag=0;
    int pf1=0;
    int pf2=0;
    
    void IntDelete()
    {
       kill(pid1,16);
       kill(pid2,17);
    }
    
    void Int1()
    {
    printf("child process 1 is killed by parent!\n");
    exit(0);
    }
    void Int2()
    {
    printf("child process 2 is killed by parent!\n");
    exit(0);
    }
    int main(int argc,char*argv[])
    {
       int exitpid;
       if(pid1=fork())
          {
               if(pid2=fork())
                  {
                      signal(SIGINT,IntDelete);
                      waitpid(-1,&exitpid,0);
                      waitpid(-1,&exitpid,0);
                      printf("parent process is killed\n");
                      exit(0);
                  }
               else
                  {
                       signal(SIGINT,SIG_IGN);
                       signal(17,Int2);
                       pause();
                  }
          }
        else
          {
             signal(SIGINT,SIG_IGN);
             signal(16,Int1);
             pause();
          }
    
    }
    

    5)
    Server:

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/msg.h>
    #include<sys/ipc.h>
    #define MSGKEY 75
    struct msgform
    {
    long mtype;
    char mtextp[1000];
    }msg;
    int msgqid;
    void server()
    {
      msgqid=msgget(MSGKEY,0777|IPC_CREAT);
      do
       {
         msgrcv(msgqid,&msg,1030,0,0);
         printf("(sever)received\n");
       }while(msg.mtype!=1);
      msgctl(msgqid,IPC_RMID,0);
      exit(0);
    }
    int main(int argc,char* argv[])
    {
      server();
    }
    

    client:

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/msg.h>
    #include<sys/ipc.h>
    #define MSGKEY 75
    struct magform
    {
    long mtype;
    char mtext[1000];
    }msg;
    int msgqid;
    void client()
    {
       int i;
       msgqid=msgget(MSGKEY,0777);
       for(i=10;i>=-1;i--)
          {
            msg.mtype=i;
            printf("(client)sent\n");
            msgsnd(msgqid,&msg,1024,0);
          }
       exit(0);
    }
    int main(int argc,char* argv[])
    {
    client();
    }
    

    ~
    6)

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/shm.h>
    #include<sys/ipc.h>
    #include<sys/wait.h>
    
    #include<unistd.h>
    #define SHMKEY 75
    int shmid,i;
    int *addr;
    void client()
    {
      int i;
      shmid=shmget(SHMKEY,1024,0777);
      addr=shmat(shmid,0,0);
      for(i=9;i>=0;i--)
        {
          while(*addr!=-1)
             printf("(client)sent\n");
          *addr=i;
        }
    exit(0);
    }
    void server()
    {
       shmid=shmget(SHMKEY,1024,0777|IPC_CREAT);
       addr=shmat(shmid,0,0);
       do
       {
        *addr=-1;
        while(*addr==-1);
            printf("*(server)receive\n");
       }while(*addr);
       shmctl(shmid,IPC_RMID,0);
       exit(0);
    
    }
    
    int main(int argc,char*argv[])
    {
      while((i=fork())==-1);
      if(!i) server();
      system("ipcs -m");
      while((i=fork())==-1);
      if(!i) client();
      wait(0);
      wait(0);
    }
    

    七、实验结果分析(截屏的实验结果,与实验结果对应的实验分析)
    1、实验结果与实验程序、实验步骤、实验原理、操作系统原理的对应分析;
    2、不同条件下的实验结果反应的问题及原因;
    3、实验结果的算法时间、效率、鲁棒性等性能分析。

    结果:
    刚开始的结果都是acb。
    在这里插入图片描述
    但是这不太符合并发执行的进程结果,所以后来我把虚拟机的处理器内核数量从1提高到3就会出现随机的结果啦!
    在这里插入图片描述
    分析:从结果来看,父进程和两个子进程的并发执行,结果是随机的,即abc的顺序都有可能,也就是说,并发执行的子进程之间,在没有优先级的限制条件下,争夺cpu的情况是随机的,在代码中,可能存在进程建立时间和代码运行输出等时间的一个不对等性,但是进程获取的时间片是随机的,故而会输出随机的不同结果。
    思考题:
    2.
    结果:
    利用wait()函数实现同步:

    在这里插入图片描述
    未用wait()函数实现同步:
    在这里插入图片描述
    分析:第一个结果是利用睡眠等待等操作实现同步,第二幅图的结果是未用等待操作导致不同步的结果,由第一幅图可知,子进程创建成功,且运行了ls命令程序,多次运行程序也是同一个结果,而当取消wait()的时候,顺序输出便错乱了,说明该程序父进程和子进程实现了同步。
    3.
    结果:
    1)不用lockf()进行进程加锁:
    在这里插入图片描述
    利用lockf()给进程加锁:
    在这里插入图片描述
    分析:
    Lockf(1,1,0)加锁输出设备,而Lockf(1,0,0)解锁输出设备,未用lockf进行进程加锁,三个循环之间会交错进行,输出也会出现交错状态,但加锁后则不会,所以在lockf(1,1,0)和lockf(1,0,0)之间的for循环输出中,不会被间断。但加锁前的三个进程间的存在并发执行,故而parent,daughter,son这三个循环的输出顺序随机,但循环不会被间断。
    分析:

    结果:
    在这里插入图片描述
    分析:
    父进程接收到ctrl+c信号才会调用kill()向两个子进程发出信号。子进程接收到信号才会打印。
    5)
    结果:
    在这里插入图片描述
    在这里插入图片描述

    分析:首先运行服务代码test5_s,server会进入等待状态,继而运行客户代码test5_c像服务器发出消息,并输出(client)sent,继而运行服务器的终端收到消息,输出(server)received。

    6)
    结果:
    在这里插入图片描述
    分析:
    Client和server两个进程拥有共享资源区,开始时,client修改了资源区内容,然后等待共享区的内容被修改为-1,因此是等待状态,而server修改了共享区的内容后,client察觉到内容变为-1,故而又继续修改,不断循环,直到client修改为-1。

    思考题:

    1、进程创建与进程并发执行
    (1)系统是怎样创建进程的?
    1.申请空白PCB(进程控制块);
    2.为新进程分配资源;
    3.初始化PCB;
    4.就新进程插入就绪队列;
    (2)当首次调用新创建进程时,其入口在哪里?
    fork()函数被调用一次,但返回两次;两次返回区别在于:子程序返回值是0,而父进程返回值是子进程的ID。子进程和父进程运行相同的代码,但是有自己的数据空间。

    (3)程序的多次运行结果为什么不同?如何控制实验结果的随机性?
    多个进程的并发执行,每个进程都有先获取cpu的可能性,故而哪一个进程先执行是随机的。可以通过等待,睡眠的等操作来实现多个进程的同步。
    (4)利用strace 和ltrace -f -i -S ./executable-file-name查看程序执行过程,并分析原因,画出进程家族树。

    2、进程的睡眠、同步、撤消等进程控制
    (1)可执行文件加载时进行了哪些处理?
    进程用exec( )装入命令ls ,exec( )后,子进程的代码被ls的代码取代,这时子进程的PC指向ls的第1条语句,开始执行ls的命令代码
    (2)什么是进程同步?wait( )是如何实现进程同步的?
    进程同步是指多个相关进程在执行次序上进行协调,以使并发执行的主进程之间有效的共享资源和相互合作,从而使程序的执行具有可再现性。
    首先程序在调用fork()创建了一个子进程后,马上调用wait(),使父进程在子进程调用之前一直处于睡眠状态,这样使子进程先运行,子进程运行exec()装入命令后,然后调用wait(0),使子进程和父进程并发执行,实现了进程同步。
    (3)wait( )和exit()是如何控制实验结果的随机性的?
    可以看出在使用了exec()函数后程序使用了ls的命令,列出/bin/目录下的文件信息,执行完execl()函数后,子进程调用exit()函数,退出当前进程,我们可以发现在使用wait()函数后,父进程永远将在其他的子进程完成之后才执行,所以在输出的结果中我们可以看到最后输出的将是父进程的信息,这样进而可以控制实验结果的随机性。
    3、多进程通过加锁互斥并发运行
    (1)进程加锁和未上锁的输出结果相同吗? 为什么?
    大致与未上锁的输出结果相同,也是随着执行时间不同,输出结果的顺序有所不同。
    不同进程之间不存在共享临界资源问题,所以加锁与不加锁的效果大致相同。
    4、进程间通过信号机制实现软中断通信
    (1)为了得到实验内容要求的结果,需要用到哪些系统调用函数来实现及进程间的通信控制和同步?
    Signal,kill,wait,exit,lockf
    (1)kill( )和signal( )函数在信号通信中的作用是什么?如果分别注释掉它们,结果会如何?
    结果都不会有输出,因为注释kill的情况下,父进程在等待子进程的信号,子进程也在等待父进程的信号,进入的死锁状态。而注释掉signal的情况下,子进程无法接受道父进程的信号,也无法向父进程发送信号,所以子进程结束后,父进程一直处于等待状态。
    5、消息的发送与接收
    (1)为了便于操作和观察结果,需要编制几个程序分别用于消息的发送与接收?
    为了便于操作和观察结果,编制了两个程序client.c和server.c,分别用于信息的发送与接受。
    (2)这些程序如何进行编辑、编译和执行?为什么?
    使用vim工具:vim test5_c.c vim test5_s.c分别编辑两个文件的代码
    使用gcc: gcc -o test5_c test5_c.c和gcc -o test5_s test5_s.c
    最后在两个终端分别先后运行./test5_s和./test5_c

    (3)如何实现消息的发送与接收的同步?
    分别在两个终端建立两个进程server和client,client发送信号,server接受信号,先运行server,后运行client。在server接受到消息后才会输出。

    6、进程的共享存储区通信
    (1)为了便于操作和观察结果,需要如何合理设计程序来实现子进程间的共享存储区通信?
    建立两个进程server和client,开辟一个共享资源区,双方都处于不断同步的状态,client等待资源区被server修改,而server修改后,也在等待client修改资源区,client察觉资源区被修改,也会继续修改,就这样进行循环多次。

    (2)比较消息通信和共享存储区通信这两种进程通信机制的性能和优缺点。
    答:由于两种机制实现的机理和用处都不一样,难以直接进行时间上的比较。如果比较其性能,应更加全面的分析。
    (1)消息队列的建立比共享区的设立消耗的资源少。前者只是一个软件上设定的问题,后者需要对硬件的操作,实现内存的映像,当然控制起来比前者复杂。如果每次都重新进行队列或共享的建立,共享区的设立没有什么优势。
    (2)当消息队列和共享区建立好后,共享区的数据传输,受到了系统硬件的支持,不耗费多余的资源;而消息传递,由软件进行控制和实现,需要消耗一定的cpu的资源。从这个意义上讲,共享区更适合频繁和大量的数据传输。
    (3)消息的传递,自身就带有同步的控制。当等到消息的时候,进程进入睡眠状态,不再消耗cpu资源。而共享队列如果不借助其他机制进行同步,接收数据的一方必须进行不断的查询,白白浪费了大量的cpu资源。可见,消息方式的使用更加灵活。

    展开全文
  • 操作系统实验1

    千次阅读 2019-06-26 18:09:58
    操作系统 实验报告 题目: 实验1 学生姓名: 周思宇 学生学号: 201608030201 ...

     

    HUNAN  UNIVERSITY

     

     

     

     

    操作系统

    实验报告

     

     

     

     

     

     

     

      目:

    实验1

     

    学生姓名:

    周思宇

     

    学生学号:

    201608030201

     

    专业班级:

    计科1601

     

    完成日期:

    2018.11.22

    目  录

    一、内容

    二、目的

    三、实验设计思想和流程

    四、主要数据结构及符号说明

    五、实验环境以及实验过程与结果分析

    六、实验体会和思考题

    附录(源代码及注释)

    一、内容

    lab1中包含一个bootloader和一个OS。这个bootloader可以切换到X86保护模式,能够读磁盘并加载ELF执行文件格式,并显示字符。而这lab1中的OS只是一个可以处理时钟中断和显示字符的幼儿园级别OS。

     

    二、目的

    操作系统是一个软件,也需要通过某种机制加载并运行它。在这里我们将通过另外一个更加简单的软件-bootloader来完成这些工作。为此,我们需要完成一个能够切换到x86的保护模式并显示字符的bootloader,为启动操作系统ucore做准备。lab1提供了一个非常小的bootloader和ucore OS,整个bootloader执行代码小于512个字节,这样才能放到硬盘的主引导扇区中。通过分析和实现这个bootloader和ucore OS,读者可以了解到:

     

    计算机原理

    • CPU的编址与寻址: 基于分段机制的内存管理
    • CPU的中断机制
    • 外设:串口/并口/CGA,时钟,硬盘

     

    Bootloader软件

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

     

    ucore OS软件

    • 编译运行ucore OS的过程
    • ucore OS的启动过程
    • 调试ucore OS的方法
    • 函数调用关系:在汇编级了解函数调用栈的结构和处理过程
    • 中断管理:与软件相关的中断处理
    • 外设管理:时钟

     

    三、实验设计思想和流程(需要编程的题)

    第五题:

    代码设计步骤:

    1. 调用read_ebp()以获得ebp.类型为(uint32_t);
    2. 调用read_eip()以获得eip的值。类型为(uint32_t);
    3. 从0.。。。到stackdepth

    ebp、eip的值打印出来

    uint32_t)calling arguments [0..4] = the contents in address (unit32_t)ebp +2 [0..4]

    输出("n");

    调用print_debuginfo(eip-1)来打印c语言调用函数名和行号等。

    弹出调用堆栈帧

    注意:

    调用函数的返回加法器eip=ss:[ebp+4]

    调用函数的ebp=ss:[ebp]

     

    第六题:

    代码设计步骤:

    首先确定每个中断服务例程的entry addrs在哪里?

    存储在vectors中。

    uintptr_t_vectors[]在哪里?

    _vectors[]位于kern/trap/vector.S中,它是由tools/vector.c生成的。

    可以使用“extern uintptr_t __vectors[]”定义这个extern变量。

    然后,在中断描述表(IDT)中设置ISR的条目。

    然后使用“lidt”指令让CPU知道IDT在哪里。

    Google libs/x86.h看一下具体是啥意思

    题目中也给过,lidt的参数是idt_pd。

     

    然后是设置100tick的输出,更简单:

    在定时器中断之后,使用全局变量记录

    每个TICK_NUM周期,都可以使用函数打印print_ticks()。

     

    第七题:

    代码设计步骤:

    指针得弄懂,堆栈也得彻底弄懂才能看懂

    在从内核态,通过中断,切换为用户态时:

    首先要执行sub 0x8,esp 这个语句

    然后执行int T_SWITCH_TOU表示发生这个中断,按照之前的叙述,此时的执行过程是:中断向量--查找中断向量表--查找入口地址:发现此时CPL并没有发生切换

    所以并不把当前的ss和esp入栈,直接把eflags,cs,eip入栈,然后进入vector规定的地址后,继续把errorno和trapno入栈,然后进入__alltraps,把ds es gs ss 入栈,pushal,当前esp入栈,执行trap,执行trapdispatch,执行相应中断向量号case处的代码:

    当前不是用户态,要不要切换呢?此时需要对堆栈进行一些操作。现在是内核栈,我们原来从用户到内核态转换时,是通过TSS查到内核态的ss和esp的,但是这里似乎并没有从TSS查用户态的ss和esp?我们需要自己建立一个堆栈给他使用,这个就是这里的switchk2u变量所对应的地址。这个变量是全局变量,所以它具体在哪存着?

    所以至少有一个意识:用户态的堆栈和内核态的堆栈不在一个地方

    看具体代码实现

    这里首先把tf所指的内容复制过来到switchk2u所对应的地址上,然后设置switchk2u这个变量的cs段,ds,es,ss等为用户数据段选择。

    为啥设置esp呢?

    原来的trapframe结构的esp保存的是如果发生了权限切换,那么保存原来那个特权级的esp,便于之后恢复。

    他的意思是tf结构体esp所在的位置。然而真正给esp赋值的地方,是在中断结束返回后,手动把当前的ebp的值给esp。

    之后设置eflags,因为用户态要实现IO,需要把eflags寄存器中的IOPL标志位设置为3,这样CPL<=IOPL是恒成立的,用户态也可以实现IO了

    switchk2u是与内核栈不同的一个地址,我们要把它作为新的用户栈,并且还要保证在iret恢复寄存器时,要从switchl2u所规定的这个栈中恢复。

    那该如何实现iret恢复寄存器时,是从switchk2u这里恢复而不是从之前的tf这里恢复呢????

    trapentry.S在call trap后第一句执行的语句是什么?

    popl esp

    这个popl esp就是我们修改用户栈指针的地方。如果把popl esp这个语句原来要弹出的内容,换成switchk2u的地址,那么就可以把esp指针设置为switchk2u了。要时后来根据esp恢复寄存器,就会从switchk2u这块恢复。

    具体实现过程在后面。这一部分只说思路。

     

    四、主要数据结构及符号说明(需要编程的题)

    第五题:

    数据结构:函数堆栈

    一个函数调用动作可分解为:零到多个PUSH指令(用于参数入栈),一个CALL指令。CALL指令内部其实还暗含了一个将返回地址(即CALL指令下一条指令的地址)压栈的动作(由硬件完成)。几乎所有本地编译器都会在每个函数体之前插入类似如下的汇编指令:

    这样在程序执行到一个函数的实际指令前,已经有以下数据顺序入栈:参数、返回地址、ebp寄存器。由此得到类似如下的栈结构

    这两条汇编指令的含义是:首先将ebp寄存器入栈,然后将栈顶指针esp赋值给ebp。“mov ebp esp”这条指令表面上看是用esp覆盖ebp原来的值,其实不然。因为给ebp赋值之前,原ebp值已经被压栈(位于栈顶),而新的ebp又恰恰指向栈顶。此时ebp寄存器就已经处于一个非常重要的地位,该寄存器中存储着栈中的一个地址(原ebp入栈后的栈顶),从该地址为基准,向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取函数局部变量值,而该地址处又存储着上一层函数调用时的ebp值。

    ss:[ebp+4]处为返回地址

    ss:[ebp+8]处为第一个参数值(最后一个入栈的参数值,此处假设其占用4字节内存)

    ss:[ebp-4]处为第一个局部变量

    ss:[ebp]处为上一层ebp值

    由于ebp中的地址处总是“上一层函数调用时的ebp值”,而在每一层函数调用中,都能通过当时的ebp值“向上(栈底方向)”能获取返回地址、参数值,“向下(栈顶方向)”能获取函数局部变量值。如此形成递归,直至到达栈底。这就是函数调用栈。

    内联函数Read Type()可以告诉我们当前EBP的值。以及

    非内联函数Read Type()是有用的,它可以读取当前EIP的值,

    因为在调用这个函数时,read_eip()可以读

    容易堆叠。

    函数说明:

    内联函数read_ebp()可以告诉我们当前的ebp。

    非内联函数read_eip()可以读取当前eip的值,因为在调用这个函数时,read_eip()可以轻松地从堆栈中读取调用方的eip。

    在print_debuginfo()中,函数debuginfo_eip()可以获得关于调用链的足够信息。

    print_stackframe()将跟踪并打印它们以进行调试。

    在boot/bootasm.S中,在跳转到内核条目之前,ebp的值被设置为零,这就是边界。

     

    第六题:

    数据结构的解释:

    __vectors[i]

    这个数组是干什么的?

    保存了每个中断向量的入口地址

    而这些入口地址,就是当中断发生时,中断描述符中所对应的那个offset,所以一旦中断发生,中断处理程序首先是会跳到vector[i]所对应的代码

    idt_init就是初始化中断向量表:vector.S规定了每个中断处理例程的代码偏移,然后idt_init通过这些偏移,设置好idt表,然后再通过lidt,把idt表的初始地址保存到idtr寄存器中,这样中断相关的数据结构初始化完毕了

     

    第三小问的数据结构:

    vector.S规定了中断的入口地址

    vector.S文件,有两部分,第一部分是代码段,定义了vector0到vector255这256个标号所对应的代码段的起始位置,每个标号后的代码无非是两种:要么是压入0和中断向量,要么就不压入0,只压入中断向量。然后是jmp __alltraps

    TICK_NUM之前都设置过了

    每轮100次就调用print函数即可,没什么特别复杂的数据结构。

     

    第七题:

       lab1_print_cur_status();当前状态:是内核态还是用户态?

       lab1_switch_to_user(); 转换去用户态

       lab1_switch_to_kernel();转换去内核态

    五、实验环境以及实验过程与结果分析

    练习1:理解通过make生成执行文件的过程。(要求在报告中写出对下述问题的回答)

    列出本实验各练习中对应的OS原理的知识点,并说明本实验中的实现部分如何对应和体现了原理中的基本概念和关键知识点。

    在此练习中,大家需要通过静态分析代码来了解:

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

    一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?

           

    实验分析:

    lab1的整体目录结构如下所示

    .

    ├── boot

    │   ├── asm.h

    │   ├── bootasm.S

    │   └── bootmain.c

    ├── kern

    │   ├── debug

    │   │  ├── assert.h

    │   │  ├── kdebug.c

    │   │  ├── kdebug.h

    │   │  ├── kmonitor.c

    │   │   ├── kmonitor.h

    │   │  ├── panic.c

    │   │  └── stab.h

    │   ├── driver

    │   │  ├── clock.c

    │   │  ├── clock.h

    │   │  ├── console.c

    │   │  ├── console.h

    │   │  ├── intr.c

    │   │  ├── intr.h

    │   │  ├── kbdreg.h

    │   │  ├── picirq.c

    │   │  └── picirq.h

    │   ├── init

    │   │  └── init.c

    │   ├── libs

    │   │  ├── readline.c

    │   │  └── stdio.c

    │   ├── mm

    │   │  ├── memlayout.h

    │   │  ├── mmu.h

    │   │  ├── pmm.c

    │   │  └── pmm.h

    │   └── trap

    │       ├── trap.c

    │       ├── trapentry.S

    │       ├── trap.h

    │       └── vectors.S

    ├── libs

    │   ├── defs.h

    │   ├── elf.h

    │   ├── error.h

    │   ├── printfmt.c

    │   ├── stdarg.h

    │   ├── stdio.h

    │   ├── string.c

    │   ├── string.h

    │   └── x86.h

    ├── Makefile

    └── tools

        ├── function.mk

        ├── gdbinit

        ├── grade.sh

        ├── kernel.ld

        ├── sign.c

        └── vector.c

    bootloader部分

    • boot/bootasm.S :定义并实现了bootloader最先执行的函数start,此函数进行了一定的初始化,完成了从实模式到保护模式的转换,并调用bootmain.c中的bootmain函数。
    • boot/bootmain.c:定义并实现了bootmain函数实现了通过屏幕、串口和并口显示字符串。bootmain函数加载ucore操作系统到内存,然后跳转到ucore的入口处执行。
    • boot/asm.h:是bootasm.S汇编文件所需要的头文件,主要是一些与X86保护模式的段访问方式相关的宏定义。

    ucore操作系统部分

    系统初始化部分:

    • kern/init/init.c:ucore操作系统的初始化启动代码

    内存管理部分:

    • kern/mm/memlayout.h:ucore操作系统有关段管理(段描述符编号、段号等)的一些宏定义
    • kern/mm/mmu.h:ucore操作系统有关X86 MMU等硬件相关的定义,包括EFLAGS寄存器中各位的含义,应用/系统段类型,中断门描述符定义,段描述符定义,任务状态段定义,NULL段声明的宏SEG_NULL, 特定段声明的宏SEG,设置中断门描述符的宏SETGATE(在练习6中会用到)
    • kern/mm/pmm.[ch]:设定了ucore操作系统在段机制中要用到的全局变量:任务状态段ts,全局描述符表gdt[],加载全局描述符表寄存器的函数lgdt,临时的内核栈stack0;以及对全局描述符表和任务状态段的初始化函数gdt_init

    外设驱动部分:

    • kern/driver/intr.[ch]:实现了通过设置CPU的eflags来屏蔽和使能中断的函数;
    • kern/driver/picirq.[ch]:实现了对中断控制器8259A的初始化和使能操作;
    • kern/driver/clock.[ch]:实现了对时钟控制器8253的初始化操作;- kern/driver/console.[ch]:实现了对串口和键盘的中断方式的处理操作;

    中断处理部分:

    • kern/trap/vectors.S:包括256个中断服务例程的入口地址和第一步初步处理实现。注意,此文件是由tools/vector.c在编译ucore期间动态生成的;
    • kern/trap/trapentry.S:紧接着第一步初步处理后,进一步完成第二步初步处理;并且有恢复中断上下文的处理,即中断处理完毕后的返回准备工作;
    • kern/trap/trap.[ch]:紧接着第二步初步处理后,继续完成具体的各种中断处理操作;

    内核调试部分:

    • kern/debug/kdebug.[ch]:提供源码和二进制对应关系的查询功能,用于显示调用栈关系。其中补全print_stackframe函数是需要完成的练习。其他实现部分不必深究。
    • kern/debug/kmonitor.[ch]:实现提供动态分析命令的kernel monitor,便于在ucore出现bug或问题后,能够进入kernel monitor中,查看当前调用关系。实现部分不必深究。
    • kern/debug/panic.c | assert.h:提供了panic函数和assert宏,便于在发现错误后,调用kernel monitor。大家可在编程实验中充分利用assert宏和panic函数,提高查找错误的效率。

    公共库部分

    • libs/defs.h:包含一些无符号整型的缩写定义。
    • Libs/x86.h:一些用GNU C嵌入式汇编实现的C函数(由于使用了inline关键字,所以可以理解为宏)。

    工具部分

    • Makefile和function.mk:指导make完成整个软件项目的编译,清除等工作。
    • sign.c:一个C语言小程序,是辅助工具,用于生成一个符合规范的硬盘主引导扇区。
    • tools/vector.c:生成vectors.S,此文件包含了中断向量处理的统一实现。

     

    这一部分写的贼长,助教你前面的不想看可以从P21的简化开始看!有画图也有简单描述文件之间的关系

    Make之后:

     

    在make ”V=”之后,输出:

    + cc kern/init/init.c

    i386-elf-gcc -Ikern/init/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/init/init.c -o obj/kern/init/init.o

    编译init.c

     

    + cc kern/libs/readline.c

    i386-elf-gcc -Ikern/libs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/libs/readline.c -o obj/kern/libs/readline.o

    编译readline.c

     

    + cc kern/libs/stdio.c

    i386-elf-gcc -Ikern/libs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/libs/stdio.c -o obj/kern/libs/stdio.o

    编译stdio.c

     

    + cc kern/debug/kdebug.c

    i386-elf-gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/kdebug.c -o obj/kern/debug/kdebug.o

    编译

     

    + cc kern/debug/kmonitor.c

    i386-elf-gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/kmonitor.c -o obj/kern/debug/kmonitor.o

    编译

     

    + cc kern/debug/panic.c

    i386-elf-gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/panic.c -o obj/kern/debug/panic.o

    编译

     

    + cc kern/driver/clock.c

    i386-elf-gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/clock.c -o obj/kern/driver/clock.o

    编译

     

    + cc kern/driver/console.c

    i386-elf-gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/console.c -o obj/kern/driver/console.o

    编译

     

    + cc kern/driver/intr.c

    i386-elf-gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/intr.c -o obj/kern/driver/intr.o

    编译

     

    + cc kern/driver/picirq.c

    i386-elf-gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/picirq.c -o obj/kern/driver/picirq.o

    编译

     

    + cc kern/trap/trap.c

    i386-elf-gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/trap.c -o obj/kern/trap/trap.o

    编译

     

    + cc kern/trap/trapentry.S

    i386-elf-gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/trapentry.S -o obj/kern/trap/trapentry.o

    编译trapentry.S

     

    + cc kern/trap/vectors.S

    i386-elf-gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/vectors.S -o obj/kern/trap/vectors.o

    编译

     

    + cc kern/mm/pmm.c

    i386-elf-gcc -Ikern/mm/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/mm/pmm.c -o obj/kern/mm/pmm.o

    编译

     

    + cc libs/printfmt.c

    i386-elf-gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/  -c libs/printfmt.c -o obj/libs/printfmt.o

    编译

     

    + cc libs/string.c

    i386-elf-gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/  -c libs/string.c -o obj/libs/string.o

    编译

     

    + ld bin/kernel

    i386-elf-ld -m    elf_i386 -nostdlib -T tools/kernel.ld -o bin/kernel obj/kern/init/init.o obj/kern/libs/readline.o obj/kern/libs/stdio.o obj/kern/debug/kdebug.o obj/kern/debug/kmonitor.o obj/kern/debug/panic.o obj/kern/driver/clock.o obj/kern/driver/console.o obj/kern/driver/intr.o obj/kern/driver/picirq.o obj/kern/trap/trap.o obj/kern/trap/trapentry.o obj/kern/trap/vectors.o obj/kern/mm/pmm.o obj/libs/printfmt.o obj/libs/string.o

    链接为kernel

    在makefile文件里面,有详细写

     

    + cc boot/bootasm.S

    i386-elf-gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootasm.S -o obj/boot/bootasm.o

    $(bootfiles)

    + cc boot/bootmain.c

    i386-elf-gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootmain.c -o obj/boot/bootmain.o

    $(bootfiles)

     

    + cc tools/sign.c

    gcc -Itools/ -g -Wall -O2 -c tools/sign.c -o obj/sign/tools/sign.o

    gcc -g -Wall -O2 obj/sign/tools/sign.o -o bin/sign

    sign

     

    以上都是编译

    + ld bin/bootblock

    i386-elf-ld -m    elf_i386 -nostdlib -N -e start -Ttext 0x7C00 obj/boot/bootasm.o obj/boot/bootmain.o -o obj/bootblock.o'

    根据sign规范生成bootblock

    为了生成ucore.img,首先需要生成bootblock  √kernel √

    需要kernel.ld init.o readline.o stdio.o kdebug.o kmonitor.o panic.o clock.o console.o intr.o picirq.o trap.o trapentry.o vectors.o pmm.o  printfmt.o string.o kernel.ld都存在

     

    obj/bootblock.out' size: 500 bytes    

    build 512 bytes boot sector: 'bin/bootblock' success!     每个块为512字节

    dd if=/dev/zero of=bin/ucore.img count=10000   

    创建10000个块

    10000+0 records in

    10000+0 records out

    5120000 bytes transferred in 0.054623 secs (93733191 bytes/sec)

    dd if=bin/bootblock of=bin/ucore.img conv=notrunc

    1+0 records in

    1+0 records out

    512 bytes transferred in 0.000051 secs (10034970 bytes/sec)

    dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc    

    从第二个块开始写kernel中的内容

    154+1 records in

    154+1 records out

    78940 bytes transferred in 0.001103 secs (71573359 bytes/sec)

     

    简化:

    为了生成bootblock,首先需要生成bootasm.o、bootmain.o、sign

    生成bootasm.o需要bootasm.S

    gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootasm.S -o obj/boot/bootasm.o

     

    生成bootmain.o需要bootmain.c

    gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootmain.c -o obj/boot/bootmain.o

     

    生成sign工具的makefile代码为

    $(call add_files_host,tools/sign.c,sign,sign)

    $(call create_target_host,sign,sign)

    gcc -Itools/ -g -Wall -O2 -c tools/sign.c -o obj/sign/tools/sign.o

    gcc -g -Wall -O2 obj/sign/tools/sign.o -o bin/sign

     

    首先生成bootblock.o

    ld -m elf_i386 -nostdlib -N -e start -Ttext 0x7C00 obj/boot/bootasm.o obj/boot/bootmain.o -o obj/bootblock.o

     

    拷贝二进制代码bootblock.o到bootblock.out

    objcopy -S -O binary obj/bootblock.o obj/bootblock.out

     

    使用sign工具处理bootblock.out,生成bootblock

    bin/sign obj/bootblock.out bin/bootblock

    $(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)

     

    为了生成kernel,首先需要kernel.ld init.o readline.o stdio.o kdebug.o

    kmonitor.o panic.o clock.o console.o intr.o picirq.o trap.o

    trapentry.o vectors.o pmm.o printfmt.o string.o

     

    生成一个有10000个块的文件,每个块默认512字节,用0填充

    dd if=/dev/zero of=bin/ucore.img count=10000

     

    把bootblock中的内容写到第一个块

    dd if=bin/bootblock of=bin/ucore.img conv=notrunc

     

    从第二个块开始写kernel中的内容

    dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc

     

     

    [练习1.2] 一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?

    一个磁盘主引导扇区只有512字节。且

    第510个(倒数第二个)字节是0x55,

    第511个(倒数第一个)字节是0xAA。

    1 大小为512字节

    2 多余的空间填0

    3 第510个(倒数第二个)字节是0x55,

    4 第511个(倒数第一个)字节是0xAA。

     

     

    练习2:使用qemu执行并调试lab1中的软件。(要求在报告中简要写出练习过程)

    为了熟悉使用qemu和gdb进行的调试工作,我们进行如下的小练习:

    从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行。

    在初始化位置0x7c00设置实地址断点,测试断点正常。

    从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和bootblock.asm进行比较。

    自己找一个bootloader或内核中的代码位置,设置断点并进行测试。

     

    实验分析:

    [练习2.1] 从CPU 加电后执行的第一条指令开始,单步跟踪BIOS 的执行。

    计算机启动后,CPU一开始会到一个特定的地址开始执行指令,这个特定的地址存放了系统初始化软件,负责完成计算机基本的IO初始化,这是系统加电后运行的第一段软件代码。

    计算机加电后,CPU从物理地址0xFFFFFFF0开始执行。在0xFFFFFFF0这里只是存放了一条跳转指令,通过跳转指令跳到BIOS例行程序起始点。BIOS做完计算机硬件自检和初始化后,会选择一个启动设备并且读取该设备的第一扇区到内存一个特定的地址0x7c00处,然后CPU控制权会转移到那个地址继续执行。至此BIOS的初始化工作做完了,进一步的工作交给了ucore的bootloader。

    file bin/kernel加载kernel的符号信息,我们可以看到符号值

    target remote :1234 建立联系

    set architecture i8086 实模式

    b *0x7c00设置断点

    continue继续执行

    x /2i $pc 把地址内容以反汇编形式展现

    加电之后可以设置断点

    加电之后第一条指令是什么?在什么位置?

    Bios

    Bios执行完之后会把bootloader放到0x7c00

    所以要设置断点看是不是放到了那个位置。

    16位cpu执行模式

    cli是屏蔽中断,cld让后续操作递增操作

    发现,这个和之前看到的汇编内容是一样的

    那么我可以确定,Bootloader确实被bios从硬盘加载到内存中执行了。

     

    Bin里面的q.log

    Bios第一条指令:看到有一个ljmp。

    Bios的代码(汇编级的)

    与bootblock.asm相同

     

     

    练习3:分析bootloader进入保护模式的过程。(要求在报告中写出分析)

    BIOS将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader。请分析bootloader是如何完成从实模式进入保护模式的。

     

    实验分析:

    BIOS将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader。bootloader完成的工作包括:

    • 切换到保护模式,启用分段机制
    • 读磁盘中ELF执行文件格式的ucore操作系统到内存
    • 显示字符串信息
    • 把控制权交给ucore操作系统
    • 在bootloader接手BIOS的工作后,当前的PC系统处于实模式(16位模式)运行状态,在这种状态下软件可访问的物理内存空间不能超过1MB
    • 只有在保护模式下,80386的全部32根地址线有效,可寻址高达4G字节的线性地址空间和物理地址空间

    在保护模式下,特权级总共有4个,CPU只用到其中的2个特权级:0(内核态)和3(用户态)

     

    依次把问题解决:

    • 为何开启A20,以及如何开启A20
    • 如何初始化GDT表
    • 如何使能和进入保护模式

     

    开启A20:通过将键盘控制器上的A20线置于高电位,全部32条地址线可用,可以访问4G的内存空间。

    Lgdt:描述gdt的信息加载(建立这个段表)

    那么从哪里加载呢?

    这里:

    一个段描述符占用8个字节

    1. 位置
    2. 大小

    第一个空

    第二个:代码段+数据段

    0x0基地址—对等映射,大小是4g

    注意:

    代码段可执行可读不可写

    数据段可读可写

     

    那么如何enable保护模式?

    把c20寄存器P1位置一

    orl $CR0_PE_ON, %eax

    段表的0x8处取段描述符

    长跳:

    然后就进入32位保护模式----16位访问方式到32位保护模式的转变,可以访问4g的空间了。

    这里设置地址空间

    设置堆栈ebp最开始,0,esp大小

    堆栈是向上增长,做减操作

    然后调用了bootmain!

     

     

    练习4:分析bootloader加载ELF格式的OS的过程。(要求在报告中写出分析)

    通过阅读bootmain.c,了解bootloader如何加载ELF文件。通过分析源代码和通过qemu来运行并调试bootloader&OS,

    bootloader如何读取硬盘扇区的?

    bootloader是如何加载ELF格式的OS?

     

    实验分析:

    它的作用是加载ucore

    那我们来解析elf格式:

    1. 要读一个扇区(bootloader是第0个扇区ucoreos是第一个扇区
    2. 是否是个valid的ELF?—存着ELF的头(对于段的纪录))

    readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);

    Call哪里去了?

    我们可以知道,它放到eax里面了

    可执行文件的程序头部是一个program header结构的数组,每个结构描述了一个段或者系统准备程序执行所必需的其它信息。目标文件的“段” 包含一个或者多个“节区”(section),也就是“段内容(Segment Contents)” 。程序头部仅对于可执行文件和共享目标文件有意义。

                  // 上面四条指令联合制定了扇区号

                  // 在这4个字节线联合构成的32位参数中

                  //   29-31位强制设为1

                  //   28位(=0)表示访问"Disk 0"

                  //   0-27位是28位的偏移量

    就是Kernel起始位置

    Kernel的汇编代码入口地址也是这个

    Bootloader已经把内核加载过来,解析,把控制权从bootloader转到了ucore

    别忘了返回

     

     

    练习5:实现函数调用堆栈跟踪函数(需要编程)

    我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_stackframe来跟踪函数调用堆栈中记录的返回地址。在如果能够正确实现此函数,可在lab1中执行“make qemu”后,在qemu模拟器中得到类似如下的输出:

    请完成实验,看看输出是否与上述显示大致一致,并解释最后一行各个数值的含义。

     

    实验分析:

                  

    他被什么调用呢?

    往上拉

    是这个

    那么该如何实现?

    首先uint32_t ebp = read_ebp(), eip = read_eip();

    我们需要把ebp eip读出来

    用的是内连汇编:

    Eip同理读返回地址

     

    Ebp有调用关系链,循环打印出来就可以

    Kdebug:

    读进ebp

    打出ebp eip

    把ebp当指针,跳回上一个caller的地方,就是打印下一个ebp

    Ebp不等于0且小于20的时候循环

    20指的就是:

    这个深度,是设置的。

    Ebp=0的时候;

    到头了,当然就停了。

     

     

    练习6:完善中断初始化和处理(需要编程)

    请完成编码工作和回答如下问题:

    中断描述符表(也可简称为保护模式下的中断向量表)中一个表项占多少字节?其中哪几位代表中断处理代码的入口?

    请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。在idt_init函数中,依次对所有中断入口进行初始化。使用mmu.h中的SETGATE宏,填充idt数组内容。每个中断的入口由tools/vectors.c生成,使用trap.c中声明的vectors数组即可。

    请编程完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用print_ticks子程序,向屏幕上打印一行文字”100 ticks”。

     

    实验分析:

    中断向量表一个表项占用8字节,其中2-3字节是段选择子,0-1字节和6-7字节拼成位移,两者联合便是中断处理程序的入口地址。

    IDT的第一项可以包含一个描述符。CPU把中断(异常)号乘以8做为IDT的索引。IDT可以位于内存的任意位置,CPU通过IDT寄存器(IDTR)的内容来寻址IDT的起始地址。指令LIDT和SIDT用来操作IDTR。

    设置好中断向量表

    需要区分外设还是软件中断还是异常,需要设置区分细节

    要求是时钟中断,如何写处理历程?

    1:中断向量表

    在mmu.H里面

    用来定义段描述符的结构体

    Sel选择值

    段内偏移

    中断号0-255分别对应的历程

    覆盖了中断异常系统调用 属性上会有一些差别

    Trap.c里面:init

    Vector.s

    向量表

    这里有个vectors.S 是vector.c生成的

    vector.S文件通过vectors.c 自动生成,其中定义了每个中断的入口程序和入口地址(保存在vectors 数组中)。其中,中断可以分成两类:一类是压入错误编码的(error code),另一类不压入错误编码。对于第二类,vector.S 自动压入一个0。此外,还会压入相应中断的中断号。在压入两个必要的参数之后,中断处理函数跳转到统一的入口alltraps 处。

    最后跳到:

    软件的保存现场与恢复

     有call trap:

     

    初始化在前面trap c

    建立中断向量表

    还需要enable

    使能产生中断

    在哪屏蔽掉的呢?

    Bootloader中cli就把中断disable掉了!之前有在实验报告里写噢!

    等idt_init之后,才开始enabke intr

    之后就可以产生中断了。

     

    在要写的代码部分,能看到提示:

    After setup the contents of IDT, you will let CPU know where is the IDT by using 'lidt' instruction.

    You don't know the meaning of this instruction? just google it! and check the libs/x86.h to know more. Notice: the argument of lidt is idt_pd. try to find it!

    可知应要写成lidt(&ldt_pd)

    使操作系统每遇到 100 次时钟中断后,调用 print_ticks 子程序,向屏幕上打印一行文字”100 ticks”。

    那我来查看函数trap,发现它调用中断处理函数trap_dispatch来处理。

    它又调用:

    具体的中断处理:

    首先建立了表中所有的入口地址

    比如case那一段:

    产生时钟中断打印tricks

    若中断号是IRQ_OFFSET + IRQ_COM1 为串口中断,则显示收到的字符。 
    若中断号是IRQ_OFFSET + IRQ_KBD 为键盘中断,则显示收到的字符。 
    若为其他中断且产生在内核状态,则挂起系统;

    查看函数trap_dispatch,发现在 “case IRQ_OFFSET + IRQ_TIMER:”下方有一段注释,指导我们写这题。

    他说使用变量ticks计数,每达到TICK_NUM次,就调用print_ticks来输出点东西。

    操作系统每遇到100次时钟中断后,调用print_ticks子程序,向屏幕上打印一行文字”100 ticks”。

    结束√

     

     

    扩展练习Challenge 1(需要编程)

    扩展proj4,增加syscall功能,即增加一用户态函数(可执行一特定系统调用:获得时钟计数值),当内核初始完毕后,可从内核态返回到用户态的函数,而用户态的函数又通过系统调用得到内核态的服务(通过网络查询所需信息,可找老师咨询。如果完成,且有兴趣做代替考试的实验,可找老师商量)。需写出详细的设计和分析报告。完成出色的可获得适当加分。

    扩展练习Challenge 2(需要编程)

    用键盘实现用户模式内核模式切换。具体目标是:“键盘输入3时切换到用户模式,键盘输入0时切换到内核模式”。基本思路是借鉴软中断(syscall功能)的代码,并且把trap.c中软中断处理的设置语句拿过来。

    注意:

     1.关于调试工具,不建议用lab1_print_cur_status()来显示,要注意到寄存器的值要在中断完成后tranentry.S里面iret结束的时候才写回,所以再trap.c里面不好观察,建议用print_trapframe(tf)

     2.关于内联汇编,最开始调试的时候,参数容易出现错误,可能的错误代码如下

    要去掉参数int %0 \n这一行

    3.软中断是利用了临时栈来处理的,所以有压栈和出栈的汇编语句。硬件中断本身就在内核态了,直接处理就可以了

     

    实验分析:

    拿到这个题,先分析:

    要做啥?主要的变化是啥:

    Trap

    初始化针对switch to kernel

    那这个函数都有什么呢?

    从内核台切换到用户态:打印当前状态

    切换回去:打印当前状态

    来回切换。

     

    我们来看switch:

    粉字:跨越集的转换,要多存ess esp

    为啥?

    为了恢复的时候正常工作

    Int中断号/系统调用号完成硬件中断跳到case T

     

    看init c:

    要从用户态执行int产生软中断trap.c

    Init里面先把权限打开

    然后

    设成了use_ds,也有esp站的指针,返回地址也没变

    然后会执行trep里面的iret

    从内核态到了用户态了

    Print cur status了

    (打寄存器)

    重点:reg1与3----区分用户态

    低两位0:内核

    低两位是11:用户态

    通过这个来观察当前处于哪个态

    如果可以正常执行——

    Print会在03之间来回切换。

    从低优先级到高优先级的跳变

    设置允许-通过特定的中断号

    其中:

    tapfream:中断帧

    trap.h里面定义结构信息:产生中断之后要保存哪些,回复之后要恢复哪些

    比如时钟中断处理

    比如用户到内核/内核到用户——软中断

     

    六、实验体会和思考题

    一开始配置环境的时候,我想要用mac来配置实验环境的,困难重重。

    收获是我加上了两个12级学长的微信,都是大大大大大佬!

    膜拜。

    然而mit的实验和ucore还是有很大差别,在后面坑了我很久。

    首先,在mac上配置环境的时候,光按照readme操作是远远不够的,他只是用brew来下载了必备资源

    那么,我剩下还要做的:

      • 把mac编译默认的clang换成gcc
      • Macos虽然有gcc,但是是很低版本的,更新,等待,找网络好的地方(唉)
      • 除了gcc,把gdb啊cgdb啊,g++啊等等一系列下载

      • 光下载不够,一定要定义到路径上去,由于我一开始接触这个一脸懵逼,路径定义的很乱,而且是强行写的只读文件。

      • 第一件让我开心的事情发生了,make成功!练习一总算做出来了。
      • 短暂的快乐之后,开始面对debug哭泣。

    报错gnome-terminal command not find *n

    仔细看makefile里面debug这里,

    用到了$(TERMINAL)

    Terminal设置的是ubuntu里面的shell gnome-terminal

    Mac里面没有啊咋办啊啊啊

    第一步,我的想法是,找到mac里面的gnome-terminal是啥,改过去不就可以了

    然而:

    似乎是mac上没有可以replace gnome的,不过底下回答给了一个方案,用iTerm2.

    我下载了,试了,仍然不ok

    说明gnome-terminal根本不是应用程序的名称,是一条指令啊!

    再去疯狂google:

    这位老哥确实能够轻松解决我的问题,但是写在makefile里面的,还用$(TERMINAL)代替就是为了简化代码,用那一大串肯定不方便,而且我刚接触这块,肯定还会出一坨错,不用这个方法。

    最后我选择了

    接触到这个之后才发现这个真的好神奇!

    当然,先把后面的代码改一下:

    就像是直接用英语和机器对话,tell terminal去开一个新的bash去执行gdb。

     

    那为啥我最后还是用了虚拟机呢

    因为用osascript相当于要新打开一个在usr地址里面的新脚本,等于说我还要设置地址!

    这么麻烦!

    不干了。

     

    附录(源代码及注释)

    Kdebug.c:第五题的

    void

    print_stackframe(void) {

         /* LAB1 YOUR CODE : STEP 1 */

         /* (1) call read_ebp() to get the value of ebp. the type is (uint32_t);

          * (2) call read_eip() to get the value of eip. the type is (uint32_t);

          * (3) from 0 .. STACKFRAME_DEPTH

          *   (3.1) printf value of ebp, eip

          *   (3.2) (uint32_t)calling arguments [0..4] = the contents in address (unit32_t)ebp +2 [0..4]

          *   (3.3) cprintf("\n");

          *   (3.4) call print_debuginfo(eip-1) to print the C calling function name and line number, etc.

          *   (3.5) popup a calling stackframe

          *           NOTICE: the calling funciton's return addr eip  = ss:[ebp+4]

          *                   the calling funciton's ebp = ss:[ebp]

          */

        uint32_t ebp = read_ebp(), eip = read_eip();

     

        inti, j;

        for(i = 0; ebp != 0 && i < STACKFRAME_DEPTH; i ++) {

            cprintf("ebp:0x%08x eip:0x%08x args:", ebp, eip);

            uint32_t *args = (uint32_t *)ebp + 2;

            for(j = 0; j < 4; j ++) {

                cprintf("0x%08x ", args[j]);

            }

            cprintf("\n");

            print_debuginfo(eip - 1);

            eip = ((uint32_t *)ebp)[1];

            ebp = ((uint32_t *)ebp)[0];

        }

    }

     

    Trap.c:第六题的

    idt_init(void) {

         /* LAB1 YOUR CODE : STEP 2 */

         /* (1) Where are the entry addrs of each Interrupt Service Routine (ISR)?

          *    All ISR's entry addrs are stored in __vectors. where is uintptr_t __vectors[] ?

          *    __vectors[] is in kern/trap/vector.S which is produced by tools/vector.c

          *    (try "make" command in lab1, then you will find vector.S in kern/trap DIR)

          *    You can use  "extern uintptr_t __vectors[];" to define this extern variable which will be used later.

          * (2) Now you should setup the entries of ISR in Interrupt Description Table (IDT).

          *    Can you see idt[256] in this file? Yes, it's IDT! you can use SETGATE macro to setup each item of IDT

          * (3) After setup the contents of IDT, you will let CPU know where is the IDT by using 'lidt' instruction.

          *    You don't know the meaning of this instruction? just google it! and check the libs/x86.h to know more.

          *    Notice: the argument of lidt is idt_pd. try to find it!

          */

        externuintptr_t __vectors[];

        inti;

        for(i = 0; i < sizeof(idt) / sizeof(structgatedesc); i ++) {

            SETGATE(idt[i], 0, GD_KTEXT, __vectors[i], DPL_KERNEL);

        }

        // set for switch from user to kernel

        SETGATE(idt[T_SWITCH_TOK], 0, GD_KTEXT, __vectors[T_SWITCH_TOK], DPL_USER);

        // load the IDT

        lidt(&idt_pd);

    }

     

    void

    trap(structtrapframe *tf) {

        // dispatch based on what type of trap occurred

        trap_dispatch(tf);

    }

     

    caseIRQ_OFFSET + IRQ_TIMER:

            /* LAB1 YOUR CODE : STEP 3 */

            /* handle the timer interrupt */

            /* (1) After a timer interrupt, you should record this event using a global variable (increase it), such as ticks in kern/driver/clock.c

             * (2) Every TICK_NUM cycle, you can print some info using a funciton, such as print_ticks().

             * (3) Too Simple? Yes, I think so!

             */

            ticks ++;

            if(ticks % TICK_NUM == 0) {

                print_ticks();

            }

            break;

     

    init.c:第七题

    staticvoid

    lab1_switch_to_user(void) {

        //LAB1 CHALLENGE 1 : TODO

        asmvolatile(

            "sub $0x8, %%esp \n"

            "int %0 \n"

            "movl %%ebp, %%esp"

            : 

            : "i"(T_SWITCH_TOU)

        );

    }

     

    staticvoid

    lab1_switch_to_kernel(void) {

        //LAB1 CHALLENGE 1 :  TODO

        asmvolatile(

            "int %0 \n"

            "movl %%ebp, %%esp \n"

            : 

            : "i"(T_SWITCH_TOK)

        );

    }

     

    staticvoid

    lab1_switch_test(void) {

        lab1_print_cur_status();

        cprintf("+++ switch to  user  mode +++\n");

        lab1_switch_to_user();

        lab1_print_cur_status();

        cprintf("+++ switch to kernel mode +++\n");

        lab1_switch_to_kernel();

        lab1_print_cur_status();

    }

     

     

    展开全文
  • 操作系统实验报告

    千次阅读 2020-06-24 12:02:17
    操作系统实验报告 1. myecho.c 1.1. 实验内容 myecho.c的功能与系统echo程序相同 接受命令行参数,并将参数打印出来 1.2. 效果展示 myecho$ ./myecho a b c a b c 1.3. 实验思路和关键代码 读取输入的参数,按...
  • 操作系统实验之系统调用

    千次阅读 2016-09-07 21:54:24
    操作系统实验之系统调用
  • 哈工大操作系统实验手册 实验资源与参考 不配环境懒人福利:实验楼 在线课程:操作系统,李治军,哈工大(网易云课堂) 参考阅读:《Linux内核完全注释》——赵炯,《操作系统原理、实现与实践》——李治军,...
  • 最近在学习清华大学操作系统课程,同时在实验楼做实验 。共9个实验,打算把每次实验过程记录下来。RunNoob!!
  • 操作系统实验报告 lab1

    万次阅读 2017-03-18 10:32:27
    操作系统实验报告lab1
  • 操作系统实验(一)

    万次阅读 2019-03-28 16:46:58
    操作系统实验 hello,我是橘子 最近突然发现我以前写的操作系统实验,然后分享给大家吧,就是自己写的代码也不是很好,希望大家见谅哈 实验目的 一、进程控制 ●基本要求:利用简单的结构和控制方法模拟进程结构、...
  • 操作系统实验五:文件系统

    千次阅读 2019-06-17 03:53:39
    操作系统实验五:文件系统 代码地址点这里 1.函数功能说明 1.1模块说明 format模块 功能: 格式化文件系统,即初始化文件系统,相当于硬盘的格式化。将其中原有的用户及用户下的文件系统全部还原初始状态,即没有...
  • 利用虚拟机搭建Linux操作系统实验环境

    千次阅读 热门讨论 2020-04-29 20:08:38
    利用虚拟机搭建Linux操作系统实验环境目的准备工作创建虚拟机可以用以下三种方法之一来创建虚拟机选择典型版本选择自定义硬件基本配置完成,安装系统 目的 搭建Linux操作系统实验环境,便于实验顺利进行。 准备工作 ...
  • 李治军操作系统实验二——系统调用总结 0、项目情况简介 本人大三,这学期开始学习操作系统,校内的课程目前仅涉及理论所以就在网上找了哈工大李志军老师的操作系统课自学。本文是个人对本次实验的一些总结。 1、...
  • 操作系统实验报告 lab3

    千次阅读 2017-04-24 09:26:24
    操作系统实验报告 lab3
  • 操作系统实验报告 lab4

    千次阅读 2017-04-30 22:44:10
    操作系统实验报告 lab4
  • 阅读分别运行用API接口函数getpid()直接调用和汇编中断调用两种方式调用Linux操作系统的同一个系统调用getpid的程序(请问getpid的系统调用号是多少?linux系统调用的中断向量号是多少?)。 2、上机完成习题1.13。 ...
  • 杭电操作系统实验一报告

    千次阅读 2019-03-24 00:13:22
    按理说操作系统实验应该自己做,这样能锻炼自己。鉴于我的报告还是比较有参考价值,能让以后的同学参考一下,就做成md的形式。仅供参考! 实验一报告 一、实验内容: (1)实验名:Linux内核编译及添加系统调用...
  • 操作系统实验四-页面置换算法实验

    千次阅读 2019-05-27 14:38:41
    操作系统实验四-页面置换算法实验 Y.xj 本实验重点为不同的页面置换算法,所以程序主体部分完全相同 程序总体函数变量如下: 程序实现步骤: 判断是否缺页 判断内存是否存满 若内存不满,则直接插入 若内存满...
  • 操作系统实验报告-系统调用

    千次阅读 2017-03-04 15:23:43
    操作系统实验报告-系统调用 实验内容 在Linux 0.11上添加两个系统调用,并编写两个简单的应用程序测试它们。 iam() 第一个系统调用是iam(),其原型为: int iam(const char * name); 完成的功能是将...
  • 课程名称 操作系统实验 课程编号 201406412 实验项目名称 实验环境的使用 学号 2017201212 班级 20172012 姓名 李博浩 专业 软件工程 学生所在学院 计算机科学与技术学院 指导教师 关键 实验室名称地点 21B476 ...
  • 杭电操作系统实验三报告

    千次阅读 2019-03-24 00:26:43
    按理说操作系统实验应该自己做,这样能锻炼自己。鉴于我的报告还是比较有参考价值,能让以后的同学参考一下,就做成md的形式。 实验三报告 一、实验内容: (1)实验名:Linux进程管理 (2)实验要求: 1)实现一...
  • 在本学期(2017-2018学年第二学期)的操作系统实验课的作业时编写操作系统,在调试过程中遇到了极大问题,下面总结一下debug工具和方法。 我使用的是bochs+nasm+Mingw(主要是其中的gcc、objdump、objcopy、ld)。 ...
  • 参考文章:哈工大李治军老师操作系统实验-系统调用 内核层面的修改: 修改 include/unistd.h 文件添加 __NR_whoami 和 __NR_iam 两个宏 /* 添加系统调用号 */ #define __NR_whoami 72 #define __NR_iam 73 修改...
  • 杭电操作系统实验二报告

    千次阅读 2019-03-24 00:19:55
    按理说操作系统实验应该自己做,这样能锻炼自己。鉴于我的报告还是比较有参考价值,能让以后的同学参考一下,就做成md的形式。仅供参考 一、实验内容: (1)实验名:Linux内核模块编程 (2)实验要求: 1)设计一...
  • 操作系统实验报告——磁盘调度算法 1、实验名称: 磁盘调度算法的实现 2、实验要求: (1)理解磁盘调度的概念, (2)掌握磁盘调度程序的三种算法; (3)用C或C++语言编程实现算法。 3、实验方式: 通过上机,调试...
  • 实验名称:操作系统初步(系统调用实验)了解系统调用不同的封装形式实验原理API系统调用两者联系与区别系统调用号系统调用和函数调用的区别软中断int与函数调用call的区别Task 1实验过程实验结果Task 2实验过程Task...
  • Linux-0.11操作系统实验1-操作系统的引导 Linux-0.11操作系统实验2-系统调用 Linux-0.11操作系统实验3-进程运行轨迹的跟踪与统计 Linux-0.11操作系统实验4-基于内核栈切换的进程切换 Linux-0.11操作系统实验5-信号量...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 33,589
精华内容 13,435
关键字:

操作系统实验