2. Среда программирования UNIX
Unix предоставляет строго определенный ограниченный набор входов в ядро системы, через которые прикладные задачи могут воспользоваться услугами ядра. Эти точки входа называются системные вызовы
Системный вызов определяет функцию выполняемую ядром от имени процесса вызвавшего вызов и является интерфейсом самого низкого уровня взаимодействия прикладных процессов с ядром
Системные вызовы описываются в man (2)
Помимо системных вызовов есть еще и большой набор библиотечных функций общего назначения
Они как правило не являются точками входа в ядро, хотя многие из них являются лишь более удобными обертками вокруг системных вызовов
Системные вызовы и библиотечные функции отличаются еще и способом передачи процессу информации об ошибке
Сисколлы обычно во время ошибки возвращают -1
и устанавливают значение переменной errno
(файл errno.h содержит коды ошибок и их краткое описание)
Для дебагинга функций же скорее всего придется обратиться к справочнику, потому что коды у них разные и кратких описаний нет
Список кодов ошибок основных сисколлов стандартизован (хотя появляются новые вызовы и их толкования могут различаться)
Полный список можно найти тут
root@two:/usr/include/asm-generic# tail errno.h
/* for robust mutexes */
#define EOWNERDEAD 130 /* Owner died */
#define ENOTRECOVERABLE 131 /* State not recoverable */
#define ERFKILL 132 /* Operation not possible due to RF-kill */
#define EHWPOISON 133 /* Memory page has hardware error */
#endif
Или в /usr/include/errno.h
На схеме ниже описана процедура создания приложения
- Компиляция: файлы с текстами программы обрабатываются компилятором (gcc), параметры компиляции определяются в Makefile. Компилятор создает набор промежуточных объектых файлов
.o
- Все эти файлы связываются друг с другом и с библиотеками линковщиком (
ld
) в статическом или динамическом режиме
- В статическом режиме создается единый исполняемый файл в котором содержится весь необходимый для работы код
- В динамическом режиме подключаются разделяемые библиотеки (
.so
) и создается файл к которому все необходимые библиотеки будут подключаться во время выполнения
Виртуальная память процесса состоит из нескольких сегментов или областей памяти
Размер, содержимое и расположение сегментов определяется как самой программой так и ее форматом
Обычно используется ELF (executable and linking format)
При запуске программы из командной строки, программный интерпретатор автоматически устанавливает для нее три стандартных потока ввода вывода
Начальную ассоциацию этих потоков (их файловых дескрипторов) с конкретными устройствами (через которые работают пользователи) производит терминальный сервер (обычно getty)
Это позволяет программе не заботиться о поиске устройства пользователя который ее запустил
Основные способы завершения программы это возврат из функции main()
и вызов фукции exit()
Процесс может завершиться по независящим от него обстаятельствам (например получение сигнала функция exit будет вызвана ядром от имени процесса)
Для работы с файлами программа может воспользоваться системными вызовами ядра или библиотечными функциями
Понятно что библиотечные функции это надстройки над системными вызовами
При открытии файла, процессу возвращается файловый дескриптор. Дальнейшее взаимодействие с этим файлом будет происходить по этому дескриптору
Значение этого дескритора определяется минимальным свободным слотом в таблице дескрипторов процесса
У функции open есть флаги, много разных, один из интересных - O_NONBLOCK
Он позволяет сделать операции с файлом - неблокирующими
То есть при чтении/записи возврат будет происходить мгновенно
При чтении файла (например используется вызов read), считанные данные помещаются в буфер приложения, указатель на этот буфер передается аргументом вызову read
Вызов read, при успешном чтении, вернет количество действительно считанных байт, это число не обязательно должно совпадать с числом запрошенных к чтению байт и это несовпадение не является ошибкой
После завершения чтения, файловый указатель (который хранится в файловой таблице ядра) будет увеличен на число действительно считанных байт
При записи данных в файл, функции write передается дескритор (куда писать) и указатель на буфер в котором хранятся данные для записи
Функция pipe позволяет создать однонаправленный (симплексный) канал для обмена данными между двумя родственными процессами
Только родственные процессы могут получить доступ к одному и тому же каналу
Функция вернет два файловых дескриптора, первый для чтения, второй для записи
No Comments