Nachos文件系统调用与shell

2023-10-21 11:36:48 浏览数 (1)

文件系统调用

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表示文件创建成功。

代码语言:javascript复制
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则会触发断言导致程序中断。

代码语言:javascript复制
int Close(int fd)
{
    int retVal = close(fd);
    ASSERT(retVal >= 0);
    return retVal;
}

打开文件的函数如下,此处使用了OpenForReadWrite()将文件以可读可写模式打开,然后将文件描述符返回。

代码语言:javascript复制
OpenFile *Open(char *name)
{
  int fileDescriptor = OpenForReadWrite(name, FALSE);

  if (fileDescriptor == -1)
    return NULL;
  return new OpenFile(fileDescriptor);
}

OpenForReadWrite()如下所示,关于Open()函数上面已经讲到了,这里的O_RDWR参数就是以可读可写的模式打开该文件。然后判断条件是否触发断言,最后返回文件描述符。

代码语言:javascript复制
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类如下

代码语言:javascript复制
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的值,将其移动到需要读取的起始位置。

代码语言:javascript复制
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当中。

代码语言:javascript复制
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开始遍历内存,直到读取的字符为表示字符串结束或者访问越界,读出内存中的文件名。(char *)&temptemp的地址强制转换为 char 类型的指针,以便以字节为单位访问内存中的数据,即将一个整数变量的地址转换为一个指向 char 类型的指针,然后使用指针逐个字节地读取文件名。得到了文件名filename之后,执行系统调用即可,将返回值ret存放在寄存器中。

关于各参数在寄存器当中的位置

创建

代码语言:javascript复制
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 != '')
    {
        if (ptr >= 32)
        {
            break;
        }
        filename[ptr] = *(char *)&temp;
        ptr  ;
        kernel->machine->ReadMem(addr   ptr, 1, &temp);
    }
    filename[ptr] = '';
    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;
}

打开

代码语言:javascript复制
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 != '')
    {
        if (ptr >= 32)
        {
            break;
        }
        filename[ptr] = *(char *)&temp;
        ptr  ;
        kernel->machine->ReadMem(addr   ptr, 1, &temp);
    }
    filename[ptr] = '';
    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;
}

写入

代码语言:javascript复制
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 != '')
    {
        if (ptr >= 100)
        {
            break;
        }
        content[ptr] = *(char *)&temp;
        ptr  ;
        kernel->machine->ReadMem(addr   ptr, 1, &temp);
    }
    content[ptr] = '';
    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;
}

读取

代码语言:javascript复制
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;
}

关闭

代码语言:javascript复制
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)

代码语言:javascript复制
#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和用户程序,执行下面命令

代码语言:javascript复制
./nachos -x ../test/test.noff

在build.linux下能够看到新建的文件,如下

shell

nachos当中的shell机制是通过两个文件来实现的。一个输入一个输出文件,分别对应的文件描述符为ConsoleInputConsoleOutput,用户在终端输入的字符串在输入文件中,命令执行完毕的结果在输出文件中。shell.c用户程序如下所示。

从输入文件中读取命令,然后系统调用Exec(buffer)创建一个线程,再使用Join(newProc)启动线程执行该命令。

代码语言:javascript复制
#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] = '';

    while (1)
    {
        Write(prompt, 3, output);

        i = 0;

        do
        {

            Read(&buffer[i], 1, input);

        } while (buffer[i  ] != 'n');

        buffer[--i] = '';

        if (i > 0)
        {
            newProc = Exec(buffer);
            Join(newProc);
        }
    }
}

在ksyscall.h中增加下面内容

代码语言:javascript复制
#define SHELL "/bin/sh"
typedef int SpaceId;

然后再在ksyscall.h中实现Exec和Join的系统调用,如下(模仿nachos_syscall.c中的代码)。为了shell界面便于观察将文件操作的几个函数中的格式化输出注释掉。

代码语言:javascript复制
// 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和用户程序,执行下面的命令

代码语言:javascript复制
./nachos -x ../test/shell.noff

与之交互结果如下

0 人点赞