文章目录
- 写在前面
- 运行环境配置
- 最大线程限制
- 实现可回收的线程ID机制
- 基于优先级的先来先服务调度算法
修改完毕的nachos我已经上传了,需要的话可以点击这里下载,积分不够可以私信我,CSDN设置的积分会自己改,我也没办法。
注意·:在Ubuntu18下能直接使用,其他版本的Ubuntu需要替换除了code外的所有文件,或者可以将链接里的code拷贝到自己的nachos上。
一.写在前面
写这篇博客初衷主要是因为最近在进行操作系统的实验,在网上找了很多教程,但都有些问题,要么就是根本实现不了,要么讲述的不够详细,对于没有面向对象编程基础的小白很不友好。于是就萌发出了写这篇博客的想法,在这篇博客中我会尽可能详细的讲述每一个实现步骤。即使是没有面向对象编程基础的小白,只要认真的跟着步骤来也能成功。如果有幸对您有帮助的话,还望不吝点赞收藏支持一下!
由于是面向小白的,所以在讲述的过程难免有些冗杂,还望谅解,已经有面向对象编程基础的朋友可以直接跳过讲解部分去看具体的实现。
关于nachos,它是一个可修改和跟踪的操作系统教学软件,它给出了一个支持多线程和虚拟内存的操作系统骨架,本篇博客主要介绍了它的线程部分的运行机制,并且进行了一些简单修改,实现了为nachos线程赋予ID号,并加入线程数量限制,在本篇中限制为128。同时在最后实现了基于优先级的先来先服务调度算法
二.运行环境配置
我是在Ubuntu18.04的版本下进行的nachos实验,关于Ubuntu18下安装nachos的方法,可以参考:Ubuntu18下安装nachos
值得注意的是nachos源码已经是被修改过的,建议直接下载使用。否则可能会出现一些未知的错误。当然此次实验不一定要在Ubuntu18下进行,实测Ubuntu版本并不影响实验结果。顺带一提,张老板是个坑。运行环境配置部分可以跳过,当然能配置好更好。
vim的配置
vim是一个很好用的工具,初学者可能会觉得不方便,但当你习惯了之后你一定会深深的爱上它。这里提供了一些简单的vim配置,包括自动缩进,语法高亮括号自动补全等。需要的朋友可以自行取用。
"All system-wide defaults are set in $VIMRUNTIME/debian.vim and sourced by
" the call to :runtime you can find below. If you wish to change any of those
" settings, you should do it in this file (/etc/vim/vimrc), since debian.vim
" will be overwritten everytime an upgrade of the vim packages is performed.
" It is recommended to make changes after sourcing debian.vim since it alters
" the value of the 'compatible' option.
" This line should not be removed as it ensures that various options are
" properly set to work with the Vim-related packages available in Debian.
runtime! debian.vim
" Uncomment the next line to make Vim more Vi-compatible
" NOTE: debian.vim sets 'nocompatible'. Setting 'compatible' changes numerous
" options, so any other options should be set AFTER setting 'compatible'.
"set compatible
" Vim5 and later versions support syntax highlighting. Uncommenting the next
" line enables syntax highlighting by default.
if has("syntax")
syntax on
endif
" If using a dark background within the editing area and syntax highlighting
" turn on this option as well
"set background=dark
" Uncomment the following to have Vim jump to the last position when
" reopening a file
"if has("autocmd")
" au BufReadPost * if line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g'\"" | endif
"endif
" Uncomment the following to have Vim load indentation rules and plugins
" according to the detected filetype.
"if has("autocmd")
" filetype plugin indent on
"endif
" The following are commented out as they cause vim to behave a lot
" differently from regular Vi. They are highly recommended though.
"set showcmd " Show (partial) command in status line.
"set showmatch " Show matching brackets.
"set ignorecase " Do case insensitive matching
"set smartcase " Do smart case matching
"set incsearch " Incremental search
"set autowrite " Automatically save before commands like :next and :make
"set hidden " Hide buffers when they are abandoned
"set mouse=a " Enable mouse usage (all modes)
" Source a global configuration file if available
if filereadable("/etc/vim/vimrc.local")
source /etc/vim/vimrc.local
endif
" 显示行号
set nu
" 自动缩进
set autoindent
" c语音的缩进
set cindent
" 颜色主题
colorscheme torte
" tab键的宽度
set tabstop=4
" 统一缩减为4
set softtabstop=4
set shiftwidth=4
" 浅色显示当前行
autocmd InsertLeave * se nocul
autocmd InsertEnter * se cul
" 在右下角显示光标位置的状态行
set ruler
" 光标移动到buffer的顶部和底部时保持3行的距离
set scrolloff=5
" 滚动光标
set cursorline
" 历史行数
set history=512
" 覆盖文件时不备份
set nobackup
" 侦测文件类型
filetype on
" 为特定文件类型载入相关缩减文件
filetype indent on
:inoremap ( ()<ESC>i
:inoremap ) <c-r>=ClosePair(')')<CR>
:inoremap { {<CR>}<ESC>O
:inoremap } <c-r>=ClosePair('}')<CR>
:inoremap [ []<ESC>i
:inoremap ] <c-r>=ClosePair(']')<CR>
:inoremap " ""<ESC>i
:inoremap ' ''<ESC>i
function! ClosePair(char)
if getline('.')[col('.') - 1] == a:char
return "\<Right>"
else
return a:char
endif
endfunction
filetype plugin indent on
"打开文件类型检测, 加了这句才可以用智能补全
"set completeopt=longest,menu
将以上的代码覆盖原来的vimrc就可以了。vimrc的路径是 /etc/vim/vimrc
当然也可以不配置,直接使用gedit
三.最大线程限制
nachos默认不对线程数进行限制,但在实际的操作系统中,如果不对线程数量进行限制,那极有可能导致整个系统的崩溃。所以在这一部分,我会详细的讲述如何实现将线程数量限制。
首先我们要明白,nachos只是模拟了一个操作系统,在这个系统中,每一个线程其实就是一个线程类定义出来的对象。不明白类和对象的意义的话,可以简单的将类理解为一个更高级的结构体(结构体内只有数据成员,而类里面可以有函数成员,也可以叫成员方法),而对象就是通过这个类定义出来的一个变量。
每一个类都有两个特殊的成员函数,他们分别是构造函数和析构函数,用来初始化对象和在对象的生命周期结束时用来做一些清理工作。
构造函数名与类名相同,析构函数名则是~+类名的形式。构造函数与析构函数都不能由用户调用,它只能由系统在定义对象是自动调用。显而易见,我们要对进程数量限制就要从构造函数入手,在构造一个线程之前先判断当前线程数量是否已经超过了限制,如果超过了就阻止创建。而析构函数的作用则是在一个线程生命周期结束时,将当前线程数量减一。
在nachos中,Thread类的的构造函数会对一个类的对象进行初始化,例如赋予线程的名字。我们要做的就是修改构造函数的内容,使之在每次调用时都判断当前的线程数量是否已经超过了限制,如果是,就打断创建并报错,此外析构函数也要做修改。
第一步.重载构造函数
首先在thread.h里定义一个宏变量MaxThreadNum,顾名思义,这个变量就是我们要限制的线程数。建议写在class Thread之前。同时定义一个枚举变量类型ThreadLevel,作用后面会讲。
#define MaxThreadNum 128
enum ThreadLevel{KERNEL,USER}; //注意大小写
找到Thread类的声明,在public:后增加一个静态变量ThreadNum用于记录当前线程的数量。同时增加一个ThreadLevel类型的变量threadlevel用于记录线程级别(属于内核线程还是用户线程)。并且将重载构造函数声明
class Thread {
private:
// NOTE: DO NOT CHANGE the order of these first two members.
// THEY MUST be in this position for SWITCH to work.
int *stackTop; // the current stack pointer
void *machineState[MachineStateSize]; // all registers except for stackTop
public:
Thread(char* debugName); // initialize a Thread
~Thread(); // deallocate a Thread
// NOTE -- thread being deleted
// must not be running when delete
// is called
//以下是修改部分
static int ThreadNum;
ThreadLevel threadlevel;//定义了一个名为threadlevel的枚举变量,它的值只能是KERNEL或者USER
Thread(char* debugName,ThreadLevel threadlevel);//重载了构造函数
//当只有一个参数时调用原来的构造函数
//当有两个参数时调用重载后的构造函数
//以上是修改部分
// basic thread operations
void Fork(VoidFunctionPtr func, void *arg);
// Make thread run (*func)(arg)
void Yield(); // Relinquish the CPU if any
// other thread is runnable
void Sleep(bool finishing); // Put the thread to sleep and
// relinquish the processor
void Begin(); // Startup code for the thread
void Finish(); // The thread is done executing
void CheckOverflow(); // Check if thread stack has overflowed
void setStatus(ThreadStatus st) { status = st; }
char* getName() { return (name); }
void Print() { cout << name; }
void SelfTest(); // test whether thread impl is working
private:
// some of the private data for this class is listed above
int *stack; // Bottom of the stack
// NULL if this is the main thread
// (If NULL, don't deallocate stack)
ThreadStatus status; // ready, running or blocked
char* name;
void StackAllocate(VoidFunctionPtr func, void *arg);
// Allocate a stack for thread.
// Used internally by Fork()
// A thread running a user program actually has *two* sets of CPU registers --
// one for