jfs jfs2_故障诊断过程因JFS2 inode带有空扩展属性条目而挂起

文件系统损坏通常导致进程(或系统)挂起也就不足为奇了。

但是有时,看起来干净的文件系统仍可能导致进程挂起。

在本文中,我们介绍了创建具有空扩展属性(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.head_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

仅当线程从内核模式返回时才传递信号。 因此,我们需要检查线程当前在内核模式下运行的功能以及为什么它无法返回用户模式。 在这种情况下, Tracekdb是非常有用的命令。

使用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.typedi_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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值