63.Память модель
Модель памяти в широком смысле слова определяет очень много вещей. Например такое фундаментальное, но в тоже время и очевидное, свойство как размер указателя (8/16/32/64 бита). Или организация кэшей, их кол-во, размер, ассоциативность и т.д. Так же для некоторых аппаратных платформ у разработчика есть возможность выбирать режим работы/адресации (например TINY, SMALL, LARGE, FLAT) — это тоже определяется моделью памяти. Однако я опишу модель памяти с точки зрения параллельного исполнения, те свойства, которые наиболее релевантны и интересны в контексте многоядерности/многопроцессорности, и относительно которых сейчас имеется серьёзное недопонимание и/или интерес со стороны разработчиков. Итак к делу. Модель памяти определяет 3 фундаментальных свойства: атомарность, видимость и упорядочивание. Атомарность (atomicity). Что такое атомарность в целом, я надеюсь, понятно. Это — "неделимость" операции в контексте многопоточного исполнения. Или, если переформулировать, может ли другой поток видеть промежуточное состояние операции. Атомарность необходимо рассмотреть для 2 типов операций. Первый тип — это обычные сохранения из памяти и загрузки в память. Модель памяти специфицирует должны ли обычные сохранения и загрузки быть атомарными или нет. Типовая гарантия, характерная для большинства современного аппаратного обеспечения, — это сохранения и загрузки размером с машинное слово по выровненному адресу — атомарны. Модель памяти может так же предоставлять дополнительные гарантии, например, что сохранения и загрузки размером с пол-слова или с двойное слово тоже атомарны. Второй тип — это так называемые RMW (read-modify-write) операции. Среди них могут быть — exchange, compare_exchange, fetch_add, increment, load_linked/store_conditional и др. Модель памяти (вместе с набором команд процессора) определяет какие RMW операции доступны и являются ли они атомарными. Обычно современные процессоры предоставляют как минимум атомарную операцию compare_exchange (или аналогичную load_linked/store_conditional), и, возможно, какие-то другие команды, например, fetch_add и exchange. Многие связывают атомарность только с RMW операциями. Однако это неправильно. Важно понимать атомарность и для обычных сохранений и загрузок, т.к. на них строится большое число алгоритмов. Видимость (visibility). Под видимостью понимается то, через какое время другие потоки увидят изменения, сделанные данным потоком, и увидят ли вообще. Многие почему-то считают это свойство очень важным, и порываются что-то предпринимать для его обеспечения. На практике, это — как раз самое неинтересное свойство для программиста и ничего предпринимать для его обеспечения не надо. Ну точнее так, ничего не надо предпринимать на кэш-когерентных архитектурах (коими являются все распространённые архитектуры: x86, Itanium, PPC, SPARC и т.д.). Когерентный кэш обеспечивает автоматическое и немедленное распространение всех изменений всем заинтересованным процессорам/ядрам. Т.е. можно считать, что любая запись в память становится немедленно видимой всем остальным потокам. На не кэш-когерентных архитектурах ситуация иная — изменения автоматически не распространяются, и для их распространения надо вручную предпринимать какие-то специальные меры. Однако не кэш-когерентные архитектуры — это очень узкоспециализированная ниша, и к тому же каждая такая архитектура достаточно уникальна. Поэтому говорить о них "в общем" не имеет особого смысла. Упорядочивание (ordering). При однопоточном исполнении аппаратура обеспечивает так называемую sequential self-consistency, т.е. для программы всё выглядит так, как будто все обращения к памяти происходят именно в том порядке, в каком они указаны в программе. На самом деле исполнение может производится в другом порядке, однако, пока речь идёт только об одном потоке, аппаратура маскирует этот факт от программиста. При многопоточном исполнении картина кардинально меняется, и другие потоки могут видеть сохранения в память в порядке отличном от программного. Упорядочивание — это наиболее важное и сложное свойство. Модель памяти должна определять какие переупорядочивания возможны, а какие — нет. Для обеспечения необходимого упорядочивания аппаратная платформа обычно предоставляет так называемые барьеры памяти (memory fences/barriers), это специальные команды, которые запрещают некоторые типы переупорядочиваний вокруг себя. Барьеры памяти бывают двух типов: двусторонние (store-store, load-load) или связанные с операциями (acquire, release). Двусторонние барьеры памяти запрещают какому-то типу обращений к памяти (сохранениям или загрузкам) перемещаться через барьер "вниз", и одновременно другому (хотя возможно и тому же) типу обращений к памяти перемещаться через барьер "вверх". Барьеры связанные с операциями всегда связаны с какой-то операцией (не удивительно) — сохранением, загрузкой или атомарной RMW операцией, и предотвращают перемещение обращений к памяти вверх или вниз относительно этой операции (или и вверх, и вниз). Относительно барьеров важно понимать один момент: барьеры памяти — это всегда игра двух игроков, т.е. поток, который производит, например, запись, должен выполнить барьер, и поток, который соответственно читает, так же должен выполнить барьер. Достичь какого-либо упорядочивания, если барьер исполняет только один поток, — невозможно.
Директивы управлением линстинговым
Директивы управления листингом делятся на следующие группы:
общие директивы управления листингом
директивы вывода в листинг включаемых файлов
директивы вывода блоков условного ассемблирования
директивы вывода в листинг макрокоманд
директивы вывода в листинг информации о перекрестных ссылках
директивы изменения формата листинга
При рассмотрении директив обращайте внимание на то, что их формат отличается для режимов работы транслятора MASM и IDEAL:
директивам режима MASM предшествует точка;
директивам режима IDEAL предшествует знак “%”.
В остальном синтаксис директив простой. Большинство директив не имеет операндов. Директивы, как и команды, задаются в отдельной строке в том месте программы, с которого должно начаться их действие.
Общие директивы управления листингом
Директивы этой группы предназначены для управления видом файла листинга. Все директивы являются парными — это означает, что если одна директива что-то разрешает, то другая, наоборот, запрещает. Рассмотрим назначение этих пар директив.
%LIST и %NOLIST (.LIST и .XLIST) Директивы .LIST или %LIST определяют необходимость вывода в файл листинга всех строк исходного кода. Эти директивы подразумеваются по умолчанию. Для запрета вывода в файл листинга всех строк исходного кода необходимо использовать директивы .XLIST или %NOLIST. В тексте программы их можно применять произвольное количество раз, при этом очередная директива отменяет действие предыдущей.
%CTLS и %NOCTLS Если предыдущие директивы влияют на полноту представления исходного кода в целом, то директивы %CTLS и %NOCTLS управляют выводом в файл листинга самих директив управления листингом.
%SYMS и %NOSYMS Эти директивы определяют, включать (%SYMS) или не включать (%NOSYMS) в файл листинга таблицу идентификаторов.
Директивы вывода текста включаемых файлов
%INCL и %NOINCL Эти директивы позволяют регулировать включение в файл листинга текста включаемых файлов (по директиве INCLUDE). По умолчанию включаемые файлы записываются в файл листинга. Директива %NOINCL запрещает вывод в файл листинга всех последующих включаемых файлов, пока вывод снова не будет разрешен директивой %INCL.
Директивы вывода блоков условного ассемблирования
%CONDS и %NOCONDS (.LFCOND и .SFCONDS) Для исследования исходного текста программы, содержащего директивы условной компиляции, удобно использовать директивы, регулирующие включение блоков условной компиляции в листинг программы. Директива %CONDS (.LFCOND) заставляет TASM выводить в файл листинга все операторы условных блоков. При этом в файл листинга выводятся все блоки, в том числе с условием false. Директива %NOCONDS (.SFCONDS)запрещает вывод в файл листинга блоков условного ассемблирования с условием false. Директива .TFCOND переключает режимы вывода %CONDS (.LFCOND) и%NOCONDS (.SFCONDS). Эту директиву можно использовать как отдельно, так и совместно с директивами .LFCOND и .SFCONDS. Первая директива .TFCOND, которую обнаруживает TASM, разрешает вывод в листинг всех блоков условного ассемблирования. Следующая директива .TFCOND будет запрещать вывод этих блоков. С директивой .TFCOND можно использовать параметр командной строки транслятора TASM ???????????????????????????????????? /X: согласно ему блоки условного ассемблирования будут сначала выводиться в листинг, но первая же директива .TFCOND запретит их вывод.
Директивы вывода макрорасширений
%MACS (.LALL) и %NOMACS (.SALL) Аналогично директивам вывода блоков условной компиляции при отладке программы удобно регулировать полноту информации о применяемых макрокомандах. По умолчанию транслятор включает макрорасширения в файл листинга. Можно запретить вывод макрорасширений в файл листинга, что удобно на некоторых стадиях отладки. Директива %MACS (.LALL)разрешает вывод в листинг всех макрорасширений. Директивы %NOMACS (.SALL) запрещает вывод всех операторов макрорасширения в файл листинга. В режиме MASM можно использовать директиву .XALL, позволяющую выводить в листинг только те макрорасширения, которые генерируют код или данные.
Директивы вывода листинга перекрестных ссылок
Приведенные выше директивы %SYMS и %NOSYMS регулировали вывод в листинг таблицы идентификаторов, в которой приводится информация о метках, группах и сегментах, но там не сообщается, где они определены и где используются. Информация в таблице перекрестных ссылок исправляет этот недостаток. Она облегчает поиск меток и полезна для отладки программы. В приложении 1 приведена опция командной строки TASM /c для получения таблицы перекрестных ссылок. Но действие этой опции распространяется на весь исходный файл, что может быть не совсем удобным. Поэтому TASM дополнительно предоставляет директивы для создания таблиц перекрестных ссылок только для отдельных частей исходного кода. Директивы %CREF (.CREF) и %NOCREF (.XCREF) соответственно разрешают и запрещают сбор информации о перекрестных ссылках, начиная с точки, где они были определены. При этом директивы %NOCREF (.XCREF) позволяют выборочно запрещать сбор информации о перекрестных ссылках для определенных идентификаторов в программе. Эти директивы имеют следующий синтаксис:
%NOCREF (.XCREF) [идентификатор, ...]
Если в директиве %NOCREF (.XCREF) не указать идентификатор, то вывод перекрестных ссылок запрещается полностью, если указать некоторые идентификаторы, то информация не будет собираться только для этих идентификаторов.
Директивы изменения формата листинга
Директивы этой группы позволяют управлять форматом файла листинга.
.PAGE Директива .PAGE задает высоту и ширину страницы файла листинга и начинает его новую страницу. Она имеет следующий синтаксис:
PAGE [число_строк][,число_столбцов] PAGE +
Здесь:
число_строк задает число строк, выводимых на странице листинга;
число столбцов находится в диапазоне 59...255 и задает число столбцов на странице.
Если опустить один из этих параметров, то текущая установка данного параметра останется без изменений. Для изменения только числа столбцов необходимо указать перед этим параметром запятую. С помощью директивы .PAGE можно разбивать листинг на разделы, в пределах которых нумерация начинается с нуля. Так, при указании после директивы .PAGE символа “+” начинается новая страница, номер раздела увеличивается, а номер страницы снова устанавливается в 1. Если использовать директиву .PAGE без аргументов, то листинг возобновляется с новой страницы без изменения номера раздела.
%PAGESIZE (.PAGESIZE) Директива %PAGESIZE работает так же, как и директива .PAGE, но, в отличие от последней, она не начинает новую страницу, а лишь определяет ее параметры:
%PAGESIZE [число_строк][,число_столбцов]
%NEWPAGE Директива %NEWPAGE работает аналогично директиве .PAGE без аргументов. Строки исходного текста после директивы %NEWPAGE будут начинаться с новой страницы.
%BIN Директива %BIN устанавливает длину поля объектного кода в файле листинга. Ее синтаксис:
%BIN размер
Здесь размер — некоторая константа. По умолчанию поле объектного кода занимает в файле листинга до 20 позиций.
%DEPTH Директива %DEPTH устанавливает размер поля глубины в файле листинга. Ее синтаксис:
%DEPTH размер
Здесь размер задает количество столбцов в поле глубины листинга. Напомню, что данное поле показывает уровень вложенности включаемых файлов (INCLUDE) и макрорасширений. Если указать в качестве размера значение 0, то поле уровня вложенности не выводится. По умолчанию это поле имеет значение 1.
%LINENUM Директива %LINENUM позволяет задать размер поля занимаемого номерами строк в файле листинга:
%LINENUM размер
По умолчанию под номер строки отводятся четыре столбца.
%TRUNC и %NOTRUNC Директивы %TRUNC и %NOTRUNC предназначены для усечения длинных полей листинга. Их синтаксис:
%TRUNC и %NOTRUNC
Если некоторая строка исходного кода получается слишком длинной, то она автоматически усекается. Если возникает необходимость увидеть всю генерируемую строку, то можно использовать директиву %NOTRUNC, действие которой будет заключаться в том, что слишком длинная строка будет переноситься на следующую строку. Для включения режима усечения нужно использовать директиву %TRUNC. Такие переключения можно осуществлять неограниченное количество раз.
%PCNT Директива %PCNT задает размер поля “сегмент:смещение” в файле листинга. Ее синтаксис:
%PCNT размер
Здесь размер — число столбцов, которое необходимо отвести для смещения в текущем ассемблируемом сегменте. По умолчанию TASM устанавливает размер, равный 4 для обычных 16-битных сегментов (атрибут размера адреса use16) и 8 для 32-битных сегментов (атрибут размера адреса use32). Директива %PCNT позволяет переопределить эти используемые по умолчанию значения.
%TITLE Директива %TITLE задает заголовок файла листинга. Ее синтаксис:
%TITLE “текст”
Здесь текст — строка, которая будет выводиться в верхней части каждой страницы после имени исходного файла и перед заголовком, заданным по директиве %SUBTTL. В отличие от других директив, %TITLE можно использовать в программе только один раз.
%SUBTTL Директива %SUBTTL задает подзаголовок файла листинга. Ее синтаксис:
%SUBTTL “текст”
Подзаголовок представляет собой текст, который выводится в верхней части каждой страницы после имени исходного файла и после заголовка, заданного директивой %TITLE. Директиву %SUBTTL можно указывать в программе столько раз, сколько необходимо. Каждая директива изменяет подзаголовок, который будет выводиться на следующей странице листинга.
%TABSIZE Директива %TABSIZE задает позицию табуляции в файле листинга. Ее синтаксис:
%TABSIZE размер
Здесь размер — число столбцов между двумя позициями табуляции в файле листинга (по умолчанию 8 столбцов).
%TEXT Директива %TEXT используется для задания длины поля исходного текста в файле листинга. Ее синтаксис:
%TEXT размер
Здесь размер — число столбцов, используемых для вывода исходных строк. Если размер строки превышает длину этого поля, то строка будет либо усекаться, либо переноситься на следующую строку, в зависимости от директив %TRUNC или %NOTRUNC.
