精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

協程庫Libtask源碼分析之架構篇

開發 架構
本文介紹libtask的基礎原理。我們從libtask的main函數開始,這個main函數就是我們在c語言中使用的c函數,libtask本身實現了main這個函數,用戶使用libtask時,要實現的是taskmain函數。taskmain和main的函數聲明是一樣的。下面我們看一下main函數。

 [[382125]]

本文轉載自微信公眾號「編程雜技」,作者theanarkh 。轉載本文請聯系編程雜技公眾號。

前言:假設讀者已經了解了協程的概念,實現協程的底層技術支持。本文會介紹基于底層基礎,如何實現協程以及協程的應用(更多基礎可以點擊這里[1])。

libtask是google大佬Russ Cox(Go的核心開發者)所寫,本文介紹libtask的基礎原理。我們從libtask的main函數開始,這個main函數就是我們在c語言中使用的c函數,libtask本身實現了main這個函數,用戶使用libtask時,要實現的是taskmain函數。taskmain和main的函數聲明是一樣的。下面我們看一下main函數。

  1. int main(int argc, char **argv) 
  2.     struct sigaction sa, osa; 
  3.     // 注冊SIGQUIT信號處理函數 
  4.     memset(&sa, 0, sizeof sa); 
  5.     sa.sa_handler = taskinfo; 
  6.     sa.sa_flags = SA_RESTART; 
  7.     sigaction(SIGQUIT, &sa, &osa); 
  8.  
  9.     // 保存命令行參數 
  10.     argv0 = argv[0]; 
  11.     taskargc = argc; 
  12.     taskargv = argv; 
  13.  
  14.     if(mainstacksize == 0) 
  15.         mainstacksize = 256*1024; 
  16.     // 創建第一個協程 
  17.     taskcreate(taskmainstart, nil, mainstacksize); 
  18.     // 開始調度 
  19.     taskscheduler(); 
  20.     fprint(2, "taskscheduler returned in main!\n"); 
  21.     abort(); 
  22.     return 0; 

main函數主要的兩個邏輯是taskcreate和taskscheduler函數。我們先來看taskcreate。

  1. int taskcreate(void (*fn)(void*), void *arg, uint stack) 
  2.     int id; 
  3.     Task *t; 
  4.  
  5.     t = taskalloc(fn, arg, stack); 
  6.     taskcount++; 
  7.     id = t->id; 
  8.     // 記錄位置 
  9.     t->alltaskslot = nalltask; 
  10.     // 保存到alltask中 
  11.     alltask[nalltask++] = t; 
  12.     // 修改狀態為就緒,可以被調度,并且加入到就緒隊列 
  13.     taskready(t); 
  14.     return id; 

taskcreate首先調用taskalloc分配一個表示協程的結構體Task。我們看看這個結構體的定義。

  1. struct Task 
  2.     char    name[256];    // offset known to acid 
  3.     char    state[256]; 
  4.     // 前后指針 
  5.     Task    *next
  6.     Task    *prev; 
  7.     Task    *allnext; 
  8.     Task    *allprev; 
  9.     // 執行上下文 
  10.     Context    context; 
  11.     // 睡眠時間 
  12.     uvlong    alarmtime; 
  13.     uint    id; 
  14.     // 棧信息 
  15.     uchar    *stk; 
  16.     uint    stksize; 
  17.     //是否退出了 
  18.     int    exiting; 
  19.     // 在alltask的索引 
  20.     int    alltaskslot; 
  21.     // 是否是系統協程 
  22.     int    system; 
  23.     // 是否就緒狀態 
  24.     int    ready; 
  25.     // 入口函數 
  26.     void    (*startfn)(void*); 
  27.     // 入口參數 
  28.     void    *startarg; 
  29.     // 自定義數據 
  30.     void    *udata; 
  31. }; 

接著看看taskalloc的實現。

  1. // 分配一個協程所需要的內存和初始化某些字段 
  2. static Task* 
  3. taskalloc(void (*fn)(void*), void *arg, uint stack) 
  4.     Task *t; 
  5.     sigset_t zero; 
  6.     uint x, y; 
  7.     ulong z; 
  8.  
  9.     /* allocate the task and stack together */ 
  10.     // 結構體本身的大小+棧大小 
  11.     t = malloc(sizeof *t+stack); 
  12.     memset(t, 0, sizeof *t); 
  13.     // 棧的內存位置 
  14.     t->stk = (uchar*)(t+1); 
  15.     // 棧大小 
  16.     t->stksize = stack; 
  17.     // 協程id 
  18.     t->id = ++taskidgen; 
  19.     // 協程工作函數和參數 
  20.     t->startfn = fn; 
  21.     t->startarg = arg; 
  22.  
  23.     /* do a reasonable initialization */ 
  24.     memset(&t->context.uc, 0, sizeof t->context.uc); 
  25.     sigemptyset(&zero); 
  26.     // 初始化uc_sigmask字段為空,即不阻塞信號 
  27.     sigprocmask(SIG_BLOCK, &zero, &t->context.uc.uc_sigmask); 
  28.  
  29.     /* must initialize with current context */ 
  30.     // 初始化uc字段 
  31.     getcontext(&t->context.uc)  
  32.     // 設置協程執行時的棧位置和大小 
  33.     t->context.uc.uc_stack.ss_sp = t->stk+8; 
  34.     t->context.uc.uc_stack.ss_size = t->stksize-64; 
  35.     z = (ulong)t; 
  36.     y = z; 
  37.     z >>= 16;    /* hide undefined 32-bit shift from 32-bit compilers */ 
  38.     x = z>>16; 
  39.     // 保存信息到uc字段 
  40.     makecontext(&t->context.uc, (void(*)())taskstart, 2, y, x); 
  41.  
  42.     return t; 

taskalloc函數代碼看起來很多,但是邏輯不算復雜,就是申請Task結構體所需的內存和執行時棧的內存,然后初始化各個字段。這樣,一個協程就誕生了。接著執行taskready把協程加入就緒隊列。

  1. // 修改協程的狀態為就緒并加入就緒隊列 
  2. void taskready(Task *t) 
  3.     t->ready = 1; 
  4.     addtask(&taskrunqueue, t); 
  5.  
  6. // 把協程插入隊列中,如果之前在其他隊列,則會被移除 
  7. void addtask(Tasklist *l, Task *t) 
  8.     if(l->tail){ 
  9.         l->tail->next = t; 
  10.         t->prev = l->tail; 
  11.     }else
  12.         l->head = t; 
  13.         t->prev = nil; 
  14.     } 
  15.     l->tail = t; 
  16.     t->next = nil; 

taskrunqueue記錄了所有就緒的協程。創建了協程并加入隊列后,協程還沒有開始執行,就像操作系統的進程和線程一樣,需要有一個調度器來調度執行。下面我們看看調度器的實現。

  1. // 協程調度中心 
  2. static void taskscheduler(void) 
  3.     int i; 
  4.     Task *t; 
  5.     for(;;){ 
  6.         // 沒有用戶協程了,則退出 
  7.         if(taskcount == 0) 
  8.             exit(taskexitval); 
  9.         // 從就緒隊列拿出一個協程 
  10.         t = taskrunqueue.head; 
  11.         if(t == nil){ 
  12.             fprint(2, "no runnable tasks! %d tasks stalled\n", taskcount); 
  13.             exit(1); 
  14.         } 
  15.         // 從就緒隊列刪除該協程 
  16.         deltask(&taskrunqueue, t); 
  17.         t->ready = 0; 
  18.         // 保存正在執行的協程 
  19.         taskrunning = t; 
  20.         // 切換次數加一 
  21.         tasknswitch++; 
  22.         // 切換到t執行,并且保存當前上下文到taskschedcontext(即下面要執行的代碼) 
  23.         contextswitch(&taskschedcontext, &t->context); 
  24.         // 執行到這說明沒有協程在執行(t切換回來的),置空 
  25.         taskrunning = nil; 
  26.         // 剛才執行的協程t退出了 
  27.         if(t->exiting){ 
  28.             // 不是系統協程,則個數減一 
  29.             if(!t->system) 
  30.                 taskcount--; 
  31.             // 當前協程在alltask的索引 
  32.             i = t->alltaskslot; 
  33.             // 把最后一個協程換到當前協程的位置,因為他要退出了 
  34.             alltask[i] = alltask[--nalltask]; 
  35.             // 更新被置換協程的索引 
  36.             alltask[i]->alltaskslot = i; 
  37.             // 釋放堆內存 
  38.             free(t); 
  39.         } 
  40.     } 

調度器的代碼看起來很多,但是核心邏輯就三個 1 從就緒隊列中拿出一個協程t,并把t移出就緒隊列 2 通過contextswitch切換到協程t中執行 3 協程t切換回調度中心,如果t已經退出,則修改數據結構,然后回收他占據的內存。如果t沒退出,則繼續調度其他協程執行。至此,協程就開始跑起來了。并且也有了調度系統。這里的調度機制是比較簡單的,就是按著先進先出的方式就緒調度,并且是非搶占的。即沒有按時間片調度的概念,一個協程的執行時間由自己決定,放棄執行的權力也是自己控制的,當協程不想執行了可以調用taskyield讓出cpu。

  1. // 協程主動讓出cpu 
  2. int taskyield(void) 
  3.     int n; 
  4.     // 當前切換協程的次數 
  5.     n = tasknswitch; 
  6.     // 插入就緒隊列,等待后續的調度 
  7.     taskready(taskrunning); 
  8.     taskstate("yield"); 
  9.     // 切換協程 
  10.     taskswitch(); 
  11.     // 等于0說明當前只有自己一個協程,調度的時候tasknswitch加一,所以這里減一 
  12.     return tasknswitch - n - 1; 
  13.  
  14. /* 
  15.     切換協程,taskrunning是正在執行的協程,taskschedcontext是調度協程(主線程)的上下文, 
  16.     切換到調度中心,并保持當前上下文到taskrunning->context 
  17. */ 
  18. void taskswitch(void) 
  19.     needstack(0); 
  20.     contextswitch(&taskrunning->context, &taskschedcontext); 
  21.  
  22. // 真正切換協程的邏輯 
  23. static void contextswitch(Context *from, Context *to
  24.     if(swapcontext(&from->uc, &to->uc) < 0){ 
  25.         fprint(2, "swapcontext failed: %r\n"); 
  26.         assert(0); 
  27.     } 

yield的邏輯也很簡單,因為協程在執行的時候,是不處于就緒隊列的,當協程準備讓出cpu時,協程首先把自己重新加入到就緒隊列,等待下次被調度執行。當然我們也可以直接調度contextswitch切換到其他協程。重點在于什么時候應該讓出cpu,又什么時候應該被調度執行。接下來會詳細講解。至此,我們已經有了支持協程所需要的底層基礎。我們看到這個實現的思路也不是很復雜,首先有一個隊列表示待執行的的協程,每一個協程對應一個Task結構體。然后調度中心不斷地按照先進先出的方式去調度協程的執行就可以。因為沒有搶占機制,所以調度中心是依賴協程本身去驅動的,協程需要主動讓出cpu,把上下文切換回調度中心,調度中心才能進行下一輪的調度。接下來我們看看,基于這些底層基礎,如果實現一個基于協程的服務器。下面我們通過一個例子進行講解。

  1. void 
  2. taskmain(int argc, char **argv) 
  3.     // 啟動一個tcp服務器 
  4.     if((fd = netannounce(TCP, 0, atoi(argv[1]))) < 0){ 
  5.         // ... 
  6.     } 
  7.     // 改為非阻塞模式 
  8.     fdnoblock(fd); 
  9.     // accept成功后創建一個客戶端協程 
  10.     while((cfd = netaccept(fd, remote, &rport)) >= 0){ 
  11.         taskcreate(proxytask, (void*)cfd, STACK); 
  12.     } 

我們剛才講過taskmain是我們需要實現的函數,首先通過netannounce建立一個tcp服務器。接著把fd改成非阻塞的,這個非常重要,因為在后面調用accept的時候,如果是阻塞的文件描述符,那么就會引起進程掛起,而非阻塞模式下,操作系統會返回EAGAIN的錯誤碼,通過這個錯誤碼我們可以決定下一步做什么。我們看看netaccept的實現。

  1. // 處理(摘下)連接 
  2. int 
  3. netaccept(int fd, char *server, int *port) 
  4.     int cfd, one; 
  5.     struct sockaddr_in sa; 
  6.     uchar *ip; 
  7.     socklen_t len; 
  8.     // 注冊事件到epoll,等待事件觸發 
  9.     fdwait(fd, 'r'); 
  10.     len = sizeof sa; 
  11.     // 觸發后說明有連接了,則執行accept 
  12.     if((cfd = accept(fd, (void*)&sa, &len)) < 0){ 
  13.         return -1; 
  14.     } 
  15.     // 和客戶端通信的fd也改成非阻塞模式 
  16.     fdnoblock(cfd); 
  17.     one = 1; 
  18.     setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof one); 
  19.     return cfd; 

netaccept就是通過調用accept逐個處理tcp連接,但是在accept之前,有一個非常重要的操作fdwait。

  1. // 協程因為等待io需要切換 
  2. void fdwait(int fd, int rw) 
  3. {     
  4.     // 是否已經初始化epoll 
  5.     if(!startedfdtask){ 
  6.         startedfdtask = 1; 
  7.         epfd = epoll_create(1); 
  8.         // 沒有初始化則創建一個協程,做io管理 
  9.         taskcreate(fdtask, 0, 32768); 
  10.     } 
  11.     struct epoll_event ev = {0}; 
  12.     // 記錄事件對應的協程和感興趣的事件 
  13.     ev.data.ptr = taskrunning; 
  14.     switch(rw){ 
  15.     case 'r'
  16.         ev.events |= EPOLLIN | EPOLLPRI; 
  17.         break; 
  18.     case 'w'
  19.         ev.events |= EPOLLOUT; 
  20.         break; 
  21.     } 
  22.  
  23.     int r = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev); 
  24.     // 切換到其他協程,等待被喚醒 
  25.     taskswitch(); 
  26.     // 喚醒后函數剛才注冊的事件 
  27.     epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev); 

fdwait首先把fd注冊到epoll中,然后把協程切換到下一個待執行的協程。這里有個細節,當協程X被調度執行的時候,他是脫離了就緒隊列的,而taskswitch函數只是實現了切換上下文到調度中心,調度中心會從就緒隊列從選擇下一個協程執行,那么這時候,脫離就緒隊列的協程X就處于孤島狀態,看起來再也無法給調度中心選中執行,這個問題的處理方式是,把協程、fd和感興趣的事件信息一起注冊到epoll中,當epoll監聽到某個fd的事件發生時,就會把對應的協程加入就緒隊列,這樣協程就可以被調度執行了。在fdwait函數一開始那里處理了epoll相關的邏輯。epoll的邏輯也是在一個協程中執行的,但是epoll所在協程和一般協程不一樣,類似于操作系統的內核線程一樣,epoll所在的協程成為系統協程,即不是用戶定義的,而是系統定義的。我們看一下實現

  1. void fdtask(void *v) 
  2.     int i, ms; 
  3.     Task *t; 
  4.     uvlong now; 
  5.     // 變成系統協程 
  6.     tasksystem(); 
  7.     struct epoll_event events[1000]; 
  8.     for(;;){ 
  9.         /* let everyone else run */ 
  10.         // 大于0說明還有其他就緒協程可執行,則先讓給他們執行,否則往下執行 
  11.         while(taskyield() > 0) 
  12.             ; 
  13.         /* we're the only one runnable - poll for i/o */ 
  14.         errno = 0; 
  15.         // 沒有定時事件則一直阻塞 
  16.         if((t=sleeping.head) == nil) 
  17.             ms = -1; 
  18.         else
  19.             /* sleep at most 5s */ 
  20.             now = nsec(); 
  21.             if(now >= t->alarmtime) 
  22.                 ms = 0; 
  23.             else if(now+5*1000*1000*1000LL >= t->alarmtime) 
  24.                 ms = (t->alarmtime - now)/1000000; 
  25.             else 
  26.                 ms = 5000; 
  27.         } 
  28.         int nevents; 
  29.         // 等待事件發生,ms是等待的超時時間 
  30.         if((nevents = epoll_wait(epfd, events, 1000, ms)) < 0){ 
  31.             if(errno == EINTR) 
  32.                 continue
  33.             fprint(2, "epoll: %s\n", strerror(errno)); 
  34.             taskexitall(0); 
  35.         } 
  36.  
  37.         /* wake up the guys who deserve it */ 
  38.         // 事件觸發,把對應協程插入就緒隊列 
  39.         for(i=0; i<nevents; i++){ 
  40.             taskready((Task *)events[i].data.ptr); 
  41.         } 
  42.  
  43.         now = nsec(); 
  44.         // 處理超時事件 
  45.         while((t=sleeping.head) && now >= t->alarmtime){ 
  46.             deltask(&sleeping, t); 
  47.             if(!t->system && --sleepingcounted == 0) 
  48.                 taskcount--; 
  49.             taskready(t); 
  50.         } 
  51.     } 

我們看到epoll的處理邏輯和一般服務器的類似,通過epoll_wait阻塞,然后epoll_wait返回時,處理每一個發生的事件,而且libtask還支持超時事件。另外libtask中當還有其他就緒協程的時候,是不會進入epoll_wait的,它會把cpu讓給就緒的協程(通過taskyield函數),當就緒隊列只有epoll所在的協程時才會進入epoll的邏輯。至此,我們看到了libtask中如何把異步變成同步的。當用戶要調用一個可能會引起進程掛起的接口時,就可以調用libtask提供的一個相應的API,比如我們想讀一個文件,我們可以調用libtask的fdread。

  1. int 
  2. fdread(int fd, void *buf, int n) 
  3.     int m; 
  4.     // 非阻塞讀,如果不滿足則再注冊到epoll,參考fdread1 
  5.     while((m=read(fd, buf, n)) < 0 && errno == EAGAIN) 
  6.         fdwait(fd, 'r'); 
  7.     return m; 

這樣就不需要擔心進程被掛起,同時也不需要處理epoll相關的邏輯(注冊事件,事件觸發時的處理等等)。異步轉同步,libtask的方式就是通過提供對應的API,先把用戶的fd注冊到epoll中,然后切換到其他協程,等epoll監聽到事件觸發時,就會把對應的協程插入就緒隊列,當該協程被調度中心選中執行時,就會繼續執行剩下的邏輯而不會引起進程掛起,因為這時候所等待的條件已經滿足。

總結:libtask的設計思想就是把業務邏輯封裝到一個個協程中,由libtask實現協程的調度,在各個業務邏輯中進行切換,從而驅動著系統的運行。另外libtask也提供了一個網絡和文件io異步變同步的解決方案。使得我們使用起來更加方便,高效。今天先講到這里。

References

[1] 更多基礎可以點擊這里: https://github.com/theanarkh/read-libtask-code/blob/main/README.md

 

責任編輯:武曉燕 來源: 編程雜技
相關推薦

2021-02-20 06:09:46

libtask協程鎖機制

2025-02-08 09:13:40

2023-04-19 21:20:49

Tars-Cpp協程

2021-05-20 09:14:09

Kotlin協程掛起和恢復

2021-09-16 09:59:13

PythonJavaScript代碼

2022-09-12 06:35:00

C++協程協程狀態

2021-08-04 16:19:55

AndroidKotin協程Coroutines

2023-11-17 11:36:59

協程纖程操作系統

2023-07-13 08:06:05

應用協程阻塞

2025-06-26 04:10:00

2023-10-24 19:37:34

協程Java

2021-12-09 06:41:56

Python協程多并發

2023-12-05 13:46:09

解密協程線程隊列

2021-05-21 08:21:57

Go語言基礎技術

2014-02-11 09:28:57

2022-09-06 20:30:48

協程Context主線程

2020-11-29 17:03:08

進程線程協程

2016-10-28 17:39:47

phpgolangcoroutine

2017-05-02 11:38:00

PHP協程實現過程

2023-08-08 07:18:17

協程管道函數
點贊
收藏

51CTO技術棧公眾號

国产欧美日韩久久| 久久99精品久久久久久水蜜桃| 欧美一区二区三区成人久久片| 看黄色录像一级片| 人人妻人人澡人人爽人人欧美一区 | 二区三区精品| 久久蜜桃香蕉精品一区二区三区| 欧美成aaa人片免费看| 日韩一级片播放| 日本人妖在线| 99精品国产一区二区三区2021| 97se亚洲国产综合自在线观| 久久99最新地址| 精品网站999www| 97视频资源在线观看| 丁香激情五月少妇| 国产69精品久久app免费版| 欧美三级黄美女| 欧美久久一二三四区| 婷婷四月色综合| 成年人av网站| 伊人久久大香线蕉综合网站 | 亚洲免费视频一区二区| 777av视频| 午夜精品在线播放| 中文字幕亚洲综合久久五月天色无吗''| 色8久久人人97超碰香蕉987| 看高清中日韩色视频| 日韩欧美性视频| 免费观看成人www动漫视频| 亚洲电影一区二区三区| 国产亚洲福利社区| 特黄视频免费看| 亚洲性视频大全| 日韩欧美在线一区二区三区| 欧美黄网在线观看| www.五月激情| 麻豆精品国产91久久久久久 | 免费一级在线观看| 美女尤物久久精品| 亚洲人成电影网站色www| 国产精品人人妻人人爽人人牛| caopen在线视频| 国产不卡在线视频| 国语自产精品视频在免费| 白嫩情侣偷拍呻吟刺激| av丝袜在线| 久久久久青草大香线综合精品| 茄子视频成人在线| 超碰人人干人人| 亚洲91在线| 亚洲男女毛片无遮挡| 岛国一区二区三区高清视频| 久久精品这里只有精品| 日韩av中字| 欧美国产精品一区二区三区| 国产精品网红福利| 欧美精品一区二区成人| 亚洲欧美网站在线观看| 日韩一区二区三区在线播放| 亚洲五月激情网| 美女露胸视频在线观看| 久久久久99精品国产片| 久久久综合亚洲91久久98| 狠狠综合久久av一区二区| 国产日韩欧美一区在线| 自拍偷拍亚洲一区| 久久黄色一级视频| 日韩天堂在线| 亚洲免费成人av| 青青在线视频免费观看| 岛国大片在线观看| 久久99精品国产麻豆不卡| 国产成人极品视频| 欧美色图一区二区| 国产99精品| 日韩欧美国产不卡| 一本久道综合色婷婷五月| a黄色片在线观看| 亚洲六月丁香色婷婷综合久久 | 色欲狠狠躁天天躁无码中文字幕 | 色婷婷av一区二区三区久久| 香蕉视频污视频| 亚洲理论电影片| 最新国产精品拍自在线播放| 永久看片925tv| 欧美日韩激情| 日韩av综合中文字幕| 91亚洲一区二区| 国产91欧美| 亚洲国产wwwccc36天堂| 国产乱子夫妻xx黑人xyx真爽| xvideos国产在线视频| 亚洲一区二区五区| 亚洲精品偷拍视频| 成年午夜在线| 亚洲美女一区二区三区| 日韩欧美视频网站| 色在线视频网| 亚洲另类在线视频| 欧美亚洲一二三区| 国产999精品在线观看| 日韩av一区在线| 性生交大片免费全黄| 亚洲久久在线| 久久久久国产一区二区三区| 四虎影视一区二区| 欧美一区二区三区激情视频| 日韩精品福利在线| 韩国一级黄色录像| 国产精品视频| 波多野结衣一区二区三区在线观看| 亚洲专区第一页| 免费观看成人av| 国产精品网红福利| 天天摸天天干天天操| 国产suv精品一区二区6| 日本高清视频一区二区三区| 羞羞视频在线观看免费| 欧美怡红院视频| 天堂网在线免费观看| 91九色综合| 亚洲精品狠狠操| 大黑人交xxx极品hd| 窝窝社区一区二区| 亚洲无限av看| 亚洲色图27p| 久久激情视频| 精品视频高清无人区区二区三区| 婷婷久久久久久| 亚洲人成伊人成综合网小说| 久久久久久av无码免费网站下载| 日产精品一区| 亚洲乱亚洲乱妇无码| 久久精品久久国产| 国产日韩一区二区三区在线| 91九色露脸| 八戒八戒神马在线电影| 678五月天丁香亚洲综合网| 男人女人拔萝卜视频| 四虎成人av| 欧美激情第1页| 91美女免费看| 免费看黄色91| 欧美一级爱爱| 成人做爰视频www网站小优视频| 亚洲精品美女在线| 自拍偷拍欧美亚洲| 99热99精品| 无遮挡亚洲一区| 春暖花开亚洲一区二区三区| 亚洲美女www午夜| 久久国产视频一区| 激情五月婷婷综合| 久久久国产精品一区二区三区| sis001亚洲原创区| 亚洲国产精品va在线看黑人 | 欧美三级电影在线| 性色av一区二区咪爱| 国产91av在线播放| 丰满岳乱妇一区二区三区| 黄色一级片国产| 成人性生交大片免费看中文视频| 高清欧美电影在线| 亚洲av毛片成人精品| 亚洲欧洲另类国产综合| 精品人妻少妇一区二区| 国语一区二区三区| 久久精品99国产精品酒店日本| 久草视频精品在线| heyzo一本久久综合| 六月丁香激情网| 欧美亚洲精品在线| 91在线视频一区| 男人天堂网在线视频| 激情久久av一区av二区av三区| 超碰在线97免费| 欧美交a欧美精品喷水| 欧美一级电影免费在线观看| 国产女人高潮时对白| 久久综合视频网| 日韩精品免费播放| 999久久久亚洲| 国产伦精品一区二区三区视频免费 | 亚洲乱码国产乱码精品| 国产精品精品国产色婷婷| 5月婷婷6月丁香| jlzzjlzz亚洲女人| 97中文在线观看| 这里有精品可以观看| 欧美成人一区二区三区| 天天躁夜夜躁狠狠是什么心态| 日韩不卡一二三区| 免费观看成人高| 日韩第二十一页| 午夜免费在线观看精品视频| 福利成人在线观看| 欧美xxxxx牲另类人与| 天天干天天操天天爱| 亚洲视频图片小说| 人妻在线日韩免费视频| 好看的av在线不卡观看| 成人福利网站在线观看11| 免费在线观看污视频| 69p69国产精品| 四虎影院在线免费播放| 一区二区三区中文字幕电影| 一级黄色大片儿| 毛片一区二区| 国产一区 在线播放| 国产影视一区| 国产精品第100页| 高清在线观看av| 精品国产精品网麻豆系列| 九九视频免费看| 日本一区二区不卡视频| 日本69式三人交| 国产一区二区你懂的| 免费成人深夜夜行网站视频| 亚洲丁香日韩| 国产综合av一区二区三区| 日本欧美在线| 国产精品国内视频| 川上优av中文字幕一区二区| 亚洲国产小视频在线观看| 国产一区二区在线视频聊天| 成人欧美一区二区三区白人 | 国产高清精品在线观看| 噜噜噜狠狠夜夜躁精品仙踪林| 久久久久久91| 麻豆影视国产在线观看| 日韩一级二级三级精品视频| 亚洲午夜无码久久久久| 欧美午夜精品久久久久久人妖| 永久免费看mv网站入口78| 男女性色大片免费观看一区二区 | 亚洲国产精品人久久电影| 国产乱叫456在线| 亚洲一区二区三区激情| xxxx日本少妇| 综合电影一区二区三区 | 久久久五月天| 国产精品久久久久免费| 国产亚洲观看| 亚洲一区二区三区成人在线视频精品| 久久久加勒比| 久久久免费在线观看| 免费不卡av| 国产亚洲一区精品| 国产黄色片在线观看| 亚洲视频在线观看视频| 黄色av网站在线免费观看| 欧美一级高清片| 国产成人久久精品77777综合| 欧美日韩美女视频| 天海翼在线视频| 亚洲视频一二区| 久久久久久久国产视频| 亚洲午夜在线观看视频在线| 日本三级视频在线| 国产精品福利一区二区三区| eeuss中文字幕| 99这里只有久久精品视频| 国产人妖在线观看| 毛片一区二区三区| www.com黄色片| 国产在线日韩欧美| 欧美伦理视频在线观看| 免费成人美女在线观看.| theporn国产精品| 石原莉奈在线亚洲二区| 欧美日韩福利在线| 国产精品99一区二区三| 欧美精品亚洲精品| 精品国产亚洲一区二区三区大结局 | 日本不卡视频在线| 日韩av中文字幕第一页| 一级成人国产| 日韩亚洲欧美视频| 裸体一区二区| 深爱五月综合网| 99久久久国产精品| 我不卡一区二区| 伊人婷婷欧美激情| 日韩在线 中文字幕| 8x8x8国产精品| 飘雪影院手机免费高清版在线观看 | 久久久久久九九九九九| 中文字幕亚洲区| 国产一级视频在线观看| 91黄色小视频| 国产成人精品av在线观| 亚洲欧美精品一区| 一二三四区在线观看| 深夜福利一区二区| bl视频在线免费观看| 国产精品嫩草视频| 巨胸喷奶水www久久久免费动漫| 55夜色66夜色国产精品视频| 青青在线精品| 老司机精品福利在线观看| 91精品1区| 黄色国产小视频| av一区二区三区| 搜索黄色一级片| 中文字幕日本不卡| 国产做受高潮漫动| 91精品国产综合久久精品| 国产亲伦免费视频播放| 亚洲久久久久久久久久| 污影院在线观看| 国产在线999| 国产精品**亚洲精品| 欧美久久久久久久| 激情另类综合| 日本在线xxx| 国产视频一区免费看| 五月六月丁香婷婷| 国产午夜亚洲精品羞羞网站| 亚欧洲乱码视频| 亚洲国产精品久久一线不卡| 国产又爽又黄免费软件| 亚洲性69xxxbbb| 亚洲精品永久免费视频| 国产精品污www一区二区三区| 91tv精品福利国产在线观看| 无需播放器的av| 国内精品写真在线观看| 国产精品久久久久久久av| 黑人巨大精品欧美一区免费视频| 亚洲国产www| 亚洲乱码国产乱码精品精天堂| 成人女同在线观看| 欧美壮男野外gaytube| gogo人体一区| 国产一线二线三线女| 国产精品一品视频| 欧美熟妇精品黑人巨大一二三区| 亚洲欧美另类久久久精品| 成人黄色激情视频| 一区二区三区精品99久久| 麻豆网站在线| 国产精品直播网红| 水蜜桃久久夜色精品一区| 污色网站在线观看| 欧美激情综合在线| 中文字幕视频二区| 精品国产乱码久久久久久牛牛| 直接在线观看的三级网址| 亚洲xxxx做受欧美| 午夜国产精品视频| 午夜精品久久久内射近拍高清| 97久久人人超碰| 久久久精品免费看| 亚洲天堂av电影| 国产综合色激情| 一区二区三区精品国产| 91久久久久| 91视频啊啊啊| 日韩欧美一区二区三区| 男操女在线观看| 国产精品视频自拍| 羞羞答答成人影院www| 中文字幕永久免费| 欧美日韩国产精品一区二区三区四区| 五月婷婷深深爱| 日本久久久久亚洲中字幕| 亚洲图色一区二区三区| 日韩视频专区| 亚洲日本久久| 人妻少妇无码精品视频区| 亚洲国产精品麻豆| 神马亚洲视频| 欧美国产在线电影| 欧洲vs亚洲vs国产| 狠狠热免费视频| 亚洲精品第一国产综合野| 日本天堂影院在线视频| 国产色视频一区| 日韩午夜免费视频| 欧美黄色高清视频| 日韩一二三区不卡| 写真福利精品福利在线观看| 精品一区二区成人免费视频| 三级在线观看一区二区| 日韩一区二区三区四区视频| 精品日韩99亚洲| 日本不卡一二三| 欧美大黑帍在线播放| 国产欧美日韩综合| 国产99视频在线| 国产成人精品在线播放| 欧美在线亚洲综合一区| 国产网站无遮挡| 91精品国产综合久久婷婷香蕉 | 波多野结衣先锋影音| 欧美色涩在线第一页| 国产在线色视频| av免费观看久久| 理论片日本一区|