2012-07-14 28 views
1

我有一个简单的程序,它使用select和类似的东西来实现多路复用IO。
要中断“服务器”进程,我集成了一个sig_handler,它对SIGINT作出反应。在SIGINT上免费分配内存的最佳实践

每次分配内存时,包含方法都会释放自己或调用方法。

使用valgrind显示出来,表明有一些分配没有被释放。
也许没有必要,但我想知道什么是处理信号的最佳方式。
当按下STRG + C时似乎不会调用free调用。
因此,以休息状态退出循环将毫无意义,这是我的第一个方法。

在关闭整个程序之前,是否有可能清理所有东西?

感谢您的任何提示和建议。

+0

如果您计划退出,则无需执行此操作。只有当您计划继续时,才有必要确保没有泄漏。 – pizza 2012-07-15 02:20:18

回答

8

Valgrind是只是寻找内存泄漏的工具,而不是甲骨文公司的建议必须得到重视。制定一项“Valgrind-clean”计划是一个有价值的目标,但不要让它失控。问问你自己一些有关该计划的问题。

  1. 方案是否需要做任何事情,当它接收到一个SIGINTSIGQUIT或什么?它需要做某种干净的关机吗?例如,服务器可能决定完成处理所有打开的请求,或者至少向已连接的客户端发送关闭消息。

  2. 突然终止是否总是留下某些块?那么你可以撤销Valgrind的报告,而不必花费额外的时间来释放已经释放的内存。

简单来说,只有两个原因调用free在程序即将退出。

  1. 如果是推翻Valgrind的消息最简单的方法(即,不读Valgrind的手册)

  2. 如果它使你的代码更简单。

否则,就是不程序退出时调用free,因为所有它的作用就是烧CPU周期。

处理SIGINT:我能想到的四种常见的方式来处理SIGINT:

  1. 使用默认的处理程序。强烈建议,这需要最少量的代码,并且不太可能导致任何异常的程序行为。你的程序将退出。使用longjmp立即退出。这是为喜欢骑摩托车而不戴头盔的民众。这就像玩图书馆电话玩俄罗斯轮盘赌。不建议。

  2. 设置一个标志,并中断主循环的pselect/ppoll。这是一个很难做到的事情,因为你必须用信号掩码来旋转。您只想中断pselect/ppoll,而不是像mallocfree这样的非重入函数,因此您必须非常小心地处理信号掩码等内容。不建议。您必须使用pselect/ppoll而不是select/poll,因为“p”版本可以自动设置信号掩码。如果您使用selectpoll,则在检查标志后但在致电select/poll之前,信号可能会到达,这很糟糕。

  3. 创建管道以在主线程和信号处理程序之间进行通信。请致电select/poll。信号处理程序只需将一个字节写入管道,并且如果主循环成功从另一端读取一个字节,则它会干净地退出。强烈推荐。您也可以让信号处理程序自行卸载,因此不耐烦的用户可以两次点击CTRL+C以立即退出。

两种最简单,最傻瓜的方法是#1和#4。

退出的程序没有任何泄漏。只有正在运行的程序可能有泄漏。一旦程序退出,所有内存都被释放(所以没有泄漏)。

+5

+1表示“退出的程序没有任何泄漏”。爱它。 – 2012-07-15 01:23:16

+0

有了这个说法,我认为如果它解决了异步信号安全和试图从异步信号处理程序中释放内存的危险性,那么您的答案可能会更好。 – 2012-07-15 01:23:56

1

这是我的简单和稍微脏的解决方案。

#include <signal.h> 

volatile bool gContinue; 

void handleCtrlC(int) { 
    gContinue = false; 
} 

int main() { 
    gContinue = true; 

    signal(SIGINT, handleCtrlC); 

    ... allocate memory ... 

    sigset_t sigmask; 
    sigemptyset (&sigmask); 

    while (gContinue) { 

     /*...*/ 
     ready = pselect(nfds, &readfds, &writefds, &exceptfds, 
      timeout, &sigmask); 
     /*...*/ 
    } 

    ... free memory ... 

    return 0; 
} 

编辑:添加到PSELECT循环。

+0

我认为'gContinue'必须是'volatile'。我不知道是谁告诉你全局变量是“坏”,但你应该停止听他们。这正是全局变量适合的问题。 – 2012-07-15 00:21:58

+0

谢谢。我会添加volatile并删除评论。 – 2012-07-15 00:27:28

+0

顺便说一句,我没有评论其他答案的能力,但我认为在使用pselect时不需要sigprocmask。这对我来说是新的信息,但我认为sigprocmask和select一样使用。 sigprocmask(SIG_SETMASK,&sigmask,&origmask); ready = select(nfds,&readfds,&writefds,&exceptfds,timeout); sigprocmask(SIG_SETMASK,&origmask,NULL); – 2012-07-15 00:30:54

0

Adam Hunt的建议将起作用,前提是您在致电select之前为国旗添加支票。它必须在之前,因为您的正常EINTR处理可能会跳过检查,否则退出将延迟,直到您的下一个select返回一个真实的事件。

while (gContinue) { 
    /*set up some stuff for next select call*/ 
    do { 
     if (gContinue == 0) break; 
     nfds = select(...); 
    } while (nfds == -1 && errno == EINTR); 
    /*handle select return*/ 
} 

编辑:迪特里希埃普指出存在的标志检查和select通话只能通过调用关闭,以pselect之间的竞争条件。 pselectselect非常相似,主要区别在于最后一个参数被用作掩码来确定要阻止哪些信号。所以下面的代码示例关闭标志检查和pselect电话之间的竞争:

sigset_t emptyset, blockset, origset; 
sigemptyset(&emptyset); 
sigemptyset(&blockset); 
sigaddset(&blockset, SIGINT); 

while (gContinue) { 
    /*...*/ 
    sigprocmask(SIG_BLOCK, &blockset, &origset); 
    do { 
     if (gContinue == 0) break; 
     nfds = pselect(..., &emptyset); 
    } while (nfds == -1 && errno == EINTR); 
    sigprocmask(SIG_SETMASK, &origset, NULL); 
    /*...*/ 
}; 

另一种方法是你分配的所有元素注册到一个全局数据结构,使他们可以通过一个atexit处理中解放出来的是你安装。如果您最终获得解除分配的代码,请首先从全局数据结构中注销它;

m = malloc(sz); 
register_allocation(m); 
/*...*/ 
unregister_allocation(m); 
free(m); 

而且使用atexit

void cleanup_allocations() { 
    /*...*/ 
} 

atexit(cleanup_allocations); 
+0

请注意,这种情况正是我们选择'pselect'的原因。你不能避免检查标志和调用'select'之间的竞争,所以你必须**使用'pselect',除非你想失去一些SIGINT。 – 2012-07-14 23:55:35

+0

虽然'atexit'的东西是愚蠢的,因为你所做的一切都是在消除Valgrind的信息而不知道它们是否是真正的泄漏。你可能只是禁用Valgrind的泄漏检查,因为这就是你正在做的事情。 – 2012-07-14 23:57:58

+0

@DietrichEpp:我将编辑“pselect”的答案,谢谢你的信息。 'atexit'的使用仅仅取决于OP的目标。它可以作为独立的内存管理器使用,它自己的泄漏检测始终处于开启状态并且可以查询,即使在已部署的代码中也是如此。 – jxh 2012-07-15 00:04:53