Всю оригинальную информацию об операционной системе 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::*;
fn main() {
// Let's read from a TCP stream
let 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: . Импортируем необходимое:
extern crate syscall; // add "redox_syscall: "*" to your cargo dependencies
use syscall::scheme::SchemeMut;
use syscall::error::{Error, Result, ENOENT, EBADF, EINVAL};
use std::cmp::min;
Определим структуру, которая будет имплементацией трэйта SchemeMut:
struct VecScheme {
vec: Vec<u8>,
}
impl VecScheme {
fn new() -> VecScheme {
VecScheme {
vec: Vec::new(),
}
}
}
impl SchemeMut for VecScheme {
fn open(&mut self, path: &[u8], _flags: usize, _uid: u32, _gid: u32) -> Result<usize> {
self.vec.extend_from_slice(path);
Ok(path.len())
}
fn read(&mut self, _id: usize, buf: &mut [u8]) -> Result<usize> {
let res = min(buf.len(), self.vec.len());
for b in buf {
*b = if let Some(x) = self.vec.pop() {
x
} else {
break;
}
}
Result::Ok(res)
}
fn write(&mut self, _id: usize, buf: &[u8]) -> Result<usize> {
for &i in buf {
self.vec.push(i);
}
Result::Ok(buf.len())
}
}
fn main() {
use syscall::data::Packet;
use std::fs::File;
use std::io::{Read, Write};
use std::mem::size_of;
let mut scheme = VecScheme::new();
// Create the handler
let mut socket = File::create(":vec").unwrap();
loop {
let mut 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:
pub fn clone(flags: usize, stack_base: usize) -> Result {
let ppid;
let pid;
{
let ruid;
let rgid;
let rns;
let euid;
let egid;
let ens;
let mut cpu_id = None;
let arch;
let vfork;
let mut kfx_option = None;
let mut kstack_option = None;
let mut offset = 0;
let mut image = vec![];
let mut heap_option = None;
let mut stack_option = None;
let mut 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();
if let Some(ref fx) = context.kfx {
let mut 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);
}
if let Some(ref stack) = context.kstack {
offset = stack_base - stack.as_ptr() as usize - mem::size_of::(); // Add clone ret
let mut new_stack = stack.clone();
unsafe {
let func_ptr = new_stack.as_mut_ptr().offset(offset as isize);
*(func_ptr as *mut usize) = arch::interrupt::syscall::clone_ret as usize;
}
kstack_option = Some(new_stack);
}
if flags & CLONE_VM == CLONE_VM {
for memory_shared in context.image.iter() {
image.push(memory_shared.clone());
}
if let Some(ref heap_shared) = context.heap {
heap_option = Some(heap_shared.clone());
}
} else {
for memory_shared in context.image.iter() {
memory_shared.with(|memory| {
let mut 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 *const u8,
new_memory.start_address().get() as *mut u8,
memory.size());
}
new_memory.remap(memory.flags(), true);
image.push(new_memory.to_shared());
});
}
if let Some(ref heap_shared) = context.heap {
heap_shared.with(|heap| {
let mut 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 *const u8,
new_heap.start_address().get() as *mut u8,
heap.size());
}
new_heap.remap(heap.flags(), true);
heap_option = Some(new_heap.to_shared());
});
}
}
if let Some(ref stack) = context.stack {
let mut 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 *const u8,
new_stack.start_address().get() as *mut u8,
stack.size());
}
new_stack.remap(stack.flags(), true);
stack_option = Some(new_stack);
}
if let Some(ref tls) = context.tls {
let mut 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 *const u8,
new_tls.mem.start_address().get() as *mut u8,
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 {
let mut 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 deadlocks
if flags & CLONE_FILES == 0 {
for (_fd, mut file_option) in files.lock().iter_mut().enumerate() {
let new_file_option = if let Some(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 switches
if flags & CLONE_VFORK == CLONE_VFORK {
let contexts = context::contexts();
let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
let mut context = context_lock.write();
context.block();
vfork = true;
} else {
vfork = false;
}
// Set up new process
{
let mut contexts = context::contexts_mut();
let context_lock = contexts.new_context()?;
let mut 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;
let mut active_table = unsafe { ActivePageTable::new() };
let mut temporary_page =
TemporaryPage::new(Page::containing_address(VirtualAddress::new(0x8_0000_0000)));
let mut 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);
});
}
if let Some(fx) = kfx_option.take() {
context.arch.set_fx(fx.as_ptr() as usize);
context.kfx = Some(fx);
}
// Set kernel stack
if let Some(stack) = kstack_option.take() {
context.arch.set_stack(stack.as_ptr() as usize + offset);
context.kstack = Some(stack);
}
// Setup heap
if flags & CLONE_VM == CLONE_VM {
// Copy user image mapping, if found
if ! 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 found
if let Some(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 mapping
if ! 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 mapping
for cpu_id in 0..::cpu_count() {
extern {
/// The starting byte of the thread data segment
static mut __tdata_start: u8;
/// The ending byte of the thread BSS segment
static mut __tbss_end: u8;
}
let size = unsafe { & __tbss_end as *const _ as usize - & __tdata_start
as *const _ as usize };
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 image
for 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 heap
if let Some(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 stack
if let Some(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 TLS
if let Some(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)
}