Всю оригинальную информацию об операционной системе Redox можно найти на официальном сайте
тут
Исходники операционной системы Redox можно найти на гитхабе либо тут
Redox написана на Rust, имеет микро-ядерную архитектуру, юниксо-подобна, бесопасна и свободна,
совместима с POSIX, вследствие чего многие утилиты, написанные для юникса, могут быть портированы сюда.
Redox работает на современном железе.
Идеи позаимствованы из Plan9, Minix, Linux, BSD.
На текущий момент над проектом работают около 40 человек.
Авторы проекта ставят перед собой амбициозную задачу - создать операционную систему, которая могла бы стать со временем
альтернативой для линукса. Безопасность и надежность самой системы является одним из основных требований при разработке.
Авторы надеются, что большинство стандартных линуксовых программ можно будет легко портировать.
Кодовая база redox достаточна компактна (а в исходниках линуксового ядра кстати уже десятки миллионов строк кода).
Лицензия redox - MIT X11, в то время как у линукса - GPL2.
Из BSD позаимствованы jails.
Юниксо-подобные операционные системы провозглашают лозунг - все есть файл.
Redox обобщает этот лозунг, он звучит так - все есть урл, где урл - это идентификатор схемы
Микроядро redox черпает идеи из концепции миникса. Код ядра redox включает в себя порядка 20000 строк,
это возможно за счет того, что многие сервисы перенесены в user space.
Кроме ядра, в Redox входят другие компоненты:
RedoxFS.
Ion: терминал
Orbital: display server
OrbTK: widget toolkit.
Magnet: пакетный менеджер
Sodium: текстовой редактор
libmalloc: memory allocator.
libextra: реализация libstd
games-for-redox
Дополнительно имеются три группы утилит:
Coreutils: базовый набор утилит
Extrautils: утилиты типа reminders, calendars, spellcheck
Binutils: утилиты для работы с бинарными файлами
Раст выбран в качестве основного инструмента, поскольку сам надежен и безопасен,
позволяет контролировать работу с памятью.
Дизайн построен на идее разделения ядра и пространства пользователя, где ядро управляет памятью
и другими критическими ресурсами.
Redox поддерживает стандартный юниксовый интерфейс системных вызовов syscall,
здесь есть такие вызовы, как: open, pipe, pipe2, lseek, read, write, brk, execv, и т.д, общим числом около 30.
Теперь перейдем непосредственно к тому, как собрать и запустить redox.
Они рекомендуют устанавливать стабильную версию раста.
Мне пришлось перебрать несколько версий раста для этого, и получилось это сделать только с помощью версии Nightly 1.16.
Чтобы запустить сам redox, в биосе пришлось включать поддержку интеловской виртуализации.
Для начала нужно забрать из репозитария исходники redox, которые совсем не маленькие:
После чего заходим в каталог исходников и запускаем команду
make all
После чего уже можно запустить собранный redox:
make qemu
Перед этим возможно понадобится выполнить команды типа
modbrobe kvm
В результате запустится эмулятор qemu, который в свою очередь запустит сам redox.
Он попросит ввести логин-пароль. Если ввести логин user, можно войти без пароля.
после чего появится возможность пользоваться графическим многооконным интерфейсом.
У меня на скриншоте видны одновременно запущенные три приложения - текстовой редактор, калькулятор и терминал:
Если в терминале набрать
ls /bin
можно увидеть список консольных приложений.
Загрузка redox происходит как обычно: bootloader загружает ядро по адресу 0x100000.
Код загрузчика лежит в bootloader/${ARCH}/bootsector.asm. Он также инициализирует таблицу памяти (c 0x500 по 0x5000)
и дисплейный режим VESA. Ядро физически лежит в файле kernel в каталоге /build. Оно берет эту таблицу
и грузит ее по нижним физическим адресам, инициализирует стартовую страницу, формирует стартовые обьекты, драйверы
и схемы. После этого запускается первый процесс - init, который находится в initfs/bin/init.
Он загружает драйвера, необходимые для доступа к корневой файловой системе (ramdisk), которая является частью ядра.
Фраза - все есть урл - в redox является обобщением фразы - все есть файл.
Урл - это идентификатор схемы или дескриптор ресурса.
В линуксе виртуальная файловая система используется уже давно .
Примером виртуальной файловой системы является содержимое каталога /proc.
Урл состоит из двух частей:
1. Схема - обьект, который управляется командой OPEN.
2. Ссылочная строка, если она начинается с file - то это ссылка на файл,
В общем случае урл - это строка
[scheme]:[reference]
Например
file:/path/to/myfile
С помощью урла мы открываем обьект, который можем прочитать или изменить.
Например, открытие tcp-шного урла:
use std::fs::OpenOptions;
use std::io::prelude::*;
fnmain() {
// Let's read from a TCP streamlet tcp = OpenOptions::new()
.read(true) // readable
.write(true) // writable
.open("tcp:0.0.0.0");
}
Схема может представлять как реальный файл, так и виртуальный.
Схема является универсальным инструментом для операций ввода-вывода.
В ядре имеется следующий набор схем:
debug: Provides access to serial console
event: Allows reading of `Event`s which are registered using fevent
env: Access and modify environmental variables
initfs: Readonly filesystem used for initializing the system
irq: Allows userspace handling of IRQs
null: Scheme that will discard all writes, and read no bytes
pipe: Used internally by the kernel to implement pipe
sys: System information, such as the context list and scheme list
zero: Scheme that will discard all writes, and always fill read buffers with zero
Схемы уровня пользователя:
disk: ahcid Raw access to disks
display: vesad Screen multiplexing of the display, provides text and graphical screens, used by orbital:
ethernet: ethernetd Raw ethernet frame send/receive, used by ip:
file: redoxfs Root filesystem
ip: ipd Raw IP packet send/receive
network: e1000d
rtl8168d: Link level network send/receive, used by ethernet:
orbital: orbital Windowing system
pty: ptyd Psuedoterminals, used by terminal emulators
rand: randd Psuedo-random number generator
tcp: tcpd TCP sockets
udp: udpd UDP sockets
Ресурс - это открытая схема. Его можно определить как тип данных, у которого имеются следующие методы:
read
write
seek
close
Теперь на конкретном примере рассмотрим реализацию схемы.
Пусть это будет вектор, куда можно будет добавлять элементы и удалять их.
Назовем схему vec: . Импортируем необходимое:
externcrate syscall; // add "redox_syscall: "*" to your cargo dependenciesuse syscall::scheme::SchemeMut;
use syscall::error::{Error, Result, ENOENT, EBADF, EINVAL};
use std::cmp::min;
Определим структуру, которая будет имплементацией трэйта SchemeMut:
structVecScheme {
vec: Vec<u8>,
}
impl VecScheme {
fnnew() -> VecScheme {
VecScheme {
vec: Vec::new(),
}
}
}
impl SchemeMut for VecScheme {
fnopen(&mutself, path: &[u8], _flags: usize, _uid: u32, _gid: u32) -> Result<usize> {
self.vec.extend_from_slice(path);
Ok(path.len())
}
fnread(&mutself, _id: usize, buf: &mut [u8]) -> Result<usize> {
let res = min(buf.len(), self.vec.len());
for b in buf {
*b = ifletSome(x) = self.vec.pop() {
x
} else {
break;
}
}
Result::Ok(res)
}
fnwrite(&mutself, _id: usize, buf: &[u8]) -> Result<usize> {
for &i in buf {
self.vec.push(i);
}
Result::Ok(buf.len())
}
}
fnmain() {
use syscall::data::Packet;
use std::fs::File;
use std::io::{Read, Write};
use std::mem::size_of;
letmut scheme = VecScheme::new();
// Create the handlerletmut socket = File::create(":vec").unwrap();
loop {
letmut packet = Packet::default();
while socket.read(&mut packet).unwrap() == size_of::<Packet>() {
scheme.handle(&mut packet);
socket.write(&packet).unwrap();
}
}
}
Для того, чтобы добавить эту схему в redox, нужно в каталоге /schemes добавить подкаталог vec,
положить туда исходник и cargo-файл.
В корневом Makefile в секции schemes добавить команду на компиляцию новой схемы,
после чего пересобрать сам Redox.
Redox имеет микро-ядерную архитектуру. Основная концепция таких ядер заключается в том,
что пользовательские процессы выполняются в простанстве пользователя, а не в ядре.
Главная задача ядра - выполнять организацию и коммуникацию процессов.
Драйверы выполняются отдельно от ядра и не могут причинить вред ядру.
В связи с чем можно достичь существенного уменьшения размера кода самого ядра,
что уменьшает количество багов и упрощает поддержку.
Схематично разницу можно посмотреть на следующей картинке:
Недостатком микро-ядерной архитектуры является бОльшее количество переключений (context switch) между процессами,
нежели в монолитном ядре.
В redox с этим борются с помощью bulk syscall.
Каждый пользовательский процесс, запущенный ядром, в качестве параметра имеет id-шник этого пользователя.
RedoxFS использует этот id для получения прав от файловой системы.
Аккаунт пользователя вместе с паролем хранится в /etc/passwd, для групп аналогично в /etc/group.
Ядро имеет свои собственные драйверы - PIT, RTC, ACPI.
Display driver, PS/2 driver, PCI driver, network stack, network drivers, disk drivers - все это вынесено из ядра в userspace.
Эти драйвера могут вызывать прерывания в ядре.
В качестве примера приведу функцию, которая вызывается в ядре при создании процесса и лежит в файле process.rs.
Это одна из самых больших функций в коде ядра redox:
pubfnclone(flags: usize, stack_base: usize) -> Result {
let ppid;
let pid;
{
let ruid;
let rgid;
let rns;
let euid;
let egid;
let ens;
letmut cpu_id = None;
let arch;
let vfork;
letmut kfx_option = None;
letmut kstack_option = None;
letmut offset = 0;
letmut image = vec![];
letmut heap_option = None;
letmut stack_option = None;
letmut tls_option = None;
let grants;
let name;
let cwd;
let env;
let files;
// Copy from old process
{
let contexts = context::contexts();
let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
let context = context_lock.read();
ppid = context.id;
ruid = context.ruid;
rgid = context.rgid;
rns = context.rns;
euid = context.euid;
egid = context.egid;
ens = context.ens;
if flags & CLONE_VM == CLONE_VM {
cpu_id = context.cpu_id;
}
arch = context.arch.clone();
ifletSome(ref fx) = context.kfx {
letmut new_fx = unsafe { Box::from_raw(::alloc::heap::allocate(512, 16)
as *mut [u8; 512]) };
for (new_b, b) in new_fx.iter_mut().zip(fx.iter()) {
*new_b = *b;
}
kfx_option = Some(new_fx);
}
ifletSome(ref stack) = context.kstack {
offset = stack_base - stack.as_ptr() asusize - mem::size_of::(); // Add clone retletmut new_stack = stack.clone();
unsafe {
let func_ptr = new_stack.as_mut_ptr().offset(offset asisize);
*(func_ptr as *mutusize) = arch::interrupt::syscall::clone_ret asusize;
}
kstack_option = Some(new_stack);
}
if flags & CLONE_VM == CLONE_VM {
for memory_shared in context.image.iter() {
image.push(memory_shared.clone());
}
ifletSome(ref heap_shared) = context.heap {
heap_option = Some(heap_shared.clone());
}
} else {
for memory_shared in context.image.iter() {
memory_shared.with(|memory| {
letmut new_memory = context::memory::Memory::new(
VirtualAddress::new(memory.start_address().get() + arch::USER_TMP_OFFSET),
memory.size(),
entry::PRESENT | entry::NO_EXECUTE | entry::WRITABLE,
true,
false
);
unsafe {
intrinsics::copy(memory.start_address().get() as *constu8,
new_memory.start_address().get() as *mutu8,
memory.size());
}
new_memory.remap(memory.flags(), true);
image.push(new_memory.to_shared());
});
}
ifletSome(ref heap_shared) = context.heap {
heap_shared.with(|heap| {
letmut new_heap = context::memory::Memory::new(
VirtualAddress::new(arch::USER_TMP_HEAP_OFFSET),
heap.size(),
entry::PRESENT | entry::NO_EXECUTE | entry::WRITABLE,
true,
false
);
unsafe {
intrinsics::copy(heap.start_address().get() as *constu8,
new_heap.start_address().get() as *mutu8,
heap.size());
}
new_heap.remap(heap.flags(), true);
heap_option = Some(new_heap.to_shared());
});
}
}
ifletSome(ref stack) = context.stack {
letmut new_stack = context::memory::Memory::new(
VirtualAddress::new(arch::USER_TMP_STACK_OFFSET),
stack.size(),
entry::PRESENT | entry::NO_EXECUTE | entry::WRITABLE,
true,
false
);
unsafe {
intrinsics::copy(stack.start_address().get() as *constu8,
new_stack.start_address().get() as *mutu8,
stack.size());
}
new_stack.remap(stack.flags(), true);
stack_option = Some(new_stack);
}
ifletSome(ref tls) = context.tls {
letmut new_tls = context::memory::Tls {
master: tls.master,
file_size: tls.file_size,
mem: context::memory::Memory::new(
VirtualAddress::new(arch::USER_TMP_TLS_OFFSET),
tls.mem.size(),
entry::PRESENT | entry::NO_EXECUTE | entry::WRITABLE,
true,
true
)
};
unsafe {
intrinsics::copy(tls.master.get() as *constu8,
new_tls.mem.start_address().get() as *mutu8,
tls.file_size);
}
new_tls.mem.remap(tls.mem.flags(), true);
tls_option = Some(new_tls);
}
if flags & CLONE_VM == CLONE_VM {
grants = context.grants.clone();
} else {
grants = Arc::new(Mutex::new(Vec::new()));
}
if flags & CLONE_VM == CLONE_VM {
name = context.name.clone();
} else {
name = Arc::new(Mutex::new(context.name.lock().clone()));
}
if flags & CLONE_FS == CLONE_FS {
cwd = context.cwd.clone();
} else {
cwd = Arc::new(Mutex::new(context.cwd.lock().clone()));
}
if flags & CLONE_VM == CLONE_VM {
env = context.env.clone();
} else {
letmut new_env = BTreeMap::new();
for item in context.env.lock().iter() {
new_env.insert(item.0.clone(), Arc::new(Mutex::new(item.1.lock().clone())));
}
env = Arc::new(Mutex::new(new_env));
}
if flags & CLONE_FILES == CLONE_FILES {
files = context.files.clone();
} else {
files = Arc::new(Mutex::new(context.files.lock().clone()));
}
}
// If not cloning files, dup to get a new number from scheme// This has to be done outside the context lock to prevent deadlocksif flags & CLONE_FILES == 0 {
for (_fd, mut file_option) in files.lock().iter_mut().enumerate() {
let new_file_option = ifletSome(file) = *file_option {
let result = {
let scheme = {
let schemes = scheme::schemes();
let scheme = schemes.get(file.scheme).ok_or(Error::new(EBADF))?;
scheme.clone()
};
let result = scheme.dup(file.number, b"clone");
result
};
match result {
Ok(new_number) => {
Some(context::file::File {
scheme: file.scheme,
number: new_number,
event: None,
})
},
Err(_err) => {
None
}
}
} else {
None
};
*file_option = new_file_option;
}
}
// If vfork, block the current process// This has to be done after the operations that may require context switchesif flags & CLONE_VFORK == CLONE_VFORK {
let contexts = context::contexts();
let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
letmut context = context_lock.write();
context.block();
vfork = true;
} else {
vfork = false;
}
// Set up new process
{
letmut contexts = context::contexts_mut();
let context_lock = contexts.new_context()?;
letmut context = context_lock.write();
pid = context.id;
context.ppid = ppid;
context.ruid = ruid;
context.rgid = rgid;
context.rns = rns;
context.euid = euid;
context.egid = egid;
context.ens = ens;
context.cpu_id = cpu_id;
context.status = context::Status::Runnable;
context.vfork = vfork;
context.arch = arch;
letmut active_table = unsafe { ActivePageTable::new() };
letmut temporary_page =
TemporaryPage::new(Page::containing_address(VirtualAddress::new(0x8_0000_0000)));
letmut new_table = {
let frame = allocate_frame().expect("no more frames in syscall::clone new_table");
InactivePageTable::new(frame, &mut active_table, &mut temporary_page)
};
context.arch.set_page_table(unsafe { new_table.address() });
// Copy kernel mapping
{
let frame = active_table.p4()[510].pointed_frame().expect("kernel table not mapped");
let flags = active_table.p4()[510].flags();
active_table.with(&mut new_table, &mut temporary_page, |mapper| {
mapper.p4_mut()[510].set(frame, flags);
});
}
ifletSome(fx) = kfx_option.take() {
context.arch.set_fx(fx.as_ptr() asusize);
context.kfx = Some(fx);
}
// Set kernel stackifletSome(stack) = kstack_option.take() {
context.arch.set_stack(stack.as_ptr() asusize + offset);
context.kstack = Some(stack);
}
// Setup heapif flags & CLONE_VM == CLONE_VM {
// Copy user image mapping, if foundif ! image.is_empty() {
let frame = active_table.p4()[0].pointed_frame().expect("user image not mapped");
let flags = active_table.p4()[0].flags();
active_table.with(&mut new_table, &mut temporary_page, |mapper| {
mapper.p4_mut()[0].set(frame, flags);
});
}
context.image = image;
// Copy user heap mapping, if foundifletSome(heap_shared) = heap_option {
let frame = active_table.p4()[1].pointed_frame().expect("user heap not mapped");
let flags = active_table.p4()[1].flags();
active_table.with(&mut new_table, &mut temporary_page, |mapper| {
mapper.p4_mut()[1].set(frame, flags);
});
context.heap = Some(heap_shared);
}
// Copy grant mappingif ! grants.lock().is_empty() {
let frame = active_table.p4()[2].pointed_frame().expect("user grants not mapped");
let flags = active_table.p4()[2].flags();
active_table.with(&mut new_table, &mut temporary_page, |mapper| {
mapper.p4_mut()[2].set(frame, flags);
});
}
context.grants = grants;
} else {
// Copy percpu mappingfor cpu_id in0..::cpu_count() {
extern {
/// The starting byte of the thread data segmentstaticmut __tdata_start: u8;
/// The ending byte of the thread BSS segmentstaticmut __tbss_end: u8;
}
let size = unsafe { & __tbss_end as *const _ asusize - & __tdata_start
as *const _ asusize };
let start = arch::KERNEL_PERCPU_OFFSET + arch::KERNEL_PERCPU_SIZE * cpu_id;
let end = start + size;
let start_page = Page::containing_address(VirtualAddress::new(start));
let end_page = Page::containing_address(VirtualAddress::new(end - 1));
for page in Page::range_inclusive(start_page, end_page) {
let frame = active_table.translate_page(page).expect("kernel percpu not mapped");
active_table.with(&mut new_table, &mut temporary_page, |mapper| {
mapper.map_to(page, frame,
entry::PRESENT | entry::NO_EXECUTE | entry::WRITABLE);
});
}
}
// Move copy of imagefor memory_shared in image.iter_mut() {
memory_shared.with(|memory| {
let start = VirtualAddress::new(memory.start_address().get()
- arch::USER_TMP_OFFSET + arch::USER_OFFSET);
memory.move_to(start, &mut new_table, &mut temporary_page, true);
});
}
context.image = image;
// Move copy of heapifletSome(heap_shared) = heap_option {
heap_shared.with(|heap| {
heap.move_to(VirtualAddress::new(arch::USER_HEAP_OFFSET), &mut new_table,
&mut temporary_page, true);
});
context.heap = Some(heap_shared);
}
}
// Setup user stackifletSome(mut stack) = stack_option {
stack.move_to(VirtualAddress::new(arch::USER_STACK_OFFSET), &mut new_table,
&mut temporary_page, true);
context.stack = Some(stack);
}
// Setup user TLSifletSome(mut tls) = tls_option {
tls.mem.move_to(VirtualAddress::new(arch::USER_TLS_OFFSET), &mut new_table,
&mut temporary_page, true);
context.tls = Some(tls);
}
context.name = name;
context.cwd = cwd;
context.env = env;
context.files = files;
}
}
unsafe { context::switch(); }
Ok(pid)
}