Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
 Languages
 С
 GNU С Library 
 Qt 
 STL 
 Threads 
 C++ 
 Samples 
 stanford.edu 
 ANSI C
 Libs
 LD
 Socket
 Pusher
 Pipes
 Encryption
 Plugin
 Inter-Process
 Errors
 Deep C Secrets
 C + UNIX
 Linked Lists / Trees
 Asm
 Perl
 Python
 Shell
 Erlang
 Go
 Rust
 Алгоритмы
NEWS
Последние статьи :
  Тренажёр 16.01   
  Эльбрус 05.12   
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
 
TOP 20
 MINIX...3057 
 Solaris...2933 
 LD...2904 
 Linux Kernel 2.6...2470 
 William Gropp...2180 
 Rodriguez 6...2011 
 C++ Templates 3...1945 
 Trees...1937 
 Kamran Husain...1865 
 Secure Programming for Li...1792 
 Максвелл 5...1710 
 DevFS...1693 
 Part 3...1682 
 Stein-MacEachern-> Час...1632 
 Go Web ...1624 
 Ethreal 4...1618 
 Arrays...1606 
 Стивенс 9...1603 
 Максвелл 1...1592 
 FAQ...1538 
 
  01.01.2024 : 3621733 посещений 

iakovlev.org

Руководство программиста для Linux

Linux Programmer's
Sven Goldt, Sven van der Meer, Skott Burkett, Matt Welsh ver 0.4

Материал взят с сайта http://linuxland.itam.nsc.ru/book/

10. Перенос прикладных программ в Linux

10.1. Введение

Перенос 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 в следующих областях: обработка сигналов, ввод/вывод с терминала, управление процессами и сбор информации и переносимая условная компиляция.

10.2. Обработка сигналов

С годами определение и семантика сигналов изменялись и совершенствовались различными реализациями UNIX. В настоящее время существует 2 класса сигналов: ненадежные (unreliable) и надежные (reliable). Ненадежные сигналы - это те, для которых вызванный однажды обработчик сигнала не остается Такие "сигналы-выстрелы" должны перезапускать обработчик внутри самого обработчика, если есть желание сохранить сигнал действующим. Из-за этого возможна ситуация гонок, в которой сигнал может прийти до перезапуска обработчика - и тогда он будет потерян, или придет вовремя - и тогда сработает в соответствии с заданным поведением (например, убьет процесс). Такие сигналы ненадежны, поскольку отлов сигнала и переинсталяция обработчика не являются атомарными операциями.

В семантике ненадежных процессов системные вызовы не повторяются автоматически будучи прерванными поступившим сигналом. Поэтому для обеспечения отработки всех системных вызовов программа должна проверять значение errno после каждого из них и повторять вызовы, если это значение равно EINTR.

По тем же причинам семантика ненадежных сигналов не предоставляет легкого пути реализации атомарных пауз для усыпления процесса до получения сигнала. Ненадежное поведение постоянно перезапускающегося обработчика может привести к неготовности спящего в нужный момент принять сигнал.

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

10.2.1. Сигналы в SVR4, BSD и POSIX.1

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.

10.2.2. Опции сигналов Linux

В Linux определены следующие значения члена sa_flags структуры sigaction.

  • SA_NOCLDSTOP: Не посылайте SIGCHLD во время остановки процесса-потомка.
  • SA_RESTART: Осуществляет перезапуск определенных системных вызовов во время прерывания обработчиком сигналов.
  • SA_NOMASK: Обнуление маски сигнала (которое блокирует сигналы во время работы обработчика сигналов).
  • SA_ONESHOT: Очищает обработчик сигналов после исполнения. Заметьте, что SVR4 использует SA_RESETHAND для тех же целей.
  • SA_INTERRUPT: Определен под Linux-ом, но не используется. Под SunOS системные вызовы автоматически перезапускались, а этот флаг отменял такое поведение.
  • SA_STACK: В настоящее время не работает; предназначен для стеков сигналов.

Заметьте, что POSIX.1 определяет только SA_NOCLDSTOP, а существуют различные другие опции, определенные SVR4, но невозможные под Linux-ом. Во время переноса прикладных программ, которые используют sigaction, вам, возможно, придется обновлять значения sa_flags, чтобы добиться желаемого поведения.

10.2.3. signal под Linux-ом

Функция signal в Linux-е эквивалентна применению sigaction с опциями SA_ONESHOT и SA_NOMASK, что соответствует классической ненадежной семантике сигналов подобно SVR4.

Если вы хотите использовать signal с семантикой BSD, то для вас большинство Linux-систем предоставляет совместимую с BSD библиотеку, которую можно прилинковать. Для подключения этой библиотеки вы можете добавить опции

-I/usr/include/bsd -lbsd

для командной строки компиляции. Перенося приложения, использующие signal, присмотритесь к тому, какие предположения делает ваша программа относительно обработчиков сигналов, и исправьте код (или компилируйте с соответствующими установками), чтобы добиться правильного поведения.

10.2.4. Сигналы, поддерживаемые Linux-ом

Linux поддерживает практически все сигналы, предоставляемые SVR4, BSD и POSIX, за несколькими исключениями:

  • SIGEMT не поддерживается; он соответствует аппаратному сбою под SVR4 и BSD.
  • SIGINFO не поддерживается; он используется для информационных запросов к клавиатуре под SVR4.
  • SIGSYS не поддерживается; он относится к некорректному системному вызову в SVR4 и BSD. В сочетании с libbsd этот сигнал переопределяется в SIGUNUSED.
  • SIGABRT и SIGIOT идентичны.
  • SIGIO, SIGPOLL и SIGURG идентичны.
  • SIGBUS определен как SIGUNUSED. Технически в среде Linux нет никаких "ошибок шины".

10.3. Ввод/вывод с терминала

Так же, как для сигналов, управление вводом/выводом имеет 3 различных реализации: под SVR4, BSD и POSIX.1.

SVR4 работает со структурой termio и различными вызовами ioctl (такими, как TCSETA, TCGETA и т.д.) для получения и установки параметров терминала. Эта структура выглядит так: struct termio {

   unsigned short c_iflag;  /* режимы ввода */
    unsigned short c_oflag;  /* режимы вывода */
    unsigned short c_cflag;  /* режимы управления */
    unsigned short c_lflag;  /* режимы упорядочения линий */
    char c_line              /* упорядочение линий */
    unsigned char c_cc[NCC]; /* символы управления */
 };

В 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.

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

Такие программы, как ps, top и free, должны иметь способ получения информации от ядра о процессах и ресурсах системы. Аналогично, отладчикам и другим подобным средствам требуется управлять и инспектировать работающий процесс.

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

10.4.1. Подпрограммы kvm

Для прямого доступа к структурам ядра многие системы используют устройство /dev/kmem и подпрограммы kvm_open, kvm_nlist и kvm_read. Программа открывает /dev/kmem, читает символьную таблицу ядра, определяет при помощи этой таблицы расположение данных в работающем ядре и читает соответствующие адреса адресного пространства ядра используя названные подпрограммы. Поскольку это требует согласования между программой пользователя и ядром, размера и формата структур данных, подобные программы перестраиваются для каждой новой версии ядра, типа процессора и т.д.

10.4.2. ptrace и файловая система /proc

Системный вызов ptrace используется в 4.3BSD и SVID для управления процессом и считывания из него информации. Классически он используется отладчиками для, скажем, trap-исполнения процесса (с условными точками останова) или исследования его состояния. Под SVR4 ptrace заменен файловой системой /proc, которая появляется как директория, содержащая единственную точку входа в файл для каждого работающего процесса, называемую ID процесса. Пользовательская программа может открыть файл интересующего ее процесса и совершить над ним различные вызовы ioctl для управления выполнением процесса или получения информации о процессе от ядра. Аналогично, программа может читать или записывать данные напрямую в адресное пространство процесса через файловый дескриптор в файловую систему /proc.

10.4.3. Управление процессами под Linux

Под 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 похожи, но все же имеют несколько отличий:

  • Запросы PTRACE_PEEKUSER и PTRACE_POKEUSER под BSD названы соответственно PTRACE_PEEKUSR и PTRACE_POKEUSR в Linux-е.
  • Регистры процесса могут быть установлены с использованием запроса PTRACE_POKEUSR со смещениями, находящимися в /usr/include/linux/ptrace.h.
  • Запросы SunOS-а PTRACE_{READ,WRITE}{TEXT,DATA} не поддерживаются, как не поддерживаются ни PTRACE_SETACBKPT, ни PTRACE_SETWRBKPT, ни PTRACE_CLRBKPT, ни PTRACE_DUMPCORE. Отсутствие этих запросов влияет лишь на малое количество существующих программ.

Linux не имеет подпрограмм kvm для чтения адресного пространства ядра из пользовательской программы, но в нем есть средства, такие как kmem_ps, в действительности являющиеся версией подобных подпрограмм. Вообще говоря, они непереносимы, и каждый код, использующий kvm, вероятнее всего, зависит от определенных обозначений или от типов данных ядра, поэтому такой код следует признать машинно-зависимым.

10.5. Переносимая условная компиляция

Если вы хотите исправить существующий код для достижения совместимости с Linux-ом, то вам потребуется использовать ifdef...endif для того, чтобы окружить необходимые для этого участки. Не существует стандарта выделения кода, зависящего от операционной системы, но многие программы используют соглашение, принятое в SVR4 для кода System V, в BSD для BSD-кода и для linux - в Linux-зависимом коде:

  • __STRICT_ANSI__: только для ANSI C
  • _POSIX_SOURCE: для POSIX.1
  • _POSIX_C_SOURCE: если определено как 1, то используется POSIX.1, если 2 - то POSIX.2
  • _BSD_SOURCE: ANSI, POSIX и BSD
  • _SVID_SOURCE: ANSI, POSIX и System V
  • _GNU_SOURCE: ANSI, POSIX, BSD, SVID и GNU расширения. Это значение по умолчанию, если ничто из вышеперечисленного не определено.

Если вы определили _BSD_SOURSE, то для библиотеки определится _FAVOR_BSD. Тогда некоторые вещи POSIX-а и SVR4 будут вести себя, как в BSD. Например, если определено _FAVOR_BSD, setgmp и longgmp будут сохранять и запоминать маску сигнала, а getpgrp будет допускать аргумент PID. Напомним, что вы должны собирать программу с libbsd, чтобы добиться BSD-поведения.

gcc Linux-а автоматически определяет набор макросов, которые вы можете использовать в своей программе:

  • __GNUC__ (major GNU C версия, e.g., 2)
  • __GNUC_MINOR__ (minor GNU C версия, e.g., 2)
  • unix
  • i386
  • linux
  • __unix__
  • __i386__
  • __linux__
  • __unix
  • __i386
  • __linux

Многие программы используют #ifdef linux для окружения Linux-зависимого кода. Заметьте, что Linux поддерживает многие вещи из Sistem V, и поэтому начинать программы, написанные также для Sistem V и BSD, лучше всего с Sistem V-версии. Впрочем, вы можете начинать и с BSD и собирать при помощи libbsd.

10.6. Дополнительные комментарии

Эта глава охватывает многое, связанное с переносом, кроме, правда, отсутствия некоторых системных вызовов, названных в главе о системных вызовах, и потоков (знатоки говорят, что загружаемый потоковый модуль должен быть на ftp.uni-stuttgart.de в pub/systems/linux/isdn). (Добавлено Свеном Голдтом /Sven Goldt/.)

Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье