feat(init,display): loader streams ELF segments (any size, kills half-built proc); window pixels RGBA8 premul (tiny-skia native), canvas mapped plain memory + pixels_mut/touch

This commit is contained in:
2026-10-28 02:57:05 +01:00
parent 864f039bc7
commit 07a0575908
4 changed files with 156 additions and 89 deletions

View File

@@ -13,8 +13,8 @@ pub(crate) const STACK_PAGES: usize = 4;
/// The most there is room for under the image (page 0 stays unmapped).
const MAX_STACK_PAGES: usize = USER_CODE_BASE / PAGE_SIZE - 1;
const CREATE_CAP: CapSlot = CapSlot::const_new(0);
/// Where a program file is read to before it is loaded (32 pages: the
/// largest program init will load; the gatekeeper is past 16).
/// Where a program file is read through on its way into a process, a
/// chunk at a time, so a program may be any size.
const SCRATCH_VA: VirtualAddress = VirtualAddress::from_raw(0x0120_0000);
const SCRATCH_PAGES: u16 = 32;
@@ -23,14 +23,54 @@ pub(crate) struct LoadedProcess {
pub(crate) entry_point: EntryPoint,
}
/// Reads all of `filename` into `scratch`; its size, or `None` if it is
/// missing, unreadable or larger than `scratch`.
fn read_file(storage: &mut Storage, scratch: &mut [u8], filename: &[u8]) -> Option<usize> {
let handle = storage.open(filename).ok()?;
let size = storage.file_size(handle).ok().filter(|s| *s <= scratch.len());
let read = size.and_then(|s| storage.read(handle, &mut scratch[..s]).ok());
let _ = storage.close(handle);
read.filter(|r| Some(*r) == size)
/// A program file read forwards through a buffer (storage has no seek): a
/// segment's bytes are asked for in increasing file order.
struct Stream<'a> {
storage: &'a mut Storage,
handle: libkos::fs::FileHandle,
buf: &'a mut [u8],
/// The file offset of `buf[0]`, and how much of `buf` holds file.
at: usize,
len: usize,
size: usize,
}
impl Stream<'_> {
/// Reads the next chunk after what `buf` holds; false at the end or
/// on an error.
fn advance(&mut self) -> bool {
let next = self.at + self.len;
let want = self.buf.len().min(self.size - next.min(self.size));
match self.storage.read(self.handle, &mut self.buf[..want]) {
Ok(n) if n > 0 => {
(self.at, self.len) = (next, n);
true
}
_ => false,
}
}
/// Hands the file's bytes `from..from + len` to `sink`, piece by piece,
/// with each piece's place in that range.
fn copy(&mut self, from: usize, len: usize, mut sink: impl FnMut(usize, &[u8])) -> bool {
if from < self.at || from.checked_add(len).is_none_or(|end| end > self.size) {
return false;
}
let mut done = 0;
while done < len {
let want = from + done;
while want >= self.at + self.len {
if !self.advance() {
return false;
}
}
let start = want - self.at;
let n = (self.len - start).min(len - done);
sink(done, &self.buf[start..start + n]);
done += n;
}
true
}
}
pub(crate) fn load_binary(
@@ -40,53 +80,57 @@ pub(crate) fn load_binary(
limits: Limits,
stack_pages: usize,
) -> Option<LoadedProcess> {
let handle = storage.open(filename).ok()?;
let loaded = storage
.file_size(handle)
.ok()
.and_then(|size| load_from(&mut Stream { storage, handle, buf: scratch, at: 0, len: 0, size }, limits, stack_pages));
let _ = storage.close(handle);
loaded
}
fn load_from(file: &mut Stream, limits: Limits, stack_pages: usize) -> Option<LoadedProcess> {
let stack_pages = stack_pages.min(MAX_STACK_PAGES);
let file_size = read_file(storage, scratch, filename)?;
let elf_data = &scratch[..file_size];
let elf_info = match elf::parse(elf_data) {
Ok(info) => info,
Err(_) => return None,
};
// The headers are in the first chunk.
if !file.advance() {
return None;
}
let elf_info = elf::parse(&file.buf[..file.len]).ok()?;
let process_cap = syscall::create_process(CREATE_CAP, limits)?;
for segment in elf_info.segments() {
let page_start = segment.vaddr & !0xFFF;
let offset_in_page = segment.vaddr & 0xFFF;
let total_size = offset_in_page + segment.mem_size;
let seg_pages = total_size.div_ceil(PAGE_SIZE);
let page_count = PageCount::new(u16::try_from(seg_pages).ok()?)?;
let local = libkos::window::request(page_count)?;
local.fill_u32(ByteOffset::ZERO, seg_pages * PAGE_SIZE / 4, 0);
let copy_len = segment.file_size.min(segment.mem_size);
let src = segment
.file_offset
.checked_add(copy_len)
.and_then(|end| elf_data.get(segment.file_offset..end));
if let Some(src) = src.filter(|_| copy_len > 0) {
local.copy_from(ByteOffset::new(offset_in_page), src);
}
for p in 0..seg_pages {
let src_page = VirtualAddress::from_raw(local.address().raw() + p * PAGE_SIZE);
let dst_page = VirtualAddress::from_raw(page_start + p * PAGE_SIZE);
syscall::map_page(process_cap, src_page, dst_page, segment.flags.execute);
}
let filled = elf_info.segments().all(|segment| load_segment(file, process_cap, segment).is_some())
&& (0..stack_pages).all(|i| {
let dst = VirtualAddress::from_raw(USER_CODE_BASE - (stack_pages - i) * PAGE_SIZE);
PageCount::new(1).and_then(syscall::request_pages).is_some_and(|va| {
syscall::map_page(process_cap, va, dst, false);
true
})
});
if !filled {
let _ = syscall::kill(process_cap);
syscall::drop_cap(process_cap);
return None;
}
Some(LoadedProcess { process_cap, entry_point: EntryPoint::from_raw(elf_info.entry_point) })
}
for i in 0..stack_pages {
let page_count = PageCount::new(1)?;
let stack_va = syscall::request_pages(page_count)?;
let dst = VirtualAddress::from_raw(USER_CODE_BASE - (stack_pages - i) * PAGE_SIZE);
syscall::map_page(process_cap, stack_va, dst, false);
/// Fresh pages with the segment's bytes, zero past them, moved into the
/// process at its address.
fn load_segment(file: &mut Stream, process_cap: CapSlot, segment: &elf::Segment) -> Option<()> {
let page_start = segment.vaddr & !0xFFF;
let offset_in_page = segment.vaddr & 0xFFF;
let seg_pages = (offset_in_page + segment.mem_size).div_ceil(PAGE_SIZE);
let local = libkos::window::request(PageCount::new(u16::try_from(seg_pages).ok()?)?)?;
local.fill_u32(ByteOffset::ZERO, seg_pages * PAGE_SIZE / 4, 0);
let copy_len = segment.file_size.min(segment.mem_size);
if copy_len > 0 && !file.copy(segment.file_offset, copy_len, |at, bytes| local.copy_from(ByteOffset::new(offset_in_page + at), bytes)) {
return None;
}
Some(LoadedProcess {
process_cap,
entry_point: EntryPoint::from_raw(elf_info.entry_point),
})
for p in 0..seg_pages {
let src_page = VirtualAddress::from_raw(local.address().raw() + p * PAGE_SIZE);
let dst_page = VirtualAddress::from_raw(page_start + p * PAGE_SIZE);
syscall::map_page(process_cap, src_page, dst_page, segment.flags.execute);
}
Some(())
}
/// A region of init's own to read program files into.