文件系统调用
Nachos 实现了两套文件系统,一套是FILESYS_STUB,它是建立在 UNIX 文件系统之上的,而不使用 Nachos 的模拟磁盘,在Makefile文件大概194行使用了该宏定义开关;另一套则是Nachos本身的文件系统,它是实现在Nachos的虚拟磁盘上的。由于Nachos本身的文件系统我还没完全弄明白,所以本文使用的是FILESYS_STUB文件系统。
关键函数分析
请勿将此部分内容照抄写入实验报告
如果使用的FILESYS_STUB系统,则FileSystem类如下所示
代码语言:javascript复制class FileSystem
{
public:
FileSystem() {}
bool Create(char *name)
{
int fileDescriptor = OpenForWrite(name);
if (fileDescriptor == -1)
return FALSE;
Close(fileDescriptor);
return TRUE;
}
OpenFile *Open(char *name)
{
int fileDescriptor = OpenForReadWrite(name, FALSE);
if (fileDescriptor == -1)
return NULL;
return new OpenFile(fileDescriptor);
}
bool Remove(char *name) { return Unlink(name) == 0; }
};
创建文件的函数Create(char *name)
,调用sysdep.cc中的OpenForWrite()
函数,如下。由于FILESYS_STUB是建立在 UNIX 文件系统之上的,因此这里使用unix标准中通用的头文件fcntl2.h的open()
函数来打开文件。
这里使用的参数除了文件名之外,还有O_RDWR | O_CREAT | O_TRUNC
这个参数。O_RDONLY
以只读方式打开文件,O_WRONLY
以只写方式打开文件,O_RDWR
以可读写方式打开文件。上述三种标志是互斥的,也就是不可同时使用,但是可以和其他标志用|(OR)
符号组合起来使用。O_CREAT
若要打开的文件不存在则自动建立该文件。而使用参数O_TRUNC
调用 open 函数打开文件的时候会将文件原本的内容全部丢弃,文件大小变为0。
创建成功则返回文件描述符fd,如果fd不是大于等于0则使用断言中断程序,然后将文件描述符fd返回给Create()
函数,最后Create()
函数调用Close()
关闭文件并返回TRUE表示文件创建成功。
int OpenForWrite(char *name)
{
int fd = open(name, O_RDWR | O_CREAT | O_TRUNC, 0666);
ASSERT(fd >= 0);
return fd;
}
关于上面使用到的Close()
函数关闭文件同样也是在sysdep.cc中定义的,如下所示。使用unistd.h
定义的封装的文件关闭的系统调用接口close()将文件关闭,然后如果返回值小于0则会触发断言导致程序中断。
int Close(int fd)
{
int retVal = close(fd);
ASSERT(retVal >= 0);
return retVal;
}
打开文件的函数如下,此处使用了OpenForReadWrite()
将文件以可读可写模式打开,然后将文件描述符返回。
OpenFile *Open(char *name)
{
int fileDescriptor = OpenForReadWrite(name, FALSE);
if (fileDescriptor == -1)
return NULL;
return new OpenFile(fileDescriptor);
}
OpenForReadWrite()
如下所示,关于Open()
函数上面已经讲到了,这里的O_RDWR
参数就是以可读可写的模式打开该文件。然后判断条件是否触发断言,最后返回文件描述符。
int OpenForReadWrite(char *name, bool crashOnError)
{
int fd = open(name, O_RDWR, 0);
ASSERT(!crashOnError || fd >= 0);
return fd;
}
当使用Open()
打开文件之后,会返回给我们一个文件描述符fd,接下来我们使用这个文件描述符来对文件进行读写操作。
Openfile模块定义了一个文件打开控制结构。当用户打开了一个文件时,系统即为其产生一个Openfile实例,以后用户对该文件的读写操作都可以通过该结构。打开文件控制结构中的对文件操作的方法同UNIX系统中的系统调用。
使用FILESYS_STUB
文件系统的Openfile类如下
class OpenFile {
public:
OpenFile(int f) { file = f; currentOffset = 0; } // open the file
~OpenFile() { Close(file); } // close the file
int ReadAt(char *into, int numBytes, int position) {
Lseek(file, position, 0);
return ReadPartial(file, into, numBytes);
}
int WriteAt(char *from, int numBytes, int position) {
Lseek(file, position, 0);
WriteFile(file, from, numBytes);
return numBytes;
}
int Read(char *into, int numBytes) {
int numRead = ReadAt(into, numBytes, currentOffset);
currentOffset = numRead;
return numRead;
}
int Write(char *from, int numBytes) {
int numWritten = WriteAt(from, numBytes, currentOffset);
currentOffset = numWritten;
return numWritten;
}
int Length() { Lseek(file, 0, 2); return Tell(file); }
private:
int file;
int currentOffset;
};
构造函数OpenFile(int f)
其中的参数f即文件描述符,使用文件描述符生成一个该实例即可对文件进行读写操作。
接下来我们看看两个关于读操作的函数,currentOffset
表示读取位置在整个文件当中的偏移量。Read函数调用ReadAt函数,ReadAt函数的三个参数into用于读取的数据的缓冲区,numBytes为读取的字节数,position参数为读取开始的位置。Lseek用于改变currentOffset的值,将其移动到需要读取的起始位置。
int ReadAt(char *into, int numBytes, int position) {
Lseek(file, position, 0);
return ReadPartial(file, into, numBytes);
}
int Read(char *into, int numBytes) {
int numRead = ReadAt(into, numBytes, currentOffset);
currentOffset = numRead;
return numRead;
}
然后使用ReadPartial
函数,使用unistd.h
定义的封装的文件读取系统调用接口read()读取文件,并将读取的nBytes字节放入缓冲区buffer当中。
int ReadPartial(int fd, char *buffer, int nBytes)
{
return read(fd, buffer, nBytes);
}
写操作差球不多,不再赘述。
理解了nachos当中使用FILESYS_STUB文件系统如何实现的文件的创建、打开、读取、写入、关闭操作后,接下来我们来实现用户程序中的文件系统调用。
实现过程
请务必自己理解代码,切勿照抄
关于nachos实现系统调用,参考nachos实现系统调用。
在ksyscall.h当中定义文件操作的系统调用函数,如下
文件的创建
代码语言:javascript复制int SysCreate(char *name)
{
bool ret = kernel->fileSystem->Create(name);
if (ret)
{
return 1;
}
else
{
return -1;
}
}
文件的打开
代码语言:javascript复制int SysOpen(char *name)
{
// 获得文件标识符
int fd = OpenForReadWrite(name, FALSE);
if (fd)
{
return fd;
}
else
{
return -1;
}
}
文件的读取
代码语言:javascript复制int SysRead(char *out, int NumOfBytes, int fd)
{
OpenFile *file = new OpenFile(fd);
// 读取文件
int ret = file->Read(out, NumOfBytes);
if (ret)
{
return 1;
}
else
return -1;
}
}
文件的写入
代码语言:javascript复制int SysWrite(char *content, int NumOfBytes, int fd)
{
OpenFile *opf = new OpenFile(fd);
ret = opf->Write(content, NumOfBytes);
return ret;
}
文件的关闭
代码语言:javascript复制int SysClose(int fd)
{
if (Close(fd))
{
return 1;
}
else
{
return -1;
}
}
然后在exception.cc当中增加case用于处理系统调用陷入产生的错误中断
以SC_Create的处理为例,其他的处理过程差球不多。
首先使用kernel->machine->ReadRegister(4)
获取将第一个参数,由于SysCreate(char *name)
参数为指针,因此这里的addr指向文件名字符串第1位。接下来从addr开始遍历内存,直到读取的字符为