我正在开发一个服务器应用程序,将在Linux和Mac OS X上运行。它的步骤如下:
- 启动主应用程序
- 分叉控制器进程
- 在控制器进程中调用lock_down()
- 终止主应用程序
- 然后,控制器进程再次分叉,创建工作进程
- 最终,控制器将继续分叉更多的工作进程
我可以使用多种方法记录日志(例如syslog或文件),但现在我正在考虑syslog。有趣的是,除非我包括下面的#ifdef部分,否则在控制器进程中永远不会看到任何syslog输出。
在Mac OS X和Linux中,工作进程都可以无缝地记录日志,无论是否包含#ifdef部分。在Mac OS X中,即使没有#ifdef部分,控制器也可以无缝记录日志,但在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 的想法接近正确,但最终解决问题的是 jilles 提出的使用 closelog()。除了从 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或日志文件。也许文件系统中存在某些缓存效应,使得第一个复制的进程对可用的文件描述符有一个不可靠的“观念”,而下一组复制的进程由于延迟足够长,因此不受此影响?