DPDK RSS 基礎

1 rss 的作用

rss 是網卡提供的分流機制。用來將報表分流到不同的收包隊列,以提高收包性能。

引用 Intel 82599 10 GbE Controller Datasheet 其中的 Section 7.1.2.8.1, RSS Hash Function 一節。

  1. The receive packet is parsed into the header fields used by the hash operation (such as IP addresses, TCP port, etc.)
  2. A hash calculation is performed.
    The 82599 supports a single hash function, as defined by MSFT RSS.
    The 82599 therefore does not indicate to the device driver which hash function is used.
    The 32-bit result is fed into the packet receive descriptor.
  3. The seven LSBs of the hash result are used as an index into a 128-entry redirection table.
    Each entry provides a 4-bit RSS output index.

When RSS is enabled, the 82599 provides software with the following information as:

  1. Required by Microsoft (MSFT) RSS
  2. Provided for device driver assist:
  • * A Dword result of the MSFT RSS hash function, to be used by the stack for flow classification, is written into the receive packet descriptor (required by MSFT RSS).
  • * A 4-bit RSS Type field conveys the hash function used for the specific packet (required by MSFT RSS).

Enabling rules:

  • RSS is enabled in the MRQC register.
  • RSS enabling cannot be done dynamically while it must be preceded by a software reset.
  • RSS status field in the descriptor write-back is enabled when the RXCSUM.PCSD bit is set (fragment checksum is disabled).
    RSS is therefore mutually exclusive with UDP fragmentation checksum offload.
  • Support for RSS is not provided when legacy receive descriptor format is used.

Disabling rules:

  • Disabling RSS on the fly is not allowed, and the 82599 must be reset after RSS is disabled.
  • When RSS is disabled, packets are assigned an RSS output index = zero.

When multiple request queues are enabled in RSS mode, un-decodable packets are assigned an RSS output index = zero.
The 32-bit tag (normally a result of the hash function) equals zero.

注意:

  • 沒有開啓 rss:
    • 則所有報文只會從一個硬件隊列來收包。
  • 開啓 rss 後:
    • rss 會解釋報文的 l3 層信息:ip 地址。甚至 l4 層信息:tcp/udp 端口。
      • 報文會經過 hash function 計算出一個 uint32_t 的 rss hash。填充到 struct rte_mbuf 的 hash.rss字段中。
      • rss hash 的 低7位 會映射到 4位長 的 RSS output index
    • 無法解釋的 報文,rss hashRSS output index 設置爲 0。

在這裏插入圖片描述

2 DPDK rss 的 配置

21 用於 DPDK rss 的 配置 的數據結構

struct rte_eth_rss_conf 用於配置網卡的 rss。

struct rte_eth_rss_conf {
	uint8_t *rss_key;    /**< If not NULL, 40-byte hash key. */
	uint8_t rss_key_len; /**< hash key length in bytes. */
	uint64_t rss_hf;     /**< Hash functions to apply - see below. */
};
字段 描述
rss_key rss_key 數組。如果 爲 NULL,留給網卡設置 rss_key。
rss_key_len rss_key 數組的字節數。
rss_hf 需要對報文的分析的元組類型。常用的組合有 l3: ETH_RSS_IP, l3+l4: ETH_RSS_IP | ETH_RSS_UDP | ETH_RSS_TCP

2.2 常見 rss 的配置

static struct rte_eth_conf eth_conf = {
	.rxmode = {
		.mq_mode = ETH_MQ_RX_RSS		/* 使用 RSS 分流 */
	},
	.rx_adv_conf = {
		.rss_conf = {
			.rss_key = NULL,	/* 留給 網卡設置 rss_key */
			.rss_key_len = 0, 	/* rss_key 數組的字節數 */
			.rss_hf = ETH_RSS_IP 	/* 通過 l3 tuple 計算 rss hash */
					| ETH_RSS_UDP 	/* + 通過 UDP tuple 計算 rss hash */
					| ETH_RSS_TCP	/* + 通過 TCP tuple 計算 rss hash */
		},
	/* ... */
	}
/* ... */
};

問題:

  • 將 rss_key 字段設置爲 NULL, 留給 網卡設置 rss key。不是一個好主意。
  • 因爲不同的驅動,rss key 有可能設置爲對稱,也有可能設置爲非對稱的。所以不同驅動網卡的分流效果,將無法預測。

比如網卡82599ES 使用的是 ixgbe 的驅動。
在 ixgbe_rss_configure() 中,會使用 對稱的 rss key 作爲默認值。

ixgbe 的驅動 用於 rss 配置的代碼:

/* filepath:
 *   dpdk/drivers/net/ixgbe/ixgbe_rxtx.c
 */

/* ixgbe 的驅動 使用了 對稱的 rss key 作爲默認值。 */
static uint8_t rss_intel_key[40] = {
	0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 
    0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 
    0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 
    0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 
    0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A,
};

static void
ixgbe_rss_configure(struct rte_eth_dev *dev)
{
	/* ... */
	if (rss_conf.rss_key == NULL)
		rss_conf.rss_key = rss_intel_key; /* <-- Default hash key */
	ixgbe_hw_rss_hash_set(hw, &rss_conf);
}

應用筆記:

  • 當使用發包機來測試的時候,如果發包機產出的報文的元組信息過於有規律,而網卡使用了對稱的 rss_key,將會很大可能分流到相同的硬件收包隊列,從而影響性能。
  • 特別當進行 2544 吞吐量測試的時候,這個問題更加需要注意。
  • 一旦發現分流效果不好。則可以 使用非對稱的 rss_key,並且將 rss_hf 設置爲 ETH_RSS_IP | ETH_RSS_UDP | ETH_RSS_TCP

2.3 顯示指定 rss key

爲了避免驅動使用了不合適的 rss_key。所以最好還是自己顯示指定。
下面的代碼,就提供了 對稱 和 非對稱 rss key 以供選擇。

rss_hash_key 的數值可以參考論文Scalable TCP Session Monitoring withSymmetric Receive-side Scaling

/* 使用 宏來選擇 默認的 rss 配置 */
#define RSS_HASH_KEY_DEFAULT			rss_hash_key_symmetric
#define RSS_HASH_FUNCTION_DEFAULT 		(ETH_RSS_IP | ETH_RSS_UDP | ETH_RSS_TCP)

/* symmetric_rss_key */
static uint8_t rss_hash_key_symmetric[40] =
	{ 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A 
	, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A 
	, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A 
	, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A
	, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A, 0x6D, 0x5A 
	}; 

/* asymmetric_rss_key */
static uint8_t rss_hash_key_asymmetric[40] = 
	{ 0x6d, 0x5a, 0x56, 0xda, 0x25, 0x5b, 0x0e, 0xc2
	, 0x41, 0x67, 0x25, 0x3d, 0x43, 0xa3, 0x8f, 0xb0
	, 0xd0, 0xca, 0x2b, 0xcb, 0xae, 0x7b, 0x30, 0xb4
	, 0x77, 0xcb, 0x2d, 0xa3, 0x80, 0x30, 0xf2, 0x0c
	, 0x6a, 0x42, 0xb7, 0x3b, 0xbe, 0xac, 0x01, 0xfa
	}; 

/* rss config */
static struct rte_eth_conf eth_conf = {
	.rxmode = {
		.mq_mode = ETH_MQ_RX_RSS		/* ETH_MQ_RX_RSS */
	},
	.rx_adv_conf = {
		.rss_conf = {
			.rss_key = RSS_HASH_KEY_DEFAULT,	/* <-- 顯示指定 rss_key */
			.rss_key_len = RTE_DIM(RSS_HASH_KEY_DEFAULT), 	/* rss_key 數組的字節數 */
			.rss_hf = RSS_HASH_FUNCTION_DEFAULT	/* <-- 顯示指定 rss hash function */
		},
	/* ... */
	}
/* ... */
};

/* 靜態的 rss 配置方法 */
int error_code;
error_code = rte_eth_dev_configure(port_id
	, rx_queue_count
	, tx_queue_count
	, &eth_conf				/* <-- 傳入上面定義的 eth_conf */		
	);

2.4 動態的 rss 配置方法

除了上面靜態的 rss 配置方法。也可以通過 rte_eth_dev_rss_hash_update() 來動態配置。
下面的例子,就是一個 rte_eth_dev_rss_hash_update() 的包裹函數。

static int 
port_set_rss_config(uint_8 port_id
	, uint64_t hash_function
	, uint8_t *hash_key
	, uint8_t hash_key_len
	)
{
	struct rte_eth_rss_conf rss_conf;
	int error_code;

	rss_conf.rss_key = hash_key;
	rss_conf.rss_key_len = hash_key_len;
	rss_conf.rss_hf = hash_function;
	
	error_code = rte_eth_dev_rss_hash_update(port_id, &rss_conf);
	if (0 != error_code) {
		goto fail; 
	}
	return 0;
	
	/* error handle */
fail: 
	switch (error_code) {
	case -ENODEV:
		printf("port index %d invalid\n", port_id);
		break;
	case -ENOTSUP:
		printf("operation not supported by device\n");
		break;
	default:
		printf("operation failed - error_code=%d\n", error_code);
		break;
	}
	return -1;
}

3 如何計算 rss hash

3.1 算法

rss hash 的算法可以參考 Intel 82599 10 GbE Controller Datasheet 其中的 Section 7.1.2.8.1, RSS Hash Function 一節。

算法的僞代碼表示如下:

ComputeHash(input[], N)
	For hash-input input[] of length N bytes (8N bits) and a random secret key K of 320 bits
	Result = 0;
	For each bit b in input[] {
		if (b == 1) then Result ^= (left-most 32 bits of K);
		shift K left 1 bit position;
	}
	return Result;

注意:

  • 輸入參數 input[],表示 元組信息,是以小端對齊的。
  • 輸入參數 N,表示 元組信息 的字節數。
  • K,就是 rss key。以 uint8_t [] 數組形式表示:K[0] K[1] K[2] … K[k-1]。總共 k 個字節。
  • K[0] 是最左端的 字節。K[0] 的 MSB(最高位)是最左端的比特位。
  • K[k-1] 是最右端的 字節。K[k-1] 的 LSB(最低位)是最右端的比特位。

例子:

項目 數值(十進制) uint8_t [] 數組形式表示 (小端對齊十六進制)
src_ipv4 66.9.149.187 {0xbb, 0x95, 0x09, 0x42}
dst_ipv4 161.142.100.80 {0x50, 0x64, 0x8e, 0xa1}
sport 2794 {0xea, 0x0a}
dport 1766 {0xe6, 0x06}
  • 如果 只考慮 ipv4 的二元組信息,則輸入參數的數值爲:
輸入參數 數值
input[] {0xbb, 0x95, 0x09, 0x42, 0x50, 0x64, 0x8e, 0xa1}
N 8
  • 如果 考慮 ipv4 和 port 的四元組信息,則輸入參數的數值爲:
輸入參數 數值
input[] {0xbb, 0x95, 0x09, 0x42, 0x50, 0x64, 0x8e, 0xa1, 0xea, 0x0a, 0xe6, 0x06}
N 12

3.2 DPDK 的 rss hash 實現函數

應用筆記:

  • 在實際的應用中。有的時候需要使用 軟件方法,來計算出與網卡硬件一致的 rss hash 值。

DPDK rss hash 實現函數 有兩個,分別是 rte_softrss() 和 rte_softrss_be()。
它們都定義在 dpdk/lib/librte_hash/rte_thash.h 中。

注意:

  • rte_softrss() 和 rte_softrss_be() 都可以對相同的 input_tuple 計算出相同的 rss hash。
  • rte_softrss_be() 在運算的時候,對 rss_key 少執行了 rte_cpu_to_be_32() 大小端轉換。所以相比 rte_softrss() 會高效點。
  • rte_softrss_be() 所需要 rss_key 是需要 經過 rte_convert_rss_key() 來預先做 大小端轉換的。

DPDK rte_softrss() 的源代碼:

/**
 * Generic implementation. Can be used with original rss_key
 * @param input_tuple
 *   Pointer to input tuple
 * @param input_len
 *   Length of input_tuple in 4-bytes chunks
 * @param rss_key
 *   Pointer to RSS hash key.
 * @return
 *   Calculated hash value.
 */
static inline uint32_t
rte_softrss(uint32_t *input_tuple, uint32_t input_len,
		const uint8_t *rss_key)
{
	uint32_t i, j, ret = 0;

	for (j = 0; j < input_len; j++) {
		for (i = 0; i < 32; i++) {
			if (input_tuple[j] & (1 << (31 - i))) {
				ret ^= rte_cpu_to_be_32(((const uint32_t *)rss_key)[j]) << i |
					(uint32_t)((uint64_t)(rte_cpu_to_be_32(((const uint32_t *)rss_key)[j + 1])) >>
					(32 - i));
			}
		}
	}
	return ret;
}

DPDK rte_softrss_be() 的源代碼:

/**
 * Prepare special converted key to use with rte_softrss_be()
 * @param orig
 *   pointer to original RSS key
 * @param targ
 *   pointer to target RSS key
 * @param len
 *   RSS key length
 */
static inline void
rte_convert_rss_key(const uint32_t *orig, uint32_t *targ, int len)
{
	int i;

	for (i = 0; i < (len >> 2); i++)
		targ[i] = rte_be_to_cpu_32(orig[i]);
}

/**
 * Optimized implementation.
 * If you want the calculated hash value matches NIC RSS value
 * you have to use special converted key with rte_convert_rss_key() fn.
 * @param input_tuple
 *   Pointer to input tuple
 * @param input_len
 *   Length of input_tuple in 4-bytes chunks
 * @param *rss_key
 *   Pointer to RSS hash key.
 * @return
 *   Calculated hash value.
 */
static inline uint32_t
rte_softrss_be(uint32_t *input_tuple, uint32_t input_len,
		const uint8_t *rss_key)
{
	uint32_t i, j, ret = 0;

	for (j = 0; j < input_len; j++) {
		for (i = 0; i < 32; i++) {
			if (input_tuple[j] & (1 << (31 - i))) {
				ret ^= ((const uint32_t *)rss_key)[j] << i |
					(uint32_t)((uint64_t)(((const uint32_t *)rss_key)[j + 1]) >> (32 - i));
			}
		}
	}
	return ret;
}

另外,rte_thash.h 也定義的 struct rte_ipv4_tuplestruct rte_ipv6_tuple 用來放置報文的元組信息。

注意:

  • struct rte_ipv4_tuple 所有字段都需要以小端對齊。
  • struct rte_ipv6_tuple 所有字段都需要以小端對齊。
/**
 * length in dwords of input tuple to
 * calculate hash of ipv4 header only
 */
#define RTE_THASH_V4_L3_LEN	((sizeof(struct rte_ipv4_tuple) -	\
			sizeof(((struct rte_ipv4_tuple *)0)->sctp_tag)) / 4)

/**
 * length in dwords of input tuple to
 * calculate hash of ipv4 header +
 * transport header
 */
#define RTE_THASH_V4_L4_LEN	 ((sizeof(struct rte_ipv4_tuple)) / 4)

/**
 * length in dwords of input tuple to
 * calculate hash of ipv6 header only
 */
#define RTE_THASH_V6_L3_LEN	((sizeof(struct rte_ipv6_tuple) -       \
			sizeof(((struct rte_ipv6_tuple *)0)->sctp_tag)) / 4)

/**
 * length in dwords of input tuple to
 * calculate hash of ipv6 header +
 * transport header
 */
#define RTE_THASH_V6_L4_LEN	((sizeof(struct rte_ipv6_tuple)) / 4)

/**
 * IPv4 tuple
 * addresses and ports/sctp_tag have to be CPU byte order
 */
struct rte_ipv4_tuple {
	uint32_t	src_addr;
	uint32_t	dst_addr;
	union {
		struct {
			uint16_t dport;
			uint16_t sport;
		};
		uint32_t     sctp_tag;
	};
};

/**
 * IPv6 tuple
 * Addresses have to be filled by rte_thash_load_v6_addr()
 * ports/sctp_tag have to be CPU byte order
 */
struct rte_ipv6_tuple {
	uint8_t		src_addr[16];
	uint8_t		dst_addr[16];
	union {
		struct {
			uint16_t dport;
			uint16_t sport;
		};
		uint32_t        sctp_tag;
	};
};

union rte_thash_tuple {
	struct rte_ipv4_tuple	v4;
	struct rte_ipv6_tuple	v6;

3.3 rss hash 的例子

以下的例子參考的是 dpdk/app/test/test_thash.c

union rte_thash_tuple tuple;
uint32_t rss_l3_original, rss_l3l4_original;
uint32_t rss_l3_converted, rss_l3l4_converted;

uint8_t default_rss_key[] = {
	0x6d, 0x5a, 0x56, 0xda, 0x25, 0x5b, 0x0e, 0xc2,
	0x41, 0x67, 0x25, 0x3d, 0x43, 0xa3, 0x8f, 0xb0,
	0xd0, 0xca, 0x2b, 0xcb, 0xae, 0x7b, 0x30, 0xb4,
	0x77, 0xcb, 0x2d, 0xa3, 0x80, 0x30, 0xf2, 0x0c,
	0x6a, 0x42, 0xb7, 0x3b, 0xbe, 0xac, 0x01, 0xfa,
	};
uint8_t rss_key_be[RTE_DIM(default_rss_key)];
	
tuple.v4.src_addr = IPv4(66, 9, 149, 187);
tuple.v4.dst_addr = IPv4(161, 142, 100, 80);
tuple.v4.sport = 2794;
tuple.v4.dport = 1766;

/*Calculate hash with original key*/
rss_l3_original = rte_softrss((uint32_t *)&tuple,
		RTE_THASH_V4_L3_LEN, default_rss_key);
rss_l3l4_original = rte_softrss((uint32_t *)&tuple,
		RTE_THASH_V4_L4_LEN, default_rss_key);
assert(0x323e8fc2 == rss_l3_original);
assert(0x51ccc178 == rss_l3l4_original);
	
/*Calculate hash with converted key*/
rss_l3_converted = rte_softrss_be((uint32_t *)&tuple,
		RTE_THASH_V4_L3_LEN, rss_key_be);
rss_l3l4_converted = rte_softrss_be((uint32_t *)&tuple,
		RTE_THASH_V4_L4_LEN, rss_key_be);
assert(0x323e8fc2 == rss_l3_converted);
assert(0x51ccc178 == rss_l3l4_converted);

ref:

Intel 82599 10 GbE Controller Datasheet
Scalable TCP Session Monitoring withSymmetric Receive-side Scaling
DPDK 之 Symmetric Receive-side Scaling

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