ся одни и те же ука- затели позиции чтения/записи. Если новому процессу требу- ется передать какие то открытые файлы, или изменить файлы стандартного ввода/вывода, настройка программы на эти файлы делается после вызова fork в процессе-сыне до вызова execl. Следует заметить, что при буферизованном вводе/выводе необ- ходимо сбросить буфера перед вызовом fork(), иначе вывод накопленной информации может произойти дважды - и в -72- "родительском", и в новом процессе. 15.2.4. Канал межпроцессной связи Межпроцессный канал - это особый файл, устроенный таким образом, что один процесс неограниченно записывает в него информацию, а другой читает, причем система обеспечивает буферизацию данных и синхронизацию процессов. Межпроцесс- ные каналы могут создаваться интерпретатором команд shell или cshell, например: ls | pr Существуют библиотечные функции popen и pclose, позволяющие запустить параллельный процесс, который будет читать инфор- мацию, записываемую в указанный файл данным процессом, или, напротив, будет поставлять породившему его процессу данные для чтения (см. popen(3)). Эти функции используют базовые возможности построения каналов, которые поддерживаются опе- рационной системой. Для создания канала межпроцессной связи служит функция pipe: int fd[2]; ... stat = pipe(fd); if(stat == -1) /* Была ошибка */... Здесь fd - массив, в который засылается два дескриптора фай- лов - fd[1] для записи в канал, fd[0] для чтения из канала. Эти дескрипторы могут использоваться наравне с дескрипторами обычных файлов. Синхронизация обменов построена таким образом, что, если процесс читает пустой канал, он будет ждать появления данных; если в канале осталось много несчитанной информации, записывающий процесс будет ждать освобождения места в канале. Наконец, если у канала сторона для записи закрыта, при чтении будет получен код ответа "0" - конец файла. Как правило, программа создает канал по запросу pipe, после чего разделяется на две копии с помощью функции fork. Затем в одном из получившихся процессов закрывается сторона канала для чтения, в другом - закрывается дескриптор записи в канал. Теперь после вызова execl начинается обмен инфор- мацией по межпроцессному каналу между параллельно выполняю- щимися программами. В случае, если обмен должен происходить через стандарт- ный ввод или вывод, используется функция dup для связывания дескрипторов файлов. Например, следующий фрагмент программы служит для запуска программы pr так, чтобы данные на -73- стандартный ввод программы pr поступали из стандартного вывода основной программы: int fd[2]; #define R 0 #define W 1 pipe(fd); if(fork() == 0) { close(fd[W]); close(0); dup(fd[R]); close(fd[R]); execl("/bin/pr","pr",NULL); exit(1); /* Если ошибка в execl */ } close(fd[R]); close(1); dup(fd[W]); close(fd[W]); .... счет, при записи проверяем, не было .... ли ошибки записи. close(1); В этом примере полностью опущена обработка возможных ошибок. Для связывания дескрипторов стандартного ввода или вывода с каналом межпроцессной связи здесь использована функция dup(fd)", которая возвращает дупликат дескриптора fd, причем используется наименьший свободный дескриптор файла. Следо- вательно, после закрытия файла с дескриптором 0 ближайшее обращение к функции dup свяжет дескриптор 0 с заданным в аргументе dup дескриптором. После вызова dup ненужный больше дескриптор fd[0] или fd[1] закрывают. 15.3. Сигналы и прерывания Нормальный ход выполнения программы в ОС ДЕМОС может прерываться "сигналами". Сигналы могут появляться как в результате действия внешних причин (например, в результате нажатия на терминале клавиши, интерпретируемой системой как "прерывание" - interupt), так и в результате ошибок прог- раммы. Функция, изменяющая принятые по умолчанию действия по сигналу, называется signal и имеет два аргумента. Первый специфицирует сигнал, а второй представляет собой либо ссылку на функцию, либо специальное выражение, означающее требование "игнорировать" сигнал либо "стандартная реакция на сигнал". Условные обозначения записаны в файле вставок signal.h: #include <signal.h> signal (СИГНАЛ,РЕАКЦИЯ) СИГНАЛ - это один из стандартных кодов сигналов, например SIGINT, SIGKILL, ... (подробнее см. signal(2)). РЕАКЦИЯ - это либо ссылка на функцию, которая будет вызвана при полу- чении сигнала, либо один из идентификаторов: -74- SIG_IGN - игнорировать, SIG_DFL - по умолчанию. Во всех случаях функция signal возвращает старое значение описателя РЕАКЦИЯ. Существуют некоторые тонкости, которые иллюстрируются следующим фрагментом программы: #include <signal.h> main() { int onintr(); /* Описание обязательно */ if(signal(SIGINT,SIG_IGN) != SIG_IGN) { signal(SIGINT, onintr); } ... exit(0); } onintr() { unlink(tempfile); exit(1); } Проверка (if(signal...) связана с тем, что сигнал SIGINT посылается на все процессы, начатые с данного терминала. Если программа выполняется в фоновом режиме, интерпретатор shell при запуске программы устанавливает в ней игнорирова- ние сигнала SIGINT, для того, чтобы с терминала прерывались только интерактивные процессы. Переключение обработки сиг- нала SIGINT на функцию onintr без проверки перечеркнуло бы все действия shell по защите фоновых процессов. Еще одна особенность связана с возвратом из программы обработки сигнала. Если прерывание произошло во время выпол- нения программы, возврат из функции обработки прерывания приведет к нормальному продолжению ее выполнения. Если, однако, прерывание пришло во время операции чтения с терми- нала, операция чтения будет прервана, и произойдет возврат из функции чтения read с нулевым счетчиком байтов. Как пра- вило, функция обработки прерываний должна в таких случаях устанавливать какой либо флаг, а программа чтения, получив нулевой счетчик байтов после операции read, может проверить этот флаг и установить, что же произошло - достигнут конец файла или было прерывание. Если программа обладает средствами реакции на прерыва- ния и, в то же время, вызывает другие программы, желательно управлять реакцией на прерывание примерно таким образом: -75- signal(SIGINT, onintr); ... if(fork() == 0) { signal(SIGINT, SIG_DFL); execl(...) ... } signal(SIGINT, SIG_IGN); wait(&status); signal(SIGINT, onintr); В этом случае прерывания, посылаемые с терминала во время выполнения запущенной параллельно программы, будут прерывать только эту программу.  * 16. СВОДКА СИНТАКСИЧЕСКИХ ПРАВИЛ Эта сводка синтаксиса языка Си предназначена скорее для облегчения понимания и не является точной формулировкой языка. 16.1. Выражения Основными выражениями являются следующие: выражение: первичное_выражение * выражение & выражение - выражение ! выражение ~ выражение ++ l_значение -- l_значение l_значение ++ l_значение -- sizeof выражение (имя типа) выражение выражение бинарная_операция выражение выражение ? выражение : выражение l_значение операция_присваивания выражение выражение , выражение -76- первичное_выражение: идентификатор константа строка ^ (выражение) первичное_выражение (список выражений) необ первичное_выражение [выражение] l_значение . Идентификатор первичное выражение -> идентификатор l_значение: идентификатор первичное_выражение [выражение] l_значение . Идентификатор первичное_выражение -> идентификатор * выражение (l_значение) Операции первичных выражений () [] . -> имеют самый высокий приоритет и группируются слева направо. Унарные операции * & - ! ~ ++ -- sizeof(имя типа) имеют более низкий приоритет, чем операции первичных выраже- ний, но более высокий, чем приоритет любой бинарной опера- ции. Эти операции группируются справа налево. Условная опе- рация группируется справа налево, все бинарные операции группируются слева направо и их приоритет убывает в следую- щем порядке: бинарная операция: * / % + - >> << < > <= >= == != & ~ | && || ?: Все операции присваивания имеют одинаковый приоритет и груп- пируются справа налево: = += -= *= ?= %= >>= <<= &= ~= |= Операция запятая имеет самый низкий приоритет и группируется -77- слева направо. 16.2. Описания Описание: спецификаторы_описания список_инициа- лизируемых_описателей; необ Спецификаторы_описания: спецификатор_типа спецификаторы_описания необ спецификатор_класса_памяти специфи- каторы_описания необ спецификатор_класса_памяти: auto static extern register typedef спецификатор_типа: char short int long unsigned float double спецификатор_структуры_или_объединения определяющее_тип_имя спецификатор_перечисления список_инициализируемых_описателей: инициализируемый_описатель инициализируемый_описатель,спи- сок_инициализируемых_описателей инициализируемый_описатель описатель_инициализатор необ описатель: идентификатор (описатель) * описатель описатель () -78- описатель [константное выражение ] необ спецификатор_структуры_или_объединения: struct список_описателей_структуры struct идентификатор {список_опи- саний_структуры} struct идентификатор union {список_описаний_структуры} union идентификатор {список_опи- саний_структуры} union идентификатор список_описаний_структуры: описание_структуры описание_структуры список_опи- саний_структуры описание структуры: спецификатор_типа список_описа- телей_структуры список_описателей_структуры описатель_структуры описатель_структуры,список_описа- телей_структуры описатель_структуры: описатель описатель: константное выражение :константное_выражение инициализатор: = выражение = {список_инициализатора} = {список_инициализатора} список инициализатора: выражение список_инициализатора,список_ини- циализатора {список_инициализатора} имя_типа: спецификатор_типа абстракт- ный_описатель -79- абстрактный_описатель: пусто {абстрактный_описатель} * абстрактный_описатель абстрактный_описатель () абстрактный_описатель [констант- ное_выражение] необ определяющее_тип_имя: идентификатор спецификатор_перечисления: enum список_перечисления enum идентификатор список_перечисления enum идентификатор список_перечисления: перечисляемое список_перечисления, перечисляемое перечисляемое: идентификатор идентификатор = константное выражение 16.3. Операторы составной_оператор: {список_описаний список_операторов} необ необ список_описаний: описание описание список_описаний список_операторов: оператор оператор список_операторов -80- оператор: составной оператор выражение; if (выражение) оператор if (выражение) оператор else оператор while (выражение) оператор do оператор while (выражение); for(выражение1;выражение2;выражение3) необ необ необ оператор switch (выражение) оператор case константное_выражение : оператор default: оператор break; continue; return; return выражение; goto идентификатор; идентификатор : оператор ; 16.4. Внешние определения программа: внешнее_определение внешнее_определение программа внешнее_определение: определение_функции определение_данных определение_функции: спецификатор_типа описатель_функ- необ ции тело_функции описатель_функции: описатель (список_параметров) необ список_параметров: идетификатор идентификатор , список_параметров тело_функции: список_описаний_типа оператор_функции оператор_функции: {список описаний список_операторов} необ -81- определение данных: extern спецификатор_типа спи- необ необ сок инициализируемых описателей; необ static спецификатор типа список необ необ инициализируемых описателей; необ 16.5. Препроцессор #define идентификатор строка_лексем #define идентификатор(идентифика- тор,...,идентификатор) строка_лексем #undef идентификатор #include "имя_файла" #include <имя_файла> #if константное_выражение #ifdef идентификатор #ifndef идентификатор #else #endif #line константа "имя_файла" необ  * 17. Примеры программ на Си Пример 1: функции fgets и fputs (см. раздел "Стандарт- ная библиотека ввода/вывода. Ввод/вывод строк"). -82- #include <stdio.h> char *fgets(s,n,iop) /*взять<=n символов*/ char *s; /* из iop */ int n; register FILE *iop; { register int c; register char *cs; cs = s; while(--n>0&&(c=getc(iop)) !=EOF) if ((*cs++ = c)=='\n') break; *cs = '\0'; return((c==EOF && cs==s) ? NULL : s); } fputs(s,iop) /*поместить строку s в */ register char *s; /* файл iop */ register FILE *iop; { register int c; while (c = *s++) putc(c,iop); } Пример 2. Программа для разделения одного большого файла на несколько частей так, чтобы каждая часть начиналась со строки .sh 1 ... -83- #include <stdio.h> #define NEWH ".sh 1" /*Признак разделения*/ /* Трансляция: cc -o ds ds.c Запуск: ds откуда кудапреф кудасуфф результат: ds a pref suff переписывает файл a в файлы pref00.suff, pref01.suff, ... */ main (ac,av) char **av; { int nfile=0; /* Порядковый номер файла*/ char str[512]; /* Буфер для строки*/ if(ac != 4) { fprintf(stderr, "Неверное число аргументов0); exit(1); } /* freopen аналогично fopen, но изменяет указанный описатель файла, а не создает новый. Здесь мы переопределяем stdin */ if(!freopen(av[1],"r",stdin)) { fprintf(stderr, "Не могу открыть:%s0,av[1]); exit(2); } /* Переопределили файл станд. вывода */ of(av[2],nfile,av[3]); while( gets(str)) { /* strncmp(s1,s2,l) сравнивает две строки и возвращает 0, если первые l символов совпадают */ if(strncmp(str,NEWH,strlen(NEWH))== 0) { fclose(fp); nfile++; /* Это просто информационное сообщение */ fprintf(stderr, "Начало части %d0,nfile); fp = of(av[2],nfile,av[3]); } puts(str); if(ferror(stdout)) { -84- fprintf(stderr, "Ош записи в файл номер %.2d0,nfile); exit(4); } } exit (0); } /* Эта функция создает имя файла из трех частей и открывает его как стандартный вывод */ of(s1,n,s2) char *s1,*s2; { register FILE *f; char buf[100]; /* sprintf возвращает свой первый аргумент */ if(( f = freopen( sprintf(buf,"%s%02d.%s",s1,n,s2) ,"w",stdout))== NULL) { fprintf(stderr, "Не могу открыть файл:%s0,buf); exit(4); } return; } -85- СОДЕРЖАНИЕ ''АННОТАЦИЯ'' ................... 2 1. ВВЕДЕНИЕ .......................................... 1 2. СИНТАКСИЧЕСКАЯ НОТАЦИЯ ............................ 3 2.1. Ключевые слова .................................. 3 2.2. Константы ....................................... 4 2.2.1. Целые константы ............................... 4 2.2.2. Длинные (long) константы ...................... 4 2.2.3. Символьные константы .......................... 4 2.2.4. Вещественные константы ........................ 5 2.3. Строки .......................................... 5 2.4. Характеристики аппаратных средств ............... 6 3. ОБ'ЕКТЫ ЯЗЫКА СИ .................................. 6 3.1. Интерпретация идентификаторов ................... 6 3.2. Объекты и l_значения ............................ 8 3.3. Преобразования .................................. 8 3.3.1. Символы и целые ............................... 8 3.3.2. Типы float и double ........................... 9 3.3.3. Вещественные и целочисленные величины ......... 9 3.3.4. Указатели и целые ............................. 9 3.3.5. Целое без знака ............................... 9 3.3.6. Арифметические преобразования ................. 10 4. ВЫРАЖЕНИЯ ......................................... 10 4.1. Первичные выражения ............................. 11 4.2. Унарные операции ................................ 13 4.3. Мультипликативные операции ...................... 14 4.4. Аддитивные операции ............................. 15 4.5. Операции сдвига ................................. 16 4.6. Операции отношения .............................. 16 4.7. Операции равенства .............................. 17 4.8. Побитовая операция 'и' .......................... 17 4.9. Побитовая операция исключающего 'или' ........... 17 4.10. Побитовая операция включающего 'или' ............ 17 4.11. Логическая операция 'и' ......................... 18 4.12. Операция логического 'или' ...................... 18 4.13. Условная операция ............................... 18 4.14. Операция присваивания ........................... 19 4.15. Присваивание структуры .......................... 20 4.16. Операция 'запятая' .............................. 20 4.17. Старшинство и порядок вычисления. ............... 20 5. ОПИСАНИЯ .......................................... 22 5.1. Спецификаторы класса памяти ..................... 22 5.2. Спецификаторы типа .............................. 23 5.3. Описатели ....................................... 24 5.4. Смысл описателей ................................ 24 -86- 5.5. Описание структур и объединений ................. 26 5.6. Перечислимый тип ................................ 29 5.7. Инициализация ................................... 30 5.8. Имена типов ..................................... 32 5.9. Описатель typedef ............................... 33 6. ОПЕРАТОРЫ ......................................... 34 6.1. Операторное выражение ........................... 34 6.2. Составной оператор (или блок) ................... 34 6.3. Условные операторы .............................. 35 6.4. Оператор while .................................. 35 6.5. Оператор do ..................................... 35 6.6. Оператор for .................................... 35 6.7. Оператор switch ................................. 36 6.8. Оператор break .................................. 37 6.9. Оператор continue ............................... 37 6.10. Оператор возврата ............................... 38 6.11. Оператор goto ................................... 38 6.12. Помеченный оператор ............................. 38 6.13. Пустой оператор ................................. 38 7. ВНЕШНИЕ ОПРЕДЕЛЕНИЯ ............................... 39 7.1. Внешнее определение функции ..................... 39 7.2. Внешние определения данных ...................... 40 8. ОБЛАСТЬ ДЕЙСТВИЯ ИДЕНТИФИКАТОРОВ .................. 40 8.1. Лексическая область действия .................... 41 8.2. Область действия внешних идентификаторов ........ 42 8.3. Неявные описания ................................ 42 9. ПРЕПРОЦЕССОР ЯЗЫКА 'СИ' ........................... 43 9.1. Замена лексем ................................... 43 9.2. Включение файлов ................................ 44 9.3. Условная компиляция ............................. 45 9.4. Команда #line ................................... 45 10. ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ О ТИПАХ ................. 46 10.1. Структуры и объединения ......................... 46 10.2. Функции ......................................... 47 10.3. Массивы, указатели и индексация ................. 47 10.4. Явные преобразования указателей ................. 48 11. КОНСТАНТНЫЕ ВЫРАЖЕНИЯ ............................. 49 12. СООБРАЖЕНИЯ О ПЕРЕНОСИМОСТИ ....................... 49 12.1. Анахронизмы ..................................... 51 13. СТАНДАРТНАЯ БИБЛИОТЕКА ВВОДА И ВЫВОДА ............. 51 13.1. Обращение к стандартной библиотеке .............. 52 13.2. Стандартный ввод и вывод ........................ 52 13.3. Форматный вывод - функция printf ................ 53 13.4. Форматный ввод - функция scanf .................. 55 13.5. Форматное преобразование в памяти ............... 58 -87- 13.6. Доступ к файлам ................................. 59 13.7. Обработка ошибок - stderr и exit .............. 61 13.8. Ввод и вывод строк .............................. 62 13.9. Функция ungetc .................................. 62 13.10.Разные стандартные функции ...................... 62 13.10.1.Управление памятью ............................ 62 13.10.2.Стандартные функции языка Си .................. 63 14. ВЗАИМОДЕЙСТВИЕ С ОПЕРАЦИОННОЙ СИСТЕМОЙ ............ 63 14.1. Подготовка программ на Си в ОС ДЕМОС ............ 64 14.2. Доступ к аргументам команды ..................... 64 15. ИНТЕРФЕЙС СИСТЕМЫ ДЕМОС ........................... 66 15.1. Ввод/вывод ...................................... 66 15.1.1. Дескрипторы файлов ............................ 66 15.1.2. Низкоуровневый ввод/вывод. .................... 67 15.1.3. Открытие, создание, закрытие и удаление ....... 68 15.1.4. Произвольный доступ - lseek ................... 69 15.2. Управление процессами ........................... 70 15.2.1. Функция system ................................ 71 15.2.2. Вызов программы на низком уровне - execl ...... 71 15.2.3. Порождение нового процесса - fork ............. 71 15.2.4. Канал межпроцессной связи ..................... 73 15.3. Сигналы и прерывания ............................ 74 16. Сводка синтаксических правил ...................... 76 16.1. Выражения ....................................... 76 16.2. Описания ........................................ 78 16.3. Операторы ....................................... 80 16.4. Внешние определения ............................. 81 16.5. Препроцессор .................................... 82 17. Примеры программ на Си ............................ 82 -88-