
- •Рекомендации по написанию командных интерпретаторов для ос unix
- •Void c_handl(int num) //обработчик sigchld
- •4. Терминальный ввод/вывод
- •Int cterm, //управляющий терминал
- •5. Запуск внешних программ
- •6. Ожидание завершения фоновых программ
- •7. Анализ статуса (кода) завершения программ
- •8. Управление заданиями
- •Приложения:
- •Задания на курсовую работу по дисциплине "спо"
Int cterm, //управляющий терминал
c;
cterm = open("/dev/tty",O_RDWR);
tcflush(cterm,TCIOFLUSH); //очищаем внутренние буферы
tcgetattr(cterm,&term); //получаем текущие параметры
oldflags = term.c_lflag; //запоминаем старые настройки
//выключаем канонический режим и эхо,
//разрешаем генерацию сигнала SIGTTOU (константа TOSTOP)
//и сигналов SIGINT, SIGQUIT, SIGTSTP (константа ISIG)
term.c_lflag=term.c_lflag&(~ICANON)&(~ECHO)|TOSTOP|ISIG;
tcsetattr(cterm,TCSANOW,&term); //устанавливаем новые параметры
printf("Enter string (LF exits): \n");
while((c=getchar())!=0x0a) printf("%x ",c);
printf("\n");
term.c_lflag=oldflags;
tcsetattr(cterm,TCSANOW,&term); //восстанавливаем параметры
}
5. Запуск внешних программ
Если после разбора командной строки первое слово не распознано как внутренняя
команда, оно считается именем выполняемого файла. Для того, чтобы запустить
этот файл применяется fork() и одна из функций семейства exec().
Процесс, порожденный с помощью fork(), наследует от родителя программную группу,
управляющий терминал, среду выполнения, сегменты кода, данных и стека, сигнальную
маску и реакцию на сигналы. Не наследуются блокировки файлов и необработанные
сигналы.
Для ускорения запуска новых процессов иногда используют vfork(), который не
копирует сегменты кода, данных и стека для потомка в ожидании системного вызова
execve(). При этом родительский процесс приостановлен системой до того момента,
пока потомок не выполнит execve() или exit(), а до этого они используют одну и
ту же память. В интерпретаторе скорее всего понадобятся какие-то действия
между fork() и exec(), поэтому vfork() не годится.
Функции семейства exec() производят смену выполняемой программы (образа) для
текущего процесса. При этом, естественно, остаются неизменными все атрибуты
процесса, включая открытые файлы, сигнальную маску, но реакция на перехваченные
сигналы восстанавливается в реакцию по умолчанию.
Первый параметр exec() представляет собой полный путь к выполняемому файлу,
который должен быть указан пользователем, либо сформирован интерпретатором на
основе списка путей поиска выполняемых программ из переменной 'path' (или 'PATH').
Функции типа execlp() и execvp() сами осуществляют поиск программ в каталогах из
переменной среды 'PATH'. Кроме того, в большинстве UNIX-систем exec() способен
правильно запустить на выполнение командный файл, в первой строке которого указан
путь к интерпретатору.
Программа может быть запущена в интерактивном или в фоновом режиме. Режим работы
программы зависит от ее взаимоотношений с управляющим терминалом и действий
интерпретатора после выполнения fork(). Интерпретатор ждет завершения интерактивной
программы с помощью функции семейства wait(). При этом управляющий терминал ("/dev/tty")
должен принадлежать той же группе программ, что и интерактивная программа, а интерпретатор
должен находиться в другой программной группе. Это достигается с помощью смены программной
группы в порожденном процессе и передачи управляющего терминала этой группе.
Используйте для описанных действий вызовы типа setpgid(), setpgrp(), tcsetpgrp(),
getpgid(), getpgrp(), tcgetpgrp(). По завершению интерактивной программы интерпретатор
должен вернуть терминал в исходное состояние.
После запуска фоновой программы терминал принадлежит интерпретатору и он продолжает
ожидать ввода команд. Фоновые программы не должны получать сигналы от управляющего
терминала, за исключением SIGTTOU (его обычно маскируют) и SIGTTIN (можно оставить
стандартную реакцию).
Пример: Вариант запуска интерактивного процесса (протестирован под Linux 6.2).
//Приведенный пример предназначен только для демонстрации использования
//системных вызовов, он не является законченной программой и тем более
//частью интерпретатора.
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <fcntl.h>
char *params[]; //массив строк с аргументами, params[0] - имя программы
//для n параметров params[n] = NULL
char *path; //путь к выполняемому файлу
int stat; //код завершения
int cterm; //управляющий терминал
pid_t chpid, shellpid; //идентификаторы
...
signal(SIGTTOU,SIG_IGN); //shell может начать получать эти сигналы после
signal(SIGTTIN,SIG_IGN); //завершения потомка (если они не замаскированы)
cterm = open("/dev/tty",O_RDWR); //он есть, его не может не быть
shellpid = getpid();
...
//здесь можно сохранить старые и установить
//новые параметры терминала
if(chpid=fork()) { //действия интерпретатора
//неизвестно, кто начнет выполняться первым - папа или сын,
//поэтому, папа может случайно получить сигнал, адресованный сыну,
//отдадим побыстрее терминал:
tcsetpgrp(cterm,chpid);
setpgid(chpid,chpid); //меняем программную группу потомка
waitpid(chpid,&stat,WUNTRACED); //ожидаем завершения или
//остановки конкретного процесса
tcsetpgrp(cterm,shellpid); //возвращаем терминал интерпретатору
//здесь можно восстановить нужные параметры терминала
...
} else { //действия порожденного процесса
tcsetpgrp(cterm,getpid()); //забираем терминал
setpgid(0,0); //меняем программную группу
execv(path,params); //пытаемся запустить
perror("shell"); //сообщаем об ошибке
exit(1); //завершаем процесс с кодом ошибки
}; //endif