Info
Content

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


На схеме ниже описана процедура создания приложения

IMG_1603.jpg

  1. Компиляция: файлы с текстами программы обрабатываются компилятором (gcc), параметры компиляции определяются в Makefile. Компилятор создает набор промежуточных объектых файлов .o
  2. Все эти файлы связываются друг с другом и с библиотеками линковщиком (ld) в статическом или динамическом режиме
  • В статическом режиме создается единый исполняемый файл в котором содержится весь необходимый для работы код
  • В динамическом режиме подключаются разделяемые библиотеки (.so) и создается файл к которому все необходимые библиотеки будут подключаться во время выполнения

Виртуальная память процесса состоит из нескольких сегментов или областей памяти
Размер, содержимое и расположение сегментов определяется как самой программой так и ее форматом
Обычно используется ELF (executable and linking format)

IMG_1604.jpg


При запуске программы из командной строки, программный интерпретатор автоматически устанавливает для нее три стандартных потока ввода вывода
Начальную ассоциацию этих потоков (их файловых дескрипторов) с конкретными устройствами (через которые работают пользователи) производит терминальный сервер (обычно getty)

Это позволяет программе не заботиться о поиске устройства пользователя который ее запустил

Основные способы завершения программы это возврат из функции main() и вызов фукции exit()
Процесс может завершиться по независящим от него обстаятельствам (например получение сигнала функция exit будет вызвана ядром от имени процесса)


Для работы с файлами программа может воспользоваться системными вызовами ядра или библиотечными функциями
Понятно что библиотечные функции это надстройки над системными вызовами

При открытии файла, процессу возвращается файловый дескриптор. Дальнейшее взаимодействие с этим файлом будет происходить по этому дескриптору
Значение этого дескритора определяется минимальным свободным слотом в таблице дескрипторов процесса

У функции open есть флаги, много разных, один из интересных - O_NONBLOCK
Он позволяет сделать операции с файлом - неблокирующими
То есть при чтении/записи возврат будет происходить мгновенно

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

При записи данных в файл, функции write передается дескритор (куда писать) и указатель на буфер в котором хранятся данные для записи

Функция pipe позволяет создать однонаправленный (симплексный) канал для обмена данными между двумя родственными процессами
Только родственные процессы могут получить доступ к одному и тому же каналу
Функция вернет два файловых дескриптора, первый для чтения, второй для записи

No Comments
Back to top