Данное руководство призвано помочь программистам разобраться с особенностями Linux. Оно также освещает проблемы переноса программ с других операционных систем; влияющие на старые программы изменения в ядре и в системных вызовах, такие как последовательный ввод/вывод и работа по сети.
В марте 1991 Л.В.Торвальдс (Linus Benedict Torvalds) купил мультизадачную операционную систему Minux для своего AT 386. Он использовал ее для того, чтобы создать свою собственную мультизадачную ОС, которую назвал Linux. В сентябре 1991 года он распространил по e-mail ее первый прототип среди пользователей Minux. С этого момента многие стали поддерживать Linux, добавляя драйверы устройств, разрабатывая разные продвинутые приложения и учитывая соглашения POSIX. В настоящее время Linux очень мощная система, но самое замечательное то, что она free. Ведется работа над Linux для других платформ.
Перенос 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/.)
В таблице я использовал такие условные обозначения:
Энциклопедия программиста Linux написана Алексеем
Паутовым (rldp@ftp.botik.ru) в 2000
году на основе The Linux Programmer's Guide, которая
сгенерирована программой
LaTeX2HTML Version 96.1-c (Feb 29, 1996)
Copyright © 1993, 1994, 1995, 1996,
Nikos Drakos,
Computer Based Learning Unit, University of Leeds.
Командная строка преобразования: Конвертировал Andrew Anderson on Fri Mar 29 14:43:04 EST 1996 Ядро является базой LINUX. Вы можете как угодно переставлять
любую из библиотек, но пока есть ядро есть и LINUX. Оно включает в
себя драйвера устройств, механизм распределения памяти, управление
процессами и связями. Разработчики ядра стараются следовать рекомендациям
POSIX, которые иногда осложняют, а иногда упрощают программирование. И если
ваша программа поведет себя иначе на новой версии ядра, то вероятнее всего, в
этой версии учтена еще какая-нибудь рекомендация POSIX. Информацию о ядре для
программиста можно найти в Linux Kernel Hacker's Guide (Справочное
руководство по ядру LINUX для программиста).
libc: ISO 8859.1, Автор надеется, что кто-нибудь из разработчиков libc напишет эту главу как
положено. Все, что автор может на данный момент сказать, это то, что
исполняемый формат a.out собираются поменять на elf (executable and lincable
format: исполняемый и собираемый формат), что подразумевает изменения и в
разделяемых библиотеках. В настоящий момент поддерживаются оба формата.
Большая часть libc находится под лицензией GNU (Library GNU Public
License). Хотя попадаются и специальные исключения, например crt0.o. Для
коммерческого использования это означает запрет на статически линкуемые
программы. Динамически линкуемые программы, однако, тоже являются
специальными исключениями.
Системный вызов, это требование к ОС (ядру) произвести аппаратно/системно
специфическую или привилегированную операцию. В Linux-1.2 были определены 140
системных вызовов. Такие вызовы, как close() реализованы в Linux libc. Эта
реализация часто включает в себя макрос, который в конце концов вызывает
syscall(). Параметры, передаваемые syscall, это номер системного вызова,
перед которым ставятся требуемые аргументы. Номера системных вызовов можно
найти в На i386 системные вызовы ограничены 5-ю аргументами кроме номера
вызова из-за аппаратного числа регистров. На другой архитектуре вы
можете поискать макрос _syscall в _syscall1 раскрывается в функцию close(), и мы получаем твикс: один
close() в libc и один в нашей программе. Возвращаемое syscall() (или
_syscall) значение есть -1, если вызов неудачен и 0 или больше в случае
успеха. В случае неудачи ошибку можно определить по глобальной переменной
errno.
Приведем системные вызовы, возможные в BSD и SYS V, но не допустимые в
LINUX: audit(), audition(), fchroot(), getauid(), getdents(), getmsg(),
mincore(), poll(), putmsg(), setaudit(), setauid().
iotcl предназначен для контроля ввода/вывода и используется для
манипуляций с устройством через файловый дескриптор. Формат ioсtl: Детальный обзор IPC (interprocess communication facilities),
поддерживаемых в Linux. Linux IPC (Inter-process communication) предоставляет
средства для взаимодействия процессов между собой.
В распоряжении программистов есть несколько методов IPC:
Если эти возможности эффективно используются, то они обеспечивают
солидную базу для поддержания идеологии клиент/сервер в любой
UNIX-системе, включая Linux.
Канал представляет собой средство связи стандартного вывода одного
процесса со стандартным вводом другого. Каналы старейший из инструментов
IPC, существующий приблизительно со времени появления самых ранних
версий оперативной системы UNIX. Они предоставляют метод односторонних
коммуникаций (отсюда термин half-duplex) между процессами.
Эта особенность широко используется даже в командной строке UNIX (в shell).
Приведенный выше канал принимает вывод ls как ввод sort, и вывод
sort за ввод lp. Данные проходят через полудуплексный канал,
перемещаясь (визуально) слева направо.
Хотя большинство из нас использует каналы в программировании на shell
довольно часто, мы редко задумываемся о том, что происходит на уровне ядра.
Когда процесс создает канал, ядро устанавливает два файловых
дескриптора для пользования этим каналом. Один такой дескриптор
используется, чтобы открыть путь ввода в канал (запись), в то время
как другой применяется для получения данных из канала (чтение). В этом
смысле, канал мало применим практически, так как создающий его процесс
может использовать канал только для взаимодействия с самим собой.
Рассмотрим следующее изображение процесса и ядра после создания канала:
Из этого рисунка легко увидеть, как файловые дескрипторы связаны
друг с другом. Если процесс посылает данные через канал (fd0), он
имеет возможность получить эту информацию из fd1. Однако этот
простенький рисунок отображает и более глобальную задачу. Хотя канал
первоначально связывает процесс с самим собой, данные, идущие через
канал, проходят через ядро. В частности, в Linux каналы внутренне
представлены корректным inode. Конечно, этот inode существует в
пределах самого ядра, а не в какой-либо физической файловой системе.
Эта особенность откроет нам некоторые привелекательные возможности для
ввода/вывода, как мы увидим немного позже.
Зачем же нам неприятности с созданием канала, если мы
всего-навсего собираемся поговорить сами с собой? На самом деле,
процесс, создающий канал, обычно порождает дочерний процесс. Как
только дочерний процесс унаследует какой-нибудь открытый файловый
дескриптор от родителя, мы получаем базу для мультипроцессовой
коммуникации (между родителем и потомком). Рассмотрим эту измененную
версию нашего рисунка:
Теперь мы видим, что оба процесса имеют доступ к файловым
дескрипторам, которые основывают канал. На этой стадии должно быть
принято критическое решение. В каком направлении мы хотим запустить
данные? Потомок посылает информацию к родителю или наоборот? Два
процесса взаимно согласовываются и "закрывают" неиспользуемый конец
канала. Пусть потомок выполняет несколько действий и посылает информацию
родителю обратно через канал. Наш новый рисунок выглядел бы примерно так:
Конструкция канала теперь полная. Все, что осталось сделать, это
использовать его. Чтобы получить прямой доступ к каналу, можно
применять системные вызовы, подобные тем, которые нужны для
ввода/вывода в файл или из файла на низком уровне (вспомним, что в
действительности каналы внутренне представлены как корректный inode).
Чтобы послать данные в канал, мы используем системный вызов
write(), а чтобы получить данные из канала системный вызов read().
Вспомним, что системные вызовы ввода/вывода в файл или из файла
работают с файловыми дескрипторами! (Однако, не забывайте, что некоторые
системные вызовы, как, например, lseek(), не работают с дескрипторами.)
Создание каналов на языке программирования Си может оказаться
чуть более сложным, чем наш простенький shell-пример. Чтобы создать
простой канал на Си, мы прибегаем к использованию системного вызова
pipe(). Для него требуется единственный аргумент, который является
массивом из двух целых (integer), и, в случае успеха, массив будет
содержать два новых файловых дескриптора, которые будут использованы
для канала. После создания канала процесс обычно порождает новый процесс
(вспомним, что процесс-потомок наследует открытые файловые дескрипторы).
Вспомните, что имя массива decays в С это указатель на его
первый член. fd это эквивалент &fd[0]. Раз мы установили
канал, то ответвим нашего нового потомка:
Если родитель хочет получить данные от потомка, то он должен
закрыть fd1, а потомок должен закрыть fd0. Если родитель хочет послать
данные потомку, то он должен закрыть fd0, а потомок - fd1. С тех пор
как родитель и потомок делят между собой дескрипторы, мы должны всегда
быть уверены, что не используемый нами в данный момент конец канала закрыт;
EOF никогда не будет возвращен, если ненужные концы канала не закрыты.
Как было упомянуто ранее, раз канал был установлен, то файловые
дескрипторы могут обрабатываться подобно дескрипторам нормальных файлов.
Часто дескрипторы потомка раздваиваются на стандартный ввод или
вывод. Потомок может затем exec() другую программу, которая наследует
стандартные потоки. Давайте посмотрим на системный вызов dup():
Несмотря на то, что старый и новосозданный дескрипторы
взаимозаменяемы, мы будем сначала закрывать один из стандартных
потоков. Системный вызов dup() использует наименьший по номеру
неиспользуемый дескриптор для нового. Рассмотрим:
Поскольку файловый дескриптор 0 (stdin) был закрыт, вызов dup()
дублировал дескриптор ввода канала (fd0) на его стандартный ввод.
Затем мы сделали вызов execlp(), чтобы покрыть код потомка кодом
программы sort. Поскольку стандартные потоки exec()-нутой программы
наследуются от родителей, это означает, что вход канала ста для
потомка стандартным вводом! Теперь все, что первоначальный
процесс-родитель посылает в канал, идет в sort.
Существует другой системный вызов, dup2(), который также может
использоваться. Этот особенный вызов произошел с Version 7 of UNIX и
был поддержан BSD, и теперь требуется по стандарту POSIX.
Благодаря этому особенному вызову мы имеем закрытую операцию и
действующую копию за один системный вызов. Вдобавок, он гарантированно
неделим, что означает, что он никогда не будет прерван поступающим
сигналом. С первым системным вызовом dup() программисты были вынуждены
предварительно выполнять операцию close(). Это приводилок наличию двух
системных вызовов с малой степенью защищенности в краткий промежуток
времени между ними. Если бы сигнал поступил в течение этого интервала
времени, копия дескриптора не состоялась бы. dup2() разрешает для нас
эту проблему. Рассмотрим:
Если все изложенные выше изыскания кажутся слишком размытым
способом создания и использования каналов, то вот альтернатива этому.
Эта стандартная библиотечная функция создает полудуплексный канал
посредством вызывания pipe() внутренне. Затем она порождает дочерний
процесс, запускает Bourne shell и исполняет аргумент command внутри
shell. Управление потоком данных определяется вторым аргументом,
type. Он может быть "r" или "w", для чтения или записи, но не может
быть и то, и другое! Под Linux канал будет открыт в виде,
определенном первым символом аргумента "type". Поэтому, если вы
попытаетесь ввести "rw", канал будет открыт только в виде "read".
Каналы, созданные popen(), должны быть закрыты pclose(). К этому
моменту вы, вероятно, уже использовали [реализовали] popen/pclose
share, удивительно похожий на стандартный файловый поток I/O функций
fopen() и fclose().
Рассмотрим пример, который открывает канал для команды сортировки
и начинает сортировать массив строк:
Поскольку popen() использует shell для своих нужд, пригодны все
символы и метасимволы shell. Кроме того, с popen() становится
возможным использование более продвитутых техник, таких, как переадресация и
даже каналирование вывода. Рассмотрим в качестве образца следующие вызовы:
В качестве другого примера popen(), рассмотрим маленькую программу,
открывающую два канала (один для команды ls, другой для сортировки):
В качестве последней демонстрации popen(), давайте создадим
программу, характерную для открытия канала между отданной командой и
именем файла:
Попробуйте выполнить эту программу с последующими заклинаниями:
Для того чтобы операция рассматривалась как "атомарная", она не
должна прерываться ни по какой причине. Неделимая операция выполняется
сразу. POSIX стандарт говорит в /usr/include/posix_lim.h, что
максимальные размеры буфера для атомарной операции в канале таковы:
Атомарно по каналу может быть получено или передано до 512 байт.
Все, что выходит за эти пределы, будет разбито и не будет выполняться
атомарно. Однако, в Linux этот атомарный операционный лимит
определен в "linux/limits.h" следующим образом:
Как вы можете заметить, Linux предоставляет минимальное
количество байт, требуемое POSIX, довольно щедро. Атомарность
операции с каналом становится важной, если вовлечено более одного
процесса (FIFOS). Например, если количество байтов, записанных в
канал, превышает лимит, отпущенный на отдельную операцию, а в канал
записываются многочисленные процессы, то данные будут смешаны, т.е.
один процесс может помещать данные в канал между записями других.
Именованные каналы во многом работают так же, как и обычные
каналы, но все же имеют несколько заметных отличий.
Есть несколько способов создания именованного канала. Первые два
могут быть осуществлены непосредственно из shell.
Эти две команды выполняют идентичные операции, за одним
исключением. Команда mkfifo предоставляет возможность для изменения
прав доступа к файлу FIFO непосредственно после создания. При
использовании mknod будет необходим вызов команды chmod.
Файлы FIFO могут быть быстро идентифицированы в физической
файловой системе посредством индикатора "p", представленного здесь в
длинном листинге директории.
Также заметьте, что вертикальный разделитель располагается
непосредственно после имени файла.
Чтобы создать FIFO на Си, мы можем прибегнуть к использованию
системного вызова mknod():
Оставим более детальное обсуждение mknod() man page, а сейчас
давайте рассмотрим простой пример создания FIFO на Си:
В данном случае файл "/tmp/MYFIFO" создан как FIFO-файл.
Требуемые права "0666", хотя они находятся под влиянием
установки umask, как например:
Общая хитрость: использовать системный вызов umask() для того,
чтобы временно устранить значение umask:
Кроме того, третий аргумент mknod() игнорируется, в противном
случае мы создаем файл устройства. В этом случае он должен отметить
верхнее и нижнее числа файла устройства.
Операции ввода/вывода с FIFO, по существу, такие же, как для
обычных каналов, за одним исключением. Чтобы физически открыть проход
к каналу, должен быть использован системный вызов "open" или
библиотечная функция. С полудуплексными каналами это невозможно,
поскольку канал находится в ядре, а не в физической файловой системе.
В нашем примере мы будем трактовать канал как поток, открывая его
fopen() и закрывая fclose().
Рассмотрим простой сервер-процесс:
Поскольку FIFO блокирует по умолчанию, запустим сервер фоном
после того, как его откомпилировали:
Скоро мы обсудим действие блокирования, но сначала рассмотрим
следующего простого клиента для нашего сервера:
Если FIFO открыт для чтения, процесс его блокирует до тех пор,
пока какой-нибудь другой процесс не откроет FIFO для записи.
Аналогично для обратной ситуации. Если такое поведение нежелательно,
то может быть использован флаг O_NONBLOCK в системном вызове open(),
чтобы отменить действие блокирования.
В примере с нашим простым сервером мы только запустили его в фоне и
позволили там осуществлять блокирование. Альтернативой могло бы быть
перепрыгивание на другую виртуальную консоль, запуск клиента и
переключение туда и обратно, чтобы увидеть результат.
Последнее, что следует отметить, это то, что каналы должны иметь
читателя и писателя. Если процесс пробует записать в канал, не имеющий
читателя, из ядра будет послан сигнал SIGPIPE. Это необходимо, когда в
каналом пользуются более чем два процесса.
Вместе с System V AT&T предложил три новых типа IPC средств
(очереди сообщений, семафоры и разделяемая память). POSIX еще не
стандартизировал эти средства, но большинство разработок их уже
поддерживает. Впрочем, Беркли (BSD) в качестве базовой формы IPC
использует скорее сокеты, чем элементы System V. Linux имеет
возможность использовать оба вида IPC (BSD и System V), хотя мы не
будем обсуждать сокеты в этой главе.
Версия System V IPC для LINUX сделана Кришной Баласубраманьяном
(Krishna Balasubramanian), balasub@cis.ohio-state.edu.
Каждый объект IPC имеет уникальный IPC идентификатор. Когда
мы говорим "объект IPC", мы подразумеваем очередь единичных сообщений,
множество семафоров или разделяемый сегмент памяти. Этот
идентификатор требуется ядру для однозначного определения объекта IPC.
Например, чтобы сослаться на определенный разделяемый сегмент,
единственное, что вам потребуется, это уникальное значение ID, которое
привязано к этому сегменту.
Идентификатор IPC уникален только для своего типа объектов. То
есть, скажем, возможна только одна очередь сообщений с идентификатором
"12345", так же как номер "12345" может иметь какое-нибудь одно
множество семафоров или (и) какой-то разделяемый сегмент.
Чтобы получить уникальный ID нужен ключ. Ключ должен быть взаимно
согласован процессом-клиентом и процессом-сервером. Для приложения это
согласование должно быть первым шагом в построении среды.
Чтобы позвонить кому-либо по телефону, вы должны знать его
номер. Кроме того, телефонная компания должна знать как провести ваш
вызов к адресату. И только когда этот адресат ответит, связь состоится.
В случае System V IPC "телефон" соединяет объекты IPC одного
типа. Под "телефонной компанией", или методом маршрутизации, следует
понимать ключ IPC.
Ключ, генерируемый приложением самостоятельно, может быть каждый
раз один и тот же. Это неудобно, полученный ключ может уже
использоваться в настоящий момент. Функцию ftok() используют для
генерации ключа и для клиента, и для сервера:
Итак, значение ключа, когда оно получено, используется в
последующих системных вызовах IPC для создания или улучшения доступа к
объектам IPC.
Команда ipcs выдает статус всех объектов System V IPC. Ее
LINUX-версия также была создана Кришной Баласубраманьяном.
Команда ipcs это очень мощное средство, позволяющее
подсматривать за механизмом ядреной памяти для IPC-объектов. Изучайте
его, пользуйтесь им, благоговейте перед ним.
Команда ipcrm удаляет объект IPC из ядра. Однако, поскольку
объекты IPC можно удалить через системные вызовы в программе пользователя
(как это делать мы увидим чуть позднее), часто нужды удалять их вручную нет.
Особенно это касается всяких программных оболочек. Внешний вид ipcrm прост:
Очереди сообщений представляют собой связный список в адресном
пространстве ядра. Сообщения могут посылаться в очередь по порядку и
доставаться из очереди несколькими разными путями. Каждая очередь
сообщений однозначно определена идентификатором IPC.
Ключом к полному осознанию такой сложной системы, как System V
IPC, является более тесное знакомство с различными структурами данных,
которые лежат внутри самого ядра. Даже для большинства примитивных
операций необходим прямой доступ к некоторым из этих структур, хотя
другие используются только на гораздо более низком уровне.
Первой структурой, которую мы рассмотрим, будет msgbuf. Его можно
понимать как шаблон для данных сообщения. Поскольку данные в
сообщении программист определяет сам, он обязан понимать, что на самом деле
они являются структурой msgbuf. Его описание находится в
linux/msg.h:
Тип сообщения, представленный натуральным числом. Он
обязан быть натуральным!
Собственно сообщение. Возможность приписывать тип конкретному сообщению позволяет
держать в одной очереди разнородные сообщения. Это может
понадобиться, например, когда сообщения процесса-клиента помечаются одним
магическим числом, а сообщения сообщения процесса-сервера другим; или
приложение ставит в очередь сообщения об ошибках с типом 1, сообщения-запросы
с типом 2 и т.д. Ваши возможности просто безграничны.
С другой стороны, старайтесь дать наглядное имя элементу данных
сообщения (в примере был mtext). В это поле можно записывать не
только массивы символов, но и вообще любые данные в любой форме. Поле
действительно полностью произвольно, поэтому вся структура может быть
переопределена программистом, например, так:
Существует, однако, ограничение на максимальный размер сообщения.
В LINUX оно определено в linux/msg.h:
Ядро хранит сообщения в очереди структуры msg. Она определена в
linux/msg.h:
Указатель на следующее сообщение в очереди. Сообщения объединены
в односвязный список и находятся в адресном пространстве ядра.
Тип сообщения, каким он был объявлен в msgbuf.
Указатель на начало тела сообщения.
Длина текста (или тела) сообщения. Каждый из трех типов IPC-объектов имеет внутреннее представление, которое
поддерживается ядром. Для очередей сообщений это структура msqid_ds.
Ядро создает, хранит и сопровождает образец такой структуры для каждой
очереди сообщений в системе. Она определена в linux/msg.h
следующим образом:
Экземпляр структуры ipc_perm, определенной в linux/ipc.h
. Она содержит информацию о доступе для очереди сообщений, включая права
доступа и информацию о создателе сообщения (uid и т.п.).
Ссылка на первое сообщение в очереди (голова списка).
Ссылка на последний элемент списка (хвост списка).
Момент времени (time_t) посылки последнего сообщения из
очереди.
Момент времени последнего изъятия элемента из очереди.
Момент времени последнего изменения, проделанного в очереди
(подробнее об этом позже).
и
Число байт, стоящих в очереди (суммарный размер всех сообщений).
Количество сообщений в очереди на настоящий момент.
Максимальный размер очереди.
PID процесса, пославшего последнее в очереди сообщение.
PID последнего процесса, взявшего из очереди сообщение. Информацию о доступе к IPC-объектам ядро хранит в структуре
ipc_perm. Например, описанная выше структура очереди сообщений
содержит одну структуру типа ipc_perm в качестве элемента. Следующее
ее определение дано в linux/ipc.h.
ЗАМЕЧАНИЕ:Все это в плане системной безопасности может иметь
принципиальное значение, и хорошо рассмотрено в книге Richard Stevens'
UNIX Network Programming, pp. 125.
Системный вызов msgget() нужен для того, чтобы создать очередь
сообщений или подключиться к существующей.
Создает очередь, если она не была создана ранее.
При использовании совместно с IPC_CREAT, приводит к неудаче если
очередь уже существует. Вызов msgget() с IPC_CREAT, но без IPC_EXCL
всегда выдает идентификатор (существующей с таким ключом или созданной)
очереди. Использование IPC_EXCL вместе с IPC_CREAT либо
создает новую очередь, либо, если очередь уже существует, заканчивается
неудачей. Самостоятельно IPC_EXCL бесполезен, но вместе c
IPC_CREAT он дает гарантию, что ни одна из существующих очередей не
открывается для доступа.
Восьмеричный режим может быть OR-нут в маску доступа. Каждый
IPC-объект имеет права доступа, аналогичные правам доступа к файлу в
файловой системе UNIX!
Напишем оберточную функцию для открытия или создания очереди сообщений:
Получив идентификатор очереди, мы можем выполнять над ней
различные действия. Чтобы поставить сообщение в очередь, используйте
системный вызов msgsnd():
Аргумент msgflg может быть нулем или:
Если очередь переполнена, то сообщение не записывается в очередь,
и управление передается вызывающему процессу. Если эта ситуация не
обрабатывается вызывающим процессом, то он приостанавливается
(блокируется), пока сообщение не будет прочитано. Напишем еще одну оберточную функцию для посылки сообщения:
Теперь, когда мы имеем сообщение в очереди, попытайтесь при помощи
ipcs посмотреть на статус нашей очереди. Обсудим, как забрать из
очереди сообщение. Для этого используется системный вызов msgrcv():
Если IPC_NOWAIT был послан флагом, и нет ни одного
удовлетворительного сообщения, msgrcv вернет вызывающему процессу ENOMSG. В
противном случае вызывающий процесс блокируется, пока в очередь не прибудет
сообщение, соответствующее параметрам msgrcv(). Если, пока клиент
ждет сообщения, очередь удаляется, то ему возвращается EIDRM. EINTR
возвращается, если сигнал поступил, пока процесс находился на промежуточной
стадии между ожиданием и блокировкой.
Давайте рассмотрим функцию-переходник для изъятия сообщения из
нашей очереди.
Бит MSG_NOERROR в msgflg предоставляет некоторые
дополнительные возможности. Если физическая длина сообщения больше, чем
msgsz, и MSG_NOERROR установлен, то сообщение обрезается и
возвращается только msgsz байт. Нормальный же msgrcv()
возвращает -1 (E2BIG), и сообщение остается в очереди до последующих
запросов. Такое поведение можно использовать для создания другой оберточной
функции, которая позволит нам "подглядывать" внутрь очереди, чтобы узнать,
пришло ли сообщение, удовлетворяющее нашему запросу.
Благодаря использованию функций-переходников вы имеете некий
элегантный подход к созданию и использованию очередей сообщений в
ваших приложениях. Теперь коснемся непосредственно манипулирования
внутренними структурами, связанными с данной очередью сообщений.
Для осуществления контроля над очередью предназначен системный
вызов msgсtl().
Сохраняет по адресу buf структуру msqid_ds для очереди сообщений.
Устанавливает значение элемента ipc_perm структуры msqid.
Значения выбирает из буфера.
Удаляет очередь из ядра. Вернемся к нашему разговору о внутреннем представлении очереди
сообщений: msqid_ds. Ядро держит экземпляр этой структуры для каждой
очереди, существующей в системе. IPC_STAT дает возможность заиметь
копию такой структуры для испытаний. Посмотрим на оберточную функцию,
которая берет эту структуру и размещает копию по указанному адресу:
Что же мы можем делать с полученной копией структуры?
Единственное, что можно поменять, это элемент ipc_perm. Это права
доступа очереди, информация о создателе и владельце очереди. Однако и
отсюда менять позволено только mode, uid и gid.
Давайте напишем оберточную функцию, изменяющую режим доступа очереди.
Режим должен быть передан как массив символов (например, "660").
ОСТОРОЖНО! Изменяя права доступа, можно случайно лишить прав себя
самого! Помните, что IPC-объекты не исчезают, пока они не уничтожены должным
образом или не перезагружена система. Поэтому то, что Вы не видите очереди
ipcs, не означает, что ее нет на самом деле.
После того, как сообщение взято из очереди, оно удаляется. Однако, как
отмечалось ранее, IPC-объекты остаются в системе до персонального удаления
или перезапуска всей системы. Поэтому наша очередь сообщений все еще
существует в ядре и пригодна к употреблению в любое время, несмотря на то,
что последнее его соообщение уже давно на небесах. Чтобы и нашу очередь
с миром отправить туда же, нужен вызов msgctl(), использующий
команду IPC_RMID:
Мало кто станет отрицать непосредственную выгоду от возможности в
любой момент получить точную техническую информацию. Подобные
материалы представляют собой мощный механизм для обучения и
исследования новых областей. Однако, неплохо было бы добавить к
технической информации и реальные примеры. Это непременно ускорит и
укрепит процесс обучения.
До сих пор все то хорошее, что мы сделали, это оберточные
функции для манипуляций с очередями сообщений. Хотя они чрезвычайно
полезны, ими неудобно пользоваться для дальнейшего обучения и
экспериментов. Существует средство, позволяющее работать с IPC-очередями из
командной строки: msgtool. Хотя msgtool будет использован в
целях обучения, он пригодится и реально при написании скриптов.
Поведение msgtool зависит от аргументов командной строки, что
удобно для вызова из скрипта shell. Позволяет делать все что угодно,
от создания, посылки и получения сообщений до редактирования прав
доступа и удаления очереди. Изначально данными сообщений могут быть
только литерные массивы. Упражнение: измените это так, чтобы можно
было посылать и другие данные.
Следующее, что мы рассмотрим, это исходный текст msgtool. Его
следует компилировать в версии системы, которая поддерживает System V IPC.
Убедитесь в наличии System V IPC в ядре, когда будете собирать программу!
На полях отметим, что наша утилита будет всегда создавать
очередь, если ее не было.
Семафоры лучше всего предствлять себе как счетчики, управляющие
доступом к общим ресурсам. Чаще всего они используются как блокирующий
механизм, не позволяющий одному процессу захватить ресурс, пока этим
ресурсом пользуется другой. Семафоры часто подаются как наиболее
трудные для восприятия из всех трех видов IPC-объектов. Для полного
понимания, что же такое семафор, мы их немного пообсуждаем, прежде чем
переходить к системным вызовам и операционной теории.
Слово семафор в действительности является старым
железнодорожным термином, соответствующим "рукам", не дающим
траекториям каров пересекаться на перекрестках. То же самое можно сказать и
про семафоры. Семафор в положении ON (руки пондяты вверх) если
ресурс свободен и в положении OFF (руки опущены) если ресурс
недоступен (надо ждать).
Этот пример неплохо показал суть работы семафора, однако важно
знать, что в IPC используются множества семафоров, а не отдельные
экземпляры. Разумеется, множество может содержать и один семафор, как
в нашем железнодорожном примере.
Возможен другой подход к семафорам: как к счетчикам ресурсов.
Приведем другой пример из жизни. Вообразим себе спулер, управляющий
несколькими принтерами, каждый из которых обрабатывает по нескольку
заданий. Гипотетический менеджер печати будет использовать множество
семафоров для установления доступа к каждому из принтеров.
Предположим, что в комнате имеются 5 работающих принтеров. Наш
менеджер конструирует 5 семафоров: по одному на каждый принтер. Поскольку
каждый принтер может обрабатывать только по одному запросу за раз, все
семафоры устанавливаются в 1, что означает готовность всех принтеров.
John послал запрос на печать. Менеджер смотрит на семафоры и
находит первый из них со значением 1. Перед тем, как запрос John
попадет на физическое устройство, менеджер печати уменьшит
соответствующий семафор на 1. Теперь значение семафора есть 0. В мире
семафоров System V нуль означает стопроцентную занятость ресурса на
семафоре. В нашем примере на принтере не будет ничего печататься, пока
значение семафора не изменится.
Когда John напечатал все свои плакаты, менеджер печати
увеличивает семафор на 1. Теперь его значение вновь равно 1 и
принтер может принимать задания снова.
Не смущайтесь тем, что все семафоры инициализируются единицей.
Семафоры, трактуемые как счетчики ресурсов, могут изначально
устанавливаться в любое натуральное число, не только в 0 или 1. Если
бы наши принтеры умели печатать по 500 документов за раз, мы могли бы
проинициализировать семафоры значением 500, уменьшая семафор на 1 при
каждом поступающем задании и увеличивая после его завершения. Как вы
увидите в следующей главе, семафоры имеют очень близкое отношение к
разделяемым участкам памяти, играя роль сторожевой собаки, кусающей
нескольких писателей в один и тот же сегмент памяти (имеется в виду
машинная память).
Перед тем, как копаться в системных вызовах, коротко пробежимся
по внутренним структурам данных, с которыми имеют дело семафоры.
Так же, как и для очередей сообщений, ядро отводит часть своего
адресного пространства под структуру данных каждого множества
семафоров. Структура определена в linux/sem.h:
Это пример структуры ipc_perm, которая описана в
linux/ipc.h. Она содержит информацию о доступе к множеству
семафоров, включая права доступа и информацию о создателе множества (uid и
т.д.).
Время последней операции semop() (подробнее чуть позже).
Время последнего изменения структуры.
Указатель на первый семафор в массиве.
Число запросов undo в массиве (подробнее чуть позже).
Количество семафоров в массиве. В sem_ds есть указатель на базу массива семафоров. Каждый элемент
массива имеет тип sem, который описан в linux/sem.h:
ID процесса, проделавшего последнюю операцию
Текущее значение семафора
Число процессов, ожидающих освобождения требуемых ресурсов
Число процессов, ожидающих освобождения всех ресурсов Системный вызов semget() используется для того, чтобы создать
новое множество семафоров или получить доступ к старому.
Создает множество семафоров, если его еще не было в системе.
При использовании вместе с IPC_CREAT вызывает ошибку, если
семафор уже существует. Если IPC_CREAT используется в одиночку, то semget()
возвращает идентификатор множества семафоров: вновь созданного или с таким же
ключом. Если IPC_EXCL используется совместно с IPC_CREAT,
то либо создается новое множество, либо, если оно уже существует, вызов
приводит к ошибке и -1. Сам по себе IPC_EXCL бесполезен, но вместе с
IPC_CREAT он дает средство гарантировать, что ни одно из
существующих множеств семафоров не открыто для доступа.
Как и в других частях System V IPC, восьмеричный режим доступа может быть
OR-нут в маску для формирования доступа к множеству семафоров.
Аргумент nems определяет число семафоров, которых требуется
породить в новом множестве. Это количество принтеров в нашей комнате.
Максимальное число семафоров определяется в "linux/sem.h":
Заметьте, что аргумент nsems игнорируется, если Вы открываете
существующее множество семафоров.
Напишем функции-переходники для открытия и создания множества семафоров:
Аргумент sops указывает на массив типа sembuf. Эта
структура описана в linux/sem.h следующим образом:
Номер семафора, с которым вы собираетесь иметь дело.
Выполняемая операция (положительное, отрицательное число или нуль).
Флаги операции. Если sem_op отрицателен, то его значение вычитается из семафора.
Это соответствует получению ресурсов, которые контролирует семафор. Если
IPC_NOWAIT не установлен, то вызывающий процесс засыпает, пока семафор
не выдаст требуемое количество ресурсов (пока другой процесс не освободит их).
Если sem_op положителен, то его значение добавляется к семафору.
Это соответствует возвращению ресурсов множеству семафоров приложения.
Ресурсы всегда нужно возвращать множеству семафоров, если они больше
не используются!
Наконец, если sem_op равен нулю, то вызывающий процесс будет
усыплен (sleep()), пока значение семафора не станет нулем. Это
соответствует ожиданию того, что ресурсы будут использованы на 100%.
Хорошим примером был бы демон, запущенный с суперпользовательскими
правами, динамически регулирующий размеры множества семафоров, если
оно достигло стопроцентного использования.
Чтобы пояснить вызов semop, вспомним нашу комнату с принтерами.
Пусть мы имеем только один принтер, способный выполнять только одно
задание за раз. Мы создаем множество семафоров из одного семафора
(только один принтер) и устанавливаем его начальное значение в 1
(только одно задание за раз).
Каждый раз, посылая задание на принтер, нам нужно сначала
убедиться, что он свободен. Мы делаем это, пытаясь получить от
семафора единицу ресурса. Давайте заполним массив sembuf, необходимый
для выполнения операции:
Когда задание на принтере выполнится, мы должны вернуть ресурсы
обратно множеству семафоров, чтобы принтером могли пользоваться другие.
Оба системных вызова используют аргумент cmd для определения
команды, которая будет выполнена над IPC-объектом. Оставшаяся разница
заключается в последнем аргументе. В msgctl он представляет копию
внутренней структуры данных ядра. Повторим, что мы используем эту
структуру для получения внутренней информации об очереди сообщений
либо для установки или изменения прав доступа и владения очередью. Для
семафоров поддерживаются дополнительные команды, которые требуют
данных более сложного типа в последнем аргументе. Использование объединения
(union) огорчает многих новичков до состояния %(. Мы очень
внимательно разберем эту структуру, чтобы не возникало никакой путаницы.
Первый аргумент semctl() является ключом (в нашем случае
возвращаемым вызовом semget). Второй аргумент (semun), это
номер семафора, над которым совершается операция. По существу, он может быть
понят как индекс на множестве семафоров, где первый семафор представлен
нулем (0).
Аргумент cmd представляет собой команду, которая будет выполнена
над множеством. Как вы можете заметить, здесь снова присутствуют
IPC_STAT/IPC_SET вместе с кучей дополнительных команд, специфичных для
множеств семафоров:
Берет структуру semid_ds для множества и запоминает ее по адресу
аргумента buf в объединении semun.
Устанавливает значение элемента ipc_perm структуры semid_ds для
множества.
Удаляет множество из ядра.
Используется для получения значений всех семафоров множества.
Целые значения запоминаются в массиве элементов unsigned short, на
который указывает член объединения array.
Выдает число процессов, ожидающих ресурсов в данный момент.
Возвращает PID процесса, выполнившего последний вызов semop.
Возвращает значение одного семафора из множества.
Возвращает число процессов, ожидающих стопроцентного освобождения
ресурса.
Устанавливает значения семафоров множества, взятые из элемента
array объединения.
Устанавливает значение конкретного семафора множества как элемент
val объединения. Аргумент arg вызова semсtl() является примером
объединения semun, описанного в linux/sem.h
следующим образом:
Определяет значение, в которое устанавливается семафор командой SETVAL.
Используется командами IPC_STAT/IPC_SET. Представляет копию
внутренней структуры данных семафора, находящейся в ядре.
Указатель для команд GETALL/SETALL. Ссылается на массив целых,
используемый для установки или получения всех значений семафоров в
множестве. Оставшиеся аргументы __buf и __pad предназначены
для ядра и почти, а то и вовсе не нужны разработчику приложения. Эти два
аргумента специфичны для LINUX, их нет в других UNIX-системах.
Поскольку этот особенный системный вызов наиболее сложен для
восприятия среди всех системных вызовов System V IPC, мы рассмотрим
несколько его примеров в действии.
Следующий отрывок выдает значение указанного семафора. Последний
аргумент (объединение) игнорируется, если используется команда GETVAL.
Вспомним из описания msgtool, что команды IPC_STAT и IPC_SET
изменяют информацию о доступе к очереди. Хотя эти же команды
поддерживаются и для семафоров их употребление несколько отличается,
поскольку внутренняя структура данных берется и копируется с элемента
объединения, а не является отдельным объектом. Можете ли вы найти
ошибку в следующем коде?
Вспомним, что команды IPC_SET/IPC_STAT используют элемент buf
объединения, который является указателем на тип semid_ds. Указатели
это указатели, и ничего кроме указателей! Элемент buf должен
ссылаться на некий корректный участок памяти, чтобы наша функция работала как
полагается. Рассмотрим исправленную версию:
Поведение semtool() зависит от аргументов командной строки, что
удобно для вызова из скрипта shell. Позволяет делать все, что угодно, от
создания и манипулирования до редактирования прав доступа и удаления
множества семафоров. Может быть использовано для управления разделяемыми
ресурсами через стандартные скрипты shell.
В дополнение к semtool, приведем исходный текст
программы-компаньона semstat. Она выводит на экран значение каждого
из семафоров множества, созданного посредством semtool.
Разделяемая память может быть наилучшим образом описана как
отображение участка (сегмента) памяти, которая будет разделена между
более чем одним процессом. Это гораздо более быстрая форма IPC, потому
что здесь нет никакого посредничества (т.е. каналов, очередей
сообщений и т.п.). Вместо этого, информация отображается
непосредственно из сегмента памяти в адресное пространство вызывающего
процесса. Сегмент может быть создан одним процессом и впоследствии
использован для чтения/записи любым количеством процессов.
Давайте взглянем на структуру данных, поддерживаемую ядром, для
разделяемых сегментов памяти.
Так же, как для очередей сообщений и множеств семафоров, ядро
поддерживает специальную внутреннюю структуру данных для каждого
разделяемого сегмента памяти, который существует врутри его адресного
пространства. Такая структура имеет тип shmid_ds и определена в
linux/shm.h как следующая:
Это образец структуры ipc_perm, который определен в
linux/ipc.h. Он содержит информацию о доступе к сегменту, включая
права доступа и информацию о создателе сегмента (uid и т.п.).
Размеры сегмента (в байтах).
Время последней привязки к сегменту.
Время последней отвязки процесса от сегмента.
Время последнего изменения этой структуры (изменение mode и т.п.).
PID создавшего процесса.
PID последнего процесса обратившегося к сегменту.
Число процессов, привязанных к сегменту на данный момент. Чтобы создать новый разделяемый сегмент памяти или получить
доступ к уже существующему, используется системный вызов shmget().
Первый аргумент для shmget() это значение ключа (в нашем
случае возвращен посредством вызова ftok()). Это значение ключа
затем сравнивается с существующими значениями, которые находятся внутри ядра
для других разделяемых сегментов памяти. В этом отношении операция открытия
или получения доступа зависит от содержания аргумента shmflg.
Создает сегмент, если он еще не существует в ядре.
При использовании совместно с IPC_CREAT приводит к ошибке, если
сегмент уже существует. Если используется один IPC_CREAT, то shmget() возвращает
либо идентификатор для вновь созданного сегмента, либо идентификатор для
сегмента, который уже существует с тем же значением ключа. Если вместе
с IPC_CREAT используется IPC_EXCL, тогда либо создается
новый сегмент, либо, если сегмент уже существует, вызов проваливается с -1.
IPC_EXCL сам по себе бесполезен, но если он комбинируется с
IPC_CREAT, то может быть использован как способ получения гарантии,
что нет уже существующих сегментов, открытых для доступа.
Повторимся: необязательный восьмеричный доступ может быть объеденен по
OR в маску доступа.
Давайте создадим функцию-переходник для обнаружения или создания
разделяемого сегмента памяти:
Как только процесс получает действующий идентификатор IPC для
выделяемого сегмента, следующим шагом является привязка или размещение
сегмента в адресном пространстве процесса.
Кроме того, если устанавливается флаг SHM_RDONLY, то разделяемый
сегмент памяти будет распределен, но помечен readonly.
Этот вызов, пожалуй, наиболее прост в использовании. Рассмотрим
функцию-переходник, которая по корректному идентификатору сегмента
возвращает адрес привязки сегмента:
Берет структуру shmid_ds для сегмента и сохрает ее по адресу,
указанному buf.
Устанавливает значение ipc_perm-элемента структуры shmid_ds. Сами
величины берет из аргумента buf.
Помечает сегмент для удаления. Команда IPC_RMID в действительности не удаляет сегмент из ядра, а только
помечает для удаления. Настоящее же удаление не происходит, пока последний
процесс, привязанный к сегменту, не "отвяжется" от него как следует. Конечно,
если ни один процесс не привязан к сегменту на данный момент, удаление
осуществляется немедленно.
Снятие привязки производит системный вызов shmdt.
Наш последний пример объектов System V IPC, shmtool: средство
командной строки для создания, чтения, записи и удаления разделяемых
сегментов памяти. Так же, как и в предыдущий примерах, во время исполнения
любой операции сегмент создается, если его прежде не было.
ПК имеет как минимум одно звуковое устройство: встроенный
динамик (PC speaker). Вы также можете купить звуковую карту для
вашего ПК, обладающую большими звуковыми возможностями (см. Linux
Sound User's Guide или Sound-HOWTO для поддерживаемых звуковых карт).
Встроенный динамик часть консоли Linux и, поэтому является
символьным устройством. Как следствие, существуют запросы ioctl для
манипуляций с ним:
Генерирует сигнал beep заданной длительности, используя таймер ядра.
Пример: ioctl (fd, KDMKTONE,(long) argument).
Генерирует бесконечный beep или прерывает звучащий в настоящий момент.
Пример: ioctl(fd,KIOCSOUND,(int) tone). Третий аргумент первого примера содержит значение тона в нижнем
слове и сдвиг в верхнем. Тон это не частота. Таймер 8254 материнской
платы ПК заведен на 1.19 МГц и поэтому тон = 1190000/частота.
Сдвиг измеряется в шагах таймера. Оба вызова срабатывают немедленно,
поэтому вы можете порождать звуковые сигналы, не блокируя программу.
Для вас как для программиста важно знать, что современные
Linux-системы имеет встроенную звуковую карту. Один путь проверки:
испытать /dev/sndstat. Если открытие /dev/sndstat закончилось неудачно
и errno=ENODEV, то это означает, что ни один звуковой драйвер не
активирован, и вы не получите никакой помощи от звукового драйвера
ядра. Тот же результат может иметь попытка открыть /dev/dsp, если он
не связан с драйвером pcsnd.
Если вы хотите повозиться со звуковыми картами на уровне железа,
знайте, что комбинация вызовов outb() и inb() определит звуковую
карту, которую вы ищете.
Программы, использующие звуковой драйвер, будут хорошо
переноситься на другие i386 системы, поскольку некоторые умные люди
решили использовать один драйвер для Linux, isc, FreeBSD и большинства
других i386 архитектур. Звуковая карта уже не часть консоли Linux, а
специальное устройство. Звуковая карта предлагает три основных возможности:
Каждая из возможностей имеет свой драйвер устройства. Для
цифровых сэмплов это /dev/dsp, для модуляции частоты /dev/sequencer,
для midi интерфейса /dev/midi. Звуковые установки (volume, balance,
bass) контролируются через интерфейс /dev/mixer. В целях сравнения
существует устройство /dev/audio, которое может читать звуковые данные
SUN-law, но с преобразованием в сэмплы цифрового устройства.
Вы были правы, если подумали, что нужно использовать ioctl() для
работы с этими устройствами. Запросы ioctl() определены в
Эта глава имеет дело с вводом/выводом символов на экран. Когда мы
говорим "символ", то подразумеваем композицию пикселов, которая может
меняться в зависимости от таблицы представлений символов (charset).
Ваша графическая карта уже предлагает одну или более таких таблиц и по
умолчанию работает в текстовом (charset) режиме, потому что текст
обрабатывается быстрее, чем пиксельная графика. Терминалы можно
использовать лучше, чем как простые и скучные текстовые дисплеи.
Рассмотрим, как использовать специальные возможности, которые
предлагает терминал Linux, особенно консоль Linux.
В следующих разделах мы рассмотрим, как пользоваться различными
пакетами доступа к терминалу. В Linux мы имеем GNU-версию termcap и
можем пользоваться ncurses вместо curses.
Функции printf(...) в libc обеспечивают форматированный вывод и
позволяют трансформировать аргументы.
format содержит два типа объектов:
Форматная информация должна начинаться с %, за которым следуют
значения для формата, дальше идет символ для трансляции (чтобы напечатать
знак %, используйте %%). Возможны следующие значения для
формата:
Возможные значения для трансформации смотри в таблице
8.1.
Точно так же, как printf(...) для форматированного вывода, Вы
можете использовать scanf(...) для форматированного ввода.
format может содержать правила форматирования аргументов (см.
таблицу 8.2. Он может также включать:
Библиотека termcap это API для базы данных termcap, которая
находится в /etc/termcap. Библиотечные функции позволяют:
Программы, использующие библиотеку termcap должны включать
termcap.h и собираться с:
Функции termcap терминально-независимые программы, но дают
программисту только низкоуровневый доступ к терминалу. Для пакета
более высокого уровня потребуется curses или ncurses.
Что касается buffer, то в GNU-версии Linux termcap не нужно
захватывать память. В других версиях вам придется выделить 2048 байт
(прежде buffer требовал 1024 байта, но сейчас размер удвоился).
tgetent(...) возвращает 1 в случае успеха и 0 когда база данных
найдена, но не имеет точки входа для TERM. Другие ошибки возвращают
различные значения.
Следующий пример объясняет как использовать tgetent(...):
По умолчанию termcap использует /etc/termcap как базу данных. Если
переменная среды TERMCAP установлена, например, в $HOME/mytermcap, то
все функции будут пользоваться mytermcap вместо /etc/termcap.
Без начального слэша в TERMCAP определенное значение понимается как имя для
терминала. Каждый фрагмент информации называется свойством (capability). Каждое
свойство это двухсимвольный код, за каждым двухсимвольным кодом стоит
значение свойства. Возможны следующие типы свойств:
Каждое свойство связано с единственным типом значений (co всегда
числовой, hc всегда флаг, а st всегда строка). Три типа
значений и три типа функций, их запрашивающих. char *name
двухсимвольный код свойства.
В этом разделе будем пользоваться следующей терминологией:
Обычно программа, использующая ncurses, выглядит так:
Подключение ncurses.h определит переменные и типы для ncurses,
такие как WINDOW, и прототипы функций. Автоматически подключатся
stdio.h, stdarg.h, termios.h и unctrl.h.
initscr() используется для инициализации структур данных ncurses
и для чтения файла terminfo. Будет захвачена память под stdscr и
curscr. Если произойдет ошибка, то initscr вернет ERR. В противном
случае возвращается указатель на stdscr. Кроме этого, экран будет
очищен и будут проинициализированы LINES и COLS.
endwin() очистит все выделенные ресурсы ncurses и восстановит
режимы tty, какими они были до вызова initscr(). Функция
endwin() должна вызываться перед любой другой функцией из библиотеки
ncurses и перед выходом из вашей программы. Если вы хотите использовать для
вывода более чем один терминал, используйте newterm(...) вместо
initscr().
Компилируйте программу посредством:
Вы можете устанавливать любые флаги (см. gcc(1)). Если путь к
ncurses.h изменился, вы должны включить следующую строку, иначе
ncurses.h, nterm.h, termcap.h и unctrl.h не будут найдены:
Другие возможные в Linux флаги: O2 скажет gcc произвести некоторую
оптимизацию; -ansi: для ANSI си-кода; -Wall выведет все
предупреждения; -m486 оптимизирует код для Intel 486 (можно и для
Intel 386).
Библиотека ncurses находится в /usr/lib. Существует 3 версии библиотеки:
Структуры данных для экрана называются windows и определены в
ncurses.h. Окно это нечто типа символьного массива в памяти, которым
программист может манипулировать без вывода на терминал. При помощи
newwin(...) вы можете создать другие окна.
Чтобы оптимально обновить физический терминал, ncurses имеет
другое окно, curscr. Это изображение, реально выводимое на экран. Для
отображения stdscr на экране используется функция refresh(). После
этого ncurses обновит curscr и физический терминал содержимым stdscr.
Библиотечные функции произведут внутреннюю оптимизацию для процесса
обновления, поэтому вы можете менять различные окна и затем обновлять
экран сразу самым оптимальным способом.
Функциями ncurses вы можете работать со структурой данных window.
Функции, начинающиеся с w, позволяют назначать окно window,
тогда как остальные обычно имеют дело с stdscr. Функции, начинающиеся с
mv, прежде всего переместят курсор на позицию y,x.
Символы имеют тип chtype, который является long unsigned int
, чтобы сохранять дополнительную информацию о себе (атрибуты и т.д.).
Библиотека ncurses использует базу данных terminfo. Обычно она
находится в usr/lib/terminfo/, и ncurses обращается туда за локальными
определениями терминала. Если вы хотите проверить некоторые другие
определения для терминала, не исправляя первоначальную terminfo,
установите соответственно переменную среды TERMINFO. Эта переменная
будет протестирована ncurses, и вместо usr/lib/terminfo/ сохранятся
ваши определения.
Текущей версией ncurses является (на момент написания книги) 1.8.6.
В конце этого раздела вы найдете обзорную таблицу для BSD-Curses,
ncurses и Sun-OS 5.4 curses.
initscr() прочитает terminfo файл и установит структуры данных
ncurses, выделит память для curscr и stdscr и проинициализирует переменные
LINES и COLS значениями, соответствующими вашему терминалу. Будет возвращен
указатель на stdscr или ERR в случае ошибки. Вам НЕ нужно
инициализировать указатель.
Дополнительный вызов refresh() после endwin() восстановит
содержимое терминала, отображавшееся до вызова initscr()
(visual-mode), в противном случае экран будет очищен (non-visual-mode).
Окна могут быть созданы, уничтожены, перемещены, скопированы,
задублированы и т.д.
Верхний левый угол нашего окна находится в строке 10 в колонке
10; окно имеет 10 строк и 60 колонок. Если nlines равна нулю, окно
будет иметь (LINES-begy) строк. Точно так же, если ncols
равна нулю, то окно будет иметь (COLS-begx) колонок.
Когда мы вызываем newwin(...) с нулевыми аргументами:
При помощи LINES и COLS мы можем открыть окно на
середине экрана, какую бы размерность оно ни имело:
Откроется окно с 22 строками и 70 колонками в середине экрана.
Проверьте размер экрана перед тем, как открывать окно. Консоль Linux
имеет не менее 25 строк и 80 колонок, но на Х-терминалах это может не
выполняться (их размеры изменяемы).
С другой стороны, используйте LINES и COLS, чтобы
поместить два окна в один экран:
Подробнее смотрите screen.c в директории с примерами.
begx и begy относятся не к origwin, а к экрану.
В libc printf() используется для форматированного вывода. Вы
можете определять выводимую строку и включать в нее переменные
различных типов. Подробнее смотрите раздел 8.1.1.
vwprintw(...) требует подключения varargs.h. y и x координаты, на которые переместится курсор перед
вставкой str; n это число вставляемых символов
(n=0 вводит чистую строку). y и x координаты, на которые курсор переместится перед
удалением.
Рисунок 8.2: Символы бокса в ncurses.
С включенной keypad(...) при нажатии функциональной клавиши
getch() вернет код, определенный в ncurses.h как макрос KEY_*. При
нажатии ESCAPE (который может быть началом функциональной клавиши)
ncurses запустит односекундный таймер. Если остаток не получен в
течение этой секунды, то возвращается ESCAPE, иначе значение
функциональной клавиши. При необходимости секундный таймер можно
отключить через notimeout().
Поговорим об использовании опций окна и режимов терминала.
Прежде всего, под Linux следует подключить keypad. Это
позволит пользоваться цифровым блоком и клавишами перемещения курсора
на клавиатуре ПК.
Теперь имеется 2 основных типа ввода:
Для первого случая мы установим следующие опции и режимы, и цикл
while сработает корректно:
Эта программа повиснет до нажатия клавиши. Если нажата q, мы
вызываем your_quit_function(), иначе ждем другого ввода.
Выражение switch может быть расширено по нашему желанию. Макросы
KEY_* служат для учета специальных клавиш. Например,
Для второго случая, нам достаточно установить echo(), и символы,
набираемые пользователем, будут напечатаны на экране. Место печати
задается функциями move(...) или wmove(...).
Или вы можете открыть окно с маской (выделяемой другими цветами)
и попросить пользователя ввести строку:
Более подробно см. input.c в каталоге примеров.
Проблема состоит в том, что ncurses иногда делает совершенно
бесполезными атрибуты окна, когда заполняет экран пробелами. Если в
lib_clrtoeol.c BLANK определен как
Как написано в обзоре, окна ncurses есть отображения в памяти.
Это означает, что любое изменение окна не отражается на физическом
экране до тех пор, пока не будет произведено обновление. Это
оптимизирует вывод на экран, поскольку вы получаете возможность
совершать множество действий, а затем лишь единожды вызвать
обновление, чтобы напечатать его на экране. В противном случае на терминале
отражалось бы каждое изменение, что ухудшало бы исполнение ваших программ.
Допустим, мы имеем следующую программу с двумя окнами. Мы
изменяем оба окна, меняя несколько линий текста. Напишем
cgangewin(win) с wrefresh(win).
Тогда ncurses обновит терминал дважды, а это замедлит исполнение
нашей программы. Благодаря doupdate() мы изменим changewin(win)
и нашу основную функцию, добившись этим лучшего исполнения.
wtouchln() захватит n линий, начинающихся в y.
Если change соответствует TRUE, то линии захватываются, в противном
случае нет (изменяются или не изменяются).
untouchwin(win) пометит окно win как неизмененное со
времени последнего вызова refresh().
Атрибуты это специальные возможности терминала, применяемые во время
печати символов на экран. Символы могут быть напечататы жирно, могут быть
подчеркнуты, могут мигать и т.д. В ncurses вы имеете возможность включать или
отключать атрибуты для достижения наилучшего внешнего вида вывода. Возможные
атрибуты перечислены в нижеследующей таблице.
Ncurses определяет 8 цветов, которыми вы можете пользоваться на
терминале с цветовой поддержкой. Сначала проинициализируйте цветовые
структуры данных посредством start_color(), затем проверьте
возможности терминала при помощи has_colors(). start_color() будет
инициализировать COLORS, наибольшее количество цветов, поддерживаемых
терминалом, и COLOR_PAIR, максимальное число цветовых пар, которые вы
можете определить.
Атрибуты могут быть совмещены '|' (OR), поэтому вы можете
получить четкий мерцающий вывод при помощи A_BOLD|A_BLINK
Если вы установите окно с атрибутом attr, все символы,
напечатанные в этом окне, приобретут это свойство и будут его
сохранять до изменения вами атрибута. Это не будет утеряно при
прокрутке или движении окна и т.п.
Будьте осторожны с цветами, если вы пишете программы для ncurses
и BSD curses, так как BSD curses не имеет цветовой поддержки. (Точно
так же не имеют цветовой поддержки старые версии SYS V). Поэтому, если
вы компилируете для обеих библиотек, придется использовать операции #ifdef.
Как комбинировать атрибуты и цвета? Некоторые терминалы, как
консоли в Linux, имеют цвета, а некоторые нет (xterm, vs100 и
т.д.). Следующий код решит эту проблему:
Прежде всего, функция CheckColor проинициализирует цвета при
помощи start_color(). Затем has_colors() вернет TRUE, если
текущий терминал имеет цвета. После этого вызывается ini_tpair(...)
для соединения цветов переднего и заднего плана, и wattrset(...) для
установки этих цветов в данном окне. Впрочем, чтобы установить атрибуты для
черно-белого терминала, мы можем использовать только wattrset(...).
Чтобы получить цвета на xterm, лучший способ, найденный автором, это
использовать ansi_xterm с надерганными элементами terminfo из
Midnight Commander. Просто добудьте исходники ansi_xterm и Midnight
Commander (mc-x.x.tar.gz). Потом скомпилируйте ansi_xterm и
используйте tic с xterm.ti и vt100.ti из архива mc-x.x.tar.gz.
Запустите ansi_xterm и протестируйте его.
При включенном scrollok(...) содержимое окна может быть
прокручено при помощи нижеследующих функций. Замечание: оно будет
прокручено и в случае, если вы напечатаете новую строку, находясь в
последней строке окна, поэтому будьте осторожны со scrollok(...).)
Следующий код объяснит, как прокручивать текст на экране. Смотрите также
type.c в директории примеров.
Мы хотим прокрутить текст в окне, имеющем 18 линий и 66 колонок. S[]
это массив символов с текстом. Max_s является номером последней
строки в S[]. Clear_line напечатает пробелы с текущей
позиции курсора до конца строки, используя текущие атрибуты окна (не
A_NORMAL, как это делает clrtoeol). Beg это последняя строка из
s[], изображенная на данный момент на экране. Scroll перечень
того, что должна сделать функция, показать NEXT или PREVious (следующую или
предыдущую) строку текста.
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
Вперед: Про этот документ
Оглавление: Оглавление
Назад: Перенос прикладных
программ в Linux
11 Справочник системных вызовов
(в алфавитном порядке)
_exit как exit, только с меньшими возможностями
(m+c) accept установка связи на сокете (m+c!) access проверка прав доступа пользователя к файлу (m+c)
adjtimex установка/получение переменных времени ядра (-c)
afs_syscall зарезервированный системный вызов файловой
системы andrew (-) alarm посылает SIGALARM в назначенное время (m+c) bdflush сбрасывает буфер на диск (-c) bind назначает сокет для межпроцессовой коммуникации (m!c)
brk изменяет размеры сегмента данных (mc) chdir изменяет рабочую директорию (m+c) chmod изменяет атрибуты файла (m+c) chown изменяет владение файлом (m+c) chroot устанавливает новую корневую директорию (mc) clone см. fork (m-) close закрывает файл по ссылке (m+c) connect связывает 2 сокета (m!c) creat создание файла (m+c) creat_module захватывает память для загружаемого модуля ядра
(-) delete_module выгружает модуль ядра (-) dup дублирует файловый дескриптор (m+c) dup2 дублирует файловый дескриптор (m+c) execl, execlp, execle, ... см. execve (m+!c) execve исполняет файл (m+c) exit завершает программу (m+c) fchdir изменяет рабочую директорию по ссылке () fchmod см. chmode (mc) fchown изменяет владение файлом (mc) fclose закрывает файл по ссылке (m+!c) fcntl управление файлом/файловым дескриптором (m+c) flock изменение запирания файла (m!c) fork порождение потомка (m+c) fpathconf получение информации о файле по ссылке (m+!c)
fread чтение массива двоичных данных из потока (m+!c)
fstat получение статуса файла (m+c) fstatus получение статуса файловой системы по ссылке (mc)
fsync запись кэша файла на диск (mc) ftime интервал времени + секунды с 1.1.1970 (m!c) ftruncate изменение размеров файла (mc) fwrite запись массива двоичных данных в поток (m+!c)
get_kernel_syms получение символьной таблицы ядра или ее
размеры (-) getdomainname получение имени системной области (m!c)
getdtablesize получение размеров таблицы файлового
дескриптора (m!c) getegid получение эффективного id группы (m+c) geteuid получение эффективного id пользователя (m+c)
getgid получение id группы (m+c) getgroups получение дополнительных групп (m+c) gethostid получение уникального идентификатора основной
системы (m!c) gethostname получение имени основной системы (m!c) getitimer получение значения интервального таймера (mc)
getpagesize получение размеров страницы в системе (m-!c)
getpeername получение имени присоединенного равного сокета
(m!c) getpgid получение id группы родительского процесса (+c)
getpgrp получение id группы родителя текущего процесса (m+c)
getpid получение id текущего процесса (m+c) getppid получение id родительского процесса (m+c) getpriority получение приоритета (процесса, группы,
пользователя) (mc) getrlimit получение лимита ресурсов (mc) getrusage сводка ресурсов (m) getsockname получение адреса сокета (m!c) getsockopt получение установок опций сокета (m!c) gettimeofday получение времени дня с 1.1.1970 (mc) getuid получение действительного id пользователя (m+c)
gtty пока не реализован () idle делает процесс кандидатом на свопинг (mc) init_module вставка загружаемого модуля ядра (-) ioctl работа с символьным устройством (mc) ioperm установка некоторых прав на ввод/вывод из порта (m-c)
iopl установка всех прав на ввод/вывод из порта (m-c)
ipc межпроцессовая коммуникация (-c) kill посылает сигнал процессу (m+c) killpg посылает сигнал группе процесса (mc!) klog см. syslog (-!) link создание жесткой ссылки на существующий файл (m+c)
listen прослушивание связей сокета (m!c) llseek lseek для больших файлов lock пока не реализован () lseek изменение позиции ptr файлового дескриптора (m+c)
lstat получение статуса файла (mc) mkdir создание директории (m+c) mknod создание устройства (mc) mmap отображение файла в память (mc) modify_ldt чтение или запись локальной таблицы дескриптора
(-) mount монтирование файловой системы (mc) mprotect чтение, запись или исполнение для защищенной
памяти (-) msgctl управление сообщением ipc (m!c) msgget получение id очереди сообщений ipc (m!c) msgrcv получение сообщения ipc (m!c) msgsnd посылка сообщение ipc (m!c) munmap удаление отображения файла из памяти (mc) nice изменение приоритета процесса (mc) open открытие файла (m+c) pathconf получение информации о файле (m+!c) pause ждет до сигнала (m+c) personality получение текущей области исполнения для ibcs (-)
pipe создание канал (m+c) prof пока не реализован () profil исполнение временн'ого профиля (m!c) ptrace трассировка потомка (mc) quotactl пока не реализован () read чтение данных из файла (m+c) readv чтение блоков данных с файла (m!c) readdir чтение директории (m+c) readlink получение содержимого символической связи (mc)
reboot перезапуск или завтрак в кратере действующего вулкана
(-mc) recv получение сообщения из присоединенного сокета (m!c)
recvfrom получение сообщения из сокета (m!c) rename перемещение/переименование файла (m+c) rmdir удаление пустой директории (m+c) sbrk см. brk (mc!) select усыпление до действия над файловым дескриптором (mc)
semctl управление семафором ipc (m!c) semget ipc выдает идентификатор множества семафоров (m!c)
semop операция ipc над членами множества семафоров (m!c)
send посылка сообщения в присоединенный сокет (m!c) sendto посылка сообщения в сокет (m!c)
setdomainname установка имени системной области (mc)
setfsgid установка id группы файловой системы () setfsuid установка id группы пользователя файловой системы ()
setgid установка действительного id группы (m+c) setgroups установка дополнительных групп (mc) sethostid установка уникального идентификатора основной
системы (mc) sethostname установка имени основной системы (mc) setitimer установка интервального таймера (mc) setpgid установка идентификатора группы процесса (m+c)
setpgrp не имеет никакого эффекта (mc!) setpriority установка приоритета (процесса, группы,
пользователя) (mc) setregid установка действительного и эффективного
идентификатора группы (mc) setreuid установка действительного и эффективного
идентификатора пользователя (mc) setrlimit установка лимита ресурса (mc) setsid создание сессии (+c) setsockopt изменение опций сокета (mc) settimeofday установка времени дня (с 1.1.1970) (mc)
setuid установка действительного идентификатора пользователя
(m+c) setup инициализация устройств и монтирование корня (-)
sgetmask см. siggetmask (m) shmat привязка разделяемой памяти к сегменту данных (m!c)
shmctl манипуляции с разделяемой памятью (m!c) shmdt отвязка разделяемой памяти от сегмента данных (m!c)
shmget получение/создание разделяемого сегмента памяти (m!c)
shutdown закрытие сокета (m!c) sigaction установка/получение обработчика сигнала (m+c)
sigblock блокировка сигналов (m!c) siggetmask получение сигнала, блокирующего текущий процесс
(!c) signal установка обработчика сигнала (mc) sigpause использование новой маски сигнала, пока не signal
(mc) sigpending получение ожидающих, но заблокированных сигналов
(m+c) sigprocmask установка/получение сигнала, блокирующего текущий
процесс (+c) sigsetmask установка сигнала, блокирующего текущий процесс
(c!) sigsuspend переустановка для sigpause (m+c) sigvec см. sigaction (m!) socket создание точки коммуникации сокета (m!c) socketcall сокет вызывает мультиплексор (-) socketpair создание 2 связанных сокетов (m!c) ssetmask см. sigsetmask (m) stat получение статуса файла (m+c) statfs получение статуса файловой системы (mc) stime установка секунд с 1.1.1970 (mc) swapoff окончание свопинга в файл/устройство (m-c) swapon начало свопинга в файл/устройство (m-c) symlink создание символической связи с файлом (m+c) sync синхронизация буферов памяти и диска (mc) syscall исполнение системного вызова по номеру (-!c) sysconf получение значения системной переменной (m+!c)
sysfs получение информации о конфигурированных файловых
системах () sysinfo получение системной информации Linux-а (m-) syslog работа с системной регистрацией (m-c) system исполнение команды shell-а (m!c) time получение секунд с 1.1.1970 (m+c) times получение временн'ых характеристик процесса (m+c)
truncate изменение размера файла (mc) ulimit установка/получение границ файла (c!) umask установка маски создания файла (m+c) umount размонтирование файловых системы (mc) uname получение системной информации (m+c) unlink удаление незанятого файла (m+c) uselib использование разделяемой библиотеки (m-c) utime модификация временн'ых элементов inode (m+c) utimes см. utime (m!c) vfork см. fork (m!c) vhangup виртуально подвешивает текущий tty (m-c) vm86 войти в виртуальный режим 8086 (m-c) wait ожидание завершения процесса (m+!c) wait3 bsd ждет указанный процесс (m!c) wait4 bsd ждет указанный процесс (mc) waitpid ожидание указанного процесса (m+c) write запись данных в файл (m+c) writev запись блоков данных в файл (m!c) (m) существует manual page (+) поддерживается POSIX (-) специфично для Linux (c) в libc (!) не одиночный системный вызов, использует другой
системный вызов 12 Аббревиатуры
ANSI American National Standard for Information
Systems API Application Programming Interface ASCII American Standard Code for Information Interchange
AT 386Advanced Technology Intel 80386 based PC FIPS Federal Information Processing Standard FSF Free Software Foundation IEEE Institute of Electrical and Electronics Engineers, Inc.
IPC Inter Process Communication ISO International Organization for Standards POSIX Portable Operating System Interface for uniX POSIX.1 IEEE Std. 1003.1-1990 Standard for Information
Technology: Portable Operating System Interface (POSIX) Part 1: System
Application Programming Interface (API)
Converted on:
Fri Mar 29 14:43:04 EST 1996
Оглавление: Оглавление
Назад: 11 Справочник
системных вызовов (в алфавитном порядке)
Про этот документ
latex2html lpg.tex.
Converted on:
Fri Mar 29 14:43:04 EST 1996
Вперед: 3 Библиотека libc
Оглавление: Оглавление
Назад:1 Операционная система
LINUX
2 Ядро LINUX
Converted on:
Fri Mar 29 14:43:04 EST 1996
Вперед: 4 Системные вызовы
Оглавление: Оглавление
Назад:2 Ядро LINUX
3 Библиотека libc
Converted on:
Fri Mar 29 14:43:04 EST 1996
Вперед: 5 ioctl
Оглавление: Оглавление
Назад: 3 Библиотека libc
4 Системные вызовы
#include <syscall.h>
extern int syscall(int, ...);
int my_close(int filedescriptor)
{
return syscall(SYS_close, filedescriptor);
}
#include <linux/unistd.h>
_syscall1(int, close, int, filedescriptor);
Converted on:
Fri Mar 29 14:43:04 EST 1996
Вперед: 6 Межпроцессовые
коммуникации LINUX
Оглавление: Оглавление
Назад: 4 Системные вызовы
5 ioctl
ioctl(unsigned int fd, unsigned int request, unsigned long argument).
Возвращаемое значение есть -1 в случае ошибки, 0 и больше если команда
(request) прошла так же, как другие системные вызовы. Ядро различает
специальные и регулярные файлы. Специальные файлы в основном находятся в /dev
и /proc. Они отличаются от регулярных файлов тем, что прячут свое описание в
драйвер, тогда как регулярные файлы содержат текст или двоичные данные. Эта
философия UNIX, которая позволяет вводить/выводить из любого файла. Но если
вам хочется сделать что-нибудь особенное со специальным файлом, вы можете это
сделать при ioсtl. В основном ioсtl нужен для борьбы со специальными файлами,
но его можно использовать и для обычных.
Converted on:
Fri Mar 29 14:43:04 EST 1996
Вперед: 7 Программирование
звука
Оглавление: Оглавление
Назад: 5 ioctl
6 Межпроцессовые коммуникации
LINUX
6.1 Введение
6.2 Полудуплексные каналы UNIX
6.2.1 Основные понятия
ls | sort | lp
in <-----
Process Kernel
out ----->
in <----- -----> in
Parent Process Kernel Child Process
out -----> <----- out
in <----- in
Parent Process Kernel Child Process
out <----- out
6.2.2 Создание каналов в Си
SYSTEM CALL: pipe();
PROTOTYPE: int pipe( int fd[2] );
RETURNS: 0 в случае успеха
-1 в случае ошибки:
errno = EMFILE (нет свободных дескрипторов)
EMFILE (системная файловая таблица переполнена)
EFAULT (массив fd некорректен)
NOTES: fd[0] устанавливается для чтения, fd[1] - для записи.
Первое целое в массиве (элемент 0) установлено и открыто для
чтения, в то время как второе целое (элемент 1) установлено и открыто
для записи. Наглядно говоря, вывод fd1 становится вводом для fd0. Еще
раз отметим, что все данные, проходящие через канал, перемещаются через ядро.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
main()
{
int fd[2];
pipe(fd);
.
.
}
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
main()
{
int fd[2];
pid_t childpid;
pipe(fd);
if ((childpid = fork()) == -1)
{
perror("fork");
exit(1);
}
.
.
}
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
main()
{
int fd[2];
pid_t childpid;
pipe(fd);
if ((childpid = fork()) == -1)
{
perror("fork");
exit(1);
}
if (childpid == 0)
{
/* Child process closes up input side of pipe */
close(fd[0]);
}
else
{
/* Parent process closes up output side of pipe */
close(fd[1]);
}
.
.
}
/**************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
**************************************************************************
MODULE: pipe.c
**************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(void)
{
int fd[2], nbytes;
pid_t childpid;
char string[] = "Hello, world!\n";
char readbuffer[80];
pipe(fd);
if ((childpid = fork()) == -1)
{
perror("fork");
exit(1);
}
if (childpid == 0)
{
/* Child process closes up input side of pipe */
close(fd[0]);
/* Send "string" through the output side of pipe */
write(fd[1], string, strlen(string));
exit(0);
}
else
{
/* Parent process closes up output side of pipe */
close(fd[1]);
/* Read in a string from the pipe */
nbytes = read(fd[0], readbuffer, sizeof(readbuffer));
printf("Received string: %s", readbuffer);
}
return(0);
}
SYSTEM CALL: dup();
PROTOTYPE: int dup( int oldfd );
RETURNS: new descriptor on success
-1 on error: errno = EBADF (oldfd некорректен)
EBADF ($newfd is out of range$)
EMFILE (слишком много дескрипторов для
процесса)
NOTES: старый дескриптор не закрыт! Оба работают совместно!
.
.
childpid = fork();
if (childpid == 0)
{
/* Close up standard input of the child */
close(0);
/* Duplicate the input side of pipe to stdin */
dup(fd[0]);
execlp("sort", "sort", NULL);
.
}
SYSTEM CALL: dup2();
PROTOTYPE: int dup2( int oldfd, int newfd );
RETURNS: новый дескриптор в случае успеха
-1 в случае ошибки: errno = EBADF (oldfd некорректен)
EBADF ($newfd is out of range$)
EMFILE (слишком много дескрипторов для
процесса)
NOTES: старый дескриптор закрыл dup2()!
childpid = fork();
if (childpid == 0)
{
/* Close stdin, duplicate the input side of pipe to stdin */
dup2(0, fd[0]);
execlp("sort", "sort", NULL);
.
.
}
6.2.3 Каналы: легкий путь!
LIBRARY FUNCTION: popen();
PROTOTYPE: FILE *popen ( char *command, char *type );
RETURNS: новый файловый поток в случае успеха
NULL при неудачном fork() или pipe()
NOTES: создает канал, и выполняет fork/exec, используя command
LIBRARY FUNCTION: pclose();
PROTOTYPE: int pclose( FILE *stream )
RETURNS: выход из статуса системного вызова wait4()
-1, если "stream" некорректен или облом с wait4()
NOTES: ожидает окончания связанного каналом процесса,
затем закрывает поток.
Функция pclose() выполняет wait4() над процессом, порожденным popen(). Когда
она возвращается, то уничтожает канал и файловый поток. Повторим еще раз, что
этот эффект аналогичен эффекту, вызываемому функцией fclose() для
нормального, основанного на потоке файлового ввода/вывода.
/*****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
*****************************************************************************
MODULE: popen1.c
*****************************************************************************/
#include <stdio.h>
#define MAXSTRS 5
int main(void)
{
int cntr;
FILE *pipe_fp;
char *strings[MAXSTRS] = {"echo", "bravo",
"alpha", "charlie",
"delta"};
/* Create one way pipe line with call to popen() */
if ((pipe_fp = popen("sort", "w")) == NULL)
{
perror("popen");
exit(1);
}
/* Processing loop */
for (cntr=0; cntr<MAXSTRS; cntr++)
{
fputs(strings[cntr], pipe_fp);
fputc('\n', pipe_fp);
}
/* Close the pipe */
pclose(pipe_fp);
return(0);
}
popen("ls ~scottb", "r");
popen("sort > /tmp/foo", "w");
popen("sort | uniq | more", "w");
/*****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
*****************************************************************************
MODULE: popen2.c
*****************************************************************************/
#include <stdio.h>
int main(void)
{
FILE *pipein_fp, *pipeout_fp;
char readbuf[80];
/* Create one way pipe line with call to popen() */
if ((pipein_fp = popen("ls", "r")) == NULL)
{
perror("popen");
exit(1);
}
/* Create one way pipe line with call to popen() */
if (( pipeout_fp = popen("sort", "w")) == NULL)
{
perror("popen");
exit(1);
}
/* Processing loop */
while(fgets(readbuf, 80, pipein_fp)) fputs(readbuf, pipeout_fp);
/* Close the pipes */
pclose(pipein_fp);
pclose(pipeout_fp);
return(0);
}
/*****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
*****************************************************************************
MODULE: popen3.c
*****************************************************************************/
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE *pipe_fp, *infile;
char readbuf[80];
if (argc != 3) {
fprintf(stderr, "USAGE: popen3 [command] [filename]\n");
exit(1);
}
/* Open up input file */
if ((infile = fopen(argv[2], "rt")) == NULL)
{
perror("fopen");
exit(1);
}
/* Create one way pipe line with call to popen() */
if ((pipe_fp = popen(argv[1], "w")) == NULL)
{
perror("popen");
exit(1);
}
/* Processing loop */
do {
fgets(readbuf, 80, infile);
if (feof(infile)) break;
fputs(readbuf, pipe_fp);
} while(!feof(infile));
fclose(infile);
pclose(pipe_fp);
return(0);
}
popen3 sort popen3.c
popen3 cat popen3.c
popen3 more popen3.c
popen3 cat popen3.c | grep main
6.2.4 Атомарные (неделимые)
операции с каналами
#define _POSIX_PIPE_BUF 512
#define PIPE_BUF 4096
6.2.5 Примечания к
полудуплексным каналам
6.3 Именованные каналы (FIFO:
First In First Out):
6.3.1 Именованные каналы (FIFO:
First In First Out): основные понятия
6.3.2 Создание FIFO
mknod MYFIFO p
mkfifo a=rw MYFIFO
$ ls -l MYFIFO
prw-r--r-- 1 root root 0 Dec 14 22:15 MYFIFO|
LIBRARY FUNCTION: mknod();
PROTOTYPE: int mknod( char *pathname, mode_t mode, dev_t dev );
RETURNS: 0 в случае успеха,
-1 в случае ошибки:
errno = EFAULT (ошибочно указан путь)
EACCESS (нет прав)
ENAMETOOLONG (слишком длинный путь)
ENOENT (ошибочно указан путь)
ENOTDIR (ошибочно указан путь)
(остальные смотрите в man page для mknod)
NOTES: Создает узел файловой системы (файл, файл устройства или FIFO)
mknod("/tmp/MYFIFO", S_IFIFO|0666, 0);
final_umask = requested_permissions & ~original_umask
umask(0);
mknod("/tmp/MYFIFO", S_IFIFO|0666, 0);
6.3.3 Операции с FIFO
/*****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
*****************************************************************************
MODULE: fifoserver.c
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/stat.h>
#define FIFO_FILE "MYFIFO"
int main(void)
{
FILE *fp;
char readbuf[80];
/* Create the FIFO if it does not exist */
umask(0);
mknod(FIFO_FILE, S_IFIFO|0666, 0);
while(1)
{
fp = fopen(FIFO_FILE, "r");
fgets(readbuf, 80, fp);
printf("Received string: %s\n", readbuf);
fclose(fp);
}
return(0);
}
$ fifoserver&
/*****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
*****************************************************************************
MODULE: fifoclient.c
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#define FIFO_FILE "MYFIFO"
int main(int argc, char *argv[])
{
FILE *fp;
if (argc != 2)
{
printf("USAGE: fifoclient [string]\n");
exit(1);
}
if ((fp = fopen(FIFO_FILE, "w")) == NULL)
{
perror("fopen");
exit(1);
}
fputs(argv[1], fp);
fclose(fp);
return(0);
}
6.3.4 Действие блокирования на
FIFO
6.3.5 Неизвестный сигнал SIGPIPE
6.4 System V IPC
6.4.1 System V IPC: базовые
понятия
IPC идентификаторы
IPC ключи
LIBRARY FUNCTION: ftok();
PROTOTYPE: key_t ftok( char *pathname, char proj );
RETURNS: новый IPC ключ в случае успеха
-1 в случае неудачи, errno устанавливается как значение вызова
stat()
Возвращаемый ftok() ключ инициируется значением inode и
нижним числом устройства файла, первого аргумента, и символом,
вторым аргументом. Это не гарантирует уникальности, но приложение может
проверить наличие коллизий и, если понадобится, сгенерировать новый ключ.
key_t mykey;
mykey = ftok("/tmp/myapp", 'a');
В предложенном выше куске директория /tmp/myapp смешивается с
однолитерным идентификатором 'a'. Другой распространенный пример,
использовать текущую директорию.
key_t mykey;
mykey = ftok(".", 'a');
Выбор алгоритма генерации ключа полностью отдается на усмотрение
прикладного программиста. Так же как и меры по предотвращению ситуации
гонок, дедлоков и т.п., любой метод имеет право на жизнь. Для наших
демонстрационных целей мы ограничимся ftok(). Если условиться, что
каждый процесс-клиент запускается со своей уникальной "домашней"
директории, то генерируемые ключи будут всегда удовлетворительны.
Команда ipcs
ipcs -q: показать только очереди сообщений
ipcs -s: показать только семафоры
ipcs -m: показать только разделяемую память
ipcs --help: для любознательных
По умолчанию показываются все три категории объектов. Посмотрим на
следующий незатейливый вывод ipcs:
------ Shared Memory Segments --------
shmid owner perms bytes nattch status
------ Semaphore Arrays --------
semid owner perms nsems status
------ Message Queues --------
msqid owner perms used-bytes messages
0 root 660 5 1
Здесь мы видим одинокую очередь с идентификатором "0". Она принадлежит
пользователю root и имеет восьмеричные права доступа 660,
или -rw-rw---. Очередь содержит одно пятибайтное сообщение.
Команда ipcrm
ipcrm <msg | sem | shm> <IPC ID>
Требуется указать, является ли удаляемый объект очередью
сообщений (msg), набором семафоров (sem), или сегментом
разделяемой памяти (shm). IPC ID может быть получен через команду
ipcs. Напомним, что ID уникален в пределах одного из трех типов
объектов IPC, поэтому мы обязаны назвать этот тип явно.
6.4.2 Очереди сообщений
Очереди сообщений: основные
концепции
Внутренние и пользовательские
структуры данных
Буфер сообщений
/* message buffer for msgsnd and msgrcv calls */
struct msgbuf {
long mtype; /* type of message */
char mtext[1]; /* message text */
};
Структура msgbuf имеет две записи:
struct my_msgbuf {
long mtype; /* Message type */
long request_id; /* Request identifier */
struct client info; /* Client information structure */
};
Здесь мы также видим структуру сообщения, но второй элемент
заменился на два, причем один из них другая структура! В этом прелесть
очередей сообщений, ядро не разбирает данные, какими бы они ни были. Может
передаваться любая информация.
#define MSGMAX 4056 /* <= 4056 max size of message (bytes) */
Сообщения не могут быть больше, чем 4056 байт, сюда входит и
элемент mtype, который занимает 4 байта (long).
Структура ядра msg
/* one msg structure for each message */
struct msg {
struct msg *msg_next; /* next message on queue */
long msg_type;
char *msg_spot; /* message text address */
short msg_ts; /* message text size */
};
Структура ядра msqid_ds
/* one msqid structure for each queue on the system */
struct msqid_ds {
struct ipc_perm msg_perm;
struct msg *msg_first; /* first message on queue */
struct msg *msg_last; /* last message in queue */
time_t msg_stime; /* last msgsnd time */
time_t msg_rtime; /* last msgrcv time */
time_t msg_ctime; /* last change time */
struct wait_queue *wwait;
struct wait_queue *rwait;
ushort msg_cbytes;
ushort msg_qnum;
ushort msg_qbytes; /* max number of bytes on queue */
ushort msg_lspid; /* pid of last msgsnd */
ushort msg_lrpid; /* last receive pid */
};
Хотя большинство элементов этой структуры вас будет мало
волновать, для какой-то законченности вкратце поясним каждый:
Структура ядра ipc_perm
struct ipc_perm
{
key_t key;
ushort uid; /* owner euid and egid */
ushort gid;
ushort cuid; /* creator euid and egid */
ushort cgid;
ushort mode; /* access modes see mode flags below */
ushort seq; /* slot usage sequence number */
};
Все приведенное выше говорит само за себя. Сохраняемая отдельно
вместе с ключом IPC-объекта информация содержит данные о владельце и
создателе этого объекта (они могут различаться). Режимы восьмеричного доступа
также хранятся здесь, как unsigned short. Наконец, сохраняется
порядковый номер использования слота (slot usage sequence). Каждый
раз когда IPC объект закрывается через системный вызов (уничтожается), этот
номер уменьшается на максимальное число объектов IPC, которые могут
находиться в системе. Касается вас это значение? Нет.
Системный вызов: msgget()
SYSTEM CALL: msgget()
PROTOTYPE: int msgget( key_t key, int msgflg );
RETURNS: идентификатор очереди сообщений в случае успеха;
-1 в случае ошибки. При этом
errno = EACCESS (доступ отклонен)
EEXIST (такая очередь уже есть, создание невозможно)
EIDRM (очередь помечена как удаляемая)
ENOENT (очередь не существует)
ENOMEM (не хватает памяти для создания новой очереди)
ENOSPC (исчерпан лимит на количество очередей)
Первый аргумент msgget() значение ключа (мы его получаем
при помощи ftok()). Этот ключ сравнивается с ключами уже
существующих в ядре очередей. При этом операция открытия или доступа к
очереди зависит от содержимого аргумента msgflg:
int open_queue( key_t keyval )
{
int qid;
if ((qid = msgget( keyval, IPC_CREAT | 0660 )) == -1)
{
return(-1);
}
return(qid);
}
Отметьте использование точного ограничителя доступа 0660.
Эта небольшая функция возвращает идентификатор очереди (int) или -1
в случае ошибки. Единственный требуемый аргумент: ключевое значение.
Системный вызов: msgsnd()
SYSTEM CALL: msgsnd();
PROTOTYPE: int msgsnd(int msqid, struct msgbuf *msgp, int msgsz,
int msgflg );
RETURNS: 0 в случае успеха
-1 в случае ошибки:
errno = EAGAIN (очередь переполнена, и установлен IPC_NOWAIT)
EACCES (доступ отклонен, нет разрешения на запись)
EFAULT (адрес msgp недоступен, неверно...)
EIDRM (очередь сообщений удалена)
EINTR (получен сигнал во время ожидания печати)
EINVAL (ошибочный идентификатор очереди сообщений,
неположительный тип сообщения или
неправильный размер сообщения)
ENOMEM (не хватает памяти для копии буфера сообщения)
Первый аргумент msgsnd: идентификатор нашей очереди, возвращенный
предварительным вызовом msgget. Второй аргумент msgp это
указатель на редекларированный и загруженный буфер сообщения. Аргумент
msgsz содержит длину сообщения в байтах, не учитывая тип сообщения
(long 4 байта).
int send_message( int qid, struct mymsgbuf *qbuf )
{
int result, length;
/* The length is essentially the size of the structure minus
sizeof(mtype) */
length = sizeof(struct mymsgbuf) - sizeof(long);
if ((result = msgsnd(qid, qbuf, length, 0)) == -1)
{
return(-1);
}
return(result);
}
Эта функция пытается послать сообщение, лежащее по указанному адресу
(qbuf), в очередь сообщений, идентифицированную qid.
Напишем небольшую утилиту с нашими двумя оберточными функциями:
#include <stdio.h>
#include <stdlib.h>
#include <linux/ipc.h>
#include <linux/msg.h>
main()
{
int qid;
key_t msgkey;
struct mymsgbuf
{
long mtype; /* Message type */
int request; /* Work request number */
double salary; /* Employee's salary */
} msg;
/* Generate our IPC key value */
msgkey = ftok(".", 'm');
/* Open/create the queue */
if ((qid = open_queue( msgkey)) == -1) {
perror("open_queue");
exit(1);
}
/* Load up the message with arbitrary test data */
msg.mtype = 1; /* Message type must be a positive number! */
msg.request = 1; /* Data element #1 */
msg.salary = 1000.00; /* Data element #2 (my yearly salary!) */
/* Bombs away! */
if ((send_message( qid, &msg )) == -1) {
perror("send_message");
exit(1);
}
}
После создания/открытия нашей очереди принимаемся за загрузку
буфера сообщения с тестовыми данными (обратите внимание на отсутствие
текстовых данных для иллюстрации нашего положения о посылке двоичной
информации). Вызов нашего send_message доставит сообщение в
очередь.
SYSTEM CALL: msgrcv();
PROTOTYPE: int msgrcv(int msqid, struct msgbuf *msgp, int msgsz,
long mtype, $$)
RETURNS: число байт, скопированных в буфер сообщения
-1 в случае ошибки:
errno = E2BIG (длина сообщения больше, чем msgsz, $$)
EACCES (нет права на чтение)
EFAULT (адрес, на который указывает msgp, ошибочен)
EIDRM (очередь была уничтожена в период изъятия
сообщения)
EINTR (прервано поступившим сигналом)
EINVAL (msgqid ошибочен или msgsz меньше 0)
ENOMSG (установлен IPC_NOWAIT, но в очереди нет
ни одного сообщения, удовлетворяющего
запросу)
Конечно, первый аргумент определяет очередь, из которой будет взято сообщение
(должен быть возвращен сделанным предварительно вызовом msgget).
Второй аргумент (msgp) представляет собой адрес буфера, куда будет
положено изъятое сообщение. Третий аргумент, msgsz, ограничивает
размер структуры-буфера без учета длины элемента mtype. Еще раз
повторимся, это может быть легко вычислено:
msgsz = sizeof(struct mymsgbuf) - sizeof(long);
Четвертый аргумент, mtype это тип сообщения, изымаемого из
очереди. Ядро будет искать в очереди наиболее старое сообщение такого
типа и вернет его копию по адресу, указанному аргументом msgp.
Существует один особый случай: если mtype = 0, то будет возвращено
наиболее старое сообщение, независимо от типа.
int read_message( int qid, long type, struct mymsgbuf *qbuf )
{
int result, length;
/* The length is essentially the size of the structure minus
sizeof(mtype) */
length = sizeof(struct mymsgbuf) - sizeof(long);
if ((result = msgrcv( qid, qbuf, length, type, 0)) == -1)
{
return(-1);
}
return(result);
}
После успешного изъятия сообщения удаляется из очереди и его ярлык.
int peek_message( int qid, long type )
{
int result, length;
if ((result = msgrcv( qid, NULL, 0, type, IPC_NOWAIT)) == -1)
{
if (errno == E2BIG) return(TRUE);
}
return(FALSE);
}
Выше вы заметили отсутствие адреса буфера и длины. В этом
конкретном случае мы хотели, чтобы вызов прошел неудачно. Однако мы
проверили возвращение E2BIG, которое должно показать, существует ли
сообщение затребованного типа. Оберточная функция возвращает TRUE в
случае успеха, и FALSE в противном случае. Отметьте также
установленный флаг IPC_NOWAIT, который помешает блокировке, о которой
мы говорили раньше.
Системный вызов: msgctl()
SYSTEM CALL: msgctl()
PROTOTYPE: int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
RETURNS: 0 в случае успеха
-1 в случае неудачи
errno = EACCES (нет прав на чтение и cmd есть IPC_STAT)
EFAULT (адрес, на который указывает buf, ошибочен
для команд IPC_SET и IPC_STAT)
EIDRM (очередь была уничтожена во время запроса)
EINVAL (ошибочный msqid или msgsz меньше 0)
EPERM (IPC_SET- или IPC_RMID-команда была
послана процессом, не имеющим прав на запись
в очередь)
Теперь из общих соображений ясно, что прямые манипуляции с
внутреностями ядра могут привести к очень занимательным последствиям.
К сожалению, по-настоящему весело будет только тому, кто любит
вдребезги и с наслаждением крушить подсистему IPC. Однако, при
использовании msgctl() с некоторыми командами вероятность
огорчительных результатов не очень велика. Вот их и рассмотрим:
int get_queue_ds( int qid, struct msgqid_ds *qbuf )
{
if (msgctl( qid, IPC_STAT, qbuf) == -1)
{
return(-1);
}
return(0);
}
Если копирование во внутренний буфер невозможно, то вызывающей
функции возвращается -1. Если же все прошло нормально, то возвращается
0, и посланный буфер должен содержать копию внутренней структуры
данных для очереди с идентификатором qid.
int change_queue_mode( int qid, char *mode )
{
struct msqid_ds tmpbuf;
/* Retrieve a current copy of the internal data structure */
get_queue_ds( qid, &tmpbuf);
/* Change the permissions using an old trick */
sscanf(mode, "%ho", &tmpbuf.msg_perm.mode);
/* Update the internal data structure */
if (msgctl( qid, IPC_SET, &tmpbuf) == -1)
{
return(-1);
}
return(0);
}
Мы взяли текущую копию внутренней структуры данных посредством вызова нашей
get_queue_ds; затем sscanf() меняет элемент mode
структуры msg_perm. Однако ничего не произойдет, пока
msgctl c IPC_SET не обновил внутреннюю версию.
Для иллюстрации этого факта расскажу анекдотический случай из своей практики.
Я занимался выполнением лабораторной работы по очередям. Все шло нормально, я
тестировал свою программу с разными параметрами, как вдруг, при указании прав
доступа ``600'' вместо ``660'', я лишился доступа к своей же очереди! Я не
смог тестировать очереди сообщений в любой точке моего каталога, а когда я
попытался вызвать функцию ftok() для создания ключа IPC, я попытался
обратиться к очереди, прав доступа к которой теперь не имел. Кончилось тем,
что я выловил локального системного администратора и потратил час на то,
чтобы объяснить ему, что произошло, и зачем мне понадобилась команда
ipcrm, grrrr.
int remove_queue(int qid)
{
if (msgctl( qid, IPC_RMID, 0) == -1)
{
return(-1);
}
return(0);
}
Эта функция-переходник возвращает 0, если очередь удалена без
инцедентов, в противном случае ввыдается -1. Удаление очереди неделимо
(атомарно) и попытка любого обращения к ней будет безуспешной.
msgtool: интерактивное
управление очередью сообщений
Фон
Синтаксис командной строки
Передача сообщений
msgtool s (type) "text"
Прием сообщений
msgtool r (type)
Изменение режима доступа
msgtool m (mode)
Удаление сообщения
msgtool d
Примеры
msgtool s 1 test
msgtool s 5 test
msgtool s 1 "This is a test"
msgtool r 1
msgtool d
msgtool m 660
Источник
ЗАМЕЧАНИЕ: Поскольку msgtool использует ftok() для
генерации ключей IPC, вы можете нарваться на конфликты, связанные с
директориями. Если вы где-то в скрипте меняете директории, то все это
наверняка не сработает. Это обходится путем более явного указания пути
в msgtool, вроде "/tmp/msgtool", или даже запроса пути из командной
строки вместе с остальными аргументами.
/*****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
*****************************************************************************
MODULE: msgtool.c
*****************************************************************************
A command line tool for tinkering with SysV style Message Queues
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MAX_SEND_SIZE 80
struct mymsgbuf {
long mtype;
char mtext[MAX_SEND_SIZE];
};
void send_message(int qid, struct mymsgbuf *qbuf, long type, char *text);
void read_message(int qid, struct mymsgbuf *qbuf, long type);
void remove_queue(int qid);
void change_queue_mode(int qid, char *mode);
void usage(void);
int main(int argc, char *argv[])
{
key_t key;
int msgqueue_id;
struct mymsgbuf qbuf;
if (argc == 1) usage();
/* Create unique key via call to ftok() */
key = ftok(".", 'm');
/* Open the queue - create if necessary */
if ((msgqueue_id = msgget(key, IPC_CREAT|0660)) == -1) {
perror("msgget");
exit(1);
}
switch(tolower(argv[1][0]))
{
case 's': send_message(msgqueue_id, (struct mymsgbuf *)&qbuf,
atol(argv[2]), argv[3]);
break;
case 'r': read_message(msgqueue_id, &qbuf, atol(argv[2]));
break;
case 'd': remove_queue(msgqueue_id);
break;
case 'm': change_queue_mode(msgqueue_id, argv[2]);
break;
default: usage();
}
return(0);
}
void send_message(int qid, struct mymsgbuf *qbuf, long type, char *text)
{
/* Send a message to the queue */
printf("Sending a message ...\n");
qbuf->mtype = type;
strcpy(qbuf->mtext, text);
if ((msgsnd(qid, (struct msgbuf *)qbuf, strlen(qbuf->mtext)+1, 0)) ==-1)
{
perror("msgsnd");
exit(1);
}
}
void read_message(int qid, struct mymsgbuf *qbuf, long type)
{
/* Read a message from the queue */
printf("Reading a message ...\n");
qbuf->mtype = type;
msgrcv(qid, (struct msgbuf *)qbuf, MAX_SEND_SIZE, type, 0);
printf("Type: %ld Text: %s\n", qbuf->mtype, qbuf->mtext);
}
void remove_queue(int qid)
{
/* Remove the queue */
msgctl(qid, IPC_RMID, 0);
}
void change_queue_mode(int qid, char *mode)
{
struct msqid_ds myqueue_ds;
/* Get current info */
msgctl(qid, IPC_STAT, &myqueue_ds);
/* Convert and load the mode */
sscanf(mode, "%ho", &myqueue_ds.msg_perm.mode);
/* Update the mode */
msgctl(qid, IPC_SET, &myqueue_ds);
}
void usage(void)
{
fprintf(stderr, "msgtool - A utility for tinkering with msg queues\n");
fprintf(stderr, "\nUSAGE: msgtool (s)end <type> <messagetext>\n");
fprintf(stderr, " (r)ecv <type>\n");
fprintf(stderr, " (d)elete\n");
fprintf(stderr, " (m)ode <octal mode>\n");
exit(1);
}
6.4.3 Семафоры
Семафоры: Основные концепции
Структура ядра semid_ds
/* One semid data structure for each set of semaphores in the system. */
struct semid_ds {
struct ipc_perm sem_perm; /* permissions .. see ipc.h */
time_t sem_otime; /* last semop time */
time_t sem_ctime; /* last change time */
struct sem *sem_base; /* ptr to first semaphore in array */
struct wait_queue *eventn;
struct wait_queue *eventz;
struct sem_undo *undo; /* undo requests on this array */
ushort sem_nsems; /* no. of semaphores in array */
};
Так же, как с очередями сообщений, операции с этой структурой
проводятся с помощью системных вызовов, а не грязными скальпелями. Вот
некоторые описания полей.
Структура ядра sem
/* One semaphore structure for each semaphore in the system. */
struct sem {
short sempid; /* pid of last operation */
ushort semval; /* current value */
ushort semncnt; /* num procs awaiting increase in semval */
ushort semzcnt; /* num procs awaiting semval = 0 */
};
Системный вызов: semget()
SYSTEM CALL: semget();
PROTOTYPE: int semget (key_t key, int nsems, int semflg);
RETURNS: IPC-идентификатор множества семафоров в случае успеха
-1 в случае ошибки
errno: EACCESS (доступ отклонен)
EEXIST (существует нельзя создать (IPC_ESCL))
EIDRM (множество помечено как удаляемое)
ENOENT (множество не существует, не было исполнено
ни одного IPC_CREAT)
ENOMEM (не хватает памяти для новых семафоров)
ENOSPC (превышен лимит на количество множеств
семафоров)
Первый аргумент semget() это ключ (в нашем случае возвращается
ftok()). Он сравнивается с ключами остальных множеств семафоров,
присутствующих в системе. Вместе с этим решается вопрос о выборе между
созданием и подключением к множеству семафоров в зависимости от
аргумента msgflg.
#define SEMMSL 32 /* <=512 max num of semaphores per id */
int open_semaphore_set( key_t keyval, int numsems )
{
int sid;
if (!numsems) return(-1);
if ((sid = semget( mykey, numsems, IPC_CREAT | 0660 )) == -1)
{
return(-1);
}
return(sid);
}
Обратите внимание на явное задание доступа 0660. Эта незатейливая
функция возвращает идентификатор множества семафоров (int) или -1 в
случае ошибки. Должны быть также заданы значение ключа и число семафоров для
того, чтобы сосчитать память, необходимую для них. В примере, завершающем
этот IPC_EXCL используется для определения существует множество семафоров
или нет.
Системный вызов: semop()
SYSTEMCALL: semop();
PROTOTYPE: int semop( int semid, struct sembuf *sops, unsigned nsops);
RETURNS: 0 в случае успеха (все операции выполнены)
-1 в случае ошибки
errno: E2BIG (nsops больше чем максимальное число
позволенных операций)
EACCESS (доступ отклонен)
EAGAIN (при поднятом флаге IPC_NOWAIT операция не
может быть выполнена)
EFAULT (sops указывает на ошибочный адрес)
EIDRM (множество семафоров уничтожено)
EINTR (сигнал получен во время сна)
EINVAL (множество не существует или неверный semid)
ENOMEM (поднят флаг SEM_UNDO, но не хватает памяти
для создания необходимой undo-структуры)
ERANGE (значение семафора вышло за пределы
допустимых значений)
Первый аргумент semop() есть значение ключа (в нашем случае
возвращается semget). Второй аргумент (sops), это указатель
на массив операций, выполняемых над семафорами, третий аргумент
(nsops) является количеством операций в этом массиве.
/* semop system call takes an array of these */
struct sembuf {
ushort sem_num; /* semaphore index in array */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
};
struct sembuf sem_lock = {0, -1, IPC_NOWAIT};
Трансляция вышеописанной инициализации структуры добавит -1 к
семафору 0 из множества семафоров. Другими словами, одна единица
ресурсов будет получена от конкретного (нулевого) семафора из нашего
множества. IPC_NOWAIT установлен, поэтому либо вызов пройдет
немедленно, либо будет провален, если принтер занят. Рассмотрим пример
инициализации sembuf с помощью semop:
if ((semop(sid, &sem_lock, 1) == -1) perror("semop");
Третий аргумент (nsops) говорит, что мы выполняем только одну (1)
операцию (есть только одна структура sembuf в нашем массиве
операций). Аргумент sid является IPC идентификатором для нашего
множества семафоров.
struct sembuf sem_unlock = {0, 1, IPC_NOWAIT};
Трансляция вышеописанной инициализации структуры добавляет 1 к
семафору номер 0 множества семафоров. Другими словами, одна единица
ресурсов будет возвращена множеству семафоров.
Системный вызов: semctl()
SYSTEM CALL: semctl();
PROTOTYPE: int semctl (int semid, int semnum, int cmd, union semun arg);
RETURNS: натуральное число в случае успеха
-1 в случае ошибки:
errno = EACCESS (доступ отклонен)
EFAULT (адрес, указанный аргументом arg, ошибочен)
EIDRM (множество семафоров удалено)
EINVAL (множество не существует или неправильный
semid)
EPERM (EUID не имеет привилегий для cmd в arg)
ERANGE (значение семафора вышло за пределы
допустимых значений)
NOTES: Выполняет операции, управляющие множеством семафоров
Вызов semctl используется для осуществления управления множеством
семафоров. Этот вызов аналогичен вызову msgctl для очередей
сообщений. Если вы сравните списки аргументов этих двух вызовов, то заметите,
что они немного отличаются. Напомним, что семафоры введены скорее как
множества, чем как отдельные объекты. С операциями над семафорами
требуется посылать не только IPC-ключ, но и конкретный семафор из множества.
/* arg for semctl system calls. */
union semun {
int val; /* value for SETVAL */
struct semid_ds *buf; /* buffer for IPC_STAT & IPC_SET */
ushort *array; /* array for GETALL & SETALL */
struct seminfo *__buf; /* buffer for IPC_INFO */
void *__pad;
};
int get_sem_val( int sid, int semnum )
{
return( semctl(sid, semnum, GETVAL, 0));
}
Возвращаясь к примеру с принтерами, допустим, что потребовалось
определить статус всех пяти принтеров:
#define MAX_PRINTERS 5
printer_usage()
{
int x;
for(x=0; x<MAX_PRINTERS; x++)
printf("Printer %d: %d\n\r", x, get_sem_val( sid, x ));
}
C
Рассмотрим следующую функцию, которая может быть использована для
инициализации нового значения семафора:
void init_semaphore( int sid, int semnum, int initval)
{
union semun semopts;
semopts.val = initval;
semctl( sid, semnum, SETVAL, semopts);
}
Заметьте, что последний аргумент semctl, это копия
объединения, а не указатель на него. Рассмотрим довольно распространенную
ошибку использования аргумента-объединения semctl().
/* Required permissions should be passed in as text (ex: "660") */
void changemode(int sid, char *mode)
{
int rc;
struct semid_ds mysemds;
/* Get current values for internal data structure */
if ((rc = semctl(sid, 0, IPC_STAT, semopts)) == -1)
{
perror("semctl");
exit(1);
}
printf("Old permissions were %o\n", semopts.buf->sem_perm.mode);
/* Change the permissions on the semaphore */
sscanf(mode, "%o", &semopts.buf->sem_perm.mode);
/* Update the internal data structure */
semctl(sid, 0, IPC_SET, semopts);
printf("Updated...\n");
}
Программа пытается создать локальную копию внутренней структуры
данных для множества семафоров, изменить права доступа и сIPC_SETить
их обратно в ядро. Однако, первый вызов semctl немедленно вернет
EFAULT или ошибочный адрес для последнего аргумента (объединения!).
Кроме того, если бы мы не следили за ошибками для этого вызова, то
заработали бы сбой памяти. Почему?
void changemode(int sid, char *mode)
{
int rc;
struct semid_ds mysemds;
/* Get current values for internal data structure */
/* Point to our local copy first! */
semopts.buf = &mysemds;
/* Let's try this again! */
if ((rc = semctl(sid, 0, IPC_STAT, semopts)) == -1)
{
perror("semctl");
exit(1);
}
printf("Old permissions were %o\n", semopts.buf->sem_perm.mode);
/* Change the permissions on the semaphore */
sscanf(mode, "%o", &semopts.buf->sem_perm.mode);
/* Update the internal data structure */
semctl(sid, 0, IPC_SET, semopts);
printf("Updated...\n");
}
semtool: интерактивное
управление семафорами
Синтаксис командной строки
Создание множества семафоров
semtool c (number of semaphores in set)
Блокировка семафора
semtool l (semaphore number to lock)
Разблокирование семафора
semtool u (semaphore number to unlock)
Изменение прав доступа
semtool m (mode)
Удаление множества семафоров
semtool d
Примеры
semtool c 5
semtool l
semtool u
semtool m 660
semtool d
Источник
/*****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
*****************************************************************************
MODULE: semtool.c
*****************************************************************************
A command line tool for tinkering with SysV style Semaphore Sets
*****************************************************************************/
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define SEM_RESOURCE_MAX 1 /* Initial value of all semaphores */
void opensem(int *sid, key_t key);
void createsem(int *sid, key_t key, int members);
void locksem(int sid, int member);
void unlocksem(int sid, int member);
void removesem(int sid);
unsigned short get_member_count(int sid);
int getval(int sid, int member);
void dispval(int sid, int member);
void changemode(int sid, char *mode);
void usage(void);
int main(int argc, char *argv[])
{
key_t key;
int semset_id;
if (argc == 1) usage();
/* Create unique key via call to ftok() */
key = ftok(".", 's');
switch(tolower(argv[1][0]))
{
case 'c': if (argc != 3) usage();
createsem(&semset_id, key, atoi(argv[2]));
break;
case 'l': if (argc != 3) usage();
opensem(&semset_id, key);
locksem(semset_id, atoi(argv[2]));
break;
case 'u': if (argc != 3) usage();
opensem(&semset_id, key);
unlocksem(semset_id, atoi(argv[2]));
break;
case 'd': opensem(&semset_id, key);
removesem(semset_id);
break;
case 'm': opensem(&semset_id, key);
changemode(semset_id, argv[2]);
break;
default: usage();
}
return(0);
}
void opensem(int *sid, key_t key)
{
/* Open the semaphore set - do not create! */
if ((*sid = semget(key, 0, 0666)) == -1)
{
printf("Semaphore set does not exist!\n");
exit(1);
}
}
void createsem(int *sid, key_t key, int members)
{
int cntr;
union semun semopts;
if (members > SEMMSL)
{
printf("Sorry, max number of semaphores in a set is %d\n",
SEMMSL);
exit(1);
}
printf("Attempting to create new semaphore set with %d members\n",
members);
if ((*sid = semget(key, members, IPC_CREAT|IPC_EXCL|0666)) == -1)
{
fprintf(stderr, "Semaphore set already exists!\n");
exit(1);
}
semopts.val = SEM_RESOURCE_MAX;
/* Initialize all members (could be done with SETALL) */
for(cntr=0; cntr<members; cntr++) semctl(*sid, cntr, SETVAL, semopts);
}
void locksem(int sid, int member)
{
struct sembuf sem_lock={ 0, -1, IPC_NOWAIT};
if (member<0 || member>(get_member_count(sid)-1))
{
fprintf(stderr, "semaphore member %d out of range\n", member);
return;
}
/* Attempt to lock the semaphore set */
if (!getval(sid, member))
{
fprintf(stderr, "Semaphore resources exhausted (no lock)!\n");
exit(1);
}
sem_lock.sem_num = member;
if ((semop(sid, &sem_lock, 1)) == -1)
{
fprintf(stderr, "Lock failed\n");
exit(1);
}
else printf("Semaphore resources decremented by one (locked)\n");
dispval(sid, member);
}
void unlocksem(int sid, int member)
{
struct sembuf sem_unlock={ member, 1, IPC_NOWAIT};
int semval;
if (member<0 || member>(get_member_count(sid)-1))
{
fprintf(stderr, "semaphore member %d out of range\n", member);
return;
}
/* Is the semaphore set locked? */
semval = getval(sid, member);
if (semval == SEM_RESOURCE_MAX)
{
fprintf(stderr, "Semaphore not locked!\n");
exit(1);
}
sem_unlock.sem_num = member;
/* Attempt to lock the semaphore set */
if ((semop(sid, &sem_unlock, 1)) == -1)
{
fprintf(stderr, "Unlock failed\n");
exit(1);
}
else printf("Semaphore resources incremented by one (unlocked)\n");
dispval(sid, member);
}
void removesem(int sid)
{
semctl(sid, 0, IPC_RMID, 0);
printf("Semaphore removed\n");
}
unsigned short get_member_count(int sid)
{
union semun semopts;
struct semid_ds mysemds;
semopts.buf = &mysemds;
/* Return number of members in the semaphore set */
return(semopts.buf->sem_nsems);
}
int getval(int sid, int member)
{
int semval;
semval = semctl(sid, member, GETVAL, 0);
return(semval);
}
void changemode(int sid, char *mode)
{
int rc;
union semun semopts;
struct semid_ds mysemds;
/* Get current values for internal data structure */
semopts.buf = &mysemds;
rc = semctl(sid, 0, IPC_STAT, semopts);
if (rc == -1)
{
perror("semctl");
exit(1);
}
printf("Old permissions were %o\n", semopts.buf->sem_perm.mode);
/* Change the permissions on the semaphore */
sscanf(mode, "%ho", &semopts.buf->sem_perm.mode);
/* Update the internal data structure */
semctl(sid, 0, IPC_SET, semopts);
printf("Updated...\n");
}
void dispval(int sid, int member)
{
int semval;
semval = semctl(sid, member, GETVAL, 0);
printf("semval for member %d is %d\n", member, semval);
}
void usage(void)
{
fprintf(stderr, "semtool - A utility for tinkering with semaphores\n");
fprintf(stderr, "\nUSAGE: semtool4 (c)reate <semcount>\n");
fprintf(stderr, " (l)ock <sem #>\n");
fprintf(stderr, " (u)nlock <sem #>\n");
fprintf(stderr, " (d)elete\n");
fprintf(stderr, " (m)ode <mode>\n");
exit(1);
}
semstat: Программа-компаньон для
semtool
/*****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
*****************************************************************************
MODULE: semstat.c
*****************************************************************************
A companion command line tool for the semtool package. semstat displays
the current value of all semaphores in the set created by semtool.
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int get_sem_count(int sid);
void show_sem_usage(int sid);
int get_sem_count(int sid);
void dispval(int sid);
int main(int argc, char *argv[])
{
key_t key;
int semset_id;
/* Create unique key via call to ftok() */
key = ftok(".", 's');
/* Open the semaphore set - do not create! */
if ((semset_id = semget(key, 1, 0666)) == -1)
{
printf("Semaphore set does not exist\n");
exit(1);
}
show_sem_usage(semset_id);
return(0);
}
void show_sem_usage(int sid)
{
int cntr=0, maxsems, semval;
maxsems = get_sem_count(sid);
while(cntr < maxsems)
{
semval = semctl(sid, cntr, GETVAL, 0);
printf("Semaphore #%d: --> %d\n", cntr, semval);
cntr++;
}
}
int get_sem_count(int sid)
{
int rc;
struct semid_ds mysemds;
union semun semopts;
/* Get current values for internal data structure */
semopts.buf = &mysemds;
if ((rc = semctl(sid, 0, IPC_STAT, semopts)) == -1) {
perror("semctl");
exit(1);
}
/* return number of semaphores in set */
return(semopts.buf->sem_nsems);
}
void dispval(int sid)
{
int semval;
semval = semctl(sid, 0, GETVAL, 0);
printf("semval is %d\n", semval);
}
6.4.4 Разделяемая память
Основные концепции
Внутренние и пользовательские
структуры данных
Структура ядра shmid_ds
/* One shmid data structure for each shared memory segment in the system. */
struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
time_t shm_atime; /* last attach time */
time_t shm_dtime; /* last detach time */
time_t shm_ctime; /* last change time */
unsigned short shm_cpid; /* pid of creator */
unsigned short shm_lpid; /* pid of last operator */
short shm_nattch; /* no. of current attaches */
/* the following are private */
unsigned short shm_npages; /* size of segment (pages) */
unsigned long *shm_pages; /* array of ptrs to frames -> SHMMAX */
struct vm_area_struct *attaches; /* descriptors for attaches */
};
Операции этой структуры исполняются посредством специального
системного вызова и с ними не следует работать непосредственно. Вот
описания полей, наиболее относящихся к делу:
Системный вызов: shmget()
SYSTEM CALL: shmget();
PROTOTYPE: int shmget ( key_t key, int size, int shmflg );
RETURNS: идентификатор разделяемого сегмента памяти в случае успеха
-1 в случае ошибки:
errno = EINVAL (Ошибочно заданы размеры сегмента)
EEXIST (Сегмент существует, нельзя создать)
EIDRM (Сегмент отмечен для удаления, или был удален)
ENOENT (Сегмент не существует)
EACCESS (Доступ отклонен)
ENOMEM (Недостаточно памяти для создания сегмента)
Этот новый вызов должен выглядеть для вас почти как старые
новости. Он поразительно похож на соответствующие вызовы get для
очередей сообщений и множеств семафоров.
int open_segment( key_t keyval, int segsize )
{
int shmid;
if ((shmid = shmget( keyval, segsize, IPC_CREAT | 0660 )) == -1)
{
return(-1);
}
return(shmid);
}
Отметьте явное использование 0660 для прав доступа. Эта небольшая
функция выдает либо идентификатор разделяемого сегмента памяти
(int), либо -1 в случае ошибки. Значение ключа и заказанные размеры
сегмента (в байтах) передаются в виде аргументов.
Системный вызов: shmat()
SYSTEM CALL: shmat();
PROTOTYPE: int shmat (int shmid, char *shmaddr, int shmflg);
RETURNS: адрес, по которому сегмент был привязан к процессу, в случае
успеха
-1 в случае ошибки:
errno = EINVAL (Ошибочно значение IPC ID или адрес
привязки)
ENOMEM (Недостаточно памяти для привязки сегмента)
EACCES (Права отклонены)
Если аргумент addr является нулем, ядро пытается найти
нераспределенную область. Это рекомендуемый метод. Адрес может быть
указан, но типично это используется только для облегчения работы
аппаратного обеспечения или для разрешения конфликтов с другими приложениями.
Флаг SHM_RND может быть OR-нут в аргумент флага, чтобы заставить переданный
адрес выровняться по странице (округление до ближайшей страницы).
char *attach_segment( int shmid )
{
return(shmat(shmid, 0, 0));
}
Если сегмент был правильно привязан, и процесс имеет указатель на
начало сегмента, чтение и запись в сегмент становятся настолько же
легкими, как манипуляции с указателями. Не потеряйте полученное значение
указателя! Иначе у вас не будет способа найти базу (начало) сегмента.
Системный вызов: shmctl()
SYSTEM SALL: shmctl();
PROTOTYPE: int shmctl (int shmqid, int cmd, struct shmid_ds *buf);
RETURNS: 0 в случае успеха
-1 в случае ошибки:
errno = EACCESS (Нет прав на чтение при cmd, равном
IPC_STAT)
EFAULT (Адрес, на который указывает буфер,
ошибочен при cmd, равном IPC_SET или
IPC_STAT)
EIDRM (Сегмент был удален во время вызова)
EINVAL (ошибочный shmqid)
EPERM (попытка выполнить команду IPC_SET или
IPC_RMID но вызывающий процесс не имеет
прав на запись, измените права доступа)
Вызов очень похож на msgctl(), выполняющий подобные задачи для
очередей сообщений. Поэтому мы не будем слишком детально его обсуждать.
Употребляемые значения команд следующие:
Системный вызов: shmdt()
SYSTEM SALL: shmdt();
PROTOTYPE: int shmdt (char *shmaddr);
RETURNS: -1 в случае ошибки:
errno = EINVAL (ошибочно указан адрес привязки)
После того, как разделяемый сегмент памяти больше не нужен процессу, он
должен быть отсоединен вызовом shmdt(). Как уже отмечалось, это не то же
самое, что удаление сегмента из ядра. После успешного отсоединения значение
элемента shm_nattch структуры shmid_ds уменьшается на 1. Когда оно
достигает 0, ядро физически удаляет сегмент.
shmtool: управление разделяемой
памятью
Фон
Синтаксис командной строки
Запись строк в сегмент
shmtool w "text"
Получение строк из сегмента
shmtool r
Изменение режима доступа
shmtool m (mode)
Удаление сегмента
shmtool d
Примеры
shmtool w test
shmtool w "This is a test"
shmtool r
shmtool d
shmtool m 660
Источник
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SEGSIZE 100
main(int argc, char *argv[])
{
key_t key;
int shmid, cntr;
char *segptr;
if (argc == 1) usage();
/* Create unique key via call to ftok() */
key = ftok(".", 'S');
/* Open the shared memory segment - create if necessary */
if ((shmid = shmget(key, SEGSIZE, IPC_CREAT|IPC_EXCL|0666)) == -1)
{
printf("Shared memory segment exists - opening as client\n");
/* Segment probably already exists - try as a client */
if ((shmid = shmget(key, SEGSIZE, 0)) == -1)
{
perror("shmget");
exit(1);
}
}
else
{
printf("Creating new shared memory segment\n");
}
/* Attach (map) the shared memory segment into the current process */
if ((segptr = shmat(shmid, 0, 0)) == -1)
{
perror("shmat");
exit(1);
}
switch(tolower(argv[1][0]))
{
case 'w': writeshm(shmid, segptr, argv[2]);
break;
case 'r': readshm(shmid, segptr);
break;
case 'd': removeshm(shmid);
break;
case 'm': changemode(shmid, argv[2]);
break;
default: usage();
}
}
writeshm(int shmid, char *segptr, char *text)
{
strcpy(segptr, text);
printf("Done...\n");
}
readshm(int shmid, char *segptr)
{
printf("segptr: %s\n", segptr);
}
removeshm(int shmid)
{
shmctl(shmid, IPC_RMID, 0);
printf("Shared memory segment marked for deletion\n");
}
changemode(int shmid, char *mode)
{
struct shmid_ds myshmds;
/* Get current values for internal data structure */
shmctl(shmid, IPC_STAT, &myshmds);
/* Display old permissions */
printf("Old permissions were: %o\n", myshmds.shm_perm.mode);
/* Convert and load the mode */
sscanf(mode, "%o", &myshmds.shm_perm.mode);
/* Update the mode */
shmctl(shmid, IPC_SET, &myshmds);
printf("New permissions are : %o\n", myshmds.shm_perm.mode);
}
usage()
{
fprintf(stderr, "shmtool - A utility for tinkering with shared memory\n");
fprintf(stderr, "\nUSAGE: shmtool (w)rite <text>\n");
fprintf(stderr, " (r)ead\n");
fprintf(stderr, " (d)elete\n");
fprintf(stderr, " (m)ode change <octal mode>\n");
exit(1);
}
Converted on:
Fri Mar 29 14:43:04 EST 1996
Вперед: 8 Символьная графика
Оглавление: Оглавление
Назад: 6 Межпроцессовые
коммуникации LINUX
7 Программирование звука
7.1 Программирование встроенного
динамика
KDMKTONE можно использовать для предупреждающих сигналов,
поскольку вам не приходится заботиться о его прекращении.
При помощи KIOCSOUND можно проигрывать мелодии, как это
демонстрируется в примере программы splay. Для остановки сигнала
значение тона устанавливается в 0.
7.2 Программирование звуковой
карты
Converted on:
Fri Mar 29 14:43:04 EST 1996
Вперед: 9 Программирование
портов ввода/вывода
Оглавление: Оглавление
Назад: 7 Программирование
звук а
8 Символьная графика
С этими функциями libc вы можете выдавать форматированные строки
в stdout (стандартный вывод), stderr (стандартная ошибка) или другие потоки,
определенные как FILE *stream (например, файлы). sscanf
обеспечивает подобные возможности для чтения форматированного ввода из stdin.
База данных TERMinal CAPabilitie это таблица элементов описания
работы с терминалом в ASCII-файле /etc/termcap. Здесь вы можете найти
информацию о том, как выводить специальные символы, как осуществлять
операции (удаления, вставки символов или строк и т.д.) и как
инициализировать терминал. База данных используется, например,
редактором vi. Имеются библиотечные функции для чтения и использования
возможностей терминала (смотри termcap(3x)). С этой базой данных
программы могут работать с различными терминалами одним и тем же
кодом. База данных termcap и библиотечные функции предоставляют только
низкоуровневый доступ к терминалу. Изменение атрибутов или цветов,
параметризованный вывод и оптимизация остаются программисту.
База данных TERMinal INFOrmation построена над базой данных
termcap и описывает некоторые возможности терминалов на более высоком
уровне. С terminfo программа может легко менять атрибуты экрана,
используя специальные клавиши, такие как функциональные клавиши и др.
Эта база данных может быть найдена в /usr/lib/terminfo/[A-z,0-9]*.
Каждый файл описывает один терминал.
Terminfo хорошая база для работы с терминалом в программе.
Библиотека (BSD-)curses дает вам высокоуровневый доступ к терминалу,
базируясь на terminfo. curses позволяет открывать и манипулировать
окнами на экране, предоставляет весь необходимый набор функций
ввода/вывода и может изменять видеоатрибуты терминально-независимым
образом на более чем 150 терминалах. Библиотека находится в
/usr/lib/libcurses.a. Это BSD-версия curses.
ncurses представляет собой развитие curses. В версии 1.8.6 она должна быть
совместима с AT&T curses, как это определено в SYSVR4, и иметь
несколько расширений, таких как манипулирование цветами, специальная
оптимизация для вывода, оптимизации, зависящие от терминала, и др.
ncurses была протестирована на множестве систем, таких как Sun-OS, HP
и Linux. Автор рекомендует предпочесть ncurses всему остальному. В
SYSV Unix системах (например, Solaris) должна существовать библиотека
curses с теми же функциональными возможностями, что и ncurses (на
самом деле солярисовская curses имеет немного больше функций и
поддержку мыши).8.1 Функции ввода/вывода в libc
8.1.1 Форматированный вывод
преобразует выводимые аргументы в соответствии с шаблоном и записывает его в
stream. Формат определяется аргументом format.
Функция возвращает число записанных символов или отрицательное число в
случае ошибки.
Форматированный аргумент будет при печати прижат влево на своем поле.
Каждое число будет напечатано со знаком, например, +12 или
-2.32.
Если первый символ не знак, то будет вставлен пробел.
Для чисел ширина поля будет заполнена слева нулями.
Изменяет вывод в зависимости от трансформации для аргумента:
Трансформированный аргумент печатается в поле, ширина которого не
меньше, чем сам аргумент. С этим числом вы можете увеличить ширину
поля. Если аргумент меньше, то оставшаяся часть поля заполняется
пробелами или нулями.
То же самое, что fprintf(stdout, ...).
То же, что и printf(...), но вывод будет записан в символьный
указатель s (с последующим \
0). Вы должны
захватить достаточно памяти для s!
vfprintf(FILE *stream, const char *format, va_list arg)
vsprintf(char *s, const char *format, va_list arg)
То же, что и для вышеописанных функций, только список аргументов
находится в arg.Символ Форматируется в d,i int signed, десятиричный o int unsigned, восьмеричный, без предваряющего 0 x,X int unsigned, шестнадцатиричный, без предваряющего 0x u int unsigned, десятиричный c int (unsigned) одиночный символ s char * до \0 f double как [-]mmm.ddd e,E double как [-]m.dddddde+xx, [-]m.dddddde-xx g,G double использует %e или %f когда нужно p void * n int * % %
Таблица 8.1: Libc: трансформации printf
8.1.2 Форматированный ввод
fscanf(...) читает из stream и преобразует ввод по правилам,
определяемым в format. Результаты помещаются в аргументы, заданные в
"..." (эти аргументы должны быть указателями!). Чтение
заканчивается, когда в format исчерпаны правила форматирования.
fscanf вернет EOF, при первом достижении конца файла или при
возникшей ошибке. Если этого не случится, будет возвращено количество
трансформированных аргументов.
То же, что fscanf(stdin,...).
То же, что scanf, но ввод производится из строки str.Символ Вход: тип аргумента d десятичный integer: int* i integer: int* (вход может быть восьми
или шестнадцатиричным) o восьмеричный integer: int* (с или без предваряющего 0)
u десятичный unsigned: unsigned int* x шестнадцатиричный integer: int* (с или без предваряющего
0x) c один или более символов: char* (без завершающего /0)
e,f,gf float: float* (такой как [-]m.dddddde+xx,
[-]m.dddddde-xx) p указатель: void* n число трансформированных аргументов: int* [...] непустое множество символов на входе: char* [^...] исключая такие символы: char* % %
Таблица 8.2: Libc: правила scanf
перед d,i,n,o,u,x может стоять h, если указатель short
то же для l, если указатель long
l также может быть перед e,f,g, если указатель double
L может стоять перед e,f,g, если указатель long double
8.2 Библиотека termcap
8.2.1 Введение
gcc [flags] files -ltermcap
8.2.2 Поиск описания терминала
В операционной системе Linux текущее имя терминала содержится в
переменой среды TERM. Поэтому termtype есть результат вызова
getenv(3).
#define buffer 0
char *termtype=getenv("TERM");
int ok;
ok=tgetent(buffer,termtype);
if (ok==1)
/* все нормально, мы имеем вход */
else if(ok==0)
/* Что-то не так с TERM
* проверим сначала termtype, затем базу данных termcap
*/
else
/* Глобальная ошибка */
8.2.3 Описание терминала
Получение свойства с числовым значением, таким как co. Функция
tgetnum(...) возвращает числовое значение, если свойство доступно, 1 в
противном случае. Заметьте, что возвращаемое значение всегда неотрицательно.
Получение логического свойства. Возвращает 1, если флаг
установлен, 0 в противном случае.
Получение строкового свойства. Возвращает указатель на строку или
NULL в случае отсутствия. В GNU-версии, если area есть NULL, termcap
выделит память сам. termcap больше не позаботится об этом указателе,
если вы не освободите name перед выходом из программы. Такой метод
предпочтителен, поскольку вы не знаете сколько памяти потребуется для
указателя, поэтому позвольте termcap сделать все за вас.8.2.4 Свойства termcap
Boolean
5i принтер не имеет эха на экране am автоматические границы, что означает автоматическое
форматирование строки bs Crtl-H представляет backspace bw backspace на левой границе переносит строку на правую
границу предыдущей da вывести сохраненное над текущим экраном db вывести сохраненное под текущим экраном eo пробел стирает литеру на позиции курсора es esc-последовательности и специальные символы работают в
строке состояния gn родовое устройство hc это терминал твердой копии (hardcopy terminal) HC курсор плохо видно, когда он не на последней линии
hs присутствует линия статуса hz терминал не может напечатать тильды (tilde characters)
in терминал вставляет нули вместо пробелов на пустые места
km терминал имеет мета клавишу mi режим вставки для курсора ms режим стандартного вывода/подчеркивания для курсора
NP нет символов-заполнителей NR ti не обращает teos, терминал может забивать ошибки
ul терминал подчеркивает, но ошибки забивать не может
xb сбой, вызванный столпотворением, F1 посылает ESCAPE, F2
посылает ^C xn сбой новой строки/соединения строк xo терминал использует xon/xoff протокол xs текст, напечатанный поверх выделенного, будет выделен
xt сбой телевизионного луча, неверная табуляция и странный
режим выделения Numeric
co число столбцов dB приостановка на милисекунды для возврата на терминалах
твердой копии dC приостановка на милисекунды для перевода каретки на
терминалах твердой копии dF приостановка на милисекунды для заполнения страницы на
терминалах твердой копии dN приостановка на милисекунды для новой линии на терминалах
твердой копии dT приостановка на милисекунды для табуляции на терминалах
твердой копии dV приостановка на милисекунды для вертикальной табуляции на
терминалах твердой копии it раница между позициями табуляции lh высота мягких меток lm линии памяти lw ширина li число линий Nl число мягких меток pb наименьшая граница, когда требуется дозаполнение sg сбой режима выделения ug сбой режима подчеркивания vt номер виртуального терминала ws ширина линии статуса, если она отлична от ширины экрана
String
!1 клавиша сохранения в верхнем регистре !2 клавиша подвешивания в верхнем регистре !3 клавиша undo в верхнем регистре #1 клавиша помощи в верхнем регистре #2 клавиша home в верхнем регистре #3 клавиша ввода в верхнем регистре #4 клавиша курсор влево в верхнем регистре %0 клавиша redo %1 клавиша помощи %2 клавиша пометки %3 клавиша сообщения %4 клавиша перемещения %5 клавиша следующего объекта %6 клавиша открытия %7 клавиша опций %8 клавиша предыдущего объекта %9 клавиша печати %a клавиша сообщения в верхнем регистре %b клавиша перемещения в верхнем регистре %c клавиша следующего объекта в верхнем регистре %d клавиша опций в верхнем регистре %e клавиша предыдущего объекта в верхнем регистре %f клавиша печати в верхнем регистре %g клавиша redo в верхнем регистре %h клавиша перестановки в верхнем регистре %i клавиша курсор-вправо в верхнем регистре %j клавиша продолжения в верхнем регистре &0 клавиша cancel в верхнем регистре &1 клавиша ссылки &2 клавиша обновления &3 клавиша перестановки &4 клавиша перезапуска &5 клавиша продолжения &6 клавиша сохранения &7 клавиша подвешивания &8 клавиша undo &9 клавиша начала в верхнем регистре *0 клавиша поиска в верхнем регистре *1 клавиша команды в верхнем регистре *2 клавиша копирования в верхнем регистре *3 клавиша создания в верхнем регистре *4 клавиша удаления символа в верхнем регистре *5 клавиша удаления строки в верхнем регистре *6 клавиша выделения *7 клавиша конца в верхнем регистре *8 клавиша очистки линии в верхнем регистре *9 клавиша выхода в верхнем регистре 0 клавиша поиска1 клавиша начала 2 клавиша cancel 3 клавиша закрытия 4 клавиша команды 5 клавиша копирования 6 клавиша создания 7 клавиша конца 8 клавиша ввода/посылки 9 клавиша выхода al клавиша вставки одной линии AL клавиша вставки %1 линий ac цвет блока символов, отображаемых в другой таблице
символов ae конец множества символов из альтернативной таблицы
as начало блока символов в альтернативной таблице bc backspace, если не ^H bl символ звонка bt переход к предыдущему месту табуляции cb очистка от начала линии до курсора cc странный командный символ cd очистка до конца экрана ce очистка до конца линии ch перемещение курсора горизонтально до столбца %1 cl очистка экрана, курсор помещается в начало cm курсор перемещается на строку %1 и колонку %2 (на экране)
CM курсор перемещается на строку %1 и колонку %2 (в памяти)
cr возврат каретки cs область прокрутки от строки %1 до строки %2 ct очистка табуляций cv вертикальное движение курсора до строки %1 dc удаление 1 символа DC удаление %1 символов dl удаление 1 строки DL удаление %1 строк dm начало режима удаления do курсор на 1 линию вниз DO курсор на %1 линию вниз ds убрать строку статуса eA активирование альтернативной символьной таблицы ec удаление %1 символов начиная с позиции курсора ed конец режима удаления ei конец режима вставки ff символ дозаполнения экрана на терминалах твердой копии
fs возврат символа на его позицию перед переходом на строку
статуса F1 строка послана функциональной клавишей F11 ... ...
F9 строка послана функциональной клавишей F19 FA строка послана функциональной клавишей F20 ... ...
FZ строка послана функциональной клавишей F45 Fa строка послана функциональной клавишей F46 ... ...
Fr строка послана функциональной клавишей F63 hd перемещение курсора на пол-линии вниз ho курсор в начало hu перемещение курсора на пол-линии вверх i1 инициализация строки 1 в начале сеанса i3 инициализация строки 3 в начале сеанса is инициализация строки 2 в начале сеанса ic вставка 1 символа IC вставка %1 символов if файл инициализации im начало режима вставки ip вставка времени и необходимых специальных символов после
вставки iP программа инициализации K1 верхняя левая клавиша на keypad K2 центральная клавиша на keypad K3 верхняя правая клавиша на keypad K4 нижняя левая клавиша на keypad K5 нижняя правая клавиша на keypad k0 функциональная клавиша 0 ... ... k9 функциональная клавиша 9 k; функциональная клавиша 10 ka клавиша очистки всех табуляций kA клавиша вставки линии kb клавиша backspace kB клавиша возврата к предыдущему месту табуляции kC клавиша очистки экрана kd клавиша down kD клавиша удаления символа под курсором ke отключение keypad kE клавиша очистки до конца строки kh клавиша курсор home kH клавиша курсор home+down kI вставка символа/клавиша режима вставки kl клавиша курсор left kL клавиша удаления строки kM Mклавиша выхода из режима вставки kN клавиша следующей страницы kP клавиша предыдущей страницы kr клавиша курсор right kR клавиша прокрутки назад/вверх ks включение keypad kS клавиша очистки до конца экрана kt клавиша очистки данной табуляции kT клавиша установки табуляции на этом месте ku клавиша курсор up l0 метка для нулевой функциональной клавиши, если не f0
l1 метка для первой функциональной клавиши, если не f1
l2 метка для второй функциональной клавиши, если не f2
... la метка для десятой функциональной клавиши, если не f10
le курсор влево на 1 символ ll перемещение курсора в нижний левый угол LE курсор влево на %1 символов LF отключение мягких меток LO включение мягких меток mb начало мерцания MC очистка мягких границ md начало режима верхнего регистра me конец всех режимов типа so, us, mb, md, mr mh начало полуяркого режима mk начало темного режима (символы не видны) ML установка левой мягкой границы mm вход терминала в метарежим mo выход терминала из метарежима mp включение защищенного атрибута mr начало режима обращения (reverse mode) MR установка правой мягкой границы nd курсор на 1 символ влево nw команда возврата каретки pc символ-заполнитель pf отключение принтера pk программная клавиша %1 для посылки строки %2, если нажата
пользователем pl программная клавиша %1 для исполнения строки %2 в
локальном режиме pn программная мягкая метка %1 для отображения строки %2
po подключение принтера pO подключение принтера для %1 (<256) байт ps печать содержимого экрана на принтере px программная клавиша %1 для посылки строки %2 в компьютер
r1 сброс строки 1, установка нормальных режимов r2 сброс строки 2, установка нормальных режимов r3 сброс строки 3, установка нормальных режимов RA отключение автоматических границ rc восстановление сохраненной позиции курсора rf сброс строки имени файла RF требование ввода с терминала RI курсор вправо на %1 символов rp повторение символа %1 %2 раз rP заполнение после присланного символа в режиме замены
rs сброс строки RX выключение XON/XOFF управления sa установка атрибутов %1 %2 %3 %4 %5 %6 %7 %8 %9 SA включение автоматических границ sc сохранение позиции курсора se конец режима стандартного вывода sf нормальная прокрутка на одну строку SF нормальная прокрутка на %1 строк so начало режима стандартного вывода sr обратная прокрутка SR прокрутка назад на %1 строк st установка табуляции во всех строках в данной колонке
SX включение XON/XOFF управления ta перемещение к следующей табуляции физического устройства
tc чтение в описании терминала с другой точки входа te конец программы, использующей движение курсора ti начало программы, использующей движение курсора ts перемещение курсора на столбец %1 строки статуса uc подчеркивание символа под курсором и движение курсора
вправо ue конец подчеркивания up курсор на 1 строку вверх UP курсор на %1 строк вверх us начало подчеркивания vb видимый звонок ve нормальный видимый курсор vi невидимый курсор vs курсор стандартного вывода wi установка окна со строки %1 до строки %2 и столбцов с %3
до %4 XF символ XOFF, если не ^S 8.3 Введение в ncurses
#include
gcc [flags] files -lncurses
-I/usr/include/ncurses
8.4 Инициализация
Обычно это первая функция, вызываемая из программы, использующей ncurses. В
некоторых случаях полезно вызывать slk_init(int), filter(),
ripoffline(...) или use_env(bf) перед initscr(). Для
работы с несколькими терминалами (или тестирования возможностей системы) Вы
можете использовать newterm(...) вместо initscr().
stdscr=initscr();
поскольку initscr() сделает это за вас. Если возвращен ERR, ваша
программа должна завершиться, поскольку ни одна функция ncurses не
будет работать:
if (!(initscr()))
{
fprintf(stderr,"type: initscr() failed\n\n");
exit (1);
}
Работая в ncurses с несколькими терминалами, вы должны вызвать для каждого из
них newterm(...) вместо initscr(). type это имя
терминала как оно содержится в $TERM (ansi, xterm, vt100, например).
outfd это указатель для вывода, infd указатель для ввода. Для
каждого терминала, открытого newterm(...), следует вызывать
endwin().
При помощи set_term(SCREEN) Вы можете переключать текущий
терминал. Все функции будут работать с текущим терминалом,
установленным set_term(SCREEN).
endwin() произведет очистку, восстановит режимы терминала,
сохраненные перед вызовом initscr(), и поставит курсор в левый
верхний угол экрана. Не забудьте закрыть все окна перед тем, как вызвать
endwin(), перед выходом из вашей программы.
Возвращает TRUE, если после endwin() была вызвана refresh(),
иначе FALSE.
Вызывается после endwin() для высвобождения всех занятых
ресурсов, когда SCREEN больше не нужен.8.5 Окна
begy и begx координаты верхнего левого угла окна. nlines
это число линий (integer); ncols число колонок (integer).
0 begx
| |
0 | | COLS
- - - - -------|-------------------------|------->
| | ncols |
begy | |<. . . . . . . . . . . .>|
- - - -|- - - -|-------------------------|
| ^| |
| .| |
| .| |
|nlines.| newwin(nlines, ncols, |
| .| begy, begx) |
| v| |
|- - - -|-------------------------|
LINES |
v
Рисунок 8.1: Ncurses: схема для newwin
WINDOW *mywin;
mywin=newwin(0,0,0,0);
открытое окно получает размеры экрана.
#define MYLINE (int) ((LINES-22)/2)
#define MYCOL ((COLS-70)/2)
#define MYLINES 22
#define MYCOLS 70
...
WINDOW *win;
...
if(!(initscr())){
fprintf(stderr, "type: iniscr() failed\n\n");
exit(1);
}
...
if ((LINES<22)||(COLS<70)){
fprintf(stderr, "screen too small\n\n");
endwin(); exit (1);
}
win=newwin(MYLINES,MYCOLS,MYLINE,MYCOL);
#define MYROWS (int) (LINES/2+LINES/4)
#define MYCOLS (int) (COLS/2+COLS/4)
#define LEFTROW (int) ((LINES-MYROWS)/2)
#define LEFTCOL (int) (((COLS-2)-MYCOLS)/2)
#define RIGHTROW (int) (LEFTROW)
#define RIGHTCOL (int) (LEFTROW+2+MYCOLS)
#define WCOLS (int) (MYCOLS/2)
...
WINDOW *leftwin, *rightwin;
...
leftwin=newwin(MYROWS, WCOLS, LEFTROW, LEFTCOL);
rightwin=newwin(MYROWS, WCOLS, RIGHTROW, RIGHTCOL);
...
Удаляет окно win. Подокна win удаляются перед win.
Будут освобождены все ресурсы, занятые win. Удаляйте все открытые
вами окна перед вызовом endwin().
Перемещает окно на координаты by,bx. Если это означает выход за
пределы экрана, то ничего не произойдет и будет возвращен ERR.
Возвращает подокно в середине origwin. Когда вы изменяете одно
из двух окон (origwin или новое), это изменение отразится на обоих
окнах. Вызывайте touchwin(origwin) перед следующим refresh().
То же, что subwin(), begx и begy относятся не к
экрану, а к origwin.
Перенесет win за пределы родительского окна.
Дублирует окно win.
int overwrite(win1, win2)
overlay(...) скопирует весь текст из win1 в win2,
игнорируя пропуски. overwrite(...) делает то же самое, но копирует
вместе с пропусками.
dmaxrow, dmaxcol, overlay)
То же, что overlay(...) и overwrite(...), но позволяет
контролировать область окна для копирования.8.6 Вывод
int waddch(win, ch)
int mvaddch(y, x, ch)
int mvwaddch(win, y, x, ch)
Эти функции выводят символ в окно. Они работают с окном; Вам
нужно вызвать refresh(), чтобы поместить окно на экран. Функции
addch(...) и waddch(...) помещают символ ch в окно
или win. mvaddch(...) и mvwaddch(...) предварительно
ставят курсор на позицию y,x.
int addnstr(str, n)
int waddstr(win, str)
int waddnstr(win, str, n)
int mvaddstr(y, x, str)
int mvaddnstr(y, x, str, n)
int mvwaddstr(win, y, x, str)
int mvwaddnstr(win, y, x, str, n)
Эти функции заносят строку в окно и эквивалентны сериям вызовов
addch(...). str это строка, заканчивающаяся символом с
кодом 0 ("blafoo\
0"). Функции, начинающиеся с
w, заносят str в окно win, остальные в stdscr.
Функции с n пишут n символов строки str. Если
n равен -1, будет записана вся строка str.
int addchnstr(chstr, n)
int waddchstr(win, chstr)
int waddchnstr(win, chstr, n)
int mvaddchstr(y, x, chstr)
int mvaddchnstr(y, x, chstr, n)
int mvwaddchstr(win, y, x, chstr)
int mvwaddchnstr(win, y, x, chstr, n)
Эти функции копируют chstr в окно stdscr или win.
Начальной позицией является позиция курсора. Функции с n пишут
n символов строки chstr. Если n равен -1, будет
записана вся строка. Курсор не двигается и символы не контролируются. Эти
функции быстрее, чем addstr(...). chstr является указателем
на массив элементов chtype.
int wechochar(win, ch)
То же, что addch(...), waddch(win), с последующим
refresh(), wrefresh(win).8.6.1 Форматированный вывод
int wprintw(win, fmt, ...)
int mvprintw(y, x, fmt, ...)
int mvwprintw(win, y, x, fmt, ...)
int vwprintw(win, fmt, va_list)
Эти функции соответствуют printf(...) и подобным ей функциям libc.
8.6.2 Вставка символов и строк
int winsch(win, c)
int mvinsch(y,x,c)
int mvwinsch(win,y,x,c)
Символ ch вставляется слева от курсора, и все символы сдвигаются
на одну позицию вправо. Самый правый символ строки может быть потерян.
int winsertln(win)
Вставляет чистую строку над текущей. Нижняя строка будет потеряна.
int winsdelln(win, n)
Для положительного n эти функции вставляют n строк в
соответствующем окне (n нижних строк будут потеряны). Для
отрицательных n будут удалены n строк под курсором,
оставшиеся сдвинутся вверх.
int insnstr(str, n)
int winsstr(win, str)
int winsnstr(win, str, n)
int mvinsstr(y, x, str)
int mvinsnstr(y, x, str, n)
int mvwinsstr(win, y, x, str)
int mvwinsnstr(win, y, x, str, n)
Эти функции вставят str в текущую строку слева от курсора
(сколько возможно до конца строки). Символы справа от курсора
сдвигаются вправо и удаляются достигнув конца строки. Курсор остается
на месте.
8.6.3 Удаление символов и строк
int wdelch(win)
int mvdelch(y, x)
int mvwdelch(win, y, x)
Выполняется удаление символа под курсором и сдвиг оставшейся справа от
курсора строки на одну позицию влево.
int wdeleteln(win)
Удаление строки под курсором и перемещение нижележащих линий на
одну позицию вверх. Последняя линия окна будет очищена.8.6.4 Боксы и линии
int wborder(win, ls, rs, ts, bs, tl, tr, bl, br)
int box(win, vert, hor)
Очерчивают соответствующее окно (stdscr или win). В таблице 8.3
вы увидите символы и их значения по умолчанию для box(...). На
картинке вы увидите позиции символов для бокса.
int wvline(win, ch, n)
int hline(ch, n)
int whline(win, ch, n)
Эти функции вычерчивают вертикальную или горизонтальную прямую
начиная с позиции курсора. ch это используемый символ, n
задает число таких символов. Позиция курсора не изменяется.8.6.5 Фоновый (background)
символ
void wbkgdset(win, ch)
Устанавливает фоновый символ и атрибут для экрана или окна.
Атрибут в ch будет OR-нут с каждым непробельным символом окна. Фон
становится частью окна и не изменяется при прокрутке и вводе/выводе.
int wbkgd(win, ch)
Установит в ch фоновый символ и атрибут.Код символа Позиция По умолчанию tl левая верхняя ACS_ULCORNER ts верхняя сторона ACS_HLINE tr правая верхняя ACS_URCORNER ls левая сторона ACS_VLINE rs правая сторона ACS_VLINE bl левая нижняя ACS_LLCORNER bs нижняя сторона ACS_HLINE br правая нижняя ACS_LRCORNER rt правая средняя ACS_RTEE lt левая средняя ACS_LTEE tt верхняя средняя ACS_TTEE bt нижняя средняя ACS_BTEE
Таблица 8.3: Ограничительные символы Ncurses
tl ts tt ts tr
|------------|------------|
| |
ls| | |rs
| |
| | |
lt|- - - - - - - - - - - - -|rt
| | |
| |
ls| | |rs
| |
|------------|------------|
bl bs bt bs br
8.7 Ввод
int wgetch(win)
int mvgetch(y, x)
int mvwgetch(win, y, x)
getch() прочитает ввод с терминала. Если режим паузы установлен,
getch() будет ждать нажатия клавиши. Если нет, вернет клавишу из
буфера ввода или ERR, если буфер пуст. mvgetch(...) и
mvwgetch(...) сначала установят курсор на позицию y,x.
w-функции читают ввод с терминала, связанного с окном win,
getch() и mvgetch(...) с stdscr.
Вернет ch в буфер ввода.
int wgetstr(win, str)
int mvgetstr(y, x, str)
int mvwgetstr(win, y, x, str)
int wgetnstr(win, str, n)
Эти функции проделают серию вызовов getch(), пока не будет
получена новая строка. Символы помещаются в str, поэтому не
забывайте захватывать память для вашего символьного указателя перед вызовом
getstr(). Если включено эхо, то строка отображается (используйте
noecho(), чтобы его отключить) и пользовательские символы удаления
будут проинтерпретированы.
chtype winch(win)
chtype mvinch(y, x)
chtype mvwinch(win, y, x)
Эти функции возвращают символ с экрана или окна. Поскольку
возвращается тип chtype, возвращается и атрибут. Информация об
атрибуте может быть получена с помощью констант A_* (см.
таблицу 8.4).
int innstr(str, n)
int winstr(win, str)
int winnstr(win, str, n)
int mvinstr(y, x, str)
int mvinnstr(y, x, str, n)
int mvwinstr(win, y, x, str)
int mvwinnstr(win, y, x, str, n)
Возвращает символьную строку из экрана или окна.
int inchnstr(chstr, n)
int winchstr(win, chstr)
int winchnstr(win, chstr, n)
int mvinchstr(y, x, chstr)
int mvinchnstr(y, x, chstr, n)
int mvwinchstr(win, y, x, chstr)
int mvwinchnstr(win, y, x, chstr, n)
Возвращает строку типа chtype из экрана или окна вместе с
атрибутом для каждого символа. Пока не реализована; lib_inchstr не
включена в библиотеку ncurses.
8.7.1 Форматированный ввод
int wscanw(win, fmt, ...)
int mvscanw(y, x, fmt, ...)
int mvwscanw(win, y, x, fmt, ...)
int vwscanw(win, fmt, va_list)
Функции эквивалентны scanf(...) из libc (см.
раздел 8.1.2). Входом для сканирования служит вызов
wgetstr(...).8.8 Опции
Опции вывода
void idcok(win, bf)
Включение и отключение возможностей вставки/удаления для окна
терминала (idlok(...) для строк, idcok(...) для символов).
Если устанавливается TRUE, то каждое изменение в окне win вызывает
физическое обновление экрана. Это может ухудшить характеристики
программы, поэтому значение по умолчанию FALSE.
Если bf равен TRUE, то следующий вызов wrefresh(win)
очистит экран и полностью его перерисует (Ctrl+L в редакторе vi).
По умолчанию ncurses ставит курсор там, где он был при последнем обновлении
окна. Программы, не использующие курсор, могут установить leaveok(...)
TRUE и сэкономить время, требующееся для движения курсора.
int nonl()
Управление переходом на новую строку. После nl() произойдетт
возврат каретки и дозаполнение; nonl() отключает контроль. В последнем
случае ncurses может ускорить перемещение курсора.8.8.1 Опции ввода
TRUE активизирует keypad на клавиатуре во время ожидания ввода.
Для функциональных клавиш и стрелок keypad ncurses вернет код клавиши,
определенный как KEY_* в ncurses.h. Это очень удобно для клавиатуры
ПК, потому что вы имеете возможность и пользоваться цифровым блоком, и
перемещать курсор.
Если TRUE, то коды клавиш, возвращаемые getch(), 7-битовые
(верхний бит не учитывается).
int nocbreak()
int crmode()
int nocrmode()
cbreak() и nocbreak() устанавливают или снимают режим терминала
CBREAK. Когда CBREAK установлен, читаемый ввод немедленно доступен
программе, когда нет будет буферизован до получения целой строки.
Замечание: crmode() и nocrmode() существуют для
повышения совместимости, их использовать не нужно.
int noraw()
Устанавливает или снимает режим RAW. RAW это то же, что и
CBREAK, только без обработки специальных символов.
int noecho()
Вызывайте echo() для отображения ввода пользователя и noecho(),
чтобы его скрыть.
То же, что и cbreak(), но с паузой в t секунд.
Терминал устанавливается в неблокируемый режим. getch() вернет
ERR, если ввод не готов. Если bf есть FALSE, то getch() будет ждать
нажатия клавиши.
int wtimeout(win, t)
Эти функции рекомендуется использовать вместо halfdelay(t) и
nodelay(win, bf). Результат getch() зависит от значения
t. При положительном t считывание блокируется на t
милисекунд; при t, равном нулю, блокировки не происходит; при
отрицательном t программа блокируется, пока ввод не станет возможен.
Если bf равен TRUE, getch() будет использовать односекундный
таймер для интерпретации вводимой последовательности, начинающейся с
ESCAPE и т.п.
При fd, равном -1, никакой проверки печати производиться не
будет, при других значениях ncurses будет использовать для таких
проверок файловый дескриптор fd вместо stdin.
При активизации истинным bf нажатая клавиша прерывания (типа
quit, break) очистит очередь драйвера tty.8.8.2 Атрибуты терминала
Возвращает скорость терминала в bps (бит в секунду).
Возвращает текущий символ erase.
Возвращает текущий символ kill.
int has_il()
has_ic() возвращает TRUE, если терминал может вставлять/удалять
символ, has_il() возвращает TRUE, если терминал может
вставлять/удалять строку. В противном случае возвращается ERR.
Указатель предоставляет доступ к описанию текущего терминала.
Возвращает содержимое TERM из пользовательской среды.8.8.3 Использование опций
char c;
noecho();
timeout(-1);
nonl();
cbreak();
keypad(stdscr,TRUE);
while(c=getch()){
switch(c){
case 'q': your_quit_function();
default: break;
}
}
KEY_UP KEY_RIGHT KEY_A1 KEY_B2 KEY_C1
KEY_DOWN KEY_LEFT KEY_A3 KEY_C3
для клавиш перемещения курсора. Для просмотра файла цикл может выглядеть
примерно так:
int loop=TRUE;
char c;
enum{UP,DOWN,RIGHT,LEFT};
noecho();
timeout(-1);
nonl();
cbreak();
keypad(stdscr,TRUE);
while(loop==TRUE){
c=getch();
switch(c){
case KEY_UP:
case 'u':
case 'U': scroll_s(UP);
break;
case KEY_DOWN:
case 'd':
case 'D': scroll_s(DOWN);
break;
case KEY_LEFT:
case 'l':
case 'L': scroll_s(LEFT);
break;
case KEY_RIGHT:
case 'r':
case 'R': scroll_s(RIGHT);
break;
case 'q':
case 'Q': loop=FALSE;
default: break;
}
}
WINDOW *maskwin;
WINDOW *mainwin;
char *ptr=(char *)malloc(255);
...
mainwin=newwin(3,37,9,21);
maskwin=newwin(1,21,10,35);
...
werase(mainwin);
werase(maskwin);
...
box(mainwin,0,0)
mvwaddstr(mainwin,1,2,"Inputstring:");
...
wnoutrefresh(mainwin);
wnoutrefresh(maskwin);
doupdate();
...
mvwgetstr(maskwin,0,0,ptr);
...
delwin(maskwin);
delwin(mainwin);
endwin();
free(ptr);
8.9 Очистка окна и линий
int werase(win)
werase(...) и erase() скопируют пробелы на каждую позицию окна
win или stdscr. Например, если вы установили атрибуты цвета в окне и
вызвали werase(), окно должно быть окрашено. Однако автор имел
некоторые проблемы с COLOR_PAIRS, если определял другие атрибуты, а
затем черный по белому, так он писал его собственную стирающую функцию
(это низкоуровневый доступ к структуре WINDOW):
void NewClear(WINDOW *win)
{
int y,x;
for (y = 0; y <= win -> _maxy; y++)
for (x = 0; x <= win -> _maxx; x++)
(chtype *) win-> _line[y][x] = ' '|win-> _attrs;
win -> _curx = win -> _cury = 0;
touchwin(win);
}
#define BLANK ' '|A_NORMAL,
то другие атрибуты окна теряются, пока идет стирание строки.
int wclear(win)
То же, что erase(), но будет также установлен clearok() (экран
будет очищен с последующим обновлением).
int wclrtobot(win)
Очистка текущей строки курсора (начинается с символа справа от
курсора) и строки под курсором.
int wclrtoeol(win)
Очистка текущей строки начиная справа от курсора и до конца строки.8.10 Обновление терминала
int wrefresh(win)
refresh() копирует stdscr на терминал, а wrefresh(win) копирует
изображение окна в stdscr и затем делает curscr подобным stdscr.
int doupdate()
wnoutrefresh(win) копирует окно win только в stdscr. Это означает, что
вывода на терминал не производится, но виртуальный экран stdscr на самом деле
выглядит именно так, как того хочет программист. doupdate() произведет
вывод на терминал. Программа может менять различные окна, вызывая
wnoutrefresh(win) для каждого окна, а затем достаточно один раз
вызвать doupdate(), чтобы обновить физический экран.
main() changewin(WINDOW *win)
{ {
WINDOW *win1,*win2; ... /* здесь мы изменяем */
... ... /* строки */
changewin(win1); wrefresh(win);
changewin(win2); return;
... }
}
main() changewin(WINDOW *win)
{ {
WINDOW *win1,*win2; ... /* здесь мы изменяем */
... ... /* строки */
changewin(win1); wnoutrefresh(win);
changewin(win2); return;
doupdate(); }
...
}
int wredrawln(win, bline, nlines)
Используйте эти функции, когда перед записью чего-нибудь нового
требуется выбросить несколько строк или целый экран (может быть строки
запорчены или что-либо вроде этого).
int touchline(win, start, count)
int wtouchln(win, y, n, changed)
int untouchwin(win)
Говорит ncurses, что были произведены манипуляции с целым окном
или линиями от start до start+count. Например, когда есть
несколько окон, перекрывающих друг друга (как в примере type.c),
изменение одного из них никак не повлияет на изображение других.
int is_wintouched(win)
При помощи этих функций вы можете проверить, были ли линия line или
окно win захвачены со времени последнего вызова refresh().
8.11 Видеоатрибуты и цвет
Определение Атрибут A_ATTRIBUTES маска для атрибутов (chtype) A_NORMAL нормальный, переустановка всего остального A_STANDOUT наиболее яркий режим A_UNDERLINE подчеркивание A_REVERSE обратное изображение A_BLINK мигание A_DIM тусклый или полуяркий режим A_BOLD четкий или очень яркий режим A_ALTCHARSET использование альтернативной символьной таблицы
A_INVIS невидимый режим A_PROTECT не понял A_CHARTEXT маска для действующих символов (chtype) A_COLOR маска для цвета COLOR_PAIR(n) установка цветовой пары n PAIR_NUMBER(a) получение цветовой пары, лежащей в атрибуте a
Определение Цвет COLOR_BLACK черный COLOR_RED красный COLOR_GREEN зеленый COLOR_YELLOW желтый COLOR_BLUE синий COLOR_MAGENTA пурпурный COLOR_CYAN голубой COLOR_WHITE белый
int wattroff(win, attr)
int attron(attr)
int wattron(win, attr)
Включают или отключают указанный атрибут attr, не влияя на другие
атрибуты в окне (stdscr или win).
int wattrset(win, attr)
Установка атрибута в attr в stdscr или win.
int standend()
int wstandout(win)
int wstandend(win)
Включают атрибут наиболее яркого режима для окна (stdscr или win).
Выдает текущие атрибуты для окна win.
Возвращает TRUE, если терминал имеет цвета. Перед тем, как
использовать цвета, проверьте терминал has_colors(), а перед этим
проинициализируйте цвета start_color().
TRUE, если терминал может переопределять цвета.
Цветовая инициализация. Эта функция должна быть вызвана перед
использованием цветов!
Если вы используете цвета в атрибутах окна, то сначала вы должны определить
цветовую пару через init_pair(...). fg и bg это
цвета переднего и заднего плана, спаренные в pair. pair
принимает значения от 1 до COLORPAIRS-1. 0 не ошибка, но
зарезервирован для черного и белого. Определенную однажды pair
можно использовать как атрибут. К примеру, нужны красные символы на синем:
init_pair(1,COLOR_RED,COLOR_BLUE);
Теперь вызовем wattr(...) для установки новой пары цветов для
win:
wattr(win,COLOR_PAIR(1));
Или соединим цветовые пары с другими атрибутами, например:
wattr(win,A_BOLD|COLOR_PAIR(1));
wattr(win1,A_STANDOUT|COLOR_PAIR(1));
Первый вызов установит цветовую пару и атрибут BOLD, второй подключит режим
STANDOUT, и вы получите светлый красный на синем экране.
Вернет цвета переднего и заднего плана из pair.
Изменит цветовые компоненты r, g и b для
color. r, g и b находятся в диапазоне от
1 до COLORS-1.
Получение компонентов r, g и b для color.
void CheckColor(WINDOW *win1, WINDOW *win2)
{
start_color();
if (has_colors()){
/* Хорошо, у нас есть цвета, определяем цветовые пары для
* цветов переднего и заднего плана
*/
init_pair(1,COLOR_BLUE,COLOR_WHITE);
init_pair(2,COLOR_WHITE,COLOR_RED);
/* теперь используем уже определенные цветовые пары для окон */
wattrset(win1,COLOR_PAIR(2));
wattrset(win2,COLOR_PAIR(1));
}
else
{
/* Нет цвета (может быть vt100 или xterm). Ладно, будем
* пользоваться вместо этого черно-белыми атрибутами.
*/
wattrset(win1,A_REVERSE);
wattrset(win2,A_BOLD);
}
return;
}
8.12 Координаты курсора и окна
int wmove(win, y, x)
Движение курсора stdscr или win. Для функций ввода/вывода определяются
дополнительные макросы, передвигающие курсор перед вызовом данных функций.
int curs_set(bf)
Переключает видимость/невидимость курсора, если терминал имеет
такую возможность.
Возвращает координаты курсора. Замечание: это макрос.
Если win подокно, getparyx(...) сохранит координаты окна
относительно родительского окна. В противном случае y и x
установятся в -1.
void getmaxyx(win, y, x)
int getmaxx(win)
int getmaxy(win)
Сохранит начальные и размерные координаты для win в y и
x.
int setsyx(int y, int x)
Сохранит виртуальный курсор экрана в y и x или установит
этот курсор. При y и x, равных -1, getsyx(...)
установит leaveok.8.13 Прокрутка
Если TRUE, текст в окне win будет прокручен вверх на одну строку,
когда курсор находится в правом нижнем углу и напечатан символ. Если
FALSE, то курсор остается на прежней позиции.
Эта функция прокрутит окно (и строки в структуре данных) на одну
строку вверх.
int wscrl(win, n)
Эти функции прокрутят окно stdscr или win вверх или вниз, в
зависимости от целого n. Если n положительное, произойдет
прокрутка окна на n линий вверх, если n отрицательное на
n линий вниз.
int wsetscrreg(win, t, b)
Устанавливают программную область прокрутки.
enum{PREV,NEXT};
void scroll_s(WINDOW *win, int scroll)
{
/* пробуем, должны ли мы прокрутить вниз и если что-нибудь есть,
* то прокрутить
*/
if((scroll==NEXT)&&(beg<=(max_s-18))){
/* одна строка вниз */
beg++;
/* задаем права на прокрутку */
scrollok(win, TRUE);
/* прокручиваем */
wscrl(win, +1);
/* отклоняем права на прокрутку */
scrollok(win, FALSE);
/* устанавливаем новую строку в последней линии */
mvwaddnstr(win,17,0,s[beg+17],66);
/* очищаем последнюю строку от последнего символа до конца
* строки. Иначе атрибуты не будут учтены.
*/
clear_line(66,win);
}
else if((scroll==PREV)&&(beg>0)){
beg--;
scrollok(win, TRUE);
wscrl(win, -1);
scrollok(win, FALSE);
mvwaddnstr(win,0,0,s[beg],66);
clear_line(66,win);
}
wrefresh(win);
return;
}
8.14 Заполнители
8.15 Мягкие метки
int slk_attrset(chtype attr)
int slk_attroff(chtype attr)
Эти функции соответствуют функциям attron(attr),
attrset(attr) and attroff(attr).8.16 Разное
Последние шесть числовых свойств присутствуют в структуре term SYSV, но не задокументированы в man page. Комментарии взяты из заголовка этой структуры.
label_format | fln | Lf | ?? |
set_clock | sclk | SC Установка времени дня | |
display_clock | dclk | DK | Вывод времени дня на экран |
remove_clock | rmclk | RC | Удаление времени дня ?? |
create_window | cwin | CW | Определение окна #1 с параметрами от #2, #3 до #4 #5 |
goto_window | wingo | WG | Переход в окно #1 |
hangup | hup | HU | Положить трубку телефона |
dial_phone | dial | DI | Набрать номер телефона #1 |
quick_dial | qdial | QD | Набрать номер телефона #1 без дальнейшего повторения |
tone | tone | TO | Выбрать длинные гудки |
pulse | pulse | PU | Выбрать короткие гудки |
flash_hook | hook | fh | Нажать телефонную клавишу |
fixed_pause | pause | PA | Пауза на 2-3 секунды |
wait_tone | wait | WA | Ожидание ответного сигнала |
user0 | u0 | u0 | Пользовательская строка #0 |
user1 | u1 | u1 | Пользовательская строка #1 |
user2 | u2 | u2 | Пользовательская строка #2 |
user3 | u3 | u3 | Пользовательская строка #3 |
user4 | u4 | u4 | Пользовательская строка #4 |
user5 | u5 | u5 | Пользовательская строка #5 |
user6 | u6 | u6 | Пользовательская строка #6 |
user7 | u7 | u7 | Пользовательская строка #7 |
user8 | u8 | u8 | Пользовательская строка #8 |
user9 | u9 | u9 | Пользовательская строка #9 |
get_mouse | getm | Gm | Curses должна предоставить события от мыши |
key_mouse | kmous | Km | ?? |
mouse_info | minfo | Mi | Информация о состоянии мыши |
pc_term_options | pctrm | S6 | Опции терминала ПК |
req_mouse_pos | reqmp | RQ | Требование отчета о позиции мыши |
zero_motion | zerom | Zx | Следующий символ не двигается |
Обычно ПК имеет как минимум 2 последовательных и 1 параллельный интерфейс. Они являются специальными устройствами и отображаются следующим образом:
Разница между /dev/ttyS* и /dev/cua* в способе вызова open(). /dev/cua* предполагают использование как устройств вывода, и, как следствие, имеют другие установки по умолчанию относительно /dev/ttyS*. /dev/ttyS* инициализируются для входящих и выходящих сигналов. По умолчанию устройства являются управляющими устройствами для процесса, их открывающего. Обычно ioctl() обрабатывает все эти специальные устройства, однако POSIX предпочитает определение новых функций для асинхронных терминалов. Эти функции жестко привязаны к структуре termios. Оба варианта требуют подключения termios.h.
Мышь подключается как к последовательному порту, так и напрямую к шине. Разные типы мышек посылают разные типы данных, что немного усложняет программирование. Однако, Эндрю Хэйлет (Andrew Haylett) был так добр, что поставил общий копирайт на свою программу selection, то есть вы можете использовать его функции работы с мышью. В этом руководстве вы найдете пре-релиз selection-1.8 с пометкой COPYRIGHT. Правда, X11 уже предложили удобный API мышки, поэтому программы Эндрю следует использовать только для не-X11 приложений. Из пакета selection вам потребуются только модули mouse.h и mouse.c.
Для получения событий от мыши вам необходимы ms_init() и get_ms_event(). ms_init() требует следующих 10 аргументов.
get_ms_event() нуждается только в указателе на структуру ms_event. Если get_ms_event() возвращает -1, то произошла ошибка. В случае успеха возвращается 0, а ms_event содержит текущее состояние мыши.
Используйте Hayes Commands для управления модемом. Для контроля порта rs232 вам потребуется termios. Смотри пример miniterm.c.
Смотрите пример checklp.c. Не используйте termios для управления принтерного порта, пользуйтесь ioctl, inb/outnb, если необходимо, команды Epson, Postscript, PCL и т.д. в linux/lp.h вызовы ioctl: LPCHAR, LPTIME, LPABORT, LPSETIRQ, LPGETIRQ, LPWAIT inb и outb определяют статус и управляют портом.
Смотрите пример js.c в модуле для джойстика пакета ядра. linux/joistick.h вызовы ioctl: JS_SET_CAL, JS_GET_CAL, JS_SET_TIMEOUT, JS_GET_TIMEOUT, JS_SET_TIMELIMIT, JS_GET_TIMELIMIT, JS_GET_ALL, JS_SET_ALL. Операция чтения /dev/jsn возвращает структуру JS_DATA_TYPE.
Данное руководство призвано помочь программистам разобраться с особенностями Linux. Оно также освещает проблемы переноса программ с других операционных систем; влияющие на старые программы изменения в ядре и в системных вызовах, такие как последовательный ввод/вывод и работа по сети.