我正在制作一个简单的shell。当用户点击control-Z时,我只想简单地切换到前台模式。当用户点击control-C时,我只想终止前台进程(后台子进程应忽略它)。
如果我运行我的shell并发送像"ls"或"sleep 10"这样的命令,它们都能正常工作。但是在执行完这些命令后,如果我按下control-C或control-Z,这些命令就停止工作了。例如,在control-C之后发送"ls"将不会执行。这是为什么?是信号干扰了标准输入吗? 更新收到的评论 现在使用
如果我运行我的shell并发送像"ls"或"sleep 10"这样的命令,它们都能正常工作。但是在执行完这些命令后,如果我按下control-C或control-Z,这些命令就停止工作了。例如,在control-C之后发送"ls"将不会执行。这是为什么?是信号干扰了标准输入吗? 更新收到的评论 现在使用
sigaction
,我可以正确处理control-C信号。然而,尝试control-Z会导致一个fork炸弹。我该如何防止这种情况发生?#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;
}
printf
、exit
是不安全的。请参阅:signal-safety(7)
。 - undefinedsignal()
来设置信号处理程序,应该使用sigaction()
(并注意SA_RESTART
标志)。信号处理程序允许访问的唯一类型是sig_atomic_t
。在x86上,它是int
,但显式类型会更易读。从信号处理程序访问的变量应该是volatile
的。在execvp()
失败后打印相关错误消息。将命令拆分为标记后,在父进程和子进程中都会执行两次。还有一些其他缺陷...对于交互式应用程序,比如shell,我建议不要搞信号处理程序,而是使用signalfd()
来使用事件循环。 - undefinedgetline()
可能返回0
的情况。这使得if (input[read_bytes - 1] == '\n') ...
非常危险,因为它将访问input
缓冲区之外的内存。请参考 从 fgets() 输入中删除尾随的换行符。 - undefined