NSH “Built-In” Applications

Overview. In addition to these commands that are a part of NSH, external programs can also be executed as NSH commands. These external programs are called “Built-In” Applications for historic reasons. That terminology is somewhat confusing because the actual NSH commands as described above are truly “built-into” NSH whereas these applications are really external to NuttX.

These applications are built-into NSH in the sense that they can be executed by simply typing the name of the application at the NSH prompt. Built-in application support is enabled with these configuration option:

  • CONFIG_BUILTIN: Enable NuttX support for builtin applications.

  • CONFIG_NSH_BUILTIN_APPS: Enable NSH support for builtin applications.

When these configuration options are set, you will also be able to see the built-in applications if you enter “nsh> help”. They will appear at the bottom of the list of NSH commands under:

Builtin Apps:

Note that no detailed help information beyond the name of the built-in application is provided.

Built-In Applications

Overview. The underlying logic that supports the NSH built-in applications is called “Built-In Applications”. The builtin application logic can be found at apps/builtin. This logic simply does the following:

  1. It supports registration mechanism so that builtin applications can dynamically register themselves at build time, and

  2. Utility functions to look up, list, and execute the builtin applications.

Built-In Application Utility Functions. The utility functions exported by the builtin application logic are prototyped in nuttx/include/nuttx/lib/builtin.h and apps/include/builtin.h. These utility functions include:

  • int builtin_isavail(FAR const char *appname); Checks for availability of application registered as appname during build time.

  • const char *builtin_getname(int index); Returns a pointer to a name of built-in application pointed by the index. This is the utility function that is used by NSH in order to list the available built-in applications when “nsh> help” is entered.

  • int exec_builtin(FAR const char *appname, FAR const char **argv); Executes built-in builtin application registered during compile time. This is the utility function used by NSH to execute the built-in application.

Autogenerated Header Files. Application entry points with their requirements are gathered together in two files when NuttX is first built:

  1. apps/builtin/builtin_proto.h: Prototypes of application task entry points.

  2. apps/builtin/builtin_list.h: Application specific information and start-up requirements

Registration of Built-In Applications. The NuttX build occurs in several phases as different build targets are executed: (1) context when the configuration is established, (2) depend when target dependencies are generated, and (3) default (all) when the normal compilation and link operations are performed. Built-in application information is collected during the make context build phase.

An example application that can be “built-in” is be found in the apps/examples/hello directory. Let’s walk through this specific cause to illustrate the general way that built-in applications are created and how they register themselves so that they can be used from NSH.

apps/examples/hello. The main routine for apps/examples/hello can be found in apps/examples/hello/main.c. The main routine is:

int hello_main(int argc, char *argv[])
{
  printf("Hello, World!!\n");
  return 0;
}

This is the built in function that will be registered during the context build phase of the NuttX build. That registration is performed by logic in apps/examples/hello/Makefile. But the build system gets to that logic through a rather tortuous path:

  1. The top-level context make target is in nuttx/Makefile. All build targets depend upon the context build target. For the apps/ directory, this build target will execute the context target in the apps/Makefile.

  2. The apps/Makefile will, in turn, execute the context targets in all of the configured sub-directories. In our case will include the Makefile in apps/examples.

  3. And finally, the apps/examples/Makefile will execute the context target in all configured examplesub-directories, getting us finally to apps/examples/Makefile which is covered below.

NOTE: Since this context build phase can only be executed one time, any subsequent configuration changes that you make will, then, not be reflected in the build sequence. That is a common area of confusion. Before you can instantiate the new configuration, you have to first get rid of the old configuration. The most drastic way to this is:

make distclean

But then you will have to re-configuration NuttX from scratch. But if you only want to re-build the configuration in the apps/ sub-directory, then there is a less labor-intensive way to do that. The following NuttX make command will remove the configuration only from the apps/ directory and will let you continue without re-configuring everything:

make apps_distclean

Logic for the context target in apps/examples/hello/Makefile registers the hello_main() application in the builtin’s builtin_proto.hand builtin_list.h files. That logic that does that in apps/examples/hello/Makefile is abstracted below:

  1. First, the Makefile includes apps/Make.defs:

    include $(APPDIR)/Make.defs
    

    This defines a macro called REGISTER that adds data to the builtin header files:

    define REGISTER
        @echo "Register: $1"
        @echo "{ \"$1\", $2, $3, $4 }," >> "$(APPDIR)/builtin/builtin_list.h"
        @echo "EXTERN int $4(int argc, char *argv[]);" >> "$(APPDIR)/builtin/builtin_proto.h"
    endef
    

    When this macro runs, you will see the output in the build “Register: hello”, that is a sure sign that the registration was successful.

  2. The make file then defines the application name (hello), the task priority (default), and the stack size that will be allocated in the task runs (2K):

    APPNAME         = hello
    PRIORITY        = SCHED_PRIORITY_DEFAULT
    STACKSIZE       = 2048
    
  3. And finally, the Makefile invokes the REGISTER macro to added the hello_main() builtin application. Then, when the system build completes, the hello command can be executed from the NSH command line. When the hello command is executed, it will start the task with entry point hello_main() with the default priority and with a stack size of 2K:

    context:
      $(call REGISTER,$(APPNAME),$(PRIORITY),$(STACKSIZE),$(APPNAME)_main)
    

Other Uses of Built-In Application. The primary purpose of builtin applications is to support command line execution of applications from NSH. However, there is one other use of builtin applications that should be mentioned.

  1. binfs. binfs is a tiny file system located at apps/builtin/binfs.c. This provides an alternative what of visualizing installed builtin applications. Without binfs, you can see the installed builtin applications using the NSH help command. binfs will create a tiny pseudo-file system mounted at /bin. Using binfs, you can see the available builtin applications by listing the contents of /bin directory. This gives some superficial Unix-like compatibility, but does not really add any new functionality.

Synchronous Built-In Applications

By default, built-in commands started from the NSH command line will run asynchronously with NSH. If you want to force NSH to execute commands then wait for the command to execute, you can enable that feature by adding the following to the NuttX configuration file:

CONFIG_SCHED_WAITPID=y

This configuration option enables support for the standard waitpid() RTOS interface. When that interface is enabled, NSH will use it to wait, sleeping until the built-in application executes to completion.

Of course, even with CONFIG_SCHED_WAITPID=y defined, specific applications can still be forced to run asynchronously by adding the ampersand (&) after the NSH command.