diff --git a/ports/src/lib/libc_noux/plugin.cc b/ports/src/lib/libc_noux/plugin.cc index 4d8d2b556..b1b1fea96 100644 --- a/ports/src/lib/libc_noux/plugin.cc +++ b/ports/src/lib/libc_noux/plugin.cc @@ -38,22 +38,50 @@ -Noux::Session *noux() +class Noux_connection { - static Noux::Connection noux_inst; - return &noux_inst; + private: + + Noux::Connection *_connection; + Noux::Sysio *_sysio; + + void _connect() + { + _connection = new (Genode::env()->heap()) Noux::Connection; + _sysio = Genode::env()->rm_session()->attach(_connection->sysio_dataspace()); + } + + void _disconnect() + { + Genode::env()->rm_session()->detach(_sysio); + Genode::destroy(Genode::env()->heap(), _connection); + } + + public: + + Noux_connection() { _connect(); } + + void reconnect() { + _disconnect(); _connect(); + PDBG("reconnected to Noux cap=%lx", _connection->cap().local_name()); + PDBG("getpid returns %d", getpid()); + } + + Noux::Session *session() { return _connection; } + Noux::Sysio *sysio() { return _sysio; } +}; + + +Noux_connection *noux_connection() +{ + static Noux_connection inst; + return &inst; } -Noux::Sysio *sysio() -{ - using namespace Noux; +Noux::Session *noux() { return noux_connection()->session(); } +Noux::Sysio *sysio() { return noux_connection()->sysio(); } - static Sysio *sysio = - Genode::env()->rm_session()->attach(noux()->sysio_dataspace()); - - return sysio; -} enum { FS_BLOCK_SIZE = 1024 }; @@ -295,11 +323,11 @@ extern "C" int select(int nfds, fd_set *readfds, fd_set *writefds, #include -#include /* for '_parent_cap', needed by 'fork' */ #include static jmp_buf fork_jmp_buf; +static Genode::Capability new_parent_cap; /* @@ -307,7 +335,15 @@ static jmp_buf fork_jmp_buf; */ extern "C" void fork_trampoline() { - static_cast(Genode::env())->reload_parent_cap(); + /* + * XXX remove hard-coded parent cap + */ + Okl4::L4_ThreadId_t tid; + tid.raw = 0x210001UL; + Genode::Native_capability cap(tid, 0x107UL); + new_parent_cap = Genode::reinterpret_cap_cast(cap); + + static_cast(Genode::env())->reload_parent_cap(new_parent_cap); longjmp(fork_jmp_buf, 1); } @@ -322,18 +358,15 @@ extern "C" pid_t fork(void) if (setjmp(fork_jmp_buf)) { /* got here via longjmp from 'fork_trampoline' */ - - PDBG("fork: got here via longjmp"); + noux_connection()->reconnect(); return 0; } else { - PDBG("fork: got here via normal control flow"); - /* got here during the normal control flow of the fork call */ sysio()->fork_in.ip = (Genode::addr_t)(&fork_trampoline); sysio()->fork_in.sp = (Genode::addr_t)(&stack[STACK_SIZE]); - sysio()->fork_in.parent_cap_addr = (Genode::addr_t)(&_parent_cap); + sysio()->fork_in.parent_cap_addr = (Genode::addr_t)(&new_parent_cap); if (!noux()->syscall(Noux::Session::SYSCALL_FORK)) { PERR("fork error %d", sysio()->error.general); diff --git a/ports/src/noux/child.h b/ports/src/noux/child.h index 3d8914a1d..8866fada3 100644 --- a/ports/src/noux/child.h +++ b/ports/src/noux/child.h @@ -35,6 +35,39 @@ namespace Noux { using namespace Genode; + + /** + * 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() + { + static Pid_allocator inst; + return &inst; + } + + class Child; bool is_init_process(Child *child); @@ -122,40 +155,150 @@ namespace Noux { */ struct Resources { - struct Dataspace_info : Object_pool::Entry + /* + * XXX not used yet + */ + struct Dataspace_destroyer { - size_t _size; - Dataspace_capability _ds_cap; + virtual void destroy(Dataspace_capability) = 0; + }; + class Dataspace_info : public Object_pool::Entry + { + private: - Dataspace_info(Dataspace_capability ds_cap) - : - Object_pool::Entry(ds_cap), - _size(Dataspace_client(ds_cap).size()), - _ds_cap(ds_cap) - { } + size_t _size; + Dataspace_capability _ds_cap; + Dataspace_destroyer &_destroyer; - size_t size() const { return _size; } - Dataspace_capability ds_cap() const { return _ds_cap; } + public: + + Dataspace_info(Dataspace_capability ds_cap, + Dataspace_destroyer &destroyer) + : + Object_pool::Entry(ds_cap), + _size(Dataspace_client(ds_cap).size()), + _ds_cap(ds_cap), + _destroyer(destroyer) + { } + + size_t size() const { return _size; } + Dataspace_capability ds_cap() const { return _ds_cap; } + + virtual Dataspace_capability fork(Ram_session_capability ram) = 0; + + Dataspace_destroyer &destroyer() { return _destroyer; } }; - struct Local_ram_session : Rpc_object + class Dataspace_registry { - Object_pool _pool; - size_t _used_quota; + private: + + Object_pool _pool; + + public: + + void insert(Dataspace_info *info) + { + _pool.insert(info); + } + + void remove(Dataspace_info *info) + { + _pool.remove(info); + } + + Dataspace_info *lookup_info(Dataspace_capability ds_cap) + { + return _pool.obj_by_cap(ds_cap); + } + }; + + + struct Ram_dataspace_info : Dataspace_info, + List::Element + { + Ram_dataspace_info(Ram_dataspace_capability ds_cap, + Dataspace_destroyer &destroyer) + : Dataspace_info(ds_cap, destroyer) { } + + Dataspace_capability fork(Ram_session_capability ram) + { + size_t const size = Dataspace_client(ds_cap()).size(); + + Ram_dataspace_capability dst_ds; + + try { + dst_ds = Ram_session_client(ram).alloc(size); + } catch (...) { + return Ram_dataspace_capability(); + } + + void *src = 0; + try { + src = Genode::env()->rm_session()->attach(ds_cap()); + } catch (...) { } + + void *dst = 0; + try { + dst = Genode::env()->rm_session()->attach(dst_ds); + } catch (...) { } + + if (src && dst) + memcpy(dst, src, size); + + if (src) Genode::env()->rm_session()->detach(src); + if (dst) Genode::env()->rm_session()->detach(dst); + + if (!src || !dst) { + Ram_session_client(ram).free(dst_ds); + return Ram_dataspace_capability(); + } + + return dst_ds; + } + }; + + + struct Local_ram_session : Rpc_object, + Dataspace_destroyer + { + List _list; + + /* + * Track the RAM resources accumulated via RAM session + * allocations. + */ + size_t _used_quota; + + Dataspace_registry &_registry; /** * Constructor */ - Local_ram_session() : _used_quota(0) { } + Local_ram_session(Dataspace_registry ®istry) + : _used_quota(0), _registry(registry) { } /** * Destructor */ ~Local_ram_session() { - PWRN("~Local_ram_session not yet implemented, leaking RAM"); + Ram_dataspace_info *info = 0; + while ((info = _list.first())) + free(static_cap_cast(info->ds_cap())); + } + + + /*********************************** + ** Dataspace_destroyer interface ** + ***********************************/ + + /* XXX not yet used */ + void destroy(Dataspace_capability ds) + { + free(static_cap_cast(ds)); } @@ -165,36 +308,35 @@ namespace Noux { Ram_dataspace_capability alloc(size_t size) { - PINF("RAM alloc %zd", size); - Ram_dataspace_capability ds_cap = env()->ram_session()->alloc(size); - Dataspace_info *ds_info = new (env()->heap()) - Dataspace_info(ds_cap); + Ram_dataspace_info *ds_info = new (env()->heap()) + Ram_dataspace_info(ds_cap, *this); _used_quota += ds_info->size(); - _pool.insert(ds_info); + _registry.insert(ds_info); + _list.insert(ds_info); return ds_cap; } void free(Ram_dataspace_capability ds_cap) { - PINF("RAM free"); - - Dataspace_info *ds_info = _pool.obj_by_cap(ds_cap); + Ram_dataspace_info *ds_info = + dynamic_cast(_registry.lookup_info(ds_cap)); if (!ds_info) { PERR("RAM free: dataspace lookup failed"); return; } - _pool.remove(ds_info); + _registry.remove(ds_info); + _list.remove(ds_info); _used_quota -= ds_info->size(); env()->ram_session()->free(ds_cap); - destroy(env()->heap(), ds_info); + Genode::destroy(env()->heap(), ds_info); } int ref_account(Ram_session_capability) { return 0; } @@ -209,51 +351,204 @@ namespace Noux { */ struct Local_rm_session : Rpc_object { - Rm_connection rm; + struct Region : List::Element + { + Dataspace_capability ds; + size_t size; + off_t offset; + addr_t local_addr; + + Region(Dataspace_capability ds, size_t size, + off_t offset, addr_t local_addr) + : + ds(ds), size(size), + offset(offset), local_addr(local_addr) + { } + + /** + * Return true if region contains specified address + */ + bool contains(addr_t addr) const + { + return (addr >= local_addr) + && (addr < local_addr + size); + } + }; + + Rm_connection _rm; + + Dataspace_registry &_ds_registry; + + List _regions; + + Region *_lookup_region_by_addr(addr_t local_addr) + { + Region *curr = _regions.first(); + for (; curr; curr = curr->next()) { + if (curr->contains(local_addr)) + return curr; + } + return 0; + } + + Local_rm_session(Dataspace_registry &ds_registry, + addr_t start = ~0UL, size_t size = 0) + : _rm(start, size), _ds_registry(ds_registry) { } + + ~Local_rm_session() + { + Region *curr = 0; + for (; curr; curr = curr->next()) + detach(curr->local_addr); + } + + /** + * Replay attachments onto specified RM session + * + * \param dst_ram backing store used for allocating the + * the copies of RAM dataspaces + */ + void replay(Ram_session_capability dst_ram, + Rm_session_capability dst_rm) + { + Region *curr = _regions.first(); + for (; curr; curr = curr->next()) { + + Dataspace_capability ds; + + Dataspace_info *info = _ds_registry.lookup_info(curr->ds); + + if (info) { + ds = info->fork(dst_ram); + if (!ds.valid()) + PERR("replay: Error while forking dataspace"); + + /* + * XXX We could detect dataspaces that are + * attached more than once. For now, we + * create a new fork for each attachment. + */ + + } else { + + /* + * If the dataspace is not a RAM dataspace, + * assume that it's a ROM dataspace. + * + * XXX Handle ROM dataspaces explicitly. For + * once, we need to make sure that they + * remain available until the child process + * exits even if the parent process exits + * earlier. Furthermore, we would like to + * detect unexpected dataspaces. + */ + ds = curr->ds; + PWRN("replay: unknown dataspace type"); + } + + Rm_session_client(dst_rm).attach(ds, curr->size, + curr->offset, + true, + curr->local_addr); + } + } + + + /************************** + ** RM session interface ** + **************************/ Local_addr attach(Dataspace_capability ds, size_t size = 0, off_t offset = 0, bool use_local_addr = false, Local_addr local_addr = (addr_t)0) { - PINF("RM attach called"); + if (size == 0) + size = Dataspace_client(ds).size(); /* * XXX look if we can identify the specified dataspace. * Is it a dataspace allocated via 'Local_ram_session'? */ - return rm.attach(ds, size, offset, use_local_addr, local_addr); + local_addr = _rm.attach(ds, size, offset, + use_local_addr, local_addr); + + /* + * Record attachement for later replay (needed during + * fork) + */ + _regions.insert(new (Genode::env()->heap()) + Region(ds, size, offset, local_addr)); + return local_addr; } void detach(Local_addr local_addr) { - PINF("RM detach called"); - rm.detach(local_addr); + _rm.detach(local_addr); + + Region *region = _lookup_region_by_addr(local_addr); + if (!region) { + PWRN("Attempt to detach unknown region at 0x%p", + (void *)local_addr); + return; + } + + _regions.remove(region); + destroy(Genode::env()->heap(), region); } Pager_capability add_client(Thread_capability thread) { - PINF("RM add client called"); - return rm.add_client(thread); + return _rm.add_client(thread); } void fault_handler(Signal_context_capability handler) { - PINF("RM fault handler called"); - return rm.fault_handler(handler); + return _rm.fault_handler(handler); } State state() { - PINF("RM state called"); - return rm.state(); + return _rm.state(); } Dataspace_capability dataspace() { - PINF("RM dataspace called"); - return rm.dataspace(); + return _rm.dataspace(); + } + }; + + + struct Sub_rm_dataspace_info : Dataspace_info + { + struct Dummy_destroyer : Dataspace_destroyer + { + void destroy(Dataspace_capability) { } + } _dummy_destroyer; + + Local_rm_session &_sub_rm; + + Sub_rm_dataspace_info(Local_rm_session &sub_rm) + : + Dataspace_info(sub_rm.dataspace(), _dummy_destroyer), + _sub_rm(sub_rm) + { } + + Dataspace_capability fork(Ram_session_capability ram) + { + Rm_connection *new_sub_rm = new Rm_connection(0, size()); + + /* + * XXX We are leaking the 'Rm_connection' because of + * keeping only the dataspace. + */ + + _sub_rm.replay(ram, new_sub_rm->cap()); + + Dataspace_capability ds_cap = new_sub_rm->dataspace(); + + return ds_cap; } }; @@ -263,9 +558,8 @@ namespace Noux { */ struct Local_cpu_session : Rpc_object { - bool forked; - Cpu_connection cpu; - + bool const forked; + Cpu_connection cpu; Thread_capability main_thread; Local_cpu_session(char const *label, bool forked) @@ -328,9 +622,8 @@ namespace Noux { void single_step(Thread_capability thread, bool enable) { cpu.single_step(thread, enable); } - /** - * Explicity start main thread, only meaningful when + * Explicitly start main thread, only meaningful when * 'forked' is true */ void start_main_thread(addr_t ip, addr_t sp) @@ -339,16 +632,15 @@ namespace Noux { } }; - Rpc_entrypoint ep; - - Local_ram_session ram; - Local_cpu_session cpu; - Local_rm_session rm; + Rpc_entrypoint &ep; + Dataspace_registry ds_registry; + Local_ram_session ram; + Local_cpu_session cpu; + Local_rm_session rm; Resources(char const *label, Rpc_entrypoint &ep, bool forked) : - ep(ep), - cpu(label, forked) + ep(ep), ram(ds_registry), cpu(label, forked), rm(ds_registry) { ep.manage(&ram); ep.manage(&rm); @@ -416,6 +708,42 @@ namespace Noux { } _local_noux_service; + struct Local_sub_rm_service : public Service + { + Rpc_entrypoint &_ep; + Resources::Dataspace_registry &_ds_registry; + + Local_sub_rm_service(Rpc_entrypoint &ep, + Resources::Dataspace_registry &ds_registry) + : + Service(Rm_session::service_name()), + _ep(ep), _ds_registry(ds_registry) + { } + + Genode::Session_capability session(const char *args) + { + addr_t start = Arg_string::find_arg(args, "start").ulong_value(~0UL); + size_t size = Arg_string::find_arg(args, "size").ulong_value(0); + + Resources::Local_rm_session *rm = + new Resources::Local_rm_session(_ds_registry, start, size); + + Genode::Session_capability cap = _ep.manage(rm); + + _ds_registry.insert(new Resources::Sub_rm_dataspace_info(*rm)); + + return cap; + } + + void upgrade(Genode::Session_capability, const char *args) { } + + void close(Genode::Session_capability) + { + PWRN("Local_sub_rm_service::close not implemented, leaking memory"); + } + + } _local_sub_rm_service; + /** * Exception type for failed file-descriptor lookup */ @@ -433,6 +761,16 @@ namespace Noux { 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); + } + public: /** @@ -466,7 +804,8 @@ namespace Noux { _args(ARGS_DS_SIZE, args), _env(env), _vfs(vfs), - _binary_ds(vfs->dataspace_from_file(name)), + _binary_ds(forked ? Dataspace_capability() + : vfs->dataspace_from_file(name)), _child(_binary_ds, _resources.ram.cap(), _resources.cpu.cap(), _resources.rm.cap(), &_entrypoint, this), _parent_services(parent_services), @@ -477,10 +816,16 @@ namespace Noux { _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) + _local_noux_service(_noux_session_cap), + _local_sub_rm_service(_entrypoint, _resources.ds_registry) { _args.dump(); strncpy(_name, name, sizeof(_name)); + + Native_capability cap = _child.parent_cap(); + + PINF("PID %d has parent cap %lx,%lx", + _pid, cap.tid().raw, cap.local_name()); } ~Child() @@ -492,6 +837,18 @@ namespace Noux { void start() { _entrypoint.activate(); } + void start_forked_main_thread(addr_t ip, addr_t sp, addr_t /* parent_cap_addr */) + { + /* + * XXX poke parent_cap_addr into child's address space + */ + _resources.cpu.start_main_thread(ip, sp); + } + + Ram_session_capability ram() const { return _resources.ram.cap(); } + Rm_session_capability rm() const { return _resources.rm.cap(); } + + /**************************** ** Child_policy interface ** ****************************/ @@ -513,6 +870,14 @@ namespace Noux { if (strcmp(service_name, Session::service_name()) == 0) return &_local_noux_service; + /* + * Check for the creation of an RM session, which is used by + * the dynamic linker to manually manage a part of the address + * space. + */ + if (strcmp(service_name, Rm_session::service_name()) == 0) + return &_local_sub_rm_service; + return _parent_services->find(service_name); } diff --git a/ports/src/noux/main.cc b/ports/src/noux/main.cc index b33023f8a..f9ec9b8bb 100644 --- a/ports/src/noux/main.cc +++ b/ports/src/noux/main.cc @@ -28,15 +28,15 @@ * ;- run 'ls -lRa' (dirent syscall) * ;- run 'cat' (read syscall) * ;- execve - * - fork + * ;- fork * - pipe - * - read init binary from vfs - * - import env into child + * ;- read init binary from vfs + * - import env into child (execve and fork) * - shell * - debug 'find' * - stacked file system infrastructure * - TMP file system - * - RAM service using a common quota pool + * ;- RAM service using a common quota pool */ /* Genode includes */ @@ -52,6 +52,9 @@ #include +enum { verbose_syscall = false }; + + namespace Noux { static Noux::Child *init_child; @@ -61,19 +64,6 @@ namespace Noux { }; -/** - * Allocate unique process ID - */ -static int alloc_pid() -{ - static Genode::Lock lock; - Genode::Lock::Guard guard(lock); - - static int num_pids = 0; - return num_pids++; -} - - extern "C" void wait_for_continue(); @@ -83,7 +73,10 @@ extern "C" void wait_for_continue(); bool Noux::Child::syscall(Noux::Session::Syscall sc) { -// Genode::printf("-> SYSCALL %s\n", Noux::Session::syscall_name(sc)); + if (verbose_syscall) + Genode::printf("PID %d -> SYSCALL %s\n", + _pid, Noux::Session::syscall_name(sc)); + try { switch (sc) { @@ -175,10 +168,7 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc) _resources.ep, false); - /* let new child inherit our file descriptors */ - for (int fd = 0; fd < MAX_FILE_DESCRIPTORS; fd++) - if (fd_in_use(fd)) - child->add_io_channel(io_channel_by_fd(fd), fd); + _assign_io_channels_to(child); /* signal main thread to remove ourself */ Genode::Signal_transmitter(_execve_cleanup_context_cap).submit(); @@ -287,8 +277,37 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc) case SYSCALL_FORK: { - PDBG("fork called - not implemented"); - _sysio->fork_out.pid = 77; + Genode::addr_t ip = _sysio->fork_in.ip; + Genode::addr_t sp = _sysio->fork_in.sp; + Genode::addr_t parent_cap_addr = _sysio->fork_in.parent_cap_addr; + + int new_pid = pid_allocator()->alloc(); + char const *env = "PWD=\"/\""; + + Child *child = new Child("borked" /*name()*/, + new_pid, + _sig_rec, + _vfs, + _args, + env, /* XXX */ + _cap_session, + _parent_services, + _resources.ep, + true); + + _assign_io_channels_to(child); + + /* copy our address space into the new child */ + _resources.rm.replay(child->ram(), child->rm()); + + /* start executing the main thread of the new process */ + child->start_forked_main_thread(ip, sp, parent_cap_addr); + + /* activate child entrypoint, thereby starting the new process */ + child->start(); + + _sysio->fork_out.pid = new_pid; + return true; } @@ -427,7 +446,7 @@ int main(int argc, char **argv) /* whitelist of service requests to be routed to the parent */ static Genode::Service_registry parent_services; - char const *service_names[] = { "LOG", "ROM", "Timer", "RM", 0 }; + char const *service_names[] = { "LOG", "ROM", "Timer", 0 }; for (unsigned i = 0; service_names[i]; i++) parent_services.insert(new Genode::Parent_service(service_names[i])); @@ -444,8 +463,6 @@ int main(int argc, char **argv) } } catch (Genode::Xml_node::Nonexistent_sub_node) { } - /* XXX union RAM service */ - /* * Entrypoint used to virtualize child resources such as RAM, RM */ @@ -456,7 +473,7 @@ int main(int argc, char **argv) static Genode::Signal_receiver sig_rec; init_child = new Noux::Child(name_of_init_process(), - alloc_pid(), + pid_allocator()->alloc(), &sig_rec, &vfs, args_of_init_process(), diff --git a/ports/src/test/noux_fork/test.cc b/ports/src/test/noux_fork/test.cc index c5c239ad0..54bd2f300 100644 --- a/ports/src/test/noux_fork/test.cc +++ b/ports/src/test/noux_fork/test.cc @@ -8,6 +8,8 @@ #include #include +enum { MAX_COUNT = 100 }; + int main(int, char **) { int i = 0; @@ -22,17 +24,19 @@ int main(int, char **) if (fork_ret == 0) { printf("pid %d: child says hello\n", getpid()); - for (int j = 0; j < 50; j++) { + for (int j = 0; j < MAX_COUNT; j++) { printf("pid %d: child i = %d\n", getpid(), i); } + return 0; } printf("pid %d: parent received child pid %d, starts counting...\n", getpid(), fork_ret); - for (; i < 50; ) { + for (; i < MAX_COUNT; ) { printf("pid %d: parent i = %d\n", getpid(), i++); } + for (;;); return 0; }