Когда лидер сеанса (оболочка) завершается, ее группы процессов оказываются в сложной ситуации. Если они активно работают, то лишаются возможности использовать стандартные потоки stdin
и stdout
, поскольку терминал закрыт. Если они приостановлены, то вероятно, никогда не будут запущены снова, поскольку пользователь терминала не имеет возможности перезапустить их, к тому же то, что они не могут быть запущены, означает также, что они не могут быть и прерваны.
В этой ситуации каждая такая группа процессов получает название висячей (orphaned). Стандарт POSIX определяет ее как группу процессов, чей родитель является также членом этой группы либо не является членом сеанса этой группы. Другими словами, группа процессов не является висячей до тех пор, пока у нее есть родительский процесс, принадлежащий тому же сеансу, но другой группе.
Хотя оба определения выглядят сложными, концепция достаточно проста. Если группа процессов приостановлена, и не существует процесса, который бы принудил ее возобновиться, то эта группа становится висячей[33].
Когда завершает работу командная оболочка, все ее дочерние процессы становятся дочерними по отношению к процессу init, оставаясь при этом в своих исходных сеансах. Предполагая, что все программы в сеансе являются потомками оболочки, все группы процессов этого сеанса становятся висячими[34]. Когда группа процессов превращается в висячую, каждый процесс этой группы получает сигнал SIGHUP
, что обычно прерывает программу.
Программы, которые не прерываются по сигналу SIGHUP
, получают сигнал SIGCONT
, который продолжает выполнение приостановленных процессов. Такая последовательность прерывает большинство процессов и обеспечивает оставшимся возможность работать (то есть гарантирует, что они не будет в приостановленном состоянии)[35].
Как только процесс становится висячим, он принудительно отключается от своего управляющего терминала (позволяя новому пользователю при необходимости применять этот терминал). Если продолжающие работать программы пытаются получить доступ к терминалу, эти попытки вызывают ошибки, устанавливающие errno
в значение EIO
. Процессы остаются в том же сеансе, и идентификатор сеанса не используется для новых идентификаторов процессов до тех пор, пока не завершатся все процессы данного сеанса.
10.7. Введение в ladsh
Чтобы помочь проиллюстрировать идеи, обсуждаемые в нашей книге, на протяжении последующих разделов книги мы разработаем подмножество командной оболочки Unix. В конечном итоге наша оболочка будет поддерживать следующее.
• Простые встроенные команды.
• Запуск внешних команд.
• Перенаправление ввода-вывода (>
, |
и так далее).
• Управление заданиями.
Полный исходный текст окончательной версии этой оболочки, ladsh4.с
, представлен в приложении Б. По мере добавления в ladsh
новых средств, изменения исходного текста описываются в тексте книги. Чтобы уменьшить количество изменений, которые мы вносим между версиями, некоторые ранние версии несколько более сложны, чем было бы нужно. Эти небольшие усложнения, однако, далее в книге упрощают разработку оболочки, поэтому будьте терпеливы. Просто пока поверьте, что эти фрагменты кода необходимы; все они будут объяснены позднее.
10.7.1. Запуск внешних программ с помощью ladsh
Вот первая (и самая простая) версия ladsh
, называемая ladsh1
.
1: /*ladsh1.c*/
2:
3: #include <ctype.h>
4: #include <errno.h>
5: #include <fcntl.h>
6: #include <signal.h>
7: #include <stdio.h>
8: #include <stdlib.h>
9: #include <string.h>
10: #include <sys/ioctl.h>
11: #include <sys/wait.h>
12: #include <unistd.h>
13:
14: #define MAX_COMMAND_LEN 250 /* максимальная длина отдельной
15: командной строки */
16: #define JOB_STATUS_FORMAT "[%d]%-22s%.40s\n"
17:
18: struct jobSet {
19: struct job *head; /* заголовок списка запущенных заданий */
20: struct job *fg; /* текущее задание переднего плана */
21: };
22:
23: struct childProgram {
24: pid_t Pid; /* 0 на выходе */
25: char **argv; /* имя программы с аргументами */
26: };
27:
28: struct job {
29: int job Id; /* номер задания */
30: int numProgs; /* общее кол-во программ в задании */
31: int runningProgs; /* кол-во работающих программ */
32: char *text; /* имя задания */
33: char *cmdBuf; /* буфер различных argv */
34: pid_t pgrp; /* идентификатор группы процессов задания */
35: struct childProgram *progs; /* массив программ в задании */
36: struct job *next; /* для слежения за фоновыми программами */
37: };
38:
39: void freeJob(struct job *cmd) {
40: int i;
41:
42: for (i=0; i<cmd->numProgs; i++) {
43: free (cmd->progs[i].argv);
44: }
45: free(cmd->progs);
46: if (cmd->text) free(cmd->text);
47: free(cmd->cmdBuf);
48: }
49:
50: int getCommand(FILE *source, char *command) {
51: if (source == stdin) {
52: printf("#");
53: fflush(stdout);
54: }
55:
56: if (!fgets(command, MAX_COMMAND_LEN, source)) {
57: if (source==stdin) printf("\n");
58: return 1;
59: }
60:
61: /* удалить завершающий перевод строки */
62: command[strlen(command) - 1] = '\0';
63:
64: return 0;
65: }
66:
67: /* Возвратить cmd->numProgs как 0, если нет никаких команд (то есть пустая
68: строка). Если найдена правильная команда, commandPtr устанавливается в
69: указатель на начало следующей команды (если исходная команда имеет более
70: одного задания, ассоциированного с ней) или NULL, если
71: больше нет команд.*/
72: int parseCommand(char **commandPtr, struct job *job, int *isBg) {
73: char *command;
74: char *returnCommand = NULL;
34
Но, может быть, не сразу. Может существовать группа процессов, содержащая процессы, чьи родители относятся к другой группе в том же сеансе. Поскольку отношения "родительский-дочерний" между процессами образуют дерево, иногда может оказаться, что существует группа процессов, которая содержит только те процессы, чьим родителем является оболочка, и когда такая группа процессов завершается, другая становится висячей.
35
Обсуждение станет более понятным после того, как вы прочитаете главы, посвященные сигналам (глава 12) и управлению заданиями (глава 15).