提问人:ahmed 提问时间:11/10/2023 最后编辑:Jonathan Lefflerahmed 更新时间:11/18/2023 访问量:167
C - 捕获 SIGINT 和 SIGTSTP 信号会导致错误
C - Catching SIGINT and SIGTSTP signals causes bug
问:
你好,我正在做一个简单的外壳。当用户单击时,我想简单地切换仅前台模式的状态。当用户单击时,我只想终止前台进程(后台子进程应忽略它)。control-Zcontrol-C
如果我运行我的 shell 并发送“ls”或“sleep 10”等命令,它们就会起作用。这样做之后,如果我按 或 ,这也有效。只有在发送或正常命令停止工作后。例如,在之后发送“ls”不会执行。为什么会这样?信号是否与STDIN混淆?control-Ccontrol-Zcontrol-Ccontrol-Zcontrol-C
对所收到评论意见的更新
现在使用 I 可以正确处理信号。然而,尝试会导致叉子炸弹。我怎样才能防止这种情况发生?sigaction
control-Ccontrol-Z
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <limits.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
int last_exit_status = 0;
int last_termination = 0;
int backgroundChildrenRunning = 0;
int backgroundPid = 0;
int ignoreBackground = 0;
void child_sigint_handler(int signum) {
//nothing here for now
}
void child_sigint_handlerSec(int signum) {
//do nothing here for background process
}
void sigint_handler(int signum) {// Signal handler for SIGINT (Ctrl-C)
//nothing here now
}
void sigtstp_handler(int signum) {// Signal handler for SIGTSTP
//nothing here for now
}
void check_background() {
if (backgroundChildrenRunning > 0) { //this function is to print the background process termination signal
int currentStatus;
int completedChild = waitpid(-1, ¤tStatus, WNOHANG); //use WHOHANG so it does not block
if (completedChild > 0) {
if (WIFEXITED(currentStatus)) {
printf("Background process with ID %d has been completed. Exit value: %d.\n", completedChild, WEXITSTATUS(currentStatus));
last_termination = WEXITSTATUS(currentStatus);
}
else {
if (completedChild == backgroundPid) {
printf("Background process with ID %d has been completed. Terminated by signal: %d.\n", completedChild, WTERMSIG(currentStatus));
last_termination = WTERMSIG(currentStatus);
}
}
backgroundPid = 0;
backgroundChildrenRunning -= 1;
}
}
}
char* prompt(int pid);
//main func
int main() {
int x = 0;
char *token;
char *argv[1024]; // Array to store command and arguments
int i = 0;
struct sigaction sa;
sa.sa_flags = SA_RESTART;
sa.sa_handler = sigint_handler;
sigaction(SIGINT, &sa, NULL);
struct sigaction sa_tstp;
sa_tstp.sa_handler = sigtstp_handler;
sigaction(SIGTSTP, &sa_tstp, NULL);
while(x == 0) {
check_background();
int pid = getpid();
char* command = prompt(pid); //get user input
if (command != NULL) {
int childExitMethod;
pid_t spwanPID = fork();
token = strtok(command, " "); // Split the command into tokens
while (token != NULL) {//add in all tokens into arr
argv[i] = token;
token = strtok(NULL, " ");
i++;
}
argv[i] = NULL;
int hasSymbolEnd = 0;
if (strcmp(argv[i - 1], "&") == 0){ //checks to see if background or foreground process
if (ignoreBackground == 0) {
hasSymbolEnd = 1;
}
argv[i - 1] = NULL; // Remove the '&' symbol
}
switch (spwanPID){
case -1:
last_exit_status = 1;
case 0:
if (hasSymbolEnd == 1) {
backgroundPid = getpid();
sa.sa_handler = child_sigint_handlerSec;
} else {
sa.sa_handler = child_sigint_handler;
}
execvp(argv[0], argv);
last_exit_status = 1;
exit(1);
default:
if (hasSymbolEnd == 0){
waitpid(spwanPID, &childExitMethod, 0); // Wait for the child only if background
if (WIFEXITED(childExitMethod) && !WEXITSTATUS(childExitMethod)) {
last_exit_status = 0;
} else if (!WIFSIGNALED(childExitMethod)){
printf("bash: %s: command not found\n", command);
last_exit_status = 1;
}
if (WIFSIGNALED(childExitMethod)) { //Check if the child was terminated by a signal
int terminatedBySignal = WTERMSIG(childExitMethod);
printf("Terminated by signal %d\n", terminatedBySignal);
}
} else {
printf("background pid is %d\n", spwanPID);
backgroundChildrenRunning += 1;
}
}
}
}
return 0;
}
char* prompt(int pid) {
printf("%d:", pid);
fflush(stdout);
char* input = NULL;
size_t input_size = 0;
ssize_t read_bytes = getline(&input, &input_size, stdin);// Use getline to read user input
if (read_bytes == -1) {
free(input); // Free the memory
return NULL;
}
if (input[read_bytes - 1] == '\n') {// Remove the newline character at the end
input[read_bytes - 1] = '\0';
}
return input;
}
答:
1赞
VonC
11/13/2023
#1
关于处理,您可以:SIGINT
- 在 Main shell 中,忽略 .
SIGINT
- 在子进程中,仅当它们是前台进程时,才处理终止。
SIGINT
为了进行处理,请在 .SIGTSTP
sigtstp_handler
// rest of your includes and global variables
void sigint_handler(int signum) {
// Main shell should ignore SIGINT
}
void sigtstp_handler(int signum) {
ignoreBackground = !ignoreBackground; // Toggle the state
}
// rest of your functions
int main() {
// rest of your main function
struct sigaction sa_ignore;
sa_ignore.sa_handler = SIG_IGN; // Ignore signal
sigaction(SIGINT, &sa_ignore, NULL);
struct sigaction sa_tstp;
sa_tstp.sa_handler = sigtstp_handler; // Handle SIGTSTP
sigaction(SIGTSTP, &sa_tstp, NULL);
// rest of your main function
switch (spwanPID) {
// rest of your switch case
case 0:
// Child process
if (hasSymbolEnd == 1) {
// Background process should ignore SIGINT
sigaction(SIGINT, &sa_ignore, NULL);
} else {
// Foreground process should handle SIGINT
struct sigaction sa_child;
sa_child.sa_handler = SIG_DFL; // Default signal handling
sigaction(SIGINT, &sa_child, NULL);
}
execvp(argv0, argv);
// rest of your case 0
}
// rest of your main function
}
对于您的分叉过程,您将获得:
User Input
|
v
Shell Loop
|
+---> Fork Process
| |
| +---> Execute Command (ls, sleep)
| | |
| | +---> SIGINT Handling (Child)
| |
| +---> SIGINT Ignored (Background Child)
|
+---> SIGINT Ignored (Main Shell)
|
+---> SIGTSTP Toggles Foreground Mode (Main Shell)
每次单击控件 Z 时,我仍然会得到一个叉形炸弹。但是,控件 C 已正确处理
当被捕获时,它应该切换仅前台模式。但是,它不应创建新流程或干扰现有流程管理。SIGTSTP
确保主循环不会在不应该分叉新进程的时候分叉,尤其是在处理之后。
应该只切换一个标志 ()。它不应执行任何进程创建或其他复杂操作。
处理后,验证主循环是否不分叉新进程。如果在捕获信号后未正确管理程序的状态,则可能会发生这种情况。SIGTSTP
sigtstp_handler
ignoreBackground
SIGTSTP
我添加了一些用于调试:printf
void sigtstp_handler(int signum) {
ignoreBackground = !ignoreBackground; // Toggle the state
printf("Toggled foreground-only mode to %d\n", ignoreBackground);
fflush(stdout);
}
int main() {
// rest of your main function setup
while (x == 0) {
check_background();
// rest of your command reading and parsing
if (command != NULL) {
// tokenizing and processing the command
if (strcmp(argv[0], "") != 0) { // Check if the command is not empty
pid_t spwanPID = fork();
// rest of your fork and exec logic
} else {
free(command); // Free the command string if it is empty
}
}
}
// rest of your main function
}
主要思想是确保空命令或未正确解析的命令不会导致意外的分叉。
评论
0赞
ahmed
11/14/2023
谢谢,每次点击控制 Z 时,我仍然会得到一个叉形炸弹。但是,控件 C 已正确处理。
0赞
VonC
11/14/2023
@ahmed好的,我已经编辑了答案以解决您的评论。
评论
signal-safety(7)
printf
exit
_exit
() 是异步信号安全的。另外,请注意,也是不安全的。_Exit
fflush
signal()
sigaction()
SA_RESTART
sig_atomic_t
int
volatile
execvp()
signalfd()
getline()
0
if (input[read_bytes - 1] == '\n') ...
input