Роберт Лав — 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)
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 остаётся одна ссылка.
Символьные ссылки (symbolic link, symlink)
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