故障排除:JFS2 索引节点引起的进程挂起

在本文中,我们将介绍一种无法被 fsck 命令检测到的特殊索引节点,并解释它会导致进程挂起和系统性能下降的原因。我们还会介绍一些处理该问题的方法。

Wu Jian Jun, 咨询软件工程师, IBM

/developerworks/i/authors/wjj_64x80.jpgWu Jian Jun 是一名软件工程师,过去五年一直效力于 IBM 公司,此外,他还关注 IBM AIX 开发支持。他从中国浙江大学获得了计算机科学博士学位。



2013 年 7 月 03 日

简介

文件系统的损坏常常会导致进程(系统)挂起,这并不奇怪。

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

在本文中,我们将介绍一些方法来创建一种启用了访问控制列表 (ACL) 的增强型日志文件系统 (JFS2) 索引节点,该节点有一个空的扩展属性 (EA) 条目,我们还将探讨该索引节点是如何导致进程挂起的。本文总结了几个处理由索引节点引起的进程挂起的方法。


设置 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。


索引节点中的扩展属性描述符

许多应用程序要求能够将可变长度控制信息与文件系统对象关联起来。这些对象被称作扩展属性或 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 时,索引节点的 di_ea.nEntry 通常不会为零。为了进行测试,我们特意将启用了 ACL 的索引节点的 di_ea.nEntry 设置为 0。有几种方法可以实现此操作。

用 fsdb 修改索引节点

首先,我们将创建一个测试文件:

# echo "a test file" > /testfs/foo
# sync

然后将获取索引节点号:

# ls -i /testfs/foo
3 /testfs/foo

卸载文件系统后,fsdb 可用于修改磁盘索引节点:

# 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 命令不能检测到启用了 ACL 的、带有空 EA 条目的 JFS2 索引节点。

使用 kdb 修改索引节点

在 IBM® AIX® 6.1 和 AIX 7.1 中,挂载文件系统的同时,可使用 kdb 命令修改内存中的索引节点。

(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  = .

再次通过检测索引节点来验证更改:

(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() 函数。

这意味着挂起线程不会为了提供一些资源而休眠,但会在 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 文件已经退出,但在尝试打开 foo 时,vnop_open() 返回了 02 (ENOENT) 错误代码。然后,不断重复调用 lookuppn()vnop_open() 函数,于是就产生了进程挂起。


如何处理这个问题

挂起进程无法中止时,我们通常会重启系统。但是,对于重要生产系统而言,重启并不是首选解决方案。在了解挂起的根本原因之后,有时无需重启便可解决问题。在这个特定示例中,我们已了解到,挂起是由空 EA 条目引起的。所以,我们可以通过禁用 ACL 退出无限循环。

用 kdb 禁用 ACL

在 AIX 6.1 和 7.1 中,可用 kdb 修改内存中的索引节点,从而禁用 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

现在,如果在信号挂起之前终止,所有挂起进程都将继续运行或者中止。

通过编写程序来禁用 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));

在运行修复程序后,所有挂起进程都将继续运行,或者在终止信号挂起之前终止。


结束语

打开一个启用了 ACL 并有空 EA 条目的文件时,进程将会挂起。IBM 最近提供一个授权程序分析报告 (APAR) IV34969,可以避免发生此类问题。如果您的 AIX 系统在应用 IV34969 之前不幸遇到这种问题,而且您不想重启系统,那么可以应用本文介绍的方法作为一种解决方法。


下载

描述名字大小
禁用 ACL 的程序fix.c3 KB

参考资料

学习

  • APAR IV34969:提供了有关 IV34969 详细信息。
  • Mem and kmen:提供了更多与 mem 和 kmem 相关的信息。
  • AIXC ACL:提供了更多与 AIXC ACL 相关的信息。
  • AIX and UNIX 专区:developerWorks 的“AIX and UNIX 专区”提供了大量与 AIX 系统管理的所有方面相关的信息,您可以利用它们来扩展自己的 UNIX 技能。
  • AIX and UNIX 新手入门:访问“AIX and UNIX 新手入门”页面可了解更多关于 AIX 和 UNIX 的内容。
  • AIX and UNIX 专题汇总:AIX and UNIX 专区已经为您推出了很多的技术专题,为您总结了很多热门的知识点。我们在后面还会继续推出很多相关的热门专题给您,为了方便您的访问,我们在这里为您把本专区的所有专题进行汇总,让您更方便的找到您需要的内容。
  • AIX and UNIX 下载中心:在这里你可以下载到可以运行在 AIX 或者是 UNIX 系统上的 IBM 服务器软件以及工具,让您可以提前免费试用他们的强大功能。
  • IBM Systems Magazine for AIX 中文版:本杂志的内容更加关注于趋势和企业级架构应用方面的内容,同时对于新兴的技术、产品、应用方式等也有很深入的探讨。IBM Systems Magazine 的内容都是由十分资深的业内人士撰写的,包括 IBM 的合作伙伴、IBM 的主机工程师以及高级管理人员。所以,从这些内容中,您可以了解到更高层次的应用理念,让您在选择和应用 IBM 系统时有一个更好的认识。

讨论

  • 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=AIX and UNIX
ArticleID=936622
ArticleTitle=故障排除:JFS2 索引节点引起的进程挂起
publish-date=07032013