Автор: Моли Б.  

Теги: программирование   linux   unix  

ISBN: 0-13-008396-8

Год: 2004

Текст
                    Брюс Моли
Unix/Linux
Теория и практика программирования
Перевод с английского
КУДИЦ-ОБРАЗ
МОСКВА • 2004


ББК 32.973-018.2 Моли Б. Unix®/Linux: теория и практика программиррвания. Пер. с англ. - М: КУДИЦ-ОБРАЗ, 2004. - 576 с. Книга посвящена вопросам системного программирования в среде Unix. Излагаемый материал является общим для всех разновидностей систем Unix. Теоретический материал сопровождается примерами реальных программ и большим количеством тем для обсуждения и самостоятельной разработки. Книга будет полезна прежде всего студентам, а также всем, кто программирует в среде Unix и хочет наилучшим образом использовать инструментальные возможности системы. ISBN 0-13-008396-8 ISBN 5-93378-087-1 Брюс Моли Unix®/Linux: теория и практика программирования Учебно-справочное издание Корректор М. Матёкин Перевод с англ. В. Д. Никитин Научный редактор Л. И. Шустова Лицензия ЛР № 071806 от 02.03.99. НОУ «ОЦ КУДИЦ-ОБРАЗ», 119034, Москва, Гагаринский пер., д. 21, стр. 1. Тел.: 333-82-11, ok@kudits.ru Подписано в печать 12.02.2004. Формат 70x100/16. Печать офсетная. Усл. печ. л. 46,4. Тираж 2000. Заказ 4227 Отпечатано с готовых диапозитивов в ООО «Типография ИПО профсоюзов Профиздат», 109044, Москва, Крутицкий вал, 18. ISBN 0-13-008396-8 ISBN 5-93378-087-1 © НОУ «ОЦ КУДИЦ-ОБРАЗ», 2004 Авторизованный перевод с англоязычного издания, озаглавленного UNDERSTANDING UNIX/LINUX PROGRAMMING, 1st Edition by MOLAY, BRUCE, опубликованного Pearson Education, Inc, под издательской маркой Prentice Hall, Copyright © 2003 by Pearson Education, Inc. All rights reserved. No part of this book may be reproduced or transmitted in any forms or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education Inc. Все права защищены. Никакая часть этой книги не может воспроизводиться или распространяться в любой форме или любыми средствами, электронными или механическими, включая фотографирование, магнитную запись или информационно-поисковые системы хранения информации без разрешения от Pearson Education, Inc Русское издание опубликовано издательством КУДИЦ-ОБРАЗ, © 2004
Предисловие Понимание Unix программирования Что такое UNIX? Я написал эту книгу, чтобы объяснить, как работает Unix, и показать, как нужно писать системны^ программы для Unix. Unix, развиваясь более тридцати лет, стал богаче, но ненамного сложнее. Дли нее всё также остаются справедливыми фундаментальная структура и принципы проекта. Помимо того, что вам станут понятны структура, принципы и история системы, вы можете читать, расширять и добавлять знания, касающиеся программирования в Unix, знания, которые рассредоточены в обширной литературе. Вам будет представлена возможность и немного поразвлечься. Для того, чтобы донести суть идей я преподношу их в книге в различных формах: в форме картинок, используя аналогии, применяя псевдокод и реальный код, используя эксперименты, упражнения, и анекдоты. Эти объяснения и факты брались из реальных, полезных задач и проектов. Кому будет полезна эта книга? Вы должны иметь навык программирования в С. Если Вы обладаете навыком работы в C++, то вы быстро адаптируетесь и будете отслеживать предлагаемые коды. Вы должны знать о массивах, структурах, указателях, связанных списках и должны понимать как использовать эти элементы при написании программных кодов. От вас не требуется знания особенностей использования Unix или знания внутренней структуры Unix. Каждую главу мы будем начинать с представления Unix с пользовательского уровня. Вопрос "Что делает этот механизм?", поставленный на пользовательском уровне, неизбежно приводит к вопросу системного уровня "Как это работает?" Вам нужно иметь доступ к системе Unix и подготовиться к тому, что потребуется иногда рисковать. > Зачем это мне? Эта книга дает теоретическое представление о компонентах системы Unix с позиций, что они делают, дает теорию с позиций, как они работают, и как следует программировать, используя эти компоненты. Вы также увидите, как можно объединять все эти компоненты, чтобы получить понятную и ясную операционную систему.
6 Благодарности Эта книга базируется на материале,курса лекций Системное Программирование в Unix, который я читал с 1990 в Harvard Extension School. Студенты, как при оценках курса, так и позже, по электронной почте писали, что дал им этот курс. Так один студент сообщил, что курс дал ему " ключи к королевству." Он понял Unix на пользовательском, программистском и теоретическом уровнях в достаточной степени для того, чтобы почувствовать все это вместе и применить в отношении большинства из возникающих проблем. Это напоминает подготовку врачей, когда студенты медики учатся работать с реальными проблемами. Другой студент, один из тех , кто он поставил целью стать лидером проекта OSF (Open Software), сказал, что курс научил его идеям и позволил получить профессиональную подготовку, необходимую для этой работы. В отношении какой версии Unix написана книга? Материал распространим по отношению большинства систем Unix, включая GNU/Linux. В книге внимание сосредоточено на структуре и подходах, из которых сформированы основы всех версий Unix. Изложение не фокусируется на специфичных отличиях между отдельными диалектами. Если были поняты основные идеи, то можно легко изучить и эти детали. / Благодарности Появление этой книги стало возможным благодаря помощи многих людей. Я благодарен Петра Рехтеру (Prentice-Hall) за предоставление возможности издания и руководство проектом, а также благодарен Грегори Даллесу за работу со мной по иллюстрации книги, предложения этой возможности и для руководства Я благодарен рецензентам книги за их внимательную работу, за замечания, способствующие улучшению книги, и конкретные предложения: Бену Абботу, Джону Б. Коннели, Геофу Сацлайфу, Луису Таберу, Сэму Р. Тангиаху и Лоуренсу Б.Уэлсу. Я благодарен Пегги Бастаманту и Амит Чаттержи за предоставление кардинальной информации о графическом программном обеспечении. Я благодарю Юрико Кувабара за несчетное число бесед, за моральную и практическую поддержку в этом проекте. Я благодарен тем многим студентам и преподавателям, которые были заняты в курсе Системное Программирование в Unix, чьи вопросы и замечания в аудиторных дискуссиях и при проведении консультаций помогли оформлению схем, объяснений, метафор и образов, использованных в этой книги. Особую благодарность выношу Ларри деЛюка, который работал в качестве ассистента по курсу, и за материал, который был изложен в главе 13.
Содержание Глава 1 Системное программирование в Unix. Общие представления > 24 1.1. Введение . 24 1.2. Что такое системное программирование? ........24 1.2.1. Простая модель программы 24 1.2.2. Реальность 25 1.2.3. Роль операционной системы 26 1.2.4. Поддержка сервиса для программ 27 1.3. Понимание системного программирования 28 1.3.1. Системные ресурсы 28 1.3.2. Наша цель: понимание системного программирования 29 1.3.3. Наш метод: три простых шага ... 29 1.4. UNIX с позиций пользователя 30 1.4.1. Что делает Unix? 30 1.4.2. Вхождение в систему — запуск программ—выход из системы 30 1.4.3. Работа с каталогами 32 1.4.4. Работа с файлами 34 1.5. Расширенное представление об UNIX 36 1.5.1 Взаимодействие (связь) между людьми и программами 36 1.5.2.Турниры по игре в бридж через Интернет ..37 1.5.3. be: секреты настольного калькулятора в Unix 38 1.5.4. От системы bc/dc к Web .41 1.6. Могу ли я сделать то же самое? 41 1.7. Еще несколько вопросов и маршрутная карта ...49 1.7.1. О чем пойдет теперь речь? 49 1.7.2. А теперь - карта 49 1.7.3 Что такое Unix? История и диалекты 50 Заключение 51 Глава 2 Пользователи, файлы и справочник. Что рассматривать в первую очередь? 52 2.1. Введение 52 2.2. Вопросы, относящиеся к команде who 53 2.2.1. Программы состоят из команд , 53 2.3. Вопрос 1: Что делает команда who? 54 2.3.1. Обращение к справочнику 54 2.4 Вопрос 2: Как работает команда who? v56 2.4.1. Мы теперь знаем, как работает who 60
8 Содержание 2.5 Вопрос 3: Могу ли я написать who? . 60 2.5.1. Вопрос: Как я буду читать структуры из файла? ...61 2.5.2. Ответ: Использование open, read и close 62 2.5.3. Написание программы whol.c 65 2.5.4,Отображение записей о вхождениях в систему 65 2.5.5. Написание версии who2.c 67 2.5.6. Взгляд назад и взгляд вперед 72 2.6. Проект два: Разработка программы ср (чтение и запись) .....73 2.6.1. Вопрос 1: Что делает команда ср? 73 2.6.2. Вопрос 2: Как команда ср создает файл и как пишет в него? 73 2.6.3. Вопрос 3: Могу ли я написать программу ср? : 74 2.6.4. Программирование в Unix кажется достаточно простым 77 2.7. Увеличение эффективности файловых операций ввода/вывода: Буферирование 77 2.7.1. Какой размер буфера следует считать лучшим? 77 2.7.2 Почему на системные вызовы требуется тратить время? .78 2.7.3. Означает ли, что наша программа who2.c неэффективна? 79 2.7.4. Добавление буферирования к программе who2.c 80 2.8. Буферизация и ядро 83 2.8.1. Если буферизация столь хороша, то почему ее не использует ядро? 83 2.9. Чтение файла и запись в файл 84 2.9.1. Выход из системы: Что происходит? : 84 2.9.2. Выход из системы: Как это происходит 85 2.9.3. Смещение текущего указателя: lseek 86 2.9.4. Кодирование выхода из системы через терминал 87 2.10. Что делать с ошибками системных вызовов? 88 Заключение 90 Исследования ..9J Программные упражнения 92 Проекты 93 Глава 3 Свойства каталогов и файлов при просмотре с помощью команды Is 95 3.1. Введение 95 3.2. Вопрос 1: Что делает команда Is? 96 3.2.1. Команда Is выводит список имен файлов и оповещает об атрибутах файлов 96 3.2.2. Получение листинга о других каталогах, получение информации о других файлах .; 96 3.2.3. Наиболее употребимые опции 97 3.2.4. Первый ответ: Итоговые замечания 97 3.3. Краткий обзор дерева файловой системы 97
Содержание 9 3.4. Вопрос 2: Как работает команда Is? ;.,• 98 3.4.1. Что же такое каталог, в конце концов? , 98 3.4.2. Работают ли системные вызовы open, read и close в отношении каталогов? 99 3.4.3. Хорошо, хорошо. Но как же мне прочитать каталог? 100 3.5. Вопрос 3: Могу ли я написать Is? ....102 3.5.1. Что еще нужно делать? 103 3.6. Проект 2: Написание версии Is -.1 ¦, 104 3.6.1. Вопрос 1: Что делает Is -1? , 104 3.6.2. Вопрос 2: Как работает Is -1? 105 3.6.3. Ответ: Системный вызов stat получает информацию о файле ...105 3.6.4. Какую еще информацию можно получить с помощью системного вызова stat? 106 3.6.5. Чего мы достигли? ,.,....., 108 3.6.6. Преобразование числового значения поля mode в символьное значение 108 3.6.7. Преобразования числового представления идентификаторов собственника/группы в строковое представление .112 3.6.8. Объединение всего вместе: ls2.c 115 3.7. Три специальных разряда , 119 3.7.1. Разряд Set-User-ID 119 3.7.2 Разряд Set-Group-ID 121 3.7.3 Разряд Sticky Bit 121 3.7.4. Специальные разряды и Is -1 122 3.8. Итоги для команды Is 122 3.9. Установка и модификация свойств файла 123 3.9.1. Тип файла 123 3.9.2. Разряды прав доступа и специальные разряды 123 3.9.3. Число ссылок на файл 124 3.9.4. Собственники группа для файла 125 3.9.5. Размер файла 126 3.9.6. Время последней модификации и доступа 126 3.9.7. Имя файла * 127 Заключение 127 Исследования -. 128 Программные упражнения 130 Проекты 132 Глава 4 Изучение файловых систем. Разработка версии pwd 133 4.1. Введение 133 4.2. Пользовательский взгляд на файловую систему 134 4.2.1. Каталоги и файлы ., 134 4.2.2. Команды для работы с каталогами 134
10 Содержание 4.2.3. Команды для работы с файлами 135 4.2.4. Команды для работы с деревом 136 4.2.5. Практически нет пределов на древовидную структуру 137 4.2.6. Итоговые замечания по файловой системе Unix 137 4.3. Внутренняя структура файловой системы UNIX 137 4.3.1. Абстракция 0: От пластин к разделам ; 138 4.3.2. Абстракция 1: От плат к массиву блоков 138 4.3.3. Абстракция 2: От массива блоков к дереву разделов 138 4.3.4. Файловая система с практических позиций: Создание файла ...139 4.3.5. Файловая система с практических позиций: Как работают каталоги 141 4.3.6. Файловая система с практических позиций: Как работает команда cat 142 4.3.7 Inodes и большие файлы : 143 4.3.8. Варианты файловых систем в Unix 145 4.4. Понимание каталогов 145 4.4.1. Понимание структуры каталога 146 Реальное значение фразы "Каталог содержит подкаталоги" 148 4.4.2. Команды и системные вызовы для работы с деревьями каталогов. 149 4.5. Разработка программы pwd ;. 153 4.5.1. Как работает команда pwd? 153 4.5.2. Версия команды pwd 154 4.6. Множественность файловых систем: Дерево из деревьев 156 4.6.1 Точки монтирования 157 4.6.2. Дублирование номеров Inode и связей между устройствами ....158 4.6.3. Символические ссылки: Панацея или блюдо спагетти? 159 Заключение ».. 160 Исследования 161 Программные упражнения 164 Проекты 164 Глава 5 Управление соединениями. Изучение stty 165 5.1. Программирование устройств 166 5.2. Устройства подобны файлам 166 5.2.1. Устройства имеют имена файлов 166 5.2.2. Устройства и системные вызовы 167 5.2.3. Пример: Терминалы аналогичны файлам k 167 5.2.4 Свойства файлов устройств 168 5.2.5. Разработка команды write 169 5.2.6. Файлы устройств и Inodes 170 5.3. Устройства не похожи на файлы 171 5.3.1. Атрибуты соединения и контроль 172
Содержание 11 5.4. Атрибуты дисковых соединений 173 5.4.1. Атрибут 1: Буферизация : » 173 5.4.2. Атрибут 2: Режим Auto-Append 174 5.4.3. Управление файловыми дескрипторами с помощью системного вызова open '..< 177 5.4.4. Итоговые замечания о дисковых соединениях 178 5.5. Атрибуты терминальных соединений 178 5.5.1. Терминальный ввод/вывод не такой, как он кажется 178 5.5.2. Драйвер терминала ;180 5.5.3. Команда stty 181 5.5.4. Программирование драйвера терминала: Установки 182 5.5.5. Программирование драйвера терминала: Функции 182 5.5.6. Программирование драйвера терминалов: Флаги 184 5.5.7. Программирование драйвера терминала: Примеры программ ..186 5.5.8. Итоговые замечания по соединениям с терминалами 189 5.6. Программирование других устройств: ioctl .......190 5.7. О небо! Это файл, это устройство, это поток! 190 Заключение 191 Исследования 193 Программные упражнения , .195 Проекты 197 Глава 6 Программирование дружественного способа управления терминалом и сигналы 198 6.1. Инструментальные программные средства 198 6.2. Режимы работы драйвера терминала 200 6.2.1. Канонический режим: Буферизация и редактирование 200 6.2.2. Неканоническая обработка .202 6.2.3. Итоговые замечания по режимам терминала 203 6.3. Написание пользовательской программы: play_again.c 204 6.3.1. Неблокируемый ввод: play__again3.c .210 6.4. Сигналы ., 214 6.4.1. Что делает управляющая последовательность Ctrl-C 215 6.4.2. Что такое сигнал? 215 6.4.3. Что может процесс сделать с сигналом? 217 6.4.4. Пример обработчика сигнала , 218 6.5 Подготовка к обработке сигналов: play__again4.c ...221 6.6. Процессы смертны ., 223 6.7. Программирование для устройств ,.. 223 Заключение 224 Исследования .224 Программные упражнения .225
12 Содержание Глава 7 Событийно-ориентированное программирование. Разработка видеоигры.... ....228 7.1. Видеоигры и операционные системы 228 7.2 Проект: Разработка pong-игры в настольный теннис для одного игрока , 231 7.3. Программирование пространства: Библиотека curses 231 7.3.1. Введение в curses 231 7.3.2. Внутренняя архитектура curses: Виртуальный и реальный экраны 234 7.4. Программирование времени: sleep ....: 235 7.5. Программирование времени .1: ALARMS .238 7.5.1. Добавление задержки: sleep 238 7.5.2. Как работает sleep(): Использование alarms в Unix 238 7.5.3. Планирование действий на будущее 241 7.6. Программирование времени II: Интервальные таймеры 241 7.6.1. Добавление улучшеной задержки: usleep 241 7.6.2. Три вида таймеров: реальные, процессные и профильные ........241 7.6.3. Два вида интервалов: начальный и период .......242 7.6.4 Программирование с помощью интервальных таймеров 243 7.6.5. Сколько часов можно иметь на компьютере? 246 7.6.6. Итоговые замечания по таймерам 248 7.7. Управление сигналами I: Использование signal 248 7.7.1. Управление сигналами в старом стиле 248 7.7.2. Управление множеством сигналов 249 7.7.3. Тестирование множества сигналов 251 7.7.4. Слабые места схемы управления множеством сигналов 253 7.8. Управление сигналами II: sigaction 254 7.8.1. Управление сигналами: sigaction 254 7.8.2. Заключительные замечания по сигналам 257 7.9. Предотвращение искажений данных 257 7.9.1. Примеры, иллюстрирующие искажение данных 258 7.9.2. Критические секции 258 7.9.3. Блокирование сигналов: sigprocmask и sigsetops 259 7.9.4. Повторно входной код: Опасность рекурсии .....260 7.9.5. Критические секции в видеоиграх 261 7.10. kill: Посылка сигнала процессом 261 7.11. Использование таймеров и сигналов: видеоигры 262 7.11.1. bounceld.c: Управляемая анимация на строке 263 7.11.2. bounce2d.c: Двухмерная анимация 266 7.11.3. Вся игра целиком , 271 7.12. Сигналы при вводе: Асинхронный ввод/вывод 271 7.12.1. Организация перемещения с помощью асинхронного ввода/вывода ; 272 7.12.2 Метод 1: Использование 0_ASYNC 272
Содержание 13 7.12.3. Метод 2: Использование aiojread ....274 7.12.4. А нужно ли нам производить асинхронное чтение для организации перемещения? 277 7.12.5. Асинхронный ввод, видеоигры и операционные системы 277 Заключение 278 Исследования 278 Программные упражнения 280 Проекты .. : 282 Глава 8 Процессы и программы. Изучение sh 283 8.1. Процессы = программы в исполнении ..,..; 283 8.2. Изучение процессов с помощью команды ps 284 8.2.1. Системные процессы 286 8.2.2. Управление процессами и управление файлами 287 8.2.3. Память компьютера и память для программ ..288 8.3. SHELL: Инструмент для управления процессами и программами .289 8.4. Как SHELL запускает программы на исполнение ...290 8.4.1. Основной цикл shell 290 8.4.2. Вопрос 1: Каким образом производится запуск программы? ....292 8.4.3. Вопрос 2: Как получить новый процесс? 296 8.4.4. Вопрос 3: Как процесс-отец ожидает окончания дочернего процесса? 300 8.4.5. Итог: Как Shell запускает программы на исполнение 306 8.5. Создание shell: psh2.c 307 8.5.1. Сигналы и psh2.c 310 8.6. Защита: программирование процессов 311 8.7. Дополнение относительно EXIT и EXEC 312 8.7.1. Окончание процесса: exitH_exit 312 8.7.2. Семейство вызовов exec 313 Заключение 314 Исследования , 315 Программные упражнения 317 Глава 9 Программируемый shell. Переменные и среда shell 318 9.1. Программирование в среде SHELL 318 9.2. SHELL-скрипты: что это такое и зачем? 319 9.2.1. Shell скрипт - это пакет команд ...319 9.3. smshl-Разбор текста командной строки . 321 9.3.1. Замечания относительно smshl 328 9.4. Поток управления в SHELL: почему и как? 328 9.4.1. Что делает if? 328 9.4.2. Как работает if.... 329 9.4.3. Добавление if к smsh 330 9.4.4. smsh2.c: Модифицированный код 331
14 Содержание 9.5. SHELL-переменные: локальные и глобальные 336 9.5.1. Использование переменных shell ...J 337 9.5.2. Система памяти для переменных 338 9.5.3. Команды для добавления переменных: встроенные команды ...339 9.5.4. Как все работает? 341 9.6. Среда: персонализированные установки 342 9.6.1. Использование среды 343 9.6.2. Что собой представляет среда? Как она работает? 344 9.6.3. Добавления средств по управлению средой в smsh 346 9.6.4. Код varlib.c ...349 9.7. Общие замечания о SHELL 353 Заключение ........ 353 Исследования ,.. 354 Программные упражнения .i. 354 Глава 10 Перенаправление ввода/вывода и программные каналы 356 10.1. SHELL-программирование 356 10.2. Приложение SHELL: наблюдение за пользователями 357 10.3. Сущность стандартного ввода/вывода и перенаправления 359 10.3.1. Фактор 1: Три стандартных файловых дескриптора 359 10.3.2. Соединения по умолчанию: терминал 360 10.3.3. Вывод происходит только на stdout 360 10.3.4. Shell, отсутствие программы, перенаправление ввода/вывода 360 10.3.5. Соглашения по перенаправлению ввода/вывода 362 10.3.6. Фактор 2: Принцип "Первый доступный,самый малый по значению дескриптор" 362 10.3.7. Синтез 363 10.4. Каким образом можно подключить stdin к файлу 363 10.4.1. Метод 1: Закрыть, а затем открыть 363 10.4.2. Метод 2: open..close..dup..close 365 10.4.3. Обобщенная информация о системном вызове dup 367 10.4.4. Метод 3: open..dup2..close 368 10.4.5. Shell перенаправляет stdin не для себя, а для других программ ... 368 10.5. Перенаправление ввода/вьюода для других программ: who > userlist 368 10.5.1. Итоговые замечания по перенаправлению стандартных потоков в файлы 372 10.6. Программирование программных каналов 372 10.6.1. Создание программного канала 373 10.6.2. Использование fork для разделения программного канала 375 10.6.3. Финал: Использование pipe, fork и exec 377 10.6.4. Технические детали: Программные каналы не являются файлами 379
Содержание 15 Заключение 381 Основные идеи 381 Исследования 381 Программные упражнения * 382 Глава 11 Соединение между локальными и удаленными процессами. Серверы и сокеты 384 11.1. Продукты и сервисы 385 11.2. Вводная метафора: интерфейс автомата для получения напитка ..385 11.3. be: калькулятор в UNIX .: 386 11.3.1. Кодирование be: pipe, fork, dup, exec 388 11.3.2. Замечания, касающиеся сопрограмм ....: 39Г 13.3.3. fdopen: файловые дескрипторы становятся похожими на файлы . 392 11.4. рореп: делает процессы похожими на файлы 392 11.4.1. Что делает функция рореп 392 11.4.2. Разработка функции рореп: использование fdopen 394 11.4.3. Доступ к данным: файлы, программный интерфейс API и сервера '. 396 11.5. Сокеты: соединения с удаленными процессами 397 11.5.1. Аналогия: "....время равно..." 397 11.5.2. Время Internet, DAP и метеорологические серверы 401 11.5.3. Списки сервисов: широко известные порты 402 11.5.4. Разработка timeserv.c: сервер времени 403 11.5.5. Проверка работы программы timeserv.c 407 11.5.6 Разработка программы timeclnt.c: клиент времени 408 11.5.7. Проверка работы программы timeclnt.c 410 11.5.8. Другие серверы: удаленный Is 411 11.6. Программные демоны 416 Заключение 416 Исследования 417 Программные упражнения 417 Глава 12 Соединения и протоколы. Разработка Web-сервера 421 12.1. В центре внимания - сервер 421 12.2. Три основные операции 422 12.3. Операции 1 и 2: установление соединения 422 12.3.1. Операция 1: установка сокета на сервере 422 12.3.2. Операция 2: соединение с сервером 423 12.3.3. socklib.c 424 12.4. Операция 3: взаимодействие между клиентом и сервером 425 12.4.1. timeserv/timeclnt, использующие socklib.c 426 12.4.2. Вторая версия сервера: использование fork 427
16 Содержание 12.4.3. Вопрос по ходу проектирования: делать самому и делегировать работу другому? .428 12.5. Написание Web-сервера 430 12.5.1. Что делает Web-сервер .. 430 12.5.2. Планирование работы нашего Web-сервера 431 12.5.3. Протокол Web-сервера 431 12.5.4. Написание Web-сервера 433 12.5.5. Запуск Web-сервера 435 12.5.6. Исходный код webserv 436 12.5.7. Сравнение Web-серверов .440 Заключение .. 440 Исследования ....441 Программные упражнения 441 Проекты , 442 Глава 13 Программирование с использованием дейтаграмм. Лицензионный сервер 443 13.1. Программный контроль 444 13.2. Краткая история лицензионного контроля .445 13.3. Пример, не связанный с компьютерами: управление использованием автомобилей в компании 445 13.3.1. Описание системы управления ключами от автомобилей 446 13.3.2. Управление автомобилями в терминах модели клиент/сервер 446 13.4. Управление лицензией 447 13.4.1. Система лицензионного сервера: что делает сервер? 447 13.4.2. Система лицензионного сервера: как работает сервер? 448 13.4.3. Коммуникационная система 450 13.5. Сокеты дейтаграмм 450 13.5.1 Потоки (streams) и дейтаграммы ....450 13.5.2. Программирование дейтаграмм 452 13.5.3. Обобщение информации о sendto и recvfrom 457 13.5.4. Ответ на принятые дейтаграммы 458 13.5.5. Итог по теме дейтаграмм 459 13.6. Лицензионный сервер. Версия 1.0 .'. 460 13.6.1. Клиент. Версия 1 461 13.6.2. Сервер. Версия 1 465 13.6.3. Тестирование Версии 1 469 13.6.4. Что еще нужно сделать? 470 13.7. Программирование с учетом существующих реалий 470 13.7.1. Управление авариями в клиенте 470 13.7.2. Управление при возникновении аварийных ситуаций на сервере 473 13.7.3. Тестирование версии 2 476
Содержание . 17 13.8. Распределенные лицензионные сервера 478 13.9. UNIX-сокеты доменов 480 13.9.1. Имена файлов, как адреса сокетов 480 13.9.2. Программирование с использование сокетов доменов 481 13.10. Итог: сокеты и сервера ! 483 Заключение 484 Исследования 484 Программные Упражнения 486 Проекты 487 Глава 14 Нити. Параллельные функции .488 14.1. Одновременное выполнение нескольких нитей 488 14.2. Нить исполнения 489 14.2.1. Однонитьевая программа 489 14.2.2. Мультинитьевая программа .....491 14.2.3 Обобщенная информация о функции pthreadcreate 493 14.3. Взаимодействие нитей .. 494 14.3.1. Пример 1: incrprint.c 494 14.3.2. Пример 2: twordcount.c 495 14.3.3. Взаимодействие между нитями: итог 502 14.4. Сравнение нитей с процессами 503 14.5. Уведомление для нитей 504 14.5.1. Уведомление для центральной комиссии о результатах выборов .505 14.5.2. Программирование с использованием условных переменных 506 14.5.3. Функции для работы с условными переменными 510 14.5.4. Обратимся опять к Web 510 14.6. Web-сервер, который использует механизм нитей 511 14.6.1 Изменения в нашем Web-сервере 511 14.6.2. При использовании нитей появляются новые возможности ....511 14.6.3 Предотвращение появления зомби для нитей: отсоединение нитей 511 14.6.4. Код 512 14.7. Нити и анимация 516 14.7.1. Преимущества нитей 517 14.7.2 Программа bounceld.c, построенная с использованием нитей .518 14.7.3. Множественная анимация: tanimate.c 519 14.7.4. Mutexes и tanimate.c 523 14.7.5. Нить для curses 524 Заключение 525 Исследования • , ....526 Программные упражнения 526
18 Содержание Глава 15 Средства межпроцессного взаимодействия (IPC). Как можно пообщаться? 529 15.1 Выбор при программировании ...530 15.2. Команда talk: Чтение многих входов 530 15.2.1. Чтение из двух файловых дескрипторов .:....531 15.2.2. Системный вызов select 532 15.2.3. select и talk 535 15.2.4. select или poll 535 15.3. Выбор соединения 535 15.3.1. Одна проблема и три ее решения 535 15.3.2. Механизм IPC на основе использования файлов 536 •15.3.3. Именованные программные каналы 537 15.3.4. Разделяемая память , 539 15.3.5. Сравнение методов коммуникации 541 15.4. Взаимодействие и координация процессов 543 15.4.1. Блокировки файлов ...543 15.4.2. Семафоры , ;.546 15.4.3. Сравнение сокетов и каналов FIFO с разделяемой памятью ...554 15.5. Спулер печати 554 15.5.1. Несколько писателей, один читатель 554 15.5.2. Модель клиент/сервер 556 15.6. Обзор средств IPC .557 15.7. Соединения и игры ...560 Заключение 561 Исследования ! ..562 Программные упражнения 562 Предметный указатель 563
Список иллюстраций 1.1 Прикладная программа в компьютере 25 1.2 Как прикладные программы рассматривают пользовательский ввод/вывод 25 1.3 Реальность: много пользователей, программ и устройств 26 1.4 Как все это соединено? . 26 1.5 Операционная система - это программа 27 1.6 Ядро управляет всеми соединениями 27 1.7 Вхождение пользователя в систему 31 1.8 Часть дерева каталогов 32 1.9 Четыре человека играют в бридж через Интернет 37 1.10 Стол для бриджа на серверном компьютере 37 1.11 Отдельные программы посылают сообщения друг другу 38 1.12 Программы посылают сообщения друг другу 40 1.13 Отдельные программы посылают сообщения друг другу 41 1.14 more читает со стандартного ввода 45 1.15 Программа who читает пользовательский ввод с терминала 46 1.16 Соединение с терминалом имеет настройки 48 1.17 Диаграмма основной структуры системы Unix 49 2.1 Пользователи, файлы, процессы и ядро ; 53 2.2 Поток данных для команды who 60 2.3 Дескриптор файла - это соединение с файлом 63 2.4 Копирование файлов посредством чтения и записи 75 2.5 Поток управления при работе системных вызовов 78 2.6 Поток управления при работе системных вызовов ;.... 80 2.7 Буферизация дисковых данных в ядре 83 2.8 Каждый открытый файл имеет текущий указатель 86 3.1 Дерево каталогов 98 3.2 Чтение содержимого каталога 101 3.3 Чтение статусной информации о файле с помощью stat 105 3.4 Представление кодов типа файла и прав доступа 108 3.5 Преобразования десятичного представления в двоичное 109 3.6 Использование двоичной маски 110 3.7 Диск содержит файлы, каталоги и статусную информацию о них 128 4.1 Дерево каталогов 134 4.2 Две связи к одному и тому же файлу 136 4.3 Нумерация дисковых блоков 138 4.4 Три области файловойгсистемы 139 4.5 Внутренняя структура файла 140 4.6 От имени файла к дисковым блокам 142 4.7 Список распределения блоков содержится в области данных 144
20 Список иллюстраций 4.8 Две точки зрения относительно дерева каталогов 146 4.9 Имена файлов и указатели на файлы 147 4.10 Имена каталогов и указатели на каталоги 147 4.11 Перемещение файла в новый каталог 151 4.12 Составление пути текущего каталога 153 4.13 "Прививка" деревьев 157 4.14 Номера inode и файловые системы 158 4.15 Inodes, блоки данных, каталоги, указатели 161 .5.1 Inode ссылается на блоки данных или на код драйвера 171 5.2 Процесс с двумя файловыми дескрипторами 172 5.3 Обрабатывающее устройство в потоке данных 173 5.4 Модификация действия файлового дескриптора 173 5.5 Присоединение записей с помощью lseek и write 175 5.6 Чередующиеся lseek и write = хаос 175 5.7 Соединения с файлами имеют установки 178 5.8 Соединения с файлами имеют установки 178 5.9 Ядро обрабатывает данные терминала 180 5.10 Драйвер терминала является частью ядра : 180 5.11 Управление драйвером терминала с помощью tcgetattr и tcsetattr 183 5.12 Разряды и символы в составе членов termios 185 5.13 Файловые дескрипторы, соединения и драйверы 192 6.1 Три стандартных файловых дескриптора 199 6.2 То, что вы набираете, и то, что получает программа 201 6.3 Обрабатывающие уровни в драйвере терминала 202 6.4 Основные компоненты драйвера терминала 204 6.5 Ctrl-C убивает процесс исполнения программы. Программа заканчивается без восстановления 214 6.6 Как работает Ctrl-C 215 6.7 Три источника сигналов ...216 6.8 По сигналу происходит обращение к подпрограмме 219 6.9 Действие от выполнения вызова signal(SIGINT, SIGJK3N) 220 7.1 Видеоигра для одного игрока 231 7.2 Curses представляет экран в виде сетки 232 7.3 Наша первая программа с использованием curses 233 7.4 Curses поддерживает копию реального экрана 234 7.5 Изображение медленно перемещается вниз по экрану 236 7.6 Сообщение движется вперед и назад 237 7.7 Процесс устанавливает alarm, в течение которого он приостанавливает свое развитие 238 7.8 Поток управления в обработчике сигнала .. 240 7.9 Каждый процесс имеет три таймера 241 7.10 Как распределяются действия во времени? 242 7.11 Чтение и запись установок для таймера 243 7.12 Внутреннее представление интервальных таймеров ..245
Список иллюстраций 21 7.13 Секунды и микросекунды * 246 7.14 Два таймера, одни часы 247 7.15 Процесс принимает несколько сигналов : 250 7.16 Прохождение потока управления через эти функции 252 7.17 Процесс для посылки сигнала использует kill() 261 7.18 Сложное использование сигналов 262 7.19 bounceld в действии: анимация, управляемая пользователем : 263 7.20 Изменение значений через пользовательский ввод. Значения управляют действием ....; 264 7.21 Двухмерная анимация ...266 7.22 Траекториядодуглом 1/3 267 7.23 Перемещение по наклонной на один шаг за такт выглядит лучше 268 7.24 Сигналы поступают от клавиатуры и таймера 272 8.1 Процессы и программы 284 8.2 Команда ps выводит список текущих процессов 284 8.3 Три модели памяти в компьютере , 288 8.4 Пользователь обращается к shell для выполнения запуска программы 290 8.5 Распределение во времени основного цикла shell 291 8.6 execvp копирует программу в память и запускает ее на исполнение 292 8.7 Построение однострокового списка аргументов 295 8.8 fork() выполняет копирование процесса 297 8.9 Дочерний процесс исполняет код после fork() ... 298 8.10 Вызов wait переводит порождающий процесс в ожидание, пока не закончится дочерний процесс 301 8.11 Управляющий поток и коммуникация с wait() 302 8.12 Представление статусной информации о дочернем процессе в трех полях 304 8.13 Последовательность шагов в цикле shell с выполнением fork(), exec(), wait() .... 306 8.14 Логика shell в Unix 307 8.15 Сигналы от клавиатуры поступают на все присоединенные процессы .310 8.16 Вызов функций и вызов программ 312 9.1 Shell с сигналами, exit и разбором командной строки 322 9.2 Добавление потока управления командами в smsh * '. 330 9.3 Скрипт, состоящий из различных областей 331 9.4 Система памяти для переменных shell 338 9.5 Добавление к smsh встроенных команд 339 9.6 Среда - это массив указателей на строки 344 9.7 Строки из среды копируются при выполнении ехес() 345 9.8 Копирование значений из среды в переменную vartab 346 9.9 Копирование значений из vartab в новую среду 347 9.10 Добавление средств управления средой в smsh 348 10.1 Соединение вывода команды who со входом команды sort 357 10.2 Команда comm сравнивает два списка и выводит три набора строк 358 10.3 Программное средство читает входные данные и записывает результаты и сообщения об ошибках \ , 359
22 Список иллюстраций 10.4 Три специальных файловых дескриптора 360 10.5 Принцип "Первый доступный, самый малый по значению дескриптор" 362 10.6 Типичная начальная конфигурация 363 10.7 Теперь stdin закрыт . 364 10.8 Теперь stdin присоединен к файлу 364 10.9 Использование dup для перенаправления 366 10.10 Shell перенаправляет вывод у дочернего процесса ¦. 368 10.11 Процесс имеет стандартный вывод и готов выполнить fork 369 10.12 Стандартный вывод дочернего процесса был скопирован от процесса-отца 369 10.13 Дочерний процесс может закрыть свой стандартный вывод 370 10.14 Дочерний процесс открывает новый файл и получает в результате fd = 1 370 10.15 Дочерний процесс запускает на исполнение программу с новым стандартным выводом 371 10.16 Два процесса соединены с помощью программного канала 372 10.17 Программный канал 373 10.1.8 Процесс создает программный канал 374 10.19 Поток данных в программе pipedemo.c 375 10.20 Разделение программного канала 376 10.21 Поток данных между процессами 376 11.1 Напиток, который готовится сейчас или заранее? 385 11.2 Один интерфейс и разные источники 386 11.3 be и dc, работающие как сопрограммы 387 11.4 be, dc и ядро 388 11.5 fopen и рореп 393 11.6 Чтение из команды 394 11.7 Соединение с удаленным процессом 397 11.8 Служба времени 398 11.9 Процессы на различных машинах 410 11.10 Система remote Is 411 11.11 Использование рореп ("Is") для получения списка файлов из удаленных каталогов .413 12.1 Основные компоненты схемы клиент/серверного взаимодействия 422 12.2 Создание сокета на сервере 423 12.3 Соединение с сервером 423 12.4 Сервер и клиент для службы времени (версия 1) 426 12.5 Сервер выполняет fork для запуска программы date 427 12.6 Web-сервер обеспечивает удаленное выполнение Is, cat, exec 430 13.1 Лицензионный сервер дает разрешение , 444 13.2 Управление доступом к автомобилям 446 13.3 Управление доступом к программному обеспечению , 448 13.4 Передача данных с помощью пакетов в Internet 450 13.5 Коммуникации можно устанавливать либо с помощью соединения, либо без соединения 451 13.6 Три составные части дейтаграммы 452 13.7 Использование sendto и reevfrom 453
Список иллюстраций 23 13.8 Клиент уносит билет с собой в могилу 470 13.9 Использование alarm для планирования процедуры восстановления билетов ... 471 13.10 Сервер повторно стартует после своего краха 474 13.11 Клиент проверяет легальность билета 474 13.12 Клиент проверяет легальность вначале и далее 475 13.13 Идентификаторы процессов (PIDs) не являются уникальными в сети 478 13.14 Процесс не может послать сигнал на другой хост 479 13.15 Работают локальные копии lserv 479 14.1 Единственная нить исполнения 490 14.2 Несколько нитей исполнения , 491 14.3 Две нити разделяют глобальную переменную 495 14.4 Общий счетчик для двух нитей 496 14.5 Две нити инкремёнтируют один и тот же счетчик 497 14.6 Две нити используют mutex для разделения счетчика 499 14.7 Каждая нить имеет указатель на собственную структуру •...;.... 502 14.8 Использование почтового ящика с замком для передачи данных 505 14.9 Использование блокируемой переменной для передачи данных 507 14.10 Анимируемое изображение и управление с помощью клавиатуры 517 14.11 Нить анимации и нить клавиатуры , 517 14.12 Множество сообщений с изменением направления 520 14.13 Отдельная нить взаимодействует с curses 524 15.1 Команда talk при работе в сети .... 530 15.2 Команда talk 531 15.3 Три файловых дескриптора \ 535 15.4 У одного процесса есть информация, которая необходима другому процессу ... 536 15.5 Три пути для передачи данных 536 15.6 Каналы FIFO являются независимыми от процессов 537 15.7 Два процесса разделяют блок памяти ...539 15.8 Семафорный набор: num_readers 547 15.9 Несколько источников данных, один принтер 554 15.10 Получение файла для принтера .....555 15.11 Клиент/серверная система печати. 556
Глава 1 Системное программирование в Unix. Общие представления ЦЕЛИ Идеи Система Unix содержит пользовательские программы и системное ядро. Ядро Unix - это набор специальных подсистем. Ядро управляет всеми программами и организует доступ к ресурсам. Коммуникации между процессами являются важнейшим аспектом для Unix - программ. Что такое системное программирование? Команды be more 1.1. Введение Что такое системное программирование? Что такое системное программирование в Unix? Что мы будем рассматривать в этой книге? В этой главе мы, образно говоря, нарисуем общую картину в соответствии с поставленными вопросами. Начнем с выяснения роли операционной системы и определения того, что означает процесс написания программ, которые работают непосредственно с операционной системой. После краткого представления общих положений мы рассмотрим Unix - программы, которые используют услуги операционной системы, и затем перейдем к написанию наших собственных версий программ. Наконец, мы рассмотрим схему, на которой представлен Unix - компьютер. Схематические представления и техника раскрытия сути программ составляют основу этой книги. 1.2. Что такое системное программирование? 1.2./. Простая модель программы Возможно, вы писали научные программы, либо финансовые программы, либо графические программы, либо программы текстовой обработки. Существуют много разновидностей программ. Большая часть программ строится в соответствии с моделью, представленной на рисунке 1.1.
1.2. Что такое системное программирование? 25 Компьютер Программа Рисунок 1.1 Прикладная программа в компьютере Программа - это некоторый код, который исполняется на компьютере. Данные.поступают на вход программы, программа выполняет некоторую обработку данных, и результирующие данные выводятся из программы. Человек может набирать данные на клавиатуре и анализировать их на экране терминала, программа способна читать данные с диска или записывать на диск, программа может посылать данные на печать на принтер. Возможны и другие варианты. В этой модели программы, которая достаточно очевидна, код выглядит следующим образом: Г копирование со стандартного ввода на стандартный вывод */ main() { int с; while((c = getchar())!=EOF) putchar(c); } Этот код соответствует визуальной модели, представленной на рисунке 1.2. '-'; ¦« "*^Л." '^ш!^ getchar() Рисунок 1.2 Как прикладные программы рассматривают пользовательский ввод/вывод Рисунок подчеркивает то обстоятельство, что клавиатура и экран имеют связь с программой. В отношении обыкновенного персонального компьютера такая модель достаточно точно воспроизводит реальность. Клавиатура и дисплей подсоединены к материнской плате. Эти компоненты соединяются с помощью обыкновенных металлических проводников. Вы можете при случае увидеть на печатной плате эти проводники, которые соответствуют направлениям линий связи на рисунке. 1.2,2. Реальность Что происходит, когда вы входите в многопользовательскую систему, подобную типичной Unix - машине? В этом случае простая модель, где клавиатура и монитор связаны с процессором (CPU), не соответствует действительности. Реальность более близка тому, что изображено на рисунке 1.3.
26 Системное программирование в Unix. Общие представления Рисунок 1.3 Реальность: много пользователей, программ и устройств В данном случае есть несколько клавиатур и дисплеев, несколько дисков, один или более принтеров, а также есть несколько программ, исполняемых одновременно. При этом программы, которые получают данные, вводимые с клавиатуры, и передают данные на дисплей или на диск, прекрасно работают. Программы могут предполагать использование простой модели и получать правильные результаты. На самом деле все гораздо сложнее. Каким-то способом все эти различные клавиатуры соединяются с различными программами. Каким-то образом строится множество связей внутри машины. Если у вас появится возможность рассмотреть материнскую плату, то увидите ли вы то, что представлено на рисунке 1.4? Вряд ли. Такие соединения были бы кошмарными. Это все просто не будет работать, поскольку различные программы сменяют друг друга по мере вхождения различных пользователей в систему и выхода их из системы. Для данного случая должна существовать другая модель, которая соответствовала бы мультипользовательскому, мультизадачному компьютеру. Рисунок 1.4 Как все это соединено? 1.2.3. Роль операционной системы Роль операционной системы сводится к управлению и защите всех ресурсов, а также к присоединению устройств к различным программам. Физический смысл этого заключается в том, что операционная система, которая реализована программно, делает то же, что и материнская плата персонального компьютера, которая реализована аппаратно. Сплетение проводов, которое было на предшествующем рисунке, заменяется моделью на рисунке 1.5.
1.Z Что такое системное программирование?. 27 тт Пользавательсков пространство Системное Рисунок 1.5 Операционная система - это программа Операционная система - это программа. Код операционной системы, аналогично коду любой исполняемой программы, располагается в памяти компьютера. В памяти находятся также и другие программы - программы, которые были написаны пользователями и запущены на исполнение. Операционная система соединяет эти программы с внешним миром. 1.2.4. Поддержка сервиса для программ После того как мы рассмотрели проблему (как можно связать множество пользователей с множеством процессов?) и возможное решение проблемы (иметь основную управляющую программу для установления всех соединений), приступим к ее рассмотрению. Начнем сначала с некоторых определений. Память компьютера предназначена для поддержания некоторого пространства для хранения программ и данных. Часть памяти компьютера, где размещается операционная система, называется системным пространством, а другая часть, где хранятся пользовательские программы, называется пользовательским пространством. Операционная система называется ядром; клавиатуры и экраны подсоединяются к компьютеру (см. рисунок 1.6). Рисунок 1.6 Ядро управляет всеми соединениями Заметим, что местом подсоединения устройств является системное пространство; таким образом, ядро является единственной программой, которая имеет доступ к этим устройствам. Пользовательские программы получают данные, обращаясь для этого к ядру. Ядро передает данные от клавиатуры к программе и пересылает данные от программы через установленное соединение на дисплей. Аналогично ядро может обеспечивать доступ к твердому диску, принтерам, сетевым картам и другим периферийным устройствам. Если программе понадобится подсоединение или управление этими устройствами, то ей необходимо обратиться с запросом к ядру.
28 Системное программирование в Unix. Общие представления Линии связи на рисунке представляют собой виртуальные соединения, которые поддерживает ядро. Ядро обеспечивает для пользовательских программ доступ к этим внешним объектам, что рассматривается как отдельные службы (сервисы). Мы теперь представляем контекст, в котором будет проходить объяснение системного программирования. Это и будет содержанием данной книги. Обычные прикладные программы могут быть написаны так, как будто они имеют непосредственное соединение с терминалами, дисками, принтерами. В этой расширенной модели работают системные программы, обеспечивая ресурсы и поддерживая службы. Мы будем изучать службы, которые поддерживает ядро, структуру этих служб, а также рассматривать, как писать программы, которые работают в этом расширенном контексте. 1.3. Понимание системного программирования Ядро обеспечивает доступ к системным ресурсам. Системные программы используют эти службы непосредственно. Что представляют собой эти службы и как мы будем изучать способы их использования? 1.3.1, Системные ресурсы Процессоры Программа представляет собой набор команд; процессор - это аппаратное устройство, которое выполняет команды. Процессор также называют обрабатывающим устройством. Некоторые компьютеры имеют несколько процессоров. Ядро назначает программы для исполнения на процессорах. Оно начинает исполнение, приостанавливает, возобновляет и заканчивает исполнение программы на процессоре. Ввод/Вывод Все данные, которые поступают на входы программ и которые вырабатываются как выходные данные программ, проходят через ядро. Данные, поступающие от пользователей, и данные, которые поступают на терминалы пользователей, также проходят через ядро. Данные, которые читаются с дисков и которые записываются на диск, проходят через ядро. Такая централизация гарантирует, что передача данных будет происходить правильно - данные попадают в необходимое место. Гарантируется эффективность - не требуется дополнительного времени, необходимого для передачи информации с одного места в другое. Гарантируется безопасность - ни один из процессов не может увидеть информацию, которая ему не предназначена. Управление процессами В Unix термин "процесс" используется для обозначения программы при ее исполнении. Процесс состоит из памяти, открытых файлов, других системных ресурсов, необходимых программе при исполнении. Новые процессы создает ядро. Ядро управляет процессами и организует их совместную работу. Память Память компьютера является ресурсом. Программы могут потребовать некую дополнительную память для хранения информации. Ядро следит за тем, какие секции памяти используют процессы, и защищает память одного процесса от возможного доступа со стороны других процессов.
1.3. Понимание системного программирования 29 Устройства К компьютеру могут быть присоединены самые разнообразные устройства. Ленточные устройства, CD-плееры, мышь, сканеры, видеокамеры - все это примеры устройств. Ядро обеспечивает доступ к устройствам и заботится обо всех сложностях управления. Когда программе необходимо получить картинку с видеокамеры, подсоединенной к компьютеру, она обращается к ядру, чтобы обеспечить доступ к этому ресурсу. Таймеры Некоторые программы зависят от времени. Они могут выполнять действия по установке временных интервалов; им может потребоваться ожидать наступления некоторого момента времени, после которого они будут что-то делать. Программам может потребоваться определение длительности выполнения неких действий. Ядро предоставляет для использования процессоров определенное число таймеров. Межпроцессные коммуникации В повседневной жизни у людей возникает потребность в установлении коммуникаций между собой. Для этого они используют телефоны, электронную почту, обыкновенную почту, радио, телевидение и другие средства для передачи информации. В вычислительной системе дри одновременном исполнении нескольких программ у процессов возникает потребность взаимодействия. Ядро поддерживает несколько способов межпроцессных коммуникаций. Такие коммуникационные системы, как телефонная сеть и почтовая служба, являются системными ресурсами. Сети Сеть, связывающая компьютеры, является расширенной формой межпроцессных коммуникаций. Сеть предоставляет возможность процессам на различных машинах обмениваться данными, даже если на этих машинах работают различные операционные системы. Сетевой доступ является службой ядра. /. 3.2. Наша цель: понимание системного программирования Мы только что ознакомились с некоторыми типами сервисов (служб) и с механизмами доступа к ресурсам в ядре, которые реализуются системными программами. Каково детальное представление каждого из типов служб? Как передавать данные от устройства к программе и обратно? Хотелось бы узнать, как работает ядро, как оно выполняет сервисные действия и как писать программы, которые могли бы использовать такие службы. 1.3.3. Наш метод: три простых шага Мы будем изучать службы Unix, используя: 1. Просмотр "реальных" программ. Мы будем изучать стандартные Unix - программы, чтобы посмотреть, что они делают и каким образом программы используются на практике. Мы посмотрим, какие системные # службы будут использоваться этими программами. 2. Изучение системных вызовов. Мы будем далее изучать системные вызовы, которые можно использовать при работе с упомянутыми службами. 3. Написание наших собственных версий. После того как поймем, как работает программа, какие системные службы она использует и как используются эти службы, мы будем способны писать наши собственные системные программы. Эти программы является расширением существующих про- гпяма/Г ним fwnrvr игттптттлпрятт. ппи ттпг/rnriftwwi* пяггмптпрнш.т<» ттпиниипи
30 Системное программирование в Unix. Общие представления Мы будем изучать системное программирование в Unix, задавая себе многократно следующие три вопроса: Кто выполняет это действие? Как выполняется это действие? Могу ли я попытаться сделать то же? 1.4. UNIX с позиций пользователя 1.4.1. Что делает Unix? Первым нашим шагом при изучении любого аспекта Unix будет получение ответа на вопрос - что делает система? Сначала ответ на этот вопрос будет относительно UNIX в целом. Как пользователь воспринимает Unix? Как система воспринимается пользователем, который садится за Unix - терминал? После беглого рассмотрения этих вопросов у нас возникнут вопросы относительно того, как это все работает. 1.4.2. Вхождение в систему—запуск программ—выход из системы Работать в Unix просто. Вы входите в систему, запускаете какие-то программы и выходите из системы. При вхождении в систему вы набираете пользовательское имя и пароль: Linux 1.2.13 (maya) (ttypl) maya login: betsy Password: _ После вхождения в систему вы запускаете программы на исполнение. Вы можете запускать различные виды программ. Можете запустить программу дня чтения и посылки электронной почты. Можете запустить программу дня расчета места расположения планет или определения фондовых показателей. Можно запускать игровые программы. Запуск программ выполняется чрезвьиайно просто. Система выводит на экран приглашение. В ответ вы набираете и вводите имя программы, которую хотели бы запустить на исполнение. Компьютер запускает программу. После выполнения этой программы система выводит на экран следующее приглашение. Даже изощренные графические десктопы следуют такому порядку действий. Приглашением является экран с иконками и меню, а нажатие кнопкой мышки на иконке или пункте меню эквивалентно набору имени команды. За графическим интерфейсом стоит программное обеспечение, которое связывает текстовые имена файлов изображений с именами программ. После окончания запущенных программ вы выполняете выход из системы (log out): $exit В зависимости от проведенных предварительно настроек для вашего входа в систему вы можете выйти из системы с помощью команды logout или при наборе на клавиатуре последовательности Ctrl-D. Как все это работает? Все выглядит достаточно просто, но что за этим стоит? Как это все работает? Что означает войти в систему! При работе с персональным компьютером используется идея персонального использования компьютера, что сравнимо с использованием семейного автомобиля. На Unix - машине в одно и то же время в систему могут входить несколько человек, даже сотни человек. Как система узнает, кто вошел в систему и где это произошло?
1.4. UNIX с позиций пользователя 31 Рассмотрим этот процесс более детально. Если ваше входное имя и пароль были восприняты при входе, то система стартует программу, которая называется shell, и свяжет вас с ней. Каждый пользователь, вошедший в систему, связывается с собственным shell-процессом. На рисунке 1.7 представлена иллюстрация вхождения пользователя в Unix - систему. Компьютер изображен в форме ящика слева, а пользователь сидит и работает с клавиатурой и экраном. Внутри компьютера находится память, где хранится ядро и пользовательские процессы. Ядро производит контроль и управление за соединением пользователя с системой. Также оно передает данные между пользователем и shell. Shell выводит на экран приглашение, по которому пользователь оповещается о готовности запустить для него некую программу. В данном примере в качестве приглашения использован знак доллара. В качестве приглашения может быть использована любая текстовая строка. Пользователь набирает имя программы, и ядро пересылает его на вход shell. Рисунок 1.7 Вхождение пользователя в систему Например, чтобы запустить программу, которая выводит на экран текущее время и дату, пользователь должен набрать такую командную строку: $ $ date Sat Jul 121:34:10 EDT 2000 Запускается программа date, она выводит дату, а затем shell выводит новое приглашение. Для запуска другой программы достаточно набрать ее имя. Во многих Unix - системах имеется программа, которая называется fortune. Вот пример ее вызова: $ fortune Algol-60 surely must be regarded as the most important programming language yet developed. -- T. Cheatham $- Когда вы выйдете из системы, ядро уничтожит shell-процесс, который был вам ассоциирован. Каким образом ядро создает такой shell-процесс? Каким образом shell-процесс получает имя программы и запускает на исполнение эту вашу программу? Как shell узнает о том, что программа закончилась? Процедура вхождения в систему и запуски программ не так просты, как это может вначале показаться. Мы будем изучать детали в главе 8.
32 Системное программирование в Unix. Общие представления 1\4.3. Работа с каталогами После того как вы вошли в систему, становится возможным работать с вашими файлами. В ваших файлах может находиться электронная почта, графические изображения, исходные коды программ, программы, готовые к исполнению, всевозможные данные. Файлы организованы в структуру с помощью каталогов. Дерево каталогов В Unix файлы объединяются в древовидные структуры с помощью каталогов, а система предоставляет пользователю команды для просмотра компонентов дерева и навигации по дереву. Ниже дана древовидная структура: * etc larry home cse215 / classes \ ^V bin / / marcus samples dev var /\ tmp lpd tmp spool mail usr / local fax \ bin Рисунок 1.8 Часть дерева каталогов Корень файловой системы обозначают символом /. В каталоге / содержится несколько каталогов. Каталог называют корневым (root directory), потому что из него вырастает полное дерево каталогов. Наиболее типичным составом корневого каталога для Unix - систем будут каталоги с такими именами, как /etc, /home, /bin, а также с другими стандартными именами. Для каждого пользователя в дереве файловой системы назначается домашний каталог для размещения в нем персональных файлов пользователя. Во многих системах пользовательские каталоги являются подкаталогами в каталоге /home. В Unix имеется ряд команд, которые позволяют работать с древовидной структурой каталогов. Это программы для создания новых каталогов, удаления каталогов, для перемещения файлов и каталогов по дереву файловой системы и программы проверки содержимого каталогов. Войдите в систему и поработайте самостоятельно с этими командами. Команды для работы с каталогами Is - представление содержимого каталога в списочном формате. Команда Is позволяет увидеть содержимое каталога в списочном формате. При выполнении команды вида Is вы получите содержимое текущего каталога. Если вы наберете Is dirname, то увидите содержимое указанного каталога. Например, вы можете набрать команду Is /etc
1.4. UNIX с позиций пользователя 33 с тем, чтобы посмотреть, какие файлы и каталоги находятся в каталоге /etc. Если же вы наберете команду Ь/ то увидите файлы и каталоги, которые находятся в корневом каталоге, cd - сменить каталог. При выполнении команды cd происходит переход в указанный каталог. Когда вы входите в систему, то попадаете в ваш домашний каталог. Далее вы можете покинуть свой домашний каталог и перейти в другую часть дерева файлов с помощью команды изменения каталога. Например, после выполнения команды cd /bin вы попадаете в каталог, в котором содержится много системных программ. Когда вы перешли в этот каталог, то можете выполнить команду Is, чтобы посмотреть, какие файлы и каталоги здесь находятся. Из любого каталога можно переместиться по дереву на уровень вверх после набора и выполнения команды cd.. Независимо от того, куда вы переместились по дереву, вы в любом месте можете вернуться в свой домашний каталог после выполнения команды cd pwd - вывести (распечатать) маршрутное имя текущего каталога. Команда/?wrf информирует вас о том, в каком каталоге дерева вы сейчас находитесь. Она выводит на экран путь от корня системы каталогов до вашего текущего каталога. Например, команда $pwd /home/cse215/samples показывает, что путь от корня дерева до нашего текущего каталога проходит через каталог home, затем через подкаталог cse215 и т. д. mkdir, rmdir - создание и удаление каталогов. Для создания каталога следует использовать команду mkdir. Например, после выполнения команд $cd $ mkdir jokes будет создан каталог jokes, который размещается в домашнем каталоге. Вам не разрешается создавать новые каталоги в каталогах других пользователей. Для удаления каталога следует использовать команду rmdir. Например, после выполнения команды $ rmdir jokes будет удален каталог jokes, если он не содержит файлов или каталогов. Вы должны удалить или переместить содержимое каталога перед тем, как попытаться его удалить. Команды для работы с каталогами: как они работают? Мы рассмотрели, как может выглядеть твердый диск в форме дерева каталогов, где каждый каталог соединен с одним вышележащим и каждый каталог может содержать некоторое количество каталогов, которые находятся на уровнях ниже текущего. Каждый каталог может содержать файлы. Пользователь имеет возможнось перемещаться по этой древовидной структуре, переходя от одного каталога к другому, создавая при этом новые каталоги здесь и там или удаляя старые каталоги.
34 Системное программирование в Unix. Общие представления А как это все работает? Твердый диск - это просто набор металлических пластин, которые способны хранить намагниченные элементы. А где же здесь каталоги? Что для вас означает выражение "находиться в вашем домашнем каталоге"? Что для вас значит переход в другой каталог? Какое-то число пользователей могут войти и работать одновременно на одной Unix - машине. При этом эти пользователи могут находиться в различных каталогах или все сразу в одном и том же каталоге, если они этого пожелают. Что будет с такими пользователями, если они все обратятся к одному каталогу? Как можно писать программы, которые будут выполнять навигационные действия по дереву каталогов? Какую роль играет ядро в создании такой древовидной модели? /. 4.4. Работа с файлами Каталоги играют роль, системной памяти для файлов. Пользователи имеют персональные файлы, которые хранятся в домашнем каталоге и в нижележащих каталогах. Система хранит свои файлы в системных каталогах. Что может делать с файлами пользователь? Мы начинаем рассмотрение некоторых базовых действий. Команды для работы с файлами Имена файлов - краткое представление. Файлы имеют имена. В большинстве версий Unix имена файлов могут быть достаточно длинными - иметь до 250 символов.(Чаще всего указывают максимальную длину, равную 255 символов.- Примеч. пер.) Имена файлов могут быть составлены из любых символов, за исключением символа "/". Символы могут быть набраны в верхнем и нижнем регистрах. В именах можно использовать знаки пунктуации, пробелы, знаки табуляции и даже символы перевода строки. cat, more, less, pg - команды для представления содержимого файлов. Файл содержит данные. Для просмотра содержимого файла можно использовать команды cat, more или less. Команда cat служит для отображения содержимого всего файла целиком: $ cat shopping-list soap cornflakes milk apples jam $ Если файл имеет большее число строк, чем размер экрана, то можно использовать команду щоге для организации постраничного вывода содержимого файла на экран. $ more longfile После вывода каждой очередной порции на экран вы должны нажать на клавишу "Пробел", чтобы вывести следующую страницу, или нажать на клавишу Enter для смещения текущего вывода на одну строку или нажать на клавишу "q" для выхода из просмотра файла. На некоторых системах доступны для использования команды less и pg. Они работают аналогично команде more.
1.4. UNIX с позиций пользователя 35 ср - копирование файла. Для выполнения копирования файла следует использовать команду ср. Например, при выполнении команда $ ср shopping-list last.week.list будет создан новый файл last.week.list, и в этот новый файл будет копироваться содержимое файла shopping-list. rm - удаление файла. Для удаления файла из каталога следует использовать команду rm. Например, после выполнения команды $ rm old.data junk shopping.junel 992 будут удалены три файла. В Unix не поддерживается действие восстановления (undelete). В одно и то же время систему могут использовать сразу несколько пользователей. Когда вы удаляете файл, то система может немедленно выделить освободившееся место на диске для другого пользователя. Дисковое пространство, в котором всего секунду назад находилась ваша курсовая работа, может теперь содержать исходный код программы на С другого пользователя. mv - переименование или перемещение файла. Для переименования файла или для перемещения файла в другой каталог следует использовать команду mv. Например, после выполнения команды $mvprog1.cfirst_program.c будет изменено имя файла prog 1.с: новым именем будет first_program.c. Можно теперь переместить эту программу в другой каталог, задавая имя каталога в качестве последнего аргумента при обращении к команде: $ mkdir mycode $ mv first_program .с mycode Ipr, Ip - распечатать содержимое файла. Вы можете распечатать содержимое файла при помощи команды Ipr. В самом простом варианте команда имеет вид: $ Ipr filename На принтер по умолчанию будет передан для печати файл с указанным именем. На многих системах используют более одного принтера. Тогда команда Ipr применяется в более сложном варианте, с тем чтобы выбрать для использования конкретный принтер. Пожалуйста, обратитесь к документации на вашей локальной системе, чтобы ознакомиться с деталями печати. На некоторых системах для печати используется команда 1р. Файловые команды: как они работают? Пользователи воспринимают файл как некое объединение информации, обычно в форме документа. Документ рассматривается как совокупность страниц, состоящих из символьных строк. Как файлы хранятся на диске? Каким образом происходит копирование файлов? Как можно перемещать файл из одного каталога в другой? Как система производит переименование файлов? И вообще, как система производит именование файлов? Вы, читатель, имеете имя; где оно хранится? В Unix все эти вопросы разрешены. Вам, как системному программисту, необходимо понимать, как это все работает.
36 Системное программирование в Unix. Общие представления Атрибуты прав доступа к файлам У вас есть некоторые файлы, у других пользователей есть свои файлы. У тех, кто запускает систему, имеются свои системные файлы. Вы можете не предоставлять всем окружающим право на изменение или даже право на чтение ваших файлов. Для тех лиц, которые будут запускать систему, требуется, чтобы пользователи не изменяли бы системные файлы или не вызвали бы беспорядок при работе с системными каталогами. Для контроля за доступом пользователей к их файлам в Unix для каждого файла устанавливаются несколько атрибутов. Файл имеет собственника, и файл имеет атрибуты прав доступа к нему. Собственник файла является пользователем в системе. Вы становитесь собственником файла, когда его создаете. Другие пользователи становятся собственниками при создании их собственных файлов. Каждый файл имеет три группы атрибутов прав доступа к файлу. Команда Is -1 показывает значения атрибутов файла: $ Is -I outline.01 -rwxr-x— 1 molay users 1064 Jun 29 00:39 outline.01 Это расширенный вариант вывода по команде Is. Символы -1 называются опцией в командной строке. Вы можете менять поведение Unix - команд с помощью указания значений этих опций при запуске команды. При расширенном варианте вывода команда Is выводит информацию о правах доступа, имя собственника файла, размер файла, дату и время последней модификации файла. Подстрока в левой части строки вывода команды Is -1, состоящая из символов и знаков пунктира, отображает состояние разрядов прав доступа. Каждый файл имеет собственника и три группы атрибутов доступа к файлу: г w х г w х г w х г: чтение, w: запись, х: исполнение user group other (собственник группа все_остальные). Весь мир пользователей делится на три категории: пользователь, являющийся собственником файла, группа, к которой принадлежит пользователь, и все другие пользователи. Пользователям в каждой из этих трех категорий может быть предоставлено право на чтение из файла, на запись в файл или на исполнение файла. Эти девять атрибутов являются независимыми. Вы можете, например, дать право на модификацию файла и не разрешить читать из файла всем пользователям из категории все_остальные. Вы даже себя можете лишить возможности читать собственные файлы. Права доступа к файлу: каким образом это все работает? Каково назначение разрядов прав доступа? Как установить указанные атрибуты прав доступа? Какие стратегии при управлении правами поддерживаются в Unix? Где хранятся эти разряды прав доступа? Мы изучим эти темы в последующих главах. 1.5. Расширенное представление об UNIX /. 5.1 Взаимодействие (связь) между людьми и программами В предшествующем разделе мы рассмотрели, что делает Unix с позиций пользователя, и начали рассмотрение вопроса, как работает система. Пользователь входит в систему, запускает программы на исполнение, работает с файлами и каталогами и выходит из системы. Возможно, что в то же самое время в систему могут входить еще какие-то пользователи, запускать на исполнение свои программы, работать с их файлами и каталогами и вы* ходить из системы. Пользователи могут работать с одними и теми же файлами и каталогами, они могут посылать электронную почту или разовые сообщения друг другу. Каждый пользователь работает в собственном пространстве, но это пространство является частью большой системы.
1.5. Расширенное представление об UNIX 37 Мы изучим, как все это работает, и рассмотрим, как писать программы, которые работают в этой большой системе. Что представляет собой эта большая система? Большая система представляет собой систему, в которой работают более одного пользователя, исполняются более одной программы, работают более одного компьютера, производится взаимодействие (связь) между людьми, программами и компьютерами. Рассмотрим три примера, с тем чтобы обсудить некоторые идеи и вопросы, которые возникают при программировании в этой большой системе. 1.5.2.Турниры по игре в бридж через Интернет Много людей играют в бридж через Интернет. Люди садятся за свои компьютеры, соединяются с сайтом для игры в бридж и ищут игру. Как только игроки подсоединились к игре, возникает ситуация: четыре человека сидят за компьютерами в разных частях света. Каждый из них видит на своем экране общий стол, каждый из них разделяет с другими игроками одну и ту же колоду карт, и каждый может видеть, что делают другие игроки. Упрощенная картинка этой игры будет такой: Рисунок 1.9 Четыре человека играют в бридж через Интернет На рисунке 1.9 изображены четверо игроков, каждый из которых работает со своим компьютером, каждый компьютер соединен через линию связи с Интернет. На этом рисунке не представлен стол для бриджа, который добавлен на рисунке 1.10. §д ЕШЭ %% Рисунок 1.10 Стол для бриджа на серверном компьютере Теперь мы имеем дело с сетью, в которой появляется пятый компонент. На столе для бриджа находятся карты, которые используются в игре. Стол представлен как поверхность, на которой отображаются образы карт. Стол - это место, вокруг которого собираются люди, чтобы сыграть в игру. В реальной игре игроки могут передавать карты от одного игрока другому. Каким образом сделать то же самое при ведении виртуальной игры?
38 Системное программирование в Unix. Общие представления Где расйолагаются карты? Как представить карты, которые находятся у вас на руках? Как программа может предотвратить "использование двумя игроками одних и тех же карт? В реальном мире это не является проблемой. В виртуальном мире каждая карта не представляет собой отдельную физическую целостность, что предотвращает возможность ее одновременного пребывания сразу в двух местах. На рисунке 1.11 изображены некоторые коммуникационные маршруты: 8А о Рисунок 1.11 Отдельные программы посылают сообщения друг другу При рассмотрении примера с игрой в бридж возникли три новые темы, которые весьма важны в системном программировании в среде Unix. Коммуникации Каким образом один пользователь или процесс связывается с другим пользователем или процессом? Координация Одновременно два игрока не могут выбирать карты из колоды. Каким образом программа должна координировать действия между процессами, чтобы они правильно разделяли ресурсы? Сетевой доступ В данном примере программы на каждом из компьютеров пользователей взаимодействуют через Интернет. Как программа может связаться с другой программой, используя Интернет? Каким образом обеспечивается программный доступ к Интернет? /. 5.3, be: секреты настольного калькулятора в Unix В каждой версии Unix имеется программа be, которая выполняет функции простого, текстового калькулятора с двумя привлекательными характеристиками. Чтобы запустить программу на исполнение, нужно набрать: $Ьс В ответ не появится ни приглашения, ни указания номера версии, ни требования набрать пароль. Программа просто будет ждать возможности выполнить некие вычисления. Наберите арифметическое выражение и нажмите на клавишу Enter. 2+3*4+5*10
1.5. Расширенное представление об UNIX 39 Программа be выведет на экран правильный результат. Программе известно, что в выражении следует сначала выполнить умножение, а затем сложение. Для выхода из программы be следует нажать на клавиши Ctrl-D. Одним из достоинств программы be является возможность работать с очень большими целыми числами, такими, как: 99999999999999999999 * 88888888888888888888 8888888888888888888711111111111111111112 Для представления больших чисел можно использовать экспонентную форму записи чисел: 3333 Л 44 10110061584495640995005898489182285794822405288498070703365111794769\ 43890411064925291154381468890721948142209004688381870355409155411563\ 21805747562427309521 Для обычного представления числа 3333 с десятичным порядком 44 понадобилось две с половиной строки десятичных цифр. Поэкспериментируйте с be, чтобы посмотреть, как программа работает с большими числами. Программа be поддерживает также свой язык программирования, где используются переменные, циклы и С-образный синтаксис. Например, следующий ниже код (будет восприниматься для выполнения программой be: х = 3 if (х == 3){ у = х*3; } У Вот еще одно интересное свойство программы be. Программа be не является' калькулятором. Она не производит вычислений. Чтобы посмотреть, что делает программа, давайте попытаемся выполнить следующее: $Ьс 2 + 3 5 <-- Нажмите здесь Ctrl-Z Приостанов процесса $ps PIDTTYSTIMECMD 25102 ttyp2T 0:00.02 be 27081 ttyp2 T 0:00.01 dc-27560 ttyp210:00.59-bash 27681 ttyp2T 0:00.00 be $fg <- Нажмите здесь Ctrl- D Программа ps выводит информацию о запущенных вами процессах. В данном листинге представлена информация о четырех процессах. Для одного из процессов указана в качестве имени исполняемой программы строка в виде "-bash," что говорит о том, что это информация о "входном shell" (log-in shell). В листинге представлены еще два процесса, в которых исполняется программа be, и один процесс, в котором исполняется программа dc. Что представляет собой программа dc?
40 Системное программирование в Unix. Общие представления В большинстве версий Unix есть электронный справочник. Для прочтения документации по команде dc следует набрать такую команду: $mandc User Commands dcA) NAME dc - desk calculator SYNOPSIS dc [ filename ] DESCRIPTION dc is an arbitrary precision arithmetic package. Ordinariiyit operates on decimal integers, but one may specify an input base, output base, and a number of fractional digits to be maintained. The overall structure of dc is a stacking (reverse Polish) calculator. If an argument is given, input is taken from that file until its end, then from the standard input. Это страничка из электронного справочника системы SunOS 5.8; в большинстве версий Unix описания команд представлены в аналогичном виде. В этом тексте из документации говорится о том, что dc - это команда, которая выполняет функции калькулятора. Более того, здесь указывается, что эта команда работает как калькулятор с использованием стека и обратной польской записи. Предполагается, что она начинает работать после набора пользователем чисел. Выполним сложение следующего вида: 2+3. 2 3 + Р 5 Этот протокол означает, что сначала в стек заносится 2, затем 3, далее производится сложение двух чисел, которые находятся в головной части стека, затем происходит выдача полученного результата, который будет размещен в голове стека. Возникает вопрос - если программа dc представляет собой калькулятор, причем калькулятор, использующий стековую память, то чем же тоща является программа 1?с и почему она тоже исполняется? Ответ мы получим после прочтения документации в электронном справочнике относительно команды be. В документации сказано о том, что команда be является препроцессором относительно команды dc1. Программа be является синтаксическим анализатором. Она взаимодействует с процессом, где выполняется программа dc через коммуникационное средство, которое называется pipes (программные каналы)^ как показано на рисунке 1.12. Данные, которые вводит пользователь в формате  +2", поступают на вход процесса be. В этом процессе происходит преобразование данных в соответствии со стековым представлением, после чего данные передаются процессу dc. Процесс dc выполняет необходимые вычисления и отправляет полученный результат обратно процессу be. Процесс be производит форматирование этого результата для оконечного пользователя. Пользователь воспринимает программу be как калькулятор. 22+р 2 + 2 4 Рисунок 1.12 Программы посылают сообщения друг другу » I I 1. В версии GNU команды be вместо dc используется внутренний стековый калькулятор.
1.6. Могу ли я сделать тожесамое? 41 В данном примере с программами bc/dc показано наличие программ, которые, аналогично случаю использования стола для бриджа в Интернете, включают в себя различные процессы, а также некоторые разновидности средств коммуникации и кооперации. Отдельные программы при совместной работе образуют систему. Каждая часть системы выполняет свою задачу и достаточно наглядно выделена. Значимую часть системы составляют средства межпроцессных коммуникаций, которые используются отдельными программами. Сходство между примером с игрой в бридж через Интернет и примером с системой и bc/dc является фундаментальным принципом системного программирования в Unix. Поэтому изучение системного программирования в Unix сводится к изучению того, как нужно писать отдельные программы и каким образом необходимо построить связи и обеспечить совместную работу программ. 1.5.4. От системы bc/dc к Web Систему из программ bc/dc отделяет от World Wide Web всего один небольшой шаг. В системе в паре bc/dc программа be выполняет функции пользовательского интерфейса, а программа dc выступает в роли программы, выполняющей заданную работу. При рассмотрении World Wide Web видно, что броузер выполняет функции пользовательского интерфейса, а Web-сервер выполняет конкретную работу. Архитектура при этом одна и та же. (http://www.xyz.com/info <htmlxhead><title>...|| Шйашй _l—server Рисунок 1.13 Отдельные программы посылают сообщения друг другу Пользователь взаимодействует с броузером. Броузер располагается не в том месте, где находятся Web-страницы. Эти страницы находятся на серверах. Совсем коротко можно сказать, что серверы поддерживают текстовый язык нттр, а программа dc разговаривает на текстовом языке, который называется rpn. Пользовательский агент (be или броузер) транслирует пользовательский ввод B+3 или нажатия кнопок мыши) в краткий текстовый язык и посылает требование на обслуживание (программа dc или Web-сервер). Пользовательский агент (be или броузер) принимает ответ от сервера и форматирует его для пользователя. Между моделью системы программ bc/dc и World Wide Web принципиальная разница отсутствует. Вероятно, не является неожиданным, что Web выросла благодаря Unix - системам2. 1.6. Могу ли я сделать то же самое? У нас теперь появилось представление о сути проблем, которые были обозначены ранее в двух первых вопросах. Мы рассмотрели несколько аспектов системы Unix, с тем чтобы ответить на вопрос "Что делает система?". Перед нами стоял вопрос "Как выполняется эта работа?" 2. Между прочим, студент моего курса Ами Чусед был первым, кто отметил связь между системой bc/dc и клиент-серверным программированием на основе TCP/IP.
42 Системное программирование в Unix. Общие представления По таким примерам, как система bc/dc, мы получили частично ответ на этот вопрос. Согласно нашему методу есть и третий вопрос: "Могу ли я сделать то же самое?" В этом разделе мы напишем версию Unix программы more. Во-первых, определим, "Что делает команда more"? Команда more служит для поэкранного вывода содержимого файла. В большинстве Unix систем есть большой текстовый файл, который называется /etc/termcap и используется некоторыми редакторами и видеоиграми. Если вы захотите постранично просмотреть содержимое этого файла, то вы должны будете набрать следующую команду: $ more /etc/termcap В начале работы программы вы увидите первый экран текста файла. В нижней части экрана программа more будет выводить в инверсном режиме отображения процент просмотренного объема текста. Для просмотра следующей страницы следует нажать на клавишу пробела, для смещения просматриваемого текста на одну строку необходимо нажать на клавишу Enter, для выхода из просмотра следует нажать на клавишу "q", для получения текста помощи вы должны нажать на клавишу "h". Заметим, что не следует нажимать на клавишу Enter после нажатия на клавишу пробела или на клавиши "q" или "h". Программа сразу отвечает на нажатие указанных клавиш. Есть три варианта использования команды more на уровне командной строки: $ more filename $ command | more $ more < filename В первом случае команда more будет отображать содержимое файла с указанным именем. Во втором случае запускается на исполнение программа с именем command и ее вывод постранично отображается на экране. В третьем случае команда more отображает тот текст, который эта программа читает из стандартного ввода. Вместо стандартного ввода был присоединен указанный файл. Во-вторых, определим, "Как это все работает"? После неоднократного запуска команды more можно заметить, что ее логика работы вполне вероятно описывается следующей последовательностью шагов: +—-> вывод 24 строк из файла | +- - > вывод сообщения [more?] | Нажатие на клавиши Enter, SPACE или q j +-- если Enter вывод одной очередной строки +—- если SPACE если q--> выход Наша программа должна быть гибкой в части организации ввода и быть похожей в этом на реальную программу more. Это означает, что если пользователь указывает для нашей программы в командной строке имя файла, то программа должна читать этот файл. Если в командной строке при обращении к программе имя файла не задано, то программа должна будет читать со стандартного ввода. Ниже представлен первый вариант нашей версии программы more: /* moreOI .с - версия 0.1 программы more * читает и выводит на экран 24 строки, затем следуют несколько * специальных команд */ #include <stdio.h>
?. Могу ли я сделать то же самое? #define PAGELEN 24 «define UNELEN 51.2 void do_more(FILE *); intsee_more(); int main(int ac, char*av[]) { RLE *fp; if (ac == 1) do_more(stdin); else while (--ac) if (ft> = fopen(*++av, T)) != NULL) { do_more(fp); fclose(fp); } " else exitA); return 0; } void do more(FILE *fp) Г * читает PAGELEN строк, затем вызывает see more() для получения дальнейших инструкций 7 { charline[UNELEN]; int numjrfjines = 0; int seejnoreQ, reply; while (fgets(line, UNELEN, fp)){ /* ввод для more 7 if*(numj)fJines == PAGELEN) {/* весь экран? 7 reply = see_more(); /* у: ответ пользователя 7 if (reply == 0) Г п: завершить7 break; num of lines -= reply; /* переустановка счетчика 7 } if (fputs(line, stdout) == EOF) /* показать строку 7 exitA );/* или закончить */ num of lines++; /* учесть очередную строку 7 } } intseemore() Л * выдать сообщение, ожидать ответа, возвратить значение числа строк * q означает по, пробел означает yes, CR означает одну строку 7 { int с; printf(H\033[7m more? \033[mM); /* реверс изображения для vt100 7 while((c=getchar()) != EOF) /* получение ответа 7
44 Системное программирование в Unix. Общие представления if (с == -Q-) */ return 0; if (с ==' ')Г'' => следующая страница 7 return PAGELEN; /* сколько показывать */ if (с == '\п*) /* Требование на 1 строку */ return 1; } return 0; } Код программы состоит из трех функций. В функции main определяется, откуда производится ввод информации - из файла или со стандартного ввода. Выяснив вопрос относительно входного потока; функция main передает этот входной поток функции, которая называется do_jnore и которая должна будет поэкранно отображать этот поток. В свою очередь функция do_more отображает экран текста и затем обращается к функции see_more, которая должна запросить у пользователя, что делать дальше. Для компиляции и запуска нашей программы следует выполнить: $ ее moreOI .с -о moreOI $ moreOI moreOI .с Полученная программа работает достаточно хорошо. Программа выводит 24 строки текста и далее выводит заметное для плаза приглашение тоге? в реверсном изображении. При нажатии на клавишу Enter будет выведена следующая строка текста. Над этой программой необходимо будет еще поработать. В частности, после вывода сообщения more? оно остается на экране и смещается (скрол- лируется вверх) вместе с текстом. Кроме того, если вы нажмете клавишу пробела или клавишу "q", ничего не произойдет, если вы не нажмете после этого на клавишу Enter. Это решение нельзя признать хорошим. Итак, на экране остается приглашение more?. Создание данной версии программы more иллюстрирует основополагающий фактор относительно программирования в Unix: Программирование в Unix не так трудно, как вы думали, но и не так просто, как можно судить по первому опыту. Программа выполняет четко заданную задачу. Логика этой задачи достаточно ясна. При ^ разработке алгоритма, который реализует действия, выполняемые задачей, не использовались всяческие ухищрения. Мы отметили ряд тонкостей в работе программы. Как модифицировать программу, которая реагировала бы сразу же на нажатие клавиш без последующего нажатия на клавишу Enter? Как можно вычислить процент объема просмотренного текста из файла? Как удалить с экрана текст приглашения more? после того, как будет нажата клавиша? Это не должно быть слишком сложным. Но прежде всего нам нужно закончить с другими характеристиками программы, которые должны быть сравнимы с оригинальной программой. Насколько хорошо наша программа справляется с управлением входными потоками? Функция main выполняет проверку числа аргументов в командной строке. Если в командной строке имена файлов отсутствуют, то программа будет производить чтение со стандартного входа. Тем самым обеспечивается возможность помещать программу more в конец конвейера, как показано ниже: $who|more
1.6. Могу ли я сделать то же самое? 45 В этом конвейере запускается команда who, которая отображает список всех пользователей, работающих в текущий момент в системе, и посылает этот список пользователей команде more. Поскольку наша программа more отображает за раз 24 строки, то она будет полезна, если число пользователей будет превышать 24. Давайте проверим работу нашей программы, но при работе не с программой who, а при работе с командой Is: $ Is/bin | moreOl Мы предполагаем увидеть содержимое каталога /bin страницами по 24 строки. Когда вы запустите нашу программу, то увидите, что moreOl не приостанавливается после вывода 24 строк. Что привело к ошибке? Причина заключается в следующем. Наша программа moreOl читает и выводит по 24 строки из входного потока, который она получает от команды Is. Когда программа moreOl будет читать двадцать пятую строку, она выведет приглашение more? и будет ожидать ответа пользователя. Наша программа ждет, что пользователь нажмет либо на клавишу пробела, либо на клавишу Enter, либо на клавишу "q". Где в программе принимается информация от пользователя? В программе используется для чтения из стандартного ввода getchar. Но в таком представлении конвейера: $ Is/bin | moreOl происходит перенаправление стандартного вывода команды Is на стандартный ввод программы moreOl. Наша версия программы more пытается читать команды пользователя из того же потока, откуда поступают данные из файла. На следующем рисунке показана ситуация:"* Рисунок 1.14 more читает со стандартного ввода Каким образом решается эта проблема в реальной программе more? To есть, как программа может читать данные со стандартного ввода и одновременно вводить информацию от пользователя с клавиатуры? Решением будет чтение данных непосредственно с клавиатуры. На рисунке 1.15 показано, как это делается в реальной версии. В каждой системе Unix есть специальный файл, который называется /dev/tty. Этот файл обеспечивает соединение с клавиатурой и экраном. Даже если пользователь с помощью символов < или > перенаправит в программе стандартный вход или стандартный вывод, программа остается связанной с терминалом, чтение и запись с которым производится через файл/dev/tty.
46 Системное программирование в Unix. Общие представления Рисунок 1.15 Программа who читает пользовательский ввод с терминала На рисунке показано, что more имеет два источника ввода. Стандартный ввод программы подсоединен к выводу программы who. Но программа more также читает данные из файла /dev/tty. Программа читает строки файла и отображает их на экране. Когда необходимо запросить у пользователя, следует ли выводить более одной строки, более одной страницы или следует выйти из просмотра, программа читает ответ от пользователя из файла /dev/tty. В соответствии с полученными новыми знаниями расширим вариант программы moreOl.c и создадим вариант more02.c: Г more02.c - версия 0.2 программы more * чтение и выдача 24 строк, затем следуют несколько * специальных команд * особенность версии 0.2: чтение команд из файла /dev/tty 7 #include <stdio.h> #define PAGELEN 24 #defineUNELEN512 void do_more(FILE *); intsee_more(FILE*); int main(int ac, char *av[]) { FILE *fp; if(ac==1) dojnore(stdin); else while (--ac) if ((fp = fopen(*++av, Г)) != NULL) { do_more(fp); fclose(fp); } else exitA); return 0; } void do_more(FILE *fp) Г
1.6. Могу ли я сделать то же самое? 47 * команд 7 { charline[UNELEN]; int nunrurfjines = 0; int see more(FILE *), reply; FILE *fp_tty; fpjty = fopen('7dev/tty", V); /* НОВОЕ: команда потока 7 if (fpjty == NULL) /* если открытие неудачно 7 exitA); Г не используется при запуске программы no use in running*/ while (fgets(line, LINELEN, fp)){ /* ввод для more 7 if (num_qfjines == PAGELEN) {/* весь экран? 7 reply = see_more(fp_tty); /* НОВОЕ: передача FILE * 7 if (reply == 0) /* n: завершить 7 ¦ f v break; num of lines -= reply; /* переустановить счетчик 7 } if (fputs(line, stdout) == EOF) /* показать строку 7 exitA); /* или закончить вывод 7 num of lines++; /* учет выведенной строки 7 } } int see_more(FILE *cmd) /* НОВОЕ: прием аргументов 7 Г * выдать сообщение, ожидать ответа, возвратить значение числа строк * q означает по, пробел означает yes, CR означает одну строку 7 { int с; printf("\033[7m more? \033[m"); /* реверсировать текст для vt100 7 while((c=getc(cmd)) != EOF) /* НОВОЕ: читать из tty 7 { if (с == -q*) /*q->N7 return 0; If (C ==f ¦) /*•¦•=> следующая страница 7 return PAGELEN; /* сколько показывать 7 if (с == f\n') /* Enter => 1 строка 7 return 1; } return 0; } Компиляция и проверка этой версии производятся с Помощью команд: $s ее -о more02 more02.c $ Is /bin | more02 Эта версия more02.c может читать данные со стандартного ввода, а команды - с клавиатуры. Заметим, что стремление написать стандартную Unix - программу привело нас к необходимости изучить файл /dev/tty и определить его роль в качестве связующего файла с пользовательским терминалом.
48 Системное программирование в Unix. Общие представления И все же над нашей программой стоит еще поработать. Мы все еще должны нажимать на клавишу Enter для получения ответа.от программы. Итак, пусть на Экране появились символы "q" и пробел. Каким-то образом в реальной версии more при вводе указанных символов сразу же произойдет выход из программы. При этом не нужно нажимать на клавишу Enter. Если вы нажали на клавишу "q", произойдет выход из программы, а вы этот символ просто не увидите на экране. , Непосредственный ввод: как это все работает? При установлении связи с терминалами можно производить настройки. Вы можете выбрать такую настройку соединения, что символы будут сразу же доступны в программе, а не после нажа(тия пользователем клавиши Enter после нажатия на какую-либо клавишу. Можно выбрать такую настройку, чтобы символы, которые пользователь набирает на клавиатуре, не отображались бы на экране. Вы в праве выбрать все варианты установок, которые управляют передачей данных вашей программе через терминал. Насколько мы углубились в проблему, детально показано на нашем рисунке. Теперь он будет представлен в таком виде: Рисунок 1.16 Соединение с терминалом имеет настройки Новым составным элементом на этом рисунке стало управляющее устройство, которое было добавлено к соединению с /dev/tty. Это устройство позволяет программисту настраивать работу линии связи между терминалом и программой, что воспроизводит возможные настройки между тюнером и громкоговорителем в радиоприемнике. Для написания полнофункциональной, хорошо работающей версии программы more нам понадобится изучить соединительное управляющее устройство (контроллер) и определить, как можно его программировать. Нам потребуется также ответить еще на ряд других вопросов. Как определить в процентах объем показанного текста? В реальной версии more процент вывода пользователь может видеть на экране. Как можно добавить эту возможность в нашу программу? Операционная система знает размер файла. Нам нужно познакомиться с тем, как запросить у операционной системы эту информацию. Что такое реверсивный режим изображения? Что можно сказать о числе строк на экране? На разных дисплеях используют различные режимы для визуализации текста в инверсном режиме. Различные дисплеи имеют различное число строк на физическом экране. Использование размера в 24 строки и кода для реверсив-
/. 7. Еще несколько вопросов и маршрутная карта 49 ного изображения в стиле vtlOO представляется недостаточно гибким. Как можно написать версию программы more, которая работала бы с любым типом терминала и с любым числом строк на экране? Для ответа на эти вопросы нам необходимо будет изучить особенности управления экраном терминала и его атрибуты. 1.7. Еще несколько вопросов и маршрутная карта /. Z /. О чем пойдет теперь речь? Мы оговорили целевое назначение этой книги. Unix - это операционная система, которая предоставляет нескольким пользователям возможность одновременной работы. Пользователи могут запускать программы на исполнение и работать с файлами и каталогами. Эти программы могут взаимодействовать между собой, с другим компьютером, а также взаимодействовать через сеть. Пользователи запускают программы для управления своими файлами, для обработки данных, для передачи и преобразования данных и для установления связи с другими пользователями. Что нужно сделать, чтобы все эти программы работали? Что делают программы? Что делает операционная система? По мере изучения основных свойств системы мы ответили на многие вопросы. Давайте продолжать отвечать на вопросы. Наша разработка команды more продемонстрировала метод, который мы возьмем для последующего использования. Мы анализируем реальную программу, изучаем, что она делает, а затем пытаемся написать нашу собственную версию такой программы. По мере разработки мы изучаем все более детально, как работает Unix, и учимся, как использовать ее принципы работы. 1.7.2. А теперь - карта Нам понадобится карта для нашего продвижения вперед. Вот она. (ммЛ [ 1 Ifr- 1 Ш Ш ! U ; ?]^{V/±;\^YJ*?T~'*'r*''- -[ШЗ г —г- L i-i. t fir if Ц^ЩуЩр^ТТТ ;<-.. > < *СП?|| ЛЕ D Рисунок 1.17 Диаграмма основной структуры системы Unix На этой диаграмме представлена основная структура любой системы Unix. Память разделяется на системное пространство и на пользовательское пространство. В системном пространстве находится ядро и его структуры данных. Пользовательские процессы размещаются в пользовательском пространстве. Некоторые пользователи взаимодействуют с системой через терминалы. Линии связи этих терминалов присоединены к ядру. Файлы
50 Системное программирование в Unix. Общие представления хранятся в файловой системе на диске. К ядру подсоединяются различные типы устройств, которые становятся доступными пользовательским процессам. Наконец, есть средства для поддержки сетевых коммуникаций. Пользователи могут работать с системой, используя сетевые средства. В каждом разделе этой книги мы рассматриваем отдельные элементы этой диаграммы. Каждый компонент будет рассмотрен более детально, чтобы, изучить службы ядра, которые их поддерживают, и объяснить догику и структуры данных ядра, которые используются для обеспечения этих служб. В конце книги мы изучим каждую часть представленной диаграммы и рассмотрим все идеи и средства, которые необходимы для написания сложных системных программ для Unix (например, для разработки версии игры в бридж через Интернет). /. 7.3 Что такое Unix? История и диалекты В этой книге объясняются базовые идеи и структуры Unix и рассматривается, как следует писать программы, которые будут работать в системе Unix. Но что же представляет собой система Unix? Откуда она появилась? Что в этой части можно ожидать от этой книги? Прежде всего, откуда появилась система Unix? Система Unix была разработана в Bell Laboratories в 1969 году несколькими компьютерными специалистами для решения специальных технических проблем и состояла из ядра и набора инструментальных средств. Система Unix не была коммерческим продуктом. В самом деле, в течение семидесятых годов Bell Labs распространяла программное обеспечение Unix, включая исходный код, в школы и научные центры за номинальную цену. Специалисты из Bell Laboratories и многие другие компьютерщики потратили много времени на изучение системы, ее улучшение и добавление новых оригинальных программ. В восьмидесятых годах несколько компаний лицензировали исходный код Unix и создали несколько версий Unix, ориентированных на потребителей. Двумя основными центрами по развитию системы стали AT&T и университет Беркли в штате Калифорния (UCB). AT&T разработала версию, которая была названа System V, а специалисты UCB разработали версию, которая была названа BSD. Большинство версий Unix были разработаны на основе одной из этих базовых систем или на основе использования той и другой системы. С годами менялась собственность на систему, прошла серия продаж системы от AT&T ряду компаний, коллектив UCB перестал работать над Unix, появились различные группы, которые пытались выверять и стандартизировать систему. Независимо от курсов и стандартов основной проект и принципы Unix распространяются через университетские и коммерческие компьютерные сферы. Развиваются различные диалекты и модели системы. В некоторые версии были включены специальные средства, например, обработка в режиме реального времени. При всех этих адаптациях и изменениях в системе всегда оставались архитектура ядра и постоянный набор функций. Хотя точная внутренняя структура и набор инструментальных средств для версии Unix от AT&T времен восьмидесятых годов будут отличаться для варианта,,написанного в 1991 году в Хельсинки, но программы, которые были написаны для версии восьмидесятых, можно будет с минимальными изменениями откомпилировать и запустить на исполнение в среде финской версии. Что же, в конце концов, представляет собой Unix? Термин система Unix чаще всего используется при ссылке на системы, которые построены на основе ядерной модели и поддерживают определенные функции, которые являются общими для всех этих вариантов систем. Некоторые системы работают и выглядят аналогично Unix, но построены они были не на основе кодов версий AT&T или UCB. Комбинация инструментальных средств
Заключение 51 и ядра вида GNU/Linux известна под названием Unix - подобная система. Одно из формальных определений системного интерфейса называется POSIX. Для понимания, чтения и написания Unix - программ вам понадобится знание более одного стандарта. Unix имеет длинную, многовариантную историю. Какое число систем Unix предполагается изучить в этой книге? Мы сосредоточим свое внимание на структуре, принципах и средствах, которые являются общими для всех систем Unix. Некоторые детали будут опущены, некоторые операции дублируются, а все идеи рассматриваются с практических позиций. Некоторые детали я не включаю в рассмотрение, и иногда я предлагаю вам обратиться к документации. Эта книга не является исчерпывающим руководством по любому аспекту относительно любой версии Unix. Существенная часть знаний об Unix должна быть получена по мере изучения и использования электронной документации на вашей системе. Иногда я описываю различные функции, которые производят одно и то же действие. Это обусловлено тем, что имеет место дублирование функций при децентрализованном процессе развития Unix. Различные группы, подобные AT&T и UCB, иногда предлагают различные решения одной и той же проблемы. Другая причина дублирования функций объясняется обычными издержками роста. Когда разработчики заменяют в более развитой версии какую-либо службу в Unix, например, такую, как аварийные таймеры, они не хотят выкидывать существующие программы. Поэтому они редко удаляют старый, упрощенный интерфейс. Иногда я привожу одно решение, а иногда несколько. Если вы будете изучать программы Unix, то будете встречать такие варианты решений. Изучение различных методов может помочь разобраться в фундаментальных идеях и помочь вам адаптироваться к локальным особенностям системы. Наконец, я представляю Unix в контексте действующих программных проектов. Unix - это система идей и средств, созданных людьми, которые искали решение реальных проблем. Мы начали рассмотрение с реальных проблем и наблюдали, как идеи приводят к нахождению решений. Unix воспринимается осмысленно, когда вы видите, что составные части работают совместно, как система. Заключение • Вычислительная система состоит из нескольких типов ресурсов, таких, как дисковая память, память, периферийные устройства и сетевые средства. Программы используют эти ресурсы для хранения, пересылки и обработки данных. • Вычислительные системы, где одновременно работают несколько программ нескольких пользователей, требуют наличия централизованной управляющей программы. Ядро Unix представляет собой программу, которая планирует исполнение программ и управляет доступом к ресурсам. Пользовательские программы обращаются к ядру за ресурсами. Некоторые Unix - программы состоят из отдельных программ, которые разделяют или обмениваются данными. Написание системных программ требует понимания структуры и использования служб ядра.
Глава 2 Пользователи, файлы и справочник. Что рассматривать в первую очередь? В-г шв- fie шртшё 4L Цели Идеи и средства Роль и использование электронной документации. Файловый интерфейс Unix: open, read, write, lseek, close. Создание и чтение файлов, запись в файлы. Дескрипторы файлов. Буферирование: пользовательский уровень и уровень ядра. Режим ядра, пользовательский режим и назначение системных вызовов. Как в Unix представлено время, как форматировать изображения времени в Unix. Использование файла utmp для определения списка текущих пользователе. Обнаружение ошибок в системных вызовах,и оповещение об ошибках. Системные вызовы и функции open, read, write, creat, lseek, close perror Команды man • who • cp • login 2.1. Введение Кто же использует систему? Не много ли пользователей? Вошел ли в систему мой друг? В каждой многопользовательской вычислительной системе есть команда who. Команда сообщает, кто работает на компьютере. Как работает эта команда? В этой главе мы будем изучать работу команды в Unix. По мере изучения мы узнаем, как в Unix можно вести обработку файлов. Дополнительно к информации об Unix, которую мы получаем при изучении, рассмотрим, как использовать систему Unix в качестве справочника об этой системе.
2.2. Вопросы, относящиеся к команде who 53 2.2. Вопросы, относящиеся к команде who Обратимся снова к представлению системы Unix. Рисунок 2.1 Пользователи, файлы, процессы и ядро Большой ящик ни рисунке представляет память компьютера. Он разделен на пользовательское и системное пространство. Пользователи соединены с системой через терминалы. В этой системе есть два твердых диска, изображенное в виде больших цилиндров, и один принтер. В пользовательском пространстве исполняются различные программы. Они связываются с внешним миром через ядро. Эти коммуникационные каналы на рисунке представлены в виде линий связи процессов с ядром. По нашему плану мы будем изучать команду who. Поэтому возникают вопросы: 1. Что делает команда who? 2. Как работает команда who? 3. Могу ли я написать программу who? 2.2.1. Программы состоят из команд Прежде чем начинать рассмотрение, важно отметить, что почти все команды Unix типа who и Is - это просто программы, которые были написаны некоторыми программистами, обычно на С. Когда вы набираете на клавиатуре Is, то обращаетесь к командному интерпретатору shell, чтобы он запустил на исполнение программу с именем Is. Программа Is при исполнении выводит список файлов в каталоге. Если вы не удовлетворены тем, что делает команда Is, то можете написать собственную версию этой команды и использовать ее вместо исходной версии. Добавить новые команды в Unix очень просто. Вы пишете новую программу и должны поместить исполнимый файл для хранения в один из стандартных каталогов, таких, как /bin, /usr/bin, /usr/local/bin. Многие команды в Unix появились как программы, которые кто- то написал для решения некоторой частной задачи. Другие пользователи сочли такие программы полезными. И тогда такие программы можно встретить на каком-то числе Unix - машин. Поэтому у вашей версии программы who есть шанс стать когда-нибудь стандартной. г^. к
54 Пользователи, файлы и справочник. Что рассматривать в первую очерщь? 2.3. Вопрос 1: Что делает команда who? Если нам нужно узнать, кто в текущий момент работает в системе, то мы должны набрать команду who: $who heckerl nlopez dgsulliv one.net) ackerman wwchen barbier ramakris czhu bpsteven molay $ ttypl ttyp2 ttyp3 ttyp4 ttyp5 ttyp6 «УР7 ttyp8 ttyp9 ttypa Jul 21 19:51 Jul 21 18:11 Jul 21 14:18 Jul 15 22:40 Jul 21 19:57 Jul 8 13:08 Jul 13 08:51 Jul 21 12:47 Jul 21 18:26 Jul 21 20:00 (tide75.surfcity.com) (roam163-141.student.ivy.edu) (h004005a8bd64.ne.media- r (asd1-254.fas.state.edu) (circle.square.edu) (labpcl 8.elsie.special.edu) (roaml 57-97.student.ivy.edu) (spa.sailboat.edu) B07.178.203.99) (xyz73-200.harvard.edu) Каждая строка этого протокола представляет одну сессию (одно вхождение в систему). В начале строки расположено пользовательское имя (usemame). Далее выводится имя терминала, через который пользователь вошел в систему. В следующей части строки выводится информация о том, когда пользователь вошел в систему. Последняя часть строки предназначена для обозначения, где находится пользователь, вошедший в систему. В некоторых версиях команды who информация об имени удаленного компьютера не выводится, если вы ее явно не затребовали. ' 2.3.1. Обращение к справочнику При запуске команды who мы получаем определенную информацию о том, что эта команда делает. Для более подробного изучения вопроса о назначении команды можно обратиться к электронному справочнику. Каждая из систем Unix поступает с документацией обо всех командах. Иногда система Unix Поступает с печатным справочником, где для каж-дой команды представлена документация в одну или две страницы. Чаще всего теперь справочник расположен на диске. Команда для чтения информации из справочника - man1. Для получения описания команды who следует выполнить команду: $ man who whoA) whoA) NAME who - Identifies users currently logged in SYNOPSIS who [-a] |[-AbdhHlmMpqrstTu] [file] who am i who am I whoami The who command displays information about users and processes on the local system. 1. В некоторых версиях Unix реализована традиционная man-документация со ссылками на основе использования системы info или справочник представлен как набор взаимосвязанных страниц HTML.
2.3. Вопрос 1: Что делает команда who? 55 STANDARDS Interfaces documented on this reference page conform to industry standards as follows: who:XPG4,XPG4-UNIX Refer to the standardsE) reference page.for more information about industry standards and associated tags. OPTIONS -a Specifies all options; processes /var/adm/utmp or the named file with all options on. Equivalent to using the -b, -d, -I, -p, -r, -t,-T, and-u options. more A0%) Все страницы руководства, которые часто называют manpages, имеют одинаковый базовый формат. Заголовок служит для представления имени команды и обозначает раздел справочника, в котором находится данный документ. В данном примере это изображается как who A), что обозначает команду who и раздел 1. В разделе 1 содержится документация обо всех пользовательских командах. Обратитесь к справочнику на вашей системе и посмотрите, что находится в других разделах справочника. Секция name на странице документации содержит имя команды и однострочное представление назначения команды. * . . Секция synopsis представляет, как можно использовать команду. Здесь показано, что следует набирать при вызове команды, список аргументов и опций, которые возможно использовать при вызове команды. Каждая опция обычно начинается со знака дефиса, за которым следуют один или более символов. С помощью опций можно указывать вариант исполнения команды. В тексте страницы справочника можно использовать квадратные скобки ([-а]), чтобы показать, что данный элемент не является обязательным для команды, но может быть при необходимости включен в текст командной строки при вызове команды. В примере страницы документации для команды who показано, что вы можете обращаться к команде просто набором ее имени who, или можете набрать: who -а (произносится who минус я), или вы можете набрать who с последующим набором знака "минус" и некоторой комбинации символов, затем указать имя файла, которое вам понравится. На странице документации для команды who представлены еще три формы обращения к команде: who ami who am I whoami Вы можете прочитать о назначении этих альтернативных форм вызова команды в справочнике или попытаться поработать с ними. В секции description находится описание того, что делает команда. Эти описания весьма сильно варьируются от команды к команде, от одной версии Unix к другой. Некоторые тексты описаний краткие, но точные. В некоторых описаниях представлено большее число деталей и несколько примеров. В любом случае описания представляют все свойства команды и содержат надлежащие авторитетные ссылки. В секции options представлен список допустимых опций и описание, для чего предназначена каждая опция. В давние времена каждая команда в Unix была простой. Каждая выполняла некоторое действие и имела одну или две опции. С годами многие команды были vcoRenuieHCTRORaHbi за счет введения в их состав новых возможностей, каждая из котооых
56 Пользователи, файлы и справочник. Что рассматривать в первую очередь? может быть активизирована с помощью опций при обращении к команде с уровня командной строки. Некоторые команды, подобные рассматриваемой версии команды who, имеют весьма много опций. В секции see also представлен список тем в справочнике, связанных с командой. В некоторых страницах справочника есть еще секция bugs. 2.4. Вопрос 2: Как работает команда who? Нами было установлено, что команда who отображает информацию о тех пользователях, которые к текущему моменту вошли в систему. На странице документации для команды who дано описание того, что может делать эта команда и каким образом заставить ее выполнить допустимое для нее действие. Как работает команда who? Как она выполняет допустимые для нее действия? Можно предположить, что системные программы, подобные who, используют специальные системные функции. В том числе, возможно, они включают расширенные привилегии администратора. Вам может потребоваться получить доступ к средствам системного разработчика, включая доступ к CD-ROM, толстым книгам и секретным кодам. Все это может потребовать каких-то расходов. Вся документация о функционировании команды who находится в самой системе. Вам только нужно знать, где следует искать документацию. Изучение Unix из Unix Вы можете изучать принципы работы любой команды, используя для этого четыре возможности: • Чтение справочника. > , ¦ Поиск в справочнике.. • Чтение файлов с именами, имеющими расширение .h. Использование ссылок из секции see also. Мы будем далее использовать эти возможности для изучения команды who. Чтение из справочника Для изучения команды who следует набрать $ man who и обратиться к секции DESCRIPTION. В справочнике для SunOS текст этой секции будет иметь такой вид: . DESCRIPTION The who utility can list the user's name, terminal line, login time, elapsed time since activity occurred on the line, and the process-ID of the command interpreter (shell) for each current UNIX system user. It examines the /var/adm/utmp file to obtain its information. If file is given, that file (which must be in utmpD) format) is exam-ined. Usually, file will be /var/adm/wtmp, which contains a history of all the logins since the file was last created.
2.4 Вопрос 2: Как работает команда who? 57 Из данного документа мы получаем полную информацию о команде. Команда who проверяет файл /var/adm/utmp для извлечения для себя из него необходимой информации. Из текста описания следует, что список текущих пользователей хранится в этом файле. Команда читает файл. Что нам следует знать об этом файле? Для этого мы можем обратиться к справочнику и найти необходимую информацию. Поиск в справочнике Команда man допускает возможность обращения к справочнику для организации поиска по ключевым словам. Для организации поиска следует использовать опцию -к. Чтобы получить информацию о 'utmp', следует выполнить: $ man -к utmp -access utmp file entry -access utmpx file entry -access utmp file entry -access utmp file entry -access utmp file entry -access utmpx file entry -access utmpx file entry -access utmpx file entry -access utmpx file entry -access utmpx file entry -access utmp file entry -access utmpx file entry -access utmp file entry -access utmpx file entry -find the slot in the utmp file of the current user -access utmpx file entry -access utmpx file entry utmp and wtmp entry formats overview of accounting and miscellaneous accounting commands -utmp and utmpx monitoring daemon -access utmp file entry utmpx and wtmpx entry formats -access utmpx file entry utmp and wtmp entry formats utmpx and wtmpx entry formats Полученный результат работы команды был получен на SunOS. Такой вывод будет представлен в аналогичном виде и на других инсталляциях. Каждая строка в этом выводе содержит тему, название страницы справочника и краткое описание. Те строки, которые помечены метками utmp и wtmp, возможно, представляют то, что нам необходимо. Другие записи с похожими метками могут нам понадобиться позже. Нотация utmp D) означает, что документация по utmp находится в разделе 4 справочника. Этот номер раздела следует использовать при обращении к команде man: endutent endutxent getutent getutid getutline getutmp getutmpx getutxent getutxid getutxline pututline pututxline setutent setutxent ttyslot updwtmp updwtmpx utmp utmp2wtmp utmpd utmpname utmpx utmpxname wtmp wtmpx $ getutent Cc) getutxent Cc) getutent Cc) getutent Cc) getutent Cc) getutxent Cc) getutxent Cc) getutxent Cc) getutxent Cc) getutxent Cc) getutent Cc) getutxent Cc) getutent Cc) getutxent Cc) ttyslot Cc) getutxent Cc) getutxent Cc) utmp D) acctAm) utmpd Am) getutent Cc) utmpx D) getutxent Cc) utmp D) utmpx D)
58 Пользователи, файлы и справочник. Что рассматривать в первую очередь? $ man 4 utmp utmpD) utmpD) * NAME utmp, wtmp - Login records SYNOPSIS #include <utmp.h> DESCRIPTION The utmp file records information about who is currently using the system. The file is a sequence of utmp entries, as defined in struct utmp in the utmp.h file. The utmp structure gives the name of the special file associated with the user's terminal, the user's login name, and the time of the login in the form of timeC). The utjype field is the type of entry, which can specify several symbolic constant values. The symbolic constants are defined in the utmp.h file. The wtmp file records all logins and logouts. A null user name indicates a logout on the associated terminal. A terminal referenced with a tilde (~) indicates that the system was rebooted at the indicated time. The adjacent pair of entries with terminal names referenced by a vertical bar (|) or a right brace (}) indicate the system-maintained time just before and just after a dale command has changed the system's time frame. The wtmp file is maintained by loginA) and init(8). Neither of these pro-grams creates the file, so, if it is removed, record keeping is turned off. See ac(8) for information on the file. FILES ' ' /usr/include/utmp.h /Var/adm/utmp more (88%) Мы достаточно быстро ответили на вопрос, как работает команда who. На первой странице документации по команде who сказано, что команда читает файл utmp. Здесь сказано, что файл utmp представляет собой последовательность записей utmp, которые определены в структуре utmp в файле utmp.h. Где же находится этот файл utmp.h? Нам повезло. В разделе FILES на странице документации есть нужная информация. Там указано маршрутное имя файла /usr/include/utmp.h. Прежде чем перейти к рассмотрению следующей возможности для работы со справочником (чтение файлов с расширением имен .h), обратимся еще к некоторой информации на данной странице документации. Речь идет о файле wtmp, куда происходит запись обо всех входах в систему и выходах из системы. Для работы с файлом указаны ссылки на команды login(l), init(8) и ас(8). Их рассмотрение будет интересно при изучении тем, которые будут представлены позже. Изучение Unix по справочнику аналогично поиску информации о каком-то объекте в Web. По мере чтения различных страниц справочника вы находите дополнительные ссылки, с помощью которых можете обратиться к интересующим вас и полезным для вас темам. Именно так, в соответствии с нашими задачами, мы подошли к изучению файла <utmp.h>.
2.4. Вопрос 2: Какработаеткоманда who? 59 Чтение файлов .h В документации по utmp сказано, что структура записей в файле utmp описана в файле /usr/include/utmp.h. В большинстве Unix - машин заголовочные файлы для системной информации хранятся в каталоге, который называется /usr/include. Когда С - компилятор обнаруживает в тексте программы строку вида: #include <stdio.h> он предполагает, что этот файл находится в каталоге /usr/include. Используем команду more для прочтения содержимого этого файла: $ more /usr/include/utmp.h 4 «define ШМР_Н1?иАаг/Мя0Лпр" «define WTMPJILE >ar/adm/Wtmp" «include <sysAypes.h> Г for pid t, time 17 Г * Структуры файлов utmp и wtmp. «define ut_name ut_user struct utmp { charut_user[32]; charutjd[14]; «charutjine[32]; shortutjype; pidj utj)id; struct exit_status { short extermination; short e_exit; } ut_exit; time J utjime; char ut_host[64]; /* совместимость */ /* Пользовательское входное имя 7 Л /etc/inittab id- IDENT^LEN в * init */ Г имя устройства (console, Inxx) 7 Г тип записи 7 Г идентификатор процесса 7 /* статус окончания процесса 7 /* статус процесса при выполнении exit 7 Г Статус exit процесса, помеченного как * DEAD PROCESS. ' 7 Г временная отметка о сделанной записи 7 Г имя хоста, такое же как ¦* MAXHOSTNAMELEN 7 }; Г Определения для utjype 7 utmp.hF0%) В начале в данном выводе пропущен ряд сообщений и другой вводный материал. Далее мы обнаруживаем определение структуры. Оказывается, записи о вхождениях в систему состоят из восьми элементов. Поле ut_user предназначено для хранения пользовательского имени. В массиве ut_line помещается информация об устройстве, что в данном случае будет означать терминал, через который пользователь соединен с системой. Через несколько строк в структуре представлено поле utjime, где хранится время вхождения в систему, а поле ut__host предназначено для хранения имени удаленного компьютера.
60 Пользователи, файлы и справочник. Что рассматривать в первую очередь? В рассматриваемой структуре есть еще и другие элементы. Они напрямую не используются для отображения информации командой who, но могут быть полезными в других ситуациях. Структура записи utmp на вашей системе может отличаться от рассмотренной. Но файл ut- mp.h на вашей системе будет описывать формат данных utmp для вашей системы. Имена полей обычно одинаковы для различных версий Unix, но наличие поля, которое имеет комментарий "совместимость", показывает, что они иногда могут и отличаться. Заголовочные файлы обычно снабжены хорошими комментариями, которые содержат полезную информацию. 2.4.7. Мы теперь знаем, как работает who При чтении электронной документации по темам who и utmp и просмотре заголовочного файла /usr/include/utmp.h, мы изучили, как работает команда who. Команда who читает структуры из файла. Файл содержит для каждой сессии по одной структуре. Мы изучили формат структуры. Поток информации изображен на рисунке 2.2. Рисунок 2.2 Поток данных для команды who Файл - это массив, откуда who может читать записи и выводить требуемую информацию. По самой простой логике следовало бы читать и выводить записи по одной. Было бы это проще? Мы не рассматривали исходный код для версии команды who, но у нас была возможность изучить все, что касается команды, из электронной документации. Из справочника мы узнали, что делает команда, и также рассмотрели, как используется структура данных в заголовочном файле. Единственная возможность проверить, действительно ли вам все понятно, — это попытаться сделать что-то самому. 2.5. Вопрос 3: Могу ли я написать who? В следующей части этой главы мы попытаемся создать программу, которая должна работать аналогично стандартной команде who. Мы продолжим обучение, используя обращение к справочнику, и проверим нашу программу, сверяя ее вывод и вывод из версии команды who на нашей системе. Проведенный анализ программы who показал, что есть только две задачи, которые необходимо выполнять в программе: • Чтение структур из файла. • Отображение инсЬоомашш. котооая хоанится в стоуктуое.
2.5 Вопрос 3: Могу ли я написать wfio? 61 2.5./. Вопрос: Как я буду читать структуры из файла? Для чтения символов и строк из файла вы можете использовать getc и f gets. Что представляют собой структуры с позиций данных? Мы можем использовать getc для посимвольного чтения, но это довольно скучное занятие. Хотелось бы читать сразу всю структуру с диска. Давайте почитаем справочник! Нам необходимо найти страницы справочника, относящиеся к file и read. С помощью опции -к можно задавать только одно ключевое слово, поэтому мы укажем только одно из ключевых слов и выполним. $ man -k file для просмотра предлагаемых тем. Нам будет выдан перечень тем, касающихся файлов. На моей системе по этой команде был получен результирующий вывод из 537 строк. Из этих строк нам нужно выбрать строки, где содержится слово "read". В Unix есть команда grep, * которая будет выводить строки, где содержится заданный шаблон. Используем в конвейере команду grep следующим образом: $ man -k file | grep read JlseekB) - reposition read/Write file offset fileevent(n) - Execute a script when a channel becomes readable or writable gftype A) - translate a generic font file for humans to read lseekB) - reposition read/Write file offset macsaveA) - Save Mac files read from standard input read B) - read from a file descriptor readprofile A) - a tool to read kernel profiling information scr_dump, scrjestore, sennit, scr_set C) - read (write) a curses screen from (to) a file tee A) - read from standard input and write to standard output and files $ Наиболее значимую информацию среди этих строк содержит readB). В других строках речь идет о других темах. Выберем страницу документации в разделе 2 относительно read: $ man 2 read READB) System calls READB) NAME read - read from a file descriptor SYNOPSIS #include <unistd.h> ssizej read(int fd, void *buf, sizej count); DESCRIPTION read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf. If count is zero, read() returns zero and has no other
62 Пользователи, файлы и справочник. Что рассматривать в первую очередь? results. If count is greater than SSIZE_MAX, the result is unspecified. RETURN VALUE On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number. It is not an error if this number is smaller than the number of bytes requested; this may hap-pen for example because fewer bytes are actually available right now (maybe because we were close to end-of-file, or because we are reading from a pipe, or from a terminal), or because read() was interrupted by a signal. On error, -1 is returned, and errno is set appropriately. In this case it is left unspecified whether the file position (if any) changes. С помощью этого системного вызова мы можем прочитать заданное число байт из файла в буфер. Нам необходимо считать за один раз. одну структуру, поэтому мы можем использовать sizeof (struct utmp) для определения того числа байтов, которое необходимо прочитать. В найденной документации сказано, что системный вызов read производит чтение из файлового дескриптора. Как мы можем получить один из них? При просмотре страницы документации относительно read мы обнаружим в последней ее части следующее: RELATED INFORMATION (called SEE ALSO in some versions) Functions: fcntlB), creatB), dupB), ioctlB), getmsgB), lockfC), lseekB), mtioG), openB), pipeB), pollB), socketB), socketpairB), termiosD), streamioG), opendirC) lockfC) Standards: standardsE) Здесь мы обнаруживаем ссылку на орепB). Запускаем на исполнение команду: man 2 open, чтобы прочитать, как работает open. Из этой страницы документации есть ссылка на close. Итак, при работе с электронным справочником мы нашли три части, которые необходимы нам для чтения структуры из файла. 2.5.2. Ответ: Использование open, read и close Мы можем использовать эти три системных вызова для извлечения из файла utmp записей о вхождениях в систему. Страницы справочника, касающиеся этих тем, могут быть весьма краткими по содержанию. Эти системные в!ызовы имеют много опций и достаточно сложны в своем поведении, когда они используются в отношении программных каналов, устройств и других источников данных. Основополагающие факторы выделяются и рассматриваются далее. Открытие файла: open Системный вызов open создает связь между процессом и файлом. Эта связь называется дескриптором файла и изображается на рисунке 2.3 в виде туннеля от процесса к ядру.
2.5 Вопрос 3: Могу ли я написать who? 63 Процесс Дескриптор файлов ^: ^ JT 1 ¦ » ':. :'.¦¦'¦'•*. k h ¦ Щ%. <* -у-.:-,--^^-<:Л: 7 Массив символов Рисунок 2.3 Дескриптор файла - это соединение с файлом. Основные свойства системного вызова open: НАЗНАЧЕНИЕ INCLUDE ИСПОЛЬЗОВАНИЕ АРГУМЕНТЫ КОДЫ ВОЗВРАТА open Создания связи с файлом #include <fcntl.h> Int fd = open(char *name, int how) name - имя файла how - 0_RDONLY, O.WRONLY или 0_RDWR -1 -при ошибке Целое число - при успехе Для открытия файла необходимо определить имя файла и тип желаемой связи. Существуют три типа связей - соединение для чтения, соединение для записи и соединение для чтения и записи. В заголовочном файле /u$r/include/fcntl.h находятся определения для макросов 0_RDONLY, OJ/VRONLY и OJTOWR. Открытие файлов - это служба ядра. Системный вызов open - это требование, которое выдает ваша программа ядру. Если ядро обнаружит ошибку при обращении к нему, то оно вернет код возврата, равный -1. Есть несколько видов ошибок. Может случиться, что указанный файл не существует. Файл может существовать, но у вас нет прав доступа на чтение из этого файла. Файл может находиться в каталоге, к которому у вас нет доступа. В странице документации по системному вызову open приведен список подобного рода ошибок. Способы обработки ошибок будут изучены далее в этой главе. Что происходит, если файл уже был открыт? То есть что будет в ситуации, когда другой процесс уже работает с файлом? В Unix не устанавливается запрета на одновременное открытие несколькими процессами одного и того же файла. Если бы такое ограничение существовало, то двум различным процессам нельзя было бы запустить одновременно одну и ту же команду who. Если открытие происходит успешно, то ядро возвращает процессу небольшое по значению целое положительное число. Это число называют дескриптором файла, который является по смыслу идентификатором соединения процесса с файлом.
64 Пользователи, файлы и справочник. Что рассматривать в первую очередь? Вы можете одновременно открыть несколько файлов. При этом для каждого соединения будет установлен уникальный дескриптор файла. Ваша программа даже может многократно отрыть один и тот же файл. При этом для каждого соединения будет установлен свой дескриптор файла. Вы можете использовать дескриптор файла для всех операций с установленным соединением. Чтение данных из файла: read Вы можете в процессе производить чтение данных, используя дескриптор файла: НАЗНАЧЕНИЕ INCLUDE read Пересылка qty байт из файлового дескриптора fd i #include <unistd.h> ИСПОЛЬЗОВАНИЕ ssizej numread = read(int fd, void *buf, АРГУМЕНТЫ КОДЫ ВОЗВРАТА fd-источник данных buf - место для сохранения данных qty - количество байт для передачи -1 -при ошибке Целое число - при успехе sizej qty) з буфер С помощью системного вызова read происходит обращение к ядру для передачи qty байтов данных из файлового дескриптора fd в массив buf, который находится в пространстве памяти вызывающего процесса. Ядро выполняет действие по запросу и возвращает информацию о результате выполнения. Если требование не было выполнено, то код возврата будет равным -1. В противном случае в качестве кода возврата будет число байтов, переданных при чтении. Почему можно получить в ответ меньшее число байтов, чем было запрошено? В файле может не быть столько байтов, сколько вы указали при обращении к системному вызову. Например, если вы запросили 1000 байтов, а в файле содержится только 500 байтов, то после выполнения вызова вы увидите в качестве результата 500 байтов. При достижении конца файла системный вызов вырабатывает код возврата, равный нулю, поскольку нет данных для чтения. Какового сорта ошибки может фиксировать системный вызов read? Ответ можно найти на странице документации в вашей системе, где приведен перечень ошибок. Закрытие файла: close Когда вы прочитали данные или записали данные через файловый дескриптор, то вы можете закрыть его. Системный вызов close представлен такими характеристиками: НАЗНАЧЕНИЕ INCLUDE ИСПОЛЬЗОВАНИЕ АРГУМЕНТЫ close Закрытие файла #include <unistd.h> int result = close(intfd) fd - дескриптор данных buf - место для сохранения данных qty - количество байт для передачи КОДЫ ВОЗВРАТА -1 - при ошибке О - при успехе
2.5Вопрос3: Могу ли я написать who? 65 Системный вызов close отключает соединение, которое было определено с помощью файлового дескриптора fd. При обнаружении ошибки системный вызов close возвращает код возврата -1. Например, при попытке закрыть файловый дескриптор, который не ссылается на открытый файл, будет выработана ошибка. Другие виды ошибок описаны в справочнике. 2.5.3. Написание программы who 1. с Итак, мы почти у цели. Мы знаем суть работы команды who, мы знаем о существовании трех системных вызовов, необходимых для установления связи с файлом, выбора данных из файла и для закрытия файла. Ведущая часть кода программы будет выглядеть так: Г whol .с - первая версия программы who * выполнить open, прочитать файл UTMP и показать результаты 7 #include <stdio.h> #include <utmp.h> #include <fcntl.h> tinclude <unistd.h> #define SHOWHOST /* подключить удаленную машину для вывода */ int main() { struct utmp current jecord; /* считывать сюда данные 7 int utmpfd; /* читать из этого дескриптора 7 int reclen = sizeof(current record); if ((utmpfd = open(UTMP>ILE, O.RDONLY)) ==-1){ perror(UTMPJILE); /* UTMP_RLE - описание в utmp.h 7 exitA); } - while (read(utmpfd, «currentjecord, reclen) == reclen) show_info(&currentjecord); close(utmpfd); return 0; /* все нормально 7 } В этой программе реализована логика, которая была рассмотрена выше в этой главе. В цикле while производится последовательное чтение записей из файлового дескриптора current jecord. Функция showjnfo отображает информацию о вхождениях в систему. Программа работает в цикле до тех пор, пока системный вызов read в состоянии читать записи из файла. Наконец, происходит закрытие файла и выход из программы. Системный вызов perror является удобным средством для оповещения о наличии системных ошибок. Мы рассмотрим его далее в этой главе. 2.5.4. Отображение записей о вхождениях в систему Далее приведен код первого наброска функции show_info, которая производит отображение информации из файла utmp. Г * show infoO
66 Пользователи, файлы и справочник. Что рассматривать в первую очередь? отображает содержимое структуры utmp в формате, удобном для восприятия * эти размеры аппаратно не зашиты 7 show info(struct utmp *utbufp) { printf("%-8.8s",utbufp->ut_name); printfC"); printfC^-e.es", utbufp->utjine); printf(""); printf(H%1 Old", utbufp->utjime); printf(""); #ifdefSHOWHOST printf(M(%s)"f utbufp->utjwst); #endif printf("\n"); } Мы выбрали в этой программе ширину полей для printf так, чтобы было соответствие с длинами строк вывода системной версии программы who. Программа выводит элемент utjime в формате long int. Значение time_t определено в заголовочном файле, но мы пока ничего об этом не знаем. Компилируем и запускаем программу на исполнение: I* входное имя */ Г пробел 7 /* терминал 7 Л пробел 7 Г время вхождения 7 /* пробел 7 /* ххт 7 Г перевод на новую строку */ $ccwho1.c-o $who1 system b run-leve whol LOGIN console ttypl shpyrko ttyp2 acotton ttyp3 ttyp4 spradlin ttyp5 dkoh ttyp6 spradlin ttyp7 king ttyp8 berschba ttyp9 rserved ttypa riahpl ttvnh 952601411() 9526014110 952601416 0 952601416 0 952601417 0 952601417 0 952601419 0 952601419 0 952601423 () 952601566 0 952601566 0 958240622 () 964318862 (nas1-093.gas.swamp.org) 964319088 (math-guest04.williams.edu) 964320298 0 963881486 (h002078c6adfb.ne.rusty.net) 964314388A28.103.223.110) 964058662 (h002078c6adfb.ne.rusty.net) 964279969 (blade-runner.mit.edu) 964188340 (dudley.learned.edu) 963538145 (gigue.eas.ivy.edu) Qfi431 QARR /rnamlQ3-97 student state erin\
2.5 Вопрос 3: Могу ли я написать who? 67 ttypc rserved ttypd dkoh ttype ttypf molay' ttyqO ttyql ttyq2 ttyq3 ttyq4 ttyqS ttyq6 ttyq7, cweiner ttyq8 964319645 0 963538287 (gigue.eas.ivy.edu) 964298769A28.103.223.110) 964314510 0 964310621 (xyz73-200.harvard.edu) 964311665 0 964310757() 964304284 0 964305014 0 964299803 () 964219533 0 964215661 () 964212019 (roam175-157.student.stats.edu) ttyqa 964277078 0 ttyq9 964231347 0 $ Давайте сравним вывод нашей программы с выводом системной версии команды who: $who shpyrko ttyp2 Jul acotton ttyp3 Jul spradlin ttypS Jul . dkoh ttyp6 Jul spradlin ttyp7 Jul king ttyp8 Jul berschba ttyp9 Jul rserved ttypa Jul dabel ttypb Jul rserved ttypd Jul dkoh ttype Jul molay ttyqO Jul cweiner ttyq8 Jul $ 22 22:21 (nas1-093.gas.swamp.edu) 22 22:24 (math-guest04.williams.edu) 17 20:51 (h002078c6adfb.ne.rusty.net) 22 21:06 A28.103.223.110) 19 22:04 (h002078c6adfb.ne.rusty.net) 2211:32 (blade-runner.mit.edu) 21 10:05 (dudley.learned.edu) 13 21:29 (gigue.eas.ivy.edu) 22 22:30 (roam193-27.student.state.edu) 13 21:31 (gigue.eas.harvard.edu) 2216:46 A28.103.223.110) 22 20:03 (xyz73-200.harvard.edu) 21 16:40 (roam175-157.student.stats.edu) Наша версия выглядит как перспективная, но все еще не в полном виде. Есть еще шероховатости, которые следует ликвидировать. У нас выводятся те же пользовательские имена, как и в who. У нас выводятся Правильные имена терминалов, правильно указываются имена удаленных машин. Но есть две проблемы. Что нам следует еще сделать: Подавить пустые записи. Получить корректное представление времени вхождения в систему. 2.5.5. Написание версии who2. с В версии 2 нашей программы who внимание уделяется двум проблемам, о которых шла речь в версии 1. И вновь мы будем решать эти проблемы, обращаясь к необходимым документам справочника и заголовочным файлам.
68 Пользователи, файлы и справочник. Что рассматривать в первую очередь? Подавление пустых записей В реальной версии команды who выводится список пользовательских имен тех пользователей, которые входили в систему. В нашей версии программы выводится список из того, что программа находит в файле utmp. Файл utmp содержит записи, касающиеся всех терминалов, даже тех, которые не используются. Необходимо изменить нашу программу так, чтобы она не выводила записи о неиспользуемых терминальных линиях. Но как определить, какая из utmp - записей не представляет активную сессию? Самое простое решение (которое не работает) - пропускать записи с пробелами в поле пользовательского имени. Это будет работать в большинстве случаев, но на экран не будет выводиться запись с полем LOGIN в строке, которая относится к консоли. Лучшим решением (которое работает) будет, если выбирать для вывода только те utmp - записи, которые соответствуют пользователем, вошедшим в систему. Обратимся к файлу /usr/include/utmp.h и мы обнаружим там следующее: Г Определения для ut type #define EMPTY #defineRUN LVL #define BOOT TIME #defineOLDTlME #define NEW TIME •define INIT PROCESS •define LOGIN PROCESS •define USER PROCESS •define DEADPROCESS Этот список весьма полезен. В каждой записи есть поле с именем utjype. Значения, которые могут находиться в этом поле, и их символические имена представлены в приведенном выше списке. Тип 7 будет для нас счастливым номером. Если теперь мы сделаем нижеследующие небольшие изменения в нашей функции showjnfo, то пробельные записи' должны исчезнуть: show info(struct utmp *utbufp) { if (utbufp->utjype != USER.PROCESS) /* только пользователи! */ return; printf("%-8.8s", utbufp->ut_name); /* имя пользователя */ Отображение времени вхождения в систему в удобном для прочтения виде Теперь решим проблемы представления времени в формате, который воспринимаем людьми. Начнем поиск в справочнике и поиск заголовочных файлов. Страниц по теме "time" во всех версиях Unix весьма много, и они разнообразны. После набора $ man-k time получим много записей. На одной своей машине я получил 73 записи, а на другой машине получил 97. Вы можете просмотреть этот длинный список или можете отфильтровать полученный вывод. Следующие ниже конвейеры прекрасно проведут фильтрацию: $ man -k time | grep transform $ man -к time j grep -i convert 7 о 1 2 3 4 5 /* Процесс был порожден процессом "init" 7 6 Г Процесс "getty" ждет login 7 7 /* Пользовательский процесс 7 8
2.5 Вопрос 3: Могу ли я написать who? / 69 Через справочник выходим на необходимые заголовочные файлы. Файл /usr/include/time.h есть на ряде систем Unix. Проверьте вашу систему относительно информации, касающейся темы *4ime". Нам же нужно обсудить вопрос Как в Unix хранится значение времени: тип данных time J В Unix значение времени представляется целым числом, которое измеряет в секундах интервал времени с полуночи первого января 1970 года по Гринвичу. Тип данных timej - это целочисленное представление времени в секундах. Этот формат в Unix используется во многих приложениях. Поле ut_time в utmp записях содержит время вхождения в систему, которое представлено числом секунд с начала Эпохи. Преобразование timej в читаемый формат: ctime Есть функция ctime, которая преобразует значение времени в секундах от начала работы системы Unix в значение времени в читабельном формате. Функция описана в разделе 3 электронного справочника. $ man 3 ctime CTIMEC) Linux Programmer's Manual CTIMEC) NAME asctime, ctime, gmtime, localtime, mktime - transform binary date and time to ASCII SYNOPSIS #include <time.h> char *asctime(const struct tm *timeptr); char *ctime(const timej *timep); struct tm *gmtime(const timej *timep); struct tm *localtime(const timej *timep); timej mktime(struct tm *timeptr); extern char *tzname[2]; long int timezone; extern int daylight; DESCRIPTION The ctime(), gmtimeQ and localtime() functions all take an argument of data type timej which represents calendar time. When interpreted as an absolute time value, it rep-resents the number of seconds elapsed since 00:00:00 on January 1,1970, Coordinated Universal Time (UTC). The ctime() function converts the calendar time timep into a string of the form 4№киип3021:49:081993\пи The abbreviations for the days of the week are Sun, Mon, Tue, Wed, Thu, Fri, and Sat. The abbre-viations for the months are Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, and Dec. The return value points to a statically allocated string which might be overwritten by subsequent calls to any of the date and time functions. The function also
70 Пользователи, файлы исправочник. Чторассматривать в первую очередь? Вот это то, что нам необходимо. Мы имеем значение time_t в записях utmp. А нам требуется строка в формате, подобном такому: Jun 30 21:49 Функция ctimeC) выбирает указатель на timej, а при окончании возвращает указатель на строку, которая будет выглядеть примерно так: Wed Jun 30 21:49:08 1993\n Заметим, что строка, которая нам нужна для работы who, вставляется в строку возврата функции ctime. Это позволяет достаточно просто производить кодировку даты для who. Мы обращаемся к функции ctime и получаем после ее работы строку из 12 символов со смещением, равным 4. Это выполняется при выполнении оператора printf(a%12.12s", ctime(&t)+4). Одновременный вывод всего сразу Теперь мы знаем, как подавить пустые записи, и знаем, как отобразить значение utjime в читабельном виде. Далее представлена окончательная версия программы who2.c: Г who2.c - читает файл /etc/utmp и выводит список информации из него * - подавляет пустые записи v * - правильно форматирует время 7 #include <stdio.h> #include <unistd.h> #include <utmp.h> #include <fcntl.h> #include <time.h> r#defineSH0WH0ST7 void showtime(long); void showJnfo(struct utmp *); int main() { struct utmp utbuf; /* сюда читается информация */ int utmpfd; /* чтение происходит из этого дескриптора 7 if((utmpfd = open(UTMP FILE, O.RDONLY)) == -1){ perror(UTMP_FILE); exitA); } while(read(utmpfd, &utbuf, sizeof(utbuf)) == sizeof(utbuf)) showJnfo(&utbuf); close(utmpfd); return 0; } Г showinfo() * отображает содежимое структуры utmp
2.5 Вопрос 3: Могу ли я написать who? 71 * в удобном для восприятия виде * * ничего не отображает, если в записи нет имени пользователя*/ void show_info(struct utmp *utbufp) { if (utbufp->ut_type != USER_PROCESS) return; printf("%-8.8s", utbufp->ut_name); /* входное имя */ printf(,,M); /* пробел*/ printf("%-8.8s'\ utbufp->utjine); /* терминал */ printff'"); /* пробел*/ showtime(utbufp->utjime); /* отображение времени */ #ifdef SHOWHOST if (utbufp->ut_host[0] != ЛО') printff' (%s)"f utbufp->ut_host); /* хост */ #endif printf("\nn); /* перевод на новую строку */ } void showtime(long timeval) Г * отображает время в формате, удобном для восприятия " использует функцию ctime для формирования строки с изображением времени * Замечание: посредством формата %12.12s выводится строка из 12 символов, * при значении LIMITS, равно 12 символов. v ¦ { char*cp; /* адрес со значением времени 7 ср = ctime(&timeval); /* преобразование значения времени в строку */ Г строка должна иметь приблизительно такой вид */ Л Mon Feb 4 00:46:40 EST 1991 */ Л 0123456789012345. */ printf("%12.12s", cp+4); /* вывести 12 символов с позиции 4 */ ) Тестирование программы who2.c Рткомпилируем и запустим на исполнение программу who2.c. Для разнообразия выключим настройку SHOWHOST. Далее запустим на исполнение системную версию команды who и сравним полученные результаты: $ccwho2.c-owho2 $who2 rlscott ttyp2 Jul 23 01:07 acotton ttyp3 Jul 22 22:24 spradlin ttyp5 Jul 17 20:51 spradlin ttyp7 Jul 19 22:04 king ttyp8 Jul 2211:32
72 Пользователи, файлы и справочник. Что рассматривать в первую очередь? berschba ttyp9 rserved ttypa rserved ttypd molay ttyqO cweiner ttyq8 mnabavi ttyx2 $who rlscott ttyp2 acotton ttyp3 spradlin ttyp5 spradlin ttyp? king ttyp8 berschba ttyp9 rserved ttypa rserved ttypd molay ttyqO cweiner ttyq8 mnabavi ttyx2 Jul 21 10:05 Jul 1321:29 Jul 13 21:31 Jul 22 20:03 Jul 21 16:40 Apr 10 23:11 Jul 23 01:07 Jul 22 22:24 Jul 17 20:51 Jul 19 22:04 Jul 22 11:32 Jul 21 10:05 Jul 13 21:29 Jul 13 21:31 Jul 22 20:03 Jul 21 16:40 Apr 1023:11 $ Есть некоторое отличие в форматировании результатов. В различных версиях команды who используются различные по ширине колонки при выводе результатов. При изменении размеров колонок протокола вывода мы можем в точности добиться совпадения формата вывода по отношению к стандартному варианту. Можете заняться этим на своей системе. В некоторых версиях команды who производится вывод имени хоста для удаленной системы, если такая система была зафиксирована. В других же версиях такое имя не выводится. Программа выдает точный список пользователей, имена их терминальных линий и времена вхождения пользователей в систему. 2.5.6. Взгляд назад и взгляд вперед Мы начали эту главу с постановки простого вопроса: "Как работает в Unix команда who?" Мы следовали в тексте трем сформулированным шагам. Во-первых, мы изучили, что делает команда. Затем мы разобрались, посредством детального изучения технической документации, как работает команда. Далее написали собственную версию программы, чтобы убедиться в том, что мы действительно понимаем, к^к работает команда. По мере нахождения решений на каждом из трех шагов мы научились использовать электронный справочник Unix и заголовочные файлы. Написание собственной версии программы привело к закреплению рассмотренного материала. Стала ясной структура файла utmp. Мы убедились в том, что каждое вхождение в систему приводит к появлению записи в журнале. Мы изучили, каким образом в Unix представляются временные величины. Это будет полезно при работе с другими частями Unix. Наконец, мы почитали документацию по надлежащим темам. На страницах справочника для файла utmp были найдены ссылки на файл wtmp. А со страниц справочника для функции ctime есть ссылки на другие функции, которые связаны со временем. Эти ссылки дают дополнительное представление о структуре системы.
2.6. Проект два: Разработка программы ср (чтение и запись) 73 2.6. Проект два: Разработка программы ср (чтение и запись) В программе who мы только читали из файла. А как можно будет записывать в файл? Для изучения возможности записи в файлы мы разработаем версию Unix команды ср. 2.6.1. Вопрос 1: Что делает команда ср? Команда ср выполняет копирование файла. Типичное обращение к команде будет таким: $ ср исходный _файл целевой_файл Если нет целевого файла, то команда ср создает его. Если целевой файл есть, то команда ср заменяет содержимое этого файла содержимым исходного файла. 2.6.2. Вопрос 2: Как команда ср создает файл и как пишет в него? Создание/транкатенация файла Один из способов создания файла или перезаписи файла является использование для этого системного вызова creat. Обобщенные характеристики системного вызова: НАЗНАЧЕНИЕ INCLUDE ИСПОЛЬЗОВАНИЕ АРГУМЕНТЫ КОДЫ ВОЗВРАТА creat Создание или уничтожение файла tlnclude < fcntl.h > int fd = creat(char *filename, modej mode) filename: имя файла mode: права доступа -1-при ошибке fd - при успехе Системный вызов creat открывает файл с именем filename на запись. Если до этого не было файла с таким именем, то ядро создает файл. Если же есть файл с таким именем, то ядро уничтожает его содержимое, сокращая (транкатинируя) его размер до нуля. Если ядро создает файл, то оно устанавливает разряды прав доступа к файлу в соответствии со значением второго аргумента2, который задается при обращении к системному вызову. Например: fd = creatf'addressbook", 0644); Будет создан или транкатенирован файл с именем addressbook. Если до этого файл не существовал, права доступа будут такими: rw-r-r-: (Смотри детали в главе 3.) Если же файл с указанным именем существовал, то он становится пустым, а права доступа не меняются. В любом случае через файловый дескриптор fd файл будет открыт только на запись. 2. На самом деле разряды прав доступа модифицируются процессом с помощью системного вызова umask. См. главу 3.
74 Пользователи, файлы и справочник. Что рассматривать в первую очередь? Запись в файл Передача данных в открытый файл производится с помощью системного вызова write: НАЗНАЧЕНИЕ INCLUDE ИСПОЛЬЗОВАНИЕ АРГУМЕНТЫ КОДЫ ВОЗВРАТА write #inc!ude < unistd.h > ssizej result = writefint fd, void *buf, size J amt) fd - файловый дескриптор but - массив amt - количество байт для записи -1 - при ошибке Количество записанных байт - при успехе Системный вызов write копирует данные из памяти процесса в файл. Если ядро не может ил и.не хочет копировать данные, то системный вызов write возвращает код -1. Если ядро переслало данные в файл, то системный вызов возвращает в качестве кода возврата количество байтов, переданных в файл. Почему может быть различие между количеством переданных байтов и тем значением, которое было заказано для передачи? Есть несколько обстоятельств, которые могут это прояснить. В системе может быть установлен предел на максимальный размер файла, который может создавать пользователь, или может быть недостаточно места на диске по отношению к затребованному значению. Если в системном вызове будет записано требование на размер, которое превышает предел или размер свободного пространства на диске, то системный вызов write запишет столько байтов, сколько он сможет, а затем остановится. В вашей программе всегда необходимо сравнивать количество байтов, которое вы запрашиваете для пересылки в файл, с числом байтов, которое действительно туда было передано. Если эти значения оказываются разными, то программа должна предусматривать реакцию на эту ситуацию. 2.6.3. Вопрос 3: Могу ли я написать программу ср? Проверим, насколько было все понятно, путем создания версии программы ср. Схема работы программы будет такой: открытие исходного файла для чтения открытие целевого файла на запись +- > чтение из исходного файла в буфер - - eof? -+ !_ запись из буфера в файл | закрыть исходный файл < + закрыть целевой файл На рисунке 2.4 показаны потоки данных при копировании:
2.6. Проектдва:Разработкапрограммыср(чтениеизапись) • 75 Рисунок 2.4. Копирование файлов посредством чтения и записи Файлы расположены на диске. Исходный файл находится слева на схематическом изображении диска, а целевой файл - справа. Буфер представляет собой область памяти в пределах среды процесса. Процесс располагает двумя файловыми дескрипторами. Данные читаются из исходного файла в буфер, а затем записываются из буфера в целевой файл. Наконец, программный код, который соответствует изображению на рисунке, будет таким: /**ср1.с * версия 1 программы ср - использует read и write при работе с буфером с * настраиваемым размером * * usage: cp1 srcdest 7 #include <stdio.h> , #!nclude <unistd.h> #include <fcntl.h> #defineBUFFERSIZE4096 #defineCOPYMODE0644 void oops(char *, char *); main(intac, char*av[]) { int in fd, out fd, n chars; charbuf[BUFFERSIZE]; Г проверка аргументов */ if (ас != 3){ fprintf(stderr, "usage: %s source destination^", *av); exitA); } Г открытие файлов */ if ((in_fd=open(av[1 ], O.RDONLY» == -1) oopsfCannot open", av[1]);
76 . Пользователи, файлыисправочник. Чтоу рассматривать впервуюочередь? if ((out.fd=creat(av[2], COPYMODE)) == -1) oops("Cannot creaf, av[2]); /* копирование файлов */ while ((n_chars = read(injd, buf, BUFFERSIZE)) > 0) if (write(out_fd, buf, n_chars) != n_chars) oops("Write error to", av[2]); if (n^chars == -1) oops("Read error from.", av[1]); Г закрытие файлов */ if (close(injd) == -11| close(out_fd) == -1) oopsf'Error closing files",'"*); } void oops(char *s1, char *s2) { fprintf<stderr/,Error:%s"ls1); perror(s2); exitA); } Откомпилируем и проверим работу программы: $ ее ср1 .с-о ср1 $ ср1 ср1 copy.of.cp1 $ Is -I cp1 copy.of.cp1 -rw-r-r- 1 bruce bruce 37419 Jul 23 03:12 copy.of.cp1 • -rwxrwxr-x 1 bruce bruce 37419 Jul 23 03:08 cp1 $ emp cp1 copy.of.cp1 $ С первого взгляда кажется, что все работает. Утилита стр сравнивает два файла и при обнаружении несовпадения по содержанию оповещает об этом. Поскольку разницы между указанными файлами нет, то нет и сообщения о несовпадении. А как наша программа будет реагировать на ошибочные ситуации? Сначала попытаемся снять копию с несуществующего файла, а затем записать копию в каталог. Получим такой результат: $ср1 xxx123file1 Error: Cannot open xxxl 23: No such file or directory $ cp1 cp1 /tmp Error: Cannot creat Amp: Is a directory Проверим другие ошибочные ситуации. Следует обратиться к документации по системному вызову и посмотреть там, какие ошибки могут возникать при его выполнении. Затем нужно попытаться воспроизвести ошибочные ситуации. При этом проверяйте - не затираете ли вы файлы, с которыми вам будет необходимо работать.
2.7. Увеличение эффективности файловых операций ввода/вывода: Буферирование 77 2.6.4. Программирование в Unix кажется достаточно простым Программа who - это программа, которая читает из файла и форматирует данные. Программа ср - это программа, которая читает один файл и производит запись в другой файл. Обе программы используют одни и те же базовые системные вызовы для установления связей с файлами и организации передачи данных в файлы и из файлов. Из справочника и из текстов заголовочных файлов мы будем извлекать всю информацию, которая будет необходима, чтобы понять, как писать такие программы. Программирование в Unix не выглядит слишком затруднительным. Следует ли пропускать некоторые основополагающие вопросы? Давайте разберемся. Помимо трех вопросов, которые мы сформулировали в отношении Unix и программирования в Unix, есть еще один важный вопрос: что можно сделать, чтобы это работало лучше? 2.7. Увеличение эффективности файловых операций ввода/ вывода: Буферирование В программе ср1 содержится символьная константа BUFFERSIZE, которая задает размер массива в байтах, где содержатся данные по мере их передачи от исходного файла к целевому файлу. Это значение равно 4096. Возникает важный вопрос: какой размер буфера следует считать лучшим? 2.7.1. Какой размер буфера следует считать лучшим? Давайте порассуждаем. Если вы используете половник для разлива супа по тарелкам, то чем больше будет этот половник, тем меньше вам потребуется манипуляций с разливом и меньше времени. Рассмотрим файл длиной в 2500 байт. Можно выделить некоторые особенности при работе с ним: Ех: Размер файла = 2500 bytes Если buffer = 100 байт, тогда для копирования потребуется 25 системных вызовов read() и 25 write() Если buffer = 1000 байт, тогда для копирования потребуется 3 системных вызова readQ и 3 write)) При изменении размера буфера со 100 байт до 1000 байт сокращается число системных вызовов read и write с 50 до 6. В следующей ниже таблице показано время выполнения программы ср1 при копировании файла размером в 5 Мбайт при различных значениях BUFFERSIZE. Размер буфера 1 4 16 64 128 256 512 Время выполнения в секундах 50.29 12.81 3.28 0.96 0.56 0.37 0.27 1024. 0.22
78 Пользователи, файлы и справочник Что рассматривать в первую очередь? Размер буфера 2048 4096 8192 16384 Время выполнения в секундах 0.19 0.18 0.18 0.18 Системные вызовы требуют время для своего выполнения. Программа, где делается больше системных вызовов, работает медленнее и отнимает время у других пользователей, которые хотели бы работать в системе. 2.7.2 Почему на системные вызовы требуется тратить время? Чем определяются временные затраты при работе системных вызовов? На рисунке 2.5 схематично показан поток управления. Рисунок 2.5 Поток управления при работе системных вызовов На рисунке изображена память. Процесс развивается в пользовательской памяти, а ядро, располагается в системном пространстве. Диск доступен для ядра. Наша программа ср1 хочет читать данные, поэтому она обращается с системным вызовом read к ядру для чтения данных. Код, который производит фактическую передачу данных процессу с диска, является частью ядра. Поэтому управление от вашего кода в пользовательском пространстве будет передано коду ядра, находящемуся в системном пространстве. После этого процессор будет выполнять ту часть кода ядра, который организует передачу данных. На выполнение кода по передаче данных требуется время. Но время требуется не только на передачу данных. Время требуется также и на переход в ядро и выход из ядра. Когда исполняется код ядра, процессор работает в супервизорном режиме со специальным стеком и памятью. При исполнении пользовательского кода процессор работает в пользовательском режиме.
2\7. Увеличениеэффективностифайловыхоперацийввода/вывода: Буферирование 79 Функции ядра должны иметь доступ к диску, терминалам, принтерам и другим ресурсам. А вот пользовательские функции не должны иметь доступа к этим ресурсам. Поэтому и организуется работа компьютера в различных режимах. Когда компьютер работает в пользовательском режиме, он имеет ограничение на доступ к памяти - возможен доступ только к определенному сегменту памяти в пользовательском пространстве. Когда происходит работа в режиме ядра, компьютер имеет доступ ко всей памяти. Особенности смены режимов работы зависят от вашего процессора. У каждого процессора имеются собственные схемы по поддержке супервизорного и пользовательского режимов. Каждая версия Unix адаптируется к той модели поддержания супервизорного и пользовательских режимов, которые обеспечиваются данным процессором. Рассмотрим Кларка Кента и Супермена. Когда происходит переход от пользовательского режима (Кларк Кент) в режим ядра (Супермен), то Кларк находит телефонную будку, там переодевается, снимает очки и меняет прическу. Далее Супермен выполняет определенные действия, на что требуется время. И на переход обратно в пользовательский режим также требуется время. Чем чаще Кларк Кент выполняет такие смены образов (режимов), тем больше времени у него на это уходит и меньше времени остается на работу репортером или на борьбу с преступностью. Ваша программа не является исключением. Чем больше времени процессор затрачивает на исполнение кода ядра и на вход в режим ядра и на выход из него, тем меньше времени у него остается для работы над вашим кодом или на обеспечение неких системных сервисов. Поскольку за время следует платить, то системные вызовы называют do/югиш/. Что приводит к удорожанию при чтении и записи данных в нашей версии программы who? 2. Z 3. Означает ли, что наша программа who2.c неэффективна? Да! Выполнение для каждой записи utmp одного системного вызова выглядит также неэффективно, как если бы мы покупали пиццу слоями или покупали бы яйца поштучно. Если вы собрались приготовить на завтрак яичницу из трех яиц, то вы должны будете поехать в магазин, купить одно яйцо, возвратиться обратно, поджарить яйцо, затем съесть его. После того как вы разделались с первым яйцом, должны будете поехать в магазин, купить другое яйцо, приехать обратно, поджарить яйцо и съесть его. Наконец, вы должны будете поехать и купить третье яйцо и понять, почему же яйца упаковывают в эти удобные коробки. Хорошая идея состоит в том, чтобы читать сразу несколько (связку) записей. Тогда (как в случае приобретения яиц в упаковке) связка записей помещается в локальную память. Упаковка с яйцами является по смыслу буфером. Далее показан псевдокод для метода getegg, где будет использована буферизация при покупке яиц. getegg(){ if (eggsjeft_in_carton == 0){ вновь упаковать коробку с яйцами в магазине if (eggs_at_store == 0) return EndOfEggs eggs left in carton = 12 } eggsJeftJn_carton--; return one egg; v
80 Пользователи, файлыисправочник. Что рассматривать впервую очередь? При каждом обращении к getegg выбирается одно яйцо, но не из магазина. Когда упаковка с яйцами опустеет, то по алгоритму функции следует ехать в магазин. Но какое отношение все это имеет к программированию в Unix? Ознакомьтесь с содержанием заголовочного файла /usr/include/stdio.h для getc. В некоторых версиях Unix функция getc, реализованная как макрос, использует ту же логику, что и функция getegg. 2.7.4. Добавление буферирования к программе who2.c Мы создадим версию программы who2.c, которая будет работать более эффективно за счет введения буферирования, что должно уменьшить число используемых системных вызовов. Идея, которая была представлена на примере функции getegg, может быть представлена в программном виде. На рисунке 2.6 показано, как будет работать программа с буферизацией. main Буфер Файл utmp Буферирование файла с utmplib Функция main обращается в utmplib.c для получения следующей utmp структуры Функция из utmplib.c читают эти структуры 16 раз с диска в массив Ядро будет вызвано, только когда отработают все 16 чтений Рисунок 2.6 Поток управления при работе системных вызовов Мы создаем массив, который может содержать 16 utmp структур. Этот массив именуется как буфер в нижней части схематического изображения процесса. Массив содержит последовательность структур в пространстве процесса, что аналогично случаю с коробкой для яиц, в которой находились яйца у вас дома. Напишем функцию с именем utmpjiext, которая будет извлекать записи из буфера. Модифицируем функцию main так, чтобы получать структуры из нашего буфера в пользовательском пространстве. Это будет сводиться к вызову нашей собственной функции utmp_next в пользовательском пространстве. После того как будут обработаны все структуры из буфера, функция utmp_next обратится к системному вызову read, чтобы потребовать от ядра считать очередные 16 записей. Эта новая модель уменьшает число системных вызовов read в 16 раз.
2.7. Увеличениеэффективности файловыхоперацийввода/вывода: Буферироваиие 81 Такой буфер для размещения в нем 16 структур и функции для загрузки в буфер данных с диска и для извлечения из него структур для функции main помещены в файл utmplib.c. Код utmplib.c Файл utmplib.c, содержимое которого здесь приведено, реализует алгоритм буферирования записей: /* utmplib.c - функции для чтения в буфер из файла utmp * функции: * utmpj)pen(filename) - открытие файла *¦ возвращает -1 при ошибке * utmp_next() - возвращает указатель на следующую структуру * возвращает NULL при достижении конца файла eof * utmp_close() - закрытие файла * при одной операции чтения происходит чтение NRECS записей и затем они * извлекаются из буфера 7 #include <stdiorh> #include <fcntl.h> #include <sys/types.h> «include <utmp.h> «define NRECS 16 «define NULLUT ((struct utmp *)NULL) «define UTSIZE (sizeof(struct utmp)) static char utmpbuf[NRECS * UTSIZE]; static int numjecs; static int cur jec; static int fdjjtmp = -1; utmp open(char *filename) { fd_utmp = open(filename, 0_RDONLY); curjec = numjecs = 0; return fd_utmp; } struct utmp *utmp next() { struct utmp *recp; if(fdutmp==-1) return NULLUT; if (curjec==numjecs && utmpjeload()==0) /* еще? 7 returnNULLUT; /*получить адрес следующей записи */ recp = (struct utmp *) &utmpbuf [curjec * UTSIZE]; curjec++; return recp; } intutmo reloadM /* место хранения */ /* количество хранимых элементов 7 /* переход 7 I* чтение из */ /* открытие 7 /* пока нет записей */ Г сообщение 7 /* ошибка? 7
82 Пользователи, файлы и справочник. Что рассматривать в первую очередь? Г * Читать следующую последовательность записей в буфер 7 { intamtjead; /* чтение записей в буфер */ amtjead = read(fd_utmp, utmpbuf, NRECS * UTSIZE); /* сколько было получено? 7 numjecs = amtjead/UTSIZE; /* сброс указателя */ curjec = 0; . return num recs; } utmp_close() { if (fd_utmp != -1) /* не закрывать, если не было 7 closeffd utmp); /* открыто 7 } utmplib.c содержит буфер, переменные и функции для управления потоком данных, который проходит через буфер. Значения переменных num_recs и curjec определяют, сколько структур находится в буфере и сколько из них было использовано. Каждый раз при выборке записи функция utmp_next определяет с помощью проверки переменной curjec - не достигли этот счетчик значения, равного числу записей в буфере. Если не осталось неиспользованных записей, то функция utmpjext производит перезагрузку буфера с диска. Прежде чем передать запись на использование, функция инкрементируеТ счетчик curjec. utmplib.c поддерживает ясный интерфейс в отношении вызываемых функций, скрывая внутренние детали расположения в памяти и формат utmp записей. Функция utmpjext просто возвращает указатели на структуры. Далее представлена модифицированная версия функции main: /* who3.c - who с буферируемым чтением * - подавление пустых записей * - форматирование времени * - буферирование ввода (используя utmplib) 7 #include <stdio.h> #include <sys/types.h> #include <utmp.h> #include <fcntl.h> #include <time.h> #defineSHOWHOST void showjnfo(struct utmp *); void showtime(timej); intmain() { struct utnrm *utbufo. /* vкaзaтeль на следоюимо запись */
2.8. Буферизация и щю 83 } Г *utmp_next(); /* возвращаемый указатель на следующую запись */ if (utmp_open(UTMP_FILE) == -1){ perror(UTMP_FILE); exitA); } while ((utbufp = utmpjiextQ) != ((struct utmp *) NULL)) showjnfo(utbufp); utmp_close(); return 0; showinfo() В данной версии вместо системных вызовов open, read, close будут вызываться эквивалентные функции в модуле буферизации. Функции для отображения находятся в showjnfo. 2.8. Буферизация и ядро Буферизация - чрезвычайно полезная идея: читать данные в большие области памяти, которые находятся в вашем пространстве. Затем процесс выбирает из этих областей более мелкие части - те, которые ему необходимы. 2.8,/. Если буферизация столь хороша, то почему ее не использует ядро? Использует. При переходе в режим ядра и при возврате из него затрачивается время, но на передачу данных между твердым диском требуется несравненно больше времени. Для экономии времени ядро хранит копии блоков с диска в памяти. На рисунке 2.7 это проиллюстрировано. Буферы ядра (в системном пространстве) Рисунок 2.7 Буферизация дисковых данных в ядре
84 Пользователи, файлы и справочник. Чторассматривать впервую очередь? Диск представляет собой объединение блоков данных аналогично тому, как файл utmp представляет собой объединение записей о входах в систему. Буферы ядра содержат копии некоторых блоков диска, точно так же, как наши utmp буферы содержат копии utmp записей. Ядро копирует блоки с диска в буферы ядра. Когда процессу становятся нужны данные из определенного файла, то ядро копирует данные из буфера ядра в буфер процесса. Ядро не копирует непосредственно с диска в пользовательское пространство памяти. Что происходит, если обнаруживается, что требуемой части данных нет в буфере ядра? Ядро приостанавливает процесс, который к нему обратился, и выставляет заявку на требуемый блок в свой "список покупок". Ядро затем находит другие процессы, которые готовы к выполнению некой работы и позволяет этим процессам развиваться. Через какое-то время ядро переместит требуемые данные с диска в буфер ядра. Теперь ядро может копировать данные в буфер пользовательского пространства, после чего разбудит спящий процесс. Понимание принципов буферированш в ядре изменяет наше восприятие системных вызовов read и write. Системный вызов read копирует данные для процесса из буфера ядра, а системный вызов write копирует данные из процесса в буфер ядра. Передача данных между буферами ядра и диском происходит не так, как при работе системных вызовов read и write. Ядро может копировать данные на диск всякий раз, когда возникает в этом потребность. Возникает ситуация, аналогичная тому, как вы складываете почтовые конверты на столе в прихожей, чтобы потом их сразу переслать по почте. В нашем случае данные, которые обозначены процессом для записи, накапливаются в буферах ядра, ожидая момента, когда ядро скопирует их на диск. Если в системе внезапно произойдет отказ, который не позволяет ядру скопировать блоки из буфера на диск, то изменения в файле или добавления данных в него произведены не будут. , Последствия буферизации в ядре: Более быстрый "дисковый " ввод/вывод. Оптимизированные записи на диск. Необходимость записи буферов на диск перед остановом системы. 2.9. Чтение файла и запись в файл Наша первая программа who читала из файла. Наша вторая программа ср читала из одного файла и писала в другой файл. А есть ли программы, которые читают из файла и пишут в тот же файл? 2.9.1. Выход из системы: Что происходит? Что происходит, когда вы выходите из системы? Прежде всего, в системе происходит изменение записи в файле utmp. По полученному результату работы whol можно было заметить, что в файле utmp могут содержаться записи для неиспользуемых терминальных линий. Поэкспериментируйте и попытайтесь выполнить следующее: 1. Войдите в систему дважды, используя для этого два окна telnet к одной машине. 2. Используйте программу whol, которую мы написали, чтобы посмотреть содержимое файла utmp. Посмотрите, какие терминальные линии используются. 3. Выполните однократный выход из ваших сессий. 4. Запустите повторно на исполнение whol, чтобы посмотреть, что произошло с нашими двумя utmp записями.
2.9. Чтение файла и запись в файл 85 Вы увидите, что одна из записей, которая содержит ваше входное имя, изменилась. Обратите внимание, что изменилось значение поля utjime. Какое будет новое значение времени в этом поле? На некоторых системах поле с входным именем очищается. Будут ли еще какие-то изменения в записи? Что произойдет с именем удаленной машины? 2.9.2. Выход из системы: Как это происходит Давайте обратимся к простому примеру. Программа, которая удаляет ваше имя из журнала, должна выполнять следующие действия: 1. Открыть файл utmp. 2. Читать файл utmp до обнаружения записи о вашем терминале.. 3. Сместить модифицированную запись utmp на ее место. 4. Закрыть файл utmp. Рассмотрим эти четыре шага, один за другим. Шаг 1: Открытие файла utmp Программа выхода читает из файла utmp (она способна найти запись о вашем терминале), а также производит запись в файл utmp (чтобы заменить запись). Поэтому программа выхода должна открыть файл utmp на чтение и запись: fd = openfUTMP.FILE, CLRDWR); Шаг 2: Поиск записи о вашем терминале Все происходит просто. В цикле while будут по одной читаться utmp записи (или будет использовано буферирование), будет производиться сравнение значения utjine с именем вашего терминала. Это может происходить так: while(read(fd, rec, utmplen) == utmplen) /* получить следующую запись*/ if (strcmp(rec.utjine, myiine) == 0) /* это моя линия? */ revise_entry(); /* удалить мое имя 7 ШагЗ: Запись модифицированной записи на место Программа выхода модифицирует запись и помещает эту запись обратно в файл. Программа изменяет значение USER_PROCESS, которое находится в utjype, на значение DEAD_PROCESS. В некоторых версиях программ выхода может производиться очистка поля с входным именем пользователя и поля с именем хост-машины, а значение, которое было в поле utjime заменяется на время выхода. Описанные действия легко запрограммировать. Теперь возникает такой большой вопрос: как же мы запишем модифицированную запись обратно в файл? Если просто вызвать системный вызов write, то произойдет модификация следующей записи. Это произойдет потому, что ядро поддерживает понятие текущей позиции в файле и смещает текущую позицию после каждого прочтения некого числа байтов или при записи в файл. При организации поиска utmp записи о нашем терминале текущая позиция была выставлена на следующую запись. Тогда возникает важный вопрос. Вопрос: Как программа может изменить текущий указатель чтения-записи в файле? Ответ: С помощью системного вызова lseek. Мы рассмотрим lseek в следующем разделе. Шаг 4: Закрытие файла Следует вызвать close(fd).
86 Пользователи, файлы и справочник. Что рассматривать в первую очередь? 2.9.3. Смещение текущего указателя: Iseek Unix управляет текущим указателем в каждом открытом файле, как это показано на рисунке 2.8. Каждый раз, когда вы читаете байты из файла, ядро будет читать данные с текущей позиции и затем смещать текущий указатель на то число байтов, которое было прочитано. Указатель используется и при записи данных в файл. Каждый раз, когда вы производите запись байтов в файл, ядро помещает их в файл, начиная с текущей позиции, а затем корректирует значение текущей позиции - увеличивает ее на число записанных байтов. Начало файла Текущая позиция Рисунок 2.8 Каждый открытый файл имеет текущий указатель Системный вызов read получает данные от текущей позиции и до места расположения новой текщей позиции, которое смещено относительно текущей позиции на количество прочитанных символов Конец Текущий указатель позиции привязан к соединению с файлом, а не к самому файлу. Например, если две программы открыли один и тот же файл, то после открытия для каждого соединения будет поддерживаться собственный указатель позиции. Программы могут читать или записывать в разных местах файла. Системный вызов Iseek дает вам возможность изменять текущую позицию в открытом файле и имеет такие характеристики: Iseek НАЗНАЧЕНИЕ Устанавливает файловый указатель с определенным смещением в файле INCLUDE «include < sysAypes.h > «include < unustd.h > ИСПОЛЬЗОВАНИЕ off J oldpos = lseek(int fd, off J dist, int base) АРГУМЕНТЫ fd - дескриптор файла dist: смещение в байтах base: base: SEEK_SET => от начала файла SEEK_CUR => от текущей позиции SEEKJEND => от конца файла КОДЫ ВОЗВРАТА -1 -приошибке Или предшествующая позиция в файле
2.9. Чтение файла и запись в файл 87 Iseek устанавливает текущий указатель через дескриптор открытого файла fd в то место в файле, которое задается парой значений - dist и base. Значением base (база) можно задавать начало файла @), текущую позицию в файле A) или конец файла B). Смещение - это число байтов относительно базы. Например, при таком обращении к системному вызову: lseek(fd, -(sizeof(struct utmp)), SEEK_CUR); произойдет смещение текущего указателя на sizeof(struct utmp) байтов относительно текущей позиции. При обращении вида: lseek(fd, 10 * sizeof(struct utmp), SEEK.SET); текущий указатель будет установлен на начало одиннадцатой utmp записи в файле. А при обращении вида: lseek(fd, О, SEEK.END); write(fd, "hello", strlenfhello")); текущий указатель будет установлен в конец файла и там будет записана текстовая строка. Наконец, нотация вида: lseek(fd, О, SEEK_CUR) означает возврат в текущую позицию. 2.9.4. Кодирование выхода из системы через терминал Теперь у нас есть все, что необходимо для написания функции, которая будет делать отметку в файле utmp при выходе из системы: Г * logout_tty(char *line) . * производит отметки в utmp - записи при выходе из системы * не затирает имени пользователя и удаленной машины * возвращает -1 - при ошибке, 0 - при успехе int logout tty(char *line) { intfd; struct utmp rec; int len = sizeof(struct utmp); int retval = -1; Г пессимизм*/ if ((fd = open(UTMPTILE,0_RDWR)) == -1) /* открытие файла 7 return-1; /* поиск и замена */ while (read(fd, &rec, len) == len) if (strncmp(rec.ut_line, line, sizeof(rec.ut line)) == 0) { rec.uUype = DEAD.PROCESS; /* установка типа 7 if (time(&rec.ut time) != -1) /* и времени 7 if (lseek(fd,-len, SEEK.CUR)!= -1) Л откат 7 if (write(fd, &rec, len) == len) /* модификация7 retval = 0;/* успех! 7
88 Пользователи, файлы и справочник. Чторассматривать\в первую очерщь? break; } /* закрытие файла */ if(close(fd)==-1) retval = -1; return retval; } В этом программном коде производится проверка возникновения ошибок для каждого системного вызова. В ваших системных программах следует всегда проверять наличие ошибок при каждом системном вызове. Такие программы в состоянии модифицировать файлы и данные, от которых зависит работа системы. Могут возникнуть серьезные последствия, если оставлять файлы в некотором противоречивом состоянии или оставлять их, не закончив с ними работу. С другой стороны, в ряде простых программ в данном тексте были опущены проверки на наличие ошибок. Это сделано было для того, чтобы оставить наглядной и ясной логику работу самих системных вызовов. Упомянув об ошибках, теперь посмотрим, как управлять ими и как оповещать об их наличии. 2.10. Что делать с ошибками системных вызовов? Если системный вызов open не может открыть файл, то он возвращает -1. Если системный вызов read не может прочитать данные, то он возвращает -1. Если системный вызов Iseek не может отыскать нужную позицию, то он возвращает-1. Системные вызовы возвращают-1, когда что-то выполняется неправильно. Ваши программы должны проверять код возврата каждого системного вызова, которые делаются в вашей программе, и предусматривать выполнение необходимых действий в случае возникновения ошибок. Что следует считать неправильным? Для каждого системного вызова установлен собственный перечень ошибок. Рассмотрим системный вызов open. Файл может не существовать, вы можете не иметь прав на открытие файла или у^ке открыли слишком много файлов. Как же ваша программа сообщит вам, какая и^з нескольких возможных ошибок возникла? Как идентифицировать ошибочную ситуацию: errno Ядро оповещает вашу программу при возникновении ошибки с помощью определенного кода ошибки, который ядро записывает в глобальную переменную errno. Каждая программа имеет доступ к этой переменной. На странице документации еггпо(З) и в заголовочном файле <errno.h> находятся символьное и числовое представление кодов ошибок. Вот несколько примеров: #define EPERM 1 /* Отсутствие прав на действие 7 #define EN0ENT 2 /* Нет такого файла или каталога */ #define ESRCH 3 •/* Нет такого процесса */ #define EINTR 4 /* Прерываемый системный вызов */ #define ЕЮ 5 /* Ошибка ввода/вывода 7 Различные ответы на различные ошибки Вы можете использовать эти символические коды ошибок в вашей программе, когда будете программировать распознавание ошибочных ситуаций и действий, которые нужно предпринимать при ошибках. Это иллюстрируется в следующем программном коде:
2.10. Что делать с ошибками системных вызовов? 89 #include <errno.h> extern int errno; ' int sample() { intfd; fd = open("file",ORDONLY); if(M==-1) { printff'Cannot open file:"); if (errno ==ENOENT) printffThere is no such file."); else if (errno == EINTR) printff'lnterrupted while opening file."); else if (errno == EACCESS) printffYou do not have permission to open file."); Действия вашей программы будут зависеть от того, что вы будете считать ошибочным действием. Например, если системный вызов open заканчивается неуспешно, поскольку указанный файл не существует, вы можете запросить у пользователя другое имя файла. С другой стороны, если программа открыла слишком много файлов (EMFILE), то можно закрыть некоторые из них и вновь попытаться открыть нужный файл. В данном случае пользователю нет необходимости знать о возникновении такой ошибки и выполненных действиях при ее возникновении. Сообщения об ошибках: реггог(З) Если вы хотите выдать сообщение, которое описывает ошибку, то требуется проверить значение переменной errno и вывести то или иное сообщение в зависимости от значения переменной. В функции sample, которая была приведена выше, это. выполняется. Вместе с тем вместо указанных действий представляется удобным использовать библиотечную функцию perror(string). Функция perror(string) выбирает код ошибки и выводит в соответствии с возникшей стандартной ошибкой строку, которую вы ей передаете, вместе с кратким сообщением об ошибке. В модифицированной версии программы sample используется perror: int sample() { intfd; fd = open("file", 0 RDONLY); if (fd == -1) { perrorf'Cannot open file"); return; } Если возникает ошибка при работе системного вызова open, то вы увидите такого рода сообщения: Cannot open file: No such file or directory Cannot ooen file: Interrupted svstem call
90 Пользователи, файлы и справочник. Что рассматривать в первую очередь? В первой части диагностического вывода находится строка, которую вы передаете при обращении к функции perror, а во второй части вывода находится текст, который соответствует коду ошибки в переменной errno. Заключение Основные идеи • Команда who выводит список текущих пользователей после чтения его из системного журнала. • В системах Unix данные хранятся в файлах. Unix - программы организуют передачу данных в файлы и из файлов с помощью шести системных вызовов: open(filename, how) creat(filename, mode) read(fd, buffer, amt) write(fd, buffer, amt) lseek(fd, distance, base) close(fd) • Процесс читает данные и записывает их с помощью файловых дескрипторов. Файловый дескриптор определяет соединение между процессом и файлом. • Каждый раз, когда программа выполняет системный вызов, компьютер переключается из пользовательского режима в режим ядра. Далее исполняется некоторый код в ядре. Программы будут работать более эффективно, если в них будет произведена минимизация числа системных вызовов. Программы, которые читают и пишут данные, могут сократить число системных вызовов, размещая данные в буферах и обращаясь к ядру, когда необходимо записать заполненный буфер или поместить данные в буфер при его опустошении. Ядро Unix использует буферы, которые располагаются в памяти ядра для того, чтобы сократить время на пересылку данных между системой и диском. В Unix значение времени хранится в форме целого числа секунд, прошедших с момента начала работы Unix. Когда в Unix системный вызов обнаруживает ошибку, система устанавливает определенное значение в глобальной переменной errno и возвращает код возврата, равный -1. Системные программы могут использовать значение errno для диагностирования ошибок и выполнения необходимых действий при возникновении ошибок. Большая часть информации, которая была представлена в этом разделе, доступна в системе. Расширенные тексты документации представляют описание команд, что они делают, а в ряде случаев описывают, и как они работают. В заголовочных файлах содержатся определения структур данных, значения символических констант, прототипы функций, используемые для создания системных средств.
Заключение 91 Исследования 2.1 Команда W. В Unix есть команда, которая называется w и которая имеет отношение к команде who. Попытайтесь выполнить команду и прочитайте документацию для нее. Какие действия поддерживаются в команде w и не поддерживаются в команде who? Какая при этом используется информация в файле utmp? Каково назначение дополнительной информации? Попытайтесь найти источники, объясняющие смысл дополнительной информации. 2.2 Авариии utmp. Когда вы входите в систему, то ваше входное имя, имя терминала, время, имя вашего удаленного хоста записываются в файл utmp. Когда вы выходите из системы, то запись зачищается. Что происходит, если в системе произойдет некая авария? Очевидно, что в файле utmp останется список пользователей, которые были в системе во время аварии. Когда система вновь стартует, то информация в файле utmp не будет верной. Что в системе Unix делается с файлом utmp, когда система стартует? Создаются ли записи для всех доступных терминальных линий? Может быть, создается пустой файл, в котором будут накапливаться записи о терминальных линиях? Для ответа на эти вопросы обратитесь к справочнику, заголовочным файлам и стартовым скриптам. Вы можете поэкспериментировать на собственных машинах. 2.3 Проверьте, как работает программа ср1, копируя некий файл на /dev/tty: Ср1 ср1 .с /dev/tty. Здесь целевым файлом является терминал. Наша программа будет открывать терминал, производить запись и закрывать терминал, используя для этого те же системные вызовы, которые она использовала при посылке данных в файл на диске. Далее скопируйте данные с терминала в дисковый файл, используя такую нотацию: ср1 /dev/tty filyl. Теперь ваша клавиатура становится входным файлом. Следут отметить, что после набора строк вы должны нажимать на клавишу Enter, а в конце следует набрать Ctri-D. 2.4 Стандартные С - функции для работы с файлами fopen, getc, fclose, fgets представляют собой часть системы буферированного ввода и вывода файлов. Эти функции используют структуру типа RLE, которая является промежуточным уровнем и подобна по назначению модулю utmplib. Найдите определение FILE в заголовочных файлах, описание структур и сравните их с переменными в utmplib.c. 2.5 Запись в буферы ядра. Как убедиться в том, что данные, которые вы пишете на диск, действительно туда были записаны? Мы отмечали, что ядро будет копировать данные, когда оно в них нуждается. Изучите справочный материал, где говорится, как системные вызовы и программы отслеживают состояние буферов при копировании на диск. 2.6 Многократное открытие одного и того же файла. В Unix допускается открытие одного файла несколькими процессами. В Unix возможно, что один и тот же процесс может многократно открывать один и тот же файл. Поэкспериментируйте с многократным открытием одного и того же файла, предварительно создав файл с некоторым произвольным текстом. Затем напишите программу, которая выполняет следующие действия: (a) Открывает файл для чтения. (b) Еще раз открывает этот же файл на запись. (с) Еще раз открывает этот же файл на чтение.
92 Пользователи, файлы и справочник. Что рассматривать в первую очередь? Вы должны получить три файловых дескриптора. После этого программа должна: (d) Прочитать 20 байтов, используя для этого первый дескриптор fd, и вывести на экран то, что прочитали. (e) Записать строку ''testing 12 3", используя для этого второй дескриптор fd. @ Прочитать 20 байтов, используя для этого третий дескриптор fd и вывести на экран то, что прочитали. 2.7 Изучение электронного справочника. Команда man предоставляет вам информацию о командах Unix, системных вызовах, системных устройствах и информацию по другим темам. Какую команду следует использовать, чтобы изучить свойства самой команды man? Сколько разделов содержится в электронном справочнике в вашей версии Unix? Для чего они предназначены? 2.8 Изучение файла utmp. Файл utmp, о котором шла речь в предшествующих экспериментах, содержит записи, которые соотнесены текущим сессиям. Какие еще виды записей содержатся в этом файле? Для чего они предназначены? 2.9 Переход в конец файла. Системный вызов Iseek позволяет вам выставить текущий указатель в файле на позицию, которая будет располагаться за концом файла. Например, системный вызов: lseek(fd,100,SEEKEND) установит текущий указатель в позицию, которая смещена на 100 байтов от конца файла. Что произойдет, если после этого вы попытаетесь читать данные сразу за концом файла? Что произойдет, если после этого вы попытаетесь писать данные сразу за концом файла? Попытайтесь записать некоторую строку, типа "hello", совмещением на большую величину относительно конца файла (например, 20 000 байтов). Проверьте, каков будет размер файла с помощью команды Is -1 и с помощью команды Is -s. Что в результате получили? Программные упражнения 2.10 Идентификация личности. В документации для команды who упоминается о возможности использования для этой команды такой нотации: who am i. Кроме того, допустим и вариант: whoami. Модифицируйте программу who2.c так, чтобы она поддерживала вариант вызова команды who am i. Поэкспериментируйте с командой whoami и почитайте документацию по ней. Чем этот вариант отличается от варианта who am i? Напишите программу, которая работала так же, как и whoami. 2.11 Что сделает стандартная программа ср, если вы попытаетесь с ее помощью копировать из файла в этот же файл? Например: ср filel filel. Насколько это корректно? Модифицируйте программу ср1 .с, которая управляла бы данной ситуацией. 2.12 Файлы и API. Мы создали utmplib.c, чтобы увеличить эффективность, но при этом достигается еще и дополнительный эффект. Этот дополнительный эффект заключается в том, что была произведена замена файла данных на набор функций, который представляет собой программный интерфейс (API). С помощью этого API программа по-
Заключение 93 лучает все структуры utmp, даже те, которые не представляют входы пользователей. Программе who необходимо только получить для рассмотрения те записи, которые представляют активные сессии. Модифицируйте utmplib.c так, чтобы она возвращала только записи об активных сессиях. Как такие изменения подействуют на остаток кода who3.C? Хороша ли эта идея с изменениями? Почему да или почему нет? 2.13 Буферирование и поиск. Функция logout Jty, которая была приведена ранее, использует системный вызов Iseek. Она производит откат на одну запись и может быть применена для перезаписи. Заметим, что функция logout_tty не использует буферирования для чтения файла utmp. Программа магла более эффективно работать, если бы использовалось буферирование. (a) Рассмотрите те проблемы, которые могут возникнуть, если мы соединим вместе системный вызов и обращения к функциям в utmplib.c. (b) Добавьте новую функцию в utmplib.c, которая должна будет вызываться так: utmp_seek(record_offset,base) и которая изменяет текущий указатель для utmp_next таким же образом, как это делает Iseek при изменении текущего указателя при вызове read. Эта новая функция должна передвигать текущий указатель на record_offset записей относительно базы base, где значениями base могут быть seek_set, seek_cur или seek_end. Заметьте, что значение аргумента представляется в количестве записей, а не в количестве байтов. (c) Модифицируйте logout Jty так, чтобы в этой версии можно было использовать utmplib.c. 2.14 Эксперименты с utmp. Программа whol пролистывает каждую запись в файле utmp. Хотя это и не входило в наши намерения, но программа тем самым предоставляет удобное средство для проверки содержимого файла utmp. Сделайте программу whol еще более полезной, добавив некий код к ней, который будет выводить все другие поля в структуре. В частности, полезно поле litjype. Модифицируйте программу так, чтобы она позволяла пользователю заменить файл utmp (файл по умолчанию)на файл, имя которого задается в командной строке. Теперь можно будет использовать это средство для проверки файла wtmp. 2.15 Предотвращение разрушений файлов. В стандартной версии команды ср безусловно происходит перезаписывание существующих файлов. То есть, если у вас есть файл file2, и вы будете выполнять команду: $ ср filel file2 то вы уничтожите оригинальное содержание файла file2. В стандартной версии ср поддерживается опция -i, которая заставляет команду запрашивать у пользователя подтверждение на перезапись файла. Добавьте это свойство в программу ср1.с. Проекты Взяв за основу материал этой главы, вы можете изучить и написать собственные версии следующих команд Unix: ас, last, cat, head, tail, od, dd
94 Пользователи, файлыи справочник. Чторассматривать впервую очередь? Последняя трудная задача: Команда tail Мы многое узнали при ознакомлении с рядом команд. Рассмотрите еще одну команду. У нас нет возможности ее изучать, поэтому вы должны будете сделать это самостоятельно. Системный вызов Iseek позволяет вам передвигать текущий указатель по файлу. Вызов Iseek(fdASEEKEND) переместит текущий указатель в конец файла. Команда tail позволяет отобразить последние десять строк файла. Попытайтесь ее выполнить. Команда tail будет выводить не от конца файла и вперед, а десять строк, которые располагаются перед концом файла. Заметим, что аргумент distance в системном, вызове Iseek измеряется в количестве символов. Как работает команда tail? Разработайте собственную версию. Подумайте о буферирова- нии, чтобы ваша программа работала эффективнее. Изучите документацию и познакомьтесь со всеми опциями, которые поддерживаются в команде tail. Как они работают? Разработка такой программы в отношении программ who и ср выглядит гораздо более простым проектом. Исходный код двух версий tail доступен на Web-сайте книги. Одна версия - это gnu версия, другая - версия bsd. В них используются различные средства. Прежде чем обратиться к этим решениям, попытайтесь написать одну из версий самостоятельно.
Глава 3 Свойства каталогов и файлов при просмотре с помощью команда Is Цели Идеи и средства • Каталог - это список файлов. • Как прочитать каталог. • Типы файлов и как определять тип файла. • Свойства файлов и как определять свойства файл. • Битовые наборы и биты маскирования. • Идентификаторы пользователя, идентификаторы группы и база данных passwd. Системные вызовы и функции • opendir, readdir, closedir, seekdir • stat • chmod, ehown, utime • rename Команды • Is 3.1. Введение Мы знаем, как прочитать содержимое файла и как записать данные в файл. Помимо содержания, файл имеет еще ряд атрибутов. Файл имеет собственника, у файла есть время его последней модификации, размер, тип и другие атрибуты. Как мы можем посмотреть имена файлов и определять свойства файлов? С помощью команды Is можно получать списки имен файлов в каталоге и информацию о файлах. Мы изучим команду Is, чтобы больше узнать о каталогах и типах файлов, узнать о свойствах файлов.
96 Свойства каталогов и файлов при просмотре с помощью команды Is 3.2. Вопрос 1: Что делает команда is? 3.2.1. Команда Is выводит список имен файлов и оповещает об атрибутах файлов Наберите команду Is, чтобы посмотреть, что она делает. JJ Is Makefile docs Is2.c s.tar statdemo.c taill .c chap03 Is1 .c old_src statl x taiM $ Действие команды Is по умолчанию ~ вывод списка имен файлов в текущем каталоге. При выводе имена файлов сортируются командой Is в алфавитном порядке. В одних версиях команда располагает список имен поколонно, в других версиях такой вывод выполняется с помощью опции (используя опцию -С). Помимо имен файлов, команда Is может выводить еще дополнительную информацию о файлах. Если при обращении к команде задается опция -1, то команда представляет информацию о каждом файле, используя длинный формат: S Is -1 total 108 -rw-rw-r-- -rw-rw-r-- drwxrwxr-x -rw-r-r-- -rw-r-r- drwxrwxr-x -rw-rw-r-- -rw-r-.-r-- -ПЛИ--Г-- -rwxrwxr-x -rw-r-r- $ 2bruce 1 bruce 2 bruce 1 bruce 1 bruce 2 bruce 1 bruce 1 bruce 1 bruce 1 bruce 1 bruce users users users users users users users support support users users 345 Jul 29 11:05 Makefile 27521 Aug 1 12:14 chap03 1024 Aug 1 12:15 docs 723 Feb 91998 Is1.с 3045 Feb15 03:51 Is2.c 1024 Aug 1 12:14oldsrc 30720 Aug 1 12:05 s.tar 946 Feb18 17:15 stall.с 191 Feb 9 1998 statdemo.c 37351 Aug 1 12:13 taih 1416 Aug 1 12:05 taill.с Каждая строка вывода представляет один файл и содержит несколько атрибутов для каждого файла. 3.2.2. Получение листинга о других каталогах, получение информации о других файлах В системе Unix находится много каталогов, в каждом из которых собраны собственные наборы файлов. Как быть, если вам понадобится информация о других каталогах и о файлах в этих каталогах? Можно обратиться к команде Is для получения информации о файлах из других каталогов, указывая имена каталогов и имена файлов в командной строке: Запрос на получение информации по Is из других каталогов и об их файлах Пример Действие Is Дтр Список имен файлов в каталоге Дтр Is -I docs Показать атрибуты файлов в каталоге docs Is -l./Makefile Показать атрибуты. /Makefile Is ".с Список файлов, имена которых удовлетворяют шаблону *.с
3.3. Краткий обзор дерева файловой системы 97 Если в качестве аргумента задан каталог, то команда Is выводит в виде списка его содержимое. Если в качестве аргумента задан файл, то команда Is выводит его имя и, возможно, атрибуты. На то, что может выполнить Is и как будет выглядеть вывод команды Is, указывают опции, которые задаются при обращении к команде. 3.2.3. Наиболее употребимые опции В документации команды Is приводится большой список опций этой команды. Наиболее популярные представлены в таблице. Команда Is-а Is-lu Is-s Is-t Is-F Действие Показать скрытые файлы Показать время последнего чтения Показать размер в блоках Сортировка по времени Показать типы файлов Ремарка относительно имен файлов с начальной точкой Опция -а требует пояснения, если вы новичок в Unix. Unix реализует концепцию скрытых файлов на основе использования простого соглашения. Соглашение заключается в том, что команда Is не включает в список вывода имена файлов, если они начинаются с точки. Нечто в операционной системе (а именно ядро) знает и поддерживает концепцию скрытых файлов. Это соглашение, которому следуют команда Is и пользователи. Некоторые программы используют имена с начальной точкой для файлов в пользовательском домашнем каталоге, чтобы указать неопределенные пользовательские предпочтения. Такие конфигурационные файлы легко редактировать. Но их имена в листинге о содержании каталога чаще всего не выводятся. 3.2.4. Первый ответ: Итоговые замечания В результате проведения экспериментов с командой Is и после изучения соответствующей документации мы обнаружили, что команда Is выполняет две функции: Выводит в виде списка содержимое каталогов. Отображает информацию о файлах. Отметим, что команда Is выполняет различную обработку каталогов и файлов. При обращении команда Is определяет, что задано в качестве аргумента - файл или каталог. Как это делается? Если мы будем писать версию программы Is, то нам потребуется ответить на три вопроса: • Как вывести в форме списка содержимое каталога? • Как получить и отобразить свойства файла? • Как различить имя файла и имя каталога? 3.3. Краткий обзор дерева файловой системы Прежде чем отвечать на сформулированные вопросы, давайте рассмотрим картину распределения файлов на диске, которая поддерживается в Unix. Информация на диске представлена как дерево каталогов, каждый из которых содержит файлы и/или каталоги. На рисунке 3.1 небольшие прямоугольники обозначают файлы,
98 Свойства каталогов и файлов при просмотре с помощью команды Is находящиеся в каталогах, а линиями обозначается, каким образом каталог соединяется с вышележащим и нижележащим каталогами. Каталоги Файлы Рисунок 3.1 Дерево каталогов В Unix каждый файл расположен в некотором месте единственного дерева каталогов. Отсутствуют такие понятия, как устройства или тома. Напротив, каталоги на отдельных физических дисках и разделы рассматриваются как составные части одного дерева. Даже гибкие диски, диски CD-ROM и другие заменяемые носители будут рассматриваться в какой-то момент как подкаталоги единого дерева. Все это значительно упрощает написание программы Is. Мы будем иметь в виду только каталоги и файлы и не думать о разделах и томах. 3.4. Вопрос 2: Как работает команда Is? Команда Is выдает список имен файлов. Как его сформировать? Первый набросок действий будет такой: открытие каталога +- > читать запись - конец каталога? -+ |__ отобразить информацию о файле | закрытие каталога < + Эта схема напоминает логику команды who. Главное отличие заключается в том, что команда who производит открытие и читает из файла, а команда Is открывает и читает данные из каталога. Насколько отлично чтение из каталога от чтения из файла? В конечном счете, что такое каталог? 3.4.1. Что же такое каталог, в конце концов? Ответ. Каталог представляет собой особый вид файла, в котором содержится список имен файлов и подкаталогов. Каталог по ряду признаков подобен файлу utmp (см. главу 2). В нем содержится последовательность записей, а каждая запись четко определена по назначению и имеет полностью документированную структуру. Каждая запись в каталоге служит для представления одной сущности - одного файла или одного каталога. В отличие от обычных файлов каталог никогда не бывает пустым. В каждом каталоге есть две специальные записи, которые обозначаются так: . (точка) и .. {точка_точка)\ точка -это имя текущего каталога, а точкаjno4Ka - это имя вышележащего каталога.
ЗА Вопрос 2: Как работает команда Is? 99 3.4.2. Работают ли системные вызовы open, read и dose в отношении каталогов? Ответ 1. В Olden Days ®#Что это такое? нет упоминаний о таких возможностях. Поработайте с такими командами на вашей системе: Scat/ as'.a'asa.-a'bw.tagsb'cf quota.userc'{ quota.group,esbetce,,sbtmp,"sbdevM sbmnt' wcsbin '2 sbopt2 ¦8 sbusr8 ¦9 sbvar9 (many lines of hard-to-read data omitted) $ more /tmp /tmp is a directory $ od -c /dev 0000000 0000020 0000040 0000060 0000100 0000120 0000140 0000160 0000200 0000220 0000240 360 001 001 200 002 \0 M A 362 001 362 001 k с 364 001 364 001 k m 366 001 \o \o \o К \o \o 0 \o \o e \o \0 024 \0 \0 002 \0 \0 001 200 E D E \0 030 \0 \0 001 200 n \0 \0 \0 030 \0 \0 001 200 m \0 \0 \0 024 \0 001 \o \o V 004 \o \o \a \o \o 003 \0 \0 024 \0 361 \0 361 \0 k \0 363 \0 363 \0 k \0 365 \0 365 \0 m \o \o 001 001 I 001 001 b 001 001 e \0 002 \o \o 0 \o \o i \o \o m \0 360 \o . \0 030 \0 001 g о \0 030 \0 001 n i \0 030 \0 001 \0 366 001 . \o 200 \o \o 200 0 \0 200 001 \0 \o \a \o \o 004 \o g 004 \o \o \o \o \o \o \o \o \o \o \o \o \o Из этих примеров следует ряд интересных результатов. Во-первых, команды cat и od могут читать каталоги так же, как они читают обыкновенные файлы. В этих командах используются стандартные системные вызовы для работы с файлами: open, read и close. Поэтому каталоги можно читать как обыкновенные файлы. Во-вторых, команда more отказывается показывать вам содержимое каталога. Она распознает, что в качестве аргумента задан каталог, и не будет отображать его содержимое. Команда more смогла бы отображать содержимое каталога, но не думаю, что вам захотелось бы его рассматривать. Некоторые версии команды cat, подобно команде more, распознают каталог при обращении и не отображают его содержимое. Наконец, примеры показывают, что каталоги не содержат однородного текста. Каталог состоит из последовательности структур. Ответ 2. Использование системных вызовов open, read и close для получения списка содержимого каталогов является плохой идеей. В Unix поддерживается много типов каталогов. Допускается читать диски, используя форматы Apple HFS, ISO9660, VFAT. Можно читать каталоги NFS и различные обыкновенные каталоги. При использовании системного вызова read потребуется знание формата записей для обработки каждого типа каталога.
100 Свойства каталогов и файлов при просмотре с помощью команды Is 3.4.3. Хорошо, хорошо. Но как же мне прочитать каталог? Обратимся к электронному справочнику. Поищем информацию по ключевому слову direct: $ man-k direct В одной из систем по этому слову была найдена 81 запись. Отфильтруем этот вывод по слову read: $ man -к direct | grep read DXmHelpSystemDisplay CX) - Displays a topic or directory of the help file in Bookreader. opendir, readdir, readdirj, telldir, seekdir, rewinddir, closedir C) - Performs operations on directories $ Первый экран документации будет выглядеть так: $ man 3 readdir opendirC) opendirC) NAME opendir, readdir, readdirj, telldir, seekdir, rewinddir, closedir-Performs operations on directories LIBRARY Standard С Library (libc.a) SYNOPSIS #include <sys/lypes.h> tinclude <dirent.h> DIR*opendir( const char *dirjame); struct dirent *readdir ( DIR *dir_pointer); int readdirj ( DIR *dirj)ointer, struct dirent *entry, struct dirent **result); long telldir ( DIR *dirj)ointer); void seekdir( DIR *dirj)ointer, long location); void rewinddir ( DIR *dir_pointer); int closedir( DIR *dir_pointer); [more] A1%)
3.4. Вопрос 2: Как работает команда Is? 101 Согласно этой странице документации мы убеждаемся в том, что данные из каталога получают аналогично тому, как получают данные из файла. Сначала с помощью opendir открывается соединение с каталогом, а далее readdir возвращает указатель на следующий элемент в каталоге. Наконец, closedir разрывает соединение. Системные вызовы: seekdir, telldir и rewinddir по назначению подобны lseek. На рисунке 3.2 показано, как происходит чтение. struct dirent opendir(char *) Создание соединения, возвращения указателя DIR * readdir(DIR *) Чтение следующей записи, возврат указателя на структуру struct dirent cJosedir(DIR *) Закрытие соединения Рисунок 3.2 Чтение записей из каталога Чтение содержимого каталога Каталог - это список файлов, а более точно, это последовательность записей, каждая из которых есть запись о каталоге. Мы читаем записи с помощью вызова readdir. После работы каждого вызова readdir возвращается указатель на очередную запись типа struct direntn. Компоненты структуры описаны в соответствующей документации и в заголовочном файле /usr/include/dirent/h. Например, начало документации по dirent, которая была взята в системе Sun OS, будет таким: File Formats direntD) NAME dirent - file system independent directory entry SYNOPSIS tinclude <dirent.h> DESCRIPTION Different file system types may have different directory entries. The dirent structure defines a file system independent directory entry, which contains information com-mon to directory entries in different file system types. A set of these structures is returned by the getdentsB) sys-tem call. The dirent structure is defined: struct dirent { inoj djno; off J <foff; unsigned short djeclen; chardjiame[1];
102 Свойства каталогов и файлов при просмотре с помощью команды Is Каждая структура dirent содержит элемент с именем djrame. Это элемент для хранения имени файла. Заметьте, что длина массива d_name в этой системе равна 1. Что означают такие установки? Один символ char задает пространство для сохранения в поле одиночного терминального нулевого символа. 3.5. Вопрос 3: Могу ли я написать Is? Логика получения содержимого каталога будет такой: main() opendir while (readdir) print d_name closedir Полный код программы lsl.c будет таким: Л* Isl.c ** цель - вывод списка содержимого каталога или каталогов при отсутствии аргументов используется., в противном случае используется список имен файлов через список аргументов У #include <stdio.h> #include <sys/types.h> #include <dirent.h> void doJs(char []); main(intac,char*av[]) { if (ac == 1) doJs(".M); else while (--ac){ printf("%s:\n", *++av); do ls(*av); > } void do ls(char dirname[]) Л list files in directory called dirname 7 { DIR *dir_ptr; /* каталог */ struct dirent *direntp; /* каждая запись 7 if ((dir_ptr = opendir(dirname)) == NULL) fprintf(stderr,Mls1: cannot open %s\nM, dirname); else { while ((direntp = readdir(dir_ptr)) != NULL) printf(,,%s\n,,1 direntp->d_name);
3.5. Вопрос 3: Могу ли я написать Is? 103 closedir(dir ptr); } } Откомпилируем и запустим этот код, а затем сравним полученный результат с выводом команды Is, которая работает на вашей системе: $ ее -о Is1 Is1 .с $ls1 s.tar tain Makefile Isl.c Is2.c chap03 old_src docs Is1 statl.c statdemo.c taill.c $ls Makefile docs Isl.c old_src statl.c taiM chap03 Is1 Is2.c s.tar statdemo.c tail 1.с $ 3.5.1. Что еще нужно делать? Неплохо для первой попытки. Эта версия 1.0 Is выводит список файлов в каталоге, но в данной версии не поддерживаются следующие возможности: (a) Нет сортировки вывода. Наш список имен файлов не отсортирован в алфавитном порядке. Устранение. Мы можем считать все имена файлов в массив, а затем использовать команду qsort для сортировки этого массива. (b) Нет поколонного вывода. Стандартная версия команды Is поддерживает возможность вывода списка имен файлов поколонно. В некоторых версиях расположение имен поколонно происходит сверху вниз, слева направо, а в других системах - слева направо, сверху вниз. (c) Вывод файлов с именами с лидирующей точкой. В этой версии отображаются имена файлов с точкой. В стандартной версии команды Is имена файлов с лидирующей точкой отображаются, только если используется опция -а. Устранение: Подавить вывод имен с лидирующей точкой достаточно просто и обеспечить их вывод по опции -а. (d) He работает опция -I.
104 Свойства каталогов и файлов при просмотре с помощью команды Is В стандартной версии is производится вывод статусной информации о файле, если пользователь задает при обращении к команде опцию -I. В нашем варианте такой возможности нет. Устранение: Добавить отработку опции -I непросто. В структуре dirent, которая определена в заголовочном файле <dirent.h>, есть только несколько необходимых элементов. В структуре dirent отсутствует информация о размере файла, о собственнике, а также данные о других характеристиках файла. Если этой информации нет в каталоге, то где же она хранится? 3.6. Проект 2: Написание версии Is -I Мы уже заметили, что команда Is выполняет два вида действий: выводит список содержимого каталогов, а также отображает статусную информацию о файлах. Далее мы увидели, что эти два аспекта не связаны между собой. В каталоге содержатся не только имена файлов. Нахождение и отображение статусной информации о файлах - это отдельный сложный проект. Мы будем его реализовывать, отвечая на три стандартных вопроса. 3.6.1. Вопрос 1: Что делает Is -I? Рассмотрим вывод команды: $ Is -I total 108 -rw-rw-r- 2 bruce users 345 Jul 29 11:05 Makefile -rw-rw-r- 1 bruce users 27521 Aug 1 12:14 chap03 drwxrwxr-x 2 bruce users 1024 Aug 1 12:15 docs -rw-r-r- 1 bruce users 723 Feb 9 1998 Is1 .c -rw-r--r- 1 bruce users 3045 Feb 15 03:51 Is2.c drwxrwxr-x 2 bruce users 1024 Aug 1 12:14 old_src -rw-rw-r- 1 bruce users 30720 Aug 1 12:05s.tar -rw-r-r- 1 bruce support 946 Feb 18 17:15 statl .c -rw-r-r- 1 bruce support 191 Feb 9 1998 statdemo.c -rwxrwxr-x 1 bruce users 37351 Aug 1 12:13 taiM -rw-r-r- 1 bruce users 1416 Aug 1 12:05taiM.c -rw-r-r- 1 cse215 cscie215 574 Feb 9 1998 writable.c $ В каждой строке содержатся следующие семь полей: Режим. Первый символ в каждой строке предназначен для обозначения типа файла. Символ ;i-" показывает, что это обычный файл, а символ "d" показывает, что это каталог. Есть еще и другие типы файлов. Вы должны еще немного изучить свойства и возможности Unix с тем, чтобы было понятно назначение других типов файлов. Последующие девять символов в первой колонке предназначены для обозначения прав доступа. Могут быть установлены или сброшены права на чтение, запись, исполнение в отношении файла для трех категорий пользователей: собственник, группа, все остальные. В предшествующем примере вывода все файлы и каталоги были доступны для чтения в каждом из классов пользователей, но файлы были доступны на запись только собственнику файлов. Откомпилированный файл taill доступен на исполнение для всех категорий пользователей. Ссылки. Ссылки указывают на файл. Эта тема будет обсуждаться в следующей главе.
3.6. Проект 2: Написание версии Is -/ 105 Собственник. Каждый файл принадлежит пользователю собственнику. В данной колонке указывается пользовательское имя собственника. Группа. Каждый файл принадлежит также группе пользователей. В ряде версий команды Is в колонке указывается имя группы. Размер. В пятой колонке находится целое число, которое обозначает число байтов в файле. Заметим, что в этой колонке каталоги в нашем примере имеют один и тот же размер. Память под каталоги выделяется блоками, поэтому размер каталога всегда кратен 512. (Это зависит от конкретной версии Unix. Так, в HP UX под каталог выделяются блоки размером 1024 байта. - Примеч.ред.) Для обычных файлов размер указывается в количестве байтов данных, которые хранятся в этом файле. Время последней модификации. Следующее поле состоит из трех подстрок, где размещается время последней модификации. Для сравнительно новых файлов в подстроки заносится месяц, день и время. Для более старых файлов заносится месяц, день и год. Почему такие отметки будут полезны в системе? Насколько должен быть "старым" файл, чтобы выводить в колонку год, а не время? Имя. В этой колонке изображается имя файла. 3.6.2. Вопрос 2: Как работает Is -I? Как мы можем получить информацию о файле? Давайте обратимся к электронному справочнику. При таком обращении: $ man -k file | grep -i information должна быть найденаполезная информация о файле, но она по-разному называется в различных версиях Unix. Многие версии вместо термина информация о файле используют термин статусная информация о файле, или свойства файла. Для извлечения статусной информации о файле используется системный вызов stat. 3.6.3. Ответ: Системный вызов stat получает информацию о файле На рисунке 3.3 изображено, как работает системный вызов stat. stat(name,ptr) Копирование информации о файле 'name" с диска в структуру, которая находится в вызывающем процессе. Статусная информация Содержимое файла Рисунок 3.3 Чтение статусной информации о файле с помощью stat Файл хранится на диске. Файл имеет содержимое и набор атрибутов: размер, идентификатор собственника и т. д. Процессу необходимо получить статусную информацию о файле. Процесс должен определить место, куда будет помещена статусная информация о файле.
106 Свойства каталогов и файлов при просмотре с помощью команды Is Поэтому он определяет буфер типа struct stat, а затем процесс обращается к ядру с требованием скопировать статусную информацию с диска в этот буфер. НАЗНАЧЕНИЕ INCLUDE ИСПОЛЬЗОВАНИЕ АРГУМЕНТЫ КОДЫ ВОЗВРАТА stat Получение статусной информации о файле #include < sys/stat.h > int result = statfchar *fname, struct stat *bufp) fname - имя файла bufp - указатель на буфер -1-при ошибке 0 -при успехе Системный вызов stat копирует статусную информацию о файле с именем fname в структуру, на которую выставлен указатель bufp. В следующем ниже примере показывается, как используется системный вызов stat для получения размера файла. Г filesize.c - выводит размер файла passwd */ #jnclude <stdio.h> #include <sys/stat.h> intmain() { struct stat infobuf; /* место хранения статусной информации */ if (statf/etc/passwd", &infobuf) — -1) /* получить информацию 7 perrorGetc/passwd"); else printff* The size of /etc/passwd is %d\nM, infobuf.st size); } Системный вызов stat копирует статусную информацию о файле в структуру infobuf, после чего программа читает размер файла из поля st_size в этой структуре. 3.6.4. Какую еще информацию можно получить с помощью системного вызова stat? Документация для stat и заголовочный файл /usr/include/sys/stat.h представляют описание перечня полей в структуре struct stat. stjnode - тип и права доступа st_uid - идентификатор собственника st_gid - идентификатор группы st„size - количество байтов в файле stjilink - число ссылок на файл stjntime - время последней модификации содержимого файла st_atime - время последнего доступа st_ctime - время последнего изменения статусной информации В структуре содержатся еще и другие поля. Но именно указанные поля отображаются при работе команды Is -1. Следующая далее простая программа fileinfo.c извлекает и выводит эти атрибуты.
3.6. Проект 2: Написание версии Is -/ 107 Г fileinfo.c - использует stat() для получения и вывода статусной информации о файле * - некоторые поля просто содержат числа... 7 #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> intmain(intac, char*av[]) { struct stat info; /* буфер для статусной информации */ if (ac>1) if(stat(av[1],&info)!=-1){ show_stat_jnfo(av[1], &info); return 0; } else perror(av[ 1 ]); /* сообщения об ошибках stat( )*/ return 1; } show_stat info(char *fname, struct stat *buf) Г * отображение информации из stat в формате a name=value 7 { printff mode: %o\n", buf->st_mode); /* тип + доступ 7 printff links: %d\n", buf->st_niink); /* количество ссылок 7 printff" user: %d\n", buf->st_uid); /* id пользователя */ printff group: %d\n", buf->st_gid); Г id группы 7 printff size: %d\n", buf->st_size); /* размер файла 7 printff modtime: %d\n", buf->stmtime); Л время модификации 7 printff name: %s\n", fname); /* имя файла 7 } Откомпилируем и запустим на исполнение программу fileinfo, а затем сравним полученный вывод с выводом, который получается при работе стандартной версии Is -1: $ ее -о fileinfo fileinfo.c $ /fileinfo fileinfo.c mode: 100664 links: 1 user: 500 group: 120 size: 1106 modtime: 965158604 name: fileinfo.c $ Is -I fileinfo.c -rw-rw-r-- 1 bruce users 1106 Aug 1 15:36 fileinfo.c
108 Свойства каталогов и файлов при просмотре с помощью команды Is 3.6.5. Чего мы достигли? Мы достигли того, что правильно отображаются такие атрибуты, как ссылки, размер, имя. Вывод значения времени модификации представлен в формате timej. Мы можем использовать ctime, чтобы конвертировать это значение в строку, где будет содержаться месяц, день, время или год. В поле mode в нашем выводе выводится значение режима в числовом виде, а при работе Is вывод будет символьным: -rw-rw-r-- Вывод в полях user и group представлен в числовом виде, а в команде Is в этих полях выводятся символьные имена собственника и имя группы. Для окончания работы над нашим вариантом по написанию Is -l нам необходимо еще ознакомиться, как конвертировать числовые значения полей mode, user и group в символьные представления значений. 3.6.6. Преобразование числового значения поля mode в символьное значение Каким образом представлены разряды, соотнесенные типу файла и правам доступа, в поле stjnode? Как нам выбрать эти атрибуты и представить их как последовательность из 10 символов? Какая связь между восьмеричным числом 100664 и строкой rw-rw-r-? Ответ: поле st mode шестнадцатиразрядное. Отдельные атрибуты закодированы в соответствующих подстроках в этом 16-разрядном поле. На рисунке 3.4 показано назначение пяти таких подстрок. Тип ~. ^ i? Собственник Группа Остальные 3 ел U ел д sti s г W X г W X г W X Рисунок 3.4 Представление кодов типа файла и прав доступа Подстрока из первых четырех разрядов предназначена для представления типа файла. В четырехразрядном поле можно хранить 16 возможных комбинаций из 1 и 0. Каждый из этих двоичных кодов может служить для представления отдельного типа файла. В настоящее время используется семь типов файлов. Следующая подстрока из трех разрядов предназначена для хранения специальных атрибутов файла. Каждый разряд в этой подстроке соответствует специальному атрибуту. Если любой разряд установлен в Ч \ то соответствующий ему атрибут установлен. Если разряд установлен в '0', то соответствующий ему атрибут не установлен. Эти специальные атрибуты называются set-user-ID, set-group-ID и sticky bits. Они будут рассмотрены позже. Наконец, далее расположены три последовательности трехразрядных подстрок для представления прав доступа к файлу. Первая подстрока - для хранения прав доступа собственника, вторая подстрока - для хранения прав доступа группы и последняя подстрока - для хранения прав доступа всех остальных пользователей. Для каждого класса пользователей в подстроке из трех разрядов можно задать наличие или отсутствие прав на чтение, запись и исполнение. Значение какого-либо разряда в любой из подстрок, равное 4Г, означает, что соответствующий вид доступа разрешен. Значение какого-либо разряда в любой из подстрок, равное '0', означает, что соответствующий вид доступа запрещен.
3.6. Проект2: Написание версии Is -I 109 Секреты кодировки подполей Весьма распространенным приемом является упаковка специальных значений в подполя больших строк. Эта идея иллюстрируется на таких примерах: Примеры кодирования подстрок 617-495-4204 Область, коммутатор, линия 027-93-1111 Личный социальный номер 128.103.33.100 IP-адрес Как читать подполя: Маскирование Как можно определить - принадлежит ли телефонный номер 212-333-4444 кодовой области 212? Очень просто. Вы берете три первых числа из номера и сравниваете их подстрокой 212. Другой подход будет заключаться в том, что вы обнуляете все цифры в телефонном номере, кроме первых трех, и затем сравниваете результат с 212-000-0000. Техника обнуления указанных подполей называется маскированием. Подход напоминает о маске на лице, которая все скрывает, за исключением ваших глаз и, возможно, ушей и рта. Мы можем использовать набор масок для преобразования значения поля stmode в символьную строку, которая выводится стандартной командой Is -1. Кодирование подполей является общим и важным методом системного программирования. Вам будет необходимо помнить о четырех моментах для понимания кодирования и маскирования подполей. Первый момент: Концепция маскирования Маскирование значения - это обнуление установленных значений разрядов в числе при условии, что остальные разряды остаются неизменяемыми. Второй момент: Целое число - это битовая строка Целые числа хранятся в компьютере как последовательность двоичных разрядов. На рисунке 3.5 показано, как десятичное значение числа 215 выражается как последовательность единиц и нулей, используя двоичную нотацию (основание 2). Каково будет десятичное значение, которое соответствует двоичному значению 00011010? 100's 10's l's \ / / 128 64 32 16 8 4 2 1 215 = oiioioiii 128 64 32 16 8 4 2 1 ooooiioilo Рисунок 3.5 Преобразования десятичного представления в двоичное Третий момент: Техника маскирования Операция поразрядного "И"(т. е. &) дает возможность маскировать одно значение с помощью другого значения. На рисунке 3.6 показано восьмеричное значение 100664 (основание 8), которое маскируется кодом, составленным пользователем. Отметьте, как некоторые единичные разряды в исходном числе будут преобразованы в 0 с помощью определенных разрядов маски.
/10 Свойства каталогов и файлов при просмотре с помощью команды Is 1000000110110100 0 0 0 0 0 0 0 0 1 0 1 1 0 0 0 0 0 0 0 0 0. 0 0 0 1 0 1 1 0 0 0 0 Рисунок 3.6 Использование двоичной маски Четвертый момент: Использование восьмеричного основания Использование масок в двоичном формате является достаточно утомительным, особенно для слов длиной в 16 или 32 разряда. Поэтому произведем группировку больших десятичных чисел в трехсимвольные "связки" (например, 23,234,456,022) для более простого прочтения значения числа, а также сгруппируем двоичные представления больших чисел в трехсимвольные "связки" и преобразуем каждую "связку" в одно восьмеричное число (значение от 0 до 7). Например, мы можем произвести группировку по связкам в двоичном числе 1000000110110100 и получить такое представление: 1,000,000,110,110,100. После чего преобразуем каждую связку и получим такое представление числа: 0100664, которое легче воспринимается. Использование маскирования для декодирования значения типа файла Тип файла кодируется в первом четырехразрядном поле mode. Для декодирования информации в этом поле мы можем использовать маскирование. Прежде всего, мы используем маску для обнуления всех разрядов, кроме первых четырех разрядов. Затем сравним полученный результат с кодами для каждого из типов: Определения этих кодов находятся в заголовочном файле <sys/stat.h>: #define #define #define #define #define #define #define #define SIFMT 0170000 SIFREG 0100000 S IFDIR 0040000 SIFBLK 0060000 SIFCHR 0020000 SJFIFO 0010000 SIFLNK 0120000 SJFSOCK 0140000 /* тип файла 7 /* обычный */ Г каталог */ /* специальный блочный */ /* специальный символьный */ /* программный канал fifo */ /* символическая ссылка */ Г сокет */ Символьная константа SJFMT - это маска, с помощью которой выбираются первые четыре разряда. Значением маски является число 0170000. Убедитесь в том, что эта маска выбирает правильный набор разрядов с помощью обратного преобразования каждого восьмеричного представления цифры в трехразрядный двоичный эквивалент. Код типа для обычного файла (SJFREG) равен 0100000. Значение кода типа для каталога равно 0040000. Например, во фрагменте кода: if ((info.stjnode & 0170000) = 0040000) printf("this is a directory."); будет проводиться проверка на тип каталога, что делается с помощью маскирования всех полей, кроме поля типа, и последующего сравнения результата с кодом типа каталога.
3.6. Проект 2: Написание версии Is -/ /// Если вы пожелаете написать код для маскирования и проверки, вы можете использовать при этом макросы из заголовочного файла <sys/stat.h>: /* • 7 #define #define #define #define #define Макросы для типов файла S ISFIFO(m) (((m)&@170000)) == @010000)) SJSDIR(m) (((m)&@170000)) == @040000)) SJSCHR(m) (((m)&@170000)) == @020000)) S ISBLK(m) (((m)&@170000)) == @060000)) S ISREG(m) (((m)&@170000)) == @100000)) С помощью этих макросов можно так написать наш код: if (S_ISDIR(info.st_mode)) printffthis is a directory."); Использование маскирования для декодирования разрядов прав доступа Последние девять разрядов в mode предназначены для представления прав доступа на выполнение операций чтения, записи и исполнения с файлом для каждого класса пользователей. В стандартной версии команды Is производится преобразование этих девяти двоичных разрядов, каждый из которых установлен в 1 или 0, в строку, которая состоит из последовательности символов и прочерков. Назначение каждого разряда маски можно посмотреть в файле <sys/stat.h>. Следующая программа представляет собой простое, читабельное приложение, которое проверяет отдельно каждый разряд: Л * В этой функции извлекается значение mode и формируется символьный массив. * В символьный массив помещается значение типа файла и * девять символов для представления прав доступа. * ЗАМЕЧАНИЕ: Коды setuid, setgid sticky * не рассматриваются 7 void mode_to_letters( int mode, char str[]) { strcpy(str,"- —"); if(SJSDIR(mode))str[0] = 'd'; if(SISCHR(mode))str[0] = ,c'; if(SJSBLK(mode))str[0] = ,b*; if(mode&SIRUSR)str[1] = r; if(mcxie&SJWUSR)str[2] = W; if(mode&SJXUSR)str[3] = V; if(mode&SJRGRP)str[4] = Y; if(mode&SIWGRP)str[5] = 'w'; if(mode&SIXGRP)str[6] = Y; if(mode&SIROTH)str[7] = r; /* по умолчанию - отсутствие всех прав */ /* каталог */ /* символьные устройства */ /* блочное устройство */ /* 3 разряда для собственника */ Г 3 разряда для группы */ /* 3 разряда для всех остальных */
/12 Свойства каталогов и файлов при просмотре с помощью команды Is if (mode & SJWOTH) str[8] = V; iffmode&SIXOTHlstr^Y; } Декодирование разрядов и написание версии Is У нас накопилось достаточно знаний для написания версии команды Is, которая может правильно работать с длинным форматом вывода. Мы можем правильно выводить значения таких атрибутов файла, как размер, ссылки и имя файла. Мы имеем возможность взять значение поля mode и преобразовать его значение в стандартную последовательность из символов и прочерков. Можно преобразовать с помощью ctime значение времени из формата time_t в строковый формат. А каковы соображения по строчному представлению имен собственника и группы? 3.6. Z Преобразования числового представления идентификаторов собственника/группы в строковое представление В нашем варианте в выводе для представления собственника и группы выдаются числа. В стандартном выводе команды Is выводится символьное пользовательское имя и имя группы. Какая связь между числовым идентификатором пользователя ию и пользовательским именем? При обращении к документации для поиска по ключевым словам usemame, w/d и group будет получен весьма различный по составу результат поиска, который будет зависеть от версии Unix. Посмотрим, что можно найти. Есть несколько интересующих нас факторов. Фактор первый: Файл /etc/passwd содержит список пользователей Как производится ваш вход в Unix - машину? Сначала система запросит у вас входное пользовательское имя, а затем пароль. Далее система определяет, верны ли указанные значения входного пользовательского имени и пароля. Как она узнает об их правильности? Традиционная система для учета пользовательских имен и паролей состоит из файла /etc/ passwd. В этом поле находится список всех пользователей данной системы. Содержимое файла выглядит так: root:WPA4d1 OwUxypE:0:0:root:/root:/bin/bash bin:*: 1:1 :bin:/bin: daemon:*:2:2:daemon:/sbin: smith:x1 mEPcp4TNokc:9768:3073:James Q Smith:/home/s/smJth:/shellsAcsh fred:mSuVNOF4CRTmE:20359:550:Fred:/homeAAred:/shellsAcsh diane:7oUS8f 1 PsrccY:20555:550:Diane Abramov:/home/d/diane:/shellsAcsh ajr:WitmEBWylar1 w:3607:3034:Ann Reuter:/home/a/ajr:/shells/bash Этот последовательный текст представляет собой список пользователей и информации о каждом из пользователей. Каждая строка в файле представляет одного пользователя. Поля в каждой строке разделяются знаком двоеточия. Первое поле предназначено для хранения пользовательского имени. Второе поле содержит зашифрованный пароль. Третье поле хранит значение пользовательского идентификатора, четвертое поле предназначено для хранения идентификатора группы, членом которой является пользователь. Следующие поля: поля для представления фактического имени пользователя, поле для указания домашнего каталога пользователя, поле для хранения маршрутного имени программы, которую пользователь использует в качестве shell. (Речь идет о произвольной про-
3.6. Проект 2: Написание версии Is -/ 113 грамме, которая запускается в начале сессии пользователя. - Примеч. пер.) Файл passwd доступен для чтения для всех категорий пользователей. Для более детального ознакомления с файлом обратитесь к электронному справочнику с аргументом passwd. Все выглядит вполне оптимистично. Достаточно найти в файле запись, которая содержит необходимый идентификатор пользователя, а далее необходимо прочитать первое поле в выбранной строке. Но этот метод не перспективен и вот почему: поиск в файле /etc/passwd - достаточно скучное занятие, кроме того метод не работает во многих сетевых системах. Фактор второй: В файле /etc/passwd не всегда содержится полный список пользователей В каждой системе Unix есть файл /etc/passwd, но во многих системах Unix в этот файл включаются не все пользователи. В сетевых реализациях систем предполагается регистрация пользователей на любой машине в сети с одним и тем же именем пользователя и паролем. Чтобы достигнуть такой возможности, используют файл /etc/passwd1. Системный администратор должен будет добавить в этот файл на каждой машине в сети одно и то же пользовательское имя и текущий пароль. Когда пользователь захочет изменить на какой-то машине пароль, то это изменение должно быть сделано в каждом файле /etc/passwd сети. Если одна из машин будет недоступна, то это может привести к нарушению процесса синхронизации в отношении оставшихся машин. Одно из решений - инсталлировать минимально файл /etc/passwd на каждой машине для автономных действий, но поддерживать полный список пользователей в базе данных, которая доступна в сети. Все новые пользователи и изменения паролей записываются в эту центральную базу данных. Все программы, которым необходима информация о пользователе, будут обращаться к центральной базе данных. Система с централизованной сетевой информацией называется nis. В электронном справочнике можно получить дополнительную информацию по этому поводу. Фактор третий: Доступ к полному списку пользователей обеспечивает функция getpwuid Библиотечная функция getpwuid предоставляет доступ к пользовательской информации в базе данных. Если в вашей системе используется файл passwd, то функция будет работать с этим файлом. А если используется центральная база данных, то функция getpwuid будет работать с этой базой. При использовании функции getpwuid в качестве аргумента задается идентификатор пользователя, а в результате функция возвращает указатель на структуру struct passwd, которая описана в файле /usr/include/pwd.h так: Г Структура passwd. */ struct passwd { char *pw_name; Л Пользовательское имя. */ char *pw_passwd; /* Пароль. */ _uidj pw_uid; /* Пользовательский ID. */ __gid_t pw_gid; /* Групповой ID. */ char *pw_gecos; /* Реальное имя. */ char *pw_dir; /* Домашний каталог. */ char *pw_shell; /* Программа Shell. 7 }; 1. Во многих системах пароли хранятся в зашифрованном виде в файле shadow, чтобы увеличить степень безопасности системы.
114 Свойства каталогов и файлов при просмотре с помощью команды Is Эта функция и описание этой структуры дают нам возможность организовать вывод поля с пользовательским именем в длинном формате. Вот таким может быть простое решение: Г * возвращается пользовательское имя, соотнесенное uid * ЗАМЕЧАНИЕ: код не работает, если нет пользовательского имени 7 char *uid to name(uid t uid) { return getpwuid(uid)->pw name; } Эта функция проста, но ненадежна. Если значению uid не найдено соответствующее пользовательское имя, то функция getpwuid возвращает указатель NULL. В этом случае нечего разыменовывать в pw_name. Как это может произойти? В стандартной версии команды Is приводится решение этой проблемы. Фактор четертый: Для некоторых UID нет входных имен Скажем, что вы зарегистрированы на некоторой Unix-машине и вам присвоено пользовательское входное имя pat, значение идентификатора пользователя равно 2000. Когда вы создаете файлы, то будете собственником этих файлов. То есть системный вызов stat будет возвращать в качестве результата структуры для ваших файлов, где в поле st_uid будет находиться 2000. Это число является атрибутом файла. Далее вы уехали в другой город. Системный администратор удалит учетную запись о вас из файла passwd. Тем самым удаляется связь между числом 2000 и пользовательским именем pat. Если программа будет передавать число 2000 при обращении к системному вызову getpwuid, то системный вызов будет возвращать null. В стандартной версии Is происходит обработка данной ситуации - будет выводиться uid, если нет соответствующего пользовательского имени. Что произойдет, если в системе будет зарегистрирован новый пользователь и ему будет присвоено значение старого UID? В системе могли остаться файлы, у которых теперь собственником становится этот новый пользователь. Этот пользователь имеет права на чтение, запись и удаление этих файлов. И наконец, как мы можем преобразовать идентификатор группы в имя группы? Что такое группа? Что такое идентификатор группы? Фактор пятый: Файл /etc/group содержит список групп Рассмотрим Unix-машину, которая используется в сфере бизнеса. В этой области все работники сгруппированы по отделам и отдельным проектам. Может быть группа людей, которые занимаются продажами, группа менеджмента и т. д. Рассмотрим школу. Весь состав людей в школе можно представить так: учителя, школьники, администрация. Людей можно сгруппировать и по другим признакам - по принадлежности студентов к одному и тому же курсу, по месту работы в одном и том же отделе. В Unix имеется система для регистрации групп и введения пользователей в состав групп. Имеется файл /etc/group, который является обыкновенным текстовым файлом и который выглядит примерно так:
3.6. Проект 2: Написание версии Is 7 115 root::0:root other:: 1: bin::2:root,bin,daemon sys::3:root,bin,sys,adm adm::4:root,adm,daemon uucp::5:root,uucp mail::6:root tty: :7: root, tty,adm lp::8:root,lp,adm Первое поле предназначено для хранения имени группы, во второе поле записывается пароль группы (редко используется на практике), в третье поле записывается идентификатор группы, в четвертом поле хранится список пользовательских имен. Элементы списка разделяются запятыми. Эти пользователи составляют группы. Фактор шестой: Пользователь может быть членом более чем одной группы В файле passwd для каждого пользователя заведены поля ию и gid. Идентификатор группы в файле passwd указывает первичную группу для пользователя, но пользователь может быть также зарегистрирован в составе других групп. В примере, приведенном выше, вы можете заметить, что пользователь adm находится в группах с именами sys, adm, tty, lp. Этот список используется при работе с разрядами прав доступа для группы. Например, если файл принадлежит группе с именем 1р и для группы установлены права на запись, тогда пользователь adm может модифицировать этот файл. Фактор седьмой: Системный вызов getgrgid предоставляет доступ к списку групп В сетевом варианте системы данные, которые размещаются в файле /etc/group, также можно переместить в центральную базу данных. Аналогично работе со статусной информацией для файлов в Unix есть возможность получать доступ к списку групп независимо от реализации системы. В документации на getgrgid приведены детали и необходимая информация. Для наших целей будем использовать код, подобный приведенному ниже. Л * возвращает имя группы, которое соотнесено указанному gid * ЗАМЕЧАНИЕ: не работает, если нет имени группы 7 char *gid_to name(gid t uid) { return getgrgid(gid)->gr name; } 3.6.А Объединение всего вместе: Is2. с Мы проверили каждый компонент в выводе Is -1. Для каждого из них мы знаем, что означает каждое поле и как можно преобразовать значение поля в форму, наиболее понятную для пользовательского восприятия. В результате программа ls2.c будет такой: Г Is2.c * цель - вывод списка содержимого каталога или каталогов * при отсутствии аргументов используется., в противном случае
6 Свойства каталогов и файлов при просмотре с помощью команд^ * используется список имен файлов через список аргументов * замечание - использует stat, pwd.h и grp.h * BUG: попробуйте Is2 Дтр 7 #include <stdio.h> «include <sys/types.h> «include <dirent.h> «include <sys/stat.h> voiddo_ls(char[]); void dostat(char *); void show_fileJnfo(char *, struct stat *); void modeJoJetters(int, char []); char *uid_to_name(uidJ); char *gid_to_name(gidJ); main(intac, char*av[]) { if (ac == 1) doJs('V'); else while (--ac){ printf(,,%s:\n,,J *++av); doJs(*av); } } void do_ls(char dimamefl) Г * перечисляет файлы в каталоге с именем dirname 7 { DIR *dirj>tr; struct dirent *direntp; if ((dir_ptr = opendir(dirname)) == NULL) fprintf(stderr,"ls1: cannot open %s\n", dirname); else { while ((direntp = readdir(dir_ptr)) != NULL) dostat(direntp- >d_name); closedir(dir ptr); } } void dostat(char *filename) { struct stat info; if (stat(filename, &info) == -1) perror(filename); else /* каталог */ /* какая запись */ Г неудача у stat 7 Г посмотреть почему 7 /* иначе показать информацию 7
7. Проект 2: Написание версии Is -I show file info(filename, &info); } void showfile_info(char *filename, struct stat *info_p) Г * выводит информацию о 'filename'. Эта информация записана в структуре *info_p 7 { char *uidJo_name(), *ctime(), *gid_to_name(), *filemode(); voidmode_to_letters(); charmodestr[11]; modeJo_letters(info_p->st,mode, modestr); printfp/os", modestr); printf("%4d", (int) info_p->st_nlink); printf("%-8s", uid_to_name(info_p->st_uid)); printf("%-8s", gid_to_name(info_p->st_gid)); printf(M%8ld", (long)info_p->st_size); printf("%.12s", 4+ctime(&info_p->st_mtime)); printf("%s\n", filename); } Г * utility functions 7 Л * В этой функции извлекается значение mode и формируется символьный масси * В символьный массив помещается значение типа файла и * девять символов для представления прав доступа. ж ЗАМЕЧАНИЕ: Коды setuid, setgid sticky * не рассматриваются 7 void mode_to_letters(int mode, char str[]) { strcpy(str," "); /* по умолчанию отсутствие прав */ if (SJSDIR(mode)) str[0] = 'd'; /* каталог? 7 if (SJSCHR(mode)) str[0] = 'с'; /* символьные устройства 7 if (SJSBLK(mode)) str[0] = 'b'; /* блочное устройство 7 if (mode & SJRUSR) str[1 ] = 'r'; /* 3 разряда для собственника 7 if (mode & SJWUSR) str[2] = 'w'; if(mode&SJXUSR)str[3] = *x'; if (mode & SJRGRP) str[4] = V; /* 3 разряда для группы 7 if(mode&SIWGRP)str[5] = 'w'; if(mode&S_IXGRP)str[6] = 'x'; if (mode & SJROTH) str[7] = 'r'; /* 3 разряда для остальных 7 if(mode&SJWOTH)str[8] = W; if(mode&SJXOTH)str[9] = 'x';
/18 Свойства каталогов и файлов при просмотре с помощью команды Is ) #include <pwd.h> char *uid to name(uid t uid) Г * возвращается указатель на пользовательское имя, соотнесенное ж идентификатору uid, используется getpw() 7 { struct passwd *getpwuid(), *pw_ptr; static char numstr[10]; if ((pw_ptr = getpwuid(uid)) == NULL){ sprintf(numstr,"% d", uid); return numstr; } else return pw ptr->pw name; } #include <grp.h> char *gid to name(gid t gid) Г * возвращается указатель на имя группы, используется getgrgidC) 7 { struct group *getgrgid(), *grp_ptr; static char numstr[10]; if ((grpjrtr = getgrgid(gid)) == NULL» sprintf(numstr,"% d", gid); return numstr; } else return grp ptr->gr name; } И вот теперь запустим нашу программу и получим также для сравнения стандартный вывод: $ls2 drwxrwxr-x drwxrwxr-x -rw-rw-r-- -rwxrwxr-x -rw-rw-r-- -rw-r-r- -rw-r-r-- -rw-rw-r- drwxrwxr-x drwxnvxr-x 4bruce 5 bruce 1 bruce 1 bruce 2 bruce 1 bruce 1 bruce 1 bruce 2 bruce 2 bruce bruce bruce users users users users users users users users 1024 Aug 1024 Aug 30720 Aug 37351 Aug 345 Jul 723 Aug 3045 Feb 27521 Aug 1024 Aug 1024 Aug 218:18. 218:14.. 1 12:05 s.tar 1 12:13 taih 2911:05 Makefile 1 14:26ls1.c 1503:51 Is2.c 1 12:14 chap03 1 12:14 old.src 1 12:15docs
3.7. Три специальных разряда 119 -rwxrwxr-x -rw-r--r-- - rwxrwxr-x -rw-r--r-- -rw-r-r- $ Is -1 total 189 -rw-rw-r- -rw-rw-r-- drwxrwxr-x -rwxrwxr-x -rw-r-r- - rwxrwxr-x -rw-r-r-- drwxrwxr-x -rw-rw-r- -rw-r-r- -rw-r-r- - rwxrwxr-x -rw-r-r- 1 bruce 1 bruce 2 bruce 1 bruce 1 bruce 2 bruce 1 bruce 2 bruce 1 bruce 1 bruce 2 bruce 1 bruce 2 bruce 1 bruce 1 bruce 1 bruce 1 bruce 1 bruce bruce support bruce support users users users users bruce users bruce users users users support support users users 37048 Aug 946 Feb 42295 Aug 191 Feb 1416 Aug 345 Jul 27521 Aug 1024 Aug 37048 Aug 723 Aug 42295 Aug 3045 Feb 1024 Aug 30720 Aug 946 Feb 191 Feb 37351 Aug 1416 Aug 1 14:26 Is1 1817:15stat1.c 218:18ls2 9 21:01 statdemo.c 1 12:05 taill. с 29 11:05 Makefile 1 12:14chap03 1 12:15 docs 1 14:26 Isl 1 14:26 Is1. с 2 18:18 Is2 15 03:51 Is2.c 1 12:14old_src 1 12:05 s.tar 1817:15stat1.c 9 1998 statdemo.c 1 12:13 taiM 1 12:05 taill.с $ Чего мы достигли? Программа ls2 отображает информацию о файлах в стандарте вывода команды Is -1. Вывод выглядит хорошо. Он происходит поколонно, производится преобразование из внутреннего представления разрядов доступа и числовых значений идентификатора в читабельные строки. Но программа все же нуждается в доработке. В реальной версии в самой первой строке вывода печатается строка total. Зачем нужна эта строка? Кроме того, в нашей программе все еще нет сортировки имен файлов, не работает опция - а, не производится упорядочения имен файлов по колонкам, программа рассматривает каждый аргумент при обращении к ней в качестве имени каталога. В программе ls2 есть еще более серьезные проблемы. Она не будет корректно выдавать информацию о файлах, которые находятся в других каталогах. Для рассмотрения проблемы попытайтесь выполнить команду ls2 /tmp. Следует решить эту проблему, что вы должны сделать в качестве упражнения. 3.7. Три специальных разряда Поле st_mode в структуре stat содержит шестнадцать разрядов. Четыре разряда используются для хранения типа файла, девять - для хранения прав доступа. Три оставшихся разряда используются для организации действий со специальными атрибутами файла. 3.7.1. Разряд Set-User-ID Первый из трех специальных разрядов называется set-user-ID. Он используется для решения важного вопроса:
120 Свойства каталогов и файлов при просмотре с помощью команды Is Как может обычный пользователь изменить его или ее пароль? Это легко сделать, используя команду passwd. Но как работает команда passwd? Заметьте - кто является собственником и каковы права доступа к файлу паролей. $ Is-I/etc/passwd -rw-r-Г" 1 root root 894 Jun 2019:17/etc/passwd Изменение вашего пароля означает изменение вашей учетной записи в этом файле, но вы не имеете прав доступа на запись в этот файл. Права на запись имеет только пользователь с именем root. Как добиться при использовании программы passwd, чтобы вы получили бы право на изменение файла, который не имеете права изменять? Почувствовали проблему? Решением будет предоставление прав на запись программе, но не вам. Вы используете программу /usr/bin/passwd или /bin/passwd для изменения вашего пароля, собственником которой является root и для которой установлен разряд set-user-ID. Права доступа будут выглядеть так: $ Is -I /usr/bin/passwd -r-sr-xr-x 1 root bin 15725 Oct 31 1997/usr/bin/passwd Установленный разряд suid сообщает ядру о необходимости запускать программу так, что предполагается, что программу запустили не вы, а собственник этой программы. Название разряда set-user-ID (Буквальный перевод - установить идентификатор пользователя. Но в русскоязычной литературе название этого разряда не переводится. - Примеч. пер.) подчеркивает тот факт, что этот разряд требует у ядра присвоения эффективному пользовательскому идентификатору значения пользовательского идентификатора собственника программы. Пользователь root является собственником файла /etc/passwd. Поэтому программа, которая запускается в статусе root, может модифицировать учетный файл. Не означает ли это, что я могу изменять пароли других пользователей? Нет. Программа passwd знает, кто вы такой. Она использует системный вызов getuid, чтобы узнать с помощью ядра- какой был у вас UID, когда вы вошли в систему. Программе passwd предоставлена возможность перезаписывать любые записи в учетном файле, но она будет изменять только запись, которая принадлежит пользователю, который запустил программу passwd. Другие случаи использования разряда Set-User-ID Разряд suid может быть использован некой программой, которая должна контролировать доступ к файлу или к каким-то другим ресурсам. Рассмотрим систему печати с буферизацией (систему спулинга). Для многих пользователей возникает необходимость распечатать свои файлы, но принтер может печатать в каждый момент времени только один файл. В Unix есть команда 1рг. (В HP-UX утверждается, что 1рг - это команда печати для Linux, а для Unix - 1р. - Примеч. ред.) Команда копирует ваш файл в каталог, где он будет ждать, когда он будет распечатан. Но было бы рискованным разрешить всем пользователям копировать свои файлы в этот каталог для спулинга и разрешать им модифицировать списки имен файлов, которые ждут в очереди на печать. Реально все происходит так. У программы 1рг собственником является root или 1рг, и для нее установлен разряд set-uid. Когда вы, обычный пользователь, используете команду 1рг, то программа будет запущена с установленным значением root или 1рг для эффективного UID и может теперь модифицировать содержание каталога для спулинга и соответствующих файлов в нем. Программы для удаления заданий на печать из очереди на печать также имеют установленные разряды set-uid.
3.7. Три специальных разряда 121 Компьютерные игры, которые модифицируют базы данных со счетчиками для игроков или читают файлы с секретными планами, будут маркироваться так, что они будут запускаться пользователем и работать в статусе собственника базы данных или секретных файлов. Любой пользователь может играть, но только программа игры может модифицировать списки со счетом или читать секретные планы. Маска для определения значения разряда sum Программа может проверить, установлен ли разряд set-user-ID для файла, с помощью маски, которая определена в заголовочном файле: <sys/stat.h>. Определение такое: #define SJSUID 0004000 /* set user id на исполнение 7 Вы можете убедиться, что маска выбирает первый из трех специальных битов. 3.7.2 Разряд Set-Group-ID Второй специальный разряд используется для установки эффективного группового идентификатора программы. Если программа принадлежит группе g и установлен разряд set - group ID, то программа будет запускаться так, если бы она запускалась на исполнение членом группы g. Этот бит предоставляет программе права доступа, которые приписаны членам группы. Программист может проверить значение данного разрядах помощью такой маски: #define SJSGID 0002000 /* установить group id на исполнение 7 3.7.3 Разряд Sticky Bit Этот разряд имеет две различные области использования - для работы с каталогами и для работы с файлами. Поговорим сначала о файлах. В Olden Days ®#.Что это такое? Unix разрешала проблему одновременного исполнения нескольких программ с помощью техники, которая называется своппированием. Рассмотрим следующую ситуацию. На вашем компьютере есть 1 мегабайт пользовательского пространства памяти, и вы запускаете на исполнение три программы, каждая из которых использует 0,5 мегабайта памяти. Очевидно, что только две программы из них могут одновременно находиться в памяти. Куда ядро поместит те программы, которые в текущий момент не могут быть исполнены? В Olden Days ®. Что это такое? было решено, что ядро может размещать сразу всю программу в разделе твердого диска, который резервируется специально для своппирования. В некоторый момент эта программа может быть повторно запущена на исполнение. Тогда ядро выгружает эту программу из области своппирования, а помещает туда одну из исполняемых до этого момента программ. Загрузка программы, которая хранилась на устройстве для своппинга, будет происходить более быстро, чем загрузка программы из обычного раздела диска. При хранении программы в обычном разделе на диске текст программы может быть фрагментирован, т. е. разбит на много малых секций, которые разбросаны по диску. При хранении программы на устройстве для своппирования текст программы не фрагментирован. Рассмотрим теперь такие программы, которые интенсивно используются. Это редакторы, компиляторы или компьютерные игры. Если копии таких программ поместить для хранения на устройстве для своппирования, то ядро будет загружать эти программы быстрее. Установленный разряд sticky bit (Название этого бита принято не переводить. - Примеч. пер.) для какой-либо программы говорит ядру о необходимости хранить эту программу на устройстве для своппинга, даже если никто эту программу в текущий момент времени не
122 Свойства каталогов и файлов при просмотре с помощью команды Is вызывает. Название разряда (sticke — приклеивать) обусловлено тем фактом, что программа "приклеивается " к устройству для своппирования так же, как жевательная резинка приклеивается к вашему ботинку. К настоящему времени признано, что своппировать полностью тексты программ (туда и обратно) больше нет необходимости. Теперь используется механизм виртуальной памяти, который позволяет ядру выгружать и загружать программы в память небольшими частями, которые называются страницами. У ядра отпадает необходимость загружать полностью блок кода, чтобы запустить программу на исполнение. Разряд sticky bit имеет другой смысл, если он установлен для каталога. Это значение также относится к проблеме "приклеивания". Некоторые каталоги создаются для хранения в них временных файлов. Эти временные каталоги, и прежде всего /tmp, доступны всем для записи, что дает возможность любому пользователю создавать и удалять любые файлы в таком каталоге. Sticky bit, который может быть установлен для каталога, аннулирует возможность доступа на запись для всех в этом каталоге. В таком случае файлы в каталоге могут удалять только их собственники. 3. Z 4. Специальные разряды и Is -/ Как мы убедились, каждый файл имеет атрибут типа и 12 разрядов для других атрибутов, но команда Is резервирует для вывода только девять знакомест для изображения в них этих 12 атрибутов. Как происходит отображение этих значений? В документации команды Is приведены детали. Пример - rwsr- sr-t 1 root root 2345 Jun 1214:02 sample показывает, что символ s используется в тех же местах, где может быть символ х для пользователя и группы. Символ s показывает, что произошла замена символа х на символы s, для обозначения установленных разрядов set-user and set-group-ID. Символ t свидетельствует об установленном разряде sticky bit. 3.8. Итоги для команды is Мы теперь имеем работающую версию команды Is, которая выводит список файлов в каталоге и отображает статусную информацию об этих файлах. По мере того как мы рассматривали возможности команды Is, рассматривали, как работает эта команда и при написании нашей собственной версии программы, у нас сложилось, в некотором смысле, определенное представление об Unix. Далее следует список основных тем. Каталоги и файлы В Unix данные хранятся в файлах. Каталог- это специальный тип файла. В каталоге имеется список имен файлов. В каталоге содержится также его собственное имя. В Unix есть набор функций, которые позволяют открывать, читать, искать и закрывать каталоги. Функции для записи в каталог отсутствуют. Пользователи и группы Каждому, кто использует систему, присваивается имя пользователя и числовое значение идентификатора пользователя. Пользовательские имена используются людьми для вхождения в систему и установления связей с другими людьми. Система использует значения UID для идентификации собственника файла. Люди принадлежат различным группам. Каждая группа имеет имя и числовой идентификатор группы.
3.9. Установка и модификация свойств файла 123 Атрибуты файла Каждый файл имеет набор свойств. Программа может получить список свойств файла с помощью системного вызова stat. Собственник файла У каждого файла есть собственник. UID собственника в Unix записывается в качестве свойства файла. Файл принадлежит группе. GID группы в Unix записывается в качестве свойства файла. Права доступа Пользователи могут читать файлы, писать в файлы и исполнять файлы. Каждый файл имеет набор разрядов, которые определяют, какие пользователи могут выполнять эти операции. Права на чтение, запись и исполнение могут контролироваться на трех уровнях: собственник, группа, остальные. 3.9. Установка и модификация свойств файла Команда Is -l отображает несколько свойств файла. Как можно устанавливать эти свойства? Можно ли изменять их значения? Если да, то как это делать? Если нет, то почему? Проверим установленные значения свойств в выводе в длинном формате: -rw-r-r-- 1 bruce users 3045 Feb 1503:51 Is2.c Рассмотрим слева направо каждый из атрибутов. 3.9.1. Тип файла Файл имеет тип. Могут быть обычные файлы, каталоги, файлы устройств, сокеты, символические ссылки и именованные программные каналы. Установка типа файла. Тип файла устанавливается при создании файла. Например, с помощью системного вызова creat создается обычный файл. Для создания каталогов, файлов устройств и других типов файлов используются другие системные вызовы. Изменение типа файла. Тип файла изменить невозможно. В сказках тыквы превращаются в кареты, но никто не объясняет, куда девать семечки и мякоть тыкв. 3.9.2. Разряды прав доступа и специальные разряды Каждый файл имеет девять разрядов прав доступа и три специальных разряда. Эти разряды устанавливаются при создании файла и могут быть модифицированы с помощью системного вызова chmod. Установка режима файла. Второй аргумент, который задается в системном вызове creat, служит для задания значений прав доступа, которые будут установлены при создании файла. Например: fd = creatrnewfile", 0744); Будет создан файл newfile, для которого требуется установить начальный набор таких прав доступа: rwxr—г—. Второй аргумент в creat - это требование на установку доступа. Ядро будет выбирать это требуемое значение и далее накладывать на него маску. В результате получается двоичный код, который и будет окончательным для установки прав доступа. Маска называется маска на создание файлов и определяет, какие разряды в исходном требовании на права доступа должны быть сброшены. Например, если вы хотите запретить программам созда-
124 Свойства каталогов и файлов при просмотре с помощью команды Is вать файлы в системе, которые можно было бы модифицировать группе и остальным пользователям, то вы должны будете сбросить разряды: —w--w-, что соответствует восьмеричному коду 022. Системный вызов umask в таком варианте: umask@22); установит маску на создание файлов, по значению которой будет происходить сброс этих двух разрядов. В общем случае маски используются для включения и выбора разрядов. В данном случае маска определяет, какие разряды следует сбросить. Да, такая вот обратная трактовка смысла. Изменение режима файла. Программа может модифицировать значения прав доступа и значения специальных разрядов с помощью системного вызова chmod. Два примера: chmodCytrnp/myfile", 04764); и chmodC'/tmp/myfile", SJSUID | SJRWXU | SJRGRP|SJWGRP | SJR0TH); имеют один и тот же результат. В первом случае указывается новый двоичный код, выраженный в восьмеричном формате, а во втором случае указываются маски, которые определены в файле <sys/stat.h>, комбинируются в один двоичный набор или оператор. Во втором случае можно изменять значение разрядов доступа в будущей работе, не прерывая для этого вашу программу. Количество существующих программ, которые используют точное восьмеричное представление, таково, что можно говорить о меньшей привлекательности этого варианта для тех, кто собирается менять значения разрядов доступа. Значение маски на создание файлов не влияет на значение режима, которое задается при обращении к системному вызову chmod. В заключение суммируем свойства в данной таблице: НАЗНАЧЕНИЕ INCLUDE ИСПОЛЬЗОВАНИЕ АРГУМЕНТЫ КОДЫ ВОЗВРАТА chmod Изменение прав доступа и специальных разрядов для файла #include < sys/types.h > #include <sys/stat.h> int result = chmod(char *path, modej mode); path - путь к файлу mode - новое значение режима -1 -при ошибке 0 - при успехе Команда Shell для изменения прав доступа и специальных разрядов. Для модификации прав доступа и специальных разрядов используется обычная Unix-команда chmod. Команда chmod допускает возможность для пользователя задавать двоичный код режима в восьмеричном представлении (например, 04764) или в символьной нотации (например, и -rws g =rw о =r). 3.9.3. Число ссылок на файл Назначение этого атрибута будет рассмотрено в следующей главе. Если говорить кратко, то число ссылок соответствует числу обращений к файлу в разных каталогах. Если файл оказывается представлен в трех местах, в различных каталогах, то число ссылок будет равно 3.
3.9. Установка и модификация свойств файла 125 Для увеличения значения счетчика ссылок нужно создать новые ссылки. (Вы можете использовать для этого системный вызов link.) Для уменьшения значения счетчика ссылок, необходимо удалить какое-то число ссылок. (Вы можете использовать для этого системный вызов unlink.) 3.9.4. Собственник и группа для файла У каждого файла есть собственник. Для внутреннего представления в Unix собственник представлен числовым значением UID, а группа, использующая файл, представлена числовым значением GID. Установление собственника файла. В самом простом толковании собственник файла - это пользователь, который создал файл. Но файлы создают не люди, а ядро. Ядро создает файл, когда в процессе выполняется системный вызов creat. Когда ядро создает файл, оно устанавливает в качестве собственника файла эффективный UID процесса, который выполнял вызов creat. Значение эффективного UID процесса обычно равно значению UID того, кто породил процесс. Если в программе процесса был установлен разряд set-user-ID, то эффективный UID будет равен значению того пользователя, кто является собственником этой программы. Все ясно? Установление группы для файла. Обычно в качестве группы для файла устанавливается эффективный GID процесса, который создает файл. Но иногда значением GID для файла становится значение GID родительского каталога. Ну, как? Такие действия напоминают процедуру, как если бы национальность устанавливалась по месту вашего рождения и не принимались во внимание ваши родители, которые вас и создали. В системе делается нечто напоминающее эту процедуру. Изменение собственника и группы для файла. Программа может изменять собственника и группу для файла с помощью системного вызова chown. Например: chownC'filel", 200,40); Здесь происходит изменение пользовательского ID на 200, значение группового ID заменяется на 40 для файла с именем filel. Если какой-либо аргумент будет иметь значение -1, то этот атрибут не модифицируется. Обычно пользователи не меняют собственника файла. Суперпользователь может установить в любой момент и для любого файла требуемое значение пользовательского ID и группового ID. Этот вызов обычно используется для установки и управления пользовательскими входами в систему. Собственник файла может изменить групповой ID файла в любой группе, к которой он принадлежит. В итоге мы имеем следующую таблицу с характеристиками вызова: НАЗНАЧЕНИЕ INCLUDE ИСПОЛЬЗОВАНИЕ АРГУМЕНТЫ chown Изменение собственника или группового ID для файла #include < unistd.h > int chown(char *path, uidj owner, gidJ group) path - путь к файлу owner - пользовательский ID для файла КОДЫ ВОЗВРАТА group - групповой ID для файла -1-при ошибке 0 - при успехе
126 Свойства каталогов и файлов при просмотре с помощью команды Is Команды Shell для изменения идентификаторов пользователя и группы для файлов В shell есть обычные команды chown и chgrp, с помощью которых программы могут модифицировать пользовательский ID и групповой ID для файлов. В одной команде с помощью этих команд можно изменять UID и GID для нескольких файлов. В документации изложены все детали. В командах chown и chgrp пользователи могут задавать идентификаторы или в числовом варианте, или в символьном - как имена пользователей и имена групп. 3.ft5. Размер файла Размер файла, каталога и именованного программного канала представляется в выводах числом хранимых байтов в таких файлах. Программы могут увеличить размер файла добавлением в него данных. Программы могут обнулить размер файла с помощью системного вызова creat. Программы не могут сокращать размер файла до некоторой ненулевой длины. (Утверждение слишком категорично. Для сокращения размера можно использовать системный вызов truncate. - Примеч. ред.) 3.Л6. Время последней модификации и доступа Каждый файл имеет три временных отметки: время последней модификации файла, время последнего чтения из файла и время последней модификации статусной информации файла (таки,е как идентификатор собственника или права доступа). Ядро автоматически модифицирует значения этих времен, когда программы пишут в файлы или читают из файла. Это может показаться странным, но вы можете написать программы, которые устанавливали бы произвольные значения для времени последней модификации и времени последнего доступа. Изменение значений времен последней модификации и последнего доступа к файлу С помощью системного вызова utime можно устанавливать время последней модификации и время последнего доступа к файлу. Для того чтобы использовать системный вызов utime, создается структура, в которой находятся два элемента time_t, один для хранения времени доступа, а другой - для времени модификации. Затем происходит вызов utime, где задается имя файла и указатель на эту структуру. Ядро устанавливает в этой структуре значения времени доступа и времени модификации для этого файла. В итоге сведем свойства вызова в таблицу: НАЗНАЧЕНИЕ INCLUDE ИСПОЛЬЗОВАНИЕ АРГУМЕНТЫ КОДЫ ВОЗВРАТА utime Изменение времени модификации и доступа к файлу #include < sys/time.h > #include <utime.h> tinclude <sys/types.h> int utime(char *path, struct utimbuf *newtimes) path - путь к файлу newtimes - указатель на структуру utimbuf См. более детально в utime. h -1 -при ошибке 0 - при успехе Почему у вас может появиться желание изменить время последней модификации или последнего доступа? Использование системного вызова utime будет полезно, в частности, когда вы извлекаете файлы из копий (backups) и архивов. Рассмотрим набор файлов, который был сброшен в backup. При хранении этого набора на диске или на ленте эти
Заключение 127 файлы будут иметь свои первоначальные значения времен модификации. Когда программа восстанавливает файлы из backup, то она гарантирует, что получит файл назад с правильным временем модификации. Программа, которая копирует файлы из места хранения backup, выполняет два действия. Во-первых, она копирует данные в новый файл. Затем она изменяет время модификации и время доступа так, чтобы они были равны значениям для оригинальных файлов, которые остались в backup на диске. Таким образом, ваши восстановленные файлы имеют то же содержимое и те же свойства, что и оригинальные файлы. Команды Shell для изменения времени модификации и времени доступа. Обычная Unix-команда touch выполняет установку значений времени модификации и времени доступа к файлам. В документации приведены подробности об этой команде. 3.9.7. Имя файла Когда вы создаете файл, вы присваиваете ему имя. С помощью команды mv можно изменять имя файла. Кроме того, команда mv может перемещать файл из одного каталога в другой. >* Установление имени файла. Системный вызов creat устанавливает имя и начальный режим для файла. Изменение имени файла. Системный вызов rename изменяет имя файла. При обращении к вызову задаются два аргумента, старое и новое имя: НАЗНАЧЕНИЕ INCLUDE ИСПОЛЬЗОВАНИЕ АРГУМЕНТЫ КОДЫ ВОЗВРАТА rename Изменение имени и/или перемещение файла #include < stdio.h > int result = rename(char *okJ, char *new) old - старое имя файла или каталога new - новое маршрутное имя для файла или каталога -1 -при ошибке 0 - при успехе Заключение Основные идеи • На диске находятся каталоги и файлы. Файлы имеют содержимое и свойства. В файле могут содержаться данные некоторого типа. Каталог может содержать только список имен файлов. • Имена в каталогах соотнесены файлам и другим каталогам. В ядре есть системные вызовы для чтения содержимого каталогов, для чтения свойств (атрибутов) файлов и для модификации большинства атрибутов файлов. • Тип файла, права доступа и три специальных атрибута хранятся как двоичный код некоторого целого числа. Для выборки определенных разрядов используется техника поразрядного маскирования. • Собственник и группа файла хранятся в числовом представлении. Соответствие между этими числами и символьными именами собственника и группы устанавливается через базы данных passwd и group.
128 Свойства каталогов и файлов при просмотре с помощью команды Is Что дальше? Каталог - это список файлов и каталогов. Каталоги связаны между собой в древовидную структуру. Как происходит работа с этим деревом? Как связываются между собой каталоги? В следующей главе мы рассмотрим внутреннюю структуру дерева. Визуальнй итог Свойства (соседа ифРОДя} Обычные файлы Рисунок 3.7 Диск содержит файлы, каталоги и статусную информацию о них Исследования 3.1 Длина d_name[ ]. В определении структуры struct dirent длина символьного массива d_name[] была указана равной 1 для некоторых систем и может быть 255 символов для других систем. Какова будет фактическая длина? Почему приведены такие странные числа? Почему не использована нотация вида: char *? 3.2 Защита файлов от самого себя. Режим доступа вида: rwx, который вы можете установить с помощью команды chmod 007 filename, вполне допустим, но явно весьма эксцентричен. Он гарантирует все остальным пользователям право на чтение, запись и исполнение в отношении файла filename, но не предоставляет таких прав ни собственнику, ни группе. Установите такой режим для некоторого файла. Смогли ли вы прочитать такой файл? Поэкспериментируйте с различными комбинациями, чтобы представить себе логику ядра, которая используется при определении прав доступа при работе системного вызова open. Если у вас есть возможность доступа к исходным
Заключение 129 кодам ядра, найдите ту часть кода, которая отвечает за права доступа, и проверьте - правильны ли ваши догадки относительно логики работы. 3.3 Идентификаторы и пользовательские имена. Каждый пользователь имеет пользовательское имя и каждому имени сопоставлен пользовательский числовой идентификатор. Возможен ли случай, когда двум различным пользовательским именам сопоставлен один и тот же числовой идентификатор пользователя? Возможен ли случай, когда одному и тому же пользовательскому имени сопоставлены два различных числовых идентификатора пользователя? Если у вас есть права доступа root на машине, то попробуйте поэкспериментировать с различными комбинациями. Заведите учетные записи на двух различных пользователей, для которых укажите один и тот же UID, но для каждого пользователя должны быть установлены свое пользовательское имя и свой пароль. Могут ли каждый из пользователей модифицировать файлы у другого? Что должна показать при выводе команда who? Что покажет команда Is -l? Что покажет команда id? А как быть с электронной почтой? Что дал вам эксперимент для понимания того, как работают эти команды? Можете ли придумать ситуации, где было бы полезным использовать множество пользовательских имен при одном и том же пользовательском идентификаторе? 3.4 Специальные разряды и каталоги. Каталоги, как и все файлы в Unix, имеют полный набор разрядов для определения прав доступа, включая разряды set-user-ID, set- group-ID. Возникает вот какая задача. Если вы установите в отношении каталога pKspnnset^roup-ID, то повлияет ли он на работу с каталогом? Если да, то как и почему? Если нет, то подумайте, как можно будет использовать этот разряд? 3.5 Исполнимый код и права на исполнение. У каждого файла есть три разряда для установки права на исполнения для собственника, группы и для всех остальных пользователей. Вы можете установить право на исполнение для любого файла, для обыкновенных текстовых файлов, даже для тех, которые содержат перечень товаров в продовольственном магазине. Но, с другой стороны, может быть файл, который содержит исполнимый код, например, a.out, и который получен после компиляции С-программы. Для этого файла может быть сброшен разряд на исполнение. Объясните разницу между идеями исполнимого кода и правами на исполнение для файла. Связаны ли эти идеи между собой? Почитайте документацию по команде file. 3.6 Входные имена и пользовательские ID. Каждый пользователь имеет символьное пользовательское имя и числовой идентификатор — UID. Зачем? Не было бы более простым указывать в качестве владельца файла символьное имя пользователя? Почему бы для каждого пользователя не завести только один числовой идентификатор? В чем проблемы с двумя системами идентификации? Какие преимущества предоставляют две системы идентификации? Если бы вы проектировали собственную операционную систему, то как бы вы поступили в отношении этого вопроса? 3.7 В документации на dirent, текст которой был приведен, была сделана ссылка на системный вызов getdentsB). Что делает этот вызов и какое отношение он имеет к readdir? 3.8 Обычно в листинге команды Is -l каталоги представлены с такими правами доступа: drwxr-xr-x.
130 Свойства каталогов и файлов при просмотре с помощью команды Is При посимвольном разборе этого поля слева направо обнаруживаем: тип файла - каталог, собственник каталога может выполнять операции чтения, записи и исполнения в отношении каталога, члены группы и все остальные пользователи могут выполнять только операции чтения и исполнения в отношении каталога. Что означает право на "исполнение" в отношении каталога? Файл может содержать код, составленный из команд конкретной машины, или код, составленный на "скрип- товом" языке. Имеет смысл маркировать такой файл как "исполняемый", поскольку компьютер может непосредственно исполнять программу на машинном языке или запустить интерпретатор для исполнения скрипта. Каталог же - это просто список имен файлов, и он не может быть запущен на исполнение. Так что же означает разряд, дающий право на исполнение каталога? Почему он полезен? Поэкспериментируйте с командной chmod и выключите разряд на исполнение для каталога и посмотрите, что получится в данной ситуации. 3.9 Работа с вашим терминалом. Пользователи связываются с системой с помощью терминалов или терминальных эмуляционных программ. Каждый терминал представлен как файл в каталоге /dev. Выполните следующие команды: Is -l /dev/tty* \ more. В результате получим вывод списка всех терминальных устройств и их свойств. Файлы, которые представляют терминалы, как и обычные файлы, имеют собственника. Собственником терминала будет тот пользователь, который вошел в систему через этот терминал. Терминальные устройства не используют root в качестве собственника. Собственник терминала изменяется программой login. Обратитесь к исходному ' коду программы login и отыщите в ней код, где происходит изменение собственника. Что изменяет программа, когда собственность возвращается опять в root, когда вы будете выходить из системы? Программные упражнения 3.10 Добавьте возможность многоколонного вывода в версии программы Is 1 .с. Поэкспериментируйте со стандартной версией команды Is с тем, чтобы посмотреть, что она делает. Вы можете увидеть, что она выводит поколонно в зависимости от длины текущего списка имен файлов. Колонки по возможности строятся в выводе равной длины. Наконец, ширина для отображения при выводе. Как команда Is конструирует такой вывод? 3.11 Модификация ls2. Следует модифицировать ls2.c так, чтобы она правильно работала, когда имя каталога задается как аргумент при обращении к программе. 3.12 Модифицируйте программу ls2.c так, чтобы ею можно было корректно управлять с помощью разрядов suid, sgid, sticky bit. Почитайте документацию, чтобы убедиться, что вы учли все возможные комбинации. 3.13 Стандартная утилита ср допускает использование имени каталога в качестве второго аргумента. В таком случае файл будет копироваться в этот каталог и получать имя оригинала. То есть, команда: $ cpfilel /tmp
Заключение 131 будет работать так же, как и команда: $cpfile1/tmp/fjle1 Модифицируйте программу cpl.c в главе 2, чтобы реализовать такой вариант обращения. 3.14 Иногда вам необходимо скопировать в некоторый каталог сразу все файлы. Просто вам требуется сделать "сброс" (backup) всех файлов. Модифицируйте программу cpl .с в главе 2 так, чтобы при задании двух имен каталогов в качестве аргументов программа копировала бы все файлы из первого каталога во второй, присваивая каждой копии имя оригинала. 3.15 Модифицируйте программу Isl.c так, чтобы она производила бы сортировку списка имен файлов. В стандартной версии команды Is поддерживается опция -г, с помощью которой можно выдавать список в обратном порядке. Добавьте возможность отработки этой опции. В некоторых версиях команды Is поддерживается опция a -q для "quick" (быстрого) вывода. При использовании такой опции команда Is не производит сортировки списка имен файлов. Эта опция полезная, когда каталог содержит слишком большое число файлов. Их так много, что даже при быстрой сортировке на вывод тратится значительное время. 3.16 Нарисуйте строку, содержащую 16 знакомест, и заполните требуемые знакоместа единицами и нулями с тем, чтобы они соответствовали такому праву доступа к каталогу: rwxr-x—х. 3.17 Блокировка сарая. Если вы выключили право чтения на файл, то вы не сможете открыть файл на чтение. Что будет, если вы открыли файл на чтение, а затем с другого терминала сбросили право на чтение для этого файла? Выполнится ли успешно следующий системный вызов read? Напишите программу, которая открывает файл на чтение, далее читает из него несколько байтов, затем выполняет системный вызов sleepB0) и находится в состоянии ожидания 20 секунд, а далее опять пытается прочитать из файла. В течение этих 20 секунд сбросьте для файла право на чтение. Что произойдет далее? Объясните истинный смысл прав доступа на чтение. 3.18 Рекурсия для Is. Стандартный вариант команды Is поддерживает опцию -R. Эта опция позволяет получить содержимое каталога и содержимое всех подкаталогов под ним. Попытайтесь выполнить такую команду. Модифицируйте вариант программы ls2.c, чтобы работала опция -R. 3.19 Вывод времени последнего доступа. В стандартном варианте команды Is поддерживается опция -и, с помощью которой можно отображать в длинном листинге время последнего доступа вместо времени последней модификации. Что произойдет, если будет использована опция -и без использования опции -1? Намек: почитайте документацию относительно опции -1 3.20 Напищите простую версию программы chown, которая при обращении должна воспринимать из командной строки пользовательское имя или пользовательский ID и произвольное имя файла. Как вы будете преобразовывать пользовательское имя в пользовательский идентификатор? Что будет, если не будет обнаружен пользователь с указанным пользовательским именем? Замечание. Чтобы проверить работу вашей программы, вам понадобится стать суперпользователем.
132 Свойства каталогов и файлов при просмотре с помощью команды Is 3.21 Время последнего доступа к файлу - это полезная информация. Получение backup для файлов - это хорошая идея. Но когда происходит восстановление (чтение) файлов из backup, то изменяется время последнего доступа для каждого восстановленного файла. Было бы очень хорошо, если бы программа для восстановления из backup при восстановлении файла оставляла для него неизменяемым время последнего доступа. Кроме того, не менее приятно было бы, если бы копия файла имела такие же времена последней модификации и последнего доступа, как у оригинала. Напишите версию программы ср, которая выполняла эти два действия. 3.22 Ваше терминальное устройство - это файл, который программа использует, чтобы получать из него данные для вас и посылать от вас данные на него. Когда программа читает с терминального устройства, то она получает данные с вашей клавиатуры. Когда программа выдает данные на терминальное устройство, то они передаются на экран. Для установления времени последней модификации в терминальном файле используется stjfitime. Напишите программу с именем lastdata, которая выводит список всех текущих пользователей и отображает для каждого пользователя время, когда была последняя модификация терминала. Используйте тот же формат, как в команде who. Проекты Основываясь на материале этой главы, вы можете изучить справочный материал и написать версии следующих Unix программ: Ghmod, file, chown, chgrp, finger, touch .
Глава 4 Изучение файловых систем. Разработка версии pwd Цели Идеи и сродства • Пользовательское восприятие дерева файловой системы в Unix. • Внутренняя структура файловой системы Unix: структуры inodes и блоки данных. • Как связаны между собой каталоги. • Твердые ссылки, символические ссылки: идеи и системные вызовы. Как работает команда pwd. • Монтирование файловых систем. Системные вызовы и функции • mkdir, rmdir, chdir • link, unlink, rename, symlink Команды • pwd 4.1. Введение Файлы содержат данные. В каталогах содержится список имен файлов. Каталоги организованы в древовидную структуру. В них могут содержаться имена других каталогов. Что для файла значит "быть в каталоге"? Когда вы сходите в Unix-машину, то вы попадаете в ваш "домашний каталог". Что означает для пользователя выражение "находиться в каталоге"? Древовидная структура является иллюзией. Твердый диск-это набор металлических пластин, на каждой из которых нанесено магнитное покрытие. Как сделать, чтобы этот набор связанных металлических пластин представлялся бы нам как дерево файлов, свойств и каталогов?
134 Изучение файловых систем. Разработка версии pwd Чтобы найти ответ на вопрос, напишем версию команды pwd. Эта команда сообщает о вашем текущем расположении в дереве каталогов. Последовательность каталогов и подкаталогов от корня дерева до места вашего расположения называют путем к вашему рабочему каталогу. Чтобы написать версию pwd, нам следует понять, как организованы и хранятся файлы и, каталоги. Мы будем изучать файловую систему, начиная с рассмотрения ее с позиций пользователя. Далее рассмотрим ее внутреннюю структуру. И наконец, изучим системные вызовы и способы их использования. 4.2. Пользовательский взгляд на файловую систему 4.2.1. Каталоги и файлы Пользователи воспринимают диск в Unix как дерево каталогов. В каждом каталоге могут содержаться файлы и другие каталоги. На рисунке 4.1 представлена схема небольшой части дерева. СО® ПЛЮСОВ IT GD d1 jclink d2 xcopy Рисунок 4.1 Дерево каталогов Начнем со строительства этой древовидной структуры и по мере строительства введем в использование ряд Unix-команд для управления такими деревьями файлов и каталогов. 4.2.2. Команды для работы с каталогами Дерево можно создать с помощью последовательности таких команд: $ mkdir demodir $ cd demodir $pwd /home/yourname/experiments/demodir $ mkdir b oops $mvbc $ rmdir oops $ cd с $ mkdir d1 d2 $cd../-. $ mkdir demodir/a Мы будем использовать много других команд. Та последовательность команд, что была только что приведена, преднамеренно усложнена и включает в себя несколько методов.
4.2. Пользовательский взгляд на файловую систему 135 Прочтите текст этого примера и нарисуйте дерево таким, как оно вам представляется. Пример использует несколько базовых команд. Команда mkdir создает новый каталог или каталоги с заданными именами. Что произойдет, если вы попытаетесь создать каталог и укажете при этом имя, которое уже присвоено файлу или каталогу? Команда rmdir удаляет каталог или каталоги. Что произойдет, если вы попытаетесь удалить каталог, в котором содержатся подкаталоги? Команда mv переименовывает каталог. С ее помощью можно также перемещать каталог с одного места в другое. Команда cd несколько отличается. Она ничего не делает с каталогом. Эта команда влияет на вас, на пользователя. Команда cd перемещает вас из одного каталога в другой, как если бы вы переходили из одной комнаты в другую. Команда pwd выводит путь к текущему каталогу. В примере мы начали работу в подкаталоге с именем demodir. Это подкаталог для каталога experiments, который в свою очередь находится в каталоге yourname. Каталог your- name расположен под каталогом home, а он в свою очередь находится под корневым каталогом, который обозначается символом слеша (/). 4.2.3. Команды для работы с файлами Теперь мы создадим несколько файлов в дереве каталогов: $ cd demodir $ ср/etc/group x $catx root::0: bin::1:bin,daemon users: :200: $ cp x copy.of.x $ mv copy .of .x у $mvxa $cdc $cp../a/xd2/xcopy $ln../a/xd1/xlink $ Is > d1/xlink $cpd1/xlinkz $ rm../. ./demodir/c/d2/. ./z $cd../- $ cat demodir/a/x (что здесь произойдет?) Эта последовательность команд для работы с файлами также намеренно усложнена, чтобы показать различные команды и проиллюстрировать их действие. Пройдите в пошаговом режиме по этой последовательности команд и нарисуйте файлы по мере их возникновения. Предскажите, каков будет результат выполнения последней команды в данной последовательности. В этом примере использована большая часть команд для управления файлами. Команда ср выполняет копирование файла. Мы разработали версию команды ср в предшествующей главе. Команда cat копирует содержимое файла на стандартный вывод. Команда mv переименовывает файл, как показано в первом примере, и перемещает файл в другой каталог, как показано во втором примере. Команда rm удаляет файл. Заметьте, что в пути может быть использована нотация вида "..". Такая нотация обозначает каталог, который расположен на
136 Изучение файловых систем. Разработка версии pwd уровень выше. Его называют родительским каталогом. Последовательность имен каталогов, с символами / в качестве разделителей в последовательности, определяет путь, который ведет к именованному объекту. В частности, заметьте* что используется окольный путь для удаления файла с именем z. Cfl<?$ OTDOCfl 0 D" ¦ Фактический файл Рисунок 4.2 Две связи к одному и тому же файлу Копирование, проверка наличия, переименование - это все стандартные операции, которые имеются на многих компьютерных системах. Команда In носит не столь общий характер, а является неотъемлемой частью Unix. В данном примере мы взяли существующий файл../а/х и сделали на него ссылку. Ссылка была названа dl/xlink. Обратитесь к рисунку 4.2 и найдите эти два элемент а. Элемент, который называется х, находится в каталоге demodir/a, а элемент с именем xlink находится в каталоге demodir/c/dl. Как х, так и xlink называют ссылками. Ссылка - это указатель на файл. Как../а/х, так и dl/xlink указывают на одни и те же данные на диске. В соответствии с нашим примером следующая команда Is > dl/xlink заменяет содержимое xlink выводом от команды Is. Что получится, если с помощью cat вывести содержимое файла../а/х? 4.2.4. Командыдляработы сдеревом Несколько команд в Unix предназначены для работы с древовидными структурами. Вот несколько примеров: Is-R Команда Is может выводить в списочном формате содержимое всего дерева. С помощью опции -R задается такой режим отображения, когда будет выводиться содержимое указанного каталога и всех подкаталогов под ним. В предшествующей главе мы разработали версию команды Is. Что еще необходимо сделать в нашей версии, чтобы добавить эти рекурсивные возможности. chmod-R Команда chmod изменяет права доступа у файлов. С помощью опции -R в этой команде можно производить изменения прав доступа у всех файлов в подкаталогах. du Команда du (имя является сокращением от disk usage - использование диска) сообщает о числе дисковых блоков, которые используются каталогом, указанным в команде, и всеми входящими в него файлами и подкаталогами с их файлами - т. е. рекурсивный спуск до листьев данного поддерева
4.3. Внутренняя структура файловой системы UNIX 137 find Команда find ищет в каталоге и во всех вложенных подкаталогах файлы и каталоги, которые удовлетворяют критерию поиска, заданному в команде. Например, вы можете искать файлы с размером большим одного мегабайта, которые не были модифицированы в течение последней недели и которые доступны на чтение всем пользователям. Бще немного о командах Поддеревья каталогов составляют существенную часть файловой системы. В Unix имеется много команд для работы с деревьями. Вы многое можете обнаружить в этой области. 4.2.5. Практически нет пределов на древовидную структуру В каталогах может находиться большое количество файлов и большое число подкаталогов. Внутренняя структура системы не накладывает ограничений на глубину дерева каталогов. Можно создавать каталоги с такой глубиной, чтобы они удовлетворяли возможностям большинства команд для работы с деревьями. Предупреждение. Если вы пожелаете, то проведите такой эксперимент на вашей собственной машине. Системный администратор в вашей школе или на вашей работе вряд ли обрадуется, если вы попытаетесь провести эксперимент на его машине. Простой shell-скрипт (Этот термин все более часто используют при переводе литературы по Unix. Хотя вполне допустимо использование эквивалентного термина - shell-процедура. - Примеч. пер.) (см. главу 8). while true do . mkdir deep-well cd deep-well done создаст связанный список каталогов очень большой глубины, даже если вы нажмете на Ctrl-C сразу через одну или две секунды после запуска скрипта на исполнение. Что покажет утилита du при обработке этого "туннеля"? А как будут вести себя команды find и Is -R? Во многих версиях Unix команда rm -r deep-well не работает. Как можно удалить такие структуры с большой глубиной? 4.2.6. Итоговые замечания по файловой системе Unix В этом разделе мы рассмотрели файловую систему Unix с позиций пользователя. С этих позиций диск представляется в виде дерева каталогов, которое может расширяться как вглубь, так и вширь. В Unix имеется много программ для работы с объектами такой структуры. Все файлы в системе Unix располагаются в составе этой структуры. Как это все работает? Что такое каталог? Как узнать, в каком каталоге мы находимся? Что означает для вас, для человека, смена одного каталога на другой? Как команда pwd определяет, где вы находитесь в дереве? 4.3. Внутренняя структура файловой системы UNIX Диск представляет собой набор металлических пластин. Несколько уровней абстракции преобразуют наше представление физического диска в файловую систему, которую мы рассматривали в предшествующем разделе.
138 Изучение файловых систем. Разработка версии pwd 4.3.1. Абстракция О: От пластин к разделам Диск может хранить набор данных. Как страна разделяется на штаты или округа, так и диск может быть разделен на разделы для создания отдельных регионов с определенной однородностью содержания. Мы будем рассматривать каждый раздел как отдельный диск. 4.3.2. Абстракция 1: От плат к массиву блоков Диск - это набор магнитных плат. Поверхность на каждой магнитной плате представляет собой структуру из концентрических окружностей, которые называют треками. Каждый из таких треков разделен на секторы, как пригородная улица разделяется на жилые массивы. Каждый их этих секторов хранит некоторое число байтов данных, например 512. Сектор - это основная единица хранения данных на диске. На современных дисках количество секторов очень большое. На рисунке 4.3 представлена схема нумерации для дисковых блоков. Рисунок 4.3 Нумерация дисковых блоков А теперь рассмотрим важную идею: нумерацию дисковых блоков. Присвоение номеров дисковым блокам в последовательном порядке дает возможность системе вести учет каждого блока на диске. Можно проводить нумерацию блоков в нисходящем порядке, от платы к плате, а можно нумеровать вовнутрь каждой платы - нумеровать блоки от трека к треку на плате. Подобно почтальону, который устанавливает соответствие каждому письму дома и улицы, должно быть программное обеспечение, которое управляет хранением данных на диске по адресам, которые назначаются отдельным блокам на некотором треке на диске. Система нумерации на диске по схеме разбиения на секторы дает вам возможность рассматривать диск как массив из блоков. 4.3.3. Абстракция 2: От массива блоков к дереву разделов Файловая система хранит файлы. Более точно, файловая система хранит содержимое файлов, атрибуты файла (собственник, временные отметки и т. д.) и каталоги, в которых находятся такие файлы. А где находятся в однородной последовательности блоков содержимое файла, атрибуты файла и каталоги? В Unix используется простой метод. Массив блоков разделяется на три секции, как показано на рисунке 4.4.
4.3. Внутренняя структура файловой системы UNIX 139 Суперблок I Таблица inode Власть данных ш\ \ '' '- г' '" ш т?> f < щ •/* Ь* М 'f- г. ЕЕ ^;|*Н?*| Здесь Здесь содержимое файла свойства файла Рисунок 4.4 Три области файловой системы В одной из секций, которая называется областью данных, находятся данные файлов, являющиеся их содержимым. В другой секции, которая называется таблицей inode, содержатся свойства (атрибуты) файлов. И в третьей секции, которая называется суперблоком, находится информация о самой файловой системе. Трехэлементная структура, которая распространяется на пронумерованную структуру блоков, и называется файловой системой. Суперблок. Первый блок в составе файловой системы называется суперблоком. В этом блоке содержится информация об организации самой файловой системы. Например, в суперблок записываются размеры каждой из областей файловой системы. В суперблоке также находится информация о неиспользуемых блоках данных. Конкретное содержание и структура суперблока зависит от версии Unix. Обратитесь к электронному справочнику и к заголовочным файлам, чтобы узнать, что содержится в вашем суперблоке. Таблица inode. Следующая часть файловой системы называется таблица inode. Каждый файл имеет набор свойств, таких, как размер, пользовательский идентификатор собственника, время последней модификации. Эти свойства записываются в структуру, которая называется inode. Все эти структуры имеют один и тот же размер, а таблица inode представляет собой просто массив из таких структур. Каждый файл, который "находится в файловой системе, имеет один inode в этой таблице. Если вы обладаете правами суперпользователя, то можете открыть раздел как файл, почитать и распечатать содержимое таблицы inode. Это аналогично варианту обращения к файлу utmp для чтения и вывода его содержимого. Важно следующее: Каждый inode идентифицируется в системе своей позицией в таблице inode. Например, inode 2 будет третьей структурой в таблице inode файловой системы. Область данных. Третьей частью файловой системы является область данных. Здесь хранится содержимое файла. Все блоки на диске имеют один и тот же размер. Если.в файле содержатся данные, для хранения которых необходимо более одного блока, то содержимое такого файла будет располагаться в нескольких блоках. Количество блоков определяется размером файла. Большие файлы могут состоять из тысяч отдельных дисковых блоков. Каким образом система учитывает наличие цепочек из таких отдельных блоков? 4.3.4. Файловая система с практических позиций: Создание файла Идея поддержки одной области для хранения содержимого файла и другой области для хранения файловых характеристик выглядит достаточно простой, но как это будет работать на практике? Что происходит, когда вы создаете новый файл? Рассмотрим простую команду: $ who > userlist Когда команда будет выполнена, то, как результат, в файловой системе появится новый файл, в котором будет находиться вывод команды who. Как это все происходит? У файла есть содержимое, у файла также есть свойства. Ядро поместит содержимое файла в область дан-
140 Изучение файловых систем. Разработка версии pwd ных, свойства файла помещаются в структуру inode, а имя файла помещается в каталог. На рисунке 4.5 показан пример создания файла, для которого необходимо три блока дисковой памяти. Номер inode Диск 200 627 992 Хранение содержимого в блоках данных. \ / Номера блоков Хранение информации о файле в inode Хранение последовательности блоков данных Добавить запись к каталогу \Ь21 200J992 гт 123 т 833 Список блоков, используемых файлом 4004 47 Каталог hello.с userlist Рисунок 4.5 Внутренняя структура файла При создании нового файла выполняются следующие четыре основных действия: Сохранение свойств Файл имеет свойства. Ядро находит свободную структуру inode. В нашем примере ядро нашло inode под номером 47. Далее ядро записывает информацию о файле в этот inode. Сохранение данных Файл имеет содержимое. Для нашего нового файла требуется три дисковых блока памяти. Ядро отыскивает в списке свободных блоков три блока. В данном случае были найдены блоки 627, 200 и 992. Далее первая часть данных копируется из буфера ядра в блок 627. Следующая часть копируется в блок 200. И наконец, последняя часть данных копируется в блок 992. Сохранение информации о распределении блоков Содержимое файла было распределено по блокам 627, 200 и 992, именно в указанном порядке. Ядро записывает эту последовательность номеров блоков в структуру inode, в секцию для хранения информации о распределении. Эта секция представляет собой массив из номеров блоков. Найденные три номера дисковых блоков будут помещены в первые три элемента массива. Добавление имени файла в каталог Наш новый файл называется userlist. Как в Unix ведется учет того, что новый файл появился в текущем каталоге? Решение простое. Ядро добавляет к каталогу запись вида: D7, userlist). Эта запись, устанавливающая взаимосвязь между именем файла и номером inode, представляет собой связь между содержимым файла с указанным именем и свойствами этого файла. Это обстоятельство заслуживает отдельного рассмотрения.
4.3. Внутренняя структура файловой системы UNIX , 141 4.3.5. Файловая система с практических позиций: Как работают каталоги Каталог - это специальный тип файла, который предназначен для хранения списка имен файлов. Внутренняя структура каталога может быть разной в разных версиях Unix, но абстрактная модель остается всегда одной и той же - это таблица номеров inode и имен файлов: Номер inode # 2342 ,.' 43989 3421 533870 Имя файла hello.c myls.c Представление внутреннего содержимого каталога Вы можете посмотреть содержимое каталога с помощью команды Is -lia (первая опция - это цифра 1): $ Is-liademodir 177865. 529193.. 588277 а 200520 с 204491 у $ В этом выводе представлен список из имен файлов и соответствующих им номеров inode. Например, файлу с именем у соответствует номер inode 204491. Текущий каталог, который обозначается символом ".", имеет номер inode 177865. Это означает, что информация о размере, собственнике, группе и т, д., расположена в таблице inode, в структуре с номером 177865. Опции -i и -1 (единица, а не 1) для команды Is могут быть для вас новыми. При использовании опции -i команда Is будет включать в вывод номер inode, при указании опции -1 команда будет производить одноколонный вывод. Постройте на вашей машине собственную версию поддерева demodir, а потом посмотрите номера inode в поддереве. Множественность ссылок на один и тот же файл Вы можете использовать команду ls-i, чтобы получить номера inode для файлов в системе. Например, вы можете посмотреть номер inode для всех элементов в корневом каталоге вашей системы: ls-ia/ 2 2 .. 3 auto 26625 bin 403457 boot 225281 dev 28673 etc 311297 home 8832 home2 24646 initrd 24579 install 161797 lib 11 lost+found 4097 mnt 108545 opt 1 proc 24681 root 233473 sbin 43829 shlib 2 40961 tmp 18433 usr 10241 var 183 xfer.log 183 transfers
142 Изучение файловых систем. Разработка версии pwd В этом листинге следует обратить внимание на две вещи. Bq-первых, в конце самой правой колонки представлены два файла с именами xfer.log и transfers. У этих файлов один и тот же номер inode, равный 183. Следовательно, оба из этих имен файла ссылаются на один и тот же inode. Структура inode представляет отдельный файл. Эта структура содержит свойства файла и список блоков данных для файла. Поэтому xfer.log и transfers - это два имени одного и того же файла. Короче говоря, это напоминает ситуацию, когда в телефонной книге один и тот же телефонный номер представлен в двух различных разделах. Но в обоих случаях - это ссылки на один телефонный номер1. Другая важная вещь, которую следует подчеркнуть в листинге содержимого корневого каталога - это изображение точки и двух точек в начале колонки слева. В этих записях указан один и тот же номер inode, равный 2, что говорит о том, что имена "." и ".." ссылаются на один и тот же каталог. Как может текущий каталог одновременно быть и родительским каталогом?2 Когда с помощью команды mkfs создается файловая система, то команда mkfs в качестве родительского каталога для корневого каталога указывает сам корневой каталог. 4.3.6. Файловаясистемаспрактических позиций: Как работает команда cat Мы рассмотрели, что делается внутри системы, когда вы пишете в новый файл, как это было в примере с командой who > userlist. А что будет происходить, когда вы будете пытаться прочитать что-то из файла. Например, как будет работать такая команда: $ cat userlist Проследуем через указатели от каталога к данным. Поиск в каталоге указанного имени файла Имена файлов хранятся в каталогах. Ядро ищет в каталоге запись, в которой содержится тюле со значением userlist. $ cat userlist От имени файла к содержимому файла: Поиск в каталоге имени файла, определение соответствующего номера inode, использование номера inode для локализации inode, inode содержит список блоков данных. т 47 ЬеШо.с userlist Рисунок 4.6 От имени файла к дисковым блокам Локализация и чтение inode 47 Ядро находит inode 47 в области, где расположена таблица inode в файловой системе. Для обнаружения inode требуется провести простой расчет. Все структуры inode имеют один и тот же размер, и в каждом дисковом блоке содержится фиксированное число таких структур. Структура inode может уже быть в буфере в ядре. В inode находится список блоков данных. 1. Пример с телефонным справочником не совсем точен, поскольку два различных человека могут проживать в одном и том же доме. См. концепцию порта в главе, посвященной вопросам сетевого программирования. 2. Послушайте I'm My Own Grandpa, 1947, by Latham & Jaffe для продолжения дискуссии по данной теме.
4.3. Внутренняя структура файловой системы UNIX 143 Обращение к блокам данных в последовательном порядке, один за другим Ядро теперь знает, в каких блоках данных находятся данные, в каком порядке было распределение данных по этим блокам. По мере того как команда cat многократно вызывает read, ядро обращается пошагово в заданном порядке к блокам данных, копируя данные с диска в буферы ядра и оттуда в массив пользовательского пространства. Все команде, которые читают из файла, такие, как cat, cp, more, who и тысячи других команд, передают имя файла системному вызову open для получения доступа к содержимому файла. При каждом вызове open отыскивает в каталоге имя файла, затем использует номер inode в каталоге, чтобы получить доступ к атрибутам файла и локализовать место нахождения содержимого файла. Рассмотрим теперь ситуацию, когда вы пытаетесь выполнить open в отношении файла, к которому у вас нет прав на запись и чтение. Ядро использует имя файла, чтобы по нему найти номер inode, затем использует этот номер inode для локализации структуры inode. В этой структуре ядро находит разряды, определяющие права доступа к файлу, и пользовательский ID собственника файла. Если ваш UID и UID для файла совпадают, а необходимые разряды доступа не установлены, то системный вызов заканчивается с кодом возврата -1 и в переменной errno устанавливается значение EPERM На основе рассмотренного представления схемы из каталогов, таблицы inode, блоков данных вы теперь должны прочувствовать смысл работы других файловых операций. Обратившись к исходному коду на какой-либо версии Unix, вы можете посмотреть, как работает системный вызов close. 4.3. 7. I nodes и большие файлы Каким образом файловая система Unix учитывает распределение по блокам для больших файлов? Те пояснения, которые были представлены в предшествующем разделе, недостаточны. Кратко проблему можно представить так: Фактор 1 Для большого файла необходимо много дисковых блоков Фактор 2 В структуре inode хранится список распределения дисковых блоков Проблема Как можно сохранять в Inode, который имеет фиксированный размер, длинный список распределения блоков? Решение Сохранять большую часть списка распределения в области блоков данных, а в inode установить указатели на эти блоки Рассмотрим ситуацию, изображенную на рисунке 4.7. Для файла необходимо выделить четырнадцать блоков для хранения его содержимого. Поэтому в списке распределения будет расположено четырнадцать номеров блоков. Печально, но факт: inode файла имеет область для хранения массива распределения блоков, в которую можно записать только тринадцать элементов массива. Звучит зловещая музыка! Как поместить список из 14 элементов в область, рассчитанную на хранение только 13 элементов? Да легко. Поместим первые 10 номеров из списка распределения в область распределения в inode. Далее поместим оставшиеся 4 номера дисковых блоков из списка распределения в какой-либо блок данных. Это — своего рода проведение инвентаризации на полке и перемещение всего лишнего с полки на склад. А теперь поговорим о деталях. В inode находится массив, в который можно записать 13 номеров блоков. Первые 10 элементов массива подобны "пространству на полке". Здесь хранятся в 10 элементах номера тех блоков, которые действительно содержат данные файла. Если реальный список распределения номеров содержит более 10 элементов, то заводится дополнительный блок для хранения номеров блоков. Но располагается дополнительная область не
144 Изучение файловых систем. Разработка версии pwd дополнительные номера, записывают в 11 элемент массива в inode. Это аналогично ситуации, когда в книжном магазине кладут на полку такую пометку: "Дополнительные книги на складе, полка 3". Запись о распределении в файле четырнадцати блоков данных: Список распределения Список распределения Элемент 12 содержит начинается в первых продолжается в блоке номер блока, где 10 элементах массива косвенной адресации. содержатся номера блоков в inode B11 элементе массива косвенной адресации. находится номер этого Этот блок называют блоком блока двойной косвенной адресации Рисунок 4.7 Список распределения блоков содержится в области данных Отметим то обстоятельство, что для рассматриваемого файла требуется 15 блоков данных. В четырнадцати блоках будет храниться содержимое файла, а еще в одном блоке будет находиться та часть списка распределения, которая не поместилась в inode. Этот дополнительный блок называют косвенным блоком. Что происходит, когда будет заполнен косвенный блок? По мере того как при работе будут добавляться новые данные, ядро будет присоединять к файлу дополнительные блоки данных. Поэтому список распределения становится все длиннее и длиннее. И для него может потребоваться дополнительная память. Рано или поздно список распределения заполнит весь косвенный блок. Поэтому ядро начинает работать со вторым дополнительным блоком. Что делает ядро с номером второго дополнительного блока? Должно ли ядро поместить номер второго дополнительного блока в 12-й элемент массива в inode? Конечно, это возможно, но тогда это будет означать, что файл будет иметь три дополнительных блока. Вместо того чтобы поместить номер второго блока в массив в inode, ядро обращается еще к одному блоку данных, в котором будет храниться список таких дополнительных косвенных блоков. А в элемент 12 в массиве в inode будет помещен номер блока,, но не второго дополнительного блока, а номер блока, в котором будут храниться номера второго, третьего, четвертого и т. д. дополнительных блоков. Такой блок называют двойной косвенный блок.
4.4. Понимание каталогов 145 Что происходит, когда будет заполнен двойной косвенный блок? Когда будет заполнен двойной косвенный блок, ядро начинает работать с новым двойным косвенным блоком. Ядро не помещает номер этого нового косвенного блока в область inode. Вместо этого ядро создает тройной косвенный блок, где будут размещаться номера нового двойного косвенного блока и всех последующих двойных косвенных блоков, которые понадобятся файлу. Номер этого тройного косвенного блока запоминается в последнем элементе в массиве в области inode. Что происходите, когда будет заполнен тройной косвенный блок? В данной ситуации предполагается, что файл достиг по размеру своего предела. Если вам требуется использовать файлы большого размера, то можно установить файловую систему с большими размерами дисковых блоков. Когда вы создаете такую файловую систему, то вы можете определять не только размер таблицы inode и области данных, но вы можете задавать и размер дискового блока. Размер дискового блока не обязательно должен совпадать с размером сектора на поверхности диска. Часто в одном дисковом блоке содержится несколько дисковых секторов. Большие файлы требуют больших системных затрат. Система распределения дисковой памяти является быстрой и эффективной для маленьких файлов. По мере роста размера файла ядро использует все больше и больше дискового пространства для хранения списка распределения. При поиске конкретного элемента в файле может потребоваться обращение к нескольким косвенным блокам, чтобы получить номер нужного блока данных. 4.3.8. Варианты файловых систем в Unix В предшествующем разделе было дано описание структуры файловой системы Unix. В различных версиях Unix используются различные версии этой модели. Из-за простоты этого классического метода возникает ряд важных слабых мест. Например, уязвимым местом в системе является суперблок. Если блок будет разрушен каким-то образом, то будет потеряна информация обо всей файловой системе. В некоторых версиях Unix сохраняют копии суперблока в самой файловой системе. Другой проблемой является фрагментация» По мере создания и удаления файлов свободные блоки распределяются в произвольном порядке по диску. Одно из решений - создавать небольшие файловые системы, которые будут называться группы цилиндров. Классическая модель не устарела. Файлы создаются и удаляются в области данных поблочно. Атрибуты файлов также хранятся в inode в таблице inode, а в составе inode содержится массив распределения блоков диска для файла. Каталоги содержат списки из имен файлов и номеров inode. Мы можем теперь возвратиться к небольшому поддереву, которое мы построили и изучили в начале главы. Обогащенные знанием внутренней структуры файловой системы, мы получим и рассмотрим "рентгеновский снимок" каталогов и файлов. 4.4. Понимание каталогов Теперь, когда мы знаем внутреннюю структуру файловой системы Unix, мы можем рассмотреть, что реально происходит с нашим поддеревом demodir. И при этом мы сможем разобраться, как работают различные команды для обработки каталогов. 4.4.1. Понимание структуры каталога Пользователи воспринимают файловую систему как набор каталогов и подкаталогов. Каждый каталог содержит файлы, в каждом каталоге могут находиться подкаталоги. Каж- пктй полк-ятя ттг имррт пилите пкпк-ий кятяппг Тякпе пепеко w\ кятяппгпр и (Ьяйлгт чягугп
146 Изучение файловых систем. Разработка версии pwd изображают как набор прямоугольников (боксов), соединенных линиями связи. В каком смысле следует понимать выражение, что файл находится в каталоге! Что означает в техническом смысле термин "dl является подкаталогом с"? Что обозначают соединительные линии на таких рисунках? В содержательном смысле каталог - это файл, который содержит список. Список состоит из таких пар: имя файла и номер inode. Более того, пользователи видят список из имен файлов, в то время как Unix видит список поименованных указателей. Пользовательская точка зрения demodir | ш а // I и Г d1 \s // jxlink) С ^S «2 |хсору) Системная точка зрения У а с х dl 62 xlink хсору Рисунок 4.8 Две точки зрения относительно дерева каталогов Как преобразовать одну диаграмму в другую? Используя номера inode, мы можем точно представить структуру дерева. Используем команду Is -iaR, чтобы получить рекурсивно список номеров inode для всех файлов. 520 с 491 у $ Is -iaR demodir 865. 193.. demodir/a: 277. 865.. demodir/c: 520. 865.. demodir/c/dl: 651. 520.. demodir/c/d2: 247. 520.. $ 277 a 402x 651 d1 402 xlink 680 xcof 247 d2 На рисунке 4.9 представлена диаграмма с указанием номеров inode в каталогах.
4,4. Понимание каталогов 147 Пользовательская точка зрения demodir га /Z ^ S d1_^-^d2 |xlink| Системная точка зрения 865 153 4$1* Й7?\ 520 ] У а LC 277 865 140,2 X 520 651 247 di d2 1 247 520 L 680lxcopy mum и him И И111ИI Рисунок 4.9 Имена файлов и указатели на файл Реальное значение фразы "Файл находится в каталоге" Пользователи в разговоре говорят, что файлы находятся в каталогах, но мы теперь знаем, что файлы представляются записями в таблице inode, а содержание файлов хранится в области данных. В каком смысле следует понимать, что файл находится в каталоге? Например, с пользовательской точки зрения, файл у содержится в каталоге demodir. С системной точки зрения мы видим, что каталог содержит запись с именем файла у и номером inode, равным 491. . Пользовательская точка зрения demodir 1 и 1 а // Ч\ | ш | | d1 // |xlink( С (\Ч * |хсору| Системная точка зрения Рисунок 4.10 Имена каталогов и указатели на каталоги 865 193 491 ,277 Г 52 0^ у а 1 _с 1 1/277 Г8б5' 402 X Г5Щ 651 .247 кл X di d2 S 402 xlink X T47 520 Sv .?80 xcopy
148 Изучение файловых систем. Разработка версии pwd Аналогично, фраза "файл х находится в каталоге а" означает, что существует ссылка на inode 402 в каталоге с именем а, и х - это имя файла, которое соответствует этой ссылке. Также заметим, и это важно, что каталог с именем dl внизу, слева на диаграмме, имеет ссылку на inode 402 и что эта ссылка имеет имя xlink. Таким образом, имеются две ссылки на узел 402. Одна из них называется demodir/a/x, а другая - demodir/c/dl/xlink. Обе ссылки обращены к одному и тому же файлу. Короче говоря, каталоги содержат ссылки на файл. Каждая из таких ссылок называется link (связь). Содержимое файла находится в блоках данных, атрибуты файла записаны в структуре в таблице inode, а номер inode и имя файла хранятся в каталоге. Этот же принцип можно использовать для раскрытия смысла выражения "каталог содержит подкаталог". Реальное значение фразы "Каталог содержит подкаталоги" С точки зрения пользователя, каталог с именем а является подкаталогом каталога demodir. А как это выглядит изнутри? Опять же, это означает, что каталог demodir имеет ссылку на inode подкаталога. В верхней части диаграммы, рассматриваемой с системных позиций, есть ссылка с именем а, для которой inode имеет номер 277. Как мы узнаем, что 277 - это номер inode для каталога, изображенного слева на диаграмме? Каждый каталог имеет inode. Ядро в каждом каталоге устанавливает запись, которая относится к собственному inode каталога. Эта запись имеет имя ".". В маленьком прямоугольнике слева точка ссылается на inode 277, поскольку каталог в левой части диаграммы имеет inode 277. Посмотрите на диаграмму и убедитесь в том, что каталог, для которого заведен inode 520, содержится в каталоге demodir. В списке имен он представлен под именем с. Аналогично, другой каталог, у которого номер inode равен 247, будет подкаталогом каталога с inode 520, и который имеет имя d2. Реальный смысл фразы "У каталога есть родительский каталог" Посмотрите с пользовательских позиций на диаграмму и найдите каталог d2. У него есть родительский каталог с именем с. Чтобы все это отобразить, опять используется простая ссылка на inode. Для каталога номер inode равен 520. А в каталоге d2 есть запись, в котором используется имя ".Л В этой записи указан номер inode 520. Двумя точками принято обозначать родительский каталог. Таким образом, inode 520 является родительским для inode. Заполнение пустых полей для записей, которые имеют в качестве имен точки Если вам стало все понятно при рассмотрении предыдущего раздела, то вы будете в состоянии заполнить пропущенные значения номеров inode на рисунке 4.10. Если вы не уверены, что нужно поместить в эти пустые поля, обратитесь к выводу команды Is, который был приведен ранее, и просмотрите еще раз текст предшествующего раздела. Множественные ссылки, счетчик ссылок В дереве demodir для inode 402 установлены две ссылки. Одна имеет имя х и находится в каталоге а, вторая называется xlink и находится в каталоге dl. Можно ли определить, какое из этих имен - имя оригинального файла, а какое имя - это имя ссылки? В структуре каталога в Unix эти две ссылки имеют одинаковый статус. Их называют твердыми ссылками на файл. Файл - это inode и "связка" блоков данных. Ссылка указывает на inode. Вы можете создать много ссылок на файл, если вам это необходимо.
4.4. Понимание каталогов U9 Ядро записывает значения числа ссылок к файлу. В случае для inode 402 это значение будет не меньше 2. К inode могут быть установлены и другие ссылки из других частей файловой системы. Счетчик ссылок хранится в inode. Счетчик ссылок - один из элементов структуры struct stat, которая возвращается в результате работы системного вызова stat. Имена файлов В файловой системе Unix файлы не имеют имен. Имена присваиваются ссылкам. А файлам соответствуют номера inodes. Полезность этого факта мы обсудим позже. 4.4.2. Команды и системные вызовы для работы с деревьями каталогов Внутренняя структура файловой системы Unix проста. Это большая структура из совместно связанных данных. Узлы в данной структуре называют inodes (индексные узлы), наборы указателей называются каталогами, и оконечные узлы называют файлами. Мы имеем возможность управлять таким деревом с помощью стандартных команд Unix, таких, как mkdir, rmdir, mv, In и rm. Как работают эти команды? В частности, какие системные вызовы используются при работе этих команд? mkdir Команда mkdir служит для создания новых каталогов. При обращении к команде можно задавать одно или более имен каталогов. В команде mkdir используется системный вызов mkdir: НАЗНАЧЕНИЕ INCLUDE ИСПОЛЬЗОВАНИЕ АРГУМЕНТЫ КОДЫ ВОЗВРАТА mkdir Создание каталога #inciude < sys/stat.h > «include <sys/types.h> int result = mkdir(char "pathname, modej mode) pathname - имя нового каталога mode - маска для разрядов прав доступа -1 - при ошибке 0- при успехе Системный вызов mkdir позволяет создавать и устанавливать ссылку на новый узел каталога в дереве файловой системы. То есть mkdir создает inode для каталога, выделяет дисковый блок для хранения его содержимого, записывает в каталоге две записи с именами, и.., с необходимыми для них номерами inode. Затем добавляется ссылка на новый узел из родительского каталога. rmdir Команда rmdir позволяет удалять каталог. При обращении к команде можно задавать одно или более имен каталогов. В команде rmdir используется системный вызов rmdir: НАЗНАЧЕНИЕ INCLUDE ИСПОЛЬЗОВАНИЕ АРГУМЕНТЫ rmdir ) Удаление каталога. Каталог должен быть пустым. #inciude < unistd.h > int result = rmdir(const char *path); path - имя каталога КОДЫ ВОЗВРАТА -1 - при ошибке 0 - при успехе
150 Изучение файловых систем. Разработка версии pwd Системный вызов rmdir удаляет узел каталога из дерева каталогов. Каталог должен быть пустым, т. е. в нем не должно содержаться записей о файлах и подкаталогах, кроме записей с именем точкам, точкаточка. Удаляется ссылка из родительского каталога. Если при этом обнаруживается, что удаляемый каталог не используется другими процессами, то освобождаются также его inode и блоки данных. rm Команда rm позволяет удалять записииз каталога. При обращении к команде можно задавать одно или более имен каталогов. В команде rm используется системный вызов unlink: НАЗНАЧЕНИЕ INCLUDE ИСПОЛЬЗОВАНИЕ АРГУМЕНТЫ КОДЫ ВОЗВРАТА unlink Удаление записи в каталоге #include < unistd.h > int result = unlink(const char *path); path - имя записи в каталог для удаления -1 -при ошибке 0 - при успехе Системный вызов unlink удаляет запись в каталоге. В соответствующем inode уменьшается на 1 счетчик ссылок. Если счетчик ссылок становится равным нулю, то освобождаются блоки данных и inode. Если после декремента счетчика остаются еще ссылки на inode, то блоки данных и inode не отсоединяются. Системный вызов unlink нельзя использовать для аналогичных действий в отношении каталогов. In Команда In позволяет создавать ссылку на файл. Команда In использует при работе системный вызов link: НАЗНАЧЕНИЕ INCLUDE ИСПОЛЬЗОВАНИЕ АРГУМЕНТЫ КОДЫ ВОЗВРАТА link Установление новой ссылки на файл #include < unistd.h > int result = linkfconst char *orig, const char *new); orig - имя оригинальной ссылки new - имя новой ссылки -1 -при ошибке 0-при успехе С помощью системного вызова link устанавливается новая ссылка на inode. В новую ссылку записывается новое имя ссылки и номер inode оригинальной ссылки. Если при обращении к вызову было указано уже существующее имя, то фиксируется ошибка выполнения системного вызова link. Использовать link для создания новых ссылок на каталоги нельзя. mv Команда mv позволяет изменять имя или расположение файла или каталога в дереве каталогов. Команда mv является более гибкой командой, нежели другие команды, которые были представлены в этом разделе. Ряд ее внутренних деталей мы рассмотрим позже. Во многих случаях при работе этой команды используется системный вызов rename:
4.4. Понимание каталогов 151 НАЗНАЧЕНИЕ INCLUDE ИСПОЛЬЗОВАНИЕ АРГУМЕНТЫ КОДЫ ВОЗВРАТА rename Переименовать или переместить ссылку #include < unistd.h > int result = renamefconst char *from, const char *to); from - имя оригинальной ссылки to - имя новой ссылки -1 -при ошибке 0 - при успехе Системный вызов rename изменяет имя или место расположения файла или каталога. Например, при вызове rename(MyM,"y.oldn) будет изменено имя файла, а при вызове renarne("yV'c/(J2/y.old") будет изменено имя и место расположения файла. Системный вызов rename можно использовать как в отношении файлов, так и в отношении каталогов. Но существует ряд ограничений при работе с перемещением каталогов. Например, вы не сможете переместить каталог в один из его подкаталогов. Попробуйте предсказать результат работы вызова rename("demodir/c","demodir/d2/c") и посмотреть, какой опустошительный будет результат. В отличие от link системный вызов rename удаляет существующий файл или пустой каталог с именем to. Как работает rename, зачем существует rename? Как rename перемещает файл в другой каталог? Файлы реально в каталогах не находятся. В каталогах находятся ссылки на файлы. Поэтому rename перемещает ссылку из одного каталога в другой. Схема действий при переименовании у на c/d2/y.old представлена на рисунке 4.11. Перед переименованием 865 Ш 1 49iv 1 277\ 1 520 У а \с ТГГ 865 402 X 5ЯГ 651 J 247l dl LU2 1 402 xlink: Т7" 520 680 хсору L После переименования ("yYc/cM/y.olcr; ~g-5F 193 \211 1 520 a с ~ТГГ 8б5 402 X 520 651 247 di ; _d2 | 402 xlink TT7" 520 680 ,491 XCODV y.old NH№ inode491 Рисунок 4.11 Перемещение файла в новый каталог
152 Изучение файловых систем. Разработка версии pwd Перед переименованием ссылка на inode 491 с именем у находилась в каталоге demodir. После переименования ссылка на 491, которая стала называться y.old, будет находиться в каталоге c/d2, а оригинальная ссылка пропадает. Как ядро перемещает ссылку? В ядре Linux базовый алгоритм системного вызова rename такой: скопировать оригинальную ссылку в соответствии с новым именем и/или местом расположения удалить оригинальную ссылку В Unix есть два системных вызова link и unlink,c помощью которых можно выполнить эти два действия. Поэтому вызов rename("xM,"z") будет работать так: if (!ink("x"/z") unlink("x"); В Olden Days ®не было системного вызова rename. Поэтому команда mv использовала вызовы link и unlink. С добавлением в ядро вызова rename были решены две проблемы. Во-первых, вызов rename делает возможным благополучно переименовывать или перемещать каталоги. Раньше, в более старых системах, обычным пользователям не разрешалось выполнять вызовы link или unlink в отношении каталогов. Поэтому они могли быть использованы для переименования каталогов. Еще одно важное преимущество системного вызова rename - поддержка файловых систем не для Unix. При работе в Unix переименование файла или каталога сводится к изменению ссылки, но в других системах эта схема может не работать. Добавляя к ядру общий метод, который был назван rename, добиваются скрытия деталей реализации, что обеспечивает возможность работы с одним и тем же кодом на все типах файловых систем. cd Команда cd изменяет текущий каталог для процесса. Команда cd влияет на процесс, а не на каталог. Пользователь может сказать "Я перешел в каталог /tmp и нашел там много моих рабочих файлов". Это по сути то же, что сказать "Я отправился на чердак и обнаружил там много моих старых книг". При работе команды cd используется системный вызов chdir: НАЗНАЧЕНИЕ INCLUDE ИСПОЛЬЗОВАНИЕ АРГУМЕНТЫ КОДЫ ВОЗВРАТА chdir Изменить текущий каталог у вызывающего процесса #include < unistd.h > int result = chdirfconst char *path); path - путь к новому каталогу -1 -при ошибке 0- при успехе Для каждой исполняемой программы в Unix устанавливается ее текущий каталог. Системный вызов chdir позволяет изменять текущий каталог процесса. После этого говорят, что '"процесс находится в этом каталоге". В процессе поддерживается переменная, в которой хранится номер inode текущего каталога. Когда вы выполняете "переход в новый каталог", то вы заставляете изменить значение этой переменной. Важное упражнение. Как работает команда cd..? Обратитесь к примеру поддерева demodir и представьте, что вы находитесь в каталоге с именем с. Какой номер inode у вашего текущего каталога? Далее выполните команду cd dl. Каким стал теперь номер inode вашего текущего каталога? Каким образом ядро получает значение этого номера? Если вы
4.5. Разработка программыpwd 153 теперь выполните команду cd../.., то какой будет номер inode вашего текущего каталога? Какие шаги выполняет ядро, чтобы получить значение этого номера? Если вы разобрались, какие действия происходят при выполнении этого важного упражнения, то вы поняли, как работает команда pwd. 4.5. Разработка программы pwd Команда pwd выводит путь для текущего каталога. Например, если вы спустились по дереву вниз в каталог demodir/c/d2 и далее выполнили команду pwd, то вы увидите нечто похожее на следующее: $pwd /home/yourname/experiments/clemoclir/c/d2 Где хранится такой длинный путь? Он не хранится в текущем каталоге. Текущий каталог обращается сам к себе с помощью ссылки ."." и имеет номер inode. Каталог - это просто узел среди набора узлов, соединенных между собой. Каким образом команда pwd узнает, что каталог называется d2, как она узнает, что у текущего каталога родительским будет каталог с, как узнает, что у каталога с родительским каталогом будет demodir и т. д.? 4.5.1. Как работает команда pwd? Ответ на этот вопрос, как и ответы на все вопросы этой главы, будет простым. Команда, следуя по указателям, читает содержимое каталогов. На самом деле команда pwd поднимается вверх по дереву, от каталога к каталогу, отмечая для каждого шага перемещения номер inode для имени "точка". Затем через родительский каталог выбирается имя, которое установлено для этого номера inode. Это делается до тех пор, пока не будет достигнута вершина дерева. Рассмотрим, например, рисунок 4.12: Computing pwd: 1. Для"." номер равен 247 2. Для номера 247 имя ссылки "d2" 3. Дляп." номер равен 520 4. Для номера 520 имя ссылки "с. 5. Для"." номер равен 865 6. Для номера 865 имя ссылки "demodir" 7. Для"." номер равен 193 Рисунок 4.12 Составление пути текущего каталога Наше восхождение по дереву начинается в текущем каталоге. Он обозначен на рисунке индексом 1 в нижнем правом углу. В этом каталоге для ячейки с именем "." номер inode равен 247. Теперь с помощью chdir переместимся в родительский каталог, где найдем i 865 193 I 491 277 1 520 у ! а с ! 520 651 | 247 dl 62 277 865 402 х 402 xlink 247 520 680 хсору
154 Изучение файловых систем. Разработка версии pwd запись, содержащую номер inode 247. Для этого номера в этой записи указано имя d2. Поэтому имя последнего компонента в пути будет d2. А какое имя у родительского каталога? В родительском каталоге обращаемся к записи, содержащей имя ".", и выбираем его номер inode 520. Далее с помощью chdir переходим еще на шаг вверх по дереву. И уже в этом родительском каталоге находим запись с номером inode 520. Из нее узнаем имя каталога - с. Поэтому последние два элемента пути будут теперь такими: c/d2. Алгоритм для повторения этих трех шагов можно выразить так: 1. Выбрать номер inode для имени"'.", назовем его п (использовать stat). 2. chdir... (использовать chdir). 3. Найти имя для ссылки с номером inode п (.использовать opendir, readdir, closedir). Повторить (до тех пор, пока не достигнете вершины дерева). Все выглядит достаточно просто. Но остались два вопроса. Вопрос 1: Как мы узнаем, что достигли вершины дерева? В корневом каталоге файловой системы Unix в записях с именами ссылок "." и ".." указан один и тот же inode. Программисты часто отмечают конец связанных структур указателем NULL. Разработчики Unix могли бы использовать нулевой указатель в записи корневого каталога, содержащего имя "..". Но вместо этого было принято решение "зациклиться на себя". В чем преимущество такого решения? В нашей версии pwd повторение при составлении пути будет происходить до тех пор, пока не будет достигнут каталог, в котором в записях с именами ссылок "." и ".." записан один и тот же inode. Вопрос 2: Как нам вывести имена каталогов в пути в правильном порядке? Мы напишем цикл и построим строку, состоящую из имен каталогов, с помощью strcat или sprintf. Мы не будем использовать при составлении строки с рекурсивной программой, которая разворачивается к вершине дерева и выводит по одному имена каталогов, по мере продвижения по дереву. 4.5.2. Версиякомандыpwd /* spwd.c: упрощенная версия pwd * начало работы в текущем каталоге и * последующее восхождение по дереву файловой системы к ее корню. * При этом сначала выводится головной элемент, а затем текущая часть пути * * Используется readdir() для получения информации об элементах каталога * . . * Нештатная ситуация: Вывести пустую строку, если достигли"/" **/ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> inoj getJnode(char *); void printpathto(inoj); void inum_tojiame(inoJ, char *, int); intmainQ
I Разработка программы pwd printpathto(get_inode(".")); putGhar('\n'); return 0; /* здесь вывод пути 7 /* затем добавить новую строку 7 } void printpathto(ino_t thisjnode) Г * вывод пути, ведущего к объекту с этим inode, * как будто рекурсивно 7 { inoj myjnode; char its_name[BUFSIZ]; if (getJnode("..M) != thisjnode) chdirf'.."); /* перемещение вверх на каталог */ inumJo_name(thisJnode,its_namefBUFSIZ);/* получить имя каталога*/ myjnode = getJnode(M."); printpathto(myjnode); printfG%s", its_name); /* name of this 7 } } void inum to name(inoJ inodejo find, char *namebuf, int buflen) /* * Найти в текущем каталоге файл с этим номером inode ж и скопировать его имя в namebuf 7 /* печать головной чаете Г рекурсия 7 /* теперь печать 7 { DIR *dir_ptr; struct dirent *direntp; dirj)tr = opendir("."); if(dirj>tr==NULL){ perror(".M); exitA); /* каталог 7 Г каждая запись 7 } Л * поиск каталога для файла с заданным inum 7 while ((direntp = readdir(dirjrtr)) != NULL) if (direntp->d ino == inode to find) { strncpy(namebuf, direntp->d_name, buflen); namebuf[buflen-1] = '\0'; closedir(dir_ptr); return; /* на всякий случай 7
156 Изучение файловых систем. Разработка версии pwd fprintf(stderr, "error looking for inum %d\n", inodejojind); exitA); } inaj get inode(char *fname) r~ * возвратить номер inode файла 7 { struct stat info; if (stat(fname, &info) == -1){ fprintf(stderr, "Cannot stat"); perror(fname); exitA); } return info.st ino; } Работает ли наша программа? Вот реальный вывод после запуска программ: $ /bin/pwd /home/bruce/experiments/demodir/c/d2 $spwd /bruce/experiments/demodir/c/d2 $ Все закончилось красиво. В реальной версии команды pwd в составе пути выводятся все каталоги до корня дерева. Но и наша версия останавливается, когда она достигает вершины дерева. Так в чем проблема? Есть ли ошибки? Нет. Программа на самом деле работает правильно. Она действительно останавливается, когда достигает корня файловой системы. Но корень этой файловой системы не является корнем всего дерева, которое поддерживается на компьютере. В Unix можно построить на одном диске дерево, состоящее из деревьев. Каждый диск, или каждый раздел на диске, содержит дерево каталогов. Эти отдельные деревья соединяются вместе и составляют одно единое дерево. Наша версия pwd столкнулась с одним из ограничений такого построения. 4.6. Множественность файловых систем: Дерево из деревьев Что происходит, когда в системе Unix используется два диска или раздела? Мы наблюдали, как происходит работа в системе. Мы смогли организовать один раздел, в котором разместилось дерево каталогов. А если у вас имеется два раздела, то можем ли мы получить два отдельных дерева? Как организуется работа с разделами в других системах? В некоторых операционных системах каждому диску или разделу назначается символ дисковода или имя тома. Этдт символ устройства или имя тома становится частью полного пути к файлу. Есть экстремальные схемы, когда в системах назначаются номера блоков по всем дискам, с тем чтобы создать один виртуальный диск. . В Unix есть еще одно, третье преимущество. В каждом разделе хранится дерево собственной файловой системы. Но поскольку на компьютере существуют более одной файловой
4.6. Множественность файловых систем: Дерево из деревьев 157 Пользовательская позиция: одно дерево Системная позиция: два диска ЛЬ da en cm Рисунок 4.13 "Прививка" деревьев Пользователь видит одно полное дерево каталогов. Действительно же существуют два дерева - одно на диске 1, а другое на диске 2. Каждое дерево имеет корневой каталог. Одна из файловых систем выступает в роли корневой файловой системы. Вершина этого дерева является одновременно вершиной единого дерева. Другая файловая система присоединяется к некоторому подкаталогу корневой файловой системы. С позиций системы ядро устанавливает ссылку на другую файловую систему из каталога в корневой файловой системе. 4.6ш1ш Точкимонтирования В Unix используется выражение монтировать файловую систему со смыслом, который аналогичен выражению пришпилить бабочку или приклеить картину на картону, е. необходимо что-то к чему-то прикрепить. Корневой каталог поддерева прикрепляется к каталогу в составе корневой файловой системы. Каталог, к которому присоединяется поддерево, называется точкой монтирования для этой второй системы. Команда mount выводит список файловых систем, которые в текущий момент примон- тированы в системе, и их точки монтирования: $ mount /dev/hda1 on / type ext2 (rw) /dev/hda6 on /home type ext2 (rw) none on /proc type proc (rw) none on /dev/pts type devpts (rw,mode=0620) В первой строке выводится информация о том, что раздел 1 на устройстве /dev/hda (первый IDE дисковод) смонтирован в корневой вершине дерева. Этот раздел является корневой файловой системой. Во второй строке сообщается о том, что файловая система на устройстве /dev/hda6 присоединена к корневой файловой системе к каталогу /home. Таким образом, когда пользователь переходит с помощью chdir из каталога / в каталог /home, to происходит переход от одной файловой системы в другую. Когда наша версия pwd будет проходить по дереву, то она остановится в каталоге /home, поскольку она достигнет вершины своей файловой системы. » ' .
158 Изучение файловых систем. Разработка версии pwd Плюрализм Unix В Unix допускается монтировать к корневой файловой системе файловые системы различных типов. Например, на Unix машине, где есть корневая файловая система, можно смонтировать а файловую систему ISO9660 на CD-ROM. Файлы и каталоги этого диска становятся после этого частью единого дерева. Если в ядре есть подпрограммы, которые могут работать со структурой файловой системы Macintosh, то можно примонтировать файловую систему Macintosh, расположенную на каком-то диске. Можно даже монтировать файловые системы, которые расположены на других компьютерах, используя сетевые средства. 4.6.2. Дублирование номеров /node и связей между устройствами Объединение нескольких файловых систем в составе одного дерева имеет ряд преимуществ. Однако есть один небольшой нюанс. При работе в Unix каждый файл в файловой системе имеет номер inode. Аналогично тому, как могут быть расположены на двух различных улицах дома с одним номером 402, так и на двух различных дисках могут быть файлы, каждый из которых имеет inode под номером 402. В каких-то каталогах могут быть записи, где для файлов с некоторыми именами указан номер inode 402. Как ядро может узнать, какой номер inode следует использовать? ,'fltl\ . Рисунок 4.14 Номера inode и файловые системы Рассмотрите внимательно на увеличенной части рисунка два каталога. Один из них находится в корневой файловой системе, а другой в примонтированной файловой системе. В каждом каталоге есть ссылка на inode 402. Имена ссылок myls.c и y.old соотнесены одному и тому же номеру inode. Но где расположен этот inode? В файловой системе на диске 1 есть inode с номером 402, а на диске 2 в файловой системе есть другой inode с номером 402. Это означает, что эти ссылки вовсе не ведут к одному и тому же файлу. На этом примере иллюстрируется проблема, которая возникает при создании дерева деревьев. Как оказалось, номер inode идентифицирует файл совсем не однозначно. Как мы только что убедились, один и тот же номер inode 402 находится в двух различных каталогах, но каждый из них ссылается на разные файлы. При этом все выглядит так, будто ссылки установлены на один и тот же файл. Но это не так. Как мне сослаться на один и тот же файл из различных файловых систем? Вы не сможете этого сделать. Файл существует как набор блоков данных и inode на диске. В каталоге на этот inode есть ссылка. Что должно произойти, если ссылка на одном диске будет указывать на inode, который находится на другом диске? Если другой диск был размонтиро-
4.6. Множественность файловых систем: Дерево из деревьев 159 ван, то файл будет потерян. Еще хуже случай, если будет примонтирован совсем другой диск, на котором окажется файл с номером inode 402. Но содержание этого другого файла будет совершенно другим, чем нам нужно. Существует еще ряд проблемных ситуаций, над которыми вы можете подумать. Знают ли системные вызовы link и rename о том, что было рассмотрено выше? Да. Системный вызов link отказывается создавать связи между устройствами, а системный вызов rename отказывается перемещать номер inode по файловым системам. Читайте документацию в справочнике для изучения того, какими могут быть коды ошибок. 4.6.3. Символические ссылки: Панацея или блюдо спагетти? Твердые ссылки по сути являются указателями, с помощью которых каталоги объединяются в дерево. Такие ссылки являются указателями, которые связывают имена файлов с собственно файлами. Твердые ссылки не могут указывать на inodes в других файловых системах. И даже root не может сделать твердую ссылку на каталог. Однако есть достаточно причин, чтобы иметь возможность ссылаться на каталоги или файлы в других файловых системах. Для удовлетворения таких требований в Unix поддерживается еще один тип ссылок - символические ссылки. Символическая ссылка производит обращение к файлу по имени, а не по номеру inode. Сделаем такое сравнение: $ who > whoson $ In whoson ulist $ Is-li whoson ulist 377 -rw-r--r-- 2 bruce users 235 Jul 16 09:42 ulist 377 - rw- r- - r- - 2 bruce users 235 Jul 16 09:42 whoson $ In-s whoson users $ Is -li whoson ulist users 377 - rw- r- - r- - 2 bruce users 235 Jul 16 09:42 ulist 289 Irwxrwxrwx 1 bruce users 6 Jul 16 09:43 users -> whoson 377 - rw- r- r- 2 bruce users 235 Jul 16 09:42 whoson Файлы whoson и ulist ссылаются на один и тот же файл. Для обоих файлов указаны одни и те же характеристики: они имеют номер inode 377, у каждого указан один и тот же размер файла, одно время модификации файла и одно и то же значение числа ссылок. Твердая ссылка ulist была создана с помощью команды In. С другой стороны, с помощью команды In -s создается символическая ссылка на файл whoson, которая будет называться users. С помощью команды Is -li обнаруживаем, что ссылка users имеет inode 289. Символ 1 в позиции для указания типа файла свидетельствует о том, что это символическая ссылка. Число ссылок, время модификации и размер имеют значения, отличные от значений этих же характеристик у оригинального файла. Файл users не является оригинальным файлом whoson, но ведет себя в точности так, как ведет себя оригинальный файл при обращении к нему на чтение или запись. Например: $wc-I whoson users 5 whoson 5 users 10 total $ diff whoson users
160 Изучение файловых систем. Разработка версии pwd С помощью команд wc и diff производится обращение к файлам и подсчет строк в этих файлах. Потом производится сравнение содержимого этих файлов. В рассмотренном случае ядро использует имя для обращения к оригинальному файлу. Но, с другой стороны, при выполнении вызова stat будет получена информация о ссылке, а не об оригинальном файле. Символические ссылки могут действовать в составе различных файловых систем, потому что они не хранят inode оригинального файла. Такие ссылки можно также устанавливать на каталоги. Это свойство значительно отличает этот вид ссылок от других ссылок и позволяет рассматривать в качестве средства "связывания" файловых систем для получения единого целого3. Символическим ссылкам свойственны те проблемы, о которых шла речь при обсуждении связей между устройствами. Если удаляется файловая система, содержащая оригинальный файл, или оригинальный файл получит новое имя, или будет инсталлирован в системе новый файл с таким же именем, как у оригинального, то символическая ссылка будет ссылаться (в соответствии с порядком перечисления этих вариантов): в никуда, в никуда, на совсем другое содержание, чем у оригинального файла. Символические ссылки могут указывать на родительские каталоги, тем самым создавая циклы в дереве каталогов. С помощью символических ссылок можно превратить вашу файловую систему в "порцию спагетти". Но ядро признает только такие символические ссылки и никакие другие. Поэтому ядро может проверять ссылки на наличие потери объектов, на которые производится ссылка, а также на наличие бесконечных циклов. Системные вызовы для символических ссылок Системный вызов symlink создает символическую ссылку. С помощью системного вызова readlink можно получить имя оригинального файла. С помощью системного вызова Istat получают статусную информацию об оригинальном файле. Обратитесь к справочнику и прочтите документацию относительно вызовов unlink, link, чтобы узнать, как они работают с символическими ссылками. Заключение Основные идеи • Unix организует на дисковой памяти файловые системы. Файловая система - это объединение файлов и каталогов. Каталог - это список имен и указателей. Каждая запись в каталоге указывает на файл или каталог. В каталоге находятся записи, которые указывают на его родительский каталог и его подкаталоги. • Файловая система в Unix состоит из трех основных частей: суперблок, таблица inode, область данных. Позиция inode в таблице называется номером inode файла. Номер inode является уникальным идентификатором файла. • Один и тот же номер inode может находиться в различных каталогах, но для каждого такого номера будут разные имена файлов. Каждая такая запись называется твердой ссылкой. Символическая ссылка - это ссылка, которая обеспечивает обращение к файлу по имени, а не номеру inode. • Несколько файловых систем могут быть объединены в одно дерево. Операция, с помощью которой ядро связывает каталог одной файловой системы с корнем другой файловой системы, называется монтированием. • В Unix имеются системные вызовы, с помощью которых программист может создавать и удалять каталоги, дублировать указатели, перемещать указатели, изменять имена, которые ассоциированы указателям, присоединять и отсоединять другие файловые системы. 3. Вызов Istat разыменовывает ссылку.
Заключение 161 Визуальное заключение Запись в каталоге состоит из имени файла и номера inode. Номер inode указывает на структуру на диске. Эта структура содержит информацию о файле и о распределении блоков данных файла. Каталог Запись в каталоге Таблица inode Рисунок 4.15 Inodes, блоки данных, каталоги, указатели Что дальше? Файлы - это только один из источников данных. Программы также обрабатывают данные, которые поступают с устройств, таких, как терминалы, видеокамеры, сканеры. Kajc программы в Unix получают данные от устройств и как посылает на них данные? Исследования 4.1 Команда pwd выводит путь к текущему каталогу в файловой системе. В определенном смысле каталог - это ваше место расположения в дереве. На самом деле такой каталог представляет собой некоторое объединение байтов, которые расположены на диске в каком-то месте. Это место можно определить с помощью указания головки, дорожки, сектора и байта. Или можно указать с помощью цилиндра, головки сектора и байта. Какие имеются возможности для преобразования имени текущего каталога в термины аппаратных средств, с помощью которых производится указание места расположения? 4.2 Исследуйте один из твердых дисков в системе, которую вы используете. Определите, сколько разделов на этом диске. Определите для каждого раздела число inodes и число блоков данных. 4.3 Абстракцию типа "диск как массив" использует не только система Unix при создании файловой системы. Ее вправе использовать любой, кто имеет необходимые права доступа Чтобы реализовать такой проект, вам необходимо иметь права доступа root. Каталог /dev содержит файлы, с помощью которых вы можете читать байты данных размещаемые в блоках на диске так, будто эти байты находятся в файле. В система> Linux с IDE-дисководами вы можете найти файлы, которые называются /dev/hda, /dev . «. /j-./lj^ ia^.iuaa cw„ Ллойгпл vrxnnMrTR не являются обычными файлам*
162 Изучение файловых систем. Разработка версии pwd данных, аналогичные файлам /etc/passwd или /var/adm/utmp. Эти файлы устройств предоставляют возможность доступа к необработанным (raw) данным на диске. Вы можете использовать команды cat, more, cp и любые другие команды для работы с файлами для чтения содержимого, которое расположено на диске. Диск, подобно файлу utmp, имеет вполне очевидную структуру. Одна из возможностей получить поблочное содержимое диска - выполнить команду od -с /dev/hda | more. По мере выполнения такого постраничного вывода вы будете читать содержимое диска так, как будто вы читаете одну непрерывную последовательность из дисковых блоков. Для каждого раздела представлен один из таких специальных файлов устройств. Например, первый раздел на/dev/hda называется /dev/hdal. Исследуйте ваш каталог /dev и определите, какие специальные файлы в этом каталоге соотнесены твердым дискам, гибким дискам, CD-ROM или другим дисковым устройствам в системе. 4.4 В ядре имеется кодовый текст, который определяет место расположения свободного inode и находит свободные дисковые блоки. Это делается, когда ядро создает новый файл. Как ядро узнает, какие из блоков являются свободными? Как ядро узнает, какие из inode являются свободными? Какой метод используется в файловой системе на вашей машине для учета последовательности неиспользуемых блоков и inodes? 4.5 В Unix можно читать и монтировать диски (такие как PC-DOS и Macintosh диски), на которых находятся non-Unix-файловые системы. В этих файловых системах нет inodes. Тем не менее, если вы используете команду mount, для присоединения одного из таких дисков к системе Unix, то после выполнения команды Is -i вы обнаружите вывод списка inode для таких систем. Обратитесь к исходному коду Linux и поищите там ответ на вопрос: откуда берутся эти номера? Зачем в Linux происходит добавление этих номеров? 4.6 Текст, предназначенный для описания списка распределения блоков в составе inode, представляет собой описание десяти прямых блоков, одного косвенного блока, одного двойного косвенного блока и одного тройного косвенного блока. В некоторых версиях Unix используют другие номера для представления прямых и косвенных блоков. (а) Какой формат списка распределения в inode используется в вашей системе? Детали можно найти при рассмотрении заголовочных файлов. (в) Какой размер блока данных на вашей системе? (c) Какой самый большой файл в вашей системе не использует косвенные блоки? (d) Какой самый большой файл в вашей системе не использует двойные косвенные блоки? Сколько реально использует блоков этот самый большой файл? 4.7 Счетчик ссылок для каталогов. Файл может иметь много ссылок. Число таких ссылок записывается в счетчик ссылок для файла. А как обстоят дела в отношении каталогов? Используйте в вашей версии дерева demodir команду Is -l, чтобы посмотреть на значения счетчика ссылок для каталогов. Сравните эти значения счетчиков с числом дуг (Для каждого каталога. - Примеч. пер.) на диаграмме. Объясните значение счетчика ссылок для каталога. Почему каждый каталог имеет значение счетчика ссылок, которое будет не меньше 2? 4.8 Ссылки на каталоги. Использовать вызов link для образования новой ссылки на каталог нельзя. В Olden Days ® делать ссылки на каталоги разрешалось суперпользователю. В примере demodir проследите действие системного вызова link("demodir/c","de- modir/d2/e") в пользовательском и системном режимах. Затем поясните результаты работы команды Is -iaR demodir.
Заключение 163 4.9 Скрытые поддеревья. Когда вы с помощью команды mount присоединяете одну файловую систему к другой файловой системе, то точка монтирования должна быть каталогом в оригинальной файловой системе. Например, вы можете присоединить файловую систему на диске /dev/hda4 к каталогу /home2. Ответьте на следующие два вопроса: (а) Что произойдет, если точки монтирования (в данном случае /Ьоте2)не существует? (в) Что произойдет, если точка монтирования существует и содержит файлы и подкаталоги? 4.10 Команда rmdir не удаляет каталог, в котором содержатся файлы и подкаталоги. Почему такое решение можно считать хорошим? Но, с другой стороны, вы можете удалять каталог, в котором находится пользователь. Сделайте следующее и удивите ваших друзей: образуйте новый каталог с произвольным именем, перейдите в этот каталог. Далее откройте еще одно shell-окно и удалите этот каталог. Закройте второе shell-окно и выполните команду /bin/pwd. Объясните, что произойдет. 4.11 Что означает термин цилиндр для твердого диска? Какая физическая конструкция твердого диска, которая делает концепцию цилиндров важной с позиций эффективного использования диска? Найдите через Web пояснения термина группа цилиндров. Объясните связь между этой идеей и моделью файловой системы, которая была представлена в тексте. 4.12 Нехватка пространства на диске. Для большинства людей знакома проблема нехватки пространства на диске. В файловой системе Unix имеется область для inodes и область для данных. Поэтому возможно, что все пространство в области для inode будет использовано, хотя еще есть свободное пространство в области данных. Когда вы инсталлируете новый диск в Unix, то вам необходимо выделить на диске область для расположения в ней таблицы inode и выделить область данных. Для каждого файла в файловой системе необходим один inode. Чем больше места будет отведено под таблицу inode, тем меньше пространства останется для хранения содержимого файлов. Пускай вы собираетесь устанавливать новый твердый диск. Команда mkfs позволяет образовать новую файловую систему и дает вам возможность определить размер таблицы inode. Почитайте документацию по этой команде. Почему вам может потребоваться много inodes? Почему у вас может появиться необходимость запросить меньшее их число, чем обычно? 4.13 Системному вызову stat передается имя файла и указатель на структуру, которую он заполняет информацией о файле. Объясните, как работает системный вызов stat, используя для этого модель каталога, inode и данных. Где системный вызов находит данные о файле, которые он копирует в структуру stat? Программные упражнения 4.14 Напишите текст одной командной строки в Unix, чтобы можно было построить дерево каталогов demodir. 4.15 В Unix-команде mkdir можно использовать опцию -р. Напишите версию команды mkdir, в которой можно использовать эту опцию. 4.16 Команда mv - это всего лишь обертка системного вызова rename. Напишите версию команды mv, для которой при обращении потребуется указывать два аргумента. Первый аргумент должен быть именем файла, а второй аргумент должен быть именем файла или
164 Изучение файловых систем. Разработка версии pwd именем каталога. Если вторым аргументом указано имя каталога, то команда mv перемещает файл в этот каталог. В противном случае команда mv переименовывает файл, если это возможно. 4.17 Текст версии rename написан с использованием link и unlink. В этом кодовом фрагменте производится проверка кода возврата из link, но не проверяется код возврата из unlink. Расширьте возможности этого кода с целью достижения корректной реакции при ошибках исполнения unlink. 4.18 Ознакомьтесь с документацией в справочнике и с содержанием заголовочных файлов, чтобы разобраться со структурой суперблока на вашей системе. Напишите программу, которая открывает файловую систему, читает содержимое суперблока, и отображает ряд характеристик файловой системы в ясном, читабельном формате. Это упражнение аналогично составлению тех программ, которые были написаны для отображения содержимого utmp записей и stat структур. 4.19 Процедура создания нового файла включает четыре основные операции. Все они должны быть успешно завершены для того, чтобы файл был правильно включен в состав файловой системы. Что случится, если вдруг будет выключено питание компьютера где-либо при выполнении этих действий по созданию файла? Например, что произойдет, если данные были размещены в области данных, но inode сформировать не успели? (a) Определите порядок, в котором должны выполняться эти четыре основные операции. Аргументируйте ваш выбор. (b) Предположите, что система будет построена и работает в соответствии с тем, как вы ответили на вопрос (а). Что будет, если авария произойдет между какими-то двумя шагами в вашей процедуре? Например, если ваш процедура состоит из четырех шагов, то таких точек между шагами будет три. Объясните, какие некорректности возникнут в системе при возникновении аварии в каждой из этих трех точек? (c) Почитайте документацию по Unix-команде fsck. Насколько похож ваш ответ на вопросы пункта (Ь) на те действия, которые выполняет команда fsck? 4.20 В главе 3 мы разработали версию команды Is -1. Модифицируйте эту программу таким образом, чтобы она выводила бы номер inode дополнительно к той информации, которую она до того выводила. Где будет производиться поиск номера inode в модифицированном варианте вашей версии? Проекты На основе материала этой главы вы можете изучить дополнительный материал и разработать на его основе следующие Unix-программы: find, du, Is -R, mount, dump
Глава 5 Управление соединениями. Изучение stty Цели Идеи и средства • Подобие файлов и устройств. • Отличие между файлами и устройствам. • Атрибуты соединений. • Условия гонок и атомарные операции. • Драйверы устройств. • Потоки Системные вызовы и функции • fcntl, ioctl • tcsetattr, tcgetattr Команды • stty • write
166 Управление соединениями. Изучение stty 5.1. Программирование устройств % В нескольких главах мы рассмотрели программы, которые работают с файлами и каталогами. В компьютере есть еще один источник данных — периферийные устройства. Это модемы, принтеры, сканеры, мыши, громкоговорители, видеокамеры, терминалы. В этой главе мы рассмотрим сходство и различие между файлами и устройствами. Рассмотрим, каким образом можно использовать такие свойства при управлении соединениями с устройствами. В этой главе мы напишем версию команды stty. Команда stty дает возможность пользователям проверять и модифицировать установки, с помощью которых производится управление соединением клавиатуры и экрана. 5.2. Устройства подобны файлам Многие считают, что файл представляет собой "связку" данных, хранимых на диске. Но в Unix поддерживается более абстрактное представление файла. Прежде всего рассмотрим несколько характеристик, касающихся файлов. Файлы содержат данные, у файлов есть свойства, файлы идентифицируются с помощью имен в каталогах. Вы можете побайтно читать данные из файла, а также побайтно записывать данные в файл. Ну а теперь заметьте: эти же самые характеристики и действия применимы и в отношении устройств. Рассмотрим звуковую карту, к которой присоединен микрофон и громкоговоритель. Вы говорите что-либо в микрофон, звуковая карта преобразует сигналы вашего голоса в поток данных, а программа может читать этот поток данных. Когда программа записывает поток данных на карту, то полученный звук передается на громкоговорители. Для программы звуковая карта является источником данных и местом, куда можно передавать данные. Терминал, имеющий клавиатуру и дисплей, также аналогичен файлу. Значения клавиш, на которые вы нажимаете, считываются программой и воспринимаются как входные данные для нее. А символы, которые процесс передает на терминал, отображаются на экране. Для Unix звуковые карты, терминалы, мышь и дисковые файлы - все это рассматривается как один и тот же тип объектов. В системе Unix каждое устройство трактуется как файл. Каждое устройство имеет имя, номер inode, собственника, разряды прав доступа и время последней модификации. Каждый, кто знает, как можно работать с файлами, автоматически может использовать эти знания при работе с терминалами и другими устройствами. 5.2.1. Устройства имеют имена файлов Каждое устройство (терминал, принтер, мышь, диск и т. д.), которое присоединено к Unix машине, представлено в системе именем файла. По традиции файлы, которые представляют устройства, помещены в каталоге /dev, но вы вправе создавать файлы устройств в любом каталоге. Рассмотрите состав каталога /dev на различных Unix-машинах. Ниже показан фрагмент листинга для машины, на которой я сейчас работаю: $ Is-C/dev | head- XOR fd1u720 agpgart fd1u800 apm_bios fd1u820 arcd fd1u830 dsp flashO 5 loopl IpO Ipl Ip2 mcd ptyqf ptyrO ptyrt ptyr2 ptyr3 sda7 sda8 sda9 sdb sdM stderr ' stdin stdout tape tcp ' ttysd ttyse ttysf ttytO ttytl
5.2. Устройства подобны файлам 167 На этом листинге представлено несколько типов устройств. Файлы с именами lp* в третьей колонке - это принтеры. Файлы с именами fd* во второй колонке - это дисководы гибких дисков. Файлы с именами sd* — это разделы SCSI-устройств. Имя файла /dev/tape присвоено ленточному устройству, предназначенному для построения на нем системных копий (backup). Файлы с именами tty* в последней колонке — это терминалы. Программы при чтении из таких файлов получают значения символов при нажатии клавиш на клавиатуре. По мере записи данных в эти файлы программы посылают данные на экраны терминалов. Файл dsp представляет собой соединение со звуковой картой. Процесс проигрывает звуковой файл путем записи данных из звукового файла в этот файл устройства. Процесс может открыть файл /dev/mouse и далее воспринимать события, связанные с нажатием на кнопки мыши и со всеми изменениями расположения курсора мыши. 5*2.2. Устройства и системные вызовы Устройствам можно не только присваивать имена файлов. В их отношении можно использовать все системные вызовы, предназначенные для работы с файлами: open, read, write, lseek, close, stat. Например, фрагмент программы для чтения данных с магнитной ленты будет иметь такой вид: intfd; fd = openGdev/tapeH, 0_RDONLY); Г связаться с ленточным устройством */ lseek(fd, (long) 4096, SEEKJJET); /* перемотка ленты на 4096 байтов */ n = readffd, buf, buflen); /* чтение Данных с ленты */ close(fd); /* разрыв связи с устройством */ Для работы с устройствами можно использовать те же системные вызовы, которые вы использовали для работы с дисковыми файлами. В Unix фактически нет других средств для связи с устройствами. Некоторые устройства не поддерживают все файловые операции Когда вы перемещаете мышь и нажимаете на кнопки мыши, то от мыши в систему поступают байты данных, которые процесс может читать с помощью вызова read. Ну а что произойдет, если процесс попытается выполнить вызов write в отношении мыши? Передачи данных на мышь не произойдет. Мышь можно только перемещать и нажимать на ней кнопки. Для файла /dev/mouse не поддерживается системный вызов write. Конечно, кто-то может придумать мышь с моторчиком и написать для нее усовершенствованный драйвер, который будет способен как принимать события от мыши, так и вырабатывать их. Для терминалов поддерживаются системные вызовы read и write, но не поддерживается вызов lseek. А почему? 5.2.3. Пример: Терминалы аналогичны файлам Большая часть пользовательских входов для Unix производится через терминалы. Файлы ttysd, ttyse и т. д. в приведенном листинге представляют собой терминалы. Терминалом называют все, что ведет себя аналогично классической клавиатуре с устройством отображения. Сюда можно отнести печатающий терминал 70-х годов, и клавиатуру с экраном, которые подсоединены к последовательному порту, и ПК с модемом и программой эмуляции терминала, которая связана с системой через телефонную линию. Окна telnet или ssh,
168 Управление соединениями. Изучение stty через которые можно входить в систему через Интернет, ведут себя как терминалы. Основными компонентами терминала являются источник ввода символов от пользователя и любое устройство отображения для вывода данных пользователю. Устройство отображения может даже выдавать тексты для слепых в кодах Брайля или воспроизводить данные в звуковом виде. С помощью команды tty можно узнать имя файла, который представляет ваш терминал. Давайте поэкспериментируем с терминальными файлами: $tty /dev/pts/2 $ ср/etc/motd/dev/pts/2 Today is Monday, we are running low on disk space. Please delete files. - your sysadmin $ who >/dev/pts/2 bruce pts/2 Jul 17 23:35 (ice.northpole.org) bruce pts/3 Jul 18 02:03 (snow.northpole.org) $ Is -li /dev/pts/2 4 crw--w--w-1 bruce tty 136,2 Jul 18 03:25 /{Jev/pts/2 Команда tty сообщает, что мой терминал подсоединен к файлу /dev/pts/2, т. е. оконечное имя файла 2, файл находится в подкаталоге pts для каталога /dev. Мы можем использовать произвольные файловые команды и операции для работы с этим файлом: ср, перенаправление вывода с помощью операции >, mv, In, rm, cat, Is. Команда ср читает данные из обычного файла /etc/motd и записывает их на устройстве /dev/ pts/2, что приводит к отображению содержимого исходного файла на экране. При записи данных в файл устройства происходит передача данных на устройство. На следующей строке в данном примере показывается, как производится передача результатов работы команды who с помощью оператора перенаправления > в файл /dev/pts/2. После этого данные отображаются в символьном виде на указанном экране1. 5.2.4 Свойства файлов устройств У файлов устройств имеется большая часть тех же свойств, что есть у дисковых файлов. В выводе результатов работы команды Is, что представлен выше, видно, что файл /dev/pts/ 2 имеет inode 4, права доступа: rw--w—w-, счетчик ссылок равен 1, собственник файла - bruce, группа-tty, время последней модификации Jul 18 03:25. Обозначение типа файла- "с". Этим обозначением показывается, что такой файл в действительности является устройством, относительно которого происходит побайтная пересылка данных. Права доступа выглядят несколько странными, а вместо размера файлов мы видим выражение 136,2. Что означает это выражение? Файлы устройств и размер файла. Обычные дисковые файлы содержат какое-то количество байтов данных. Число байтов в дисковом файле называют размером файла. Файл устройства - это соединение, а не контейнер. Клавиатуры и мышь не хранят, сколько было нажатий на клавиши или на кнопки мыши. В inode файла устройства хранится не размер файла и распределение его по памяти, а указатель на подпрограмму в ядре. Такая подпрограмма ядра, которая получает данные от устройства и передает данные на устройство, называется драйверам устройства. 1. Или в коде Брайля, или воспроизводится звук.
5.2. Устройства подобны файлам 169 В нащем примере с файлом /dev/pts/2 программный код, который перемещает данные между системой и терминалом (туда и обратно). Это подпрограмма под номером 136 в таблице драйверов. Для этой подпрограммы при вызове задается целочисленный аргумент. В случае работы с файлом /dev/pts/2 значением аргумента будет 2. Эти два номера, 136 и 2, называют старший номер и младший номер устройства. Старший номер определяет, какая подпрограмма будет управлять конкретным устройством. Значение младшего номера будет передаваться этой подпрограмме. Файлы устройств и права доступа. У каждого файла имеются разряды, с помощью которых задаются права на чтение, запись и исполнение. Какой смысл будет в использовании этих разрядов прав доступа, когда речь идет не о файле, а о файле устройства? При попытке записи данных в файл устройства данные передаются устройству. Поэтому право на запись означает право на передачу данных устройству. В нашем текущем примере собственник файла и члены группы tty имеют право писать на терминале, но только собственнику файла разрешено читать данные с терминала. При чтении из файла устройства, аналогично чтению из обычного файла, получают данные из файла. Ввод данных с терминала заключается в нажатии пользователем на клавиши клавиатуры. Если пользователи, не являясь собственниками файла терминала, получат право на чтение из файла /dev/pts/2, то они смогут читать символы, которые будут нажиматься на клавиатуре. Но при чтении данных с клавиатуры другого пользователя могут возникнуть проблемы. С другой стороны, запись символов на терминал другого пользователя является целью команды write. 5.2.5. Разработка команды write Еще задолго до появления средств обмена сообщениями и всевозможных chat rooms (Без перевода! - Примеч. пер.) Пользователи в Unix беседовали с друзьями, которые находились за другими терминалами, с помощью команды write: $ man 1 write WRITE) 1) Linux Programmer's Manual WRITE( 1) NAME write - send a message to another user SYNOPSIS write user [ttyname] DESCRIPTION Write allows you to communicate with other users by copy-ing lines from your terminal to theirs. When you run the write command, the user you are writing to gets a message of the form: Message from youmame@yourhost on yourtty at hh:mm Any further lines you enter will be copied to the speci-fied user's terminal. If the other user wants to reply, they must run write as well. When you are done, type an end-of-file or interrupt char-acter. The other user will see the message EOF indicating that the conversation is over.
170 Управление соединениями. Изучение stty Версия команды write, следующая далее, не будет посылать сообщение "Message from" и требует имени файла для терминала (ttyname), а не пользовательского имени собеседника: Г writeO.c * цель: посылка сообщений на другой терминал * метод: открыть другой терминал для вывода, затем * произвести копирование stdin на другой терминал * представление: терминал, который воспринимается как файл и поддерживает * обычный ввод/вывод * обращение: writeO ttyname 7 #include <stdio.h> #include <fcntl.h> main(intac, char*av[]) { intfd; charbufpUFSIZJ; /* проверка аргументов */ if (ас != 2){ fprintf(stderr,Husage: writeO ttyname\n"); exitA); } Г открытие устройств */ fd = open(av[1],OWRONLY); lf(W —1){ perror(av[1));exitA); } /* цикл, пока не будет признак EOF на входе */ while(fgets(buf, BUFSIZ, stdin) != NULL) if (write(fd, buf, strien(buf)) == -1) break; close(fd); } Тщательно проанализируйте эту программу и попытайтесь найти в ней специальные средства для установления соединения вашей клавиатуры с экраном другого пользователя. Их нет. В этом примере программы write производится копирование строк из одного файла в другой. Эта простая программа и примеры в предшествующем разделе показывают, что к терминалам, а также ко всем устройствам, которые присоединены к Unix-машине, можно обращаться точно так же, как к дисковым файлам. 5.2.6. Файлы устройств и Inodes Как работать с файлами устройств? Как в файловой системе Unix inodes и блоки данных поддерживают идею файлов устройств? На рисунке 5.1 показаны соединения.
5.3. Устройства не похожи на файлы 171 Inode для дискового файла содержит список указателей на блоки в области данных Inode для файла устройства содержит указатель на драйвер устройства в ядре Пластины Каталог tty02 utmp Рисунок 5.1 Inode ссылается на блоки данных или на код драйвера Каталог - это список имен файлов и номеров inode. В каталоге нет ничего такого, что говорило бы о принадлежности имени дисковому файлу или принадлежности имени устройству. Разница между типами файлов проявляет себя на уровне inode. Каждый номер inode - это ссылка на структуру в таблице inode. Каждая такая структура может быть либо inode дискового файла, либо inode файла устройства. Тип inode записывается в поле типа в элементе stjnode структуры struct stat. В inode дискового файла находятся указатели на блоки на диске, где содержатся данные. В inode файла устройства находится указатель на таблицу подпрограмм ядра. С помощью старшего номера указывается, где нужно искать программный код, с помощью которого можно будет получать данные от устройства. Рассмотрим, например, как работает системный вызов read. Ядро находит inode для файлового дескриптора. С помощью inode ядро узнает о типе файла. Если это дисковый файл, то ядро будет получать данные с использованием списка распределения блоков. Если это файл устройства, то ядро читает данные с помощью обращения к коду read в составе драйвера для этого устройства. Подобная логика поддерживается и в отношении других операций - open, write, lseek, close. 5.3. Устройства не похожи на файлы Внешне дисковые файлы и файльгустройсхв похожи. Оба имеют имена и обладают свойствами. Системный вызов open создает соединение файлами и с устройствами. Но соединение с дисковыми файлами отличается от соединения с терминалом. На рисунке показан процесс, у которого имеются два файловых дескриптора. Один определяет соединение с дисковым файлом, а другой определяет соединение с пользователем за терминалом.
172 Управление соединениями. Изучение^ Дисковый Файл терминала Дисковые файлы используют буферирование Файлы терминала обладают свойствами: эхоотображение, скорость передачи данных, редактирование, переход на новую строку Рисунок 5.2 Процесс с двумя файловыми дескрипторами. У нас уже есть определенное представление о структуре этих соединений. В соединении с дисковым файлом обычно присутствуют буферы ядра. Данные, которые передаются от процесса к диску, накапливаются в буферах и позже передаются из буферов в память ядра. Буферирование является атрибутом соединения с диском. Соединения с терминалами имеют отличия. Процессы, которые желают послать данные на терминалы, хотят, чтобы это происходило максимально быстро. Соединение с терминалом или модемом также имеет атрибуты. Для последовательного соединения это скорость передачи, биты четности, опредленные значения стоп-битов. Символы, которые вы набираете, на клавиатуре, обычно отображаются на экране. Но иногда, например при наборе вашего пароля, символы набираются без эхоотображения. Эхо- отображение символов не является частью клавиатуры и не является частью действий, выполняемых в программе. Эхоотображение - это атрибут соединения. Соединения с дисковыми файлами не имеют таких атрибутов. 5,3.1. Атрибуты соединения и контроль В Unix поддерживается подобие файлов и устройств, когда нужно это подобие. И принимается во внимание их различие, когда в этом есть необходимость. Соединение с дисковым файлом отличается от соединения с модемом. Обратимся к атрибутам соединений: 1. Какие атрибуты есть у соединения? 2. Как можно проверить текущие атрибуты? 3. Как можно изменять текущие атрибуты? Далее мы рассмотрим два примера: соединения с дисковыми файлами и соединения с терминалами.
5.4. Атрибуты дисковых соединений 173 5.4. Атрибуты дисковых соединений Системный вызов open создает соединение между процессом и дисковым файлом. Это соединение имеет несколько атрибутов. Рассмотрим более подробно два атрибута и обменяемся мнениями о других. 5.4.1. Атрибут i: Буферизация На следующей диаграмме изображен файловый дескриптор в виде двух каналов, которые присоединены к обрабатывающему устройству. Таким обрабатывающим устройством является ядро, которое выполняет буферизацию и другие задачи по обработке данных. Внутри этого ящика находятся переменные, с помощью которых определяется, какие выполнять шаги по обработке данных. Картинка выглядит так: к V . i ¦• i i I ,' - ¦ ZD - У\ Щ С помощью файлового дескриптора контролируется, как работает драйвер Рисунок 5.3 Обрабатывающее устройство в потоке данных Вы можете модифицировать действие файлового дескриптора, изменяя значения этих управляющих переменных. Например, вы можете выключить дисковую буферизацию, используя для этого простую, трехшаговую процедуру. ^: L-i iffiflinflffil 1 1 ff { 1 I // л |^*-| ^¦ч^ Изменить установки драйвера: 1. Получить значения установок 2. Модифицировать их 3. Поместить значения обратно. Рисунок 5.4 * Модификация действия файлового дескриптора Сначала выполним системный вызов, чтобы скопировать управляющие переменные из файлового дескриптора в ваш процесс. Далее модифицируем копию ваших управляющих переменных. Новые значения установок помещаются в состав обрабатывающего кода. И теперь ядро обрабатывает данные в cooteeTCTBHH с новыми значениями установок. Ниже представлен код, который воспроизводит в программном виде эти три шага:
174 Управлениесоединениями. Изучение^ #include <fcntl.h> ints;//установки s = fcntl(fd, F_GETFL); // получить флаги s |= O.SYNC;//установить бит SYNC result = fcntl(fd, F.SETFL, s); // установить флаги if (result ==-1)//если ошибка perror( "setting SYNC"); // отчетность Атрибуты файлового дескриптора кодируются с помощью битового представления целого числа. Системный вызов fcntl позволяет вам получить контроль над файловым дескриптором с помощью операций чтения и записи этого целого числа: НАЗНАЧЕНИЕ INCLUDE ИСПОЛЬЗОВАНИЕ АРГУМЕНТЫ КОДЫ ВОЗВРАТА fcntl Управление файловыми дескрипторами «include < fcntl.h > «include <unistd.h> «include <sys/types.h> int result = fcntl(int fd, int cmd); int result = fcntl(int fd, int cmd, long arg); int result = fcntl(int fd, int cmd, struct flock *lockp); fd - файловый дескриптор.который контролируется cmd - операция, которую нужно выполнить arg - аргументы для операции lock - информация о блокировке •1-при ошибке Другие значения зависят от операции Системный вызов fcntl выполняет действие cmd над открытым файлом, который определен дескриптором fd. Для действия cmd можно задавать аргументы arg. В нашем примере с помощью действия f_getfl получаем текущий набор битов (также называют флаги). Этот набор флагов помещается в переменную s. Оператор поразрядного или устанавливает бит OSYNC. Тем самым с помощью этого бита устанавливается требование к ядру о том, что возврат из вызова write должен произойти только после окончания записи на реальное устройство. Следовательно, действие по умолчанию не выполняется, когда возврат из вызова происходит сразу после того, как данные будут скопированы в буфер ядра. Наконец, мы должны передать модифицированные значения установок обратно ядру. Мы определяем действие F_SETFL как второй аргумент в системном вызове и указываем с помощью третьего аргумента, какие модифицированные значения следует установить в ядре. Такая трехшаговая процедура - чтение текущих установок из ядра в переменную, изменение значений считанных установок, помещение этих установок обратно в ядро - является стандартным средством для чтения и модификации атрибутов соединений в Unix. При установке 0_SYNC снижается эффективность от буферирования в ядре. Поэтому необходимы убедительные причины, которые вынуждают отключить буферирование. 5.4.2. Атрибут2: Режим Auto-Append Другим атрибутом файлового дескриптора является режим auto-append. Режим auto-append полезен для файлов, в которые производится одновременная запись несколькими процессами.
5.4. Атрибуты дисковых соединений 175 Почему полезен режим Auto-Append Рассмотрим журнал wtmp. В журнале wtmp сохраняется история всех вхождений в систему и выходов из системы. Когда пользователь входит в систему, то программа login добавляет запись в конец файла wtmp. Когда пользователь выходит из системы, то система добавляет запись о выходе в конец файла wtmp, этого своеобразного дневника системы. Как в дневнике человека, так и здесь каждая новая запись должна добавляться в конец имеющегося текста. А не можем ли мы использовать Iseek, чтобы добавлять записи в конец файла? Рассмотрим следующую логику для login: Присоединение данных к файлу с помощью двух системных вызовов: lseek(fd,0,SEEK__END) ; write(f d,&rec,len) Рисунок 5.5 Присоединение записей с помощью lseek и write Системный вызов lseek устанавливает текущую позицию на конец файла, а затем системный вызов write добавляет входную запись к файлу. Что плохое может здесь произойти? Что будет, если два человека входят в систему в одно и то же время? Обратимся к рисунку 5.6, на котором изображено распределение времени обработки. Время I Вхождение пользователя А * 1 - 2 ; lseek(fd,0,SEEKJ2ND); 4* write(fd,&rec;len); Вхождение пользователя В lseek(fd,0,SEEKJEND); write(fd,&rec,len); Рисунок 5.6 Чередующиеся lseek и write = хаос Файл wtmp изображен в середине рисунка. Слева изображена временная ось, на которой находятся четыре временных отметки. Последовательность действий при вхождении пользователя А представлена слева, а последовательность действий при входе пользова-
176 Управление соединениями. Изучение stty теля В приведена справа. Пока все нормально? Важным обстоятельством является тот факт, что Unix является системой разделения времени, и что в этой процедуре требуется выполнение двух отдельных шагов: lseek и write. Теперь посмотрим в замедленном темпе, как все будет происходить: time 1 — Процесс В ищет конец файла. time 2 — Интервал времени для В закончился. Процесс А ищет конец файла. time 3 — Интервал времени для А закончился. Процесс В производит запись. time 4 — Интервал времени для В закончился. Процесс А производит запись. Таким образом, запись от процесса В будет потеряна, поскольку произойдет ее перезапись процессом А. Такая ситуация называется условием гонок. (Иногда называют условием состязаний. - Примеч. пер.) Окончательный результат обработки файла, который разделяется этими двумя процессами, будет зависеть от того, как будет спланировано развитие этих процессов. Если выполнить даже небольшие изменения в распределении действий во времени, то это может привести к потере записи о входе пользователя А. Или все может произойти так, что ничего не будет потеряно. Как можно аннулировать это условие гонок? Есть множество вариантов, чтобы избежать условий гонок. Условия гонок представляют собой критическую проблему в области системного программирования. Мы будем многократно возвращаться к этой теме. В нашем конкретном случае можно воспользоваться средством, которое находится в ядре и которое обеспечивает простое решение проблемы: поддержка режима auto-append. Если будет установлен бит 0_APPEND для файлового дескриптора, то это приведет к тому, что в системном вызове write автоматически будет включен lseek для выставления на конец файла. В этом фрагменте кода устанавливается режим auto append и затем вызывается write: #include <fcntl.h> ints;//установки s = fcntl(fd, FJ3ETFL); // получить флаги s |= 0_APPEND; // установить бит APPEND result = fcntl(fd, FJ5ETFL, s); // установить флаги if (result — -1) // если ошибка perrorf'setting APPEND"); // сообщение else write(fd, &rec, I); // записать в конец файла Атомарные операции. С важным термином условия гонок связан другой важный термин-атомарная операция. Вызовы lseek и write представляют собой раздельные во времени системные вызовы. Ядро может прервать процесс в точке, которая расположена между этими двумя вызовами. Когда установлен бит 0_APPEND, то ядро комбинирует одну атомарную операцию из lseek и write. Две операции объединяются в одну неделимую операцию. (При выполнении неделимой операции ядро уже не может ее прервать. -Примеч. пер.)
5.4. Атрибуты дисковых соединений 177 5А. 3. Управление файловыми дескрипторами с помощью системного вызова open OSYNC и 0_APPEND - два атрибута файлового дескриптора. Но их гораздо больше. Мы рассмотрим другие установки в последующих главах. В документации системногр вызова fcntl представлен список всех опций и операций, которые поддерживаются для вызова в вашей системе. Установка атрибутов файлового дескриптора с помощью fcntl не является единственной возможностью. Часто при открытии файлов вам известно, какие нужно сделать установки. Системный вызов, open дает вам возможность определить биты атрибутов файлового дескриптора, используя для этого часть второго аргумента при обращении к вызову. Например, с помощью вызова, fd = open(WTMP_FILE, OJ/VRONLY|0>PPEND|0„SYNC); будет открыт на запись файл wtmp (В примере, в системном вызове open указано имя файла WTMP_FILE. - Примеч. пер.) с установленными битами 0_APPEND и 0_SYNC. Второй аргумент в системном вызове open используется не только для указания режима открытия: на чтение, на запись, на чтение/запись. Например, вы можете запросить при выполнении open, чтобы файл был предварительно создан. Это делается с помощью флага 0_CREAT. Следующие два вызова будут эквивалентны: fd = creat(filename, permission bits); fd = open(filename, O^CREAT|OJRUNC|0_WRONLY, permission _bits); Почему существует системный вызов creat, если ту же работу можно выполнить с помощью системного вызова open? В старых системах с помощью open происходило только открытие файлов, а с помощью creat создавался новый файл. Позже системный вызов open был модифицирован и стал поддерживать большее количество флагов, в том числе и опцию по созданию файла. Другие флаги, которые поддерживаются в open O.CREAT Создать файл, если он не существовал. Смотри 0_EXCL OJRUNC Если файл существует, то следует уничтожить его содержимое -установить размер файла равным 0 (транкатенировать). 0_EXCL Флаг 0_EXCL предполагает предотвращение попытки создания одного и того же файла двумя процессами. Если указанный файл существует и установлен флаг 0_CREAT, то системный вызов возвратит-1. Комбинация флагов 0„CREAT и 0_EXCL может быть использована для устранения следующей ситуации гонок. Что произойдет, если два процесса попытаются одновременно создать один и тот же файл? Например, что будет, если два процесса пожелают вести записи в файл wtmp, но потребуется создать этот файл, если он до этого не существовал? Программа определяет, существует ли файл с помощью вызова stat. Затем вызывается creat, если обнаруживается, что файл не существует. Проблема может возникнуть тогда, если процесс будет прерван в точке между stat и creat. Флаги 0_EXCL/0_CREAT позволяют объединить эти два системных вызова в атомарную операцию. Несмотря на наши старания, в ряде важных случаев эта комбинация работать не будет. Надежным альтернативным решением будет использование link. В упражнениях есть пример на эту тему.
178 Управление соединениями. Изучение stty 5.4.4. Итоговые замечания о дисковых соединениях Ядро передает данные между дисками и процессами. Код в ядре, который выполняет .такие передачи, имеет много опций. Программа может использовать системные вызовы open и fcntl с тем, чтобы управлять (контролировать) выполнением внутренней работой по пересылке этих данных. Установки соединены! Соединение с дисковым файлом Рисунок 5.7 Соединения с файлами имеют установки 5.5. Атрибуты терминальных соединений Системный вызов open создает соединение между процессом и терминалом. Рассмотрим более детально ряд атрибутов соединения с терминалом. 5.5.1. Терминальный ввод/вывод не такой, как он кажется Соединение между терминалом и процессом выглядит достаточно простым. Вы можете передавать байты данных между устройством и процессом, используя getchar и putchar. Абстракция потока данных делает похожей такую систему пересылки на систему, где клавиатура и экран подключены прямо к процессу: getchar putchar Симво) >лы I Шруср Процесс Разрез потока mctmmcm Пользователь Рисунок 5.8 Соединения с файлами имеют установки
5.5. Атрибуты терминальных соединений 179 Простой эксперимент показывает, что эта модель не является полной. Рассмотрим программу: /* listchars.c * цель: представление в списке тех символов, которые * поступают на вход программы * вывод: одна пара в строке, значения символа в формате char и в ascii коде * ввод: stdin,noKa не появится на входе символ Q * замечание: программа полезна для показа, что присутствуют средства * буферирование/редактирование 7 #include <stdio.h> main () { int с, п = 0; while((c = getchar())!=,Q') printff'char %3d is %c code %d\n", n++, с, с); } Программа производит посимвольную обработку, т. е. читает очередной символ, а затем выводит значение счетчика цикла, сам символ и его внутренний код. Откомпилируйте и запустите программу на исполнение. После запуска вы можете получить такой результат: $ /listchars hello char 0 is h code 104 char 1 is e code 101 char 2 is I code 108 char 3 is I code 108 char 4 is о code 111 char 5 is code 10 Q $ Что происходит? Если коды символов поступают непосредственно с клавиатуры на get- char, то мы должны были бы получать желаемый результат сразу после нажатия на каждую клавишу. Но вместо этого нам придется нажать на пять клавиш при наборе слова hello, а потом нажать на клавишу Enter. Только тогда программа обработает введенные символы. Ввод оказался буферируемым. Подобно данным, которые передаются на диск, поток байтов при передаче с терминала сохраняется в каком-то месте. Иногда listchars может вести себя по-другому. При нажатии на клавиши Enter или Return обычно посылается код ASCII, равный 13, что соответствует символу возврат каретки {carriage return). Из вывода программы listchars следует, что код ASCII, равный 13, при передаче был заменен на код 10, который соответствует символу line feed или newline (перевод строки). Третий вид обработки, который влияет на программный вывод. Программа listchars при выводе посылает символ перехода на новую строку (\п) в конце каждой строки. Код перехода на новую, строку указывает на необходимость перемещения курсора на одну строку, но не указывает на необходимость возврата курсора в самую левую колонку строки. Код 13 (carriage return, т. е. возврат каретки) требует возврата курсора в самую левую колонку.
180 Управление соединениями. Изучение^ Попросите вашего дедушку, чтобы он рассказал вам о блестящей ручке, которая находилась с левой стороны каретки пишущей машинки. Вы узнаете, что с ее помощью происходил возврат каретки в левую исходную позицию листа бумаги. Результат исполнения listchars показал, что в файловом дескрипторе должен быть где-то обрабатывающий уровень. На рисунке 5.9'проиллюстрировано действие этого уровня. L^^ Символ V' Рисунок 5.9 Ядро обрабатывает данные терминала На этом примере представлено три вида обработки: 1. Процесс не принимает данных до тех пор, пока пользователь не нажмет на клавишу Return. 2. Пользователь нажимает Return (код ASCII 13), но процесс воспринимает символ new- line (код ASCII 10). 3. Процесс посылает символ newline и терминал принимает пару символов: Return-New- line. Соединения с терминалами имеют сложный набор свойств и шагов по обработке данных. 5.5.2. Драйвертерминала Соединение терминала с процессом выглядит так, как показано на рисунке 5.10. Рисунок 5.10 Драйвер терминала является частью ядра
5.5. Атрибуты терминальных соединений 181 Набор подпрограмм ядра, которые обрабатывают данные, передаваемые между процессом и внешним устройством, называют драйвером терминала или драйвер tty 2. Драйвер содержит много установок, с помощью которых производится управление его работой. Процесс может читать, модифицировать и сбрасывать значения этих управляющих флагов драйвера. 5.5.3. Команда stty Команда stty предоставляет пользователю возможность читать и изменять значения установок в драйвере терминала. Использование команды stty для работы с установками драйвера дисплея. Выходные результаты работы команды stty будут выглядеть приблизительно так: $stty speed 9600 baud; line = 0; $ stty —all speed 9600 baud; rows 15; columns 80; line = 0; intr = AC; quit = Л\; erase =л?; kHI = TJ; eof = AD; eol = <undef>; eol2 = <undef>; start = AQ; stop = AS; susp = AZ; rprnt = AR; werase = AW; Inext = AV; flush = A0; min = 1; time = 0; -parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts -ignbrk brkint ignpar -parmrk -inpck -istrip -inlcr -igncr icml ixon -ixoff -iuclc -ixany imaxbel opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nIO crO tabO bsO vtOffO isig icanon iexten echo echoe echok -echonl -nofish -xcase -tostop -echoprt echoctl echoke По умолчанию команда выдает короткий листинг. При использовании опции -all выдается список, в котором представлено много установок. Некоторые установки представлены как переменные, имеющие некоторые значения. Некоторые же установки являются булевскими константами. Например, установки baud rate (скорость в бодах), число строк и колонок на экране имеют числовые значения. А такие установки, как intr, quit и eof, имеют символьные значения. Наконец, такие значения, как icrnl, -olcuc и onlcr, представляют собой флаги, которые либо установлены, либо сброшены. Каково назначение всех этих установок? Установка icrnl является аббревиатурой от Input: convert Carriage Return to NewLine (Ввод: преобразование Carriage Return в NewLine). Она обозначает действие, которое выполнял драйвер в нашем предшествующем примере. Аббревиатура onlcr означает Output: add to NewLine a Carriage Return (Вывод: добавить Carriage Return к NewLine). Знак минус, который стоит перед атрибутом, указывает на то, что данная операция выключена. Например, установка -olcuc означает невозможность выполнения действия типа Output: convert LowerCase to UpperCase (Вывод: преобразование из нижнего регистра в верхний). Многие старинные терминалы печатали только заглавными символами, поэтому преобразование вывода в заглавные символы было для них полезным. Изменение установок драйвера с помощью команды stty. Вот несколько примеров использования команды stty по изменению установок драйвера: 2. tty - это осталось от ссылок на первые'печатающие терминалы, которые производились Teletype Corporation.
182 Управление соединениями. Изучение stty $ stty erase X # сделать X клавишей стирания $ stty -echo # набор без эхо отображения $ stty erase @ echo # несколько установок терминала В первом примере мы используем команду stty для изменения клавиши, с помощью которой можно корректировать ошибки при вводе. Обычно такое действие по стиранию введенного символа закреплено за клавишей backspace или delete. Но вы можете закрепить выполнение такого действия за любой клавишей. Во втором примере мы отключили воспроизведение символа при нажатии на клавишу. Когда вы набираете пароль при входе в систему, то при наборе символов пароля эти символы не воспроизводятся на вашем экране. А дальше, при нажатии на клавиши, символы опять будут отображаться. Выключение отображения символов приводит к тому, что при наборе вы не будете видеть на экране, что набираете. В третьем примере мы используем команду stty для изменения сразу нескольких установок. Мы заменили символ стирания на символ 4@' и опять включили режим echo (режим с эхоотображением при наборе). Как работает команда stty? Можем ли мы написать команду stty? 5.5.4. Программирование драйвера терминала: Установки Драйвер терминала поддерживает дюжины операций, которые он может выполнить над данными, передаваемые с его помощью. Эти операции сгруппированы по четырем категориям: Входная Задается, что драйвер делает с символами, которые поступают к нему с терминала Выходная Задается, что драйвер делает символами, которые он выдает на терминал Управляющая Задается, как представлены символы - число разрядов, четность, стоп-биты и т. д. Локальная Задается, что драйвер делает, когда символы находятся внутри драйвера Входная обработка включает в себя преобразования представления символов из нижнего регистра в верхний, сбрасывание high bit, преобразование управляющего символа carriage returns в newlines. При выходной обработке символы табуляции заменются на последовательность из пробелов, происходит преобразование управляющего символа newlines в carriage returns, происходит преобразование символов из нижнего регистра в верхний. Управляющая обработка включает в себя even parity, odd parity (проверку на четность или нечетность), работу со стоп-бйтами. Локальная обработка включает в себя установление для пользователя режима эхо отображения и буферирование ввода до тех пор, пока пользователь не нажмет на клавишу Return. Кроме включений/выключений установок, драйвер поддерживает список ключей (клавиш) со специальным назначением. Например, пользователи могут для удаления символа нажимать на клавишу backspace. Драйвер отслеживает нажатия на эту клавишу и производит действие по этому ключу стирания. Кроме того, драйвер отслеживает нажатия еще ряда других управляющих символов. В документации на команду stty приводится список большинства установок и управляющих символов. 5.5.5. Программирование драйвера терминала: Функции Изменение установок в драйвере терминала производится аналогично тому, как делаются изменения установок для соединений с дисковым файлом: (a) Получить атрибуты от драйвера. (b) Модифицировать какие-то атрибуты, которые вы желаете. (c) Пеоелать эти молиАипиоованные атоибуты обоатно лпайвеоу.
5.5. Атрибуты терминальных соединений 183 Например, в последующем программном коде включается режим эхоотображения для соединения: #include <termios.h> struct termios attribs; •/* структура, где хранятся атрибуты */ tcgetattr(fd, Ssettings); /* получить атрибуты драйвера */ settings.cjflag |= ECHO; /* включить бит ECHO в наборе флагов */ tcsetattr(fd, TCSANOW, &settings); /* передать атрибуты обратно драйверу */ Общая процедура изменения атрибутов изображена на рисунке 5.11: #include <termios.h> struct termios settings; tcgetattr(fd,&settings); /* проверака, установка или сброс битов */ */ tcsetattr(fd, how.&settings); Рисунок 5.11 Управление драйвером терминала с помощью tcgetattr и tcsetattr Библиотечные функции tcgetattr и tcsetattr обеспечивают доступ к драйверу терминала. Обе функции работают с установками в структуре struct termios. Детали функций следующие: tcgetattr НАЗНАЧЕНИЕ Чтение атрибутов драйвера терминала INCLUDE #include <termios.h> #include < unistd.h > ИСПОЛЬЗОВАНИЕ int result = tcgetattrfmt fd, struct termios *info); АРГУМЕНТЫ fd - файловый дескриптор для терминала info - указатель на структуру termios КОДЫ ВОЗВРАТА -1 при ошибке О - при успехе Функция tcgetattr копирует текущие установки драйвера терминала, которому сопоставлен дескриптор открытого файла устройства/^, в структуру info. tcsetattr .НАЗНАЧЕНИЕ Установить атрибуты драйвера терминала INCLUDE #include <termios.h> tinclude < unistd.h > ИСПОЛЬЗОВАНИЕ int result = tcsetattr(int fd, int when, struct termios *info);
184 Управление соединениями. Изучение stiy tcsetattr АРГУМЕНТЫ fd - файловый дескриптор для терминала info - указатель на структуру termios when - когда изменять установки КОДЫ ВОЗВРАТА -1-при ошибке О-при успехе Функция tcsetattr копирует установки драйвера из структуры, на которую указывает info, и передает их драйверу терминала, которому сопоставлен файловый дескриптор fd. С помощью аргумента when при обращении к функции указывается, когда следует модернизировать установки драйвера. Допустимы следующие значения для аргумента when: TCSANOW Немедленно модернизировать установки драйвера. TCSADRAIN Ждать, пока все выходные данные, которые собраны в очереди в драйвере, не будут переданы на терминал. TCSAFLUSH Ждать, пока все выходные данные, которые собраны в очереди в драйвере, не будут переданы на терминал. Далее сбросить все поступившие входные данные. Затем выполнить изменения установок. 5.5.6. Программирование драйвера терминалов: Флаги В типе данных struct termios содержится несколько наборов флагов и массив управляющих символов. Во всех версиях Unix включены следующие поля: . struct termios { tcflagj cjflag; /* флаги режима ввода */ tcflagj c_pflag; /* флаги режима вывода */ tcflagj c.cflag; /* флаги управляющего режима 7 tcflagj cjflag; /* флаги локального режима */¦ ее J c_cc[NCCS]; /* управляющие символы */ speedj cjspeed;/* скорость ввода 7 speed t с ospeed; /* скорость вывода 7 }; Скорость передачи в бодах для входных и выходных потоков данных хранится в полях cjspeed и c_ospeed. Распределение разрядов в каждом из наборов флагов показано на рисунке 5.12. Первые четыре члена - это наборы флагов. Каждый набор флагов состоит из разрядов, которым сопоставлены операции в этой группе. Например, член cjflag содержит разряд со значением INLCR. Член c_cflag содержит бит проверки на нечетность (odd parity), который называется маской PARODD. Все эти маски определены в termios.h. Когда вы обращаетесь к текущим атрибутам из драйвера в struct termios, то все значения в этой структуре вам доступны для проверки и модификации.
5.5. Атрибуты терминальных соединений 185 c_iflag I I I I I I IT Mill c__of lag en 1 I I I I 1 I I I I [ I I 3 3 ^ О 2 68R О Г f t- к; к; OFD m r OFI r» r« ONL 50 И >3 О О * OLC G О ONL о я c_cflag I i I I I I I I I I I l 1ТЛ под SO It» G Ч О >0 woo 3 P^ ТЭ ТЭ О egg g § s D CD О О w w •-Э и О N тэ и ш c_lflag I 1 I II I I I I I I I I 1 I I I §OOMOOOOOOOW 1ПЧХ'Ж«ЯЯДИ>Н 2 о r о 3 3 M О ana о 73 ^ < О н s § Рисунок 5.12 Разряды и символы в составе членов termios Член с__сс-это массив управляющих символов. В этом массиве хранятся символы тех клавиш, при нажатии на которые выполняются различные управляющие функции. Каждый элемент в массиве определяется константой из файла termios.h. Например, присвоение значения вида attribs.c_cc[VERASE]-\b' будет означать для драйвера, что он будет рассматривать ключ backspace как символ стирания. Программирование драйвера терминала: Битовые операции Теперь мы знаем, как получить установки драйвера и как их передать драйверу обратно. Рассмотрим теперь технику модификации атрибутов драйвера. Каждый атрибут - это бит в составе набора флагов. Маски для атрибутов определены в файле termios.h. Для проверки значения некоторого атрибута вы должны замаскировать набор флагов маской для этого бита. Для установления значения некоторого атрибута вы должны установить соответствующий бит. Для сброса значения атрибута, вы должны сбросить соответствующий бит. В таблице показаны эти действия. Действие Проверка бит Установка бита КОД if(flagest&MASK)... flagset | = MASK Очистка бита flagset &= -MASK
186 Управление соединениями. Изучение stty 5.5.7. Программирование драйвера терминала: Примеры программ Пример: echostate.c - показать состояние бита echo Наша первая программа будет сообщать нам об установке символов режима эхо отображения. Производится чтение установок, проверка бита и вывод результатов проверки. Г echostate.c * сообщает, установлен ли бит echo у драйвера терминала, файловый дескриптор fd которого равен О * показывает, как читаются атрибуты из драйвера терминала и как проверяются значения бита 7 #include <stdio.h> #include <termios.h> main() { struct termios info; intrv; rv = tcgetattr@, &info); /* читать значения атрибутов драйвера 7 if (rv ==-!){ perrorftcgetattr"); exitA); } if (info.c_Iflag & ECHO) printf(" echo is on, since its bit is 1\n"); else printfC echo if OFF, since its bit is 0\n"); } В этой программе читаются атрибуты терминала через файловый дескриптор 0. Нулевой файловый дескриптор принадлежит стандартному вводу, т. е. такой файловый дескриптор обычно устанавливается для клавиатуры. Далее показаны команды компиляции и запуска программы на исполнение: $ ее echostate.c -о echostate $./echostate echo is on, since its bit is 1 $ stty -echo $./echostatr: not found $ echo is OFF, since its bit is 0 Пример показывает нам, что по команде stty -echo устанавливается запрет на эхоотображе- ние в драйвере. Если пользователь будет набирать тексты двух каких-то команд после указанной команды, то тексты этих команд отображаться на экране не будут. Но при этом результаты выполнения этих команд отображаются на экране. Пример: setecho.c - изменить состояние бита echo Наша вторая программа может переключать режим эхоотображения для клавиатуры. Если при обращении к программе аргумент будет начинаться с символа "у", то флаг терминала echo должен быть установлен. В противном случае флаг echo будет сброшен. Программа выглядит так:
5.5. Атрибуты терминальных соединений 187 Г setecho.c * обращение: setecho [y|n] х показывает, как читать, изменять и переустанавливать атрибуты терминала 7 #include <stdio.h> #include <termios.h> #define oops(s,x) {perror(s); exit(x);} mainfintac, char*av[]) { struct termios info; if(ac==1) exlt(O); if (tcgetattr@,&info) == -1) /* получить атрибуты */ oops("tcgettattrM, 1); if(av[1][0]==V) info.cjflag |= ECHO; Г включить бит */ else info.cjflag &= -ECHO; /* выключить бит */ if (tcsetattr@,TCSAN~OW,&info) == -1) /* установить атрибуты */ oops("tcsetattr",2); } Проверим и запустим на исполнение наши две программы и выполним обычную команду stty: $ echostate; setecho n; echostate; stty echo echo is on, since its bit is 1 echo is OFF, since its bit is 0 $ stty -echo; echostate; setecho y; setecho n echo is OFF, since its bit is 0 В первой командной строке мы использовали программу setecho для выключения режима эхоотображения. Далее мы использовали команду stty, чтобы восстановить режим эхоотображения. Драйвер и установки драйвера хранятся в ядре, а не в процессе. Процесс может изменять установки драйвера. Другой процесс также может читать или изменять установки. Пример: showtty.c - отобразить набор атрибутов драйвера Мы можем применить технику, которая была использована в программах setecho.c и echos- tate.c и разработать полную версию команды stty. Драйвер терминала обрабатывает три вида установок: специальные символы, числовые значения и битовые значения. В программе showtty находятся функции для отображения каждого из этих типов данных. Далее следует код программы: Г showtty.c * отображает некоторые текущие установки терминала 7 #include <stdio.h> #include <termios.h> main()
8 Управление соединениями. Изучение stty struct termios ttyinfo; /* эта структура содержит информацию о терминале 7 if (tcgetattr@, &ttyinfo) == -1){/* получить информацию 7 perrorfcannot get params about stdin"); exit(l); } Г show info 7 showbaud (cfgetospeed(&ttyinfo)); /* получить + показать baud rate*/ printf(The erase character is ascii %d, Ctrl-%c\n", ttyinfo.c_cc[VERASE]lttyinfo.cxc[\/ERASE]-H-,A,); printf('The line kill character \s ascii %d, Ctrl-%c\n", ttyinfo.c_cc[VKILL]>ttyinfo.c-.cc[VKIЩ-1+,A,); show some flags(&ttyinfo); /* nc } showbaud(int thespeed) Г * вывод скорости по-английски 7 { printf("the baud rate is"); switch (thespeed){ case B300: case B600: case В1200: case В1800: case B2400: case B4800: case B9600: default: } } struct flaginfo {int fl_value; char *fl_name;}; struct flaginfo input flags[] = { " IGNBRK, BRKINT, I6NPAR, PARMRK, INPCK, ISTRIP, INLCR, IGNCR, ICRNL, IXON, Г IXANY, IXOFF, O.NULL}; struct flaginfo localjagsn = { misc. flags 7 рппИС'ЗОО^"); < printf(M600\nM); рппй(200\пн); printf(M1800\nn); prirrtfl,,2400\n"); printf<800\nM); printfC^eOOXn"); printf("Fasl\n"); break; break; break; break; break; break; break; break; "Ignore break condition", "Signal interrupt on break", ч "Ignore chars with parity errors", "Mark parity errors", "Enable input parity check", "Strip character", "Map NLtoCR on input", "Ignore CR", "Map CR to NL on input", "Enable start/stop output control", ¦ "разрешить некоему символу производить рестарт вывода", */ "Enable start/stop input control",
5.5. Атрибуты терминальных соединений 189 ISIG, "Enable signals", ICANON, "Canonical input (erase and kill)", Г _XCASE, "Каноническое проявление upper/lower", */ ECHO, "Enable echo", ECHOE, "Echo ERASE as BS-SPACE-BS", ECHOK, "Echo KILL by starting new line", O.NULL}; show some flags(struct termios *ttyp) Г * показывает значения двух флагов в наборе: c_iflag и c_lflag * добавление флагов c_oflag и c_cflag - это подпрограмма, которая * добавляет новую таблицу, указанную выше, и биты, как это показано ниже 7 { showJagset(ttyp->c_iflag, inputjlags); show flagset(ttyp->c Iflag, local flags); } show flagset(int thevalue, struct flaginfo thebitnamesf]) I* * проверят каждый битовый шаблон и выводит краткое сообщение 7 { inti; for (i=0; thebitnames[i].f Ualue; i++) { printff %s is", thebitnamesp] .fl_name); if (thevalue & thebitnamesp] .fhralue) printf("ON\n"); else printf("OFF\n"); } } Программа showtty выводит текущее состояние семнадцати атрибутов драйвера, сопровождая вывод разъяснительным текстом. Наша программа использует массив структур для упрощения кода. При вызове функция showjlagset задается целое число и набор флагов драйвера. В функции show_flagset циклически проверяются все биты и отображается статус каждого из них. Что потребуется добавить в нашей программе для работы с другими наборами флагов? Что еще нужно добавить в эту программу, чтобы она работала как полная версия stty? 5.5.8. Итоговые замечания по соединениям с терминалами Терминал - это устройство, которое используется человеком для связи с процессами Unix. Терминал имеет клавиатуру, с которой процесс читает символы, и дисплей, на котором отображаются символы, выдаваемые процессом. Терминал - это устройство. Поэтому он представлен как специальный файл в дереве каталогов. Обычно он заносится в каталог /dev. Передача и обработка данных между процессом и терминалом происходит под управлением драйвера терминала, который является частью ядра. Этот код ядра поддерживает буферирование, редактирование и преобразование данных. Программы могут проверять значения и модифицировать значения установок этого драйвера с помощью функций tceetattr и tcsetattr.
190 Управление соединениями. Изучение stty 5.6. Программирование других устройств: ioctl Соединение с дисковым файлом имеет один набор атрибутов, а соединение с терминалом имеет другой набор. А что можно сказать относительно соединений с другими типами устройств? Рассмотрим пишущие CD. Перезаписываемые CD можно стирать. На CD можно проводить запись с разными скоростями. Сканеры имеют собственный набор установок, таких как разрешение при сканировании и глубина цвета. У других типов устройств также имеются собственные наборы установок. Как прбграммист может проверять и управлять установками для устройств? С каждый файлом устройства может работать системный вызов ioctl: НАЗНАЧЕНИЕ INCLUDE ИСПОЛЬЗОВАНИЕ АРГУМЕНТЫ КОДЫ ВОЗВРАТА ioctl Управление устройством #include < sys/ioctl.h > int result = ioctl (intfd, int operation [, arg..]) fd - файловый дескриптор устройства operation - операция, которую необходимо выполнить arg... - аргументы, необходимые для выполнения операции -1 - при ошибке Другие значения зависят от устройства Системный вызов ioctl позволяет получить доступ к атрибутам драйвера устройства и к операциям над атрибутами. Драйверу соответствует файловый дескриптор/^. Каждый тип устройства имеет собственный набор свойств и ioctl операций. Например, экран терминала имеет размер, который измеряют числом строк и числом колонок в строке или в пикселях. Следующий ниже код. #include <sys/ioctl.h> . void print screen_dimensions() { struct winsizewbuf; if (ioctl@, TIOCGWINSZ, &wbuf) != -1){ printf("%d rows x %d cols\n", wbuf.wsjow, wbuf.ws_col); printf("%d wide x %d tall\n", wbuf.ws xpixel, wbuf.ws ypixel); } } выполняет вывод значения размера экрана. Здесь TIOCGWINSZ - это имя кода функции, а адрес wbuf является аргументом при обращении к этой функции управления устройством. Лучшим способом изучения типов устройств и их функций является чтение заголовочного файла. В документации на устройства также приведены списки свойств и функций. Например, при обращении к справочнику Linux за документом stD), получим детальное описание вариантов использования ioctl для управления ленточным устройством SCSI. 5.7. О небо! Это файл, это устройство, это поток! В Unix файл рассматривается либо как источник данных, либо как приемник данных. Основные системные вызовы применимы в равной степени как к дисковым файлам, так и к файлам устройств. Различия возникают в действиях, которые происходят в соединениях. В файловом дескрипторе для дискового файла содержится код для буферирования
Заключение 191 и присоединения данных. Файловый дескриптор для терминала содержит код, который производит редактирование, поддерживает эхоотображение, преобразование символов, а также другие операции. Мы описали каждый шаг по обработке, как атрибут соединения. Но вместо этого можно также сказать, что соединение - это просто комбинация шагов по обработке. System V Unix, являющаяся одним из вариантов Unix, была разработана AT&T в 80-х годах. В этой системе была предложена модель потока данных. Основаная идея модели - построение последовательности шагов обработки. Это напоминает последовательность действий при мытье автомобиля. Сначала ваш автомобиль обрызгивают моющим раствором. Затем смывают грязь с помощью больших щеток. Далее смывают грязь с поверхности, что делают с помощью мыльного раствора из шлангов с высоким давлением. Накладывается антикоррозийный ингибитор, набрызгивается горячий воск и накладывается полировка для хромированных колпаков. Наконец, обрабатывают поверхность мягкой тканью и горячим воздухом. Все, дело сделано! Конечно, каждый этап является отдельной операцией, которую захотел выполнить владелец автомобиля. Владелец выбрал их из последовательности тех операций, на которые в данной компании был разбит процесс помывки машины. Кроме того, вы можете отказаться от некоторых конкретных шагов. (Но, пожалуйста, не отказывайтесь от шага нанесения горячего воска!) То, что было описано, представляет собой в огрубленном виде идею модели ПОТОКОВ (STREAMS) относительно данных и атрибутов соединений. Элегантной частью потоковой "модели является модульность обработки. Если вы не удовлетворены драйвером терминала, который поддерживает только такие скучные операции как преобразование символов из нижнего регистра в верхний и наоборот, то вы можете разработать и инсталлировать модуль для перевода цифр в римские цифры. Итак, вы должны написать обрабатывающий модуль, который выполняет преобразования арабских цифр в римские. Вы должны были написать его в соответствии со спецификациями модуля STREAMS. Затем следует использовать специальные системные вызовы для правильной инсталляции этого модуля между шагом отделки колпака и шагом протирки мягкой тканью. Когда ваш автомобиль достигнет конца мытья, то все цифры на приборном щитке будет заменены на римские. Обратитесь к вашему справочнику за документами по теме streamio, чтобы более подробно изучить вопросы управления свойствами соединений. ПОТОКИ используются в некоторых версиях Unix для реализации сетевых служб. Заключение Основные идеи • Ядро передает данные между процессами и теми объектами, которые находятся извне. Такими внешними объектами могут быть дисковые файлы, терминалы и периферийные устройства (принтеры, ленточные устройства, звуковые карты, мыши). Соединения с дисковыми файлами и соединения с устройствами имеют как подобия, так и отличия. • Дисковые файлы и файлы устройств имеют имена, свойства и разряды прав доступа. Как для файлов, так и для устройств можно использовать стандартные файловые системные вызовы: open, read, write, close и Iseek. Управление доступом к устройствам происходит с помощью разрядов прав доступа к файлам. Управление происходит точно так же, как это происходит при работе с дисковыми файлами.
192 Управление соединениями. Изучение stty • Соединения с дисковыми файлами отличаются от соединений с файлами устройств в методах обработки и передачи данных. Код ядра, который управляет соединениями с неким устройством, называют драйвером устройства. Процесс может читать и изменять установки в драйвере устройства с помощью системных вызовов fcntl и ioctl. • Соединения с терминалами являются настолько важными, что были разработаны специальные функции tcgetattr и tcsetattr, с помощью которых можно контролировать работу драйверов терминалов. • Unix команда stty предоставляет пользователю доступ к функциям tcgetattr и tcsetattr. Визуальное заключение Драйвер порта принте| Рисунок 5.13 Файловые дескрипторы, соединения и драйверы Процесс использует системные вызовы read и write для извлечения данных из файлового дескриптора и помещения данных в него соответственно. Файловые дескрипторы можно устанавливать для связи с дисковыми файлами, терминалами и периферийными устройствами. Файловый дескриптор приводит процесс к драйверу устройства. Драйвер устройства имеет установки. Что дальше? Чтение данных с дисков производится достаточно просто, а вот чтение данных, которые поступают от людей, может быть достаточно изощренным, поскольку люди ведут себя весьма непредсказуемо. Программы, которые разрабатываются для чтения данных, которые поступают от людей, могут использовать свойства терминального драйвера по управлению соединением. В следующей главе мы более детально рассмотрим некоторые темы программирования в отношении пользовательских программ. Исследования. 5.1 На Linux-машине легко читать данные, поступающие от мыши. Чтобы достичь этого, вам необходимо находиться в текстовом режиме. Находясь в shell, убедитесь в том, что не работает программа gpm. Для этого наберите gpm -k. Затем выполните cat /dev/ mouse. Теперь перемещайте мышь и нажимайте на кнопки. Команда cat будет читать
Заключение 193 данные из файла устройства. Те байты, которые будут прочитаны, будут соответствовать сообщениям о таких событиях, как нажатия на кнопки и перемещение мыши. 5.2 Каково назначение бита, разрешающего исполнение для файла устройства? Изучите команду biff, чтобы получить представление об использовании этого бита. 5.3 Операции над каталогами и файлы устройств. Мы обсудили, как работают операции ввода/вывода для файлов устройств. А что можно сказать относительно операций над каталогами типа In, mv, rm и т. д. Используя рисунок 5.1, объясните, каким образом будут воздействовать эти три команды на каталоги, inodes и на драйверы. 5.4 rm и специальные файлы. Команда rm и лежащий в ее основе системный вызов unlink удаляют ссылку на inode. Если число ссылок на inode достигнет нуля, то ядро освобождает дисковые блоки и inode (Которые относятся к файлу, имя которого указано в команде. -Примеч. пер.) В inode устройства нет списка распределения. Файл устройства не содержит блоков данных. Вместо этого в inode для файла устройства содержится указатель на подпрограмму устройство-драйвер в ядре. Если вы удалите имя файла для устройства и ядро отметит соответствующий inode как свободный, то драйвер при этом останется в ядре. Как можно будет создать новый файл устройства и соединить его с устройством? (Подсказка: почитайте документацию о системном вызове mknod.) 5.5 Рассмотрим условия гонок, которые возникают при присоединении данных к файлу. В обсуждении проблемы, которое было представлено в тексте книги, рассматривалась одна из возможных последовательностей планирования. Сколько можно составить управляющих последовательностей в отношении двух операций для двух процессов? Что должно происходить при выполнении каждой их этих последовательностей? 5.6 Обратитесь к коду ядра Linux и найдите там место, где можно увидеть, как проверяется бит 0_APPEND. Как реализуется автоматический переход в конец файла? 5.7 Как работает системный вызов rename? Системный вызов rename является атомарной операцией. Из каких шагов состоит этот целостный вызов? Найдите код в ядре некоторого варианта Unix и рассмотрите все условия гонок и возможные конфликты, которые могут возникнуть при управлении. Комментарии, которые вы можете найти в ядре Linux, выглядят весьма неряшливо и забавно. 5.8 Стандартная библиотечная функция fopen поддерживает открытие файла в режиме append (режим добавления в конец файла). Например, fopen ("data","a"). Как устанавливается на вашей системе режим append - с помощью OAPPEND или с помощью lseek выполняется переход в конец файла после его открытия? Найдите исходный код для функции fopen или проведите эксперимент и напишите программу, которая дважды открывает один и тот же файл в режиме присоединения, а затем поочередно производит запись в два потока. 5.9 Проверка работы программы echostate с другими устройствами. Программа echos- tate.c оповещала о состоянии бита echo в драйвере для файлового дескриптора 0. Используйте оператор перенаправления < для присоединения стандартного ввода к другим файлам или устройствам. Проведите такой эксперимент: echostate < /dev/tty echostate </dev/lp echostate < /etc/passwd echostate < 'tty Объясните результаты, которые будут получены после работы каждой их этих команд.
194 Управление соединениями. Изучение stty 5.10 Изменение атрибутов других терминалов. Программа setecho позволяла изменять бит echo в драйвере, присоединенном к стандартному вводу. Если вы перенаправите стандартный ввод на другой терминал, то вы можете изменить бит echo для этого терминала. Проведите такой эксперимент: (a) Войдите дважды в систему на одной и той же машине (или сразу откройте два окна). (b) Выполните в каждом окне команду tty, чтобы определить имена файлов устройств для этих двух окон. Скажем, одно из них будет связано с файлом устройства /dev/ ttyp 1, а другое - с файлом устройства /dev/ttyp2. (c) В окне ttypl выполните setecho n < /dev/ttyp2 (d) В окне ttyp2 выполните команду echostate (e) Теперь в ttypl выполните команду echostate < /dev/ttyp2 @ Объясните эффект от того, что в результате получилось, (g) Попытайтесь выполнить то же с обычной командой stty. Вы когда-нибудь поймете, что полученные результаты чрезвычайно полезны. 5.11 Файлы устройств и управление терминалом. Примеры в тексте использовали значение 0 для файлового дескриптора в вызовах tcgetattr и tcsetattr. Первый аргумент при обращении к вызову - это файловый дескриптор. Он может быть любым значением, с помощью которого производится ссылка на связь с терминальным устройством. Файловый дескриптор 1 ссылается на стандартный вывод. Модифицируйте программы echostate и setecho так, чтобы использовать файловый дескриптор 1 вместо 0. Как это повлияет на работу программ? Часто стандартный ввод и стандартный вывод ссылаются на терминал. Объясните, что будет при таком перенаправлении: echostate » echostate.log. Какие преимущества от использования файлового дескриптора 0? 5.12 Если вы хотите установить систему с присоединением ррр соединений, то вам понадобится инсталлировать модем и произвести конфигурацию последовательного порта. Терминальный драйвер для последовательного порта может быть сконфигурирован для работы с модемом. Почитайте документацию о файлах /etc/gettydefs и /etc/inittab, чтобы понять, как Unix определяет терминальные установки для вхождений по последовательным линиям связи. 5.13 В некоторых версиях Unix поддерживаются три варианта использования OSYNC: только для блоков данных, только для inodes, то и другое вместе. Почему вам может потребоваться использовать одну из версий? Как называются флаги, которые контролируют работу в каждой из этих версий? 5.14 Каково назначение прав на чтение и запись при управлении терминальным специальным файлом? Используйте tty, чтобы определить имя вашего терминала, а потом выполните команду chmod 000 /dev/yourtty, чтобы сделать ваш терминал недоступным на чтение даже для вас. Что после этого произойдет? Почему? 5.15 Обратитесь к каталогу /dev на вашей системе и найдите там файлы, которые не поддерживают read, файлы, которые не поддерживают write, файлы, которые не поддерживают Iseek. 5.16 Используйте Is -I в каталоге /dev, чтобы посмотреть на старшие и младшие номера различных устройств. Какой формат вы видите? Какие устройства разделяют один и тот же главный номер? Что общего у этих устройств, в чем они различаются? 5.17 Назовите имя для каждой из четырех групп установок для tty драйвера, объясняет назначение каждой группы и назовите имя двух бит в каждой группе.
Заключение 195 5.18 Программа использует stcsetattr для выключения режима echo для текущего терминала. Когда эта программа заканчивается, то терминал остается в режиме с отключенным echo. Но с другой стороны, когда программа открывает файл и использует fcntl для установке дескриптора в режим OAPPEND, то следующая программа, которая открывает этот файл, не получает возможности установить режим auto-append. Объясните эту явную несовместимость. 5.19 Соединением с терминалом является обыкновенный файловый дескриптор. Можно ли использовать вызов fcntl для установки атрибута OAPPEND в файловом дескрипторе? Что означает режим auto-append для устройств? 5.20 В чем заключается разница между ioctl и fcntl? 5.21 В каталоге /dev содержатся файлы /dev/null и /dev/zero. Эти файлы не представляют собой реальное соединение с устройствами, но это также и не дисковые файлы. Каково назначение этих файлов и для чего они могут быть полезны? Можно ли найти в каталоге /dev еще файлы, которые являются виртуальными устройствами, как эти два файла? Программные упражнения 5.22 Расширьте версию программы write, которая была представлена в главе. Представленная версия требовала, чтобы пользователь указывал имя файла устройства. В этой версии не выводились начальные идентификационные приветствия. Напишите новую версию, для которой при обращении можно будет указывать в качестве аргумента имя пользователя. Эта версия будет выводить на экран сообщение о том, какой пользователь приглашает вас к взаимодействию. Посмотрите, как это делается в обычной версии команды write. Ваша программа должна предусматривать обработку при возникновении ряда особых ситуаций. Например, лицо, с которым вы хотели бы вступить во взаимодействие, может просто не работать в системе. Другая ситуация, когда лицо, с которым вам хотелось бы взаимодействовать, могло войти в систему сразу с нескольких терминалов. 5.23 Пользователи, которые не хотят, чтобы им мешали другие пользователи, выполняющие команду write, могут использовать команду mesg. Почитайте документацию по этой команде. Поэкспериментируйте с командой и посмотрите, как она работает. Затем напишите версию этой программы. 5.24 Использование linkQ для блокировки. Условие гонок возникает в ситуации, когда два процесса пытаются одновременно модернизировать один и тот же файл. Например, когда вы изменяете ваш пароль на некоторой системе, то программа passwd перезаписывает некоторую информацию в файле /etc/passwd. А что произойдет, если два пользователя попытаются одновременно изменить свои пароли? Один из подходов по предотвращению одновременного доступа к файлу заключается в использовании важного свойства системного вызова link. Рассмотрим код: Г * Программа пытается построить ссылку /etc/passwd.LCK * После выполнения программы код возврата равен 0, если ссылка была построена, * 1 - если уже есть блокировка, 2 - если возникли другие проблемы 7 int lock passwd() { int rv = 0; Г код возврата по умолчанию */
196 Управление соединениями. Изучение stty if (link("/etc/passwd", "/etc/passwd.LCK") == -1) rv = (errno==EEXISTS?1 :2); return rv; } (a) Если два процесса попытаются одновременно выполнить этот код, только одному из них удастся достичь успешного решения. Что вы можете сказать о системном вызове link, который может служить полезным средством для установления блокировки доступа к файлам? (b) Напишите короткую программу, которая использует метод присоединения строки текста к файлу. Ваша программа должна попытаться построить ссылку с помощью link. Если попытка создания ссылки будет успешной, то программа может далее открыть файл, присоединить строку, а затем удалить связь. Если попытка построения ссылки будет неуспешной, то программа должна использовать системный вызов sleep A), чтобы подождать одну секунду и потом повторить попытку построения ссылки. При программировании требуется позаботиться о гарантии, чтобы ваша программа не ждала бы бесконечно долго. (c) Напишите функцию unlock_passwd, которая отменяет действие lock_passwd. (d) В примере показан способ, как процессы могут блокировать доступ к существующему файлу. Но как может программа, где используется link, предотвратить возможность попытки одновременного процесса одного и того же файла? (e) Изучите команду vipw. Может ли vipw использовать links для установления блокировок? 5.25 Связи и блокировки, часть II В предшествующей задачи показано, как при возникновении проблемы гонок можно использовать связи для установления блокировок на файлы. Блокировка на файл должна быть снята, когда программа, которая установила блокировку, закончит модификацию файла. Если программа не снимет блокировку файла, то другие программы будут ждать доступа к этому файлу неопределенно долго. А что будет, если в программе (Которая установила блокировку - Примеч. пер.) есть ошибки и она аварийно закончилась или она была "убита" пользователем, который нажал на клавиши Ctrl-C? И все это произошло до сброса программой блокировки на файл. Один из вариантов решения проблемы - в программе, которая установила блокировку, выполнять модификацию файла каждые п секунд. Для этого программа моэкет использовать utime. Программы, которые ожидают по условию блокировки, могут проверять время модификации, чтобы убедиться - находится или нет файл в "теплом "состоянии. Если блокировка не была модифицирована на установленном временном интервале, то другие программы будут в праве удалить связь с тем, чтобы потом повторно ее установить. Напишите новую версию функции lock_passwd, которая при вызове получает в качестве аргумента значение длительности интервала в секундах. Эта новая версия должна реализовать логику, которая была описана в предыдущем параграфе.
Заключение 197 5.26 Как скажется на производительности, если вы отключите механизм буферизации? Напишите программу, которая производит запись в большой файл. Запись производится небольшими порциями. Например, запись производится частями в 16 байт в файл размером 2 Мегабайта. Попытайтесь писать в файл в ситуациях, когда установлен атрибут 0_SYNC и когда атрибут 0__SYNC не установлен. Поэкспериментируйте с размерами файла и размерами записываемых порций. Посмотрите, как это будет влиять на результаты работы. 5.27 В рассмотренном ранее тексте есть программный код, где выключается дисковая буферизация для файлового дескриптора. Напишите функцию, которая включала бы механизм буферизации. 5.28 Напишите программу uppercase.c, которая могла бы переключать бит OLCUC в драйвере терминала и оповещала бы о текущем состоянии этого бита. 5.29 Размер окна u ioctl В выводе команды stty -а есть информация о числе строк и колонок в терминальном окне. Эти значения исходят не от tcgetattr, исходят ioctl. Используйте этот системный вызов для модификации версии программы more из главы 1 с тем, чтобы программа использовала устанавливаемый размер экрана терминала вместо фиксированного значения 24. Проекты На основе материала этой главы вы можете изучить документацию и написать версию следующих программ Unix: write, stty, passwd, wall, biff, mt (программа управления ленточным устройством; может отсутствовать на вашей системе).
Глава 6 Программирование дружественного способа управления терминалом и сигналы Цели Идеи и средства • Инструментальные программные средства или пользовательские программы. • Чтение и изменение установок драйвера терминала. • Режимы работы драйвера терминала. • Неблокируемый ввод. • Таймауты на пользовательский ввод. • Введение по теме сигналов: Как работает Ctrl~C. Системные вызовы • fcntl • signal 6.1. Инструментальные программные средства В системе Unix устройства во многом выглядят аналогично дисковым файлам. Но устройства - это не то же, чем является дисковый файл. В главе 5 нами была рассмотрена возможность использования системных вызовов open, close, read, write, lseek в отношении устройств. Но мы также видели, что устройства имеют драйверы, и эти драйверы имеют большое число атрибутов и средств управления устройствами. Как программам относиться к этой двойственности?
6.1. Инструментальные программные средства 199 Инструментальные программные средства: в stdout Чтение из stdin или из файлов, запись Программы, которые не ощущают разницы между дисковыми файлами и устройствами, называют инструментальными программными средствами (Эти средства чаще всего называют утилитами. - Примеч. пер.) В системах Unix используют сотни инструментальных программных средств таких, как who, Is, sort, uniq, grep, tr, du. Эти средства построены на основе модели, которая представлена на рисунке 6.1. Стандартный ввод Особенность: большинство процессов автоматически получают открытыми первые три файловых дескриптора. Процессам не нужно выполнять системный вызов ореп() для получения таких дескрипторов Рисунок 6.1 Три стандартных файловых дескриптора Инструментальные программные средства читают данные со стандартного входа, производят некоторую обработку данных, записывают результирующий выходной поток байтов на стандартный вывод. Утилита посылает сообщения об ошибках, которые опять же рассматриваются как поток байтов, на стандартный вывод сообщений об ошибках. С файлами, терминалами, мышью, фотоэлементами, принтерами, программными каналами должны быть соединены три файловых дескриптора. Утилиты не обращают внимания на то, каков источник поступления данных на обработку и каков приемник полученных результатов обработки. Многие из таких программ читают информацию из файлов, имена которых задаются при обращении к команде в командной строке. Ввод и выход для таких программ может быть присоединен ко всем типам соединений: $ sort > outputfile $ sort x > /dev/lp $who|tr,[a-z],,[A-Z]f Программы, ориентированные на устройства: Управление устройством в конкретном применении Однако есть и другие программы, которые написаны для взаимодействия с конкретными устройствами. В качестве примера можно назвать программы для управления сканерами, для записи компакт дисков, для работы с ленточными накопителями, для производства цифровых фотографий. В этой главе мы изучим идеи и средства написания программ, ориентированных на конкретные устройства. Мы познакомимся с наиболее общими типами таких программ. Это программы, которые взаимодействуют с терминалами, которые разработаны специально для удобного использования человеком. Мы будем далее называть такие терминально ориентированные про