轉載
原文地址:https://blog.csdn.net/liuxingen/article/details/44995467
內核中的socket概覽
圖一:socket概覽
內核中套接字是一層一層進行抽象展示的,把共性的東西抽取出來,這樣對外提供的接口可以盡量的統一。內核中把套接字的定義會抽象出來展示,如struct sock->struct inet_sock->struct tcp_sock從抽象到具體。還會把套接字的操作也會抽象,下面我們會提到怎么進行抽象展示的。
Socket函數中的三個參數其實就是把抽象的socket具體化的條件,domain參數決定了圖中所示的第二層通信域,type決定了第三層的通信模式,protocol決定了第四層真正的通信協議。
Domain參數
Domain參數指定了通信的”域”(在后文中會用family替代domain),我們是在IPv4還是IPv6這個范圍內通信,也就決定了我們通信的地址是IPv4格式還是IPv6格式。通常可選的定義如下:
名稱
目的
AF_UNIX, AF_LOCAL
本地通信
AF_INET
IPv4網絡通信
AF_INET6
IPv6網絡通信
AF_PACKET
鏈路層通信
在Linux系統中AF_*和PF_*是等價的。
在內核源碼中net目錄下面有Af_開頭的一系列文件(如:Af_inet.c、Af_inet6.c、Af_unix.c等),每一個文件分別代表了一種協議族。
在Net.h中定義了一個結構體:
//net_proto_family結構體定義了每一個協議族的新建socket句柄
struct net_proto_family {
int family;
int (*create)(struct net *net, struct socket *sock,
int protocol, int kern);
struct module *owner;
};
//Af_inet.c中的PF_INET domain的定義
static const struct net_proto_family inet_family_ops = {
.family = PF_INET,
.create = inet_create,
.owner = THIS_MODULE,
};
在協議棧初始化的通過sock_register(const struct net_proto_family *ops)(socket.c)函數把協議棧支持的協議族family加入net_families數組中。
當我們通過socket系統調用創建套接字的時候流程走到__sock_create函數(SYSCALL_DEFINE3->sock_create->__sock_create)的時候根據family在net_families數組中取得對應協議族的create句柄,所以對於PF_INET協議族的套接字就是調用inet_create來新建socket。
通過上面分析可知family這個參數決定了調用哪個協議族create函數來新建socket,說得可能不准確點就是決定了你使用net/目錄下面的哪個Af_*.c文件中的函數。
Type參數
Type就是socket的類型,對於AF_INET協議族而言有流套接字(SOCK_STREAM)、數據包套接字(SOCK_DGRAM)、原始套接字(SOCK_RAW)。
/**
* enum sock_type - Socket types
* @SOCK_STREAM: stream (connection) socket
* @SOCK_DGRAM: datagram (conn.less) socket
* @SOCK_RAW: raw socket
* @SOCK_RDM: reliably-delivered message
* @SOCK_SEQPACKET: sequential packet socket
* @SOCK_DCCP: Datagram Congestion Control Protocol socket
* @SOCK_PACKET: linux specific way of getting packets at the dev level.
* For writing rarp and other similar things on the user level.
*/
enum sock_type {
SOCK_STREAM = 1,
SOCK_DGRAM = 2,
SOCK_RAW = 3,
SOCK_RDM = 4,
SOCK_SEQPACKET = 5,
SOCK_DCCP = 6,
SOCK_PACKET = 10,
};
在內核協議棧中有一個很重要的結構體,定義了每一個協議族中套接字在傳輸層的操作集合。PS:這里我理解傳輸層包括了套接字類型(STREAM,DGRAM)和具體的傳輸層協議(TCP,UDP)
/* This is used to register socket interfaces for IP protocols. */
struct inet_protosw {
struct list_head list;
/* These two fields form the lookup key. */
unsigned short type; /* This is the 2nd argument to socket(2). */
unsigned short protocol; /* This is the L4 protocol number. */
struct proto *prot;
const struct proto_ops *ops;
unsigned char flags; /* See INET_PROTOSW_* below. */
};
struct proto_ops結構體定義了每一種套接字類型 (SOCK_STREAM、SOCK_DGRAM、SOCK_RAW)的操作集合,在Af_inet.c中分別定義了inet_stream_ops、inet_dgram_ops、inet_sockraw_ops這三種類型的proto_ops:
const struct proto_ops inet_stream_ops = {
.family = PF_INET,
.owner = THIS_MODULE,
.release = inet_release,
.bind = inet_bind,
.connect = inet_stream_connect,
.socketpair = sock_no_socketpair,
.accept = inet_accept,
.getname = inet_getname,
.poll = tcp_poll,
.ioctl = inet_ioctl,
.listen = inet_listen,
.shutdown = inet_shutdown,
.setsockopt = sock_common_setsockopt,
.getsockopt = sock_common_getsockopt,
.sendmsg = inet_sendmsg,
.recvmsg = inet_recvmsg,
.mmap = sock_no_mmap,
.sendpage = inet_sendpage,
.splice_read = tcp_splice_read,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_sock_common_setsockopt,
.compat_getsockopt = compat_sock_common_getsockopt,
.compat_ioctl = inet_compat_ioctl,
#endif
};
我們新建socket的時候指定了type為SOCK_STREAM那么在后面的套接字操作中(如connect)會調用對應的inet_stream_ops中對應的函數(如inet_stream_connect)。
圖2:inetsw、inet_protosw、proto_ops、proto的關系圖
Protocol參數
上文中我們通過family和type已經基本確定了新建的socket具體是什么類型的套接字,最后一步通過protocol來確定socket到底支持的哪個協議(TCP?UDP?)。
在inet_protosw結構體中我們已經解釋過proto_ops對應的是每一種套接字類型的操作集合,那么可知struct proto對應的就是具體協議的操作集合。在Tcp_ipv4.c中定義TCP協議的struct proto tcp_prot:
struct proto tcp_prot = {
.name = "TCP",
.owner = THIS_MODULE,
.close = tcp_close,
.connect = tcp_v4_connect,
.disconnect = tcp_disconnect,
.accept = inet_csk_accept,
.ioctl = tcp_ioctl,
.init = tcp_v4_init_sock,
.destroy = tcp_v4_destroy_sock,
.shutdown = tcp_shutdown,
.setsockopt = tcp_setsockopt,
.getsockopt = tcp_getsockopt,
.recvmsg = tcp_recvmsg,
.sendmsg = tcp_sendmsg,
.sendpage = tcp_sendpage,
.backlog_rcv = tcp_v4_do_rcv,
.release_cb = tcp_release_cb,
.hash = inet_hash,
.unhash = inet_unhash,
.get_port = inet_csk_get_port,
.enter_memory_pressure = tcp_enter_memory_pressure,
.stream_memory_free = tcp_stream_memory_free,
.sockets_allocated = &tcp_sockets_allocated,
.orphan_count = &tcp_orphan_count,
.memory_allocated = &tcp_memory_allocated,
.memory_pressure = &tcp_memory_pressure,
.sysctl_mem = sysctl_tcp_mem,
.sysctl_wmem = sysctl_tcp_wmem,
.sysctl_rmem = sysctl_tcp_rmem,
.max_header = MAX_TCP_HEADER,
.obj_size = sizeof(struct tcp_sock),
.slab_flags = SLAB_DESTROY_BY_RCU,
.twsk_prot = &tcp_timewait_sock_ops,
.rsk_prot = &tcp_request_sock_ops,
.h.hashinfo = &tcp_hashinfo,
.no_autobind = true,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_tcp_setsockopt,
.compat_getsockopt = compat_tcp_getsockopt,
#endif
#ifdef CONFIG_MEMCG_KMEM
.init_cgroup = tcp_init_cgroup,
.destroy_cgroup = tcp_destroy_cgroup,
.proto_cgroup = tcp_proto_cgroup,
#endif
};
所以對於TCP socket當執行connect連接的時候經過的流程大致是SYSCALL_DEFINE3 connect(系統調用)->inet_stream_connect(inet_stream_ops中定義)-> tcp_v4_connect(tcp_prot中定義)。
Family&type&protocol結合
上面分別說明了family、type、protocol這三個參數代表的意義,接下來我們把這三個參數結合起來一起看一下最后的效果。
struct inet_protosw結構體在內核中是如何初始化的呢?
/* Upon startup we insert all the elements in inetsw_array[] into
* the linked list inetsw.
*/
static struct inet_protosw inetsw_array[] =
{
{
.type = SOCK_STREAM,
.protocol = IPPROTO_TCP,
.prot = &tcp_prot,
.ops = &inet_stream_ops,
.flags = INET_PROTOSW_PERMANENT |
INET_PROTOSW_ICSK,
},
{
.type = SOCK_DGRAM,
.protocol = IPPROTO_UDP,
.prot = &udp_prot,
.ops = &inet_dgram_ops,
.flags = INET_PROTOSW_PERMANENT,
},
{
.type = SOCK_DGRAM,
.protocol = IPPROTO_ICMP,
.prot = &ping_prot,
.ops = &inet_dgram_ops,
.flags = INET_PROTOSW_REUSE,
},
{
.type = SOCK_RAW,
.protocol = IPPROTO_IP, /* wild card */
.prot = &raw_prot,
.ops = &inet_sockraw_ops,
.flags = INET_PROTOSW_REUSE,
}
};
在Af_inet.c中定義了PF_INET協議族的四個初始化的inet_protosw結構體,在內核協議棧初始化的時候通過inet_register_protosw函數將這些結構體按照類型(type)hash到全局的inetsw數組中。
圖3:socket調用流程
上面是socket系統調用的一個主要的流程圖,左半部分在前文中已經提到過了。當流程走到inet_create函數的時候根據type去inetsw數組中找到對應類型套接字的inet_protosw結構體,我們前面提到協議棧中已經定義了PF_INET協議族支持的inet_protosw結構體,總共有4個。
找到inet_protosw結構體以后還需要進一步判斷protocol和inet_protosw中定義的protocol是否是一致的。內核中定義支持的protocol有一個特殊的值IPPROTO_IP(IPPROTO_IP為0),可以理解為一個通配符也可以理解為一個默認值,就是說我不指定protocol,由內核自己決定使用哪一個protocol。
那么內核根據什么來選擇protocol呢?就是根據內核定義的全局inetsw中對應類型的inet_protosw中的protocol。
說起來可能比較拗口,直接看一下代碼就很清楚了。
list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {
err = 0;
/* Check the non-wild match. */
if (protocol == answer->protocol) {
if (protocol != IPPROTO_IP)
break;
} else {
/* Check for the two wild cases. */
if (IPPROTO_IP == protocol) {
protocol = answer->protocol;
break;
}
if (IPPROTO_IP == answer->protocol)
break;
}
err = -EPROTONOSUPPORT;
}
所以如果我們在新建套接字的時候使用socket(PF_INET,SOCK_STREAM,0),那么內核就會默認給你把protocol修正為IPPROTO_TCP。
好吧,其實整篇文章我就是想搞清楚這最后的一句話。
---------------------
作者:NoneSec
來源:CSDN
原文:https://blog.csdn.net/liuxingen/article/details/44995467
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!