[英]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后,如何返回主循環執行?
除了將處置設置為SIG_DFL
或SIG_IGN
之外,請使用sigaction()
而不是signal()
。 雖然在C和sigaction()
POSIX.1中指定了signal()
,但是無論如何,您都需要POSIX對信號做任何有意義的事情。
僅在信號處理程序中使用異步信號安全功能。 代替標准I / O(在<stdio.h>
聲明),可以對基礎文件描述符使用POSIX低級I / O( read()
, write()
)。 但是,您確實需要避免對使用相同基礎描述符的流使用標准I / O,否則由於在標准I / O中進行緩沖,輸出可能會出現亂碼。
如果將信號處理程序中的信號設置更改為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之后立即鍵入exit
或quit
。
這里沒有分段錯誤,因此,如果您的代碼確實生成了一個分段錯誤,則一定是由於程序中的錯誤所致。
進入thandler后,如何返回主循環執行?
信號傳遞會在執行信號處理程序的過程中中斷執行。 然后,被中斷的代碼繼續執行。 因此,對該問題的嚴格正確答案是從信號處理程序中返回 。
如果在安裝了SA_RESTART
標志的情況下安裝了信號處理程序,則被中斷的代碼應繼續進行,好像什么都沒發生一樣。 如果安裝的信號處理程序沒有該標志,則中斷“慢速”系統調用可能會返回EINTR錯誤。
errno
必須在信號處理程序中保持不變的原因-這是很多很多程序員忽略的錯誤-原因是,如果信號處理程序中的操作更改errno,並且信號處理程序在系統或C庫之后立即被調用調用失敗並顯示錯誤代碼,該代碼看到的錯誤號將是錯誤的! 當然,這是一種罕見的情況(對於每個系統或C庫調用而言,發生這種情況的時間窗口很小),但這是可能發生的實際錯誤。 當確實發生這種情況時,正是這種Heisenbug導致開發人員扯開頭發,赤身裸體地奔跑,並且通常變得比現在更瘋狂。
還要注意,僅在安裝信號處理程序失敗的代碼路徑中使用stderr
,因為我想確保不要將I / O和POSIX低級I / O混合到標准錯誤中。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.