Linux TCP/IP協議棧剖析【體系結構篇】

最近在看linux內核代碼,學習之餘記點筆記。但是,目前整個linux源碼文件大小是360M,相當於100個哈利波特全集-_-,看完的都是神仙了。所以我只準備粗略地看下它的TCP/IP協議棧,然後記點心得。

嗯,我研究的內核版本是目前最新的,linux-2.6.33.1,其實應該都大同小異。學習源碼時,首先應該把它的整體脈絡理清楚,然後再局部細看,這是最簡捷的。本文也是簡潔的歸納一下。

在linux下,socket都掛接到虛擬文件系統(VFS)上,所以可以把它當做文件來操作,如write()與read()都可以對它使用。

當我們調用fd = socket()操作時,傳遞過程從上到下如下所示:

VFS層:file

運輸層:socket

網絡層:sock

嗯,相信大家都知道了吧,VFS層上面就是應用層了,每本計算機網絡相關書籍總是會先講這幾個層級結構。其中file, socket, sock是對應層次的一個內核結構,file涉及到虛擬文件系統,所以不予剖析了。

我們先看看socket結構的定義:

struct socket {
	socket_state		state;

	kmemcheck_bitfield_begin(type);
	short			type;
	kmemcheck_bitfield_end(type);

	unsigned long		flags;
	/*
	 * Please keep fasync_list & wait fields in the same cache line
	 */
	struct fasync_struct	*fasync_list;
	wait_queue_head_t	wait;

	struct file		*file;
	struct sock		*sk;
	const struct proto_ops	*ops;
};

我們只關心最後3個結構成員,file就是對應上面的VFS層,sk則是下面的網絡層,而ops則指向一個函數指針集合,對應於該運輸層的操作,實際上它可指向3個集合:inet_stream_ops,  inet_dgram_ops,  inet_sockraw_ops。還記得我們創建socket時需要設定的type參數嗎,SOCK_STREAM,SOCK_DGRAM,SOCK_RAW,相應的參數使套接字在初始化時讓ops指向對應的函數指針集合。

下面就是這個函數指針集合的定義:

struct proto_ops {
	int		family;
	struct module	*owner;
	int		(*release)   (struct socket *sock);
	int		(*bind)	     (struct socket *sock,
				      struct sockaddr *myaddr,
				      int sockaddr_len);
	int		(*connect)   (struct socket *sock,
				      struct sockaddr *vaddr,
				      int sockaddr_len, int flags);
	int		(*accept)    (struct socket *sock,
				      struct socket *newsock, int flags);
	unsigned int	(*poll)	     (struct file *file, struct socket *sock,
				      struct poll_table_struct *wait);
	int		(*ioctl)     (struct socket *sock, unsigned int cmd,
				      unsigned long arg);
	int		(*listen)    (struct socket *sock, int len);
	int		(*shutdown)  (struct socket *sock, int flags);
	int		(*setsockopt)(struct socket *sock, int level,
				      int optname, char __user *optval, unsigned int optlen);
	int		(*getsockopt)(struct socket *sock, int level,
				      int optname, char __user *optval, int __user *optlen);
	int		(*sendmsg)   (struct kiocb *iocb, struct socket *sock,
				      struct msghdr *m, size_t total_len);
	int		(*recvmsg)   (struct kiocb *iocb, struct socket *sock,
				      struct msghdr *m, size_t total_len,
				      int flags);
        。。。。。。。
};

是不是都似曾相識呢,對,上面的都是套接字的常用操作。

最後,我們再看看網絡層,sock結構,只列舉了我覺得有用的成員,這個結構很長。

struct sock {

	struct sock_common	__sk_common;


	struct sk_buff_head	sk_receive_queue;
	struct sk_buff_head	sk_write_queue;


	struct socket		*sk_socket;
	void			*sk_user_data;
	struct sk_buff		*sk_send_head;


	void			(*sk_state_change)(struct sock *sk);
	void			(*sk_data_ready)(struct sock *sk, int bytes);
	void			(*sk_write_space)(struct sock *sk);
	void			(*sk_error_report)(struct sock *sk);
  	int			(*sk_backlog_rcv)(struct sock *sk,
						  struct sk_buff *skb);  
	void                    (*sk_destruct)(struct sock *sk);
};

加下劃線的兩個函數指針,用於與更底層交互,當網卡有數據可讀可寫時用於回調。繼續鑽下去的話還會很深。最重要的是,它的頭部是一個sock_common 結構。嗯,這相當於一個面向對象的設計,sock_common就類似是一個基類,通過強制轉換可以轉化。還要關注下sk_buff_head結構,他是socket內部使用的緩衝區,鏈表形式,以後我再寫下它。


sock_common結構定義如下:同樣只是簡單列舉一些重要的。

struct sock_common {
	union {
		struct hlist_node	skc_node;
		struct hlist_nulls_node skc_nulls_node;
	};
	atomic_t		skc_refcnt;
	int			skc_tx_queue_mapping;

	unsigned short		skc_family;
	volatile unsigned char	skc_state;
	unsigned char		skc_reuse;
	int			skc_bound_dev_if;

	struct proto		*skc_prot;

};
注意skc_prot也指向一個函數指針集合,對應於網絡層的各種操作。但是據我觀察,按理說tcp_recv應該在socket層調用,而實際上該tcp_recv卻定義在sock層,socket層只是簡單跳轉一下,由skc_prot調用,我想可能是爲了方便tcp協議的實現吧。

 

好了,大概結構就是這樣的吧。。可以看到,就算是C語言,也運用了很多面向對象的思想。還有很抱歉,我本來準備畫圖說明的,結果還是惰性使然,就沒畫了。本來體系結構就是個很模糊的東西,關於數據包的接收與傳送,以及他們在這體系結構間的傳遞,下篇再講解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章