31

我有一个程序可以创建许多线程并运行,直到嵌入式计算机关闭电源,或者用户使用killctrlc终止进程。

这是一些代码以及 main() 的外观。

static int terminate = 0;  // does this need to be volatile?

static void sighandler(int signum) { terminate = 1; }

int main() {
  signal(SIGINT, sighandler);
  // ...
  // create objects, spawn threads + allocate dynamic memory
  // ...
  while (!terminate) sleep(2);
  // ...
  // clean up memory, close threads, etc.
  // ...
  signal(SIGINT, SIG_DFL);  // is this necessary?
}

我想知道几件事:

  1. 是否需要任何信号处理?
    我在这个线程中读到“Linux C 捕捉终止信号以进行优雅终止”,显然操作系统将为我处理清理工作。因此,我可以只用一个无限循环替换信号处理程序,让操作系统优雅地退出线程、取消分配内存等吗?

  2. 关于干净终止,我还需要关注其他任何信号吗?此线程“SIGINT 与其他终止信号有何关系?” ,列出我可能关心的所有信号很有用,但实际需要处理的信号有多少?

  3. 我的示例中的终止变量是否必须是可变的?我见过很多例子,这个变量是易变的,而其他的不是。

  4. 我读过signal()现在已弃用,并使用sigaction(). 有没有很好的例子来展示如何从之前的signal()调用转换?我对必须创建/传递的新结构以及它们如何组合在一起遇到了麻烦。

  5. 第二次打电话有signal()必要吗?
    是否有类似的事情需要我关注sigaction()

需要明确的是,我想要完成的所有工作都是让我的:主循环运行,直到ctrlc电源断开或发生非常糟糕的事情。

4

5 回答 5

28

[Q-3]terminate我的示例中的变量是否必须是volatile?我见过很多例子,这个变量是易变的,而其他的不是。

标志terminate应该是volatile sig_atomic_t

因为处理函数可以异步调用。也就是说,处理程序可能会在程序中的任何位置被调用,这是不可预测的。如果两个信号在很短的时间间隔内到达,一个处理程序可以在另一个处理程序内运行。并且认为更好的做法是声明volatile sig_atomic_t,这种类型总是以原子方式访问,避免中断访问变量的不确定性。volatile告诉编译器不要优化并将其放入寄存器。(阅读:原子数据访问和信号处理详细说明)。
另一个参考:24.4.7 原子数据访问和信号处理。此外,7.14.1.1-5 中的 C11 标准表明,只能volatile sig_atomic_t从信号处理程序访问 的对象(访问其他对象具有未定义的行为)。

[Q-4]我读到signal()现在已弃用,并使用sigaction(). 有没有很好的例子来展示如何从之前的signal()调用转换?我对必须创建/传递的新结构以及它们如何组合在一起遇到了麻烦。

下面的示例(以及评论中的链接)可能会有所帮助:

// 1. Prepare struct 
struct sigaction sa;
sa.sa_handler =  sighandler;

// 2. To restart functions if interrupted by handler (as handlers called asynchronously)
sa.sa_flags = SA_RESTART; 

// 3. Set zero 
sigemptyset(&sa.sa_mask);

/* 3b. 
 // uncomment if you wants to block 
 // some signals while one is executing. 
sigaddset( &sa.sa_mask, SIGINT );
*/ 

// 4. Register signals 
sigaction( SIGINT, &sa, NULL );

参考:

  1. 开始 Linux 编程,第 4 版:在本书中,您的代码sigaction()在“第 11 章:进程和信号”中得到了很好的解释。
  2. sigaction 文档,包括一个示例(快速学习)。
  3. GNU C 库:信号处理
    *我从1开始,目前我正在阅读3 GNU 库

[Q-5]是否需要第二次调用signal()?是否有类似的事情需要我关注sigaction()

我不清楚为什么在程序终止之前将其设置为默认操作。我想下面的段落会给你一个答案:

处理信号

对信号的调用只为信号的一次出现建立信号处理。在调用信号处理函数之前,库会重置信号,以便在再次出现相同信号时执行默认操作。例如,如果在信号处理程序中执行的操作再次引发相同的信号,则重置信号处理有助于防止无限循环。如果您希望处理程序在每次发生时都用于信号,则必须在处理程序中调用信号以恢复它。在恢复信号处理时应谨慎。例如,如果您不断恢复SIGINT处理,您可能会失去中断和终止程序的能力。

signal()函数仅定义下一个接收信号的处理程序,之后恢复默认处理程序因此,如果程序需要继续使用非默认处理程序处理信号,则有必要调用信号处理程序。signal()

阅读讨论以供进一步参考:何时重新启用信号处理程序

[Q-1a]是否需要任何信号处理?

是的,Linux 会为你做清理工作。例如,如果您不关闭文件或套接字,Linux 将在进程终止后进行清理。但是 Linux 可能不需要立即执行清理,并且可能需要一些时间(可能是为了保持系统性能较高或其他一些问题)。例如,如果您不关闭 tcp-socket 并且程序终止,内核将不会立即关闭套接字以确保所有数据都已传输,如果可能,TCP 会保证交付。

[Q-1b]因此,我可以只用一个无限循环替换信号处理程序,让操作系统优雅地退出线程、取消分配内存等吗?

不,操作系统仅在程序终止后执行清理。在进程执行时,分配给该进程的资源不会被操作系统占用。(操作系统无法知道您的进程是否处于无限循环中 -这是一个无法解决的问题)。如果您希望在进程终止后操作系统为您执行清理操作,那么您不需要处理信号(即使您的进程被信号异常终止)。

[Q]我想要完成的所有工作就是让我的:主循环运行,直到ctrlc电源断开或发生非常糟糕的事情。

不,有限制!你无法捕捉到所有信号。有些信号是不可捕捉的,例如SIGKILLSIGSTOP并且两者都是终止信号。引用一个:

— 宏:intSIGKILL

SIGKILL信号用于立即终止程序。它不能被处理或忽略,因此总是致命的。无法阻止此信号。

所以你不能做一个不能被中断的程序(一个不间断的程序)

我不确定,但可能你可以在 Windows 系统中做这样的事情:通过编写 TSR(某种内核模式挂钩)。我记得在我的论文时间里,有些病毒甚至无法从任务管理器中终止,但我也相信它们会通过管理员权限欺骗用户。

我希望这个答案会对你有所帮助。

于 2013-07-30T08:42:48.497 回答
6

sigaction改为使用,您可以使用如下函数:

/*
 * New implementation of signal(2), using sigaction(2).
 * Taken from the book ``Advanced Programming in the UNIX Environment''
 * (first edition) by W. Richard Stevens.
 */
sighandler_t my_signal(int signo, sighandler_t func)
{
    struct sigaction nact, oact;

    nact.sa_handler = func;
    nact.sa_flags = 0;
    # ifdef SA_INTERRUPT
    nact.sa_flags |= SA_INTERRUPT;
    # endif
    sigemptyset(&nact.sa_mask);

    if (sigaction(signo, &nact, &oact) < 0)
        return SIG_ERR;

    return oact.sa_handler;
}
于 2013-07-30T08:43:19.047 回答
5

1. Is any signal handling necessary?

  • In the particular case of the link you posted, yes. The runs of softwares that concerns network need particular operations, as warning client of the close of a socket by example, in order to not disturb its run.
  • In your particular case, you don't need to handle any signal for the process to be clear gracefully : your OS will do it for you.

2. Are there any other signals that I need to be concerned with regarding clean termination?

  • First, take a look at his page : The GNU Library Signals The termination signals is what you look after. But take a look at SIGUSR1 and SIGUSR2, even if you 'll never find them in any software, except for debugging purposes.

  • All of this termination signals need to be handled if you don't want your soft to terminate all of a sudden.

3. Does the terminate variable in my example have to be volatile?

  • Absolutely not.

4. I've read that signal() is now deprecated, and to use sigaction()

  • Sigaction() is POSIX while signal is a C standard.

  • Signal() works fine for me, but if you want any example : IBM Example

于 2013-07-30T08:53:36.217 回答
4

没有必要为此使用信号。不需要捕获标准终止。您可能有理由抓住它,但这取决于您的应用程序,而不是操作系统要求的任何东西。

就信号而言,现在您通常应该使用 sigaction 而不是信号,它解决了标准化问题。

信号处理程序必须写成可重入的。这并不要求您的终止变量是可变的,但它可能取决于您如何使用它!

W. Richard Stevens的“UNIX 环境中的高级编程”一书中有一个很好的例子,说明了为什么以及如何处理信号。

不,您不必在应用程序终止之前放回默认处理程序,您的处理程序仅对您的应用程序有效,因此如果您只是关闭应用程序,则不需要它。

于 2013-07-30T08:45:20.817 回答
0

首先 - 如果您不知道是否应该处理任何信号,那么您可能不知道。信号需要在某些特定情况下进行处理,例如关闭套接字、在退出之前向其他连接的进程发送一些消息,或者处理来自 write() 的 SIGPIPE 信号(可能还有更多)。

其次 - 这while (!terminate) sleep(2);不是很好 - 在最坏的情况下,它可能会使用户(甚至系统)不耐烦等待 2 秒并向您的程序发送您无法处理的 SIGKILL。

恕我直言,这里最好的解决方案是使用 signalfd 和 select,因此您可以终止程序而无需等待 2 秒。

于 2013-08-04T19:54:33.230 回答