diff --git a/src/arch/aarch64/context.rs b/src/arch/aarch64/context.rs index 9e1cd482..06f50d9f 100644 --- a/src/arch/aarch64/context.rs +++ b/src/arch/aarch64/context.rs @@ -58,9 +58,9 @@ impl StackBuilder { self.sp } - fn init_common(&mut self, entry: usize, ttbr0: u64) { + fn init_common(&mut self, entry: usize, ttbr0: u64, tpidr_el0: u64) { self.push(ttbr0 as _); // ttbr0_el1 - self.push(0); // tpidr_el0 + self.push(tpidr_el0 as _); // tpidr_el0 self.push(entry); // x30/lr self.push(0); // x29 @@ -91,7 +91,7 @@ impl TaskContextImpl for TaskContext { stack.push(entry as _); stack.push(arg); - stack.init_common(__aarch64_task_enter_kernel as _, 0); + stack.init_common(__aarch64_task_enter_kernel as _, 0, 0); let sp = stack.build(); @@ -104,7 +104,13 @@ impl TaskContextImpl for TaskContext { }) } - fn user(entry: usize, arg: usize, ttbr0: u64, user_stack_sp: usize) -> Result { + fn user( + entry: usize, + arg: usize, + ttbr0: u64, + user_stack_sp: usize, + tpidr_el0: usize, + ) -> Result { const USER_TASK_PAGES: usize = 16; let stack_base = phys::alloc_pages_contiguous(USER_TASK_PAGES)?.virtualize_raw(); @@ -115,7 +121,7 @@ impl TaskContextImpl for TaskContext { stack.push(0); stack.push(user_stack_sp); - stack.init_common(__aarch64_task_enter_user as _, ttbr0); + stack.init_common(__aarch64_task_enter_user as _, ttbr0, tpidr_el0 as _); let sp = stack.build(); diff --git a/src/arch/x86_64/context.S b/src/arch/x86_64/context.S index 1892e63e..d89ff421 100644 --- a/src/arch/x86_64/context.S +++ b/src/arch/x86_64/context.S @@ -1,5 +1,7 @@ // vi: set ft=asm : +.set MSR_IA32_FS_BASE, 0xC0000100 + .macro SAVE_TASK_STATE sub ${context_size}, %rsp @@ -8,6 +10,16 @@ mov %r13, 16(%rsp) mov %r14, 24(%rsp) mov %r15, 32(%rsp) + + // Store FS_BASE + mov $MSR_IA32_FS_BASE, %ecx + rdmsr + mov %edx, %ecx + shl $32, %rcx + or %rax, %rcx + + mov %rcx, 40(%rsp) + // TODO save %fs mov %rbp, 48(%rsp) @@ -24,7 +36,16 @@ mov 16(%rsp), %r13 mov 24(%rsp), %r14 mov 32(%rsp), %r15 - // TODO + + // Load FS_BASE + // edx:eax = fs_base + mov 40(%rsp), %rdx + mov %edx, %eax + shr $32, %rdx + + mov $MSR_IA32_FS_BASE, %ecx + wrmsr + // mov 40(%rsp), %fs mov 48(%rsp), %rbp diff --git a/src/arch/x86_64/context.rs b/src/arch/x86_64/context.rs index 2c4458ea..430e9337 100644 --- a/src/arch/x86_64/context.rs +++ b/src/arch/x86_64/context.rs @@ -65,14 +65,14 @@ impl StackBuilder { self.sp } - fn init_common(&mut self, entry: usize, cr3: u64) { + fn init_common(&mut self, entry: usize, cr3: u64, fs_base: usize) { self.push(entry); // address for ret // End of common context self.push(cr3 as _); // %cr3 self.push(0); // %rbp - self.push(0); // %fs (TODO) + self.push(fs_base); // fs_base self.push(0); // %r15 self.push(0); // %r14 self.push(0); // %r13 @@ -98,9 +98,11 @@ impl TaskContextImpl for TaskContext { stack.push(entry as _); stack.push(arg); - stack.init_common(__x86_64_task_enter_kernel as _, unsafe { - KERNEL_TABLES.as_physical_address().into_raw() - }); + stack.init_common( + __x86_64_task_enter_kernel as _, + unsafe { KERNEL_TABLES.as_physical_address().into_raw() }, + 0, + ); let sp = stack.build(); infoln!("Kernel stack {:#x}", sp); @@ -114,7 +116,13 @@ impl TaskContextImpl for TaskContext { }) } - fn user(entry: usize, arg: usize, cr3: u64, user_stack_sp: usize) -> Result { + fn user( + entry: usize, + arg: usize, + cr3: u64, + user_stack_sp: usize, + fs_base: usize, + ) -> Result { const USER_TASK_PAGES: usize = 8; let stack_base = phys::alloc_pages_contiguous(USER_TASK_PAGES)?.virtualize_raw(); @@ -125,7 +133,7 @@ impl TaskContextImpl for TaskContext { stack.push(arg); stack.push(user_stack_sp); - stack.init_common(__x86_64_task_enter_user as _, cr3); + stack.init_common(__x86_64_task_enter_user as _, cr3, fs_base); let sp = stack.build(); let rsp0 = stack_base + USER_TASK_PAGES * 0x1000; diff --git a/src/proc/elf.rs b/src/proc/elf.rs index 06afd6aa..16ed50f2 100644 --- a/src/proc/elf.rs +++ b/src/proc/elf.rs @@ -2,15 +2,22 @@ use core::ops::DerefMut; use elf::{ - abi::{PF_W, PF_X, PT_LOAD}, + abi::{PF_W, PF_X, PT_LOAD, PT_TLS}, endian::AnyEndian, + segment::ProgramHeader, ElfStream, ParseError, }; use vfs::{FileRef, Read, Seek}; use yggdrasil_abi::{error::Error, io::SeekFrom}; -use crate::mem::{ - phys, pointer::PhysicalRefMut, process::ProcessAddressSpace, table::MapAttributes, +use crate::{ + mem::{ + phys, + pointer::{PhysicalRef, PhysicalRefMut}, + process::ProcessAddressSpace, + table::{EntryLevel, MapAttributes}, + }, + task::process::{ProcessImage, ProcessTlsInfo, ProcessTlsLayout}, }; #[derive(Clone, Copy)] @@ -56,6 +63,54 @@ fn from_parse_error(v: ParseError) -> Error { Error::InvalidFile } +pub fn clone_tls(space: &ProcessAddressSpace, image: &ProcessImage) -> Result { + let Some(tls) = image.tls.as_ref() else { + // No TLS + return Ok(0); + }; + + assert_ne!(tls.master_copy_base, 0); + assert_ne!(tls.layout.mem_size, 0); + + let address = space.allocate( + None, + 0x1000, + |_| phys::alloc_page(), + MapAttributes::USER_READ | MapAttributes::USER_WRITE | MapAttributes::NON_GLOBAL, + )?; + + debugln!( + "Clone TLS from {:#x} (data={:#x}) to {:#x} (data={:#x})", + tls.master_copy_base, + tls.master_copy_base + tls.layout.data_offset, + address, + address + tls.layout.data_offset + ); + debugln!("tls ptr = {:#x}", address + tls.layout.ptr_offset); + + let src_phys = space.translate(tls.master_copy_base)?; + let dst_phys = space.translate(address)?; + + // Copy data + unsafe { + let src = + PhysicalRef::::map_slice(src_phys.add(tls.layout.data_offset), tls.layout.mem_size); + let mut dst = + PhysicalRefMut::map_slice(dst_phys.add(tls.layout.data_offset), tls.layout.mem_size); + + dst.copy_from_slice(&src); + } + + // Setup self-pointer + unsafe { + let mut dst = PhysicalRefMut::::map(dst_phys.add(tls.layout.ptr_offset)); + + *dst = address + tls.layout.ptr_offset; + } + + Ok(address + tls.layout.ptr_offset) +} + fn load_bytes( space: &ProcessAddressSpace, addr: usize, @@ -92,69 +147,162 @@ where Ok(()) } -/// Loads an ELF binary from `file` into the target address space -pub fn load_elf_from_file(space: &ProcessAddressSpace, file: FileRef) -> Result { - let file = FileReader { file: &file }; +fn load_segment( + space: &ProcessAddressSpace, + phdr: &ProgramHeader, + file: &FileReader, +) -> Result<(), Error> { + if phdr.p_memsz == 0 { + return Ok(()); + } + let attrs = match (phdr.p_flags & PF_W, phdr.p_flags & PF_X) { + (0, 0) => MapAttributes::USER_READ, + (_, 0) => MapAttributes::USER_WRITE | MapAttributes::USER_READ, + (0, _) => MapAttributes::USER_READ, + (_, _) => MapAttributes::USER_WRITE | MapAttributes::USER_READ, + } | MapAttributes::NON_GLOBAL; + + // Map the range + let aligned_start = (phdr.p_vaddr as usize) & !0xFFF; + let aligned_end = ((phdr.p_vaddr + phdr.p_memsz) as usize + 0xFFF) & !0xFFF; + + space.map( + aligned_start, + aligned_end - aligned_start, + |_| phys::alloc_page(), + attrs, + )?; + + if phdr.p_filesz > 0 { + load_bytes( + space, + phdr.p_vaddr as usize, + |off, mut dst| { + let mut source = file.file.borrow_mut(); + source.seek(SeekFrom::Start(phdr.p_offset + off as u64))?; + source.read_exact(dst.deref_mut()) + }, + phdr.p_filesz as usize, + )?; + } + + if phdr.p_memsz > phdr.p_filesz { + let addr = (phdr.p_vaddr + phdr.p_filesz) as usize; + let len = (phdr.p_memsz - phdr.p_filesz) as usize; + + load_bytes( + space, + addr, + |_, mut dst| { + dst.fill(0); + Ok(()) + }, + len, + )?; + } + + Ok(()) +} + +fn tls_segment( + space: &ProcessAddressSpace, + phdr: &ProgramHeader, + file: &FileReader, +) -> Result { + assert_ne!(phdr.p_memsz, 0); + + if !phdr.p_align.is_power_of_two() { + return Err(Error::InvalidArgument); + } + + let layout = ProcessTlsLayout::new(phdr.p_align as _, phdr.p_filesz as _, phdr.p_memsz as _); + let data_offset = layout.data_offset; + let data_size = layout.data_size; + let mem_size = layout.mem_size; + let aligned_size = (layout.full_size + 0xFFF) & !0xFFF; + assert!(aligned_size <= 0x1000); + + let base_address = space.allocate( + None, + aligned_size, + |_| phys::alloc_page(), + MapAttributes::USER_READ | MapAttributes::USER_WRITE | MapAttributes::NON_GLOBAL, + )?; + + debugln!( + "Allocated TLS master copy @ {:#x}, tls={:#x}, tls_data={:#x}", + base_address, + base_address + layout.ptr_offset, + base_address + layout.data_offset + ); + + let tls = ProcessTlsInfo { + master_copy_base: base_address, + layout, + }; + + if data_size > 0 { + load_bytes( + space, + base_address + data_offset, + |off, mut dst| { + let mut source = file.file.borrow_mut(); + source.seek(SeekFrom::Start(phdr.p_offset + off as u64))?; + source.read_exact(dst.deref_mut()) + }, + data_size, + ); + } + + if mem_size > data_size { + load_bytes( + space, + base_address + data_offset + data_size, + |off, mut dst| { + dst.fill(0); + Ok(()) + }, + mem_size - data_size, + ); + } + + Ok(tls) +} + +/// Loads an ELF binary from `file` into the target address space +pub fn load_elf_from_file( + space: &ProcessAddressSpace, + file: FileRef, +) -> Result { + let file = FileReader { file: &file }; let elf = ElfStream::::open_stream(file).map_err(from_parse_error)?; + struct TlsInfo { + master_address: usize, + offset: usize, + size: usize, + } + + let mut tls = None; + for phdr in elf.segments() { - if phdr.p_type != PT_LOAD { - continue; - } - - debugln!("LOAD {:#x?}", phdr.p_vaddr..phdr.p_vaddr + phdr.p_memsz); - - let attrs = match (phdr.p_flags & PF_W, phdr.p_flags & PF_X) { - (0, 0) => MapAttributes::USER_READ, - (_, 0) => MapAttributes::USER_WRITE | MapAttributes::USER_READ, - (0, _) => MapAttributes::USER_READ, - (_, _) => MapAttributes::USER_WRITE | MapAttributes::USER_READ, - } | MapAttributes::NON_GLOBAL; - - if phdr.p_memsz > 0 { - // Map the range - let aligned_start = (phdr.p_vaddr as usize) & !0xFFF; - let aligned_end = ((phdr.p_vaddr + phdr.p_memsz) as usize + 0xFFF) & !0xFFF; - - space.map( - aligned_start, - aligned_end - aligned_start, - |_| phys::alloc_page(), - attrs, - )?; - } else { - continue; - } - - if phdr.p_filesz > 0 { - load_bytes( - space, - phdr.p_vaddr as usize, - |off, mut dst| { - let mut source = file.file.borrow_mut(); - source.seek(SeekFrom::Start(phdr.p_offset + off as u64))?; - source.read_exact(dst.deref_mut()) - }, - phdr.p_filesz as usize, - )?; - } - - if phdr.p_memsz > phdr.p_filesz { - let addr = (phdr.p_vaddr + phdr.p_filesz) as usize; - let len = (phdr.p_memsz - phdr.p_filesz) as usize; - - load_bytes( - space, - addr, - |_, mut dst| { - dst.fill(0); - Ok(()) - }, - len, - )?; + match phdr.p_type { + PT_LOAD => { + load_segment(space, phdr, &file)?; + } + PT_TLS => { + assert!(tls.is_none()); + tls.replace(tls_segment(space, phdr, &file)?); + // tls_master_address = tls_segment(space, phdr, &file)?; + // tls_size = phdr.p_memsz as usize; + } + _ => (), } } - Ok(elf.ehdr.e_entry as usize) + Ok(ProcessImage { + entry: elf.ehdr.e_entry as usize, + tls, + }) } diff --git a/src/proc/exec.rs b/src/proc/exec.rs index 2ac0b8a5..f1566591 100644 --- a/src/proc/exec.rs +++ b/src/proc/exec.rs @@ -11,11 +11,19 @@ use vfs::FileRef; use crate::{ mem::{ - phys, pointer::PhysicalRefMut, process::ProcessAddressSpace, table::MapAttributes, + phys, + pointer::{PhysicalRef, PhysicalRefMut}, + process::ProcessAddressSpace, + table::MapAttributes, ForeignPointer, }, proc, - task::{context::TaskContextImpl, process::Process, thread::Thread, TaskContext}, + task::{ + context::TaskContextImpl, + process::{Process, ProcessImage}, + thread::Thread, + TaskContext, + }, }; pub struct BufferPlacer<'a> { @@ -99,7 +107,7 @@ fn setup_program_env( fn setup_binary>( name: S, space: ProcessAddressSpace, - entry: usize, + image: ProcessImage, args: &[&str], envs: &[&str], ) -> Result<(Arc, Arc), Error> { @@ -120,7 +128,7 @@ fn setup_binary>( debugln!( "Entry: {:#x}, Stack: {:#x}..{:#x}, Args: {:#x}", - entry, + image.entry, virt_stack_base, virt_stack_base + USER_STACK_PAGES * 0x1000, virt_args_base @@ -138,9 +146,22 @@ fn setup_binary>( } } - let context = TaskContext::user(entry, arg, space.as_address_with_asid(), user_sp)?; + let tls_address = proc::elf::clone_tls(&space, &image)?; - Ok(Process::new_with_main(name, Arc::new(space), context)) + let context = TaskContext::user( + image.entry, + arg, + space.as_address_with_asid(), + user_sp, + tls_address, + )?; + + Ok(Process::new_with_main( + name, + Arc::new(space), + context, + Some(image), + )) // Ok(Process::new_with_context(name, Some(space), context)) } @@ -152,7 +173,7 @@ pub fn load_elf>( envs: &[&str], ) -> Result<(Arc, Arc), Error> { let space = ProcessAddressSpace::new()?; - let elf_entry = proc::elf::load_elf_from_file(&space, file)?; + let image = proc::elf::load_elf_from_file(&space, file)?; - setup_binary(name, space, elf_entry, args, envs) + setup_binary(name, space, image, args, envs) } diff --git a/src/syscall/mod.rs b/src/syscall/mod.rs index 39dd9b05..fa2d8d20 100644 --- a/src/syscall/mod.rs +++ b/src/syscall/mod.rs @@ -4,7 +4,7 @@ use core::{mem::MaybeUninit, time::Duration}; use abi::{ error::Error, io::{DeviceRequest, DirectoryEntry, FileAttr, FileMode, OpenOptions, RawFd, SeekFrom}, - process::{ExitCode, Signal, SpawnOption, SpawnOptions}, + process::{ExitCode, Signal, SpawnOption, SpawnOptions, ThreadSpawnOptions}, syscall::SyscallFunction, }; use alloc::rc::Rc; @@ -68,15 +68,14 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result Ok(0) } SyscallFunction::Nanosleep => { - todo!(); - // let seconds = args[0]; - // let nanos = args[1] as u32; - // let duration = Duration::new(seconds, nanos); + let seconds = args[0]; + let nanos = args[1] as u32; + let duration = Duration::new(seconds, nanos); - // block! { - // runtime::sleep(duration).await - // } - // .map(|_| 0) + block! { + runtime::sleep(duration).await + } + .map(|_| 0) } // Resource management SyscallFunction::MapMemory => { @@ -361,6 +360,11 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result Ok(pid as _) }) } + SyscallFunction::SpawnThread => { + let options = arg_user_ref::(args[0] as usize)?; + let id = process.spawn_thread(options)?; + Ok(id.as_user() as _) + } SyscallFunction::Exit => { let code = ExitCode::from(args[0] as i32); // TODO separate handlers for process exit and thread exit? @@ -368,6 +372,19 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result // Process::current().exit(code); panic!(); } + SyscallFunction::SendSignal => { + let pid = ProcessId::from(args[0] as u32); + let signal = Signal::try_from(args[1] as u32).map_err(|_| Error::InvalidArgument)?; + + let target = Process::get(pid).ok_or(Error::DoesNotExist)?; + target.raise_signal(signal); + + Ok(0) + } + SyscallFunction::ExitSignal => { + panic!("Handled elsewhere"); + // Process::current().exit_signal(); + } SyscallFunction::GetPid => Ok(u32::from(process.id()) as usize), SyscallFunction::GetSessionId => todo!(), SyscallFunction::GetProcessGroupId => todo!(), @@ -419,20 +436,6 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result Ok(0) } - _ => todo!("{:?}", func), - // SyscallFunction::SendSignal => { - // let pid = args[0] as u32; - // let signal = Signal::try_from(args[1] as u32).map_err(|_| Error::InvalidArgument)?; - - // let target = Process::get(pid as _).ok_or(Error::DoesNotExist)?; - // target.raise_signal(signal); - - // Ok(0) - // } - // SyscallFunction::ExitSignal => { - // panic!("Handled elsewhere"); - // // Process::current().exit_signal(); - // } } } diff --git a/src/task/context.rs b/src/task/context.rs index 0c011f96..c6c36c14 100644 --- a/src/task/context.rs +++ b/src/task/context.rs @@ -58,7 +58,13 @@ pub trait TaskContextImpl: Sized { /// Constructs a user thread context. The caller is responsible for allocating the userspace /// stack and setting up a valid address space for the context. - fn user(entry: usize, arg: usize, cr3: u64, user_stack_sp: usize) -> Result; + fn user( + entry: usize, + arg: usize, + cr3: u64, + user_stack_sp: usize, + tls_address: usize, + ) -> Result; /// Performs an entry into a context. /// diff --git a/src/task/process.rs b/src/task/process.rs index 556d85e0..3e81ddb6 100644 --- a/src/task/process.rs +++ b/src/task/process.rs @@ -2,6 +2,7 @@ use core::{ fmt, + mem::size_of, pin::Pin, sync::atomic::{AtomicU64, Ordering}, task::{Context, Poll}, @@ -9,7 +10,7 @@ use core::{ use abi::{ error::Error, - process::{ExitCode, Signal}, + process::{ExitCode, Signal, ThreadSpawnOptions}, }; use alloc::{ collections::{BTreeMap, VecDeque}, @@ -21,7 +22,17 @@ use futures_util::Future; use kernel_util::util::OneTimeInit; use vfs::VnodeRef; -use crate::{mem::process::ProcessAddressSpace, proc::io::ProcessIo, sync::IrqSafeSpinlock}; +use crate::{ + mem::{ + phys, + pointer::{PhysicalRef, PhysicalRefMut}, + process::ProcessAddressSpace, + table::MapAttributes, + }, + proc::{self, io::ProcessIo}, + sync::IrqSafeSpinlock, + task::context::TaskContextImpl, +}; use super::{ runtime::QueueWaker, @@ -39,6 +50,80 @@ pub enum ProcessState { #[repr(transparent)] pub struct ProcessId(u64); +// TLS layout (x86-64): +// | mem_size | uthread_size | +// | Data .......| self, ??? | +// +// TLS layout (aarch64): +// | uthread_size (0x10?) | mem_size | +// | ??? | Data .....| +#[derive(Debug)] +pub struct ProcessTlsInfo { + pub master_copy_base: usize, + pub layout: ProcessTlsLayout, +} + +#[derive(Debug)] +pub struct ProcessTlsLayout { + pub data_offset: usize, + pub uthread_offset: usize, + pub ptr_offset: usize, + + pub data_size: usize, + pub mem_size: usize, + + pub full_size: usize, +} + +#[cfg(target_arch = "aarch64")] +impl ProcessTlsLayout { + pub fn new(align: usize, data_size: usize, mem_size: usize) -> Self { + debug_assert!(align.is_power_of_two()); + let tls_block0_offset = (size_of::() * 2 + align - 1) & !(align - 1); + + let full_size = (tls_block0_offset + mem_size + align - 1) & !(align - 1); + + Self { + data_offset: tls_block0_offset, + uthread_offset: 0, + ptr_offset: 0, + + data_size, + mem_size, + full_size, + } + } +} + +#[cfg(target_arch = "x86_64")] +impl ProcessTlsLayout { + pub fn new(align: usize, data_size: usize, mem_size: usize) -> Self { + // The static TLS blocks are placed below TP + // TP points to the TCB + debug_assert!(align.is_power_of_two()); + let back_size = (mem_size + align - 1) & !(align - 1); + // Self-pointer + let forward_size = size_of::(); + + let full_size = back_size + forward_size; + + Self { + data_offset: 0, + uthread_offset: back_size, + ptr_offset: back_size, + + data_size, + mem_size, + full_size, + } + } +} + +pub struct ProcessImage { + pub entry: usize, + pub tls: Option, +} + struct ProcessInner { state: ProcessState, @@ -56,6 +141,7 @@ pub struct Process { space: Arc, inner: IrqSafeSpinlock, + image: Option, exit_waker: QueueWaker, pub io: IrqSafeSpinlock, @@ -69,6 +155,7 @@ impl Process { name: S, space: Arc, context: TaskContext, + image: Option, ) -> (Arc, Arc) { let name = name.into(); let id = ProcessId::next(); @@ -77,6 +164,8 @@ impl Process { name, id, + image, + space: space.clone(), inner: IrqSafeSpinlock::new(ProcessInner { state: ProcessState::Running, @@ -99,6 +188,37 @@ impl Process { (process, thread) } + pub fn spawn_thread(self: &Arc, options: &ThreadSpawnOptions) -> Result { + debugln!( + "Spawn thread in {} with options: {:#x?}", + self.id(), + options + ); + + let tls_address = if let Some(image) = self.image.as_ref() { + proc::elf::clone_tls(&self.space, image)? + } else { + 0 + }; + + let space = self.space.clone(); + let context = TaskContext::user( + options.entry as _, + options.argument as _, + space.as_address_with_asid(), + options.stack_top, + tls_address, + )?; + let thread = Thread::new_uthread(self.clone(), space, context); + let id = thread.id(); + + self.inner.lock().threads.push(thread.clone()); + + thread.enqueue_somewhere(); + + Ok(id) + } + pub fn id(&self) -> ProcessId { self.id } diff --git a/src/task/thread.rs b/src/task/thread.rs index 19f47c14..ec52067e 100644 --- a/src/task/thread.rs +++ b/src/task/thread.rs @@ -416,6 +416,13 @@ impl ThreadId { let id = COUNT.fetch_add(1, Ordering::SeqCst); Self::User(id) } + + pub fn as_user(&self) -> u64 { + match self { + Self::Kernel(_) => panic!(), + &Self::User(id) => id, + } + } } impl fmt::Display for ThreadId {