Asterisk是一个开源的pbx系统,在公开的资料中,很难找到asterisk内核系统的详细描述。因此,很有必要写一篇内核框架的描述文档,作为内部培训文档,相互学习提高。
本文主要从三个层面来描述asterisk内核,即asterisk内核模块、内核启动过程、基本呼叫流程。
一、 asterisk内核模块
Asterisk由内部核心和外围动态可加载模块组成。内部核心由以下六个部分组成:PBX交换核心模块(PBX Switching Core)、调度和I/O管理模块(Scheduler and I/O Manager)、应用调用模块(Application Launcher)、编解码转换模块(Codec Translator)、动态模块加载器模块(Dynamic Module Loader)和CDR生成模块(CDR Core)。
外围动态可加载模块包括以App_开始的Applications、以Func_开始的Functions、以Res_开始的Resources、以Chan_开始的channels、以Codec_开始的codec编解码模块等。
1. 内核模块
1) PBX交换核心模块(PBX Switching Core):
l pbx.c
pbx.c是asterisk的核心模块,每路呼叫都需要经过它调度。pbx实现了builtin applications,也就是内置的应用,比如最常见的Answer,Hangup, Background,Wait等等。
struct ast_app是一个关键数据结构,它定义了注册builtin applications的结构。
load_pbx函数用来注册builtin applications和一些命令行CLI命令(每个模块都有些CLI命令)。该函数在系统启动时被调用。
pbx_exec是Answer/BackGround/Busy/Goto/GotoIf/Hangup/Set等builtin applications的执行入口函数,它被pbx_extension_helper调用。
ast_pbx_start函数是每路呼叫的起点。
2) 调度和I/O管理模块(Scheduler and I/O Manager):
l Channel.c:
Channel.c/channel.h定义了channel操作的结构体和接口函数。
struct ast_channel_tech结构体是所有channel都要用到的关键结构体,它定义channel操作的一系列回调函数指针,如call、hangup、answer等。每个channel模块都会定义ast_channel_tech的实体,并将各自的回调函数赋值给它。例如chan_sip.c中定义如下:
/*! /brief Definition of this channel for PBX channel registration */
static const struct ast_channel_tech sip_tech = {
.type = "SIP",
.description = "Session Initiation Protocol (SIP)",
.capabilities = ((AST_FORMAT_MAX_AUDIO << 1) - 1),
.properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER,
.requester = sip_request_call,
.devicestate = sip_devicestate,
.call = sip_call,
.hangup = sip_hangup,
.answer = sip_answer,
.read = sip_read,
.write = sip_write,
.write_video = sip_write,
.indicate = sip_indicate,
.transfer = sip_transfer,
.fixup = sip_fixup,
.send_digit_begin = sip_senddigit_begin,
.send_digit_end = sip_senddigit_end,
.bridge = ast_rtp_bridge,
.send_text = sip_sendtext,
.func_channel_read = acf_channel_read,
};
ast_call、ast_hangup、ast_answer等函数分别实现ast_channel_tech中的call、hangup、answer等回调函数的调用。
struct ast_channel结构体定义了channel的上下文参数,它是每个参与呼叫的channel必不可少的,都会调用ast_channel_alloc来申请ast_channel。
l io.c
io.c实现了asterisk跟外部交互时的I/O管理,如chan_sip为了从外部接收SIP信令,调用ast_io_add添加IO接口,并调用ast_io_wait实现外部消息接收。
3) 应用调用模块(Application Launcher):
在pbx.c中定义了一系列的应用调用接口。
applications模块定义了application回调函数并注册后,在pbx.c中通过应用调用接口回调执行。
应用调用接口的关键函数是pbx_extension_helper,它执行dialplan,在cli上打印“Executing ……”,并抛出ami event事件,同时调用pbx_exec执行application回调函数。
4) 编解码转换模块(Codec Translator):
Translate.c:
struct ast_translator:编码转换描述结构体,它定义了编码转换的名称、回调函数、运行时选项。
struct ast_trans_pvt:编码转换上下文描述结构体。
ast_register_translator:编码转换注册接口函数,供各编码模块调用,注册struct ast_translator类型的结构体变量。
ast_unregister_translator:编码转换注销函数
ast_translate:编码转换的执行函数。
codec_gsm.c/codec_...:对应各种编码的编解码执行模块,如g.711alaw/g.711ulaw/gsm等。
5) 动态模块加载器模块(Dynamic Module Loader):
该模块主要是Module.h。
Module.h中定义了struct ast_module_info结构,用来保存各模块的注册、注销回调函数,以及模块描述信息。
load_module、unload_module,每个应用模块的注册、注销函数,由各个模块自行定义为static函数。
AST_MODULE_INFO_STANDARD:注册接口、注销接口、模块描述信息等模块信息的登记接口。它是一个宏定义,动态模块调用它时,首先定义类型为ast_module_info的__mod_info静态结构变量,保存模块信息,并定义__attribute__ ((constructor)) __reg_module和__attribute__ ((destructor)) __unreg_module,在程序启动和退出时调用。
6) CDR生成模块(CDR Core):
Cdr.c:
ast_cdr_register:cdr driver注册,供cdr_mysql等调用,注册话单保存的回调函数。
ast_cdr_engine_init:CDR模块初始化,注册cdr status、加载cdr.conf、启动CDR线程。
ast_cdr_detach:产生话单的接口函数,呼叫结束时被调用。
2. 外围可加载模块:
1) Applications
以app_开始的模块,如app_dial.c、app_db.c、app_queue.c、app_record.c、app_meetme.c等,代码保存在apps目录中。每个application模块都定义了load_module函数和unload_module函数,分别用来注册和注销application。
load_module函数调用ast_register_application函数,注册application命令,例如app_dial模块注册Dial:res = ast_register_application(app, dial_exec, synopsis, descrip)。
unload_module函数调用ast_unregister_application函数,注销application命令。
每个application模块都会使用AST_MODULE_INFO_STANDARD宏来登记模块信息__mod_info。AST_MODULE_INFO_STANDARD将load_module和unload_module注册为回调函数,供module load/unload/reload调用。
2) Channel
以chan_开始的模块,如chan_sip.c、chan_h323.c、chan_mgcp.c 、chan_iax2.c、 chan_zap.c等,对应代码保存在channels目录中。
channel注册、注销过程和application基本类似。由于每个channel需要和外部交互,都会在load_module中启用do_monitor线程来侦听外部tcp/udp端口,接收外部消息。
每个channel也定义了各自的cli命令和Function命令,例如chan_sip定义了sip debug/history/no/notify/prune/ reload/set/show等cli命令和SIP_HEADER、CHECKSIPDOMAIN、SIPPEER、SIPCHANINFO等Function命令。
3) Functions
以Fun_开始的模块,例如Fun_db.c、func_moh.c、func_cdr.c等,对应代码保存在funcs目录中。
Function注册、注销过程也和application类似。
每个Function模块也定义了各自的Function命令,例如Fun_db.c就定义了DB、DB_EXISTS、DB_DELETE等Function命令。
二、 asterisk启动过程
主要就main函数讲解asterisk的启动过程:
int main(int argc, char *argv[])
{
int c;
char filename[80] = "";
char hostname[MAXHOSTNAMELEN] = "";
char tmp[80];
char * xarg = NULL;
int x;
FILE *f;
sigset_t sigs;
int num;
int isroot = 1;
char *buf;
char *runuser = NULL, *rungroup = NULL;
/*保存命令行参数(argv[]->_argv[]),以便程序重启时使用*/
/* Remember original args for restart */
if (argc > sizeof(_argv) / sizeof(_argv[0]) - 1) {
fprintf(stderr, "Truncating argument size to %d/n", (int)(sizeof(_argv) / sizeof(_argv[0])) - 1);
argc = sizeof(_argv) / sizeof(_argv[0]) - 1;
}
for (x=0; x<argc; x++)
_argv[x] = argv[x];
_argv[x] = NULL;
if (geteuid() != 0)
isroot = 0;
/*命令如果是rasterisk,设置AST_OPT_FLAG_NO_FORK和AST_OPT_FLAG_REMOTE标志位*/
/* if the progname is rasterisk consider it a remote console */
if (argv[0] && (strstr(argv[0], "rasterisk")) != NULL) {
ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK | AST_OPT_FLAG_REMOTE);
}
/*得到当前主机名,在启动时打印出来*/
if (gethostname(hostname, sizeof(hostname)-1))
ast_copy_string(hostname, "<Unknown>", sizeof(hostname));
/*获取当前的进程标识*/
ast_mainpid = getpid();
/*建立mu-law和a-law转换表*/
ast_ulaw_init();
ast_alaw_init();
/*为FFT逆变换(傅立叶逆变换)做一些初始化,用于在zaptel里进行callerid的DTMF检测*/
callerid_init();
/*初始化内置命令的_full_cmd字符串,并注册常用命令,ast_builtins_init() -> ast_cli_register_multiple() -> ast_cli_register() -> __ast_cli_register() */
ast_builtins_init();
/*初始化base64转换*/
ast_utils_init();
/* tty/tdd初始化*/
tdd_init();
/*设置用户历史命令的保存路径*/
if (getenv("HOME"))
snprintf(filename, sizeof(filename), "%s/.asterisk_history", getenv("HOME"));
/* Check for options */
/*检查命令行的输入参数,匹配参数范围是“mtThfFdvVqprRgciInx:U:G:C:L:M:”,不同的参数输入走到不同的case分支处理。有几个v,verbose级别就增加几*/
while ((c = getopt(argc, argv, "mtThfFdvVqprRgciInx:U:G:C:L:M:")) != -1) {
switch (c) {
#if HAVE_WORKING_FORK
case 'F':
ast_set_flag(&ast_options, AST_OPT_FLAG_ALWAYS_FORK);
break;
case 'f':
ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK);
break;
#endif
case 'd':
option_debug++;
ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK);
break;
case 'c':
ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK | AST_OPT_FLAG_CONSOLE);
break;
case 'n':
ast_set_flag(&ast_options, AST_OPT_FLAG_NO_COLOR);
break;
case 'r':
ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK | AST_OPT_FLAG_REMOTE);
break;
case 'R':
ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK | AST_OPT_FLAG_REMOTE | AST_OPT_FLAG_RECONNECT);
break;
case 'p':
ast_set_flag(&ast_options, AST_OPT_FLAG_HIGH_PRIORITY);
break;
case 'v':
option_verbose++;
ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK);
break;
case 'm':
ast_set_flag(&ast_options, AST_OPT_FLAG_MUTE);
break;
case 'M':
if ((sscanf(optarg, "%d", &option_maxcalls) != 1) || (option_maxcalls < 0))
option_maxcalls = 0;
break;
case 'L':
if ((sscanf(optarg, "%lf", &option_maxload) != 1) || (option_maxload < 0.0))
option_maxload = 0.0;
break;
case 'q':
ast_set_flag(&ast_options, AST_OPT_FLAG_QUIET);
break;
case 't':
ast_set_flag(&ast_options, AST_OPT_FLAG_CACHE_RECORD_FILES);
break;
case 'T':
ast_set_flag(&ast_options, AST_OPT_FLAG_TIMESTAMP);
break;
case 'x':
ast_set_flag(&ast_options, AST_OPT_FLAG_EXEC);
xarg = ast_strdupa(optarg);
break;
case 'C':
ast_copy_string(ast_config_AST_CONFIG_FILE, optarg, sizeof(ast_config_AST_CONFIG_FILE));
ast_set_flag(&ast_options, AST_OPT_FLAG_OVERRIDE_CONFIG);
break;
case 'I':
ast_set_flag(&ast_options, AST_OPT_FLAG_INTERNAL_TIMING);
break;
case 'i':
ast_set_flag(&ast_options, AST_OPT_FLAG_INIT_KEYS);
break;
case 'g':
ast_set_flag(&ast_options, AST_OPT_FLAG_DUMP_CORE);
break;
case 'h':
show_cli_help();
exit(0);
case 'V':
show_version();
exit(0);
case 'U':
runuser = ast_strdupa(optarg);
break;
case 'G':
rungroup = ast_strdupa(optarg);
break;
case '?':
exit(1);
}
}
/*如果用了-c或者-v或者-r并且没有-x cmd参数,则打印欢迎信息*/
if (ast_opt_console || option_verbose || (ast_opt_remote && !ast_opt_exec)) {
ast_register_verbose(console_verboser);
WELCOME_MESSAGE;
}
/*如果没有开调试则简单打印Booting... */
if (ast_opt_console && !option_verbose)
ast_verbose("[ Booting.../n");
/*显示控制台时,不论是本地还是远程,都不能使用-F参数,否则无效*/
if (ast_opt_always_fork && (ast_opt_remote || ast_opt_console)) {
ast_log(LOG_WARNING, "'alwaysfork' is not compatible with console or remote console mode; ignored/n");
ast_clear_flag(&ast_options, AST_OPT_FLAG_ALWAYS_FORK);
}
/* For remote connections, change the name of the remote connection.
* We do this for the benefit of init scripts (which need to know if/when
* the main asterisk process has died yet). */
if (ast_opt_remote) {
strcpy(argv[0], "rasterisk");
for (x = 1; x < argc; x++) {
argv[x] = argv[0] + 10;
}
}
/*读取主配置文件,主配置文件是由make menuselect配置的*/
if (ast_opt_console && !option_verbose)
ast_verbose("[ Reading Master Configuration ]/n");
ast_readconfig();
/*如果启动加了-g,取消core dump文件的大小限制*/
if (ast_opt_dump_core) {
struct rlimit l;
memset(&l, 0, sizeof(l));
l.rlim_cur = RLIM_INFINITY;
l.rlim_max = RLIM_INFINITY;
if (setrlimit(RLIMIT_CORE, &l)) {
ast_log(LOG_WARNING, "Unable to disable core size resource limit: %s/n", strerror(errno));
}
}
/*修改用户和组权限*/
if ((!rungroup) && !ast_strlen_zero(ast_config_AST_RUN_GROUP))
rungroup = ast_config_AST_RUN_GROUP;
if ((!runuser) && !ast_strlen_zero(ast_config_AST_RUN_USER))
runuser = ast_config_AST_RUN_USER;
#ifndef __CYGWIN__
if (isroot)
ast_set_priority(ast_opt_high_priority);
if (isroot && rungroup) {
struct group *gr;
gr = getgrnam(rungroup);
if (!gr) {
ast_log(LOG_WARNING, "No such group '%s'!/n", rungroup);
exit(1);
}
if (setgid(gr->gr_gid)) {
ast_log(LOG_WARNING, "Unable to setgid to %d (%s)/n", (int)gr->gr_gid, rungroup);
exit(1);
}
if (setgroups(0, NULL)) {
ast_log(LOG_WARNING, "Unable to drop unneeded groups/n");
exit(1);
}
if (option_verbose)
ast_verbose("Running as group '%s'/n", rungroup);
}
if (runuser && !ast_test_flag(&ast_options, AST_OPT_FLAG_REMOTE)) {
#ifdef HAVE_CAP
int has_cap = 1;
#endif /* HAVE_CAP */
struct passwd *pw;
pw = getpwnam(runuser);
if (!pw) {
ast_log(LOG_WARNING, "No such user '%s'!/n", runuser);
exit(1);
}
#ifdef HAVE_CAP
if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0)) {
ast_log(LOG_WARNING, "Unable to keep capabilities./n");
has_cap = 0;
}
#endif /* HAVE_CAP */
if (!isroot && pw->pw_uid != geteuid()) {
ast_log(LOG_ERROR, "Asterisk started as nonroot, but runuser '%s' requested./n", runuser);
exit(1);
}
if (!rungroup) {
if (setgid(pw->pw_gid)) {
ast_log(LOG_WARNING, "Unable to setgid to %d!/n", (int)pw->pw_gid);
exit(1);
}
if (isroot && initgroups(pw->pw_name, pw->pw_gid)) {
ast_log(LOG_WARNING, "Unable to init groups for '%s'/n", runuser);
exit(1);
}
}
if (setuid(pw->pw_uid)) {
ast_log(LOG_WARNING, "Unable to setuid to %d (%s)/n", (int)pw->pw_uid, runuser);
exit(1);
}
if (option_verbose)
ast_verbose("Running as user '%s'/n", runuser);
#ifdef HAVE_CAP
if (has_cap) {
cap_t cap;
cap = cap_from_text("cap_net_admin=ep");
if (cap_set_proc(cap))
ast_log(LOG_WARNING, "Unable to install capabilities./n");
if (cap_free(cap))
ast_log(LOG_WARNING, "Unable to drop capabilities./n");
}
#endif /* HAVE_CAP */
}
#endif /* __CYGWIN__ */
#ifdef linux
if (geteuid() && ast_opt_dump_core) {
if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0) {
ast_log(LOG_WARNING, "Unable to set the process for core dumps after changing to a non-root user. %s/n", strerror(errno));
}
}
#endif
/*初始化模拟终端ast_term_init(),默认是VT100*/
ast_term_init();
printf(term_end());
fflush(stdout);
if (ast_opt_console && !option_verbose)
ast_verbose("[ Initializing Custom Configuration Options ]/n");
/* custom config setup */
/*注册命令core show config mappings*/
register_config_cli();
/*配置文件的映射和绑定*/
read_config_maps();
if (ast_opt_console) {
if (el_hist == NULL || el == NULL)
ast_el_initialize();
if (!ast_strlen_zero(filename))
ast_el_read_history(filename);
}
/*设置和检查本地或远程终端的连接*/
if (ast_tryconnect()) {
/* One is already running */
if (ast_opt_remote) {
if (ast_opt_exec) {
ast_remotecontrol(xarg);
quit_handler(0, 0, 0, 0);
exit(0);
}
printf(term_quit());
ast_remotecontrol(NULL);
quit_handler(0, 0, 0, 0);
exit(0);
} else {
ast_log(LOG_ERROR, "Asterisk already running on %s. Use 'asterisk -r' to connect./n", ast_config_AST_SOCKET);
printf(term_quit());
exit(1);
}
} else if (ast_opt_remote || ast_opt_exec) {
ast_log(LOG_ERROR, "Unable to connect to remote asterisk (does %s exist?)/n", ast_config_AST_SOCKET);
printf(term_quit());
exit(1);
}
/* Blindly write pid file since we couldn't connect */
unlink(ast_config_AST_PID);
f = fopen(ast_config_AST_PID, "w");
if (f) {
fprintf(f, "%ld/n", (long)getpid());
fclose(f);
} else
ast_log(LOG_WARNING, "Unable to open pid file '%s': %s/n", ast_config_AST_PID, strerror(errno));
#if HAVE_WORKING_FORK
if (ast_opt_always_fork || !ast_opt_no_fork) {
#ifndef HAVE_SBIN_LAUNCHD
daemon(1, 0);
ast_mainpid = getpid();
/* Blindly re-write pid file since we are forking */
unlink(ast_config_AST_PID);
f = fopen(ast_config_AST_PID, "w");
if (f) {
fprintf(f, "%ld/n", (long)ast_mainpid);
fclose(f);
} else
ast_log(LOG_WARNING, "Unable to open pid file '%s': %s/n", ast_config_AST_PID, strerror(errno));
#else
ast_log(LOG_WARNING, "Mac OS X detected. Use '/sbin/launchd -d' to launch with the nofork option./n");
#endif
}
#endif
/* Test recursive mutex locking. */
/*测试线程安全,避免出现死锁*/
if (test_for_thread_safety())
ast_verbose("Warning! Asterisk is not thread safe./n");
/*创建用于和控制台交互的服务器端socket接口*/
ast_makesocket();
/*加入信号集,设置掩码,以及注册信号的相应handler */
sigemptyset(&sigs);
sigaddset(&sigs, SIGHUP);
sigaddset(&sigs, SIGTERM);
sigaddset(&sigs, SIGINT);
sigaddset(&sigs, SIGPIPE);
sigaddset(&sigs, SIGWINCH);
pthread_sigmask(SIG_BLOCK, &sigs, NULL);
signal(SIGURG, urg_handler);
signal(SIGINT, __quit_handler);
signal(SIGTERM, __quit_handler);
signal(SIGHUP, hup_handler);
signal(SIGCHLD, child_handler);
signal(SIGPIPE, SIG_IGN);
/* ensure that the random number generators are seeded with a different value every time
Asterisk is started
*/
/*设置种子并初始化随机数发生器*/
srand((unsigned int) getpid() + (unsigned int) time(NULL));
initstate((unsigned int) getpid() * 65536 + (unsigned int) time(NULL), randompool, sizeof(randompool));
/*初始化日志模块*/
if (init_logger()) {
printf(term_quit());
exit(1);
}
#ifdef HAVE_ZAPTEL
{
int fd;
int x = 160;
fd = open("/dev/zap/timer", O_RDWR);
if (fd >= 0) {
if (ioctl(fd, ZT_TIMERCONFIG, &x)) {
ast_log(LOG_ERROR, "You have Zaptel built and drivers loaded, but the Zaptel timer test failed to set ZT_TIMERCONFIG to %d./n", x);
exit(1);
}
if ((x = ast_wait_for_input(fd, 300)) < 0) {
ast_log(LOG_ERROR, "You have Zaptel built and drivers loaded, but the Zaptel timer could not be polled during the Zaptel timer test./n");
exit(1);
}
if (!x) {
const char zaptel_timer_error[] = {
"Asterisk has detected a problem with your Zaptel configuration and will shutdown for your protection. You have options:"
"/n/t1. You only have to compile Zaptel support into Asterisk if you need it. One option is to recompile without Zaptel support."
"/n/t2. You only have to load Zaptel drivers if you want to take advantage of Zaptel services. One option is to unload zaptel modules if you don't need them."
"/n/t3. If you need Zaptel services, you must correctly configure Zaptel."
};
ast_log(LOG_ERROR, "%s/n", zaptel_timer_error);
exit(1);
}
close(fd);
}
}
#endif
/*注册threadstorage show allocations和threadstorage show summary这两个命令*/
threadstorage_init();
astobj2_init();
ast_autoservice_init();
/*加载配置文件/etc/asterisk/modules.conf中标记为preload的模块,再去掉标记为noload的模块*/
if (load_modules(1)) {
printf(term_quit());
exit(1);
}
/* DNS manager的初始化*/
if (dnsmgr_init()) {
printf(term_quit());
exit(1);
}
/*配置http服务器*/
ast_http_init();
/*注册两个命令core show channeltypes和core show channeltype */
ast_channels_init();
/*注册管理命令*/
if (init_manager()) {
printf(term_quit());
exit(1);
}
/*用来创建一个调度上下文以及注册相应的命令,然后用do_reload来读取配置文件cdr.conf和创建后台线程do_cdr */
if (ast_cdr_engine_init()) {
printf(term_quit());
exit(1);
}
/*用来创建一个后台线程轮巡设备的状态,如果发生变化则及时通告*/
if (ast_device_state_engine_init()) {
printf(term_quit());
exit(1);
}
/*注册rtp,rtcp,stun相关的CLI命令,然后调用ast_rtp_reload()读取配置文件rtp.conf,设置相关参数*/
ast_rtp_init();
/*注册udptl相关的CLI命令,然后调用ast_udptl_reload()读取配置文件udptl.conf,设置相关参数*/
ast_udptl_init();
/*注册core show image formats */
if (ast_image_init()) {
printf(term_quit());
exit(1);
}
/*注册core show file formats */
if (ast_file_init()) {
printf(term_quit());
exit(1);
}
/*注册dialplan相关的CLI命令,然后调用ast_register_application来注册所有的app */
if (load_pbx()) {
printf(term_quit());
exit(1);
}
/*注册与codec相关的CLI命令*/
if (init_framer()) {
printf(term_quit());
exit(1);
}
/*注册与database相关的CLI命令,然后再注册两个管理命令DBGet和DBPut */
if (astdb_init()) {
printf(term_quit());
exit(1);
}
/*读取配置文件enum.conf,初始化支持ENUM(e164)的子系统*/
if (ast_enum_init()) {
printf(term_quit());
exit(1);
}
/* load_modules(0)加载所有其它需要加载的动态链接库*/
if (load_modules(0)) {
printf(term_quit());
exit(1);
}
dnsmgr_start_refresh();
/* We might have the option of showing a console, but for now just
do nothing... */
if (ast_opt_console && !option_verbose)
ast_verbose(" ]/n");
if (option_verbose || ast_opt_console)
ast_verbose(term_color(tmp, "Asterisk Ready./n", COLOR_BRWHITE, COLOR_BLACK, sizeof(tmp)));
if (ast_opt_no_fork)
consolethread = pthread_self();
/*创建管道*/
if (pipe(sig_alert_pipe))
sig_alert_pipe[0] = sig_alert_pipe[1] = -1;
ast_set_flag(&ast_options, AST_OPT_FLAG_FULLY_BOOTED);
pthread_sigmask(SIG_UNBLOCK, &sigs, NULL);
#ifdef __AST_DEBUG_MALLOC
__ast_mm_init();
#endif
time(&ast_startuptime);
/*注册asterisk相关的命令,比如stop,restart,halt等等*/
ast_cli_register_multiple(cli_asterisk, sizeof(cli_asterisk) / sizeof(struct ast_cli_entry));
if (ast_opt_console) {
/* Console stuff now... */
/* Register our quit function */
char title[256];
pthread_attr_t attr;
pthread_t dont_care;
/*创建线程,轮询上面创建的sig_alert_pipe管道*/
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
ast_pthread_create(&dont_care, &attr, monitor_sig_flags, NULL);
pthread_attr_destroy(&attr);
set_icon("Asterisk");
snprintf(title, sizeof(title), "Asterisk Console on '%s' (pid %ld)", hostname, (long)ast_mainpid);
set_title(title);
/*接收和处理控制台命令*/
for (;;) {
buf = (char *)el_gets(el, &num);
if (!buf && write(1, "", 1) < 0)
goto lostterm;
if (buf) {
if (buf[strlen(buf)-1] == '/n')
buf[strlen(buf)-1] = '/0';
consolehandler((char *)buf);
} else if (ast_opt_remote && (write(STDOUT_FILENO, "/nUse EXIT or QUIT to exit the asterisk console/n",
strlen("/nUse EXIT or QUIT to exit the asterisk console/n")) < 0)) {
/* Whoa, stdout disappeared from under us... Make /dev/null's */
int fd;
fd = open("/dev/null", O_RDWR);
if (fd > -1) {
dup2(fd, STDOUT_FILENO);
dup2(fd, STDIN_FILENO);
} else
ast_log(LOG_WARNING, "Failed to open /dev/null to recover from dead console. Bad things will happen!/n");
break;
}
}
}
monitor_sig_flags(NULL);
lostterm:
return 0;
}
三、 asterisk基本呼叫流程
从内核的角度去分析问题时,弄清楚呼叫流程是非常关键的,只有理清了呼叫流程,才能从流程的各个环节细节中分析出问题所在。
Asterisk所有功能都是基于函数调用的模式,呼叫流程也不例外。因此如何从一团乱麻似的内核函数调用中理出函数调用执行路线,是解读呼叫流程的关键。
所有呼叫都跟astersisk的channel有关。这路通话都包含一个incoming连接和一个outbound连接。每个电话都是通过对应的channel程序建立起来的,比如Chan_sip,Chan_zap,Chan_iax2等等。每一类的channel,都拥有自己私有的channel数据结构,例如chan_sip的struct sip_pvt结构,这些私有的结构从属于一个通用的Asterisk通道数据结构中,具体定义在channel.h的struct ast_channe中。
下图是asterisk 的呼叫流程图:
我们以sip的呼叫过程为例来描述,其他channel的呼叫过程基本类似。
Astersik下注册的sip用户主动发起一个呼叫的函数调用过程(incoming)如下:
do_monitor->sipsock_read->handle_request->handle_request_invite->sip_new/ast_pbx_start->pbx_thread->__ast_pbx_run
-> ast_spawn_extension ->pbx_extension_helper->pbx_exec->执行dialplan
当Chan_sip模块被加载时,会启动一个独立的监听线程do_monitor,不断侦听sip端口上的外部消息;
当sip用户拨叫被叫号码后,chan_sip的do_monitor调用sipsock_read函数,在sip端口收到invite消息,然后就调用handle_request和handle_request_invite进行处理。
在handle_request_invite中,首先解析invite消息,对该sip用户的业务属性分析,确认被叫可达,然后就调用sip_new申请channel资源,并调用ast_pbx_start函数启动一个pbx_thread线程来专门处理该呼叫。
pbx_thread线程调用__ast_pbx_run。
__ast_pbx_run是一个衔接dialplan和内核的关键函数,它首先调用ast_exists_extension函数,根据分机号码的context属性,匹配到对应的dialplan;然后进入一个for死循环,逐条执行dialplan对应的context中的语句。
pbx_extension_helper函数调用pbx_extension_helper,在pbx_extension_helper中调用pbx_find_extension找到对应的context后,通过verbose打印dialplan执行语句“Executing ……”,同时调用pbx_exec执行该dialplan。执行到dial语句呼叫被叫。
在等待被叫接通的过程中,完成媒体协商过程,向主叫发送180、200OK消息接通呼叫。
当其他用户呼叫asterisk的sip用户时,函数调用过程(outbound)如下: Dial->dial_exec->dial_exec_full->ast_request/ast_call/wait_for_answer/ ast_bridge_call
呼叫执行到dial时,pbx_exec调用application dial的接口函数dial_exec,dial_exec调用dial_exec_full。
在dial_exec_full中,首先调用ast_request,在ast_request调用chan_sip对应的回调函数sip_request_call为该被叫sip用户申请channel资源。然后调用ast_call,在ast_call中调用chan_sip对应的回调函数sip_call向被叫发送INVITE消息,呼叫被叫SIP用户。
然后该呼叫线程会调用wait_for_answer等待被叫接通。
在呼叫接通后,也即wait_for_answer函数返回,在dial_exec_full中调用ast_bridge_call桥接媒体,这样呼叫就正式接通了。
当chan_sip的侦听线程接收到BYE消息,则调用handle_request_bye找到相应的channel,执行hangup释放呼叫。