Операционная система UNIX. Руководство программиста

         

Программа editor


Здесь подпрограммы curses используются для создания редактора текстов. Для простоты программа editor хранит буфер в stdscr; ясно, что в настоящем редакторе следовало бы иметь отдельную структуру для буфера. В этой программе есть и другие упрощения: не предусматривается редактирование файлов, содержащих больше строк, чем помещается на экране, либо со строками, длина которых больше длины строки на экране; считается, что файл не содержит управляющих символов.

Об этой программе стоит сказать следующее. Во-первых, она использует подпрограммы move(), mvaddstr(), flash(), wnoutrefresh(), clrtoeol(). Эти подпрограммы рассматривались в разделе Использование подпрограмм пакета curses.

Во-вторых, в ней применяются некоторые подпрограммы curses, о которых мы ранее не упоминали. Например, функция, осуществляющая запись файла, обращается к подпрограмме mvinch(), которая возвращает символ, находящийся в указанной позиции окна. Структура данных, используемая для записи файла, не содержит информации о длине строк и их количестве в файле, поэтому при записи уничтожаются хвостовые пробелы. Программа использует также подпрограммы insch(), delch(), insertln(), deleteln(), которые вставляют или удаляют символ или строку. Информацию об этих подпрограммах см. в curses(3X).

В-третьих, этот редактор воспринимает в качестве команд как специальные клавиши, так и символы ASCII. С одной стороны, для новичков более удобен в освоении редактор, использующий специальные клавиши. Им легче использовать стрелки для перемещения курсора, чем запомнить, что букве h соответствует движение влево, j - вниз, k - вверх, а l - вправо. С другой стороны, опытные пользователи обычно предпочитают использовать символы ASCII, так как в этом случае не нужно переносить пальцы в ту часть клавиатуры, где находятся специальные клавиши.

Примечание

Ваша curses-программа сможет работать с более широким набором терминалов, если в ней с каждой специальной клавишей будет связан символ ASCII, поскольку не на каждом терминале клавиатура включает, например, стрелки.


В-четвертых, команда CTRL+ L выполняет функцию, которую должны иметь большинство curses-программ. Случается так, что другие программы выводят что-либо на экран (например, при передаче сообщений), либо экран портится в результате сбоев оборудования. В этом случае пользователь может ввести CTRL+L, в результате чего экран очищается и заполняется вновь посредством

wrefresh (curscr)

Наконец, еще одной важной особенностью является то, что вводимые команды заканчиваются нажатием CTRL+D, а не ESC. Было бы соблазнительно использовать ESC, так как эта клавиша является одной из немногих, присутствующих на любой клавиатуре (наряду с RETURN и BREAK). Однако, это может привести к возникновению двусмысленности. Большинство терминалов использует последовательности символов, начинающиеся с ESC для управления терминалом и имеют клавиши, при нажатии на которые передаются такие последовательности. Если программа получает с клавиатуры ESC, она не может различить, нажал ли пользователь ESC или другую функциональную клавишу.

Editor и другие использующие curses программы решают эту проблему, устанавливая таймер. Если за некоторый интервал времени приходит еще символ и он может быть началом последовательности, которую генерирует функциональная клавиша, программа пытается читать последующие символы до тех пор, пока не будет считана вся последовательность для функциональной клавиши, либо символ, который не может быть частью такой последовательности, либо пока не истечет определенное время. Эта стратегия хороша, но не абсолютно надежна. Пользователь может нажать ESC и сразу за ней еще клавишу, и тогда curses-программа решит, что была нажата функциональная клавиша. Кроме того, ESC не сразу передается обрабатывающей программе, в результате чего последняя медленнее реагирует на нажатие этой клавиши.

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



/* Программа editor - экранный редактор. Пользовательский интерфейс подобен подмножеству vi. Для простоты буфер хранится в stdscr */

#include <curses.h>

#define CTRL(c) ((c) & 037)

main (argc, argv) int argc; char **argv; { extern void perror(), exit(); int i, n, l; int c; int line = 0; FILE *fd;

if (argc != 2) { fprintf (stderr, "usage: %s file\n", argv [0]); exit (1); }

fd = fopen (argv [1], "r"); if (fd == NULL) { perror (argv [1]); exit (2); }

initscr (); cbreak (); nonl (); noecho (); idlok (stdscr, TRUE); keypad (stdscr, TRUE);



/* Читаем файл */ while ((c = getc(fd)) != EOF) { if (c == '\n') line++; if (line > LINES - 2) break; addch(c); } fclose (fd);

move (0, 0); refresh (); edit ();

/* Записываем файл */ fd = fopen (argv [1], "w"); for (l = 0; l < LINES - 1; l++) { n = len(l); for (i = 0; i < n; i++) putc (mvinch (l, i) & A_CHARTEXT, fd); putc('\n', fd); } fclose(fd);

endwin (); exit (0); }

len (lineno) int lineno; { int linelen = COLS - 1;

while (linelen >= 0 && mvinch (lineno, linelen) == ' ') linelen--; return linelen + 1; }

/* Глобальное значение текущего положения курсора */ int row, col;

edit () { int c;

for (;;) { move (row, col); refresh (); c = getch ();

/* Команды редактора */ switch (c) {

/* hjkl и стрелки: перемещают курсор в указанном направлении */ case 'h': case KEY_LEFT: if (col > 0) col--; else flash (); break;

case 'j': case KEY_DOWN: if (row < LINES - 1) row++; else flash (); break;

case 'k': case KEY_UP: if (row > 0) row--; else flash (); break;

case 'l': case KEY_RIGHT: if (col < COLS - 1) col++; else flash (); break;

/* i: переход в режим ввода */ case KEY_IC: case 'i': input (); break;

/* x: удалить текущий символ */ case KEY_DC: case 'x': delch (); break;

/* o: вставить строку и перейти в режим ввода */ case KEY_IL: case 'o': move (++row, col = 0); insertln (); input (); break;

/* d: удалить текущую строку */ case KEY_DL: case 'd': deleteln (); break;



/* CTRL+L: перерисовать экран */ case KEY_CLEAR: case CTRL('L'): wrefresh (curscr); break;

/* w: записать и закончить работу */ case 'w': return;

/* q: закончить работу без записи файла */ case 'q': endwin (); exit (2);

default: flash (); break; } } }

/* Режим ввода: принимает и вставляет символы Выход: CTRL+D или EIC */ input () { int c;

standout (); mvaddstr (LINES - 1, COLS - 20, "Режим ввода"); standend (); move (row, col); refresh (); for (;;) { c = getch (); if (c == CTRL('D') c == KEY_EIC) break; insch (c); move (row, ++col); refresh (); } move (LINES - 1, COLS - 20); clrtoeol (); move (row, col); refresh (); }




Программа highlight


Эта программа иллюстрирует использование подпрограммы attrset(). Программа highlight читает текстовый файл и использует находящиеся в нем управляющие цепочки для переключения атрибутов отображения. \U означает подчеркивание, \B - повышенную яркость, \N восстанавливает подразумеваемые значения атрибутов.

Обратите внимание на первый вызов подпрограммы scrollok(), о которой мы еще не упоминали [см. curses(3X)]. Эта подпрограмма позволяет роллировать экран терминала, если его размер меньше размера файла. Когда программа пытается вывести что-либо ниже нижнего края экрана, scrollok() автоматически роллирует на одну строку вверх и вызывает refresh().

/* highlight: программа, использующая последовательности \U, \B, \N для выделения текста на экране, позволяя подчеркивать их или отображать с повышенной яркостью */

#include <curses.h>

main (argc, argv) int argc; char **argv; { FILE *fd; int c, c2; void exit (), perror ();

if (argc != 2) { fprintf (stderr, "usage: %s file\n", argv [0]); exit (1); }

fd = fopen (argv [1], "r"); if (fd == NULL) { perror (argv [1]); exit (2); }

initscr (); scrollok (stdscr, TRUE); nonl ();

while ((c = getc (fd)) != EOF) { if (c == '\\') { c2 = getc (fd); switch (c2) { case 'B': attrset (A_BOLD); continue; case 'U': attrset (A_UNDERLINE); continue; case 'N': attrset (0); continue; } addch (c); addch (c2); } else addch (c); }

fclose (fd); refresh (); endwin (); exit (0); }



Программа-пример


В данном пункте приводится программа-пример, управляемая посредством меню. Она позволяет поупражняться со всевозможными комбинациями в использовании системного вызова msgget(2), проследить, как передаются аргументы и получаются результаты.

Имена переменных выбраны максимально близкими к именам, используемым в спецификации синтаксиса системного вызова, что облегчает чтение программы.

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

Затем выбранные флаги комбинируются с правами на операции, после чего выполняется системный вызов, результат которого помещается в переменную msqid (строка 49). Контроль успешного завершения системного вызова производится в строке 50. Если значение msqid равно -1, выдается сообщение об ошибке и выводится значение внешней переменной errno (строки 52, 53). Если ошибки не произошло, выводится значение полученного идентификатора очереди сообщений (строка 57).

Далее приводится текст программы-примера. Здесь и далее номера строк служат только для ссылок и не являются частью программы.

1 /* Программа иллюстрирует 2 возможности системного вызова msgget() 3 (получение идентификатора очереди сообщений) */

4 #include <stdio.h> 5 #include <sys/types.h> 6 #include <sys/ipc.h> 7 #include <sys/sem.h> 8 #include <errno.h>

9 main () 10 { 11 key_t key; /* Тип описан как целое */ 12 int opperm, flags; /* Права на операции и флаги */ 13 int msgflg, msqid;

14 /* Ввести требуемый ключ */ 15 printf ("\nВведите шестнадцатеричный ключ: "); 16 scanf ("%x", &key);

17 /* Ввести права на операции */ 18 printf ("\nВведите права на операции "); 19 printf ("в восьмеричной записи: "); 20 scanf ("%o", &opperm);

21 /* Установить требуемые флаги */ 22 printf ("\nВведите код, соответствущий "); 23 printf ("нужной комбинации флагов:\n"); 24 printf (" Нет флагов = 0\n"); 25 printf (" IPC_CREAT = 1\n"); 26 printf (" IPC_EXCL = 2\n"); 27 printf (" IPC_CREAT и IPC_EXCL = 3\n"); 28 printf (" Выбор = ");


Назначение переменных, описанных в программе:

    command 
  Код управляющего действия.
    choice 
  Используется для выбора поля ассоциированной структуры данных, которое нужно изменить.
    msqid_ds 
  Структура для хранения информации об очереди.

Программа ведет себя следующим образом. Прежде всего предлагается ввести допустимый идентификатор очереди сообщений, который заносится в переменную msqid (строки 15, 16). Это значение требуется в каждом системном вызове msgct(2).

Затем нужно ввести код выбранного управляющего действия (строки 17-22); он заносится в переменную command.

Если выбрано действие IPC_STAT (код 1), системный вызов выполняется (строка 31) и распечатывается информация о состоянии очереди (строки 32-39); в программе распечатываются только те поля структуры, которые могут быть переустановлены. Отметим, что если системный вызов завершается неудачей, распечатывается информация о состоянии очереди на момент последнего успешного выполнения системного вызова. Кроме того, выводится сообщение об ошибке и распечатывается значение переменной errno (строки 90, 91). Если системный вызов завершается успешно, выводится сообщение, уведомляющее об этом, и значение использованного идентификатора очереди сообщений (строки 95, 96).

Если выбрано действие IPC_SET (код 2), программа прежде всего получает информацию о текущем состоянии очереди сообщений с заданным идентификатором (строка 45). Это необходимо, поскольку пример обеспечивает изменение только одного поля за один раз, в то время ведет себя следующим образом. Прежде всего предлагается ввести допустимый идентификатор очереди сообщений, который заносится в переменную msqid (строки 15, 16). Это значение требуется в каждом системном вызове msgct(2).

Если выбрано действие IPC_RMID (код 3), выполняется системный вызов (строка 86), удаляющий из системы идентификатор msqid, очередь сообщений и ассоциированную с ней структуру данных. Отметим, что для выполнения этого управляющего действия аргумент buf не требуется, поэтому его значение может быть заменено нулем (NULL).




Назначение переменных, описанных в программе:

sndbuf 
  Используется в качестве буфера, содержащего посылаемое сообщение (строка 12); шаблон при описании этой переменной - структура данных msgbufl (строки 9-12). Структура msgbufl является почти точной копией структуры msgbuf, описание которой находится во включаемом файле <sys/msg.h>. Единственное различие состоит в том, что длина символьного массива в msgbufl равна максимально допустимому размеру сообщения для данной конфигурации (MSGMAX), в то время как в msgbuf она устанавливается равной единице, чтобы отвечать требованиям компилятора. По этой причине нельзя использовать в пользовательской программе описание msgbuf непосредственно, то есть пользователь должен сам определять поля данной структуры.
    rcvbuf 
  Используется в качестве буфера, содержащего принимаемое сообщение (строка 12); шаблон при описании этой переменной - структура данных msgbufl (строки 9-12).
    i 
  Используется как счетчик символов при вводе с клавиатуры и занесении в массив, а также отслеживает длину сообщения при выполнении системного вызова msgsnd(); кроме того, используется как счетчик при выводе принятого сообщения после выполнения системного вызова msgrcv().
    c 
  Содержит символ, возвращаемый функцией getchar() (строка 45).
    flag  
  При выполнении системного вызова msgsnd() содержит значение, определяющее, нужен ли флаг IPC_NOWAIT

(строка 55).

    flags 
  При выполнении системного вызова msgrcv() содержит значение, определяющее комбинацию флагов IPC_NOWAIT и MSG_NOERROR (строка 103).
    choice 
  Содержит признак, определяющий выбранную операцию - посылка или прием сообщения (строка 27).

Отметим, что в программе структура данных msqid_ds снабжается указателем на нее (строка 19), указатель соответствующим образом инициализируется (строка 20); это позволяет следить за полями ассоциированной структуры данных, которые могут измениться в результате операций над сообщениями. При помощи системного вызова msgctl() (действие IPC_STAT) программа получает значения полей ассоциированной структуры данных и выводит их (строки 74-82 и 145-152).




/* Программа иллюстрирует возможности системного вызова semctl() (управление семафорами) */

#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>

#define MAXSETSIZE 25

main () { extern int errno; struct semid_ds semid_ds; int length, rtrn, i, c; int semid, semnum, cmd, choice; union semun { int val; struct semid_ds *buf; ushort array [MAXSETSIZE]; } arg;

/* Инициализация указателя на структуру данных */ arg.buf = &semid_ds;

/* Ввести идентификатор множества семафоров */ printf ("Введите ид-р множества семафоров: "); scanf ("%d", &semid);

/* Выбрать требуемое управляющее действие */ printf ("\nВведите номер требуемого действия:\n"); printf (" GETVAL = 1\n"); printf (" SETVAL = 2\n"); printf (" GETPID = 3\n"); printf (" GETNCNT = 4\n"); printf (" GETZCNT = 5\n"); printf (" GETALL = 6\n"); printf (" SETALL = 7\n"); printf (" IPC_STAT = 8\n"); printf (" IPC_SET = 9\n"); printf (" IPC_RMID = 10\n"); printf (" Выбор = "); scanf ("%d", &cmd);

/* Проверить значения */ printf ("идентификатор = %d, команда = %d\n", semid, cmd);

/* Сформировать аргументы и выполнить вызов */ switch (cmd) { case 1: /* Получить значение */ printf ("\nВведите номер семафора: "); scanf ("%d", &semnum); /* Выполнить системный вызов */ rtrn = semctl (semid, semnum, GETVAL, 0); printf ("\nЗначение семафора = %d\n", rtrn); break;

case 2: /* Установить значение */ printf ("\nВведите номер семафора: "); scanf ("%d", &semnum); printf ("\nВведите значение: "); scanf ("%d", &arg.val); /* Выполнить системный вызов */ rtrn = semctl (semid, semnum, SETVAL, arg.val); break;

case 3: /* Получить ид-р процесса */ rtrn = semctl (semid, 0, GETPID, 0); printf ("\nПоследнюю операцию выполнил: %d\n",rtrn); break;




/* Программа иллюстрирует возможности системного вызова semop() (операции над множеством семафоров) */

#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>

#define MAXOPSIZE 10

main () { extern int errno; struct sembuf sops [MAXOPSIZE]; int semid, flags, i, rtrn; unsigned nsops;

/* Ввести идентификатор множества семафоров */ printf ("\nВведите идентификатор множества семафоров,"); printf ("\nнад которым будут выполняться операции: "); scanf ("%d", &semid); printf ("\nИд-р множества семафоров = %d", semid);

/* Ввести число операций */ printf ("\nВведите число операций "); printf ("над семафорами из этого множества: \n"); scanf ("%d", &nsops); printf ("\nЧисло операций = %d", nsops);

/* Инициализировать массив операций */ for (i = 0; i < nsops; i++) { /* Выбрать семафор из множества */ printf ("\nВведите номер семафора: "); scanf ("%d", &sops [i].sem_num); printf ("\nНомер = %d", sops [i].sem_num);

/* Ввести число, задающее операцию */ printf ("\nЗадайте операцию над семафором: "); scanf ("%d", &sops [i].sem_op); printf ("\nОперация = %d", sops [i].sem_op);

/* Указать требуемые флаги */ printf ("\nВведите код, "); printf ("соответствующий требуемым флагам:\n"); printf (" Нет флагов = 0\n"); printf (" IPC_NOWAIT = 1\n"); printf (" SEM_UNDO = 2\n"); printf (" IPC_NOWAIT и SEM_UNDO = 3\n"); printf (" Выбор = "); scanf ("%d", &flags);

switch (flags) { case 0: sops [i].sem_flg = 0; break; case 1: sops [i].sem_flg = IPC_NOWAIT; break; case 2: sops [i].sem_flg = SEM_UNDO; break; case 3: sops [i].sem_flg = IPC_NOWAIT | SEM_UNDO; break; } printf ("\nФлаги = 0%o", sops [i].sem_flg); }

/* Распечатать все структуры массива */ printf ("\nМассив операций:\n"); for (i = 0; i < nsops; i++) { printf (" Номер семафора = %d\n", sops [i].sem_num); printf (" Операция = %d\n", sops [i].sem_op); printf (" Флаги = 0%o\n", sops [i].sem_flg); }

/* Выполнить системный вызов */ rtrn = semop (semid, sops, nsops); if (rtrn == -1) { printf ("\nsemop завершился неудачей!\n"); printf ("Код ошибки = %d\n", errno); } else { printf ("\nsemop завершился успешно.\n"); printf ("Идентификатор semid = %d\n", semid); printf ("Возвращенное значение = %d\n", rtrn); }

exit (0); }




/* Программа иллюстрирует возможности системного вызова shmctl() (операции управления разделяемыми сегментами) */

#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h>

main () { extern int errno; int rtrn, shmid, command, choice; struct shmid_ds shmid_ds, *buf; buf = &shmid_ds;

/* Ввести идентификатор сегмента и действие */ printf ("Введите идентификатор shmid: "); scanf ("%d", &shmid);

printf ("Введите номер требуемого действия:\n"); printf (" IPC_STAT = 1\n"); printf (" IPC_SET = 2\n"); printf (" IPC_RMID = 3\n"); printf (" SHM_LOCK = 4\n"); printf (" SHM_UNLOCK = 5\n"); printf (" Выбор = "); scanf ("%d", &command);

/* Проверить значения */ printf ("\nидентификатор = %d, действие = %d\n", shmid, command);

switch (command) { case 1: /* Скопировать информацию о состоянии разделяемого сегмента в пользовательскую структуру и вывести ее */

rtrn = shmctl (shmid, IPC_STAT, buf); printf ("\nИд-р пользователя = %d\n", buf->shm_perm.uid); printf ("Ид-р группы пользователя = %d\n", buf->shm_perm.gid); printf ("Ид-р создателя = %d\n", buf->shm_perm.cuid); printf ("Ид-р группы создателя = %d\n", buf->shm_perm.cgid); printf ("Права на операции = 0%o\n", buf->shm_perm.mode); printf ("Последовательность номеров "); printf ("используемых слотов = 0%x\n", buf->shm_perm.seq); printf ("Ключ = 0%x\n", buf->shm_perm.key); printf ("Размер сегмента = %d\n", buf->shm_segsz); printf ("Выполнил последнюю операцию = %d\n", buf->shm_lpid); printf ("Создал сегмент = %d\n", buf->shm_cpid); printf ("Число присоединивших сегмент = %d\n", buf->shm_nattch); printf ("Число удерживаюших в памяти = %d\n", buf->shm_cnattch); printf ("Последнее присоединение = %d\n", buf->shm_atime); printf ("Последнее отсоединение = %d\n", buf->shm_dtime); printf ("Последнее изменение = %d\n", buf->shm_ctime); break;




/* Программа иллюстрирует возможности системных вызовов shmat() и shmdt() (операции над разделяемыми сегментами памяти) */

#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h>

main () { extern int errno; int shmid, shmaddr, shmflg; int flags, attach, detach, rtrn, i;

/* Цикл присоединений для данного процесса */ printf ("\nВведите число присоединений "); printf ("для процесса (1-4): "); scanf ("%d", &attach); printf ("\nЧисло присоединений = %d\n", attach);

for (i = 0; i < attach; i++) { /* Ввести идентификатор разделяемого сегмента */ printf ("\nВведите ид-р разделяемого сегмента,\n"); printf ("над которым нужно выполнить операции: "); scanf ("%d", &shmid); printf ("\nИд-р сегмента = %d\n", shmid);

/* Ввести адрес присоединения */ printf ("\nВведите адрес присоединения "); printf ("в шестнадцатеричной записи: "); scanf ("%x", &shmaddr); printf ("\nАдрес присоединения = 0x%x\n", shmaddr);

/* Выбрать требуемые флаги */ printf ("\nВведите номер нужной комбинации флагов:\n"); printf (" SHM_RND = 1\n"); printf (" SHM_RDONLY = 2\n"); printf (" SHM_RND и SHM_RDONLY = 3\n"); printf (" Выбор = "); scanf ("%d", &flags);

switch (flags) { case 1: shmflg = SHM_RND; break; case 2: shmflg = SHM_RDONLY; break; case 3: shmflg = SHM_RND | SHM_RDONLY; break; } printf ("\nФлаги = 0%o", shmflg);

/* Выполнить системный вызов shmat */ rtrn = shmat (shmid, shmaddr, shmflg); if (rtrn == -1) { printf ("\nshmat завершился неудачей!\n"); printf ("\Код ошибки = %d\n", errno); } else { printf ("\nshmat завершился успешно.\n"); printf ("Идентификатор shmid = %d\n", shmid); printf ("Адрес = 0x%x\n", rtrn); } }

/* Цикл отсоединений для данного процесса */ printf ("\nВведите число отсоединений "); printf ("для процесса (1-4): "); scanf ("%d", &detach); printf ("\nЧисло отсоединений = %d\n", detach);

for (i = 0; i < detach; i++) { /* Ввести адрес отсоединения */ printf ("\nВведите адрес отсоединяемого сегмента "); printf ("в шестнадцатеричной записи: "); scanf ("%x", &shmaddr); printf ("\nАдрес отсоединения = 0x%x\n", shmaddr);

/* Выполнить системный вызов shmdt */ rtrn = shmdt (shmaddr); if (rtrn == -1) { printf ("\nshmdt завершился неудачей!\n"); printf ("\Код ошибки = %d\n", errno); } else { printf ("\nshmdt завершился успешно,\n"); printf ("идентификатор shmid = %d\n", shmid); } }

exit (0); }



Программа scatter


Эта программа берет первые (LINES - 1) строк стандартного ввода и отображает символы на экране терминала в случайном порядке. Чтобы эта программа работала правильно, входной файл не должен содержать символов табуляции и неотображаемых символов.

/* Программа scatter */

#include <curses.h> #include <sys/types.h>

extern time_t time ();

#define MAXLINES 120 #define MAXCOLS 160

char s [MAXLINES] [MAXCOLS]; /* Массив экрана */ int T [MAXLINES] [MAXCOLS]; /* Результирующий массив, чтобы сохранять количество и расположение введенных символов */

main () { register int row = 0, col = 0; register int c; int char_count = 0; time_t t; void exit (), srand ();

initscr (); for (row = 0; row < MAXLINES; row++) for (col = 0; col < MAXCOLS; col++) s [row] [col] = ' ';

col = row = 0;

/* Считываем */ while ((c=getchar ()) != EOF && row < LINES) { if (c != '\n') { /* Помещаем символ в массив экрана */ s [row] [col] = c; if (c != ' ') char_count++; } else { col = 0; row++; } }

time (&t); /* Инициализация датчика случайных чисел */ srand ((unsigned) t);

while (char_count) { row = rand () % LINES; col = (rand () >> 2) % COLS; if (T [row] [col] !=1 && s [row] [col] != ' ') { move (row, col); addch (s [row] [col]); T [row] [col] = 1; char_count--; refresh (); } }

endwin (); exit (0); }



Программа show


Программа show просматривает файл, показывая его содержимое экран за экраном в ответ на нажатия пробела. Программа вызывает cbreak(), чтобы не нужно было нажимать возврат каретки после пробела, и noecho(), чтобы пробел не выводился на экран. Не обсуждавшаяся ранее подпрограмма nonl() вызывается для дополнительной оптимизации. Также не обсуждавшаяся ранее подпрограмма idlok() вызывается, чтобы дать возможность вставлять и удалять строки [дополнительную информацию см. в curses(3X)]. Заметьте, что вызываются еще подпрограммы clrtoeol(), clrtobot().

#include <curses.h> #include <signal.h>

main (argc, argv) int argc; char **argv; { FILE *fd; char linebuf[BUFSIZ]; int line; void done(), perror(), exit();

if (argc != 2) { fprintf (stderr, "Вызов: %s файл\n", argv [0]); exit (1); }

fd = fopen (argv [1], "r"); if (fd == NULL) { perror (argv [1]); exit (2); }

signal (SIGINT, done);

initscr (); noecho (); cbreak (); nonl (); idlok (stdscr, TRUE);

while (1) { move (0, 0); for (line = 0; line < LINES; line++) { if (!fgets (linebuf, sizeof linebuf, fd)) { clrtobot (); done (); } move (line, 0); printw ("%s", linebuf); } refresh (); if (getch == 'q') done(); } }

void done () { move (LINES - 1, 0); clrtoeol (); refresh (); endwin (); exit (0); }



Программа two


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

Программа two - это простейший пример двухтерминальной curses-программы. Она не поддерживает уведомления и требует указания имени и типа второго терминала в командной строке. После этого на втором терминале нужно ввести команду sleep 100000, чтобы перевести его в неактивное состояние на время работы программы. Пользователь первого терминала должен иметь полномочия для ввода и вывода на втором терминале.

#include <curses.h> #include <signal.h>

SCREEN *me, *you; SCREEN *set_term ();

FILE *fd, *fdyou; char linebuf [512];

main (argc, argv) int argc; char **argv; { void done (), exit (); unsigned sleep (); char *getenv (); int c;

if (argc != 4) { fprintf (stderr, "usage: %s oterm otype ifile\n", argv [0]); exit (1); }

fd = fopen (argv [3], "r"); fdyou = fopen (argv [1], "w+"); signal (SIGINT, done); /* Красиво умереть */

me = newterm (getenv ("TERM"), stdout, stdin); /* Инициализация своего терминала */ you = newterm (argv [2], fdyou, fdyou); /* Инициализация второго терминала */

set_term (me); /* Устанавливаем режимы своего терминала */ noecho (); /* Отменяем эхо */ cbreak (); /* Включаем cbreak */ nonl (); /* Разрешаем переход к новой строке */ nodelay (stdscr, TRUE) /* Не зависаем на вводе */

set_term (you); /* Уст. реж. другого терминала */ noecho (); /* Отменяем эхо */ cbreak (); /* Включаем cbreak */ nonl (); /* Разрешаем переход к новой строке */ nodelay (stdscr, TRUE) /* Не зависаем на вводе */

/* Выдаем первый экран на свой терминал */ dump_page (me);

/* Выдаем второй экран на другой терминал */ dump_page (you);

for (;;) { /* Для каждого экрана */ set_term (me); c = getch (); /* Ждем, пока пользователь прочитает все это */ if (c == 'q') done (); if (c == ' ') dump_page (me); set_term (you); c = getch (); /* Ждем, пока пользователь прочитает все это */ if (c == 'q') done (); if (c == ' ') dump_page (you); sleep(1); } }


dump_page (term) SCREEN *term; { int line;

set_term (term); move (0, 0); for (line = 0; line < LINES - 1; line++) { if (fgetc (linebuf, sizeof linebuf, fd) == NULL) { clrtobot (); done (); } mvaddstr (line, 0, linebuf); } standout (); mvprintw (LINES - 1, 0, "--Еще--"); standend (); refresh (); /* Выводим */ }

/* Очищаем и заканчиваем */ void done () { /* Очищаем первый терминал */ set_term (me); move (LINES - 1, 0); /* В левый нижний угол */

clrtoeol (); /* Очищаем нижнюю строку */ refresh (); /* Все обновляем */ endwin (); /* Для выхода из curses */

/* Очищаем второй терминал */ set_term (you); move (LINES - 1, 0); /* В левый нижний угол */

clrtoeol (); /* Очищаем нижнюю строку */ refresh (); /* Все обновляем */ endwin (); /* Для выхода из curses */ exit (0); }




Программа window


Эта программа представляет собой пример работы со многими окнами. Основная информация содержится в stdscr. Если на физический экран нужно временно вывести что-либо еще, создается новое окно, покрывающее часть экрана. Обращение к wrefresh() для этого окна приводит к тому, что его содержимое записывается на экран терминала поверх содержимого stdscr. Последующий вызов refresh() для stdscr приводит к восстановлению экрана. Обратите внимание на вызов подпрограммы touchwin() [мы не упоминали о ней ранее, см. curses(3X)], который производится перед выводом содержимого окна поверх того окна, которое уже находится на экране. Эта подпрограмма препятствует оптимизации работы с экраном. Если у Вас возникают проблемы с выводом одного окна поверх другого, для нормальной работы следует предварительно вызывать touchwin() для нового окна.

#include <curses.h>

WINDOW *cmdwin;

main () { int i, c; char buf [120]; void exit ();

initscr (); nonl (); noecho (); cbreak ();

cmdwin = newwin (3, COLS, 0, 0); /* Верхние 3 строки */ for (i = 0; i < LINES; i++) mvprintw (i, 0, "Это строка %d окна stdscr", i);

for (;;) { refresh (); c = getch (); switch (c) { case 'c': /* Ввод команды с клавиатуры */ werase (cmdwin); wprintw (cmdwin, "Введите команду:"); wmove (cmdwin, 2, 0); for (i = 0; i < COLS; i++) waddch (cmdwin, '-'); wmove (cmdwin, 1, 0); touchwin (cmdwin); wrefresh (cmdwin); wgetstr (cmdwin, buf); touchwin (stdscr);

/* Теперь команда находится в буфере. В этом месте должны располагаться операторы для ее обработки */

case 'q': endwin (); exit (0); } } }



Программисты-одиночки


Программисты этой категории пишут программы только для того, чтобы облегчить выполнение своей основной работы. Полученные ими результаты могут быть помещены в фонд программ, доступных коллективу, в котором данный программист работает. Это похоже на то, как развивалась сама ОС UNIX: кто-то изготавливает полезный инструмент и затем использует его совместно с остальными членами организации. Программисты данной категории могут не иметь внешних требований к программам, работать без соавторов и без какого-либо управления их деятельностью. На процесс создания программы влияет только сама задача, решаемая программистом. Одним из факторов, способствующих наличию программистов данной категории, является то, что ОС UNIX является системой с разделением времени. Действительно, сотрудники, умеющие программировать, могут решать свои проблемы, минуя процедуру офици- ального одобрения их проектов и не ожидания в течение месяцев, когда штатные программисты напишут программы.

Программистам данной категории необходимо знать, как:

Выбирать подходящий язык программирования. Компилировать и выполнять программы. Использовать системные библиотеки. Анализировать программы. Отлаживать программы. Отслеживать версии программ.

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



ПРОГРАММЫ ДОСТУПА К ОБЪЕКТНЫМ ФАЙЛАМ ОБЫЧНОГО ФОРМАТА


Операционная система UNIX предоставляет набор инструментальных средств для доступа к различным частям объектных файлов. Эти средства избавляют использующую их программу от необходимости знать общую структуру объектного файла, хотя строение обрабатываемых частей программа должна знать в деталях.

Средства доступа можно разделить на четыре категории:

Открытие и закрытие объектного файла.

Чтение заголовков, данных из таблицы имен и информации о номерах строк.

Поиск начала секции, информации о настройке ссылок или таблицы номеров строк, относящихся к определенной секции.

Получение номера элемента таблицы имен объектного файла.

Функции доступа хранятся в библиотеке libld.a и описаны в разделе 3 Справочника программиста. Статья с обзором этих функций помещена в разделе 4 Справочника программиста, см. ldfcn(4).



Простая формулировка философии системы UNIX


В течение всего времени разработки программ в операционной системе UNIX держите в поле зрения девиз:


Используйте труд других программистов!   
 

В отличие от компьютерных окружений, в которых разработка новых проектов обычно начинается с нуля, в ОС UNIX значительная часть работы уже проделана и хранится, ожидая использования, в каталогах /bin, /usr/bin, а также /etc.

Особенности ОС UNIX (каналы, процессы, устройство файловой системы) способствуют многократному использованию программ, что подтверждается историей разработки самой системы, восходящей к 1969 году. Вы рискуете не понять сути системы UNIX, если не будете в своей работе использовать эти ее особенности.



Простой пример


Этот пример - полная yacc-спецификация, реализующая небольшой калькулятор; калькулятор имеет 26 регистров, помеченных буквами от a до z, и обрабатывает арифметические выражения, построенные при помощи операций

+, -, *, /, % (остаток от деления), & (побитное и), | (побитное или)

и присваиваний. Если на вход калькулятору дается присваивание, оно выполняется; в противном случае выводится значение выражения. Как и в языке C, целая константа, если она начинается с 0 (нуль), трактуется как восьмеричная, иначе - как десятичная.

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

%{

#include <stdio.h> #include <ctype.h>

int regs [26]; int base;

%}

%start list

%token DIGIT LETTER

%left '|' %left '&' %left '+' '-' %left '*' '/' '%' %left UMINUS /* устанавливает приоритет унарного минуса */

%% /* начало секции правил */

list : /* пусто */ | list stat '\n' | list error '\n' { yyerrok; } ;

stat : expr { (void) printf ("%d\n", $1); } | LETTER '=' expr { regs [$1] = $3; } ;

expr : '(' expr ')' { $$ = $2; } | expr '+' expr { $$ = $1 + $3; } | expr '-' expr { $$ = $1 - $3; } | expr '*' expr { $$ = $1 * $3; } | expr '/' expr { $$ = $1 / $3; } | expr '%' expr { $$ = $1 % $3; } | expr '&' expr { $$ = $1 & $3; } | expr '|' expr { $$ = $1 | $3; } | '-' expr %prec UMINUS { $$ = - $2; } | LETTER { $$ = regs[$1]; } | number ;

number : DIGIT { $$ = $1; base = ($1==0) ? 8 : 10; } | number DIGIT { $$ = base * $1 + $2; } ;

%% /* начало секции подпрограмм */

int yylex () /* процедура лексического анализа */ { /* возвращает значение LETTER для */ /* строчной буквы, yylval = от 0 до 25, */ /* возвращает значение DIGIT для цифры, */ /* yylval = от 0 до 9, для остальных */ /* символов возвращается их значение */

int c;

/* пропуск пробелов */ while ((c = getchar ()) == ' ') ;

/* c - не пробел */ if (islower (c)) { yylval = c - 'a'; return (LETTER); } if (isdigit (c)) { yylval = c - '0'; return (DIGIT); } return (c); }



Пустое значение


(Несуществующее) значение объекта типа void никаким образом невозможно использовать, к нему нельзя применить ни явное, ни неявное преобразования. Так как выражение типа void обозначает несуществующее значение, такое выражение можно использовать только как оператор-выражение (см. пункт Оператор-выражение в разделе ОПЕРАТОРЫ) или как левый операнд в выражении запятая (см. пункт Операция запятая в разделе ВЫРАЖЕНИЯ И ОПЕРАЦИИ).

Выражение можно преобразовать к типу void при помощи явной операции преобразования (например, чтобы отбросить результат вызова функции, используемого как оператор-выражение).



Пустой оператор


Пустой оператор имеет вид

;

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



Пустой суффикс


Многие программы состоят из единственного исходного файла. Для обработки этих случаев утилита make предоставляет правило с пустым суффиксом. Так, чтобы поддерживать системную программу cat(1), требуется make-файл, содержащий правило следующего вида:

.c: $(CC) $(CFLAGS) $< -o $@

На самом деле, поскольку правило .c является встроенным, не нужно вообще никакого make-файла. Пользователю надо только набрать

make cat dd echo date

(указанные программы состоят из одного файла), и все четыре исходных C-файла передаются из командной строки shell'а на обра- ботку при помощи .c-правила. Встроены следующие правила с одним суффиксом:

.c: .c~: .f: .f~: .sh: .sh~:

Пользователь может включить в make-файл дополнительные правила.



Работа с архивами


Чаще всего архивные файлы используются для хранения объектных файлов, составляющих библиотеку. Имя библиотеки может указываться в командной строке при вызове редактора связей (или в опции редактора связей команды cc). В результате при разрешении ссылок редактор связей просматривает таблицу имен архивного файла.

Команда ar(1) служит для создания архивных файлов, различных действий над их содержимым, а также для операций над их таблицами имен. Синтаксис команды ar несколько отличается от принятого в ОС UNIX. В командной строке необходимо задать ключ - один из символов из набора drqtpmx. Значение ключа определяет действия команды ar. Для изменения способа выполнения требуемой операции ключ может быть скомбинирован с одним или несколькими символами из набора vuaibcls. Общий вид команды ar:

ar -ключ [позиционирующее_имя] а_файл [имя]...

где позиционирующее_имя - это имя элемента архива, которое используется с некоторыми ключевыми символами для управления размещением файлов в архиве; а_файл - это имя архивного файла. По соглашению, имена архивных файлов имеют расширение .a (например, libc.a - архивный файл, содержащий объектные файлы стандартных функций языка C). Наконец, может быть указано одно или несколько имен, задающих файлы, над которыми будет выполнена операция, определенная значением ключа.

С помощью следующей команды можно создать архивный файл, содержащий объектные модули, используемые в программе restate:

ar -rv rste.a restate.o oppty.o pft.o rfe.o

Если в текущем каталоге нет других объектных файлов, то же можно проделать с помощью команды

ar -rv rste.a *.o

В результате выполнения последней команды на экран будет выведено следующее:

ar: creating rste.a a - oppty.o a - pft.o a - restate.o a - rfe.o

Команда nm(1) используется для получения различной информации из таблицы имен объектных файлов обычного формата. Объектные файлы могут, но не обязаны, храниться в архиве. Ниже приводится результат выполнения команды

nm -f rste.a

Опция -f предписывает выдачу полной информации. Предполагается, что при компиляции использовалась опция -g.


Symbols from rste.a[oppty.o]:

Name Value Class Type Size Line Section

oppty.c | | file | | | | rec | |strtag| struct| 50| | pname | 0|strmem| char[25]| 25| |(ABS) ppx | 26|strmem| float| | |(ABS) dp | 30|strmem| float| | |(ABS) i | 34|strmem| float| | |(ABS) c | 38|strmem| float| | |(ABS) t | 42|strmem| float| | |(ABS) spx | 46|strmem| float| | |(ABS) .eos | |endstr| | 50| |(ABS) oppty | 0|extern| float( )| 100| |.text .bf | 0|fcn | | | 7|.text ps | 8|argm't| *struct-rec| 50| |(ABS) .ef | 80|fcn | | | 3|.text .text | 0|static| | 4| |.text .data | 100|static| | | |.data .bss | 100|static| | | |.bss

Symbols from rste.a[pft.o]:

Name Value Class Type Size Line Section

pft.c | | file | | | | rec | |strtag| struct| 50| | pname | 0|strmem| char[25]| 25| |(ABS) ppx | 26|strmem| float| | |(ABS) dp | 30|strmem| float| | |(ABS) i | 34|strmem| float| | |(ABS) c | 38|strmem| float| | |(ABS) t | 42|strmem| float| | |(ABS) spx | 46|strmem| float| | |(ABS) .eos | |endstr| | 50| |(ABS) pft | 0|extern| float( )| 88| |.text .bf | 0|fcn | | | 7|.text ps | 8|argm't| *struct-rec| 50| |(ABS) .ef | 68|fcn | | | 3|.text .text | 0|static| | 4| |.text .data | 88|static| | | |.data .bss | 88|static| | | |.bss

Symbols from rste.a[restate.o]:

Name Value Class Type Size Line Section

restate.c | | file | | | | .0fake | |strtag| struct| 14| | _cnt | 0|strmem| int| | |(ABS) _ptr | 4|strmem| *Uchar| | |(ABS) _base | 8|strmem| *Uchar| | |(ABS) _flag | 12|strmem| char| | |(ABS) _file | 13|strmem| char| | |(ABS) .eos | |endstr| | 14| |(ABS) rec | |strtag| struct| 50| | pname | 0|strmem| char[25]| 25| |(ABS) ppx | 26|strmem| float| | |(ABS) dp | 30|strmem| float| | |(ABS) i | 34|strmem| float| | |(ABS) c | 38|strmem| float| | |(ABS) t | 42|strmem| float| | |(ABS) spx | 46|strmem| float| | |(ABS) .eos | |endstr| | 50| |(ABS) main | 0|extern| int( )| 600| |.text .bf | 0|fcn | | | 12|.text argc | 8|argm't| int| | |(ABS) argv | 12|argm't| **char| | |(ABS) fin | -4|auto | *struct-.0fake| 14| |(ABS) oflag | -8|auto | int| | |(ABS) pflag | -12|auto | int| | |(ABS) rflag | -16|auto | int| | |(ABS) ch | -20|auto | int| | |(ABS) first | -70|auto | struct-rec| 50| |(ABS) .ef | 580|fcn | | | 68|.text FILE | |typdef| struct-.0fake| 14| | .text | 0|static| | 31| 40|.text .data | 600|static| | | |.data .bss | 892|static| | | |.bss _iob | 0|extern| | | | fprintf | 0|extern| | | | exit | 0|extern| | | | opterr | 0|extern| | | | getopt | 0|extern| | | | fopen | 0|extern| | | | fscanf | 0|extern| | | | printf | 0|extern| | | | oppty | 0|extern| | | | pft | 0|extern| | | | rfe | 0|extern| | | |



Symbols from rste.a[rfe.o]:

Name Value Class Type Size Line Section

rfe.c | | file | | | | rec | |strtag| struct| 50| | pname | 0|strmem| char[25]| 25| |(ABS) ppx | 26|strmem| float| | |(ABS) dp | 30|strmem| float| | |(ABS) i | 34|strmem| float| | |(ABS) c | 38|strmem| float| | |(ABS) t | 42|strmem| float| | |(ABS) spx | 46|strmem| float| | |(ABS) .eos | |endstr| | 50| |(ABS) rfe | 0|extern| float( )| 104| |.text .bf | 0|fcn | | | 7|.text ps | 8|argm't| *struct-rec| 50| |(ABS) .ef | 84|fcn | | | 3|.text .text | 0|static| | 4| |.text .data | 104|static| | | |.data .bss | 104|static| | | |.bss

Для работы команды nm требуется, чтобы все содержимое архива было составлено из объектных модулей. Если поместить в архив что-либо другое, то при выполнении команды nm будет выдано сообщение:

nm: rste.a: bad magic




Работа с несколькими терминалами сразу


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

Писать программы, работающие с несколькими терминалами, - дело нелегкое, и подпрограммы curses не решают всех возникающих при этом проблем. Например, сами программы, а не подпрограммы библиотеки, должны определять имена файлов для терминалов и типы этих терминалов. Стандартный способ с использованием переменной окружения $TERM не срабатывает, поскольку каждый процесс может знать только о своем собственном окружении.

Другая проблема возникает, когда несколько программ пытаются читать данные с одного и того же терминала. Следует избегать ситуации, когда программы перехватывают данные друг у друга. В то же время, программа, которая пытается занять дополнительный терминал, не должна просто прерывать программу, которая уже работает на нем. (Обычно это также неприемлемо из соображений безопасности, но бывают случаи, когда это может оказаться полезным, например, если терминал не используется, либо для разработки программ связи между терминалами). Типичным решением проблемы является запуск каждым пользователем программы, уведомляющей главную программу о желании с ней работать и передающей главной программе идентификатор процесса уведомляющей программы, имя линии и тип используемого терминала. Затем уведомляющая программа переходит в неактивное состояние до окончания работы главной. Перед своим завершением, главная программа активизирует все уведомляющие программы, после чего все они завершаются.

Концепция работы curses со многими терминалами заключается в поддержании текущего терминала. Все обращения к подпрограммам относятся к этому текущему терминалу. Главная программа должна описать каждый терминал и сохранить ссылку на него в своих переменных. Когда программе нужно работать с некоторым терминалом, ей следует установить его в качестве текущего и вызывать обычные подпрограммы curses.


Ссылки на терминал в curses-программе имеют тип *SCREEN. Новый терминал инициализируется путем обращения к newterm (type, outfd, infd). newterm возвращает указатель на новый терминал, type - это цепочка символов, содержащая тип используемого терминала. outfd является указателем на файл [*FILE, stdio(3S)], который используется для вывода на терминал, а infd указывает на файл для ввода с терминала. Вызов newterm() заменяет обычное обращение к initscr(), которое расширяется в newterm (getenv ("TERM"), stdout, stdin).

Для изменения текущего терминала необходимо вызвать set_term (sp), где sp указывает на терминал, который должен стать текущим. set_term() возвращает указатель на терминал, который был текущим на момент ее вызова.

Важно понять, что каждый терминал будет иметь свои собственные режимы работы и окна. Каждый терминал должен быть проинициализирован соответствующим вызовом newterm(). Для каждого терминала отдельно должны быть установлены режимы работы, например, cbreak() или noecho(). Равным образом для каждого терминала отдельно должны вызываться endwin() и refresh(). Ниже изображен типичный сценарий рассылки сообщения на несколько терминалов.

for (i=0; i<nterm; i++) { set_term (terms[i]); mvaddstr (0, 0, "Важное сообщение"); refresh (); }

Более подробный пример см. в программе two раздела Примеры программ, работающих с curses.




Работа с окнами


Ранее, в разделе Еще об окнах и подпрограмме refresh(), объяснялось, что такое окна и спецокна и для чего они могут Вам понадобиться. В этом разделе описываются подпрограммы curses, предназначенные для создания окон и спецокон и управления ими.



Разделяемые библиотеки ОС UNIX


Разделяемая библиотека представляет собой два файла: разделяемую библиотеку сборки и разделяемую библиотеку выполнения. Разделяемая библиотека сборки - это файл, который редактор связей просматривает для создания секции .lib выполняемого файла. Разделяемая библиотека выполнения - это файл, который ОС UNIX использует во время работы процесса. Разумеется, разделяемая библиотека выполнения должна быть доступна процессу.

Имена разделяемых библиотек (и сборки, и выполнения) характеризуются суффкисом _s. Наличие этого суффикса отличает, например, разделяемую библиотеку языка C /lib/libc_s.a от стандартной библиотеки /lib/libc.a. Кроме того, суффикс _s обозначает, что эти библиотеки статически отредактированы (см. ниже). Как правило, разделяемые библиотеки выполнения хранятся в каталоге /shlib. Отметим, что по умолчанию в данной версии ОС UNIX используется разделяемая версия библиотеки языка C, рассчитанная на сопроцессор вещественной арифметики MC68881. Имя библиотеки сборки - /lib/libc881_s.a, библиотеки выполнения - /shlib/libc881_s.



РАЗДЕЛЯЕМЫЕ СЕГМЕНТЫ ПАМЯТИ


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

Разделение памяти обеспечивает наиболее быстрый обмен данными между процессами.

Работа с разделяемой памятью начинается с того, что процесс при помощи системного вызова shmget(2) создает разделяемый сегмент, специфицируя первоначальные права доступа к сегменту (чтение и/или запись) и его размер в байтах. Чтобы затем получить доступ к разделяемому сегменту, его нужно присоединить посредством системного вызова shmat() [см. shmop(2)], который разместит сегмент в виртуальном пространстве процесса. После присоединения, в соответствии с правами доступа, процессы могут читать данные из сегмента и записывать их (быть может, синхронизируя свои действия с помощью семафоров).

Когда разделяемый сегмент становится ненужным, его следует отсоединить, воспользовавшись системным вызовом shmdt().

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



Размещение секций в именованных областях памяти


Можно потребовать, чтобы секция была размещена где-либо внутри именованной области памяти, определенной ранее предложением MEMORY. По аналогии с принятым в операционной системе UNIX синтаксисом переназначения стандартного вывода, для этого применяется символ >. Пример:

MEMORY { mem1: o=0x000000 l=0x10000 mem2 (RW): o=0x020000 l=0x40000 mem3 (RW): o=0x070000 l=0x40000 mem1: o=0x120000 l=0x04000 }

SECTIONS { outsec1: {f1.o (.data)} > mem1 outsec2: {f2.o (.data)} > mem3 }

Эти предложения предписывают ld(1) разместить секцию outsec1

где-либо внутри области памяти mem1, то есть между 0x0 и 0xFFFF или между 0x120000 и 0x123FFF. Секция outsec2 будет размещена в диапазоне адресов от 0x70000 до 0xAFFFF.



РАЗРАБОТКА LEX-ПРОГРАММ


lex-спецификация состоит максимум из трех секций: определений, правил и подпрограмм пользователя. Секция правил обязательна. Секции определений и подпрограмм могут отсутствовать, но если они имеются, то должны встречаться в указанном порядке.



Редактирование внешних связей


Команда ld(1) вызывает редактор внешних связей непосредственно. Однако обычно эта команда не используется. Как правило, применяется команда запуска системы компиляции того или иного языка (например, cc), которая сама вызывает редактор связей. Редактор связей объединяет несколько объектных файлов в один, выполняет настройку ссылок, включает процедуры инициализации и генерирует таблицу имен, используемую отладчиком. Разумеется, можно выпол- нять редактирование связей и в случае единственного объектного файла. Результат редактирования связей по умолчанию помещается в файл a.out.

Файлы, указанные в команде ld и не являющиеся объектными (имена объектных файлов обычно оканчиваются на .o), рассматриваются как архивные библиотеки или файлы, содержащие директивы редактора связей. Команда ld имеет 16 опций, мы опишем четыре из них.

-o вых_файл
Используя эту опцию, можно сообщить редактору связей имя файла, которое следует использовать вместо a.out. Здесь нужно указать имя, по которому вы хотите в будущем вызывать Вашу программу. Конечно, можно достигнуть того же результата, выполнив команду mv a.out progname

-l библ
  Редактор связей будет использовать библиотеку с именем libбибл.a, где библ является цепочкой символов длиной не более 9. Данная опция позволяет дополнить список просматриваемых библиотек. Например, библиотека libc.a используется по умолчанию при вызове редактора связей посредством cc, а математическая библиотека libm.a, если это необходимо, должна быть указана с помощью данной опции.
Опция -l может встречаться в команде ld

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

-L каталог
  Последовательность поиска библиотек вида libбибл.a

изменяется следующим образом: сначала производится поиск в указанном каталоге, а затем в каталогах, принимаемых по умолчанию, обычно /lib и /usr/lib.
Опцию удобно применять в случае, если у Вас имеется несколько версий библиотеки и Вы хотите использовать одну из них. Если библиотека найдена в указанном с помощью опции -L каталоге, ее поиск в других каталогах не производится. Поскольку опция -L влияет на поиск библиотек, указанных с помощью опции -l, в командной строке она должна предшествовать -l.

-u имя
  В таблицу имен заносится объект имя как неопределенный. Такая возможность полезна, когда загружаются только библиотечные файлы, поскольку в начальный момент таблица имен пуста и нужна какая-либо неразрешенная ссылка, чтобы начать загрузку первой подпрограммы.

Если редактор связей вызывается посредством команды cc, в выполняемый файл включается процедура инициализации (обычно для C-программ это /lib/crt0.o). После выполнения главной программы процедура инициализации обращается к системному вызову exit(2).

Редактор связей воспринимает файлы, содержащие директивы для него. Подробному описанию управляющего языка редактора связей посвящена отдельная глава.



Регулярные выражения


Кроме возможностей, проиллюстрированных в предыдущем разделе, в awk'е есть и более мощное средство спецификации шаблонов - регулярные выражения. Простейшим регулярным выражением является буквальное указание требуемой цепочки. Пример (регулярные выражения обрамляются символами /):

/Asia/

Данное регулярное выражение - законченная awk-программа, печатающая все строки, содержащие какое-либо вхождение цепочки Asia. Если строка содержит более длинное слово, частью которого является цепочка Asia, например Asiatic, она все равно печатается (в файле countries, однако, таких слов нет).

Используемые в awk'е регулярные выражения трактуются так же, как в распознавателе шаблонов egrep(1). Ряд символов имеет специальный смысл.

Например, чтобы напечатать все строки, начинающиеся с A, следу- ет указать регулярное выражение

/^A/

Для печати всех строк, начинающихся с A, B или C, годится регулярное выражение

/^[ABC]/

Все строки, оканчивающиеся цепочкой ia, распечатываются с помощью шаблона

/ia$/

В общем случае, символ ^ обозначает начало строки, символ $ - конец строки, а символы, заключенные в квадратные скобки, [], сопоставляются с любым одиночным символом из перечисленных в скобках. Кроме того, awk позволяет использовать круглые скобки для группировки, а символ | - для перечисления альтернатив. Знак + определяет, что предшествовавшее ему выражение должно сопоставляться один или более раз, знак ? обозначает повторение нуль или один раз. Программа

/x|y/ { print }

печатает все цепочки, содержащие x или y, программа

/ax+b/ { print }

выводит все цепочки, содержащие символ a, за которым следует один или более символов x, а затем b (например, axb, Paxxxxxxxb, QaxxbR). Программа

/ax?b/ { print }

печатает все цепочки, содержащие символ a, за которым следует необязательный x, а затем символ b (например, ab, axb, yaxbPPPP, CabD).

Два символа, . и *, имеют тот же смысл, что и в ed(1). Более подробно, . обозначает произвольный символ, а * - нуль или более вхождений предыдущего символа. Так, шаблон


/a.b/

сопоставляется с любой записью, содержащей символ a, за которым следует произвольный символ, а затем символ b. Другими словами, запись должна включать символы a и b, разделенные произвольным символом. Например, /a.b/ сопоставляются с axb, aPb и xxxxaXbxx, но не с ab, axxb. Шаблон

/ax*c/

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

ac axc pqraxxxxxxxxxxc901

Так же, как и в ed(1), можно отменить специальную интерпретацию метасимволов (например, ^ и *). Для этого перед такими символами ставится знак \. Например, шаблон

/\/.*\//

сопоставляется с произвольной цепочкой символов, заключенной в "скобки" /.../.

Можно также специфицировать, что некоторое поле (переменная) должно сопоставляться с регулярным выражением (либо не сопоставляться). Это делается при помощи операций ~ и !~. Например, для входного файла countries следующая программа выводит на печать все страны, названия которых заканчиваются цепочкой ia:

Russia Australia India Algeria




Рекомендации по разработке исходного текста для разделяемой библиотеки


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

Рекомендации иллюстрируются примерами, заимствованными из опыта разработки разделяемой библиотеки языка C.

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

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

Адреса экспортируемых (доступных для внешнего использования) имен фиксированы. Если какой-либо адрес изменится (будь то функции или переменной), Вам придется перередактировать все работающие с разделяемой библиотекой выполняемые файлы.

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

Если библиотека использует какое-либо имя явно (то есть не через ссылку), его значение (то есть адрес) времени выполнения должно быть известно на стадии создания библиотеки.

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



РЕКУРСИВНЫЕ MAKE-ФАЙЛЫ


Еще одна особенность утилиты make касается учета окружения и возможности рекурсивных запусков. Если в командной строке shell'а где-либо указана последовательность $(MAKE), данная строка выполняется, даже если установлен флаг -n. Так как флаг -n распространяется по цепочке вызовов make'а (через переменную MAKEFLAGS), единственное выполняемое действие - это сама команда make. Данная особенность полезна в тех случаях, когда иерархия make-файлов описывает совокупность компонентов программных систем. Обращение make -n ... можно использовать для целей отладки; при этом распечатываются все те команды, которые должны быть выполнены, а также вывод вложенных вызовов утилиты make.



Мы описали разделяемые библиотеки ОС


Мы описали разделяемые библиотеки ОС UNIX с точки зрения их разработки и применения. Использование любой разделяемой библиотеки, как правило, обеспечит экономию дисковой и оперативной памяти, а также ресурсов процессора. Поэтому применение разделяемых библиотек следует настоятельно рекомендовать.

Sdb(1)


sdb(1) - это символьный отладчик (Symbolic Debugger). Слово "символьный" означает, что во время отладки можно использовать символические имена из программы. С помощью sdb можно отлаживать программы на языках C и Фортран 77 [если использовался компилятор f77(1)]. sdb можно использовать двумя способами: либо для контролируемого выполнения программы, либо для анализа образа памяти, оставшегося после аварийного завершения программы. В первом случае можно проследить, что происходит при выполнении программы до того места, где она аварийно завершается (или обойти это место, с тем чтобы продолжить выполнение). Во втором случае можно анализировать состояние на момент аварийного завершения программы, что, возможно, позволит выяснить причину неправильного функционирования.

Отладчик sdb(1) подробно описан в Справочнике пользователя. Еще раз подчеркнем, что использование отладчика КРОТ предпочтительнее.



Секции


Секция есть наименьшая часть объектного файла, которая подвергается перемещению и рассматривается как нечто отдельное и различимое. Как правило, в объектном файле присутствуют три секции, именуемые .text, .data и .bss. Это секции команд, инициализированных и неинициализированных данных. В других секциях могут размещаться комментарии, дополнительные сегменты команд и данных, разделяемые сегменты данных. Допускаются секции, определенные пользователем. Однако в любом случае ОС UNIX при загрузке файла на выполнение помещает в память только секции .text, .data и .bss.

Примечание

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


В объектном файле обычного формата за таблицей заголовков секций следует содержимое секций - соответствующее количество байт команд или данных. Содержимое начинается с границы, кратной четырем байтам.

Предложение SECTIONS управляющего языка редактора связей (см. Руководство по редактору связей) предоставляет, помимо прочих, следующие возможности:

Определять, как надлежит компоновать входные секции.

Управлять расположением выходных секций.

Переименовывать выходные секции.

Если предложения SECTIONS отсутствуют, каждая входная секция размещается в одноименной выходной. Например, если редактируются связи набора объектных файлов, в каждом из которых имеется секция .text, то выходной объектный файл будет содержать единственную секцию .text, полученную путем объединения одноименных входных секций.




Секция объектного файла должна занимать непрерывный участок памяти и является минимальным объектом, который подвергается перемещению. Координатами секции являются ее начальный адрес и длина. В начале файла располагаются заголовки секций, описывающие все секции этого файла. В процессе обработки из секций входных файлов вырабатываются выходные секции, которые могут содержать команды, данные или смесь того и другого. Как между входными, так и между выходными секциями могут быть пустоты ("дыры"), однако в пределах одной выходной секции память выделяется последовательно и с пустотами перекрываться не может.



Секции DSECT, COPY, INFO и OVERLAY


При определении секций им может быть назначен тип. Пример:

SECTIONS { name1 0x200000 (DSECT) : { file1.o } name2 0x400000 (COPY) : { file2.o } name3 0x600000 (NOLOAD): { file3.o } name4 (INFO) : { file4.o } name5 0x900000 (NOLOAD): { file5.o } }

Применение опции DSECT приводит к созданию так называемой фиктивной секции. Свойства фиктивной секции таковы:

Не размещается, то есть не принимается во внимание при распределении памяти для выходных секций, поэтому не занимает памяти и не фигурирует в карте распределения памяти, которую выдает ld(1).

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

Глобальные имена, определенные в фиктивной секции, перемещаются обычным образом. В таблице имен выходного файла они имеют то же значение, которое имели бы, будь DSECT-секция действительно загружена по назначенному ей виртуальному адресу. Если в DSECT-секции найдены неопределенные внешние имена, то библиотеки объектных файлов, если указаны, просматриваются, чтобы найти элементы, где эти имена определяются. Связи найденных объектных файлов редактируются обычным образом, то есть не как фиктивные секции.

В выходной файл не включается ни содержимое фиктивной секции, ни ассоциированная с ней информация о настройке ссылок и номерах строк, если таковая имеется.

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

Секция с опцией COPY подобна фиктивной секции с той лишь разницей, что данные COPY-секции и вся связанная с ней информация включаются в выходной файл.

Те же свойства, что и COPY, имеют INFO-секции, однако назначение последних - хранить информацию о самом объектном файле, в то время как секции COPY могут содержать действительные данные или команды.

Секция типа NOLOAD лишь в одном отношении отличается от обычной выходной секции: содержащиеся в ней команды и/или данные не включаются в выходной файл. Под NOLOAD-секцию, однако, отводится виртуальная память, она фигурирует в карте распределения памяти и т.п.

Секция типа OVERLAY настраивается и записывается в выходной файл. От обычной секции она отличается тем, что не размещается и может перекрываться с другими секциями и неконфигурируемыми областями памяти.



Секция


Вспомогательные элементы для секций имеют формат, изображенный в следующей таблице:

Байты Описание Имя Смысл
0-3 long int x_scnlen Длина секции
4-5 unsigned short x_nreloc Число элементов в таблице настройки ссылок
6-7 unsigned short x_nlinno Число элементов в таблице номеров строк
8-17 - - Не используются (заполнены нулями)



Секция определений


Секция определений может содержать конструкции разных видов, самыми важными из которых являются описания внешних объектов, операторы #include и сокращения. Напомним, что в принципе данная секция необязательна, однако в большинстве случаев некоторые определения необходимы. Описания внешних объектов имеют те же форму и смысл, что и в языке C. Они означают, что переменные, глобально определенные где-то в другом месте (возможно, в другом исходном файле), будут доступны в сгенерированной lex-программе a.out. Рассмотрим определение из примера, который обсуждается ниже:

extern int tokval;

Целочисленное значение, присвоенное переменной, определенной таким образом, будет доступно в функции, вызывающей лексический анализатор, скажем, в функции разбора. Если, с другой стороны, нужно описать локальную переменную для использования в пределах последовательности действий из одного правила (например, управляющую переменную цикла), ее описание можно поместить в начале самого действия, справа от открывающей скобки, {.

Назначение оператора #include - то же, что и в языке C: включать в программу нужные файлы. Некоторые описания могут требоваться более чем в oодном исходном lex-файле, поэтому предпочтительно поместить их в один файл, включаемый в нужных случаях. Примером может служить использование lex'а совместно с yacc(1), который генерирует функции разбора, вызывающие лексический анализатор. В такой ситуации следует включать файл y.tab.h, поскольку он может содержать операторы #define для имен лексем. Подобно описаниям, операторы #include должны содержаться между скобками %{ и %}:

%{ #include "y.tab.h" extern int tokval; int lineno; %}

В секции определений после разделителя %}, который завершает описания и операторы #include, можно поместить сокращения для регулярных выражений, используемые в секции правил. В левой части строки указывается сокращение, а в правой, отделенной одним или несколькими пробелами, - его описание. Сокращения, используемые в правилах, обязательно должны быть заключены в фигурные скобки.


Примечание

Назначение сокращений - избежать излишних повторов при написании спецификаций и улучшить их читаемость.

В качестве примера вернемся к lex-спецификации, рассмотренной в начале раздела Более сложные элементы lex'а. Использование сокращений упрощает последующие ссылки на цифры, буквы и пробелы. Сокращения особенно полезны, когда они встречаются в правилах несколько раз.

D [0-9] L [a-zA-Z] B [ ] %% -{D}+ printf("отрицательное число"); \+?{D}+ printf("положительное число"); -0\.{D}+ printf("отрицательная дробь"); G{L}* printf("G-слово"); rail{B}+road printf("railroad - одно слово"); crook printf("не положено"); \"\./{B}+ printf(".\""); . . . . . .

Применение последнего правила, несколько более сложного, чем остальные, гарантирует, что в конце предложения точка окажется перед кавычкой. Так, example". будет заменено на example."




Секция подпрограмм


Смысл применения подпрограмм при использовании lex'а тот же, что и в случае других языков программирования. Действия, которые должны встречаться в нескольких правилах, можно записать один раз и вызывать там, где требуется. Подпрограммы, как и сокращения, облегчают написание и чтение программ. Например, функция put_in_tabl(), обсуждаемая в следующем о разделе, - достойный кандидат в подпрограммы.

Иногда подпрограмму помещают в эту секцию по методологическим соображениям - чтобы выделить некоторую часть действий или упростить секцию правил, даже если данное действие используется только в одном правиле. В качестве примера рассмотрим следующую подпрограмму, которая пропускает комментарии в языке, подобном C, где комментарии выделяются символами /* и */:

"/*" skipcmnts(); . . . /* Остальные правила */ %% skipcmnts () { for (;;) { while (input () != '*') ; if (input () != '/') { unput (yytext [yyleng-1]); } else return; } }

В этом примере затронуты три интересных вопроса. Во-первых, функция unput(c) (возвратить последний прочитанный символ) нужна, чтобы правильно обработать комментарии, заканчивающиеся комбинацией символов **/. Во-вторых, выражение yytext [yyleng-1] используется для выборки последнего прочитанного символа. В-третьих, в рассматриваемой подпрограмме предполагается, что комментарии не могут быть вложены. (Для языка C это действительно так.) Если, в отличие от C, в исходном тексте комментарии вложены, после распознавания цепочки */, закрывающей внутренний комментарий, сгенерированная программа будет продолжать чтение оставшейся части комментариев, как если бы это была часть текста, которую надо распознавать.

Другими примерами подпрограмм могут служить заданные программистом варианты обсуждавшихся выше функций ввода/вывода input(), unput() и output(). Подобные функции могут использоваться во многих программах, поэтому, вероятно, лучше всего было бы поместить их в отдельный файл или библиотеку и вызывать по мере необходимости. При этом в секцию определений следовало бы поместить соответствующие операторы #include.



СЕМАФОРЫ


Рассматриваемые в данном разделе средства позволяют процессам взаимодействовать, изменяя значения объектов, называемых семафорами. Значение семафора - это целое число в диапазоне от 0 до 32767. Поскольку во многих приложениях требуется более одного семафора, ОС UNIX предоставляет возможность создавать множества семафоров. Их максимальный размер ограничен системным параметром SEMMSL. Множества семафоров создаются при помощи системного вызова semget(2).

Процесс, выполнивший системный вызов semget(2), становится владельцем/создателем множества семафоров. Он определяет, сколько будет семафоров в множестве; кроме того, он специфицирует первоначальные права на выполнение операций над множеством для всех процессов, включая себя. Впоследствии данный процесс может уступить право собственности или изменить права на операции при помощи системного вызова semctl(2), предназначенного для управления семафорами, однако на протяжении всего времени существования множества семафоров создатель остается создателем. Другие процессы, обладающие соответствующими правами, для выполнения прочих управляющих действий также могут использовать системный вызов semctl(2).

Над каждым семафором, принадлежащим множеству, при помощи системного вызова semop(2) можно выполнить любую из трех операций:

Увеличить значение. Уменьшить значение. Дождаться обнуления.

Для выполнения первых двух операций у процесса должно быть право на изменение, для выполнения третьей достаточно права на чтение. Чтобы увеличить значение семафора, системному вызову semop(2) следует передать требуемое число. Чтобы уменьшить значение семафора, нужно передать требуемое число, взятое с обратным знаком; если результат получается отрицательным, операция не может быть успешно выполнена. Для третьей операции нужно передать 0; если текущее значение семафора отлично от нуля, операция не может быть успешно выполнена.

Операции могут снабжаться флагами. Флаг SEM_UNDO означает, что операция выполняется в проверочном режиме, то есть требуется только узнать, можно ли успешно выполнить данную операцию.


При отсутствии флага IPC_NOWAIT системный вызов semop(2) может быть приостановлен до тех пор, пока значение семафора, благодаря действиям другого процесса, не позволит успешно завершить операцию (ликвидация множества семафоров также приведет к завершению системного вызова). Подобные операции называются "операциями с блокировкой". С другой стороны, если обработка завершается неудачей и не указано, что выполнение процесса должно быть приостановлено, операция над семафором называется "операцией без блокировки".

Системный вызов semop(2) оперирует не с отдельным семафором, а с множеством семафоров, применяя к нему "массив операций". Массив содержит информацию о том, с какими семафорами нужно оперировать и каким образом. Выполнение массива операций с точки зрения пользовательского процесса является неделимым действием. Это значит, во-первых, что получается отрицательным, операция не может быть успешно выполнена. Для третьей операции нужно передать 0; если текущее значение семафора отлично от нуля, операция не может быть успешно выполнена.

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




ШАБЛОНЫ


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

некоторые ключевые слова

арифметические выражения и выражения сравнения

регулярные выражения

комбинации перечисленных выражений



Шаблоны-диапазоны


Шаблон в awk'е может также состоять из двух шаблонов, разделенных запятой:

шаблон1, шаблон2 { действие }

В этом случае действие выполняется для каждой строки, начиная со строки, удовлетворяющей шаблону1, и заканчивая строкой, удовлетворяющей шаблону2. Следующий оператор, в котором отсутствует действие,

/Canada/,/Brazil/

печатает все строки, расположенные между строкой, содержащей цепочку Canada, и строкой, содержащей цепочку Brazil. Например:

Canada 3852 24 North America China 3692 866 Asia USA 3615 219 North America Brazil 3286 116 South America

Шаблон

NR == 2, NR == 5 { ... }

вызывает выполнение действия для входных строк со второй по пятую. Разные типы шаблонов можно смешивать, например:

/Canada/, $4 == "Africa"

Данный оператор печатает все строки, начиная с той, которая содержит цепочку Canada, и заканчивая той, четвертое поле которой суть Africa.

Примечание

Приведенное выше обсуждение механизма сопоставления с шаблоном относится к разделу шаблонов awk-операторов. Сопоставление с шаблоном может также иметь место в операторах if и while в разделе действий. См. раздел Управляющие конструкции.



Shell как средство изготовления прототипов


Всякий раз, когда Вы входите в ОС UNIX, Вы используете shell. Shell - это интерактивный интерпретатор команд, позволяющий взаимодействовать с ядром ОС UNIX. Но это лишь одно из применений shell'а. Shell может быть использован как язык программирования. Программы на этом языке в дальнейшем будут называться shell-процедурами. Shell-процедуры могут иметь разветвленную структуру, из них можно запускать процессы. В языке имеются средства для обработки исключительных ситуаций, а также для переназначения ввода и вывода. Таким образом, shell является полноценным языком программирования.

Одним из достоинств shell'а является возможность организации так называемых конвейеров - цепочек команд, которые будут выполняться одна за другой таким образом, что вывод предыдущей команды является вводом следующей. В конвейерах может использоваться большинство команд, описанных в Справочнике пользователя, поэтому время, затраченное Вами на изучение Справочника пользователя, безусловно окупится. Просмотрите этот Справочник, и, возможно, Вы найдете команду, которая, будучи употребленной с нужными опциями, решит трудную программистскую проблему. Чем больше команд станет Вам известно, тем в большей степени Вы сможете воспользоваться возможностями ОС UNIX.

В данный момент мы не будем заниматься обучением программированию на shell'е. Подчеркнем лишь ту важную роль, которую может играть shell при разработке макетных образцов крупномасштабного программного обеспечения. Действительно, несмотря на то, что изучение shell'а со всеми его нюансами является достаточно сложной задачей, на получение shell-программ требуется гораздо меньше времени, чем на разработку программ на обычных языках высокого уровня.

Благодаря возможности быстрого получения работающих программ, shell является мощным инструментом при разработке программного обеспечения. Применение shell'а позволяет использовать труд других в максимальной степени, так как при программировании на shell'е можно легко и эффективно комбинировать различные компоненты. На shell'е часто удается запрограммировать даже большие прикладные программные системы. Даже если такое программирование выполняется с целью получения макетного образца программной системы, предназначенного лишь для проверки ее целей, могут быть сэкономлены многие месяцы работы. Действительно, имея такой макетный образец, можно определить диапазон возможных пользовательских ошибок, что не всегда легко проделать на стадии проектирования программной системы. Кроме того, макет позволяет отработать реакцию системы при некорректном вводе пользователя. Таким образом, можно избежать большого объема работ по исправлению ошибок, возникших на стадии проектирования разрабатываемой программной системы.

В заключении заметим, что из shell-процедур можно обращаться как к программам, написанным на обычных языках программирования, так и к стандартным командам ОС UNIX. Это удобно, так как часто с помощью имеющихся инструментов можно выполнить какие-либо действия, записав всего пару строк вместо полуторастраничной программы на каком-либо языке.



Сигналы и прерывания


Термины сигнал и прерывание в контексте ОС UNIX являются синонимами: оба означают сообщения, посылаемые операционной системой выполняющимся процессам. Как правило, результатом получения сигнала является прекращение выполнения процесса. Некоторые сигналы генерируются, если процесс пытается выполнить недопустимые действия; другие сигналы могут инициироваться обычным пользователем для своих собственных процессов, или суперпользователем для всех процессов.

Чтобы послать сигнал из одного процесса другому, также имеющему Ваш идентификатор пользователя, можно воспользоваться системным вызовом kill(2). Он имеет следующий формат:

int kill (pid, sig) int pid, sig;

где pid - это идентификатор процесса, которому посылается сигнал, а sig - целое число от 1 до 19, обозначающее посылаемый сигнал. Название kill является некоторым преувеличением - далеко не все сигналы смертельны. Ниже приведены некоторые сигналы, определенные во включаемом файле <sys/signal.h>.

#define SIGHUP 1 /* Освобождение линии */ #define SIGINT 2 /* Прерывание */ #define SIGQUIT 3 /* Выход */ #define SIGILL 4 /* Некорректная команда. Не пере- устанавливается при перехвате */ #define SIGTRAP 5 /* Трассировочное прерывание. Не пере- устанавливается при перехвате */ #define SIGIOT 6 /* Машинная команда IOT */ #define SIGABRT 6 /* Рекомендуемый синоним предыдущего */ #define SIGEMT 7 /* Машинная команда EMT */ #define SIGFPE 8 /* Исключительная ситуация при выполнении операции с вещественными числами */ #define SIGKILL 9 /* Уничтожение процесса. Не перехватывается и не игнорируется */ #define SIGBUS 10 /* Ошибка шины */ #define SIGSEGV 11 /* Некорректное обращение к сегменту памяти */ #define SIGSYS 12 /* Некорректный параметр системного вызова */ #define SIGPIPE 13 /* Запись в канал, из которого некому читать */ #define SIGALRM 14 /* Будильник */ #define SIGTERM 15 /* Программный сигнал завершения */ #define SIGUSR1 16 /* Определяемый пользователем сигнал 1 */ #define SIGUSR2 17 /* Определяемый пользователем сигнал 2 */ #define SIGCLD 18 /* Завершение порожденного процесса */ #define SIGPWR 19 /* Ошибка питания */


/* Сигналы SIGWIND и SIGPHONE используются только в UNIX/PC */ /*#define SIGWIND 20 */ /* Изменение окна */ /*#define SIGPHONE 21*/ /* Изменение строки состояния */

#define SIGPOLL 22 /* Регистрация выборочного события */

#define NSIG 23 /* Максимально допустимый номер сигнала. Сигналы могут иметь номера от 1 до NSIG-1 */ #define MAXSIG 32 /* Размер u_signal[], NSIG-1<=MAXSIG. MAXSIG больше, чем сейчас необходимо. В будущем, возможно, будут добавлены новые сигналы, при этом не придется менять user.h */

С помощью системного вызова signal(2) можно выбрать один из трех возможных способов реакции на получаемые сигналы. Имеется возможность:

Установить стандартную реакцию на сигнал. Игнорировать сигнал. Задать собственную функцию для обработки сигнала.




Сильная блокировка. Некоторые предупреждения


Сильная блокировка защищает только те сегменты файла, которые явно заблокированы. Доступность других частей определяется имеющимися правами.

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

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

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



Символьные константы


Символьная константа - это символ, заключенный в одинарные кавычки ', например 'x'. Значение символьной константы равно численному значению символа в принятой для данной системы кодировке. Некоторые неизображаемые символы, одинарную кавычку (') и обратную наклонную черту (\) можно представить в соответствии со следующей таблицей управляющих последовательностей:

перевод строки   \n  
 горизонтальная табуляция   \t  
 вертикальная табуляция   \v  
 забой   \b  
 возврат каретки   \r  
 переход к новой странице   \f  
 обратная наклонная черта   \\  
 одинарная кавычка   \' 
 набор бит   \ddd  

Управляющая последовательность \ddd состоит из символа \, за которым следуют восьмеричные цифры (одна, две или три), задающие значение требуемого символа. Специальный случай данной конструкции - \0 (дальше идут не цифры) - обозначает ASCII-символ NUL. Если символ, следующий за \, не совпадает ни с одним из приведенных в таблице, действие не определено. Явный символ перевода строки в символьной константе недопустим. Тип символьной константы - int.



Символы и целые


Символ или короткое целое можно использовать повсюду, где можно использовать целое. Во всех этих случаях данное значение преобразуется в целое. Преобразование более короткого целого в более длинное сохраняет знак. Для процессоров MC68020/30 переменные типа char являются знаковыми.

Когда более длинное целое преобразуется в более короткое или в char, оно усекается слева. Лишние биты просто отбрасываются.



СИНТАКСИС УПРАВЛЯЮЩЕГО ЯЗЫКА РЕДАКТОРА СВЯЗЕЙ


Примечание

В данном разделе символы квадратных и фигурных скобок несут двойную нагрузку:

Обычные символы [ ] и { } являются частью синтаксических конструкций и должны присутствовать во входных предложениях.

Cимволы [ и ], выделенные жирным шрифтом, указывают, что заключенная в них часть синтаксической конструкции является необязательной.

Символы { и }, выделенные жирным шрифтом, указывают, что заключенная в них часть синтаксической конструкции может быть повторена несколько раз.

Символ перевода строки в середине правила эквивалентен символу альтернативы |.

ifile ::= {cmd}

cmd ::= memory | sections | assignment | filename | flags

memory ::= MEMORY {[memory_spec]} memory_spec ::= name [attributes]:origin_spec,length_spec

attributes ::= ({ R | W | X | I }) origin_spec ::= origin=long

length_spec ::= length=long

origin ::= origin | o | org | ORIGIN length ::= length | l | len | LENGTH

sections ::= SECTIONS{{sec_or_group}} sec_or_group ::= section | group | library

group ::= GROUPgroup_options:{section_list} section_list ::= section{[,]section} section::=section_name[sec_options]:{[{statement}]} [fill][mem_spec]

group_options ::= [addr_2] | [align_option][block_option]

sec_options ::= [addr_2]

[align_option][type_option][block_option]

addr_2 ::= long | bind(expr) align_option ::= align(expr) align ::= align | ALIGN block_option ::= block(long) block ::= block | BLOCK type_option ::= (DSECT) | (NOLOAD) | (COPY) | (INFO) | (OVERLAY) fill ::= =long

mem_spec ::= name | attributes

statement ::= filename[[COMMON]][fill]

filename(name_list)[[COMMON]][fill]

*(name_list)[[COMMON]][fill]

assignment | library | пусто name_list ::= section_name[{delimiter section_name}]

library ::= -lname

bind ::= bind | BIND

assignment ::= lside assign_op expr end

lside ::= name | . assign_op ::= = | += | -= | *= | /= end ::= ; | , expr ::= expr binary_op expr | term

binary_op ::= * | / | % | + | - | << | >> | == | != > | < | >= | <= | & | &&


term ::= long | name | align(term) | (expr) unary_op term | phy(lside) | sizeof(section_name) next(long) | addr_4(section_name) unary_op ::= ! | - | ~ phy ::= phy | PHY sizeof ::= sizeof | SIZEOF next ::= next | NEXT addr_4 ::= addr | ADDR
flags ::= flag{wht_space flag} flag ::= -ewht_space name | -fwht_space long | -hwht_space long
-lname | -m | -owht_space filename | -r | -s | -t -uwht_space name | -z | -H | -Lpath_name | -М | -N | -S | -V -VSwht_space long | -a | -x delimiter ::= wht_space | , name ::= произвольное допустимое имя переменной
long ::= произвольная допустимая длинная целая константа
wht_space ::= пробел, символ табуляции, перевод строки
filename ::= любое допустимое в ОС UNIX имя файла
path_name ::= любое допустимое в ОС UNIX маршрутное имя
section_name ::= любое допустимое имя секции

Системные программисты


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



Системные вызовы и библиотечные функции


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

Внешне обращения к системным вызовам и библиотечным функциям ничем не отличаются и выглядят как вызовы обычных функций языка. Однако некоторые отличия все же имеются:

Во время редактирования связей команды, реализующие библиотечные функции, копируются в объектный файл Вашей программы, а коды, реализующие системные вызовы, находятся в ядре ОС.

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

Это означает, что несмотря на то, что библиотечные функции увеличивают размер выполняемого файла, накладные расходы на переключение при их выполнении меньше, чем при выполнении системных вызовов.



Size(1)


Команда size(1) выдает количество байт, занимаемое тремя секциями (.text, .data и .bss) объектного файла обычного формата при загрузке его в память для выполнения. Применяя эту команду к нашему объектному файлу, получаем:

3876 + 1228 + 2240 = 7344

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



Смысл описателей


Можно трактовать всякий описатель как следующее утверждение: если конструкция, имеющая такой же вид, что и описатель, встречается в выражении, она выдает объект с указанными типом и классом памяти.

Каждый описатель содержит ровно один идентификатор; это именно тот идентификатор, который описывается. Если в качестве описателя указан просто идентификатор, он будет иметь тип, заданный спецификатором_типа в начале описания.

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

Рассмотрим описание:

T D1

где T - спецификатор_типа (такой как int и т.п.), D1 - описатель. Предположим, что это описание придает идентификатору тип "... T", где "..." пусто, если D1 является просто идентификатором (например, тип объекта x в "int x" есть int). Если же описатель D1 имеет вид

*D

то тип содержащегося в нем идентификатора - "... указатель на T".

Если описатель D1 имеет вид

D ( )

то тип содержащегося в нем идентификатора - "... функция, возвращающая T".

Если описатель D1 имеет вид

D [константное_выражение]

или

D [ ]

то тип содержащегося в нем идентификатора - "... массив T". В первом случае константное_выражение есть выражение, тип которого суть int, а значение определяется во время компиляции и должно быть положительным. (Точное определение константных_выражений дается в разделе КОНСТАНТНЫЕ ВЫРАЖЕНИЯ). Если к описателю приписано несколько спецификаций "массив ...", формируется многомерный массив; константное_выражение, специфицирующее границы массива, может быть опущено только для первой границы. Это бывает полезно, если массив является внешним и настоящее определение, для которого и выделяется память, задано где-то в другом месте. Первое константное выражение может быть опущено также, когда за описателем следует инициализатор. В таком случае размер массива вычисляется исходя из числа указанных инициализирующих элементов.


Тип элемента массива может быть одним из базовых типов, указателем, структурой либо объединением, или тоже массивом (при формировании многомерного массива).

Не все синтаксически допустимые конструкции в действительности разрешаются. Имеются следующие ограничения: функции не могут возвращать массивы и функции, однако могут возвращать указатели на них; не может быть массивов функций, однако могут быть массивы указателей на функции. Аналогично, структура или объединение не могут содержать функции, но могут содержать указатели на функции.

Например, запись

int i, *ip, f (), *fip (), (*pfi) ();

описывает i как целое, ip - как указатель на целое, f - как функцию, возвращающую целое, fip - как функцию, возвращающую указатель на целое, и pfi - как указатель на функцию, возвращающую целое. Особенно полезно сравнить два последних описателя. *fip() интерпретируется как *(fip()). Описание указывает (этого требует и конструкция такого же вида в выражении), что вызов функции fip, а затем применение к результату-указателю операции перехода к значению, дает целое. В описателе (*pfi)() (как и в соответствующем выражении) дополнительные скобки необходимы, поскольку они обозначают, что операция перехода к значению, примененная к указателю на функцию, дает функцию, которая, выполнившись, возвращает целый результат.

Еще один пример

float fa [17], *afp [17];

описывает массив типа float и массив указателей на float. Наконец, конструкция

static int x3d [3] [5] [7];

описывает статический трехмерный массив целых размера 3*5*7. Подробно: x3d - это массив из трех компонентов; каждый компонент - массив из пяти массивов; каждый из последних - массив из семи целых чисел. Любое из подвыражений x3d, x3d[i], x3d[i][j], x3d[i][j][k] может оказаться приемлемым в некотором выражении. Первые три из них имеют тип "массив", тип последнего - int.




Соглашения


Везде, где в тексте Руководства приводятся примеры выдачи компьютера и/или вводимые Вами команды, мы следуем стандартной схеме обозначений, принятой во всей документации по ОС UNIX:

Жирным шрифтом изображаются команды и их аргументы, которые следует вводить именно так, как указано в тексте.

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

Считается, что после каждой команды или вводимого ответа на вопрос нажимается клавиша возврата каретки. Чтобы показать, что ожидается только нажатие клавиши возврата каретки самой по себе, в тексте используется обозначение <CR>.

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

В качестве стандартных приглашений для обычного пользователя и суперпользователя используются символы $ и #

соответственно. Приглашение $ означает, что Вы вошли в систему как обычный пользователь, # - как пользователь root.

Если в примере используется приглашение #, это означает, что иллюстрируемую этим примером команду может применять только пользователь root.



Составной оператор (блок)


Для того, чтобы несколько операторов можно было использовать там, где ожидается один, предусмотрен составной_оператор (назы- ваемый также блоком):

составной_оператор: { список_описаний список_операторов }

список_описаний: описание описание список_описаний

список_операторов: оператор оператор список_операторов

Если какой-либо идентификатор из списка_описаний ранее был описан, более внешнее описание теряет силу до конца блока, после чего оно снова начинает действовать.

Все инициализации автоматических и регистровых переменных выполняются при каждом входе в блок через его начало. В настоящий момент можно (правда, это порочная практика) передавать управление внутрь блока; в таком случае инициализации не выполняются. Инициализации статических переменных выполняются только один раз, когда начинается выполнение программы. Описания extern внутри блока память не резервируют, поэтому инициализация таких переменных не допускается.



СОВЕТЫ И ПРЕДОСТЕРЕЖЕНИЯ


Чаще всего причиной возникновения трудностей является специфическая трактовка утилитой make понятия зависимостей. Если файл x.c содержит строку

#include "defs.h"

считается, что от defs.h зависит объектный файл x.o, но не исходный файл x.c. Если defs.h изменяется, с x.c ничего не делается, в то время как x.o должен быть создан заново.

Чтобы понять, какие действия будет делать make, очень удобно использовать опцию -n. Обращение

make -n

предписывает выводить команды, которые make должен вычислять, не тратя на самом деле времени на их выполнение.

Если абсолютно точно известно, что изменение в файле касается только его внешнего представления (например, вставка комментария во включаемый файл), использование опции -t может сэкономить уйму времени. Вместо запуска большого числа ненужных перекомпиляций make просто обновит времена изменения затронутых файлов. Так, команда

make -ts

приведет в соответствие времена модификации соответствующих файлов. Очевидно, при использовании опции -t требуется осторожность, поскольку данный режим обработки отменяет первоначальное назначение утилиты make и разрушает всю информацию о более ранних изменениях.



СОВЕТЫ ПО ПОДГОТОВКЕ СПЕЦИФИКАЦИЙ


Этот раздел содержит разнообразные рекомендации по подготовке эффективных, легко изменяемых и понятных спецификаций.



Совместное использование lex'а и yacc'а


При работе над компилятором или программой, проверяющей правильность исходного текста, целесообразно воспользоваться мощным инструментальным средством системы UNIX - yacc(1). yacc генерирует функции синтаксического разбора и программы, анализирующие входной текст с целью убедиться в его синтаксической корректности. При разработке компиляторов объединение lex'а и yacc'а часто оказывается плодотворным. Даже если Вы пока не предполагаете использовать lex вместе с yacc, обязательно прочитайте этот раздел, так как он содержит информацию, интересную для всех пользователей lex'а.

Процедура лексического анализа, которую генерирует lex, (но не файл, в котором она сохраняется), имеет имя yylex(). Это удобно, поскольку yacc обращается к лексическому анализатору как раз по этому имени. При использовании lex'а для создания лексического анализатора в компиляторе следует завершать каждое lex- действие оператором return лексема, где лексема - введенное обозначение некоторого целочисленного значения. Возвращаемое значение лексемы указывает процедуре разбора, какую цепочку обнаружил лексический анализатор. Процедура разбора, которую yacc

помещает в файл y.tab.c, вновь получает управление и выполняет очередное обращение к лексическому анализатору, когда ей требуется очередная лексема.

При компиляции различные значения обозначают типы лексем, то есть показывают, является ли лексема определенным зарезервированным словом, идентификатором, константой, арифметической операцией или операцией сравнения. Во всех перечисленных случаях, кроме первого, анализатор, кроме того, должен указать точное значение лексемы: какой именно идентификатор обнаружен, какая константа, скажем, 10 или 1812, какая арифметическая операция, + или * (умножение), какая операция сравнения, = или >. Рассмотрим следующий фрагмент lex-спецификации лексического анализатора для некоторого языка программирования, несколько напоминающего язык Ада:

begin return (BEGIn); end return (END); while return (WHILE); if return (IF); package return (PACKAGE); reverse return (REVERSE); loop return (LOOP); [a-zA-Z][a-zA-Z0-9]* { tokval = put_in_tabl (); return (IDENTIFIER); } [0-9]+ { tokval = put_in_tabl (); return (INTEGER); } \+ { tokval = PLUS; return (ARITHOP); } \- { tokval = MINUS; return (ARITHOP); } > { tokval = GREATER; return (RELOP); } >= { tokval = GREATEREQL; return (RELOP); }


Классы лексем и значения, присваиваемые переменной tokval, обозначены именованными целочисленными константами, что облегчает понимание и модификацию правил. Соответствие между именами и значениями констант устанавливается при помощи операторов #define в C-процедуре разбора. Пример:

#define BEGIn 1 #define END 2 . . . #define PLUS 7 . . .

(В имени BEGIn последняя буква сделана строчной, поскольку имя BEGIN зарезервировано lex'ом.) При использовании yacc'а целесообразно вставить оператор

#include "y.tab.h"

в секцию определений lex-спецификаций. Файл y.tab.h содержит операторы #define, связывающие имена лексем, такие, как BEGIn, END и т.д. с целочисленными значениями, имеющими смысл для сгенерированной процедуры разбора.

В нашем примере для того, чтобы однозначно задать зарезервированное слово, достаточно возвращаемого значения. Для других типов лексем значение лексемы сохраняется в определенной программистом переменной tokval. Эта переменная описывается в секции определений как глобальная, доступная и процедуре лексического анализа, и процедуре синтаксического разбора. yacc для этих же целей предоставляет переменную yylval.

Отметим, что в примере показано два способа установки значения переменной tokval. В соответствии с первым способом функция put_in_tabl() заносит имя и тип идентификатора или константы в таблицу символов, к которой компилятор имеет доступ на этой и следующих стадиях процесса компиляции. Кроме того, put_in_tabl() выдает в качестве результата имя идентификатора или значение константы; этот результат присваивается переменной tokval. Предполагается, что функция put_in_tabl() помещается разработчиком компилятора в секцию подпрограмм. При втором способе, например, в нескольких последних действиях примера, переменной tokval присваивается определенное число, обозначающее, какую операцию распознал анализатор. Если PLUS в соответствии с приведенным выше оператором #define обозначает семерку, то при обнаружении знака + переменной tokval будет присвоено значение 7, характеризующее +. Значение, возвращаемое процедуре разбора, обозначает целый класс операций (в нашем примере - значения ARITHOP и RELOP).




Создание файла спецификаций библиотеки


После подготовки исходного текста для разделяемой библиотеки Вам нужно создать файл спецификаций. Этот файл содержит всю информацию, необходимую mkshlib(1) для создания файлов разделяе- мой библиотеки сборки и выполнения. Пример файла спецификаций приводится ниже, в разделе Пример. В файл спецификаций включаются следующие директивы [см. также mkshlib(1)]:

#address секция адрес
Указывает начальный адрес секции разделяемой библиотеки выполнения. Эта директива обычно используется для задания начальных адресов секций .text и .data.
#target маршрутное_имя
Указывает маршрутное_имя разделяемой библиотеки выполнения, по которому она будет находиться на целевом компьютере. По указанному маршрутному имени операционная система ищет разделяемую библиотеки выполнения, когда она нужна выполняемому файлу. Обычно, хотя и не обязательно, маршрутное_имя задает полный маршрут. Эта директива должна встречаться в файле спецификаций один и только один раз.
#branch
Отмечает начало спецификаций таблицы переходов. Строки, следующие за этой директивой, воспринимаются как строки спецификации таблицы переходов. Последние имеют следующий формат:

имя_функции пробел_или_табуляция позиция

Здесь имя_функции - это имя, для которого задается элемент таблицы переходов, а позиция задает позицию этого элемента в таблице. Позиция может быть целым числом или диапазоном целых чисел в формате позиция1-позиция2. Каждая позиция должна быть больше нуля и не может быть указана дважды. В таблице переходов не должно быть пропущенных позиций, то есть каждое число от 1 и до максимального указанного должно быть задействовано.

Если для имени указывается несколько элементов таблицы переходов (путем спецификации для него диапазона позиций или указания этого имени в нескольких строках спецификаций таблицы переходов), то используется максимальная из указанных позиций. Остальные выделенные для него позиции можно считать пустыми и выделять их другим именам в будущих версиях библиотеки.

Элементы таблицы переходов следует задавать только для функций, доступных извне библиотеки (экспортируемых).

Данная директива должна встречаться в файле спецификаций один и только один раз.

Данная директива должна встречаться в файле спецификаций один и только один раз. Здесь указатель - это указатель на внешнее (импортируемое) имя; он должен определяться в объектном_файле. Для каждой такой строки генерируются инструкции, соответствующие следующему оператору присваивания языка C: указатель=&имя; Об инициализации указателей см. ниже. Все спецификации инициализации для указанного объектного_файла должны быть собраны вместе, повторное указание того же объектного_файла не допускается. Данная директива должна встречаться в файле спецификаций один и только один раз.
#objects
Задает имена объектных модулей, из которых собирается разделяемая библиотека выполнения. Строки, следующие за этой директивой, воспринимаются как список объектных файлов, в том порядке, в котором они должны быть загружены в разделяемую библиотеку выполнения. Этот список состоит из имен объектных файлов, разделенных пробелами или символами табуляции. Из этих объектных файлов и будет состоять разделяемая библиотека.
#init объектный_файл
Указывает, что объектный_файл требует включения инструкций инициализации. Последующие строки рассматриваются как спецификации таких инструкций и должны иметь следующий формат: указатель пробел_или_табуляция имя
#ident цепочка_символов
Задает цепочку_символов, которая будет включена в секцию .comment разделяемой библиотеки выполнения, а также в одноименные секции каждого из объектных модулей разделяемой библиотеки сборки.
## комментарий
Остаток строки игнорируется.



Создание и определение имен при редактировании связей


Операторы присваивания ld(1) нужны для того, чтобы имя могло получить значение, которое вычисляется при редактировании связей. Обычно употребляются три вида операторов присваивания:

С символом . в левой части - для управления счетчиком размещения.

С символом . в правой части - для присваивания имени настраиваемого значения.

Без символа . - для присваивания имени абсолютного значения.

Первый вид присваиваний обсуждался в предыдущем разделе.

В операторах второго вида имени присваивается адрес, который становится известным только после размещения. Пример:

SECTIONS { outsc1: { ... } outsc2: { file1.o (s1) s2_start = . ; file2.o (s2) s2_end = . - 1; } }

Значением s2_start будет адрес начала секции file2.o (s2), а значением s2_end - адрес последнего байта file2.o (s2).

Рассмотрим следующий пример.

SECTIONS { outsc1: { file1.o (.data) mark = .; . += 4; file2.o (.data) } }

Здесь создается имя mark и его значением становится адрес байта, следующего за окончанием секции .data из file1.o. Затем резервируются четыре байта для будущего использования mark при выполнении. Тип имени mark - длинное целое (32 бита).

Значение выражения, содержащего счетчик размещения, вычисляется в процессе размещения, поэтому такие выражения могут встречаться только внутри предложений SECTIONS. В предложениях SECTIONS

могут быть и операторы присваивания, не содержащие ., хотя обычно так не делается. Значения выражений из правых частей таких операторов определяются после окончания размещения. Изменять подобным образом адрес какого-либо имени опасно. Пусть, например, имя определено в секции .data, где для него отведен и проинициализирован некоторый участок памяти. Тогда, если редактируется ряд объектных файлов, содержащих ссылки на это имя, соответствующий ему элемент таблицы имен с момента присваивания будет отражать новый, измененный адрес этого имени. В то же время инициализированные данные не переписываются по новому адресу; кроме того, могут остаться ссылки на старый адрес. ld(1) выдает предупреждение при изменении значения каждого имени, ранее уже определенного. Заметим, однако, что присваивание абсолютных значений новым именам совершенно безопасно, так как с такими именами не связаны ни ссылки, ни данные.



Создание множеств семафоров


Для создания множества семафоров служит системный вызов semget(2). В статье semget(2) Справочника программиста синтаксис данного системного вызова описан так:

#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>

int semget (key, nsems, semflg) key_t key; int nsems, semflg;

Целочисленное значение, возвращаемое в случае успешного завершения, есть идентификатор множества семафоров (semid). В случае неудачи результат равен -1.

Смысл аргументов key и semflg тот же, что и у соответствующих аргументов системного вызова msgget(2). Аргумент nsems задает число семафоров в множестве. Если запрашивается идентификатор существующего множества, значение nsems не должно превосходить числа семафоров в множестве.

Превышение системных параметров SEMMNI, SEMMNS и SEMMSL при попытке создать новое множество всегда ведет к неудачному завершению. Системный параметр SEMMNI определяет максимально допустимое число уникальных идентификаторов множеств семафоров в системе. Системный параметр SEMMNS определяет максимальное общее число семафоров в системе. Системный параметр SEMMSL определяет максимально допустимое число семафоров в одном множестве.

В статье semget(2) Справочника программиста описывается начальное значение ассоциированной структуры данных, формируемое в случае успешного завершения системного вызова.