如何在信号驱动的恶魔中暂停主循环?

Posted

技术标签:

【中文标题】如何在信号驱动的恶魔中暂停主循环?【英文标题】:How to suspend the main-loop in a signal-driven demon? 【发布时间】:2019-02-10 15:41:21 【问题描述】:

我正在为树莓派开发电子电话线仿真。为此,我需要一个函数,每 20 毫秒调用一次(=50Hz,振铃电压频率的一半),检查电流并更新硬件 PWM 的占空比。为了到达那里,我在相应的计时器上设置了一个信号处理程序(因为我将它挂在纯嵌入式环境中的调度程序中)并让它完成它的工作。请注意,下面的代码被缩短只是为了专注于这个问题。

这已经很好用了。我很惊讶,时间保持的准确度(抖动低于 10µs,用示波器测量)。

但是,到目前为止,还存在一个缺陷:当这样工作时,实际上不需要主循环。我无法从中返回,因为那会终止该过程。当我让它成为一个空循环时,一切正常。但我正在考虑不必要地消耗 CPU 负载。

我试过sleepwait(我知道这两个都不应该再用了),和 sigsuspend。但是所有这些都让警报处理程序不再被调用。

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <syslog.h>
#include <string.h>
#include <signal.h>
#include <syslog.h>
#include <sys/time.h>
#include <bcm2835.h>

#define PIN_PWM RPI_V2_GPIO_P1_35

bool b_Alive;

/** Signal-Handlers: ******************************************************************/

void SIG_Alarm (int signum) 
    /** Just toggle a pin for now:                                                    */
    static bool bOn;
    if (bOn) 
        bOn = 0;
        bcm2835_gpio_write(PIN_PWM, LOW);
    else
        bOn = 1;
        bcm2835_gpio_write(PIN_PWM, HIGH);
    


void SIG_Quit (int signum) 
    /** Shutdown the bcm-library:                                                     */
    bcm2835_close();
    /** Close the sys-log handler:                                                    */
    syslog(LOG_NOTICE + LOG_DAEMON, "Received SigInt and closed.");
    closelog();
    b_Alive = false;    


/** Main-Function: ********************************************************************/


int main(void) 
    /** Variables:                                                                    */
    struct sigaction sa;
    struct itimerval timer;
    /** Open syslog instead: */
    openlog( "PhoneLined", LOG_PID | LOG_CONS | LOG_NDELAY, LOG_LOCAL0 );
    /** Setup pins:                                                                   */
    if (!bcm2835_init()) return 1;
    bcm2835_gpio_fsel(PIN_PWM, BCM2835_GPIO_FSEL_OUTP);
    /** Setup signal handler for kill:                                                */
    memset(&sa, 0, sizeof (sa));
    sigemptyset(&sa.sa_mask);
    sa.sa_handler = &SIG_Quit;
    sigaction(SIGINT , &sa, NULL);
    sigaction(SIGQUIT, &sa, NULL);
    sigaction(SIGKILL, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);
    /** Setup signal handler for timer:                                               */
    memset(&sa, 0, sizeof (sa));
    sigemptyset(&sa.sa_mask);
    sa.sa_handler = &SIG_Alarm;
    sigaction(SIGVTALRM, &sa, NULL);
    /** Configure the timer of a start- and cycle-time of 20ms (=50Hz):               */
    timer.it_value.tv_sec = 0;
    timer.it_value.tv_usec = 20000;
    timer.it_interval.tv_sec = 0;
    timer.it_interval.tv_usec = 20000;
    setitimer(ITIMER_VIRTUAL, &timer, NULL);
    /** Prepare main-loop:                                                            */
    b_Alive = true;
    syslog(LOG_NOTICE + LOG_DAEMON, "Sucessfully initialized.");
    /** ... and do nothing, while the timer works:                                    */
    while (b_Alive) 
        //pause(), suspend, wait or anything?
    
    exit(EXIT_SUCCESS);

在这个循环中实际上什么都不做时,是否有任何提示,如何进行?

【问题讨论】:

while (b_Alive) ... 可以在没有 volatile 的情况下进行优化。而pause() 是必经之路。 我试过了。但是,使用 pause(); 不会再调用该警报的信号处理程序。我将计时器的循环时间设置为 500 毫秒(应该是年龄),并在日志中添加了一个输出。细针都没有切换,也没有写入日志条目。 确保您已阅读man7.org/linux/man-pages/man7/signal-safety.7.html 建议做这项工作与编写一个守护进程非常相似。 write a deamon @user3629249: 就是这个方案,原代码很好的封装在里面。但这仍然使“main-while”被填充,一旦我添加暂停或睡眠,我就不再收到警报了。 【参考方案1】:

至少volatile 添加到您的标志定义中,否则循环将被优化器(for-O2 或更高版本)删除

(与cc -Wall -O4 -S signal.c核对)


#include <stdbool.h>
// volatile sig_atomic_t
volatile bool
        b_Alive=false;

main():

...
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = 20000;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 20000;
setitimer(ITIMER_REAL, &timer, NULL);

// Prepare main-loop:            

b_Alive = true;
    syslog(LOG_NOTICE + LOG_DAEMON, "Sucessfully initialized.");
    /** ... and do nothing, while the timer works:                                    */
    while (b_Alive) 
        pause(); //, suspend, wait or anything?
    
    exit(EXIT_SUCCESS);

还有,计时器类型(来自精美手册):


   ITIMER_REAL    This timer counts down in real (i.e., wall clock) time.  At each expiration, a SIGALRM signal is generated.

   ITIMER_VIRTUAL This timer counts down against the user-mode CPU time consumed by the process.  (The measurement includes CPU time consumed by all threads in the process.)  At each
                  expiration, a SIGVTALRM signal is generated.

而且,由于pause() 不消耗任何 CPU 滴答,因此使用 ITIMER_VIRTUAL 时计时器永远不会过期。 (也:传递了不同的信号)

【讨论】:

感谢您的跟进。而且,是的,您对 volatile 的看法是绝对正确的,我添加了它。但是,一旦我添加了 pause(),警报信号处理程序仍然没有被触发。难道 pause() 也会暂停计时器吗? 顺便说一句:ITIMER_REAL 可能比ITIMER_VIRTUAL 更受欢迎 见鬼,我早该看到的:这是错误的计时器!我使用了 ITIMER_REAL,而 pause(); 现在效果很好。非常感谢您的支持! :-)

以上是关于如何在信号驱动的恶魔中暂停主循环?的主要内容,如果未能解决你的问题,请参考以下文章

Qt中暂停线程的执行(利用QMutex,超级简单明了)

无论我是在主时间轴动画还是子/孙 MC 动画上暂停,如何从最后一个位置恢复动画?

如何使用 tkinter 事件“继续”或暂停不同的 while 循环?

如何将函数连接到主线程外的 PyQt 信号

如何在 for 循环中暂停

如何在播放/暂停控制功能中停止无限循环