Содержание статьи
Почитать
Fuchsia: архитектура и безопасность
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++ дополнительно повышают надежность. Например, часто используется версия оператора [] с проверками границ массива.
Разработчику
Kotlin в качестве скриптового языка
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 в противовес параллельному вычислению. Мы по максимуму загружаем основной поток работой, при этом позволяя ему быстро переключаться между задачами. Факториал при таком подходе вычисляется примерно в два раза медленнее, зато интерфейс остается плавным даже без использования фоновых потоков.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»