...

суббота, 28 сентября 2019 г.

Google закручивает гайки

Последние годы компания Google в отношении своей операционной системы Android – все больше “закручивает гайки”: запрещает ранее работающие функции, ограничивает изменение настроек системы из приложений. И старые приложения перестают работать на новых версиях Android.

Кроме этого, их магазин приложений Google Play Store (“Маркет”, короче говоря) уже так же перенасыщен приложениями, и старыми и новыми на любые темы. Найти что-то нужное все сложнее и сложнее, вновь созданное приложение если просто опубликовать там, и не заниматься рекламой и раскруткой – практически не найдет своих пользователей.

И Google также все активнее “чистит” свой Маркет.
К разработчикам выдвигаются новые требования по части оформления приложений, их описаний, содержимого, которое может быть не приемлемым для детей и т.п. И это правильно, с точки зрения простого пользователя приложений. Но владельцам аккаунтов (независимым разработчикам или компаниям) – теперь все больше требуется времени и усилий, чтобы следовать новым правилам. Т.к. за нарушения, особенно злостные, Google не только удаляет приложения из магазина, или запрещает публикацию новых версий (до устранения нарушений) – но и может удалить аккаунт полностью, и этот разработчик уже не сможет зарегистрировать аккаунт снова.

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

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

Раньше я, выполняя сторонние заказы на разработку Android-приложений, запросто предлагал услугу публикации разработанного мной полностью бесплатного для конечного пользователя приложения в моем аккаунте, если заказчику было неважно, лишь бы приложение было доступно в Маркете. Но за последние месяцы этого 2019 года у меня внезапно два приложения приобрели статусы:

  • «Удалено» — Приложение удалено из Google Play, так как оно нарушает Правила программы для разработчиков приложений Google Play.
  • «Обновление приложения отклонено» — Приложение или обновление не было опубликовано, так как оно нарушает Правила программы для разработчиков приложений Google Play. Если вы пытались опубликовать обновление, то в Google Play по-прежнему доступна предыдущая версия приложения.

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

Но на днях я внезапно обнаружил свое собственное приложение (которое лет 5 было опубликовано) в 3-ем варианте статуса:

  • «Приложение заблокировано.» — Приложение удалено из Google Play Маркета.

Приложение все эти годы дорабатывалось для соблюдения правил и требований Google, и большинство положительных отзывов подтверждали, что приложение полезное, пользователи были довольны, на 4pda.ru так же есть ветка программы, и люди пользовались и пользуются ей.
Теперь же никакая информация о приложении более не доступна, и «живой робот» от Гугл отвечает, что:

As mentioned in the previous email, we found that your app violates Deceptive Behavior Policy. Apps must provide accurate disclosure of their functionality and should perform as reasonably expected by the user.

For auditing purposes, we won't be able to reinstate or remove apps that have been removed or rejected due to policy violations. You'll need to upload the policy compliant app with a new package name and a new app name.

Т.е. что конкретно было нарушено — неизвестно, перечитать тексты описания приложения больше не представляется возможным, и «человеки» в support Гугла — так же не властны в этой ситуации.

И тут мы подходим к главной мысли, ради которой писалась статья: приложения, выполненные на заказ (бесплатные для пользователей) теперь неразумно разработчику (владельцу аккаунта Play Store) публиковать в своем аккаунте, предоставляя услугу (бесплатно, или за дополнительные деньги), т.к. выполнение требований Google теперь все больше зависит от заказчика приложения, от контента приложения, а не от разработчика. И заранее нет 100% гарантии, что заказанное приложение пройдет все этапы проверки, и не нарушит их в будущем.

И получается, что теперь любой заказчик приложения, если думать головой заранее – обязан заводить себе аккаунт сам, заниматься его поддержкой, обновлением описаний приложений, скриншотов… Разумеется, сначала заплатив Google “за членство” 25 долларов.

Конечно, в любом аккаунте сторонний разработчик может быть подключен владельцем как “публикатор” приложений, но ответственность за публикацию приложений, риск запрета публикации или даже риск потери аккаунта со всеми приложениями – теперь заказчик разработки должен нести сам.

Вот только очень часто заказчиков эта проблема вообще не интересует — а интересует выполнение заказа «под ключ», когда оплачивается уже полностью готовый результат. А кто будет внезапно возникающие от Google последствия разгребать, следить за аккаунтом — неясно.

Let's block ads! (Why?)

[Из песочницы] Instant View, мгновенный и недоступный

Я люблю Телеграм, и люблю читать в статьи в Instant View. Причины: скорость, удобство, отсутствие рекламы, но самое главное — возможность создать IV для любого сайта за ~5-10 минут.

Я сказал "5-10 минут"? Ой, я имел ввиду 2 года.


Скорость


Сравнение: Браузер | Приложение | Instant View

Instant View быстр. Я понимаю, что его скорость обусловлена тем, что Телеграм уже открыт, а для открытия стороннего браузера/приложения нужно время. Но мне кажется, что IV работает быстрее AMP и Instant Articles.

Впрочем, выбор зависит лишь от того, где вы проводите больше времени — в мессенджере, браузере или приложении Facebook.


Шаблоны

Написать код для Instant View и правда можно за 5-10 минут. Достаточно прочитать мануал, посмотреть шпаргалку по XPath и нажать Ctrl+Shift+I. После задать элементы, содержащие тело и заголовок статьи, обрезать лишнее с помощью @remove и всё.

~version: "2.1"     # Последняя версия Instant View
body: //div[has-class("post__text")]

Этого кода достаточно, чтобы перевести весь Хабр в Instant View. Однако многие сайты не так просты, поэтому в языке IV есть переменные, условия и функции.


Публикация

Если вам попался сайт с простой разметкой, то трудности начнутся здесь.

Единственный способ опубликовать статью в Instant View — это сделать ссылку вида https://t.me/iv?url=[ССЫЛКА]&rhash=[...], куда необходимо вставить ссылку на статью и rhash-идентификатор вашего Шаблона.

Удобно? Быстро? Красиво? Нет, нет, и ещё раз нет. Итоговую ссылку обычно прячут в текст, точку или неразрывный пробел. Я даже создал бесплатного бота, который делает это за пользователя.

В то же время на сайте IV написано, что есть и другой способ получить Instant View для своего сайта — дождаться одобрения вашего Шаблона от команды Телеграм. Я отправил свои Шаблоны (1, 2, 3). Прошло больше года — реакции не последовало.


Так что же делать

Единственный способ получить Instant View без костылей сейчас — зайти на сайт Конкурса IV, нажать Add Domain и добавить свой сайт.

Между первым и вторым Конкурсом прошло 2 года, и второй Конкурс длился в полтора раза дольше объявленного (комментарий победителя). Такими темпами прогноз, что вам придётся ждать 2 года, может оказаться слишком оптимистичным.

И желательно не менять разметку после Конкурса, потому что Instant View просто перестанет работать, и не факт, что это пофиксят.


Заключение

К сожалению, развитию и популярности самой скоростной технологии Телеграм мешает… сам Телеграм. Ваш сайт не получит Instant View, даже если вы сами напишете для этого идеальный код.

Я вижу решение в создании волонтёрских команд, как сделано с Переводами и Поддержкой, или же в увеличении количества сотрудников, работающих над IV.

Искренне надеюсь, что мессенджер не прекратит поддержку этой технологии и станет по-настоящему удобнее AMP-страниц и RSS-агрегаторов. Судя по вчерашнему объявлению конкурса на создание алгоритмов кластеризации и ранжирования контента, который очевидно будет встроен в Instant View, развитие продолжится.

Своей статьёй я лишь хочу привлечь внимание к этому узкому месту, которое сильно снижает потенциал развития всей платформы.

Let's block ads! (Why?)

“Железные” конференции. SOM i.MX6, Aliceduino, Keras+STM32Cube.AI

Каждому любопытному сотруднику IT рано или поздно становится интересно узнать чем занимаются коллеги по цеху, обменяться опытом и посмотреть на новые технологии, появляющиеся на рынке. Для этих целей, как правило, представители крупных компаний организуют различного рода конференции, семинары и прочие митапы. Из-за специфики своей деятельности далее речь пойдет о собраниях, посвященных хардверным продуктам или технологиям, а в частности о форматах проведения такого рода встреч. Хочу поделиться с вами, дорогие хабровчане, новостями и опытом посещения трех таких мероприятый за прошедшую неделю. Обзор по свежим следам.


25.09.2019 в Санкт-Петербурге проводился семинар посвященный микропроцессорам серии i.MX6, где рассказывалось о данном и некоторых других семействах микропроцессоров NXP, в том числе была анонсирована новая линейка — i.MX8 M pico. Кроме того рассматривались особенности перехода с микроконтроллерных систем на микропроцесссорные. Хочется отметить, что бОльшая часть времени семинара была отведена практической части — работе с демонстрационной платой на базе i.MX6 ULL под управлением embedded Linux. Данный KIT представляет из себя следующее:

  • SOM модуль VisionSOM-6ULL, выполненный по стандарту SODIMM200, на котором расположен микропроцессор, RAM и ROM (NAND \ eMMC \ uSD);
  • Материнская плата с интерфейсными разъемами (Ethernet 10\100, USB OTG, USB Host, UART-USB консоль).


Демонстрационная плата VisionCB-6ULL-STD
Особенность SOM модуля
Данный SOM модуль ко всему прочему может быть интересен в приложениях, требующих соблюдения повышенных требований к безопасности, связанных с работой в ГИС, информационных системах работы с персональными данными (ИСПДн) и в объектах критической информационной инфраструктуры (КИИ) т.к. для него существует и поставляется доверенный загрузчик TMS. (https://www.aladdin-rd.ru/catalog/tsm).

Вроде бы кажется, что ничего особенного, но сам факт того, что все это выдается в рамках конференции, проводится занятие с участниками по старту работы с данным оборудованием (загрузка образа ОС на microSD карту, старт Linux системы, работа с GPIO, настройки сетевого интерфейса и dchp сервера) и демонстрация различного функционала (пример работы с модулем гироскопа, реализация простенького WEB-сервера, работа с NFC модулем) – лично у меня вызывало неподдельное уважение. Такой подход вдохновляет. Более того, все выданные демонстрационные платы были подарены участникам компанией-организатором с целью дальнейшего изучения. Из недостатков стоит отметить разве что основное выступление представителя NXP и практические занятия проходили на английском языке, о чем организаторы встречи нигде не упоминали ранее. Для меня это не составило никакие проблем однако присутствовали и явно недовольные слушатели.В общем, на этой конференции-семинаре присутствовать было очень интересно — как будто в институт на лабы сходил. Организаторам и представителям компании большое спасибо.

Далее в моем календаре было отмечено посещение вебинара 27.08.2019, проводящегося компанией-партнером STM — Doulos, посвященный внедрению нейросетей в микроконтроллеры STM32. Я далек от машинного обучения и нейронных сетей, но наблюдать за этими штуками крайне интересно и познавательно. Так вот оказывается, помимо привычных уже CPU и GPU для запуска нейросетей начинают (а может где-то уже и активно используют) применять MCU и FPGA. В отличие от известных облачных расчетов, у встраиваемых решений помимо очевидных недостатков памяти и мощностей, есть свои преимущества — малые задержки принятия решений, стоимость и энергопотребление. Идея состоит в том, что ресурсотребовательная стадия обучения нейросети производится на хост-машине или в облаке, а уже обученная и настроенная сеть затем собирается и прошивается в микроконтроллер. Вебинар демонстрировал пример работы тулчейна Keras (высокоуровневый python-фреймворк для создания нейросетей) + STM32Cube.AI (пакет расширения для STM32CubeMX для встраивания нейронных сетей в микроконтроллеры STM32). Демонстрация закончилась на моменте генерации некоего С-кода в виде огромного массива выделяемого в памяти МК и run-time библиотеки. Мой вопрос в чатике «Как из прошивки общаться с получившейся нейросетью?», к сожалению, проигнорировали и даже почему-то забанили. Но, тем не менее, тема интересная и в любом случае лучше о таком доступном инструментарии знать, чем не знать.


Структурная схема приложения с использованием встроенной нейросети


Генерация кода для МК STM32 из модели нейросети, полученной с помощью Keras

Ну вот и настали выходные и можно смело отправляться на первую для Яндекс.Железо конференцию для hardware разработчиков. Основными освещаемыми темами данного мероприятия были: внедрение Алисы в свой умный дом, алгоритмы и аппаратная начинка беспилотного автомобиля и, конечно же, обед. Яндекс активно продвигает своего голосового помощника в том числе и в сфере умного дома и прекрасно понимает, что поддерживать абсолютно все устройства, начиная от бытовой техники, заканчивая кастомными девайсами кормушек для котов и тому подобное — невозможно. Поэтому сейчас они предлагают Алису в качестве интерфейса, API которого производители бытовой техники смогли бы внедрять в свои устройства и поддерживать. Кроме того, они анонсировали, на мой взгляд, крайне удачное решение, как для крупных производителей техники, так и для DIY-разработчиков — Aliceduino. Фото самой платы, к сожалению, мне сделать не удалось, однако функциональными схемами модулей и кратким описанием поделиться могу.


Анонс Aliceduino — платы расширения с поддержкой Алисы (Картинка платы не соответствует действительности)

Это модуль, имеющий WiFi, вход подключения микрофонов и выход в виде обычного 5V UART'а и позволяющий в реальном времени обрабатывать голосовые команды. На момент анонса имелось два режима работы:

1. Распознавание речи (в UART попадают слова в виде строк, распознанные после слова «Алиса»)

2. Распознавание смысла (в UART приходят не слова, а команды*)
*нужно взаимодействовать с инструментами Яндекс

Микрофонная плата предоставляется в нескольких конфигурациях в зависимости от задачи и области применения приложения


Варианты микрофонной платы для Aliceduino. 2, 4, 7 — микрофонное исполнение

Сам модуль представляет собой System on module (SOM) + плату расширения с аудиоподсистемой (аудиокодек и усилитель для вывода звука на внешний динамик)


Функциональная схема SOM — «мозг Алисы»


Функциональная схема плата расширения с аудиоподсистемой и выводами UART

В итоге мы получаем интеграцию Алисы в свои приложения по средствам 4 проводов:
RX, TX, +5V, GND. Примерную стоимость озвучить не смогли, т.к. тираж еще не выпущен. Выпуск в продажу, как упомянули, планируется к Новому году.

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

Немножко фоточек беспилотного автомобиля

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

P.S. Приношу извинения за качество фото. Ссылки на упомянутые технологии можно найти ниже.

1. somlabs.com/news-room/visionsom-6ull-new-som-imx6-ull
2. www.tensorflow.org/guide/keras?hl=ru
3. www.st.com/content/st_com/en/stm32-ann.html

Let's block ads! (Why?)

«Находки аудиомана»: древо музыкальных жанров, ксилофон из GitHub-событий и эфиры спутников

В прошлый раз мы рассказывали о картах, на которые нанесена разного рода аудиоинформация. Продолжаем тему музыкальных находок и говорим о сервисах, в которых можно «залипнуть».


Фото Cristofer Jeschke / Unsplash





Это — сервис с интерактивной инфографикой, которая представляет собой генеалогическое древо музыкальных жанров: с 1880 года и до нашего времени.

Его запустил бельгийский архитектор Квинтен Крауэлс (Kwinten Crauwels) вместе со своим братом-программистом. Идея проекта родилась в тот момент, когда братья не смогли найти ресурс, посвященный обзору всех музыкальных жанров. Поэтому они решили сделать его самостоятельно.

Как рассказал Квинтен на конференции TED в 2017 году, на запуск сервиса ушло более восьми лет. За это время авторы изучили 230 музыкальных направлений и установили связи между ними.


Скриншот: musicmap.info / Заглавный экран сервиса с основными музыкальными жанрами

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

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


Скриншот: musicmap.info / Некоторые поджанры из категорий рэп, брейкбит и драм-н-бэйс

Если кликнуть по названию любого музыкального направления, откроется панель с его краткой историей. Также внизу появится плейлист с культовыми композициями этого поджанра на YouTube.

Среди них можно найти довольно интересные экземпляры, например, работу шотландского драм-н-бейс коллектива Konflict — «Messiah» (нейрофанк), трек ирландской рок-группы My Bloody Valentine — «When You Sleep» (дрим-поп) и композицию джазового музыканта Арильда Андерсена — «Anima» (норвежский джаз).


В целом найти интересующий вас жанр довольно просто — на сайте есть поиск (на панели слева). Под ним вы найдете список литературы, который использовали авторы во время исследований.



На орбите Земли вращается множество спутников, задействованных в различных космических экспериментах. Мониторить их эфир могут все желающие. Для этого можно воспользоваться специальным веб-приемником для прослушивания эфира — WebSDR.

Это — сервис, объединяющий множество программно-определяемых радиосистем (SDR), подключенных к интернету. Его автором выступил инженер из обсерватории в Двингело (Нидерланды), где установлен 25-метровый радиотелескоп. Он является частью комплекса связи «Земля–Луна–Земля», который использует естественный спутник Земли в качестве отражателя для передачи сигнала в разные точки планеты. Нидерландский инженер создал специальное серверное ПО, чтобы радиолюбители со всего мира могли прослушивать эфир радиотелескопа. Позже некоторые пользователи выразили желание настроить собственный WebSDR-сервер. Сегодня на сайте проекта можно подключиться к 162 программно-определяемым радиосистемам.

В списке есть SDR, которую поддерживают инженеры из города Фарнем (Великобритания). Она принимает телеметрию с высотных аэростатов и Международной космической станции. Еще пример — сервер спутниковой земной станции Гунхилли, через которую можно слушать эфир катарского спутника Es'hail 2, запущенного на борту ракеты SpaceX Falcon 9 в прошлом году.


Скриншот: websdr.org / KiwiSDR — веб-приемник в Чунцине, Китай

К слову, если у вас есть собственная приемопередающая станция, вы также можете открыть к ней доступ для широкой публики. Для этого вам понадобится серверное ПО, которое можно попросить у создателя WebSDR напрямую — достаточно написать ему на электронную почту.





Этот проект представляет собой своеобразный музыкальный инструмент, на котором «играют» разработчики со всего мира. Приложение воспроизводит звуки каждый раз, когда кто-то выполняет push, merge или отмечает issue на GitHub.

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


Скриншот: github.audio

Медитативная мелодия, которая получается, похожа на игру на ксилофоне. Громкость можно регулировать с помощью ползунка в правом верхнем углу экрана (сервис не работает в Safari на iPhone).

Автором проекта выступил разработчик Суданшу Мишра (Sudhanshu Mishra), он представил его в 2016 году. Проект является полностью открытым, код можно найти в репозитории github-audio. К слову, название приложения вызвало определенное замешательство в ИТ-сообществе. Некоторые резиденты Hacker News сперва подумали, что разработчики GitHub решили представить новый сервис контроля версий для музыкантов.

Отметим, что у github-audio есть аналог — bitlisten. Только он генерирует мелодию на основе транзакций, проводимых в биткоин-блокчейне.



Дополнительное чтение — в нашем «Мире Hi-Fi»:

Прислушиваясь к информационному шуму: музыка и видео, которые никто не должен был найти
«Все, что вы прочитаете, будет использовано против вас»: как рэп-музыка попала в зал суда
Как треск костра, скрип дверей и обыкновенный шум становятся музыкой
«За нами следят»: что может происходить в неприметном минивэне прямо у вас под окном
«Находки аудиомана»: карты звуков как способ погрузиться в атмосферу незнакомого города
Как визуализировать звук в вебе: подборка тематических материалов с теорией и практикой
Что такое музыкальное программирование: кто и почему им занимается


Let's block ads! (Why?)

Битва Големов из карт. Как мы превращали игру в Карточную лигу Пароботов

Продолжение данной истории. Сейчас речь пойдет не о концепции, а том, как мы из вот этого

т.е. классической версии в которой само игровое поле, элементы на нем, жетоны повреждений были представлены «вырубкой» из картона и было «дорого и богато» делали карточный «движок» и наполнение и уменьшали число «не карточных» компонентов до минимума. В терминологии компьютерных игр это «левел-дизайн» и «дизайн персонажей». Но у нас же и ИТ-игра про программирование роботов.

Итак по порядку. Если ранее я уже рассказывал про карты команд на языке Scratch, то сейчас речь пойдет о замене других компонентов.

Игровое поле


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

Пришлось пожертвовать только лестницами (они теперь работают как универсальные порталы и войдя в одну «дверь» вы можете выйти в любом месте где есть такое же поле. Теперь игроки могут собирать как классические квадратные уровни 3x3 или 4x4 так и более сложные и замысловатые конфигурации, включая многоэтажные (или комнатные). На фото пример уровня и внешний вид прототипа, на котором отрабатываем последние нюансы перед отправкой игры в печать.

Новинкой (из-за возможностей применения бочек и наличия «летающего» шасси у ваших Пароботов) будут уровни, которые невозможно было представить в предыдущих версиях игры. К примеру, дуэль на мосту двух колесных роботов, где удар может разрушить сам мост или соперник или вы сможете его разобрать.

Пароботы-Големы


В предыдущих версиях игры карты роботов-Големов выглядели вот так

А повреждения учитывались специальными жетонами «поломанных» шестеренок, которые выкладывались на них сверху.

В карточной версии было решено, что големы и их повреждения будут «работать» по совсем другой механике. Долго ломали голову над балансом (чтобы не дать игроку «читерских» возможностей, скомбинировав самые крутые компоненты). При этом:

1. Игрок должен собирать своего боевого робота перед началом боя, комбинируя его из трех компонентов (а в будущем возможно добавим и другие «запчасти»): шасси, верхней части — «мозгов» и вооружения. У каждого робота решено было оставить три «жизни», отмеченные целыми шестеренками. Разработали по два вида шасси, верхней части и вооружения для каждого игрока, которые будут отличаться по свойствам. К примеру ракетное шасси позволит «летать» над водой, а Бронемозг хоть и не вмещает большое число команд, но менее чувствителен к повреждениям.

2. При пропущенных Ударах игроку придется решать, какая часть его робота будет повреждена и чем он готов пожертвовать: числом исполняемых команд, дополнительными возможностями оружия или передвижением.

Для отображения повреждений игроку достаточно будет перевернуть соответствующую карту (или карты, если вы пропустили 2 единицы Удара) на сторону со сломанной шестеренкой. Когда он перевернет все три карты (3 пропущенных единицы Удара), и останется последний шанс — следующий Удар его робот не переживет.

Вот такие решения были сделаны, чтобы уйти «в карточный» формат, а успешны они будут или нет, скажут уже игроки. Игра сейчас собирает средства на издание и надеюсь к концу года (или в начале следующего) увидит свет и мы очень надеемся, что детям понравятся новые Големы-пароботы.

P.S. Можно задавать вопросы в комментариях по игре, гейм-дизайну и т.п., с удовольствие отвечу.

Let's block ads! (Why?)

НАСА показало симуляцию чёрной дыры


На визуализации обозначены основные элементы: тень чёрной дыры; фотонное кольцо; аккреационный диск; задняя часть диска, которая из-за гравитационного искажения видна сверху и снизу, и допплеровское свечение (газ на аккреационном диске светится ярче при движении по направлению к наблюдателю). Иллюстрация: Центр космических полетов Годдарда НАСА / Jeremy Schnittman

Центр космических полетов Годдарда опубликовал максимально детализированные статические изображения и анимацию, как будут выглядеть окрестности чёрной дыры для внешнего наблюдателя (видео 4K).

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

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


Видео: Центр космических полетов Годдарда НАСА / Jeremy Schnittman

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

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


Увеличенное изображение центрального региона чёрной дыры. Видео: Центр космических полетов Годдарда НАСА / Jeremy Schnittman


Ещё более увеличенное изображение центрального региона чёрной дыры с фокусом на фотонное кольцо. Видео: Центр космических полетов Годдарда НАСА / Jeremy Schnittman

Ближе всего к чёрной дыре гравитационное искривление света становится настолько экстремальным, что мы можем видеть нижнюю сторону диска как яркое кольцо света. Так называемое «фотонное кольцо» состоит из множества колец, которые постепенно становятся все слабее и тоньше из-за того, что свет обходит чёрную дыру два, три или даже больше раз, прежде чем достичь наших глаз.

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

«Моделирование и такие видеоролики действительно помогают нам визуализировать то, что имел в виду Эйнштейн, когда сказал, что гравитация деформирует ткань пространства и времени, — говорит Джереми Шнитман (Jeremy Schnittman), который создал эти великолепные изображения с помощью специального программного обеспечения в Центре космических полетов им. Годдарда НАСА. — До недавнего времени эти визуализации ограничивались нашим воображением и компьютерными программами. Я никогда не думал, что можно будет увидеть настоящую чёрную дыру».

В последних словах Джереми Шнитман имеет в виду историческое событие, которое произошло 10 апреля 2019 года. В этот день команда телескопа Event Horizon опубликовала первое в истории изображение тени чёрной дыры, созданное на основе радионаблюдений в центре галактики M87.


Изображение Event Horizon Telescope Collaboration

У сверхмассивной чёрной дыры в галактике М87 диаметр 40 миллиардов километров, её тень в 2,5 раза больше (100 миллиардов километров), а вокруг вращается аккреционный диск поглощаемого газа ещё в несколько раз шире.

Заметим, что научная визуализация НАСА очень близка к изображению чёрной дыры из фильма «Интерстеллар» (2014), созданному на базе уравнений физика Кипа Торна.


Кадр из фильма «Интерстеллар» (2014)

В то же время оно как будто немного отличается от изображения чёрной дыры в картине «Высшее общество», которое смоделировали по уравнениям астрофизика Орельена Барро.


Кадр из фильма «Высшее общество» (2018)

Let's block ads! (Why?)

[Из песочницы] Инициализация и работа интерпретатора байткода в JVM HotSpot под x86

Почти каждый Java разработчик знает, что программы, написанные на языке Java изначально компилируются в JVM-байткод и хранятся в виде class-файлов стандартизованного формата. После попадания таких class-файлов внутрь виртуальной машины и пока до них еще не успел добраться компилятор, JVM интерпретирует байткод, содержащийся в этих class-файлах. Данная статься содержит обзор принципов работы интерпретатора применительно к OpenJDK JVM HotSpot.

Содержание статьи:


  • Окружение
  • Запуск java приложения
  • Инициализация интепретатора и перадача управления java-коду
  • Пример

Окружение

Для экспериментов используется сборка крайней доступной ревизии OpenJDK JDK12 с autoconf конфигурацией

--enable-debug --with-native-debug-symbols=internal

на Ubuntu 18.04/gcc 7.4.0.

--with-native-debug-symbols=internal означает, что, при сборке JDK, дебажные символы будут содержаться в самих бинарях.

--enable-debug — то, что в бинарнике будет содержаться дополнительный дебажный код.

Сборка JDK 12 в таком окружении — это не сложный процесс. Все, что мне потребовалось проделать это поставить JDK11 (для сборки JDK n требуется JDK n-1) и доставить руками необходимые библиотеки о которых сигналил autoconf. Далее выполнив команду

bash configure --enable-debug --with-native-debug-symbols=internal && make CONF=fastdebug images

и немного подождав (на моем ноуте порядка 10 минут), получаем fastdebug сборку JDK 12.

В принципе вполне достаточно было бы просто установить jdk из публичных репозиториев и дополнительно доставить пакет openjdk-xx-dbg с дебажными символами, где xx- версия jdk, но fastdebug сборка предоставляет функции для отладки из gdb, которые могут облегчить жизнь в некоторых случаях. На данный момент я активно использую ps() — функция для просмотра Java-стектрейсов из gdb и pfl() — функция для анализа стек фреймов (очень удобно при отладке интерпретатора в gdb).


Пример ps() и pfl()

Для примера рассмотрим следующий gdb-скрипт

#путь к исполняемому java
file /home/dmitrii/jdk12/build/linux-x86_64-server-fastdebug/images/jdk/bin/java

#не останавливаемся при SEGV-ах, HotSpot
#искусственно генерирует SEGV для проверок.
#например, https://hg.openjdk.java.net/jdk/jdk12/file/06222165c35f/src/hotspot/cpu/x86/vm_version_x86.cpp#l361
handle SIGSEGV nostop noprint

set breakpoint pending on

set pagination off

#Брейкпоинт на методе, который вызывается
#непосредственно перед передачей управления
#в java-метод public static void main(String args[])
b PostJVMInit thread 2
commands
    #Буффер для имен методов,
    #иначе падаем в корку
    set $buf = (char *) malloc(1000)

    #Ставим брейкпоинт на точке входа в интерпретируемые функции
    #(Подробнее об этом ниже)
    b *AbstractInterpreter::_entry_table[0] thread 2
    commands
        #Указатель на метод хранится в регистре rbx.
        #Кастуем его к Method*
        set $mthd = ((Method *) $rbx)
        #Читаем сигнатуру метода в $buf
        call $mthd->name_and_sig_as_C_string($buf, 1000)
        #пропускаем все, кроме public static void main(String args)
        if strcmp()("Main.main([Ljava/lang/String;)V", $buf) == 0
            #ставим брейкпоинт на функции, которая вызывается из шаблона интерпретатора
            #вызов ps/pfl напрямую из интерпретатора валит процесс в корку
            #(скорее всего это ограничение ps/pfl)
            b InterpreterRuntime::build_method_counters(JavaThread*, Method*)
            commands
                #удаляем все брейкпоинты, чтобы завершиться после
                #вызова функций ниже
                delete breakpoints
                call ps()
                call pfl()
                c
            end
        end
        c
    end
    c
end

r -cp /home/dmitrii/jdk12/ Main

Результат запуска такого скрипта имеет вид:

"Executing ps"                                                                                                                                                                                                     
 for thread: "main" #1 prio=5 os_prio=0 cpu=468,61ms elapsed=58,65s tid=0x00007ffff001b800 nid=0x5bfa runnable  [0x00007ffff7fd9000]                                                                               
   java.lang.Thread.State: RUNNABLE                                                                                                                                                                                
Thread: 0x00007ffff001b800  [0x5bfa] State: _running _has_called_back 0 _at_poll_safepoint 0                                                                                                                       
   JavaThread state: _thread_in_Java                                                                                                                                                                               

 1 - frame( sp=0x00007ffff7fd9920, unextended_sp=0x00007ffff7fd9920, fp=0x00007ffff7fd9968, pc=0x00007fffd828748b)                                                                                                 
Main.main(Main.java:10)                                                                                                                                                                                            

"Executing pfl"                                                                                                                                                                                                    
 for thread: "main" #1 prio=5 os_prio=0 cpu=468,83ms elapsed=58,71s tid=0x00007ffff001b800 nid=0x5bfa runnable  [0x00007ffff7fd9000]                                                                               
   java.lang.Thread.State: RUNNABLE                                                                                                                                                                                
Thread: 0x00007ffff001b800  [0x5bfa] State: _running _has_called_back 0 _at_poll_safepoint 0
   JavaThread state: _thread_in_Java

[Describe stack layout]
 0x00007ffff7fd99e0: 0x00007ffff7fd9b00 #2 entry frame
                                        call_stub word fp - 0
 0x00007ffff7fd99d8: 0x00007ffff7fd9c10 call_stub word fp - 1
 0x00007ffff7fd99d0: 0x00007fffd8287160 call_stub word fp - 2
 0x00007ffff7fd99c8: 0x00007fffbf1fb3e0 call_stub word fp - 3
 0x00007ffff7fd99c0: 0x000000000000000a call_stub word fp - 4
 0x00007ffff7fd99b8: 0x00007ffff7fd9ce8 call_stub word fp - 5
 0x00007ffff7fd99b0: 0x00007ffff7fd9a80 call_stub word fp - 6
 0x00007ffff7fd99a8: 0x00007ffff001b800 call_stub word fp - 7
 0x00007ffff7fd99a0: 0x00007ffff7fd9b40 call_stub word fp - 8
 0x00007ffff7fd9998: 0x00007ffff7fd9c00 call_stub word fp - 9
 0x00007ffff7fd9990: 0x00007ffff7fd9a80 call_stub word fp - 10
 0x00007ffff7fd9988: 0x00007ffff7fd9ce0 call_stub word fp - 11
 0x00007ffff7fd9980: 0x00007fff00001fa0 call_stub word fp - 12
 0x00007ffff7fd9978: 0x0000000716a122b8 sp for #2
                                        locals for #1
                                        unextended_sp for #2
                                        local 0
 0x00007ffff7fd9970: 0x00007fffd82719f3
 0x00007ffff7fd9968: 0x00007ffff7fd99e0 #1 method Main.main([Ljava/lang/String;)V @ 0
                                        - 1 locals 1 max stack
 0x00007ffff7fd9960: 0x00007ffff7fd9978 interpreter_frame_sender_sp
 0x00007ffff7fd9958: 0x0000000000000000 interpreter_frame_last_sp
 0x00007ffff7fd9950: 0x00007fffbf1fb3e0 interpreter_frame_method
 0x00007ffff7fd9948: 0x0000000716a11c40 interpreter_frame_mirror
 0x00007ffff7fd9940: 0x0000000000000000 interpreter_frame_mdp
 0x00007ffff7fd9938: 0x00007fffbf1fb5e8 interpreter_frame_cache
 0x00007ffff7fd9930: 0x00007ffff7fd9978 interpreter_frame_locals
 0x00007ffff7fd9928: 0x00007fffbf1fb3d0 interpreter_frame_bcp
 0x00007ffff7fd9920: 0x00007ffff7fd9920 sp for #1
                                        interpreter_frame_initial_sp
                                        unextended_sp for #1

Как можно видеть, в случае ps() мы просто получаем стек вызовов, в случае pfl() — полную организацию стека.


Запуск java приложения

Прежде чем перейти к рассмотрению непосредственно интерпретатора, сделаем краткий обзор действий, выполняющихся до передачи управления java-коду. Для примера возьмем программу на языке Java, которая "не делает вообще ничего":

public class Main {
    public static void main(String args[]){ }
}

и попробуем разобраться в том, что происходит при запуске такого приложения:

javac Main.java && java Main

Первое что нужно сделать, чтобы ответить на этот вопрос, это найти и посмотреть на бинарник java — тот самый, который мы используем для запуска все наших JVM-приложений. В моем случае он располагается по пути

/home/dmitrii/jdk12/build/linux-x86_64-server-fastdebug/images/jdk/bin/java.

Но смотреть в итоге тут особо не на что. Это бинарник который вместе с дебажными символами занимает всего 20КБ и скомпилирован только из одного исходного файла launcher/main.c.

Все, что он делает это получает аргументы командной строки (char *argv[]), читает аргументы из переменной среды JDK_JAVA_OPTIONS, делает базовый препроцессинг и валидацию (например, нельзя добавить терминальную опцию или имя Main-класса в эту переменну среды) и вызывает функцию JLI_Launch с полученным списком аргументов.

Опреление функции JLI_Launch не содержится в бинарнике java и, если посмотреть на его прямые зависимости:

$ ldd java
        linux-vdso.so.1 (0x00007ffcc97ec000)
        libjli.so => /home/dmitrii/jdk12/build/linux-x86_64-server-fastdebug/images/jdk/bin/./../lib/libjli.so (0x00007ff27518d000) // <--------- Вот эта либа
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff274d9c000)
        libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007ff274b7f000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007ff27497b000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007ff27475c000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ff27559f000)

то можно заметить libjli.so которая к нему прилинкована. Данная библиотека содержит launcher interface — набор функций, которые используются java для инициализации и запуска виртуальной машины, среди которых присутствует и JLI_Launch.


Полный список функций интерфейса
$ objdump -T -j .text libjli.so 

libjli.so:     file format elf64-x86-64

DYNAMIC SYMBOL TABLE:
0000000000009280 g    DF .text  0000000000000038  Base        JLI_List_add
0000000000003330 g    DF .text  00000000000001c3  Base        JLI_PreprocessArg
0000000000008180 g    DF .text  0000000000000008  Base        JLI_GetStdArgs
0000000000008190 g    DF .text  0000000000000008  Base        JLI_GetStdArgc
0000000000007e50 g    DF .text  00000000000000b8  Base        JLI_ReportErrorMessage
000000000000a400 g    DF .text  00000000000000df  Base        JLI_ManifestIterate
0000000000002e70 g    DF .text  0000000000000049  Base        JLI_InitArgProcessing
0000000000008000 g    DF .text  0000000000000011  Base        JLI_ReportExceptionDescription
0000000000003500 g    DF .text  0000000000000074  Base        JLI_AddArgsFromEnvVar
0000000000007f10 g    DF .text  00000000000000e9  Base        JLI_ReportErrorMessageSys
0000000000005840 g    DF .text  00000000000000b8  Base        JLI_ReportMessage
0000000000009140 g    DF .text  000000000000003a  Base        JLI_SetTraceLauncher
0000000000009020 g    DF .text  000000000000000a  Base        JLI_MemFree
0000000000008f90 g    DF .text  0000000000000026  Base        JLI_MemAlloc
00000000000059c0 g    DF .text  0000000000002013  Base        JLI_Launch
00000000000091c0 g    DF .text  000000000000003b  Base        JLI_List_new
0000000000008ff0 g    DF .text  0000000000000026  Base        JLI_StringDup
0000000000002ec0 g    DF .text  000000000000000c  Base        JLI_GetAppArgIndex

После передачи управления JLI_Launch происходит ряд действий необходимых для запуска JVM такие как:

I. Загрузка символов JVM HotSpot в память и получение указателя на функцию для создания VM.

Весь код JVM HotSpot располагается в библиотеке libjvm.so. После определения абсолютного пути к libjvm.so происходит загрузка библиотеки в память и выдирание из нее указателя на функцию JNI_CreateJavaVM. Этот указатель на функцию сохраняется и в дальнейшем используется для создания и инициализации виртуальной машины.

Очевидно, что libjvm.so не прилинкована к libjli.so

II. Парсинг аргументов, переданных после препроцессинга.

Функция с говорящим названием ParseArguments разбирает аргументы, переданные из командной строки. Этот парсер аргументов определяет режим запуска приложения

enum LaunchMode {               // cf. sun.launcher.LauncherHelper
    LM_UNKNOWN = 0,
    LM_CLASS,
    LM_JAR,
    LM_MODULE,
    LM_SOURCE
};

Также он преобразует часть аргументов в формат -DpropertyName=propertyValue, например -cp=/path приводится к виду -Djava.class.path=/path. Далее такие SystemProperty сохраняются в глобальном массиве в JVM HotSpot и пробрасываются в java.lang.System::props в первой фазе инициализации (В JDK12 механизм инициализации java.lang.System.props был модифицирован, подробнее в этом коммите).

Парсинг аргументов также отбрасывает часть опций, которые не обрабатываются JVM (например --list-modules, обработка данной опции происходит непосредственно в launcher'e в этом месте).

III. Форк primordial потока и создание в нем VM

Но если что-то пошло не так, то делается попытка запустить JVM в main-треде "just give it a try".

Поизучав вопрос, я нашел одну из возможных причин, по которой JVM запускается не в main-треде. Дело в том, что (по крайней мере в Linux) pthread'ы и main-тред работают со стеком по разному. Размер main-thread'a ограничен значением ulimit -s, т.е. при выставлении сколь угодно большого значения мы получим сколь угодно большой стек. Main-тред использует нечто похожее на MAP_GROWSDOWN, но не MAP_GROWSDOWN. Использование MAP_GROWSDOWN в чистом виде не безопасно и, если мне не изменяет память, задепрекейчено. На моей машине MAP_GROWSDOWN не добавляет никакого эффекта. Отличие маппинга main-треда от MAP_GROWSDOWN в том, что никакой другой mmap, за исключением MAP_FIXED, не сможет сделать коллизию с областью возможного расширения стека. Все что нужно от софта — это выставить соответствующее значение rsp и дальше ОС сама разберется: И page-fault обработает и guard выставит. Такое различие пораждает некоторое количество граблей: При определении размера стека текущего потока, при создании guard-pages

Итак, будем считать, что на данный момент у нас успешно распарсились опции и создался поток для VM. После этого, только что форкнутый поток начинает создание виртуальной машины и попадает в функцию Threads::create_vm

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


Инициализация интепретатора и перадача управления java-коду

Для каждой инструкции в JVM HotSpot существует определенный шаблон машинного кода под конкретную архитектуру. Когда интерпретатор приступает к выполнению какой-либо инструкции, первым делом ищется адрес ее шаблона в специальной таблице DispatchTable. Далее происходит jump по адресу данного шаблона и после того как выполнение инструкции завершено, jvm достает адрес следующей по порядку инструкции) и начинает выполнять ее аналогичным образом, и так далее. Такое поведение наблюдается у интерепретатора только для инструкций, которые не "делают dispatch", например, арифметические инструкции (xsub, xdiv, etc, где xi, l, f, d). Все, что они делают — это выполняют арифметические операции.

В случае инструкций вызова процедур (invokestatic, invokevirtual, и т.д.) следующей к выполнению инструкцей будет первая по порядку инструкция вызываемой процедуры. Такие инструкции самостоятельно проставляют адрес следующей bytecode-инструкции к выполнению в своем шаблоне.

Чтобы обеспечить работу данной машинерии в Threads::create_vm выполняется ряд инициализаций, от которых зависит интерпретатор:

I. Инициализация таблицы доступных байткодов

Прежде чем приступить к инициализации интерпретатора, необходимо проинициализировать таблицу используемых байткодов. Она выполняется в функции Bytecodes::initialize и представлена в виде очень удобочитаемой таблички. Ее фрагмент выглядит следующим образом:

  //  Java bytecodes
  //  bytecode               bytecode name           format   wide f.   result tp  stk traps
  def(_nop                 , "nop"                 , "b"    , NULL    , T_VOID   ,  0, false);
  def(_aconst_null         , "aconst_null"         , "b"    , NULL    , T_OBJECT ,  1, false);
  def(_iconst_m1           , "iconst_m1"           , "b"    , NULL    , T_INT    ,  1, false);
  def(_iconst_0            , "iconst_0"            , "b"    , NULL    , T_INT    ,  1, false);
  def(_iconst_1            , "iconst_1"            , "b"    , NULL    , T_INT    ,  1, false);
  def(_iconst_2            , "iconst_2"            , "b"    , NULL    , T_INT    ,  1, false);
  def(_iconst_3            , "iconst_3"            , "b"    , NULL    , T_INT    ,  1, false);
  def(_iconst_4            , "iconst_4"            , "b"    , NULL    , T_INT    ,  1, false);
  def(_iconst_5            , "iconst_5"            , "b"    , NULL    , T_INT    ,  1, false);
  def(_lconst_0            , "lconst_0"            , "b"    , NULL    , T_LONG   ,  2, false);
  def(_lconst_1            , "lconst_1"            , "b"    , NULL    , T_LONG   ,  2, false);
  def(_fconst_0            , "fconst_0"            , "b"    , NULL    , T_FLOAT  ,  1, false);
  def(_fconst_1            , "fconst_1"            , "b"    , NULL    , T_FLOAT  ,  1, false);
  def(_fconst_2            , "fconst_2"            , "b"    , NULL    , T_FLOAT  ,  1, false);
  def(_dconst_0            , "dconst_0"            , "b"    , NULL    , T_DOUBLE ,  2, false);
  def(_dconst_1            , "dconst_1"            , "b"    , NULL    , T_DOUBLE ,  2, false);
  def(_bipush              , "bipush"              , "bc"   , NULL    , T_INT    ,  1, false);
  def(_sipush              , "sipush"              , "bcc"  , NULL    , T_INT    ,  1, false);
  def(_ldc                 , "ldc"                 , "bk"   , NULL    , T_ILLEGAL,  1, true );
  def(_ldc_w               , "ldc_w"               , "bkk"  , NULL    , T_ILLEGAL,  1, true );
  def(_ldc2_w              , "ldc2_w"              , "bkk"  , NULL    , T_ILLEGAL,  2, true );

В соответствии с данной таблицей, для каждого байткода выставляются его длина (размер всегда 1 байт, но может быть еще индекс в ConstantPool, а также широкие байткоды), имя, байткод и флаги:

bool            Bytecodes::_is_initialized = false;
const char*     Bytecodes::_name          [Bytecodes::number_of_codes];
BasicType       Bytecodes::_result_type   [Bytecodes::number_of_codes];
s_char          Bytecodes::_depth         [Bytecodes::number_of_codes];
u_char          Bytecodes::_lengths       [Bytecodes::number_of_codes];
Bytecodes::Code Bytecodes::_java_code     [Bytecodes::number_of_codes];
unsigned short  Bytecodes::_flags         [(1<<BitsPerByte)*2];

Эти параметры в дальнейшем нужны для генерации кода шаблонов интерпретатора

II. Инициализация код кэша

Для того, чтобы сгенерить код шаблонов интерпретатора, необходимо сперва выделить под это дело память. Резервация памяти под код кэш реализована в функции с одноименным названием CodeCache::initialize(). Как можно видеть из следущего участка кода данной функции

 CodeCacheExpansionSize = align_up(CodeCacheExpansionSize, os::vm_page_size());

  if (SegmentedCodeCache) {
    // Use multiple code heaps
    initialize_heaps();
  } else {
    // Use a single code heap
    FLAG_SET_ERGO(uintx, NonNMethodCodeHeapSize, 0);
    FLAG_SET_ERGO(uintx, ProfiledCodeHeapSize, 0);
    FLAG_SET_ERGO(uintx, NonProfiledCodeHeapSize, 0);
    ReservedCodeSpace rs = reserve_heap_memory(ReservedCodeCacheSize);
    add_heap(rs, "CodeCache", CodeBlobType::All);
  } 

код кэш контролируется опциями -XX:ReservedCodeCacheSize, -XX:SegmentedCodeCache, -XX:CodeCacheExpansionSize, -XX:NonNMethodCodeHeapSize, -XX:ProfiledCodeHeapSize, -XX:NonProfiledCodeHeapSize. Краткое описание данных опций можно посмотреть по ссылкам на которые они ведут. Помимо коммандной строки, значения некоторых из этих опций подстраивается эргономикой, например, если используется значение SegmentedCodeCache по умолчанию (выключен), то при размере кода >= 240Mb, SegmentedCodeCache будет включен в CompilerConfig::set_tiered_flags.

После выполнения проверок резервируется область размером в ReservedCodeCacheSize байт. В случае, если SegmentedCodeCache оказалась выставленной, то данная область разбивается на части: JIT-скомпилированные методы, стаб рутины, и т.д.

III. Инициализация шаблонов интерпретатора

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

Рассмотрим каждый из этих этапов по отдельности:

  { CodeletMark cm(_masm, "slow signature handler");
    AbstractInterpreter::_slow_signature_handler = generate_slow_signature_handler();
  }

signature handler используется для подготовки аргументов для вызовов нативных методов. В данном случае генерится обощенный хэндлер, если, например у нативного метода больше 13 аргументов (В дебаггере не проверял, но судя по коду должны быть так)

 { CodeletMark cm(_masm, "error exits");
    _unimplemented_bytecode    = generate_error_exit("unimplemented bytecode");
    _illegal_bytecode_sequence = generate_error_exit("illegal bytecode sequence - method not verified");
  }

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

После вызова процедур необходимо восстановить данные стек фрейма, который был до вызова процедуры из которой делается return.

Используется при вызовах рантайма из интерепретатора.

  // Bytecodes
  set_entry_points_for_all_bytes();

  // installation of code in other places in the runtime
  // (ExcutableCodeManager calls not needed to copy the entries)
  set_safepoints_for_all_bytes();

Для выполнения инструкции спецификация VM требует чтобы операнды находились в Operand Stack, но это не запрещает HotSpot кэшировать их в регистре. Для определения текущего состояния вершины стека используется перечисление

enum TosState {         // describes the tos cache contents
  btos = 0,             // byte, bool tos cached
  ztos = 1,             // byte, bool tos cached
  ctos = 2,             // char tos cached
  stos = 3,             // short tos cached
  itos = 4,             // int tos cached
  ltos = 5,             // long tos cached
  ftos = 6,             // float tos cached
  dtos = 7,             // double tos cached
  atos = 8,             // object cached
  vtos = 9,             // tos not cached
  number_of_states,
  ilgl                  // illegal state: should not occur
};

Каждая инструкция определяет входные и выходные состояния TosState вершины стека, и генерация шаблонов происходит в зависимости от этого состояния. Данные шаблоны инициализируются в удобочитаемой таблице шаблонов. Фрагмент этой таблицы выглядит следующим образом:


 //                                    interpr. templates
  // Java spec bytecodes                ubcp|disp|clvm|iswd  in    out   generator             argument
  def(Bytecodes::_nop                 , ____|____|____|____, vtos, vtos, nop                 ,  _           );
  def(Bytecodes::_aconst_null         , ____|____|____|____, vtos, atos, aconst_null         ,  _           );
  def(Bytecodes::_iconst_m1           , ____|____|____|____, vtos, itos, iconst              , -1           );
  def(Bytecodes::_iconst_0            , ____|____|____|____, vtos, itos, iconst              ,  0           );
  def(Bytecodes::_iconst_1            , ____|____|____|____, vtos, itos, iconst              ,  1           );
  def(Bytecodes::_iconst_2            , ____|____|____|____, vtos, itos, iconst              ,  2           );
  def(Bytecodes::_iconst_3            , ____|____|____|____, vtos, itos, iconst              ,  3           );
  def(Bytecodes::_iconst_4            , ____|____|____|____, vtos, itos, iconst              ,  4           );
  def(Bytecodes::_iconst_5            , ____|____|____|____, vtos, itos, iconst              ,  5           );
  def(Bytecodes::_lconst_0            , ____|____|____|____, vtos, ltos, lconst              ,  0           );
  def(Bytecodes::_lconst_1            , ____|____|____|____, vtos, ltos, lconst              ,  1           );
  def(Bytecodes::_fconst_0            , ____|____|____|____, vtos, ftos, fconst              ,  0           );
  def(Bytecodes::_fconst_1            , ____|____|____|____, vtos, ftos, fconst              ,  1           );
  def(Bytecodes::_fconst_2            , ____|____|____|____, vtos, ftos, fconst              ,  2           );
  def(Bytecodes::_dconst_0            , ____|____|____|____, vtos, dtos, dconst              ,  0           );
  def(Bytecodes::_dconst_1            , ____|____|____|____, vtos, dtos, dconst              ,  1           );
  def(Bytecodes::_bipush              , ubcp|____|____|____, vtos, itos, bipush              ,  _           );
  def(Bytecodes::_sipush              , ubcp|____|____|____, vtos, itos, sipush              ,  _           );

Нам будут особенно интересны столбцы in, out и generator.

in — состояние вершины стека на момент начала исполнения инструкции
out — состояния вершины стека на момент завершения исполнения инструкции
generator — генератор шаблона машинного кода инструкции

Общий вид шаблона для всех байткодов можно описать в виде:


  1. Если для инструкции не выставлен dispatch bit, то выполняется пролог инструкции (no-op на x86)


  2. Используя generator, генерится машинный код


  3. Если для инструкции не выставлен dispatch bit, то выполняется переход к следующей по порядку инструкции в зависимости от out состояния вершины стека, которое будет являтся in для следующей инструкции


Адрес точки входа для полученного шаблона сохраняется в глобальной таблице и его можно использовать при отладке.

В HotSpot за это отвечает следущий, относительно стремный кусок кода:


Кодогенератор инструкций
void TemplateInterpreterGenerator::set_entry_points(Bytecodes::Code code) {
  CodeletMark cm(_masm, Bytecodes::name(code), code);
  // initialize entry points
  assert(_unimplemented_bytecode    != NULL, "should have been generated before");
  assert(_illegal_bytecode_sequence != NULL, "should have been generated before");
  address bep = _illegal_bytecode_sequence;
  address zep = _illegal_bytecode_sequence;
  address cep = _illegal_bytecode_sequence;
  address sep = _illegal_bytecode_sequence;
  address aep = _illegal_bytecode_sequence;
  address iep = _illegal_bytecode_sequence;
  address lep = _illegal_bytecode_sequence;
  address fep = _illegal_bytecode_sequence;
  address dep = _illegal_bytecode_sequence;
  address vep = _unimplemented_bytecode;
  address wep = _unimplemented_bytecode;
  // code for short & wide version of bytecode
  if (Bytecodes::is_defined(code)) {
    Template* t = TemplateTable::template_for(code);
    assert(t->is_valid(), "just checking");
    set_short_entry_points(t, bep, cep, sep, aep, iep, lep, fep, dep, vep);
  }
  if (Bytecodes::wide_is_defined(code)) {
    Template* t = TemplateTable::template_for_wide(code);
    assert(t->is_valid(), "just checking");
    set_wide_entry_point(t, wep);
  }
  // set entry points
  EntryPoint entry(bep, zep, cep, sep, aep, iep, lep, fep, dep, vep);
  Interpreter::_normal_table.set_entry(code, entry);
  Interpreter::_wentry_point[code] = wep;
}

//...
void TemplateInterpreterGenerator::set_short_entry_points(Template* t, address& bep, address& cep, address& sep, address& aep, address& iep, address& lep, address& fep, address& dep, address& vep) {
  assert(t->is_valid(), "template must exist");
  switch (t->tos_in()) {
    case btos:
    case ztos:
    case ctos:
    case stos:
      ShouldNotReachHere();  // btos/ctos/stos should use itos.
      break;
    case atos: vep = __ pc(); __ pop(atos); aep = __ pc(); generate_and_dispatch(t); break;
    case itos: vep = __ pc(); __ pop(itos); iep = __ pc(); generate_and_dispatch(t); break;
    case ltos: vep = __ pc(); __ pop(ltos); lep = __ pc(); generate_and_dispatch(t); break;
    case ftos: vep = __ pc(); __ pop(ftos); fep = __ pc(); generate_and_dispatch(t); break;
    case dtos: vep = __ pc(); __ pop(dtos); dep = __ pc(); generate_and_dispatch(t); break;
    case vtos: set_vtos_entry_points(t, bep, cep, sep, aep, iep, lep, fep, dep, vep);     break;
    default  : ShouldNotReachHere();                                                 break;
  }
}

//...

void TemplateInterpreterGenerator::generate_and_dispatch(Template* t, TosState tos_out) {
  if (PrintBytecodeHistogram)                                    histogram_bytecode(t);
#ifndef PRODUCT
  // debugging code
  if (CountBytecodes || TraceBytecodes || StopInterpreterAt > 0) count_bytecode();
  if (PrintBytecodePairHistogram)                                histogram_bytecode_pair(t);
  if (TraceBytecodes)                                            trace_bytecode(t);
  if (StopInterpreterAt > 0)                                     stop_interpreter_at();
  __ verify_FPU(1, t->tos_in());
#endif // !PRODUCT
  int step = 0;
  if (!t->does_dispatch()) {
    step = t->is_wide() ? Bytecodes::wide_length_for(t->bytecode()) : Bytecodes::length_for(t->bytecode());
    if (tos_out == ilgl) tos_out = t->tos_out();
    // compute bytecode size
    assert(step > 0, "just checkin'");
    // setup stuff for dispatching next bytecode
    if (ProfileInterpreter && VerifyDataPointer
        && MethodData::bytecode_has_profile(t->bytecode())) {
      __ verify_method_data_pointer();
    }
    __ dispatch_prolog(tos_out, step);
  }
  // generate template
  t->generate(_masm);
  // advance
  if (t->does_dispatch()) {
#ifdef ASSERT
    // make sure execution doesn't go beyond this point if code is broken
    __ should_not_reach_here();
#endif // ASSERT
  } else {
    // dispatch to next bytecode
    __ dispatch_epilog(tos_out, step);
  }
}

Как только данная кодогенерация завершена, интепретатор можно считать полностью проинициализированным. После интерпретатора выполняется еще много инициализаций различных подсистем JVM. Для некоторых из них требуется вызывать Java-код из кода виртуальной машины. Это реализовано с помощью стандартного механизма JavaCalls. После того как инициализация JVM полностью завершена, этот механизм используется для вызова метода main.


Пример

Для того, чтобы представлять как это все работает на практике, рассмотрим следующий относительно простой пример:

public class Sum{
    public static void sum(int a, int b){
        return a + b;
    }
}

public class Main {
    public static void main(String args[]){
        Sum.sum(2, 3);
    }
}

и попытаемся понять что происходит при вызове метода Sum.sum(II).

Скомпилируем эти 2 класса javac -c *.java и убедимся в том, что компилятор не сделал никаких оптимизаций.
Байткод Sum.sum:

    descriptor: (II)I
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_0
         1: iload_1
         2: iadd
         3: ireturn
      LineNumberTable:
        line 3: 0

Байткод Main.main

    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: iconst_2
         1: iconst_3
         2: invokestatic  #2                  // Method Sum.sum:(II)I
         5: pop
         6: return
      LineNumberTable:
        line 13: 0
        line 14: 6

Байткод ровно такой, какой нам нужен и первое с чего придется начать — это с анализа вызова статического метода.

Генератор шаблона invokestatic'а для x86 находится в архитектурно-зависимой секции кода HotSpot и представлен в виде

void TemplateTable::invokestatic(int byte_no) {
  transition(vtos, vtos);
  assert(byte_no == f1_byte, "use this argument");
  prepare_invoke(byte_no, rbx);  // get f1 Method*
  // do the call
  __ profile_call(rax);
  __ profile_arguments_type(rax, rbx, rbcp, false);
  __ jump_from_interpreted(rbx, rax);
}

byte_no == f1_byte — это секция ConstantPoolCache, относящаяся к статическим методам, rbx — регистр, в котором будет храниться указатель Method *. В остальном все в принципе понятно: Подготовка вызова, профайлинг, переход на точку входа метода (method_entry при генерации шаблонов интерпретатора).

Рассмотрим подронее prepare_invoke. Как известно, следом за байткодом инструкции invokestatic идет индекс в ConstantPool на Constant_Methodref_Info. В случае HotSpot это не совсем так. Следущие 2 байта указывают на индекс в т.н. ConstantPoolCache. ConstantPoolCache это структура данных в которой хранится информация, нужная для интерпретатора (например, было ли зарезолвено ConstantPoolCacheEntry по данному индексу, реализуя таким образом ленивость загрузки классов). После того как это ConstantPoolCacheEntry зарезолвено, в него записывается номер байткода (изначально там был 0) и этот номер используется при дальнейшем определении зарезолвено/не зарезолвено. Несмотря на то, что при загрузке класса индексы изначально указывают в ConstantPool, при линковке класса они будут перезаписаны на ConstantPoolCache индексы в нативном байт ордере (на x86 Little Endian).

Итак, первое, что HotSpot пытается сделать в prepare_invoke — это достать индекс на ConstantPoolCache. После того, как индекс получен, делается проверка на зарезолвенность ConstantPoolCacheEntry по данному индексу

  __ get_cache_and_index_and_bytecode_at_bcp(Rcache, index, temp, byte_no, 1, index_size);
  __ cmpl(temp, code);  // have we resolved this bytecode?
  __ jcc(Assembler::equal, resolved);

  // resolve first time through
  address entry = CAST_FROM_FN_PTR(address, InterpreterRuntime::resolve_from_cache);
  __ movl(temp, code);
  __ call_VM(noreg, entry, temp);
  // Update registers with resolved info
  __ get_cache_and_index_at_bcp(Rcache, index, 1, index_size);
  __ bind(resolved);

Если нет, значит нужно вызывать InterpreterRuntime::resolve_from_cache.

В данной функции выполняется загрузка класса receiver'a вызываемого статического метода, если на данный момент класс еще не был загружен. После загрузки выполняется инициализация (линковка, валидация, перезаписывание байткода, создание ConstantPoolCache и вызов <clinit>, если такой метод присутствует в байткоде). Не ленивая инициализация может выполняться и сразу после define class, если выставлен флаг EagerInitialization (флаг девелоперский, поэтому из коммандной строки не доступен, но кто нам запретит у себя его поменять на продакшн :)). Вообще загрузка классов в HotSpot в общем (и CDS в частности) имеет относительно не тривиальную реализацию.

После того, как класс инициализирован и нужный метод в классе найден, инициализируется соответветствующее ConstantPoolCacheEntry этими байткодом и методом. После этого интерпретатор загружает указатель Method * в rbx, достает адрес возврата, делает профайлинг и переходит на точку входа вызовав метода.

Исследуем теперь точку входа при вызове Sum.sum(2, 3). Для этого нам потребуется следующий gdb-script sum.gdb:

#Путь к исполняемому файлу java
file /home/dmitrii/jdk12/build/linux-x86_64-server-fastdebug/images/jdk/bin/java

#Говорим gdb не останавливаться на SEGV'ах
#таких, как этот https://hg.openjdk.java.net/jdk/jdk12/file/06222165c35f/src/hotspot/cpu/x86/vm_version_x86.cpp#l361
handle SIGSEGV nostop noprint

#Символы на данный момент еще не загружены
set breakpoint pending on

#Чтобы быстро проскипать статические методы,
#не относящиеся к эксперименту
set pagination off

#Ставим брейк сразу перед вызовом метода main
b PostJVMInit
commands
    #Буффер для сигнатур методов,
    #иначе упадем в корку
    set $buffer = malloc(1000)

    #Точка входа в метод.
    #jmp по этому адресу делается в генерируемом
    #шаблоне invokestatic
    b *AbstractInterpreter::_entry_table[0] thread 2
    commands
        #В соответсвии с кодом шаблона invokestatic,
        #указатель Method* хранится в rbx
        set $mthd = (Method *) $rbx
        #Получаем сигнатуру метода в $buffer
        call $mthd->name_and_sig_as_C_string($buffer, 1000)
        if strcmp()($buffer, "Sum.sum(II)I") == 0
            #Брейкпоинт на iload_0, вершина стека не закеширована
            b *TemplateInterpreter::_normal_table._table[vtos][26] thread 2
            #Брейкпоинт на iload_1, вершина стека - int, закеширована
            #после выполнения iload_0
            b *TemplateInterpreter::_normal_table._table[itos][27] thread 2
            #Брейкпоинт на инструкции iadd
            b *TemplateInterpreter::_normal_table._table[itos][96] thread 2
        end
        c
    end
    c
end

r -cp . Main

Запустив данный скрипт gdb -x sum.gdb, останавливаемся на точке входа в метод Sum.sum

$453 = 0x7ffff7fdcdd0 "Sum.sum(II)I"

Если открыть layout asm, то мы увидим код, сгенеренный методом generate_normal_entry. В данном шаблоне делается создание стек-фрейма, проверка StackOverflow, stack-banging и далее делается dispatch на первую инструкцию iload_0 при незакешированной вершине стека. В этом случае код интерпретатора имеет вид:

0x7fffd828fa1f  mov    eax,DWORD PTR [r14] ;собственно, iload_0                                                                                                                                    0x7fffd828fa22  movzx  ebx,BYTE PTR [r13+0x1] ;загружаем следующий байткод                                                                                                                                     0x7fffd828fa27  inc    r13 ;инкремент bcp (byte code pointer)                                                                                                                    0x7fffd828fa2a  movabs r10,0x7ffff717e8a0 ;загрузка DispatchTable                                                                                                                                 0x7fffd828fa34  jmp    QWORD PTR [r10+rbx*8] ;jump в зависимости от вершины стека

После этого вершина стека оказалась закешированной в rax, а значит интерпретатор переходит в следующий шаблон

0x7fffd828fabe  push   rax ;кладем кешированную вершину на стек                                                                                                                       
;далее все тоже самое, что и в предыдущем примере
0x7fffd828fabf  mov    eax,DWORD PTR [r14-0x8]                                                                                                                                                               0x7fffd828fac3  movzx  ebx,BYTE PTR [r13+0x1]                                                                                                                                                                0x7fffd828fac8  inc    r13                                                                                                                                                                                   0x7fffd828facb  movabs r10,0x7ffff717e8a0                                                                                                                                                                     0x7fffd828fad5  jmp    QWORD PTR [r10+rbx*8]    

Ну а теперь и сама инструкция iadd:

0x7fffd8292ba7  mov    edx,DWORD PTR [rsp] ;загружаем то, что ранее запушили в iload_1                                                                                                                                    0x7fffd8292baa  add    rsp,0x8 ;поправляем rsp руками после загрузки                                                                                                                            0x7fffd8292bae  add    eax,edx ;сложение двух интов                                                                                                                      0x7fffd8292bb0  movzx  ebx,BYTE PTR [r13+0x1]                                                                                                                                                                 0x7fffd8292bb5  inc    r13                                                                                                                                                                                    0x7fffd8292bb8  movabs r10,0x7ffff717e8a0                                                                                                                                                                     0x7fffd8292bc2  jmp    QWORD PTR [r10+rbx*8] 

Если посмотреть в gdb на eax и edx сразу перед выполнением сложения, то можно заметить

(gdb) p $eax
$457 = 3
(gdb) p $edx
$458 = 2

А это и есть те самые операнды, которые мы передали функции Sum.sum.

Let's block ads! (Why?)

Что такое маржинальная торговля на бирже, и как она работает

Huawei запустит собственный сервис видео в России и еще в нескольких странах

Китайская компания Huawei намерена запустить свой сервис Huawei Video. Его будут продвигать в России и в ряде других стран, заявил РБК вице-президент направления мобильных сервисов подразделения потребительских продуктов компании в Европе Джейме Гонсало.

Видеосервис будет объединять контент различных видеоплатформ. В их числе оказались и российские, в частности, ivi.ru и Megogo. Гонсало пояснил, что Huawei Video не будет конкурировать с Netflix или Spotify, а предложит им партнерство.

Гендиректор Megogo Виктор Чеканов подтвердил, что компания ждет развертывания проекта, чтобы начать сотрудничать с китайцами.

Сроки запуска Huawei Video пока не разглашаются. Известно лишь, что пользователи сервиса смогут оплачивать доступ ко всем платформам только через него.

Гендиректор ассоциации «Интернет-видео» Алексей Бырдин предположил, что из-за относительно невысокой популярности платных подписок у пользователей Android новый сервис Huawei, скорее, будет конкурировать не с другими видеосервисами, а с теми, кто агрегирует их контент в России. По его оценкам, число устройств Huawei в России может достигать 10–12 млн.

Huawei Video запустили в Китае в 2016 году, а в 2018-м сервис заработал в Италии и Испании. В этих странах стоимость подписки составляет € 4,99 в месяц, а аренда фильма или сериала на 48 часов — € 2,99. Сервис доступен на мобильных устройствах Huawei или ее дочернего бренда Honor.

До этого Huawei решила поставлять серверы под брендом Taishan поставщику облачных сервисов «Тионикс», который входит в «Ростелеком». В компании отмечали, что поставки произведут в рамках нацпрограммы «Цифровая экономика» по разработке локализованной платформы «единого облака». Эта концепция предусматривает поэтапный перевод информационных систем госорганов в единую облачную платформу. На новую систему планируют потратить 47,6 млрд рублей, из них 6,5 млрд поступит из бюджета.

Let's block ads! (Why?)

«Яндекс.Дзен» разрешил пользователям публиковать видеоролики

Пользователи платформы «Яндекс.Дзен» смогут публиковать видеоролики и размещать нативную рекламу в видеоформате. Об этом сообщают «Известия».

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

Первые эксперименты показали, что самыми эффективными являются короткие видео хронометражем до 15 минут. Также выяснилось, что каждый пользователь сервиса смотрит, в среднем, по два ролика в день. В компании отметили, что видеоформат уже обогнал по популярности нарратив, который всегда имел самые высокие показатели вовлеченности. Так, видео набирают по 20 млн просмотров.

Генеральный директор «Яндекс.Дзена» Дмитрий Иванов отметил, что площадка стала текстовой платформой для многих блогеров: «Мы рады тому, что алгоритмы Дзена помогают пишущим авторам найти свою аудиторию, но не хотим на этом останавливаться».

На платформе «Яндекс.Дзен» доступны авторский контент пользователей и материалы СМИ. Алгоритм платформы отбирает их в соответствии с интересами пользователей. «Яндекс» при этом зарабатывает на рекламе и делится доходами с авторами.

Как отдельная площадка проект заработал в 2017 году. Со временем в «Дзене» добавились форматы коротких сообщений, возможности оставлять комментарии под постами и другие.

По подсчетам «Яндекса», свои каналы на платформе имеют 25 тысяч авторов и СМИ. Хотя бы раз в месяц «Дзеном» пользуются 58 млн человек.

Let's block ads! (Why?)

Инженер Байков выплатит 450 000 руб. штрафа за майнинг на суперкомпьютере в ядерном центре

17 сентября 2019 года Саровский городской суд Нижегородской области оштрафовал на 450 тыс. руб. сотрудника Российского федерального ядерного центра (РФЯЦ-ВНИИЭФ) за майнинг криптовалюты на служебном суперкомпьютере. В ближайшее время суд рассмотрит дела двух его коллег (см. скриншот выше). Все они закончили университеты с красными дипломами и были ведущими специалистами на своих научных кафедрах.
Адвокат одного из обвиняемых Алексей Королёв рассказал, что инженеры до последнего момента надеялись, что их не обнаружат: «Они несколько месяцев готовились, изучали способы добычи криптовалюты. Смотрели, соответствует ли компьютер необходимым мощностям, а также разработали собственную программу, чтобы оставаться незамеченными. В компьютере в диспетчере задач всегда отражается, куда и когда заходил пользователь. Они разработали программу, которая не отображала в диспетчере задач эту информацию. Думали, что так их не сможет никто вычислить, ведь они профессионалы в области программирования», — рассказал адвокат.

«В Саровский городской суд переданы два уголовных дела в отношении сотрудников ядерного центра Рыбкина и Шатохина. Дата рассмотрения дел пока не назначена. В отношении ещё одного сотрудника, Байкова, дело уже рассмотрено, ему назначен штраф 450 тысяч рублей. Приговор пока не вступил в законную силу», — сказали в суде.

Скольно намайнили программисты на суперкомпьютере ядерного центра — не сообщается. В приговоре Денису Байкову также ничего не сказано о конфискации добытых монет.

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

«Я не могу сказать, какие оперативные мероприятия проводились, чтобы их обнаружить, потому что некоторые из них до сих пор засекречены. Следователи говорили во время следствия, что дело находится на контроле у администрации президента и якобы даже Путину об этом докладывали. Но одно могу сказать точно: задержали их не в первый день, когда они начали майнить, — пояснил адвокат. Он добавил, что сразу после задержания майнеры признали вину и дали показания. — Вначале суд избрал им меру пресечения в виде заключения под стражу, но позднее её заменили на подписку о невыезде с учётом того, что они дали признательные показания. Конечно, они сожалеют о том, что сделали. Но, думаю, пошли они на это из профессионального интереса, а не с целью наживы».

Если майнинг осуществлялся из профессионального интереса, а не с целью наживы, то это безусловно станет смягчающим обстоятельством.

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

По словам адвоката, обвиняемые окончили вузы с красными дипломами, были на хорошем счету во ВНИИЭФ. Одному из них лично пожимал руку президент России Владимир Путин, когда приезжал в Саров в 2014 году: «Из-за сделанного они лишились должностей в госкорпорации. Конечно, их зарплата была выше среднего по меркам города, но не думаю, что сейчас они получают меньше. Мой подзащитный занимается муниципальными государственными контрактами по закупкам. То есть зарабатывает на тендерах по обеспечению компьютерными технологиями государственных корпораций, к которым относится тот же ВНИИЭФ», — сказал адвокат с расчётом на то, что суд всё-таки отвергнет корыстные мотивы инженера.

Представители института заявили, что деятельность обвиняемых не принесла ущерба: «Благодаря совместной работе с органами безопасности данная деятельность была своевременно пресечена. Горе-майнеры задержаны компетентными органами. Насколько нам известно, в их отношении возбуждено уголовное дело. Обращаем внимание, что подобные попытки в последнее время были зарегистрированы в целом ряде крупных компаний, имеющих большие вычислительные мощности. На наших предприятиях они будут жёстко пресекаться, это технически бесперспективное и уголовно наказуемое занятие», — сообщила пресс-служба ядерного центра.

Андей Рыбкин и Андрей Шатохин обвиняются по статья 272 УК РФ («Неправомерный доступ к компьютерной информации»), ст. 273 УК РФ («Создание, использование и распространение вредоносных компьютерных программ»), а также ст. 274 УК РФ («Нарушение правил эксплуатации средств хранения, обработки или передачи компьютерной информации и информационно-телекоммуникационных сетей»). Вышеупомянутому Денису Байкову вынесен приговор только по статьям 272 и 274.


Российский федеральный ядерный центр в Сарове

Российский федеральный ядерный центр в Сарове — крупнейший в стране научно-исследовательский институт, решающий сложные задачи оборонного, научного и народнохозяйственного значения. Основанный в 1946 году институт внёс определяющий вклад в создание ядерного и термоядерного оружия в СССР. Здесь были разработаны первые отечественные атомная и водородная бомбы.

«Главная задача ядерного центра сегодня — обеспечение и поддержание надёжности и безопасности ядерного оружия России, — сказано на официальном сайте. — РФЯЦ-ВНИИЭФ обладает мощной расчётной, экспериментальной, испытательной, технологической и производственной базой, что позволяет оперативно и качественно решать возлагаемые на него задачи».

Суперкомпьютер мощностью 1 петафлопс в Саровском ядерном центре прошёл приемочные испытания и официально введен в эксплуатацию 9 марта 2011 года. На тот момент он занял 12-е место в мировом рейтинге самых мощных суперкомпьютеров топ-500.

Технические детали системы не разглашались. По информации источника в «Росатоме», суперкомпьютер построен на базе процессоров с микроархитектурой x86, графические ускорители в нём не используются. Известно также, что в системе используется только воздушное охлаждение.


Иллюстрация к патенту на систему охлаждения массивно-параллельных вычислительных систем, выданного Саровскому ядерному центру в 2018 году

В планах учёных было постепенное увеличение вычислительных мощностей в десять раз каждые три года с выходом на 1 экзафлопс к 2020 году. Судя по всему, эти планы не воплотились в жизнь, потому что технические характеристики суперкомпьютера за последние восемь лет не изменились.

Let's block ads! (Why?)