在内核中为何要有struct socket结构体呢?node
struct socket结构体的做用是什么?linux
由这个图可知,内核中的进程能够经过使用struct socket结构体来访问linux内核中的网络系统中的传输层、网络层、数据链路层。也能够说struct socket是内核中的进程与内核中的网路系统的桥梁。缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
struct
socket
{
socket_state state;
// socket state
short
type ;
// socket type
unsigned
long
flags;
// socket flags
struct
fasync_struct *fasync_list;
wait_queue_head_t wait;
struct
file *file;
struct
sock *sock;
// socket在网络层的表示;
const
struct
proto_ops *ops;
}
struct
socket结构体的类型
enum
sock_type
{
SOCK_STREAM = 1,
// 用于与TCP层中的tcp协议数据的struct socket
SOCK_DGRAM = 2,
//用于与TCP层中的udp协议数据的struct socket
SOCK_RAW = 3,
// raw struct socket
SOCK_RDM = 4,
//可靠传输消息的struct socket
SOCK_SEQPACKET = 5,
// sequential packet socket
SOCK_DCCP = 6,
SOCK_PACKET = 10,
//从dev level中获取数据包的socket
};
struct
socket 中的flags字段取值:
#define SOCK_ASYNC_NOSPACE 0
#define SOCK_ASYNC_WAITDATA 1
#define SOCK_NOSPACE 2
#define SOCK_PASSCRED 3
#define SOCK_PASSSEC 4
|
咱们知道在TCP层中使用两个协议:tcp协议和udp协议。而在将TCP层中的数据往下传输时,要使用网络层的协议,而网络层的协议不少,不一样的网络使用不一样的网络层协议。咱们经常使用的因特网中,网络层使用的是IPV4和IPV6协议。网络
因此在内核中的进程在使用struct socket提取内核网络系统中的数据时,不光要指明struct socket的类型(用于说明是提取TCP层中tcp协议负载的数据,仍是udp层负载的数据),还要指明网络层的协议类型(网络层的协议用于负载TCP层中的数据)。socket
linux内核中的网络系统中的网络层的协议,在linux中被称为address family(地址簇,一般以AF_XXX表示)或protocol family(协议簇,一般以PF_XXX表示)。async
1.建立一个struct socket结构体:tcp
int sock_create(int family, int type, int protocol,
函数
struct socket **res);ui
int sock_create_kern(int family, int type, int protocol,
struct socket **res);
EXPROT_SYMBOL(sock_create);
EXPROT_SYMBOL(sock_create_kern);
family : 指定协议簇的类型,其值为:PF_XXX或 AF_XXX
type : 指定要建立的struct socket结构体的类型;
protocol : 通常为0;
res : 中存放建立的struct socket结构体的地址;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
int
sock_create(
int
family,
int
type,
int
protocol,
struct
socket **res)
{
return
__sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
}
int
sock_create_kern(
int
family,
int
type,
int
protocol,
struct
socket **res)
{
return
__sock_create( &init_net, family, type, protocot, res, 1 );
}
若是在内核中建立
struct
socket时,推荐使用sock_create_kern()函数;
// 网络协议簇结构体
struct
net_proto_family
{
int
family ;
// 协议簇
int
(*create)(
struct
net *net,
struct
socket *sock,
int
protocol);
struct
module *owner;
};
内核中的全部的网络协议的响应的网络协议簇结构体都存放在 net_families[]指针数组中;
static
struct
net_proto_family *net_families[NPROTO];
static
int
__sock_create(
struct
net *net,
int
family,
int
type,
int
protocol,
struct
socket **res,
int
kern )
{
struct
socket *sock;
struct
net_proto_family *pf;
sock = sock_alloc();
//分配一个struct socket 结构体
sock->type = type;
pf = rcu_dereference(net_families[family]);
//获取相应的网络协议簇结构体的地址;
pf->create(net, sock, protocol);
// 对struct socket结构体作相应的处理;
*res = sock;
// res中保存建立的struct socket结构体的地址;
return
0;
}
struct
socket_alloc
{
struct
socket socket ;
struct
inode vfs_node ;
}
static
inline
struct
socket *SOCKET_I(
struct
inode *inode)
{
return
&contain_of(inode,
struct
socket_alloc, vfs->node)->socket;
}
static
struct
socket *sock_alloc(
void
)
{
struct
inode *inode;
struct
socket *sock;
inode = new_inode(sock_mnt->mnt_sb);
//分配一个新的struct inode节点
sock = SOCKET_I(inode);
inode->i_mode = S_IFSOCK | S_IRWXUGO;
//设置inode节点的权限
inode->i_uid = current_fsuid();
// 设置节点的UID
inode->i_gid = current_fsgid();
//设置节点的GID
return
sock;
}
|
有以上的代码可知:linux内核在使用sock_create()、sock_create_kern()
进行struct socket结构体的建立时,其本质是分配了一个struct socket_alloc
结构体,而这个struct socket_alloc结构体中包含了struct socket 和struct
inode(struct inode结构体,是linux内核用来刻画一个存放在内存中的文件的,经过将struct inode 和 struct socket绑定在一块儿造成struct socket_alloc结构体,来表示内核中的网络文件)。而后对分配的struct socket结构体进行初始化,来定义内核中的网络文件的类型(family, type, protocol).
在linux网络系统中还有两个很是重要的套接字地址结构体:
struct sockaddr_in
struct sockaddr;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
typedef
unsigned
short
sa_family_t;
// Internet Address
struct
in_addr{
__b32 s_addr;
}
//struct describing an Internet socket address
//sockaddr_in 中存放端口号、网路层中的协议类型(ipv4,ipv6)等,网络层的IP地址;
struct
sockaddr_in
{
sa_family_t sin_family ;
// Address family AF_XXX
__be16 sin_port ;
// 端口号
struct
in_addr sin_addr ;
// Internet Address
/*Pad to size of 'struct sockaddr'*/
...........
};
//套接字地址结构体。
struct
sockaddr
{
sa_family_t sa_family;
// 存放网络层所使用的协议类型(AF_XXX 或 PF_XXX);
char
sa_data[14];
// 里面存放端口号、网络层地址等信息;
}
|
从本质上来讲,struct sockaddr与struct sockaddr_in是相同的。
但在,实际的使用过程当中,struct sockaddr_in是 Internet环境下的套接字地址形式,而struct sockaddr是经过的套接字地址个形式。在linux内核中struct sockaddr使用的更多,目的是使linux内核代码更为通用。
struct sockaddr_in 能够与 struct sockaddr 进行自由的转换。
2.将建立的套接字(struct socket)与套接字地址结构体(struct sockaddr or struct sockaddr_in)进行绑定:
int kernel_bind(struct socket *sock, struct sockaddr *addr,
int addrlen)
EXPROT_SYMBOL(kernel_bind);
sock : 为经过sock_create()或sock_create_kern()建立的套接字;
addr : 为套接字地址结构体;
addrlen:为套接字地址结构体的大小;
3.将一个套接字(struct socket)设置为监听状态:
int kernel_listen(struct socket *sock, int backlog);
backlog :通常状况下设置为0;
EXPORT_SYMBOL(kernel_listen);
4.当把一个套接字设置为监听状态之后,使用这个套接字去监听其它的套接字;
int kernel_accept(struct socket *sock, struct socket **new_sock,
int flags);
EXPORT_SYMBOL(kernel_accept);
sock : listening socket 处于监听状态的套接字;
new_sock : 被监听的套接字;
flags: struct socket中的flags字段的取值;
5.把一个套接字链接到另外一个套接字地址结构体上:
int kernel_connect(struc socket *sock, struct sockaddr *addr,
int addrlen, int flags);
EXPORT_SYMBOL(kernel_connect);
sock : struct socket;
addr : 为另外一个新的套接字地址结构体;
addrlen : 套接字地址结构体的大小;
flags :file-related flags associated with socket
6.把一个应用层中的数据发送给另外一个设备中的进程:
int kernel_sendmsg(struct socket *sock, struct msghdr *msg,
struct kvec *vec, size_t num, size_t size)
EXPORT_SYMBOL(kernel_sendmsg);
sock : 为当前进程中的struct socket套接字;
msg : 用于接收来自应用层的数据包;
kvec : 中存放将要发送出去的数据;
num : 见代码;
size : 为将要发送的数据的长度;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
struct
iovec
{
void
__user *iov_base;
__kernel_size_t iov_len;
}
struct
msghdr
{
//用于存放目的进程所使用的套接字地址
void
*msg_name;
// 用于存放目的进程的struct sockaddr_in
int
msg_namelen;
// 目的进程的sizeof(struct sockaddr_in)
//用于来自应用层的数据
struct
iovec *msg_iov ;
// 指向一个struct iovec的数组,数组中的每一个成员表示一个数据块
__kernel_size_t msg_iovlen ;
//数据块数,即struct iovec数组的大小
//用于存放一些控制信息
void
*msg_control ;
__kernel_size_t msg_controllen;
//控制信息的长度;
//
int
msg_flags;
}
|
1
2
3
4
5
|
struct
kvec
{
void
*iov_base;
//用于存放来自应用层的数据;
size_t
iov_len;
//来自应用层的数据的长度;
}
|
struct msghdr中的flags字段的取值为:
int kernel_sendmsg(struct socket *sock, struct msghdr *msg,
struct kvec *vec, size_t num, size_t size)函数的实现为:
有kernel_sendmsg()的实现代码可知,struct kvec中的数据部分最终仍是要放到struct msghdr之中去的。
kernel_sendmsg()的用法:
也可使用下面这个函数来实现相同的功能:
int sock_sendmsg(struct socket *sock, struct msghdr *msg,
size_t size);
EXPORT_SYMBOL(sock_sendmsg);
7.接受来自另外一个网络进程中的数据:
int kernel_recvmsg(struct socket *sock, struct msghdr *msg,
struct kvec *vec, size_t num, size_t size, int flags)
EXPORT_SYMBOL(kernel_recvmsg);
sock : 为接受进程的套接字;
msg : 用于存放接受到的数据;
vec : 用于指向本地进程中的缓存区;
num : 为数据块的块数;
size : 缓存区的大小;
flags: struct msghdr中的flags字段中的取值范围;
int kernel_recvmsg()的实现:
kernel_recvmsg()的用法:
8.关闭一个套接字:
void sock_release(struct socket *sock);
用于关闭一个套接字,而且若是一个它struct socket绑定到了一个struct
inode节点上的话,相应的struct inode也会被释放。
以上这些函数位于linux源代码包中的/net/socket.c之中。
本文出自 “阿辉仔” 博客,请务必保留此出处http://weiguozhihui.blog.51cto.com/3060615/1585297