Как работает программа в ОС на самом деле
Когда пользователь запускает программу, ему кажется, что компьютер просто начинает её выполнять. Но файл на диске — это всего лишь набор байтов. Процессор не умеет выполнять файлы. Он умеет выполнять инструкции только из памяти. Возникает вопрос: кто переносит программу с диска в память? Кто выделяет ей память? Кто следит за тем, чтобы одна программа не уничтожила данные другой? Ответом на все эти вопросы является операционная система. Но как именно она решает эти задачи? Чтобы это понять, нам придётся заглянуть в один из важнейших механизмов современных компьютеров — виртуальную память.
Привет! Я уже какое-то время пишу свою операционную систему в качестве хобби, делая её для x86-64 систем. Недавно я прошёл путь загрузки сторонних программ внутри ОС. Именно как чего-то отдельного, к чему ядро не имеет прямого доступа из кода без должной загрузки в оперативную память (собственно чем, с какой-то стороны, программа и является). Я провёл не одну ночь в борьбе с этими механизмами, поэтому подумал, что будет неплохо рассказать об этом.
Однако, чтобы постичь этого волка в овечьей шкуре, нам нужно пройти небольшой исторический экскурс в то, как разработчики пришли к тому, что мы сейчас имеем.
Проблема №1: память одна, а программ много
Представь себе компьютер начала 70-х. В нём уже есть операционная система, уже существуют программы. Но память устроена предельно просто: каждая программа работает напрямую с физической оперативной памятью.
На первый взгляд это кажется естественным. Если в компьютере есть 64 килобайта памяти, значит программа может использовать эти 64 килобайта. Однако, хоть это и кажется логичным на первый взгляд, давай добавим ещё одну "переменную": две разные программы знать не знают о том, что творится у соседа и на какие куски памяти он претендует.

Уже понятно, к чему это может привести, однако добавим ещё одну переменную в виде самой операционной системы. По-сути, это же тоже программа, которая также имеет виды на оперативную память. Итого мы имеем целые три проблемы: программы могут случайно перезаписать и повредить память соседа или ОС; сложно держать несколько работающих программ; программы привязаны к конкретному расположению в памяти.
Инженеры постепенно пришли к закономерной мысли: а что если программа вообще не должна знать, где находятся её данные в памяти? Так и появилась концепция виртуальной памяти. Вместо того, чтобы показывать программе реальную оперативную память компьютера, операционная система создаёт для неё иллюзию "собственного компьютера". Каждому процессу кажется, что он владеет непрерывным адресным пространством только для себя, поэтому может работать так, будто является единственной программой в системе здесь и сейчас:

Проблема №2: программы имеют разный размер
Возвращаемся к нашему компьютеру начала 70-х. Даже если две программы с ОС смогли договориться о расположении в оперативной памяти, остаётся проблема, известная и для жёстких дисков (HDD) - фрагментация. Допустим, программа №1 занимает 10 КБ, а №2 - 15 КБ. Расположены в памяти они примерно так:

А затем происходит две вещи: программа №1 завершила свою работу, а пользователь запустил программу №3 с весом 15 КБ. Что тогда произойдёт?

А произошло вот что: так как программа №1 завершила работу, она выгрузилась из ОЗУ, освободив те самые 10 КБ, что занимала. Но вот дилемма, программа №3 никак не сможет поместиться в эти 10 КБ, потому что занимает 15 КБ. Поэтому она и расположится там, где влезет - после программы №2.
В 10-килобайтовый участок, конечно, может влезть несколько программ меньшего размера, но это не решит главную проблему - фрагментацию. Однако, и на это у инженеров появилась идея: что если мы возьмём и разделим всю физическую память на равные участки и будем не просто создавать виртуальное пространство памяти, а ещё, чтобы это пространство собиралось из разбросанных кусков? Так и появился пейджинг — механизм, при котором память делится на страницы (те самые равномерные участки памяти), а виртуальная память их "склеивает" так, чтобы работающая программа не замечала, что на деле она скачет по куче разных участков физической оперативной памяти, так как это уже работа процессора:

Теперь, даже если мы выгрузим программу №1 из памяти и загрузим №3, она просто займёт те участки, на которых раньше была программа №1 и доберёт себе ещё страниц по необходимости.
Итог
На самом деле, тонкостей работы у управления памяти операционной системой ещё великое множество. Но, собрав эти два главных механизма, мы получаем виртуальную память, которая примерно в таком виде и используется на большинстве современных компьютеров, работающих под управлением чипов от AMD и Intel. Эти механизмы являются неотъемлемой частью x86-64 архитектуры.
Если тебе интересны нюансы реализации или хочется почитать более углубленно об этой теме, то советую глянуть на:
- OSDev Wiki – главный ресурс у современных разработчиков, делающих ОС как пет-проекты (статьи про управление памятью, пейджинг и аллокацию страниц)
- Интересная статья на Хабре – тык
- Серия видеоуроков на YouTube - тык
Также, если тебе в целом интересна разработка ОС и ты готов с головой окунуться в это, сразу скажу, нелёгкое дело, то крайне рекомендую прочитать от корки до корки книгу Эндрю Танненбаума — "Операционные Системы". Пока что я ни разу не пожалел о прочтении во время работы над своей ОС.
Ну и, если интересно, вот репозиторий моей ОС – EtherOS. Спасибо за прочтение и до новых встреч!