C++ 硬件中断处理与 `signal` 机制:理解底层事件响应

哈喽,各位好!今天咱们来聊聊C++里那些深藏功与名的家伙——硬件中断和signal机制。这俩哥们儿,一个是硬件界的急先锋,一个是软件界的救火队,都是处理紧急事件的高手。别看名字听起来玄乎,其实理解起来并不难,咱们争取用最通俗的方式,把这俩家伙扒个底朝天。

第一幕:硬件中断——硬件界的急先锋

想象一下,你正悠哉游哉地用电脑敲代码,突然,你的鼠标动了一下。这看似不起眼的动作,背后却隐藏着一个英雄——硬件中断。

  • 什么是硬件中断?

简单来说,硬件中断就是硬件设备(比如鼠标、键盘、网卡)向CPU发出的一种信号,告诉CPU:“嘿,老大,我这儿有个紧急情况,你赶紧过来处理一下!”

  • 为什么要用硬件中断?

如果没有硬件中断,CPU就只能不停地轮询各个硬件设备,看看它们有没有什么请求。这就像一个保安,不停地在各个房间巡逻,看看有没有人需要帮助。这样效率太低了,CPU的大部分时间都浪费在了无用的巡逻上。

有了硬件中断,硬件设备就可以主动向CPU发出请求,CPU可以先处理其他事情,等到中断发生时再来处理。这就像保安平时可以休息,只有当有人按下紧急按钮时,他才会立即赶到现场。这样CPU的效率就大大提高了。

  • 硬件中断的工作流程
  1. 硬件设备发出中断请求(IRQ): 硬件设备通过中断请求线(IRQ)向中断控制器发出中断请求。每个硬件设备通常对应一个IRQ线。
  2. 中断控制器处理中断请求: 中断控制器(比如PIC或APIC)接收到中断请求后,会根据优先级等因素决定哪个中断请求应该被处理。
  3. CPU响应中断: 中断控制器将中断信号发送给CPU,CPU暂停当前正在执行的任务,保存现场(比如寄存器值、程序计数器等)。
  4. CPU跳转到中断处理程序(ISR): CPU根据中断向量表找到对应的中断处理程序的地址,然后跳转到该地址执行中断处理程序。
  5. 中断处理程序执行: 中断处理程序负责处理中断事件,比如读取鼠标数据、处理网络数据包等。
  6. 中断处理程序返回: 中断处理程序执行完毕后,恢复之前保存的现场,然后返回到被中断的任务继续执行。
  • 中断向量表

中断向量表是一个存储中断处理程序地址的表格。CPU根据中断号(由中断控制器提供)在中断向量表中找到对应的中断处理程序的地址。

  • 中断优先级

不同的硬件设备的中断优先级可能不同。比如,键盘的中断优先级通常比网卡的中断优先级高,因为键盘输入需要立即响应,而网络数据包可以稍微延迟处理。

  • C++与硬件中断

C++本身不能直接处理硬件中断。硬件中断的处理通常由操作系统内核负责。但是,C++可以通过操作系统提供的API来注册中断处理程序。

第二幕:signal机制——软件界的救火队

硬件中断是硬件设备向CPU发出的信号,而signal机制是操作系统向进程发出的信号。

  • 什么是signal

signal是操作系统通知进程发生了某个事件的一种机制。比如,当用户按下Ctrl+C时,操作系统会向进程发送一个SIGINT信号。

  • 为什么要用signal

signal机制可以使进程能够及时响应各种事件,比如用户中断、程序错误、定时器到期等。

  • 常见的signal类型
信号名称 信号值 描述
SIGHUP 1 终端挂断。通常在用户关闭终端窗口时发送给前台进程。
SIGINT 2 键盘中断。通常在用户按下Ctrl+C时发送给前台进程。
SIGQUIT 3 键盘退出。通常在用户按下Ctrl+时发送给前台进程。
SIGILL 4 非法指令。当程序执行了非法指令时,操作系统会发送该信号。
SIGABRT 6 中止信号。通常由abort()函数触发。
SIGFPE 8 浮点异常。当程序执行了浮点运算错误时,操作系统会发送该信号,例如除以零。
SIGKILL 9 强制终止。这个信号不能被忽略或捕获。用于立即终止进程。
SIGSEGV 11 段错误。当程序访问了非法内存地址时,操作系统会发送该信号。
SIGPIPE 13 管道破裂。当程序尝试向一个没有读取者的管道写入数据时,操作系统会发送该信号。
SIGALRM 14 定时器到期。当程序使用alarm()函数设置的定时器到期时,操作系统会发送该信号。
SIGTERM 15 终止信号。这是终止进程的默认信号。可以被忽略或捕获。
SIGUSR1 30, 10, 16 用户自定义信号1。可以用于进程间通信。
SIGUSR2 31, 12, 17 用户自定义信号2。可以用于进程间通信。
SIGCHLD 17, 20, 18 子进程状态改变。当子进程终止、停止或继续时,操作系统会向父进程发送该信号。
SIGCONT 19, 18, 25 继续执行。用于继续被停止的进程。
SIGSTOP 17, 19, 23 停止执行。这个信号不能被忽略或捕获。用于立即停止进程。
SIGTSTP 18, 20, 24 终端停止信号。通常在用户按下Ctrl+Z时发送给前台进程。
  • signal的处理方式

进程可以对signal采取以下三种处理方式:

  1. 忽略信号: 进程可以忽略某些信号,比如SIGCHLD
  2. 执行默认操作: 操作系统会对某些信号执行默认操作,比如终止进程、生成core dump文件等。
  3. 自定义信号处理程序: 进程可以注册自己的信号处理程序(也称为信号处理函数),当信号发生时,操作系统会调用该处理程序。
  • C++中使用signal

C++可以使用<csignal>头文件中的signal()函数来注册信号处理程序。

#include <iostream>
#include <csignal>
#include <unistd.h>

void signalHandler(int signal) {
    std::cout << "Received signal " << signal << std::endl;
    // 在这里处理信号
    exit(signal); // 退出程序
}

int main() {
    // 注册信号处理程序
    signal(SIGINT, signalHandler);

    std::cout << "Program running..." << std::endl;
    while (true) {
        sleep(1); // 模拟程序运行
    }

    return 0;
}

在这个例子中,我们使用signal()函数将SIGINT信号(通常由Ctrl+C触发)与signalHandler()函数关联起来。当程序接收到SIGINT信号时,signalHandler()函数会被调用,打印一条消息并退出程序。

  • signal的限制

signal机制有一些限制:

  1. 不可重入性: 信号处理程序应该尽量避免调用不可重入的函数,比如malloc()printf()等。因为在信号处理程序执行期间,可能会再次发生信号,导致不可重入的函数被多次调用,从而引发问题。
  2. 信号处理程序的执行环境: 信号处理程序是在异步环境中执行的,这意味着它可能会在程序的任何时刻被调用。因此,信号处理程序应该尽量简单,避免执行耗时的操作。
  3. signal函数的线程安全性: signal函数在多线程环境下不是线程安全的。在多线程程序中,应该使用pthread_sigmask()函数来屏蔽信号,并使用sigwait()函数来等待信号。

第三幕:高级话题——sigaction和实时信号

signal函数比较简单,但是功能有限。为了提供更强大的信号处理能力,POSIX标准定义了sigaction函数。

  • sigaction函数

sigaction函数可以设置信号的处理方式,包括信号处理程序、信号屏蔽字、信号标志等。

#include <iostream>
#include <csignal>
#include <unistd.h>

void signalHandler(int signal, siginfo_t *siginfo, void *context) {
    std::cout << "Received signal " << signal << std::endl;
    std::cout << "Signal number: " << siginfo->si_signo << std::endl;
    std::cout << "Sending process ID: " << siginfo->si_pid << std::endl;
    // 在这里处理信号
    exit(signal); // 退出程序
}

int main() {
    struct sigaction sa;
    sa.sa_sigaction = signalHandler; // 使用 sa_sigaction 代替 sa_handler
    sa.sa_flags = SA_SIGINFO; // 需要 SA_SIGINFO 标志才能使用 sa_sigaction
    sigemptyset(&sa.sa_mask); // 不屏蔽任何其他信号

    if (sigaction(SIGINT, &sa, nullptr) == -1) {
        perror("sigaction");
        return 1;
    }

    std::cout << "Program running..." << std::endl;
    while (true) {
        sleep(1); // 模拟程序运行
    }

    return 0;
}

在这个例子中,我们使用了sigaction函数来注册信号处理程序。与signal函数不同,sigaction函数可以传递更多的信息给信号处理程序,比如信号的编号、发送信号的进程ID等。

  • 实时信号

实时信号是POSIX标准定义的一组信号,用于提供更可靠的信号传递机制。实时信号的编号范围是SIGRTMINSIGRTMAX

实时信号的特点:

  1. 可靠性: 实时信号不会丢失,即使在信号处理程序执行期间再次发生相同的信号,也会被排队等待处理。
  2. 优先级: 实时信号可以设置优先级,优先级高的信号会先被处理。
  3. 传递额外信息: 实时信号可以传递额外的信息给信号处理程序。
  • 使用实时信号
#include <iostream>
#include <csignal>
#include <unistd.h>

void signalHandler(int signal, siginfo_t *siginfo, void *context) {
    std::cout << "Received real-time signal " << signal << std::endl;
    std::cout << "Signal number: " << siginfo->si_signo << std::endl;
    std::cout << "Sending process ID: " << siginfo->si_pid << std::endl;
    std::cout << "Data: " << siginfo->si_value.sival_int << std::endl;
    // 在这里处理信号
    exit(signal); // 退出程序
}

int main() {
    struct sigaction sa;
    sa.sa_sigaction = signalHandler;
    sa.sa_flags = SA_SIGINFO;
    sigemptyset(&sa.sa_mask);

    int realTimeSignal = SIGRTMIN; // 使用第一个实时信号

    if (sigaction(realTimeSignal, &sa, nullptr) == -1) {
        perror("sigaction");
        return 1;
    }

    std::cout << "Program running..." << std::endl;

    // 发送实时信号
    union sigval value;
    value.sival_int = 123;

    if (sigqueue(getpid(), realTimeSignal, value) == -1) {
        perror("sigqueue");
        return 1;
    }

    pause(); // 等待信号

    return 0;
}

在这个例子中,我们使用了sigqueue函数来发送实时信号,并传递了一个整数值给信号处理程序。

第四幕:硬件中断与signal的联系与区别

硬件中断和signal机制都是处理紧急事件的机制,但是它们之间也有很大的区别。

特性 硬件中断 signal机制
触发者 硬件设备 操作系统或进程
目标 CPU 进程
处理者 中断处理程序(ISR),通常由操作系统内核提供 信号处理程序(也称为信号处理函数),由进程注册
级别 硬件级别 软件级别
响应速度 非常快 相对较慢
用途 处理硬件设备的紧急事件,比如鼠标点击、键盘输入、网络数据包到达等 处理软件事件,比如用户中断、程序错误、定时器到期等
直接访问 C++程序不能直接访问硬件中断,通常需要操作系统内核提供的API C++程序可以使用<csignal>头文件中的signal()函数或sigaction()函数来注册信号处理程序
例子 鼠标点击 -> 鼠标控制器发出中断请求 -> CPU响应中断 -> 执行鼠标中断处理程序 用户按下Ctrl+C -> 操作系统向进程发送SIGINT信号 -> 进程执行SIGINT信号处理程序

总结

硬件中断和signal机制是C++程序中处理紧急事件的重要手段。硬件中断是硬件设备向CPU发出的信号,用于处理硬件设备的紧急事件。signal机制是操作系统向进程发出的信号,用于处理软件事件。

虽然C++不能直接处理硬件中断,但是可以通过操作系统提供的API来注册中断处理程序。C++可以使用<csignal>头文件中的signal()函数或sigaction()函数来注册信号处理程序。

理解硬件中断和signal机制,可以帮助我们编写更健壮、更高效的C++程序。

好了,今天的讲座就到这里。希望大家有所收获!如果有什么问题,欢迎提问。

下次有机会再和大家聊聊其他有趣的技术话题! 拜拜!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注