1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
//! TLS manager //! //! # Abstract //! //! For each thread of a process, the kernel allocates a 0x200-bytes "Thread Local Storage" //! memory region in UserLand. In this region resides the 0x100-bytes IPC command buffer, //! which is used by the user for passing IPC arguments, and a pointer to the user-controlled //! "thread context", which will likely be used for holding userspace thread local variables. //! //! Each thread in a process has its own private TLS, and from userspace its address can be found out //! at anytime by reading an architecture-specific register (aarch64 uses `tpidrro_el0`, x86 uses the //! `gs` segment selector). //! //! # Location //! //! The TLS content is defined by the [TLS] structure. It is a 0x200-bytes memory area that leaves //! in UserLand so it can be accessed and modified by the user. //! The user is allowed to access and modify the TLS of other thread from its process if it //! manages to find the location of their TLS, but this is not advised, as it serves little purpose. //! //! Kernel-side, each thread holds a raw pointer to its TLS (`*mut TLS`) in its [ThreadStruct]. //! This pointer is used by the kernel to get the thread's `ipc_command_buffer` address, //! and is restored as part of hardware context on every context-switch. //! //! # Allocation //! //! Each process holds a [TLSManager] in its ProcessStruct, which manages the TLSs for this process, //! keeps track of which ones are in-use and which ones are free, and try to re-use free TLSs when //! spawning a thread. //! //! When a thread is being created, it asks its process's `TLSManager` via [allocate_tls] to get a pointer //! to its TLS, and saves it in the `ThreadStruct`. //! //! When a thread dies, it notifies its process's `TLSManager` via [free_tls], so its TLS can be re-used. //! //! TLSs are only 0x200 bytes, so the `TLSManager` groups them together to fit inside a page, //! and will allocate a new page every time it is full and cannot satisfy a TLS allocation. //! //! [TLS]: sunrise_libkern::TLS //! [TLSManager]: TLSManager //! [ThreadStruct]: crate::process::ThreadStruct //! [allocate_TLS]: TLSManager::allocate_tls //! [free_TLS]: TLSManager::free_tls use crate::VirtualAddress; use crate::PAGE_SIZE; use crate::paging::process_memory::ProcessMemory; use crate::paging::MappingAccessRights; use crate::error::KernelError; use sunrise_libutils::bit_array_first_zero; use sunrise_libkern::{MemoryType, TLS}; use core::mem::size_of; use bit_field::BitArray; use alloc::vec::Vec; /// Manages a page containing 8 TLS /// /// A TLS being only 0x200 bytes, the kernel aggregates the TLSs of a same process in groups of 8 /// so that they fit in one page. /// /// # Memory leak /// /// Dropping this struct will leak the page, until the process is killed and all its memory is freed. /// See [TLSManager] for more on this topic. #[derive(Debug)] struct TLSPage { /// Address of the page, in UserLand. page_address: VirtualAddress, /// Bitmap indicating if the TLS is in use (`1`) or free (`0`). usage: [u8; PAGE_SIZE / size_of::<TLS>() / 8] } impl TLSPage { /// Allocates a new page holing 8 TLS. /// /// The page is user read-write, and its memory type is `ThreadLocal`. /// /// # Error /// /// Fails if the allocation fails. fn new(pmemory: &mut ProcessMemory) -> Result<Self, KernelError> { let addr = pmemory.find_available_space(PAGE_SIZE)?; pmemory.create_regular_mapping(addr, PAGE_SIZE, MemoryType::ThreadLocal, MappingAccessRights::u_rw())?; Ok(TLSPage { page_address: addr, usage: [0u8; PAGE_SIZE / size_of::<TLS>() / 8] }) } /// Finds an available slot in the TLSPage, bzero it, marks it allocated, and gives back a pointer to it. /// /// If no slot was available, this function returns `None`. /// /// The returned TLS still has to be bzeroed, has it may contain the data of a previous thread. fn allocate_tls(&mut self) -> Option<VirtualAddress> { let index = bit_array_first_zero(&self.usage)?; self.usage.set_bit(index, true); Some(self.page_address + index * size_of::<TLS>()) } /// Marks a TLS in this TLSPage as free so it can be used by the next spawned thread. /// /// # Panics /// /// Panics if `address` does not fall in this TLSPage, not a valid offset, or marked already free. fn free_tls(&mut self, address: VirtualAddress) { debug_assert!(address.floor() == self.page_address, "Freed TLS ptr is outside of TLSPage."); debug_assert!(address.addr() % size_of::<TLS>() == 0, "Freed TLS ptr is not TLS size aligned."); let index = (address - self.page_address) / size_of::<TLS>(); debug_assert!(self.usage.get_bit(index), "Freed TLS was not marked occupied"); self.usage.set_bit(index, false); } } // size_of::<TLS>() is expected to divide PAGE_SIZE evenly. const_assert_eq!(PAGE_SIZE % size_of::<TLS>(), 0); /// TLS allocator /// /// Each process holds a `TLSManager` in its [ProcessStruct]. /// /// When a thread is being created, we ask the `TLSManager` to allocate a TLS for it, and when /// it dies we give it back to the manager so it can be re-used the next time this process spawns a thread. /// /// When all of its TLS are occupied, the `TLSManager` will expend its memory by allocating a new page. /// /// # Memory leak /// /// The `TLSManager` will never free the pages it manages, and they are leaked when the `TLSManager` is dropped. /// They will become available again after the process dies and its [ProcessMemory] is freed. /// /// A `TLSManager` will always be dropped at process's death, at the same time as the `ProcessMemory`. /// This prevents a dependency in the order in which the `TLSManager` and the `ProcessMemory` are dropped. /// /// [ProcessStruct]: crate::process::ProcessStruct #[derive(Debug, Default)] pub struct TLSManager { /// Vec of tracked pages. When all slots are occupied, we allocate a new page. tls_pages: Vec<TLSPage> } impl TLSManager { /// Allocates a new TLS. /// /// This function will try to re-use free TLSs, and will only allocate when all TLS are in use. /// /// The returned TLS still has to be bzeroed, has it may contain the data of a previous thread. /// /// # Error /// /// Fails if the allocation fails. pub fn allocate_tls(&mut self, pmemory: &mut ProcessMemory) -> Result<VirtualAddress, KernelError> { for tls_page in &mut self.tls_pages { if let Some(tls) = tls_page.allocate_tls() { return Ok(tls); } } // no free slot, we need to allocate a new page. let mut new_tls_page = TLSPage::new(pmemory)?; let tls = new_tls_page.allocate_tls().expect("Empty TLSPage can't allocate"); self.tls_pages.push(new_tls_page); Ok(tls) } /// Mark this TLS as free, so it can be re-used by future spawned thread. /// /// # Safety /// /// The TLS will be reassigned, so it must never be used again after calling this function. /// /// # Panics /// /// Panics if the TLS is not managed by this TLSManager, doesn't have a valid offset, or is already marked free. pub unsafe fn free_tls(&mut self, tls: VirtualAddress) { // round down ptr to find out which page in belongs to. let tls_page_ptr = tls.floor(); for tls_page in &mut self.tls_pages { if tls_page.page_address == tls_page_ptr { tls_page.free_tls(tls); return; } } panic!("Freed TLS {:?} is not in TLSManager.", tls); } }