一个文件除了数据需要存储之外,一些描述信息也需要存储,例如文件类型(常规、目录、符号链接等),权限,文件大小,创建/修改/访问时间等,也就是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[]; /* filename */
};

扩展

真实的文件数据也存储在物理存储体上,被称为数据块。

根据不同的文件类型有以下几种情况

》对于常规文件,文件的数据存储在数据块中。

》对于目录,该目录下的所有文件名和目录名存储在数据块中,注意文件名保存在它所在目录的数据块中,除文件名之外,ls -l命令看到的其它信息都保存在该文件的inode中。注意这个概念:目录也是一种文件,是一种特殊类型的文件。

》对于硬链接,其只是在硬链接所在目录的数据块中增加或修改文件名,并修改硬链接目标inode的链接数(ls显示属性的第二项),并不会增加inode或数据块。

》对于符号链接,如果目标路径名较短则直接保存在inode中以便更快地查找,如果目标路径名较长则分配一个数据块来保存。

》设备文件、FIFO和socket等特殊文件没有数据块,设备文件的主设备号和次设备号保存在inode中。

参考:ext2文件系统了解

inode表元数据,存储在物理存储体上的更多相关文章

  1. InnoDB的表类型,逻辑存储结构,物理存储结构

    表类型 对比Oracle支持的各种表类型,InnoDB存储引擎表更像是Oracle中的索引组织表(index organized table).在InnoDB存储引擎表中,每张表都有个主键,如果在创建 ...

  2. NDB Cluster 存储引擎物理备份

    NDB Cluster 存储引擎物理备份NDB Cluster 存储引擎也是一款事务性存储引擎,和Innodb 一样也有redo 日志.NDBCluter 存储引擎自己提供了备份功能,可以通过相关的命 ...

  3. SQL Server 堆表行存储大小(Record Size)

    一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 堆表行记录存储格式(Heap) 案例分析(Case) 参考文献(References) 二.背 ...

  4. 【Java数据结构学习笔记之一】线性表的存储结构及其代码实现

    应用程序后在那个的数据大致有四种基本的逻辑结构: 集合:数据元素之间只有"同属于一个集合"的关系 线性结构:数据元素之间存在一个对一个的关系 树形结构:数据元素之间存在一个对多个关 ...

  5. MySQL数据库表的设计和优化(上)

    一.单表设计与优化: (1)设计规范化表,消除数据冗余(以使用正确字段类型最明显):数据库范式是确保数据库结构合理,满足各种查询需要.避免数据库操作异常的数据库设计方式.满足范式要求的表,称为规范化表 ...

  6. saiku 元数据存储分析

    一.介绍 使用saiku的人一定对他的元数据存储都特别感兴趣,特别是有分布式管理需求的项目,更是迫切需要了解.其实它是使用Apache的开源项目Jackrabbit管理文件的! 二.代码跟踪 我也是使 ...

  7. 三元组表压缩存储稀疏矩阵实现稀疏矩阵的快速转置(Java语言描述)

    三元组表压缩存储稀疏矩阵实现稀疏矩阵的快速转置(Java语言描述) 用经典矩阵转置算法和普通的三元组矩阵转置在时间复杂度上都是不乐观的.快速转置算法在增加适当存储空间后实现快速转置具体原理见代码注释部 ...

  8. mysql修改表的存储引擎(myisam<=>innodb)

    查看当前数据库的所支持的数据库引擎以及默认数据库引擎 mysql> show engines; +--------------------+---------+----------------- ...

  9. Yii2表单提交(带文件上传)

    今天写一个php的表单提交接口,除了基本的字符串数据,还带文件上传,不用说前端form标签内应该有这些属性 <form enctype="multipart/form-data&quo ...

随机推荐

  1. Spring IOC 中三种注入方式

    项目错误知识点记录 正文 最近在项目的时候,用到Spring框架,Spring框架提供了一种IOC的自动注入功能,可以很轻松的帮助我们创建一个Bean,这样就省的我们四处写new Object()这样 ...

  2. Android 架构 2.界面

    其中,最上层的界面,是变化最频繁的一个层面,也是最复杂最容易出问题的一个层面,如果规划不好,很容易做着做着,又乱成一团了.要规划好界面层,至少应该遵循几条基本的原则: 保持规范性:定义好开发规范,包括 ...

  3. 简单抓取安居客房产数据,并保存到Oracle数据库

    思路和上一篇差不多,先获取网站html文件,使用BeautifulSoup进行解析,将对应属性取出,逐一处理,最后把整理出的记录保存到oracle中,持久化储存. '''Created on 2017 ...

  4. 【mybatis】mybatis 中select 查询 select * 查询出来的数据,字段值带不出来 数据不全

    原来的代码如下: <select id="findByGoodsUid" resultType="com.pisen.cloud.luna.ms.goods.bas ...

  5. Mysql -- Can’t connect to local MySQL server through socket ‘/var/lib/mysql/mysql.sock’解决方法

    启动mysql 报错: ERROR 2002 (HY000): Can’t connect to local MySQL server through socket ‘/var/lib/mysql/m ...

  6. [shell编程] sh脚本异常:/bin/sh^M:bad interpreter: No such file or directory

    转载地址:http://www.cnblogs.com/pipelone/archive/2009/04/17/1437879.html 在Linux中执行.sh脚本,异常/bin/sh^M: bad ...

  7. Oracle中读取数据一些原理研究

    文章很多摘录了 http://blog.163.com/liaoxiangui@126/blog/static/7956964020131069843572/ 同时基于这篇文章的基础上,补充一些学习要 ...

  8. Javascript高级程序设计 -- 第三章 -- 总结

    1.Javascript有几种数据类型 2.变量 Javascript有几种数据类型 JavaScript中有5种简单数据类型(也称为基本数据类型):Undefined.Null.Boolean.Nu ...

  9. kernel简介

    内存管理 一般来看有三种类型的地址:物理地址.线性地址和逻辑地址,逻辑地址的精髓在于将地址分成两部分:段基地址+偏移,翻译的过程如下: 线性地址的精髓在于将所有的内存按照一定的大小分成了一页一页,对多 ...

  10. Centos下输入法全角半角转换

    写个文档 ,发现输入法的设置框没有了,打出来的字全是全角,找了半天终于发现在设置的快捷键. 废话少说,Centos的全角半角转换快捷键是shift+space. 中英文标点的转换快捷键 ctrl+.