Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1636821
  • 博文数量: 511
  • 博客积分: 967
  • 博客等级: 准尉
  • 技术积分: 2560
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-06 14:19
文章分类

全部博文(511)

文章存档

2016年(11)

2015年(61)

2014年(257)

2013年(63)

2012年(119)

分类: C/C++

2015-12-01 20:29:40

原文地址:Nuttx系统启动 作者:merafour

                                    Nuttx系统启动

                                                                             -------- 转载请注明出处

               -------- 2014-7-29.冷月追风

                                                                             -------- email

 

 TOC \o "1-3" \h \z \u

 

写在前面

       最近弄PX4飞控,使用了Nuttx实时操作系统。想必跑裸机而言,使用了操作系统的复杂度是不一样的。至少来说在初期复杂度增加的可不止一点点。

       然而在初期,最痛苦的还是Nuttx的资料不够,简直是稀少。所以也就只能一点点地啃源码以期望能够理清Nuttx的系统流程与基本轮廓。毕竟跑系统不比跑裸机,自己用到的资源一目了然。引入了C++之后有些东西就更加显得不是那么的清晰了。

       其实到这个文档写完《》的时候我才发现我反了一个很严重的错误:我一直都在以裸奔的方式在分析PX4,这给我带来了极大的麻烦,使得我一开始就在代码中找寻应用启动的过程。其实Nuttx只是启动了系统而已,对于应用,那是在脚本中启动的。

1. 走流程

       Nuttx系统我并没有完全搞透,只是从系统的第一条代码到我们的飞控启动的基本轮廓理清了而已,各中细节还得深入分析。

       那么我们先来讲解系统启动后的第一条代码即入口。

1.1 入口

       PX4使用的是STM32,入口自然就在stm32_start.c中:

void __start(void)

{

  const uint32_t *src;

  uint32_t *dest;

 

#ifdef CONFIG_ARMV7M_STACKCHECK

  /* Set the stack limit before we attempt to call any functions */

 

  __asm__ volatile ("sub r10, sp, %0" : : "r" (CONFIG_IDLETHREAD_STACKSIZE - 64) : );

#endif

 

  /* Configure the uart so that we can get debug output as soon as possible */

 

  stm32_clockconfig();

  stm32_fpuconfig();

  stm32_lowsetup();

  stm32_gpioinit();

  showprogress('A');

 

  /* Clear .bss.  We'll do this inline (vs. calling memset) just to be

   * certain that there are no issues with the state of global variables.

   */

 

  for (dest = &_sbss; dest < &_ebss; )

    {

      *dest++ = 0;

    }

 

  showprogress('B');

 

  /* Move the intialized data section from his temporary holding spot in

   * FLASH into the correct place in SRAM.  The correct place in SRAM is

   * give by _sdata and _edata.  The temporary location is in FLASH at the

   * end of all of the other read-only data (.text, .rodata) at _eronly.

   */

 

  for (src = &_eronly, dest = &_sdata; dest < &_edata; )

    {

      *dest++ = *src++;

    }

 

  showprogress('C');

 

  /* Perform early serial initialization */

 

#ifdef USE_EARLYSERIALINIT

  up_earlyserialinit();

#endif

  showprogress('D');

 

  /* For the case of the separate user-/kernel-space build, perform whatever

   * platform specific initialization of the user memory is required.

   * Normally this just means initializing the user space .data and .bss

   * segments.

   */

 

#ifdef CONFIG_NUTTX_KERNEL

  stm32_userspace();

  showprogress('E');

#endif

 

  /* Initialize onboard resources */

 

  stm32_boardinitialize();

  showprogress('F');

 

  /* Then start NuttX */

 

  showprogress('\r');

  showprogress('\n');

  os_start();

 

  /* Shoulnd't get here */

 

  for(;;);

}

我们可以先通过注释大概了解这里都做了哪些事情。在这之前还有一个程序在跑:bootloader。这个不是我们今天讨论的话题。在最后我们看到调用了一个os_start()函数也就是接下来系统将启动,对应的源文件是os_start.c:

void os_start(void)

{

  int i;

 

  slldbg("Entry\n");

 

  /* Initialize RTOS Data ***************************************************/

  /* Initialize all task lists */

 

  dq_init(&g_readytorun);

  dq_init(&g_pendingtasks);

  dq_init(&g_waitingforsemaphore);

#ifndef CONFIG_DISABLE_SIGNALS

  dq_init(&g_waitingforsignal);

#endif

#ifndef CONFIG_DISABLE_MQUEUE

  dq_init(&g_waitingformqnotfull);

  dq_init(&g_waitingformqnotempty);

#endif

#ifdef CONFIG_PAGING

  dq_init(&g_waitingforfill);

#endif

  dq_init(&g_inactivetasks);

  sq_init(&g_delayed_kufree);

#if defined(CONFIG_NUTTX_KERNEL) && defined(CONFIG_MM_KERNEL_HEAP)

  sq_init(&g_delayed_kfree);

#endif

 

  /* Initialize the logic that determine unique process IDs. */

 

  g_lastpid = 0;

  for (i = 0; i < CONFIG_MAX_TASKS; i++)

    {

      g_pidhash[i].tcb = NULL;

      g_pidhash[i].pid = INVALID_PROCESS_ID;

    }

 

  /* Assign the process ID of ZERO to the idle task */

 

  g_pidhash[ PIDHASH(0)].tcb = &g_idletcb.cmn;

  g_pidhash[ PIDHASH(0)].pid = 0;

 

  /* Initialize the IDLE task TCB *******************************************/

  /* Initialize a TCB for this thread of execution.  NOTE:  The default

   * value for most components of the g_idletcb are zero.  The entire

   * structure is set to zero.  Then only the (potentially) non-zero

   * elements are initialized. NOTE:  The idle task is the only task in

   * that has pid == 0 and sched_priority == 0.

   */

 

  bzero((void*)&g_idletcb, sizeof(struct task_tcb_s));

  g_idletcb.cmn.task_state = TSTATE_TASK_RUNNING;

  g_idletcb.cmn.entry.main = (main_t)os_start;

 

  /* Set the IDLE task name */

 

#if CONFIG_TASK_NAME_SIZE > 0

  strncpy(g_idletcb.cmn.name, g_idlename, CONFIG_TASK_NAME_SIZE-1);

#endif /* CONFIG_TASK_NAME_SIZE */

 

  /* Configure the task name in the argument list.  The IDLE task does

   * not really have an argument list, but this name is still useful

   * for things like the NSH PS command.

   *

   * In the kernel mode build, the arguments are saved on the task's stack

   * and there is no support that yet.

   */

 

#if defined(CONFIG_CUSTOM_STACK) || !defined(CONFIG_NUTTX_KERNEL)

#if CONFIG_TASK_NAME_SIZE > 0

  g_idletcb.argv[0] = g_idletcb.cmn.name;

#else

  g_idletcb.argv[0] = (char*)g_idlename;

#endif /* CONFIG_TASK_NAME_SIZE */

#endif /* CONFIG_CUSTOM_STACK || !CONFIG_NUTTX_KERNEL */

 

  /* Then add the idle task's TCB to the head of the ready to run list */

 

  dq_addfirst((FAR dq_entry_t*)&g_idletcb, (FAR dq_queue_t*)&g_readytorun);

 

  /* Initialize the processor-specific portion of the TCB */

 

  up_initial_state(&g_idletcb.cmn);

 

  /* Initialize RTOS facilities *********************************************/

  /* Initialize the semaphore facility(if in link).  This has to be done

   * very early because many subsystems depend upon fully functional

   * semaphores.

   */

 

#ifdef CONFIG_HAVE_WEAKFUNCTIONS

  if (sem_initialize != NULL)

#endif

    {

      sem_initialize();

    }

 

  /* Initialize the memory manager */

 

  {

    FAR void *heap_start;

    size_t heap_size;

 

    /* Get the user-mode heap from the platform specific code and configure

     * the user-mode memory allocator.

     */

 

    up_allocate_heap(&heap_start, &heap_size);

    kumm_initialize(heap_start, heap_size);

 

#if defined(CONFIG_NUTTX_KERNEL) && defined(CONFIG_MM_KERNEL_HEAP)

    /* Get the kernel-mode heap from the platform specific code and configure

     * the kernel-mode memory allocator.

     */

 

    up_allocate_kheap(&heap_start, &heap_size);

    kmm_initialize(heap_start, heap_size);

#endif

  }

 

  /* Initialize tasking data structures */

 

#if defined(CONFIG_SCHED_HAVE_PARENT) && defined(CONFIG_SCHED_CHILD_STATUS)

#ifdef CONFIG_HAVE_WEAKFUNCTIONS

  if (task_initialize != NULL)

#endif

    {

      task_initialize();

    }

#endif

 

  /* Initialize the interrupt handling subsystem (if included) */

 

#ifdef CONFIG_HAVE_WEAKFUNCTIONS

  if (irq_initialize != NULL)

#endif

    {

      irq_initialize();

    }

 

  /* Initialize the watchdog facility (if included in the link) */

 

#ifdef CONFIG_HAVE_WEAKFUNCTIONS

  if (wd_initialize != NULL)

#endif

    {

      wd_initialize();

    }

 

  /* Initialize the POSIX timer facility (if included in the link) */

 

#ifndef CONFIG_DISABLE_CLOCK

#ifdef CONFIG_HAVE_WEAKFUNCTIONS

  if (clock_initialize != NULL)

#endif

    {

      clock_initialize();

    }

#endif

 

#ifndef CONFIG_DISABLE_POSIX_TIMERS

#ifdef CONFIG_HAVE_WEAKFUNCTIONS

  if (timer_initialize != NULL)

#endif

    {

      timer_initialize();

    }

#endif

 

  /* Initialize the signal facility (if in link) */

 

#ifndef CONFIG_DISABLE_SIGNALS

#ifdef CONFIG_HAVE_WEAKFUNCTIONS

  if (sig_initialize != NULL)

#endif

    {

      sig_initialize();

    }

#endif

 

  /* Initialize the named message queue facility (if in link) */

 

#ifndef CONFIG_DISABLE_MQUEUE

#ifdef CONFIG_HAVE_WEAKFUNCTIONS

  if (mq_initialize != NULL)

#endif

    {

      mq_initialize();

    }

#endif

 

  /* Initialize the thread-specific data facility (if in link) */

 

#ifndef CONFIG_DISABLE_PTHREAD

#ifdef CONFIG_HAVE_WEAKFUNCTIONS

  if (pthread_initialize != NULL)

#endif

    {

      pthread_initialize();

    }

#endif

 

  /* Initialize the file system (needed to support device drivers) */

 

#if CONFIG_NFILE_DESCRIPTORS > 0

#ifdef CONFIG_HAVE_WEAKFUNCTIONS

  if (fs_initialize != NULL)

#endif

    {

      fs_initialize();

    }

#endif

 

  /* Initialize the network system */

 

#ifdef CONFIG_NET

#if 0

  if (net_initialize != NULL)

#endif

    {

      net_initialize();

    }

#endif

 

  /* The processor specific details of running the operating system

   * will be handled here.  Such things as setting up interrupt

   * service routines and starting the clock are some of the things

   * that are different for each  processor and hardware platform.

   */

 

  up_initialize();

 

  /* Initialize the C libraries (if included in the link).  This

   * is done last because the libraries may depend on the above.

   */

 

#ifdef CONFIG_HAVE_WEAKFUNCTIONS

  if (lib_initialize != NULL)

#endif

    {

      lib_initialize();

    }

 

  /* IDLE Group Initialization **********************************************/

  /* Allocate the IDLE group and suppress child status. */

 

#ifdef HAVE_TASK_GROUP

  DEBUGVERIFY(group_allocate(&g_idletcb));

#endif

 

  /* Create stdout, stderr, stdin on the IDLE task.  These will be

   * inherited by all of the threads created by the IDLE task.

   */

 

  DEBUGVERIFY(group_setupidlefiles(&g_idletcb));

 

  /* Complete initialization of the IDLE group.  Suppress retention

   * of child status in the IDLE group.

   */

 

#ifdef HAVE_TASK_GROUP

  DEBUGVERIFY(group_initialize(&g_idletcb));

  g_idletcb.cmn.group->tg_flags = GROUP_FLAG_NOCLDWAIT;

#endif

 

  /* Bring Up the System ****************************************************/

  /* Create initial tasks and bring-up the system */

 

  DEBUGVERIFY(os_bringup());

 

  /* The IDLELoop**********************************************************/

  /* When control is return to this point, the system is idle. */

 

  sdbg("Beginning Idle Loop\n");

  for (;;)

    {

      /* Perform garbage collection (if it is not being done by the worker

       * thread).  This cleans-up memory de-allocations that were queued

       * because they could not be freed in that execution context (for

       * example, if the memory was freed from an interrupt handler).

       */

 

#ifndef CONFIG_SCHED_WORKQUEUE

      /* We must have exclusive access to the memory manager to do this

       * BUT the idle task cannot wait on a semaphore.  So we only do

       * the cleanup now if we can get the semaphore -- this should be

       * possible because if the IDLE thread is running, no other task is!

       */

 

      if (kmm_trysemaphore() == 0)

        {

          sched_garbagecollection();

          kmm_givesemaphore();

        }

#endif

 

      /* Perform any processor-specific idle state operations */

 

      up_idle();

    }

}

我们看到这里初始化了各种资源,最后进入了一个空闲任务。而在空闲任务的前面我们看到对其他任务也进行了初始化,这是在os_bringup.c文件中:

int os_bringup(void)

{

  int taskid;

 

  /* Setup up the initial environment for the idle task.  At present, this

   * may consist of only the initial PATH variable.  The PATH variable is

   * (probably) not used by the IDLE task.  However, the environment

   * containing the PATH variable will be inherited by all of the threads

   * created by the IDLE task.

   */

 

#if !defined(CONFIG_DISABLE_ENVIRON) && defined(CONFIG_PATH_INITIAL)

  (void)setenv("PATH", CONFIG_PATH_INITIAL, 1);

#endif

 

  /* Start the page fill worker kernel thread that will resolve page faults.

   * This should always be the first thread started because it may have to

   * resolve page faults in other threads

   */

 

#ifdef CONFIG_PAGING

  svdbg("Starting paging thread\n");

 

  g_pgworker = KERNEL_THREAD("pgfill", CONFIG_PAGING_DEFPRIO,

                             CONFIG_PAGING_STACKSIZE,

                             (main_t)pg_worker, (FAR char * const *)NULL);

  DEBUGASSERT(g_pgworker > 0);

#endif

 

  /* Start the worker thread that will serve as the device driver "bottom-

   * half" and will perform misc garbage clean-up.

   */

 

#ifdef CONFIG_SCHED_WORKQUEUE

#ifdef CONFIG_SCHED_HPWORK

 

#ifdef CONFIG_SCHED_LPWORK

  svdbg("Starting high-priority kernel worker thread\n");

#else

  svdbg("Starting kernel worker thread\n");

#endif

 

  g_work[HPWORK].pid = KERNEL_THREAD(HPWORKNAME, CONFIG_SCHED_WORKPRIORITY,

                                     CONFIG_SCHED_WORKSTACKSIZE,

                                     (main_t)work_hpthread, (FAR char * const *)NULL);

  DEBUGASSERT(g_work[HPWORK].pid > 0);

 

  /* Start a lower priority worker thread for other, non-critical continuation

   * tasks

   */

 

#ifdef CONFIG_SCHED_LPWORK

 

  svdbg("Starting low-priority kernel worker thread\n");

 

  g_work[LPWORK].pid = KERNEL_THREAD(LPWORKNAME, CONFIG_SCHED_LPWORKPRIORITY,

                                     CONFIG_SCHED_LPWORKSTACKSIZE,

                                     (main_t)work_lpthread, (FAR char * const *)NULL);

  DEBUGASSERT(g_work[LPWORK].pid > 0);

 

#endif /* CONFIG_SCHED_LPWORK */

#endif /* CONFIG_SCHED_HPWORK */

 

#if defined(CONFIG_NUTTX_KERNEL) && defined(CONFIG_SCHED_USRWORK)

  /* Start the user-space work queue */

 

  DEBUGASSERT(USERSPACE->work_usrstart != NULL);

  taskid = USERSPACE->work_usrstart();

  DEBUGASSERT(taskid > 0);

#endif

 

#endif /* CONFIG_SCHED_WORKQUEUE */

 

  /* Once the operating system has been initialized, the system must be

   * started by spawning the user init thread of execution.  This is the

   * first user-mode thead.

   */

 

  svdbg("Starting init thread\n");

 

  /* Perform any last-minute, board-specific initialization, if so

   * configured.

   */

 

#ifdef CONFIG_BOARD_INITIALIZE

  board_initialize();

#endif

 

  /* Start the default application.  In a flat build, this is entrypoint

   * is given by the definitions, CONFIG_USER_ENTRYPOINT.  In the kernel

   * build, however, we must get the address of the entrypoint from the

   * header at the beginning of the user-space blob.

   */

 

#ifdef CONFIG_NUTTX_KERNEL

  DEBUGASSERT(USERSPACE->us_entrypoint != NULL);

  taskid = TASK_CREATE("init", SCHED_PRIORITY_DEFAULT,

                       CONFIG_USERMAIN_STACKSIZE, USERSPACE->us_entrypoint,

                       (FAR char * const *)NULL);

#else

  taskid = TASK_CREATE("init", SCHED_PRIORITY_DEFAULT,

                       CONFIG_USERMAIN_STACKSIZE,

                       (main_t)CONFIG_USER_ENTRYPOINT,

                       (FAR char * const *)NULL);

#endif

  ASSERT(taskid > 0);

 

  /* We an save a few bytes by discarding the IDLE thread's environment. */

 

#if !defined(CONFIG_DISABLE_ENVIRON) && defined(CONFIG_PATH_INITIAL)

  (void)clearenv();

#endif

 

  return OK;

}

这里跟我们关系最大的就是init进程。在Linux中也有init进程,是所有应用程序进程的祖先进程,进程ID1。这里其实类似。Init进程启动了所有应用程序进程。不过这里需要注意的是CONFIG_USER_ENTRYPOINT是在配置文件中指定的,相当于一个宏:

CONFIG_USER_ENTRYPOINT="nsh_main"

int nsh_main(int argc, char *argv[])

{

  int exitval = 0;

  int ret;

 

  /* Call all C++ static constructors */

 

#if defined(CONFIG_HAVE_CXX) && defined(CONFIG_HAVE_CXXINITIALIZE)

  up_cxxinitialize();

#endif

 

  /* Make sure that we are using our symbol take */

 

#if defined(CONFIG_LIBC_EXECFUNCS) && defined(CONFIG_EXECFUNCS_SYMTAB)

  exec_setsymtab(CONFIG_EXECFUNCS_SYMTAB, 0);

#endif

 

  /* Register the BINFS file system */

 

#if defined(CONFIG_FS_BINFS) && (CONFIG_BUILTIN)

  ret = builtin_initialize();

  if (ret < 0)

    {

     fprintf(stderr, "ERROR: builtin_initialize failed: %d\n", ret);

     exitval = 1;

   }

#endif

 

  /* Initialize the NSH library */

 

  nsh_initialize();

 

  /* If the Telnet console is selected as a front-end, then start the

   * Telnet daemon.

   */

 

#ifdef CONFIG_NSH_TELNET

  ret = nsh_telnetstart();

  if (ret < 0)

    {

     /* The daemon is NOT running.  Report the the error then fail...

      * either with the serial console up or just exiting.

      */

 

     fprintf(stderr, "ERROR: Failed to start TELNET daemon: %d\n", ret);

     exitval = 1;

   }

#endif

 

  /* If the serial console front end is selected, then run it on this thread */

 

#ifdef CONFIG_NSH_CONSOLE

  ret = nsh_consolemain(0, NULL);

 

  /* nsh_consolemain() should not return.  So if we get here, something

   * is wrong.

   */

 

  fprintf(stderr, "ERROR: nsh_consolemain() returned: %d\n", ret);

  exitval = 1;

#endif

 

  return exitval;

}

这里就相当于Linux中启动了shell,然后去执行初始化脚本。

int nsh_consolemain(int argc, char *argv[])

{

  FAR struct console_stdio_s *pstate = nsh_newconsole();

  int ret;

 

  DEBUGASSERT(pstate);

 

  /* Execute the start-up script */

 

#ifdef CONFIG_NSH_ROMFSETC

  (void)nsh_initscript(&pstate->cn_vtbl);

#endif

 

  /* Initialize any USB tracing options that were requested */

 

#ifdef CONFIG_NSH_USBDEV_TRACE

  usbtrace_enable(TRACE_BITSET);

#endif

 

  /* Execute the session */

 

  ret = nsh_session(pstate);

 

  /* Exit upon return */

 

  nsh_exit(&pstate->cn_vtbl, ret);

  return ret;

}

在这里实际上nsh_initscript和nsh_session都会去执行命令,但nsh_session从代码上看更像是Linux的命令行参数解析, nsh_initscript回去执行/etc/init.d/rcS脚本。

int nsh_initscript(FAR struct nsh_vtbl_s *vtbl)

{

  static bool initialized;

  bool already;

  int ret = OK;

 

  /* Atomic test and set of the initialized flag */

 

  sched_lock();

  already     = initialized;

  initialized = true;

  sched_unlock();

 

  /* If we have not already executed the init script, then do so now */

 

  if (!already)

    {

      ret = nsh_script(vtbl, "init", NSH_INITPATH);

    }

 

  return ret;

}

这里我们需要对NSH_INITPATH进行展开:

#  ifndef CONFIG_NSH_ROMFSMOUNTPT

#    define CONFIG_NSH_ROMFSMOUNTPT "/etc"

#  endif

 

#  ifndef CONFIG_NSH_INITSCRIPT

#    define CONFIG_NSH_INITSCRIPT "init.d/rcS"

#  endif

 

#  undef NSH_INITPATH

#  define NSH_INITPATH CONFIG_NSH_ROMFSMOUNTPT "/" CONFIG_NSH_INITSCRIPT

所以到了这里我们需要把目光转向启动脚本。后面我们所需要的很多东西,包括PX4飞控主线程都是在脚本中意命令的方式启动的。所以说如果抛开脚本,单纯看源码是看不出PX4飞控主线程是如何启动的,因为这些个东西根本就不在源码中。

1.2 命令

       说到脚本,我们当然知道脚本中最多的是命令。在Nuttx中命令跟Linux还是有很大的区别的。

       看到这里,可能有的人会想Nuttx到底跟Linux有什么关系。其实Nuttx只是跟Linux某些地方相似而已。而我多次提到Linux只是在分析Nuttx的时候以Linux为坐标,仅此而已。有了坐标,我就不至于完全迷失了方向。

       通过前面的分析,我们很容易想到接下来的代码分析将从nsh_script开始。

int nsh_script(FAR struct nsh_vtbl_s *vtbl, FAR const char *cmd,

               FAR const char *path)

{

  char *fullpath;

  FILE *stream;

  char *buffer;

  char *pret;

  int ret = ERROR;

 

  /* The path to the script may be relative to the current working directory */

 

  fullpath = nsh_getfullpath(vtbl, path);

  if (!fullpath)

    {

      return ERROR;

    }

 

  /* Get a reference to the common input buffer */

 

  buffer = nsh_linebuffer(vtbl);

  if (buffer)

    {

      /* Open the file containing the script */

 

      stream = fopen(fullpath, "r");

      if (!stream)

        {

          nsh_output(vtbl, g_fmtcmdfailed, cmd, "fopen", NSH_ERRNO);

          nsh_freefullpath(fullpath);

          return ERROR;

        }

 

      /*Loop, processing each command line in the script file (or

       * until an error occurs)

       */

 

      do

        {

          /* Get the next line of input from the file */

 

          fflush(stdout);

          pret = fgets(buffer, CONFIG_NSH_LINELEN, stream);

          if (pret)

            {

              /* Parse process the command.  NOTE:  this is recursive...

               * we got to cmd_sh via a call to nsh_parse.  So some

               * considerable amount of stack may be used.

               */

 

              ret = nsh_parse(vtbl, buffer);

            }

        }

      while (pret && ret == OK);

      fclose(stream);

    }

 

  nsh_freefullpath(fullpath);

  return ret;

}

这段代码并不难理解,就是打开了我们的脚本文件,然后逐行读取并解析执行。那显然nsh_parse就是解析这些脚本的关键。

int nsh_parse1(FAR struct nsh_vtbl_s *vtbl, char *cmdline)

{

  /* Does this command correspond to a builtin command?

   * nsh_builtin() returns:

   *

   *   -1 (ERROR)  if the application task corresponding to 'argv[0]' could not

   *               be started (possibly because it doesn not exist).

   *    0 (OK)     if the application task corresponding to 'argv[0]' was

   *               and successfully started.  If CONFIG_SCHED_WAITPID is

   *               defined, this return value also indicates that the

   *               application returned successful status (EXIT_SUCCESS)

   *    1          If CONFIG_SCHED_WAITPID is defined, then this return value

   *               indicates that the application task was spawned successfully

   *               but returned failure exit status.

   *

   * Note the priority if not effected by nice-ness.

   */

 

#if defined(CONFIG_NSH_BUILTIN_APPS) && (!defined(CONFIG_NSH_FILE_APPS) || !defined(CONFIG_FS_BINFS))

  ret = nsh_builtin(vtbl, argv[0], argv, redirfile, oflags);

#endif

    {

      /* Then execute the command in "foreground" -- i.e., while the user waits

       * for the next prompt.  nsh_execute will return:

       *

       * -1 (ERRROR) if the command was unsuccessful

       *  0 (OK)     if the command was successful

       */

 

      ret = nsh_execute(vtbl, argc, argv);

    }

  return nsh_saveresult(vtbl, true);

}

需要说明的是这个函数是删减过的,就是说只留下了我最想要看到的部分。nsh_builtin和nsh_execute分别对应两个数组:g_builtins和g_cmdmap。什么区别呢?在我看来这两组命令是可以合并到一起的,但如果真的这样做了会有一个问题,用户扩展不方便。于是对其进行区分。像ls这样的命令不需要用户实现及处理,就作为系统命令在g_cmdmap数组中,而作为用户的应用程序如ArduPilot完全有用户决定,便作为用户命令在g_builtins数组中。对于系统命令我们常用的基本都已经实现,通过特定的宏进行选择编译。而用户命令我们只需按照相应规则编写一个入口函数然并设定堆栈大小然后放到g_builtins数组中即可。

       Nuttx本身提供了一个g_builtins数组,但PX4没有使用。PX4使用了一些技巧,利用Makefile在编译的时候自动生成g_builtins数组。这样做好处是我们修改源码的时候可以不关心这个数组,但坏处也显而易见:增加了我们刚接触是阅读源码的壁垒。

       那么关于命令部分我们先讲解到这里。下面我们将稍微看下脚本。

1.3 启动脚本

       前面我们说过Nuttx的启动脚本是/etc/init.d/rcS,跟Linux是一样的。这样做也有一个好处,就是如果你对Linux比较熟悉即便你完全不看源码只要看下根文件系统你就能够找到启动脚本并进行分析。

       启动脚本并不少,我们不逐条分析。

set MODE autostart

set USB autoconnect

if rgbled start

then

        set HAVE_RGBLED 1

        rgbled rgb 16 16 16

else

        set HAVE_RGBLED 0

fi

echo "[init] looking for microSD..."

if mount -t vfat /dev/mmcsd0 /fs/microsd

then

    echo "[init] card mounted at /fs/microsd"

        set HAVE_MICROSD 1

    tone_alarm start

else

    echo "[init] no microSD card found"

        set HAVE_MICROSD 0

    tone_alarm 2

        if [ $HAVE_RGBLED == 1 ]

        then

                rgbled rgb 16 0 0

        fi

fi

if [ -f /fs/microsd/etc/rc ]

then

    echo "[init] reading /fs/microsd/etc/rc"

    sh /fs/microsd/etc/rc

fi

if [ -f /fs/microsd/etc/rc.txt ]

then

    echo "[init] reading /fs/microsd/etc/rc.txt"

    sh /fs/microsd/etc/rc.txt

fi

if [ $USB != autoconnect ]

then

    echo "[init] not connecting USB"

else

    if sercon

    then

       echo "[init] USB interface connected"

    else

       echo "[init] No USB connected"

    fi

fi

if [ -f /etc/init.d/rc.APM -a $HAVE_MICROSD == 1 -a ! -f /fs/microsd/APM/nostart ]

then

    echo Running rc.APM

    sh /etc/init.d/rc.APM

else

        nshterm /dev/ttyACM0 &

fi

我们看到先是设置了环境变量,然后打开了RGB灯,就是PX4飞控上的那个三色高亮LED,设置了这几个等如何闪烁。然后挂载SD卡并执行SD卡中的脚本,接下来自动连接USB,最后是执行rc.APM脚本。所以PX4要用的东西基本上都在rc.APM脚本中启动。

set deviceA /dev/ttyACM0

if [ -f /fs/microsd/APM ]

then

   echo "APM file found - renaming"

   mv /fs/microsd/APM /fs/microsd/APM.old

fi

if [ -f /fs/microsd/APM/nostart ]

then

   echo "APM/nostart found - skipping APM startup"

   sh /etc/init.d/rc.error

fi

if [ -f /bin/reboot ]

then

    echo "binfs already mounted"

else

    echo "Mounting binfs"

        if mount -t binfs /dev/null /bin

        then

                echo "binfs mounted OK"

        else

                sh /etc/init.d/rc.error

        fi

fi

set sketch NONE

if rm /fs/microsd/APM/boot.log

then

   echo "removed old boot.log"

fi

set logfile /fs/microsd/APM/BOOT.LOG

if [ ! -f /bin/ArduPilot ]

then

   echo "/bin/ardupilot not found"

   sh /etc/init.d/rc.error

fi

if mkdir /fs/microsd/APM > /dev/null

then

     echo "Created APM directory"

fi

if [ -f /bin/lsm303d ]

then

     echo "Detected FMUv2 board"

     set BOARD FMUv2

else

     echo "Detected FMUv1 board"

     set BOARD FMUv1

fi

if [ $BOARD == FMUv1 ]

then

   set deviceC /dev/ttyS2

   if [ -f /fs/microsd/APM/AUXPWM.en ]

   then

      set deviceD /dev/null

   else

      set deviceD /dev/ttyS1

   fi

else

   set deviceC /dev/ttyS1

   set deviceD /dev/ttyS2

fi

if uorb start

then

    echo "uorb started OK"

else

    sh /etc/init.d/rc.error

fi

if [ -f /fs/microsd/APM/mkblctrl ]

then

    echo "Setting up mkblctrl driver"

    echo "Setting up mkblctrl driver" >> $logfile

    mkblctrl -d /dev/pwm_output

fi

if [ -f /fs/microsd/APM/mkblctrl_+ ]

then

    echo "Setting up mkblctrl driver +"

    echo "Setting up mkblctrl driver +" >> $logfile

    mkblctrl -mkmode + -d /dev/pwm_output

fi

if [ -f /fs/microsd/APM/mkblctrl_x ]

then

    echo "Setting up mkblctrl driver x"

    echo "Setting up mkblctrl driver x" >> $logfile

    mkblctrl -mkmode x -d /dev/pwm_output

fi

set HAVE_PX4IO false

if px4io start norc

then

    set HAVE_PX4IO true

else

    echo Loading /etc/px4io/px4io.bin

    tone_alarm MBABGP

    if px4io update /etc/px4io/px4io.bin

    then

    echo "upgraded PX4IO firmware OK"

        tone_alarm MSPAA

    else

    echo "Failed to upgrade PX4IO firmware"

        tone_alarm MNGGG

    fi

    sleep 1

    if px4io start norc

    then

        set HAVE_PX4IO true

    tone_alarm start

    fi

fi

if [ $HAVE_PX4IO == true ]

then

    echo "PX4IO board OK"

    if px4io checkcrc /etc/px4io/px4io.bin

    then

        echo "PX4IO CRC OK"

    else

        echo "PX4IO CRC failure"

        echo "PX4IO CRC failure" >> $logfile   

        tone_alarm MBABGP

        if px4io forceupdate 14662 /etc/px4io/px4io.bin

        then

               sleep 1

               if px4io start norc

               then

                  echo "PX4IO restart OK"

                  echo "PX4IO restart OK" >> $logfile   

                  tone_alarm MSPAA

               else

                  echo "PX4IO restart failed"

                  echo "PX4IO restart failed" >> $logfile   

                  tone_alarm MNGGG

                  sh /etc/init.d/rc.error

               fi

        else

               echo "PX4IO update failed"

               echo "PX4IO update failed" >> $logfile   

               tone_alarm MNGGG

        fi

    fi

else

    echo "No PX4IO board found"

    echo "No PX4IO board found" >> $logfile

    if [ $BOARD == FMUv2 ]

    then

       sh /etc/init.d/rc.error

    fi

fi

if [ $BOARD == FMUv1 -a $deviceD == /dev/ttyS1 ]

then

        echo "Setting FMU mode_serial"

        fmu mode_serial

else

        echo "Setting FMU mode_pwm"

        fmu mode_pwm

fi

echo "Starting APM sensors"

if ms5611 start

then

    echo "ms5611 started OK"

else

    sh /etc/init.d/rc.error

fi

if adc start

then

    echo "adc started OK"

else

    sh /etc/init.d/rc.error

fi

if [ $BOARD == FMUv1 ]

then

    echo "Starting FMUv1 sensors"

    if hmc5883 start

    then

        echo "hmc5883 started OK"

        if hmc5883 calibrate

        then

          echo "hmc5883 calibrate OK"

        else

          echo "hmc5883 calibrate failed"

          echo "hmc5883 calibrate failed" >> $logfile

          tone_alarm MSBBB

        fi

    else

        echo "hmc5883 start failed"

        echo "hmc5883 start failed" >> $logfile

        sh /etc/init.d/rc.error

    fi

    if mpu6000 start

    then

       echo "mpu6000  started OK"

    else

       sh /etc/init.d/rc.error

    fi

    if l3gd20 start

    then

       echo "l3gd20 started OK"

    else

       echo "No l3gd20"

       echo "No l3gd20" >> $logfile

    fi

else

    echo "Starting FMUv2 sensors"

    if hmc5883 -C -X start

    then

        echo "Have external hmc5883"

    else

        echo "No external hmc5883"

    fi

    if hmc5883 -C -I -R 4 start

    then

        echo "Have internal hmc5883"

    else

        echo "No internal hmc5883"

    fi

    if mpu6000 -X -R 4 start

    then

       echo "Found MPU6000 external"

       set HAVE_FMUV3 true

    else

       echo "No MPU6000 external"

       set HAVE_FMUV3 false

    fi

    if [ $HAVE_FMUV3 == true ]

    then

        if mpu6000 -R 14 start

        then

               echo "Found MPU6000 internal"

        else

               echo "No MPU6000"

               echo "No MPU6000" >> $logfile

               sh /etc/init.d/rc.error

        fi

        if l3gd20 -X -R 4 start

        then

               echo "l3gd20 external started OK"

        else

               echo "No l3gd20"

               sh /etc/init.d/rc.error

        fi

        if lsm303d -X -R 6 start

        then

               echo "lsm303d external started OK"

        else

               echo "No lsm303d"

               sh /etc/init.d/rc.error

        fi

    else

        if mpu6000 start

        then

               echo "Found MPU6000"

        else

               echo "No MPU6000"

               echo "No MPU6000" >> $logfile

        fi

        if l3gd20 start

        then

               echo "l3gd20 started OK"

        else

               sh /etc/init.d/rc.error

        fi

        if lsm303d start

        then

               echo "lsm303d started OK"

        else

               sh /etc/init.d/rc.error

        fi

    fi

fi

if ets_airspeed start

then

    echo "Found ETS airspeed sensor"

fi

if meas_airspeed start

then

    echo "Found MEAS airspeed sensor"

fi

if ll40ls start

then

    echo "Found ll40ls sensor"

fi

if mb12xx start

then

    echo "Found mb12xx sensor"

fi

echo "Trying PX4IO board"

if mtd start /fs/mtd

then

    echo "started mtd driver OK"

else

    echo "failed to start mtd driver"

    echo "failed to start mtd driver" >> $logfile

    sh /etc/init.d/rc.error          

fi

if mtd readtest /fs/mtd

then

    echo "mtd readtest OK"

else

    echo "failed to read mtd"

    echo "failed to read mtd" >> $logfile

    sh /etc/init.d/rc.error          

fi

if [ $BOARD == FMUv2 ]

then

  if mtd rwtest /fs/mtd

  then

    echo "mtd rwtest OK"

  else

    echo "failed to test mtd"

    echo "failed to test mtd" >> $logfile

    sh /etc/init.d/rc.error          

  fi

fi

echo Starting ArduPilot $deviceA $deviceC $deviceD

if ArduPilot -d $deviceA -d2 $deviceC -d3 $deviceD start

then

    echo ArduPilot started OK

else

    sh /etc/init.d/rc.error

fi

echo "rc.APM finished"

这个脚本比较长。第一句应该是对设备进行重定向,然后对上一次的日志进行备份并创建日志。然后通过lsm303d判断PX4的版本,是V1还是V2。换句话说V1V2最大的区别是V2有lsm303d传感器,这是一个地磁传感器,而且内置加计。从这里我们也可以看出相比V1V2在设计上采用了内置地磁的设计。然后根据硬件版本分别初始化输入输出设备。

       后面那个uorb没搞懂是什么。后面的mkblctrl感觉上像是机型配置相关的,具体是与不是还有待证实。然后是跟新IO板的固件。接下来启动的fmu想必是一个很重要的进程。然后又是根据硬件版本进行初始化,启动传感器。后面那几个估计也是传感器,具体还需要查。Mtd呢肯定是跟存储设备相关的。

       最后,ArduPilot便是对于我们来讲最重要的一个进程,这个进程启动基本上就表示我们的飞控可以正常使用了。

 

 

 

 

 

 

 

 

 

 

 

 

 

Nuttx系统启动.pdf

阅读(3011) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~