socket和send兩個系統調用爲例,協議棧是如何工作
首先以socket和send兩個系統調用爲例,來回顧一下協議棧是如何工作的,在這過程中可以找到如何在協議棧中增加對UDP協議的支持。socket系統調用的原型是
int socket(int domain, int type, int protocol);
domain是協議域,對於ipv4協議來說,其值是PF_INET(ipv4因特網協議),對於我們自己實現的ipv4協議模塊,我們爲其新增MY_PF_INET。所有的協議域在include/linux/socket.h被定義,如下:
#define AF_UNSPEC 0
#define AF_UNIX 1 // Unix域的socket
#define AF_LOCAL 1 // AF_UNIX的POSIX命名
#define AF_INET 2 // 因特網IP協議
#define AF_AX25 3 // Amateur Radio AX.25
#define AF_IPX 4 // Novell IPX
#define AF_APPLETALK 5 // AppleTalk DDP
#define AF_NETROM 6 // Amateur Radio NET/ROM
#define AF_BRIDGE 7 // Multiprotocol bridge
#define AF_ATMPVC 8 // ATM PVCs
#define AF_X25 9 // Reserved for X.25 project
#define AF_INET6 10 // IP version 6
#define AF_ROSE 11 // Amateur Radio X.25 PLP
#define AF_DECnet 12 // Reserved for DECnet project
#define AF_NETBEUI 13 // Reserved for 802.2LLC project
#define AF_SECURITY 14 // Security callback pseudo AF
#define AF_KEY 15 // PF_KEY key management API
#define AF_NETLINK 16
#define AF_ROUTE AF_NETLINK // Alias to emulate 4.4BSD
#define AF_PACKET 17 // Packet family
#define AF_ASH 18 // Ash
#define AF_ECONET 19 // Acorn Econet
#define AF_ATMSVC 20 // ATM SVCs
#define AF_SNA 22 // Linux SNA Project (nutters!)
#define AF_IRDA 23 // IRDA sockets
#define AF_PPPOX 24 // PPPoX sockets
#define AF_WANPIPE 25 // Wanpipe API Sockets
#define AF_LLC 26 // Linux LLC
#define AF_BLUETOOTH 31 // Bluetooth sockets
#define AF_MAX 32 // For now..
可以看到,當前,內核最多支持31個協議域(0爲未指定,32爲MAX)。而當前的定義中還有27,28,30爲空,所以我們定義了MY_PF_INET爲28。
在內核中,結構體struct net_proto_family用於表示一個協議域,而全局數組變量static struct net_proto_family *net_families[NPROTO]是一個有32項的數組,用於保存當前內核中所有已註冊的協議域,函數sock_register用於把一個協議域註冊到內核中,即把一個協議域跟net_families數組
中的某一項相關聯。struct net_proto_family的完整定義如下:
struct net_proto_family {
int family;
int (*create)(struct socket *sock, int protocol);
short authentication;
short encryption;
short encrypt_net;
struct module *owner;
};
其中,family爲域編號,對於我們的模塊即爲MY_PF_INET。通過sock_register函數,使net_families[MY_PF_INET]指向需要註冊的域。create是該域的socket的創建函數,我們的MY_PF_INET域定義如下:
static struct net_proto_family myinet_family_ops = {
.family = MY_PF_INET,
.create = myinet_create,
.owner = THIS_MODULE,
};
現在回到socket系統調用上來,內核實現socket系統調用的函數是sys_socket。該函數通過調用sock_create進行創建,sock_create調用__sock_create。__sock_create要創建一個struct socket,這是一個普通BSD socket的結構體,其定義如下:
struct socket {
socket_state state;
unsigned long flags;
struct proto_ops *ops;
struct fasync_struct *fasync_list;
struct file *file;
struct sock *sk;
wait_queue_head_t wait;
short type;
};
__sock_create創建的時候,爲其type賦上socket系統調用的第二個參數type,最後通過調用net_families[family]->create(sock, protocol)完成socket的創建。對於MY_PF_INET域來說,該create函數即myinet_create。MY_PF_INET域支持的網絡層協議是IP協議,在該協議上支持的套接字接口有流套接字(SOCK_STREAM),數據報套接字(SOCK_DGRAM)和原始套接字(SOCK_RAW)。在IP協議上註冊一個套接字接口,也即創建一個套接字,需要知道該類型的套接字必需的一些相關信息。結構體struct inet_protosw就是用於在IP協議上註冊套接字接口,其完整定義如下:
struct inet_protosw {
struct list_head list;
unsigned short type; //套接字類型,即socket系統調用的第二個參數。
int protocol; //第4層(傳輸層)協議號
struct proto *prot; //第4層協議的操作函數集
struct proto_ops *ops; //該類型的套接字的操作函數集
int capability;
char no_check;
unsigned char flags;
};
myinet_create函數註冊套接字的過程本質上就是爲指定套接字類型和第4層協議號的一個socket找到對應的操作函數集,使這個socket隨後能真正被操作。全局數組inetsw_array包含了系統當前支持的所有在IP協議上能夠註冊的套接字接口,在系統初始化的時候,這些結構體以type作爲依據,被組織到
static struct list_head inetsw[SOCK_MAX]中。當在inetsw數組中找到對應的socket類型和第4層協議號後,令struct socket->ops的值爲struct inet_protosw->ops,即爲該類型的套接字指定操作函數集。而struct socket->sk是網絡層的套接字接口,其成員sk_prot的值爲struct inet_protosw->prot,即爲該類型的第4層協議指定操作函數集。套接字的創建工作大致如此。
接下來,再來看send系統調用,它的原型如下:
ssize_t send(int s, const void *buf, size_t len, int flags);
s是文件描述符,在內核中跟一個struct socket結構體建立一一對應的映射關係。buf和len分別爲待發送數據的內容和長度,flag是一些標誌位。內核實現該系統調用的函數是sys_send。sys_send直接調用sys_sendto,把sys_sendto的最後兩個參數addr和addr_len置空。sys_sendto根據文件描述符s找到對應的struct socket,然後建立一個結構體struct msghdr msg用於發送數據內容,該結構體的定義如下:
struct msghdr {
void * msg_name; /* Socket 的名字 */
int msg_namelen; /* 名字的長度 */
struct iovec * msg_iov; /* 數據塊 */
__kernel_size_t msg_iovlen; /* 數據塊的數量 */
void * msg_control; /* Per protocol magic (eg BSD file descriptor passing) */
__kernel_size_t msg_controllen; /* Length of cmsg list */
unsigned msg_flags;
};
然後,sys_sendto調用sock_sendmsg發送數據,sock_sendmsg調用__sock_sendmsg,__sock_sendmsg調用struct socket->ops->sendmsg,即調用特定套接字類型的操作函數集中的sendmsg成員函數。比如,SOCK_RAW類型的套接字的sendmsg成員函數的實現如下(實際上SOCK_DGRAM類型的套接字的sendmsg成員函數也是這個):
int inet_sendmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size)
{
struct sock *sk = sock->sk;
if (!inet_sk(sk)->num && inet_autobind(sk))
return -EAGAIN;
return sk->sk_prot->sendmsg(iocb, sk, msg, size);
}
可以看到,在該函數中,調用了具體的第4層協議的操作函數集中的sendmsg成員函數,而該函數真正實現了對應協議的數據報文發送工作。
int socket(int domain, int type, int protocol);
domain是協議域,對於ipv4協議來說,其值是PF_INET(ipv4因特網協議),對於我們自己實現的ipv4協議模塊,我們爲其新增MY_PF_INET。所有的協議域在include/linux/socket.h被定義,如下:
#define AF_UNSPEC 0
#define AF_UNIX 1 // Unix域的socket
#define AF_LOCAL 1 // AF_UNIX的POSIX命名
#define AF_INET 2 // 因特網IP協議
#define AF_AX25 3 // Amateur Radio AX.25
#define AF_IPX 4 // Novell IPX
#define AF_APPLETALK 5 // AppleTalk DDP
#define AF_NETROM 6 // Amateur Radio NET/ROM
#define AF_BRIDGE 7 // Multiprotocol bridge
#define AF_ATMPVC 8 // ATM PVCs
#define AF_X25 9 // Reserved for X.25 project
#define AF_INET6 10 // IP version 6
#define AF_ROSE 11 // Amateur Radio X.25 PLP
#define AF_DECnet 12 // Reserved for DECnet project
#define AF_NETBEUI 13 // Reserved for 802.2LLC project
#define AF_SECURITY 14 // Security callback pseudo AF
#define AF_KEY 15 // PF_KEY key management API
#define AF_NETLINK 16
#define AF_ROUTE AF_NETLINK // Alias to emulate 4.4BSD
#define AF_PACKET 17 // Packet family
#define AF_ASH 18 // Ash
#define AF_ECONET 19 // Acorn Econet
#define AF_ATMSVC 20 // ATM SVCs
#define AF_SNA 22 // Linux SNA Project (nutters!)
#define AF_IRDA 23 // IRDA sockets
#define AF_PPPOX 24 // PPPoX sockets
#define AF_WANPIPE 25 // Wanpipe API Sockets
#define AF_LLC 26 // Linux LLC
#define AF_BLUETOOTH 31 // Bluetooth sockets
#define AF_MAX 32 // For now..
可以看到,當前,內核最多支持31個協議域(0爲未指定,32爲MAX)。而當前的定義中還有27,28,30爲空,所以我們定義了MY_PF_INET爲28。
在內核中,結構體struct net_proto_family用於表示一個協議域,而全局數組變量static struct net_proto_family *net_families[NPROTO]是一個有32項的數組,用於保存當前內核中所有已註冊的協議域,函數sock_register用於把一個協議域註冊到內核中,即把一個協議域跟net_families數組
中的某一項相關聯。struct net_proto_family的完整定義如下:
struct net_proto_family {
int family;
int (*create)(struct socket *sock, int protocol);
short authentication;
short encryption;
short encrypt_net;
struct module *owner;
};
其中,family爲域編號,對於我們的模塊即爲MY_PF_INET。通過sock_register函數,使net_families[MY_PF_INET]指向需要註冊的域。create是該域的socket的創建函數,我們的MY_PF_INET域定義如下:
static struct net_proto_family myinet_family_ops = {
.family = MY_PF_INET,
.create = myinet_create,
.owner = THIS_MODULE,
};
現在回到socket系統調用上來,內核實現socket系統調用的函數是sys_socket。該函數通過調用sock_create進行創建,sock_create調用__sock_create。__sock_create要創建一個struct socket,這是一個普通BSD socket的結構體,其定義如下:
struct socket {
socket_state state;
unsigned long flags;
struct proto_ops *ops;
struct fasync_struct *fasync_list;
struct file *file;
struct sock *sk;
wait_queue_head_t wait;
short type;
};
__sock_create創建的時候,爲其type賦上socket系統調用的第二個參數type,最後通過調用net_families[family]->create(sock, protocol)完成socket的創建。對於MY_PF_INET域來說,該create函數即myinet_create。MY_PF_INET域支持的網絡層協議是IP協議,在該協議上支持的套接字接口有流套接字(SOCK_STREAM),數據報套接字(SOCK_DGRAM)和原始套接字(SOCK_RAW)。在IP協議上註冊一個套接字接口,也即創建一個套接字,需要知道該類型的套接字必需的一些相關信息。結構體struct inet_protosw就是用於在IP協議上註冊套接字接口,其完整定義如下:
struct inet_protosw {
struct list_head list;
unsigned short type; //套接字類型,即socket系統調用的第二個參數。
int protocol; //第4層(傳輸層)協議號
struct proto *prot; //第4層協議的操作函數集
struct proto_ops *ops; //該類型的套接字的操作函數集
int capability;
char no_check;
unsigned char flags;
};
myinet_create函數註冊套接字的過程本質上就是爲指定套接字類型和第4層協議號的一個socket找到對應的操作函數集,使這個socket隨後能真正被操作。全局數組inetsw_array包含了系統當前支持的所有在IP協議上能夠註冊的套接字接口,在系統初始化的時候,這些結構體以type作爲依據,被組織到
static struct list_head inetsw[SOCK_MAX]中。當在inetsw數組中找到對應的socket類型和第4層協議號後,令struct socket->ops的值爲struct inet_protosw->ops,即爲該類型的套接字指定操作函數集。而struct socket->sk是網絡層的套接字接口,其成員sk_prot的值爲struct inet_protosw->prot,即爲該類型的第4層協議指定操作函數集。套接字的創建工作大致如此。
接下來,再來看send系統調用,它的原型如下:
ssize_t send(int s, const void *buf, size_t len, int flags);
s是文件描述符,在內核中跟一個struct socket結構體建立一一對應的映射關係。buf和len分別爲待發送數據的內容和長度,flag是一些標誌位。內核實現該系統調用的函數是sys_send。sys_send直接調用sys_sendto,把sys_sendto的最後兩個參數addr和addr_len置空。sys_sendto根據文件描述符s找到對應的struct socket,然後建立一個結構體struct msghdr msg用於發送數據內容,該結構體的定義如下:
struct msghdr {
void * msg_name; /* Socket 的名字 */
int msg_namelen; /* 名字的長度 */
struct iovec * msg_iov; /* 數據塊 */
__kernel_size_t msg_iovlen; /* 數據塊的數量 */
void * msg_control; /* Per protocol magic (eg BSD file descriptor passing) */
__kernel_size_t msg_controllen; /* Length of cmsg list */
unsigned msg_flags;
};
然後,sys_sendto調用sock_sendmsg發送數據,sock_sendmsg調用__sock_sendmsg,__sock_sendmsg調用struct socket->ops->sendmsg,即調用特定套接字類型的操作函數集中的sendmsg成員函數。比如,SOCK_RAW類型的套接字的sendmsg成員函數的實現如下(實際上SOCK_DGRAM類型的套接字的sendmsg成員函數也是這個):
int inet_sendmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size)
{
struct sock *sk = sock->sk;
if (!inet_sk(sk)->num && inet_autobind(sk))
return -EAGAIN;
return sk->sk_prot->sendmsg(iocb, sk, msg, size);
}
可以看到,在該函數中,調用了具體的第4層協議的操作函數集中的sendmsg成員函數,而該函數真正實現了對應協議的數據報文發送工作。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.