Различный поток выполнения с использованием read () и fgets () в C - PullRequest
0 голосов
/ 21 октября 2018

У меня есть пример программы, которая принимает входные данные из терминала и выполняет их в клонированном дочернем элементе в подоболочке.

#define _GNU_SOURCE
#include <stdlib.h>
#include <sys/wait.h>
#include <sched.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>

int clone_function(void *arg) {
  execl("/bin/sh", "sh", "-c", (char *)arg, (char *)NULL);
}

int main() {
  while (1) {
    char data[512] = {'\0'};
    int n = read(0, data, sizeof(data));
    // fgets(data, 512, stdin);
    // int n = strlen(data);
    if ((strcmp(data, "exit\n") != 0) && n > 1) {
      char *line;
      char *lines = strdup(data);
      while ((line = strsep(&lines, "\n")) != NULL && strcmp(line, "") != 0) {
        void *clone_process_stack = malloc(8192);
        void *stack_top = clone_process_stack + 8192;
        int clone_flags = CLONE_VFORK | CLONE_FS;
        clone(clone_function, stack_top, clone_flags | SIGCHLD, (void *)line);
        int status;
        wait(&status);
        free(clone_process_stack);
      }
    } else {
      exit(0);
    }
  }
  return 0;
}

Приведенный выше код работает в старой системе Linux (с минимальным объемом ОЗУ (но не в более новой. Не работает означает, что если я набираю простую команду, такую ​​как "ls" , я не вижу вывод на консоли. Но в более старой системе я вижу ее.

Кроме того, если я запускаю тот же код на gdb в режиме отладчика, тогда я вижу вывод, напечатанный на консоли в более новой системе.

Кроме того, если я использую fgets () вместо read () работает в обеих системах, как и ожидалось, без проблем.

Я пытался понять поведение и не смогЯ попытался сделать strace . Разница, которую я вижу в том, что возврат wait () имеет вывод программы ls в тех случаях, когдаработает и ничего для случаев это не работает.

Единственное, что я могу думать о себеs, что read (), так как не библиотечная функция имеет неопределенное поведение в разных системах.Но я не могу согласиться с тем, как это влияет на результат.

Может кто-то указать мне, почему я могу наблюдать это поведение?

РЕДАКТИРОВАТЬ

Код скомпилирован как:

gcc test.c -o test

strace, когда он не работает должным образом, как показано ниже

enter image description here

strace, когда он работает как положено (единственное отличие - я добавил printf ("% d \ n", n); после вызова read () ) enter image description here

Спасибо
Шабир

Ответы [ 2 ]

0 голосов
/ 21 октября 2018

Похоже, 8192 слишком мало для размера стека в современной системе.execl нужно больше, поэтому вы попали в переполнение стека .Увеличьте значение до 32768 или около того, и все снова начнет работать.

0 голосов
/ 21 октября 2018

В вашем коде несколько проблем:

  • успешный системный вызов read может вернуть любое ненулевое число от 1 до размера буфера в зависимости от типа дескриптора и доступного ввода.Он не останавливается на новых строках, таких как fgets(), поэтому вы можете получить фрагменты строк, несколько строк или несколько строк и фрагмент строки.
  • , кроме того, если read заполняет весь буфер, как это может быть припри чтении из обычного файла завершающий нулевой терминатор отсутствует, поэтому передача буфера в строковые функции имеет неопределенное поведение.
  • тест if ((strcmp(data, "exit\n") != 0) && n > 1) { выполняется в неправильном порядке: первый тест, если read был успешными только затем проверяйте содержимое буфера.
  • вы не устанавливаете нулевой терминатор после последнего байта, прочитанного read, полагаясь на инициализацию буфера, что является расточительным и недостаточным, если чтение заполняет весь буфер.Вместо этого вы должны сделать data на один байт длиннее аргумента размера чтения и установить data[n] = '\0';, если n > 0.

Вот способы исправить код:

  • , используя fgets(), вы можете удалить код разделения строки: просто удалите начальный и конечный пробел, игнорируйте пустые строки и комментарии, клонируйте и выполните команды.
  • , используя read(), вы можете просто прочитатьодин байт за раз, собирайте их в буфер до тех пор, пока не получите полную строку, завершите буфер нулем и используйте тот же элементарный синтаксический анализатор, что и выше.Этот подход имитирует fgets(), минуя буферизацию, выполняемую стандартными потоками: он довольно неэффективен, но избегает чтения из дескриптора 0 после конца строки, оставляя доступным ожидающий ввод для дочернего процесса для чтения.
...