示范如何在命令行程序中处理用户中断信号(ctrl+c)

本文介绍了一种跨平台的方法来处理命令行程序中的用户中断信号。通过使用Boost库和平台特有的API,如Windows下的SetConsoleCtrlHandler和Linux下的信号处理机制,实现了能够在多种操作系统上运行的中断处理框架。

文章来源:http://blog.sina.com.cn/s/blog_48d4cf2d0100poiz.html

实现捕获、处理用户中断信号的命令行程序还是比较繁琐的。尤其是Linux,Windows有一定差异。下面我实现了一个跨平台的捕获、处理用户中断信号(ctrl+c)的命令行程序框架。在windows下用SetConsoleCtrlHandler API捕获处理中断,在Linux下用信号机制。

===================================================
    先看看interrupttable_console_program.h文件
===================================================
#pragma once

#include <boost/thread.hpp>

class InterruptServiceHandler
{
public:
    virtual ~InterruptServiceHandler(){}
    virtual void run() = 0;
    virtual void beforeInterruptQuit() = 0;
    virtual void beforeNormalQuit() = 0;
};

class InterruptService
{
public:
    InterruptService(InterruptServiceHandler* handler_);
    void join(bool grace_quit_flag_);   
private:   
    InterruptServiceHandler* handler;
    boost::thread_group grp;
    bool grace_quit_flag;
    boost::mutex single_close_mtx;
    bool single_close_flag;
    bool checkFirstClose();
#ifdef WIN32
    void signalHandler();
#else
    void detectSignalThread();
#endif
};
======================================================
    InterruptServiceHandler是纯虚接口类,可以看到需要实现三个函数:
    run,你想要做的事情;
    beforeInterruptQuit,发生中断时(用户输入ctrl+c)想要执行的代码;
    beforeNormalQuit,run正常结束时想要执行的代码,执行它的时候run一定已经结束。
    框架保证了beforeInterruptQuit和beforeNormalQuit只会有一个被调用。
    解释:接口里最让人疑惑之处应该是为什么需要beforeNormalQuit,这块代码貌似放在run函数的最后面即可。这样设计是为了避免执行两次退出清理代码。因为有可能中断处理的时候run函数也结束了。也有可能run函数结束后正在清理的时候发生中断。
    再解释:InterruptServiced的join的参数很重要。如果设置为true的话,程序会用优雅的方式退出,即必须在beforeInterruptQuit里通知run结束,等run结束后join函数返回,继续执行join之后的代码。这种方式的话如果beforeInterruptQuit没有合适地通知run结束,或者run无法结束(比如锁死在哪个疙瘩),则程序无法退出。如果join参数设置为false,则在调用beforeInterruptQuit之后直接调用exit(0)退出进程。后一种方式比较不优雅,但是其实很多时候都已经要退出进程了,做做清理工作也就ok了。不一定很care要run执行完毕。前者优雅,后者简单。根据实际情况各取所需吧。
======================================================
    来个示例吧。main.cpp
======================================================
#include <iostream>
#include "interrupttable_console_program.h"

using namespace std;
using namespace global;

class TestService
    : public InterruptServiceHandler
{
public:
    TestService(int max_)
        : running_flag(true)
        , max(max_)
        , cnt(0)
    {
    }
    virtual void run()
    {
        while (running_flag)
        {
            boost::mutex::scoped_lock lock(mtx);
            if (cnt < max)
            {
                ++ cnt;               
            }
            else
            {
                break;
            }
        }
    }
    virtual void beforeInterruptQuit()
    {
        boost::mutex::scoped_lock lock(mtx);
        running_flag = false;
        cout << "InterruptQuit after lock " << cnt << " times" << endl;
    }
    virtual void beforeNormalQuit()
    {
        boost::mutex::scoped_lock lock(mtx);
        cout << "NormailQuit after lock " << cnt << " times" << endl;
    }
public:
    boost::mutex mtx;
    int max;
    int cnt;
    bool running_flag;
};

int main()
{
    TestService test_service(1000000);
    InterruptService service(&test_service);
    service.join(true);
    cout << "Hello,World!" << endl;//如果join(true),一定会看到这行字,如果join(false),用户中断的时候就看不到这行字了
    return 0;

======================================================
    最后是interrupttable_console_program.cpp
======================================================
#include "interrupttable_console_program.h"
#include <boost/bind.hpp>
#include <stdexcept>
#ifdef WIN32
#include <windows.h>
#else
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#endif

#ifdef WIN32
boost::function0<void> before_interrupt_quit_func;
BOOL WINAPI console_ctrl_handler(DWORD ctrl_type)
{
    switch (ctrl_type)
    {
    case CTRL_C_EVENT:
    case CTRL_BREAK_EVENT:
    case CTRL_CLOSE_EVENT:
    case CTRL_SHUTDOWN_EVENT:
        before_interrupt_quit_func();
        return TRUE;
    default:
        return FALSE;
    }
}
void InterruptService::signalHandler()
{
    if (checkFirstClose())
    {
        handler->beforeInterruptQuit();
        if (!grace_quit_flag)
        {
            exit(0);
                 
                 
}
#else
void InterruptService::detectSignalThread()
{
    // Wait for signal indicating time to shut down.
    sigset_t wait_mask;
    sigemptyset(&wait_mask);
    sigaddset(&wait_mask, SIGINT);
    sigaddset(&wait_mask, SIGQUIT);
    sigaddset(&wait_mask, SIGTERM);
    int sig = 0;
    sigwait(&wait_mask, &sig);
    if (checkFirstClose())
    {
        handler->beforeInterruptQuit();
        if (!grace_quit_flag)
        {
            exit(0);
        }
    }
}
#endif

InterruptService::InterruptService(InterruptServiceHandler* handler_)
: handler(handler_)
, grace_quit_flag(true)
, single_close_flag(false)
{
    static int INTERRUPT_SERVICE_INSTANCE_TIMES = 0;
    if ((INTERRUPT_SERVICE_INSTANCE_TIMES++) != 0)
    {
        throw std::runtime_error("misuse: InterruptService should only be instance once!");
    }
#ifdef WIN32
    grp.create_thread(boost::bind(&InterruptServiceHandler::run, handler));
    before_interrupt_quit_func = boost::bind(&InterruptService::signalHandler, this);
    SetConsoleCtrlHandler(console_ctrl_handler, TRUE);
#else
    // Block all signals for background thread.
    sigset_t new_mask;
    sigfillset(&new_mask);   
    pthread_sigmask(SIG_BLOCK, &new_mask, NULL);
    grp.create_thread(boost::bind(&InterruptServiceHandler::run, handler));
#endif
}

void InterruptService::join(bool grace_quit_flag_)
{
    grace_quit_flag = grace_quit_flag_;
#ifndef WIN32
    sigset_t wait_mask;
    sigemptyset(&wait_mask);
    sigaddset(&wait_mask, SIGINT);
    sigaddset(&wait_mask, SIGQUIT);
    sigaddset(&wait_mask, SIGTERM);
    pthread_sigmask(SIG_BLOCK, &wait_mask, 0);
    boost::thread detect_signal_thread(boost::bind(&InterruptService::detectSignalThread, this));
    sigset_t new_mask;
    sigfillset(&new_mask);   
    pthread_sigmask(SIG_BLOCK, &new_mask, NULL);
#endif
    grp.join_all();
    if (checkFirstClose())
    {
        handler->beforeNormalQuit();           
    }
}

bool InterruptService::checkFirstClose()
{
    boost::mutex::scoped_lock lock(single_close_mtx);
    if (!single_close_flag)
    {
        single_close_flag = true;
        return true;       
     
    else
    {
        return false;
    }
}
<think> 我们正在讨论的是在中断服务程(ISR)中尝试获取二值信号量时,如果获取不到是否会阻塞程。 根据RTOS的设计原则和FreeRTOS的实际情况: 1. 中断服务程(ISR)必须快速执行,不能阻塞。因此,在中断中不能使用可能会阻塞的API。 2. 在FreeRTOS中,用于中断的API函数通常以`FromISR`结尾,这些函数是专门设计在中断中使用的,它们不会阻塞。 具体到二值信号量的获取: - FreeRTOS提供了`xSemaphoreTakeFromISR()`函数用于在中断中获取信号量。 - 该函数是非阻塞的:如果信号量可用(即二值信号量为1),则获取成功,信号量被置0,并返回pdTRUE;如果信号量不可用(为0),则立即返回pdFALSE,不会阻塞。 因此,在中断中获取二值信号量不会阻塞程,因为中断不能等待。如果获取不到,程会立即继续执行。 参考引用: - 引用[2]提到:“二值信号量可以在某个特殊的中断发生时,让任务解除阻塞”,这里指的是在中断中给出信号量(give)来解除任务的阻塞,而不是在中断中获取(take)信号量。 - 引用[4]中的中断服务程示例,在中断中调用了`xSemaphoreGiveFromISR()`来给出信号量,并没有在中断中获取信号量。 所以,用户的问题“中断中获取二值信号量,获取不到是否会阻塞程”的答案是:不会阻塞,因为中断中使用的获取函数(如果正确使用了FromISR版本)是非阻塞的。 注意:在中断中不要使用普通的`xSemaphoreTake()`,因为它可能会导致阻塞,而中断不允许阻塞。必须使用`xSemaphoreTakeFromISR()`。 总结步骤: 1. 在中断中,只能使用以`FromISR`结尾的信号量操作函数。 2. `xSemaphoreTakeFromISR()`用于获取信号量,如果获取不到,立即返回pdFALSE,不会阻塞。 3. 中断服务程必须快速执行,因此不能包含任何可能阻塞的操作。 因此,在中断中获取二值信号量,如果获取不到,程不会阻塞,中断服务程会继续执行并退出。 扩展:为什么中断中不能阻塞? 因为中断的优先级高于任何任务,如果中断阻塞,那么整个系统的任务调度可能无法进行,导致系统死锁或不可预测的行为。 相关问题: 1. 在中断服务程中,除了信号量操作,还有哪些操作是禁止的? 2. 如果中断中必须进行长时间处理,应该采用什么设计模式? 3. FreeRTOS中,从中断给出信号量后为什么要使用`portYIELD_FROM_ISR()`? </think> 在中断服务程(ISR)中尝试获取二值信号量时,**不会发生阻塞**。这是RTOS设计的核心原则之一,具体机制如下: ### 中断中信号量获取的特殊性 1. **非阻塞设计** 所有RTOS(如FreeRTOS)为中断提供专门的API函数(如 `xSemaphoreTakeFromISR()`),这些函数具有**非阻塞特性**: ```c BaseType_t xSemaphoreTakeFromISR( SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken // 输出标志位 ); ``` - 当信号量不可用(值为0)时,立即返回 `pdFAIL`(而非阻塞) - 中断上下文保持活跃状态,继续执行后续代码 2. **无法阻塞的原理** 中断优先级高于任何任务,若允许中断阻塞将导致: - 系统调度器无法运行(中断上下文不参与调度) - 所有低优先级任务永久挂起 - 整个系统死锁 ### 正确使用模式 典型的中断-任务协作流程: ```mermaid sequenceDiagram participant ISR as 中断服务程 participant Task as 处理任务 ISR->>Task: 1. 事件发生(如串口接收) ISR->>ISR: 2. 快速处理关键操作 ISR->>Task: 3. 给出信号量(xSemaphoreGiveFromISR) Task->>Task: 4. 获取信号量(xSemaphoreTake) Task->>Task: 5. 执行耗时处理 Task->>Task: 6. 返回阻塞态等待信号 ``` ### 代码示例分析 参考引用[4]的串口中断实现: ```c void USART1_IRQHandler(void) { // ...中断检测逻辑... if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) { // 仅给出信号量,不尝试获取! xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } ``` 关键点: 1. 中断中只执行 `Give` 操作释放信号量 2. 信号量获取(`Take`)交给任务上下文执行 3. `portYIELD_FROM_ISR` 触发任务切换(若有必要)[^2] ### 工程设计原则 1. **中断最小化** 遵循"快进快出"原则,ISR执行时间应小于10μs[^1] 2. **职责分离** | 上下文 | 允许操作 | 禁止操作 | |---------------|-------------------------|------------------| | **中断ISR** | 给出信号量(Give) | 获取信号量(Take) | | **任务** | 获取/释放信号量 | 无限制 | 3. **优先级配置** 延迟处理任务应设为最高优先级,确保中断后立即执行[^2],如: ```c xTaskCreate(vHandlerTask, "Handler", 512, NULL, configMAX_PRIORITIES-1, NULL); ``` ### 常见错误及后果 若错误地在中断中调用阻塞式获取: ```c // 错误示范!导致系统崩溃 void ISR() { xSemaphoreTake(xSemaphore, portMAX_DELAY); // 阻塞调用 } ``` 结果: 1. RTOS触发 `configASSERT()` 断言失败 2. 系统进入死锁状态 3. 看门狗定时器超时复位 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值