- Различать процессы и потоки и объяснять состояние гонки
- Вычислять среднее время ожидания для FCFS, SJF и Round Robin
- Переводить виртуальный адрес в физический и подсчитывать страничные прерывания
- Применять четыре условия взаимной блокировки и алгоритм банкира
На ноутбуке одновременно работают браузер, музыка, мессенджер и антивирус, хотя ядер у процессора всего несколько, а память ограничена. Каждая программа «думает», что весь компьютер принадлежит ей, — эту иллюзию создаёт операционная система (ОС). ОС делит процессорное время, выдаёт каждой программе собственную память, организует файлы на диске и не даёт программам заблокировать друг друга.
Процессы, потоки и планирование
Процесс — выполняющаяся программа: у него собственное виртуальное адресное пространство (код, данные, куча), открытые файлы и хотя бы один поток. Поток (thread) — последовательность выполнения внутри процесса: у него свой стек, регистры и PC, но память процесса он делит с другими потоками. Потоки дешевле создавать и переключать, чем процессы, но общая память требует синхронизации.
Два потока увеличивают общую переменную counter = 5 на 1. Каждый делает три шага: чтение (tmp = counter), прибавление (tmp + 1), запись (counter = tmp). Каким будет результат при таком чередовании: A читает, B читает, A прибавляет, A пишет, B прибавляет, B пишет?
Показать решениеСкрыть решение
A: tmpA = 6, counter = 6. B: tmpB = 6, counter = 6.
После двух увеличений получилось 6 вместо 7 — одно обновление «потерялось». Результат зависит от чередования: это состояние гонки. Решение: защитить критическую секцию мьютексом (блокировкой), чтобы «чтение–прибавление–запись» стало неделимым.
Готовые процессы ждут в очереди, а планировщик решает, кому достанется процессор. FCFS — в порядке поступления; SJF — сначала самая короткая задача (минимизирует среднее время ожидания, но требует заранее знать длину задач); Round Robin — каждый процесс получает квант времени q, а затем уходит в конец очереди. Каждое переключение требует смены контекста: сохранения и восстановления регистров.
- Wсреднее время ожидания
- Cᵢмомент завершения процесса i
- Aᵢмомент поступления
- Bᵢдлительность работы на процессоре (burst)
Процессы P1 = 10, P2 = 4, P3 = 2 мс поступили в момент 0 в этом порядке. Найди среднее время ожидания для FCFS, SJF и Round Robin (q = 3).
Показать решениеСкрыть решение
SJF: P3 0–2, P2 2–6, P1 6–16 → ожидание 0, 2, 6 → W = 8/3 ≈ 2,67 мс.
RR, q = 3: P1 0–3, P2 3–6, P3 6–8 (готов), P1 8–11, P2 11–12 (готов), P1 12–16 (готов).
Ожидание = завершение − работа: P1 16 − 10 = 6, P2 12 − 4 = 8, P3 8 − 2 = 6 → W = 20/3 ≈ 6,67 мс.
RR проигрывает SJF по ожиданию, зато каждый процесс начинает обслуживаться в течение 6 мс — а для интерактивных систем важно именно это.
from collections import deque
def fcfs(bursts):
t, waits = 0, []
for b in bursts:
waits.append(t)
t += b
return waits
def round_robin(bursts, q):
rem, t, done = list(bursts), 0, [0] * len(bursts)
queue = deque(range(len(bursts)))
while queue:
i = queue.popleft()
run = min(q, rem[i])
t += run
rem[i] -= run
if rem[i]:
queue.append(i)
else:
done[i] = t
return [done[i] - bursts[i] for i in range(len(bursts))]
jobs = [10, 4, 2]
for name, w in [('FCFS', fcfs(jobs)), ('SJF', fcfs(sorted(jobs))), ('RR q=3', round_robin(jobs, 3))]:
print(name, w, 'average wait:', round(sum(w) / len(w), 2))▸ Ожидаемый результат
FCFS [0, 10, 14] average wait: 8.0 SJF [0, 2, 6] average wait: 2.67 RR q=3 [6, 8, 6] average wait: 6.67
Виртуальная память и страничная организация
Каждый процесс видит собственное виртуальное адресное пространство, начинающееся с 0. Память делится на одинаковые страницы (например, 4 КБ), физическая RAM — на такие же кадры; таблица страниц каждого процесса хранит соответствие «страница → кадр». Перевод выполняет аппаратура (MMU), а недавние переводы держит в кэше TLB. Если страницы нет в RAM, происходит страничное прерывание (page fault), и ОС загружает её с диска — так программы могут использовать больше памяти, чем есть RAM, и не видят память друг друга.
- VA, PAвиртуальный и физический адрес
- Pразмер страницы, байт
- p, dномер страницы и смещение внутри неё
При P = 2ᵏ деление — это просто разделение битов: для страниц 4 КБ = 2¹² младшие 12 бит — смещение, остальные — номер страницы.
а) Страница 4 КБ, VA = 0x3A7C, в таблице страниц 3 → 7. Каков физический адрес?
б) Доступ к RAM — 100 нс, обработка страничного прерывания — 8 мс, вероятность прерывания p = 0,001. Каково эффективное время доступа?
Показать решениеСкрыть решение
PA = 7 · 4096 + 2684 = 28 672 + 2684 = 31 356 = 0x7A7C. В шестнадцатеричной записи изменилась только цифра страницы: 0x3A7C → 0x7A7C.
б) EAT = (1 − p) · 100 + p · 8 000 000 нс = 99,9 + 8000 ≈ 8100 нс ≈ 8,1 мкс — одно прерывание на тысячу обращений замедляет память в 81 раз!
Когда RAM заполнена, ОС должна выбрать, какую страницу вытеснить на диск. FIFO вытесняет страницу, загруженную раньше всех, LRU — ту, что дольше всех не использовалась. Теоретический идеал OPT вытесняет страницу, которая понадобится позже всех, но будущее неизвестно, поэтому он служит лишь эталоном.
def faults(refs, frames, policy):
mem, count = [], 0
for p in refs:
if p in mem:
if policy == 'LRU':
mem.remove(p)
mem.append(p)
continue
count += 1
if len(mem) == frames:
mem.pop(0)
mem.append(p)
return count
refs = [1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5]
for frames in [3, 4]:
print(frames, 'frames: FIFO', faults(refs, frames, 'FIFO'), '| LRU', faults(refs, frames, 'LRU'))
page_size, va = 4096, 0x3A7C
page, offset = divmod(va, page_size)
page_table = {3: 7}
pa = page_table[page] * page_size + offset
print('page', page, 'offset', offset, '-> physical', pa, hex(pa))▸ Ожидаемый результат
3 frames: FIFO 9 | LRU 10 4 frames: FIFO 10 | LRU 8 page 3 offset 2684 -> physical 31356 0x7a7c
Файловые системы
Диск делится на блоки фиксированного размера (обычно 4 КБ). В файловых системах Unix (ext4) индексный дескриптор (inode) каждого файла хранит размер, владельца, права, временные метки и указатели на блоки данных; каталог — это просто таблица «имя → номер inode». В классическом inode ext2/ext3 12 прямых указателей и одно-, двух- и трёхуровневые косвенные: при блоках 4 КБ и 4-байтовых указателях прямые блоки покрывают 48 КБ, одноуровневый косвенный — ещё 1024 · 4 КБ = 4 МБ, двухуровневый — 4 ГБ, трёхуровневый — 4 ТБ. Журналирование (ext4, NTFS) сначала записывает изменения в журнал, чтобы файловая система пережила отключение питания. В FAT32 один файл должен быть меньше 4 ГБ.
Взаимная блокировка (deadlock)
Поток A держит блокировку 1 и ждёт блокировку 2, а поток B держит 2 и ждёт 1 — никто не может продвинуться. Взаимная блокировка возможна, когда одновременно выполняются четыре условия Коффмана: взаимное исключение (ресурсом пользуется один), удержание и ожидание, отсутствие вытеснения (ресурс не отбирают) и циклическое ожидание. Достаточно нарушить любое; самый практичный способ — всегда захватывать блокировки в одном глобальном порядке, что исключает циклическое ожидание.
В системе 10 одинаковых ресурсов. P0: максимум 7, держит 3; P1: максимум 3, держит 1; P2: максимум 6, держит 3. Безопасно ли состояние? Если P0 запросит ещё 2, можно ли их выдать?
Показать решениеСкрыть решение
P1: 2 ≤ 3 → завершается и возвращает → свободно 4. P2: 3 ≤ 4 → свободно 7. P0: 4 ≤ 7 → свободно 10.
Есть безопасная последовательность P1, P2, P0 → состояние безопасно.
Если выдать P0 ещё 2: P0 держит 5, нужно 2, свободно 1. Ни у кого потребность не ≤ 1 (P0 2, P1 2, P2 3) → небезопасное состояние, запрос откладывается. Небезопасное — ещё не взаимная блокировка, но ОС не рискует.
Главное
- У процесса собственное адресное пространство; его потоки делят память, поэтому общие данные нужно защищать блокировками.
- SJF минимизирует среднее ожидание, Round Robin сокращает время отклика; ожидание = завершение − поступление − работа.
- Виртуальный адрес = страница + смещение; PA = frame · P + d; страничные прерывания резко увеличивают эффективное время доступа.
- LRU обычно лучше FIFO; у FIFO возможна аномалия Белади.
- Для взаимной блокировки нужны все четыре условия; единый порядок захвата и алгоритм банкира её предотвращают.
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.