| /**************************************************************************** |
| * apps/testing/ostest/fork.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * 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 <nuttx/config.h> |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #include "ostest.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #define FORK_HEAPSIZE 256 |
| #define FORK_PARENTMARK 0x5a |
| #define FORK_CHILDMARK 0xa5 |
| |
| /* Distinct values written through a pointer to a stack local, to check that |
| * the child's stack is at the same virtual address as the parent's. |
| */ |
| |
| #define FORK_STACKPARENT 0x1234 |
| #define FORK_STACKCHILD 0x5678 |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* .data and .bss, which a fork() child must get its own copy of */ |
| |
| static volatile int g_forkdata = 1; |
| static volatile int g_forkbss; |
| static FAR unsigned char *g_forkheap; |
| |
| /* Pointer to a local in fork_test()'s frame, taken before fork(). In .data |
| * rather than on the stack: the compiler could rematerialise a local |
| * pointer from the current stack pointer, which would test nothing. |
| */ |
| |
| static FAR volatile int *g_forkstackptr; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: fork_child |
| * |
| * Description: |
| * Everything here is forbidden to a vfork() child and permitted to a |
| * fork() child. Returns the child's exit status. |
| * |
| ****************************************************************************/ |
| |
| static int fork_child(void) |
| { |
| FAR char *scratch; |
| |
| /* The parent must see none of this. */ |
| |
| g_forkdata = 2; |
| g_forkbss = 2; |
| memset(g_forkheap, FORK_CHILDMARK, FORK_HEAPSIZE); |
| |
| /* A vfork() child may not call these; a fork() child may. */ |
| |
| scratch = malloc(64); |
| if (scratch == NULL) |
| { |
| printf("fork_test: ERROR Child could not malloc()\n"); |
| return 1; |
| } |
| |
| strlcpy(scratch, "child", 64); |
| printf("fork_test: Child running independently (%s)\n", scratch); |
| free(scratch); |
| |
| /* Give the parent time to make its own writes, so that if the two shared |
| * memory we would see the parent's values below rather than our own. |
| */ |
| |
| usleep(200 * 1000); |
| |
| if (g_forkdata != 2 || g_forkbss != 2) |
| { |
| printf("fork_test: ERROR Child saw the parent's writes: " |
| "data=%d bss=%d\n", g_forkdata, g_forkbss); |
| return 1; |
| } |
| |
| if (g_forkheap[0] != FORK_CHILDMARK || |
| g_forkheap[FORK_HEAPSIZE - 1] != FORK_CHILDMARK) |
| { |
| printf("fork_test: ERROR Child saw the parent's heap writes\n"); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: fork_test |
| * |
| * Description: |
| * Verify the defining property of POSIX fork(): the child gets its own |
| * copy of the parent's memory, in both directions. That is the exact |
| * opposite of the sharing vfork() gives. Also verifies that none of |
| * vfork()'s restrictions apply. |
| * |
| ****************************************************************************/ |
| |
| int fork_test(void) |
| { |
| pid_t pid; |
| int status = 0; |
| int ret = 0; |
| |
| /* volatile so it is really in memory and the compare is not folded. */ |
| |
| volatile int stackvar = FORK_STACKPARENT; |
| |
| printf("fork_test: Started\n"); |
| |
| /* Publish its address before forking. See g_forkstackptr. */ |
| |
| g_forkstackptr = &stackvar; |
| |
| g_forkdata = 1; |
| g_forkbss = 1; |
| |
| g_forkheap = malloc(FORK_HEAPSIZE); |
| if (g_forkheap == NULL) |
| { |
| printf("fork_test: ERROR Failed to allocate the heap probe\n"); |
| ASSERT(false); |
| return -1; |
| } |
| |
| memset(g_forkheap, FORK_PARENTMARK, FORK_HEAPSIZE); |
| |
| pid = fork(); |
| if (pid == 0) |
| { |
| /* The child's stack is at the parent's virtual addresses, so this |
| * pointer names the child's own live local. A relocated stack would |
| * write into the copy of the parent's instead. |
| */ |
| |
| *g_forkstackptr = FORK_STACKCHILD; |
| if (stackvar != FORK_STACKCHILD) |
| { |
| printf("fork_test: ERROR Child stack was relocated: wrote %d " |
| "through %p, local at %p reads %d\n", |
| FORK_STACKCHILD, g_forkstackptr, &stackvar, stackvar); |
| _exit(1); |
| } |
| |
| /* Returns from fork_child() and from this branch -- both illegal |
| * for a vfork() child. |
| */ |
| |
| _exit(fork_child()); |
| } |
| else if (pid < 0) |
| { |
| printf("fork_test: ERROR fork() failed: %d\n", errno); |
| free(g_forkheap); |
| ASSERT(false); |
| return -1; |
| } |
| |
| /* Parent runs concurrently: write now, check isolation afterwards. */ |
| |
| g_forkdata = 3; |
| g_forkbss = 3; |
| memset(g_forkheap, FORK_PARENTMARK, FORK_HEAPSIZE); |
| |
| #ifdef CONFIG_SCHED_WAITPID |
| /* Wait for the child to be done before comparing memory. waitpid() blocks |
| * on a child that is still alive whether or not its exit status will be |
| * retained, so this synchronises either way; ECHILD simply means the child |
| * had already finished, which is just as good. It is not a failure: |
| * ostest_main() sets SA_NOCLDWAIT on SIGCHLD for the whole run, so an |
| * exited child's status is not kept even where CONFIG_SCHED_CHILD_STATUS |
| * is enabled. |
| */ |
| |
| if (waitpid(pid, &status, 0) != pid && errno != ECHILD) |
| { |
| printf("fork_test: ERROR waitpid() failed: %d\n", errno); |
| free(g_forkheap); |
| ASSERT(false); |
| return -1; |
| } |
| #else |
| sleep(1); |
| #endif |
| |
| if (g_forkdata != 3 || g_forkbss != 3) |
| { |
| printf("fork_test: ERROR Parent saw the child's writes: " |
| "data=%d bss=%d (expected 3, 3)\n", g_forkdata, g_forkbss); |
| ret = -1; |
| } |
| |
| if (g_forkheap[0] != FORK_PARENTMARK || |
| g_forkheap[FORK_HEAPSIZE - 1] != FORK_PARENTMARK) |
| { |
| printf("fork_test: ERROR Parent saw the child's heap writes\n"); |
| ret = -1; |
| } |
| |
| /* The child wrote to the same stack address; the parent must not see it */ |
| |
| if (stackvar != FORK_STACKPARENT) |
| { |
| printf("fork_test: ERROR Parent saw the child's stack write: " |
| "%d (expected %d)\n", stackvar, FORK_STACKPARENT); |
| ret = -1; |
| } |
| |
| #ifdef CONFIG_SCHED_WAITPID |
| if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) |
| { |
| printf("fork_test: ERROR Child reported failure, status 0x%04x\n", |
| status); |
| ret = -1; |
| } |
| #endif |
| |
| free(g_forkheap); |
| g_forkheap = NULL; |
| |
| if (ret < 0) |
| { |
| ASSERT(false); |
| return ret; |
| } |
| |
| printf("fork_test: Parent and child had independent memory\n"); |
| return 0; |
| } |