Руководство программиста для Linux
================================================================
Linux Programmer's
Guide
Sven Goldt, Sven van der Meer,
Skott Burkett, Matt Welsh ver 0.4
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- 2 -
Данное руководство далеко не полное. Первая версия (0.1) вышла в
свет в сентябре 1994 года. По причине отсутствия личного состава и
информации акцент был сделан на системных вызовах. Планировалось
описание библиотечных функций и существенных изменений ядра, а также
экскурс в наиболее важные области, такие как сети, звук, графика и
асинхронный ввод/вывод. Может быть, позже будут включены некоторые
соображения о том, как создать разделяемые библиотеки и указатели на
полезные инструменты.
* Введение
Однажды автор инсталлировал Linux на своем ПК, чтобы больше
узнать о системе управления. Он пытался инсталировать slip сервер,
который не таботал с теневыми программами и mggetty. Пришлось
подправить sliplogin и все работало, но до нового релиза Linux 1.1.
Никто не смог объяснить что приключилось, а комментарии Расса Нельсона
(Russ Nelson) об измиенениях в ядре с версии 0.99 не слишком помогли.
Данное руководство призвано помочь программистам разобраться с
особенностями Linux. Оно также освещает проблемы переноса программ с
других операционных систем; влияющие на старые программы изменения в
ядре и в системных вызовах, такие как последовательный ввод/вывод и
работа по сети.
1. Операционная система LINUX
В марте 1991 Л.В.Торвальдс (Linus Benedict Torvalds) купил
мультизадачную операционную систему Minux для своего AT 386. Он
использовал ее для того, чтобы создать свою собственную мультизадачную
ОС, которую назвал Linux. В сентябре 1991 года он распространил по
e-mail ее первый прототип среди пользователей Minux. С этого момента
многие стали поддерживать Linux, добавляя драйверы устройств,
разрабатывая разные продвинутые приложения и учитывая соглашения
POSIX. В настоящее время Linux - очень мощная система, но самое
замечательное то, что она free. Ведется работа над Linux-ом для других
платформ.
- 3 -
2. Ядро LINUX
Ядро является базой LINUX-а. Вы можете как угодно переставлять
любую из библиотек, но пока есть ядро есть и LINUX. Оно включает в
себя драйвера устройств, механизм распределения памяти, управление
процессами и связями. Разработчики ядра стараются следовать
рекомендациям POSIX, которые иногда осложняют, а иногда упрощают
программирование. И если ваша программа поведет себя иначе на новой
версии ядра, то вероятнее всего, в этой версии учтена еще какая-нибудь
рекомендация POSIX. Информацию о ядре для программиста можно найти в
Linux Kernel Hacker's Guide (Справочное руководство по ядру LINUX для
программиста).
3. Библиотека libc
libc: ISO 8859.1, , YP функции, функции
кодирования, некоторые базовые теневые программы (по умолчанию не
добавлены), ..., старые программы для совместимости с libcompact (по
умолчанию не запущены), сообщения об ошибках на английском,
французском и немецком, bsd 4.4lite-совместимые программы работы с
экраном в libcourses, bsd-совместимые программы в libbsd, программы
работы с экраном в libtermcap, поддержка баз данных в libdbm,
математика в libm, точка запуска программ в crt0.o (entry to execute
programs in crt0.o ??? (-авторский знак)), байт sex-информации в
libeee (??? объясните мне, лучше, что это такое, вместо того, чтобы
ржать - автор), профилирование пространства пользователя в libgmon.
Автор надеется, что кто-нибудь из разработчиков libc напишет эту
главу как положено. Все, что автор может на данный момент сказать, это
то, что исполняемый формат a.out собираются поменять на elf
(executable and lincable format - исполняемый и собираемый формат),
что подразумевает изменения и в разделяемых библиотеках. В настоящий
момент поддерживаются оба формата.
Большая часть libc находится под лицензией GNU (Library GNU
Public License). Хотя попадаются и специальные исключения, например
crt0.o. Для коммерческого использования это означает запрет на
статически линкуемые программы. Динамически линкуемые программы,
- 4 -
однако, тоже являются специальными исключениями.
4. Системные вызовы
Системный вызов - это требование к ОС (к ядру) произвести
аппаратно/системно специфическую или привилегированную операцию. В
Linux-1.2 были определены 140 системных вызовов. Такие вызовы, как
close() реализованы в Linux libc. Эта реализация часто включает в себя
макрос, который в конце концов вызывает syscall(). Параметры,
передаваемые syscall-y - это номер системного вызова, перед которым
ставятся требуемые аргументы. Номера системных вызовов можно найти в
, а обновленные вместе с новой версией libc - в
. Если появились новые системные вызовы, но их до сих
пор нет в libc, вы можете использовать syscall(). Как пример,
рассмотрим закрытие файла при помощи syscall-а (не советуем, однако):
#include
extern int syscall(int,...)
int my_close(int filedescriptor)
{
return syscall(SYS_close, filedescriptor);
}
На i386 системные вызовы ограничены 5-ю аргументами кроме номера
вызова из-за аппаратного числа регистров. На другой архитектуре вы
можете поискать макрос _syscall в и там посмотреть
сколько аргументов поддерживается у вас или $how many developers chose
to support$. Макросами _syscall можно пользоваться вместо syscall(),
но это не рекомендуется, поскольку макрос может развернуться в
функцию, которая уже существует в библиотеке. Поэтому только ядреные
хакеры имеют право поиграться с _syscall-ом :). Для демонстрации
посмотрим на пример close(), использующий макрос _syscall.
#include
_syscall1 (int, close, int, filedescriptor);
- 5 -
_syscall1 раскрывается в функцию close(), и мы получаем твикс:
один close() в libc и один в нашей программе.
Возвращаемое syscall()-ом (или _syscall-ом) значение есть -1,
если вызов неудачен и 0 или больше в случае успеха. В случае неудачи
ошибку можно определить по глобальной переменной errno.
Приведем системные вызовы, возможные в BSD и SYS V, но не
допустимые в LINUX: audit(), audition(), fchroot(), getauid(),
getdents(), getmsg(), mincore(), poll(), putmsg(), setaudit(),
setauid().
5. ioсtl
iotcl предназначен для контроля ввода/вывода и используется для
манипуляций с устройством через файловый дескриптор. Формат ioсtl:
ioсtl(unsigned int fd, unsigned int request, unsigned long argument)
Возвращаемое значение есть -1 в случае ошибки, 0 и больше если
команда (request) прошла так же, как другие системные вызовы. Ядро
различает специальные и регулярные файлы. Специальные файлы в основном
находятся в /dev и /proc. Они отличаются от регулярных файлов тем, что
прячут свое описание в драйвер, тогда как регулярные файлы содержат
текст или двоичные данные. Эта философия UNIX-а, которая позволяет
вводить/выводить из любого файла. Но если вам хочется сделать
что-нибудь особенное со специальным файлом, вы можете это сделать при
ioсtl. В основном ioсtl нужен для борьбы со специальными файлами, но
его можно использовать и для обычных.
- 6 -
6. Межпроцессовые коммуникации LINUX
6.1. Введение
Система Linux IPC (Inter-process communication) предоставляет
средства для взаимодействия процессов между собой.
В распоряжении программистов есть несколько методов IPC:
* полудуплексные каналы UNIX
* FIFO (именованные каналы)
* Очереди сообщений в стиле SYSV
* Множества семафоров в стиле SYSV
* Разделяемые сегменты памяти в стиле SYSV
* Сетевые сокеты (в стиле Berkeley) (не охватывается этой
статьей)
* Полнодуплексные каналы (каналы потоков) (не охватывается этой
статьей)
Если эти возможности эффективно используются, то они обеспечивают
солидную базу для поддержания идеологии клиент/сервер в любой
UNIX-системе, включая Linux.
6.2. Полудуплексные каналы UNIX
6.2.1. Основные понятия
Канал - это средство связи стандартного вывода одного процесса со
стандартным вводом другого. Каналы - это старейший из инструментов
IPC, существующий приблизительно со времени появления самых ранних
версий оперативной системы UNIX. Они предоставляют метод односторонних
коммуникаций (отсюда термин half-duplex) между процессами.
Эта особенность широко используется даже в командной строке UNIX
(в shell-е).
ls | sort | lp
Приведенный выше канал принимает вывод ls как ввод sort, и вывод
sort за ввод lp. Данные проходят через полудуплексный канал,
- 7 -
перемещаясь (визуально) слева направо.
Хотя большинство из нас использует каналы в программировании на
shell-е довольно часто, мы редко задумываемся о том, что происходит на
уровне ядра.
Когда процесс создает канал, ядро устанавливает два файловых
дескриптора для пользования этим каналом. Один такой дескриптор
используется, чтобы открыть путь ввода в канал (запись), в то время
как другой применяется для получения данных из канала (чтение). В этом
смысле, канал мало применим практически, так как создающий его процесс
может использовать канал только для взаимодействия с самим собой.
Рассмотрим следующее изображение процесса и ядра после создания
канала:
in <-----
Process Kernel
out ----->
Из этого рисунка легко увидеть, как файловые дескрипторы связаны
друг с другом. Если процесс посылает данные через канал (fd0), он
имеет возможность получить эту информацию из fd1. Однако этот
простенький рисунок отображает и более глобальную задачу. Хотя канал
первоначально связывает процесс с самим собой, данные, идущие через
канал, проходят через ядро. В частности, в Linux-е каналы внутренне
представлены корректным inode-ом. Конечно, этот inode существует в
пределах самого ядра, а не в какой-либо физической файловой системе.
Эта особенность откроет нам некоторые привелекательные возможности для
ввода/вывода, как мы увидим немного позже.
Зачем же нам неприятности с созданием канала, если мы
всего-навсего собираемся поговорить сами с собой? На самом деле,
процесс, создающий канал, обычно порождает дочерний процесс. Как
только дочерний процесс унаследует какой-нибудь открытый файловый
дескриптор от родителя, мы получаем базу для мультипроцессовой
коммуникации (между родителем и потомком). Рассмотрим эту измененную
версию нашего рисунка:
- 8 -
in <----- -----> in
Parent Process Kernel Child Process
out -----> <----- out
Теперь мы видим, что оба процесса имеют доступ к файловым
дескрипторам, которые основывают канал. На этой стадии должно быть
принято критическое решение. В каком направлении мы хотим запустить
данные? Потомок посылает информацию к родителю или наоборот? Два
процесса взаимно согласовываются и "закрывают" неиспользуемый конец
канала. Пусть потомок выполняет несколько действий и посылает
информацию к родителю обратно через канал. Наш новый рисунок выглядел
бы примерно так:
in <----- in
Parent Process Kernel Child Process
out <----- out
Конструкция канала теперь полная. Все, что осталось сделать - это
использовать его. Чтобы получить прямой доступ к каналу, можно
применять системные вызовы, подобные тем, которые нужны для
ввода/вывода в файл или из файла на низком уровне (вспомним, что в
действительности каналы внутренне представлены как корректный inode).
Чтобы послать данные в канал, мы используем системный вызов
write(), а чтобы получить данные из канала - системный вызов read().
Вспомним, что системные вызовы ввода/вывода в файл или из файла
работают с файловыми дескрипторами! (Однако, не забывайте, что
некоторые системные вызовы, как, например, lseek(), не работают с
дескрипторами.)
6.2.2. Создание каналов на Си
Создание каналов на языке программирования Си может оказаться
чуть более сложным, чем наш простенький shell-пример. Чтобы создать
простой канал на Си, мы прибегаем к использованию системного вызова
pipe(). Для него требуется единственный аргумент, который является
массивом из двух целых (integer), и, в случае успеха, массив будет
содержать два новых файловых дескриптора, которые будут использованы
- 9 -
для канала. После создания канала процесс обычно порождает новый
процесс (вспомним, что процесс-потомок наследует открытые файловые
дескрипторы).
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
#include
#include
main()
{
int fd[2];
pipe(fd);
.
.
}
Вспомните, что имя массива decays в указатель на его первый член.
fd - это эквивалент &fd[0]. Раз мы установили канал, то ответвим
нашего нового потомка:
- 10 -
#include
#include
#include
main()
{
int fd[2];
pid_t childpid;
pipe(fd);
if((childpid = fork()) == -1)
{
perror("fork");
exit(1);
}
.
.
}
Если родитель хочет получить данные от потомка, то он должен
закрыть fd1, а потомок должен закрыть fd0. Если родитель хочет послать
данные потомку, то он должен закрыть fd0, а потомок - fd1. С тех пор
как родитель и потомок делят между собой дескрипторы, мы должны всегда
быть уверены, что не используемый нами в данный момент конец канала
закрыт; EOF никогда не будет возвращен, если ненужные концы канала не
закрыты.
#include
#include
#include
main()
{
int fd[2];
pid_t childpid;
2. Ядро LINUX
- 11 -
if((childpid = fork()) == -1)
{
perror("fork");
exit(1);
}
if(childpid == 0)
{
/* Потомок закрывает вход */
close(fd[0]);
}
else
{
/* Родитель закрывает выход */
close(fd[1]);
}
.
.
}
Как было упомянуто ранее, раз канал был установлен, то файловые
дескрипторы могут обрабатываться подобно дескрипторам нормальных
файлов.
/*************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
*************************************************************************
MODULE: pipe.c
*************************************************************************/
#include
#include
#include
int main(void)
{
int fd[2], nbytes;
- 12 -
pid_t childpid;
char string[] = "Hello, world!\n";
char readbuffer[80];
pipe(fd);
if((childpid = fork()) == -1)
{
perror("fork");
exit(1);
}
if(childpid == 0)
{
/* Потомок закрывает вход */
close(fd[0]);
/* Посылаем "string" через выход канала */
write(fd[1], string, strlen(string));
exit(0);
}
else
{
/* Родитель закрывает выход */
close(fd[1]);
/* Чтение строки из канала */
nbytes = read(fd[0], readbuffer, sizeof(readbuffer));
printf("Received string: %s", readbuffer);
}
return(0);
}
Часто дескрипторы потомка раздваиваются на стандартный ввод или
вывод. Потомок может затем exec() другую программу, которая наследует
стандартные потоки. Давайте посмотрим на системный вызов dup():
- 13 -
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: старый дескриптор не закрыт! Оба работают совместно!
Несмотря на то, что старый и новосозданный дескрипторы
взаимозаменяемы, мы будем сначала закрывать один из стандартных
потоков. Системный вызов dup() использует наименьший по номеру
неиспользуемый дескриптор для нового.
Рассмотрим:
.
.
childpid = fork();
if(childpid == 0)
{
/* Закрываем стандартный ввод потомка */
close(0);
/* Дублируем вход канала на stdin */
dup(fd[0]);
execlp("sort", "sort", NULL);
.
}
Поскольку файловый дескриптор 0 (stdin) был закрыт, вызов dup()
дублировал дескриптор ввода канала (fd0) на его стандартный ввод.
Затем мы сделали вызов execlp(), чтобы покрыть код потомка кодом
программы sort. Поскольку стандартные потоки exec()-нутой программы
наследуются от родителей, это означает, что вход канала ста для
потомка стандартным вводом! Теперь все, что первоначальный
- 14 -
процесс-родитель посылает в канал, идет в sort.
Существует другой системный вызов, dup2(), который также может
использоваться. Этот особенный вызов произошел с Version 7 of UNIX и
был поддержан BSD, и теперь требуется по стандарту POSIX.
SYSTEM CALL: dup2();
PROTOTYPE: int dup2( int oldfd, int newfd );
RETURNS: новый дескриптор в случае успеха
-1 в случае ошибки: errno = EBADF (oldfd некорректен)
EBADF ($newfd is out of range$)
EMFILE (слишком много дескрипторов для
процесса)
NOTES: старый дескриптор закрыл dup2()!
Благодаря этому особенному вызову мы имеем закрытую операцию и
действующую копию за один системный вызов. Вдобавок, он гарантированно
неделим, что означает, что он никогда не будет прерван поступающим
сигналом. С первым системным вызовом dup() программисты были вынуждены
предварительно выполнять операцию close(). Это приводилок наличию двух
системных вызовов с малой степенью защищенности в краткий промежуток
времени между ними. Если бы сигнал поступил в течение этого интервала
времени, копия дескриптора не состоялась бы. dup2() разрешает для нас
эту проблему.
Рассмотрим:
.
.
childpid = fork();
if(childpid == 0)
{
/* Закрываем стандартный ввод, дублируем вход канала на
стандартный ввод */
dup2(0, fd[0]);
- 15 -
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
Эта стандартная библиотечная функция создает полудуплексный канал
посредством вызывания pipe() внутренне. Затем она порождает дочерний
процесс, запускает Bourne shell и исполняет аргумент command внутри
shell-а. Управление потоком данных определяется вторым аргументом,
type. Он может быть "r" или "w" - для чтения или записи, но не может
быть и то, и другое! Под Linux-ом канал будет открыт в виде,
определенном первой литерой аргумента "type". Поэтому, если вы
попытаетесь ввести "rw", канал будет открыт только в виде "read".
Каналы, созданные popen(), должны быть закрыты pclose(). К этому
моменту вы, вероятно, уже использовали [реализовали] popen/pclose
share, удивительно похожий на стандартный файловый поток I/O функций
fopen() и fclose().LIBRARY FUNCTION: pclose();
PROTOTYPE: int pclose( FILE *stream )
RETURNS: выход из статуса системного вызова wait4()
-1, если "stream" некорректен или облом с wait4()
- 16 -
NOTES: ожидает окончания связанного каналом процесса, затем закрывает поток.
Функция pclose() выполняет wait4() над процессом, порожденным
popen()-ом. Когда она возвращается, то уничтожает канал и файловый
поток. Повторим еще раз, что этот эффект аналогичен эффекту,
вызываемому функцией fclose() для нормального, основанного на потоке
файлового ввода/вывода.
Рассмотрим пример, который открывает канал для команды сортировки
и начинает сортировать массив строк:
/****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
****************************************************************************
MODULE: popen1.c
****************************************************************************/
#include
#define MAXSTRS 5
int main(void)
{
int cntr;
FILE *pipe_fp;
char *strings[MAXSTRS] = {"echo", "bravo", "alpha", "charlie", "delta"};
/* Создаем односторонний канал вызовом popen() */
if (( pipe_fp = popen("sort", "w")) == NULL)
{
perror("popen");
exit(1);
}
/* Цикл */
for(cntr=0; cntr /tmp/foo", "w")
popen("sort | uniq | more", "w");
В качестве другого примера popen()-а, рассмотрим маленькую
программу, открывающую два канала (один - для команды ls, другой - для
сортировки):
/****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
****************************************************************************
MODULE: popen2.c
****************************************************************************/
#include
int main(void)
{
FILE *pipein_fp, *pipeout_fp;
char readbuf[80];
- 18 -
/* Создаем односторонний канал вызовом popen() */
if (( pipein_fp = popen("ls", "r")) == NULL)
{
perror("popen");
exit(1);
}
/* Создаем односторонний канал вызовом popen() */
if (( pipeout_fp = popen("sort", "w")) == NULL)
{
perror("popen");
exit(1);
}
/* Цикл */
while(fgets(readbuf, 80, pipein_fp))
fputs(readbuf, pipeout_fp);
/* Закрываем каналы */
pclose(pipein_fp);
pclose(pipeout_fp);
return(0);
}
В качестве последней демонстрации popen(), давайте создадим
программу, характерную для открытия канала между отданной командой и
именем файла:
/****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
****************************************************************************
MODULE: popen3.c
****************************************************************************/
#include
- 19 -
int main(int argc, char *argv[])
{
FILE *pipe_fp, *infile;
char readbuf[80];
if( argc != 3 ) {
fpintf(stderr, "USAGE": popen3 [command] [filename]\n);
exit(1);
}
/* Открываем вводимый файл */
if (( infile = popen(argv[2], "rt")) == NULL)
{
perror("fopen");
exit(1);
}
/* Создаем односторонний канал вызовом popen() */
if (( pipe_fp = popen(argv[1], "w")) == NULL)
{
perror("popen");
exit(1);
}
/* Цикл */
do {
fgets(readbuf, 80, infile);
if(feof(infile)) break;
fputs(readbuf, pipe_fp);
} while(!feof(infile));
fclose(infile);
pclose(pipe_fp);
return(0);
}
- 20 -
Попробуйте выполнить эту программу с последующими заклинаниями:
popen3 sort popen3.c
popen3 cat popen3.c
popen3 more popen3.c
popen3 cat popen3.c | grep main
6.2.4. Атомарные (неделимые) операции с каналами
Для того чтобы операция рассматривалась как "атомарная", она не
должна прерываться ни по какой причине. Неделимая операция выполняется
сразу. POSIX стандарт говорит в /usr/include/posix_lim.h, что
максимальные размеры буфера для атомарной операции в канале таковы:
#define _POSIX_PIPE_BUF 512
Атомарно по каналу может быть получено или записано до 512 байт.
Все, что выходит за эти пределы, будет разбито и не будет выполняться
атомарно. Однако, под Linux-ом этот атомарный операционный лимит
определен в "linux/limits.h" следующим образом:
#define PIPE_BUF 4096
Как вы можете заметить, Linux предоставляет минимальное
количество байт, требуемое POSIX-ом, довольно щедро. Атомарность
операции с каналом становится важной, если вовлечено более одного
процесса (FIFOS). Например, если количество байтов, записанных в
канал, превышает лимит, отпущенный на отдельную операцию, а в канал
записываются многочисленные процессы, то данные будут смешаны, т.е.
один процесс может помещать данные в канал между записями других.
6.2.5. Примечания к полудуплексным каналам
* Двусторонние каналы могут быть созданы посредством открывания
двух каналов и правильным переопределением файловых дескрипторов в
процессе-потомке.
- 21 -
* Вызов pipe() должен быть произведен ПЕРЕД вызовом fork(), или
дескрипторы не будут унаследованы процессом-потомком! (то же для
popen()).
* С полудуплексными каналами любые связанные процессы должны
разделять происхождение. Поскольку канал находится в пределах ядра,
любой процесс, не состоящий в родстве с создателем канала, не имеет
способа адресовать его. Это не относится к случаю с именованными
каналами (FIFOS).
6.3. Именованные каналы (FIFOs - First In First Out)
6.3.1. Основные понятия
Именованные каналы во многом работают так же, как и обычные
каналы, но все же имеют несколько заметных отличий.
* Именованные каналы существуют в виде специального файла
устройства в файловой системе.
* Процессы различного происхождения могут разделять данные через
такой канал.
* Именованный канал остается в файловой системе для дальнейшего
использования и после того, как весь ввод/вывод сделан.
6.3.2. Создание FIFO
Есть несколько способов создания именованного канала. Первые два
могут быть осуществлены непосредственно из shell-а.
mknod MYFIFO p
mkfifo a=rw MYFIFO
Эти две команды выполняют идентичные операции, за одним
исключением. Команда mkfifo предоставляет возможность для изменения
прав доступа к файлу FIFO непосредственно после создания. При
использовании mknod будет необходим вызов команды chmod.
- 22 -
Файлы FIFO могут быть быстро идентифицированы в физической
файловой системе посредством индикатора "p", представленного здесь в
длинном листинге директории.
$ ls -1 MYFIFO
^prw-r--r-- 1 root root 0 Dec 14 22:15 MYFIFO| ...
Также заметьте, что вертикальный разделитель располагается
непосредственно после имени файла. Другая веская причина запустить
Linux, eh?
Чтобы создать FIFO на Си, мы можем прибегнуть к использованию
системного вызова mknod():
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()-а man page, а сейчас
давайте рассмотрим простой пример создания FIFO на Си:
mknod("/tmp/MYFIFO", S_IFIFO|0666, 0);
В данном случае файл "/tmp/MYFIFO" создан как FIFO-файл.
Требуемые права - это "0666", хотя они находятся под влиянием
установки umask, как например:
- 23 -
final_umask = requested_permissions & ~original_umask ...
Общая хитрость - использовать системный вызов umask() для того,
чтобы временно устранить значение umask-а:
umask(0);
mknod("/tmp/MYFIFO", S_IFIFO|0666, 0);
Кроме того, третий аргумент mknod()-а игнорируется, в противном
случае мы создаем файл устройства. В этом случае он должен отметить
верхнее и нижнее числа файла устройства.
6.3.3. Операции FIFO
Операции ввода/вывода FIFO, по существу, такие же, как для
обычных каналов, за одним исключением. Чтобы физически открыть проход
к каналу, должен быть использован системный вызов "open" или
библиотечная функция. С полудуплексными каналами это невозможно,
поскольку канал находится в ядре, а не в физической файловой системе.
В нашем примере мы будем трактовать канал как поток, открывая его
fopen()-ом и закрывая fclose()-ом.
Рассмотрим простой сервер-процесс:
/****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
****************************************************************************
MODULE: fifoserver.c
****************************************************************************
#include
#include #include
#include
#include
- 24 -
#define FIFO_FILE "MYFIFO"
int main(void)
{
FILE *fp;
char readbuf[80];
/* Создаем FIFO, если он еще не существует */
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);
}
Поскольку FIFO блокирует по умолчанию, запустим сервер фоном
после того, как его откомпилировали:
$ fifoserver&
Скоро мы обсудим действие блокирования, но сначала рассмотрим
следующего простого клиента для нашего сервера:
/****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
****************************************************************************
MODULE: fifoclient.c
****************************************************************************
#include
- 25 -
#include
#define FIFO_FILE "MYFIFO"int main(int argc, char *argv[])
{
FILE *fp;
if ( argc != 2 ) {
printf("USAGE: fifoclient [string]\n");
exit(1);
}
fputs(argv[1], fp);
fclose(fp);
return(0);
}
6.3.4. Действие блокирования над FIFO
Если FIFO открыт для чтения, процесс его блокирует до тех пор,
пока какой-нибудь другой процесс не откроет FIFO для записи.
Аналогично для обратной ситуации. Если такое поведение нежелательно,
то может быть использован флаг O_NONBLOCK в системном вызове open(),
чтобы отменить действие блокирования.
В примере с нашим простым сервером мы только засунули его в фон и
позволили там осуществлять блокирование. Альтернативой могло бы быть
перепрыгивание на другую виртуальную консоль и запуск клиента,
переключение туда и обратно, чтобы увидеть результат.
6.3.5. Неизвестный SIGPIPE
Последнее, что следует отметить, это то, что каналы должны иметь
читателя и писателя. Если процесс пробует записать в канал, не имеющий
читателя, из ядра будет послан сигнал SIGPIPE. Это необходимо, когда в
каналом пользуются более чем два процесса.
- 26 -
6.4 System V IPC
6.4.1. Базовые понятия
Вместе с 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", мы подразумеваем очередь единичных сообщений,
множество семафоров или разделяемый сегмент памяти.) Этот
идентификатор требуется ядру для однозначного определения объекта IPC.
Например, чтобы сослаться на определенный разделяемый сегмент,
единственное, что вам потребуется, это уникальное значение ID, которое
привязано к этому сегменту.
Идентификатор IPC уникален только для своего типа объектов. То
есть, скажем, возможна только одна очередь сообщений с идентификатором
"12345", так же как номер "12345" может иметь какое-нибудь одно
множество семафоров или (и) какой-то разделяемый сегмент.
Ключи IPC
Чтобы получить уникальный ID нужен ключ. Ключ должен быть взаимно
согласован процессом-клиентом и процессом-сервером. Для приложения это
согласование должно быть первым шагом в построении среды.
- 27 -
(Чтобы позвонить кому-либо по телефону, вы должны знать его
номер. Кроме того, телефонная компания должна знать как провести ваш
вызов к адресату. И только когда этот адресат ответит, связь
состоится.)
В случае System V IPC "телефон" соединяет объекты IPC одного
типа. Под "телефонной компанией", или методом маршрутизации, следует
понимать ключ IPC.
Ключ, генерируемый приложением самостоятельно, может быть каждый
раз один и тот же. Это неудобно, полученный ключ может уже
использоваться в настоящий момент. Функцию ftok() используют для
генерации ключа и для клиента, и для сервера:
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');
- 28 -
Выбор алгоритма генерации ключа полностью отдается на усмотрение
прикладного программиста. Так же как и меры по предотвращению ситуации
гонок, дедлоков и т.п., любой метод имеет право на жизнь. Для наших
демонстрационных целей мы ограничимся ftok()-ом. Если условиться, что
каждый процесс-клиент запускается со своей уникальной "домашней"
директории, то генерируемые ключи будут всегда удовлетворительны.
Итак, значение ключа, когда оно получено, используется в
последующих системных вызовах IPC для создания или улучшения доступа к
объектам IPC.
Команда ipcs выдает статус всех объектов System V IPC.
LINUX-версия 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-butes messages
0 root 660 5 1
Здесь мы видим одинокую очередь с идентификатором "0". Она
принадлежит пользователю root и имеет восьмеричные права доступа 660,
или -rw-rw---. Очередь содержит одно пятибайтное сообщение.
Команда ipcs - это очень мощное средство, позволяющее
подсматривать за механизмом ядреной памяти для IPC-объектов. Изучайте
- 29 -
его, пользуйтесь им, благоговейте перед ним.
Команда ipcrm
Команда ipcrm удаляет объект IPC из ядра. Однако, поскольку
объекты IPC можно удалить через системные вызовы в программе
пользователя (как это делать мы увидим чуть позднее), часто нужды
удалять их "вручную" нет. Особенно это касается всяких программных
оболочек.
Внешний вид ipcrm прост:
ipcrm
Требуется сказать, является ли удаляемый объект очередью
сообщений (msg), набором семафоров (sem), или сегментом разделяемой
памяти (shm). IPC ID может быть получен через команду ipcs. Напомним,
что ID уникален в пределах одного из трех типов объектов IPC, поэтому
мы обязаны назвать этот тип.
6.4.2. Очереди сообщений
Базовые принципы
Очереди сообщений представляют собой связный список в адресном
пространстве ядра. Сообщения могут посылаться в очередь по порядку и
доставаться из очереди несколькими разными путями. Каждая очередь
сообщений однозначно определена идентификатором IPC.
Внутренние и пользовательские структуры данных
Ключом к полному осознанию такой сложной системы, как System V
IPC, является более тесное знакомство с различными структурами данных,
которые лежат внутри самого ядра. Даже для большинства примитивных
операций необходим прямой доступ к некоторым из этих структур, хотя
другие используются только на гораздо более низком уровне.
- 30 -
Буфер сообщения
Первой структурой, которую мы рассмотрим, будет msgbuf. Его можно
понимать как шаблон для данных сообщения. Поскольку данные в сообщении
программист определяет сам, он обязан понимать, что на самом деле они
являются структурой msgbuf. Его описание находится в linux/msg.h:
/* буфер сообщения для вызовов msgsnd и msgrcv*/
struct msgbuf {
long mtype; /* тип сообщения */
char mtext[1]; /* текст сообщения */
};
mtype
Тип сообщения, представленный натуральным числом. Он обязан быть
натуральным!
mtext
Собственно сообщение.
Возможность приписывать тип конкретному сообщению позволяет
держать в одной очереди разнородные сообщения. Это может понадобиться,
например, когда сообщения процесса-клиента помечаются одним магическим
числом, а сообщения сообщения процесса-сервера - другим; или
приложение ставит в очередь сообщения об ошибках с типом 1,
сообщения-запросы - с типом 2 и т.д. Ваши возможности просто
безграничны.
С другой стороны, старайтесь дать наглядное имя элементу данных
сообщения (в примере был mtext). В это поле можно записывать не только
массивы литер, но и вообще любые данные в любой форме. Поле
действительно полностью произвольно, поэтому вся структура может быть
переопределена программистом, например, так:
- 31 -
struct my_msgbuf {
long mtype; /* тип сообщения */
long request_id; /* идентификатор запроса */
struct client info; /* информация о клиенте */
}
Здесь мы также видим структуру сообщения, но второй элемент
заменился на два, причем один из них - другая структура! В этом
прелесть очередей сообщений, ядро не разбирает данные, какими бы они
ни были.
Существует, однако, ограничение на максимальный размер сообщения.
В LINUX-е он определен в linux/msg.h:
#define MSGMAX 4056 /* <= 4056 */ /* максимальный размер сообщения,
в байтах*/
Сообщения не могут быть больше, чем 4056 байт, сюда входит и
элемент mtype, который занимает 4 байта (long).
Структура msg ядра
Ядро хранит сообщение в очереди структуры msg. Она определена в
linux/msg.h следующим образом:
struct msg {
struct msg *msg_next; /* следующее сообщение в очереди */
long msg_type;
char *msg_spot; /* адрес текста сообщения */
short msg_ts; /* размер текста */
};
msg_next
Указатель на следующее сообщение в очереди. Сообщения объединены
в односвязный список и находятся в адресном пространстве ядра.
msg_type
Тип сообщения, каким он был объявлен в msgbuf.
- 32 -
msg_spot
Указатель на начало тела сообщения.
msg_ts
Длина текста (или тела) сообщения.
Структура msqid_ds ядра
Каждый из трех типов IPC-объектов имеет внутреннее представление,
которое поддерживается ядром. Для очередей сообщений это структура
msqid_ds. Ядро создает, хранит и сопровождает образец такой структуры
для каждой очереди сообщений в системе. Она определена в linux/msg.h
следующим образом:
/* структура msqid для каждой очереди в системе */
struct msqid_ds {
struct ipc_perm msg_perm;
struct msg *msg_first; /* первое сообщение в очереди */
struct msg *msg_last; /* последнее сообщение в очереди */
time_t msg_stime; /* время последнего вызова msgsnd */
time_t msg_rtime; /* время последнего вызова msgrcv */
time_t msg_ctime; /* время последнего изменения */
struct wait_queue *wwait;
struct wait_queue *rwait;
ushort msg_cbytes;
ushort msg_qnum;
ushort msg_qbytes; /* максимальное число байтов на очередь */
ushort msg_lspid; /* pid последнего испустившего msgsnd */
ushort msg_lrpid; /* последний полученный pid */
};
Хотя большинство элементов этой структуры вас будет мало
волновать, для какой-то законченности мы вкратце поясним каждый.
msg_perm
Экземпляр структуры ipc_perm, определенной в linux/ipc.h. Она
содержит информацию о доступе для очереди сообщений, включая права
доступа и информацию о создателе сообщения (uid и т.п.).
- 33 -
msg_first
Ссылка на первое сообщение в очереди (голова списка).
msg_last
Ссылка на последний элемент списка (хвост списка).
msg_stime
Момент времени (time_t) посылки последнего сообщения из очереди.
msg_rtime
Момент времени последнего изъятия элемента из очереди.
msg_ctime
Момент времени последнего изменения, проделанного в очереди
(подробнее об этом позже).
wwait и rwait
Указатели в очередь ожидания ядра. Они используются, когда
операция над очередью сообщений переводит процесс в состояние спячки
(то есть очередь переполнена, и процесс ждет открытия).
msg_cbytes
Число байт, стоящих в очереди (суммарный размер всех сообщений).
msg_qnum
Количество сообщений в очереди на настоящий момент.
msg_qbytes
Максимальный размер очереди.
msg_lspid
PID процесса, пославшего последнее в очереди сообщение.
msg_lrpid
PID последнего процесса, взявшего из очереди сообщение.
Структура ipc_perm ядра
- 34 -
Информацию о доступе к IPC-объектам ядро хранит в структуре
ipc_perm. Например, описанная выше структура очереди сообщений
содержит одну структуру типа ipc_perm в качестве элемента. Следующее
ее определение дано в linux/ipc.h.
struct ipc_perm {
key_t key;
ushort uid; /* euid и egid владельца */
ushort gid; ushort cuid; /* euid и egid создателя */
ushort cgid;
ushort mode; /* режим доступа, см. режимные флаги ниже */
ushort seq; /* порядковый номер использования гнезда */
};
Все приведенное выше говорит само за себя. Сохраняемая отдельно
вместе с ключом IPC-объекта информация содержит данные о владельце и
создателе этого объекта (они могут различаться). Режимы восьмеричного
доступа также хранятся здесь, как unsigned short. Наконец, сохраняется
порядковый номер использования гнезда. Каждый раз когда IPC объект
закрывается через системный вызов (уничтожается), этот номер
уменьшается на максимальное число объектов IPC, которые могут
находиться в системе. Касается вас это значение? Нет.
Системный вызов msgget() нужен для того, чтобы создать очередь
сообщений или подключиться к существующей.
SYSTEM CALL: msgget()
PROTOTYPE: int msgget( key_t key, int msgflg );
RETURNS: идентификатор очереди сообщений в случае успеха;
-1 в случае ошибки. При этом
errno = EACCESS (доступ отклонен)
EEXIST (такая очередь уже есть, создание невозможно)
EIDRM (очередь помечена как удаляемая)
ENOENT (очередь не существует)
ENOMEM (не хватает памяти для создания новой очереди)
- 35 -
ENOSPC (исчерпан лимит на количество очередей)
NOTES:
Первый аргумент msgget() значение ключа (мы его получаем при
помощи ftok()). Этот ключ сравнивается с ключами уже существующих в
ядре очередей. При этом операция открытия или доступа к очереди
зависит от содержимого аргумента msgflg:
IPC_CREAT
Создает очередь, если она не была создана ранее.
IPC_EXCL
При использовании совместно с IPC_CREAT, приводит к неудаче если
очередь уже существует.
Вызов msgget() с IPC_CREAT, но без IPC_EXCL всегда выдает
идентификатор (существующей с таким ключом или созданной) очереди.
Использование IPC_EXCL вместе с IPC_CREAT либо создает новую очередь,
либо, если очередь уже существует, заканчивается неудачей.
Самостоятельно IPC_EXCL бесполезен, но вместе c IPC_CREAT он дает
гарантию, что ни одна из существующих очередей не открывается для
доступа.
Восьмеричный режим может быть OR-нут в маску доступа. Каждый
IPC-объект имеет права доступа, аналогичные правам доступа к файлу в
файловой системе UNIX-а.
Напишем оберточную функцию для открытия или создания очереди
сообщений.
int open_queue( key_t keyval )
{
int qid;
if ((qid = msgget ( keyval, IPC_CREAT | 0660 )) == -1)
{
return (-1);
- 36 -
}
return (qid);
}
Отметьте использование точного ограничителя доступа 0660. Эта
небольшая функция возвращает идентификатор очереди (int) или -1 в
случае ошибки. Единственный требуемый аргумент - ключевое значение.
Системный вызов msgsnd()
Получив идентификатор очереди, мы можем выполнять над ней
различные действия. Чтобы поставить сообщение в очередь, используйте
системный вызов 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 (не хватает памяти для копии буфера сообщения)
NOTES:
Первый аргумент msgsnd - идентификатор нашей очереди,
возвращенный предварительным вызовом msgget. Второй аргумент, msgp -
это указатель на редекларированный и загруженный буфер сообщения.
Аргумент msgsz содержит длину сообщения в байтах, не учитывая тип
сообщения (long 4 байта).
- 37 -
Аргумент msgflg может быть нулем или:
IPC_NOWAIT
Если очередь переполнена, то сообщение не записывается в очередь,
и управление передается вызывающему процессу. Если эта ситуация не
обрабатывается вызывающим процессом, то он приостанавливается
(блокируется), пока сообщение не будет прочитано.
Напишем незатейливую оберточную функцию для посылки сообщения:
int send_message( int qid, struct mymsgbuf *qbuf )
{
int result, length;
/* Длина есть в точности размер структуры минус sizeof(mtype) */
length = sizeof(struct mymsgbuf) - sizeof(long);
if((result = msgsnd( qid, qbuf, length, 0)) == -1)
{
return(-1);
}
return(result);
}
Эта функция пытается послать сообщение, лежащее по указанному
адресу (qbuf), в очередь сообщений, идентифицированную qid. Напишем
небольшую утилиту с нашими двумя оберточными функциями:
#include
#include
#include
#include
main()
{
int qid;
key_t msgkey;
- 38 -
struct mymsgbuf {
long mtype; /* тип сообщения */
int request; /* рабочий номер запроса */
double salary; /* зарплата */
} msg;
/* Генерируем IPC-ключ */
msgkey = ftok(".", 'm');
/* Открываем/создаем очередь */
if (( qid = open_queue( msgkey)) == -1) {
perror("open_queue");
exit(1);
}
/* Заполняем сообщение произвольными тестовыми данными */
msg.type = 1; /* тип сообщения должен быть положительным! */
msg.request = 1; /* элемент данных 1 */
msg.salary = 1000.00; /* элемент данных 2
(моя годовая зарплата! - авт.) */
/* Бомбим! */
if((send_message( qid, &msg )) == -1) {
perror("send_message");
exit(1);
}
}
После создания/открытия нашей очереди принимаемся за загрузку
буфера сообщения с тестовыми данными (заметьте отсутствие текстовых
данных для иллюстрации нашего положения о посылке двоичной
информации). Вызов нашего send_message ловко доставит сообщение в
очередь.
Теперь, когда мы имеем сообщение в очереди, попытайтесь при
помощи ipcs посмотреть на статус нашей очереди. Обсудим, как забрать
из очереди сообщение. Для этого используется системный вызов msgrcv():
- 39 -
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, но в очереди нет
ни одного сообщения, удовлетворяющего запросу)
NOTES:
Конечно, первый аргумент определяет очередь, из которой будет
взято сообщение (должен быть возвращен сделанным предварительно
вызовом msgget). Второй аргумент (msgp) представляет собой адрес
буфера, куда будет положено изъятое сообщение. Третий аргумент, msgsz,
ограничивает размер структуры-буфера без учета длины элемента mtype.
Еще раз повторимся, это может быть легко вычислено:
msgsz = sizeof(struct mymsgbuf) - sizeof(long);
Четвертый аргумент, mtype - это тип сообщения, изымаемого из
очереди. Ядро будет искать в очереди наиболее старое сообщение такого
типа и вернет его копию по адресу, указанному аргументом msgp.
Существует один особый случай: если mtype = 0, то будет возвращено
наиболее старое сообщение, независимо от типа.
Если IPC_NOWAIT был послан флагом, и нет ни одного
удовлетворительного сообщения, msgrcv вернет вызывающему процессу
ENOMSG. В противном случае вызывающий процесс блокируется, пока в
очередь не прибудет сообщение, соответствующее параметрам msgrcv().
Если, пока клиент ждет сообщения, очередь удаляется, то ему
возвращается EIDRM. EINTR возвращается, если сигнал поступил, пока
процесс находился на промежуточной стадии между ожиданием и
- 40 -
блокировкой.
Давайте рассмотрим функцию-переходник для изъятия сообщения из
нашей очереди.
int read_message( int qid, long type, struct mymsgbuf *qbuf )
{
int result, length;
/* Длина есть в точности размер структуры минус sizeof(mtype) */
length = sizeof(struct mymsgbuf) - sizeof(long);
if((result = msgrcv( qid, qbuf, length, type, 0 )) == -1)
{
return(-1);
}
return(result);
}
После успешного изъятия сообщения удаляется из очереди и его
ярлык.
Бит MSG_NOERROR в msgflg предоставляет некоторые дополнительные
возможности. Если физическая длина сообщения больше, чем msgsz, и
MSG_NOERROR установлен, то сообщение обрезается и возвращается только
msgsz байт. Нормальный же msgrcv() возвращает -1 (E2BIG), и сообщение
остается в очереди до последующих запросов. Такое поведение можно
использовать для создания другой оберточной функции, которая позволит
нам "подглядывать" внутрь очереди, чтобы узнать, пришло ли сообщение,
удовлетворяющее нашему запросу.
int peek_message( int qid, long type )
{
int result, length;
if((result = msgrcv( qid, NULL, 0, type, IPC_NOWAIT )) == -1)
{ if(errno == E2BIG)
- 41 -
return(TRUE);
}
return(FALSE);
}
Выше вы заметили отсутствие адреса буфера и длины. В этом
конкретном случае мы хотели, чтобы вызов прошел неудачно. Однако мы
проверили возвращение E2BIG, которое должно показать, существует ли
сообщение затребованного типа. Оберточная функция возвращает TRUE в
случае успеха, и FALSE - в противном случае. Отметьте также
установленный флаг IPC_NOWAIT, который помешает блокировке, о которой
мы говорили раньше.
Системный вызов msgсtl()
Благодаря использованию функций-переходников вы имеете некий
элегантный подход к созданию и использованию очередей сообщений в
ваших приложениях. Теперь коснемся непосредственно манипулирования
внутренними структурами, связанными с данной очередью сообщений.
Для осуществления контроля над очередью предназначен системный
вызов msgсtl.
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-команда была
послана процессом, не имеющим прав на запись
(в очередь))
- 42 -
NOTES:
Теперь из общих соображений ясно, что прямые манипуляции с
внутреностями ядра могут привести к очень занимательным последствиям.
К сожалению, по-настоящему весело будет только тому, кто любит
вдребезги и с наслаждением крушить подсистему IPC. Однако при
использовании msgctl() с некоторыми командами вероятность
огорчительных результатов не очень велика. Вот их и рассмотрим.
IPC_STAT
Сохраняет по адресу buf структуру msqid_ds для очереди сообщений.
IPC_SET
Устанавливает значение элемента ipc_perm структуры msqid.
Значения выбирает из буфера.
IPC_RMID
Удаляет очередь из ядра.
Вернемся к нашему разговору о внутреннем представлении очереди
сообщений: msqid_ds. Ядро держит экземпляр этой структуры для каждой
очереди, существующей в системе. IPC_STAT дает возможность заиметь
копию такой структуры для испытаний. Посмотрим на оберточную функцию,
которая берет эту структуру и размещает копию по указанному адресу.
int get_queue_ds( int qid, struct msgqid_ds *qbuf )
{
if( msgctl( qid, IPC_STAT, qbuf) == -1 )
{
return(-1);
}
return(0);
}
Если копирование во внутренний буфер невозможно, то вызывающей
функции возвращается -1. Если же все прошло нормально, то возвращается
0, и посланный буфер должен содержать копию внутренней структуры
- 43 -
данных для очереди с идентификатором qid.
Что же мы можем делать с полученной копией структуры?
Единственное, что можно поменять, это элемент ipc_perm. Это права
доступа очереди, информация о создателе и владельце очереди. Однако и
отсюда менять позволено только mode, uid и gid.
Давайте напишем оберточную функцию, изменяющую режим доступа
очереди. Режим должен быть передан как массив литер (например, "660").
int change_queue_mode( int qid, char *mode )
{
struct msqid_ds tmpbuf;
/* Берем текущую копию внутренней структуры */
get_queue_ds( qid, &tmpbuf );
/* Применяем уже известный прикол для изменения прав доступа */
sscanf(mode, "%ho", &tmpbuf.msg_perm.mode); /* Модернизируем внутреннюю структуру */
if( msgctl( qid, IPC_SET, &tmpbuf ) == -1 )
{
return(-1);
}
return(0);
}
Мы взяли текущую копию внутренней структуры данных посредством
вызова нашей get_queue_ds; затем sscanf() меняет элемент mode
структуры msg_perm. Однако ничего не произойдет, пока msgctl c IPC_SET
не обновил внутреннюю версию.
ОСТОРОЖНО! Изменяя права доступа, можно случайно лишить прав себя
самого! Помните, что IPC-объекты не исчезают, пока они не уничтожены
должным образом или не перезагружена система. Поэтому то, что вы не
видите очереди ipcs-ом, не означает, что ее нет на самом деле.
- 44 -
После того, как сообщение взято из очереди, оно удаляется.
Однако, как отмечалось ранее, IPC-объекты остаются в системе до
персонального удаления или перезапуска всей системы. Поэтому наша
очередь сообщений все еще существует в ядре и пригодна к употреблению
в любое время, несмотря на то, что последнее его соообщение уже давно
на небесах. Чтобы и душа нашей очереди с миром отошла к богам, нужен
вызов msgctl(), использующий команду IPC_RMID:
int remove_queue( int qid )
{
if( msgctl( qid, IPC_RMID, 0) == -1)
{
return(-1)
}
return(0);
}
Эта функция-переходник возвращает 0, если очередь удалена без
инцедентов, в противном случае ввыдается -1. Удаление очереди неделимо
и попытка любого обращения к ней будет безуспешной.
msgtool: интерактивный обработчик очередей сообщений
Мало кто станет отрицать непосредственную выгоду от возможности в
любой момент получить точную техническую информацию. Подобные
материалы представляют собой мощный механизм для обучения и
исследования новых областей. Однако, неплохо было бы добавить к
технической информации и реальные примеры. Это непременно ускорит и
укрепит процесс обучения.
До сей поры все то хорошее, что мы сделали - это оберточные
функции для манипуляций с очередями сообщений. Хотя они чрезвычайно
полезны, ими неудобно пользоваться для дальнейшего обучения и
экспериментов. Существует средство, позволяющее работать с
IPC-очередями из командной строки - msgtool(). Хотя msgtool() будет
использован в целях обучения, он пригодится и реально при написании
скриптов.
- 45 -
Описание
Поведение msgtool()-а зависит от аргументов командной строки, что
удобно для вызова из скрипта shell. Позволяет делать все что угодно,
от создания, посылки и получения сообщений до редактирования прав
доступа и удаления очереди. Изначально данными сообщений могут быть
только литерные массивы. Упражнение - измените это так, чтобы можно
было посылать и другие данные.
Синтаксис командной строки
Посылка сообщений
msgtool s (type) "text"
Изъятие сообщений
msgtool к (type)
Изменение прав доступа
msgtool (mode)
Уничтожение очереди
msgtool d
Примеры
msgtool s 1 test
msgtool s 5 test
msgtool s 1 "This is test"
msgtool r 1
msgtool d
msgtool m 660
Код
Следующее, что мы рассмотрим, это исходный текст msgtool. Его
следует компилировать в версии системы, которая поддерживает System V
IPC. Убедитесь в наличии System V IPC в ядре, когда будете
- 46 -
пересобирать программу!
На полях отметим, что наша утилита будет всегда создавать
очередь, если ее не было.
Замечание. Поскольку msgtool использует ftok() для генерации
ключей IPC, вы можете нарваться на конфликты, связанные с
директориями. Если вы где-то в скрипте меняете директории, то все это
наверняка не сработает. Это обходится путем более явного указания пути
в msgtool, вроде "/tmp/msgtool", или даже запроса пути из командной
строки вместе с остальными аргументами.
/****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
****************************************************************************
MODULE: msgtool.c
****************************************************************************
Средство командной строки для работы со очередями сообщений в стиле SysV
****************************************************************************/
#include
#include
#include
#include
#include
#include
#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);
- 47 -
void usage(void);
int main(int argc, char *argv[])
{
key_t key;
int msgqueue_id;
struct mymsgbuf qbuf;
if(argc == 1)
usage();
/* Создаем уникальный ключ через вызов ftok() */
key = ftok(".",'m');
/* Открываем очередь - при необходимости создаем */
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 mymsg buf *)&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);
}
- 48 -
void send_message(int qid, struct mymsgbuf *qbuf, long type, char *text)
{
/* Посылаем сообщение в очередь */
printf("Sending a message ...\n");
qbuf->mtype = type;
strcopy(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)
{
/* Вычитываем сообщение из очереди */
printf("Reading a message ...\");
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)
{
/* Удаляем очередь */
msgctl(qid, IPC_RMID, 0);
}
void change_queue_mode(int qid, char *mode)
{
struct msqid_ds myqueue_ds;
/* Получаем текущее состояние */
msgctl(qid, IPC_STAT, &myqueue_ds);
- 49 -
/* Меняем состояние в копии внутренней структуры данных */
sscanf(mode, "%ho", &myqueue_ds.msg_perm.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 \n");
fprintf(stderr, " (r)ecv \n");
fprintf(stderr, " (d)elete\n");
fprintf(stderr, " (m)ode \n");
exit(1);
}
6.4.3. Семафоры
Основные понятия
Семафоры лучше всего предствлять себе как счетчики, управляющие
доступом к общим ресурсам. Чаще всего они используются как блокирующий
механизм, не позволяющий одному процессу захватить ресурс, пока этим
ресурсом пользуется другой. Семафоры часто подаются как наиболее
трудные для восприятия из всех трех видов IPC-объектов. Для полного
понимания, что же такое семафор, мы их немного пообсуждаем, прежде чем
переходить к системным вызовам и операционной теории.
Слово "семафор" в действительности является старым
железнодорожным термином, соответствующим "рукам", не дающим
траекториям каров (это то, что у буржуев ездит по рельсам - перев.)
пересекаться на перекрестках. То же самое можно сказать и про
семафоры. Семафор в положении ON (руки пондяты вверх) если ресурс
свободен и в положении OFF (руки опущены) если ресурс недоступен
(должны ждать).
- 50 -
Этот пример неплохо показал суть работы семафора, однако важно
знать, что в IPC используются множества семафоров, а не отдельные
экземпляры. Разумеется, множество может содержать и один семафор, как
в нашем железнодорожном примере.
Возможен другой подход к семафорам - как к счетчикам ресурсов.
Приведем другой пример из жизни. Вообразим себе спулер, управляющий
несколькими принтерами, каждый из которых обрабатывает по нескольку
заданий. Гипотетический менеджер печати будет использовать множество
семафоров для установления доступа к каждому из принтеров.
Предположим, что в корпоративной комнате имеются 5 работающих
принтеров. Наш менеджер конструирует 5 семафоров - по одному на каждый
принтер. Поскольку каждый принтер может обрабатывать только по одному
запросу за раз, все семафоры устанавливаются в 1, что означает
готовность всех принтеров печатать.
Машенька послала запрос на печать. Менеджер смотрит на семафоры и
находит первый из них со значением 1. Перед тем, как Машенькин запрос
попадет на физическое устройство, менеджер печати уменьшит
соответствующий семафор на 1. Теперь значение семафора есть 0. В мире
семафоров System V нуль означает стопроцентную занятость ресурса на
семафоре. В нашем примере на принтере не будет ничего печататься, пока
значение семафора не изменится.
Когда Машенька напечатала все свои плакаты, менеджер печати
увеличивает семафор на 1. Теперь его значение вновь равно 1 и принтер
может принимать задания снова.
Не смущайтесь тем, что все семафоры инициализируются единицей.
Семафоры, трактуемые как счетчики ресурсов, могут изначально
устанавливаться в любое натуральное число, не только в 0 или 1. Если
бы наши принтеры умели печатать по 500 документов за раз, мы могли бы
проинициализировать семафоры значением 500, уменьшая семафор на 1 при
каждом поступающем задании и увеличивая после его завершения. Как вы
увидите в следующей главе, семафоры имеют очень близкое отношение к
разделяемым участкам памяти, играя роль сторожевой собаки, кусающей
нескольких писателей в один и тот же сегмент памяти (имеется в виду
- 51 -
машинная память).
Перед тем, как копаться в системных вызовах, коротко пробежимся
по внутренним структурам данных, с которыми имеют дело семафоры.
Структура semid_ds ядра
Так же, как и для очередей сообщений, ядро отводит часть своего
адресного пространства под структуру данных каждого множества
семафоров. Структура определена в linux/sem.h:
/* Структура данных semid для каждого множества семафоров системы */
struct semid_ds {
struct ipc_perm sem_perm; /* права доступа, см. ipc.h */
time_t sem_otime; /* время последнего semop-а */
time_t sem_ctime; /* время последнего изменения */
struct sem *sem_base; /* указатель на первый семафор в массиве */
struct wait_queue *eventn;
struct wait_queue *eventz;
struct sem_undo *undo; /* запросы undo в этом массиве */
ushort sem_nsems; /* номера семафоров в массиве */
};
Так же, как с очередями сообщений, операции с этой структурой
проводятся с помощью системных вызовов, а не грязными скальпелями. Вот
некоторые описания полей.
sem_perm
Это пример структуры ipc_perm, котораф описана в linux/ipc.h. Она
содержит информацию о доступе к множеству семафоров, включая права
доступа и информацию о создателе множества (uid и т.д.).
sem_otime
Время последней операции semop() (подробнее чуть позже).
sem_ctime
Время последнего изменения структуры.
- 52 -
sem_base
Указатель на первый семафор в массиве.
sem_undo
Число запросов undo в массиве (подробнее еще чуть позже).
sem_nsems
Количество семафоров в массиве.
Структура sem ядра
В sem_ds есть указатель на базу массива семафоров. Каждый элемент
массива имеет тип sem, который описан в linux/sem.h:
/* Структура для каждого семафора в системе */
struct sem {
short sempid; /* pid последней операции */
ushort semval; /* текущее значение */
ushort semncnt; /* число процессов, ждущих увеличения semval-а */
ushort semzcnt; /* число процессов, ждущих semval-а , равного 0 */
};
sem_pid
ID процесса, проделавшего последнюю операцию
sem_semval
Текущее значение семафора
sem_semncnt
Число процессов, ожидающих освобождения требуемых ресурсов
sem_semzcnt
Число процессов, ожидающих освобождения всех ресурсов
Системный вызов semget() используется для того, чтобы создать
новое множество семафоров или получить доступ к старому.
- 53 -
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 (превышен лимит на количество множеств семафоров)
NOTES:
Первый аргумент semget() - это ключ (в нашем случае возвращается
ftok()-ом). Он сравнивается с ключами остальных множеств семафоров,
присутствующих в системе. Вместе с этим решается вопрос о выборе между
созданием и подключением к множеству семафоров в зависимости от
аргумента msgflg.
IPC_CREAT
Создает множество семафоров, если его еще не было в системе.
IPC_EXCL
При использовании вместе с IPC_CREAT вызывает ошибку, если
семафор уже существует.
Если IPC_CREAT используется в одиночку, то semget() возвращает
идентификатор множества семафоров - вновь созданного или с таким же
ключом. Если IPC_EXCL используется совместно с IPC_CREAT, то либо
создается новое множество, либо, если оно уже существует, вызов
приводит к ошибке и -1. Сам по себе IPC_EXCL бесполезен, но вместе с
IPC_CREAT он дает средство гарантировать, что ни одно из существующих
множеств семафоров не открыто для доступа.
Как и в других частях System V IPC, восьмеричный режим доступа
может быть OR-нут в маску для формирования доступа к множеству
семафоров.
- 54 -
Аргумент nems определяет число семафоров, которых требуется
породить в новом множестве. Это количество принтеров в нашей
корпоративной комнате. Максимальное число семафоров определяется в
"linux/sem.h":
#define SEMMSL 32 /* <= 512 */
Аргумент nsems игнорируется, если вы открвываете существующую
очередь.
Напишем функции-переходники для открытия и создания множества
семафоров.
Обратите внимание на явное задание доступа 0660. Эта незатейливая
функция возвращает идентификатор множества семафоров или -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-структуры)
- 55 -
ERANGE (значение семафора вышло за пределы
допустимых значений)
NOTES:
Первый аргумент semop() есть значение ключа (в нашем случае
возвращается semget-ом). Второй аргумент (sops) - это указатель на
массив операций, выполняемых над семафорами, третий аргумент (nsops)
является количеством операций в этом массиве.
Аргумент sops указывает на массив типа sembuf. Эта структура
описана в linux/sem.h следующим образом:
/* системный вызов semop требует массив таких структур */
struct sembuf { ushort sem_num; /* индекс семафора в массиве */
short sem_op; /* операция над семафором */
short sem_flg; /* флаги */
};
sem_num
Номер семафора, с которым вы собираетесь иметь дело.
sem_op
Выполняемая операция (положительное, отрицательное число или
нуль).
sem_flg
Флаги операции.
Если sem_op отрицателен, то его значение вычитается из семафора
(семафор уменьшается - перев.). Это соответствует получению ресурсов,
которые контролирует семафор. Если IPC_NOWAIT не установлен, то
вызывающий процесс засыпает, пока семафор не выдаст требуемое
количество ресурсов (пока другой процесс не освободит их).
Если sem_op положителен, то его значение добавляется к семафору.
Это соответствует возвращению ресурсов множеству семафоров приложения.
Ресурсы всегда нужно возвращать множеству семафоров, если они больше
не используются!
- 56 -
Наконец, если sem_op равен нулю, то вызывающий процесс будет
усыплен (sleep()), пока значение семафора не станет нулем. Это
соответствует ожиданию того, что ресурсы будут использованы на 100%.
Хорошим примером был бы демон, запущенный с суперпользовательскими
правами, динамически регулирующий размеры множества семафоров, если
оно достигло стопроцентного использования.
Чтобы пояснить вызов semop, вспомним нашу комнату с принтерами.
Пусть мы имеем только один принтер, способный выполнять только одно
задание за раз. Мы создаем множество семафоров из одного семафора
(только один принтер) и устанавливаем его начальное значение в 1
(только одно задание за раз).
Каждый раз, посылая задание на принтер, нам нужно сначала
убедиться, что он свободен. Мы делаем это, пытаясь получить от
семафора единицу ресурса. Давайте заполним массив sembuf, необходимый
для выполнения операции:
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 идентификатором для нашего множества
семафоров.
Когда задание на принтере выполнится, мы должны вернуть ресурсы
обратно множеству семафоров, чтобы принтером могли пользоваться
- 57 -
другие.
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-ключ, но и конкретный семафор из
множества.
Оба системных вызова используют аргумент cmd для определения
команды, которая будет выполнена над IPC-объектом. Оставшаяся разница
заключается в последнем аргументе. В msgctl он представляет копию
внутренней структуры данных ядра. Повторим, что мы используем эту
структуру для получения внутренней информации об очереди сообщений
либо для установки или изменения прав доступа и владения очередью. Для
семафоров поддерживаются дополнительные команды, которые требуют
- 58 -
данных более сложного типа в последнем аргументе. Использование
объединения (union) огорчает многих новичков до состояния %(. Мы очень
внимательно разберем эту структуру, чтобы не возникало никакой
путаницы.
Первый аргумент semctl() является ключом (в нашем случае
возвращаемым вызовом semget). Второй аргумент (semun) - это номер
семафора, над которым совершается операция. По существу, он может быть
понят как индекс на множестве семафоров, где первый семафор
представлен нулем.
Аргумент cmd представляет собой команду, которая будет выполнена
над множеством. Как вы можете заметить, здесь снова присутствуют
IPC_STAT/IPC_SET вместе с кучей дополнительных команд, специфичных для
множеств семафоров:
IPC_STAT
Берет структуру semid_ds для множества и запоминает ее по адресу
аргумента buf в объединении semun.
IPC_SET
Устанавливает значение элемента ipc_perm структуры semid_ds для
множества.
IPC_RMID
Удаляет множество из ядра.
GETALL
Используется для получения значений всех семафоров множества.
Целые значения запоминаются в массиве элементов unsigned short, на
который указывает член объединения array.
GETNCNT
Выдает число процессов, ожидающих ресурсов в данный момент.
GETPID
Возвращает PID процесса, выполнившего последний вызов semop.
- 59 -
GETVAL
Возвращает значение одного семафора из множества.
GETZCNT
Возвращает число процессов, ожидающих стопроцентного освобождения
ресурса.
SETALL
Устанавливает значения семафоров множества, взятые из элемента
array объединения.
SETVAL
Устанавливает значение конкретного семафора множества как элемент
val объединения.
Аргумент arg вызова semсtl() является примером объединения semun,
описанного в linux/sem.h следующим образом:
/* аргумент arg для системного вызова semctl */
union semun {
int val; /* значение для SETVAL-а */
struct semid_ds *buf; /* буфер для IPC_STAT и IPC_SET */
ushort *array; /* массив для GETALL и SETALL */
struct seminfo *__buf; /* буфер для IPC_INFO */
void *__pad;
};
val
Определяет значение, в которое устанавливается семафор командой
SETVAL.
buf
Используется командами IPC_STAT/IPC_SET. Представляет копию
внутренней структуры данных семафора, находящейся в ядре.
array
Указатель для команд GETALL/SETALL. Ссылается на массив целых,
используемый для установки или получения всех значений семафоров в
- 60 -
множестве.
Оставшиеся аргументы __buf и __pad предназначены для ядра и
почти, а то и вовсе не нужны разработчику приложения. Эти два
аргумента специфичны для LINUX-а, их нет в других системах UNIX-а.
Поскольку этот особенный системный вызов наиболее сложен для
восприятия среди всех системных вызовов System V IPC, мы рассмотрим
несколько его примеров в действии.
Следующий отрывок выдает значение указанного семафора. Последний
аргумент (объединение) игнорируется, если используется команда GETVAL.
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; xsem_perm.mode);
/* Изменяем права доступа к семафору */
sscanf(mode, "%0", &semopts.buf->sem_perm.mode);
/* Обновляем внутреннюю структуру данных */
semctl(sid, 0, IPC_SET, semopts);
printf("Updated...\n");
- 62 -
}
Программа пытается создать локальную копию внутренней структуры
данных для множества семафоров, изменить права доступа и сIPC_SETить
их обратно в ядро. Однако, первый вызов semctl-а немедленно вернет
EFAULT или ошибочный адрес для последнего аргумента (объединения!).
Кроме того, если бы мы не следили за ошибками для этого вызова, то
заработали бы сбой памяти. Почему?
Вспомним, что команды IPC_SET/IPC_STAT используют элемент buf
объединения, который является указателем на тип semid_ds. Указатели -
это указатели, и ничего кроме указателей! Элемент buf должен ссылаться
на некий корректный участок памяти, чтобы наша функция работала как
полагается. Рассмотрим исправленную версию:
void changemode(int sid, char *mode)
{
int rc;
struct semid_ds mysemds;
/* Получаем текущие значения для внутренней структуры данных */
/* Сначала указываем на нашу локальную копию! */
semopts.buf = &mysemds;
/* Попробуем еще разок! */
if((rc = semctl(sid, 0, IPC_STAT, semopts)) == -1)
{
perror("semctl");
exit(1);
}
printf("Old permissions were %o\n", semopts.buf->sem_perm.mode);
/* Изменяем права доступа к семафору */
sscanf(mode, "%0", &semopts.buf->sem_perm.mode);
/* Обновляем внутреннюю структуру данных */
semctl(sid, 0, IPC_SET, semopts);
- 63 -
printf("Updated...\n");
}
semtool: Интерактивное средство для работы с семафорами
Описание
Поведение semtool()-а зависит от аргументов командной строки, что
удобно для вызова из скрипта shell-а. Позволяет делать все, что
угодно, от создания и манипулирования до редактирования прав доступа и
удаления множества семафоров. Может быть использовано для управления
разделяемыми ресурсами через стандартные скрипты shell-а.
Синтаксис командной строки
Создание множества семафоров
semtool c (количество семафоров в множестве)
Запирание семафора
semtool l (номер семафора для запирания)
Отпирание семафора
semtool u (номер семафора для отпирания)
Изменение прав доступа (mode)
semtool m (mode)
Удаление множества семафоров
semtool d
Примеры
semtool c 5
semtool l
semtool u
semtool m 660
semtool d
- 64 -
Исходный текст
/****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
****************************************************************************
MODULE: semtool.c
****************************************************************************
Средство командной строки для работы со множествами семафоров в стиле SysV
****************************************************************************/
#include
#include
#include
#include
#include
#include #define SEM_RESOURCE_MAX 1
/* Начальное значение для всех семафоров */
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();
- 65 -
/* Создаем особый ключ через вызов 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)
{
/* Открываем множество семафоров - не создаем! */
if((*sid = semget(key, 0, 0666)) == -1)
{
- 66 -
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;
/* Инициализируем все элементы (может быть сделано с SETALL) */
for(cntr=0; cntr(get_member_count(sid)-1))
{
fprintf(stderr, "semaphore member %d out of range\n", member);
return;
- 67 -
}
/* Попытка запереть множество семафоров */
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;
}
/* Заперто ли множество семафоров? */
semval = getval(sid, member);
if(semval == SEM_RESOURCE_MAX) {
fprintf(stderr, "Semaphore not locked!\n");
exit(1);
- 68 -
}
sem_unlock.sem_num = member;
/* Попытка запереть множество семафоров */
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(semopts.buf->sem_nsems);
}
int getval(int sid, int member)
{
int semval;
semval = semctl(sid, member, GETVAL, 0);
return(semval);
- 69 -
}
void changemode(int sid, char *mode)
{
int rc;
union semun semopts;
struct semid_ds mysemds;
/* Получаем текущее значение для внутренней структуры данных */
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);
/* Изменяем права доступа к семафору */
sscanf(mode, "%ho", &semopts.buf->sem_perm.mode);
/* Обновляем внутреннюю структуру данных */
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);
}
- 70 -
void usage(void)
{
fprintf(stderr, "semtool - A utility for tinkering with semaphores\n");
fprintf(stderr, "\nUSAGE: semtool4 (c)reate \n");
fprintf(stderr, " (l)ock \n");
fprintf(stderr, " (u)nlock \n");
fprintf(stderr, " (d)elete\n");
fprintf(stderr, " (m)ode \n");
exit(1);
}
semstat: Программа-компаньон для semtool
В дополнение к semtool, приведем исходный текст
программы-компаньона semstat. Она выводит на экран значение каждого из
семафоров множества, созданного посредством semtool.
/****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
****************************************************************************
MODULE: semstat.c
****************************************************************************
Компаньон для cредства командной строки semtool-а. Semstat выводит на экран
текущее значение всех семафоров множества, созданного посредством semtool.
****************************************************************************/
#include
#include
#include
#include
#include
int get_sem_count(int sid);
void show_sem_usage(int sid);
int get_sem_count(int sid);
void dispval(int sid);
- 71 -
int main(int argc, char *argv[])
{
key_t key;
int semset_id;
/* Создаем уникальный ключ через вызов ftok() */
key = ftok(".", 's');
/* Открываем множество семафоров - не создаем! */
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;
/* Получаем текущие значения для внутренней структуры данных */
- 72 -
semopts.buf = &mysemds;
if((rc = semctl(sid, 0, IPCJ_STAT, semopts)) == -1) {
perror("semctl");
exit(1);
}
/* Выдаем количество семафоров в множестве */
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. Разделяемая память
Основные понятия
Разделяемая память может быть наилучшим образом описана как
отображение участка (сегмента) памяти, которая будет разделена между
более чем одним процессом. Это гораздо более быстрая форма IPC, потому
что здесь нет никакого посредничества (т.е. каналов, очередей
сообщений и т.п.). Вместо этого, информация отображается
непосредственно из сегмента памяти в адресное пространство вызывающего
процесса. Сегмент может быть создан одним процессом и впоследствии
использован для чтения/записи любым количеством процессов.
Внутренние и пользовательские структуры данных
Давайте взглянем на структуру данных, поддерживаемую ядром, для
разделяемых сегментов памяти.
- 73 -
Структура ядра shmid_ds
Так же, как для очередей сообщений и множеств семафоров, ядро
поддерживает специальную внутреннюю структуру данных для каждого
разделяемого сегмента памяти, который существует врутри его адресного
пространства. Такая структура имеет тип shmid_ds и определена в
Linux/shm.h как следующая:
/* Структура данных shmid для каждого разделяемого сегмента памяти
системы */
struct shmid_ds {
struct ipc_perm shm_perm; /* права доступа */
int shm_segsz; /* размеры сегмента (в байтах) */
time_t shm_atime; /* время последней привязки */
time_t shm_dtime; /* время последней отвязки */
time_t shm_ctime; /* время последнего изменения */
unsigned short shm_cpid; /* pid создателя */
unsigned short shm_lpid; /* pid последнего пользователя
сегмента */
short shm_nattch; /* номер текущей привязки */
/* следующее носит частный характер */
unsigned short shm_npages; /* размеры сегмента (в страницах) */
unsigned long *shm_pages; /* массив указателей на $frames -> S$ */
struct vm_area_struct *attaches; /* дескрипторы для привязок */
};
Операции этой структуры исполняются посредством специального
системного вызова и с ними не следует работать непосредственно. Вот
описания полей, наиболее относящихся к делу:
shm_perm
Это образец структуры ipc_perm, который определен в Linux/ipc.h.
Он содержит информацию о доступе к сегменту, включая права доступа и
информацию о создателе сегмента (uid и т.п.).
- 74 -
shm_segsz
Размеры сегмента (в байтах).
shm_atime
Время последней привязки к сегменту.
shm_dtime
Время последней отвязки процесса от сегмента.
shm_ctime
Время последнего изменения этой структуры (изменение mode и т.п.).
shm_cpid
PID создавшего процесса.
shm_lpid
PID последнего процесса обратившегося к сегменту.
shm_nattch
Число процессов, привязанных к сегменту на данный момент.
Системный вызов shmget()
Чтобы создать новый разделяемый сегмент памяти или получить
доступ к уже существующему, используется системный вызов shmget().
SYSTEM CALL: shmget();
PROTOTYPE: int shmget ( key_t key, int size, int shmflg );
RETURNS: идентификатор разделяемого сегмента памяти в случае успеха
-1 в случае ошибки:
errno = EINVAL (Ошибочно заданы размеры сегмента)
EEXIST (Сегмент существует, нельзя создать)
EIDRM (Сегмент отмечен для удаления, или был удален)
ENOENT (Сегмент не существует)
EACCESS (Доступ отклонен)
ENOMEM (Недостаточно памяти для создания сегмента)
- 75 -
NOTES:
Этот новый вызов должен выглядеть для вас почти как старые
новости. Он поразительно похож на соответствующие вызовы get для
очередей сообщений и множеств семафоров.
Первый аргумент для shmget() - это значение ключа (в нашем случае
возвращен посредством вызова ftok()-а). Это значение ключа затем
сравнивается с существующими значениями, которые находятся внутри ядра
для других разделяемых сегментов памяти. В этом отношении операция
открытия или получения доступа зависит от содержания аргумента shmflg.
IPC_CREAT
Создает сегмент, если он еще не существует в ядре.
IPC_EXCL
При использовании совместно с IPC_CREAT приводит к ошибке, если
сегмент уже существует.
Если используется один IPC_CREAT, то shmget() возвращает либо
идентификатор для вновь созданного сегмента, либо идентификатор для
сегмента, который уже существует с тем же значением ключа. Если вместе
с IPC_CREAT используется IPC_EXCL, тогда либо создается новый сегмент,
либо, если сегмент уже существует, вызов "проваливается" с -1.
IPC_EXCL сам по себе бесполезен, но если он комбинируется с IPC_CREAT,
то может быть использован как способ получения гарантии, что нет уже
существующих сегментов, открытых для доступа.
Повторимся, (необязательный) восьмеричный доступ может быть
объеденен по ИЛИ в маску доступа.
Давайте создадим функцию-переходник для обнаружения или создания
разделяемого сегмента памяти:
int open_segment( key_t keyval, int segsize )
{
int shmid;
if((shmid = shmget( keyval, segsize, IPC_CREAT | 0660 )) == -1)
- 76 -
{
return(-1);
}
return(shmid);
}
Отметьте явное использование 0660 для прав доступа. Эта небольшая
функция выдает либо идентификатор разделяемого сегмента памяти (int),
либо -1 в случае ошибки. Значение ключа и заказанные размеры сегмента
(в байтах) передаются в виде аргументов.
Как только процесс получает действующий идентификатор IPC для
выделяемого сегмента, следующим шагом является привязка или размещение
сегмента в адресном пространстве процесса.
Системный вызов shmat()
SYSTEM CALL: shmat();
PROTOTYPE: int shmat ( int shmid, char *shmaddr, int shmflg );
RETURNS: адрес, по которому сегмент был привязан к процессу, в случае
успеха
-1 в случае ошибки:
errno = EINVAL (Ошибочно значение IPC ID или адрес привязки)
ENOMEM (Недостаточно памяти для привязки сегмента)
EACCES (Права отклонены)
NOTES:
Если аргумент addr является нулем, ядро пытается найти
нераспределенную область. Это рекомендуемый метод. Адрес может быть
указан, но типично это используется только для облегчения работы
аппаратного обеспечения или для разрешения конфликтов с другими
приложениями. Флаг SHM_RND может быть OR-нут в аргумент флага, чтобы
заставить переданный адрес выровняться по странице (округление до
ближайшей страницы).
- 77 -
Кроме того, если устанавливается флаг SHM_RDONLY, то разделяемый
сегмент памяти будет распределен, но помечен readonly.
Этот вызов, пожалуй, наиболее прост в использовании. Рассмотрим
функцию-переходник, которая по корректному идентификатору сегмента
возвращает адрес привязки сегмента:
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 но
вызывающий процесс не имеет прав на запись (измените права доступа))
- 78 -
NOTES:
Вызов очень похож на msgctl(), выполняющий подобные задачи для
очередей сообщений. Поэтому мы не будем слишком детально его
обсуждать. Употребляемые значения команд следующие:
IPC_STAT
Берет структуру shmid_ds для сегмента и сохрает ее по адресу,
указанному buf-ом.
IPC_SET
Устанавливает значение ipc_perm-элемента структуры shmid_ds. Сами
величины берет из аргумента buf.
IPC_RMID
Помечает сегмент для удаления.
IPC_RMID в действительности не удаляет сегмент из ядра, а только
помечает для удаления. Настоящее же удаление не происходит, пока
последний процесс, привязанный к сегменту, не "отвяжется" от него как
следует. Конечно, если ни один процесс не привязан к сегменту на
данный момент, удаление осуществляется немедленно.
Снятие привязки производит системный вызов shmdt.
Системный вызов shmdt()
SYSTEM SALL: shmdt();
PROTOTYPE: int shmdt ( char *shmaddr );
RETURNS: -1 в случае ошибки:
errno = EINVAL (ошибочно указан адрес привязки) После того, как
разделяемый сегмент памяти больше не нужен процессу, он должен быть
отсоединен вызовом shmdt(). Как уже отмечалось, это не то же самое,
что удаление сегмента из ядра. После успешного отсоединения значение
элемента shm_nattch структуры shmid_ds уменьшается на 1. Когда оно
достигает 0, ядро физически удаляет сегмент.
- 79 -
shmtool: Интерактивный обработчик разделяемых сегментов памяти
Описание
Наш последний пример объектов System V IPC - это shmtool:
средство командной строки для создания, чтения, записи и удаления
разделяемых сегментов памяти. Так же, как и в предыдущий примерах, во
время исполнения любой операции сегмент создается, если его прежде не
было.
Синтаксис командной строки
Запись строк в сегмент
shmtool w "text"
Получение строк из сегмента
shmtool r
Изменение прав доступа (mode)
shmtool m (mode)
Удаление сегмента
shmtool d
Примеры
shmtool w test
shmtool w "This is a test"
shmtool r
shmtool d
shmtool m 660
Код
#include
#include
#include
#include
- 80 -
#define SEGSIZE 100main(int argc, char *argv[])
{
key_t key;
int shmid, cntr;
char *segptr;
if(argc == 1)
usage();
/* Создаем особый ключ через вызов ftok() */
key = ftok(".", 'S');
/* Открываем разделяемый сегмкнт памяти - если нужно, создаем */
if((shmid = shmget(key, SEGSIZE, IPC_CREAT|IPC_EXCL|0660)) == -1)
{
printf("Shared memory segment exists - opening as a client\n");
/* Скорее всего, сегмент уже существует - попробуем как клиент */
if((shmid = shmget(key, SEGSIZE, 0)) == -1)
{
perror("shmget");
exit(1);
}
}
else
{
printf("Creating new shared memory segment\n");
}
/* Привязывем (распределяем) разделяемый сегмент памяти в текущем
процессе */
if((segptr = shmat(shmid, 0, 0)) == -1)
{
perror("shmat");
exit(1);
}
- 81 -
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: %\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;
/* Копируем текущее значение для внутренней структуры данных в myshmds */
shmctl(shmid, IPC_STAT, &myshmds);
- 82 -
/* Выводим на экран старые права доступа */
printf("Old permissions were: %o\n", myshmds.shm_perm.mode);
/* Исправляем значение режима доступа в копии */
sscanf(mode, "%o", &myshmds.shm_perm.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 \n");
fprintf(stderr, " (r)ead\n");
fprintf(stderr, " (d)elete\n");
exit(1); fprintf(stderr, " (m)ode change \n");
exit(1);
}
7. Программирование звука
ПК имеет как минимум одно звуковое устройство: встроенный
микрофон (PC speaker). Вы также можете купить звуковую карту для
вашего ПК, обладающую большими звуковыми возможностями (см. Linux
Sound User's Guide или Sound-HOWTO для поддерживаемых звуковых карт).
7.1. Программирование встроенного динамика
Встроенный динамик - это часть консоли Linux и, поэтому является
символьным устройством. Как следствие, существуют запросы ioctl для
манипуляций с ним. Для встроенного микрофона это такие два запроса:
- 83 -
1. KDMKTONE
Генерирует сигнал beep заданной длительности, используя таймер
ядра. Например, ioctl(fd, KDMKTONE, (long) argument).
2. KIOCSOUND
Генерирует бесконечный beep или прерывает звучащий в настоящий
момент. Например, ioctl(fd, KIOCSOUND, (int) tone).
Третий аргумент первого примера содержит значение тона в нижнем
слове и сдвиг в верхнем. Тон - это не частота. Таймер 8254 материнской
платы ПК заведен на 1.19 МГц и поэтому тон - это 1190000/частота.
Сдвиг измеряется в шагах таймера. Оба вызова срабатывают немедленно,
поэтому вы можете порождать звуковые сигналы, не блокируя программу.
KDMKTONE можно использовать для предупреждающих сигналов,
поскольку вам не приходится заботиться о его прекращении.
При помощи KIOCSOUND можно проигрывать мелодии, как это
демонстрируется в примере программы splay. Для остановки сигнала
значение тона устанавливается в 0.
7.2. Программирование звуковой карты
Для вас как для программиста важно знать, что современные
Linux-системы имеет встроенную звуковую карту. Один путь проверки -
испытать /dev/sndstat. Если открытие /dev/sndstat закончилось неудачно
и errno = ENODEV, то это означает, что ни один звуковой драйвер не
активирован, и вы не получите никакой помощи от звукового драйвера
ядра. Тот же результат может иметь попытка открыть /dev/dsp, если он
не связан с драйвером pcsnd.
Если вы хотите повозиться со звуковыми картами на уровне железа,
знайте, что комбинация вызовов outb() и inb() определит звуковую
карту, которую вы ищете.
Программы, использующие звуковой драйвер, будут хорошо
переноситься на другие i386 системы, поскольку некоторые умные люди
решили использовать один драйвер для Linux, isc, FreeBSD и большинства
- 84 -
других i386 архитектур. Звуковая карта - это не часть консоли Linux,
но специальное устройство. Звуковая карта предлагает три основных
возможности:
* цифровой образец ввода/вывода
* вывод модуляции частоты
* midi интерфейс
Каждая из возможностей имеет свой драйвер устройства. Для
цифровых сэмплов это /dev/dsp, для модуляции частоты - /dev/sequencer,
для midi интерфейса - /dev/midi. Звуковые установки (volume, balance,
bass) контролируются через /dev/mixer интерфейс. В целях сравнения
существует устройство /dev/audio, которое может отображать мю-low
звуковые данные в цифровое устройство.
Вы были правы, если подумали, что нужно использовать ioctl() для
работы с этими устройствами. Запросы ioctl() определены в
и начинаются с SNDCTL_.
Поскольку у автора нет звуковой карты, продолжать здесь должен
кто-то явно другой.
8. Символьная графика
Эта глава имеет дело с вводом/выводом символов на экран. Когда мы
говорим "символ", то подразумеваем композицию пикселов, которая может
меняться в зависимости от таблицы представлений символов (charset).
Ваша графическая карта уже предлагает одну или более таких таблиц и по
умолчанию работает в текстовом (charset) режиме, потому что текст
обрабатывается быстрее, чем пиксельная графика. Терминалы можно
использовать лучше, чем как простые и скучные текстовые дисплеи.
Рассмотрим, как использовать специальные возможности, которые
предлагает терминал Linux-а, особенно консоль Linux-а.
- 85 -
* printf, sprintf, fprintf, scanf, sscanf, fscanf
С этими функциями libc вы можете выдавать форматированные строки
в stdout (стандартный вывод), с stderr (стандартная ошибка) или другие
потоки, определенные как FILE *stream (например, файлы). sscanf
обеспечивает подобные возможности для чтения форматированного ввода из
stdin.
* termcap
База данных TERMinal CAPabilitie - это таблица элементов описания
работы с терминалом в ASCII-файле /etc/termcap. Здесь вы можете найти
информацию о том, как выводить специальные символы, как осуществлять
операции (удаления, вставки символов или строк и т.д.) и как
инициализировать терминал. База данных используется, например,
редактором vi. Имеются библиотечные функции для чтения и использования
возможностей терминала (смотри termcap(3x)). С этой базой данных
программы могут работать с различными терминалами одним и тем же
кодом. База данных termcap и библиотечные функции предоставляют только
низкоуровневый доступ к терминалу. Изменение атрибутов или цветов,
параметризованный вывод и оптимизация остаются программисту.
* База данных terminfo
База данных TERMinal INFOrmation построена над базой данных
termcap и описывает некоторые возможности терминалов на более высоком
уровне. С terminfo программа может легко менять атрибуты экрана,
используя специальные клавиши, такие как функциональные клавиши и др.
Эта база данных может быть найдена в /usr/lib/terminfo/[A-z,0-9]*.
Каждый файл описывает один терминал.
* curses
Terminfo - хорошая база для работы с терминалом в программе.
Библиотека (BSD-)curses дает вам высокоуровневый доступ к терминалу,
базируясь на terminfo. curses позволяет открывать и манипулировать
окнами на экране, предоставляет весь необходимый набор функций
ввода/вывода и может изменять видеоатрибуты терминально-независимым
образом на более чем 150 терминалах. Библиотека находится в
/usr/lib/libcurses.a. Это BSD-версия curses.
- 86 -
* ncurses
ncurses - это развитие curses. В версии 1.8.6 она должна быть
совместима с AT&T curses, как это определено в SYSVR4, и иметь
несколько расширений, таких как манипулирование цветами, специальная
оптимизация для вывода, оптимизации, зависящие от терминала, и др.
ncurses была протестирована на множестве систем, таких как Sun-OS, HP
и Linux. Автор рекомендует предпочесть ncurses всему остальному. В
SYSV Unix системах (например, Solaris) должна существовать библиотека
curses с теми же функциональными возможностями, что и ncurses (на
самом деле солярисовская curses имеет немного больше функций и
поддержку мыши).
В следующих разделах мы рассмотрим, как пользоваться различными
пакетами доступа к терминалу. В Linux-е мы имеем GNU-версию termcap и
можем пользоваться ncurses вместо curses.
8.1. Функции ввода/вывода в libc
8.1.1. Форматированный вывод
Функции printf(...) в libc обеспечивают форматированный вывод и
позволяют трансформировать аргументы.
* int fprintf(FILE *stream, const char *format, ...),
преобразует выводимые аргументы в соответствии с шаблоном и
записывает его в stream. Формат определяется аргументом format.
Функция возвращает число записанных символов или отрицательное число в
случае ошибки.
format содержит два типа объектов: обычные символы и информацию,
как трансформировать или форматировать аргументы.
Форматная информация должна начинаться с %, за которым следуют
значения для формата, дальше идет символ для трансляции (чтобы
напечатать знак %, используйте %%). Возможны следующие значения для
формата:
- 87 -
- Флаги
* -
Форматированный аргумент будет при печати прижат влево на своем
поле.
* +
Каждое число будет напечатано со знаком, например, +12 или -2.32.
- Пробел
Если первый символ - не знак, то будет вставлен пробел.
- 0
Для чисел ширина поля будет заполнена слева нулями.
- #
Изменяет вывод в зависимости от трансформации для аргумента:
* Для o первое число будет 0.
* Для x или X будет напечатано в конце 0x или 0X соответственно.
* Для e, E, f, F вывод имеет десятичную точку.
* Для g или G в конце аргумента будут напечатаны нули.
- Число, указывающее минимальную ширину поля
Трансформированный аргумент печатается в поле, ширина которого не
меньше, чем сам аргумент. С этим числом вы можете увеличить ширину
поля. Если аргумент меньше, то оставшаяся часть поля заполняется
пробелами или нулями.
- Точка для отделения ширины поля и точности
- Число для точности
Возможные значения для трансформации смотри в таблице 8.1.
* int printf(const char *format, ...)
То же самое, что fprintf(stdout, ...).
* int sprintf(char *s, const char *format, ...)
То же, что и printf(...), но вывод будет записан в символьный
указатель s (с последующим \0). (Вы должны захватить достаточно памяти
- 88 -
для s.)
* vprintf(const char *format, va_list arg)
vfprintf(FILE *stream, const char *format, va_list arg)
vsprintf(char *s, const char *format, va_list arg)
То же, что и для вышеописанных функций, только список аргументов
находится в arg.
8.1.2. Форматированный ввод
Точно так же, как printf(...) для форматированного вывода, вы
можете использовать scanf(...) для форматированного ввода.
* int fscanf(FILE *stream, const char *format, ...)
fscanf(...) читает из stream и преобразует ввод по правилам,
определяемым в format. Результаты помещаются в аргументы, заданные в
"..." (эти аргументы должны быть указателями!). Чтение заканчивается,
когда в format исчерпаны правила форматирования.
Таблица 8.1: Libc - трансформации printf
Символ | Форматируется в
--------|-----------------------------------
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 *
% | %
fscanf вернет EOF, при первом достижении конца файла или при
возникшей ошибке. Если этого не случится, будет возвращено количество
- 89 -
трансформированных аргументов.
format может содержать правила форматирования аргументов (см.
табл. 8.2)
Он может также включать:
- пропуски или табуляции, которые игнорируются;
- любой нормальный символ, кроме %. Символы должны быть во вводе
на соответствующих позициях.
- правила преобразования, заданные с %, необязательный символ *
(позволит fscanf(...) присвоить аргументу), необязательное
число, необязательный символ h, l или L (для задания длины
считываемой инфомации) и символ трансформации.
* int scanf(const char *format, ...)
То же, что fscanf(stdin,...)
* int sscanf(char *str, const char *format, ...)
То же, что scanf, но ввод производится из строки str.
8.2. Библиотека termcap
8.2.1. Введение
Библиотека termcap - это API для базы данных termcap, которая
находится в /etc/termcap/. Библиотечные функции позволяют:
* получить описание текущего терминала: tgetent(...);
- 90 -
Таблица 8.2: libc - трансформации sсanf
Символ | Вход - тип аргумента
-------|---------------------------------------------------------------
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*
% | %
-----------------------------------------------------------------------
перед d,i,n,o,u,x может стоять h, если указатель - short
то же для l, если указатель - long
l также может быть перед e,f,g, если указатель - double
L может стоять перед e,f,g, если указатель - long double
* найти описание для информации: tgetnum(...), tgetflag(...),
tgetstr(...);
* вычислить и произвести поточечный вывод: tputs()
Программы, использующие библиотеку termcap должны включать
termcap.h и собираться с:
gcc [flags] files -ltermcap
Функции termcap терминально-независимые программы, но дают
программисту только низкоуровневый доступ к терминалу. Для пакета
более высокого уровня потребуется curses или ncurses.
- 91 -
8.2.2. Поиск описания терминала
* int tgetent(void *buffer, const char *termtype)
В операционной системе Linux текущее имя терминала содержится в
переменой среды TERM. Поэтому termtype есть результат вызова
getenv(3). Что касается buffer, то в GNU-версии Linux termcap не нужно
захватывать память. В других версиях вам придется выделить 2048 байт
(прежде buffer требовал 1024 байта, но сейчас размер удвоился).
tgetent(...) возвращает 1 в случае успеха и 0 когда база данных
найдена, но не имеет точки входа для TERM. Другие ошибки возвращают
различные значения.
Следующий пример объясняет как использовать getent(...):
#define buffer 0
char *termtype=getenv("TERM");
int ok;
ok=tgetent(buffer,termtype);
if(ok==1)
/* все нормально, мы имеем вход */
else if(ok==0)
/* ой, что-то не так с TERM
* проверим сначала termtype, затем базу данных termcap
*/
else
/* у-у-у, глобальная ошибка */
По умолчанию termcap использует /etc/termcap/ как базу данных.
Если переменная среды TERMCAP установлена, например, в
$HOME/mytermcap, то все функции будут пользоваться mytermcap вместо
/etc/termcap. Без начального слэша в TERMCAP определенное значение
понимается как имя для терминала.
- 92 -
8.2.3. Описание терминала
Каждый фрагмент информации называется свойством (capability).
Каждое свойство - это двухлитерный код, за каждым двухлитерным кодом
стоит значение свойства. Возможны следующие типы свойств.
* Числовой: например, co - число столбцов
* Логический или флаговый: например, hc - терминал твердой копии
(hardcopy terminal)
* Строковый: например, st - установка табуляции (подробнее см.
8.22.3)
Каждое свойство связано с единственным типом значений (co всегда
числовой, hc всегда флаг, а st всегда строка). Три типа значений - и
три типа функций, их запрашивающих. char *name - это двухлитерный код
свойства.
* int tgetnum(char *name)
Получение свойства с числовым значением, таким как co. Функция
tgetnum(...) возвращает числовое значение, если свойство доступно, 1 в
противном случае. (Заметьте, что возвращаемое значение всегда
неотрицательно.)
* int tgetflag(char *name)
Получение логического свойства. Возвращает 1, если флаг
установлен, 0 в противном случае.
* char *tgetstr(char *name, char **area)
Получение строкового свойства. Возвращает указатель на строку или
NULL в случае отсутствия. В GNU-версии, если area есть NULL, termcap
выделит память сам. termcap больше не позаботится об этом указателе,
если вы не освободите name перед выходом из программы. Такой метод
предпочтителен, поскольку вы не знаете сколько памяти потребуется для
указателя, поэтому позвольте termcap сделать все за вас.
- 93 -
char *clstr, *cmstr;
int lines, cols;
void term_caps()
{
char *tmp;
clstr=tgetstr("cl",0); /* очистка экрана */
cmstr=tgetstr("cm",0); /* перемещение y,x */
lines=tgetnum("li"); /* полосы терминала */
cols=tgetnum("co"); /* колонки терминала */
tmp=tgetstr("pc",0); /* символ дозаполнения */
PC=tmp ? *tmp : 0;
BC=tgetstr("le",0); /* сдвиг курсора на символ влево */
UP=tgetstr("up",0); /* сдвиг курсора на линию вверх */
}
8.2.4. Свойства termcap
Логические свойства
5i принтер не имеет эха на экране
am автоматические границы, что означает автоматическое
форматирование строки
bs Crtl-H представляет backspace
bw backspace на левой границе переносит строку на правую границу
предыдущей
da вывести сохраненное над текущим экраном
db вывести сохраненное под текущим экраном
eo пробел стирает литеру на позиции курсора
es esc-последовательности и специальные символы работают в строке
состояния
gn родовое устройство
hc это терминал твердой копии (hardcopy terminal)
HC курсор плохо видно, когда он не на последней линии
- 94 -
hs присутствует линия статуса
hz терминал не может напечатать тильды (tilde characters)
in терминал вставляет нули вместо пробелов на пустые места
km терминал имеет мета клавишу
mi режим вставки для курсора
ms режим стандартного вывода / подчеркивания для курсора
NP нет символов-заполнителей
NR ti не обращает teos терминал может забивать ошибки
ul терминал подчеркивает, но ошибки забивать не может
xb сбой, вызванный столпотворением, F1 посылает ESCAPE, F2 посылает ^C
xn сбой новой линии / соединения строк
xo терминал использует xon/xoff протокол
xs текст, напечатанный поверх выделенного, будет выделен
xt сбой телевизионного луча, неверная табуляция и странный режим
выделения
Числовые свойства
co число столбцов
dB приостановка на милисекунды для возврата на терминалах твердой
копии
dC приостановка на милисекунды для перевода каретки на терминалах
твердой копии
dF приостановка на милисекунды для заполнения страницы на терминалах
твердой копии
dN приостановка на милисекунды для новой линии на терминалах твердой
копии
dT приостановка на милисекунды для табуляции на терминалах твердой
копии
dV приостановка на милисекунды для вертикальной табуляции на терминалах
твердой копии
it раница между позициями табуляции
lh высота мягких меток
lm линии памяти
lw ширина
li число линий
Nl число мягких меток
pb наименьшая граница, когда требуется дозаполнение
- 95 -
sg сбой режима выделения
ug сбой режима подчеркивания
vt номер виртуального терминала
ws ширина линии статуса, если она отлична от ширины экрана
Строковые свойства
!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 клавиша перестановки
- 96 -
&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
- 97 -
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 символов
- 98 -
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
- 99 -
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 сброс строки имени файла
- 100 -
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
- 101 -
8.3. Введение в ncurses
В этом разделе будем пользоваться следующей терминологией.
* окно (window) - внутреннее представление, содержащее
изображение части экрана. WINDOW определен в ncurses.h.
* экран (screen) - это окно размером в целый экран (с верхнего
левого до нижнего правого угла). Экранами являются stdscr и curscr.
* терминал (terminal) - специальный экран с информацией о том,
как выглядит экран на данный момент.
* переменные - следующие переменные и константы, определенные в
ncurses.h:
- WINDOW *curscr - текущий экран
- WINDOW *stdscr - стандартный экран
- int LINES - полосы на терминале
- int COLS - колонки на терминале
- bool TRUE - флаг истины, 1
- bool FALSE - флаг лжи, 0
- int ERR - флаг ошибки, -1
- int OK - флаг ok, 0
* функции - в описаниях функций аргументы будут следующих типов:
- win - WINDOW*
- bf - bool
- ch - chtype
- str - char*
- chstr - chtype*
- fmt - char*
- иначе int
- 102 -
Обычно программа, использующая ncurses, выглядит так:
#include
...
main()
{
...
initscr();
/* вызов функции ncurses */
endwin();
...
}
Подключение 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 [flags] files -lncurses
Вы можете устанавливать любые флаги (см. gcc(1)). Если путь к
ncurses.h изменился, вы должны включить следующую строку, иначе
ncurses.h, nterm.h, termcap.h и unctrl.h не будут найдены:
- 103 -
-I/usr/include/ncurses
Другие возможные в Linux-е флаги: -O2 -ansi -Wall -m486. O2
скажет gcc произвести некоторую оптимизацию; -ansi - для ANSI си-кода;
-Wall выведет все предупреждения; -m486 оптимизирует код для Intel 486
(можно и для Intel 386).
Библиотека ncurses находится в /usr/lib/. Существует 3 версии
библиотеки:
* libncurses.a - обычная ncurses
* libdcurses.a - ncurses для отладки
* libpcurses.a - ncurses для профилирования (существует ли что-нибудь
после 1.8.6libcurses.a?)
* libcurses.a - не четвертая версия, это первоначальная BSD curses
Структуры данных для экрана называются 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 обращается туда за локальными
- 104 -
определениями терминала. Если вы хотите проверить некоторые другие
определения для терминала, не исправляя первоначальную terminfo,
установите соответственно переменную среды TERMINFO. Эта переменная
будет протестирована ncurses, и вместо usr/lib/terminfo/ сохранятся
ваши определения.
Текущей версией ncurses является 1.8.6.
В конце этого раздела вы найдете обзорную таблицу для BSD-Curses,
ncurses и Sun-OS 5.4 curses.
8.4. Инициализация
* WINDOW *initscr()
Обычно это первая функция, вызываемая из программы, использующей
ncurses. В некоторых случаях полезно вызывать slk_init(int), filter(),
ripoffline(...) или use_env(bf) перед initscr(). Для работы с
несколькими терминалами (или тестирования возможностей системы) вы
можете использовать newterm(...) вместо initscr(). initscr() прочитает
terminfo файл и установит структуры данных ncurses, выделит память для
curscr и stdscr и проинициализирует переменные LINES и COLS
значениями, соответствующими вашему терминалу. Будет возвращен
указатель на stdscr или ERR в случае ошибки. Вам НЕ нужно
инициализировать указатель.
stdscr=initscr();
поскольку initscr() сделает это за вас. Если возвращен ERR, ваша
программа должна завершиться, поскольку ни одна функция ncurses не
будет работать:
if(!(initscr())){
fprintf(stderr,"type: initscr() failed\n\n");
exit (1);
}
- 105 -
* SCREEN *newterm(char *type, FILE *outfd, FILE *infd)
Работая в ncurses с несколькими терминалами, вы должны вызвать
для каждого из них newterm(...) вместо initscr(). type - это имя
терминала как оно содержится в $TERM (ansi, xterm, vt100, например);
outfd - это указатель для вывода, infd - указатель для ввода. Для
каждого терминала, открытого newterm(...), следует вызывать endwin().
* SCREEN *set_term(SCREEN *new)
При помощи set_term(SCREEN) вы можете переключать текущий
терминал. Все функции будут работать с текущим терминалом,
установленным set_term(SCREEN).
* int endwin()
endwin() произведет очистку, восстановит режимы терминала,
сохраненные перед вызовом initscr(), и поставит курсор в левый верхний
угол экрана. Не забудьте закрыть все окна перед тем, как вызвать
endwin(), перед выходом из вашей программы. Дополнительный вызов
refresh() после endwin() восстановит содержимое терминала,
отображавшееся до вызова initscr() (visual-mode), в противном случае
экран будет очищен (non-visual-mode).
* int isendwin()
Возвращает TRUE, если после endwin() была вызвана refresh(),
иначе - FALSE.
* void delscreen(SCREEN *sp)
Вызывается после endwin() для высвобождения всех занятых
ресурсов, когда SCREEN больше не нужен. (Пока не реализована.)
8.5. Окна
Окна могут быть созданы, уничтожены, перемещены, скопированы,
задублированы и т.д.
* WINDOW *newwin(nlines, ncols, begy, begx)
begy и begx - координаты верхнего левого угла окна. nlines - это
число линий (integer); ncols - число колонок (integer).
- 106 -
WINDOW *mywin;
mywin=newwin(10,60,10,10);
Верхний левый угол нашего окна находится на линии 10 в колонке
10; окно имеет 10 линий и 60 колонок. Если nlines равна нулю, окно
будет иметь (LINES - begy) полос. Точно так же, если ncols равна нулю,
то окно будет иметь (COLS - begx) колонок. Когда мы вызываем
newwin(...) с нулевыми аргументами:
WINDOW *mywin;
mywin=newwin(0,0,0,0);
открытое окно получает размеры экрана.
При помощи LINES и COLS мы можем открыть окно на середине экрана,
какую бы размерность оно ни имело:
0 begx
| |
0 | | COLS
- - - - -------|-------------------------|------->
| | ncols |
begy | |<. . . . . . . . . . . .>|
- - - -|- - - -|-------------------------|
| ^| |
| .| |
| .| |
|nlines.| newwin(nlines, ncols, |
| .| begy, begx) |
| v| |
|- - - -|-------------------------|
LINES |
v
#define MYLINE (int) ((LINES-22)/2)
#define MYCOL ((COLS-70)/2)
#define MYLINES 22
#define MYCOLS 70
- 107 -
...
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);
...
Откроется окно с 22 линиями и 70 колонками в середине экрана.
Проверьте размер экрана перед тем, как открывать окно. Консоль Linux-а
имеет не менее 25 линий и 80 колонок, но на x-терминалах это может не
выполняться (их размеры изменяемы).
С другой стороны, используйте LINES и COLS, чтобы поместить два
окна в один экран:
#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);
...
- 108 -
Подробнее смотрите screen.c в директории с примерами.
* int delwin(win)
Удаляет окно win. Подокна win удаляются перед win. Будут
освобождены все ресурсы, занятые win. Удаляйте все открытые вами окна
перед вызовом endwin().
* int mvwin(win, by, bx)
Перемещает окно на координаты by,bx. Если это означает выход за
пределы экрана, то ничего не произойдет и будет возвращен ERR.
* WINDOW *subwin(origwin, nlines, ncols, begy, begx)
Возвращает подокно в середине origwin-а. Когда вы изменяете одно
из двух окон (origwin или новое), это изменение отразится на обоих
окнах. Вызывайте touchwin(origwin) перед следующим refresh().
begx и begy относятся не к origwin, а к экрану.
* WINDOW *derwin(origwin, nlines, ncols, begy, begx)
То же, что subwin(), begx и begy относятся не к экрану, а к
origwin.
* int mvderwin(win, y, x)
Перенесет win за пределы родительского окна. (Пока не
реализована.)
* WINDOW *dupwin(win)
Дублирует окно win.
* int syncok(win, bf)
void wsyncup(win)
void wcursyncup(win)
void wsyncdown(win)
(Пока не реализованы.)
- 109 -
* int overlay(win1, win2)
int overwrite(win1, win2)
overlay(...) скопирует весь текст из win1 в win2, игнорируя
пропуски. overwrite(...) делает то же самое, но копирует вместе с
пропусками.
* int copywin(win1, win2, sminrow, smincol, dminrow, dmincol, dmaxrow,
dmaxcol, overlay)
То же, что overlay(...) и overwrite(...), но позволяет
контролировать область окна для копирования.
8.6. Вывод
* int addch(ch)
int waddch(win, ch)
int mvaddch(y, x, ch)
int mvwaddch(win, y, x, ch)
Эти функции выводят символ в окно. Они работают с окном; вам
нужно вызвать refresh(), чтобы поместить окно на экран. Функции
addch(...) и waddch(...) помещают символ ch в окно stdscr или win.
mvaddch(...) и mvwaddch(...) предварительно ставят курсор на позицию
y,x.
* int addstr(str)
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
("кукареку\0"). Функции, начинающиеся с w, заносят str в окно win,
остальные - в stdscr. Функции с n пишут n символов строки str. Если n
равен -1, будет записана вся строка.
- 110 -
* int addchstr(chstr)
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 echochar(ch)
int wechochar(win, ch)
То же, что addch(...), waddch(win), с последующим refresh(),
wrefresh(win).
8.6.1. Форматированный вывод
* int printw(fmt, ...)
int wprintw(win, fmt, ...)
int mvprintw(y, x, fmt, ...)
int mvwprintw(win, y, x, fmt, ...)
int vwprintw(win, fmt, va_list)
Эти функции соответствуют printf(...) и подобным ей функциям
libc. В libc printf(...) используется для форматированного вывода. Вы
можете определять выводимую строку и включать в нее переменные
различных типов. Подробнее смотрите раздел 8.1.1.
vwprintw(...) требует подключения varargs.h.
- 111 -
8.6.2. Вставка символов и линий
* int insch(c)
int winsch(win, c)
int mvinsch(y, x, c)
int mvwinsch(win, y, x, c)
Символ ch вставляется слева от курсора, и все символы сдвигаются
на одну позицию вправо. Самый правый символ строки может быть потерян.
* int insertln()
int winsertln(win)
Вставляет чистую строку над текущей. Нижняя строка будет
потеряна.
* int insdelln(n)
int winsdelln(win, n)
Для положительного n эти функции вставляют n линий в
соответствующем окне (n нижних линий будут потеряны). Для
отрицательных n будут удалены n линий под курсором, оставшиеся
сдвинутся вверх.
* int insstr(str)
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 в текущую строку слева от курсора
(сколько возможно до конца строки). Символы справа от курсора
сдвигаются вправо и удаляются достигнув конца строки. Курсор остается
на месте. y и x - координаты, на которые переместится курсор перед
вставкой str; n - это число вставляемых символов (n=0 вводит чистую
строку).
- 112 -
8.6.3. Удаление символов и линий
* int delch()
int wdelch(win)
int mvdelch(y, x)
int mvwdelch(win, y, x)
Удаление символа под курсором и сдвиг оставшейся справа от
курсора строки на одну позицию влево.
y и x - координаты, на которые курсор переместится перед
удалением.
* int deleteln()
int wdeleteln(win)
Удаление строки под курсором и перемещение нижележащих линий на
одну позицию вверх. Последняя линия окна будет очищена.
8.6.4. Боксы и линии
* int border(ls, rs, ts, bs, tl, tr, bl, br)
int wborder(win, ls, rs, ts, bs, tl, tr, bl, br)
int box(win, vert, hor)
Очерчивают соответствующее окно (stdscr или win). В таблице 8.3
вы увидите символы и их значения по умолчанию для box(...). На
картинке вы увидите позиции символов для бокса.
* int vline(ch, n)
int wvline(win, ch, n)
int hline(ch, n)
int whline(win, ch, n)
Эти функции вычерчивают вертикальную или горизонтальную прямую
начиная с позиции курсора. ch - это используемый символ, n - число
таких символов. Позиция курсора не изменяется.
- 113 -
8.6.5. Фоновый (background) символ
* void bkgdset(ch)
void wbkgdset(win, ch)
Устанавливает фоновый символ и атрибут для экрана или окна.
Атрибут в ch будет OR-нут с каждым непробельным символом окна. Фон
становится частью окна и не изменяется при прокрутке и вводе/выводе.
Таблица 8.3: ncurses - ограничительные символы
Литера | Позиция | По умолчанию
-------|----------------------|----------------------
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
- 114 -
Рисунок 8.2: ncurses - символы бокса
tl ts tt ts tr
|------------|------------|
| |
ls| | |rs
| |
| | |
lt|- - - - - - - - - - - - -|rt
| | |
| |
ls| | |rs
| |
|------------|------------|
bl bs bt bs br
* int bkgd(ch)
int wbkgd(win, ch)
Установит в ch фоновый символ и атрибут.
8.7. Ввод
* int getch()
int wgetch(win)
int mvgetch(y, x)
int mvwgetch(win, y, x)
getch() прочитает ввод с терминала. Если режим паузы установлен,
getch() будет ждать нажатия клавиши. Если нет - вернет клавишу из
буфера ввода или ERR, если буфер пуст. mvgetch(...) и mvwgetch(...)
сначала установят курсор на позицию y,x. w-функции читают ввод с
терминала, связанного с окном win, getch() и mvgetch(...) - с stdscr.
Со включенной keypad(...) при нажатии функциональной клавиши
getch() вернет код, определенный в ncurses.h как макрос KEY_*. При
нажатии ESCAPE (который может быть началом функциональной клавиши)
ncurses запустит односекундный таймер. Если остаток не получен в
течение этой секунды, то возвращается ESCAPE, иначе - значение
функциональной клавиши. При необходимости секундный таймер можно
отключить через notimeout().
- 115 -
* int ungetch(ch)
Вернет ch в буфер ввода.
* int getstr(str)
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 inch()
chtype winch(win)
chtype mvinch(y, x)
chtype mvwinch(win, y, x)
Эти функции возвращают литеру с экрана или окна. Поскольку
возвращается тип chtype, возвращается и атрибут. Информация об
атрибуте может быть получена с помощью констант A_* (см. таблицу 8.4).
* int instr(str)
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)
Возвращает символьную строку из экрана или окна. (Пока не
реализована.)
- 116 -
* int inchstr(chstr)
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 scanw(fmt, ...)
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. Опции
Опции вывода
* int idlok(win, bf)
void idcok(win, bf)
Включение и отключение возможностей вставки/удаления для окна
терминала (idlok(...) для линий, idcok(...) - для символов).
(idcok(...) пока не реализована.)
* void immedok(win, bf)
Если устанавливается TRUE, то каждое изменение в окне вызывает
физическое обновление экрана. Это может ухудшить характеристики
программы, поэтому значение по умолчанию - FALSE. (Пока не
реализована.)
- 117 -
|-------|-------|-------| |-------|-------|-------|-------|
| ??? | KEY_ | KEY_ | | NUM | / | * | - |
| | HOME | PPAGE | | | | | |
|-------|-------|-------| |-------|-------|-------|-------|
| CTRL | KEY_ | KEY_ | | KEY_ | KEY_ | KEY_ | |
| +D | END | NPAGE | | HOME | UP | PPAGE | |
|-------|-------|-------| |-------|-------|-------| + |
| KEY_ | ??? | KEY_ | |
| LEFT | | RIGHT | |
|-------| |-------|-------|-------|-------|
| KEY_ | | KEY_ | KEY_ | KEY_ | |
| UP | | END | DOWN | NPAGE | CTRL |
|-------|-------|-------| |-------|-------|-------| +M |
| KEY_ | KEY_ | KEY_ | | ??? | KEY_ | |
| LEFT | DOWN | RIGHT | | | DC | |
|-------|-------|-------| |-------|-------|-------|-------|
* int clearok(win, bf)
Если bf равен TRUE, то следующий вызов wrefresh(win) очистит
экран и полностью его перерисует (Ctrl+L в редакторе vi).
* int leaveok(win, bf)
По умолчанию ncurses ставит курсор там, где он был при последнем
обновлении окна. Программы, не использующие курсор, могут установить
leaveok(...) TRUE и сэкономить время, требующееся движения курсора.
Кроме того, ncurses попытается сделать курсор терминала невидимым.
* int nl()
nonl()
Управление переходом на новую строку. После nl() произойдетт
возврат каретки и дозаполнение; nonl() отключает контроль. В последнем
случае ncurses может ускорить перемещение курсора.
- 118 -
8.8.1. Опции ввода
* int keypad(win, bf)
TRUE активизирует keypad на клавиатуре во время ожидания ввода.
Для функциональных клавиш и стрелок keypad ncurses вернет код клавиши,
определенный как KEY_* в ncurses.h. Это очень удобно для клавиатуры
ПК, потому что вы имеете возможность и пользоваться цифровым блоком, и
перемещать курсор.
* int meta(win, bf)
Если TRUE, то коды клавиш, возвращаемые getch(), 8-битовочистые
(верхний бит не учитывается).
* int cbreak()
int nocbreak()
int crmode()
int nocrmode()
cbreak() и nocbreak() устанавливают или снимают режим терминала
CBREAK. Когда CBREAK установлен, читаемый ввод немедленно доступен
программе, когда нет - будет буферизован до получения целой строки.
(Замечание: crmode() и nocrmode() существуют для повышения
совместимости, их использовать не нужно.)
* int raw()
int noraw()
Устанавливает или снимает режим RAW. RAW - это то же, что и
CBREAK, только без обработки специальных символов.
* int echo()
int noecho()
Вызывайте echo() для отображения ввода пользователя и noecho(),
чтобы его скрыть.
* int halfdelay(t)
То же, что cbreak() с паузой в t секунд.
- 119 -
* int nodelay(win, bf)
Терминал устанавливается в неблокируемый режим. getch() вернет
ERR, если ввод не готов. Если bf есть FALSE, то getch() будет ждать
нажатия клавиши.
* int timeout(t)
int wtimeout(win, t)
Эти функции рекомендуется использовать вместо halfdelay(t) и
nodelay(win, bf). Результат getch() зависит от значения t. При
положительном t считывание блокируется на t милисекунд; при t, равном
нулю, блокировки не происходит; при отрицательном t программа
блокируется, пока ввод не станет возможен.
* int notimeout(win, bf)
Если bf равен TRUE, getch() будет использовать односекундный
таймер для интерпретации вводимой последовательности, начинающейся с
ESCAPE и т.п.
* int typeahead(fd)
При fd, равном -1, никакой проверки печати производиться не
будет, при других значениях ncurses будет использовать для таких
проверок файловый дескриптор fd вместо stdin.
* int intrflush(win, bf)
При активизации истинным bf нажатая клавиша прерывания (типа
quit, break) очистит очередь драйвера tty.
* void noqiflush()
void qiflush()
(Пока не реализованы.)
8.8.2. Атрибуты терминала
* int baudrate()
Возвращает скорость терминала в bps (бит в секунду).
* char erasechar()
Возвращает текущий символ erase.
- 120 -
* char killchar()
Возвращает текущий символ kill.
* int has_ic()
int has_il()
has_ic() возвращает TRUE, если терминал может вставлять/удалять
символ, has_il() возвращает TRUE, если терминал может
вставлять/удалять линию. В противном случае возвращается ERR. (Пока не
реализованы.)
* char *longname()
Указатель предоставляет доступ к описанию текущего терминала.
* chtype termattrs()
(Пока не реализована.)
* char *termname
Возвращает содержимое TERM из пользовательской среды. (Пока не
реализована.)
8.8.3. Использование опций
Поговорим об использовании опций окна и режимов терминала.
Прежде всего, под Linux-ом вам следует подключить keypad. Это
позволит пользоваться цифровым блоком и клавишами перемещения курсора
на клавиатуре ПК.
keypad(stdscr,TRUE);
Теперь имеется 2 основных типа ввода:
1) программа ожидает нажатия клавиши, чтобы вызвать
соответствующую функцию (например, что-нибудь вроде "press 'q' for
quit" и ждет q);
2) ожидается, что пользователь напечатает строку символов в маску
на экране (например, каталог или адрес в базе данных).
- 121 -
Для первого случая мы установим следующие опции и режимы, и цикл
while сработает корректно:
char c;
noecho();
timeout(-1);
nonl();
cbreak();
keypad(stdscr,TRUE);
while(c=getch()){
switch(c){
case 'q': your_quit_function();
default: break;
}
}
Эта программа повиснет до нажатия клавиши. Если нажата q, мы
вызываем your_quit_function(), иначе - ждем другого ввода.
Выражение switch может быть расширено по нашему желанию. Макросы
KEY_* служат для учета специальных клавиш. Например,
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();
- 122 -
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;
}
}
Для второго случая, нам достаточно установить echo(), и символы,
набираемые пользователем, будут напечатаны на экране. Место печати
задается функциями move(...) или wmove(...).
Или вы можете открыть окно с маской (выделяемой другими цветами)
и попросить пользователя ввести строку:
WINDOW *maskwin;
WINDOW *mainwin;
char *ptr=(char *)malloc(255);
...
mainwin=newwin(3,37,9,21);
- 123 -
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);
Более подробно см. input.c в директории примеров.
8.9. Очистка окна и линий
* int erase()
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++ )
- 124 -
(chtype *) win-> _line[y][x] = ' '|win-> _attrs;
win -> _curx = win -> _cury = 0; touchwin(win);
}
Проблема состоит в том, что ncurses иногда делает совершенно
бесполезными атрибуты окна, когда заполняет экран пробелами. Если в
lib_clrtoeol.c BLANK определен как
#define BLANK ' '|A_NORMAL,
то другие атрибуты окна теряются, пока идет стирание строки.
* int clear()
int wclear(win)
То же, что erase(), но будет также установлен clearok() (экран
будет очищен с последующим обновлением).
* int clrtobot()
int wclrtobot(win)
Очистка текущей линии курсора (начинается с символа справа от
курсора) и строки под курсором.
* int clrtoeol()
int wclrtoeol(win)
Очистка текущей строки начиная справа от курсора и до конца
строки.
8.10. Обновление терминала
Как написано в обзоре, окна ncurses есть отображения в памяти.
Это означает, что любое изменение окна не отражается на физическом
экране до тех пор, пока не будет произведено обновление. Это
оптимизирует вывод на экран, поскольку вы получаете возможность
совершать множество действий, а затем лишь единожды вызвать
обновление, чтобы напечатать его на экране. В противном случае на
терминале отражалось бы каждое изменение, что ухудшало бы исполнение
ваших программ.
- 125 -
* int refresh()
int wrefresh(win)
refresh() копирует stdscr на терминал, а wrefresh(win) копирует
изображение окна в stdscr и затем делает curscr подобным stdscr.
* int wnoutrefresh(win)
int dourdate()
wnoutrefresh(win) копирует окно win только в stdscr. Это
означает, что вывода на терминал не производится, но виртуальный экран
stdscr на самом деле выглядит именно так, как того хочет программист.
doupdate() произведет вывод на терминал. Программа может менять
различные окна, вызывая wnoutrefresh(win) для каждого окна, а затем
достаточно один раз вызвать doupdate(), чтобы обновить физический
экран.
Допустим, мы имеем следующую программу с двумя окнами. Мы
изменяем оба окна, меняя несколько линий текста. Напишем
cgangewin(win) с wrefresh(win).
main() changewin(WINDOW *win)
{ {
WINDOW *win1,*win2; ... /* здесь мы изменяем */
... ... /* строки */
changewin(win1); wrefresh(win);
changewin(win2); return;
... }
}
Тогда ncurses обновит терминал дважды, а это замедлит исполнение
нашей программы. Благодаря doupdate() мы изменим changewin(win) и нашу
основную функцию, добившись этим лучшего исполнения.
main() changewin(WINDOW *win)
{ {
WINDOW *win1,*win2; ... /* здесь мы изменяем */
... ... /* строки */
changewin(win1); wnoutrefresh(win);
changewin(win2); return;
- 126 -
doupdate(); }
...
}
* int redrawwin(win)
int wredrawln(win, bline, nlines)
Используйте эти функции, когда перед записью чего-нибудь нового
требуется выбросить несколько линий или целый экран (может быть линии
попорчены или что-либо вроде этого).
* int touchwin(win)
int touchline(win, start, count)
int wtouchln(win, y, n, changed)
int untouchwin(win)
Говорит ncurses-у, что были произведены манипуляции с целым окном
или линиями от start до start+count. Например, когда у вас есть
несколько окон, перекрывающих друг друга (как в примере type.c),
изменение одного из них никак не повлияет на изображение других.
wtouchln(...) захватит n линий, начинающихся в y. Если change
соответствует TRUE, то линии захватываются, в противном случае - нет
(изменяются или не изменяются).
untouchwin(win) пометит окно win как неизмененное со времени
последнего вызова refresh().
* int is_linetouched(win, line)
int is_wintouched(win)
При помощи этих функций вы можете проверить, были ли линия line
или окно win захвачены со времени последнего вызова refresh().
- 127 -
Таблица 8.4: Ncurses - атрибуты
Определение | Атрибут
----------------|------------------------------------------------
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
8.11. Видеоатрибуты и цвет
Атрибуты - это специальные возможности терминала, применяемые во
время печати символов на экран. Символы могут быть напечататы жирно,
могут быть подчеркнуты, могут мигать и т.д. В ncurses вы имеете
возможность включать или отключать атрибуты для достижения наилучшего
внешнего вида вывода. Возможные атрибуты перечислены в нижеследующей
таблице.
Определение | Цвет
--------------|-----------
COLOR_BLACK | черный
COLOR_RED | красный
COLOR_GREEN | зеленый
COLOR_YELLOW | желтый
COLOR_BLUE | синий
COLOR_MAGENTA | пурпурный
COLOR_CYAN | голубой
COLOR_WHITE | белый
- 128 -
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.
* int attroff(attr)
int wattroff(win, attr)
int attron(attr)
int wattron(win, attr)
Включают или отключают указанный атрибут attr, не влияя на другие
атрибуты в окне (stdscr или win).
* int attrset(attr)
int wattrset(win, attr)
Установка атрибута в attr в stdscr или win.
- 129 -
* int standout()
int standend()
int wstandout(win)
int wstandend(win)
Включают атрибут наиболее яркого режима для окна (stdscr или
win).
* chtype getattrs(win)
Выдает текущие атрибуты для окна win.
* bool has_colors()
Возвращает TRUE, если терминал имеет цвета. Перед тем, как
использовать цвета, проверьте терминал has_colors()-ом, а перед этим
проинициализируйте цвета start_color()-ом.
* bool can_change_color()
TRUE, если терминал может переопределять цвета.
* int start_color()
Цветовая инициализация. Эта функция должна быть вызвана перед
использованием цветов!
* int init_pair(pair, fg, bg)
Если вы используете цвета в атрибутах окна, то сначала вы должны
определить цветовую пару через 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));
- 130 -
Или соединим цветовые пары с другими атрибутами, например:
wattr(win,A_BOLD|COLOR_PAIR(1));
wattr(win1,A_STANDOUT|COLOR_PAIR(1));
Первый вызов установит цветовую пару и атрибут BOLD, второй -
подключит режим STANDOUT, и вы получите светлый красный на синем
экране.
* int pair_content(pair, f, b)
Вернет цвета переднего и заднего плана из pair.
* int init_color(color, r, g, b)
Изменит цветовые компоненты r, g и b для color. r, g и b
находятся в диапазоне от 1 до COLORS -1.
* int color_content(color, r, g, b)
Получение компонентов r, g и b для color.
Как комбинировать атрибуты и цвета? Некоторые терминалы, как
консоли в Linux-е, имеют цвета, а некоторые - нет (xterm, vs100 и
т.д.). Следующий код решит эту проблему:
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{
- 131 -
/* Ох, нет цвета (может быть vt100 или xterm). Ладно, будем
* пользоваться вместо этого черно-белыми атрибутами.
*/
wattrset(win1,A_REVERSE);
wattrset(win2,A_BOLD);
}
return;
}
Прежде всего, функция 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 и протестируйте его.
8.12. Координаты курсора и окна
* int move(y, x)
int wmove(win, y, x)
Движение курсора stdscr или win. Для функций ввода/вывода
определяются дополнительные макросы, передвигающие курсор перед
вызовом данных функций.
* int curs_set(bf)
Переключает видимость/невидимость курсора, если терминал имеет
такую возможность.
* void getyx(win, y, x)
Возвращает координаты курсора. Замечание: это макрос.
- 132 -
* void getparyx(win, y, x)
Если win - подокно, getparyx(...) сохранит координаты окна
относительно родительского окна. В противном случае y и x установятся
в -1. (Пока не реализована.)
* void getbegyx(win, y, x)
void getmaxyx(win, y, x)
int getmaxx(win)
int getmaxy(win)
Сохранит начальные и размерные координаты для win в y и x.
* int getsyx(int y, int x)
int setsyx(int y, int x)
Сохранит виртуальный курсор экрана в y и x или установит этот
курсор. При y и x, равных -1, getsyx(...) установит leaveok.
8.13. Прокрутка
* int scrollok(win, bf)
Если TRUE, текст в окне win будет прокручен вверх на одну линию,
когда курсор находится в правом нижнем углу и напечатан символ.Если
FALSE, то курсор остается на прежней позиции.
При включенном scrollok(...) содержимое окна может быть
прокручено при помощи нижеследующих функций. (Замечание: оно будет
прокручено и в случае, если вы напечатаете новую линию, находясь на
последней линии окна, поэтому будьте осторожны со scrollok(...).)
* int scroll(win)
Эта функция прокрутит окно (и строки в структуре данных) на одну
линию вверх.
* int scrl(n)
int wscrl(win, n)
Эти функции прокрутят окно stdscr или win вверх или вниз, в
зависимости от целого n. Если n положительное, произойдет прокрутка
окна на n линий вверх, если n отрицательное - на n линий вниз.
- 133 -
* int setscrreg(t, b)
int wsetscrreg(win, t, b)
Устанавливают программную область прокрутки.
Следующий код объяснит, как прокручивать текст на экране. Смотри
также type.c в директории примеров.
Мы хотим прокрутить текст в окне, имеющем 18 линий и 66 колонок.
S[] - это массив символов с текстом. Max_s является номером последней
линии в S[]. Clear_line напечатает пробелы с текущей позиции курсора
до конца линии, используя текущие атрибуты окна (не A_NORMAL, как это
делает clrtoeol). Beg - это последняя линия из s[], изображенная на
данный момент на экране. Scroll - это перечень того, что должна
сделать функция, показать NEXT или PREVious (следующую или предыдущую)
линию текста.
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);
- 134 -
/* устанавливаем новую строку в последней линии */
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. Заполнители
* WINDOW *newpad(nlines, ncols)
* WINDOW *subpad(orig, nlines, ncols, begy, begx)
* int prefresh(pad, pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol)
* int pnoutrefresh(pad, pminrow, pmincol, sminrow, smincol, smaxrow,
smaxcol)
* int pechochar(pad, ch)
8.15. Мягкие метки (Soft-labels)
* int slk_init(int fmt)
* int slk_set(int labnum, char *label, int fmt)
- 135 -
* int slk_refresh()
* int slk_noutrefresh()
* char *slk_label(int labnum)
* int slk_clear()
* int slk_restore()
* int slk_touch()
* int slk_attron(chtype attr)
int slk_attrset(chtype attr)
int slk_attroff(chtype attr)
Эти функции соответствуют функциям attron(attr), attrset(attr) и
attroff(attr). Пока не реализованы.
8.16. Разное
* int beep()
* int flash()
* char *unctrl(chtype c)
* char *keyname(int c)
* int filter()
(Пока не реализована.)
* void use_env(bf)
* int putwin(WINDOW *win, FILE *filep)
(Пока не реализована.)
* WINDOW *getwin(FILE *filep)
(Пока не реализована.)
- 136 -
* int delay_output(int ms)
* int flushinp
8.17. Низкоуровневый доступ
* int def_prog_mode()
* int def_shell_mode()
* int reset_prog_mode()
* int reset_shell_mode()
* int resetty()
* int savetty()
* int ripoffline(int line, int (*init)(WINDOW *, int))
* int napms(int ms)
8.18. Дамп экрана
* int scr_dump(char *filename)
(Пока не реализована.)
* int scr_restore(char *filename)
(Пока не реализована.)
* int scr_init(char *filename)
(Пока не реализована.)
* int scr_set(char *filename)
(Пока не реализована.)
- 137 -
8.19. Эмуляция termcap
* int tgetent(char *bp, char *name)
* int tgetflag(char id[2])
* int tgetnum(char id[2])
* char tgetstr(char id[2], char **area)
* char tgoto(char *cap, int col, int row)
* int tputs(char *str, int affcnt, int (*putc)())
8.20. Функции terminfo
* int setupterm(char *term, int fildes, int *errret)
* int setterm(char *term)
* int set_curterm(TERMINAL *nterm)
* int del_curterm(TERMINAL *oterm)
* int restartterm(char *term, int fildes, int *errret)
(Пока не реализована.)
* char *tparm(char *str, p1, p2, p3, p4, p5, p6, p7, p8, p9)
p1 - p9 long int.
* int truts(char *str, int affcnt, int (*putc)(char))
* int putp(char *str)
* int vidputs(chtype attr, int (*putc)(char))
* int vidsttr(chtype attr)
* int mvcur(int oldrow, int oldcol, int newrow, int newcol)
- 138 -
* int tigetflag(char *capname)
* int tigetnum(char *capname)
* int tigetstr(char *capname)
8.21. Функции отладки
* void _init_trace()
* void _tracef(char *, ...)
* char *_traceattr(mode)
* void traceon()
* void traceoff()
8.22. Свойства (capabilities) terminfo
8.22.1. Логические свойства
Переменная Имя Внутр. Описание
свойства код
auto_left_margin bw bw Cub1 переносит с колонки 0 в
последнюю колонку
auto_right_margin am am Границы терминала
устанавливаются автоматически
back_color_erase bce ut Экран очищается с цветом фона
can_change ccc cc Терминал может переопределять
существующие цвета
ceol_standout_glitch xhp xs выделение не удаляется
перезаписью (hp)
col_addr_glith xhpa YA Только положительное движение
для свойств hpa/mhpa
cpi_changes_res cpix YF Изменение размеров символа
изменяет разрешение
- 139 -
cr_cancels_micro_mode crxm YB Использование cr отключает режим
micro
eat_newline_glitch xenl xn Новая линия игнорируется после
80 столбцов (Concept)
erase_overstrike eo eo Можно забить пробелом
generic_type gn gn Общий тип линии (такой как
выделенная или
коммутируемая)
hard_copy hc hc Терминал твердой копии
hard_cursor chts HC Курсор аппаратный
has_meta_key km km Имеет метаклавишу ($shift, sets
parity bit$)
has_print_wheel daisy YC требуется вмешательство
оператора, чтобы изменить
символьную таблицу притера
has_status_line hs hs Имеет дополнительную "линию
статуса"
hue_lightness_saturation hls hl Терминал использует только
цветовую нотацию HLS (Tektronix)
insert_null_glitch in in Режим вставки распознает нули
lpi_changes_res lpix YG Изменение толщины линии изменяет
разрешение
memory_above da da Сохранение содержимого дисплея
над экраном
memory_below db db Сохранение содержимого дисплея
под экраном
move_insert_mode mir mi Безопасность передвижения в
режиме вставки
move_standout_mode msgr ms Безопасность передвижения в
режиме выделения
needs_xon_xoff nxon nx Дозаполнение не будет работать,
требуется xon/xoff
no_esc_ctl_c xsb xb $Beehive$ (f1=escape, f2=Ctrl-C)
non_rev_rmcup nrrmc NR smcup не оборачивает rmcup
no_pad_char npc NP Символ заполнения не существует
non_dest_scroll_region ndscr ND Область прокрутки ненарушаема
over_strike os os Терминал забивает символы
prtr_silent mc5i 5i Принтер не создает эха на экране
- 140 -
row_addr_glitch xvpa YD Только положительное движение
для свойств vhp\mvpa
semi_auto_right_margin sam YE Печать в последнем столбце
вызывает cr
status_line_esc_ok eslok es Escape может быть использован
на линии статуса
dest_tabs_magic_smso xt xt Нарушена табуляция, magic so
char (Teleray 1061)
tilde_glitch hz hz Невозможно напечатать тильды
transparent_underline ul ul Забой подчеркивания
xon_xoff xon xo Терминал использует команды
xon/xoff
8.22.2. Числа
Переменная Имя Внутр. Описание
свойства код
bit_image_entwining bitwin Yo Не документировано в SYSV
buffer_capacity bufsz Ya Буфер печати
columns cols co Число колонок в линии
dot_vert_spacing spinv Yb Высота позиции, в pin-ах на
дюйм
dot_horz_spacing spinh Yc Ширина позиции, в точках на дюйм
init_tabs it it Табуляторы изначально каждые
# позиций
label_height lh lh Линий в каждой метке
label_width lw lw Колонок в каждой метке
lines lines li Число линий на экране или
странице
lines_of_memory lm lm Число линий в памяти, 0 означает
переменное
magic_cookie_glitch xmc sg Число пробелов слева от smso
или rmso
max_colors colors Co Максимальное количество цветов
на экране
max_micro_address maddr Yd Максимальное значение в
micro_..._adress
max_micro_jump mjump Ye Максимальное значение в
- 141 -
parm_..._micro
max_pairs pairs pa Максимальное число цветовых
пар на экране
micro_col_size mcs Yf Размер шага символа в режиме
micro
micro_line_size mls Yg Размер шага линии в режиме micro
no_color_video ncv NC Видеоатрибуты, которые нельзя
использовать с цветами
number_of_pins npins Yh Число pin-ов на головке принтера
num_labels nlab Nl Число меток на экране
output_res_char orc Yi Горизонтальное разрешение, в
единицах на линию
output_res_line orl Yj Вертикальное разрешение, в
единицах на линию
output_res_horz_inch orhi Yk Горизонтальное разрешение, в
единицах на дюйм
output_res_vert_inch orvi Yl Вертикальное разрешение, в
единицах на дюйм
padding_baud_rate pb pb Нижняя граница, когда требуется
cr/nl заполнение
virtual_terminal vt vt Номер виртуального терминала
(UNIX)
width_status_line wsl ms Число колонок в линии статуса
(Следующие числовые свойства присутствуют в структуре term SYSV, но не
задокументированы в man page. Комментарии взяты из заголовка этой
структуры.)
bit_image_type bitype Yp Тип устройства двоичного образа
buttons btns BT Количество кнопок мыши
max_attributes ma ma Максимальное число атрибутов,
которое можно установить для
терминала одновременно
maximum_windows wnum MW Максимальное число возможных
окон
print_rate cps Ym Скорость печати, в символах в
секунду
wide_char_size widcs Yn Размер шага символа в режиме
- 142 -
двойной ширины
8.22.3. Строки
Переменная Имя Внутр. Описание
свойства код
acs_chars acsc ac $Graphics charset pairs$ -
def=vt100
alt_scancode_esc scesa S8 Альтернативный esc для эмуляции
$scancode$ (по умолчанию vt100)
back_tab cbt bt Возврат табулятора (P)
bell bel bl Слышимый сигнал (звонок) (P)
bit_image_repeat birep Xy Повторение двоичного образа
ячейки #1 #2 раз (использует
tparm)
bit_image_newline binel Zz Передвижение на следующую линию
двоичного образа (использует
tparm)
bit_image_carriage_return bicr Yv Передвижение на начало линии
двоичного образа (использует
tparm)
carriage_return cr cr Возврат каретки
change_char_pitch cpi ZA Изменение # символов на дюйм
change_line_pitch lpi ZB Изменение # линий на дюйм
change_res_horz chr ZC Изменение горизонтального
разрешения
change_res_vert cvr ZD Изменение вертикального
разрешения
change_scroll_region csr cs Изменение до #1 линий через #2
(vt100) (PG)
char_padding rmp rP Как ip, только когда в режиме
вставки
char_set_names csnm Zy Список наименований символьной
таблицы
clear_all_tabs tbc ct Очистка всех позиций табуляции
(P)
clear_margins mgc MC Очистка всех границ (верхней,
нижней и сторон)
clear_screen clear cl Очистка экрана и курсор home
- 143 -
(P*)
clr_bol el1 cb Очистка до начала линии
clr_eol el ce Очистка до конца линии (P)
clr_eos ed cd Очистка до конца дисплея (P*)
code_set_init csin ci Инициализация последовательности
для сложных таблиц кодов
color_names colornm Yw Дать наименование для цвета #1
column_address hpa ch Установка колонки курсора (PG)
command_character cmdch CC cmd символ терминально
устанавливаем в прототипе
cursor_address cup cm Показ движения курсора линия #1
колонка #2 (PG)
cursor_down cud1 do Вниз на одну линию
cursor_home home ho Курсор - в начало (если не
указана позиция курсора)
cursor_invisible civis vi Сделать курсор невидимым
cursor_left cub1 le Передвижение курсора на одну
позицию влево
cursor_mem_address mrcup CM Запоминание соответствующей
адресации курсора
cursor_normal cnorm ve Придать курсору нормальную
видимость (undo vs/vi)
cursor_right cuf1 nd Ненарушаемое пространство
(курсор вправо)
cursor_to_ll ll ll Последняя линия, первая колонка
(если не указана позиция
курсора)
cursor_up cuu1 up Пересылка на верхний уровень
(курсор вверх)
cursor_visible cvvis vs Сделать курсор сильновидимым
define_bit_image_region defbi Yx Определение прямоугольной
области двоичного образа
(использует tparm)
define_char defc ZE Определение символов в
символьной таблице
delete_character dch1 dc Удаление символа (P*) delete_line dl1 dl Удаление линии (P*)
device_type devt dv Индикация поддержки
языка/таблицы кодов
- 144 -
dis_status_line dsl ds Невозможна линия статуса
display_pc_char dispc S1 Отображение символа ПК
down_half_line hd hd Пол-линии вниз (вперед 1/2
перевода строки)
ena_acs enacs eA Включена альтернативная
символьная таблица
end_bit_image_region endbi Yy Конец области двоичного образа
(использует tparm)
enter_alt_charset_mode smacs as Начало альтернативная символьной
таблицы (P)
enter_am_mode smam SA Включение автоматических границ
enter_blink_mode blink mb Включение мигания
enter_bold_mode bold md Включения суперяркого режима
enter_ca_mode smcup ti Строка начала программ,
использующих позицию курсора
enter_delete_mode smdc dm Режим удаления (enter)
enter_dim_mode dim mh Включение полуяркого режима
enter_doublewide_mode swidm ZF Возможен режим двойной ширины
enter_draft_quality sdrfq ZG Установка качества печати
enter_insert_mode smir im Режим вставки (enter)
enter_italics_mode sitm ZH Возможен режим курсива
enter_leftward_mode slm ZI Возможно движение каретки
влево
enter_micro_mode smicm ZJ Разблокирование возможностей
микропередвижения
enter_near_letter_quality snlq ZK Установка печати NLQ
enter_normal_quality snrmq ZL Установка нормального качества
печати
enter_pc_charset_mode smpch S2 Ввод режима показа символов ПК
enter_protected_mode prot mp Включение защищенного режима
enter_reverse_mode rev mr Включение обратного видеорежима
enter_scancode_mode smsc S4 Ввод режима scancode ПК
enter_secure_mode invis mk Включение пробельного режима
(символы невидимы)
enter_shadow_mode sshm ZM Возможен режим оттененной печати
enter_standout_mode smso so Начало режима выделения
enter_subscript_mode ssubm ZN Возможна печать индекса
enter_superscript_mode ssupm ZO Возможна печать верхнего индекса
- 145 -
enter_underline_mode smul us Начало режима подчеркивания
enter_upward_mode sum ZP Возможно движение каретки вверх
enter_xon_mode smxon SX Включение подтверждения связи
xon/xoff
erase_chars ech ec Очистка #1 символов (PG)
exit_alt_charset_mode rmacs ae Конец альтернативной символьной
таблицы (P)
exit_am_mode rmam RA Отключение автоматических границ
exit_attribute_mode sgr0 me Отключение всех атрибутов
exit_ca_mode rmcup te Строка конца программ,
использующих позицию курсора
exit_delete_mode rmdc ed Конец режима удаления
exit_doublewide_mode rwidm ZQ Невозможна печать с двойной
шириной
exit_insert_mode rmir ei Конец режима вставки
exit_italics_mode ritm ZR Невозможна печать курсивом
exit_leftward_mode rlm ZS Возможно движение каретки
вправо (нормального режима)
exit_micro_mode rmicm ZT Заблокированы возможности
микропередвижения
exit_pc_charset_mode rmpch S3 Невозможен показ символов ПК
exit_scancode_mode rmsc S5 Невозможен режим просмотра ПК
exit_shadow_mode rshm ZU Невозможна оттененная печать
exit_standout_mode rmso se Конец режима выделения
exit_subscript_mode rsubm ZV Невозможна подстрочная печать
exit_superscript_mode rsupm ZW Невозможна надстрочная печать
exit_underline_mode rmul ue Конец режима подчеркивания exit_upward_mode rum ZX Возможно движение каретки вниз
exit_xon_mode rmxon RX Отключение подтверждения связи
xon/xoff
flash_screen flash vb Видимый звонок (невозможно
передвижение курсора)
form_feed ff ff Терминал твердой копии извергает
страницы (P*)
form_status_line fsl fs Возврат с линии статуса
init_1string is1 i1 Строка инициализации терминала
init_2string is2 i2 Строка инициализации терминала
init_3string is3 i3 Строка инициализации терминала
init_file if if Название содержащего файла
- 146 -
init_prog iprog iP Путь программы для инициализации
initialize_color initc Ic Инициализация определения цвета
initialize_pair initp Ip Инициализация цветовой пары
insert_character ich1 ic Символ вставки (P)
insert_line il1 al Добавление новой чистой линии
(P*)
insert_padding ip ip Забивка после вставленного
символа (p*)
key_a1 ka1 K1 Верхний левый keypad-а
key_a3 ka3 K3 Верхний правый keypad-а
key_b2 ka2 K2 Центр keypad-а
key_backspace kbs kb Послан клавишей backspace
key_beg kbeg 1 Клавиша начала
key_btab kcbt kB Клавиша назад табуляции
key_c1 kc1 K4 Нижний левый keypad-а
key_c3 kc3 K5 Нижний левый keypad-а
key_cancel kcan 2 Клавиша cancel
key_catab ktbc ka Послан клавишей очистки всей
табулиции
key_clear kclr kC Послан клавишей очистки экрана
или клавишей очистки (стирания)
key_close kclo 3 Клавиша close
key_command kcmd 4 Командная клавиша
key_copy kcpy 5 Клавиша копирования
key_create kcrt 6 Клавиша создания
key_ctab kctab kt Послан клавишей очистки
табулиции
key_dc kdch1 kD Послан клавишей удаления символа
key_dl kdl1 kL Послан клавишей удаления линии
key_down kcud1 kd Послан клавишей нижнего
терминального массива
key_eic krmir kM Послан клавишей rmir или smir
в режиме вставки
key_end kend 7 Клавиша end
key_enter kent 8 Клавиша ввода/посылки
key_eol kel kE Послан клавишей очистки до конца
линии
key_eos ked kS Послан клавишей очистки до конца
- 147 -
экрана
key_exit kext 9 Клавиша выхода
key_f0 kf0 k0 Функциональная клавиша F00
key_f1 kf1 k1 Функциональная клавиша F01
key_f2 kf2 k2 Функциональная клавиша F02
key_f3 kf3 k3 Функциональная клавиша F03
key_f4 kf4 k4 Функциональная клавиша F04
key_f5 kf5 k5 Функциональная клавиша F05
key_f6 kf6 k6 Функциональная клавиша F06
key_f7 kf7 k7 Функциональная клавиша F07
key_f8 kf8 k8 Функциональная клавиша F08
key_f9 kf9 k9 Функциональная клавиша F09
key_f10 kf10 k; Функциональная клавиша F10
key_f11 kf11 F1 Функциональная клавиша F11
key_f12 kf12 F2 Функциональная клавиша F12
key_f13 kf13 F3 Функциональная клавиша F13
key_f14 kf14 F4 Функциональная клавиша F14
key_f15 kf15 F5 Функциональная клавиша F15
key_f16 kf16 F6 Функциональная клавиша F16
key_f17 kf17 F7 Функциональная клавиша F17
key_f18 kf18 F8 Функциональная клавиша F18
key_f19 kf19 F9 Функциональная клавиша F19
key_f20 kf20 FA функциональная клавиша F00
key_f21 kf21 FB функциональная клавиша F01
key_f22 kf22 FC функциональная клавиша F02
key_f23 kf23 FD функциональная клавиша F03
key_f24 kf24 FE функциональная клавиша F04
key_f25 kf25 FF функциональная клавиша F05
key_f26 kf26 FG функциональная клавиша F06
key_f27 kf27 FH функциональная клавиша F07
key_f28 kf28 FI функциональная клавиша F08
key_f29 kf29 FJ функциональная клавиша F09
key_f30 kf30 FK функциональная клавиша F10
key_f31 kf31 FL функциональная клавиша F11
key_f32 kf32 FM функциональная клавиша F12
key_f33 kf33 FN функциональная клавиша F13
key_f34 kf34 FO функциональная клавиша F14
key_f35 kf35 FP функциональная клавиша F15
- 148 -
key_f36 kf36 FQ функциональная клавиша F16
key_f37 kf37 FR функциональная клавиша F17
key_f38 kf38 FS функциональная клавиша F18
key_f39 kf39 FT функциональная клавиша F19
key_f40 kf40 FU функциональная клавиша F00
key_f41 kf41 FV функциональная клавиша F01
key_f42 kf42 FW функциональная клавиша F02
key_f43 kf43 FX функциональная клавиша F03
key_f44 kf44 FY функциональная клавиша F04
key_f45 kf45 FZ функциональная клавиша F05
key_f46 kf46 Fa функциональная клавиша F06
key_f47 kf47 Fb функциональная клавиша F07
key_f48 kf48 Fc функциональная клавиша F08
key_f49 kf49 Fd функциональная клавиша F09
key_f50 kf50 Fe функциональная клавиша F10
key_f51 kf51 Ff функциональная клавиша F11
key_f52 kf52 Fg функциональная клавиша F12
key_f53 kf53 Fh функциональная клавиша F13
key_f54 kf54 Fi функциональная клавиша F14
key_f55 kf55 Fj функциональная клавиша F15
key_f56 kf56 Fk функциональная клавиша F16
key_f57 kf57 Fl функциональная клавиша F17
key_f58 kf58 Fm функциональная клавиша F18
key_f59 kf59 Fn функциональная клавиша F19
key_f60 kf60 Fo функциональная клавиша F10
key_f61 kf61 Fp функциональная клавиша F11
key_f62 kf62 Fq функциональная клавиша F12
key_f63 kf63 Fr функциональная клавиша F13
key_find kfnd 0 клавиша поиска
key_help khlp %1 клавиша помощи
key_home khome kh послан клавишей home
key_ic kich1 kI послан клавишей символ вставки/
ввод режима вставки
key_il kil1 kA послан клавишей вставки линии
key_left kcub1 kl послан клавишей левого
терминального массива
key_ll kll kH послан клавишей home-down
key_mark kmrk %2 клавиша пометки
- 149 -
key_message kmsg %3 клавиша сообщения
key_move kmov %4 клавиша перемещения
key_next knxt %5 клавиша следующего
key_npage knp kN послан клавишей следующей
страницы
key_open kopn %6 клавиша открывания
key_options kopt %7 клавиша опций
key_ppage kpp kP послан клавишей предыдущей
страницы
key_previous kprv %8 клавиша предыдущего
key_print kprt %9 клавиша печати
key_redo krdo %0 клавиша redo
key_reference kref &1 клавиша ссылки
key_refresh krfr &2 клавиша обновления
key_replace krpl &3 клавиша перестановки
key_restart krst &4 клавиша перезапуска
key_resume kres &5 клавиша возобновления работы
key_right kcuf1 kr послан клавишей стрелки враво
key_save ksav &6 клавиша сохранения
key_sbeg kBEG &9 клавиша начала в верхнем
регистре
key_scancel kCAN &0 клавиша cancel в верхнем
регистре
key_scommand kCMD *1 командная клавиша в верхнем
регистре
key_scopy kCPY *2 клавиша копирования в верхнем
регистре
key_screate kCRT *3 клавиша создания в верхнем
регистре key_sdc kDC *4 клавиша удаления символа
в верхнем регистре
key_sdl kDL *5 клавиша удаления линии
в верхнем регистре
key_select kslt *6 клавиша выделения
key_send kEND *7 клавиша конца в верхнем
регистре
key_seol kEOL *8 клавиша конца строки в верхнем
регистре
key_sexit kEXT *9 клавиша выхода в верхнем
- 150 -
регистре
key_sf kind kF послан клавишей прокрутки
прямо/вниз
key_sfind kFND *0 клавиша поиска в верхнем
регистре
key_shelp kHLP #1 клавиша помощи в верхнем
регистре
key_shome kHOM #2 клавиша начала в верхнем
регистре
key_sic kIC #3 клавиша вставки символа в
верхнем регистре
key_sleft kLFT #4 клавиша влево в верхнем регистре
key_smessage kMSG %a клавиша сообщения в верхнем
регистре
key_smove kMOV %b клавиша перемещения в верхнем
регистре
key_snext kNXT %c клавиша следующего в верхнем
регистре
key_soptions kOPT %d клавиша опций в верхнем регистре
key_sprevious kPRV %e клавиша предыдущего в верхнем
регистре
key_sprint kPRT %f клавиша печати в верхнем
регистре
key_sr kri kR послан клавишей прокрутки
назад/вверх
key_sredo kRDO %g клавиша redo в верхнем регистре
key_sreplace kRPL %h клавиша перестановки в верхнем
регистре
key_sright kRIT %i клавиша вправо в верхнем
регистре
key_sresume kRES %j клавиша возобновления работы в
верхнем регистре
key_ssave kSAV !1 клавиша сохранения в верхнем
регистре
key_ssuspend kSPD !2 клавиша приостановки в верхнем
регистре
key_stab khts kT послан клавишей установки
табуляции
- 151 -
key_sundo kUND !3 клавиша undo в верхнем регистре
key_suspend kspd &7 клавиша приостановки
key_undo kund &8 клавиша undo
key_up kcuu1 ku послан клавишей вверх терминала
keypad_local rmkx ke выход из режима $"keypad
transmit"$
keypad_xmit smkx ks установка режима $"keypad
transmit"$ терминала
lab_f0 lf0 l0 метки на функциональную клавишу
f0, если не f0
lab_f1 lf1 l1 метки на функциональную клавишу
f1, если не f1
lab_f2 lf2 l2 метки на функциональную клавишу
f2, если не f2
lab_f3 lf3 l3 метки на функциональную клавишу
f3, если не f3
lab_f4 lf4 l4 метки на функциональную клавишу
f4, если не f4
lab_f5 lf5 l5 метки на функциональную клавишу
f5, если не f5
lab_f6 lf6 l6 метки на функциональную клавишу
f6, если не f6
lab_f7 lf7 l7 метки на функциональную клавишу
f7, если не f7
lab_f8 lf8 l8 метки на функциональную клавишу
f8, если не f8
lab_f9 lf9 l9 метки на функциональную клавишу
f9, если не f9
lab_f10 lf10 la метки на функциональную клавишу
f10, если не f10
label_on smln LO подключение мягких меток
label_off rmln LF отключение мягких меток
meta_off rmm mo отключение метарежима
meta_on smm mm включение метарежима (8-ой бит)
micro_column_address mhpa ZY как column_address для
микрорегулировки
micro_down mcud1 ZZ как cursor_down для
микрорегулировки
- 152 -
micro_left mcub1 Za как cursor_left для
микрорегулировки
micro_right mcuf1 Zb как cursor_right для
микрорегулировки
micro_row_address mvpa Zc как row_address для
микрорегулировки
micro_up mcuu1 Zd как cursor_up для
микрорегулировки newline nel nw новая линия (действует, как lf
после cr)
order_of_pins porder Ze Соединяет программные $buts$
с pin-ами головки принтера
orig_colors oc oc Сброс всех цветовых пар
orig_pair op op Установка цветовой пары по
умолчанию, как в первоначальном
варианте
pad_char pad pc Символ-заполнитель (скорее всего
null)
parm_dch dch DC Удаление #1 символов (PG*)
parm_delete_line dl DL Удаление #1 линий (PG*)
parm_down_cursor cud DO Движение курсора вниз на #1
линий (PG*)
parm_down_micro mcud Zf Как cud для микрорегулирования
parm_ich ich IC Вставка #1 пробельных символов
(PG*)
parm_index indn SF Прокрутка вперед #1 линий (PG)
parm_insert_line il AL Добавление #1 новых чистых линий
(PG*)
parm_left_cursor cub LE Перемещение курсора влево на #1
позиций (PG)
parm_left_micro mcub Zg Как cul для микрорегулирования
parm_right_cursor cuf RI Перемещение курсора вправо на #1
позиций (PG*)
parm_right_micro mcuf Zh Как cur для микрорегулирования
parm_rindex rin SR Прокрутка назад на #1 линий
(PG*)
parm_upcursor cuu UP Перемещение курсора вверх на #1
линий (PG*)
parm_upmicro mcuu Zi Как cuu для микрорегулирования
- 153 -
pkey_key pfkey pk Программная функциональная
клавиша #1 для печати строки #2
pkey_local pfloc pl Программная функциональная
клавиша #1 для выполнения
строки #2
pkey_xmit pfx px Программная функциональная
клавиша #1 для xmit строки #2
pkey_plab pfxl xl Программная клавиша #1 для xmit
#2 и показа #3
plab_norm pln pn Программная метка #1 для показа
строки #2
print_screen mc0 ps Печать содержимого экрана
prtr_non mc5p pO Включение принтера для #1 байт
prtr_off mc4 pf Выключение принтера
prtr_on mc5 po Включение принтера
repeat_char rep rp Повторение символа #1 #2 раз
(PG*)
req_for_input rfi RF Запрос на ввод
reset_1string rs1 r1 Перезапуск терминала в
нормальный режим
reset_2string rs2 r2 Перезапуск терминала в
нормальный режим
reset_3string rs3 r3 Перезапуск терминала в
нормальный режим
reset_file rf rf Имя файла, содержащего строку
перезапуска
restore_cursor rc rc Установка курсора на позицию
последнего sc
row_address vpa cv Абсолютная вертикальная позиция
(установка линии) (PG)
save_cursor sc sc Сохранение позиции курсора (P)
scancode_escape scesc S7 Escape для эмуляции scancode
scroll_forward ind sf Прокрутка текста вверх (P)
scroll_reverse ri sr Прокрутка текста вниз (P)
select_char_set scs Zj Выбор символьной таблицы
set0_des_seq s0ds s0 $Shift to codeset 0 (EUC set 0,
ASCII)$
set1_des_seq s1ds s1 $Shift to codeset 1$
- 154 -
set2_des_seq s2ds s2 $Shift to codeset 2$
set3_des_seq s3ds s3 $Shift to codeset 3$
set_a_background setab AB Установка цвета заднего плана,
используя ANSI escape
set_a_foreground setaf AF Установка цвета переднего плана,
используя ANSI escape
set_attributes sgr sa Установка видеоатрибутов (PG9)
set_background setb Sb Установка текущего цвета заднего
плана
set_bottom_margin smgb Zk Объявление текущей линии нижней
границей
set_bottom_margin_parm smgbp Zl Линии на расстоянии #1 или #2 от
нижней границы объявляются
нижней границей
set_color_band setcolor Yz Установить текущим цвет #1 set_color_pair scp sp Установка текущей цветовой пары
set_foreground setf Sf Установка текущего цвета
переднего плана
set_left_margin smgl ML Установка текущей колонки как
левой границы
set_left_margin_parm smglp Zm Установка левой (правой) границы
на #1 (#2)
set_lr_margin smglr ML Установка левой и правой границ
set_page_lingth slines YZ Установка длины страницы в #1
линий (используйте tparm)
set_right_margin smgr MR Установка текущей колонки как
правой границы
set_right_margin_parm smgrp Zn Установка колонки #1 как правой
границы
set_tab hts st Установка табуляций на всех
линиях в текущей колонке
set_tb_margin smgtb MT Установка верхней и нижней
границ
set_top_margin smgt Zo Установка текущей линии как
верхней границы
set_top_margin_parm smgtp Zp Установка линии #1 как верхней
границы
set_window wind wi Текущее окно - это линии #1-#2,
колонки #3-#4
- 155 -
start_bit_image sbim Zq Начало печати bit image
start_char_set_def scsd Zr Начало определения символьной
таблицы
stop_bit_image rbim Zs Конец печати bit image
stop_char_set_def rcsd Zt Конец определения символьной
таблицы
subscript_characters subcs Zu Список подстрочных символов
superscript_characters supcs Zv Список надстрочных символов
tab ht ta Табуляция на 8 следующих позиций
these_cause_cr docr Zw Данные символы вызывают CR
to_status_line tsl ts Переход на линию статуса, первую
колонку
underline_char uc uc Подчеркнуть символ и встать
после него
up_half_line hu hu Передвижение на 1/2 содержимого
линии
xoff_character xoffc XF символ XOFF
xon_character xonc XN символ XON
(Следующие числовые свойства присутствуют в структуре 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 секунды
- 156 -
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 Следующий символ не двигается 8.23. Обзор функций [n]curses
В следующем тексте вы найдете обзор различных пакетов (n)curses.
Первая колонка содержит bsd-curses (в версии 2.1.0 и в SunOS 4.x), во
второй колонке - sysv-curses (в SunOS 5.4 / Solaris 2), наконец,
третья колонка - это (n)curses. В четвертой колонке содержится ссылка
на страницу данного руководства, где описывается эта функция.
x - пакет содержит эту функцию
n - функция пока не реализована
Функция BSD SYSV Nc. Страница
_init_trace() x 118
_traceattr(mode) x 118
_tracef(char *, ...) x 118
addbytes(...) x
addch(ch) x x x 96
addchnstr(...) x x 96
addchstr(chstr) x x 96
addnstr(...) x x 96
- 157 -
addnwstr(...) x
addstr(str) x x x 96
addwch(...) x
addwchnstr(...) x
addwchstr(...) x
addwstr(...) x
adjcurspos() x
attroff(attr) x x 110
attron(attr) x x 110
attrset(attr) x x 110
baudrate() x x x 103
beep() x x 115
bkgd(ch) x x 100
bkgdset(ch) x x 98
border(...) x x 98
box(...) x x x 98
can_change_color() x x 110
cbreak() x x x 102
clear() x x x 107
clearok(...) x x x 102
clrtobot() x x x 107
clrtoeol() x x x 107
color_content(...) x x 111
copywin(...) x x 96
crmode() x x x 102
curs_set(bf) x x 112
curserr() x
def_prog_mode() x x 116
def_shell_mode() x x 116
del_curterm(...) x x 117
delay_output(ms) x x 115
delch() x x x 98
deleteln() x x x 98
delscreen(...) x x,n 93
delwin(win) x x x 95
derwin(...) x x 95
doupdate() x x 107
drainio(int) x
- 158 -
dupwin(win) x x 95
echo() x x x 103
echochar(ch) x x 96
echowchar(ch) x
endwin() x x x 93
erase() x x x 106
erasechar() x x x 104
filter() x x 115
flash() x x 115
flushinp() x x 115
flushok(...) x
garbagedlines(...) x
garbagedwin(win) x
getattrs(win) x x 110
getbegyx(...) x x 112
getbkgd(win) x
getbmap() x
getcap(str) x
getch() x x x 100
getmaxx(win) x x 112
getmaxy(win) x x 112
getmaxyx(...) x x 112
getmouse() x
getnwstr(...) x
getparyx(...) x x 112
getstr(str) x x x 100
getsyx(...) x x 112
gettmode() x x
getwch(...) x
getwin(...) x
getwin(FILE *) x x,n 115
getwstr(...) x
getyx(...) x x x 112
halfdelay(t) x x 103
has_colors() x x 110
has_ic() x x,n 104
has_il() x x,n 104
hline(...) x x 98
- 159 -
idcok(...) x x,n 101
idlok(...) x x x 101
immedok(...) x x 101
inch() x x x 100
inchnstr(...) x x,n 101
inchstr(...) x x,n 101
init_color(...) x x 111
init_pair(...) x x 110
initscr() x x x 92
innstr(...) x x,n 101
innwstr(...) x
insch(c) x x x 97
insdelln(n) x x 97
insertln() x x x 97
insnstr(...) x x 97
insstr(str) x x 97
instr(str) x x,n 101
inswch(...) x
inswstr(...) x
intrflush(...) x x 103
inwch(...) x
inwchnstr(...) x
inwchstr(...) x
inwchstr(...) x
inwstr(...) x
is_linetouched(...) x x 108
is_wintouched(win) x x 108
isendwin() x x 93
keyname(c) x x 115
keypad(...) x x 102
killchar() x x x 104
leaveok(...) x x x 102
longname() x x x 104
map_button(long) x
meta(...) x x 102
mouse_off(long) x
mouse_on(long) x
mouse_set(long) x
- 160 -
move(...) x x x 112
movenextch() x
moveprevch() x
mvaddbytes(...) x
mvaddch(...) x x x 96
mvaddchnstr(...) x x 96
mvaddchstr(...) x x 96
mvaddnstr(...) x x 96
mvaddnwstr(...) x
mvaddstr(...) x x x 96
mvaddwch(...) x
mvaddwchnstr(...) x
mvaddwchstr(...) x
mvaddwstr(...) x
mvcur(...) x x x 117
mvdelch(...) x x x 98
mvderwin(...) x x,n 95
mvgetch(...) x x x 100
mvgetnwstr(...) x
mvgetstr(...) x x x 100
mvgetwch(...) x
mvgetwstr(...) x
mvhline(...) x
mvinch(...) x x x 100
mvinchnstr(...) x x,n 101
mvinchstr(...) x x,n 101
mvinnstr(...) x x,n 101
mvinnwstr(...) x
mvinsch(...) x x x 97
mvinsnstr(...) x x 97
mvinsnwstr(...) x
mvinsstr(...) x x 97
mvinstr(...) x x,n 101
mvinswch(...) x
mvinswstr(...) x
mvinwch(...) x
mvinwchnstr(...) x
mvinwchstr(...) x
- 161 -
mvinwstr(...) x
mvprintw(...) x x x 97
mvscanw(...) x x x 101
mvvline(...) x
mvwaddbytes(...) x
mvwaddch(...) x x x 96
mvwaddchnstr(...) x x 96
mvwaddchstr(...) x x 96
mvwaddnstr(...) x x 96
mvwaddnwstr(...) x
mvwaddstr(...) x x x 96
mvwaddwch(...) x
mvwaddwchnstr(...) x
mvwaddwchstr(...) x
mvwaddwstr(...) x
mvwdelch(...) x x x 98
mvwgetch(...) x x x 100
mvwgetnwstr(...) x
mvwgetstr(...) x x x 100
mvwgetwch(...) x
mvwgetwstr(...) x
mvwhline(...) x
mvwin(...) x x x 95
mvwinch(...) x x x 100
mvwinchnstr(...) x x,n 101
mvwinchstr(...) x x,n 101
mvwinnstr(...) x x,n 101
mvwinnwstr(...) x
mvwinsch(...) x x x 97
mvwinsnstr(...) x x 97
mvwinsstr(...) x x 97
mvwinstr(...) x x,n 101
mvwinswch(...) x
mvwinswstr(...) x
mvwinwch(...) x
mvwinwchnstr(...) x
mvwinwchstr(...) x
mvwinwstr(...) x
- 162 -
mvwprintw(...) x x x 97
mvwscanw(...) x x x 101
mvwvline(...) x
napms(ms) x x 116
newkey(...) x
newpad(...) x x 114
newscreen(...) x
newterm(...) x x 92
newwin(...) x x x 93
nl() x x x 102
nocbreak() x x x 102
nocrmode() x x x 102
nodelay(...) x x 103
noecho() x x x 103
nonl() x x x 102
noqiflush() x x,n 103
noraw() x x x 103
notimeout(...) x x 103
overlay(...) x x x 95
overwrite(...) x x x 95
pair_content(...) x x 111
pechochar(...) x x 114
pechowchar(...) x
pnoutrefresh(...) x x 114
prefresh(...) x x 114
printw(...) x x x 97
putp(char *) x x 117
putwin(...) x x,n 115
qiflush() x x,n 103
raw() x x x 103
redrawwin(win) x x 108
refresh() x x x 107
request_mouse_pos() x
reset_prog_mode() x x 116
reset_shell_mode() x x 116
resetty() x x x 116
restartterm(...) x x,n 117
ripoffline(...) x x 116
- 163 -
savetty() x x x 116
scanw(...) x x x 101
scr_dump(char *) x x,n 116
scr_init(char *) x x,n 116
scr_restore(char *) x x,n 116
scr_set(char *) x x,n 116
scrl(n) x x 113
scroll(win) x x x 113
scrollok(...) x x x 112
set_curterm(...) x x 117
set_term(...) x x 93
setcurscreen(SCREEN *) x
setscrreg(...) x x 113
setsyx(...) x x 112
setterm(char *) x x x 117
setupterm(...) x x 117
slk_attroff(attr) x x,n 115
slk_attron(attr) x x,n 115
slk_attrset(attr) x x,n 115
slk_clear() x x 115
slk_init(fmt) x x 114
slk_label(labnum) x x 115
slk_noutrefresh() x x 114
slk_refresh() x x 114
slk_restore() x x 115
slk_set(...) x x 114
slk_touch() x x 115
standend() x x x 110
standout() x x x 110
start_color() x x 110
subpad(...) x x 114
subwin(...) x x x 95
syncok(...) x x,n 95
--------------------------------------------------------------
termattrs() x x,n 104
termname() x x,n 104
tgetent(...) x x 116
tgetflag(char [2]) x x 116
- 164 -
tgetnum(char [2]) x x 116
tgetstr(...) x x 117
tgoto(...) x x 117
tigetflag(...) x x 117
tigetnum(...) x x 117
tigetstr(...) x x 117
timeout(t) x x 103
touchline(...) x x x 108
touchwin(win) x x x 108
tparm(...) x x 117
tputs(...) x 117
traceoff() x x 118
traceon() x x 118
typeahead(fd) x x 103
unctrl(chtype c) x x 115
ungetch(ch) x x 100
ungetwch(c) x
untouchwin(win) x x 108
use_env(bf) x x 115
vidattr(...) x x 117
vidputs(...) x x 117
vidupdate(...) x
vline(...) x x 98
vwprintw(...) x x 97
vwscanw(...) x x 101
waddbytes(...) x
waddch(...) x x x 96
waddchnstr(...) x x 96
waddchstr(...) x x 96
waddnstr(...) x x 96
waddnwstr(...) x
waddstr(...) x x x 96
waddwch(...) x
waddwchnstr(...) x
waddwchstr(...) x
waddwstr(...) x
wadjcurspos(win) x
wattroff(...) x x 110
- 165 -
wattron(...) x x 110
wattrset(...) x x 110
wbkgd(...) x x 100
wbkgdset(...) x x 98
wborder(...) x x 98
wclear(win) x x x 107
wclrtobot(win) x x x 107
wclrtoeol(win) x x x 107
wcursyncup(win) x x,n 95
wdelch(win) x x x 98
wdeleteln(win) x x x 98
wechochar(...) x x 96
wechowchar(...) x
werase(win) x x x 106
wgetch(win) x x x 100
wgetnstr(...) x x 100
wgetnwstr(...) x
wgetstr(...) x x x 100
wgetwch(...) x
wgetwstr(...) x
whline() x
whline(...) x
whline(...) x x 98
winch(win) x x x 100
winchnstr(...) x x,n 101
winchstr(...) x x,n 101
winnstr(...) x x,n 101
winnwstr(...) x
winsch(...) x x x 97
winsdelln(...) x x x 97
winsertln(win) x x 97
winsnstr(...) x x 97
winsnwstr(...) x
winsstr(...) x x 97
winstr(...) x x,n 101
winswch(...) x
winswstr(...) x
winwch(...) x
- 166 -
winwchnstr(...) x
winwchstr(...) x
winwstr(...) x
wmouse_position(...) x
wmove(...) x x x 112
wmovenextch(win) x
wmoveprevch(win) x
wnoutrefresh(win) x x 107
wprintw(...) x x x 97
wredrawln(...) x x 108
wrefresh(win) x x x 107
wscanw(...) x x x 101
wscrl(...) x x 113
wsetscrreg(...) x x 113
wstandend(win) x x x 110
wstandout(win) x x x 110
wsyncdown(win) x x,n 95
wsyncup(win) x x,n 95
wtimeout(...) x x 103
wtouchln(...) x x 108
wvline() x
wvline(...) x
wvline(...) x x 98
To be continued...
9. Программирование портов ввода/вывода
Обычно ПК имеет как минимум 2 последовательных и 1 параллельный
интерфейс. Они являются специальными устройствами и отображаются
следующим образом.
* /dev/ttyS0 - /dev/ttySn
RS232 последовательные устройства 0 - n, где n зависит от вашего
аппаратного обеспечения.
* /dev/cua0 - /dev/cuan
RS232 последовательные устройства 0 - n, где n зависит от вашего
- 167 -
аппаратного обеспечения.
* /dev/lp0 - /dev/lpn
параллельные устройства 0 - n, где n зависит от вашего
аппаратного обеспечения.
* /dev/js0 - /dev/jsn
джойстики 0 - n, где 0<= n <=1.
Разница между /dev/ttyS* и /dev/cua* в способе вызова open().
/dev/cua* предполагают использование как устройств вывода, и, как
следствие, имеют другие установки по умолчанию относительно
/dev/ttyS*. /dev/ttyS* инициализируются для входящих и выходящих
сигналов. По умолчанию устройства являются управляющими устройствами
для процесса, их открывающего. Обычно ioctl() обрабатывает все эти
специальные устройства, однако POSIX предпочитает определение новых
функций для асинхронных терминалов. Эти функции жестко привязаны к
структуре termios. Оба варианта требуют подключения .
1) Метод ioctl:
TCSBRK, TCSBRKP, TCGETA (get attributes - получить атрибуты),
TCSETA (set attributes - установить атрибуты); Запросы управления
вводом/выводом терминала (terminal I/O control - TIOC): TIOCGSOFTCAR
($get soft carrier$), TIOCSSOFTCAR ($setsoftcarrier$), TIOCSCTTY (set
controlling tty - установка управления tty), TIOCMGET (get
modemlines), TIOCMSET, TIOCGSERIAL, TIOCSSERIAL, TIOCSERCONFIG,
TIOCSERGWILD, TIOCSERGSTRUCT, TIOCMBIS, TIOCMBIC, ...
2) Метод POSIX:
tcgetattr(), tcsetattr(), tcsendbreak(), tcdrain(), tcflush(),
tcflow(), tcgetpgrp(), tcsetpgrp() cfsetispeed(), fgetispeed(),
cfgetospeed()
3) Другие методы:
outb, inb для нестандартных устройств, например, для
использования принтерного порта не для принтера.
- 168 -
9.1. Программирование мыши
Мышь подключается как к последовательному порту, так и напрямую к
шине. Разные типы мышек посылают разные типы данных, что немного
усложняет программирование. Однако, Эндрю Хэйлет (Andrew Haylett) был
так добр, что поставил общий копирайт на свою программу selection, то
есть вы можете использовать его функции работы с мышью. В этом
руководстве вы найдете пре-релиз selection-1.8 с пометкой COPYRIGHT.
Правда, X11 уже предложили удобный мышечный API, поэтому программы
Эндрю следует использовать только для не-X11 приложений. Из пакета
selection вам потребуются только модули mouse.h и mouse.c.
Для получения событий от мыши вам необходимы ms_init() и
get_ms_event(). ms_init() требует следующих 10 аргументов.
1) int acceleration
Ускоряющий множитель. Если вы передвигаете курсор более, чем на
delta пикселов, то движение ускоряется в зависимости от этого
коэффициента.
2) int baud
bps - оценка скорости мыши (обычно 1200 bps)
3) int delta
Число пикселов, на которые сдвинется курсор, для активизации
ускорения (см. 1).
4) char *device
Имя устройства (например, /dev/mouse).
5) int toggle
Выключатель DTR, RTS или и DTR и RTS каналов мыши и модема при
инициализации (нормально - 0).
6) int sample
Разрешение мыши в dpi (обычно 100).
- 169 -
7) mouse_type mouse
Идентификатор присоединенной мыши, вроде P_MSC (Mouse Systems
Corp.).
8) int slack
Если равно -1, то курсор застопоривается на границах экрана; при
slack >=0 курсор выезжает с противоположной стороны, если он зашел за
пределы на slack пикселов.
9) int maxx
Разрешение текущего терминала по x. Символы шрифта по умолчанию
имеют ширину 10 пикселов, поэтому полный режим по x равен 10*80-1.
10) int maxy
Шрифт по умолчанию имеет символы высотой 12 пикселов, поэтому
полное разрешение экрана по y 12*25-1 пиксел.
get_ms_event() нуждается только в указателе на структуру
ms_event. Если get_ms_event() возвращает -1, то произошла ошибка. В
случае успеха возвращается 0, а ms_event содержит текущее состояние
мыши.
9.2. Программирование модема
Используйте Hayes Commands для управления модемом. Для контроля
порта rs232 вам потребуется termios. Смотри пример miniterm.c.
9.3. Программирование принтера
Смотрите пример checklp.c. Не используйте termios для управления
принтерного порта, пользуйтесь ioctl, inb/outnb, если необходимо,
команды Epson, Postscript, PCL и т.д.
вызовы ioctl: LPCHAR, LPTIME, LPABORT, LPSETIRQ, LPGETIRQ, LPWAIT
inb и outb определяют статус и управляют портом.
- 170 -
9.4. Программирование джойстика
Смотри пример js.c в модуле для джойстика пакета ядра.
вызовы 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. Операция чтения
10. Перенос прикладных программ в Linux
10.1. Введение
Перенос UNIX-приложений под Linux удивительно легок. Linux и его
GNU Си библиотека разработана для приложений, переносимых по замыслу;
это означает что многие программы компилируются просто через make.
Речь идет обо всех программах, не обращающихся к каким-то туманным
возможностям частной реализации, или сильно завязанных на
недокументированном или неопределенном поведении, или, скажем,
особенном системном вызове.
Linux часто не согласуется со стандартом IEEE Std 1003.1-1988
(POSIX.1), но это никак не сертифицировано. Linux позаимствовал много
хорошего от SVID и BSD ветвей UNIX, но опять же не подражал им во всех
возможных случаях. Проще говоря, Linux разработан чтобы быть
совместимым с другими реализациями UNIX, сделать прикладные программы
легко переносимыми, и в ряде случаев продвинут благодаря отобранным
лучшим идеям из этих реализаций.
Например, аргумент timeout, посылаемый системному вызову select,
на самом деле уменьшается Linux-ом во время опроса. Другие реализации
не изменяют это значение вовсе, и программа, скомпилированная под
Linux-ом может сломаться. Руководства SunOS и BSD говорят, что
модифицируемость указателя timeout - дело "будущих реализаций". К
сожалению, многие приложения до сих пор предполагают, что timeout
неприкосновенен.
Цель этой главы - сделать обзор основных вещей, связанных с
переносом приложений в Linux, освещая различия между Linux, POSIX.1,
SVID и BSD в следующих областях: обработка сигналов, ввод/вывод с
- 171 -
терминала, управление процессами и сбор информации и переносимая
условная компиляция.
10.2. Обработка сигналов
С годами определение и семантика сигналов изменялись и
совершенствовались различными реализациями UNIX. В настоящее время
существует 2 класса сигналов: ненадежные (unreliable) и надежные
(reliable). Ненадежные сигналы - это те, для которых вызванный однажды
обработчик сигнала не остается Такие "сигналы-выстрелы" должны
перезапускать обработчик внутри самого обработчика, если есть желание
сохранить сигнал действующим. Из-за этого возможна ситуация гонок, в
которой сигнал может прийти до перезапуска обработчика - и тогда он
будет потерян, или придет вовремя - и тогда сработает в соответствии с
заданным поведением (например, убьет процесс). Такие сигналы
ненадежны, поскольку отлов сигнала и переинсталяция обработчика не
являются атомарными операциями.
В семантике ненадежных процессов системные вызовы не повторяются
автоматически будучи прерванными поступившим сигналом. Поэтому для
обеспечения отработки всех системных вызовов программа должна
проверять значение errno после каждого из них и повторять вызовы, если
это значение равно EINTR.
По тем же причинам семантика ненадежных сигналов не предоставляет
легкого пути реализации атомарных пауз для усыпления процесса до
получения сигнала. Ненадежное поведение постоянно перезапускающегося
обработчика может привести к неготовности спящего в нужный момент
принять сигнал.
Напротив, семантика надежных сигналов оставляет обработчик
проинсталированным и ситуация гонок при перезапуске избегается. В то
же время определенные сигналы могут быть запущены заново, а атомарная
операция паузы доступна через функцию POSIX sigsuspend.
- 172 -
10.2.1. Сигналы в SVR4, BSD и POSIX.1
SVR4-реализация сигналов заключается функциях signal, sigset,
sighold, sigrelse, sigignore и sigpause. Функция signal эквивалентна
классическим сигналам UNIX V7, она предоставляет только ненадежные
сигналы. Остальные функции автоматически перезапускают обработчик.
Перезапуск системных вызовов не предусмотрен.
BSD предлагает функции signal, sigvec, sigblock, sigsetmask и
sigpause. Все сигналы надежны, а все системные вызовы перезапускаемы.
Программист имеет возможность это отключить.
В POSIX.1 предоставляются функции sigaction, sigprocmask,
sigpending и sigsuspend. Заметьте отсутствие функции signal: она
оказалась излишней. Указанные функции работают с надежными сигналами,
но перезапуск системных вызовов не определен совсем. Если sigaction
используется в BSD или SVR4, то перезапуск системных вызовов по
умолчанию отключен. Но он может включаться поднятием флага SA_RESTART.
В итоге, лучший путь работы с сигналами - это sigaction, которая
позволит точно определить поведение обработчиков сигналов. Однако
signal до сих пор используется во многих приложениях и, как мы видели,
имеет различную семантику в BSD и SVR4.
10.2.2. Опции сигналов Linux
В Linux определены следующие значения члена sa_flags структуры
sigaction.
* SA_NOCLDSTOP: Не посылайте SIGCHLD во время остановки
процесса-потомка.
* SA_RESTART: Осуществляет перезапуск определенных системных
вызовов во время прерывания обработчиком сигналов.
* SA_NOMASK: Обнуление маски сигнала (которое блокирует сигналы
во время работы обработчика сигналов).
- 173 -
* SA_ONESHOT: Очищает обработчик сигналов после исполнения.
Заметьте, что SVR4 использует SA_RESETHAND для тех же целей.
* SA_INTERRUPT: Определен под Linux-ом, но не используется. Под
SunOS системные вызовы автоматически перезапускались, а этот флаг
отменял такое поведение.
* SA_STACK: В настоящее время не работает; предназначен для
стеков сигналов.
Заметьте, что POSIX.1 определяет только SA_NOCLDSTOP, а
существуют различные другие опции, определенные SVR4, но невозможные
под Linux-ом. Во время переноса прикладных программ, которые
используют sigaction, вам, возможно, придется обновлять значения
sa_flags, чтобы добиться желаемого поведения.
10.2.3. signal под Linux-ом
Функция signal в Linux-е эквивалентна применению sigaction с
опциями SA_ONESHOT и SA_NOMASK, что соответствует классической
ненадежной семантике сигналов подобно SVR4.
Если вы хотите использовать signal с семантикой BSD, то для вас
большинство Linux-систем предоставляет совместимую с BSD библиотеку,
которую можно прилинковать. Для подключения этой библиотеки вы можете
добавить опции
-I/usr/include/bsd -lbsd
для командной строки компиляции. Перенося приложения, использующие
signal, присмотритесь к тому, какие предположения делает ваша
программа относительно обработчиков сигналов, и исправьте код (или
компилируйте с соответствующими установками), чтобы добиться
правильного поведения.
- 174 -
10.2.4. Сигналы, поддерживаемые Linux-ом
Linux поддерживает практически все сигналы, предоставляемые SVR4,
BSD и POSIX, за несколькими исключениями:
* SIGEMT не поддерживается; он соответствует аппаратному сбою под
SVR4 и BSD.
* SIGINFO не поддерживается; он используется для информационных
запросов к клавиатуре под SVR4.
* SIGSYS не поддерживается; он относится к некорректному
системному вызову в SVR4 и BSD. В сочетании с libbsd этот сигнал
переопределяется в SIGUNUSED.
* SIGABRT и SIGIOT идентичны.
* SIGIO, SIGPOLL и SIGURG идентичны.
* SIGBUS определен как SIGUNUSED. Технически в среде Linux нет
никаких "ошибок шины".
10.3. Ввод/вывод с терминала
Так же, как для сигналов, управление вводом/выводом имеет 3
различных реализации: под SVR4, BSD и POSIX.1.
SVR4 работает со структурой termio и различными вызовами ioctl
(такими, как TCSETA, TCGETA и т.д.) для получения и установки
параметров терминала. Эта структура выглядит так:
struct termio {
unsigned short c_iflag; /* режимы ввода */
unsigned short c_oflag; /* режимы вывода */
unsigned short c_cflag; /* режимы управления */
unsigned short c_lflag; /* режимы упорядочения линий */
char c_line /* упорядочение линий */
unsigned char c_cc[NCC]; /* символы управления */
- 175 -
};
В BSD вызовы ioctl типа TIOCGETP, TIOCSETP и т.д. работают со
структурой sgtty.
В POSIX-е используется структура termios вместе с различными
функциями POSIX.1, такими как tcsetattr и tcgetattr. Структура termios
соответствует структуре termio в SVR4, но типы переименованы
(например, tcflag_t вместо unsigned short), и для размера массива c_cc
употребляется NCCS.
Под Linux-ом ядром поддерживается и termios POSIX.1, и termio
SVR4. Это означает, что, если ваша программа использует оба метода
доступа к вводу/выводу на терминал, то ее следует компилировать прямо
под Linux-ом. Если вы в чем-то сомневаетесь, то вам понадобится совсем
немного знания обоих методов, чтобы исправить termio на termios.
Будем, однако, надеяться, что это не потребуется. Обратите внимание на
то, пытается ли программа использовать поле c_line структуры termio.
Практически для всех приложений оно должно быть равно N_TTY, и если
программа предполагает возможность другого упорядочения линий, вы
можете заработать ошибку.
Если ваша программа использует реализацию BSD sgtty, вы можете
прилинковать libbsd, как описывалось выше. Это обеспечит перекройку
ioctl, означающую пересмотр запросов ввода/вывода на терминал в
термины структуры termios POSIX-а, поддерживаемые ядром. При
компиляции такой программы, если символы вроде TIOCGETP не определены,
вам придется прилинковать libbsd.
10.4. Управление процессами
Такие программы, как ps, top и free, должны иметь способ
получения информации от ядра о процессах и ресурсах системы.
Аналогично, отладчикам и другим подобным средствам требуется управлять
и инспектировать работающий процесс.
Такие возможности предоставляет ряд интерфейсов различных версий
UNIX, и практически все они либо машинно-зависимы, либо привязаны к
- 176 -
конкретной реализации ядра, поэтому не существует универсально
доступного интерфейса для такого взаимодействия ядра и процесса.
10.4.1. Подпрограммы kvm
Для прямого доступа к структурам ядра многие системы используют
устройство /dev/kmem и подпрограммы kvm_open, kvm_nlist и kvm_read.
Программа открывает /dev/kmem, читает символьную таблицу ядра,
определяет при помощи этой таблицы расположение данных в работающем
ядре и читает соответствующие адреса адресного пространства ядра
используя названные подпрограммы. Поскольку это требует согласования
между программой пользователя и ядром, размера и формата структур
данных, подобные программы перестраиваются для каждой новой версии
ядра, типа процессора и т.д.
10.4.2. ptrace и файловая система /proc
Системный вызов ptrace используется в 4.3BSD и SVID для
управления процессом и считывания из него информации. Классически он
используется отладчиками для, скажем, trap-исполнения процесса (с
условными точками останова) или исследования его состояния. Под SVR4
ptrace заменен файловой системой /proc, которая появляется как
директория, содержащая единственную точку входа в файл для каждого
работающего процесса, называемую ID процесса. Пользовательская
программа может открыть файл интересующего ее процесса и совершить над
ним различные вызовы ioctl для управления выполнением процесса или
получения информации о процессе от ядра. Аналогично, программа может
читать или записывать данные напрямую в адресное пространство процесса
через файловый дескриптор в файловую систему /proc.
10.4.3. Управление процессами под Linux
Под Linux-ом для управления процессом поддерживается системный
вызов ptrace, работающий так же, как 4.3BSD. Для получения информации
о процессе или системе Linux также предоставляет файловую систему
/proc, но с совершенно другой семантикой. Под Linux-ом /proc состоит
из ряда файлов с общесистемной информацией, такой как использование
памяти, средняя загруженность, статистика загружаемых модулей и
- 177 -
сетевая статистика. Эти файлы общедоступны для read и write; их
содержимое можно разбирать, используя scanf. Файловая система /proc
под Linux-ом также предоставляет точку входа в директорию для каждого
работающего процесса, называемую ID процесса. Она содержит файловые
точки входа для информации типа командной линии, связей с текущей
директорией и исполняемым файлом, открытых файловых дескрипторов и
т.д. Ядро предоставляет всю эту информацию в ответ на запрос read.
Такая реализация не противопоставляется файловой системе /proc,
находящейся в Plan 9, и имеет некоторые ее недостатки. Например, для
ps, чтобы просмотреть таблицу с информацией о всех работающих
процессах, нужно пересечь многие директории, открыть и прочитать
многие файлы. Для сравнения: подпрограммы kvm в других UNIX-системах
считывают структуры ядра напрямую, потратив лишь несколько системных
вызовов.
Очевидно, все реализации настолько различны, что перенос
приложений, их использующих, может стать серьезной задачей. Следует
особо отметить, что файловая система /proc в SVR4 намного грубее, чем
в Linux-е, и их нельзя использовать в одно и том же контексте. На
самом деле каждая программа, которая использует kvm или файловую
систему /proc SVR4, просто непереносима, и такие фрагменты кода должны
быть переписаны.
Вызовы ptrace Linux-а и BSD похожи, но все же имеют несколько
отличий:
* Запросы PTRACE_PEEKUSER и PTRACE_POKEUSER под BSD названы
соответственно PTRACE_PEEKUSR и PTRACE_POKEUSR в Linux-е.
* Регистры процесса могут быть установлены с использованием
запроса PTRACE_POKEUSR со смещениями, находящимися в
/usr/include/linux/ptrace.h.
* Запросы SunOS-а PTRACE_{READ,WRITE}{TEXT,DATA} не
поддерживаются, как не поддерживаются ни PTRACE_SETACBKPT, ни
PTRACE_SETWRBKPT, ни PTRACE_CLRBKPT, ни PTRACE_DUMPCORE. Отсутствие
этих запросов влияет лишь на малое количество существующих программ.
- 178 -
Linux не имеет подпрограмм kvm для чтения адресного пространства
ядра из пользовательской программы, но в нем есть средства, такие как
kmem_ps, в действительности являющиеся версией подобных подпрограмм.
Вообще говоря, они непереносимы, и каждый код, использующий kvm,
вероятнее всего, зависит от определенных обозначений или от типов
данных ядра, поэтому такой код следует признать машинно-зависимым.
10.5. Переносимая условная компиляция
Если вы хотите исправить существующий код для достижения
совместимости с Linux-ом, то вам потребуется использовать
ifdef...endif для того, чтобы окружить необходимые для этого участки.
Не существует стандарта выделения кода, зависящего от операционной
системы, но многие программы используют соглашение, принятое в SVR4
для кода System V, в BSD для BSD-кода и для linux - в Linux-зависимом
коде:
* __STRICT_ANSI__: только для ANSI C
* _POSIX_SOURCE: для POSIX.1
* _POSIX_C_SOURCE: если определено как 1, то используется POSIX.1,
если 2 - то POSIX.2
* _BSD_SOURCE: ANSI, POSIX и BSD
* _SVID_SOURCE: ANSI, POSIX и System V
* _GNU_SOURCE: ANSI, POSIX, BSD, SVID и GNU расширения. Это
значение по умолчанию, если ничто из вышеперечисленного не определено.
Если вы определили _BSD_SOURSE, то для библиотеки определится
_FAVOR_BSD. Тогда некоторые вещи POSIX-а и SVR4 будут вести себя, как
в BSD. Например, если определено _FAVOR_BSD, setgmp и longgmp будут
сохранять и запоминать маску сигнала, а getpgrp будет допускать
аргумент PID. Напомним, что вы должны собирать программу с libbsd,
чтобы добиться BSD-поведения.
- 179 -
gcc Linux-а автоматически определяет набор макросов, которые вы
можете использовать в своей программе:
* __GNUC__ (major GNU C версия, e.g., 2)
* __GNUC_MINOR__ (minor GNU C версия, e.g., 2)
* unix
* i386
* linux
* __unix__
* __i386__
* __linux__
* __unix
* __i386
* __linux
Многие программы используют
#ifdef linux
для окружения Linux-зависимого кода. Заметьте, что Linux поддерживает
многие вещи из Sistem V, и поэтому начинать программы, написанные
также для Sistem V и BSD, лучше всего с Sistem V-версии. Впрочем, вы
можете начинать и с BSD и собирать при помощи libbsd.
10.6. Дополнительные комментарии
Эта глава охватывает многое, связанное с переносом, кроме,
правда, отсутствия некоторых системных вызовов, названных в главе о
системных вызовах, и потоков (знатоки говорят, что загружаемый
потоковый модуль должен быть на ftp.uni-stuttgart.de в
pub/systems/linux/isdn). (Добавлено Свеном Голдтом /Sven Goldt/.)
- 180 -
11. Справочник системных вызовов
_exit - как exit, только с меньшими
возможностями (m+c)
accept - установка связи на сокете (m+c!)
access - проверка прав доступа пользователя к
файлу (m+c)
acct - пока не реализован (mc)
adjtimex - установка/получение переменных времени
ядра (-c)
afs_syscall - зарезервированный системный вызов
файловой системы andrew (-)
alarm - посылает SIGALARM в назначенное время
(m+c)
bdflush - сливает грязные буфера на диск (-c)
bind - назначает сокет для межпроцессовой
коммуникации (m!c)
break - пока не реализован (-)
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)
- 181 -
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)
- 182 -
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 - чтение, запись или исполнение для защищенной
- 183 -
памяти (-)
mpx - пока не реализован ()
msgctl - управление сообщением ipc (m!c)
msgget - получение id очереди сообщений ipc (m!c)
msgrcv - получение сообщения ipc (m!c)
msgsnd - посылка сообщение ipc (m!c)
munmap - удаление отображения файла из памяти (mc)
nice - изменение приоритета процесса (mc)
oldfstat - больше не существует
oldlstat - больше не существует
oldolduname - больше не существует
oldstat - больше не существует
olduname - больше не существует
open - открытие файла (m+c)
pathconf - получение информации о файле (m+!c)
pause - ждет до сигнала (m+c)
personality - получение текущей области исполнения для
ibcs (-)
phys - пока не реализован (m)
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)
- 184 -
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)
- 185 -
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)
sigreturn - пока не используется ()
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)
stty - пока не реализован ()
swapoff - окончание свопинга в файл/устройство
(m-c)
swapon - начало свопинга в файл/устройство (m-c)
symlink - создание символической связи с файлом (m+c)
sync - синхронизация буферов памяти и диска (mc)
syscall - исполнение системного вызова по номеру (-!c)
sysconf - получение значения системной переменной
- 186 -
(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)
ustat - пока не реализован (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.
(!) не одиночный системный вызов, использует другой системный вызов.
- 187 -
12. Аббревиатуры
ANSI American National Standard for Information Systems
API Application Programming Interface
ASCII American Standard Code for Information Interchange
AT 386 Advanced 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)
- 188 -
С О Д Е Р Ж А Н И Е
1. Операционная система LINUX 2
2. Ядро LINUX 3
3. Библиотека libc 3
4. Системные вызовы 4
5. ioсtl 5
6. Межпроцессовые коммуникации LINUX 6
6.1. Введение 6
6.2. Полудуплексные каналы UNIX 6
6.2.1. Основные понятия 6
6.2.2. Создание каналов на Си 8
2. Ядро LINUX 10
6.2.3. Каналы - легкий путь! 15
6.2.4. Атомарные (неделимые) операции с каналами 20
6.2.5. Примечания к полудуплексным каналам 20
6.3. Именованные каналы (FIFOs - First In First Out) 21
6.3.1. Основные понятия 21
6.3.2. Создание FIFO 21
6.3.3. Операции FIFO 23
6.3.4. Действие блокирования над FIFO 25
6.3.5. Неизвестный SIGPIPE 25
6.4 System V IPC 26
6.4.1. Базовые понятия 26
6.4.2. Очереди сообщений 29
6.4.3. Семафоры 49
6.4.4. Разделяемая память 72
7. Программирование звука 82
7.1. Программирование встроенного динамика 82
7.2. Программирование звуковой карты 83
8. Символьная графика 84
8.1. Функции ввода/вывода в libc 86
8.1.1. Форматированный вывод 86
8.1.2. Форматированный ввод 88
8.2. Библиотека termcap 89
8.2.1. Введение 89
8.2.2. Поиск описания терминала 91
8.2.3. Описание терминала 92
- 189 -
8.2.4. Свойства termcap 93
8.3. Введение в ncurses 101
8.4. Инициализация 104
8.5. Окна 105
8.6. Вывод 109
8.6.1. Форматированный вывод 110
8.6.2. Вставка символов и линий 111
8.6.3. Удаление символов и линий 112
8.6.4. Боксы и линии 112
8.6.5. Фоновый (background) символ 113
8.7. Ввод 114
8.7.1. Форматированный ввод 116
8.8. Опции 116
8.8.1. Опции ввода 118
8.8.2. Атрибуты терминала 119
8.8.3. Использование опций 120
8.9. Очистка окна и линий 123
8.10. Обновление терминала 124
8.11. Видеоатрибуты и цвет 127
8.12. Координаты курсора и окна 131
8.13. Прокрутка 132
8.14. Заполнители 134
8.15. Мягкие метки (Soft-labels) 134
8.16. Разное 135
8.17. Низкоуровневый доступ 136
8.18. Дамп экрана 136
8.19. Эмуляция termcap 137
8.20. Функции terminfo 137
8.21. Функции отладки 138
8.22. Свойства (capabilities) terminfo 138
8.22.1. Логические свойства 138
8.22.2. Числа 140
8.22.3. Строки 142
9. Программирование портов ввода/вывода 166
9.1. Программирование мыши 168
9.2. Программирование модема 169
9.3. Программирование принтера 169
9.4. Программирование джойстика 170
- 190 -
10. Перенос прикладных программ в Linux 170
10.1. Введение 170
10.2. Обработка сигналов 171
10.2.1. Сигналы в SVR4, BSD и POSIX.1 172
10.2.2. Опции сигналов Linux 172
10.2.3. signal под Linux-ом 173
10.2.4. Сигналы, поддерживаемые Linux-ом 174
10.3. Ввод/вывод с терминала 174
10.4. Управление процессами 175
10.4.1. Подпрограммы kvm 176
10.4.2. ptrace и файловая система /proc 176
10.4.3. Управление процессами под Linux 176
10.5. Переносимая условная компиляция 178
10.6. Дополнительные комментарии 179
11. Справочник системных вызовов 180
12. Аббревиатуры 187