1. 程式人生 > >FreeSwitch 的初始化及其模組載入過程

FreeSwitch 的初始化及其模組載入過程



FS 主函式main()

Freeswitch的主函式是在檔案switch.c中定義的,該檔案的260行是整個程式的入口,主函式主要完成的功能是包括,命令列解析,初始化apr庫,構建全域性記憶體池,模組載入和初始化核心元件。

初始化apr庫是由apr_initialize()函式完成的,apr庫是apache的可移植動態庫,完成相關的記憶體池,執行緒管理的跨平臺工作。該函式的呼叫在主函式的659行。

745行的switch_core_set_globals()主要是完成全域性目錄的設定。不過,在switch_core_init()中再一次呼叫了該函式。

747行的pid= getpid()獲取程式的程序號。

754行利用apr_pool_create()建立一個匿名的記憶體池,由主函式中定義的switch_memory_pool_t*pool區域性指標指向,但是可以知道,該記憶體池將作為程式的整個執行週期所使用。

本分析最關鍵的一點出現在784行,該行呼叫了switch_core_init_and_modload()函式,該函式完成了核心元件的初始化以及各個模組的動態載入。最終,形成了一個統一的系統。

switch_core_init_and_modload()

函式定義在switch_core.c檔案中,第1526行。函式原型如下:

SWITCH_DECLARE(switch_status_t)

switch_core_init_and_modload(switch_core_flag_tflags, switch_bool_t console, const char **err)

其中,SWITCH_DECLARE(type)巨集在windows下展開為

#define SWITCH_DECLARE(type) __declspec(dllexport) type __stdcall

主要用於將函式宣告為dll的匯出符號,這樣,在其他模組中,便可以使用該函數了。而在其他系統平臺上,該巨集是一個空巨集,例如在linux下,共享庫的符號是全域性的,不需要宣告為匯出符號。一般來說,freeswitch其他的動態載入模組所定義的函式不需要用該巨集宣告,在windows平臺下,各個模組之間是隔離的,而核心模組中定義的函式大部分使用了該巨集宣告,因為其他模組需要大量使用核心模組中的核心函式,這裡所指的核心模組是FreeSwitchCoreLib共享物件。

於是可以知道,switch_core_init_and_modload()函式可以在其他依賴於核心模組的動態載入模組中使用,這裡主函式所在的模組是FreeSwitchConsole,依賴於核心模組,於是,便可以使用該函式來完成模組載入。

switch_core_init()

在該函式中呼叫了switch_core_init()函式,用來初始化一些全域性化的資訊,包括一個全域性的switch_runtime結構,各種全域性的雜湊表,互斥變數。一條一條地分心如下:

①  全域性的switch_runtime結構runtime部分欄位的初始化——

程式碼段如下:

     if(runtime.runlevel > 0) {

         /* one percustomer */

         returnSWITCH_STATUS_SUCCESS;

     }

     runtime.runlevel++;//從這裡可見,runlevel大於0是一個伺服器已啟動的標誌,所以不必在進行

     //以下的初始化操作,直接返回SWITCH_STATUS_SUCCESS即可。

     runtime.dummy_cng_frame.data =runtime.dummy_data;

     runtime.dummy_cng_frame.datalen = sizeof(runtime.dummy_data);

     runtime.dummy_cng_frame.buflen = sizeof(runtime.dummy_data);

     switch_set_flag((&runtime.dummy_cng_frame),SFF_CNG);

     switch_set_flag((&runtime),SCF_NO_NEW_SESSIONS);

     runtime.hard_log_level = SWITCH_LOG_DEBUG;

     runtime.mailer_app = "sendmail";

     runtime.mailer_app_args = "-t";

     runtime.max_dtmf_duration =SWITCH_MAX_DTMF_DURATION;

     runtime.default_dtmf_duration =SWITCH_DEFAULT_DTMF_DURATION;

runtime.min_dtmf_duration= SWITCH_MIN_DTMF_DURATION;

接下來又重新初始化了一遍apr庫,很奇怪,不知道是不是一個多餘的步驟。^_^

     /* INIT APR andCreate the pool context */

     if(apr_initialize() != SWITCH_STATUS_SUCCESS) {

         *err = "FATALERROR! Could not initialize APR\n";

         returnSWITCH_STATUS_MEMERR;

     }

     if(!(runtime.memory_pool = switch_core_memory_init())) {

         *err = "FATALERROR! Could not allocate memory pool\n";

         returnSWITCH_STATUS_MEMERR;

}//從這裡可以看見,全域性的runtime是有一個記憶體池來管理它所需要的其他資源的。

②  安裝時的目錄資訊的相關設定,與runtime結構掛鉤,程式碼如下

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.base_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//主目錄,即工程所在目錄,一般為./bin,./表示安裝路徑

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.mod_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//模組所在目錄,一般為安裝目錄./mod,./表示安裝路徑

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.conf_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//配置檔案所在目錄,一般為./conf

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.log_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//日誌所在的目錄。一般為./log

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.run_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//程序檔案所在目錄,一般為./run,程序檔案為freeswitch.pid

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.db_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//資料庫檔案所在目錄,一般為./db

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.script_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//指令碼檔案所在目錄,一般為./script,存放系統需要執行的指令碼檔案,

//比較常用的由javascript指令碼和lua指令碼。

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.htdocs_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.grammar_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.recordings_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//錄音檔案所在目錄

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.sounds_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//聲音檔案所在目錄。

switch_dir_make_recursive(SWITCH_GLOBAL_dirs.temp_dir,SWITCH_DEFAULT_DIR_PERMS, runtime.memory_pool);//臨時目錄。

③     全域性的互斥變數和雜湊表初始化,程式碼片段如下:

     switch_mutex_init(&runtime.uuid_mutex,SWITCH_MUTEX_NESTED, runtime.memory_pool);

     switch_mutex_init(&runtime.throttle_mutex,SWITCH_MUTEX_NESTED, runtime.memory_pool);

    switch_mutex_init(&runtime.session_hash_mutex,SWITCH_MUTEX_NESTED, runtime.memory_pool);

     switch_mutex_init(&runtime.global_mutex,SWITCH_MUTEX_NESTED, runtime.memory_pool);

switch_mutex_init(&runtime.global_var_mutex,SWITCH_MUTEX_NESTED, runtime.memory_pool);

以及

    switch_core_hash_init(&runtime.global_vars,runtime.memory_pool);

     switch_core_hash_init(&runtime.mime_types,runtime.memory_pool);

④     系統相關的很重要的初始化

1.       switch_core_set_globals()

由於在主函式中已經設定好了各個安裝目錄,所以此次呼叫將不做任何實際意義的工作。

2.       switch_core_session_init(runtime.memory_pool)

3.       load_mime_types()

4.       gethostname(hostname, sizeof(hostname))獲取主機名

5.       switch_find_local_ip(guess_ip,sizeof(guess_ip), &mask, AF_INET)獲取主機的ip地址。這裡主要是獲取ipv4的地址,下面還要重新呼叫一次該函式獲取ipv6的地址。

6.        switch_console_init(runtime.memory_pool)初始化控制檯。函式的實際程式碼如下:

a)   SWITCH_DECLARE(switch_status_t)switch_console_init(switch_memory_pool_t *pool)

b)   {

c)    switch_mutex_init(&globals.func_mutex,SWITCH_MUTEX_NESTED, pool);

d)    switch_core_hash_init(&globals.func_hash,pool);

e)    switch_console_add_complete_func("::console::list_uuid",(switch_console_complete_callback_t) switch_console_list_uuid);

f)    returnSWITCH_STATUS_SUCCESS;

g)        }

7.       switch_event_init(runtime.memory_pool)初始化freeswitch整個系統的事件機制,這個初始化很重要,在函式內部除了初始化一些互斥量,雜湊佇列,還建立了三個用於事件迴圈的佇列,然後啟動三個執行緒,分別代表了三個佇列的時間迴圈處理執行緒。而所有的資源,都有runtime.memory_pool進行管理,event事件的迴圈處理見後續分析。

8.       switch_xml_init(runtime.memory_pool,err)進行xml配置檔案相關的初始化。

9.       switch_log_init(runtime.memory_pool,runtime.colorize_console)日誌系統的初始化。

10.    switch_load_core_config("switch.conf")讀取全域性的配置檔案,然後根據該配置檔案中的指令,依次讀取後續的子目錄下面的各個配置檔案,詳見後續分析。

11.    switch_core_state_machine_init(runtime.memory_pool)state_machine是整個FS系統的核心部位了,即通話狀態機,根據各個channel的狀態執行相應的狀態處理函式,見後續分析。此處的函式為空函式。

12.    switch_core_sqldb_start()sql資料庫的相關初始化。

13.    switch_rtp_init(runtime.memory_pool)rtp協議的初始化。函式內呼叫srtp_init()初始化rtp協議棧,freeswitch所用的rtp庫是libsrtp。

14.    switch_scheduler_add_task(switch_epoch_time_now(NULL),heartbeat_callback, "heartbeat", "core", 0, NULL, SSHF_NONE |SSHF_NO_DEL)

在freeswitch中有一個task排程機制,這裡講heartbeat加入到task佇列中。事件由

switch_scheduler_task_container_t結構描述,在switch_scheduler.c中,通過全域性的

static struct {

switch_scheduler_task_container_t*task_list;

switch_mutex_t*task_mutex;

uint32_t task_id;

int task_thread_running;

switch_memory_pool_t *memory_pool;

} globals;

Globals變數對task佇列進行管理。Task的排程的執行緒也是在switch_core_init()中啟動的,具體的啟動函式時switch_scheduler_task_thread_start().該函式內部生成的執行緒主函式為switch_scheduler_task_thread():函式裡有主迴圈;

        while (globals.task_thread_running == 1) {

         if(task_thread_loop(0)) {

              break;

         }

         switch_yield(500000);

}通過層層剝離,會進入task_thread_loop中一次執行掛接在switch_scheduler.c中得全域性globals的task佇列。

switch_loadable_module_init()

在switch_core_init_and_modload()中還呼叫了switch_loadable_module_init(),這裡就是根據目錄資訊載入各個動態模組的地方了。函式定義在switch_loadable_module.c檔案中,屬於核心元件的一部分。

函式內根據平臺做了相關處理,在win32平臺下,還需要通過函式switch_loadable_module_path_init()獲取環境變數的相關資訊。另外需要注意的是,該函式內部重新重新生成了一個memory_pool不再是上面描述的runtime的memory_pool了。程式碼如下:

switch_core_new_memory_pool(&loadable_modules.pool);

其中loadable_modules是一個檔案作用域範圍的全域性量,

static structswitch_loadable_module_container loadable_modules;型別為

switch_loadable_module_container,定義如下:

//************* switch_loadable_module_container的定義*****************************//

structswitch_loadable_module_container {

switch_hash_t *module_hash;//存放各個模組結構的雜湊表指標

switch_hash_t *endpoint_hash;// 存放各個endpoint_interface的雜湊表指標

switch_hash_t *codec_hash; // 存放各個codec_interface的雜湊表指標

switch_hash_t *dialplan_hash; // 存放各個diaplan_interface的雜湊表指標

switch_hash_t *timer_hash;// // 存放各個計時器的雜湊表指標

switch_hash_t *application_hash;//存放各個application_interface的雜湊表指標

switch_hash_t *api_hash; // 存放各個api_interface的雜湊表指標

switch_hash_t *file_hash;

switch_hash_t *speech_hash;

switch_hash_t *asr_hash;

switch_hash_t *directory_hash;

switch_hash_t *chat_hash;

switch_hash_t *say_hash;

switch_hash_t *management_hash;

switch_mutex_t *mutex;//全域性互斥量

switch_memory_pool_t *pool;//用於模組相關的apr記憶體池

};

該結構包含了若該的雜湊表指標,分別指向存放各個介面結構的雜湊表。

//***********************************************************************************//

接下來函式初始化了用於存放各個介面的雜湊表,以及全域性互斥量。

該函式是通過switch_loadable_module_load_module_ex((char *) SWITCH_GLOBAL_dirs.mod_dir, (char *) val, SWITCH_FALSE, global, &err)函式載入模組的。可見這裡使用到了模組的目錄資訊SWITCH_GLOBAL_dirs.mod_dir。

switch_loadable_module_load_module_ex()

函式原型為:

static switch_status_t switch_loadable_module_load_module_ex(char *dir, char*fname, switch_bool_t runtime, switch_bool_t global, constchar **err)

這裡講該函式頂定義成了一個static型別,只能在本檔案中被呼叫。dir是上面個傳下來的目錄資訊,fname是讀取配置檔案得到的需要載入的動態物件名(例如mod_conference.dll,mod_sofia.dll或mod_conference.so,mod_sofia.so等)

在該函式中,通過以下兩個函式完成動態物件的載入:

1.  switch_loadable_module_load_file(path,file, global, &new_module),這裡我是用了呼叫時的實參,globals並非上面提出的全域性管理結構,而是一個SWITCH_STATUS的列舉物件。Path是加上了路徑的完整檔名,而file仍然是配置檔案中取得的名稱。New_module是一個秒速模組的結構物件,具體的型別為

a)   struct switch_loadable_module {

b)        char *key;

c)        char*filename;

d)        int perm;

e)        switch_loadable_module_interface_t*module_interface;

f)        switch_dso_lib_t lib;

g)        switch_module_load_t switch_module_load;

h)        switch_module_runtime_tswitch_module_runtime;

i)        switch_module_shutdown_tswitch_module_shutdown;

j)        switch_memory_pool_t *pool;

k)        switch_status_t status;

l)        switch_thread_t *thread;

m)        switch_bool_t shutting_down;

n)        calltime_t *time_record;

o)   };

在switch_loadable_module_load_module_ex函式的開始出定義 

switch_loadable_module_t*new_module = NULL;

在switch_loadable_module_load_file函式中,會為每一個模組生成一個資源池

switch_core_new_memory_pool(&pool);

2.  switch_loadable_module_process(file,new_module)函式主要是將new_module以及module中定義的各個介面結構加入全域性雜湊表,在插入雜湊表的過程中,由loadable_modules.mutex進行臨界保護,舉例如下:

①   switch_core_hash_insert(loadable_modules.module_hash, key,new_module);//將new_module

//插入loadable_modules.module_hash指向的雜湊表。

②   if (new_module->module_interface->endpoint_interface){

constswitch_endpoint_interface_t *ptr;

          for (ptr =new_module->module_interface->endpoint_interface; ptr; ptr =ptr->next) {

switch_core_hash_insert(loadable_modules.endpoint_hash,ptr->interface_name, (const void *) ptr);

}//end if

//若new_module的module_interface中包含endpoint_interface,則將該endpoint_interface插入全域性的endpoint_interface雜湊表。

至此,模組載入也結束了。各個模組載入後各自進入自己的主執行緒中迴圈處理。