簡體   English   中英

在c中捕獲ctrl-c並繼續執行

[英]Catching ctrl-c in c and continuing execution

我正在用c編寫一個簡單的shell程序,需要處理ctrl-c。

如果前台進程正在運行,則需要終止它並繼續執行主Shell循環。 如果沒有,我什么都不做,只需要打印出信號已被捕獲即可。

下面是基於此線程的我的代碼: 在C中捕獲Ctrl-C

void inthandler(int dummy){
    signal(dummy, SIG_IGN);
    printf("ctrl-c caught\n");
}

我在進入主循環之前立即調用signal()

int main(int argc, char*argv[]){
    signal(SIGINT, inthandler)
    while(true){
        //main loop
    }
}

到目前為止,我已經能夠攔截ctrl-c並打印我想要的消息,但是任何進一步的輸入都會導致段錯誤。

進入thandler后,如何返回主循環執行?

  1. 除了將處置設置為SIG_DFLSIG_IGN之外,請使用sigaction()而不是signal() 雖然在C和sigaction() POSIX.1中指定了signal() ,但是無論如何,您都需要POSIX對信號做任何有意義的事情。

  2. 僅在信號處理程序中使用異步信號安全功能。 代替標准I / O(在<stdio.h>聲明),可以對基礎文件描述符使用POSIX低級I / O( read()write() )。 但是,您確實需要避免對使用相同基礎描述符的流使用標准I / O,否則由於在標准I / O中進行緩沖,輸出可能會出現亂碼。

  3. 如果將信號處理程序中的信號設置更改為ignore ,則僅捕獲(每種捕獲類型的)第一個信號。

考慮以下示例程序:

#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

/* Helper function: Write a string to a descriptor, keeping errno unchanged. 
   Returns 0 if success, errno error code if an error occurs. */
static inline int  wrs(const int fd, const char *s)
{
    /* Invalid descriptor? */
    if (fd == -1)
        return EBADF;

    /* Anything to actually write? */
    if (s && *s) {
        const int   saved_errno = errno;
        const char *z = s;
        ssize_t     n;
        int         err = 0;

        /* Use a loop to find end of s, since strlen() is not async-signal safe. */
        while (*z)
            z++;

        /* Write the string, ignoring EINTR, and allowing short writes. */
        while (s < z) {
            n = write(fd, s, (size_t)(z - s));
            if (n > 0)
                s += n;
            else
            if (n != -1) {
                /* This should never occur, so it's an I/O error. */
                err = EIO;
                break;
            } else
            if (errno != EINTR) {
                /* An actual error occurred. */
                err = errno;
                break;
            }
        }

        errno = saved_errno;
        return err;

    } else {
        /* Nothing to write. NULL s is silently ignored without error. */
        return 0;
    }
}

/* Signal handler. Just outputs a line to standard error. */
void catcher(int signum)
{
    switch (signum) {
    case SIGINT:
        wrs(STDERR_FILENO, "Caught INT signal.\n");
        return;
    default:
        wrs(STDERR_FILENO, "Caught a signal.\n");
        return;
    }
}

/* Helper function to install the signal handler. */
int install_catcher(const int signum)
{
    struct sigaction  act;

    memset(&act, 0, sizeof act);
    sigemptyset(&act.sa_mask);

    act.sa_handler = catcher;
    act.sa_flags = SA_RESTART;  /* Do not interrupt "slow" functions */
    if (sigaction(signum, &act, NULL) == -1)
        return -1;  /* Failed */

    return 0;
}

/* Helper function to remove embedded NUL characters and CRs and LFs,
   as well as leading and trailing whitespace.  Assumes data[size] is writable. */
size_t clean(char *data, size_t size)
{
    char *const  end = data + size;
    char        *src = data;
    char        *dst = data;

    /* Skip leading ASCII whitespace. */
    while (src < end && (*src == '\t' || *src == '\n' || *src == '\v' ||
                         *src == '\f' || *src == '\r' || *src == ' '))
        src++;

    /* Copy all but NUL, CR, and LF chars. */
    while (src < end)
        if (*src != '\0' && *src != '\n' && *src != '\r')
            *(dst++) = *(src++);
        else
            src++;

    /* Backtrack trailing ASCII whitespace. */
    while (dst > data && (dst[-1] == '\t' || dst[-1] == '\n' || dst[-1] == '\v' ||
                          dst[-1] == '\n' || dst[-1] == '\r' || dst[-1] == ' '))
        dst--;

    /* Mark end of string. */
    *dst = '\0';

    /* Return new length. */
    return (size_t)(dst - data);
}


int main(void)
{
    char   *line = NULL;
    size_t  size = 0;
    ssize_t len;

    if (install_catcher(SIGINT)) {
        fprintf(stderr, "Cannot install signal handler: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    printf("Type Ctrl+D, 'exit', or 'quit' on an empty line to exit.\n");

    while (1) {
        len = getline(&line, &size, stdin);
        if (len < 0)
            break;

        clean(line, len);
        printf("Read %zd chars: %s\n", len, line);

        if (!strcmp(line, "exit") || !strcmp(line, "quit"))
            break;
    }

    return EXIT_SUCCESS;
}

在大多數POSIXy系統上,偽終端中的Ctrl + C也會清除當前行緩沖區,因此在以交互方式提供行的中間按下它會丟棄該行(數據未發送到進程)。

請注意,當您按Ctrl + C時,通常在偽終端中看到的^C是終端功能,由ECHO termios設置控制。 該設置還控制一般是否在終端上回顯按鍵。

如果將signal(SIGINT, SIG_IGN)添加到catcher(),則在wrs()行之后,只有第一個Ctrl + C會顯示“捕獲的INT信號”; 以下所有Ctrl + C組合鍵都將丟棄不完整的行。

因此,如果您輸入,例如Foo Ctrl + C Bar Enter ,您將看到

Foo^CCaught INT signal.
Bar
Read 4 chars: Bar

要么

Foo^CBar
Read 4 chars: Bar

這取決於處理程序是否捕獲了Ctrl + C生成的INT信號,還是忽略了該信號。

要退出,請在行首或Ctrl + C之后立即鍵入exitquit

這里沒有分段錯誤,因此,如果您的代碼確實生成了一個分段錯誤,則一定是由於程序中的錯誤所致。


進入thandler后,如何返回主循環執行?

信號傳遞會在執行信號處理程序的過程中中斷執行。 然后,被中斷的代碼繼續執行。 因此,對該問題的嚴格正確答案是從信號處理程序中返回

如果在安裝了SA_RESTART標志的情況下安裝了信號處理程序,則被中斷的代碼應繼續進行,好像什么都沒發生一樣。 如果安裝的信號處理程序沒有該標志,則中斷“慢速”系統調用可能會返回EINTR錯誤。

errno必須在信號處理程序中保持不變的原因-這是很多很多程序員忽略的錯誤-原因是,如果信號處理程序中的操作更改errno,並且信號處理程序在系統或C庫之后立即被調用調用失敗並顯示錯誤代碼,該代碼看到的錯誤號將是錯誤的! 當然,這是一種罕見的情況(對於每個系統或C庫調用而言,發生這種情況的時間窗口很小),但這是可能發生的實際錯誤。 當確實發生這種情況時,正是這種Heisenbug導致開發人員扯開頭發,赤身裸體地奔跑,並且通常變得比現在更瘋狂。

還要注意,僅在安裝信號處理程序失敗的代碼路徑中使用stderr ,因為我想確保不要將I / O和POSIX低級I / O混合到標准錯誤中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM