2.4文件和目录操作的系统函数
Linux提供一些文件和目录操作的常用系统函数,文件操作命令比如ls,
cp,mv等都是基于这些系统调用实现的。
stat:
读取文件的inode, 把inode中的各种文件属性填入struct stat结构体返回;
假如读一个文件/opt/file,其查找顺序是:
- 1.读出inode表中第2项,也就是根目录的inode,从中找出根目录数据块的位置
- 2.从根目录的数据块中找出文件名为opt的记录,从记录中读出它的inode号
- 3. 读出opt目录的inode,从中找出它的数据块的位置
- 4. 从opt目录的数据块中找出文件名为file的记录,从记录中读出它的inode号
- 5.读出file文件的inode
还有另外两个类似stat的函数:fstat(2)函数,lstat(2)函数
access(2):
检查执行当前进程的用户是否有权限访问某个文件,access去取出文件inode中的st_mode字段,比较访问权限,返回0表示允许访问,-1不允许。
chmod(2)和fchmod(2):
改变文件的访问权限,也就是修改inode中的st_mode字段。
chmod(1)命令是基于chmod(2)实现的。
chown(2)/fchown(2)/lchown(2):
改变文件的所有者和组,也就是修改inode中的User和Group字段。
utime(2):
改边文件访问时间和修改时间,也就是修改inode中的atime和mtime字段。touch(1)命令是基于utime实现的。
truncate(2)/ftruncate(2):
截断文件,修改inode中的Blocks索引项以及块位图中的bit.
link(2):
创建硬链接,就是在目录的数据块中添加一条记录,其中的inode号字段与源文件相同。
syslink(2):
创建符号链接,需要创建一个新的inode,其中st_mode字段的文件类型是符号链接。指向路径名,不是inode,替换掉同名文件,符号链接依然可以正常访问。
ln(1)命令是基于link和symlink函数实现的。
unlink(2):
删除一个链接,如果是符号链接则释放符号链接的inode和数据块,清除inode位图和块位图中相应位。如果是硬链接,从目录的数据块中清除文件名记录,如果当前文件的硬链接数已经是1,还要删除它,同时释放inode和数据块,清除inode位图和块位图相应位,这时文件就真的删除了。
rename(2):
修改文件名,就是修改目录数据块中的文件名记录,如果新旧文件名不在一个目录下,则需要从原目录数据中清楚记录,然后添加到新目录的数据块中。mv(1)命令是基于rename实现的。
readlink(2):
从符号链接的inode或数据块中读出保存的数据。
rmdir(2):
删除一个目录,目录必须是空的(只含.和…)才能删除,释放它的inode和数据块,清除inode位图和块位图的相应位,清除父目录数据块中的记录,父目录的硬链接数减1,rmdir(1)命令是基于rmdir函数实现的。
opendir(3)/readdir(3)/closedir(3):
用于遍历目录数据块中的记录。
目录,是一个特殊的文件,其存放inode号与文件名的映射关系;
2.5VFS
Linux支持各种文件系统格式,ext2,ext3,ext4,fat,ntfs,yaffs等,内核在不同的文件系统格式之上做了一个抽象层,使得文件目录访问等概念成为抽象层概念,对APP提供统一访问接口,由底层驱动去实现不同文件系统的差异,这个抽象层叫虚拟文件系统(VFS, Virtual Filesystem)。
File,dentry,inode,super_block这几个结构体组成了VFS的核心概念。
icache/dcache
访问过的文件或目录,内核都会做cache;
inode_cachep = kmem_cache_create() dentry_cache=KMEM_CACHE()
这两个函数申请的slab可以回收,内存自动释放;
Linux配置回收优先级
(1).free pagecache:
echo 1 >> /proc/sys/vm/drop_caches
(2)free reclaimable slab objects (includes dentries and inodes)
echo 2 >> /proc/sys/vm/drop_caches
(3)free slab objects and pagecache:
echo 3 >> /proc/sys/vm/drop_caches
fuse
Linux支持用户空间实现文件系统,fuse实际上是把内核空间实现的VFS支持接口,放到用户层实现。
三、文件系统一致性
3.1掉电与文件系统一致性
由上一节文件系统的布局分析可知,当操作一个文件时,比如往/a目录下添加一个b,即添加/a/b文件,需要修改inode bitmap, inode table, block bitmap, data block。
这一系列的操作是非原子的,假如任何一个环节掉电,造成某些步骤丢失,就会造成数据的不完整,文件将无法正常访问。
3.2append一个文件的全流程
而硬件是不可能原子执行的,因此会造成不一致性。
3.3 模拟文件系统不一致性案例
(1) 做一个image,用来模拟磁盘
dd if=/dev/zero of=image bs=1024 count=4096
(2).格式化为ext4文件系统
mkfs.ext4 -b 4096 image
(3).mount到test目录,写入一个ok.txt文件
sudo mount -o loop image test/ cd test/ sudo touch ok.txt cd .. sudo umount test
(4).查看磁盘详细信息
(base) leon\@pc:\~/io\$ dumpe2fs image dumpe2fs 1.42.13 (17-May-2015) Filesystem volume name: <none> Last mounted on: /home/leon/io/test Filesystem UUID: 759835e3-9508-4c57-b511-9c4f7e13f0ad Filesystem magic number: 0xEF53 Filesystem revision #: 1 (dynamic) … Inode count: 1024 Block count: 1024 Reserved block count: 51 Free blocks: 982 Free inodes: 1012 First block: 0 Block size: 4096 Fragment size: 4096 Blocks per group: 32768 Fragments per group: 32768 Inodes per group: 1024 … First inode: 11 Inode size: 128 Default directory hash: half_md4 Directory Hash Seed: 9cf91d57-8528-4d39-b6ba-5f8e2e86fcb7 Group 0: (Blocks 0-1023) [ITABLE_ZEROED] Checksum 0x240a, unused inodes 1012 主 superblock at 0, Group descriptors at 1-1 Block bitmap at 2 (+2), Inode bitmap at 18 (+18) Inode表位于 34-65 (+34) 982 free blocks, 1012 free inodes, 2 directories, 1012个未使用的inodes 可用块数: 8-17, 19-33, 67-1023 可用inode数: 13-1024 (base) leon\@pc:\~/io\$
可以看到inode bitmap在18个块。
(5).查看inodebitmap块
dd if=image bs=4096 skip=18 | hexdump -C -n 32
由于ext4默认用掉11个inode,新创建的ok.txt文件后,inode bitmap用掉12位。
(6).现在模拟掉电,修改inode bitmap
vim -b image :%!xxd –g 1 :%!xxd –r
找到inode bitmap块对应地址4096*18=0x12000
将bitmap改为ff 07
电导致的不一致性,会出现各种奇怪的问题,甚至都无法修复;
任何软件的手段只能保持一致性,无法保证不丢失数据。
fsck
人为破坏data block,用fsck修复,修复原理,扫描bitmap和inode table的一致性。早期Linux/Windows系统异常掉电后启动,都用fsck修复磁盘,速度很慢,为提高速度,新系统都采用日志系统方式。
3.4文件系统的日志
将要修改的行为,记录为一个日志,若操作磁盘过程掉电,开机根据日志回放,将磁盘操作全部重做一遍。磁盘操作完成,删除日志。
优点:保持文件系统的一致性,也提高速度。
EXT2/3/4都采用日志系统。
日志的几个阶段:
- 1.开始写日志
- 2.日志区写完日志,commited;
- 3.执行完一条日志,磁盘操作完成,checkpoint。
- 4.操作完成,free日志。
完整的日志方式,相当于每个数据都写了两遍,让系统变很慢,实际工程上会根据数据情况,做部分日志,即日志方式分为三种:速度递增,安全性递减
data=journal: 完整日志;
data = ordered: 只写元数据,且先写完数据块,再写元数据
data=writeback 只写元数据,循序不确定;ubuntu默认方式;
这样日志就分为5个阶段:
3.5文件系统的调试工具
创建一个文件t.txt,df –h
(base) leon@pc:~/io$ **sudo debugfs -R 'stat /home/leon/io/t.txt' /dev/sda7** sudo: 无法解析主机:pc: 连接超时 debugfs 1.42.13 (17-May-2015) Inode: 13896517 Type: regular Mode: 0664 Flags: 0x80000 Generation: 507799365 Version: 0x00000000:00000001 User: 1000 Group: 1000 Size: 17 File ACL: 0 Directory ACL: 0 Links: 1 Blockcount: 8 Fragment: Address: 0 Number: 0 Size: 0 ctime: 0x5f40e439:2182bba0 -- Sat Aug 22 17:24:09 2020 atime: 0x5f40e43b:c9589800 -- Sat Aug 22 17:24:11 2020 mtime: 0x5f40e439:2182bba0 -- Sat Aug 22 17:24:09 2020 crtime: 0x5f40e439:208e98b8 -- Sat Aug 22 17:24:09 2020 Size of extra inode fields: 32 EXTENTS: (0):55636171 (END)
得到文件的数据块55636171
查看数据块内容
sudo **blkcat** /dev/sda7 55636171
sudo dd if=/dev/sda of=1 skip=$((55636171*8+824123392)) bs=512c count=1
sudo debugfs -R 'icheck 55636171' /dev/sda7
debugfs 根据块号查inode号
sudo debugfs -R 'icheck 55636171' /dev/sda7
根据inode号,查文件路径
sudo debugfs -R 'ncheck 13896517' /dev/sda7
3.6Copy On Write文件系统: btrfs
不用日志,实现文件系统一致性。每次写磁盘时,先将更新数据写入一个新的block,当新数据写入成功之后,再更新相关的数据结构指向新block。
COW稳健性系统的实现方式,有利于实现子卷和snapshot,类似git的思想:
四、块I/O流程与I/O调度器
4.1一个块IO的一生
从page cache到bio到request,当APP打开一个文件,内核为文件维护一个pagecache(磁盘的一个副本);
读写文件时如果page cache命中,只会读写内存不操作磁盘;没有命中,才操作磁盘。
在内核用file结构体表示,可见其包含一个inode结构体,一个地址空间;
相关的几个结构体在内核对应关系如下:
可见,当多个进程同时打开同一个文件时,不同的file结构体对应同一个inode和同一个地址空间,地址空间是由一颗radixtree维护(即pagecache),读写文件时,查看对应内存页在page cache中是否命中,若命中直接从内存空间读写;若不命中,申请一个内存页,从磁盘读入数据,挂到page cache的radix tree中。
另外,page cache与磁盘的同步由地址空间操作函数readpage/writepage完成
对磁盘访问,有两种方法:
- a.裸磁盘直接访问;
- b.通过文件系统访问;
它们在内核page cached对应关系如下:
一个address_space对应一个inode
free命令统计的buffer/cached,只是统计上的区别;
buffer=操作裸分区的地址空间+文件系统的元数据地址空间;
cached=文件系统的地址空间(page cached)缓存;
但是对同一个磁盘,裸磁盘和文件系统两种方式同时操作的时候,同一个数据块会被映射到不同的address_space,会产生同步的问题;
在用dd的时候,最好不要操作文件系统数据:
4.2O_DIRECT和O_SYNC
直接操作裸磁盘分区用O_DIRECT,内核没有任何cache,直接操作磁盘;用户可以根据数据特点,在用户空间做cache。O_DIRECT申请内存要用posix_memalign接口;
而O_SYNC依然通过page cache,但是会立即写入同步磁盘;
App通过page cache访问文件和直接操作裸磁盘的模型,与CPU通过cache访问内存和DMA直接访问内存的模型非常类似;
这里page cache是内存,file是磁盘分区数据块。当有一个进程启用O_DIRECT模式,其他进程最好也用O_DIRECT;
4.3BIO 流程blktrace
对于一个pagecache地址空间,指向的是page页,文件系统ext4读取inode,将page转化为磁盘数据块,变成BIO请求;
BIO最终要转化成request,然后request被块设备驱动程序调用完成;
Bio经过三个队列变成request,三进三出
step1:原地蓄势
把bio转化为request,把request放入进程本进程的plug队列;蓄势多个request后,再进行泄洪。
可能合并相邻bio为一个request;
Bio数量>=request数量;
多个进程,泄洪request到电梯;
step2.电梯排序
进程本地的plug队列的request进入到电梯队列,进行再次的合并、排序,执行QoS的排队,之后按照QoS的结果,分发给dispatch队列。电梯内部的实现,可以有各种各样的队列。
比如两个进程需要读邻近的数据块,可以合并为一个request
电梯调度层可以做QoS,设定进程访问IO的优先级;
step3.分发执行dispatch
电梯分发的request,被设备驱动的request_fn()挨个取出来,派发真正的硬件读写命令到硬盘。这个分发的队列,一般就是我们在块设备驱动里面见到的request_queue了。request_queue完成真正的硬件操作;
4.4工具ftrace
do.sh
#!/bin/bash debugfs=/sys/kernel/debug echo nop > $debugfs/tracing/current_tracer echo 0 > $debugfs/tracing/tracing_on echo `pidof read` > $debugfs/tracing/set_ftrace_pid echo function_graph > $debugfs/tracing/current_tracer echo vfs_read > $debugfs/tracing/set_graph_function echo 1 > $debugfs/tracing/tracing_on
执行./read读文件
查看trace过程
sudo cat /sys/kernel/debug/tracing/trace > t.txt
用VIM查看t.txt,用.funcgrahp.vim插件打开可以合并/展开对应函数调用
vim -S ~/.funcgrahp.vim
4.5IO调度算法,CFQ和ionice
查看当前系统的IO调度算法
(base) leon\@pc:/sys/block/sda/queue\$ cat scheduler noop deadline [cfq]
修改调度算法:
sudo echo cfg > scheduler
CFQ调度算法:类似进程调度的CFS算法
ionice –help ionice –c 2 –n 0 dd if=/dev/sda of=/dev/null & //设置nice值=0 ionice –c 2 –n 8 dd if=/dev/sda of=/dev/null & //设置nice值=8
iotop查看IO读写速度,有明显差异
改成deadline策略
echo deadline > scheduler
此时尽管优先级不同,但两个进程IO速度相近了;
4.6cgroup与IO
cd /sys/fs/cgroup/blkio
a.创建群组A,B
Mkdir A;makedir B
cat blkiolweight //默认是500
分别把两个进程放到A和B执行
cgexec -g blkio:A dd if=/dev/sda of=/dev/null iflag=direct & cgexec -g blkio:B dd if=/dev/sda of=/dev/null iflag=direct &
查看io占用iotop
默认是相近
b.修改群权重
echo 50 > blkio.weight
两个进程优先级一样,但IO速度差别很大;
c.cgroup还可以控制阀门,限制最大读速度:
8:0磁盘的主次设备号
echo "8:0 1048576" > /sys/fs/cgroup/blkio/A/blkio.throttle.read_bps_device
带cache读写,这里的限速不起作用;direct方式才生效;
cgroup v2版本支持带cache限速
4.6IO性能调试:iotop, iostat
blktrace跟踪硬盘的各个读写点
blktrace –d /dev/sda –o - |blkparse –I –
dd if=main.c of=t.txt oflag=sync debugfs –R ‘ickeck xxxx’ /dev/sda1 debugfs –R ‘nckeck inode’ /dev/sda1 blkcat /dev/sda1 xxx
精品文章推荐阅读: