Модуль ядра Linux на Swift

в 17:48, , рубрики: linux kernel, linux modules, swift, Разработка под Linux

Модуль ядра Linux на Swift - 1

Раз Swift компилируется в нативный код, то почему бы не попробовать на нём написать модуль ядра? Всех заинтересовавшихся просьба под кат!

Swift – кроссплатформенный язык программирования, начавший свою историю в июле 2010 года в качестве личного проекта Криса Латтнера, отца таких известных проектов, как LLVM и Clang. В 2013 году Swift стал важной задачей для группы разработчиков Apple Development Tools. Версия 1.0 вышла 9 сентября 2014 года. 3 декабря 2015 года компилятор и другие инструменты были опубликованы под лицензией Apache 2.0. Разработка ведётся на GitHub. Проект разрабатывается с целью создать быстрый, безопасный, лаконичный и современный язык.

Становление компилятора свободным ПО заставило меня им заинтересоваться. Одним из первых экспериментов стало создание хелловорлда для голого железа на Swift. Однако, я оказался не первым. Kevin Lange, сотрудник Yelp и автор любительской Unix-подобной ОС Toaru, несколькими днями раньше опубликовал нечто похожее.

Не так давно я обратил внимание на rust.ko, пример модуля ядра Linux на Rust, и задался вопросом "А можно ли сделать то же самое, но на Swift?" Как оказалось, можно!

Прослойка на C

К сожалению, код ядра Linux сильно завязан на макросах и GCC-специфичных расширениях, поэтому без C в этом деле не обойтись.

shim.c

#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Roman Zhikharevich");
MODULE_DESCRIPTION("a little bit swifty Linux kernel module");
MODULE_VERSION("0.1");

int swift_main(void);

static int __init swift_init(void) {
        return swift_main();
}

static void __exit swift_exit(void) {}

module_init(swift_init);
module_exit(swift_exit);

Рассмотрим этот код по порядку.

#include <linux/init.h>
#include <linux/module.h>

init.h содержит макросы __init и __exit, помещающие функции в секции .init.text и .exit.text соотвественно. module.h нужен для module_init и module_exit, указывающих на функции инициализации и выхода, а также для указания информации о модуле.

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Roman Zhikharevich");
MODULE_DESCRIPTION("a little bit swifty Linux kernel module");
MODULE_VERSION("0.1");

Тут указывается информация о модуле. Здесь всё говорит само за себя.

int swift_main(void);

static int __init swift_init(void) {
        return swift_main();
}

static void __exit swift_exit(void) {}

Указываем сигнатуру функции swift_main, написанной на Swift, после чего вызываем её. Функция swift_exit пуста, так как освобождать нечего.

module_init(swift_init);
module_exit(swift_exit);

Указываем функции инициализации и выхода.

Затычка Код на Swift

@_silgen_name("swift_main")
func swift_main() -> Int32 {
        let msg: StaticString = "u{1}6Linux + Swift = <тут сердечко>n" // Emoji в коде на Хабре отображаться не хочет

        printk(unsafeBitCast(msg.utf8Start, to: UnsafePointer<Int8>!.self))

        return 0
}

Снова разбираем по порядку.

@_silgen_name("swift_main")

Атрибут @_silgen_name позволяет вручную задать название символа. Если это не сделать, то имя будет "искалеченное" (англ. mangled) – __TF1n10swift_mainFT_Vs5Int32, то есть будет хранить информацию о типах – вызывать такое из C неудобно.

let msg: StaticString = "u{1}6Linux + Swift = <тут сердечко>n" // Emoji в коде на Хабре отображаться не хочет

Здесь объявляется сообщение, имеющее тип StaticString, не требующий поддержки со стороны среды исполнения. Стоит разъяснить фрагмент "u{1}6". Дело в том, что для передачи log level сообщения функции printk необходимо использовать макрос KERN_INFO, но в Swift он недоступен. К счастью, он имеет довольно простое содержание (include/linux/kern_levels.h):

...
#define KERN_SOH        "01"          /* ASCII Start Of Header */
...
#define KERN_INFO       KERN_SOH "6"    /* informational */
...

printk(unsafeBitCast(msg.utf8Start, to: UnsafePointer<Int8>!.self))

Выводим сообщение!

return 0

"Инициализация" модуля прошла успешно!

Взболтать, но не смешивать – собираем код

Код на Swift я собирал под macOS Sierra, используя страшные костыли, а сишный код – на Raspberry Pi под управлением Void Linux. Однако, последующее нетрудно адаптировать и под другие сетапы.

Для начала необходимо создать условия для сборки модуля.

pi $ mkdir -p ~/projects/swift.ko
pi $ cd ~/projects/swift.ko
pi $ nano shim.c
pi $ nano Makefile

Makefile

obj-m += swift.o
swift-objs := shim.o main.o

all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Теперь самое интересное – Swift.

mac $ nano import.h

import.h

int printk(const char *msg);

Важно отметить, что сигнатура не совсем верная, но работать с va_list в данных условиях мы не можем, поэтому приходится мухлевать.

mac $ xcrun -sdk iphoneos swiftc -import-objc-header import.h -emit-library -emit-bc -target armv7-apple-ios7 main.swift
mac $ xcrun -sdk iphoneos clang -target armv7-none-elf -mfloat-abi=soft -O2 -c main.bc
mac $ arm-none-eabi-objcopy --rename-section .text=.init.text main.o

Объясню, что тут происходит. Проблема в том, что компилятор Swift не умеет генерировать код для нестандартных целей. Зато это умеет Clang! Выход заключается в том, чтобы перевести компилятор Swift в режим генерирования биткода LLVM, а затем скормить его Clang. Последняя же команда перемещает swift_main в секцию .init.text. Осталось только отправить объектный файл на Raspberry Pi:

mac $ scp main.o pi@pi:~/projects/swift.ko/main.o_shipped

Собираем модуль!

pi $ make

Финишная прямая

Модуль ядра Linux на Swift - 2

Заключение

Конечно, от языка тут, увы, только синтаксис. Чтобы задействовать все возможности, нужно предпринять немалые усилия по портированию рантайма Swift на ядро Linux. Также не очень ясно, имеет ли вообще смысл писать такой низкоуровневый код на Swift. Линус Торвальдс, наверное, не одобрил бы.

В любом случае happy hacking!

Автор: rzhikharevich

Источник

Поделиться новостью

* - обязательные к заполнению поля