用 C 語言處理 SIGINT 訊號

Jinku Hu 2023年1月30日 2021年3月21日
  1. 使用 signal 函式註冊 SIGINT 訊號處理程式
  2. 使用 sigaction 功能註冊 SIGINT 訊號處理程式例程
用 C 語言處理 SIGINT 訊號

本文將演示有關如何處理 C 語言中 SIGINT 訊號的多種方法。

使用 signal 函式註冊 SIGINT 訊號處理程式

SIGINT 是與終端中斷字元(通常為Ctrl+C)相關的預定義訊號之一。它使殼程式停止當前程序並返回其主迴圈,向使用者顯示一個新的命令提示符。注意,訊號只是核心內部的程序和使用者空間之間傳送的小通知。它們有時被稱為軟體中斷,因為它們通常會停止程式的正常執行並針對給定的訊號型別執行特殊操作。這些動作大多數情況下被定義為系統中的預設動作,但是使用者可以實現特殊功能並將其註冊為訊號的新動作。

請注意,某些訊號具有從作業系統分配的嚴格固定的行為,因此不能被覆蓋,因為核心使用它們來執行一些重要的事情,例如終止無響應的程序。

但是,SIGINT 是可以處理的一種訊號,這意味著使用者可以註冊一個自定義函式,以便在程序接收到該訊號時執行該函式。SIGINT 訊號的預設動作是使程序終止。在以下示例程式碼中,我們實現了一個程式,該程式執行一個無限的 while 迴圈,並在其中不斷呼叫 fprintf 函式。儘管在迴圈開始之前,我們呼叫 signal 函式將 SIGINTsigintHandler 函式註冊為處理程式,該處理程式只有一個函式呼叫,並在 stdout 中列印了一個字串。

請注意,使用 write 而不是 printf,因為訊號處理程式程式碼不得呼叫在模組內部修改全域性程式資料的非可重入函式。為了演示該示例,你應該執行該程式,然後從另一個終端傳送 SIGINT 訊號以觀察其行為。通常,它應該停止執行 while 迴圈,列印"Caught SIGINT!"字串,並以成功狀態程式碼退出。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
                               } while (0)

static void sigintHandler(int sig)
{
    write(STDERR_FILENO, "Caught SIGINT!\n", 15);
}

int main(int argc, char *argv[]) {
    if (signal(SIGINT, sigintHandler) == SIG_ERR)
        errExit("signal SIGINT");

    while (1) {
        fprintf(stderr, "%d", 0);
        sleep(3);
    }

    exit(EXIT_SUCCESS);
}

使用 sigaction 功能註冊 SIGINT 訊號處理程式例程

即使 UNIX 系統中 signal 函式呼叫的現代實現對於簡單的用例也可以可靠地工作,但還是建議使用 sigaction 函式來註冊訊號處理程式。與 signal 呼叫相比,它提供了更多的選擇,但它也提供了對於訊號的任何嚴重使用情況都必需的核心功能。sigaction 帶有特殊的 struct sigaction 引數,以指定處理程式函式指標和其他指示符。在這種情況下,我們實現了一個方案,其中子程序執行帶有全域性變數 shutdown_flag 作為條件表示式的 while 迴圈,而父程序等待它。請注意,shutdown_flag 變數的型別為 sig_atomic_t,這是一個特殊的整數,可以從訊號處理程式程式碼中安全地對其進行全域性修改。因此,一旦使用者向子程序傳送 SIGINT 訊號,則將呼叫 cleanupRoutine 函式,該函式會將 shutdown_flag 設定為 0 值,並且控制元件返回到 while 迴圈,在該迴圈中再次評估條件表示式,零迫使它從迴圈中中斷。當 waitpid 函式返回時,子程序退出,父程序獲得其狀態。

#define _POSIX_C_SOURCE 199309
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
                               } while (0)

volatile sig_atomic_t shutdown_flag = 1;

void cleanupRoutine(int signal_number)
{
    shutdown_flag = 0;
}

int main(void) {
    int wstatus;

    pid_t c_pid = fork();
    if (c_pid == -1)
        errExit("fork");

    if (c_pid == 0) {
        printf("printed from child process - %d\n", getpid());

        int count = 0;
        struct sigaction sigterm_action;
        memset(&sigterm_action, 0, sizeof(sigterm_action));
        sigterm_action.sa_handler = &cleanupRoutine;
        sigterm_action.sa_flags = 0;

        // Mask other signals from interrupting SIGINT handler
        if (sigfillset(&sigterm_action.sa_mask) != 0)
            errExit("sigfillset");

        // Register SIGINT handler
        if (sigaction(SIGINT, &sigterm_action, NULL) != 0)
            errExit("sigaction");

        while (shutdown_flag) {
            getpid();
        }
        printf("pid: %d exited\n", getpid());

        exit(EXIT_SUCCESS);
    } else {
        printf("printed from parent process - %d\n", getpid());
        int ret;

        if (waitpid(c_pid, &wstatus, WUNTRACED ) == -1)
            errExit("waitpid");
    }

    exit(EXIT_SUCCESS);
}
Author: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.

LinkedIn

相關文章 - C Signal