Nachos是什么

Nachos (Not Another Completely Heuristic Operating System),是一个教学用操作系统,提供了操作系统框架:

  1. 线程
  2. 中断
  3. 虚拟内存(位图管理所有物理页,虚拟地址与物理地址之间的转换等)
  4. 同步与互斥机制(锁、条件变量、信号量),读者写者问题,生产者消费者问题,BARRIER问题等
  5. 线程调度(基于优先级可抢占式调度,时间片轮转算法,FIFO调度)
  6. 文件系统
  7. 系统调用
  8. 机器指令、汇编指令、寄存器

    ……

    Nachos模拟了一个MIPS模拟器,运行用户程序。

目录结构

.
├── COPYRIGHT
├── gnu-decstation-ultrix // 交叉编译工具链
├── nachos-3.4.zip // 未经任何修改的源码和交叉编译工具,实验就是修改源码完善各个模块的功能
├── README
└── nachos-3.4 // 实验过程中完善的代码
├── test // 该目录下编写用户自己的程序,需要修改Makfile添加自己的文件
├── bin // 用户自己的程序需要利用coff2noff转换,才能在nachos下跑起来
├── filesys // 文件系统管理
│ ├── directory.cc // 目录文件,由目录项组成,目录项里记录了文件头所在扇区号
│ ├── directory.h
│ ├── filehdr.cc
│ ├── filehdr.h // 文件头数据结构,通过索引记录了文件内容实际存储的所有扇区号
│ ├── filesys.cc // 文件系统数据结构,创建/删除/读/写/修改/重命名/打开/关别等接口
│ ├── filesys.h
│ ├── fstest.cc
│ ├── Makefile
│ ├── openfile.cc // 管理所有打开的文件句柄
│ ├── openfile.h
│ ├── synchdisk.cc // 同步磁盘类,加锁保证互斥和文件系统的一致性
│ ├── synchdisk.h
│ └── test
├── machine // 机器硬件模拟
│ ├── console.cc // 终端
│ ├── console.h
│ ├── disk.cc // 磁盘
│ ├── disk.h
│ ├── interrupt.cc // 中断处理器,利用FIFO维护一个中断队列
│ ├── interrupt.h
│ ├── timer.cc // 模拟硬件时钟,用于时钟中断
│ ├── timer.h
│ ├── translate.cc // 用户程序空间虚拟地址和物理之间的转换类
│ └── translate.h
├── network // 网络系统管理
│ ├── Makefile
│ ├── nettest.cc
│ ├── post.cc
│ ├── post.h
│ └── README
├── threads // 内核线程管理
│ ├── list.cc // 工具模块 定义了链表结构及其操作
│ ├── list.h
│ ├── main.cc // main入口,可以传入argv参数
│ ├── Makefile
│ ├── scheduler.cc // 调度器,维护一个就绪的线程队列,时间片轮转/FIFO/优先级抢占
│ ├── scheduler.h
│ ├── stdarg.h
│ ├── switch.c // 线程启动和调度模块
│ ├── switch.h
│ ├── switch-old.s
│ ├── switch.s // 线程切换
│ ├── synch.cc // 同步与互斥,锁/信号量/条件变量
│ ├── synch.dis
│ ├── synch.h
│ ├── synchlist.cc // 类似于一个消息队列
│ ├── synchlist.h
│ ├── system.cc // 主控模块
│ ├── system.h
│ ├── thread.cc // 线程数据结构
│ ├── thread.h
│ ├── threadtest.cc
│ ├── utility.cc
│ └── utility.h
├── userprog // 用户进程管理
│ ├── addrspace.cc // 为noff文件的代码段/数据段分配空间,虚拟地址空间
│ ├── addrspace.h
│ ├── bitmap.cc // 位图,用于管理扇区的分配和物理地址的分配
│ ├── bitmap.h
│ ├── exception.cc // 异常处理
│ ├── Makefile
│ ├── progtest.cc // 测试nachos是否可执行用户程序
│ └── syscall.h // 系统调用
└── vm // 虚拟内存管理
└── Makefile // 多线程编译: make -j4
└── Makefile.common // 各个模块公共的Makefile内容存放到这里面
└── Makefile.dep // 依赖

环境

选择Linux或Unix系统,安装32位GCC开发环境,安装32的ubuntu。

源码获取

https://github.com/icoty/nachos-3.4-Lab

内容一:总体概述

本次Lab针对的内容是实现线程机制最基本的数据结构——进程控制块(PCB)。当一个进程创建时必然会生成一个相应的进程控制块,记录一些该线程特征,如进程ID、进程状态、进程优先级,进程开始运行时间,在cpu上已经运行了多少时间,程序计数器,SP指针,根目录和当前目录指针,文件描述符表,用户ID,组ID,指向代码段、数据段和栈段的指针等(当然,Nachos简化了进程控制块的内容)。实验的主要内容是修改和扩充PCB,主要难点在于发现修改PCB影响到的文件并进行修改。PCB是系统感知进程存在的唯一标志,且进程与PCB一一对应。可将PCB内部信息划分为:进程描述信息,进程控制信息,进程占有的资源和使用情况,进程的cpu现场。扩展字段如下:

内容二:任务完成情况

任务完成列表(Y/N)

Exercise1 Exercise2 Exercise3 Exercise4
第一部分 Y Y Y Y

具体Exercise的完成情况

Exercise1 调研

调研Linux或Windows中进程控制块(PCB)的基本实现方式,理解与Nachos的异同。

linux-4.19.23调研:Linux中的每一个进程由一个task_struct数据结构来描述。task_struct也就是PCB的数据结构。task_struct容纳了一个进程的所有信息,linux内核代码中的task_struct在linux-4.19.23/include/linux/sched.h内。

Linux内核进程状态:如下可分为运行态,可中断和不可中断态,暂停态,终止态,僵死状态,挂起状态等。

Linux内核进程调度:sched_info数据结构,包括被调度次数,等待时间,最后一次调度时间。

vi linux-4.19.23/include/linux/sched.h

……
/* Used in tsk->state: */
#define TASK_RUNNING 0x0000 // 运行态
#define TASK_INTERRUPTIBLE 0x0001 // 可中断
#define TASK_UNINTERRUPTIBLE 0x0002 // 不可中断
#define __TASK_STOPPED 0x0004
#define __TASK_TRACED 0x0008
/* Used in tsk->exit_state: */
#define EXIT_DEAD 0x0010
#define EXIT_ZOMBIE 0x0020 // 僵死态
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
/* Used in tsk->state again: */
#define TASK_PARKED 0x0040
#define TASK_DEAD 0x0080
#define TASK_WAKEKILL 0x0100
#define TASK_WAKING 0x0200
#define TASK_NOLOAD 0x0400
#define TASK_NEW 0x0800
#define TASK_STATE_MAX 0x1000
……
……
struct sched_info {
#ifdef CONFIG_SCHED_INFO
/* Cumulative counters: */ /* # of times we have run on this CPU: */
unsigned long pcount; /* Time spent waiting on a runqueue: */
unsigned long long run_delay; /* Timestamps: */ /* When did we last run on a CPU? */
unsigned long long last_arrival; /* When were we last queued to run? */
unsigned long long last_queued; #endif /* CONFIG_SCHED_INFO */
};
……

时钟与锁:内核需要记录进程在其生存期内使用CPU的时间以便于统计、计费等有关操作。进程耗费CPU的时间由两部分组成:一是在用户态下耗费的时间,一是在系统态下耗费的时间。这类信息还包括进程剩余的时间片和定时器信息等,以控制相应事件的触发。

文件系统信息:进程可以打开或关闭文件,文件属于系统资源,Linux内核要对进程使用文件的情况进行记录。

虚拟内存信息:除了内核线程,每个进程都拥有自己的地址空间,Linux内核中用mm_struct结构来描述。

物理页管理信息:当物理内存不足时,Linux内存管理子系统需要把内存中部分页面交换到外存,并将产生PageFault的地址所在的页面调入内存,交换以页为单位。这部分结构记录了交换所用到的信息。

多处理器信息:与多处理器相关的几个域,每个处理器都维护了自己的一个进程调度队列,Linux内核中没有线程的概念,统一视为进程。

处理器上下文信息:当进程因等待某种资源而被挂起或停止运行时,处理机的状态必须保存在进程的task_struct,目的就是保存进程的当前上下文。当进程被调度重新运行时再从进程的task_struct中把上下文信息读入CPU(实际是恢复这些寄存器和堆栈的值),然后开始执行。

与Nachos的异同:Nachos相对于Linux系统的线程部分来讲,要简单许多。它的PCB仅有几个必须的变量,并且定义了一些最基本的对线程操作的函数。Nachos线程的总数目没有限制,线程的调度比较简单,而且没有实现线程的父子关系。很多地方需要完善。

Exercise2 源代码阅读

code/threads/main.cc:main.cc是整个nachos操作系统启动的入口,通过它可以直接调用操作系统的方法。通过程序中的main函数,配以不同的参数,可以调用Nachos操作系统不同部分的各个方法。

code/threads/threadtest.cc:nachos内核线程测试部分,Fork两个线程,交替调用Yield()主动放弃CPU,执行循环体,会发现线程0和线程1刚好是交替执行。

int main(int argc, char **argv)
{
int argCount; // the number of arguments
DEBUG('t', "Entering main");
(void) Initialize(argc, argv);
#ifdef THREADS
for (argc--, argv++; argc > 0; argc -= argCount, argv += argCount) {
argCount = 1;
switch (argv[0][1]) {
case 'q':
testnum = atoi(argv[1]);
argCount++;
break;
case 'T':
if(argv[0][2] == 'S')
testnum = 3;
break;
default:
testnum = 1;
break;
}
}
ThreadTest();
#endif
……
} threadtest.cc
// 线程主动让出cpu,在FIFO调度策略下能够看到多个线程按顺序运行
void SimpleThread(int which)
{
for (int num = 0; num < 5; num++) {
int ticks = stats->systemTicks - scheduler->getLastSwitchTick();
// 针对nachos内核线程的时间片轮转算法,判断时间片是否用完,如果用完主动让出cpu
if(ticks >= TimerSlice){
currentThread->Yield();
} // 多个线程同时执行该接口的话,会交替执行,交替让出cpu
// currentThread->Yield();
}
} root@yangyu-ubuntu-32:/mnt/nachos-3.4/code/threads#
root@yangyu-ubuntu-32:/mnt/nachos-3.4/code/threads# ./nachos -q 1
userId=0,threadId=0,prio=5,loop:0,lastSwitchTick=0,systemTicks=20,usedTicks=20,TimerSlice=30
userId=0,threadId=1,prio=5,loop:0,lastSwitchTick=20,systemTicks=30,usedTicks=10,TimerSlice=30
userId=0,threadId=0,prio=5,loop:1,lastSwitchTick=30,systemTicks=40,usedTicks=10,TimerSlice=30
userId=0,threadId=1,prio=5,loop:1,lastSwitchTick=40,systemTicks=50,usedTicks=10,TimerSlice=30
userId=0,threadId=0,prio=5,loop:2,lastSwitchTick=50,systemTicks=60,usedTicks=10,TimerSlice=30
userId=0,threadId=1,prio=5,loop:2,lastSwitchTick=60,systemTicks=70,usedTicks=10,TimerSlice=30
userId=0,threadId=0,prio=5,loop:3,lastSwitchTick=70,systemTicks=80,usedTicks=10,TimerSlice=30
userId=0,threadId=1,prio=5,loop:3,lastSwitchTick=80,systemTicks=90,usedTicks=10,TimerSlice=30
userId=0,threadId=0,prio=5,loop:4,lastSwitchTick=90,systemTicks=100,usedTicks=10,TimerSlice=30
userId=0,threadId=1,prio=5,loop:4,lastSwitchTick=100,systemTicks=110,usedTicks=10,TimerSlice=30
No threads ready or runnable, and no pending interrupts.
Assuming the program completed.
Machine halting! Ticks: total 130, idle 0, system 130, user 0
Disk I/O: reads 0, writes 0
Console I/O: reads 0, writes 0
Paging: faults 0
Network I/O: packets received 0, sent 0 Cleaning up...

code/threads/thread.h:这部分定义了管理Thread的数据结构,即Nachos中线程的上下文环境。主要包括当前线程栈顶指针,所有寄存器的状态,栈底,线程状态,线程名。当前栈指针和机器状态的定义必须必须放作为线程成员变量的前两个,因为Nachos执行线程切换时,会按照这个顺序找到线程的起始位置,然后操作线程上下文内存和寄存器。在Thread类中还声明了一些基本的方法,如Fork()、Yield()、Sleep()等等,由于这些方法的作用根据名字已经显而易见了,在此不再赘述。

code/threads/thread.cc: Thread.cc中主要是管理Thread的一些事务。主要接口如下:

  • Fork(VoidFunctionPtr func,int arg):func是新线程运行的函数,arg是func函数的入参,Fork的实现包括分为几步:分配一个堆栈,初始化堆栈,将线程放入就绪队列。
  • Finish():不是直接收回线程的数据结构和堆栈,因为当前仍在这个堆栈上运行这个线程。先将threadToBeDestroyed的值设为当前线程,在Scheduler的Run()内切换到新的线程时在销毁threadToBeDestroyed。Yield()、Sleep()。这里实现的方法大多是都是原子操作,在方法的一开始保存中断层次关闭中断,并在最后恢复原状态。
  • Yield():当前线程放入就绪队列,从scheduler就绪队列中的找到下一个线程上cpu,以达到放弃CPU的效果。

Exercise3 扩展线程的数据结构

Exercise4 增加全局线程管理机制

这里我把Exercise3和Exercise4放在一起完成。

  • 在Thread类中添加私有成员userId和threadId,添加公有接口getUserId()和getThreadId(),userId直接沿用Linux个getuid()接口。

  • system.h内部添加全局变量maxThreadsCount=128,全局数组threads[maxThreadsCount],每创建一个线程判断并分配threadId。

  • -TS模仿Linux的PS命令打印所有线程信息,仔细阅读list.cc代码和scheduler.cc的代码,就会发现可以直接用scheduler.cc::Print()接口,不用我们重新造轮子。

  • 在system.cc中的void Initialize(int argc, char argv)函数体对全局数组初始化。如下我用root用户执行分配的userId为0,切换到其他用户userId会发生变化,线程id分别为0和1。当线程数超过128个线程时,ASSERT断言报错。

threadtest.cc:
void ThreadTest()
{
switch (testnum) {
case 1:
ThreadTest1();
break;
case 2:
ThreadCountLimitTest();
break;
case 3:
ThreadPriorityTest();
break;
case 4:
ThreadProducerConsumerTest();
break;
case 5:
ThreadProducerConsumerTest1();
break;
case 6:
barrierThreadTest();
break;
case 7:
readWriteThreadTest();
break;
default:
printf("No test specified.\n");
break;
}
} // 线程最多128个,超过128个终止运行
void ThreadCountLimitTest()
{
for (int i = 0; i <= maxThreadsCount; ++i) {
Thread* t = new Thread("fork thread");
printf("thread name = %s, userId = %d, threadId = %d\n", t->getName(), t->getUserId(), t->getThreadId());
}
} root@yangyu-ubuntu-32:/mnt/nachos-3.4/code/threads# ./nachos -TS
thread name = fork thread, userId = 0, threadId = 1
thread name = fork thread, userId = 0, threadId = 2
thread name = fork thread, userId = 0, threadId = 3
……
thread name = fork thread, userId = 0, threadId = 122
thread name = fork thread, userId = 0, threadId = 123
thread name = fork thread, userId = 0, threadId = 124
thread name = fork thread, userId = 0, threadId = 125
thread name = fork thread, userId = 0, threadId = 126
thread name = fork thread, userId = 0, threadId = 127
allocatedThreadID fail, maxThreadsCount:[128]
Assertion failed: line 73, file "../threads/thread.cc"
Aborted (core dumped)
root@yangyu-ubuntu-32:/mnt/nachos-3.4/code/threads#

内容三:遇到的困难以及解决方法

困难1

开始make编译出错,通过定位到具体行,复制出来手动执行,发现是gcc交叉编译工具链路径不对。

困难2

刚开始修改代码验证效果,重定义错误,外部文件全局变量使用方式不对导致。

内容四:收获及感想

动手实践很重要,不管你是做什么事、什么项目、什么作业,一定要落实到代码和跑到程序上面来。绝知此事要躬行,学习来不得半点虚假。

内容五:对课程的意见和建议

暂无。

内容六:参考文献

暂无。

Nachos-Lab1-完善线程机制的更多相关文章

  1. 一步一步掌握java的线程机制(一)----创建线程

    现在将1年前写的有关线程的文章再重新看了一遍,发现过去的自己还是照本宣科,毕竟是刚学java的人,就想将java的精髓之一---线程进制掌握到手,还是有点难度.等到自己已经是编程一年级生了,还是无法将 ...

  2. Vue.js线程机制问题还是数据双向绑定有延迟的问题

    最近用select2做一个下拉多选,若只是从后端获取一个列表渲染还好说,没有任何问题.但要用select2对数据初始化时进行selected的默认选项进行显示,就出现问题了. vm.$set('are ...

  3. WebClient.DownloadFile(线程机制,异步下载文件)

    线程机制(避免卡屏),异步下载文件. 我做网站的监控,WebClient.DownloadFile这个方法是我经常用到的,必要的时候肯定是要从网上下载些什么(WebRequest 也可以下载网络文件, ...

  4. Java多线程与并发库高级应用-传统线程机制回顾

    1.传统线程机制的回顾 1.1创建线程的两种传统方式 在Thread子类覆盖的run方法中编写运行代码 // 1.使用子类,把代码放到子类的run()中运行 Thread thread = new T ...

  5. 从setTimeout到浏览器线程机制

    看高性能javascipt 这本书时,看到这么一句话: Putting scripts at the top of the page in this way typically leads to a ...

  6. java中线程机制

    java中线程机制,一开始我们都用的单线程.现在接触到多线程了. 多线性首先要解决的问题是:创建线程,怎么创建线程的问题: 1.线程的创建: 四种常用的实现方法 1.继承Thread. Thread是 ...

  7. 线程机制、CLR线程池以及应用程序域

    最近在总结多线程.CLR线程池以及TPL编程实践,重读一遍CLR via C#,比刚上班的时候收获还是很大的.还得要多读书,读好书,同时要多总结,多实践,把技术研究透,使用好. 话不多说,直接上博文吧 ...

  8. Java线程机制学习

    前面的文章中总结过Java中用来解决共享资源竞争导致线程不安全的几种常用方式: synchronized: ReentrantLock: ThreadLocal: 这些都是在简单介绍了基本用法的基础上 ...

  9. 从swing分发线程机制上理解多线程[转载]

    本文参考了 http://space.itpub.net/13685345/viewspace-374940,原文作者:javagui 在多线程编程当中,总会提到图形编程,比如java中的swing, ...

随机推荐

  1. 卸载sql server 2008

    一.    SQL2008卸载. 1.从控制面板卸载 1)点击计算机右下角“开始”,点击“控制面板” 2)点击“卸载程序”. 3)在程序列表中找到“Microsoft SQL Server 2008” ...

  2. Django的urls(路由)

    目录 Django的urls(路由) 正则表达式详解 路由匹配(分组匹配) 无名分组 有名分组 反向解析 无名分组反向解析 有名分组反向解析 路由分发 名称空间 虚拟环境 伪静态 Django的url ...

  3. Restful API及接口安全

    一.简介 REST(Representational State Transfer,具体状态转移),是一种基于HTTP协议.URI(统一资源定位符).JSON和XML这些现有协议与标准的,针对网络应用 ...

  4. markdown基本语法教程

    标题 一级标题 二级标题 三级标题 以此类推,总共六级标题,建议在警号后面加一个空格,这是最标准的markdown语法 列表 在markdown下: 列表的显示只需要在文字前加上-.+或*即可变为无序 ...

  5. v-show和element中表单验证validate起到的化学反应

    说起v-show和v-if,进行前端开发的大家一定不会陌生,他们都是用来控制标签元素的显示与隐藏的,他们的区别就是v-show会把标签渲染出来,只是会隐藏起来,相当于visibility:hidden ...

  6. Shiro登录身份认证(从SecurityUtils.getSubject().login(token))到Realm的doGetAuthenticationInfo

    ssm框架下,controller接收到登录请求交给Service并开始处理流程: 1.Service的login方法: @Service public class SysUserServiceImp ...

  7. C# WebApi的controller中如何存取session

    在MVC以后,Session方式可能已经不太常用,但偶尔还是会用到,比如页面验证码之类的.例如登录页面使用的验证码通过Controller提供一个View来实现,可以使用Session来存储这个值.但 ...

  8. django+celery+ RabbitMQ实现异步任务实例

    背景   django要是针对上传文件等需要异步操作的场景时,celery是一个非常不错的选择.笔者的项目就是使用了这个组合,这里就做一个备忘吧. 安装RabbitMQ   这个安装及使用我已经在前一 ...

  9. 创建用户(adduser和useradd)和删除用户(userdel)及

    一  用户创建命令: # adduser  用户名 # useradd  用户名 1) useradd 与 adduser 的区别 在CentOs系统中: useradd与adduser是没有区别的, ...

  10. ThinkPHP 3.2 生成静态页面

    1:在根目录下的全局index.php中加下面这行: define('HTML_PATH', './htm');//生成静态页面的文件位置 2:在项目的配置文件config.php中加下面这行: 'H ...