面试题答案
一键面试信号屏蔽策略
- 初始化信号屏蔽集
- 在程序启动时,初始化一个信号屏蔽集,将所有可能在处理信号过程中产生竞态条件的信号加入该屏蔽集。例如,对于处理
SIGCHLD
信号时可能影响其他进程相关操作的情况,屏蔽SIGCHLD
信号。 - 使用
sigemptyset
函数清空信号集,然后使用sigaddset
函数将需要屏蔽的信号逐个添加到信号集中。
- 在程序启动时,初始化一个信号屏蔽集,将所有可能在处理信号过程中产生竞态条件的信号加入该屏蔽集。例如,对于处理
- 在关键代码段屏蔽信号
- 在涉及共享资源访问或其他可能被信号打断的关键代码段之前,临时屏蔽相关信号。例如,在修改全局变量或进行文件I/O操作时,屏蔽可能干扰这些操作的信号。
- 使用
sigprocmask
函数设置信号屏蔽字,将信号屏蔽集应用到进程。这样可以防止信号在关键代码段执行期间被传递。
- 在信号处理函数中合理屏蔽信号
- 在信号处理函数内部,再次屏蔽相同类型的信号,以防止信号处理函数在执行过程中被同一信号再次中断。这可以避免递归调用信号处理函数,导致竞态条件和栈溢出等问题。
- 可以在信号处理函数开始时,使用
sigprocmask
函数将当前信号添加到屏蔽集中,在处理函数结束时再恢复原来的信号屏蔽状态。
- 优化性能
- 尽量减少信号处理函数中的复杂操作,避免在信号处理函数中进行大量计算或I/O操作,因为信号处理函数的执行会中断正常程序流程,影响性能。
- 如果需要进行复杂操作,可以在信号处理函数中设置一个标志,然后在主程序的合适位置(如循环中)检测该标志并进行相应处理,这样可以将复杂操作放回主程序的正常流程中,减少对信号处理函数执行时间的依赖。
关键代码示例
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
// 全局变量,用于演示共享资源
volatile sig_atomic_t flag = 0;
// 信号处理函数
void signal_handler(int signum) {
sigset_t set;
// 屏蔽当前信号,防止递归调用
sigemptyset(&set);
sigaddset(&set, signum);
sigprocmask(SIG_BLOCK, &set, NULL);
flag = 1;
printf("Caught signal %d, flag set to 1\n", signum);
// 恢复原来的信号屏蔽状态
sigprocmask(SIG_UNBLOCK, &set, NULL);
}
int main() {
sigset_t block_set;
struct sigaction sa;
// 初始化信号处理函数
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
// 初始化信号屏蔽集
sigemptyset(&block_set);
sigaddset(&block_set, SIGINT);
// 在关键代码段(这里以一个简单循环模拟)屏蔽信号
sigprocmask(SIG_BLOCK, &block_set, NULL);
for (int i = 0; i < 10; i++) {
printf("Main loop: %d\n", i);
sleep(1);
}
sigprocmask(SIG_UNBLOCK, &block_set, NULL);
// 主循环检测标志并处理
while (1) {
if (flag) {
printf("Handling flag in main loop\n");
flag = 0;
}
sleep(1);
}
return 0;
}
在上述代码中:
signal_handler
函数中屏蔽了当前信号,防止递归调用。main
函数中,在一个简单的循环(模拟关键代码段)之前屏蔽了SIGINT
信号,避免信号干扰循环执行。- 主循环检测
flag
标志,并在标志被设置时进行相应处理,将复杂操作放在主程序正常流程中。