Как происходит запуск выполнения программы на уровне операционной системы?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как происходит запуск выполнения программы на уровне операционной системы?
Процесс запуска программы на уровне ОС — это сложная многоэтапная операция, которая включает взаимодействие между пользовательским пространством, ядром операционной системы и аппаратурой. Давайте разберёмся в деталях.
Основные этапы запуска программы
1. Загрузка исполняемого файла
Когда вы запускаете программу, ОС находит исполняемый файл (например, .exe на Windows или ELF на Linux) и проверяет его формат. Для Python скрипта это выглядит так:
# При выполнении: python script.py
# ОС сначала находит интерпретатор Python
# Затем передаёт ему файл script.py в качестве аргумента
2. Создание процесса
ОС создаёт новый процесс с помощью системного вызова:
- На Linux/Unix:
fork()иexec()(или простоexecve()) - На Windows:
CreateProcess()
import os
import subprocess
# На уровне Python это выглядит так:
process = subprocess.Popen(["python", "script.py"])
# Под капотом происходит fork() + execve()
Структура виртуального адресного пространства процесса
Когда процесс создан, ОС выделяет ему виртуальное адресное пространство (обычно 4 ГБ на 32-битных системах, 2^48 на 64-битных):
┌─────────────────────────────────────┐
│ Переменные окружения и аргументы
│ (Environment & Arguments)
├─────────────────────────────────────┤
│ Stack (растёт вниз)
│ (локальные переменные, возвращаемые адреса)
├─────────────────────────────────────┤
│ Heap (растёт вверх)
│ (динамическая память)
├─────────────────────────────────────┤
│ Uninitialized data (BSS)
│ (глобальные переменные без инициализации)
├─────────────────────────────────────┤
│ Initialized data (Data segment)
│ (инициализированные глобальные переменные)
├─────────────────────────────────────┤
│ Text (Code segment) - R/O
│ (сам код программы)
└─────────────────────────────────────┘
Инициализация процесса
Загрузка динамических библиотек:
# При импорте модулей Python, ОС загружает зависимые библиотеки
import numpy as np # ОС загружает libnumpy.so и её зависимости
# На уровне ОС это работает через динамический компоновщик (ld.so)
# Он резолвит символы и выполняет relocations
Вызов конструкторов и инициализаторов:
Перед вызовом main() ОС:
- Инициализирует runtime (для Python это инициализация интерпретатора)
- Загружает все необходимые библиотеки
- Выполняет конструкторы глобальных объектов (если есть)
# Пример: при запуске Python интерпретатора
python -c "import sys; print(sys.executable)"
# ОС выполняет множество инициализаций за кулисами
Распределение памяти
Stack:
def function():
x = 10 # x находится в stack
y = 20 # y находится в stack
return x + y
# Stack автоматически очищается при выходе из функции
Heap:
# Список растёт в heap
my_list = [1, 2, 3, 4, 5]
# При добавлении элемента, может потребоваться reallocation
my_list.append(6)
# Garbage collector отслеживает, когда память больше не нужна
Управление памятью и виртуальная память
ОС использует виртуальную память — абстракцию над физической оперативной памятью. Каждый процесс думает, что имеет все адресное пространство для себя.
import os
import psutil
# Получить информацию о памяти процесса
process = psutil.Process(os.getpid())
mem_info = process.memory_info()
print(f"RSS (Resident Set Size): {mem_info.rss / 1024 / 1024:.2f} MB")
print(f"VMS (Virtual Memory Size): {mem_info.vms / 1024 / 1024:.2f} MB")
# RSS — реально используемая физическая память
# VMS — общий размер виртуального адресного пространства
Трансляция адресов (Page table):
Процессор использует TLB (Translation Lookaside Buffer) и page tables для трансляции виртуальных адресов в физические. Если страница отсутствует в памяти — происходит page fault, и ОС загружает её с диска.
Контекст процесса (Process Context)
ОС сохраняет полный контекст каждого процесса:
# На уровне Python вы не имеете прямого доступа к контексту,
# но вот что сохраняет ОС:
"""
Контекст процесса включает:
- Регистры процессора (EIP, ESP, EAX и т.д.)
- PID (Process ID)
- GID (Group ID)
- UID (User ID)
- Дескрипторы открытых файлов (file descriptor table)
- Таблица страниц (page table)
- Сигналы (signal handlers)
- Переменные окружения
"""
Управление процессами
import subprocess
import time
# Создание процесса
process = subprocess.Popen(["python", "long_running_script.py"])
# Получить PID
print(f"PID: {process.pid}")
# Отправить сигнал SIGTERM
process.terminate() # На уровне ОС: kill(pid, SIGTERM)
# Ждать завершения
return_code = process.wait()
print(f"Return code: {return_code}")
Многопроцессность и планирование
ОС использует планировщик для управления несколькими процессами. Каждому выделяется time slice процессорного времени (обычно 10-100 ms).
from multiprocessing import Process
import time
def worker(name):
for i in range(3):
print(f"{name}: {i}")
time.sleep(0.1)
# ОС планирует параллельное выполнение процессов
p1 = Process(target=worker, args=("Process-1",))
p2 = Process(target=worker, args=("Process-2",))
p1.start()
p2.start()
p1.join()
p2.join()
Завершение процесса
Когда процесс завершается:
- ОС закрывает все открытые файлы и ресурсы
- Освобождает всю выделенную память
- Удаляет процесс из таблицы процессов
- Отправляет сигнал SIGCHLD родительскому процессу
import sys
def cleanup():
print("Очистка ресурсов...")
# Закрыть файлы, БД соединения и т.д.
try:
# Основной код программы
pass
finally:
cleanup() # Гарантированный вызов при выходе
sys.exit(0) # Завершение процесса с кодом 0
Таким образом, запуск программы — это целый оркестр действий ОС, включающий создание процесса, распределение памяти, загрузку библиотек и инициализацию runtime. Python как интерпретируемый язык добавляет свой слой абстракции поверх этого процесса.