Nachos LAB2 代码:
Nachos LAB2 文件系统 试验报告PDF:
内容一:总体概述
本LAB2 主要是关于文件系统相关的内容,涉及到的概念包括:文件系统布局,文件控制块,文件目录,文件的物理结构,文件的打开,创建,读写操作,系统调用相关知识等等。当然,也包括对上一个LAB(进程和线程)相关知识的回顾,例如同步与互斥,多线程操作等等。
内容二:任务完成情况
任务完成列表(Y/N)
Exercise1
Exercise2
Exercise3
Exercise4
Exercise5
Exercise6
第一部分
Y
Y
Y
Y
N
第二部分
Y
Y
第三部分
Y
N
具体Exercise的完成情况
第一部分
Exercise1
code/filesys/filehdr.h和code/filesys/filehdr.cc
这里面定义了nachos文件头的类。
code/filesys/filesys.h和code/filesys/filesys.cc
这里面定义了一个全局的类filesys, 它是系统或用户进行文件系统操作的接口,所有的操作都可以通过这个类来展开。
code/filesys/directory.h和code/filesys/directory.cc
这里面定义了目录项和目录文件的类,部分属性是在目录项里记录的
code /filesys/openfile.h和code /filesys/openfile.cc
这里定义了多文件进行主要操作的类,所有关于文件的核心操作,如打开,创建,读和写文件的操作,都在这里进行了定义
code/userprog/bitmap.h和code/userprog/bitmap.cc
这里面定义的是关于位图操作的类。因为nachos里磁盘块或者内存页的管理都是通过位图的形式来进行的
Exercise2
扩展文件属性:
增加文件描述信息,如“类型”、“创建时间”、“上次访问时间”、“上次修改时间”、“路径”等等;尝试突破文件名长度的限制
设计思想:
关于文件描述的信息FCB 一般是保存在目录项(DirectoryEntry)和文件头(FileHeader)里,这里选择将文件描述信息加入到目录项中。所以,可以通过如下的更改:
1.设置目录文件的属性为文件,标识为“-”;
设置目录文件的属性为目录,标识为“d”;
2. 创建时间,上次访问时间,上次修改时间格式为:2012-09-28 18:30
3. 文件长度的限制,可以通过更改FileNameMaxLen来实现
4. 关于路径,考虑到前面添加的属性太多了,为了节省空间,这里就不设置变量存储路径了。在后面的作业中,会通过动态获取的方式得到目录文件的当前路径。
更改的代码:
directory.h:
在DirectoryEntry class里,添加如下属性,并在构造函数里初始化这些属性变量:
DirectoryEntry(){
strncpy(createDate, "0000-00-00", StringMaxLen);
strncpy(lastAccessDate, "0000-00-00", StringMaxLen);
strncpy(lastUpdateDate, "0000-00-00", StringMaxLen);
strncpy(createTime, "00:00:00", StringMaxLen);
strncpy(lastAccessTime, "00:00:00", StringMaxLen);
strncpy(lastUpdateTime, "00:00:00", StringMaxLen);
fileSize = 0;
};
char fileType; // file '-' or folder 'd'
char createDate[StringMaxLen];
char createTime[StringMaxLen];
char lastAccessTime[StringMaxLen];
char lastAccessDate[StringMaxLen];
char lastUpdateTime[StringMaxLen];
char lastUpdateDate[StringMaxLen];
int fileSize;
char fName[FileNameMaxLen + 1]; //father folder name
在directory class里,添加一些方法:
class Directory {
…
char *DirGetCurrentDate(); //Get the DirectoryEntry's current date
char *DirGetCurrentTime(); //Get the DirectoryEntry's current time
void UpdateFileSize(char str[], int initialSize);
bool UpdateLastAccessTimestamp(char filename[], char lastTime[], char lastDate[]);
bool UpdateLastUpdateTimestamp(char filename[], char lastTime[], char lastDate[]);
…
}
在directory.cc里,添加相应的方法的实现,由于篇幅限制,这里暂时忽略了。值得一提的是,原来的List() 方法,也需要进行一些相应的更改:
void Directory::List()
{
char str[16];
for (int i = 0; i < tableSize; i++)
if ((table[i].inUse) && (strcmp(table[0].fName, table[i].fName)==0))
{
printf("%c\t%s %s\t%d\t\t%s\n", table[i].fileType, table[i].createDate, table[i].createTime, table[i].fileSize, table[i].name);
}
}
这样,在显示文件信息的时候,才能添加的文件属性信息打印出来。
其他相关的更改还有:
filesys.h:
在 FileSystem class里,添加如下方法声明:
void UpdateAccessTime(char filename[], char time_str[], char date_str[]);
void UpdateUpdateTime(char filename[], char time_str[], char date_str[]);
openfile.h:
在openFile class public里,添加一些变量:
char openDate[StringMaxLen];
char openTime[StringMaxLen];
char readDate[StringMaxLen];
char readTime[StringMaxLen];
char writeDate[StringMaxLen];
char writeTime[StringMaxLen];
openfile.cc
在openFile::Write()这个方法里,需要调用GetCurrentDate() and GetCurrentTime()这两个方法,来记录最近更新更新时间
fstest.cc:
在print(char *)函数里,需要调用GetCurrentDate(),GetCurrentTime() and fileSystem->UpdateAccessTime(),来记录最近访问时间
../thread/main.cc
更改原来文件系统部分的参数传递相关代码,增加新的显示某一个文件的信息的过功能:
...
} else if (!strcmp(*argv, "-l")) { // list Nachos directory
if(argc == 1)
{
fileSystem->List();
argCount = 1;
}
else if(argc == 2)
{
fileSystem->ListOne(*(argv+1));
argCount = 2;
}
测试运行:
首先格式化nachos磁盘空间:./nachos -f
然后输入:./nachos -l 查看以下有啥内容
可以看到,当前目录下只有 . 和 ..这两个文件,文件类型都为 “d”, 创建时间暂时没有记录, 文件大小为 4096(预先定义文件夹大小为4k, 跟linux 相一致)
接下来,创建两个文件:
./nachos -cp test/small small (copy 一个linux文件成nachos文件)
./nachos -mkdir home (这个函数是我自己写的,方便测试用的,用来创建文件夹)
下面在看看文件信息:./nachos -l
可以看到,多了2个文件了:small 和 home,其中small是文件,home是目录,创建时间,文件大小也都能看出来
看看具体某个文件的信息:
./nachos -l
看到small文件显示了3类时间戳,创建时间,访问时间和更改时间。
先用./nachos -p small 访问一遍small,然后再用./nachos -l small 看一下文件信息:
我们会发现,small的访问时间得到了更新。以后如果添加文件写入的功能,第三个时间戳也会相应得到更新,这里就不作演示了。
Exercise3
扩展文件长度
改直接索引为间接索引,以突破文件长度不得超过 4KB 的限制。
设计思想:
只有通过间接索引,才能无限扩大文件的大小限制。这里,由于作业时间的限制,只进行了二级索引的简单设计。设计的主要思想是在不更该原有的FileHeader 类接口的前提下,只更改了部分成员方法的代码,让dataSectors的第NumDirect位(dataSectors[29])数据,指向下一组dataSectors2[]的地址(dataSectors2 数组个数为30),文件块的地址首先从dataSectors[]数组里进行查找,如果超过29块,则从dataSector2数组里查找。这样单个文件实际最大可以扩大到(29+30)*128= 7552 (字节)
更改的代码:
filehhdr.cc, 至少需要更改AllocateToDataSectors(), Deallocate(), 以及Print()这三个方法!
//为了简化代码,先添加一个为数组分配块地址的小成员方法,以后会内部经常调用这个方法
void
FileHeader::AllocateToDataSectors(BitMap *freeMap, int numSectors, int dataSectors[])
{
int i;
for(i=0;i dataSectors[i] = freeMap->Find();
}
下面先更改Allocate()代码,让分配bitmap块的时候,将块号按顺序从dataSectors[]数组里开始保存,如果达到29个了,自动转到下一级的dataSectors2[]数组里。
Bool FileHeader::Allocate(BitMap *freeMap, int fileSize)
{
int lastIndex = NumDirect - 1;
int subSectors;
numBytes += fileSize;
printf("fileheader->numBytes=%d\n",numBytes);
numSectors = divRoundUp(fileSize, SectorSize);
if (freeMap->NumClear() < numSectors)
return FALSE; // not enough space
if(numSectors < lastIndex)
{
AllocateToDataSectors(freeMap, numSectors, dataSectors);
dataSectors[lastIndex] = -1;
}
else
{
AllocateToDataSectors(freeMap, lastIndex, dataSectors);
}
更改Deallocate()代码,注销BitMap块的时候,也是从二级的dataSectors2[]开始注销,然后是一级的dataSectors[]。
Void FileHeader::Deallocate(BitMap *freeMap)
{
int lastIndex = NumDirect - 1;
int dataSectors2[lastIndex];
int Sectors;
if(numSectors >= lastIndex)
{
int subSectors = numSectors - lastIndex;
int dataSectors2[lastIndex];
synchDisk->ReadSector(dataSectors[lastIndex],(char *)dataSectors2);
for(int i = 0; i < subSectors;i++)
{
ASSERT(freeMap->Test((int) dataSectors2[i])); // ought to be marked!
freeMap->Clear((int) dataSectors2[i]);
}
Sectors = NumDirect;
}
else
Sectors = numSectors;
for (int i = 0; i < Sectors; i++) {
ASSERT(freeMap->Test((int) dataSectors[i])); // ought to be marked!
freeMap->Clear((int) dataSectors[i]);
}
}
更改ByteToSector()代码,该方法会被openfile.cc频繁调用,用于文件的打开,读写操作等等,所以也很重要!
Int FileHeader::ByteToSector(int offset)
{
int lastIndex = NumDirect - 1;
if(offset / SectorSize >= lastIndex) //edit by YePeng
{
int dataSectors2[lastIndex];
synchDisk->ReadSector(dataSectors[lastIndex],(char *)dataSectors2);
return(dataSectors2[offset / SectorSize - lastIndex]);
}
else
return(dataSectors[offset / SectorSize]);
}
更改Print()方法代码:该方法不多说了,打印文件内容以及头文件信息的,据我所知,在./nachos -D中会被调用。
void FileHeader::Print()
{
int i, j, k;
char *data = new char[SectorSize];
int lastIndex = NumDirect - 1;
int subSectors;
printf("FileHeader contents. File size: %d. File blocks:\n", numBytes);
if(dataSectors[lastIndex] == -1)
{
for (i = 0; i < numSectors; i++)
printf("%d ", dataSectors[i]);
printf("\nFile contents:\n");
for (i = k = 0; i < numSectors; i++) {
synchDisk->ReadSector(dataSectors[i], data);
for (j = 0; (j < SectorSize) && (k < numBytes); j++, k++) {
if ('\040' <= data[j] && data[j] <= '\176') // isprint(data[j])
printf("%c", data[j]);
else
printf("\\%x", (unsigned char)data[j]);
}
printf("\n");
}
}
else
{
for (i = 0; i < lastIndex; i++)
printf("%d ", dataSectors[i]);
int dataSectors2[lastIndex];
synchDisk->ReadSector(dataSectors[lastIndex],(char *)dataSectors2);
subSectors = numSectors - lastIndex;
for (i = 0; i < subSectors; i++)
printf("%d ", dataSectors2[i]);
printf("\nFile contents:\n");
for (i = k = 0; i < lastIndex; i++) {
synchDisk->ReadSector(dataSectors[i], data);
for (j = 0; (j < SectorSize) && (k < numBytes); j++, k++) {
if ('\040' <= data[j] && data[j] <= '\176') // isprint(data[j])
printf("%c", data[j]);
else
printf("\\%x", (unsigned char)data[j]);
}
printf("\n");
}
for (i = k = 0; i < subSectors; i++) {
synchDisk->ReadSector(dataSectors2[i], data);
for (j = 0; (j < SectorSize) && (k < numBytes); j++, k++) {
if ('\040' <= data[j] && data[j] <= '\176') // isprint(data[j])
printf("%c", data[j]);
else
printf("\\%x", (unsigned char)data[j]);
}
printf("\n");
}
}
delete [] data;
}
测试运行:
首先通过cp指令创建一个大于4k的nachos文件:./nachos -cp test/tbig2 tbig2
如上图,我们可以看到,成功创建了一个大小为7372字节的文件tbig2(代码更改前,该操作是不可能成功执行的)。然后用./nachos -D查看以下文件块的分布情况:
如图,有6040字节的内容是保存的文件头信息,共占用了49个块(已经查过了30个块的限制了)
如图,有7372字节的内容,保存的是tbig2的内容,共占用了59个磁盘块(53~111)
接下来,测试一下如果创建的文件超过了预测的大小(7552),会发生啥事情:
如图,当试图cp一个7942大小的文件到nachos文件里,操作失败了!果然如预测的
那样,这个简易版的二级文件索引只支持不超过7.5k左右大小字节的文件
Exercise4
实现多级目录
设计思想:
通过在目录项里添加fName变量记录父目录的名字,建立目录文件之间的逻辑关系,然后通过List() ,Pwd()这些操作,立体的显示这些文件或目录的逻辑关系
更改的代码:
directory.h
在DirectoryEntry class里,添加:
char fName[FileNameMaxLen + 1];
在directory class 里, 添加 两个方法:
void RecordName(char name[]);
void GetPath(char path[]);
Recordname() 方法的功能是将参数里的内容替换当前父目录名记录,这个方法会在文件创建和路径更改的时候频繁被调用
GetPath()方法的功能是将当前路径以字符串的形式保存早参数变量里,这个方法会在需要显示当前路径的时候被调用
directory.cc:
方法实现代码:
void Directory::RecordName(char name[])
{
int i;
if( strcmp(name,"..") == 0 )
{
if((i = FindIndex(table[0].fName)) != -1)
{
printf("i=%d\n",i);
strncpy(table[0].fName, table[i].fName, FileNameMaxLen + 1);
strncpy(table[1].fName, table[i].fName, FileNameMaxLen + 1);
}
}
else
{
strncpy(table[0].fName, name, FileNameMaxLen + 1);
strncpy(table[1].fName, name, FileNameMaxLen + 1);
}
}
void Directory::GetPath(char path[])
{
char name[10][FileNameMaxLen + 1];
int i;
int index = 0;
for(i=0;i<10;i++)
{
if(strcmp(table[index].fName, "root") == 0)
break;
strcpy(name[i], table[index].fName);
index = FindIndex(table[index].fName);
}
for(i=i-1;i>=0;i--)
{
strcat(path, "/");
strcat(path, name[i]);
}
}
filesys.h 和 filesys.cc
由于Directory and DirectoryEntry这两个类的方法不容易被外界调用,所以通过FileSystem这个类来为用户提供接口,添加两个方法:
void ChangeRoot(char name[]);
void ShowPath();
fstest.cc
在这个文件里,添加两个用户使用的操作函数:
void Pwd() //显示当前路径
{
fileSystem->ShowPath();
}
void Cd(char *name) //进入某文件夹
{
char str[FileNameMaxLen + 1];
strcpy(str, name);
fileSystem->ChangeRoot(str);
}
当然,还有../thread/main.cc
…
}else if (!strcmp(*argv, "-cd")) { // mkdir a directory file
ASSERT(argc > 1);
Cd(*(argv + 1));
argCount = 2;
} else if (!strcmp(*argv, "-D")) { // print entire filesystem
fileSystem->Print();
} else if (!strcmp(*argv, "-pwd")) { // Show the current path
fileSystem->ShowPath();
printf("\n\n");
...
测试运行:
下面进行一组文件和目录的创建:
首先在根下创建 home 和 usr这两个目录,创建一个small文件
然后,cd home, 创建 yep 和 tom 两个目录
返回上级,进入 usr, 创建 bin 和 sbin这两个目录
进入sbin, 创建 medium 文件
文件之间 的逻辑关系如下:
/home: ----目录
/home/yep ----目录
/home/tom ----目录
/usr: ----目录
/usr/bin ----目录
/usr/sbin: ----目录
/usr/sbin/medium ----文件
/small ----文件
创建目录用./nachos -mkdir filenname
创建文件用 ./nachos -cp linuxfile nachosfile
进入 某文件夹用./nachos -cd 目录名
如上图,根目录下有2个目录和一个文件
如上图,当前目录在/home下,有yep和tom两个文件夹
如上图,当前路径为/usr, 有bin和 sbin两个文件夹
最后一张图,是进入最二层目录 /usr/sbin, 里面有medium这个文件
Exercise5
动态调整文件长度
对文件的创建操作和写入操作进行适当的修改,以使其符合实践要求。
设计思路:
原来的文件系统是在nachos文件创建之初就已经写死了文件大小了(默认格式化为一个Sector大小,既128字节),并且设置了文件长度的检测机制,如果读写的偏移量超过了这个大小,就停止继续读写。这样的好处是让设计变得简单和容易实现,弊端就是任何文件,都无法通过二次写入来改变大小了。想要动态的调整长度,一是要屏蔽掉文件的长度检测机制,二就是要能有已给累加器,在文件进行二次写入结束的时候,将累计器的最终数值更新到磁盘里相应的文件长度这个属性中。
第二部分 系统调用
Exercise1
类比Halt的实现,完成如下系统调用:Create, Open,Close,Write,Read
设计思想:
系统调用的通用接口,被定义在userprog/syscall.h 里,原则上,任何用户只要在自己写的用户程序里调用这些定义的接口函数,就能够通过系统调用实现想要的功能。这些接口如:
void Create(char *name);
void Write(char *buffer, int size, OpenFileId id);
int Read(char *buffer, int size, OpenFileId id);
void Close(OpenFileId id);
OpenFileId Open(char *name);
但是,这些接口是为用户态提供的,接口的具体实现,是需要在内核态完成的(也就是说,用户是不能自己访问那些系统内部的函数或方法的)。系统调用就是通过异常机制(expection.cc,mipssim.cc等等), 汇编代码的部分(start.s), 内核部分的函数或方法调用(很多),用户态通用接口(syscall.h) 等共同协作完成的。具体来说,就是当用户执行一条调用指令的时候(比如说Write(buffer, size, id)), start.s会执行汇编代码里相应的Write部分,将SC_Write的系统调用码保存到寄存器2里,将参数分别保存到寄存器4,5,6里,然后,系统通过异常处理,从寄存器2获取到这个系统调用码,并读出寄存器4,5,6里的参数,然后就会调用内核部分的write函数代码,并将返回数值返回存入寄存器2,用户在用户态调用这个接口的时候,所获得的返回值就是从寄存器2里获得的. 这里,内核函数(方法)的调用,我是通过 三组 类实现的:
一部分在新定义的KsysCall class(machine/ksyscall.h ksyscall.cc);
一部分在 已有的fileSystem class(filesys/filesys.h, filesys.cc);
还有一部分在已有的sysdep class(machine/sysdep.h sysdep.cc)里实现的。
在设计的时候,还需要考虑到因为支持两种文件系统格式(Linux和nachos),所以为了区别开来,定义了两组系统调用,分别支持FILESYS_STUB(默认linux文件系统)和FILESYS(nachos文件系统)。此外,为了方便测试,还需要定义两组printf系统调用指令(用户程序里,可以调用的函数只有Halt(),所以很多基本的函数还需要自己去实现).
更改的代码:
1.userprog/syscall.h 应为添加了一些新的调用,所以需要添加新的系统调用码
#define SC_Printf 11 //打印字符窜的函数
#define SC_PrintfNum 12 //打印整型的函数
test/start.s 里为这两个函数添加相应的汇编代码:
Printf:
addiu $2,$0,SC_Printf
syscall
j $31
.end Printf
.globl PrintfNum
.ent PrintfNum
PrintfNum:
addiu $2,$0,SC_PrintfNum
syscall
j $31
.end PrintfNum
2.userprog/exception.cc 这里边,需要添加两组 系统调用指令,限于篇幅,只写结构了,具体实现请看附件中的代码
...
if ((which == SyscallException)) {
switch(type)
{
case SC_Halt:
...
#ifdef FILESYS_STUB
case SC_Printf:
...
case SC_PrintfNum:
...
case SC_Create:
case SC_Open:
case SC_Close:
case SC_Write:
case SC_Read:
#else //FILESYS
case SC_Printf:
...
case SC_PrintfNum:
case SC_Create:
case SC_Open:
case SC_Close:
case SC_Write:
case SC_Read:
#endif
default:
break;
}
}
else {
printf("Unexpected user mode exception %d %d\n", which, type);
ASSERT(FALSE);
}
//下面的代码很重要,每完成一次系统调用,需要将PC value向后推进一位,否者,之前的系统调用就会陷入死循环中!!!
NextPC = machine->ReadRegister(NextPCReg);
machine->WriteRegister(PCReg, NextPC);
}
3. machine/ksyscall.c ksyscall.h 这两个文件是我新添加的,为了通过编译,需要在Makefile.common 里添加相应的machine/ksyscall.h, machine/ksyscall.cc 和 ksycall.o,这样系统才能够调用这些定义的方法:
ksyscall.h:
#include "copyright.h"
#include "utility.h"
#include "translate.h"
#define OpenFileNum 10
#define BufferSize 256
class KsysCall {
public:
KsysCall(char *name);
~KsysCall();
int GetOption(char argstr[], int argaddr);
void NachosPrintf(char *str, int length); //支持nachos的字符串输出函数,被exception.cc调用
void NachosPrintfNum(int num); //支持nachos的整型输出函数,被exception.cc调用
int SysRead(int addr, int size, int id); //支持nachos 的读操作,被exception.cc调用
};
ksyscall.cc :略
4.filesys/filesys.h filesys.cc , 支持nachos的系统调用操作有相当一部分是通过exception.cc调用了这里面的方法,因为在系统运行程序的时候,fileSystem 这个类是全局性的。
Filesys.h 添加的属性和方法(只限于FILESYS部分,供nachos系统调用):
Public:
int SysCallOpen(char *name);
void SysCallClose(int id);
void SysCallWrite(char *buffer, int Bytes, int id);
int SysCallRead(char *buffer, int Bytes, int id);
void IncreaseFileSize(char *name, int Bytes);
Private:
OpenFile* OpenFileQueue[10]; //最多允许用户同时打开10个文件
char OpenFileName[10][255]; //10个文件,每个文件名字最长不超过255字节
int OpenFileInUse[10]; //第几个文件正在被使用,1为使用,0为未使用
filesys.cc 具体方法的实现:省略
相关的更改还有 openfile.h , openfile.cc ,filehdr.h/filehdr.cc 暂省略
5.machine/sysdep.h, sysdep.cc 这里面定义的是供 Linux 文件系统使用的系统调用。
sysdep.h 新添加的函数:
extern void Printf(char *str);
extern void PrintfNum(int num);
具体实现在sysdep.cc里,省略
测试结果:
具体的测试,需要编写相应的测试程序,这个将在下一个练习中得到体现。目前只想强调一点, userprog/ 在编译的时候,分两大种情况:
如果之前的nachos 文件系统没有组建好,只能用Makefile里的第一种情况FILESYE_STUB来进行编译,测试的时候执行:
userprog/nachos -x test/程序名 ,系统会自从识别linux下的应用程序文件格式。
如果之前的nachos文件系统已经组建好了,各种nachos文件相关的操纵指令也都能正常使用的话,建议使用第二种编译,既DEFINES = -DUSER_PROGRAM -DFILESYS_NEEDED -DFILESYS。这种测试有一定的复杂度,因为需要把linux文件格式的应用程序文件用filesys/nachos -cp的方式拷贝成nachos文件格式的文件,然后再用userprog/nachos -x nachos文件名 来进行执行。有一定难度,但是能更深刻的学习nachos文件系统。
Exercise2
编写用户程序:
编写并运行用户程序,调用 练习6中所写系统调用,测试其正误
设计思想:
我们已经有了Create(), Open(), Write(), Read(), Close()这一组文件操作的系统调用函数,可以设计一个应用程序,包含这5组函数调用:
1.首先创建一个文件 test;
2.打开这个文件test,获得它的返还数值 fd;
3.通过fd, 在这个文件里写入一行字符,比如:"hello,world.I am YePeng.How are ou?\n" 这37个字节,然后关闭文件;
4.再次打开这个文件test, 获得新的fd;
5.调用Read()函数,从这个打开的文件里读取40个字节的文件(实际上,它只能读取37个字节);
6关闭这个文件。
这样,通过这一条龙的操作,把所有想测试的系统调用函数都包括进去了,没调用一个函数成功后,通过Printf()和PrintfNum()这两个小函数检验以下运行的过程。
添加更改的代码:
1.编写一个叫homework.c的文件:
/* homework.c
* Simple program to test whether running a user program works.
*
* do a group of "syscall" which include:
* create a file.
* open the file
* write one 37 Bytes line into the file
* try to read 40 Bytes from the file
* close the file
* halt the sytem
*/
#include "syscall.h"
int mystrlen(char *buffer) //similar like strleng() ,get the length of the char arry
{
int i;
for(i=0;i<500;i++)
{
if(buffer[i]==0)
return i;
}
return -1;
}
void main()
{
OpenFileId fd;
char *filename = "test";
int ByteNum;
char *buffer = "hello,world.I am YePeng.How are you?\n";
char buffersize = mystrlen(buffer);
char buf[20]; //testing for Create(char *)
Create(filename);
Printf("Calling 'Create(filename)'... ");
Printf(" done!\n"); //testing for Open()
fd = Open(filename);
Printf("Calling 'fd = Open(filename)'... done!\n");
Printf("Return value fd = ");
PrintfNum(fd);
Printf("\n"); //testing for Write()
Write(buffer, buffersize, fd);
Close(fd);
Printf("Calling 'Write(buffer, buffersize, fd)' ...done!\n"); //testing for Read()
fd = Open(filename);
ByteNum = Read(buf, 40, fd);
Printf("Calling 'Read(buf, 40, fd)' ... Done\n");
Printf("Begin to print the 40 Bytes content of a nachos file:\n");
Printf(buf);
Printf("\nActually reading size: ");
PrintfNum(ByteNum);
Printf(" Bytes\n");
Close(fd);
Halt();
}
更改 test/Makefile, 结尾添加:
homework.o: homework.c
$(CC) $(CFLAGS) -c homework.c
homework: homework.o start.o
$(LD) $(LDFLAGS) start.o homework.o -o homework.coff
../bin/coff2noff homework.coff homework
这样在编译的时候,就可以生成homework和homework.coff 应用程序了
2.编写一个自动运行测试脚本AutoNachosTest.sh,让nachos文件的格式化,拷贝,用户程序的运行一条龙执行:
#!/bin/bash
if [[ `pwd` != "/home/backup/LAB2/nachos-3.4/code/filesys" ]]; then
echo "wrong folder executions!"
exit
fi
./nachos -f > /dev/null
sleep 2
echo "step 1. format nachos filesystem... Done!"
./nachos -cp ../test/homework homework > /dev/null
sleep 2
echo "step 2. copying ../test/homework to nachos file 'homework' ... Done!"
sleep 2
echo "step 3. execute nachos -x homework..."
echo -e "------------------------------------------------\n"
sleep 2
../userprog/nachos -x homework
echo -e "\nDone!"
注意:这个脚本必须要放到filesys/文件夹里去执行。执行权限为755
测试结果:
在code/目录下,执行make,经过一段较长时间的编译(假定库文件包含没有问题,代码无错误),编辑成功了。Test/目录下会生成homework和homework.coff两个文件。
进入filesys/文件目录下,首选确定nachos文件运行正常:./nachos -l
如上图,nachos当前目录下除了.和..没有任何文件。
执行:./AutoNachosTest.sh:
如上图,auto脚本先执行了准备工作:格式化nachos, 然后copy nachos用户程序,最后是执行应用程序,虚线以下到”....Maching halting!”这之间的内容就是用户程序运行的结果:
1.先创建文件 2.打开文件 3.看返还值fd 4.写文件 5.读文件 6.关闭文件
如果想手动测试一下是否真的创建了一个叫test 并且里面有写入的字符串的nachos文件,可以手动执行一下./nachos -l 和 ./nachos -p test来看一下:
如上图,nachos增加了两个文件,homework(用户应用程序)和 test, test文件里有一行字符串:hello,world. I am YePeng.How are you?
看来,文件被成功的创建,打开,写入和读取了。
第三部分
Exercise1
源代码阅读
(1)阅读Nachos源代码中与异步磁盘有关的代码,理解Nachos系统中的异步访问模拟磁盘的工作原理。
代码分析:nachos里的disk.cc/disk.h里关于磁盘的访问操作是异步的,为了保证文件读写的同步与互斥,必须通过增加信号量或者锁机制来完成。Synchdisk.h/synchdisk.cc就是来完成这样的工作的。
synchdisk.h/synchdisk.cc里面关于读写磁盘的主要操作是靠方法ReadSector()和 WriteSector()完成的。这两个方法,为了保证磁盘操作的互斥,运用了lock->Acquire()和lock->Release(), 所以保证了磁盘的每一个线程操作都不会受到其他线程或本线程下一个操作的影响(互斥原则);同时也保证了磁盘读与写操作的同步,运用了信号量P()和V()操作,即下一个写/读操作必须在上一个写/读操作完成之后才能进行。
(2)利用异步访问模拟磁盘的工作原理,在类Console的基础上,实现类 SynchConsole
设计思想:
console.h/console.cc里面关于 read/write buffer的操作是通过GetChar()和PutChar()这两个方法完成的,它们是异步的,并且是非互斥的。异步造成了读写buffer的操作不能保持一致,非互斥造成了在多线程操作的时候会产生读写混乱。所以,需要有一对支持同步和互斥的读写操作:SynchGetChar()和SynchPutChar(),这两个方法,在原来操作的基础上,添加了Lock机制和信号机制。
添加的代码:
需要添加新的类:SynchConsole 类
synchconsole.h:
#include "copyright.h"
#include "utility.h"
#include "console.h"
#include "synch.h"
#include "system.h"
// The following class defines a hardware console device.
// Input and output to the device is simulated by reading
// and writing to UNIX files ("readFile" and "writeFile").
// Since the device is asynchronous, the interrupt handler "readAvail"
// is called when a character has arrived, ready to be read in.
// The interrupt handler "writeDone" is called when an output character
// has been "put", so that the next character can be written.
class SynchConsole {
public:
SynchConsole(char *readFile, char *writeFile, int callArg); // initialize the hardware console device
~SynchConsole(); // clean up console emulation
void SynchPutChar(char ch);
char SynchGetChar();
private:
Console *console;
Lock *readLock;
Lock *writeLock;
};
……
synchconsole.cc:
#include "copyright.h"
#include "console.h"
#include "synchconsole.h"
#include "system.h"
// Dummy functions because C++ is weird about pointers to member functions
static Semaphore *readAvail;
static Semaphore *writeDone;
static void ReadAvail(int arg) { DEBUG('a', "ReadAvail:readAvail->V()\n"); readAvail->V(); }
static void WriteDone(int arg) { DEBUG('a', "WriteDone:writeDone->V()\n"); writeDone->V(); }
//----------------------------------------------------------------------
// SynchConsole::SynchConsole
// Initialize the simulation of a hardware console device.
//
// "readFile" -- UNIX file simulating the keyboard (NULL -> use stdin)
// "writeFile" -- UNIX file simulating the display (NULL -> use stdout)
// "readAvail" is the interrupt handler called when a character arrives
// from the keyboard
// "writeDone" is the interrupt handler called when a character has
// been output, so that it is ok to request the next char be
// output
//----------------------------------------------------------------------
SynchConsole::SynchConsole(char *readFile, char *writeFile, int callArg)
{
console = new Console(readFile, writeFile, ReadAvail, WriteDone, callArg);
readAvail = new Semaphore("read avail", callArg);
writeDone = new Semaphore("write done", callArg);
readLock = new Lock("readLock");
writeLock = new Lock("writeLock"); //printf("init console done\n");
}
SynchConsole::~SynchConsole()
{
delete console;
delete readLock;
delete writeLock;
}
char SynchConsole::SynchGetChar()
{
char ch;
readLock->Acquire();
readAvail->P();
ch = console->GetChar();
readLock->Release();
return ch;
}
void SynchConsole::SynchPutChar(char ch)
{
writeLock->Acquire();
console->PutChar(ch);
writeDone->P();
writeLock->Release();
}
其他更改:
synchconso.cc里面主要的设计难点是在创建console()类的时候,需要把信号量的V操作以函数指针的方式作为参数传进去,所以,需要在外边定义两组局部的变量readAvail, writeDone 和两个函数ReadAvail(),WriteDone(), 通过函数调用readAvail->V()和writeDone->V(),然后把这两个函数地址地址作为参数传进console()类里。
为了能够编译新的文件,将synchconsole.h/synchconsole.cc放入到code/machine里,然后更改code/Makefile.common:
USERPROG_H = ../userprog/addrspace.h\
../userprog/bitmap.h\
../filesys/filesys.h\
../filesys/openfile.h\
../machine/console.h\
../machine/synchconsole.h\
../machine/machine.h\
../machine/mipssim.h\
../machine/translate.h\
../machine/ksyscall.h\
../userprog/syscall.h
USERPROG_C = ../userprog/addrspace.cc\
../userprog/bitmap.cc\
../userprog/exception.cc\
../userprog/progtest.cc\
../machine/console.cc\
../machine/synchconsole.cc\
../machine/machine.cc\
../machine/mipssim.cc\
../machine/ksyscall.cc\
../machine/translate.cc
USERPROG_O = addrspace.o bitmap.o exception.o progtest.o console.o machine.o \
mipssim.o translate.o ksyscall.o synchconsole.o
然后进入code目录,执行make。这样就可以将新写的类编入到synchconsole.o里,方便以后的使用了。
测试结果:
可以通过编写一个测试用例,调用SynchConsole类来进行测试,这里选择在userprog/progtest.cc里面,添加如下新函数:
#include “synchconsole.h”
void SynchConsoleTest(char *in, char *out)
{
char str[255];
char ch;
int i, j;
SynchConsole *synchconsole = new SynchConsole(in, out, 0);
for(i=0; i<255; i++)
{
ch = synchconsole->SynchGetChar();
strncpy(&str[i],&ch,1);
if(str[i] == 'q') break;
}
for(j=0; j synchconsole->SynchPutChar(str[j]);
}
当然,为了能调用这个函数,需要在threads/main.cc里面,添加一个新参数:-u
#endif // USER_PROGRAM
#ifdef FILESYS
if (!strcmp(*argv, "-cp")) { // copy from UNIX to Nachos
ASSERT(argc > 2);
Copy(*(argv + 1), *(argv + 2));
argCount = 3;
…
} else if (!strcmp(*argv, "-u")) { // test the console
SynchConsoleTest(NULL, NULL);
interrupt->Halt(); // once we start the console, then
// Nachos will loop forever waiting
// for console input
}
…
运行一下:cd userprog/; ./nachos -u
如上图,q是结束输入。这里用了str[255]来保存整段的输入,在结束输入以后,调用SynchPutChar()逐字显示在屏幕上。q 之前的字符段是手写输入回显的内容,q之后的字符段才是真正的从ch中写入到屏幕上的。
Exercise2
实现文件系统的同步互斥访问机制,达到如下效果:
1) 一个文件可以同时被多个线程访问。且每个线程独自打开文件,独自拥有一个当前文件访问位置,彼此之间不会互相干扰。
2) 所有对文件系统的操作必须是原子操作和序列化的。例如,当一个线程正在修改一个文件,而另一个线程正在读取该文件的内容时,读线程要么读出修改过的文件,要么读出原来的文件,不存在不可预计的中间状态。
3) 当某一线程欲删除一个文件,而另外一些线程正在访问该文件时,直到所有线程关闭了这个文件,该文件才被删除。也就是说,只要还有一个线程打开了这个文件,该文件的物理空间就不能真正地被删除。
设计思想:
1) openfile.h/openfile.cc里面的添加新私有属性openfile->threadInUse[],每打开一次同一个文件,就在openfile这个类的计数器数组里(openfile->threadInUse[n]) 标记为1,然后通过返还数组下标来确定是哪组线程打开的文件。等操作结束将文件关闭的时候,在相应的计数器数组里将1置换为0,供后来的线程使用。
2) openfile.h/openfile.cc里面关于读写的操作,如read()和write()方法,需要加设关中断机制的代码,既一个线程在执行read()或 write()操作的时候,不可以中途有其他的线程也在对相同的文件进行read()或write(0的操作
3) 如果某个线程要删除一个文件,它必须先遍历一边openfile类里的ThreadInUse[]数组,确保当前的线程是唯一最后一个占用该数组的线程,如果不是,说明还有其他线程在打开相同的文件,则该线程不能进行这个文件的物理删除操作。
代码的实现:由于作业时间的限制,未能在有限的时间里完成该设计思想的实现。
测试结果:未实现。
内容三:遇到的困难以及解决方法
困难1
没有足够的作业时间完成作业。尽管LAB2的作业时间基本上是我用在LAB1作业时间的1.5~2倍,但感觉还是不够用,直到最后一天了,刚完成第三部分的第一道小题。只能说比较遗憾了。
困难2
LAB2作业无论从作业难度和作业量上,我个人感觉比LAB1高了至少一个级别。需要看的代码很多,测试和调试的难度也相当大,最基础知识的掌握和编程技能都是不小的挑战。而且,很多内容教科书和讲义甚至nachos man page里都没有,只能通过上网查找,比如说系统调用寄存器那块的内容。所以,费时费力进度缓慢。
内容四:收获及感想
如果说LAB1是初学nachos的入门作业的话,LAB2因该能算上中级作业了,光第一部分就有5个小作业要做,每个小作业想真正的完成都不是1~2个小时,简单读读代码就能搞定的,可见LAB2作业难度之大了!不过,正是因为作业的难度比较大,才更督促我去沉下心来仔细研读filesys目录里的相关代码,才能对nachos文件系统的运作机理,有一个从”无从下手”到”基本理解”,最后到”了然于心”的过程.虽然到目前为止,对nachos文件系统的每个文件的具体功能和内容还不是完全的理解和掌握,但是至少对LAB2里的作业题,能够通过相应的文件整理出设计思想;对需要更改的代码,也能有个大概的方向了。我觉得,作为一个nachos学习者,能够达到这个层次已经基本算是这门课程的合格者。毕竟,想精通nachos,没有一年半载的努力,普通人是不太容易达到的。
第二部分的作业,可以说是LAB2里的最大挑战作业,因为不光要掌握filesys目录里的内容,还需要熟悉machine目录里的内容,还有threads目录,userprog目录和test目录,此外,还需要对汇编有所熟悉,因为要用到寄存器,还有C编译器相关知识,如MakeFile, 动态链接库等等。所以,系统调用部分的作业貌似题目描述比较简单,里面却考验了作业者相当多的知识和技术功底。我个人在做这部分作业的时候,投入了相当多的精力,也感觉自己学到了不少的东西,对编程技能的确是一种锤炼。
内容五:对课程的意见和建议
个人感觉文件系统的教学进度有些偏快,如果能够为文件系统提供更多的教学时间和作业时间,相信大家的作业完成度和完成质量上能更好,对文件系统的掌握能更深!
内容六:参考文献
[1] Andrew S. Tanenbaum 著.陈向群 马洪兵 译 .现代操作系统[M].北京:机械工业
出版社,2011:47-95.
[2] 陈向群. 高级擦作系统讲义[Z]. 北京:北京大学信息科学技术学院, 2012:5-8.
[3] 陈向群. Nachos 操作系统课件[Z]. 北京: 北京大学信息科学技术学院, 2012: 5-8.
[3] Assoc. Prof. Peiyi Tang
. CSC2404/66204: Operating Systems
[C].
Australia
: University of Southern Queensland
1998-2002
阅读(9787) | 评论(1) | 转发(0) |