Rust в ядре OSX

Как-то в дискуссии на РСДН-е проскользнула мысль о том, что одной из ключевых сфер применения Rust может оказаться разработка не User-mode приложений, а драйверов. Вот и результат небольшого эксперимента:

17/12/13 23:43:23,000 kernel[0]: hello from rust


Несмотря на то, что на данный момент возникает куча вопросов связанных с отсутствием стандартных библиотек времени исполнения, все получается очень просто и изящно, может быть за исключением системы сборки, т.к. сборки приходится проводить в два этапа, но об этом чуть ниже.

#[allow(ctypes)];
#[no_std];                     (1)
#[no_core];                    (2)

extern "rust-intrinsic" {
    pub fn transmute<T,U>(val: T) -> U;
}

extern {
    #[fast_ffi]
    pub fn printf(fmt: *u8);   (3)
}

#[fixed_stack_segment]
unsafe fn print(s: &str) {
    let (ptr, _): (*u8, uint) = transmute(s);
    printf(ptr);
}

#[no_mangle]                    (4)
pub unsafe fn rust_main() {
    print("hello from rust\n");
}

Во-первых, у Rust есть стандартный режим сборки запрещающий компилятору использование стандартных библиотек std 1 и core 2, что в данном случае и нужно. Во-вторых, необходимо экспортировать стандартные функции ядра, которые планируется использовать из Rust 3. В-третьих, как написать точку входа/выхода драйвера на Rust не совсем очевидно, да и надобности в этом никакой нет, проще всего реализовать точку входа на обычном Си и уже из нее вызвать основной код реализованный на Rust. С учетом того, что по умолчанию Rust декорирует имена функций, необходимо отключить этот функционал для точки входа в основной код Rust 4.
В качестве первого этапа сборки, необходимо скомпилировать все Rust файлы (в данном случае один), они будут использоваться в виде объектных файлов на этапе линковки.

Точка входа в драйвер приобретает следующий вид:

extern void rust_main(void);
char __morestack[1024];              (1)

kern_return_t RustyKext_start(kmod_info_t * ki, void *d)
{
    rust_main();                     (2)
    return KERN_SUCCESS;
}

Скорей всего в реальном проекте в процессе инициализации потребуется больше действий нежели перейти в основной код, написанный на Rust 2, но в качестве примера этого достаточно.

Сразу хочу обратить внимание на переменную __morestack 1. На самом деле, это крайне не правильный и очень опасный код. Что бы в этом убедиться достаточно глянуть на то, как выглядит функция rust_main в ассемблерных инструкциях:

           _rust_main:
cmp        rsp, qword [gs:0x330]         ; XREF=0xe40
jnbe       0xed5
           ; Basic Block Input Regs: rsp -  Killed Regs: r10 r11
mov        r10, 0x18
mov        r11, 0x0
call       ___morestack
ret

__morestack используется для увеличения размера стека в случаях его нехватки и использованное выше решение с использованием переменной не более чем хак с целью собрать код и загрузить его в ядро.
При наличии желания проект можно собрать самостоятельно и “поиграться” с ним; а если у меня дойдут до этого руки, сделаю из этой заметки полноценную статью, т.к. потенциальная возможность очень интересная, но открытых вопросов крайне много

7 Comments Rust в ядре OSX

  1. Saf

    Привет. Что думаешь насчёт написания модулей на Rust для Node.js?
    Насколько такое реально сейчас?

    Reply
    1. Alexander Stavonin

      А какой в этом смысл? Если у Node.js есть какой-либо интерфейс взаимодействия с Си, то это однозначно можно реализовать. Но, повторюсь, а зачем?

      Reply
      1. Saf

        Это как минимум лучше с точки зрения утечек памяти и прочих неприятностей от которых защищает Раст.
        Да и просто just for fun

        Reply
    2. mipselqq

      Привет из будущего! Вы тоже роетесь в истории этого языка?

      Reply
      1. Alexander Stavonin

        Да, для каждого файла с тестами. Не для индивидуального теста, само собой, так как один файл может содержать кучу тестов.

        Reply

Leave a Reply