Фон execvp: как это сделать правильно? - PullRequest
0 голосов
/ 04 февраля 2019

Как и многие другие, я пытаюсь смоделировать оболочку.Я правильно использовал execvp в строке, исходящей от пользователя.Строка анализируется и генерируется массив строк (каждое слово имеет свой массив, разбитый на символ space), включая NULL в самом конце.

Когда я нахожу последнее слововведенный пользователем &, я установил флаг, чтобы уведомить свою оболочку о том, что команда должна выполняться в фоновом режиме, позволяя пользователю сразу ввести другую команду.Команда "background-execute" видит, что ее & заменяется символом NULL в массиве строк, переданных в execvp.

Как я пытался использовать pthread чтобы запустить процесс в фоновом режиме, но он действует несколько странно: команда, переданная в execvp через функцию потока, требует от меня два раза нажать ENTER после отправки команды.

Вот мой упрощенныйmain функция, которая должна имитировать оболочку:

int main (void) {

    fprintf (stdout, "%% ");

    bool running = true;

    while(running) {

        /* Ask for an instruction and parses it. */
        char** args = query_and_split_input();

        /* Executing the commands. */
        if (args == NULL) {  // error while reading input
            running = false;
        } else {
            printf("shell processing new command\n");

            int count = count_words(args);
            split_line* line = form_split_line(args, count);
            Expression* ast = parse_line(line, 0, line->size - 1);

            if(line->thread_flag) {
                pthread_t cmd_thr;

                /* Setting up the content of the thread. */
                thread_data_t       thr_data;
                thr_data.ast        = *ast;
                thr_data.line       = *line;

                /* Executing the thread. */
                int thr_err;
                if ((thr_err = pthread_create(&cmd_thr, NULL, thr_func, &thr_data))) {
                    fprintf(stderr, "error: pthread_create, rc: %d\n", thr_err);
                    return EXIT_FAILURE;
                }
                printf("thread has been created.\n");

            } else {
                run_shell(args);
            }
            free(line);

            printf("done running shell on one command\n");
        }
    }

    /* We're all done here. See you! */
    printf("Bye!\n");
    exit (0);
}

Вот функция моего потока:

void *thr_func(void *arg) {
    thread_data_t *data = (thread_data_t *)arg;

    data->line.content[data->line.size-1] = NULL;  // to replace the trailing '&' from the command
    run_shell(data->line.content);

    printf("thread should have ran the command\n");
    pthread_exit(NULL);
}

И фактическая строка, которая запускает команду:

void run_shell(char** args) {

    /* Forking. */
    int status;
    pid_t    pid; /* Right here, the created THREAD somehow awaits a second 'ENTER' before going on and executing the next instruction that forks the process. This is the subject of my first question. */
    pid = fork();

    if (pid < 0) {
        fprintf(stderr, "fork failed");

    } else if (pid == 0) {  // child
        printf("Child executing the command.\n");

        /* Executing the commands. */
        execvp(args[0], args);

        /* Child process failed. */
        printf("execvp didn't finish properly: running exit on child process\n");
        exit(-1);


    } else {  // back in parent
        waitpid(-1, &status, 0);  // wait for child to finish

        if (WIFEXITED(status)) { printf("OK: Child exited with exit status %d.\n", WEXITSTATUS(status)); }
        else { printf("ERROR: Child has not terminated correctly. Status is: %d\n", status); }

        free(args);
        printf("Terminating parent of the child.\n");
    }
}

Таким образом, в качестве примера, например, run_shell(args) получает либо ["echo","bob","is","great",NULL] (в случае последовательного выполнения), либо ["echo","bob","is","great",NULL,NULL] (в случае выполнения команды в фоновом режиме).

Я оставил printf трассировки, так как это может помочь вам понять поток выполнения.

Если я введу echo bob is great, вывод (трассировки printf) будет:

shell processing new command
Child executing the command.
bob is great
OK: Child exited with exit status 0.
Terminating parent of the child.
done running shell on one command

Однако, если я введу echo bob is great &, получится:

shell processing new command
thread has been created.
done running shell on one command

Aи тогда мне действительно нужно снова нажать ENTER, чтобы получить следующий вывод:

Child executing the command.
bob is great
OK: Child exited with exit status 0.
Terminating parent of the child.
thread should have ran the command

(При этом последнем выполнении я также получаю следы своей функции, которая запрашивает и анализирует ввод пользователя, ноэто казалось неуместным, поэтому я абстрагировал всю эту часть.)

Итак, мои вопросы :

  1. Как получается, что созданный поток ожидает секунду ENTER перед запускомexecvp?(thr_func прекращает выполнение run_shell и ожидает вторую ENTER прямо перед инструкцией pid = fork();)
  2. Есть ли у меня правильный подход для решения проблемы?(Попытка выполнить команду оболочки в фоновом режиме.)

1 Ответ

0 голосов
/ 05 февраля 2019

Вы не можете использовать поток для имитации процесса.Ну, строго можно, но это бесполезно.Проблема в том, что все потоки, принадлежащие процессу, используют одно и то же виртуальное адресное пространство.Нет смысла создавать поток, так как вам, наконец, нужно fork() создать новый процесс (вам понадобится это по причинам, объясненным ниже), так зачем создавать два потока выполнения, если один из них будет остановлен всевремя ожидания завершения подпроцессаЭта схема бесполезна.

Исторически сложилось, что системный вызов fork() требует простого вызова для создания нового процесса (с другой картой виртуальной памяти), чтобы позволить новой программе иметь возможностьбыть выполненным.Вам необходимо создать новый завершенный процесс перед вызовом системного вызова exec(2), поскольку адресное пространство процесса будет перезаписано сегментами текста и данных новой программы.Если вы сделаете это в потоке, вы перезапишете все адресное пространство процесса (это оболочка) и уничтожите все потоки, которые вы можете запустить от имени этого процесса.Схема (псевдокод):

/* create pipes for redirection here, before fork()ing, so they are available
 * in the parent process and the child process */
int fds[2];
if (pipe(fds) < 0) { /* error */
    ... /* do error treatment */
}
pid_t child_pid = fork();
switch(child_pid) {
case -1: /* fork failed for some reason, no subprocess created */
    ...
    break;
case 0: /* this code is executed in the childd process, do redirections
         * here on pipes acquired ***before*** the fork() call */
        if (dup2(0 /* or 1, or 2... */, fds[0 /* or 1, or 2... */]) < 0) { /* error */
            ... /* do error management, considering you are in a different process now */
        }
        execvpe(argc, argv, envp);
        ... /* do error management, as execvpe failed (exec* is non-returning if ok) */
        break; /* or exit(2) or whatever */ 
    default: /* we are the parent, use the return value to track the child */
        save_child_pid(child_pid);
        ... /* close the unused file descriptors */
        close(fds[1 /* or 0, or 2, ... */]);
        ... /* more bookkeeping */
        /* next depends on if you have to wait for the child or not */
        wait*(...);  /* wait has several flavours */
} /* switch */

Системные вызовы Exec и fork разделены по двум причинам:

  • вы должны иметь возможность вести домашнюю работу между обоими вызовамивыполнить фактические перенаправления в дочернем элементе до того, как exec().
  • было время, когда unix не был многозадачным или защищенным, и вызов exec просто заменил всю память в системе новой программой для выполнения (включаякод ядра, чтобы справиться с тем фактом, что исполняемая программа может повредить незащищенную систему). Это было распространено в старых операционных системах, и я видел его в таких системах, как CP / M или TRS-DOS.Реализация в unix сохранила почти всю семантику вызова exec () и добавила с помощью fork () только недоступную функциональность.Это было хорошо, так как позволяло как родительским, так и дочерним процессам вести необходимый учет, когда пришло время для конвейеров.

Только если вам нужен другой поток для взаимодействия с каждым дочерним процессом, когда вы, вероятно,может использовать другой поток для выполнения задачи.Но подумайте, что поток разделяет все виртуальное пространство с родителем (в случае, если мы можем говорить об отношениях родитель / потомок между потоками), и если вы сделаете вызов exec, вы получите перезаписать это виртуальное пространство для всего процесса (все потоки там есть).)

...