diff --git a/repos/base-nova/include/32bit/nova/syscalls.h b/repos/base-nova/include/32bit/nova/syscalls.h index 6d30c6eb1f..608e45f407 100644 --- a/repos/base-nova/include/32bit/nova/syscalls.h +++ b/repos/base-nova/include/32bit/nova/syscalls.h @@ -67,7 +67,8 @@ namespace Nova { ALWAYS_INLINE - inline uint8_t syscall_1(Syscall s, uint8_t flags, unsigned sel, mword_t p1) + inline uint8_t syscall_1(Syscall s, uint8_t flags, mword_t sel, mword_t p1, + mword_t * p2 = 0) { mword_t status = eax(s, flags, sel); @@ -78,9 +79,10 @@ namespace Nova { " mov (%%esp), %%edx;" " sysenter;" "1:" - : "+a" (status) - : "D" (p1) - : "ecx", "edx"); + : "+a" (status), "+D" (p1) + : + : "ecx", "edx", "memory"); + if (p2) *p2 = p1; return status; } @@ -180,7 +182,7 @@ namespace Nova { ALWAYS_INLINE inline uint8_t call(unsigned pt) { - return syscall_0(NOVA_CALL, 0, pt); + return syscall_1(NOVA_CALL, 0, pt, 0); } @@ -199,17 +201,34 @@ namespace Nova { ALWAYS_INLINE - inline uint8_t create_pd(unsigned pd0, unsigned pd, Crd crd) + inline uint8_t create_pd(unsigned pd0, unsigned pd, Crd crd, + unsigned short lower_limit, unsigned upper_limit) { - return syscall_2(NOVA_CREATE_PD, 0, pd0, pd, crd.value()); + return syscall_3(NOVA_CREATE_PD, 0, pd0, pd, crd.value(), + upper_limit << 16 | lower_limit); } + /** + * Create an EC. + * + * \param ec two selectors - ec && ec + 1 + * First selector must be unused and second selector is + * either unused or must be a valid portal selector. + * The thread will call this portal if the PD it runs in runs + * out of kernel memory. + * \param pd selector of PD the EC will created in + * \param cpu CPU number the EC will run on + * \param utcb PD local address where the UTCB of the EC will be appear + * \param esp initial stack address + * \param evt base selector for all exception portals of the EC + * \param global if true - thread requires a SC to be runnable + * if false - thread is runnable solely if it receives a IPC + * (worker thread) + */ ALWAYS_INLINE - inline uint8_t create_ec(unsigned ec, unsigned pd, - mword_t cpu, mword_t utcb, - mword_t esp, mword_t evt, - bool global = 0) + inline uint8_t create_ec(mword_t ec, mword_t pd, mword_t cpu, mword_t utcb, + mword_t esp, mword_t evt, bool global = false) { return syscall_4(NOVA_CREATE_EC, global, ec, pd, (cpu & 0xfff) | (utcb & ~0xfff), @@ -265,32 +284,39 @@ namespace Nova { } + /** + * Revoke memory, capabilities or i/o ports from a PD + * + * \param crd describes region and type of resource + * \param self also revoke from source PD iif self == true + * \param remote if true the 'pd' parameter below is used, otherwise + * current PD is used as source PD + * \param pd selector describing remote PD + * \param sm SM selector which gets an up() by the kernel if the + * memory of the current revoke invocation gets freed up + * (end of RCU period) + */ ALWAYS_INLINE - inline uint8_t revoke(Crd crd, bool self = true) + inline uint8_t revoke(Crd crd, bool self = true, bool remote = false, + mword_t pd = 0, mword_t sm = 0) { - return syscall_1(NOVA_REVOKE, self, 0, crd.value()); + uint8_t flags = self ? 0x1 : 0; + + if (remote) + flags |= 0x2; + + mword_t value_crd = crd.value(); + return syscall_5(NOVA_REVOKE, flags, sm, value_crd, pd); } ALWAYS_INLINE inline uint8_t lookup(Crd &crd) { - mword_t status = eax(NOVA_LOOKUP, 0, 0); - mword_t raw = crd.value(); - - asm volatile (" mov %%esp, %%ecx;" - " call 0f;" - "0:" - " addl $(1f-0b), (%%esp);" - " mov (%%esp), %%edx;" - " sysenter;" - "1:" - : "+a" (status), "+D" (raw) - : - : "ecx", "edx", "memory"); - - crd = Crd(raw); - return status; + mword_t crd_r; + uint8_t res = syscall_1(NOVA_LOOKUP, 0, 0, crd.value(), &crd_r); + crd = Crd(crd_r); + return res; } @@ -308,6 +334,21 @@ namespace Nova { } + ALWAYS_INLINE + inline uint8_t pd_ctrl(mword_t pd_src, Pd_op op, mword_t pd_dst, + mword_t transfer) + { + return syscall_5(NOVA_PD_CTRL, op, pd_src, pd_dst, transfer); + } + + + ALWAYS_INLINE + inline uint8_t pd_ctrl_debug(mword_t pd, mword_t &limit, mword_t &usage) + { + return syscall_5(NOVA_PD_CTRL, Pd_op::PD_DEBUG, pd, limit, usage); + } + + ALWAYS_INLINE inline uint8_t assign_pci(mword_t pd, mword_t mem, mword_t rid) { diff --git a/repos/base-nova/include/64bit/nova/syscalls.h b/repos/base-nova/include/64bit/nova/syscalls.h index e65f0a0f72..ed6ec269bf 100644 --- a/repos/base-nova/include/64bit/nova/syscalls.h +++ b/repos/base-nova/include/64bit/nova/syscalls.h @@ -136,7 +136,7 @@ namespace Nova { ALWAYS_INLINE inline uint8_t call(mword_t pt) { - return syscall_0(NOVA_CALL, 0, pt); + return syscall_1(NOVA_CALL, 0, pt, 0); } @@ -156,17 +156,34 @@ namespace Nova { ALWAYS_INLINE - inline uint8_t create_pd(mword_t pd0, mword_t pd, Crd crd) + inline uint8_t create_pd(mword_t pd0, mword_t pd, Crd crd, + unsigned lower_limit, unsigned long upper_limit) { - return syscall_2(NOVA_CREATE_PD, 0, pd0, pd, crd.value()); + return syscall_3(NOVA_CREATE_PD, 0, pd0, pd, crd.value(), + upper_limit << 32 | lower_limit); } + /** + * Create an EC. + * + * \param ec two selectors - ec && ec + 1 + * First selector must be unused and second selector is + * either unused or must be a valid portal selector. + * The thread will call this portal if the PD it runs in runs + * out of kernel memory. + * \param pd selector of PD the EC will created in + * \param cpu CPU number the EC will run on + * \param utcb PD local address where the UTCB of the EC will be appear + * \param esp initial stack address + * \param evt base selector for all exception portals of the EC + * \param global if true - thread requires a SC to be runnable + * if false - thread is runnable solely if it receives a IPC + * (worker thread) + */ ALWAYS_INLINE - inline uint8_t create_ec(mword_t ec, mword_t pd, - mword_t cpu, mword_t utcb, - mword_t esp, mword_t evt, - bool global = 0) + inline uint8_t create_ec(mword_t ec, mword_t pd, mword_t cpu, mword_t utcb, + mword_t esp, mword_t evt, bool global = false) { return syscall_4(NOVA_CREATE_EC, global, ec, pd, (cpu & 0xfff) | (utcb & ~0xfff), @@ -222,10 +239,29 @@ namespace Nova { } + /** + * Revoke memory, capabilities or i/o ports from a PD + * + * \param crd describes region and type of resource + * \param self also revoke from source PD iif self == true + * \param remote if true the 'pd' parameter below is used, otherwise + * current PD is used as source PD + * \param pd selector describing remote PD + * \param sm SM selector which gets an up() by the kernel if the + * memory of the current revoke invocation gets freed up + * (end of RCU period) + */ ALWAYS_INLINE - inline uint8_t revoke(Crd crd, bool self = true) + inline uint8_t revoke(Crd crd, bool self = true, bool remote = false, + mword_t pd = 0, mword_t sm = 0) { - return syscall_1(NOVA_REVOKE, self, 0, crd.value()); + uint8_t flags = self ? 0x1 : 0; + + if (remote) + flags |= 0x2; + + mword_t value_crd = crd.value(); + return syscall_5(NOVA_REVOKE, flags, sm, value_crd, pd); } @@ -265,6 +301,20 @@ namespace Nova { } + ALWAYS_INLINE + inline uint8_t pd_ctrl(mword_t pd_src, Pd_op op, mword_t pd_dst, mword_t transfer) + { + return syscall_5(NOVA_PD_CTRL, op, pd_src, pd_dst, transfer); + } + + + ALWAYS_INLINE + inline uint8_t pd_ctrl_debug(mword_t pd, mword_t &limit, mword_t &usage) + { + return syscall_5(NOVA_PD_CTRL, Pd_op::PD_DEBUG, pd, limit, usage); + } + + ALWAYS_INLINE inline uint8_t assign_pci(mword_t pd, mword_t mem, mword_t rid) { diff --git a/repos/base-nova/include/nova/syscall-generic.h b/repos/base-nova/include/nova/syscall-generic.h index fd46f233b6..b2b48e5e05 100644 --- a/repos/base-nova/include/nova/syscall-generic.h +++ b/repos/base-nova/include/nova/syscall-generic.h @@ -63,6 +63,7 @@ namespace Nova { NOVA_SM_CTRL = 0xc, NOVA_ASSIGN_PCI = 0xd, NOVA_ASSIGN_GSI = 0xe, + NOVA_PD_CTRL = 0xf, }; /** @@ -79,6 +80,7 @@ namespace Nova { NOVA_INV_FEATURE = 6, NOVA_INV_CPU = 7, NOVA_INVD_DEVICE_ID = 8, + NOVA_PD_OOM = 9, }; /** @@ -160,6 +162,11 @@ namespace Nova { */ enum Ec_op { EC_RECALL = 0U, EC_YIELD = 1U, EC_DONATE_SC = 2U, EC_RESCHEDULE = 3U }; + /** + * Pd operations + */ + enum Pd_op { TRANSFER_QUOTA = 0U, PD_DEBUG = 2U }; + class Descriptor { diff --git a/repos/base-nova/ports/nova.hash b/repos/base-nova/ports/nova.hash index 3e0e11586b..5def9b9d82 100644 --- a/repos/base-nova/ports/nova.hash +++ b/repos/base-nova/ports/nova.hash @@ -1 +1 @@ -50a7e018fac5bd0bbb7c43ccb9681beae1c93db8 +9f2aef990ef7fcf148a53e63ba9bebdb0e3fff10 diff --git a/repos/base-nova/ports/nova.port b/repos/base-nova/ports/nova.port index 61977a49d8..e1f7ec6a68 100644 --- a/repos/base-nova/ports/nova.port +++ b/repos/base-nova/ports/nova.port @@ -2,9 +2,9 @@ LICENSE := GPLv2 VERSION := git DOWNLOADS := nova.git +# r9 branch URL(nova) := https://github.com/alex-ab/NOVA.git -# r9_pae branch -REV(nova) := 902ccd998fddbf140bb6f87b75d7c9df97e6380f +REV(nova) := 7de4ddd3b7af33affdd8f36aa997b20b52bd2ef6 DIR(nova) := src/kernel/nova PATCHES := $(wildcard $(REP_DIR)/patches/*.patch) diff --git a/repos/base-nova/src/core/echo.cc b/repos/base-nova/src/core/echo.cc index 17f959976c..e81945c810 100644 --- a/repos/base-nova/src/core/echo.cc +++ b/repos/base-nova/src/core/echo.cc @@ -22,7 +22,8 @@ enum { ECHO_STACK_SIZE = 512, ECHO_GLOBAL = false, - ECHO_EXC_BASE = 0 + ECHO_EXC_BASE = 0, + ECHO_LOG2_COUNT = 1 /* selector for EC and out-of-memory portal */ }; @@ -61,7 +62,7 @@ static void echo_reply() Echo::Echo(Genode::addr_t utcb_addr) : - _ec_sel(Genode::cap_map()->insert()), + _ec_sel(Genode::cap_map()->insert(ECHO_LOG2_COUNT)), _pt_sel(Genode::cap_map()->insert()), _utcb((Nova::Utcb *)utcb_addr) { diff --git a/repos/base-nova/src/core/include/pager.h b/repos/base-nova/src/core/include/pager.h index 556990f82a..7f3cca9762 100644 --- a/repos/base-nova/src/core/include/pager.h +++ b/repos/base-nova/src/core/include/pager.h @@ -120,11 +120,14 @@ namespace Genode { Thread_capability _thread_cap; Exception_handlers _exceptions; + addr_t _pd; + void _copy_state(Nova::Utcb * utcb); - addr_t sel_pt_cleanup() { return _selectors; } - addr_t sel_sm_notify() { return _selectors + 1; } - addr_t sel_sm_block() { return _selectors + 2; } + addr_t sel_pt_cleanup() const { return _selectors; } + addr_t sel_sm_notify() const { return _selectors + 1; } + addr_t sel_sm_block() const { return _selectors + 2; } + addr_t sel_oom_portal() const { return _selectors + 3; } __attribute__((regparm(1))) static void _page_fault_handler(addr_t pager_obj); @@ -138,6 +141,9 @@ namespace Genode { __attribute__((regparm(1))) static void _recall_handler(addr_t pager_obj); + __attribute__((regparm(3))) + static void _oom_handler(addr_t, addr_t, addr_t); + public: const Affinity::Location location; @@ -159,6 +165,13 @@ namespace Genode { _exception_sigh = sigh; } + /** + * Assign PD selector to PD + */ + void assign_pd(addr_t pd_sel) { _pd = pd_sel; } + addr_t pd_sel() const { return _pd; } + void dump_kernel_quota_usage(Pager_object * = (Pager_object *)~0UL); + void exception(uint8_t exit_id); /** @@ -301,6 +314,41 @@ namespace Genode { { _client_exc_vcpu = cap_map()->insert(Nova::NUM_INITIAL_VCPU_PT_LOG2); } + + /** + * Portal called by thread that causes a out of memory in kernel. + */ + addr_t get_oom_portal(); + + enum Policy { + STOP = 1, + UPGRADE_CORE_TO_DST = 2, + UPGRADE_PREFER_SRC_TO_DST = 3, + }; + + enum Oom { + SEND = 1, REPLY = 2, SELF = 4, + SRC_CORE_PD = ~0UL, SRC_PD_UNKNOWN = 0, + }; + + /** + * Implements policy on how to react on out of memory in kernel. + * + * Used solely inside core. On Genode core creates all the out + * of memory portals per EC. If the PD of a EC runs out of kernel + * memory it causes a OOM portal traversal, which is handled + * by the pager object of the causing thread. + * + * /param pd_sel PD selector from where to transfer kernel memory + * resources. The PD of this pager_object is the + * target PD. + * /param pd debug feature - string of PD (transfer_from) + * /param thread debug feature - string of EC (transfer_from) + */ + uint8_t handle_oom(addr_t pd_sel = SRC_CORE_PD, + const char * pd = "core", + const char * thread = "unknown", + Policy = Policy::UPGRADE_CORE_TO_DST); }; /** @@ -338,6 +386,12 @@ namespace Genode { */ void ep(Pager_entrypoint *ep) { _ep = ep; } + /* + * Used for diagnostic/debugging purposes + * - see Pager_object::dump_kernel_quota_usage + */ + Pager_object * pager_head(); + /** * Thread interface */ diff --git a/repos/base-nova/src/core/include/platform_thread.h b/repos/base-nova/src/core/include/platform_thread.h index 048c8bb0c0..e551c53490 100644 --- a/repos/base-nova/src/core/include/platform_thread.h +++ b/repos/base-nova/src/core/include/platform_thread.h @@ -48,8 +48,9 @@ namespace Genode { char _name[Thread_base::Context::NAME_LEN]; - addr_t _sel_ec() const { return _id_base; } - addr_t _sel_sc() const { return _id_base + 1; } + addr_t _sel_ec() const { return _id_base; } + addr_t _sel_pt_oom() const { return _id_base + 1; } + addr_t _sel_sc() const { return _id_base + 2; } /* convenience function to access _feature variable */ inline bool is_main_thread() { return _features & MAIN_THREAD; } diff --git a/repos/base-nova/src/core/pager.cc b/repos/base-nova/src/core/pager.cc index 9b26741081..32cd9c2705 100644 --- a/repos/base-nova/src/core/pager.cc +++ b/repos/base-nova/src/core/pager.cc @@ -25,6 +25,8 @@ #include #include /* map_local */ +static bool verbose_oom = false; + using namespace Genode; using namespace Nova; @@ -409,14 +411,19 @@ void Pager_object::cleanup_call() static uint8_t create_portal(addr_t pt, addr_t pd, addr_t ec, Mtd mtd, - addr_t eip, addr_t localname) + addr_t eip, Pager_object * oom_handler) { - uint8_t res = create_pt(pt, pd, ec, mtd, eip); + addr_t const badge_localname = reinterpret_cast(oom_handler); + + uint8_t res; + do { + res = create_pt(pt, pd, ec, mtd, eip); + } while (res == Nova::NOVA_PD_OOM && Nova::NOVA_OK == oom_handler->handle_oom()); if (res != NOVA_OK) return res; - res = pt_ctrl(pt, localname); + res = pt_ctrl(pt, badge_localname); if (res == NOVA_OK) revoke(Obj_crd(pt, 0, Obj_crd::RIGHT_PT_CTRL)); else @@ -443,8 +450,7 @@ void Exception_handlers::register_handler(Pager_object *obj, Mtd mtd, /* compiler generates instance of exception entry if not specified */ addr_t entry = func ? (addr_t)func : (addr_t)(&_handler); uint8_t res = create_portal(obj->exc_pt_sel_client() + EV, - __core_pd_sel, ec_sel, mtd, entry, - reinterpret_cast(obj)); + __core_pd_sel, ec_sel, mtd, entry, obj); if (res != Nova::NOVA_OK) throw Rm_session::Invalid_thread(); } @@ -494,6 +500,29 @@ Exception_handlers::Exception_handlers(Pager_object *obj) ******************/ +void Pager_object::dump_kernel_quota_usage(Pager_object *obj) +{ + if (obj == (Pager_object *)~0UL) { + unsigned use_cpu = location.xpos(); + obj = pager_threads[use_cpu]->pager_head(); + PINF("-- kernel memory usage of Genode PDs --"); + } + + if (!obj) + return; + + addr_t limit = 0; addr_t usage = 0; + Nova::pd_ctrl_debug(obj->pd_sel(), limit, usage); + + char const * thread_name = reinterpret_cast(obj->badge()); + PINF("pd=0x%lx pager=%p thread='%s' limit=0x%lx usage=0x%lx", + obj->pd_sel(), obj, thread_name, limit, usage); + + dump_kernel_quota_usage(static_cast(obj->child(Genode::Avl_node_base::LEFT))); + dump_kernel_quota_usage(static_cast(obj->child(Genode::Avl_node_base::RIGHT))); +} + + Pager_object::Pager_object(unsigned long badge, Affinity::Location location) : _badge(badge), @@ -552,8 +581,7 @@ Pager_object::Pager_object(unsigned long badge, Affinity::Location location) /* create portal for final cleanup call used during destruction */ res = create_portal(sel_pt_cleanup(), pd_sel, ec_sel, Mtd(0), - reinterpret_cast(_invoke_handler), - reinterpret_cast(this)); + reinterpret_cast(_invoke_handler), this); if (res != Nova::NOVA_OK) { PERR("could not create pager cleanup portal, error = %u\n", res); throw Rm_session::Invalid_thread(); @@ -592,6 +620,210 @@ Pager_object::~Pager_object() cap_map()->remove(_client_exc_vcpu, NUM_INITIAL_VCPU_PT_LOG2, false); } +uint8_t Pager_object::handle_oom(addr_t transfer_from, + char const * src_pd, char const * src_thread, + enum Pager_object::Policy policy) +{ + const char * dst_pd = "unknown"; + const char * dst_thread = reinterpret_cast(badge()); + + enum { QUOTA_TRANSFER_PAGES = 2 }; + + if (transfer_from == SRC_CORE_PD) + transfer_from = __core_pd_sel; + + /* request current kernel quota usage of target pd */ + addr_t limit_before = 0, usage_before = 0; + Nova::pd_ctrl_debug(pd_sel(), limit_before, usage_before); + + if (verbose_oom) { + addr_t limit_source = 0, usage_source = 0; + /* request current kernel quota usage of source pd */ + Nova::pd_ctrl_debug(transfer_from, limit_source, usage_source); + + PINF("oom - '%s:%s' (%lu/%lu) - transfer %u pages from '%s:%s' (%lu/%lu)", + dst_pd, dst_thread, + usage_before, limit_before, QUOTA_TRANSFER_PAGES, + src_pd, src_thread, usage_source, limit_source); + } + + uint8_t res = Nova::NOVA_PD_OOM; + + if (transfer_from != pd_sel()) { + /* upgrade quota */ + uint8_t res = Nova::pd_ctrl(transfer_from, Pd_op::TRANSFER_QUOTA, + pd_sel(), QUOTA_TRANSFER_PAGES); + if (res == Nova::NOVA_OK) + return res; + } + + /* retry upgrade using core quota if policy permits */ + if (policy == UPGRADE_PREFER_SRC_TO_DST) { + if (transfer_from != __core_pd_sel) { + res = Nova::pd_ctrl(__core_pd_sel, Pd_op::TRANSFER_QUOTA, + pd_sel(), QUOTA_TRANSFER_PAGES); + if (res == Nova::NOVA_OK) + return res; + } + } + + PWRN("kernel memory quota upgrade failed - trigger memory free up for " + "causing '%s:%s' - donator is '%s:%s', policy=%u", + dst_pd, dst_thread, src_pd, src_thread, policy); + + /* if nothing helps try to revoke memory */ + enum { REMOTE_REVOKE = true, PD_SELF = true }; + Mem_crd crd_all(0, ~0U, Rights(true, true, true)); + Nova::revoke(crd_all, PD_SELF, REMOTE_REVOKE, pd_sel(), sel_sm_block()); + + /* re-request current kernel quota usage of target pd */ + addr_t limit_after = 0, usage_after = 0; + Nova::pd_ctrl_debug(pd_sel(), limit_after, usage_after); + /* if we could free up memory we continue */ + if (usage_after < usage_before) + return Nova::NOVA_OK; + + /* + * There is still the chance that memory gets freed up, but one has to + * wait until RCU period is over. If we are in the pager code, we can + * instruct the kernel to block the faulting client thread during the reply + * syscall. If we are in a normal (non-pagefault) RPC service call, + * we can't block. The caller of this function can decide based on + * the return value what to do and whether blocking is ok. + */ + return Nova::NOVA_PD_OOM; +} + + +void Pager_object::_oom_handler(addr_t pager_dst, addr_t pager_src, + addr_t reason) +{ + if (sizeof(void *) == 4) { + /* On 32 bit edx and ecx as second and third regparm parameter is not + * available. It is used by the kernel internally to store ip/sp. + */ + asm volatile ("" : "=D" (pager_src)); + asm volatile ("" : "=S" (reason)); + } + + Thread_base * myself = Thread_base::myself(); + Utcb * utcb = reinterpret_cast(myself->utcb()); + Pager_object * obj_dst = reinterpret_cast(pager_dst); + Pager_object * obj_src = reinterpret_cast(pager_src); + + /* Policy used if the Process of the paged thread runs out of memory */ + enum Policy policy = Policy::UPGRADE_CORE_TO_DST; + + + /* check assertions - cases that should not happen on Genode@Nova */ + enum { NO_OOM_PT = ~0UL, EC_OF_PT_OOM_OUTSIDE_OF_CORE }; + + /* all relevant (user) threads should have a OOM PT */ + bool assert = pager_dst == NO_OOM_PT; + + /* + * PT OOM solely created by core and they have to point to the pager + * thread inside core. + */ + assert |= pager_dst == EC_OF_PT_OOM_OUTSIDE_OF_CORE; + + /* + * This pager thread does solely reply to IPC calls - it should never + * cause OOM during the sending phase of a IPC. + */ + assert |= ((reason & (SELF | SEND)) == (SELF | SEND)); + + /* + * This pager thread should never send words (untyped items) - it just + * answers page faults by typed items (memory mappings). + */ + assert |= utcb->msg_words(); + + if (assert) { + PERR("unknown OOM case - stop core pager thread"); + utcb->set_msg_word(0); + reply(myself->stack_top(), myself->tid().exc_pt_sel + Nova::SM_SEL_EC); + } + + /* be strict in case of the -strict- STOP policy - stop causing thread */ + if (policy == STOP) { + PERR("PD has insufficient kernel memory left - stop thread"); + utcb->set_msg_word(0); + reply(myself->stack_top(), obj_dst->sel_sm_block()); + } + + char const * src_pd = "core"; + char const * src_thread = "pager"; + + addr_t transfer_from = SRC_CORE_PD; + + switch (pager_src) { + case SRC_PD_UNKNOWN: + /* should not happen on Genode - we create and know every PD in core */ + PERR("Unknown PD has insufficient kernel memory left - stop thread"); + utcb->set_msg_word(0); + reply(myself->stack_top(), myself->tid().exc_pt_sel + Nova::SM_SEL_EC); + + case SRC_CORE_PD: + /* core PD -> other PD, which has insufficient kernel resources */ + + if (!(reason & SELF)) { + /* case that src thread != this thread in core */ + src_thread = "unknown"; + utcb->set_msg_word(0); + } + + transfer_from = __core_pd_sel; + break; + default: + /* non core PD -> non core PD */ + utcb->set_msg_word(0); + + if (pager_src == pager_dst || policy == UPGRADE_CORE_TO_DST) + transfer_from = __core_pd_sel; + else { + /* delegation of items between different PDs */ + src_pd = "unknown"; + src_thread = reinterpret_cast(obj_src->badge()); + transfer_from = obj_src->pd_sel(); + } + } + + uint8_t res = obj_dst->handle_oom(transfer_from, src_pd, src_thread, + policy); + if (res == Nova::NOVA_OK) + /* handling succeeded - continue with original IPC */ + reply(myself->stack_top()); + + /* transfer nothing */ + utcb->set_msg_word(0); + + if (res != Nova::NOVA_PD_OOM) + PERR("Upgrading kernel memory failed, policy %u, error %u " + "- stop thread finally", policy, res); + /* else: caller will get blocked until RCU period is over */ + + /* block caller in semaphore */ + reply(myself->stack_top(), obj_dst->sel_sm_block()); +} + + +addr_t Pager_object::get_oom_portal() +{ + addr_t const pt_oom = sel_oom_portal(); + + unsigned const use_cpu = location.xpos(); + addr_t const ec_sel = pager_threads[use_cpu]->tid().ec_sel; + + uint8_t res = create_portal(pt_oom, __core_pd_sel, ec_sel, Mtd(0), + reinterpret_cast(_oom_handler), this); + if (res == Nova::NOVA_OK) + return pt_oom; + + PERR("creating portal for out of memory notification failed"); + return 0; +} + /********************** ** Pager activation ** @@ -615,6 +847,9 @@ Pager_activation_base::Pager_activation_base(const char *name, size_t stack_size void Pager_activation_base::entry() { } +Pager_object * Pager_activation_base::pager_head() { + return _ep ? _ep->first() : nullptr; } + /********************** ** Pager entrypoint ** **********************/ diff --git a/repos/base-nova/src/core/platform.cc b/repos/base-nova/src/core/platform.cc index 1f5500b1d7..c56a9add64 100644 --- a/repos/base-nova/src/core/platform.cc +++ b/repos/base-nova/src/core/platform.cc @@ -184,7 +184,7 @@ static void init_core_page_fault_handler() EXC_BASE = 0 }; - addr_t ec_sel = cap_map()->insert(); + addr_t ec_sel = cap_map()->insert(1); uint8_t ret = create_ec(ec_sel, __core_pd_sel, boot_cpu(), CORE_PAGER_UTCB_ADDR, core_pager_stack_top(), diff --git a/repos/base-nova/src/core/platform_thread.cc b/repos/base-nova/src/core/platform_thread.cc index 7ecdf69bce..50510a1e9a 100644 --- a/repos/base-nova/src/core/platform_thread.cc +++ b/repos/base-nova/src/core/platform_thread.cc @@ -62,24 +62,37 @@ int Platform_thread::start(void *ip, void *sp) return -2; } + addr_t const pt_oom = _pager->get_oom_portal(); + if (!pt_oom || map_local((Utcb *)Thread_base::myself()->utcb(), + Obj_crd(pt_oom, 0), Obj_crd(_sel_pt_oom(), 0))) { + PERR("setup of out-of-memory notification portal - failed"); + return -8; + } + if (!is_main_thread()) { - addr_t initial_sp = reinterpret_cast(sp); - addr_t utcb = is_vcpu() ? 0 : round_page(initial_sp); + addr_t const initial_sp = reinterpret_cast(sp); + addr_t const utcb = is_vcpu() ? 0 : round_page(initial_sp); if (_sel_exc_base == Native_thread::INVALID_INDEX) { PERR("exception base not specified"); return -3; } + _pager->assign_pd(_pd->pd_sel()); + /* ip == 0 means that caller will use the thread as worker */ bool thread_global = ip; - uint8_t res = create_ec(_sel_ec(), _pd->pd_sel(), _location.xpos(), - utcb, initial_sp, _sel_exc_base, thread_global); - if (res != Nova::NOVA_OK) { - PERR("creation of new thread failed %u", res); - return -4; - } + uint8_t res; + do { + res = create_ec(_sel_ec(), _pd->pd_sel(), _location.xpos(), + utcb, initial_sp, _sel_exc_base, thread_global); + if (res == Nova::NOVA_PD_OOM && Nova::NOVA_OK != _pager->handle_oom()) { + _pd->assign_pd(Native_thread::INVALID_INDEX); + PERR("creation of new thread failed %u", res); + return -4; + } + } while (res != Nova::NOVA_OK); if (!thread_global) { _features |= WORKER; @@ -127,10 +140,15 @@ int Platform_thread::start(void *ip, void *sp) Obj_crd::RIGHT_SM_UP | Obj_crd::RIGHT_SM_DOWN; unsigned pts = is_vcpu() ? NUM_INITIAL_VCPU_PT_LOG2 : NUM_INITIAL_PT_LOG2; + enum { KEEP_FREE_PAGES_NOT_AVAILABLE_FOR_UPGRADE = 2, UPPER_LIMIT_PAGES = 32 }; Obj_crd initial_pts(_sel_exc_base, pts, rights); - uint8_t res = create_pd(pd_sel, pd_core_sel, initial_pts); + uint8_t res = create_pd(pd_sel, pd_core_sel, initial_pts, + KEEP_FREE_PAGES_NOT_AVAILABLE_FOR_UPGRADE, UPPER_LIMIT_PAGES); if (res != NOVA_OK) { PERR("create_pd returned %d", res); + + _pager->dump_kernel_quota_usage(); + goto cleanup_pd; } @@ -151,10 +169,14 @@ int Platform_thread::start(void *ip, void *sp) _pager->client_set_ec(_sel_ec()); _pager->initial_eip((addr_t)ip); _pager->initial_esp((addr_t)sp); + _pager->assign_pd(pd_sel); + + do { + /* let the thread run */ + res = create_sc(_sel_sc(), pd_sel, _sel_ec(), + Qpd(Qpd::DEFAULT_QUANTUM, _priority)); + } while (res == Nova::NOVA_PD_OOM && Nova::NOVA_OK == _pager->handle_oom()); - /* let the thread run */ - res = create_sc(_sel_sc(), pd_sel, _sel_ec(), - Qpd(Qpd::DEFAULT_QUANTUM, _priority)); if (res != NOVA_OK) { /* * Reset pd cap since thread got not running and pd cap will @@ -164,6 +186,7 @@ int Platform_thread::start(void *ip, void *sp) _pager->client_set_ec(Native_thread::INVALID_INDEX); _pager->initial_eip(0); _pager->initial_esp(0); + _pager->assign_pd(Native_thread::INVALID_INDEX); PERR("create_sc returned %d", res); goto cleanup_ec; @@ -208,8 +231,12 @@ void Platform_thread::resume() using namespace Nova; if (!is_worker()) { - uint8_t res = create_sc(_sel_sc(), _pd->pd_sel(), _sel_ec(), - Qpd(Qpd::DEFAULT_QUANTUM, _priority)); + uint8_t res; + do { + res = create_sc(_sel_sc(), _pd->pd_sel(), _sel_ec(), + Qpd(Qpd::DEFAULT_QUANTUM, _priority)); + } while (res == Nova::NOVA_PD_OOM && Nova::NOVA_OK == _pager->handle_oom()); + if (res == NOVA_OK) return; } @@ -312,7 +339,7 @@ unsigned long long Platform_thread::execution_time() const Platform_thread::Platform_thread(const char *name, unsigned prio, int thread_id) : - _pd(0), _pager(0), _id_base(cap_map()->insert(1)), + _pd(0), _pager(0), _id_base(cap_map()->insert(2)), _sel_exc_base(Native_thread::INVALID_INDEX), _location(boot_cpu(), 0, 0, 0), _features(0), _priority(Cpu_session::scale_priority(Nova::Qpd::DEFAULT_PRIORITY, prio)) @@ -332,6 +359,6 @@ Platform_thread::~Platform_thread() using namespace Nova; /* free ec and sc caps */ - revoke(Obj_crd(_id_base, 1)); - cap_map()->remove(_id_base, 1, false); + revoke(Obj_crd(_id_base, 2)); + cap_map()->remove(_id_base, 2, false); } diff --git a/repos/base-nova/src/kernel/target.mk b/repos/base-nova/src/kernel/target.mk index 59e2ba88cd..29388ccd6f 100644 --- a/repos/base-nova/src/kernel/target.mk +++ b/repos/base-nova/src/kernel/target.mk @@ -13,7 +13,7 @@ CC_WARN = -Wall -Wextra -Waggregate-return -Wcast-align -Wcast-qual \ -Wpointer-arith -Wredundant-decls -Wshadow -Wwrite-strings \ -Wabi -Wctor-dtor-privacy -Wno-non-virtual-dtor \ -Wold-style-cast -Woverloaded-virtual -Wsign-promo \ - -Wframe-larger-than=64 -Wlogical-op -Wstrict-null-sentinel \ + -Wframe-larger-than=112 -Wlogical-op -Wstrict-null-sentinel \ -Wstrict-overflow=5 -Wvolatile-register-var CC_OPT += -pipe \ -fdata-sections -fomit-frame-pointer -freg-struct-return \ diff --git a/tool/autopilot.list b/tool/autopilot.list index 26827c4dfb..18372d6d58 100644 --- a/tool/autopilot.list +++ b/tool/autopilot.list @@ -53,3 +53,4 @@ bomb cpu_quota stdcxx nic_loopback +platform