文件系统损坏通常导致进程(或系统)挂起也就不足为奇了。
但是有时,看起来干净的文件系统仍可能导致进程挂起。
在本文中,我们介绍了创建具有空扩展属性(EA)条目的,启用访问控制列表(ACL)的增强型日记文件系统(JFS2)索引节点的方法,并研究了它如何导致进程挂起。 本文还概述了解决此类JFS2索引节点导致的进程挂起的方法。
设置AIXC ACL的扩展权限
AIXC ACL包括基本权限和扩展权限。 基本权限是分配给文件所有者,文件组和其他用户的传统文件访问模式。 扩展权限通过允许,拒绝或指定特定个人,组或用户和组组合的访问模式来修改(所有者,组或其他人的)基本文件权限。
要为/ testfs / foo文件启用ACL并向用户添加特定权限,我们可以使用acledit
命令:
# export EDITOR=/usr/bin/vi; acledit /testfs/foo
然后,我们可以使用vi
命令将扩展权限更改为enabled
并添加特定权限:
*
* ACL_type AIXC
*
attributes:
base permissions
owner(root): rw-
group(system): r--
others: r--
extended permissions
enabled
permit rwx u:bin
Should the modified ACL be applied? (yes) or (no) yes
我们可以运行aclget
检查文件的ACL。
inode中的扩展属性描述符
许多应用程序要求具有将可变长度控制信息与文件系统对象相关联的能力。 这些对象称为扩展属性或EA。 di_ea
节点di_ea
的EA描述符描述了di_ea
节点是否为其存储了任何EA。 如果索引节点确实具有与其关联的EA,则EA描述符还将描述与索引节点关联的索引节点扩展。 EA描述符在j2_types.h
和ead_t
定义:
typedef struct {
uint8 flag; /* 1: flags */
uint8 nEntry; /* 1: */
uint8 len; /* 1: length in unit of fsblksize */
uint8 addr1; /* 1: address in unit of fsblksize */
uint32 addr2; /* 4: address in unit of fsblksize */
uint16 type; /* 2: ea type */
int16 nblocks; /* 2: nBlocks for outline pages */
int32 rsrvd; /* 4: */
} ead_t; /* 16 */
创建一个具有空EA条目的启用ACL的JFS2索引节点
启用ACL后,inode的di_ea.nEntry通常不会为零。 为了进行测试,我们需要为启用了ACL的inode故意将di_ea.nEntry设置为0。 我们有几种方法可以完成它。
使用fsdb修改inode
首先,我们创建一个测试文件:
# echo "a test file" > /testfs/foo
# sync
然后,我们获得索引节点编号:
# ls -i /testfs/foo
3 /testfs/foo
在卸载文件系统后,可以使用fsdb修改磁盘上的inode:
# umount /testfs
# fsdb /testfs
File System: /testfs
File System Size: 2620952 (512 byte blocks)
Aggregate Block Size: 4096
Allocation Group Size: 8192 (aggregate blocks)
> i 3
Inode 3 at block 33, offset 0x600:
……
[5] di_nlink: 1 [22] di_ixpxd.addr2: 0x00000021
[6] di_mode: 0x000081a4 di_ixpxd.address: 33
0100644 -rw-r--r-- [24] di_uid: 0
……
[13] di_ea.flag: 0x00 [30] di_ea.len: 0
EAv1 [31] di_ea.addr1: 0x00
[15] di_ea.nEntry: 0x00 [32] di_ea.addr2: 0x00000000
[16] di_ea.type: 0x0000 di_ea.address: 0
[34] di_ea.nblocks: 0
change_inode: [m]odify, [e]a, [t]ree, or e[x]it > m 6 0x020081a4
change_inode: [m]odify, [e]a, [t]ree, or e[x]it > m 16 2
Inode 3 at block 33, offset 0x600:
……
Inode 3 at block 33, offset 0x600:
……
[5] di_nlink: 1 [22] di_ixpxd.addr2: 0x00000021
[6] di_mode: 0x020081a4 di_ixpxd.address: 33
0100644 -rw-r--r-- [24] di_uid: 0
……
[13] di_ea.flag: 0x00 [30] di_ea.len: 0
EAv1 [31] di_ea.addr1: 0x00
[15] di_ea.nEntry: 0x00 [32] di_ea.addr2: 0x00000000
[16] di_ea.type: 0x0002 di_ea.address: 0
[34] di_ea.nblocks: 0
change_inode: [m]odify, [e]a, [t]ree, or e[x]it > x
> q
现在,我们运行fscck
命令来检查/ testfs:
# fsck -yvv /testfs
The current volume is: /dev/fslv00
Primary superblock is valid.
Superblock s_state = 0x0 mode = 0x3
*** Phase 1 - Initial inode scan
*** Phase 2 - Process remaining directories
*** Phase 3 - Process remaining files
*** Phase 4 - Check and repair inode allocation map
*** Phase 5 - Check and repair block allocation map
File system is clean.
输出显示fsck
命令无法检测到具有空EA条目的启用ACL的JFS2索引节点。
使用kdb修改inode
在IBM®AIX®6.1和AIX 7.1中,可以在安装文件系统时使用kdb
命令修改内存中的inode。
(0)> i2 -i 3
ADDRESS DEVICE I_NUM/FS COUNT TYPE FLAG
F1000A00588F6880 8000000A0000000C 3/16 00000 VREG
……
On-disk Persistent Inode @ 0xF1000A00588F6A20:
uid..........0x00000000 gid..........0x00000000
mode......0x000081A4 mode.........-rw-r--r--- mode.........0100644
……
ea.flag......0x00000000 ea.address...0x0000000000000000
ea.addr1.....0x00000000 ea.addr2.....0x00000000
ea.nEntry....0x00000000 ea.len.........0x00000000
ea.type.......0x00000000
(0)> mw 0xF1000A00588F6A20+0x3C
F1000A00588F6A5C: 000081A4 = 020081a4
F1000A00588F6A60: 00000000 = .
(0)> mw0xF1000A00588F6A20+0x88
F1000A00588F6AA8: 00000000 = 00020000
F1000A00588F6AAC: 00000000 = .
再次检查inode以确认更改:
(0)> i2 F1000A00588F6880
……
On-disk Persistent Inode @ 0xF1000A00588F6A20:
uid..........0x00000000 gid..........0x00000000
mode......0x020081A4 mode.........-rw-r--r--- mode.........0100644
……
ea.flag......0x00000000 ea.address...0x0000000000000000
ea.addr1.....0x00000000 ea.addr2.....0x00000000
ea.nEntry....0x00000000 ea.len.........0x00000000
ea.type.......0x00000002
typeNames....
AIXACL
故障排除过程挂起
当进程打开使用上述方法创建的索引节点时,该进程可能会挂起:
# more foo
# ps ax|grep more
25100524 pts/3 A 0:25 more foo
当更多进程尝试打开该文件并且挂起进程无法终止时,处理器系统时间将急剧增加:
# kill -9 25100524
# ps ax|grep more
25100524 pts/3 A 0:57 more foo
仅当线程从内核模式返回时才传递信号。 因此,我们需要检查线程当前在内核模式下运行的功能以及为什么它无法返回用户模式。 在这种情况下, Trace
和kdb
是非常有用的命令。
使用kdb检查挂起过程
# kdb
(0)> th * |grep more
pvthread+006200 98*more RUN 062027 06D 0 0
pvthread+00EB00 235!more RUN 0EB039 078 2 0
pvthread+015800 344!more RUN 158047 06C 7 0
(0)> f 344
pvthread+015800 STACK:
Use current context [F00000002FF47600] of cpu 7
[00009518].simple_lock+000018 ()
[00274808]iPut+000054 (??, ??)
[20646678]20646678 ()
[DEADBEEB]DEADBEEB ()
[0044FE64]openpnp+000788 (??, ??, ??, ??, ??)
[0044FF9C]openpath+0000BC (??, ??, ??, ??, ??, ??, ??)
[0045027C]copen+000218 (??, ??, ??, ??, ??)
[0044F678]kopen+00001C (??, ??, ??)
(0)> f 344
pvthread+015800 STACK:
Use current context [F00000002FF47600] of cpu 7
[0000B598].fetch_and_addlp+000018 ()
[003DAEEC]lookuppn+0000D8 (??, ??, ??, ??, ??, ??, ??, ??)
[0044FE64]openpnp+000788 (??, ??, ??, ??, ??)
[0044FF9C]openpath+0000BC (??, ??, ??, ??, ??, ??, ??)
[0045027C]copen+000218 (??, ??, ??, ??, ??)
[0044F678]kopen+00001C (??, ??, ??)
kdb
命令显示了挂起线程的内核堆栈在不同的时间是不同的,但是一直在调用openpath()
和openpnp()
函数。
这意味着挂起线程没有在Hibernate某些资源,而是进入了openpath()
和openpnp()
内核函数之间的无限循环。 这也解释了为什么挂起过程会消耗大量的处理器系统时间。
跟踪日志和报告
跟踪可用于获取有关挂起过程的更多详细信息。
# trace -anl -C all -T20M -L40M -o trace.raw; sleep 5; trcstop
# ps ax|grep more
286908 pts/5 A 13:07 more foo
323740 pts/8 A 12:26 more foo
# trcrpt -C all -O 'exec=on,pid=on,tid=on,cpu=on' -o trace.log -p 323740 trace.raw
trace.log重复以下块:
107 more 3 323740 1401077 4.450242983 0 lookuppn: foo
4DF more 3 323740 1401077 4.450244140 1 JFS2 iget: vp = F100010038159FE8,
count = 0017, ino = 0002, dev = 000000A0000000B
4DB more 3 323740 1401077 4.450244521 0 vnop_hold(vp = F100010038159FE8,
getcaller = 3DB1B8) = 0000
4DF more 3 323740 1401077 4.450246527 2 JFS2 iget: vp = F100010038169FE8,
count = 0006, ino = 0003, dev = 000000A0000000B
107 more 3 323740 1401077 4.450247078 0
vnop_lookup(dvp = F100010038159FE8, flag = 000A) = 0000, *vpp = 100010038169FE8
107 more 3 323740 1401077 4.450247558 0
lookuppn exit: 'foo' = vnode F100010038169FE8
15B more 3 323740 1401077 4.450248828 1
vnop_open(vp = F100010038169FE8, flags = 4000001, ext = 0000) = 0002
显然,线程首先调用了lookuppn()
函数并找到了foo
文件出口,但是当vnop_open()
尝试打开foo
时,它返回了02 (ENOENT)
错误代码。 然后,它不断重复调用lookuppn()
和vnop_open()
函数,从而导致进程挂起。
如何解决问题
当挂起进程无法终止时,我们通常必须重新启动系统。 但是对于重要的生产系统,重启不是首选。 当我们知道挂起的根本原因时,有时可以无需重新启动即可解决它。 在此特定示例中,我们已经知道挂起是由空白EA条目引起的。 因此,我们可以通过禁用ACL退出无限循环。
使用kdb禁用ACL
在AIX 6.1和7.1中,可以使用kdb
修改内存中的inode以禁用ACL。
(0)> i2 F1000A0034496880
On-disk Persistent Inode @ 0xF1000A0034496A20:
uid..........0x00000000 gid..........0x00000000
mode...... 0x020081A4 mode.........-rw-r--r--- mode.........0100644
……
ea.flag....... 0x00000000 ea.address...0x0000000000000000
ea.addr1.....0x00000000 ea.addr2.....0x00000000
ea.nEntry....0x00000000 ea.len.........0x00000000
ea.type........0x00000002
typeNames....
AIXACL
(0)> mw 0xF1000A0034496A20+0x3c
F1000A0034496A5C: 020081A4 = 000081A4
F1000A0034496A60: 00000000 = .
(0)> mw 0xF1000A0034496A20+0x88
F1000A0034496AA8: 00020000 = 0
F1000A0034496AAC: 00000000 = .
(0)> q
现在,所有挂起进程将继续运行,或者如果之前挂起了kill信号,则将挂起。
编写程序以禁用ACL
在AIX 5.3中,我们编写了一个修复程序,因为kdb
无法修改内存。 (请参见下载部分。)
该修订程序也适用于AIX 6.1和AIX 7.1。
首先,我们使用kdb
获取on-disk persistent inode
的内存地址。 然后,我们通过/dev/kmem
接口读取,修改和写回ea.type
和di_mode
:
/* on-disk inode */
dinode_t dip;
/* on-disk inode address got from kdb */
unsigned long long ip=0xF1000A005D84F620;
open("/dev/kmem", O_RDWR, 0);
kread(ip, (char *)&dip, sizeof(dinode_t));
/* clear S_IXACL */
if(dip.di_mode == 0x20081a4)
dip.di_mode = 0x81a4;
/* clear ea.type */
if(dip.di_ea.type == 0x02)
dip.di_ea.type = 0;
kwrite(ip, (char *)&dip, sizeof(dinode_t));
运行修订程序后,所有挂起进程将继续运行,或者如果之前已挂起kill信号,则将挂起所有挂起进程。
结论
当进程打开带有空EA条目的启用ACL的文件时,进程将挂起。 IBM最近提供了可以避免此问题的授权程序分析师报告(APAR)IV34969。 如果不幸的是,您的AIX系统在申请IV34969之前遇到了此问题,并且您不想重新启动系统,则可以应用本文介绍的方法作为解决方法。
翻译自: https://www.ibm.com/developerworks/aix/library/au-aix-jfs2-inode/index.html