diff --git a/arch/Kconfig b/arch/Kconfig index 4d94cbf40a..3beab388c2 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -142,6 +142,7 @@ config ARCH_X86_64 select LIBC_ARCH_ELF_64BIT if LIBC_ARCH_ELF select ARCH_TOOLCHAIN_GNU select ARCH_HAVE_BACKTRACE + select ARCH_HAVE_FORK ---help--- x86-64 architectures. diff --git a/arch/x86_64/src/common/CMakeLists.txt b/arch/x86_64/src/common/CMakeLists.txt index 79ba14789f..3ee09cfdb1 100644 --- a/arch/x86_64/src/common/CMakeLists.txt +++ b/arch/x86_64/src/common/CMakeLists.txt @@ -33,6 +33,10 @@ set(SRCS x86_64_udelay.c x86_64_tcbinfo.c) +if(CONFIG_ARCH_HAVE_FORK) + list(APPEND SRCS x86_64_fork.c fork.S) +endif() + if(CONFIG_PCI) list(APPEND SRCS x86_64_pci.c) endif() diff --git a/arch/x86_64/src/common/Make.defs b/arch/x86_64/src/common/Make.defs index ccf8bee3ac..3c2db5bf01 100644 --- a/arch/x86_64/src/common/Make.defs +++ b/arch/x86_64/src/common/Make.defs @@ -25,6 +25,11 @@ CMN_CSRCS += x86_64_getintstack.c x86_64_mdelay.c x86_64_initialize.c CMN_CSRCS += x86_64_modifyreg8.c x86_64_modifyreg16.c x86_64_modifyreg32.c CMN_CSRCS += x86_64_nputs.c x86_64_switchcontext.c x86_64_udelay.c +ifeq ($(CONFIG_ARCH_HAVE_FORK),y) +CMN_CSRCS += x86_64_fork.c +CMN_ASRCS += fork.S +endif + ifeq ($(CONFIG_PCI),y) CMN_CSRCS += x86_64_pci.c endif diff --git a/arch/x86_64/src/common/fork.S b/arch/x86_64/src/common/fork.S new file mode 100644 index 0000000000..fba2301d54 --- /dev/null +++ b/arch/x86_64/src/common/fork.S @@ -0,0 +1,137 @@ +/**************************************************************************** + * arch/x86_64/src/common/fork.S + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "x86_64_fork.h" +#include "x86_64_internal.h" + + .file "fork.S" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: up_fork + * + * Description: + * The up_fork() function is the base of fork() function that provided in + * libc, and fork() is implemented as a wrapper of up_fork() function. + * The fork() function has the same effect as posix fork(), except that the + * behavior is undefined if the process created by fork() either modifies + * any data other than a variable of type pid_t used to store the return + * value from fork(), or returns from the function in which fork() was + * called, or calls any other function before successfully calling _exit() + * or one of the exec family of functions. + * + * This thin layer implements fork by simply calling up_fork() with the + * fork() context as an argument. The overall sequence is: + * + * 1) User code calls fork(). fork() collects context information and + * transfers control up up_fork(). + * 2) x86_64_fork() and calls nxtask_setup_fork(). + * 3) nxtask_setup_fork() allocates and configures the child task's TCB. + * This consists of: + * - Allocation of the child task's TCB. + * - Initialization of file descriptors and streams + * - Configuration of environment variables + * - Allocate and initialize the stack + * - Setup the input parameters for the task. + * - Initialization of the TCB (including call to up_initial_state()) + * 4) x86_64_fork() provides any additional operating context. arm_fork must: + * - Initialize special values in any CPU registers that were not + * already configured by up_initial_state() + * 5) x86_64_fork() then calls nxtask_start_fork() + * 6) nxtask_start_fork() then executes the child thread. + * + * Input Parameters: + * None + * + * Returned Value: + * Upon successful completion, fork() returns 0 to the child process and + * returns the process ID of the child process to the parent process. + * Otherwise, -1 is returned to the parent, no child process is created, + * and errno is set to indicate the error. + * + ****************************************************************************/ + +/* Stack Layout: + * | ......... | + * | rip | <- rsp when enter up_fork + * | [ss] | + * | [rflags] | + * | [cs] | + * | [rsp] | S + * | [rbp] | a + * | [rbx] | v + * | [r15] | i + * | [r14] | n + * | [r13] | g + * | [r12] | <- rsp before calling x86_64_fork, rdi = rsp + * | ......... | + */ + + .globl up_fork + .type up_fork, @function + +up_fork: + movq %rsp, %rax + addq $8, %rax + movq %ss, %rdi + pushq %rdi + pushfq + movq %cs, %rdi + pushq %rdi + + /* push %rsp */ + + pushq %rax + pushq %rbp + pushq %rbx + pushq %r15 + pushq %r14 + pushq %r13 + pushq %r12 + movq %rsp, %rdi + + /* call function */ + + callq x86_64_fork + + /* Do not modify return value %rax */ + /* Restore non-volatile registers */ + + popq %r12 + popq %r13 + popq %r14 + popq %r15 + popq %rbx + popq %rbp + /* consume %rsp */ + popq %rdi + popq %rdi + popq %rdi + popq %rdi + retq diff --git a/arch/x86_64/src/common/x86_64_fork.c b/arch/x86_64/src/common/x86_64_fork.c new file mode 100644 index 0000000000..87ae9b482e --- /dev/null +++ b/arch/x86_64/src/common/x86_64_fork.c @@ -0,0 +1,197 @@ +/**************************************************************************** + * arch/x86_64/src/common/x86_64_fork.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "x86_64_fork.h" +#include "x86_64_internal.h" +#include "sched/sched.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: x86_64_fork + * + * Description: + * The fork() function has the same effect as posix fork(), except that the + * behavior is undefined if the process created by fork() either modifies + * any data other than a variable of type pid_t used to store the return + * value from fork(), or returns from the function in which fork() was + * called, or calls any other function before successfully calling _exit() + * or one of the exec family of functions. + * + * The overall sequence is: + * + * 1) User code calls fork(). fork() collects context information and + * transfers control up x86_64_fork(). + * 2) x86_64_fork() and calls nxtask_setup_fork(). + * 3) nxtask_setup_fork() allocates and configures the child task's TCB. + * This consists of: + * - Allocation of the child task's TCB. + * - Initialization of file descriptors and streams + * - Configuration of environment variables + * - Allocate and initialize the stack + * - Setup the input parameters for the task. + * - Initialization of the TCB (including call to up_initial_state()) + * 4) x86_64_fork() provides any additional operating context. It must: + * - Initialize special values in any CPU registers that were not + * already configured by up_initial_state() + * 5) x86_64_fork() then calls nxtask_start_fork() + * 6) nxtask_start_fork() then executes the child thread. + * + * nxtask_abort_fork() may be called if an error occurs between steps 3 and + * 6. + * + * Input Parameters: + * context - Caller context information saved by fork() + * + * Returned Value: + * Upon successful completion, fork() returns 0 to the child process and + * returns the process ID of the child process to the parent process. + * Otherwise, -1 is returned to the parent, no child process is created, + * and errno is set to indicate the error. + * + ****************************************************************************/ + +pid_t x86_64_fork(const struct fork_s *context) +{ + struct tcb_s *parent = this_task(); + struct task_tcb_s *child; + uint64_t newsp; + uint64_t newfp; + uint64_t newtop; + uint64_t stacktop; + uint64_t stackutil; + + sinfo("fork context [%p]:\n", context); + sinfo(" rbx:%08" PRIx64 " rbp:%08" PRIx64 "\n" + " r12:%08" PRIx64 " r13:%08" PRIx64 "\n", + context->rbx, context->rbp, context->r12, context->r13); + sinfo(" r14:%08" PRIx64 " r15:%08" PRIx64 "\n", + context->r14, context->r15); + sinfo(" sp:%08" PRIx64 " ret ip:%08" PRIx64 "\n", + context->rsp, context->rip); + + /* Allocate and initialize a TCB for the child task. */ + + child = nxtask_setup_fork((start_t)context->rip); + if (!child) + { + serr("ERROR: nxtask_setup_fork failed\n"); + return (pid_t)ERROR; + } + + sinfo("TCBs: Parent=%p Child=%p\n", parent, child); + + /* How much of the parent's stack was utilized? The ARM uses + * a push-down stack so that the current stack pointer should + * be lower than the initial, adjusted stack pointer. The + * stack usage should be the difference between those two. + */ + + stacktop = (uint64_t)XCP_ALIGN_DOWN((uintptr_t)parent->stack_base_ptr + + parent->adj_stack_size - + XCPTCONTEXT_SIZE); + DEBUGASSERT(stacktop > context->rsp); + stackutil = stacktop - context->rsp; + + sinfo("Parent: stackutil:%" PRIu64 "\n", stackutil); + + /* Make some feeble effort to preserve the stack contents. This is + * feeble because the stack surely contains invalid pointers and other + * content that will not work in the child context. However, if the + * user follows all of the caveats of fork() usage, even this feeble + * effort is overkill. + */ + + newtop = (uint64_t)XCP_ALIGN_DOWN((uintptr_t)child->cmn.stack_base_ptr + + child->cmn.adj_stack_size - + XCPTCONTEXT_SIZE); + + newsp = newtop - stackutil; + + /* Move the register context (from parent) to newtop. */ + + memcpy(child->cmn.xcp.regs, parent->xcp.regs, XCPTCONTEXT_SIZE); + + memcpy((void *)newsp, (const void *)context->rsp, stackutil); + + /* Was there a frame pointer in place before? */ + + if (context->rbp >= context->rsp && context->rbp < stacktop) + { + uint32_t frameutil = stacktop - context->rbp; + newfp = newtop - frameutil; + } + else + { + newfp = context->rbp; + } + + /* We do not need to update the frame-pointer */ + + sinfo("Old stack top:%08" PRIx64 " RSP:%08" PRIx64 " RBP:%08" PRIx64 "\n", + stacktop, context->rsp, context->rbp); + sinfo("New stack top:%08" PRIx64 " RSP:%08" PRIx64 "\n", + newtop, newsp); + + /* Update the stack pointer, frame pointer, and volatile registers. When + * the child TCB was initialized, all of the values were set to zero. + * up_initial_state() altered a few values, but the return value in RAX + * should be cleared to zero, providing the indication to the newly started + * child thread. + */ + + child->cmn.xcp.regs[REG_RAX] = 0; /* Parent proc return 0 */ + child->cmn.xcp.regs[REG_R12] = context->r12; /* Non-volatile register r12 */ + child->cmn.xcp.regs[REG_R13] = context->r13; /* Non-volatile register r13 */ + child->cmn.xcp.regs[REG_R14] = context->r14; /* Non-volatile register r14 */ + child->cmn.xcp.regs[REG_R15] = context->r15; /* Non-volatile register r15 */ + child->cmn.xcp.regs[REG_RBX] = context->rbx; /* Non-volatile register rbx */ + child->cmn.xcp.regs[REG_SS] = context->ss; /* SS */ + child->cmn.xcp.regs[REG_CS] = context->cs; /* CS */ + child->cmn.xcp.regs[REG_RFLAGS] = context->rflags; + child->cmn.xcp.regs[REG_RIP] = context->rip; + child->cmn.xcp.regs[REG_RSP] = newsp; /* Stack pointer */ + child->cmn.xcp.regs[REG_RBP] = newfp; /* Like registers */ + + /* And, finally, start the child task. On a failure, nxtask_start_fork() + * will discard the TCB by calling nxtask_abort_fork(). + */ + + return nxtask_start_fork(child); +} diff --git a/arch/x86_64/src/common/x86_64_fork.h b/arch/x86_64/src/common/x86_64_fork.h new file mode 100644 index 0000000000..3cd752db59 --- /dev/null +++ b/arch/x86_64/src/common/x86_64_fork.h @@ -0,0 +1,58 @@ +/**************************************************************************** + * arch/x86_64/src/common/x86_64_fork.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_X86_64_SRC_COMMON_X86_64_FORK_H +#define __ARCH_X86_64_SRC_COMMON_X86_64_FORK_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define FORK_SIZEOF (sizeof(struct fork_s)) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +#ifndef __ASSEMBLY__ +struct fork_s +{ + uint64_t r12; /* 0 */ + uint64_t r13; /* 8 */ + uint64_t r14; /* 16 */ + uint64_t r15; /* 24 */ + uint64_t rbx; /* 32 */ + uint64_t rbp; /* 40 */ + uint64_t rsp; /* 48 */ + uint64_t cs; /* 56 */ + uint64_t rflags; /* 64 */ + uint64_t ss; /* 72 */ + uint64_t rip; /* 80 */ + + /* Assuming fork do not use any float or vector registers */ +}; +#endif + +#endif /* __ARCH_X86_64_SRC_COMMON_X86_64_FORK_H */