diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index 531f3a06cd..9e02c5c0b8 100644 --- a/Kernel/CMakeLists.txt +++ b/Kernel/CMakeLists.txt @@ -170,6 +170,7 @@ set(KERNEL_SOURCES Tasks/FinalizerTask.cpp Tasks/SyncTask.cpp Thread.cpp + ThreadBlockers.cpp ThreadTracer.cpp Time/APICTimer.cpp Time/HPET.cpp diff --git a/Kernel/Devices/Device.cpp b/Kernel/Devices/Device.cpp index eda56e1cfe..5652de33b8 100644 --- a/Kernel/Devices/Device.cpp +++ b/Kernel/Devices/Device.cpp @@ -95,6 +95,8 @@ void Device::process_next_queued_request(Badge, const AsyncD if (next_request) next_request->start(); + + evaluate_block_conditions(); } } diff --git a/Kernel/Devices/KeyboardDevice.cpp b/Kernel/Devices/KeyboardDevice.cpp index 4b5aae2645..7e97cb109f 100644 --- a/Kernel/Devices/KeyboardDevice.cpp +++ b/Kernel/Devices/KeyboardDevice.cpp @@ -272,6 +272,8 @@ void KeyboardDevice::key_state_changed(u8 scan_code, bool pressed) } m_has_e0_prefix = false; + + evaluate_block_conditions(); } void KeyboardDevice::handle_irq(const RegisterState&) diff --git a/Kernel/Devices/PS2MouseDevice.cpp b/Kernel/Devices/PS2MouseDevice.cpp index c05b30747c..1aa5222ee0 100644 --- a/Kernel/Devices/PS2MouseDevice.cpp +++ b/Kernel/Devices/PS2MouseDevice.cpp @@ -89,8 +89,11 @@ void PS2MouseDevice::irq_handle_byte_read(u8 byte) auto mouse_packet = backdoor->receive_mouse_packet(); if (mouse_packet.has_value()) { m_entropy_source.add_random_event(mouse_packet.value()); - ScopedSpinLock lock(m_queue_lock); - m_queue.enqueue(mouse_packet.value()); + { + ScopedSpinLock lock(m_queue_lock); + m_queue.enqueue(mouse_packet.value()); + } + evaluate_block_conditions(); } return; } @@ -102,8 +105,11 @@ void PS2MouseDevice::irq_handle_byte_read(u8 byte) #endif m_entropy_source.add_random_event(m_data.dword); - ScopedSpinLock lock(m_queue_lock); - m_queue.enqueue(parse_data_packet(m_data)); + { + ScopedSpinLock lock(m_queue_lock); + m_queue.enqueue(parse_data_packet(m_data)); + } + evaluate_block_conditions(); }; ASSERT(m_data_state < sizeof(m_data.bytes) / sizeof(m_data.bytes[0])); diff --git a/Kernel/DoubleBuffer.cpp b/Kernel/DoubleBuffer.cpp index 4b9285e327..8c9a01867d 100644 --- a/Kernel/DoubleBuffer.cpp +++ b/Kernel/DoubleBuffer.cpp @@ -70,6 +70,8 @@ ssize_t DoubleBuffer::write(const UserOrKernelBuffer& data, size_t size) compute_lockfree_metadata(); if (!data.read(write_ptr, bytes_to_write)) return -EFAULT; + if (m_unblock_callback && !m_empty) + m_unblock_callback(); return (ssize_t)bytes_to_write; } @@ -88,6 +90,8 @@ ssize_t DoubleBuffer::read(UserOrKernelBuffer& data, size_t size) return -EFAULT; m_read_buffer_index += nread; compute_lockfree_metadata(); + if (m_unblock_callback && m_space_for_writing > 0) + m_unblock_callback(); return (ssize_t)nread; } diff --git a/Kernel/DoubleBuffer.h b/Kernel/DoubleBuffer.h index b855fb8853..6db3718b8e 100644 --- a/Kernel/DoubleBuffer.h +++ b/Kernel/DoubleBuffer.h @@ -29,6 +29,7 @@ #include #include #include +#include #include namespace Kernel { @@ -53,6 +54,12 @@ public: size_t space_for_writing() const { return m_space_for_writing; } + void set_unblock_callback(Function callback) + { + ASSERT(!m_unblock_callback); + m_unblock_callback = move(callback); + } + private: void flip(); void compute_lockfree_metadata(); @@ -68,6 +75,7 @@ private: InnerBuffer m_buffer2; KBuffer m_storage; + Function m_unblock_callback; size_t m_capacity { 0 }; size_t m_read_buffer_index { 0 }; size_t m_space_for_writing { 0 }; diff --git a/Kernel/FileSystem/FIFO.cpp b/Kernel/FileSystem/FIFO.cpp index 2aa6c55cd9..7878f4aa81 100644 --- a/Kernel/FileSystem/FIFO.cpp +++ b/Kernel/FileSystem/FIFO.cpp @@ -95,6 +95,11 @@ FIFO::FIFO(uid_t uid) LOCKER(all_fifos().lock()); all_fifos().resource().set(this); m_fifo_id = ++s_next_fifo_id; + + // Use the same block condition for read and write + m_buffer.set_unblock_callback([this]() { + evaluate_block_conditions(); + }); } FIFO::~FIFO() @@ -116,6 +121,8 @@ void FIFO::attach(Direction direction) klog() << "open writer (" << m_writers << ")"; #endif } + + evaluate_block_conditions(); } void FIFO::detach(Direction direction) @@ -133,6 +140,8 @@ void FIFO::detach(Direction direction) ASSERT(m_writers); --m_writers; } + + evaluate_block_conditions(); } bool FIFO::can_read(const FileDescription&, size_t) const diff --git a/Kernel/FileSystem/File.cpp b/Kernel/FileSystem/File.cpp index 594652d643..6a192626db 100644 --- a/Kernel/FileSystem/File.cpp +++ b/Kernel/FileSystem/File.cpp @@ -31,6 +31,7 @@ namespace Kernel { File::File() + : m_block_condition(*this) { } diff --git a/Kernel/FileSystem/File.h b/Kernel/FileSystem/File.h index b2400b803b..ed56f1cc33 100644 --- a/Kernel/FileSystem/File.h +++ b/Kernel/FileSystem/File.h @@ -39,6 +39,36 @@ namespace Kernel { +class File; + +class FileBlockCondition : public Thread::BlockCondition { +public: + FileBlockCondition(File& file) + : m_file(file) + { + } + + virtual bool should_add_blocker(Thread::Blocker& b, void* data) override + { + ASSERT(b.blocker_type() == Thread::Blocker::Type::File); + auto& blocker = static_cast(b); + return !blocker.unblock(true, data); + } + + void unblock() + { + ScopedSpinLock lock(m_lock); + do_unblock([&](auto& b, void* data) { + ASSERT(b.blocker_type() == Thread::Blocker::Type::File); + auto& blocker = static_cast(b); + return blocker.unblock(false, data); + }); + } + +private: + File& m_file; +}; + // File is the base class for anything that can be referenced by a FileDescription. // // The most important functions in File are: @@ -103,8 +133,27 @@ public: virtual bool is_character_device() const { return false; } virtual bool is_socket() const { return false; } + virtual FileBlockCondition& block_condition() { return m_block_condition; } + protected: File(); + + void evaluate_block_conditions() + { + if (Processor::current().in_irq()) { + // If called from an IRQ handler we need to delay evaluation + // and unblocking of waiting threads + Processor::deferred_call_queue([this]() { + ASSERT(!Processor::current().in_irq()); + evaluate_block_conditions(); + }); + } else { + block_condition().unblock(); + } + } + +private: + FileBlockCondition m_block_condition; }; } diff --git a/Kernel/FileSystem/FileDescription.cpp b/Kernel/FileSystem/FileDescription.cpp index ae38eccacd..ced161052a 100644 --- a/Kernel/FileSystem/FileDescription.cpp +++ b/Kernel/FileSystem/FileDescription.cpp @@ -75,6 +75,26 @@ FileDescription::~FileDescription() m_inode = nullptr; } +Thread::FileBlocker::BlockFlags FileDescription::should_unblock(Thread::FileBlocker::BlockFlags block_flags) const +{ + u32 unblock_flags = (u32)Thread::FileBlocker::BlockFlags::None; + if (((u32)block_flags & (u32)Thread::FileBlocker::BlockFlags::Read) && can_read()) + unblock_flags |= (u32)Thread::FileBlocker::BlockFlags::Read; + if (((u32)block_flags & (u32)Thread::FileBlocker::BlockFlags::Write) && can_write()) + unblock_flags |= (u32)Thread::FileBlocker::BlockFlags::Write; + // TODO: Implement Thread::FileBlocker::BlockFlags::Exception + + if ((u32)block_flags & (u32)Thread::FileBlocker::BlockFlags::SocketFlags) { + auto* sock = socket(); + ASSERT(sock); + if (((u32)block_flags & (u32)Thread::FileBlocker::BlockFlags::Accept) && sock->can_accept()) + unblock_flags |= (u32)Thread::FileBlocker::BlockFlags::Accept; + if (((u32)block_flags & (u32)Thread::FileBlocker::BlockFlags::Connect) && sock->setup_state() == Socket::SetupState::Completed) + unblock_flags |= (u32)Thread::FileBlocker::BlockFlags::Connect; + } + return (Thread::FileBlocker::BlockFlags)unblock_flags; +} + KResult FileDescription::stat(::stat& buffer) { LOCKER(m_lock); @@ -113,6 +133,7 @@ off_t FileDescription::seek(off_t offset, int whence) // FIXME: Return -EINVAL if attempting to seek past the end of a seekable device. m_current_offset = new_offset; + evaluate_block_conditions(); return m_current_offset; } @@ -124,8 +145,11 @@ KResultOr FileDescription::read(UserOrKernelBuffer& buffer, size_t count if (new_offset.has_overflow()) return -EOVERFLOW; auto nread_or_error = m_file->read(*this, offset(), buffer, count); - if (!nread_or_error.is_error() && m_file->is_seekable()) - m_current_offset += nread_or_error.value(); + if (!nread_or_error.is_error()) { + if (m_file->is_seekable()) + m_current_offset += nread_or_error.value(); + evaluate_block_conditions(); + } return nread_or_error; } @@ -137,8 +161,11 @@ KResultOr FileDescription::write(const UserOrKernelBuffer& data, size_t if (new_offset.has_overflow()) return -EOVERFLOW; auto nwritten_or_error = m_file->write(*this, offset(), data, size); - if (!nwritten_or_error.is_error() && m_file->is_seekable()) - m_current_offset += nwritten_or_error.value(); + if (!nwritten_or_error.is_error()) { + if (m_file->is_seekable()) + m_current_offset += nwritten_or_error.value(); + evaluate_block_conditions(); + } return nwritten_or_error; } @@ -340,4 +367,9 @@ KResult FileDescription::chown(uid_t uid, gid_t gid) return m_file->chown(*this, uid, gid); } +FileBlockCondition& FileDescription::block_condition() +{ + return m_file->block_condition(); +} + } diff --git a/Kernel/FileSystem/FileDescription.h b/Kernel/FileSystem/FileDescription.h index 60d85db945..03d281c12e 100644 --- a/Kernel/FileSystem/FileDescription.h +++ b/Kernel/FileSystem/FileDescription.h @@ -45,6 +45,8 @@ public: static NonnullRefPtr create(File&); ~FileDescription(); + Thread::FileBlocker::BlockFlags should_unblock(Thread::FileBlocker::BlockFlags) const; + bool is_readable() const { return m_readable; } bool is_writable() const { return m_writable; } @@ -130,11 +132,18 @@ public: KResult chown(uid_t, gid_t); + FileBlockCondition& block_condition(); + private: friend class VFS; explicit FileDescription(File&); FileDescription(FIFO&, FIFO::Direction); + void evaluate_block_conditions() + { + block_condition().unblock(); + } + RefPtr m_custody; RefPtr m_inode; NonnullRefPtr m_file; diff --git a/Kernel/FileSystem/InodeFile.cpp b/Kernel/FileSystem/InodeFile.cpp index bc4d243957..b1c3b2b45c 100644 --- a/Kernel/FileSystem/InodeFile.cpp +++ b/Kernel/FileSystem/InodeFile.cpp @@ -47,8 +47,10 @@ InodeFile::~InodeFile() KResultOr InodeFile::read(FileDescription& description, size_t offset, UserOrKernelBuffer& buffer, size_t count) { ssize_t nread = m_inode->read_bytes(offset, count, buffer, &description); - if (nread > 0) + if (nread > 0) { Thread::current()->did_file_read(nread); + evaluate_block_conditions(); + } if (nread < 0) return KResult(nread); return nread; @@ -60,6 +62,7 @@ KResultOr InodeFile::write(FileDescription& description, size_t offset, if (nwritten > 0) { m_inode->set_mtime(kgettimeofday().tv_sec); Thread::current()->did_file_write(nwritten); + evaluate_block_conditions(); } if (nwritten < 0) return KResult(nwritten); diff --git a/Kernel/FileSystem/InodeWatcher.cpp b/Kernel/FileSystem/InodeWatcher.cpp index 043396986e..d4d698440b 100644 --- a/Kernel/FileSystem/InodeWatcher.cpp +++ b/Kernel/FileSystem/InodeWatcher.cpp @@ -78,6 +78,7 @@ KResultOr InodeWatcher::read(FileDescription&, size_t, UserOrKernelBuffe }); if (nwritten < 0) return KResult(nwritten); + evaluate_block_conditions(); return bytes_to_write; } @@ -97,18 +98,21 @@ void InodeWatcher::notify_inode_event(Badge, Event::Type event_type) { LOCKER(m_lock); m_queue.enqueue({ event_type }); + evaluate_block_conditions(); } void InodeWatcher::notify_child_added(Badge, const InodeIdentifier& child_id) { LOCKER(m_lock); m_queue.enqueue({ Event::Type::ChildAdded, child_id.index() }); + evaluate_block_conditions(); } void InodeWatcher::notify_child_removed(Badge, const InodeIdentifier& child_id) { LOCKER(m_lock); m_queue.enqueue({ Event::Type::ChildRemoved, child_id.index() }); + evaluate_block_conditions(); } } diff --git a/Kernel/FileSystem/Plan9FileSystem.cpp b/Kernel/FileSystem/Plan9FileSystem.cpp index 975883df67..3f7f17dd52 100644 --- a/Kernel/FileSystem/Plan9FileSystem.cpp +++ b/Kernel/FileSystem/Plan9FileSystem.cpp @@ -36,6 +36,7 @@ NonnullRefPtr Plan9FS::create(FileDescription& file_description) Plan9FS::Plan9FS(FileDescription& file_description) : FileBackedFS(file_description) + , m_completion_blocker(*this) { } @@ -216,6 +217,8 @@ private: bool Plan9FS::initialize() { + ensure_thread(); + Message version_message { *this, Message::Type::Tversion }; version_message << (u32)m_max_message_size << "9P2000.L"; @@ -412,7 +415,97 @@ const KBuffer& Plan9FS::Message::build() return m_built.buffer; } -KResult Plan9FS::post_message(Message& message) +Plan9FS::ReceiveCompletion::ReceiveCompletion(u16 tag) + : tag(tag) +{ +} + +Plan9FS::ReceiveCompletion::~ReceiveCompletion() +{ +} + +bool Plan9FS::Blocker::unblock(u16 tag) +{ + { + ScopedSpinLock lock(m_lock); + if (m_did_unblock) + return false; + m_did_unblock = true; + + if (m_completion->tag != tag) + return false; + if (!m_completion->result.is_error()) + m_message = move(*m_completion->message); + } + return unblock(); +} + +void Plan9FS::Blocker::not_blocking(bool) +{ + { + ScopedSpinLock lock(m_lock); + if (m_did_unblock) + return; + } + + m_fs.m_completion_blocker.try_unblock(*this); +} + +bool Plan9FS::Blocker::is_completed() const +{ + ScopedSpinLock lock(m_completion->lock); + return m_completion->completed; +} + +bool Plan9FS::Plan9FSBlockCondition::should_add_blocker(Thread::Blocker& b, void*) +{ + // NOTE: m_lock is held already! + auto& blocker = static_cast(b); + return !blocker.is_completed(); +} + +void Plan9FS::Plan9FSBlockCondition::unblock_completed(u16 tag) +{ + unblock([&](Thread::Blocker& b, void*) { + ASSERT(b.blocker_type() == Thread::Blocker::Type::Plan9FS); + auto& blocker = static_cast(b); + return blocker.unblock(tag); + }); +} + +void Plan9FS::Plan9FSBlockCondition::unblock_all() +{ + BlockCondition::unblock_all([&](Thread::Blocker& b, void*) { + ASSERT(b.blocker_type() == Thread::Blocker::Type::Plan9FS); + auto& blocker = static_cast(b); + return blocker.unblock(); + }); +} + +void Plan9FS::Plan9FSBlockCondition::try_unblock(Plan9FS::Blocker& blocker) +{ + if (m_fs.is_complete(*blocker.completion())) { + ScopedSpinLock lock(m_lock); + blocker.unblock(blocker.completion()->tag); + } +} + +bool Plan9FS::is_complete(const ReceiveCompletion& completion) +{ + LOCKER(m_lock); + if (m_completions.contains(completion.tag)) { + // If it's still in the map then it can't be complete + ASSERT(!completion.completed); + return false; + } + + // if it's not in the map anymore, it must be complete. But we MUST + // hold m_lock to be able to check completion.completed! + ASSERT(completion.completed); + return true; +} + +KResult Plan9FS::post_message(Message& message, RefPtr completion) { auto& buffer = message.build(); const u8* data = buffer.data(); @@ -421,9 +514,21 @@ KResult Plan9FS::post_message(Message& message) LOCKER(m_send_lock); + if (completion) { + // Save the completion record *before* we send the message. This + // ensures that it exists when the thread reads the response + LOCKER(m_lock); + auto tag = completion->tag; + m_completions.set(tag, completion.release_nonnull()); + // TODO: What if there is a collision? Do we need to wait until + // the existing record with the tag completes before queueing + // this one? + } + while (size > 0) { if (!description.can_write()) { - if (Thread::current()->block(nullptr, description).was_interrupted()) + auto unblock_flags = Thread::FileBlocker::BlockFlags::None; + if (Thread::current()->block(nullptr, description, unblock_flags).was_interrupted()) return KResult(-EINTR); } auto data_buffer = UserOrKernelBuffer::for_kernel_buffer(const_cast(data)); @@ -443,7 +548,8 @@ KResult Plan9FS::do_read(u8* data, size_t size) auto& description = file_description(); while (size > 0) { if (!description.can_read()) { - if (Thread::current()->block(nullptr, description).was_interrupted()) + auto unblock_flags = Thread::FileBlocker::BlockFlags::None; + if (Thread::current()->block(nullptr, description, unblock_flags).was_interrupted()) return KResult(-EINTR); } auto data_buffer = UserOrKernelBuffer::for_kernel_buffer(data); @@ -461,9 +567,6 @@ KResult Plan9FS::do_read(u8* data, size_t size) KResult Plan9FS::read_and_dispatch_one_message() { - ASSERT(m_someone_is_reading); - // That someone is us. - struct [[gnu::packed]] Header { u32 size; @@ -485,112 +588,42 @@ KResult Plan9FS::read_and_dispatch_one_message() LOCKER(m_lock); auto optional_completion = m_completions.get(header.tag); - if (!optional_completion.has_value()) { - if (m_tags_to_ignore.contains(header.tag)) { - m_tags_to_ignore.remove(header.tag); - } else { - dbg() << "Received a 9p message of type " << header.type << " with an unexpected tag " << header.tag << ", dropping"; - } - return KSuccess; + if (optional_completion.has_value()) { + auto completion = optional_completion.value(); + ScopedSpinLock lock(completion->lock); + completion->result = KSuccess; + completion->message = new Message { move(buffer) }; + completion->completed = true; + + m_completions.remove(header.tag); + m_completion_blocker.unblock_completed(header.tag); + } else { + dbg() << "Received a 9p message of type " << header.type << " with an unexpected tag " << header.tag << ", dropping"; } - ReceiveCompletion& completion = *optional_completion.value(); - completion.result = KSuccess; - completion.message = Message { move(buffer) }; - completion.completed = true; - m_completions.remove(header.tag); return KSuccess; } -bool Plan9FS::Blocker::should_unblock(Thread&) -{ - if (m_completion.completed) - return true; - - bool someone_else_is_reading = m_completion.fs.m_someone_is_reading.exchange(true); - if (!someone_else_is_reading) { - // We're gonna start reading ourselves; unblock. - return true; - } - return false; -} - -KResult Plan9FS::wait_for_specific_message(u16 tag, Message& out_message) -{ - KResult result = KSuccess; - ReceiveCompletion completion { *this, out_message, result, false }; - - { - LOCKER(m_lock); - m_completions.set(tag, &completion); - } - - // Block until either: - // * Someone else reads the message we're waiting for, and hands it to us; - // * Or we become the one to read and dispatch messages. - if (Thread::current()->block(nullptr, completion).was_interrupted()) { - LOCKER(m_lock); - m_completions.remove(tag); - return KResult(-EINTR); - } - - // See for which reason we woke up. - if (completion.completed) { - // Somebody else completed it for us; nothing further to do. - return result; - } - - while (!completion.completed && result.is_success()) { - result = read_and_dispatch_one_message(); - } - - if (result.is_error()) { - // If we fail to read, wake up everyone with an error. - LOCKER(m_lock); - - for (auto& it : m_completions) { - it.value->result = result; - it.value->completed = true; - } - m_completions.clear(); - } - - // Wake up someone else, if anyone is interested... - m_someone_is_reading = false; - // ...and return. - return result; -} - KResult Plan9FS::post_message_and_explicitly_ignore_reply(Message& message) { - auto tag = message.tag(); - { - LOCKER(m_lock); - m_tags_to_ignore.set(tag); - } - - auto result = post_message(message); - if (result.is_error()) { - LOCKER(m_lock); - m_tags_to_ignore.remove(tag); - } - - return result; + return post_message(message, {}); } -KResult Plan9FS::post_message_and_wait_for_a_reply(Message& message, bool auto_convert_error_reply_to_error) +KResult Plan9FS::post_message_and_wait_for_a_reply(Message& message) { auto request_type = message.type(); auto tag = message.tag(); - auto result = post_message(message); - if (result.is_error()) - return result; - result = wait_for_specific_message(tag, message); + auto completion = adopt(*new ReceiveCompletion(tag)); + auto result = post_message(message, completion); if (result.is_error()) return result; + if (Thread::current()->block(nullptr, *this, message, completion).was_interrupted()) + return KResult(-EINTR); - if (!auto_convert_error_reply_to_error) - return KSuccess; + if (completion->result.is_error()) { + dbg() << "Plan9FS: Message was aborted with error " << completion->result; + return KResult(-EIO); + } auto reply_type = message.type(); @@ -624,6 +657,39 @@ ssize_t Plan9FS::adjust_buffer_size(ssize_t size) const return min(size, max_size); } +void Plan9FS::thread_main() +{ + dbg() << "Plan9FS: Thread running"; + do { + auto result = read_and_dispatch_one_message(); + if (result.is_error()) { + // If we fail to read, wake up everyone with an error. + LOCKER(m_lock); + + for (auto& it : m_completions) { + it.value->result = result; + it.value->completed = true; + } + m_completions.clear(); + m_completion_blocker.unblock_all(); + dbg() << "Plan9FS: Thread terminating, error reading"; + return; + } + } while (!m_thread_shutdown.load(AK::MemoryOrder::memory_order_relaxed)); + dbg() << "Plan9FS: Thread terminating"; +} + +void Plan9FS::ensure_thread() +{ + ScopedSpinLock lock(m_thread_lock); + if (!m_thread_running.exchange(true, AK::MemoryOrder::memory_order_acq_rel)) { + Process::create_kernel_process(m_thread, "Plan9FS", [&]() { + thread_main(); + m_thread_running.store(false, AK::MemoryOrder::memory_order_release); + }); + } +} + Plan9FSInode::Plan9FSInode(Plan9FS& fs, u32 fid) : Inode(fs, fid) { diff --git a/Kernel/FileSystem/Plan9FileSystem.h b/Kernel/FileSystem/Plan9FileSystem.h index 5851cbe8dd..d1de37a5c7 100644 --- a/Kernel/FileSystem/Plan9FileSystem.h +++ b/Kernel/FileSystem/Plan9FileSystem.h @@ -68,38 +68,86 @@ public: private: Plan9FS(FileDescription&); - struct ReceiveCompletion { - Plan9FS& fs; - Message& message; - KResult& result; - Atomic completed; + class Blocker; + + class Plan9FSBlockCondition : public Thread::BlockCondition { + public: + Plan9FSBlockCondition(Plan9FS& fs) + : m_fs(fs) + { + } + + void unblock_completed(u16); + void unblock_all(); + void try_unblock(Blocker&); + + protected: + virtual bool should_add_blocker(Thread::Blocker&, void*) override; + + private: + Plan9FS& m_fs; + mutable SpinLock m_lock; + }; + + struct ReceiveCompletion : public RefCounted { + mutable SpinLock lock; + bool completed { false }; + const u16 tag; + OwnPtr message; + KResult result { KSuccess }; + + ReceiveCompletion(u16 tag); + ~ReceiveCompletion(); }; class Blocker final : public Thread::Blocker { public: - Blocker(ReceiveCompletion& completion) - : m_completion(completion) + Blocker(Plan9FS& fs, Message& message, NonnullRefPtr completion) + : m_fs(fs) + , m_message(message) + , m_completion(move(completion)) { + set_block_condition(fs.m_completion_blocker); } - virtual bool should_unblock(Thread&) override; virtual const char* state_string() const override { return "Waiting"; } + virtual Type blocker_type() const override { return Type::Plan9FS; } + virtual void not_blocking(bool) override; + + const NonnullRefPtr& completion() const { return m_completion; } + u16 tag() const { return m_completion->tag; } + bool is_completed() const; + + bool unblock() + { + unblock_from_blocker(); + return true; + } + + bool unblock(u16 tag); private: - ReceiveCompletion& m_completion; + Plan9FS& m_fs; + Message& m_message; + NonnullRefPtr m_completion; + bool m_did_unblock { false }; }; + friend class Blocker; virtual const char* class_name() const override { return "Plan9FS"; } - KResult post_message(Message&); + bool is_complete(const ReceiveCompletion&); + KResult post_message(Message&, RefPtr); KResult do_read(u8* buffer, size_t); KResult read_and_dispatch_one_message(); - KResult wait_for_specific_message(u16 tag, Message& out_message); - KResult post_message_and_wait_for_a_reply(Message&, bool auto_convert_error_reply_to_error = true); + KResult post_message_and_wait_for_a_reply(Message&); KResult post_message_and_explicitly_ignore_reply(Message&); ProtocolVersion parse_protocol_version(const StringView&) const; ssize_t adjust_buffer_size(ssize_t size) const; + void thread_main(); + void ensure_thread(); + RefPtr m_root_inode; Atomic m_next_tag { (u16)-1 }; Atomic m_next_fid { 1 }; @@ -108,9 +156,13 @@ private: size_t m_max_message_size { 4 * KiB }; Lock m_send_lock { "Plan9FS send" }; - Atomic m_someone_is_reading { false }; - HashMap m_completions; - HashTable m_tags_to_ignore; + Plan9FSBlockCondition m_completion_blocker; + HashMap> m_completions; + + SpinLock m_thread_lock; + RefPtr m_thread; + Atomic m_thread_running { false }; + Atomic m_thread_shutdown { false }; }; class Plan9FSInode final : public Inode { diff --git a/Kernel/Forward.h b/Kernel/Forward.h index 66afa30896..8711757201 100644 --- a/Kernel/Forward.h +++ b/Kernel/Forward.h @@ -51,6 +51,7 @@ class PerformanceEventBuffer; class PhysicalPage; class PhysicalRegion; class Process; +class ProcessGroup; class ThreadTracer; class Range; class RangeAllocator; diff --git a/Kernel/Net/IPv4Socket.cpp b/Kernel/Net/IPv4Socket.cpp index a6011acc71..182a2078f6 100644 --- a/Kernel/Net/IPv4Socket.cpp +++ b/Kernel/Net/IPv4Socket.cpp @@ -138,6 +138,7 @@ KResult IPv4Socket::listen(size_t backlog) set_backlog(backlog); m_role = Role::Listener; + evaluate_block_conditions(); #ifdef IPV4_SOCKET_DEBUG dbg() << "IPv4Socket{" << this << "} listening with backlog=" << backlog; @@ -262,10 +263,11 @@ KResultOr IPv4Socket::receive_byte_buffered(FileDescription& description return KResult(-EAGAIN); locker.unlock(); - auto res = Thread::current()->block(nullptr, description); + auto unblocked_flags = Thread::FileDescriptionBlocker::BlockFlags::None; + auto res = Thread::current()->block(nullptr, description, unblocked_flags); locker.lock(); - if (!m_can_read) { + if (!((u32)unblocked_flags & (u32)Thread::FileDescriptionBlocker::BlockFlags::Read)) { if (res.was_interrupted()) return KResult(-EINTR); @@ -279,7 +281,7 @@ KResultOr IPv4Socket::receive_byte_buffered(FileDescription& description if (nreceived > 0) Thread::current()->did_ipv4_socket_read((size_t)nreceived); - m_can_read = !m_receive_buffer.is_empty(); + set_can_read(!m_receive_buffer.is_empty()); return nreceived; } @@ -299,7 +301,7 @@ KResultOr IPv4Socket::receive_packet_buffered(FileDescription& descripti if (!m_receive_queue.is_empty()) { packet = m_receive_queue.take_first(); - m_can_read = !m_receive_queue.is_empty(); + set_can_read(!m_receive_queue.is_empty()); #ifdef IPV4_SOCKET_DEBUG dbg() << "IPv4Socket(" << this << "): recvfrom without blocking " << packet.data.value().size() << " bytes, packets in queue: " << m_receive_queue.size(); #endif @@ -312,10 +314,11 @@ KResultOr IPv4Socket::receive_packet_buffered(FileDescription& descripti } locker.unlock(); - auto res = Thread::current()->block(nullptr, description); + auto unblocked_flags = Thread::FileDescriptionBlocker::BlockFlags::None; + auto res = Thread::current()->block(nullptr, description, unblocked_flags); locker.lock(); - if (!m_can_read) { + if (!((u32)unblocked_flags & (u32)Thread::FileDescriptionBlocker::BlockFlags::Read)) { if (res.was_interrupted()) return KResult(-EINTR); @@ -325,7 +328,7 @@ KResultOr IPv4Socket::receive_packet_buffered(FileDescription& descripti ASSERT(m_can_read); ASSERT(!m_receive_queue.is_empty()); packet = m_receive_queue.take_first(); - m_can_read = !m_receive_queue.is_empty(); + set_can_read(!m_receive_queue.is_empty()); #ifdef IPV4_SOCKET_DEBUG dbg() << "IPv4Socket(" << this << "): recvfrom with blocking " << packet.data.value().size() << " bytes, packets in queue: " << m_receive_queue.size(); #endif @@ -411,14 +414,14 @@ bool IPv4Socket::did_receive(const IPv4Address& source_address, u16 source_port, ssize_t nwritten = m_receive_buffer.write(scratch_buffer, nreceived_or_error.value()); if (nwritten < 0) return false; - m_can_read = !m_receive_buffer.is_empty(); + set_can_read(!m_receive_buffer.is_empty()); } else { if (m_receive_queue.size() > 2000) { dbg() << "IPv4Socket(" << this << "): did_receive refusing packet since queue is full."; return false; } m_receive_queue.append({ source_address, source_port, packet_timestamp, move(packet) }); - m_can_read = true; + set_can_read(true); } m_bytes_received += packet_size; #ifdef IPV4_SOCKET_DEBUG @@ -625,7 +628,14 @@ KResult IPv4Socket::close() void IPv4Socket::shut_down_for_reading() { Socket::shut_down_for_reading(); - m_can_read = true; + set_can_read(true); +} + +void IPv4Socket::set_can_read(bool value) +{ + m_can_read = value; + if (value) + evaluate_block_conditions(); } } diff --git a/Kernel/Net/IPv4Socket.h b/Kernel/Net/IPv4Socket.h index 2bee154361..67c339a67a 100644 --- a/Kernel/Net/IPv4Socket.h +++ b/Kernel/Net/IPv4Socket.h @@ -113,6 +113,8 @@ private: KResultOr receive_byte_buffered(FileDescription&, UserOrKernelBuffer& buffer, size_t buffer_length, int flags, Userspace, Userspace); KResultOr receive_packet_buffered(FileDescription&, UserOrKernelBuffer& buffer, size_t buffer_length, int flags, Userspace, Userspace, timeval&); + void set_can_read(bool); + IPv4Address m_local_address; IPv4Address m_peer_address; diff --git a/Kernel/Net/LocalSocket.cpp b/Kernel/Net/LocalSocket.cpp index 0b4d4fc658..345cb7db05 100644 --- a/Kernel/Net/LocalSocket.cpp +++ b/Kernel/Net/LocalSocket.cpp @@ -68,6 +68,13 @@ LocalSocket::LocalSocket(int type) m_prebind_gid = current_process->gid(); m_prebind_mode = 0666; + m_for_client.set_unblock_callback([this]() { + evaluate_block_conditions(); + }); + m_for_server.set_unblock_callback([this]() { + evaluate_block_conditions(); + }); + #ifdef DEBUG_LOCAL_SOCKET dbg() << "LocalSocket{" << this << "} created with type=" << type; #endif @@ -170,22 +177,23 @@ KResult LocalSocket::connect(FileDescription& description, Userspaceinode()->socket(); auto result = peer->queue_connection_from(*this); if (result.is_error()) { - m_connect_side_role = Role::None; + set_connect_side_role(Role::None); return result; } if (is_connected()) { - m_connect_side_role = Role::Connected; + set_connect_side_role(Role::Connected); return KSuccess; } - if (Thread::current()->block(nullptr, description).was_interrupted()) { - m_connect_side_role = Role::None; + auto unblock_flags = Thread::FileDescriptionBlocker::BlockFlags::None; + if (Thread::current()->block(nullptr, description, unblock_flags).was_interrupted()) { + set_connect_side_role(Role::None); return KResult(-EINTR); } @@ -193,11 +201,11 @@ KResult LocalSocket::connect(FileDescription& description, Userspace>> s_arp_table; +class ARPTableBlocker : public Thread::Blocker { +public: + ARPTableBlocker(IPv4Address ip_addr, Optional& addr); + + virtual const char* state_string() const override { return "Routing (ARP)"; } + virtual Type blocker_type() const override { return Type::Routing; } + virtual bool should_block() override { return m_should_block; } + + virtual void not_blocking(bool) override; + + bool unblock(bool from_add_blocker, const IPv4Address& ip_addr, const MACAddress& addr) + { + if (m_ip_addr != ip_addr) + return false; + + { + ScopedSpinLock lock(m_lock); + if (m_did_unblock) + return false; + m_did_unblock = true; + m_addr = addr; + } + + if (!from_add_blocker) + unblock_from_blocker(); + return true; + } + + const IPv4Address& ip_addr() const { return m_ip_addr; } + +private: + const IPv4Address m_ip_addr; + Optional& m_addr; + bool m_did_unblock { false }; + bool m_should_block { true }; +}; + +class ARPTableBlockCondition : public Thread::BlockCondition { +public: + void unblock(const IPv4Address& ip_addr, const MACAddress& addr) + { + unblock_all([&](auto& b, void*) { + ASSERT(b.blocker_type() == Thread::Blocker::Type::Routing); + auto& blocker = static_cast(b); + return blocker.unblock(false, ip_addr, addr); + }); + } + +protected: + virtual bool should_add_blocker(Thread::Blocker& b, void*) override + { + ASSERT(b.blocker_type() == Thread::Blocker::Type::Routing); + auto& blocker = static_cast(b); + auto val = s_arp_table->resource().get(blocker.ip_addr()); + if (!val.has_value()) + return true; + return blocker.unblock(true, blocker.ip_addr(), val.value()); + } +}; + +static AK::Singleton s_arp_table_block_condition; + +ARPTableBlocker::ARPTableBlocker(IPv4Address ip_addr, Optional& addr) + : m_ip_addr(ip_addr) + , m_addr(addr) +{ + if (!set_block_condition(*s_arp_table_block_condition)) + m_should_block = false; +} + +void ARPTableBlocker::not_blocking(bool timeout_in_past) +{ + ASSERT(timeout_in_past || !m_should_block); + auto addr = s_arp_table->resource().get(ip_addr()); + + ScopedSpinLock lock(m_lock); + if (!m_did_unblock) { + m_did_unblock = true; + m_addr = move(addr); + } +} + Lockable>& arp_table() { return *s_arp_table; } +void update_arp_table(const IPv4Address& ip_addr, const MACAddress& addr) +{ + LOCKER(arp_table().lock()); + arp_table().resource().set(ip_addr, addr); + s_arp_table_block_condition->unblock(ip_addr, addr); + + klog() << "ARP table (" << arp_table().resource().size() << " entries):"; + for (auto& it : arp_table().resource()) { + klog() << it.value.to_string().characters() << " :: " << it.key.to_string().characters(); + } +} + bool RoutingDecision::is_zero() const { return adapter.is_null() || next_hop.is_zero(); @@ -135,13 +229,8 @@ RoutingDecision route_to(const IPv4Address& target, const IPv4Address& source, c request.set_sender_protocol_address(adapter->ipv4_address()); adapter->send({ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, request); - (void)Thread::current()->block_until("Routing (ARP)", [next_hop_ip] { - return arp_table().resource().get(next_hop_ip).has_value(); - }); - - { - LOCKER(arp_table().lock()); - auto addr = arp_table().resource().get(next_hop_ip); + Optional addr; + if (!Thread::current()->block(nullptr, next_hop_ip, addr).was_interrupted()) { if (addr.has_value()) { #ifdef ROUTING_DEBUG klog() << "Routing: Got ARP response using adapter " << adapter->name().characters() << " for " << next_hop_ip.to_string().characters() << " (" << addr.value().to_string().characters() << ")"; diff --git a/Kernel/Net/Routing.h b/Kernel/Net/Routing.h index 50c7d72ed3..d58cbb7215 100644 --- a/Kernel/Net/Routing.h +++ b/Kernel/Net/Routing.h @@ -27,6 +27,7 @@ #pragma once #include +#include namespace Kernel { @@ -37,6 +38,7 @@ struct RoutingDecision { bool is_zero() const; }; +void update_arp_table(const IPv4Address&, const MACAddress&); RoutingDecision route_to(const IPv4Address& target, const IPv4Address& source, const RefPtr through = nullptr); Lockable>& arp_table(); diff --git a/Kernel/Net/Socket.cpp b/Kernel/Net/Socket.cpp index 96bfb8a42c..7b6cd51a5c 100644 --- a/Kernel/Net/Socket.cpp +++ b/Kernel/Net/Socket.cpp @@ -70,6 +70,7 @@ void Socket::set_setup_state(SetupState new_setup_state) #endif m_setup_state = new_setup_state; + evaluate_block_conditions(); } RefPtr Socket::accept() @@ -86,6 +87,8 @@ RefPtr Socket::accept() client->m_acceptor = { process.pid().value(), process.uid(), process.gid() }; client->m_connected = true; client->m_role = Role::Accepted; + if (!m_pending.is_empty()) + evaluate_block_conditions(); return client; } @@ -98,6 +101,7 @@ KResult Socket::queue_connection_from(NonnullRefPtr peer) if (m_pending.size() >= m_backlog) return KResult(-ECONNREFUSED); m_pending.append(peer); + evaluate_block_conditions(); return KSuccess; } diff --git a/Kernel/Net/TCPSocket.cpp b/Kernel/Net/TCPSocket.cpp index 8f66216dd4..91b5252207 100644 --- a/Kernel/Net/TCPSocket.cpp +++ b/Kernel/Net/TCPSocket.cpp @@ -52,6 +52,9 @@ void TCPSocket::set_state(State new_state) dbg() << "TCPSocket{" << this << "} state moving from " << to_string(m_state) << " to " << to_string(new_state); #endif + auto was_disconnected = protocol_is_disconnected(); + auto previous_role = m_role; + m_state = new_state; if (new_state == State::Established && m_direction == Direction::Outgoing) @@ -61,6 +64,9 @@ void TCPSocket::set_state(State new_state) LOCKER(closing_sockets().lock()); closing_sockets().resource().remove(tuple()); } + + if (previous_role != m_role || was_disconnected != protocol_is_disconnected()) + evaluate_block_conditions(); } static AK::Singleton>>> s_socket_closing; @@ -389,13 +395,16 @@ KResult TCPSocket::protocol_connect(FileDescription& description, ShouldBlock sh m_role = Role::Connecting; m_direction = Direction::Outgoing; + evaluate_block_conditions(); + if (should_block == ShouldBlock::Yes) { locker.unlock(); - if (Thread::current()->block(nullptr, description).was_interrupted()) + auto unblock_flags = Thread::FileBlocker::BlockFlags::None; + if (Thread::current()->block(nullptr, description, unblock_flags).was_interrupted()) return KResult(-EINTR); locker.lock(); ASSERT(setup_state() == SetupState::Completed); - if (has_error()) { + if (has_error()) { // TODO: check unblock_flags m_role = Role::None; return KResult(-ECONNREFUSED); } diff --git a/Kernel/Process.cpp b/Kernel/Process.cpp index f7cb346114..cd65429ea5 100644 --- a/Kernel/Process.cpp +++ b/Kernel/Process.cpp @@ -311,7 +311,7 @@ RefPtr Process::create_user_process(RefPtr& first_thread, const return process; } -NonnullRefPtr Process::create_kernel_process(RefPtr& first_thread, String&& name, void (*entry)(void*), void *entry_data, u32 affinity) +NonnullRefPtr Process::create_kernel_process(RefPtr& first_thread, String&& name, void (*entry)(void*), void* entry_data, u32 affinity) { auto process = adopt(*new Process(first_thread, move(name), (uid_t)0, (gid_t)0, ProcessID(0), true)); first_thread->tss().eip = (FlatPtr)entry; @@ -343,6 +343,7 @@ Process::Process(RefPtr& first_thread, const String& name, uid_t uid, gi , m_cwd(move(cwd)) , m_tty(tty) , m_ppid(ppid) + , m_wait_block_condition(*this) { #ifdef PROCESS_DEBUG dbg() << "Created new process " << m_name << "(" << m_pid.value() << ")"; @@ -365,8 +366,13 @@ Process::Process(RefPtr& first_thread, const String& name, uid_t uid, gi Process::~Process() { - ASSERT(!m_next && !m_prev); // should have been reaped ASSERT(thread_count() == 0); // all threads should have been finalized + + { + ScopedSpinLock processses_lock(g_processes_lock); + if (prev() || next()) + g_processes->remove(this); + } } void Process::dump_regions() @@ -528,38 +534,21 @@ void kgettimeofday(timeval& tv) tv = kgettimeofday(); } -siginfo_t Process::reap(Process& process) +siginfo_t Process::wait_info() { siginfo_t siginfo; memset(&siginfo, 0, sizeof(siginfo)); siginfo.si_signo = SIGCHLD; - siginfo.si_pid = process.pid().value(); - siginfo.si_uid = process.uid(); + siginfo.si_pid = pid().value(); + siginfo.si_uid = uid(); - if (process.m_termination_signal) { - siginfo.si_status = process.m_termination_signal; + if (m_termination_signal) { + siginfo.si_status = m_termination_signal; siginfo.si_code = CLD_KILLED; } else { - siginfo.si_status = process.m_termination_status; + siginfo.si_status = m_termination_status; siginfo.si_code = CLD_EXITED; } - - ASSERT(g_processes_lock.is_locked()); - - if (!!process.ppid()) { - auto parent = Process::from_pid(process.ppid()); - if (parent) { - parent->m_ticks_in_user_for_dead_children += process.m_ticks_in_user + process.m_ticks_in_user_for_dead_children; - parent->m_ticks_in_kernel_for_dead_children += process.m_ticks_in_kernel + process.m_ticks_in_kernel_for_dead_children; - } - } - -#ifdef PROCESS_DEBUG - dbg() << "Reaping process " << process; -#endif - ASSERT(process.is_dead()); - g_processes->remove(&process); - process.unref(); return siginfo; } @@ -587,7 +576,7 @@ KResultOr Process::get_syscall_path_argument(const Syscall::StringArgume return get_syscall_path_argument(path.characters, path.length); } -void Process::finalize() +void Process::finalize(Thread& last_thread) { ASSERT(Thread::current() == g_finalizer); #ifdef PROCESS_DEBUG @@ -614,26 +603,46 @@ void Process::finalize() m_root_directory = nullptr; m_root_directory_relative_to_global_root = nullptr; + m_dead = true; + disown_all_shared_buffers(); { - InterruptDisabler disabler; // FIXME: PID/TID BUG if (auto parent_thread = Thread::from_tid(m_ppid.value())) { - if (parent_thread->m_signal_action_data[SIGCHLD].flags & SA_NOCLDWAIT) { - // NOTE: If the parent doesn't care about this process, let it go. - m_ppid = 0; - } else { + if (!(parent_thread->m_signal_action_data[SIGCHLD].flags & SA_NOCLDWAIT)) parent_thread->send_signal(SIGCHLD, this); + } + } + + { + ScopedSpinLock processses_lock(g_processes_lock); + if (!!ppid()) { + if (auto parent = Process::from_pid(ppid())) { + parent->m_ticks_in_user_for_dead_children += m_ticks_in_user + m_ticks_in_user_for_dead_children; + parent->m_ticks_in_kernel_for_dead_children += m_ticks_in_kernel + m_ticks_in_kernel_for_dead_children; } } } + unblock_waiters(last_thread, Thread::WaitBlocker::UnblockFlags::Terminated); + { ScopedSpinLock lock(m_lock); m_regions.clear(); } - m_dead = true; + ASSERT(ref_count() > 0); + // WaitBlockCondition::finalize will be in charge of dropping the last + // reference if there are still waiters around, or whenever the last + // waitable states are consumed. Unless there is no parent around + // anymore, in which case we'll just drop it right away. + m_wait_block_condition.finalize(); +} + +void Process::unblock_waiters(Thread& thread, Thread::WaitBlocker::UnblockFlags flags, u8 signal) +{ + if (auto parent = Process::from_pid(ppid())) + parent->m_wait_block_condition.unblock(thread, flags, signal); } void Process::die() @@ -746,10 +755,8 @@ void Process::terminate_due_to_signal(u8 signal) KResult Process::send_signal(u8 signal, Process* sender) { - InterruptDisabler disabler; - Thread* receiver_thread; // Try to send it to the "obvious" main thread: - receiver_thread = Thread::from_tid(m_pid.value()); + auto receiver_thread = Thread::from_tid(m_pid.value()); // If the main thread has died, there may still be other threads: if (!receiver_thread) { // The first one should be good enough. diff --git a/Kernel/Process.h b/Kernel/Process.h index 7e85373988..84a542a184 100644 --- a/Kernel/Process.h +++ b/Kernel/Process.h @@ -130,11 +130,13 @@ public: static NonnullRefPtr create_kernel_process(RefPtr& first_thread, String&& name, EntryFunction entry, u32 affinity = THREAD_AFFINITY_DEFAULT) { auto* entry_func = new EntryFunction(move(entry)); - return create_kernel_process(first_thread, move(name), [](void* data) { - EntryFunction* func = reinterpret_cast(data); - (*func)(); - delete func; - }, entry_func, affinity); + return create_kernel_process( + first_thread, move(name), [](void* data) { + EntryFunction* func = reinterpret_cast(data); + (*func)(); + delete func; + }, + entry_func, affinity); } static NonnullRefPtr create_kernel_process(RefPtr& first_thread, String&& name, void (*entry)(void*), void* entry_data = nullptr, u32 affinity = THREAD_AFFINITY_DEFAULT); @@ -152,7 +154,8 @@ public: EntryFunction* func = reinterpret_cast(data); (*func)(); delete func; - }, priority, name, affinity, joinable); + }, + priority, name, affinity, joinable); } RefPtr create_kernel_thread(void (*entry)(void*), void* entry_data, u32 priority, const String& name, u32 affinity = THREAD_AFFINITY_DEFAULT, bool joinable = true); @@ -206,7 +209,7 @@ public: void for_each_thread(Callback) const; void die(); - void finalize(); + void finalize(Thread&); ALWAYS_INLINE SpinLock& get_lock() const { return m_lock; } @@ -366,7 +369,8 @@ public: static void initialize(); [[noreturn]] void crash(int signal, u32 eip, bool out_of_memory = false); - [[nodiscard]] static siginfo_t reap(Process&); + static void reap(Process&); + [[nodiscard]] siginfo_t wait_info(); const TTY* tty() const { return m_tty; } void set_tty(TTY*); @@ -426,7 +430,7 @@ public: u16 thread_count() const { - return m_thread_count.load(AK::MemoryOrder::memory_order_consume); + return m_thread_count.load(AK::MemoryOrder::memory_order_relaxed); } Lock& big_lock() @@ -484,6 +488,9 @@ public: KResultOr peek_user_data(Userspace address); KResult poke_user_data(Userspace address, u32 data); + void unblock_waiters(Thread&, Thread::WaitBlocker::UnblockFlags, u8 signal = 0); + Thread::WaitBlockCondition& wait_block_condition() { return m_wait_block_condition; } + private: friend class MemoryManager; friend class Scheduler; @@ -625,6 +632,8 @@ private: // If it is set to true, the process will stop at the next execve syscall // and wait for a tracer to attach. bool m_wait_for_tracer_at_next_execve { false }; + + Thread::WaitBlockCondition m_wait_block_condition; }; extern InlineLinkedList* g_processes; @@ -710,7 +719,6 @@ inline bool InodeMetadata::may_write(const Process& process) const return may_write(process.euid(), process.egid(), process.extra_gids()); } - inline bool InodeMetadata::may_execute(const Process& process) const { return may_execute(process.euid(), process.egid(), process.extra_gids()); diff --git a/Kernel/ProcessGroup.cpp b/Kernel/ProcessGroup.cpp index 1fd9a7e53c..bca954f7d7 100644 --- a/Kernel/ProcessGroup.cpp +++ b/Kernel/ProcessGroup.cpp @@ -50,13 +50,15 @@ NonnullRefPtr ProcessGroup::create(ProcessGroupID pgid) NonnullRefPtr ProcessGroup::find_or_create(ProcessGroupID pgid) { + ScopedSpinLock lock(g_process_groups_lock); + if (auto existing = from_pgid(pgid)) - return *existing; + return existing.release_nonnull(); return create(pgid); } -ProcessGroup* ProcessGroup::from_pgid(ProcessGroupID pgid) +RefPtr ProcessGroup::from_pgid(ProcessGroupID pgid) { ScopedSpinLock lock(g_process_groups_lock); diff --git a/Kernel/ProcessGroup.h b/Kernel/ProcessGroup.h index 17b93739a2..3f437f0ba8 100644 --- a/Kernel/ProcessGroup.h +++ b/Kernel/ProcessGroup.h @@ -50,7 +50,7 @@ public: static NonnullRefPtr create(ProcessGroupID); static NonnullRefPtr find_or_create(ProcessGroupID); - static ProcessGroup* from_pgid(ProcessGroupID); + static RefPtr from_pgid(ProcessGroupID); const ProcessGroupID& pgid() const { return m_pgid; } diff --git a/Kernel/Ptrace.cpp b/Kernel/Ptrace.cpp index 8e8bc6c7bf..39953effb8 100644 --- a/Kernel/Ptrace.cpp +++ b/Kernel/Ptrace.cpp @@ -35,6 +35,7 @@ namespace Ptrace { KResultOr handle_syscall(const Kernel::Syscall::SC_ptrace_params& params, Process& caller) { + ScopedSpinLock scheduler_lock(g_scheduler_lock); if (params.request == PT_TRACE_ME) { if (Thread::current()->tracer()) return KResult(-EBUSY); @@ -50,11 +51,7 @@ KResultOr handle_syscall(const Kernel::Syscall::SC_ptrace_params& params, P if (params.tid == caller.pid().value()) return KResult(-EINVAL); - Thread* peer = nullptr; - { - InterruptDisabler disabler; - peer = Thread::from_tid(params.tid); - } + auto peer = Thread::from_tid(params.tid); if (!peer) return KResult(-ESRCH); @@ -67,10 +64,9 @@ KResultOr handle_syscall(const Kernel::Syscall::SC_ptrace_params& params, P return KResult(-EBUSY); } peer->start_tracing_from(caller.pid()); + ScopedSpinLock lock(peer->get_lock()); if (peer->state() != Thread::State::Stopped) { - ScopedSpinLock lock(peer->get_lock()); - if (!(peer->has_blocker() && peer->blocker().is_reason_signal())) - peer->send_signal(SIGSTOP, &caller); + peer->send_signal(SIGSTOP, &caller); } return KSuccess; } @@ -104,7 +100,6 @@ KResultOr handle_syscall(const Kernel::Syscall::SC_ptrace_params& params, P case PT_GETREGS: { if (!tracer->has_regs()) return KResult(-EINVAL); - auto* regs = reinterpret_cast(params.addr); if (!copy_to_user(regs, &tracer->regs())) return KResult(-EFAULT); diff --git a/Kernel/Scheduler.cpp b/Kernel/Scheduler.cpp index e89bd89f77..27cf61dd51 100644 --- a/Kernel/Scheduler.cpp +++ b/Kernel/Scheduler.cpp @@ -28,8 +28,6 @@ #include #include #include -#include -#include #include #include #include @@ -77,292 +75,6 @@ WaitQueue* g_finalizer_wait_queue; Atomic g_finalizer_has_work { false }; static Process* s_colonel_process; -Thread::JoinBlocker::JoinBlocker(Thread& joinee, KResult& try_join_result, void*& joinee_exit_value) - : m_joinee(&joinee) - , m_joinee_exit_value(joinee_exit_value) -{ - auto* current_thread = Thread::current(); - // We need to hold our lock to avoid a race where try_join succeeds - // but the joinee is joining immediately - ScopedSpinLock lock(m_lock); - try_join_result = joinee.try_join(*current_thread); - m_join_error = try_join_result.is_error(); -} - -void Thread::JoinBlocker::was_unblocked() -{ - ScopedSpinLock lock(m_lock); - if (!m_join_error && m_joinee) { - // If the joinee hasn't exited yet, remove ourselves now - ASSERT(m_joinee != Thread::current()); - m_joinee->join_done(); - m_joinee = nullptr; - } -} - -bool Thread::JoinBlocker::should_unblock(Thread&) -{ - // We need to acquire our lock as the joinee could call joinee_exited - // at any moment - ScopedSpinLock lock(m_lock); - - if (m_join_error) { - // Thread::block calls should_unblock before actually blocking. - // If detected that we can't really block due to an error, we'll - // return true here, which will cause Thread::block to return - // with BlockResult::NotBlocked. Technically, because m_join_error - // will only be set in the constructor, we don't need any lock - // to check for it, but at the same time there should not be - // any contention, either... - return true; - } - - return m_joinee == nullptr; -} - -void Thread::JoinBlocker::joinee_exited(void* value) -{ - ScopedSpinLock lock(m_lock); - if (!m_joinee) { - // m_joinee can be nullptr if the joiner timed out and the - // joinee waits on m_lock while the joiner holds it but has - // not yet called join_done. - return; - } - - m_joinee_exit_value = value; - m_joinee = nullptr; - set_interrupted_by_death(); -} - -Thread::FileDescriptionBlocker::FileDescriptionBlocker(const FileDescription& description) - : m_blocked_description(description) -{ -} - -const FileDescription& Thread::FileDescriptionBlocker::blocked_description() const -{ - return m_blocked_description; -} - -Thread::AcceptBlocker::AcceptBlocker(const FileDescription& description) - : FileDescriptionBlocker(description) -{ -} - -bool Thread::AcceptBlocker::should_unblock(Thread&) -{ - auto& socket = *blocked_description().socket(); - return socket.can_accept(); -} - -Thread::ConnectBlocker::ConnectBlocker(const FileDescription& description) - : FileDescriptionBlocker(description) -{ -} - -bool Thread::ConnectBlocker::should_unblock(Thread&) -{ - auto& socket = *blocked_description().socket(); - return socket.setup_state() == Socket::SetupState::Completed; -} - -Thread::WriteBlocker::WriteBlocker(const FileDescription& description) - : FileDescriptionBlocker(description) -{ -} - -auto Thread::WriteBlocker::override_timeout(const BlockTimeout& timeout) -> const BlockTimeout& -{ - auto& description = blocked_description(); - if (description.is_socket()) { - auto& socket = *description.socket(); - if (socket.has_send_timeout()) { - m_timeout = BlockTimeout(false, &socket.send_timeout(), timeout.start_time()); - if (timeout.is_infinite() || (!m_timeout.is_infinite() && m_timeout.absolute_time() < timeout.absolute_time())) - return m_timeout; - } - } - return timeout; -} - -bool Thread::WriteBlocker::should_unblock(Thread&) -{ - return blocked_description().can_write(); -} - -Thread::ReadBlocker::ReadBlocker(const FileDescription& description) - : FileDescriptionBlocker(description) -{ -} - -auto Thread::ReadBlocker::override_timeout(const BlockTimeout& timeout) -> const BlockTimeout& -{ - auto& description = blocked_description(); - if (description.is_socket()) { - auto& socket = *description.socket(); - if (socket.has_receive_timeout()) { - m_timeout = BlockTimeout(false, &socket.receive_timeout(), timeout.start_time()); - if (timeout.is_infinite() || (!m_timeout.is_infinite() && m_timeout.absolute_time() < timeout.absolute_time())) - return m_timeout; - } - } - return timeout; -} - -bool Thread::ReadBlocker::should_unblock(Thread&) -{ - return blocked_description().can_read(); -} - -Thread::ConditionBlocker::ConditionBlocker(const char* state_string, Function&& condition) - : m_block_until_condition(move(condition)) - , m_state_string(state_string) -{ - ASSERT(m_block_until_condition); -} - -bool Thread::ConditionBlocker::should_unblock(Thread&) -{ - return m_block_until_condition(); -} - -Thread::SleepBlocker::SleepBlocker(const BlockTimeout& deadline, timespec* remaining) - : m_deadline(deadline) - , m_remaining(remaining) -{ -} - -auto Thread::SleepBlocker::override_timeout(const BlockTimeout& timeout) -> const BlockTimeout& -{ - ASSERT(timeout.is_infinite()); // A timeout should not be provided - // To simplify things only use the sleep deadline. - return m_deadline; -} - -void Thread::SleepBlocker::was_unblocked() -{ - if (!m_remaining) - return; - auto time_now = TimeManagement::the().monotonic_time(); - if (time_now < m_deadline.absolute_time()) - timespec_sub(m_deadline.absolute_time(), time_now, *m_remaining); - else - *m_remaining = {}; -} - -Thread::BlockResult Thread::SleepBlocker::block_result(bool did_timeout) -{ - auto result = Blocker::block_result(did_timeout); - if (result == Thread::BlockResult::InterruptedByTimeout) - return Thread::BlockResult::WokeNormally; - return result; -} - -Thread::SelectBlocker::SelectBlocker(const FDVector& read_fds, const FDVector& write_fds, const FDVector& except_fds) - : m_select_read_fds(read_fds) - , m_select_write_fds(write_fds) - , m_select_exceptional_fds(except_fds) -{ -} - -bool Thread::SelectBlocker::should_unblock(Thread& thread) -{ - auto& process = thread.process(); - for (int fd : m_select_read_fds) { - if (!process.m_fds[fd]) - continue; - if (process.m_fds[fd].description()->can_read()) - return true; - } - for (int fd : m_select_write_fds) { - if (!process.m_fds[fd]) - continue; - if (process.m_fds[fd].description()->can_write()) - return true; - } - - return false; -} - -Thread::WaitBlocker::WaitBlocker(int wait_options, ProcessID& waitee_pid) - : m_wait_options(wait_options) - , m_waitee_pid(waitee_pid) -{ -} - -bool Thread::WaitBlocker::should_unblock(Thread& thread) -{ - bool should_unblock = m_wait_options & WNOHANG; - if (m_waitee_pid != -1) { - auto peer = Process::from_pid(m_waitee_pid); - if (!peer) - return true; - } - thread.process().for_each_child([&](Process& child) { - if (m_waitee_pid != -1 && m_waitee_pid != child.pid()) - return IterationDecision::Continue; - - bool child_exited = child.is_dead(); - bool child_stopped = false; - if (child.thread_count()) { - child.for_each_thread([&](auto& child_thread) { - if (child_thread.state() == Thread::State::Stopped && !child_thread.has_pending_signal(SIGCONT)) { - child_stopped = true; - return IterationDecision::Break; - } - return IterationDecision::Continue; - }); - } - - bool fits_the_spec = ((m_wait_options & WEXITED) && child_exited) - || ((m_wait_options & WSTOPPED) && child_stopped); - - if (!fits_the_spec) - return IterationDecision::Continue; - - m_waitee_pid = child.pid(); - should_unblock = true; - return IterationDecision::Break; - }); - return should_unblock; -} - -Thread::SemiPermanentBlocker::SemiPermanentBlocker(Reason reason) - : m_reason(reason) -{ -} - -bool Thread::SemiPermanentBlocker::should_unblock(Thread&) -{ - // someone else has to unblock us - return false; -} - -// Called by the scheduler on threads that are blocked for some reason. -// Make a decision as to whether to unblock them or not. -void Thread::consider_unblock() -{ - ScopedSpinLock lock(m_lock); - switch (state()) { - case Thread::Invalid: - case Thread::Runnable: - case Thread::Running: - case Thread::Dead: - case Thread::Stopped: - case Thread::Queued: - case Thread::Dying: - /* don't know, don't care */ - return; - case Thread::Blocked: { - ASSERT(m_blocker != nullptr); - if (m_blocker->should_unblock(*this)) - unblock(); - return; - } - } -} - void Scheduler::start() { ASSERT_INTERRUPTS_DISABLED(); @@ -425,22 +137,7 @@ bool Scheduler::pick_next() current_thread->set_state(Thread::Dying); } - // Check and unblock threads whose wait conditions have been met. - Scheduler::for_each_nonrunnable([&](Thread& thread) { - thread.consider_unblock(); - return IterationDecision::Continue; - }); - Process::for_each([&](Process& process) { - if (process.is_dead()) { - if (current_thread->process().pid() != process.pid() && (!process.ppid() || !Process::from_pid(process.ppid()))) { - auto name = process.name(); - auto pid = process.pid(); - auto exit_status = Process::reap(process); - dbg() << "Scheduler[" << Processor::current().id() << "]: Reaped unparented process " << name << "(" << pid.value() << "), exit status: " << exit_status.si_status; - } - return IterationDecision::Continue; - } if (process.m_alarm_deadline && TimeManagement::the().uptime_ms() > process.m_alarm_deadline) { process.m_alarm_deadline = 0; // FIXME: Should we observe this signal somehow? @@ -449,26 +146,6 @@ bool Scheduler::pick_next() return IterationDecision::Continue; }); - // Dispatch any pending signals. - Thread::for_each_living([&](Thread& thread) -> IterationDecision { - ScopedSpinLock lock(thread.get_lock()); - if (!thread.has_unmasked_pending_signals()) - return IterationDecision::Continue; - // NOTE: dispatch_one_pending_signal() may unblock the process. - bool was_blocked = thread.is_blocked(); - if (thread.dispatch_one_pending_signal() == ShouldUnblockThread::No) - return IterationDecision::Continue; - if (was_blocked) { -#ifdef SCHEDULER_DEBUG - dbg() << "Scheduler[" << Processor::current().id() << "]:Unblock " << thread << " due to signal"; -#endif - ASSERT(thread.m_blocker != nullptr); - thread.m_blocker->set_interrupted_by_signal(); - thread.unblock(); - } - return IterationDecision::Continue; - }); - #ifdef SCHEDULER_RUNNABLE_DEBUG dbg() << "Scheduler[" << Processor::current().id() << "]: Non-runnables:"; Scheduler::for_each_nonrunnable([&](Thread& thread) -> IterationDecision { @@ -656,6 +333,14 @@ bool Scheduler::context_switch(Thread* thread) thread->did_schedule(); auto from_thread = Thread::current(); + + // Check if we have any signals we should deliver (even if we don't + // end up switching to another thread) + if (from_thread && from_thread->state() == Thread::Running && from_thread->pending_signals_for_state()) { + ScopedSpinLock lock(from_thread->get_lock()); + from_thread->dispatch_one_pending_signal(); + } + if (from_thread == thread) return false; diff --git a/Kernel/Syscall.cpp b/Kernel/Syscall.cpp index 2b2e074dd0..a4bd7e15a1 100644 --- a/Kernel/Syscall.cpp +++ b/Kernel/Syscall.cpp @@ -93,6 +93,13 @@ int handle(RegisterState& regs, u32 function, u32 arg1, u32 arg2, u32 arg3) if (function == SC_exit || function == SC_exit_thread) { // These syscalls need special handling since they never return to the caller. + + if (auto* tracer = current_thread->tracer(); tracer && tracer->is_tracing_syscalls()) { + regs.eax = 0; + tracer->set_trace_syscalls(false); + current_thread->tracer_trap(regs); // this triggers SIGTRAP and stops the thread! + } + cli(); if (function == SC_exit) process.sys$exit((int)arg1); @@ -131,9 +138,9 @@ void syscall_handler(TrapFrame* trap) auto& regs = *trap->regs; auto current_thread = Thread::current(); - if (current_thread->tracer() && current_thread->tracer()->is_tracing_syscalls()) { - current_thread->tracer()->set_trace_syscalls(false); - current_thread->tracer_trap(regs); + if (auto* tracer = current_thread->tracer(); tracer && tracer->is_tracing_syscalls()) { + tracer->set_trace_syscalls(false); + current_thread->tracer_trap(regs); // this triggers SIGTRAP and stops the thread! } // Make sure SMAP protection is enabled on syscall entry. @@ -176,11 +183,11 @@ void syscall_handler(TrapFrame* trap) u32 arg1 = regs.edx; u32 arg2 = regs.ecx; u32 arg3 = regs.ebx; - regs.eax = (u32)Syscall::handle(regs, function, arg1, arg2, arg3); + regs.eax = Syscall::handle(regs, function, arg1, arg2, arg3); - if (current_thread->tracer() && current_thread->tracer()->is_tracing_syscalls()) { - current_thread->tracer()->set_trace_syscalls(false); - current_thread->tracer_trap(regs); + if (auto* tracer = current_thread->tracer(); tracer && tracer->is_tracing_syscalls()) { + tracer->set_trace_syscalls(false); + current_thread->tracer_trap(regs); // this triggers SIGTRAP and stops the thread! } process.big_lock().unlock(); @@ -188,8 +195,7 @@ void syscall_handler(TrapFrame* trap) // Check if we're supposed to return to userspace or just die. current_thread->die_if_needed(); - if (current_thread->has_unmasked_pending_signals()) - (void)current_thread->block(nullptr, Thread::SemiPermanentBlocker::Reason::Signal); + ASSERT(!g_scheduler_lock.own_lock()); } } diff --git a/Kernel/Syscalls/execve.cpp b/Kernel/Syscalls/execve.cpp index 48a4974982..b3bfd57010 100644 --- a/Kernel/Syscalls/execve.cpp +++ b/Kernel/Syscalls/execve.cpp @@ -568,9 +568,6 @@ int Process::sys$execve(Userspace user_params) if (params.arguments.length > ARG_MAX || params.environment.length > ARG_MAX) return -E2BIG; - if (wait_for_tracer_at_next_execve()) - Thread::current()->send_urgent_signal_to_self(SIGSTOP); - String path; { auto path_arg = get_syscall_path_argument(params.path); diff --git a/Kernel/Syscalls/kill.cpp b/Kernel/Syscalls/kill.cpp index 41ccea9a31..a8de14ee86 100644 --- a/Kernel/Syscalls/kill.cpp +++ b/Kernel/Syscalls/kill.cpp @@ -111,10 +111,8 @@ KResult Process::do_killself(int signal) return KSuccess; auto current_thread = Thread::current(); - if (!current_thread->should_ignore_signal(signal)) { + if (!current_thread->should_ignore_signal(signal)) current_thread->send_signal(signal, this); - (void)current_thread->block(nullptr, Thread::SemiPermanentBlocker::Reason::Signal); - } return KSuccess; } diff --git a/Kernel/Syscalls/read.cpp b/Kernel/Syscalls/read.cpp index df2873a8fe..498024553f 100644 --- a/Kernel/Syscalls/read.cpp +++ b/Kernel/Syscalls/read.cpp @@ -50,10 +50,12 @@ ssize_t Process::sys$read(int fd, Userspace buffer, ssize_t size) return -EISDIR; if (description->is_blocking()) { if (!description->can_read()) { - if (Thread::current()->block(nullptr, *description).was_interrupted()) + auto unblock_flags = Thread::FileBlocker::BlockFlags::None; + if (Thread::current()->block(nullptr, *description, unblock_flags).was_interrupted()) return -EINTR; - if (!description->can_read()) + if (!((u32)unblock_flags & (u32)Thread::FileBlocker::BlockFlags::Read)) return -EAGAIN; + // TODO: handle exceptions in unblock_flags } } auto user_buffer = UserOrKernelBuffer::for_user_buffer(buffer, size); diff --git a/Kernel/Syscalls/select.cpp b/Kernel/Syscalls/select.cpp index 226fe084ec..984e1a193a 100644 --- a/Kernel/Syscalls/select.cpp +++ b/Kernel/Syscalls/select.cpp @@ -68,66 +68,77 @@ int Process::sys$select(const Syscall::SC_select_params* user_params) current_thread->update_signal_mask(previous_signal_mask); }); - Thread::SelectBlocker::FDVector rfds; - Thread::SelectBlocker::FDVector wfds; - Thread::SelectBlocker::FDVector efds; + fd_set fds_read, fds_write, fds_except; + if (params.readfds && !copy_from_user(&fds_read, params.readfds)) + return -EFAULT; + if (params.writefds && !copy_from_user(&fds_write, params.writefds)) + return -EFAULT; + if (params.exceptfds && !copy_from_user(&fds_except, params.exceptfds)) + return -EFAULT; - auto transfer_fds = [&](auto* fds_unsafe, auto& vector) -> int { - vector.clear_with_capacity(); - if (!fds_unsafe) - return 0; - fd_set fds; - if (!copy_from_user(&fds, fds_unsafe)) - return -EFAULT; - for (int fd = 0; fd < params.nfds; ++fd) { - if (FD_ISSET(fd, &fds)) { - if (!file_description(fd)) { - dbg() << "sys$select: Bad fd number " << fd; - return -EBADF; - } - vector.append(fd); - } + Thread::SelectBlocker::FDVector fds_info; + Vector fds; + for (int fd = 0; fd < params.nfds; fd++) { + u32 block_flags = (u32)Thread::FileBlocker::BlockFlags::None; + if (params.readfds && FD_ISSET(fd, &fds_read)) + block_flags |= (u32)Thread::FileBlocker::BlockFlags::Read; + if (params.writefds && FD_ISSET(fd, &fds_write)) + block_flags |= (u32)Thread::FileBlocker::BlockFlags::Write; + if (params.exceptfds && FD_ISSET(fd, &fds_except)) + block_flags |= (u32)Thread::FileBlocker::BlockFlags::Exception; + if (block_flags == (u32)Thread::FileBlocker::BlockFlags::None) + continue; + + auto description = file_description(fd); + if (!description) { + dbg() << "sys$select: Bad fd number " << fd; + return -EBADF; } - return 0; - }; - if (int error = transfer_fds(params.writefds, wfds)) - return error; - if (int error = transfer_fds(params.readfds, rfds)) - return error; - if (int error = transfer_fds(params.exceptfds, efds)) - return error; - -#if defined(DEBUG_IO) || defined(DEBUG_POLL_SELECT) - dbg() << "selecting on (read:" << rfds.size() << ", write:" << wfds.size() << "), timeout=" << params.timeout; -#endif - - if (timeout.should_block()) { - if (current_thread->block(timeout, rfds, wfds, efds).was_interrupted()) - return -EINTR; + fds_info.append({ description.release_nonnull(), (Thread::FileBlocker::BlockFlags)block_flags }); + fds.append(fd); } - int marked_fd_count = 0; - auto mark_fds = [&](auto* fds_unsafe, auto& vector, auto should_mark) { - if (!fds_unsafe) - return 0; - fd_set fds; - FD_ZERO(&fds); - for (int fd : vector) { - if (auto description = file_description(fd); description && should_mark(*description)) { - FD_SET(fd, &fds); - ++marked_fd_count; - } - } - if (!copy_to_user(fds_unsafe, &fds)) - return -EFAULT; - return 0; - }; - if (int error = mark_fds(params.readfds, rfds, [](auto& description) { return description.can_read(); })) - return error; - if (int error = mark_fds(params.writefds, wfds, [](auto& description) { return description.can_write(); })) - return error; - // FIXME: We should also mark exceptfds as appropriate. +#if defined(DEBUG_IO) || defined(DEBUG_POLL_SELECT) + dbg() << "selecting on " << fds_info.size() << " fds, timeout=" << params.timeout; +#endif + if (current_thread->block(timeout, fds_info).was_interrupted()) { + dbg() << "select was interrupted"; + return -EINTR; + } + + if (params.readfds) + FD_ZERO(&fds_read); + if (params.writefds) + FD_ZERO(&fds_write); + if (params.exceptfds) + FD_ZERO(&fds_except); + + int marked_fd_count = 0; + for (size_t i = 0; i < fds_info.size(); i++) { + auto& fd_entry = fds_info[i]; + if (fd_entry.unblocked_flags == Thread::FileBlocker::BlockFlags::None) + continue; + if (params.readfds && ((u32)fd_entry.unblocked_flags & (u32)Thread::FileBlocker::BlockFlags::Read)) { + FD_SET(fds[i], &fds_read); + marked_fd_count++; + } + if (params.writefds && ((u32)fd_entry.unblocked_flags & (u32)Thread::FileBlocker::BlockFlags::Write)) { + FD_SET(fds[i], &fds_write); + marked_fd_count++; + } + if (params.exceptfds && ((u32)fd_entry.unblocked_flags & (u32)Thread::FileBlocker::BlockFlags::Exception)) { + FD_SET(fds[i], &fds_except); + marked_fd_count++; + } + } + + if (params.readfds && !copy_to_user(params.readfds, &fds_read)) + return -EFAULT; + if (params.writefds && !copy_to_user(params.writefds, &fds_write)) + return -EFAULT; + if (params.exceptfds && !copy_to_user(params.exceptfds, &fds_except)) + return -EFAULT; return marked_fd_count; } @@ -165,15 +176,22 @@ int Process::sys$poll(Userspace user_params) return -EFAULT; } - Thread::SelectBlocker::FDVector rfds; - Thread::SelectBlocker::FDVector wfds; - - for (unsigned i = 0; i < params.nfds; ++i) { + Thread::SelectBlocker::FDVector fds_info; + for (size_t i = 0; i < params.nfds; i++) { auto& pfd = fds_copy[i]; + auto description = file_description(pfd.fd); + if (!description) { + dbg() << "sys$poll: Bad fd number " << pfd.fd; + return -EBADF; + } + u32 block_flags = (u32)Thread::FileBlocker::BlockFlags::Exception; // always want POLLERR, POLLHUP, POLLNVAL if (pfd.events & POLLIN) - rfds.append(pfd.fd); + block_flags |= (u32)Thread::FileBlocker::BlockFlags::Read; if (pfd.events & POLLOUT) - wfds.append(pfd.fd); + block_flags |= (u32)Thread::FileBlocker::BlockFlags::Write; + if (pfd.events & POLLPRI) + block_flags |= (u32)Thread::FileBlocker::BlockFlags::ReadPriority; + fds_info.append({ description.release_nonnull(), (Thread::FileBlocker::BlockFlags)block_flags }); } auto current_thread = Thread::current(); @@ -187,31 +205,45 @@ int Process::sys$poll(Userspace user_params) }); #if defined(DEBUG_IO) || defined(DEBUG_POLL_SELECT) - dbg() << "polling on (read:" << rfds.size() << ", write:" << wfds.size() << ")"; + dbg() << "polling on " << fds_info.size() << " fds, timeout=" << params.timeout; #endif - if (timeout.should_block()) { - if (current_thread->block(timeout, rfds, wfds, Thread::SelectBlocker::FDVector()).was_interrupted()) - return -EINTR; - } + if (current_thread->block(timeout, fds_info).was_interrupted()) + return -EINTR; int fds_with_revents = 0; for (unsigned i = 0; i < params.nfds; ++i) { auto& pfd = fds_copy[i]; - auto description = file_description(pfd.fd); - if (!description) { - pfd.revents = POLLNVAL; - } else { - pfd.revents = 0; - if (pfd.events & POLLIN && description->can_read()) - pfd.revents |= POLLIN; - if (pfd.events & POLLOUT && description->can_write()) - pfd.revents |= POLLOUT; + auto& fds_entry = fds_info[i]; - if (pfd.revents) - ++fds_with_revents; + pfd.revents = 0; + if (fds_entry.unblocked_flags == Thread::FileBlocker::BlockFlags::None) + continue; + + if ((u32)fds_entry.unblocked_flags & (u32)Thread::FileBlocker::BlockFlags::Exception) { + if ((u32)fds_entry.unblocked_flags & (u32)Thread::FileBlocker::BlockFlags::ReadHangUp) + pfd.revents |= POLLRDHUP; + if ((u32)fds_entry.unblocked_flags & (u32)Thread::FileBlocker::BlockFlags::WriteError) + pfd.revents |= POLLERR; + if ((u32)fds_entry.unblocked_flags & (u32)Thread::FileBlocker::BlockFlags::WriteHangUp) + pfd.revents |= POLLNVAL; + } else { + if ((u32)fds_entry.unblocked_flags & (u32)Thread::FileBlocker::BlockFlags::Read) { + ASSERT(pfd.events & POLLIN); + pfd.revents |= POLLIN; + } + if ((u32)fds_entry.unblocked_flags & (u32)Thread::FileBlocker::BlockFlags::ReadPriority) { + ASSERT(pfd.events & POLLPRI); + pfd.revents |= POLLPRI; + } + if ((u32)fds_entry.unblocked_flags & (u32)Thread::FileBlocker::BlockFlags::Write) { + ASSERT(pfd.events & POLLOUT); + pfd.revents |= POLLOUT; + } } + if (pfd.revents) + fds_with_revents++; } if (params.nfds > 0 && !copy_to_user(¶ms.fds[0], &fds_copy[0], params.nfds * sizeof(pollfd))) diff --git a/Kernel/Syscalls/socket.cpp b/Kernel/Syscalls/socket.cpp index b54f6959e7..ede0d11370 100644 --- a/Kernel/Syscalls/socket.cpp +++ b/Kernel/Syscalls/socket.cpp @@ -112,7 +112,8 @@ int Process::sys$accept(int accepting_socket_fd, Userspace user_addre if (!socket.can_accept()) { if (accepting_socket_description->is_blocking()) { - if (Thread::current()->block(nullptr, *accepting_socket_description).was_interrupted()) + auto unblock_flags = Thread::FileBlocker::BlockFlags::None; + if (Thread::current()->block(nullptr, *accepting_socket_description, unblock_flags).was_interrupted()) return -EINTR; } else { return -EAGAIN; diff --git a/Kernel/Syscalls/waitid.cpp b/Kernel/Syscalls/waitid.cpp index 9e75ba376e..f98cdb6d85 100644 --- a/Kernel/Syscalls/waitid.cpp +++ b/Kernel/Syscalls/waitid.cpp @@ -32,74 +32,20 @@ namespace Kernel { KResultOr Process::do_waitid(idtype_t idtype, int id, int options) { - if (idtype == P_PID) { - ScopedSpinLock lock(g_processes_lock); - if (idtype == P_PID && !Process::from_pid(id)) - return KResult(-ECHILD); - // FIXME: Race: After 'lock' releases, the 'id' process might vanish. - // If that is not a problem, why check for it? - // If it is a problem, let's fix it! (Eventually.) - } - - ProcessID waitee_pid { 0 }; - - // FIXME: WaitBlocker should support idtype/id specs directly. - if (idtype == P_ALL) { - waitee_pid = -1; - } else if (idtype == P_PID) { - waitee_pid = id; - } else { - // FIXME: Implement other PID specs. + switch (idtype) { + case P_ALL: + case P_PID: + case P_PGID: + break; + default: return KResult(-EINVAL); } - if (Thread::current()->block(nullptr, options, waitee_pid).was_interrupted()) + KResultOr result = KResult(KSuccess); + if (Thread::current()->block(nullptr, options, idtype, id, result).was_interrupted()) return KResult(-EINTR); - - ScopedSpinLock lock(g_processes_lock); - - // NOTE: If waitee was -1, m_waitee_pid will have been filled in by the scheduler. - auto waitee_process = Process::from_pid(waitee_pid); - if (!waitee_process) - return KResult(-ECHILD); - - ASSERT(waitee_process); - if (waitee_process->is_dead()) { - return reap(*waitee_process); - } else { - // FIXME: PID/TID BUG - // Make sure to hold the scheduler lock so that we operate on a consistent state - ScopedSpinLock scheduler_lock(g_scheduler_lock); - auto waitee_thread = Thread::from_tid(waitee_pid.value()); - if (!waitee_thread) - return KResult(-ECHILD); - ASSERT((options & WNOHANG) || waitee_thread->state() == Thread::State::Stopped); - siginfo_t siginfo; - memset(&siginfo, 0, sizeof(siginfo)); - siginfo.si_signo = SIGCHLD; - siginfo.si_pid = waitee_process->pid().value(); - siginfo.si_uid = waitee_process->uid(); - - switch (waitee_thread->state()) { - case Thread::State::Stopped: - siginfo.si_code = CLD_STOPPED; - break; - case Thread::State::Running: - case Thread::State::Runnable: - case Thread::State::Blocked: - case Thread::State::Dying: - case Thread::State::Dead: - case Thread::State::Queued: - siginfo.si_code = CLD_CONTINUED; - break; - default: - ASSERT_NOT_REACHED(); - break; - } - - siginfo.si_status = waitee_thread->m_stop_signal; - return siginfo; - } + ASSERT(!result.is_error() || (options & WNOHANG) || result.error() != KSuccess); + return result; } pid_t Process::sys$waitid(Userspace user_params) diff --git a/Kernel/Syscalls/write.cpp b/Kernel/Syscalls/write.cpp index fbdcd3e893..8eee56bb1e 100644 --- a/Kernel/Syscalls/write.cpp +++ b/Kernel/Syscalls/write.cpp @@ -96,10 +96,12 @@ ssize_t Process::do_write(FileDescription& description, const UserOrKernelBuffer ASSERT(total_nwritten > 0); return total_nwritten; } - if (Thread::current()->block(nullptr, description).was_interrupted()) { + auto unblock_flags = Thread::FileBlocker::BlockFlags::None; + if (Thread::current()->block(nullptr, description, unblock_flags).was_interrupted()) { if (total_nwritten == 0) return -EINTR; } + // TODO: handle exceptions in unblock_flags } auto nwritten_or_error = description.write(data.offset(total_nwritten), data_size - total_nwritten); if (nwritten_or_error.is_error()) { diff --git a/Kernel/TTY/MasterPTY.cpp b/Kernel/TTY/MasterPTY.cpp index 6bbcfb92b2..9ac4ba2290 100644 --- a/Kernel/TTY/MasterPTY.cpp +++ b/Kernel/TTY/MasterPTY.cpp @@ -45,6 +45,11 @@ MasterPTY::MasterPTY(unsigned index) auto process = Process::current(); set_uid(process->uid()); set_gid(process->gid()); + + m_buffer.set_unblock_callback([this]() { + if (m_slave) + evaluate_block_conditions(); + }); } MasterPTY::~MasterPTY() diff --git a/Kernel/TTY/SlavePTY.cpp b/Kernel/TTY/SlavePTY.cpp index e7daab0a39..8d74137d69 100644 --- a/Kernel/TTY/SlavePTY.cpp +++ b/Kernel/TTY/SlavePTY.cpp @@ -71,10 +71,11 @@ void SlavePTY::on_master_write(const UserOrKernelBuffer& buffer, ssize_t size) { ssize_t nread = buffer.read_buffered<128>(size, [&](const u8* data, size_t data_size) { for (size_t i = 0; i < data_size; ++i) - emit(data[i]); + emit(data[i], false); return (ssize_t)data_size; }); - (void)nread; + if (nread > 0) + evaluate_block_conditions(); } ssize_t SlavePTY::on_tty_write(const UserOrKernelBuffer& data, ssize_t size) @@ -108,4 +109,9 @@ KResult SlavePTY::close() return KSuccess; } +FileBlockCondition& SlavePTY::block_condition() +{ + return m_master->block_condition(); +} + } diff --git a/Kernel/TTY/SlavePTY.h b/Kernel/TTY/SlavePTY.h index 2f1d0d5e76..a37e7f741e 100644 --- a/Kernel/TTY/SlavePTY.h +++ b/Kernel/TTY/SlavePTY.h @@ -42,6 +42,8 @@ public: time_t time_of_last_write() const { return m_time_of_last_write; } + virtual FileBlockCondition& block_condition() override; + private: // ^TTY virtual String tty_name() const override; diff --git a/Kernel/TTY/TTY.cpp b/Kernel/TTY/TTY.cpp index 69617991c9..49a1d89f85 100644 --- a/Kernel/TTY/TTY.cpp +++ b/Kernel/TTY/TTY.cpp @@ -24,6 +24,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include #include @@ -64,6 +65,7 @@ KResultOr TTY::read(FileDescription&, size_t, UserOrKernelBuffer& buffer size = m_input_buffer.size(); ssize_t nwritten; + bool need_evaluate_block_conditions = false; if (in_canonical_mode()) { nwritten = buffer.write_buffered<512>(size, [&](u8* data, size_t data_size) { size_t i = 0; @@ -73,6 +75,7 @@ KResultOr TTY::read(FileDescription&, size_t, UserOrKernelBuffer& buffer //Here we handle a ^D line, so we don't add the //character to the output. m_available_lines--; + need_evaluate_block_conditions = true; break; } else if (ch == '\n' || is_eol(ch)) { data[i] = ch; @@ -93,6 +96,8 @@ KResultOr TTY::read(FileDescription&, size_t, UserOrKernelBuffer& buffer } if (nwritten < 0) return KResult(nwritten); + if (nwritten > 0 || need_evaluate_block_conditions) + evaluate_block_conditions(); return (size_t)nwritten; } @@ -145,7 +150,7 @@ bool TTY::is_werase(u8 ch) const return ch == m_termios.c_cc[VWERASE]; } -void TTY::emit(u8 ch) +void TTY::emit(u8 ch, bool do_evaluate_block_conditions) { if (should_generate_signals()) { if (ch == m_termios.c_cc[VINFO]) { @@ -173,6 +178,11 @@ void TTY::emit(u8 ch) } } + ScopeGuard guard([&]() { + if (do_evaluate_block_conditions) + evaluate_block_conditions(); + }); + if (in_canonical_mode()) { if (is_eof(ch)) { m_available_lines++; @@ -218,6 +228,8 @@ void TTY::do_backspace() echo(8); echo(' '); echo(8); + + evaluate_block_conditions(); } } @@ -231,6 +243,7 @@ void TTY::erase_word() //Note: if we have leading whitespace before the word //we want to delete we have to also delete that. bool first_char = false; + bool did_dequeue = false; while (can_do_backspace()) { u8 ch = m_input_buffer.last(); if (ch == ' ' && first_char) @@ -238,16 +251,23 @@ void TTY::erase_word() if (ch != ' ') first_char = true; m_input_buffer.dequeue_end(); + did_dequeue = true; erase_character(); } + if (did_dequeue) + evaluate_block_conditions(); } void TTY::kill_line() { + bool did_dequeue = false; while (can_do_backspace()) { m_input_buffer.dequeue_end(); + did_dequeue = true; erase_character(); } + if (did_dequeue) + evaluate_block_conditions(); } void TTY::erase_character() @@ -277,6 +297,7 @@ void TTY::flush_input() { m_available_lines = 0; m_input_buffer.clear(); + evaluate_block_conditions(); } void TTY::set_termios(const termios& t) @@ -330,7 +351,7 @@ int TTY::ioctl(FileDescription&, unsigned request, FlatPtr arg) return -EPERM; if (process && pgid != process->pgid()) return -EPERM; - m_pg = *process_group; + m_pg = process_group; if (process) { if (auto parent = Process::from_pid(process->ppid())) { diff --git a/Kernel/TTY/TTY.h b/Kernel/TTY/TTY.h index aafbe17292..8ba84a0dc6 100644 --- a/Kernel/TTY/TTY.h +++ b/Kernel/TTY/TTY.h @@ -72,7 +72,7 @@ protected: void set_size(unsigned short columns, unsigned short rows); TTY(unsigned major, unsigned minor); - void emit(u8); + void emit(u8, bool do_evaluate_block_conditions = false); virtual void echo(u8) = 0; bool can_do_backspace() const; diff --git a/Kernel/Tasks/FinalizerTask.cpp b/Kernel/Tasks/FinalizerTask.cpp index f05d573a7f..8b2574b62c 100644 --- a/Kernel/Tasks/FinalizerTask.cpp +++ b/Kernel/Tasks/FinalizerTask.cpp @@ -32,16 +32,17 @@ namespace Kernel { void FinalizerTask::spawn() { RefPtr finalizer_thread; - Process::create_kernel_process(finalizer_thread, "FinalizerTask", [](void*) { - Thread::current()->set_priority(THREAD_PRIORITY_LOW); - for (;;) { - Thread::current()->wait_on(*g_finalizer_wait_queue, "FinalizerTask"); + Process::create_kernel_process( + finalizer_thread, "FinalizerTask", [](void*) { + Thread::current()->set_priority(THREAD_PRIORITY_LOW); + for (;;) { + Thread::current()->wait_on(*g_finalizer_wait_queue, "FinalizerTask"); - bool expected = true; - if (g_finalizer_has_work.compare_exchange_strong(expected, false, AK::MemoryOrder::memory_order_acq_rel)) - Thread::finalize_dying_threads(); - } - }, nullptr); + if (g_finalizer_has_work.exchange(false, AK::MemoryOrder::memory_order_acq_rel) == true) + Thread::finalize_dying_threads(); + } + }, + nullptr); g_finalizer = finalizer_thread; } diff --git a/Kernel/Thread.cpp b/Kernel/Thread.cpp index 65e441b41b..e5e2db25e6 100644 --- a/Kernel/Thread.cpp +++ b/Kernel/Thread.cpp @@ -51,7 +51,7 @@ Thread::Thread(NonnullRefPtr process) : m_process(move(process)) , m_name(m_process->name()) { - if (m_process->m_thread_count.fetch_add(1, AK::MemoryOrder::memory_order_acq_rel) == 0) { + if (m_process->m_thread_count.fetch_add(1, AK::MemoryOrder::memory_order_relaxed) == 0) { // First thread gets TID == PID m_tid = m_process->pid().value(); } else { @@ -125,14 +125,27 @@ Thread::~Thread() ScopedSpinLock lock(g_scheduler_lock); g_scheduler_data->thread_list_for_state(m_state).remove(*this); } - - ASSERT(!m_joiner); } -void Thread::unblock() +void Thread::unblock_from_blocker(Blocker& blocker) +{ + ScopedSpinLock scheduler_lock(g_scheduler_lock); + ScopedSpinLock lock(m_lock); + if (m_blocker != &blocker) + return; + if (!is_stopped()) + unblock(); +} + +void Thread::unblock(u8 signal) { ASSERT(g_scheduler_lock.own_lock()); ASSERT(m_lock.own_lock()); + if (m_state != Thread::Blocked) + return; + ASSERT(m_blocker); + if (signal != 0) + m_blocker->set_interrupted_by_signal(signal); m_blocker = nullptr; if (Thread::current() == this) { set_state(Thread::Running); @@ -218,7 +231,7 @@ void Thread::die_if_needed() void Thread::exit(void* exit_value) { ASSERT(Thread::current() == this); - m_exit_value = exit_value; + m_join_condition.thread_did_exit(exit_value); set_should_die(); unlock_process_if_locked(); die_if_needed(); @@ -226,6 +239,7 @@ void Thread::exit(void* exit_value) void Thread::yield_without_holding_big_lock() { + ASSERT(!g_scheduler_lock.own_lock()); bool did_unlock = unlock_process_if_locked(); // NOTE: Even though we call Scheduler::yield here, unless we happen // to be outside of a critical section, the yield will be postponed @@ -310,11 +324,7 @@ void Thread::finalize() dbg() << "Finalizing thread " << *this; #endif set_state(Thread::State::Dead); - - if (auto* joiner = m_joiner.exchange(nullptr, AK::memory_order_acq_rel)) { - // Notify joiner that we exited - static_cast(joiner->m_blocker)->joinee_exited(m_exit_value); - } + m_join_condition.thread_finalizing(); } if (m_dump_backtrace_on_finalization) @@ -323,9 +333,12 @@ void Thread::finalize() kfree_aligned(m_fpu_state); auto thread_cnt_before = m_process->m_thread_count.fetch_sub(1, AK::MemoryOrder::memory_order_acq_rel); + ASSERT(thread_cnt_before != 0); if (thread_cnt_before == 1) - process().finalize(); + process().finalize(*this); + else + process().unblock_waiters(*this, Thread::WaitBlocker::UnblockFlags::Terminated); } void Thread::finalize_dying_threads() @@ -363,19 +376,26 @@ bool Thread::tick() bool Thread::has_pending_signal(u8 signal) const { ScopedSpinLock lock(g_scheduler_lock); - return m_pending_signals & (1 << (signal - 1)); + return pending_signals_for_state() & (1 << (signal - 1)); } u32 Thread::pending_signals() const { ScopedSpinLock lock(g_scheduler_lock); - return m_pending_signals; + return pending_signals_for_state(); +} + +u32 Thread::pending_signals_for_state() const +{ + ASSERT(g_scheduler_lock.own_lock()); + constexpr u32 stopped_signal_mask = (1 << (SIGCONT - 1)) | (1 << (SIGKILL - 1)) | (1 << (SIGTRAP - 1)); + return m_state != Stopped ? m_pending_signals : m_pending_signals & stopped_signal_mask; } void Thread::send_signal(u8 signal, [[maybe_unused]] Process* sender) { ASSERT(signal < 32); - ScopedSpinLock lock(g_scheduler_lock); + ScopedSpinLock scheduler_lock(g_scheduler_lock); // FIXME: Figure out what to do for masked signals. Should we also ignore them here? if (should_ignore_signal(signal)) { @@ -393,7 +413,15 @@ void Thread::send_signal(u8 signal, [[maybe_unused]] Process* sender) #endif m_pending_signals |= 1 << (signal - 1); - m_have_any_unmasked_pending_signals.store(m_pending_signals & ~m_signal_mask, AK::memory_order_release); + m_have_any_unmasked_pending_signals.store(pending_signals_for_state() & ~m_signal_mask, AK::memory_order_release); + + ScopedSpinLock lock(m_lock); + if (m_state == Stopped) { + if (pending_signals_for_state()) + resume_from_stopped(); + } else { + unblock(signal); + } } u32 Thread::update_signal_mask(u32 signal_mask) @@ -401,7 +429,7 @@ u32 Thread::update_signal_mask(u32 signal_mask) ScopedSpinLock lock(g_scheduler_lock); auto previous_signal_mask = m_signal_mask; m_signal_mask = signal_mask; - m_have_any_unmasked_pending_signals.store(m_pending_signals & ~m_signal_mask, AK::memory_order_release); + m_have_any_unmasked_pending_signals.store(pending_signals_for_state() & ~m_signal_mask, AK::memory_order_release); return previous_signal_mask; } @@ -419,7 +447,7 @@ u32 Thread::signal_mask_block(sigset_t signal_set, bool block) m_signal_mask &= ~signal_set; else m_signal_mask |= signal_set; - m_have_any_unmasked_pending_signals.store(m_pending_signals & ~m_signal_mask, AK::memory_order_release); + m_have_any_unmasked_pending_signals.store(pending_signals_for_state() & ~m_signal_mask, AK::memory_order_release); return previous_signal_mask; } @@ -440,15 +468,19 @@ void Thread::clear_signals() void Thread::send_urgent_signal_to_self(u8 signal) { ASSERT(Thread::current() == this); - ScopedSpinLock lock(g_scheduler_lock); - if (dispatch_signal(signal) == ShouldUnblockThread::No) - Scheduler::yield(); + DispatchSignalResult result; + { + ScopedSpinLock lock(g_scheduler_lock); + result = dispatch_signal(signal); + } + if (result == DispatchSignalResult::Yield) + yield_without_holding_big_lock(); } -ShouldUnblockThread Thread::dispatch_one_pending_signal() +DispatchSignalResult Thread::dispatch_one_pending_signal() { ASSERT(m_lock.own_lock()); - u32 signal_candidates = m_pending_signals & ~m_signal_mask; + u32 signal_candidates = pending_signals_for_state() & ~m_signal_mask; ASSERT(signal_candidates); u8 signal = 1; @@ -460,6 +492,17 @@ ShouldUnblockThread Thread::dispatch_one_pending_signal() return dispatch_signal(signal); } +DispatchSignalResult Thread::try_dispatch_one_pending_signal(u8 signal) +{ + ASSERT(signal != 0); + ScopedSpinLock scheduler_lock(g_scheduler_lock); + ScopedSpinLock lock(m_lock); + u32 signal_candidates = pending_signals_for_state() & ~m_signal_mask; + if (!(signal_candidates & (1 << (signal - 1)))) + return DispatchSignalResult::Continue; + return dispatch_signal(signal); +} + enum class DefaultSignalAction { Terminate, Ignore, @@ -542,22 +585,16 @@ void Thread::resume_from_stopped() ASSERT(is_stopped()); ASSERT(m_stop_state != State::Invalid); ASSERT(g_scheduler_lock.own_lock()); - set_state(m_stop_state); - m_stop_state = State::Invalid; - // make sure SemiPermanentBlocker is unblocked - if (m_state != Thread::Runnable && m_state != Thread::Running) { - ScopedSpinLock lock(m_lock); - if (m_blocker && m_blocker->is_reason_signal()) - unblock(); - } + set_state(m_stop_state != Blocked ? m_stop_state : Runnable); } -ShouldUnblockThread Thread::dispatch_signal(u8 signal) +DispatchSignalResult Thread::dispatch_signal(u8 signal) { ASSERT_INTERRUPTS_DISABLED(); ASSERT(g_scheduler_lock.own_lock()); ASSERT(signal > 0 && signal <= 32); ASSERT(process().is_user_process()); + ASSERT(this == Thread::current()); #ifdef SIGNAL_DEBUG klog() << "signal: dispatch signal " << signal << " to " << *this; @@ -568,7 +605,14 @@ ShouldUnblockThread Thread::dispatch_signal(u8 signal) // at least in Runnable state and is_initialized() returns true, // which indicates that it is fully set up an we actually have // a register state on the stack that we can modify - return ShouldUnblockThread::No; + return DispatchSignalResult::Deferred; + } + + if (is_stopped() && signal != SIGCONT && signal != SIGKILL && signal != SIGTRAP) { +#ifdef SIGNAL_DEBUG + klog() << "signal: " << *this << " is stopped, will handle signal " << signal << " when resumed"; +#endif + return DispatchSignalResult::Deferred; } auto& action = m_signal_action_data[signal]; @@ -579,30 +623,35 @@ ShouldUnblockThread Thread::dispatch_signal(u8 signal) m_pending_signals &= ~(1 << (signal - 1)); m_have_any_unmasked_pending_signals.store(m_pending_signals & ~m_signal_mask, AK::memory_order_release); - if (signal == SIGSTOP) { + auto* thread_tracer = tracer(); + if (signal == SIGSTOP || (thread_tracer && default_signal_action(signal) == DefaultSignalAction::DumpCore)) { if (!is_stopped()) { - m_stop_signal = SIGSTOP; +#ifdef SIGNAL_DEBUG + dbg() << "signal: signal " << signal << " stopping thread " << *this; +#endif + m_stop_signal = signal; set_state(State::Stopped); } - return ShouldUnblockThread::No; + return DispatchSignalResult::Yield; } if (signal == SIGCONT && is_stopped()) { +#ifdef SIGNAL_DEBUG + dbg() << "signal: SIGCONT resuming " << *this << " from stopped"; +#endif resume_from_stopped(); } else { - auto* thread_tracer = tracer(); if (thread_tracer != nullptr) { // when a thread is traced, it should be stopped whenever it receives a signal // the tracer is notified of this by using waitpid() // only "pending signals" from the tracer are sent to the tracee if (!thread_tracer->has_pending_signal(signal)) { m_stop_signal = signal; - // make sure SemiPermanentBlocker is unblocked - ScopedSpinLock lock(m_lock); - if (m_blocker && m_blocker->is_reason_signal()) - unblock(); +#ifdef SIGNAL_DEBUG + dbg() << "signal: " << signal << " stopping " << *this << " for tracer"; +#endif set_state(Stopped); - return ShouldUnblockThread::No; + return DispatchSignalResult::Yield; } thread_tracer->unset_signal(signal); } @@ -614,7 +663,7 @@ ShouldUnblockThread Thread::dispatch_signal(u8 signal) case DefaultSignalAction::Stop: m_stop_signal = signal; set_state(Stopped); - return ShouldUnblockThread::No; + return DispatchSignalResult::Yield; case DefaultSignalAction::DumpCore: process().for_each_thread([](auto& thread) { thread.set_dump_backtrace_on_finalization(); @@ -623,11 +672,11 @@ ShouldUnblockThread Thread::dispatch_signal(u8 signal) [[fallthrough]]; case DefaultSignalAction::Terminate: m_process->terminate_due_to_signal(signal); - return ShouldUnblockThread::No; + return DispatchSignalResult::Terminate; case DefaultSignalAction::Ignore: ASSERT_NOT_REACHED(); case DefaultSignalAction::Continue: - return ShouldUnblockThread::Yes; + return DispatchSignalResult::Continue; } ASSERT_NOT_REACHED(); } @@ -636,7 +685,7 @@ ShouldUnblockThread Thread::dispatch_signal(u8 signal) #ifdef SIGNAL_DEBUG klog() << "signal: " << *this << " ignored signal " << signal; #endif - return ShouldUnblockThread::Yes; + return DispatchSignalResult::Continue; } ProcessPagingScope paging_scope(m_process); @@ -700,7 +749,7 @@ ShouldUnblockThread Thread::dispatch_signal(u8 signal) #ifdef SIGNAL_DEBUG klog() << "signal: Okay, " << *this << " {" << state_string() << "} has been primed with signal handler " << String::format("%w", m_tss.cs) << ":" << String::format("%x", m_tss.eip) << " to deliver " << signal; #endif - return ShouldUnblockThread::Yes; + return DispatchSignalResult::Continue; } void Thread::set_default_signal_dispositions() @@ -827,11 +876,6 @@ void Thread::set_state(State new_state) } } - if (new_state == Stopped) { - // We don't want to restore to Running state, only Runnable! - m_stop_state = m_state != Running ? m_state : Runnable; - } - m_state = new_state; #ifdef THREAD_DEBUG dbg() << "Set Thread " << *this << " state to " << state_string(); @@ -842,7 +886,16 @@ void Thread::set_state(State new_state) ASSERT(g_scheduler_data->has_thread(*this)); } - if (m_state == Dying) { + if (previous_state == Stopped) { + m_stop_state = State::Invalid; + process().unblock_waiters(*this, Thread::WaitBlocker::UnblockFlags::Continued); + } + + if (m_state == Stopped) { + // We don't want to restore to Running state, only Runnable! + m_stop_state = previous_state != Running ? m_state : Runnable; + process().unblock_waiters(*this, Thread::WaitBlocker::UnblockFlags::Stopped, m_stop_signal); + } else if (m_state == Dying) { ASSERT(previous_state != Queued); if (this != Thread::current() && is_finalizable()) { // Some other thread set this thread to Dying, notify the @@ -1027,7 +1080,6 @@ Thread::BlockResult Thread::wait_on(WaitQueue& queue, const char* reason, const } }); if (!timer) { - dbg() << "wait_on timed out before blocking"; // We timed out already, don't block // The API contract guarantees we return with interrupts enabled, // regardless of how we got called diff --git a/Kernel/Thread.h b/Kernel/Thread.h index 07e98cd046..bb40634f18 100644 --- a/Kernel/Thread.h +++ b/Kernel/Thread.h @@ -47,9 +47,11 @@ namespace Kernel { -enum class ShouldUnblockThread { - No = 0, - Yes +enum class DispatchSignalResult { + Deferred = 0, + Yield, + Terminate, + Continue }; struct SignalActionData { @@ -102,35 +104,6 @@ public: u32 effective_priority() const; - KResult try_join(Thread& joiner) - { - if (&joiner == this) - return KResult(-EDEADLK); - - ScopedSpinLock lock(m_lock); - if (!m_is_joinable || state() == Dead) - return KResult(-EINVAL); - - Thread* expected = nullptr; - if (!m_joiner.compare_exchange_strong(expected, &joiner, AK::memory_order_acq_rel)) - return KResult(-EINVAL); - - // From this point on the thread is no longer joinable by anyone - // else. It also means that if the join is timed, it becomes - // detached when a timeout happens. - m_is_joinable = false; - return KSuccess; - } - - void join_done() - { - // To avoid possible deadlocking, this function must not acquire - // m_lock. This deadlock could occur if the joiner times out - // almost at the same time as this thread, and calls into this - // function to clear the joiner. - m_joiner.store(nullptr, AK::memory_order_release); - } - void detach() { ScopedSpinLock lock(m_lock); @@ -275,88 +248,284 @@ public: bool m_should_block { false }; }; + class BlockCondition; + class Blocker { public: - virtual ~Blocker() { } - virtual bool should_unblock(Thread&) = 0; + enum class Type { + Unknown = 0, + File, + Plan9FS, + Join, + Routing, + Sleep, + Wait + }; + virtual ~Blocker(); virtual const char* state_string() const = 0; - virtual bool is_reason_signal() const { return false; } + virtual bool should_block() { return true; } + virtual Type blocker_type() const = 0; virtual const BlockTimeout& override_timeout(const BlockTimeout& timeout) { return timeout; } - virtual void was_unblocked() { } + virtual void not_blocking(bool) = 0; + virtual void was_unblocked(bool did_timeout) + { + if (did_timeout) { + ScopedSpinLock lock(m_lock); + m_did_timeout = true; + } + } void set_interrupted_by_death() { ScopedSpinLock lock(m_lock); - m_was_interrupted_by_death = true; + do_set_interrupted_by_death(); } - void set_interrupted_by_signal() + void set_interrupted_by_signal(u8 signal) { ScopedSpinLock lock(m_lock); - m_was_interrupted_while_blocked = true; + do_set_interrupted_by_signal(signal); } - virtual Thread::BlockResult block_result(bool did_timeout) + u8 was_interrupted_by_signal() const + { + ScopedSpinLock lock(m_lock); + return do_get_interrupted_by_signal(); + } + virtual Thread::BlockResult block_result() { ScopedSpinLock lock(m_lock); if (m_was_interrupted_by_death) return Thread::BlockResult::InterruptedByDeath; - if (m_was_interrupted_while_blocked) + if (m_was_interrupted_by_signal != 0) return Thread::BlockResult::InterruptedBySignal; - if (did_timeout) + if (m_did_timeout) return Thread::BlockResult::InterruptedByTimeout; return Thread::BlockResult::WokeNormally; } + void begin_blocking(Badge); + BlockResult end_blocking(Badge, bool); + protected: + void do_set_interrupted_by_death() + { + m_was_interrupted_by_death = true; + } + void do_set_interrupted_by_signal(u8 signal) + { + ASSERT(signal != 0); + m_was_interrupted_by_signal = signal; + } + void do_clear_interrupted_by_signal() + { + m_was_interrupted_by_signal = 0; + } + u8 do_get_interrupted_by_signal() const + { + return m_was_interrupted_by_signal; + } + bool was_interrupted() const + { + return m_was_interrupted_by_death || m_was_interrupted_by_signal != 0; + } + void unblock_from_blocker() + { + RefPtr thread; + + { + ScopedSpinLock lock(m_lock); + if (m_is_blocking) { + m_is_blocking = false; + ASSERT(m_blocked_thread); + thread = m_blocked_thread; + } + } + + if (thread) + thread->unblock_from_blocker(*this); + } + + bool set_block_condition(BlockCondition&, void* = nullptr); + mutable RecursiveSpinLock m_lock; private: - bool m_was_interrupted_while_blocked { false }; + BlockCondition* m_block_condition { nullptr }; + void* m_block_data { nullptr }; + Thread* m_blocked_thread { nullptr }; + u8 m_was_interrupted_by_signal { 0 }; + bool m_is_blocking { false }; bool m_was_interrupted_by_death { false }; - friend class Thread; + bool m_did_timeout { false }; }; + class BlockCondition { + AK_MAKE_NONCOPYABLE(BlockCondition); + AK_MAKE_NONMOVABLE(BlockCondition); + + public: + BlockCondition() = default; + + virtual ~BlockCondition() + { + ScopedSpinLock lock(m_lock); + ASSERT(m_blockers.is_empty()); + } + + bool add_blocker(Blocker& blocker, void* data) + { + ScopedSpinLock lock(m_lock); + if (!should_add_blocker(blocker, data)) + return false; + m_blockers.append({ &blocker, data }); + return true; + } + + void remove_blocker(Blocker& blocker, void* data) + { + ScopedSpinLock lock(m_lock); + // NOTE: it's possible that the blocker is no longer present + m_blockers.remove_first_matching([&](auto& info) { + return info.blocker == &blocker && info.data == data; + }); + } + + protected: + template + void unblock(UnblockOne unblock_one) + { + ScopedSpinLock lock(m_lock); + do_unblock(unblock_one); + } + + template + void do_unblock(UnblockOne unblock_one) + { + ASSERT(m_lock.is_locked()); + for (size_t i = 0; i < m_blockers.size();) { + auto& info = m_blockers[i]; + if (unblock_one(*info.blocker, info.data)) { + m_blockers.remove(i); + continue; + } + + i++; + } + } + + template + void unblock_all(UnblockOne unblock_one) + { + ScopedSpinLock lock(m_lock); + do_unblock_all(unblock_one); + } + + template + void do_unblock_all(UnblockOne unblock_one) + { + ASSERT(m_lock.is_locked()); + for (auto& info : m_blockers) { + bool did_unblock = unblock_one(*info.blocker, info.data); + ASSERT(did_unblock); + } + m_blockers.clear(); + } + + virtual bool should_add_blocker(Blocker&, void*) { return true; } + + SpinLock m_lock; + + private: + struct BlockerInfo { + Blocker* blocker; + void* data; + }; + Vector m_blockers; + }; + + friend class JoinBlocker; class JoinBlocker final : public Blocker { public: explicit JoinBlocker(Thread& joinee, KResult& try_join_result, void*& joinee_exit_value); - virtual bool should_unblock(Thread&) override; + virtual Type blocker_type() const override { return Type::Join; } virtual const char* state_string() const override { return "Joining"; } - virtual void was_unblocked() override; - void joinee_exited(void* value); + virtual bool should_block() override { return !m_join_error && m_should_block; } + virtual void not_blocking(bool) override; + + bool unblock(void*, bool); private: - Thread* m_joinee; + NonnullRefPtr m_joinee; void*& m_joinee_exit_value; bool m_join_error { false }; + bool m_did_unblock { false }; + bool m_should_block { true }; }; - class FileDescriptionBlocker : public Blocker { + class FileBlocker : public Blocker { + public: + enum class BlockFlags : u32 { + None = 0, + + Read = 1 << 0, + Write = 1 << 1, + ReadPriority = 1 << 2, + + Accept = 1 << 3, + Connect = 1 << 4, + SocketFlags = Accept | Connect, + + WriteNotOpen = 1 << 5, + WriteError = 1 << 6, + WriteHangUp = 1 << 7, + ReadHangUp = 1 << 8, + Exception = WriteNotOpen | WriteError | WriteHangUp | ReadHangUp, + }; + + virtual Type blocker_type() const override { return Type::File; } + + virtual bool should_block() override + { + return m_should_block; + } + + virtual bool unblock(bool, void*) = 0; + + protected: + bool m_should_block { true }; + }; + + class FileDescriptionBlocker : public FileBlocker { public: const FileDescription& blocked_description() const; + virtual bool unblock(bool, void*) override; + virtual void not_blocking(bool) override; + protected: - explicit FileDescriptionBlocker(const FileDescription&); + explicit FileDescriptionBlocker(FileDescription&, BlockFlags, BlockFlags&); private: NonnullRefPtr m_blocked_description; + const BlockFlags m_flags; + BlockFlags& m_unblocked_flags; + bool m_did_unblock { false }; + bool m_should_block { true }; }; class AcceptBlocker final : public FileDescriptionBlocker { public: - explicit AcceptBlocker(const FileDescription&); - virtual bool should_unblock(Thread&) override; + explicit AcceptBlocker(FileDescription&, BlockFlags&); virtual const char* state_string() const override { return "Accepting"; } }; class ConnectBlocker final : public FileDescriptionBlocker { public: - explicit ConnectBlocker(const FileDescription&); - virtual bool should_unblock(Thread&) override; + explicit ConnectBlocker(FileDescription&, BlockFlags&); virtual const char* state_string() const override { return "Connecting"; } }; class WriteBlocker final : public FileDescriptionBlocker { public: - explicit WriteBlocker(const FileDescription&); - virtual bool should_unblock(Thread&) override; + explicit WriteBlocker(FileDescription&, BlockFlags&); virtual const char* state_string() const override { return "Writing"; } virtual const BlockTimeout& override_timeout(const BlockTimeout&) override; @@ -366,8 +535,7 @@ public: class ReadBlocker final : public FileDescriptionBlocker { public: - explicit ReadBlocker(const FileDescription&); - virtual bool should_unblock(Thread&) override; + explicit ReadBlocker(FileDescription&, BlockFlags&); virtual const char* state_string() const override { return "Reading"; } virtual const BlockTimeout& override_timeout(const BlockTimeout&) override; @@ -375,77 +543,136 @@ public: BlockTimeout m_timeout; }; - class ConditionBlocker final : public Blocker { - public: - ConditionBlocker(const char* state_string, Function&& condition); - virtual bool should_unblock(Thread&) override; - virtual const char* state_string() const override { return m_state_string; } - - private: - Function m_block_until_condition; - const char* m_state_string { nullptr }; - }; - class SleepBlocker final : public Blocker { public: explicit SleepBlocker(const BlockTimeout&, timespec* = nullptr); - virtual bool should_unblock(Thread&) override { return false; } virtual const char* state_string() const override { return "Sleeping"; } + virtual Type blocker_type() const override { return Type::Sleep; } virtual const BlockTimeout& override_timeout(const BlockTimeout&) override; - virtual void was_unblocked() override; - virtual Thread::BlockResult block_result(bool) override; + virtual void not_blocking(bool) override; + virtual void was_unblocked(bool) override; + virtual Thread::BlockResult block_result() override; private: + void calculate_remaining(); + BlockTimeout m_deadline; timespec* m_remaining; }; - class SelectBlocker final : public Blocker { + class SelectBlocker final : public FileBlocker { public: - typedef Vector FDVector; - SelectBlocker(const FDVector& read_fds, const FDVector& write_fds, const FDVector& except_fds); - virtual bool should_unblock(Thread&) override; + struct FDInfo { + NonnullRefPtr description; + BlockFlags block_flags; + BlockFlags unblocked_flags { BlockFlags::None }; + }; + + typedef Vector FDVector; + SelectBlocker(FDVector& fds); + virtual ~SelectBlocker(); + + virtual bool unblock(bool, void*) override; + virtual void not_blocking(bool) override; + virtual void was_unblocked(bool) override; virtual const char* state_string() const override { return "Selecting"; } private: - const FDVector& m_select_read_fds; - const FDVector& m_select_write_fds; - const FDVector& m_select_exceptional_fds; + size_t collect_unblocked_flags(); + + FDVector& m_fds; + size_t m_registered_count { 0 }; + bool m_did_unblock { false }; }; class WaitBlocker final : public Blocker { public: - WaitBlocker(int wait_options, ProcessID& waitee_pid); - virtual bool should_unblock(Thread&) override; - virtual const char* state_string() const override { return "Waiting"; } - - private: - int m_wait_options { 0 }; - ProcessID& m_waitee_pid; - }; - - class SemiPermanentBlocker final : public Blocker { - public: - enum class Reason { - Signal, + enum class UnblockFlags { + Terminated, + Stopped, + Continued }; - SemiPermanentBlocker(Reason reason); - virtual bool should_unblock(Thread&) override; - virtual const char* state_string() const override - { - switch (m_reason) { - case Reason::Signal: - return "Signal"; - } - ASSERT_NOT_REACHED(); - } - virtual bool is_reason_signal() const override { return m_reason == Reason::Signal; } + WaitBlocker(int wait_options, idtype_t id_type, pid_t id, KResultOr& result); + virtual const char* state_string() const override { return "Waiting"; } + virtual Type blocker_type() const override { return Type::Wait; } + virtual bool should_block() override { return m_should_block; } + virtual void not_blocking(bool) override; + virtual void was_unblocked(bool) override; + + bool unblock(Thread& thread, UnblockFlags flags, u8 signal, bool from_add_blocker); + bool is_wait() const { return !(m_wait_options & WNOWAIT); } private: - Reason m_reason; + void do_set_result(const siginfo_t&); + + const int m_wait_options; + const idtype_t m_id_type; + const pid_t m_waitee_id; + KResultOr& m_result; + RefPtr m_waitee; + RefPtr m_waitee_group; + bool m_did_unblock { false }; + bool m_error { false }; + bool m_got_sigchild { false }; + bool m_should_block; }; + class WaitBlockCondition final : public BlockCondition { + friend class WaitBlocker; + + public: + WaitBlockCondition(Process& process) + : m_process(process) + { + } + + bool unblock(Thread&, WaitBlocker::UnblockFlags, u8); + void try_unblock(WaitBlocker&); + void finalize(); + + protected: + virtual bool should_add_blocker(Blocker&, void*) override; + + private: + struct ThreadBlockInfo { + NonnullRefPtr thread; + WaitBlocker::UnblockFlags flags; + u8 signal; + bool was_waited { false }; + + explicit ThreadBlockInfo(NonnullRefPtr&& thread, WaitBlocker::UnblockFlags flags, u8 signal) + : thread(move(thread)) + , flags(flags) + , signal(signal) + { + } + }; + + Process& m_process; + Vector m_threads; + bool m_finalized { false }; + }; + + KResult try_join(JoinBlocker& blocker) + { + if (Thread::current() == this) + return KResult(-EDEADLK); + + ScopedSpinLock lock(m_lock); + if (!m_is_joinable || state() == Dead) + return KResult(-EINVAL); + + bool added = m_join_condition.add_blocker(blocker, nullptr); + ASSERT(added); + + // From this point on the thread is no longer joinable by anyone + // else. It also means that if the join is timed, it becomes + // detached when a timeout happens. + m_is_joinable = false; + return KSuccess; + } + void did_schedule() { ++m_times_scheduled; } u32 times_scheduled() const { return m_times_scheduled; } @@ -481,21 +708,23 @@ public: template [[nodiscard]] BlockResult block(const BlockTimeout& timeout, Args&&... args) { + ScopedSpinLock lock(m_lock); + // We need to hold m_lock so that nobody can unblock a blocker as soon + // as it is constructed and registered elsewhere T t(forward(args)...); bool did_timeout = false; RefPtr timer; { ScopedSpinLock scheduler_lock(g_scheduler_lock); - ScopedSpinLock lock(m_lock); // We should never be blocking a blocked (or otherwise non-active) thread. ASSERT(state() == Thread::Running); ASSERT(m_blocker == nullptr); m_blocker = &t; - if (t.should_unblock(*this)) { + if (!t.should_block()) { // Don't block if the wake condition is already met - t.was_unblocked(); + t.not_blocking(false); m_blocker = nullptr; return BlockResult::NotBlocked; } @@ -508,12 +737,16 @@ public: ScopedSpinLock lock(m_lock); if (m_blocker) { m_blocker_timeout = nullptr; - unblock(); + if (!is_stopped()) { + // Only unblock if we're not stopped. In either + // case the blocker should be marked as timed out + unblock(); + } } }); if (!m_blocker_timeout) { // Timeout is already in the past - t.was_unblocked(); + t.not_blocking(true); m_blocker = nullptr; return BlockResult::InterruptedByTimeout; } @@ -521,18 +754,30 @@ public: m_blocker_timeout = nullptr; } + t.begin_blocking({}); + set_state(Thread::Blocked); } + lock.unlock(); + // Yield to the scheduler, and wait for us to resume unblocked. yield_without_holding_big_lock(); + lock.lock(); + + bool is_stopped = false; { ScopedSpinLock scheduler_lock(g_scheduler_lock); - ScopedSpinLock lock(m_lock); + if (t.was_interrupted_by_signal()) + dispatch_one_pending_signal(); - // We should no longer be blocked once we woke up - ASSERT(state() != Thread::Blocked); + auto current_state = state(); + // We should no longer be blocked once we woke up, but we may be stopped + if (current_state == Stopped) + is_stopped = true; + else + ASSERT(current_state == Thread::Running); // Remove ourselves... m_blocker = nullptr; @@ -549,19 +794,20 @@ public: // Notify the blocker that we are no longer blocking. It may need // to clean up now while we're still holding m_lock - t.was_unblocked(); - return t.block_result(did_timeout); - } + auto result = t.end_blocking({}, did_timeout); // calls was_unblocked internally - [[nodiscard]] BlockResult block_until(const char* state_string, Function&& condition) - { - return block(nullptr, state_string, move(condition)); + if (is_stopped) { + // If we're stopped we need to yield + yield_without_holding_big_lock(); + } + return result; } BlockResult wait_on(WaitQueue& queue, const char* reason, const BlockTimeout& = nullptr, Atomic* lock = nullptr, RefPtr beneficiary = {}); void wake_from_queue(); - void unblock(); + void unblock_from_blocker(Blocker&); + void unblock(u8 signal = 0); BlockResult sleep(const timespec&, timespec* = nullptr); BlockResult sleep_until(const timespec&); @@ -588,7 +834,6 @@ public: void send_urgent_signal_to_self(u8 signal); void send_signal(u8 signal, Process* sender); - void consider_unblock(); u32 update_signal_mask(u32 signal_mask); u32 signal_mask_block(sigset_t signal_set, bool block); @@ -597,14 +842,16 @@ public: void set_dump_backtrace_on_finalization() { m_dump_backtrace_on_finalization = true; } - ShouldUnblockThread dispatch_one_pending_signal(); - ShouldUnblockThread dispatch_signal(u8 signal); + DispatchSignalResult dispatch_one_pending_signal(); + DispatchSignalResult try_dispatch_one_pending_signal(u8 signal); + DispatchSignalResult dispatch_signal(u8 signal); bool has_unmasked_pending_signals() const { return m_have_any_unmasked_pending_signals.load(AK::memory_order_consume); } void terminate_due_to_signal(u8 signal); bool should_ignore_signal(u8 signal) const; bool has_signal_handler(u8 signal) const; bool has_pending_signal(u8 signal) const; u32 pending_signals() const; + u32 pending_signals_for_state() const; FPUState& fpu_state() { return *m_fpu_state; } @@ -710,6 +957,7 @@ public: void start_tracing_from(ProcessID tracer); void stop_tracing(); void tracer_trap(const RegisterState&); + bool is_traced() const { return !!m_tracer; } RecursiveSpinLock& get_lock() const { return m_lock; } @@ -720,6 +968,61 @@ private: private: friend struct SchedulerData; friend class WaitQueue; + + class JoinBlockCondition : public BlockCondition { + public: + void thread_did_exit(void* exit_value) + { + ScopedSpinLock lock(m_lock); + ASSERT(!m_thread_did_exit); + m_thread_did_exit = true; + m_exit_value.store(exit_value, AK::MemoryOrder::memory_order_release); + do_unblock_joiner(); + } + void thread_finalizing() + { + ScopedSpinLock lock(m_lock); + do_unblock_joiner(); + } + void* exit_value() const + { + ASSERT(m_thread_did_exit); + return m_exit_value.load(AK::MemoryOrder::memory_order_acquire); + } + + void try_unblock(JoinBlocker& blocker) + { + ScopedSpinLock lock(m_lock); + if (m_thread_did_exit) + blocker.unblock(exit_value(), false); + } + + protected: + virtual bool should_add_blocker(Blocker& b, void*) override + { + ASSERT(b.blocker_type() == Blocker::Type::Join); + auto& blocker = static_cast(b); + + // NOTE: m_lock is held already! + if (m_thread_did_exit) + blocker.unblock(exit_value(), true); + return m_thread_did_exit; + } + + private: + void do_unblock_joiner() + { + do_unblock_all([&](Blocker& b, void*) { + ASSERT(b.blocker_type() == Blocker::Type::Join); + auto& blocker = static_cast(b); + return blocker.unblock(exit_value(), false); + }); + } + + Atomic m_exit_value { nullptr }; + bool m_thread_did_exit { false }; + }; + bool unlock_process_if_locked(); void relock_process(bool did_unlock); String backtrace_impl(); @@ -747,10 +1050,9 @@ private: const char* m_wait_reason { nullptr }; WaitQueue* m_queue { nullptr }; + JoinBlockCondition m_join_condition; Atomic m_is_active { false }; bool m_is_joinable { true }; - Atomic m_joiner { nullptr }; - void* m_exit_value { nullptr }; unsigned m_syscall_count { 0 }; unsigned m_inode_faults { 0 }; diff --git a/Kernel/ThreadBlockers.cpp b/Kernel/ThreadBlockers.cpp new file mode 100644 index 0000000000..b04eccb68c --- /dev/null +++ b/Kernel/ThreadBlockers.cpp @@ -0,0 +1,656 @@ +/* + * Copyright (c) 2020, The SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +namespace Kernel { + +bool Thread::Blocker::set_block_condition(Thread::BlockCondition& block_condition, void* data) +{ + ASSERT(!m_block_condition); + if (block_condition.add_blocker(*this, data)) { + m_block_condition = &block_condition; + m_block_data = data; + return true; + } + return false; +} + +Thread::Blocker::~Blocker() +{ + ScopedSpinLock lock(m_lock); + if (m_block_condition) + m_block_condition->remove_blocker(*this, m_block_data); +} + +void Thread::Blocker::begin_blocking(Badge) +{ + ScopedSpinLock lock(m_lock); + ASSERT(!m_is_blocking); + ASSERT(!m_blocked_thread); + m_blocked_thread = Thread::current(); + m_is_blocking = true; +} + +auto Thread::Blocker::end_blocking(Badge, bool did_timeout) -> BlockResult +{ + ScopedSpinLock lock(m_lock); + // if m_is_blocking is false here, some thread forced to + // unblock us when we get here. This is only called from the + // thread that was blocked. + ASSERT(Thread::current() == m_blocked_thread); + m_is_blocking = false; + m_blocked_thread = nullptr; + + was_unblocked(did_timeout); + return block_result(); +} + +Thread::JoinBlocker::JoinBlocker(Thread& joinee, KResult& try_join_result, void*& joinee_exit_value) + : m_joinee(joinee) + , m_joinee_exit_value(joinee_exit_value) +{ + { + // We need to hold our lock to avoid a race where try_join succeeds + // but the joinee is joining immediately + ScopedSpinLock lock(m_lock); + try_join_result = joinee.try_join(*this); + m_join_error = try_join_result.is_error(); + } + if (!set_block_condition(joinee.m_join_condition)) + m_should_block = false; +} + +void Thread::JoinBlocker::not_blocking(bool timeout_in_past) +{ + if (!m_should_block) { + // set_block_condition returned false, so unblock was already called + ASSERT(!timeout_in_past); + return; + } + // If we should have blocked but got here it must have been that the + // timeout was already in the past. So we need to ask the BlockCondition + // to supply us the information. We cannot hold the lock as unblock + // could be called by the BlockCondition at any time! + ASSERT(timeout_in_past); + m_joinee->m_join_condition.try_unblock(*this); +} + +bool Thread::JoinBlocker::unblock(void* value, bool from_add_blocker) +{ + { + ScopedSpinLock lock(m_lock); + if (m_did_unblock) + return false; + m_did_unblock = true; + m_joinee_exit_value = value; + do_set_interrupted_by_death(); + } + + if (!from_add_blocker) + unblock_from_blocker(); + return true; +} + +Thread::FileDescriptionBlocker::FileDescriptionBlocker(FileDescription& description, BlockFlags flags, BlockFlags& unblocked_flags) + : m_blocked_description(description) + , m_flags(flags) + , m_unblocked_flags(unblocked_flags) +{ + m_unblocked_flags = BlockFlags::None; + + if (!set_block_condition(description.block_condition())) + m_should_block = false; +} + +bool Thread::FileDescriptionBlocker::unblock(bool from_add_blocker, void*) +{ + auto unblock_flags = m_blocked_description->should_unblock(m_flags); + if (unblock_flags == BlockFlags::None) + return false; + + { + ScopedSpinLock lock(m_lock); + if (m_did_unblock) + return false; + m_did_unblock = true; + m_unblocked_flags = unblock_flags; + } + + if (!from_add_blocker) + unblock_from_blocker(); + return true; +} + +void Thread::FileDescriptionBlocker::not_blocking(bool timeout_in_past) +{ + if (!m_should_block) { + // set_block_condition returned false, so unblock was already called + ASSERT(!timeout_in_past); + return; + } + // If we should have blocked but got here it must have been that the + // timeout was already in the past. So we need to ask the BlockCondition + // to supply us the information. We cannot hold the lock as unblock + // could be called by the BlockCondition at any time! + ASSERT(timeout_in_past); + + // Just call unblock here because we will query the file description + // for the data and don't need any input from the FileBlockCondition. + // However, it's possible that if timeout_in_past is true then FileBlockCondition + // may call us at any given time, so our call to unblock here may fail. + // Either way, unblock will be called at least once, which provides + // all the data we need. + unblock(false, nullptr); +} + +const FileDescription& Thread::FileDescriptionBlocker::blocked_description() const +{ + return m_blocked_description; +} + +Thread::AcceptBlocker::AcceptBlocker(FileDescription& description, BlockFlags& unblocked_flags) + : FileDescriptionBlocker(description, (BlockFlags)((u32)BlockFlags::Accept | (u32)BlockFlags::Exception), unblocked_flags) +{ +} + +Thread::ConnectBlocker::ConnectBlocker(FileDescription& description, BlockFlags& unblocked_flags) + : FileDescriptionBlocker(description, (BlockFlags)((u32)BlockFlags::Connect | (u32)BlockFlags::Exception), unblocked_flags) +{ +} + +Thread::WriteBlocker::WriteBlocker(FileDescription& description, BlockFlags& unblocked_flags) + : FileDescriptionBlocker(description, (BlockFlags)((u32)BlockFlags::Write | (u32)BlockFlags::Exception), unblocked_flags) +{ +} + +auto Thread::WriteBlocker::override_timeout(const BlockTimeout& timeout) -> const BlockTimeout& +{ + auto& description = blocked_description(); + if (description.is_socket()) { + auto& socket = *description.socket(); + if (socket.has_send_timeout()) { + m_timeout = BlockTimeout(false, &socket.send_timeout(), timeout.start_time()); + if (timeout.is_infinite() || (!m_timeout.is_infinite() && m_timeout.absolute_time() < timeout.absolute_time())) + return m_timeout; + } + } + return timeout; +} + +Thread::ReadBlocker::ReadBlocker(FileDescription& description, BlockFlags& unblocked_flags) + : FileDescriptionBlocker(description, (BlockFlags)((u32)BlockFlags::Read | (u32)BlockFlags::Exception), unblocked_flags) +{ +} + +auto Thread::ReadBlocker::override_timeout(const BlockTimeout& timeout) -> const BlockTimeout& +{ + auto& description = blocked_description(); + if (description.is_socket()) { + auto& socket = *description.socket(); + if (socket.has_receive_timeout()) { + m_timeout = BlockTimeout(false, &socket.receive_timeout(), timeout.start_time()); + if (timeout.is_infinite() || (!m_timeout.is_infinite() && m_timeout.absolute_time() < timeout.absolute_time())) + return m_timeout; + } + } + return timeout; +} + +Thread::SleepBlocker::SleepBlocker(const BlockTimeout& deadline, timespec* remaining) + : m_deadline(deadline) + , m_remaining(remaining) +{ +} + +auto Thread::SleepBlocker::override_timeout(const BlockTimeout& timeout) -> const BlockTimeout& +{ + ASSERT(timeout.is_infinite()); // A timeout should not be provided + // To simplify things only use the sleep deadline. + return m_deadline; +} + +void Thread::SleepBlocker::not_blocking(bool timeout_in_past) +{ + // SleepBlocker::should_block should always return true, so timeout + // in the past is the only valid case when this function is called + ASSERT(timeout_in_past); + calculate_remaining(); +} + +void Thread::SleepBlocker::was_unblocked(bool did_timeout) +{ + Blocker::was_unblocked(did_timeout); + + calculate_remaining(); +} + +void Thread::SleepBlocker::calculate_remaining() +{ + if (!m_remaining) + return; + auto time_now = TimeManagement::the().monotonic_time(); + if (time_now < m_deadline.absolute_time()) + timespec_sub(m_deadline.absolute_time(), time_now, *m_remaining); + else + *m_remaining = {}; +} + +Thread::BlockResult Thread::SleepBlocker::block_result() +{ + auto result = Blocker::block_result(); + if (result == Thread::BlockResult::InterruptedByTimeout) + return Thread::BlockResult::WokeNormally; + return result; +} + +Thread::SelectBlocker::SelectBlocker(FDVector& fds) + : m_fds(fds) +{ + for (auto& fd_entry : m_fds) { + fd_entry.unblocked_flags = FileBlocker::BlockFlags::None; + + if (!m_should_block) + continue; + if (!fd_entry.description->block_condition().add_blocker(*this, &fd_entry)) + m_should_block = false; + m_registered_count++; + } +} + +Thread::SelectBlocker::~SelectBlocker() +{ + if (m_registered_count > 0) { + for (auto& fd_entry : m_fds) { + fd_entry.description->block_condition().remove_blocker(*this, &fd_entry); + if (--m_registered_count == 0) + break; + } + } +} + +void Thread::SelectBlocker::not_blocking(bool timeout_in_past) +{ + // Either the timeout was in the past or we didn't add all blockers + ASSERT(timeout_in_past || !m_should_block); + ScopedSpinLock lock(m_lock); + if (!m_did_unblock) { + m_did_unblock = true; + if (!timeout_in_past) { + auto count = collect_unblocked_flags(); + ASSERT(count > 0); + } + } +} + +bool Thread::SelectBlocker::unblock(bool from_add_blocker, void* data) +{ + ASSERT(data); // data is a pointer to an entry in the m_fds vector + auto& fd_info = *static_cast(data); + + { + ScopedSpinLock lock(m_lock); + if (m_did_unblock) + return false; + + auto unblock_flags = fd_info.description->should_unblock(fd_info.block_flags); + if (unblock_flags == BlockFlags::None) + return false; + + m_did_unblock = true; + + // We need to store unblock_flags here, otherwise someone else + // affecting this file descriptor could change the information + // between now and when was_unblocked is called! + fd_info.unblocked_flags = unblock_flags; + } + + // Only do this once for the first one + if (!from_add_blocker) + unblock_from_blocker(); + return true; +} + +size_t Thread::SelectBlocker::collect_unblocked_flags() +{ + size_t count = 0; + for (auto& fd_entry : m_fds) { + ASSERT(fd_entry.block_flags != FileBlocker::BlockFlags::None); + + // unblock will have set at least the first descriptor's unblock + // flags that triggered the unblock. Make sure we don't discard that + // information as it may have changed by now! + if (fd_entry.unblocked_flags == FileBlocker::BlockFlags::None) + fd_entry.unblocked_flags = fd_entry.description->should_unblock(fd_entry.block_flags); + + if (fd_entry.unblocked_flags != FileBlocker::BlockFlags::None) + count++; + } + return count; +} + +void Thread::SelectBlocker::was_unblocked(bool did_timeout) +{ + Blocker::was_unblocked(did_timeout); + if (!did_timeout && !was_interrupted()) { + { + ScopedSpinLock lock(m_lock); + ASSERT(m_did_unblock); + } + size_t count = collect_unblocked_flags(); + // If we were blocked and didn't time out, we should have at least one unblocked fd! + ASSERT(count > 0); + } +} + +void Thread::WaitBlockCondition::try_unblock(Thread::WaitBlocker& blocker) +{ + ScopedSpinLock lock(m_lock); + // We if we have any processes pending + for (size_t i = 0; i < m_threads.size(); i++) { + auto& info = m_threads[i]; + // We need to call unblock as if we were called from add_blocker + // so that we don't trigger a context switch by yielding! + if (info.was_waited && blocker.is_wait()) + continue; // This state was already waited on, do not unblock + if (blocker.unblock(info.thread, info.flags, info.signal, true)) { + if (blocker.is_wait()) { + if (info.flags == Thread::WaitBlocker::UnblockFlags::Terminated) + m_threads.remove(i); + else + info.was_waited = true; + } + break; + } + } +} + +bool Thread::WaitBlockCondition::unblock(Thread& thread, WaitBlocker::UnblockFlags flags, u8 signal) +{ + bool did_unblock_any = false; + bool did_wait = false; + bool was_waited_already = false; + + ScopedSpinLock lock(m_lock); + if (m_finalized) + return false; + if (flags != WaitBlocker::UnblockFlags::Terminated) { + // First check if this state was already waited on + for (auto& info : m_threads) { + if (info.thread == &thread) { + was_waited_already = info.was_waited; + break; + } + } + } + + do_unblock([&](Blocker& b, void*) { + ASSERT(b.blocker_type() == Blocker::Type::Wait); + auto& blocker = static_cast(b); + if (was_waited_already && blocker.is_wait()) + return false; // This state was already waited on, do not unblock + if (blocker.unblock(thread, flags, signal, false)) { + did_wait |= blocker.is_wait(); // anyone requesting a wait + did_unblock_any = true; + return true; + } + return false; + }); + + // If no one has waited (yet), or this wasn't a wait, or if it's anything other than + // UnblockFlags::Terminated then add it to your list + if (!did_unblock_any || !did_wait || flags != WaitBlocker::UnblockFlags::Terminated) { + bool updated_existing = false; + for (auto& info : m_threads) { + if (info.thread == &thread) { + ASSERT(info.flags != WaitBlocker::UnblockFlags::Terminated); + info.flags = flags; + info.signal = signal; + info.was_waited = did_wait; + updated_existing = true; + break; + } + } + if (!updated_existing) + m_threads.append(ThreadBlockInfo(thread, flags, signal)); + } + return did_unblock_any; +} + +bool Thread::WaitBlockCondition::should_add_blocker(Blocker& b, void*) +{ + // NOTE: m_lock is held already! + if (m_finalized) + return false; + ASSERT(b.blocker_type() == Blocker::Type::Wait); + auto& blocker = static_cast(b); + // See if we can match any process immediately + for (size_t i = 0; i < m_threads.size(); i++) { + auto& info = m_threads[i]; + if (blocker.unblock(info.thread, info.flags, info.signal, true)) { + // Only remove the entry if UnblockFlags::Terminated + if (info.flags == Thread::WaitBlocker::UnblockFlags::Terminated && blocker.is_wait()) + m_threads.remove(i); + return false; + } + } + return true; +} + +void Thread::WaitBlockCondition::finalize() +{ + ScopedSpinLock lock(m_lock); + ASSERT(!m_finalized); + m_finalized = true; + + // Clear the list of threads here so we can drop the references to them + m_threads.clear(); + + // No more waiters, drop the last reference immediately. This may + // cause us to be destructed ourselves! + ASSERT(m_process.ref_count() > 0); + m_process.unref(); +} + +Thread::WaitBlocker::WaitBlocker(int wait_options, idtype_t id_type, pid_t id, KResultOr& result) + : m_wait_options(wait_options) + , m_id_type(id_type) + , m_waitee_id(id) + , m_result(result) + , m_should_block(!(m_wait_options & WNOHANG)) +{ + switch (id_type) { + case P_PID: { + m_waitee = Process::from_pid(m_waitee_id); + if (!m_waitee || m_waitee->ppid() != Process::current()->pid()) { + m_result = KResult(-ECHILD); + m_error = true; + return; + } + break; + } + case P_PGID: { + m_waitee_group = ProcessGroup::from_pgid(m_waitee_id); + if (!m_waitee_group) { + m_result = KResult(-ECHILD); + m_error = true; + return; + } + break; + } + case P_ALL: + break; + default: + ASSERT_NOT_REACHED(); + } + + // NOTE: unblock may be called within set_block_condition, in which + // case it means that we already have a match without having to block. + // In that case set_block_condition will return false. + if (m_error || !set_block_condition(Process::current()->wait_block_condition())) + m_should_block = false; +} + +void Thread::WaitBlocker::not_blocking(bool timeout_in_past) +{ + ASSERT(timeout_in_past || !m_should_block); + if (!m_error) + Process::current()->wait_block_condition().try_unblock(*this); +} + +void Thread::WaitBlocker::was_unblocked(bool) +{ + bool got_sigchld, try_unblock; + { + ScopedSpinLock lock(m_lock); + try_unblock = !m_did_unblock; + got_sigchld = m_got_sigchild; + } + + if (try_unblock) + Process::current()->wait_block_condition().try_unblock(*this); + + // If we were interrupted by SIGCHLD (which gets special handling + // here) we're not going to return with EINTR. But we're going to + // deliver SIGCHLD (only) here. + auto* current_thread = Thread::current(); + if (got_sigchld && current_thread->state() != State::Stopped) + current_thread->try_dispatch_one_pending_signal(SIGCHLD); +} + +void Thread::WaitBlocker::do_set_result(const siginfo_t& result) +{ + ASSERT(!m_did_unblock); + m_did_unblock = true; + m_result = result; + + if (do_get_interrupted_by_signal() == SIGCHLD) { + // This makes it so that wait() will return normally despite the + // fact that SIGCHLD was delivered. Calling do_clear_interrupted_by_signal + // will disable dispatching signals in Thread::block and prevent + // it from returning with EINTR. We will then manually dispatch + // SIGCHLD (and only SIGCHLD) in was_unblocked. + m_got_sigchild = true; + do_clear_interrupted_by_signal(); + } +} + +bool Thread::WaitBlocker::unblock(Thread& thread, UnblockFlags flags, u8 signal, bool from_add_blocker) +{ + ASSERT(flags != UnblockFlags::Terminated || signal == 0); // signal argument should be ignored for Terminated + + auto& process = thread.process(); + switch (m_id_type) { + case P_PID: + ASSERT(m_waitee); + if (process.pid() != m_waitee_id && thread.tid() != m_waitee_id) // TODO: pid/tid + return false; + break; + case P_PGID: + ASSERT(m_waitee_group); + if (process.pgid() != m_waitee_group->pgid()) + return false; + break; + case P_ALL: + break; + default: + ASSERT_NOT_REACHED(); + } + + switch (flags) { + case UnblockFlags::Terminated: + if (!(m_wait_options & WEXITED)) + return false; + break; + case UnblockFlags::Stopped: + if (!(m_wait_options & WSTOPPED)) + return false; + if (!(m_wait_options & WUNTRACED) && !thread.is_traced()) + return false; + break; + case UnblockFlags::Continued: + if (!(m_wait_options & WCONTINUED)) + return false; + if (!(m_wait_options & WUNTRACED) && !thread.is_traced()) + return false; + break; + } + + if (flags == UnblockFlags::Terminated) { + ASSERT(process.is_dead()); + + ScopedSpinLock lock(m_lock); + if (m_did_unblock) + return false; + // Up until this point, this function may have been called + // more than once! + do_set_result(process.wait_info()); + } else { + siginfo_t siginfo; + memset(&siginfo, 0, sizeof(siginfo)); + { + ScopedSpinLock lock(g_scheduler_lock); + // We need to gather the information before we release the sheduler lock! + siginfo.si_signo = SIGCHLD; + siginfo.si_pid = thread.tid().value(); + siginfo.si_uid = process.uid(); + siginfo.si_status = signal; + + switch (flags) { + case UnblockFlags::Terminated: + ASSERT_NOT_REACHED(); + case UnblockFlags::Stopped: + siginfo.si_code = CLD_STOPPED; + break; + case UnblockFlags::Continued: + siginfo.si_code = CLD_CONTINUED; + break; + } + } + + ScopedSpinLock lock(m_lock); + if (m_did_unblock) + return false; + // Up until this point, this function may have been called + // more than once! + do_set_result(siginfo); + } + + if (!from_add_blocker) { + // Only call unblock if we weren't called from within set_block_condition! + unblock_from_blocker(); + } + // Because this may be called from add_blocker, in which case we should + // not be actually trying to unblock the thread (because it hasn't actually + // been blocked yet), we need to return true anyway + return true; +} + +} diff --git a/Kernel/UnixTypes.h b/Kernel/UnixTypes.h index 2d50eb65c6..d942603044 100644 --- a/Kernel/UnixTypes.h +++ b/Kernel/UnixTypes.h @@ -71,6 +71,7 @@ enum { #define WSTOPPED WUNTRACED #define WEXITED 4 #define WCONTINUED 8 +#define WNOWAIT 0x1000000 #define R_OK 4 #define W_OK 2 @@ -427,11 +428,12 @@ struct stat { }; #define POLLIN (1u << 0) -#define POLLPRI (1u << 2) -#define POLLOUT (1u << 3) -#define POLLERR (1u << 4) -#define POLLHUP (1u << 5) -#define POLLNVAL (1u << 6) +#define POLLPRI (1u << 1) +#define POLLOUT (1u << 2) +#define POLLERR (1u << 3) +#define POLLHUP (1u << 4) +#define POLLNVAL (1u << 5) +#define POLLRDHUP (1u << 13) struct pollfd { int fd; diff --git a/Libraries/LibC/poll.h b/Libraries/LibC/poll.h index af0ff254c5..ff0a1112b7 100644 --- a/Libraries/LibC/poll.h +++ b/Libraries/LibC/poll.h @@ -32,11 +32,12 @@ __BEGIN_DECLS #define POLLIN (1u << 0) -#define POLLPRI (1u << 2) -#define POLLOUT (1u << 3) -#define POLLERR (1u << 4) -#define POLLHUP (1u << 5) -#define POLLNVAL (1u << 6) +#define POLLPRI (1u << 1) +#define POLLOUT (1u << 2) +#define POLLERR (1u << 3) +#define POLLHUP (1u << 4) +#define POLLNVAL (1u << 5) +#define POLLRDHUP (1u << 13) struct pollfd { int fd; diff --git a/Libraries/LibC/sys/wait.cpp b/Libraries/LibC/sys/wait.cpp index 88e06522a6..9969a8eec2 100644 --- a/Libraries/LibC/sys/wait.cpp +++ b/Libraries/LibC/sys/wait.cpp @@ -56,12 +56,22 @@ pid_t waitpid(pid_t waitee, int* wstatus, int options) id = waitee; } + // To be able to detect if a child was found when WNOHANG is set, + // we need to clear si_pid, which will only be set if it was found. + siginfo.si_pid = 0; int rc = waitid(idtype, id, &siginfo, options | WEXITED); if (rc < 0) return rc; if (wstatus) { + if ((options & WNOHANG) && siginfo.si_pid == 0) { + // No child in a waitable state was found. All other fields + // in siginfo are undefined + *wstatus = 0; + return 0; + } + switch (siginfo.si_code) { case CLD_EXITED: *wstatus = siginfo.si_status << 8; diff --git a/Libraries/LibC/sys/wait.h b/Libraries/LibC/sys/wait.h index fadc12d780..825453292a 100644 --- a/Libraries/LibC/sys/wait.h +++ b/Libraries/LibC/sys/wait.h @@ -44,6 +44,7 @@ __BEGIN_DECLS #define WSTOPPED WUNTRACED #define WEXITED 4 #define WCONTINUED 8 +#define WNOWAIT 0x1000000 typedef enum { P_ALL = 1, diff --git a/Userland/strace.cpp b/Userland/strace.cpp index 6922218bb7..00bd6fbeec 100644 --- a/Userland/strace.cpp +++ b/Userland/strace.cpp @@ -53,7 +53,6 @@ static void handle_sigint(int) int main(int argc, char** argv) { Vector child_argv; - bool spawned_new_process = false; Core::ArgsParser parser; parser.add_option(g_pid, "Trace the given PID", "pid", 'p', "pid"); @@ -61,6 +60,7 @@ int main(int argc, char** argv) parser.parse(argc, argv); + int status; if (g_pid == -1) { if (child_argv.is_empty()) { fprintf(stderr, "strace: Expected either a pid or some arguments\n"); @@ -68,7 +68,6 @@ int main(int argc, char** argv) } child_argv.append(nullptr); - spawned_new_process = true; int pid = fork(); if (pid < 0) { perror("fork"); @@ -89,7 +88,7 @@ int main(int argc, char** argv) } g_pid = pid; - if (waitpid(pid, nullptr, WSTOPPED) != pid) { + if (waitpid(pid, &status, WSTOPPED | WEXITED) != pid || !WIFSTOPPED(status)) { perror("waitpid"); return 1; } @@ -104,66 +103,42 @@ int main(int argc, char** argv) perror("attach"); return 1; } - - if (waitpid(g_pid, nullptr, WSTOPPED) != g_pid) { + if (waitpid(g_pid, &status, WSTOPPED | WEXITED) != g_pid || !WIFSTOPPED(status)) { perror("waitpid"); return 1; } - if (spawned_new_process) { - - if (ptrace(PT_CONTINUE, g_pid, 0, 0) < 0) { - perror("continue"); - return 1; - } - if (waitpid(g_pid, nullptr, WSTOPPED) != g_pid) { - perror("wait_pid"); - return 1; - } - } - for (;;) { if (ptrace(PT_SYSCALL, g_pid, 0, 0) == -1) { - if (errno == ESRCH) - return 0; perror("syscall"); return 1; } - if (waitpid(g_pid, nullptr, WSTOPPED) != g_pid) { + if (waitpid(g_pid, &status, WSTOPPED | WEXITED) != g_pid || !WIFSTOPPED(status)) { perror("wait_pid"); return 1; } - PtraceRegisters regs = {}; if (ptrace(PT_GETREGS, g_pid, ®s, 0) == -1) { perror("getregs"); return 1; } - u32 syscall_index = regs.eax; u32 arg1 = regs.edx; u32 arg2 = regs.ecx; u32 arg3 = regs.ebx; - // skip syscall exit if (ptrace(PT_SYSCALL, g_pid, 0, 0) == -1) { - if (errno == ESRCH) - return 0; perror("syscall"); return 1; } - if (waitpid(g_pid, nullptr, WSTOPPED) != g_pid) { + if (waitpid(g_pid, &status, WSTOPPED | WEXITED) != g_pid || !WIFSTOPPED(status)) { perror("wait_pid"); return 1; } if (ptrace(PT_GETREGS, g_pid, ®s, 0) == -1) { - if (errno == ESRCH && syscall_index == SC_exit) { - regs.eax = 0; - } else { - perror("getregs"); - return 1; - } + perror("getregs"); + return 1; } u32 res = regs.eax;