http://www.oenhan.com/block-dump-linux-io
很多情况下开发者调测程序需要在Linux下获取具体的IO的状况,目前常用的IO观察工具用vmstat和iostat,具体功能上说当然是iostat更胜一筹,在IO统计上时间点上更具体精细。但二者都是在全局上看到IO,宏观上的数据对于判断IO到哪个文件上毫无帮助,这个时候block_dump的作用就显现出来了。
一、使用方法:
需要先停掉syslog功能,因为具体IO数据要通过printk输出,如果syslog存在,则会往message产生大量IO,干扰正常结果
1
2
|
suse:~
# service syslog stop
Shutting down syslog services
done
|
然后启动block_dump
1
|
suse:~
# echo 1 > /proc/sys/vm/block_dump
|
先说效果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
suse:~
# dmesg | tail
dmesg(3414): dirtied inode 9594 (LC_MONETARY) on sda1
dmesg(3414): dirtied inode 9238 (LC_COLLATE) on sda1
dmesg(3414): dirtied inode 9241 (LC_TIME) on sda1
dmesg(3414): dirtied inode 9606 (LC_NUMERIC) on sda1
dmesg(3414): dirtied inode 9350 (LC_CTYPE) on sda1
kjournald(506): WRITE block 3683672 on sda1
kjournald(506): WRITE block 3683680 on sda1
kjournald(506): WRITE block 3683688 on sda1
kjournald(506): WRITE block 3683696 on sda1
kjournald(506): WRITE block 3683704 on sda1
kjournald(506): WRITE block 3683712 on sda1
kjournald(506): WRITE block 3683720 on sda1
kjournald(506): WRITE block 3683728 on sda1
kjournald(506): WRITE block 3683736 on sda1
kjournald(506): WRITE block 3683744 on sda1
|
通过dmesg信息可以看到IO正在写那些文件,有进程号,inode号,文件名和磁盘设备名;但每个文件写了多少呢,仅仅通过dirtied inode就看不出来了,还需要分析WRITE block,后面的数字并不是真正的块号,而是内核IO层获取的扇区号,除以8即为块号,然后根据debugfs工具的icheck和ncheck选项,就可以获取该文件系统块属于哪个具体文件,具体请google之。
二、基本原理:
block_dump的原理其实很简单,内核在IO层根据标志block_dump在IO提交给磁盘的关口卡主过关的每一个BIO,将它们的数据打出来:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
void
submit_bio(
int
rw,
struct
bio *bio)
{
int
count = bio_sectors(bio);
bio->bi_rw |= rw;
/*
* If it's a regular read/write or a barrier with data attached,
* go through the normal accounting stuff before submission.
*/
if
(bio_has_data(bio) && !(rw & REQ_DISCARD)) {
if
(rw & WRITE) {
count_vm_events(PGPGOUT, count);
}
else
{
task_io_account_read(bio->bi_size);
count_vm_events(PGPGIN, count);
}
if
(unlikely(block_dump)) {
char
b[BDEVNAME_SIZE];
printk(KERN_DEBUG
"%s(%d): %s block %Lu on %s (%u sectors)n"
,
current->comm, task_pid_nr(current),
(rw & WRITE) ?
"WRITE"
:
"READ"
,
(unsigned
long
long
)bio->bi_sector,
bdevname(bio->bi_bdev, b),
count);
}
}
generic_make_request(bio);
}
|
具体WRITE block块号和文件系统块号之间的对应关系在submit_bh函数中决定
1
|
bio->bi_sector = bh->b_blocknr * (bh->b_size >> 9);
|
inode的block_dump实现是通过block_dump___mark_inode_dirty搞定的,这次把关口架在inode脏数据写回的路上,把每个过关的inode信息打出来:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
void
__mark_inode_dirty(
struct
inode *inode,
int
flags)
{
if
(unlikely(block_dump))
block_dump___mark_inode_dirty(inode);
}
static
noinline
void
block_dump___mark_inode_dirty(
struct
inode *inode)
{
if
(inode->i_ino ||
strcmp
(inode->i_sb->s_id,
"bdev"
)) {
struct
dentry *dentry;
const
char
*name =
"?"
;
dentry = d_find_alias(inode);
if
(dentry) {
spin_lock(&dentry->d_lock);
name = (
const
char
*) dentry->d_name.name;
}
printk(KERN_DEBUG
"%s(%d): dirtied inode %lu (%s) on %sn"
,
current->comm, task_pid_nr(current), inode->i_ino,
name, inode->i_sb->s_id);
if
(dentry) {
spin_unlock(&dentry->d_lock);
dput(dentry);
}
}
|
三、总结
1.内核由很多合适的关口来截获获取的IO信息,不改动内核,也可以用jprobe抢劫很多东西。
2.debugfs在大量的block–>file转换过程总太慢,自己用ext2fs写一个,效率应该能提高很多。