Выбрать главу

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, а в случае остановки всего задания выводится сообщение.

Последняя возможность, требуемая для ladsh — перемещение задания между состоянием выполнения на переднем плане, состоянием выполнения в фоне и остановом. Это делается с помощью двух встроенных команд: fg и bg. Они являются ограниченными версиями нормальных команд оболочки, носящих те же имена. Оба принимают один параметр, являющийся номером задания, которому предшествует знак % (для совместимости со стандартными оболочками). Команда fg перемещает определенное задание на передний план, a bg запускает его в фоне.

Обе операции выполняются передачей SIGCONT каждому процессу в активизируемой группе процессов. Поскольку этот сигнал может передаваться каждому процессу с помощью отдельных вызовов kill(), несколько проще передать его всей группе процессов, используя отдельный вызов kill(). Ниже приведена реализация встроенных команд fg и bg.

461: } else if (! strcmp(newJob.progs[0].argv[0], "fg") ||

462:  !strcmp(newJob.progs[0].argv[0], "bg")) {

463:  if (!newJob.progs[0].argv[1] || newJob.progs[0].argv[2]) {

464:   fprintf(stderr,

465:    "%s: ожидался в точности один аргумент\n",

466:   newJob.progs[0].argv[0]);

467:   return 1;

468:  }

469:

470:  if (sscanf(newJob.progs[0].argv[l], "%%%d", &jobNum) != 1)

471:   fprintf(stderr, "%s: ошибочный аргумент '%s'\n",

472:   newJob.progs[0].argv[0],

473:   newJob.progs[0].argv[1]);

474:   return 1;

475:  }

476:

477:  for (job = jobList->head; job; job = job->next)

478:   if (job->jobId == jobNum) break;

479:

480:  if (!job) {

481:   fprintf(stderr, "%s: неизвестное задание %d\n",

482:    newJob.progs[0].argv[0], jobNum);

483:   return 1;

484:  }

485:

486:  if (* new Job.progs[0].argv [0] == 'f') {