Linux文件系统的内部结构
硬盘实际上是由一些磁性盘片组成的计算机系统的一个设备。文件系统是对该设备的一种多层次的抽象。
第一层抽象:一个磁盘能够储存大量的数据,一个磁盘可以被划分成分区,每个分区可以看作是一个独立的磁盘。
第二层抽象:一个硬盘有一些磁性盘片组成。每个盘片的表面都被划分为很多同心圆,这些同心圆称作磁道,每个磁道又被进一步划分为扇区,每个扇区可以储存一定字节数的数据。扇区是磁盘上的基本储存单元。一个磁盘扇区编号的系统使得我们可以把磁盘视为一系列块的组合。
第三层抽象:文件系统可以用来储存文件内容,文件属性和目录Unix把磁盘块分成了3个部分来储存上述信息:
- 超级块:文件系统的第一个块称为超级块。这个块存放文件系统本身的结构信息。
- i-节点表:每个文件都有一些属性,如大小、文件所有者和最近修改时间等。这些性质被记录在i-节点的文件中。所有的i-节点都有相同的大小,并且i-节点表示这些结构的一个列表。文件系统中每个文件在该表中都有一个i-节点。
- 数据区:数据区用于保存文件内容。
文件有内容和属性,内核将文件内容存放在数据区, 文件属性存放在i-节点,文件名存放在目录。
可以根据上图来描绘根据文件名读取文件内容的过程。
- 系统根据目录名或文件名找到文件对应的i-节点的序号。
- 根据序号确定文件内容储存在磁盘的块序号。
- 根据块序号定位磁盘内容。
有两个比较特别的i-节点,分别是“.”和“..”,前者代表本目录的i-节点序号,后者代表父目录的i-节点。shell命令pwd就是通过遍历“.”与“..”的i-节点序号来得到文件目录的。另外,目录顶端的“.”与“..”的i-节点序号相同,实际应用中经常根据这一特性来判断是否目录遍历到了顶端。
与目录树相关的系统调用
mkdir
命令mkdir用来创建新的目录,它接受命令行上的一个或多个目录名,使用mkdir系统调用:
#include <sys/stat.h>
#include <sys/types.h>
/*****************************
param pathname: 新目录名
param mode:权限位的掩码
return 0 if successed else return -1
*****************************/
int result = mkdir(char *pathname, mode_t mode)
rmdir
命令rmdir用来删除一个目录。它接受命令行上一个或者多个目录名,使用rmdir系统调用
#include <unistd.h>
/*****************************
param path: 目录名
return 0 if successed else return -1
*****************************/
int result = rmdir(const char *path)
rm
命令rm用来从一个目录文件中删除一个记录,它接受命令行上一个或者多个文件名,使用unlink系统调用
#include <unistd.h>
/*****************************
param path: 需要删除的链接名
return 0 if successed else return -1
*****************************/
int result = unlink(const char *path)
unlink函数不能被用来删除目录, 它会减少相应i-节点的连接数。
ln
命令ln用来创建一个文件的链接,使用系统调用link
#include <unistd.h>
/*****************************
param orig:原始链接的名字
parm new:新建链接的名字
return 0 if successed else return -1
*****************************/
int result = link(const char *orig, const char *new)
link生成一个i-节点的链接。新链接包含原始链接的i-节点号并且具有特定的名字。注意:link不能用来生成目录的新链接
mv
命令mv用来改变文件和目录的名字或位置,mv命令使用系统调用rename:
#include <unistd.h>
/*****************************
param from: 原始链接的名字
param to:新建链接的名字
return 0 if successed else return -1
*****************************/
int result = rename(const char *from, const char *to)
cd
cd用来改变进程的当前目录,cd会对进程产生影响,但是不会影响目录。cd命令使用系统调用chdir:
#include <unistd.h>
/*****************************
param path: 要到达的目录
return 0 if successed else return -1
*****************************/
int result = chdir(const char *path)
编写pwd命令
#include <iostream>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
using namespace std;
ino_t get_inode(char*);
void printpathto(ino_t);
void inum_to_name(ino_t, char*, int);
int main(int argc, char *argv[])
{
printpathto(get_inode("."));
putchar('\n');
return 0;
}
void printpathto(ino_t this_inode){
ino_t my_inode;
char its_name[100];
if(get_inode("..") != this_inode){ //根据i-节点号获取父目录
chdir(".."); //进入父目录
inum_to_name(this_inode, its_name,100); //在父目录中搜寻子目录名
my_inode = get_inode(".");
printpathto(my_inode); //递归调用直到到达目录顶层
printf("/%s", its_name);
}
}
void inum_to_name(ino_t inode_to_find, char *namebuf, int buflen){
DIR *dir_ptr;
struct dirent *direntp;
dir_ptr = opendir(".");
if(dir_ptr == NULL){
perror(".");
exit(1);
}
while ((direntp = readdir(dir_ptr)) != NULL) //一直搜索知道找到子目录
if(direntp->d_ino == inode_to_find){
strncpy(namebuf, direntp->d_name, buflen);
namebuf[buflen-1] = '\0';
closedir(dir_ptr);
return;
}
fprintf(stderr, "error looking for inum %d\n", inode_to_find);
exit(1);
}
ino_t get_inode(char *fname){ //获取i-节点序号
struct stat info;
if(stat(fname,&info) == -1){
fprintf(stderr, "Cannot stat");
perror(fname);
exit(1);
}
return info.st_ino;
}