Я не отвечаю на вопросы по порядку, поэтому я ставлю свои ответы перед вопросами.Я позволил себе немного их отредактировать.Вы не указали архитектуру процессора, но я предполагаю, что вы хотите знать о x86, поэтому детали уровня процессора будут относиться к x86.Другие архитектуры могут вести себя по-разному (управление памятью, как выполняются системные вызовы и т. Д.).Я также использую Linux для примеров.
Генерирует ли компилятор c исполняемый код, который можно запускать прямо на процессоре без необходимости в помощи ОС, пока не будет достигнут системный вызов, и в этот момент онсделать что-нибудь для загрузки инструкций ОС?
Да, это правильно.Компилятор генерирует машинный код, который может быть запущен прямо на процессоре.Однако исполняемые файлы, которые вы получаете от компилятора, содержат как код, так и другие необходимые данные, например, инструкции о том, где загрузить код в память.В Linux формат ELF обычно используется для исполняемых файлов.
Если процесс полностью загружен в память и имеет достаточно места в стеке, ему не потребуется дополнительная помощь ОС, прежде чем он захочет выполнить системный вызов.Когда вы делаете системный вызов, это просто инструкция в машинном коде, которая вызывает ОС.Сама программа никак не должна «загружать инструкции ОС».Процессор обрабатывает передачу выполнения в код ОС.
В Linux с архитектурой x86 один из способов, с помощью которого машинный код выполняет системный вызов, - это использование вектора 128 программных прерываний для передачи выполнения в операционную систему.В сборке x86 (синтаксис Intel) это выражается int 0x80
.Затем Linux будет выполнять задачи, основываясь на значениях, которые вызывающая программа помещает в регистры процессора перед выполнением системного вызова: номер системного вызова находится в регистре процессора eax
, а параметры системного вызова находятся в других регистрах процессора.После завершения работы ОС она вернет результат в регистр eax
и, возможно, изменила буферы, на которые указывают параметры системного вызова и т. Д. Обратите внимание, что это не единственный способ сделать системный вызов.
Однако, если процесс не полностью находится в памяти, а выполнение перемещается в ту часть кода, которой в данный момент нет в памяти, процессор вызывает сбой страницы, который перемещает выполнение в операционную систему, которая затемзагружает требуемую часть процесса в память и передает выполнение обратно процессу, который затем может продолжить выполнение в обычном режиме, даже не заметив, что что-то произошло.
Я не совсем уверен в следующем пункте, поэтому возьмитеэто с зерном соли.Статья в Википедии о переполнении стека (ошибка компьютера, а не этого сайта :), похоже, указывает на то, что стеки обычно имеют фиксированный размер, поэтому int x;
не должно вызывать запуск ОС, если только эта частьстек не находится в памяти (см. предыдущий абзац).Если у вас была система с динамическим размером стека (если это даже возможно, но, насколько я вижу, так и есть), int x;
может также вызвать сбой страницы при использовании пространства стека, побуждая операционную систему квыделить больше места в стеке для процесса.
Ошибки страницы приводят к перемещению выполнения в операционную систему, но не являются системными вызовами в обычном смысле этого слова.Системные вызовы - это явные вызовы ОС, когда вы хотите, чтобы она выполнила некоторую работу за вас.Ошибки страницы и другие подобные события неявны.Аппаратные прерывания непрерывно переносят выполнение из вашего процесса в ОС, чтобы он мог реагировать на них.После этого он передает выполнение обратно вашему процессу или другому процессу.
В многозадачной ОС вы можете запускать много программ одновременно, даже если у вас только один процессор / ядро. Это достигается путем запуска только одной программы за раз, но быстрого переключения между программами. Аппаратное прерывание по таймеру обеспечивает своевременную передачу управления обратно в ОС, так что один процесс не может полностью задействовать процессор. Когда управление передается ОС и оно делает то, что ему нужно, он всегда может начать процесс, отличный от того, который был прерван. ОС обрабатывает все это совершенно прозрачно, поэтому вам не нужно об этом думать, и ваш процесс этого не заметит. С точки зрения вашего процесса, он выполняется непрерывно.
Вкратце: Ваша программа выполняет системные вызовы только тогда, когда вы явно просите об этом. Операционная система может также обменивать части вашего процесса в памяти и из нее, когда она этого хочет, и обычно выполняет действия, связанные и не связанные с вашим процессом, в фоновом режиме, но обычно вам вообще не нужно об этом думать. (Тем не менее, вы можете уменьшить количество сбоев страниц, сделав вашу программу настолько маленькой, насколько это возможно, и тому подобное)
В этом случае open()
- это явный системный вызов, но я полагаю, что когда оболочка его запускает, он выполняет несколько сотен других системных вызовов для его реализации.
Нет, оболочка не имеет ничего общего с вызовом open()
в вашей c-программе. Ваша программа выполняет один системный вызов, и оболочка вообще не появляется.
Оболочка будет влиять на вашу программу только при запуске. Когда вы запускаете вашу программу с оболочкой, оболочка выполняет системный вызов fork
, чтобы отключить второй процесс, а затем выполняет системный вызов execve
, чтобы заменить себя вашей программой. После этого ваша программа находится под контролем. Перед тем как элемент управления перейдет к вашей функции main()
, он выполняет некоторый код инициализации, который был добавлен компилятором. Если вы хотите увидеть, какие системные вызовы вызывает процесс, в Linux вы можете использовать strace
для их просмотра. Просто скажите strace ls
, например, чтобы увидеть, что системные вызовы ls
делают во время его выполнения. Если вы скомпилируете программу на c с помощью только функции main()
, которая немедленно возвращается, вы можете увидеть с помощью strace
, что система вызывает из кода инициализации.
Как процесс получает свою память от компьютера и т. Д.? Он должен снова включать некоторые системные вызовы, верно? Я не уверен, какова граница между системным вызовом и обычными вещами. Все, в конце концов, нуждается в помощи ОС, верно?
Да, системные вызовы. Когда ваша программа загружается в память с помощью системного вызова execve
, она заботится о том, чтобы получить достаточно памяти для вашего процесса. Когда вам потребуется больше памяти и вызовите malloc()
, он сделает системный вызов brk
, чтобы увеличить сегмент данных вашего процесса, если он исчерпал внутренне кэшированную память, чтобы дать вам.
Не всем нужна явная помощь от ОС. Если у вас достаточно памяти, все ваши входные данные находятся в памяти, и вы записываете свои выходные данные в память, вам вообще не понадобится ОС. То есть, пока вы выполняете вычисления только для данных, которые у вас уже есть в памяти, вам не нужно больше памяти и вам не нужно общаться с внешним миром, вам не нужна ОС. С другой стороны, программа, которая вообще не взаимодействует с внешним миром, является довольно бесполезной, потому что она не может получить никакого ввода и не может дать никакого вывода. Даже если вы вычислите миллионное десятичное число от пи, совершенно не имеет значения, если вы не выводите его пользователю.
Этот ответ стал довольно большим, поэтому, если я что-то пропустил или не объяснил что-то достаточно четко, пожалуйста, оставьте мне комментарий, и я постараюсь уточнить. Если кто-то обнаружит какие-либо ошибки, обязательно укажите на них.