mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-04 05:20:30 +00:00
Rework process states to make a bit more sense.
Processes are either alive (with many substates), dead or forgiven. A dead process is forgiven when the parent waitpid()s on it. Dead orphans are also forgiven. There's a lot of work to be done around this.
This commit is contained in:
parent
71bffa9864
commit
678882e020
Notes:
sideshowbarker
2024-07-19 18:32:27 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/678882e0201
4 changed files with 154 additions and 93 deletions
|
@ -705,15 +705,6 @@ void Process::dumpRegions()
|
|||
}
|
||||
}
|
||||
|
||||
void Process::notify_waiters(pid_t waitee, int exit_status, int signal)
|
||||
{
|
||||
ASSERT_INTERRUPTS_DISABLED();
|
||||
for (auto* process = s_processes->head(); process; process = process->next()) {
|
||||
if (process->waitee() == waitee)
|
||||
process->m_waiteeStatus = (exit_status << 8) | (signal);
|
||||
}
|
||||
}
|
||||
|
||||
void Process::sys$exit(int status)
|
||||
{
|
||||
cli();
|
||||
|
@ -721,59 +712,76 @@ void Process::sys$exit(int status)
|
|||
kprintf("sys$exit: %s(%u) exit with status %d\n", name().characters(), pid(), status);
|
||||
#endif
|
||||
|
||||
set_state(Exiting);
|
||||
|
||||
s_processes->remove(this);
|
||||
|
||||
notify_waiters(m_pid, status, 0);
|
||||
set_state(Dead);
|
||||
m_termination_status = status;
|
||||
m_termination_signal = 0;
|
||||
|
||||
if (!scheduleNewProcess()) {
|
||||
kprintf("Process::sys$exit: Failed to schedule a new process :(\n");
|
||||
HANG;
|
||||
}
|
||||
|
||||
s_deadProcesses->append(this);
|
||||
|
||||
switchNow();
|
||||
}
|
||||
|
||||
void Process::terminate_due_to_signal(int signal, Process* sender)
|
||||
void Process::terminate_due_to_signal(byte signal)
|
||||
{
|
||||
ASSERT_INTERRUPTS_DISABLED();
|
||||
bool wasCurrent = this == current;
|
||||
|
||||
set_state(Exiting);
|
||||
s_processes->remove(this);
|
||||
|
||||
notify_waiters(m_pid, 0, signal);
|
||||
|
||||
if (wasCurrent) {
|
||||
kprintf("Current process (%u) committing suicide!\n", pid());
|
||||
if (!scheduleNewProcess()) {
|
||||
kprintf("Process::send_signal: Failed to schedule a new process :(\n");
|
||||
HANG;
|
||||
}
|
||||
}
|
||||
s_deadProcesses->append(this);
|
||||
if (wasCurrent)
|
||||
switchNow();
|
||||
ASSERT(signal < 32);
|
||||
dbgprintf("terminate_due_to_signal %s(%u) <- %u\n", name().characters(), pid(), signal);
|
||||
m_termination_status = 0;
|
||||
m_termination_signal = signal;
|
||||
set_state(Dead);
|
||||
}
|
||||
|
||||
void Process::send_signal(int signal, Process* sender)
|
||||
void Process::send_signal(byte signal, Process* sender)
|
||||
{
|
||||
ASSERT_INTERRUPTS_DISABLED();
|
||||
ASSERT(signal < 32);
|
||||
|
||||
// FIXME: Handle send_signal to self.
|
||||
ASSERT(this != sender);
|
||||
m_pending_signals |= 1 << signal;
|
||||
|
||||
if (sender)
|
||||
dbgprintf("signal: %s(%u) sent %d to %s(%u)\n", sender->name().characters(), sender->pid(), signal, name().characters(), pid());
|
||||
else
|
||||
dbgprintf("signal: kernel sent %d to %s(%u)\n", signal, name().characters(), pid());
|
||||
}
|
||||
|
||||
bool Process::has_unmasked_pending_signals() const
|
||||
{
|
||||
return m_pending_signals & ~m_signal_mask;
|
||||
}
|
||||
|
||||
void Process::dispatch_one_pending_signal()
|
||||
{
|
||||
ASSERT_INTERRUPTS_DISABLED();
|
||||
dword signal_candidates = m_pending_signals & ~m_signal_mask;
|
||||
ASSERT(signal_candidates);
|
||||
|
||||
byte signal = 0;
|
||||
for (; signal < 32; ++signal) {
|
||||
if (signal_candidates & (1 << signal)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
dispatch_signal(signal);
|
||||
}
|
||||
|
||||
void Process::dispatch_signal(byte signal)
|
||||
{
|
||||
ASSERT_INTERRUPTS_DISABLED();
|
||||
ASSERT(signal < 32);
|
||||
|
||||
dbgprintf("dispatch_signal %s(%u) <- %u\n", name().characters(), pid(), signal);
|
||||
|
||||
auto& action = m_signal_action_data[signal];
|
||||
// FIXME: Implement SA_SIGINFO signal handlers.
|
||||
ASSERT(!(action.flags & SA_SIGINFO));
|
||||
|
||||
auto handler_laddr = action.handler_or_sigaction;
|
||||
if (handler_laddr.is_null())
|
||||
return terminate_due_to_signal(signal, sender);
|
||||
if (handler_laddr.is_null()) {
|
||||
// FIXME: Is termination really always the appropriate action?
|
||||
return terminate_due_to_signal(signal);
|
||||
}
|
||||
|
||||
word ret_cs = m_tss.cs;
|
||||
dword ret_eip = m_tss.eip;
|
||||
|
@ -781,6 +789,7 @@ void Process::send_signal(int signal, Process* sender)
|
|||
|
||||
if ((ret_cs & 3) == 0) {
|
||||
// FIXME: Handle send_signal to process currently in kernel code.
|
||||
kprintf("Boo! dispatch_signal with return to %w:%x\n", ret_cs, ret_eip);
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
|
@ -801,7 +810,9 @@ void Process::send_signal(int signal, Process* sender)
|
|||
m_tss.eip = handler_laddr.get();
|
||||
|
||||
if (m_return_from_signal_trampoline.is_null()) {
|
||||
auto* region = allocate_region(LinearAddress(), PAGE_SIZE, "signal_trampoline", true, true); // FIXME: Remap as read-only after setup.
|
||||
// FIXME: This should be a global trampoline shared by all processes, not one created per process!
|
||||
// FIXME: Remap as read-only after setup.
|
||||
auto* region = allocate_region(LinearAddress(), PAGE_SIZE, "signal_trampoline", true, true);
|
||||
m_return_from_signal_trampoline = region->linearAddress;
|
||||
byte* code_ptr = m_return_from_signal_trampoline.asPtr();
|
||||
*code_ptr++ = 0x61; // popa
|
||||
|
@ -809,16 +820,14 @@ void Process::send_signal(int signal, Process* sender)
|
|||
*code_ptr++ = 0xc3; // ret
|
||||
*code_ptr++ = 0x0f; // ud2
|
||||
*code_ptr++ = 0x0b;
|
||||
// FIXME: For !SA_NODEFER, maybe we could do something like emitting an int 0x80 syscall here that
|
||||
// unmasks the signal so it can be received again? I guess then I would need one trampoline
|
||||
// per signal number if it's hard-coded, but it's just a few bytes per each.
|
||||
}
|
||||
|
||||
push_value_on_stack(m_return_from_signal_trampoline.get());
|
||||
|
||||
dbgprintf("signal: %s(%u) sent %d to %s(%u)\n", sender->name().characters(), sender->pid(), signal, name().characters(), pid());
|
||||
|
||||
if (current == this) {
|
||||
sched_yield();
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
dbgprintf("signal: Okay, %s(%u) has been primed\n", name().characters(), pid());
|
||||
}
|
||||
|
||||
void Process::push_value_on_stack(dword value)
|
||||
|
@ -828,29 +837,19 @@ void Process::push_value_on_stack(dword value)
|
|||
*stack_ptr = value;
|
||||
}
|
||||
|
||||
void Process::processDidCrash(Process* crashedProcess)
|
||||
void Process::crash()
|
||||
{
|
||||
ASSERT_INTERRUPTS_DISABLED();
|
||||
ASSERT(state() != Dead);
|
||||
|
||||
if (crashedProcess->state() == Crashing) {
|
||||
kprintf("Double crash :(\n");
|
||||
HANG;
|
||||
}
|
||||
|
||||
crashedProcess->set_state(Crashing);
|
||||
crashedProcess->dumpRegions();
|
||||
|
||||
s_processes->remove(crashedProcess);
|
||||
|
||||
notify_waiters(crashedProcess->m_pid, 0, SIGSEGV);
|
||||
m_termination_signal = SIGSEGV;
|
||||
set_state(Dead);
|
||||
dumpRegions();
|
||||
|
||||
if (!scheduleNewProcess()) {
|
||||
kprintf("Process::processDidCrash: Failed to schedule a new process :(\n");
|
||||
kprintf("Process::crash: Failed to schedule a new process :(\n");
|
||||
HANG;
|
||||
}
|
||||
|
||||
s_deadProcesses->append(crashedProcess);
|
||||
|
||||
switchNow();
|
||||
}
|
||||
|
||||
|
@ -896,6 +895,30 @@ void switchNow()
|
|||
);
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
static void for_each_process_in_state(Process::State state, Callback callback)
|
||||
{
|
||||
ASSERT_INTERRUPTS_DISABLED();
|
||||
for (auto* process = s_processes->head(); process;) {
|
||||
auto* next_process = process->next();
|
||||
if (process->state() == state)
|
||||
callback(*process);
|
||||
process = next_process;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
static void for_each_process_not_in_state(Process::State state, Callback callback)
|
||||
{
|
||||
ASSERT_INTERRUPTS_DISABLED();
|
||||
for (auto* process = s_processes->head(); process;) {
|
||||
auto* next_process = process->next();
|
||||
if (process->state() != state)
|
||||
callback(*process);
|
||||
process = next_process;
|
||||
}
|
||||
}
|
||||
|
||||
bool scheduleNewProcess()
|
||||
{
|
||||
ASSERT_INTERRUPTS_DISABLED();
|
||||
|
@ -909,28 +932,55 @@ bool scheduleNewProcess()
|
|||
// Check and unblock processes whose wait conditions have been met.
|
||||
for (auto* process = s_processes->head(); process; process = process->next()) {
|
||||
if (process->state() == Process::BlockedSleep) {
|
||||
if (process->wakeupTime() <= system.uptime) {
|
||||
if (process->wakeupTime() <= system.uptime)
|
||||
process->unblock();
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (process->state() == Process::BlockedWait) {
|
||||
if (!Process::fromPID(process->waitee())) {
|
||||
process->unblock();
|
||||
continue;
|
||||
auto* waitee = Process::fromPID(process->waitee());
|
||||
if (!waitee) {
|
||||
kprintf("waitee %u of %s(%u) reaped before I could wait?\n", process->waitee(), process->name().characters(), process->pid());
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
if (waitee->state() == Process::Dead) {
|
||||
process->m_waitee_status = (waitee->m_termination_status << 8) | waitee->m_termination_signal;
|
||||
process->unblock();
|
||||
waitee->set_state(Process::Forgiven);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (process->state() == Process::BlockedRead) {
|
||||
ASSERT(process->m_fdBlockedOnRead != -1);
|
||||
if (process->m_file_descriptors[process->m_fdBlockedOnRead]->hasDataAvailableForRead()) {
|
||||
if (process->m_file_descriptors[process->m_fdBlockedOnRead]->hasDataAvailableForRead())
|
||||
process->unblock();
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Forgive dead orphans.
|
||||
// FIXME: Does this really make sense?
|
||||
for_each_process_in_state(Process::Dead, [] (auto& process) {
|
||||
if (!Process::fromPID(process.ppid()))
|
||||
process.set_state(Process::Forgiven);
|
||||
});
|
||||
|
||||
// Clean up forgiven processes.
|
||||
// FIXME: Do we really need this to be a separate pass over the process list?
|
||||
for_each_process_in_state(Process::Forgiven, [] (auto& process) {
|
||||
s_processes->remove(&process);
|
||||
s_deadProcesses->append(&process);
|
||||
});
|
||||
|
||||
// Dispatch any pending signals.
|
||||
// FIXME: Do we really need this to be a separate pass over the process list?
|
||||
for_each_process_not_in_state(Process::Dead, [] (auto& process) {
|
||||
if (!process.has_unmasked_pending_signals())
|
||||
return;
|
||||
process.dispatch_one_pending_signal();
|
||||
});
|
||||
|
||||
#ifdef SCHEDULER_DEBUG
|
||||
dbgprintf("Scheduler choices:\n");
|
||||
for (auto* process = s_processes->head(); process; process = process->next()) {
|
||||
|
@ -1352,11 +1402,11 @@ pid_t Process::sys$waitpid(pid_t waitee, int* wstatus, int options)
|
|||
if (!Process::fromPID(waitee))
|
||||
return -1;
|
||||
m_waitee = waitee;
|
||||
m_waiteeStatus = 0;
|
||||
m_waitee_status = 0;
|
||||
block(BlockedWait);
|
||||
sched_yield();
|
||||
if (wstatus)
|
||||
*wstatus = m_waiteeStatus;
|
||||
*wstatus = m_waitee_status;
|
||||
return m_waitee;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,15 +33,14 @@ public:
|
|||
|
||||
enum State {
|
||||
Invalid = 0,
|
||||
Runnable = 1,
|
||||
Running = 2,
|
||||
Terminated = 3,
|
||||
Crashing = 4,
|
||||
Exiting = 5,
|
||||
BeingInspected = 6,
|
||||
BlockedSleep = 7,
|
||||
BlockedWait = 8,
|
||||
BlockedRead = 9,
|
||||
Runnable,
|
||||
Running,
|
||||
Dead,
|
||||
Forgiven,
|
||||
BeingInspected,
|
||||
BlockedSleep,
|
||||
BlockedWait,
|
||||
BlockedRead,
|
||||
};
|
||||
|
||||
enum RingLevel {
|
||||
|
@ -145,7 +144,7 @@ public:
|
|||
|
||||
static void initialize();
|
||||
|
||||
static void processDidCrash(Process*);
|
||||
void crash();
|
||||
|
||||
const TTY* tty() const { return m_tty; }
|
||||
|
||||
|
@ -172,8 +171,11 @@ public:
|
|||
size_t number_of_open_file_descriptors() const;
|
||||
size_t max_open_file_descriptors() const { return m_max_open_file_descriptors; }
|
||||
|
||||
void send_signal(int signal, Process* sender);
|
||||
void terminate_due_to_signal(int signal, Process* sender);
|
||||
void send_signal(byte signal, Process* sender);
|
||||
void dispatch_one_pending_signal();
|
||||
void dispatch_signal(byte signal);
|
||||
bool has_unmasked_pending_signals() const;
|
||||
void terminate_due_to_signal(byte signal);
|
||||
|
||||
Process* fork(RegisterDump&);
|
||||
int exec(const String& path, Vector<String>&& arguments, Vector<String>&& environment);
|
||||
|
@ -216,10 +218,15 @@ private:
|
|||
void* m_kernelStack { nullptr };
|
||||
dword m_timesScheduled { 0 };
|
||||
pid_t m_waitee { -1 };
|
||||
int m_waiteeStatus { 0 };
|
||||
int m_waitee_status { 0 };
|
||||
int m_fdBlockedOnRead { -1 };
|
||||
size_t m_max_open_file_descriptors { 16 };
|
||||
SignalActionData m_signal_action_data[32];
|
||||
dword m_pending_signals { 0 };
|
||||
dword m_signal_mask { 0 };
|
||||
|
||||
byte m_termination_status { 0 };
|
||||
byte m_termination_signal { 0 };
|
||||
|
||||
RetainPtr<VirtualFileSystem::Node> m_cwd;
|
||||
RetainPtr<VirtualFileSystem::Node> m_executable;
|
||||
|
@ -272,9 +279,8 @@ static inline const char* toString(Process::State state)
|
|||
case Process::Invalid: return "Invalid";
|
||||
case Process::Runnable: return "Runnable";
|
||||
case Process::Running: return "Running";
|
||||
case Process::Terminated: return "Term";
|
||||
case Process::Crashing: return "Crash";
|
||||
case Process::Exiting: return "Exit";
|
||||
case Process::Dead: return "Dead";
|
||||
case Process::Forgiven: return "Forgiven";
|
||||
case Process::BlockedSleep: return "Sleep";
|
||||
case Process::BlockedWait: return "Wait";
|
||||
case Process::BlockedRead: return "Read";
|
||||
|
|
|
@ -147,7 +147,7 @@ void exception_6_handler(RegisterDump& regs)
|
|||
}
|
||||
HANG;
|
||||
|
||||
Process::processDidCrash(current);
|
||||
current->crash();
|
||||
}
|
||||
|
||||
// 13: General Protection Fault
|
||||
|
@ -176,7 +176,7 @@ void exception_13_handler(RegisterDumpWithExceptionCode& regs)
|
|||
HANG;
|
||||
}
|
||||
|
||||
Process::processDidCrash(current);
|
||||
current->crash();
|
||||
}
|
||||
|
||||
// 14: Page Fault
|
||||
|
@ -232,7 +232,7 @@ void exception_14_handler(RegisterDumpWithExceptionCode& regs)
|
|||
|
||||
if (response == PageFaultResponse::ShouldCrash) {
|
||||
kprintf("Crashing after unresolved page fault\n");
|
||||
Process::processDidCrash(current);
|
||||
current->crash();
|
||||
} else if (response == PageFaultResponse::Continue) {
|
||||
#ifdef PAGE_FAULT_DEBUG
|
||||
dbgprintf("Continuing after resolved page fault\n");
|
||||
|
|
|
@ -15,6 +15,7 @@ OBJS = \
|
|||
mm.o \
|
||||
kill.o \
|
||||
ft.o \
|
||||
ft2.o \
|
||||
strsignal.o \
|
||||
tty.o
|
||||
|
||||
|
@ -35,6 +36,7 @@ APPS = \
|
|||
mm \
|
||||
kill \
|
||||
ft \
|
||||
ft2 \
|
||||
strsignal \
|
||||
tty
|
||||
|
||||
|
@ -98,6 +100,9 @@ tst: tst.o
|
|||
ft: ft.o
|
||||
$(LD) -o $@ $(LDFLAGS) $< ../LibC/LibC.a
|
||||
|
||||
ft2: ft2.o
|
||||
$(LD) -o $@ $(LDFLAGS) $< ../LibC/LibC.a
|
||||
|
||||
mm: mm.o
|
||||
$(LD) -o $@ $(LDFLAGS) $< ../LibC/LibC.a
|
||||
|
||||
|
|
Loading…
Reference in a new issue