Для приложения системный вызов внешне ничем не отличается от вызова обычной функции языка си
В этой главе речь пойдет о процессах. Скомпилированная программа хранится на диске как обычный нетекстовый файл. Когда она будет загружена в память компьютера и начнет выполняться - она станет процессом.
UNIX - многозадачная система (мультипрограммная). Это означает, что одновременно может быть запущено много процессов. Процессор выполняет их в режиме разделения времени - выделяя по очереди квант времени одному процессу, затем другому, третьему. В результате создается впечатление параллельного выполнения всех процессов (на многопроцессорных машинах параллельность истинная). Процессам, ожидающим некоторого события, время процессора не выделяется. Более того, "спящий" процесс может быть временно откачан (т.е. скопирован из памяти машины) на диск, чтобы освободить память для других процессов. Когда "спящий" процесс дождется события, он будет "разбужен" системой, переведен в ранг "готовых к выполнению" и, если был откачан будет возвращен с диска в память (но, может быть, на другое место в памяти!). Эта процедура носит название "своппинг" (swapping).
Можно запустить несколько процессов, выполняющих программу из одного и того же файла; при этом все они будут (если только специально не было предусмотрено иначе) независимыми друг от друга. Так, у каждого пользователя, работающего в системе, имеется свой собственный процесс-интерпретатор команд (своя копия), выполняющий программу из файла /bin/csh (или /bin/sh).
Процесс представляет собой изолированный "мир", общающийся с другими "мирами" во Вселенной при помощи:
a) Аргументов функции main:
Если мы наберем команду
то функция main программы из файла a.out вызовется с
По соглашению argv[0] содержит имя выполняемого файла из которого загружена эта программа * .
b) Так называемого "окружения" (или "среды") char *envp[], продублированного также в предопределенной переменной
Окружение состоит из строк вида
Массив этих строк завершается NULL (как и argv). Для получения значения переменной с именем ИМЯ существует стандартная функция
Она выдает либо значение, либо NULL если переменной с таким именем нет.
c) Открытых файлов. По умолчанию (неявно) всегда открыты 3 канала:
Эти каналы достаются процессу "в наследство" от запускающего процесса и связаны с дисплеем и клавиатурой, если только не были перенаправлены. Кроме того, программа может сама явно открывать файлы (при помощи open, creat, pipe, fopen). Всего программа может одновременно открыть до 20 файлов (считая стандартные каналы), а в некоторых системах и больше (например, 64). В MS DOS есть еще 2 предопределенных канала вывода: stdaux - в последовательный коммуникационный порт, stdprn - на принтер.
d) Процесс имеет уникальный номер, который он может узнать вызовом
а также узнать номер "родителя" вызовом
Процессы могут по этому номеру посылать друг другу сигналы:
и реагировать на них
f) Существуют некоторые другие параметры (контекст) процесса: например, его текущий каталог, который достается в наследство от процесса-"родителя", и может быть затем изменен системным вызовом
У каждого процесса есть свой собственный текущий рабочий каталог (в отличие от MS DOS, где текущий каталог одинаков для всех задач). К "прочим" характеристикам отнесем также: управляющий терминал; группу процессов (pgrp); идентификатор (номер) владельца процесса (uid), идентификатор группы владельца (gid), реакции и маски, заданные на различные сигналы; и.т.п.
g) Издания других запросов (системных вызовов) к операционной системе ("богу") для выполнения различных "внешних" операций.
h) Все остальные действия происходят внутри процесса и никак не влияют на другие процессы и устройства ("миры"). В частности, один процесс НИКАК не может получить доступ к памяти другого процесса, если тот не позволил ему это явно (механизм shared memory); адресные пространства процессов независимы и изолированы (равно и пространство ядра изолировано от памяти процессов).
Операционная система выступает в качестве коммуникационной среды, связывающей "миры"-процессы, "миры"-внешние устройства (включая терминал пользователя); а также в качестве распорядителя ресурсов "Вселенной", в частности - времени (по очереди выделяемого активным процессам) и пространства (в памяти компьютера и на дисках).
Мы уже неоднократно упоминали "системные вызовы". Что же это такое? С точки зрения Си-программиста - это обычные функции. В них передают аргументы, они возвращают значения. Внешне они ничем не отличаются от написанных нами или библиотечных функций и вызываются из программ одинаковым с ними способом.
С точки же зрения реализации - есть глубокое различие. Тело функции-сисвызова расположено не в нашей программе, а в резидентной (т.е. постоянно находящейся в памяти компьютера) управляющей программе, называемой ядром операционной системы * .
Поведение всех программ в системе вытекает из поведения системных вызовов, которыми они пользуются. Даже то, что UNIX является многозадачной системой, непосредственно вытекает из наличия системных вызовов fork, exec, wait и спецификации их функционирования! То же можно сказать про язык Си - мобильность программы зависит в основном от набора используемых в ней библиотечных функций (и, в меньшей степени, от диалекта самого языка, который должен удовлетворять стандарту на язык Си). Если две разные системы предоставляют все эти функции (которые могут быть по-разному реализованы, но должны делать одно и то же), то программа будет компилироваться и работать в обоих системах, более того, работать в них одинаково.
Сам термин "системный вызов" как раз означает "вызов системы для выполнения действия", т.е. вызов функции в ядре системы. Ядро работает в привелегированном режиме, в котором имеет доступ к некоторым системным таблицам * , регистрам и портам внешних устройств и диспетчера памяти, к которым обычным программам доступ аппаратно запрещен (в отличие от MS DOS, где все таблицы ядра доступны пользовательским программам, что создает раздолье для вирусов). Системный вызов происходит в 2 этапа: сначала в пользовательской программе вызывается библиотечная функция-"корешок", тело которой написано на ассемблере и содержит команду генерации программного прерывания. Это - главное отличие от нормальных Си-функций - вызов по прерыванию. Вторым этапом является реакция ядра на прерывание:
- переход в привелегированный режим;
- разбирательство, КТО обратился к ядру, и подключение u-area этого процесса к адресному пространству ядра (context switching);
- извлечение аргументов из памяти запросившего процесса;
- выяснение, ЧТО же хотят от ядра (один из аргументов, невидимый нам - это номер системного вызова);
- проверка корректности остальных аргументов;
- проверка прав процесса на допустимость выполнения такого запроса;
- вызов тела требуемого системного вызова - это обычная Си-функция в ядре;
- возврат ответа в память процесса;
- выключение привелегированного режима;
- возврат из прерывания.
Во время системного вызова (шаг 7) процесс может "заснуть", дожидаясь некоторого события (например, нажатия кнопки на клавиатуре). В это время ядро передаст управление другому процессу. Когда наш процесс будет "разбужен" (событие произошло) - он продолжит выполнение шагов системного вызова.
Большинство системных вызовов возвращают в программу в качестве своего значения признак успеха: 0 - все сделано, (-1) - сисвызов завершился неудачей; либо некоторое содержательное значение при успехе (вроде дескриптора файла в open(), и (-1) при неудаче. В случае неудачного завершения в предопределенную переменную errno заносится номер ошибки, описывающий причину неудачи (коды ошибок предопределены, описаны в include-файле <errno.h> и имеют вид Eчтото). Заметим, что при УДАЧЕ эта переменная просто не изменяется и может содержать любой мусор, поэтому проверять ее имеет смысл лишь в случае, если ошибка действительно произошла:
Предопределенный массив sys_errlist, хранящийся в стандартной библиотеке, содержит строки-расшифровку смысла ошибок (по-английски). Посмотрите описание функции per- ror().
6.1. Файлы и каталоги.
6.1.1. Используя системный вызов stat, напишите программу, определяющую тип файла: обычный файл, каталог, устройство, FIFO-файл. Ответ:
6.1.2. Напишите программу, печатающую: свои аргументы, переменные окружения, информацию о всех открытых ею файлах и используемых трубах. Для этой цели используйте системный вызов
Программа может использовать дескрипторы файлов с номерами 0..NOFILE-1 (обычно 0..19). Если fstat для какого-то fd вернул код ошибки (<0), это означает, что данный дескриптор не связан с открытым файлом (т.е. не используется). NOFILE определено в include-файле <sys/param.h>, содержащем разнообразные параметры данной системы.
6.1.3. Напишите упрощенный аналог команды ls, распечатывающий содержимое текущего каталога (файла с именем ".") без сортировки имен по алфавиту. Предусмотрите чтение каталога, чье имя задается как аргумент программы. Имена "." и ".." не выдавать.
Формат каталога описан в header-файле <sys/dir.h> и в "канонической" версии выглядит так: каталог - это файл, состоящий из структур direct, каждая описывает одно имя файла, входящего в каталог:
В семействе BSD формат каталога несколько иной - там записи имеют разную длину, зависящую от длины имени файла, которое может иметь длину от 1 до 256 символов.
Имя файла может состоять из любых символов, кроме '\0', служащего признаком конца имени и '/', служащего разделителем. В имени допустимы пробелы, управляющие символы (но не рекомендуются!), любое число точек (в отличие от MS DOS, где допустима единственная точка, отделяющая собственно имя от суффикса (расширения)), разрешены даже непечатные (т.е. управляющие) символы! Если имя файла имеет длину 14 (DIRSIZ) символов, то оно не оканчивается байтом '\0'. В этом случае для печати имени файла возможны три подхода:
- Выводить символы при помощи putchar()-а в цикле. Цикл прерывать по индексу равному DIRSIZ, либо по достижению байта '\0'.
- Скопировать поле 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 - для каталога он означает, что удалить или переименовать некий файл в данном каталоге могут только: владелец каталога, владелец данного файла, суперпользователь. И никто другой. Это исключает удаление файлов чужими.
Совет: для каталога полезно иметь такие коды доступа:
В системах BSD используется, как уже было упомянуто, формат каталога с переменной длиной записей. Чтобы иметь удобный доступ к именам в каталоге, возникли специальные функции чтения каталога: opendir, closedir, readdir. Покажем, как простейшая команда ls реализуется через эти функции.
Обратите внимание, что тут не требуется добавление '\0' в конец поля d_name, поскольку его предоставляет нам сама функция readdir().
6.1.4. Напишите программу удаления файлов и каталогов, заданных в argv. Делайте stat, чтобы определить тип файла (файл/каталог). Программа должна отказываться удалять файлы устройств. Для удаления пустого каталога (не содержащего иных имен, кроме "." и "..") следует использовать сисвызов
(если каталог не пуст - errno получит значение EEXIST); а для удаления обычных файлов (не каталогов)
Программа должна запрашивать подтверждение на удаление каждого файла, выдавая его имя, тип, размер в килобайтах и вопрос "удалить ?".
* - Именно это имя показывает команда ps -ef
* - Собственно, операционная система характеризуется набором предоставляемых ею системных вызовов, поскольку все концепции, заложенные в системе, доступны нам только через них. Если мы имеем две реализации системы с разным внутренним устройством ядер, но предоставляющие одинаковый интерфейс системных вызовов (их набор, смысл и поведение), то это все-таки одна и та же система! Ядра могут не просто отличаться, но и быть построенными на совершенно различных принципах: так обстоит дело с UNIX-ами на однопроцессорных и многопроцессорных машинах. Но для нас ядро - это "черный ящик", полностью определяемый его поведением, т.е. своим интерфейсом с программами, но не внутренним устройством. Вторым параметром, характеризующим ОС, являются форматы данных, используемые системой: форматы данных для сисвызовов и формат информации в различных файлах, в том числе формат оформления выполняемых файлов (формат данных в физической памяти машины в этот список не входит - он зависим от реализации и от процессора). Как правило, программа пишется так, чтобы использовать соглашения, принятые в данной системе, для чего она просто включает ряд стандартных include-файлов с описанием этих форматов. Имена этих файлов также можно отнести к интерфейсу системы.
* - Таким как таблица процессов, таблица открытых файлов (всех вместе и для каждого процесса), и.т.п.
* - Суперпользователь (superuser) имеет uid==0. Это "привелегированный" пользователь, который имеет право делать ВСЕ. Ему доступны любые сисвызовы и файлы, несмотря на коды доступа и.т.п.
В чем разница между системным вызовом и вызовом функции? Является ли fopen() системным вызовом или вызовом функции?
спросил(а) 2020-03-29T15:40:15+03:00 1 год, 8 месяцев назадСистемный вызов - это вызов кода ядра, который обычно выполняется путем выполнения прерывания. Прерывание заставляет ядро взять на себя и выполнить запрошенное действие, а затем управление руками обратно в приложение. Это переключение режима является причиной того, что системные вызовы работают медленнее, чем эквивалентная функция уровня приложения.
fopen - это функция из библиотеки C, которая внутренне выполняет один или несколько системных вызовов. Как правило, в качестве программиста на C вам редко приходится использовать системные вызовы, потому что библиотека C обертывает их для вас.
ответил(а) 2020-03-29T15:51:41.587080+03:00 1 год, 8 месяцев назадfopen - вызов функции.
Системный вызов взаимодействует с базовой ОС, которая управляет ресурсами. Его заказы magnitud дороже вызова функции, потому что необходимо предпринять много шагов для сохранения состояния процесса, который сделал системный вызов.
В системах * nix открываются обрывы fopen, что делает системный вызов (open - это C-обертка для syscall). То же самое происходит с fread/read, fwrite/write и т.д.
ответил(а) 2020-03-29T15:40:15+03:00 1 год, 8 месяцев назадНа самом деле системный вызов не связан с вызовом функции. Единственным распространенным из этих двух механизмов является то, что они оба предоставляют услуги вызывающему абоненту.
Из представления выполнения потока, чтобы увидеть системный вызов:
Системный вызов - это функция для программы режима приложения для запроса услуг, предоставляемых базовой ОС. Системный вызов приведет текущий поток из пользовательского режима в режим ядра, выполнит функцию обработчика системных вызовов, а затем вернется в пользовательский режим.
Параметр системного вызова (номер syscall, params. ). Значение и формат параметров зависят от номера системы.
Из представления библиотеки syscall, предоставленной программе userland:
Программа пользовательского режима обычно вызывает библиотеку glibc для вызова системного вызова. Например, функция open() в glibc:
-
введите номер системного вызова SYS_OPEN в регистре eax
запрос системного вызова путем вызова программного прерывания или команды sys_enter
Если вы используете Linux, вы можете контролировать системные вызовы, выполняемые приложением:
Его вывод может дать вам хорошее представление о том, что происходит внутри libc, и какие функции на самом деле являются системными вызовами.
ответил(а) 2020-03-29T15:40:15+03:00 1 год, 8 месяцев назадСистемный вызов фактически вызывает API, выполняемый ядром. При всех связанных с этим расходах это предполагает (см. Wiki или эту ссылку для деталей)
Вызов функции - это вызов части кода в пользовательском пространстве.
Однако обратите внимание, что вызов функции МОЖЕТ быть в функции, которая в процессе ее выполнения выполняет системные вызовы - "fopen", являющийся одним из таких примеров. Таким образом, хотя вызов fopen сам является вызовом функции, не означает, что системный вызов не будет обрабатывать фактический IO.
ответил(а) 2020-03-29T15:40:15+03:00 1 год, 8 месяцев назадСистемный вызов имеет следующие свойства.
-
Он выполняет гораздо больше инструкций, он должен заморозить процесс, а не просто состояние стека.
Используемый временной интервал является в основном недетерминированным.
Это часто место для планирования, и планировщик может выбрать перепланирование.
Для этих трех примитивных причин (их, вероятно, больше) необходимо уменьшить количество системных вызовов там, где это возможно - например, сетевое системное программное обеспечение поддерживает дескрипторы сокетов (и другие внутренние структуры данных, используемые приложением) назначить новому соединению, зачем беспокоить ядро?
Помните, что программное обеспечение построено как перевернутая пирамида. Системные вызовы находятся в базе.
ответил(а) 2020-03-29T15:40:15+03:00 1 год, 8 месяцев назадЧтобы завершить изображение, представленное другими, fopen обычно реализуется в качестве обертки вокруг open , что также доступная пользователю функция. fopen в некотором смысле более высокий уровень, чем open , поскольку структура FILE* возвращает инкапсуляции для пользователя. Некоторые пользователи используют open напрямую для особых нужд. Поэтому было бы неправильно называть fopen "системный вызов" каким-либо образом. Он также не выполняет системные вызовы напрямую, так как open также является функцией, вызываемой пользователем.
ответил(а) 2020-03-29T15:40:15+03:00 1 год, 8 месяцев назадfopen - вызов функции, но иногда его можно назвать системным вызовом, потому что он в конечном итоге обрабатывается "системой" (ОС). fopen встроен в библиотеку C.
ответил(а) 2020-03-29T15:40:15+03:00 1 год, 8 месяцев назадСистемный вызов выполняется на уровне ядра, а не в пользовательском spce, потому что для доступа к оборудованию требуется некоторый prievilege.
При программировании в пользовательском пространстве и при вызове какой-либо обычной функции, например, fopen на языке C, libc обычно обертывает эту функцию на конкретный кодовый код, где генерируется прерывание для переключения из пространства пользователя в пространство ядра, а затем в пространстве ядра требуемый системный вызов для выполнения функциональных функций вызова функции на аппаратном уровне будет выполняться в пространстве ядра.
В этой главе речь пойдет о процессах. Скомпилированная программа хранится на диске как обычный нетекстовый файл. Когда она будет загружена в память компьютера и начнет выполняться - она станет процессом.
UNIX - многозадачная система (мультипрограммная). Это означает, что одновременно может быть запущено много процессов. Процессор выполняет их в режиме разделения времени - выделяя по очереди квант времени одному процессу, затем другому, третьему. В результате создается впечатление параллельного выполнения всех процессов (на многопроцессорных машинах параллельность истинная). Процессам, ожидающим некоторого события, время процессора не выделяется. Более того, "спящий" процесс может быть временно откачан (т.е. скопирован из памяти машины) на диск, чтобы освободить память для других процессов. Когда "спящий" процесс дождется события, он будет "разбужен" системой, переведен в ранг "готовых к выполнению" и, если был откачан будет возвращен с диска в память (но, может быть, на другое место в памяти!). Эта процедура носит название "своппинг" (swapping).
Можно запустить несколько процессов, выполняющих программу из одного и того же файла; при этом все они будут (если только специально не было предусмотрено иначе) независимыми друг от друга. Так, у каждого пользователя, работающего в системе, имеется свой собственный процесс-интерпретатор команд (своя копия), выполняющий программу из файла /bin/csh (или /bin/sh).
Процесс представляет собой изолированный "мир", общающийся с другими "мирами" во Вселенной при помощи:
a) Аргументов функции main:
Если мы наберем команду
то функция main программы из файла a.out вызовется с
По соглашению argv[0] содержит имя выполняемого файла из которого загружена эта программа * .
b) Так называемого "окружения" (или "среды") char *envp[], продублированного также в предопределенной переменной
Окружение состоит из строк вида
Массив этих строк завершается NULL (как и argv). Для получения значения переменной с именем ИМЯ существует стандартная функция
Она выдает либо значение, либо NULL если переменной с таким именем нет.
c) Открытых файлов. По умолчанию (неявно) всегда открыты 3 канала:
Эти каналы достаются процессу "в наследство" от запускающего процесса и связаны с дисплеем и клавиатурой, если только не были перенаправлены. Кроме того, программа может сама явно открывать файлы (при помощи open, creat, pipe, fopen). Всего программа может одновременно открыть до 20 файлов (считая стандартные каналы), а в некоторых системах и больше (например, 64). В MS DOS есть еще 2 предопределенных канала вывода: stdaux - в последовательный коммуникационный порт, stdprn - на принтер.
d) Процесс имеет уникальный номер, который он может узнать вызовом
а также узнать номер "родителя" вызовом
Процессы могут по этому номеру посылать друг другу сигналы:
и реагировать на них
f) Существуют некоторые другие параметры (контекст) процесса: например, его текущий каталог, который достается в наследство от процесса-"родителя", и может быть затем изменен системным вызовом
У каждого процесса есть свой собственный текущий рабочий каталог (в отличие от MS DOS, где текущий каталог одинаков для всех задач). К "прочим" характеристикам отнесем также: управляющий терминал; группу процессов (pgrp); идентификатор (номер) владельца процесса (uid), идентификатор группы владельца (gid), реакции и маски, заданные на различные сигналы; и.т.п.
g) Издания других запросов (системных вызовов) к операционной системе ("богу") для выполнения различных "внешних" операций.
h) Все остальные действия происходят внутри процесса и никак не влияют на другие процессы и устройства ("миры"). В частности, один процесс НИКАК не может получить доступ к памяти другого процесса, если тот не позволил ему это явно (механизм shared memory); адресные пространства процессов независимы и изолированы (равно и пространство ядра изолировано от памяти процессов).
Операционная система выступает в качестве коммуникационной среды, связывающей "миры"-процессы, "миры"-внешние устройства (включая терминал пользователя); а также в качестве распорядителя ресурсов "Вселенной", в частности - времени (по очереди выделяемого активным процессам) и пространства (в памяти компьютера и на дисках).
Мы уже неоднократно упоминали "системные вызовы". Что же это такое? С точки зрения Си-программиста - это обычные функции. В них передают аргументы, они возвращают значения. Внешне они ничем не отличаются от написанных нами или библиотечных функций и вызываются из программ одинаковым с ними способом.
С точки же зрения реализации - есть глубокое различие. Тело функции-сисвызова расположено не в нашей программе, а в резидентной (т.е. постоянно находящейся в памяти компьютера) управляющей программе, называемой ядром операционной системы * .
Поведение всех программ в системе вытекает из поведения системных вызовов, которыми они пользуются. Даже то, что UNIX является многозадачной системой, непосредственно вытекает из наличия системных вызовов fork, exec, wait и спецификации их функционирования! То же можно сказать про язык Си - мобильность программы зависит в основном от набора используемых в ней библиотечных функций (и, в меньшей степени, от диалекта самого языка, который должен удовлетворять стандарту на язык Си). Если две разные системы предоставляют все эти функции (которые могут быть по-разному реализованы, но должны делать одно и то же), то программа будет компилироваться и работать в обоих системах, более того, работать в них одинаково.
Сам термин "системный вызов" как раз означает "вызов системы для выполнения действия", т.е. вызов функции в ядре системы. Ядро работает в привелегированном режиме, в котором имеет доступ к некоторым системным таблицам * , регистрам и портам внешних устройств и диспетчера памяти, к которым обычным программам доступ аппаратно запрещен (в отличие от MS DOS, где все таблицы ядра доступны пользовательским программам, что создает раздолье для вирусов). Системный вызов происходит в 2 этапа: сначала в пользовательской программе вызывается библиотечная функция-"корешок", тело которой написано на ассемблере и содержит команду генерации программного прерывания. Это - главное отличие от нормальных Си-функций - вызов по прерыванию. Вторым этапом является реакция ядра на прерывание:
- переход в привелегированный режим;
- разбирательство, КТО обратился к ядру, и подключение u-area этого процесса к адресному пространству ядра (context switching);
- извлечение аргументов из памяти запросившего процесса;
- выяснение, ЧТО же хотят от ядра (один из аргументов, невидимый нам - это номер системного вызова);
- проверка корректности остальных аргументов;
- проверка прав процесса на допустимость выполнения такого запроса;
- вызов тела требуемого системного вызова - это обычная Си-функция в ядре;
- возврат ответа в память процесса;
- выключение привелегированного режима;
- возврат из прерывания.
Во время системного вызова (шаг 7) процесс может "заснуть", дожидаясь некоторого события (например, нажатия кнопки на клавиатуре). В это время ядро передаст управление другому процессу. Когда наш процесс будет "разбужен" (событие произошло) - он продолжит выполнение шагов системного вызова.
Большинство системных вызовов возвращают в программу в качестве своего значения признак успеха: 0 - все сделано, (-1) - сисвызов завершился неудачей; либо некоторое содержательное значение при успехе (вроде дескриптора файла в open(), и (-1) при неудаче. В случае неудачного завершения в предопределенную переменную errno заносится номер ошибки, описывающий причину неудачи (коды ошибок предопределены, описаны в include-файле <errno.h> и имеют вид Eчтото). Заметим, что при УДАЧЕ эта переменная просто не изменяется и может содержать любой мусор, поэтому проверять ее имеет смысл лишь в случае, если ошибка действительно произошла:
Предопределенный массив sys_errlist, хранящийся в стандартной библиотеке, содержит строки-расшифровку смысла ошибок (по-английски). Посмотрите описание функции per- ror().
6.1. Файлы и каталоги.
6.1.1. Используя системный вызов stat, напишите программу, определяющую тип файла: обычный файл, каталог, устройство, FIFO-файл. Ответ:
6.1.2. Напишите программу, печатающую: свои аргументы, переменные окружения, информацию о всех открытых ею файлах и используемых трубах. Для этой цели используйте системный вызов
Программа может использовать дескрипторы файлов с номерами 0..NOFILE-1 (обычно 0..19). Если fstat для какого-то fd вернул код ошибки (<0), это означает, что данный дескриптор не связан с открытым файлом (т.е. не используется). NOFILE определено в include-файле <sys/param.h>, содержащем разнообразные параметры данной системы.
6.1.3. Напишите упрощенный аналог команды ls, распечатывающий содержимое текущего каталога (файла с именем ".") без сортировки имен по алфавиту. Предусмотрите чтение каталога, чье имя задается как аргумент программы. Имена "." и ".." не выдавать.
Формат каталога описан в header-файле <sys/dir.h> и в "канонической" версии выглядит так: каталог - это файл, состоящий из структур direct, каждая описывает одно имя файла, входящего в каталог:
В семействе BSD формат каталога несколько иной - там записи имеют разную длину, зависящую от длины имени файла, которое может иметь длину от 1 до 256 символов.
Имя файла может состоять из любых символов, кроме '\0', служащего признаком конца имени и '/', служащего разделителем. В имени допустимы пробелы, управляющие символы (но не рекомендуются!), любое число точек (в отличие от MS DOS, где допустима единственная точка, отделяющая собственно имя от суффикса (расширения)), разрешены даже непечатные (т.е. управляющие) символы! Если имя файла имеет длину 14 (DIRSIZ) символов, то оно не оканчивается байтом '\0'. В этом случае для печати имени файла возможны три подхода:
- Выводить символы при помощи putchar()-а в цикле. Цикл прерывать по индексу равному DIRSIZ, либо по достижению байта '\0'.
- Скопировать поле 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 - для каталога он означает, что удалить или переименовать некий файл в данном каталоге могут только: владелец каталога, владелец данного файла, суперпользователь. И никто другой. Это исключает удаление файлов чужими.
Совет: для каталога полезно иметь такие коды доступа:
В системах BSD используется, как уже было упомянуто, формат каталога с переменной длиной записей. Чтобы иметь удобный доступ к именам в каталоге, возникли специальные функции чтения каталога: opendir, closedir, readdir. Покажем, как простейшая команда ls реализуется через эти функции.
Обратите внимание, что тут не требуется добавление '\0' в конец поля d_name, поскольку его предоставляет нам сама функция readdir().
6.1.4. Напишите программу удаления файлов и каталогов, заданных в argv. Делайте stat, чтобы определить тип файла (файл/каталог). Программа должна отказываться удалять файлы устройств. Для удаления пустого каталога (не содержащего иных имен, кроме "." и "..") следует использовать сисвызов
(если каталог не пуст - errno получит значение EEXIST); а для удаления обычных файлов (не каталогов)
Программа должна запрашивать подтверждение на удаление каждого файла, выдавая его имя, тип, размер в килобайтах и вопрос "удалить ?".
* - Именно это имя показывает команда ps -ef
* - Собственно, операционная система характеризуется набором предоставляемых ею системных вызовов, поскольку все концепции, заложенные в системе, доступны нам только через них. Если мы имеем две реализации системы с разным внутренним устройством ядер, но предоставляющие одинаковый интерфейс системных вызовов (их набор, смысл и поведение), то это все-таки одна и та же система! Ядра могут не просто отличаться, но и быть построенными на совершенно различных принципах: так обстоит дело с UNIX-ами на однопроцессорных и многопроцессорных машинах. Но для нас ядро - это "черный ящик", полностью определяемый его поведением, т.е. своим интерфейсом с программами, но не внутренним устройством. Вторым параметром, характеризующим ОС, являются форматы данных, используемые системой: форматы данных для сисвызовов и формат информации в различных файлах, в том числе формат оформления выполняемых файлов (формат данных в физической памяти машины в этот список не входит - он зависим от реализации и от процессора). Как правило, программа пишется так, чтобы использовать соглашения, принятые в данной системе, для чего она просто включает ряд стандартных include-файлов с описанием этих форматов. Имена этих файлов также можно отнести к интерфейсу системы.
* - Таким как таблица процессов, таблица открытых файлов (всех вместе и для каждого процесса), и.т.п.
* - Суперпользователь (superuser) имеет uid==0. Это "привелегированный" пользователь, который имеет право делать ВСЕ. Ему доступны любые сисвызовы и файлы, несмотря на коды доступа и.т.п.
1.1 Качественно различать вызовы функций и системные вызовы
Многие студенты, которые плохо знакомы с языком C или те, кто редко касается нижней части системы во время разработки, могут подумать, что вызовы функций и системные вызовы - это одно и то же. Потому что в приложении оба абстрагируются до интерфейсов, которые должны вызываться приложением. Фактически, между вызовом функции и системным вызовом все еще есть разница. Давайте получим общее представление с помощью следующего рисунка!
Из рисунка 1-1 мы можем знатьЯдро доступа к приложениям, В основном двумя способами:Интерфейс прерывания и системного вызова.
Один,Прерывания размещаются вне потока программы, поэтому этот метод не является обычным для нас способом доступа к ядру, а скорее является прерыванием, вызванным программными исключениями;
Второй, Интерфейс системного вызова, например использование open, read и write для управления данными файла (эквивалент доступа к жесткому диску).
Вызов функции не может напрямую обращаться к ядру. Вызов функции должен иметь доступ к ядру, и это можно сделать только с помощью системных вызовов.
Таким образом, мы можем качественно различать вызовы функций и системные вызовы:Интерфейсы, которые напрямую обращаются к ядру, относятся к системным вызовам, а остальные - к вызовам функций.。
1.2 Зачем инкапсулировать системные вызовы
мы знаем,32-битная система LinuxЧтобы гарантировать независимость программы, каждому процессу назначается независимое адресное пространство 4G (пространство виртуальной памяти), но адресное пространство 4G разделено на два пространства:Пространство пользователя и пространство ядра,Как показано ниже:
В пользовательском пространстве хранятся общие данные некоторых процессов, такие как код, переменные данные, область кучи и область стека и т. Д. Что требует внимания, так это пространство ядра. Хотя система выделяет 1 ГБ пространства ядра каждому процессу, на самом деле,Пространство ядра разделяется процессами в операционной системе.Публичное использование порождает практическую проблему. Например, мы можем делать что угодно в нашей комнате (пространство пользователя), мы не можем делать все, что хотим, в публичном пространстве (пространство ядра), и наше поведение является публичным (пространство ядра). Он будет ограничен, и то же самое применимо к операционной системе.Пространство ядра совместно используется процессом, и процесс должен гарантировать, что он не повлияет на другие процессы для доступа к ядру.
Как гарантировать отсутствие незаконных операций в общем пространстве ядра в операционной системе процесса, например: Как убедиться, что внутренние данные банка не используются незаконно? Банк предоставляет вам банкомат, и вы можете только выбрать вышеупомянутые операции интерфейса. Операционная система также «научилась» этому режиму, инкапсулируя интерфейсы и обеспечивая правильность операций с данными!
Мало того, что приложение не может напрямую обращаться к пространству ядра, но может получить доступ к пространству ядра только через системные вызовы, но также ядро не доверяет данным пользовательского пространства.Данные, передаваемые через системные вызовы, также будут проверены для обеспечения безопасности ядра. . Это необходимо для обеспечения безопасности ядра. Ядро принадлежит программному ядру системы. Сбой ядра приведет к сбою всей программной системы. Чтобы гарантировать законность данных, передаваемых приложением, и высший секрет данных ядра, copy_from_user обычно используется для проверки и копирования переданных данных, а copy_to_user () используется для проверки и копирования данных в приложение.
2.1 Что называется вызовом функции
Выше представлен простейший процесс вызова функции. Чтобы понять, что такое вызов функции, мы можем понять, что такое функция?
функция (function) Является независимой единицей программного кода, которая выполняет определенную задачу.
----《C Primer Plus》
Давайте разберемся, зачем нам использовать функции?
Прежде всего, использование функций может избавить от необходимости писать повторяющийся код. Если программа выполняет определенный элемент несколько раз. Если программе необходимо выполнить задачу несколько раз, вам нужно только написать подходящую функцию, чтобы использовать эту функцию, когда это необходимо, или использовать функцию в разных программах, точно так же, как многие программы используют putchar ().
Во-вторых, даже если программа выполняет определенную задачу только один раз, стоит использовать функции. Поскольку эта функция делает программу более модульной, что улучшает читаемость программного кода, она более удобна для последующих изменений и улучшений.
----《C Primer Plus》
Вызовы функций хорошо понятны, улучшают возможность повторного использования кода и модульность, вызывают соответствующие функции и получают определенный результат.
2.1 Процесс вызова функции
Анализ процесса вызова функций
Рабочий процесс стека при вызове функции в сборке
3.1 Что такое системный вызов
Для взаимодействия с процессами, запущенными в пользовательском пространстве, ядро предоставляет набор интерфейсов. Через этот интерфейс приложения могут получать доступ к аппаратным устройствам и другим ресурсам операционной системы. Этот набор интерфейсов играет роль посредника между приложением и ядром. Приложение отправляет различные запросы, а ядро отвечает за их удовлетворение (или перевод приложения в режим ожидания).
Дизайн и реализация ядра Linux
3.2 Процесс системного вызова
Практически нет разницы между системным вызовом и вызовом функции в прикладной программе, но процесс внутренней реализации совершенно другой. Вызов функции напрямую переходит к служебной программе функции через адрес входа функции, а системный вызов не переходит напрямую к Системный вызов служебной программы, поскольку адрес ядра недоступен для прикладной программы, ей необходимо войти в режим ядра через прерывание, а затем найти соответствующую служебную программу с помощью ряда операций.
Мы можем понять это по следующему рисунку:
Читайте также: