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哈希表。
至此,模塊加載也結束了。各個模塊加載后各自進入自己的主線程中循環處理。