libc: use monitor for fork

Issue #3874
This commit is contained in:
Christian Helmuth 2020-09-03 14:07:41 +02:00 committed by Norman Feske
parent a891b3832c
commit a0a112ffe7
5 changed files with 82 additions and 183 deletions

View File

@ -12,7 +12,6 @@
*/
/* Genode includes */
#include <base/log.h>
#include <base/thread.h>
#include <base/registry.h>
#include <base/child.h>
@ -34,9 +33,7 @@
/* libc-internal includes */
#include <internal/init.h>
#include <internal/clone_session.h>
#include <internal/kernel_routine.h>
#include <internal/suspend.h>
#include <internal/resume.h>
#include <internal/monitor.h>
#include <internal/signal.h>
namespace Libc {
@ -58,15 +55,15 @@ namespace Libc {
using namespace Libc;
namespace { using Fn = Monitor::Function_result; }
static pid_t fork_result;
static Env *_env_ptr;
static Allocator *_alloc_ptr;
static Suspend *_suspend_ptr;
static Resume *_resume_ptr;
static Monitor *_monitor_ptr;
static Libc::Signal *_signal_ptr;
static Kernel_routine_scheduler *_kernel_routine_scheduler_ptr;
static Heap *_malloc_heap_ptr;
static void *_user_stack_base_ptr;
static size_t _user_stack_size;
@ -77,6 +74,15 @@ static Binary_name const *_binary_name_ptr;
static Forked_children *_forked_children_ptr;
static Libc::Monitor & monitor()
{
struct Missing_call_of_init_fork : Genode::Exception { };
if (!_monitor_ptr)
throw Missing_call_of_init_fork();
return *_monitor_ptr;
}
struct Libc::Child_config
{
Constructible<Attached_ram_dataspace> _ds { };
@ -348,44 +354,35 @@ struct Libc::Local_clone_service : Noncopyable
typedef Local_service<Session> Service;
Child_ready &_child_ready;
Resume &_resume;
Io_signal_handler<Local_clone_service> _child_ready_handler;
void _handle_child_ready()
{
_child_ready.child_ready();
_resume.resume_all();
}
struct Factory : Local_service<Session>::Factory
{
Session &_session;
Signal_context_capability _started_sigh;
Session &_session;
Child_ready &_child_ready;
Factory(Session &session, Signal_context_capability started_sigh)
: _session(session), _started_sigh(started_sigh) { }
Factory(Session &session, Child_ready &child_ready)
: _session(session), _child_ready(child_ready) { }
Session &create(Args const &, Affinity) override { return _session; }
void upgrade(Session &, Args const &) override { }
void destroy(Session &) override { Signal_transmitter(_started_sigh).submit(); }
void destroy(Session &) override
{
Mutex mutex { };
mutex.acquire();
monitor().monitor(mutex, [&] {
_child_ready.child_ready();
return Fn::COMPLETE;
});
}
} _factory;
Service service { _factory };
Local_clone_service(Env &env, Entrypoint &ep, Child_ready &child_ready,
Resume &resume)
:
_session(env, ep), _child_ready(child_ready), _resume(resume),
_child_ready_handler(env.ep(), *this, &Local_clone_service::_handle_child_ready),
_factory(_session, _child_ready_handler)
{ }
Local_clone_service(Env &env, Entrypoint &ep, Child_ready &child_ready)
: _session(env, ep), _factory(_session, child_ready) { }
};
@ -395,8 +392,6 @@ struct Libc::Forked_child : Child_policy, Child_ready
Binary_name const _binary_name;
Resume &_resume;
Signal &_signal;
pid_t const _pid;
@ -408,8 +403,8 @@ struct Libc::Forked_child : Child_policy, Child_ready
Name const _name { _pid };
/*
* Signal handler triggered at the main entrypoint, waking up the libc
* suspend mechanism.
* Signal handler triggered at the main entrypoint, charging 'SIGCHILD'
* and waking up the libc monitor mechanism.
*/
Io_signal_handler<Libc::Forked_child> _exit_handler {
_env.ep(), *this, &Forked_child::_handle_exit };
@ -417,7 +412,7 @@ struct Libc::Forked_child : Child_policy, Child_ready
void _handle_exit()
{
_signal.charge(SIGCHLD);
_resume.resume_all();
monitor().trigger_monitor_examination();
}
Child_config _child_config;
@ -427,20 +422,6 @@ struct Libc::Forked_child : Child_policy, Child_ready
Local_clone_service _local_clone_service;
Local_rom_service _config_rom_service;
struct Wait_fork_ready : Kernel_routine
{
Forked_child const &child;
Wait_fork_ready(Forked_child const &child) : child(child) { }
void execute_in_kernel() override
{
/* keep executing this kernel routine until child is running */
if (!child.running() && !child.exited())
_kernel_routine_scheduler_ptr->register_kernel_routine(*this);
}
} wait_fork_ready { *this };
pid_t pid() const { return _pid; }
bool running() const { return _state == State::RUNNING; }
@ -459,7 +440,7 @@ struct Libc::Forked_child : Child_policy, Child_ready
/*
* Don't modify the state if the child already exited.
* This can happen for short-lived children where the asynchronous
* notification for '_handle_exit' arrives before '_handle_child_ready'
* notification for 'Child_exit' arrives before 'Child_ready'
* (while the parent is still blocking in the fork call).
*/
if (_state == State::STARTING_UP)
@ -544,6 +525,10 @@ struct Libc::Forked_child : Child_policy, Child_ready
_exit_code = code;
_state = State::EXITED;
/*
* We can't destroy the child object in a monitor from this RPC
* function as this would deadlock in an 'Entrypoint::dissolve()'.
*/
Signal_transmitter(_exit_handler).submit();
}
@ -553,7 +538,6 @@ struct Libc::Forked_child : Child_policy, Child_ready
Entrypoint &fork_ep,
Allocator &alloc,
Binary_name const &binary_name,
Resume &resume,
Signal &signal,
pid_t pid,
Config_accessor const &config_accessor,
@ -561,11 +545,11 @@ struct Libc::Forked_child : Child_policy, Child_ready
Local_rom_services &local_rom_services)
:
_env(env), _binary_name(binary_name),
_resume(resume), _signal(signal), _pid(pid),
_signal(signal), _pid(pid),
_child_config(env, config_accessor, pid),
_parent_services(parent_services),
_local_rom_services(local_rom_services),
_local_clone_service(env, fork_ep, *this, resume),
_local_clone_service(env, fork_ep, *this),
_config_rom_service(fork_ep, "config", _child_config.ds_cap()),
_child(env.rm(), fork_ep.rpc_ep(), *this)
{ }
@ -574,7 +558,7 @@ struct Libc::Forked_child : Child_policy, Child_ready
};
static void fork_kernel_routine()
static Forked_child * fork_kernel_routine()
{
fork_result = 0;
@ -585,7 +569,6 @@ static void fork_kernel_routine()
Env &env = *_env_ptr;
Allocator &alloc = *_alloc_ptr;
Resume &resume = *_resume_ptr;
Libc::Signal &signal = *_signal_ptr;
pid_t const child_pid = ++_pid_cnt;
@ -597,15 +580,15 @@ static void fork_kernel_routine()
static Local_rom_services local_rom_services(env, fork_ep, alloc);
Registered<Forked_child> &child = *new (alloc)
Registered<Forked_child> *child = new (alloc)
Registered<Forked_child>(*_forked_children_ptr, env, fork_ep, alloc,
*_binary_name_ptr, resume,
*_binary_name_ptr,
signal, child_pid, *_config_accessor_ptr,
parent_services, local_rom_services);
fork_result = child_pid;
_kernel_routine_scheduler_ptr->register_kernel_routine(child.wait_fork_ready);
return child;
}
@ -627,25 +610,28 @@ extern "C" pid_t __sys_fork(void)
_user_stack_base_ptr = (void *)mystack.base;
_user_stack_size = mystack.top - mystack.base;
struct Fork_kernel_routine : Kernel_routine
{
void execute_in_kernel() override { fork_kernel_routine(); }
Mutex mutex { };
mutex.acquire();
} kernel_routine { };
enum class Stage { FORK, WAIT_FORK_READY };
Stage stage { Stage::FORK };
Forked_child *child { nullptr };
struct Missing_call_of_init_fork : Exception { };
if (!_kernel_routine_scheduler_ptr || !_suspend_ptr)
throw Missing_call_of_init_fork();
monitor().monitor(mutex, [&] {
switch (stage) {
case Stage::FORK:
child = fork_kernel_routine();
stage = Stage::WAIT_FORK_READY; [[ fallthrough ]]
case Stage::WAIT_FORK_READY:
if (child->running() || child->exited()) {
return Fn::COMPLETE;
}
break;
}
_kernel_routine_scheduler_ptr->register_kernel_routine(kernel_routine);
struct Suspend_functor_impl : Suspend_functor
{
bool suspend() override { return false; }
} suspend_functor { };
_suspend_ptr->suspend(suspend_functor, 0);
return Fn::INCOMPLETE;
});
return fork_result;
}
@ -669,15 +655,15 @@ pid_t getpid(void) __attribute__((weak, alias("__sys_getpid")));
** wait4 **
***********/
namespace Libc { struct Wait4_suspend_functor; }
namespace Libc { struct Wait4_functor; }
struct Libc::Wait4_suspend_functor : Suspend_functor
struct Libc::Wait4_functor
{
Forked_children &_children;
pid_t const _pid;
Wait4_suspend_functor(pid_t pid, Forked_children &children)
Wait4_functor(pid_t pid, Forked_children &children)
: _children(children), _pid(pid) { }
template <typename FN>
@ -700,14 +686,6 @@ struct Libc::Wait4_suspend_functor : Suspend_functor
fn(*child_ptr);
return true;
}
bool suspend() override
{
bool const any_child_exited =
with_exited_child([] (Forked_child const &) { });
return !any_child_exited;
}
};
@ -724,25 +702,23 @@ extern "C" pid_t __sys_wait4(pid_t pid, int *status, int options, rusage *rusage
return -1;
}
struct Missing_call_of_init_fork : Exception { };
if (!_suspend_ptr)
throw Missing_call_of_init_fork();
Wait4_functor functor { pid, *_forked_children_ptr };
Wait4_suspend_functor suspend_functor { pid, *_forked_children_ptr };
Mutex mutex { };
mutex.acquire();
for (;;) {
suspend_functor.with_exited_child([&] (Registered<Forked_child> &child) {
monitor().monitor(mutex, [&] {
functor.with_exited_child([&] (Registered<Forked_child> &child) {
result = child.pid();
exit_code = child.exit_code();
destroy(*_alloc_ptr, &child);
});
if (result >= 0 || (options & WNOHANG))
break;
return Fn::COMPLETE;
_suspend_ptr->suspend(suspend_functor, 0);
}
return Fn::INCOMPLETE;
});
/*
* The libc expects status information in bits 0..6 and the exit value
@ -761,16 +737,13 @@ extern "C" pid_t wait4(pid_t, int *, int, rusage *) __attribute__((weak, alias("
void Libc::init_fork(Env &env, Config_accessor const &config_accessor,
Allocator &alloc, Heap &malloc_heap, pid_t pid,
Suspend &suspend, Resume &resume, Signal &signal,
Kernel_routine_scheduler &kernel_routine_scheduler,
Monitor &monitor, Signal &signal,
Binary_name const &binary_name)
{
_env_ptr = &env;
_alloc_ptr = &alloc;
_suspend_ptr = &suspend;
_resume_ptr = &resume;
_monitor_ptr = &monitor;
_signal_ptr = &signal;
_kernel_routine_scheduler_ptr = &kernel_routine_scheduler;
_malloc_heap_ptr = &malloc_heap;
_config_accessor_ptr = &config_accessor;
_pid = pid;

View File

@ -35,7 +35,6 @@ namespace Libc {
struct Current_time;
struct Current_real_time;
struct Clone_connection;
struct Kernel_routine_scheduler;
struct Watch;
struct Signal;
struct File_descriptor_allocator;
@ -126,8 +125,7 @@ namespace Libc {
*/
void init_fork(Genode::Env &, Config_accessor const &,
Genode::Allocator &heap, Heap &malloc_heap, int pid,
Suspend &, Resume &, Signal &, Kernel_routine_scheduler &,
Binary_name const &);
Monitor &, Signal &, Binary_name const &);
struct Reset_malloc_heap : Interface
{

View File

@ -34,7 +34,6 @@
#include <internal/suspend.h>
#include <internal/resume.h>
#include <internal/select.h>
#include <internal/kernel_routine.h>
#include <internal/current_time.h>
#include <internal/kernel_timer_accessor.h>
#include <internal/watch.h>
@ -108,7 +107,6 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
Suspend,
Monitor,
Select,
Kernel_routine_scheduler,
Current_time,
Current_real_time,
Watch,
@ -236,8 +234,6 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
Select_handler_base *_scheduled_select_handler = nullptr;
Kernel_routine *_kernel_routine = nullptr;
void _resume_main() { _resume_main_once = true; }
Kernel_timer_accessor _timer_accessor { _env };
@ -356,7 +352,7 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
exit(1);
}
if (!check.suspend() && !_kernel_routine)
if (!check.suspend())
return timeout_ms;
if (timeout_ms > 0)
@ -425,6 +421,7 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
/* _setjmp() returned directly -> switch to user stack and call application code */
if (_cloned) {
_main_monitor_job->complete();
_switch_to_user();
} else {
_state = USER;
@ -438,22 +435,6 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
while ((!_app_returned)) {
if (_kernel_routine) {
Kernel_routine &routine = *_kernel_routine;
/* the 'kernel_routine' may install another kernel routine */
_kernel_routine = nullptr;
routine.execute_in_kernel();
if (!_kernel_routine) {
_switch_to_user();
}
if (_kernel_routine) {
_env.ep().wait_and_dispatch_one_io_signal();
}
}
/*
* Dispatch all pending I/O signals at once and execute
* monitors that may now become able to complete.
@ -521,7 +502,7 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
/*
* Return to the application
*/
if (!_kernel_routine && _resume_main_once && !_setjmp(_kernel_context)) {
if (_resume_main_once && !_setjmp(_kernel_context)) {
_switch_to_user();
}
}
@ -701,14 +682,6 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
void handle_io_progress() override;
/**
* Kernel_routine_scheduler interface
*/
void register_kernel_routine(Kernel_routine &kernel_routine) override
{
_kernel_routine = &kernel_routine;
}
/********************************
** Access to kernel singleton **

View File

@ -1,46 +0,0 @@
/*
* \brief Interface executing code in the context of the libc kernel
* \author Norman Feske
* \date 2019-09-18
*/
/*
* Copyright (C) 2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _LIBC__INTERNAL__KERNEL_ROUTINE_H_
#define _LIBC__INTERNAL__KERNEL_ROUTINE_H_
/* libc-internal includes */
#include <internal/types.h>
namespace Libc {
/**
* Base class to be implemented by a kernel routine
*/
struct Kernel_routine : Interface
{
virtual void execute_in_kernel() = 0;
};
struct Kernel_routine_scheduler : Interface
{
/**
* Register routine to be called once on the next libc-kernel activation
*
* The specified routine is executed only once. For a repeated execution,
* the routine must call 'register_kernel_routine' with itself as
* argument.
*
* This mechanism is used by 'fork' to implement the blocking for the
* startup of a new child and for 'wait4'.
*/
virtual void register_kernel_routine(Kernel_routine &) = 0;
};
}
#endif /* _LIBC__INTERNAL__KERNEL_ROUTINE_H_ */

View File

@ -348,6 +348,7 @@ void Libc::Kernel::_clone_state_from_parent()
/* fetch user contex of the parent's application */
_clone_connection->memory_content(&_user_context, sizeof(_user_context));
_clone_connection->memory_content(&_main_monitor_job, sizeof(_main_monitor_job));
_valid_user_context = true;
_libc_env.libc_config().for_each_sub_node([&] (Xml_node node) {
@ -466,8 +467,8 @@ Libc::Kernel::Kernel(Genode::Env &env, Genode::Allocator &heap)
init_malloc(*_malloc_heap);
}
init_fork(_env, _libc_env, _heap, *_malloc_heap, _pid, *this, *this, _signal,
*this, _binary_name);
init_fork(_env, _libc_env, _heap, *_malloc_heap, _pid, *this, _signal,
_binary_name);
init_execve(_env, _heap, _user_stack, *this, _binary_name,
*file_descriptor_allocator());
init_plugin(*this);