ТГК: NetIntelRU
Когда программист пишет код, он редко задумывается о том, что происходит с программой после того, как он её написал. Но понимание этого процесса необходимо для эффективной отладки, оптимизации и написания надёжного кода. А ещё, это просто интересно.
Три пути выполнения

Если код компилируемый (например, написан на C++ или Go), то компилятор берет ваш исходный код (.cpp, .rs) и превращает его в так называемый объектный файл (.o или .obj). Это уже почти машинный код, но в нем могут быть "пробелы", например, вызов функции printf, код которой находится в другой библиотеке.
В работу вступает линковка (линкер/компоновщик/сборщик). Его задача взять один или несколько объектных файлов и склеить их в единый, работоспособный исполняемый файл (.exe или ELF). Он находит все недостающие функции (в других объектных файлах или в системных библиотеках), подставляет их реальные адреса на место "пробелов" и создает итоговый файл, готовый к загрузке в память.
Если код интерпретируемый (Python, JavaScript), то ситуация иная. Здесь нет классической компиляции и линковки перед запуском. Файл, который вы запускаете, это исходный текст. В память загружается сама программа-интерпретатор, а уже она получает путь к вашему скрипту. То есть, процесс создается для интерпретатора, а он уже, в свою очередь, будет читать и выполнять ваш код построчно в реальном времени.
Существует и гибридный подход (JIT-компиляция в Java или C#), когда исходный код сначала компилируется в промежуточный байт-код CIL (промежуточный код между высокоуровневым исходным кодом, который пишут разработчики, и машинным кодом, исполняемым процессором), а специальная среда выполнения (виртуальная машина) компилирует его в машинный код уже в процессе работы программы, совмещая гибкость интерпретации и скорость компиляции.
Теперь, когда у нас есть готовый исполняемый файл или запущен интерпретатор, загрузчик (см. Подготовка среды) наконец-то может сделать свою работу. Он открывает файл, считывает его структуру и раскладывает сегменты кода и данных по тем самым зонам в памяти, которые ОС выделила для процесса.
Подготовка среды
Операционная система готовит для программы пространство в памяти. ОС (Операционная система) - это главный менеджер вашего компьютера. Она постоянно слушает, что вы делаете (через графический интерфейс или командную строку). Когда она получает команду "запустить эту программу", она понимает, что задача найти исполняемый файл и подготовить его к работе.
Первый и самый важный шаг это создание процесса. Программа на диске это пассивный набор инструкций. Чтобы она заработала, ОС создает для нее сущность, называемую процессом. Это изолированная "песочница" с собственными ресурсами, чтобы ваша программа не могла вмешаться в работу других, например, браузера или антивируса.
ОС отрезает в оперативной памяти (RAM) приватный участок и называется он "виртуальное адресное пространство". Это мощная абстракция, которая дает каждому процессу иллюзию, что у него в распоряжении есть вся память компьютера целиком, начиная с адреса 0 и заканчивая очень большим числом (например, 2^64 в 64-битных системах).
В этом пространстве будут жить сам код программы, данные (глобальные переменные) и служебные структуры, такие как стек для вызовов функций и куча для динамической памяти. Эта изоляция - фундамент безопасности и стабильности системы.
Интересный момент не по теме: Если ОС так тщательно изолирует процессы, как тогда программа-вирус может украсть данные, зашифровать файлы или встроиться в систему? Ответ прост: вирус должен получить разрешение от пользователя или ОС на выход из своей "песочницы". Вирус, запущенный от имени обычного пользователя (без прав админа), не может повредить систему, но он может свободно работать с файлами текущего пользователя. Также вирус может использовать уязвимости и внедриться в другие процессы.
Каждому новому процессу ОС присваивает уникальный номер - Process ID. Так она может отличать твою запущенную косынку от десятков других процессов.
Теперь, когда процесс есть в памяти, специальный компонент ОС, "загрузчик", открывает твой исполняемый файл (.exe или файл в формате ELF) и раскладывает его части по заранее размеченным зонам в памяти.
Управление
Планировщик задач
Загрузчик разместил код в памяти, ОС передала управление процессору. Программа ожила и начала выполнять свои инструкции, но она не одна. На компьютере одновременно работают десятки других процессов. Как ОС управляет этим?
Современные процессоры умеют выполнять несколько инструкций одновременно, но одно физическое ядро в каждый момент времени обрабатывает только один поток (если не учитывать технологию Hyper-Threading).
Если у тебя четырёхъядерный процессор с Hyper-Threading, он может одновременно выполнять до восьми потоков (по одному на каждое логическое ядро). Однако в системе обычно работают десятки или даже сотни процессов и потоков, что гораздо больше, чем ядер.
Для этого существует планировщик ОС. Его задача эффективно распределять все процессы и потоки по доступным ядрам. Планировщик выделяет каждому потоку квант времени, обычно от нескольких до десятков миллисекунд, запускает поток на одном из ядер, даёт ему поработать, затем принудительно останавливает его, сохраняет контекст (содержимое регистров, счётчик команд и т.д.) и ставит на выполнение следующий поток.
Когда два потока выполняются одновременно на разных ядрах это истинный параллелизм. А когда планировщик быстро переключается между множеством потоков на одном ядре, создавая иллюзию одновременной работы, это конкурентность. Современные ОС используют оба принципа.
Этот процесс, называемый контекстным переключением, происходит так быстро (тысячи раз в секунду), что для пользователя все выглядит так, будто сотни программ работают параллельно.
Системные вызовы
Ваша программа не может делать все, что захочет. Она не может напрямую читать с диска, писать в сеть или выводить что-то на экран. Это было бы небезопасно и привело бы к полному хаосу. Вместо этого она просит ОС.
Системный вызов это специальный механизм, с помощью которого программа в пользовательском режиме обращается к ядру операционной системы, работающему в привилегированном режиме, чтобы выполнить операцию, недоступную в пользовательском режиме.
Когда программе нужно выполнить привилегированную операцию (например, открыть файл, выделить память или отправить данные по сети), она инициирует системный вызов. Для этого используется специальная инструкция процессора (например, syscall или svc), которая вызывает программное прерывание. Это прерывание приостана��ливает выполнение программы и передаёт управление ядру ОС.
Ядро проверяет запрос, выполняет необходимые действия (например, чтение файла или выделение памяти), а затем возвращает результат программе и снова передаёт ей управление. Если операция требует ожидания, например, данные ещё не загружены с диска, ядро может временно блокировать программу, чтобы не тратить ресурсы процессора на ожидание.
Управление памятью и файлами
Во время работы программы её потребности в ресурсах могут меняться. ОС постоянно отслеживает это. Если программе требуется выделение или освобождение памяти на уровне ОС (например, при увеличении кучи или создании нового сегмента памяти), она делает системный вызов.
Менеджер памяти ОС зарезервирует виртуальное адресное пространство для процесса, а физическую память выделит по мере необходимости (например, при первом обращении к памяти). Если свободной физической памяти нет, ОС может использовать файл подкачки на диске. После выделения памяти ОС обновляет таблицы страниц процесса, чтобы он мог использовать новые блоки. Когда программа завершается, ОС освобождает память и другие ресурсы, возвращая их системе.
ОС ведёт учёт всех файлов, сетевых соединений, мьютексов, семафоров и других ресурсов, которые использует процесс. Это позволяет ей пытаться корректно закрыть все ресурсы, даже если процесс завершится аварийно, и минимизировать риск утечек или блокировок.
Исполнение
Исполнение это процесс, при котором процессор выполняет инструкции программы, преобразованные в машинный код.
Выборка (Fetch)
Процессор имеет специальный регистр - счётчик команд (Program Counter, PC или Instruction Pointer, IP), в котором хранится адрес следующей инструкции, которую нужно выполнить. Этот адрес виртуальный, и он преобразуется в физический с помощью MMU (Memory Management Unit).
Процессор обращается по этому адресу к памяти (в сегмент кода, загруженный загрузчиком) и загружает инструкцию в кэш - сверхбыструю внутреннюю память. Если инструкции нет в кэше, процессор обращается к оперативной памяти. Современные процессоры используют конвейеризацию и предварительную выборку, чтобы заранее загружать инструкции и минимизировать простои.
Конвейеризация это технология, при которой процессор разбивает выполнение инструкций на эти этапы и выполняет их параллельно для разных инструкций.
Благодаря конвейеризации процессор может выполнять несколько инструкций одновременно, даже если у него только одно ядро. Это значительно ускоряет работу программы.
Предварительная выборка это механизм, при котором процессор заранее загружает инструкции и данные в кэш, ещё до того, как они понадобятся.
Предварительная выборка помогает уменьшить простои процессора, которые возникают из-за медленной работы оперативной памяти. Чем точнее процессор предсказывает, что понадобится дальше, тем быстрее выполняется программа.
Декодирование (Decode)
Скопированная инструкция это просто числа. Процессор должен понять, что она означает. Каждая инструкция состоит из кода операции (opcode), который определяет действие, и операндов, указывающих, с какими данными работать.
Устройство управления анализирует opcode и генерирует сигналы для активации нужных компонентов процессора. Например, для инструкции сложения (ADD EAX, EBX) оно отправит сигнал в арифметико-логическое устройство (АЛУ), чтобы выполнить операцию, и в регистры, чтобы сохранить результат.
На этом этапе процессор также извлекает операнды из регистров или памяти. Например, если инструкция требует сложить два числа, процессор загрузит их из указанных регистров в АЛУ.
Пример: Рассмотрим инструкцию MOV EAX, [EBX] (переместить данные из ячейки памяти, адрес которой хранится в EBX, в регистр EAX). На этапе декодирования процессор:
Определяет, что это инструкция перемещения данных (MOV).
Извлекает адрес из регистра EBX.
Подготавливает АЛУ и регистры для передачи данных из памяти в EAX.
В современных процессорах декодирование может быть многоступенчатым и включать микропрограммы для сложных инструкций. Если процессор встречает недопустимую инструкцию, он генерирует исключение, и управление передаётся операционной системе.
Выполнение (Execute)
Где процессор берет числа для операций и куда кладет результат? Для этого у него есть своя сверхбыстрая внутренняя память - регистры.
Давайте посмотрим на этот процесс на простом примере. Возьмем строчку кода: c = a + 5;
После того как процессор декодировал инструкцию, соответствующую этой строке, он переходит к выполнению. Процессор загружает значение переменной a из оперативной памяти в один из своих регистров. Затем он загружает константу 5 в другой регистр (например, в RBX).
Он отдает команду своему АЛУ, чтобы сложить то, что лежит в RAX, с тем, что лежит в RBX. АЛУ выполняет сложение, и результат (например, 23) помещается обратно в регистр RAX. Наконец, процессор сохраняет значение из регистра RAX обратно в оперативную память, по адресу, где находится переменная c.
После выполнения этой последовательности микро-операций процессор обновляет счетчик команд, чтобы он указывал на следующую инструкцию в вашей программе, и весь цикл "выборка-декодирование-выполнение" повторяется снова миллиарды раз в секунду.
Завершение работы и уборка
Завершение работы
Прерывание это сигнал от аппаратного устройства, который говорит процессору остановиться. Оно не связано с текущей выполняемой инструкцией и может произойти в любой момент.
Когда процессор получает прерывание, текущее состояние программы сохраняется в стек. Процессор передает управление специальной функции в ядре ОС - обработчику прерываний. Это заранее подготовленный код для конкретного устройства.
Обработчик прерываний читает данные с клавиатуры, обрабатывает сетевой пакет или, в случае таймера, вызывает планировщик для переключения на другой процесс.
Исключение это событие, сгенерированное самим процессором в ответ на ошибку или особую ситуацию во время выполнения инструкции. Это синхронное событие, напрямую связанное с выполняемой инструкцией.
Это может быть деление на ноль, обращение к неверной памяти или системный вызов. Для ошибки ОС обычно завершает виновный процесс. Она отправляет ему сигнал, который, если не обработан программой, приводит к ее закрытию. Это защитный механизм, чтобы сбойная программа не повлияла на всю систему.
Для системного вызова ОС выполняет запрошенную операцию и возвращает управление программе, как мы описывали ранее.
При нормальном завершении, программа успешно выполнила все свои задачи и добралась до конца, тогда она закрывается, вызвав специальную функцию, или просто завершает свою главную функцию. При аварийном завершении, процессор генерирует исключение, ОС его перехватывает и решает, что такой процесс жить не может.
Уборка
Как бы программа не завершалась, во всех случаях ОС собирает мусор. У ОС есть таблица, где она отслеживает все файлы, которые открыл процесс. ОС проходит по этому списку и закрывает каждый файл. Это гарантирует, что все буферы на диске будут записаны, а сам файл станет доступен для других программ.
Если процесс устанавливал сетевые соединения, ОС корректно их разрывает, отправляя соответствующие пакеты удаленным хостам. Это освобождает сетевые порты. ОС также освобождает любые другие захваченные ресурсы.
Когда все ресурсы освобождены, ОС забирает обратно память. ОС полностью удаляет структуру, которая описывала виртуальное адресное пространство процесса. Таблицы страниц, которые связывали виртуальные адреса процесса с физической памятью, очищаются.
Вся физическая оперативная память (RAM) и место в файле подкачки, которые были заняты процессом, помечаются как свободные. Теперь они могут быть выделены другим процессам.
Его уникальный номер (PID) теперь свободен и может быть выдан новому процессу. Планировщик задач убирает этот процесс из своих очередей на выполнение.
Сборщик мусора (Garbage Collector, GC) это не механизм операционной системы, а компонент среды выполнения конкретного языка программирования. Если уборка, которую делает ОС, это снос целого процесса, то сборщик мусора это уборка внутри процесса, пока он живёт.
Если понравилась статья - рекомендую подписаться на телеграм‑канал NetIntel. Там вы сможете найти множество полезных материалов по IT и разработке!