Разработка приложений в среде Linux. Второе издание
Шрифт:
42: int isStopped; /* выполняется ли программа в данный момент?*/
43: };
Мы уже различаем работающие и завершенные дочерние программы с помощью элемента
pid
структуры struct childProgram
, равного нулю в случае завершения дочерней программы, а в противном случае содержащего действительный идентификатор процесса. Новый элемент, isStopped
, не равен нулю, если процесс был остановлен, в ином же случае он равен нулю. Обратите внимание, что его значение неважно, если pid
равен нулю. Аналогичные изменения потребуется внести и в
struct job
. Ранее эта структура отслеживала определенное количество программ в
stoppedProgs
, записывает количество процессов задания, остановленных в данный момент. Он может быть вычислен на основе элементов isStopped
дочерних программ, содержащихся в задании, но лучше отслеживать его отдельно. После этого изменения получаем окончательную форму структуры struct job
. 45: struct job {
46: int jobld; /* номер задания */
47: int numProgs; /* количество программ в задании */
48: int runningProgs; /* количество выполняющихся программ */
49: char * text; /* имя задания */
50: char * cmdBuf; /* буфер различных argv */
51: pid_t pgrp; /* идентификатор группы процессов задания */
52: struct childProgram* progs; /* массив программ в задании */
53: struct job * next; /* для слежения за фоновыми программами */
54: int stopped Progs; /* количество активных, однако остановленных программ */
55: };
Как и предыдущие версии
ladsh
, код ladsh4.с
игнорирует SIGTTOU
. Это делается, чтобы позволить использовать tcsetpgrp
даже тогда, когда оболочка не является процессом переднего плана. Однако поскольку оболочка уже будет поддерживать правильное управление заданиями, дочерним процессам не следует игнорировать сигнал. Как только новый процесс разветвляется с помощью runCommand
, он устанавливает обработчик для SIGTTOU
в SIG_DFL
. Это позволяет драйверу терминала приостановить фоновые процессы, пытающиеся выполнить запись в терминал или провести с ним еще какие-то действия. Ниже приведен код, который начинается с создания дочернего процесса, где сбрасывается SIGTTOU
и выполняется дополнительная работа по синхронизации. 514: pipe(controlfds);
515:
516: if (!(newJob.progs[i].pid = fork)) {
517: signal(SIGTTOU, SIG_DFL);
518:
519: close(controlfds[1]);
520: /* это чтение вернет 0, когда закроется записывающая сторона*/
521: read(controlfds[0], &len, 1);
522: close(controlfds[0]);
Канал
controlfds
используется для приостановки дочернего процесса до того, как оболочка переместит этот процесс в подходящую группу процессов. Закрытием записывающей стороны канала и чтением из считывающей стороны дочерний процесс останавливается до тех пор, пока родительский процесс закроет записывающую сторону, что происходит после вызова setpgid
в строке 546. Этот тип механизма необходим для гарантии того, что дочерний процесс перемещается в группу процессов до происшествия exec
. Если подождать до exec
, то не будет уверенности, что процесс попадет в правильную группу процессов, пока он не начнет доступ к терминалу (который может быть запрещен). Завершенные дочерние процессы проверяются
ladsh
два раза. Первый раз это происходит во время ожидания процессов в группе процессов переднего плана. После завершения либо остановки процесса переднего плана ladsh
проверяет изменения в состояниях своих фоновых процессов с помощью функции checkJobs
. Обе этих кодовых цепочки необходимо модифицировать с целью обработки остановленных и завершенных дочерних процессов. Добавление флага
WUNTRACED
к вызову waitpid
, ожидающему на процессах переднего плана, позволяет заметить также остановленные процессы. Когда процесс скорее останавливается, чем завершается, устанавливается флаг дочернего процесса isStopped
и увеличивается номер задания stoppedProgs
. Если все программы задания были остановлены, ladsh
снова перемещается на передний план и ожидает команды пользователя. Вот как выглядит часть главного цикла ladsh
, ожидающая на процессе переднего плана. 708: /* задание выполняется на переднем плане; ожидать его */
709: i = 0;
710: while (!jobList.fg->progs[i].pid ||
711: jobList.fg->progs[i].isStopped) i++;
712:
713: waitpid(jobList.fg->progs[i].pid, &status, WUNTRACED);
714:
715: if (WIFSIGNALED(status) &&
716: (WTERMSIG(status) != SIGINT)) {
717: printf("%s\n", strsignal(status));
718: }
719:
720: if (WIFEXITED(status) || WIFSIGNALED(status)) {
721: /* дочерний процесс завершен */
722: jobList.fg->runningProgs--;
723: jobList.fg->progs[i].pid = 0;
724:
725: if (!jobList.fg->runningProgs) {
726: /* дочерний процесс завершен */
727:
728: removeJob(&jobList, jobList.fg);
729: jobList. fg = NULL;
730:
731: /* переместить оболочку на передний план */
732: if (tcsetpgrp (0, getpid))
733: perror("tcsetpgrp");
734: }
735: } else {
736: /* дочерний процесс остановлен */
737: jobList.fg->stoppedProgs++;
738: jobList.fg->progs[i].isStopped = 1;
739:
740: if (jobList.fg->stoppedProgs ==
741: jobList.fg->runningProgs) {
742: printf ("\n" JOB_STATUS_FORMAT,
743: jobList.fg->jobId,
744: "Остановлен", jobList.fg->text);
745: jobList.fg = NULL;
746: }
747: }
748:
749: if (!jobList.fg) {
750: /* переместить оболочку на передний план */
751: if (tcsetpgrp (0, getpid))
752: perror("tcsetpgrp");
753: }
754: }
Подобным образом фоновые задания могут прерываться с помощью сигналов. Мы снова добавляем
WUNTRACED
к waitpid
, что проверяет состояния фоновых процессов. После остановки фонового процесса обновляются флаг isStopped
и счетчик stoppedProgs
, а в случае остановки всего задания выводится сообщение.
Поделиться с друзьями: