Здравствуйте, уважаемые подписчики! Рассылка вернулась из летнего отпуска. Этот выпуск подводит итоги темы "Введение в многозадачность Linux". Сегодня мы напишем очень простенькую командную оболочку (shell). Если возникнут вопросы, задавайте их в форуме. Справочная информация и электронные учебники доступны в файловом архиве на Lindevel.Ru. Сайт вышел из отпуска и постоянно пополняется новыми материалами и программным обеспечением.
2. Программа
2.1. Описание
Итак, в этом выпуске мы рассматриваем простенькую командную оболочку (shell). Приведенный пример наглядно демонстрирует совместное использование системных вызовов fork() и exec(), а также знакомит вас с функцией wait(), имеющей непосредственное отношение к многозадачности в Linux. Представленная мини-оболочка располагает только одной встроенной командой exit. При изучении темы "Программное окружение", мы вернемся к рассмотрению этой оболочки и внедрим в нее команду cd. С компиляцией, сборкой и запуском разбирайтесь
сами :-)
mysh.c
#define MAX_ARGS 16
#define MAX_LENGTH 256
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
int run_p (char* const args [])
{
pid_t npid = fork ();
if (npid > 0) { return npid; }
else if (npid == 0) {
execvp (args[0], args);
return -1;
} else { abort (); }
}
int main (void)
{
char str [MAX_LENGTH];
char* buf[MAX_ARGS];
int i, j, status;
for (i = 0; i < MAX_ARGS; i++) { buf[i] = (char*) malloc (MAX_LENGTH); }
printf ("mysh# ");
while (fgets (str, MAX_LENGTH, stdin))
{
str[strlen(str)-1] = '\0';
char* p = (char*) malloc (MAX_LENGTH);
p = strtok (str, " ");
if (!strcmp (p, "exit")) { exit (0); }
for (i = 0; i < MAX_ARGS; i++)
{ buf[i] = (char*) malloc (MAX_LENGTH); }
for (i = 0; p != NULL; i++) {
strcpy (buf[i], p);
p = strtok (NULL, " ");
}
buf[i] = NULL;
status = run_p (buf);
if (status < 0)
{ fprintf (stderr, "mysh: %s: command not found\n", buf[0]);
exit (1); }
wait (&status);
free (p);
for (i = 0; i < MAX_ARGS; i++) { free (buf[i]); }
printf ("mysh# ");
};
exit (0);
}
3. Теория
3.1. Запуск программ в отдельном процессе
Ранее мы рассматривали возможность порождать в Linux новые процессы функцией fork() и возможность "подсовывать" программе чужой код функциями из семейства exec(). Но чаще всего системные вызовы fork() и exec() используются в одном контексте. Их совместное использование позволяет запускать программы в одтельных процессах. Как это делается, вы уже, вероятно, догадались. Функция fork() создает отдельный процесс, продолжающий выполнять исходную программу. Отлавливая (при помощи тривиального ветвления) порожденный
процесс, заставляем его выполнить exec() - и дело сделано! Чтобы "отловить" потомка, достаточно вспомнить то, что функция fork() возвращает в порожденный процесс нулевое значение. В рассмотренном примере функция run_p() наглядно демонстрирует вышеописанную стратегию, хотя это далеко не единственный способ запустить программу в отдельном процессе.
3.2. Блокировка процессов
Возьмем командную оболочку, в которой вы работаете, будь то bash, csh, Korn (ksh) или какая-нибудь другая. Запуская внешнюю программу, оболочка ждет, пока программа завершится и только после этого выдается приглашение на ввод следующей команды. Однако мы знаем, что процессы в Linux выполняются асинхронно, т. е. независимо, пытаясь обогнать друг друга и "выхватить" у системы как можно больше ресурсов для себя-любимого. Эта т. н. вытесняющая многозадачность не может гарантировать, например, что процесс A завершится
раньше процесса B или наоборот. Все это очень хорошо, но иногда от программиста требуется синхронизировать работу нескольких процессов.
Программист может заморозить родительский процесс до тех пор, пока не завершится какой-нибудь его потомок. Делается это очень просто: в том месте, где процесс нужно заблокировать и ждать завершения потомка, вызывается одна из функций семейства wait(). Вот и все! В семейство wait() входят функции wait(), waitpid(), wait3(), wait4() и waitid(). Последние три характерны не для всех Unix-систем, используются редко и в ближайшее время мы их рассматривать не будем.
Функция wait() блокирует процесс до тех пор, пока не завершится один из его потомков, а функция waitpid() "ждет" завершения конкретного процесса. Все функции семейства wait() возвращают идентификатор завершившегося дочернего процесса, если только не было ошибок.
Функция wait() вызывается с одним аргументом. Это указатель на целое число, в которое записывается подробная информация о том, как завершился дочерний процесс. Это число называют статусом завершенного процесса.
Функция waitpid() вызывается с тремя аргументами: идентификатор ожидаемого дочернего процесса, статус завершенного процесса (указатель) и целое число для установки некоторых параметров.
Использование функций семейства wait() будет подробно рассмотрено позже при детальном изучении многозадачности Linux. Ниже представлены адаптированные прототипы функций wait() и waitpid().
pid_t wait (int * stat_loc);
pid_t waitpid (pid_t pid, int * stat_loc, int options);
4.1. Разбор полетов
Рассмотрим наш пример, командную оболочку mysh. Механизм работы функции run_p() не должен вызвать вопросов, поскольку этот материал уже подробно изучался в предыдущих выпусках рассылки.
Любая уважающая себя оболочка должна запускать внешние программы и ожидать их завершения. В нашем случае программа запускается функцией run_p(). Если программа успешно запущена, вызывается функция wait(), которая блокирует оболочку до завершения дочернего процесса.
В качестве аргумента функции wait() был передан адрес первой попавшейся переменной status. Полученное значение никак не используется, но эта тема будет рассматриваться в последующих выпусках рассылки.
Сегодняшний выпуск завершает тему "Введение в многозадачность Linux". Теперь мы приступаем к теме "Программное окружение". Настоятельно рекомендую вам ликвидировать все пробелы в знаниях по предыдущим темам, поскольку теперь я не буду "разжевывать" материал так подробно, как это было раньше.
До новых встреч.
Николай.
mailto: nnivanov@mail.ru