ReacOS源代碼閱讀之驅動--atexit實現原理和機制

    之前的<<main函數執行前後--全局構造和atexit>>文章裏說到atexit函數註冊的函數是在main函數之後執行的,同時類似於棧,後註冊的函數先執行。

    今天通過分析ReactOS的源代碼,來驗證這個結論吧。

    atexit函數註冊的參數是通過C語言的函數指針,註冊的函數是不帶參數的。首先看看atexit註冊機制的實現吧。

     註冊的實現在 E:\Open_Source_Code\ReactOS\sdk\lib\crt\stdlib\atexit.c 源文件中。

     

/*********************************************************************
 *		_onexit (MSVCRT.@)
 */
_onexit_t CDECL _onexit(_onexit_t func)
{
  TRACE("(%p)\n",func);

  if (!func)
    return NULL;

  LOCK_EXIT;
  if (atexit_registered > atexit_table_size - 1)
  {
    _onexit_t *newtable;
    TRACE("expanding table\n");
    newtable = calloc(atexit_table_size + 32, sizeof(void *));
    if (!newtable)
    {
      TRACE("failed!\n");
      UNLOCK_EXIT;
      return NULL;
    }
    memcpy (newtable, atexit_table, atexit_table_size);
    atexit_table_size += 32;
    free (atexit_table);
    atexit_table = newtable;
  }
  atexit_table[atexit_registered] = func;
  atexit_registered++;
  UNLOCK_EXIT;
  return func;
}

/*********************************************************************
 *		atexit (MSVCRT.@)
 */
int CDECL atexit(void (*func)(void))
{
  TRACE("(%p)\n", func);
  return _onexit((_onexit_t)func) == (_onexit_t)func ? 0 : -1;
}
      如上代碼,atexit函數的實現其實很簡單,就是輸入一個函數指針,內部實現調用 _onexit 函數,該函數首先判斷已經分配的函數指針數組是否已經滿了,如果是,那麼多分配32個函數指針的空間。如下2句,就是把函數指針放入到指針數組的最後面,同時註冊的函數指針數量+1。

        atexit_table[atexit_registered] = func;
        atexit_registered++; 

      接下來就是調用的問題了。在 E:\Open_Source_Code\ReactOS\sdk\lib\crt\startup\crtexe.c 文件中,定義了函數 __tmainCRTStartup,該函數是c運行時庫的函數,在啓動一個進程的時候由系統調用,這個函數會調用我們定義的main函數。同時也會調用我們通過atexit函數註冊過的全部函數。

       

    __main ();
#ifdef WPRFLAG
    __winitenv = envp;
    /* C++ initialization.
       gcc inserts this call automatically for a function called main, but not for wmain.  */
    mainret = wmain (argc, argv, envp);
#else
    __initenv = envp;
    mainret = main (argc, argv, envp);
#endif
    if (!managedapp)
      exit (mainret);

    if (has_cctor == 0)
      _cexit ();

       如上代碼片段,__tmainCRTStartup 函數先調用了了main函數,等main函數執行完成,退出後。又調用了 _cexit 函數,這個函數就會去調用我們通過atexit註冊的函數。

      

void _cexit( void )
{
  LOCK_EXIT;
  __call_atexit();
  UNLOCK_EXIT;
}
   
_onexit_t *atexit_table = NULL;
int atexit_table_size = 0;
int atexit_registered = 0; /* Points to free slot */

/* INTERNAL: call atexit functions */
void __call_atexit(void)
{
  /* Note: should only be called with the exit lock held */
  TRACE("%d atext functions to call\n", atexit_registered);
  /* Last registered gets executed first */
  while (atexit_registered > 0)
  {
    atexit_registered--;
    TRACE("next is %p\n",atexit_table[atexit_registered]);
    if (atexit_table[atexit_registered])
      (*atexit_table[atexit_registered])();
    TRACE("returned\n");
  }
}

      上面的 __call_atexit 應該是不需要多餘的解釋了。通過這句  (*atexit_table[atexit_registered])(); 很明顯說明了,後面註冊的函數會先被調用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章