Системное программирование Линукс. Linux. Системное программирование. Вступление
Скачать 0.65 Mb.
|
37 интерфейсов Linux, абстрагируясь от проблем совместимости с многочисленными другими UNIXподобными системами и не думая о соответствии всем стандартам. Мы будем говорить только о Linux, поэтому можем позволить себе подробно оста новиться на ультрасовременных интерфейсах этой операционной системы, которые, несомненно, останутся востребованными и действующими в обозримом будущем. В основу книги положены глубокие знания Linux, информация о реализации и по ведении таких компонентов, как ядро и gcc . Эту работу можно считать повествова нием разработчикаветерана, полным проверенных методов и советов по оптими зации. Концепции программирования в Linux В этом разделе вы найдете краткий обзор сервисов, предоставляемых в системе Linux. Все UNIXподобные системы, включая Linux, предлагают общий набор аб стракций и интерфейсов. На самом деле в этой взаимосовместимости и заключает ся суть UNIX. Такие абстракции, как файл и процесс, интерфейсы для управления конвейерами и сокетами и т. д., являются главной составляющей систем UNIX. Этот обзор предполагает, что вы знакомы с системой Linux. Имеется в виду, что вы умеете обращаться с командной оболочкой, использовать базовые команды и ком пилировать простую программу на C. Это не обзор Linux или системы для програм мирования в ней, а рассказ об основах системного программирования Linux. Файлы и файловая система Файл — это самая простая и базовая абстракция в Linux. Linux придерживается философии «все есть файл», пусть и не так строго, как некоторые другие систе мы — достаточно вспомнить Plan 9 1 . Следовательно, многочисленные взаимодей ствия представляют собой считывание из файлов и запись в них, даже если объект, с которым вы имеете дело, совсем не похож на «традиционный» файл. Вообще, чтобы получить доступ к файлу, его сначала нужно открыть. Файлы можно открывать для чтения, записи или того и другого сразу. На открытый файл указывает уникальный дескриптор, отображающий метаданные, ассоциированные с открытым файлом, обратно на сам этот файл. В ядре Linux такой дескриптор управляется целым числом (в системе типов C целому числу соответствует тип int ). Эта сущность, называемая файловым дескриптором, сокращенно обозначает ся fd. Дескрипторы файлов совместно используются в системе и пользовательском пространстве. Пользовательские программы применяют их непосредственно для доступа к файлам. Значительная часть системного программирования в Linux сводится к открытию файлов, манипуляциям с ними, закрытию файлов и исполь зованию файловых дескрипторов иными способами. 1 Plan 9 — это операционная система, разработанная в BellLabs и часто характеризующая ся как наследница UNIX. В ней воплощено несколько инновационных идей, и она четко придерживается философии «все есть файл». Концепции программирования в Linux 38 Обычные файлы Сущности, которые большинству из нас известны под названием «файлы», в Linux именуются обычными файлами. Обычный файл содержит байты данных, органи зованные в виде линейного массива, который называется потоком байтов. В Linux для файла не задается никаких других видов упорядочения или форматирования. Байты могут иметь любые значения и быть организованы внутри файла любы ми способами. На системном уровне Linux не регламентирует для файлов ни какой структуры, кроме организации в виде потока байтов. В некоторых опера ционных системах, например VMS, используются высокоструктурированные файлы, в которых применяются так называемые записи. В Linux такие записи отсутствуют. Любые байты внутри файла могут использоваться для считывания или запи си. Эти операции всегда начинаются с указанного байта, который можно назвать местоположением в файле. Это местоположение называется файловой позицией или смещением файла. Файловая позиция — это важнейший элемент метаданных, который ядро ассоциирует с каждым открытием файла. Когда файл открывается впервые, его файловая позиция равна нулю. Обычно по мере того, как байты из файла считываются либо в него записывается информация (байт за байтом), значение файловой позиции увеличивается. Файловую позицию можно также вручную устанавливать в определенное значение, причем оно может находиться даже за пределами (за последним байтом) конкретного файла. Когда файловая позиция находится за пределами файла, промежуточные байты будут заполнять ся нулями. Вполне возможно воспользоваться этим способом и задать файловую позицию дальше конца файла, однако вы никак не сможете установить эту пози цию перед началом файла. Правда, такая практика кажется бессмысленной и дей ствительно она почти не применяется. Файловая позиция начинается с нуля; она не может иметь отрицательное значение. При записи в байт в середине файла значение, которое ранее находилось по этому смещению, заменяется новым, по этому вы не сможете расширить файл, записывая информацию в его середину. Как правило, запись в файл происходит в его конце. Максимальное значение файловой позиции ограничено только размером типа C, используемого для хра нения файла. В современных системах Linux максимальное значение этого пара метра равно 64 бит. Размер файла измеряется в байтах и называется его длиной. Можно сказать, что длина — это просто количество байтов в линейном массиве, составляющем файл. Длину файла можно изменить с помощью операции, которая называется усечени- ем. Файл можно укоротить, уменьшив его размер по сравнению с исходным. В ре зультате будут удалены байты, расположенные в конце файла. Термин «усечение» немного неудачный, поскольку им обозначается и удлинение файла, то есть уве личение его размера по сравнению с исходным. В таком случае новые байты (до бавляемые в конце файла) заполняются нулями. Файл может быть пуст (иметь нулевую длину) и, соответственно, не содержать ни одного валидного байта. Мак симальная длина файла, как и файловая позиция, ограничена лишь размерами тех типов C, которые применяются ядром Linux для управления файлами. Однако Глава 1. Введение и основополагающие концепции 39 в конкретных файловых системах могут действовать собственные ограничения, изза которых потолок длины файла существенно снижается. Отдельно взятый файл можно одновременно открыть несколько раз как в ином, так и в том же самом процессе. Каждому открытому экземпляру файла присваива ется уникальный дескриптор. С другой стороны, процессы могут совместно ис пользовать свои файловые дескрипторы, один дескриптор может применяться в не скольких процессах. Ядро не накладывает никаких ограничений на параллельный доступ к файлу. Множественные процессы вполне могут одновременно считывать информацию из файла и записывать туда новые данные. Результаты такой парал лельной работы зависят от упорядочения отдельных операций и, в принципе, непредсказуемы. Программы пользовательского пространства обычно должны взаимно координироваться, чтобы обеспечить правильную синхронизацию парал лельных обращений к файлам. Хотя доступ к файлам обычно осуществляется по их именам, непосредственная связь файла с его названием отсутствует. В действительности ссылка на файл вы полняется по индексному дескриптору 1 . Этому дескриптору присваивается цело численное значение, уникальное для файловой системы (но не обязательно уни кальное во всей системе в целом). Данное значение называется номером индексного дескриптора. В индексном дескрипторе сохраняются метаданные, ассоциированные с файлом, например отметка о времени его последнего изменения, владелец файла, тип, длина и местоположение данных файла, но имя файла там не сохраняется! Индексный дескриптор одновременно является и физическим объектом, располо женным на диске в UNIXподобной файловой системе, и концептуальной сущностью, представленной как структура данных в ядре Linux. Каталоги и ссылки Обращение к файлам по их индексным дескрипторам — довольно трудоемкий процесс (а также потенциальная брешь в системе безопасности), поэтому из поль зовательского пространства файлы обычно вызываются по имени, а не по индексно му дескриптору. Для предоставления имен, по которым можно обращаться к фай лам, используются каталоги. Каталог представляет собой отображение понятных человеку имен в номера индексных дескрипторов. Пара, состоящая из имени и ин дексного дескриптора, называется ссылкой. Физическая форма этого отображения, присутствующая на диске, например простая таблица или хеш, реализуется и управ ляется кодом ядра, поддерживающим конкретную файловую систему. В принципе, каталог ничем не отличается от обычного файла, за исключением того, что в нем содержатся лишь отображения имен в индексные дескрипторы. Ядро непосред ственно пользуется этими отображениями для разрешения имен в индексные дес крипторы. Когда из пользовательского пространства приходит запрос на открытие фай ла с указанным именем, ядро открывает каталог, в котором содержится файл с таким названием, и ищет данное имя. По имени файла ядро получает номер его 1 См.: http://ru.wikipedia.org/wiki/Inode. — Примеч. пер. Концепции программирования в Linux 40 индексного дескриптора. По этому номеру находится сам индексный дескриптор. Индексный дескриптор содержит метаданные, ассоциированные с файлом, в част ности информацию о том, в каком именно фрагменте диска записаны данные этого файла. Сначала на диске присутствует лишь один корневой каталог. К нему обычно ведет путь / . Однако, как известно, в любой системе, как правило, множество ката логов. Как ядро узнает, в каком именно нужно искать файл с заданным именем? Выше мы говорили о том, что каталоги во многом похожи на обычные файлы. Действительно, с ними даже ассоциированы свои индексные дескрипторы, поэто му ссылки внутри каталогов могут указывать на индексные дескрипторы, находя щиеся в других каталогах. Это означает, что одни каталоги можно вкладывать в дру гие, образуя иерархические структуры, что, в свою очередь, позволяет использовать полные пути к элементам, знакомые каждому пользователю UNIX, например /home/blackbeard/concorde.png Когда мы запрашиваем у ядра открытие подобного пути к файлу, оно обходит все записи каталогов, указанные в пути к элементу. Так удается найти индексный дескриптор следующей записи. В предыдущем примере ядро начинает работу с /, получает индексный дескриптор home, идет туда, получает индексный дескриптор blackbeard, идет туда и, наконец, получает индексный дескриптор concorde.png. Эта операция называется разрешением каталога или разрешением пути к элементу. Кроме того, в ядре Linux используется кэш, называемый кэшем каталогов. В кэше каталогов сохраняются результаты разрешения каталогов, впоследствии обеспе чивающие более быстрый поиск с учетом временной локальности 1 Если имя пути начинается с корневого каталога, говорят, что путь полностью уточнен. Его называют абсолютным путем к элементу. Некоторые имена путей уточнены не полностью, а указываются относительно какогото другого каталога (например, todo/plunder). Такие пути называются относительными. Если ядру предоставляется относительный путь, то оно начинает разрешение пути с текуще- го рабочего каталога. Отсюда ядро ищет путь к каталогу todo. В каталоге todo ядро получает индексный дескриптор plunder . В результате комбинации относительно го пути к элементу и пути к текущему рабочему каталогу получается полностью уточненный путь. Хотя каталоги и воспринимаются как обычные файлы, ядро не позволяет их открывать и производить с ними те же манипуляции, что и с обычными файлами. Для работы с каталогами используется специальный набор системных вызовов. Эти системные вызовы предназначаются для добавления и удаления ссылок — в принципе, на этом перечень разумных операций с каталогами заканчивается. Если бы можно было манипулировать каталогами прямо из пользовательского простран ства, без посредничества ядра, то единственной простой ошибки хватило бы для повреждения всей файловой системы. 1 Временная локальность — это высокая вероятность обращения к конкретному ресурсу после другой, более ранней операции доступа к нему же. Временная локальность харак терна для многих ресурсов компьютера. Глава 1. Введение и основополагающие концепции 41 Жесткие ссылки С учетом всего вышесказанного ничто вроде бы не препятствует разрешению мно жества имен в один и тот же индексный дескриптор. Действительно, это допуска ется. Когда множественные ссылки отображают различные имена на один и тот же индексный дескриптор, эти ссылки называются жесткими. Благодаря жестким ссылкам в файловых системах обеспечивается создание сложных структур, где множественные имена путей могут указывать на одни и те же данные. Эти жесткие ссылки могут находиться в одном каталоге, а также в двух и более различных каталогах. В любом случае ядро просто разрешает имя пути в вер ный индексный дескриптор. Например, можно поставить жесткую ссылку на кон кретный индексный дескриптор, ссылающийся на определенный фрагмент данных из двух мест — /home/bluebeard/treasure.txt и /home/blackbeard/to_steal.txt При удалении файла он отсоединяется от структуры каталогов. Для этого нуж но просто удалить из каталога пару, в которой содержится имя файла и его индекс ный дескриптор. Однако, поскольку в Linux поддерживаются жесткие ссылки, файловая система не может просто уничтожать индексный дескриптор и ассоци ированные с ним данные при каждой операции удаления. Что, если на этот файл были проставлены и другие жесткие ссылки из файловой системы? Чтобы гаран тировать, что файл не будет уничтожен, пока не исчезнут все указывающие на него жесткие ссылки, в каждом индексном дескрипторе содержится счетчик ссылок, отслеживающий количество ссылок в файловой системе, указывающих на этот дескриптор. Когда путь к элементу отсоединяется от файловой системы, значение этого счетчика уменьшается на 1. Лишь если значение счетчика ссылок достигает нуля, и индексный дескриптор, и ассоциированные с ним данные окончательно удаляются из файловой системы. Символьные ссылки Жесткие ссылки не могут связывать файловые системы, поскольку номер индекс ного дескриптора не имеет смысла вне его собственной файловой системы. Чтобы ссылки могли соединять информацию из различных файловых систем, становясь при этом и более простыми, и менее прозрачными, в системах UNIX применяются так называемые символьные ссылки. Символьные ссылки похожи на обычные файлы. Такая ссылка имеет свой ин дексный дескриптор и ассоциированный с ним фрагмент данных, содержащий полное имя пути к связанному файлу. Таким образом, символьные ссылки могут указывать куда угодно, в том числе на файлы и каталоги, расположенные в иных файловых системах, и даже на несуществующие файлы и каталоги. Символьная ссылка, указывающая на несуществующий файл, называется сломанной. C использованием символьных ссылок связано больше издержек, чем при ра боте с жесткими ссылками, так как символьная ссылка, в сущности, требует разре шения двух файлов: самой символьной ссылки и связанного с ней файла. При ис пользовании жестких ссылок такие дополнительные затраты отсутствуют — нет разницы между обращениями к файлам, обладающим одной связью в файловой Концепции программирования в Linux 42 системе либо несколькими связями. Издержки при работе с символьными ссыл ками минимальны, но тем не менее они воспринимаются отрицательно. Кроме того, символьные ссылки менее прозрачны, чем жесткие. Использование жестких ссылок — совершенно очевидный процесс. Более того, не так просто най ти файл, на который проставлено несколько жестких ссылок! Для манипуляций же с символьными ссылками требуются специальные системные вызовы. Эта не прозрачность зачастую воспринимается как положительный момент, так как в сим вольной ссылке ее структура выражается открытым текстом. Символьные ссылки используются именно как инструменты быстрого доступа (ярлыки), а не как внутрисистемные ссылки. Специальные файлы Специальные файлы — это объекты ядра, представленные в виде файлов. С годами в системах UNIX накопилось множество типов поддерживаемых специальных файлов. В Linux поддерживается четыре типа таких файлов: файлы блочных устройств, файлы символьных устройств, именованные каналы 1 и доменные соке ты UNIX. Специальные файлы обеспечивают возможности встраивания опреде ленных абстракций в файловую систему и, таким образом, поддерживают парадиг му «все есть файл». Для создания специального файла в Linux предоставляется специальный системный вызов. Доступ к устройствам в системах UNIX осуществляется через файлы устройств, которые выглядят и действуют как обычные файлы, расположенные в файловой системе. Файлы устройств можно открывать, считывать из них информацию и за писывать ее в них. Из пользовательского пространства можно получать доступ к фай лам устройств и манипулировать устройствами в системе (как физическими, так и виртуальными). Как правило, все устройства в UNIX подразделяются на две группы — символьные устройства и блочные устройства. Каждому типу устрой ства соответствует свой специальный файл устройства. Доступ к символьному устройству осуществляется как к линейной последова тельности байтов. Драйвер устройства ставит байты в очередь, один за другим, а про грамма из пользовательского пространства считывает байты в порядке, в котором они были помещены в очередь. Типичным примером символьного устройства яв ляется клавиатура. Если пользователь наберет на клавиатуре последовательность peg , то приложению потребуется считать из файлаустройства клавиатуры снача ла p , потом e и, наконец, g — именно в таком порядке. Когда больше не остается символов, которые необходимо прочитать, устройство возвращает «конец файла» (EOF). Если какойто символ будет пропущен или символы будут прочтены в не правильном порядке, то операция получится фактически бессмысленной. Доступ к символьным устройствам происходит через файлы символьных устройств. 1 Именованный канал называется в оригинале named pipe, более точный перевод — имено ванный конвейер. Тем не менее мы остановимся на варианте «именованный канал» (http://ru.wikipedia.org/wiki/Именованный_канал), как на более употребительном в рус ском языке, а термин pipe будем далее переводить как «конвейер». — Примеч. пер. Глава 1. Введение и основополагающие концепции |