Московский государственный технический университет им. Н.Э. Баумана
Опубликован: 28.06.2006 | Доступ: свободный | Студентов: 12463 / 341 | Оценка: 4.54 / 3.83 | Длительность: 22:03:00
ISBN: 978-5-9556-0055-0
Лекция 1:

Введение в архитектуру Microsoft .NET Framework

Лекция 1: 123456 || Лекция 2 >

Обзор архитектуры .NET

Платформа .NET состоит из двух основных компонентов. Это Common Language Runtime и .NET Framework Class Library.

Common Language Runtime (сокращенно CLR) можно назвать "двигателем" платформы .NET. Его задача - обеспечить выполнение приложений .NET, которые, как правило, закодированы на языке CIL, рассчитаны на автоматическое управление памятью и вообще требуют гораздо больше заботы, чем обычные приложения Windows. Поэтому CLR занимается управлением памятью, компиляцией и выполнением кода, работой с потоками управления, обеспечением безопасности и т.п.

.NET Framework Class Library - это набор классов на все случаи жизни. Далее мы рассмотрим эту библиотеку подробнее, а сейчас остановимся на двух ключевых моментах, которые с ней связаны. Во-первых, на платформе .NET реализованы компиляторы для различных языков программирования, и большинство этих языков позволяют легко использовать одну и ту же библиотеку классов. То есть .NET Framework Class Library - это единая библиотека для всех языков платформы .NET. Во-вторых, использование этой библиотеки позволяет существенно сократить размер приложений, что способствует их распространению через Internet.

Спецификация CLI

Разработчику системного программного обеспечения важно понимать, что .NET - всего лишь одна из возможных реализаций так называемой общей инфраструктуры языков (Common Language Infrastructure, сокращенно CLI), спецификация которой разработана корпорацией Microsoft.

Можно, руководствуясь этой спецификацией, разработать собственную реализацию CLI (рис. 1.3). В настоящее время ведутся по крайней мере два посвященных этому проекта. Это платформа Mono, создаваемая компанией Xamarin, и разрабатываемый в рамках GNU проект Portable .NET. Кроме того, Microsoft распространяет в исходных текстах еще одну свою реализацию CLI, работающую как в Windows, так и под управлением FreeBSD. Эта реализация называется Shared Source CLI (иногда можно услышать другое название - Rotor).

Существующие реализации CLI и поддерживаемые ими операционные системы

Рис. 1.3. Существующие реализации CLI и поддерживаемые ими операционные системы

Итак, чтобы понять, как работает .NET, необходимо изучить спецификацию CLI. Этим мы и займемся в ближайшее время, а пока перечислим ее составные части:

  • Общая система типов (Common Type System, сокращенно CTS) - охватывает большую часть типов, встречающихся в распространенных языках программирования.
  • Виртуальная система исполнения (Virtual Execution System, сокращенно VES) - отвечает за загрузку и выполнение программ, написанных для CLI.
  • Система метаданных (Metadata System) - предназначена для описания типов, хранится в независимом от конкретного языка программирования виде, используется для передачи типовой информации между различными метаинструментами, а также между этими инструментами и VES.
  • Общий промежуточный язык (Common Intermediate Language, сокращенно CIL) - независимый от платформы объектно-ориентированный байт-код, выступающий в роли целевого языка для любого поддерживающего CLI компилятора.
  • Общая спецификация языков (Common Language Specification, сокращенно CLS) - соглашение между разработчиками языков программирования и разработчиками библиотек классов, в котором определено подмножество CTS и набор правил. Если разработчики языка реализуют хотя бы определенное в этом соглашении подмножество CTS и при этом действуют в соответствии с указанными правилами, то пользователь языка получает возможность использовать любую соответствующую спецификации CLS библиотеку. То же самое верно и для разработчиков библиотек: если их библиотеки используют только определяемое в соглашении подмножество CTS и при этом написаны в соответствии с указанными правилами, то эти библиотеки можно использовать из любого соответствующего спецификации CLS языка.
JIT-компиляция

Программы для платформы .NET распространяются в виде так называемых сборок (assemblies). Каждая сборка представляет собой совокупность метаданных, описывающих типы, и CIL-кода.

Ключевой особенностью выполнения программ в среде .NET является JIT-компиляция. Аббревиатура JIT расшифровывается как Just-In-Time, и термин JIT-компиляция можно перевести как компиляция программ "на лету". JIT-компиляция заключается в том, что CIL-код, находящийся в запускаемой сборке, тут же компилируется в машинный код, на который затем передается управление.

Такая схема выполнения программ в среднем является более эффективной, чем интерпретация инструкций CIL, так как потеря времени на предварительную компиляцию CIL-кода с лихвой компенсируется высокой скоростью работы откомпилированного кода.

В .NET реализованы два JIT-компилятора: один компилирует сборку непосредственно перед ее выполнением, а другой позволяет откомпилировать ее заранее и поместить в так называемый кэш откомпилированных сборок. JIT-компилятор первого типа вызывается автоматически при запуске программы, а JIT-компилятор второго типа реализован в виде служебной программы ngen, которая входит в состав .NET Framework SDK.

Программу ngen нельзя воспринимать как простой компилятор, позволяющий превратить сборку .NET в обычное приложение Windows. Дело в том, что откомпилированная сборка не может быть непосредственно запущена пользователем - загрузчик выдает сообщение об ошибке, гласящее, что запускаемая программа не является правильным приложением Windows. Откомпилированная сборка запускается системой только при вызове исходной сборки!

Сборка мусора

Одни из самых неприятных ошибок, которые портят жизнь программисту, это, безусловно, ошибки, связанные с управлением памятью. В таких языках, как C и C++, в которых управление памятью целиком возложено на программиста, львиная доля времени, затрачиваемого на отладку программы, приходится на борьбу с подобными ошибками.

Давайте перечислим типичные ошибки при управлении памятью (некоторые из них особенно усугубляются в том случае, если в программе существуют несколько указателей на один и тот же блок памяти):

  1. Преждевременное освобождение памяти (premature free).

    Эта беда случается, если мы пытаемся использовать объект, память для которого была уже освобождена. Указатели на такие объекты называются висящими (dangling pointers), а обращение по этим указателям дает непредсказуемый результат.

  2. Двойное освобождение (double free).

    Иногда бывает важно не перестараться и не освободить ненужный объект дважды.

  3. Утечки памяти (memory leaks).

    Когда мы постоянно выделяем новые блоки памяти, но забываем освобождать блоки, ставшие ненужными, память в конце концов заканчивается.

  4. Фрагментация адресного пространства (external fragmentation).

    При интенсивном выделении и освобождении памяти может возникнуть ситуация, когда непрерывный блок памяти определенного размера не может быть выделен, хотя суммарный объем свободной памяти вполне достаточен. Это происходит, если используемые блоки памяти чередуются со свободными блоками и размер любого из свободных блоков меньше, чем нам нужно. Проблема особенно критична в серверных приложениях, работающих в течение длительного времени.

В программах, работающих в среде .NET, все вышеперечисленные ошибки никогда не возникают, потому что эти программы используют реализованное в CLR автоматическое управление памятью, а именно - сборщик мусора. Если не вдаваться в излишние на данном этапе изучения .NET подробности, можно сказать, что работа сборщика мусора заключается в освобождении памяти, занятой ненужными объектами. При этом сборщик мусора также умеет "двигать" объекты в памяти, тем самым устраняя фрагментацию адресного пространства.

Все эти чудеса, которые творит сборщик мусора, возможны исключительно благодаря тому, что во время выполнения программы известны типы всех используемых в ней объектов. Другими словами, данные, с которыми работает программа, находятся под полным контролем среды выполнения и называются, соответственно, управляемыми данными (managed data).

Верификация кода

При разработке платформы .NET было уделено много внимания обеспечению безопасности выполняемого программного кода. С точки зрения обеспечения безопасности можно привести следующую классификацию CIL-кода:

  • Недопустимый код (illegal code).

    Это код, который не может быть обработан JIT-компилятором, то есть не может быть транслирован в машинный код.

  • Допустимый код (legal code).

    Это код, который может быть представлен в виде машинного кода. При этом он может содержать вредоносные фрагменты (например, вирусы) или ошибки, способные нарушить работу не только программы, но и среды выполнения и даже операционной системы.

  • Безопасный код (safe code).

    Безопасный код не содержит вредоносных фрагментов (в том числе ошибок) и не может повредить ни системе выполнения, ни операционной системе, ни другим выполняемым программам.

  • Верифицируемый код (verifiable code).

    Верифицируемый код - это код, безопасность которого может быть строго доказана алгоритмом верификации, встроенным в CLR.

Весь код, который поступает в JIT-компилятор, автоматически подвергается верификации. Верификатор платформы .NET реализует достаточно простой линейный алгоритм проверки правильной работы программного кода с типами данных и для каждого метода, входящего в сборку .NET, способен дать ответ на вопрос, проходит код этого метода верификацию или нет. В зависимости от настроек безопасности .NET система выполнения может разрешить или не разрешить выполнять на машине код, отбракованный верификатором.

Следует понимать, что верифицируемый код всегда является безопасным, а обратное в общем случае неверно. То есть, можно себе представить такой CIL-код, который определенно является безопасным, но в силу тех или иных причин не может пройти верификацию.

Лекция 1: 123456 || Лекция 2 >
Анастасия Булинкова
Анастасия Булинкова
Рабочим названием платформы .NET было