一、inode(索引节点)
(1) 理解inode,要从文件储存说起。
文件储存在硬盘上,硬盘的最小存储单位叫做"扇区"(Sector)。每个扇区储存512字节(相当于0.5KB)。操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个"块"(block)。这种由多个扇区组成的"块",是文件存取的最小单位。“块"的大小,最常见的是4KB,即连续八个 sector组成一个 block。
(2) 通常情况下,文件系统会将文件的实际内容和属性分开存放:
文件的属性保存在 inode 中(i 节点)中,每个 inode 都有自己的编号。每个文件各占用一个 inode。不仅如此,inode 中还记录着文件数据所在 block 块的编号;
文件的实际内容保存在 block 中(数据块),类似衣柜的隔断,用来真正保存衣物。每个 block 都有属于自己的编号。当文件太大时,可能会占用多个 block 块。
另外,还有一个 super block(超级块)用于记录整个文件系统的整体信息,包括 inode 和 block 的总量、已经使用量和剩余量,以及文件系统的格式和相关信息等。
如图所示:文件系统先格式化出 inode 和 block 块,假设某文件的权限和属性信息存放到 inode 4 号位置,这个 inode 记录了实际存储文件数据的 block 号有 4 个,分别为 2、7、13、15,由此,操作系统就能快速地找到文件数据的存储位置。
note:
每个inode都有一个号码,操作系统用inode号码来识别不同的文件。Unix/Linux系统内部不使用文件名,而使用inode号码来识别文件。对于系统来说,文件名只是inode号码便于识别的别称或者绰号。表面上,用户通过文件名,打开文件。实际上,系统内部这个过程分成三步:首先,系统找到这个文件名对应的inode号码;其次,通过inode号码,获取inode信息;最后,根据inode信息,找到文件数据所在的block,读出数据。
(3) 联系平时实践,大家格式化硬盘(U盘)时发现有:快速格式化和底层格式化。快速格式化非常快,格式化一个32GB的U盘只要1秒钟,普通格式化格式化速度慢。这两个的差异?其实快速格式化就是只删除了U盘中的硬盘内容管理表(其实就是inode),真正存储的内容没有动。这种格式化的内容是有可能被找回的。
(4) inode本质上是一个结构体,定义如下
struct inode {
struct hlist_node i_hash; /* 哈希表 */
struct list_head i_list; /* 索引节点链表 */
struct list_head i_dentry; /* 目录项链表 */
unsigned long i_ino; /* 节点号 */
atomic_t i_count; /* 引用记数 */
umode_t i_mode; /* 访问权限控制 */
unsigned int i_nlink; /* 硬链接数 */
uid_t i_uid; /* 使用者id */
gid_t i_gid; /* 使用者id组 */
kdev_t i_rdev; /* 实设备标识符 */
loff_t i_size; /* 以字节为单位的文件大小 */
struct timespec i_atime; /* 最后访问时间 */
struct timespec i_mtime; /* 最后修改(modify)时间 */
struct timespec i_ctime; /* 最后改变(change)时间 */
unsigned int i_blkbits; /* 以位为单位的块大小 */
unsigned long i_blksize; /* 以字节为单位的块大小 */
unsigned long i_version; /* 版本号 */
unsigned long i_blocks; /* 文件的块数 */
unsigned short i_bytes; /* 使用的字节数 */
spinlock_t i_lock; /* 自旋锁 */
struct rw_semaphore i_alloc_sem; /* 索引节点信号量 */
struct inode_operations *i_op; /* 索引节点操作表 */
struct file_operations *i_fop; /* 默认的索引节点操作 */
struct super_block *i_sb; /* 相关的超级块 */
struct file_lock *i_flock; /* 文件锁链表 */
struct address_space *i_mapping; /* 相关的地址映射 */
struct address_space i_data; /* 设备地址映射 */
struct dquot *i_dquot[MAXQUOTAS]; /* 节点的磁盘限额 */
struct list_head i_devices; /* 块设备链表 */
struct pipe_inode_info *i_pipe; /* 管道信息 */
struct block_device *i_bdev; /* 块设备驱动 */
unsigned long i_dnotify_mask; /* 目录通知掩码 */
struct dnotify_struct *i_dnotify; /* 目录通知 */
unsigned long i_state; /* 状态标志 */
unsigned long dirtied_when; /* 首次修改时间 */
unsigned int i_flags; /* 文件系统标志 */
unsigned char i_sock; /* 可能是个套接字吧 */
atomic_t i_writecount; /* 写者记数 */
void *i_security; /* 安全模块 */
__u32 i_generation; /* 索引节点版本号 */
union {
void *generic_ip; /* 文件特殊信息 */
} u;
};
可以用stat命令,查看某个文件的inode信息:
二、文件描述符表
(1) 每个进程在Linux内核中都有一个task_struct结构体来维护进程相关的信息,称为进程描述符(Process Descriptor),而在操作系统理论中称为进程控制块 (PCB,Process Control Block)。
struct task_struct {
...
/* open file information */
struct files_struct *files;
...
};
task_struct中有一个指针(struct files_struct *files)指向files_struct结构体(如下所示),称为文件描述符表(我认为这个定义不确切,暂时还没找到这个说法的来源),记录该进程打开的所有文件。该表中有一个域(struct file * fd_array[NR_OPEN_DEFAULT]),为数组,该数组的每个元素指向已打开的文件的指针(已打开的文件在内核中用file 结构体表示,文件描述符表中的指针指向file 结构体)。
struct files_struct
{
atomic_t count; //引用计数 累加
struct fdtable *fdt;
struct fdtable fdtab;
spinlock_t file_lock ____cacheline_aligned_in_smp;
int next_fd;
struct embedded_fd_set close_on_exec_init;
struct embedded_fd_set open_fds_init;
struct file * fd_array[NR_OPEN_DEFAULT]; //文件描述符数组
};
struct file
{
mode_t f_mode;//表示文件是否可读或可写,FMODE_READ或FMODE_WRITE
dev_ t f_rdev ;// 用于/dev/tty
off_t f_ops;//当前文件位移
unsigned short f_flags;//文件标志,O_RDONLY,O_NONBLOCK和O_SYNC
unsigned short f_count;//打开的文件数目
unsigned short f_reada;
struct inode *f_inode;//指向inode的结构指针
struct file_operations *f_op;//文件操作索引指针
}
(2) 用户程序不能直接访问内核中的文件描述符表,而只能使用文件描述符表的索引 (即0、1、2、3这些数字),这些索引就称为文件描述符(File Descriptor),用int 型变量保存。
文件描述符(本质上是个数字)是open系统调用内部由操作系统自动分配的,操作系统分配这个fd时也不是随意分配,操作系统规定,fd从0开始依次增加。linux中文件描述符表是个数组(不是链表),所以这个文件描述符表其实就是一个数组,fd是index,文件表指针是value
当我们去open时,内核会从文件描述符表中挑选一个最小的未被使用的数字给我们返回。也就是说如果之前fd已经占满了0-9,那么我们下次open得到的一定是10.(但是如果上一个fd得到的是9,下一个不一定是10,这是因为可能前面更小的一个fd已经被close释放掉了)
fd中0、1、2已经默认被系统占用了,因此用户进程得到的最小的fd就是3了。
linux内核占用了0、1、2这三个fd是有用的,当我们运行一个程序得到一个进程时,内部就默认已经打开了3个文件,这三个文件对应的fd就是0、1、2。这三个文件分别叫stdin(标准输入)、stdout(标准输出)、stderr(标准错误)。标准输入一般对应的是键盘,标准输出一般是LCD显示器(可以理解为:1对应LCD的设备文件)
printf函数其实就是默认输出到标准输出stdout上了。stdio中还有一个函数叫fpirntf,这个函数就可以指定输出到哪个文件描述符中。
三、文件表
struct file
{
mode_t f_mode;//表示文件是否可读或可写,FMODE_READ或FMODE_WRITE
dev_ t f_rdev ;// 用于/dev/tty
off_t f_ops;//当前文件位移
unsigned short f_flags;//文件标志,O_RDONLY,O_NONBLOCK和O_SYNC
unsigned short f_count;//打开的文件数目
unsigned short f_reada;
struct inode *f_inode;//指向inode的结构指针
struct file_operations *f_op;//文件索引指针
}
内核为所有打开文件维护一张文件表项,每个文件表项包含内容可以由以上结构体看出,其中比较重要的内容有:
a. 文件状态(读 写 添写 同步 非阻塞等)
b. 当前文件偏移量
c. 指向该文件i节点(i节点)的指针
d. 指向该文件操作的指针(file_operations )
file_operations 结构体在linux内核2.6.5定义如下所示:
struct file_operations
{
struct module *owner;
loff_t(*llseek) (struct file *, loff_t, int);
ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *);
ssize_t(*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area) (struct file *, unsigned long,
unsigned long, unsigned long,
unsigned long);
};
总结:
https://blog.51cto.com/13574131/2064574
https://www.jianshu.com/p/0520d6b76318
http://www.ruanyifeng.com/blog/2011/12/inode.html
https://www.cnblogs.com/how-are-you/p/5699257.html
https://blog.csdn.net/jnu_simba/article/details/8806654
https://blog.csdn.net/luotuo44/article/details/17474099
https://www.cnblogs.com/wanghetao/archive/2012/05/28/2521675.html
https://blog.csdn.net/u010944778/article/details/45077565
————————————————
版权声明:本文为CSDN博主「wwwlyj123321」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wwwlyj123321/article/details/100298377