Skip to content

Commit b01a9ba

Browse files
authored
Merge pull request #14 from thomasarmel/allocator_and_other_improvements
Allocator and other improvements
2 parents 70e6527 + 82fd0f7 commit b01a9ba

File tree

7 files changed

+266
-42
lines changed

7 files changed

+266
-42
lines changed

src/dos.rs

+6
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@ pub mod kbc;
66
pub mod file;
77
pub mod error_code;
88
pub mod panic;
9+
pub mod math;
910
use core::arch::asm;
1011

12+
pub use alloc::string::String as String;
13+
pub use alloc::boxed::Box as Box;
14+
pub use alloc::vec::Vec as Vec;
15+
pub use alloc::vec as vec;
16+
1117
pub fn exit(rt: u8) -> ! {
1218
unsafe {
1319
asm!("mov ah, 0x4C",

src/dos/allocator.rs

+114-24
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
// Allocator for MS-DOS, using 21h 48h and 49h functions.
2-
// The allocator is a simple linear allocator. It is used to allocate memory for the DOS kernel.
1+
//! Memory heap allocation for DOS programs.
2+
//! Uses conventional memory for DOS programs, from _heap segment start to extended BIOS data area (EBDA)
3+
//! Uses linear algorithm for allocating memory, which is not optimal, but it's simple and works.
34
45
use core::alloc::{GlobalAlloc, Layout};
56
use core::arch::asm;
@@ -8,42 +9,123 @@ use core::mem::size_of;
89

910
struct AllocatorBlock {
1011
next: Option<*mut AllocatorBlock>,
11-
size: usize,
12+
prev: Option<*mut AllocatorBlock>,
13+
size: usize, // includes size of this block
1214
used: bool,
1315
}
1416

1517
pub struct DosAllocator {
1618
first_block_ptr: *mut AllocatorBlock,
1719
}
1820

19-
// Todo: check total memory amount
20-
#[allow(unused_assignments)]
21-
#[allow(unused_variables)]
21+
impl DosAllocator {
22+
const LAST_MEMORY_BYTE_ADDR: u32 = 0x9FBFF; // (0X9000 << 4) + 0XFBFF, last byte of memory before extended BIOS data area
23+
const ALLOCATOR_BLOCK_SIZE: usize = size_of::<AllocatorBlock>();
24+
const MIN_BLOCK_USEFUL_SIZE: usize = 16;
25+
26+
fn diff_between_blocks_ptr(first_block: *mut AllocatorBlock, second_block: *mut AllocatorBlock) -> usize {
27+
let first_block_addr = first_block as usize;
28+
let second_block_addr = second_block as usize;
29+
assert!(first_block_addr < second_block_addr);
30+
second_block_addr - first_block_addr
31+
}
32+
33+
fn free_space_before_next_block(block: *mut AllocatorBlock) -> usize {
34+
assert_ne!(block, core::ptr::null_mut());
35+
assert!((block as u32) < Self::LAST_MEMORY_BYTE_ADDR);
36+
let next_block = unsafe { (*block).next };
37+
if next_block.is_none() {
38+
return Self::LAST_MEMORY_BYTE_ADDR as usize - block as usize;
39+
}
40+
let next_block = next_block.unwrap();
41+
Self::diff_between_blocks_ptr(block, next_block) - Self::ALLOCATOR_BLOCK_SIZE
42+
}
43+
44+
/// Converts block address to pointer usable by the program
45+
fn block_addr_to_useful_ptr(block: *mut AllocatorBlock) -> *mut u8 {
46+
assert_ne!(block, core::ptr::null_mut());
47+
(block as usize + Self::ALLOCATOR_BLOCK_SIZE) as *mut u8
48+
}
49+
}
50+
2251
unsafe impl GlobalAlloc for DosAllocator {
2352
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
53+
// Look for next free block
2454
let mut current_block_ptr = self.first_block_ptr;
25-
while (*current_block_ptr).used || ((*current_block_ptr).size != 0 && (*current_block_ptr).size < layout.size() + size_of::<AllocatorBlock>()) {
26-
current_block_ptr = (*current_block_ptr).next.unwrap();
55+
while (*current_block_ptr).used || (*current_block_ptr).size < layout.size() + Self::ALLOCATOR_BLOCK_SIZE {
56+
current_block_ptr = match (*current_block_ptr).next {
57+
Some(ptr) => ptr,
58+
None => return core::ptr::null_mut(), // No free block found, return null ptr
59+
};
2760
}
28-
(*current_block_ptr).used = true;
29-
(*current_block_ptr).size = layout.size();
30-
let new_block_ptr = ((*current_block_ptr).size as u32 + current_block_ptr as u32 + size_of::<AllocatorBlock>() as u32) as *mut AllocatorBlock;
31-
(*new_block_ptr).next = None;
61+
62+
let free_space_before_next_block = Self::free_space_before_next_block(current_block_ptr);
63+
if free_space_before_next_block <= Self::MIN_BLOCK_USEFUL_SIZE + Self::ALLOCATOR_BLOCK_SIZE {
64+
// No space for new block, just use the whole space
65+
(*current_block_ptr).used = true;
66+
(*current_block_ptr).size = free_space_before_next_block;
67+
return Self::block_addr_to_useful_ptr(current_block_ptr);
68+
}
69+
70+
// Create a new unused block between current and next
71+
72+
let current_block_size = layout.size() + Self::ALLOCATOR_BLOCK_SIZE;
73+
74+
// Create a new block just after current block
75+
let new_block_ptr = (current_block_size + current_block_ptr as usize) as *mut AllocatorBlock;
76+
(*new_block_ptr).next = (*current_block_ptr).next;
77+
(*new_block_ptr).prev = Some(current_block_ptr);
78+
(*new_block_ptr).size = free_space_before_next_block - current_block_size;
3279
(*new_block_ptr).used = false;
33-
(*new_block_ptr).size = 0;
80+
3481
(*current_block_ptr).next = Some(new_block_ptr);
35-
let ret_ptr = current_block_ptr as u32 + size_of::<AllocatorBlock>() as u32;
36-
ret_ptr as *mut u8
82+
(*current_block_ptr).size = current_block_size;
83+
(*current_block_ptr).used = true;
84+
85+
Self::block_addr_to_useful_ptr(current_block_ptr)
3786
}
3887

3988
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
40-
let current_block_ptr = (ptr as u32 - size_of::<AllocatorBlock>() as u32) as *mut AllocatorBlock;
89+
// Freeing null pointer is a no-op
90+
if ptr == core::ptr::null_mut() {
91+
return;
92+
}
93+
94+
let current_block_ptr = (ptr as u32 - Self::ALLOCATOR_BLOCK_SIZE as u32) as *mut AllocatorBlock;
95+
// Mark block as free
4196
(*current_block_ptr).used = false;
97+
98+
// Merge with next block if it's free
99+
let next_block_ptr = (*current_block_ptr).next;
100+
if next_block_ptr.is_some() {
101+
let next_block_ptr = next_block_ptr.unwrap();
102+
if !(*next_block_ptr).used {
103+
if (*next_block_ptr).next.is_some() {
104+
(*(*next_block_ptr).next.unwrap()).prev = Some(current_block_ptr);
105+
}
106+
(*current_block_ptr).size += (*next_block_ptr).size;
107+
(*current_block_ptr).next = (*next_block_ptr).next;
108+
}
109+
}
110+
111+
// Merge with previous block if it's free
112+
let prev_block_ptr = (*current_block_ptr).prev;
113+
if prev_block_ptr.is_some() {
114+
let prev_block_ptr = prev_block_ptr.unwrap();
115+
if !(*prev_block_ptr).used {
116+
if (*current_block_ptr).next.is_some() {
117+
(*(*current_block_ptr).next.unwrap()).prev = Some(prev_block_ptr);
118+
}
119+
(*prev_block_ptr).size += (*current_block_ptr).size;
120+
(*prev_block_ptr).next = (*current_block_ptr).next;
121+
}
122+
}
42123
}
43124

44125
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
45-
let current_block_ptr = (ptr as u32 - size_of::<AllocatorBlock>() as u32) as *mut AllocatorBlock;
46-
if (*current_block_ptr).size >= new_size {
126+
assert_ne!(ptr, core::ptr::null_mut()); // Avoid undefined behavior
127+
let current_block_ptr = (ptr as u32 - Self::ALLOCATOR_BLOCK_SIZE as u32) as *mut AllocatorBlock;
128+
if (*current_block_ptr).size >= new_size + Self::ALLOCATOR_BLOCK_SIZE {
47129
return ptr;
48130
}
49131
let new_ptr = self.alloc(Layout::from_size_align(new_size, layout.align()).unwrap());
@@ -54,20 +136,28 @@ unsafe impl GlobalAlloc for DosAllocator {
54136
}
55137

56138
impl DosAllocator {
139+
#[allow(unused_assignments)]
57140
pub fn init(&mut self) {
58-
let mut _heap_segment_addr: u32 = 0;
141+
let mut heap_segment_number: u32 = 0;
59142
unsafe {
60-
asm!("mov ax, _heap", out("ax") _heap_segment_addr);
143+
asm!("mov ax, _heap", out("ax") heap_segment_number);
61144
}
62-
let mut _heap_ptr_as_block = ((_heap_segment_addr & 0xFFFF) << 4) as *mut AllocatorBlock;
145+
// Compute heap address from segment number
146+
let heap_addr = ((heap_segment_number & 0xFFFF) << 4) as u32;
147+
let heap_ptr_as_block = heap_addr as *mut AllocatorBlock;
148+
149+
// Create an empty block at the beginning of the heap, containing all free space
150+
assert!(heap_addr as u32 <= DosAllocator::LAST_MEMORY_BYTE_ADDR);
151+
let first_block_size = DosAllocator::LAST_MEMORY_BYTE_ADDR - heap_addr;
63152
unsafe {
64-
*_heap_ptr_as_block = AllocatorBlock {
153+
*heap_ptr_as_block = AllocatorBlock {
65154
next: None,
66-
size: 0,
155+
prev: None,
156+
size: first_block_size as usize,
67157
used: false,
68158
};
69159
}
70-
self.first_block_ptr = _heap_ptr_as_block
160+
self.first_block_ptr = heap_ptr_as_block
71161
}
72162

73163
const fn new() -> Self {

src/dos/file.rs

+93-17
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,42 @@
11
use core::arch::asm;
22
use core::cmp::min;
3+
use crate::dos::error_code::ErrorCode;
34

45
extern crate rlibc;
56

67
#[allow(dead_code)]
78
pub struct File {
89
handle: u16,
910
}
11+
#[allow(dead_code)]
12+
pub enum SeekFrom {
13+
Start(u32),
14+
End(u32),
15+
Current(u32),
16+
}
17+
18+
impl SeekFrom {
19+
fn to_dos_seek_code(&self) -> u8 {
20+
match self {
21+
SeekFrom::Start(_) => 0,
22+
SeekFrom::End(_) => 2,
23+
SeekFrom::Current(_) => 1,
24+
}
25+
}
26+
27+
fn to_seek_offset(&self) -> u32 {
28+
match self {
29+
SeekFrom::Start(offset) => *offset,
30+
SeekFrom::End(offset) => *offset,
31+
SeekFrom::Current(offset) => *offset,
32+
}
33+
}
34+
}
1035

11-
// TODO: return Err(error_code) instead of Err()
1236
#[allow(dead_code)]
1337
#[allow(unused_assignments)]
1438
impl File {
15-
pub fn open(filename: &str) -> Result<Self, ()> {
39+
pub fn open(filename: &str) -> Result<Self, ErrorCode> {
1640
let mut is_open_success: u16 = 1; // 0: success, 1: fail
1741
let mut error_code_or_handle: u16 = 0;
1842
// DOS PATH length limit is 66 bytes.
@@ -25,35 +49,34 @@ impl File {
2549
asm!("mov al, 0x40", "mov ah, 0x3d", "int 0x21", "setc dl", "movzx cx, dl", in("dx") filename_ptr as u16, lateout("cx") is_open_success, lateout("ax") error_code_or_handle);
2650
}
2751
if is_open_success == 1 {
28-
return Err(());
52+
return Err(ErrorCode::from_u8(error_code_or_handle as u8).unwrap_or(ErrorCode::UnknownError));
2953
}
3054
Ok(Self {
3155
handle: error_code_or_handle,
3256
})
3357
}
3458

35-
pub fn read(&self, buffer: &mut [u8]) -> Result<usize, ()> {
59+
pub fn read(&self, buffer: &mut [u8]) -> Result<usize, ErrorCode> {
3660
let mut total_bytes_read: usize = 0;
3761
for buffer_write_pos in 0..buffer.len() {
3862
let mut is_read_success: u16 = 1; // 0: success, 1: fail
3963
let mut error_code_or_bytes_read: u16 = 0;
4064
let mut tmp_stack_buffer: [u8; 1] = [0; 1]; // To be sure of the segment
4165
let tmp_buffer_ptr = tmp_stack_buffer.as_mut_ptr();
4266
unsafe {
43-
let mut registers: [u16; 4] = [0; 4]; // Save registers content on the stack
44-
asm!("nop", out("ax") registers[0], out("bx") registers[1], out("cx") registers[2], out("dx") registers[3]);
67+
asm!("push ax", "push bx", "push cx", "push dx");
4568
asm!("mov cx, 1", "mov ah, 0x3f", "int 0x21", "setc dl", "movzx cx, dl", in("bx") self.handle, in("dx") tmp_buffer_ptr, lateout("cx") is_read_success, lateout("ax") error_code_or_bytes_read);
46-
asm!("nop", in("ax") registers[0], in("bx") registers[1], in("cx") registers[2], in("dx") registers[3]);
69+
asm!("pop dx", "pop cx", "pop bx", "pop ax");
4770
}
4871
if is_read_success == 1 {
49-
return Err(());
72+
return Err(ErrorCode::from_u8(error_code_or_bytes_read as u8).unwrap_or(ErrorCode::UnknownError));
5073
}
5174
if error_code_or_bytes_read == 0 {
5275
// End of file
5376
break;
5477
}
55-
56-
total_bytes_read += error_code_or_bytes_read as usize; // = 1
78+
//total_bytes_read += error_code_or_bytes_read as usize; // = 1
79+
total_bytes_read += 1 as usize;
5780
buffer[buffer_write_pos] = tmp_stack_buffer[0];
5881
}
5982

@@ -64,18 +87,71 @@ impl File {
6487
Ok(total_bytes_read)
6588
}
6689

67-
pub fn close(self) -> Result<(), ()> {
90+
// TODO check
91+
pub fn write(&self, buffer: &[u8]) -> Result<usize, ErrorCode> {
92+
let mut total_bytes_written: usize = 0;
93+
for buffer_read_pos in 0..buffer.len() {
94+
let mut is_write_success: u16 = 1; // 0: success, 1: fail
95+
let mut error_code_or_bytes_written: u16 = 0;
96+
let mut tmp_stack_buffer: [u8; 1] = [0; 1]; // To be sure of the segment
97+
tmp_stack_buffer[0] = buffer[buffer_read_pos];
98+
let tmp_buffer_ptr = tmp_stack_buffer.as_ptr();
99+
unsafe {
100+
asm!("push ax", "push bx", "push cx", "push dx");
101+
asm!("mov cx, 1", "mov ah, 0x40", "int 0x21", "setc dl", "movzx cx, dl", in("bx") self.handle, in("dx") tmp_buffer_ptr, lateout("cx") is_write_success, lateout("ax") error_code_or_bytes_written);
102+
asm!("pop dx", "pop cx", "pop bx", "pop ax");
103+
}
104+
if is_write_success == 1 {
105+
return Err(ErrorCode::from_u8(error_code_or_bytes_written as u8).unwrap_or(ErrorCode::UnknownError));
106+
}
107+
//total_bytes_written += error_code_or_bytes_written as usize; // = 1
108+
total_bytes_written += 1 as usize;
109+
}
110+
Ok(total_bytes_written)
111+
}
112+
113+
pub fn close(self) -> Result<(), ErrorCode> {
114+
self.close_with_ref()
115+
}
116+
117+
fn close_with_ref(&self) -> Result<(), ErrorCode> {
68118
let mut is_close_success: u16 = 1; // 0: success, 1: fail
69-
let mut _error_code: u16 = 0; // 6 = unknown handle
119+
let mut error_code: u16 = 0; // 6 = unknown handle
70120
unsafe {
71-
let mut registers: [u16; 4] = [0; 4]; // Save registers content on the stack
72-
asm!("nop", out("ax") registers[0], out("bx") registers[1], out("cx") registers[2], out("dx") registers[3]);
73-
asm!("mov ah, 0x3e", "int 0x21", "setc dl", "movzx cx, dl", in("bx") self.handle, lateout("cx") is_close_success, lateout("ax") _error_code);
74-
asm!("nop", in("ax") registers[0], in("bx") registers[1], in("cx") registers[2], in("dx") registers[3]);
121+
asm!("push ax", "push bx", "push cx", "push dx");
122+
asm!("mov ah, 0x3e", "int 0x21", "setc dl", "movzx cx, dl", in("bx") self.handle, lateout("cx") is_close_success, lateout("ax") error_code);
123+
asm!("pop dx", "pop cx", "pop bx", "pop ax");
75124
}
76125
if is_close_success == 1 {
77-
return Err(());
126+
return Err(ErrorCode::from_u8(error_code as u8).unwrap_or(ErrorCode::UnknownError));
78127
}
79128
Ok(())
80129
}
130+
131+
/// Seek to an offset, in bytes, in a stream.
132+
/// Returns number of bytes from the start of the stream if success, or an error code otherwise.
133+
pub fn seek(&self, pos: SeekFrom) -> Result<u32, ErrorCode> {
134+
let mut is_seek_success: u16 = 1; // 0: success, 1: fail
135+
let mut error_code_or_new_pos_low_from_start: u16 = 0;
136+
let mut new_pos_high_from_start: u16 = 0;
137+
let requested_relative_new_pos: u32 = pos.to_seek_offset();
138+
let requested_relative_new_pos_low = (requested_relative_new_pos & 0xffff) as u16;
139+
let requested_relative_new_pos_high = ((requested_relative_new_pos >> 16) & 0xffff) as u16;
140+
let seek_from: u8 = pos.to_dos_seek_code();
141+
unsafe {
142+
asm!("push ax", "push bx", "push cx", "push dx");
143+
asm!("mov ah, 0x42", "int 0x21", "setc dl", "movzx cx, dl", in("bx") self.handle, in("cx") requested_relative_new_pos_high as u16, in("dx") requested_relative_new_pos_low, in("al") seek_from, lateout("cx") is_seek_success, lateout("ax") error_code_or_new_pos_low_from_start, lateout("dx") new_pos_high_from_start);
144+
asm!("pop dx", "pop cx", "pop bx", "pop ax");
145+
}
146+
if is_seek_success == 1 {
147+
return Err(ErrorCode::from_u8(error_code_or_new_pos_low_from_start as u8).unwrap_or(ErrorCode::UnknownError));
148+
}
149+
Ok((new_pos_high_from_start as u32) << 16 | (error_code_or_new_pos_low_from_start as u32))
150+
}
81151
}
152+
153+
impl Drop for File {
154+
fn drop(&mut self) {
155+
let _ = self.close_with_ref();
156+
}
157+
}

src/dos/math.rs

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const fn num_bits<T>() -> usize { core::mem::size_of::<T>() * 8 }
2+
3+
pub const fn log_2(x: usize) -> usize {
4+
num_bits::<usize>() as usize - x.leading_zeros() as usize - 1
5+
}

0 commit comments

Comments
 (0)