Что такое системный вызов
Перейти к содержимому

Что такое системный вызов

Что такое системный вызов

intro(2) введение в системные вызовы

ОПИСАНИЕ

В разделе 2 описываются системные вызовы Linux. Системный вызов — это точка входа в ядро Linux. Обычно, системные вызовы не запускаются напрямую, вместо этого почти все системные вызовы имеют соответствующие обёрточные функции в библиотеке C, которая выполняет некоторые действия (например, подготовку к работе в режиме ядра) для запуска системного вызова. Таким образом, работа с системным вызовом выглядит также как вызов обычной библиотечной функции. Во многих случаях обёрточная функция библиотеки C делает не более чем: * копирует аргументы и уникальный номер системного вызова в регистры, где ядро ожидает их найти, и затем; * передаёт управление в режим ядра в место, где ядро выполняет всю работу системным вызовом; * изменяет errno, если системный вызов вернул номер ошибки при возврате ядром ЦП в пользовательский режим. Однако в некоторых случаях обёрточная функция может делать дополнительную работу, например, выполнять предварительную обработку аргументов до перехода в режим ядра и обработку значений, полученных от системного вызова. В этом случае в справочных страницах раздела 2, обычно, приводятся подробности программного интерфейса библиотеки C (обычно GNU) и системного вызова. Чаще всего, в главном ОПИСАНИИ даётся интерфейс библиотеки C, а отличия системного вызова приводятся в разделе ЗАМЕЧАНИЯ. Список системных вызовов Linux можно найти в syscalls(2).

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ

В случае ошибки большинство системных вызовов возвращает отрицательное число (т.е., отрицательное значение одной из констант, описанных в errno(3)). Обёртка в библиотеке C скрывает эту информацию от вызывающей стороны: когда системный вызов возвращает отрицательное значение, обёртка копирует абсолютное значение в переменную errno и возвращает -1 в качестве результата своей работы. Значение результата успешной работы системного вызова зависит от вызова. Многие системные вызовы в этом случае возвращают 0, а некоторые могут вернуть ненулевое значение. Подробно об этом написано в справочной странице самого вызова. В некоторых случаях программист должен определить макрос тестирования свойств, чтобы получить объявление системного вызова из заголовочного файла, указанного в справочной странице в разделе СИНТАКСИС. (Где необходимо, эти макросы тестирования свойств должны быть определены перед включением какого-либо заголовочного файла.) В таких случаях требуемый макрос описан в справочной странице. Подробней о макросах тестирования свойств написано в feature_test_macros(7).

СООТВЕТСТВИЕ СТАНДАРТАМ

Для обозначение вариантов UNIX и разнообразных стандартов, которым соответствуют системные вызовы, описанные в этом разделе, используются различные сокращения. См. standards(7).

ЗАМЕЧАНИЯ

Непосредственный вызов

В большинстве случаев, не требуется запускать системный вызов напрямую, но иногда в стандартной библиотеке C может не оказаться нужной обёрточной функции. В этом случае программист должен вручную выполнить системный вызов с помощью syscall(2). Исторически, также осталось возможно сделать с помощью макросов _syscall, описанных в _syscall(2).

Авторы и условия распространения

Авторы и условия распространения указаны в заголовке исходной справочной страницы. Заметим, что у разных страниц они могут быть разными! Список русских переводчиков для каждой страницы можно найти в .po файле в исходном коде пакета.

ПЕРЕВОДЫ
СМОТРИТЕ ТАКЖЕ
  • _syscall (2)
  • syscall (2)
  • syscalls (2)
  • errno (3)
  • intro (3)
  • capabilities (7)
  • credentials (7)
  • feature_test_macros (7)
  • mq_overview (7)
  • path_resolution (7)
  • pipe (7)
  • pty (7)
  • sem_overview (7)
  • shm_overview (7)
  • signal (7)
  • socket (7)
  • standards (7)
  • svipc (7)
  • symlink (7)
  • time (7)
ПОСЛЕДНИЕ ЗАПРОСЫ
  • syscalls (2)
  • CLUSTER (7)
  • dvipng (1)
  • TAP::Formatter::Console (3)
  • Encode::MIME::EncWords (3)
  • md4 (1)
  • aepanel (1)
  • irk (1)
  • rsync (1)
  • QTextDecoder (3)
  • read_random (9)

Unix2019b/Системные вызовы

При программировании на C мы используем средства стандартной библиотеки языка, такие как тип FILE*, функции fopen(), fread(), fwrite(), fclose(). Эти функции кроссплатформенные и основываются внутри на более низкоуровневых функциях.

Например, C-функция fread и fwrite:

#include size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

На POSIX-совместимых системах (в том числе Linux) она сводится к функции

#include ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count);

На Windows она сводится к функциям из WinAPI:

#include BOOL ReadFile( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped ); BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped );

Если использовать только функции из библиотеки C, то можно писать переносимые программы, которые будут работать под разными ОС. Но если использовать API операционной системы, то можно получить больше возможностей и несколько лучшую производительность за счёт меньшего числа копирований данных в промежуточные буферы.

Системные вызовы

Как же устроены те самые POSIX-функции read() и write()?

Они реализуются посредством системных вызовов. Системный вызов (англ. system call) — обращение прикладной программы к ядру операционной системы для выполнения какой-либо операции.

AppInVat.png

Современные операционные системы предусматривают разделение полномочий, препятствующее обращению исполняемых программ к данным других программ и оборудованию. Ядро ОС исполняется в привилегированном режиме работы процессора. Для выполнения межпроцессной операции или операции, требующей доступа к оборудованию, программа обращается к ядру, которое, в зависимости от полномочий вызывающего процесса, исполняет либо отказывает в исполнении такого вызова.

Syscall.png

Обычное пользовательское приложение работает в непривилегированном режиме в своём виртуальном адресном пространстве. В обычном случае для выполнения вычислительной работы, для доступа к памяти и пр. не требуются системные вызовы. Например, функции библиотеки C, такие как strlen() и memcpy(), не имеют ничего общего с ядром и всегда выполняются целиком в приложении. Однако такие функции, как malloc() и printf(), могут делать внутри системные вызовы.

Набор системных вызовов разный в разных операционных системах. Итого в ядре Linux около 310 системных вызовов. С ними можно познакомиться в таблице. Для сравнения, в ОС Windows системных вызовов около 460.

GNU C Library

Когда мы компилируем обычную программу на C под Linux, она автоматически линкуется с библиотекой glibc.

Библиотека GNU C Library (часто используется название glibc) — это вариант реализации стандартной библиотеки С от проекта GNU. Является одним из основных компонентов операционной системы GNU/Linux.

Реализует как стандартные C-функции типа malloc(), strcpy(), fopen() (они являются частью стандарта языка программирования C и доступны на всех платформах), так и POSIX-функции типа getpid(), open() (эти функции не входят в стандарт C и, как правило, скажем, под Windows не реализованы).

Библиотека GNU C Library предоставляет программисту удобный интерфейс для работы с ОС в виде интерфейсных функций. Многие функции в libc являются тонкими обёртками над системными вызовами. Однако не каждая POSIX-функция является системным вызовом. Так и наоборот, не для каждого системного вызова есть соответствующая C-функция.

Размещение

Библиотека libc является одним файлом (динамическая so и статическая a), размещается в каталоге /usr/lib. Кроме того, в состав glibc (GNU libc) входят ещё несколько библиотек:

  • libm — математическая библиотека (там реализованы функции вида sin(), cos(). )
  • libpthread — POSIX Threads — библиотека для работы с потоками (мы обратимся к ней на следующих занятиях)

Всего функций в glibc много, мы рассмотрим только несколько.

Пример: fwrite

Вот шаги, которые включает в себя вызов C-функции fwrite:

Image002.png

  1. fwrite вместе с остальной частью стандартной библиотеки C реализован в glibc.
  2. fwrite вызывает более низкоуровневую функцию write.
  3. write загружает идентификатор системного вызова (который равен 1 для write) и аргументы в регистры процессора, а затем заставит процессор переключиться на уровень ядра. То, как это делается, зависит от архитектуры процессора, а иногда и от модели процессора. Например, процессоры x86 обычно вызывают прерывание 80, а процессоры x86-64 используют инструкцию процессора syscall.
  4. Процессор, который теперь работает в режиме ядра, передает идентификатор системного вызова в таблицу системных вызовов, извлекает указатель функции со смещением 1 и вызывает функцию. Эта функция, sys_write, является реализацией записи в файл.

Как работают системные вызовы

Из пользовательского пространства (ring 3) нельзя просто так вызвать функцию из ядра (ring 0), как обычную функцию. На шаге №3 в предыдущем примере используется тот или иной механизм перехода в режим ядра в зависимости от архитектуры компьютера. На компьютерах самой популярной архитектуры x86 для системный вызов делается тем или иным методом:

  • через программное прерывание,
  • через инструкцию sysenter,
  • через инструкцию syscall.

Программное прерывание

Прерывания (англ. interrupts) — это как бы сигнал процессору, что надо прервать выполнение (их поэтому и назвали прерываниями) текущего кода и срочно сделать то, что указано в обработчике.

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

Программное прерывание — синхронное прерывание, которое может осуществить программа с помощью специальной инструкции.

В процессорах архитектуры x86 для явного вызова синхронного прерывания имеется инструкция int, аргументом которой является номер прерывания (от 0 до 255). В защищённом и длинном режиме обычные программы не могут обслуживать прерывания, эта функция доступна только системному коду (операционной системе).

В ОС Linux номер прерывания 0x80 (в десятичной системе — 128) используется для выполнения системных вызовов. Обработчиком прерывания 0x80 является ядро Linux. Программа перед выполнением прерывания помещает в регистр eax номер системного вызова, который нужно выполнить. Когда управление переходит в ring 0, то ядро считывает этот номер и вызывает нужную функцию.

Метод этот широко применялся на 32-битных системах, на 64-битных он считается устаревшим и не применяется, но тоже работает, хотя с целым рядом ограничений (например, нельзя в качестве параметра передать 64-битный указатель).

  • Поместить номер системного вызова в eax.
  • Поместить аргументы в регистры ebx, ecx, edx, esi, edi, ebp.
  • Вызвать инструкцию int 0x80.
  • Получить результат из eax.

Пример реализации mygetpid() (получение PID текущего процесса) на ассемблере (для системного вызова getpid используется номер 20):

.intel_syntax noprefix .globl mygetpid .text mygetpid: mov eax, 20 int 0x80 ret

Инструкция sysenter

Спустя некоторое время, ещё когда не было x86-64, в Intel поняли, что можно ускорить системные вызовы, если создать специальную инструкцию системного вызова, тем самым минуя некоторые издержки прерывания. Ускорение достигается за счёт того, что на аппаратном уровне при выполнении инструкции sysenter опускается множество проверок на валидность дескрипторов, а так же проверок, зависящих от уровня привилегий.

На сегодня эти инструкции (sysenter и sysexit) поддерживаются процессорами Intel в 32- и 64-битных режимах, процессорами AMD — только в 32-битном (на 64-битном приводит к исключению неизвестного опкода).

Поскольку 32-битные архитектуры теряют популярность, рассматривать не будем.

Инструкция syscall

Так как именно AMD разработали x86-64 архитектуру, которая и называется AMD64, то они решили создать свою собственную инструкцию для системных вызовов.

Эти инструкции (syscall и парная sysret) поддерживаются процессорами Intel только в 64-битном режиме, процессорами AMD — во всех режимах.

Системные вызовы при помощи этой инструкции делаются в современных версиях 64-битного Linux.

  • Номер системного вызова помещается в rax.
  • Аргументы записываются в rdi, rsi, rdx, r10, r8 и r9.
  • Затем вызывается syscall.
  • Когда управление возвращается, результат находится в rax.
  • Значения всех регистров, кроме r11, rcx и rax, системным вызовом не изменяются, дополнительно сохранять их не требуется.

Пример реализации mygetpid() (получение PID текущего процесса) на ассемблере (для системного вызова getpid по таблице используется номер 39):

.intel_syntax noprefix .globl mygetpid .text mygetpid: mov rax, 39 syscall ret

Производительность

Системные вызовы требуют переключения контекста и перехода процессора в режим с высоким уровнем привилегий. Поэтому системный вызов выполняется относительно медленно по сравнению с вызовом обычной C-функции. Ещё хуже стало после обнаружения уязвимости Meltdown: патч KPTI (kernel page-table isolation), помогающий против уязвимости, приводит к сбросу TLB-кешей и дополнительному падению производительности.

Ориентировочные цифры, сколько занимает системный вызов на конкретном процессоре [1]:

  • int 80h — 500 нс,
  • sysenter — 340 нс,
  • syscall — 280 нс,
  • патч KPTI увеличивает эти числа на 180 нс,
  • вызов обычной C-функции — единицы нс.

Есть приёмы под названием vsyscall (уже устарел) и vDSO (virtual dynamic shared object) [2], которые позволяют в некоторых случаях избежать переключения контекста и ускорить выполнение. Помогает для системных вызовов, которым реально не нужны высокие привилегии, например gettimeofday. Удобно, если надо часто получать таймстемпы, например, для логов.

Мониторинг системных вызовов

Существует несколько инструментов, которые можно использовать для просмотра системных вызовов, которые выполняются программами. Самый известный из них, strace, доступен во многих операционных системах, и, вероятно, он уже установлен на вашем компьютере.

strace может запустить новый процесс или подключиться к уже запущенному. Вы можете многое узнать, подглядывая за системными вызовами, сделанными различными программами.

Использование в коде на C

Заголовочный файл unistd.h — основной, его наличие и содержимое обеспечивается стандартом POSIX.1. Видимо, «uni» пошло от UNIX.

Типы данных

Примеры типов данных POSIX (тем не менее, они не являются стандартными типами в языке C).

  • pid_t — идентификаторы процессов.
  • ssize_t — аналогичен size_t, но обязан быть знаковым и, главное, обязан уметь хранить минус единицу (это число используется как возвращаемое значение в случае ошибки во многих функциях).
  • off_t и off64_t — знаковый тип для хранения смещения в файле.

Получение справки

Сигнатуры функций и информацию об использовании можно почерпнуть из man-страниц:

man 2 read man 3 printf

Бонус: пишем «Hello, world!» на ассемблере

.intel_syntax noprefix .text .globl _start _start: # write(1, message, 14) mov rax, 1 # system call 1 is write mov rdi, 1 # file handle 1 is stdout lea rsi, [rip+msg] # address of string to output mov rdx, 14 # number of bytes syscall # invoke operating system to do the write # exit(0) mov rax, 60 # system call 60 is exit mov rdi, 0 # we want return code 0 syscall # invoke operating system to exit .data msg: .ascii "Hello, world!\n"
$ gcc main.s -nostdlib $ ./a.out Hello, world!

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

$ strace ./a.out > /dev/null execve("./a.out", ["./a.out"], 0x7fffd263a9b0 /* 19 vars */) = 0 brk(NULL) = 0x7fffd2101000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8595c50000 arch_prctl(ARCH_SET_FS, 0x7f8595c50a80) = 0 mprotect(0x7f8596000000, 4096, PROT_READ) = 0 write(1, "Hello, world!\n\0", 14) = 14 exit(0) = ? +++ exited with 0 +++

Литература

  • System Calls Make the World Go Round
  • Creating a vDSO: the Colonel’s Other Chicken
  • Эволюция системных вызовов архитектуры x86

Что такое системный вызов

Обычно, системные вызовы не вызываются напрямую, это делается через обёрточную функцию из glibc (или другой библиотеки). Подробней о непосредственном вызове системного вызова, см. intro(2). Часто, но не всегда, имя обёрточной функции совпадает с именем системного вызова, который она осуществляет. Например, в glibc есть функция truncate(), которая осуществляет делающий всё работу системный вызов «truncate». Часто, обёрточная функция glibc очень маленькая, она просто копирует аргументы в нужные регистры перед запуском системного вызова, а затем присваивает переменной errno значение, которое было возвращено системным вызовом. (Эти те же шаги выполняет syscall(2), её можно использовать для осуществления системных вызовов, для которых нет обёрточных функций.) Замечание: системные вызовы указывают, что произошла ошибка возвращая отрицательное целое число вызывающей стороне; когда это происходит, обёрточная функция меняет знак у возвращённого значения (на положительный), копирует его в errno и возвращает -1 вызвавшей обёртку функции. Иногда, однако, обёрточная функция производит дополнительную работу до осуществления системного вызова. Например, в настоящее время существует (по причинам, описанным далее) два похожих системных вызова — truncate(2) и truncate64(2); обёрточная функция glibc truncate() проверяет какой из системных вызовов предоставляет ядро и решает какой нужно задействовать.

Список системных вызовов

Далее приведён список список системных вызовов Linux. В колонке Ядро указана версия ядра для системных вызовов, которые появились в Linux 2.2, и с какой именно версии. Также заметим следующее: * Если версия ядра не указана, то системный вызов появился в ядре 1.0 или более раннем. * Системные вызовы, помеченные «1.2», скорее всего появились в в версиях ядра 1.1.x, и впервые появились в стабильном ядре 1.2. (Разработка ядра 1.2 началась с ответвления от ядра 1.0.6 и прошла несколько выпусков ядер 1.1.x.) * Системные вызовы, помеченные «1.2», скорее всего появились в в версиях ядра 1.3.x, и впервые появились в стабильном ядре 2.0. (Разработка ядра 2.0 началась с ответвления от ядра 1.2.x, начиная, приблизительно, с 1.2.10, и прошла несколько выпусков ядер 1.3.x.) * Системные вызовы, помеченные «2.2», скорее всего появились в в версиях ядра 2.1.x, и впервые появились в стабильном ядре 2.2.0. (Разработка ядра 2.2 началась с ответвления от ядра 2.0.21, и прошла несколько выпусков ядер 2.1.x.) * Системные вызовы, помеченные «2.4», скорее всего появились в в версиях ядра 2.3.x, и впервые появились в стабильном ядре 2.4.0. (Разработка ядра 2.4 началась с ответвления от ядра 2.2.8, и прошла несколько выпусков ядер 2.3.x.) * Системные вызовы, помеченные «2.6», скорее всего появились в в версиях ядра 2.5.x, и впервые появились в стабильном ядре 2.6.0. (Разработка ядра 2.6 началась с ответвления от ядра 2.4.15, и прошла несколько выпусков ядер 2.5.x.) * Начиная с ядра 2.6.0 порядок разработки был изменён, и новые системные вызовы могут появляться в каждом выпуске 2.6.x. В этом случае для системного вызова указан точный номер версии. 2.6.39. Это соглашение продолжает действовать и в ядрах серии 3.x, которая началась с ядра 2.6.39, и в ядрах серии 4.х, которая началась с ядра 3.19. * Иногда системный вызов, добавленный в текущую на тот момент стабильную ветвь ядра, переносился в предыдущие стабильные ветви ядра. Например, некоторые системные вызовы, которые появились в в 2.6.x были перенесены и в выпуски 2.4.x, начиная с 2.4.15. Если это производилось, будут показаны основные версии обеих ветвей ядра. Список системных вызовов, доступных в ядре версии 4.4 (или, в некоторых случаях, только в более старых ядрах):

Системный вызов Ядро Примечания
_llseek(2) 1.2
_newselect(2) 2.0
_sysctl(2) 2.0
accept(2) 2.0 смотрите замечания по socketcall(2)
accept4(2) 2.6.28
access(2) 1.0
acct(2) 1.0
add_key(2) 2.6.11
adjtimex(2) 1.0
alarm(2) 1.0
alloc_hugepages(2) 2.5.36 удалён в 2.5.44
bdflush(2) 1.2 устарел (ничего не делает)
начиная с 2.6
bind(2) 2.0 смотрите замечания по socketcall(2)
bpf(2) 3.18
brk(2) 1.0
cacheflush(2) 1.2 не для x86
capget(2) 2.2
capset(2) 2.2
chdir(2) 1.0
chmod(2) 1.0
chown(2) 2.2 Смотрите в chown(2)
подробности по версии
chown32(2) 2.4
chroot(2) 1.0
clock_adjtime(2) 2.6.39
clock_getres(2) 2.6
clock_gettime(2) 2.6
clock_nanosleep(2) 2.6
clock_settime(2) 2.6
clone(2) 1.0
close(2) 1.0
connect(2) 2.0 0
creat(2) 1.0
create_module(2) 1.0 удалён в 2.6
delete_module(2) 1.0
dup(2) 1.0
dup2(2) 1.0
dup3(2) 2.6.27
epoll_create(2) 2.6
epoll_create1(2) 2.6.27
epoll_ctl(2) 2.6
epoll_pwait(2) 2.6.19
epoll_wait(2) 2.6
eventfd(2) 2.6.22
eventfd2(2) 2.6.27
execve(2) 1.0
execveat(2) 3.19
exit(2) 1.0
exit_group(2) 2.6
faccessat(2) 2.6.16
fadvise64(2) 2.6
fadvise64_64(2) 2.6
fallocate(2) 2.6.23
fanotify_init(2) 2.6.37
fanotify_mark(2) 2.6.37
fchdir(2) 1.0
fchmod(2) 1.0
fchmodat(2) 2.6.16
fchown(2) 1.0
fchown32(2) 2.4
fchownat(2) 2.6.16
fcntl(2) 1.0
fcntl64(2) 2.4
fdatasync(2) 2.0
fgetxattr(2) 2.6; 2.4.18
finit_module(2) 3.8
flistxattr(2) 2.6; 2.4.18
flock(2) 2.0
fork(2) 1.0
free_hugepages(2) 2.5.36 удалён в 2.5.44
fremovexattr(2) 2.6; 2.4.18
fsetxattr(2) 2.6; 2.4.18
fstat(2) 1.0
fstat64(2) 2.4
fstatat64(2) 2.6.16
fstatfs(2) 1.0
fstatfs64(2) 2.6
fsync(2) 1.0
ftruncate(2) 1.0
ftruncate64(2) 2.4
futex(2) 2.6
futimesat(2) 2.6.16
get_kernel_syms(2) 1.0 удалён в 2.6
get_mempolicy(2) 2.6.6
get_robust_list(2) 2.6.17
get_thread_area(2) 2.6
getcpu(2) 2.6.19
getcwd(2) 2.2
getdents(2) 2.0
getdents64(2) 2.4
getegid(2) 1.0
getegid32(2) 2.4
geteuid(2) 1.0
geteuid32(2) 2.4
getgid(2) 1.0
getgid32(2) 2.4
getgroups(2) 1.0
getgroups32(2) 2.4
getitimer(2) 1.0
getpeername(2) 2.0 смотрите замечания по socketcall(2)
getpagesize(2) 2.0 не для x86
getpgid(2) 1.0
getpgrp(2) 1.0
getpid(2) 1.0
getppid(2) 1.0
getpriority(2) 1.0
getrandom(2) 3.17
getresgid(2) 2.2
getresgid32(2) 2.4
getresuid(2) 2.2
getresuid32(2) 2.4
getrlimit(2) 1.0
getrusage(2) 1.0
getsid(2) 2.0
getsockname(2) 2.0 смотрите замечания по socketcall(2)
getsockopt(2) 2.0 смотрите замечания по socketcall(2)
gettid(2) 2.4.11
gettimeofday(2) 1.0
getuid(2) 1.0
getuid32(2) 2.4
getxattr(2) 2.6; 2.4.18
init_module(2) 1.0
inotify_add_watch(2) 2.6.13
inotify_init(2) 2.6.13
inotify_init1(2) 2.6.27
inotify_rm_watch(2) 2.6.13
io_cancel(2) 2.6
io_destroy(2) 2.6
io_getevents(2) 2.6
io_setup(2) 2.6
io_submit(2) 2.6
ioctl(2) 1.0
ioperm(2) 1.0
iopl(2) 1.0
ioprio_get(2) 2.6.13
ioprio_set(2) 2.6.13
ipc(2) 1.0
kcmp(2) 3.5
kern_features(2) 3.7 Sparc64
kexec_file_load(2) 3.17
kexec_load(2) 2.6.13
keyctl(2) 2.6.11
kill(2) 1.0
lchown(2) 1.0 Смотрите в chown(2)
подробности по версии
lchown32(2) 2.4
lgetxattr(2) 2.6; 2.4.18
link(2) 1.0
linkat(2) 2.6.16
listen(2) 2.0 смотрите замечания по socketcall(2)
listxattr(2) 2.6; 2.4.18
llistxattr(2) 2.6; 2.4.18
lookup_dcookie(2) 2.6
lremovexattr(2) 2.6; 2.4.18
lseek(2) 1.0
lsetxattr(2) 2.6; 2.4.18
lstat(2) 1.0
lstat64(2) 2.4
madvise(2) 2.4
mbind(2) 2.6.6
memfd_create(2) 3.17
migrate_pages(2) 2.6.16
mincore(2) 2.4
mkdir(2) 1.0
mkdirat(2) 2.6.16
mknod(2) 1.0
mknodat(2) 2.6.16
mlock(2) 2.0
mlock2(2) 4.4
mlockall(2) 2.0
mmap(2) 1.0
mmap2(2) 2.4
modify_ldt(2) 1.0
mount(2) 1.0
move_pages(2) 2.6.18
mprotect(2) 1.0
mq_getsetattr(2) 2.6.6
mq_notify(2) 2.6.6
mq_open(2) 2.6.6
mq_timedreceive(2) 2.6.6
mq_timedsend(2) 2.6.6
mq_unlink(2) 2.6.6
mremap(2) 2.0
msgctl(2) 2.0 смотрите замечания по ipc(2)
msgget(2) 2.0 смотрите замечания по ipc(2)
msgrcv(2) 2.0 смотрите замечания по ipc(2)
msgsnd(2) 2.0 смотрите замечания по ipc(2)
msync(2) 2.0
munlock(2) 2.0
munlockall(2) 2.0
munmap(2) 1.0
name_to_handle_at(2) 2.6.39
nanosleep(2) 2.0
nfsservctl(2) 2.2 удалён в 3.1
nice(2) 1.0
oldfstat(2) 1.0
oldlstat(2) 1.0
oldolduname(2) 1.0
oldstat(2) 1.0
olduname(2) 1.0
open(2) 1.0
open_by_handle_at(2) 2.6.39
openat(2) 2.6.16
pause(2) 1.0
pciconfig_iobase(2) 2.2.15; 2.4 не в x86
pciconfig_read(2) 2.0.26; 2.2 не в x86
pciconfig_write(2) 2.0.26; 2.2 не в x86
perf_event_open(2) 2.6.31 Был perf_counter_open() в
2.6.31; в 2.6.32 переименовано
personality(2) 1.2
perfctr(2) 2.2 Sparc; удалён в 2.6.34
perfmonctl(2) 2.4 ia64
pipe(2) 1.0
pipe2(2) 2.6.27
pivot_root(2) 2.4
poll(2) 2.0.36; 2.2
ppc_rtas(2) 2.6.2 Только для PowerPC
ppoll(2) 2.6.16
prctl(2) 2.2
pread64(2) добавлен под именем «pread» в 2.2
переименован в «pread64» в 2.6
preadv(2) 2.6.30
prlimit64(2) 2.6.36
process_vm_readv(2) 3.2
process_vm_writev(2) 3.2
pselect6(2) 2.6.16
ptrace(2) 1.0
pwrite64(2) добавлен под именем «pwrite» в 2.2
переименован в «pwrite64» в 2.6
pwritev(2) 2.6.30
query_module(2) 2.2 удалён в 2.6
quotactl(2) 1.0
read(2) 1.0
readahead(2) 2.4.13
readdir(2) 1.0
readlink(2) 1.0
readlinkat(2) 2.6.16
readv(2) 2.0
reboot(2) 1.0
recv(2) 2.0 смотрите замечания по socketcall(2)
recvfrom(2) 2.0 смотрите замечания по socketcall(2)
recvmsg(2) 2.0 смотрите замечания по socketcall(2)
recvmmsg(2) 2.6.33
remap_file_pages(2) 2.6 устарел начиная с 3.16
removexattr(2) 2.6; 2.4.18
rename(2) 1.0
renameat(2) 2.6.16
renameat2(2) 3.15
request_key(2) 2.6.11
restart_syscall(2) 2.6
rmdir(2) 1.0
rt_sigaction(2) 2.2
rt_sigpending(2) 2.2
rt_sigprocmask(2) 2.2
rt_sigqueueinfo(2) 2.2
rt_sigreturn(2) 2.2
rt_sigsuspend(2) 2.2
rt_sigtimedwait(2) 2.2
rt_tgsigqueueinfo(2) 2.6.31
s390_runtime_instr(2) 3.7 только для s390
s390_pci_mmio_read(2) 3.19 только для s390
s390_pci_mmio_write(2) 3.19 только для s390
sched_get_priority_max(2) 2.0
sched_get_priority_min(2) 2.0
sched_getaffinity(2) 2.6
sched_getattr(2) 3.14
sched_getparam(2) 2.0
sched_getscheduler(2) 2.0
sched_rr_get_interval(2) 2.0
sched_setaffinity(2) 2.6
sched_setattr(2) 3.14
sched_setparam(2) 2.0
sched_setscheduler(2) 2.0
sched_yield(2) 2.0
seccomp(2) 3.17
select(2) 1.0
semctl(2) 2.0 смотрите замечания по ipc(2)
semget(2) 2.0 смотрите замечания по ipc(2)
semop(2) 2.0 смотрите замечания по ipc(2)
semtimedop(2) 2.6; 2.4.22
send(2) 2.0 смотрите замечания по socketcall(2)
sendfile(2) 2.2
sendfile64(2) 2.6; 2.4.19
sendmmsg(2) 3.0
sendmsg(2) 2.0 смотрите замечания по socketcall(2)
sendto(2) 2.0 смотрите замечания по socketcall(2)
set_mempolicy(2) 2.6.6
set_robust_list(2) 2.6.17
set_thread_area(2) 2.6
set_tid_address(2) 2.6
setdomainname(2) 1.0
setfsgid(2) 1.2
setfsgid32(2) 2.4
setfsuid(2) 1.2
setfsuid32(2) 2.4
setgid(2) 1.0
setgid32(2) 2.4
setgroups(2) 1.0
setgroups32(2) 2.4
sethostname(2) 1.0
setitimer(2) 1.0
setns(2) 3.0
setpgid(2) 1.0
setpriority(2) 1.0
setregid(2) 1.0
setregid32(2) 2.4
setresgid(2) 2.2
setresgid32(2) 2.4
setresuid(2) 2.2
setresuid32(2) 2.4
setreuid(2) 1.0
setreuid32(2) 2.4
setrlimit(2) 1.0
setsid(2) 1.0
setsockopt(2) 2.0 смотрите замечания по socketcall(2)
settimeofday(2) 1.0
setuid(2) 1.0
setuid32(2) 2.4
setup(2) 1.0 удалён в 2.2
setxattr(2) 2.6; 2.4.18
sgetmask(2) 1.0
shmat(2) 2.0 смотрите замечания по ipc(2)
shmctl(2) 2.0 смотрите замечания по ipc(2)
shmdt(2) 2.0 смотрите замечания по ipc(2)
shmget(2) 2.0 смотрите замечания по ipc(2)
shutdown(2) 2.0 смотрите замечания по socketcall(2)
sigaction(2) 1.0
sigaltstack(2) 2.2
signal(2) 1.0
signalfd(2) 2.6.22
signalfd4(2) 2.6.27
sigpending(2) 1.0
sigprocmask(2) 1.0
sigreturn(2) 1.0
sigsuspend(2) 1.0
socket(2) 2.0 смотрите замечания по socketcall(2)
socketcall(2) 1.0
socketpair(2) 2.0 смотрите замечания по socketcall(2)
splice(2) 2.6.17
spu_create(2) 2.6.16 только для PowerPC
spu_run(2) 2.6.16 только для PowerPC
ssetmask(2) 1.0
stat(2) 1.0
stat64(2) 2.4
statfs(2) 1.0
statfs64(2) 2.6
stime(2) 1.0
subpage_prot(2) 2.6.25 только для PowerPC
swapoff(2) 1.0
swapon(2) 1.0
symlink(2) 1.0
symlinkat(2) 2.6.16
sync(2) 1.0
sync_file_range(2) 2.6.17
sync_file_range2(2) 2.6.22
syncfs(2) 2.6.39
sysfs(2) 1.2
sysinfo(2) 1.0
syslog(2) 1.0
tee(2) 2.6.17
tgkill(2) 2.6
time(2) 1.0
timer_create(2) 2.6
timer_delete(2) 2.6
timer_getoverrun(2) 2.6
timer_gettime(2) 2.6
timer_settime(2) 2.6
timerfd_create(2) 2.6.25
timerfd_gettime(2) 2.6.25
timerfd_settime(2) 2.6.25
times(2) 1.0
tkill(2) 2.6; 2.4.22
truncate(2) 1.0
truncate64(2) 2.4
ugetrlimit(2) 2.4
umask(2) 1.0
umount(2) 1.0
umount2(2) 2.2
uname(2) 1.0
unlink(2) 1.0
unlinkat(2) 2.6.16
unshare(2) 2.6.16
uselib(2) 1.0
ustat(2) 1.0
userfaultfd(2) 4.2
utime(2) 1.0
utimensat(2) 2.6.22
utimes(2) 2.2
utrap_install(2) 2.2 только для SPARC
vfork(2) 2.2
vhangup(2) 1.0
vm86old(2) 1.0 ранее «vm86»; переименован в 2.0.28/2.2
vm86(2) 2.0.28; 2.2
vmsplice(2) 2.6.17
wait4(2) 1.0
waitid(2) 2.6.10
waitpid(2) 1.0
write(2) 1.0
writev(2) 2.0

Для многих платформ, включая x86-32, все сокетные вызовы мультиплексируются (с помощью обёрточных функций glibc) через socketcall(2), а подобные IPC вызовы System V мультиплексируются через ipc(2). Хотя для них и зарезервированы места в таблице системных вызовов, следующие системные вызовы не реализованы в стандартном ядре: afs_syscall(2), break(2), ftime(2), getpmsg(2), gtty(2), idle(2), lock(2), madvise1(2), mpx(2), phys(2), prof(2), profil(2), putpmsg(2), security(2), stty(2), tuxcall(2), ulimit(2) и vserver(2) (см. также unimplemented(2)). Однако ftime(3), profil(3) и ulimit(3) есть среди библиотечных функций. Место для phys(2) занято начиная с ядра 2.1.116 под umount(2); phys(2) никогда не будет реализован. Вызовы getpmsg(2) и putpmsg(2) есть в ядрах с заплатами, обеспечивающими поддержку STREAMS, и могут никогда не появиться в стандартном ядре. На короткое время появлялся set_zone_reclaim(2), добавленный в Linux 2.6.13 и удалённый в 2.6.16; данный системный вызов никогда не был доступен из пользовательского пространства.

ЗАМЕЧАНИЯ

Чаще всего, код системного вызова с номером __NR_xxx, определённого в /usr/include/asm/unistd.h, можно найти в исходном коде ядра Linux в функции sys_xxx(). (Таблицу вызовов для i386 можно найти в /usr/src/linux/arch/i386/kernel/entry.S.) Есть много исключений из этого правила, в основном из-за того, что большинство старых системных вызовов заменена на новые, при чём без всякой системы. На платформах с эмуляцией собственнических ОС, таких как parisc, sparc, sparc64 и alpha, существует много дополнительных системных вызовов; для mips64 также есть полный набор 32-битных системных вызовов. С течением времени при необходимости происходили изменения в интерфейсе некоторых системных вызовов. Одной из причин таких изменений была необходимость увеличения размера структур или скалярных значений передаваемых системному вызову. Из-за этих изменений на некоторых архитектурах (а именно на старых 32-битных i386) появились различные группы похожих системных вызовов (например, truncate(2) и truncate64(2)), которые выполняют одинаковые задачи, но отличаются размером своих аргументов. (Как уже отмечалось, на приложения это не влияет: обёрточные функции glibc выполняют некоторые действия по запуску правильного системного вызова, и это обеспечивает совместимость по ABI для старых двоичных файлов.) Примеры системных вызовов, у которых есть несколько версий: * В настоящее время есть три различные версии stat(2): sys_stat() (место __NR_oldstat), sys_newstat() (место __NR_stat) и sys_stat64() (место __NR_stat64), последняя используется в в данный момент. Похожая ситуация с lstat(2) и fstat(2). * Похожим образом определены __NR_oldolduname, __NR_olduname и__NR_uname для вызовов sys_olduname(), sys_uname() и sys_newuname(). * В Linux 2.0 появилась новая версия vm86(2), новая и старая версии ядерных процедур называются sys_vm86old() и sys_vm86(). * В Linux 2.4 появилась новая версия getrlimit(2) новая и старая версии ядерных процедур называются sys_old_getrlimit() (место __NR_getrlimit) и sys_getrlimit() (место __NR_ugetrlimit). * В Linux 2.4 увеличено размер поля ID пользователей и групп с 16 до 32 бит. Для поддержки этого изменения добавлено несколько системных вызовов (например, chown32(2), getuid32(2), getgroups32(2), setresuid32(2)), упраздняющих ранние вызовы с теми же именами, но без суффикса «32». * В Linux 2.4 добавлена поддержка доступа к большим файлам (у которых размеры и смещения не умещаются в 32 бита) в приложениях на 32-битных архитектурах. Для этого потребовалось внести изменения в системные вызовы, работающие с размерами и смещениями по файлам. Были добавлены следующие системные вызовы: fcntl64(2), getdents64(2), stat64(2), statfs64(2), truncate64(2) и их аналоги, которые обрабатывают файловые дескрипторы или символьные ссылки. Эти системные вызовы упраздняют старые системные вызовы, которые, за исключением вызовов «stat», называются также, но не имеют суффикса «64». На новых платформах, имеющих только 64-битный доступ к файлам и 32-битные UID/GID (например, alpha, ia64, s390x, x86-64), есть только одна версия системных вызовов для UID/GID и файлового доступа. На платформах (обычно это 32-битные платформы) где имеются *64 и *32 вызовы, другие версии устарели. * Вызовы rt_sig* добавлены в ядро 2.2 для поддержки дополнительных сигналов реального времени (см. signal(7)). Эти системные вызовы упраздняют старые системные вызовы с теми же именами, но без префикса «rt_». * В системных вызовах select(2) и mmap(2) используется пять или более аргументов, что вызывало проблемы определения способа передачи аргументов на i386. В следствии этого, тогда как на других архитектурах вызовы sys_select() и sys_mmap() соответствуют __NR_select и __NR_mmap, на i386 они соответствуют old_select() и old_mmap() (процедуры, использующие указатель на блок аргументов). В настоящее время больше нет проблемы с передачей более пяти аргументов и есть __NR__newselect, который соответствует именно sys_select(), и такая же ситуация с __NR_mmap2.

Что такое системный вызов

В этой главе речь пойдет о процессах. Скомпилированная программа хранится на диске как обычный нетекстовый файл. Когда она будет загружена в память компьютера и начнет выполняться — она станет процессом.

UNIX — многозадачная система (мультипрограммная). Это означает, что одновременно может быть запущено много процессов. Процессор выполняет их в режиме разделения времени — выделяя по очереди квант времени одному процессу, затем другому, третьему. В результате создается впечатление параллельного выполнения всех процессов (на многопроцессорных машинах параллельность истинная). Процессам, ожидающим некоторого события, время процессора не выделяется. Более того, «спящий» процесс может быть временно откачан (т.е. скопирован из памяти машины) на диск, чтобы освободить память для других процессов. Когда «спящий» процесс дождется события, он будет «разбужен» системой, переведен в ранг «готовых к выполнению» и, если был откачан будет возвращен с диска в память (но, может быть, на другое место в памяти!). Эта процедура носит название «своппинг» (swapping).

Можно запустить несколько процессов, выполняющих программу из одного и того же файла; при этом все они будут (если только специально не было предусмотрено иначе) независимыми друг от друга. Так, у каждого пользователя, работающего в системе, имеется свой собственный процесс-интерпретатор команд (своя копия), выполняющий программу из файла /bin/csh (или /bin/sh).

Процесс представляет собой изолированный «мир», общающийся с другими «мирами» во Вселенной при помощи:

a) Аргументов функции main:

void main(int argc, char *argv[], char *envp[]);

Если мы наберем команду

$ a.out a1 a2 a3

то функция main программы из файла a.out вызовется с

argc = 4 /* количество аргументов */ argv[0] = "a.out" argv[1] = "a1" argv[2] = "a2" argv[3] = "a3" argv[4] = NULL 

По соглашению argv[0] содержит имя выполняемого файла из которого загружена эта программа * .

b) Так называемого «окружения» (или «среды») char *envp[], продублированного также в предопределенной переменной

extern char **environ;

Окружение состоит из строк вида

"ИМЯПЕРЕМЕННОЙ=значение"

Массив этих строк завершается NULL (как и argv). Для получения значения переменной с именем ИМЯ существует стандартная функция

char *getenv( char *ИМЯ );

Она выдает либо значение, либо NULL если переменной с таким именем нет.

c) Открытых файлов. По умолчанию (неявно) всегда открыты 3 канала:

ВВОД В Ы В О Д FILE * stdin stdout stderr соответствует fd 0 1 2 связан с клавиатурой дисплеем
#include main(ac, av) char **av; < execl("/bin/sleep", "Take it easy", "1000", NULL); >

Эти каналы достаются процессу «в наследство» от запускающего процесса и связаны с дисплеем и клавиатурой, если только не были перенаправлены. Кроме того, программа может сама явно открывать файлы (при помощи open, creat, pipe, fopen). Всего программа может одновременно открыть до 20 файлов (считая стандартные каналы), а в некоторых системах и больше (например, 64). В MS DOS есть еще 2 предопределенных канала вывода: stdaux — в последовательный коммуникационный порт, stdprn — на принтер.

d) Процесс имеет уникальный номер, который он может узнать вызовом

int pid = getpid();

а также узнать номер «родителя» вызовом

int ppid = getppid();

Процессы могут по этому номеру посылать друг другу сигналы:

kill(pid /* кому */, sig /* номер сигнала */);

и реагировать на них

signal (sig /*по сигналу*/, f /*вызывать f(sig)*/);

e) Существуют и другие средства коммуникации процессов: семафоры, сообщения, общая память, сетевые коммуникации.

f) Существуют некоторые другие параметры (контекст) процесса: например, его текущий каталог, который достается в наследство от процесса-«родителя», и может быть затем изменен системным вызовом

chdir(char *имя_нового_каталога);

У каждого процесса есть свой собственный текущий рабочий каталог (в отличие от MS DOS, где текущий каталог одинаков для всех задач). К «прочим» характеристикам отнесем также: управляющий терминал; группу процессов (pgrp); идентификатор (номер) владельца процесса (uid), идентификатор группы владельца (gid), реакции и маски, заданные на различные сигналы; и.т.п.

g) Издания других запросов (системных вызовов) к операционной системе («богу») для выполнения различных «внешних» операций.

h) Все остальные действия происходят внутри процесса и никак не влияют на другие процессы и устройства («миры»). В частности, один процесс НИКАК не может получить доступ к памяти другого процесса, если тот не позволил ему это явно (механизм shared memory); адресные пространства процессов независимы и изолированы (равно и пространство ядра изолировано от памяти процессов).

Операционная система выступает в качестве коммуникационной среды, связывающей «миры»-процессы, «миры»-внешние устройства (включая терминал пользователя); а также в качестве распорядителя ресурсов «Вселенной», в частности — времени (по очереди выделяемого активным процессам) и пространства (в памяти компьютера и на дисках).

Мы уже неоднократно упоминали «системные вызовы». Что же это такое? С точки зрения Си-программиста — это обычные функции. В них передают аргументы, они возвращают значения. Внешне они ничем не отличаются от написанных нами или библиотечных функций и вызываются из программ одинаковым с ними способом.

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

Поведение всех программ в системе вытекает из поведения системных вызовов, которыми они пользуются. Даже то, что UNIX является многозадачной системой, непосредственно вытекает из наличия системных вызовов fork, exec, wait и спецификации их функционирования! То же можно сказать про язык Си — мобильность программы зависит в основном от набора используемых в ней библиотечных функций (и, в меньшей степени, от диалекта самого языка, который должен удовлетворять стандарту на язык Си). Если две разные системы предоставляют все эти функции (которые могут быть по-разному реализованы, но должны делать одно и то же), то программа будет компилироваться и работать в обоих системах, более того, работать в них одинаково.

Сам термин «системный вызов» как раз означает «вызов системы для выполнения действия», т.е. вызов функции в ядре системы. Ядро работает в привелегированном режиме, в котором имеет доступ к некоторым системным таблицам * , регистрам и портам внешних устройств и диспетчера памяти, к которым обычным программам доступ аппаратно запрещен (в отличие от MS DOS, где все таблицы ядра доступны пользовательским программам, что создает раздолье для вирусов). Системный вызов происходит в 2 этапа: сначала в пользовательской программе вызывается библиотечная функция-«корешок», тело которой написано на ассемблере и содержит команду генерации программного прерывания. Это — главное отличие от нормальных Си-функций — вызов по прерыванию. Вторым этапом является реакция ядра на прерывание:

  1. переход в привелегированный режим;
  2. разбирательство, КТО обратился к ядру, и подключение u-area этого процесса к адресному пространству ядра (context switching);
  3. извлечение аргументов из памяти запросившего процесса;
  4. выяснение, ЧТО же хотят от ядра (один из аргументов, невидимый нам — это номер системного вызова);
  5. проверка корректности остальных аргументов;
  6. проверка прав процесса на допустимость выполнения такого запроса;
  7. вызов тела требуемого системного вызова — это обычная Си-функция в ядре;
  8. возврат ответа в память процесса;
  9. выключение привелегированного режима;
  10. возврат из прерывания.

Во время системного вызова (шаг 7) процесс может «заснуть», дожидаясь некоторого события (например, нажатия кнопки на клавиатуре). В это время ядро передаст управление другому процессу. Когда наш процесс будет «разбужен» (событие произошло) — он продолжит выполнение шагов системного вызова.

Большинство системных вызовов возвращают в программу в качестве своего значения признак успеха: 0 — все сделано, (-1) — сисвызов завершился неудачей; либо некоторое содержательное значение при успехе (вроде дескриптора файла в open(), и (-1) при неудаче. В случае неудачного завершения в предопределенную переменную errno заносится номер ошибки, описывающий причину неудачи (коды ошибок предопределены, описаны в include-файле errno.h> и имеют вид Eчтото). Заметим, что при УДАЧЕ эта переменная просто не изменяется и может содержать любой мусор, поэтому проверять ее имеет смысл лишь в случае, если ошибка действительно произошла:

#include errno.h> /* коды ошибок */ extern int errno; extern char *sys_errlist[]; int value; if((value = sys_call(. )) < 0 )< printf("Error:%s(%d)\n", sys_errlist[errno], errno ); exit(errno); /* принудительное завершение программы */ >

Предопределенный массив sys_errlist, хранящийся в стандартной библиотеке, содержит строки-расшифровку смысла ошибок (по-английски). Посмотрите описание функции per- ror().

6.1. Файлы и каталоги.

6.1.1. Используя системный вызов stat, напишите программу, определяющую тип файла: обычный файл, каталог, устройство, FIFO-файл. Ответ:

#include sys/types.h> #include sys/stat.h> typeOf( name ) char *name; < int type; struct stat st; if( stat( name, &st ) < 0 )< printf( "%s не существует\n", name ); return 0; > printf("Файл имеет %d имен\n", st.st_nlink); switch(type = (st.st_mode & S_IFMT))< case S_IFREG: printf( "Обычный файл размером %ld байт\n", st.st_size ); break; case S_IFDIR: printf( "Каталог\n" ); break; case S_IFCHR: /* байтоориентированное */ case S_IFBLK: /* блочноориентированное */ printf( "Устройство\n" ); break; case S_IFIFO: printf( "FIFO-файл\n" ); break; default: printf( "Другой тип\n" ); break; > return type; >

6.1.2. Напишите программу, печатающую: свои аргументы, переменные окружения, информацию о всех открытых ею файлах и используемых трубах. Для этой цели используйте системный вызов

struct stat st; int used, fd; for(fd=0; fd < NOFILE; fd++ )< used = fstat(fd, &st)

Программа может использовать дескрипторы файлов с номерами 0..NOFILE-1 (обычно 0..19). Если fstat для какого-то fd вернул код ошибки (<0), это означает, что данный дескриптор не связан с открытым файлом (т.е. не используется). NOFILE определено в include-файле sys/param.h>, содержащем разнообразные параметры данной системы.

6.1.3. Напишите упрощенный аналог команды ls, распечатывающий содержимое текущего каталога (файла с именем «.») без сортировки имен по алфавиту. Предусмотрите чтение каталога, чье имя задается как аргумент программы. Имена «.» и «..» не выдавать.

Формат каталога описан в header-файле sys/dir.h> и в «канонической» версии выглядит так: каталог — это файл, состоящий из структур direct, каждая описывает одно имя файла, входящего в каталог:

struct direct < unsigned short d_ino; /* 2 байта: номер I-узла */ char d_name[DIRSIZ]; /* имя файла */ >;

В семействе BSD формат каталога несколько иной — там записи имеют разную длину, зависящую от длины имени файла, которое может иметь длину от 1 до 256 символов.

Имя файла может состоять из любых символов, кроме ‘\0’, служащего признаком конца имени и ‘/’, служащего разделителем. В имени допустимы пробелы, управляющие символы (но не рекомендуются!), любое число точек (в отличие от MS DOS, где допустима единственная точка, отделяющая собственно имя от суффикса (расширения)), разрешены даже непечатные (т.е. управляющие) символы! Если имя файла имеет длину 14 (DIRSIZ) символов, то оно не оканчивается байтом ‘\0’. В этом случае для печати имени файла возможны три подхода:

  1. Выводить символы при помощи putchar()-а в цикле. Цикл прерывать по индексу равному DIRSIZ, либо по достижению байта ‘\0‘.
  2. Скопировать поле d_name в другое место:

char buf[ DIRSIZ + 1 ]; strncpy(buf, d.d_name, DIRSIZ); buf[ DIRSIZ ] = '\0';

Этот способ лучший, если имя файла надо не просто напечатать, но и запомнить на будущее, чтобы использовать в своей программе.

#include sys/types.h> #include sys/dir.h> struct direct d; . printf( "%*.*s\n", DIRSIZ, DIRSIZ, d.d_name );

Если файл был стерт, то в поле d_ino записи каталога будет содержаться 0 (именно поэтому I-узлы нумеруются начиная с 1, а не с 0). При удалении файла содержимое его (блоки) уничтожается, I-узел освобождается, но имя в каталоге не затирается физически, а просто помечается как стертое: d_ino=0; Каталог при этом никак не уплотняется и не укорачивается! Поэтому имена с d_ino==0 выдавать не следует — это имена уже уничтоженных файлов.

При создании нового имени (creat, link, mknod) система просматривает каталог и переиспользует первый от начала свободный слот (ячейку каталога) где d_ino==0, записывая новое имя в него (только в этот момент старое имя-призрак окончательно исчезнет физически). Если пустых мест нет — каталог удлиняется.

Любой каталог всегда содержит два стандартных имени: «.» — ссылка на этот же каталог (на его собственный I-node), «..» — на вышележащий каталог. У корневого каталога «/» оба этих имени ссылаются на него же самого (т.е. содержат d_ino==2).

Имя каталога не содержится в нем самом. Оно содержится в «родительском» каталоге ...

Каталог в UNIX — это обычный дисковый файл. Вы можете читать его из своих программ. Однако никто (включая суперпользователя * ) не может записывать что-либо в каталог при помощи write. Изменения содержимого каталогов выполняет только ядро, отвечая на запросы в виде системных вызовов creat, unlink, link, mkdir, rmdir, rename, mknod. Коды доступа для каталога интерпретируются следующим образом:

w запись S_IWRITE. Означает право создавать и уничтожать в каталоге имена файлов при помощи этих вызовов. То есть: право создавать, удалять и переименовывать файлы в каталоге. Отметим, что для переименования или удаления файла вам не требуется иметь доступ по записи к самому файлу — достаточно иметь доступ по записи к каталогу, содержащему его имя! r чтение S_IREAD. Право читать каталог как обычный файл (право выполнять opendir, см. ниже): благодаря этому мы можем получить список имен файлов, содержащихся в каталоге. Однако, если мы ЗАРАНЕЕ знаем имена файлов в каталоге, мы МОЖЕМ работать с ними — если имеем право доступа «выполнение» для этого каталога! x выполнение S_IEXEC. Разрешает поиск в каталоге. Для открытия файла, создания/удаления файла, перехода в другой каталог (chdir), система выполняет следующие действия (осуществляемые функцией namei() в ядре): чтение каталога и поиск в нем указанного имени файла или каталога; найденному имени соответствует номер I-узла d_ino; по номеру узла система считывает с диска сам I-узел нужного файла и по нему добирается до содержимого файла. Код «выполнение» — это как раз разрешение такого просмотра каталога системой. Если каталог имеет доступ на чтение — мы можем получить список файлов (т.е. применить команду ls); но если он при этом не имеет кода доступа «выполнение» — мы не сможем получить доступа ни к одному из файлов каталога (ни открыть, ни удалить, ни создать, ни сделать stat, ни chdir). Т.е. «чтение» разрешает применение вызова read, а «выполнение» — функции ядра namei. Фактически «выполнение» означает «доступ к файлам в данном каталоге»; еще более точно — к I-nodам файлов этого каталога. t sticky bit S_ISVTX — для каталога он означает, что удалить или переименовать некий файл в данном каталоге могут только: владелец каталога, владелец данного файла, суперпользователь. И никто другой. Это исключает удаление файлов чужими.

Совет: для каталога полезно иметь такие коды доступа:

chmod o-w,+t каталог 

В системах BSD используется, как уже было упомянуто, формат каталога с переменной длиной записей. Чтобы иметь удобный доступ к именам в каталоге, возникли специальные функции чтения каталога: opendir, closedir, readdir. Покажем, как простейшая команда ls реализуется через эти функции.

#include #include #include int listdir(char *dirname)< register struct dirent *dirbuf; DIR *fddir; ino_t dot_ino = 0, dotdot_ino = 0; if((fddir = opendir (dirname)) == NULL)< fprintf(stderr, "Can't read %s\n", dirname); return 1; > /* Без сортировки по алфавиту */ while ((dirbuf = readdir (fddir)) != NULL ) < if (dirbuf->d_ino == 0) continue; if (strcmp (dirbuf->d_name, "." ) == 0)< dot_ino = dirbuf->d_ino; continue; > else if(strcmp (dirbuf->d_name, "..") == 0)< dotdot_ino = dirbuf->d_ino; continue; > else printf("%s\n", dirbuf->d_name); > closedir (fddir); if(dot_ino == 0) printf("Поврежденный каталог: нет имени \".\"\n"); if(dotdot_ino == 0) printf("Поврежденный каталог: нет имени \"..\"\n"); if(dot_ino && dot_ino == dotdot_ino) printf("Это корневой каталог диска\n"); return 0; > int main(int ac, char *av[])< int i; if(ac > 1) for(i=1; i < ac; i++) listdir(av[i]); else listdir("."); return 0; >

Обратите внимание, что тут не требуется добавление ‘\0’ в конец поля d_name, поскольку его предоставляет нам сама функция readdir().

6.1.4. Напишите программу удаления файлов и каталогов, заданных в argv. Делайте stat, чтобы определить тип файла (файл/каталог). Программа должна отказываться удалять файлы устройств. Для удаления пустого каталога (не содержащего иных имен, кроме «.» и «..») следует использовать сисвызов

rmdir(имя_каталога);

(если каталог не пуст — errno получит значение EEXIST); а для удаления обычных файлов (не каталогов)

unlink(имя_файла);

Программа должна запрашивать подтверждение на удаление каждого файла, выдавая его имя, тип, размер в килобайтах и вопрос «удалить ?».

* — Именно это имя показывает команда ps -ef

* — Собственно, операционная система характеризуется набором предоставляемых ею системных вызовов, поскольку все концепции, заложенные в системе, доступны нам только через них. Если мы имеем две реализации системы с разным внутренним устройством ядер, но предоставляющие одинаковый интерфейс системных вызовов (их набор, смысл и поведение), то это все-таки одна и та же система! Ядра могут не просто отличаться, но и быть построенными на совершенно различных принципах: так обстоит дело с UNIX-ами на однопроцессорных и многопроцессорных машинах. Но для нас ядро — это «черный ящик», полностью определяемый его поведением, т.е. своим интерфейсом с программами, но не внутренним устройством. Вторым параметром, характеризующим ОС, являются форматы данных, используемые системой: форматы данных для сисвызовов и формат информации в различных файлах, в том числе формат оформления выполняемых файлов (формат данных в физической памяти машины в этот список не входит — он зависим от реализации и от процессора). Как правило, программа пишется так, чтобы использовать соглашения, принятые в данной системе, для чего она просто включает ряд стандартных include-файлов с описанием этих форматов. Имена этих файлов также можно отнести к интерфейсу системы.

* — Таким как таблица процессов, таблица открытых файлов (всех вместе и для каждого процесса), и.т.п.

* — Суперпользователь (superuser) имеет uid==0. Это «привелегированный» пользователь, который имеет право делать ВСЕ. Ему доступны любые сисвызовы и файлы, несмотря на коды доступа и.т.п.

© Copyright А. Богатырев, 1992-95
Си в UNIX

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *