Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
 Languages
 С
 GNU С Library 
 Qt 
 STL 
 Threads 
 C++ 
 Samples 
 stanford.edu 
 ANSI C
 Libs
 LD
 Socket
 Pusher
 Pipes
 Encryption
 Plugin
 Inter-Process
 Errors
 Deep C Secrets
 C + UNIX
 Linked Lists / Trees
 Asm
 Perl
 Python
 Shell
 Erlang
 Go
 Rust
 Алгоритмы
NEWS
Последние статьи :
  Тренажёр 16.01   
  Эльбрус 05.12   
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
 
TOP 20
 Secure Programming for Li...6507 
 Linux Kernel 2.6...5280 
 Trees...1119 
 Максвелл 3...1051 
 William Gropp...991 
 Go Web ...963 
 Ethreal 3...930 
 Ethreal 4...918 
 Gary V.Vaughan-> Libtool...914 
 Ext4 FS...905 
 Clickhouse...902 
 Rodriguez 6...900 
 Ethreal 1...897 
 Steve Pate 1...886 
 C++ Patterns 3...864 
 Assembler...854 
 Ulrich Drepper...844 
 DevFS...788 
 MySQL & PosgreSQL...774 
 Стивенс 9...758 
 
  01.01.2024 : 3621733 посещений 

iakovlev.org

Redox

Всю оригинальную информацию об операционной системе 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, которые совсем не маленькие:
 git clone https://github.com/redox-os/redox.git --origin upstream --recursive && \
    cd redox && git submodule update --recursive --init
 
Для сборки redox понадобятся следующие пакеты:
 nasm qemu libfuse-dev
После чего заходим в каталог исходников и запускаем команду
 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)
 }





Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье