Linux文件系统代码学习笔记--磁盘格式化&block分配

第一部分——准备知识

磁盘格式化这一步骤到底做了什么?
为了读懂代码,首先先掌握几个知识点。

memcpy函数

void *  __cdecl memcpy(_Out_writes_bytes_all_(_Size) void * _Dst, _In_reads_bytes_(_Size) const void * _Src, _In_ size_t _Size);
void *memcpy(void *memTo, const void *memFrom, size_t size)
{
  if((memTo == NULL) || (memFrom == NULL)) //memTo和memFrom必须有效
         return NULL;
  char *tempFrom = (char *)memFrom;             //保存memFrom首地址
  char *tempTo = (char *)memTo;                  //保存memTo首地址 
  while(size -- > 0)                //循环size次,复制memFrom的值到memTo中
         *tempTo++ = *tempFrom++ ;  
  return memTo;
}
struct direct{
    char d_name[DIRSIZ];//目录名
    unsigned int d_ino;//磁盘 i节点标识符id
};
strcpy(dir_buf[0].d_name,"..");//父目录
    dir_buf[0].d_ino = 1;
    strcpy(dir_buf[1].d_name,".");//当前目录root
    dir_buf[1].d_ino = 1;
    strcpy(dir_buf[2].d_name,"etc");//子目录etc目录
    dir_buf[2].d_ino = 2;

    memcpy(disk+DATASTART, &dir_buf, 3*(DIRSIZ+4));//dir_buf中的前3个元素拷贝到磁盘数据区的第1个数据块

Linux文件系统代码学习笔记--磁盘格式化&block分配_第1张图片
dir_buf是一个direct结构体,目录名是12个字节,int是4个字节,一共16个字节,拷贝到char中的连续的16个元素中,char一个元素就是一个字节。

memset函数

void *  __cdecl memset(_Out_writes_bytes_all_(_Size) void * _Dst, _In_ int _Val, _In_ size_t _Size);
memset(disk, 0x00, ((DINODEBLK+FILEBLK+2)*BLOCKSIZ));

把_Dst所指内存区域的前_Size个字节设置成字符 _Val。

软链接和硬链接

  1. 格式化将分区分成等大小的数据块block。
  2. 用户想要去找一个叫sb的文件,先去找inode,从inode得知sb存在哪些数据块。
  3. 硬链接
    假设创建了sb的硬链接文件叫做bs,bs和sb拥有相同的inode的id号,inode相同,存储的数据块也就相同。sb和bs相当于同一个文件,只不过叫不同的名字。一间教室的前门和后门。如果删除任何一个,通过另一个也能访问存储空间,可以理解成同一个存储空间的不同入口。
    不能跨分区使用,sb和bs需要在同一个分区。
    不能针对目录使用。

  1. 软链接
    假设为csb,有自己的inode和数据块,用户访问csb,先找到csb自己的inode结点,然后找到自己的数据块,在找到原文件的inode,再找到原文件的数据块。
    修改原文件,软链接也变,因为原始数据块变了。
    修改软链接,最终也是修改原文件的数据块,原文件也被修改了。
    删除原文件,软链接不能打开了。
    删除原文件,硬链接还可以打开。
    Linux文件系统代码学习笔记--磁盘格式化&block分配_第2张图片

第二部分——代码解读

inode解读

目录项=文件名+磁盘结点标号
Linux文件系统代码学习笔记--磁盘格式化&block分配_第3张图片

文件系统存储结构

格式化是要将磁盘分成等大小的数据块,确定inode的数量和数据块的数量,并做一些初始的写入工作,比如创建根目录和一些必要的目录等等。

    ls -i 查看文件的inode结点

0. 整个磁盘被划分为等大小的块,称为物理块,引导块占第0个物理块,超级块占用第1个物理块,inode结点表占用接下来的物理块,数据块占用剩下的物理块。这里要注意一点,inode结点表是由多个inode结点组成的,inode大小一般小于一个物理块的大小,因此一个物理块可能存放多个inode结点。
1. 一个inode对应一个文件。inode存放的是文件信息,不是文件内容,数据块存放文件内容。 Linux一切皆文件,目录和设备都是文件。当文件是目录时,文件内容就是目录下的文件的信息,当文件是数据文件时,文件内容就是数据。
2.inode中不包含文件的名字,当用户要查找一个文件时,通过文件名
先查找目录项,找到文件信息所在的inode结点,在inode中找到文件的物理块号。
3. 一个inode可以保存多个物理块号(数据块号),因为一个文件可能存放在多个物理块。
4. 磁盘inode和内存inode
磁盘inode在文件创建时就已经创建,存放在磁盘上,内存inode 在文件被打开时才创建,存放在内存中。

format.cpp——格式化

//初始化硬盘
    memset(disk, 0x00, ((DINODEBLK+FILEBLK+2)*BLOCKSIZ));//disk数组初始化,全为0
    /* 0.initialize the passwd */
    passwd[0].p_uid = 2116;//0用户uid=2116
    passwd[0].p_gid = 03;//0用户所属组gid=03
    strcpy(passwd[0].password, "dddd");//0用户密码为dddd

    passwd[1].p_uid = 2117;//1用户uid=2117
    passwd[1].p_gid = 03;//1用户所属组gid=03
    strcpy(passwd[1].password, "bbbb");//1用户密码为bbbb

    passwd[2].p_uid = 2118;//2用户uid
    passwd[2].p_gid = 04;//2用户所属组
    strcpy(passwd[2].password, "abcd"); //2用户密码 

    passwd[3].p_uid = 2119;//3用户
    passwd[3].p_gid = 04;
    strcpy(passwd[3].password, "cccc");

    passwd[4].p_uid = 2120;//4用户
    passwd[4].p_gid = 05;
    strcpy(passwd[4].password, "eeee");

    /* 1.creat the main directory and its sub dir etc and the file password */

    inode = iget(0);   /* 0 empty dinode id*/
    inode->di_number = 1;//关联文件数设为1 
    inode->di_mode = DIEMPTY;//权限设为空权限
    iput(inode);//回收inode结点

    inode = iget(1);   /* 1 main dir id*/
    inode->di_number = 1;
    inode->di_mode = DEFAULTMODE | DIDIR;
    inode->di_size = 3*(DIRSIZ + 4);
    inode->di_addr[0] = 0; /*block 0# is used by the main directory*/

    strcpy(dir_buf[0].d_name,"..");//父目录
    dir_buf[0].d_ino = 1;//磁盘inode结点标号为1
    strcpy(dir_buf[1].d_name,".");//根目录
    dir_buf[1].d_ino = 1;
    strcpy(dir_buf[2].d_name,"etc");//子目录etc目录
    dir_buf[2].d_ino = 2;

    memcpy(disk+DATASTART, &dir_buf, 3*(DIRSIZ+4));//dir_buf中的前3个元素拷贝到磁盘数据区的第1个数据块
    iput(inode);

    inode = iget(2);  /* 2 etc dir id */
    inode->di_number = 1;
    inode->di_mode = DEFAULTMODE | DIDIR;
    inode->di_size = 3*(DIRSIZ + 4);
    inode->di_addr[0] = 1; /*block 1# is used by the etc directory*/

    strcpy(dir_buf[0].d_name,"..");
    dir_buf[0].d_ino = 1;
    strcpy(dir_buf[1].d_name,".");
    dir_buf[1].d_ino = 2;//etc
    strcpy(dir_buf[2].d_name,"password");//子目录是password
    dir_buf[2].d_ino = 3;

    memcpy(disk+DATASTART+BLOCKSIZ*1, dir_buf, 3*(DIRSIZ+4));//dir_buf中的前3个元素拷贝到磁盘数据区的第2个数据块
    iput(inode);

    inode = iget(3);  /* 3 password id */
    inode->di_number = 1;
    inode->di_mode = DEFAULTMODE | DIFILE;
    inode->di_size = BLOCKSIZ;
    inode->di_addr[0] = 2; /*block 2# is used by the password file*/

    for (i=5; i<PWDNUM; i++){//设置其余用户的属性
        passwd[i].p_uid = 0;
        passwd[i].p_gid = 0;
        strcpy(passwd[i].password, " ");  // 密码为空
    }

    memcpy(pwd, passwd, 32*sizeof(struct pwd));//passwd的内容拷贝到pwd中
    memcpy(disk+DATASTART+BLOCKSIZ*2, passwd, BLOCKSIZ);//passwd的内容拷贝到disk的第3个数据块(只拷贝一个数据块的大小)
    iput(inode);
  1. 初始化硬盘
    将硬盘所有的块(引导块+超级块+inode块+数据块)的内容全部置为0;
  2. 初始化数组 passwd
    创建用户:用户uid+用户所属组gid+用户密码;将这些信息存储在passwd[]数组里。
  3. 创建主目录..+子目录.+etc目录+password目录
    这几个目录的关系是 .././etc/passwd
    Linux文件系统代码学习笔记--磁盘格式化&block分配_第4张图片
    这一步具体是怎么创建的,需要细细说明。

1)inode0 用于创建..目录(/的父目录)

    inode = iget(0);   /* 0 empty dinode id*/
    inode->di_number = 1;//关联文件数设为1 
    inode->di_mode = DIEMPTY;//权限设为空权限
    iput(inode);//回收inode结点

两点是父目录,一点是当前目录,为了和Linux系统对应,我们可以这里的将..理解为根目录/的父目录,.理解为根目录/,etc是根目录/的子目录。其实/是没有父目录的,之所以为/创建一个父目录是因为除了/之外,是为了和其他目录的操作保持一致,其他目录都有父目录。
但是由于实际上/没有父目录,因此这里的/的父目录只是占用了inode 0,并不占用block。
2)inode1 & block0 用于创建.目录(根目录/)

    inode = iget(1);   /* 1 main dir id*/
    inode->di_addr[0] = 0; /*block 0# is used by the main directory*/

block0中的内容为:

文件名     inode编号
.. 1
. 1
etc             2

这个地方,..的inode本来是0,源码是不是写错了?
对于所有目录来说,我们都能在它的block中找到父目录的inode编号,它自己对应的inode编号,以及它底下包含的文件的inode编号。
3)inode2 & block1用于创建etc目录(/的子目录)

    inode = iget(2);  /* 2 etc dir id */
    inode->di_addr[0] = 1; /*block 1# is used by the etc directory*/

block1中的内容为:

文件名     inode编号
.. 1
. 2
password        3

4)inode3 & block2 用于创建password文件(/etc目录下的文件)

    inode = iget(3);  /* 3 password id */
    inode->di_addr[0] = 2; /*block 2# is used by the password file*/

block2中的内容为:

p_uid   p_gid   password
2116    03      dddd
2117    03      bbbb
2118    04      abcd
2119    04      cccc
2120    05      eeee

对于所有文件来说,block中存放的就是文件的数据内容。
4. 初始化超级块(superblock)
- 空闲inode数组

/*2. initialize the superblock */

    filsys.s_isize = DINODEBLK;//inode占用的block块数32(注意不是inode的个数)
    filsys.s_fsize = FILEBLK;//data block总块数512

    /*注意:inode存放在block中,共占用32block,一个inode大小为52,可以计算出inode的总个数*/
    filsys.s_ninode = DINODEBLK * BLOCKSIZ/DINODESIZ - 4;//4块被 root的父目录,root,etc,password占用
    filsys.s_nfree = FILEBLK - 3;//空闲block数:root,etc,password已经占用了三块数据块

    for (i=0; i < NICINOD; i++){
        /* begin with 4, 0,1,2,3, is used by main,etc,password */
        filsys.s_inode[i] = 4+i;//空闲inode数组
    }

    filsys.s_pinode = 0;//空闲inode指针,指向s_inode的第0个元素
    filsys.s_rinode = NICINOD + 4; //??

    block_buf[NICFREE-1] = FILEBLK+1;  /*FILEBLK+1 is a flag of end*/
    for (i=0; i<NICFREE-1; i++)
        block_buf[NICFREE-2-i] = FILEBLK-i-1;           //从最后一个数据块开始分配??????

    /*将block map表存放在block中:从最后一个block开始存,每个block存放50个block号,block号和存放的第1个block号相同*/
    memcpy(disk+DATASTART+BLOCKSIZ*(FILEBLK-NICFREE), block_buf, BLOCKSIZ);
    for (i=FILEBLK-2*NICFREE+1; i>2; i-=NICFREE){
        for (j=0; j<NICFREE;j++){
            block_buf[j] = i+j;
        }
        memcpy(disk+DATASTART+BLOCKSIZ*(i-1), block_buf, BLOCKSIZ);
    }
    i += NICFREE;
    j = 1;
    for (; i>3; i--)
    {
        filsys.s_free[NICFREE-j] = i-1; 
        j ++;
    }

    filsys.s_pfree = NICFREE - j+1; //空闲块指针
    memcpy(disk+BLOCKSIZ, &filsys, sizeof(struct filsys));//filsys的内容存放在超级块:拷贝在磁盘的第0块
    return;

超级块的初始化重点要理解空闲block堆栈。试想一下假设用户要创建一个文件夹Lin

mkdir Lin

那么系统要给Lin文件夹分配一个空的inode和一个空的block,那么怎么知道要分配哪一个inode和哪一个block呢?于是在超级块中就要存放空闲inode数组和空闲block栈的信息,以便快速地找到可分配的inode和block。
1)空闲inode数组的初始化——s_inode[]

由于前0,1,2,3inode已经被占用了,0被..占用,1被.占用,2被etc占用,3被passwd占用,因此空闲inode从4开始。

for (i=0; i < NICINOD; i++){ /* begin with 4, 0,1,2,3, is used by main,etc,password */ filsys.s_inode[i] = 4+i;//空闲inode数组 }

2)空闲block栈的初始化——s_free[]
栈大小是50,栈底存放结束标志,因此栈总共能存放49个block编号。磁盘共512个block,有3块已经被用了,所以物理块号从3开始。初始化时从511号(第512块)block开始,向后数49个(一个栈能存放的大小)block到463号block,将这50个block的编号存放在第463号block中,按照这个规律向后推,462号block到414号block,一共49个block的编号存放在414号block中,按照这个规律一直计算下去,512%49=10,最后剩下10个block不够一个栈的大小,就存在栈中。

当要分配block时就从栈顶开始分配block号,当栈为空,就从磁盘block中依次读出一个block存放的block编号,以供后续block的分配,然后栈再为空,又再从磁盘读入block编号。

后续在block分配的代码中,我们可以看到这种存放方式,使得一组新的block编号的载入变得很简单。因为每一组的第一block就是用来存放这一组block编号的,例如 463号block就存放463-511这组block编号,414号block就存放414-462这组block编号。

一组block编号(一组的大小就是栈的大小)
Linux文件系统代码学习笔记--磁盘格式化&block分配_第5张图片
Linux文件系统代码学习笔记--磁盘格式化&block分配_第6张图片

初始化后的空闲block栈

ballfre.cpp——分配block



 static unsigned int block_buf[BLOCKSIZ]; /********************************************************** 函数:balloc 功能:维护超级块中的空闲数据栈,分配空闲数据块,并返回其块号 ***********************************************************/ unsigned int balloc(){ unsigned int free_block; int i; //如果没有空闲盘块 if (filsys.s_nfree == 0){ printf("\nDisk Full!!!\n"); return DISKFULL; } free_block = filsys.s_free[filsys.s_pfree]; //取堆栈中的盘块号(从3号物理块开始分配) if (filsys.s_pfree == NICFREE-1){ //如果堆栈只剩一个块 memcpy(block_buf,disk+DATASTART+(free_block)*BLOCKSIZ,BLOCKSIZ); //从中读取下一组块号 for (i=0; i<NICFREE; i++) filsys.s_free[i] = block_buf[i]; filsys.s_pfree = 0; //设置堆栈指针 }else{//如果堆栈中大于一个盘块 filsys.s_pfree++; //修改堆栈指针 } filsys.s_nfree --; //修改总块数 filsys.s_fmod = SUPDATE; return free_block; }

磁盘格式化后,由于已经用掉了3个block,因此空闲block的编号从3开始。s_free空闲block栈如下所示,栈大小为50,栈底s_free[49]=12,盘块号为12,栈顶s_free[40]=3,即盘块号为3,一共有9个物理块号。s_pfree空闲block栈指针值为40,即指向3号盘块的下标。

那么,当用户登录,开始进行第一次创建时,假设执行命令

mkdir Lin

第一次分配block的具体过程说明如下:
1)查看空闲block数s_nfree,s_nfree>0,表明还有空闲block
2)查找空闲块栈s_free,通过s_pfree找到盘块号3
3)空闲block数s_nfree减1
4)返回空闲block编号,即3
此时,Lin文件夹所用的block就是3号block。

第9次分配block的具体过程说明如下:
1)查看空闲block数s_nfree,s_nfree>0,表明还有空闲block
2)查找空闲块栈s_free,通过s_pfree找到盘块号12
3)栈中只剩下一个盘块,从磁盘中读取下一组块号,盘块号从13开始,到62,共50个物理块号,到栈中。
这里写图片描述
….

这里写图片描述
4)空闲block数s_nfree减1
5)返回空闲block编号,即12
inode的分配和block的分配区别不大。

第三部分— 错误修复和代码完善

  1. Run-Time Check Failure #2 - Stack around the variable ‘passwd’ was corrupted.
    把 project->配置属性->c/c++->代码生成->基本运行时检查 为 默认值 就不会报本异常。具体原因正在研究中。。。
    如果改为其他就有exception。
  2. log.cpp/logout增加
user[i].u_uid = 0;
user[i].u_gid = 0;

你可能感兴趣的:(操作系统,格式化,文件系统,iNode,超级块)