一个文件除了数据需要存储之外,一些描述信息也需要存储,例如文件类型(常规、目录、符号链接等),权限,文件大小,创建/修改/访问时间等,也就是ls -l命令看到的那些信息,这些信息存在inode中而不是数据块中。每个文件都有一个inode,inode存在物理存储体上,并非是RAM结构体(与inode对应的ram结构体为stat)。每个文件仅有一个inode,可能多个文件共用一个inode,如文件的硬链接,硬链接名存储在文件所在目录的数据块中。物理存储体上存储的与文件相关的内容就是inode和数据内容(数据块),其他相关结构都是由此填充而成的RAM结构体,并不存储在物理存储体上。inode唯一标示一个物理实体(文件或目录或软连接或特殊文件(设备文件、FIFO、socket等))。
$ ln ./hello hello2
$ ls -l
total 0
lrwxrwxrwx 1 akaedu akaedu 7 2008-10-25 15:08 halo -> ./hello
-rw-r--r-- 2 akaedu akaedu 0 2008-10-25 15:04 hello
-rw-r--r-- 2 akaedu akaedu 0 2008-10-25 15:04 hello2
hello2和hello除了文件名不一样之外,别的属性都一模一样,并且hello的属性发生了变化,第二栏的数字原本是1,现在变成2了。从根本上说,hello和hello2是同一个文件在文件系统中的两个名字,ls -l第二栏的数字是硬链接数,表示一个文件在文件系统中有几个名字(这些名字可以保存在不同目录的数据块中,或者说可以位于不同的路径下),硬链接数也保存在inode中。既然是同一个文件,inode当然只有一个,所以用ls -l看它们的属性是一模一样的,因为都是从这个inode里读出来的。
linux/fs.h定义了struct inode:
/* * Keep mostly read-only and often accessed (especially for * the RCU path lookup and 'stat' data) fields at the beginning * of the 'struct inode' */ struct inode { umode_t i_mode; unsigned short i_opflags; uid_t i_uid; gid_t i_gid; unsigned int i_flags; #ifdef CONFIG_FS_POSIX_ACL struct posix_acl *i_acl; struct posix_acl *i_default_acl; #endif const struct inode_operations *i_op; struct super_block *i_sb; struct address_space *i_mapping; #ifdef CONFIG_SECURITY void *i_security; #endif /* Stat data, not accessed from path walking */ unsigned long i_ino; /* * Filesystems may only read i_nlink directly. They shall use the * following functions for modification: * * (set|clear|inc|drop)_nlink * inode_(inc|dec)_link_count */ union { const unsigned int i_nlink; unsigned int __i_nlink; }; dev_t i_rdev; /* 若是设备文件,此字段将记录设备的设备号 */ struct timespec i_atime; struct timespec i_mtime; struct timespec i_ctime; spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */ unsigned short i_bytes; blkcnt_t i_blocks; loff_t i_size; #ifdef __NEED_I_SIZE_ORDERED seqcount_t i_size_seqcount; #endif /* Misc */ unsigned long i_state; struct mutex i_mutex; unsigned long dirtied_when; /* jiffies of first dirtying */ struct hlist_node i_hash; struct list_head i_wb_list; /* backing dev IO list */ struct list_head i_lru; /* inode LRU list */ struct list_head i_sb_list; union { struct list_head i_dentry; struct rcu_head i_rcu; }; atomic_t i_count; unsigned int i_blkbits; u64 i_version; atomic_t i_dio_count; atomic_t i_writecount; const struct file_operations *i_fop; /* former ->i_op->default_file_ops */ struct file_lock *i_flock; struct address_space i_data; #ifdef CONFIG_QUOTA struct dquot *i_dquot[MAXQUOTAS]; #endif struct list_head i_devices; union { struct pipe_inode_info *i_pipe; struct block_device *i_bdev; /* 若是块设备,为其对应的block_device结构体指针 */ struct cdev *i_cdev; /* 若是字符设备,为其对应的cdev结构体指针 */ }; __u32 i_generation; #ifdef CONFIG_FSNOTIFY __u32 i_fsnotify_mask; /* all events this inode cares about */ struct hlist_head i_fsnotify_marks; #endif #ifdef CONFIG_IMA atomic_t i_readcount; /* struct files open RO */ #endif void *i_private; /* fs or device private pointer */ };
对于表示设备文件的inode结构,i_rdev字段包含设备编号。linux2.6设备编号分为主设备号和次设备号,前者为dev_t的高12位,后者为dev_t的低20位。下面操作用于从一个inode中获得主设备号和次设备号。
unsigned int iminor(struct inode *inode); unsigned int imajor(struct inode *inode);
inode操作函数
stat(2)函数读取文件的inode,然后把inode中的各种文件属性填入一个struct stat结构体传出给调用者。 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。
struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* inode number */ mode_t st_mode; /* protection */ nlink_t st_nlink; /* number of hard links */ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ dev_t st_rdev; /* device ID (if special file) */ off_t st_size; /* total size, in bytes */ blksize_t st_blksize; /* blocksize for file system I/O */ blkcnt_t st_blocks; /* number of 512B blocks allocated */ time_t st_atime; /* time of last access */ time_t st_mtime; /* time of last modification */ time_t st_ctime; /* time of last status change */ };
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读取目录数据块中的记录,每次返回一个指向struct dirent的指
针,反复读就可以遍历所有记录,所有记录遍历完之后readdir返回NULL。
struct dirent { ino_t d_ino; /* inode number */ off_t d_off; /* offset to the next dirent */ unsigned short d_reclen; /* length of this record */ unsigned char d_type; /* type of file; not supported by all file system types */ char d_name[256]; /* filename */ };
扩展
真实的文件数据也存储在物理存储体上,被称为数据块。
根据不同的文件类型有以下几种情况
》对于常规文件,文件的数据存储在数据块中。
》对于目录,该目录下的所有文件名和目录名存储在数据块中,注意文件名保存在它所在目录的数据块中,除文件名之外,ls -l命令看到的其它信息都保存在该文件的inode中。注意这个概念:目录也是一种文件,是一种特殊类型的文件。
》对于硬链接,其只是在硬链接所在目录的数据块中增加或修改文件名,并修改硬链接目标inode的链接数(ls显示属性的第二项),并不会增加inode或数据块。
》对于符号链接,如果目标路径名较短则直接保存在inode中以便更快地查找,如果目标路径名较长则分配一个数据块来保存。
》设备文件、FIFO和socket等特殊文件没有数据块,设备文件的主设备号和次设备号保存在inode中。
参考:ext2文件系统了解