学习Linux-4.12内核网路协议栈(1.3)——协议栈的初始化(sock_init)

时间:2021-08-20 11:02:05


初始化的时候,第一个调用的是sock_init,它主要完成:

1. sysctl文件的创建

2. skb高速缓存初始化,它会在slab创建两个节点skbuff_head_cache和skbuff_fclone_cache
3.注册并挂socket文件系统



static int __init sock_init(void)
{
int err;
/*
* Initialize the network sysctl infrastructure.
*/
err = net_sysctl_init(); //这里创建sys文件,用于管理和查看一下网络参数
if (err)
goto out;

/*
* Initialize skbuff SLAB cache
*/
skb_init(); //skb高速缓存初始化,它会在slab创建两个节点skbuff_head_cache和skbuff_fclone_cache

/*
* Initialize the protocols module.
*/

init_inodecache();

err = register_filesystem(&sock_fs_type); //注册sock文件系统
if (err)
goto out_fs;
sock_mnt = kern_mount(&sock_fs_type); //挂载sock文件系统到super_blocks
if (IS_ERR(sock_mnt)) {
err = PTR_ERR(sock_mnt);
goto out_mount;
}

/* The real protocol initialization is performed in later initcalls.
*/


1. skb_init

 945 /* Layout of fast clones : [skb1][skb2][fclone_ref] */
946 struct sk_buff_fclones {
947 struct sk_buff skb1;
948
949 struct sk_buff skb2;
950
951 atomic_t fclone_ref;
952 };

void __init skb_init(void)
{
skbuff_head_cache = kmem_cache_create("skbuff_head_cache", //skbuff_head_cache高速缓存,一般情况下,SKB都是从该高速缓存中分配的。
sizeof(struct sk_buff),
0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC,
NULL);
skbuff_fclone_cache = kmem_cache_create("skbuff_fclone_cache", //创建每次以两倍SKB描述符长度来分配空间的skbuff_fclone_cache高速缓存。如果在分配SKB时
就知道可能被克隆,那么应该从这个高速缓存中分配空间,因为在这个高速缓存中分配SKB时,会同时分配一个后备的SKB,以便将来用于克隆,
这样在克隆时就不用再次分配SKB了,直接使用后备的SKB即可,这样做的目的主要是提高效率。
sizeof(struct sk_buff_fclones),
0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC,
NULL);
}
关于slab缓存的机制,可以查看 这篇文章,讲的比较通俗
从code可以看到
struct sk_buff_fclone

其实是两个skb的大小加上一个fclone_ref引用标识,两个高速缓存的区别在于创建时指定的单位内存区域大小不同,skbuff_head_cache的单位内存区域长度是sizeof(struct sk_buff),而skbuff_fclone_cache的单位内存区域长度是2*sizeof(struct sk_buff)+sizeof(atomic_t),即一对SKB和一个引用计数,可以说这一对SKB是"父子"关系,指向同一个数据缓存区,引用计数值为0,1或2,用来表示这一对SKB中有几个已被使用。

学习Linux-4.12内核网路协议栈(1.3)——协议栈的初始化(sock_init)

2. init_inodecache

 
 290 static void init_inodecache(void) 291 { 292     sock_inode_cachep = kmem_cache_create("sock_inode_cache", 293                           sizeof(struct socket_alloc), 294                           0, 295                           (SLAB_HWCACHE_ALIGN | 296                            SLAB_RECLAIM_ACCOUNT | 297                            SLAB_MEM_SPREAD | SLAB_ACCOUNT), 298                           init_once); 299     BUG_ON(sock_inode_cachep == NULL); 300 }
在 linux 系统中,socket属于文件系统的一部分,网络通信可以被看作对文件的读取。这种特殊的文件系统叫sockfs。 上一节中提到在sock_init 函数中先调用init_inodecache,为创建socket 文件系统做好内存准备。不过要注意的是在Linux内核中存在init_inodecache多个定义,但都是静态型,即只能由该.c文件中的函数调用,在socket.c中, 就定义了这么一个函数 

3.准备socket文件系统

为文件系统准备 inode 缓存部分了,下面进入初始化文件系统。首先是调用 register_filesystem(&sock_fs_type)把文件系统类型注册到file_systems链表上,

然后调用kern_mount(&sock_fs_type);把该文件系统注册到super_blocks上。在系统初始化的时候要通过kern_mount安装此文件系统。

所谓创建一个套接字就是在sockfs文件系统中创建一个特殊文件。super_block里面有一个字段s_op是用来指向某文件系统内部的支持函数,

这个字段的类型为super_operation。这个结构定义了12个函数指针,这些指针是要让VFS来调用的。因此这是 VFS和文件系统之间的一个接口,经由这层接口,

超级块可以控制文件系统下的文件或目录。

 960 struct vfsmount *
961 vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
962 {
963 struct mount *mnt;
964 struct dentry *root;
965
966 if (!type)
967 return ERR_PTR(-ENODEV);
968
969 mnt = alloc_vfsmnt(name);
970 if (!mnt)
971 return ERR_PTR(-ENOMEM);
972
973 if (flags & MS_KERNMOUNT)
974 mnt->mnt.mnt_flags = MNT_INTERNAL;
975
976 root = mount_fs(type, flags, name, data);
977 if (IS_ERR(root)) {
978 mnt_free_id(mnt);
979 free_vfsmnt(mnt);
980 return ERR_CAST(root);
981 }
982
983 mnt->mnt.mnt_root = root;
984 mnt->mnt.mnt_sb = root->d_sb;
985 mnt->mnt_mountpoint = mnt->mnt.mnt_root;
986 mnt->mnt_parent = mnt;
987 lock_mount_hash();
988 list_add_tail(&mnt->mnt_instance, &root->d_sb->s_mounts);
989 unlock_mount_hash();
990 return &mnt->mnt;
991 }
992 EXPORT_SYMBOL_GPL(vfs_kern_mount);