Во-первых, с помощью fork()
порождается дочерний процесс. Родитель сохраняет идентификатор pid дочернего процесса в newJob.progs[0].pid
, тогда как дочерний процесс сохраняет там 0
(помните, что родитель и потомок имеют разные образы памяти, хотя изначально они и содержат одинаковую информацию). В результате управление в дочернем процессе входит в тело оператора if
, в то время как родитель пропускает его. Дочерний немедленно запускает новую программу с помощью вызова execvp()
. Если ему этот вызов не удается, печатается сообщение об ошибке и работа завершается. Это все необходимо, чтобы породить простой дочерний процесс.
После порождения дочернего процесса родитель помещает его в его собственную группу и записывает задание в список запущенных заданий. Если процесс должен выполняться на переднем плане (не в фоне), родитель вносит дочерний процесс в группу процессов переднего плана управляющего терминала, на котором работает командная оболочка.
Следующая функция, checkJobs()
, ищет фоновые задания, которые были завершены, и соответствующим образом чистит список работающих заданий. Для каждого процесса, который был завершен (помните, что waitpid()
возвращает только информацию о завершенных процессах, если только не было указано WUNTRACED
), оболочка делает следующие вещи.
1. Ищет задание, частью которого является процесс.
2. Помечает программу как завершенную (устанавливая сохраненный pid равным 0) и уменьшает количество работающих программ в задании на единицу.
Если задание, содержавшее завершенный процесс, не имеет других работающих процессов, что всегда верно для данной версии ladsh
, оболочка печатает сообщение пользователю о том, что процесс завершен, и удаляет задание из списка фоновых процессов.
Процедура main()
из ladsh1.с
контролирует поток управления оболочки. Если при ее запуске ей передан аргумент, он трактуется как имя файла, из которого нужно читать последовательность команд. В противном случае в качестве источника команд используется stdin
. Затем программа игнорирует сигнал SIGTTOU
. Это элемент "магии" управления заданиями, который обеспечивает, что все происходит гладко. Смысл этого будет пояснен в главе 15. Пока что это только скелет.
Остаток функции main()
составляет главный цикл программы. Условие выхода из цикла не предусмотрено. Программа завершается вызовом exit()
внутри функции runCommand()
.
Переменная nextCommand
указывает на исходное (не разобранное) строковое представление следующей команды, которая должна быть выполнена, либо NULL
, если команда должна быть прочитана из входного файла, коим обычно является stdin
. Когда никакое задание не выполняется на переднем плане, ladsh
вызывает checkJobs()
для проверки выполняющихся фоновых заданий, читает следующую команду из входного файла, если nextCommand
равно NULL
, затем разбирает и выполняет следующую команду.
Когда выполняется задание переднего плана, ladsh1.с
ожидает завершения одного из процессов задания переднего плана. Когда все процессы задания переднего плана завершены, задание исключается из списка запущенных заданий и ladsh1.с
читает следующую команду, как описано выше.
10.8. Создание клонов
Хотя fork()
является традиционным способом создания новых процессов в Unix, Linux также предлагает системный вызов call()
, позволяющий процессам дублироваться с указанием ресурсов, которые родительский процесс должен разделять со своими потомками.
int clone(int flags);
Это ненамного отличается от fork()
. Единственная разница в наличии параметра flags
. Он должен быть установлен равным сигналу, который посылается родительскому процессу, когда потомок завершает работу (обычно это SIGCHLD
), объединенному логическим "или" с любым сочетанием перечисленных ниже флагов, определенных в
.
CLONE_VM | Два процесса разделяют пространство виртуальной памяти (включая стек). |
CLONE_FS | Разделяется информация файловой системы (такая как текущий каталог). |
CLONE_FILES | Разделяются открытые файлы. |
CLONE_SIGHAND | Обработчики сигналов разделяются двумя процессами. |
Когда ресурсы разделяется двумя процессами, оба они видят эти ресурсы идентично. Если указан CLONE_SIGHAND
, то когда один процесс заменяет обработчик определенного сигнала, оба начинают использовать новый обработчик (подробности об обработчиках сигналов представлены в главе 12). Когда используется CLONE_FILES
, разделяются не только наборы открытых файлов, но также текущие позиции в каждом файле. Значения возврата для clone()
те же самые, что и у fork()
.