blob: 9d2c3ae91373dd0316c20c7dd824ac60db3e33b1 [file]
/****************************************************************************
* 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;
}