Skip to main content

rustubs/proc/
task.rs

1use crate::arch::x86_64::arch_regs::{Context64, TrapFrame};
2use crate::arch::x86_64::paging::{address_space_clone, get_cr3};
3use crate::arch::x86_64::{arch_regs, is_int_enabled};
4use crate::mm::vmm::{VMArea, VMMan, VMPerms, VMType};
5use crate::mm::KSTACK_ALLOCATOR;
6use crate::proc::exec::child_task_entry;
7use crate::proc::sched::GLOBAL_SCHEDULER;
8use crate::proc::sync::bellringer::{BellRinger, Sleeper};
9use crate::{defs::*, Scheduler};
10use alloc::collections::VecDeque;
11use alloc::string::String;
12use core::ops::Range;
13use core::ptr;
14use core::str::FromStr;
15/// currently only kernelSp and Context are important.
16/// the task struct will be placed on the starting addr (low addr) of the kernel stack.
17/// therefore we can retrive the task struct at anytime by masking the kernel stack
18/// NOTE: we assume all fields in [Task] are only modified by the task itself,
19/// i.e. no task should modify another task's state. (this may change though, in
20/// which case we will need some atomics)
21/// TODO: the mm is heap allocated object (vec of vmas). But the task struct
22/// doesn't have a lifetime. Must cleanup the memory used by the mm itself when
23/// exiting a task.
24#[repr(C)]
25pub struct Task {
26	pub magic: u64,
27	pub pid: u64,
28	/// note that this points to the stack bottom (low addr)
29	pub kernel_stack: u64,
30	pub mm: VMMan,
31	pub page_root_va: u64,
32	// the user stack pointer is pushed to the kernel stack when interrupt
33	// happens. We don't manage user stack in the task struct, (at least for
34	// now)
35	pub state: TaskState,
36	pub context: arch_regs::Context64,
37}
38
39/// not to confuse with a integer TID. A TaskID identifies a task and __locate__
40/// it. In this case the TaskID wraps around the task struct's address. The
41/// reason why the scheduler doesn't directly store `Box<Task>` (or alike) is that
42/// the smart pointer types automatically drops the owned values when their
43/// lifetime end. For now want to have manual control of when, where and how I
44/// drop the Task because there could be more plans than just freeing the memory
45#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
46pub struct TaskId(u64);
47
48impl TaskId {
49	pub fn new(addr: u64) -> Self { Self(addr) }
50
51	pub fn get_task_ref(&self) -> &Task {
52		return unsafe { &*(self.0 as *mut Task) };
53	}
54
55	pub fn get_task_ref_mut(&self) -> &mut Task {
56		return unsafe { &mut *(self.0 as *mut Task) };
57	}
58}
59
60/// currently don't differentiate between running and ready states because the
61/// scheduler push the next task to the back of the queue. i.e. the running task
62/// is also "ready" in the run_queue
63#[derive(Debug, PartialEq)]
64pub enum TaskState {
65	Run,
66	Wait,
67	Block, // Block should be equivilant to Wait
68	Dead,  // Dead also means "killed / zombie"
69	Eating,
70	Purr,
71	Meow,
72	Angry,
73}
74
75extern "C" {
76	/// IMPORTANT! the context_swap* functions DO NOT swap cr3. You must do it
77	/// explicitly.
78	pub fn context_swap(from_ctx: u64, to_ctx: u64);
79	pub fn context_swap_to(to_ctx: u64);
80}
81
82// NOTE Task struct is manually placed on the stack, new() or default() is not
83// provided.
84impl Task {
85	/// unsafe because you have to make sure the stack pointer is valid
86	/// i.e. allocated through KStackAllocator.
87	#[inline(always)]
88	unsafe fn settle_on_stack<'a>(stack_addr: u64, t: Task) -> &'a mut Task {
89		ptr::write_volatile(stack_addr as *mut Task, t);
90		return &mut *(stack_addr as *mut Task);
91	}
92
93	/// settle_on_stack and prepare_context must be called before switching to
94	/// the task. TODO: combine them into one single API
95	#[inline(always)]
96	fn prepare_context(&mut self, entry: u64) {
97		let mut sp = self.get_init_kernel_sp();
98		unsafe {
99			sp -= 8;
100			*(sp as *mut u64) = 0;
101			sp -= 8;
102			*(sp as *mut u64) = entry;
103		}
104		self.context.rsp = sp;
105	}
106
107	/// get kernel stack top (high addr) to initialize the new task Note that
108	/// there are often alignment requirements of stack pointer. sysv calling
109	/// convention mandates 16 bytes alignment on function calls.
110	#[inline(always)]
111	pub fn get_init_kernel_sp(&self) -> u64 {
112		let mut sp = self.kernel_stack + Mem::KERNEL_STACK_SIZE;
113		sp &= !0b111;
114		sp
115	}
116
117	/// return a reference of the current running task struct. Return none of
118	/// the magic number is currupted on the kernel stack, this is because
119	/// 1. the task struct is not currectly put on the stack
120	/// 2. trying to get the current of the initial task, who has no task struct
121	///    on the stack
122	/// 3. the stack is corrupted (due to e.g. stack overflow)
123	///
124	/// TODO add a canary also at the end of the task struct and check it.
125	pub fn current<'a>() -> Option<&'a mut Task> {
126		let addr = arch_regs::get_sp() & !Mem::KERNEL_STACK_MASK;
127		let t = unsafe { &mut *(addr as *mut Task) };
128		if t.magic != Mem::KERNEL_STACK_TASK_MAGIC {
129			return None;
130		}
131		return Some(t);
132	}
133
134	#[inline]
135	pub fn taskid(&self) -> TaskId { TaskId::new(self as *const _ as u64) }
136
137	/// a task may be present in multiple wait rooms; this is logically not
138	/// possible at the moment, but would be necessary for stuffs like EPoll.
139	/// require manual attention for sync
140	pub unsafe fn curr_wait_in(wait_room: &mut VecDeque<TaskId>) {
141		let t = Task::current().unwrap();
142		debug_assert_ne!(t.state, TaskState::Wait);
143		t.state = TaskState::Wait;
144		wait_room.push_back(t.taskid());
145	}
146
147	/// does not lock the GLOBAL_SCHEDULER, the caller is responsible of doing
148	/// that, e.g. call task.wakeup() from epilogue
149	pub unsafe fn wakeup(&mut self) {
150		if self.state != TaskState::Wait {
151			// already awake. why? I don't know.
152			return;
153		}
154		// TODO: makesure you don't put a task in the run queue more than once.
155		self.state = TaskState::Run;
156		let sched = GLOBAL_SCHEDULER.get_ref_mut_unguarded();
157		sched.insert_task(self.taskid());
158	}
159
160	pub fn nanosleep(&mut self, ns: u64) {
161		debug_assert!(self.state == TaskState::Run);
162		self.state = TaskState::Wait;
163		BellRinger::check_in(Sleeper::new(self.taskid(), ns));
164		debug_assert!(is_int_enabled());
165		Scheduler::yield_cpu();
166	}
167
168	/// create a kernel thread, you need to add it to the scheduler run queue
169	/// manually
170	pub fn create_task(pid: u64, entry: u64) -> TaskId {
171		let sp = unsafe { KSTACK_ALLOCATOR.lock().allocate() };
172		let tid = TaskId::new(sp);
173		println!("new task on {:#X}", sp);
174		let src_root_va = P2V(get_cr3()).unwrap();
175		let root_va = unsafe { address_space_clone(src_root_va).unwrap() };
176		println!("new addr space{:#X}", root_va);
177
178		let nt = unsafe {
179			Task::settle_on_stack(
180				sp,
181				Task {
182					magic: Mem::KERNEL_STACK_TASK_MAGIC,
183					pid,
184					kernel_stack: sp,
185					state: TaskState::Run,
186					context: Context64::default(),
187					mm: VMMan::new(),
188					page_root_va: root_va,
189				},
190			)
191		};
192		// KERNEL ID MAPPING
193		nt.mm.vmas.push(VMArea {
194			vm_range: Range::<u64> {
195				start: Mem::ID_MAP_START,
196				end: Mem::ID_MAP_END,
197			},
198			tag: String::from_str("KERNEL IDMAP").unwrap(),
199			user_perms: VMPerms::NONE,
200			backing: VMType::ANOM,
201		});
202		// KERNEL
203		nt.mm.vmas.push(VMArea {
204			vm_range: Range::<u64> {
205				start: Mem::KERNEL_OFFSET,
206				end: Mem::KERNEL_OFFSET + 64 * Mem::G,
207			},
208			tag: String::from_str("KERNEL").unwrap(),
209			user_perms: VMPerms::NONE,
210			backing: VMType::ANOM,
211		});
212		// KERNEL
213		nt.mm.vmas.push(VMArea {
214			vm_range: Range::<u64> {
215				start: Mem::USER_STACK_START,
216				end: Mem::USER_STACK_START + Mem::USER_STACK_SIZE,
217			},
218			tag: String::from_str("USER STACK").unwrap(),
219			user_perms: VMPerms::R | VMPerms::W,
220			backing: VMType::ANOM,
221		});
222		nt.prepare_context(entry);
223		tid
224	}
225
226	// we do not provide a function to clone a kernel task (yet).. perhaps we
227	// need multi-threaded kernel tasks. But not now.
228	// The clone_task_user will clone the whole user space and wipe the kernel
229	// stack for the newly created task. The child task will return via
230	// `child_task_entry` to usermode, thinking it has just returned from a
231	// syscall.
232	//
233	// In this function we simply clone everything; the preparation into
234	// usermode is handled by `child_task_entry()`, which is pretty much the
235	// same as `enter_usermode`..
236	// TODO: keep trap frame pointer in the task struct.
237	pub fn clone_task_user(&self, fp_parent: u64, pid: u64) -> TaskId {
238		let ksp_bottom = unsafe { KSTACK_ALLOCATOR.lock().allocate() };
239		let tid = TaskId::new(ksp_bottom);
240		let src_root_va = P2V(get_cr3()).unwrap();
241		let root_va = unsafe { address_space_clone(src_root_va).unwrap() };
242		// clone the address space:
243
244		// for a cloned task, the task.context doesn't matter. We will restore
245		// the full user context from the stored trap frame.
246		let nt = unsafe {
247			Task::settle_on_stack(
248				ksp_bottom,
249				Task {
250					magic: Mem::KERNEL_STACK_TASK_MAGIC,
251					pid,
252					kernel_stack: ksp_bottom,
253					state: TaskState::Run,
254					context: Context64::default(),
255					mm: self.mm.clone(),
256					page_root_va: root_va,
257				},
258			)
259		};
260
261		// clone the trapframe for the user
262		let mut sp = nt.get_init_kernel_sp();
263		let tf_size = core::mem::size_of::<TrapFrame>();
264		sp -= tf_size as u64;
265
266		unsafe {
267			ptr::copy_nonoverlapping(
268				fp_parent as *const u8,
269				sp as *mut u8,
270				tf_size,
271			)
272		}
273
274		let child_tf = sp as *mut TrapFrame;
275
276		// the child fork() call returns 0.
277		unsafe {
278			(*child_tf).rax = 0;
279		}
280
281		sp -= 8;
282		unsafe {
283			*(sp as *mut u64) = child_task_entry as *const () as u64;
284		}
285		nt.context.rsp = sp;
286		tid
287	}
288}