从linux源码看socket的close
笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情。上篇博客讲了socket的阻塞和非阻塞,这篇就开始谈一谈socket的close(以tcp为例且基于linux-2.6.24内核版本)
TCP关闭状态转移图:
众所周知,TCP的close过程是四次挥手,状态机的变迁也逃不出TCP状态转移图,如下图所示:
tcp的关闭主要分主动关闭、被动关闭以及同时关闭(特殊情况,不做描述)
主动关闭
close(fd)的过程
以C语言为例,在我们关闭socket的时候,会使用close(fd)函数:
int socket_fd;
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
...
// 此处通过文件描述符关闭对应的socket
close(socket_fd)
而close(int fd)又是通过系统调用sys_close来执行的:
asmlinkage long sys_close(unsigned int fd)
{
// 清除(close_on_exec即退出进程时)的位图标记
FD_CLR(fd, fdt->close_on_exec);
// 释放文件描述符
// 将fdt->open_fds即打开的fd位图中对应的位清除
// 再将fd挂入下一个可使用的fd以便复用
__put_unused_fd(files, fd);
// 调用file_pointer的close方法真正清除
retval = filp_close(filp, files);
}
我们看到最终是调用的filp_close方法:
int filp_close(struct file *filp, fl_owner_t id)
{
// 如果存在flush方法则flush
if (filp->f_op && filp->f_op->flush)
filp->f_op->flush(filp, id);
// 调用fput
fput(filp);
......
}
紧接着我们进入fput:
void fastcall fput(struct file *file)
{
// 对应file->count--,同时检查是否还有关于此file的引用
// 如果没有,则调用_fput进行释放
if (atomic_dec_and_test(&file->f_count))
__fput(file);
}
同一个file(socket)有多个引用的情况很常见,例如下面的例子:
所以在多进程的socket服务器编写过程中,父进程也需要close(fd)一次,以免socket无法最终关闭
然后就是_fput函数了:
void fastcall __fput(struct file *file)
{
// 从eventpoll中释放file
eventpoll_release(file);
// 如果是release方法,则调用release
if (file->f_op && file->f_op->release)
file->f_op->release(inode, file);
}
由于我们讨论的是socket的close,所以,我们现在探查下file->f_op->release在socket情况下的实现:
f_op->release的赋值
我们跟踪创建socket的代码,即
socket(AF_INET, SOCK_STREAM, 0);
|-sock_create // 创建sock
|-sock_map_fd // 将sock和fd关联
|-sock_attach_fd
|-init_file(file,...,&socket_file_ops);
|-file->f_op = fop; //fop赋值为socket_file_ops
socket_file_ops的实现为:
static const struct file_operations socket_file_ops = {
.owner = THIS_MODULE,
......
// 我们在这里只考虑sock_close
.release = sock_close,
......
};
继续跟踪:
<