data/attach/1907/z1diwyihlzjzniqwh4f9hfcaxpvwiks6.jpg
本次Lab针对的内容是实现线程机制最基本的数据结构——进程控制块(PCB)。当一个进程创建时必然会生成一个相应的进程控制块,记录一些该线程特征,如进程的标示符、状态、相应的程序和数据地址、资源清单等(当然,Nachos简化了进程控制块的内容)。实验的主要内容是修改和扩充PCB,主要难点在于发现修改PCB影响到的文件并进行修改。
另一个存在的困难就是对C++语言的语法不熟悉,需要补一些语法课。
【用简洁的语言描述本次lab的主要内容;阐述本次lab中涉及到的重要的概念,技术,原理等,以及其他你认为的最重要的知识点。这一部分主要是看大家对lab的总体的理解。
要求:简洁,不需要面面俱到,把重要的知识点阐述清楚即可。】
Exercise1
Exercise2
Exercise3
Exercise4
完成情况
Y
Y
Y
Y
Exercise1
调研Linux或Windows中进程控制块(PCB)的基本实现方式,理解与Nachos的异同。
我选择了开源的Linux作为调研内容。在Linux中的每一个进程由一个task_struct数据结构来描述。task_struct就是通常所说的进程控制块(PCB)。task_struct容纳了一个进程的所有信息,是对系统进程进行控制的唯一手段,也是最有效的手段。task_struct存放在/include/linux/sched.h中。
我的调研选择了Linux-3.5.4版本阅读。并参考了网上的一些博客。Linux系统的PCB包括了很多参数,主要的参数有:
1) 进程状态
进程状态是进程调度和交换的依据。Linux下进程设置了5种状态。分别是运行态、可中断态、不可中断态、僵尸状态、暂停态。
2) 进程调度信息
调度程序利用这些信息决定下一个应该运行的进程。这部分信息包括进程的类别(普通进程还是实时进程)、进程的优先级等。
3) 标识符
每个进程都有很多标识符来标识它,标识符有进程标识符、用户标识符、组标识符、备份用户标识符、文件系统用户标识符等。标识符可以用于控制进程对系统中文件和设备的访问。
4) 进程通信相关信息
Linux支持多种不同形式的通信机制。它支持典型的Unix通信机制:信号、管道,也支持System V通信机制:共享内存、信号量和消息队列。
5) 进程链接信息
Linux中进程有继承关系。除了初始化进程init,其他进程都有一个父进程。每个进程可以通过fork()或clone()系统调用来创建子进程,除了进程标识符等必要的信息外,子进程的task_struct结构中的绝大部分信息都是从父进程中拷贝过来的。系统记录这种父/子、兄/弟关系,使进程间的协作更加方便。task_struct中有许多指针,通过这些指针,系统中所有的task_struct结构就构成了一颗进程树。
6) 时间和定时器信息
内核需要记录进程在其生存期内使用CPU的时间以便用于统计、计费等有关操作。进程耗费CPU的时间由两部分组成:一是在用户态下耗费的时间,一是在系统态下耗费的时间。这类信息还包括进程剩余的时间片和定时器信息等,以控制相应事件的触发。
7) 文件系统信息
进程可以打开或关闭文件,文件属于系统资源,Linux内核要对进程使用文件的情况进行记录。
8) 虚拟内存信息
除了内核线程,每个进程都拥有自己的地址空间,用mm_struct来描述。
9) 页面管理信息
当物理内存不足时,Linux内存管理子系统需要把内存中部分页面交换到外存,其交换是以页为单位的。这部分结构记录了交换所用到的信息、
10) 对称多处理器信息
与多处理器相关的几个域。
11) 处理器上下文信息
当进程暂时停止运行时,处理机的状态必须保存在进程的task_struct。当进程被调度重新运行时再从中恢复这些环境,也就是恢复这些寄存器和堆栈的值。
12) 其他
记录一些其他的必要信息。
Nachos相对与Linux系统的线程部分来讲,要简单许多。它的PCB仅有几个必须的变量,并且定义了一些最基本的对线程操作的函数。Nachos线程的总数目没有限制,线程的调度比较简单,而且没有实现线程的父子关系等。很多地方需要我们进行完善。
Exercise2
仔细阅读下列源代码,理解Nachos现有的线程机制。
code/threads/main.cc和code/threads/threadtest.cc
code/threads/thread.h和code/threads/thread.cc
main.cc
main.cc是整个操作系统kernel启动的入口,通过它可以直接调用操作系统的方法。通过程序中的main函数,配以不同的参数,可以调用Nachos操作系统不同部分的各个方法。Execise4中添加TS操作就要从整个函数入手。
threadtest.cc
这是一个简单的线程实验的测试用例。用于指导我们如何对线程的修改进行测试的。其中比较重要的变量有:
testnum:测试号,对应相应的测试函数。
SimpleThread():一个5次循环的程序,每次循环中都让出CPU,让其他就绪的线程执行。
ThreadTest1():一个测试方法,创建两个线程,让他们都执行SimpleThread()方法,使这两个线程可以交替执行。
ThreadTest():可以看做一个总控程序,根据main函数传过来testnum参数值来执行不同的测试程序。例如,当testnum==1时,就执行ThreadTest1()。
Thread.h
定义了管理Thread的数据结构,即Nachos中线程的上下文环境,都被定义在Thread.h中。主要包括当前线程栈顶指针、所有寄存器的状态、栈低、线程状态、名字。当前栈指针和机器状态的定义必须按指定位置,因为Nachos在线程切换时,会按照预先指定的顺序操作线程上下文内存和寄存器。
在Thread类中还声明了一些基本的方法,如Fork()、Yield()、Sleep()等等,由于这些方法的作用根据名字已经显而易见了,在此不再赘述。
ThreadStatus:线程的状态,有刚创建状态、运行状态、就绪状态和阻塞状态这四个状态。
Thread.cc
Thread.cc中主要是管理Thread的一些事务。主要包括了四个主要的方法。Fork()、Finish()、Yield()、Sleep()。在Thread.h中对它们进行了声明,在Thread.cc中则负责具体的实现。注意到,这里实现的方法大多是都是原子操作,在方法的一开始保存中断层次关闭中断,并在最后恢复原状态。
Thread():构造函数,初始化一个新的Thread。
Fork(VoidFunctionPtr func,int arg):func,新线程运行的函数;arg,func函数的参数。它的实现包括一下几步:分配一个堆栈、初始化堆栈、将线程放入就绪队列。
Finish():并不是直接收回线程的数据结构和堆栈,因为我们仍在这个堆栈上运行这个线程。做法是将threadToBeDestroyed的值设为当前线程,使得Scheduler的Run()可以调用销毁程序,当我们这个程序退出上下文时,将其销毁。
Yield():调用scheduler找到就绪队列中的下一个线程,并让其执行。以达到放弃CPU的效果。
Exercise3
增加“用户ID、线程ID”两个数据成员,并在Nachos现有的线程管理机制中增加对这两个数据成员的维护机制。
增加用户ID:
设计思路:
现有的Nachos代码不支持多用户,我获取当前Linux系统的用户ID作为Nachos的用户ID。
对代码进行的修改:(截图见增加线程ID实验)
在thread.h文件的thread类声明中添加私有的int型成员变量userID、添加公有的成员方法getUserID()用于获取当前的userID。
在thread.cc文件的开始位置,引入头文件“unistd.h”。它是C和C++中提供的对POSIX操作系统的访问功能的头文件,这样,我们就可以在Nachos中直接获取当前Linux的用户ID了。此后在Thread的构造函数中添加语句userID=getuid(),即可获取当前Linux系统的用户ID。在thread.cc文件中还实现了getUserID()方法,这个方法很简单,return一个userID即可。
增加线程ID:
设计思路:
声明一个全局的数组,定义这个数据有128个元素。每个元素的取值为0或1。0表示该数组下标没有作为线程的ID分配出去,1表示以分配。每次创建一个新线程的时候从1开始遍历此数组,得到的第一个值为0元素的数组下标作为线程的ID分配给将要创建的线程。如果遍历一遍数组发现没有可用的ID了,则不创建该线程,并输出提示。另外,由于系统的全局变量都声明和定义在system.h或system.cc中,我们也将该数组定义在这里。
对代码进行的修改:
在system.h中,声明可以创建线程数的最大值MaxThread为128、并声明一个threadIDs[]数组用于记录分配和未分配的线程ID数值。
在system.cc的Initialize()中,定义threadIDs[MaxThread]数组,并将其值都初始化为0。
在thread.h文件的thread类声明中添加int型的私有成员变量threadID、添加公有的成员方法getThreadID()用于获取当前的threadID。
在thread.cc文件中,定义allocatedThreadID()方法。该方法每次从1开始遍历threadIDs数组,发现的第一个值为零的元素下标作为线程的ID。如果没有可分配的ID,则返回-1。同时,在thread构造函数的一开始,用flag接收一个allocatedThreadID()返回的线程ID。如果为-1,则输出提示,停止创建过程。否则继续创建该线程。并在析构函数中将完成的线程ID的对应的数组值重新赋为0。
实验结果截图:
测试代码截图如下:
SimpleThread()也进行了相应的修改:
执行结果如下图:
Exercise4
在Nachos中增加对线程数量的限制,使得Nachos中最多能够同时存在128个线程;
仿照Linux中PS命令,增加一个功能TS(Threads Status),能够显示当前系统中所有线程的信息和状态。
使Nachos最多能同时存在128个线程:
设计思路:
根据上一个实验。为每个线程分配了一个ID。最大值仍设为128。如果线程数超过128则返回ID为-1。因此在创建新线程时进行一次判断,如果返回的ID为-1,则停止创建即可。
对代码进行的修改:
在Exercise3的基础上,修改thread.cc文件。关键在ASSERT(flag!=-1)。
修改ThreadTest.cc中的ThreadLimit()循环创建128个线程,如果超过则判断。
修改ThreadTest.cc中的ThreadTest()。使之得到调用ThreadLimit()的接口:
实验结果截图:
命令行输入“./nachos –q 2”观察实验结果。
增加TS功能:
设计思路:
当前的Nachos操作系统中只有READY队列,所以要输出系统中所有的线程,只需要输出READY队列,即可,需要注意的是,总是有一个线程处于正在执行的状态。所以输出中要把这个线程也加上。
通过阅读代码和查资料了解到,readyList是定义在scheduler.cc中的,但是它是一个私有类型的变量,所以要添加一个公有的get方法获得它。
同时了解到list.cc中有一个Mapcar(func)的方法。可以对List中的每个元素调用func方法。
对代码进行的修改:
在main.cc中,添加相应方法,使得系统能识别TS命令,在其中将testnum设为3。
在threadtest.cc中。将testnum==3,跳转到ThreadTest2()方法中。(比较简单不再截图)
ThreadTest2()中创建了3个线程,每个线程都执行ReadyListPrint()方法。
ReadyListPrint()的主要作用是打印出该线程正在执行时的状态,包括打印出当前线程和readyList中的线程。
这里用到了关中断,以防过程中被其他线程中断。
Mapcar会对队列中的每个item对象执行MyThreadPrint()方法。
在thread.cc中添加MyThreadPrint()的定义,首先将item强制类型转换为Thread类型。之后打印其相关内容。
同时,由于Status是一个枚举类型,要输出其字符串型的内容有些困难,通过添加一个字符串类型的数组,才将枚举的变量以字符串类型输出。
在thread.h中,定义了该字符串数组。
实验结果截图:
可以看到,一共有三个线程,一个线程处在RUNNING状态,另外两个线程则处于READY状态。
【对于阅读代码类的exercise,请对其中你认为重要的部分(比如某文件,或某个类、或某个变量、或某个函数……)做出说明。
对于要编程实现的exercise,请对你增加或修改的内容作出说明。如果增加或修改了某个类,请写出这个类写在那个文件中,它的功能是什么,你自己添加或修改的成员变量以及成员函数有哪些,它们的功能是什么;特别的,对于复杂的函数,请说明你的实现方法。不需要贴具体的实现代码。
要求:表述清楚即可, 可采用图表来辅助说明,不需要贴代码】
困难1
对C++语言不熟悉。
对C++的很多语法掌握的不好,大概的能看懂,读代码理解还可以,自己写就不太轻松了,很多地方都是对比着Nachos现有的代码的实现方法,比葫芦画瓢写的,所有造成运行的时候有很多Bug,调起来很非脑筋。整个实验的思路还是比较简单的,只是编程实现起来有些困难,需要不停的百度Bug的解决方法。
最显著的困难出现在C++的指针部分,虽然仍是可以理解代码的意思,但是遇到指针还是会很困惑。另一方面就是对C++ 的工程不是很熟悉。
解决这个问题我基本上考的是参考Nachos代码的写法,或者百度。特别难理解的知识点就翻翻书来看。基本上经过一次实习对C++的语法已经熟悉了不少。
困难2
刚开始对Nachos Threads的各个类的功能不太熟悉,哪一个变量或方法应该添加到哪个部分,有些疑惑。
通过仔细的研读代码,这方面的困难已经基本解决了。
【描述你在实习过程中遇到的困难,是与实习直接相关的技术方面的难题。突出说明你是如何攻克这些难关的。
要求:只需写一下有较深体会的困难。如果觉得整个过程都比较简单的话此部分可不用写。】
相信通过操作系统课的实习,我写C++代码的能力会有显著的提升,以前都是写Java的web,对C++忘的差不多了,通过这次的实验,可以捡回来不少。
另一个就是对线程的了解更加的透彻,虽然还仅仅只是对线程中PCB的一部分内容的实验。相信可以通过以后的实验,对操作系统的各种机制有更深入的了解。
【自己的收获,任何关于实习的感想,可以是技术方面的或非技术方面的,可随意发挥。
要求:内容不限,体裁不限,字数不限,多多益善,有感而发。】
目前来讲,我觉得一切还都挺好的,老师讲的很不错,进度也可以,没有什么建议。
【请写下你认为课程需要改进的地方,任何方面,比如进度安排、难易程度、课堂讲解、考核方式、题目设置……甚至如果你认为源代码哪里写得不好也欢迎提出。
各位同学反馈的信息对课程建设会有极大帮助。】
[1] 鸡蛋壳. 看雪论坛
http://bbs.pediy.com/showthread.php?t=15794
[2] 佚名 Nachos中文教程
http://wenku.baidu.com/link?url=1rGnypg8Hq6-43gAvuIYPWyVlPLZ0S_XNEXQJ-2ShqPPg3n2bqWvQgRYC8PdVXLmr66e9GpC2nCSbE1ofkgcT6aASWqVklMWUaBuZNSmXDy
【我们希望大家不要使用复制粘贴来拼凑你的报告。详细地列出你在完成lab的过程中引用的书籍,网站,讲义,包括你咨询过的大牛们。
要求:诚实,格式尽量按照论文的要求,请参考“论文参考文献格式.doc”】