TCP/IP卷二 mbuf

skywe000發表於2020-11-16

概述

核心中的儲存器管理排程直接關係到聯網協議的效能

網路協議對核心的儲存管理能力提出了很多要求。包括:

  1. 能方便地操作可變長快取
  2. 能在快取頭部和尾部新增資料(如底層封裝來自高層的資料)
  3. 能從快取中移去資料(如當資料分二組向上經過新協議棧時要去掉首部)
  4. 儘量減少為這些操作所作的資料複製。

mbuf的主要用途是

  1. 儲存在程式和網路介面間互相傳遞的使用者資料
  2. 儲存其他各種資料:源與目標地址、插口選項等等

四種型別的mbuf(依據成員m_flags填寫的標誌分類)

  1. m_flags=0,mbuf只含資料。mbuf中有108位元組的資料空間(m_dat陣列),m_data指向快取的起始,但它能指向快取中的任何位置。
  2. 第二類mbuf的m_flags值是M_PKTHDR,它指示這是一個分組首部,描述一個分組資料的第一個mbuf。資料仍然儲存在這個mbuf中,但是由於分組首部佔用了8位元組,只有100位元組的資料可儲存在這個mbuf中。
  3. mbuf不包含分組首部(沒有設定K_PKTHDR),但包含超過208位元組的資料,用到簇的外部快取(設定M_EXT)。在此mbuf中仍然為分組首部結構分配了空間,但沒有用。
  4. mbuf包含一個分組首部,幷包含超過208位元組的資料(同時設定了M_PKTHDR和M_EXT)

在這裡插入圖片描述

應用mbuf的例項
在這裡插入圖片描述

mbuf結構

結構mbuf是用一個m_hdr結構跟著一個聯合來定義的。

在這裡插入圖片描述

/* header at beginning of each mbuf: */
struct m_hdr {
	struct	mbuf *mh_next;		/* next buffer in chain */
	struct	mbuf *mh_nextpkt;	/* next chain in queue/record */
	int	mh_len;			/* amount of data in this mbuf */
	caddr_t	mh_data;		/* location of data */
	short	mh_type;		/* type of data in this mbuf */
	short	mh_flags;		/* flags; see below */
};

/* record/packet header in first mbuf of chain; valid if M_PKTHDR set */
struct	pkthdr {
	int	len;		/* total packet length */
	struct	ifnet *rcvif;	/* rcv interface */
};

/* description of external storage mapped into mbuf, valid if M_EXT set */
struct m_ext {
	caddr_t	ext_buf;		/* start of buffer */
	void	(*ext_free)();		/* free routine if not the usual */
	u_int	ext_size;		/* size of buffer, for ext_free */
};

struct mbuf {
	struct	m_hdr m_hdr;
	union {
		struct {
			struct	pkthdr MH_pkthdr;	/* M_PKTHDR set */
			union {
				struct	m_ext MH_ext;	/* M_EXT set */
				char	MH_databuf[MHLEN];
			} MH_dat;
		} MH;
		char	M_databuf[MLEN];		/* !M_PKTHDR, !M_EXT */
	} M_dat;
};

#define	m_next		m_hdr.mh_next
#define	m_len		m_hdr.mh_len
#define	m_data		m_hdr.mh_data
#define	m_type		m_hdr.mh_type
#define	m_flags		m_hdr.mh_flags
#define	m_nextpkt	m_hdr.mh_nextpkt
#define	m_act		m_nextpkt
#define	m_pkthdr	M_dat.MH.MH_pkthdr
#define	m_ext		M_dat.MH.MH_dat.MH_ext
#define	m_pktdat	M_dat.MH.MH_dat.MH_databuf
#define	m_dat		M_dat.M_databuf

指標m_next把mbuf連結成一個mbuf連結串列
指標m_nextpkt把mbuf連結串列連線成一個mbuf佇列。
指標m_data指向相應快取的開始(mbuf快取本身或一個簇)。這個指標能指向相應快取的任意位置,不一定是起始。
m_data指示儲存在mbuf中的資料的型別。mbuf可以儲存各種不同的資料結構。
在這裡插入圖片描述

m_flags值說明
在這裡插入圖片描述
m_len和分組首部中的成員m_pkthdr.len區別。m_pkthdr.len是連結串列中所有mbuf的成員m_len的總和。

mbuf巨集和函式

巨集——M開頭的大寫字母名稱
函式——m_開始的小寫字母名稱。
巨集在每個被用到的地方都被c前處理器展開(要求更多的程式碼空間),但是它在執行時更快。
函式在每個被呼叫的地方變成了一些指令,要求較少的程式碼空間,但會花費更多的執行時間。

m_get函式

這個函式僅僅是巨集MGET的展開

/*
 * Space allocation routines.
 * These are also available as macros
 * for critical paths.
 */
struct mbuf *
m_get(nowait, type)
	int nowait, type;//nowait引數的值為M_WAIT或M_DONTWAIT,取決於在儲存器不可用時是否要求等待
{
	register struct mbuf *m;

	MGET(m, nowait, type);
	return (m);
}

MGET巨集

呼叫MGET巨集來分配儲存

#define	MGET(m, how, type) { \
	MALLOC((m), struct mbuf *, MSIZE, mbtypes[type], (how)); \//核心巨集MALLOC,通用核心儲存器分配器進行的。mbtypes把mbuf的MT_XXX值轉換為相應的M_XXX的值
	if (m) { \
		(m)->m_type = (type); \
		MBUFLOCK(mbstat.m_mtypes[type]++;) \	
		(m)->m_next = (struct mbuf *)NULL; \
		(m)->m_nextpkt = (struct mbuf *)NULL; \
		(m)->m_data = (m)->m_dat; \
		(m)->m_flags = 0; \
	} else \
		(m) = m_retry((how), (type)); \
}


m_retry函式

struct mbuf *
m_retry(i, t)
	int i, t;
{
	register struct mbuf *m;

	m_reclaim();//每個協議都能定義一個"drain"函式,在系統缺乏可用儲存器時能被m_reclaim呼叫
#define m_retry(i, t)	(struct mbuf *)0 //m_retry被定義為一個空指標,這可以防止當儲存器仍然不可用時的無休止的迴圈
	MGET(m, i, t);	//m_reclaim後可能有機會獲得更多的儲存器,因此再次呼叫MGET,試圖獲得mbuf
#undef m_retry
	return (m);
}

mbuf鎖

呼叫MBUFLOCK來保護函式和巨集不被終端。由於mbuf在核心的所有層中被分配和釋放,因此核心必須保護那些用於儲存器分配的資料結構。
因為儲存器分配與釋放及簇分配與釋放的巨集被保護起來防止被終端,通常在MGET和m_get這樣的函式和巨集的前後不再呼叫spl函式

/*
 * mbuf utility macros:
 *
 *	MBUFLOCK(code)
 * prevents a section of code from from being interrupted by network
 * drivers.
 */
#define	MBUFLOCK(code) \
	{ int ms = splimp(); \
	  { code } \
	  splx(ms); \
	}

m_devget函式

m_devget,當接收2到一個乙太網幀時,裝置被驅動程式呼叫函式m_devget來建立一個mbuf連結串列,並把裝置中的幀複製到這個連結串列中。根據所接受到的幀的長度(不包括乙太網首部),可能導致4中不同的mbuf連結串列。

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

第一種的mbuf用於資料的長度在0-84位元組之間的情況。在mbuf的開始保留了16位元組。雖然14位元組的乙太網首部不存放在這裡,但還是分配了一個14位元組的用於輸出的乙太網首部。16位元組而不是14位元組的原因是為了在mbuf中庸長字對準方式儲存IP首部
第二種的mbuf用於資料的長度在85-100位元組之間。無16位元組的保留空間。資料儲存在陣列m_pktdat的開始。
第三種mbuf用於資料的長度在101-207位元組之間,要求有兩個mbuf。前100個位元組放在第一個mbuf中(有分組首部的mbuf),剩下的存放在第二個mbuf中。
第四種mbuf用於資料的長度超過或等於208位元組,要用一個或多個簇的場景。

mtod和dtom巨集

// * mtod(m,t) -	convert mbuf pointer to data pointer of correct type
// * dtom(x) -	convert data pointer within mbuf to mbuf pointer (XXX)
#define mtod(m,t)	((t)((m)->m_data))
#define	dtom(x)		((struct mbuf *)((int)(x) & ~(MSIZE-1)))//MSIZE=128

mtod(“mbuf到資料”)返回一個只想mbuf資料的指標,並把指標宣告為制定型別。
dtom(“資料到mbuf”)取得一個存放在一個mbuf中任意位置的資料的指標。並返回這個mbuf結構本身的一個指標。
MSIZE(128)是2的冪,核心儲存器分配器總是為mbuf分配連續的MSIZE位元組的儲存快,dtom僅僅是清除引數中指標的低位來發現這個mbuf的起始位置。
dtom有一個問題,當它的引數指向一個簇,或者在一個簇內,它不能正確執行。因為那裡沒有指標從簇內指回mbuf結構。

m_pullup函式

m_pullup函式用來保證指定數目的位元組(相應的協議首部的大小)在連結串列的第一個mbuf中緊挨著存放。即這些指定書目的位元組被複制到一個新的mbuf並緊挨著存放。
m_pullup兩個目的:1.當一個協議發現在第一個mbuf的資料量小於協議首部的最小長度時。呼叫m_pullup時基於假定協議首部的剩餘部分存放在連結串列的下一個mbuf。m_pullup重新安排mbuf連結串列,使得前N位元組的資料被連續地存放在連結串列的第一個mbuf中。
N是這個函式的一個引數必須<=100(MHLEN)
2.第二個用途涉及到IP和TCP的重組。
IP分片演算法將各分片都存放在一個雙向連結串列中,使用IP首部中的源與目標IP地址來存放向前與向後連結串列指標。如果IP首部在一個簇中,這些連結串列指標也會存放在這個簇中,導致以後遍歷連結串列時,指向IP首部的指標不能被轉換成指向mbuf的指標。
TCP報文段重組使用不同的技術而不是呼叫m_pullup。因為m_pullup開銷較大:分配儲存器並且資料從一個簇複製到一個mbuf中。TCP試圖儘可能地避免資料的複製。除非TCP報文段被IP分片,否則不呼叫m_pullup。
TCP把mbuf指標存放在TCP首部中的一些未用的欄位中,提供一個從簇指回mbuf的指標,來避免對每個失序的報文段呼叫m_pullup。

struct mbuf *
m_pullup(n, len)
	register struct mbuf *n;
	int len;
{
	register struct mbuf *m;
	register int count;
	int space;

	/*
	 * If first mbuf has no cluster, and has room for len bytes
	 * without shifting current data, pullup into it,
	 * otherwise allocate a new mbuf to prepend to the chain.
	 */
	if ((n->m_flags & M_EXT) == 0 &&
	    n->m_data + len < &n->m_dat[MLEN] && n->m_next) {
		if (n->m_len >= len)
			return (n);
		m = n;
		n = n->m_next;
		len -= m->m_len;
	} else {
		if (len > MHLEN)
			goto bad;
		MGET(m, M_DONTWAIT, n->m_type);
		if (m == 0)
			goto bad;
		m->m_len = 0;
		if (n->m_flags & M_PKTHDR) {
			M_COPY_PKTHDR(m, n);
			n->m_flags &= ~M_PKTHDR;
		}
	}
	space = &m->m_dat[MLEN] - (m->m_data + m->m_len);
	do {
		count = min(min(max(len, max_protohdr), space), n->m_len);
		bcopy(mtod(n, caddr_t), mtod(m, caddr_t) + m->m_len,
		  (unsigned)count);
		len -= count;
		m->m_len += count;
		n->m_len -= count;
		space -= count;
		if (n->m_len)
			n->m_data += count;
		else
			n = m_free(n);
	} while (len > 0 && n);
	if (len > 0) {
		(void) m_free(m);
		goto bad;
	}
	m->m_next = n;
	return (m);
bad:
	m_freem(n);
	MPFail++;
	return (0);
}

在這裡插入圖片描述
在這裡插入圖片描述

m_copy和簇引用計數

簇的使用好處是多個mbuf間可以共享一個簇。由一個mbuf指向的簇(外部快取)能通過m_copy被共享。例如,用於TCP輸出,因為一個被傳輸的資料的副本要被髮送端儲存,直到資料被對方確認。

總結

mbuf的主要用途是在程式和網路介面之間傳遞使用者資料時用來存放使用者資料,但mbuf還用於儲存其他各種資料:源地址和目的地址、插口選項等等。
mbuf幾乎貫穿了協議棧的所有的函式,足見其重要性。
正確理解mbuf各種巨集操作和函式呼叫極為關鍵,因為在後續的各種函式中,將會經常遇到

相關文章