磁盘格式化这一步骤到底做了什么?
为了读懂代码,首先先掌握几个知识点。
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个数据块
dir_buf是一个direct结构体,目录名是12个字节,int是4个字节,一共16个字节,拷贝到char中的连续的16个元素中,char一个元素就是一个字节。
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。
文件系统存储结构
格式化是要将磁盘分成等大小的数据块,确定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 在文件被打开时才创建,存放在内存中。
//初始化硬盘
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)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栈
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的分配区别不大。
user[i].u_uid = 0;
user[i].u_gid = 0;