接上篇
初始化与创建,本篇阐述Socket操作和
销毁两部分的实现。
Socket操作
系统调用read(v)、write(v)是用户空间读写socket的一种方法,为了弄清楚它们是怎么通过VFS将请求转发到特定协议的实现,下面以read为例(write同理),并假定文件描述符对应的是IPv4 TCP类型的socket,来跟踪它的执行流程。首先来看下sys_read的代码,定义在fs/read_write.c中。
1
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
2![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
3
struct file *file;
4
ssize_t ret = -EBADF;
5
int fput_needed;
6![](/Images/OutliningIndicators/InBlock.gif)
7
file = fget_light(fd, &fput_needed);
8![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
if (file)
{
9
loff_t pos = file_pos_read(file);
10
ret = vfs_read(file, buf, count, &pos);
11
![](http://www.cppblog.com/Images/dot.gif)
12
}
13![](/Images/OutliningIndicators/InBlock.gif)
14
return ret;
15
}
先调用fget_light得到fd对应的file,再调用vfs_read。接着跟踪vfs_read的代码,定义在fs/read_write.c中。
1
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
2![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif)
{
3
ssize_t ret;
4
![](http://www.cppblog.com/Images/dot.gif)
5
ret = rw_verify_area(READ, file, pos, count);
6![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
if (ret >= 0)
{
7
count = ret;
8
if (file->f_op->read)
9
ret = file->f_op->read(file, buf, count, pos);
10
else
11
ret = do_sync_read(file, buf, count, pos);
12
![](http://www.cppblog.com/Images/dot.gif)
13
}
14![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
15
return ret;
16
}
在
上篇Socket创建一节已知,因为sockfs_file_ops没有定义read(即read指针为空),所以这儿实际调用了do_sync_read,继续跟踪它的代码,定义在fs/read_write.c中。
1
ssize_t do_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
2![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif)
{
3![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
struct iovec iov =
{ .iov_base = buf, .iov_len = len };
4
struct kiocb kiocb;
5
ssize_t ret;
6![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
7
![](http://www.cppblog.com/Images/dot.gif)
8![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
for (;;)
{
9
ret = filp->f_op->aio_read(&kiocb, &iov, 1, kiocb.ki_pos);
10
if (ret != -EIOCBRETRY)
11
break;
12
wait_on_retry_sync_kiocb(&kiocb);
13
}
14![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
15
if (-EIOCBQUEUED == ret)
16
ret = wait_on_sync_kiocb(&kiocb);
17
*ppos = kiocb.ki_pos;
18
return ret;
19
}
显而易见,这儿调用到了f_op->aio_read,使用异步读来实现同步读,若异步读没有完成,则调用wait_on_sync_kiocb等待。由
上篇Socket创建一节可知sockfs_file_ops的aio_read设为sock_aio_read函数,定义在net/socket.c中,至此sys_read的实现完成了前一半(操作对象是file)而进入后一半(操作对象是socket),即socket层的实现。
在socket层跟踪sock_aio_read,可以得到最后调用的是sock->ops->recvmsg,由于socket类型为IPv4 TCP,因此sock->ops在socket创建过程中被设为inet_stream_ops,定义在net/ipv4/af_inet.c中。
1![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
const struct proto_ops inet_stream_ops =
{
2
.family = PF_INET,
3
![](http://www.cppblog.com/Images/dot.gif)
4
.release = inet_release,
5
![](http://www.cppblog.com/Images/dot.gif)
6
.recvmsg = sock_common_recvmsg,
7
![](http://www.cppblog.com/Images/dot.gif)
8
};
从上可知recvmsg设为sock_common_recvmsg,跟踪它的代码,定义在net/core/sock.c中。
1
int sock_common_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t size, int flags)
2![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif)
{
3
struct sock *sk = sock->sk;
4
int addr_len = 0;
5
int err;
6![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
7
err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT,flags & ~MSG_DONTWAIT, &addr_len);
8
![](http://www.cppblog.com/Images/dot.gif)
9
return err;
10
}
struct sock表示套接字的网络接口层,它的成员sk_prot表示网络协议块,在这它对应tcp_prot结构,定义在net/ipv4/tcp_ipv4.c中,由此可见进入到特定协议的实现。
1![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
struct proto tcp_prot =
{
2
.name = "TCP",
3
![](http://www.cppblog.com/Images/dot.gif)
4
.close = tcp_close,
5
![](http://www.cppblog.com/Images/dot.gif)
6
.recvmsg = tcp_recvmsg,
7
![](http://www.cppblog.com/Images/dot.gif)
8
};
recvmsg设为tcp_recvmsg,至此跟踪结束。对于sys_readv的实现,调用的是vfs_readv,后面的过程和sys_read相同,总结核心调用链如下图:
由此可知,sockfs_file_ops只须实现aio_read,就能支持普通和聚集两种方式的读操作。为了对比,这里也给出Berkeley Sockets API中recv的核心调用链如下图:
显而易见,recv内部实现调用的是sys_recvfrom,它没有经过VFS,而是先调用sock_lookup_light从fd得到socket,再调用sock_recvmsg,后面的流程和recv就是一样的了。
Socket销毁
Socket操作既可以调用文件IO,也可以调用Berkeley Sockets API。但销毁不同,系统调用close是用户空间销毁socket的唯一方法,它定义在fs/open.c中。
1
SYSCALL_DEFINE1(close, unsigned int, fd)
2![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
3
struct file * filp;
4
struct files_struct *files = current->files;
5
struct fdtable *fdt;
6
int retval;
7![](/Images/OutliningIndicators/InBlock.gif)
8
spin_lock(&files->file_lock);
9
fdt = files_fdtable(files);
10
![](http://www.cppblog.com/Images/dot.gif)
11
filp = fdt->fd[fd];
12
![](http://www.cppblog.com/Images/dot.gif)
13
rcu_assign_pointer(fdt->fd[fd], NULL);
14
FD_CLR(fd, fdt->close_on_exec);
15
__put_unused_fd(files, fd);
16
spin_unlock(&files->file_lock);
17
retval = filp_close(filp, files);
18
![](http://www.cppblog.com/Images/dot.gif)
19
}
首先从fd获取对应的file,若file非空则设置进程描述符数组对应项为空,并将fd从exec时关闭的文件描述符链表和打开的文件描述符链表中移除;最后调用filp_close,跟踪它的代码,定义在fs/open.c中。
1
int filp_close(struct file *filp, fl_owner_t id)
2![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif)
{
3
int retval = 0;
4![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
5![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
if (!file_count(filp))
{
6
printk(KERN_ERR "VFS: Close: file count is 0\n");
7
return 0;
8
}
9![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
10
if (filp->f_op && filp->f_op->flush)
11
retval = filp->f_op->flush(filp, id);
12![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
13
dnotify_flush(filp, id);
14
locks_remove_posix(filp, id);
15
fput(filp);
16
return retval;
17
}
首先判断file的引用计数,若为0则打印一个错误日志(说明这是一个bug,因为file已经被释放)并返回;由于sockfs_file_ops中的flush没有定义即为空,因此跳过;dnotify_flush用于释放任何相关的dnotify(一种文件监控机制)资源,locks_remove_posix用于清除文件锁相关的资源,由于socket对应的inode没有使用文件锁,因此它什么也没做。最后调用fput来释放file,定义在fs/file_table.c中。
1
void fput(struct file *file)
2![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif)
{
3
if (atomic_long_dec_and_test(&file->f_count))
4
__fput(file);
5
}
先递减引用计数,若为0则调用__fput释放file,它会调用到sockfs_file_ops定义的release函数即sock_close,它是sock_release的包装函数,sock_release定义在net/socket.c中。
1
void sock_release(struct socket *sock)
2![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif)
{
3![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
if (sock->ops)
{
4
struct module *owner = sock->ops->owner;
5![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
6
sock->ops->release(sock);
7
sock->ops = NULL;
8
module_put(owner);
9
}
10
if (sock->fasync_list)
11
printk(KERN_ERR "sock_release: fasync list not empty!\n");
12![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
13
percpu_sub(sockets_in_use, 1);
14![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
if (!sock->file)
{
15
iput(SOCK_INODE(sock));
16
return;
17
}
18
sock->file = NULL;
19
}
先调用ops->release即特定协议的释放操作,对于IPv4 TCP,就是inet_stream_ops中定义的inet_release函数,它又会调用到tcp_prot中定义的close即tcp_close;对于关联inode的释放,这里要分2种情况:如果sock->file为空,就调用iput释放,否则返回到__fput中,会调用dput释放dentry,而dentry又关联着inode,最终调用iput释放inode;当最后一个iput被调用时,sockfs_ops中定义的sock_destroy_inode就会被调用,归还由sock_alloc_inode分配的struct socket_alloc对象到SALB缓存中。总结核心调用链如下图:
在
上篇初始化一节,我们已知sockfs文件系统被装载,然而实际上没有卸载它的方式。由于TCP/IP协议栈和sockfs被静态编译到内核中,而不是一个内核模块。因此没必要提供一个卸载函数,sockfs伪文件系统在启动到关闭期间,总是被装载着的。
posted on 2015-05-03 16:55
春秋十二月 阅读(5184)
评论(0) 编辑 收藏 引用 所属分类:
Network