- •1.Архитектура операционных систем
- •1.1Общие вопросы архитектуры операционных систем
- •1.2Архитектура Windows
- •1.2.1История возникновения Windows
- •1.2.2Архитектура ос Windows
- •1.2.3История возникновения ос Linux
- •1.2.4Архитектура Linux
- •1.2.5Интерфейсы системы unix
- •1.2.6Файловая система unix
- •1.2.7Аутентификация в unix
- •1.2.8Сценарии командной оболочки unix
- •1.3Операционная система qnx
- •1.3.1 Архитектура qnx
- •1.4Выводы
- •1.5Вопросы для самоконтроля
- •2.Типы и алгоритмы работы с оперативной памятью
- •2.1Общие принципы функционирования подсистемы памяти в ос
- •2.1.1Обобщённые принципы управления памятью
- •2.1.2Однозадачная система без подкачки на диск
- •2.1.3Многозадачность с фиксированными разделами
- •2.1.4Подкачка
- •2.1.5Управление памятью с помощью битовых массивов
- •2.1.6Управление памятью с помощью связанных списков
- •2.1.7Виртуальная память
- •2.1.8Многоуровневые таблицы страниц
- •2.1.9Алгоритмы замещения страниц
- •2.2Виртуальная память ос Windows
- •2.2.1Архитектура памяти в ос Windows
- •2.2.2Работа с виртуальной памятью в ос Windows
- •2.2.3Использование виртуальной памяти в приложениях
- •2.3Пример организации страничной памяти на примере linux
- •2.3.1Страничная организация памяти в Linux
- •2.3.2Права доступа к области памяти
- •2.3.3Работа с областями памяти в Linux
- •3.Процессы и потоки
- •3.1Процессы
- •3.1.1Модель процесса
- •3.1.2Создание процесса
- •3.1.3Завершение процесса
- •3.1.4Состояния процессов
- •3.1.5Реализация процессов
- •3.2Потоки
- •3.2.1Реализация потоков
- •3.2.2Реализация потоков на уровне ядра
- •3.2.3Смешанная реализация
- •3.2.4 Метод управления «Активация планировщика»
- •3.2.5Всплывающие потоки
- •3.3Межпроцессное взаимодействие
- •3.3.1Состояние состязания
- •3.3.2Критические секции (Критические области)
- •3.3.3Взаимное исключение с активным ожиданием
- •3.3.4Примитивы межпроцессного взаимодействия
- •3.4Семафоры
- •3.5Мьютексы
- •3.6Организация многопоточной обработки в среде Windows
- •3.6.1Объекты ядра Windows
- •3.6.2Потоки Windows
- •3.6.3Синхронизация потоков в Windows
- •3.6.4Синхронизация потоков с помощью объектов ядра
- •3.6.5Сравнение объектов, используемых для синхронизации потоков
- •3.7Организация процессов и потоков в Linux
- •3.7.1Среда окружения в Linux
- •3.7.2Создание нового процесса. Системный вызов exec.
- •3.7.3Потоки unix. Функции потоков стандарта posix.
- •3.8Синхронизация потоков в unix
- •3.8.1Мьютексы
- •3.8.2Семафоры
- •0,0,0, //Ожидать обнуления семафора
- •0,1,0 // Затем увеличить значение семафора на 1};
- •0,1, 0 // Увеличитьзначение семафора на 1};
2.2Виртуальная память ос Windows
2.2.1Архитектура памяти в ос Windows
В ОС Windows каждому процессу выделяется собственное виртуальное адресное пространство. Для 32-разрядных процессов его размер составляет 4 Гб. Соответственно 32-битный указатель может быть любым числом от 0x00000000 до 0xFFFFFFFF. Всего, таким образом, указатель может принимать 4 294 967 296 значений, что является количеством байтов в 4-гигабайтовом диапазоне. Для 64-разрядных процессов размер адресного пространства равен 16 экзабайтам, поскольку 64-битный указатель может быть любым числом от 0x00000000 00000000 до 0xFFFFFFFF FFFFFFFF и принимать 18 446 744 073 709 551 616 значений, что равняется16 экзабайтам.
Поскольку каждому процессу отводится закрытое изолированное адресное пространство, то, когда в процессе выполняется какой-нибудь поток, он получает доступ только к той памяти, которая принадлежит его процессу. Память, отведенная другим процессам, скрыта от этого потока и недоступна ему.
В Windows память, принадлежащая собственно операционной системе, тоже скрыта от любого выполняемого потока. Иными словами, ни один поток не может случайно повредить ее данные. В Windows 98 такой подход реализован частично, и есть вероятность, что выполняемый поток, случайно получив доступ к данным операционной системы, тем самым нарушит ее нормальную работу. Тем не менее, в Windows 98, ни один поток не может получить доступ к памяти чужого процесса.
Рассмотрим пример. Допустим, что процесс А в своем адресном пространстве может хранить какую-то структуру данных по адресу 0x12345678, и одновременно у процесса В по тому же адресу — но уже в его адресном пространстве — может находиться совершенно иная структура данных. Обращаясь к памяти по адресу 0x12345678, потоки, выполняемые в процессе А, получают доступ к структуре данных процесса А, Но, когда по тому же адресу обращаются потоки, выполняемые в процессе В, они получают доступ к структуре данных процесса В. Иначе говоря, потоки процесса А не могут обратиться к структуре данных в адресном пространстве процесса В, и наоборот.
Адресное пространство процесса виртуальное, а не физическое. Прежде чем обратиться к каким-либо данным, не вызвав нарушения доступа, необходимо спроецировать нужную часть адресного пространства на конкретный участок физической памяти, что фактически означает добавление записи в таблицу страниц.
Виртуальное адресное пространство каждого процесса разбивается на разделы и страницы. Размер страницы различен и во многом зависит от аппаратной платформы, для которой реализована ОС. Помимо страничного разбиения, виртуальная память в Windows разбивается на разделы. Их размер и назначение в какой-то мере зависят от конкретного ядра Windows и представлены в табл. 2.1
Таблица 2.1. Наименование разделов памяти в Windows
Раздел |
32-разрядная Windows 2000/XP (на х86) |
32-разрядная Windows 2000/XP (на х86 с ключом /3GB) |
64-разрядная Windows XP (на x86_64) |
Windows 98 |
Для выявления нулевых указателей |
0x00000000 |
0x00000000 |
0x00000000 00000000 |
0x00000000 |
0x0000FFFF |
0x0000FFFF |
0x00000000 0000FFFF |
0x00000FFF |
|
Для совместимости с программами DOS и 16-разрядной Windows |
Hет |
Нет |
Нет |
0x00001000 0x003FFFFF |
Для кода и данных |
0x00010000 |
0x00010000 |
0x00000000 00010000 |
0x00400000 |
пользовательского режима |
0x7FFEFFFF |
0xBFFFFFFF |
0x000003FF FFFEFFFF |
0x7FFFFFFF |
Закрытый, |
0x7FFF0000 |
0xBFFF0000 |
0x000003FF FFFF0000 |
Нет |
размером 64 Кб |
0x7FFFFFFF |
0xBFFFFFFF |
0x000003FF FFFFFFFF |
|
Для общих MMF (файлов, проецируемых в память) |
Нет |
Нет |
Нет |
0x80000000 0xBFFFFFFF |
Для кода и данных |
0x800000000 |
0xC0000000 |
0x00000400 00000000 |
0xC0000000 |
режима ядра |
0xFFFFFFFF |
0xFFFFFFFF |
0xFFFFFFFF |
|
Раздел для выявления нулевых указателей резервируется для того, чтобы разработчики могли выявлять нулевые указатели. Любая попытка чтения или записи в память по этим адресам вызывает нарушение доступа.
Довольно часто в программах, написанных на С/С++, отсутствует необходимый код для обработки ошибок. Например, в следующем фрагменте кода такой обработки нет:
int* pnSomeInteger = (int*) malloc(sizeof(int));
*pnSomeInteger = 5;
При нехватке памяти malloc вернет NULL. Ho код не учитывает эту возможность и при ошибке обратится к памяти по адресу 0x00000000 А поскольку этот раздел адресного пространства заблокирован, возникнет нарушение доступа и данный процесс завершится с сообщением об обращении к недопустимой области памяти.
Раздел для совместимости с программами DOS и 16-разрядной Windows размером 4 Мб в адресном пространстве процесса необходим Windows 98 для поддержки совместимости с программами MS-DOS и 16-разрядной Windows. Обращение к нему из 32-разрядных Windows-приложений может вызвать непредвиденные последствия. Вообще говоря, процессор должен был бы генерировать нарушение доступа при обращении потока к этому участку адресного пространства, но Microsoft не смогла реализовать эту возможность.
В Windows XP программы для MS-DOS и 16-разрядной Windows выполняются в собственных адресных пространствах и 32-разрядные приложения повлиять на них не могут.
В разделе для кода и данных пользовательского режима в Windows располагается закрытая (неразделяемая) часть адресного пространства процесса. Ни один процесс не может получить доступ к данным другого процесса, размещенным в этом разделе. Основной объем данных, принадлежащих процессу, хранится именно здесь. Поэтому приложения менее зависимы от сбоев других приложений, и вся система функционирует устойчивее
В Windows XP сюда загружаются все EXE- и DLL-модули В каждом процессе эти DLL можно загружать по разным адресам в пределах данного раздела, но так делается крайне редко. На этот же раздел отображаются все проецируемые в память файлы, доступные данному процессу
В Windows 98 основные 32-разрядные системные DLL (Kernel32.dll, AdvAPI32.dll, User32.dll и GDI32.dll) загружаются в раздел для общих MMF (memory mapped files -проецируемых в память файлов), a EXE- и остальные DLL-модули — в раздел для кода и данных пользовательского режима. Общие DLL располагаются по одному и тому же виртуальному адресу во всех процессах, но другие DLL могут загружать их (общие DLL) по разным адресам в границах раздела для кода и данных пользовательского режима (хотя это маловероятно). Проецируемые в память файлы в этот раздел никогда не помещаются
Реально используемый приложением объём виртуальной памяти несколько меньше. Остальное пространство резервируется системой. Это пространство нужно системе для кода ядра, драйверов устройств, кэш-буферов ввода-вывода, областей памяти, не сбрасываемых в файл подкачки, таблиц, используемых для контроля страниц памяти в процессе и т.д. Такой подход свойственен системам с большим монолитным ядром.
Увеличение размера ПО привело к необходимости увеличения адресного пространства выделяемого для процесса. Особенно остро данная проблема встала для серверных ОС. Как один из вариантов её решения в версиях Windows 2000 Advanced Server и Windows 2000 Data Center для процессоров x86 предусмотрена возможность увеличения этого пространства до 3 Гб. Чтобы все процессы использовали раздел для кода и данных пользовательского режима размером 3 Гб, а раздел для кода и данных режима ядра — объемом 1 Гб, необходимо добавить ключ /3GB к нужной записи в системном файле Boot.ini. Как выглядит адресное пространство процесса в этом случае, показано в графе "32-разрядная Windows 2000/XP (на x86 с ключом /3GB)" таблицы 2.1.
Раньше, когда такого ключа не было, программа не имела доступа к адресам памяти по указателю с установленным старшим битом. Некоторые разработчики самостоятельно использовали этот бит как флаг, который имел смысл только в их приложениях. При обращении программы пo адресам за пределами 2 Гб предварительно выполнялся специальный код, который сбрасывал старший бит указателя.
Во избежание конфликтов при выполнении таких приложений в расширенной 3 гигабайтовой среде, система в момент запуска приложения проверяет, не скомпоновано ли оно с ключом /LARGEADDRESSAWARE. Это означает, что приложение готово к использованию трехгигабайтового адресного пространства пользовательского режима. А если нет, операционная система резервирует область памяти размером 1 Гб в диапазоне адресов от 0x80000000 до 0xBFFFFFFF. Это предотвращает выделение памяти по адресам с установленным старшим битом.
При использовании ключа /3GB уменьшается количество потоков, стеков и других ресурсов, которые система могла бы предоставить приложению. Кроме того, система в этом случае способна задействовать максимум 16 Гб оперативной памяти против 64 Гб в нормальных условиях — из-за нехватки виртуального адресного пространства для кода и данных режима ядра, необходимого для управления дополнительной оперативной памятью.
Флаг LARGEADDRESSAWARE в исполняемом файле проверяется в тот момент, когда операционная система создает адресное пространство процесса. Для DLL этот флаг игнорируется При написании DLL разработчик должен сам обеспечить их корректное поведение в трехгигабайтовом разделе пользовательского режима.
Закрытый раздел размером 64 Кб заблокирован, и любая попытка обращения к нему приводит к нарушению доступа. Этот раздел резервируется для упрощения внутренней реализацию ОС. Вспомните, когда Вы передаете Windows-функции адрес блока памяти и его размер, то она (функция), прежде чем приступить к работе, проверяет, действителен ли данный блок. Например:
BYTE bBuf[70000];
DWORD dwNumBytesWritTen;
WriteProcessMemory(GetCurrentProcess(), (PVOID) 0x7FFEEE90, bBuf, sizeof(bBuf), &dwNumBytesWritten);
В случае функций типа WriteProcessMemory область памяти, в которую предполагается запись, проверяется кодом, работающим в режиме ядра, — только он имеет право обращаться к памяти, выделяемой под код и данные режима ядра (в 32-разрядных системах — по адресам выше 0x80000000). Если по этому адресу есть память, вызов WriteProcessMemory, показанный выше, благополучно запишет данные в ту область памяти, которая, по идее, доступна только коду, работающему в режиме ядра. Чтобы предотвратить это и в то же время ускорить проверку таких областей памяти, Microsoft предпочла заблокировать данный раздел, и поэтому любая попытка чтения или записи в нем всегда вызывает нарушение доступа.
Раздел для общих MMF (только Windows 98). В этом разделе размером 1 Гб система хранит данные, разделяемые всеми 32-разрядными процессами. Сюда, например, загружаются все системные DLL (Kernel32.dll, AdvAPI32 dll, User32.dll и GDI32 dll), и поэтому они доступны любому 32-разрядному процессу. Кроме того, эти DLL загружаются в каждом процессе по одному и тому же адресу памяти. На этот раздел система также отобра-жает все проецируемые в память файлы.
Раздел для кода и данных режима ядра (Windows 2000/XP и Windows 98).В этот раздел помещается код операционной системы, в том числе драйверы устройств и код низкоуровневого управления потоками, памятью, файловой системой, сетевой поддержкой. Все, что находится здесь, доступно любому процессу. В Windows 2000/XP эти компоненты полностью защищены. Поток, который попытается обратиться по одному из адресов памяти в этом разделе, вызовет нарушение доступа, а это приведет к тому, что система в конечном счете просто закроет его приложение.
В 64-разрядной Windows 2000 раздел пользовательского режима (4 Тб) выглядит непропорционально малым по сравнению с 16 777 212 Тб, отведенными под раздел для кода и данных режима ядра. Дело не в том, что ядру так уж необходимо все это виртуальное пространство, просто 64-разрядное адресное пространство настолько огромно, что его большая часть не задействована. Система разрешает нашим программам использовать 4 Тб, а ядру — столько, сколько ему нужно. К счастью, какие-либо внутренние структуры данных для управления не-задействованными частями раздела для кода и данных режима ядра не требуются.
В Windows 98 данные, размещенные в этом разделе, увы, не защищены — любое приложение может что-то считать или записать в нем и нарушить нормальную работу операционной системы.