Программирование в стандарте POSIX
История создания и текущий статус стандарта POSIX
Обеспечение мобильности (переносимости, портабельности) программного обеспечения (ПО) - задача исключительной важности и сложности; в наше время это обстоятельство едва ли нуждается в пространных обоснованиях. Один из общепринятых способов повышения мобильности ПО - стандартизация окружения приложений: предоставляемых программных интерфейсов, утилит и т.п. На уровне системных сервисов подобное окружение описывает стандарт POSIX (Portable Operating System Interface - мобильный интерфейс операционной системы); название предложено известным специалистом, основателем Фонда свободного программного обеспечения Ричардом Столмэном.Мы будем рассматривать наиболее современную из доступных версий стандарта POSIX, в редакции 2003 г., которую можно назвать "стандартом втройне", а именно: стандартом IEEE Std 1003.1, Техническим стандартом Open Group и (см. [6]), что для нас важнее всего, международным стандартом ISO/IEC 9945 (см. [1], [2], [3], [4]).
История создания этой версии такова. В начале 1998 г. представители трех организаций - Комитета по стандартам мобильных приложений Института инженеров по электротехнике и электронике, Open Group и рабочей группы 15 подкомитета 22 совместного технического комитета 1 (JTC1/SC22/WG15) Международной организации по стандартизации - начали консультации по вопросу слияния и развития курируемых ими стандартов интерфейсов к системным сервисам: IEEE Std 1003.1, IEEE Std 1003.2, Базовых спецификаций от Open Group, ISO/IEC 9945-1, ISO/IEC 9945-2. В сентябре того же года в городе Остин, штат Техас, в офисе корпорации IBM состоялось организационное заседание группы, сформированной для достижения поставленной цели (см. http://www.opengroup.org/austin).
Основополагающим документом для пересмотренного стандарта, первый проект которого был представлен в июле 1999 года, стали Базовые спецификации от Open Group, поскольку они включали положения стандартов IEEE и ISO/IEC. В 2001 году, по завершении подготовительной работы, стандарт содержал следующие четыре части:
Далее в ISO, IEEE и Open Group с большей или меньшей скоростью (в 2001-2002 гг.) прошло формальное утверждение нового стандарта POSIX. Тем временем накапливались относительно мелкие исправления, учтенные в редакции 2003-го года.
С развитием стандарта расширялась и трактовка термина "POSIX". Первоначально он относился к документу IEEE Std 1003.1-1988, описывавшему прикладной программный интерфейс ОС класса Unix. После стандартизации интерфейса на уровне командного языка и служебных программ более правильно понимать под словом "POSIX" стандарт в целом, обозначая перечисленные выше части 2 и 3 через POSIX.1 и POSIX.2 в соответствии с нумерацией документов IEEE и ISO/IEC.
Мобильность POSIX-совместимых приложений
Мобильность приложений, соответствующих стандарту POSIX, принципиально достижима благодаря двум основным факторам. Во-первых - это наличие огромного числа стандартизованных системных сервисов, а во-вторых - возможность динамического выяснения характеристик целевой платформы и подстройки под них приложения. (Естественно, мы имеем в виду мобильность в рамках, регламентируемых стандартом.)Приложения, соответствующие стандарту POSIX, могут быть одно- и многопроцессными, с возможностью динамической адаптации конфигурации к свойствам целевой платформы. Стандартизованы средства порождения и завершения процессов, смены их программ, опроса и/или изменения разнообразных характеристик. Процессы можно приостанавливать и активизировать в заданное время. Механизм сигналов позволяет извещать о событиях и завершать процессы внешним образом. Для их группирования предусмотрены средства управления заданиями. Приложения снабжены регуляторами для управления планированием и приоритетами процессов. Широк спектр средств межпроцессного взаимодействия (очереди сообщений, разделяемая память, семафоры) и управления памятью. Наконец, в пределах процесса можно организовать несколько потоков управления.
Необходимая степень детерминизма выполнения достигается благодаря средствам поддержки реального времени (к ним относятся управление дисциплиной выделение процессоров, сигналы реального времени, удержание страниц в оперативной памяти, таймеры высокого разрешения и т.д.).
Функции для работы с файлами удовлетворяют потребности приложений в чтении и записи долговременных данных, защите таких данных от несанкционированного доступа. Механизм блокировки фрагментов файлов позволяет обеспечить атомарность транзакций. Асинхронный ввод/вывод дает возможность совмещать операции обмена, оптимизируя тем самым приложения. С помощью множества служебных программ можно относительно легко организовать сложную обработку данных.
В стандарте POSIX тщательно проработаны вопросы доступа к внешним устройствам, подсоединенным по последовательным линиям, особенно к терминалам.
Возможно, в большей детализации нуждаются средства работы с такими распространенными носителями, как магнитная лента.
Стандартизованный командный язык shell - адекватное средство для написания небольших мобильных процедур и их быстрой интерактивной отладки. Выделим механизм конвейеров, позволяющий объединять команды в цепочки с фильтрацией промежуточных результатов. Служебные программы образуют развитую среду выполнения для shell-процедур. За счет фонового режима можно организовать одновременное выполнение нескольких программ и взаимодействие с ними посредством обычного терминала без многооконных возможностей (впрочем, окна, несомненно, не помешали бы).
POSIX стандартизует интерфейс командной строки. В принципе, он достаточен, в меру удобен и, что важно, создает минимум проблем с точки зрения мобильности. Вероятно, в будущих версиях стандарта будет регламентирован графический интерфейс, но, безусловно, это чревато дополнительными сложностями для разработчиков мобильных приложений.
Языково-культурная среда - одно из важнейших понятий стандарта POSIX с точки зрения мобильности. Приложения способны определять нужную им среду и адаптироваться к потребностям пользователей.
Для многопользовательских систем требуется организация взаимодействия большого числа людей. POSIX решает эту проблему, регламентируя средства непосредственного и почтового обмена информацией.
Стандартом POSIX предусмотрены базовые средства поддержки разработки (в первую очередь - для языка C), что, конечно, не снижает потребности в специализированных, развитых системах, когда речь идет о работе с действительно большими программными проектами.
Приложениям предоставляются стандартизованные средства для выяснения как "крупноблочных" характеристик целевой системы (например, спектр поддерживаемых необязательных возможностей), так и более мелких характеристик (текущий размер свободного дискового пространства).
Проблема мобильности приложений чрезвычайно сложна, и было бы преувеличением утверждать, что стандарт POSIX-2001 решает ее полностью.Во-первых, за его рамками остаются такие важнейшие вопросы, как графика, многооконный интерфейс и целый ряд других. Во-вторых, в регламентируемых областях присутствуют "белые пятна" неспецифицированного поведения реализаций. Тем не менее, подчеркнем это еще раз, следование стандарту POSIX - обязательный элемент современной дисциплины разработки прикладных систем.
Основные идеи стандарта POSIX
Стандарт POSIX описывает множество базовых, системных сервисов, необходимых для функционирования прикладных программ. Доступ к ним предоставляется посредством интерфейса, специфицированного для языка C, командного языка и общеупотребительных служебных программ.У каждого интерфейса есть две стороны: вызывающая и вызываемая. Стандарт POSIX ориентирован в первую очередь на вызывающую. Его цель - сделать приложения мобильными на уровне исходного языка. Это значит, в частности, что при переносе C-программ на другую операционную платформу потребуется перекомпиляция. О мобильности выполнимых программ и/или объектных файлов речь не идет.
Стандарт POSIX отнюдь не ограничен рамками Unix-среды. Существуют операционные системы (ОС) "независимого происхождения" (например, системы реального времени), предоставляющие необходимые сервисы и тем самым поддерживающие выполнение POSIX-совместимых приложений. Можно утверждать, что следование стандарту POSIX облегчает перенос приложений практически на любую сколько-нибудь распространенную операционную платформу. Дополнительные усилия по повышению мобильности, прилагаемые на этапе разработки, безусловно, окупятся.
Определяя интерфейс к системным сервисам, POSIX оставляет за рамками рассмотрения их реализацию. В частности, не различаются системные вызовы и библиотечные функции. Не являются объектом стандартизации средства администрирования, аппаратные ограничения и функции, необходимые только суперпользователю, что еще раз подчеркивает направленность стандарта POSIX на приложения, а не на операционные системы.
POSIX нейтрален по отношению к системной архитектуре и разрядности процессора. Это очень важный аспект мобильности приложений.
Ориентация на международный стандарт языка C определила не только стиль описания функций, но и, до некоторой степени, направление развития спецификаций POSIX в плане синхронизации обоих стандартов. Как известно в утвержденной в 1999 г. редакции спецификаций языка C (см. [5]) узаконен комплексный тип данных, что вызвало соответствующее пополнение POSIX-функций.
В стандарте POSIX проведено разделение на обязательные и дополнительные функции, причем обязательное ядро сделано по возможности компактным. Разумеется, особое внимание уделяется способам реализации стандартизуемых функций как в "классической" Unix-среде, так и на других операционных платформах, в сетевых и распределенных конфигурациях.
Разработчики новой версии стандарта POSIX очень бережно отнеслись и к его предыстории, и к предыстории Unix-систем, и, главное, к приложениям, удовлетворявшим более ранним версиям стандарта. Существующие интерфейсы старались сохранять; в процессе развития соблюдался принцип обратной совместимости; новые интерфейсы добавлялись так, чтобы они не конфликтовали со старыми. Полностью избежать внесения изменений в приложения не удалось по вполне понятным причинам: потребовалось устранить противоречия между разными исходными спецификациями, а также отказаться от поддержки "традиционного" варианта языка C и перейти на его международный стандарт.
Основные понятия операционных систем, соответствующих стандарту POSIX
Мы рассмотрим следующие основные понятия операционных систем, соответствующих стандарту POSIX:Это первичные понятия. Их нельзя строго определить, но можно пояснить с помощью других понятий и отношений. Для каждого из выделенных понятий будут описаны присущие им атрибуты и применимые к ним операции.
В тексте стандарта POSIX содержатся следующие пояснения основных понятий вместе со ссылками на атрибуты и операции.
Для работы с большим числом сущностей всегда предоставляются механизмы группирования и построения иерархий. Существует иерархия файлов, группы пользователей и процессов, подсети и т.п.
Для написания программ, оперирующих с сущностями POSIX-совместимых систем, применяются командный интерпретатор (язык shell) и/или компилируемый язык C. В первом случае приложение может пользоваться служебными программами (утилитами), во втором - функциями. Функциональный интерфейс операционных систем естественно считать первичным, поскольку большинство служебных программ предназначены, по сути, для вызова той или иной функции. По этой причине далее мы будем рассматривать преимущественно уровень функций.
Основными операциями, применимыми к объектам ОС, являются чтение, запись и выполнение.
Механизм прав доступа позволяет избирательно разрешать и запрещать осуществление подобных операций. Ранее в стандарте фигурировало понятие суперпользователя, не подверженного контролю прав доступа. В POSIX-2001 выбрана более гибкая формулировка - "имеющий соответствующие привилегии", что отражает прогресс в реализации ОС с расщеплением суперпользовательских возможностей.
В POSIX-совместимых ОС определены объекты, которые можно назвать вспомогательными; они помогают организовать взаимодействие между основными сущностями. Особенно широк спектр средств межпроцессного взаимодействия.
Процессы выполняются в определенном окружении, частью которого является языково-культурная среда (Locale), образованная такими категориями, как символы и их свойства, форматы сообщений, дата и время, числовые и денежные величины.
Как правило, с процессом ассоциированы по крайней мере три файла - стандартный ввод, стандартный вывод, стандартный протокол. Обычно стандартный ввод назначается на клавиатуру терминала, а стандартный вывод и стандартный протокол - на экран. Со стандартного ввода читаются команды и (иногда) исходные данные для них. На стандартный вывод поступают результаты выполнения команд. В стандартный протокол помещаются диагностические сообщения.
К операционным системам могут предъявляться качественные требования, например, требование поддержки реального времени: способность обеспечить необходимый сервис в течение заданного отрезка времени.
Основные понятия стандарта POSIX
Стандарт POSIX в редакции 2003-го года - весьма обширный, многогранный документ, где подробно рассматриваются следующие категории системных компонентов:Именно такой (на верхнем уровне, далеко не полный) репертуар должна предоставлять операционная система для работы приложения.
Важнейшим является понятие соответствия стандарту POSIX. Мы уже отмечали, что всякий интерфейс располагает двумя сторонами: вызывающей и вызываемой. Две стороны есть и у POSIX-соответствия: соответствие реализации (операционной системы) и приложения.
Реализация (операционная система), соответствующая стандарту POSIX, должна поддерживать все обязательные служебные программы, функции, заголовочные файлы с обеспечением специфицированного в стандарте поведения. Константа _POSIX_VERSION имеет значение 200112L.
ОС может предоставлять возможности, помеченные в стандарте в качестве дополнительных, а также содержать нестандартные функции. Если утверждается, что поддерживается некоторое расширение, это должно производиться непротиворечивым образом, для всех необходимых частей и так, как описано в стандарте.
В заголовочном файле
Для минимизации размеров ОС и приложений стандартом POSIX предусмотрена весьма мелкая гранулярность необязательных возможностей (всего их сорок). С другой стороны, проведено объединение взаимосвязанных необязательных возможностей в группы, что во многих случаях избавляет от анализа большого числа опций.
Группы эти таковы:
Например, в группу "средства реального времени" (_XOPEN_REALTIME) входят возможности четырнадцати видов, в том числе планирование на основе приоритетов, асинхронный ввод/вывод, семафоры, таймеры и т.п.
Версия ОС Linux, на которой готовился текст данного курса, выдавала следующие значения некоторых конфигурационных констант (см. листинг 1.1).
$ getconf _POSIX_VERSION 199506 $ getconf POSIX2_C_DEV 1 $ getconf _XOPEN_REALTIME 1 $ getconf _POSIX_TRACE undefined
Листинг 1.1. Результат применения утилиты getconf к одной из версий ОС Linux. (html, txt)
Это значит, что поддерживается устаревшая версия стандарта POSIX, среди прочих присутствуют средства разработки и возможности реального времени; средства трассировки отсутствуют.
В документации на ОС должны быть отражены вопросы соответствия стандарту POSIX, описаны поддерживаемые дополнительные и нестандартные возможности.
Для приложений понятие соответствия стандарту POSIX богаче нюансами. Предусмотрено строгое соответствие, главный отличительный признак которого - ограничение круга используемых возможностей рамками стандарта. Рассматривается и соответствие с применением расширений; в этом случае документация на приложение должна содержать описание требуемых нестандартных возможностей. Желательно, чтобы используемые расширения POSIX-возможностей описывались международными и/или национальными стандартами.
(Отметим, что для реализации понятие строгого POSIX-соответствия бессмысленно хотя бы по той причине, что не бывает операционных систем без средств администрирования, а они не описываются данным стандартом.)
Профилем будем называть набор опций, описывающих необязательные возможности. Соответствие профилю означает соответствие стандарту POSIX и поддержку заданных возможностей. Разумным образом выбранные профили позволяют учитывать потребности представительных классов пользователей и/или приложений.
Допускается существование "подпрофилей", описывающих подмножества стандартных возможностей. Реализация, соответствующая подпрофилю, может функционировать на аппаратных платформах с ограниченными ресурсами и/или обслуживать нужды специфических приложений.
К числу важнейших принадлежат понятия, описывающие поведение реализации в различных ситуациях. Для многих корректных ситуаций поведение бывает неспецифицированным, а значит, мобильное приложение не должно полагаться на совпадение поведения разных реализаций. Для некорректных ситуаций поведение может быть неопределенным; приложению не только не следует полагаться на определенный характер подобного поведения - оно не должно совершать некорректных действий, вызывающих неопределенное поведение.
Еще один близкий термин, "поведение, зависящее от реализации", дополнительно означает, что поведение реализации необходимо документировать.
Стандарт POSIX - это существующий много лет, развивающийся организм, в котором с каждой новой редакцией что-то появляется, а что-то утрачивается. Устаревшими называются возможности, которые еще поддерживаются различными реализациями, но в будущем они, вероятно, отомрут. Новые приложения не должны их использовать; для каждой из них стандартом предусмотрена адекватная по функциональности современная замена.
Более ограниченный смысл придан термину "унаследованный": он описывает устаревшие необязательные возможности, которых, разумеется, следует избегать в новых приложениях.
к одной из версий ОС
| $ getconf _POSIX_VERSION 199506 $ getconf POSIX2_C_DEV 1 $ getconf _XOPEN_REALTIME 1 $ getconf _POSIX_TRACE undefined |
| Листинг 1.1. Результат применения утилиты getconf к одной из версий ОС Linux. |
| Закрыть окно |
| #if defined(_REENTRANT) || (_POSIX_C_SOURCE - 0 >= 199506L) # define LIBXML_THREAD_ENABLED #endif |
| Листинг 1.2. Пример использования макроса проверки возможностей _POSIX_C_SOURCE. |
| Закрыть окно |
Среда компиляции POSIX-совместимых приложений
Как правило (хотя это и не всегда осознается), разработка приложений ведется в кросс-режиме, то есть платформа разработки (эквивалентный термин - инструментальная платформа) не совпадает с платформой выполнения (называемой также целевой платформой). На инструментальной платформе создается среда компиляции приложений, так что результат компиляции может быть перенесен для последующего выполнения на целевую платформу.Важнейшая часть среды компиляции - заголовочные (или включаемые) файлы, содержащие прототипы функций, определения символических констант, макросов, типов данных, структур и т.п. Для каждой описанной в стандарте POSIX функции определено, какие заголовочные файлы должны быть включены использующим ее приложением (обычно требуется один файл).
Выше было указано, что посредством символических констант, определенных в заголовочном файле
Основным макросом проверки возможностей является _POSIX_C_SOURCE. Среди требований к приложениям, строго соответствующим стандарту POSIX, фигурирует необходимость определения символической константы _POSIX_C_SOURCE со значением 200112L до включения каких-либо заголовочных файлов. Таким образом POSIX-совместимое приложение заявляет, что ему нужны POSIX-имена. Близкую по смыслу роль играет макрос _XOPEN_SOURCE (со значением 600).
Примером использования макроса _POSIX_C_SOURCE во включаемых файлах ОС Linux может служить фрагмент, приведенный на листинге 1.2.
#if defined(_REENTRANT) || (_POSIX_C_SOURCE - 0 >= 199506L) #define LIBXML_THREAD_ENABLED #endif
Листинг 1.2. Пример использования макроса проверки возможностей _POSIX_C_SOURCE. (html, txt)
Стандартом POSIX предусмотрены некоторые меры для решения важной и трудной проблемы (вызванной в первую очередь необъектным характером языка C), заключающейся в отсутствии пересечений по именам между приложением и операционной системой.
Префиксы posix_, POSIX_ и _POSIX_ зарезервированы для нужд стандарта.
С подчеркивания, за которым следует еще одно подчеркивание или заглавная латинская буква, могут начинаться только системные (но не прикладные) имена. Для включаемых файлов описаны префиксы используемых в них имен. Например, для операций управления файлами, фигурирующих в
Программирование в стандарте POSIX
Генерация маршрутных имен файлов
После всех подстановок, прежде чем команда начнет выполняться, в каждом составляющем ее поле осуществляется поиск символов *, ?, и [. Если находится хотя бы один из них, то это поле рассматривается как шаблон имен файлов и заменяется именами файлов, удовлетворяющими данному шаблону. Имена подставляются в алфавитном порядке. Если ни одно имя файла не удовлетворяет шаблону, поле остается неизменным. Символ . в начале имени файла или непосредственно после /, так же как и сам символ /, должны быть заданы в шаблоне явно. Трактовка символов *, ? и [:| * - сопоставляется с произвольной цепочкой символов, в том числе с пустой. | ? - сопоставляется с произвольным символом. | [...] - сопоставляется с любым из перечисленных в скобках символов. Пара символов, разделенных знаком -, обозначает отрезок алфавита, включающий сами указанные символы. Если сразу вслед за [ идет !, шаблону удовлетворяет любой символ, не перечисленный в скобках. |
Рассмотрим несколько примеров. Чтобы подсчитать суммарное число строк во всех C-файлах текущего каталога, достаточно выполнить команду
wc -l *.c
В качестве второго примера расширим приведенный выше фрагмент действий при загрузке системы (см. листинг 2.22):
for f in /etc/rc$runlevel.d/S* do if [ -s ${f} ] then /bin/sh ${f} start fi done
Листинг 2.22. Пример сочетания управляющих конструкций с генерацией имен файлов. (html, txt)
В цикле будут в алфавитном порядке запускаться все непустые файлы из каталога, соответствующего заданному уровню выполнения (обычно это /etc/rc3.d, поскольку runlevel имеет значение 3, но в данном случае это не суть важно), имена которых начинаются на S.
Как пример использования шаблона имен файлов приведем команду
rm -f .*.[Bb]?
Она удалит из текущего каталога все файлы, имена которых начинаются с точки, сразу после второй точки стоит одна из букв - B или b, а затем произвольный символ.
Читателю предлагается рассмотреть еще один способ применения шаблона имен файлов и самостоятельно ответить на вопрос, удалит ли команда
rm -f *.[Bb][AaUu]
файл с именем .bu.
Наконец, рассмотрим более сложный пример. Пусть требуется так переименовать фортрановские файлы текущего каталога, чтобы окончание .f заменилось на .for. Служебная программа
basename цепочка_символов [суффикс]
убирает из цепочки_символов любой оканчивающийся на / префикс и суффикс (если он есть) и выдает результат на стандартный вывод. Применим ее для решения сформулированной задачи (см. листинг 2.23).
for f in *.f do mv $f `basename $f .f`.for done
Листинг 2.23. Пример подстановки результатов команды как части слова. (html, txt)
Особенностью приведенного примера является использование подстановки результатов команды как части слова (в данном случае - как части нового имени файла).
Еще одним видом генерации маршрутных имен файлов можно было бы считать обработку символа тильда '~', однако это действие открывает, а не завершает подстановки в командной строке. Префикс слов, от тильды до ближайшего слэша (или конца слова, если слэш отсутствует), заменяется на имя домашнего каталога пользователя, входное имя которого задано префиксом. Если префикс пустой, вместо него подставляется значение переменной окружения HOME. В присваиваниях тильда распознается и обрабатывается не только в начале слова, но и после знаков равенства и двоеточия, что позволяет, в частности, естественным образом обращаться со значением переменной окружения PATH.
Так, при подстановке результатом работы команды echo ~ может быть /home/galat.
Экранирование
Под экранированием имеется в виду защита символов от интерпретации со стороны языка shell. Следующие символы shell трактует по-особому; не будучи экранированными, они завершают предшествующее им слово:| & ; < > ( ) $ ` \ " ' пробел перевод_строки
Не являясь разделителями слов, особую роль играют для shell и символы
* ? [ ] # ~ = %
Каждый из перечисленных выше символов может быть экранирован, т. е. представлять самого себя, если перед ним стоит \ либо он расположен между одиночными или двойными кавычками ('' или "").
Экранирование с помощью двойных кавычек имеет некоторые особенности. Во-первых, оно не отменяет особой трактовки символов $ и `. Иными словами, внутри двойных кавычек обычным образом выполняется подстановка значений переменных, результатов выполнения команд и результатов вычисления арифметических выражений, только результат подстановки расценивается как одно слово. Во-вторых, особая трактовка символа \ отменяется внутри двойных кавычек лишь частично: символ \ сохраняет экранирующую роль, если после него следуют:
$ ` " \ перевод_строки
Приведем несколько примеров использования двойных кавычек для экранирования. В первом команда
echo "$(echo *)"
выдаст список файлов текущего каталога, а во втором результатом выполнения строки
echo "$(echo "*")"
станет звездочка. Если же аргумент "$(echo *)" предложить усовершенствованной процедуре three_args, то число фактических аргументов окажется равным единице, а список файлов текущего каталога станет значением первого аргумента.
Рассмотрим третий пример. Пусть нужно выдать информацию о всех файлах текущего каталога, которые модифицировались последний раз 3 октября. Конвейер
ls -al | grep "Oct 3"
сделает то, что нужно (служебная программа grep выполняет сопоставление заданного шаблона со строками заданных файлов или стандартного ввода). Кавычки использованы для экранирования двух пробелов в разыскиваемой цепочке символов. Без экранирования пробелы превращаются в разделители аргументов, и команда grep попыталась бы найти текст Oct в файле с цифровым именем 3.
Еще один пример. Запись
$(($i\*5))
синтаксически неверна, поскольку в арифметических выражениях shell рассматривает звездочку как операцию умножения, а не как элемент шаблона имен файлов; соответственно, она не нуждается в экранировании, и символ \ становится лишним, нарушая синтаксис выражения. Таким образом, лишнее экранирование может быть вредным.
Конвейеры и примеры их использования
Конвейер - одна из самых красивых конструкций ОС Unix, вошедшая, разумеется, и в стандарт POSIX. Идея его проста, но на редкость продуктивна. С помощью конвейеров удается комбинировать возможности, предоставляемые разными командами, получая по существу новое качество.Например, утилита ls не подсчитывает число файлов в каталоге, а лишь выдает информацию о них. С другой стороны, служебная программа wc способна подсчитать число строк в файле, но не имеет отношения к распечатке содержимого каталогов. Если же построить конвейер из двух упомянутых команд, количество файлов в каталоге легко вычисляется. Например, результатом работы конвейера (см. листинг 2.1) на нашей установке ОС Linux будет число 92 (утилита wc, вызванная без аргументов, обрабатывает файл стандартного ввода, который в данном случае является результатом работы команды ls). Значит, в каталоге /bin 91 файл, если считать и элементы, соответствующие текущему и вышележащему каталогам (первая строка выдачи ls содержит суммарное число блоков, занятых файлами каталога).
ls -al /bin | wc -l
Листинг 2.1. Пример конвейера. (html, txt)
Еще один пример. Пусть необходима информация о файлах текущего каталога, модифицированных в октябре. К цели ведет конвейер, показанный в листинге 2.2:
ls -al | grep "Oct "
Листинг 2.2. Еще один пример конвейера. (html, txt)
Служебная программа grep играет здесь роль фильтра, который пропускает для вывода только часть строк, выдаваемых ls.
Можно выстроить и трехступенчатый конвейер, если требуется подсчитать число файлов, модифицированных в октябре (см. листинг 2.3):
ls -al | grep "Oct " | wc -l
Листинг 2.3. Пример трехступенчатого конвейера. (html, txt)
Здесь утилиту grep с еще большим правом можно назвать фильтром.
Приведем еще один пример конвейера, полезного, когда нужна подробная информация о большом каталоге (см. листинг 2.4):
ls -Rl /dev | more
Листинг 2.4. Конвейер для поэкранного просмотра результатов. (html, txt)
Связующее звено между последовательными компонентами конвейера называется каналом. Иными словами, для интерпретации конвейера shell создает временный файл типа "канал", с одного конца в него заносят данные, а с другого - читают. С помощью служебной программы tee можно организовать ответвление канала, т. е. помещать информацию не только на стандартный вывод, но и в указанные файлы:
tee файл ...
Например, если нужно не только подсчитать число файлов из текущего каталога, модифицированных в октябре, но и поместить информацию о них в файл для последующего анализа, следует построить четырехступенчатый конвейер (см. листинг 2.5):
ls -al | grep "Oct" | tee /tmp/tmpinf | wc -l
Листинг 2.5. Четырехступенчатый конвейер. (html, txt)
В результате его выполнения на экране появится сообщение о количестве нужных файлов, а в файле /tmp/tmpinf - информация о них.
Окружение процессов
Окружение - это набор пар (имя, значение), который передается выполняемой программе так же, как и обычный список аргументов. Иными словами, порождаемые процессы наследуют окружение процесса-предка. Компонентами окружения являются, помимо прочих, следующие переменные и их значения:HOME
Подразумеваемый аргумент утилиты смены текущего каталога cd - домашний каталог пользователя.
IFS
Цепочка символов, где перечислены разделители полей; обычно включает пробел, табуляцию и перевод строки.
PATH
Список имен каталогов для поиска команд. В дальнейшем подобные списки называются списками поиска. Элементы списка разделяются двоеточием. Пустой элемент означает текущий каталог.
PS1
Основное приглашение интерактивного языка shell (по умолчанию "$").
TERM
Тип пользовательского терминала.
TZ
Информация о часовом поясе.
Для отсылки информации об окружении на стандартный вывод следует воспользоваться командой
env
Поясним подробнее смысл некоторых переменных окружения.
Прежде чем выполнить команду, shell ищет файл с соответствующим именем в последовательности каталогов, являющейся значением переменной PATH. Назовем подобную последовательность списком поиска. Если, например, значение $PATH суть
/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin
то нужный файл будет сначала разыскиваться в каталоге /usr/local/bin, затем в /bin и т.д. Как только файл отыщется, поиск прекратится. Это важно, если в разных каталогах есть одноименные выполнимые файлы.
Значение $IFS влияет не только на разбор команд, но и на чтение строк данных с помощью команды read (см. далее). Кроме того, первый символ из значения $IFS вставляется между фактическими аргументами при выполнении подстановки $*.
Переменная TERM хранит тип терминала пользователя. Интерактивные утилиты (редакторы или другие программы с экранным интерфейсом, например talk) с помощью значения $TERM настраиваются на конкретный тип терминала.
Переменная TZ задает локальный часовой пояс. Эта информация необходима всегда, когда требуется показать текущее время.
Особенно полезна она при почтовом взаимодействии с территориально удаленными (в частности, зарубежными) пользователями.
Для изменения окружения мало присвоить новое значение соответствующей переменной. Дело в том, что по умолчанию переменные считаются локальными по отношению к shell-процедуре, т. е. присваивание изменит локальную переменную, но не затронет одноименную переменную окружения. Таким образом, в окружение новых процессов (порожденных, например, для выполнения последующих команд данной shell-процедуры) войдет переменная со старым значением.
С помощью конструкции
export имя[=слово]
переменная с указанным именем и значением (последнее может быть опущено) помещается в окружение, т. е. становится глобальной.
Следующая строка иллюстрирует типичное применение команды export:
export PATH=/local/bin:$PATH
Переменная PATH изменилась, и новое значение экспортировано в окружение.
Команда
export -p
выдает на стандартный вывод имена и значения всех экспортированных переменных. Эту выдачу можно использовать для сохранения и последующего восстановления (быть может, с некоторыми модификациями) окружения. Ниже приведен фрагмент возможного результата работы команды export -p (см. листинг 2.17).
export HISTSIZE="1000" export HOME="/home/galat" export LANG="C" export LESSCHARSET="koi8-r" export LOGNAME="galat" export MAIL="/var/spool/mail/galat" export TTY="/dev/ttyS4" export USER="galat"
Листинг 2.17. Возможные результаты выполнения команды export -p. (html, txt)
Мы видим, что выдача устроена так, чтобы вновь быть поданной на вход командного интерпретатора.
Основные понятия языка shell
В дальнейшем изложении слово shell будет употребляться в двух смыслах - как имя языка программирования и как название командного интерпретатора.Для нас shell важен прежде всего как мощный язык мобильного программирования. Предваряя дальнейшее изложение, отметим имеющиеся в нем возможности комбинирования команд с помощью конвейеров, подстановки значений переменных и результатов выполнения команд, генерации имен файлов по шаблонам.
Свойства shell как интерактивного командного интерпретатора, непосредственно взаимодействующего с пользователем, хранение и использование истории сеанса, возможности редактирования командной строки и т.п., на ваш взгляд, менее актуальны, поскольку современный пользовательский интерфейс строится на иной основе.
Выделим основные понятия языка shell на лексическом уровне.
Под пробелом в дальнейшем понимается не только собственно пробел, но также и символ табуляции.
Слово - это лексема, отличная от знака операции.
Имя - последовательность букв, цифр, символов подчеркивания, начинающаяся с буквы или подчеркивания.
Параметр - имя, цифра или любой из символов *, @, #, ?, -, $, !.
Комментарий - лексема, начинающаяся с символа #, а также вся последующая часть строки.
На синтаксическом уровне различаются несколько видов команд.
Простая команда - последовательность полей с разделителями (обычно пробелами) между ними. Первое поле определяет имя команды, которая будет выполняться; оставшиеся поля, за исключением присваиваемых параметрам и перенаправления ввода/вывода (см. далее), передаются команде в качестве аргументов. Имя команды передается как аргумент 0.
Значение простой команды - ее код завершения.
Команда - это либо простая команда, либо одна из управляющих конструкций (см. далее). Кодом завершения команды является код завершения последней выполненной простой команды.
Конвейер - последовательность команд, разделенных знаком |. Стандартный вывод всех команд, кроме последней, направляется на стандартный ввод следующей команды конвейера.
Каждая команда выполняется как самостоятельный процесс; shell ожидает завершения последней команды, код завершения которой становится кодом завершения конвейера. Формально будем считать простую команду частным случаем конвейера.
Список - последовательность из одного или нескольких разделенных символами ;, &, && или || конвейеров, она может заканчиваться символами ; или &. Из четырех указанных операций ; и & имеют равные приоритеты, меньшие, чем у && и ||. Приоритеты последних также равны между собой. Символ ; означает, что конвейеры будут выполняться последовательно, а & - параллельно (т. е. shell не ожидает завершения конвейера). Операция && (||) означает, что список, следующий за ней, будет выполняться лишь в том случае, если код завершения предыдущего конвейера нулевой (ненулевой). В списке в качестве разделителя конвейеров вместо символа ; можно использовать символ перевода строки.
Командная строка - строка текста на языке shell.
Shell-процедура - файл, содержащий программу на языке shell.
Для выполнения (почти) каждой простой команды shell порождает отдельный процесс, в рамках которого выполняется программа, хранящаяся в файле, заданном именем команды. Программа может быть выполнимой, т. е. содержать машинные инструкции, или представлять собой shell-процедуру - содержать текст на языке shell.
Далее при описании синтаксиса конструкций языка shell и способа вызова служебных программ будут использоваться следующие соглашения:
Переменные и аргументы shell-процедур
Переменные обозначаются именами. Значения могут присваиваться им привычным способом, то есть посредством команд вида:имя=значение [имя=значение] ...
Все значения в языке shell трактуются как текстовые. Подчеркнем, что, в соответствии с этими требованиями, конструкция
имя=значение
должна представлять собой одно слово - в ней не может быть пробелов.
Обычно в языках программирования ясно из контекста, где подразумевается имя переменной, а где значение. Так, в левой части оператора присваивания обычно используется имя, в правой - значение. В shell все не так. Переход от имени переменной к значению помечается посредством явной операции $. Если в команде встречается конструкция
$имя
то вместо нее интерпретатор shell подставляет значение переменной с указанным именем. Допускается и запись
${имя}
с тем же смыслом, если нужно отделить имя от последующего текста.
Рассмотрим пример. После выполнения команд (см. листинг 2.6) - утилита echo (эхо) выдает на стандартный вывод свои аргументы - на экране появится результат вывода значений переменных (см. листинг 2.7).
a=value_of_variable b=1+2 echo a = $a echo b = $b
Листинг 2.6. Присваивание и извлечение значение переменных. (html, txt)
a = value_of_variable b = 1+2
Листинг 2.7. Результат вывода значений переменных. (html, txt)
Значения формальных аргументов shell-процедур обозначаются как
$цифра
$0 - это имя интерпретируемой shell-процедуры. Если заданных при вызове команды фактических аргументов меньше, чем 9, "лишние" формальные аргументы получают пустые значения. О том, как добраться до фактических аргументов с номерами большими, чем 9, будет сказано ниже (см. управляющую конструкцию for и команду shift).
В качестве примера рассмотрим shell-процедуру, которая выдает на стандартный вывод свое имя и значения трех первых аргументов (см. листинг 2.8).
echo Имя команды: $0 echo Значение первого аргумента: $1 echo Значение второго аргумента: $2 echo Значение третьего аргумента: $3
Листинг 2.8.
Пример shell-процедуры. (html, txt)
Пусть приведенный текст помещен в файл с именем three_args. Тогда после выполнения команды (см. листинг 2.9) на экране появится ее результат (см. листинг 2.10).
three_args arg1 . - arg4
Листинг 2.9. Пример вызова shell-процедуры с аргументами. (html, txt)
Имя команды: three_args Значение первого аргумента: arg1 Значение второго аргумента: . Значение третьего аргумента: -
Листинг 2.10. Результат выполнения shell-процедуры. (html, txt)
Поскольку в тексте shell-процедуры упомянуты только первые 3 аргумента, значения аргументов с большими номерами (даже если они заданы, как в приведенном примере) не влияют на ее работу.
Команда
three_args arg1 --
выдаст другой результат (см. листинг 2.11):
Имя команды: three_args Значение первого аргумента: arg1 Значение второго аргумента: -- Значение третьего аргумента:
Листинг 2.11. Еще один результат выполнения shell-процедуры. (html, txt)
Значение третьего формального аргумента пусто.
Прежде чем начнется выполнение командной строки, т. е. будет вызвана заданная в ней команда, строку обрабатывает shell, выполняющий в ней некоторые подстановки. С одной из них - значения переменной вместо конструкции $имя - мы уже познакомились. Язык shell содержит ряд аналогичных конструкций. Рассмотрим их.
Там, где в командной строке встречается запись вида
${имя:-слово}
вместо нее подставляется значение переменной с указанным именем, если это значение непусто, в противном случае подставляется слово. Например, при наличии в shell-процедуре присваивания
initdir=${1:-/}
переменная initdir получит значение первого аргумента, если оно непусто. Если же процедуру вызвали без аргументов, значением initdir станет символ /. Подобные конструкции - удобный способ использования подразумеваемых значений.
Запись вида
${имя:=слово}
влечет присваивание нового значения (слово) переменной с заданным именем, только если ее старое значение не установлено или пусто (удобный способ присваивания переменным подразумеваемых значений).
Вместо всей конструкции в командную строку подставляется итоговое (непустое старое или присвоенное новое) значение переменной.
Конструкция
${имя:?слово}
предназначена для выдачи в стандартный протокол сообщения об ошибке, если значение переменной с заданным именем не установлено или пусто, после чего неинтерактивный shell завершает работу с ненулевым кодом возврата. Непустое значение подставляется в командную строку, и интерпретация командного файла продолжается обычным образом.
Вместо конструкции
${имя:+слово}
подставляется слово, если значение переменной с заданным именем непусто; в противном случае не подставляется ничего. Такой способ удобен для использования альтернативных значений.
Если в приведенных выше конструкциях опустить двоеточие, будет отменена проверка непустоты значения (останется лишь проверка того, установлено ли значение переменной с заданным именем. В остальном смысл конструкций остается прежним.
Shell содержит базовые средства для обработки цепочек символов. Так, вместо конструкции
${#имя}
подставляется число символов в значении переменной с заданным именем.
Предоставляется четыре конструкции для сопоставления с образцом:
${имя%слово} ${имя%%слово} ${имя#слово} ${имя##слово}
Во всех случаях слово рассматривается как образец (шаблон, см. далее); после сопоставления с ним подставляется значение переменной с заданным именем, из которого удален минимальный (максимальный) сопоставленный суффикс (префикс).
Приведем несколько примеров. Для их понимания достаточно знать, что шаблон * сопоставляется с произвольной цепочкой символов, в том числе с пустой.
После присваивания переменной
HOME=/home/galat
команда
echo ${#HOME}
выдаст 11.
Заменить в имени файла суффикс .f на .for можно с помощью команд, показанных в листинге 2.12:
f=file.f mv $f ${f%.f}.for
Листинг 2.12. Пример сопоставления с образцом. (html, txt)
Выдать первый элемент маршрутного имени файла (см. далее) можно с помощью команд, показанных в листинге 2.13:
f=маршрутное_имя echo ${f%%/*}
Листинг 2.13. Второй пример сопоставления с образцом. (html, txt)
Последний элемент выдается командой, приведенной в листинге 2.14:
echo ${f##*/}
Листинг 2.14. Третий пример сопоставления с образцом. (html, txt)
Перенаправление ввода/вывода
Командный интерпретатор shell ассоциирует с каждым открытым файлом так называемый дескриптор. Дескрипторы нумеруются десятичными целыми числами, начиная с нуля. Верхняя граница зависит от реализации, но, согласно стандарту POSIX, должны поддерживаться по крайней мере десять одновременно открытых файлов (с номерами дескрипторов от 0 до 9 включительно).Дескрипторы с номерами 0, 1 и 2 имеют специальный смысл. Они соответствуют стандартному вводу, стандартному выводу и стандартному протоколу.
Перед выполнением команды ее ввод и вывод могут быть перенаправлены, для чего используется специальная нотация, которую интерпретирует shell. Описанные ниже конструкции располагаются в любом месте простой команды, предшествуют команде или завершают ее и не передаются в качестве аргументов команды; если они завершают определение функции, то воздействуют на все команды тела, не производящие собственных перенаправлений.
Перенаправление ввода:
<слово
Использовать файл слово для стандартного ввода (дескриптор файла 0).
Перенаправление вывода с перезаписью:
>слово >|слово
Использовать файл слово для стандартного вывода (дескриптор файла 1). Имеющийся файл опустошается, а если файла нет, он создается. (При использовании первой разновидности перенаправления вывода в некоторых случаях попытка перезаписи существующего файла является ошибкой; см. далее описание опции -C команды set.)
Перенаправление вывода с дозаписью:
>>слово
Использовать файл слово для стандартного вывода. Если файл существует, то выводимая информация добавляется в конец (сначала производится поиск конца файла); в противном случае файл создается. Если любой из этих конструкций предшествует цифра, она определяет дескриптор (вместо подразумеваемых дескрипторов 0 или 1), который будет ассоциирован с файлом, указанным в конструкции. Например, строка
... 2>protocol
перенаправляет стандартный протокол (дескриптор 2) в файл по имени protocol.
Ввод и вывод могут перенаправляться не только в файл с заданным именем, но и в открытый файл с заданным дескриптором.
Для этого в описанных выше конструкциях в качестве слова следует употребить связку &номер_дескриптора:
<&номер_дескриптора >&номер_дескриптора
Например, чтобы перенаправить стандартный протокол туда же, куда назначен стандартный вывод, употребить конструкцию
... 2>&1
Если в качестве номера_дескриптора указан минус ('-'), соответствующий файл (стандартный ввод, вывод или явно заданный своим дескриптором файл) закрывается.
Shell позволяет открыть файл одновременно на чтение и запись при помощи конструкции
<>слово
Ей, как всегда, может предшествовать номер дескриптора для открываемого файла. По умолчанию используется 0, то есть стандартный ввод.
Приведем два примера. Пусть нужно измерить время выполнения некоторой команды, направив ее результаты в файл cmd.res, а данные о времени - в файл cmd.time. К цели ведет строка, приведенная в листинге 2.24:
time команда >cmd.res 2>cmd.time
Листинг 2.24. Пример перенаправления стандартного вывода и стандартного протокола. (html, txt)
Второй пример. Рассмотрим цикл, описанный в листинге 2.25:
i=0 while [ $i -lt 40 ] do > lost+found/g$i i=$(($i+1)) done rm lost+found/*
Листинг 2.25. Пример перенаправления стандартного вывода пустой команды. (html, txt)
С его помощью создается 40 файлов в каталоге lost+found, которые затем удаляются. Отметим, что перенаправляется стандартный вывод пустой команды, а в результате создается пустой файл.
(Поясним смысл приведенного фрагмента. При проверке и коррекции файловой системы утилитой fsck в каталог /lost+found помещаются непустые файлы, на которые нет ссылок. Сложность в том, что пока утилита fsck работает, ни один файл не должен расширяться, т. е. в каталоге /lost+found должны быть заранее заготовленные пустые места.)
Shell дает возможность размещать исходные данные в виде вставки в интерпретируемый командный файл, для чего употребляются многострочные конструкции:
<<слово вставка слово
и
<<-слово вставка слово
Их можно считать разновидностями перенаправления ввода.
На вход команды подается вставка, представляющая собой череду строк вплоть до слова, указанного при перенаправлении. Второй вариант отличается тем, что из строк вставки удаляются начальные символы табуляции.
В качестве примера использования вставок приведем файл ОС Linux /etc/tripwire/twinstall.sh, который содержит следующие фрагменты (см. листинг 2.26 и листинг 2.27):
cat << END_OF_TEXT A clear-text version of the Tripwire configuration file $TXT_CFG has been preserved for your inspection. It is recommended that you delete this file manually after you have examined it. END_OF_TEXT
Листинг 2.26. Пример использования вставки. (html, txt)
cat <
Листинг 2.27. Пример использования двух последовательных вставок. (html, txt)
В результате выполнения второго фрагмента будут выданы две строки:
Вставка 1 Вставка 2
Подстановка результатов выполнения команд
Если в командной строке встретилась цепочка символов, заключенная в обратные кавычки (`), она интерпретируется как команда, стандартный вывод которой подставляется вместо упомянутой конструкции. Говорят, что в этом случае производится подстановка результатов выполнения команды, а сами обратные кавычки называют символами подстановки.Эквивалентной синтаксической формой для подстановки результата выполнения команды является конструкция вида
$(команда)
Для выполнения заданной команды порождается процесс, в рамках которого работает еще один экземпляр командного интерпретатора.
Опишем несколько употребительных способов использования подстановки результатов. Пусть файл filelist содержит список имен других файлов и над совокупностью последних требуется проделать некоторое действие. Если в командную строку поместить конструкцию
... `cat filelist` ...
то это аналогично явному указанию в командной строке на все файлы из списка. (Утилита cat выдает на стандартный вывод содержимое файлов, заданных в качестве аргументов.) Пример подстановки результатов выполнения команды (выдача информации о файлах):
ls -l `cat filelist`
Нет нужды в том, чтобы каждая служебная программа умела читать свои аргументы из файла: имеются универсальный механизм подстановки результатов и команда cat.
Подстановка результатов выполнения команд может сочетаться с другими видами подстановок, например, значения вместо имени переменной. Так, после обработки конструкции
${x:-$(ls)}
команда ls выполнится только тогда, когда значение x не установлено или пусто; в противном случае выполнится команда $x, и ее результат займет место в командной строке.
В языке shell все значения считаются текстовыми. Значит, для выполнения операций с числами нужны особые средства. Вместо конструкции вида
$((выражение))
shell подставит результат вычисления этого выражения, что (с некоторой натяжкой) можно рассматривать как частный случай подстановки результатов выполнения команд. Например, после обработки строки
i=$(($i+1))
значение i (если оно было числом) увеличится на единицу.
Стандарт POSIX обязывает поддерживать арифметику длинных целых со знаком; константы (десятичные, восьмеричные, шестнадцатеричные) должны удовлетворять требованиям языка C.
Правила формирования и средства разбора командных строк
К теме командного языка и его интерпретатора логически примыкают правила формирования и средства разбора командных строк.В общем случае командная строка состоит из имени служебной программы (утилиты), опций, аргументов этих опций и, наконец, операндов команды.
Разработчики служебных программ должны руководствоваться следующими правилами.
Стандартным средством разбора опций и их аргументов, заданных в командной строке, является служебная программа getopts (чаще всего реализуемая как обычная встроенная команда shell):
getopts цепочка_имен_опций переменная [аргумент ...]
Как правило, shell-процедура, запущенная разбираемой командной строкой, вызывает утилиту getopts многократно (в цикле). При каждом таком вызове getopts помещает имя очередной выделенной ею опции в качестве значения заданной переменной. Место продолжения разбора (индекс в командной строке) запоминается в shell-переменной OPTIND, начальным значением которой служит единица.
Если у опции должен быть аргумент (что обозначается двоеточием после имени опции в цепочке имен опций), getopts выделяет его из командной строки и помещает в shell-переменную OPTARG.
Заданную при вызове getopts переменную, а также OPTIND и OPTARG следует использовать в качестве локальных, они не должны экспортироваться в окружение.
Если при вызове getopts указаны аргументы, разбираются они, а не элементы упомянутой выше командной строки.
Когда в командной строке обнаруживается опция, отсутствующая в цепочке имен опций, значением заданной переменной становится знак вопроса. Если первый символ цепочки имен опций - двоеточие, то в OPTARG помещается обнаруженный символ, иначе в стандартный протокол выдается диагностическое сообщение. С точки зрения shell-процедуры, вызвавшей getopts, это должно считаться ошибкой; с точки зрения getopts - нет.
По достижении конца опций утилита getopts возвращает положительный код завершения, а в OPTIND помещается номер первого элемента командной строки, не являющегося опцией.
Если переменой OPTIND присваивается единица, осуществляется (новый) разбор текущей командной строки (сформированной, быть может, с помощью специальной встроенной команды set) или текущих значений заданных при вызове getopts аргументов.
В качестве примера использования служебной программы getopts рассмотрим фрагмент shell-процедуры cmd, где производится разбор заданных при ее вызове опций (см. листинг 2.31).
Листинг 2.31. Пример использования служебной программы getopts. (html, txt)
Если вызвать shell-процедуру cmd любым из способов, показанных в листинге 2.32, будет выдан результат, приведенный в листинге 2.33.
cmd -a -o f1.o,f2.o file1 - cmd -ba -o f1.o,f2.o -- file1 - cmd -o f1.o,f2.o -b -a file1 -
Листинг 2.32. Возможные варианты вызова shell-процедуры, использующей служебную программу getopts. (html, txt)
Заданный флаг: a Аргумент опции o: f1.o,f2.o Остаток командной строки: file1 -
Листинг 2.33. Возможный результат работы shell-процедуры, использующей служебную программу getopts. (html, txt)

![]() | © 2003-2007 INTUIT.ru. Все права защищены. |
Листинг 2.
| ls -al /bin | wc -l |
| Листинг 2. 1. Пример конвейера. |
| Закрыть окно |
| ls -al | grep "Oct " |
| Листинг 2.2. Еще один пример конвейера. |
| Закрыть окно |
| ls -al | grep "Oct " | wc -l |
| Листинг 2.3. Пример трехступенчатого конвейера. |
| Закрыть окно |
| ls -Rl /dev | more |
| Листинг 2.4. Конвейер для поэкранного просмотра результатов. |
| Закрыть окно |
| ls -al | grep "Oct" | tee /tmp/tmpinf | wc -l |
| Листинг 2. 5. Четырехступенчатый конвейер. |
| Закрыть окно |
| a=value_of_variable b=1+2 echo a = $a echo b = $b |
| Листинг 2.6. Присваивание и извлечение значение переменных. |
| Закрыть окно |
| a = value_of_variable b = 1+2 |
| Листинг 2.7. Результат вывода значений переменных. |
| Закрыть окно |
| echo Имя команды: $ 0 echo Значение первого аргумента: $1 echo Значение второго аргумента: $2 echo Значение третьего аргумента: $3 |
| Листинг 2.8. Пример shell-процедуры. |
| Закрыть окно |
| three_args arg1 . - arg4 |
| Листинг 2.9. Пример вызова shell-процедуры с аргументами. |
| Закрыть окно |
| Имя команды: three_args Значение первого аргумента: arg1 Значение второго аргумента: . Значение третьего аргумента: - |
| Листинг 2.10. Результат выполнения shell-процедуры. |
| Закрыть окно |
| Имя команды: three_args Значение первого аргумента: arg1 Значение второго аргумента: -- Значение третьего аргумента: |
| Листинг 2.11. Еще один результат выполнения shell-процедуры. |
| Закрыть окно |
| f=file. f mv $f ${f%.f}.for |
| Листинг 2.12. Пример сопоставления с образцом. |
| Закрыть окно |
| f=маршрутное_имя echo ${f%%/*} |
| Листинг 2.13. Второй пример сопоставления с образцом. |
| Закрыть окно |
| echo ${f##*/} |
| Листинг 2.14. Третий пример сопоставления с образцом. |
| Закрыть окно |
| echo Идентификатор текущего процесса: $$ echo Имя команды: $ 0 echo Число фактических аргументов: $# echo Совокупность всех аргументов: $@ echo Значение первого аргумента: $1 echo Значение второго аргумента: $2 echo Значение третьего аргумента: $3 |
| Листинг 2.15. Усовершенствованная shell-процедура three_args. |
| Закрыть окно |
| Идентификатор текущего процесса: 3882 Имя команды: three_args Число фактических аргументов: 4 Совокупность всех аргументов: arg1 . - arg4 Значение первого аргумента: arg1 Значение второго аргумента: . Значение третьего аргумента: - |
| Листинг 2.16. Результат вызова усовершенствованной процедуры three_args. |
| Закрыть окно |
| export HISTSIZE="1000" export HOME="/home/galat" export LANG="C" export LESSCHARSET="koi8-r" export LOGNAME="galat" export MAIL="/var/spool/mail/galat" export TTY="/dev/ttyS4" export USER="galat" |
| Листинг 2.17. Возможные результаты выполнения команды export -p. |
| Закрыть окно |
| echo Идентификатор текущего процесса: $$ echo Имя команды: $ 0 echo Число фактических аргументов: $# echo Совокупность всех аргументов: $@ i=1 for arg do echo Значение аргумента номер ${i}: $arg i=$(($i+1)) done |
| Листинг 2.18. Еще одно усовершенствование shell-процедуры three_args. |
| Закрыть окно |
| if [ -s ${f} ] then /bin/sh ${f} start fi |
| Листинг 2.19. Пример условного оператора. |
| Закрыть окно |
| case "$1" in start) start ;; stop) stop ;; reload | restart) restart ;; condrestart) if [ -f /var/lock/subsys/atd ] then restart fi ;; *) echo $"Usage: $0 {start | stop | restart | condrestart}" exit 1 esac |
| Листинг 2.20. Пример оператора выбора. |
| Закрыть окно |
| echo $# $1 f ( ) { echo $# $1 } f a b f b echo $# $1 |
| Листинг 2.21. Пример определения и вызова функции. |
| Закрыть окно |
| for f in /etc/rc$runlevel.d/S* do if [ -s ${f} ] then /bin/sh ${f} start fi done |
| Листинг 2.22. Пример сочетания управляющих конструкций с генерацией имен файлов. |
| Закрыть окно |
| for f in *. f do mv $f `basename $f .f`.for done |
| Листинг 2.23. Пример подстановки результатов команды как части слова. |
| Закрыть окно |
| time команда >cmd.res 2>cmd.time |
| Листинг 2.24. Пример перенаправления стандартного вывода и стандартного протокола. |
| Закрыть окно |
| i= 0 while [ $i -lt 40 ] do > lost+found/g$i i=$(($i+1)) done rm lost+found/* |
| Листинг 2.25. Пример перенаправления стандартного вывода пустой команды. |
| Закрыть окно |
| cat << END_OF_TEXT A clear- text version of the Tripwire configuration file $TXT_CFG has been preserved for your inspection. It is recommended that you delete this file manually after you have examined it. END_OF_TEXT |
| Листинг 2.26. Пример использования вставки. |
| Закрыть окно |
|
cat < |
| Листинг 2.27. Пример использования двух последовательных вставок. |
| Закрыть окно |
| while [ "$1" ] do [ -f $1 ] && echo $ 1 shift done |
| Листинг 2.28. Пример использования специальной встроенной команды shift. |
| Закрыть окно |
| argv1="$1" set `/sbin/runlevel` runlevel=$2 previous=$1 |
| Листинг 2.29. Пример использования специальной встроенной команды set. (административная утилита runlevel выдает предыдущий и текущий уровни выполнения системы). |
| Закрыть окно |
|
#include |
| Листинг 2.30. Описание функций system() и popen(). |
| Закрыть окно |
| while getopts :abo: c do case $c in a | b) FLAG=$c;; o) OARG=$OPTARG;; ?) printf "Использование: %s: [-a | -b] [-o выходной_файл] [аргумент ...]\n" $0 exit 1;; esac done shift $(($OPTIND - 1)) printf "Заданный флаг: %s\n" $FLAG printf "Аргумент опции o: %s\n" $OARG printf "Остаток командной строки: %s\n" "$*" |
| Листинг 2.31. Пример использования служебной программы getopts. |
| Закрыть окно |
| cmd -a -o f1.o,f2.o file1 - cmd -ba -o f1.o,f2.o -- file1 - cmd -o f1.o,f2.o -b -a file1 - |
| Листинг 2.32. Возможные варианты вызова shell-процедуры, использующей служебную программу getopts. |
| Закрыть окно |
| Заданный флаг: a Аргумент опции o: f1.o,f2. o Остаток командной строки: file1 - |
| Листинг 2.33. Возможный результат работы shell-процедуры, использующей служебную программу getopts. |
| Закрыть окно |
|
#include |
| Листинг 2.34. Описание функции getopt() и ассоциированных с ней внешних переменных. |
| Закрыть окно |
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа разбирает опции вызвавшей ее командной строки */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include |
| Листинг 2.35. Пример использования функции getopt(). |
| Закрыть окно |
Служебные переменные языка shell
Значения некоторых переменных устанавливаются самим языком shell. Перечислим эти переменные и опишем их предназначение.#
Количество фактических аргументов (десятичное).
-
Флаги (однобуквенные опции), указанные при запуске shell или установленные посредством команды set (см. далее).
?
Десятичное значение, возвращенное предыдущей синхронно выполненной командой.
$
Идентификатор процесса, в рамках которого выполняется shell.
!
Идентификатор последнего асинхронно запущенного процесса.
*, @
Совокупность всех фактических аргументов (начиная с $1), разделенных пробелами.
Напомним: чтобы получить значения этих переменных, перед ними нужно поставить знак $.
Значения $@ и $* имеют некоторые тонкие различия, на которых мы, однако, останавливаться не будем.
Несколько усложним процедуру three_args, чтобы продемонстрировать только что описанные возможности (см. листинг 2.15).
echo Идентификатор текущего процесса: $$ echo Имя команды: $0 echo Число фактических аргументов: $# echo Совокупность всех аргументов: $@ echo Значение первого аргумента: $1 echo Значение второго аргумента: $2 echo Значение третьего аргумента: $3
Листинг 2.15. Усовершенствованная shell-процедура three_args. (html, txt)
Теперь, вызывая усовершенствованную процедуру three_args, выполним командную строку
three_args arg1 . - arg4
Тогда на экране появится примерно следующее (см. листинг 2.16):
Идентификатор текущего процесса: 3882 Имя команды: three_args Число фактических аргументов: 4 Совокупность всех аргументов: arg1 . - arg4 Значение первого аргумента: arg1 Значение второго аргумента: . Значение третьего аргумента: -
Листинг 2.16. Результат вызова усовершенствованной процедуры three_args. (html, txt)
Управляющие конструкции
Среди прочих язык shell содержит следующие управляющие конструкции.Оператор цикла for:
for имя [in слово ...] do список done
При каждой итерации переменная имя принимает следующее значение из набора
in слово ...
Если конструкция in слово ... опущена, то список выполняется для каждого формального аргумента.
Условный оператор:
if список_1 then список_2 [elif список_3 then список_4] ... [else список_5] fi
Выполняется список_1. Если код его завершения 0, то выполняется список_2, в противном случае - список_3, и если код его завершения 0, то выполняется список_4 и т.д. Если же коды завершения всех списков, использованных в качестве условий, оказались ненулевыми, выполняется else-часть (список_5). Если else-часть отсутствует и ни одна then-часть не выполнялась, возвращается нулевой код завершения.
Оператор цикла while (until):
while список_1 do список_2 done
Пока код завершения последней команды списка_1 есть 0, выполняются команды списка_2. При замене служебного слова while на until условие продолжения цикла меняется на противоположное. Если команды из списка_2 не выполнялись вообще, код завершения устанавливается равным нулю.
Оператор выбора:
case слово in [шаблон [| шаблон] ...) список ;;] ... esac
Выполняется список, соответствующий первому из шаблонов, успешно сопоставленных со словом. Формат шаблона аналогичен используемому для генерации имен файлов (см. далее).
Определение функции:
имя () открывающая_скобка список закрывающая_скобка
Определяется функция с заданным именем. Список является ее телом, которое окружают круглые или фигурные скобки. Для вызова функции используется обычный синтаксис команды:
имя [аргумент ...]
Если тело функции заключено в фигурные скобки, она выполняется в рамках текущего процесса; в случае круглых скобок порождается отдельный процесс. На время выполнения функции аргументы $1, $2, ..., а также служебная переменная # получают новые значения, определенные аргументами команды вызова. Затем восстанавливаются старые значения.
Приведем примеры использования управляющих конструкций. Сначала усовершенствуем процедуру three_args, чтобы она выдавала значения всех, а не только первых трех аргументов (см. листинг 2.18):
echo Идентификатор текущего процесса: $$ echo Имя команды: $0 echo Число фактических аргументов: $# echo Совокупность всех аргументов: $@ i=1 for arg do echo Значение аргумента номер ${i}: $arg i=$(($i+1)) done
Листинг 2.18. Еще одно усовершенствование shell-процедуры three_args. (html, txt)
| ! | Унарная операция отрицания |
| -a | Логическое И. |
| -o | Логическое ИЛИ. |
Приведем пример использования управляющей конструкции if. В процессе загрузки практически любой разновидности ОС Unix выполняются строки следующего или близкого вида (см. листинг 2.19):
if [ -s ${f} ] then /bin/sh ${f} start fi
Листинг 2.19. Пример условного оператора.
Если файл, имя которого является значением переменной f, существует и имеет ненулевой размер, он выполняется с аргументом start.
В качестве примера употребления конструкции case приведем еще один фрагмент, типичный для процесса загрузки системы (см. листинг 2.20):
case "$1" in start) start ;; stop) stop ;; reload | restart) restart ;; condrestart) if [ -f /var/lock/subsys/atd ] then restart fi ;; *) echo $"Usage: $0 {start | stop | restart | condrestart}" exit 1 esac
Листинг 2.20. Пример оператора выбора.
Известные значения первого аргумента распознаются, в ответ на все прочие (шаблон *) сообщается, как пользоваться данной shell-процедурой.
Следующий пример иллюстрирует определение и вызов функции (см. листинг 2.21).
echo $# $1 f ( ) { echo $# $1 } f a b f b echo $# $1
Листинг 2.21. Пример определения и вызова функции.
Если данный текст хранится в файле g, то по окончании выполнения команды (вызов shell-процедуры, содержащей функцию)
g c d e
будет выдан следующий результат:
3 c 2 a 1 b 3 c
Встроенные команды
Мы уже отмечали, что подавляющее большинство команд разыскивается в списке поиска и выполняется языком shell в рамках порожденных процессов. Однако для некоторых команд - либо по соображениям эффективности, либо потому, что требуемую функциональность трудно достичь в рамках отдельного процесса - целесообразно сделать исключение и реализовать их как встроенные, их выполняет сам shell. Очевидным примером встроенной команды служит export - пометить переменную как глобальную, входящую в окружение процесса проще внутренними методами.Встроенные команды подразделяются на обычные и специальные. С точки зрения приложений первые ничем (кроме, быть может, эффективности обработки) не отличаются от невстроенных утилит. В частности, они могут фигурировать в качестве программы процесса. Специальные встроенные команды этим свойством обладать не обязаны. Кроме того, у них есть еще два отличия:
Перечислим некоторые из специальных встроенных команд.
. файл
Shell читает и выполняет команды из файла, затем возобновляется чтение со стандартного ввода; при поиске файла используется значение переменной PATH. Иными словами, специальная команда . позволяет выполнить любую команду в рамках текущего процесса.
eval [аргумент ...]
Выполнить команду, заданную аргументами eval.
exec [аргумент ...]
Сменить программу процесса: в рамках текущего процесса команда, заданная аргументами exec, заменяет shell. В качестве аргументов могут указываться спецификации ввода/вывода, и, если нет никаких других аргументов, будет лишь перенаправлен ввод/вывод текущего процесса shell.
exit [код_завершения]
Завершить выполнение shell с указанным кодом. При отсутствии аргумента код завершения определяется последней выполненной командой. Чтение символа конца файла также приводит к завершению shell.
shift [n]
Формальные аргументы, начиная с (n+1)-го, переименовываются в $1 и т.д. По умолчанию n=1.
К числу обычных встроенных команд относятся:
cd [каталог]
Сделать текущим заданный каталог. Если каталог не указан, используется значение переменной окружения HOME.
pwd
Выводит имя текущего каталога.
read [переменная ...]
Со стандартного ввода читается одна строка и делится на поля; первое поле присваивается первой переменной, второе - второй и т.д., причем последовательность всех оставшихся полей присваивается последней переменной. Исходная строка имеет продолжение, если в конце ее стоит последовательность \перевод_строки. Символы, отличные от перевода строки, также могут быть экранированы с помощью \, который удаляется перед присваиванием полей. Возвращается нулевой код завершения, если только не встретился конец файла.
Некоторые пояснения. С помощью команды exec осуществляется смена программы текущего процесса. Например, если пользователь привык работать в командном интерпретаторе bash, он может перейти туда по команде
exec bash
При этом обычный shell бесследно исчезнет.
Аргументы специальной встроенной команды eval обрабатываются дважды: когда исходную строку интерпретирует shell и когда работает сама eval. Так, после выполнения строк
a=1 b=a eval c='$'$b echo $c
на стандартный вывод будет выдана единица, поскольку аргументом eval будет оператор присваивания c=$a.
Если файл data содержит строку
1 2 3 4 5
то после выполнения специальной встроенной команды read
read a b c echo $c $b $a
на стандартный вывод будет выдан следующий результат:
3 4 5 2 1
Текст "3 4 5" (остаток строки) стал значением переменной c.
Команду shift используют при последовательной обработке аргументов shell-процедур. Например, если требуется выдать аргументы, которые являются именами существующих обычных файлов, употребляют цикл вида (см. листинг 2.28):
while [ "$1" ] do [ -f $1 ] && echo $1 shift done
Листинг 2.28. Пример использования специальной встроенной команды shift. (html, txt)
Важную роль играет еще одна специальная встроенная команда - set. Она используется для изменения режима работы shell и для присваивания новых значений формальным аргументам. Сразу же отметим, что наивное присваивание
цифра=слово
shell трактует как запуск программы с именем цифра=слово, т. е. оно не позволяет изменить значение формального аргумента.
Команда set записывается в виде
set [опция ...] [аргумент ...]
Из ее опций упомянем следующие:
-a
Экспортировать в окружение все переменные, которым производится присваивание.
-C
Защищать существующие файлы от перезаписи при перенаправлении вывода посредством конструкции ">" (см. выше раздел "Перенаправление ввода/вывода").
+o
Выдать на стандартный вывод текущие опции языка shell в виде команд, которые устанавливают те же значения и которые можно подать ему на вход.
-x
Устанавливает режим трассировки после выполнения всех подстановок в командных строках, но до выполнения они будут выдаваться в стандартный протокол. Опция +x выключает трассировку. Стандарт не специфицирует, будет ли трассироваться команда set +x.
Если в команде set заданы аргументы, они станут новыми значениями $1, $2 и т.д. Типичный пример использования команды set содержится в одном из файлов ОС Linux (/etc/rc.d/rc) выполняемых при загрузке системы (см. листинг 2.29):
argv1="$1" set `/sbin/runlevel` runlevel=$2 previous=$1
Листинг 2.29. Пример использования специальной встроенной команды set. (административная утилита runlevel выдает предыдущий и текущий уровни выполнения системы). (html, txt)
Возможностью, не обязательной с точки зрения стандарта POSIX-2001 (отнесенной к расширению "Мобильность пользователей", UP), но весьма полезной на практике, является механизм синонимов для имен простых команд. Синонимы задаются с помощью обычной встроенной команды alias:
alias [имя[=синоним] ...]
Когда shell выделяет в командной строке слово, расположенное вместо имени простой команды, он проверяет, не является ли оно именем синонима, и при положительном ответе заменяет его значением, заданным командой alias и представляющим собой цепочку символов.
Например, если хочется, чтобы команда ls по умолчанию выдавала информацию о файлах, имена которых начинаются с точки, можно воспользоваться спецификацией для определения синонима:
alias ls="ls -a"
Если подставленный синоним оканчивается пробелом, то анализируется следующее слово командной строки; если и оно оказывается именем синонима, подстановки продолжаются. Например, если синоним для ls задать в виде строки с завершающим пробелом
alias ls="ls -a "
и дополнительно ввести еще одно определение синонима
alias l="-Rl"
то команда, использующая синоним с завершающим пробелом,
ls l
будет раскрыта в строку
ls -a -Rl
и выдаст в длинном формате информацию о всех файлах текущего каталога и его подкаталогов.
Команда alias без аргументов выдает список имеющихся синонимов.
Вызов командного интерпретатора shell
Вызов командного интерпретатора shell осуществляется командойsh [опция...] [командный_файл [аргумент ...]]
или
sh -c [опция...] командная_цепочка [имя_команды [аргумент ...]]
или
sh -s [опция...] [аргумент ...]
В первом случае интерпретируется заданный командный файл (или содержимое стандартного ввода, если файл не указан), во втором - цепочка символов, в третьем команды читаются со стандартного ввода.
Большинство опций команд sh и set (см. выше) совпадают. Им может предшествовать не только знак минус, но и плюс, что означает инвертирование их смысла. Из специфических опций команды sh выделим -i, предписывающую считать shell интерактивным. Shell будет интерактивным и тогда, когда команды читаются со стандартного ввода, направленного, как и стандартный протокол, на терминал.
Заданные в командной строке аргументы становятся значениями фактических аргументов $1, $2 и т.д. Если при наличии опции -c задано имя_команды, то в результате интерпретации командной цепочки оно становится значением $0.
Пример. Команда
sh -c 'echo $0 $1 $2' a b c
выдаст на стандартный вывод
a b c
Читателю предлагается самостоятельно определить, что выдаст на стандартный вывод похожая команда
sh -c "echo $0 $1 $2" a b c
(вместо одиночных кавычек для экранирования пробелов использованы двойные), и объяснить полученный результат.
Командный интерпретатор можно вызвать и из программы на языке C, воспользовавшись функциями system() или popen() (см. листинг 2.30):
#include
Листинг 2.30. Описание функций system() и popen(). (html, txt)
Аргумент command язык shell трактует как командную цепочку в вызове
sh -c command
и может содержать имя и аргументы любой выполнимой программы. При обращении к system() вызвавшая программа ожидает завершения выполнения переданной команды, а затем продолжает выполнение со следующего выполняемого оператора. Возвращаемое функцией system() значение - код завершения shell. Пример вызова функции system():
code = system ("cd /usr/bin; ./ls > /tmp/lst");
Функция popen(), как и system(), вызывает выполнение указанной команды. Отличие в том, что при использовании функции popen() создается канал между вызвавшей ее программой и командой. Аргумент mode определяет, что программа будет делать с этим каналом: читать ("r") или писать ("w").
Программирование в стандарте POSIX
Данные, ассоциированные с пользователем
Операционная система, соответствующая стандарту POSIX, должна поддерживать базу данных пользователей, в которой о каждом из них хранится по крайней мере следующая информация:Поясним смысл перечисленных элементов данных.
Каждый зарегистрированный пользователь ОС имеет имя, которое он указывает для целей идентификации при входе в систему. После проведения идентификации и, как правило, аутентификации пользователя, с ним ассоциируются (неотрицательные) числовые идентификаторы пользователя и начальной группы. В отличие от имен, ОС оперирует ими во всех случаях, кроме первоначальной идентификации. Затем запускается начальная программа пользователя (например, командный интерпретатор shell) с указанным начальным рабочим каталогом.
Поля начального рабочего каталога и начальной программы пользователя могут быть пустыми; в таком случае их трактовка зависит от реализации. Обычно в системе определена подразумеваемая начальная программа, в качестве которой обычно используется /bin/sh.
Пользователи объединяются в группы; каждый пользователь является членом хотя бы одной из них. Для групп также существует база данных, ее записи содержат по крайней мере следующие поля:
В базе данных пользователей указывается идентификатор начальной группы, в нее пользователь попадает сразу после входа в систему. В процессе работы возможен переход в другую группу (см. далее описание утилиты newgrp), однако на эти переходы наложены ограничения в виде списка возможных членов группы.
С объектно-ориентированной точки зрения можно считать, что класс "пользователь" предоставляет один метод - начальную программу. Его можно применить для программирования определенных услуг. Например, если нужно дать возможность любому человеку (не обязательно зарегистрированному пользователю), оказавшемуся рядом со свободным терминалом, узнать текущие дату и время, заводят пользователя date с начальной программой /bin/date.
Опросить ассоциированные с пользователем данные (точнее, идентификаторы и имена пользователя и начальной группы, а также всех групп, членами которых ему разрешено быть, - так называемых дополнительных групп) позволяет служебная программа
id [имя_пользователя]
Если имя_пользователя опущено, выдается информация о текущем пользователе и его текущей группе.
Пример использования служебной программы id. Команда
id root
может выдать на стандартный вывод следующий результат:
uid=0(root) gid=0(root) groups=0(root), 1(bin),2(daemon),3(sys),4(adm),6(disk), 10(wheel)
Числовой идентификатор 0 характеризует суперпользователя, который в большинстве Unix-систем не подвержен контролю прав доступа.
Входное имя текущего пользователя можно узнать также с помощью утилиты
logname
и функции getlogin():
#include
Отметим, что одному идентификатору пользователя может соответствовать несколько записей в базе данных пользователей, различающихся входными именами и, возможно, другими атрибутами; logname и getlogin() выдают имя, под которым начат текущий сеанс работы с ОС.
Над базой данных пользователей определены операции поиска по идентификатору или имени пользователя, реализуемые, соответственно, функциями getpwuid() и getpwnam() (см. пример 3.1):
#include
Листинг 3.1. Описание функций getpwuid() и getpwnam(). (html, txt)
По стандарту структура passwd должна содержать по крайней мере следующие поля, соответствующие описанным выше обязательным элементам базы данных пользователей:
char *pw_name; /* Имя пользователя */
uid_t pw_uid; /* Числовой идентификатор пользователя */ gid_t pw_gid; /* Числовой идентификатор начальной группы */ char *pw_dir; /* Начальный рабочий каталог */ char *pw_shell; /* Начальная программа пользователя */
Типы uid_t и gid_t определяются в заголовочном файле
Приведем пример выдачи информации о текущем пользователе и пользователе root с идентификатором 0 (см.
пример 3.2).
Листинг 3.2. Пример работы с базой данных пользователей. (html, txt)
По окончании работы этой программы может быть получен следующий результат (см. пример 3.3):
Листинг 3.3. Возможный результат работы с базой данных пользователей. (html, txt)
Аналогичные функции имеются для поиска в базе данных групп - getgrgid() и getgrnam() (см. пример 3.4):
#include
Листинг 3.4. Описание функций getgrgid() и getgrnam(). (html, txt)
Структура group обязана содержать поля
char *gr_name;/* Имя группы */ gid_t gr_gid; /* Числовой идентификатор группы */ char **gr_mem; /* Указатель на ограниченный пустым указателем массив символьных указателей на имена пользователей, которым разрешено становиться членами данной группы */
Применение функции getgrgid() отражено в пример 3.5.
Листинг 3.5. Пример работы с базой данных групп. (html, txt)
Приведенная в качестве примера программа может привести к результату, показанному в пример 3.6:
Пользователи, включенные в группу с идентификатором 1: root bin daemon
Листинг 3.6. Возможный результат работы с базой данных групп. (html, txt)
Для смены текущей группы пользователя предназначена служебная программа newgrp (стандарт POSIX-2001 относит ее к числу необязательных, входящих в расширение "Мобильность пользователей", UP):
newgrp [-l] [группа]
Группа, в которую осуществляется переход, задается именем или числовым идентификатором. Будучи вызванной без аргументов, утилита newgrp возвращает пользователя в его начальную группу, заданную в базе данных пользователей.
При смене группы порождается новый процесс, в рамках которого запускается новый экземпляр командного интерпретатора, наследующий у своего предшественника текущий каталог и значения переменных окружения. При указании опции -l окружение формируется так, будто пользователь заново вошел в систему.
Если пользователь не входит в список возможных членов новой группы, при переходе в нее может запрашиваться пароль, однако в стандарте POSIX-2001 этот аспект считается зависящим от реализации.Более того, отмечается, что в базе данных групп нет удобных способов задания паролей, поэтому их использование в приложениях не приветствуется, а со временем пароли групп могут стать ненужными.
Листинг 3.4. Описание функций getgrgid() и getgrnam().
Структура group обязана содержать поля
char *gr_name;/* Имя группы */ gid_t gr_gid; /* Числовой идентификатор группы */ char **gr_mem; /* Указатель на ограниченный пустым указателем массив символьных указателей на имена пользователей, которым разрешено становиться членами данной группы */
Применение функции getgrgid() отражено в пример 3.5.
#include
Листинг 3.5. Пример работы с базой данных групп.
Приведенная в качестве примера программа может привести к результату, показанному в пример 3.6:
Пользователи, включенные в группу с идентификатором 1: root bin daemon
Листинг 3.6. Возможный результат работы с базой данных групп.
Для смены текущей группы пользователя предназначена служебная программа newgrp (стандарт POSIX-2001 относит ее к числу необязательных, входящих в расширение "Мобильность пользователей", UP):
newgrp [-l] [группа]
Группа, в которую осуществляется переход, задается именем или числовым идентификатором. Будучи вызванной без аргументов, утилита newgrp возвращает пользователя в его начальную группу, заданную в базе данных пользователей.
При смене группы порождается новый процесс, в рамках которого запускается новый экземпляр командного интерпретатора, наследующий у своего предшественника текущий каталог и значения переменных окружения.При указании опции -l окружение формируется так, будто пользователь заново вошел в систему.
Если пользователь не входит в список возможных членов новой группы, при переходе в нее может запрашиваться пароль, однако в стандарте POSIX-2001 этот аспект считается зависящим от реализации. Более того, отмечается, что в базе данных групп нет удобных способов задания паролей, поэтому их использование в приложениях не приветствуется, а со временем пароли групп могут стать ненужными.
struct passwd
|
#include |
| Листинг 3.1. Описание функций getpwuid() и getpwnam(). |
| Закрыть окно |
|
#include |
| Листинг 3.2. Пример работы с базой данных пользователей. |
| Закрыть окно |
| Информация о текущем пользователе Имя пользователя: galat Идентификатор пользователя: 108 Идентификатор группы: 3 Начальный каталог: /home/galat Начальная программа: /bin/sh Информация о пользователе root Имя пользователя: root Идентификатор пользователя: 0 Идентификатор группы: 0 Начальный каталог: /root Начальная программа: /bin/bash |
| Листинг 3.3. Возможный результат работы с базой данных пользователей. |
| Закрыть окно |
|
#include |
| Листинг 3.4. Описание функций getgrgid() и getgrnam(). |
| Закрыть окно |
|
#include |
| Листинг 3.5. Пример работы с базой данных групп. |
| Закрыть окно |
| Пользователи, включенные в группу с идентификатором 1: root bin daemon |
| Листинг 3.6. Возможный результат работы с базой данных групп. |
| Закрыть окно |
| who | while read a b c do write $a $b << ! $1 ! done |
| Листинг 3.7. Пример использования утилит who и write. |
| Закрыть окно |
| address=... . . . mailx $address << ! . . . текст письма . . . ! |
| Листинг 3.8. Пример использования вставки для формирования письма. |
| Закрыть окно |
Служебные программы, обслуживающие взаимодействие пользователей
Активными мы будем называть пользователей, работающих в системе в некоторый момент времени.Чтобы узнать, какие пользователи активны и за какими терминалами они работают, можно воспользоваться служебной программой
who
(заметим, что стандарт POSIX-2001 трактует ее как необязательную, входящую в расширение "Мобильность пользователей").
Выдача утилиты who может выглядеть, например, так (правый столбец означает время входа в систему):
galat ttyS4 Aug 22 12:41 kost ttyS6 Aug 22 10:09
К той же дополнительной категории, что и who, принадлежат утилиты write, talk и mesg (а также описанная выше служебная программа newgrp).
После установления соединения утилита
write имя_пользователя [терминал]
позволяет построчно пересылать стандартный ввод отправителя на терминал пользователя-получателя. Аргументы имя_пользователя и терминал задаются в том виде, как их выводит служебная программа who. Необязательный аргумент [терминал] нужен в тех случаях, когда пользователь-получатель вошел в систему с нескольких терминалов.
Утилиту
talk имя_пользователя [терминал]
можно рассматривать как более современный аналог write, поскольку она имеет экранный интерфейс и поддерживает двустороннее взаимодействие активных пользователей.
С помощью служебной программы
mesg [y|n]
пользователь может разрешить или запретить установление соединений со своим терминалом. При вызове без аргументов mesg выдает текущий статус терминала.
Приведем пример употребления описанных служебных программ. Рассылку сообщения-аргумента всем активным пользователям можно реализовать посредством shell-процедуры (см. пример 3.7).
who | while read a b c do write $a $b << ! $1 ! done
Листинг 3.7. Пример использования утилит who и write. (html, txt)
Обратим внимание на использование во вставке значения аргумента shell-процедуры.
Базовым средством обеспечения почтового взаимодействия между пользователями, согласно стандарту POSIX-2001, является служебная программа mailx. Она позволяет и отправлять, и получать письма.
В первом случае ее следует вызывать командной строкой
mailx [-s тема] адрес ...
во втором -
mailx -e
или
mailx [опция ...]
или
mailx -f [опция ...] [почтовый_ящик]
В процессе отправки текст письма читается со стандартного ввода. При получении всеми указанными выше способами, кроме последнего, проверяется системный почтовый ящик, выделенный текущему пользователю, а при наличии опции -f в качестве почтового ящика используется явно заданный файл. Опция -e предписывает только проверить почтовый ящик и, если он не пуст, вернуть код успеха.
В режиме получения можно отправлять письма и управлять содержимым почтового ящика (для чего утилита mailx предоставляет весьма богатый набор команд), однако поддержку этого режима стандарт POSIX-2001 относит к числу необязательных возможностей.
При отправке писем shell-процедурами часто пользуются вставками (см. пример 3.8):
address=... . . . mailx $address << ! . . . текст письма . . . !
Листинг 3.8. Пример использования вставки для формирования письма. (html, txt)
Разумеется, интерактивные пользователи редко применяют утилиту mailx напрямую; для работы с почтой практически во всех ОС имеются средства с более дружественным интерфейсом.
Программирование в стандарте POSIX
Изменение атрибутов файлов и текущей позиции в файловой иерархии
Для смены текущего каталога (т. е. начальной точки маршрутов, не начинающихся символом /) служат упоминавшаяся ранее обычная встроенная команда языка shellcd [-L | -P] [целевой_каталог] cd -
и функция chdir():
#include
Команда cd без аргументов осуществляет переход в домашний каталог пользователя, заданный переменной окружения HOME. Если аргументом является минус, выполняются действия, показанные в пример 4.22: осуществляется переход в каталог, ранее бывший текущим, и в случае успеха выводится его абсолютное маршрутное имя.
cd "$OLDPWD" && pwd
Листинг 4.22. Действия, выполняемые по команде cd -. (html, txt)
Алгоритм работы команды cd, как ни странно, довольно сложен и содержит целый ряд тонкостей. Сначала, если целевой каталог задан относительным маршрутным именем, выполняется преобразование к абсолютному формату. При этом:
Затем выполняется раскрытие символьных ссылок и устранение имен "точка" и "точка-точка" ("точка" уничтожается вместе со следующим за ней символом /, "точка-точка" - вместе с предыдущим компонентом, отличным от "точки-точки", и символом / между ними). Опции команды cd влияют на порядок выполняемых действий. По опции -P сначала раскрываются символьные ссылки. Это значит, что "точка-точка" трактуется как физический надкаталог (каталог, вышележащий по отношению к указуемому файлу). При наличии (подразумеваемой) опции -L порядок действий обратный; в результате "точка-точка" обозначает логический надкаталог (каталог, вышележащий по отношению к символьной ссылке, а не к указуемому файлу).
Наконец, выполняется переход по результирующему маршруту.
В случае успешной смены текущего каталога соответственно изменяются значения переменных окружения $OLDPWD (текущий каталог непосредственно перед выполнением команды cd) и $PWD (текущий каталог после выполнения команды cd).
Рассмотрим пример выполнения команды cd с разными опциями (см. пример 4.23).
Возможный результат показан в пример 4.24.
ls -dl /usr/tmp /var/tmp cd /usr/tmp pwd pwd -P cd .. pwd cd - cd -P .. pwd
Пример 4.23. Пример выполнения команды cd с разными опциями (html, txt)
Пример 4.24. результат выполнения команд cd с разными опциями (html, txt)
Можно видеть, что /usr/tmp является символьной ссылкой на каталог /var/tmp. При варьировании опций команды pwd каталог /usr/tmp по-разному отображается в качестве текущего (напомним, опция -P команды pwd вызывает раскрытие символьных ссылок). По-разному срабатывает и команда cd с целевым каталогом "точка-точка", опцией -P и без таковой. В результате видно различие между физическим и логическим надкаталогами символьной ссылки.
Для изменения атрибутов файлов служат утилиты
chown [-R] [-H | -L | -P ] владелец[:группа] файл ...
(смена владельца и, быть может, владеющей группы файла) и
chmod [-R] изменение_режима файл ...
(модификация режима файла), а также аналогичные им по назначению и именам функции (см. пример 4.25).
#include
Листинг 4.25. Описание функций chown(), fchown(), chmod() и fchmod(). (html, txt)
При обращении к утилите chown владелец и группа задаются именами или числовыми идентификаторами. Реализации должны сначала произвести поиск заданных аргументов как имен в базах данных пользователей и, если нужно, групп и извлечь оттуда соответствующие числовые идентификаторы; если поиск окажется неудачным, аргументы рассматриваются как идентификаторы.
Изменить владельца может только нынешний владелец файла или пользователь, "имеющий соответствующие привилегии" (см. выше раздел "Основные понятия и идеи стандарта POSIX"); некоторые реализации предоставляют подобное право только привилегированным пользователям. Обычно приходится изменять владельца и/или группу после переноса файлов с другого компьютера с иным соответствием числовых идентификаторов и имен.
Опции -R, -H и -L имеют в целом тот же смысл, что и для утилиты ls (см. выше): первая предписывает рекурсивный обход встретившихся подкаталогов, две другие - выборочное или полное раскрытие символьных ссылок, указывающих на каталоги. Опция -P означает, что изменения относятся к самим символьным ссылкам.
Если при использовании функций chown() и fchown() меняется лишь владелец, то аргумент group задается равным (gid_t) (-1); при смене только группы идентификатор владельца следует задать как (uid_t) (-1).
Изменить владельца может только нынешний владелец файла или пользователь, "имеющий соответствующие привилегии" (см. выше раздел "Основные понятия и идеи стандарта POSIX"); некоторые реализации предоставляют подобное право только привилегированным пользователям. Обычно приходится изменять владельца и/или группу после переноса файлов с другого компьютера с иным соответствием числовых идентификаторов и имен.
Опции -R, -H и -L имеют в целом тот же смысл, что и для утилиты ls (см. выше): первая предписывает рекурсивный обход встретившихся подкаталогов, две другие - выборочное или полное раскрытие символьных ссылок, указывающих на каталоги. Опция -P означает, что изменения относятся к самим символьным ссылкам.
Если при использовании функций chown() и fchown() меняется лишь владелец, то аргумент group задается равным (gid_t) (-1); при смене только группы идентификатор владельца следует задать как (uid_t) (-1).
Задавая аргумент изменение_режима служебной программы chmod владельца файла обозначают буквой u, владеющую группу - буквой g, прочих пользователей - o. Добавлению прав соответствует знак +, их удаление помечается знаком -. Знак = обозначает буквальное задание прав (для указанной категории пользователей устанавливается указанный режим доступа). После букв, определяющих категорию пользователей, и знака операции следуют сами добавляемые (удаляемые, устанавливаемые) режимы доступа - обычно r, w и/или x; можно указать несколько подобных связок, разделяя их запятыми (без пробелов).
Пусть, например, все могут читать и изменять файл myfile. Чтобы отнять право на запись у владеющей группы и прочих пользователей и одновременно добавить себе право на выполнение, владельцу следует воспользоваться командой, показанной в пример 4.26.
chmod go-w,u+x myfile
Листинг 4.26. Пример использования служебной программы chmod.
Как указывалось выше, для удобства программирования аналогичных действий на языке C в заголовочном файле
( Любопытны правила формирования старших цифр года, если они опущены. Когда младшие цифры лежат в диапазоне от 69 до 99, подразумевается 19; в противном случае - 20. Несомненно, в будущих версиях стандарта данное соглашение изменится.)
Если файл-аргумент не существует, он создается по умолчанию служебной программой touch. Опция -c запрещает делать это.
Приведем пример совместного использования служебных программ touch, chown и chmod. В процессе загрузки ОС Linux выполняются действия по инициализации файлов с информацией о пользовательских сеансах, подобные тем, что приведены в пример 4.28.
> /var/run/utmp touch /var/log/wtmp chown :utmp /var/run/utmp /var/log/wtmp chmod ug=rw,o=r /var/run/utmp /var/log/wtmp
Листинг 4.28. использования совместного утилит touch, chown и chmod.
Здесь полезны обе возможности touch - и модификация атрибутов существующих файлов, и создание новых.
Часто touch применяют при работе с файлами-замками (см. пример 4.29).
start () { echo -n "Starting cupsd: " daemon cupsd RETVAL=$? echo [ $RETVAL = 0 ] && touch /var/lock/subsys/cups return $RETVAL }
Листинг 4.29. Пример использования утилиты touch для работы с файлами-замками при загрузке ОС Linux.
Обход и обработка файловых иерархий
Для обхода файловой иерархии и систематической обработки ее элементов служит утилита find:find [-H | -L] файл ... [выражение]
Она рекурсивно обходит каждую из иерархий с корнями в заданных файлах (разумеется, обычно в этом качестве указываются каталоги), отыскивая файлы, которые удовлетворяют логическому выражению, построенному с помощью описанных ниже средств. Опции -H и -L стандартным образом влияют на трактовку символьных ссылок. Если указуемый файл не существует, find оперирует с самой ссылкой.
Перечислим элементарные логические выражения и правила их вычисления. (Далее n обозначает целое десятичное число; вместо него могут указываться также комбинации +n и -n, что означает, соответственно, "больше, чем n" и "меньше, чем n".)
-name шаблон_файлов
Истина, если имя текущего файла удовлетворяет шаблону_файлов. Символы шаблона, имеющие для shell специальный смысл, должны быть экранированы.
-type тип_файла
Истина, если файл имеет заданный тип: b, c, d, f, p или s - является, соответственно, блочным или символьным специальным файлом, каталогом, обычным файлом, каналом или сокетом.
-size n[c]
Истина, если файл занимает n блоков по 512 байт. Когда указана буква c, размер файла задается в символах. Напомним, что с помощью комбинаций +n и -n можно проверять размер (и три указанные ниже характеристики) не только на равенство, но и на неравенство.
-atime n
Истина, если последний доступ к файлу производился n дней назад (в данном контексте день - это промежуток времени в 86400 секунд).
-mtime n
Истина, если файл последний раз модифицировался n дней назад.
-ctime n
Истина, если атрибуты файла последний раз изменялись n дней назад.
-perm [-]режим
Истина, если режим файла соответствует заданному. При наличии знака минус соответствие понимается как включение (все заданные биты должны присутствовать в режиме доступа к файлу); если минус отсутствует, требуется точное совпадение. Режим задается аналогично утилите chmod.
-links n
Истина, если на файл имеется n жестких ссылок.
-user имя_пользователя
Истина, если владельцем файлаявляется заданный пользователь. Когда в качестве имени задано десятичное число и пользователя с таким именем нет, число трактуется как идентификатор пользователя.
-nouser
Истина, если идентификатор владельца файла отсутствует в базе данных пользователей.
-group владеющая_группа
Истина, если файлом владеет заданная группа.
-nogroup
Истина, если идентификатор владеющей группы файла отсутствует в базе данных групп.
-depth
Всегда истина; задает дисциплину обхода иерархии вглубь: сначала обрабатываются все элементы каталога, потом - сам каталог (по умолчанию find в первую очередь обрабатывает каталог и лишь после этого - его элементы).
-xdev
Всегда истина; предписывает не спускаться в каталоги, имеющие другой идентификатор устройства (st_dev, см. выше описание структуры stat).
-prune
Всегда истина; предписывает не обрабатывать текущий файл, если он является каталогом.
-exec команда
Истина, если после выполнения команды возвращается нулевой код завершения. запись команды должна заканчиваться экранированной точкой с запятой. Аргумент команды, заданный в виде пары фигурных скобок {}, заменяется маршрутным именем обрабатываемого файла.
-ok команда
Эквивалентно -exec за исключением того, что перед выполнением команды запрашивается подтверждение (в виде сгенерированной командной строки со знаком вопроса в конце), и она выполняется только при ответе y.
Всегда истина; вызывает выдачу маршрутного имени обрабатываемого файла на стандартный вывод. Если в командной строке find не задано выражение, то подразумевается -print. Если выражение не содержит ни -exec, ни -ok, ни -print, вместо него используется конструкция( выражение ) -print
-newer файл
Истина, если текущий файл был модифицирован позднее указанного файла
( выражение )
Истина, если истинно заключенное в скобки выражение (скобки должны быть экранированы от интерпретации языком shell).
Элементарные логические выражения могут комбинироваться с помощью следующих операций (в порядке уменьшения приоритета):
логическое И, обозначается пробелом или -a. Если значением первого операнда оказалась ложь, второй не вычисляется. Таким образом, последовательность разделенных пробелами выражений-операндов можно рассматривать как составной фильтр, через который пропускается текущий файл: если значением очередного операнда оказалась ложь, обработка прекращается; в противном случае файл передается следующему компоненту фильтра.
логическое ИЛИ, обозначается -o. Если значением первого операнда оказалась истина, второй не вычисляется.
Основные понятия.
В трактовке стандарта POSIX понятие файла охватывает все, что может содержать, потреблять и/или поставлять информацию. В объектно-ориентированной терминологии файл должен предоставлять методы для чтения и/или записи и иметь такие атрибуты, как тип, имя и режим.В стандарте зафиксированы следующие типы файлов:
обычный файл;
каталог;
канал;
символьный специальный файл;
блочный специальный файл;
символьная ссылка;
сокет.
Обычный файл представляет собой последовательность байт с возможностью случайного доступа и без какой-либо дополнительной структуры, наложенной операционной системой.
Каталог состоит из элементов (ссылок), ассоциирующих имена с файлами. Одинаковые имена не могут фигурировать в разных элементах одного каталога, но разные элементы могут ссылаться на один и тот же файл.
Канал можно представлять себе в виде транспортера, с одной стороны которого находится поставщик (процесс, пишущий в канал), а с другой - потребитель (процесс, читающий из канала). Данные читаются в том же порядке, в каком производилась запись, т. е. с точки зрения структур данных канал - это очередь.
Специальные файлы соответствуют аппаратным компонентам компьютера. Обычно при использовании символьных специальных файлов остаются видимыми аппаратные характеристики соответствующих устройств, а при доступе к устройствам посредством блочных специальных файлов аппаратные характеристики, как правило, остаются скрытыми. Типичный пример устройства, которому соответствует символьный специальный файл, - терминал.
Символьная ссылка - это файл, хранящий цепочку символов. Когда подобный файл является компонентом маршрутного имени (см. далее), хранимая цепочка символов влияет на результирующий маршрут. Обычно ссылка раскрывается (прозрачным для приложений образом), т. е. вместо ее имени подставляется содержимое (цепочка символов).
Сокет - это оконечная точка межпроцессных коммуникаций.
Файлы вместе со служебной информацией, хранящейся в объектах, которые называются описателями файлов, объединяются в иерархическую структуру (направленный граф), именуемую файловой системой.
Все неконцевые вершины графа (т. е. вершины, откуда выходит хотя бы одна дуга) являются каталогами; все концевые имеют другие типы.
Согласно стандарту POSIX-2001, для каждого процесса определен корневой каталог с именем / - вершина графа, из которой осуществляется доступ к прочим файлам данной файловой системы. "Абсолютного", единого для всех корня может и не существовать.
В пределах файловой системы каждый файл имеет уникальный идентификатор (порядковый номер - он же номер описателя файла).
Имена, ассоциируемые с файлами (точнее, с их порядковыми номерами) посредством каталогов, называются простыми. С формальной точки зрения они могут включать любые символы, кроме слэша (/), однако для повышения мобильности приложений стандарт рекомендует ограничиться латинскими буквами, цифрами, а также символами ., _, - (точка, подчеркивание, минус), причем минус не должен быть первым символом. В соответствующей стандарту операционной системе сохраняется различие между большими и малыми буквами.
(При использовании в именах файловнекоторых других символов довольно часто возникают проблемы. Укажем лишь на часть из них.
Символы национальных алфавитов создают трудности с отображением имен. Символы, имеющие специальный смысл для командного интерпретатора, могут вызвать неожиданные и нежелательные модификации имен. Символ : (двоеточие) в имени каталога способен нарушить нормальную интерпретацию значения переменной окружения PATH. Минус в качестве первого символа придает имени файла вид комбинации опций, нарушая процесс разбора аргументов утилит. И т.д, и т.п.)
В каждом каталоге должны присутствовать имена . (точка) и .. (по стандарту читается как точка-точка), которые трактуются как ссылки на текущий и вышележащий каталоги. Для корневого каталога имя .. может также ссылаться на корень.
Под маршрутным именем понимается цепочка символов, идентифицирующая файл и состоящая из нескольких (в том числе из нуля) простых имен файлов, разделенных символами / и называемых компонентами маршрута.
Получение информации о файлах и файловых системах
Чтобы узнать абсолютное маршрутное имя текущего каталога, приложение может воспользоваться упоминавшейся ранее обычной встроенной командой языка shellpwd [-L | -P]
#include
Листинг 4.1. Описание функции getcwd(). (html, txt)
Команда pwd с (подразумеваемой) опцией -L извлекает имя текущего каталога из переменной окружения PWD, если это возможно. Посредством опции -P выполняется раскрытие символьных ссылок: в выдаваемом на стандартный вывод абсолютном маршрутном имени вместо символьных ссылок подставляется их содержимое.
Функция getcwd() помещает абсолютное маршрутное имя текущего каталога (без символьных ссылок) в массив buf длины size, который и возвращается в качестве результата (при ошибке результат равен NULL).
Приведем пример программы, использующей функцию getcwd() (см. пример 4.2).
Листинг 4.2. Пример использования функции getcwd(). (html, txt)
Обратим внимание на применение функции pathconf(), возвращающей текущее значение одного из конфигурируемых параметров целевой системы - в данном случае максимальную длину маршрутного имени. Подобный прием характерен для мобильных приложений.
Для выдачи на стандартный вывод информации о файлах всех типов служит утилита
ls [опция ...] [файл ...]
(напомним, что в квадратные скобки заключаются необязательные аргументы). Как правило, если в качестве файла задано имя каталога, то выводится информация обо всех содержащихся в нем файлах.
Опции управляют порядком и степенью подробности выдаваемой информации о файлах. Если опции не заданы, выводятся только имена файлов. Если не заданы файлы, выдается информация о файлах текущего каталога. Опция -l предписывает выводить подробную информацию. Например, при использовании служебной программы ls по команде
ls -l /
может быть выдано следующее (см. пример 4.3):
Пример 4.3. Возможный результат использования служебной программы ls (html, txt)
Число в первой строке есть суммарный размер (в блоках по 512 байт) всех файлов каталога, информация о которых выдана.
Далее следуют строки с информацией об отдельных файлах. Первый символ в этих строках задает тип файла:
Отметим, что стандартом POSIX-2001 не предусмотрено обозначение для сокетов, указано только, что реализации могут вводить свои типы файлов и обозначения для них.
Девять последующих символов отражают режим доступа к файлу: первые три символа - права доступа его владельца, следующие три - владеющей группы, последние три - права доступа прочих пользователей. Наличие буквы r (чтение), w (запись) или x (выполнение) означает, что соответствующее право имеется; знак минус свидетельствует об отсутствии права. Например, файл /usr является каталогом, куда может писать только пользователь root, а читать и искать информацию - все остальные.
Следом идет число ссылок на файл (считаются только так называемые жесткие ссылки, т. е. количество вхождений имени файла в каталоги). Для каталога оно заведомо не меньше двух (обоснование этого факта предоставляется читателю).
Следующие два столбца - имена владельца файла и владеющей группы, после чего идет размер файла в байтах. Для специальных файлов вместо размера нередко выдается зависящая от реализации информация о соответствующем устройстве.
Наконец, следуют дата и время последнего изменения и имя файла. По умолчанию имена файлов сортируются в алфавитном порядке.
Опишем еще несколько употребительных опций служебной программы ls.
| -a | Вывести список всех файлов (обычно не выводятся данные о файлах, имена которых начинаются с точки). |
| -C | Выдавать имена файлов в несколько столбцов (с сортировкой по столбцам). Отметим, что если в командной строке встречаются пары взаимоисключающих опций (например, -lC), то действует та, что указана последней. |
| -d | Трактовать каталоги наравне с файлами других типов. Часто используется с опцией -l для получения сведений о состоянии каталога. |
| -F | Выводить символ / после имен каталогов, * - после выполнимых файлов, | - после каналов, @ - после символьных ссылок. |
| -i | Выдавать порядковый номер файла в файловой системе (см. выше). |
| -R | Рекурсивно обойти встретившиеся подкаталоги. |
| -r | Изменить порядок сортировки файлов на обратный. |
| -t | Использовать в качестве первичного ключа сортировки время последнего изменения (сначала идут самые свежие файлы); имя служит вторичным ключом. Обычно символьные ссылки трактуются утилитой ls наравне с файлами других типов, только на месте имени выводится комбинацияимя_файла-ссылки -> содержимое_файла-ссылки. Для получения информации о файле, на который указывает символьная ссылка, следует воспользоваться одной из следующих опций. |
| -L | Если символьная ссылка является аргументом утилиты ls или встретилась в процессе обхода файловой иерархии, выдавать информацию об указуемом файле, а не о ссылке. На месте имени выводится имя файла-ссылки (а не указуемого файла). |
| -H | Аналогична -L, но воздействует только на символьные ссылки, заданные в командной строке и указывающие на каталог. |
Наличие буквы r (чтение), w (запись) или x (выполнение) означает, что соответствующее право имеется; знак минус свидетельствует об отсутствии права. Например, файл /usr является каталогом, куда может писать только пользователь root, а читать и искать информацию - все остальные.
Следом идет число ссылок на файл (считаются только так называемые жесткие ссылки, т. е. количество вхождений имени файла в каталоги). Для каталога оно заведомо не меньше двух (обоснование этого факта предоставляется читателю).
Следующие два столбца - имена владельца файла и владеющей группы, после чего идет размер файла в байтах. Для специальных файлов вместо размера нередко выдается зависящая от реализации информация о соответствующем устройстве.
Наконец, следуют дата и время последнего изменения и имя файла. По умолчанию имена файлов сортируются в алфавитном порядке.
Опишем еще несколько употребительных опций служебной программы ls.
| -a | Вывести список всех файлов (обычно не выводятся данные о файлах, имена которых начинаются с точки). |
| -C | Выдавать имена файлов в несколько столбцов (с сортировкой по столбцам). Отметим, что если в командной строке встречаются пары взаимоисключающих опций (например, -lC), то действует та, что указана последней. |
| -d | Трактовать каталоги наравне с файлами других типов. Часто используется с опцией -l для получения сведений о состоянии каталога. |
| -F | Выводить символ / после имен каталогов, * - после выполнимых файлов, | - после каналов, @ - после символьных ссылок. |
| -i | Выдавать порядковый номер файла в файловой системе (см. выше). |
| -R | Рекурсивно обойти встретившиеся подкаталоги. |
| -r | Изменить порядок сортировки файлов на обратный. |
| -t | Использовать в качестве первичного ключа сортировки время последнего изменения (сначала идут самые свежие файлы); имя служит вторичным ключом. Обычно символьные ссылки трактуются утилитой ls наравне с файлами других типов, только на месте имени выводится комбинацияимя_файла-ссылки -> содержимое_файла-ссылки. Для получения информации о файле, на который указывает символьная ссылка, следует воспользоваться одной из следующих опций. |
| -L | Если символьная ссылка является аргументом утилиты ls или встретилась в процессе обхода файловой иерархии, выдавать информацию об указуемом файле, а не о ссылке. На месте имени выводится имя файла-ссылки (а не указуемого файла). |
| -H | Аналогична -L, но воздействует только на символьные ссылки, заданные в командной строке и указывающие на каталог. |
4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 ex 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 rvi 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 rview 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 vi 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 view
Листинг 4.8. Возможный результат использования служебной программы ls при выдаче информации о символьных ссылках и наличии опции -L.
Данные о специальных файлах (см. пример 4.9) могут выглядеть так, как показано в пример 4.10.
ls -l /dev/ttyp[0-3] /dev/fd[2-5]
Листинг 4.9. Пример использования служебной программы ls для выдачи информации о специальных файлах.
brw-rw---- 1 root floppy 2, 2 Apr 1 2002 /dev/fd2 brw-rw---- 1 root floppy 2, 3 Apr 1 2002 /dev/fd3 brw-rw---- 1 root floppy 2, 128 Apr 1 2002 /dev/fd4 brw-rw---- 1 root floppy 2, 129 Apr 1 2002 /dev/fd5 crw-rw-rw- 1 root root 3, 0 Sep 2 17:21 /dev/ttyp0 crw-rw-rw- 1 root tty 3, 1 Apr 1 2002 /dev/ttyp1 crw-rw-rw- 1 root tty 3, 2 Apr 1 2002 /dev/ttyp2 crw-rw-rw- 1 root tty 3, 3 Apr 1 2002 /dev/ttyp3
Листинг 4.10. Возможный результат использования служебной программы ls при выдаче информации о специальных файлах.
Для специальных файлов вместо размера выдаются так называемые старший и младший номера, однозначно определяющие соответствующее устройство; наличие такой возможности, разумеется, зависит от реализации.
Сведения о файлах-каналах могут выглядеть так, как показано в пример 4.11.
total 0 prw--w---- 1 root root 0 Sep 2 10:42 xdmctl prw--w---- 1 shmyrev root 0 Sep 2 10:42 xdmctl-:0
Листинг 4.11. Возможный результат использования служебной программы ls при выдаче информации о каналах.
В пример 4.12 приведен пример вывода информации о сокетах.
srwx------ 1 shmyrev root 0 Sep 2 10:42 /dev/gpmctl srw-rw-rw- 1 root root 0 Sep 2 10:42 /dev/log
Листинг 4.12. Возможный результат использования служебной программы ls при выдаче информации о сокетах.
Опция -t позволяет увидеть сначала те файлы, которые изменялись позже других. Например, командная строка служебной программы ls
Функция stat() предоставляет информацию о поименованном файле: аргумент path указывает на маршрутное имя файла. Чтобы получить эти сведения, достаточно иметь право на поиск для всех компонентов маршрутного префикса. Функция fstat() сообщает данные об открытом файле, задаваемом дескриптором файла fildes. Функция lstat() эквивалентна stat() за одним исключением: если аргумент path задает символьную ссылку, lstat() возвращает информацию о ней, а stat() - о файле, на который эта ссылка указывает.
В случае нормального завершения результат функций семейства stat() равен 0.
Аргумент buf является указателем на структуру типа stat, в которую помещается информация о файле. Согласно стандарту POSIX-2001, в ней содержатся по крайней мере следующие поля:
dev_t st_dev; /* Идентификатор устройства, содержащего файл */ ino_t st_ino; /* Порядковый номер файла в файловой системе */ mode_t st_mode; /* Режим файла nlink_t st_nlink; /* Число жестких ссылок на файл */ uid_t st_uid; /* Идентификатор владельца файла */ gid_t st_gid; /* Идентификатор владеющей группы */ off_t st_size; /* Для обычных файлов и символьных ссылок - размер в байтах */ /* Для файлов других типов значение этого поля неспецифицировано */ time_t st_atime; /* Время последнего доступа */ time_t st_mtime; /* Время последнего изменения файла */ time_t st_ctime; /* Время последнего изменения статуса файла */
Некоторые пояснения. Комбинация значений (st_dev, st_ino) должна однозначно определять файл в пределах объединенной (в том числе сетевой) файловой системы. Статус файла меняется, когда модифицируются его атрибуты (например, режим), а не содержимое.
В файле
Приведем пример использования функций stat() и lstat() (см. пример 4.15).
/* Программа выдает информацию о файлах - аргументах командной строки */ #include
Описание функций fstatvfs() и statvfs().
Утилита df выдает на стандартный вывод информацию об имеющемся пространстве и, возможно, некоторые другие данные. Если файлы-аргументы не указаны, поступают сообщения обо всех компонентах объединенной файловой системы.
По умолчанию пространство измеряется в 512-байтных блоках; опция -k предписывает использовать блоки размером в килобайт. Подразумеваемый формат вывода не специфицирован, но должны становиться известными по крайней мере имя файловой системы и объем свободного пространства. Если задана опция -P, употребляется так называемый мобильный формат вывода с заголовком
Filesystem 512-blocks Used Available Capacity Mounted on
и следующей построчной информацией о каждой файловой системе:
занятое пространство;
свободное пространство;
процент занятости;
Возможный результат выполнения служебной программы df с опциями -kP приведен в пример 4.18.
Filesystem 1024-blocks Used Available Capacity Mounted on /dev/sda3 75822432 43456504 28514348 61% / /dev/sda1 46636 21129 23099 48% /boot none 257212 0 257212 0% /dev/shm /dev/fd0 1424 392 1032 28% /a
Листинг 4.18. Возможный результат выполнения командной строки df -kP.
Пара функций statvfs() и fstatvfs() по интерфейсу аналогична функциям stat() и fstat(), только в выходную структуру (типа fstatvfs) помещается информация не о файлах, а о файловых системах, содержащих эти файлы. Согласно стандарту POSIX-2001, в структуре statvfs должны присутствовать по крайней мере следующие поля:
unsigned long f_bsize; /* Размер блока файловой системы */ unsigned long f_frsize; /* Базовый размер блока файловой системы */ fsblkcnt_t f_blocks; /* Общее число блоков базового размера в файловой системе */ fsblkcnt_t f_bfree; /* Общее число свободных блоков */ fsblkcnt_t f_bavail; /* Число свободных блоков, доступных непривилегированным процессам */ fsfilcnt_t f_files; /* Общее число описателей файлов */ fsfilcnt_t f_ffree; /* Общее число свободных описателей файлов */ fsfilcnt_t f_favail; /* Число описателей файлов, доступных непривилегированным процессам */ unsigned long f_fsid; /* Идентификатор файловой системы */ unsigned long f_flag; /* Битная шкала флагов */ unsigned long f_namemax; /* Максимальная длина имени файла */
statvfs-информация о файловой системе, содержащей файл /a: Размер блока файловой системы: 512 Базовый размер блока файловой системы: 512 Общее число блоков базового размера в файловой системе: 2847 Общее число свободных блоков: 1960 Число свободных блоков, доступных непривилегированным процессам: 1960 Общее число описателей файлов: 0 Общее число свободных описателей файлов: 0 Число описателей файлов, доступных непривилегированным процессам: 0 Идентификатор файловой системы: 0 Битная шкала флагов: f Максимальная длина имени файла: 260
Листинг 4.20. Возможный результат работы программы, использующей функцию statvfs().
Интерпретация полученных результатов предоставляется читателю.
Служебная программа
du [опция ...] [файл ...]
выдает информацию о суммарном объеме пространства (измеряемого аналогично df), занятого иерархиями файлов с указанными в командной строке корнями, предваряя ее аналогичными сведениями для каждого из подкаталогов, входящих в иерархии. При отсутствии аргументов выводятся сведения о текущем каталоге. Файлы, на которые есть несколько жестких ссылок, учитываются только один раз. Символьные ссылки трактуются по сути так же, как и в служебной программе ls, включая смысл опций -H и -L.
Выделим несколько других опций.
| -a | В дополнение к подразумеваемому выводу сообщать размеры файлов, входящих в обрабатываемые иерархии и не являющихся каталогами. |
| -s | Вместо подразумеваемого вывода информировать только о суммарном объеме занятого пространства для каждой заданной в командной строке иерархии. |
du -k /usr/local/man /bin/vi /bin/view
может быть выдано следующее (см. пример 4.21):
1428 /usr/local/man/man1 12 /usr/local/man/man5 64 /usr/local/man/man7 36 /usr/local/man/man8 1544 /usr/local/man 384 /bin/vi 0 /bin/view
Листинг 4.21. Возможный результат использования утилиты du.
Таким образом, иерархия файлов с корнем в /usr/local/man занимает около 1.5 Мб (почти все приходится на долю файлов подкаталога man1), обычный файл /bin/vi - 384 Кб (читателю предлагается сопоставить эту величину и приведенный выше размер данного файла в байтах), символьная ссылка лишнего места не занимает.
Описание функции
|
#include |
| Листинг 4.1. Описание функции getcwd(). |
| Закрыть окно |
|
#include |
| Листинг 4.2. Пример использования функции getcwd(). |
| Закрыть окно |
| total 338 drwxrwxr-x 2 root root 4096 Jul 6 13:31 a drwxr-xr-x 2 root root 4096 Jul 8 13:32 bin drwxr-xr-x 4 root root 1024 Jul 6 15:06 boot drwxrwxr-x 2 root root 4096 Jul 6 13:31 cdrom drwxr-xr-x 18 root root 86016 Sep 2 10:42 dev drwxr-xr-x 6 root root 4096 Feb 23 2003 dss drwxr-xr-x 60 root root 4096 Sep 2 10:42 etc drwxr-xr-x 20 root root 4096 Jul 15 18:00 home drwxr-xr-x 2 root root 4096 Jun 12 2001 initrd drwxr-xr-x 8 root root 4096 Aug 8 13:16 lib drwx------ 2 root root 16384 Jul 6 14:15 lost+found drwxr-xr-x 2 root root 4096 Apr 1 2002 misc drwxr-xr-x 5 root root 4096 Jul 6 11:24 mnt drwxr-xr-x 7 root root 4096 Jul 23 13:09 opt dr-xr-xr-x 84 root root 0 Sep 2 2003 proc drwxr-x--- 5 root root 4096 Sep 1 17:10 root drwxr-xr-x 2 root root 4096 Jul 11 17:38 sbin drwxrwxrwt 23 root root 8192 Sep 2 12:43 tmp drwxr-xr-x 14 root root 4096 Jul 15 12:50 usr drwxr-xr-x 16 root root 4096 Jul 9 16:22 var |
| Пример 4.3. Возможный результат использования служебной программы ls |
| Закрыть окно |
| cd /usr/share/zoneinfo/posix ls -il UTC Universal Zulu |
| Листинг 4.4. Еще один пример использования служебной программы ls. |
| Закрыть окно |
| 2506763 -rw-r--r-- 6 root root 56 Apr 15 2002 UTC 2506763 -rw-r--r-- 6 root root 56 Apr 15 2002 Universal 2506763 -rw-r--r-- 6 root root 56 Apr 15 2002 Zulu |
| Листинг 4.5. Возможный результат использования служебной программы ls. |
| Закрыть окно |
| cd /bin ls - il ex rvi rview vi view |
| Листинг 4.6. Применение служебной программы ls для выдачи информации о символьных ссылках. |
| Закрыть окно |
| 4849722 lrwxrwxrwx 1 root root 2 Jul 6 14:17 ex -> vi 4849723 lrwxrwxrwx 1 root root 2 Jul 6 14:17 rvi -> vi 4849724 lrwxrwxrwx 1 root root 2 Jul 6 14:17 rview -> vi 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 vi 4849726 lrwxrwxrwx 1 root root 2 Jul 6 14:17 view -> vi |
| Листинг 4.7. Возможный результат использования служебной программы ls при выдаче информации о символьных ссылках. |
| Закрыть окно |
| 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 ex 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 rvi 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 rview 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 vi 4849725 -rwxr-xr-x 1 root root 386120 Mar 8 2002 view |
| Листинг 4.8. Возможный результат использования служебной программы ls при выдаче информации о символьных ссылках и наличии опции -L. |
| Закрыть окно |
| ls -l /dev/ttyp[0-3] /dev/fd[2-5] |
| Листинг 4.9. Пример использования служебной программы ls для выдачи информации о специальных файлах. |
| Закрыть окно |
| brw-rw---- 1 root floppy 2, 2 Apr 1 2002 /dev/fd2 brw-rw---- 1 root floppy 2, 3 Apr 1 2002 /dev/fd3 brw-rw---- 1 root floppy 2, 128 Apr 1 2002 /dev/fd4 brw-rw---- 1 root floppy 2, 129 Apr 1 2002 /dev/fd5 crw-rw-rw- 1 root root 3, 0 Sep 2 17:21 /dev/ttyp0 crw-rw-rw- 1 root tty 3, 1 Apr 1 2002 /dev/ttyp1 crw-rw-rw- 1 root tty 3, 2 Apr 1 2002 /dev/ttyp2 crw-rw-rw- 1 root tty 3, 3 Apr 1 2002 /dev/ttyp3 |
| Листинг 4.10. Возможный результат использования служебной программы ls при выдаче информации о специальных файлах. |
| Закрыть окно |
| total 0 prw--w---- 1 root root 0 Sep 2 10:42 xdmctl prw--w---- 1 shmyrev root 0 Sep 2 10:42 xdmctl-:0 |
| Листинг 4.11. Возможный результат использования служебной программы ls при выдаче информации о каналах. |
| Закрыть окно |
| srwx------ 1 shmyrev root 0 Sep 2 10:42 /dev/gpmctl srw-rw-rw- 1 root root 0 Sep 2 10:42 /dev/log |
| Листинг 4.12. Возможный результат использования служебной программы ls при выдаче информации о сокетах. |
| Закрыть окно |
| total 338 drwxrwxrwt 23 root root 8192 Sep 2 16:26 tmp dr-xr-xr-x 143 root root 0 Sep 2 14:42 proc drwxr-xr-x 60 root root 4096 Sep 2 10:42 etc drwxr-xr-x 18 root root 86016 Sep 2 10:42 dev drwxr-x--- 5 root root 4096 Sep 1 17:10 root drwxr-xr-x 8 root root 4096 Aug 8 13:16 lib drwxr-xr-x 7 root root 4096 Jul 23 13:09 opt drwxr-xr-x 20 root root 4096 Jul 15 18:00 home drwxr-xr-x 14 root root 4096 Jul 15 12:50 usr drwxr-xr-x 2 root root 4096 Jul 11 17:38 sbin drwxr-xr-x 16 root root 4096 Jul 9 16:22 var drwxr-xr-x 2 root root 4096 Jul 8 13:32 bin drwxr-xr-x 4 root root 1024 Jul 6 15:06 boot drwx------ 2 root root 16384 Jul 6 14:15 lost+found drwxrwxr-x 2 root root 4096 Jul 6 13:31 cdrom drwxrwxr-x 2 root root 4096 Jul 6 13:31 a drwxr-xr-x 5 root root 4096 Jul 6 11:24 mnt drwxr-xr-x 6 root root 4096 Feb 23 2003 dss drwxr-xr-x 2 root root 4096 Apr 1 2002 misc drwxr-xr-x 2 root root 4096 Jun 12 2001 initrd |
| Листинг 4.13. Возможный результат использования служебной программы ls с сортировкой файлов по времени последнего изменения. |
| Закрыть окно |
|
#include |
| Листинг 4.14. Описание функций семейства stat(). |
| Закрыть окно |
|
/* Программа выдает информацию о файлах - аргументах командной строки */ #include /* Функция возвращает односимвольное обозначение типа файла */ /* Для неизвестного типа возвращается 'u' */ static char my_filetype (const mode_t mode) { switch (mode &S_IFMT) { case S_IFDIR: return ('d'); case S_IFBLK: return ('b'); case S_IFCHR: return ('c'); case S_IFLNK: return ('l'); case S_IFIFO: return ('p'); case S_IFREG: return ('-'); case S_IFSOCK: return ('s'); default: return ('u'); } } int main (int argc, char *argv[]) { struct stat buf; int i; for (i = 1; i < argc; i++) { if (stat (argv [i], &buf)) { fprintf (stderr, "\nstat: не удалось получить информацию о файле %s\n", argv [i]); return (-1); } printf ("\nstat-информация о файле %s:\n", argv [i]); printf ("Тип: %c\n", my_filetype (buf.st_mode)); printf ("Размер: %ld\n", buf.st_size); if (lstat (argv [i], &buf)) { fprintf (stderr, "\nlstat: не удалось получить информацию о файле %s\n", argv [i]); return (-1); } printf ("\nlstat-информация о файле %s:\n", argv [i]); printf ("Тип: %c\n", my_filetype (buf.st_mode)); printf ("Размер: %ld\n", buf.st_size); } return 0; } |
| Листинг 4.15. Пример использования функций stat() и lstat(). |
| Закрыть окно |
| stat-информация о файле /bin/view: Тип: - Размер: 386120 lstat-информация о файле /bin/view: Тип: l Размер: 2 stat-информация о файле /bin/vi: Тип: - Размер: 386120 lstat-информация о файле /bin/vi: Тип: - Размер: 386120 |
| Листинг 4.16. Возможный результат работы программы, использующей функции stat() и lstat(). |
| Закрыть окно |
|
#include |
| Листинг 4.17. Описание функций fstatvfs() и statvfs(). |
| Закрыть окно |
| Filesystem 1024- blocks Used Available Capacity Mounted on /dev/sda3 75822432 43456504 28514348 61% / /dev/sda1 46636 21129 23099 48% /boot none 257212 0 257212 0% /dev/shm /dev/fd0 1424 392 1032 28% /a |
| Листинг 4.18. Возможный результат выполнения командной строки df -kP. |
| Закрыть окно |
|
#include int main (int argc, char *argv[]) { struct statvfs buf; int i; for (i = 1; i < argc; i++) { if (statvfs (argv [i], &buf)) { fprintf (stderr, "\nstatvfs: не удалось получить информацию о файловой системе, содержащей файл %s\n", argv [i]); return (-1); } printf ("\nstatvfs-информация о файловой системе, содержащей файл %s:\n", argv [i]); printf ("Размер блока файловой системы: %ld\n", buf.f_bsize); printf ("Базовый размер блока файловой системы: %ld\n", buf.f_frsize); printf ("Общее число блоков базового размера в файловой системе: %ld\n", buf.f_blocks); printf ("Общее число свободных блоков: %ld\n", buf.f_bfree); printf ("Число свободных блоков, доступных непривилегированным процессам: %ld\n", buf.f_bavail); printf ("Общее число описателей файлов: %ld\n", buf.f_files); printf ("Общее число свободных описателей файлов: %ld\n", buf.f_ffree); printf ("Число описателей файлов, доступных непривилегированным процессам: %ld\n", buf.f_favail); printf ("Идентификатор файловой системы: %ld\n", buf.f_fsid); printf ("Битная шкала флагов: %lx\n", buf.f_flag); printf ("Максимальная длина имени файла: %ld\n", buf.f_namemax); } return 0; } |
| Листинг 4.19. Пример использования функции statvfs(). |
| Закрыть окно |
|
statvfs-информация о файловой системе, содержащей файл /: Размер блока файловой системы: 4096 Базовый размер блока файловой системы: 4096 Общее число блоков базового размера в файловой системе: 18955608 Общее число свободных блоков: 7990010 Число свободных блоков, доступных непривилегированным процессам: 7027115 Общее число описателей файлов: 9633792 Общее число свободных описателей файлов: 8259049 Число описателей файлов, доступных непривилегированным процессам: 8259049 Идентификатор файловой системы: 0 Битная шкала флагов: 0 Максимальная длина имени файла: 255 statvfs-информация о файловой системе, содержащей файл /a: Размер блока файловой системы: 512 Базовый размер блока файловой системы: 512 Общее число блоков базового размера в файловой системе: 2847 Общее число свободных блоков: 1960 Число свободных блоков, доступных непривилегированным процессам: 1960 Общее число описателей файлов: 0 Общее число свободных описателей файлов: 0 Число описателей файлов, доступных непривилегированным процессам: 0 Идентификатор файловой системы: 0 Битная шкала флагов: f Максимальная длина имени файла: 260 |
| Листинг 4.20. Возможный результат работы программы, использующей функцию statvfs(). |
| Закрыть окно |
| 1428 /usr/local/man/man1 12 /usr/local/man/man5 64 /usr/local/man/man7 36 /usr/local/man/man8 1544 /usr/local/man 384 /bin/vi 0 /bin/view |
| Листинг 4.21. Возможный результат использования утилиты du. |
| Закрыть окно |
| cd "$OLDPWD" && pwd |
| Листинг 4.22. Действия, выполняемые по команде cd -. |
| Закрыть окно |
| ls -dl /usr/tmp /var/tmp cd /usr/ tmp pwd pwd -P cd .. pwd cd - cd -P .. pwd |
| Пример 4.23. Пример выполнения команды cd с разными опциями |
| Закрыть окно |
| lrwxrwxrwx 1 root root 10 Jul 6 14:16 /usr/tmp -> ../var/tmp drwxrwxrwt 5 root root 4096 Sep 4 18:05 /var/tmp /usr/tmp /var/tmp /usr /usr/tmp /var |
| Пример 4.24. результат выполнения команд cd с разными опциями |
| Закрыть окно |
|
#include |
| Листинг 4.25. Описание функций chown(), fchown(), chmod() и fchmod(). |
| Закрыть окно |
| chmod go-w,u+ x myfile |
| Листинг 4.26. Пример использования служебной программы chmod. |
| Закрыть окно |
|
#include for (i = 1; i < argc; i++) { if (stat (argv [i], &buf)) { fprintf (stderr, "\nstat: не удалось получить информацию о файле %s\n", argv [i]); return (-1); } if (chmod (argv [i], buf.st_mode | S_IXUSR | S_IXGRP | S_IXOTH)) { fprintf (stderr, "\nchmod: не удалось изменить режим доступа к файлу %s\n", argv [i]); return (-1); } } return 0; } |
| Листинг 4.27. использования функций stat() и chmod(). |
| Закрыть окно |
| > /var/run/utmp touch /var/log/wtmp chown :utmp /var/run/utmp /var/log/wtmp chmod ug=rw,o=r /var/run/utmp /var/log/wtmp |
| Листинг 4.28. использования совместного утилит touch, chown и chmod. |
| Закрыть окно |
| start () { echo -n " Starting cupsd: " daemon cupsd RETVAL=$? echo [ $RETVAL = 0 ] && touch /var/lock/subsys/cups return $RETVAL } |
| Листинг 4.29. Пример использования утилиты touch для работы с файлами-замками при загрузке ОС Linux. |
| Закрыть окно |
|
#include |
| Листинг 4.30. Описание функции creat(). |
| Закрыть окно |
|
#include |
| Листинг 4.31. Описание функции perror(). |
| Закрыть окно |
|
#include |
| Листинг 4.32. Пример программы, использующей функции creat() и perror(). |
| Закрыть окно |
| CREAT failed: Too many open files errno = 24 Неудача на файле номер 1022 |
| Листинг 4.33. Возможный результат первого выполнения программы, использующей функции creat() и perror(). |
| Закрыть окно |
| CREAT failed: Permission denied errno = 22 Неудача на файле номер 1 |
| Листинг 4.34. Результат повторного выполнения программы, использующей функции creat() и perror(). |
| Закрыть окно |
| df = creat ("/tmp/sample", S_IRWXU | S_IRWXG | S_IRWXO); |
| Листинг 4.35. Пример вызова функции creat(). |
| Закрыть окно |
| mkdir -p work/tmp/save |
| Листинг 4.36. Пример использования служебной программы mkdir для создания цепочки каталогов. |
| Закрыть окно |
|
#include |
| Листинг 4.37. Описание функции mkfifo(). |
| Закрыть окно |
|
#include |
| Листинг 4.38. Описание функций link() и symlink(). |
| Закрыть окно |
| if [ -n "$mver" ]; then ln -sf /lib/modules/$mver\ /lib/modules/default fi |
| Листинг 4.39. Использование утилиты ln для формирования содержимого символьной ссылки |
| Закрыть окно |
|
#include |
| Листинг 4.40. Описание функций unlink(), rmdir() и remove(). |
| Закрыть окно |
| rm -p a/b |
| Листинг 4.41. Пример команды удаления цепочки каталогов. |
| Закрыть окно |
|
#include /* Выполнение операций над рабочим файлом */ /* . . . */ /* В случае неудачи выдадим диагностическое сообщение и удалим рабочий файл */ if (! work_success) { fprintf (stderr, "\nНеудачное завершение операций над рабочим файлом %s\n", WORK_FILE); unlink (WORK_FILE); return (-1); } /* Установим режим доступа к рабочему файлу, */ /* подходящие для последующего использования */ /* Пусть, например, это будет доступ для всех */ /* только на чтение */ chmod (WORK_FILE, S_IRUSR | S_IRGRP | S_IROTH); /* Удалим ранее сохраненную старую версию основного файла */ unlink (OLD_FILE); /* Сохраним текущую версию основного файла */ if (link (MAIN_FILE, OLD_FILE)) { perror ("Не удалось сохранить текущую версию основного файла"); return (-1); } /* Удалим текущую версию основного файла */ unlink (MAIN_FILE); /* Сделаем рабочий файл основным */ if (link (WORK_FILE, MAIN_FILE)) { perror ("Не удалось сделать рабочий файл основным"); /* Восстановим основной файл */ link (OLD_FILE, MAIN_FILE); return (-1); } /* Удалим рабочий файл */ unlink (WORK_FILE); return 0; } |
| Листинг 4.42. Пример программы, использующей функции link() и unlink(). |
| Закрыть окно |
| .: d1/ x y ./d1: |
| Листинг 4.43. Состояние текущего каталога перед перемещением файлов. |
| Закрыть окно |
| ls -RF mv x y d1 mv d1 d2 ls -RF |
| Листинг 4.44. Использование утилиты mv для перемещения файлов и файловых иерархий. |
| Закрыть окно |
| .: d2/ ./d2: x y |
| Листинг 4.45. Состояние текущего каталога после перемещения файлов. |
| Закрыть окно |
| cp -R d2 d1 cp -R d2 d1 ls -RF |
| Листинг 4.46. Применение утилиты cp для копирования файловых иерархий. |
| Закрыть окно |
| .: d1/ d2/ ./d1: d2/ x y ./d1/d2: x y ./d2: x y |
| Листинг 4.47. Результат использования утилиты cp для копирования файловых иерархий. |
| Закрыть окно |
|
#include |
| Листинг 4.48. Описание функции rename(). |
| Закрыть окно |
|
#include |
| Листинг 4.49. Пример использования функции rename() для переименования каталогов. |
| Закрыть окно |
| .: d1/ d2/ ./d1: x y ./d2: x y |
| Листинг 4.50. Состояние текущего каталога после переименования каталогов с помощью функции rename(). |
| Закрыть окно |
| find . \( -size 0c -o -name \*.o -a -atime +30 \) -ok rm {} \; |
| Листинг 4.51. Еще один пример использования утилиты find. |
| Закрыть окно |
| find /mnt \( -nouser -o -nogroup \) -exec chown nobody:nobody {} \; |
| Листинг 4.52. Пример выявления и обработки файлов с неизвестными владельцем или владеющей группой. |
| Закрыть окно |
| find . - name skip -prune -o -print find . -print -name skip -prune |
| Листинг 4.53. Пример использования элементарного выражения -prune. |
| Закрыть окно |
| pax -w . mkdir new_dir pax -rw old_dir new_dir |
| Листинг 4.54. Пример использования служебной программы pax. |
| Закрыть окно |
Создание, удаление, копирование и перемещение файлов
Стандарт POSIX-2001 не требует наличия утилиты для создания обычных файлов. Они появляются по мере необходимости как побочный продукт многочисленных служебных программ (например, утилиты копирования), поэтому сама постановка задачи - создать файл "просто так" - является отчасти надуманной. В то же время, если новый файл все-таки нужен, полезно иметь в виду возможность перенаправления вывода пустой команды, которая имеется в языке shell.При программировании на языке C для создания обычных файлов можно воспользоваться функцией creat() (см. пример 4.30).
#include
Листинг 4.30. Описание функции creat(). (html, txt)
Функция creat() имеет два аргумента: маршрутное имя вновь образуемого файла и устанавливаемый режим доступа (идентификаторы владельца и владеющей группы наследуются у текущего пользователя). Результатом служит файловый дескриптор (который, напомним, представляется неотрицательным целым числом), т. е. функция creat() не только создает файл, но и открывает его.
Если файл, который пытаются создать при помощи creat(), уже существует, он опустошается (размер становится равным 0), а режим доступа и владелец не изменяются.
В случае неудачи результат creat() равен -1, а внешней переменной errno присваивается код ошибки, позволяющий определить причину ее (ошибки) возникновения. Переменная errno, а также мнемоники для кодов ошибок определены в заголовочном файле
#include
Листинг 4.31. Описание функции perror(). (html, txt)
Например, при первом выполнении программы, приведенной в пример 4.32, в стандартный протокол может быть выдан соответствующий результат (см. пример 4.33).
#include
Листинг 4.32. Пример программы, использующей функции creat() и perror(). (html, txt)
CREAT failed: Too many open files errno = 24 Неудача на файле номер 1022
Листинг 4.33. Возможный результат первого выполнения программы, использующей функции creat() и perror(). (html, txt)
Результат второго запуска той же программы показан в пример 4.34.
CREAT failed: Permission denied errno = 22 Неудача на файле номер 1
Листинг 4.34. Результат повторного выполнения программы, использующей функции creat() и perror(). (html, txt)
При первом запуске причина неудачи - превышение максимально допустимого числа одновременно открытых файлов (с учетом стандартных ввода, вывода и протокола), при повторном - попытка создать существующий файл вопреки отсутствию права на запись в него (файлы создавались с нулевым режимом доступа).
Перечислим несколько других условий, способных привести к неудачному завершению вызова creat(): компонент маршрутного имени не существует или не является каталогом; у компонента маршрута отсутствует право на поиск; создание файла требует записи в каталог, права на запись в который нет; файл существует и является каталогом.
Рассмотренный в примере стиль уведомления о неудачном завершении общий для большинства функций. Неудача определяется возвращением результата, невозможного в другом случае (почти всегда это -1 или пустой указатель NULL); код ошибки заносится в переменную errno. Разумеется, в реальных программах errno не выводят, а анализируют. В данном случае уместно было бы сравнивать errno с константами EACCES, EINVAL, EMFILE и т.д. (см.
файл не обязательно создавать в текущем каталоге; в качестве аргумента creat() может быть передано составное имя. Пример, когда устанавливаются все биты режима доступа, приведен в пример 4.35.
df = creat ("/tmp/sample", S_IRWXU | S_IRWXG | S_IRWXO);
Листинг 4.35. Пример вызова функции creat(). (html, txt)
Для создания (пустых) каталогов служит утилита
mkdir [-p] [-m режим_доступа] каталог ...
Листинг 4.32. Пример программы, использующей функции creat() и perror().
CREAT failed: Too many open files errno = 24 Неудача на файле номер 1022
Листинг 4.33. Возможный результат первого выполнения программы, использующей функции creat() и perror().
Результат второго запуска той же программы показан в пример 4.34.
CREAT failed: Permission denied errno = 22 Неудача на файле номер 1
Листинг 4.34. Результат повторного выполнения программы, использующей функции creat() и perror().
При первом запуске причина неудачи - превышение максимально допустимого числа одновременно открытых файлов (с учетом стандартных ввода, вывода и протокола), при повторном - попытка создать существующий файл вопреки отсутствию права на запись в него (файлы создавались с нулевым режимом доступа).
Перечислим несколько других условий, способных привести к неудачному завершению вызова creat(): компонент маршрутного имени не существует или не является каталогом; у компонента маршрута отсутствует право на поиск; создание файла требует записи в каталог, права на запись в который нет; файл существует и является каталогом.
Рассмотренный в примере стиль уведомления о неудачном завершении общий для большинства функций. Неудача определяется возвращением результата, невозможного в другом случае (почти всегда это -1 или пустой указатель NULL); код ошибки заносится в переменную errno. Разумеется, в реальных программах errno не выводят, а анализируют. В данном случае уместно было бы сравнивать errno с константами EACCES, EINVAL, EMFILE и т.д. (см.
файл не обязательно создавать в текущем каталоге; в качестве аргумента creat() может быть передано составное имя. Пример, когда устанавливаются все биты режима доступа, приведен в пример 4.35.
df = creat ("/tmp/sample", S_IRWXU | S_IRWXG | S_IRWXO);
Листинг 4.35. Пример вызова функции creat().
Для создания (пустых) каталогов служит утилита
mkdir [-p] [-m режим_доступа] каталог ...
и функция mkdir()
#include
Мобильное приложение должно считать, что исходные и целевой файлы обязаны принадлежать одной файловой системеи создать новую жесткую ссылку на каталог или символьную ссылку нельзя.
Функция link() аналогична служебной программе ln в первой форме, без опций. Ее нормальным результатом служит 0; в случае ошибки возвращается -1. Важно отметить, что с точки зрения файловой системы образование нового элемента каталога и увеличение счетчика жестких ссылок на исходный файл осуществляется функцией link() как неделимое действие.
Утилита ln с опцией -s и функция symlink() создают новые символьные ссылки. Исходные файлы не обязаны существовать; соответствующие аргументы трактуются как цепочки символов и задают содержимое ссылок.
Опция -f позволяет замещать существующие элементы каталогов новыми (по умолчанию задание существующего файла в качестве целевого считается ошибкой).
Примером смены содержимого символьной ссылки посредством служебной программы ln с опциями -s и -f может служить фрагмент действий при загрузке ОС Linux, приведенный в пример 4.39.
if [ -n "$mver" ]; then ln -sf /lib/modules/$mver\ /lib/modules/default fi
Листинг 4.39. Использование утилиты ln для формирования содержимого символьной ссылки
Для удаления файлов служат утилиты
rm [-fiRr] файл ...
и
rmdir [-p] файл ...
а также функции unlink(), rmdir() и remove() (см. пример 4.40).
#include
Листинг 4.40. Описание функций unlink(), rmdir() и remove().
Строго говоря, перечисленные утилиты и функции удаляют не файлы, а указанные ссылки на них, содержащиеся в каталогах - маршрутных префиксах, и, соответственно, уменьшают на единицу число жестких ссылок на эти файлы. файл удаляется и занятое им пространство освобождается, только если других ссылок на него не остается. Мы, однако, для краткости будем употреблять не совсем точное словосочетание "удаление файлов".
В пример 4. 42 приведен пример программы, которая с помощью функций link() и unlink() осуществляет ответственную обработку файлов с сохранением копии текущей версии.
#include
/* Выполнение операций над рабочим файлом */ /* . . . */
/* В случае неудачи выдадим диагностическое сообщение и удалим рабочий файл */ if (! work_success) { fprintf (stderr, "\nНеудачное завершение операций над рабочим файлом %s\n", WORK_FILE); unlink (WORK_FILE); return (-1); }
/* Установим режим доступа к рабочему файлу, */ /* подходящие для последующего использования */ /* Пусть, например, это будет доступ для всех */ /* только на чтение */ chmod (WORK_FILE, S_IRUSR | S_IRGRP | S_IROTH);
/* Удалим ранее сохраненную старую версию основного файла */ unlink (OLD_FILE);
/* Сохраним текущую версию основного файла */ if (link (MAIN_FILE, OLD_FILE)) { perror ("Не удалось сохранить текущую версию основного файла"); return (-1); }
/* Удалим текущую версию основного файла */ unlink (MAIN_FILE);
/* Сделаем рабочий файл основным */ if (link (WORK_FILE, MAIN_FILE)) { perror ("Не удалось сделать рабочий файл основным"); /* Восстановим основной файл */ link (OLD_FILE, MAIN_FILE); return (-1); }
/* Удалим рабочий файл */ unlink (WORK_FILE);
return 0; }
Листинг 4.42. Пример программы, использующей функции link() и unlink().
Выше мы отмечали, что, как правило, файлы создаются по мере необходимости при выполнении определенных операций. Одной из таких операций является копирование файлов, выполняемое служебной программой cp:
cp [-fip] исходный_файл целевой_файл cp [-fip] исходный_файл ...
На самом деле утилита mv, как правило, применяется для переименования файлов, и тогда ее работа сводится к созданию новых элементов каталогов и уничтожению старых, а реальное копирование может потребоваться только при перемещении файлов между файловыми системами (например, со съемного носителя на постоянный).
По опции -i запрашивается подтверждение перед замещением существующего файла, опция -f влечет отсутствие подобных запросов (по поводу логики запроса подтверждений см. выше описание команды rm).
Приведем пример употребления утилиты mv. Пусть текущий каталог содержит только файлы x и y и пустой каталог d1 (см. пример 4.43). Тогда после выполнения команд, показанных в пример 4.44, будет создан каталог d2, где окажутся файлы x и y, а каталог d1 исчезнет (см. пример 4.45).
.: d1/ x y ./d1:
Листинг 4.43. Состояние текущего каталога перед перемещением файлов.
ls -RF mv x y d1 mv d1 d2 ls -RF
Листинг 4.44. Использование утилиты mv для перемещения файлов и файловых иерархий.
.: d2/ ./d2: x y
Листинг 4.45. Состояние текущего каталога после перемещения файлов.
Продолжим этот пример двумя одинаковыми командами копирования (см. пример 4.46).
cp -R d2 d1 cp -R d2 d1 ls -RF
Листинг 4.46. Применение утилиты cp для копирования файловых иерархий.
Первая команда скопирует иерархию с корнем d2 во вновь созданный каталог d1, вторая - под d1 (с сохранением имени d2 для корня копии). Результат показан в пример 4.47.
.: d1/ d2/ ./d1: d2/ x y ./d1/d2: x y ./d2: x y
Листинг 4.47. Результат использования утилиты cp для копирования файловых иерархий.
Для перемещения (переименования) одного файла служит функция rename() из репертуара C99 (см. пример 4.48).
#include
Листинг 4.48. Описание функции rename().
Функция rename() обладает частью функциональности служебной программы mv. Она не перемещает файловых иерархий и не раскрывает символьных ссылок, если их имена заданы в качестве аргументов.
Программирование в стандарте POSIX
Чтение и запись данных
Чтение данных из файла выполняют функции read() и fread() (см. пример 5.7).#include
Листинг 5.7. Описание функций read() и fread(). (html, txt)
Функция read() пытается прочитать nbyte байт из файла, ассоциированного с дескриптором fd, и поместить их в буфер buf.
Для файлов, допускающих позиционирование, read() выполняет чтение, начиная со значения индикатора текущей позиции, ассоциированного с дескриптором fd. После завершения операции этот индикатор увеличивается на количество прочитанных байт. Для устройств, не поддерживающих позиционирования (таких, например, как терминал), значение упомянутого индикатора не определено, а чтение выполняется с текущей позиции устройства.
При успешном завершении read() возвращает количество байт, реально прочитанных и помещенных в буфер; это значение может оказаться меньше значения аргумента nbyte, если до конца файла оставалось меньше, чем nbyte байт. Например, если текущая позиция совпадала с концом файла, результат будет равен 0. В случае ошибки возвращается -1.
Функция буферизованного ввода/вывода fread() во многом аналогична read(), но число читаемых байт задается как произведение размера одного элемента (аргумент size) на число элементов (аргумент nitems), а результатом служит количество успешно прочитанных элементов. В стандарте оговаривается, что элементы читаются побайтно.
Число элементов, успешно прочитанных функцией fread(), может быть меньше затребованного, только если достигнут конец файла или произошла ошибка чтения. В таком случае fread() устанавливает для потока индикатор ошибки или конца файла, проверить которые позволяют функции feof() и ferror(), соответственно (см. пример 5.8), возвращая при установленном индикаторе ненулевой результат.
#include
Листинг 5.8. Описание функций feof() и ferror(). (html, txt)
Отметим, что использование функции бинарного ввода fread() ограничивает мобильность приложений, так как результат зависит от размера элементов и порядка байт, поддерживаемого процессором.
Обратим также внимание на некоторые нюансы синхронного и асинхронного ввода с помощью функции read(). При попытке чтения из пустого канала, не открытого кем-либо на запись, результат равен 0 (как признак конца файла). Если пустой канал открыт кем-либо на запись, при установленном флаге O_NONBLOCK возвращается -1 (как признак ошибки EAGAIN); при отсутствии флага O_NONBLOCK процесс (поток управления) блокируется до появления данных в канале. Аналогичным образом устроен ввод из файлов других типов, поддерживающих чтение в асинхронном режиме.
Содержимое символьных ссылок приходится читать особым образом (хотя бы потому, что обычно функция open() раскрывает их, т. е. открывает указуемый файл). Для этого служит функция readlink() (см. пример 5.9). Она помещает содержимое ссылки с именем link_name в буфер buf длины buf_size (если буфер мал, остаток содержимого отбрасывается). Результат равен числу помещенных в буфер байт или -1 в случае неудачи.
#include
Листинг 5.9. Описание функции readlink(). (html, txt)
Следующая программа (см. пример 5.10) переправляет недлинные сообщения с управляющего терминала процесса (ему соответствует специальный файл /dev/tty) на стандартный вывод до тех пор, пока не будет введен символ конца файла.
Листинг 5.10. Пример чтения из файла. (html, txt)
В качестве примера мобильного использования функции fread(), а также функций feof() и ferror(), рассмотрим программу, подсчитывающую число символов, слов и строк в файле – аргументе командной строки (см. пример 5.11).
Листинг 5.11. Программа, подсчитывающая число строк, слов и символов в файле. (html, txt)
Читателю предлагается убрать из цикла проверку feof (fp) и оценить, как изменится обработка интерактивного стандартного ввода.
Для иллюстрации использования функции readlink() напишем программу, выдающую на стандартный вывод содержимое символьных ссылок, имена которых заданы в командной строке (см. пример 5.12).
Листинг 5.12. Пример программы, читающей содержимое символьных ссылок. (html, txt)
Запись данных в файл выполняют функции write() и fwrite() (см. пример 5.13).
#include
Листинг 5.13. Описание функций write() и fwrite(). (html, txt)
Вероятно, лишь несколько моментов, связанных с функциями write() и fwrite(), нуждаются в пояснениях. При записи размер файла может увеличиться. Если файл открыт на добавление, запись производится в его конец.
Листинг 5.10. Пример чтения из файла.
В качестве примера мобильного использования функции fread(), а также функций feof() и ferror(), рассмотрим программу, подсчитывающую число символов, слов и строк в файле – аргументе командной строки (см. пример 5.11).
/* * * * * * * * * * * * * * * * * * * * * */ /* Подсчет символов, слов и строк в файле */ /* * * * * * * * * * * * * * * * * * * * * */ #include
Листинг 5.11. Программа, подсчитывающая число строк, слов и символов в файле.
Читателю предлагается убрать из цикла проверку feof (fp) и оценить, как изменится обработка интерактивного стандартного ввода.
Для иллюстрации использования функции readlink() напишем программу, выдающую на стандартный вывод содержимое символьных ссылок, имена которых заданы в командной строке (см. пример 5.12).
#include
Листинг 5.12. Пример программы, читающей содержимое символьных ссылок.
Запись данных в файл выполняют функции write() и fwrite() (см. пример 5.13).
#include
Листинг 5.13. Описание функций write() и fwrite().
Вероятно, лишь несколько моментов, связанных с функциями write() и fwrite(), нуждаются в пояснениях. При записи размер файла может увеличиться. Если файл открыт на добавление, запись производится в его конец.
При записи в канал, если флаг O_NONBLOCK не установлен, процесс (поток управления) может быть отложен, но после нормального завершения функция write() вернет nbyte. При установленном флаге O_NONBLOCK поведение зависит от значения nbyte и наличия свободного места в канале. Если nbyte не превосходит константы PIPE_BUF, запишется все или ничего (в последнем случае результат будет равен -1).
При попытке записать порцию данных большего размера запишется сколько можно или ничего.
Приведем несколько примеров. Следующая программа (см. пример 5.14) выводит приветствие на управляющий терминал.
#include
Листинг 5.14. Пример программы, использующей функцию write().
Программа prnmyself (см. пример 5.15) выводит свой собственный исходный текст. При этом применяется следующий прием: данные фиксированными порциями читаются из файла и выводятся на терминал; процесс повторяется до тех пор, пока число реально прочитанных байт совпадает с указанным (до обнаружения конца файла).
#include
#define SOURCE_FILE "prnmyself.c" #define C_TERM "/dev/tty"
int main (void) { unsigned char buf [BUFSIZ]; int fdr, fdw; /* Дескрипторы для чтения и записи */ ssize_t nb; if (((fdr = open (SOURCE_FILE, O_RDONLY)) < 0) || ((fdw = open (C_TERM, O_WRONLY)) < 0)) { perror ("OPEN " SOURCE_FILE " or " C_TERM); return (1); } do { if ((nb = read (fdr, buf, BUFSIZ)) < 0) { perror ("READ"); break; } if (write (fdw, buf, nb) != nb) { perror ("WRITE"); break; } } while (nb == BUFSIZ); (void) close (fdw); (void) close (fdr); return (0); }
Листинг 5.15. Пример программы, использующей функции read() и write().
Для буферизованного ввода/вывода байт служат функции fgetc() и fputc(), строки рекомендуется вводить, вызывая функцию fgets(), а выводить с помощью функций fputs() и puts() (см.
пример 5.16).
#include
Листинг 5.16. Описание функций fgetc(), fputc(), fgets(), fputs(), puts().
Описание аналогичных функций для широких символов приведено в пример 5.17.
#include
Листинг 5.17. Описание функций fgetwc(), fputwc(), fgetws(), fputws().
Функция fgetc() пытается прочитать из заданного потока один байт, преобразовать его из типа unsigned char в int и вернуть в качестве результата. В случае ошибки или при достижении конца файла возвращается константа EOF.
Функция fgets() читает из заданного потока и помещает в буфер с адресом s (n - 1) байт или строку, включая символ перевода строки, или байты, оставшиеся до конца файла, если длина строки или число байт до конца меньше (n - 1). После прочитанных добавляется нулевой байт.
При нормальном завершении fgets() возвращает s. В случае ошибки или при достижении конца файла возвращается пустой указатель.
Функция fputc() помещает в поток значение c, предварительно преобразовав его к типу unsigned char. Результатом служит c или EOF.
Функция fputs() выводит в поток цепочку символов (без завершающего нулевого байта) и возвращает неотрицательное целое число или EOF. Функция puts() делает то же для потока stdout, завершая вывод переводом строки.
Функции для работы с потоками широкой ориентации устроены аналогично с точностью до замены int на wint_t, char на wchar_t и EOF на WEOF.
Программа, показанная в пример 5.18, иллюстрирует использование функций fgets() и fputs(). Читателю предлагается сравнить ее с пример 5.10.
#include
/* Программа копирует строки со стандартного ввода на стандартный вывод */ int main (void) { char line [LINE_MAX]; fputs ("Вводите строки\n", stdout); while (fgets (line, sizeof (line), stdin) != NULL) { if ((fputs ("Вы ввели: ", stdout) == EOF) || (fputs (line, stdout) == EOF)) { break; } } return (ferror (stdin) || ferror (stdout)); }
Листинг 5.18. Пример использования функций fgets() и fputs().
Использование функций fgetc() и fputc() иллюстрируется программой, написанной С.В. Самборским (см. пример 5.19). Она выполняет раскодировку файлов формата base64, применяемого, например, в электронной почте.
#include
FILE *input=NULL, *output=NULL; const char str [] ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; unsigned int Prm [256]; /* Таблица перекодировки */ const int WHITE = 100, ERR = 101, END = 102; static void usage (char argv0 []) { fprintf (stderr,"Программа раскодирует файлы формата base64\n"); fprintf (stderr,"Использование:\n%s входной_файл выходной_файл\n", argv0); fprintf (stderr,"Файл должен начинаться с первого символа в кодировке base64.\n"); } int main (int argc, char *argv []) { int n; union { unsigned long l; char c[4]; } a; { int i; for (i = 0; i < 256; i++) Prm [i] = ERR; Prm [' '] = WHITE; Prm ['\t'] = WHITE; Prm ['\n'] = WHITE; Prm ['\r'] = WHITE; Prm ['='] = END; for (i = 0; i < 64; i++) Prm [(int) str [i]] = i; } if (argc != 3) { usage (argv [0]); return (1); } assert (NULL != (input = fopen (argv [1], "r"))); assert (NULL != (output = fopen (argv [2], "w"))); for (a.l = 0, n = 0; ; ) { /* Цикл обработки входного файла */ int c, b, shift; assert (EOF != (c = fgetc (input))); b = Prm [c]; if (WHITE == b) continue; if (END == b) break; if (ERR == b) { fprintf (stderr,"Символ номер %d: %d не входит в кодировку base64\n", n, c); return (1); } n++; assert (b < 64); shift = 6 * (4 - n % 4); if (shift != 24) b = b << shift; a.l += b; if (0 == n % 4) { #if __BYTE_ORDER == __BIG_ENDIAN fputc (a.c[1], output); fputc (a.c[2], output); fputc (a.c[3], output); #elif __BYTE_ORDER == __LITTLE_ENDIAN fputc (a.c[2], output); fputc (a.c[1], output); fputc (a.c[0], output); #elif __BYTE_ORDER == __PDP_ENDIAN fputc (a.c[0], output); fputc (a.c[3], output); fputc (a.c[2], output); #else #error "Unknown endian" #endif a.l = 0; } } { /* Обработка остатка входного файла */ int tl = (((n - 1) % 4) * 6 + 7) / 8; if (tl == 3) { #if __BYTE_ORDER == __BIG_ENDIAN fputc (a.c[1], output); fputc (a.c[2], output); fputc (a.c[3], output); #elif __BYTE_ORDER == __LITTLE_ENDIAN fputc (a.c[2], output); fputc (a.c[1], output); fputc (a.c[0], output); #elif __BYTE_ORDER == __PDP_ENDIAN fputc (a.c[0], output); fputc (a.c[3], output); fputc (a.c[2], output); #else #error "Unknown endian" #endif } if (tl == 2) { #if __BYTE_ORDER == __BIG_ENDIAN fputc (a.c[1], output); fputc (a.c[2], output); #elif __BYTE_ORDER == __LITTLE_ENDIAN fputc (a.c[2], output); fputc (a.c[1], output); #elif __BYTE_ORDER == __PDP_ENDIAN fputc (a.c[0], output); fputc (a.c[3], output); #else #error "Unknown endian" #endif } if (tl == 1) { #if __BYTE_ORDER == __BIG_ENDIAN fputc (a.c[1], output); #elif __BYTE_ORDER == __LITTLE_ENDIAN fputc (a.c[2], output); #elif __BYTE_ORDER == __PDP_ENDIAN fputc (a.c[0], output); #else #error "Unknown endian" #endif } } fclose (input); fclose (output); return (0); }
Листинг 5.19. Пример использования функций fgetc() и fputc().
Приведенный пример показывает, что написание мобильных программ даже для сравнительно простых задач требует заметных усилий. В данном случае пришлось воспользоваться нестандартной возможностью – включаемым файлом
Отметим также стиль обработки ошибочных ситуаций, основанный на применении макроса assert. Для авторов программ такой стиль, безусловно, упрощает жизнь, исходный текст получается более компактным, однако для пользователей сообщение вида
decode64: decode64.c:39: main: Assertion `((void *)0) != (input = fopen (argv [1], "r"))' failed.
может оказаться менее информативным, чем выдача функции perror().
Основные понятия
В стандарте POSIX-2001 предусмотрены две основные группы функций, обслуживающие операции ввода/вывода:Применение потоков не расширяет базовых возможностей ввода/вывода, поскольку в конечном счете используются функции нижнего уровня, но в отличие от них позволяет приложениям получить дополнительный сервис в виде управляемой буферизации и форматного ввода/вывода.
Согласно стандарту POSIX-2001, поток – это объект, служащий для доступа к файлам как к упорядоченной последовательности символов.
Поток представляется структурой типа FILE, с которой ассоциирован соответствующий дескриптор открытого файла. Несколько дескрипторов и/или потоков могут ссылаться на одно описание открытого файла.
Существенны не только содержимое, но и адрес объекта типа FILE; копия подобного объекта не обязательно является корректным представлением потока.
И файловые дескрипторы, и потоки формируются в результате выполнения функций открытия файлов, которые должны предшествовать операциям ввода/вывода. Имеется, однако, три предопределенных потока: стандартный ввод, стандартный вывод и стандартный протокол, открываемые окружением времени выполнения еще перед началом работы C-программ. Для обращения к ним служат указатели на объекты типа FILE с именами, соответственно, stdin, stdout и stderr.
При открытии файлов указывается вид последующих операций ввода/вывода: чтение, запись, модификация (чтение и запись), добавление (запись в конец). Вид операций должен быть согласован с правами доступа к файлу; в противном случае открытие закончится неудачей.
Если файл поддерживает запросы на позиционирование (таковы обычные файлы в противоположность, например, символьным специальным, соответствующим терминалам), то после открытия индикатор текущей позиции устанавливается в начало (на нулевой байт) при условии, что файл не открывали на добавление; в этом случае от реализации зависит, будет ли индикатор указывать на начало или конец файла.
В дальнейшем индикатор текущей позиции смещается под воздействием операций чтения, записи и позиционирования, чтобы упростить последовательное продвижение по файлу.
Потоки могут быть полностью буферизованными, буферизованными построчно и небуферизованными. В первом случае передача байт из файла/в файл осуществляется преимущественно блоками, когда буфер оказывается заполненным. При построчной буферизации передача данных также осуществляется блоками, по достижении символа перевода строки или заполнении буфера. При отсутствии буферизации байты передаются по возможности без задержек.
Стандартом C99 [5] предусмотрены байтные и широкие символы. Соответственно, в стандарте POSIX-2001 введено понятие ориентации потока, которая может быть широкой или байтной. Задает ориентацию первая после открытия файла ввода/вывода операция. Если вначале применяется функция ввода/вывода широких символов, поток получает широкую ориентацию; в противном случае – байтную. Сменить ориентацию можно только повторным открытием файла; применяемые к потоку функции ввода/вывода должны соответствовать его ориентации.
После завершения работы с файлом его необходимо закрыть. При этом не только разрывается связь между файлами с одной стороны и дескрипторами и потоками с другой, но и обеспечивается передача данных, остававшихся буферизованными.
Стандарт POSIX-2001 предусматривает как синхронный, так и асинхронный ввод/вывод.
Операция асинхронного ввода/вывода сама по себе не способна привести к приостановке процесса (потока управления) и лишить его возможности использовать процессор. Это означает, что процесс и асинхронная операция ввода/вывода могут выполняться параллельно.
При синхронном вводе/выводе процесс (поток управления) приостанавливается до завершения запрошенной операции обмена данными.
С одним файлом могут параллельно работать несколько процессов, выполняющихся на разных хостах (узлах сети) и использующих разные буфера. Для корректного обслуживания подобной ситуации в стандарте POSIX-2001 определено (в качестве необязательной возможности) понятие синхронизированного ввода/вывода – так называют механизм, повышающий детерминированность и устойчивость средств обмена данными, поэтому приложение может быть уверено, что данные, которыми оно манипулирует, физически присутствуют на устройствах вторичной (массовой, стабильной) памяти и наряду с файлами находятся в целостном состоянии.
В дальнейшем индикатор текущей позиции смещается под воздействием операций чтения, записи и позиционирования, чтобы упростить последовательное продвижение по файлу.
Потоки могут быть полностью буферизованными, буферизованными построчно и небуферизованными. В первом случае передача байт из файла/в файл осуществляется преимущественно блоками, когда буфер оказывается заполненным. При построчной буферизации передача данных также осуществляется блоками, по достижении символа перевода строки или заполнении буфера. При отсутствии буферизации байты передаются по возможности без задержек.
Стандартом C99 [5] предусмотрены байтные и широкие символы. Соответственно, в стандарте POSIX-2001 введено понятие ориентации потока, которая может быть широкой или байтной. Задает ориентацию первая после открытия файла ввода/вывода операция. Если вначале применяется функция ввода/вывода широких символов, поток получает широкую ориентацию; в противном случае – байтную. Сменить ориентацию можно только повторным открытием файла; применяемые к потоку функции ввода/вывода должны соответствовать его ориентации.
После завершения работы с файлом его необходимо закрыть. При этом не только разрывается связь между файлами с одной стороны и дескрипторами и потоками с другой, но и обеспечивается передача данных, остававшихся буферизованными.
Стандарт POSIX-2001 предусматривает как синхронный, так и асинхронный ввод/вывод.
Операция асинхронного ввода/вывода сама по себе не способна привести к приостановке процесса (потока управления) и лишить его возможности использовать процессор. Это означает, что процесс и асинхронная операция ввода/вывода могут выполняться параллельно.
При синхронном вводе/выводе процесс (поток управления) приостанавливается до завершения запрошенной операции обмена данными.
С одним файлом могут параллельно работать несколько процессов, выполняющихся на разных хостах (узлах сети) и использующих разные буфера. Для корректного обслуживания подобной ситуации в стандарте POSIX-2001 определено (в качестве необязательной возможности) понятие синхронизированного ввода/вывода – так называют механизм, повышающий детерминированность и устойчивость средств обмена данными, поэтому приложение может быть уверено, что данные, которыми оно манипулирует, физически присутствуют на устройствах вторичной (массовой, стабильной) памяти и наряду с файлами находятся в целостном состоянии.
Открытие и закрытие файлов
Как уже указывалось, открытие файла должно предшествовать операциям ввода/вывода, поскольку оно возвращает дескриптор файла или поток, которые используют подобные операции. Для открытия файлов и формирования новых описаний открытых файлов, файловых дескрипторов и потоков служат функции нижнего уровня open() и pipe() (см. пример 5.1), а также функции буферизованного ввода/вывода, показанные в пример 5.2.#include
Листинг 5.1. Описание функций open() и pipe(). (html, txt)
#include
Листинг 5.2. Описание функций fopen(), fdopen(), freopen(). (html, txt)
Функция open() открывает файл с заданным маршрутным именем (первый аргумент, path), создавая для него описание – новое и, следовательно, не разделяемое с другими процессами. Возвращаемый в качестве результата файловый дескриптор является минимальным из числа не используемых в данный момент текущим процессом (при неудаче возвращается -1).
Второй аргумент, oflag, устанавливает флаги статуса файла и определяет допустимые виды операций ввода/вывода. Его значение формируется как побитное ИЛИ перечисленных ниже флагов. Из первых трех флагов должен быть задан ровно один.
O_RDONLY
Открыть файл только на чтение.
O_WRONLY
Открыть файл только на запись.
O_RDWR
Открыть файл на чтение и запись.
Следующие флаги могут комбинироваться произвольным образом.
O_APPEND
Перед каждой записью устанавливать индикатор текущей позиции на конец файла.
O_CREAT
Если файл существует, данный флаг принимается во внимание только при наличии описываемого далее флага O_EXCL. Если файла нет, он создается от имени текущего пользователя. Обратим внимание, что функция open() имеет переменное число аргументов.
При создании файла предполагается, что в качестве третьего аргумента типа mode_t задается режим доступа.
O_EXCL
Если установлены флаги O_CREAT и O_EXCL, а файл существует (хотя бы и в виде символьной ссылки на несуществующий файл), вызов open() завершится неудачей. Проверка существования файла и его создание представляют собой атомарное действие по отношению к попыткам других процессов (потоков управления) выполнить аналогичный запрос.
O_TRUNC
Если файл существует, является обычным и успешно открывается с флагами O_RDWR или O_WRONLY, он опустошается (размер устанавливается равным нулю).
Отметим, что рассмотренная ранее функция creat (path, mode) по определению эквивалентна вызову open (path, O_WRONLY | O_CREAT | O_TRUNC, mode).
Следующий флаг относится к асинхронному вводу/выводу.
O_NONBLOCK
Обычно, если канал открывается только на чтение (или только на запись), процесс (поток управления) блокируется, пока этот же канал не откроют на запись (чтение). При установленном флаге O_NONBLOCK открытие на чтение завершается без задержки, а открытие на запись заканчивается неудачей, если канал еще не открыт кем-либо на чтение.
При открытии специального файла вызов open() завершается только после того, как устройство оказывается в состоянии готовности. Флаг O_NONBLOCK отменяет эту задержку, однако последующее поведе- ние устройства зависит от реализации.
Следующая группа флагов обслуживает синхронизированный ввод/вывод.
O_DSYNC
Операции записи с возвращаемым файловым дескриптором должны завершаться с обеспечением целостности данных.
O_SYNC
Операции записи должны завершаться с обеспечением целостности файла.
O_RSYNC
Операции чтения должны завершаться на уровне целостности, заданном флагами O_DSYNC или O_SYNC.
Смысл последнего из стандартизованных флагов будет пояснен при рассмотрении процессов.
O_NOCTTY
Если открывается специальный файл, соответствующий терминалу, последний не должен становиться управляющим терминалом процесса. Для создания и открытия канала предназначена функция pipe().В массиве fildes она возвращает сразу два дескриптора: fildes [0]служит для чтения, fildes [1] – для записи. Данные читаются в том же порядке, в каком были записаны.
При успешном завершении pipe() возвращает 0, при неудаче – -1.
В массиве fildes она возвращает сразу два дескриптора: fildes [0]служит для чтения, fildes [1] – для записи. Данные читаются в том же порядке, в каком были записаны.
При успешном завершении pipe() возвращает 0, при неудаче – -1.
Функция fopen() из группы буферизованного ввода/вывода по сути аналогична open(), только вместо файлового дескриптора в качестве результата возвращается указатель на объект, служащий для управления сформированным потоком (в случае неудачи результат равен пустому указателю NULL).
Второй аргумент, mode, определяющий допустимые виды операций ввода/вывода, задается как цепочка символов. Он может принимать следующие значения.
"r"
Открыть файл на чтение.
"w"
Опустошить или создать файл, открыв его на запись.
"a"
Открыть или создать файл на запись в конец.
"r+"
Открыть файл на изменение (чтение и запись).
"w+"
Опустошить или создать файл, открыв его на изменение.
"a+"
Открыть или создать файл на изменение с записью в конец.
(Стандарт языка C позволяет приписывать к перечисленным цепочкам символ 'b', который, впрочем, ни на что не влияет.)
Если открытый файл не соответствует интерактивному устройству, ассоциированный с ним поток полностью буферизуется.
Функция fdopen() формирует поток, ассоциированный с дескриптором ранее открытого файла.
Второй аргумент, mode, может принимать те же значения, что и для fopen(), но их трактовка по понятным причинам отличается: существующие файлы не опустошаются, а новые не создаются.
Функция freopen() предназначена для ассоциирования существующего потока (третий аргумент, stream) с заданным файлом (первый аргумент, path) и разрешенными видами операций ввода/вывода (второй аргумент, mode).
В первую очередь freopen() пытается вытолкнуть буфера потока stream и закрыть ассоциированный с ним файл. Неудача данного действия ни на что не влияет.
Затем, аналогично fopen(), открывается заданный файл, только без формирования нового потока; результатом служит stream (лишенный, правда, ориентации) или NULL (в случае неудачи).
Если в качестве первого аргумента функции freopen() задан пустой указатель, делается попытка изменить виды операций, разрешенные для потока stream. (Формально можно считать, что первым аргументом freopen() служит имя файла, ассоциированного с этим потоком.) От реализации зависит, разрешены ли подобные действия вообще и при каких условиях они завершаются успешно.
Для закрытия файлов (точнее, файловых дескрипторов или потоков) предназначены функции close() и fclose() (см. пример 5.3).
#include
Листинг 5.3. Описание функций close() и fclose().
Функция close() освобождает файловый дескриптор fildes, который становится доступным для последующего использования при открытии файлов.
Когда закрывается последний дескриптор, ссылающийся на описание открытого файла, оно освобождается.
Если число жестких ссылок на файл равно нулю и закрывается последний ассоциированный с ним дескриптор, файл перестает быть доступным, а занимавшееся им пространство освобождается.
Когда закрывается последний дескриптор, ассоциированный с каналом, все оставшиеся непрочитанными данные теряются.
Функция close() возвращает 0 в случае успешного завершения и -1 при неудаче.
Функция fclose() по сути аналогична, только она освобождает поток, выталкивая при этом буфера. Признаком успешного завершения также служит 0, признаком неудачи – константа EOF.
Приведем примеры использования описанных функций. Сочетание флагов O_CREAT и O_EXCL функции open() позволяет организовать проверку и создание файлов-замков, для которых важен факт существования в одном экземпляре, а не содержимое (см. пример 5.4).
#include
Листинг 5.4. Пример программы, использующей функции open() и close().
Читателю предлагается выполнить приведенную программу дважды.
Следующая программа иллюстрирует перенаправление стандартного вывода в файл (см. пример 5.5). Ее тоже полезно выполнить дважды и затем самостоятельно осмыслить результаты.
#include
Листинг 5.5. Перенаправление стандартного вывода с помощью функции freopen().
Весьма полезной с практической точки зрения является функция создания и открытия временных файлов tmpfile() (см. пример 5.6).
#include
Листинг 5.6. Описание функции tmpfile().
Временный файл открывается на изменение (w+) и автоматически удаляется после закрытия всех ссылок на него.
Использование функции tmpfile() предпочтительнее генерации «временного» имени с помощью функции tmpnam() и последующего создания файла с этим именем, поскольку в промежутке какой-либо другой процесс может создать одноименный файл.
int open
|
#include |
| Листинг 5.1. Описание функций open() и pipe(). |
| Закрыть окно |
|
#include |
| Листинг 5.2. Описание функций fopen(), fdopen(), freopen(). |
| Закрыть окно |
|
#include |
| Листинг 5.3. Описание функций close() и fclose(). |
| Закрыть окно |
|
#include |
| Листинг 5.4. Пример программы, использующей функции open() и close(). |
| Закрыть окно |
|
#include |
| Листинг 5.5. Перенаправление стандартного вывода с помощью функции freopen(). |
| Закрыть окно |
|
#include |
| Листинг 5.6. Описание функции tmpfile(). |
| Закрыть окно |
|
#include |
| Листинг 5.7. Описание функций read() и fread(). |
| Закрыть окно |
|
#include |
| Листинг 5.8. Описание функций feof() и ferror(). |
| Закрыть окно |
|
#include |
| Листинг 5.9. Описание функции readlink(). |
| Закрыть окно |
|
#include int main (void) { char buf [BUFSIZ]; int fd; ssize_t line_len; /* Открытие на чтение специального файла, */ /* ассоциированного с управляющим терминалом */ if ((fd = open (C_TERM, O_RDONLY)) < 0) { perror ("OPEN"); return (-1); } /* Ввод с терминала */ printf ("Вводите строки\n"); while ((line_len = read (fd, buf, BUFSIZ - 1)) > 0) { buf [line_len] = '\0'; printf ("Вы ввели: %s", buf); } if (line_len == -1) { perror ("READ"); close (fd); return (-1); } return (close (fd)); } |
| Листинг 5.10. Пример чтения из файла. |
| Закрыть окно |
|
/* * * * * * * * * * * * * * * * * * * * * */ /* Подсчет символов, слов и строк в файле */ /* * * * * * * * * * * * * * * * * * * * * */ #include |
| Листинг 5.11. Программа, подсчитывающая число строк, слов и символов в файле. |
| Закрыть окно |
|
#include |
| Листинг 5.12. Пример программы, читающей содержимое символьных ссылок. |
| Закрыть окно |
|
#include |
| Листинг 5.13. Описание функций write() и fwrite(). |
| Закрыть окно |
|
#include |
| Листинг 5.14. Пример программы, использующей функцию write(). |
| Закрыть окно |
|
#include #define SOURCE_FILE "prnmyself.c" #define C_TERM "/dev/tty" int main (void) { unsigned char buf [BUFSIZ]; int fdr, fdw; /* Дескрипторы для чтения и записи */ ssize_t nb; if (((fdr = open (SOURCE_FILE, O_RDONLY)) < 0) || ((fdw = open (C_TERM, O_WRONLY)) < 0)) { perror ("OPEN " SOURCE_FILE " or " C_TERM); return (1); } do { if ((nb = read (fdr, buf, BUFSIZ)) < 0) { perror ("READ"); break; } if (write (fdw, buf, nb) != nb) { perror ("WRITE"); break; } } while (nb == BUFSIZ); (void) close (fdw); (void) close (fdr); return (0); } |
| Листинг 5.15. Пример программы, использующей функции read() и write(). |
| Закрыть окно |
|
#include |
| Листинг 5.16. Описание функций fgetc(), fputc(), fgets(), fputs(), puts(). |
| Закрыть окно |
|
#include |
| Листинг 5.17. Описание функций fgetwc(), fputwc(), fgetws(), fputws(). |
| Закрыть окно |
|
#include /* Программа копирует строки со стандартного ввода на стандартный вывод */ int main (void) { char line [LINE_MAX]; fputs ("Вводите строки\n", stdout); while (fgets (line, sizeof (line), stdin) != NULL) { if ((fputs ("Вы ввели: ", stdout) == EOF) || (fputs (line, stdout) == EOF)) { break; } } return (ferror (stdin) || ferror (stdout)); } |
| Листинг 5.18. Пример использования функций fgets() и fputs(). |
| Закрыть окно |
|
#include FILE *input=NULL, *output=NULL; const char str [] ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; unsigned int Prm [256]; /* Таблица перекодировки */ const int WHITE = 100, ERR = 101, END = 102; static void usage (char argv0 []) { fprintf (stderr,"Программа раскодирует файлы формата base64\n"); fprintf (stderr,"Использование:\n%s входной_файл выходной_файл\n", argv0); fprintf (stderr,"Файл должен начинаться с первого символа в кодировке base64.\n"); } int main (int argc, char *argv []) { int n; union { unsigned long l; char c[4]; } a; { int i; for (i = 0; i < 256; i++) Prm [i] = ERR; Prm [' '] = WHITE; Prm ['\t'] = WHITE; Prm ['\n'] = WHITE; Prm ['\r'] = WHITE; Prm ['='] = END; for (i = 0; i < 64; i++) Prm [(int) str [i]] = i; } if (argc != 3) { usage (argv [0]); return (1); } assert (NULL != (input = fopen (argv [1], "r"))); assert (NULL != (output = fopen (argv [2], "w"))); for (a.l = 0, n = 0; ; ) { /* Цикл обработки входного файла */ int c, b, shift; assert (EOF != (c = fgetc (input))); b = Prm [c]; if (WHITE == b) continue; if (END == b) break; if (ERR == b) { fprintf (stderr,"Символ номер %d: %d не входит в кодировку base64\n", n, c); return (1); } n++; assert (b < 64); shift = 6 * (4 - n % 4); if (shift != 24) b = b << shift; a.l += b; if (0 == n % 4) { #if __BYTE_ORDER == __BIG_ENDIAN fputc (a.c[1], output); fputc (a.c[2], output); fputc (a.c[3], output); #elif __BYTE_ORDER == __LITTLE_ENDIAN fputc (a.c[2], output); fputc (a.c[1], output); fputc (a.c[0], output); #elif __BYTE_ORDER == __PDP_ENDIAN fputc (a.c[0], output); fputc (a.c[3], output); fputc (a.c[2], output); #else #error "Unknown endian" #endif a.l = 0; } } { /* Обработка остатка входного файла */ int tl = (((n - 1) % 4) * 6 + 7) / 8; if (tl == 3) { #if __BYTE_ORDER == __BIG_ENDIAN fputc (a.c[1], output); fputc (a.c[2], output); fputc (a.c[3], output); #elif __BYTE_ORDER == __LITTLE_ENDIAN fputc (a.c[2], output); fputc (a.c[1], output); fputc (a.c[0], output); #elif __BYTE_ORDER == __PDP_ENDIAN fputc (a.c[0], output); fputc (a.c[3], output); fputc (a.c[2], output); #else #error "Unknown endian" #endif } if (tl == 2) { #if __BYTE_ORDER == __BIG_ENDIAN fputc (a.c[1], output); fputc (a.c[2], output); #elif __BYTE_ORDER == __LITTLE_ENDIAN fputc (a.c[2], output); fputc (a.c[1], output); #elif __BYTE_ORDER == __PDP_ENDIAN fputc (a.c[0], output); fputc (a.c[3], output); #else #error "Unknown endian" #endif } if (tl == 1) { #if __BYTE_ORDER == __BIG_ENDIAN fputc (a.c[1], output); #elif __BYTE_ORDER == __LITTLE_ENDIAN fputc (a.c[2], output); #elif __BYTE_ORDER == __PDP_ENDIAN fputc (a.c[0], output); #else #error "Unknown endian" #endif } } fclose (input); fclose (output); return (0); } |
| Листинг 5.19. Пример использования функций fgetc() и fputc(). |
| Закрыть окно |
|
#include |
| Листинг 5.20. Описание функций lseek(), fseek(), ftell(), ftello(), fgetpos(), fsetpos(), rewind(). |
| Закрыть окно |
| (void) lseek (fildes, (off_t) 0, SEEK_SET); (void) lseek (fildes, (off_t) 0, SEEK_END); (void) lseek (fildes, inc, SEEK_CUR); |
| Листинг 5.21. Примеры вызова функции lseek(). |
| Закрыть окно |
|
/* * * * * * * * * * * * * * * * * * * * * * * */ /* Набор функций для занесения текстов в файл, */ /* который составляется из двух частей: */ /* таблицы длин и смещений текстов от начала */ /* файла собственно текстов */ /* */ /* В начало файла помещается специальный */ /* элемент таблицы с магическим числом и общим */ /* количеством текстов */ /* * * * * * * * * * * * * * * * * * * * * * * */ #include int main (int argc, char *argv[]) { if (g_init_add_txt (argc, argv, MAX_TXTS) || g_add_txt (0, "Reference to section number %d in %s\n") || g_add_txt (1, "Data .init section in %s\n")) { (void) g_term_add_txt (); return (-1); } return (g_term_add_txt ()); } |
| Листинг 5.22. Пример использования функций буферизованного ввода/вывода. |
| Закрыть окно |
|
#include |
| Листинг 5.23. Описание функции fcntl(). |
| Закрыть окно |
|
#include int main (void) { int fd; int flags; assert ((fd = open (LOGFILE, O_WRONLY | O_CREAT | O_APPEND, S_IRWXU)) > 2); printf ("До перенаправления стандартного вывода в файл " LOGFILE "\n"); close (1); assert (fcntl (fd, F_DUPFD, 1) == 1); close (fd); printf ("После перенаправления стандартного вывода в файл " LOGFILE "\n"); /* Добавим флаг обеспечения целостности файла */ /* при записи */ assert ((flags = fcntl (1, F_GETFL, 0)) != -1); assert (fcntl (1, F_SETFL, flags | O_SYNC) != -1); fprintf (stderr, "До перенаправления стандартного протокола на стандартный вывод\n"); close (2); assert (fcntl (1, F_DUPFD, 2) == 2); fprintf (stderr, "После перенаправления стандартного протокола на стандартный вывод\n"); close (1); close (2); return (0); } |
| Листинг 5.24. Пример перенаправления стандартного вывода в файл, а стандартного протокола – на стандартный вывод. |
| Закрыть окно |
|
#include |
| Листинг 5.25. Примеры заполнения структуры flock. |
| Закрыть окно |
| if (fcntl (fd, F_SETLK, &lck) != -1) ... if (fcntl (fd, F_SETLKW, &lck) != -1) ... |
| Листинг 5.26. Примеры вызова функции 2 для установки блокировок. |
| Закрыть окно |
|
#include #define LOCKFILE "my_lockfile" /* Программа устанавливает несколько блокировок */ /* на файл LOCKFILE */ int main (void) { int fd; struct flock lck; assert ((fd = open (LOCKFILE, O_RDWR | O_CREAT | O_TRUNC, S_IRWXU)) != -1); /* Установим блокировку на запись на весь файл */ lck.l_type = F_WRLCK; lck.l_whence = SEEK_SET; lck.l_start = (off_t) 0; lck.l_len = (off_t) 0; if (fcntl (fd, F_SETLK, &lck) == -1) { perror ("FCNTL-F_SETLK-1"); close (fd); return (-1); } /* Сделаем размер файла ненулевым */ if (lseek (fd, (off_t) 1024, SEEK_SET) == -1) { perror ("LSEEK"); close (fd); return (-1); } if (write (fd, &lck, sizeof (lck)) != sizeof (lck)) { perror ("WRITE"); close (fd); return (-1); } /* Снимем блокировку в середине файла */ lck.l_type = F_UNLCK; lck.l_whence = SEEK_SET; lck.l_start = (off_t) 512; lck.l_len = (off_t) sizeof (lck); if (fcntl (fd, F_SETLK, &lck) == -1) { perror ("FCNTL-F_SETLK-2"); close (fd); return (-1); } /* Установим блокировку на чтение в конце файла */ lck.l_type = F_RDLCK; lck.l_whence = SEEK_END; lck.l_start = (off_t) -sizeof (lck); lck.l_len = (off_t) sizeof (lck); if (fcntl (fd, F_SETLK, &lck) == -1) { perror ("FCNTL-F_SETLK-2"); close (fd); return (-1); } sleep (10); return (close (fd)); } |
| Листинг 5.27. Пример программы set_locks, устанавливающей блокировки файла. |
| Закрыть окно |
|
#include #define LOCKFILE "my_lockfile" /* Программа выявляет блокировки, установленные */ /* на файл LOCKFILE */ int main (void) { int fd; struct flock lck; assert ((fd = open (LOCKFILE, O_WRONLY)) != -1); (void) printf ("ид-р проц. тип начало длина\n"); /* Начнем с попытки установить блокировку на */ /* весь файл */ lck.l_whence = SEEK_SET; lck.l_start = 0; lck.l_len = 0; do { lck.l_type = F_WRLCK; (void) fcntl (fd, F_GETLK, &lck); if (lck.l_type != F_UNLCK) { (void) printf ("%9d %3c %7ld %5ld\n", lck.l_pid, (lck.l_type == F_WRLCK) ? 'W' : 'R', lck.l_start, lck.l_len); /* Если эта блокировка покрывает остаток файла, */ /* нет нужды выявлять другие блокировки */ if (lck.l_len == 0) break; /* Иначе поищем новую блокировку после найденной */ lck.l_start += lck.l_len; } while (lck.l_type != F_UNLCK); return (close (fd)); } |
| Листинг 5.28. Пример программы test_locks, выявляющей блокировки файла. |
| Закрыть окно |
| ид- р проц. тип начало длина 31174 W 0 512 31174 W 528 496 31174 R 1024 16 31174 W 1040 0 |
| Листинг 5.29. Возможный результат выполнения командной строки set_locks & test_locks. |
| Закрыть окно |
|
#include |
| Листинг 5.30. Описание функций setbuf(), setvbuf() и fflush(). |
| Закрыть окно |
| char name [LINE_MAX]; (void) printf (" Введите Ваше имя: "); (void) fflush (stdout); (void) fgets (name, sizeof (name), stdin); |
| Листинг 5.31. Пример использования функции fflush(). |
| Закрыть окно |
Управляющие операции с файлами и ассоциированными данными
К числу управляющих операций с файлами мы отнесем прежде всего позиционирование. Индикатор текущей позиции может быть опрошен или передвинут при помощи функции нижнего уровня lseek(), а также функций буферизованного ввода/вывода fseek(), ftell(), ftello(), fgetpos(), fsetpos(), rewind() (см. пример 5.20).#include
Листинг 5.20. Описание функций lseek(), fseek(), ftell(), ftello(), fgetpos(), fsetpos(), rewind(). (html, txt)
Функция lseek() устанавливает индикатор текущей позиции следующим образом. Сначала, в зависимости от значения третьего аргумента, whence, выбирается точка отсчета: 0, если это значение равно SEEK_SET, текущая позиция для SEEK_CUR и размер файла для SEEK_END. Затем к точке отсчета прибавляется смещение offset (второй аргумент).
Индикатор текущей позиции можно сместить за конец файла (причем его размер не изменится). Если с этой позиции будет произведена запись, в файле образуется дыра, чтение из которой выдает нулевые байты.
Результатом функции lseek() служит новое значение индикатора текущей позиции, отсчитанное в байтах от начала файла. В случае ошибки возвращается (off_t) (-1), а текущая позиция остается прежней.
Функция fseek() по сути аналогична, только в случае нормального завершения возвращается 0. Кроме того, если поток имеет широкую ориентацию, значение аргумента whence должно равняться SEEK_SET, а значение offset – нулю или результату вызова функции ftell() для того же потока.
Функция ftell() возвращает значение индикатора текущей позиции для заданного потока (в случае ошибки – (long) (-1)). Функция ftello() эквивалентна ftell() с точностью до типа результата; в новых приложениях рекомендуется использовать ftello(), так как эта функция применима к большим файлам.
Функции fgetpos() и fsetpos() являются парными. Первая из них заполняет структуру, на которую указывает аргумент pos, а вторая применяет ее для установки индикатора текущей позиции. Нормальным результатом служит 0.
Функция rewind(), если пренебречь некоторыми тонкостями, сводится к вызову
(void) fseek (stream, 0L, SEEK_SET).
Приведем несколько примеров. В пример 5.21 показана установка индикатора текущей позиции в начало и конец файла, а также его (индикатора) приращение.
(void) lseek (fildes, (off_t) 0, SEEK_SET); (void) lseek (fildes, (off_t) 0, SEEK_END); (void) lseek (fildes, inc, SEEK_CUR);
Листинг 5.21. Примеры вызова функции lseek(). (html, txt)
Применение функций буферизованного ввода/вывода иллюстрируется программой, показанной в пример 5.22. Отметим, что она не является мобильной относительно смены порядка байт, поддерживаемого процессором.
Листинг 5.22. Пример использования функций буферизованного ввода/вывода. (html, txt)
Функция fcntl() предназначена для выполнения разнообразных управляющих операций над открытым файлом (см. пример 5.23).
#include
Листинг 5.23. Описание функции fcntl(). (html, txt)
Аргумент fildes задает дескриптор открытого файла, cmd – управляющую команду, дополнительные данные для которой могут быть переданы в качестве третьего (необязательного) аргумента arg (обычно имеющего тип int). В случае успешного завершения возвращаемое значение естественным образом зависит от команды; при неудаче всегда возвращается -1.
Допустимые команды (значения аргумента cmd) определены в файле
F_DUPFD
Дублирование дескриптора открытого файла: вернуть минимальный среди бывших незанятыми файловый дескриптор (не меньший, чем arg) и ассоциировать его с тем же описанием открытого файла, что и fildes.
F_GETFL
Вернуть флаги статуса и режим доступа к файлу (см. выше описание функции open()). Для выделения режима доступа из возвращаемого значения предоставляется маска O_ACCMODE.

![]() | © 2003-2007 INTUIT.ru. Все права защищены. |
Программирование в стандарте POSIX
Аналоги реляционных операций для текстовых файлов
Служебная программа cutcut -b список [-n] [файл ...] cut -c список [файл ...] cut -f список [-d разделитель] [-s] [файл ...]
используется для выборки байт (опция -b), символов (-c) либо ограниченных разделителями полей (опция -f) с заданными списком номерами из строк исходных файлов, их конкатенации и выдачи на стандартный вывод. Если применить терминологию реляционных баз данных, cut выполняет операцию проекции отношения.
Список, являющийся аргументом опций -b, -c и -f, задается как последовательность разделенных запятыми или пробелами положительных чисел или диапазонов (с естественными умолчаниями, когда границы опущены). Опция -d определяет разделитель полей (по умолчанию это символ табуляции), -n предписывает не разрывать (многобайтные) символы, опция -s в сочетании с -f подавляет вывод строк, в которых не оказалось символов-разделителей (по умолчанию такие строки копируются на стандартный вывод без изменений).
Пример. Чтобы выделить из базы данных пользователей входные имена и идентификаторы, можно воспользоваться командой, показанной в пример 6.40. Начальный фрагмент возможного результата приведен в пример 6.41.
cut -d : -f 1,3 /etc/passwd
Листинг 6.40. Пример использования служебной программы cut. (html, txt)
root:0 bin:1 daemon:2 adm:3 lp:4 sync:5 . . .
Листинг 6.41. Начальный фрагмент возможного результата работы служебной программы cut. (html, txt)
Служебная программа paste
paste [-s] [-d список] файл ...
в терминологии реляционных баз данных осуществляет горизонтальное соединение, конкатенируя соответственные строки исходных файлов и помещая результат на стандартный вывод. При наличии опции -s конкатенируются строки каждого из исходных файлов. Во всех случаях строки склеиваются посредством символа табуляции (по умолчанию) или символов из списка - аргумента опции -d. Список этих символов рассматривается как кольцевой, т. е. будучи исчерпан, он используется повторно. Сочетание \0 в нем трактуется как пустая цепочка, а не как нулевой символ.
В качестве имени исходного файла может быть задан минус, что означает стандартный ввод.
Если минус употреблен многократно, то стандартный ввод читается построчно, циклически по именам - минусам. Любопытно отметить, что стандарт POSIX-2001 предписывает реализациям поддерживать обработку не менее двенадцати исходных файлов.
Рассмотрим несколько примеров. Для выдачи в четыре столбца имен файлов текущего каталога можно воспользоваться конвейером, показанным в пример 6.42.
ls | paste - - - -
Листинг 6.42. Пример использования служебной программы paste. (html, txt)
Для попарного слияния последовательных строк файла целесообразно "сыграть" на том, что список разделителей - кольцевой (см. пример 6.43).
paste -s -d "\0\n" f.txt
Листинг 6.43. Пример использования служебной программы paste с кольцевым списком разделителей. (html, txt)
Если файл не подпадает под определение текстового из-за чрезмерно длинных строк, утилитой cut можно выделить начальные байты, поместив "хвосты" строк в другой файл. В дальнейшем из двух полученных файлов с помощью служебной программы paste можно воссоздать исходный (см. пример 6.44).
cut -b 1-80 -n f > f1.txt cut -b 81- -n f > f2 . . . paste -d '\0' f1.txt f2 > f3
Листинг 6.44. Пример использования служебных программ cut и paste. (html, txt)
Подразумеваемым разделителем при выводе является пробел.
Допустимы следующие опции.
-a номер_файла
В дополнение к обычному выводу выдать строку для каждой непарной строки из файла с указанным номером (1 или 2).
-e цепочка
Заменить пустые поля вывода из списка опции -o заданной цепочкой символов.
-o список
Составлять выходные строки из полей, заданных в списке. Элемент списка имеет вид номер_файла.номер_поля или
-t символ
Использовать символ в качестве разделителя. Каждое вхождение символа в строку значимо. Указанный символ используется как разделитель и при вводе, и при выводе.
-v номер_файла
Вместо подразумеваемого вывода выдавать только непарные строки из файла с указанным номером (1 или 2).
-1 номер_поля
Производить соединение по полю файла1 с заданным номером.
-2 номер_поля
Производить соединение по полю файла2 с заданным номером.
Рассмотрим примеры. Командная строка (см. пример 6.45) выполняет соединение баз данных пользователей и групп, отсортированных в порядке возрастания идентификаторов групп. На стандартный вывод поступают входные имена, имена групп и основные каталоги (см. пример 6.46).
join -1 4 -2 3 -o 1.1,2.1,1.6 -t : passwd.sorted group.sorted
Листинг 6.45. Пример использования служебной программы join.
halt:root:/sbin operator:root:/root root:root:/root shutdown:root:/sbin sync:root:/sbin bin:bin:/bin daemon:daemon:/sbin . . .
Листинг 6.46. Начальный фрагмент возможного результата работы служебной программы join.
Пусть имеется два упорядоченных по алфавиту справочника: номера телефонов и адреса электронной почты (см. пример 6.47). Предполагается, что в качестве разделителя полей применяется символ табуляции. Тогда командная строка, показанная в пример 6.48, позволит получить объединенный справочник (см. пример 6.49).
Имя Номер телефона Иван 123-4567 Петр 123-5678 Яков 123-6789
Имя Адрес электронной почты Иван ivan123@mail.ru Олег oleg@yahoo.com Яков yak@yandex.ru
Листинг 6.47. Возможное содержимое двух справочников с информацией о телефонных номерах и об адресах электронной почты.
join -t '
Листинг 6.48. Еще один пример использования служебной программы join.
Имя Номер телефона Адрес электронной почты Иван 123-45-67 ivan123@mail.ru Олег --------- oleg@yahoo.com Петр 123-56-78 --------- Яков 123-67-89 yak@yandex.ru
Листинг 6.49. Возможный результат работы служебной программы join.
Читателю предлагается самостоятельно выбрать правильный способ задания символа табуляции в качестве аргумента опции -t служебной программы join, а также объяснить, зачем нужен пробел в начале заголовка каждого из справочников.
Использование регулярных выражений
Мы приступаем к описанию наиболее употребительных служебных программ и функций, использующих механизм регулярных выражений. Безусловно, на первое место следует поставить утилиту grep:grep [-E | -F] [-c | -l | -q] [-insvx] -e список_шаблонов ... [-f файл_шаблонов] ... [файл ...]
grep [-E | -F] [-c | -l | -q] [-insvx] [-e список_шаблонов ...] -f файл_шаблонов ... [файл ...]
grep [-E | -F] [-c | -l | -q] [-insvx] список_шаблонов [файл ...]
Она служит для выборки строк исходных файлов, удовлетворяющих хотя бы одному шаблону из заданного списка.
Три приведенные выше варианта вызова служебной программы grep отличаются способом задания списка шаблонов. В первом случае он является аргументом опции -e, во втором извлекается из файла шаблонов - аргумента опции -f, в третьем указывается как самостоятельный аргумент командной строки, но отдельные элементы этого списка всегда разделяются символами перевода строки. Опция -e полезна в ситуациях, когда нужно задать шаблон, начинающийся со знака минус.
По умолчанию шаблоны трактуются как базовые регулярные выражения. Опция -E предписывает переход к расширенным РВ, а опция -F означает, что в качестве шаблонов выступают цепочки символов (и, следовательно, процесс сопоставления существенно упрощается).
Опции -c, -l, -q и -n влияют на выдачу результатов. По умолчанию на стандартный вывод выдаются строки исходных файлов, в которых присутствуют успешно сопоставленные цепочки символов. Опция -c предписывает выдавать только общее число подобных строк, -l - только имена файлов, где имеются успешно сопоставленные строки, -q - только код завершения (0 - есть успешно сопоставленные строки), -n требует ставить ее номер в исходном файле перед каждой выводимой строкой (нумерация строк начинается с 1).
Опции -i, -v и -x воздействуют на процесс сопоставления: -i предписывает не различать при сопоставлении большие и малые буквы, -v - выбирать строки, не удовлетворяющие ни одному из заданных шаблонов, -x - рассматривать только строки, все символы которых участвуют в успешном сопоставлении с одним из шаблонов.
Опция - s подавляет выдачу диагностических сообщений о том, что исходный файл не существует или не доступен на чтение.
Отметим, что опции -F и -q, каждая по-своему, ускоряют работу служебной программы grep: -F упрощает сопоставление, -q позволяет завершить действие после первого успешного сопоставления (и не обрабатывать оставшиеся строки и/или файлы). Сочетание опций -q и -s позволяет также более свободно задавать исходные файлы, не заботясь об их существовании и доступности.
Рассмотрим примеры использования утилиты grep. Для выборки пустых строк из файла стандартного ввода пригодны два шаблона:
grep ^$ grep -v .
Если нужно выбрать строки, имеющие вид abc или def, можно воспользоваться одной из трех команд:
grep -E '^abc$|^def$' grep -F -x 'abc def'
И наконец, пусть в файлах с исходными текстами Фортран-программ требуется найти все строки, содержащие вызовы подпрограмм и не являющиеся комментариями. Для такой выборки "в первом приближении" (учитывая нерегулярний синтаксис Фортрана) подойдет следующая команда:
grep -i '^[^C].* CALL ' *.for
В командных файлах для обработки текстов часто используется потоковый редактор sed:
sed [-n] сценарий [файл ...] sed [-n] [-e сценарий] ... [-f файл_сценария] ... [файл ...]
Редактор sed читает указанные текстовые файлы (по умолчанию - стандартный ввод), выполняет редактирование в соответствии с командами сценария и записывает результат на стандартный вывод. Смысл опций -e и -f аналогичен утилите grep. Опция -n подавляет подразумеваемый вывод и предписывает выдавать только явно отобранные строки.
Сценарий для sed состоит из редактирующих команд (каждая на отдельной строке), имеющих следующий формат:
[адрес [, адрес]] функция [аргумент ...]
Функция имеет здесь однобуквенное обозначение.
В нормальном режиме sed циклически выполняет следующие действия:
Некоторые команды используют хранилище, чтобы запомнить весь буфер или его часть для последующего применения.
Адрес в редактирующей команде sed - это либо десятичное число, означающее номер входной строки в совокупности входных файлов, либо символ $, который обозначает последнюю входную строку, либо контекстный адрес, имеющий вид /базовое_регулярное_выражение/. Контекстный адрес задает первую (начиная с текущей) из строк, успешно сопоставленных с БРВ при движении вперед.
Командная строка без адреса применима к любому буферу, командная строка с одним адресом - к буферу с соответствующим адресом, командная строка с двумя адресами - к буферам с адресами в диапазоне от первого до второго включительно; затем процесс повторяется, начиная с первой строки вслед за выбранным диапазоном.
Очищает буфер.
Некоторые команды используют хранилище, чтобы запомнить весь буфер или его часть для последующего применения.
Адрес в редактирующей команде sed - это либо десятичное число, означающее номер входной строки в совокупности входных файлов, либо символ $, который обозначает последнюю входную строку, либо контекстный адрес, имеющий вид /базовое_регулярное_выражение/. Контекстный адрес задает первую (начиная с текущей) из строк, успешно сопоставленных с БРВ при движении вперед.
Командная строка без адреса применима к любому буферу, командная строка с одним адресом - к буферу с соответствующим адресом, командная строка с двумя адресами - к буферам с адресами в диапазоне от первого до второго включительно; затем процесс повторяется, начиная с первой строки вслед за выбранным диапазоном.
Перечислим команды редактора sed. В скобках указывается максимальное число допустимых адресов для каждой функции.
Аргумент текст состоит из одной или более строк. Все строки, кроме последней, заканчиваются на \, чтобы экранировать символ перевода строки.
(2){ функция функция ... }
Выполнить заданную последовательность функций.
(1)a\ текст
Добавить. Вывести текст перед чтением следующей входной строки.
(2)b [метка]
Перейти к команде :, содержащей метку. Если метка пуста, перейти на конец сценария.
(2)c\ текст
Заменить. Удалить содержимое буфера. При 0 или 1 адресе или в конце двухадресного диапазона вывести текст. Начать новый цикл.
(2)d
Удалить содержимое буфера. Начать новый цикл.
(2)D
Удалить начало буфера до первого перевода строки. Начать новый цикл.
(2)g
Заменить содержимое буфера содержимым хранилища.
(2)G
Добавить к содержимому буфера содержимое хранилища.
(2)h
Заменить содержимое хранилища содержимым буфера.
(2)H
Добавить к содержимому хранилища содержимое буфера.
(1)i\ текст
Вставить. Вывести текст.
(2)l
Вывести буфер, заменяя непечатные символы на пары символов ASCII и разбивая длинные строки.
(2)n
Скопировать буфер на стандартный вывод, если подразумеваемый вывод не подавлен.
Заменить содержимое буфера на следующую входную строку. Если таковой не оказалось, завершить выполнение сценария.
(2)N
Добавить к буферу следующую входную строку, вставив перед ней символ перевода строки. (Текущий номер строки изменяется.) Если входных строк больше нет, завершить выполнение сценария.
(2)p
Скопировать буфер на стандартный вывод.
(2)P
Скопировать начальный сегмент буфера (до первого перевода строки) на стандартный вывод.
(1)q
Выйти. Перейти на конец сценария. Нового цикла не начинать.
(2)r ч_файл
Прочитать содержимое ч_файла. Поместить его на стандартный вывод перед чтением следующей входной строки.
(2)s/БРВ/замена/флаги
Подставить замену вместо фрагментов буфера, отождествленных с БРВ. Флаги могут быть опущены или иметь следующие значения:
g - заменить все вхождения БРВ, а не только первое;
p - если замена произошла, вывести содержимое буфера;
w з_файл - если замена произошла, добавить содержимое буфера к з_файлу.
Вместо символа & в замене подставляется цепочка, отождествленная с БРВ.
(2)t [метка]
Проверить. Перейти к команде :, содержащей метку, если со времени последнего чтения входной строки или последнего выполнения команды t в буфере производились подстановки. Если метка пуста, перейти на конец сценария.
(2)w з_файл
Записать. Добавить содержимое буфера к з_файлу.
(2)x
Обменять содержимое буфера и хранилища.
(2)y/цепочка1/цепочка2/
Заменить все символы буфера, содержащиеся в цепочке1, на соответствующие символы цепочки2. Длины цепочек должны совпадать.
(2)! функция
Отрицание. Применить функцию (или группу, если функция начинается с {) только к строкам, не соответствующим адресам.
(0): метка
Не делает ничего. Содержит лишь метку, на которую может быть осуществлен переход командами t или b.
(1)=
Вывести в качестве отдельной строки номер текущей строки.
(0)
Пустая команда.
(0)#
Управляющий комментарий. Если сценарий начинается с символов #n, подразумеваемый вывод подавляется (что эквивалентно опции -n в командной строке).
В остальных случаях игнорировать # и остаток строки.
Последовательность символов \n успешно сопоставляется с переводом строки. Явный символ перевода строки не должен использоваться в БРВ контекстных адресов и функции замены.
Приведем примеры использования потокового редактора sed. В процессе загрузки ОС Linux выполняются командные строки, аналогичные показанным в пример 6.23.
map=`basename $map | sed -e s/^auto_home/auto.home/ -e s/^auto_mnt/auto.mnt/` cat /etc/auto.master | grep -v '^+' | sed -e '/^#/d' -e '/^$/d'
Листинг 6.23. Пример использования редактора sed.
Первая из них заменяет подчеркивание на точку в именах файлов, обслуживающих автомонтирование файловых систем, вторая отсеивает строки файла auto.master, начинающиеся с символа + (это делает grep -v), комментарии (строки, начинающиеся символом #) и пустые строки.
Следующий вызов sed (см. пример 6.24) сжимает несколько идущих подряд пустых строк в одну.
sed -n ' p /^$/ { # Текущая строка - пустая. # Добавляем следующие строки к буферу, # пока он остается пустым. # Тем самым игнорируются "лишние" пустые # строки. :Empty n /^$/ b Empty # Добавленная строка оказалась непустой. # Выведем ее. p } '
Листинг 6.24. Сжатие пустых строк средствами редактора sed.
Любопытно сопоставить приведенный нами сценарий с примером, включенным в текст стандарта POSIX-2001 (см. пример 6.25). Наш вариант явно проще и короче.
sed -n ' # Выведем непустые строки /./ { p d } # Выведем одну пустую строку, затем # проанализируем следующие. /^$/ p # Прочитаем следующую строку, отбросим # оставшийся перевод строки (пустую строку) # и вернемся к проверке пустой строки. :Empty /^$/ { N s /.// b Empty } # Выведем непустую строку, затем вернемся к # поиску первой пустой. p '
Листинг 6.25. «Стандартный» вариант сжатия пустых строк средствами редактора sed.
Еще одно популярное средство обработки текстовых файлов - служебная программа awk:
awk [-F РРВ] [-v присваивание] ... программа [аргумент ...] awk [-F РРВ] -f программный_файл ... [-v присваивание] ... [аргумент ...]
Утилита awk выполняет программы, написанные на одноименном языке программирования, специально предназначенном для обработки текстов. Программа на языке awk представляет собой последовательность шаблонов и соответствующих действий, которые выполняются при чтении входных данных, успешно сопоставляющихся с шаблоном. Оператор шаблон-действие имеет вид
шаблон { действие }
Ввод для awk делится на записи, разделяемые специальным символом. По умолчанию это перевод строки; в таком случае awk обрабатывает ввод построчно. Разделитель записей можно изменить, переопределив переменную RS. Каждая запись делится на поля, ограниченные разделителями полей (по умолчанию - пробелами или табуляциями). Любой из них можно изменить, переопределив переменную FS или указав опцию -F с аргументом - расширенным регулярным выражением (РРВ). Поля исходных строк доступны по именам $1, $2,...; $0 - вся входная строка.
Каждая исходная строка сопоставляется с каждым из шаблонов; в случае успеха выполняются указанные действия. После сопоставления со всеми шаблонами вводится следующая строка и процесс сопоставления повторяется. Может быть опущен либо шаблон, либо действие, но не оба вместе. Если для данного шаблона не указаны действия, то строка просто копируется на стандартный вывод. Если для действия не определен шаблон, то оно будет выполняться для каждой входной строки. Строки, которые не удалось сопоставить ни одному шаблону, игнорируются.
Действие есть последовательность операторов. Поскольку шаблоны и действия могут быть опущены, то, чтобы различать их в программе, последние надо брать в фигурные скобки. Оператор - это одна из конструкций:
Операторы завершаются точкой с запятой, переводом строки или правой скобкой. Пустой список_выражений означает всю строку. Выражения строятся из цепочек символов и чисел с помощью операций +, -, *, /, %, ^ (возведение в степень) и конкатенации (обозначается пробелом). В них также можно использовать операции из языка C: ++, --, +=, -=, *=, /=, %=, ^=, ? : (условное выражение). Переменные инициализируются пустыми цепочками, могут быть скалярами, элементами массива (обозначается x[i]) или полями. Индексами массива служат любые (не обязательно числовые) цепочки символов, что позволяет реализовать разновидность ассоциативной памяти. Цепочки символов заключаются в двойные кавычки (").
Оператор print выдает свои аргументы на стандартный вывод (или в файл, если присутствует часть >выражение), разделяя их текущим разделителем полей и завершая каждую запись выходным разделителем записей. Оператор printf делает то же, но под управлением формата.
Язык awk содержит большое число встроенных функций. Кратко опишем их.
Математические функции atan2 (y, x), cos (x), sin (x), exp (x), log (x), sqrt (x) не нуждаются в пояснениях. Функция int (x) отбрасывает дробную часть своего аргумента, rand () возвращает псевдослучайное число в диапазоне от
В число функций, оперирующих цепочками символов, входят gsub (РРВ, замена[, цепочка]) и sub (РРВ, замена[, цепочка]) - соответственно, глобальная и однократная замена вхождений РРВ в $0 или цепочку, по аналогии с командой s редактора sed и ее флагом g; index (цепочка, подцепочка) - поиск подцепочки в цепочке; length [([цепочка])] - вычисление длины цепочки-аргумента или $); match> (цепочка, РРВ) - поиск вхождения РРВ в цепочку с установкой значений переменных RSTART и RLENGTH (см. далее); split (цепочка, массив[, РРВ-разделитель]) - расщепление цепочки по полям в элементы массива); sprintf (формат, выражение, выражение, ...) - формирование цепочки символов средствами форматного вывода; substr (цепочка, m[, n]) - выделение n-символьной подцепочки, начинающейся с позиции m; tolower (цепочка) - приведение к строчным буквам; toupper (цепочка) - приведение к прописным буквам.
В языке awk имеются также группа функций ввода/вывода и функции общего назначения. Функция close (выражение) закрывает файл или канал, поименованный заданным выражением, getline [переменная] обеспечивает чтение записи из текущего входного файла (возможно использование конвейера вида выражение | getline [переменная] и перенаправление ввода getline [переменная] < выражение), system (выражение) - выполнение команды, заданной выражением.)
Язык awk допускает определение пользовательских функций, для чего служит конструкция
function имя_функции ([аргумент, ...]) { операторы }
Шаблон в языке awk - это произвольная логическая комбинация, составленная с помощью операций !, ||, && и скобок из расширенных регулярных выражений и выражений сравнения. РРВ обрамляются символами /. Отдельное РРВ в шаблоне сопоставляется со всей строкой. РРВ допускаются и в выражениях сравнения. Шаблон может состоять из двух шаблонов, разделенных запятой; указанные действия выполняются для всех строк между строкой, удовлетворяющей первому шаблону, и строкой, удовлетворяющей второму.
Выражение сравнения - одна из следующих конструкций:
выражение опер_сопост РРВ выражение опер_сравн выражение
Здесь опер_сравн - любая из шести операций сравнения языка C, опер_сопост - это ~ (успешно сопоставляется) или !~ (не сопоставляется).
Условие - арифметическое выражение, выражение сравнения или их логическая комбинация.
Для выполнения каких-либо действий перед чтением первой или после чтения последней исходной строки определены специальные шаблоны BEGIN и END. Шаблон BEGIN следует указывать первым, END - последним.
Присваивания, заданные в командной строке с помощью опции -v, выполняются до начала интерпретации awk-программы (в частности, до действий, ассоциированных с шаблоном BEGIN). Например, для использования символа c в качестве разделителя полей можно указать в командной строке -v 'FS = c'.
В командной строке можно указать также аргументы двух типов - имена файлов с исходными данными и присваивания.
Последние выполняются непосредственно перед чтением указанного следующим исходного файла. В частности, присваивания, заданные перед первым аргументом-файлом выполняются после действий, ассоциированных с шаблоном BEGIN, а те, что расположены в конце командной строки, - перед действиями, ассоциированными с шаблоном END. Если в командной строке нет аргументов-файлов, присваивания выполняются перед обработкой стандартного ввода.
Перечислим специальные переменные awk.
ARGC
Число элементов в массиве ARGV.
ARGV
Массив аргументов командной строки awk, исключая опции и программы.
CONVFMT
Формат для преобразования чисел в цепочки символов (кроме операторов вывода, где используется переменная OFMT, см. далее). По умолчанию - %.6g.
ENVIRON
Массив, представляющий окружение. Индексами служат цепочки символов, совпадающих с именами переменных окружения.
FILENAME
Имя файла, из которого в данный момент производится ввод.
FNR
Порядковый номер текущей записи в текущем исходном файле.
FS
РРВ - разделитель полей во входных данных, по умолчанию - пробел.
NF
Количество полей в текущей записи.
NR
Порядковый номер текущей записи, считая от начала обработки исходных данных.
OFMT
Формат вывода чисел, по умолчанию %.6g.
OFS
Разделитель полей при выводе, по умолчанию - пробел.
ORS
Разделитель записей при выводе, по умолчанию - перевод строки.
RLENGTH
Длина успешно сопоставленной функцией match() цепочки символов.
RS
Первым символом цепочки, представляющей собой значение переменной RS, является разделитель исходных записей (по умолчанию - перевод строки). Если значение RS пусто, между записями может располагаться несколько пустых строк.
RSTART
Начальная позиция успешно сопоставленной функцией match() цепочки символов (считая от 1).
SUBSEP
Цепочка символов - разделитель индексов многомерных массивов; подразумеваемое значение зависит от реализации.
Приведем примеры использования утилиты awk. Сложить числа, стоящие в первом столбце исходного файла, вывести сумму и среднее арифметическое позволяет awk-программа, показанная в пример 6.26.
{ s += $1 } END { print "Сумма:", s, " Среднее арифметическое:", s/NR }
Листинг 6.26. Пример awk-программы, оперирующей с числами.
Командная строка из пример 6.27, служит для вывода тех строк файла f1.txt, у которых первое поле не совпадает с первым полем предыдущей строки.
awk '$1 != prev { print; prev = $1 }' f1.txt
Листинг 6.27. Пример awk-программы, заданной в командной строке.
Чтобы распечатать файл f2.txt, вставляя после слова "Page" номера страниц (начиная с первой), можно воспользоваться awk-программой (предполагается, что она помещена в файл prog.awk) и командной строкой, представленными, соответственно, в листингах пример 6.28 и пример 6.29.
/Page/ { $2 = n++ } { print }
Листинг 6.28. Пример awk-программы, использующей шаблоны.
awk -f prog.awk -v 'n=1' f2.txt
Листинг 6.29. Пример вызова awk-программы, использующей шаблоны.
Программа, показанная в пример 6.30, выводит поля входных записей, по одному на строке.
{ for (i = NF; i > 0; --i) print $i }
Листинг 6.30. Пример awk-программы, использующей оператор цикла.
Промоделировать работу утилиты echo можно с помощью awk-программы (см. пример 6.31).
BEGIN { for (i = 1; i < ARGC; ++i) printf ("%s%s", ARGV [i], i == ARGC - 1 ? "\n" : " ") }
Листинг 6.31. Пример awk-программы, использующей оператор цикла и специальные переменные awk.
Следующая awk-программа (см. пример 6.32) позволяет разложить список поиска, хранящийся в переменной окружения PATH, по элементам массива.
BEGIN { n = split (ENVIRON ["PATH"], path, ":") for (i = 1; i <= n; ++i) print path [i] }
Листинг 6.32. Пример awk-программы, использующей встроенную функцию split().
В пример 6.33 приведен фрагмент командного файла, выполняемого при выключении системы. Здесь можно обратить внимание на разные виды экранирования. (Третье поле в выдаче mount - это точка монтирования.)
# Перемонтируем на чтение все, что еще остается смонтированным. mount | awk '/( \/ |^\/dev\/root)/ { print $3 }' | while read line; do mount -n -o ro,remount $line done
Листинг 6.33. Пример использования утилиты awk в системном командном файле.
Отметим, что в POSIX- 2001 стандартизована весьма развитая версия awk, входной язык которой приближен к языку C.
На уровне функций работа с регулярными выражениями поддержана семейством regex (см. пример 6.34).
#include
Листинг 6.34. Описание функций семейства regex().
Первый член этого семейства, функция regcomp(), компилирует регулярное выражение, заданное аргументом pattern, и помещает результат компиляции в структуру типа regex_t, на которую указывает аргумент preg. Эта структура, описанная в заголовочном файле
size_t re_nsub; /* Число заключенных в скобки подвыражений */
Третий аргумент функции regcomp(), cflags, задается как побитное ИЛИ следующих флагов:
REG_EXTENDED
Использовать расширенные регулярные выражения (подразумеваемый тип регулярных выражений - базовые).
REG_ICASE
При сопоставлении не различать большие и малые буквы.
REG_NOSUB
В regexec() сообщать только об успехе/неудаче сопоставления (и не устанавливать значения поля re_nsub структуры regex_t).
REG_NEWLINE
Изменить трактовку переводов строк (мы не будем на этом останавливаться).
Функция regexec() сопоставляет цепочку символов string со скомпилированным шаблоном, заданным аргументом preg. При успешном выполнении результат равен нулю; в противном случае возвращается ненулевое значение, свидетельствующее о неудаче сопоставления или ошибке. Аргумент eflags - побитное ИЛИ флагов REG_NOTBOL и REG_NOTEOL - определяет, являются ли границы цепочки границами строки, что важно для обработки фиксаторов ^ и $.
Если значение аргумента nmatch равно нулю или при вызове regcomp() был задан флаг REG_NOSUB, аргумент pmatch функции regexec() игнорируется. В противном случае он должен указывать на массив не менее чем из nmatch элементов, который будет заполнен смещениями подцепочек, сопоставленных с заключенными в скобки подвыражениями шаблона (pmatch [0] соответствует всему регулярному выражению, в неиспользуемые элементы помещается -1).
Структурный тип regmatch_t должен включать по крайней мере следующие поля:
regoff_t rm_so; /* Смещение в байтах начала подцепочки от начала цепочки */
regoff_t rm_eo; /* Смещение в байтах первого символа за концом подцепочки от начала цепочки */
Тип regoff_t определяется как целое со знаком, способное вместить любое значение типов off_t и ssize_t.
Функция regfree() освобождает память, запрошенную вызовом regcomp() с тем же значением аргумента preg, которое после этого нельзя использовать как указатель на скомпилированное регулярное выражение.
В файле
Приведем пример использования функций семейства regex() (см. пример 6.35). Обратим внимание на задание флага REG_NOTBOL при повторных обращениях к regexec().
#include
/* Программа ищет все вхождения заданного шаблона во всех входных строках */ /* и выводит успешно сопоставленные подцепочки */
#define PATTERN "[A-Za-z][A-Za-z0-9]{0,31}"
int main (void) { char line [LINE_MAX]; /* Буфер для входных строк */ char *pline; /* Указатель на начало сопоставляемой части строки */ regex_t cere; /* Скомпилированное расширенное регулярное выражение */ regmatch_t pm; /* Структура для запоминания границ сопоставленной подцепочки */ int reerrcode; /* Код ошибки от regcomp или regexec */ char reerrbuf [LINE_MAX]; /* Буфер для строк с сообщениями об ошибках */ int i;
if ((reerrcode = regcomp (&cere, PATTERN, REG_EXTENDED)) != 0) { (void) regerror (reerrcode, &cere, reerrbuf, sizeof (reerrbuf)); fputs (reerrbuf, stderr); fputc ('\n', stderr); regfree (&cere); return (reerrcode); }
fputs ("Вводите строки, сопоставляемые с шаблоном " PATTERN "\n", stdout); while (fgets (line, sizeof (line), stdin) != NULL) { /* Произведем первое сопоставление с прочитанной строкой. */ /* Оно отличается от остальных при наличии в шаблоне фиксатора начала */ reerrcode = regexec (&cere, pline = line, 1, &pm, 0); while (reerrcode == 0) { /* Повторяем, пока сопоставления с остатком строки успешны */ fputs ("Сопоставленная подцепочка: ", stdout); for (pline += pm.rm_so, i = pm.rm_eo - pm.rm_so; i-- > 0; ) { fputc (*pline++, stdout); } fputc ('\n', stdout); reerrcode = regexec (&cere, pline, 1, &pm, REG_NOTBOL); } }
regfree (&cere); return (ferror (stdin) || ferror (stdout)); }
Листинг 6.35. Пример использования функций семейства regex().
Читателю рекомендуется поэкспериментировать с шаблоном PATTERN, пробуя различные варианты, в том числе некорректные.
Идейно простым, но весьма мощным и полезным средством обработки текстовых файлов является служебная программа преобразования символов tr:
tr [-c | -C] [-s] цепочка1 цепочка2 tr -s [-c | -C] цепочка1 tr -d [-c | -C] цепочка1 tr -ds [-c | -C] цепочка1 цепочка2
Она не применяет регулярных выражений как таковых, но в цепочках - аргументах можно употреблять конструкции, аналогичные некоторым односимвольным РВ.
Утилита tr копирует стандартный ввод на стандартный вывод с заменой либо удалением выбранных символов.
При отсутствии опций введенные символы, найденные в цепочке1, заменяются на соответствующие (стоящие на тех же относительных позициях) символы из цепочки2. Опции -c и -C предписывают использовать вместо цепочки1 ее дополнение до множества всех символов; в первом случае дополнение упорядочивается в соответствии с кодировкой, во втором - по алфавиту. По опции -d будут удалены все входные символы, заданные цепочкой1. Опция -s задает сжатие (до одного) последовательностей одинаковых символов, специфицированных последней из цепочек, указанных в командной строке (сжатие производится после каждой замены и/или удаления).
Чтобы задавать в цепочках - аргументах утилиты tr группы символов, можно воспользоваться следующими конструкциями.
c1-c2
Обозначает цепочку символов, лежащих в диапазоне от c1 до c2 включительно.
[:класс_символов:]
Обозначает цепочку символов, принадлежащих указанному классу (см. выше описание базовых регулярных выражений).
[=класс_эквивалентности=]
Обозначает цепочку символов, принадлежащих указанному классу эквивалентности при алфавитном сравнении.
[c*n]
Обозначает символ c, повторенный n раз. Может использоваться только в цепочке2. Если первая цифра в n есть 0, n рассматривается как восьмеричное число; иначе - как десятичное. Нулевое или отсутствующее n воспринимается как "очень много"; эта возможность полезна при дополнении цепочки2 до длины цепочки1.
Обратный слэш можно использовать для задания управляющих символов ('\\', '\a', '\b', '\f', '\n', '\r', '\t', '\v'). Кроме того, \ обозначает код символа, если за ним идут одна, две или три восьмеричные цифры.
Следующая команда (см. пример 6.36) помещает список всех слов из файла f1, по одному на строку, в файл f2 (под словом понимается максимальная последовательность букв).
tr -cs '[:alpha:]' '[\n*]' < f1 > f2
Листинг 6.36. Пример использования служебной программы tr.
Команда, показанная в пример 6.37, переводит большие буквы в малые, попутно сжимая последовательности одинаковых (без учета регистра) букв.
tr -s '[:upper:]' '[:lower:]'
Листинг 6.37. Пример трансляции и сжатия последовательностей символов с использованием служебной программы tr.
Служебная программа uniq
uniq [-c | -d | -u] [-f число] [-s число] [входной_файл [выходной_файл]]
позволяет сократить до одной подряд идущие одинаковые строки (сделать одинаковые строки файла смежными можно с помощью утилиты sort). Опции предоставляют дополнительный сервис.
-c
Перед каждой выходной строкой помещать ее кратность во входном файле.
-d
Подавить вывод неповторяющихся строк.
-f число
При сравнении строк игнорировать заданное число начальных полей. Поле определяется как максимальная цепочка символов, успешно сопоставляемая с базовым регулярным выражением [[:blank:]]*[^[:blank:]]*.
-s число
При сравнении строк игнорировать заданное число начальных символов. При совместном использовании опций -f и -c игнорируется указанное число символов, идущих после заданного числа полей.
-u
Подавить вывод строк, повторявшихся во входном файле.
В качестве примера употребления утилиты uniq приведем конвейер, позволяющий найти десять самых употребительных заголовочных файлов среди включаемых в стандартные заголовочные файлы, расположенные в каталоге /usr/include и его подкаталогах (см. пример 6.38). Результат работы конвейера может выглядеть так, как показано в пример 6.39.
find /usr/include -name '*.h' -exec cat {} \; | tr -d '[:blank:]' | \ grep -E -e '^#include(<.*>|".*")' | sort | uniq -dc | sort -r | head
Листинг 6.38. Пример использования служебной программы uniq.
977 #include"nsISupports.h" 315 #include
Листинг 6.39. Возможный результат работы конвейера, показанного в листинге 6.38.
txt cat
| cat f1.txt - f2.txt - f3. txt cat g1.txt - g2.txt - g3.txt < input.txt cat g1.txt - g2.txt /dev/null g3.txt < input.txt cat f1.txt f2.txt > f1.txt |
| Листинг 6.1. Пример использования утилиты cat. |
| Закрыть окно |
| od -A x -t a ascii.tab |
| Листинг 6.2. Пример использования утилиты od. |
| Закрыть окно |
| 000000 nul soh stx etx eot enq ack bel bs ht nl vt ff cr so si 000010 dle dc1 dc2 dc3 dc4 nak syn etb can em sub esc fs gs rs us 000020 sp ! " # $ % & ' ( ) * + , - . / 000030 0 1 2 3 4 5 6 7 8 9 : ; < = > ? 000040 @ A B C D E F G H I J K L M N O 000050 P Q R S T U V W X Y Z [ \ ] ^ _ 000060 ` a b c d e f g h i j k l m n o 000070 p q r s t u v w x y z { | } ~ del 000080 |
| Листинг 6.3. Возможный результат использования утилиты od. |
| Закрыть окно |
| echo " Reloading automounter: checking\ for changes ... " TMP=/var/run/autofs.tmp getmounts > $TMP for i in /var/run/autofs.*.pid do pid=`head -n 1 $i 2>/dev/null` [ "$pid" = "" ] && continue command=`tail -n +2 $i` if ! grep -q "^$command" $TMP then echo "Stopping automounter: $command" kill -USR2 $pid fi done rm -f $TMP |
| Листинг 6.4. Пример использования служебных программ head и tail. |
| Закрыть окно |
| 2003-10-17 17:24 Список файлов каталога /var/tmp Page 1 1 . 2 .. 3 Blank.ReS 4 Make45.ReS 5 Make46.ReS 6 from_cvs 7 gcc-20032204 8 gcc-3.4-16-jun-2003 9 htdocs 10 rpm-tmp.29785 . . . |
| Листинг 6.5. Начальный фрагмент возможного результата работы служебной программы pr. |
| Закрыть окно |
| wc ascii.od |
| Листинг 6.6. Пример использования утилиты wc. |
| Закрыть окно |
| 9 137 575 ascii.od |
| Листинг 6.7. Возможный результат использования утилиты wc. |
| Закрыть окно |
| 12.05.2000 17: 30 200 125 120 15.05.2000 17:00 130 80 70 17.05.2000 10:30 150 90 70 17.05.2000 21:45 154 99 74 19.05.2000 10:05 158 83 89 21.05.2000 21:00 161 104 64 22.05.2000 21:00 147 104 69 . . . |
| Листинг 6.8. Пример исходных данных для служебной программы sort. |
| Закрыть окно |
| sort -r -k 3,3 pp.txt |
| Листинг 6.9. Пример использования служебной программы sort. |
| Закрыть окно |
| 12.05.2000 17:30 200 125 120 18.11.2000 19:30 172 107 68 04.07.2002 09:00 170 98 85 30.10.2001 13:00 168 94 88 27.12.2000 20:30 166 98 69 23.05.2002 10:00 166 104 56 22.05.2002 10:00 166 103 57 23.10.2001 11:00 165 88 88 . . . |
| Листинг 6.10. Возможный результат работы служебной программы sort. |
| Закрыть окно |
| # Первый вариант - ключ сортировки покрывает # несколько полей sort -r -k 3,5 pp.txt # Второй вариант - используется несколько # ключей сортировки # sort -n -r -k 3,3 -k 4,4 -k 5,5 pp.txt |
| Листинг 6.11. Два варианта использования служебной программы sort. |
| Закрыть окно |
| 12.05.2000 17:30 200 125 120 18.11.2000 19:30 172 107 68 04.07.2002 09:00 170 98 85 30.10.2001 13:00 168 94 88 23.05.2002 10:00 166 104 56 22.05.2002 10:00 166 103 57 27.12.2000 20:30 166 98 69 23.10.2001 11:00 165 88 88 . . . |
| Листинг 6.12. Результат работы служебной программы sort с несколькими ключами сортировки. |
| Закрыть окно |
| # Первый вариант слияния файлов # sort -m -o pp.sorted -n -r -k 3,3 -k 4,4 # -k 5,5 pp2*.sorted # Второй вариант слияния файлов > pp.sorted for f in pp2*.sorted do sort -m -o pp.sorted -n -r -k 3,3\ -k 4,4 -k 5,5 $f pp.sorted done |
| Листинг 6.13. Два варианта слияния файлов с помощью служебной программы sort. |
| Закрыть окно |
| name=pp.sorted if sort -c -r -n -k 3,3 -k 4,4 -k 5,5 $name then echo Данные в файле $ name отсортированы\ верно elif echo Данные в файле $name отсортированы\ неверно fi |
| Листинг 6.14. Проверка правильности упорядоченности строк в файле с помощью служебной программы sort. |
| Закрыть окно |
| sort -k 2.2b,2. 2b f1 f2 |
| Листинг 6.15. Пример использования служебной программы sort с модификаторами в определении ключей. |
| Закрыть окно |
| sort -t ':' -k 3,3n /etc/passwd |
| Листинг 6.16. Пример использования служебной программы sort с опцией -t. |
| Закрыть окно |
| sort -um -k 3.1,3 f.sorted |
| Листинг 6.17. Пример использования служебной программы sort с опциями -m и -u. |
| Закрыть окно |
| diff -r binutils-2_14 binutils-2_14-branch |
| Листинг 6.18. Пример использования служебной программы diff. |
| Закрыть окно |
| diff -r binutils-2_14/bfd/version.h binutils-2_14-branch/bfd/version.h 1c1 < #define BFD_VERSION_DATE 20030612 --- > # define BFD_VERSION_DATE 20031007 Only in binutils-2_14-branch/binutils: ChangeLog Only in binutils-2_14-branch/binutils: arlex.c Only in binutils-2_14-branch/binutils: deflex.c Only in binutils-2_14-branch/binutils: rclex.c Only in binutils-2_14-branch/binutils: syslex.c Only in binutils-2_14-branch: config.guess Only in binutils-2_14-branch/gas: ChangeLog Only in binutils-2_14-branch/gas/config: tc-ns32k.c Only in binutils-2_14-branch/gas: configure Only in binutils-2_14-branch/gas: configure.in Only in binutils-2_14-branch/gas: itbl-lex.c . . . |
| Листинг 6.19. Фрагмент возможного результата работы служебной программы diff. |
| Закрыть окно |
| cmp -l binutils-2_14/bfd/version.h\ binutils-2_14-branch/bfd/version.h |
| Листинг 6.20. Пример использования служебной программы cmp. |
| Закрыть окно |
| 30 60 61 31 66 60 32 61 60 33 62 67 |
| Листинг 6.21. Возможный результат работы служебной программы cmp. |
| Закрыть окно |
| comm - 12 xpg3 svid89 | comm -23 - xcu |
| Листинг 6.22. Пример использования служебной программы comm. |
| Закрыть окно |
| map=`basename $map | sed -e s/^auto_home/auto.home/ -e s/^auto_mnt/auto.mnt/` cat /etc/auto.master | grep -v '^+' | sed -e '/^#/d' -e '/^$/d' |
| Листинг 6.23. Пример использования редактора sed. |
| Закрыть окно |
| sed -n ' p /^$/ { # Текущая строка - пустая. # Добавляем следующие строки к буферу, # пока он остается пустым. # Тем самым игнорируются "лишние" пустые # строки. :Empty n /^$/ b Empty # Добавленная строка оказалась непустой. # Выведем ее. p } ' |
| Листинг 6.24. Сжатие пустых строк средствами редактора sed. |
| Закрыть окно |
| sed -n ' # Выведем непустые строки /./ { p d } # Выведем одну пустую строку, затем # проанализируем следующие. /^$/ p # Прочитаем следующую строку, отбросим # оставшийся перевод строки (пустую строку) # и вернемся к проверке пустой строки. :Empty /^$/ { N s /.// b Empty } # Выведем непустую строку, затем вернемся к # поиску первой пустой. p ' |
| Листинг 6.25. «Стандартный» вариант сжатия пустых строк средствами редактора sed. |
| Закрыть окно |
| { s += $1 } END { print "Сумма:", s, " Среднее арифметическое:", s/NR } |
| Листинг 6. 26. Пример awk-программы, оперирующей с числами. |
| Закрыть окно |
| awk '$1 != prev { print; prev = $1 }' f1.txt |
| Листинг 6.27. Пример awk-программы, заданной в командной строке. |
| Закрыть окно |
| /Page/ { $2 = n++ } { print } |
| Листинг 6. 28. Пример awk-программы, использующей шаблоны. |
| Закрыть окно |
| awk -f prog.awk -v 'n=1' f2.txt |
| Листинг 6.29. Пример вызова awk-программы, использующей шаблоны. |
| Закрыть окно |
| { for (i = NF; i > 0; --i) print $i } |
| Листинг 6.30. Пример awk-программы, использующей оператор цикла. |
| Закрыть окно |
| BEGIN { for (i = 1; i < ARGC; ++i) printf ("%s%s", ARGV [i], i == ARGC - 1 ? "\n" : " ") } |
| Листинг 6.31. Пример awk-программы, использующей оператор цикла и специальные переменные awk. |
| Закрыть окно |
| BEGIN { n = split (ENVIRON ["PATH"], path, ":") for (i = 1; i <= n; ++i) print path [i] } |
| Листинг 6.32. Пример awk-программы, использующей встроенную функцию split(). |
| Закрыть окно |
| # Перемонтируем на чтение все, что еще остается смонтированным. mount | awk '/( \/ |^\/dev\/root)/ { print $3 }' | while read line; do mount -n -o ro,remount $line done |
| Листинг 6.33. Пример использования утилиты awk в системном командном файле. |
| Закрыть окно |
|
#include |
| Листинг 6.34. Описание функций семейства regex(). |
| Закрыть окно |
|
#include /* Программа ищет все вхождения заданного шаблона во всех входных строках */ /* и выводит успешно сопоставленные подцепочки */ #define PATTERN "[A-Za-z][A-Za-z0-9]{0,31}" int main (void) { char line [LINE_MAX]; /* Буфер для входных строк */ char *pline; /* Указатель на начало сопоставляемой части строки */ regex_t cere; /* Скомпилированное расширенное регулярное выражение */ regmatch_t pm; /* Структура для запоминания границ сопоставленной подцепочки */ int reerrcode; /* Код ошибки от regcomp или regexec */ char reerrbuf [LINE_MAX]; /* Буфер для строк с сообщениями об ошибках */ int i; if ((reerrcode = regcomp (&cere, PATTERN, REG_EXTENDED)) != 0) { (void) regerror (reerrcode, &cere, reerrbuf, sizeof (reerrbuf)); fputs (reerrbuf, stderr); fputc ('\n', stderr); regfree (&cere); return (reerrcode); } fputs ("Вводите строки, сопоставляемые с шаблоном " PATTERN "\n", stdout); while (fgets (line, sizeof (line), stdin) != NULL) { /* Произведем первое сопоставление с прочитанной строкой. */ /* Оно отличается от остальных при наличии в шаблоне фиксатора начала */ reerrcode = regexec (&cere, pline = line, 1, &pm, 0); while (reerrcode == 0) { /* Повторяем, пока сопоставления с остатком строки успешны */ fputs ("Сопоставленная подцепочка: ", stdout); for (pline += pm.rm_so, i = pm.rm_eo - pm.rm_so; i-- > 0; ) { fputc (*pline++, stdout); } fputc ('\n', stdout); reerrcode = regexec (&cere, pline, 1, &pm, REG_NOTBOL); } } regfree (&cere); return (ferror (stdin) || ferror (stdout)); } |
| Листинг 6.35. Пример использования функций семейства regex(). |
| Закрыть окно |
| tr -cs '[:alpha:]' '[\n*]' < f1 > f2 |
| Листинг 6.36. Пример использования служебной программы tr. |
| Закрыть окно |
| tr -s '[:upper:]' '[:lower:]' |
| Листинг 6.37. Пример трансляции и сжатия последовательностей символов с использованием служебной программы tr. |
| Закрыть окно |
| find /usr/include -name '*.h' - exec cat {} \; | tr -d '[:blank:]' | \ grep -E -e '^#include(<.*>|".*")' | sort | uniq -dc | sort -r | head |
| Листинг 6.38. Пример использования служебной программы uniq. |
| Закрыть окно |
|
977 #include"nsISupports.h" 315 #include |
| Листинг 6.39. Возможный результат работы конвейера, показанного в листинге 6.38. |
| Закрыть окно |
| cut -d : -f 1,3 /etc/passwd |
| Листинг 6.40. Пример использования служебной программы cut. |
| Закрыть окно |
| root:0 bin:1 daemon:2 adm:3 lp:4 sync:5 . . . |
| Листинг 6.41. Начальный фрагмент возможного результата работы служебной программы cut. |
| Закрыть окно |
| ls | paste - - - - |
| Листинг 6.42. Пример использования служебной программы paste. |
| Закрыть окно |
| paste -s -d "\0\n" f.txt |
| Листинг 6.43. Пример использования служебной программы paste с кольцевым списком разделителей. |
| Закрыть окно |
| cut -b 1-80 -n f > f1. txt cut -b 81- -n f > f2 . . . paste -d '\0' f1.txt f2 > f3 |
| Листинг 6.44. Пример использования служебных программ cut и paste. |
| Закрыть окно |
| join -1 4 -2 3 -o 1.1,2.1,1.6 -t : passwd.sorted group.sorted |
| Листинг 6.45. Пример использования служебной программы join. |
| Закрыть окно |
| halt:root:/sbin operator:root:/root root:root:/root shutdown:root:/sbin sync:root:/sbin bin:bin:/bin daemon:daemon:/sbin . . . |
| Листинг 6.46. Начальный фрагмент возможного результата работы служебной программы join. |
| Закрыть окно |
|
Имя Номер телефона Иван 123-4567 Петр 123-5678 Яков 123-6789 Имя Адрес электронной почты Иван ivan123@mail.ru Олег oleg@yahoo.com Яков yak@yandex.ru |
| Листинг 6.47. Возможное содержимое двух справочников с информацией о телефонных номерах и об адресах электронной почты. |
| Закрыть окно |
|
join -t ' |
| Листинг 6.48. Еще один пример использования служебной программы join. |
| Закрыть окно |
| Имя Номер телефона Адрес электронной почты Иван 123-45-67 ivan123@mail.ru Олег --------- oleg@yahoo.com Петр 123-56-78 --------- Яков 123-67-89 yak@yandex.ru |
| Листинг 6.49. Возможный результат работы служебной программы join. |
| Закрыть окно |
|
#include |
| Листинг 6.50. Описание функции opendir(). |
| Закрыть окно |
|
#include |
| Листинг 6.51. Описание функции rewinddir(). |
| Закрыть окно |
|
#include |
| Листинг 6.52. Описание функции readdir(). |
| Закрыть окно |
|
#include |
| Листинг 6.53. Описание функции closedir(). |
| Закрыть окно |
|
#include |
| Листинг 6.54. Описание функции fnmatch(). |
| Закрыть окно |
|
#include /* Программа сопоставляет имена файлов текущего каталога с заданными шаблонами */ #define SEARCH_DIR "." static void match_names (DIR *dirp, const char *pattern) { struct dirent *dp; rewinddir (dirp); while (errno = 0, (dp = readdir (dirp)) != NULL) { if (fnmatch (pattern, dp->d_name, FNM_PERIOD) == 0) { (void) printf (" %s\n", dp->d_name); } } if (errno != 0) { perror ("Ошибка при чтении каталога " SEARCH_DIR); } } int main (int argc, char *argv []) { DIR *dirp; int i; if ((dirp = opendir (SEARCH_DIR)) == NULL) { perror ("Ошибка при открытии каталога " SEARCH_DIR); return (-1); } for (i = 1; i < argc; i++) { (void) printf ("Файлы каталога " SEARCH_DIR ", удовлетворяющие шаблону %s\n", argv [i]); match_names (dirp, argv [i]); } return (closedir (dirp)); } |
| Листинг 6.55. Пример использования функций, обрабатывающих каталоги. |
| Закрыть окно |
|
#include |
| Листинг 6.56. Описание функций glob() и globfree(). |
| Закрыть окно |
|
#include /* Программа выводит маршрутные имена, сгенерированные по заданным шаблонам */ static int errfunc (const char *epath, int eerrno) { fprintf (stderr, "Ошибка при обработке каталога %s: ", epath); errno = eerrno; perror (NULL); return (0); } int main (int argc, char *argv []) { glob_t gl_buf; int i; for (i = 1; i < argc; i++) { (void) glob (argv [i], ((i == 1) ? } (void) printf ("Маршрутные имена, сгенерированные по заданным шаблонам:\n"); for (i = 0; (unsigned) i < gl_buf.gl_pathc; i++) { (void) printf ("%s\n", gl_buf.gl_pathv [i]); } globfree (&gl_buf); return (0); } |
| Листинг 6.57. Пример программы, использующей функции glob() и globfree(). |
| Закрыть окно |
| if [ "`dirname $RAW`" = "/dev/raw" -a -f /dev/raw ]; then echo $" Please correct your /etc/sysconfig/rawdevices:" echo $" rawdevices are now located in the directory /dev/raw/ " echo $" If the command 'raw' still refers to /dev/raw as a file." echo $" you'll have to upgrade your util-linux package" exit fi |
| Листинг 6.58. Пример использования служебной программы dirname. |
| Закрыть окно |
| gcc -Wall -W -pedantic -o $(basename "$1" .c) $(dirname "$1")/$(basename "$1" .c).c |
| Листинг 6.59. Пример совместного использования служебных программ basename и dirname. |
| Закрыть окно |
Простейшие средства обработки текстовых файлов
Согласно определению, данному в стандарте POSIX-2001, текстовым называется файл, символы которого объединены в строки длиной не более {LINE_MAX}, не содержащие нулевого символа.Вероятно, простейшей операцией с файлами (не обязательно текстовыми) является их выдача на стандартный вывод, что обеспечивается посредством утилиты
cat [-u] [файл ...]
Несмотря на внешнюю простоту, применение служебной программы cat сопряжено с рядом тонкостей. Во-первых, опция -u предписывает передавать на стандартный вывод байты из входных файлов без задержек, это означает, в частности, отмену буферизации стандартного вывода. Во-вторых, если не указаны исходные файлы или в качестве аргумента задан знак минус, используется стандартный ввод. Минус может фигурировать в командной строке несколько раз, стандартный ввод не закрывается и не переоткрывается. Например, допустимы команды, показанные в пример 6.1, причем первая обеспечит ввод двух интерактивных вставок, а вторая эквивалентна третьей (читателю предлагается самостоятельно объяснить данный факт).
cat f1.txt - f2.txt - f3.txt cat g1.txt - g2.txt - g3.txt < input.txt cat g1.txt - g2.txt /dev/null g3.txt < input.txt cat f1.txt f2.txt > f1.txt
Листинг 6.1. Пример использования утилиты cat. (html, txt)
В-третьих, нужно учитывать особенности перенаправления вывода командным интерпретатором shell. В общем случае утилита cat выдает результат конкатенации содержимого файлов-аргументов, но выполнение четвертой команды, показанной в пример 6.1, приведет к утрате содержимого файла f1.txt.
Для просмотра больших текстовых файлов предпочтительнее пользоваться служебной программой
more [опция ...] [файл ...]
Она разбивает вывод на страницы (экраны) и выдает их по явному указанию пользователя (например, по нажатию клавиши пробела).
Программа more - довольно мощный просмотрщик файлов со своей системой команд, которую мы, однако, описывать не будем. Отметим лишь возможности поиска и перемещения по файлам. Трактовка аргументов аналогична cat, а из опций выделим три.
-n число
Задает число строк, выдаваемых на экран.
-p команды more
Команды more выполняются после входа в очередной файл и вывода его первого экрана. Это может быть, к примеру, команда поиска.
-s
Сжимать последовательные пустые строки в одну.
Утилита more часто используется как заключительное звено конвейеров для удобного просмотра результатов.
Для просмотра нетекстовых файлов рекомендуется служебная программа
od [опция ...] [файл ...]
Она выдает на стандартный вывод содержимое исходных файлов в формате, заданном пользователем, а также в виде блоков, каждый из которых в простейшем (и наиболее употребительном) случае состоит из шестнадцати байт и занимает при выводе одну строку. В начале подобной строки располагается смещение блока от начала файла, затем следует содержимое блока. С помощью аргумента опции -A можно задать основание системы счисления для смещений (d - десятичное, o - восьмеричное, x - шестнадцатеричное, n - не выдавать смещение). Формат выдачи содержимого определяется аргументом опции -t. Помимо перечисленных типов могут быть заданы a (именованные символы), c (символы), f (вещественные числа), u (беззнаковые десятичные). За спецификациями d, f, o, u и x может следовать десятичное число - количество байт в одном значении заданного типа; за спецификацией f - символы F, D или L, указывающие тип вещественных чисел (float, double или long double, соответственно), а за спецификациями d, o, u, x - спецификаторы целочисленного типа C (char), S (short), I(int) или L (long).
Рассмотрим пример. Пусть файл ascii.tab содержит байты от
od -A x -t a ascii.tab
Листинг 6.2. Пример использования утилиты od. (html, txt)
Листинг 6.3. Возможный результат использования утилиты od. (html, txt)
Полезная возможность - взглянуть на начало файла, чтобы понять, нуждается ли он в более детальном изучении. Эта возможность реализуется служебной программой head:
head [-n число] [файл ...]
Утилита head копирует на стандартный вывод указанное число (по умолчанию - 10) начальных строк исходных файлов (или весь файл, если он слишком короткий).
-n число
Задает число строк, выдаваемых на экран.
-p команды more
Команды more выполняются после входа в очередной файл и вывода его первого экрана. Это может быть, к примеру, команда поиска.
-s
Сжимать последовательные пустые строки в одну.
Утилита more часто используется как заключительное звено конвейеров для удобного просмотра результатов.
Для просмотра нетекстовых файлов рекомендуется служебная программа
od [опция ...] [файл ...]
Она выдает на стандартный вывод содержимое исходных файлов в формате, заданном пользователем, а также в виде блоков, каждый из которых в простейшем (и наиболее употребительном) случае состоит из шестнадцати байт и занимает при выводе одну строку. В начале подобной строки располагается смещение блока от начала файла, затем следует содержимое блока. С помощью аргумента опции -A можно задать основание системы счисления для смещений (d - десятичное, o - восьмеричное, x - шестнадцатеричное, n - не выдавать смещение). Формат выдачи содержимого определяется аргументом опции -t. Помимо перечисленных типов могут быть заданы a (именованные символы), c (символы), f (вещественные числа), u (беззнаковые десятичные). За спецификациями d, f, o, u и x может следовать десятичное число - количество байт в одном значении заданного типа; за спецификацией f - символы F, D или L, указывающие тип вещественных чисел (float, double или long double, соответственно), а за спецификациями d, o, u, x - спецификаторы целочисленного типа C (char), S (short), I(int) или L (long).
Рассмотрим пример. Пусть файл ascii.tab содержит байты от
od -A x -t a ascii.tab
Листинг 6.2. Пример использования утилиты od.
000000 nul soh stx etx eot enq ack bel bs ht nl vt ff cr so si 000010 dle dc1 dc2 dc3 dc4 nak syn etb can em sub esc fs gs rs us 000020 sp ! " # $ % & ' ( ) * + , - . / 000030 0 1 2 3 4 5 6 7 8 9 : ; < = > ? 000040 @ A B C D E F G H I J K L M N O 000050 P Q R S T U V W X Y Z [ \ ] ^ _ 000060 ` a b c d e f g h i j k l m n o 000070 p q r s t u v w x y z { | } ~ del 000080
является фильтром для печати и оформления страниц. По умолчанию выдача разбивается на страницы, каждая из которых содержит в пятистрочном заголовке свой номер, дату, время и имя файла. Длина страницы 66 строк, включая пять пустых строк хвостовика.
Утилита pr обрабатывает следующие аргументы командной строки.
+номер_страницы
Начать вывод со страницы с заданным номером (по умолчанию с первой).
-число_столбцов
Вывод в заданное число столбцов (по умолчанию в один). При выводе в несколько столбцов автоматически действуют опции -e и -i. Этот аргумент несовместим с опцией -m.
-a
Выдача в несколько столбцов с упорядочением по строкам. Например, если заказана печать в два столбца, первая исходная строка попадает в первый столбец, вторая - во второй, третья - снова в первый и т.п.
-d
Выдача через строку.
-e[символ][число]
Установка табуляции через заданное число позиций, начиная с первой. Символы табуляции во входном файле развертываются в соответствующее количество пробелов. Если задан любой нецифровой символ, он трактуется как символ табуляции во входном файле.
-F
Применять символы перехода к новой странице (по умолчанию переход на новую страницу осуществляется при помощи последовательности переводов строк).
-h заголовок
Использовать заданный заголовок (по умолчанию в качестве заголовка выступает имя исходного файла).
-i[символ][число]
При выводе, где это возможно, последовательности пробельных символов заменяются символами табуляции, при условии, что табуляции установлены через заданное число позиций, начиная с первой. Если задан любой нецифровой символ, он выводится в качестве символа табуляции.
-l число_строк
Установка длины страницы (по умолчанию 66 строк).
-m
Слияние и печать всех файлов параллельно, по одному в столбце. Реализация должна поддерживать слияние по крайней мере девяти файлов.
-n[символ][ширина]
Производится нумерация строк. Под номер отводится поле заданной ширины (по умолчанию - 5). Если задан любой нецифровой символ, то он присоединяется к номеру строки, отделяя ее от последующего текста (подразумеваемым значением является символ табуляции).
Служебная программа sort
sort [-m] [-o выходной_файл] [-bdfinru] [-t символ] [-k определение_ключа] ... [файл ...]
sort -c [-bdfinru] [-t символ] [-k определение_ключа] [файл]
в зависимости от заданных опций выполняет одно из трех возможных действий:
сортировку строк всех исходных файлов с записью результата в выходной файл;
слияние всех исходных (предварительно отсортированных) файлов с записью результата в выходной файл;
При упорядочении используется один или несколько ключей сортировки, выделяемых из каждой вводимой строки. По умолчанию ключ сортировки один - вся строка, а порядок является лексикографическим.
Следующие опции управляют порядком работы утилиты sort.
-c
Проверить, является ли (единственный) исходный файл уже отсортированным. В выходной файл ничего не записывается, результат работы определяется по коду завершения (0 - успешное завершение, 1 - данные в файле не упорядочены должным образом, больше единицы - зафиксирована ошибка).
-m
Слияние исходных файлов, которые предполагаются отсортированными.
-o выходной_файл
Результат направляется не на стандартный вывод, а в выходной_файл, который может совпадать с одним из исходных.
-u
Опция уникальности: из всех совпадающих строк выводить только одну, при наличии опции -c контролировать отсутствие строк с совпадающими ключами сортировки.
Следующие опции изменяют подразумеваемый способ сравнения. Если они употреблены независимо от определения ключей сортировки, то диктуемые ими правила действуют глобально, на все ключи.
-d
"Словарный" порядок: при сравнении являются значимыми только буквы, цифры и пробельные символы.
-f
При сравнении преобразовывать малые буквы в большие.
-i
При сравнении игнорировать непечатные символы.
-n
Числовое сравнение. Ограничить ключ сортировки начальной числовой цепочкой, которая может содержать пробельные символы, знак минус, цифры, символ основания системы счисления и разделители тысяч.
Возможный результат приведен в пример 6.10.
12.05.2000 17:30 200 125 120 15.05.2000 17:00 130 80 70 17.05.2000 10:30 150 90 70 17.05.2000 21:45 154 99 74 19.05.2000 10:05 158 83 89 21.05.2000 21:00 161 104 64 22.05.2000 21:00 147 104 69 . . .
Листинг 6.8. Пример исходных данных для служебной программы sort.
sort -r -k 3,3 pp.txt
Листинг 6.9. Пример использования служебной программы sort.
12.05.2000 17:30 200 125 120 18.11.2000 19:30 172 107 68 04.07.2002 09:00 170 98 85 30.10.2001 13:00 168 94 88 27.12.2000 20:30 166 98 69 23.05.2002 10:00 166 104 56 22.05.2002 10:00 166 103 57 23.10.2001 11:00 165 88 88 . . .
Листинг 6.10. Возможный результат работы служебной программы sort.
Если ключи сортировки равны, строки упорядочиваются с учетом всех байт. В рассматриваемом примере это не очень удобно, поскольку они начинаются с номера дня в месяце. Более естественно либо расширить ключ сортировки путем охвата полей нижнего давления и пульса, либо сделать эти поля дополнительными ключами. Оба варианта показаны в пример 6.11, а результат сортировки - в пример 6.12. Отметим, что второй вариант предпочтительнее, поскольку он устойчив к ошибкам выравнивания столбцов.
# Первый вариант - ключ сортировки покрывает # несколько полей sort -r -k 3,5 pp.txt # Второй вариант - используется несколько # ключей сортировки # sort -n -r -k 3,3 -k 4,4 -k 5,5 pp.txt
Листинг 6.11. Два варианта использования служебной программы sort.
12.05.2000 17:30 200 125 120 18.11.2000 19:30 172 107 68 04.07.2002 09:00 170 98 85 30.10.2001 13:00 168 94 88 23.05.2002 10:00 166 104 56 22.05.2002 10:00 166 103 57 27.12.2000 20:30 166 98 69 23.10.2001 11:00 165 88 88 . . .
Листинг 6.12. Результат работы служебной программы sort с несколькими ключами сортировки.
Предположим теперь, что данные о давлении и пульсе разбиты по годам и в уже отсортированном виде хранятся в файлах pp2000.sorted, pp2001.sorted и т.д. Для их слияния можно воспользоваться командной строкой или циклом из пример 6.13. Опять-таки второй вариант предпочтительнее по причине его устойчивости к количеству исходных файлов.
Игнорировать пробельные символы в конце строк; остальные цепочки пробельных символов считать равными.
-c
Производить вывод в формате, обеспечивающем три строки контекста.
-C число
Производить вывод в формате, обеспечивающем заданное число строк контекста.
-e
Производить вывод в формате, пригодном для подачи на вход редактора ed и преобразования файла1 в файл2.
-f
Производить вывод в альтернативном формате, напоминающем -e, но в обратном порядке и не предназначенном для подачи на вход редактора ed.
-r
Если файл1 и файл2 представляют собой каталоги, применять утилиту diff рекурсивно к одноименным и однотипным обычным файлам и каталогам. Если только один из файлов является каталогом, в нем сравнивается "тезка" другого исходного файла.
В качестве примера использования служебной программы diff сравним две версии бинарных утилит (см. пример 6.18). Начальный фрагмент результата показан в пример 6.19. Видно, что различия, по сути, сводятся к добавлению новых файлов.
diff -r binutils-2_14 binutils-2_14-branch
Листинг 6.18. Пример использования служебной программы diff.
diff -r binutils-2_14/bfd/version.h binutils-2_14-branch/bfd/version.h 1c1 < #define BFD_VERSION_DATE 20030612 --- > #define BFD_VERSION_DATE 20031007 Only in binutils-2_14-branch/binutils: ChangeLog Only in binutils-2_14-branch/binutils: arlex.c Only in binutils-2_14-branch/binutils: deflex.c Only in binutils-2_14-branch/binutils: rclex.c Only in binutils-2_14-branch/binutils: syslex.c Only in binutils-2_14-branch: config.guess Only in binutils-2_14-branch/gas: ChangeLog Only in binutils-2_14-branch/gas/config: tc-ns32k.c Only in binutils-2_14-branch/gas: configure Only in binutils-2_14-branch/gas: configure.in Only in binutils-2_14-branch/gas: itbl-lex.c . . .
Листинг 6.19. Фрагмент возможного результата работы служебной программы diff.
Если нужно проверить два файла на совпадение, предпочтительнее воспользоваться не утилитой diff, а более простой и быстрой служебной программой cmp:
Регулярные выражения
Понятие регулярного выражения (РВ) - одно из важнейших для программ обработки текстовых файлов. Согласно стандарту POSIX-2001, регулярное выражение - это шаблон, служащий для выборки определенных цепочек символов из множества подобных цепочек. Говорят, что выбранные цепочки удовлетворяют РВ (успешно с ним сопоставляются).Когда хотят подчеркнуть составной характер РВ, используют термин "полное регулярное выражение". Под этим понимается шаблон, заданный как конкатенация одного или нескольких элементарных РВ.
Стандарт накладывает на сложность (полных) РВ единственное ограничение: реализация должна поддерживать любое РВ, длина которого не превышает 256 байт.
Оговаривается, что при наличии альтернатив выбирается самая левая из возможных цепочек, имеющая к тому же максимальную длину.
Различают базовые (БРВ) и расширенные регулярные выражения (РРВ). В большинстве случаев используются БРВ, они и будут описаны в первую очередь.
Минимальным элементом БРВ являются односимвольные БРВ, т. е. БРВ, которым удовлетворяют цепочки из одного символа. Односимвольные БРВ строятся по следующим правилам.
. [ \
Эти символы имеют специальный смысл, за исключением случая, когда они заключены в квадратные скобки.
*
Символ имеет специальный смысл, если он не заключен в квадратные скобки или не является первым символом БРВ.
^
Символ имеет специальный смысл в начале полного БРВ или в квадратных скобках сразу после открывающей скобки.
$
Символ имеет специальный смысл в конце полного БРВ.
Если цепочка начинается с символа ^, то БРВ успешно сопоставляется с любым не входящим в данный набор символом.
Внутри квадратных скобок можно использовать также конструкции, описываемые ниже.
Диапазон символов можно задать с помощью знака -. Например, БРВ [0-9] эквивалентно [0123456789]. Минус теряет свой специальный смысл, если он стоит в начале (допустимо после ^) или в конце цепочки символов в квадратных скобках либо использован в качестве границы диапазона. Так, БРВ [%--] успешно сопоставляется со всеми символами от процента до минуса включительно.
Закрывающая квадратная скобка не рассматривается как окончание цепочки символов, если она стоит вначале (быть может, после ^). Таким образом, БРВ []a-f] определяет либо закрывающую квадратную скобку, либо любой символ от a до f включительно.
Стандарт POSIX-2001 предусматривает поддержку классов символов (таких, например, как буквы, цифры, знаки пунктуации, пробельные символы, управляющие символы и т.д.). Класс символов задается своим именем, заключенным в квадратные скобки с двоеточиями. Должны поддерживаться по крайней мере следующие классы:
[:alnum:] [:cntrl:] [:lower:] [:space:] [:alpha:] [:digit:] [:print:] [:upper:] [:blank:] [:graph:] [:punct:] [:xdigit:]
Например, БРВ [^[:alnum:]] удовлетворяют символы, отличные от букв и цифр.
В некоторых языках элемент алфавитного сравнения может задаваться несколькими символами. Такие элементы следует заключать в квадратные скобки с точками: [[.ch.]].
Если определены классы элементов, эквивалентных при алфавитном сравнении, то подобный класс можно задать, заключив любой его элемент в квадратные скобки со знаками равенства: ([= и =]).
Таковы правила построения заключенных в квадратные скобки односимвольных БРВ.
Построение многосимвольных БРВ из односимвольных регламентируется следующим образом.
Обратной ссылке удовлетворяет такая же цепочка символов, что была успешно сопоставлена подвыражением, открывающимся экранированной скобкой номер n, считая от начала полного БРВ. Например, БРВ \(.\)\1 удовлетворяют пары одинаковых символов.
Операции построения многосимвольных БРВ описаны выше в порядке убывания приоритетов. Наибольшим приоритетом обладают подвыражения и обратные ссылки, далее следуют повторители - звездочка и интервальные выражения, затем конкатенация и, наконец, фиксация границ.
Опишем отличия расширенных регулярных выражений (РРВ) от базовых.
Таковы правила построения и обработки регулярных выражений, зафиксированные в стандарте POSIX-2001. Отметим, что базовые регулярные выражения не являются подмножеством расширенных, хотя число специфических особенностей БРВ невелико.
Средства обработки каталогов
Обработка каталогов, как и обычных файлов, начинается с их открытия. Для этого предназначена функция opendir() (см. пример 6.50).#include
Листинг 6.50. Описание функции opendir(). (html, txt)
После открытия текущим становится первый элемент каталога. Если в дальнейшем понадобится вновь позиционироваться на первый элемент, можно воспользоваться функцией rewinddir() (см. пример 6.51).
#include
Листинг 6.51. Описание функции rewinddir(). (html, txt)
Чтение элементов каталога выполняет функция readdir() (см. пример 6.52), которая возвращает указатель на структуру, представляющую текущий элемент каталога; после ее завершения текущим становится следующий элемент достижении конца каталога и в случае ошибки возвращается пустой указатель. Следовательно, если приложению необходимо различать обе ситуации, оно должно обнулить значение переменной errno перед вызовом readdir(), а затем, если результат равен NULL, проанализировать это значение.
#include
Листинг 6.52. Описание функции readdir(). (html, txt)
Согласно стандарту POSIX-2001, структура dirent содержит по крайней мере одно поле:
char d_name []; /* Имя файла */
В качестве необязательного описано еще одно поле:
ino_t d_ino; /* Порядковый номер файла */
Если элемент каталога представляет символьную ссылку, значение этого поля не определено.
Следует учитывать, что указатель, возвращаемый функцией readdir(), может ссылаться на область памяти, перезаписываемую другими обращениями к readdir() с тем же значением аргумента dirp. Кроме того, нужно помнить и о том, что содержимое читаемого каталога асинхронно изменяют другие процессы (потоки управления), создающие и удаляющие файлы.
После завершения работы с каталогом его следует закрыть с помощью функции closedir() (см. пример 6.53), возвращающей
#include
Листинг 6.53. Описание функции closedir(). (html, txt)
Нередко чтение элементов каталога и сопоставление с шаблоном имен файлов сочетаются (см. выше раздел "Генерация маршрутных имен файлов"). Подобное сопоставление реализует функция fnmatch() (см. пример 6.54).
#include
Листинг 6.54. Описание функции fnmatch(). (html, txt)
На процесс сопоставления имени file_name с шаблоном file_pattern влияют следующие флаги:
FNM_PATHNAME
Трактовать имя file_name как маршрутное. Символу / в имени должен явным образом сопоставляться этот же символ в шаблоне (а не *, ? или выражение в квадратных скобках). Если флаг не установлен, символ / трактуется наравне с другими.
FNM_NOESCAPE
При наличии этого флага символ \ трактуется наравне с другими. В противном случае он играет экранирующую роль.
FNM_PERIOD
Если этот флаг установлен, точка в начале имени файла должна сопоставляться с точкой в шаблоне. Иначе точка в начале имени сопоставляется на общих основаниях.
В случае успешного сопоставления функция fnmatch() возвращает ноль, при неудаче результат равен FNM_NOMATCH, в случае ошибки возвращается другое ненулевое значение.
В качестве примера употребления описанных функций рассмотрим программу, которая выводит имена файлов текущего каталога, удовлетворяющие заданным в командной строке шаблонам (см. пример 6.55).
Листинг 6.55. Пример использования функций, обрабатывающих каталоги. (html, txt)
Программирование в стандарте POSIX
Опрос и изменение атрибутов процессов
Для выдачи информации о процессах служит утилита ps:ps [-aA] [-defl] [-G список_групп] [-o формат] ... [-p список_процессов] [-t список_терминалов] [-U список_пользователей] -g список_групп] [-n список_имен] [-u список_пользователей]
По умолчанию информация выдается обо всех процессах, имеющих тот же действующий идентификатор и тот же управляющий терминал, что и у текущего пользователя. При этом выводятся идентификатор процесса, имя управляющего терминала, истраченное к данному моменту процессорное время и имя программы (команды), выполняемой в рамках процесса. Например, выдача команды ps может выглядеть так, как показано в листинге 7.1.
PID TTY TIME CMD 1594 ttyS4 00:00:02 sh 1645 ttyS4 00:00:00 sh 1654 ttyS4 00:02:45 rk.20.01 18356 ttyS4 00:00:00 prconecm 18357 ttyS4 00:00:00 sh 18358 ttyS4 00:00:00 ps
Листинг 7.1. Возможный результат использования утилиты ps. (html, txt)
Если нужна более подробная информация о более широком наборе процессов, следует пользоваться опциями. Перечислим наиболее употребительные из них.
-a
Выдать информацию обо всех процессах, ассоциированных с терминалами. Заметим, однако, что, во-первых, при получении информации о процессах контролируются права доступа (например, пользователю будут видны только порожденные им процессы), а во-вторых, по стандарту реализация может не включать в выдаваемый список лидеров сеансов.
-A
Выдать информацию обо всех процессах.
-G список_групп
Выдать информацию о процессах с заданными реальными идентификаторами групп.
-o формат
Выдать информацию о процессах в заданном формате.
-p список_процессов
Выдать информацию о процессах с заданными идентификаторами.
-t список_терминалов
Выдать информацию о процессах, ассоциированных с заданными терминалами. Способ задания терминалов зависит от реализации. Обычно указывается имя специального файла, например, ttyS4, или, если имя начинается с tty, просто S4.
-U список_пользователей
Выдать информацию о процессах с заданными реальными идентификаторами пользователей (они могут указываться и в виде входных имен).
Все перечисленные опции, кроме -o, ведают отбором процессов. Если задан целый ряд подобных опций, выводится информация обо всех специфицированных ими процессах.
Опции -o (их в командной строке может быть несколько) позволяют задать выходной формат информации о процессах. Указываются выводимые поля и, если нужно, отличные от подразумеваемых тексты соответствующих им заголовков, отделяющиеся от имени поля знаком равенства и продолжающиеся до конца аргумента опции -o.
Перечислим имена полей, которые могут указываться в выходном формате, и соответствующие им подразумеваемые заголовки.
ruser (RUSER)
Выдавать реальный идентификатор пользователя процесса (в символьной или числовой форме).
user (USER)
Действующий идентификатор пользователя процесса.
rgroup (RGROUP)
Реальный идентификатор группы процесса.
group (GROUP)
Действующий идентификатор группы процесса.
pid (PID)
Идентификатор процесса.
ppid (PPID)
Идентификатор родительского процесса.
pgid (PGID)
Идентификатор группы процессов.
pcpu (%CPU)
Процент процессорного времени, потребляемый процессом.
vsz (VSZ)
Размер процесса в (виртуальной) памяти (в килобайтных блоках).
nice (NI)
Число, используемое как рекомендация системе при планировании процессов. Меньшие значения соответствуют более приоритетным процессам.
etime (ELAPSED)
Астрономическое время, прошедшее с момента запуска процесса.
time (TIME)
Процессорное время, потребленное процессом.
tty (TT)
Имя управляющего терминала.
comm (COMMAND)
Имя выполняемой команды (argv [0]).
args (COMMAND)
Выполняемая командная строка.
Листинг 7.3. Фрагмент возможного результата использования утилиты ps.
Для опроса идентификаторов процесса, родительского процесса и группы процессов предусмотрены функции getpid() и getppid() getpgrp() (см. листинг 7.4).
#include
#include
#include
Листинг 7.4. getpid(), getppid() и getpgrp().
По стандарту эти функции всегда завершаются успешно, поэтому ошибочных кодов возврата не предусмотрено.
Для установки идентификатора группы процессов в целях управления заданиями предназначена функция setpgid() (см. листинг 7.5).
#include
Листинг 7.5. Описание функции setpgid().
Выполнение функции setpgid() влечет либо присоединение к существующей группе процессов, либо создание новой группы в рамках сеанса, в который входит вызывающий процесс. Процесс может установить идентификатор группы для себя или для порожденного процесса. Нельзя изменить идентификатор группы процессов лидера сеанса.
В случае успешного завершения функции setpgid() (результат при этом равен нулю) идентификатор группы процессов устанавливается равным pgid для заданного аргументом pid процесса. Если значение pid равно нулю, установка производится для вызывающего процесса. А если значение pgid равно нулю, то в качестве идентификатора группы процессов используется идентификатор процесса, заданного аргументом pid.
Для создания сеанса и установки идентификатора группы процессов служит функция setsid() (см. листинг 7.6).
#include
Листинг 7.6. Описание функции setsid().
Если вызывающий процесс не является лидером группы, в результате выполнения функции setsid() будет создан новый сеанс, причем вызывающий процесс станет лидером этого сеанса, равно как и лидером новой группы процессов (без управляющего терминала и без других процессов в группе и сеансе).
Программа, показанная в листинге 7.7, служит примером использования (в том числе некорректного) описанных функций.
листинг 7.9). Как и getpid(), они всегда завершаются успешно.
#include
#include
#include
#include
Листинг 7.9. Описание функций getuid(), geteuid(), getgid(), getegid().
Более сложный интерфейс имеет функция getgroups(), предназначенная для получения идентификаторов дополнительных групп вызывающего процесса (см. листинг 7.10). Эти идентификаторы (в их число может входить и действующий идентификатор группы процесса) помещаются в массив grouplist.
#include
Листинг 7.10. Описание функции getgroups().
Аргумент gidsetsize задает число элементов в массиве grouplist, а реальное количество записанных идентификаторов групп возвращается в виде результата функции. Если в качестве значения gidsetsize задать нуль, getgroups() выдаст количество дополнительных групп, не модифицируя массив grouplist.
Переустановить действующий идентификатор пользователя вызывающего процесса позволяют функции setuid() и seteuid() (см. листинг 7.11). Операция разрешена, если реальный или сохраненный ПДП-идентификатор пользователя совпадает со значением аргумента uid. Помимо этого, обладающие соответствующими привилегиями процессы с помощью функции setuid() могут установить по значению uid все три идентификатора пользователя процесса – реальный, действующий и сохраненный.
#include
#include
Листинг 7.11. Описание функций setuid() и seteuid().
Для непривилегированных процессов по соображениям мобильности рекомендуется использование функции seteuid().
Аналогичные функции для переустановки идентификаторов группы процесса показаны в листинге 7.12.
#include
#include
Листинг 7.12. Описание функций setgid() и setegid().
Листинг 7.13. Пример использования функций опроса и изменения идентификаторов пользователя процесса.
Если эту программу запустить от имени обычного пользователя, результат может выглядеть так, как показано в листинге 7.14.
Идентификаторы пользователя текущего процесса: реальный: 108, действующий: 108 Идентификаторы группы текущего процесса: реальный: 3, действующий: 3 Количество дополнительных групп текущего процесса: 1 Идентификаторы дополнительных групп текущего процесса: 3 setuid (1): Operation not permitted Идентификаторы пользователя текущего процесса после первой смены: реальный: 108, действующий: 108 Идентификаторы пользователя текущего процесса после второй смены: реальный: 108, действующий: 108 seteuid (1): Operation not permitted Идентификаторы пользователя текущего процесса после третьей смены: реальный: 108, действующий: 108
Листинг 7.14. Возможный результат работы программы, показанной в листинге 7.13 и запущенной от имени обычного пользователя.
После запуска той же программы от имени суперпользователя может получиться результат, показанный в листинге 7.15.
Идентификаторы пользователя текущего процесса: реальный: 0, действующий: 0 Идентификаторы группы текущего процесса: реальный: 0, действующий: 0 Количество дополнительных групп текущего процесса: 7 Идентификаторы дополнительных групп текущего процесса: 0 1 2 3 4 6 10 Идентификаторы пользователя текущего процесса после первой смены: реальный: 1, действующий: 1 setuid (uid): Operation not permitted Идентификаторы пользователя текущего процесса после второй смены: реальный: 1, действующий: 1 Идентификаторы пользователя текущего процесса после третьей смены: реальный: 1, действующий: 1
Листинг 7.15. Возможный результат работы программы, показанной в листинге 7.13 и запущенной от имени суперпользователя.
Утерять статус суперпользователя легко, а вернуть трудно...
Наконец, сделаем владельцем выполнимого файла рассматриваемой программы пользователя с идентификатором 1, то же проделаем с владеющей группой, взведем в режиме этого файла биты ПДИП и ПДИГ(на ОС Linux можно воспользоваться командой chmod ug+s) и вновь запустим его от имени обычного пользователя (см.
Основные понятия
Напомним данное в стандарте POSIX-2001 определение процесса. Процесс – это адресное пространство вместе с выполняемыми в нем потоками управления, а также системными ресурсами, которые этим потокам требуются.Каждый процесс обладает целым рядом атрибутов. Важнейшим среди них является идентификатор процесса – положительное целое число, однозначно идентифицирующее процесс в течение времени его жизни.
Процессы могут создаваться и завершаться. Время жизни процесса – это период от его создания до возврата идентификатора операционной системе.
После того как процесс создан с помощью функции fork(), он считается активным. До завершения процесса в его рамках существуют по крайней мере один поток управления и адресное пространство.
Процесс может перейти в неактивное состояние, и тогда некоторые из его ресурсов (но не идентификатор) могут быть возвращены системе. Когда по отношению к неактивному процессу выполняют функцию семейства wait(), системе возвращаются остальные ресурсы. Последний из них – идентификатор процесса, и на этом время жизни процесса заканчивается.
Завершение процесса может быть нормальным или аварийным. Нормальное завершение происходит, в частности, при возврате из функции main().
Зомби-процесс – завершившийся процесс, подлежащий ликвидации после того, как код его завершения будет передан ожидающему этого другому процессу.
Процесс, создавший данный, называется родительским, в течение времени жизни которого существует идентификатор родительского процесса. По завершении времени жизни указанного процесса родительским становится определяемый реализацией системный процесс.
Группа – совокупность процессов, допускающая согласованную доставку сигналов. У каждой группы имеется уникальный положительный целочисленный идентификатор, представляющий ее в течение времени ее жизни. В такой роли выступает идентификатор процесса, именуемого лидером группы.
Временем жизни группы процессов называют период от создания группы до момента, когда ее покидает последний процесс (по причине завершения или смены группы).
Задание – это набор процессов, составляющих конвейер, а также порожденных ими процессов, входящих в одну группу.
Под управлением заданиями подразумеваются предоставленные пользователям средства выборочно (при)останавливать и затем продолжать (возобновлять) выполнение процессов. На отдельные задания ссылаются с помощью идентификаторов.
Сеансом называется множество групп процессов, сформированное для целей управления заданиями. Каждая группа принадлежит некоторому сеансу; считается, что все процессы группы принадлежат тому же сеансу. Вновь созданный процесс присоединяется к сеансу своего создателя; в дальнейшем принадлежность сеансу может быть изменена.
Время жизни сеанса представляет собой период от создания сеанса до истечения времени жизни всех групп процессов, принадлежавших сеансу.
Лидер сеанса – процесс, создавший данный сеанс.
Управляющим терминалом называется терминал, ассоциированный с сеансом. У сеанса может быть не более одного управляющего терминала, а тот, в свою очередь, ассоциируется ровно с одним сеансом. Некоторые последовательности символов, вводимые с управляющего терминала, вызывают посылку сигналов всем процессам группы, ассоциированной с данным управляющим терминалом.
Управляющий процесс – это лидер сеанса, установивший соединение с управляющим терминалом. Если в дальнейшем терминал перестанет быть управляющим для сеанса, лидер сеанса утратит статус управляющего процесса.
Задания, группы процессов и процессы подразделяются на приоритетные (переднего плана) и фоновые. Процессы переднего плана, в отличие от фоновых, имеют некоторые привилегии при доступе к управляющему терминалу. В сеансе, установившем соединение с управляющим терминалом, может быть не более одной группы процессов, приоритетной по отношению к данному управляющему терминалу.
С каждым процессом ассоциируется идентификатор создавшего его пользователя. Этот атрибут называется реальным идентификатором пользователя процесса.
В момент создания процесса пользователь входил в некоторую группу, идентификатор которой называется реальным идентификатором группы процесса.
Задание – это набор процессов, составляющих конвейер, а также порожденных ими процессов, входящих в одну группу.
Под управлением заданиями подразумеваются предоставленные пользователям средства выборочно (при)останавливать и затем продолжать (возобновлять) выполнение процессов. На отдельные задания ссылаются с помощью идентификаторов.
Сеансом называется множество групп процессов, сформированное для целей управления заданиями. Каждая группа принадлежит некоторому сеансу; считается, что все процессы группы принадлежат тому же сеансу. Вновь созданный процесс присоединяется к сеансу своего создателя; в дальнейшем принадлежность сеансу может быть изменена.
Время жизни сеанса представляет собой период от создания сеанса до истечения времени жизни всех групп процессов, принадлежавших сеансу.
Лидер сеанса – процесс, создавший данный сеанс.
Управляющим терминалом называется терминал, ассоциированный с сеансом. У сеанса может быть не более одного управляющего терминала, а тот, в свою очередь, ассоциируется ровно с одним сеансом. Некоторые последовательности символов, вводимые с управляющего терминала, вызывают посылку сигналов всем процессам группы, ассоциированной с данным управляющим терминалом.
Управляющий процесс – это лидер сеанса, установивший соединение с управляющим терминалом. Если в дальнейшем терминал перестанет быть управляющим для сеанса, лидер сеанса утратит статус управляющего процесса.
Задания, группы процессов и процессы подразделяются на приоритетные (переднего плана) и фоновые. Процессы переднего плана, в отличие от фоновых, имеют некоторые привилегии при доступе к управляющему терминалу. В сеансе, установившем соединение с управляющим терминалом, может быть не более одной группы процессов, приоритетной по отношению к данному управляющему терминалу.
С каждым процессом ассоциируется идентификатор создавшего его пользователя. Этот атрибут называется реальным идентификатором пользователя процесса.
В момент создания процесса пользователь входил в некоторую группу, идентификатор которой называется реальным идентификатором группы процесса.
PID TTY TIME CMD 1594
| PID TTY TIME CMD 1594 ttyS4 00:00:02 sh 1645 ttyS4 00:00:00 sh 1654 ttyS4 00:02:45 rk.20.01 18356 ttyS4 00:00:00 prconecm 18357 ttyS4 00:00:00 sh 18358 ttyS4 00:00:00 ps |
| Листинг 7.1. Возможный результат использования утилиты ps. |
| Закрыть окно |
| ps -A -o ruser,user,pid,ppid,tty=TTY -o nice,vsz,args |
| Листинг 7.2. Пример использования утилиты ps. |
| Закрыть окно |
| RUSER USER PID PPID TTY NI VSZ COMMAND root root 1 0 ? 0 1372 init [5] root root 4 1 ? 19 0 [ksoftirqd_CPU0] root root 555 1 ? 0 1428 syslogd -m 0 root root 560 1 ? 0 1364 klogd -x rpc rpc 580 1 ? 0 1508 portmap rpcuser rpcuser 608 1 ? 0 1560 rpc.statd root root 743 1 ? 0 2620 /usr/sbin/sshd root root 776 1 ? 0 2200 xinetd -stayalive -reuse -pidfi root root 805 1 ? 0 1500 rpc.rquotad root root 810 1 ? 0 1504 rpc.mountd root root 897 1 ? 0 3236 /usr/libexec/postfix/master postfix postfix 906 897 ? 0 3384 nqmgr -l -n qmgr -t fifo -u -c root root 918 1 ? 0 1400 gpm -t ps/2 -m /dev/mouse root root 936 1 ? 0 1548 crond xfs xfs 968 1 ? 0 4432 xfs -droppriv -daemon nobody nobody 987 1 ? 0 36488 dictd 1.9.7: 0/0 root daemon 1022 1 ? 0 1404 /usr/sbin/atd root root 1057 1 ? 0 5768 cupsd root root 1064 1 tty1 0 1344 /sbin/mingetty tty1 root root 1070 1 ttyS2 0 1352 /sbin/agetty -i -L ttyS2 38400 root root 1072 1 ? 0 2300 login -- galat galat galat 1086 1072 ttyS4 0 2260 -sh root root 1124 1085 ? 0 16900 /usr/bin/kdm_greet postfix postfix 1826 897 ? 0 3304 pickup -l -t fifo -u -c galat galat 2013 1171 ttyS4 0 1940 /bin/sh -c ps -A -o user,pid,pp galat galat 2014 2013 ttyS4 0 2584 ps -A -o user,pid,ppid,tty=TTY |
| Листинг 7.3. Фрагмент возможного результата использования утилиты ps. |
| Закрыть окно |
|
#include #include #include |
| Листинг 7.4. getpid(), getppid() и getpgrp(). |
| Закрыть окно |
|
#include |
| Листинг 7.5. Описание функции setpgid(). |
| Закрыть окно |
|
#include |
| Листинг 7.6. Описание функции setsid(). |
| Закрыть окно |
|
#include |
| Листинг 7.7. Пример программы, использующей функции getpid(), getppid(), getpgrp(), setpgid(), setsid(). |
| Закрыть окно |
| Атрибуты текущего процесса: pid: 11726, ppid: 11725, pgid: 1153 Новая группа текущего процесса: 11726 setsid от имени лидера группы: Operation not permitted Группа текущего процесса после повторной смены: 1153 Группа текущего процесса после создания нового сеанса: 11726 setpgid (ppid, 0): No such process setpgid (0, ppid): Operation not permitted |
| Листинг 7.8. Возможный результат работы программы, показанной в листинге 7.7. |
| Закрыть окно |
|
#include #include #include #include |
| Листинг 7.9. Описание функций getuid(), geteuid(), getgid(), getegid(). |
| Закрыть окно |
|
#include |
| Листинг 7.10. Описание функции getgroups(). |
| Закрыть окно |
|
#include #include |
| Листинг 7.11. Описание функций setuid() и seteuid(). |
| Закрыть окно |
|
#include #include |
| Листинг 7.12. Описание функций setgid() и setegid(). |
| Закрыть окно |
|
#include int main (void) { uid_t uid; int nsupgroups; gid_t *supgroupslist; int i; /* Отменим буферизацию стандартного вывода */ setbuf (stdout, NULL); printf ("Идентификаторы пользователя текущего процесса:\n" " реальный: %d, действующий: %d\n", uid = getuid (), geteuid ()); printf ("Идентификаторы группы текущего процесса:\n" " реальный: %d, действующий: %d\n", getgid (), getegid ()); printf ("Количество дополнительных групп текущего процесса: %d\n", nsupgroups = getgroups (0, supgroupslist)); if (nsupgroups > 0) { if ((supgroupslist = (gid_t *) malloc (nsupgroups * sizeof (gid_t))) == NULL) { perror ("MALLOC"); } else if (getgroups (nsupgroups, supgroupslist) == (-1)) { perror ("GETGROUPS"); } else { /* Выдадим идентификаторы дополнительных */ /* групп процесса */ printf ("Идентификаторы дополнительных групп текущего процесса:\n"); for (i = 0; i < nsupgroups; i++) { printf (" %d", supgroupslist [i]); } printf ("\n"); } } /* Попробуем переустановить идентификатор */ /* пользователя процесса */ if (setuid ((uid_t) 1) != 0) { perror ("setuid (1)"); } printf ("Идентификаторы пользователя текущего процесса после первой смены:\n" " реальный: %d, действующий: %d\n", getuid (), geteuid ()); /* Попробуем вернуть прежний идентификатор */ /* пользователя процесса */ if (setuid (uid) != 0) { perror ("setuid (uid)"); } printf ("Идентификаторы пользователя текущего процесса после второй смены:\n" " реальный: %d, действующий: %d\n", getuid (), geteuid ()); /* Попробуем сменить действующий идентификатор */ /* с помощью функции seteuid() */ if (seteuid ((uid_t) 1) != 0) { perror ("seteuid (1)"); } printf ("Идентификаторы пользователя текущего процесса после третьей смены:\n" " реальный: %d, действующий: %d\n", getuid (), geteuid ()); return (0); } |
| Листинг 7.13. Пример использования функций опроса и изменения идентификаторов пользователя процесса. |
| Закрыть окно |
| Идентификаторы пользователя текущего процесса: реальный: 108, действующий: 108 Идентификаторы группы текущего процесса: реальный: 3, действующий: 3 Количество дополнительных групп текущего процесса: 1 Идентификаторы дополнительных групп текущего процесса: 3 setuid (1): Operation not permitted Идентификаторы пользователя текущего процесса после первой смены: реальный: 108, действующий: 108 Идентификаторы пользователя текущего процесса после второй смены: реальный: 108, действующий: 108 seteuid (1): Operation not permitted Идентификаторы пользователя текущего процесса после третьей смены: реальный: 108, действующий: 108 |
| Листинг 7.14. Возможный результат работы программы, показанной в листинге 7.13 и запущенной от имени обычного пользователя. |
| Закрыть окно |
| Идентификаторы пользователя текущего процесса: реальный: 0, действующий: 0 Идентификаторы группы текущего процесса: реальный: 0, действующий: 0 Количество дополнительных групп текущего процесса: 7 Идентификаторы дополнительных групп текущего процесса: 0 1 2 3 4 6 10 Идентификаторы пользователя текущего процесса после первой смены: реальный: 1, действующий: 1 setuid (uid): Operation not permitted Идентификаторы пользователя текущего процесса после второй смены: реальный: 1, действующий: 1 Идентификаторы пользователя текущего процесса после третьей смены: реальный: 1, действующий: 1 |
| Листинг 7.15. Возможный результат работы программы, показанной в листинге 7.13 и запущенной от имени суперпользователя. |
| Закрыть окно |
| Идентификаторы пользователя текущего процесса: реальный: 108, действующий: 1 Идентификаторы группы текущего процесса: реальный: 3, действующий: 1 Количество дополнительных групп текущего процесса: 1 Идентификаторы дополнительных групп текущего процесса: 3 Идентификаторы пользователя текущего процесса после первой смены: реальный: 108, действующий: 1 Идентификаторы пользователя текущего процесса после второй смены: реальный: 108, действующий: 108 Идентификаторы пользователя текущего процесса после третьей смены: реальный: 108, действующий: 1 |
| Листинг 7.16. Возможный результат работы программы, показанной в листинге 7.13 и запущенной от имени обычного пользователя после взведения в режиме выполнимого файла бита ПДИП. |
| Закрыть окно |
|
#include |
| Листинг 7.17. Описание функции umask(). |
| Закрыть окно |
| umask 0 umask -S umask -- -x umask |
| Листинг 7.18. Пример использования служебной программы umask. |
| Закрыть окно |
| u=rwx,g=rwx,o=rwx 0111 |
| Пример 7.19. Листинг 7.19. Возможный результат использования служебной программы umask. |
| Закрыть окно |
|
#include |
| Листинг 7.20. Описание функции fork(). |
| Закрыть окно |
| int main ( int argc, char *argv []); |
| Пример 7.21. Заголовок функции main() C-программы. |
| Закрыть окно |
|
#include |
| Пример 7.22. Описание функций семейства exec(). |
| Закрыть окно |
|
#include |
| Пример 7.23. Описание функций семейства wait(). |
| Закрыть окно |
|
#include |
| Пример 7.24. Описание функций семейства exit(). |
| Закрыть окно |
|
#include |
| Пример 7.25. Описание функции atexit(). |
| Закрыть окно |
|
#include static void atefunc (void) { /* Перед завершением выдадим информацию о */ /* процессах */ printf ("Ситуация перед завершением родительского процесса\n"); (void) system ("ps -o pid,ppid,vsz,args"); } int main (void) { int pid; int stat; /* Отменим буферизацию стандартного вывода */ setbuf (stdout, NULL); /* Зарегистрируем обработчик завершения процесса */ if (atexit (atefunc) != 0) { perror ("ATEXIT"); exit (1); } /* Создадим новый процесс */ if ((pid = fork ()) < 0) { perror ("FORK"); exit (2); } else if (pid == 0) { /* Порожденный процесс */ /* Выполним служебную программу ps */ printf ("Ситуация с точки зрения порожденного процесса\n"); (void) execl ("/bin/ps", "ps", "-o", "pid,ppid,args", (char *) 0); perror ("EXEC"); exit (3); /* execl() завершился неудачей */ } else { /* Родительский процесс */ sleep (1); /* Вероятно, порожденный процесс уже */ /* завершился */ /* Посмотрим на текущую ситуацию */ printf ("Ситуация перед вызовом waitpid() в родительском процессе\n"); (void) system ("ps -o pid,ppid,vsz,args"); (void) waitpid (pid, &stat, 0); printf ("Статус завершения порожденного процесса с идентификатором %d: %d\n", pid, stat); } return 0; } |
| Пример 7.26. Пример использования функций порождения и завершения процессов. |
| Закрыть окно |
|
Ситуация с точки зрения порожденного процесса PID PPID COMMAND 6123 1072 -sh 29568 6123 prog30 29569 29568 ps -o pid,ppid,args Ситуация перед вызовом waitpid() в родительском процессе PID PPID VSZ COMMAND 6123 1072 2260 -sh 29568 6123 1340 prog30 29569 29568 0 [ps |
| Пример 7.27. Возможный результат работы программы, использующей функции порождения и завершения процессов. |
| Закрыть окно |
Создание и завершение процессов
Новые процессы создаются при помощи функции fork() (см. листинг 7.20).#include
Листинг 7.20. Описание функции fork(). (html, txt)
Новый (порожденный) процесс является точной копией процесса, вызвавшего fork() (родительского), за исключением следующих моментов.
В случае успешного завершения функция fork() возвращает порожденному процессу 0, а родительскому процессу – идентификатор порожденного процесса. После этого оба процесса начинают независимо выполнять инструкции, расположенные за обращением к fork(). При неудаче родительскому процессу возвращается -1, новый процесс не создается.
Поскольку возвращаемые функцией fork() значения различны для обеих копий, родительский и порожденный процессы могут далее выполняться по-разному. Например, процесс-предок переходит в состояние ожидания завершения процесса-потомка либо, если процесс-потомок запущен асинхронно, продолжает выполнение параллельно с ним. Процесс-потомок при помощи функции семейства exec() подменяет программу, которая определяет поведение процесса, и передает ей управление и список аргументов.
Напомним, что заголовок функции main() C-программы выглядит в общем случае так, как показано в листинге 7.21.
int main (int argc, char *argv []);
Пример 7.21. Заголовок функции main() C-программы. (html, txt)
Значение argc равно количеству аргументов; argv – это массив указателей собственно на аргументы, которые определяются исходя из командной строки, запускающей C-программу.
В соответствии с принятым соглашением, значение argc не меньше единицы, а первый элемент массива argv указывает на цепочку символов, содержащую имя выполняемого файла.
Аналогичный смысл имеют аргументы функций семейства exec() (см. листинг 7.22).
Пример 7.22. Описание функций семейства exec(). (html, txt)
Функции семейства exec() заменяют текущий образ процесса новым (и, следовательно, в случае успешного завершения возврат в вызывающий процесс невозможен). Новый образ создается на основе выполнимого файла, называемого файлом образа процесса.
Переменная environ инициализируется как указатель на массив указателей на составляющие окружение цепочки символов. Массивы argv и environ завершаются пустым указателем.
Аргумент path указывает на маршрутное имя файла с новым образом процесса.
Аргумент file имеет аналогичный смысл, однако, если он задан как простое имя, то производится поиск в каталогах, заданных переменной окружения PATH.
Аргументы arg0, ..., являются указателями на цепочки символов, составляющие список аргументов нового образа процесса. Последним в списке располагается пустой указатель, а аргумент arg0 должен указывать на имя файла-образа.
Аргумент envp имеет тот же смысл и назначение, что и переменная environ.
Файловые дескрипторы остаются открытыми в новом образе, если только они не были снабжены флагом FD_CLOEXEC.
Если у файла с новым образом процесса взведен бит ПДИП, действующий идентификатор пользователя процесса переустанавливается равным идентификатору владельца файла (аналогично для группы).
Следующие атрибуты процесса остаются неизменными:
Родительский процесс реализует ожидание завершения процессов-потомков и получает информацию о его (завершения) статусе с помощью функций семейства wait() (см.
листинг 7.23). Если информация о статусе завершения была доступна до вызова wait(), родительский процесс не приостанавливается, возврат из wait() происходит немедленно.
#include
Пример 7.23. Описание функций семейства wait(). (html, txt)
Функция waitpid() эквивалентна wait(), если аргумент pid равен (pid_t) (-1), а аргумент options имеет нулевое значение. Аргумент pid задает набор порожденных процессов, статус завершения которых запрашивается. Значение (pid_t) (-1) представляет произвольный элемент множества порожденных процессов. Если pid > 0>, имеется в виду один процесс с данным идентификатором. Нулевое значение специфицирует любого потомка из той же группы процессов, что и вызывающий. Наконец, при pid < (pid_t) (-1) запрашивается статус завершения любого порожденного процесса из группы, идентификатор которой равен абсолютной величине pid.
Программирование в стандарте POSIX
Каналы
Средства локального межпроцессного взаимодействия реализуют высокопроизводительную, детерминированную передачу данных между процессами в пределах одной системы.К числу наиболее простых и в то же время самых употребительных средств межпроцессного взаимодействия принадлежат каналы, представляемые файлами соответствующего типа. Стандарт POSIX-2001 различает именованные и безымянные каналы. Напомним, что первые создаются функцией mkfifo() и одноименной служебной программой, а вторые - функцией pipe(). Именованным каналам соответствуют элементы файловой системы, ко вторым можно обращаться только посредством файловых дескрипторов. В остальном эти разновидности каналов эквивалентны.
Взаимодействие между процессами через канал может быть установлено следующим образом: один из процессов создает канал и передает другому соответствующий открытый файловый дескриптор. После этого процессы обмениваются данными через канал при помощи функций read() и write(). Примером подобного взаимодействия служит программа, показанная в листинге 8.1.
Листинг 8.1. Пример взаимодействия между процессами через канал с помощью функций ввода/вывода нижнего уровня. (html, txt)
Решение той же задачи, но с использованием функций буферизованного ввода/вывода, показано в листинге 8.2.
Листинг 8.2. Пример взаимодействия между процессами через канал с помощью функций буферизованного ввода/вывода. (html, txt)
Если не указано противное, обмен данными через канал происходит в синхронном режиме: процесс, пытающийся читать из пустого канала, открытого кем-либо на запись, приостанавливается до тех пор, пока данные не будут в него записаны; с другой стороны, запись в полный канал задерживается до освобождения необходимого для записи места. Чтобы отменить подобный режим взаимодействия, надо связать с дескрипторами канала флаг статуса O_NONBLOCK (это может быть сделано при помощи функции fcntl()). В таком случае чтение или запись, которые невозможно выполнить немедленно, завершаются неудачей.
Подчеркнем, что при попытке чтения из пустого канала результат равен 0 (как признак конца файла), только если канал не открыт кем-либо на запись.
Под "кем-либо" понимается и сам читающий процесс; по этой причине в приведенной выше программе потребовалось закрыть все экземпляры файлового дескриптора fd [1], возвращенного функцией pipe() как дескриптор для записи в канал.
Функция popen(), описанная выше, при рассмотрении командного интерпретатора, является более высокоуровневой по сравнению с pipe(). Она делает сразу несколько вещей: порождает процесс, обеспечивает выполнение в его рамках заданной команды, организует канал между вызывающим и порожденным процессами и формирует необходимые потоки для этого канала. Если при обращении к popen() задан режим "w", то стандартный ввод команды, выполняющейся в рамках порожденного процесса, перенаправляется на конец канала, предназначенный для чтения; если задан режим "r", то в канал перенаправляется стандартный вывод.
После вызова popen() процесс может писать в канал или читать из него посредством функций буферизованного ввода/вывода, используя сформированный поток. Канал остается открытым до момента вызова функции pclose() (см. листинг 8.3).
#include
Листинг 8.3. Описание функции pclose(). (html, txt)
Функция pclose() не только закрывает поток, сформированный popen(), но и дожидается завершения порожденного процесса, возвращая его статус.
Типичное применение popen() - организация канала для выдачи динамически порождаемых данных на устройство печати командой lp (см. листинг 8.4).
Листинг 8.4. Пример создания и использования канала для вывода данных. (html, txt)
Сходным образом можно организовать канал для чтения результатов выполнения команды (см. листинг 8.5).
Листинг 8.5. Пример создания и использования канала для ввода данных. (html, txt)
exit (0); }
/* Чтение со стандартного ввода и запись в канал */ /* возложим на родительский процесс. */ /* Из соображений симметрии закроем поток, */ /* предназначенный для чтения из канала */ fclose (fp [0]); fputs ("Вводите строки\n", fp [1]); while (fgets (line, sizeof (line), stdin) != NULL) { if ((fputs ("Вы ввели: ", fp [1]) == EOF) || (fputs (line, fp [1]) == EOF)) { break; } } fclose (fp [1]);
(void) wait (NULL); return (0); }
Листинг 8.2. Пример взаимодействия между процессами через канал с помощью функций буферизованного ввода/вывода.
Если не указано противное, обмен данными через канал происходит в синхронном режиме: процесс, пытающийся читать из пустого канала, открытого кем-либо на запись, приостанавливается до тех пор, пока данные не будут в него записаны; с другой стороны, запись в полный канал задерживается до освобождения необходимого для записи места. Чтобы отменить подобный режим взаимодействия, надо связать с дескрипторами канала флаг статуса O_NONBLOCK (это может быть сделано при помощи функции fcntl()). В таком случае чтение или запись, которые невозможно выполнить немедленно, завершаются неудачей.
Подчеркнем, что при попытке чтения из пустого канала результат равен 0 (как признак конца файла), только если канал не открыт кем-либо на запись. Под "кем-либо" понимается и сам читающий процесс; по этой причине в приведенной выше программе потребовалось закрыть все экземпляры файлового дескриптора fd [1], возвращенного функцией pipe() как дескриптор для записи в канал.
Функция popen(), описанная выше, при рассмотрении командного интерпретатора, является более высокоуровневой по сравнению с pipe(). Она делает сразу несколько вещей: порождает процесс, обеспечивает выполнение в его рамках заданной команды, организует канал между вызывающим и порожденным процессами и формирует необходимые потоки для этого канала. Если при обращении к popen() задан режим "w", то стандартный ввод команды, выполняющейся в рамках порожденного процесса, перенаправляется на конец канала, предназначенный для чтения; если задан режим "r", то в канал перенаправляется стандартный вывод.
После вызова popen() процесс может писать в канал или читать из него посредством функций буферизованного ввода/вывода, используя сформированный поток. Канал остается открытым до момента вызова функции pclose() (см. листинг 8.3).
#include
Листинг 8.3. Описание функции pclose().
Функция pclose() не только закрывает поток, сформированный popen(), но и дожидается завершения порожденного процесса, возвращая его статус.
Типичное применение popen() - организация канала для выдачи динамически порождаемых данных на устройство печати командой lp (см. листинг 8.4).
#include
/* Инициализируем массив, чтобы далее все элементы */ /* можно было считать и выводить единообразно */ tp [0] = 1; for (i = 1; i < T_SIZE; i++) { tp [i] = 0; }
/* Создадим канал с командой */ if ((outptr = popen ("lp", "w")) == NULL) { perror ("POPEN"); return (-1); }
(void) fprintf (outptr, "\nТреугольник Паскаля:\n");
for (i = 0; i < T_SIZE; i++) { /* Элементы очередной строки нужно считать от конца к началу */ /* Элемент tp [0] пересчитывать не нужно */ for (j = i; j > 0; j--) { tp [j] += tp [j - 1]; } /* Вывод строки треугольника в канал */ for (j = 0; j <= i; j++) { (void) fprintf (outptr, " %ld", tp [j]); } (void) fprintf (outptr, "\n"); }
return (pclose (outptr)); }
Листинг 8.4. Пример создания и использования канала для вывода данных.
Сходным образом можно организовать канал для чтения результатов выполнения команды (см. листинг 8.5).
#include
#define MY_CMD "ls -l *.c"
int main (void) { FILE *inptr; char line [LINE_MAX];
assert ((inptr = popen (MY_CMD, "r")) != NULL);
while (fgets (line, sizeof (line), inptr) != NULL) { fputs (line, stdout); }
return (pclose (inptr)); }
Листинг 8.5. Пример создания и использования канала для ввода данных.
Очереди сообщений
Мы переходим к рассмотрению средств локального межпроцессного взаимодействия, относящихся к необязательной части стандарта POSIX-2001, именуемой "X/Open-расширение системного интерфейса" (XSI). Будут описаны очереди сообщений, семафоры и разделяемые сегменты памяти.Остановимся сначала на понятиях и структурах, общих для всех трех упомянутых средств.
Каждая очередь сообщений, набор семафоров и разделяемый сегмент однозначно идентифицируются положительным целым числом, которое обычно обозначается, соответственно, как msqid, semid и shmid и возвращается в качестве результатов функций msgget(), semget() и shmget().
При получении идентификаторов средств межпроцессного взаимодействия используется еще одна сущность - ключ, а для его генерации предназначена функция ftok() (см. листинг 8.22). Аргумент path должен задавать маршрутное имя существующего файла, к которому вызывающий процесс может применить функцию stat(). В качестве значения аргумента id, по соображениям мобильности, рекомендуется использовать однобайтный символ. Гарантируется, что функция ftok() сгенерирует один и тот же ключ для заданной пары (файл, символ) и разные ключи для разных пар.
#include
Листинг 8.22. Описание функции ftok(). (html, txt)
С идентификатором средства межпроцессного взаимодействия ассоциирована структура данных, содержащая информацию о допустимых и выполненных операциях. Соответствующие декларации сосредоточены в заголовочных файлах
В упомянутую структуру входит подструктура ipc_perm с данными о владельцах и режимом доступа, описанная в файле
uid_t uid; /* Идентификатор владельца */ gid_t gid; /* Идентификатор владеющей группы */ uid_t cuid; /* Идентификатор пользователя, создавшего данное средство межпроцессного взаимодействия */ gid_t cgid; /* Идентификатор создавшей группы */ mode_t mode; /* Режим доступа на чтение/запись */
Управление доступом к описываемым средствам межпроцессного взаимодействия осуществляется аналогично файловому, только наряду (и наравне) с владельцами (пользователем и группой) рассматриваются те, кто эти средства создал (создатели).
Опросить статус присутствующих в данный момент в системе (т. е. активных) средств межпроцессного взаимодействия позволяет служебная программа ipcs:
ipcs [-qms] [-a | -bcopt]
По умолчанию выдается краткая информация обо всех средствах - очередях сообщений, семафорах и разделяемых сегментах памяти. Если нужно ограничиться их отдельными видами, следует воспользоваться опциями -q, -s и/или -m, соответственно.
Следующие опции управляют форматом выдачи. Задание опции -a равносильно указанию всех опций формата. Опция -b предписывает выдавать лимиты на размер (максимальное количество байт в сообщениях очереди и т.п.), -c - имена пользователя и группы создателя средства, -o - информацию об использовании (количество сообщений в очереди, их суммарный размер и т.п.), -p - информацию о процессах (идентификаторы последнего отправителя, получателя и т.п.), -t - информацию о времени (последняя управляющая операция, последняя отправка сообщения и т.п.).
Для удаления из системы активных средств межпроцессного взаимодействия предназначена служебная программа ipcrm (разумеется, подверженная контролю прав доступа). Удаляемые средства могут задаваться идентификаторами или ключами:
ipcrm [-q msgid | -Q msgkey | -s semid | -S semkey | -m shmid | -M shmkey ] ...
На этом мы завершаем изложение общих вопросов, относящихся к средствам межпроцессного взаимодействия, и переходим к рассмотрению специфических возможностей каждого из них.
Механизм очередей сообщений позволяет процессам взаимодействовать, обмениваясь данными. Данные передаются между процессами дискретными порциями, называемыми сообщениями. Процессы выполняют над сообщениями две основные операции - прием и отправку. Процессы, отправляющие или принимающие сообщение, могут приостанавливаться, если требуемую операцию невозможно выполнить немедленно.В частности, могут быть отложены попытки отправить сообщение в заполненную до отказа очередь, получить сообщение из пустой очереди и т.п. ("операции с блокировкой"). Если же указано, что приостанавливать процесс нельзя, "операции без блокировки" либо выполняются немедленно, либо завершаются неудачей.
Прежде чем процессы смогут обмениваться сообщениями, один из них должен создать очередь. Одновременно определяются первоначальные права на выполнение операций для различных процессов, в том числе соответствующих управляющих действий над очередями.
В частности, могут быть отложены попытки отправить сообщение в заполненную до отказа очередь, получить сообщение из пустой очереди и т.п. ("операции с блокировкой"). Если же указано, что приостанавливать процесс нельзя, "операции без блокировки" либо выполняются немедленно, либо завершаются неудачей.
Прежде чем процессы смогут обмениваться сообщениями, один из них должен создать очередь. Одновременно определяются первоначальные права на выполнение операций для различных процессов, в том числе соответствующих управляющих действий над очередями.
Для работы с очередями сообщений стандарт POSIX-2001 предусматривает следующие функции (см. листинг 8.23): msgget() (получение идентификатора очереди сообщений), msgctl() (управление очередью сообщений), msgsnd() (отправка сообщения) и msgrcv() (прием сообщения).
#include
Листинг 8.23. Описание функций для работы с очередями сообщений.
Структура msqid_ds, ассоциированная с идентификатором очереди сообщений, должна содержать по крайней мере следующие поля.
struct ipc_perm msg_perm; /* Данные о правах доступа к очереди сообщений */ msgqnum_t msg_qnum; /* Текущее количество сообщений в очереди */ msglen_t msg_qbytes; /* Максимально допустимый суммарный размер сообщений в очереди */ pid_t msg_lspid; /* Идентификатор процесса, отправившего последнее сообщение */ pid_t msg_lrpid; /* Идентификатор процесса, принявшего последнее сообщение */ time_t msg_stime; /* Время последней отправки */ time_t msg_rtime; /* Время последнего приема */ time_t msg_ctime; /* Время последнего изменения посредством msgctl() */
Перейдем к детальному рассмотрению функций для работы с очередями сообщений.
Функция msgget() возвращает идентификатор очереди сообщений, ассоциированный с ключом key.
Новая очередь, ее идентификатор и соответствующая структура msqid_ds создаются для заданного ключа, если значение аргумента key равно IPC_PRIVATE или очередь еще не ассоциирована с ключом, а в числе флагов msgflg задан IPC_CREAT.
Если необходима уверенность в том, что очередь с указанным ключом создается заново, в дополнение к флагу IPC_CREAT следует установить IPC_EXCL. Тогда попытка получить идентификатор уже существующий очереди завершится неудачей.
Структура msqid_ds для новой очереди инициализируется следующим образом.
Один из тонких вопросов, связанных с созданием очереди сообщений, заключается в выборе ключа. Всем процессам, которые намереваются работать с общей очередью сообщений, для получения идентификатора msqid необходимо знать ключ очереди. Задание ключа одинаковым константным значением во всех этих программах небезопасно, поскольку может оказаться так, что тот же ключ будет случайно задействован и другими программами. Как одно из возможных решений рекомендуется использование функции ftok(), вычисляющей действительно "уникальный" ключ.
В листинге 8.24 приведен простейший пример программы, где создается очередь сообщений с правами доступа, указанными в командной строке.
#include
/* Программа создает очередь сообщений. */ /* В командной строке задаются имя файла для ftok() */ /* и режим доступа к очереди сообщений */
#define FTOK_CHAR 'G'
int main (int argc, char *argv []) { key_t key; int msqid; int mode = 0;
if (argc != 3) { fprintf (stderr, "Использование: %s маршрутное_имя режим_доступа\n", argv [0]); return (1); }
if ((key = ftok (argv [1], FTOK_CHAR)) == (key_t) (-1)) { perror ("FTOK"); return (2); } (void) sscanf (argv [2], "%o", (unsigned int *) &mode);
if ((msqid = msgget (key, IPC_CREAT | mode)) < 0) { perror ("MSGGET"); return (3); }
return 0; }
Листинг 8.24. Пример программы, создающей очередь сообщений.
Если после выполнения этой программы воспользоваться командой ipcs -q, то результат может выглядеть так, как показано в листинге 8.25.
------ Message Queues -------- key msqid owner perms used-bytes messages 0x47034bac 163840 galat 644 0 0
Листинг 8.25. Возможный результат опроса статуса очередей сообщений.
Удалить созданную очередь из системы, соответствующей стандарту POSIX-2001, можно командой ipcrm -q 163840.
Операции отправки/приема сообщений выполняют функции msgsnd() и msgrcv(); msgsnd() помещает сообщения в очередь, а msgrcv() читает и "достает" их оттуда.
В обоих случаях первый аргумент задает идентификатор очереди; второй является указателем на содержащую сообщение структуру. Сообщение состоит из двух частей: текста (последовательности байт) и так называемого типа (положительного целого числа). Тип, указанный во время отправки, используется впоследствии при выборе сообщения из очереди. Аргумент msgsz определяет длину сообщения; аргумент msgflg задает флаги.
В зависимости от значения, указанного в качестве аргумента msgtyp функции msgrcv(), из очереди выбирается то или иное сообщение. Если значение аргумента равно нулю, запрашивается первое сообщение в очереди, если больше нуля - первое сообщение типа msgtyp, а если меньше нуля - первое сообщение наименьшего из типов, не превышающих абсолютную величину аргумента msgtyp. Пусть, например, в очередь последовательно помещены сообщения с типами 5, 3 и 2. Тогда вызов msgrcv (msqid, msgp, size, 0, flags) выберет из очереди сообщение с типом 5, поскольку оно отправлено первым; вызов msgrcv (msqid, msgp, size, -4, flags) - последнее сообщение, так как 2 - это наименьший из возможных типов в указанном диапазоне; наконец, вызов msgrcv (msqid, msgp, size, 3, flags) - сообщение с типом 3.
Во многих приложениях взаимодействующим посредством очереди сообщений процессам требуется синхронизировать свое выполнение. Например, процесс-получатель, пытавшийся прочитать сообщение и обнаруживший, что очередь пуста (либо сообщение указанного типа отсутствует), должен иметь возможность подождать, пока процесс-отправитель не поместит в очередь требуемое сообщение. Аналогичным образом, процесс, желающий отправить сообщение в очередь, в которой нет достаточного для него места, может ожидать его освобождения в результате чтения сообщений другими процессами. Процесс, вызвавший подобного рода "операцию с блокировкой", приостанавливается до тех пор, пока либо станет возможным выполнение операции, либо будет ликвидирована очередь. С другой стороны, имеются приложения, где подобные ситуации должны приводить к немедленному (и неудачному) завершению вызова функции.
Если не указано противное, функции msgsnd() и msgrcv() выполняют операции с блокировкой, например: msgsnd (msqid, msgp, size, 0); msgrcv (msqid, msgp, size, type, 0). Чтобы выполнить операцию без блокировки, необходимо установить флаг IPC_NOWAIT: msgsnd (msqid, msgp, size, IPC_NOWAIT); msgrcv (msqid, msgp, size, type, IPC_NOWAIT).
Аргумент msgp указывает на значение структурного типа, в котором представлены тип и тело сообщения (см. листинг 8.26).
struct msgbuf { long mtype; /* Тип сообщения */ char mtext [1]; /* Текст сообщения */ };
Листинг 8.26. Описание структурного типа для представления сообщений.
Для хранения реальных сообщений в прикладной программе следует определить аналогичную структуру, указав желаемый размер сообщения, например, так, как это сделано в листинге 8.27.
#define MAXSZTMSG 8192
struct mymsgbuf { long mtype; /* Тип сообщения */ char mtext [MAXSZTMSG]; /* Текст сообщения */ }; struct mymsgbuf msgbuf;
Листинг 8.27. Описание структуры для хранения сообщений.
В качестве аргумента msgsz обычно указывается размер текстового буфера, например: sizeof (msgbuf.text).
Если не указано противное, в случае, когда длина выбранного сообщения больше, чем msgsz, вызов msgrcv() завершается неудачей.
Если же установить флаг MSG_NOERROR, длинное сообщение обрезается до msgsz байт. Отброшенная часть пропадает, а вызывающий процесс не получает никакого уведомления о том, что сообщение обрезано.
При успешном завершении msgsnd() возвращает 0, а msgrcv() - значение, равное числу реально полученных байт; при неудаче возвращается -1.
Процессы, обладающие достаточными правами доступа, посредством функции msgctl() могут получать информацию о состоянии очереди, изменять ряд характеристик, удалять очередь.
Управляющее действие определяется значением аргумента cmd. Допустимых значений три: IPC_STAT - получить информацию о состоянии очереди, IPC_SET - переустановить характеристики очереди, IPC_RMID - удалить очередь.
Команды IPC_STAT и IPC_SET для хранения информации об очереди используют имеющуюся в прикладной программе структуру типа msqid_ds, указатель на которую содержит аргумент buf: IPC_STAT копирует в нее ассоциированную с очередью структуру данных, а IPC_SET, наоборот, в соответствии с ней обновляет ассоциированную структуру. Команда IPC_SET позволяет переустановить значения идентификаторов владельца (msg_perm.uid) и владеющей группы (msg_perm.gid), режима доступа (msg_perm.mode), максимально допустимый суммарный размер сообщений в очереди (msg_qbytes). Увеличить значение msg_qbytes может только процесс, обладающий соответствующими привилегиями.
В листинге 8.28 приведена программа, изменяющая максимально допустимый суммарный размер сообщений в очереди. Предполагается, что очередь сообщений уже создана, а ее идентификатор известен. Читателю предлагается выполнить эту программу с разными значениями максимально допустимого суммарного размера (как меньше, так и больше текущего), действуя от имени обычного и привилегированного пользователя.
#include
int main (int argc, char *argv []) { int msqid; struct msqid_ds msqid_ds;
if (argc != 3) { fprintf (stderr, "Использование: %s идентификатор_очереди максимальный_размер\n", argv [0]); return (1); }
(void) sscanf (argv [1], "%d", &msqid);
/* Получим исходное значение структуры данных */ if (msgctl (msqid, IPC_STAT, &msqid_ds) == -1) { perror ("IPC_STAT-1"); return (2); } printf ("Максимальный размер очереди до изменения: %ld\n", msqid_ds.msg_qbytes);
(void) sscanf (argv [2], "%d", (int *) &msqid_ds.msg_qbytes);
/* Попробуем внести изменения */ if (msgctl (msqid, IPC_SET, &msqid_ds) == -1) { perror ("IPC_SET"); }
/* Получим новое значение структуры данных */ if (msgctl (msqid, IPC_STAT, &msqid_ds) == -1) { perror ("IPC_STAT-2"); return (3); } printf ("Максимальный размер очереди после изменения: %ld\n", msqid_ds.msg_qbytes);
return 0; }
Листинг 8.28. Пример программы управления очередями сообщений.
Две программы, показанные в листингах 8.29 и 8.30, демонстрируют полный цикл работы с очередями сообщений - от создания до удаления. Программа из листинга 8.29 представляет собой родительский процесс, читающий строки со стандартного ввода и отправляющий их в виде сообщений процессу-потомку (листинг 8.30). Последний принимает сообщения и выдает их тела на стандартный вывод. Предполагается, что программа этого процесса находится в файле msq_child текущего каталога.
#include
/* Программа копирует строки со стандартного ввода на стандартный вывод, */ /* "прокачивая" их через очередь сообщений */
#define FTOK_FILE "/home/galat" #define FTOK_CHAR "G"
#define MSGQ_MODE 0644
#define MY_PROMPT "Вводите строки\n" #define MY_MSG "Вы ввели: "
int main (void) { key_t key; int msqid; struct mymsgbuf { long mtype; char mtext [LINE_MAX]; } line_buf, msgbuf;
switch (fork ()) { case -1: perror ("FORK"); return (1); case 0: /* Чтение из очереди и выдачу на стандартный вывод */ /* реализуем в порожденном процессе. */ (void) execl ("./msq_child", "msq_child", FTOK_FILE, FTOK_CHAR, (char *) 0); perror ("EXEC"); return (2); /* execl() завершился неудачей */ }
/* Чтение со стандартного ввода и запись в очередь */ /* возложим на родительский процесс */
/* Выработаем ключ для очереди сообщений */ if ((key = ftok (FTOK_FILE, FTOK_CHAR [0])) == (key_t) (-1)) { perror ("FTOK"); return (3); }
/* Получим идентификатор очереди сообщений */ if ((msqid = msgget (key, IPC_CREAT | MSGQ_MODE)) < 0) { perror ("MSGGET"); return (4); }
/* Приступим к отправке сообщений в очередь */ msgbuf.mtype = line_buf.mtype = 1; strncpy (msgbuf.mtext, MY_PROMPT, sizeof (msgbuf.mtext)); if (msgsnd (msqid, (void *) &msgbuf, strlen (msgbuf.mtext) + 1, 0) != 0) { perror ("MSGSND-1"); return (5); } strncpy (msgbuf.mtext, MY_MSG, sizeof (msgbuf.mtext));
while (fgets (line_buf.mtext, sizeof (line_buf.mtext), stdin) != NULL) { if (msgsnd (msqid, (void *) &msgbuf, strlen (msgbuf.mtext) + 1, 0) != 0) { perror ("MSGSND-2"); break; } if (msgsnd (msqid, (void *) &line_buf, strlen (line_buf.mtext) + 1, 0) != 0) { perror ("MSGSND-3"); break; } }
/* Удалим очередь */ if (msgctl (msqid, IPC_RMID, NULL) == -1) { perror ("MSGCTL-IPC_RMID"); return (6); }
return (0); }
Листинг 8.29. Передающая часть программы работы с очередями сообщений.
#include
/* Программа получает сообщения из очереди */ /* и копирует их тела на стандартный вывод */
#define MSGQ_MODE 0644
int main (int argc, char *argv []) { key_t key; int msqid; struct mymsgbuf { long mtype; char mtext [LINE_MAX]; } msgbuf;
if (argc != 3) { fprintf (stderr, "Использование: %s имя_файла цепочка_символов\n", argv [0]); return (1); }
/* Выработаем ключ для очереди сообщений */ if ((key = ftok (argv [1], *argv [2])) == (key_t) (-1)) { perror ("CHILD FTOK"); return (2); }
/* Получим идентификатор очереди сообщений */ if ((msqid = msgget (key, IPC_CREAT | MSGQ_MODE)) < 0) { perror ("CHILD MSGGET"); return (3); }
/* Цикл приема сообщений и выдачи строк */ while (msgrcv (msqid, (void *) &msgbuf, sizeof (msgbuf.mtext), 0, 0) > 0) { if (fputs (msgbuf.mtext, stdout) == EOF) { break; } }
return 0; }
Листинг 8.30. Приемная часть программы работы с очередями сообщений.
Обратим внимание на способ выработки согласованного ключа, а также на то, что, вообще говоря, неизвестно, какой из процессов - родительский или порожденный - создаст очередь, а какой получит уже ассоциированный с ключом идентификатор (вызовы msgget() в обоих процессах одинаковы), но на корректность работы программы это не влияет.
Программа копирует строки со стандартного
|
#include /* Программа копирует строки со стандартного ввода на стандартный вывод, */ /* "прокачивая" их через канал. */ /* Используются функции ввода/вывода нижнего уровня */ #define MY_PROMPT "Вводите строки\n" #define MY_MSG "Вы ввели: " int main (void) { int fd [2]; char buf [1]; int new_line = 1; /* Признак того, что надо выдать сообщение MY_MSG */ /* перед отображением очередной строки */ /* Создадим безымянный канал */ if (pipe (fd) < 0) { perror ("PIPE"); exit (1); } switch (fork ()) { case -1: perror ("FORK"); exit (2); case 0: /* Чтение из канала и выдачу на стандартный вывод */ /* реализуем в порожденном процессе. */ /* Необходимо закрыть дескриптор, предназначенный */ /* для записи в канал, иначе чтение не завершится */ /* по концу файла */ close (fd [1]); while (read (fd [0], buf, 1) == 1) { if (write (1, buf, 1) != 1) { perror ("WRITE TO STDOUT"); break; } } exit (0); } /* Чтение со стандартного ввода и запись в канал */ /* возложим на родительский процесс. */ /* Из соображений симметрии закроем дескриптор, */ /* предназначенный для чтения из канала */ close (fd [0]); if (write (fd [1], MY_PROMPT, sizeof (MY_PROMPT) - 1) != sizeof (MY_PROMPT) - 1) { perror ("WRITE TO PIPE-1"); } while (read (0, buf, 1) == 1) { /* Перед отображением очередной строки */ /* нужно выдать сообщение MY_MSG */ if (new_line) { if (write (fd [1], MY_MSG, sizeof (MY_MSG) - 1) != sizeof (MY_MSG) - 1) { perror ("WRITE TO PIPE-2"); break; } } if (write (fd [1], buf, 1) != 1) { perror ("WRITE TO PIPE-3"); break; } new_line = (buf [0] == '\n'); } close (fd [1]); (void) wait (NULL); return (0); } |
| Листинг 8.1. Пример взаимодействия между процессами через канал с помощью функций ввода/вывода нижнего уровня. |
| Закрыть окно |
|
#include /* Программа копирует строки со стандартного ввода на стандартный вывод, */ /* "прокачивая" их через канал. */ /* Используются функции буферизованного ввода/вывода */ int main (void) { int fd [2]; FILE *fp [2]; char line [LINE_MAX]; /* Создадим безымянный канал */ if (pipe (fd) < 0) { perror ("PIPE"); exit (1); } /* Сформируем потоки по файловым дескрипторам канала */ assert ((fp [0] = fdopen (fd [0], "r")) != NULL); assert ((fp [1] = fdopen (fd [1], "w")) != NULL); /* Отменим буферизацию вывода */ setbuf (stdout, NULL); setbuf (fp [1], NULL); switch (fork ()) { case -1: perror ("FORK"); exit (2); case 0: /* Чтение из канала и выдачу на стандартный вывод */ /* реализуем в порожденном процессе. */ /* Необходимо закрыть поток, предназначенный для */ /* записи в канал, иначе чтение не завершится */ /* по концу файла */ fclose (fp [1]); while (fgets (line, sizeof (line), fp [0]) != NULL) { if (fputs (line, stdout) == EOF) { break; } } exit (0); } /* Чтение со стандартного ввода и запись в канал */ /* возложим на родительский процесс. */ /* Из соображений симметрии закроем поток, */ /* предназначенный для чтения из канала */ fclose (fp [0]); fputs ("Вводите строки\n", fp [1]); while (fgets (line, sizeof (line), stdin) != NULL) { if ((fputs ("Вы ввели: ", fp [1]) == EOF) || (fputs (line, fp [1]) == EOF)) { break; } } fclose (fp [1]); (void) wait (NULL); return (0); } |
| Листинг 8.2. Пример взаимодействия между процессами через канал с помощью функций буферизованного ввода/вывода. |
| Закрыть окно |
|
#include |
| Листинг 8.3. Описание функции pclose(). |
| Закрыть окно |
|
#include /* Инициализируем массив, чтобы далее все элементы */ /* можно было считать и выводить единообразно */ tp [0] = 1; for (i = 1; i < T_SIZE; i++) { tp [i] = 0; } /* Создадим канал с командой */ if ((outptr = popen ("lp", "w")) == NULL) { perror ("POPEN"); return (-1); } (void) fprintf (outptr, "\nТреугольник Паскаля:\n"); for (i = 0; i < T_SIZE; i++) { /* Элементы очередной строки нужно считать от конца к началу */ /* Элемент tp [0] пересчитывать не нужно */ for (j = i; j > 0; j--) { tp [j] += tp [j - 1]; } /* Вывод строки треугольника в канал */ for (j = 0; j <= i; j++) { (void) fprintf (outptr, " %ld", tp [j]); } (void) fprintf (outptr, "\n"); } return (pclose (outptr)); } |
| Листинг 8.4. Пример создания и использования канала для вывода данных. |
| Закрыть окно |
|
#include #define MY_CMD "ls -l *.c" int main (void) { FILE *inptr; char line [LINE_MAX]; assert ((inptr = popen (MY_CMD, "r")) != NULL); while (fgets (line, sizeof (line), inptr) != NULL) { fputs (line, stdout); } return (pclose (inptr)); } |
| Листинг 8.5. Пример создания и использования канала для ввода данных. |
| Закрыть окно |
|
#include |
| Листинг 8.6. Описание функции kill(). |
| Закрыть окно |
| 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 32) SIGRTMIN 33) SIGRTMIN+1 34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5 38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9 42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13 46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14 50) SIGRTMAX-13 51) SIGRTMAX-12 52) SIGRTMAX-11 53) SIGRTMAX-10 54) SIGRTMAX-9 55) SIGRTMAX-8 56) SIGRTMAX-7 57) SIGRTMAX-6 58) SIGRTMAX-5 59) SIGRTMAX-4 60) SIGRTMAX-3 61) SIGRTMAX-2 62) SIGRTMAX-1 63) SIGRTMAX |
| Листинг 8.7. Возможный результат выполнения команды kill -l. |
| Закрыть окно |
|
#include |
| Листинг 8.8. Описание функции raise(). |
| Закрыть окно |
|
#include |
| Листинг 8.9. Описание функции abort(). |
| Закрыть окно |
|
#include |
| Листинг 8.10. Описание функции sigaction(). |
| Закрыть окно |
| save_traps=$(trap) . . . eval "$save_traps" |
| Листинг 8.11. Пример сохранения и восстановления способа обработки сигналов посредством специальной встроенной команды trap. |
| Закрыть окно |
| trap '$HOME/logout' EXIT |
| Листинг 8.12. Пример использования специальной встроенной команды trap. |
| Закрыть окно |
| trap "" PIPE echo "$INITLOG_ARGS -n $0 -s \"$1\" -e 1" >&21 trap - PIPE |
| Листинг 8.13. Пример использования специальной встроенной команды trap для защиты от ошибок, специфичных для каналов. |
| Закрыть окно |
|
#include |
| Листинг 8.14. Описание функций для работы с наборами сигналов. |
| Закрыть окно |
|
#include |
| Листинг 8.15. Описание функции sigprocmask(). |
| Закрыть окно |
|
#include |
| Листинг 8.16. Описание функции sigpending(). |
| Закрыть окно |
|
#include |
| Листинг 8.17. Описание функции sigwait(). |
| Закрыть окно |
|
#include |
| Листинг 8.18. Описание функции pause(). |
| Закрыть окно |
|
#include |
| Листинг 8.19. Описание функции sigsuspend(). |
| Закрыть окно |
|
#include void abort (void) { struct sigaction sact; sigset_t sset; /* Вытолкнем буфера */ (void) fflush (NULL); /* Снимем блокировку сигнала SIGABRT */ if ((sigemptyset (&sset) == 0) && (sigaddset (&sset, SIGABRT) == 0)) { (void) sigprocmask (SIG_UNBLOCK, &sset, (sigset_t *) NULL); } /* Пошлем себе сигнал SIGABRT. */ /* Возможно, его перехватит функция обработки, */ /* и тогда вызывающий процесс может не завершиться */ raise (SIGABRT); /* Установим подразумеваемую реакцию на сигнал SIGABRT */ sact.sa_handler = SIG_DFL; sigfillset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIGABRT, &sact, NULL); /* Снова пошлем себе сигнал SIGABRT */ raise (SIGABRT); /* Если сигнал снова не помог, попробуем еще одно средство завершения */ _exit (127); } int main (void) { printf ("Перед вызовом abort()\n"); abort (); printf ("После вызова abort()\n"); return 0; } |
| Листинг 8.20. Упрощенная реализация функции abort() как пример использования функций работы с сигналами. |
| Закрыть окно |
|
#include /* Функция обработки сигнала SIGALRM. */ /* Она ничего не делает, но игнорировать сигнал нельзя */ static void signal_handler (int sig) { /* В демонстрационных целях распечатаем номер обрабатываемого сигнала */ printf ("Принят сигнал %d\n", sig); } /* Функция для "засыпания" на заданное число секунд */ /* Результат равен разности между заказанной и фактической */ /* продолжительностью "сна" */ unsigned int sleep (unsigned int seconds) { time_t before, after; unsigned int slept; sigset_t set, oset; struct sigaction act, oact; if (seconds == 0) { return 0; } /* Установим будильник на заданное время, */ /* но перед этим блокируем сигнал SIGALRM */ /* и зададим свою функцию обработки для него */ if ((sigemptyset (&set) < 0) || (sigaddset (&set, SIGALRM) < 0) || sigprocmask (SIG_BLOCK, &set, &oset)) { return seconds; } act.sa_handler = signal_handler; act.sa_flags = 0; act.sa_mask = oset; if (sigaction (SIGALRM, &act, &oact) < 0) { return seconds; } before = time ((time_t *) NULL); (void) alarm (seconds); /* Как атомарное действие восстановим старую маску сигналов */ /* (в надежде, что она не блокирует SIGALRM) */ /* и станем ждать доставки обрабатываемого сигнала */ (void) sigsuspend (&oset); /* сигнал доставлен и обработан */ after = time ((time_t *) NULL); /* Восстановим прежний способ обработки сигнала SIGALRM */ (void) sigaction (SIGALRM, &oact, (struct sigaction *) NULL); /* Восстановим первоначальную маску сигналов */ (void) sigprocmask (SIG_SETMASK, &oset, (sigset_t *) NULL); return ((slept = after - before) > seconds ? 0 : (seconds - slept)); } int main (void) { struct sigaction act; /* В демонстрационных целях установим обработку прерывания с клавиатуры */ act.sa_handler = signal_handler; (void) sigemptyset (&act.sa_mask); act.sa_flags = 0; (void) sigaction (SIGINT, &act, (struct sigaction *) NULL); printf ("Заснем на 10 секунд\n"); printf ("Проснулись, не доспав %d секунд\n", sleep (10)); return (0); } |
| Листинг 8.21. Упрощенная реализация функции sleep() как пример использования механизма сигналов. |
| Закрыть окно |
|
#include |
| Листинг 8.22. Описание функции ftok(). |
| Закрыть окно |
|
#include |
| Листинг 8.23. Описание функций для работы с очередями сообщений. |
| Закрыть окно |
|
#include /* Программа создает очередь сообщений. */ /* В командной строке задаются имя файла для ftok() */ /* и режим доступа к очереди сообщений */ #define FTOK_CHAR 'G' int main (int argc, char *argv []) { key_t key; int msqid; int mode = 0; if (argc != 3) { fprintf (stderr, "Использование: %s маршрутное_имя режим_доступа\n", argv [0]); return (1); } if ((key = ftok (argv [1], FTOK_CHAR)) == (key_t) (-1)) { perror ("FTOK"); return (2); } (void) sscanf (argv [2], "%o", (unsigned int *) &mode); if ((msqid = msgget (key, IPC_CREAT | mode)) < 0) { perror ("MSGGET"); return (3); } return 0; } |
| Листинг 8.24. Пример программы, создающей очередь сообщений. |
| Закрыть окно |
| ------ Message Queues -------- key msqid owner perms used- bytes messages 0x47034bac 163840 galat 644 0 0 |
| Листинг 8.25. Возможный результат опроса статуса очередей сообщений. |
| Закрыть окно |
| struct msgbuf { long mtype; /* Тип сообщения */ char mtext [1]; /* Текст сообщения */ }; |
| Листинг 8.26. Описание структурного типа для представления сообщений. |
| Закрыть окно |
|
#define MAXSZTMSG 8192 struct mymsgbuf { long mtype; /* Тип сообщения */ char mtext [MAXSZTMSG]; /* Текст сообщения */ }; struct mymsgbuf msgbuf; |
| Листинг 8.27. Описание структуры для хранения сообщений. |
| Закрыть окно |
|
#include int main (int argc, char *argv []) { int msqid; struct msqid_ds msqid_ds; if (argc != 3) { fprintf (stderr, "Использование: %s идентификатор_очереди максимальный_размер\n", argv [0]); return (1); } (void) sscanf (argv [1], "%d", &msqid); /* Получим исходное значение структуры данных */ if (msgctl (msqid, IPC_STAT, &msqid_ds) == -1) { perror ("IPC_STAT-1"); return (2); } printf ("Максимальный размер очереди до изменения: %ld\n", msqid_ds.msg_qbytes); (void) sscanf (argv [2], "%d", (int *) &msqid_ds.msg_qbytes); /* Попробуем внести изменения */ if (msgctl (msqid, IPC_SET, &msqid_ds) == -1) { perror ("IPC_SET"); } /* Получим новое значение структуры данных */ if (msgctl (msqid, IPC_STAT, &msqid_ds) == -1) { perror ("IPC_STAT-2"); return (3); } printf ("Максимальный размер очереди после изменения: %ld\n", msqid_ds.msg_qbytes); return 0; } |
| Листинг 8.28. Пример программы управления очередями сообщений. |
| Закрыть окно |
|
#include /* Программа копирует строки со стандартного ввода на стандартный вывод, */ /* "прокачивая" их через очередь сообщений */ #define FTOK_FILE "/home/galat" #define FTOK_CHAR "G" #define MSGQ_MODE 0644 #define MY_PROMPT "Вводите строки\n" #define MY_MSG "Вы ввели: " int main (void) { key_t key; int msqid; struct mymsgbuf { long mtype; char mtext [LINE_MAX]; } line_buf, msgbuf; switch (fork ()) { case -1: perror ("FORK"); return (1); case 0: /* Чтение из очереди и выдачу на стандартный вывод */ /* реализуем в порожденном процессе. */ (void) execl ("./msq_child", "msq_child", FTOK_FILE, FTOK_CHAR, (char *) 0); perror ("EXEC"); return (2); /* execl() завершился неудачей */ } /* Чтение со стандартного ввода и запись в очередь */ /* возложим на родительский процесс */ /* Выработаем ключ для очереди сообщений */ if ((key = ftok (FTOK_FILE, FTOK_CHAR [0])) == (key_t) (-1)) { perror ("FTOK"); return (3); } /* Получим идентификатор очереди сообщений */ if ((msqid = msgget (key, IPC_CREAT | MSGQ_MODE)) < 0) { perror ("MSGGET"); return (4); } /* Приступим к отправке сообщений в очередь */ msgbuf.mtype = line_buf.mtype = 1; strncpy (msgbuf.mtext, MY_PROMPT, sizeof (msgbuf.mtext)); if (msgsnd (msqid, (void *) &msgbuf, strlen (msgbuf.mtext) + 1, 0) != 0) { perror ("MSGSND-1"); return (5); } strncpy (msgbuf.mtext, MY_MSG, sizeof (msgbuf.mtext)); while (fgets (line_buf.mtext, sizeof (line_buf.mtext), stdin) != NULL) { if (msgsnd (msqid, (void *) &msgbuf, strlen (msgbuf.mtext) + 1, 0) != 0) { perror ("MSGSND-2"); break; } if (msgsnd (msqid, (void *) &line_buf, strlen (line_buf.mtext) + 1, 0) != 0) { perror ("MSGSND-3"); break; } } /* Удалим очередь */ if (msgctl (msqid, IPC_RMID, NULL) == -1) { perror ("MSGCTL-IPC_RMID"); return (6); } return (0); } |
| Листинг 8.29. Передающая часть программы работы с очередями сообщений. |
| Закрыть окно |
|
#include /* Программа получает сообщения из очереди */ /* и копирует их тела на стандартный вывод */ #define MSGQ_MODE 0644 int main (int argc, char *argv []) { key_t key; int msqid; struct mymsgbuf { long mtype; char mtext [LINE_MAX]; } msgbuf; if (argc != 3) { fprintf (stderr, "Использование: %s имя_файла цепочка_символов\n", argv [0]); return (1); } /* Выработаем ключ для очереди сообщений */ if ((key = ftok (argv [1], *argv [2])) == (key_t) (-1)) { perror ("CHILD FTOK"); return (2); } /* Получим идентификатор очереди сообщений */ if ((msqid = msgget (key, IPC_CREAT | MSGQ_MODE)) < 0) { perror ("CHILD MSGGET"); return (3); } /* Цикл приема сообщений и выдачи строк */ while (msgrcv (msqid, (void *) &msgbuf, sizeof (msgbuf.mtext), 0, 0) > 0) { if (fputs (msgbuf.mtext, stdout) == EOF) { break; } } return 0; } |
| Листинг 8.30. Приемная часть программы работы с очередями сообщений. |
| Закрыть окно |
|
#include |
| Листинг 8.31. Описание функций для работы с семафорами. |
| Закрыть окно |
|
sembuf [0].sem_num = 1; sembuf [0].sem_flg = 0; sembuf [0].sem_op = -2; sembuf [1].sem_num = 0; sembuf [1].sem_flg = IPC_NOWAIT; sembuf [1].sem_op = 0; |
| Листинг 8.32. Пример задания массива операций над семафорами. |
| Закрыть окно |
| union semun { int val; struct semid_ds *buf; unsigned short *array; } arg; |
| Листинг 8.33. Описание четвертого (дополнительного) аргумента функции semctl(). |
| Закрыть окно |
|
val = semctl (semid, semnum, GETVAL); arg.val = ...; if (semctl (semid, semnum, SETVAL, arg) == -1) ...; arg.array = ( unsigned short *) malloc (nsems * sizeof (unsigned short)); err = semctl (semid, 0, GETALL, arg); for (i = 0; i < nsems; i++) arg.array [i] = ...; err = semctl (semid, 0, SETALL, arg); lpid = semctl (semid, semnum, GETPID); ncnt = semctl (semid, semnum, GETNCNT); zcnt = semctl (semid, semnum, GETZCNT); |
| Листинг 8.34. Примеры управляющих действий над семафорами. |
| Закрыть окно |
| arg.buf = (struct semid_ds *) malloc (sizeof (struct semid_ds); err = semctl (semid, 0, IPC_STAT, arg); arg.buf->sem_perm.mode = 0644; err = semctl (semid, 0, IPC_SET, arg); |
| Листинг 8.35. Дополнительные примеры управляющих действий над семафорами. |
| Закрыть окно |
|
#include /* Программа-монитор обеда философов */ #define QPH 5 #define ARG_SIZE 20 int main (void) { int key; /* Ключ набора семафоров */ int semid; /* Идентификатор набора семафоров */ int no; /* Номер философа и/или вилки */ char ssemid [ARG_SIZE], sno [ARG_SIZE], sqph [ARG_SIZE]; /* Создание и инициализация набора семафоров */ /* (по семафору на вилку) */ key = ftok ("phdin.c", 'C'); if ((semid = semget (key, QPH, 0600 | IPC_CREAT)) < 0) { perror ("SEMGET"); return (1); } for (no = 0; no < QPH; no++) { if (semctl (semid, no, SETVAL, 1) < 0) { perror ("SETVAL"); return (2); } } sprintf (ssemid, "%d", semid); sprintf (sqph, "%d", QPH); /* Все - к столу */ for (no = 1; no <= QPH; no++) { switch (fork ()) { case -1: perror ("FORK"); return (3); case 0: sprintf (sno, "%d", no); execl ("./phil", "phil", ssemid, sqph, sno, (char *) 0); perror ("EXEC"); return (4); } } /* Ожидание завершения обеда */ for (no = 1; no <= QPH; no++) { (void) wait (NULL); } /* Удаление набора семафоров */ if (semctl (semid, 0, IPC_RMID) < 0) { perror ("SEMCTL"); return (5); } return 0; } |
| Листинг 8.36. Процесс-монитор для обеда философов. |
| Закрыть окно |
|
#include /* Процесс обеда одного философа */ #define ernd (rand () % 3 + 1) #define trnd (rand () % 5 + 1) #define FO 15 int main (int argc, char *argv []) { int semid; /* Идентификатор набора семафоров */ int qph; /* Число философов */ int no; /* Номер философа */ int t; /* Время очередного отрезка еды или беседы */ int fo; /* Время до конца обеда */ struct sembuf sembuf [2]; if (argc != 4) { fprintf (stderr, "Использование: %s идентификатор_набора_семафоров число_философов номер_философа \n", argv [0]); return (1); } fo = FO; sscanf (argv [1], "%d", &semid); sscanf (argv [2], "%d", &qph); sscanf (argv [3], "%d", &no); /* Выбор вилок */ sembuf [0].sem_num = no - 1; /* Левая */ sembuf [0].sem_flg = 0; sembuf [1].sem_num = no % qph; /* Правая */ sembuf [1].sem_flg = 0; while (fo > 0) { /* Обед */ /* Философ говорит */ printf ("Философ %d беседует\n", no); t = trnd; sleep (t); fo -= t; /* Пытается взять вилки */ sembuf [0].sem_op = -1; sembuf [1].sem_op = -1; if (semop (semid, sembuf, 2) < 0) { perror ("SEMOP"); return (1); } /* Ест */ printf ("Философ %d ест\n", no); t = ernd; sleep (t); fo -= t; /* Отдает вилки */ sembuf [0].sem_op = 1; sembuf [1].sem_op = 1; if (semop (semid, sembuf, 2) < 0) { perror ("SEMOP"); return (2); } } printf ("Философ %d закончил обед\n", no); return 0; } |
| Листинг 8.37. Программа, описывающая обед одного философа. |
| Закрыть окно |
|
/* Обедающие философы. Запуск: mudrecProc [-a | -p | -I -V] [-t число_секунд] имя_философа ... Опции: -t число_секунд - сколько секунд моделируется Стратегии захвата вилок: -a - сначала захватывается вилка с меньшим номером; -I - некорректная (но эффективная) интеллигентная стратегия: во время ожидания уже захваченная вилка кладется; -p - сначала захватывается нечетная вилка; -V - использован групповой захват семафоров. Пример запуска: mudrecProc -p -t 600 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z */ static char rcsid[] __attribute__((unused)) = \ "$Id: mudrecProc.c,v 1.7 2003/11/11 13:14:07 sambor Exp $"; #include #include union semun { int val; struct semid_ds *buf; unsigned short *array; } arg; #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)>(b)?(b):(a)) struct mudrec { long num; char *name; int left_fork, right_fork; int eat_time, wait_time, think_time, max_wait_time; int count; }; int Stop; /* Семафор для синхронизации выхода */ /* Различные дескрипторы */ int protokol [2] = {-1, -1}; #define pFdIn (protokol [1]) #define pFdOut (protokol [0]) int semFork; /* Вилки */ int from_fil; /* Очередь для возврата результатов */ /* Разные алгоритмы захвата вилок */ static void get_forks_simple (struct mudrec *this); static void get_forks_parity (struct mudrec *this); static void get_forks_maybe_infinit_time (struct mudrec *this); static void get_forks_use_groups (struct mudrec *this); /* Используемый метод захвата вилок */ void (*get_forks) (struct mudrec *this) = get_forks_simple; /* Возвращение вилок */ static void put_forks (struct mudrec *this); /* * Философы */ void filosof (struct mudrec this) { char buffer [LINE_MAX]; int bytes; if (fork ()) return; srandom (getpid ()); /* Очень важно для процессов, иначе получим одно и то же! */ random (); random (); random (); random (); random (); random (); random (); random (); random (); random (); /* Пока семафор Stop не поднят */ while (!semctl (Stop, 0, GETVAL)) { /* Пора подкрепиться */ { int wait_time, tm; sprintf (buffer, "%s: хочет есть\n", this.name); bytes = write (pFdIn, buffer, strlen (buffer)); tm = time (0); (*get_forks) (&this); wait_time = time (0) - tm; /* Сколько времени получали вилки */ this.wait_time += wait_time; this.max_wait_time = max (wait_time, this.max_wait_time); sprintf (buffer, "%s: ждал вилок %d сек\n", this.name, wait_time); bytes = write (pFdIn, buffer, strlen (buffer)); } /* Может, обед уже закончился? */ if (semctl (Stop, 0, GETVAL)) { put_forks (&this); break; } /* Едим */ { int eat_time = random () % 20 + 1; sleep (eat_time); this.eat_time += eat_time; this.count++; sprintf (buffer,"%s: ел %d сек\n", this.name, eat_time); bytes = write (pFdIn, buffer, strlen (buffer)); } /* Отдаем вилки */ put_forks (&this); if (semctl (Stop, 0, GETVAL)) break; /* Размышляем */ { int think_time = random () % 10 + 1; sleep (think_time); this.think_time += think_time; } } sprintf (buffer,"%s: уходит\n", this.name); bytes = write (pFdIn, buffer, strlen (buffer)); msgsnd (from_fil, &this, sizeof (this), 0); /* Отослали статистику своего обеда */ _exit (0); /* ВАЖНО (_): Нам не нужны преждевременные вызовы cleanup_ipc */ } /* Кладем вилки одну за другой */ static void put_forks (struct mudrec *this) { struct sembuf tmp_buf; tmp_buf.sem_flg = 0; tmp_buf.sem_op = 1; tmp_buf.sem_num = this->left_fork - 1; semop (semFork, &tmp_buf, 1); tmp_buf.sem_flg = 0; tmp_buf.sem_op = 1; tmp_buf.sem_num = this->right_fork - 1; semop (semFork, &tmp_buf, 1); } /* Берем вилки по очереди в порядке номеров */ static void get_forks_simple (struct mudrec *this) { struct sembuf tmp_buf; int first = min (this->left_fork, this->right_fork); int last = max (this->left_fork, this->right_fork); tmp_buf.sem_flg = SEM_UNDO; tmp_buf.sem_op = -1; tmp_buf.sem_num = first - 1; semop (semFork, &tmp_buf, 1); tmp_buf.sem_flg = SEM_UNDO; tmp_buf.sem_op = -1; tmp_buf.sem_num = last - 1; semop (semFork, &tmp_buf, 1); } /* Берем сначала нечетную вилку (если обе нечетные - то с большим номером) */ static void get_forks_parity (struct mudrec *this) { struct sembuf tmp_buf; int left = this->left_fork, right = this->right_fork; int first = max ((left & 1) * 1000 + left, (right & 1) * 1000 + right) % 1000; int last = min ((left & 1) * 1000 + left, (right & 1) * 1000 + right) % 1000; tmp_buf.sem_flg = SEM_UNDO; tmp_buf.sem_op = -1; tmp_buf.sem_num = first - 1; semop (semFork, &tmp_buf, 1); tmp_buf.sem_flg = SEM_UNDO; tmp_buf.sem_op = -1; tmp_buf.sem_num = last - 1; semop (semFork, &tmp_buf, 1); } /* Берем вилки по очереди, в произвольном порядке. * Но если вторая вилка не берется сразу, то кладем первую. * То есть философ не расходует вилочное время впустую. */ static void get_forks_maybe_infinit_time (struct mudrec *this) { struct sembuf tmp_buf; int left = this->left_fork, right = this->right_fork; for (;;) { tmp_buf.sem_flg = SEM_UNDO; /* Первую вилку берем с ожиданием */ tmp_buf.sem_op = -1; tmp_buf.sem_num = left - 1; semop (semFork, &tmp_buf, 1); tmp_buf.sem_flg = SEM_UNDO | IPC_NOWAIT; /* Вторую - без ожидания */ tmp_buf.sem_op = -1; tmp_buf.sem_num = right - 1; if (0 == semop (semFork, &tmp_buf, 1)) return; /* Успех */ tmp_buf.sem_flg = 0; /* Неуспех: возвращаем первую вилку */ tmp_buf.sem_op = 1; tmp_buf.sem_num = left - 1; semop(semFork,&tmp_buf,1); tmp_buf.sem_flg = SEM_UNDO; /* Отдав первую, ждем вторую */ tmp_buf.sem_op = -1; tmp_buf.sem_num = right - 1; semop (semFork, &tmp_buf, 1); tmp_buf.sem_flg = SEM_UNDO | IPC_NOWAIT; /* Берем первую вилку без ожидания */ tmp_buf.sem_op = -1; tmp_buf.sem_num = left - 1; if (0 == semop (semFork, &tmp_buf, 1)) return; /* Успех */ tmp_buf.sem_flg = 0; /* Неуспех: отдаем вторую вилку, */ tmp_buf.sem_op = 1; /* чтобы ждать первую */ tmp_buf.sem_num = right - 1; semop (semFork, &tmp_buf, 1); } } /* Хватаем обе вилки сразу, используя групповые операции */ static void get_forks_use_groups (struct mudrec *this) { struct sembuf tmp_buf [2]; tmp_buf[0].sem_flg = SEM_UNDO; tmp_buf[0].sem_op = -1; tmp_buf[0].sem_num = this->left_fork - 1; tmp_buf[1].sem_flg = SEM_UNDO; tmp_buf[1].sem_op = -1; tmp_buf[1].sem_num = this->right_fork - 1; semop (semFork, tmp_buf, 2); } /* * Мелкие служебные функции. */ static void stop (int dummy) { struct sembuf tmp_buf; tmp_buf.sem_flg = 0; tmp_buf.sem_op = 1; tmp_buf.sem_num = 0; semop (Stop, &tmp_buf, 1); } void cleanup_ipc (void) { /* * Уничтожение семафоров. */ semctl (semFork, 1, IPC_RMID); semctl (Stop, 1, IPC_RMID); /* То же с очередью */ msgctl (from_fil, IPC_RMID, NULL); } static void usage (char name []) { fprintf (stderr,"Использование: %s [-a | -p | -I| -V] [-t число_секунд] имя_философа ...\n", name); exit (1); } /* * Точка входа демонстрационной программы. */ int main (int argc, char *argv[]) { char buffer [LINE_MAX], *p; int i, n, c; int open_room_time = 300; union semun tmp_arg; int nMudr; struct sigaction sact; while ((c = getopt (argc, argv, "apIVt:")) != -1) { switch (c) { case 'a': get_forks = get_forks_simple; break; case 'p': get_forks = get_forks_parity; break; case 'I': get_forks = get_forks_maybe_infinit_time; break; case 'V': get_forks = get_forks_use_groups; break; case 't': open_room_time = strtol (optarg, &p, 0); if (optarg [0] == 0 || *p != 0) usage (argv [0]); break; default: usage (argv [0]); } } nMudr = argc - optind; if (nMudr < 2) usage (argv [0]); /* Меньше двух философов неинтересно ... */ /* * Создание канала для протокола обработки событий */ pipe (protokol); /* * Создадим семафоры для охраны вилок */ semFork = semget (ftok (argv [0], 2), nMudr, IPC_CREAT | 0777); tmp_arg.val = 1; for (i=1; i <= nMudr; i++) semctl (semFork, i - 1, SETVAL, tmp_arg); /* Начальное значение 1 */ /* Прежде чем впускать философов, обеспечим окончание обеда */ Stop = semget (ftok (argv [0], 3), 1, IPC_CREAT | 0777); tmp_arg.val = 0; semctl (Stop, 0, SETVAL, tmp_arg); /* Начальное значение 0 */ /* Очередь для возврата результатов */ from_fil = msgget (ftok (argv [0], 4), IPC_CREAT | 0777); atexit (cleanup_ipc); /* Запланировали уничтожение семафоров */ /* и других средств межпроцессного взаимодействия */ /* * Философы входят в столовую */ for (i = 0; i < nMudr; i++, optind++) { struct mudrec next; memset (&next, 0, sizeof (next)); next.num = i + 1; /* Номер */ next.name = argv [optind]; /* Имя */ /* Указали, какими вилками пользоваться */ next.left_fork = i + 1; next.right_fork = i + 2; if (i == nMudr - 1) next.right_fork = 1; /* Последний пользуется вилкой первого */ filosof (next); } /* Зададим реакцию на сигналы и установим будильник на конец обеда */ sact.sa_handler = stop; (void) sigemptyset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIGINT, &sact, (struct sigaction *) NULL); (void) sigaction (SIGALRM, &sact, (struct sigaction *) NULL); alarm (open_room_time); /* * Выдача сообщений на стандартный вывод и выход после окончания обеда. */ close (pFdIn); /* Сами должны закрыть, иначе из цикла не выйдем! */ for (;;) { n = read (pFdOut, buffer, LINE_MAX); if ((n == 0) || ((n == -1) && (errno != EINTR))) break; for (i = 0; i < n; i++) putchar (buffer [i]); } close (pFdOut); /* Распечатали сводную информацию */ { int full_eating_time = 0; int full_waiting_time = 0; int full_thinking_time = 0; for (i = 1; i <= nMudr; i++) { struct mudrec this; /* Получили статистику обеда */ msgrcv (from_fil, &this, sizeof (this), i, 0); /* За счет i получаем */ /* строго по порядку */ full_eating_time += this.eat_time; full_waiting_time += this.wait_time; full_thinking_time += this.think_time; if (this.count > 0) { float count = this.count; float think_time = this.think_time / count; float eat_time = this.eat_time / count; float wait_time = this.wait_time / count; printf ("%s: ел %d раз в среднем: думал=%.1f ел=%.1f ждал=%.1f (максимум %d)\n", this.name, this.count, think_time, eat_time, wait_time, this.max_wait_time); } else printf("%s: не поел\n", this.name); } { float total_time = (full_eating_time + full_waiting_time + full_thinking_time) / (float)nMudr; printf (" Среднее число одновременно едящих = %.3f\n Среднее число одновременно ждущих = %.3f\n", full_eating_time / total_time, full_waiting_time / total_time); } } /* Сообщим об окончании работы */ printf ("Конец обеда\n"); return 0; } |
| Листинг 8.38. Второй вариант решения задачи об обедающих философах. |
| Закрыть окно |
|
-a: A: ел 2 раза в среднем: думал=3.5 ел=11.5 ждал=36.5 (максимум 73) B: ел 3 раза в среднем: думал=5.7 ел=7.7 ждал=20.0 (максимум 41) C: ел 3 раза в среднем: думал=5.7 ел=11.3 ждал=17.0 (максимум 33) D: ел 3 раза в среднем: думал=1.7 ел=16.7 ждал=15.7 (максимум 19) E: ел 1 раз в среднем: думал=10.0 ел=20.0 ждал=73.0 ( максимум 41) Среднее число одновременно едящих = 1.471 Среднее число одновременно ждущих = 2.980 -p: A: ел 3 раза в среднем: думал=3.7 ел=15.3 ждал=16.0 (максимум 34) B: ел 4 раза в среднем: думал=5.0 ел=13.8 ждал=8.2 (максимум 15) C: ел 3 раза в среднем: думал=6.7 ел=3.7 ждал=25.7 (максимум 27) D: ел 4 раза в среднем: думал=5.8 ел=8.5 ждал=13.8 (максимум 28) E: ел 3 раза в среднем: думал=5.3 ел=15.3 ждал=16.7 (максимум 29) Среднее число одновременно едящих = 1.761 Среднее число одновременно ждущих = 2.413 -I: A: ел 5 раз в среднем: думал=4.2 ел=9.4 ждал=6.6 (максимум 15) B: ел 3 раза в среднем: думал=6.3 ел=10.3 ждал=17.0 (максимум 31) C: ел 4 раза в среднем: думал=6.8 ел=7.0 ждал=12.2 (максимум 45) D: ел 3 раза в среднем: думал=4.3 ел=16.0 ждал=13.0 (максимум 16) E: ел 4 раза в среднем: думал=5.8 ел=8.5 ждал=10.8 (максимум 22) Среднее число одновременно едящих = 1.858 Среднее число одновременно ждущих = 2.125 -V: A: ел 5 раз в среднем: думал=5.6 ел=5.6 ждал=8.8 (максимум 17) B: ел 3 раза в среднем: думал=6.3 ел=10.3 ждал=16.7 (максимум 20) C: ел 4 раза в среднем: думал=4.8 ел=11.0 ждал=9.8 (максимум 18) D: ел 4 раза в среднем: думал=5.2 ел=12.0 ждал=8.8 (максимум 15) E: ел 4 раза в среднем: думал=5.2 ел=10.5 ждал=10.2 (максимум 20) Среднее число одновременно едящих = 1.892 Среднее число одновременно ждущих = 2.049 |
| Листинг 8.39. Результаты моделирования поведения философов. |
| Закрыть окно |
|
#include |
| Листинг 8.40. Описание функций для работы с разделяемыми сегментами памяти. |
| Закрыть окно |
|
#include int main (void) { struct region { pid_t fpid; } *shm_ptr; struct sembuf P = {0, -1, 0}; struct sembuf V = {0, 1, 0}; int shmid; int semid; shmid = shmget (IPC_PRIVATE, sizeof (struct region), 0777); semid = semget (IPC_PRIVATE, 1, 0777); (void) semctl (semid, 0, SETVAL, 1); switch (fork ()) { case -1: perror ("FORK"); return (1); case 0: if ((int) (shm_ptr = (struct region *) shmat (shmid, NULL, 0)) == (-1)) { perror ("CHILD-SHMAT"); return (2); } if (semop (semid, &p, 1) != 0) { perror ("CHILD-SEMOP-P"); return (3); } printf ("Процесс-потомок вошел в критический интервал\n"); shm_ptr->fpid = getpid (); /* Монопольный доступ */ printf ("Процесс- потомок перед выходом из критического интервала\n"); if (semop (semid, &V, 1) != 0) { perror ("CHILD-SEMOP-V"); return (4); } (void) shmdt (shm_ptr); return 0; } if ((int) (shm_ptr = (struct region *) shmat (shmid, NULL, 0)) == (-1)) { perror ("PARENT-SHMAT"); return (2); } if (semop (semid, &p, 1) != 0) { perror ("PARENT-SEMOP-P"); return (3); } printf ("Родительский процесс вошел в критический интервал\n"); shm_ptr->fpid = getpid (); /* Монопольный доступ */ printf ("Родительский процесс перед выходом из критического интервала\n"); if (semop (semid, &V, 1) != 0) { perror ("PARENT-SEMOP-V"); return (4); } (void) wait (NULL); printf ("Идентификатор родительского процесса: %d\n", getpid ()); printf ("Идентификатор процесса в разделяемой структуре: %d\n", shm_ptr->fpid); (void) shmdt (shm_ptr); (void) semctl (semid, 1, IPC_RMID); (void) shmctl (shmid, IPC_RMID, NULL); return 0; } |
| Листинг 8.41. Пример работы с разделяемыми сегментами памяти. |
| Закрыть окно |
| Родительский процесс вошел в критический интервал Родительский процесс перед выходом из критического интервала Процесс-потомок вошел в критический интервал Процесс-потомок перед выходом из критического интервала Идентификатор родительского процесса: 2161 Идентификатор процесса в разделяемой структуре: 2162 |
| Листинг 8.42. Возможный результат синхронизации доступа к разделяемым данным. |
| Закрыть окно |
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Реализация "виртуальной" памяти из одного сегмента. */ /* Используются разделяемые сегменты памяти */ /* и обработка сигнала SIGSEGV */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include /* Константа, зависящая от реализации */ #define SHM_BASE_ADDR 0x40014000 static int shm_id = -1; static void *shm_addr; /* Реакция на сигнал SIGSEGV. */ /* Создаем и присоединяем на чтение разделяемый сегмент, */ /* накрывающий переданный адрес. */ /* Если это не помогло, переприсоединяем сегмент на запись */ static void sigsegv_sigaction (int sig, siginfo_t *sig_info, void *addr) { struct shmid_ds shmid_ds; if (shm_id == -1) { /* Сегмента еще нет. Создадим */ if ((shm_id = shmget (IPC_PRIVATE, SHMLBA, S_IRUSR)) == -1) { perror ("SHMGET"); exit (1); } /* Присоединим сегмент на чтение */ if ((int) (shm_addr = shmat (shm_id, sig_info->si_addr, SHM_RDONLY | SHM_RND)) == (-1)) { perror ("SHMAT-RDONLY"); exit (2); } return; } else { /* Сегмент уже есть, но обращение по адресу вызвало сигнал SIGSEGV. */ /* Значит, это была попытка записи, и сегмент нужно */ /* переприсоединить на запись, поменяв соответственно режим доступа */ if (shmctl (shm_id, IPC_STAT, &shmid_ds) == -1) { perror ("SHMCTL-IPC_STAT"); exit (3); } shmid_ds.shm_perm.mode |= S_IWUSR; if (shmctl (shm_id, IPC_SET, &shmid_ds) == -1) { perror ("SHMCTL-IPC_SET"); exit (4); } (void) shmdt (shm_addr); if (shmat (shm_id, shm_addr, 0) != shm_addr) { perror ("SHMAT-RDWD"); exit (5); } } } int main (void) { char *test_ptr; struct sigaction sact; /* Установим реакцию на сигнал SIGSEGV */ (void) sigemptyset (&sact.sa_mask); sact.sa_flags = SA_SIGINFO; sact.sa_sigaction = sigsegv_sigaction; (void) sigaction (SIGSEGV, &sact, (struct sigaction *) NULL); /* Убедимся, что разделяемые сегменты инициализируются нулями */ test_ptr = (char *) (SHM_BASE_ADDR + 3); printf ("Результат попытки чтения до записи: %x\n", *test_ptr); /* Попробуем записать */ *test_ptr = 'A'; printf ("Результат попытки чтения после записи: %x\n", *test_ptr); return (shmctl (shm_id, IPC_RMID, NULL)); } |
| Листинг 8.43. Пример работы с разделяемыми сегментами памяти и сигналами. |
| Закрыть окно |
Разделяемые сегменты памяти
В стандарте POSIX-2001 разделяемый объект памяти определяется как объект, представляющий собой память, которая может быть параллельно отображена в адресное пространство более чем одного процесса.Таким образом, процессы могут иметь общие области виртуальной памяти и разделять содержащиеся в них данные. Единицей разделяемой памяти являются сегменты. Разделение памяти обеспечивает наиболее быстрый обмен данными между процессами.
Работа с разделяемой памятью начинается с того, что один из взаимодействующих процессов посредством функции shmget() создает разделяемый сегмент, специфицируя первоначальные права доступа к нему и его размер в байтах.
Чтобы получить доступ к разделяемому сегменту, его нужно присоединить (для этого служит функция shmat()), т. е. разместить сегмент в виртуальном пространстве процесса. После присоединения, в соответствии с правами доступа, процессы могут читать данные из сегмента и записывать их (быть может, синхронизируя свои действия с помощью семафоров). Когда разделяемый сегмент становится ненужным, его следует отсоединить с помощью функции shmdt().
Предусмотрена возможность выполнения управляющих действий над разделяемыми сегментами (функция shmctl()).
Описание перечисленных функций представлено в листинге 8.40.
#include
Листинг 8.40. Описание функций для работы с разделяемыми сегментами памяти. (html, txt)
Структура shmid_ds, ассоциированная с идентификатором разделяемого сегмента памяти, должна содержать по крайней мере следующие поля.
struct ipc_perm shm_perm; /* Данные о правах доступа к разделяемому сегменту */ size_t shm_segsz; /* Размер сегмента в байтах */ pid_t shm_lpid; /* Идентификатор процесса, выполнившего последнюю операцию над разделяемым сегментом */ pid_t shm_cpid; /* Идентификатор процесса, создавшего разделяемый сегмент */ shmatt_t shm_nattch; /* Текущее число присоединений сегмента */ time_t shm_atime; /* Время последнего присоединения */ time_t shm_dtime; /* Время последнего отсоединения */ time_t shm_ctime; /* Время последнего изменения посредством shmctl() */
Функция shmget() аналогична msgget() и semget(); аргумент size задает нижнюю границу размера сегмента в байтах; реализация, учитывающая, например, правила выравнивания, имеет право создать разделяемый сегмент большего размера.
Структура shmid_ds инициализируется в соответствии с общими для средств межпроцессного взаимодействия правилами. Поле shm_segsz устанавливается равным значению аргумента size.
Число уникальных идентификаторов разделяемых сегментов памяти ограничено; попытка его превышения ведет к неудачному завершению shmget() (возвращается -1). Вызов shmget() завершится неудачей и тогда, когда значение аргумента size меньше минимально допустимого либо больше максимально допустимого размера разделяемого сегмента.
Чтобы присоединить разделяемый сегмент, используется функция shmat(). Аргумент shmid задает идентификатор разделяемого сегмента; аргумент shmaddr - адрес, по которому сегмент должен быть присоединен, т. е. тот адрес в виртуальном пространстве процесса, который получит начало сегмента. Поскольку свойства сегментов зависят от аппаратных особенностей управления памятью, не всякий адрес является приемлемым. Если установлен флаг SHM_RND, адрес присоединения округляется до величины, кратной константе SHMLBA.
Если shmaddr задан как пустой указатель, реализация выбирает адрес присоединения по своему усмотрению.
По умолчанию присоединяемый сегмент будет доступен и на чтение, и на запись (если процесс обладает необходимыми правами). Флаг SHM_RDONLY предписывает присоединить сегмент только для чтения.
При успешном завершении функции shmat() результат равен адресу, который получил присоединенный сегмент; в случае неудачи возвращается -1. (Разумеется, для использования результата shmat() в качестве указателя его нужно преобразовать к требуемому типу.)
Отсоединение сегментов производится функцией shmdt(); аргумент shmaddr задает начальный адрес отсоединяемого сегмента.
Управление разделяемыми сегментами осуществляется при помощи функции shmctl(), аналогичной msgctl().
Как и для очередей сообщений, для разделяемых сегментов определены управляющие команды IPC_STAT (получить информацию о состоянии разделяемого сегмента), IPC_SET (переустановить характеристики), IPC_RMID (удалить разделяемый сегмент). Удалять сегмент нужно после того, как от него отсоединились все процессы.
Аппарат разделяемых сегментов предоставляет нескольким процессам возможность одновременного доступа к общей области памяти. Обеспечивая корректность доступа, процессы тем или иным способом должны синхронизировать свои действия. В качестве средства синхронизации удобно использовать семафор. В листинге 8.41 показана реализация так называемого критического интервала - механизма, обеспечивающего взаимное исключение разделяющих общие данные процессов.
Для "создания" подобного механизма необходимо породить разделяемый сегмент памяти, присоединить его во всех процессах, которым предоставляется доступ к разделяемым данным, а также породить и проинициализировать простейший семафор. После этого монопольный доступ к разделяемой структуре обеспечивается применением P- и V-операций.
Листинг 8.41. Пример работы с разделяемыми сегментами памяти. (html, txt)
Результат работы приведенной программы может выглядеть так, как показано в листинге 8.42.
Листинг 8.42. Возможный результат синхронизации доступа к разделяемым данным. (html, txt)
В листинге 8.43 представлен пример использования разделяемых сегментов памяти в сочетании с обработкой сигнала SIGSEGV, который посылается процессу при некорректном обращении к памяти. Идея в том, чтобы создавать разделяемые сегменты, "накрывающие" запрашиваемые адреса. При некотором воображении пример можно считать основой программной реализации виртуальной памяти.
Листинг 8.43. Пример работы с разделяемыми сегментами памяти и сигналами. (html, txt)
Обратим внимание на использование флагов округления адреса присоединения разделяемого сегмента (SHM_RND) и присоединения только на чтение (SHM_RDONLY), а также обработчика сигналов, задаваемого полем sa_sigaction структуры типа sigaction (в сочетании с флагом SA_SIGINFO) и имеющего доступ к расширенной информации о сигнале и его причинах.
Семафоры
Согласно определению стандарта POSIX-2001, семафор - это минимальный примитив синхронизации, служащий основой для более сложных механизмов синхронизации, определенных в прикладной программе.У семафора есть значение, которое представляется целым числом в диапазоне от 0 до 32767.
Семафоры создаются (функцией semget()) и обрабатываются (функцией semop()) наборами (массивами), причем операции над наборами с точки зрения приложений являются атомарными. В рамках групповых операций для любого семафора из набора можно сделать следующее: увеличить значение, уменьшить значение, дождаться обнуления.
Процессы, обладающие соответствующими правами, также могут выполнять различные управляющие действия над семафорами. Для этого служит функция semctl().
Описание перечисленных функций представлено в листинге 8.31.
#include
Листинг 8.31. Описание функций для работы с семафорами. (html, txt)
Структура semid_ds, ассоциированная с идентификатором набора семафоров, должна содержать по крайней мере следующие поля.
struct ipc_perm sem_perm; /* Данные о правах доступа к набору семафоров */ unsigned short sem_nsems; /* Число семафоров в наборе */ time_t sem_otime; /* Время последней операции semop() */ time_t sem_ctime; /* Время последнего изменения посредством semctl() */
Отдельные семафоры из набора представляются безымянной структурой, состоящей по крайней мере из следующих полей.
unsigned short semval; /* Значение семафора */ pid_t sempid; /* Идентификатор процесса, выполнившего последнюю операцию над семафором */ unsigned short semncnt; /* Число процессов, ожидающих увеличения текущего значения семафора */ unsigned short semzcnt; /* Число процессов, ожидающих обнуления значения семафора */
Функция semget() аналогична msgget(); аргумент nsems задает число семафоров в наборе. Структура semid_ds инициализируется так же, как msqid_ds.
Безымянные структуры, соответствующие отдельным семафорам, функцией semget() не инициализируются.
Операции, выполняемые посредством функции semop(), задаются массивом sops с числом элементов nsops, состоящим из структур типа sembuf , каждая из которых содержит по крайней мере следующие поля.
unsigned short sem_num; /* Номер семафора в наборе (нумерация с нуля) */ short sem_op; /* Запрашиваемая операция над семафором */ short sem_flg; /* Флаги операции */
Операция над семафором определяется значением поля sem_op: положительное значение предписывает увеличить значение семафора на указанную величину, отрицательное - уменьшить, нулевое - сравнить с нулем. Вторая операция не может быть успешно выполнена, если в результате значение семафора становится отрицательным, а третья - если значение семафора ненулевое.
Выполнение массива операций с точки зрения пользовательского процесса является неделимым действием. Это значит, во-первых, что если операции выполняются, то только все вместе и, во-вторых, никакой другой процесс не может получить доступ к промежуточному состоянию набора семафоров, когда часть операций из массива уже выполнилась, а другая еще не успела. Операционная система, разумеется, выполняет операции из массива по очереди, причем порядок не оговаривается. Если очередная операция не может быть выполнена, то эффект предыдущих аннулируется, а вызов функции semop() приостанавливается (операция с блокировкой) или немедленно завершается неудачей (операция без блокировки). Подчеркнем, что в случае неудачного завершения вызова semop() значения всех семафоров в наборе останутся неизменными.
Приведенный в листинге 8.32 массив операций задает уменьшение (с блокировкой) семафора 1 при условии, что значение семафора 0 равно нулю.
sembuf [0].sem_num = 1; sembuf [0].sem_flg = 0; sembuf [0].sem_op = -2;
sembuf [1].sem_num = 0; sembuf [1].sem_flg = IPC_NOWAIT; sembuf [1].sem_op = 0;
Листинг 8.32. Пример задания массива операций над семафорами. (html, txt)
Обращаясь к функции semctl(), процессы могут получать информацию о состоянии набора семафоров, изменить ряд его характеристик, удалить набор.
Аргументы semid (идентификатор набора семафоров) и semnum (номер семафора в наборе) определяют объект, над которым выполняется управляющее действие, задаваемое значением аргумента cmd. Если объектом является набор, значение semnum игнорируется.
Для некоторых действий задействован четвертый аргумент (см. листинг 8.33).
union semun { int val; struct semid_ds *buf; unsigned short *array; } arg;
Листинг 8.33. Описание четвертого (дополнительного) аргумента функции semctl(). (html, txt)
Среди допустимых действий - GETVAL (получить значение семафора и выдать его в качестве результата) и SETVAL (установить значение семафора равным arg.val). Имеются и аналогичные групповые действия - GETALL (прочитать значения всех семафоров набора и поместить их в массив arg.array) и SETALL (установить значения всех семафоров набора равными значениям элементов массива). Предусмотрены действия, позволяющие выяснить идентификатор процесса, выполнившего последнюю операцию над семафором (GETPID), а также число процессов, ожидающих увеличения/обнуления (GETNCNT/GETZCNT) значения семафора (информация о процессах выдается в качестве результата, см. листинг 8.34).
Листинг 8.34. Примеры управляющих действий над семафорами. (html, txt)
Наконец, для семафоров, как и для очередей сообщений, определены управляющие команды IPC_STAT (получить информацию о состоянии набора семафоров), IPC_SET (переустановить характеристики), IPC_RMID (удалить набор семафоров), представленные в листинге 8.35.
Листинг 8.35. Дополнительные примеры управляющих действий над семафорами. (html, txt)
Листинг 8.35. Дополнительные примеры управляющих действий над семафорами.
В качестве примера использования семафоров рассмотрим известную задачу об обедающих философах. За круглым столом сидит несколько философов. В каждый момент времени каждый из них либо беседует, либо ест. Для еды одновременно требуется две вилки. Поэтому, прежде чем в очередной раз перейти от беседы к приему пищи, философу надо дождаться, пока освободятся обе вилки - слева и справа от него, и взять их в руки. Немного поев, философ кладет вилки на стол и вновь присоединяется к беседе. Требуется разработать программную модель обеда философов. Главное в этой задаче - корректная дисциплина захвата и освобождения вилок. В самом деле, если, например, каждый из философов одновременно с другими возьмется за вилку, лежащую слева от него, и будет ждать освобождения правой, обед не завершится никогда.
Предлагаемое решение состоит из двух программ. Первая (см. листинг 8.36) реализует процесс-монитор, который порождает набор семафоров (по одному семафору на каждую вилку), устанавливает начальные значения семафоров (занятой вилке будет соответствовать значение 0, свободной - 1), запускает несколько процессов, представляющих философов, указывая место за столом (в качестве одного из аргументов передается число от 1 до QPH), ожидает, пока все процессы завершатся (все философы съедят свой обед), и удаляет набор семафоров. Предполагается (для нужд функции ftok()), что исходный текст программы находится в файле phdin.c (точнее, что такой файл существует).
#include
/* Программа-монитор обеда философов */
#define QPH 5
#define ARG_SIZE 20
int main (void) { int key; /* Ключ набора семафоров */ int semid; /* Идентификатор набора семафоров */ int no; /* Номер философа и/или вилки */ char ssemid [ARG_SIZE], sno [ARG_SIZE], sqph [ARG_SIZE];
/* Создание и инициализация набора семафоров */ /* (по семафору на вилку) */ key = ftok ("phdin.c", 'C'); if ((semid = semget (key, QPH, 0600 | IPC_CREAT)) < 0) { perror ("SEMGET"); return (1); } for (no = 0; no < QPH; no++) { if (semctl (semid, no, SETVAL, 1) < 0) { perror ("SETVAL"); return (2); } }
sprintf (ssemid, "%d", semid); sprintf (sqph, "%d", QPH);
/* Все - к столу */ for (no = 1; no <= QPH; no++) { switch (fork ()) { case -1: perror ("FORK"); return (3); case 0: sprintf (sno, "%d", no); execl ("./phil", "phil", ssemid, sqph, sno, (char *) 0); perror ("EXEC"); return (4); } }
/* Ожидание завершения обеда */ for (no = 1; no <= QPH; no++) { (void) wait (NULL); }
/* Удаление набора семафоров */ if (semctl (semid, 0, IPC_RMID) < 0) { perror ("SEMCTL"); return (5); }
return 0; }
Листинг 8.36. Процесс-монитор для обеда философов.
Вторая программа (см. листинг 8.37) описывает обед каждого философа. Философ какое-то время беседует (случайное значение trnd), затем пытается взять вилки слева и справа от себя, когда ему это удается, некоторое время ест (случайное значение ernd), после чего освобождает вилки. Так продолжается до тех пор, пока не будет съеден весь обед. Предполагается, что выполнимый файл программы называется phil.
#include
/* Процесс обеда одного философа */
#define ernd (rand () % 3 + 1) #define trnd (rand () % 5 + 1) #define FO 15
int main (int argc, char *argv []) { int semid; /* Идентификатор набора семафоров */ int qph; /* Число философов */ int no; /* Номер философа */ int t; /* Время очередного отрезка еды или беседы */ int fo; /* Время до конца обеда */ struct sembuf sembuf [2];
if (argc != 4) { fprintf (stderr, "Использование: %s идентификатор_набора_семафоров число_философов номер_философа \n", argv [0]); return (1); }
fo = FO; sscanf (argv [1], "%d", &semid); sscanf (argv [2], "%d", &qph); sscanf (argv [3], "%d", &no);
/* Выбор вилок */ sembuf [0].sem_num = no - 1; /* Левая */ sembuf [0].sem_flg = 0; sembuf [1].sem_num = no % qph; /* Правая */ sembuf [1].sem_flg = 0;
while (fo > 0) { /* Обед */
/* Философ говорит */ printf ("Философ %d беседует\n", no); t = trnd; sleep (t); fo -= t; /* Пытается взять вилки */ sembuf [0].sem_op = -1; sembuf [1].sem_op = -1; if (semop (semid, sembuf, 2) < 0) { perror ("SEMOP"); return (1); }
/* Ест */ printf ("Философ %d ест\n", no); t = ernd; sleep (t); fo -= t; /* Отдает вилки */ sembuf [0].sem_op = 1; sembuf [1].sem_op = 1; if (semop (semid, sembuf, 2) < 0) { perror ("SEMOP"); return (2); } }
printf ("Философ %d закончил обед\n", no); return 0; }
Листинг 8.37. Программа, описывающая обед одного философа.
Отметим, что возможность выполнения групповых операций над семафорами предельно упростила решение, сделав его прямолинейным, по большому счету нечестным, но зато очевидным образом гарантирующим отсутствие тупиков.
В листинге 8.38 приведен второй вариант решения задачи, предложенный С.В. Самборским. В нем реализованы четыре стратегии захвата вилок, которые сравниваются по результатам моделирования поведения философов в течение нескольких минут. Все стратегии гарантируют отсутствие тупиков, но только две из них, соответствующие опциям -a и -p, заведомо не позволят ни одному философу умереть от голода из-за невозможности получить обе вилки сразу. (Это свойство "стратегий -a и -p" является следствием упорядоченности ресурсов.)
/* Обедающие философы. Запуск: mudrecProc [-a | -p | -I -V] [-t число_секунд] имя_философа ... Опции: -t число_секунд - сколько секунд моделируется Стратегии захвата вилок: -a - сначала захватывается вилка с меньшим номером; -I - некорректная (но эффективная) интеллигентная стратегия: во время ожидания уже захваченная вилка кладется; -p - сначала захватывается нечетная вилка; -V - использован групповой захват семафоров. Пример запуска: mudrecProc -p -t 600 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z */ static char rcsid[] __attribute__((unused)) = \ "$Id: mudrecProc.c,v 1.7 2003/11/11 13:14:07 sambor Exp $";
#include
#include
union semun { int val; struct semid_ds *buf; unsigned short *array; } arg;
#define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)>(b)?(b):(a))
struct mudrec { long num; char *name; int left_fork, right_fork; int eat_time, wait_time, think_time, max_wait_time; int count; };
int Stop; /* Семафор для синхронизации выхода */
/* Различные дескрипторы */ int protokol [2] = {-1, -1}; #define pFdIn (protokol [1]) #define pFdOut (protokol [0])
int semFork; /* Вилки */
int from_fil; /* Очередь для возврата результатов */
/* Разные алгоритмы захвата вилок */ static void get_forks_simple (struct mudrec *this); static void get_forks_parity (struct mudrec *this); static void get_forks_maybe_infinit_time (struct mudrec *this); static void get_forks_use_groups (struct mudrec *this);
/* Используемый метод захвата вилок */ void (*get_forks) (struct mudrec *this) = get_forks_simple;
/* Возвращение вилок */ static void put_forks (struct mudrec *this);
/* * Философы */ void filosof (struct mudrec this) { char buffer [LINE_MAX]; int bytes;
if (fork ()) return;
srandom (getpid ()); /* Очень важно для процессов, иначе получим одно и то же! */ random (); random (); random (); random (); random (); random (); random (); random (); random (); random ();
/* Пока семафор Stop не поднят */ while (!semctl (Stop, 0, GETVAL)) { /* Пора подкрепиться */ { int wait_time, tm;
sprintf (buffer, "%s: хочет есть\n", this.name); bytes = write (pFdIn, buffer, strlen (buffer));
tm = time (0);
(*get_forks) (&this);
wait_time = time (0) - tm; /* Сколько времени получали вилки */ this.wait_time += wait_time; this.max_wait_time = max (wait_time, this.max_wait_time);
sprintf (buffer, "%s: ждал вилок %d сек\n", this.name, wait_time); bytes = write (pFdIn, buffer, strlen (buffer)); }
/* Может, обед уже закончился? */ if (semctl (Stop, 0, GETVAL)) { put_forks (&this); break; }
/* Едим */ { int eat_time = random () % 20 + 1;
sleep (eat_time);
this.eat_time += eat_time; this.count++; sprintf (buffer,"%s: ел %d сек\n", this.name, eat_time); bytes = write (pFdIn, buffer, strlen (buffer)); }
/* Отдаем вилки */ put_forks (&this);
if (semctl (Stop, 0, GETVAL)) break;
/* Размышляем */ { int think_time = random () % 10 + 1;
sleep (think_time);
this.think_time += think_time; } }
sprintf (buffer,"%s: уходит\n", this.name); bytes = write (pFdIn, buffer, strlen (buffer));
msgsnd (from_fil, &this, sizeof (this), 0); /* Отослали статистику своего обеда */
_exit (0); /* ВАЖНО (_): Нам не нужны преждевременные вызовы cleanup_ipc */ }
/* Кладем вилки одну за другой */ static void put_forks (struct mudrec *this) { struct sembuf tmp_buf;
tmp_buf.sem_flg = 0; tmp_buf.sem_op = 1; tmp_buf.sem_num = this->left_fork - 1; semop (semFork, &tmp_buf, 1);
tmp_buf.sem_flg = 0; tmp_buf.sem_op = 1; tmp_buf.sem_num = this->right_fork - 1; semop (semFork, &tmp_buf, 1); }
/* Берем вилки по очереди в порядке номеров */ static void get_forks_simple (struct mudrec *this) { struct sembuf tmp_buf;
int first = min (this->left_fork, this->right_fork); int last = max (this->left_fork, this->right_fork);
tmp_buf.sem_flg = SEM_UNDO; tmp_buf.sem_op = -1; tmp_buf.sem_num = first - 1; semop (semFork, &tmp_buf, 1);
tmp_buf.sem_flg = SEM_UNDO; tmp_buf.sem_op = -1; tmp_buf.sem_num = last - 1; semop (semFork, &tmp_buf, 1); }
/* Берем сначала нечетную вилку (если обе нечетные - то с большим номером) */ static void get_forks_parity (struct mudrec *this) { struct sembuf tmp_buf;
int left = this->left_fork, right = this->right_fork; int first = max ((left & 1) * 1000 + left, (right & 1) * 1000 + right) % 1000; int last = min ((left & 1) * 1000 + left, (right & 1) * 1000 + right) % 1000;
tmp_buf.sem_flg = SEM_UNDO; tmp_buf.sem_op = -1; tmp_buf.sem_num = first - 1; semop (semFork, &tmp_buf, 1);
tmp_buf.sem_flg = SEM_UNDO; tmp_buf.sem_op = -1; tmp_buf.sem_num = last - 1; semop (semFork, &tmp_buf, 1); }
/* Берем вилки по очереди, в произвольном порядке. * Но если вторая вилка не берется сразу, то кладем первую. * То есть философ не расходует вилочное время впустую. */ static void get_forks_maybe_infinit_time (struct mudrec *this) { struct sembuf tmp_buf;
int left = this->left_fork, right = this->right_fork;
for (;;) { tmp_buf.sem_flg = SEM_UNDO; /* Первую вилку берем с ожиданием */ tmp_buf.sem_op = -1; tmp_buf.sem_num = left - 1; semop (semFork, &tmp_buf, 1);
tmp_buf.sem_flg = SEM_UNDO | IPC_NOWAIT; /* Вторую - без ожидания */ tmp_buf.sem_op = -1; tmp_buf.sem_num = right - 1;
if (0 == semop (semFork, &tmp_buf, 1)) return; /* Успех */
tmp_buf.sem_flg = 0; /* Неуспех: возвращаем первую вилку */ tmp_buf.sem_op = 1; tmp_buf.sem_num = left - 1; semop(semFork,&tmp_buf,1);
tmp_buf.sem_flg = SEM_UNDO; /* Отдав первую, ждем вторую */ tmp_buf.sem_op = -1; tmp_buf.sem_num = right - 1; semop (semFork, &tmp_buf, 1);
tmp_buf.sem_flg = SEM_UNDO | IPC_NOWAIT; /* Берем первую вилку без ожидания */ tmp_buf.sem_op = -1; tmp_buf.sem_num = left - 1;
if (0 == semop (semFork, &tmp_buf, 1)) return; /* Успех */
tmp_buf.sem_flg = 0; /* Неуспех: отдаем вторую вилку, */ tmp_buf.sem_op = 1; /* чтобы ждать первую */ tmp_buf.sem_num = right - 1; semop (semFork, &tmp_buf, 1); } }
/* Хватаем обе вилки сразу, используя групповые операции */ static void get_forks_use_groups (struct mudrec *this) { struct sembuf tmp_buf [2];
tmp_buf[0].sem_flg = SEM_UNDO; tmp_buf[0].sem_op = -1; tmp_buf[0].sem_num = this->left_fork - 1; tmp_buf[1].sem_flg = SEM_UNDO; tmp_buf[1].sem_op = -1; tmp_buf[1].sem_num = this->right_fork - 1; semop (semFork, tmp_buf, 2); }
/* * Мелкие служебные функции. */ static void stop (int dummy) { struct sembuf tmp_buf;
tmp_buf.sem_flg = 0; tmp_buf.sem_op = 1; tmp_buf.sem_num = 0; semop (Stop, &tmp_buf, 1); }
void cleanup_ipc (void) { /* * Уничтожение семафоров. */ semctl (semFork, 1, IPC_RMID); semctl (Stop, 1, IPC_RMID);
/* То же с очередью */ msgctl (from_fil, IPC_RMID, NULL); }
static void usage (char name []) { fprintf (stderr,"Использование: %s [-a | -p | -I| -V] [-t число_секунд] имя_философа ...\n", name); exit (1); }
/* * Точка входа демонстрационной программы. */ int main (int argc, char *argv[]) { char buffer [LINE_MAX], *p; int i, n, c; int open_room_time = 300; union semun tmp_arg; int nMudr; struct sigaction sact;
while ((c = getopt (argc, argv, "apIVt:")) != -1) { switch (c) { case 'a': get_forks = get_forks_simple; break; case 'p': get_forks = get_forks_parity; break; case 'I': get_forks = get_forks_maybe_infinit_time; break; case 'V': get_forks = get_forks_use_groups; break; case 't': open_room_time = strtol (optarg, &p, 0); if (optarg [0] == 0 || *p != 0) usage (argv [0]); break; default: usage (argv [0]); } }
nMudr = argc - optind; if (nMudr < 2) usage (argv [0]); /* Меньше двух философов неинтересно ... */
/* * Создание канала для протокола обработки событий */ pipe (protokol);
/* * Создадим семафоры для охраны вилок */ semFork = semget (ftok (argv [0], 2), nMudr, IPC_CREAT | 0777); tmp_arg.val = 1; for (i=1; i <= nMudr; i++) semctl (semFork, i - 1, SETVAL, tmp_arg); /* Начальное значение 1 */
/* Прежде чем впускать философов, обеспечим окончание обеда */ Stop = semget (ftok (argv [0], 3), 1, IPC_CREAT | 0777); tmp_arg.val = 0; semctl (Stop, 0, SETVAL, tmp_arg); /* Начальное значение 0 */
/* Очередь для возврата результатов */ from_fil = msgget (ftok (argv [0], 4), IPC_CREAT | 0777);
atexit (cleanup_ipc); /* Запланировали уничтожение семафоров */ /* и других средств межпроцессного взаимодействия */
/* * Философы входят в столовую */ for (i = 0; i < nMudr; i++, optind++) { struct mudrec next;
memset (&next, 0, sizeof (next));
next.num = i + 1; /* Номер */ next.name = argv [optind]; /* Имя */
/* Указали, какими вилками пользоваться */ next.left_fork = i + 1; next.right_fork = i + 2; if (i == nMudr - 1) next.right_fork = 1; /* Последний пользуется вилкой первого */
filosof (next); }
/* Зададим реакцию на сигналы и установим будильник на конец обеда */ sact.sa_handler = stop; (void) sigemptyset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIGINT, &sact, (struct sigaction *) NULL); (void) sigaction (SIGALRM, &sact, (struct sigaction *) NULL);
alarm (open_room_time);
/* * Выдача сообщений на стандартный вывод и выход после окончания обеда. */ close (pFdIn); /* Сами должны закрыть, иначе из цикла не выйдем! */ for (;;) { n = read (pFdOut, buffer, LINE_MAX); if ((n == 0) || ((n == -1) && (errno != EINTR))) break; for (i = 0; i < n; i++) putchar (buffer [i]); } close (pFdOut);
/* Распечатали сводную информацию */ { int full_eating_time = 0; int full_waiting_time = 0; int full_thinking_time = 0; for (i = 1; i <= nMudr; i++) { struct mudrec this; /* Получили статистику обеда */ msgrcv (from_fil, &this, sizeof (this), i, 0); /* За счет i получаем */ /* строго по порядку */ full_eating_time += this.eat_time; full_waiting_time += this.wait_time; full_thinking_time += this.think_time;
if (this.count > 0) { float count = this.count; float think_time = this.think_time / count; float eat_time = this.eat_time / count; float wait_time = this.wait_time / count;
printf ("%s: ел %d раз в среднем: думал=%.1f ел=%.1f ждал=%.1f (максимум %d)\n", this.name, this.count, think_time, eat_time, wait_time, this.max_wait_time); } else printf("%s: не поел\n", this.name); } { float total_time = (full_eating_time + full_waiting_time + full_thinking_time) / (float)nMudr;
printf (" Среднее число одновременно едящих = %.3f\n Среднее число одновременно ждущих = %.3f\n", full_eating_time / total_time, full_waiting_time / total_time); } }
/* Сообщим об окончании работы */ printf ("Конец обеда\n");
return 0; }
Листинг 8.38. Второй вариант решения задачи об обедающих философах.
Получит ли в конце концов философ вилки при групповых операциях (опция -V), зависит от реализации. Может случиться так, что хотя бы одна из них в каждый момент времени будет в руках у одного из соседей.
То же верно и для "интеллигентной" стратегии (опция -I). Тем не менее, результаты моделирования показывают, что на практике две последние стратегии эффективнее в смысле минимизации времени ожидания вилок.
Отметим небольшие терминологические различия в двух приведенных вариантах решения задачи об обедающих философах. Во втором варианте явно выделены начальные и конечные моделируемые события - вход философов в столовую и выход из нее (в первом варианте они просто сидят за столом).
С методической точки зрения второй вариант интересен тем, что в нем использованы все рассмотренные нами средства межпроцессного взаимодействия - каналы, сигналы, очереди сообщений и, конечно, семафоры. (Тонкость: флаг SEM_UNDO обеспечивает корректировку значения семафора при завершении процесса.)
В листинге 8.39 приведена статистика поведения пяти философов для всех четырех стратегий при времени моделирования 100 секунд. Эти результаты говорят в пользу групповых операций над семафорами.
-a: A: ел 2 раза в среднем: думал=3.5 ел=11.5 ждал=36.5 (максимум 73) B: ел 3 раза в среднем: думал=5.7 ел=7.7 ждал=20.0 (максимум 41) C: ел 3 раза в среднем: думал=5.7 ел=11.3 ждал=17.0 (максимум 33) D: ел 3 раза в среднем: думал=1.7 ел=16.7 ждал=15.7 (максимум 19) E: ел 1 раз в среднем: думал=10.0 ел=20.0 ждал=73.0 (максимум 41) Среднее число одновременно едящих = 1.471 Среднее число одновременно ждущих = 2.980 -p: A: ел 3 раза в среднем: думал=3.7 ел=15.3 ждал=16.0 (максимум 34) B: ел 4 раза в среднем: думал=5.0 ел=13.8 ждал=8.2 (максимум 15) C: ел 3 раза в среднем: думал=6.7 ел=3.7 ждал=25.7 (максимум 27) D: ел 4 раза в среднем: думал=5.8 ел=8.5 ждал=13.8 (максимум 28) E: ел 3 раза в среднем: думал=5.3 ел=15.3 ждал=16.7 (максимум 29) Среднее число одновременно едящих = 1.761 Среднее число одновременно ждущих = 2.413
-I: A: ел 5 раз в среднем: думал=4.2 ел=9.4 ждал=6.6 (максимум 15) B: ел 3 раза в среднем: думал=6.3 ел=10.3 ждал=17.0 (максимум 31) C: ел 4 раза в среднем: думал=6.8 ел=7.0 ждал=12.2 (максимум 45) D: ел 3 раза в среднем: думал=4.3 ел=16.0 ждал=13.0 (максимум 16) E: ел 4 раза в среднем: думал=5.8 ел=8.5 ждал=10.8 (максимум 22) Среднее число одновременно едящих = 1.858 Среднее число одновременно ждущих = 2.125
-V: A: ел 5 раз в среднем: думал=5.6 ел=5.6 ждал=8.8 (максимум 17) B: ел 3 раза в среднем: думал=6.3 ел=10.3 ждал=16.7 (максимум 20) C: ел 4 раза в среднем: думал=4.8 ел=11.0 ждал=9.8 (максимум 18) D: ел 4 раза в среднем: думал=5.2 ел=12.0 ждал=8.8 (максимум 15) E: ел 4 раза в среднем: думал=5.2 ел=10.5 ждал=10.2 (максимум 20) Среднее число одновременно едящих = 1.892 Среднее число одновременно ждущих = 2.049
Листинг 8.39. Результаты моделирования поведения философов.
Сигналы
Как и каналы, сигналы являются внешне простым и весьма употребительным средством локального межпроцессного взаимодействия, но связанные с ними идеи существенно сложнее, а понятия - многочисленнее.Согласно стандарту POSIX-2001, под сигналом понимается механизм, с помощью которого процесс или поток управления уведомляют о некотором событии, произошедшем в системе, или подвергают воздействию этого события. Примерами подобных событий могут служить аппаратные исключительные ситуации и специфические действия процессов. Термин "сигнал" используется также для обозначения самого события.
Говорят, что сигнал генерируется (или посылается) для процесса (потока управления), когда происходит вызвавшее его событие (например, выявлен аппаратный сбой, отработал таймер, пользователь ввел с терминала специфическую последовательность символов, другой процесс обратился к функции kill() и т.п.). Иногда по одному событию генерируются сигналы для нескольких процессов (например, для группы процессов, ассоциированных с некоторым управляющим терминалом).
В момент генерации сигнала определяется, посылается ли он процессу или конкретному потоку управления в процессе. Сигналы, сгенерированные в результате действий, приписываемых отдельному потоку управления (таких, например, как возникновение аппаратной исключительной ситуации), посылаются этому потоку. Сигналы, генерация которых ассоциирована с идентификатором процесса или группы процессов, а также с асинхронным событием (к примеру, пользовательский ввод с терминала) посылаются процессу.
В каждом процессе определены действия, предпринимаемые в ответ на все предусмотренные системой сигналы. Говорят, что сигнал доставлен процессу, когда взято для выполнения действие, соответствующее данным процессу и сигналу. сигнал принят процессом, когда он выбран и возвращен одной из функций sigwait().
В интервале от генерации до доставки или принятия сигнал называется ждущим. Обычно он невидим для приложений, однако доставку сигнала потоку управления можно блокировать.
Если действие, ассоциированное с заблокированным сигналом, отлично от игнорирования, он будет ждать разблокирования.
У каждого потока управления есть маска сигналов, определяющая набор блокируемых сигналов. Обычно она достается в наследство от родительского потока.
С сигналом могут быть ассоциированы действия одного из трех типов.
SIG_DFL
Подразумеваемые действия, зависящие от сигнала. Они описаны в заголовочном файле
SIG_IGN
Игнорировать сигнал. Доставка сигнала не оказывает воздействия на процесс.
указатель на функцию
Обработать сигнал, выполнив при его доставке заданную функцию. После завершения функции обработки процесс возобновляет выполнение с точки прерывания. Обычно функция обработки вызывается в соответствии со следующим C-заголовком: void func (int signo); где signo - номер доставленного сигнала.
Первоначально, до входа в функцию main(), реакция на все сигналы установлена как SIG_DFL или SIG_IGN.
Функция называется асинхронно-сигнально-безопасной (АСБ), если ее можно вызывать без каких-либо ограничений при обработке сигналов. В стандарте POSIX-2001 имеется список функций, которые должны быть либо повторно входимыми, либо непрерываемыми сигналами, что превращает их в АСБ-функции. В этот список включены 117 функций, в том числе почти все из рассматриваемых нами.
Если сигнал доставляется потоку, а реакция заключается в завершении, остановке или продолжении, весь процесс должен завершиться, остановиться или продолжиться.
Перейдем к изложению возможностей по генерации сигналов. Выше была кратко рассмотрена служебная программа kill как средство терминирования процессов извне. На самом деле она посылает заданный сигнал; то же делает и одноименная функция (см. листинг 8.6).
#include
Листинг 8.6. Описание функции kill(). (html, txt)
Сигнал задается аргументом sig, значение которого может быть нулевым; в этом случае действия функции kill() сводятся к проверке допустимости значения pid (нулевой результат - признак успешного завершения kill()).
Если pid > 0, это значение трактуется как идентификатор процесса. При нулевом значении pid сигнал посылается всем процессам из той же группы, что и вызывающий. Если значение pid равно -1, адресатами являются все процессы, которым вызывающий имеет право посылать сигналы. При прочих отрицательных значениях pid сигнал посылается группе процессов, чей идентификатор равен абсолютной величине pid.
Процесс имеет право послать сигнал адресату, заданному аргументом pid, если он (процесс) имеет соответствующие привилегии или его реальный или действующий идентификатор пользователя совпадает с реальным или сохраненным ПДП-идентификатором адресата.
У служебной программы kill имеется полезная опция -l, позволяющая увидеть соответствие между номерами сигналов и их мнемоническими именами. Результат выполнения команды kill -l может выглядеть так, как показано в листинге 8.7.
Листинг 8.7. Возможный результат выполнения команды kill -l. (html, txt)
SIGABRT
Сигнал аварийного завершения процесса. Подразумеваемая реакция предусматривает, помимо аварийного завершения, создание файла с образом памяти процесса.
SIGALRM
Срабатывание будильника. Подразумеваемая реакция - аварийное завершение процесса.
SIGBUS
Ошибка системной шины как следствие обращения к неопределенной области памяти. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGCHLD
Завершение, остановка или продолжение порожденного процесса. Подразумеваемая реакция - игнорирование.
SIGCONT
Продолжение процесса, если он был остановлен. Подразумеваемая реакция - продолжение выполнения или игнорирование (если процесс не был остановлен).
SIGFPE
Некорректная арифметическая операция. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGHUP
Сигнал разъединения. Подразумеваемая реакция - аварийное завершение процесса.
SIGILL
Некорректная команда. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGINT
Сигнал прерывания, поступивший с терминала. Подразумеваемая реакция - аварийное завершение процесса.
SIGKILL
Уничтожение процесса (этот сигнал нельзя перехватить для обработки или проигнорировать). Подразумеваемая реакция - аварийное завершение процесса.
SIGPIPE
Попытка записи в канал, из которого никто не читает. Подразумеваемая реакция - аварийное завершение процесса.
SIGQUIT
Сигнал выхода, поступивший с терминала. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGSEGV
Некорректное обращение к памяти. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGSTOP
Остановка выполнения (этот сигнал нельзя перехватить для обработки или проигнорировать). Подразумеваемая реакция - остановка процесса.
SIGTERM
Сигнал терминирования. Подразумеваемая реакция - аварийное завершение процесса.
SIGTSTP
Сигнал остановки, поступивший с терминала. Подразумеваемая реакция - остановка процесса.
SIGTTIN
Попытка чтения из фонового процесса. Подразумеваемая реакция - остановка процесса.
SIGTTOU
Попытка записи из фонового процесса. Подразумеваемая реакция - остановка процесса.
SIGUSR1, SIGUSR2
Определяемые пользователем сигналы. Подразумеваемая реакция - аварийное завершение процесса.
SIGPOLL
Опрашиваемое событие. Подразумеваемая реакция - аварийное завершение процесса.
SIGPROF
Срабатывание таймера профилирования. Подразумеваемая реакция - аварийное завершение процесса.
SIGSYS
Некорректный системный вызов. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGTRAP
Попадание в точку трассировки/прерывания. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGURG
Высокоскоростное поступление данных в сокет. Подразумеваемая реакция - игнорирование.
SIGVTALRM
Срабатывание виртуального таймера. Подразумеваемая реакция - аварийное завершение процесса.
SIGXCPU
Исчерпан лимит процессорного времени. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGXFSZ
Превышено ограничение на размер файлов. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
Процесс (поток управления) может послать сигнал самому себе с помощью функции raise() (см. листинг 8.8). Для процесса вызов raise() эквивалентен kill (getpid(), sig);
#include
Листинг 8.8. Описание функции raise().
Посылка сигнала самому себе использована в функции abort() (см. листинг 8.9), вызывающей аварийное завершение процесса. (Заметим, что этого не произойдет, если функция обработки сигнала SIGABRT не возвращает управления. С другой стороны, abort() отменяет блокирование или игнорирование SIGABRT.)
#include
Листинг 8.9. Описание функции abort().
Опросить и изменить способ обработки сигналов позволяет функция sigaction() (см. листинг 8.10).
#include
Листинг 8.10. Описание функции sigaction().
Для описания способа обработки сигнала используется структура sigaction, которая должна содержать по крайней мере следующие поля:
void (*sa_handler) (int); /* Указатель на функцию обработки сигнала */ /* или один из макросов SIG_DFL или SIG_IGN */ sigset_t sa_mask; /* Дополнительный набор сигналов, блокируемых */ /* на время выполнения функции обработки */ int sa_flags; /* Флаги, влияющие на поведение сигнала */ void (*sa_sigaction) (int, siginfo_t *, void *); /* Указатель на функцию обработки сигнала */
Приложение, соответствующее стандарту, не должно одновременно использовать поля обработчиков sa_handler и sa_sigaction.
Тип sigset_t может быть целочисленным или структурным и представлять набор сигналов (см. далее).
Тип siginfo_t должен быть структурным по крайней мере со следующими полями:
int si_signo; /* Номер сигнала */ int si_errno; /* Значение переменной errno, ассоциированное с данным сигналом */ int si_code; /* Код, идентифицирующий причину сигнала */ pid_t si_pid; /* Идентификатор процесса, пославшего сигнал */ uid_t si_uid; /* Реальный идентификатор пользователя процесса, пославшего сигнал */ void *si_addr; /* Адрес, вызвавший генерацию сигнала */ int si_status; /* Статус завершения порожденного процесса */ long si_band; /* Событие, связанное с сигналом SIGPOLL */
В заголовочном файле
SI_USER
Сигнал послан функцией kill().
SI_QUEUE
Сигнал послан функцией sigqueue().
SI_TIMER
Сигнал сгенерирован в результате срабатывания таймера, установленного функцией timer_settime().
SI_ASYNCIO
Сигнал вызван завершением асинхронной операции ввода/вывода.
SI_MESGQ
Сигнал вызван приходом сообщения в пустую очередь сообщений.
Из кодов, специфичных для конкретных сигналов, мы упомянем лишь несколько, чтобы дать представление о степени детализации диагностики, предусмотренной стандартом POSIX-2001. (Из имени константы ясно, к какому сигналу она относится.)
ILL_ILLOPC
Некорректный код операции.
ILL_COPROC
Ошибка сопроцессора.
FPE_INTDIV
Целочисленное деление на нуль.
FPE_FLTOVF
Переполнение при выполнении операции вещественной арифметики.
FPE_FLTSUB
Индекс вне диапазона.
SEGV_MAPERR
Адрес не отображен на объект.
BUS_ADRALN
Некорректное выравнивание адреса.
BUS_ADRERR
Несуществующий физический адрес.
TRAP_BRKPT
Процесс достиг точки прерывания.
TRAP_TRACE
Срабатывание трассировки процесса.
CLD_EXITED
Завершение порожденного процесса.
CLD_STOPPED
Остановка порожденного процесса.
POLL_PRI
Поступили высокоприоритетные данные.
Вернемся непосредственно к описанию функции sigaction(). Если аргумент act отличен от NULL, он указывает на структуру, специфицирующую действия, которые будут ассоциированы с сигналом sig. По адресу oact (если он не NULL) возвращаются сведения о прежних действиях. Если значение act есть NULL, обработка сигнала остается неизменной; подобный вызов можно использовать для опроса способа обработки сигналов.
Следующие флаги в поле sa_flags влияют на поведение сигнала sig.
SA_NOCLDSTOP
Не генерировать сигнал SIGCHLD при остановке или продолжении порожденного процесса (значение аргумента sig должно равняться SIGCHLD).
SA_RESETHAND
При входе в функцию обработки сигнала sig установить подразумеваемую реакцию SIG_DFL и очистить флаг SA_SIGINFO (см. далее).
SA_SIGINFO
Если этот флаг не установлен и определена функция обработки сигнала sig, она вызывается с одним целочисленным аргументом - номером сигнала. Соответственно, в приложении следует использовать поле sa_handler структуры sigaction. При установленном флаге SA_SIGINFO функция обработки вызывается с двумя дополнительными аргументами, как void func (int sig, siginfo_t *info, void *context); второй аргумент указывает на данные, поясняющие причину генерации сигнала, а третий может быть преобразован к указателю на тип ucontext_t - контекст процесса, прерванного доставкой сигнала. В этом случае приложение должно использовать поле sa_sigaction и поля структуры типа siginfo_t.
В частности, если значение si_code неположительно, сигнал был сгенерирован процессом с идентификатором si_pid и реальным идентификатором пользователя si_uid.
SA_NODEFER
По умолчанию обрабатываемый сигнал добавляется к маске сигналов процесса при входе в функцию обработки; флаг SA_NODEFER предписывает не делать этого, если только sig не фигурирует явным образом в sa_mask.
Опросить и изменить способ обработки сигналов можно и на уровне командного интерпретатора, посредством специальной встроенной команды trap:
trap [действие условие ...]
Аргумент "условие" может задаваться как EXIT (завершение командного интерпретатора) или как имя доставленного сигнала (без префикса SIG). При задании аргумента "действие" минус обозначает подразумеваемую реакцию, пустая цепочка ("") - игнорирование. Если в качестве действия задана команда, то при наступлении условия она обрабатывается как eval действие.
Команда trap без аргументов выдает на стандартный вывод список команд, ассоциированных с каждым из условий. Выдача имеет формат, пригодный для восстановления способа обработки сигналов (см. листинг 8.11).
save_traps=$(trap) . . . eval "$save_traps"
Листинг 8.11. Пример сохранения и восстановления способа обработки сигналов посредством специальной встроенной команды trap.
Обеспечить выполнение утилиты logout из домашнего каталога пользователя во время завершения командного интерпретатора можно с помощью команды, показанной в листинге 8.12.
trap '$HOME/logout' EXIT
Листинг 8.12. Пример использования специальной встроенной команды trap.
При перенаправлении вывода в файл приходится считаться с возможностью возникновения ошибок, специфичных для каналов. Чтобы защитить от них процедуры начальной загрузки, в ОС Lunix применяются связки из игнорирования и последующего восстановления подразумеваемой реакции на сигнал SIGPIPE (см. листинг 8.13).
trap "" PIPE echo "$INITLOG_ARGS -n $0 -s \"$1\" -e 1" >&21 trap - PIPE
Листинг 8.13. Пример использования специальной встроенной команды trap для защиты от ошибок, специфичных для каналов.
К техническим аспектам можно отнести работу с наборами сигналов, которая выполняется посредством функций, показанных в листинге 8.14. Функции sigemptyset() и sigfillset() инициализируют набор, делая его, соответственно, пустым или "полным". Функция sigaddset() добавляет сигнал signo к набору set, sigdelset() удаляет сигнал, а sigismember() проверяет вхождение в набор. Обычно признаком завершения является нулевой результат, в случае ошибки возвращается -1. Только sigismember() выдает 1, если сигнал signo входит в набор set.
#include
Листинг 8.14. Описание функций для работы с наборами сигналов.
Функция sigprocmask() (см. листинг 8.15) предназначена для опроса и/или изменения маски сигналов процесса, определяющей набор блокируемых сигналов.
#include
Листинг 8.15. Описание функции sigprocmask().
Если аргумент set отличен от NULL, он указывает на набор, используемый для изменения текущей маски сигналов. Аргумент how определяет способ изменения; он может принимать одно из трех значений: SIG_BLOCK (результирующая маска получается при объединении текущей и заданной аргументом set), SIG_SETMASK (результирующая маска устанавливается равной set) и SIG_UNBLOCK (маска set вычитается из текущей).
По адресу oset (если он не NULL) возвращается прежняя маска. Если значение set есть NULL, набор блокируемых сигналов остается неизменным; подобный вызов можно использовать для опроса текущей маски сигналов процесса.
Если к моменту завершения sigprocmask() будут существовать ждущие неблокированные сигналы, по крайней мере один из них должен быть доставлен до возврата из sigprocmask().
Нельзя блокировать сигналы, не допускающие игнорирования.
Функция sigpending() (см. листинг 8.16) позволяет выяснить набор блокированных сигналов, ожидающих доставки вызывающему процессу (потоку управления). Дождаться появления подобного сигнала можно с помощью функции sigwait() (см. листинг 8.17).
#include
Листинг 8.16. Описание функции sigpending().
#include
Листинг 8.17. Описание функции sigwait().
Функция sigwait() выбирает ждущий сигнал из заданного набора (он должен включать только блокированные сигналы), удаляет его из системного набора ждущих сигналов и помещает его номер по адресу, заданному аргументом sig. Если в момент вызова sigwait() нужного сигнала нет, процесс (поток управления) приостанавливается до появления такового.
Отметим, что стандарт POSIX-2001 не специфицирует воздействие функции sigwait() на обработку сигналов, включенных в набор set. Чтобы дождаться доставки обрабатываемого или терминирующего процесс сигнала, можно воспользоваться функцией pause() (см. листинг 8.18).
#include
Листинг 8.18. Описание функции pause().
Функция pause() может ждать доставки сигнала неопределенно долго. Возврат из pause() осуществляется после возврата из функции обработки сигнала (результат при этом равен -1). Если прием сигнала вызывает завершение процесса, возврата из функции pause(), естественно, не происходит.
Несмотря на внешнюю простоту, использование функции pause() сопряжено с рядом тонкостей. При наивном подходе сначала проверяют некоторое условие, связанное с сигналом, и, если оно не выполнено (сигнал отсутствует), вызывают pause(). К сожалению, сигнал может быть доставлен в промежутке между проверкой и вызовом pause(), что нарушает логику работы процесса и способно привести к его зависанию. Решить подобную проблему позволяет функция sigsuspend() (см. листинг 8.19) в сочетании с рассмотренной выше функцией sigprocmask().
#include
Листинг 8.19. Описание функции sigsuspend().
Функция sigsuspend() заменяет текущую маску сигналов вызывающего процесса на набор, заданный аргументом sigmask, а затем переходит в состояние ожидания, аналогичное функции pause(). После возврата из sigsuspend() (если таковой произойдет) восстанавливается прежняя маска сигналов.
Обычно парой функций sigprocmask() и sigsuspend() обрамляют критические интервалы. Перед входом в критический интервал посредством sigprocmask() блокируют некоторые сигналы, а на выходе вызывают sigsuspend() с маской, которую возвратила sigprocmask(), восстанавливая тем самым набор блокированных сигналов и дожидаясь их доставки.
В качестве примера использования описанных выше функций работы с сигналами рассмотрим упрощенную реализацию функции abort() (см. листинг 8.20).
#include
void abort (void) { struct sigaction sact; sigset_t sset;
/* Вытолкнем буфера */ (void) fflush (NULL);
/* Снимем блокировку сигнала SIGABRT */ if ((sigemptyset (&sset) == 0) && (sigaddset (&sset, SIGABRT) == 0)) { (void) sigprocmask (SIG_UNBLOCK, &sset, (sigset_t *) NULL); }
/* Пошлем себе сигнал SIGABRT. */ /* Возможно, его перехватит функция обработки, */ /* и тогда вызывающий процесс может не завершиться */ raise (SIGABRT);
/* Установим подразумеваемую реакцию на сигнал SIGABRT */ sact.sa_handler = SIG_DFL; sigfillset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIGABRT, &sact, NULL);
/* Снова пошлем себе сигнал SIGABRT */ raise (SIGABRT);
/* Если сигнал снова не помог, попробуем еще одно средство завершения */ _exit (127); }
int main (void) { printf ("Перед вызовом abort()\n"); abort (); printf ("После вызова abort()\n"); return 0; }
Листинг 8.20. Упрощенная реализация функции abort() как пример использования функций работы с сигналами.
В качестве нюанса, характерного для работы с сигналами, отметим, что до первого обращения к raise() нельзя закрыть потоки (можно только вытолкнуть буфера), поскольку функция обработки сигнала SIGABRT, возможно, осуществляет вывод.
Еще одним примером использования механизма сигналов может служить приведенная в листинге 8.21 упрощенная реализация функции sleep(), предназначенной для "засыпания" на заданное число секунд. (Можно надеяться, что не описанные пока средства работы с временем интуитивно понятны.)
#include
/* Функция обработки сигнала SIGALRM. */ /* Она ничего не делает, но игнорировать сигнал нельзя */ static void signal_handler (int sig) { /* В демонстрационных целях распечатаем номер обрабатываемого сигнала */ printf ("Принят сигнал %d\n", sig); }
/* Функция для "засыпания" на заданное число секунд */ /* Результат равен разности между заказанной и фактической */ /* продолжительностью "сна" */ unsigned int sleep (unsigned int seconds) { time_t before, after; unsigned int slept; sigset_t set, oset; struct sigaction act, oact;
if (seconds == 0) { return 0; }
/* Установим будильник на заданное время, */ /* но перед этим блокируем сигнал SIGALRM */ /* и зададим свою функцию обработки для него */ if ((sigemptyset (&set) < 0) || (sigaddset (&set, SIGALRM) < 0) || sigprocmask (SIG_BLOCK, &set, &oset)) { return seconds; }
act.sa_handler = signal_handler; act.sa_flags = 0; act.sa_mask = oset; if (sigaction (SIGALRM, &act, &oact) < 0) { return seconds; }
before = time ((time_t *) NULL); (void) alarm (seconds);
/* Как атомарное действие восстановим старую маску сигналов */ /* (в надежде, что она не блокирует SIGALRM) */ /* и станем ждать доставки обрабатываемого сигнала */ (void) sigsuspend (&oset); /* сигнал доставлен и обработан */
after = time ((time_t *) NULL);
/* Восстановим прежний способ обработки сигнала SIGALRM */ (void) sigaction (SIGALRM, &oact, (struct sigaction *) NULL);
/* Восстановим первоначальную маску сигналов */ (void) sigprocmask (SIG_SETMASK, &oset, (sigset_t *) NULL);
return ((slept = after - before) > seconds ? 0 : (seconds - slept)); }
int main (void) { struct sigaction act;
/* В демонстрационных целях установим обработку прерывания с клавиатуры */ act.sa_handler = signal_handler; (void) sigemptyset (&act.sa_mask); act.sa_flags = 0; (void) sigaction (SIGINT, &act, (struct sigaction *) NULL);
printf ("Заснем на 10 секунд\n"); printf ("Проснулись, не доспав %d секунд\n", sleep (10)); return (0); }
Листинг 8.21. Упрощенная реализация функции sleep() как пример использования механизма сигналов.
Обратим внимание на применение функции sigsuspend(), которая реализует (неделимую) транзакцию снятия блокировки сигналов и перехода в режим ожидания. Отметим также, что по умолчанию при входе в функцию обработки к маске добавляется принятый сигнал для защиты от бесконечной рекурсии. Наконец, если происходит возврат из функции sigsuspend() (после возврата из функции обработки), то автоматически восстанавливается маска сигналов, существовавшая до вызова sigsuspend(). В данном случае в этой маске блокирован сигнал SIGALRM, и потому можно спокойно менять способ его обработки.
Вызвать "недосыпание" приведенной программы можно, послав ей сигнал SIGALRM (например, посредством команды kill -s SIGALRM идентификатор_процесса) или SIGINT (путем нажатия на клавиатуре терминала комбинации клавиш CTRL+C).
Программирование в стандарте POSIX
Основные понятия и объекты
В стандарте POSIX-2001 терминал или терминальное устройство определяется как символьный специальный файл, удовлетворяющий спецификациям общего терминального интерфейса.Наряду с физическими устройствами в стандарте рассматриваются псевдотерминалы - сущности, поддерживающие интерфейс, идентичный терминальному. Псевдотерминал состоит из двух "устройств": главного и подчиненного. Подчиненное "устройство" предоставляет процессам терминальный интерфейс, не опирающийся на прямую аппаратную поддержку. Данные, которые записываются на главное устройство, становятся входными для подчиненного и наоборот.
Обычно терминальное устройство работает в полнодуплексном режиме, когда ввод и вывод могут совмещаться во времени.
С каждым терминальным устройством ассоциирована очередь ввода, куда система помещает входные данные до того, как их прочитают прикладные процессы. На размер этой очереди (в байтах) может быть наложено ограничение {MAX_INPUT}. Поддерживается также очередь вывода, где хранятся записанные прикладными процессами, но еще не выведенные на терминал символы.
Ввод может происходить в каноническом и неканоническом режимах. Канонический режим означает построчную буферизацию ввода системой, т. е. запрос на чтение из прикладной программы будет удовлетворен лишь после того, как с клавиатуры поступит символ перевода строки или конца файла, а прочитает программа заведомо не больше одной строки, независимо от того, сколько байт она запросила. На размер строки может быть наложено ограничение {MAX_CANON}. Канонический режим подразумевает также естественную обработку системой символов забоя и уничтожения строки.
(Отметим, что в каноническом режиме приложение не обязано сразу прочитать всю буферизованную строку. Можно запросить любое количество байт (даже один), и данные не будут потеряны.)
В неканоническом режиме входные данные не подвергаются препроцессированию системой, а обработка запроса на чтение зависит от двух параметров - MIN и TIME. Запрос на чтение не будет удовлетворен, пока не поступит по крайней мере MIN байт или не истечет время задержки TIME (время задается в десятых долях секунды).
Нулевое значение TIME трактуется как бесконечная задержка.
Более точно, если MIN > 0, TIME трактуется как задержка между поступлениями байт; следовательно, отсчет времени начинается после прихода очередного байта. Если MIN = 0, TIME означает общее время обслуживания запроса на чтение. Такой подход позволяет эффективно читать во время вспышек активности ввода и не препятствует побайтному вводу.
Помимо режима, канонического или нет, на передачу данных читающему процессу оказывает влияние флаг O_NONBLOCK, устанавливаемый функциями open() или fcntl(), а на обработку входных символов - режимы ввода и локальные режимы. Подобная обработка может включать, например, эхоотображение вводимых символов.
Подвергаются обработке системой (в соответствии с режимами вывода) и выводимые прикладной программой символы (в частности, они могут буферизоваться).
Ряд управляющих символов играет специальную роль при вводе и/или выводе. Кратко опишем эти функции, не уточняя их соответствия с нажатиями на клавиатуре, поскольку оно зависит от реализации. Отметим, что за некоторыми очевидными исключениями (например, перевод строки), специальные управляющие символы не передаются читающему процессу.
INTR
Генерирует сигнал прерывания (SIGINT), посылаемый всем процессам, для которых данный терминал является управляющим.
QUIT
Генерирует сигнал выхода.
ERASE
При каноническом режиме ввода устраняет предыдущий символ, но не далее начала строки.
KILL
При каноническом режиме ввода уничтожает всю строку.
EOF
При каноническом режиме ввода при получении этого символа все буферизованные байты передаются процессу, а сам символ EOF отбрасывается. Таким образом, если буферизованных байт нет, т. е. EOF встретился в начале строки, в процесс будет передано нуль байт, что и является стандартным обозначением конца файла.
NL
Стандартный разделитель строк (перевод строки) при каноническом режиме ввода. Его нельзя изменить.
EOL
Дополнительный разделитель строк, аналогичный NL, при каноническом режиме ввода.Обычно не используется.
SUSP
Генерирует сигнал остановки.
STOP
Специальный символ как при вводе, так и при выводе, распознаваемый в случае наличия флагов IXON (управление выводом) или IXOFF (управление вводом). Обычно используется для временной приостановки вывода, когда нужно прочитать текст на экране терминала.
START
Употребляется для возобновления вывода, приостановленного с помощью символа STOP.
CR
При каноническом режиме ввода и выполнении некоторых дополнительных условий - эквивалент перевода строки.
Обычно не используется.
SUSP
Генерирует сигнал остановки.
STOP
Специальный символ как при вводе, так и при выводе, распознаваемый в случае наличия флагов IXON (управление выводом) или IXOFF (управление вводом). Обычно используется для временной приостановки вывода, когда нужно прочитать текст на экране терминала.
START
Употребляется для возобновления вывода, приостановленного с помощью символа STOP.
CR
При каноническом режиме ввода и выполнении некоторых дополнительных условий - эквивалент перевода строки.
Центральную роль в управлении терминалами играет структура termios, определенная во включаемом файле
tcflag_t c_iflag; /* Режимы ввода */ tcflag_t c_oflag; /* Режимы вывода */ tcflag_t c_cflag; /* Управляющие режимы */ tcflag_t c_lflag; /* Локальные режимы */ cc_t c_cc [NCCS]; /* Специальные управ- ляющие символы */
Типы tcflag_t, cc_t и фигурирующий далее speed_t должны определяться реализацией посредством typedef как беззнаковые целые.
Обращение к элементам массива c_cc, хранящего специальные управляющие символы, которые могут быть изменены, выполняется с помощью индексов с именами, полученными вставкой буквы V перед названием символа: VEOF, VEOL, VERASE, VINTR, VKILL, VQUIT, VSTART, VSTOP, VSUSP. Кроме того, еще два индекса, VMIN и VTIME, используются для работы со значениями MIN и TIME и могут совпадать с VEOF и VEOL, соответственно (поскольку символы EOF и EOL нужны только в каноническом режиме, а значения MIN и TIME - только в неканоническом).
Поле c_iflag структуры termios описывает основные параметры терминального ввода.
BRKINT
При разрыве соединения (когда в течение времени, превышающего длительность передачи байта, поступают нулевые биты) генерировать сигнал прерывания и сбрасывать очереди ввода/вывода.
ICRNL
Преобразовывать возврат каретки в перевод строки.
IGNBRK
Игнорировать разрыв соединения.
IGNCR
Игнорировать возврат каретки.
IGNPAR
Игнорировать символы с ошибками четности.
INLCR
Преобразовывать перевод строки в возврат каретки.
INPCK
Разрешить контроль четности.
ISTRIP
Отбрасывать старший бит, обрезая байты до семи бит.
IXOFF
Разрешить старт/стопное управление вводом.
IXON
Разрешить старт/стопное управление выводом.
PARMRK
Отмечать ошибки четности.
Поле c_oflag определяет системную обработку вывода. К числу обязательных для поддержки стандарт POSIX-2001 относит только один флаг - OPOST> (постпроцессировать вывод). В расширение XSI входят флаги, определяющие характер постпроцессирования: ONLCR (преобразовывать перевод строки в пару - перевод строки, возврат каретки), OCRNL (преобразовывать возврат каретки в перевод строки), NLDLY (выбрать задержку для перевода строки) и т.п.
Поле управляющих режимов c_cflag описывает аппаратные характеристики линии и терминала: размер символа в битах (CSIZE: от CS5 - 5 бит до CS8 - 8 бит), число стоп-бит (CSTOPB: два стоп-бита), освобождение линии при закрытии последнего файлового дескриптора, ассоциированного с терминалом (HUPCL), контроль четности (PARENB: контроль включен; PARODD: проверка на нечетность) и т.п.
Стандарт осторожен в части представления скорости передачи. Оговаривается только, что скорость хранится в структуре termios как значение типа speed_t, но не утверждается, что она представлена как часть поля c_cflag (хотя в исторически сложившихся реализациях это так). Допустимые значения скорости задаются именованными константами: от B0 - нулевой, означающей разрыв соединения, до B38400 - 38400 бит/сек.
Поле локальных режимов c_lflag структуры termios используется для управления различными характеристиками терминала. В их число входят:
ECHO
Включить эхоотображение.
ECHOE
Отображать символ забоя как тройку (возврат на шаг, пробел, возврат на шаг).
ECHOK
Отображать символ уничтожения строки (по крайней мере путем выдачи перевода строки).
ECHONL
Включить эхоотображение перевода строки.
ICANON
Канонический режим ввода.
ISIG
Разрешить сигналы. Если установлен флаг ISIG, то каждый вводимый символ сравнивается со специальными управляющими символами INTR, QUIT и SUSP.В случае совпадения выполняется ассоциированная функция.
res tty
| tty > /tmp/tty. res tty < /tmp/tty.res > /tmp/tty.res |
| Листинг 9.1. Пример использования служебной программы tty. |
| Закрыть окно |
| /dev/ttyS4 not a tty |
| Листинг 9.2. Возможный результат использования служебной программы tty. |
| Закрыть окно |
|
#include |
| Листинг 9.3. Описание функций isatty() и ttyname(). |
| Закрыть окно |
|
speed 19200 baud; rows 0; columns 0; line = 0; intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = |
| Листинг 9.4. Возможный результат команды stty -a. |
| Закрыть окно |
| saved="$(stty -g)" stty новые_характеристики . . . stty $saved |
| Листинг 9.5. Пример сохранения и восстановления характеристик терминала. |
| Закрыть окно |
| if [ -x /usr/bin/tput ]; then if [ "x`tput kbs`" != "x" ]; then stty erase `tput kbs` elif [ -x /usr/bin/wc ]; then if [ "`tput kbs | wc -c `" -gt 0 ]; then stty erase ` tput kbs` fi fi fi |
| Листинг 9.6. Пример совместного использования утилит stty и tput. |
| Закрыть окно |
|
#include |
| Листинг 9.7. Описание функций семейства tc*(). |
| Закрыть окно |
|
#include |
| Листинг 9.8. Описание функций семейства cf*(). |
| Закрыть окно |
|
#include |
| Листинг 9.9. Описание функции poll(). |
| Закрыть окно |
|
/* * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа запускает shell на псевдотерминале */ /* * * * * * * * * * * * * * * * * * * * * * * * */ #include /* Действия при завершении процесса */ static void termination (int errcode) { endwin (); exit (errcode); } /* Функция обработки сигнала SIGCHLD */ static void chldied (int dummy) { /* Просто завершимся*/ termination (34); } int main (void) { WINDOW *win1, *win2; /* win1 - окно только для рамки */ /* win2 - окно для shell */ int pty, tty; /* Дескрипторы обеих сторон псевдотерминала */ int fr; /* Результат fork'а */ unsigned char ch; /* Прочитанный символ */ struct termios pt; /* Структура для смены характеристик псевдотерминала */ struct pollfd fds [2]; /* Массив параметров для вызова poll */ char ptybuf [L_ctermid]; /* Массив для хранения имени псевдотерминала */ char *s, *t; /* Указатели для перебора компонентов имени псевдотерминала */ int w2lines, w2cols; /* Размер создаваемого окна */ int x, y; /* Координаты в окне */ struct sigaction sact; int i; initscr (); cbreak (); noecho (); win1 = newwin (LINES, COLS, 0, 0); box (win1, 0, 0); wrefresh (win1); w2lines = LINES - 2; w2cols = COLS - 4; win2 = newwin (w2lines, w2cols, 1, 2); scrollok (win2, TRUE); /* Откроем первый свободный псевдотерминал */ for (s = "pqrs"; *s; s++) { for (t = "0123456789abcdef"; *t; t++) { sprintf (ptybuf, "/dev/pty%c%c", *s, *t); if ((pty = open (ptybuf, O_RDWR)) >= 0) { goto findpty; } } } fprintf (stderr, "Не удалось найти свободный псевдотерминал\n"); termination (-1); findpty: ptybuf [5] = 't'; if ((tty = open (ptybuf, O_RDWR)) < 0) { perror ("TTY OPEN ERROR"); termination (-1); } /* Установим подходящие характеристики псевдотерминала */ if (tcgetattr (pty, &pt) < 0) { perror ("PTY TERMIOS GET ERROR"); return (1); } pt.c_iflag = 0; pt.c_oflag = ONLCR; pt.c_cflag = CS8 | HUPCL; pt.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK; pt.c_cc [VINTR] = 3; /* CTRL+C */ pt.c_cc [VEOF] = 4; /* CTRL+D */ if (tcsetattr (pty, TCSADRAIN, &pt) < 0) { perror ("PTY TERMIOS SET ERROR"); return (2); } /* То же - для стандартного ввода */ (void) tcgetattr (0, &pt); pt.c_lflag &= ~ISIG; (void) tcsetattr (0, TCSADRAIN, &pt); /* Установим обработку сигнала о завершении потомка */ sact.sa_handler = chldied; (void) sigemptyset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIGCHLD, &sact, (struct sigaction *) NULL); /* Раздвоимся на процесс чтения с клавиатуры и вывода на экран */ /* и на процесс, в рамках которого запустим shell */ if ((fr = fork ()) < 0) { perror ("FORK1 ERROR"); termination (-1); } else if (fr) { /* Это процесс, читающий с клавиатуры */ /* и выводящий на экран */ close (tty); /* Будем ждать ввода с клавиатуры или псевдотерминала */ fds [0].fd = 0; fds [0].events = POLLIN; fds [1].fd = pty; fds [1].events = POLLIN; while (1) { if (poll (fds, 2, -1) < 0) { perror ("POLL ERROR"); termination (0); } if (fds [0].revents & POLLIN) { /* Пришел символ со стандартного ввода */ read (0, &ch, 1); write (pty, &ch, 1); } if (fds [1].revents & POLLIN) { /* Пришел символ с псевдотерминала */ read (pty, &ch, 1); switch (ch) { case '\n': { /* Проинтерпретируем перевод строки */ getyx (win2, y, x); if (y == (w2lines - 1)) { wmove (win2, y, w2cols - 1); waddch (win2, (chtype) ch); } else { wmove (win2, y + 1, 0); } break; } default: { /* Символ не интерпретируется */ waddch (win2, (chtype) ch); break; } } wrefresh (win2); } } /* Просто завершимся */ termination (0); } else { /* Порожденный процесс - запустим в нем shell */ /* Закроем все файлы, кроме псевдотерминала */ for (i = 0; i < RLIMIT_NOFILE; i++) { if (i != tty) { (void) close (i); } } /* Сделаем процесс лидером сеанса */ (void) setsid (); /* Свяжем стандартные ввод, вывод и протокол с псевдотерминалом */ (void) fcntl (tty, F_DUPFD, 0); (void) fcntl (tty, F_DUPFD, 0); (void) fcntl (tty, F_DUPFD, 0); close (tty); /* Сделаем псевдотерминал управляющим */ if ((tty = open (ptybuf, O_RDWR)) < 0) { perror ("TTY OPEN ERROR"); exit (-1); } close (tty); /* Поместим в окружение параметры псевдотерминала */ { char lnbuf [20]; char clbuf [20]; sprintf (lnbuf, "LINES=%2d", w2lines); sprintf (clbuf, "COLUMNS=%2d", w2cols); putenv (lnbuf); putenv (clbuf); } if (execl ("/bin/sh", "sh", (char *) NULL) < 0) { perror ("EXECL ERROR"); exit (-1); } } return 0; } |
| Листинг 9.10. Пример программы, использующей псевдотерминалы. |
| Закрыть окно |
|
#include #include #include |
| Листинг 9.11. Описание функций семейства tc*() для работы с управляющими терминалами. |
| Закрыть окно |
|
#include |
| Листинг 9.12. Описание функции ctermid(). |
| Закрыть окно |
Служебные программы и функции для управления терминалами
Служебная программаtty
позволяет узнать имя пользовательского терминала. Более точно, она выдает на стандартный вывод имя терминала, открытого в качестве стандартного ввода.
Если для примера перенаправить стандартный ввод (см. пример 9.1), можно получить результат, показанный в пример 9.2.
tty > /tmp/tty.res tty < /tmp/tty.res > /tmp/tty.res
Листинг 9.1. Пример использования служебной программы tty. (html, txt)
/dev/ttyS4 not a tty
Листинг 9.2. Возможный результат использования служебной программы tty. (html, txt)
Узнать, ассоциирован ли открытый файловый дескриптор с терминальным устройством, а также получить имя этого устройства можно с помощью функций isatty() и ttyname() (см. пример 9.3).
#include
Листинг 9.3. Описание функций isatty() и ttyname(). (html, txt)
Если с дескриптором ассоциирован терминал, функция isatty() возвращает единицу, а ttyname() - указатель на цепочку символов (располагающуюся, быть может, в перезаписываемой каждым вызовом статической области). В противном случае возвращаются, соответственно, нуль и пустой указатель.
Каждый терминал обладает рядом характеристик, которые можно опросить и/или изменить. Для этого служит утилита stty:
stty [ -a | -g] stty характеристика ...
Будучи вызванной без опций и операндов, она выдает значения основных характеристик терминала, ассоциированного со стандартным вводом. Смысл опций таков:
-a
Выдать значение всех установленных характеристик.
-g
Выдать текущие установки в формате, который может быть использован в качестве аргумента другой команды stty.
В частности, выдача команды
stty -a
может выглядеть так, как показано в пример 9.4. Здесь присутствуют как стандартные, так и специфичные для ОС Linux характеристики (минус перед именем характеристики означает, разумеется, что соответствующий флаг не установлен). Например, для опрашиваемого терминала задан канонический режим ввода и постпроцессирование вывода, но отсутствует контроль четности.
Можно видеть также, какие символы назначены на роли специальных управляющих.
Листинг 9.4. Возможный результат команды stty -a. (html, txt)
Чтобы переустановить какую-либо характеристику, следует указать ее имя и, если нужно, новое значение. Например, после выполнения команды
stty kill '^k'
для отмены строки придется нажимать CTRL+K. Вслед за выполнением команды
stty -echo
на экране перестанут отображаться символы, вводимые пользователем, что, правда, не повлияет на выдачу результатов работы команд. Подобный режим применяется для обеспечения секретности, например, во время ввода пароля. Для восстановления эхоотображения советуем воспользоваться командой
stty echo
Сохранение и восстановление характеристик терминала можно реализовать так, как показано в пример 9.5.
saved="$(stty -g)" stty новые_характеристики . . . stty $saved
Листинг 9.5. Пример сохранения и восстановления характеристик терминала. (html, txt)
Программирование в стандарте POSIX
Опрос идентифицирующих данных хостов
Самую общую информацию о характеристиках хоста позволяют получить служебная программа unameuname [-snrvma]
и одноименная функция (см. листинг 10.1).
#include
Листинг 10.1. Описание функции uname (html, txt)
Функция uname( помещает информацию, идентифицирующую опрашиваемую систему, в структуру типа utsname, которая должна содержать по крайней мере следующие поля.
char sysname []; /* Имя реализации ОС */ char nodename []; /* Имя хоста как узла */ /* коммуникационной сети */ char release []; /* Номер выпуска ОС */ char version []; /* Номер версии ОС */ char machine []; /* Название аппаратной */ /* конфигурации */
Размер массивов не специфицируется, оговаривается только, что хранящиеся в них цепочки символов завершаются нулевым байтом.
Аналогичные данные выдает на стандартный вывод служебная программа uname, опции которой соответствуют полям структуры utsname (-m – выдать название аппаратной конфигурации, -n – имя узла, -s, -r и -v – имя реализации ОС, номера выпуска и версии, соответственно). Опция -a предписывает выдавать все; без опций выдается имя реализации.
Результат выполнения команды
uname -a
может выглядеть так, как показано в листинге 10.2. Правда, здесь фигурирует нестандартная составляющая, выдаваемая по опции -p, – тип процессора (в данном случае unknown).
Linux t94 2.4.18-3 #1 Thu Apr 18 07:37:53 EDT 2002 i686 unknown
Листинг 10.2. Возможный результат выполнения команды uname -a. (html, txt)
Функция gethostname() (см. листинг 10.3) возвращает в массиве name длины namelen имя хоста. Подходящее значение для namelen – HOST_NAME_MAX + 1 (см. следующий раздел).
#include
Листинг 10.3. Описание функции gethostname(). (html, txt)
Опрос конфигурационных параметров хоста
Значения конфигурационных параметров – важнейшая характеристика хоста, а настройка на конфигурацию целевой системы – обязательный элемент мобильного программирования приложений. Подобная настройка может выполняться статически, средствами условной компиляции, а также динамически, путем опроса значений конфигурационных параметров во время выполнения.Основная часть статической конфигурационной информации сосредоточена в упоминавшемся ранее заголовочном файле
_POSIX_VERSION
Поддерживаемая версия системного интерфейса для языка C стандарта POSIX. Для POSIX-2001 значение этой константы должно равняться 200112L. Предыдущей версии (1996 года) соответствует значение 199506L.
_POSIX2_VERSION
Поддерживаемая версия интерфейса к системным сервисам на уровне командного языка и служебных программ; для реализаций, соответствующих стандарту POSIX-2001, значение этой константы должно равняться 200112L.
В листинге 10.4 показан пример использования значения _POSIX_VERSION для условной компиляции.
#if _POSIX_VERSION >= 200112L /* Используем новую функцию, применимую к */ /* большим файлам */ off_t fpos = ftello (fp); #else /* Либо реализация поддерживает только */ /* старую версию стандарта POSIX, либо */ /* константа _POSIX_VERSION вообще не */ /* определена. */ /* Используем старую, традиционную функцию */ /* опроса текущей позиции в файле */ long fpos = ftell (fp); #endif
Листинг 10.4. Пример условной компиляции с использованием конфигурационной константы _POSIX_VERSION. (html, txt)
Следующая группа констант описывает поддерживаемые необязательные возможности стандарта POSIX-2001. Значения этих констант могут равняться -1 (необязательная возможность не поддерживается), 0 (в системе присутствуют все необходимые описания, но наличие поддержки нужно опросить во время выполнения) или быть положительными (возможность полностью поддерживается реализацией).
_POSIX_CHOWN_RESTRICTED
Функции chown() и fchown() могут вызываться для смены владельца только процессами, обладающими соответствующими привилегиями.
_POSIX_IPV6
Реализация поддерживает IPv6. Положительное значение этой константы должно равняться 200112L.
_POSIX_JOB_CONTROL
Реализация поддерживает управление заданиями.
_POSIX_NO_TRUNC
Компоненты маршрутных имен, имеющие длину более NAME_MAX, вызывают сообщения об ошибках.
_POSIX_REGEXP
Реализация поддерживает обработку регулярных выражений.
_POSIX_SAVED_IDS
Каждый процесс имеет сохраненный ПДП-идентификатор и сохраненный ПДГ-идентификатор.
_POSIX_SHELL
Реализация поддерживает стандартный командный интерпретатор.
_POSIX_VDISABLE
Значением этой константы должен быть символ, отключающий обработку соответствующего специального управляющего символа терминала, если его (символ _POSIX_VDISABLE) сделать значением элемента массива c_cc[].
_POSIX2_C_BIND
Реализация поддерживает системный интерфейс для языка C. Значение этой константы должно равняться 200112L.
_POSIX2_CHAR_TERM
Реализация поддерживает по крайней мере один тип терминалов.
_POSIX2_LOCALEDEF
Реализация поддерживает создание языково-культурных сред. Положительное значение должно равняться 200112L.
_POSIX_V6_ILP32_OFF32
Реализация предоставляет среду компиляции C-программ с 32-битными типами int, long, off_t и такими же указателями.
_POSIX_V6_ILP32_OFFBIG
Реализация предоставляет среду компиляции C-программ с 32-битными типами int, long и такими же указателями; размер значений типа off_t составляет не менее 64 бит.
_POSIX_V6_LP64_OFF64
Реализация предоставляет среду компиляции C-программ с 32-битным типом int и 64-битными указателями и типами long и off_t.
_POSIX_V6_LPBIG_OFFBIG
Реализация предоставляет среду компиляции C-программ с не менее чем 32-битными значениями типа int и не менее чем 64-битными указателями и значениями типов long и off_t.
_POSIX_ASYNC_IO
Поддерживается асинхронный ввод/вывод.
_POSIX_PRIO_IO
Поддерживается приоритетный ввод/вывод.
_POSIX_SYNC_IO
Поддерживается синхронизированный ввод/вывод.
Три константы задают номера файловых дескрипторов для стандартных ввода (STDIN_FILENO со значением 0), вывода (STDOUT_FILENO – 1) и протокола (STDERR_FILENO – 2).Их нужно не анализировать, а использовать вместо явных числовых значений.
int uname
|
#include |
| Листинг 10.1. Описание функции uname |
| Закрыть окно |
| Linux t94 2.4.18-3 #1 Thu Apr 18 07:37: 53 EDT 2002 i686 unknown |
| Листинг 10.2. Возможный результат выполнения команды uname -a. |
| Закрыть окно |
|
#include |
| Листинг 10.3. Описание функции gethostname(). |
| Закрыть окно |
| #if _POSIX_VERSION >= 200112L /* Используем новую функцию, применимую к */ /* большим файлам */ off_t fpos = ftello (fp); #else /* Либо реализация поддерживает только */ /* старую версию стандарта POSIX, либо */ /* константа _POSIX_VERSION вообще не */ /* определена. */ /* Используем старую, традиционную функцию */ /* опроса текущей позиции в файле */ long fpos = ftell (fp); #endif |
| Листинг 10.4. Пример условной компиляции с использованием конфигурационной константы _POSIX_VERSION. |
| Закрыть окно |
|
#include |
| Листинг 10.5. Описание функций sysconf(), confstr(), fpathconf() и pathconf(). |
| Закрыть окно |
| if unm=$(getconf "$@"); then if [ "$unm" = "undefined" ]; then echo Значение "$@" не определено else echo Значение "$@" равно $unm. fi else echo Ошибка при выполнении getconf fi |
| Листинг 10.6. Пример аккуратной обработки результатов утилиты getconf. |
| Закрыть окно |
|
#include |
| Листинг 10.7. Пример аккуратного опроса конфигурационной цепочки символов. |
| Закрыть окно |
Программирование в стандарте POSIX
Функции для работы с сокетами
Работа с сокетами как оконечными точками при взаимодействии процессов начинается с их (сокетов) создания посредством функции socket() (см. пример 11.19). Она возвращает открытый файловый дескриптор, который может быть использован в последующих вызовах функций, оперирующих с сокетами. В том же листинге показано описание функции socketpair(), создающей пару сокетов с установленным между ними соединением.#include
Листинг 11.19. Описание функций socket() и socketpair(). (html, txt)
Аргумент af задает адресное семейство создаваемого сокета, аргумент type - тип, аргумент protocol - конкретный протокол (0 обозначает неспецифицированный подразумеваемый протокол, пригодный для запрошенного типа). Напомним, что подходящие значения для аргументов af, type и protocol можно получить с помощью описанной ранее функции getaddrinfo().
Функция socketpair() по назначению аналогична pipe(), только организуется не безымянный канал, а пара соединенных, безымянных (не привязанных к каким-либо адресам), идентичных сокетов, открытые файловые дескрипторы которых помещаются в массив sds. Обычно она используется для адресного семейства AF_UNIX; поддержка для других семейств не гарантируется.
После того как посредством функции bind() (см. пример 11.20) создан сокет, идентифицируемый дескриптором sd, ему присваивают локальный адрес, заданный аргументом address (address_len - длина структуры sockaddr, на которую указывает address). Источником локальных адресов для сокетов может служить вышеупомянутая функция getaddrinfo().
#include
Листинг 11.20. Описание функции bind(). (html, txt)
Опросить присвоенный локальный адрес (его иногда называют именем сокета) можно с помощью функции getsockname() (см. пример 11.21): она помещает его в структуру sockaddr, на которую указывает аргумент address, а длину адреса записывает по указателю address_len.
#include
Листинг 11.21. Описание функции getsockname(). (html, txt)
Если сокет ориентирован на режим с установлением соединения (имеет тип SOCK_STREAM), то, воспользовавшись функцией listen() (см. пример 11.22), его следует пометить как готового принимать соединения ("слушающего").
#include
Листинг 11.22. Описание функции listen(). (html, txt)
Аргумент backlog сообщает операционной системе рекомендуемую длину очереди соединений, ожидающих обработки слушающим сокетом. Реализация должна поддерживать значения аргумента backlog вплоть до конфигурационной константы SOMAXCONN, определенной в
заголовочном файле
Прием соединений выполняет функция accept() (см. пример 11.23). Она выбирает первое соединение из очереди, ассоциированной с заданным дескриптором sd слушающим сокетом, создает новый сокет с теми же адресным семейством, типом и протоколом и возвращает в качестве результата его файловый дескриптор.
#include
Листинг 11.23. Описание функции accept(). (html, txt)
Если значение аргумента address отлично от пустого указателя, то в структуру sockaddr, на которую указывает address, помещается адрес сокета, пытающегося установить соединение. По указателю address_len при обращении к функции accept() должна быть задана длина переданной структуры sockaddr, а на выходе туда записывается длина адреса партнера по взаимодействию.
Если очередь соединений, ожидающих обработки слушающим сокетом, пуста и для дескриптора sd не установлен флаг O_NONBLOCK, то вызвавший функцию accept() процесс (поток управления) блокируется до появления подобного соединения.При непустой очереди функция select() сообщит о готовности дескриптора sd к чтению.
Другая сторона, т. е. потенциальный партнер по взаимодействию, инициирует соединение с помощью функции connect() (см. пример 11.24). Аргументы address и address_len стандартным образом задают адрес сокета (как правило, слушающего), с которым необходимо установить соединение.
#include
Листинг 11.24. Описание функции connect(). (html, txt)
При непустой очереди функция select() сообщит о готовности дескриптора sd к чтению.
Другая сторона, т. е. потенциальный партнер по взаимодействию, инициирует соединение с помощью функции connect() (см. пример 11.24). Аргументы address и address_len стандартным образом задают адрес сокета (как правило, слушающего), с которым необходимо установить соединение.
#include
Листинг 11.24. Описание функции connect().
Если для сокета, заданного аргументом sd (запрашивающего установление соединения), еще не выполнена привязка к локальному адресу, функция connect() сама осуществит связывание со свободным локальным адресом (правда, лишь при условии, что адресное семейство сокета отлично от AF_UNIX).
Функция connect() ограничится фиксацией адреса сокета, взаимодействующего с заданным аргументом sd, если тип сокета не требует установления соединения. В частности, для сокетов типа SOCK_DGRAM таким способом можно специфицировать адреса отправляемых (с помощью функции send()) и принимаемых (посредством обращения к функции recv()) датаграмм.
Когда в качестве аргумента address передается пустой указатель, адрес взаимодействующего сокета сбрасывается.
Попытка установить соединение блокирует вызывающий процесс на неспецифицируемый промежуток времени (в случае наличия флага O_NONBLOCK). Если по истечении этого промежутка соединение установить не удалось, вызов connect(), равно как и попытка установления соединения, завершаются неудачей. Если ожидание прерывается обрабатываемым сигналом, вызов connect() завершается неудачей (переменной errno присваивается значение EINTR), но установление соединения продолжается и будет завершено асинхронно.
Если для дескриптора sd задан флаг O_NONBLOCK, а соединение не может быть установлено немедленно, то вызов connect() завершается неудачей со значением errno, равным EINPROGRESS, но установление соединения продолжается и будет завершено асинхронно.
Последующие обращения к функции connect() с тем же сокетом, выполненные до установления соединения, завершаются неудачей (EALREADY).
Сокет оказывается в неспецифицированном состоянии, если функция connect() завершается неудачей по другим причинам. Приложения, соответствующие стандарту POSIX-2001, должны закрыть файловый дескриптор sd и создать новый сокет для продолжения попыток установить соединение.
После асинхронного установления соединения функции select() и poll() сообщат, что файловый дескриптор sd готов к записи.
Функция poll(), позволяющая мультиплексировать ввод/вывод в пределах набора файловых дескрипторов, была описана нами выше. Имеющая сходную направленность (но входящая в обязательную часть стандарта POSIX-2001) функция select() и ее недавно введенный аналог pselect() представлены в пример 11.25.
#include
Листинг 11.25. Описание функций семейства select*().
Если значение аргумента readfds (writefds, errorfds) функции pselect() отлично от NULL, оно ссылается на объект типа fd_set, который на входе специфицирует набор файловых дескрипторов, проверяемых на готовность к чтению (или к записи, или к обработке исключительных ситуаций), а на выходе указывает, какие из них успешно прошли проверку. Аргумент nfds задает границу проверяемых дескрипторов (они являются небольшими целыми числами): дескриптор подлежит проверке, если его значение находится в пределах от 0 до (nfds - 1) включительно.
Стандарт POSIX-2001 определяет тип fd_set как абстрактный. Для работы со значениями этого типа служат макросы FD_ZERO() (сделать набор пустым), FD_SET() (добавить дескриптор к набору), FD_CLR() (удалить дескриптор из набора), FD_ISSET() (проверить принадлежность дескриптора набору).
Значение именованной константы FD_SETSIZE равно максимально допустимому числу дескрипторов в наборе.
В качестве результата pselect() возвращается общее количество дескрипторов во всех трех наборах, успешно прошедших проверку.
При отсутствии готовых дескрипторов вызывающий процесс блокируется, пока таковые не появятся, или пока не истечет заданное аргументом timeout время ожидания, или пока ожидание не будет прервано сигналом. Пустой указатель в качестве значения timeout означает бесконечное ожидание, а нулевые значения полей структуры timespec - отсутствие блокировки. Реализация может накладывать ограничение на максимальное время ожидания, но оно не должно быть меньше тридцати одного дня.
Если значение аргумента sigmask отлично от пустого указателя, функция pselect() на входе заменяет маску сигналов процесса на заданную, а на выходе восстанавливает прежнюю маску.
Функция pselect() поддерживает обычные файлы, терминалы и псевдотерминалы, каналы и сокеты.
Функция select() эквивалентна pselect() со следующими оговорками.
С сокетами могут быть ассоциированы опции, влияющие на их функционирование. Значения этих опций можно опросить или изменить с помощью функций getsockopt() и setsockopt() (см. пример 11.26).
#include
Листинг 11.26. Описание функций getsockopt() и setsockopt().
Опции задаются именованными константами (аргумент option_name), которые определены в заголовочном файле
Выделим среди них наиболее употребительные и разделим на несколько групп. К первой отнесем опции с целочисленными значениями, описывающими характеристики или состояние сокета.
SO_ERROR
Статус ошибок (после опроса очищается).
SO_TYPE
Тип сокета.
Ко второй группе отнесем булевы опции, представленные целочисленными значениями (0 означает ложь).
SO_DEBUG
Сообщает, записывается ли отладочная информация о работе сокета.
SO_ACCEPTCONN
Указывает, является ли сокет слушающим.
В третью группу включены опции с целочисленными значениями, определяющими количественные характеристики объектов, ассоциированных с сокетом.
SO_SNDBUF
Размер буфера для передаваемых данных (выходного буфера).
SO_RCVBUF
Размер входного буфера.
SO_RCVLOWATM
Минимальное число байт, обрабатываемых при вводе. Подразумеваемое значение равно единице. Большие значения могут вызвать блокировку принимающего процесса до поступления в сокет необходимого объема данных.
SO_SNDLOWAT
Минимальное число байт, обрабатываемых при выводе.
В четвертую группу входят опции со структурными значениями.
SO_LINGER
Определяет, блокировать ли процесс при закрытии дескриптора sd до передачи буферизованных данных, и если блокировать, то на какой срок. Значением опции является структура linger, определенная в заголовочном файле
int l_onoff; /* Признак, включена ли опция блокирования */ /* при закрытии */
int l_linger; /* Длительность блокирования в секундах */ SO_RCVTIMEO
Длительность ожидания поступления данных при вводе. Значение - упомянутая выше структура типа timeval. Подразумевая длительность равна нулю. Если в течение специфицированного промежутка времени новых данных не поступило, операция ввода вернет число байт, меньше запрошенного, или завершится ошибкой EAGAIN.
SO_SNDTIMEO
Длительность ожидания отправки данных при выводе.
Не все из перечисленных опций могут быть переустановлены функцией setsockopt().
Исключение по понятным причинам составляют SO_ERROR, SO_TYPE, SO_ACCEPTCONN.
Аргумент level задает протокольный уровень опции. Уровню сокетов соответствует значение SOL_SOCKET, уровню TCP - IPPROTO_TCP.
Функция getpeername() (см. пример 11.27), во многом аналогичная рассмотренной выше функции getsockname(), позволяет опросить еще одну характеристику - адрес (имя) сокета, с которым установлено соединение.
#include
Листинг 11.27. Описание функции getpeername().
После привязки сокета к локальному адресу, а также установления, при необходимости, соединения и задания значений опций, можно приступать к отправке и/или приему данных через сокет. Для этого служат функции, описания которых показаны в пример 11.28.
#include
Листинг 11.28. Описание функций обмена данными через сокет.
Функция recvfrom() позволяет прочитать данные (в рассматриваемом контексте называемые также сообщением) из сокета с дескриптором sd и поместить их в буфер buffer длины length. Обычно функцию recvfrom() применяют к сокетам, ориентированным на режим без установления соединения, поскольку она выдает исходный адрес в структуре типа sockaddr.
В число возможных составляющих значения аргумента flags входят следующие флаги.
MSG_PEEK
Не удалять прочитанные данные. Следующий вызов recvfrom() или другой функции ввода снова вернет их.
MSG_WAITALL
Для сокетов типа SOCK_STREAM флаг означает, что вызывающий процесс блокируется до получения всего запрошенного объема данных (а не до прихода первого сообщения).
MSG_OOB
Запрашиваются экстренные данные. Трактовка этого понятия зависит от протокола.
Как и положено функции ввода, в результате recvfrom() возвращает количество прочитанных и помещенных в буфер данных. Для сокетов, учитывающих границы сообщений (SOCK_RAW, SOCK_DGRAM, SOCK_SEQPACKET), за одно обращение читается все сообщение; если оно не помещается в буфер, лишние байты (в отсутствие флага MSG_PEEK) отбрасываются.
Формально можно считать, что функция recv() эквивалентна recvfrom() с нулевым значением аргумента address_len. Поскольку она не позволяет узнать исходный адрес, ее обычно используют для сокетов, установивших соединение. (Можно провести и еще одну аналогию: если пренебречь флагами, функция recv() эквивалентна read().)
По сравнению с recv(), более содержательным аналогом функции recvfrom() является recvmsg(). Отличия в данном случае носят скорее синтаксический характер и по сути сводятся к способу передачи входных значений и возврата результатов: для минимизации числа аргументов функция recvmsg() использует структуру типа msghdr, которая, согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля.
void *msg_name; /* Указатель на буфер для адреса */ /* (исходного или целевого) */
socklen_t msg_namelen; /* Размер буфера для адреса */
struct iovec *msg_iov; /* Массив для разнесения/сборки сообщений */
int msg_iovlen; /* Число элементов в массиве */ /* для разнесения/сборки сообщений */
void *msg_control; /* Указатель на буфер */ /* для вспомогательных данных */
socklen_t msg_controllen; /* Размер буфера для вспомогательных данных */
int msg_flags; /* Флаги принятого сообщения */
Если для сокета, специфицированного дескриптором sd, соединение не установлено, в заданный указателем msg_name буфер длины msg_namelen помещается исходный адрес сообщения; если этого не нужно, указатель msg_name можно сделать пустым.
При установлении соединения оба поля игнорируются.
Массив msg_iov совместно с полем msg_iovlen задает набор буферов для размещения принимаемых данных. Структура типа iovec определяется в заголовочном файле
void *iov_base; /* Адрес области памяти (буфера) */
size_t iov_len; /* Размер области памяти (в байтах) */
Заданные полем msg_iov области памяти по очереди заполняются поступающими данными, пока не будут размещены все принятые данные или не заполнятся все буфера.
Трактовка вспомогательных данных (поля msg_control и msg_controllen структуры типа msghdr) в стандарте POSIX-2001 весьма туманна. Описаны лишь средства доступа к ним. Мы, однако, не будем на этом останавливаться.
В случае успешного завершения функции recvmsg() в поле msg_flags могут быть установлены следующие флаги.
MSG_EOR
Достигнута граница записи (если это понятие поддерживается протоколом).
MSG_OOB
Получены экстренные данные.
MSG_TRUNC
Пришлось урезать обычные данные.
MSG_CTRUNC
Пришлось урезать управляющие данные.
Подобно тому, как write() составляет пару read(), функцию sendto() можно считать парной по отношению к recvfrom(). Она предназначена для отправки через сокет sd сообщения, заданного аргументами message и length, по адресу dest_addr длины dest_len. Если для сокета установлено соединение, целевой адрес dest_addr игнорируется.
В число возможных составляющих значения аргумента flags входят следующие флаги.
MSG_EOR
Обозначает границу записи (если это понятие поддерживается протоколом).
MSG_OOB
Отправляются экстренные данные.
Разумеется, успешное завершение функции sendto() не гарантирует доставку сообщения. Результат, равный -1, указывает только на локально выявляемые ошибки.
Функция send() - пара для recv() со всеми следующими из этого обстоятельства эквивалентностями и аналогиями.
Для функции sendmsg() структура типа msghdr, на которую указывает аргумент message, является входной (что показывает спецификатор const).В поле msg_name задается целевой адрес. Поле msg_flags игнорируется.
Для завершения операций приема и/или отправки данных через сокет служит функция shutdown() (см. пример 11.29).
#include
Листинг 11.29. Описание функции shutdown().
Значение аргумента how показывает, что именно завершается: SHUT_RD прекращает прием, SHUT_WR - отправку, SHUT_RDWR - и то, и другое.
Опрос данных о сети
Данные о хостах как узлах сети хранятся в сетевой базе, последовательный доступ к которой обслуживается функциями sethostent(), gethostent() и endhostent() (см. пример 11.1).#include
Листинг 11.1. Описание функций последовательного доступа к сетевой базе данных о хостах - узлах сети. (html, txt)
Функция sethostent() устанавливает соединение с базой, остающееся открытым после вызова gethostent(), если значение аргумента stayopen отлично от нуля. Функция gethostent() последовательно читает элементы базы, возвращая результат в структуре типа hostent, содержащей по крайней мере следующие поля.
char *h_name; /* Официальное имя хоста */ char **h_aliases; /* Массив указателей на альтернативные */ /* имена хоста, завершаемый пустым */ /* указателем */ int h_addrtype; /* Тип адреса хоста */ int h_length; /* Длина в байтах адреса данного типа */ char **h_addr_list; /* Массив указателей на сетевые адреса */ /* хоста, завершаемый пустым указателем */
Функция endhostent() закрывает соединение с базой.
В пример 11.2 показана программа, осуществляющая последовательный просмотр сетевой базы данных о хостах - узлах сети, а в пример 11.3 приведен фрагмент ее возможной выдачи.
Листинг 11.2. Пример программы, осуществляющей последовательный доступ к сетевой базе данных о хостах - узлах сети. (html, txt)
Листинг 11.3. Фрагмент возможных результатов работы программы, осуществляющей последовательный доступ к сетевой базе данных о хостах - узлах сети. (html, txt)
К рассматриваемой базе возможен и случайный доступ по ключам - именам и адресам хостов с помощью функций gethostbyname() и gethostbyaddr(), однако они считаются устаревшими и из новой версии стандарта POSIX могут быть исключены. Вместо них предлагается использовать функции getnameinfo() и getaddrinfo() (см. пример 11.4).
#include
void freeaddrinfo (struct addrinfo *ai);
int getaddrinfo (const char *restrict nodename, const char * restrict servname, const struct addrinfo *restrict hints, struct addrinfo **restrict res);
int getnameinfo (const struct sockaddr *restrict sa, socklen_t salen, char *restrict node, socklen_t nodelen, char *restrict service, socklen_t servicelen, int flags);
Листинг 11.4. Описание функций freeaddrinfo(), getaddrinfo(), getnameinfo(). (html, txt)
Функция getaddrinfo() позволяет по имени узла сети (хоста) (аргумент nodename) и/или имени сетевого сервиса (servname) получить набор адресов сокетов и ассоциированную информацию, что дает возможность создать сокет для обращения к заданному сервису.
Если аргумент nodename отличен от пустого указателя, он способен задавать описательное имя или адресную цепочку. Для адресных семейств AF_INET и AF_UNSPEC (см. ниже описание аргумента hints) именем может служить имя хоста, а адресной цепочкой - стандартные для Internet адреса в точечных обозначениях (например, 193.232.173.17).
При пустом значении nodename подразумевается хост, локальный для вызывающего процесса.
Аргумент servname может задавать имя сервиса или (для адресных семейств AF_INET и AF_UNSPEC) десятичный номер порта. Пустое значение servname означает запрос сетевого адреса.
Аргумент hints позволяет передать дополнительную информацию об опрашиваемом сервисе - адресное семейство, тип сокета, протокол, флаги. Согласно стандарту, структура addrinfo, описанная в заголовочном файле
int ai_flags; /* Входные флаги */
int ai_family; /* Адресное семейство сокета */
int ai_socktype; /* Тип сокета */
int ai_protocol; /* Протокол сокета */
socklen_t ai_addrlen; /* Длина адреса сокета */
struct sockaddr *ai_addr; /* Адрес сокета */
char *ai_canonname; /* Официальное имя узла сети */
struct addrinfo *ai_next; /* Указатель на следующий элемент списка */
При обращении к функции getaddrinfo() все поля структуры addrinfo, на которую указывает аргумент hints, кроме первых четырех (ai_flags, ai_family, ai_socktype, ai_protocol), должны быть нулевыми или равными NULL.
Значение AF_UNSPEC в поле ai_family подразумевает, что вызывающего устроит любое адресное семейство. Аналогичный смысл имеют нулевые значения полей ai_socktype и ai_protocol. При hints, равном NULL, подразумевается AF_UNSPEC для ai_family и нулевые значения для других полей.
Из флагов, которые могут быть установлены в поле ai_flags, упомянем следующие.
AI_PASSIVE
Если значение аргумента nodename равно NULL, этот флаг игнорируется. В противном случае, если он указан, будет возвращен адрес сокета, предназначенного для принятия входящих соединений.
AI_CANONNAME
Данный флаг предписывает выяснить официальное имя узла сети.
AI_NUMERICHOST
Флаг означает, что хост задан адресной цепочкой, и не допускает использования какого-либо сервиса имен.
AI_NUMERICSERV
Флаг помечает, что сервис (аргумент servname) задан номером порта, и налагает запрет на обращение к какому-либо сервису имен.
AI_NUMERICSERV
Флаг помечает, что сервис ( аргумент servname) задан номером порта, и налагает запрет на обращение к какому-либо сервису имен.
Признаком успешного завершения функции getaddrinfo() является нулевой результат. В таком случае выходной аргумент res будет ссылаться на указатель на список структур типа addrinfo (связанных полем ai_next) - принадлежащие им значения полей ai_family, ai_socktype, ai_protocol пригодны для создания подходящих сокетов с помощью функции socket(), а значения ai_addr и ai_addrlen, в зависимости от флага AI_PASSIVE, могут служить аргументами функций connect() или bind(), применяемых к созданному сокету.
Функция freeaddrinfo() позволяет освободить память, занятую списком структур типа addrinfo и ассоциированными данными.
Функцию getnameinfo() можно считать обратной по отношению к getaddrinfo(). Аргумент sa - входной, он задает транслируемый адрес сокета (salen - размер структуры sockaddr). Аргументы node и service - выходные, задающие адреса областей памяти, куда помещаются, соответственно, имя узла и сервиса; размеры этих областей ограничены значениями nodelen и servicelen.
По умолчанию предполагается, что сокет имеет тип SOCK_STREAM, а кроме того, возвращается полное доменное имя хоста. Аргумент flags позволяет изменить подразумеваемое поведение. Если задан флаг NI_DGRAM, сокет считается датаграммным. При установленном флаге NI_NOFQDN возвращается короткое имя узла. Флаги NI_NUMERICHOST и NI_NUMERICSERV предписывают возвращать числовые цепочки для адресов хоста и сервиса, соответственно.
Обратим внимание на несколько технических деталей. При работе с адресами сокетов вместо родовой структуры типа sockaddr обычно используются более специализированные (описанные в заголовочном файле
sa_family_t sin_family; /* AF_INET */ in_port_t sin_port; /* Номер порта */ struct in_addr sin_addr; /* IP-адрес */
Структура типа sockaddr_in6 устроена несколько сложнее; мы не будем ее рассматривать.
Структуры типов sockaddr и sockaddr_in (или sockaddr_in6) мысленно накладываются друг на друга, а преобразование типов между ними выполняется по мере необходимости. Отметим также, что тип in_port_t эквивалентен uint16_t, структура типа in_addr содержит по крайней мере одно поле:
in_addr_t s_addr; тип in_addr_t эквивалентен uint32_t.
Техническую роль играют и функции преобразования IP-адресов из текстового представления в числовое и наоборот (см. пример 11.5).
#include
Листинг 11.5. Описание функций преобразования IP-адресов из текстового представления в числовое и наоборот.
Первые две функции манипулируют только адресами IPv4: inet_addr() преобразует текстовую цепочку cp (адрес в стандартных точечных обозначениях) в пригодное для использования в качестве IP-адреса целочисленное значение, inet_ntoa() выполняет обратное преобразование.
Вторая пара функций по сути аналогична первой, но имеет чуть более общий характер, так как способна преобразовывать адреса в формате IPv6. Первый аргумент этих функций, af, задает адресное семейство: AF_INET для IPv4 и AF_INET6 для IPv6. Буфер, на который указывает аргумент dst функции inet_pton() (в него помещается результат преобразования - IP-адрес в числовой двоичной форме с сетевым порядком байт), должен иметь длину не менее 32 бит для адресов IPv4 и 128 бит для IPv6.
Аргумент src функции inet_ntop(), возвращающей текстовое представление, указывает на буфер с IP-адресом в числовой форме с сетевым порядком байт. Аргумент size задает длину выходного буфера, на него указывает аргумент dst. Подходящими значениями, в зависимости от адресного семейства, могут служить INET_ADDRSTRLEN или INET6_ADDRSTRLEN.
Выше было отмечено, что преобразование значений типов uint16_t и uint32_t из хостового порядка байт в сетевой выполняется посредством функций htons() и htonl(); функции ntohs() и ntohl() осуществляют обратную операцию (см. пример 11.6).
#include
Листинг 11.6. Описание функций преобразования целочисленных значений из хостового порядка байт в сетевой и наоборот.
Использование функции getaddrinfo() вместе с сопутствующими техническими деталями проиллюстрируем программой, показанной в пример 11.7. Возможные результаты ее выполнения приведены в пример 11.8.
#include
int main (void) { struct addrinfo hints = {AI_CANONNAME, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; struct addrinfo *addr_res;
if (getaddrinfo ("www", "http", &hints, &addr_res) != 0) { perror ("GETADDRINFO"); } else { printf ("Результаты для сервиса http\n"); /* Пройдем по списку возвращенных структур */ do { printf ("Адрес сокета: Порт: %d IP-адрес: %s\n", ntohs (((struct sockaddr_in *) addr_res->ai_addr)->sin_port), inet_ntoa (((struct sockaddr_in *) addr_res->ai_addr)->sin_addr)); printf ("Официальное имя хоста: %s\n", addr_res->ai_canonname); } while ((addr_res = addr_res->ai_next) != NULL); }
return 0; }
Листинг 11.7. Пример программы, использующей функцию getaddrinfo().
Результаты для сервиса http Адрес сокета: Порт: 80 IP-адрес: 193.232.173.1 Официальное имя хоста: t01
Листинг 11.8. Возможный результат работы программы, использующей функцию getaddrinfo().
Завершая изложение серии технических моментов, укажем, что полезным дополнением к функциям getaddrinfo() и getnameinfo() является функция gai_strerror() (см. пример 11.9). Она возвращает текстовую цепочку, расшифровывающую коды ошибок, перечисленные в заголовочном файле
Стандарт POSIX- 2001 специфицирует следующие коды ошибок, имена которых говорят сами за себя: EAI_AGAIN, EAI_BADFLAGS, EAI_FAIL, EAI_FAMILY, EAI_MEMORY, EAI_NONAME, EAI_OVERFLOW, EAI_SERVICE, EAI_SOCKTYPE, EAI_SYSTEM.
#include
Листинг 11.9. Описание функции gai_strerror().
Если немного модифицировать приведенную выше программу (в пример 11.10 показан измененный фрагмент, где имя сервиса - "HTTP" - задано большими буквами), то в стандартный протокол с помощью функции gai_strerror() будет выдано содержательное диагностическое сообщение (см. пример 11.11). Отметим, что выдача функции perror() в данном случае невразумительна.
. . . int res;
if ((res = getaddrinfo ("www", "HTTP", &hints, &addr_res)) != 0) { perror ("GETADDRINFO"); fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); } else { printf ("Результаты для сервиса HTTP\n"); . . .
Листинг 11.10. Модифицированный фрагмент программы, использующей функции getaddrinfo() и gai_strerror().
GETADDRINFO: No such file or directory GETADDRINFO: Servname not supported for ai_socktype
Листинг 11.11. Диагностические сообщения от функций perror() и gai_strerror(), выданные по результатам работы функции getaddrinfo().
Наряду с базой данных хостов (узлов сети) поддерживается база данных сетей с аналогичной логикой работы и набором функций (см. пример 11.12).
#include
Листинг 11.12. Описание функций доступа к базе данных сетей.
Функция getnetent() обслуживает последовательный доступ к базе, getnetbyaddr() осуществляет поиск по адресному семейству (аргумент type) и номеру net сети, а getnetbyname() выбирает сеть с заданным (официальным) именем. Структура типа netent, указатель на которую возвращается в качестве результата этих функций, согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля.
char *n_name; /* Официальное имя сети */
char **n_aliases; /* Массив указателей на альтернативные */ /* имена сети, завершаемый пустым указателем */
int n_addrtype; /* Адресное семейство (тип адресов) сети */
uint32_t n_net; /* Номер сети (в хостовом порядке байт) */
Точно такой же программный интерфейс предоставляет база данных сетевых протоколов (см. пример 11.13).
#include
Листинг 11.13. Описание функций доступа к базе данных сетевых протоколов.
Структура типа protoent содержит по крайней мере следующие поля.
char *p_name; /* Официальное имя протокола */
char **p_aliases; /* Массив указателей на альтернативные */ /* имена протокола, завершаемый пустым */ /* указателем */
int p_proto; /* Номер протокола */
В пример 11.14 показан пример программы, осуществляющей последовательный и случайный доступ к базе данных сетевых протоколов. пример 11.15 содержит фрагмент возможных результатов работы этой программы.
#include
int main (void) { struct protoent *pht; char *pct; int i;
setprotoent (1);
while ((pht = getprotoent ()) != NULL) { printf ("Официальное имя протокола: %s\n", pht->p_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->p_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Номер протокола: %d\n\n", pht->p_proto); }
if ((pht = getprotobyname ("ipv6")) != NULL) { printf ("Номер протокола ipv6: %d\n\n", pht->p_proto); } else { fprintf (stderr, "Протокол ip в базе не найден\n"); }
if ((pht = getprotobyname ("IPV6")) != NULL) { printf ("Номер протокола IPV6: %d\n\n", pht->p_proto); } else { fprintf (stderr, "Протокол IPV6 в базе не найден\n"); }
endprotoent ();
return 0; }
Листинг 11.14. Пример программы, осуществляющей последовательный и случайный доступ к базе данных сетевых протоколов.
Официальное имя протокола: ip Альтернативные имена: IP Номер протокола: 0
Официальное имя протокола: icmp Альтернативные имена: ICMP Номер протокола: 1
. . .
Официальное имя протокола: tcp Альтернативные имена: TCP Номер протокола: 6
. . .
Официальное имя протокола: udp Альтернативные имена: UDP Номер протокола: 17
. . .
Официальное имя протокола: ipv6 Альтернативные имена: IPv6 Номер протокола: 41
. . .
Официальное имя протокола: ipv6-crypt Альтернативные имена: IPv6-Crypt Номер протокола: 50
. . .
Официальное имя протокола: visa Альтернативные имена: VISA Номер протокола: 70
. . .
Официальное имя протокола: iso-ip Альтернативные имена: ISO-IP Номер протокола: 80
. . .
Официальное имя протокола: sprite-rpc Альтернативные имена: Sprite-RPC Номер протокола: 90
. . .
Официальное имя протокола: ipx-in-ip Альтернативные имена: IPX-in-IP Номер протокола: 111
. . .
Официальное имя протокола: fc Альтернативные имена: FC Номер протокола: 133
Номер протокола ipv6: 41
Протокол IPV6 в базе не найден
Листинг 11.15. Фрагмент возможных результатов работы программы, осуществляющей последовательный и случайный доступ к базе данных сетевых протоколов.
Еще одно проявление той же логики работы - база данных сетевых сервисов (см. пример 11.16).
#include
Листинг 11.16. Описание функций доступа к базе данных сетевых сервисов.
Обратим внимание на то, что в данном случае можно указывать второй аргумент поиска - имя протокола. Впрочем, значение аргумента proto может быть пустым указателем, и тогда поиск производится только по имени сервиса (функция getservbyname()) или номеру порта ( getservbyport()), который должен быть задан с сетевым порядком байт.
Структура типа servent содержит по крайней мере следующие поля.
char *s_name; /* Официальное имя сервиса */
char **s_aliases; /* Массив указателей на альтернативные */ /* имена сервиса, завершаемый пустым */ /* указателем */
int s_port; /* Номер порта, соответствующий сервису */ /* (в сетевом порядке байт) */
char *s_proto; /* Имя протокола для взаимодействия с */ /* сервисом */
В пример 11.17 приведен пример программы, использующей функции доступа к базе данных сервисов, а также функции преобразования между хостовым и сетевым порядками байт. В пример 11.18 показан фрагмент возможных результатов работы этой программы.
#include
int main (void) { struct servent *pht; char *pct; int i;
setservent (1);
while ((pht = getservent ()) != NULL) { printf ("Официальное имя сервиса: %s\n", pht->s_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->s_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Номер порта: %d\n", ntohs ((in_port_t) pht->s_port)); printf ("Имя протокола: %s\n\n", pht->s_proto); }
if ((pht = getservbyport (htons ((in_port_t) 21), "udp")) != NULL) { printf ("Официальное имя сервиса: %s\n", pht->s_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->s_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Номер порта: %d\n", ntohs ((in_port_t) pht->s_port)); printf ("Имя протокола: %s\n\n", pht->s_proto); } else { perror ("GETSERVBYPORT"); }
if ((pht = getservbyport (htons ((in_port_t) 21), (char *) NULL)) != NULL) { printf ("Официальное имя сервиса: %s\n", pht->s_name); printf ("Альтернативные имена:\n"); for (i = 0; (pct = pht->s_aliases [i]) != NULL; i++) { printf (" %s\n", pct); } printf ("Номер порта: %d\n", ntohs ((in_port_t) pht->s_port)); printf ("Имя протокола: %s\n\n", pht->s_proto); } else { perror ("GETSERVBYPORT"); }
endservent ();
return 0; }
Листинг 11.17. Пример программы, использующей функции доступа к базе данных сервисов, а также функции преобразования между хостовым и сетевым порядками байт.
. . .
Официальное имя сервиса: ftp-data Альтернативные имена: Номер порта: 20 Имя протокола: tcp
Официальное имя сервиса: ftp-data Альтернативные имена: Номер порта: 20 Имя протокола: udp
Официальное имя сервиса: ftp Альтернативные имена: Номер порта: 21 Имя протокола: tcp
Официальное имя сервиса: ftp
Альтернативные имена: fsp fspd Номер порта: 21 Имя протокола: udp
. . .
Официальное имя сервиса: kerberos Альтернативные имена: kerberos5 krb5 Номер порта: 88 Имя протокола: tcp
Официальное имя сервиса: kerberos Альтернативные имена: kerberos5 krb5 Номер порта: 88 Имя протокола: udp
. . .
Официальное имя сервиса: auth Альтернативные имена: authentication tap ident Номер порта: 113 Имя протокола: tcp
Официальное имя сервиса: auth Альтернативные имена: authentication tap ident Номер порта: 113 Имя протокола: udp
. . .
Официальное имя сервиса: printer Альтернативные имена: spooler Номер порта: 515 Имя протокола: tcp
Официальное имя сервиса: printer Альтернативные имена: spooler Номер порта: 515 Имя протокола: udp
. . .
Официальное имя сервиса: fido Альтернативные имена: Номер порта: 60179 Имя протокола: tcp
Официальное имя сервиса: fido Альтернативные имена: Номер порта: 60179 Имя протокола: udp
Официальное имя сервиса: ftp Альтернативные имена: fsp fspd Номер порта: 21 Имя протокола: udp
Официальное имя сервиса: ftp Альтернативные имена: Номер порта: 21 Имя протокола: tcp
Листинг 11.18. Фрагмент возможных результатов работы программы, использующей функции доступа к базе данных сервисов, а также функции преобразования между хостовым и сетевым порядками байт.
Отметим, что при поиске по ключу возвращается первый подходящий элемент базы данных. По этой причине, когда не был задан протокол (второе обращение к функции getservbyport()), в качестве результата был возвращен элемент с протоколом tcp.
Основные понятия и объекты
Стандарт POSIX-2001 определяет сеть как совокупность взаимосвязанных хостов. Тем самым предполагается, что сетевая инфраструктура остается скрытой от приложений, которым предоставляются высокоуровневые средства взаимодействия в распределенной среде.Под сетевым адресом понимается видимый в пределах сети идентификатор, используемый для обозначения оконечных точек сети. Адреса есть у определенных оконечных точек хостов, могут они быть и у хостов в целом.
Данные о хостах как узлах сети хранятся в сетевой базе, допускающей и последовательный, и случайный доступ с возможностью поиска по именам и адресам хостов.
Процесс присвоения сетевого адреса оконечной точке называется связыванием, или привязкой, а обратное действие - освобождением, или отменой привязки.
Обычно оконечной точкой служит аппаратный сетевой интерфейс, посредством которого данные передаются и принимаются, однако с таким интерфейсом, как шлейфовый (loopback), никакой аппаратуры не ассоциировано.
Поддерживается база данных маршрутизации, используемая при выборе сетевого интерфейса для передачи порции данных (сетевого пакета).
Данные передаются по сети в виде последовательности октетов (восьмибитных беззнаковых величин). Если некоторый элемент данных (например, адрес или номер порта) состоит более чем из восьми бит, для его передачи и хранения требуется несколько октетов. Сетевым называется порядок октетов (байт), при котором первый (с наименьшим адресом) октет содержит старшие (наиболее значимые) биты.
Последовательности октетов - неудобный объект обработки на хостах, где предпочтительнее аппаратно поддерживаемые типы, в особенности целочисленные. Значения этих типов обычно хранятся с другим порядком байт, называемым хостовым, поэтому вполне возможно, что старшего бита не окажется в первом байте и вообще будет использоваться некое неочевидное распределение бит по байтам.
Для преобразования значений типов uint16_t и uint32_t из хостового порядка байт в сетевой служат функции htons() и htonl(); функции ntohs() и ntohl() осуществляют обратную операцию.
При взаимодействии процессов оконечными точками служат сокеты, они трактуются стандартом POSIX-2001 как отдельный тип файлов.
Под адресом сокета как (удаленной) оконечной точки понимается структура, включающая идентификатор адресного семейства и адресную информацию, специфичную для данного семейства. Последняя может состоять из нескольких компонентов, в том числе сетевого адреса хоста и идентификатора конкретной оконечной точки.
Основные описания, относящиеся к сокетам, сосредоточены в заголовочном файле
sa_family_t sa_family; /* Адресное семейство */ char sa_data []; /* Адрес сокета (данные переменной длины) */
Адресное семейство соответствует конкретной среде взаимодействия. Стандарт POSIX-2001 определяет три таких семейства.
AF_UNIX
Адресное семейство UNIX поддерживает межпроцессное взаимодействие в пределах одной системы. Формально это можно считать вырожденным случаем сетевого взаимодействия. Описания, специфичные для данного семейства, содержатся в заголовочном файле
AF_INET
Адресное семейство, поддерживающее взаимодействие по протоколам IPv4. Специфичные для него описания располагаются в заголовочном файле
AF_INET6
Взаимодействие по протоколам IPv6 (необязательная возможность). За счет поддержки адресов IPv6, отображенных на IPv4, обеспечивается совместимость с приложениями, использующими IPv4. Применяемые эти адресным семейством описания распределены по заголовочным файлам
В пределах каждого адресного семейства могут существовать сокеты нескольких типов. В стандарте POSIX-2001 их четыре.
SOCK_STREAM
Сокеты данного типа поддерживают надежные, упорядоченные, полнодуплексные потоки октетов в режиме с установлением соединения.
SOCK_SEQPACKET
Аналог SOCK_STREAM с дополнительным сохранением границ между записями.
При взаимодействии процессов оконечными точками служат сокеты, они трактуются стандартом POSIX-2001 как отдельный тип файлов.
Под адресом сокета как (удаленной) оконечной точки понимается структура, включающая идентификатор адресного семейства и адресную информацию, специфичную для данного семейства. Последняя может состоять из нескольких компонентов, в том числе сетевого адреса хоста и идентификатора конкретной оконечной точки.
Основные описания, относящиеся к сокетам, сосредоточены в заголовочном файле
sa_family_t sa_family; /* Адресное семейство */ char sa_data []; /* Адрес сокета (данные переменной длины) */
Адресное семейство соответствует конкретной среде взаимодействия. Стандарт POSIX-2001 определяет три таких семейства.
AF_UNIX
Адресное семейство UNIX поддерживает межпроцессное взаимодействие в пределах одной системы. Формально это можно считать вырожденным случаем сетевого взаимодействия. Описания, специфичные для данного семейства, содержатся в заголовочном файле
AF_INET
Адресное семейство, поддерживающее взаимодействие по протоколам IPv4. Специфичные для него описания располагаются в заголовочном файле
AF_INET6
Взаимодействие по протоколам IPv6 (необязательная возможность). За счет поддержки адресов IPv6, отображенных на IPv4, обеспечивается совместимость с приложениями, использующими IPv4. Применяемые эти адресным семейством описания распределены по заголовочным файлам