Как системный вызов переводится в инструкции CPU?

Скажем, есть простая программа, например:

#include<stdio.h>

void main() 
{ 
    int x;
    printf("Cool");
    fd = open("/tmp/cool.txt", O_READONLY)
}

open - системный вызов здесь. Я полагаю, что когда оболочка запускает его, он выполняет несколько сотен других системных вызовов для его реализации? Как насчет объявления типа int x - в какой-то момент он должен иметь некоторые дополнительные системные вызовы на заднем плане, чтобы получить память с компьютера?

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

Или это похоже на то, что C генерирует исполняемый код (код), который может быть запущен на процессоре, и не требуется никакой поддержки ОС, пока не будет достигнут системный вызов - в этот момент он должен что-то сделать для загрузки инструкций ОС и т.д....

Немного туманно:) Просьба уточнить.

Ответ 1

Я не отвечаю на вопросы в порядке, поэтому я предопределяю свои ответы на вопросы. Я позволил себе немного отредактировать их. Вы не указали архитектуру процессора, но я предполагаю, что вы хотите знать о x86, поэтому детали на уровне процессора будут относиться к x86. Другие архитектуры могут вести себя по-разному (управление памятью, способы создания системных вызовов и т.д.). Я также использую Linux для примеров.

Скомпилирует ли компилятор c исполняемый код, который можно запустить прямо на процессоре, без необходимости поддержки ОС до тех пор, пока не будет достигнут системный вызов, и в какой момент он должен что-то сделать для загрузки инструкций ОС?

Да, это правильно. Компилятор генерирует собственный машинный код, который можно запускать прямо на процессоре. Однако исполняемые файлы, которые вы получаете от компилятора, содержат как код, так и другие необходимые данные, например инструкции о том, где загрузить код в памяти. В Linux формат ELF обычно используется для исполняемых файлов.

Если процесс полностью загружен в память и имеет достаточное пространство стека, ему больше не понадобится помощь ОС, прежде чем он захочет сделать системный вызов. Когда вы выполняете системный вызов, это всего лишь инструкция в машинный код, вызывающий ОС. Сама программа не требует "загрузки инструкций ОС". Процессор обрабатывает выполнение кода ОС.

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

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

Я не совсем уверен в следующей точке, поэтому возьмите его с солью. Статья в Wikipedia о переполнении стека (ошибка компьютера, а не этот сайт:), кажется, указывает, что стеки обычно имеют фиксированный размер, поэтому int x; не должен вызывать запуск ОС, если только эта часть стека не находится в памяти (см. предыдущий параграф). Если у вас была система с динамическим размером стека (если это даже возможно, но, насколько я вижу, это так), int x; также может вызвать ошибку страницы, когда пространство стека исчерпано, что побуждает операционную систему к выделить больше пространства стека для процесса.

Ошибки страниц заставляют выполнение переходить в операционную систему, но не являются системными вызовами в обычном смысле слова. Системные вызовы - это явные вызовы ОС, когда вы хотите, чтобы они выполняли какую-то работу для вас. Ошибки страниц и другие подобные события являются неявными. Аппаратные прерывания непрерывно переносят исполнение из вашего процесса в ОС, чтобы он мог реагировать на них. После этого он переносит выполнение обратно в ваш процесс или какой-либо другой процесс.

В многозадачной ОС вы можете запускать сразу несколько программ, даже если у вас есть только один процессор/ядро. Это достигается за счет запуска только одной программы за раз, но быстрого переключения между программами. Прерывание аппаратного таймера гарантирует, что управление будет передано обратно в ОС своевременно, так что один процесс не сможет полностью запустить процессор. Когда управление передается ОС, и он выполнил все, что ему нужно, он может всегда начинать другой процесс с того, который был прерван. ОС полностью управляет всем этим, поэтому вам не нужно об этом думать, и ваш процесс не заметит этого. С точки зрения вашего процесса он выполняется непрерывно.

Короче:Ваша программа выполняет системные вызовы только тогда, когда вы явно запрашиваете ее. Операционная система также может заменять части вашего процесса в памяти и из памяти, когда захочет, и вообще делает вещи, связанные и не связанные с вашим процессом, в фоновом режиме, но вам обычно не нужно об этом думать вообще. (Тем не менее, вы можете уменьшить количество ошибок страницы, сохраняя при этом вашу программу как можно меньше и т.д.)

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

Нет, оболочка не имеет никакого отношения к вызову open() в вашей программе c. Ваша программа делает один системный вызов, а оболочка вообще не попадает в изображение.

Оболочка затронет вашу программу только при ее запуске. Когда вы запускаете свою программу с помощью оболочки, оболочка выполняет системный вызов fork для разблокировки второго процесса, который затем выполняет системный вызов execve, чтобы заменить себя на вашу программу. После этого ваша программа находится под контролем. Прежде чем элемент управления получит вашу функцию main(), однако, он выполняет некоторый код инициализации, который был поставлен там компилятором. Если вы хотите увидеть, какие системные вызовы вызывают процесс, в Linux вы можете использовать strace для их просмотра. Просто скажите strace ls, например, чтобы узнать, какие системные вызовы ls делает во время его выполнения. Если вы скомпилируете программу c только с функцией main(), которая немедленно возвращается, вы можете увидеть с помощью strace, что система вызывает код инициализации.

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

Да, системные вызовы. Когда ваша программа загружается в память при системном вызове execve, она заботится о получении достаточного объема памяти для вашего процесса. Когда вам понадобится больше памяти и вызовите malloc(), он сделает системный вызов brk, чтобы увеличить сегмент данных вашего процесса, если он исчерпал внутренне кэшированную память, чтобы дать вам.

Не все нуждается в явной помощи от ОС. Если у вас достаточно памяти, все ваши данные в памяти, и вы записываете выходные данные в память, вам вообще не понадобится ОС. То есть, пока вы выполняете только вычисления данных, которые у вас уже есть в памяти, вам не нужно больше памяти, и вам не нужно общаться с внешним миром, вам не нужна ОС. С другой стороны, программа, которая вообще не взаимодействует с внешним миром, является довольно бесполезной, потому что она не может получить никакого ввода и не может дать никакого вывода. Даже если вы вычисляете миллионный десятичный знак pi, это не имеет значения, если вы не выводите его пользователю.

Этот ответ получился довольно большой, поэтому, если я что-то пропустил или не объяснил что-то достаточно ясно, пожалуйста, оставьте мне комментарий, и я попытаюсь уточнить. Если кто-либо обнаруживает какие-либо ошибки, обязательно укажите их также.