Хотя код в ladsh1.с
поддерживает концепцию задания как множества процессов (предположительно, объединенных вместе каналами), он не предоставляет способа указания того, какие файлы использовать для ввода и вывода. Чтобы позволить это, добавляются новые структуры данных и модифицируются существующие.
24: REDIRECT_APPEND};
25:
26: struct redirectionSpecifier {
27: enum redirectionTypetype; /* тип перенаправления */
28: int fd; /*перенаправляемый файловый дескриптор*/
29: char * filename; /* файл для перенаправления fd */
30: };
31:
32: struct childProgram {
33: pid_t pid; /* 0 если завершен */
34: char **argv; /* имя программы и аргументы */
35: int numRedirections; /* элементы в массиве перенаправлений */
36: struct redirectionSpecifier *redirections; /* перенаправления ввода-вывода*/
37: } ;
Структура struct redirectionSpecifier
сообщает ladsh2.с
о том, как установить отдельный файловый дескриптор. Она содержит enum redirectionTypetype
, который указывает, является ли это перенаправление перенаправлением ввода, перенаправлением вывода, который должен быть добавлен к существующему файлу, либо перенаправлением вывода, которое заменяет существующий файл. Она также включает перенаправляемый файловый дескриптор и имя файла. Каждая дочерняя программа ( struct childProgram
) теперь специфицирует нужное ей количество перенаправлений.
Эти новые структуры данных не связаны с установкой каналов между процессами. Поскольку задание определено как множество дочерних процессов с каналами, связывающими их, нет необходимости в более подробной информации, описывающей каналы. На рис. 11.1 показано, как эти новые структуры должны выглядеть для команды tail < input-file | sort > output-file
.
Рис. 11.1. Структуры данных, описывающие задание для ladsh2.с
Как только в parseCommand()
будут правильно отражены структуры данных, то запуск команд в правильном порядке становится довольно простым при достаточном внимании к деталям. Прежде всего, мы добавляем цикл в parseCommand()
для запуска дочерних процессов, поскольку теперь их может быть множество. Прежде чем войти в цикл, мы устанавливаем nextin
и nextout
, которые являются файловыми дескрипторами, используемыми в качестве стандартных потоков ввод и вывода для следующего запускаемого процесса. Для начала мы используем те же stdin и stdout, что и оболочка.
Теперь посмотрим, что происходит внутри цикла. Основная идея описана ниже.
1. Если это финальный процесс в задании, убедиться, что nextout
указывает на stdout. В противном случае нужно подключить вывод этого задания к входному концу неименованного канала.
2. Породить новый процесс. Внутри дочернего перенаправить stdin и stdout, как указано с помощью nextin
, nextout
и всех специфицированных ранее перенаправлений.
3. Вернувшись обратно в родительский процесс, закрыть nextin
и nextout
, используемые только что запущенным дочерним процессом (если только они не являются потоками ввода и вывода самой оболочки).
4. Теперь настроить следующий процесс в задании для приема его ввода из вывода процесса, который мы только что создали (через nextin
).
Вот как эти идеи перевести на С.
365: nextin=0, nextout=1;
366: for (i=0; i
367: if ((i+1) < newJob.numProgs) {
368: pipe(pipefds);
369: nextout = pipefds[1];
370: } else {
371: nextout = 1;
372: }
373:
374: if (!(newJob.progs[i].pid = fork())) {
375: if (nextin != 0) {
376: dup2(nextin, 0);
377: close(nextin);
378: }
379:
380: if (nextout != 1) {
381: dup2(nextout, 1);
382: close(nextout);
383: }
384:
385: /* явное перенаправление перекрывает каналы */
386: setupRedirections(newJob.progs+i);
387:
388: execvp(newJob.progs[i].argv[0], newJob.progs[i].argv);
389: fprintf(stderr, "exec() of %s failed: %s\n",
390: newJob.progs[i].argv[0],
391: strerror(errno));
392: exit(1);
393: }
394:
395: /* поместить наш дочерний процесс в группу процессов,
396: чей лидер - первый процесс канала */
397: setpgid(newJob.progs[i].pid, newJob.progs[0].pid);
398:
399: if (nextin != 0) close(nextin);
400: if (nextout != 1) close (nextout);
401:
402: /* Если больше нет процессов, то nextin - мусор,
403: но это не имеет значения */
404: nextin = pipefds[0];
Читать дальше