原标题:兄弟连教育分享Linux系统编程(6)——文件系统
计算机的文件系统是一种存储和组织计算机数据的方法,它使得对其访问和查找变得容易,文件系统使用文件和树形目录的抽象逻辑概念代替了硬盘和光盘等物理设备使用数据块的概念,用户使用文件系统来保存数据不必关心数据实际保存在硬盘(或者光盘)的地址为多少的数据块上,只需要记住这个文件的所属目录和文件名。在写入新数据之前,用户不必关心硬盘上的那个块地址没有被使用,硬盘上的存储空间管理(分配和释放)功能由文件系统自动完成,用户只需要记住数据被写入到了哪个文件中。
文件系统通常使用硬盘和光盘这样的存储设备,并维护文件在设备中的物理位置。但是,实际上文件系统也可能仅仅是一种访问数据的界面而已,实际的数据是通过网络协议(如NFS、SMB、9P等)提供的或者内存上,甚至可能根本没有对应的文件(如proc文件系统)。严格地说,文件系统是一套实现了数据的存储、分级组织、访问和获取等操作的抽象数据类型
随着Linux的不断发展,它所支持的文件格式系统也在迅速扩充,Linux系统核心可以支持十多种文件系统类型:Btrfs、JFS、ReiserFS、ext、ext2、ext3、ext4、ISO9660、XFS、Minx、MSDOS、UMSDOS、VFAT、NTFS、HPFS、NFS、SMB、SysV、PROC等。
1.Linux磁盘分区和目录
Linux发行版本之间的差别很少,差别主要表现在系统管理的特色工具以及软件包管理方式的不同。目录结构基本上都是一样的。Windows的文件结构是多个并列的树状结构,最顶部的是不同的磁盘(分区),如:C,D,E,F等。
Linux的文件结构是单个的树状结构.可以用tree进行展示。在Ubuntu下安装tree(sudoapt-getinstalltree),并可通过命令来查看。
每次安装系统的时候我们都会进行分区,Linux下磁盘分区和目录的关系如下:
任何一个分区都必须挂载到某个目录上。
目录是逻辑上的区分。分区是物理上的区分。
磁盘Linux分区都必须挂载到目录树中的某个具体的目录上才能进行读写操作。
根目录是所有Linux的文件和目录所在的地方,需要挂载上一个磁盘分区。
以下是我们可能存在的一种目录和分区关系:
2.Mount挂载和NFS简介
挂载的概念:当要使用某个设备时,例如要读取硬盘中的一个格式化好的分区、光盘或软件等设备时,必须先把这些设备对应到某个目录上,而这个目录就称为“挂载点(mountpoint)”,这样才可以读取这些设备,而这些对应的动作就是“挂载”。将物理分区细节屏蔽掉。用户只有统一的逻辑概念。所有的东西都是文件。Mount命令可以实现挂载:
mount[-fnrsvw][-tvfstype][-ooptions]devicedir
3.文件类型
Linux下面的文件类型主要有:
a)普通文件:C语言元代码、SHELL脚本、二进制的可执行文件等。分为纯文本和二进制。
b)目录文件:目录,存储文件的唯一地方。
c)链接文件:指向同一个文件或目录的的文件。
d)特殊文件:与系统外设相关的,通常在/dev下面。分为块设备和字符设备。
可以通过ls–l,file,stat几个命令来查看文件的类型等相关信息。
4.文件存储结构
Linux正统的文件系统(如ext2、ext3)一个文件由目录项、inode和数据块组成。
目录项:包括文件名和inode节点号。
Inode:又称文件索引节点,是文件基本信息的存放地和数据块指针存放地。
数据块:文件的具体内容存放地。
Linux正统的文件系统(如ext2、3等)将硬盘分区时会划分出目录块、inodeTable区块和datablock数据区域。一个文件由一个目录项、inode和数据区域块组成。Inode包含文件的属性(如读写属性、owner等,以及指向数据块的指针),数据区域块则是文件内容。当查看某个文件时,会先从inodetable中查出文件属性及数据存放点,再从数据块中读取数据。
5.软连接、硬链接
软链接和硬链接是我们常见的两种概念:
硬连接:是给文件一个副本,同时建立两者之间的连接关系。修改其中一个,与其连接的文件同时被修改。如果删除其中[color=red]任意一个[/color]其余的文件将不受影响。
软连接:也叫符号连接,他只是对源文件在新的位置建立一个“快捷(借用一下wondows常用词)”,所以,当源文件删除时,符号连接的文件将成为无源之水->仅仅剩下个文件名了,当然删除这个连接,也不会影响到源文件,但对连接文件的使用、引用都是直接调用源文件的。
硬链接和软链接的区别:
1:硬链接原文件和新文件的inode编号一致。而软链接不一样。
2:对原文件删除,会导致软链接不可用,而硬链接不受影响。
3:对原文件的修改,软、硬链接文件内容也一样的修改,因为都是指向同一个文件内容的。
6、常见目录解释
Linux各种发行版的目录结构基本一致,各个目录简单介绍如下:
/:根目录
/bin:做为基础系统所需要的最基础的命令就是放在这里。比如ls、cp、mkdir等命令;功能和/usr/bin类似,这个目录中的文件都是可执行的,普通用户都可以使用的命令。
/boot:Linux的内核及引导系统程序所需要的文件,比如vmlinuzinitrd.img文件都位于这个目录中。在一般情况下,GRUB或LILO系统引导管理器也位于这个目录;启动装载文件存放位置,如kernels,initrd,grub。一般是一个独立的分区。
/dev:一些必要的设备,声卡、磁盘等。还有如/dev/null./dev/console/dev/zero/dev/full等。
/etc:系统的配置文件存放地.一些服务器的配置文件也在这里;比如用户帐号及密码配置文件;
/etc/opt:/opt对应的配置文件
/etc/X11:Xwindows系统配置文件
/etc/xml:XML配置文件
……
/home:用户工作目录,和个人配置文件,如个人环境变量等,所有的账号分配一个工作目录。一般是一个独立的分区。
/lib:库文件存放地。bin和sbin需要的库文件。类似windows的DLL。
/media:可拆卸的媒介挂载点,如CD-ROMs、移动硬盘、U盘,系统默认会挂载到这里来。
/mnt:临时挂载文件系统。这个目录一般是用于存放挂载储存设备的挂载目录的,比如有cdrom等目录。可以参看/etc/fstab的定义。
/opt:可选的应用程序包。
/proc:操作系统运行时,进程(正在运行中的程序)信息及内核信息(比如cpu、硬盘分区、内存信息等)存放在这里。/proc目录伪装的文件系统proc的挂载目录,proc并不是真正的文件系统,它的定义可以参见/etc/fstab。
/root:Root用户的工作目录
/sbin:和bin类似,是一些可执行文件,不过不是所有用户都需要的,一般是系统管理所需要使用得到的。
/tmp:系统的临时文件,一般系统重启不会被保存。
/usr:包含了系统用户工具和程序。
/usr/bin:非必须的普通用户可执行命令
/usr/include:标准头文件
/usr/lib:/usr/bin/和/usr/sbin/的库文件
/usr/sbin:非必须的可执行文件
/usr/src:内核源码
/usr/X11R6:XWindowSystem,Version11,Release6.
/srv:该目录存放一些服务启动之后需要提取的数据
下面介绍一下文件和目录操作常用的系统函数,常用的文件操作命令如ls、cp、mv等也是基于这些函数实现的。本节的侧重点在于讲解这些函数的工作原理,而不是如何使用它们,理解了实现原理之后再看这些函数的用法就很简单了,请读者自己查阅ManPage了解其用法。
stat(2)函数读取文件的inode,然后把inode中的各种文件属性填入一个structstat结构体传出给调用者。stat(1)命令是基于stat函数实现的。stat需要根据传入的文件路径找到inode,假设一个路径是/opt/file,则查找的顺序是:
1读出inode表中第2项,也就是根目录的inode,从中找出根目录数据块的位置
2从根目录的数据块中找出文件名为opt的记录,从记录中读出它的inode号
3读出opt目录的inode,从中找出它的数据块的位置
4从opt目录的数据块中找出文件名为file的记录,从记录中读出它的inode号
5读出file文件的inode
还有另外两个类似stat的函数:fstat(2)函数传入一个已打开的文件描述符,传出inode信息,lstat(2)函数也是传入路径传出inode信息,但是和stat函数有一点不同,当文件是一个符号链接时,stat(2)函数传出的是它所指向的目标文件的inode,而lstat函数传出的就是符号链接文件本身的inode。
access(2)函数检查执行当前进程的用户是否有权限访问某个文件,传入文件路径和要执行的访问操作(读/写/执行),access函数取出文件inode中的st_mode字段,比较一下访问权限,然后返回0表示允许访问,返回-1表示错误或不允许访问。
chmod(2)和fchmod(2)函数改变文件的访问权限,也就是修改inode中的st_mode字段。这两个函数的区别类似于stat/fstat。chmod(1)命令是基于chmod函数实现的。
chown(2)/fchown(2)/lchown(2)改变文件的所有者和组,也就是修改inode中的User和Group字段,只有超级用户才能正确调用这几个函数,这几个函数之间的区别类似于stat/fstat/lstat。chown(1)命令是基于chown函数实现的。
utime(2)函数改变文件的访问时间和修改时间,也就是修改inode中的atime和mtime字段。touch(1)命令是基于utime函数实现的。
truncate(2)和ftruncate(2)函数把文件截断到某个长度,如果新的长度比原来的长度短,则后面的数据被截掉了,如果新的长度比原来的长度长,则后面多出来的部分用0填充,这需要修改inode中的Blocks索引项以及块位图中相应的bit。这两个函数的区别类似于stat/fstat。
link(2)函数创建硬链接,其原理是在目录的数据块中添加一条新记录,其中的inode号字段和原文件相同。symlink(2)函数创建一个符号链接,这需要创建一个新的inode,其中st_mode字段的文件类型是符号链接,原文件的路径保存在inode中或者分配一个数据块来保存。ln(1)命令是基于link和symlink函数实现的。
unlink(2)函数删除一个链接。如果是符号链接则释放这个符号链接的inode和数据块,清除inode位图和块位图中相应的位。如果是硬链接则从目录的数据块中清除一条文件名记录,如果当前文件的硬链接数已经是1了还要删除它,就同时释放它的inode和数据块,清除inode位图和块位图中相应的位,这样就真的删除文件了。unlink(1)命令和rm(1)命令是基于unlink函数实现的。
rename(2)函数改变文件名,需要修改目录数据块中的文件名记录,如果原文件名和新文件名不在一个目录下则需要从原目录数据块中清除一条记录然后添加到新目录的数据块中。mv(1)命令是基于rename函数实现的,因此在同一分区的不同目录中移动文件并不需要复制和删除文件的inode和数据块,只需要一个改名操作,即使要移动整个目录,这个目录下有很多子目录和文件也要随着一起移动,移动操作也只是对顶级目录的改名操作,很快就能完成。但是,如果在不同的分区之间移动文件就必须复制和删除inode和数据块,如果要移动整个目录,所有子目录和文件都要复制删除,这就很慢了。
readlink(2)函数读取一个符号链接所指向的目标路径,其原理是从符号链接的inode或数据块中读出保存的数据,这就是目标路径。
mkdir(2)函数创建新的目录,要做的操作是在它的父目录数据块中添加一条记录,然后分配新的inode和数据块,inode的st_mode字段的文件类型是目录,在数据块中填两个记录,分别是.和..,由于..表示父目录,因此父目录的硬链接数要加1。mkdir(1)命令是基于mkdir函数实现的。
rmdir(2)函数删除一个目录,这个目录必须是空的(只包含.和..)才能删除,要做的操作是释放它的inode和数据块,清除inode位图和块位图中相应的位,清除父目录数据块中的记录,父目录的硬链接数要减1。rmdir(1)命令是基于rmdir函数实现的。
opendir(3)/readdir(3)/closedir(3)用于遍历目录数据块中的记录。opendir打开一个目录,返回一个DIR*指针代表这个目录,它是一个类似FILE*指针的句柄,closedir用于关闭这个句柄,把DIR*指针传给readdir读取目录数据块中的记录,每次返回一个指向structdirent的指针,反复读就可以遍历所有记录,所有记录遍历完之后readdir返回NULL。结构体structdirent的定义如下:
structdirent{
ino_td_ino;/*inodenumber*/
off_td_off;/*offsettothenextdirent*/
unsignedshortd_reclen;/*lengthofthisrecord*/
unsignedchard_type;/*typeoffile*/
chard_name[256];/*filename*/
};
下面这个例子是递归地打印出一个目录下的所有子目录和文件,类似ls-R。
#include
#include
#include
#include
#include
#include
#defineMAX_PATH1024
/*dirwalk:applyfcntoallfilesindir*/
voiddirwalk(char*dir,void(*fcn)(char*))
{
charname[MAX_PATH];
structdirent*dp;
DIR*dfd;
if((dfd=opendir(dir))==NULL){
fprintf(stderr,"dirwalk:can'topen%s\n",dir);
return;
}
while((dp=readdir(dfd))!=NULL){
if(strcmp(dp->d_name,".")==0
||strcmp(dp->d_name,"..")==0)
continue;/*skipselfandparent*/
if(strlen(dir)+strlen(dp->d_name)+2>sizeof(name))
fprintf(stderr,"dirwalk:name%s%stoolong\n",
dir,dp->d_name);
else{
sprintf(name,"%s/%s",dir,dp->d_name);
(*fcn)(name);
}
}
closedir(dfd);
}
/*fsize:printthesizeandnameoffile"name"*/
voidfsize(char*name)
{
structstatstbuf;
if(stat(name,&stbuf)==-1){
fprintf(stderr,"fsize:can'taccess%s\n",name);
return;
}
if((stbuf.st_mode&S_IFMT)==S_IFDIR)
dirwalk(name,fsize);
printf("%8ld%s\n",stbuf.st_size,name);
}
intmain(intargc,char**argv)
{
if(argc==1)/*default:currentdirectory*/
fsize(".");
else
while(--argc>0)
fsize(*++argv);
return0;
责任编辑: