Перенос UNIX-приложений под Linux удивительно легок. Linux и его GNU Си библиотека разработана для приложений, переносимых по замыслу; это означает что многие программы компилируются просто через make. Речь идет обо всех программах, не обращающихся к каким-то туманным возможностям частно й реализации, или сильно завязанных на недокументированном или неопределенном поведении, или, скажем, особенном системном вызове.
Linux часто не согласуется со стандартом IEEE Std 1003.1-1988 (POSIX.1), но это никак не сертифицировано. Linux позаимствовал много хорошего от SVID и BSD ветвей UNIX, но опять же не подражал им во всех возможных случаях. Проще говоря, Linux разработан чтобы быть совместимым с другими реализациями UNIX, сделать прикладные программы легко переносимыми, и в ряде случаев продвинут благодаря отобранным лучшим идеям из этих реализаций.
Например, аргумент timeout, посылаемый системному вызову select, на самом деле уменьшается Linux во время опроса. Другие реализации не изменяют это значение вовсе, и программа, скомпилированная под Linux может сломаться. Руководства SunOS и BSD говорят, что модифицируемость указателя timeout дело "будущих реализаций". К сожалению, многие приложения до сих пор предполагают, что timeout неприкосновенен.
Цель этой главы: сделать обзор основных вещей, связанных с переносом приложений в Linux, освещая различия между Linux, POSIX.1, SVID и BSD в следующих областях: обработка сигналов, ввод/вывод с терминала, управление процессами и сбор информации и переносимая условная компиляция.
С годами определение и семантика сигналов изменялись и совершенствовались различными реализациями UNIX. В настоящее время существует 2 класса сигналов: ненадежные (unreliable) и надежные (reliable). Ненадежные сигналы это те, для которых вызванный однажды обработчик сигнала не остается. Такие "сигналы-выстрелы" должны перезапускать обработчик внутри самого обработчика, если есть желание сохранить сигнал действующим. Из-за этого возможна ситуация гонок, в которой сигнал может прийти до перезапуска обработчика, и тогда он будет потерян, или прийти вовремя, и тогда сработает в соответствии с заданным поведением (например, убьет процесс). Такие сигналы ненадежны, поскольку отлов сигнала и переинсталяция обработчика не являются атомарными операциями.
В семантике ненадежных процессов системные вызовы не повторяются автоматически будучи прерванными поступившим сигналом. Поэтому для обеспечения отработки всех системных вызовов программа должна проверять значение errno после каждого из них и повторять вызовы, если это значение равно EINTR.
По тем же причинам семантика ненадежных сигналов не предоставляет легкого пути реализации атомарных пауз для усыпления процесса до получения сигнала. Ненадежное поведение постоянно перезапускающегося обработчика может привести к неготовности спящего в нужный момент принять сигнал.
Напротив, семантика надежных сигналов оставляет обработчик проинсталированным и ситуация гонок при перезапуске избегается. В то же время определенные сигналы могут быть запущены заново, а атомарная операция паузы доступна через функцию POSIX sigsuspend.
SVR4-реализация сигналов заключается в функциях signal, sigset, sighold, sigrelse, sigignore и sigpause. Функция signal эквивалентна классическим сигналам UNIX V7, она предоставляет только ненадежные сигналы. Остальные функции автоматически перезапускают обработчик. Перезапуск системных вызовов не предусмотрен.
BSD предлагает функции signal, sigvec, sigblock, sigsetmask и sigpause. Все сигналы надежны, а все системные вызовы перезапускаемы Программист имеет возможность это отключить.
В POSIX.1 предоставляются функции sigaction, sigprocmask, sigpending и sigsuspend. Заметьте отсутствие функции signal: она оказалась излишней. Указанные функции работают с надежными сигналами, но перезапуск системных вызовов не определен совсем. Если sigaction используется в BSD или SVR4, то перезапуск системных вызовов по умолчанию отключен. Но он может включаться поднятием флага SA_RESTART.
В итоге, лучший путь работы с сигналами это sigaction, которая позволит точно определить поведение обработчиков сигналов. Однако signal до сих пор используется во многих приложениях и, как мы видели, имеет различную семантику в BSD и SVR4.
В Linux определены следующие значения члена sa_flags структуры sigaction.
Заметьте, что POSIX.1 определяет только SA_NOCLDSTOP, а существуют различные другие опции, определенные SVR4, но невозможные под Linux. Во время переноса прикладных программ, которые используют sigaction, вам, возможно, придется обновлять значения sa_flags, чтобы добиться желаемого поведения.
Функция signal в Linux эквивалентна применению sigaction с опциями SA_ONESHOT и SA_NOMASK, что соответствует классической ненадежной семантике сигналов подобно SVR4.
Если вы хотите использовать signal с семантикой BSD, то для
Вас большинство Linux-систем предоставляет совместимую с BSD библиотеку,
которую можно прилинковать. Для подключения этой библиотеки вы можете
добавить опции
-I/usr/include/bsd -lbsd
для командной строки компиляции. Перенося приложения, использующие
signal, присмотритесь к тому, какие предположения делает ваша
программа относительно обработчиков сигналов, и исправьте код (или
компилируйте с соответствующими установками), чтобы добиться
правильного поведения.
Linux поддерживает практически все сигналы, предоставляемые SVR4, BSD и POSIX, за несколькими исключениями:
Так же, как для сигналов, управление вводом/выводом имеет 3 различных реализации: под SVR4, BSD и POSIX.1.
SVR4 работает со структурой termio В BSD вызовы ioctl типа TIOCGETP, TIOCSETP и т.д.
работают со структурой sgtty.
В POSIX используется структура termios вместе с различными
функциями POSIX.1, такими как tcsetattr и tcgetattr.
Структура termios соответствует структуре termio в SVR4, но
типы переименованы (например, tcflag_t вместо unsigned short
), и для размера массива c_cc употребляется NCCS.
Под Linux ядром поддерживается и termios POSIX.1, и
termio SVR4. Это означает, что, если ваша программа использует оба
метода доступа к вводу/выводу на терминал, то ее следует компилировать прямо
под Linux. Если вы в чем-то сомневаетесь, то вам понадобится совсем
немного знания обоих методов, чтобы исправить termio на
termios. Будем, однако, надеяться, что это не потребуется. Обратите
внимание на то, пытается ли программа использовать поле c_line
структуры termio. Практически для всех приложений оно должно быть
равно N_TTY, и если программа предполагает возможность другого
упорядочения линий, вы можете заработать ошибку.
Если ваша программа использует реализацию BSD sgtty, вы можете
прилинковать libbsd, как описывалось выше. Это обеспечит перекройку
ioctl, означающую пересмотр запросов ввода/вывода на терминал в
термины структуры termios POSIX, поддерживаемые ядром. При
компиляции такой программы, если символы вроде TIOCGETP не
определены, вам придется прилинковать libbsd.
Такие программы, как ps, top и free, должны иметь способ
получения информации от ядра о процессах и ресурсах системы.
Аналогично, отладчикам и другим подобным средствам требуется управлять
и инспектировать работающий процесс. Такие возможности предоставляет ряд
интерфейсов различных версий UNIX, и практически все они либо
машинно-зависимы, либо привязаны к конкретной реализации ядра, поэтому не
существует универсально доступного интерфейса для такого взаимодействия
ядра и процесса.
Для прямого доступа к структурам ядра многие системы используют
устройство /dev/kmem и подпрограммы kvm_open, kvm_nlist и
kvm_read. Программа открывает /dev/kmem, читает символьную
таблицу ядра, определяет при помощи этой таблицы расположение данных в
работающем ядре и читает соответствующие адреса адресного пространства ядра
используя названные подпрограммы. Поскольку это требует согласования
между программой пользователя и ядром, размера и формата структур
данных, подобные программы перестраиваются для каждой новой версии
ядра, типа процессора и т.д.
Системный вызов ptrace используется в 4.3BSD и SVID для
управления процессом и считывания из него информации. Классически он
используется отладчиками для, скажем, trap-исполнения процесса (с условными
точками останова) или исследования его состояния. Под SVR4 ptrace
заменен файловой системой /proc, которая появляется как
директория, содержащая единственную точку входа в файл для каждого
работающего процесса, называемую ID процесса. Пользовательская
программа может открыть файл интересующего ее процесса и совершить над
ним различные вызовы ioctl для управления выполнением процесса или
получения информации о процессе от ядра. Аналогично, программа может
читать или записывать данные напрямую в адресное пространство процесса
через файловый дескриптор в файловую систему /proc.
Под Linux для управления процессом поддерживается системный вызов
ptrace, работающий так же, как 4.3BSD. Для получения информации
о процессе или системе Linux также предоставляет файловую систему
/proc, но с совершенно другой семантикой. Под Linux /proc
состоит из ряда файлов с общесистемной информацией, такой как использование
памяти, средняя загруженность, статистика загружаемых модулей и
сетевая статистика. Эти файлы общедоступны для read и write; их
содержимое можно разбирать, используя scanf. Файловая система
/proc под Linux также предоставляет точку входа в директорию для
каждого работающего процесса, называемую ID процесса. Она содержит файловые
точки входа для информации типа командной линии, связей с текущей
директорией и исполняемым файлом, открытых файловых дескрипторов и т.д. Ядро
предоставляет всю эту информацию в ответ на запрос read.
Такая реализация не противопоставляется файловой системе /proc,
находящейся в Plan 9, и имеет некоторые ее недостатки. Например, для
ps, чтобы просмотреть таблицу с информацией о всех работающих
процессах, нужно пересечь многие директории, открыть и прочитать
многие файлы. Для сравнения: подпрограммы kvm в других UNIX-системах
считывают структуры ядра напрямую, потратив лишь несколько системных вызовов.
Очевидно, все реализации настолько различны, что перенос приложений, их
использующих, может стать серьезной задачей. Следует особо отметить, что
файловая система /proc в SVR4 намного грубее, чем в Linux, и их
нельзя использовать в одно и том же контексте. На самом деле каждая
программа, которая использует kvm или файловую систему
/proc SVR4, просто непереносима, и такие фрагменты кода
должны быть переписаны.
Вызовы ptrace Linux и BSD похожи, но все же имеют несколько
отличий:
Linux не имеет подпрограмм kvm для чтения адресного
пространства ядра из пользовательской программы, но в нем есть средства,
такие как kmem_ps, в действительности являющиеся версией подобных
подпрограмм. Вообще говоря, они непереносимы, и каждый код, использующий
kvm, вероятнее всего, зависит от определенных обозначений или от
типов данных ядра, поэтому такой код следует признать машинно-зависимым.
Если вы хотите исправить существующий код для достижения совместимости с
Linux, то вам потребуется использовать ifdef...endif для того, чтобы
окружить необходимые для этого участки. Не существует стандарта выделения
кода, зависящего от операционной системы, но многие программы используют
соглашение, принятое в SVR4 для кода System V, в BSD для BSD-кода и для
linux в Linux-зависимом коде:
Библиотека GNU C, используемая в Linux, позволяет определять макросы для
компиляции программ с разными типами кода:
Если вы определили _BSD_SOURSE, то для библиотеки определится
_FAVOR_BSD. Тогда некоторые вещи POSIX и SVR4 будут вести себя, как
в BSD. Например, если определено _FAVOR_BSD, setgmp и longgmp будут
сохранять и запоминать маску сигнала, а getpgrp будет допускать
аргумент PID. Напомним, что вы должны собирать программу с libbsd,
чтобы добиться BSD-поведения.
Linux gcc автоматически определяет набор макросов, которые вы
можете использовать в своей программе:
Эта глава охватывает многое, связанное с переносом, кроме, правда,
отсутствия некоторых системных вызовов, названных в главе о системных
вызовах, и потоков (знатоки говорят, что загружаемый потоковый модуль должен
быть на ftp.uni-stuttgart.de в pub/systems/linux/isdn). (Добавлено Свеном
Голдтом /Sven Goldt/.)
struct termio {
unsigned short c_iflag; /* Input modes */
unsigned short c_oflag; /* Output modes */
unsigned short c_cflag; /* Control modes */
unsigned short c_lflag; /* Line discipline modes */
char c_line; /* Line discipline */
unsigned char c_cc[NCC]; /* Control characters */
};
10.4 Управление процессами
10.4.1 Подпрограммы kvm
10.4.2 ptrace и
файловая система /proc
10.4.3 Управление процессами
под Linux
10.5 Переносимая условная
компиляция
Многие программы используют
#ifdef linux
для окружения Linux-зависимого кода. Заметьте, что Linux поддерживает
многие вещи из System V, и поэтому начинать программы, написанные
также для System V и BSD, лучше всего с System V-версии. Впрочем, вы
можете начинать и с BSD и собирать при помощи libbsd.
10.6 Дополнительные комментарии
Converted on:
Fri Mar 29 14:43:04 EST 1996