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

    文件格式为: Day 制1課 U12 ASSY01 Wrist 1009 0 2018/05/18 09:35:59 Day 制1課 U12 ASSY02 Wrist 1010 0 2018/05/1 ...

  2. access 数据更新语句

    UPDATE YS_POINT AS a, YS_LINE AS b SET a.管线高程 = b.SELEV1WHERE (((a.物探点号)=[b].[起点号]));

  3. 通过API获取 Portus+registry docker仓库信息

    接口获取docker centos 镜像的 tag 列表脚本 # -*- encoding:utf-8 -*- import requests import json ""&quo ...

  4. C#中使用 HttpWebRequest 向网站提交数据

    HttpWebRequest 是 .NET 基类库中的一个类,在命名空间 System.Net 里,用来使用户通过 HTTP 协议和服务器交互. HttpWebRequest 对 HTTP 协议进行了 ...

  5. 利用github和git命令,将本地项目共享到服务器上

    一.步骤 1. 创建项目根目录 mkdir 文件夹名 2. 初始化文件夹 git init 3. 配置用户名和邮箱(第一次配置后,不需要再登录) git config user.name 名字 git ...

  6. 【重点突破】—— fetch()方法介绍

    前言:ant-design-pro的技术组成主要是react+redux+dva+antd+fetch+roadhog,dva在源码包index.js里面导出了fetch,但是如果不想使用fetch库 ...

  7. [TypeScript] Dynamically Allocate Function Types with Conditional Types in TypeScript

    Conditional types take generics one step further and allow you to test for a specific condition, bas ...

  8. Android架构分析之Android消息处理机制(一)

    作者:刘昊昱 博客:http://blog.csdn.net/liuhaoyutz Android版本号:4.4.2 在这个系列文章中我们将来分析Android消息处理机制. 本文介绍了一个使用Han ...

  9. 微信小程序 的文字复制功能如何实现?

    text设置属性  selectable="true" 就可以长按复制了 文章来源:刘俊涛的博客 地址:http://www.cnblogs.com/lovebing 欢迎关注,有 ...

  10. Linux 多线程环境下 进程线程终止函数小结(转)

    pthread_kill: pthread_kill与kill有区别,是向线程发送signal.,大部分signal的默认动作是终止进程的运行,所以,我们才要用signal()去抓信号并加上处理函数. ...