一、写在前面

上一节我们分析了nachos文件系统底层的裸磁盘对象Disk和线程安全磁盘对象SynchDisk,在本节我们沿着文件系统的结构继续向上进行分析,介绍FileHeader、OpenFile与BitMap相关文件.

二、源码分析

1、位图对象:BitMap

磁盘的最小读写单元为扇区,而BitMap就是用于表示磁盘扇区空闲状况的.BitMap本身也是一个文件,位置在0号扇区.


class BitMap {public:BitMap(int nitems);        // Initialize a bitmap, with "nitems" bits// initially, all bits are cleared.~BitMap();           // De-allocate bitmapvoid Mark(int which);      // Set the "nth" bitvoid Clear(int which);    // Clear the "nth" bitbool Test(int which);       // Is the "nth" bit set?int Find();               // Return the # of a clear bit, and as a side// effect, set the bit. // If no bits are clear, return -1.int NumClear();     // Return the number of clear bitsvoid Print();     // Print contents of bitmap// These aren't needed until FILESYS, when we will need to read and // write the bitmap to a filevoid FetchFrom(OpenFile *file);    // fetch contents from disk void WriteBack(OpenFile *file);     // write contents to diskprivate:int numBits;           // number of bits in the bitmapint numWords;            // number of words of bitmap storage// (rounded up if numBits is not a//  multiple of the number of bits in//  a word)unsigned int *map;            // bit storage
};

BitMap的数据成员有两个整型值和一个无符号整型指针.numBits代表BitMap中有多少个扇区的使用信息,numWords为将位图BitMap中的位使用整型表示后有多少值(32位机器中int为32位).map为表示位图数组的指针,BitMap使用无符号整型数组表示位图,因此map中的一项表示了32个扇区的信息.

下面对BitMap的成员函数进行简要介绍.

  1. Mark:对某一位进行标记,即设置该位所表示扇区的使用情况
  2. Clear:清空某一位
  3. Test:测试某一位是否已经被使用
  4. Find:返回一个空闲扇区的序号,若不存在则返回-1
  5. NumClear:返回BitMap中空闲扇区的数量
  6. Print:打印位图的使用信息
  7. FetchFrom:取得位图文件
  8. WriteBack:写回位图文件
BitMap::BitMap(int nitems)
{ numBits = nitems;numWords = divRoundUp(numBits, BitsInWord);map = new unsigned int[numWords];for (int i = 0; i < numBits; i++) Clear(i);
}BitMap::~BitMap()
{ delete map;
}void
BitMap::Clear(int which)
{ASSERT(which >= 0 && which < numBits);map[which / BitsInWord] &= ~(1 << (which % BitsInWord));
}bool
BitMap::Test(int which)
{ASSERT(which >= 0 && which < numBits);if (map[which / BitsInWord] & (1 << (which % BitsInWord)))return TRUE;elsereturn FALSE;
}
int
BitMap::NumClear()
{int count = 0;for (int i = 0; i < numBits; i++)if (!Test(i)) count++;return count;
}int
BitMap::Find()
{for (int i = 0; i < numBits; i++)if (!Test(i)) {Mark(i);return i;}return -1;
}

BitMap的构造函数接收一个整型值nitems,表示位图的大小.首先设置位图位数numBits,然后计算并设置能够表示所有位的整型值数目numWords,即numBits/32上取整.接着建立一个numWords大小的数组用于表示位图.建立位图之后,调用Clear函数将整型数组每个单元的每一位都置为0.析构函数使用delete释放动态建立的整型数组.

Clear函数接收一个位编号,首先断言这个编号没有越界,然后找到数组中表示这个位的整型值,对这个整型值进行位操作将该位变为0.在这里只需要简单叙述一下原理,对于一个无符号整型数,其取值范围是0~65535,每一个取值都与32个bit的一种状态一一对应,因此可以通过这个方法改变整型值大小来间接表示bit的状态.Test函数测试对应bit位是否为1,是则返回TRUE,否则返回FALSE.

NumClear函数对每一位都使用Test函数,如果该位位0则将计数变量+1,最后返回空闲扇区的总数.Find函数对每一位都使用Test函数,如果找到某一位为0则将该位的标号返回,如果搜索了整个map都没有找到空闲位则返回-1.

void
BitMap::FetchFrom(OpenFile *file)
{file->ReadAt((char *)map, numWords * sizeof(unsigned), 0);
}void
BitMap::WriteBack(OpenFile *file)
{file->WriteAt((char *)map, numWords * sizeof(unsigned), 0);
}

因为位图需要进行持久化存储,所以位图也是一个在模拟磁盘上的文件,除了新建文件系统时可以直接在内存中生成全部空闲的BitMap,其余时候在建立BitMap对象后必须访问位图文件所在的0扇区取得当前位图文件的描述符,然后使用FetchFrom取得位图文件的数据.在对位图进行修改后,必须调用WriteBack将其写回.

位图文件对于文件系统而言是十分重要的,因为文件的增删修改都可能涉及到扇区的增加与减少,空闲的扇区号需要通过查询位图文件获得,而BitMap就是位图文件在内存中的表示.

2、文件头:FileHeader

文件头FileHeader用于表示文件的元数据,如文件大小、文件扇区的分配情况等,因此想要访问一个文件首先要找到该文件的文件头.文件头本身也是一个文件,在nachos的文件系统中,文件头默认占用一个扇区.


#define NumDirect   (int)((SectorSize - 2 * sizeof(int)) / sizeof(int))
#define MaxFileSize     (NumDirect * SectorSize)class FileHeader {public:bool Allocate(BitMap *bitMap, int fileSize);// Initialize a file header, //  including allocating space //  on disk for the file datavoid Deallocate(BitMap *bitMap);          // De-allocate this file's //  data blocksvoid FetchFrom(int sectorNumber);    // Initialize file header from diskvoid WriteBack(int sectorNumber);    // Write modifications to file header//  back to diskint ByteToSector(int offset);  // Convert a byte offset into the file// to the disk sector containing// the byteint FileLength();          // Return the length of the file // in bytesvoid Print();           // Print the contents of the file.private:int numBytes;         // Number of bytes in the fileint numSectors;           // Number of data sectors in the fileint dataSectors[NumDirect];        // Disk sector numbers for each data // block in the file
};

filehdr.cc定义了两个常量,NumDirect为文件头中直接索引的数量,MaxFileSize规定了文件的最大长度,这个值的决定因素如下:nachos的文件头占用一个扇区,除了存储其它两个整数型必要信息外整个扇区都用于存储文件的扇区分配情况,一个扇区能够存放128字节.FileHeader一共有三个数据成员,numBytes为文件长度,numSectors为文件占用的扇区数,dataSectors表示文件扇区的分配情况.

下面对FileHeader的成员函数进行简要介绍.

  1. Allocate:给定位图对象与文件大小,为文件分配指定数目的空闲扇区
  2. Deallocate:给定位图对象,释放文件使用的扇区
  3. FetchFrom:取得存在于位于某扇区的文件头
  4. WriteBack:将文件头写回所在的扇区
  5. ByteToSector:将给定长度的字节数映射到分配给文件的某个扇区
  6. FileLength:返回文件的字节数
  7. Print:打印文件头的基本信息(文件长度、文件扇区的分配情况等)
bool
FileHeader::Allocate(BitMap *freeMap, int fileSize)
{ numBytes = fileSize;numSectors  = divRoundUp(fileSize, SectorSize);if (freeMap->NumClear() < numSectors)return FALSE;     // not enough spacefor (int i = 0; i < numSectors; i++)dataSectors[i] = freeMap->Find();return TRUE;
}void
FileHeader::Deallocate(BitMap *freeMap)
{for (int i = 0; i < numSectors; i++) {ASSERT(freeMap->Test((int) dataSectors[i]));  // ought to be marked!freeMap->Clear((int) dataSectors[i]);}
}void
FileHeader::FetchFrom(int sector)
{synchDisk->ReadSector(sector, (char *)this);
}void
FileHeader::WriteBack(int sector)
{synchDisk->WriteSector(sector, (char *)this);
}int
FileHeader::ByteToSector(int offset)
{return(dataSectors[offset / SectorSize]);
}int
FileHeader::FileLength()
{return numBytes;
}

Allocate函数接收BitMap指针freeMap与整型数fileSize作为参数,freeMap用于获得空闲扇区,fileSize为指定的文件大小.首先使用fileSize设置文件长度,然后使用文件字节数处理扇区字节数并上取整得到需要分配的扇区数.如果freeMap中没有足够的空闲扇区供分配,则返回FALSE.否则循环获取空闲扇区号并填充扇区分配列表dataSectors.最后返回TRUE.Deallocate函数接收BitMap指针作为参数,进入方法后循环调用Clear释放文件所持有的扇区然后返回.

FetchFrom函数接收文件头所在的扇区号sector作为参数,调用synchDisk的ReadSector函数读取指定扇区的信息,用于接收数据的缓冲区为(char*)this,而WriteBack则调用了synchDIsk的WriteSector函数,指定写入数据的缓冲区为(char*)this.这里看上去比较奇怪,但实际上这是将自身序列化以字符流的方式写入与读取从而实现持久化的存储.

ByteToSector接收一个整型参数offset,offset表示文件读写指针的偏移量,该函数首先计算offset/SectorSize得到该偏移位于文件的第几个扇区,然后访问dataSectors的指定位置得到这个扇区的扇区号并返回.FileLength简单地返回文件长度.

总结一下,文件头FileHeader是存储文件元数据的位置,文件的分配、扩容与删除等涉及到扇区更改的操作都需要文件头的参与.文件头可以通过字符流的方式将自己持久存储至某个扇区并作为文件的索引,只要知道文件头的扇区号就可以找到整个文件.

3、打开文件:OpenFIle

打开文件是文件在内存中的表示,通过打开文件对象可以对文件进行读写操作,其作用相当于Linux中的文件描述符fd.


class OpenFile {public:OpenFile(int sector);        // Open a file whose header is located// at "sector" on the disk~OpenFile();          // Close the filevoid Seek(int position);       // Set the position from which to // start reading/writing -- UNIX lseekint Read(char *into, int numBytes); // Read/write bytes from the file,// starting at the implicit position.// Return the # actually read/written,// and increment position in file.int Write(char *from, int numBytes);int ReadAt(char *into, int numBytes, int position);// Read/write bytes from the file,// bypassing the implicit position.int WriteAt(char *from, int numBytes, int position);int Length();            // Return the number of bytes in the// file (this interface is simpler // than the UNIX idiom -- lseek to // end of file, tell, lseek back private:FileHeader *hdr;         // Header for this file int seekPosition;           // Current position within the file
};

OpenFIle类持有两个数据成员,hdr为文件头指针,用于在读写时取得给定扇区的位置,seekPositon则表示文件的读写游标,文件的读取与写入都是从游标的位置进行的.

下面对OpenFile的成员函数进行简要的介绍.

  1. Seek:接收一个整型值作为位置坐标,将游标seekPosition移动至指定位置.
  2. Read:从游标处开始读取指定长度的字节
  3. Write:从游标处开始写入指定长度的字节
  4. ReadAt:从给定位置处开始读取指定长度的字节
  5. WriteAt:从给定位置处开始写入指定长度的字节
  6. Length:返回文件长度.
OpenFile::OpenFile(int sector)
{ hdr = new FileHeader;hdr->FetchFrom(sector);seekPosition = 0;
}OpenFile::~OpenFile()
{delete hdr;
}void
OpenFile::Seek(int position)
{seekPosition = position;
}   int
OpenFile::Read(char *into, int numBytes)
{int result = ReadAt(into, numBytes, seekPosition);seekPosition += result;return result;
}int
OpenFile::Write(char *into, int numBytes)
{int result = WriteAt(into, numBytes, seekPosition);seekPosition += result;return result;
}

OpenFile的构造函数接收一个整型值sector, sector代表该文件的文件头所在的扇区号,该函数首先新建文件头FildHeader对象,然后调用其FetchFrom函数从给定扇区取得文件头信息,最后将文件读写游标归零.析构函数使用delete释放动态建立的文件头对象.

Seek函数接收一个整型值,在函数内部将本对象的文件读写游标移动至指定位置.Read函数接收两个参数,into代表存放读取数据的缓冲区,numBytes代表欲读取的数据长度.Read函数实际调用了ReadAt函数,指定从游标处读取数据,在读取完成后将游标增加等于读取数据的长度的值.Write函数与Read函数类似,实际调用了WriteAt函数.

int
OpenFile::ReadAt(char *into, int numBytes, int position)
{int fileLength = hdr->FileLength();int i, firstSector, lastSector, numSectors;char *buf;if ((numBytes <= 0) || (position >= fileLength))return 0;              // check requestif ((position + numBytes) > fileLength)     numBytes = fileLength - position;DEBUG('f', "Reading %d bytes at %d, from file of length %d.\n",   numBytes, position, fileLength);firstSector = divRoundDown(position, SectorSize);lastSector = divRoundDown(position + numBytes - 1, SectorSize);numSectors = 1 + lastSector - firstSector;// read in all the full and partial sectors that we needbuf = new char[numSectors * SectorSize];for (i = firstSector; i <= lastSector; i++)  synchDisk->ReadSector(hdr->ByteToSector(i * SectorSize), //666&buf[(i - firstSector) * SectorSize]);// copy the part we wantbcopy(&buf[position - (firstSector * SectorSize)], into, numBytes);delete [] buf;return numBytes;
}

ReadAt函数接收三个参数,into表示存放读取到数据的缓冲区,numBytes为欲读取的字节数,position为欲读取的位置.首先通过文件头获取文件长度fileLength,并初始化若干变量.接着判断参数的合法性,如果欲读取数据长度不大于0或欲读取数据位置超出文件长度则返回0.如果从指定位置读取指定长度的数据会读取至文件外部(结束位置大于文件长度),则将其进行截断.

在检查完毕后通过对起始位置与结束位置除以扇区大小并上取整得起始位置与结束位置位于文件的第几个扇区.接着根据欲读取数据需要查询的扇区数目建立一个对应大小的缓冲区buf,然后进入循环,在循环中调用synchDisk的ReadSector函数读取指定扇区.在这里我们要注意传递给ReadSector的两个个参数.第一个参数代表需要读取的扇区号,我们在之前的出的firstSector和LastSector都是对于该文件而言的扇区序数,而并非真实的扇区号,因此需要调用hdr的ByteToSector函数通过偏移量计算出扇区序数再通过dataSectors数组映射返回实际的扇区号.第二个参数代表存放数据的缓冲区,这里并没有将整个buf都传入,因此默认是从缓冲区头部填充数据,这么做会时buf的内容仅为最后一次循环读取的结果.在这里使用计数变量i计算出偏移后传入了该buf中该单元的地址,想当于传入了一个子数组用于承接数据.

因为磁盘的读取是以扇区为基本单位的,但我们想要读取的位置很可能并不位于扇区开头,因此buf中可能具有一些额外的数据,因此使用bcopy从buf中复制我们指定位置与长度的数据至into完成数据的传输.在方法结束前释放动态建立的缓冲数组buf,然后返回实际读取到的数据长度(因为有可能被截断).


int
OpenFile::WriteAt(char *from, int numBytes, int position)
{int fileLength = hdr->FileLength();int i, firstSector, lastSector, numSectors;bool firstAligned, lastAligned;char *buf;if ((numBytes <= 0) || (position >= fileLength))  // For original Nachos file system
//    if ((numBytes <= 0) || (position > fileLength))  // For lab4 ...return 0;              // check requestif ((position + numBytes) > fileLength)numBytes = fileLength - position;DEBUG('f', "Writing %d bytes at %d, from file of length %d.\n",    numBytes, position, fileLength);firstSector = divRoundDown(position, SectorSize);lastSector = divRoundDown(position + numBytes - 1, SectorSize);numSectors = 1 + lastSector - firstSector;buf = new char[numSectors * SectorSize];firstAligned = (bool)(position == (firstSector * SectorSize));lastAligned = (bool)((position + numBytes) == ((lastSector + 1) * SectorSize));// read in first and last sector, if they are to be partially modifiedif (!firstAligned)ReadAt(buf, SectorSize, firstSector * SectorSize); if (!lastAligned && ((firstSector != lastSector) || firstAligned))ReadAt(&buf[(lastSector - firstSector) * SectorSize], SectorSize, lastSector * SectorSize);  // copy in the bytes we want to change bcopy(from, &buf[position - (firstSector * SectorSize)], numBytes);// write modified sectors backfor (i = firstSector; i <= lastSector; i++)  synchDisk->WriteSector(hdr->ByteToSector(i * SectorSize), &buf[(i - firstSector) * SectorSize]);delete [] buf;return numBytes;
}

WriteAt函数接收灿哥参数,from为待写入数据的缓冲数组,numBytes为待写入的数据长度,position为欲写入数据的位置.与ReadAt函数类似,该函数首先得到文件长度、建立局部数据,然后验证参数有效性并选择是否进行截断.因此,如果写入的数据总量超过了文件大小,nachos会简单地进行截断,只写入不会使文件超过建立时大小的字节数.

在得到扇区序数后,这里进行了不一样的操作,通过计算写入头部位置与该扇区头部是否相等判断头部是否对其,即是完整地对该扇区进行写入还是仅仅进行局部写入.对写入尾部也进行相同的判断.因为位于中间的扇区总是被完全地写入,因此无需进行判断.这么做是必要的,因为扇区是磁盘读写的基本单元,如果对一个扇区进行局部修改,必须将该扇区数据提取后进行局部修改再写回,否则会丢失原有数据.下面根据之前计算的对其情况选择是否复制头尾扇区的内容填充缓冲区from.然后下面与ReadAt函数基本相同:循环调用synchDisk的WriteSector函数将from中的数据逐扇区写入磁盘.最后释放动态建立的数组并返回实际写入的字节.

三、总结

本节的分析涉及到了三个联系较为紧密的类.

位图负责管理磁盘中扇区的空闲情况,本身存在于0号扇区,而BitMap是位图在内存中的表示,任何涉及到扇区分配的问题都需要建立BitMap对象读取位图文件实现扇区分配回收操作.在完成后还需要进行写回.

文件头负责表示文件的元数据,如文件的长度和文件的扇区分配、扇区分布情况,文件建立时空间的分配就是从文件头处进行的.FileHeader对象是文件头的内存表示,需要访问文件的详细内容时需要建立FileHeader对象并从该文的文件头所在扇区读取文件头从而实现对文件的管理.

打开文件OpenFile是文件在内存中的表示,对文件的读写操作都是通过OpenFile对象实现的(包括对位图文件的读写操作).OpenFile的读写操作依赖于FileHeader的文件长度信息与扇区分配信息,虽然我们使用文件头管理文件信息,但在这里我们也可以认为OpenFIle是对FildHeader这一数据结构进行操作与管理的类.

在本节中我们了解到了文件头的地位与作用,文件头能够找到文件内容所在的所有扇区,但文件头本身的扇区却没有说明.在下一节我们会对文件系统的高层即Directory与FileSystem的相关源码进行分析,其中Directory即实现了我们所希望的文件名到文件头扇区的映射.

山东大学操作系统课程设计源码分析 filesys(2)相关推荐

  1. 山东大学操作系统课程设计源码分析 filesys(3)

    一.写在前面 本节分析为文件系统相关源码的最后一篇分析,本次分析的重点在于介绍目录与逻辑文件系统系统相关源码,学习文件系统的高层逻辑与宏观结构. 二.源码分析 1.文件目录:Directory 正如上 ...

  2. C语言源码做的运动会管理系统课程设计(源码+课程设计报告)

    一.课程设计的目的: C语言程序设计课程设计是计算机科学与技术专业重要的实践性教学环节之一,本次设计结合实际应用的要求,使课程设计既覆盖C语言的知识点,又接近工程实际需要.目的是通过课程设计的综合训练 ...

  3. C语言源码做的职工工资管理系统课程设计(源码+课程设计报告)

    一.课程设计的目的: C语言课程设计是计算机科学与技术专业重要的实践性教学环节之一,本次设计结合实际应用的要求,使课程设计既覆盖C语言程序设计的知识点,又接近工程实际需要.本次设计的目的是通过课程设计 ...

  4. C语言源码做的班级档案管理系统课程设计(源码+课程设计报告)

    一.课程设计的目的: C语言课程设计是计算机科学与技术专业重要的实践性教学环节之一,本次设计结合实际应用的要求,使课程设计既覆盖C语言程序设计的知识点,又接近工程实际需要.本次设计的目的是通过课程设计 ...

  5. java简单计算器课程设计_java仿windows简易计算器课程设计 源码+报告

    [实例简介] java仿windows简易计算器课程设计 源码+报告 课直接运行. [实例截图] [核心代码] Java课设-简易计算器 └── Java课设-简易计算器 ├── Java课程设计.d ...

  6. html课堂考勤系统源码,考勤管理系统课程设计源码

    考勤管理系统课程设计源码 源码描述: 主体分两个大块 员工信息 个人信息查询,员工信息修改,修改密码,添加用户,删除用户 企业管理 考勤登记,基本工资设置,员工考勤,自动生成变动工资表,自动生成福利费 ...

  7. oracle课程设计代码,Oracle 课程设计源码

    创建主表空间: create tablespace test datafile 'D:\OracleSpace\test' size 20m extent management local; 创建用户 ...

  8. Kafka#4:存储设计 分布式设计 源码分析

    https://sites.google.com/a/mammatustech.com/mammatusmain/kafka-architecture/4-kafka-detailed-archite ...

  9. HTML5期末大作业:化妆品网站设计——大气简洁的品牌化妆品网页(7页) HTML+CSS+JavaScript web前端课程设计源码...

    常见网页设计作业题材有 ​​个人. 美食. 公司. 学校. 旅游. 电商. 宠物. 电器. 茶叶. 家居. 酒店. 舞蹈. 动漫. 明星. 服装. 体育. 化妆品. 物流. 环保. 书籍. 婚纱. 军 ...

最新文章

  1. 2021年大数据HBase(十一):Apache Phoenix的视图操作
  2. java 启动某个类_java – Spring Boot – 如何指定备用启动类? (多个入口点)
  3. java对象序列化克隆_JAVA 对象克隆和序列化
  4. 一篇文章彻底了解清楚什么是负载均衡
  5. python 判断一个点(坐标)是否在一个多边形内利用射线法
  6. electron 菜单栏_如何在Electron JS中添加任务栏图标菜单?
  7. oracle sql语句 只读,Oracle_SQL语句
  8. linux备份文件命令tar.gz,Linux系统tar命令备份数据
  9. flutter优缺点_混合开发框架最全对比,为什么我更推荐Flutter?
  10. 设计原则 —— 针对接口编程而不针对实现编程
  11. 小D课堂 - 新版本微服务springcloud+Docker教程_3-05 服务注册和发现Eureka Server搭建实战...
  12. hivesql 列转行,并用逗号分隔
  13. 网易云动态小视频下载方法
  14. Invalid watch source: undefined A watch source can only be a getter/effect function, a ref, a react
  15. 用SQL语句进行数据分页查询
  16. [日推荐]『我的时间线』记录你的生活
  17. (附源码)springboot车辆管理系统 毕业设计 031034
  18. 电脑很小,电脑声音太小了加满了就是很小声怎么办
  19. Activiti学习(4)简单的请假流程
  20. 2023美赛各题选择及思路分析

热门文章

  1. 达芬奇技术背景和规范
  2. Swift 语言 于 2014 年 9 月 18 日 的 XCode 6.0.1 的更新
  3. 微信小程序实时监测网络状态变化
  4. 四波混频 matlab,四波混频原理及其应用.pdf
  5. android游戏物理引擎开发——粒子系统(三)
  6. Golang 生成压缩包
  7. 最优质的空投糖果——平台币
  8. 张驰课堂:质量人为什么要参加六西格玛绿带培训?
  9. 【课程设计】教学设备管理系统(源码 + 详解)
  10. v-model和input结合使用