由来:公司内部外网记录日志的方式如今都是经过Nginx模块收到数据发送到系统消息队列,而后由另一个进程来从消息队列读取而后写回磁盘这样的操做,尽可能的减小Nginx的阻塞。node
可是因为System/V消息队列在使用前须要规定消息长度,且结构不同须要从新定义消息格式等等...因此在增长需求的时候须要修改代码并从新编译,这样带来的坏处可想而知,外网服务器也会所以重启。shell
因此组内有同事准备在Nginx中加入异步写日志的功能,大体方式就是将数据写入到一块内存而后由另一个进程读取而后flush到磁盘,或者直接使用同步写的方式。而后测试对比后发现其实同步写和异步写差异很小。数据库
并且最大的疑惑就是,Nginx的多进程写在没有应用层加锁的状况下是写到同一个日志文件的,究竟是应该把日志写到同一个文件下仍是按照进程PID来分割日志呢。若是应用层不加锁会致使文件写混乱吗?缓存
好了,说了那么多屁话,其实今天讨论的主题和这个功能需求没多大关系。安全
不过也是由于这个勾起了我研究内核中文件缓存的欲望。服务器
下面经过这几天的资料收集,简单的介绍一下在系统调用read/write调用的时候底层到底发生了什么等等...并发
因为主题是文件和文件系统,那么首先第一个要了解的是什么是文件。app
一 、文件的描述异步
文件实际上是一种对磁盘中存储的一堆零散的数据的一种描述,在Linux上,一个文件由一个inode 表示。inode在系统管理员看来是每个文件的惟一标识,在系统里面,inode是一个结构,存储了关于这个文件的大部分信息。测试
命令 stat [file]能够看到某个文件的信息
inode号就是这个文件的惟一标识,能够看作是数据库中的主键。一个inode 通常占了128KB或者是256KB,是的,有可能比文件自己还大。
inode中存储了一个文件的如下信息:
1.文件大小
2.文件的存储位置
3.用户的GID, UID
4.文件的访问权限
5.时间戳
6.硬连接数()
将inode直观的展示出来,而后根据inode来说解整个文件系统就显得很容易理解了。
不一样于数据库的自增主键,inode号在系统中是会用完的,查看系统的inode总体信息能够用命令
df -i
是的,若是你的系统中零散的小文件不少,是会浪费掉不少的inode的,有可能致使的状况就是磁盘任然有空间可是建立文件缺失败了。
若是为一个文件建立了一个硬连接,就是将不一样的文件名指向了相同的inode,跟文件路径无关,由于inode没有存储文件路径。
系统在发现一个文件的Links == 0 的时候就会删除对应的文件。
以上是属于系统管理员的,若是你不止想了解这些,请往下看。
inode就是一个文件的一部分描述,不是所有,在内核中,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; };
纵观整个inode的C语言描述,没有发现关于文件名的东西,也就是说文件名不禁inode保存,实际上系统是不关心文件名的,对于系统中任何的操做,大部分状况下你都是经过文件名来作的,但系统最终都要经过找到文件对应的inode来操做文件,由inode结构中 *i_op指向的接口来操做。
系统是怎样经过文件名找到inode的?
要想明白这一点,就须要知道在内核中,目录也是一个文件,也有对应的inode,只不过inode中存储文件实际内容的不是文件内容而是一个 dentry(dir entry)结构。
好比说在目录 /data/shells/text.txt中,test/既是一个文件也是一个目录
找到根目录/data/的'data' dentry,根据'data' dentry中的inode找到'shells' dentry和inode,而后递归的查找下去,最终找到test.txt的inode.
文件名就存在于dentry中,路径中的每一级的路径名也算作是其文件名。
在dentry结构中有一个指向父节点的指针,也就是 '../',值得一提的是 '..'是指向上层目录的一个硬连接
inode的基本介绍就算完了,下面会介绍一下每一个进程是怎样关联到每一个文件的,也就是文件描述符那一块。而后介绍一下多个进程对同一个文件操做的时候并发问题以及写操做过程当中缓存结构的管理。