static void terminate_port(Port *prt)
{
Eterm send_closed_port_id;
Eterm connected_id = NIL /* Initialize to silence compiler */;
erts_driver_t *drv;
ERTS_SMP_CHK_NO_PROC_LOCKS;
ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));
ASSERT(!prt->nlinks);
ASSERT(!prt->monitors);
if (prt->status & ERTS_PORT_SFLG_SEND_CLOSED) {
erts_port_status_band_set(prt, ~ERTS_PORT_SFLG_SEND_CLOSED);
send_closed_port_id = prt->id;
connected_id = prt->connected;
}
else {
send_closed_port_id = NIL;
}
#ifdef ERTS_SMP
erts_cancel_smp_ptimer(prt->ptimer);
#else
erts_cancel_timer(&prt->tm);
#endif
drv = prt->drv_ptr;
if ((drv != NULL) && (drv->stop != NULL)) {
int fpe_was_unmasked = erts_block_fpe();
(*drv->stop)((ErlDrvData)prt->drv_data);
/* 若有则调用port的driver的stop函数 */
erts_unblock_fpe(fpe_was_unmasked);
#ifdef ERTS_SMP
if (prt->xports)
erts_smp_xports_unlock(prt);
ASSERT(!prt->xports);
#endif
}
if(drv->handle != NULL) {
erts_smp_mtx_lock(&erts_driver_list_lock);
erts_ddll_decrement_port_count(drv->handle);
/* 若driver使用了动态链接库或共享库,则减少其引用计数 */
erts_smp_mtx_unlock(&erts_driver_list_lock);
}
stopq(prt); /* clear queue memory */
if(prt->linebuf != NULL){
erts_free(ERTS_ALC_T_LINEBUF, (void *) prt->linebuf);
/* 释放用于保存未集齐的数据的线性缓冲区 */
prt->linebuf = NULL;
}
if (prt->bp != NULL) {
free_message_buffer(prt->bp);
/* 释放堆分片 */
prt->bp = NULL;
prt->data = am_undefined;
}
if (prt->psd)
erts_free(ERTS_ALC_T_PRTSD, prt->psd);
/* 释放port特定数据结构占用的内存 */
kill_port(prt);
/*
* We don't want to send the closed message until after the
* port has been removed from the port table (in kill_port()).
*/
if (is_internal_port(send_closed_port_id))
deliver_result(send_closed_port_id, connected_id, am_closed);
ASSERT(prt->dist_entry == NULL);
}
static ERTS_INLINE void kill_port(Port *pp)
{
ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(pp));
erts_port_task_free_port(pp);
ASSERT(pp->status & ERTS_PORT_SFLGS_DEAD);
}
void erts_port_task_free_port(Port *pp)
{
ErtsRunQueue *runq;
int port_is_dequeued = 0;
ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(pp));
ASSERT(!(pp->status & ERTS_PORT_SFLGS_DEAD));
runq = erts_port_runq(pp);
ASSERT(runq);
ERTS_PT_CHK_PRES_PORTQ(runq, pp);
if (pp->sched.exe_taskq) {
/* I (this thread) am currently executing this port, free it
when scheduled out... */
ErtsPortTask *ptp = port_task_alloc();
erts_smp_port_state_lock(pp);
pp->status &= ~ERTS_PORT_SFLG_CLOSING;
pp->status |= ERTS_PORT_SFLG_FREE_SCHEDULED;
erts_may_save_closed_port(pp);
erts_smp_port_state_unlock(pp);
ERTS_SMP_LC_ASSERT(erts_smp_atomic_read(&pp->refc) > 1);
ptp->type = ERTS_PORT_TASK_FREE;
ptp->event = (ErlDrvEvent) -1;
ptp->event_data = NULL;
set_handle(ptp, NULL);
push_task(pp->sched.exe_taskq, ptp);
ERTS_PT_CHK_PRES_PORTQ(runq, pp);
erts_smp_runq_unlock(runq);
}
else {
/* 仅仅分析这个简单的场景以说明问题,另外一个场景类似 */
ErtsPortTaskQueue *ptqp = pp->sched.taskq;
if (ptqp) {
dequeue_port(runq, pp);
ERTS_PORT_NOT_IN_RUNQ(pp);
port_is_dequeued = 1;
}
erts_smp_port_state_lock(pp);
pp->status &= ~ERTS_PORT_SFLG_CLOSING;
pp->status |= ERTS_PORT_SFLG_FREE_SCHEDULED;
/* port的状态被更改为了ERTS_PORT_SFLG_FREE_SCHEDULED,它也是ERTS_PORT_SFLGS_DEAD的一种 */
erts_may_save_closed_port(pp);
/* 能够让erts_dead_ports_ptr保存已经退出的port,则port在退出时一定走到了这里,我们其实仅需要关注在这里之后是否有port资源泄露即可 */
erts_smp_port_state_unlock(pp);
#ifdef ERTS_SMP
erts_smp_atomic_dec(&pp->refc); /* Not alive */
#endif
ERTS_SMP_LC_ASSERT(erts_smp_atomic_read(&pp->refc) > 0); /* Lock */
handle_remaining_tasks(runq, pp); /* May release runq lock */
/*这个函数将释放挂在port上的所有ErtsPortTask,port能够执行的各项任务,也被像消息一样发给port,由port异步执行,这里将释放port的任务队列上的所有ErtsPortTask任务的数据结构*/
ASSERT(!pp->sched.exe_taskq && (!ptqp || !ptqp->first));
pp->sched.taskq = NULL;
ERTS_PT_CHK_PRES_PORTQ(runq, pp);
#ifndef ERTS_SMP
ASSERT(pp->status & ERTS_PORT_SFLG_PORT_DEBUG);
erts_port_status_set(pp, ERTS_PORT_SFLG_FREE);
/* port的状态又被改为了ERTS_PORT_SFLG_FREE,它也是ERTS_PORT_SFLGS_DEAD的一种,但设置为这个状态后,表名port原先的描述符Port数据结构可以被重新分配给一个新建立的port了,因为之前已经触发了erts_may_save_closed_port,因此按照顺序执行流的执行,除非发生异常,否则必然会到此处 */
#endif
erts_smp_runq_unlock(runq);
if (erts_system_profile_flags.runnable_ports && port_is_dequeued) {
profile_runnable_port(pp, am_inactive);
}
if (ptqp)
port_taskq_free(ptqp);
/*释放port的任务队列*/
}
}
由此可见port的释放其实没有那么复杂,虚拟机本身就有port数量限制,每次的port释放都仅仅将port的描述符设置为ERTS_PORT_SFLG_FREE以进行复用,而不会真正释放数据结构。
再来看看用于获取空闲port描述符的get_free_port:
io.c
static int get_free_port(void)
{
Uint num;
Uint tries = erts_max_ports;
Port* port;
erts_smp_spin_lock(&get_free_port_lck);
num = last_port_num + 1;
for (;; ++num) {
port = &erts_port[num & erts_port_tab_index_mask];
erts_smp_port_state_lock(port);
if (port->status & ERTS_PORT_SFLG_FREE) {
last_port_num = num;
erts_smp_spin_unlock(&get_free_port_lck);
break;
}
erts_smp_port_state_unlock(port);
if (--tries == 0) {
erts_smp_spin_unlock(&get_free_port_lck);
return -1;
}
}
port->status = ERTS_PORT_SFLG_INITIALIZING;
#ifdef ERTS_SMP
ERTS_SMP_LC_ASSERT(erts_smp_atomic_read(&port->refc) == 0);
erts_smp_atomic_set(&port->refc, 2); /* Port alive + lock */
#endif
erts_smp_port_state_unlock(port);
return num & port_num_mask;
}
get_free_port用于取得一个空闲port描述符,它将遍历erts_port记录的所有port描述符,然后从中取得一个状态为ERTS_PORT_SFLG_FREE的描述符。
由此可见port的分配与释放都不会引发port描述符的内存分配与释放,仅仅会复用一个而已。
至此,问题原因已经基本清楚了,erlang:ports()和erlang:processes()将返回在某个时刻的端口和进程的快照,这样的结果更加一致,因为时刻的快照比时间间隔的快照更加精准。