/* * Copyright 2014, Paweł Dziepak, pdziepak@quarnos.org. * Copyright 2008-2011, Ingo Weinhold, ingo_weinhold@gmx.de. * Copyright 2002-2007, Axel Dörfler, axeld@pinc-software.de. * Distributed under the terms of the MIT License. * * Copyright 2001-2002, Travis Geiselbrecht. All rights reserved. * Distributed under the terms of the NewOS License. */ #ifndef _THREAD_H #define _THREAD_H #include #include #include // For the thread blocking inline functions only. #include #include #include struct arch_fork_arg; struct kernel_args; struct select_info; struct thread_creation_attributes; // thread notifications #define THREAD_MONITOR '_tm_' #define THREAD_ADDED 0x01 #define THREAD_REMOVED 0x02 #define THREAD_NAME_CHANGED 0x04 namespace BKernel { struct ThreadCreationAttributes : thread_creation_attributes { // when calling from kernel only team_id team; Thread* thread; sigset_t signal_mask; size_t additional_stack_size; // additional space in the stack // area after the TLS region, not // used as thread stack thread_func kernelEntry; void* kernelArgument; arch_fork_arg* forkArgs; // If non-NULL, the userland thread // will be started with this // register context. public: ThreadCreationAttributes() {} // no-init constructor ThreadCreationAttributes( thread_func function, const char* name, int32 priority, void* arg, team_id team = -1, Thread* thread = NULL); status_t InitFromUserAttributes( const thread_creation_attributes* userAttributes, char* nameBuffer); }; } // namespace BKernel using BKernel::ThreadCreationAttributes; extern spinlock gThreadCreationLock; #ifdef __cplusplus extern "C" { #endif void thread_at_kernel_entry(bigtime_t now); // called when the thread enters the kernel on behalf of the thread void thread_at_kernel_exit(void); void thread_at_kernel_exit_no_signals(void); void thread_reset_for_exec(void); status_t thread_init(struct kernel_args *args); status_t thread_preboot_init_percpu(struct kernel_args *args, int32 cpuNum); void thread_yield(void); void thread_exit(void); void thread_map(void (*function)(Thread* thread, void* data), void* data); int32 thread_max_threads(void); int32 thread_used_threads(void); const char* thread_state_to_text(Thread* thread, int32 state); int32 thread_get_io_priority(thread_id id); void thread_set_io_priority(int32 priority); #define thread_get_current_thread arch_thread_get_current_thread static thread_id thread_get_current_thread_id(void); static inline thread_id thread_get_current_thread_id(void) { Thread *thread = thread_get_current_thread(); return thread ? thread->id : 0; } static inline bool thread_is_idle_thread(Thread *thread) { return thread->priority == B_IDLE_PRIORITY; } thread_id allocate_thread_id(); thread_id peek_next_thread_id(); status_t thread_enter_userspace_new_team(Thread* thread, addr_t entryFunction, void* argument1, void* argument2); status_t thread_create_user_stack(Team* team, Thread* thread, void* stackBase, size_t stackSize, size_t additionalSize); thread_id thread_create_thread(const ThreadCreationAttributes& attributes, bool kernel); thread_id spawn_kernel_thread_etc(thread_func, const char *name, int32 priority, void *args, team_id team); status_t select_thread(int32 object, struct select_info *info, bool kernel); status_t deselect_thread(int32 object, struct select_info *info, bool kernel); #define syscall_64_bit_return_value() arch_syscall_64_bit_return_value() status_t thread_block(); status_t thread_block_with_timeout(uint32 timeoutFlags, bigtime_t timeout); void thread_unblock(Thread* thread, status_t status); // used in syscalls.c status_t _user_set_thread_priority(thread_id thread, int32 newPriority); status_t _user_rename_thread(thread_id thread, const char *name); status_t _user_suspend_thread(thread_id thread); status_t _user_resume_thread(thread_id thread); status_t _user_rename_thread(thread_id thread, const char *name); thread_id _user_spawn_thread(struct thread_creation_attributes* attributes); status_t _user_wait_for_thread(thread_id id, status_t *_returnCode); status_t _user_wait_for_thread_etc(thread_id id, uint32 flags, bigtime_t timeout, status_t *_returnCode); status_t _user_snooze_etc(bigtime_t timeout, int timebase, uint32 flags, bigtime_t* _remainingTime); status_t _user_kill_thread(thread_id thread); status_t _user_cancel_thread(thread_id threadID, void (*cancelFunction)(int)); void _user_thread_yield(void); void _user_exit_thread(status_t return_value); bool _user_has_data(thread_id thread); status_t _user_send_data(thread_id thread, int32 code, const void *buffer, size_t buffer_size); status_t _user_receive_data(thread_id *_sender, void *buffer, size_t buffer_size); thread_id _user_find_thread(const char *name); status_t _user_get_thread_info(thread_id id, thread_info *info); status_t _user_get_next_thread_info(team_id team, int32 *cookie, thread_info *info); int _user_get_cpu(); status_t _user_block_thread(uint32 flags, bigtime_t timeout); status_t _user_unblock_thread(thread_id thread, status_t status); status_t _user_unblock_threads(thread_id* threads, uint32 count, status_t status); // ToDo: these don't belong here struct rlimit; int _user_getrlimit(int resource, struct rlimit * rlp); int _user_setrlimit(int resource, const struct rlimit * rlp); #ifdef __cplusplus } #endif /*! Checks whether the current thread would immediately be interrupted when blocking it with the given wait/interrupt flags. The caller must hold the scheduler lock. \param thread The current thread. \param flags Wait/interrupt flags to be considered. Relevant are: - \c B_CAN_INTERRUPT: The thread can be interrupted by any non-blocked signal. Implies \c B_KILL_CAN_INTERRUPT (specified or not). - \c B_KILL_CAN_INTERRUPT: The thread can be interrupted by a kill signal. \return \c true, if the thread would be interrupted, \c false otherwise. */ static inline bool thread_is_interrupted(Thread* thread, uint32 flags) { sigset_t pendingSignals = thread->AllPendingSignals(); return ((flags & B_CAN_INTERRUPT) != 0 && (pendingSignals & ~thread->sig_block_mask) != 0) || ((flags & B_KILL_CAN_INTERRUPT) != 0 && (pendingSignals & KILL_SIGNALS) != 0); } /*! Checks whether the given thread is currently blocked (i.e. still waiting for something). If a stable answer is required, the caller must hold the scheduler lock. Alternatively, if waiting is not interruptible and cannot time out, holding the client lock held when calling thread_prepare_to_block() and the unblocking functions works as well. \param thread The thread in question. \return \c true, if the thread is blocked, \c false otherwise. */ static inline bool thread_is_blocked(Thread* thread) { return atomic_get(&thread->wait.status) == 1; } /*! Prepares the current thread for waiting. This is the first of two steps necessary to block the current thread (IOW, to let it wait for someone else to unblock it or optionally time out after a specified delay). The process consists of two steps to avoid race conditions in case a lock other than the scheduler lock is involved. Usually the thread waits for some condition to change and this condition is something reflected in the caller's data structures which should be protected by a client lock the caller knows about. E.g. in the semaphore code that lock is a per-semaphore spinlock that protects the semaphore data, including the semaphore count and the queue of waiting threads. For certain low-level locking primitives (e.g. mutexes) that client lock is the scheduler lock itself, which simplifies things a bit. If a client lock other than the scheduler lock is used, this function must be called with that lock being held. Afterwards that lock should be dropped and the function that actually blocks the thread shall be invoked (thread_block[_locked]() or thread_block_with_timeout()). In between these two steps no functionality that uses the thread blocking API for this thread shall be used. When the caller determines that the condition for unblocking the thread occurred, it calls thread_unblock_locked() to unblock the thread. At that time one of locks that are held when calling thread_prepare_to_block() must be held. Usually that would be the client lock. In two cases it generally isn't, however, since the unblocking code doesn't know about the client lock: 1. When thread_block_with_timeout() had been used and the timeout occurs. 2. When thread_prepare_to_block() had been called with one or both of the \c B_CAN_INTERRUPT or \c B_KILL_CAN_INTERRUPT flags specified and someone calls thread_interrupt() that is supposed to wake up the thread. In either of these two cases only the scheduler lock is held by the unblocking code. A timeout can only happen after thread_block_with_timeout() has been called, but an interruption is possible at any time. The client code must deal with those situations. Generally blocking and unblocking threads proceed in the following manner: Blocking thread: - Acquire client lock. - Check client condition and decide whether blocking is necessary. - Modify some client data structure to indicate that this thread is now waiting. - Release client lock (unless client lock is the scheduler lock). - Block. - Acquire client lock (unless client lock is the scheduler lock). - Check client condition and compare with block result. E.g. if the wait was interrupted or timed out, but the client condition indicates success, it may be considered a success after all, since usually that happens when another thread concurrently changed the client condition and also tried to unblock the waiting thread. It is even necessary when that other thread changed the client data structures in a way that associate some resource with the unblocked thread, or otherwise the unblocked thread would have to reverse that here. - If still necessary -- i.e. not already taken care of by an unblocking thread -- modify some client structure to indicate that the thread is no longer waiting, so it isn't erroneously unblocked later. Unblocking thread: - Acquire client lock. - Check client condition and decide whether a blocked thread can be woken up. - Check the client data structure that indicates whether one or more threads are waiting and which thread(s) need(s) to be woken up. - Unblock respective thread(s). - Possibly change some client structure, so that an unblocked thread can decide whether a concurrent timeout/interruption can be ignored, or simply so that it doesn't have to do any more cleanup. Note that in the blocking thread the steps after blocking are strictly required only if timeouts or interruptions are possible. If they are not, the blocking thread can only be woken up explicitly by an unblocking thread, which could already take care of all the necessary client data structure modifications, so that the blocking thread wouldn't have to do that. Note that the client lock can but does not have to be a spinlock. A mutex, a semaphore, or anything that doesn't try to use the thread blocking API for the calling thread when releasing the lock is fine. In particular that means in principle thread_prepare_to_block() can be called with interrupts enabled. Care must be taken when the wait can be interrupted or can time out, especially with a client lock that uses the thread blocking API. After a blocked thread has been interrupted or the the time out occurred it cannot acquire the client lock (or any other lock using the thread blocking API) without first making sure that the thread doesn't still appear to be waiting to other client code. Otherwise another thread could try to unblock it which could erroneously unblock the thread while already waiting on the client lock. So usually when interruptions or timeouts are possible a spinlock needs to be involved. \param thread The current thread. \param flags The blocking flags. Relevant are: - \c B_CAN_INTERRUPT: The thread can be interrupted by any non-blocked signal. Implies \c B_KILL_CAN_INTERRUPT (specified or not). - \c B_KILL_CAN_INTERRUPT: The thread can be interrupted by a kill signal. \param type The type of object the thread will be blocked at. Informative/ for debugging purposes. Must be one of the \c THREAD_BLOCK_TYPE_* constants. \c THREAD_BLOCK_TYPE_OTHER implies that \a object is a string. \param object The object the thread will be blocked at. Informative/for debugging purposes. */ static inline void thread_prepare_to_block(Thread* thread, uint32 flags, uint32 type, const void* object) { thread->wait.flags = flags; thread->wait.type = type; thread->wait.object = object; atomic_set(&thread->wait.status, 1); // Set status last to guarantee that the other fields are initialized // when a thread is waiting. } /*! Unblocks the specified blocked thread. If the thread is no longer waiting (e.g. because thread_unblock_locked() has already been called in the meantime), this function does not have any effect. The caller must hold the scheduler lock and the client lock (might be the same). \param thread The thread to be unblocked. \param status The unblocking status. That's what the unblocked thread's call to thread_block_locked() will return. */ static inline void thread_unblock_locked(Thread* thread, status_t status) { if (atomic_test_and_set(&thread->wait.status, status, 1) != 1) return; // wake up the thread, if it is sleeping if (thread->state == B_THREAD_WAITING) scheduler_enqueue_in_run_queue(thread); } /*! Interrupts the specified blocked thread, if possible. The function checks whether the thread can be interrupted and, if so, calls \code thread_unblock_locked(thread, B_INTERRUPTED) \endcode. Otherwise the function is a no-op. The caller must hold the scheduler lock. Normally thread_unblock_locked() also requires the client lock to be held, but in this case the caller usually doesn't know it. This implies that the client code needs to take special care, if waits are interruptible. See thread_prepare_to_block() for more information. \param thread The thread to be interrupted. \param kill If \c false, the blocked thread is only interrupted, when the flag \c B_CAN_INTERRUPT was specified for the blocked thread. If \c true, it is only interrupted, when at least one of the flags \c B_CAN_INTERRUPT or \c B_KILL_CAN_INTERRUPT was specified for the blocked thread. \return \c B_OK, if the thread is interruptible and thread_unblock_locked() was called, \c B_NOT_ALLOWED otherwise. \c B_OK doesn't imply that the thread actually has been interrupted -- it could have been unblocked before already. */ static inline status_t thread_interrupt(Thread* thread, bool kill) { if (thread_is_blocked(thread)) { if ((thread->wait.flags & B_CAN_INTERRUPT) != 0 || (kill && (thread->wait.flags & B_KILL_CAN_INTERRUPT) != 0)) { thread_unblock_locked(thread, B_INTERRUPTED); return B_OK; } } return B_NOT_ALLOWED; } static inline void thread_pin_to_current_cpu(Thread* thread) { thread->pinned_to_cpu++; } static inline void thread_unpin_from_current_cpu(Thread* thread) { thread->pinned_to_cpu--; } static inline void thread_prepare_suspend() { Thread* thread = thread_get_current_thread(); thread->going_to_suspend = true; } static inline void thread_suspend(bool alreadyPrepared = false) { Thread* thread = thread_get_current_thread(); if (!alreadyPrepared) thread_prepare_suspend(); cpu_status state = disable_interrupts(); acquire_spinlock(&thread->scheduler_lock); if (thread->going_to_suspend) scheduler_reschedule(B_THREAD_SUSPENDED); release_spinlock(&thread->scheduler_lock); restore_interrupts(state); } static inline void thread_continue(Thread* thread) { thread->going_to_suspend = false; cpu_status state = disable_interrupts(); acquire_spinlock(&thread->scheduler_lock); if (thread->state == B_THREAD_SUSPENDED) scheduler_enqueue_in_run_queue(thread); release_spinlock(&thread->scheduler_lock); restore_interrupts(state); } #endif /* _THREAD_H */