Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Роберт Лав — Linux системное программирование

Системное программирование — это практика написания системного ПО, низкоуровневый код которого взаимодействует непосредственно с ядром (API ядра через системные вызовы) и основными системными библиотеками.

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

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

Системное программирование - это сердце всего создаваемого нами софта. Эта книга посвящена не всему системному программированию, а системному программированию в Linux.

В системном программировании для Linux можно выделить три основных крае­ угольных камня: системные вызовы, библиотеку C и компилятор C.

Системные вызовы (syscall) — это вызовы функций, совершаемые из пользовательского пространства. Они направлены из приложений к ядру. Смысл системного вызова — запросить у операционной системы определенную службу или ресурс. Системные вызовы, доступные в конкретной архитектуре, могут отли­чаться от доступных в другой. Тем не менее значительное подмножество всех систем­ ных вызовов — более 90 % — реализуется во всех архитектурах.

По причинам, связанным с обеспечением безопасности и надежности, приложениям пользовательского про­странства нельзя разрешать непосредственно исполнять код ядра или манипулиро­вать данными ядра. Вместо этого ядро должно предоставлять механизм, с помощью которого пользовательские приложения будут «сигнализировать» ядру о требовании активировать системный вызов. После этого приложение сможет осуществить системное прерывание ядра (trap) в соответствии с этим строго определенным меха­низмом и выполнить только тот код, который разрешит выполнить ядро.обычно не нуж­но знать, как именно ядро обрабатывает системные вызовы. Эта информация уже интегрирована в стандартные соглашения вызова, соблюдаемые в конкретной ар­хитектуре, и автоматически обрабатывается компилятором и библиотекой C.

Библиотека C (libc) — это сердце всех приложений UNIX. Даже если вы програм­мируете на другом языке, то библиотека C, скорее всего, при этом задействуется. Она обернута более высокоуровневыми библиотеками и предоставляет основные службы, а также способствует активации системных вызовов. В современных сис­темах Linux библиотека C предоставляется в форме GNUlibc, сокращенно glibc. Кроме реализации стандартной библиотеки C, glibc дает обертки для системных вызовов, поддерживает работу с потоками и основные функции приложений.

В Linux стандартный компилятор языка C предоставляется в форме коллекции компиляторов GNU (GNU Compiler Collection, сокращенно gcc). При этом gcc — это еще и двоичный файл, используемый для активации компилятора C.

При написании на C++ используют — стандартную библиотеку C++ и компилятор GNUC++. Стандартная библиотека C++ реализует системные интерфейсы C++ и использует стандарт ISOC++ 11. Он обеспечивается библиотекой libstdc++ (иногда используется название libstdcxx). Компилятор GNUC++ — это стандартный компилятор для кода на языке C++ в системах Linux. Он предоставляется в двоичном файле g++.

API и ABI

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

Обе эти концепции интерфейс программирования приложений (Application Programming Interface, API), и двоичным интерфейсом приложения (Application Binary Interface, ABI)яяя определяют и описывают интерфейсы между различными компонентами программного обеспечения.

API определяет интерфейсы, на которых происходит обмен информацией между двумя компонентами программного обеспечения на уровне исходного кода. API просто определяет интерфейс; тот компонент программы, который обеспечивает работу API, обычно называется реализацией этого API. API часто называют «контрактом». Это неверно как минимум в юридическом смысле этого слова, поскольку API не имеет ничего общего с двусторонним согла­шением. Пользователь может применять API «как есть» или не использовать его вообще: возьми или не трогай! Задача API — просто гарантировать, что, если оба компонен­та ПО воспользуются этим API, они будут совместимы на уровне исходного кода.

Если API определяет интерфейсы в исходном коде, то ABI предназначен для определения двоичного интерфейса между двумя и более программными ком­понентами в конкретной архитектуре. ABI тесно связаны с архитек­турой; абсолютное большинство ABI оперирует машинно-специфичными кон­цепциями. ABI определяет, как приложение взаи­модействует с самим собой, с ядром и библиотеками. В то время как API обес­печивает совместимость на уровне исходного кода, ABI отвечает за совместимость на двоичном уровне. Это означает, что фрагмент объектного кода будет функ­ционировать в любой системе с таким же ABI без необходимости перекомпи­ляции. ABI помогают решать проблемы, связанные с соглашениями на уровне вызовов, порядком следования байтов, использованием регистров, активацией системных вызовов, связыванием, поведением библиотек и форматом двоичных объектов.

Каталоги и ссылки

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

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

Hard link полезен, когда нужно иметь несколько имён для одних и тех же данных.

Жёсткая ссылка — это ещё одно имя того же самого файла.

➜  example echo "Hello" > file.txt
➜  example ln file.txt hard.txt
➜  example ls -li file.txt hard.txt
25570954 -rw-rw-r-- 2 jeka jeka 6 Sep 22 16:48 file.txt
25570954 -rw-rw-r-- 2 jeka jeka 6 Sep 22 16:48 hard.txt
file.txt ──┐
           ├──> inode 25570954 ──> данные "Hello"
hard.txt ──┘

Изменение через любое имя изменяет один и тот же файл:

➜  example echo "World" >> hard.txt
➜  example cat file.txt
Hello
World

Файл физически удаляется только когда счётчик hard links становится 0 и больше нет открытых ссылок на inode. Если удалить file.txt: rm file.txt то hard.txt продолжит работать, потому что данные файла всё ещё существуют. У inode остаётся одна ссылка.

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

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

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

Символическая ссылка это отдельный файл, внутри которого хранится путь к другому файлу. У link.txt свой inode.

➜  example echo "Hello" > file.txt
➜  example ln -s file.txt link.txt
➜  example ls -li                  
total 4
25570953 -rw-rw-r-- 1 jeka jeka 6 Sep 22 16:58 file.txt
25570972 lrwxrwxrwx 1 jeka jeka 8 Sep 22 16:59 link.txt -> file.txt

Если удалить оригинал: rm file.txt то ссылка становится битой (dangling symlink), потому что file.txt больше не существует.

Специальные файлы

Специальные файлы — это объекты ядра, представленные в виде файлов. В Linux поддерживается четыре типа таких файлов: файлы блочных устройств (жесткий диск), файлы символьных устройств (клавиатура), именованные каналы (named pipe) и доменные соке­ты UNIX (межпроцессное взаимодействия между двумя несвязанными процессами).

Файловые системы и пространства имен

В Linux поддерживаются самые разные файловые системы, решительно любые, которые могут встретиться среднему пользователю на практике. В частности, Linux поддерживает файловые системы, специфичные для определенных носителей (например, ISO9660), сетевые файловые системы (NFS), нативные файловые системы (ext4), файловые системы из других разно­видностей UNIX (XFS), а также файловые системы, не относящиеся к семейству UNIX (FAT).

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

Аналогично, наименьшим логически адресуемым элементом файловой системы является блок. Блок — это абстракция, применяемая в файловой системе, а не на физическом носителе, на котором эта система находится. Обычно размер блока равен степени двойки от размера сектора. В Linux блоки, как правило, крупнее сектора, но они должны быть меньше размера страницы. Размеры большинства блоков составляют 512 байт, 1 Кбайт и 4 Кбайт.


... дальше - просто изучайте язык C