从一个死锁问题说起

碰到一个死锁问题,A 进程一直持有锁。

加锁和解锁代码如下:

fd = open(PATH, O_RDRW);
if (fd < 0)
        goto out;
ret = flock(fd, LOCK_SH);
if (ret < 0)
        goto out;

/* do something */

out:
        if (fd > 0) {
                flock(fd, LOCK_UN);
                close(fd);
        }

追送代码发现,出现了加锁而没有释放锁的情况;唯一的可能性就是 fd > 0 不成立。 但加锁成功,说明 fd 不会小于 0;所以 fd == 0.

为什么? fd 是由系统调用 open 返回的,通常情况是 > 0 的;内核根据进程打开文件的记录返回没有使用的 值;如果 fd 为 0,说明之前 0 是空着的;但我们知道 0 通常代表着 标准输入,可能情况就是有人把标注输入 close 掉了。daemon 程序通常会将 0, 1,2 重新定向于 /dev/null, 但我们并没有这样处理。 通过 proc 文件系统查看 fd 的使用情况。

正常情况:

# ls -lh /proc/1845/fd/*
lr-x------    1 root     root          64 Oct 25 17:50 1845/fd/0 -> /var/console
lrwx------    1 root     root          64 Oct 25 18:49 1845/fd/1 -> /dev/console
lrwx------    1 root     root          64 Oct 25 18:49 1845/fd/2 -> /dev/console

异常情况:

# ls -lh /proc/1845/fd/*
lr-x------    1 root     root          64 Oct 25 17:50 1845/fd/0 -> /var/conf/myfile
lrwx------    1 root     root          64 Oct 25 18:49 1845/fd/1 -> /dev/console
lrwx------    1 root     root          64 Oct 25 18:49 1845/fd/2 -> /dev/console

很明显标准输入被 close 了,然后进程重新 open 文件时就得到了值为 0 的 fd.

谁关闭了标准输入? 为什么?

有了方向很快就查到了元凶。

我们知道 Linux 同时支持 POSIX 标准 和 System V 标准的 IPC 机制。 项目中使用了 Message queue 的 IPC 方法,但我们混用了两种机制;在获取 IPC handle 的时候使用了 System V 标准(msgget);但释放 IPC handle 的时候使用了 POSIX 标准 (mq_close). 问题是 msgget 的拿到的值有很大概率为 0;mq_close(0) 会将 0 号 fd close 掉,导致 0 号 fd 重新进入了分配池。

两点结论:
  1. 系统调用 open/create 的返回值是有可能为 0 的
  2. POSIX 标准和 System V 标准的 系统调用不能混合使用