/* * \brief Noux child process * \author Norman Feske * \date 2011-02-17 */ /* * Copyright (C) 2011-2013 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU General Public License version 2. */ #ifndef _NOUX__CHILD_H_ #define _NOUX__CHILD_H_ /* Genode includes */ #include #include #include #include #include /* Noux includes */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Noux { /** * Allocator for process IDs */ class Pid_allocator { private: Lock _lock; int _num_pids; public: Pid_allocator() : _num_pids(0) { } int alloc() { Lock::Guard guard(_lock); return _num_pids++; } }; /** * Return singleton instance of PID allocator */ Pid_allocator *pid_allocator(); /** * Return singleton instance of timeout scheduler */ class Timeout_scheduler; Timeout_scheduler *timeout_scheduler(); /** * Return singleton instance of user information */ class User_info; User_info *user_info(); /** * Return singleton instance of Io_receptor_registry */ Io_receptor_registry *io_receptor_registry(); /** * Return ELF binary of dynamic linker */ Dataspace_capability ldso_ds_cap(); /* * Return lock for protecting the signal queue */ Genode::Lock &signal_lock(); class Child; bool is_init_process(Child *child); void init_process_exited(int); class Child : public Rpc_object, public File_descriptor_registry, public Family_member, public Destruct_queue::Element, public Interrupt_handler { private: Parent_exit *_parent_exit; Kill_broadcaster &_kill_broadcaster; Parent_execve &_parent_execve; Signal_receiver *_sig_rec; Allocator *_alloc; Destruct_queue &_destruct_queue; Destruct_dispatcher _destruct_dispatcher; Signal_context_capability _destruct_context_cap; Cap_session * const _cap_session; enum { STACK_SIZE = 4*1024*sizeof(long) }; Rpc_entrypoint _entrypoint; Pd_connection _pd; /** * Resources assigned to the child */ struct Resources { /** * Entrypoint used to serve the RPC interfaces of the * locally-provided services */ Rpc_entrypoint &ep; /** * Registry of dataspaces owned by the Noux process */ Dataspace_registry ds_registry; /** * Locally-provided services for accessing platform resources */ Ram_session_component ram; Cpu_session_component cpu; Rm_session_component rm; Resources(char const *label, Rpc_entrypoint &ep, bool forked) : ep(ep), ram(ds_registry), cpu(label, forked), rm(ds_registry) { ep.manage(&ram); ep.manage(&rm); ep.manage(&cpu); } ~Resources() { ep.dissolve(&ram); ep.dissolve(&rm); ep.dissolve(&cpu); } } _resources; /** * Command line arguments */ Args_dataspace _args; /** * Environment variables */ Environment _env; /** * ELF binary handling */ struct Elf { enum { NAME_MAX_LEN = 128 }; char _name[NAME_MAX_LEN]; Vfs::Dir_file_system * const _root_dir; Dataspace_capability const _binary_ds; Elf(char const * const binary_name, Vfs::Dir_file_system * root_dir, Dataspace_capability binary_ds) : _root_dir(root_dir), _binary_ds(binary_ds) { strncpy(_name, binary_name, sizeof(_name)); _name[NAME_MAX_LEN - 1] = 0; } ~Elf() { _root_dir->release(_name, _binary_ds); } } _elf; enum { PAGE_SIZE = 4096, PAGE_MASK = ~(PAGE_SIZE - 1) }; enum { SYSIO_DS_SIZE = PAGE_MASK & (sizeof(Sysio) + PAGE_SIZE - 1) }; Attached_ram_dataspace _sysio_ds; Sysio * const _sysio; typedef Ring_buffer Signal_queue; Signal_queue _pending_signals; Session_capability const _noux_session_cap; Local_noux_service _local_noux_service; Parent_service _parent_ram_service; Local_cpu_service _local_cpu_service; Local_rm_service _local_rm_service; Local_rom_service _local_rom_service; Service_registry &_parent_services; Static_dataspace_info _binary_ds_info; Static_dataspace_info _sysio_ds_info; Static_dataspace_info _ldso_ds_info; Static_dataspace_info _args_ds_info; Static_dataspace_info _env_ds_info; Child_policy _child_policy; Genode::Child _child; /** * Exception type for failed file-descriptor lookup */ class Invalid_fd { }; Shared_pointer _lookup_channel(int fd) const { Shared_pointer channel = io_channel_by_fd(fd); if (channel) return channel; throw Invalid_fd(); } enum { ARGS_DS_SIZE = 4096 }; /** * Let specified child inherit our file descriptors */ void _assign_io_channels_to(Child *child) { for (int fd = 0; fd < MAX_FILE_DESCRIPTORS; fd++) if (fd_in_use(fd)) child->add_io_channel(io_channel_by_fd(fd), fd); } /** * Block until the IO channel is ready for reading or writing or an * exception occured. * * \param io the IO channel * \param rd check for data available for reading * \param wr check for readiness for writing * \param ex check for exceptions */ void _block_for_io_channel(Shared_pointer &io, bool rd, bool wr, bool ex) { /* reset the blocker lock to the 'locked' state */ _blocker.unlock(); _blocker.lock(); Wake_up_notifier notifier(&_blocker); io->register_wake_up_notifier(¬ifier); for (;;) { if (io->check_unblock(rd, wr, ex) || !_pending_signals.empty()) break; /* block (unless the lock got unlocked in the meantime) */ _blocker.lock(); } io->unregister_wake_up_notifier(¬ifier); } Vfs::Dir_file_system * const root_dir() { return _elf._root_dir; } /** * Method for handling noux network related system calls */ bool _syscall_net(Syscall sc); void _destruct() { _sig_rec->dissolve(&_destruct_dispatcher); _entrypoint.dissolve(this); if (is_init_process(this)) init_process_exited(_child_policy.exit_value()); } public: struct Binary_does_not_exist : Exception { }; struct Insufficient_memory : Exception { }; /** * Constructor * * \param forked false if the child is spawned directly from * an executable binary (i.e., the init process, * or children created via execve, or * true if the child is a fork from another child * * \throw Binary_does_not_exist if child is not a fork and the * specified name could not be * looked up at the virtual file * system * \throw Insufficent_memory if the child could not be started by * the parent */ Child(char const *binary_name, Parent_exit *parent_exit, Kill_broadcaster &kill_broadcaster, Parent_execve &parent_execve, int pid, Signal_receiver *sig_rec, Vfs::Dir_file_system *root_dir, Args const &args, Sysio::Env const &env, Cap_session *cap_session, Service_registry &parent_services, Rpc_entrypoint &resources_ep, bool forked, Allocator *destruct_alloc, Destruct_queue &destruct_queue, bool verbose) : Family_member(pid), Destruct_queue::Element(destruct_alloc), _parent_exit(parent_exit), _kill_broadcaster(kill_broadcaster), _parent_execve(parent_execve), _sig_rec(sig_rec), _destruct_queue(destruct_queue), _destruct_dispatcher(_destruct_queue, this), _destruct_context_cap(sig_rec->manage(&_destruct_dispatcher)), _cap_session(cap_session), _entrypoint(cap_session, STACK_SIZE, "noux_process", false), _pd(binary_name), _resources(binary_name, resources_ep, false), _args(ARGS_DS_SIZE, args), _env(env), _elf(binary_name, root_dir, root_dir->dataspace(binary_name)), _sysio_ds(Genode::env()->ram_session(), SYSIO_DS_SIZE), _sysio(_sysio_ds.local_addr()), _noux_session_cap(Session_capability(_entrypoint.manage(this))), _local_noux_service(_noux_session_cap), _parent_ram_service(""), _local_cpu_service(_entrypoint, _resources.cpu.cpu_cap()), _local_rm_service(_entrypoint, _resources.ds_registry), _local_rom_service(_entrypoint, _resources.ds_registry), _parent_services(parent_services), _binary_ds_info(_resources.ds_registry, _elf._binary_ds), _sysio_ds_info(_resources.ds_registry, _sysio_ds.cap()), _ldso_ds_info(_resources.ds_registry, ldso_ds_cap()), _args_ds_info(_resources.ds_registry, _args.cap()), _env_ds_info(_resources.ds_registry, _env.cap()), _child_policy(_elf._name, _elf._binary_ds, _args.cap(), _env.cap(), _entrypoint, _local_noux_service, _local_rm_service, _local_rom_service, _parent_services, *this, parent_exit, *this, _destruct_context_cap, _resources.ram, verbose), _child(forked ? Dataspace_capability() : _elf._binary_ds, _pd.cap(), _resources.ram.cap(), _resources.cpu.cap(), _resources.rm.cap(), &_entrypoint, &_child_policy, _parent_ram_service, _local_cpu_service, _local_rm_service) { if (verbose) _args.dump(); if (!forked && !_elf._binary_ds.valid()) { PERR("Lookup of executable \"%s\" failed", binary_name); _destruct(); throw Binary_does_not_exist(); } if (!_child.main_thread_cap().valid()) { _destruct(); throw Insufficient_memory(); } } ~Child() { _destruct(); } void start() { _entrypoint.activate(); } void start_forked_main_thread(addr_t ip, addr_t sp, addr_t parent_cap_addr) { /* poke parent_cap_addr into child's address space */ Capability const &cap = _child.parent_cap(); Capability::Raw raw = { cap.dst(), cap.local_name() }; _resources.rm.poke(parent_cap_addr, &raw, sizeof(raw)); /* start execution of new main thread at supplied trampoline */ _resources.cpu.start_main_thread(ip, sp); } void submit_exit_signal() { if (is_init_process(this)) { PINF("init process exited"); /* trigger exit of main event loop */ init_process_exited(_child_policy.exit_value()); } else { Signal_transmitter(_destruct_context_cap).submit(); } } Ram_session_capability ram() const { return _resources.ram.cap(); } Rm_session_capability rm() const { return _resources.rm.cap(); } Dataspace_registry &ds_registry() { return _resources.ds_registry; } /**************************** ** Noux session interface ** ****************************/ Dataspace_capability sysio_dataspace() { return _sysio_ds.cap(); } Rm_session_capability lookup_rm_session(addr_t const addr) { return _resources.rm.lookup_rm_session(addr); } bool syscall(Syscall sc); int next_open_fd(int start_fd) { if (start_fd >= 0) for (int fd = start_fd; fd < MAX_FILE_DESCRIPTORS; fd++) if (fd_in_use(fd)) return fd; return -1; } /**************************************** ** File_descriptor_registry overrides ** ****************************************/ /** * Find out if the IO channel associated with 'fd' has more file * descriptors associated with it */ bool _is_the_only_fd_for_io_channel(int fd, Shared_pointer io_channel) { for (int f = 0; f < MAX_FILE_DESCRIPTORS; f++) { if ((f != fd) && fd_in_use(f) && (io_channel_by_fd(f) == io_channel)) return false; } return true; } int add_io_channel(Shared_pointer io_channel, int fd = -1) { fd = File_descriptor_registry::add_io_channel(io_channel, fd); /* Register the interrupt handler only once per IO channel */ if (_is_the_only_fd_for_io_channel(fd, io_channel)) { Io_channel_listener *l = new (env()->heap()) Io_channel_listener(this); io_channel->register_interrupt_handler(l); } return fd; } void remove_io_channel(int fd) { Shared_pointer io_channel = _lookup_channel(fd); /* * Unregister the interrupt handler only if there are no other * file descriptors associated with the IO channel. */ if (_is_the_only_fd_for_io_channel(fd, io_channel)) { Io_channel_listener *l = io_channel->lookup_io_channel_listener(this); io_channel->unregister_interrupt_handler(l); Genode::destroy(env()->heap(), l); } File_descriptor_registry::remove_io_channel(fd); } void flush() { for (int fd = 0; fd < MAX_FILE_DESCRIPTORS; fd++) try { remove_io_channel(fd); } catch (Invalid_fd) { } } /***************************** ** Family_member interface ** *****************************/ void submit_signal(Noux::Sysio::Signal sig) { try { _pending_signals.add(sig); } catch (Signal_queue::Overflow) { PERR("signal queue is full - signal dropped"); } _blocker.unlock(); } Family_member *do_execve(const char *filename, Args const &args, Sysio::Env const &env, bool verbose) { Lock::Guard signal_lock_guard(signal_lock()); Child *child = new Child(filename, _parent_exit, _kill_broadcaster, _parent_execve, pid(), _sig_rec, root_dir(), args, env, _cap_session, _parent_services, _resources.ep, false, Genode::env()->heap(), _destruct_queue, verbose); _assign_io_channels_to(child); /* move the signal queue */ while (!_pending_signals.empty()) child->_pending_signals.add(_pending_signals.get()); /* * Close all open files. * * This action is not part of the child destructor, * because in the case that a child exits itself, * it may need to close all files to unblock the * parent (which might be reading from a pipe) before * the parent can destroy the child object. */ flush(); /* signal main thread to remove ourself */ Genode::Signal_transmitter(_destruct_context_cap).submit(); /* start executing the new process */ child->start(); /* this child will be removed by the execve_finalization_dispatcher */ return child; } /********************************* ** Interrupt_handler interface ** *********************************/ void handle_interrupt() { submit_signal(Sysio::SIG_INT); } }; }; #endif /* _NOUX__CHILD_H_ */