`
diecui1202
  • 浏览: 98494 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Linux内核网络协议栈3-创建socket

阅读更多

 

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内核协议栈的详解完整版

    详解Linux协议栈的数据流向,SOCKET的操作流程,unicast multicast等等的区别。

    linux-1.2.13 网络协议栈源码

    在Linux内核中,网络协议栈是负责处理网络数据传输的关键部分,它实现了TCP/IP协议族的各个层次,包括链路层、网络层、传输层以及应用层。 1. 链路层:在这个层次,主要涉及的是以太网协议,如Ethernet和ARP(地址...

    linux内核协议栈源码解析(2.6.18内核)

    《Linux内核协议栈源码解析(2.6.18内核)》是一本针对Linux内核网络协议栈深入解析的重要参考资料,尤其适合那些希望深入理解Linux内核以及网络通信机制的IT专业人士。该书详细阐述了Linux 2.6.18版本内核中的网络...

    Linux网络体系结构 Linux内核中网络协议的设计与实现

    Linux内核通过socket API为上层应用提供了与网络协议栈交互的接口。 在设计与实现方面,Linux内核的网络协议栈采用了模块化和层次化的结构,使得代码易于维护和扩展。例如,协议处理函数可以通过注册和注销机制动态...

    linux-2.6.18内核基础上分析网络协议栈,适合初学者看

    学习Linux内核的网络协议栈,不仅可以帮助我们理解数据在网络中的流动,还能让我们更好地调试网络问题、优化网络性能,甚至编写网络驱动程序和协议。通过阅读"Linux内核协议栈源码分析(新版).pdf",初学者可以逐步...

    linux内核协议栈分析

    Linux内核协议栈是Linux操作系统中负责处理网络数据包传输的核心组件。...通过文档的阅读,可以为研究和开发人员提供参考,帮助他们理解网络数据包在网络协议栈中的处理流程以及在Linux内核中的具体实现。

    Linux内核1.2.13网络协议栈初始化及源码剖析

    主要涵盖了Socket编程基础知识和Linux内核网络协议栈的工作方式,其中包括协议实现模块和网络设备驱动的初始化过程;探讨了数据链路层的功能,以及客户端、服务器的简单Socket程序和多线程并发模型的设计与注意事项...

    《linux内核网络栈源代码情景分析》.(曹桂平).[PDF].&ckook;.pdf.zip

    《Linux内核网络栈源代码情景分析》是曹桂平撰写的一本深入解析Linux内核网络处理机制的著作。这本书详细介绍了Linux操作系统如何处理网络数据包,从硬件接口到高层协议栈的每一个环节,帮助读者理解Linux网络内核的...

    基于Linux网络协议栈的嵌入式开发

    3. **集成到Linux内核中**:将VSP协议栈作为模块加载到Linux内核中,确保它与现有的网络协议栈兼容并行。 4. **测试与优化**:进行详尽的测试以确保新协议栈的稳定性和性能,根据测试结果进行必要的调整和优化。 ##...

    linux-网络协议栈流程.pdf

    Linux内核中的网络驱动负责与物理网络设备交互,接收网络帧并将其传递给上层的网络协议栈。这一过程涉及中断处理和DMA(Direct Memory Access)等技术,以确保数据能高效地从网络设备传输到内存中。 接着,数据包在...

    Linux内核 tcp ip协议栈源码分析

    在Linux内核中,TCP/IP协议栈的各个组件通过sk_buff(socket buffer)结构进行交互。这是一个高效的数据结构,用于存储网络数据并传递到不同层次。在`net/core/skbuff.c`中,你可以看到关于sk_buff的详细操作。 4....

    深入分析linux tcp/ip协议栈

    在Linux中,网络数据包从上到下通过协议栈时,会经过socket接口、协议处理、设备驱动、物理媒介传输等步骤。数据包的发送涉及socket缓冲区、TCP窗口管理、拥塞控制算法(如慢启动、快速重传、快速恢复等);接收则...

    linux网络协议栈完全实现

    在这个主题中,我们将深入探讨Linux内核中的网络协议栈实现,包括TCP/IP协议族的主要组件和工作流程。 首先,我们要理解TCP/IP协议族是一个四层模型,包括应用层、传输层、网络层和数据链路层。在Linux中,这些层...

    Linux内核TCP-IP协议栈分析.rar

    Linux内核中的TCP/IP协议栈是操作系统的心脏,负责处理所有网络通信。它是一套复杂的软件,用于实现互联网协议族,使得Linux系统能够与其他设备进行数据传输。在本资料"Linux内核TCP/IP协议栈分析"中,我们将深入...

    linux TCP IP协议栈源码解析

    - `net/`目录下包含所有网络协议栈的源代码,如`tcp.c`处理TCP协议,`ipv4/`处理IPv4。 - `socket`层是用户空间与内核空间通信的接口,通过系统调用如`socket()`、`bind()`、`connect()`等实现。 - `sk_buff`...

    linux内核协议栈源码分析

    Linux内核协议栈是操作系统核心的一部分,负责...通过深入研究Linux内核协议栈源码,不仅可以提升对网络协议的理解,还能为开发高效、稳定的网络应用提供理论支持。同时,这也有助于解决网络问题,提高系统的整体性能。

    LINUX-1.2.13内核网络栈实现.pdf

    本书主要对Linux 1.2.13内核协议栈的全部源代码做了详细的分析,该版本所有代码都在一个文件夹中,每种协议的实现都只有一个文件与之对应,分析该版本源代码可以方便读者迅速掌握Linux网络协议结构。 本书共分为5个...

    linux 内核精髓-精通linux内核必会的75个绝技

    6. **网络编程**:涵盖网络协议栈,从网络接口层到应用层,包括TCP/IP协议、套接字编程、网络服务的实现,以及socket选项和错误处理。 7. **系统调用与内核接口**:解析系统调用的原理,讲解如何利用strace等工具...

    Reading-and-comprehense-linux-Kernel-network-protocol-stack_y123456yz.tar.gz

    《Linux内核网络协议栈源码阅读与理解》 Linux内核网络协议栈是操作系统核心的重要组成部分,负责处理网络通信的各个层面,包括从硬件接口到应用层协议的转换。这个压缩包“Reading-and-comprehense-linux-Kernel-...

Global site tag (gtag.js) - Google Analytics