Раз 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        "\001"          /* 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

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




Заключение


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


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

Поделиться с друзьями
-->

Комментарии (12)


  1. Terranz
    12.07.2016 22:08
    +7

    at first i was
    >swift
    but then
    >Прослойка на C
    ну что это такое? с таким же успехом можно модуль ядра сделать на java


    1. ploop
      13.07.2016 08:20
      +5

      ну что это такое?
      Самая настоящая каша из топора.


      1. Terranz
        13.07.2016 14:01
        +2

        ну это как «операционная система на java»
        где есть микроядро на сях или ассемблере, который запускает jvm


        1. rzhikharevich
          13.07.2016 15:58
          +1

          Swift компилируется в нативный код, на этом языке можно при желании прямо обращаться к памяти. Ядро можно целиком написать на Swift (на самом деле не без ассемблера, но без него никуда, хотя бы из-за вещей вроде инструкции LGDT), поэтому сравнение с Java на мой взгляд не очень уместно.


          1. Terranz
            13.07.2016 17:51
            +2

            swift не может выполнять ассемблерные вставки?


            1. rzhikharevich
              13.07.2016 18:59

              Увы, их там нет. Любопытно посмотреть, были ли предложения в swift-evolution добавить их.


    1. Anakros
      13.07.2016 09:12
      +1

      Так прослойка нужна только для корректного исполнения кода на свифте, нет?


  1. asgaardson
    13.07.2016 10:40
    +1

    А на rust модуль ядра можно без прослойки на C?


    1. rzhikharevich
      13.07.2016 10:41
      +1

      Насколько я знаю, к сожалению, нет, так как снова вступает в силу завязанность на макросах.


    1. rzhikharevich
      13.07.2016 10:47
      +1

      Однако, теоретически можно писать модули на Swift и Rust без C, если реализовать некоторый неимпортируемый функционал и не забывать его обновлять при изменениях в ядре.


  1. lorc
    13.07.2016 16:26

    А почему бы не написать всё же сразу на Swift? Нужно будет всего лишь использовать правильный linker script который положит нужные данные в нужные секции. Зато никаких трамплинов на C.


  1. exfizik
    13.07.2016 21:11

    Даже боюсь подумать, какими выражениями это прокомментировал бы Линус.
    P.S. Для справки harmful.cat-v.org/software/c++/linus