LINUX.ORG.RU

Продемонстрирована возможность разработки частей Linux на Rust

 , ,


4

9

Французский программист написал статью, в которой рассмотрел возможность переписывания ядра Linux на Rust.

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

В качестве «Proof of Concept» была приведена реализация системного вызова, содержащая вставки на Assembler внутри unsafe-блоков. Код компилируется в объектный файл, не связанный с библиотеками и интегрируемый в ядро во время сборки. Работа производилась на основе исходного кода Linux 4.8.17.

>>> Статья



Проверено: Shaman007 ()
Последнее исправление: sudopacman (всего исправлений: 5)

Ответ на: комментарий от RazrFalcon

Сударь, вы невежа! Вы не понимаете, как работают конструкторы в C++ и что происходит при выбрасывании исключения из конструктора.

В C++ном примере у вас нет объекта типа D. Он вообще не был создан. Вы напечатали какой-то случайный мусор, поскольку указатель d у вас не был проинициализирован. Попробуйте вот так:

#include <iostream>

class Data {
public:
    Data() {
        v1 = 1;
        throw "error";
        v2 = 2;
    }

public:
    int v1;
    int v2;
};

int main()
{
    Data *d = nullptr;

    try {
        d = new Data();
    } catch(...) { }

    std::cout << d << std::endl;
}

eao197 ★★★★★
()
Последнее исправление: eao197 (всего исправлений: 1)

Мертворожденный язык

anonymous
()
Ответ на: комментарий от eao197

Да. С конструктором погорячился. Я подразумевал случай из первого примера, когда объект уже создан и мы вызываем метод бросающий исключение.

RazrFalcon ★★★★★
()
Ответ на: комментарий от RazrFalcon

Я подразумевал случай из первого примера, когда объект уже создан и мы вызываем метод бросающий исключение

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

DarkEld3r ★★★★★
()
Ответ на: комментарий от eao197

Например, в нормальном языке компилятор гарантирует, что вот в этом случае: ... адреса у a и у b будут разными.

Это точно критерий нормальности языка? Раст его не проходит, если что.

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

В расте можно в одном скоупе создать на стеке два разных объекта с одинаковыми адресами? Можно пример?

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Можно пример?

В расте структуры без данных занимают нулевой размер, а не один байт как в С++. Так что да, «разные» (разных типов) объекты на стеке вполне могут иметь одинаковый адрес.

Смотреть надо в релизе: https://is.gd/bgmfNM

DarkEld3r ★★★★★
()
Последнее исправление: DarkEld3r (всего исправлений: 1)
Ответ на: комментарий от eao197

Ну значит Rust упорот не только своим синтаксисом.

Почти не сомневался в таком выводе. Было бы интересно услышать более развёрнутое мнение почему это плохо и чем «костыли» с однобайтовыми пустыми классами (и костыли к костылями типа «empty base optimization») лучше. Понятное дело, что какие-то трюки с указателями оно поломать может, но так ли они нужны?

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

Нет. Так как в расте у нас запрещены неинициализрованные переменные. То есть будет логическая ошибка, да. Но не обращение к мусору.

Но мой пример с исключением всё же кривой

RazrFalcon ★★★★★
()
Ответ на: комментарий от RazrFalcon

Так как в расте у нас запрещены неинициализрованные переменные.

Ну я о случае когда объект уже создан, а перехваченная паника после вызова метода оставляет его в неконсистентном состоянии. Хотя при (большом) желании можно и аналог из С++ изобразить, если завести «сырую ссылку» на объект, но это уже будет извращением.

Просто в С++ исключения в конструкторе проблем не вызывают. Да, можно выстрелить в ногу, но так с большинством языковых возможностей. Как по мне, преимущество раста тут вовсе не в каких-то принципиальных различиях между паникой и исключениями.

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

А как предлагается решать проблему однозначной идентификации объектов, если разные объекты в программе могут получать одинаковые адреса?

eao197 ★★★★★
()
Ответ на: комментарий от eao197

(Наверное) предполагается, что гораздо чаще важнее «более высокоуровневое» равенство. То есть, два экземпляра структуры struct A { int a; int b; }; равны, если равны соответствующие поля. Если полей нет, то все экземпляры равны. Сравнение указателей, в моей практике, это скорее способ сэкономить и работает оно в специфических условиях.

Если есть наготове пример когда нужно сравнивать именно указатели, то послушаю с удовольствием.

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

DarkEld3r ★★★★★
()
Последнее исправление: DarkEld3r (всего исправлений: 1)
Ответ на: комментарий от DarkEld3r

По хорошему, нужно различать уникальность объектов (неравенство ссылок/указателей на объекты) и уникальность значений объектов. Очевидный пример: две строки (пусть это будут std::string в C++ или String в Java). Они могут быть равны по значению, но быть разными объектами.

Это фундаментально разные понятия, поэтому когда одно из них оказывается с особым частным случаем, то это странно, по меньшей мере.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Это фундаментально разные понятия, поэтому когда одно из них оказывается с особым частным случаем, то это странно, по меньшей мере.

Ага, странно. Странно потому что непривычно. Когда я об этой фиче узнал, то полез разбираться как (и почему) сделали в других языках. Честно говоря, уже и не помню, что нарыл, но остановился, что проблем это не вызывает. Повторюсь: не нулевые типы в других местах к дополнительным приседаниям приводят и тут ещё непонятно, что лучше.

Кстати, касательно «фундаментальности разных сравнений» и джавы: там тоже не без своих приколов. Некоторые (вроде, это даже настраивается) числовые значения кешируются и отсюда появляются такие забавные штуки:

Integer a1 = 127;
Integer b1 = 127;
System.out.println(a1 == b1); // true
Integer a2 = 128;
Integer b2 = 128;
System.out.println(a2 == b2); // false
Интуитивно? Совершенно нет. Вызывает проблемы? А хрен его знает, но не думаю, что джависты жалуются.

А в лиспе так вообще сравнений больше чем два вида. И тоже логично, если вникнуть/привыкнуть. Как по мне, в расте проблем быть не должно, ну если не пытаться переносить один в один какой-нибудь специфический код из С. Хотя, если ничего не путаю, в С по стандарту нулевых типов в принципе быть не может.

DarkEld3r ★★★★★
()
Последнее исправление: DarkEld3r (всего исправлений: 1)
Ответ на: комментарий от DarkEld3r

Ага, странно. Странно потому что непривычно.

Ну так когда рассматриваешь риски перехода с того, что привычно и логично на то, что странно, то каждая лишняя странность риски только увеличивает.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Рассматривать можно долго, но, пока не попробуешь, очень сложно оценить что вызовет проблемы, а что нет.

red75prim ★★★
()
Ответ на: комментарий от eao197

перехода с того, что привычно и логично на то, что странно, то каждая лишняя странность риски только увеличивает.

Можно поспорить. Как по мне, скорее наоборот: если нечто «странное» маскируется под «привычное», но при этом в каких-то мелких нюансах может преподнести неожиданности, то это «опаснее» в плане рисков.

Ну и в расте, в отличии от С или плюсов, где нубы постоянно порываются сравнить (сишные) строки через указатели, такое сравнение наружу, вроде, не торчит.

В любом случае, меня больше интересовало не упустили ли разработчики раста какую-нибудь деталь из-за которой такое решение будет невыгодным именно в техническом плане.

DarkEld3r ★★★★★
()
Последнее исправление: DarkEld3r (всего исправлений: 1)
Ответ на: комментарий от DarkEld3r

В любом случае, меня больше интересовало не упустили ли разработчики раста какую-нибудь деталь из-за которой такое решение будет невыгодным именно в техническом плане.

Ну вот в тех же плюсах я могу спокойно делать так:

class some_iface { // Аналог interface из Java.
public :
  virtual void foo() = 0;
  virtual void bar() = 0;
  ...
};
...
std::set<some_iface *> objects;
...
for(auto p : objects) p->foo();
Причем мне по барабану, будет ли реализация some_iface у пользователя иметь какие-то атрибуты или же наследник some_iface будет пустым (т.е. кроме методов у него ничего не будет).

Более того, при работе с шаблонами вообще не обязательно иметь some_iface с декларацией набора чистых виртуальных методов. Может быть что-то вроде:

template<typename T>
class demo {
  std::set<T *> objects_;
...
  void foo() {
    for(auto p : objects_) p->foo();
  }
};
И, опять же, не суть важно, T пустой или не пустой.

В Rust-е, как я понимаю, нужно будет иметь дело с типажами. Т.е. у меня может быть типаж SomeIface, но вот могу ли я безопасно сделать множество ссылок на SomeIface?

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Т.е. у меня может быть типаж SomeIface, но вот могу ли я безопасно сделать множество ссылок на SomeIface?

Конечно. Box<Trait> в расте - особенная сущность состоящая из двух указателей. Так что даже если тип, для которого реализован нужный нам трейт, сам по себе размера не имеет, но условный «указатель на таблицу виртуальных методов» где-то хранить ведь надо.

DarkEld3r ★★★★★
()
Ответ на: комментарий от eao197

Ссылку на трайт в Rust-е можно только через Box сохранить?

Нет, конечно. Просто обычные указатели на интерфейс и в плюсах редко в контейнеры складывают, чаще будет какой-нибудь set<unique_ptr<T>>. Так что по инерции про Box и ответил, но это ничего не меняет. То есть, не важно будет ли у нас Box<T>, &Т или даже *const/mut T.

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

Просто обычные указатели на интерфейс и в плюсах редко в контейнеры складывают, чаще будет какой-нибудь set<unique_ptr<T>>.

Давайте не будем говорить за всех. И уважать чужое время, для этого нужно читать, что вам пишут. В моем примере было множество голых указателей. Что временами имеет смысл, если a) это множество формируется на очень короткое время или b) если система владения допускает такие вещи.

То есть, не важно будет ли у нас Box<T>, &Т или даже *const/mut T.

Меня начинает мучить шиза. Если у экземпляра struct в Rust-е может быть нулевой размер, то когда для этого struct есть реализация конкретного трейта SomeIface, то размер этого struct вдруг перестает быть нулевым?

Если не перестает, то откуда гарантии, что у ссылок на SomeIface будут разные значения?

Или здесь, как и с защитой от data races, гарантии в Rust-е от слова «мамой клянусь»?

eao197 ★★★★★
()
Последнее исправление: eao197 (всего исправлений: 1)
Ответ на: комментарий от eao197

Давайте не будем говорить за всех.

Давай поменьше пафоса. Или ты правда думаешь, что это солидно смотрится? А если говорить о времени, то я мог бы отправить мануал читать, но мы же тут «культурно общаемся» и хотим узнать что-то новое в процессе?

b) если система владения допускает такие вещи.

Про голые указатели система владения мало что знает. Сложить ссылки можно, но да, они должны гарантированно не пережить значения.

Меня начинает мучить шиза.

Ну попробуем ещё раз. В расте можно сказать типы отдельно, реализация трейтов - отдельно. Трейт представляет из себя два указателя: на данные и на таблицу виртуальных функций. Соответственно, тип «гарантированно» останется нулевого размера, а трейт будет ссылаться на этот тип и на соответствующие реализации методов.

DarkEld3r ★★★★★
()
Ответ на: комментарий от eao197

Если не перестает, то откуда гарантии, что у ссылок на SomeIface будут разные значения?

Нет таких гарантий. Указатель на данные в trait object, может быть одинаковым. Поэтому, если зачем-то требуется различать идентичные объекты без внутреннего состояния по адресам, лучше использовать

pub struct NoState(u8)

red75prim ★★★
()
Последнее исправление: red75prim (всего исправлений: 1)
Ответ на: комментарий от DarkEld3r

Давай поменьше пафоса.

Напомню, с чего началось:

Если есть наготове пример когда нужно сравнивать именно указатели, то послушаю с удовольствием.

Вы захотели послушать. Я вам привел пример того, что делается в С++ регулярно.

От вас вменяемого пояснения по поводу ссылок в Rust-е не увидел.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Ну так когда рассматриваешь риски перехода с того, что привычно и логично на то, что странно, то каждая лишняя странность риски только увеличивает.

Я не припомню случая из моей практики когда бы мне пригодилось сравнить ссылки между собой. На ум приходят всякие контейнеры откуда например ты хочешь удалить какой-то обьект, но в таком случае у тебя обычно и так есть его ключ (если словарь) или у обьекта есть оператор сравнения (если всякие списки). Конкретно ссылки не обязательны.

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

А вот пример из жизни, буквально на прошлой неделе написал такой тест:

    let out_svd = write_device(&svd_device);
    let parsed = svd::parse(&out_svd);
    assert_eq!(svd_device, parsed);

Тут я одной строчкой кода проверил что глубокое дерево обьектов состоящее из десятка структур и векторов структур, не изменилось после сериализации и десериализации.

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

pftBest ★★★★
()
Ответ на: комментарий от pftBest

Я не припомню случая из моей практики когда бы мне пригодилось сравнить ссылки между собой.

И из этого вполне логично сделать вывод о том, что такое никогда не нужно. Это, я смотрю, распространенная практика на профильных ресурсах (LOR здесь не исключение).

Простейший пример: С++ная функция std::lock. Она берет набор lockable-объектов (вроде std::mutex) и производит их поочередный захват. Только она работает так, чтобы при вызовах std::lock(e2, e3, e1) и std::lock(e1, e3, e2) на параллельных нитях не возникал дэдлок. Простейший способ обеспечить это — это отсортировать ссылки на e1, e2, e3 и заватывать их в одном и том же порядке на каждой из нитей.

В C++ без разницы, чем будет e1 — ссылкой на объект или на интерфейс. В Rust-е чтобы вызвать метод нужно давать ссылку на Trait. Отсюда и вопрос про то, будут ли у ссылок на Trait-ы разные значения для разных объектов. Да еще с учетом возможного инлайнинга.

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

Интересно, с каких таких языков вы в Rust пришли.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

И из этого вполне логично сделать вывод о том
Простейший пример: С++ная функция std::lock.

Пример отличный, как часто вам приходится писать такую функцию? А как часто нужно сравнить две строки? А если положить две строки в структуру?

Язык должен быть удобен для повседневного использования а не для написания частных случаев. Если пишешь что-то нестандартное то можно и потратить время, разобраться какие ссылки будут равны а какие нет.

Интересно, с каких таких языков вы в Rust пришли.

Я в основном в embedded проектах, так что это чистый С, или в последнее время Java (android).

Может вы знаете много хорших языков где не надо писать оператор сравнения вручную для каждого класса?

pftBest ★★★★
()
Ответ на: комментарий от pftBest

Пример отличный, как часто вам приходится писать такую функцию?

Не часто. Но сам факт того, что не может быть объекта нулевого размера, а значит у двух разных объектов не может быть одного адреса, вполне себе логичен.

А как часто нужно сравнить две строки? А если положить две строки в структуру?

В том же C++ это возможно уже более 30 лет.

Язык должен быть удобен для повседневного использования а не для написания частных случаев.

Думаю, что это в вас говорит опыт работы с языками, которые сложно назвать хорошими — чистый C и Java. Язык должен позволять решать простые задачи просто и делать решение сложных задач возможным. Как раз частные случаи относятся к решению сложных задач.

Я так понимаю, что в Rust-е структура нулевого размера никого не смущает, поскольку в Rust-е структуры нулевого размера никому не нужны и не существуют как класс. Как только на структуру навешивается трейт, и делается приведение к этому трейту, так разу же появляется неявная пара из ссылки на экземпляр и таблицу методов. И эта пара уже оказывается уникальной, как любой нормальный объект в других языках.

Может вы знаете много хорших языков где не надо писать оператор сравнения вручную для каждого класса?

Я так понимаю, речь идет об операторах строгого равенства и неравенства. Сходу не могу вспомнить, в каком из языков, с которыми приходилось иметь дело, генерация таких операторов была бы автоматической.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

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

Блин, проклятый склероз, вроде как в D компилятор генерирует opEquals автоматически.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Но сам факт того, что не может быть объекта нулевого размера, а значит у двух разных объектов не может быть одного адреса, вполне себе логичен.

И почему же? По моему, как раз логично, что размер структуры зависит от её содержимого.

DarkEld3r ★★★★★
()
Ответ на: комментарий от red75prim

Мне не доводилось писать что-то больше «hello world» на таких языках.

eao197 ★★★★★
()
Ответ на: комментарий от pftBest

Касательно Rust и Kotlin: у них еще не было достаточного времени для захвата доли на рынке. Той же Java потребовалось 3 или 4 года для того, чтобы захватить себе плацдарм на server-side. А уж до широкого использования в десктопном софте Java вообще за всю свою жизнь не дошла.

С D сложнее, но сами разработчики D убили перспективы D1 десять лет назад. А D2 вроде как всего 2 или 3 года как стабилизировался. Так что опять таки, время еще мало прошло.

eao197 ★★★★★
()
Ответ на: комментарий от DarkEld3r

По моему, как раз логично, что размер структуры зависит от её содержимого.

Моя логика заканчивается там, где начинаются объекты нулевого размера. Т.к. в этом случае мы вплотную подходим к поиску ответов на вопрос «сколько ангелов умещается на кончике иглы» — ведь это же ничем не отличается от поиска ответа на вопрос «сколько разных объектов могут иметь один и тот же адрес в программе».

Если же говорить более серьезно, то возникает вопрос по поводу управления памятью: как должен вести себя malloc(0)? Как затем должен вести себя free? Ну и в чем здесь логика?

eao197 ★★★★★
()
Ответ на: комментарий от eao197

В расте нет malloc и free, так что и филосовской дилеммы тоже нет. А все Box<T> равны единице, и уничтожение их noop

https://godbolt.org/g/iv44bQ

pftBest ★★★★
()
Ответ на: комментарий от eao197

как должен вести себя malloc(0)? Как затем должен вести себя free?

В safe Rust нет malloc и free. Box::new(()), да и все другие smart pointers, выделяющие память на куче, возвращают адрес alloc::heap::EMPTY для объектов нулевого размера. Не помню, стандартизировано это или нет.

red75prim ★★★
()
Ответ на: комментарий от red75prim

Не важно, есть ли именно malloc и free. Если программисту потребуется сделать свой менеджер памяти под конкретную задачу, ему нужно будет ожидать пришествие 0 в качестве корректного аргумента.

Может это кому-то и кажется логичным. Но к такому точно нужно привыкнуть.

eao197 ★★★★★
()
Ответ на: комментарий от RazrFalcon

Для Rc это работает, потому что там аллоцируется структура, состоящая из счетчиков сильных и слабых ссылок и типа T. Даже если T имеет нулевой размер, для разных Rc ссылки на него будут разными, поскольку он - часть структуры.

red75prim ★★★
()
Ответ на: комментарий от red75prim

Вот и я о том, еще один момент в Rust, про который остается только сказать: это нельзя понять, остается только смириться и принять :)

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Вы перебарщиваете с фанбойством. Хватит.

это нельзя понять, остается только смириться и принять

У C++ 1400 страниц стандарта этим пронизано.

RazrFalcon ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.