ляющая строка будет вторым аргументом.

Указатели файлов stdin и stdout заранее определены в
библиотеке ввода-вывода как стандартный ввод и стандартный
вывод; они могут быть использованы в любом месте, где можно
использовать объект типа FILE *. Они, однако, являются конс-
тантами, а не переменными, так что их нельзя изменять.

Функция fclose является обратной по отношению к fopen;
она разрывает связь между указателем файла и внешним именем,
установленную функцией fopen, и высвобождает указатель файла
для другого файла. В операционной системе имеются ограниче-
ния на число одновременно открытых файлов, которыми может
распоряжаться программа. Функция fclose закрывает файл, а
также вызывает выдачу информации из буфера, в котором putc
собирает вывод (при нормальном завершении программы функция
fclose вызывается автоматически для каждого открытого
файла).

    13.7. Обработка ошибок - stderr и exit



При печати диагностических сообщений желательно, чтобы
они поступали на терминал, даже если стандартный вывод пос-
тупает в некоторый файл или в межпроцессный канал.

Чтобы лучше обрабатывать такую ситуацию, к программе
точно таким же образом, как stdin и stdout, автоматически
присоединяется второй выходной файл, называемый stderr. Если
это вообще возможно, вывод, записанный в файле stderr, появ-
ляется на терминале пользователя, даже если стандартный
вывод направляется в другое место (на самом деле имеется
возможность направить такие сообщения в файл, но этого не
происходит при простом перенаправлении стандартного вывода).

Программа может также использовать функцию exit из
стандартной библиотеки, обращение к которой вызывает завер-
шение выполнения программы. Аргумент функции exit доступен
программе, вызвавшей программу пользователя в качестве под-
задачи, так что она может проверить успешное или неудачное
завершение данной программы. По соглашению, величина 0 в
качестве возвращаемого значения свидетельствует о том, что
все в порядке, а различные ненулевые значения являются приз-
наками ненормальных ситуаций.

Функция exit вызывает функцию fclose для каждого откры-
того выходного файла, с тем чтобы вывести всю помещенную в
буферы выходную информацию, а затем вызывает функцию _exit.
Функция _exit приводит к немедленному завершению без очистки


-61-


каких-либо буферов; конечно, при желании к этой функции
можно обратиться непосредственно.

    13.8. Ввод и вывод строк



Стандартная библиотека содержит функцию fgets. В
результате обращения

fgets(line, maxline, fp)

следующая строка ввода (включая символ новой строки) считы-
вается из файла fp в символьный массив line; самое большее
maxline-1 символ будет прочитан. Результирующая строка
заканчивается символом \0. Обычно функция fgets возвращает
line; в конце файла она возвращает NULL.

Предназначенная для вывода функция fputs записывает
строку (которая не обязана содержать символ новой строки) в
файл:

fputs(line, fp)


Функции gets и puts являются упрощенными вариантами
fgets и fputs, которые работают с файлами стандартного
ввода и вывода и не проверяют длину строки; gets не записы-
вает символ новой строки в память, а puts дописывает этот
символ в файл в конце строки:

gets(line)
puts(line)


    13.9. Функция ungetc



Стандартная библиотека содержит функцию, возвращающую
последний считанный символ. В результате обращения

ungetc(c,fp)

символ c возвращается в файл fp. Позволяется возвращать в
каждый файл только один символ.

    13.10. Разные стандартные функции



Стандартная библиотека предоставляет множество разнооб-
разных функций, некоторые из которых оказываются особенно
полезными.

    13.10.1. Управление памятью



Функция calloc служит для запросов памяти. В резуль-
тате обращения


-62-


calloc(n, sizeof(objеct))

возвращается либо указатель пространства, достаточного для
размещения n объектов указанного размера, либо NULL, если
запрос не может быть удовлетворен. Отводимая память инициа-
лизируется нулевыми значениями. Функция malloc делает то же
самое, но память задается в байтах:

malloc(size)


Указатель обладает нужным для рассматриваемых объектов
выравниванием, но ему следует приписывать соответствующий
тип, как в следующем примере

char *calloc();
int *ip;
ip=(int*) calloc(n,sizeof(int));


Функция free(p) освобождает пространство, на которое
указывает p, причем указатель p первоначально должен быть
получен в результате обращения к calloc. Здесь нет никаких
ограничений на порядок освобождения пространства, но осво-
бождение чего либо, не полученного с помощью calloc или mal-
loc, приводит к тяжелым ошибкам.

    13.10.2. Стандартные функции языка Си



В стандартную библиотеку функций на языке Си входит,
помимо описанных, множество самых разных функций. Подробное
описание их приведено в руководстве программисту по ОС
ДЕМОС, часть 4 (библиотечные функции), и в оперативной доку-
ментации (man(3)). Ниже в скобках приведены названия разде-
лов оперативной документации, в которых имеются соответству-
ющие описания:

- операции со строками (string);

- преобразование данных без sscanf и sprintf" (atoi,
itoa, atof, ftoa);

- математические функции (sin, exp, ...);

- проверка и преобразование символов (ctype);

- и многое другое.

    * 14. ВЗАИМОДЕЙСТВИЕ С ОПЕРАЦИОННОЙ СИСТЕМОЙ



-63-


    14.1. Подготовка программ на Си в ОС ДЕМОС



В операционной системе ДЕМОС программы могут состоять
из одного или нескольких модулей, написанных на языках Си,
Фортран-77, Ассемблер. Для трансляции и сборки программ на
языке Си служит команда cc. В простейшем случае трансляция
осуществляется по команде:

cc файл1.c файл2.c ...

где файл1.c, файл2.c, ... - имена файлов, содержащих прог-
раммы на языке Си (имена таких файлов должны оканчиваться на
суффикс .c). Команда осуществляет трансляцию перечисленных
программ и их объединение редактором связей. Если трансля-
ция прошла без ошибок, создается исполняемый файл a.out,
который можно запустить на счет, введя команду:

a.out

(то есть набрав просто имя этого файла). Трансляцию часто
проводят в два этапа: сначала транслируют отдельные прог-
раммы, получая объектные модули, а затем объединяют их
вместе (в предыдущем примере это было сделано автоматически
командой cc). Раздельная трансляция выглядит примерно так:

cc файл1.c файл2.c ...
cc файлN.c файлN1.c ...
cc файл1.o файл2.o ... файлN.o ...

В более сложном случае программа может состоять из модулей
на разных языках, результат трансляции может быть записан в
файл, отличный от a.out, можно оттранслировать программу для
отладки с помощью отладчика cdeb, и т.п. Подробное описание
вызова компилятора имеется в руководстве программиста
(cc(1), ld(1)). В общем случае программы на Си запускаются
интерпретаторами shell или cshell командой:

имя_файла аргументы назначение_ввода_вывода

где любая часть, кроме имени файла, может отсутствовать.

Любая программа на Си в ОС ДЕМОС должна содержать
головную функцию с именем main. Работа программы начинается
с этой функции, причем информация о аргументах команды пере-
дается через ее формальные параметры.

    14.2. Доступ к аргументам команды



Операционная система ДЕМОС позволяет передавать аргу-
менты команды начинающей выполняться программе. Когда функ-
ция main вызывается системой, она вызывается с двумя аргу-
ментами. Первый аргумент (типа int, условно называемый argc)
указывает число аргументов в командной строке, с которыми


-64-


происходит обращение к программе; второй аргумент (argv)
является указателем на массив символьных строк, содержащих
эти аргументы, по одному в строке.

Самую простую иллюстрацию этой возможности и необходи-
мых при этом описаний дает программа echo, которая просто
печатает в одну строку аргументы командной строки, разделяя
их пробелами. , Если дана команда

echo hello, world

то в результате получим:

hello, world


По соглашению argv[0] является именем, по которому
вызывается программа, так что argc по меньшей мере равен 1.
В приведенном выше примере argc равен 3, а argv[0], argv[1]
и argv[2] равны соответственно echo, hello, и world. Первым
фактическим агументом является argv[1], а последним -
argv[argc-1]. Если argc равен 1, то за именем программы не
следует никакой командной строки аргументов. Все это пока-
зано в echo:

main(argc, argv)
int argc;
char *argv[];
{
int i;
for (i = 1; i < argc; i++)
printf("%s%c", argv[i],
(i<argc-1) ? ' ' : '\n');
}

Поскольку argv является указателем на массив указателей, то
существует несколько способов написания этой программы,
использующих работу с указателем, а не с индексацией мас-
сива. Следующий пример демонстрирует другой вариант:

main(argc, argv)
int argc;
char **argv;
{
while (--argc > 0)
printf("%s%c",*++argv,
(argc > 1) ? ' ' : '\n');
}


Кроме строки аргументов, программа получает от системы
набор переменных, описывающих среду, в которой она выполня-
ется. Каждая переменная состоит из имени и значения


-65-


(текстовой строки). Например, переменная TERM передает тип
терминала, с которого программа запущена. Для запроса значе-
ния переменной по имени используется функция getenv:

char *getenv();
par = getenv("имя_переменной")

Функция возвращает указатель на строку - значение перемен-
ной, либо NULL, если имя не найдено в описании среды.

    * 15. ИНТЕРФЕЙС СИСТЕМЫ ДЕМОС



Все без исключения возможности операционной системы
ДЕМОС доступны из программ на языке Си. Материал этой главы
относится к интерфейсу между Си-программами и операционной
системой ДЕМОС. Материал делится на следующие части:
ввод/вывод, система файлов, процессы, сигналы. Предполага-
ется знание основных концепций ОС ДЕМОС, а также понятий
"файл", "процесс", "сигнал". Подробное описание системных
вызовов и соответствующих им функций из стандартной библио-
теке имеется в руководстве программиста по ОС ДЕМОС и в опе-
ративной документации (части 2 и 3). Например, если в опи-
сании говорится о функции popen(3), то подробное описание
следует искать в руководстве программиста, часть 4, или в
оперативной документации, часть 3; справку о функции можно
получить на терминал, набрав man 3 popen.

    15.1. Ввод/вывод



В описании библиотеки ввода/вывода был описан универ-
сальный интерфейс, который одинаков для всего многообразия
операционных систем. На каждой конкретной операционной сис-
теме функции стандартной библиотеки должны быть написаны в
терминах ввода-вывода, доступных на данной машине. В следую-
щих разделах описан набор функций ввода/вывода нижнего
уровня, поддерживаемых ядром операционной системы ДЕМОС.

    15.1.1. Дескрипторы файлов



В операционной системе ДЕМОС весь ввод и вывод осу-
ществляется посредством чтения файлов или их записи, потому
что все периферийные устройства, включая терминал пользова-
теля, являются файлами определенной файловой системы. Это
означает, что один однородный интерфейс управляет всеми свя-
зями между программой и периферийными устройствами.

В наиболее общем случае перед чтением из файла или
записью в файл необходимо сообщить системе о намерении сде-
лать это; этот процесс называется "открытием" файла. Система
выясняет, имеет ли программа право поступать таким образом
(существует ли этот файл? имеется ли разрешение на обраще-
ние к нему?), и если все в порядке, возвращает в программу
небольшое положительное целое число, называемое дескриптором


-66-


файла. Всякий раз, когда этот файл используется для ввода
или вывода, для идентификации файла употребляется дескриптор
файла, а не его имя (здесь существует примерная аналогия с
использованием read (5,...) и write (6,...) в Фортране). Вся
информация об открытом файле содержится в системе; программа
пользователя обращается к файлу только через дескриптор
файла.

Для удобства выполнения обычных операций ввода и вывода
с помощью терминала пользователя существуют специальные сог-
лашения. Когда интерпретатор команд (shell) прогоняет прог-
рамму, он открывает три файла, называемые стандартным вво-
дом, стандартным выводом и стандартным выводом ошибок, кото-
рые имеют соответственно числа 0, 1 и 2 в качестве дескрип-
торов этих файлов. В нормальном состоянии все они связаны с
терминалом, так что если программа читает с дескриптором
файла 0 и пишет с дескрипторами файлов 1 и 2, то она может
осуществлять ввод и вывод с помощью терминала, не заботясь
об открытии соответствующих файлов.

Пользователь программы может перенаправлять ввод и
вывод на файлы, используя в интерпретаторе команд символы <
и >:

prog < infile > outfile

В этом случае интерпретатор команд изменит определение
дескрипторов файлов 0 и 1 с терминала на указанные файлы.
Обычно дескриптор файла 2 остается связанным с терминалом,
так что сообщения об ошибках могут поступать туда. Подобные
замечания справедливы и тогда, когда ввод и вывод связан с
межпроцессным каналом. Следует отметить, что в этом случае
связь программы с файлами изменяется интерпретатором shell
(или cshell), а не программой. Сама программа, пока она
использует файл 0 для ввода и файлы 1 и 2 для вывода, не
знает ни откуда приходит ее ввод, ни куда поступает ее
выдача.

    15.1.2. Низкоуровневый ввод/вывод.



Самый низкий уровень ввода/вывода в системе ДЕМОС не
предусматривает ни какой-либо буферизации, ни какого-либо
другого сервиса; он по существу является непосредственным
обращением к операционной системе. Весь ввод и вывод осу-
ществляется двумя функциями: read и write. Первым аргумен-
том обеих функций является дескриптор файла. Вторым аргумен-
том является буфер в вашей программе, откуда или куда должны
поступать данные. Третий аргумент - это число подлежащих
пересылке байтов. Обращения к этим функциям имеют вид:

n_read=read(fd,buf,n);
n_written=write(fd,buf,n);

-67-


При каждом обращении возвращается счетчик байтов, указываю-
щий фактическое число переданных байтов. При чтении возвра-
щенное число байтов может оказаться меньше, чем запрошенное
число. Возвращенное нулевое число байтов означает конец
файла, а "-1" указывает на наличие какой-либо ошибки. При
записи возвращенное значение равно числу фактически записан-
ных байтов; несовпадение этого числа с числом байтов, кото-
рое предполагалось записать, обычно свидетельствует об
ошибке.

Количество байтов, подлежащих чтению или записи, может
быть совершенно произвольным. Двумя самыми распространенными
величинами являются "1", что означает передачу одного сим-
вола за обращение (т.е. без использования буфера), и "512",
что соответствует физическому размеру блока на многих пери-
ферийных устройствах. Этот последний размер будет наиболее
эффективным, но даже ввод или вывод по одному символу за
обращение не будет слишком дорогим.

Пример. Копирование ввода на вывод. В системе ДЕМОС
эта программа будет копировать что угодно куда угодно,
потому что ввод и вывод могут быть перенаправлены на любой
файл или устройство.

#define BUFSIZE 512
main() /*copy input to output*/
{
char buf[BUFSIZE];
int n;
while((n=read(0,buf,BUFSIZE))>0)
write(1,buf,n);
}

Если размер файла не будет кратен BUFSIZE, то при очередном
обращении к read будет возвращено меньшее число байтов,
которые затем записываются с помощью write; при следующем
после этого обращении к read будет возвращен нуль.

    15.1.3. Открытие, создание, закрытие и удаление



Во всех случаях, если только не используются определен-
ные по умолчанию стандартные файлы ввода, вывода и ошибок,
вы должны явно открывать файлы, чтобы затем читать из них
или писать в них. Для этой цели существуют две функции: open
и creat.

Функция open весьма сходна с функцией fopen, рассмот-
ренной выше, за исключением того, что вместо возвращения
указателя файла она возвращает дескриптор файла, который
является просто целым типа int.

int fd;
fd=open(name,rwmode);


-68-


Как и в случае fopen, аргумент name является символьной
строкой, соответствующей внешнему имени файла. Однако аргу-
мент, определяющий режим доступа, отличен: rwmode равно: 0 -
для чтения, 1 - для записи, 2 - для чтения и записи. Если
происходит какая-то ошибка, функция open возвращает "-1"; в
противном случае она возвращает неотрицательный дескриптор
файла.

Попытка открыть файл, который не существует, является
ошибкой. Функция creat предоставляет возможность создания
новых файлов или перезаписи старых. В результате обращения:

fd=creat(name,pmode);

возвращает дескриптор файла, если оказалось возможным соз-
дать файл с именем name, и "-1" в противном случае. Созда-
ние файла, который уже существует, не является ошибкой:
creat усечет его до нулевой длины.

Если файл ранее не существовал, то creat создает его с
определенным режимом защиты, специфицируемым аргументом
pmode. В системе файлов ОС ДЕМОС с файлом связываются девять
битов защиты информации, которые управляют разрешением на
чтение, запись и выполнение для владельца файла, для группы
владельцев и для всех остальных пользователей. Таким обра-
зом, трехзначное восьмеричное число наиболее удобно для
записи режима защиты. Например, число 0755 свидетельствует о
разрешении на чтение, запись и выполнение для владельца и о
разрешении на чтение и выполнение для группы и всех осталь-
ных.

Существует ограничение (обычно 15 - 25) на количество
файлов, которые программа может иметь открытыми одновре-
менно. В соответствии с этим любая программа, собирающаяся
работать со многими файлами, должна быть подготовлена к пов-
торному использованию дескрипторов файлов. Процедура close
прерывает связь между дескриптором файла и открытым файлом и
освобождает дескриптор файла для использования с другим фай-
лом. Завершение выполнения программы через exit или в
результате возврата из головной функции приводит к закрытию
всех открытых файлов.

Функция удаления unlink(filename) удаляет из системы
файл с именем filename (Точнее, удаляет имя filename, файл
удаляется, если на него не остается ссылок под другими име-
нам).

    15.1.4. Произвольный доступ - lseek



Обычно при работе с файлами ввод и вывод осуществляется
последовательно: при каждом обращении к функциям read и
write чтение или запись начинаются с позиции, непосредст-
венно следующей за предыдущей обработанной. Но при


-69-


необходимости файл может читаться или записываться в любом
произвольном порядке. Обращение к системе с помощью функции
lseek позволяет передвигаться по файлу, не производя факти-
ческого чтения или записи. В результате обращения

lseek(fd,offset,origin);

текущая позиция в файле с дескриптором fd передвигается на
позицию offset (смещение), которая отсчитывается от места,
указываемого аргументом origin (начало отсчета). Последующее
чтение или запись будут теперь начинаться с этой позиции.
Аргумент offset имеет тип long; fd и origin имеют тип int.
Аргумент origin может принимать значения 0, 1 или 2, указы-
вая на то, что величина offset должна отсчитываться соот-
ветственно от начала файла, от текущей позиции или от конца
файла. Например, чтобы дополнить файл, следует перед записью
найти его конец:

lseek(fd,0l,2);

чтобы вернуться к началу, можно написать:

lseek(fd,0l,0);

Обратите внимание на аргумент 0l; его можно было бы записать
и в виде (long) 0.

Функция lseek позволяет обращаться с файлами примерно
так же, как с большими массивами, только ценой более медлен-
ного доступа.

Пример. Функция, считывающая любое количество байтов, начи-
ная с произвольного места в файле.

/*читать n байтов с позиции pos в buf */
get(fd,pos,buf,n)
int fd, n;
long pos;
char *buf;
{
lseek(fd,pos,0); /*get to pos */
return(read(fd,buf,n));
}


    15.2. Управление процессами



В операционной системе ДЕМОС часто требуется вызвать из
программы и выполнить в виде отдельного процесса другую
программу. Следующий раздел описывает простейший способ
сделать это, а далее будут рассмотрены базовые средства
управления процессами, имеющиеся в ОС ДЕМОС.

-70-


    15.2.1. Функция system



Простейший способ вызвать другую программу - использо-
вать стандартную функцию system:

system("командная строка")

Функция имеет один параметр - строку, которую она анализи-
рует и выполняет точно так же, как выполняются команды, вво-
димые интерпретатором shell с терминала. Функция выполняет
команду и возвращает целое число - код ответа выполненной
команды (0, если все кончилось нормально). В командной
строке воспринимаются любые символы управления
вводом/выводом >, <, и т.п.

Следует учесть, что, если в программе вывод буферизу-
ется, то перед вызовом функции system необходимо вытолкнуть
буфера, например вызвав функцию fflush.

    15.2.2. Вызов программы на низком уровне - execl



Вызов программы в ОС ДЕМОС осуществляется с помощью
нескольких элементарных функций, одна из которых - функция
execl - осуществляет вызов новой программы вместо уже выпол-
няющейся, без возврата в вызывающую программу. Обращение в
ней имеет вид:

execl(команда,арг0,арг1,...,аргN,NULL);

где "команда" - строка символов, точно именующая файл вызы-
ваемой команды. Например, для вызова команды pr необходимо
указать имя /bin/pr. Остальные аргументы также представляют
собой строки символов и просто передаются команде в качестве
аргументов, при этом арг0 обычно представляет собой просто
сокращенное имя команды, а остальные аргументы - параметры
данной команды.

Вызов execl в случае нормального запуска новой прог-
раммы заменяет ею текущую программу, управление из функции
execl возвращается только в случае ошибки (например, не най-
дена команда с указанным именем). В библиотеке имеется
целый набор функций, осуществляющих то же самое и отличаю-
щихся только представлением параметров (execl(2), execv(2),
execvp(2), ...) и тем, что некоторые функции осуществляют
поиск команды в стандартном наборе справочников.

    15.2.3. Порождение нового процесса - fork



Для того, чтобы запустить параллельно новую программу,
необходимо прежде всего уметь запускать параллельный про-
цесс. Для этого в ОС ДЕМОС служит функция "fork" (развет-
виться):

-71-


proc_id = fork()

Программа разделяется на две идентичные копии, которые про-
должают выполняться как два независимых процесса. Одна из
программ - процесс "сын" - получает от функции fork код
ответа 0, другая - "родитель" - получает номер, под которым
запущен процесс "сын". В простейшем случае для запуска
параллельной программы вызов fork комбинируется с execl сле-
дующим образом:

if( fork() == 0)
{ /* Это процесс - сын */
... настройка файлов ...
execl(... );
/*Сюда мы попадаем при ошибке в execl */
perror("Не могу запустить процесс");
exit(1);
}
... продолжение основной программы ...

Здесь программа после вызова fork анализирует, в каком про-
цессе ("родитель" или "сын") она выполняется и, в зависи-
мости от этого, ведет себя по разному. Если основная прог-
рамма должна ждать окончания "сына", то она должна вызвать
функцию wait:

int status;
...
if( fork() == 0)
{ ... execl(...); exit(1);
}
wait(&status));

Функция wait возвращает идентификатор процесса - "сына", и
засылает в переменную status код завершения этого процесса.
Код завершения состоит из двух частей - младшие 8 битов фор-
мируются системой и обозначают причину окончания процесса; в
случае нормального окончания по функции exit" они содержат
0. Старшие 8 битов в случае, если программа окончилась в
результате вызова exit, берутся из аргумента вызова функции
exit; обычно передается 0 при нормальном завершении и число,
отличное от нуля, в случае каких либо ошибок.

Ни fork, ни execl не затрагивают открытых файлов, после
fork ранее открытые файлы начинают использоваться обоими
процессами совместно, то есть используются одни и те же ука-
затели позиции чтения/записи. Если новому процессу требу-
ется передать какие то открытые файлы, или изменить файлы
стандартного ввода/вывода, настройка программы на эти файлы