久久福利_99r_国产日韩在线视频_直接看av的网站_中文欧美日韩_久久一

您的位置:首頁技術(shù)文章
文章詳情頁

深入理解Linux網(wǎng)絡(luò)之內(nèi)核是如何發(fā)送網(wǎng)絡(luò)包的

瀏覽:203日期:2023-09-14 20:49:07
目錄一、相關(guān)實際問題二、網(wǎng)絡(luò)包發(fā)送過程總覽三、網(wǎng)卡啟動準備四、數(shù)據(jù)從用戶進程到網(wǎng)卡的詳細過程1)系統(tǒng)調(diào)用實現(xiàn)2)傳輸層處理3)網(wǎng)絡(luò)層發(fā)送處理4)鄰居子系統(tǒng)5)網(wǎng)絡(luò)設(shè)備子系統(tǒng)6)軟中斷調(diào)度7)igb網(wǎng)卡驅(qū)動發(fā)送五、RingBuffer內(nèi)存回收六、問題解答一、相關(guān)實際問題查看內(nèi)核發(fā)送數(shù)據(jù)消耗的CPU時應(yīng)該看sy還是si在服務(wù)器上查看/proc/softirqs,為什么NET_RX要比NET_TX大得多發(fā)送網(wǎng)絡(luò)數(shù)據(jù)的時候都涉及那些內(nèi)存拷貝操作零拷貝到底是怎么回事為什么Kafka的網(wǎng)絡(luò)性能很突出二、網(wǎng)絡(luò)包發(fā)送過程總覽

調(diào)用系統(tǒng)調(diào)用send發(fā)送內(nèi)存拷貝協(xié)議處理進入驅(qū)動RingBuffer實際發(fā)送中斷通知發(fā)送完成清理RingBuffer三、網(wǎng)卡啟動準備

現(xiàn)在的服務(wù)器上的網(wǎng)卡一般都是支持多隊列的。每一個隊列都是由一個RingBuffer表示的,開啟了多隊列以后的網(wǎng)卡就會有多個RingBuffer。

網(wǎng)卡啟動時最重要的任務(wù)就是分配和初始化RingBuffer,在網(wǎng)卡啟動的時候會調(diào)用到__igb_open函數(shù),RingBuffer就是在這里分配的。

static int __igb_open(struct net_device *netdev, bool resuming){ // 分配傳輸描述符數(shù)組 err = igb_setup_all_tx_resources(adpater); // 分配接收描述符數(shù)組 err = igb_setup_all_rx_resources(adpater); // 注冊中斷處理函數(shù) 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)); ......}static int igb_setup_all_tx_resources(struct igb_adapter *adapter){ // 有幾個隊列就構(gòu)造幾個RingBuffer for(int i = 0; i < adapter->num_tx_queues; i++) { igb_setup_tx_resources(adapter->tx_ring[i]); }}

igb_setup_tx_resources內(nèi)部也是申請了兩個數(shù)組,igb_tx_buffer數(shù)組和e1000_adv_tx_desc數(shù)組,一個供內(nèi)核使用,一個供網(wǎng)卡硬件使用。

在這個時候它們之間還沒什么關(guān)系,將來在發(fā)送數(shù)據(jù)的時候這兩個數(shù)組的指針都指向同一個skb,這樣內(nèi)核和硬件就能共同訪問同樣的數(shù)據(jù)了。

內(nèi)核往skb寫數(shù)據(jù),網(wǎng)卡硬件負責發(fā)送。

硬中斷的處理函數(shù)igb_msix_ring也是在__igb_open函數(shù)中注冊的。

四、數(shù)據(jù)從用戶進程到網(wǎng)卡的詳細過程1)系統(tǒng)調(diào)用實現(xiàn)

send系統(tǒng)調(diào)用內(nèi)部真正使用的是sendto系統(tǒng)調(diào)用,主要做了兩件事:

在內(nèi)核中把真正的socket找出來構(gòu)造struct msghdr對象, 把用戶傳入的數(shù)據(jù),比如buffer地址(用戶待發(fā)送數(shù)據(jù)的指針)、數(shù)據(jù)長度、發(fā)送標志都裝進去SYS_CALL_DEFINE6(sendto, ......){ sock = sockfd_lookup_light(fd, &err, &fput_needed); struct msghdr msg; struct iovec iov; iov.iov_base = buff; iov.iov_len = len; msg.msg_iovlen = &iov; msg.msg_iov = &iov; msg.msg_flags = flags; ...... sock_sendmsg(sock, &msg, len);}

sock_sendmsg經(jīng)過一系列調(diào)用,最終來到__sock_sendmsg_nosec中調(diào)用sock->ops->sendmsg

對于AF_INET協(xié)議族的socket,sendmsg的實現(xiàn)統(tǒng)一為inet_sendmsg

2)傳輸層處理

1. 傳輸層拷貝

在進入?yún)f(xié)議棧inet_sendmsg以后,內(nèi)核接著會找到sock中具體的協(xié)議處理函數(shù),對于TCP協(xié)議而言,sk_prot操作函數(shù)集實例為tcp_prot,其中.sendmsg的實現(xiàn)為tcp_sendmsg(對于UDP而言中的為udp_sendmsg)。

int inet_sendmsg(......){ ...... return sk->sk_prot->sendmsg(iocb, sk, msg, size);}int tcp_sendmsg(......){ ...... // 獲取用戶傳遞過來的數(shù)據(jù)和標志 iov = msg->msg_iov; // 用戶數(shù)據(jù)地址 iovlen = msg->msg_iovlen; // 數(shù)據(jù)塊數(shù)為1 flags = msg->msg_flags; // 各種標志 copied = 0; // 已拷貝到發(fā)送隊列的字節(jié)數(shù) // 遍歷用戶層的數(shù)據(jù)塊 while(--iovlen >= 0) {// 待發(fā)送數(shù)據(jù)塊的長度size_t seglen = iov->len; // 待發(fā)送數(shù)據(jù)塊的地址unsigned char __user *from = iov->iov_base;// 指向下一個數(shù)據(jù)塊iovlen++; ...... while(seglen > 0) { int copy = 0; int max = size_goal; // 單個skb最大的數(shù)據(jù)長度 skb = tcp_write_queue_tail(sk); // 獲取發(fā)送隊列最后一個skb // 用于返回發(fā)送隊列第一個數(shù)據(jù)包,如果不是NULL說明還有未發(fā)送的數(shù)據(jù) if(tcp_send_head(sk)) { ...copy = max - skb->len; // 該skb還可以存放的字節(jié)數(shù) } // 需要申請新的skb if(copy <= 0) {// 發(fā)送隊列的總大小大于等于發(fā)送緩存的上限,或尚發(fā)送緩存中未發(fā)送的數(shù)據(jù)量超過了用戶的設(shè)置值,進入等待if(!sk_stream_memory_free(sk)) { goto wait_for_sndbuf;}// 申請一個skbskb = sk_stream_alloc_skb(sk, select_size(sk, sg), sk->sk_allocation);...// 把skb添加到sock的發(fā)送隊列尾部skb_entail(sk, skb); } if(copy > seglen)copy = seglen; // skb的線性數(shù)據(jù)區(qū)中有足夠的空間 if(skb_availroom(skb)) > 0) {copy = min_t(int, copy, skb_availroom(skb));// 將用戶空間的數(shù)據(jù)拷貝到內(nèi)核空間,同時計算校驗和err = skb_add_data_nocache(sk, skb, from, copy);if(err) goto do_fault; } // 線性數(shù)據(jù)區(qū)用完,使用分頁區(qū) else{... }

這個函數(shù)的實現(xiàn)邏輯比較復(fù)雜,代碼總只顯示了skb拷貝的相關(guān)部分,總體邏輯如下:

如果使用了TCP Fast Open,則會在發(fā)送SYN包的同時帶上數(shù)據(jù)

如果連接尚未建好,不處于ESTABLISHED或者CLOSE_WAIT狀態(tài)則進程進入睡眠,等待三次握手的完成

獲取當前的MSS(最大報文長度)和size_goal(一個理想的TCP數(shù)據(jù)包大小,受MTU、MSS、TCP窗口大小影響)

如果網(wǎng)卡支持GSO(利用網(wǎng)卡分片),size_goal會是MSS的整數(shù)倍

遍歷用戶層的數(shù)據(jù)塊數(shù)組

獲取發(fā)送隊列的最后一個skb,如果是尚未發(fā)送的,且長度未到達size_goal,那么向這個skb繼續(xù)追加數(shù)據(jù)

否則申請一個新的skb來裝載數(shù)據(jù)

如果發(fā)送隊列的總大小大于等于發(fā)送緩存的上限,或者發(fā)送緩存中尚未發(fā)送的數(shù)據(jù)量超過了用戶的設(shè)置值:設(shè)置發(fā)送時發(fā)送緩存不夠的標志,進入等待申請一個skb,其線性區(qū)的大小為通過select_size()得到的線性數(shù)據(jù)區(qū)中TCP負荷的大小和最大的協(xié)議頭長度,申請失敗則等待可用內(nèi)存前兩步成功則更新skb的TCP控制塊字段,把skb加入發(fā)送隊列隊尾,增加發(fā)送隊列的大小,減少預(yù)分配緩存的大小

將數(shù)據(jù)拷貝至skb中

如果skb的線性數(shù)據(jù)區(qū)還有剩余,就復(fù)制到線性數(shù)據(jù)區(qū)同時計算校驗和

如果已經(jīng)用完則使用分頁區(qū)

檢查分頁區(qū)是否有可用空間,沒有則申請新的page,申請失敗則說明內(nèi)存不足,之后會設(shè)置TCP內(nèi)存壓力標志,減小發(fā)送緩沖區(qū)的上限,睡眠等待內(nèi)存判斷能否往最后一個分頁追加數(shù)據(jù),不能追加時,檢查分頁數(shù)是否已經(jīng)達到了上限或網(wǎng)卡是否不支持分散聚合,如果是的話就將skb設(shè)置為PSH標志,然后回到4.2中重新申請一個skb來繼續(xù)填裝數(shù)據(jù)從系統(tǒng)層面判斷此次分頁發(fā)送緩存的申請是否合法拷貝用戶空間的數(shù)據(jù)到skb的分頁中,同時計算校驗和。更新skb的長度字段,更新sock的發(fā)送隊列大小和預(yù)分配緩存如果把數(shù)據(jù)追加到最后一個分頁了,更新最后一個分頁的數(shù)據(jù)大小。否則初始化新的分頁

拷貝成功后更新:發(fā)送隊列的最后一個序號、skb的結(jié)束序號、已經(jīng)拷貝到發(fā)送隊列的數(shù)據(jù)量

發(fā)送數(shù)據(jù)

如果所有數(shù)據(jù)都拷貝好了就退出循環(huán)進行發(fā)送如果skb還可以繼續(xù)裝填數(shù)據(jù)或者發(fā)送的是帶外數(shù)據(jù)那么就繼續(xù)拷貝數(shù)據(jù)先不發(fā)送如果為發(fā)送的數(shù)據(jù)已經(jīng)超過最大窗口的一半則設(shè)置PUSH標志后盡可能地將發(fā)送隊列中的skb發(fā)送出去如果當前skb就是發(fā)送隊列中唯一一個skb,則將這一個skb發(fā)送出去如果上述過程中出現(xiàn)緩存不足,且已經(jīng)有數(shù)據(jù)拷貝到發(fā)送隊列了也直接發(fā)送

這里的發(fā)送數(shù)據(jù)只是指調(diào)用tcp_push或者tcp_push_one(情況4)或者__tcp_push_pending_frames(情況3)嘗試發(fā)送,并不一定真的發(fā)送到網(wǎng)絡(luò)(tcp_sendmsg主要任務(wù)只是將應(yīng)用程序的數(shù)據(jù)封裝成網(wǎng)絡(luò)數(shù)據(jù)包放到發(fā)送隊列)。

數(shù)據(jù)何時實際被發(fā)送到網(wǎng)絡(luò),取決于許多因素,包括但不限于:

TCP的擁塞控制算法:TCP使用了復(fù)雜的擁塞控制算法來防止網(wǎng)絡(luò)過載。如果TCP判斷網(wǎng)絡(luò)可能出現(xiàn)擁塞,它可能會延遲發(fā)送數(shù)據(jù)。發(fā)送窗口的大小:TCP使用發(fā)送窗口和接收窗口來控制數(shù)據(jù)的發(fā)送和接收。如果發(fā)送窗口已滿(即已發(fā)送但未被確認的數(shù)據(jù)量達到了發(fā)送窗口的大小),那么TCP必須等待接收到確認信息后才能發(fā)送更多的數(shù)據(jù)。網(wǎng)絡(luò)設(shè)備(如網(wǎng)卡)的狀態(tài):如果網(wǎng)絡(luò)設(shè)備繁忙或出現(xiàn)錯誤,數(shù)據(jù)可能會被暫時掛起而無法立即發(fā)送。

struct sk_buff(常簡稱為skb)在Linux網(wǎng)絡(luò)棧中表示一個網(wǎng)絡(luò)包。它有兩個主要的數(shù)據(jù)區(qū)用來存儲數(shù)據(jù),分別是線性數(shù)據(jù)區(qū)(linear data area)和分頁區(qū)(paged data area)。

線性數(shù)據(jù)區(qū)(linear data area): 這個區(qū)域連續(xù)存儲數(shù)據(jù),并且能夠容納一個完整的網(wǎng)絡(luò)包的所有協(xié)議頭,比如MAC頭、IP頭和TCP/UDP頭等。除了協(xié)議頭部,線性數(shù)據(jù)區(qū)還可以包含一部分或全部的數(shù)據(jù)負載。每個skb都有一個線性數(shù)據(jù)區(qū)。分頁區(qū)(paged data area): 一些情況下,為了優(yōu)化內(nèi)存使用和提高性能,skb的數(shù)據(jù)負載部分可以存儲在一個或多個內(nèi)存頁中,而非線性數(shù)據(jù)區(qū)。分頁區(qū)的數(shù)據(jù)通常只包含數(shù)據(jù)負載部分,不包含協(xié)議頭部。如果一個skb的數(shù)據(jù)全部放入了線性數(shù)據(jù)區(qū),那么這個skb就沒有分頁區(qū)。

這種設(shè)計的好處是,對于大的數(shù)據(jù)包,可以將其數(shù)據(jù)負載部分存儲在分頁區(qū),避免對大塊連續(xù)內(nèi)存的分配,從而提高內(nèi)存使用效率,減少內(nèi)存碎片。另外,這種設(shè)計也可以更好地支持零拷貝技術(shù)。例如,當網(wǎng)絡(luò)棧接收到一個大數(shù)據(jù)包時,可以直接將數(shù)據(jù)包的數(shù)據(jù)負載部分留在原始的接收緩沖區(qū)(即分頁區(qū)),而無需將其拷貝到線性數(shù)據(jù)區(qū),從而節(jié)省了內(nèi)存拷貝的開銷。

2. 傳輸層發(fā)送

上面的發(fā)送數(shù)據(jù)步驟,不論是調(diào)用__tcp_push_pending_frames還是tcp_push_one,最終都會執(zhí)行到tcp_write_xmit(在網(wǎng)絡(luò)協(xié)議中學到滑動窗口、擁塞控制就是在這個函數(shù)中完成的),函數(shù)的主要邏輯如下:

如果要發(fā)送多個數(shù)據(jù)段則先發(fā)送一個路徑mtu探測檢測擁塞窗口的大小,如果窗口已滿(通過窗口大小-正在網(wǎng)絡(luò)上傳輸?shù)陌鼣?shù)目判斷)則不發(fā)送檢測當前報文是否完全在發(fā)送窗口內(nèi),如果不是則不發(fā)送判斷是否需要延時發(fā)送(取決于擁塞窗口和發(fā)送窗口)根據(jù)需要對數(shù)據(jù)包進行分段(取決于擁塞窗口和發(fā)送窗口)tcp_transmit_skb發(fā)送數(shù)據(jù)包如果push_one則結(jié)束循環(huán),否則繼續(xù)遍歷隊列發(fā)送結(jié)束循環(huán)后如果本次有數(shù)據(jù)發(fā)送,則對TCP擁塞窗口進行檢查確認

這里我們只關(guān)注發(fā)送的主過程,其他部分不過多展開,即來到tcp_transmit_skb函數(shù)

static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, gfp_t gfp_mask){ // 1.克隆新的skb出來 if(likely(clone_it)) { skb = skb_clone(skb, gfp_mask);...... } // 2.封裝TCP頭 th = tcp_hdr(skb); th->source = inet->inet_sport; th->dest = inet->inet_dport; th->window = ...; th->urg = ...; ...... // 3.調(diào)用網(wǎng)絡(luò)層發(fā)送接口 err = icsk->icsk_af_ops->xmit(skb, &inet->cort.fl);}

第一件事就是先克隆一個新的skb,因為skb后續(xù)在調(diào)用網(wǎng)絡(luò)層,最后到達網(wǎng)卡發(fā)送完成的時候,這個skb會被釋放掉。而TCP協(xié)議是支持丟失重傳的,在收到對方的ACK之前,這個skb不能被刪除掉。所以內(nèi)核的做法就是每次調(diào)用網(wǎng)卡發(fā)送的時候,實際上傳遞出去的是skb的一個拷貝。等收到ACK再真正刪除。

第二件事是修改skb的TCP頭,根據(jù)實際情況把TCP頭設(shè)置好。實際上skb內(nèi)部包含了網(wǎng)絡(luò)協(xié)議中所有的頭,在設(shè)置TCP頭的時候,只是把指針指向skb合適的位置。后面設(shè)置IP頭的時候,再把指針挪動一下即可,避免了頻繁的內(nèi)存申請和拷貝,提高效率。

tcp_transmit_skb是發(fā)送數(shù)據(jù)位于傳輸層的最后一步,調(diào)用了網(wǎng)絡(luò)層提供的發(fā)送接口icsk->icsk_Af_ops->queue_xmit()之后就可以進入網(wǎng)絡(luò)層進行下一層的操作了。

3)網(wǎng)絡(luò)層發(fā)送處理

在tcp_ipv4中,queue_xmit指向的是ip_queue_xmit,具體實現(xiàn)如下:

int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl){ // 檢查socket中是否有緩存的路由表 rt = (struct rtable*)__sk_dst_check(sk, 0); ...... if(rt == null) { // 沒有緩存則展開查找路由項并緩存到socket中rt = ip_route_output_ports(...); sk_setup_caps(sk, &rt->dst); } // 為skb設(shè)置路由表 skb_dst_set_noref(skb, &rt->dst); // 設(shè)置IP頭 iph = ip_hdr(skb); ip->protocol = sk->sk_protocol; iph->ttl = ip_select_ttl(inet, &rt->dst); ip->frag_off = ...; ip_copy_addr(iph, f14); ...... // 發(fā)送 ip_local_out(skb);}

這個函數(shù)主要做的就是找到該把這個包發(fā)往哪,并構(gòu)造好IP包頭。它會去查詢socket中是否有緩存的路由表,如果有則直接構(gòu)造包頭,如果沒有就去查詢并緩存到sokect,然后為skb設(shè)置路由表,最后封裝ip頭,發(fā)往ip_local_out函數(shù)。

ip_local_out中主要會經(jīng)過__ip_local_out => nf_hook 的過程進行netfilter的過濾。如果使用iptables配置了一些規(guī)則,那么這里將檢測到是否命中規(guī)則,然后進行相應(yīng)的操作,如網(wǎng)絡(luò)地址轉(zhuǎn)換、數(shù)據(jù)包內(nèi)容修改、數(shù)據(jù)包過濾等。如果設(shè)置了非常復(fù)雜的netfilter規(guī)則,則在這個函數(shù)會導(dǎo)致進程CPU的開銷大增。經(jīng)過netfilter處理之后,(忽略其他部分)調(diào)用dst_output(skb)函數(shù)。

dst_output會去調(diào)用skb_dst(skb)->output(skb),即找到skb的路由表(dst條目),然后調(diào)用路由表的output方法。這里是個函數(shù)指針,指向的是ip_output方法。

在ip_output方法中首先會進行一些簡單的統(tǒng)計工作,隨后再次執(zhí)行netfilter過濾。過濾通過之后回調(diào)ip_finish_output。

在ip_finish_output中,會校驗數(shù)據(jù)包的長度,如果大于MTU,就會執(zhí)行分片。MTU的大小是通過MTU發(fā)現(xiàn)機制確定,在以太網(wǎng)中為1500字節(jié)。分片會帶來兩個問題:

需要進行額外的處理,會有性能開銷只要一個分片丟失,整個包都要重傳

如果不需要分片則調(diào)用ip_finish_output2函數(shù),根據(jù)下一跳的IP地址查找鄰居項,找不到就創(chuàng)建一個,然后發(fā)給下一層——鄰居子系統(tǒng)。

總體過程如下:

ip_queue_xmit

查找并設(shè)置路由項設(shè)置IP頭

ip_local_out:netfilter過濾

ip_output

統(tǒng)計工作再次netfilter過濾

ip_finish_output

大于MTU的話進行分片調(diào)用ip_finish_output24)鄰居子系統(tǒng)

鄰居子系統(tǒng)是位于網(wǎng)絡(luò)層和數(shù)據(jù)鏈路層中間的一個系統(tǒng),其作用是為網(wǎng)絡(luò)層提供一個下層的封裝,讓網(wǎng)絡(luò)層不用關(guān)心下層的地址信息,讓下層來決定發(fā)送到哪個MAC地址。

鄰居子系統(tǒng)不位于協(xié)議棧net/ipv4/目錄內(nèi),而是位于net/core/neighbour.c,因為無論對于ipv4還是ipv6都需要使用該模塊

在鄰居子系統(tǒng)中主要查找或者創(chuàng)建鄰居項,在創(chuàng)建鄰居項時有可能會發(fā)出實際的arp請求。然后封裝MAC頭,將發(fā)生過程再傳遞給更下層的網(wǎng)絡(luò)設(shè)備子系統(tǒng)。

ip_finish_output2的實現(xiàn)邏輯大致流程如下:

rt_nexthop:獲取路由下一跳的IP信息

__ipv4_neigh_lookup_noref:根據(jù)下一條IP信息在arp緩存中查找鄰居項

__neigh_create:創(chuàng)建一個鄰居項,并加入鄰居哈希表

dst_neight_output => neighbour->output(實際指向neigh_resolve_output):

封裝MAC頭(可能會先觸發(fā)arp請求)調(diào)用dev_queue_xmit發(fā)送到下層5)網(wǎng)絡(luò)設(shè)備子系統(tǒng)

鄰居子系統(tǒng)通過dev_queue_xmit進入網(wǎng)絡(luò)設(shè)備子系統(tǒng),dev_queue_xmit的工作邏輯如下

選擇發(fā)送隊列獲取排隊規(guī)則存在隊列則調(diào)用__dev_xmit_skb繼續(xù)處理

在前面講過,網(wǎng)卡是有多個發(fā)送隊列的,所以首先需要選擇一個隊列進行發(fā)送。隊列的選擇首先是通過獲取用戶的XPS配置(為隊列綁核),如果沒有配置則調(diào)用skb_tx_hash去計算出選擇的隊列。接著會根據(jù)與此隊列關(guān)聯(lián)的qdisc得到該隊列的排隊規(guī)則。

最后會根據(jù)是否存在隊列(如果是發(fā)給回環(huán)設(shè)備或者隧道設(shè)備則沒有隊列)來決定后續(xù)數(shù)據(jù)包流向。對于存在隊列的設(shè)備會進入__dev_xmit_skb函數(shù)。

在Linux網(wǎng)絡(luò)子系統(tǒng)中,qdisc(Queueing Discipline,隊列規(guī)則)是一個用于管理網(wǎng)絡(luò)包排隊和發(fā)送的核心組件。它決定了網(wǎng)絡(luò)包在發(fā)送隊列中的排列順序,以及何時從隊列中取出包進行發(fā)送。qdisc還可以應(yīng)用于網(wǎng)絡(luò)流量控制,包括流量整形(traffic shaping)、流量調(diào)度(traffic scheduling)、流量多工(traffic multiplexing)等。

Linux提供了許多預(yù)定義的qdisc類型,包括:

pfifo_fast:這是默認的qdisc類型,提供了基本的先入先出(FIFO)隊列行為。mq:多隊列時的默認類型,本身并不進行任何數(shù)據(jù)包的排隊或調(diào)度,而是為網(wǎng)絡(luò)設(shè)備的每個發(fā)送隊列創(chuàng)建和管理一個子 qdisc。tbf (Token Bucket Filter):提供了基本的流量整形功能,可以限制網(wǎng)絡(luò)流量的速率。htb (Hierarchical Token Bucket):一個更復(fù)雜的流量整形qdisc,可以支持多級隊列和不同的流量類別。sfq (Stochastic Fairness Queueing):提供了公平隊列調(diào)度,可以防止某一流量占用過多的帶寬。

每個網(wǎng)絡(luò)設(shè)備(如eth0、eth1等)都有一個關(guān)聯(lián)的qdisc,用于管理這個設(shè)備的發(fā)送隊列。用戶可以通過tc(traffic control)工具來配置和管理qdisc。

對于支持多隊列的網(wǎng)卡,Linux內(nèi)核為發(fā)送和接收隊列分別分配一個qdisc。每個qdisc獨立管理其對應(yīng)的隊列,包括決定隊列中的數(shù)據(jù)包發(fā)送順序,應(yīng)用流量控制策略等。這樣,可以實現(xiàn)每個隊列的獨立調(diào)度和流量控制,提高整體網(wǎng)絡(luò)性能。

我們可以說,對于支持多隊列的網(wǎng)卡,內(nèi)核中的每個發(fā)送隊列都對應(yīng)一個硬件的發(fā)送隊列(也就是 Ring Buffer)。選擇哪個內(nèi)核發(fā)送隊列發(fā)送數(shù)據(jù)包,也就決定了數(shù)據(jù)包將被放入哪個 Ring Buffer。數(shù)據(jù)包從 qdisc 的發(fā)送隊列出隊后,會被放入 Ring Buffer,然后由硬件發(fā)送到網(wǎng)絡(luò)線路上。所以,Ring Buffer 在發(fā)送路徑上位于發(fā)送隊列之后。

將struct sock的發(fā)送隊列和網(wǎng)卡的Ring Buffer之間設(shè)置一個由qdisc(隊列規(guī)則)管理的發(fā)送隊列,可以提供更靈活的網(wǎng)絡(luò)流量控制和調(diào)度策略,以適應(yīng)不同的網(wǎng)絡(luò)環(huán)境和需求。

下面是一些具體的原因:

流量整形和控制:qdisc可以實現(xiàn)各種復(fù)雜的排隊規(guī)則,用于控制數(shù)據(jù)包的發(fā)送順序和時間。這可以用于實現(xiàn)流量整形(比如限制數(shù)據(jù)的發(fā)送速率以避免網(wǎng)絡(luò)擁塞)和流量調(diào)度(比如按照優(yōu)先級或服務(wù)質(zhì)量(QoS)要求來調(diào)度不同的數(shù)據(jù)包)。對抗網(wǎng)絡(luò)擁塞:qdisc可以通過管理發(fā)送隊列,使得在網(wǎng)絡(luò)擁塞時可以控制數(shù)據(jù)的發(fā)送,而不是簡單地將所有數(shù)據(jù)立即發(fā)送出去,這可以避免網(wǎng)絡(luò)擁塞的加劇。公平性:在多個網(wǎng)絡(luò)連接共享同一個網(wǎng)絡(luò)設(shè)備的情況下,qdisc可以確保每個連接得到公平的網(wǎng)絡(luò)帶寬,而不會因為某個連接的數(shù)據(jù)過多而餓死其他的連接。性能優(yōu)化:qdisc可以根據(jù)網(wǎng)絡(luò)設(shè)備的特性(例如,對于支持多隊列(Multi-Queue)的網(wǎng)卡)和當前的網(wǎng)絡(luò)條件來優(yōu)化數(shù)據(jù)包的發(fā)送,以提高網(wǎng)絡(luò)的吞吐量和性能。

__dev_xmit_skb分為三種情況:

qdisc停用:釋放數(shù)據(jù)并返回代碼設(shè)置為NET_XMIT_DROP

qdisc允許繞過排隊系統(tǒng)&&沒有其他包要發(fā)送&&qdisc沒有運行:繞過排隊系統(tǒng),調(diào)用sch_direct_xmit發(fā)送數(shù)據(jù)

其他情況:正常排隊

調(diào)用q->enqueue入隊調(diào)用__qdisc_run開始發(fā)送void __qdisc_run(struct Qdisc *q){ int quota = weight_p; // 循環(huán)從隊列取出一個skb并發(fā)送 while(qdisc_restart(q)) {// 如果quota耗盡或其他進程需要CPU則延后處理if(--quota <= 0 || need_resched) { // 將觸發(fā)一次NET_TX_SOFTIRQ類型的softirq __netif_shcedule(q); break;} }}

從上述代碼中可以看到,while循環(huán)不斷地從隊列中取出skb并進行發(fā)送,這個時候其實占用的都是用戶進程系統(tǒng)態(tài)時間sy,只有當quota用盡或者其他進程需要CPU的時候才觸發(fā)軟中斷進行發(fā)送。

這就是為什么服務(wù)器上查看/proc/softirqs,一般NET_RX要比NET_TX大得多的原因。對于接收來說,都要經(jīng)過NET_RX軟中斷,而對于發(fā)送來說,只有系統(tǒng)配額用盡才讓軟中斷上。

這里我們聚焦于qdisc_restart函數(shù)上,這個函數(shù)用于從qdisc隊列中取包并發(fā)給網(wǎng)絡(luò)驅(qū)動

static inline int qdisc_restart(struct Qdisc *q){ struct sk_buff *skb = dequeue_skb(q); if (!skb)return 0; ...... return sch_direct_xmit(skb, q, dev, txq, root_lock);}

首先調(diào)用 dequeue_skb() 從 qdisc 中取出要發(fā)送的 skb。如果隊列為空,返回 0, 這將導(dǎo)致上層的 qdisc_restart() 返回 false,繼而退出 while 循環(huán)。

如果拿到了skb則調(diào)用sch_direct_xmit繼續(xù)發(fā)送,該函數(shù)會調(diào)用dev_hard_start_xmit,進入驅(qū)動程序發(fā)包,如果無法發(fā)送則重新入隊。

即整個__qdisc_run的整體邏輯為:while 循環(huán)調(diào)用 qdisc_restart(),后者取出一個 skb,然后嘗試通過 sch_direct_xmit() 來發(fā)送;sch_direct_xmit 調(diào)用 dev_hard_start_xmit 來向驅(qū)動程序進行實際發(fā)送。任何無法發(fā)送的 skb 都重新入隊,將在 NET_TX softirq 中進行發(fā)送。

6)軟中斷調(diào)度

上一部分中如果發(fā)送網(wǎng)絡(luò)包的時候CPU耗盡了,會調(diào)用進入__netif_schedule,該函數(shù)會進入__netif_reschedule,將發(fā)送隊列設(shè)置到softnet_data上,并最終發(fā)出一個NET_TX_SOFTIRQ類型的軟中斷。軟中斷是由內(nèi)核進程運行的,該進程會進入net_tx_action函數(shù),在該函數(shù)中能獲得發(fā)送隊列,并最終也調(diào)用到驅(qū)動程序的入口函數(shù)dev_hard_start_xmit。

從觸發(fā)軟中斷開始以后發(fā)送數(shù)據(jù)消耗的CPU就都顯示在si中,而不會消耗用戶進程的系統(tǒng)時間

static void net_tx_action(struct softirq_action *h){ struct softnet_data *sd = &__get_cpu_var(softnet_data); // 如果softnet_data設(shè)置了發(fā)送隊列 if(sd->output_queue) { // 將head指向第一個qdisc head = sd->output_queue;// 遍歷所有發(fā)送隊列 while(head) { struct Qdisc *q = head; head = head->next_sched; // 處理數(shù)據(jù) qdisc_run(q);} }}static inline void qdisc_run(struct Qdisc *q){ if(qdisc_run_begin(q)) __qdisc_run(q);}

可以看到軟中斷的處理中,最后和前面一樣都是調(diào)用了__qdisc_run。也就是說不管是在qdisc_restart中直接處理,還是軟中斷來處理,最終實際都會來到dev_hard_start_xmit(__qdisc_run => qdisc_restart => dev_hard_start_xmit)。

7)igb網(wǎng)卡驅(qū)動發(fā)送

通過前面的介紹可知,無論對于用戶進程的內(nèi)核態(tài),還是對于軟中斷上下文,都會調(diào)用網(wǎng)絡(luò)設(shè)備子系統(tǒng)的dev_hard_start_xmit函數(shù),在這個函數(shù)中,會調(diào)用驅(qū)動里的發(fā)送函數(shù)igb_xmit_frame。在驅(qū)動函數(shù)里,會將skb掛到RingBuffer上,驅(qū)動調(diào)用完畢,數(shù)據(jù)包真正從網(wǎng)卡發(fā)送出去。

int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev, struct netdev_queue *txq){ // 獲取設(shè)備的回調(diào)函數(shù)ops const struct net_device_ops * ops = dev->netdev_ops; // 獲取設(shè)備支持的功能列表 features = netif_skb_features(skb); // 調(diào)用驅(qū)動的ops里的發(fā)送回調(diào)函數(shù)ndo_start_xmit將數(shù)據(jù)包傳給網(wǎng)卡設(shè)備 skb_len = skb->len; rc = ops->ndo_start_xmit(skb, dev);}

這里ndo_start_xmit是網(wǎng)卡驅(qū)動要實現(xiàn)的函數(shù),igb網(wǎng)卡驅(qū)動中的實現(xiàn)是igb_xmit_frame(在網(wǎng)卡驅(qū)動程序初始化的時候賦值的)。igb_xmit_frame主要會去調(diào)用igb_xmit_frame_ring函數(shù)

netdev_tx_t igb_xmit_frame_ring(struct sk_buff *skb, struct igb_ring *tx_ring){ // 獲取TX queue中下一個可用緩沖區(qū)的信息 first = &tx_ring->tx_buffer_info[tx_ring->next_to_use]; first->skb = skb; first->bytecount = skb->len; first->gso_segs = 1; // 準備給設(shè)備發(fā)送的數(shù)據(jù) igb_tx_map(tx_ring, first, hdr_len);}static void igb_tx_map(struct igb_ring *tx_ring, struct igb_tx_buffer *first, const u8 hdr_len){ // 獲取下一個可用的描述符指針 tx_desc = IGB_TX_DESC(tx_ring, i); // 為skb->data構(gòu)造內(nèi)存映射,以允許設(shè)備通過DMA從RAM中讀取數(shù)據(jù) dma = dma_map_single(tx_ring->dev, skb->data, size, DMA_TO_DEVICE); // 遍歷該數(shù)據(jù)包的所有分片,為skb的每個分片生成有效映射 for(frag = &skb_shinfo(skb)->frags[0]; ; flag++){tx_desc->read.buffer_addr = cpu_to_le64(dma); tx_desc->read.cmd_type_len = ...; tx_desc->read.olinfo_status = 0; } // 設(shè)置最后一個descriptor cmd_type |= size | IGB_TXD_DCMD; tx_desc->read.cmd_type_len = cpu_to_le32(cmd_type);}

在這里從網(wǎng)卡的發(fā)送隊列的RingBuffer上取下來一個元素,并將skb掛到元素上。然后使用igb_tx_map函數(shù)將skb數(shù)據(jù)映射到網(wǎng)卡可訪問的內(nèi)存DMA區(qū)域。

這里可以理解為&tx_ring->tx_buffer_info[tx_ring->next_to_use]拿到了RingBuffer發(fā)送隊列中指針數(shù)組(前文提到的igb_tx_buffer,網(wǎng)卡啟動的時候創(chuàng)建的供內(nèi)核使用的數(shù)組)的下一個可用的元素,然后為其填充skb、byte_count等數(shù)據(jù)。

填充完成之后,獲取描述符數(shù)組(前文提到的e1000_adv_tx_desc,網(wǎng)卡啟動的時候創(chuàng)建的供網(wǎng)卡使用的數(shù)組)的下一個可用元素。

調(diào)用dma_map_single函數(shù)創(chuàng)建內(nèi)存和設(shè)備之間的DMA映射,tx_ring->dev是設(shè)備的硬件描述符,即網(wǎng)卡,skb->data是要映射的地址,size是映射的數(shù)據(jù)的大小,即數(shù)據(jù)包的大小,DMA_TO_DEVICE是指映射的方向,這里是數(shù)據(jù)將從內(nèi)存?zhèn)鬏數(shù)皆O(shè)備,返回的調(diào)用結(jié)果是一個DMA地址,存儲在dma變量中,設(shè)備可以直接通過這個地址訪問到skb的數(shù)據(jù)。

最后就是為前面拿到的描述符填充信息,將dma賦值給buffer_addr,網(wǎng)卡使用的時候就是從這里拿到數(shù)據(jù)包的地址。

當所有需要的描述符都建好,且skb的所有數(shù)據(jù)都映射到DMA地址后,驅(qū)動就會進入到它的最后一步,觸發(fā)真實的發(fā)送。

到目前為止我們可以這么理解:

應(yīng)用程序?qū)?shù)據(jù)發(fā)送到 socket,這些數(shù)據(jù)會被放入與 sock 中的發(fā)送隊列。然后,網(wǎng)絡(luò)協(xié)議棧(例如 TCP 或 UDP)將這些數(shù)據(jù)從 socket 的發(fā)送隊列中取出,往下層封裝,然后將這些數(shù)據(jù)包放入由 qdisc 管理的設(shè)備發(fā)送隊列中。最后,這些數(shù)據(jù)包將從設(shè)備發(fā)送隊列出隊,放置到RingBuffer的指針數(shù)組中,通過dma將數(shù)據(jù)包的地址映射到可供網(wǎng)卡訪問的內(nèi)存DMA區(qū)域,由硬件讀取后發(fā)送到網(wǎng)絡(luò)上。

五、RingBuffer內(nèi)存回收

當數(shù)據(jù)發(fā)送完以后,其實工作并沒有結(jié)束,因為內(nèi)存還沒有清理。當發(fā)送完成的時候,網(wǎng)卡設(shè)備會觸發(fā)一個硬中斷(硬中斷會去觸發(fā)軟中斷)來釋放內(nèi)存。

這里需要注意的就是,雖然是數(shù)據(jù)發(fā)送完成通知,但是硬中斷觸發(fā)的軟中斷是NET_RX_SOFTIRQ,這也就是為什么軟中斷統(tǒng)計中RX要高于TX的另一個原因。

硬中斷中會向softnet_data添加poll_list,軟中斷中輪詢后調(diào)用其poll回調(diào)函數(shù),具體實現(xiàn)是igb_poll,其會在q_vector->tx.ring存在時去調(diào)用igb_clean_tx_irq。

static bool igb_clean_tx_irq(struct igb_q_vector *q_vector){ // 釋放skb dev_kfree_skb_any(tx_buffer->skb); // 清除tx_buffer數(shù)據(jù) tx_buffer->skb = NULL; // 將tx_buffer指定的DMA緩沖區(qū)的長度設(shè)置為0 dma_unmap_len_set(tx_buffer, len 0); // 清除最后的DMA位置,解除映射 while(tx_desc != eop_desc) { }}

其實邏輯無非就是清理了skb(其中data保存的數(shù)據(jù)包沒有釋放),解決了DMA映射等,到了這一步傳輸才算基本完成。

當然因為傳輸層需要保證可靠性,所以數(shù)據(jù)包還沒有刪除,此時還有前面的拷貝過的skb指向它,它得等到收到對方的ACK之后才會真正刪除。

六、問題解答

查看內(nèi)核發(fā)送數(shù)據(jù)消耗的CPU時應(yīng)該看sy還是si

在網(wǎng)絡(luò)包發(fā)送過程中,用戶進程(在內(nèi)核態(tài))完成了絕大部分的工作,甚至連調(diào)用驅(qū)動的工作都干了。只有當內(nèi)核態(tài)進程被切走前才會發(fā)起軟中斷。發(fā)送過程中百分之九十以上的開銷都是在用戶進程內(nèi)核態(tài)消耗掉的,只有一少部分情況才會觸發(fā)軟中斷,有軟中斷ksoftirqd內(nèi)核線程來發(fā)送。所以在監(jiān)控網(wǎng)絡(luò)IO對服務(wù)器造成的CPU開銷的時候,不能近看si,而是應(yīng)該把si、sy(內(nèi)核占用CPU時間比例)都考慮進來。

在服務(wù)器上查看/proc/softirqs,為什么NET_RX要比NET_TX大得多

對于讀來說,都是要經(jīng)過NET_RX軟中斷的,都走ksoftirqd內(nèi)核線程。而對于發(fā)送來說,絕大部份工作都是在用戶進程內(nèi)核態(tài)處理了,只有系統(tǒng)態(tài)配額用盡才會發(fā)出NET_TX,讓軟中斷處理。當數(shù)據(jù)發(fā)送完以后,通過硬中斷的方式來通知驅(qū)動發(fā)送完畢。但是硬中斷無論是有數(shù)據(jù)接收還是發(fā)送完畢,觸發(fā)的軟中斷都是NET_RX_SOFTIRQ而不是NET_TX_SOFTIRQ。

發(fā)送網(wǎng)絡(luò)數(shù)據(jù)的時候都涉及那些內(nèi)存拷貝操作

這里只指內(nèi)存拷貝內(nèi)核申請完skb之后,將用戶傳遞進來的buffer里的數(shù)據(jù)拷貝到skb。如果數(shù)據(jù)量大,這個拷貝操作還是開銷不小的。從傳輸層進入網(wǎng)絡(luò)層時。每個skb都會被克隆出一個新的副本,目的是保存原始的skb,當網(wǎng)絡(luò)對方?jīng)]有發(fā)揮ACK的時候還可以重新發(fā)送,易實現(xiàn)TCP中要求的可靠傳輸。不過這次只是淺拷貝,只拷貝skb描述符本身,所指向的數(shù)據(jù)還是復(fù)用的。第三次拷貝不是必須的,只有當IP層發(fā)現(xiàn)skb大于MTU時才需要進行,此時會再申請額外的skb,并將原來的skb拷貝成多個小的skb。

零拷貝到底是怎么回事

如果想把本機的一個文件通過網(wǎng)絡(luò)發(fā)送出去,需要先調(diào)用read將文件讀到內(nèi)存,之后再調(diào)用send將文件發(fā)送出去假設(shè)數(shù)據(jù)之前沒有讀去過,那么read系統(tǒng)調(diào)用需要兩次拷貝才能到用戶進程的內(nèi)存。第一次是從硬盤DMA到Page Cache。第二次是從Page Cache拷貝到內(nèi)存。send系統(tǒng)調(diào)用也同理,先CPU拷貝到socket發(fā)送隊列,之后網(wǎng)卡進行DMA拷貝。如果要發(fā)送的數(shù)據(jù)量較大,那么就需要花費不少的時間在數(shù)據(jù)拷貝上。而sendfile就是內(nèi)核提供的一個可用來減少發(fā)送文件時拷貝開銷的一個技術(shù)方案。在sendfile系統(tǒng)調(diào)用里,數(shù)據(jù)不需要拷貝到用戶空間,在內(nèi)核態(tài)就能完成發(fā)送處理,減少了拷貝的次數(shù)。

為什么Kafka的網(wǎng)絡(luò)性能很突出

Kafka高性能的原因有很多,其中重要的原因之一就是采用了sendfile系統(tǒng)調(diào)用來發(fā)送網(wǎng)絡(luò)數(shù)據(jù)包,減少了內(nèi)核態(tài)和用戶態(tài)之間的頻繁數(shù)據(jù)拷貝。

以上就是深入理解Linux網(wǎng)絡(luò)之內(nèi)核是如何發(fā)送網(wǎng)絡(luò)包的的詳細內(nèi)容,更多關(guān)于Linux 內(nèi)核發(fā)送網(wǎng)絡(luò)包的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標簽: Linux
主站蜘蛛池模板: 成人免费在线观看 | 中文字幕亚洲在线 | 亚洲福利视频在线 | 国产99久久久精品视频 | 国产精品久久久久久久久久久久久久 | 久久国产经典视频 | h视频免费观看 | 91精品国产欧美一区二区成人 | 日韩成人影院在线观看 | 亚洲日韩欧美一区二区在线 | 色婷婷综合久久 | 国产精品一区二区在线观看 | 国内精品视频一区 | 精品av | 国产午夜精品一区二区三区视频 | 精品国产欧美一区二区三区成人 | 国产高清av在线一区二区三区 | www.久久久 | 香蕉夜色 | www.成人国产 | 国产亚洲精品精品国产亚洲综合 | 久久精品一区二区三区四区毛片 | 国产二区三区 | 久福利| 午夜视频在线免费观看 | 视频精品一区二区三区 | 精品无码三级在线观看视频 | 久久国产精品无码网站 | 免费黄色av | 91精品国产综合久久久久久丝袜 | 天天操,夜夜操 | 亚洲黄色高清视频 | 欧美久久精品一级c片 | 日韩精品观看 | 中文字幕一区二区三区四区 | 精品视频在线观看 | 成人欧美一区二区三区黑人孕妇 | 国产精品一区二区三 | 国产成人在线视频 | 国产精品久久久久毛片软件 | 在线视频91 | 亚洲国产精久久久久久久 | 久久久久久91| 亚洲一区二区国产 | 日韩中文字幕在线播放 | 中国特级毛片 | 国产一级黄色av | 中文字幕乱码亚洲精品一区 | 日韩a∨精品日韩在线观看 山岸逢花在线 | 漂亮少妇videoshd忠贞 | 久久久精品久久 | 日韩不卡一区二区三区 | 久久精品电影网 | 看毛片的网站 | 99综合| 成人性视频免费网站 | 国产综合精品一区二区三区 | 亚洲lesbianxxxxhd| 黄色a三级| 久久久久亚洲精品 | 亚洲一区二区三区免费视频 | 国产精品视频一区二区三区 | 色婷婷在线视频观看 | 亚洲成av人片在线观看无码 | 欧美视频中文字幕 | 国产精品久久在线观看 | 欧美一区二区三 | 精品国产鲁一鲁一区二区在线观看 | 一区二区三区免费在线观看 | 9999国产精品| 黄色一级电影 | 久久电影一区 | 欧美激情第二页 | 国产麻豆乱码精品一区二区三区 | 精品亚洲一区二区三区 | 91中文字幕在线 | 一级毛片免费视频 | 欧美一区二区三 | 国产视频91在线 | 国产精品国产精品国产专区不片 | 在线一级视频 | 欧美国产日韩另类 | 国产精品久久久久久久久 | 日韩精品一区二区三区第95 | 你懂的免费在线观看 | 中文字幕国产日韩 | 91亚洲视频 | 国产精品揄拍一区二区久久国内亚洲精 | 久久亚洲一区二区 | 亚洲成人中文字幕 | 欧美日韩国产一区二区 | 色吧一区 | 亚洲精品www久久久久久广东 | 久久久国产精品入口麻豆 | 欧亚视频在线观看 | 欧美成人一级 | 亚洲成人综合在线 | 久久噜噜噜精品国产亚洲综合 | 亚洲男人的天堂网站 | 亚洲精品一区二区三区 | 黄色短视频在线观看 | 久久久精品国产 | 日韩av片在线免费观看 | 国产午夜精品一区二区三区嫩草 | 国产成人在线一区 | 欧美精品区 | 欧美久久成人 | 91在线资源 | 成人免费视频网站在线观看 | 国产精品久久 | 亚州激情| 日日日日日 | 亚洲精品乱码久久久久久不卡 | 日日操夜夜| 日韩91视频 | 日本精品久久久一区二区三区 | 成人在线视频一区二区 | 午夜成人免费电影 | 亚洲精品一区中文字幕乱码 | 亚洲综合区| 毛片91| 日韩国产一区二区三区 | 精品欧美日韩 | 成人不卡在线 | 天天干人人插 | 久久免费精品 | 精品视频久久 | 日韩在线中文字幕 | 国产美女网站视频 | 欧美国产一区二区 | 国产福利91精品一区二区 | 国产大学生情侣呻吟视频 | 国产一级做a爰片在线看免费 | 91精品国产乱码久久久久久久久 | 九九久久久 | 欧美日韩在线免费观看 | 免费午夜电影 | 免费一区二区三区视频在线 | 成人免费高清视频 | 一区二区三区四区日韩 | 成人国产精品免费观看 | 亚洲午夜视频 | 高清一区二区 | 亚洲男人天堂网 | 亚洲国产精品一区二区久久,亚洲午夜 | 一区二区三区成人久久爱 | 欧美午夜一区二区三区免费大片 | 国产精品一区欧美 | 日本电影网址 | 国产成人综合一区二区三区 | 中文字幕av在线 | 精品亚洲一区二区 | 三级黄色片在线观看 | 成人动慢 | 国产视频一二三区 | 黄色福利 | 中文字幕在线影院 | 国产精品极品美女在线观看免费 | 国产区日韩区欧美区 | 91精品一区二区三区久久久久久 | 日韩成人在线播放 | 久草美女 | 久久亚洲一区二区 | 国产精品久久久久久中文字 | 国内精品国产成人国产三级粉色 | 2018天天操夜夜操 | 欧美精品亚洲 | yy6080久久伦理一区二区 | 中文字幕av网 | 日韩欧美国产一区二区 | 日韩中文一区二区 | 国产精品久久久久久久一区探花 | 国产羞羞视频在线观看 | 午夜免费剧场 | 伊人亚洲 | 欧美在线视频网站 | 日日操av | 亚洲网站在线观看 | 成年免费视频 | 91免费视频观看 | 91精品久久久久久久91蜜桃 | 欧美精品一区二区三区在线四季 | 四影虎影ww4hu55.com | 久草.com | 超碰在线99 | 男女免费在线观看 | 激情欧美日韩一区二区 | 99久久99久久久精品色圆 | 日韩欧美国产成人一区二区 | 国产视频精品自拍 | 久久精品欧美一区二区三区不卡 | 国产成人在线看 | 婷婷五月色综合 | 久久久天天 | 在线欧美日韩 | 成人精品一区二区三区中文字幕 | 国产精品成人3p一区二区三区 | 久久男人 | 日韩精品一区二区三区中文字幕 | 久久久精品| 精品亚洲一区二区三区四区五区 | 国产精品久久免费视频在线 | 国产精品高颜值在线观看 | 国产精品99久久久久久久vr | 国产视频一二三区 | 国产精品久久久久久久粉嫩 | 国产成人精品一区二区三区四区 | 国产欧美日韩在线观看 | 成人福利在线观看 | 亚洲精区 | 可以免费看黄视频的网站 | 国产精品九九九 | xxxx网 | 人妖天堂狠狠ts人妖天堂狠狠 | 国产精品视频免费 | 黄色大片网站在线观看 | 国产毛片在线看 | 青青久草| 欧美一区视频 | 久久影院一区 | 日韩美香港a一级毛片免费 国产综合av | 国产精品欧美一区二区三区 | 亚洲精品一区二三区不卡 | 黄色片免费观看网站 | 日本私人网站在线观看 | 日本三级做a全过程在线观看 | 成年人在线观看视频 | 亚洲免费视频一区 | 欧美 日韩 国产 一区 | 国产在线观看免费 | 国产精品一区在线观看你懂的 | 成人精品在线观看 | 日韩中文字幕在线播放 | 国产精品久久久久久久久动漫 | 成av在线 | 国产精品99久久久久 | 午夜天堂精品久久久久 | 亚洲 欧美 另类 综合 偷拍 | 国产操片 | 在线播放三级 | 国产欧美一区二区精品婷 | 国产精品1 | 精品久久久久一区二区国产 | 亚洲a视频| av毛片在线免费看 | 日日摸天天做天天添天天欢 | 欧美三级电影在线播放 | 日韩精品一区二 | 国产综合精品一区二区三区 | 激情欧美一区二区 | 亚洲精品乱码久久久久久蜜糖图片 | 欧美精品在线观看 | 久久精品免费看 | 日本一区二区三区四区 | 成人深夜小视频 | xx视频在线观看 | 亚洲免费网| 日韩欧美中文 | 国产精品成人在线视频 | 国产精品一区三区 | 1000部精品久久久久久久久 | 久久久久久成人 | 韩国精品一区 | 99热国产在线观看 | 一级a性色生活片毛片 | 日韩av福利| 亚洲一区中文字幕在线观看 | 免费视频爱爱太爽了 | 欧美一区二区三区四区五区 | 在线亚洲免费 | 日韩欧美一二三区 | 日韩一及片 | 91亚洲精品乱码久久久久久蜜桃 | 成人欧美一区二区三区 | 国产黄色大片 | 中文成人在线 | 午夜视频在线免费观看 | 日韩在线视频网站 | 国产精品.xx视频.xxtv | 欧美日韩在线观看一区二区 | 欧美一区二区大片 | √8天堂资源地址中文在线 成人欧美一区二区三区白人 | 国产成人精品一区二区三区视频 | 三级免费网站 | 欧美精品一区二区三区在线播放 | 九一精品国产 | 无码日韩精品一区二区免费 | 一区二区三区国产在线观看 | 在线观看国产精品一区二区 | 一级久久久久 | 亚洲欧洲无码一区二区三区 | 日韩一区二区免费视频 | 国产精品亚洲a | 欧美国产视频一区 | 欧美在线视频一区 | 国产羞羞视频免费在线观看 | 天天综合天天色 | 欧美日韩精品一区二区在线播放 | 久久精品久久综合 | 国产精品成人一区二区三区夜夜夜 | 一区二区在线视频 | 色综合国产 | 午夜精品久久久久久 | 波多野结衣一区二区三区高清 | 久久一区 | 中文字幕一区二区三区日韩精品 | 日韩精品视频在线播放 | ririsao久久精品一区 | 免费在线观看国产 | 天天天操操操 | 91精品国产综合久久久久久丝袜 | 午夜爽| 国产精品污www在线观看 | 色就是色网站 | 国产伦精品一区二区三区四区视频 | 国产精品污www一区二区三区 | 亚洲一区二区在线免费观看 | 中文字幕在线观看精品视频 | 成人免费毛片高清视频 | 国产美女在线观看 | 日本在线观看视频一区 | 欧美成人免费观看 | 久久手机免费视频 | 亚洲狠狠爱一区二区三区 | 精品欧美黑人一区二区三区 | 日韩色视频| 国产一级视频 | 在线免费黄色小视频 | 成人看片免费网站 | 91麻豆精品一二三区在线 | 国产伦精品一区二区三区四区视频 | 欧美大片在线看免费观看 | 九九九色| 欧美一区二区激情三区 | 免费的污网站 | 一级a性色生活片久久毛片 夜夜视频 | 免费二区 | 成人黄页在线观看 | 色综合久久久久综合99 | 欧美日韩久久 | 久久av资源网 | 一区二区三区精品 | 99爱视频| 成人影院在线 | 成人免费crm一区二区 | 亚洲午夜在线 | 在线观看亚洲一区 | a级三四级黄大片 | 欧美日韩免费一区二区三区 | 日韩毛片免费看 | 西西做爰免费视频 | 国产一级片a | 亚洲精品一区二三区不卡 | 国产影音先锋 | 国产黄色一级大片 | 国产精品久久精品 | 国产精品视频一区二区三区四蜜臂 | 日日操夜夜操天天操 | 国产成人精品一区二 | 精品无码久久久久久国产 | 日韩一区二区不卡 | 午夜影院免费体验区 | 四色成人av永久网址 | av网站免费在线观看 | 夜本色| 国产精品国产三级国产a | www.日韩三级 | 一级全黄少妇性色生活片毛片 | 97久久久 | 亚洲一区二区三区高清 | 国产精品亚洲精品久久 | 一区二区不卡视频 | 永久精品| 中文字幕亚洲一区二区三区 | 日韩三级av在线 | 成人免费视频在线观看 | 精品国产91亚洲一区二区三区www | 一区二区免费视频观看 | 久久不卡 | av在线播放国产 | 日韩精品 电影一区 亚洲 | 久久亚洲一区 | 欧美1314 | 日本三级电影网站 | 国产在线看片 | 国产精品久久久久久久久久免费看 | 亚洲视频一区在线 | 99精品久久久久久久免费看蜜月 | 成人国产网站 | 影音先锋亚洲精品 | 一区二区在线看 | 99视频网 | 中文字幕亚洲精品 | 亚洲国产欧美在线 | 免费毛片网 | 91爱爱 | 天天澡天天狠天天天做 | 美女视频黄色免费 | 一区二区视频 | 国产精品日产欧美久久久久 | 国产一区二区自拍视频 | 国产精品久久久久久久午夜片 | 国产日韩欧美一二三区 | 久久久精品久久 | 日韩在线欧美 | 免费福利网站 | 免费观看日韩av | 91丁香婷婷综合久久欧美 | 久久久久91| 理论黄色片 | 国产精品福利网站 | 中文字幕在线播放一区 | 日韩不卡av | 久久人 | av在线日韩 | 国产精品一区二区不卡 | 欧洲妇女成人淫片aaa视频 | 亚洲精品在线免费 | 日本一级中文字幕久久久久久 | 在线一区观看 | 国内精品一级毛片国产99 | 一级黄色大片 | 日本一区二区三区免费观看 | 麻豆视频91 | 性处破╳╳╳高清欧美 | 91精品国产91久久久久久吃药 | 亚洲国产成人av | 国产色| 在线观看国产www | 亚洲国产成人av好男人在线观看 | 一区二区三区免费网站 | 天天操天天添 | 成人在线黄色 | 欧美激情视频一区二区三区在线播放 | 亚洲一区在线观看视频 | 中文字幕成人 | 久久久久久久久免费视频 | 日韩一区精品视频 | 亚洲在线视频 | 国产成人精品久久二区二区 | 国产乱码精品一区二区三区av | 久久久久久一区 | 中文字幕av一区二区 | 久久精品视频在线播放 | 亚洲一区二区国产 | 国产二区三区 | 亚洲午夜精品一区二区三区他趣 | 欧美精品1区2区3区 国产女无套免费网站 | 国产精品片aa在线观看 | 国产91久久精品一区二区 | 免费视频一区二区 | 国产福利一区二区三区四区 | 久久久久久久一区 | 国产亚洲精品精品国产亚洲综合 | 免费黄在线观看 | 日本一区二区不卡视频 | 久久国产精品视频 | 中文字幕日韩欧美一区二区三区 | 亚洲欧美中文字幕 | 欧美国产在线观看 | 久久人| 日韩精品一区二区三区在线播放 | 免费看片色 | 综合精品| 在线观看日韩 | 超碰在线一区二区三区 | 性色av一区二区三区免费看开蚌 | 国产3区| 亚洲国产中文字幕 | 操操日| 欧美黄色一级 | 久久99精品国产99久久6男男 | 久久久精品免费视频 | 五月婷婷综合激情 | 国产精品久久天天躁 | 国产精品国产精品国产专区不片 | 成人午夜影院 | 日韩精品专区 | 91色电影 | 亚洲精品一区二三区不卡 | 日本在线视频不卡 | 久久夜视频 | 亚洲天堂久久 | 国产高清免费 | 久久成人综合网 | 亚洲午夜精品久久久久久app | 国产美女啪啪 | 日本末发育嫩小xxxx | 天天操天天拍 | 国产人妖视频 | 亚洲成人一区二区三区 | 久久久国产一区二区三区四区小说 | 最新午夜 | 欧美国产日韩一区二区 | 97人人做人人人难人人做 | 欧洲一区在线 | 国产高清精品一区二区三区 | 二区免费视频 | 日本中文字幕在线观看 | 欧美国产精品一区 | 成人精品视频 | 日韩欧美一区二区三区 | 久久久999成人 | 日韩www | 伊人影院在线观看 | 欧美精品一区在线发布 | 色婷婷国产精品 | 色天天综合久久久久综合片 | 欧美精品1区2区3区 国产女无套免费网站 | 青青草久久网 | 久久99精品久久久久蜜臀 | av在线成人 | 91.成人天堂一区 | 日本在线小视频 | 国产精品91久久久久 | 欧美一级精品片在线看 | 国产精品毛片一区二区 | 91视频.com | 国产精品日本一区二区不卡视频 | 成人福利影院 | 国产精品视频免费看 | 国产日韩久久 | 免费高清av | 精品久久精品久久 | 91视频国产网站 | www.av在线| 中文字幕av一区二区三区 | 国产精品久久久久久久午夜 | 在线观看a视频 | 欧美日韩不卡合集视频 | 日本一区二区精品 | 国产一区二区三区在线 | 日韩福利| 一本一道久久久a久久久精品91 | 日日做夜夜爱 | h免费在线 | 国产精品精品视频 | 91免费看电影 | 国产精品久久久久久亚洲调教 | 午夜精品久久久久久久久久久久 | 久久99深爱久久99精品 | 91高清在线 | 少妇久久久 | 91精品久久久久久综合五月天 | 国产精品夜夜 | 国产成人一级片 | 日韩在线中文字幕 | 91高清在线| 成人免费一区二区三区视频网站 | 激情网页 | 看a网址 | 亚洲欧美91 | 欧美成人性生活 | 国内精品成人 | 中文字幕一区二区在线观看 | 一区二区在线看 | 日韩一区精品 | 精品乱码一区二区 | 久久99国产精品久久99大师 | 国产一区二区三区免费播放 | 久久久久九九九九九 | 黄瓜av| 亚洲精品久久久久国产 | 国产老女人精品毛片久久 | 日本不卡免费新一二三区 | 九九热在线免费视频 | 亚洲国产精品麻豆 | 欧美自拍一区 | 2020天天操 | 天堂综合网| 久久这里只有精品免费 | 亚洲欧美日韩另类一区二区 | 精品国产一区二区在线 | av在线网址观看 | 97人人做人人人难人人做 | 精品一区二区三区中文字幕 | 国产三级在线观看 | 久久免费福利视频 | 91在线免费视频 | 综合伊人 | 国产电影一区二区 | 色999精品 | 成人片网址| 日韩av免费在线观看 | 国产综合av | 免费国产视频在线观看 | 超碰3 | 国产精品成人3p一区二区三区 | 国产精品久久久久久久久久久久 | 色视频在线免费观看 | 亚洲成人福利在线观看 | 国产大毛片 | 久久九| 国产精品久久 | 欧美一区二区三区在线观看视频 | 91在线播| 久久精品久久久久电影 | 亚洲一区二区在线 | 日韩免费在线观看视频 | 成人欧美一区二区三区白人 | 亚洲精品一区二区在线观看 | 爱爱视频在线观看 | 国产v片| 欧美视频二区 | 麻豆成人在线 | 蜜桃精品视频在线 | 日本三级中国三级99人妇网站 | 亚洲视频一区 | 国产精品久久九九 | 北条麻妃国产九九九精品小说 | a级在线免费 | 欧美国产精品一区二区三区 | 色伊人| 99精品全国免费观看视频软件 | 日本久久影视 |