Программа копирует любые файлы, указанные при ее вызове в качестве аргументов, при условии, что разрешено открытие существующего файла и создание нового файла. Файл может включать в себя как текст, который может быть выведен на печатающее устройство, например, исходный текст программы, так и символы, не выводимые на печать, даже саму программу. Таким образом, оба вызова:
copy copy.c newcopy.c
copy copy newcopy
являются допустимыми. Существующий файл также может быть каталогом. Например, по вызову:
copy . dircontents
копируется содержимое текущего каталога, обозначенного символом ".", в обычный файл «dircontents»; информация в новом файле совпадает, вплоть до каждого байта, с содержимым каталога, только этот файл обычного типа (для создания нового каталога предназначена операция mknod). Наконец, любой из файлов может быть файлом устройства. Например, программа, вызванная следующим образом:
copy /dev/tty terminalread
читает символы, вводимые с терминала (файл /dev/tty соответствует терминалу пользователя), и копирует их в файл terminalread, завершая работу только в том случае, если пользователь нажмет ‹Ctrl/d›. Похожая форма запуска программы:
copy /dev/tty /dev/tty
вызывает чтение символов с терминала и их копирование обратно на терминал.
#include <fcntl.h>
char buffer[2048];
int version = 1; /* будет объяснено в главе 2 */
main(argc, argv)
int argc;
char *argv[];
{
int fdold, fdnew;
if (argc!= 3)
{
printf("need 2 arguments for copy program\n");
exit(1);
}
fdold = open(argv[1], O_RDONLY); /* открыть исходный файл только для чтения */
if (fdold == -1) {
printf("cannot open file %s\n", argv[1]);
exit(1);
}
fdnew = creat(argv[2], 0666); /* создать новый файл с разрешением чтения и записи для всех пользователей */
if (fdnew == -1)
{
printf("cannot create file %s\n", argv[2]);
exit(1);
}
copy(fdold, fdnew);
exit(0);
}
copy(old, new)
int old, new;
{
int count;
while ((count = read(old, buffer, sizeof(buffer))) > 0)
write(new, buffer, count);
}
Рисунок 1.3. Программа копирования файла
1.3.2 Среда выполнения процессов
Программой называется исполняемый файл, а процессом называется последовательность операций программы или часть программы при ее выполнении. В системе UNIX может одновременно выполняться множество процессов (эту особенность иногда называют мультипрограммированием или многозадачным режимом), при чем их число логически не ограничивается, и множество частей программы (такой как copy) может одновременно находиться в системе. Различные системные операции позволяют процессам порождать новые процессы, завершают процессы, синхронизируют выполнение этапов процесса и управляют реакцией на наступление различных событий. Благодаря различным обращениям к операционной системе, процессы выполняются независимо друг от друга.
Например, процесс, выполняющийся в программе, приведенной на Рисунке 1.4, запускает операцию fork, чтобы породить новый процесс. Новый процесс, именуемый порожденным процессом, получает значение кода завершения операции fork, равное 0, и активизирует операцию execl, которая выполняет программу copy (Рисунок 1.3). Операция execl загружает файл „copy“, который предположительно находится в текущем каталоге, в адресное пространство порожденного процесса и запускает программу с параметрами, полученными от пользователя. В случае успешного выполнения операции execl управление в вызвавший ее процесс не возвращается, поскольку процесс выполняется в новом адресном пространстве (подробнее об этом в главе 7). Тем временем, процесс, запустивший операцию fork (родительский процесс), получает ненулевое значение кода завершения операции, вызывает операцию wait, которая приостанавливает его выполнение до тех пор, пока не закончится выполнение программы copy, и завершается (каждая программа имеет выход в конце главной процедуры, после которой располагаются программы стандартных библиотек Си, подключаемые в процессе компиляции). Например, если исполняемая программа называется run, пользователь запускает ее следующим образом:
run oldfile newfile
Процесс выполняет копирование файла с именем „oldfile“ в файл с именем „newfile“ и выводит сообщение. Хотя данная программа мало что добавила к программе „copy“, в ней появились четыре основных обращения к операционной системе, управляющие выполнением процессов: fork, exec, wait и exit.
main(argc, argv)
int argc;
char *argv[];
{ /* предусмотрено 2 аргумента: исходный файл и новый файл */
if (fork() == 0) execl("copy", "copy", argv[1], argv[2], 0);
wait((int *)0);
printf("copy done\n");
}
Рисунок 1.4. Программа порождения нового процесса, выполняющего копирование файлов
Вообще использование обращений к операционной системе дает возможность пользователю создавать программы, выполняющие сложные действия, и как следствие, ядро операционной системы UNIX не включает в себя многие функции, являющиеся частью «ядра» в других системах. Такие функции, и среди них компиляторы и редакторы, в системе UNIX являются программами пользовательского уровня. Наиболее характерным примером подобной программы может служить командный процессор shell, с которым обычно взаимодействуют пользователи после входа в систему. Shell интерпретирует первое слово командной строки как имя команды: во многих командах, в том числе и в командах fork (породить новый процесс) и exec (выполнить порожденный процесс), сама команда ассоциируется с ее именем, все остальные слова в командной строке трактуются как параметры команды.