Unix
Операционная система UNIX
Записи по книге А.Робачевского, С.Немнюгина, О.Стесик
0. Введение
В центре архитектуры unix находится ядро, оно взаимодействует с аппаратной частью компьютера, изолируя прикладные программы от особенностей архитектуры
Ядро предоставляет прикладным программам услуги с помощью системных вызовов. Например работа с файлами, создание процессов и управление ими, межпроцессное взаимодействие итд
Вокруг ядра, используя его возможности, работают приложения
Процесс запрашивает услугу с помощью системного вызова (внешне похожего на обычный вызов библиотечной функции), ядро от имени процесса выполняет запрос и возвращает процессу результат
Вот тут видно как открывается файл, читается и закрывается:
root@mars:/var/tmp/test/test# cat test.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
main() {
int fd;
char buf[80];
fd = open("file", O_RDONLY);
read(fd, buf, sizeof(buf));
close(fd);
}
root@mars:/var/tmp/test/test# strace ./a.out |& tail -5
openat(AT_FDCWD, "file", O_RDONLY) = 3
read(3, "Mar 19 17:05:24 mars sshd[29845]"..., 80) = 80
close(3) = 0
exit_group(0) = ?
+++ exited with 0 +++
root@mars:/var/tmp/test/test#
Ядро состоит из трех основных систем:
- Файловая подсистема
- Подсистема управления процессами и памятью
- Подсистема ввода-вывода
Файловая подсистема дает унифицированный доступ к данным и переферийным устройствам. Также она контролирует права, выполняет размещение/удаление/запись/чтение
Большая часть прикладных функций выполняется через интерфейс ФС (в том числе доступ к периферийным устройствам), поэтому права на файлы определяют привилегии пользователя в системе
Подсистема управления процессами. Запущенные программы порождают процессы, эта подсистема контролирует и управляет ими, а также распределяет ресурсы между ними
Специальная задача ядра - scheduler, разрешает конфликты процессов в конкуренции за системные ресурсы
Модуль управления памятью обеспечивает размещение памяти для прикладных задач
Модуль межпроцессного взаимодействия уведомляет процессы о каких-либо событиях в системе и позволяет передавать данные между разными процессами
Подсистема ввода-вывода выполняет запросы двух предыдущих подсистем к периферийным устройствам. Она обеспечивает буферизацию и взаимодействует с драйверами
Драйверы это модули ядра которые обслуживают внешние устройства
1. Работа в операционной системе UNIX
Имя файла является атрибутом файловой системы, а не набора некоторых данных на диске, который не имеет имени как такового
Каждый файл имеет связанные с ним метаданные, которые хранятся в индексных дескрипторах - inode, и содержат в себе все характеристики файла, которые позволяют ОС работать с ним
В частности метаданные содержат указатели на дисковые блоки хранения данных файла
Имя файла является указателем на метаданные
В то время как метаданные никак не указывают на имя (односторонняя связь)
Отсюда становится понятно как работают хардлинки
Существует 6 типов файлов (от типа зависят возможные действия, назначение итд):
- обычный файл
- каталог
- специальный файл устройства
- именованный канал или fifo
- символическая связь
- сокет
Обычный файл для операционной системы это просто набор байтов, вся их обработка выполняется уровнем выше (прикладной программой)
Каталог это файл содержащий имена находящихся в нем файлов а так же указатели на матаднные этих файлов
Каталоги определяют положение файла в дереве ФС
Прочесть каталог могут многие, но записать в него может только ядро
Специальный файл устройства обеспечивает доступ к физическому устройству
Бывают символьные и блочные, Символьные используются для небуферизованного обмена, а блочные позволяют обмениваться пакетами фиксированной длины - блоками
FIFO позволяет передвать данные между процессами
Связь. Записи в каталогах связывают имя файла и метаданные (имя -> inode), метаданные связываются с блоками данных (inode -> blocks)
Соответственно на одну и ту же айноду может указывать несколько имен и один файл может иметь несколько имен
Файлу безразлично какие имена он имеет, но не безразлично их количество
Если у файла не осталось имен в файловой системе, то он должен быть удален
Это называется жесткой связью (hardlink)
root@mars:/var/tmp/test/test# touch file1
root@mars:/var/tmp/test/test# ln file1 file2
root@mars:/var/tmp/test/test# ls -li
total 0
4856290 -rw-r--r-- 2 root root 0 Mar 19 20:33 file1
4856290 -rw-r--r-- 2 root root 0 Mar 19 20:33 file2
root@mars:/var/tmp/test/test# touch otherfile
root@mars:/var/tmp/test/test# ls -li
total 0
4856290 -rw-r--r-- 2 root root 0 Mar 19 20:33 file1
4856290 -rw-r--r-- 2 root root 0 Mar 19 20:33 file2
4856297 -rw-r--r-- 1 root root 0 Mar 19 20:33 otherfile
root@mars:/var/tmp/test/test# echo 'test' > file1
root@mars:/var/tmp/test/test# cat file2
test
root@mars:/var/tmp/test/test#
Кроме одинаковых inode, выше виден еще и третий столбец (числа 2/1), он как раз и указывает на количество хардлинков
Символьная связь ведет себя иначе
Содержимое символьной ссылки это имя целевого файла
На примере видна логика работы
root@mars:/var/tmp/test/test/test# echo 'niga1' > file
root@mars:/var/tmp/test/test/test# mkdir test
root@mars:/var/tmp/test/test/test# cd test
root@mars:/var/tmp/test/test/test/test# echo 'niga2' > file
root@mars:/var/tmp/test/test/test/test# cd ..
root@mars:/var/tmp/test/test/test# tree
.
|-- file
`-- test
`-- file
1 directory, 2 files
# Подготовили площадку, имеем два файла с одинаковым именем но с разным содержимым
root@mars:/var/tmp/test/test/test# ln -s file link
# Делаем ссылку с относительным именем цели и тестим
root@mars:/var/tmp/test/test/test# cat link
niga1
root@mars:/var/tmp/test/test/test# mv link test/.
root@mars:/var/tmp/test/test/test# cat test/link
niga2
Символьная ссылка это особый тип файла
ОС ведет себя особым образом при работе с ним
Хардлинк имеет ограничение, он может существовать только в рамках одной ФС, тогда как символические ссылки могут быть cross file system
Сокеты предназначены для межпроцессного взаимодействия, в том числе и для процессов на разных компьютерах
Поэтому сокеты часто используются для доступа к сети
lost+found - как ранее говорилось, у файла должен быть минимум один хардлинк, иначе файл должен быть удален
Но "должен" не равно "будет"
Поэтому при ошибках остановки ОС или аппаратных сбоях могут возникать ситуации когда на диске есть правильная структура файла но для него отсутсвует имя в каком-либо каталоге
Системы проверки и восстановления ФС кладут такие файлы в lost+found
Один пользователь может быть членом нескольких групп, одна из которых будет первичной, а остальные дополнительными
При создании файла ему назначается владелец-пользователь и владелец-группа
Владелец-группа не обязательно должен быть первичной группой пользователя-создателя
Например в freebsd владелец-группа для файла наследуется от вышестоящего каталога
На самом деле файл создает не пользователь а процесс который пользователь породил
У этого процесса есть разные аттрибуты
Такие как UID, EUID, GID и EGID
От них как раз и зависит очень многое
Для бинарей чтобы запуститься достаточно иметь права на исполнение
А для скритов также требуется право на чтение
upd: не совсем
root@two:~# cat scr
#!/bin/bash
echo test
root@two:~# ls -l
total 4
-rw-r--r-- 1 root root 22 May 6 06:08 scr
root@two:~# . scr
test
root@two:~# chmod 100 scr
root@two:~# ls -l
total 4
---x------ 1 root root 22 May 6 06:08 scr
root@two:~# ./scr
test
Символьные ссылки вообще не имеют своих прав, они наследуют права от целевого файла
У каталогов права используются немного иначе
Право чтения каталога позволяет прочитать его, те получить список имен файлов внутри (только имена и только на один уровень вглубь) = ls
Право исполнения на каталоге позволит пройти внутрь и обратиться к метаданным файлов внутри каталога = ls -l
Чтобы зайти в папку не обязательно иметь права на ее чтение
macbook:a vandud$ pwd
/tmp/a
macbook:a vandud$ ls b
ls: b: Permission denied
macbook:a vandud$ tree
.
└── b [error opening dir]
1 directory, 0 files
macbook:a vandud$ cd b/c
macbook:c vandud$ ls -l
total 0
-rwx------ 1 vandud wheel 0 Mar 26 19:36 file
Как видно в примере выше можно перескочить папку
Достаточно лишь иметь право зайти в нее и не обязательно ее читать
Например когда ты заранее знаешь путь по которому тебе нужно оказаться
Биты x
и r
независимы
С помощью различных их комбинаций можно добиваться инетересных эффектов
macbook:test vandud$ mkdir a
macbook:test vandud$ chmod a-r+x a
macbook:test vandud$ touch a/file
macbook:test vandud$ ls -l a
ls: a: Permission denied
macbook:test vandud$ echo 'text' > a/file
macbook:test vandud$ cat a/file # <tab> тут не срабатывает
text
Чтобы удалить файл достаточно иметь право на запись в папку в которой он находится (не нужно иметь прав на сам файл)
Право на запись в каталог дает много полномочий
Такая проблема решается Sticky bit'ом на каталог
Даже если пользователь 'user' входит в группу 'group', он не сможет прочитать 'file', зато все остальные смогут
----rw-r-- 2 user group 0 Mar 19 20:33 file
Выше было про основные аттрибуты файлов
Но существуют также и дополнительные
Они выставляются так же командой chmod
, но используются другие буквы
Code | Name | Description |
---|---|---|
t | Sticky bit | Сохранить образ выполняемого файла в памяти после завершения, чтобы в следующий раз он запустился быстрее |
s | Set UID, SUID | Установить UID процесса при выполнении, позволяет изменить права пользователя на время выполнения файла |
s | Set GID, SGID | Установить GID процесса при выполнении |
l | Блокирование | Установить обязательное блокирование файла |
SUID и SGID следует высталять осторожно, потому что в процессе выполнения программа может запускать другие программы и права будут наследоваться
SUID и SGID позволяют запускать файл с правами владельца-пользователя или владельца-группы
i.dudin_pro@mon-test-vm-01y:/usr/bin$ ls -l passwd
-rwsr-xr-x 1 root root 54192 May 17 2017 passwd
passwd как раз работает за счет SUID. Потому что пароли хранятся в системном файле /etc/shadow и пользователям как-то нужно иметь возможно его изменять (чтобы изменить свой пароль в нем)
Блокирование позволяет устранить возможность конфликта когда две+ задачи работают с одним файлом
Sticky bit для каталога позволяет пользователю удалять только те файлы которыми он владеет или имеет права на запись (при наличие права на запись в каталог)
Так работает раздел /tmp, в него могут писать все, но удалить можно только свое
i.dudin_pro@mon-test-vm-01y:~/test$ ls -la
total 12
drwxr-xr-x 3 i.dudin_pro linux_users 4096 Mar 26 16:18 .
drwxr-xr-x 5 i.dudin_pro linux_users 4096 Mar 26 13:56 ..
drwxr-xr-x 2 i.dudin_pro linux_users 4096 Mar 26 16:18 dir
-rw-r--r-- 1 i.dudin_pro linux_users 0 Mar 26 16:18 file
i.dudin_pro@mon-test-vm-01y:~/test$ chmod +t dir/
i.dudin_pro@mon-test-vm-01y:~/test$ chmod +t file ### можно использовать букву 'a' или 'o' или никакую как в этой строке
i.dudin_pro@mon-test-vm-01y:~/test$ ls -l
total 4
drwxr-xr-t 2 i.dudin_pro linux_users 4096 Mar 26 16:18 dir
-rw-r--r-T 1 i.dudin_pro linux_users 0 Mar 26 16:18 file
i.dudin_pro@mon-test-vm-01y:~/test$ touch file2
i.dudin_pro@mon-test-vm-01y:~/test$ mkdir dir2
i.dudin_pro@mon-test-vm-01y:~/test$ ls -l
total 8
drwxr-xr-t 2 i.dudin_pro linux_users 4096 Mar 26 16:18 dir
drwxr-xr-x 2 i.dudin_pro linux_users 4096 Mar 26 16:19 dir2
-rw-r--r-T 1 i.dudin_pro linux_users 0 Mar 26 16:18 file
-rw-r--r-- 1 i.dudin_pro linux_users 0 Mar 26 16:19 file2
i.dudin_pro@mon-test-vm-01y:~/test$ chmod a+t file2
i.dudin_pro@mon-test-vm-01y:~/test$ chmod a+t dir2
i.dudin_pro@mon-test-vm-01y:~/test$ ls -l
total 8
drwxr-xr-t 2 i.dudin_pro linux_users 4096 Mar 26 16:18 dir
drwxr-xr-t 2 i.dudin_pro linux_users 4096 Mar 26 16:19 dir2
-rw-r--r-T 1 i.dudin_pro linux_users 0 Mar 26 16:18 file
-rw-r--r-T 1 i.dudin_pro linux_users 0 Mar 26 16:19 file2
Размер буквы указывает на включенность аттрибута x
-rw-r--r-T 1 i.dudin_pro linux_users 0 Mar 26 16:21 file3
-rw-r--r-t 1 i.dudin_pro linux_users 0 Mar 26 16:22 file4
- T — означает, что разрешения на выполнение отключены
- t — означает, что права на выполнение включены
При установке SGID на каталог, новые файлы в каталоге будут наследовать владельца-группу по владельцу-группе каталога
i.dudin_pro@mon-test-vm-01y:~/test/a$ ls -la
total 8
drwxr-sr-x 2 i.dudin_pro www-data 4096 Mar 26 13:53 . # видим что текущая директория имеет sgid
drwxr-xr-x 3 i.dudin_pro linux_users 4096 Mar 26 13:53 ..
i.dudin_pro@mon-test-vm-01y:~/test/a$ touch file # создаем файл
i.dudin_pro@mon-test-vm-01y:~/test/a$ ls -la
total 8
drwxr-sr-x 2 i.dudin_pro www-data 4096 Mar 26 14:12 .
drwxr-xr-x 3 i.dudin_pro linux_users 4096 Mar 26 13:53 ..
-rw-r--r-- 1 i.dudin_pro www-data 0 Mar 26 14:12 file # файл унаследовал группу от папки (пользователь-создатель имеет другую группу
SUID на каталог не ставится
Кроме обычных и дополнительных аттрибутов, файлы могут наделяться скрытыми аттрибутами, обусловленными конкретной реализацией файловой системы
В случае конфликта прав между между обычными и специфическими аттрибутами, решают специфические
i.dudin_pro@mon-test-vm-01y:~/test$ sudo chattr +i file
i.dudin_pro@mon-test-vm-01y:~/test$ lsattr file
----i--------e-- file
i.dudin_pro@mon-test-vm-01y:~/test$ sudo rm file
rm: cannot remove 'file': Operation not permitted # даже от рута не удаляется
i.dudin_pro@mon-test-vm-01y:~/test$ sudo chattr +a file
i.dudin_pro@mon-test-vm-01y:~/test$ lsattr file
-----a-------e-- file
i.dudin_pro@mon-test-vm-01y:~/test$ echo "test" > file # нельзя перезаписать
-bash: file: Operation not permitted
i.dudin_pro@mon-test-vm-01y:~/test$ echo "test" >> file # но можно дозаписать
i.dudin_pro@mon-test-vm-01y:~/test$ cat file
test
При настройке прав с помощью числовой комбинации можно использовать эти числа в качестве четвертого (самого левого) префикса
- 0 - setuid, setgid, sticky bits не установлены
- 1 - Установить sticky bit
- 2 - Установить setgid bit
- 3 - Установить setgid и sticky bits
- 4 - Установить setuid bit
- 5 - Установить setuid и sticky bits
- 6 - Установить setuid и setgid bit-ы
- 7 - setuid, setgid, sticky bits активированы
Для того чтобы программа могла быть запущена на выполнение, ОС сначала должна создать соответствующее окружение (среду выполнения) куда относятся ресурсы памяти, возможность доступа к устройствам и другие различные ресурсы
Это окружение называется процесс
Не следует отождествлять процесс и программу, потому что одна программа может породить множество процессов
Выполнение процесса заключается в точном следовании набору инструкций, который никогда не передает управление набору инструкций другого процесса
Процесс взаимодействует с разделом данных и стеком, но ему недоступны разделы данных и стеки других процессов
Чтобы процессы могли взаимодействовать между собой существует система межпроцессного взаимодействия
В нее входят сигналы, каналы, разделяемая память, семафоры, сообщения и файлы, но в остальном процессы изолированы друг от друга
Бывают системные демоны которые запускаются при инициализации ядра и нужны для работы ОС (они не имеют исполняемых файлов, их код является частью ядра)
Это разные демоны свопинга, демоны памяти, деспетчеры буферного кэша итд
Бывают просто демоны которые стртуют либо от рук пользователя либо автоматически после инициализации ядра (это разные web, ftp, ssh демоны)
Бывают прикладные процессы, они как правило интерактивные
Таким процессом является оболочка
Процессы имеют некоторые аттрибуты благодаря которым ОС может эффективно управлять ими
PID (Process ID) - уникальный в системе номер процесса позволяющий различать процессы. Новому процессу присваивается следующий свободный номер (присвоение происходит по возрастающей, т.е. чем новее процесс тем больше его pid)
Когда номера заканчиваются то новому процессу выдается минимальный из свободных (ведь при завершении процесса его pid освобождается) и все идет по второму кругу
PPID (Parent Process ID) - Pid родительского процесса
Nice Number - Относительный приоритет процесса учитываемый планировщиком при определнии очередности запуска (процессорные ресурсы распределяются исходя из приоритета выполнения который вычисляется динамически ядром и зависит в том числе от относительного приоритета)
TTY (Терминальная линия) - Терминал или псевдотерминал ассоциированный с процессом (демоны такого не имеют)
RID,EUID (Реальный и эффективный ID) - Реальный это id пользователя который запустил программу, а эффективный это id пользователя чьими правами наделен процесс. Обычно они эквивалентны, но euid можно изменить с помощью suid
RGID,EGID - То же самое что и выше но про группу
Новый процесс порождается с помощью системного вызова fork
Дочерний процесс является точной копией родительского
Дочерний отличается от родительского только pid'ом (ну и ppid'ом), все остальное (данные, инструкции, права и прочее) наследуется
Даже выполнение начнется с одного и того же момента
Далее выполняется вызов exec для загрузки кода новой программы в новый процесс и выполнения
Сигналы нужны для уведомления процессов о разных событиях
Например если процесс пытается поделить на ноль то ядро пошлет ему сигнал SIGFPE
А при нажатии Ctrl+C процесс получит сигнал SIGINT
Для отправления сигнала используется команда kill
root может слать сигналы кому угодно, а пользователь должен слать сигналы тем процессам владельцем которых он является
Должны совпадать RID/EUID процесса отправителя и процесса получателя
При получении сигнала процесс имеет три пути:
- Игнорирование сигнала (но так нельзя, потому что дальшейшая судьба процесса непредсказуема)
- Действие по умолчанию (обычно это завершение)
- Можно перехватить сигнал и самостоятельно обработать его. Например перехватив сигнал SIGINT процесс может удалить свои временные файлы и произвести прочие подготовительные работы перед смертью
Некоторые сигналы нельзя перехватить (SIGKILL, SIGSTOP)
Но иногда и после неперехватываемых сигналов процесс может не завершиться:
- Зомби (зомби это запись в таблице процессов, самого процесса уже нет. Поэтому в малых количествах они не представляют опасности, но опасно когда их много потому что они могут заполнить всю таблицу процессов)
- Процессы ожидающие недоступные ресурсы (можно послать SIGINT,SIGQUIT, должно помочь)
- Процессы ожидающие завершения операции с устройством (перемотка ленты)
Сигналы могут иметь специфичные для приложения значения
Например SIGHUP для named заставит его перечитать базу с диска
UNIX предоставляет единый интерфейс различных устройств в виде специальных файлов
Каждый файл соответствует физическому устройству или псевдоустройству
Такой файл связывает приложение с драйвером устройства
Существует два типа специальных файлов устройств
- Файлы блочных устройств
- Файлы символьных устройств
С блочными устройствами обмен происходит большими блоками данных (например диски), буферизация происходит на уровне ОС
С символьными устройствами обмен происходит побайтно и буферизацию предоставляет драйвер самого устройства
Вместо размера у специальных файлов устройств выводится мажор и минор
Эти числа указывают на драйвер (мажор) и конкретное устройство обслуживаемое этим драйвером (минор) (минор передается драйверу который находится по мажору)
Пользователь тоже имеет аттрибуты
Их можно увидеть в файле /etc/passwd
[19:39:16] vandud@macbook: vacuum-on-db-mon [0]$ grep -vE '^#' /etc/passwd | head
nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false
root:*:0:0:System Administrator:/var/root:/bin/sh
daemon:*:1:1:System Services:/var/root:/usr/bin/false
...
- name - логин пользователя
- passwd - закодированный пароль, обычно на месте этого аттрибута стоит 'x', а сам пароль хранится в /etc/shadow, в случае если в этом месте стоит '*' то пользователь не сможет зайти в систему, обычно такой пароль имеют псевдоюзеры
- UID - user id
- GID - id первичной группы пользователя
- comment - обычно это реальное имя пользователя
- homedir - хомяк
- shell - шелл
root имеет uid=0
root - это только имя, неограниченные полномочия имеет пользователь с uid=0 (то есть можно сменить root на что-то свое)
Аналогично с группой root или wheel которая имеет GID=0
Сегодня нормой считается создание персональной группы для каждого пользователя
Таким образом исключается случайное превышение полномочий за счет групповых прав
При включении терминала активируется программа getty, она является сервером терминального доступа и запускает программу login, которая после успешной аутентификации запускает программу из последнего столбца /etc/passwd (оболочку)
Начальное окружение устанавливается программой login
А именно:
- HOME - 6 поле из passwd
- LOGNAME - 1 поле из passwd
- PATH
- SHELL - 7 поле из passwd
- MAIL - /var/mail/LOGNAME
- TZ - определено системой
Экспорт переменных
[21:28:33] vandud@macbook: ~ [0]$ echo $TEST
[21:28:43] vandud@macbook: ~ [0]$ TEST=test
[21:28:48] vandud@macbook: ~ [0]$ echo $TEST
test
[21:28:51] vandud@macbook: ~ [0]$ bash -c 'echo $TEST'
[21:28:59] vandud@macbook: ~ [0]$ export TEST=test
[21:29:08] vandud@macbook: ~ [0]$ bash -c 'echo $TEST'
test
[21:29:11] vandud@macbook: ~ [0]$
Просто определнные переменные являются внутренними текущего экземпляра оболочки и не наследуются порожденными процессами
Чтобы они начали наследоваться нужно обозначить их как экспортируемые
Любая команда в оболочке относится к одной из трех категорий:
- Встроенные функции (builtins)
- Функции определенные пользователем
- Внешние программы и утилиты
Встроенные функции выполняются быстрее всего, потому что для их выполнения не требутся порождать новый процесс (новый процесс это дорого)
В bash есть функция exec
Она делает то что описано выше про системеный вызов exec
[14:22:09] vandud@macbook: ~ [0]$ ssh mon-test-vm-01y.g01.i-free.ru
i.dudin_pro@mon-test-vm-01y:~$ exec ps ### Вызываю ps через exec
PID TTY TIME CMD
2268 pts/0 00:00:00 ps ### Видим только ps
Connection to mon-test-vm-01y.g01.i-free.ru closed. ### И вылетаем (потому что ps завершился, а bash'a у нас нет)
[14:22:23] vandud@macbook: ~ [0]$ ssh mon-test-vm-01y.g01.i-free.ru
i.dudin_pro@mon-test-vm-01y:~$ ps ### То же самое но без exec
PID TTY TIME CMD
2295 pts/0 00:00:00 bash ### bash есть
2310 pts/0 00:00:00 ps
i.dudin_pro@mon-test-vm-01y:~$ ### И мы не вылетаем
У bash есть функция для определения действий которые будут выполнены при получении bash'ем сигналов
i.dudin_pro@mon-test-vm-01y:~$ trap 'echo signal recieved' 14
i.dudin_pro@mon-test-vm-01y:~$ ps
PID TTY TIME CMD
3687 pts/1 00:00:00 bash
3705 pts/1 00:00:00 ps
i.dudin_pro@mon-test-vm-01y:~$ kill -14 3687
signal recieved
i.dudin_pro@mon-test-vm-01y:~$
Функции в bash не особо отличаются от скриптов, в них так же предаются аргументы и внутри перечисляются команды
Но функции располагаются внутри памяти и поэтому работают быстрее
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 позволяет создать однонаправленный (симплексный) канал для обмена данными между двумя родственными процессами
Только родственные процессы могут получить доступ к одному и тому же каналу
Функция вернет два файловых дескриптора, первый для чтения, второй для записи