[Из песочницы] Модуль ядра Linux на Swift
Раз 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
#include
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
#include
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!.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 "\001" /* ASCII Start Of Header */
...
#define KERN_INFO KERN_SOH "6" /* informational */
...
printk(unsafeBitCast(msg.utf8Start, to: UnsafePointer!.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
Финишная прямая
Заключение
Конечно, от языка тут, увы, только синтаксис. Чтобы задействовать все возможности, нужно предпринять немалые усилия по портированию рантайма Swift на ядро Linux. Также не очень ясно, имеет ли вообще смысл писать такой низкоуровневый код на Swift. Линус Торвальдс, наверное, не одобрил бы.
В любом случае happy hacking!
Комментарии (3)
12 июля 2016 в 22:08 (комментарий был изменён)
+4↑
↓
at first i was
>swift
but then
>Прослойка на C
ну что это такое? с таким же успехом можно модуль ядра сделать на java13 июля 2016 в 08:20 (комментарий был изменён)
+1↑
↓
ну что это такое?
Самая настоящая каша из топора.13 июля 2016 в 09:12
0↑
↓
Так прослойка нужна только для корректного исполнения кода на свифте, нет?