5. Задание на лабораторную работу
С помощью командного интерпретатора Shell набрать и выполнить команды, исходный текст которых приведен в примерах.
6. Методика выполнения задания
Составьте и выполните shell - программы, включающей следующие действия:
1. Создать каталог DIR и в нем создать файл Myfile.txt, записать в файл свою фамилию и текущее время.
2. Переименовать файл Myfile.txt в Old_Myfile.txt и установить у него атрибут ReadOnly.
3. Вычислить омическое сопротивление двух параллельно соединенных резисторов. Значения сопротивлений резисторов в диапазоне от 0,1 ом до 1000 Мом вводить с клавиатуры.
4. Перевести заданное десятичное число в шестнадцатеричную форму. Ввод десятичного числа с клавиатуры в диапазоне от 0 до 4096.
5. Запрос и ввод имени пользователя, сравнение с текущим логическим именем пользователя и вывод сообщения: верно/неверно.
6. Запрос и ввод имени файла в текущем каталоге и вывод сообщения о типе файла.
7. Циклическое чтение системного времени и очистка экрана в заданный момент.
8. Циклический просмотр списка файлов и выдача сообщения при появлении заданного имени в списке.
7. Требования к содержанию и оформлению отчета
Отчет по лабораторной работе должен содержать:
а) титульный лист;
б) исходный текст выполненных программ;
в) результаты, выведенные программами на экран дисплея;
г) ответы на контрольные вопросы.
Контрольные вопросы
1. Какое назначение имеют shell - файлы?
2. Как создать shell - файл и сделать его выполняемым?
3. Какие типы переменных используются в shell - файлах?
4. В чем заключается анализ цепочки символов?
5. Какие встроенные команды используются в shell - файлах?
6. Как производится управление программами?
7. Назовите операторы создания циклов.
Лабораторная работа №4
ФУНКЦИИ ДЛЯ СОЗДАНИЯ ПРОЦЕССОВ
1. Цель работы
Целью работы является изучение методов программирования по созданию пользовательских процессов в ОС Linux.
2. Задачи работы
– Закрепление, углубление и расширение знаний студентов при использовании процессов в прикладных программах.
– Приобретение умений и навыков работы с системой программирования на языках С и С++ в операционной системе Linux.
– Выработка способности логического мышления, осмысления полученных результатов при применении набора функций по созданию процессов.
3. Теоретическая часть
Идентификаторы процессов. Каждый процесс в Linux помечается уникальным идентификатором (PID , process identifer). Идентификаторы – это 16-разрядные числа, назначаемые последовательно по мере создания процессов. У всякого процесса имеется также родительский процесс за исключением специального суперсервера init с идентификатором 1. Таким образом, все процессы Linux организованы в виде сложной иерархии, на вершине которой находится процесс init. Иерархию процессов можно увидеть, выполнив команду ps –axf . К атрибутам процесса относится идентификатор его родительского процесса (PPID, parent process identifer).
Работая с идентификаторами процессов в программах, написанных на языках C и C++, следует объявить соответствующие переменные как имеющие тип pid_t (этот тип определяется в файле ). Программа может узнать идентификатор своего собственного процесса с помощью системного вызова getpid(), а идентификатор своего родительского процесса с помощью системного вызова getppid().
Пример 1:
#include
#include
int main()
{
printf (“Номер процесса: %d\n”, (int) getpid() );
printf («Номер родительского процесса: %d\n», (int) getppid());
return 0;
}
Обратите внимание на важную особенность: при каждом вызове программа сообщает о разных идентификаторах, поскольку всякий раз запускается новый процесс. Тем не менее, если программа вызывается из одного и того же интерпретатора команд, то родительский идентификатор оказывается одинаковым.
Создание процессов
Существуют два способа создания процессов. Первый из них относительно прост, применяется редко, поскольку неэффективен и связан со значительным риском для безопасности системы. Второй способ сложнее, но избавлен от недостатков первого. Первый способ основан на применении функции system(), а второй на основе применения функций fork() и exec().
Функция system()
Функция system() определена в стандартной библиотеке языка C и позволяет вызвать из программы системную команду, как если бы она была набрана в командной строке.
По сути, эта функция запускает стандартный интерпретатор и передает ему команду на выполнение.
Пример 2:
#include
int main()
{
int return_value;
return_value = system(“ls –l /”);
return return_value;
}
Функция system() возвращает код завершения указной команды. Если интерпретатор не может быть запущен, возвращается значения 127, а в случае возникновения других ошибок (-1).
Поскольку функция system() запускает интерпретатор команд, она подвержена всем тем ограничениям безопасности, что и командный интерпретатор.
В большинстве Unix-системах программа /bin/sh представляет собой символическую ссылку на другой интерпретатор. В Linux это в основном bash.
Вызов из функции system() программы с привилегиями пользователя root также может иметь неодинаковые последствия в разных системах. Таким образом, лучше создавать процессы с помощью функций fork() и exec().
Функции fork() и exec()
В DOS и Windows API имеется семейство функций spawn(). Они принимают в качестве аргумента имя программы, создают новый экземпляр ее процесса и запускают его.
В Linux нет такой функции, которая выполнила бы все это за одни заход. Вместо этого имеются функция fork(), создающая дочерний процесс, который является точной копией родительского процесса, и семейство функций exec(), заставляющих требуемый процесс перестать быть вторым экземпляром одной программы и превратиться в экземпляр другой программы.
Чтобы создать новый процесс, нужно сначала с помощью функции fork() создать копию текущего процесса, а затем с помощью функции exec() преобразовать одну из копий в экземпляр запускаемой программы.
Вызов функции fork()
Вызывая функцию fork(), программа создает свой дубликат, называемый дочерним процессом. Родительский процесс продолжает выполнять программу с той точки, где была вызвана функция fork(). То же самое делает и дочерний процесс.
Процессы отличаются своими идентификаторами. Таким образом, программа может вызывать функцию getpid() и узнать где именно она находится.
Но сама функция fork() реализует другой способ: она возвращает разные значения в родительском и дочернем процессах. В родительском процессе функция fork() равна идентификатору своего потомка, а в дочернем процессе она равна 0. Рассмотрим данную ситуацию на примере, учтите, что первая часть инструкции if выполняется только в родительском процессе, тогда как ветвь else – только в дочернем.
Пример 3:
#include
#include
#include
int main()
{
pid_t child_pid;
printf(“ID процесса основной программы: %d\n”, (int) getpid() );
child_pid = fork();
if (child_pid)
{
printf(«Это родительский процесс, с ID %d\n», (int) getpid() );
printf(“Дочерний процесс, с ID %d\n”, (int) child_pid );
}
else
printf(«Дочерний процесс с ID %d\n», (int) getpid() );
return 0;
}
Семейство функций exec()
Функции семейства exec() заменяют программу, выполняющуюся в текущем процессе, другой программой. Когда программа вызывает функцию exec(), ее выполнение немедленно прекращается и начинает работу новая программа.
Функции, в название которых присутствует суффикс 'p' (execvp() и execlp()), принимают в качестве аргумента имя программы и ищут эту программу в каталогах, определяемых переменной среды PATH. Всем остальным функциям нужно передавать полное путевое имя программы.
Функции, в названии которых присутствует суффикс 'v' (execv(), execvp(), execve()), принимают список аргументов программы в виде массива строковых указателей, оканчивающегося NULL-указателем. Функции с суффиксом 'l' (excevl(), execlp(), execlve()), принимают список аргументов переменного размера.
Функции, в названии которых присутствует суффикс 'e' (execve(), execle()), в качестве дополнительного аргумента принимают массив переменных среды. Этот массив содержит строковые указатели и оканчивается пустым указателем. Каждая строка должна иметь вид «Переменная = значение».
Поскольку функция exec() заменяет одну программу другой, она никогда не возвращает значение – только если вызов программы оказался невозможен в случае ошибки.
Список аргументов, передаваемых программе, аналогичен аргументам командной строки, указываемым при запуске программы в интерактивном режиме. Их тоже можно получить с помощью параметров argc и argv функции main(). Когда программу запускает интерпретатор команд, первый элемент массива argv будет содержать имя программы, а далее будут находиться переданные программе аргументы. Аналогичным образом следует поступить, формируя список аргументов для функции exec().
Совместное использование функций fork() и exec()
Стандартная методика запуска одной программы из другой такова: сначала с помощью функции fork() создается дочерний процесс, затем в нем вызывается функция exec().
Это позволяет главной программе продолжать выполнение в родительском процессе. В качестве примера напишем программу, которая отображает корневой каталог.
Пример 4:
#include
#include
#include
#include
int spawn(char* program, char** arg_list)
{
pid_t child_pid;
child_pid = fork();
if(child_pid)
return child_pid;
else
{
execvp (program, arg_list);
fprintf (stderr, “an error роцесс in execvp\n”);
abort();
}
}
int main()
{
int child_status;
char* arg_list[] = {“ls”,”-l”,”/”,NULL};
spawn (“ls”, arg_list);
wait (&child_status);
printf(“done\n”);
return 0;
}
Системные вызовы wait()
Самая простая функция в семействе называется wait(). Она блокирует вызывающий процесс до тех пор, пока один из его дочерних процессов не завершиться (или не произойдет ошибка).
Пример использования данной функции приведен выше.
Функция waitpid() позволяет дождаться завершения конкретного дочернего процесса.
Функция wait3() возвращает информацию о статистике использования центрального процессора завершившемся дочерним роцесссом.
Функция wait4() позволяет задать дополнительную информацию о том, каких процессов следует дождаться.
|