文件系统调用
Nachos 实现了两套文件系统,一套是FILESYS_STUB,它是建立在 UNIX 文件系统之上的,而不使用 Nachos 的模拟磁盘,在Makefile文件大概194行使用了该宏定义开关;另一套则是Nachos本身的文件系统,它是实现在Nachos的虚拟磁盘上的。由于Nachos本身的文件系统我还没完全弄明白,所以本文使用的是FILESYS_STUB文件系统。
关键函数分析
如果使用的FILESYS_STUB系统,则FileSystem类如下所示
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()
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
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);
}
如下所示,关于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
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
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
int ReadPartial(int fd, char *buffer, int nBytes)
{
return read(fd, buffer, nBytes);
}
写操作差球不多,不再赘述。
实现过程
请务必自己理解代码,切勿照抄
在ksyscall.h当中定义文件操作的系统调用函数,如下
文件的创建
int SysCreate(char *name)
{
bool ret = kernel->fileSystem->Create(name);
if (ret)
{
return 1;
}
else
{
return -1;
}
}
文件的打开
int SysOpen(char *name)
{
// 获得文件标识符
int fd = OpenForReadWrite(name, FALSE);
if (fd)
{
return fd;
}
else
{
return -1;
}
}
文件的读取
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;
}
}
文件的写入
int SysWrite(char *content, int NumOfBytes, int fd)
{
OpenFile *opf = new OpenFile(fd);
ret = opf->Write(content, NumOfBytes);
return ret;
}
文件的关闭
int SysClose(int fd)
{
if (Close(fd))
{
return 1;
}
else
{
return -1;
}
}
然后在exception.cc当中增加case用于处理系统调用陷入产生的错误中断
以SC_Create的处理为例,其他的处理过程差球不多。
首先使用获取将第一个参数,由于SysCreate(char *name)
参数为指针,因此这里的addr指向文件名字符串第1位。接下来从addr开始遍历内存,直到读取的字符为\0
表示字符串结束或者访问越界,读出内存中的文件名。(char *)&temp
将temp
的地址强制转换为 char
类型的指针,以便以字节为单位访问内存中的数据,即将一个整数变量的地址转换为一个指向 char
类型的指针,然后使用指针逐个字节地读取文件名。得到了文件名filename之后,执行系统调用即可,将返回值ret存放在寄存器中。
关于各参数在寄存器当中的位置
创建
case SC_Create:
{
int addr = kernel->machine->ReadRegister(4);
char filename[32];
int temp;
kernel->machine->ReadMem(addr, 1, &temp);
int ptr = 0;
while (*(char *)&temp != '\0')
{
if (ptr >= 32)
{
break;
}
filename[ptr] = *(char *)&temp;
ptr++;
kernel->machine->ReadMem(addr + ptr, 1, &temp);
}
filename[ptr] = '\0';
int ret = SysCreate(filename);
kernel->machine->WriteRegister(2, ret);
/* Modify return point */
{
/* set previous programm counter (debugging only)*/
kernel->machine->WriteRegister(PrevPCReg, kernel->machine->ReadRegister(PCReg));
/* set programm counter to next instruction (all Instructions are 4 byte wide)*/
kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(PCReg) + 4);
/* set next programm counter for brach execution */
kernel->machine->WriteRegister(NextPCReg, kernel->machine->ReadRegister(PCReg) + 4);
}
return;
ASSERTNOTREACHED();
break;
}
打开
case SC_Open:
{
int addr = kernel->machine->ReadRegister(4);
char filename[32];
int temp;
kernel->machine->ReadMem(addr, 1, &temp);
int ptr = 0;
while (*(char *)&temp != '\0')
{
if (ptr >= 32)
{
break;
}
filename[ptr] = *(char *)&temp;
ptr++;
kernel->machine->ReadMem(addr + ptr, 1, &temp);
}
filename[ptr] = '\0';
int fd = SysOpen(filename);
kernel->machine->WriteRegister(2, fd);
/* Modify return point */
{
/* set previous programm counter (debugging only)*/
kernel->machine->WriteRegister(PrevPCReg, kernel->machine->ReadRegister(PCReg));
/* set programm counter to next instruction (all Instructions are 4 byte wide)*/
kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(PCReg) + 4);
/* set next programm counter for brach execution */
kernel->machine->WriteRegister(NextPCReg, kernel->machine->ReadRegister(PCReg) + 4);
}
return;
ASSERTNOTREACHED();
break;
}
写入
case SC_Write:
{
int addr = kernel->machine->ReadRegister(4);
int NumOfBytes = kernel->machine->ReadRegister(5);
int fd = kernel->machine->ReadRegister(6);
char content[100];
int temp;
kernel->machine->ReadMem(addr, 1, &temp);
int ptr = 0;
while (*(char *)&temp != '\0')
{
if (ptr >= 100)
{
break;
}
content[ptr] = *(char *)&temp;
ptr++;
kernel->machine->ReadMem(addr + ptr, 1, &temp);
}
content[ptr] = '\0';
int ret = SysWrite(content, NumOfBytes, fd);
kernel->machine->WriteRegister(2, ret);
/* Modify return point */
{
/* set previous programm counter (debugging only)*/
kernel->machine->WriteRegister(PrevPCReg, kernel->machine->ReadRegister(PCReg));
/* set programm counter to next instruction (all Instructions are 4 byte wide)*/
kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(PCReg) + 4);
/* set next programm counter for brach execution */
kernel->machine->WriteRegister(NextPCReg, kernel->machine->ReadRegister(PCReg) + 4);
}
return;
ASSERTNOTREACHED();
break;
}
读取
case SC_Read:
{
int addr = kernel->machine->ReadRegister(4);
int NumOfBytes = kernel->machine->ReadRegister(5);
int fd = kernel->machine->ReadRegister(6);
char content[100];
int ret = SysRead(content, NumOfBytes, fd);
if (ret > 0)
{
for (size_t i = 0; i < ret; i++)
{
kernel->machine->WriteMem(addr + i, 1, (int)content[i]);
}
}
kernel->machine->WriteRegister(2, ret);
/* Modify return point */
{
/* set previous programm counter (debugging only)*/
kernel->machine->WriteRegister(PrevPCReg, kernel->machine->ReadRegister(PCReg));
/* set programm counter to next instruction (all Instructions are 4 byte wide)*/
kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(PCReg) + 4);
/* set next programm counter for brach execution */
kernel->machine->WriteRegister(NextPCReg, kernel->machine->ReadRegister(PCReg) + 4);
}
return;
ASSERTNOTREACHED();
break;
}
关闭
case SC_Close:
{
int fd = kernel->machine->ReadRegister(4);
int ret = SysClose(fd);
kernel->machine->WriteRegister(2, ret);
/* Modify return point */
{
/* set previous programm counter (debugging only)*/
kernel->machine->WriteRegister(PrevPCReg, kernel->machine->ReadRegister(PCReg));
/* set programm counter to next instruction (all Instructions are 4 byte wide)*/
kernel->machine->WriteRegister(PCReg, kernel->machine->ReadRegister(PCReg) + 4);
/* set next programm counter for brach execution */
kernel->machine->WriteRegister(NextPCReg, kernel->machine->ReadRegister(PCReg) + 4);
}
return;
ASSERTNOTREACHED();
break;
}
新建一个测试用的用户程序test.c,如下所示(需要修改此处的Makerfile)
#include "syscall.h"
int main()
{
char *filename = "caixing";
int fd;
char *in_content = "caixing 2023.5.26";
char *out_content;
// 创建文件
Create(filename);
// 打开文件,可读可写
fd = Open(filename, RW);
// 写入文件
Write(in_content, 18, fd);
// 读取文件
Read(out_content, 100, fd);
// 关闭文件
Close(fd);
Halt();
/* not reached */
}
然后重新编译nachos和用户程序,执行下面命令
./nachos -x ../test/test.noff
shell
nachos当中的shell机制是通过两个文件来实现的。一个输入一个输出文件,分别对应的文件描述符为ConsoleInput
和ConsoleOutput
,用户在终端输入的字符串在输入文件中,命令执行完毕的结果在输出文件中。shell.c用户程序如下所示。
从输入文件中读取命令,然后系统调用Exec(buffer)创建一个线程,再使用Join(newProc)启动线程执行该命令。
#include "syscall.h"
#define ConsoleInput 0
#define ConsoleOutput 1
int main()
{
int newProc;
int input = ConsoleInput;
int output = ConsoleOutput;
char prompt[3], ch, buffer[60];
int i;
prompt[0] = '-';
prompt[1] = '-';
prompt[2] = '\0';
while (1)
{
Write(prompt, 3, output);
i = 0;
do
{
Read(&buffer[i], 1, input);
} while (buffer[i++] != '\n');
buffer[--i] = '\0';
if (i > 0)
{
newProc = Exec(buffer);
Join(newProc);
}
}
}
在ksyscall.h中增加下面内容
#define SHELL "/bin/sh"
typedef int SpaceId;
然后再在ksyscall.h中实现Exec和Join的系统调用,如下(模仿nachos_syscall.c中的代码)。为了shell界面便于观察将文件操作的几个函数中的格式化输出注释掉。
// shell
int SysExec(char *cmd)
{
pid_t child;
child = vfork();
if (child == 0)
{
execl(SHELL, SHELL, "-c", cmd, NULL);
_exit(EXIT_FAILURE);
}
else if (child < 0)
return EPERM;
return (SpaceId)child;
}
int SysJoin(int id)
{
return waitpid((pid_t)id, (int *)0, 0);
}
最后还需要再exception.cc中增加两个case来处理这两个系统调用,实现的思路与文件系统调用类似,这里不再赘述
最后重新编译一下nachos和用户程序,执行下面的命令
./nachos -x ../test/shell.noff
与之交互结果如下
如果觉得本文对你有帮助,请点点下方的赞赏按钮
- 最新
- 最热
只看作者