Журнал "Открытые Системы", #09-10/1999
Сергей Романюк
В ответ на вопрос, что такое POSIX, довольно часто можно услышать, что это стандарт на операционную систему, а некоторые при этом добавят "класса UNIX". Ответ неправильный. Об этом и других "открытиях", сделанных в процессе перевода стандарта ISO/IEC 9945-1 [1] на русский язык, а также о некоторых ассоциациях, возникших у автора при чтении оригинального текста, говорится в предлагаемой статье.
Среди практикующих программистов бытует мнение, что стандарты в программировании вообще не нужны, поскольку:
Может быть, на это мнение не следовало бы обращать внимания, если бы не два обстоятельства:
Слово "стандарт" ассоциируется обычно с чем-то материальным (стандартные габариты, стандартное электрическое напряжение и т.д.), в то время как компьютерная программа - объект нематериальный ("The new intangible"), и может быть, стандарты в нематериальной сфере действительно бессмысленны?
Существует, однако, опровергающий пример. Совокупность правил орфографии русского языка по существу представляет собой стандарт, хотя и не утвержденный органами стандартизации. Далее, кроме правил (или, если угодно, требований) орфографии, существуют синтаксические правила и, самое главное, семантика. Последнее хорошо иллюстрирует "детский" вопрос: почему кошку называют кошкой? На этот вопрос существует точный ответ: потому, что наши предки так договорились; предки англичан договорились называть этого же зверя cat, предки немцев - kitten, и т.д. И вообще, смысл, или семантика, или правила интерпретации любого слова или сочетания слов - вопрос договоренности.
Как следует из названия, POSIX (Portable Operating System Interface) - это стандарт на сопряжение (интерфейс) между операционной системой и прикладной программой. Времена, когда программисты писали программы для "голой" машины (реализуя собственные пакеты программ ввода/вывода, тригонометрических функций и т.п.), прошли безвозвратно. В тексте POSIX неоднократно подчеркивается, что стандарт не выдвигает никаких требований к деталям реализации операционной системы; его можно рассматривать как совокупность договоренностей между прикладными программистами и разработчиками операционных систем. Таким образом (опять же вопреки довольно распространенному мнению), POSIX представляет интерес не только для разработчиков операционных систем, но прежде всего для гораздо более многочисленной категории программистов - прикладных.
Потребность в стандарте такого рода была осознана еще в 1980-е годы, когда получили широкое распространение операционные системы UNIX. Оказалось, что хотя эта система и задумывалась как унифицированная, различия между конкретными ее реализациями приводили к тому, что прикладные программы, написанные для одной системы, далеко не всегда могли исполняться в другой. На решение именно этой проблемы, известной как проблема мобильности программного обеспечения, нацелен POSIX. Первая редакция стандарта была выпущена в 1988 году (имеется перевод, см. [2]), в которой все многообразие вопросов, связанных с мобильностью программ, было разбито на две части: (1) интерфейс прикладных программ, (2) командный интерпретатор и утилиты (интерфейс пользователя); эти части получили название POSIX.1 и POSIX.2 соответственно1.
Уточним, что в данной статье речь пойдет только о стандарте на интерфейс прикладных программ, POSIX.1, вторая (и последняя к настоящему времени) редакция которого была утверждена 12 июля 1996 года.
В информативной части стандарта подчеркивается также, что POSIX представляет собой не описание интерфейса некоторой "идеальной" операционной системы, а результат обобщения и систематизации опыта, накопленного при разработке операционных систем UNIX. Кроме того, POSIX не может служить руководством или учебным пособием по операционным системам, хотя в информативной части содержатся рекомендации программистам и фрагменты программ.
В стандарте прямо говорится о том, что невозможно создать полноценную операционную систему, ориентируясь исключительно на описанные в нем интерфейсные функции. (В частности, в POSIX.1 не отражены такие вопросы, как работа в сети и соответствующие интерфейсные функции или графический интерфейс.) Тем не менее, финансовые издержки, вызванные немобильностью программ и проистекающая отсюда потребность в унификации интерфейса настолько велики, что большинство производителей предпочитает иметь хоть какой-нибудь стандарт, чем не иметь никакого. По этой причине на POSIX стремятся ориентироваться очень многие разработчики программного обеспечения. Это позволяет если не ликвидировать немобильность программ полностью, то по крайней мере существенно уменьшить немобильную часть программы.
Как уже говорилось, POSIX можно рассматривать как совокупность договоренностей между разработчиком операционной системы и прикладным программистом. "Договоренность" означает прежде всего одинаковость интерпретации (семантики) слов и выражений. Далее приводятся примеры, иллюстрирующие трудность задачи достижения "договоренности".
Как передать смысл при переводе
Прежде всего, нужно напомнить, что стандарт POSIX изложен на английском языке, который по своей природе провоцирует двусмысленности (например, одно и то же слово может быть существительным, прилагательным и глаголом), и "семантические ловушки" подстерегают чуть ли не на каждой странице. Хорошей иллюстрацией сказанному служит пример из художественной литературы. Одно из самых известных произведений Оскара Уайльда, который блестяще пользовался этой особенностью английского языка, - The Importance of being Earnest - известно на русском под названием "Как важно быть серьезным". Однако английское название имеет второй смысл: Earnest (серьезный) - фамилия одного из персонажей, и название можно было бы перевести иначе: "Как важно быть Эрнстом". Есть русская фамилия Серебряный, и если бы была фамилия Серьезный, перевод был бы идеально точным, с передачей обоих смыслов.
Аналогично обстоит дело с самим названием стандарта: Portable Operating System Interface. Прилагательное Portable (мобильный) относится и к операционной системе, и к прикладной программе, но выразить это так же коротко по-русски не удается, можно перевести как "Интерфейс мобильной операционной системы" или "Интерфейс операционной системы, обеспечивающий мобильность прикладных программ". Второй вариант лучше отражает намерения разработчиков стандарта, но при этом теряется первый смыл (при переводе был сохранен более привычный первый вариант).
Семантика слова "стандарт"
Основной текст стандарта предваряется преамбулой, в которой разъясняется смысл слов IEEE Standards2. Как следует из этих разъяснений, существует по крайней мере три смысловых отличия от русскоязычного термина ГОСТ:
Таким образом, перевод даже такого общеизвестного слова как Standard словом "стандарт" требует комментариев.
Семантика слов "должен", "не специфицировано", "не определено", "определяется реализацией"
Раздел 2 стандарта называется "Терминология и общие требования". В нем содержатся определения не только специальных терминов (вроде "процесс" или "семафор"), но и таких, казалось бы, самоочевидных слов, как "должен" или "может". Поскольку POSIX.1 - стандарт на интерфейс, то его требования относятся как к операционной системе, так и к прикладной программе. Явное требование выражается словом "должен" (shall), например: "В случае успешного завершения функция link() должна возвращать нулевое значение". В данном примере речь идет о требовании к операционной системе: функция link() должна быть реализована так, чтобы при успешном завершении возвращала нулевое значение.
Термины "не специфицировано" и "не определено" выражают одну и ту же идею состоящую в том, что стандарт допускает свободу выбора, однако смысл этих терминов различен. Термин "не специфицировано" подразумевает, что речь идет о каких-то корректных (действиях, программных конструкциях и т.д.), а термин "не определено" - о некорректных (ошибочных). В информативной части стандарта приводится следующее пояснение.
Термин "не специфицировано" означает, что множество вариантов (исходов, результатов вызова функции и т.д.) предполагается известным, и стандарт допускает любой из них; в конкретной реализации операционной системы может быть выбран любой, и такая система считается соответствующей стандарту. Прикладная программа (строго соответствующая стандарту) должна быть написана так, чтобы корректно работала при любом варианте; по существу, этим термином неявно выражается требование к прикладной программе.
Приведем пример: "Функция readdir() должна возвращать указатель на структуру, относящуюся к очередному элементу каталога. Возвращаются ли элементы каталога с именами "точка" и "точка - точка", стандартом не специфицировано". В этом примере возможно четыре исхода, а требование к прикладной программе состоит в том, что она должна быть рассчитана на любой из них.
Другой пример: "Если функция sigwait(), предписывающая ожидание сигнала с указанным номером, вызвана в нескольких потоках управления, то при поступлении сигнала не более чем в одном из них должен произойти возврат из sigwait(). В каком именно потоке управления это произойдет, стандартом не специфицировано." В этом примере множество возможных исходов может оказаться больше, но все они также известны, и требование к прикладной программе состоит в том, что она должна правильно работать при любом исходе.
Термин "не определено" подразумевает, что даже множество исходов не известно, возможен любой исход, даже плачевный (например, крах системы, потеря данных и т.п.). По существу, этим термином неявно выражается требование к прикладной программе не использовать соответствующие данные или конструкции. Например: "Если аргумент dirp функции readdir() не ссылается на открытый в данный момент каталог, результат не определен".
В этом примере содержится требование к прикладной программе подставлять в качестве аргумента функции readdir() только ссылку на открытый каталог; нарушение этого требования может привести к катастрофическим последствиям, и ответственность за это возлагается на прикладного программиста.
Вот еще один пример: "В случае успешного завершения функция read() должна возвратить целое число, обозначающее количество фактически прочитанных байт. В противном случае функция должна присвоить переменной errno код ошибки и возвратить -1, причем содержимое буфера, на который указывает аргумент buf, не определено."
Использование в прикладной программе данных из буфера в случае ошибки функции read() стандарт запрещает, и последствия нарушения этого требования возлагает на прикладного программиста.
Смысл термина "определяется реализацией" отличается от интуитивного. Очевидно, что в конкретной операционной системе не может быть "неспецифицированного" или "неопределенного" результата, какой-то конкретный результат будет получен обязательно. Термин "определяется реализацией" выражает требование стандарта к документации на операционную систему: результат не только должен быть уточнен (разработчику операционной системы это придется сделать в любом случае), но и явно отражен в документации на систему.
Семантика умолчания
Ни один регламентирующий документ не может охватить всего многообразия случаев, которые могут встретиться на практике, поэтому он неизбежно о чем-то умалчивает3. Например, в описании функции может быть сказано, что ее аргумент может принимать значения из некоторого диапазона, но ничего не говорится о том, каков будет результат, если аргумент не попадает в этот диапазон. Очевидно, что во избежание недоразумений необходимо иметь единую семантику умолчания. В информативной части стандарта имеется любопытная фраза: "общепринятая семантика умолчания - запрещено". Это утверждение противоречит известному лозунгу десятилетней давности "разрешено все, что явно не запрещено". По-видимому, он настолько укоренился в сознании граждан, что многие, даже программисты, не соглашаются с процитированным утверждением стандарта. Между тем, если применение какой-либо конструкции явно не разрешено и не следует из описания, то любой практикующий программист осознает, что использование ее рискованно, и если она не срабатывает, ему не приходит в голову предъявлять претензии.
Процессы и потоки управления
Оба этих термина выражают идею параллельности исполнения. Операционная система UNIX была изначально задумана как многопользовательская, и программы, запускаемые разными пользователями, должны быть надежно изолированы друг от друга, чтобы случайно не исказить "чужие" данные. Эта изоляция обеспечена тем, что программа пользователя исполняется в рамках некоторого процесса, развивающегося в собственном виртуальном адресном пространстве. Даже если в программе есть глобальные данные, при запуске ее в разных процессах они будут автоматически "разведены" по разным адресным пространствам.
Однако механизм процессов не вполне удовлетворителен при программировании задач реального времени. Прикладная программа реального времени (исполняющаяся от имени одного и того же пользователя) часто может быть естественным образом представлена в виде параллельно исполняющихся частей, которые называют "потоками управления" (thread). Наиболее существенное их отличие от процессов состоит в том, все потоки управления развиваются в едином адресном пространстве; этим обеспечивается быстрый доступ к глобальным данным, но одновременно возникает риск непреднамеренного их искажения, и чтобы этого не происходило, необходимо соблюдать некоторую дисциплину программирования. Соответствующие рекомендации содержатся в информативной части стандарта.
Нужно подчеркнуть, что идея многопоточности реализована во многих операционных системах реального времени, и реализована по-разному в том смысле, что каждому потоку управления соответствуют разные множества атрибутов и интерфейсных функций; иногда вместо термина "поток управления" (thread) используется термин "задача" (task). Для того чтобы избежать недоразумений, в стандарте подчеркивается, что речь в нем идет исключительно о потоках управления POSIX, а названия соответствующих интерфейсных функций имеют префикс pthread_ (например, pthread_create(), pthread_join() и т.д.).
Интуитивно считается, что если два предмета изготовлены в соответствии с одним и тем же стандартом, то они гарантированно "сопрягаются" друг с другом и будут совместно работать в паре; именно в этом состоит цель введения стандарта на сопряжение (интерфейс). Поскольку POSIX - стандарт на интерфейс, то можно говорить о соответствии стандарту как операционной системы, так и прикладной программы.
Стандарт POSIX.1 содержит несколько сотен (если не тысяч) требований; считается самоочевидным, что если не выполнено хотя бы одно из них, то система (или прикладная программа) не удовлетворяет стандарту. Вместе с тем, к настоящему времени написано такое количество операционных систем класса UNIX и прикладных программ для них, что вряд ли разумно требовать полного соответствия в указанном смысле. Трудности разработки международного стандарта такого рода усугубляются существованием разных национальных языков. Даже если забыть о прикладных программах, предназначенных для обработки текстов на национальных языках, практически любая прикладная программа должна выдавать какие-то диагностические сообщения и/или воспринимать тексты, вводимые оператором.
Осознавая такого рода трудности, авторы POSIX предлагают уточненную семантику слова "соответствует". Во-первых, вводится несколько видов соответствия (прикладной программы стандарту):
Во-вторых, многие интерфейсные средства объявлены факультативными (optional); стандарт требует, чтобы факультативные интерфейсные функции либо отрабатывались так, как предписано стандартом, либо всегда возвращали особый код ошибки, ENOSYS (означающий, что функция не реализована). Факультативные средства разбиты на несколько групп, каждой из которых соответствует некоторая конфигурационная константа, которая декларируется (оператором #define) в соответствующем заголовочном файле; тем самым обеспечивается возможность выяснения, реализована ли функция, на фазе компиляции.
Описанный прием достижения мобильности изобретен не авторами POSIX, а давно применяется на практике; во многих операционных системах конфигурационные константы применяются для идентификации самой системы или ее версии. И здесь стандарт не предлагает ничего принципиально нового, а лишь систематизирует сложившуюся практику.
Если говорить кратко, то объектами стандартизации POSIX.1 являются имена и семантика. Более конкретно, речь идет о следующем.
Основное содержание стандарта - семантика интерфейсных функций. Важно отметить, что стандарт описывает лишь часть функций, встречающихся в реальной операционной системе, но даже описание этого подмножества занимает около 400 страниц (нормативная часть).
В целом стандарт состоит из двух больших частей примерно одинакового объема. Первая половина - нормативная часть - содержит требования и рекомендации стандарта (18 разделов), вторая - информативная часть - содержит Приложения, в которых приводится список литературы, комментарии и разъяснения к нормативной части, состав заголовочных файлов, пример профиля ("проекции") стандарта (для Дании), характеристики и методика измерения производительности наиболее важных функций, а также описание дополнительных интерфейсных функций для работы с файлами в реальном времени; ожидается, что в следующих редакциях стандарта эти функции будут включены в нормативную часть.
Представление о том, какие именно виды услуг операционной системы охватываются стандартом, дает врезка "Краткое содержание разделов стандарта".
Основное содержание стандарта POSIX - семантика интерфейсных функций. Стандартизация семантики - дело само по себе нелегкое (каждый знает, как трудно бывает договорится даже двум персонам), и трудности усугубляются тем, что в программистскую деятельность в настоящее время вовлечено очень много людей. Например, парадигма параллельного исполнения выражается такими терминами, как "процесс", "задача" и "поток управления", однако с точки зрения практического программирования "задача" в операционной системе IBM OS/360 и в операционной системе реального времени VxWorks - совсем не одно и то же. Еще один пример - семафоры. Семафоры бывают двоичные, целочисленные ("со счетчиком") и взаимного исключения (которые, между прочим, программисты называют между собой "мьютексами", стихийно стремясь избегать недоразумений). И целочисленные семафоры например, в операционной системе VxWorks, вовсе не то же самое, что семафоры POSIX.
Авторы стандарта POSIX, прекрасно осознавая, как трудно заставить людей отказаться от своих привычек (которые они называют "сложившейся практикой"), заявляют, что составили логически связную и минимальную по составу систему интерфейсных функций, охватывающих большую часть услуг, традиционно предоставляемых операционной системой, подробно описали точную семантику этих функций и предлагают всем желающим пользоваться ими в своих разработках4.
При чтении стандарта иногда возникает впечатление, что некоторые формулировки имели единственную цель: не вывести из категории удовлетворяющих стандарту какие-то прикладные программы или операционные системы. Такая цель действительно была поставлена и явно сформулирована во Введении: стандарт должен в максимальной степени учитывать сложившуюся практику. Однако главная цель - это все-таки обеспечение мобильности прикладных программ.
Сергей Романюк - старший научный сотрудник Научно-исследовательского института системных исследований, руководитель группы переводчиков стандарта POSIX. С ним можно связаться по электронной почте по адресу: rom@niisi.msk.ru
Раздел 1 | - Введение |
Раздел 2 | - Терминология и определения |
Раздел 3 | - Функции управления процессами (создание, замена образа, завершение) и сигналами (управление масками, реагирование на сигналы) |
Раздел 4 | - Идентификация (процессов, пользователей, системы, терминала), опрос времен, затраченных на исполнение процесса, опрос переменных окружения. |
Раздел 5 | - Управление файлами и каталогами |
Раздел 6 | - Функции ввода и вывода |
Раздел 7 | - Функции управления терминалом |
Раздел 8 | - Функции, заимствованные из стандарта на язык Си |
Раздел 9 | - Доступ к базам данных пользователей и групп пользователей |
Раздел 10 | - Форматы данных для архивирования и обмена (tar и cpio) |
Раздел 11 | - Средства синхронизации: семафоры, мьютексы и переменные условий |
Раздел 12 | - Функции управления памятью: закрепление и открепление адресного пространства процесса, отображение файлов на память, защита памяти, разделяемая память |
Раздел 13 | - Функции, связанные с планированием процессов и потоков управления |
Раздел 14 | - Управление часами и таймерами |
Раздел 15 | - Управление очередями сообщений |
Раздел 16 | - Базовые функции, относящиеся к потокам управления |
Раздел 17 | - Индивидуальные данные потоков управления (thread-specific data) |
Раздел 18 | - Средства уничтожения потоков управления |