Стандартный ввод, стандартный вывод и стандартный вывод диагностики
Стандартная библиотека ввода/вывода обеспечивает две структуры TFILE, связанные со стандартным вводом и стандартным выводом, и переменная типа TEXT, связанная со стандартным выводом диагностики. (Еще раз напомним, что не следует путать эти потоки с одноименными дескрипторами ввода/вывода 0, 1 и 2.) Эти стандартные структуры не требуют открытия и задаются предопределенными указателями:
stdin Соответствует стандартному вводу
stdout Соответствует стандартному выводу
stderr Соответствует стандартному выводу диагностики
Следующий вызов получает очередной символ из структуры stdin, которая так же, как и дескриптор файла со значением 0, по умолчанию соответствует клавиатуре:
inchar := getc (stdin);
Так как ввод и вывод через потоки stdin и stdout используются очень часто, для удобства определены еще две процедуры – getchar и putchar. Процедура getchar возвращает очередной символ из stdin, а процедура
putchar выводит символ в stdout. Они аналогичны процедурам getc и putc, но не имеют аргументов.
Следующая программа io2 использует процедуры getchar и putchar для копирования стандартного ввода в стандартный вывод:
(* Программа io2 - копирует stdin в stdout *)
uses stdio;
var
c:integer;
begin
c := getchar;
while c <> EOF do
begin
putchar (c);
c := getchar;
end;
end.
Программа io2 ведет себя почти аналогично приведенному ранее примеру – программе io
из главы 2.
Так же, как getc и putc, getchar и putchar могут быть макросами. Фактически getchar часто просто определяется как getс(stdin), a putchar – как putc(stdout).
stderr обычно предназначена для вывода сообщений об ошибках, поэтому вывод в stderr обычно не буферизуется. Другими словами, символ, который посылается в stderr, будет немедленно записан в файл или устройство, соединенное со стандартным выводом диагностики. При включении отладочной печати в код для тестирования рекомендуется выполнять вывод в stderr. Вывод в stdout буферизуется и может появиться через несколько шагов после того, как он в действительности произойдет. (Вместо этого можно использовать процедуру fflush(stdout) после каждого вывода для записи всех сообщений из буфера stdout.)[18]
Упражнение 11.4. При помощи стандартной команды time сравните производительность программы io2 и программы io, разработанной в главе 2. Измените исходную версию программы io так, чтобы она использовала вызовы fdread и fdwrite для посимвольного ввода и вывода. Снова сравните производительность полученной программы и программы io2.
Упражнение 11.5. Перепишите программу io2 так, чтобы она более соответствовала команде cat. В частности, сделайте так, чтобы она выводила на экран содержимое файлов, заданных в качестве аргументов командной строки. При отсутствии аргументов она должна принимать ввод из stdin.
Структура книги
Книга состоит из тринадцати глав.
Глава 1 представляет собой обзор основных понятий и терминологии. Два наиболее важных из обсуждаемых терминов – это файл (file) и процесс (process). Мы надеемся, что большинство читателей книги уже хотя бы частично знакомы с приведенным в главе материалом (см. в следующем разделе предпосылки для изучения книги).
В главе 2 описаны системные примитивы доступа к файлам, включая открытие и создание файлов, чтение и запись данных в них, а также произвольный доступ к содержимому файлов. Представлены также способы обработки ошибок, которые могут генерироваться системными вызовами.
В главе 3 изучается контекст работы с файлами. В ней рассмотрены вопросы владения файлами, управления системными привилегиями в системе UNIX и оперирования атрибутами файлов при помощи системных вызовов.
Глава 4 посвящена концепции дерева каталогов
(directories) с точки зрения программиста. В нее также включено краткое обсуждение файловых систем (file systems) и специальных файлов
(special files), используемых для представления устройств.
Глава 5 посвящена природе процессов
UNIX и методам работы с ними. В ней представляются и подробно объясняются системные вызовы fork и ехес. Приводится пример простого командного интерпретатора (command processor).
Глава 6 – первая из трех глав, посвященных межпроцессному взаимодействию. Она охватывает сигналы (signals) и обработку сигналов (signal handling) и весьма полезна для перехвата ошибок и обработки аномальных ситуаций.
В главе 7 рассматривается наиболее полезный метод межпроцессного взаимодействия в системе UNIX – программные каналы, или конвейеры (pipes), позволяющие передавать выход одной программы на вход другой. Будет исследовано создание каналов, чтение и запись с их помощью, а также выбор из множества каналов.
Глава 8 посвящена методам межпроцессного взаимодействия, которые были впервые введены в ОС System V. В ней описаны блокировка записей (record locking), передача сообщений (message passing), семафоры (semaphores) и разделяемая память (shared memory).
В главе 9 рассматривается работа терминала на уровне системных вызовов. Представлен пример использования псевдотерминалов (pseudo terminals).
В главе 10 дается краткое описание сетевой организации UNIX и рассматриваются сокеты (sockets), которые могут использоваться для пересылки данных между компьютерами.
В главе 11 мы отходим от системных вызовов и начинаем рассмотрение основных библиотек. В этой главе приведено систематическое изложение стандартной библиотеки ввода/вывода (Standard I/O Library), содержащей намного больше средств для работы с файлами, чем системные примитивы, представленные в главе 2.
Глава 12 дает обзор дополнительных системных вызовов и библиотечных процедур, многие из которых очень важны при создании реальных программ. Среди обсуждаемых тем – обработка строк, функции работы со временем и функции управления памятью.
И, наконец, глава 13 содержит задачи с решениями, обобщающими весь предыдущий материал.
Структура TFILE
Процедуры буферизованного ввода/вывода
идентифицируют открытые файлы (каналы, сокеты, устройства и другие объекты) при помощи указателя на структуру типа FILE. Процедуры этого семейства также называют процедурами стандартного ввода/вывода, так как они содержатся в стандартной библиотеке языка С. Указатель на объект FILE часто называется также потоком ввода/вывода и является аналогом файловых дескрипторов базового ввода/вывода.
Определение структуры TFILE находится в заголовочном файле stdio. Следует отметить, что программисту нет необходимости знать устройство структуры TFILE, более того, ее определение различно в разных системах.
Все данные, считываемые из файла или записываемые в файл, передаются через буфер структуры TFILE. Например, стандартная процедура вывода сначала будет лишь заполнять символ за символом буфер. Только после заполнения буфета очередной вызов библиотечной процедуры вывода автоматически запишет его содержимое в файл вызовом fdwrite. Эти действия невидимы для пользовательской программы. Размер буфера составляет BUFSIZ байтов. Постоянная BUFSIZ определена в файле stdio и, как уже описывалось во второй главе, обычно задает размер блоков на диске. Как правило, ее значение равно 512 или 1024 байта.
Аналогично процедура ввода извлекает данные из буфера, связанного со структурой TFILE. Как только буфер опустеет, для его заполнения автоматически считывается еще один фрагмент файла. Эти действия также не видимы для пользовательской программы.
Механизм буферизации стандартной библиотеки ввода/вывода гарантирует, что данные всегда считываются и записываются блоками стандартного размера. В результате число обращений к файлам и число внутренних системных вызовов поддерживаются на оптимальном уровне. Но поскольку вся буферизация скрыта внутри процедур ввода/вывода, программист может их использовать для чтения или записи произвольных порций данных, даже по одному байту. Поэтому программа может быть составлена исходя из требований простоты и наглядности, а проблему общей эффективности ввода/вывода решат стандартные библиотеки. Далее увидим, что стандартные библиотечные процедуры также обеспечиваю простые в использовании средства форматирования. Поэтому для большинства приложений стандартный ввод/вывод является предпочтительным методом доступа к файлам.
Структура tstat
Структура tstat, которую уже была обсуждена в главе 3, позволяет хранить ин формацию о файле устройства в двух полях:
mode | В случае файла устройства это поле содержит права доступа к файлу, к которым прибавлено восьмеричное значение 060000 для блочных устройств или 020000 для символьных устройств. В модуле linux определены константы STAT_IFBLK и STAT_IFCHR, которые могут использоваться вместо этих чисел | ||
rdev | Это поле содержит старший и младший номера устройства |
Можно вывести эту информацию при помощи команды ls с параметром -l, например:
$ ls -l /dev/tty3
crw--w--w- 1 ben other 8,3 Sep 13 10:19 /dev/tty3
Обратите внимание на символ с в первой строке вывода, что говорит о том, что /dev/tty3 является символьным устройством. Значения 8 и 3 представляют старший и младший номера устройства соответственно.
Можно получить в программе значение поля mode при помощи методики, введенной в упражнении 4.2:
if S_ISCHR(buf.mode) then
writeln('Символьное устройство')
else
writeln('He символьное устройство');
S_ISCHR – это макрос, определенный в модуле linux.
Структуры данных статуса
При создании объекта межпроцессного взаимодействия система также создает структуру статуса средства межпроцессного взаимодействия (IPC facility status structure),
содержащую всю управляющую информацию, связанную с объектом. Для сообщений, семафоров и разделяемой памяти существуют разные типы структуры статуса. Каждый тип содержит информацию, свойственную этому средству межпроцессного взаимодействия. Тем не менее все три типа структуры статуса содержат общую структуру прав доступа. Структура прав доступа tipc_perm содержит следующие элементы:
TIPC_Perm = record
key : TKey;
uid, (* Действующий идентификатор пользователя *)
gid, (* Действующий идентификатор группы *)
cuid, (* Идентификатор пользователя создателя объекта *)
cgid, (* Идентификатор группы создателя объекта *)
mode, (* Права доступа *)
seq : Word;
end;
Права доступа определяют, может ли пользователь выполнять «чтение» из объекта (получать информацию о нем) или «запись» в объект (работать с ним). Коды прав доступа образуются точно таким же образом, как и для файлов. Поэтому значение 0644 для элемента umode означает, что владелец может выполнить чтение и запись объекта, а другие пользователи – только чтение из него. Обратите внимание, что права доступа, заданные элементом mode, применяются в сочетании с действующими идентификаторами пользователя и группы (записанными в элементах uid и gid).[16]
Очевидно также, что права на выполнение в данном случае не имеют значения. Как обычно, суперпользователь имеет неограниченные полномочия. В отличие от других конструкций UNIX,
значение переменной umask пользователя не действует при создании средства межпроцессного взаимодействия.
Связывание
Системный вызов bind связывает сетевой адрес компьютера с идентификатором сокета.
Текущий корневой каталог
С каждым процессом также связан корневой каталог, который используется при поиске абсолютного пути. Так же, как и текущий рабочий каталог, корневым каталогом процесса первоначально является корневой каталог его родительского процесса. Для изменения начала иерархии файловой системы для процесса в ОС UNIX существует системный вызов chroot:
Текущий рабочий каталог
После входа в систему пользователь находится в определенном месте файловой структуры, называемом текущим рабочим каталогом (current working directory) или иногда просто текущим каталогом (current directory). Это будет, например, каталог, содержимое которого выведет команда ls
при ее запуске без параметров. Первоначально в качестве текущего рабочего каталога для пользователя выступает его домашний каталог, заданный в файле паролей системы. Можно перейти в другой каталог при помощи команды cd, например, команда
$ cd /usr/keith
сделает /usr/keith текущим каталогом. Имя текущего каталога можно при необходимости узнать при помощи команды вывести рабочий каталог (print working directory, сокращенно pwd):
$ pwd
/usr/keith
В отношении текущего каталога основной особенностью является то, что с него система начинает поиск при задании относительного пути – то есть такого, который не начинается с корня /. Например, если текущий рабочий каталог /usr/keith, то команда
$ cat book/chap1
эквивалентна команде
$ cat /usr/keith/book/chap1
а команда
$ cat file1
эквивалентна команде
$ cat usr/keith/file1
Текущий рабочий каталог
Как уже было рассмотрено в разделе 4.2, после входа в систему пользователь работает в текущем рабочем каталоге. Фактически каждый процесс UNIX, то есть каждый экземпляр выполняемой программы, имеет свой текущий рабочий каталог, который используется в качестве начальной точки при поиске относительных путей в вызовах fdopen и им подобных. Текущий рабочий каталог пользователя на самом деле является текущим рабочим каталогом процесса оболочки, интерпретирующего команды пользователя.
Первоначально в качестве текущего рабочего каталога процесса задается текущий рабочий каталог запустившего его процесса, обычно оболочки. Процесс может поменять свой текущий рабочий каталог при помощи системного вызова chdir.
Текущий рабочий каталог
Как было установлено в главе 4, с каждым процессом связан текущий рабочий каталог. Первоначально текущий рабочий каталог наследуется во время создавшего процесс вызова fork или exec. Другими словами, процесс первоначально помещается в тот же каталог, что и родительский процесс.
Важным фактом является то, что текущий рабочий каталог является атрибутом отдельного процесса. Если дочерний процесс меняет каталог при помощи вызова chdir (определение которого приведено в главе 4), то текущий рабочий каталог родительского процесса не меняется. Поэтому стандартная команда cd
на самом деле является «встроенной» командой оболочки, а не программой.
Терминал UNIX
Как уже упоминалось в главе 4, терминалы обозначаются файлами устройств (из-за природы терминалов они рассматриваются как символьные устройства). Вследствие этого доступ к терминалам, а точнее к портам терминалов, обычно можно получить при помощи имен файлов в каталоге dev. Типичные имена терминалов могут быть такими:
/dev/console
/dev/tty01
/dev/tty02
/dev/tty03
...
Обозначение tty является широко используемым в UNIX синонимом терминала.
Из-за универсальности понятия файла
UNIX к терминалам можно получить доступ при помощи стандартных примитивов доступа к файлам, таких как fdread или fdwrite. Права доступа к файлам сохраняют свои обычные значения и поэтому управляют доступом к терминалам в системе. Чтобы эта схема работала, владелец терминала меняется при входе пользователя в систему, при этом все пользователи являются владельцами терминала, за которым они работают.
Обычно процессу не нужно явно открывать файл терминала для взаимодействия с пользователем. Это происходит из-за того, что его стандартный ввод и вывод, если они не переопределены, будут по умолчанию связаны с терминалом пользователя. Поэтому, если предположить, что стандартный вывод не назначен в файл, то следующий фрагмент кода приведет к выводу данных на экран терминала:
const FD_STDOUT=1;
.
.
.
fdwrite(FD_STDOUT, mybuffer, somesize);
В традиционном окружении UNIX терминалы, обеспечивающие вход в систему, обычно первоначально открываются при старте системы программой управления процессами
init. Дескрипторы файла терминала передаются потомка программы init, и, в конечном итоге, каждый процесс пользовательской оболочки будет наследовать три дескриптора файла, связанные с терминалом пользователя. Эти дескрипторы будут представлять стандартный ввод, стандартный вывод и стандартный вывод диагностики оболочки. Они в свою очередь передаются всем запущенным из оболочки программам.
Типы соединения
Если процессам нужно передать данные по сети, они могут выбрать для этого один из двух способов связи. Процесс, которому нужно посылать неформатированный, непрерывный поток символов одному и тому же абоненту, например, процессу удаленного входа в систему, может использовать модель соединения (connection oriented model) или виртуальное соединение (virtual circuit). В других же случаях (например, если серверу нужно разослать сообщение клиентам, не проверяя его доставку) процесс может использовать модель дейтаграмм (connectionless oriented model). При этом процесс может посылать сообщения (дейтаграммы) по произвольным адресам через один и тот же сокет без предварительного установления связи с этими адресами. Термин дейтаграмма (datagram) обозначает пакет пользовательского сообщения, посылаемый через сеть. Для облегчения понимания приведем аналогию: модель виртуальных соединений напоминает телефонную сеть, а модель дейтаграмм – пересылку писем по почте. Поэтому в последнем случае нельзя быть абсолютно уверенным, что сообщение дошло до адресата, а если необходимо получить ответ на него, то нужно указать свой обратный адрес на конверте. Модель соединений будет более подходящей при необходимости получения тесного взаимодействия между системами, когда обмен сообщениями и подтверждениями происходит в определенном порядке. Модель без соединений является более эффективной и лучше подходит в таких случаях, как рассылка широковещательных сообщений большому числу компьютеров.
Для того чтобы взаимодействие между процессами на разных компьютерах стало возможным, они должны быть связаны между собой как на аппаратном уровне при помощи сетевого оборудования – кабелей, сетевых карт и различных устройств маршрутизации, так и на программном уровне при помощи стандартного набора сетевых протоколов. Протокол представляет собой просто набор правил, в случае сетевого протокола – набор правил обмена сообщениями между компьютерами. Поэтому в системе UNIX должны существовать наборы правил для обеих моделей – как для модели соединений, так и для модели дейтаграмм. Для модели соединений используется протокол управления передачей (Transmission Control Protocol, сокращенно TCP), а для модели дейтаграмм – протокол пользовательских дейтаграмм (User Datagram Protocol, сокращенно UDP).[17]
Точка и двойная точка
В каждом каталоге всегда присутствуют два странных имени файлов: точка (.) и двойная точка (..). Точка является стандартным для системы UNIX способом обозначения текущего рабочего каталога, как в команде
$ cat ./fred
которая выведет на экран файл fred в текущем каталоге, или
$ ls .
которая выведет список файлов в текущем каталоге. Двойная точка является стандартным способом ссылки на родительский каталог текущего рабочего каталог, то есть каталог, содержащий текущий каталог. Поэтому команда
$ cd ..
позволяет пользователю переместиться на один уровень вверх по дереву каталогов.
Фактически имена «точка» (.) и «двойная точка» (..) просто являются ссылками на текущий рабочий каталог и родительский каталог соответственно, и любой каталог UNIX содержит в первых двух позициях эти два имени. Другими словами, во время создания каталога в него автоматически добавляются эти два имени.
Можно более ясно это представить себе, рассмотрев участок дерева каталогов, приведенный на рис. 4.4.
![]() ![]() | ben | ||||||||||||
book | memos | ||||||||||||
![]() ![]() ![]() ![]() | | | | | |||||||||||
chap1 | chap2 | chap3 | kd | kh | mv |
Рис. 4.4. Часть дерева каталогов
Если рассмотреть каждый из каталогов ben, book и memos, то откроется картина, похожая на рис. 4.5. Нужно обратить внимание на то, что в каталоге book номер записи с именем . равен 260, а номер записи с именем ..
равен 123, и эти номера соответствуют элементам book и . в родительском каталоге ben. Аналогично имена . и ..
в каталоге memos (с номерами узлов 401 и 123) соответствуют каталогу memos и имени .
в каталоге ben.
Удаление файла
Существует один метод удаления файла из системы – при помощи вызова unlink.
Указатель чтения-записи
Достаточно естественно, что программа может последовательно вызывать fdread для просмотра файла. Например, если предположить, что файл foo содержит не менее 1024 символов, то следующий фрагмент кода поместит первые 512 символов из файла foo в массив buf1, а вторые 512 символов – в массив buf2.
var
fd:integer;
n1,n2:longint;
buf1, buf2 : array [0..511] of char;
.
.
.
fd := fdopen('foo', Open_RDONLY);
if fd = -1 then
halt(-1);
n1 := fdread(fd, buf1, 512);
n2 := fdread(fd, buf2, 512);
Система отслеживает текущее положение в файле при помощи объекта, который называется указателем ввода/вывода (read-write pointer), или указателем файла
(file pointer). По существу, в этом указателе записано положение очередного байта в файле, который должен быть считан (или записан) следующим для определенного дескриптора файла; следовательно, указатель файла можно себе представить в виде закладки. Его значение отслеживает система, и программисту нет необходимости выделять под него переменную. Произвольный доступ, при котором положение указателя ввода/вывода изменяется явно, может осуществляться при помощи системного вызова fdseek, который описан в разделе 2.1.10. В случае вызова fdread система просто перемещает указатель ввода/вывода вперед на число байтов, считанных в результате данного вызова.
Поскольку вызов fdread может использоваться для просмотра файла с начала и до конца, программа должна иметь возможность определять конец файла. При этом становится важным возвращаемое вызовом fdread значение. Если число запрошенных во время вызова fdread символов больше, чем оставшееся число символов в файле, то система передаст только оставшиеся символы, установив соответствующее возвращаемое значение. Любые последующие вызовы fdread будут возвращать значение 0. При этом больше не останется данных, которые осталось бы прочитать. Обычным способом проверки достижения конца файла в программе является проверка равенства нулю значения, возвращаемого вызовом fdread, по крайней мере, для программы, использующей вызов fdread.
Следующая программа count иллюстрирует некоторые из этих моментов:
(* Программа count подсчитывает число символов в файле *)
uses linux;
const
BUFSIZE=512;
var
filedes:integer;
nread:longint;
buffer:array [0..BUFSIZE-1] of byte;
total:longint;
begin
total := 0;
(* Открыть файл 'anotherfile' только для чтения *)
filedes := fdopen ('anotherfile', Open_RDONLY);
if filedes=-1 then
begin
writeln('Ошибка при открытии файла anotherfile');
halt(1);
end;
(* Повторять до конца файла, пока nread не будет равно 0 *)
nread := fdread (filedes, buffer, BUFSIZE);
while nread > 0 do
begin
inc(total,nread); (* увеличить total на nread *)
nread := fdread (filedes, buffer, BUFSIZE);
end;
writeln('Число символов в файле anotherfile: ', total);
fdclose(filedes);
halt(0);
end.
Эта программа будет выполнять чтение из файла anotherfile блоками по 512 байт. После каждого вызова fdread значение переменной total будет увеличиваться на число символов, действительно скопированных в массив buffer. Почему total объявлена как переменная типа longint?
Здесь использовано для числа считываемых за один раз символов значение 512, поскольку система UNIX сконфигурирована таким образом, что наибольшая производительность достигается при перемещении данных блоками, размер которых кратен размеру блока на диске, в этом случае 512. (В действительности размер блока зависит от конкретной системы и может составлять до и более 8 Кбайт.) Тем не менее мы могли бы задавать в вызове fdread произвольное число, в том числе единицу. Введение определенного значения, соответствующего вашей системе, не дает выигрыша в функциональности, а лишь повышает производительность программы, но, как мы увидим в разделе 2.1.9, это улучшение может быть значительным.
Упражнение 2.4. Если вы знаете, как это сделать, перепишите программу count так, чтобы вместо использования постоянного имени файла она принимала его в качестве аргумента командной строки. Проверьте работу программы на небольшом файле, состоящем из нескольких строк.
Упражнение 2.5. Измените программу count так, чтобы она также выводила число слов и строк в файле. Определите слово как знак препинания или алфавитно-цифровую строку, не содержащую пробельных символов, таких как пробел, табуляция или перевод строки. Строкой, конечно же, будет любая последовательность символов, завершающаяся символом перевода строки.
Следующая программа демонстрирует функции fdread и fdtrucate.
Uses linux;
Const Data : string[10] = '12345687890';
Var FD : Longint;
l : longint;
begin
FD:=fdOpen('test.dat',open_wronly or open_creat,octal(666));
if fd>0 then
begin
{ Fill file with data }
for l:=1 to 10 do
if fdWrite (FD,Data[1],10)<>10 then
begin
writeln ('Error when writing !');
halt(1);
end;
fdClose(FD);
FD:=fdOpen('test.dat',open_rdonly);
{ Read data again }
If FD>0 then
begin
For l:=1 to 5 do
if fdRead (FD,Data[1],10)<>10 then
begin
Writeln ('Error when Reading !');
Halt(2);
end;
fdCLose(FD);
{ Truncating file at 60 bytes }
{ For truncating, file must be open or write }
FD:=fdOpen('test.dat',open_wronly,octal(666));
if FD>0 then
begin
if not fdTruncate(FD,60) then
Writeln('Error when truncating !');
fdClose (FD);
end;
end;
end;
end.
Управление динамическим распределением памяти
Каждая из программ, рассмотренных ранее, использовала структуры данных, определенные при помощи стандартных объявлений языка Паскаль, например:
var
x, у: something;
z: ^something;
a: array [0..19] of something;
Другими словами, расположение данных в наших примерах определялось во время компиляции. Однако многие вычислительные задачи удобнее решать при помощи динамического создания и уничтожения структур данных, а это значит, что расположение данных в программе определяется окончательно только во время выполнения программы. Семейство библиотечных функций ОС UNIX malloc (от memory allocation – выделение памяти) позволяет создавать объекты в области динамической памяти на стадии выполнения программы, которая в англоязычной литературе часто называется heap – «куча», «кипа» (книг). Функция malloc определяется следующим образом:
Управление файлами
Упражнение 13.41. Составьте аналог команды rm.
uses linux,sysutils;
var
f:text;
d:boolean;
k:char;
s:string;
begin
writeln('введите имя файла, который нужно удалить');
readln(s);
assign(f,s);
if s='' then
begin
writeln('повторите попытку');
exit;
end;
writeln('подтвердите удаление файла Y/N');
readln(k);
if (k='Y') or (k='y') then
begin
d:=deletefile(s);
if d then
writeln('файл удален')
else
writeln('файл не удален');
end
else
writeln('файл не удален');
end.
Упражнение 13.42. Используя системный вызов fstat, напишите программу, определяющую тип файла: обычный файл, каталог, устройство, FIFO-файл.
uses linux,strings,sysutils;
function gettype(mode:integer):string;
begin
if S_ISREG(mode) then
gettype:='файл'
else
if S_ISDIR(mode) then
gettype:='каталог'
else
if S_ISCHR(mode) then
gettype:='байтоориентированное устройство'
else
if S_ISBLK(mode) then
gettype:='блочноориентированное устройство'
else
if S_ISFIFO(mode) then
gettype:='FIFO-файл'
else
gettype:='другое';
end;
var
st:stat;
name:array[0..255] of char;
begin
if paramcount = 0 then
name:='.'
else
name:=fexpand(paramstr(1));
if not fstat(pchar(name),st) then
writeln('Ошибка вызова stat для ',name)
else
write(gettype(st.mode));
end.
Упражнение 13.43. Составьте аналог команды chgrp.
Uses linux;
Var
UID,GID:Longint;
F:Text;
Code:Integer;
begin
Writeln('This will only work if you are root.');
if ParamCount<3 then
begin
Writeln('Error!!!');
Writeln('Format: ./task <Filename> <UID> <GID>');
Halt(1);
end;
val(Paramstr(2),UID,Code);
if Code<>0 then
begin
Writeln('Error!!!');
Writeln('Format: ./task <Filename> <UID> <GID>');
ааааа Halt(1);
ааа end;
а val(Paramstr(3),GID,Code);
а if Code<>0 then
ааа begin
ааааа Writeln('Error!!!');
ааааа Writeln('Format: ./task <Filename> <UID> <GID>');
ааааа Halt(1);
ааа end;
а if not Chown(ParamStr(1),UID,GID)а then
а if LinuxError=Sys_EPERM then
аааа Writeln('You are not root!')
а else
аааа Writeln(' Chmod failed with exit code: ',LinuxError)
а else
аааа Writeln('Changed owner successfully!');
end.
LяЁрцэхэшх 13.44. TюёЄрт№Єх рэрыюу ъюьрэфv mkdir.
Program Tabs;
begin
{$I-}
if ParamCount=1 then
а begin
ааа MkDir(ParamStr(1));
ааа if IOResult <> 0 then Writeln('Cannot create directory')
ааа else Writeln('New directory created');
а end
else Writeln('Error');
end.
LяЁрцэхэшх 13.45. TюёЄрт№Єх рэрыюу ъюьрэфv chmod.
uses linux;
var
а f,ch:string;
а n,i:byte;
а d:integer;
begin
а if paramcount<>2 then
а begin
ааа writeln('Lёяюы№чєщЄх: ',paramstr(0),' яЁртр_фюёЄєяр Їрщы/ърЄрыюу');
ааа exit;
а end;
а f:=paramstr(2);
а ch:=paramstr(1);
а n:=length(ch);
а d:=0;
а for i:=1 to n do
ааа if not (ch[i] in ['0'..'7']) then
ааа begin
ааааа writeln('¦Ёртр фюёЄєяр фюыцэv сvЄ№ т тюё№ьхЁшўэюь ЇюЁьрЄх');
ааааа exit;
ааа end
ааа else
ааааа d:=d*8+byte(ch[i])-byte('0');
а if not chmod(f,d) then
ааа writeln('+°шсър єёЄрэютъш яЁрт фюёЄєяр ',ch,' фы ',f);
end.
LяЁрцэхэшх 13.46. TюёЄрт№Єх рэрыюу ъюьрэфv chown.
uses linux,strings,sysutils,crt;
type
а plong=^longint;
procedure perror(s:pchar);cdecl;external 'c';
function strchr(s:string;c:char):boolean;
var
а i:integer;
begin
а for i:=1 to length(s) do
ааа if s[i]=c then
ааа begin
ааааа strchr:=true;
ааааа exit;
ааа end;
а strchr:=false;
end;
procedure getall(w:string;name:string;var uid,gid:integer);
var ts,nam1,namb1,namb2:string;
ааа tx:text;
ааа d:integer;
ааа f:boolean;
begin
а assign(tx,w);
а reset(tx);
а f:=false;
а while not EOF (tx) and not f do
а begin
аааааа readln(tx,ts);
аааааа d:=pos(':',ts);
аааааа nam1:=copy(ts,1,d-1);
аааааа delete(ts,1,d+2);
аааааа d:=pos(':',ts);
аааааа namb1:=copy(ts,1,d-1);
аааааа delete(ts,1,d);
аааааа val(namb1,d);
аааааа uid:=d;
аааааа d:=pos(':',ts);
аааааа namb2:=copy(ts,1,d-1);
аааааа val(namb2,d);
аааааа gid:=d;
аааааа if nam1=name then
аааааааа f:=true;
а end;
а if not f then
а begin
ааа uid:=-1;
ааа gid:=-1;
а end;
а close(tx);ааааа
end;
var
а username,groupname,fname:string;
а uid,gid:integer;а
а posit,temp:integer;
begin
а if paramcount<>2 then
а begin
ааа writeln('Lёяюы№чєщЄх: ',paramstr(0),' тырфхыхЎ[:уЁєяяр] Їрщы');
ааа exit;аа
а end;
а username:=paramstr(1);
а fname:=paramstr(2);
а posit:=0;
а posit:=pos(':',username);
а if posit<>0 then
а begin
ааа groupname:=copy(username,posit+1,length(username)-posit);
ааа username[0]:=char(posit-1);
ааа getall('/etc/passwd',username,uid,gid);
ааа getall('/etc/group',groupname,gid,temp);
а end
а else
ааа getall('/etc/passwd',username,uid,gid);
а if (uid=-1) or (gid=-1) then
а begin
ааа writeln('=хтхЁэюх шь тырфхы№Ўр (уЁєяяv)');
ааа exit;
а end;
а if not chown(fname,uid,gid) then
ааа perror('+°шсър тvчютр chown');
end.
LяЁрцэхэшх 13.47. TючфрщЄх яЁюуЁрььє chmodr, ЁхъєЁёштэю шчьхэ ¦•є¦ яЁртр фюёЄєяр фы тёхї Їрщыют ърЄрыюур ш тыюцхээvї т эхую яюфърЄрыюуют. Lь ърЄрыюур ш яЁртр єърчvтр¦Єё т ъюьрэфэющ ёЄЁюъх.
uses linux,strings,sysutils,crt;
function gettype(mode:integer):char;
begin
а if S_ISREG(mode) then
ааа gettype:='-'
а else
ааа if S_ISDIR(mode) then
ааааа gettype:='d'
ааа else
ааааа if S_ISCHR(mode) then
ааааааа gettype:='c'
ааааа else
ааааааа if S_ISBLK(mode) then
ааааааааа gettype:='b'
ааааааа else
ааааааааа if S_ISFIFO(mode) then
ааааааааааа gettype:='p'
ааааааааа else
ааааааааааа gettype:='l';
end;
function obhod(prava:integer;name:pchar):boolean;
var
а flag:boolean;
а d:PDIR;
а el:pdirent;
а st:stat;
а res:integer;
а polniypath:array [0..2000] of char;
а ch:string;
а n,i:byte;
begin
а flag:=true;
а d:=opendir(name);
а if d=nil then
а begin
ааа writeln('+°шсър юЄъЁvЄш ърЄрыюур ',name);
ааа exit;
а end;
а el:=readdir(d);
а while el<>nil do
а begin
ааа polniypath:=name;
ааа if strcomp(name,'/')=0 then
ааааа strcat(polniypath,el^.name)
ааа else
ааа begin
ааааа if name[strlen(name)-1]<>'/' then
ааааааааа strcat(polniypath,'/');
ааааа strcat(polniypath,el^.name);
ааа end;
ааа
ааа if not fstat(pchar(polniypath),st) then
ааааааа writeln('+°шсър тvчютр stat фы ',polniypath)
ааа else
ааа begin
аааааааааа //if not (gettype(st.mode) = 'd') then
аааа аif not chmod(pchar(polniypath),prava) then
ааааааа writeln('+°шсър єёЄрэютъш яЁрт фюёЄєяр ',prava,' фы ',polniypath);
ааа end;
ааа el:=readdir(d);
а end;
а closedir(d);
а
а d:=opendir(name);
а el:=readdir(d);
а while el<>nil do
а begin
ааа polniypath:=name;
ааа if strcomp(name,'/')=0 then
ааааа strcat(polniypath,el^.name)
ааа else
ааа begin
ааааа if name[strlen(name)-1]<>'/' then
ааааааа strcat(polniypath,'/');
ааааа strcat(polniypath,el^.name);
ааа end;
ааа if not fstat(pchar(polniypath),st) then
аааа аwriteln('+°шсър тvчютр stat фы ',polniypath)
ааа else
ааа begin
ааааа if (gettype(st.mode)='d') and
аааааааа (strcomp(el^.name,'.')<>0) and
аааааааа (strcomp(el^.name,'..')<>0) then
ааааа begin
ааааааа writeln('¦хЁхїюф т ърЄрыюу ',polniypath);ааааа
аа аааааif not obhod(prava,polniypath) then
ааааааааа flag:=false;
ааааа end;
ааа end;
ааа el:=readdir(d);
а end;
а closedir(d);
а if not flag then
ааа writeln(' L ърЄрыюур ',name, ' эх єфрыюё№ шчьхэшЄ№ яЁртр фюёЄєяр ');
//а writeln('-ы ърЄрыюур ',name, ' яюыєўхэ ',flag);
а obhod:=flag;
end;
var
а name:array [0..2000] of char;
а prava,i:integer;
а ch:string;
begin
а if paramcount<>2 then
а begin
ааа writeln('Lёяюы№чєщЄх: ',paramstr(0),' яЁртр_фюёЄєяр Їрщы/ърЄрыюу');
ааа exit;
а end;
а name:=paramstr(2);
ааch:=paramstr(1);
а prava:=0;
а for i:=1 to length(ch) do
ааа if not (ch[i] in ['0'..'7']) then
ааа begin
ааааа writeln('¦Ёртр фюёЄєяр фюыцэv сvЄ№ т тюё№ьхЁшўэюь ЇюЁьрЄх');
ааааа exit;
ааа end
ааа else
ааааа prava:=prava*8+byte(ch[i])-byte('0');
а
а obhod(prava,name);
end.
LяЁрцэхэшх 13.48. =ряш°шЄх яЁюуЁрььє, ёютьх•р¦•р ъюьрэфv mv ш cp (т чртшёшьюёЄш юЄ ётюхую эрчтрэш ).
uses linux,sysutils;
var
а b:byte;
а s:string;
а f1,f2:file of byte;
begin
а s:=paramstr(0);
а delete(s,1,length(s)-2);
а if s='mv' then
а begin
ааа if paramcount<2 then
ааа begin
ааааа writeln('Error: wrong arguments');
ааааа writeln('ттхфшЄх шь Їрщыр, ъюЄюЁvщ їюЄшЄх яхЁхшьхэютрЄ№ ш эютюх шь Їрщыр');
ааааа halt(1);
ааа end;
ааа Assign(F1,paramstr(1));
ааа Assign(F2,paramstr(2));
ааа if not frename(paramstr(1),paramstr(2)) then
ааа begin
ааааа writeln('эхтючьюцэю яхЁхшьхэютрЄ№ ');
ааааа halt(1);
ааа end;
а end
а else
ааа if s='cp' then
ааа begin
ааааа if paramcount<2 then
ааааа begin
ааааааа writeln('Error: wrong arguments');
ааааааа writeln('format: cp <fileinp> <fileout>');
ааааааа Halt(1);
ааааа end;
ааааа Assign(f1,paramstr(1));
ааааа Reset(f1);
ааааа Assign(f2,paramstr(2));
ааааа Rewrite(f2);
ааааа while not eof(f1)do
ааааа begin
ааааааа read(f1,b);
ааааааа write(f2,b);
ааааа end;
ааааа close(f1);
ааааа close(f2);
ааа end
ааа else
ааааа writeln('¦хЁхшьхэєщЄх яЁюуЁрььє т mv / cp');
end.
LяЁрцэхэшх 13.49. TюёЄрт№Єх рэрыюу ъюьрэфv sync.
procedure sync;cdecl; external 'c';
begin
а sync;
end.
LяЁрцэхэшх 13.50. TючфрщЄх яЁюуЁрььє, тvтюф •є¦ ёюфхЁцшьюх ёшьтюышўхёъющ ёёvыъш, р чрЄхь Ц Ўхыхтюую Їрщыр, эр ъюЄюЁvщ юэр єърчvтрхЄ.
uses linux;
var
а name,temp:array [0..1023] of char;
а kol,fd:integer;
begin
а if paramcount<>1 then
а begin
ааа writeln('Lёяюы№чєщЄх: ',paramstr(0),' шь _ёёvыъш');
ааа exit;
а end;
а temp:=paramstr(1);
а kol:=readlink(temp,name,1023);
а if kol=-1 then
а begin
ааа writeln('+°шсър ўЄхэш ёёvыъш ',temp);
ааа exit;
а end;
а name[kol]:=#0;
а writeln('¦ю ёёvыъх ',paramstr(1), ' эрщфхэ Їрщы ',name);
а fd:=fdopen(name,Open_RDONLY);
а if fd=-1 then
а begin
ааа writeln('+°шсър юЄъЁvЄш ',name);
ааа exit;
а end;
а kol:=fdread(fd,name,1024);
а while kol>0 do
а begin
ааа fdwrite(1,name,kol);
ааа kol:=fdread(fd,name,1024);
а end;
а fdclose(fd);
end.
Управление процессами
Упражнение 13.51. Создайте простейший командный интерпретатор.
uses dos;
var
cmd:string;
begin
while true do
begin
write('> ');
readln(cmd);
if cmd='exit' then
break
else
begin
cmd:='-c '+cmd;
writeln('Введена команда ',cmd);
exec('/bin/sh',cmd);
end;
end;
end.
Упражнение 13.52. Создайте программу, выводящую установленные для процесса ограничения.
uses linux;
const
_SC_ARG_MAX=1;
_SC_CHILD_MAX=2;
_SC_CLK_TCK=3;
_SC_NGROUPS_MAX=4;
_SC_OPEN_MAX=5;
_SC_STREAM_MAX=6;
_SC_TZNAME_MAX=7;
_SC_JOB_CONTROL=8;
_SC_SAVED_IDS=9;
_SC_REALTIME_SIGNALS=10;
_SC_PRIORITY_SCHEDULING=11;
_SC_TIMERS=12;
_SC_ASYNCHRONOUS_IO=13;
_SC_PRIORITIZED_IO=14;
_SC_SYNCHRONIZED_IO=15;
_SC_FSYNC=16;
_SC_MAPPED_FILES=17;
_SC_MEMLOCK=18;
_SC_MEMLOCK_RANGE=19;
_SC_MEMORY_PROTECTION=20;
_SC_MESSAGE_PASSING=21;
_SC_SEMAPHORES=22;
_SC_SHARED_MEMORY_OBJECTS=23;
_SC_AIO_LISTIO_MAX=24;
_SC_AIO_MAX=25;
_SC_AIO_PRIO_DELTA_MAX=26;
_SC_DELAYTIMER_MAX=27;
_SC_MQ_OPEN_MAX=28;
_SC_MQ_PRIO_MAX=29;
_SC_VERSION=30;
_SC_PAGESIZE=31;
_SC_RTSIG_MAX=32;
_SC_SEM_NSEMS_MAX=33;
_SC_SEM_VALUE_MAX=34;
_SC_SIGQUEUE_MAX=35;
_SC_TIMER_MAX=36;
_SC_BC_BASE_MAX=37;
_SC_BC_DIM_MAX=38;
_SC_BC_SCALE_MAX=39;
_SC_BC_STRING_MAX=40;
_SC_COLL_WEIGHTS_MAX=41;
_SC_EQUIV_CLASS_MAX=42;
_SC_EXPR_NEST_MAX=43;
_SC_LINE_MAX=44;
_SC_RE_DUP_MAX=45;
_SC_CHARCLASS_NAME_MAX=46;
_SC_2_VERSION=47;
_SC_2_C_BIND=48;
_SC_2_C_DEV=49;
_SC_2_FORT_DEV=50;
_SC_2_FORT_RUN=51;
_SC_2_SW_DEV=52;
_SC_2_LOCALEDEF=53;
_SC_PII=54;
_SC_PII_XTI=55;
_SC_PII_SOCKET=56;
_SC_PII_INTERNET=57;
_SC_PII_OSI=58;
_SC_POLL=59;
_SC_SELECT=60;
_SC_UIO_MAXIOV=61;
_SC_IOV_MAX=62;
_SC_PII_INTERNET_STREAM=63;
ааа _SC_PII_INTERNET_DGRAM=64;
ааа _SC_PII_OSI_COTS=65;
ааа _SC_PII_OSI_CLTS=66;
ааа _SC_PII_OSI_M=67;
ааа _SC_T_IOV_MAX=68;
ааа _SC_THREADS=69;
ааа _SC_THREAD_SAFE_FUNCTIONS=70;
ааа _SC_GETGR_R_SIZE_MAX=71;
ааа _SC_GETPW_R_SIZE_MAX=72;
ааа _SC_LOGIN_NAME_MAX=73;
ааа _SC_TTY_NAME_MAX=74;
ааа _SC_THREAD_DESTRUCTOR_ITERATIONS=75;
ааа _SC_THREAD_KEYS_MAX=76;
ааа _SC_THREAD_STACK_MIN=77;
ааа _SC_THREAD_THREADS_MAX=78;
ааа _SC_THREAD_ATTR_STACKADDR=79;
ааа _SC_THREAD_ATTR_STACKSIZE=80;
ааа _SC_THREAD_PRIORITY_SCHEDULING=81;
ааа _SC_THREAD_PRIO_INHERIT=82;
ааа _SC_THREAD_PRIO_PROTECT=83;
ааа _SC_THREAD_PROCESS_SHARED=84;
ааа _SC_NPROCESSORS_CONF=85;
ааа _SC_NPROCESSORS_ONLN=86;
ааа _SC_PHYS_PAGES=87;
ааа _SC_AVPHYS_PAGES=88;
ааа _SC_ATEXIT_MAX=89;
ааа _SC_PASS_MAX=90;
ааа _SC_XOPEN_VERSION=91;
ааа _SC_XOPEN_XCU_VERSION=92;
ааа _SC_XOPEN_UNIX=93;
ааа _SC_XOPEN_CRYPT=94;
ааа _SC_XOPEN_ENH_I18N=95;
ааа _SC_XOPEN_SHM=96;
ааа _SC_2_CHAR_TERM=97;
ааа _SC_2_C_VERSION=98;
ааа _SC_2_UPE=99;
ааа _SC_XOPEN_XPG2=100;
ааа _SC_XOPEN_XPG3=101;
ааа _SC_XOPEN_XPG4=102;
ааа _SC_CHAR_BIT=103;
ааа _SC_CHAR_MAX=104;
ааа _SC_CHAR_MIN=105;
ааа _SC_INT_MAX=106;
ааа _SC_INT_MIN=107;
ааа _SC_LONG_BIT=108;
ааа _SC_WORD_BIT=109;
ааа _SC_MB_LEN_MAX=110;
ааа _SC_NZERO=111;
ааа _SC_SSIZE_MAX=112;
ааа _SC_SCHAR_MAX=113;
ааа _SC_SCHAR_MIN=114;
ааа _SC_SHRT_MAX=115;
ааа _SC_SHRT_MIN=116;
ааа _SC_UCHAR_MAX=117;
ааа _SC_UINT_MAX=118;
ааа _SC_ULONG_MAX=119;
ааа _SC_USHRT_MAX=120;
ааа _SC_NL_ARGMAX=121;
ааа _SC_NL_LANGMAX=122;
ааа _SC_NL_MSGMAX=123;
ааа _SC_NL_NMAX=124;
ааа _SC_NL_SETMAX=125;
ааа _SC_NL_TEXTMAX=126;
ааа _SC_XBS5_ILP32_OFF32=127;
ааа _SC_XBS5_ILP32_OFFBIG=128;
ааа _SC_XBS5_LP64_OFF64=129;
ааа _SC_XBS5_LPBIG_OFFBIG=130;
ааа _SC_XOPEN_LEGACY=131;
ааа _SC_XOPEN_REALTIME=132;
ааа _SC_XOPEN_REALTIME_THREADS=133;
ааа _SC_ADVISORY_INFO=134;
ааа _SC_BARRIERS=135;
ааа _SC_BASE=136;
ааа _SC_C_LANG_SUPPORT=137;
ааа _SC_C_LANG_SUPPORT_R=138;
ааа _SC_CLOCK_SELECTION=139;
аа а_SC_CPUTIME=140;
ааа _SC_THREAD_CPUTIME=141;
ааа _SC_DEVICE_IO=142;
ааа _SC_DEVICE_SPECIFIC=143;
ааа _SC_DEVICE_SPECIFIC_R=144;
ааа _SC_FD_MGMT=145;
ааа _SC_FIFO=146;
ааа _SC_PIPE=147;
ааа _SC_FILE_ATTRIBUTES=148;
ааа _SC_FILE_LOCKING=149;
ааа _SC_FILE_SYSTEM=150;
ааа _SC_MONOTONIC_CLOCK=151;
ааа _SC_MULTI_PROCESS=152;
ааа _SC_SINGLE_PROCESS=153;
ааа _SC_NETWORKING=154;
ааа _SC_READER_WRITER_LOCKS=155;
ааа _SC_SPIN_LOCKS=156;
ааа _SC_REGEXP=157;
ааа _SC_REGEX_VERSION=158;
ааа _SC_SHELL=159;
ааа _SC_SIGNALS=160;
ааа _SC_SPAWN=161;
ааа _SC_SPORADIC_SERVER=162;
ааа _SC_THREAD_SPORADIC_SERVER=163;
ааа _SC_SYSTEM_DATABASE=164;
ааа _SC_SYSTEM_DATABASE_R=165;
ааа _SC_TIMEOUTS=166;
ааа _SC_TYPED_MEMORY_OBJECTS=167;
ааа _SC_USER_GROUPS=168;
ааа _SC_USER_GROUPS_R=169;
ааа _SC_2_PBS=170;
ааа _SC_2_PBS_ACCOUNTING=171;
ааа _SC_2_PBS_LOCATE=172;
ааа _SC_2_PBS_MESSAGE=173;
ааа _SC_2_PBS_TRACK=174;
ааа _SC_SYMLOOP_MAX=175;
ааа _SC_STREAMS=176;
ааа _SC_2_PBS_CHECKPOINT=177;
ааа _SC_V6_ILP32_OFF32=178;
ааа _SC_V6_ILP32_OFFBIG=179;
ааа _SC_V6_LP64_OFF64=180;
ааа _SC_V6_LPBIG_OFFBIG=181;
ааа _SC_HOST_NAME_MAX=182;
ааа _SC_TRACE=183;
ааа _SC_TRACE_EVENT_FILTER=184;
ааа _SC_TRACE_INHERIT=185;
ааа _SC_TRACE_LOG=186;
type struct=record
а name:integer;
а value:string;
end;
function sysconf(name:integer):longint;cdecl;external 'c';
const
а count=20;
а mas:array [1..count] of struct=(
а (name:_SC_ARG_MAX;value:' ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_CHILD_MAX;value:'ьръёшьры№эюх ъюышўхёЄтю яЁюЎхёёют эр юфэюую яюы№чютрЄхы '),
а (name: _SC_CLK_TCK;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_NGROUPS_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а
а (name:_SC_OPEN_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_STREAM_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_TZNAME_MAX;value:' ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_JOB_CONTROL;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а
а (name:_SC_ARG_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_SAVED_IDS;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_REALTIME_SIGNALS;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_PRIORITY_SCHEDULING;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а
а (name:_SC_TIMERS;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_ASYNCHRONOUS_IO;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_PRIORITIZED_IO;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_SYNCHRONIZED_IO;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а
а (name:_SC_FSYNC;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_MAPPED_FILES;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_MEMLOCK;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_MEMLOCK_RANGE;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec')
а (name:_SC_MEMORY_PROTECTION;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_CHILD_MAX;value:'ьръёшьры№эюх ъюышўхёЄтю яЁюЎхёёют эр юфэюую яюы№чютрЄхы '),
а (name:_SC_MESSAGE_PASSING;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_SEMAPHORES;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а
а (name:_SC_SHARED_MEMORY_OBJECTS;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_AIO_LISTIO_MAX;value:'ьръёшьры№эюх ъюышўхёЄтю яЁюЎхёёют эр юфэюую яюы№чютрЄхы '),
а (name:_SC_AIO_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_AIO_PRIO_DELTA_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а
а (name:_SC_DELAYTIMER_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_MQ_OPEN_MAX;value:' ьръёшьры№эюх ъюышўхёЄтю яЁюЎхёёют эр юфэюую яюы№чютрЄхы '),
а (name:_SC_MQ_PRIO_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_VERSION;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а
а (name:_SC_PAGESIZE;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_RTSIG_MAX;value:'ьръёшьры№эюх ъюышўхёЄтю яЁюЎхёёют эр юфэюую яюы№чютрЄхы '),
а (name:_SC_SEM_NSEMS_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_SEM_VALUE_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а
а (name:_SC_SIGQUEUE_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_TIMER_MAX;value:'ьръёшьры№эюх ъюышўхёЄтю яЁюЎхёёют эр юфэюую яюы№чютрЄхы '),
а (name:_SC_BC_BASE_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_BC_DIM_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а
а (name:_SC_BC_SCALE_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_BC_STRING_MAX;value:'ьръёшьры№эюх ъюышўхёЄтю яЁюЎхёёют эр юфэюую яюы№чютрЄхы '),
а (name:_SC_COLL_WEIGHTS_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_EQUIV_CLASS_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а
а (name:_SC_EXPR_NEST_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_LINE_MAX;value:'ьръёшьры№эюх ъюышўхёЄтю яЁюЎхёёют эр юфэюую яюы№чютрЄхы '),
а (name:_SC_RE_DUP_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_CHARCLASS_NAME_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а
а (name:_SC_2_VERSION;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_2_C_BIND;value:'ьръёшьры№эюх ъюышўхёЄтю яЁюЎхёёют эр юфэюую яюы№чютрЄхы '),
а (name:_SC_2_C_DEV;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_2_FORT_DEV;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а
а (name:_SC_2_FORT_RUN;value:' ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_2_SW_DEV;value:'ьръёшьры№эюх ъюышўхёЄтю яЁюЎхёёют эр юфэюую яюы№чютрЄхы '),
а (name:_SC_2_LOCALEDEF;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_PII;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а
а (name:_SC_PII_XTI;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_PII_SOCKET;value:'ьръёшьры№эюх ъюышўхёЄтю яЁюЎхёёют эр юфэюую яюы№чютрЄхы '),
а (name:_SC_PII_INTERNET;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_PII_OSI;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а
а (name:_SC_POLL;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_SELECT;value:'ьръёшьры№эюх ъюышўхёЄтю яЁюЎхёёют эр юфэюую яюы№чютрЄхы '),
а (name:_SC_UIO_MAXIOV;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_IOV_MAX;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а
а (name:_SC_PII_INTERNET_STREAM;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_PII_INTERNET_DGRAM;value:'ьръёшьры№эюх ъюышўхёЄтю яЁюЎхёёют эр юфэюую яюы№чютрЄхы '),
а (name:_SC_PII_OSI_COTS;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_PII_OSI_CLTS;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а
а (name:_SC_PII_OSI_M;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:_SC_T_IOV_MAX;value:'ьръёшьры№эюх ъюышўхёЄтю яЁюЎхёёют эр юфэюую яюы№чютрЄхы '),
а (name:_SC_THREADS;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а
а (name:;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:;value:'ьръёшьры№эюх ъюышўхёЄтю яЁюЎхёёют эр юфэюую яюы№чютрЄхы '),
а (name:;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а (name:;value:'ьръёшьры№эр фышэр рЁуєьхэЄют ЇєэъЎшщ ёхьхщёЄтр exec'),
а
а);
var
а i:integer;
begin
а for i:=1 to count do
ааа writeln(mas[i].value,' = ',sysconf(mas[i].name));
end.
LяЁрцэхэшх 13.53. =ряш°шЄх яЁюуЁрььє, яхўрЄр¦•є¦ ётюш ярЁрьхЄЁv ш чэрўхэшх яхЁтvї фхё Єш яхЁхьхээvї юъЁєцхэш .
program Parameters;
uses Dos,Crt;
var
а i:Byte;
begin
а ClrScr;
а if ParamCount>1 then
аа begin
ааа Writeln('Owner parameters:');
ааа For i:=1 to ParamCount do
ааааа Writeln(Paramstr(i));
аа end
а else Writeln('No owner parameters.');
а Writeln('System parameters:');
а if envcount<10 then
ааа for i := 1 to EnvCount do
ааааа Writeln(EnvStr(i))
а else
ааа for i := 1 to 10 do
ааааа Writeln(EnvStr(i));
end.
LяЁрцэхэшх 13.54. =ряш°шЄх яЁюуЁрььє, єчэ𦕺¦ є ёшёЄхьv ш ЁрёяхўрЄvт𦕺¦: эюьхЁ яЁюЎхёёр, эюьхЁ ш шь ётюхую тырфхы№Ўр, эюьхЁ уЁєяяv, эрчтрэшх ш Єшя ЄхЁьшэрыр, эр ъюЄюЁюь юэр ЁрсюЄрхЄ.
uses linux;
function getnamebyuid(uid:longint):string;
var
а f:text;
а s,name:string;
а n,res:integer;
begin
а assign(f,'/etc/passwd');
а reset(f);
а while not eof(f) do
а begin
ааа readln(f,s);
ааа n:=pos(':',s);
ааа name:=copy(s,1,n-1);
ааа delete(s,1,n+2);
ааа n:=pos(':',s);
ааа delete(s,n,length(s)-n+1);
ааа val(s,res);
ааа if uid=res then
ааа begin
ааааа getnamebyuid:=name;
аа аааexit;
ааа end;
а end;
а getnamebyuid:='';
end;
var
а pid,uid,gid:longint;
а name,term:string;
а t:termios;
begin
а pid:=getpid;
а uid:=getuid;
а gid:=getgid;
а name:=getnamebyuid(uid);
а term:=getenv('TERM');
а tcgetattr(0,t);
а writeln('=юьхЁ яЁюЎхёёр - ',pid);
а writeln('¦ЁюЎхёё яЁшэрфыхцшЄ яюы№чютрЄхы¦ ё эюьхЁюь ',uid, ' яю шьхэш ',name);
а writeln('¦ЁюЎхёё тїюфшЄ т уЁєяяє ',gid);
а write('¦ЁюЎхёё тvяюыэ хЄё эр ');
а if (t.c_lflag and ICANON)=0 then
ааа write('эх');
а writeln('ърэюэшўхёъюь ЄхЁьшэрых ',term);
end.
LяЁрцэхэшх 13.55. =ряш°шЄх яЁюуЁрььє, тvсюЁюўэю тvтюф •є¦ чэрўхэшх яхЁхьхээvї юъЁєцхэш ш єёЄрэртышт𦕺¦ эютvх чэрўхэш яю цхырэш¦ яюы№чютрЄхы .
uses crt,dos;
var i:integer;
ааа s,st:array[0..200]of char;
ааа
ааа c:char;
function setenv(s1,s2:pchar;ower:integer):integer;cdecl;external 'c';
begin
а for i:=1 to envcount do
ааа writeln(envstr(i),'=',getenv(envstr(i)));а
а writeln(' TтхфшЄх яхЁхьхээє¦ юъЁєцхэш , эю Єюы№ъю сюы№°шьш сєътрьш.');
а readln(s);
а if getenv(strpas(s))<>'' then
ааа writeln(getenv(strpas(s)))
а else
а begin
ааа writeln('¦хЁхьхээющ юъЁєцхэш ',s,' эх ёє•хёЄтєхЄ');
ааа halt;
а end;
а writeln('-юЄшЄх шчьхэшЄ№ чэрўхэшх ',s,' эрцьшЄха "y" ');
а c:=readkey;
а if upcase(c)='Y' then
а begin
ааа writeln('TтхфшЄх чэрўхэшх яхЁхьхээющ');
ааа readln(st);
ааа if setenv(s,st,1)<0 then
ааааа writeln('¦эрўхэшх яхЁхьхээющ эх яюьхэ ыюё№')
ааа else
ааааа writeln(setenv(s,st,1),' ',getenv(strpas(s)));
а end;
end.
LяЁрцэхэшх 13.56. =ряш°шЄх ЇєэъЎш¦ sleep(n), чрфхЁцшт𦕺¦ тvяюыэхэшх яЁюуЁрььv эр n ёхъєэф. Tюёяюы№чєщЄхё№ ёшёЄхьэvь тvчютюь alarm(n) (сєфшы№эшъ) ш тvчютюь pause(), ъюЄюЁvщ чрфхЁцштрхЄ яЁюуЁрььє фю яюыєўхэш ы¦сюую ёшуэрыр. ¦ЁхфєёьюЄЁшЄх ЁхёЄрЁЄ яЁш яюыєўхэшш тю тЁхь юцшфрэш фЁєуюую ёшуэрыр, эхцхыш SIGALRM. TюїЁрэ щЄх чрърч alarm, ёфхырээvщ фю тvчютр sleep (alarm тvфрхЄ ўшёыю ёхъєэф, юёЄрт°ххё фю чртхЁ°хэш яЁхфvфє•хую чрърчр).
uses dos,crt,sysutils,linux;
а
procedure handler(sig:integer);cdecl;
begin
end;
а
procedure sleep(count:integer);
var
а oldhandler,newhandler:sigactionrec;
begin
ааааа
а writeln('count=',count);
а newhandler.handler.sh:=@handler;
а (*newhandler.sa_mask:=$ffffffff;*)
а newhandler.sa_mask:=0;
а sigaction(SIGALRM,@newhandler,@oldhandler);
а alarm(count);
а pause;
а sigaction(SIGALRM,@oldhandler,nil);
end;
begin
repeat
sleep(3);
writeln('¦ЁштхЄ');
until keypressed;
end.
. Управление терминалом
Упражнение 13.61. Напишите функции включения и выключения режима эхо-отображения набираемых на клавиатуре символов.
uses linux,crt;
const ECHO=8;
var
t:termios;
s,ms:string;
c:char;
begin
writeln('Для включения эхо введите on, для выключения эхо введите off');
readln(s);
tcgetattr(0,t);
writeln(t.c_lflag);
if s='on' then
begin
t.c_lflag:=t.c_lflag or ECHO;
end;
if s='off' then
begin
t.c_lflag:=t.c_lflag and not ECHO;
end;
tcsetattr(0,tcsanow,t);
readln(ms);
writeln(ms);
end.
Управляющий терминал
При обычных обстоятельствах терминал, связанный с процессом при помощи его стандартных дескрипторов файлов, является управляющим терминалом (control terminal) этого процесса и его сеанса. Управляющий терминал является важным атрибутом процесса, который определяет обработку генерируемых с клавиатуры прерываний. Например, если пользователь нажимает текущую клавишу прерывания, то все процессы, которые считают терминал своим управляющим терминалом, получат сигнал SIGINT. Управляющие терминалы, как и другие атрибуты процесса, наследуются при вызове fork. (Более конкретно, терминал становится управляющим терминалом для сеанса, когда его открывает лидер сеанса, при условии, что терминал еще не связан с сеансом и лидер сеанса еще не имеет управляющего терминала. Вследствие этого процесс может разорвать свою связь с управляющим терминалом, изменив свой сеанс при помощи вызова setsid. Этот аспект был рассмотрен в главе 5, хотя, возможно, там это было несколько преждевременно. Теперь же следует получить понимание того, как процесс init инициализирует систему при старте.)
Если процесс должен получить доступ к своему управляющему терминалу независимо от состояния его стандартных дескрипторов файлов, то можно использовать имя файла
/dev/tty
которое всегда интерпретируется как определяющее текущий управляющий терминал процесса. Следовательно, терминал, обозначаемый в действительности этим файлом, различается для разных процессов.
Установка блокировки при помощи вызова fcntl
Следующий пример показывает, как можно использовать вызов fcntl для установления блокировки записи.
uses linux,stdio;
.
.
.
var
my_lock:flockrec;
my_lock.l_type := F_WRLCK;
my_lock.l_whence := SEEK_CUR;
my_lock.l_start := 0;
my_lock.l_len := 512;
fcntl (fd, F_SETLKW, longint(@my_lock));
При этом будут заблокированы 512 байт, начиная с текущего положения указателя чтения-записи. Заблокированный участок теперь считается «зарезервированным» для исключительного использования процессом. Информация о блокировке помещается в свободную ячейку системной таблицы блокировок.
Если весь участок файла или какая-то его часть уже были заблокированы другим процессом, то вызывающий процесс будет приостановлен до тех пор, пока не будет доступен весь участок. Приостановка работы процесса может быть прервана при помощи сигнала; в частности, для задания времени ожидания может быть использован вызов alarm. Если ожидание не будет прервано, и, в конце концов, участок файла освободится, то процесс заблокирует его. Если происходит ошибка, например, если вызову fcntl передается неверный дескриптор файла или переполнится системная таблица блокировок, то будет возвращено значение -1.
Следующий пример – программа lockit открывает файл с именем locktest (который должен существовать) и блокирует его первые десять байт при помощи вызова fcntl. Затем она порождает дочерний процесс, пытающийся заблокировать первые пять байт файла; родительский процесс в это время делает паузу на пять секунд, а затем завершает работу. В этот момент система автоматически снимает блокировку, установленную родительским процессом.
(* Программа lockit - блокировка при помощи вызова fcntl *)
uses linux,stdio;
var
fd:integer;
my_lock:flockrec;
begin
(* Установка параметров блокировки записи *)
my_lock.l_type := F_WRLCK;
my_lock.l_whence := SEEK_SET;
my_lock.l_start := 0;
my_lock.l_len := 10;
(* Открыть файл *)
fd := fdopen ('locktest', Open_RDWR);
(* Заблокировать первые десять байт *)
Включение приема TCP-соединений
После выполнения связывания с адресом и перед тем, как какой-либо клиент сможет подключиться к созданному сокету, сервер должен включить прием соединений. Для этого служит вызов listen.
Владелец файла и права доступа
Файл характеризуется не только содержащимися в нем данными: существует еще ряд других простых атрибутов, связанных с каждым файлом UNIX. Например, для каждого файла задан определенный пользователь – владелец файла
(file owner). Владелец файла обладает определенными правами, в том числе возможностью изменять права доступа
(permissions) к файлу. Как показано в главе 3, права доступа определяют, кто из пользователей может читать или записывать информацию в файл либо запускать его на выполнение, если файл является программой.
Время
В ОС UNIX существует группа процедур для установки и получения системного времени. Время в системе измеряется как число секунд, прошедших с 00:00:00 по Гринвичу с 1 января 1970 г., и размер переменной для хранения этого числа должен быть не меньше формата longint.
Основным вызовом этой группы является системный вызов GetTimeOfDay, который возвращает текущее время в стандартном формате времени системы UNIX.
. Вспомогательные процедуры
Этот раздел будет посвящен краткому описанию различных дополнительных процедур стандартной библиотеки ввода/вывода. Более подробная информация содержится в справочном руководстве системы.
Второй пример: процедура find_entry
Процедура find_entry будет искать в каталоге следующий файл (или подкаталог), заканчивающийся определенным суффиксом. Она имеет три параметра: имя каталога, в котором будет выполняться поиск, строка суффикса и флаг, определяющий, нужно ли продолжать дальнейший поиск после того, как искомый элемент будет найден.
Процедура find_entry использует процедуру проверки совпадения строк match с целью определения, заканчивается ли файл заданным суффиксом. Процедура match, в свою очередь, вызывает две процедуры из стандартной библиотеки C системы UNIX: функцию strlen, возвращающую длину строки в символах, и функцию strcmp, которая сравнивает две строки, возвращая нулевое значение в случае их совпадения.
uses linux,strings;
function match(s1, s2: pchar):boolean;forward;
function find_entry(dirname:pchar;suffix:pchar;cont:integer):pchar;
const
dp:pdir=nil;
var
d:pdirent;
begin
if (dp = nil) or (cont = 0) then
begin
if dp <> nil then
closedir (dp);
dp:=opendir(dirname);
if dp = nil then
begin
find_entry:=nil;
exit;
end;
end;
d := readdir (dp);
while d <> nil do
begin
if d^.ino = 0 then
continue;
if match (d^.name, suffix) then
begin
find_entry:=d^.name;
exit;
end;
d := readdir (dp);
end;
closedir (dp);
dp := nil;
find_entry:=nil;
end;
function match(s1, s2: pchar):boolean;
var
diff:integer;
begin
diff := strlen (s1) - strlen (s2);
if strlen (s1) > strlen (s2) then
match:=(strcomp (@s1[diff], s2) = 0)
else
match:=false;
end;
Упражнение 4.1. Измените функцию my_double_ls из предыдущего примера так, чтобы она имела второй параметр – целочисленную переменную skip. Если значение skip равно нулю, то функция my_double_ls должна выполняться так же, как и раньше. Если значение переменной skip равно 1, функция my_double_ls должна пропускать все имена файлов, которые начинаются сточки (.).
Упражнение 4.2. В предыдущей главе мы познакомились с использованием системного вызова fstat для получения информации о файле. Структура tstat, возвращаемая вызовом fstat, содержит поле mode, режим доступа к файлу. Режим доступа к файлу образуется при помощи выполнения побитовой операции ИЛИ значения кода доступа с константами, определяющими, является ли этот файл обычным файлом, каталогом, специальным файлом, или механизмом межпроцессного взаимодействия, таким как именованный канал. Наилучший способ проверить, является ли файл каталогом – использовать макрос S_ISDIR:
(* Переменная buf получена в результате вызова fstat *)
if S_ISDIR(buf.mode) then
writeln('Это каталог')
else
writeln('Это не каталог');
Измените процедуру my_double_ls так, чтобы она вызывала fstat для каждого найденного файла и выводила звездочку после каждого имени каталога.
В дополнение к упражнению приведем пример, демонстрирующий остальные S_-функции:
Uses linux;
Var Info : Stat;
begin
if LStat (paramstr(1),info) then
begin
if S_ISLNK(info.mode) then
Writeln ('File is a link');
if S_ISREG(info.mode) then
Writeln ('File is a regular file');
if S_ISDIR(info.mode) then
Writeln ('File is a directory');
if S_ISCHR(info.mode) then
Writeln ('File is a character device file');
if S_ISBLK(info.mode) then
Writeln ('File is a block device file');
if S_ISFIFO(info.mode) then
Writeln ('File is a named pipe (FIFO)');
if S_ISSOCK(info.mode) then
Writeln ('File is a socket');
end;
end.
в главе 2, ОС UNIX
Как уже упоминалось в главе 2, ОС UNIX обеспечивает набор стандартных кодов ошибок и сообщений, описывающих ошибки. Ошибки генерируются при неудачном завершении системных вызовов; С каждым типом ошибки системного вызова связан номер ошибки, мнемонический код (константа, имеющая значение номера ошибки) и строка сообщения. Эти объекты можно использовать в программе, если включить в нее модуль linux.
В случае возникновения ошибки системный вызов устанавливает новое значение переменной linuxerror. Почти всегда системный вызов сообщает об ошибке, возвращая вызывающему процессу в качестве результата величину –1. После этого можно проверить соответствие значения переменной linuxerror мнемоническим кодам, определенным в файле linux, например:
uses linux;
var
pid:longint;
.
.
.
pid := fork;
if pid = -1 then
begin
if linuxerror = Sys_EAGAIN then
writeln('Превышен предел числа процессов')
else
writeln('Другая ошибка');
end;
Внешний массив sys_errlist является таблицей сообщений об ошибках, выводимых процедурой perror. Переменная linuxerror может использоваться в качестве индекса этого массива, если нужно вручную получить системное сообщение об ошибке. Внешняя целочисленная переменная sys_nerr задает текущий размер таблицы sys_errlist. Для получения индекса в массиве следует всегда проверять, что значение переменной linuxerror меньше значения sys_nerr, так как новые номера ошибок могут вводиться с опережением соответствующего пополнения таблицы системных сообщений.
Введение
В этой главе будут рассмотрены основные примитивы для работы с файлами, предоставляемые системой UNIX. Эти примитивы состоят из небольшого набора системных вызовов, которые обеспечивают прямой доступ к средствам ввода/вывода, обеспечиваемым ядром UNIX. Они образуют строительные блоки для всего ввода/вывода в системе UNIX, и многие другие механизмы доступа к файлам в конечном счете основаны на них. Названия этих примитивов приведены в табл. 2.1. Дублирование функций, выполняемых различными вызовами, соответствует эволюции UNIX в течение последнего десятилетия.
Типичная программа UNIX вызывает для инициализации файла вызов fdopen (или fdcreat), а затем использует вызовы fdread, fdwrite или fdseek для работы с данными в файле. Если файл больше не нужен программе, она может вызвать fdclose, показывая, что работа с файлом завершена. Наконец, если пользователю больше не нужен файл, его можно удалить из системы при помощи вызова unlink.
Следующая программа, читающая начальный фрагмент некоторого файла, более ясно демонстрирует эту общую схему. Так как это всего лишь вступительный пример, мы опустили некоторые необходимые детали, в частности обработку ошибок. Заметим, что такая практика совершенно недопустима в реальных программах.
Таблица 2.1. Примитивы UNIX
Имя | Функция | ||
fdopen | Открывает файл для чтения или записи либо создает пустой файл | ||
fdcreat | Создает пустой файл | ||
fdclose | Закрывает открытый файл | ||
fdread | Считывает информацию из файла | ||
fdwrite | Записывает информацию в файл | ||
fdseek | Перемещается в заданную позицию в файле | ||
unlink | Удаляет файл | ||
fcntl | Управляет связанными с файлом атрибутами |
(* элементарный пример *)
uses linux;
var
fd:integer;
nread:longint;
buf:array [0..1024-1] of char;
begin
(* Открыть файл 'data' для чтения *)
fd := fdopen ('data', Open_RDONLY);
(* Прочитать данные *)
nread := fdread (fd, buf, 1024);
(* Закрыть файл *)
fdclose (fd);
end.
Первый системный вызов программа примера делает в строке
fd := fdopen ('data', Open_RDONLY);
Это вызов функции fdopen, он открывает файл data в текущем каталоге. Второй аргумент функции, Open_RDONLY, является целочисленной константой, определенной в модуле linux. Это значение указывает на то, что файл должен быть открыт в режиме только для чтения (read only). Другими словами, программа сможет только читать содержимое файла и не изменит файл, записав в него какие-либо данные.
Результат вызова fdopen крайне важен, в данном примере он помещается в переменную fd. Если вызов fdopen был успешным, то переменная fd
будет содержать так называемый дескриптор файла (file descriptor) – неотрицательное целое число, значение которого определяется системой. Оно определяет открытый файл при передаче его в качестве параметра другим примитивам доступа к файлам, таким как fdread, fdwrite, fdseek и fdclose. Если вызов fdopen завершается неудачей, то он возвращает значение -1 (большинство системных вызовов возвращает это значение в случае ошибки). В реальной программе нужно выполнять проверку возвращаемого значения и в случае возникновения ошибки предпринимать соответствующие действия.
После открытия файла программа делает системный вызов fdread:
nread := fdread (fd, buf, 1024);
Этот вызов требует считать 1024 символа из файла с идентификатором fd, если это возможно, и поместить их с символьный массив buf. Возвращаемое значение nread дает число считанных символов, которое в нормальной ситуации должно быть равно 1024, но может быть и меньше, если длина файла оказалась меньше 1024 байт. Так же, как и fdopen, вызов fdread возвращает в случае ошибки значение -1.
Переменная nread имеет тип longint, определенный в модуле linux.
Этот оператор демонстрирует еще один важный момент: примитивы доступа к файлам имеют дело с простыми линейными последовательностями символов или байтов. Вызов fdread, например, не будет выполнять никаких полезных преобразований типа перевода символьного представления целого числа в форму, используемую для внутреннего представления целых чисел. Не нужно путать системные вызовы fdread и fdwrite с операторами более высокого уровня в таких языках, как Fortran или Pascal. Системный вызов fdread типичен для философии, лежащей в основе интерфейса системных вызовов: он выполняет одну простую функцию и представляет собой строительный блок, с помощью которого могут быть реализованы другие возможности.
В конце примера файл закрывается:
fdclose(fd);
Этот вызов сообщает системе, что программа закончила работу с файлом, связанным с идентификатором fd. Легко увидеть, что вызов fdclose противоположен вызову fdopen. В действительности, так как программа на этом завершает работу, вызов fdclose не является необходимым, поскольку все открытые файлы автоматически закрываются при завершении процесса. Тем не менее обязательное использование вызова fdclose считается хорошей практикой.
Этот короткий пример должен дать представление о примитивах UNIX для доступа к файлам. Теперь каждый из этих примитивов будет рассмотрен более подробно.
Введение
В двух предыдущих главах внимание было сконцентрировано на основном компоненте файловой структуры UNIX – обычных файлах. В этой главе будут рассмотрены другие компоненты файловой структуры, а именно:
–
каталоги. Каталоги выступают в качестве хранилища имен файлов и, следовательно, позволяют пользователям группировать произвольные наборы файлов. Понятие каталогов должно быть знакомо большинству пользователей UNIX и многим «эмигрантам» из других операционных систем. Далее будет показано, что каталоги UNIX могут быть вложенными, это придает структуре файлов древовидную иерархическую форму;
– файловые системы. Файловые системы представляют собой набор каталогов и файлов и являются подразделами иерархического дерева каталогов и файлов, образующих общую файловую структуру UNIX. Файловые системы обычно соответствуют физическим разделам (partitions) дискового устройства или всему дисковому устройству. При решении большинства задач файловые системы остаются невидимыми для пользователя;
– специальные файлы. Концепция файла получила в системе UNIX дальнейшее развитие и включает в себя присоединенные к системе периферийные устройства. Эти периферийные устройства, такие как принтеры, дисковые накопители и даже системная память, представляются в файловой структуре именами файлов. Файл, представляющий устройство, называется специальным файлом
(special file). К устройствам можно получить доступ при помощи обычных системных вызовов доступа к файлам, описанных в главах 2 и 3 (например, вызовов fdopen, fdread и fdwrite). Каждый такой вызов задействует код драйвера устройства в ядре системы, отвечающий за управление заданным устройством. Тем не менее программе не нужно ничего знать об этом, так как система позволяет обращаться со специальными файлами почти как с обычными.
Введение
Часто требуется создавать программные системы, состоящие не из одной монолитной программы, а из нескольких сотрудничающих процессов. Для этого может быть множество причин: одиночная программа может оказаться слишком громоздкой и не поместиться в памяти; часть требуемых функций часто уже реализована в каких-либо существующих программах; задачу может быть удобнее решать при помощи процесса-сервера, взаимодействующего с произвольным числом процессов-клиентов; есть возможность использовать несколько процессоров и т.д.
К счастью, ОС UNIX имеет развитые механизмы межпроцессного взаимодействия. В этой и следующей главах мы обсудим три наиболее популярных средства: сигналы (signals), программные каналы (pipes) и именованные каналы (FIFO). Данные средства, наряду с более сложными средствами, которым будут посвящены главы 8 и 10, предоставляют разработчику программного обеспечения широкий выбор средств построения многопроцессных систем.
Эта глава будет посвящена изучению первого средства – сигналам. Рассмотрим пример запуска команды UNIX, выполнение которой, вероятно, займет много времени:
$ fpc verybigprog.pas
Позже становится ясным, что программа содержит ошибку, и ее компиляция не может завершиться успехом. Тогда, чтобы сэкономить время, следует прекратить выполнение команды нажатием специальной клавиши прерывания задания (interrupt key) терминала; обычно это клавиша Del
или клавиатурная комбинация Ctrl+C.
Выполнение программы прекратится, и программист вернется к приглашению ввода команды командного интерпретатора.
В действительности при этом происходит следующая последовательность событий: часть ядра, отвечающая за ввод с клавиатуры, распознает символ прерывания задания. Затем ядро посылает сигнал SIGINT всем процессам, для которых текущий терминал является управляющим терминалом. Среди этих процессов будет и экземпляр компилятора cc. Когда процесс компилятора ее получит этот сигнал, он выполнит связанное с сигналом SIGINT действие по умолчанию – завершит работу. Интересно отметить, что сигнал SIGINT посылается и процессу оболочки, тоже связанному с терминалом. Тем не менее процесс оболочки благоразумно игнорирует этот сигнал, поскольку он должен продолжать работу для интерпретации последующих команд. Как будет рассмотрено далее, пользовательские
программы также могут выбирать, нужно ли им перехватывать сигнал SIGINT, есть выполнять специальную процедуру реакции на поступление сигнала, и полагаться на действие по умолчанию для данного сигнала.
Сигналы также используются ядром для обработки определенных типов критических ошибок. Например, предположим, что файл программы поврежден и содержит недопустимые машинные инструкции. При выполнении этой программы процессом, ядро определит попытку выполнения недопустимой инструкции и пошлет процессу сигнал SIGILL (здесь ILL является сокращением от illegal, то есть недопустимый) для завершения его работы. Получившийся диалог может выглядеть примерно так:
$ badprog
illegal instruction - core dumped
Смысл термина core dumped (сброс образа памяти) будет объяснен ниже.
Сигналы могут посылаться не только от ядра к процессу, но и между процессами. Проще всего показать это на примере команды kill. Предположим, например, что программист запускает в фоновом режиме длительную команду
$ fpc verybigprog.pas &
[1] 1098
а затем решает завершить ее работу. Тогда, чтобы послать процессу сигнал SIGTERM, можно использовать команду kill. Так же, как и сигнал SIGINT, сигнал SIGTERM завершит процесс, если в процессе не переопределена стандартная реакция на этот сигнал. В качестве аргумента команды kill должен быть задан идентификатор процесса:
$ kill 1098
Terminated
Сигналы обеспечивают простой метод программного прерывания работы процессов UNIX. Образно можно сравнить его с похлопыванием по плечу, отвлекающим от работы. Из-за своей природы сигналы обычно используются для обработки исключительных ситуаций, а не для обмена данными между процессами.
Процесс может выполнять три действия с сигналами, а именно:
–
изменять свою реакцию на поступление определенного сигнала (изменять обработку сигналов);
– блокировать сигналы (то есть откладывать их обработку) на время выполнения определенных критических участков кода;
– посылать сигналы другим процессам.
Каждое из этих действий будет рассмотрено далее в этой главе.
Введение
Используя средства из глав 6 и 7, можно осуществить основные взаимодействия между процессами. В этой главе рассматриваются усовершенствованные средства межпроцессного взаимодействия, которые позволят использовать более сложные методы программирования.
Первая и наиболее простая тема данной главы – блокировка записей (record locking), которая фактически является не формой прямого межпроцессного взаимодействия, а скорее – методом координирования работы процессов. Блокировка позволяет процессу временно резервировать часть файла для исключительного использования при решении некоторых сложных задач управления базами данных. Здесь стоит сделать предупреждение: спецификация XSI определяет блокировку записей как рекомендательную (advisory), означающую, что она не препятствует непосредственному выполнению операций файлового ввода/вывода, а вся ответственность за проверку установленных блокировок полностью ложится на процесс.[12]
Другие механизмы межпроцессного взаимодействия, обсуждаемые в этой главе, являются более редкими. В общем случае эти средства описываются как средства IPC (IPC facilities, где сокращение IPC означает inter-process communication – межпроцессное взаимодействие) и включены в одноименный модуль. Этот общий термин подчеркивает общность их применения и структуры, хотя существуют три определенных типа таких средств:
–
очереди сообщений (message passing). Они позволяют процессу посылать и принимать сообщения, под которыми понимается произвольная последовательность байтов или символов;
– семафоры (semaphores). По сравнению с очередями сообщений семафоры представляют собой низкоуровневый метод синхронизации процессов, малопригодный для передачи больших объемов данных. Их теория берет начало из работ Дейкстры (Е.W. Dijkstra, 1968);
– разделяемая память (shared memory). Это средство межпроцессного взаимодействия позволяет двум и более процессам совместно использовать данные, содержащиеся в определенных сегментах памяти. Естественно, обычно данные процесса являются недоступными для других процессов. Этот механизм обычно является самым быстрым механизмом межпроцессного взаимодействия.[13]
Введение
Когда пользователь взаимодействует с программой при помощи терминала, происходит намного больше действий, чем может показаться на первый взгляд. Например, если программа выводит строку на терминальное устройство, то она вначале обрабатывается в разделе ядра, которое будем называть драйвером терминала (terminal driver). В зависимости от значения определенных флагов состояния системы строка может передаваться буквально или как-то изменяться драйвером. Одно из обычных изменений заключается в замене символов line-feed (перевод строки) или newline (новая строка) на последовательность из двух символов carriage-return (возврат каретки) и newline. Это гарантирует, что каждая строка всегда будет начинаться с левого края экрана терминала или открытого окна.
Аналогично драйвер терминала обычно позволяет пользователю редактировать ошибки в строке ввода при помощи текущих символов
erase (стереть) и kill (уничтожить). Символ erase удаляет последний напечатанный символ, а символ kill – все символы до начала строки. Только после того, как вид строки устроит пользователя и он нажмет на клавишу Return (ввод), драйвер терминам передаст строку программе.
Но это еще не все. После того, как выводимая строка достигает терминала, аппаратура терминала может либо просто вывести ее на экран, либо интерпретировать ее как escape-последовательность (escape-sequence), посланную для управления экраном. В результате, например, может произойти не вывод сообщения, а очистка экрана.
На рис. 9.1 более ясно показаны различные компоненты связи между компьютером и терминалом.
![](image/index-image026.gif)
Рис. 9.1. Связь между процессом UNIX и терминалом
Эта связь включает четыре элемента:
– программы (А). Программа генерирует выходные последовательности волов и интерпретирует входные. Она может взаимодействовать с терминалом при помощи системных вызовов (fdread или fdwrite), стандартной библиотеки ввода/вывода или специального библиотечного пакета, разработанного для управления экраном. Разумеется, в конечном счете весь ввод/вывод будет осуществляться при помощи вызовов fdread и fdwrite, так как высокоуровневые библиотеки могут вызывать только эти основные примитивы;
– драйвер терминала (В). Основная функция драйвера терминала заключается в передаче данных от программы к периферийному устройству и наоборот. В самом ядре UNIX терминал обычно состоит из двух основных программных компонентов – драйвера устройства (device driver) и дисциплины линии связи (line discipline). Драйвер устройства является низкоуровневым программным обеспечением, написанным для связи с определенным аппаратным обеспечением, которое позволяет компьютеру взаимодействовать с терминалом. На самом деле чаще всего драйверы устройств нужны для того, чтобы работать с разными типами аппаратного обеспечения. Над этим нижним слоем надстроены средства, которые полагаются на то, что основные свойства, поддерживаемые драйвером устройства, являются общими независимо от аппаратного обеспечения. Кроме этой основной функции передачи данных, драйвер терминала будет также выполнять некоторую логическую обработку входных и выходных данных, преобразуя одну последовательность символов в другую. Это осуществляется дисциплиной линии связи. Она также может обеспечивать множество функций для помощи конечному пользователю, таких как редактирование строки ввода. Точная обработка и преобразование данных зависят от флагов состояния, которые хранятся в дисциплине линии связи для каждого порта терминала. Они могут устанавливаться при помощи группы системных вызовов, которая будут рассмотрена в следующих разделах;
– клавиатура и экран (С и D). Эти два элемента представляют сам терминал и подчеркивают его двойственную природу. Узел (С) означает клавиатуру терминала и служит источником ввода. Узел (D) представляет экран терминала и выступает в качестве назначения вывода. Программа может получить доступ к терминалу и как к устройству ввода, и как к устройству вывода при помощи общего имени терминала, и, в конечном счете, единственного дескриптора файла. Для того чтобы это было возможно, дисциплина лини связи имеет раздельные очереди ввода и вывода для каждого терминала. Эта схема показана на рис. 9.2.
![](image/index-image027.gif)
Рис. 9.2. Реализация терминала
До сих пор предполагалось, что подключенное к терминалу периферийное устройство является стандартным дисплеем. Вместе с тем периферийное устройство может быть принтером, плоттером, сетевым адаптером или даже другим компьютером. Тем не менее, независимо от природы периферийного устройства, оно может служить и в качестве источника, и в качестве назначения для входного и выходного потоков данных соответственно.
Эта глава будет в основном посвящена узлам (А) и (В) схемы. Другими словами, будет рассмотрено взаимодействие между программой и драйвером устройства на уровне системных вызовов. Не будем касаться совершенно отдельного вопроса работы с экраном, поскольку драйвер терминала не принимает участия в создании соответствующих escape-последовательностей, управляющих экраном.
Перед тем как продолжить дальше, следует сделать два предостережения. Во-первых, будут рассматриваться только «обычные» терминалы, а не графические, построенные на оконных системах Х Window System или MS Windows. Для них характерны свои проблемы, которых касаться не будем. Во-вторых, работа с терминалом в UNIX является областью, печально известной своей несовместимостью. Тем не менее спецификация XSI обеспечивает стандартный набор системных вызовов. Именно на них и сфокусируем внимание.
Введение
В предыдущих главах был изучен ряд механизмов межпроцессного взаимодействия системы UNIX. В настоящее время пользователями и разработчиками часто используются и сетевые среды, предоставляющие возможности технологи клиент/сервер. Такой подход позволяет совместно использовать данные, дисковое пространство, периферийные устройства, процессорное время и другие ресурсы. Работа в сетевой среде по технологии клиент/сервер предполагает взаимодействие процессов, находящихся на клиентских и серверных системах, разделенных средой передачи данных.
Исторически сетевые средства
UNIX развивались двумя путями. Разработчики Berkeley UNIX создали в начале 80-х годов известный и широко применяемый интерфейс сокетов, а разработчики System V выпустили в 1986 г. Transport Level Interface (интерфейс транспортного уровня, сокращенно
TLI). Раздел сетевого программирования в документации по стандарту Х/Open часто называют спецификацией XTI. Спецификация XTI включает и интерфейс сокетов, и интерфейс TLI. Основные понятия являются общими для обеих реализации, но интерфейс TLI использует намного больше структур данных, и его реализация гораздо сложнее, чем реализация интерфейса сокетов. Поэтому в этой главе будет рассмотрен хорошо известный и испытанный интерфейс сокетов. Они обеспечивают простой программный интерфейс, применимый как для связи процессов на одном компьютере, так и для связи процессов через сети. Целью сокетов является обеспечение средства межпроцессного взаимодействия для двунаправленного обмена сообщениями между двумя процессами независимо от того, находятся ли они на одном или на разных компьютерах.
Глава будет посвящена краткому ознакомлению с основными понятиями и средствами работы с сокетами. При необходимости продолжить их изучение следует обратиться к более подробному руководству (например, книге «Advanced Programming in the UNIX Environment» У.Р. Стивенса) или к специалисту по сетевому программированию.
Кроме этого, необходимо отметить, что во время компоновки программ может понадобиться подключение сетевых библиотек – за
рекомендациями обратитесь к справочному руководству системы.
Введение
В последних главах книги рассмотрим некоторые из стандартных библиотек процедур системы UNIX (а также большинства сред поддержки языка С в других операционных системах).
Начнем с изучения очень важной стандартной библиотеки ввода/вывода, образующей основную часть стандартной библиотеки С, поставляемой со всеми системами UNIX. Интерфейс этой библиотекой составляет основную часть приведенного в приложении модуля stdio.
Читатели кратко ознакомились со стандартным вводом/выводом во второй главе и уже встречались с некоторыми из входящих в его состав процедур, например, процедурами getchar и printf. Основная цель стандартной библиотеки ввода/вывода состоит в предоставлении эффективных, развитых и переносимых средств доступа к файлам. Эффективность процедур, образующих библиотеку, достигается за счет обеспечения механизма автоматической буферизации, который невидим для пользователя и минимизирует число действительных обращений к файлам и число выполняемых низкоуровневых системных вызовов. Библиотека предлагает широкий выбор функций, таких как форматированный вывод и преобразование данных. Процедуры стандартного ввода/вывода являются переносимыми, так как они не привязаны к особым свойствам системы
UNIX и на самом деле являются частью независимого от UNIX стандарта ANSI языка С. Любой полноценный компилятор языка С предоставляет доступ к стандартной библиотеке ввода/вывода независимо от используемой операционной системы. Компилятор Free Pascal позволяет нам использовать эту библиотеку, как и многие другие, простым экспортом её функций.
Введение
В предпоследней главе будут рассмотрены несколько системных вызовов и некоторые полезные библиотечные процедуры, которые не соответствовали тематике всех предыдущих глав, такие как управление памятью, работа с временными значениями, предикаты типов символов, функции работы со строками.
Введение
В последней главе будут рассмотрены несколько блоков задач, которые соответствуют тематике всех предыдущих глав, с примерами решения.
Введение и основные понятия
ОС UNIX предлагает множество дополнительных механизмов межпроцессного взаимодействия. Их наличие дает UNIX богатые возможности в области связи между процессами и позволяет разработчику использовать различные подходы при программировании многозадачных систем. Дополнительные средства межпроцессного взаимодействия, которые будут рассмотрены, можно разбить на следующие категории:
–
передача сообщений;
– семафоры;
– разделяемая память.
Эти средства широко применяются и ведут свое начало от системы UNIX System V, поэтому их иногда называют IPC System V. Следует заметить, что вышеназванные дополнительные средства были определены в последних версиях стандарта POSIX.[15]
Ввод/вывод с отображением в память и работа с памятью
При выполнении процессом ввода/вывода больших объемов данных с диска, возникают накладные расходы, связанные с двойным копированием данных сначала с диска во внутренние буферы ядра, а потом из этих буферов в структуры данных процесса. Ввод/вывод с отображением в память увеличивает скорость доступа к файлу, напрямую «отображая» дисковый файл в пространство памяти процесса. Как уже было видно на примере работы с разделяемой памятью в разделе 8.3.4, работа с данными в адресном пространстве процесса является наиболее эффективной формой доступа. (Но хотя использование ввода/вывода с отображением в память и приводит к ускорению доступа к файлам, его интенсивное использование может уменьшить объем памяти, доступный другим средствам, использующим память процесса.)
Вскоре будут рассмотрены системные вызовы mmap и munmap. Но вначале опишем несколько простых процедур для работы с блоками памяти.
Вывод сообщений об ошибках при помощи функции writeln
Функция printf может использоваться для вывода диагностических ошибок. К сожалению, она осуществляет запись в стандартный вывод, а не в стандартный вывод диагностики. Тем не менее можно использовать для этого функцию writeln. Следующий фрагмент программы показывает, как можно это сделать:
uses stdio; (* Для определения stderr *)
.
.
writeln (stderr, 'Ошибка номер ', linuxerror);
Отличие между использованием writeln и вызовом printf заключается в параметре stderr, являющемся указателем на текстовый файл, автоматически связанный с потоком вывода стандартной диагностики.
Следующая процедура расширяет возможности использования функции writeln в более общей процедуре вывода сообщения об ошибке:
(* Функция notfound - вывести сообщение об ошибке и выйти *)
uses linux;
function notfound(progname, filename: string):integer;
begin
writeln(stderr, progname, ': файл ', filename, ' не найден');
halt(1);
end;
В последующих примерах для вывода сообщений об ошибках будет использована функция writeln, а не printf. Это обеспечит совместимость с большинством команд и программ, применяющих для диагностики стандартный вывод диагностики.
Вызов ехес и открытые файлы
Дескрипторы открытых файлов обычно сохраняют свое состояние также во время вызова ехес, то есть файлы, открытые в исходной программе, остаются открытыми, когда совершенно новая программа запускается при помощи вызова ехес. Указатели чтения-записи на такие файлы остаются неизменными после вызова ехес. (Очевидно, не имеет смысла говорить о сохранении значений переменных после вызова ехес, так как в общем случае новая программа совершенно отличается от старой.)
Тем не менее есть связанный с файловым дескриптором флаг close-on-exec
(закрывать при вызове ехес), который может быть установлен с помощью универсальной процедуры fcntl. Если этот флаг установлен (по умолчанию он сброшен), то файл закрывается при вызове любой функции семейства ехес. Следующий фрагмент показывает, как устанавливается флаг close-on-exec:
uses linux;
.
.
.
var
fd:longint;
fd := fdopen('file', Open_RDONLY);
.
.
.
(* Установить флаг close-on-exec *)
fcntl(fd, F_SETFD, 1);
Флаг close-on-exec можно сбросить так:
fcntl(fd, F_SETFD, 0);
Значение флага можно получить следующим образом:
res := fcntl(fd, F_GETFD);
Целое res будет иметь значение 1, если флаг close-on-exit установлен для дескриптора файла fd, и 0 – в противном случае.
Вызов fdopen и права доступа к файлу
Если вызов fdopen используется для открытия существующего файла на чтение или запись, то система проверяет, разрешен ли запрошенный процессом режим доступа (только для чтения, только для записи или для чтения-записи), проверяя права доступа к файлу. Если режим не разрешен, вызов fdopen вернет значение -1, указывающее на ошибку, а переменная linuxerror будет содержать код ошибки Sys_EACCES, означающий: нет доступа (permission denied).
Если для создания файла используется расширенная форма вызова fdopen, то использование флагов Open_CREAT, Open_TRUNC и Open_EXCL позволяет по-разному работать с существующими файлами. Примеры использования вызова fdopen с заданием прав доступа к файлу:
filedes := fdopen(pathname, Open_WRONLY or Open_CREAT or Open_TRUNC, octal(0600));
и:
filedes := fdopen(pathname, Open_WRONLY or Open_CREAT or Open_EXCL, octal(0600));
В первом примере, если файл существует, он будет усечен до нулевой для в случае, когда права доступа к файлу разрешают вызывающему процессу доступ на запись. Во втором – вызов fdopen завершится ошибкой, если файл существу независимо от заданных прав доступа к нему, а переменная linuxerror будет содержать код ошибки Sys_EEXIST.
Упражнение 3.5.
А. Предположим, что действующий идентификатор пользовать euid процесса равен 100, а его действующий идентификатор группы egid равен 200. Владельцем файла testfile являет пользователь с идентификатором 101, а идентификатор группы файла gid равен 200. Для каждого возможного режима доступа (только для чтения, только для записи, для записи-чтения) определите, будет ли успешным вызов open, если файл testfile имеет следующие права доступа:
rwxr-xrwx r-xrwxr-x rwx--x--- rwsrw-r--
--s--s--x ---rwx--- ---r-x--x
В. Что произойдет, если real user-id (действующий идентификатор пользователя) процесса равен 101, a real group-id (действующий идентификатор группы) равен 201?
Вызов fdseek и произвольный доступ
Системный вызов fdseek позволяет пользователю изменять положение указателя чтения-записи, то есть изменять номер байта, который будет первым считан или записан в следующей операции ввода/вывода. Таким образом, использование вызова fdseek позволяет получить произвольный доступ к файлу.
Вызов fork, файлы и данные
Созданный при помощи вызова fork дочерний процесс является почти точной копией родительского. Все переменные в дочернем процессе будут иметь те же самые значения, что и в родительском (единственным исключением являете значение, возвращаемое самим вызовом fork). Так как данные в дочернем процессе являются копией данных в родительском процессе и занимают другое абсолютное положение в памяти, важно понимать, что последующие изменения в одном процессе не будут затрагивать переменные в другом.
Аналогично все файлы, открытые в родительском процессе, также будут открытыми и в потомке; при этом дочерний процесс будет иметь свою копию связанных с каждым файлом дескрипторов. Тем не менее файлы, открытые до вызова fork, остаются тесно связанными в родительском и дочернем процессах. Это обусловлено тем, что указатель чтения-записи для каждого из таких файлов используется совместно родительским и дочерним процессами благодаря тому, что он поддерживается системой и существует не только в самом процессе. Следовательно, если дочерний процесс изменяет положение указателя в файле, то в родительском процессе он также окажется в новом положении. Это поведение демонстрирует следующая короткая программа, в которой использована процедура fatal, приведенная ранее в этой главе, а также новая процедура printpos. Дополнительно введено допущение, что существует файл с именем data длиной не меньше 20 символов (xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx).
(* Программа proc_file -- поведение файлов при ветвлении *)
(* Предположим, что длина файла "data" не менее 20 символов *)
uses linux,stdio;
var
fd:integer;
pid:longint; (* идентификатор процесса *)
buf:array [0..9] of char; (* буфер данных для файла *)
begin
fd := fdopen ('data', Open_RDONLY);
if fd = -1 then
fatal ('Ошибка вызова open ');
fdread (fd, buf, 10); (* переместить вперед указатель файла *)
printpos ('До вызова fork', fd);
(* Создать два процесса *)
pid := fork;
case pid of
1: (* ошибка *)
fatal ('Ошибка вызова fork ');
0: (* потомок *)
begin
printpos ('Дочерний процесс до чтения', fd);
fdread (fd, buf, 10);
printpos ('Дочерний процесс после чтения', fd);
end;
else (* родитель *)
begin
wait(nil);
printpos ('Родительский процесс после ожидания', fd);
end;
end;
end.
Процедура printpos просто выводит текущее положение в файле, а также короткое сообщение. Ее можно реализовать следующим образом:
(* Вывести положение в файле *)
procedure printpos(_string:pchar;filedes:integer);
var
pos:longint;
begin
pos := fdseek (filedes, 0, SEEK_CUR);
if pos=-1 then
fatal ('Ошибка вызова lseek');
writeln(_string,':',pos);
end;
После запуска этого примера получены результаты, которые убедительно подтверждают то, что указатель чтения-записи совместно используется обоими процессами:
До вызова fork:10
Дочерний процесс до чтения:10
Дочерний процесс после чтения:20
Родительский процесс после ожидания:20
Упражнение 5.5. Напишите программу, показывающую, что значения переменных программы в родительском и дочернем процессах первоначально совпадают, но не зависят друг от друга.
Упражнение 5.6. Определите, что происходит в родительском процессе, если дочерний процесс закрывает файл, дескриптор которого он унаследовал после ветвления. Другими словами, останется ли файл открытым в родительском процессе или же будет закрыт?
Вызовы brk и sbrk
Для полноты изложения необходимо упомянуть вызовы brk и sbrk. Это базовые низкоуровневые вызовы UNIX для динамического выделения памяти. Они изменяют размер сегмента данных процесса или, если быть более точным, смещают верхнюю границу сегмента данных процесса. Вызов brk устанавливает абсолютное значение границы сегмента, а вызов sbrk – ее относительное смещение. В большинстве ситуаций для выделения динамической памяти рекомендуется использовать функции семейства malloc, а не эти вызовы.
Упражнение 12.1. Односвязный список нашего примера может использоваться для реализации стека, в котором первым используется последний добавленный элемент. Процедура add_member будет соответствовать операции вставки (push) данных в стек. Напишите процедуру, реализующую обратную операцию извлечения (pop) данных из стека за счет удаления первого элемента списка.
Упражнение 12.2. Напишите программу, использующую функции семейства malloc для выделения памяти для целого числа, массива из трех переменных типа integer и массива указателей на переменные типа char.
Вызовы execv, execlp и execvp
Другие формы вызова ехес упрощают задание списков параметров запуска загружаемой программы. Вызов execv принимает два аргумента: первый (path в описании применения вызова) является строкой, которая содержит полное имя и путь к запускаемой программе. Второй аргумент (argv) является массивом строк, определенным как:
argv:ppchar;
Первый элемент этого массива указывает, по принятому соглашению, на имя запускаемой программы (исключая префикс пути). Оставшиеся элементы указывают на все остальные аргументы программы. Так как этот список имеет неопределенную длину, он всегда должен заканчиваться нулевым указателем.
Следующий пример использует вызов execv для запуска той же программы ls, что и в предыдущем примере:
(* Программа runls2 - использует вызов execv для запуска ls *)
uses linux,stdio;
const
av: array [0..2] of pchar=('ls', '-l', nil);
begin
execv ('/bin/ls', av);
(* Если мы оказались здесь, то произошла ошибка *)
perror ('execv failed');
halt(1);
end.
Функции execlp и execvp почти эквивалентны функциям execl и execv. Основное отличие между ними состоит в том, что первый аргумент обоих функций execlp и execvp – просто имя программы, не включающее путь к ней. Путь к файлу находится при помощи поиска в каталогах, заданных в переменной среды PATH. Переменная PATH может быть легко задана на уровне командного интерпретатора с помощью следующих команд:
$ PATH = /bin:/usr/bin:/usr/keith/mybin
$ export PATH
Теперь командный интерпретатор и вызов
execvp будут вначале искать команды в каталоге /bin, затем в /usr/bin, и, наконец, в /usr/keith/mybin.
Упражнение 5.2. В каком случае нужно использовать вызов execv вместо execl?
Упражнение 5.3. Предположим, что вызовы execvp и execlp не существуют. Напишите эквиваленты этих процедур, используя вызовы execl и execv. Параметры этих процедур должны состоять из списка каталогов и набора аргументов командной строки.
Взгляд с точки зрения программы
До сих пор изучались функции драйвера терминала, относящиеся к пользовательскому интерфейсу. Теперь же мы рассмотрим их с точки зрения программы, использующей терминал.
Задача об авиакомпании ACME Airlines
Теперь удастся разрешить конфликтную ситуацию в примере с авиакомпаний ACME Airlines. Для того, чтобы гарантировать целостность базы данных, нужно построить критический участок кода в программе acmebook следующим образом:
заблокировать соответствующий участок базы данных на запись
обновить участок базы данных
разблокировать участок базы данных
Если ни одна программа не обходит механизм блокировки, то запрос на блокировку гарантирует, что вызывающий процесс имеет исключительный доступ к критической части базы данных. Запрос на снятие блокировки снова делает доступной эту область для общего использования. Выполнение любой конкурирующей копии программы acmebook, которая пытается получить доступ к соответствующей части базы данных, будет приостановлено на стадии попытки наложения блокировки.
Текст критического участка программы можно реализовать следующим образом:
(* Набросок процедуры обновления программы acmebook *)
var
db_lock:flockrec;
.
.
.
(* Установить параметры блокировки *)
db_lock.l_type := F_WRLCK;
db_lock.l_whence := SEEK_SET;
db_lock.l_start := recstart;
db_lock.l_len := RECSIZE;
.
.
.
(* Заблокировать запись в базе, выполнение приостановится *)
(* если запись уже заблокирована *)
fcntl(fd, F_SETLKW, longint(@db_lock));
if linuxerror <> 0 then
fatal('Ошибка блокировки');
(* Код для проверки и обновления данных о заказах *)
.
.
.
(* Освободить запись для использования другими процессами *)
db_lock.l_type := F_UNLCK;
fcntl(fd, F_SETLK, longint(@db_lock));
Задание обработчика сигналов: вызов sigaction
После определения списка сигналов можно задать определенный метод обработки сигнала при помощи процедуры sigaction.
Задание ширины поля и точности
Спецификации формата могут также включать информацию о минимальной ширине (width) поля, в котором выводится аргумент, и точности (precision). В случае целочисленного аргумента под точностью понимается максимальное число выводимых цифр. Если аргумент имеет тип single или double, то точность задает число цифр после десятичной точки. Для строчного аргумента этот параметр определяет число символов, которые будут взяты из строки.
Значения ширины поля и точности находятся в спецификации формата сразу же после знака процента и разделены точкой, например, спецификация
%10.5d
означает: вывести соответствующий аргумент типа integer в поле шириной 10 символов; если аргумент имеет меньше пяти цифр, то дополнить его спереди нулями до пяти знаков. Спецификация
%.5f
означает: вывести соответствующий аргумент типа single или double с точностью до пяти десятичных знаков после запятой. Этот пример также показывает, что можно опускать параметр ширины поля. Аналогично можно задавать только ширину поля, поэтому спецификация
%10s
показывает: вывести соответствующую строку в поле длиной не менее 10 символов.
Во всех приведенных примерах вывод будет выравниваться по правой границе заданного поля. Для выравнивания по левой границе нужно задать знак минус сразу же за символом процента. Например, спецификация
%-30s
означает, что соответствующий строчный аргумент будет выведен в левой части поля шириной не менее 30 символов.
Может случиться, что ширина спецификации формата не может быть вычислена до запуска программы. В этом случаем можно заменить спецификацию ширины поля символом (*). Тогда процедура printf будет ожидать для этого кода дополнительный аргумент типа integer, определяющий ширину поля. Поэтому выполнение оператора
var
width, iarg:integer;
.
.
.
printf('%*d', [width, iarg]);
приведет к тому, что целочисленная переменная iarg будет выведена в поле шириной width.
Закрытие каналов
Что произойдет, если дескриптор файла, соответствующий одному из концов канала, будет закрыт? Возможны два случая:
–
закрывается дескриптор файла, открытого только на запись. Если существуют другие процессы, в которых канал открыт на запись, то ничего не произойдет. Если же больше не существует процессов, которые могли бы выполнять запись в канал, и канал при этом пуст, то любой процесс, который попытается выполнить чтение из канала, получит пустой блок данных. Процессы, которые были приостановлены и ожидали чтения из канала, продолжат свою работу, вызовы read вернут нулевое значение. Для процесса, выполняющего чтение, результат будет похож на достижение конца файла;
– закрывается дескриптор файла, открытого только на чтение. Если еще есть процессы, в которых канал открыт на чтение, то снова ничего не произойдет. Если же больше не существует процессов, выполняющих чтение из канала, то ядро посылает всем процессам, ожидающим записи в канал, сигнал SIGPIPE. Если этот сигнал не перехватывается в процессе, то процесс при этом завершит свою работу. Если же сигнал перехватывается, то после завершения процедуры обработчика прерывания вызов fdwrite вернет значение -1 и переменная linuxerror после этого будет содержать значение Sys_EPIPE. Процессам, которые будут пытаться после этого выполнить запись в канал, также будет посылаться сигнал SIGPIPE.
Закрытие TCP-соединения
При работе с сокетами важно корректно реагировать на завершение работы абонентского процесса. Так как сокет является двусторонним механизмом связи, то нельзя предсказать заранее, когда произойдет разрыв соединения – во время чтения или записи. Поэтому нужно учитывать оба возможных варианта.
Если процесс пытается записать данные в оборванный сокет при помощи вызова fdwrite или send, то он получит сигнал SIGPIPE, который может быть перехвачен соответствующим обработчиком сигнала. При чтении обрыв диагностируется проще.
В случае разорванной связи вызов fdread или recv возвращает нулевое значение. Поэтому для вызовов fdread и recv необходимо всегда проверять возвращаемое значение, чтобы не зациклиться при приеме данных.
Закрываются сокеты так же, как и обычные дескрипторы файлового ввода/вывода, – при помощи системного вызова fdclose. Для сокета типа
SOCK_STREAM ядро гарантирует, что все записанные в сокет данные будут переданы принимающему процессу. Это может вызвать блокирование операции закрытия сокета до тех пор, пока данные не будут доставлены. (Если сокет имеет тип SOCK_DGRAM, то сокет закрывается немедленно.)
Теперь можно привести полный текст примера клиента и сервера, добавив в серверный процесс обработку сигналов и вызов fdclose в обе программы. В данном случае эти меры могут показаться излишними, но в реальном клиент/серверном приложении обязательна надежная обработка всех исключительных ситуаций. Приведем окончательный текст программы сервера:
(* Серверный процесс *)
uses sockets,stdio,linux;
const
SIZE=sizeof(tinetsockaddr);
server:tinetsockaddr = (family:AF_INET; port:7000; addr:INADDR_ANY);
var
newsockfd:longint;
procedure catcher (sig:integer);cdecl;
begin
fdclose (newsockfd);
halt (0);
end;
var
sockfd:longint;
c:char;
act:sigactionrec;
mask:sigset_t;
client:tinetsockaddr;
clientaddrlen:longint;
begin
act.handler.sh := @catcher;
sigfillset (@mask);
act.sa_mask:=mask.__val[0];
sigaction (SIGPIPE, @act, nil);
(* Установить абонентскую точку сокета *)
sockfd := socket (AF_INET, SOCK_STREAM, 0);
if sockfd = -1 then
begin
perror ('Ошибка вызова socket');
halt (1);
end;
(* Связать адрес с абонентской точкой *)
if not bind (sockfd, server, SIZE) then
begin
perror ('Ошибка вызова bind');
halt (1);
end;
(* Включить прием соединений *)
if not listen (sockfd, 5) then
begin
perror ('ошибка вызова listen');
halt (1);
end;
while true do
begin
(* Прием запроса на соединение *)
newsockfd := accept (sockfd, client, clientaddrlen);
if newsockfd = -1 then
begin
perror ('Ошибка вызова accept');
continue;
end;
(* Создать дочерний процесс для работы с соединением *)
if fork = 0 then
begin
while recv (newsockfd, c, 1, 0) > 0 do
begin
c := upcase (c);
send (newsockfd, c, 1, 0);
end;
(* После того, как клиент прекратит передачу данных,
* сокет может быть закрыт и дочерний процесс
* завершает работу *)
fdclose (newsockfd);
halt (0);
end;
(* В родительском процессе newsockfd не нужен *)
fdclose (newsockfd);
end;
end.
И клиента:
(* Клиентский процесс *)
uses sockets,stdio,linux;
const
SIZE=sizeof(tinetsockaddr);
server:tinetsockaddr=(family:AF_INET; port:7000);
var
sockfd:longint;
c,rc:char;
begin
(* Преобразовать и сохранить IP address сервера *)
server.addr := inet_addr ('127.0.0.1');
(* Установить абонентскую точку сокета *)
sockfd := socket (AF_INET, SOCK_STREAM, 0);
if sockfd = -1 then
begin
perror ('Ошибка вызова socket');
halt (1);
end;
(* Подключить сокет к адресу сервера *)
if not connect (sockfd, server, SIZE) then
begin
perror ('Ошибка вызова connect');
halt (1);
end;
(* Обмен данными с сервером *)
rc := #$a;
while true do
begin
if rc = #$a then
writeln ('Введите строчный символ');
c:=char(getchar);
send (sockfd, c, 1, 0);
if recv (sockfd, rc, 1, 0) > 0 then
write (rc)
else
begin
writeln ('Сервер не отвечает');
fdclose (sockfd);
halt (1);
end;
end;
end.
Упражнение 10.1. Запустите приведенную программу сервера и несколько клиентских процессов. Что произойдет после того, как все клиентские процессы завершат работу?
Упражнение 10.2. Измените код программ так, чтобы после того, как все клиентские процессы завершат свою работу, сервер также завершал работу после заданного промежутка времени, если не поступят новые запросы на соединение.
Упражнение 10.3. Измените код программ так, чтобы два взаимодействующих процесса выполнялись на одном и том же компьютере. В этом случае сокет должен иметь коммуникационный домен AF_UNIX.
Запись и чтение без блокирования
Как уже было упомянуто, при использовании и вызова fdread, и вызова fdwrite может возникнуть блокирование, которое иногда нежелательно. Может, например, понадобиться, чтобы программа выполняла процедуру обработки ошибок или опрашивала несколько каналов до тех пор, пока не получит данные из одного из них. К счастью, есть простые способы пресечения нежелательных остановов внутри fdread и fdwrite.
Первый метод заключается в использовании для вызова fstat. Поле size в возвращаемой вызовом структуре tstat сообщает текущее число символов, находящихся в канале. Если только один процесс выполняет чтение из канала, такой подход работает прекрасно. Если же несколько процессов выполняют чтение из канала, то за время, прошедшее между вызовами fstat и fdread, ситуация может измениться, если другой процесс успеет выполнить чтение из канала.
Второй метод заключается в использовании вызова
fcntl. Помимо других выполняемых им функций этот вызов позволяет процессу устанавливать для дескриптора файла флаг Open_NONBLOCK. Это предотвращает блокировку последующих вызовов fdread или fdwrite. В этом контексте вызов fcntl может использоваться следующим образом:
uses linux;
.
.
.
fcntl(filedes, F_SETFL, Open_NONBLOCK);
if linuxerror <> 0 then
perror('fcntl');
Если дескриптор filedes является открытым только на запись, то следующие вызовы fdwrite не будут блокироваться при заполнении канала. Вместо этого они будут немедленно возвращать значение -1 и присваивать переменной linuxerror значение Sys_EAGAIN. Аналогично, если дескриптор filedes соответствует выходу канала, то процесс немедленно вернет значение -1, если в канале нет данных, а не приостановит работу. Так же, как и в случае вызова fdwrite, переменной linuxerror будет присвоено значение Sys_EAGAIN. (Если установлен другой флаг – Open_NDELAY, то поведение вызова fdread будет другим. Если канал пуст, то вызов вернет нулевое значение. Далее этот случай не будет рассматриваться.)
Следующая программа иллюстрирует применение вызова fcntl. В ней создается канал, для дескриптора чтения из канала устанавливается флаг Open_NONBLOCK, а затем выполняется вызов fork. Дочерний процесс посылает сообщения родительскому, выполняющему бесконечный цикл, опрашивая канал и проверяя, поступили ли данные.
. Запуск программ при помощи библиотек стандартного ввода/вывода
Стандартная библиотека ввода/вывода содержит несколько процедур для запуска одних программ из других. Основной из них является уже известная процедура runshell.
Зомби-процессы и преждевременное завершение программы
До сих пор предполагалось, что вызовы halt и wait используются правильно, и родительский процесс ожидает завершения каждого подпроцесса. Вместе с тем иногда могут возникать две другие ситуации, которые стоит обсудить:
– в момент завершения дочернего процесса родительский процесс не выполняет вызов wait;
– родительский процесс завершается, в то время как один или несколько дочерних процессов продолжают выполняться.
В первом случае завершающийся процесс как бы «теряется» и становится зомби-процессом (zombie). Зомби-процесс занимает ячейку в таблице, поддерживаемой ядром для управления процессами, но не использует других ресурсов ядра. В конце концов, он будет освобожден, если его родительский процесс вспомнит о нем и вызовет wait. Тогда родительский процесс сможет прочитать статус завершения процесса, и ячейка освободится для повторного использования. Во втором случае родительский процесс завершается нормально. Дочерние процесс (включая зомби-процессы) принимаются процессом init (процесс, идентификатор которого pid = 1, становится их новым родителем).