2015-10-11 2 views
3

Я пишу простую оболочку в С.Ctrl + Z Обработка сигналов в C

Однако, я обнаружил, что моя программа не может правильно обрабатывать + Z сигнала Ctrl. Моя программа выглядит так:

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
#include <limits.h> 
#include <errno.h> 
void interpreter() { 
char input[256]; 
int i; 
char dir[PATH_MAX+1]; 
char *argv[256]; 
int argc = 0; 
char *token; 
if (getcwd(dir, PATH_MAX+1) == NULL) { 
    //error occured 
    exit(0); 
} 
printf("[shell:%s]$ ", dir); 
fgets(input,256,stdin); 
if (strlen(input) == 0) { 
    exit(0); 
} 
input[strlen(input)-1] = 0; 
if (strcmp(input,"") == 0) { 
    return; 
} 
token = strtok(input, " "); 
while(token && argc < 255) { 
    argv[argc++] = token; 
    token = strtok(NULL, " "); 
} 
argv[argc] = 0; 
    pid_t forknum = fork(); 
    if (forknum != 0) { 
     int status; 
     waitpid(forknum, &status, WUNTRACED); 
    } else { 
     signal(SIGINT, SIG_DFL); 
     signal(SIGTERM, SIG_DFL); 
     signal(SIGQUIT, SIG_DFL); 
     signal(SIGTSTP, SIG_DFL); 
     setenv("PATH","/bin:/usr/bin:.",1); 
     execvp(argv[0], argv); 
     if (errno == ENOENT) { 
      printf("%s: command not found\n", argv[0]); 
     } else { 
      printf("%s: unknown error\n", argv[0]); 
     } 
     exit(0); 
    } 
} 

int main() { 
signal(SIGINT, SIG_IGN); 
signal(SIGTERM, SIG_IGN); 
signal(SIGQUIT, SIG_IGN); 
signal(SIGTSTP, SIG_IGN); 
while(1) { 
    interpreter(); 
} 
} 

Я проигнорировал выше сигналы в основном процессе.

Когда я начинаю cat(1), а затем нажмите Ctrl + Z, следующая строка ввода по-прежнему будет захвачена программой cat(1), а не мой основной процесс. Это означает, что мой основной процесс ничего не сделает, но если я просыпаюсь в программе cat(1), он выводит то, что я набрал сразу. После этого все нормализуется.

Не могу понять, как это решить. Я все еще не уверен, ясно ли я это сказал.

+0

Это очень неясно: * следующая строка ввода будет по-прежнему идти к кошке, а не к моей основной * ... –

+0

Позвольте мне привести пример. После того, как программа cat приостановлена, я набираю «cat», другой кот должен быть запущен, но ничего не происходит. Если я просыпаюсь с предыдущей программой кошки, она сразу же выводит «cat». – vincentzhou

+0

Можете ли вы опубликовать полный код, ожидаемое поведение и поведение, которое вы получаете? –

ответ

1

Интересно. Даже если это помечено Linux, я выйду на конечность и скажу, что вы используете это на OS X.

При компиляции в Linux проблема не существует, но на Mac это происходит точно так, как вы описали , Это похоже на ошибку в OS X: поскольку и процесс оболочки, и cat(1) находятся в одной группе процессов (так как вы явно не меняете членство в группе), похоже, что OS X делает ошибку в подаче следующей строки ввода fgets(3), который спит в процессе cat(1), поэтому вы теряете эту линию ввода из процесса оболочки (потому что она потребляется спящим cat(1)).

Причина, по которой это не происходит с bash, заключается в том, что bash поддерживает управление заданиями, и поскольку такие процессы помещаются в отдельные группы процессов (в частности, bash выбирает первый процесс конвейера процесса как лидера группы процессов). Поэтому, когда вы делаете то же самое в bash, каждый вызов cat(1) заканчивается тем, что помещает его в отдельную группу процессов (а затем оболочка контролирует, какая группа процессов находится на переднем плане с tcsetpgrp(3)). Таким образом, в любое время ясно, какая группа процессов имеет контроль над терминальным входом; когда вы приостановите cat(1) в bash, группа процессов переднего плана снова изменится на bash, и ввод будет считан успешно.

Если вы делаете то же самое, что и bash в своей оболочке, он будет работать в Linux, OS/X и, в основном, в любом другом варианте UNIX (и это делают другие оболочки).

На самом деле, если вы хотите, чтобы у вашей оболочки была поддержка работы, вам нужно будет это сделать рано или поздно (узнайте о группах процессов, сеансах, tcsetpgrp(3), setpgid(2) и т. Д.).

Так, в общем, делать правильные вещи, если вы хотите поддержку работы и обернуть раздвоенный процесс в новой группе процессов:

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
#include <limits.h> 
#include <errno.h> 
#include <sys/types.h> 
#include <sys/wait.h> 

void interpreter() { 
    char input[256]; 
    char dir[PATH_MAX+1]; 
    char *argv[256]; 
    int argc = 0; 
    char *token; 
    if (getcwd(dir, PATH_MAX+1) == NULL) { 
     //error occured 
     exit(0); 
    } 
    printf("[shell:%s]$ ", dir); 
    fgets(input,256,stdin); 

    if (strlen(input) == 0) { 
     exit(0); 
    } 
    input[strlen(input)-1] = 0; 
    if (strcmp(input,"") == 0) { 
     return; 
    } 
    token = strtok(input, " "); 
    while(token && argc < 255) { 
     argv[argc++] = token; 
     token = strtok(NULL, " "); 
    } 
    argv[argc] = 0; 
    pid_t forknum = fork(); 
    if (forknum != 0) { 
     setpgid(forknum, forknum); 
     signal(SIGTTOU, SIG_IGN); 
     tcsetpgrp(STDIN_FILENO, forknum); 
     tcsetpgrp(STDOUT_FILENO, forknum); 
     int status; 
     waitpid(forknum, &status, WUNTRACED); 
     tcsetpgrp(STDOUT_FILENO, getpid()); 
     tcsetpgrp(STDIN_FILENO, getpid()); 
    } else { 
     setpgid(0, getpid()); 
     signal(SIGINT, SIG_DFL); 
     signal(SIGTERM, SIG_DFL); 
     signal(SIGQUIT, SIG_DFL); 
     signal(SIGTSTP, SIG_DFL); 
     setenv("PATH","/bin:/usr/bin:.",1); 
     execvp(argv[0], argv); 
     if (errno == ENOENT) { 
      printf("%s: command not found\n", argv[0]); 
     } else { 
      printf("%s: unknown error\n", argv[0]); 
     } 
     exit(0); 
    } 
} 

int main() { 
    signal(SIGINT, SIG_IGN); 
    signal(SIGTERM, SIG_IGN); 
    signal(SIGQUIT, SIG_IGN); 
    signal(SIGTSTP, SIG_IGN); 
    while(1) { 
     interpreter(); 
    } 
} 

(Хотя, по общему признанию, это прискорбно, что OS X делает такое бедная работа в этой ситуации - вы действительно не должны иметь, чтобы сделать это).

Изменения происходят только внутри кода, специфичного для процесса: как дочерний, так и родительский вызов setpgid(2), чтобы убедиться, что процесс новорожденного действительно находится в одной группе процессов, прежде чем либо родительский процесс сам предполагает, что это уже true (этот шаблон рекомендуется в Расширенное программирование в среде UNIX); вызов tcsetpgrp(3) должен вызываться родителем.

Конечно, это далеко не полный, вам необходимо запрограммировать необходимые функции, чтобы вернуть работу на передний план, задание списка и т. Д. Но, тем не менее, код выше работает с вашим тестовым сценарием.

Nitpick: вы должны использовать sigaction(2) вместо устаревшего, ненадежного и зависящего от платформы signal(3), но это незначительная проблема.

+0

Большое спасибо за вашу помощь! – vincentzhou

+0

@vincentzhou Рад, что я мог бы помочь. Желаем удачи в вашем проекте :) –

+0

Ваш код работает хорошо. Тем не менее, я добавил в свою программу трубы и функции управления заданиями, которые сделали вещи довольно сложными. Для команды «A | B | C» мой основной процесс разворачивает детей в цикле, а затем ждет их. Насколько мне известно, весь процесс одной строки команды должен принадлежать одной группе, и я задал идентификатор группы первому парню. Для tcsetgrp я не уверен, к какому процессу следует привязать stdout. На данный момент предыдущая проблема, похоже, решена, но когда я просыпаю приостановленную программу, появляется сообщение об ошибке «stdin: Прерванный системный вызов». – vincentzhou

Смежные вопросы