哈喽,各位好!今天咱们来聊聊C++里那些深藏功与名的家伙——硬件中断和signal
机制。这俩哥们儿,一个是硬件界的急先锋,一个是软件界的救火队,都是处理紧急事件的高手。别看名字听起来玄乎,其实理解起来并不难,咱们争取用最通俗的方式,把这俩家伙扒个底朝天。
第一幕:硬件中断——硬件界的急先锋
想象一下,你正悠哉游哉地用电脑敲代码,突然,你的鼠标动了一下。这看似不起眼的动作,背后却隐藏着一个英雄——硬件中断。
- 什么是硬件中断?
简单来说,硬件中断就是硬件设备(比如鼠标、键盘、网卡)向CPU发出的一种信号,告诉CPU:“嘿,老大,我这儿有个紧急情况,你赶紧过来处理一下!”
- 为什么要用硬件中断?
如果没有硬件中断,CPU就只能不停地轮询各个硬件设备,看看它们有没有什么请求。这就像一个保安,不停地在各个房间巡逻,看看有没有人需要帮助。这样效率太低了,CPU的大部分时间都浪费在了无用的巡逻上。
有了硬件中断,硬件设备就可以主动向CPU发出请求,CPU可以先处理其他事情,等到中断发生时再来处理。这就像保安平时可以休息,只有当有人按下紧急按钮时,他才会立即赶到现场。这样CPU的效率就大大提高了。
- 硬件中断的工作流程
- 硬件设备发出中断请求(IRQ): 硬件设备通过中断请求线(IRQ)向中断控制器发出中断请求。每个硬件设备通常对应一个IRQ线。
- 中断控制器处理中断请求: 中断控制器(比如PIC或APIC)接收到中断请求后,会根据优先级等因素决定哪个中断请求应该被处理。
- CPU响应中断: 中断控制器将中断信号发送给CPU,CPU暂停当前正在执行的任务,保存现场(比如寄存器值、程序计数器等)。
- CPU跳转到中断处理程序(ISR): CPU根据中断向量表找到对应的中断处理程序的地址,然后跳转到该地址执行中断处理程序。
- 中断处理程序执行: 中断处理程序负责处理中断事件,比如读取鼠标数据、处理网络数据包等。
- 中断处理程序返回: 中断处理程序执行完毕后,恢复之前保存的现场,然后返回到被中断的任务继续执行。
- 中断向量表
中断向量表是一个存储中断处理程序地址的表格。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
采取以下三种处理方式:
- 忽略信号: 进程可以忽略某些信号,比如
SIGCHLD
。 - 执行默认操作: 操作系统会对某些信号执行默认操作,比如终止进程、生成core dump文件等。
- 自定义信号处理程序: 进程可以注册自己的信号处理程序(也称为信号处理函数),当信号发生时,操作系统会调用该处理程序。
- 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
机制有一些限制:
- 不可重入性: 信号处理程序应该尽量避免调用不可重入的函数,比如
malloc()
、printf()
等。因为在信号处理程序执行期间,可能会再次发生信号,导致不可重入的函数被多次调用,从而引发问题。 - 信号处理程序的执行环境: 信号处理程序是在异步环境中执行的,这意味着它可能会在程序的任何时刻被调用。因此,信号处理程序应该尽量简单,避免执行耗时的操作。
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标准定义的一组信号,用于提供更可靠的信号传递机制。实时信号的编号范围是SIGRTMIN
到SIGRTMAX
。
实时信号的特点:
- 可靠性: 实时信号不会丢失,即使在信号处理程序执行期间再次发生相同的信号,也会被排队等待处理。
- 优先级: 实时信号可以设置优先级,优先级高的信号会先被处理。
- 传递额外信息: 实时信号可以传递额外的信息给信号处理程序。
- 使用实时信号
#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++程序。
好了,今天的讲座就到这里。希望大家有所收获!如果有什么问题,欢迎提问。
下次有机会再和大家聊聊其他有趣的技术话题! 拜拜!