日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

深入操作系统,从内核理解网络包的接收过程(Linux篇)

發布時間:2023/12/13 综合教程 55 生活家
生活随笔 收集整理的這篇文章主要介紹了 深入操作系统,从内核理解网络包的接收过程(Linux篇) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1、引言

因為要對百萬、千萬、甚至是過億的用戶提供各種網絡服務,所以在一線互聯網企業里面試和晉升后端開發同學的其中一個重點要求就是要能支撐高并發,要理解性能開銷,會進行性能優化。而很多時候,如果你對網絡底層的理解不深的話,遇到很多線上性能瓶頸你會覺得狗拿刺猬,無從下手。

這篇文章將用圖解的方式,從操作系統這一層來深度理解一下網絡包的接收過程(因為能直接看到內核源碼,本文以Linux為例)。

按照慣例來借用一段最簡單的代碼開始思考。

為了簡單起見,我們用udp來舉例,如下:

int main(){

intserverSocketFd = socket(AF_INET, SOCK_DGRAM, 0);

bind(serverSocketFd, ...);

char buff[BUFFSIZE];

int readCount = recvfrom(serverSocketFd, buff, BUFFSIZE, 0, ...);

buff[readCount] = '';

printf("Receive from client:%s
", buff);

}

上面代碼是一段udp server接收收據的邏輯。當在開發視角看的時候,只要客戶端有對應的數據發送過來,服務器端執行recv_from后就能收到它,并把它打印出來。

我們現在想知道的是:當網絡包達到網卡,直到我們的recvfrom收到數據,這中間,究竟都發生過什么?

通過本文,你將從操作系統內部這一層深入理解網絡是如何實現的,以及各個部分之間是如何交互的。相信這對你的工作將會有非常大的幫助(本文將以Linux為例,源碼基于Linux 3.10,源代碼參見:https://mirrors.edge.kernel.org/pub/linux/kernel/v3.x/,網卡驅動采用Intel的igb網卡舉例)。

2、閱讀本文遇到問題解惑

閱讀本文遇到困惑可以加群4915800聯系群主方靜(人稱六哥),實力不輸輪子哥,長期研究操作系統原理,CPU原理,高級語言虛擬機、網絡和數據庫,以及編程語言實現。

====================================================================================================================

3、網絡收包總覽

在TCP/IP網絡分層模型里,整個協議棧被分成了:物理層、鏈路層、網絡層,傳輸層和應用層。

物理層對應的是網卡和網線,應用層對應的是我們常見的Nginx,FTP等等各種應用。對于Linux來說,它實現的是鏈路層、網絡層和傳輸層這三層。

在Linux內核實現中,鏈路層協議靠網卡驅動來實現,內核協議棧來實現網絡層和傳輸層。內核對更上層的應用層提供socket接口來供用戶進程訪問。

我們用Linux的視角來看到的TCP/IP網絡分層模型應該是下面這個樣子的:

在Linux的源代碼中,網絡設備驅動對應的邏輯位于driver/net/ethernet。

其中:

1)intel系列網卡的驅動在driver/net/ethernet/intel目錄下;

2)協議棧模塊代碼位于kernel和net目錄。

內核和網絡設備驅動是通過中斷的方式來處理的。

當設備上有數據到達的時候:會給CPU的相關引腳上觸發一個電壓變化,以通知CPU來處理數據。

對于網絡模塊來說:由于處理過程比較復雜和耗時,如果在中斷函數中完成所有的處理,將會導致中斷處理函數(優先級過高)將過度占據CPU,將導致CPU無法響應其它設備,例如鼠標和鍵盤的消息。

因此Linux中斷處理函數是分上半部和下半部的。上半部是只進行最簡單的工作,快速處理然后釋放CPU,接著CPU就可以允許其它中斷進來。剩下將絕大部分的工作都放到下半部中,可以慢慢從容處理。Linux 2.4以后的內核版本采用的下半部實現方式是軟中斷,由ksoftirqd內核線程全權處理。和硬中斷不同的是,硬中斷是通過給CPU物理引腳施加電壓變化,而軟中斷是通過給內存中的一個變量的二進制值以通知軟中斷處理程序。

好了,大概了解了網卡驅動、硬中斷、軟中斷和ksoftirqd線程之后,我們在這幾個概念的基礎上給出一個內核收包的路徑示意。

Linux內核網絡收包總覽:

如上圖所示:當網卡上收到數據以后,Linux中第一個工作的模塊是網絡驅動。網絡驅動會以DMA的方式把網卡上收到的幀寫到內存里。再向CPU發起一個中斷,以通知CPU有數據到達。第二,當CPU收到中斷請求后,會去調用網絡驅動注冊的中斷處理函數。網卡的中斷處理函數并不做過多工作,發出軟中斷請求,然后盡快釋放CPU。ksoftirqd檢測到有軟中斷請求到達,調用poll開始輪詢收包,收到后交由各級協議棧處理。對于UDP包來說,會被放到用戶socket的接收隊列中。

我們從上面這張圖中已經從整體上把握到了操作系統對數據包的處理過程。但是要想了解更多網絡模塊工作的細節,我們還得往下看。

4、網絡數據到來前操作系統的準備

Linux驅動、內核協議棧等等模塊在具備接收網卡數據包之前,要做很多的準備工作才行。

比如:要提前創建好ksoftirqd內核線程,要注冊好各個協議對應的處理函數,網絡設備子系統要提前初始化好,網卡要啟動好。只有這些都Ready之后,我們才能真正開始接收數據包。

那么我們現在來看看這些準備工作都是怎么做的。

4.1 創建ksoftirqd內核線程

Linux的軟中斷都是在專門的內核線程(ksoftirqd)中進行的,因此我們非常有必要看一下這些進程是怎么初始化的,這樣我們才能在后面更準確地了解收包過程。該進程數量不是1個,而是N個,其中N等于你的機器的核數。

系統初始化的時候在kernel/smpboot.c中調用了smpboot_register_percpu_thread, 該函數進一步會執行到spawn_ksoftirqd(位于kernel/softirq.c)來創建出softirqd進程。

創建ksoftirqd內核線程:

相關代碼如下:

//file: kernel/softirq.c

static struct smp_hotplug_thread softirq_threads = {

.store = &ksoftirqd,

.thread_should_run = ksoftirqd_should_run,

.thread_fn = run_ksoftirqd,

.thread_comm = "ksoftirqd/%u",};

static__init intspawn_ksoftirqd(void){

register_cpu_notifier(&cpu_nfb);

BUG_ON(smpboot_register_percpu_thread(&softirq_threads));

return0;

}

early_initcall(spawn_ksoftirqd);

當ksoftirqd被創建出來以后,它就會進入自己的線程循環函數ksoftirqd_should_run和run_ksoftirqd了。不停地判斷有沒有軟中斷需要被處理。

這里需要注意的一點是,軟中斷不僅僅只有網絡軟中斷,還有其它類型:

//file: include/linux/interrupt.h

enum{

HI_SOFTIRQ=0,

TIMER_SOFTIRQ,

NET_TX_SOFTIRQ,

NET_RX_SOFTIRQ,

BLOCK_SOFTIRQ,

BLOCK_IOPOLL_SOFTIRQ,

TASKLET_SOFTIRQ,

SCHED_SOFTIRQ,

HRTIMER_SOFTIRQ,

RCU_SOFTIRQ,

};

4.2 網絡子系統初始化

網絡子系統初始化:

linux內核通過調用subsys_initcall來初始化各個子系統,在源代碼目錄里你可以grep出許多對這個函數的調用。

這里我們要說的是網絡子系統的初始化,會執行到net_dev_init函數:

//file: net/core/dev.c

static int __init net_dev_init(void){

......

for_each_possible_cpu(i) {

structsoftnet_data *sd = &per_cpu(softnet_data, i);

memset(sd, 0, sizeof(*sd));

skb_queue_head_init(&sd->input_pkt_queue);

skb_queue_head_init(&sd->process_queue);

sd->completion_queue = NULL;

INIT_LIST_HEAD(&sd->poll_list);

......

}

......

open_softirq(NET_TX_SOFTIRQ, net_tx_action);

open_softirq(NET_RX_SOFTIRQ, net_rx_action);

}

subsys_initcall(net_dev_init);

在這個函數里,會為每個CPU都申請一個softnet_data數據結構,在這個數據結構里的poll_list是等待驅動程序將其poll函數注冊進來,稍后網卡驅動初始化的時候我們可以看到這一過程。

另外open_softirq注冊了每一種軟中斷都注冊一個處理函數。NET_TX_SOFTIRQ的處理函數為net_tx_action,NET_RX_SOFTIRQ的為net_rx_action。繼續跟蹤open_softirq后發現這個注冊的方式是記錄在softirq_vec變量里的。后面ksoftirqd線程收到軟中斷的時候,也會使用這個變量來找到每一種軟中斷對應的處理函數。

//file: kernel/softirq.c

void open_softirq(int nr, void(*action)(struct softirq_action *)){

softirq_vec[nr].action = action;

}

4.3 協議棧注冊

操作系統內核實現了網絡層的ip協議,也實現了傳輸層的tcp協議和udp協議。這些協議對應的實現函數分別是ip_rcv(),tcp_v4_rcv()和udp_rcv()。和我們平時寫代碼的方式不一樣的是,內核是通過注冊的方式來實現的。

Linux內核中的fs_initcall和subsys_initcall類似,也是初始化模塊的入口。fs_initcall調用inet_init后開始網絡協議棧注冊。通過inet_init,將這些函數注冊到了inet_protos和ptype_base數據結構中了。

如下圖:

相關代碼如下:

//file: net/ipv4/af_inet.c

static struct packet_type ip_packet_type __read_mostly = {

.type = cpu_to_be16(ETH_P_IP),

.func = ip_rcv,};static const struct net_protocol udp_protocol = {

.handler = udp_rcv,

.err_handler = udp_err,

.no_policy = 1,

.netns_ok = 1,};static const struct net_protocol tcp_protocol = {

.early_demux = tcp_v4_early_demux,

.handler = tcp_v4_rcv,

.err_handler = tcp_v4_err,

.no_policy = 1,

.netns_ok = 1,

};

static int __init inet_init(void){

......

if(inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)

pr_crit("%s: Cannot add ICMP protocol
", __func__);

if(inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)

pr_crit("%s: Cannot add UDP protocol
", __func__);

if(inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)

pr_crit("%s: Cannot add TCP protocol
", __func__);

......

dev_add_pack(&ip_packet_type);

}

上面的代碼中我們可以看到,udp_protocol結構體中的handler是udp_rcv,tcp_protocol結構體中的handler是tcp_v4_rcv,通過inet_add_protocol被初始化了進來。

int inet_add_protocol(const struct net_protocol *prot, unsigned charprotocol){

if(!prot->netns_ok) {

pr_err("Protocol %u is not namespace aware, cannot register.
",

protocol);

return-EINVAL;

}

return !cmpxchg((conststructnet_protocol **)&inet_protos[protocol],

NULL, prot) ? 0 : -1;

}

inet_add_protocol函數將tcp和udp對應的處理函數都注冊到了inet_protos數組中了。再看dev_add_pack(&ip_packet_type);這一行,ip_packet_type結構體中的type是協議名,func是ip_rcv函數,在dev_add_pack中會被注冊到ptype_base哈希表中。

//file: net/core/dev.c

void dev_add_pack(struct packet_type *pt){

struct list_head *head = ptype_head(pt);

......

}

static inline struct list_head *ptype_head(const struct packet_type *pt){

if(pt->type == htons(ETH_P_ALL))

return &ptype_all;

else

return &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK];

}

這里我們需要記住inet_protos記錄著udp,tcp的處理函數地址,ptype_base存儲著ip_rcv()函數的處理地址。后面我們會看到軟中斷中會通過ptype_base找到ip_rcv函數地址,進而將ip包正確地送到ip_rcv()中執行。在ip_rcv中將會通過inet_protos找到tcp或者udp的處理函數,再而把包轉發給udp_rcv()或tcp_v4_rcv()函數。

擴展一下,如果看一下ip_rcv和udp_rcv等函數的代碼能看到很多協議的處理過程。

例如:ip_rcv中會處理netfilter和iptable過濾,如果你有很多或者很復雜的 netfilter 或 iptables 規則,這些規則都是在軟中斷的上下文中執行的,會加大網絡延遲。

再例如:udp_rcv中會判斷socket接收隊列是否滿了。對應的相關內核參數是net.core.rmem_max和net.core.rmem_default。如果有興趣,建議大家好好讀一下inet_init這個函數的代碼。

4.4 網卡驅動初始化

每一個驅動程序(不僅僅只是網卡驅動)會使用 module_init 向內核注冊一個初始化函數,當驅動被加載時,內核會調用這個函數。

比如igb網卡驅動的代碼位于drivers/net/ethernet/intel/igb/igb_main.c:

//file: drivers/net/ethernet/intel/igb/igb_main.c

static struct pci_driver igb_driver = {

.name = igb_driver_name,

.id_table = igb_pci_tbl,

.probe = igb_probe,

.remove= igb_remove,

......

};

static int __init igb_init_module(void){

......

ret = pci_register_driver(&igb_driver);

return ret;

}

驅動的pci_register_driver調用完成后,Linux內核就知道了該驅動的相關信息,比如igb網卡驅動的igb_driver_name和igb_probe函數地址等等。當網卡設備被識別以后,內核會調用其驅動的probe方法(igb_driver的probe方法是igb_probe)。驅動probe方法執行的目的就是讓設備ready,對于igb網卡,其igb_probe位于drivers/net/ethernet/intel/igb/igb_main.c下。

主要執行的操作如下:

第5步中我們看到:網卡驅動實現了ethtool所需要的接口,也在這里注冊完成函數地址的注冊。當 ethtool 發起一個系統調用之后,內核會找到對應操作的回調函數。對于igb網卡來說,其實現函數都在drivers/net/ethernet/intel/igb/igb_ethtool.c下。

相信你這次能徹底理解ethtool的工作原理了吧?這個命令之所以能查看網卡收發包統計、能修改網卡自適應模式、能調整RX 隊列的數量和大小,是因為ethtool命令最終調用到了網卡驅動的相應方法,而不是ethtool本身有這個超能力。

第6步:注冊的igb_netdev_ops中包含的是igb_open等函數,該函數在網卡被啟動的時候會被調用。

//file: drivers/net/ethernet/intel/igb/igb_main.c

static const struct net_device_ops igb_netdev_ops = {

.ndo_open = igb_open,

.ndo_stop = igb_close,

.ndo_start_xmit = igb_xmit_frame,

.ndo_get_stats64 = igb_get_stats64,

.ndo_set_rx_mode = igb_set_rx_mode,

.ndo_set_mac_address = igb_set_mac,

.ndo_change_mtu = igb_change_mtu,

.ndo_do_ioctl = igb_ioctl,

......

第7步:在igb_probe初始化過程中,還調用到了igb_alloc_q_vector。他注冊了一個NAPI機制所必須的poll函數,對于igb網卡驅動來說,這個函數就是igb_poll,如下代碼所示。

static int igb_alloc_q_vector(struct igb_adapter *adapter,

int v_count, int v_idx,

int txr_count, int txr_idx,

int rxr_count, int rxr_idx){

......

/* initialize NAPI */

netif_napi_add(adapter->netdev, &q_vector->napi, igb_poll, 64);

}

4.5 啟動網卡

當上面的初始化都完成以后,就可以啟動網卡了。

回憶前面網卡驅動初始化時,我們提到了驅動向內核注冊了 structure net_device_ops 變量,它包含著網卡啟用、發包、設置mac 地址等回調函數(函數指針)。當啟用一個網卡時(例如,通過 ifconfig eth0 up),net_device_ops 中的 igb_open方法會被調用。

它通常會做以下事情:

//file: drivers/net/ethernet/intel/igb/igb_main.c

static int __igb_open(struct net_device *netdev, bool resuming){

/* allocate transmit descriptors */

err = igb_setup_all_tx_resources(adapter);

/* allocate receive descriptors */

err = igb_setup_all_rx_resources(adapter);

/* 注冊中斷處理函數 */

err = igb_request_irq(adapter);

if(err)

goto err_req_irq;

/* 啟用NAPI */

for(i = 0; i < adapter->num_q_vectors; i++)

napi_enable(&(adapter->q_vector[I ]->napi));

......

}

在上面__igb_open函數調用了igb_setup_all_tx_resources,和igb_setup_all_rx_resources。在igb_setup_all_rx_resources這一步操作中,分配了RingBuffer,并建立內存和Rx隊列的映射關系。(Rx Tx 隊列的數量和大小可以通過 ethtool 進行配置)。

我們再接著看中斷函數注冊igb_request_irq:

static int igb_request_irq(struct igb_adapter *adapter){

if(adapter->msix_entries) {

err = igb_request_msix(adapter);

if(!err)

goto request_done;

......

}

}

static int igb_request_msix(struct igb_adapter *adapter){

......

for(i = 0; i < adapter->num_q_vectors; i++) {

...

err = request_irq(adapter->msix_entries[vector].vector,

igb_msix_ring, 0, q_vector->name,

}

在上面的代碼中跟蹤函數調用, __igb_open => igb_request_irq => igb_request_msix, 在igb_request_msix中我們看到了,對于多隊列的網卡,為每一個隊列都注冊了中斷,其對應的中斷處理函數是igb_msix_ring(該函數也在drivers/net/ethernet/intel/igb/igb_main.c下)。

我們也可以看到,msix方式下,每個 RX 隊列有獨立的MSI-X 中斷,從網卡硬件中斷的層面就可以設置讓收到的包被不同的 CPU處理。(可以通過 irqbalance ,或者修改 /proc/irq/IRQ_NUMBER/smp_affinity能夠修改和CPU的綁定行為)。

當做好以上準備工作以后,就可以開門迎客(數據包)了!

5、開始迎接數據的到來

5.1 硬中斷處理

首先:當數據幀從網線到達網卡上的時候,第一站是網卡的接收隊列。

網卡在分配給自己的RingBuffer中尋找可用的內存位置,找到后DMA引擎會把數據DMA到網卡之前關聯的內存里,這個時候CPU都是無感的。當DMA操作完成以后,網卡會像CPU發起一個硬中斷,通知CPU有數據到達。

網卡數據硬中斷處理過程:

注意:當RingBuffer滿的時候,新來的數據包將給丟棄。ifconfig查看網卡的時候,可以里面有個overruns,表示因為環形隊列滿被丟棄的包。如果發現有丟包,可能需要通過ethtool命令來加大環形隊列的長度。

在啟動網卡一節,我們說到了網卡的硬中斷注冊的處理函數是igb_msix_ring:

//file: drivers/net/ethernet/intel/igb/igb_main.c

static irqreturn_t igb_msix_ring(intirq, void *data){

struct igb_q_vector *q_vector = data;

/* Write the ITR value calculated from the previous interrupt. */

igb_write_itr(q_vector);

napi_schedule(&q_vector->napi);

return IRQ_HANDLED;

}

igb_write_itr只是記錄一下硬件中斷頻率(據說目的是在減少對CPU的中斷頻率時用到)。

順著napi_schedule調用一路跟蹤下去,__napi_schedule=>____napi_schedule:

/* Called with irq disabled */

static inline void____napi_schedule(struct softnet_data *sd,

struct napi_struct *napi){

list_add_tail(&napi->poll_list, &sd->poll_list);

__raise_softirq_irqoff(NET_RX_SOFTIRQ);

}

這里我們看到:list_add_tail修改了CPU變量softnet_data里的poll_list,將驅動napi_struct傳過來的poll_list添加了進來。

其中:softnet_data中的poll_list是一個雙向列表,其中的設備都帶有輸入幀等著被處理。緊接著__raise_softirq_irqoff觸發了一個軟中斷NET_RX_SOFTIRQ, 這個所謂的觸發過程只是對一個變量進行了一次或運算而已。

void __raise_softirq_irqoff(unsigned int nr){

trace_softirq_raise(nr);

or_softirq_pending(1UL << nr);

}

//file: include/linux/irq_cpustat.h

#define or_softirq_pending(x) (local_softirq_pending() |= (x))

我們說過:Linux在硬中斷里只完成簡單必要的工作,剩下的大部分的處理都是轉交給軟中斷的。

通過上面代碼可以看到:硬中斷處理過程真的是非常短。只是記錄了一個寄存器,修改了一下下CPU的poll_list,然后發出個軟中斷。就這么簡單,硬中斷工作就算是完成了。

5.2 ksoftirqd內核線程處理軟中斷

ksoftirqd內核線程:

內核線程初始化的時候,我們介紹了ksoftirqd中兩個線程函數ksoftirqd_should_run和run_ksoftirqd。

其中ksoftirqd_should_run代碼如下:

static int ksoftirqd_should_run(unsigned int cpu){

return local_softirq_pending();

}

#define local_softirq_pending() __IRQ_STAT(smp_processor_id(), __softirq_pending)

這里看到和硬中斷中調用了同一個函數local_softirq_pending。使用方式不同的是硬中斷位置是為了寫入標記,這里僅僅只是讀取。如果硬中斷中設置了NET_RX_SOFTIRQ,這里自然能讀取的到。

接下來會真正進入線程函數中run_ksoftirqd處理:

static void run_ksoftirqd(unsigned int cpu){

local_irq_disable();

if(local_softirq_pending()) {

__do_softirq();

rcu_note_context_switch(cpu);

local_irq_enable();

cond_resched();

return;

}

local_irq_enable();

}

在__do_softirq中,判斷根據當前CPU的軟中斷類型,調用其注冊的action方法。

asmlinkage void__do_softirq(void){

do{

if(pending & 1) {

unsigned int vec_nr = h - softirq_vec;

int prev_count = preempt_count();

...

trace_softirq_entry(vec_nr);

h->action(h);

trace_softirq_exit(vec_nr);

...

}

h++;

pending >>= 1;

} while(pending);

}

在網絡子系統初始化小節, 我們看到我們為NET_RX_SOFTIRQ注冊了處理函數net_rx_action。所以net_rx_action函數就會被執行到了。

這里需要注意一個細節,硬中斷中設置軟中斷標記,和ksoftirq的判斷是否有軟中斷到達,都是基于smp_processor_id()的。這意味著只要硬中斷在哪個CPU上被響應,那么軟中斷也是在這個CPU上處理的。所以說,如果你發現你的Linux軟中斷CPU消耗都集中在一個核上的話,做法是要把調整硬中斷的CPU親和性,來將硬中斷打散到不同的CPU核上去。

我們再來把精力集中到這個核心函數net_rx_action上來:

static void net_rx_action(struct softirq_action *h){

struct softnet_data *sd = &__get_cpu_var(softnet_data);

unsigned long time_limit = jiffies + 2;

int budget = netdev_budget;

void *have;

local_irq_disable();

while(!list_empty(&sd->poll_list)) {

......

n = list_first_entry(&sd->poll_list, struct napi_struct, poll_list);

work = 0;

if(test_bit(NAPI_STATE_SCHED, &n->state)) {

work = n->poll(n, weight);

trace_napi_poll(n);

}

budget -= work;

}

}

函數開頭的time_limit和budget是用來控制net_rx_action函數主動退出的,目的是保證網絡包的接收不霸占CPU不放。等下次網卡再有硬中斷過來的時候再處理剩下的接收數據包。其中budget可以通過內核參數調整。這個函數中剩下的核心邏輯是獲取到當前CPU變量softnet_data,對其poll_list進行遍歷, 然后執行到網卡驅動注冊到的poll函數。

對于igb網卡來說,就是igb驅動力的igb_poll函數了:

static int igb_poll(struct napi_struct *napi, int budget){

...

if(q_vector->tx.ring)

clean_complete = igb_clean_tx_irq(q_vector);

if(q_vector->rx.ring)

clean_complete &= igb_clean_rx_irq(q_vector, budget);

...

}

在讀取操作中,igb_poll的重點工作是對igb_clean_rx_irq的調用:

static bool igb_clean_rx_irq(struct igb_q_vector *q_vector, const int budget){

...

do{

/* retrieve a buffer from the ring */

skb = igb_fetch_rx_buffer(rx_ring, rx_desc, skb);

/* fetch next buffer in frame if non-eop */

if(igb_is_non_eop(rx_ring, rx_desc))

continue;

}

/* verify the packet layout is correct */

if(igb_cleanup_headers(rx_ring, rx_desc, skb)) {

skb = NULL;

continue;

}

/* populate checksum, timestamp, VLAN, and protocol */

igb_process_skb_fields(rx_ring, rx_desc, skb);

napi_gro_receive(&q_vector->napi, skb);

}

igb_fetch_rx_buffer和igb_is_non_eop的作用就是把數據幀從RingBuffer上取下來。

為什么需要兩個函數呢?因為有可能幀要占多多個RingBuffer,所以是在一個循環中獲取的,直到幀尾部。獲取下來的一個數據幀用一個sk_buff來表示。收取完數據以后,對其進行一些校驗,然后開始設置sbk變量的timestamp, VLAN id, protocol等字段。

接下來進入到napi_gro_receive中:

//file: net/core/dev.c

gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb){

skb_gro_reset_offset(skb);

return napi_skb_finish(dev_gro_receive(napi, skb), skb);

}

dev_gro_receive這個函數代表的是網卡GRO特性,可以簡單理解成把相關的小包合并成一個大包就行,目的是減少傳送給網絡棧的包數,這有助于減少 CPU 的使用量。我們暫且忽略,直接看napi_skb_finish。

這個函數主要就是調用了netif_receive_skb:

//file: net/core/dev.c

static gro_result_t napi_skb_finish(gro_result_t ret, struct sk_buff *skb){

switch(ret) {

case GRO_NORMAL:

if(netif_receive_skb(skb))

ret = GRO_DROP;

break;

......

}

在netif_receive_skb中,數據包將被送到協議棧中。聲明,以下的5.3、5.4、5.5也都屬于軟中斷的處理過程,只不過由于篇幅太長,單獨拿出來成小節。

5.3 網絡協議棧處理

netif_receive_skb函數會根據包的協議,假如是udp包,會將包依次送到ip_rcv(),udp_rcv()協議處理函數中進行處理。

網絡協議棧處理:

//file: net/core/dev.c

int netif_receive_skb(struct sk_buff *skb){

//RPS處理邏輯,先忽略 ......

return __netif_receive_skb(skb);

}

static int __netif_receive_skb(struct sk_buff *skb){

......

ret = __netif_receive_skb_core(skb, false);}static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc){

......

//pcap邏輯,這里會將數據送入抓包點。tcpdump就是從這個入口獲取包的 list_for_each_entry_rcu(ptype, &ptype_all, list) {

if(!ptype->dev || ptype->dev == skb->dev) {

if(pt_prev)

ret = deliver_skb(skb, pt_prev, orig_dev);

pt_prev = ptype;

}

}

......

list_for_each_entry_rcu(ptype,

&ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {

if(ptype->type == type &&

(ptype->dev == null_or_dev || ptype->dev == skb->dev ||

ptype->dev == orig_dev)) {

if(pt_prev)

ret = deliver_skb(skb, pt_prev, orig_dev);

pt_prev = ptype;

}

}

}

在__netif_receive_skb_core中,我看著原來經常使用的tcpdump的抓包點,很是激動,看來讀一遍源代碼時間真的沒白浪費。

接著__netif_receive_skb_core取出protocol,它會從數據包中取出協議信息,然后遍歷注冊在這個協議上的回調函數列表。ptype_base 是一個 hash table,在協議注冊小節我們提到過。ip_rcv 函數地址就是存在這個 hash table中的。

//file: net/core/dev.c

static inline int deliver_skb(struct sk_buff *skb,

struct packet_type *pt_prev,

struct net_device *orig_dev){

......

return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);

}

pt_prev->func這一行就調用到了協議層注冊的處理函數了。對于ip包來講,就會進入到ip_rcv(如果是arp包的話,會進入到arp_rcv)。

5.4 IP協議層處理

我們再來大致看一下linux在ip協議層都做了什么,包又是怎么樣進一步被送到udp或tcp協議處理函數中的。

//file: net/ipv4/ip_input.c

int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev){

......

return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish);

}

這里NF_HOOK是一個鉤子函數,當執行完注冊的鉤子后就會執行到最后一個參數指向的函數ip_rcv_finish。

static int ip_rcv_finish(struct sk_buff *skb){

......

if(!skb_dst(skb)) {

int err = ip_route_input_noref(skb, iph->daddr, iph->saddr, iph->tos, skb->dev);

...

}

......

return dst_input(skb);

}

跟蹤ip_route_input_noref 后看到它又調用了 ip_route_input_mc。

在ip_route_input_mc中,函數ip_local_deliver被賦值給了dst.input, 如下:

//file: net/ipv4/route.c

static int ip_route_input_mc(struct sk_buff *skb, __be32 daddr, __be32 saddr,u8 tos, struct net_device *dev, int our){

if(our) {

rth->dst.input= ip_local_deliver;

rth->rt_flags |= RTCF_LOCAL;

}

}

所以回到ip_rcv_finish中的return dst_input(skb):

/* Input packet from network to transport. */

static inline intdst_input(struct sk_buff *skb){

return skb_dst(skb)->input(skb);

}

skb_dst(skb)->input調用的input方法就是路由子系統賦的ip_local_deliver:

//file: net/ipv4/ip_input.c

int ip_local_deliver(struct sk_buff *skb){

/* * Reassemble IP fragments. */

if(ip_is_fragment(ip_hdr(skb))) {

if(ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))

return 0;

}

return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, skb, skb->dev, NULL, ip_local_deliver_finish);

}

static int ip_local_deliver_finish(struct sk_buff *skb){

......

int protocol = ip_hdr(skb)->protocol;

const struct net_protocol *ipprot;

ipprot = rcu_dereference(inet_protos[protocol]);

if(ipprot != NULL) {

ret = ipprot->handler(skb);

}

}

如協議注冊小節看到inet_protos中保存著tcp_rcv()和udp_rcv()的函數地址。這里將會根據包中的協議類型選擇進行分發,在這里skb包將會進一步被派送到更上層的協議中,udp和tcp。

5.5 UDP協議層處理

在協議注冊小節的時候我們說過,udp協議的處理函數是udp_rcv。

//file: net/ipv4/udp.c

int udp_rcv(struct sk_buff *skb){

return __udp4_lib_rcv(skb, &udp_table, IPPROTO_UDP);

}

int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable,

int proto){

sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable);

if(sk != NULL) {

intret = udp_queue_rcv_skb(sk, skb

}

icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);

}

__udp4_lib_lookup_skb是根據skb來尋找對應的socket,當找到以后將數據包放到socket的緩存隊列里。如果沒有找到,則發送一個目標不可達的icmp包。

//file: net/ipv4/udp.c

int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb){

......

if(sk_rcvqueues_full(sk, skb, sk->sk_rcvbuf))

goto drop;

rc = 0;

ipv4_pktinfo_prepare(skb);

bh_lock_sock(sk);

if(!sock_owned_by_user(sk))

rc = __udp_queue_rcv_skb(sk, skb);

else if(sk_add_backlog(sk, skb, sk->sk_rcvbuf)) {

bh_unlock_sock(sk);

goto drop;

}

bh_unlock_sock(sk);

return rc;

}

sock_owned_by_user判斷的是用戶是不是正在這個socker上進行系統調用(socket被占用),如果沒有,那就可以直接放到socket的接收隊列中。如果有,那就通過sk_add_backlog把數據包添加到backlog隊列。

當用戶釋放的socket的時候,內核會檢查backlog隊列,如果有數據再移動到接收隊列中。

sk_rcvqueues_full接收隊列如果滿了的話,將直接把包丟棄。接收隊列大小受內核參數net.core.rmem_max和net.core.rmem_default影響。

6、recvfrom系統調用

花開兩朵,各表一枝。上面我們說完了整個Linux內核對數據包的接收和處理過程,最后把數據包放到socket的接收隊列中了。那么我們再回頭看用戶進程調用recvfrom后是發生了什么。

我們在代碼里調用的recvfrom是一個glibc的庫函數,該函數在執行后會將用戶進行陷入到內核態,進入到Linux實現的系統調用sys_recvfrom。

在理解Linux對sys_revvfrom之前,我們先來簡單看一下socket這個核心數據結構。這個數據結構太大了,我們只把對和我們今天主題相關的內容畫出來。

如下(socket內核數據機構):

socket數據結構中的const struct proto_ops對應的是協議的方法集合。每個協議都會實現不同的方法集,對于IPv4 Internet協議族來說,每種協議都有對應的處理方法,如下。對于udp來說,是通過inet_dgram_ops來定義的,其中注冊了inet_recvmsg方法。

//file: net/ipv4/af_inet.c

const struct proto_ops inet_stream_ops = {

......

.recvmsg = inet_recvmsg,

.mmap = sock_no_mmap,

......

}

const struct proto_ops inet_dgram_ops = {

......

.sendmsg = inet_sendmsg,

.recvmsg = inet_recvmsg,

......

}

socket數據結構中的另一個數據結構struct sock *sk是一個非常大,非常重要的子結構體。其中的sk_prot又定義了二級處理函數。對于UDP協議來說,會被設置成UDP協議實現的方法集udp_prot。

//file: net/ipv4/udp.c

struct proto udp_prot = {

.name = "UDP",

.owner = THIS_MODULE,

.close = udp_lib_close,

.connect = ip4_datagram_connect,

......

.sendmsg = udp_sendmsg,

.recvmsg = udp_recvmsg,

.sendpage = udp_sendpage,

......

}

看完了socket變量之后,我們再來看sys_revvfrom的實現過程。

recvfrom函數內部實現過程:

在inet_recvmsg調用了sk->sk_prot->recvmsg:

//file: net/ipv4/af_inet.c

int inet_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,size_tsize, int flags){

......

err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT,

flags & ~MSG_DONTWAIT, &addr_len);

if(err >= 0)

msg->msg_namelen = addr_len;

return err;

}

上面我們說過這個對于udp協議的socket來說,這個sk_prot就是net/ipv4/udp.c下的struct proto udp_prot。由此我們找到了udp_recvmsg方法。

//file:net/core/datagram.c:EXPORT_SYMBOL(__skb_recv_datagram);

struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned int flags,int*peeked, int *off, int *err){

......

do{

struct sk_buff_head *queue = &sk->sk_receive_queue;

skb_queue_walk(queue, skb) {

......

}

/* User doesn't want to wait */

error = -EAGAIN;

if(!timeo)

goto no_packet;

} while(!wait_for_more_packets(sk, err, &timeo, last));

}

終于:我們找到了我們想要看的重點,在上面我們看到了所謂的讀取過程,就是訪問sk->sk_receive_queue。如果沒有數據,且用戶也允許等待,則將調用wait_for_more_packets()執行等待操作,它加入會讓用戶進程進入睡眠狀態。

7、本文小結

網絡模塊是操作系統內核中最復雜的模塊了,看起來一個簡簡單單的收包過程就涉及到許多內核組件之間的交互,如網卡驅動、協議棧、內核ksoftirqd線程等,看起來很復雜。本文想通過圖示的方式,盡量以容易理解的方式來將內核收包過程講清楚。

現在讓我們再串一串整個收包過程:當用戶執行完recvfrom調用后,用戶進程就通過系統調用進行到內核態工作了。如果接收隊列沒有數據,進程就進入睡眠狀態被操作系統掛起。這塊相對比較簡單,剩下大部分的戲份都是由Linux內核其它模塊來表演了。

首先在開始收包之前,操作系統要做許多的準備工作(以Linux為例):

1)創建ksoftirqd線程,為它設置好它自己的線程函數,后面指望著它來處理軟中斷呢;
2)協議棧注冊,linux要實現許多協議,比如arp,icmp,ip,udp,tcp,每一個協議都會將自己的處理函數注冊一下,方便包來了迅速找到對應的處理函數;
3)網卡驅動初始化,每個驅動都有一個初始化函數,內核會讓驅動也初始化一下。在這個初始化過程中,把自己的DMA準備好,把NAPI的poll函數地址告訴內核;
4)啟動網卡,分配RX,TX隊列,注冊中斷對應的處理函數。

以上是內核準備收包之前的重要工作,當上面都ready之后,就可以打開硬中斷,等待數據包的到來了。

當數據到來了以后,第一個迎接它的是網卡(我去,這不是廢話么):

1)網卡將數據幀DMA到內存的RingBuffer中,然后向CPU發起中斷通知;
2)CPU響應中斷請求,調用網卡啟動時注冊的中斷處理函數;
3)中斷處理函數幾乎沒干啥,就發起了軟中斷請求;
4)內核線程ksoftirqd線程發現有軟中斷請求到來,先關閉硬中斷;
5)ksoftirqd線程開始調用驅動的poll函數收包;
6)poll函數將收到的包送到協議棧注冊的ip_rcv函數中;
7)ip_rcv函數再講包送到udp_rcv函數中(對于tcp包就送到tcp_rcv)。

現在,我們可以回到開篇的問題了:我們在用戶層看到的簡單一行recvfrom,Linux內核要替我們做如此之多的工作,才能讓我們順利收到數據。

這還是簡簡單單的UDP,如果是TCP,內核要做的工作更多,不由得感嘆內核的開發者們真的是用心良苦。

理解了整個收包過程以后,我們就能明確知道Linux收一個包的CPU開銷了:

1)首先第一塊是用戶進程調用系統調用陷入內核態的開銷;
2)其次第二塊是CPU響應包的硬中斷的CPU開銷;
3)接著第三塊是ksoftirqd內核線程的軟中斷上下文花費的。

后面我們再專門發一篇文章實際觀察一下這些開銷。

另外:網絡收發中有很多末支細節咱們并沒有展開了說,比如說:no NAPI, GRO,RPS等。因為我覺得說的太對了反而會影響大家對整個流程的把握,所以盡量只保留主框架了,少即是多!

博主長期對外收徒,歡迎咨詢。

《編程語言設計和實現》《MUD游戲開發》《軟件破解和加密》《游戲輔助外掛》《JAVA開發》

以上課程非誠勿擾!

=================================
QQ:184377367
GOLang Q群:6848027
電子電路入門群 436173132
C/C++/QT群 1414577
單片機嵌入式群 306312845
MUD/LIB/巫師交流群 391486684
java/springboot/hadoop/ 群 4915800
WEB前端開發交流群 214737701
操作系統研發群:15375777
Linux公社Q群:812742841
匯編/輔助/破解新手群:755783453

本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

總結

以上是生活随笔為你收集整理的深入操作系统,从内核理解网络包的接收过程(Linux篇)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

狠狠干夜夜 | 久久99热这里只有精品 | 久久精品影片 | 亚洲综合婷婷 | 91麻豆国产福利在线观看 | www九九热 | 亚洲高清视频在线播放 | 91传媒在线观看 | 国产成人99av超碰超爽 | 国产精品久久久久永久免费观看 | 99久久国产免费,99久久国产免费大片 | 久久这里只有精品首页 | 狠狠的干| 午夜三级影院 | 91精品免费在线视频 | 天天干天天操天天 | 狠狠色丁香婷婷综合视频 | 日本中文字幕在线免费观看 | 久草视频在线新免费 | 亚洲最新av在线 | 夜夜夜夜猛噜噜噜噜噜初音未来 | 亚洲激情在线 | 岛国一区在线 | 久久久国产精品网站 | 国产精品99久久久久的智能播放 | 五月天综合网站 | 精品久久久国产 | 97超碰资源网 | 九色porny真实丨国产18 | 国产精品精品国产婷婷这里av | 色噜噜在线观看视频 | 五月婷影院| 精品国产成人av在线免 | 97精品国产97久久久久久免费 | 婷婷综合亚洲 | 日韩夜夜爽 | 亚洲视屏在线播放 | 中文字幕在线视频网站 | 少妇bbbb揉bbbb日本 | 久久午夜精品影院一区 | 亚洲午夜久久久影院 | 91精品高清| 欧美不卡视频在线 | 97免费在线视频 | 国产精品v a免费视频 | 视频成人免费 | 日韩中文字幕视频在线观看 | 日韩免费一区二区 | 色偷偷888欧美精品久久久 | 天天操天天干天天爽 | 日韩欧美国产免费播放 | 亚洲免费精彩视频 | 97碰碰视频| 人人澡人人添人人爽一区二区 | 国产精品久久久久久久久久久久午夜 | 成人播放器 | 日韩欧美大片免费观看 | 波多野结衣精品 | 久草精品视频 | 久久精品国产精品亚洲精品 | 国产资源免费在线观看 | 国产一级视频在线免费观看 | 日日躁夜夜躁xxxxaaaa | 天天天天爱天天躁 | 不卡视频国产 | 欧洲性视频 | 不卡av电影在线 | 在线免费观看国产 | 免费人成网 | 午夜精品电影 | 亚洲精品国久久99热 | 久免费视频 | 狠狠狠狠狠狠狠干 | av电影免费看 | 国产色视频123区 | 在线观看aaa | 99精品视频一区 | 蜜臀av夜夜澡人人爽人人 | 色婷婷激情五月 | 4hu视频 | 久久久久亚洲天堂 | 中文字幕av有码 | 日韩在线观看视频中文字幕 | 精品资源在线 | 三级免费黄 | 很污的网站 | 9999国产精品 | 国产美女精品人人做人人爽 | 激情五月六月婷婷 | 亚洲影视九九影院在线观看 | 国产精品久久久久久一区二区三区 | 91爱爱中文字幕 | 欧美综合在线视频 | 国产免费观看高清完整版 | 成人试看120秒 | 色视频在线 | 超碰人人99| 久久夜色精品国产欧美一区麻豆 | 91热爆视频 | 片黄色毛片黄色毛片 | 国产精品99久久久精品 | 福利一区二区在线 | 色无五月 | 色人久久| 激情av一区二区 | www178ccom视频在线 | 天天搞天天干 | 在线播放 日韩专区 | 国产精品美女久久久久久 | 国产成人久久av | 国产一区二区成人 | 91人人澡人人爽 | 久久九九精品久久 | 狠狠色噜噜狠狠 | 黄色大片日本 | 国产r级在线观看 | 深爱五月激情网 | 久久久综合香蕉尹人综合网 | 在线观看视频一区二区三区 | 亚洲精品字幕在线 | 国产亚洲精品久久久网站好莱 | 欧美精品免费在线观看 | 一区二区精品在线视频 | 国产精品久久久久久久毛片 | 日韩网页| 亚洲精品男女 | 在线观看mv的中文字幕网站 | 色综合小说 | 久久激情小说 | 国产女人免费看a级丨片 | 最近2019中文免费高清视频观看www99 | 亚洲午夜久久久久 | 福利片视频区 | 天天爽人人爽夜夜爽 | 九九视频在线 | 国产精品初高中精品久久 | 在线91观看 | 91最新网址 | 日韩精品一区二区在线观看 | 久久亚洲电影 | 九色激情网 | 日本精品一区二区三区在线观看 | 国产精品久久久久久久久久久久午夜 | 日韩毛片在线播放 | 国产免费高清视频 | 人人爽人人爽人人片 | 午夜av一区 | 日韩欧美极品 | 国产精品福利小视频 | 午夜精品久久久久久久99无限制 | 国产在线97 | 婷婷综合久久 | 国产午夜精品一区二区三区在线观看 | 91黄色在线视频 | 精品福利在线观看 | 99精品欧美一区二区 | 四虎国产精品永久在线国在线 | 久色婷婷 | 超碰97网站| 波多野结衣一区二区 | 久久这里只有精品9 | 在线观看网站你懂的 | 久草精品免费 | 91精品国自产拍天天拍 | 麻豆成人精品视频 | 亚洲精选国产 | 国产一区二区三区高清播放 | 国产一区二区在线播放 | 97热在线观看 | bayu135国产精品视频 | 日本成人中文字幕在线观看 | 国产最新精品视频 | 99九九免费视频 | 国产伦理久久 | 最近日本中文字幕a | 免费a现在观看 | 中文字幕网址 | 免费观看91视频 | 免费国产在线视频 | 99免费在线视频 | 最近免费中文字幕 | 亚洲男模gay裸体gay | 97在线超碰 | 超碰人人舔 | 黄色一区三区 | 日产乱码一二三区别免费 | 天天五月天色 | 国产亚洲精品久久久久久久久久久久 | 中文理论片 | 久久精品高清视频 | 日韩中出在线 | 天天操天天干天天综合网 | 亚洲精品乱码白浆高清久久久久久 | 欧美日韩aa| 狠狠干电影| 日韩免费电影一区二区 | 久久国产精品一区二区 | 一本一道久久a久久精品蜜桃 | 久久久久久久久久久久久9999 | 国产91精品一区二区麻豆亚洲 | 久草视频在线资源站 | 日韩免费看视频 | 久久久久亚洲精品男人的天堂 | 成人国产精品 | 日韩小视频 | 在线观看视频你懂的 | 麻豆 91 在线 | 九九视频在线播放 | 91九色精品女同系列 | 婷婷色视频 | 天天摸天天操天天爽 | 精品国产亚洲一区二区麻豆 | 久久久精品国产免费观看同学 | 蜜臀久久99静品久久久久久 | 91精品爽啪蜜夜国产在线播放 | 狠狠色丁香婷婷综合久久片 | 亚洲成人av一区 | 91桃色国产在线播放 | 国产91精品在线播放 | 国产精品久久久久久五月尺 | 久久影院一区 | 少妇bbbb搡bbbb桶 | 亚洲精品中文字幕视频 | 91成人免费在线视频 | 中文字幕视频网站 | 日韩精品在线观看av | 久久男人视频 | 久久久18| 蜜臀av性久久久久蜜臀aⅴ四虎 | 欧美一区三区四区 | 狠狠色噜噜狠狠 | 在线成人中文字幕 | 久久99国产视频 | 最新av免费在线观看 | 亚洲成色| 久久久免费看 | 正在播放亚洲精品 | 久久久九色精品国产一区二区三区 | 久久久久免费精品国产小说色大师 | 最新色视频 | 成年人看片网站 | 国产精品99久久久久久久久 | 狠狠色狠狠色综合系列 | 国产成人精品电影久久久 | 欧美日韩综合在线 | 午夜视频久久久 | 久久久国产精品久久久 | 精品久久精品久久 | 黄网站免费大全入口 | 在线观看午夜av | 最近中文字幕第一页 | 亚洲综合视频在线 | www.eeuss影院av撸 | 三级视频日韩 | 精品视频成人 | 夜又临在线观看 | 99久久精品国产欧美主题曲 | 波多野结衣一区三区 | 97在线资源 | 91亚洲精品久久久蜜桃 | 久草在线播放视频 | 久草精品在线播放 | 中文字幕视频一区二区 | 一区二区欧美在线观看 | 97国产在线视频 | 欧美一级日韩免费不卡 | 黄色不卡av| 中文字幕高清有码 | 国产精品欧美 | 香蕉久草在线 | 一区二区三区高清在线观看 | 精品国产伦一区二区三区 | 99精品视频在线观看播放 | 国语精品视频 | 日韩av片免费在线观看 | 九九99靖品 | 日日天天av| 久草在线最新免费 | 亚洲成人av一区 | www.色五月.com| 久草国产在线观看 | 久久草网站 | 亚洲精品动漫在线 | 国产精品一区二区中文字幕 | 97精品国产97久久久久久粉红 | 国产18精品乱码免费看 | 在线日韩中文 | 国产精品国产三级国产不产一地 | 成人日韩av| 国产成人精品一区一区一区 | 开心激情五月婷婷 | 亚洲精品影视在线观看 | 国产九色视频在线观看 | 免费在线观看不卡av | 日韩一区二区三区在线观看 | 日躁夜躁狠狠躁2001 | 国产亚洲精品无 | 六月丁香婷婷久久 | 亚洲日韩中文字幕在线播放 | 国产在线最新 | 亚洲国产精品影院 | 日韩免费观看一区二区 | 中文字幕日韩国产 | 精品国产伦一区二区三区观看体验 | 国产又粗又猛又黄又爽 | av先锋中文字幕 | 99精品视频免费在线观看 | 日韩精品在线观看视频 | av电影一区 | 国产精品va在线观看入 | 永久免费av在线播放 | 成人午夜av电影 | 国产毛片aaa | 国产精品国产三级国产不产一地 | 黄色片毛片 | 欧美久久久久久久久久久 | 久久视频免费看 | 欧美日韩国产色综合一二三四 | 久久99精品一区二区三区三区 | www.97色.com | 欧美在线一级片 | 久久五月情影视 | 久草视频在 | 精品在线视频一区二区三区 | 999国内精品永久免费视频 | 日日夜夜噜噜噜 | 五月激情婷婷丁香 | 粉嫩av一区二区三区四区五区 | 日韩免费在线观看 | 日韩精品不卡 | 欧美肥妇free | 亚洲精品网页 | 久久永久免费视频 | 欧美精品乱码99久久影院 | 欧美色操 | 免费视频一级片 | 成人h视频在线播放 | 久草影视在线 | 天天干天天在线 | 国产成人一区二区三区电影 | 中文字幕专区高清在线观看 | 在线观看免费福利 | 粉嫩aⅴ一区二区三区 | 欧美视频在线观看免费网址 | 色999在线| 亚洲伦理精品 | 2018亚洲男人天堂 | 国产成人精品一区二三区 | 国产很黄很色的视频 | 久久影视精品 | 日韩欧美视频 | 亚洲精选国产 | 日韩午夜视频在线观看 | 久久伦理 | 免费黄色网止 | 日本久久不卡视频 | 免费观看丰满少妇做爰 | 日韩欧美在线免费 | 国产美女精品人人做人人爽 | 国产免费专区 | 国产高清视频网 | 一区二区视频欧美 | 午夜久久影视 | 天天综合狠狠精品 | 中文字幕欧美日韩va免费视频 | 国产精品免费在线播放 | 久久久久久国产精品 | 亚洲午夜精品一区二区三区电影院 | 黄污在线看 | 亚洲欧美日韩在线一区二区 | 91视频 - 114av| 人人模人人爽 | 亚洲成人精品在线观看 | 韩国在线一区二区 | 久久久精品国产一区二区 | 91 在线视频 | 日韩在线观看你懂得 | 欧美在线91 | 欧美日韩视频在线播放 | av电影免费在线 | 99人久久精品视频最新地址 | 国产一线二线三线性视频 | 午夜精品一区二区三区免费 | 欧美ⅹxxxxxx| 韩日av在线 | 久久9999久久| 青青色影院 | a视频免费看 | 免费高清男女打扑克视频 | 欧洲精品久久久久毛片完整版 | 久久天天躁狠狠躁亚洲综合公司 | 激情视频国产 | 激情xxxx| www最近高清中文国语在线观看 | 91亚洲网| 日日日操 | 久久久www成人免费毛片麻豆 | 午夜三级福利 | 国产69精品久久久久久久久久 | 人人cao| 亚洲成人在线免费 | 日韩成人免费观看 | 欧美精品在线观看 | 人人爽久久涩噜噜噜网站 | 97av视频在线观看 | 国产精品大尺度 | 最新av免费在线观看 | 欧美精品九九99久久 | 美女视频黄是免费的 | 亚洲乱码久久久 | 特级西西444www大胆高清无视频 | 在线免费观看欧美日韩 | 97国产大学生情侣白嫩酒店 | 国产1级毛片 | 日韩欧美视频二区 | 91av在线视频免费观看 | 麻豆传媒视频观看 | 91麻豆精品国产91久久久更新时间 | 在线影院中文字幕 | 国产v在线观看 | 国产精品久久久久久一区二区三区 | 91九色免费视频 | av免费网站 | 久久久久国产一区二区三区 | 狠狠操综合 | 在线之家免费在线观看电影 | 色搞搞| 久久精品视频在线 | 三级视频日韩 | 久久国语露脸国产精品电影 | 欧美黑人性猛交 | 蜜桃视频成人在线观看 | 国产亚洲视频在线 | 天天精品视频 | 久草青青在线观看 | 国产精品久久久久久久久久了 | 玖玖在线免费视频 | 视频成人免费 | 久精品视频在线观看 | 色婷婷av一区二 | 久久在线免费观看视频 | 欧美色精品天天在线观看视频 | 国产精品亚洲精品 | 国产91大片 | 福利一区二区 | 精品国产诱惑 | 天天激情| 夜夜夜夜夜夜操 | 国产亚洲精品无 | 亚洲精品免费在线观看视频 | 麻豆视频免费在线 | 一级性生活片 | 麻花传媒mv免费观看 | 久久久久福利视频 | 狠狠色丁香婷婷综合最新地址 | 日日精品| 国产精品aⅴ | 69亚洲乱 | 91av在线不卡 | 天天躁日日躁狠狠躁 | 丁香综合激情 | 欧美天堂久久 | 日本二区三区在线 | 中文字幕在线观看不卡 | 99热九九这里只有精品10 | 国产香蕉视频在线播放 | 精品a视频 | 久综合网| 久久久av电影 | 午夜视频久久久 | 精品国产欧美 | 97视频一区 | 97人人模人人爽人人喊中文字 | 欧洲不卡av | 国产在线91精品 | 欧美一区二区在线免费观看 | 字幕网av | 国产亚洲精品免费 | av网站地址 | 美女在线观看av | 国产精品一区二区免费 | 特级西西444www大精品视频免费看 | 日本xxxxav | 日日夜夜网 | 91亚洲精品久久久久图片蜜桃 | 天天性天天草 | 精品国产123| 一区二区三区四区精品 | 激情影音先锋 | 久久 一区 | 爱av在线网 | 91精品免费 | 亚洲精品视频一二三 | 在线观看av网 | 日韩欧美视频在线观看免费 | 天天操夜操视频 | 黄网站色视频免费观看 | 国产精品成人久久久 | 看av免费网站| 色婷婷六月天 | 欧美日韩一区二区三区视频 | 狠狠干天天射 | 国产无遮挡又黄又爽在线观看 | 精品美女在线视频 | 永久免费av在线播放 | 国产精品久久久久久影院 | 久久理伦片 | 亚洲精品日韩一区二区电影 | 亚洲欧美乱综合图片区小说区 | 久久久91精品国产一区二区三区 | 免费看av片网站 | 久久免费的视频 | 综合黄色网 | 国产丝袜在线 | 欧美日韩性生活 | 欧美日韩国产mv | 高清免费在线视频 | 在线观看中文字幕视频 | 午夜视频久久久 | 日日摸日日 | 色天天中文 | 欧美一区二区三区免费看 | 日本99久久 | 精品美女在线视频 | 在线观看免费视频你懂的 | 国产高清 不卡 | 98超碰人人 | 午夜婷婷在线播放 | 亚洲国产中文字幕 | av在线播放观看 | 激情欧美一区二区三区免费看 | 9i看片成人免费看片 | 97在线观视频免费观看 | 欧美a在线免费观看 | 91成人亚洲 | 国产精品久久人 | 国产精品国产三级国产aⅴ入口 | 一级α片免费看 | 国产无套精品久久久久久 | 成人久久18免费网站麻豆 | www.天天综合 | 国产麻豆精品免费视频 | 午夜精品一区二区三区在线 | 在线播放日韩av | 国产r级在线观看 | 人人爽人人爽av | 久操久| 久久免费的视频 | 在线观看成人一级片 | 99r在线 | 狠色在线| 99国内精品久久久久久久 | 亚洲伊人色 | 91视频com| 麻豆视频免费在线播放 | 欧美日韩一区久久 | 99久久99视频| 亚洲婷婷免费 | 日日夜夜免费精品视频 | 99婷婷狠狠成为人免费视频 | 久久婷婷久久 | 九九视频这里只有精品 | 日韩视频一区二区三区在线播放免费观看 | 久久女同性恋中文字幕 | 黄色午夜| 999久久国精品免费观看网站 | 久草资源免费 | 日日噜噜噜噜夜夜爽亚洲精品 | 婷婷色 亚洲 | av在线亚洲天堂 | 久久avav | 亚洲免费精品视频 | 久久婷婷激情 | 免费观看v片在线观看 | 天天干天天干天天干天天干天天干天天干 | 夜夜嗨av色一区二区不卡 | 精品久久久久久久久久久久久 | 欧美在线视频一区二区 | 一区中文字幕电影 | 青青河边草免费视频 | 99视频精品 | 香蕉久草| 国内精品久久久久久久久久 | 国产精品入口传媒 | 日韩免费一级a毛片在线播放一级 | 婷婷色伊人 | 久久成人毛片 | 欧美激情综合五月色丁香小说 | 国产视频精品在线 | 久久久av电影 | 国产小视频91 | 五月婷香蕉久色在线看 | 夜夜操天天干 | 在线免费试看 | 日韩免费在线 | 日本韩国精品一区二区在线观看 | 亚洲午夜精品一区二区三区电影院 | 久久久久人人 | 91在线精品一区二区 | 欧美大片www | 黄色日本片 | 天天草天天爽 | 国产一区二区精 | av黄色免费看 | 欧美成人h版 | 天天色天天干天天色 | 在线亚洲日本 | 91桃色免费观看 | 国产做a爱一级久久 | 免费在线观看日韩视频 | 久久综合色播五月 | 精品久久中文 | 色吊丝在线永久观看最新版本 | 久热久草在线 | av黄色一级片 | 欧美日韩调教 | 日韩久久久久久久 | 国产精品久久久一区二区三区网站 | 尤物一区二区三区 | 激情综合网五月婷婷 | 中文字幕区 | 久久久久久久久久久免费 | 中文在线字幕免费观看 | 中文字幕资源网在线观看 | 精品女同一区二区三区在线观看 | 人人舔人人干 | 在线免费观看视频一区二区三区 | 韩国一区二区在线观看 | 免费观看黄色12片一级视频 | 成人在线超碰 | 91传媒在线| 91电影福利 | 久久久久亚洲精品成人网小说 | 亚洲另类视频 | 免费看片网站91 | 激情五月av| 成人xxxx| 天天色天天色 | se婷婷 | 国产成年免费视频 | 97超碰超碰 | 精品一区二区在线播放 | 香蕉成人在线视频 | 四虎影视精品 | 在线91观看 | 成人午夜电影网站 | 国产精品99久久久久久武松影视 | 草久视频在线 | www.在线观看av | 五月婷婷综 | 在线免费观看视频一区 | 中文免费在线观看 | 激情婷婷综合 | 尤物九九久久国产精品的分类 | 99这里只有 | 黄色精品在线看 | 国产精品乱码久久久 | 国产精品一区二区三区久久久 | 国产欧美日韩精品一区二区免费 | 久久久国产99久久国产一 | 成人中文字幕+乱码+中文字幕 | av丝袜制服 | 91丨九色丨勾搭 | 在线免费av电影 | 成人av电影免费 | 毛片.com| 毛片基地黄久久久久久天堂 | 欧美91精品久久久久国产性生爱 | 国产手机在线精品 | 久久在线观看视频 | 天堂久色 | 91精品国产九九九久久久亚洲 | 色黄www小说 | 免费在线观看黄 | www.成人sex | 亚洲精品中文字幕视频 | 亚洲一区 av | 欧美日韩另类在线 | 亚洲黄a| 国产免费不卡 | 色鬼综合网 | 在线观看一区二区精品 | 成人黄色大片网站 | 99精品色| 亚洲精品高清一区二区三区四区 | 国产第一页精品 | 91精品国产乱码在线观看 | 国产a高清| 91人人澡| 色婷婷激情电影 | 久久黄色a级片 | 97色免费视频 | 欧美做受xxx | 激情丁香久久 | 天天干天天草天天爽 | 一区二区三区电影在线播 | 激情久久影院 | 欧美午夜精品久久久久久孕妇 | 91精品在线观看视频 | 中文字幕久久精品亚洲乱码 | 怡红院av | 久久歪歪 | 日本不卡123 | 国产区在线看 | 国产精品久久久免费看 | 国产午夜一区二区 | 激情黄色av | 91重口视频 | 狠狠色丁香婷婷综合 | 欧美日一级片 | www.天堂av | 国产日韩精品一区二区三区在线 | 天天天天色综合 | 亚洲成人国产精品 | 99人久久精品视频最新地址 | 伊人久久国产精品 | 国产日韩精品在线观看 | japanesefreesex中国少妇 | 亚洲综合一区二区精品导航 | 99国内精品 | 成人在线黄色 | 美女黄网站视频免费 | 欧美性生活小视频 | 99爱精品在线 | 久草在线久草在线2 | 免费观看久久久 | 亚洲精品伦理在线 | 91成人免费看片 | 国产成人在线精品 | 黄色网址国产 | 日韩黄色免费在线观看 | 国产手机在线观看视频 | 久久精品牌麻豆国产大山 | 天天天干天天射天天天操 | 国内一级片在线观看 | 亚洲成人软件 | 久久成人免费视频 | 亚洲国产wwwccc36天堂 | 久久精品视频网站 | 青春草免费在线视频 | 久色婷婷 | 91精品国自产拍天天拍 | a视频在线观看免费 | 色综合久久久久综合99 | 在线观看日韩一区 | 色综合久久精品 | 黄色网中文字幕 | 91精品国产92久久久久 | 久久综合九色综合欧美就去吻 | 麻豆国产在线播放 | 亚洲国产成人在线观看 | 毛片无卡免费无播放器 | 成 人 黄 色 视频免费播放 | 久草手机视频 | 国产高清第一页 | 97精品电影院 | 亚洲精品在线免费播放 | 亚洲精品99久久久久中文字幕 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 日韩av高潮 | 国产亚洲综合性久久久影院 | 国产精品视频专区 | 一区二区三区在线视频观看58 | 91精品久久久久久久久久久久久 | av色图天堂网| 成 人 免费 黄 色 视频 | 日韩激情中文字幕 | 久久久久久国产精品 | 爱爱av网| 中文字幕在线观看完整版电影 | 免费男女网站 | 久久综合五月天 | 欧美日本在线视频 | 国内精品视频一区二区三区八戒 | 日本成址在线观看 | 成年人免费观看国产 | 99久久久久久 | 夜夜躁日日躁狠狠久久av | 久久精品99精品国产香蕉 | 免费色黄 | 91成年人在线观看 | 波多野结衣电影一区 | 中文字幕中文字幕中文字幕 | 深爱激情站 | 欧美日韩网址 | 久久只有精品 | 亚洲一级性 | 精品国产精品久久一区免费式 | 国产精品美女久久久免费 | 欧美日韩免费一区二区三区 | 天天色天天操天天爽 | 久久蜜桃av | 久久精品2 | 最新国产在线观看 | 久久精品中文字幕免费mv | 国产一区免费观看 | 狠狠88综合久久久久综合网 | 99在线视频观看 | 久久综合免费 | 婷婷丁香六月天 | 日韩电影在线观看一区 | 综合色综合 | 伊人宗合网 | 麻豆精品在线视频 | 天天操天天射天天操 | 四虎成人精品永久免费av | 懂色av一区二区在线播放 | 亚洲码国产日韩欧美高潮在线播放 | 超碰资源在线 | 亚洲一级久久 | 久久五月婷婷丁香 | 亚洲精品字幕 | 久久桃花网 | 人人舔人人爽 | 字幕网在线观看 | 九色视频网 | 精品女同一区二区三区在线观看 | 中文字幕在线观看完整版电影 | 美女黄频在线观看 | 国产精品视频app | 亚洲成av人片 | 亚洲首页| 中文字幕精品一区二区精品 | 国产福利中文字幕 | 精品产品国产在线不卡 | 青春草视频在线播放 | 久久久久久综合 | 欧美在线观看视频一区二区三区 | 国产剧情一区二区 | 国产黄色免费看 | 99riav1国产精品视频 | 一本色道久久精品 | 在线观看www91 | 在线国产黄色 | 在线观看 亚洲 | 丁香婷婷射| 亚洲午夜精品久久久 | 亚洲成人黄色网址 | 国产一区二区三区在线 | 久草在线免费色站 | 色激情五月 | 韩国精品在线观看 | 亚洲少妇xxxx| 狠狠综合网 | 天天操夜夜干 | 99精品视频在线播放观看 | 亚洲精品视频一二三 | 毛片一区二区 | 欧美在线观看禁18 | 伊人精品在线 | 亚洲人成精品久久久久 | 色播五月激情综合网 | 99这里只有精品视频 | 奇米四色影狠狠爱7777 | 99久久精品国产一区二区成人 | 日韩视频免费观看高清完整版在线 | 久久成人黄色 | 成人a视频 | 成人av直播| 免费观看www小视频的软件 | 成人免费视频网 | 久久综合九色综合97_ 久久久 | 国产免费又黄又爽 | 久久亚洲区 | 国产精品婷婷 | 亚洲在线看 | 婷婷六月在线 | 在线黄色免费 | 亚洲黄电影 | 国产精品99免费看 | 日批视频在线播放 | 久久综合九色欧美综合狠狠 | 天天综合网在线 | 国产美女视频免费观看的网站 | 日韩av专区 | 婷婷综合五月 | 天天操,夜夜操 | 久黄色 | 综合婷婷| 综合网在线视频 | av片子在线观看 | 97免费视频在线播放 | 亚洲高清网站 | 国产一区二区三区免费视频 | 久久夜夜操| 国产精品扒开做爽爽的视频 | 色搞搞 | 九九九热精品免费视频观看网站 | 人人躁 | 夜夜操狠狠干 | 亚洲免费视频在线观看 | 免费日韩一区二区三区 | 欧美成人精品三级在线观看播放 | 婷婷六月在线 | 久久免费视频8 | 干综合网 | 日韩1页| 久久人人看 | 又黄又刺激视频 | 久久精品直播 | 亚洲另类人人澡 | 免费观看黄 | 亚洲伊人成综合网 | 日韩精品欧美一区 | 久久免费视频这里只有精品 | 国产小视频免费在线观看 | 高清精品在线 | 久久特级毛片 | 最近字幕在线观看第一季 | 99久久久久久久久 | 国产小视频在线免费观看视频 | 日韩深夜在线观看 | 久久久精品综合 | 人人超碰免费 | 欧美高清成人 | 婷婷在线免费视频 | 热热热热热色 | 亚洲欧美国产精品va在线观看 | 久久私人影院 | 久久这里只有精品视频首页 | 国产精品扒开做爽爽的视频 | 日韩r级电影在线观看 | av在线电影播放 | 500部大龄熟乱视频使用方法 | 国产成人精品一区二区三区免费 | 亚洲伊人网在线观看 | 91视频88av | 亚洲在线激情 | 最近中文字幕mv免费高清在线 | 亚洲精品免费在线观看视频 | 亚洲视频999 | 日韩精品一区二区三区视频播放 | 福利视频入口 | 激情综合啪 | 中文区中文字幕免费看 | 国产精品一区二区三区免费看 | 91成人小视频 | 中文字幕一区二区三区四区 | 欧美91精品久久久久国产性生爱 | 免费看的黄色 | 在线观看av小说 | 国产精品久久一 | 夜添久久精品亚洲国产精品 | 免费观看一区 | 国产高清免费在线观看 | 久久五月精品 | 久久福利精品 | 国产一区二区在线免费观看 | 黄色av网站在线观看 | 国产91精品高清一区二区三区 | 欧美人操人 | 五月天激情视频 | av在线网站观看 | 97精品视频在线播放 | 国产三级午夜理伦三级 | 久久免费试看 | 91视频国产免费 | 国产二区电影 | 免费观看的av网站 | 91视频在线播放视频 | 色片网站在线观看 | 色网免费观看 | 欧美激情精品久久久久久变态 | 日日天天av| 69中文字幕 | 国产真实精品久久二三区 | 亚洲va欧美va| 一级一片免费观看 | 夜添久久精品亚洲国产精品 | 国产精品一区二区免费在线观看 | 天天射综合| 日韩av成人在线 | www.福利| 狠狠狠狠狠色综合 | 久久99国产精品免费网站 | 日韩成人xxxx | 91香蕉视频好色先生 | 亚洲精品日韩一区二区电影 | 国产91aaa| 欧美日韩在线网站 | 综合久久影院 | 免费成人结看片 | 91重口视频| 久热爱 | 精品电影一区二区 | 国产精品久久久久久久久久东京 | 五月婷婷天堂 | 日本aaa在线观看 | 亚洲男模gay裸体gay | 操老逼免费视频 | 超碰人人91 | 亚洲视频大全 | 亚洲欧美在线综合 | 成人 亚洲 欧美 | 91精品老司机久久一区啪 | 久久任你操 | 天堂在线视频免费观看 | 在线免费黄网站 |