Linux上的文件描述符3有什么特别之处?

时间:2010-08-20 22:24:49

标签: c++ c linux macos

我正在开发一个可以在Linux和Mac OS X上运行的服务器应用程序。它是这样的:

  • 启动主要应用程序
  • 控制器进程的分支
  • 在控制器进程中调用lock_down()
  • 终止主要申请
  • 然后控制器进程再次分叉,创建一个工作进程
  • 最终控制器继续分支更多的工作进程

我可以使用几种方法(例如syslog或文件)进行日志记录,但现在我正在思考syslog。 “有趣”的是,除非我在下面包含#ifdef部分,否则在控制器进程中看不到任何syslog输出。

无论是否有ifdef'ed部分,工作人员都可以在Mac OS X和Linux中完美地处理日志。控制器还可以在没有#ifdef'ed部分的情况下在Mac OS X中完美记录,但是在linux上,如果我想从控制器进程看到syslog(或者那个日志文件)的任何输出,则需要ifdef。

那么,为什么呢?

static int
lock_down(void)
{
    struct rlimit rl;
    unsigned int n;
    int fd0;
    int fd1;
    int fd2;

    // Reset file mode mask
    umask(0);

    // change the working directory
    if ((chdir("/")) < 0)
        return EXIT_FAILURE;

    // close any and all open file descriptors
    if (getrlimit(RLIMIT_NOFILE, &rl))
        return EXIT_FAILURE;
    if (RLIM_INFINITY == rl.rlim_max)
        rl.rlim_max = 1024;

    for (n = 0; n < rl.rlim_max; n++) {
#ifdef __linux__        
        if (3 == n) // deep magic...
            continue;
#endif
        if (close(n) && (EBADF != errno))
            return EXIT_FAILURE;
    }

    // attach file descriptors 0, 1 and 2 to /dev/null
    fd0 = open("/dev/null", O_RDWR);
    fd1 = dup2(fd0, 1);
    fd2 = dup2(fd0, 2);
    if (0 != fd0)
        return EXIT_FAILURE;

    return EXIT_SUCCESS;
}

camh很接近,但是使用closelog()就是这个想法,所以荣誉归于jilles。除了从syslogs脚下关闭文件描述符之外,其他一些事情必须继续。为了使代码工作,我在循环之前添加了对closelog()的调用:

closelog();
for (n = 0; n < rl.rlim_max; n++) {
    if (close(n) && (EBADF != errno))
        return EXIT_FAILURE;
}

我依靠对手册页的逐字理解,说:

  

openlog()的使用是可选的;如有必要,它将由syslog()自动调用...

我将此解释为说syslog会检测文件描述符是否在其下关闭。显然它没有。需要在linux上使用显式的closelog()来告诉syslog描述符已关闭。

还有一件令我困惑的事情是,不使用closelog()会阻止第一个分叉进程(控制器)甚至打开并使用日志文件。以下分叉进程可以使用syslog或日志文件,没有任何问题。也许在文件系统中存在一些缓存效果,这使得第一个分叉进程具有哪个文件描述符可用的不可靠“想法”,而下一组分叉进程被充分延迟而不受此影响?

4 个答案:

答案 0 :(得分:14)

文件描述符3的特殊之处在于它通常是从分配新文件描述符的系统调用返回的第一个文件描述符,因为通常为stdin,stdout和stderr设置0,1和2。

这意味着如果你调用的任何库函数为了自己的内部目的而分配一个文件描述符来执行它的功能,那么它将获得fd 3.

openlog(3)库调用需要打开/dev/log以与syslog守护进程通信。如果随后关闭所有文件描述符,如果没有以处理它的方式编写它们,则可能会破坏syslog库函数。

答案 1 :(得分:8)

在Linux上调试此方法的方法是使用strace来跟踪正在进行的实际系统调用;然后使用syslog的文件描述符变得很明显:

$ cat syslog_test.c
#include <stdio.h>
#include <syslog.h>

int main(void)
{
    openlog("test", LOG_PID, LOG_LOCAL0);
    syslog(LOG_ERR, "waaaaaah");
    closelog();
    return 0;
}
$ gcc -W -Wall -o syslog_test syslog_test.c
$ strace ./syslog_test
...
socket(PF_FILE, SOCK_DGRAM, 0)          = 3
fcntl64(3, F_SETFD, FD_CLOEXEC)         = 0
connect(3, {sa_family=AF_FILE, path="/dev/log"}, 16) = 0
send(3, "<131>Aug 21 00:47:52 test[24264]"..., 42, MSG_NOSIGNAL) = 42
close(3)                                = 0
exit_group(0)                           = ?
Process 24264 detached

答案 2 :(得分:6)

syslog(3)可以保持文件描述符到syslogd的socket打开;在它脚下关闭它可能会导致问题。闭路电话(3)呼叫可能有所帮助。

答案 3 :(得分:1)

Syslog在启动时绑定给定的描述符。大多数时候描述符3.如果你关闭它没有日志。

syslog-ng -d -v

为您提供有关幕后工作的更多信息。

输出应该如下所示:

binding fd 3, inetaddr: 0.0.0.0, port: 514
io.c: Preparing fd 3 for reading
io.c: Preparing fd 4 for reading
binding fd 5, unixaddr: /dev/log
io.c: listening on fd 5