TCP内存
<h2>概述</h2>
<p>1、客户端创建 socket 涉及以下几方面的内存:socket 对象(sock_inode_cache)、tcp_sock 对象(TCP)、dentry 对象(dentry)、file 对象(filp -> kmalloc-256)
2、服务端是通过 accept 创建 sokcet,其对象和客户端类似,有一点不太一样的是:在第 3 次握手的时候就创建了 tcp_sock 放在全连接队列中,因此在 accept 时直接取出来就行
3、一条空的 socket 连接占用内存大概 3.3 kb(包含上面 4 个对象,以及 socket_wq、端口绑定关系 inet_bind_bucket(服务端不用),后 2 者都是在 kmalloc-64 中)
4、TIME_WAIT 状态下空的 TCP 连接占用内存大概 0.3-0.4 KB 左右
5、客户端发送的数据,即使服务端应用层不收取,但只要服务端协议栈回了 ACK,客户端就认为数据已经被收取,就会释放发送的数据空间
6、待分析发送、接收缓冲区</p>
<h2>接收和发送缓冲区</h2>
<p><img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=24c2c8cadac3f5f67ef205202125910a&amp;file=file.png" alt="" /></p>
<h2>客户端创建 socket</h2>
<p>创建 socket 涉及的内存申请:</p>
<pre><code class="language-c">// file: net/socket.c
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
retval = sock_create(family, type, protocol, &amp;sock); // 涉及 socket 和 tcp_sock 对象分配
retval = sock_map_fd(sock, flags &amp; (O_CLOEXEC | O_NONBLOCK)); // 涉及 dentry 和 file 申请
// ...
}</code></pre>
<h2>socket 对象</h2>
<p>先看 socket 对象分配:</p>
<pre><code class="language-c">int __sock_create(struct net *net, int family, int type, int protocol,
struct socket **res, int kern)
{
sock = sock_alloc(); // 在 sock_inode_cachep(名称为 sock_inode_cache)中分配 struct socket_alloc
err = pf-&gt;create(net, sock, protocol, kern); // 分配 tcp_sock 对象
// ...
}</code></pre>
<p>对于 sock_alloc 分析如下:</p>
<pre><code class="language-c">// file: net/socket.c
struct socket_alloc {
struct socket socket;
struct inode vfs_inode;
};
static int init_inodecache(void)
{
sock_inode_cachep = kmem_cache_create(&quot;sock_inode_cache&quot;,
sizeof(struct socket_alloc),
0,
(SLAB_HWCACHE_ALIGN |
SLAB_RECLAIM_ACCOUNT |
SLAB_MEM_SPREAD),
init_once); // 创建专用的 keme_cache,对象大小为 sizeof(struct socket_alloc)
if (sock_inode_cachep == NULL)
return -ENOMEM;
return 0;
}
static const struct super_operations sockfs_ops = {
.alloc_inode = sock_alloc_inode, // 创建 socket 对象时会用到
.destroy_inode = sock_destroy_inode,
.statfs = simple_statfs,
};
static struct dentry *sockfs_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
return mount_pseudo(fs_type, &quot;socket:&quot;, &amp;sockfs_ops,
&amp;sockfs_dentry_operations, SOCKFS_MAGIC);
}</code></pre>
<p>申请 struct socket 内核的调用树:</p>
<pre><code class="language-c">sock_alloc
new_inode_pseudo
alloc_inode
sb-&gt;s_op-&gt;alloc_inode // 即 sock_alloc_inode</code></pre>
<p>继续:</p>
<pre><code class="language-c">// file: net/socket.c
static struct inode *sock_alloc_inode(struct super_block *sb)
{
struct socket_alloc *ei;
struct socket_wq *wq;
ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL); // socket_alloc 对象
if (!ei)
return NULL;
wq = kmalloc(sizeof(*wq), GFP_KERNEL); // 用来记录在 socket 上等待事件的等待项。据说很小,可忽略
if (!wq) {
kmem_cache_free(sock_inode_cachep, ei);
return NULL;
}
init_waitqueue_head(&amp;wq-&gt;wait);
wq-&gt;fasync_list = NULL;
RCU_INIT_POINTER(ei-&gt;socket.wq, wq);
ei-&gt;socket.state = SS_UNCONNECTED;
ei-&gt;socket.flags = 0;
ei-&gt;socket.ops = NULL;
ei-&gt;socket.sk = NULL;
ei-&gt;socket.file = NULL;
return &amp;ei-&gt;vfs_inode;
}</code></pre>
<h2>tcp_sock 对象</h2>
<p>再看 sk 分配。上文的 create 对于 IPV4 而言是 inet_create:</p>
<pre><code class="language-c">// file: net/ipv4/af_inet.c
static int inet_create(struct net *net, struct socket *sock, int protocol,
int kern)
{
sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot); // 实际申请的是 struct tcp_sock,只是结构层层包含,但第一个字段就是 sk
// ...
}
// file: net/core/sock.c
/**
* sk_alloc - All socket objects are allocated here
* @net: the applicable net namespace
* @family: protocol family
* @priority: for allocation (%GFP_KERNEL, %GFP_ATOMIC, etc)
* @prot: struct proto associated with this new sock instance
*/
struct sock *sk_alloc(struct net *net, int family, gfp_t priority,
struct proto *prot)
{
struct sock *sk;
sk = sk_prot_alloc(prot, priority | __GFP_ZERO, family); // 继续
if (sk) {
sk-&gt;sk_family = family;
/*
* See comment in struct sock definition to understand
* why we need sk_prot_creator -acme
*/
sk-&gt;sk_prot = sk-&gt;sk_prot_creator = prot;
sock_lock_init(sk);
sock_net_set(sk, get_net(net));
atomic_set(&amp;sk-&gt;sk_wmem_alloc, 1);
sock_update_classid(sk);
sock_update_netprioidx(sk);
}
return sk;
}
EXPORT_SYMBOL(sk_alloc);
static struct sock *sk_prot_alloc(struct proto *prot, gfp_t priority,
int family)
{
slab = prot-&gt;slab;
if (slab != NULL) {
sk = kmem_cache_alloc(slab, priority &amp; ~__GFP_ZERO); // 在 tcp_prot-&gt;slab 中申请对象,对象为 struct tcp_sock
// ...
}
// ...
}</code></pre>
<p>申请的对象是 <code>struct tcp_sock</code>,其包含关系如下:</p>
<p><img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=05e5d14db2826024b90827e8ec4a638c&amp;file=file.png" alt="" /></p>
<p>那么 tcp_prot->slab 是怎么初始化的?</p>
<pre><code class="language-c">// file: net/ipv4/af_inet.c
static int __init inet_init(void)
{
rc = proto_register(&amp;tcp_prot, 1);
rc = proto_register(&amp;udp_prot, 1);
// ...
}
// file: net/core/sock.c
int proto_register(struct proto *prot, int alloc_slab)
{
if (alloc_slab) {
prot-&gt;slab = kmem_cache_create(prot-&gt;name, prot-&gt;obj_size, 0,
SLAB_HWCACHE_ALIGN | prot-&gt;slab_flags,
NULL); // prot-&gt;name = &quot;TCP&quot;
// ...
}
// ...
}
// file: net/ipv4/tcp_ipv4.c
struct proto tcp_prot = {
.name = &quot;TCP&quot;,
.owner = THIS_MODULE,
.obj_size = sizeof(struct tcp_sock),
.slab_flags = SLAB_DESTROY_BY_RCU,
// ...
}</code></pre>
<h2>dentry 对象</h2>
<p>再看 dentry 申请:</p>
<pre><code class="language-c">// file: include/linux/dcache.h
struct dentry {
struct dentry *d_parent; /* parent directory */
struct qstr d_name;
struct inode *d_inode; /* Where the name belongs to - NULL is
* negative */
unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */
// ...
};</code></pre>
<p>内存初始化:</p>
<pre><code class="language-c">// file: fs/dcache.c
static void __init dcache_init(void)
{
dentry_cache = KMEM_CACHE(dentry,
SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|SLAB_MEM_SPREAD); // 名称就是 dentry,大小就是 sizeof(struct dentry)
// ...
}
// file: include/linux/slab.h
#define KMEM_CACHE(__struct, __flags) kmem_cache_create(#__struct,\
sizeof(struct __struct), __alignof__(struct __struct),\
(__flags), NULL)</code></pre>
<p>继续看 <code>sock_map_fd</code>:</p>
<pre><code class="language-c">// file: net/socket.c
static int sock_map_fd(struct socket *sock, int flags)
{
struct file *newfile;
int fd = get_unused_fd_flags(flags);
if (unlikely(fd &lt; 0))
return fd;
newfile = sock_alloc_file(sock, flags, NULL); // 申请 dentry、file 内核对象
if (likely(!IS_ERR(newfile))) {
fd_install(fd, newfile);
return fd;
}
put_unused_fd(fd);
return PTR_ERR(newfile);
}
struct file *sock_alloc_file(struct socket *sock, int flags, const char *dname)
{
path.dentry = d_alloc_pseudo(sock_mnt-&gt;mnt_sb, &amp;name);
file = alloc_file(&amp;path, FMODE_READ | FMODE_WRITE,
&amp;socket_file_ops);
// ...
}
// file: fs/dcache.c
struct dentry *d_alloc_pseudo(struct super_block *sb, const struct qstr *name)
{
struct dentry *dentry = __d_alloc(sb, name);
if (dentry)
dentry-&gt;d_flags |= DCACHE_DISCONNECTED;
return dentry;
}
EXPORT_SYMBOL(d_alloc_pseudo);
struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
{
struct dentry *dentry;
char *dname;
dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL); // dentry_cache 就是上文申请的 kmem_cache,其中对象为 struct dentry
// ...
}</code></pre>
<h2>file 对象</h2>
<p>再看 filp 对象申请:</p>
<pre><code class="language-c">// file: fs/file_table.c
struct file *alloc_file(struct path *path, fmode_t mode,
const struct file_operations *fop)
{
struct file *file;
file = get_empty_filp();
// ...
}
struct file *get_empty_filp(void)
{
f = kmem_cache_zalloc(filp_cachep, GFP_KERNEL); // 在 filp_cachep 上申请一个 struct file 对象
}</code></pre>
<p>那么 filp_cachep 是在哪里初始化的?</p>
<pre><code class="language-c">// file: fs/file_table.c
void __init files_init(unsigned long mempages)
{
unsigned long n;
filp_cachep = kmem_cache_create(&quot;filp&quot;, sizeof(struct file), 0,
SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
// ...
}</code></pre>