В этом выпуске: погружение в архитектуру и безопасность ОС Fuchsia, несколько способов использовать Kotlin в качестве скриптового языка, неожиданный способ запустить тяжеловесные вычисления в основном потоке приложения, читшит по коллекциям Kotlin, советы по работе с отладчиком, способ быстрого заполнения полей data-классов и способ хранения логов на удаленном сервере без необходимости поднимать сервер. А также свежая подборка библиотек для разработчиков.
Playing Around With The Fuchsia Operating System — исследование безопасности операционной системы Fuchsia, которая, по слухам, должна прийти на смену Android. Исследователи нашли в ОС несколько стандартных багов, которые тем не менее не дают каких-либо полномочий в системе в силу самой архитектуры ОС. И именно описание архитектуры — наиболее интересная часть статьи.
Fuchsia — это микроядерная операционная система на базе ядра Zircon, написанного на языке C++. Все остальные компоненты ОС, обычно реализованные внутри ядра, вынесены в сервисы пространства пользователя и общаются между собой с помощью независимого от языка механизма IPC. Часть этих компонентов, как и само ядро, реализована на языке C++ (драйверы USB, например), другая часть — на других языках. Например, TCP/IP-стек написан на языке Rust, также поддерживается Go.
В отличие от других микроядерных ОС, драйверы Fuchsia могут быть объединены в один процесс, именуемый devhost. На рисунке ниже драйверы AHCI, SATA, MinFS и BlobFS объединены в Devhost Proccess 3.
Такая архитектура позволяет сократить количество переключений контекста и сделать ОС более эффективной. С другой стороны, надежность компонентов снижается, но не катастрофически — devhost-процессы обычно объединяют в себе драйверы одного стека (в случае с Devhost Process 3 это драйверы для работы с внутренним накопителем ПК), поэтому уязвимость в одном процессе Devhost приведет к уязвимости в драйверах одного стека и не заденет другие (например, драйверы USB). Fuchsia активно использует модуль IOMMU для защиты памяти устройств. Каждый процесс Devhost имеет право обращаться только к своим адресам ввода-вывода.
Как и Unix, Fuchsia следует концепции «все есть файл», когда файлы могут представлять собой как данные на диске, так и устройства или каналы коммуникации. Однако, в отличие от Unix, Fuchsia не открывает каждому процессу доступ ко всей файловой иерархии, а создает для него собственное пространство имен (так же как это делает Plan 9, неудавшаяся преемница Unix). Так реализуется идея песочницы, когда каждое приложение имеет доступ только к ограниченному набору ресурсов.
На самом низком уровне (уровне микроядра Zircon) все файлы представлены хендлами (handle) — это нечто вроде токена для доступа к файловому объекту. Каждый хендл должен иметь свой вид (kind) и права, которые контролируют доступ к системным вызовам.
В репозитории Fuchsia все компоненты имеют unit-тесты и фуззеры. Код собирается с активированными технологиями защиты: ASLR, DEP, SafeStack, ShadowCallStack и AutoVarInit. Для кода на C++ дополнительно повышают надежность. Например, часто используется версия оператора [] с проверками границ массива.
May 2020: The state of Kotlin Scripting — статья об использовании Kotlin в качестве скриптового языка.
Существует два способа запустить код Kotlin как скрипт: kscript, успевший стать стандартным методом, и запуск с помощью самого компилятора Kotlin (который тоже может работать в качестве интерпретатора).
В первом случае скрипт для macOS/Linux будет выглядеть так:
#!/usr/bin/env kscript println("my args are ${args.joinToString(", ")}")
Во втором — так:
#!/usr/bin/env kotlin println("my args are ${args.joinToString(", ")}")
Первый способ предлагает наибольшую функциональность, в том числе:
возможность подключать зависимости Maven:
@file:DependsOn("com.squareup.okhttp3:okhttp:4.7.2")
возможность автоматической установки kscript при запуске скрипта.
Второй способ менее функционален, но в данный момент поддерживается командой Kotlin в первую очередь. Кроме того, такие скрипты можно редактировать в IDEA напрямую, без необходимости создавать проект. Примеры скриптов: kscript, Kotlin.
How to run an expensive calculation with Kotlin Coroutines on the Android Main Thread without freezing the UI — статья о том, как запустить вычисления в основном потоке приложения и при этом не создать проблем с отрисовкой интерфейса.
Все знают, что Android выполняет операции отрисовки графического интерфейса в основном потоке приложения. Поэтому, чтобы не получить фризы, необходимо выносить все тяжеловесные вычисления в фоновый поток. Это золотой стандарт разработки для Android. Но, как ни странно, его можно обойти.
Вот стандартный пример выноса вычислений в фоновый поток с помощью короутин:
private suspend fun calculateFactorialOnDefaultDispatcher(number: Int): BigInteger = withContext(Dispatchers.Default) { var factorial = BigInteger.ONE for (i in 1..number) { factorial = factorial.multiply(BigInteger.valueOf(i.toLong())) } factorial }
Если вынести код этого метода из блока withContext()
, он закономерно подвесит интерфейс на несколько секунд. Но! Если при этом добавить в код вызов функции yield()
, интерфейс никак не пострадает и останется плавным:
private suspend fun calculateFactorialInMainThreadUsingYield(number: Int): BigInteger { var factorial = BigInteger.ONE for (i in 1..number) { yield() factorial = factorial.multiply(BigInteger.valueOf(i.toLong())) } return factorial }
Как это возможно? Все дело в том, как Android вызывает код отрисовки интерфейса в основном потоке приложения. Каждые 16 миллисекунд (при частоте обновления экрана в 60 герц) фреймворк Android добавляет новый Runnable (по сути, блок кода) с кодом обновления интерфейса в очередь исполнения (MessageQueue) основного потока приложения. Если основной поток не занят в это время другой работой, он исполнит этот код. В противном случае продолжится исполнение текущего кода, а операция обновления будет пропущена. Так происходит пропуск кадра, а пропуск нескольких кадров подряд выглядит как фриз интерфейса.
Именно это должно было случиться при запуске предыдущего кода. Но не случилось благодаря вызову функции yield()
. Эта функция приостанавливает исполнение текущей короутины до получения следующего элемента (в данном случае числа). Приостановка короутины, в свою очередь, приводит к перемещению кода обработки следующего элемента в MessageQueue. В итоге весь код вычисления факториала разбивается на множество маленьких блоков, которые помещаются в очередь исполнения вперемешку с кодом обновления экрана. Поток успевает выполнить несколько шагов вычисления факториала, затем код обновления UI, затем еще несколько шагов факториала и так далее.
Это канонический пример того, что называют словом concurrency в противовес параллельному вычислению. Мы по максимуму загружаем основной поток работой, при этом позволяя ему быстро переключаться между задачами. Факториал при таком подходе вычисляется примерно в два раза медленнее, зато интерфейс остается плавным даже без использования фоновых потоков.
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score! Подробнее
1 год7690 р. |
1 месяц720 р. |
Я уже участник «Xakep.ru»
Читайте также
Последние новости