Address Environments

CPUs that support memory management units (MMUs) may provide address environments within which tasks and their child threads execute. The configuration indicates the CPUs ability to support address environments by setting the configuration variable CONFIG_ARCH_HAVE_ADDRENV=y. That will enable the selection of the actual address environment support which is indicated by the selection of the configuration variable CONFIG_ARCH_ADDRENV=y. These address environments are created only when tasks are created via exec() or exec_module() (see include/nuttx/binfmt/binfmt.h).

When CONFIG_ARCH_ADDRENV=y is set in the board configuration, the CPU-specific logic must provide a set of interfaces as defined in the header file include/nuttx/arch.h. These interfaces are listed below and described in detail in the following paragraphs.

The CPU-specific logic must provide two categories in interfaces:

  1. Binary Loader Support. These are low-level interfaces used in binfmt/ to instantiate tasks with address environments. These interfaces all operate on type arch_addrenv_t which is an abstract representation of a task group’s address environment and the type must be defined inarch/arch.h if CONFIG_ARCH_ADDRENV is defined. These low-level interfaces include:

  2. Tasking Support. Other interfaces must be provided to support higher-level interfaces used by the NuttX tasking logic. These interfaces are used by the functions in sched/ and all operate on the task group which as been assigned an address environment by up_addrenv_clone().

    • up_addrenv_attach(): Clone the group address environment assigned to a new thread. This operation is done when a pthread is created that share’s the same address environment.

    • up_addrenv_detach(): Release the thread’s reference to a group address environment when a task/thread exits.

  3. Dynamic Stack Support. CONFIG_ARCH_STACK_DYNAMIC=y indicates that the user process stack resides in its own address space. This option is also required if CONFIG_BUILD_KERNEL and CONFIG_LIBC_EXECFUNCS are selected. Why? Because the caller’s stack must be preserved in its own address space when we instantiate the environment of the new process in order to initialize it.

    NOTE: The naming of the CONFIG_ARCH_STACK_DYNAMIC selection implies that dynamic stack allocation is supported. Certainly this option must be set if dynamic stack allocation is supported by a platform. But the more general meaning of this configuration environment is simply that the stack has its own address space.

    If CONFIG_ARCH_STACK_DYNAMIC=y is selected then the platform specific code must export these additional interfaces:

  4. If CONFIG_ARCH_KERNEL_STACK is selected, then each user process will have two stacks: (1) a large (and possibly dynamic) user stack and (2) a smaller kernel stack. However, this option is required if both CONFIG_BUILD_KERNEL and CONFIG_LIBC_EXECFUNCS are selected. Why? Because when we instantiate and initialize the address environment of the new user process, we will temporarily lose the address environment of the old user process, including its stack contents. The kernel C logic will crash immediately with no valid stack in place.

    If CONFIG_ARCH_KERNEL_STACK=y is selected then the platform specific code must export these additional interfaces:

int up_addrenv_create(size_t textsize, size_t datasize, size_t heapsize, FAR arch_addrenv_t *addrenv);

This function is called when a new task is created in order to instantiate an address environment for the new task group. up_addrenv_create() is essentially the allocator of the physical memory for the new task.

Parameters:
  • textsize – The size (in bytes) of the .text address environment needed by the task. This region may be read/execute only.

  • datasize – The size (in bytes) of the .bss/.data address environment needed by the task. This region may be read/write only.

  • heapsize – The initial size (in bytes) of the heap address environment needed by the task. This region may be read/write only.

  • addrenv – The location to return the representation of the task address environment.

Returns:

Zero (OK) on success; a negated errno value on failure.

int up_addrenv_destroy(arch_addrenv_t *addrenv)

This function is called when a final thread leaves the task group and the task group is destroyed. This function then destroys the defunct address environment, releasing the underlying physical memory allocated by up_addrenv_create().

Parameters:
  • addrenv – The representation of the task address environment previously returned by up_addrenv_create().

Returns:

Zero (OK) on success; a negated errno value on failure.

int up_addrenv_vtext(FAR arch_addrenv_t addrenv, FAR void **vtext)

Return the virtual .text address associated with the newly create address environment. This function is used by the binary loaders in order get an address that can be used to initialize the new task.

Parameters:
  • addrenv – The representation of the task address environment previously returned by up_addrenv_create().

  • vtext – The location to return the virtual address.

Returns:

Zero (OK) on success; a negated errno value on failure.

int up_addrenv_vdata(FAR arch_addrenv_t *addrenv, size_t textsize, FAR void **vdata)

Return the virtual .text address associated with the newly create address environment. This function is used by the binary loaders in order get an address that can be used to initialize the new task.

Parameters:
  • addrenv – The representation of the task address environment previously returned by up_addrenv_create().

  • textsize – For some implementations, the text and data will be saved in the same memory region (read/write/execute) and, in this case, the virtual address of the data just lies at this offset into the common region.

  • vdata – The location to return the virtual address.

Returns:

Zero (OK) on success; a negated errno value on failure.

ssize_t up_addrenv_heapsize(FAR const arch_addrenv_t *addrenv)

Return the initial heap allocation size. That is the amount of memory allocated by up_addrenv_create() when the heap memory region was first created. This may or may not differ from the heapsize parameter that was passed to up_addrenv_create().

Parameters:
  • addrenv – The representation of the task address environment previously returned by up_addrenv_create().

Returns:

The initial heap size allocated is returned on success; a negated errno value on failure.

int up_addrenv_select(arch_addrenv_t *addrenv)

After an address environment has been established for a task (via up_addrenv_create()), this function may be called to instantiate that address environment in the virtual address space. This might be necessary, for example, to load the code for the task from a file or to access address environment private data.

Parameters:
  • addrenv – The representation of the task address environment previously returned by up_addrenv_create().

Returns:

Zero (OK) on success; a negated errno value on failure.

int up_addrenv_clone(FAR const task_group_s *src, FAR struct task_group_s *dest)

Duplicate an address environment. This does not copy the underlying memory, only the representation that can be used to instantiate that memory as an address environment.

Parameters:
  • src – The address environment to be copied.

  • dest – The location to receive the copied address environment.

Returns:

Zero (OK) on success; a negated errno value on failure.

int up_addrenv_attach(FAR struct task_group_s *group, FAR struct tcb_s *tcb)

This function is called from the core scheduler logic when a thread is created that needs to share the address environment of its task group. In this case, the group’s address environment may need to be “cloned” for the child thread.

NOTE: In most platforms, nothing will need to be done in this case. Simply being a member of the group that has the address environment may be sufficient.

Parameters:
  • group – The task group to which the new thread belongs.

  • ctcb – The TCB of the thread needing the address environment.

Returns:

Zero (OK) on success; a negated errno value on failure.

int up_addrenv_detach(FAR struct task_group_s *group, FAR struct task_group_s *tcb)

This function is called when a task or thread exits in order to release its reference to an address environment. The address environment, however, should persist until up_addrenv_destroy() is called when the task group is itself destroyed. Any resources unique to this thread may be destroyed now.

Parameters:
  • group – The group to which the thread belonged.

  • tcb – The TCB of the task or thread whose the address environment will be released.

Returns:

Zero (OK) on success; a negated errno value on failure.

int up_addrenv_ustackalloc(FAR struct tcb_s *tcb, size_t stacksize)

This function is called when a new thread is created in order to instantiate an address environment for the new thread’s stack. up_addrenv_ustackalloc() is essentially the allocator of the physical memory for the new task’s stack.

Parameters:
  • tcb – The TCB of the thread that requires the stack address environment.

  • stacksize – The size (in bytes) of the initial stack address environment needed by the task. This region may be read/write only.

Returns:

Zero (OK) on success; a negated errno value on failure.

int up_addrenv_ustackfree(FAR struct tcb_s *tcb)

This function is called when any thread exits. This function then destroys the defunct address environment for the thread’s stack, releasing the underlying physical memory.

Parameters:
  • tcb – The TCB of the thread that no longer requires the stack address environment.

Returns:

Zero (OK) on success; a negated errno value on failure

int up_addrenv_vustack(FAR const struct tcb_s *tcb, FAR void **vstack)

Return the virtual address associated with the newly create stack address environment.

Parameters:
  • tcb – The TCB of the thread with the stack address environment of interest.

  • vstack – The location to return the stack virtual base address.

Returns:

Zero (OK) on success; a negated errno value on failure.

int up_addrenv_ustackselect(FAR const struct tcb_s *tcb)

After an address environment has been established for a task’s stack (via up_addrenv_ustackalloc(). This function may be called to instantiate that address environment in the virtual address space. This is a necessary step before each context switch to the newly created thread (including the initial thread startup).

Parameters:
  • tcb – The TCB of the thread with the stack address environment to be instantiated.

Returns:

Zero (OK) on success; a negated errno value on failure.

int up_addrenv_kstackalloc(FAR struct tcb_s *tcb)

This function is called when a new thread is created to allocate the new thread’s kernel stack. This function may be called for certain terminating threads which have no kernel stack. It must be tolerant of that case.

Parameters:
  • tcb – The TCB of the thread that requires the kernel stack.

Returns:

Zero (OK) on success; a negated errno value on failure.

int up_addrenv_kstackfree(FAR struct tcb_s *tcb);

This function is called when any thread exits. This function frees the kernel stack.

Parameters:
  • tcb – The TCB of the thread that no longer requires the kernel stack.

Returns:

Zero (OK) on success; a negated errno value on failure.