1、示例及函数入口:1) 示例代码如下:
int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
2) 入口:net/Socket.c:sys_socketcall(),根据子系统调用号,创建socket会执行sys_socket()函数;2、分配socket结构:1) 调用链:net/Socket.c:sys_socket()->sock_create()->__sock_create()->sock_alloc();2) 在socket文件系统中创建i节点:
inode = new_inode(sock_mnt->mnt_sb);
其中,sock_mnt为socket文件系统的根节点,是在内核初始化安装socket文件系统时赋值的,mnt_sb是该文件系统安装点的超级块对象的指针;这里,new_inode函数是文件系统的通用函数,其作用是在相应的文件系统中创建一个inode;其主要代码如下(fs/Inode.c):
struct inode *new_inode(struct super_block *sb) {
struct inode * inode;
inode = alloc_inode(sb);
…...
return inode;
}
这里调用了alloc_inode函数分配inode结构(fs/Inode.c):
static struct inode *alloc_inode(struct super_block *sb) {
struct inode *inode;
if (sb->s_op->alloc_inode)
inode = sb->s_op->alloc_inode(sb);
else
inode = (struct inode *) kmem_cache_alloc(inode_cachep, GFP_KERNEL);
…...
}
上面有个条件判断:if (sb->s_op->alloc_inode),意思是说如果当前文件系统的超级块有自己分配inode的操作函数,则调用它自己的函数分配inode,否则从公用的高速缓存区中分配一块inode;3) 创建socket专用inode:在“socket文件系统注册”一文中后面提到,在安装socket文件系统时,会初始化该文件系统的超级块,此时会初始化超级块的操作指针s_op为sockfs_ops结构;因此此时分配inode会调用sock_alloc_inode函数来完成:
static struct inode *sock_alloc_inode(struct super_block *sb) {
struct socket_alloc *ei;
ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL);
…...
return &ei->vfs_inode;
}
从这里可以看到,实际上分配了一个socket_alloc结构体,该结构体包含socket和inode:
struct socket_alloc {
struct socket socket;
struct inode vfs_inode;
};
但最终返回的是该结构体中的inode成员;至此,socket结构和inode结构均分配完毕;分配inode后,应用程序便可以通过文件描述符对socket进行read()/write()之类的操作,这个是由虚拟文件系统(VFS)来完成的。3、根据inode取得socket对象:由于创建inode是文件系统的通用逻辑,因此其返回值是inode对象的指针;但这里在创建socket的inode后,需要根据inode得到socket对象;内联函数SOCKET_I由此而来:
static inline struct socket *SOCKET_I(struct inode *inode)
{
return &container_of(inode, struct socket_alloc, vfs_inode)->socket;
}
再看看container_of宏(include/linux/Kernel.h):
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
和offsetof宏(include/linux/Stddef.h):
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
1) offerset(TYPE, MEMBER)宏的作用:返回MEMBER成员在结构体TYPE中的偏移量;先看一下例子,假设有个结构体A如下:
struct struct_A {
char a;
int b;
}
其中,成员a相对于结构的偏移量为0,成员b相对于结构体的偏移量为1;结构体struct_A的变量m在内存中地址结构如下:
我们再来看offset宏:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
可以这样来理解,把0地址强制转化为TYPE结构的指针,然后再拿到MEMBER成员的地址,该地址正好等于MEMBER成员在结构体TYPE中的偏移量;还是拿上面的例子来说吧,如下图,offset(struct_A, b)的值为1,正好等于其偏移量;如下图所示:
2) container_of(ptr, type, member)宏的作用:返回ptr指针所在的结构体;其中ptr为结体体type的变量中member成员的指针;再来看看它的实现:
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
将ptr指针转化为char *,然后减去其在结构体中的偏移量,得到的是ptr所在的结构体的地址,最后强制转换成type *;回到sock_alloc函数,SOCKET_I根据inode取得socket变量后,记录当前进程的一些信息,如fsuid, fsgid,并增加sockets_in_use的值(该变量表示创建socket的个数);创建后socket变量后,在__sock_create()函数中设置其type为应用程序传递下来的type,上面的例子中即为SOCK_STREAM;4、使用协议族来初始化socket:1) 协议族的概念:协议族是由多个协议组成的一个通信协议栈, 如我们最熟悉的TCP/IP(AF_INET因特网协议族)包括TCP,IP,ICMP,ARP等协议;2) Linux支持的协议族:
Linux2.6.26中支持33个协议域,在net/Socket.c中定义全局变量:
static const struct net_proto_family *net_families[NPROTO] __read_mostly;
在/include/linux/socket.h中定义了每个协议域的宏,每个协议域占用该数组的一项,如AF_INET占用net_families[2],如果net_families[2]=null,则说明当前内核没有注册AF_INET模块;
3) 注册AF_INET协议域:
在“socket文件系统注册”中提到系统初始化的工作,AF_INET的注册也正是通过这个来完成的;
初始化入口net/ipv4/Af_inet.c:
fs_initcall(inet_init);
static int __init inet_init(void) {
…...
// 为不同的套接字分配高速缓冲区
rc = proto_register(&tcp_prot, 1);
rc = proto_register(&udp_prot, 1);
rc = proto_register(&raw_prot, 1);
…...
(void)sock_register(&inet_family_ops);
…...
/* 将所有的socket类型按type通过inetsw管理起来 */
for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
INIT_LIST_HEAD(r);
for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
inet_register_protosw(q);
…...
}
这里调用sock_register函数来完成注册:
int sock_register(const struct net_proto_family *ops) {
int err;
…...
if (net_families[ops->family])
err = -EEXIST;
else {
net_families[ops->family] = ops;
err = 0;
}
…...
}
根据family将AF_INET协议域inet_family_ops注册到内核中的net_families数组中;下面是其定义:
static struct net_proto_family inet_family_ops = {
.family = PF_INET,
.create = inet_create,
.owner = THIS_MODULE,
};
其中,family指定协议域的类型,create指向相应协议域的socket的创建函数;
4) 套接字类型
在相同的协议域下,可能会存在多个套接字类型;如AF_INET域下存在流套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW),在这三种类型的套接字上建立的协议分别是TCP, UDP,ICMP/IGMP等。
在Linux内核中,结构体struct proto表示域中的一个套接字类型,它提供该类型套接字上的所有操作及相关数据(在内核初始化时会分配相应的高速缓冲区,见上面提到的inet_init函数)。
AF_IENT域的这三种套接字类型定义用结构体inet_protosw(net/ipv4/Af_inet.c)来表示,如下:
static struct inet_protosw inetsw_array[] =
{
{
.type = SOCK_STREAM,
.protocol = IPPROTO_TCP,
.prot = &tcp_prot,
.ops = &inet_stream_ops,
.capability = -1,
.no_check = 0,
.flags = INET_PROTOSW_PERMANENT |
INET_PROTOSW_ICSK,
},
{
.type = SOCK_DGRAM,
.protocol = IPPROTO_UDP,
.prot = &udp_prot,
.ops = &inet_dgram_ops,
.capability = -1,
.no_check = UDP_CSUM_DEFAULT,
.flags = INET_PROTOSW_PERMANENT,
},
{
.type = SOCK_RAW,
.protocol = IPPROTO_IP, /* wild card */
.prot = &raw_prot,
.ops = &inet_sockraw_ops,
.capability = CAP_NET_RAW,
.no_check = UDP_CSUM_DEFAULT,
.flags = INET_PROTOSW_REUSE,
}
};
其中,tcp_prot(net/ipv4/Tcp_ipv4.c)、udp_prot(net/ipv4/Udp.c)、raw_prot(net/ipv4/Raw.c)分别表示三种类型的套接字,分别表示相应套接字的操作和相关数据;ops成员提供该协议域的全部操作集合,针对三种不同的套接字类型,有三种不同的域操作inet_stream_ops、inet_dgram_ops、inet_sockraw_ops,其定义均位于net/ipv4/Af_inet.c下;
内核初始化时,在inet_init中,会将不同的套接字存放到全局变量inetsw中统一管理;inetsw是一个链表数组,每一项都是一个struct inet_protosw结构体的链表,总共有SOCK_MAX项,在inet_init函数对AF_INET域进行初始化的时候,调用函数inet_register_protosw把数组inetsw_array中定义的套接字类型全部注册到inetsw数组中;其中相同套接字类型,不同协议类型的套接字通过链表存放在到inetsw数组中,以套接字类型为索引,在系统实际使用的时候,只使用inetsw,而不使用inetsw_array;
5) 使用协议域来初始化socket
了解了上面的知识后,我们再回到net/Socket.c:sys_socket()->sock_create()->__sock_create()中:
const struct net_proto_family *pf;
…...
pf = rcu_dereference(net_families[family]);
err = pf->create(net, sock, protocol);
上面的代码中,找到内核初始化时注册的协议域,然后调用其create方法;
未完,下一篇blog待续;
分享到:
相关推荐
详解Linux协议栈的数据流向,SOCKET的操作流程,unicast multicast等等的区别。
《Linux内核网络栈源代码情景分析》主要对Linux1.2.13内核协议栈的全部源代码做了详细的分析,该版本所有代码都在一个文件夹中,每种协议的实现都只有一个文件与之对应,分析该版本源代码可以方便读者迅速掌握Linux...
本书主要对 Linux 1.2.13 内核协议栈的全部源代码做了详细的分析, 该版本所有代码都在一个文件夹中,每种协议的实现都只有一个文件与之对应,分析该版本源代码可以方便读者迅速掌握 Linux 网络协议结构。...
本书主要对Linux 1.2.13内核协议栈的全部源代码做了详细的分析,该版本所有代码都在一个文件夹中,每种协议的实现都只有一个文件与之对应,分析该版本源代码可以方便读者迅速掌握Linux网络协议结构。 本书共分为5个...
很详细的一个原理图,看明白之后对linux网络协议栈有很深的理解和认知,有关socket、ip报文、ip分片、转发、桥等图解
准备将readme.md作为笔记,记录linux内核网络协议栈源码剖析的过程 天2,2016年11月18日20:24:07 增加了对sys_socket(), sock_create, __sock_create(), inet_create()的完全注释 socket的创建过程已经完成,接下来...
文章详细介绍了linux 内核中有关socket 相关的协议栈的初始化部分。文章中各个函数调用关系清晰,重要代码逻辑都有中文注释及中文旁白解释。是一篇很好的学习linux 内核网络子系统的文章。
摘 要:基于对Linux 下蓝牙协议栈BlueZ 源代码的分析,给出BlueZ的组织结构和特点。分析蓝牙USB 传输驱动机制和数据处理过程, 给出实现蓝牙设备驱动的重要数据结构和流程,并总结Linux 下开发蓝牙USB 设备驱动的...
1.5.1 Linux内核的主要模块 7 1.5.2 Linux的文件结构 9 1.6 GNU通用公共许可证 10 1.6.1 GPL许可证的历史 10 1.6.2 GPL的自由理念 10 1.6.3 GPL的基本条款 11 1.6.4 关于GPL许可证的争议 12 1.7 Linux...
1.5.1 Linux内核的主要模块 7 1.5.2 Linux的文件结构 9 1.6 GNU通用公共许可证 10 1.6.1 GPL许可证的历史 10 1.6.2 GPL的自由理念 10 1.6.3 GPL的基本条款 11 1.6.4 关于GPL许可证的争议 12 1.7 Linux...
(1)Socket应用层的各种网络应用程序基本上都是通过LinuxSocket编程接口来和内核空间的网络协议栈通信的。LinuxSocket是从BSDSocket发展而来的,它是Linux操作系统的重要组成部分之一,它是网络应用程序的基础。从...
12.2 网络协议 12.2.1 网络参考模型 12.2.2 TCP/IP 协议工作原理及数据流 12.2.3 Internet 协议 12.2.4 TCP协议 12.3 套接字(socket) 12.3.1 套接字在网络中的地位和作用 12.3.2 套接字接口的种类 12.3.3 ...
1.5.1 Linux内核的主要模块 7 1.5.2 Linux的文件结构 9 1.6 GNU通用公共许可证 10 1.6.1 GPL许可证的历史 10 1.6.2 GPL的自由理念 10 1.6.3 GPL的基本条款 11 1.6.4 关于GPL许可证的争议 12 1.7 Linux...
网络协议栈的实现基本采用TCP/IP的四层架构(链路、网络、传输、应用)。不过在实际学习中通常讲到的是5层架构(物理、链路、网络、传输、应用)。 BSD风格 BSD风格就是通常说的 socket、bind、connect、listen、...
linux内核协议栈源码阅读有任何理解错误的地方,还望指出linux官网 目标理解tcp / ip的协议栈,结合RFC和代码加深理解。微信群想一起阅读的小伙伴可以加我微信sheepbao-520 ,加入阅读群,备注阅读linux ...
《追踪Linux TCP/IP代码运行·基于2.6内核》以应用程序为线索,详细描述了数据包在协议栈的分段、重组、发送、接收过程,同时分析了路由的初始化和设置过程,主要包括socket应用程序、TCP/IP协议、路由、通知链、...
ZMQ的明确目标是“成为标准网络协议栈的一部分,之后进入Linux内核”。现在还未看到它们的成功。但是,它无疑是极具前景的、并且是人们更加需要的“传统”BSD套接字之上的一 层封装。ZMQ让编写高性能网络应用程序...
《追踪Linux TCP/IP代码运行·基于2.6内核》以应用程序为线索,详细描述了数据包在协议栈的分段、重组、发送、接收过程,同时分析了路由的初始化和设置过程,主要包括socket应用程序、TCP/IP协议、路由、通知链、...
ZMQ的明确目标是“成为标准网络协议栈的一部分,之后进入Linux内核”。现在还未看到它们的成功。但是,它无疑是极具前景的、并且是人们更加需要的“传统”BSD套接字之上的一 层封装。ZMQ让编写高性能网络应用程序...