亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C > 正文

C語言實現用戶態線程庫案例

2020-01-26 14:09:06
字體:
來源:轉載
供稿:網友

輪子年年有人造,我們也來湊熱鬧,參考協程實現,大概有以下幾種方法:

1)利用setjmp,longjmp

2)利用ucontext接口函數

3)匯編

(線程無非就是多了個搶占功能,由定時器觸發,而非自愿讓出運行權限)

因為我寫的時候還沒看到其他帖子,如果看到了,鐵定會用最直觀的ucontext接口寫的(注意,在macOSX中已經標注為廢除,頭文件得換做sys/ucontext.h),結果就是我用了匯編來寫,但是盡量不用匯編來寫整個switch_to調度函數(這樣有個明顯的壞處,那就是用gas/nasm的標準匯編格式寫的函數在macOSX下不能編譯通過,這個與系統自帶的編譯工具有關),而用經量少的內嵌匯編來寫。switch_to函數參考的是minix操作系統中任務切換函數實現的,用軟件時鐘器每隔1s發信號以激發switch_to函數切換任務。下面直接貼代碼了,對外提供了類似pthread的接口(只有兩個,分別是threadCreate和threadJoin)?,F在的代碼還非常的buggy,只能安全地支持在線程函數里頭純計算,其他的行為非??赡芤lbus error和segmentation fault。(要更加嚴謹地研究用戶態線程庫,請去看gnu pth的實現代碼)

 thread.h

#pragma once#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <signal.h>#include <assert.h>#include <time.h>#define JMP(r)  asm volatile /        (  "pushl %3/n/t" /          "popfd/n/t" /          "movl %2, %%ebp/n/t" /          "movl %0, %%esp/n/t" /          "jmp *%1/n/t" /          : /          : "m"(r._esp),"m"(r._eip),"m"(r._ebp),"m"(r._eflags) /          : /        )#define SAVE()         asm volatile /              (  "movl %%eax, %0/n/t" /                "movl %%ecx, %1/n/t" /                "movl %%edx, %2/n/t" /                "movl %%ebx, %3/n/t" /                  "movl %%esp, %4/n/t" /                "movl %%ebp, %5/n/t" /                "movl %%esi, %6/n/t" /                "movl %%edi, %7/n/t" /                "pushfd/n/t" /                "movl (%%esp), %%eax/n/t" /                "movl %%eax, %8/n/t" /                "popfd/n/t" /                : "=m"(_eax),"=m"(_ecx),"=m"(_edx),"=m"(_ebx) /                ,"=m"(_esp),"=m"(_ebp) /                , "=m"(_esi),"=m"(_edi),"=m"(_eflags) /                : /                : "%eax" /              )#define RESTORE(r)     asm volatile /              (  "movl %0, %%eax/n/t" /                "movl %1, %%ecx/n/t" /                "movl %1, %%edx/n/t" /                "movl %3, %%ebx/n/t" /                "movl %4, %%esi/n/t" /                "movl %5, %%edi/n/t" /                : /                :"m"(r._eax),"m"(r._ecx),"m"(r._edx),"m"(r._ebx) /                , "m"(r._esi),"m"(r._edi) /              )typedef void Func(int);/* __timer struct is the real Timer struct we use * id is unique to each timer * intersec is the inteval seconds to each signal forwarding the this Timer * sigactor is the handler for this Timer * next is a internal member used for linked list */struct __timer{  void *next;  unsigned int sec;  unsigned int intersec;  int id;  Func *sigactor;};/* struct alarm is ugly for the compatibility with early struct. * I should have used unnamed member instead of __inner. */typedef struct alarm *Timer;struct alarm{  union{    struct    {      Timer next;      unsigned int sec;    };    struct __timer __inner;  }; };typedef struct list *Header;struct list{  Timer head;};typedef struct __thread_table_regs Regs;struct __thread_table_regs{  int _edi;  int _esi;  int _ebp;  int _esp;  int _ebx;  int _edx;  int _ecx;  int _eax;  int _eip;  int _eflags;};typedef struct __ez_thread Thread_t;struct __ez_thread{  Regs regs;  int tid;  sigset_t sigmask;  unsigned int priority;  int tick;  int state;  int errno;  unsigned int stacktop;  unsigned int stacksize;  void *stack;  void *retval;  volatile int __reenter;};typedef struct __pnode pNode;struct __pnode{  pNode *next;  pNode *prev;  Thread_t *data;};typedef struct __loopcursor Cursor;struct __loopcursor{  int total;  pNode *current;};typedef struct __stack *Stack_t;struct __stack{  int __pad[4096];};void switch_to(int);extern Header hdr_ptr;extern Cursor live;extern Cursor dead;extern Thread_t pmain;

 thread.c

/* MIT LicenseCopyright (c) 2017 Yuandong-ChenPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in allcopies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THESOFTWARE. */#include "thread.h"/************************* Alarm facility *************************/struct list linkedlist;Header hdr_ptr = &linkedlist;Timer mallocTimer(int id, Func *actor,unsigned int sec, unsigned int interval){  Timer ret = (Timer)malloc(sizeof(struct alarm));  assert(ret);  ret->__inner.id = id;  ret->__inner.sigactor = actor;  ret->__inner.intersec = interval;  ret->sec = sec;  return ret;}/* find Timer in linked list which id is id. * return: return NULL if not found, -1 if it's header link,  * otherwise prev which is the previous Timer member to this Timer */Timer findTimerPrev(Header h, int id){  assert(h);  if(h->head == NULL)    return NULL;  Timer t = h->head;  Timer prev = NULL;  while(t)  {    if(t->__inner.id == id){      if(prev == NULL)        return (Timer)-1;      else        return prev;    }    prev = t;    t = t->next;  }  return NULL;}/* delete Timer in linked list. * return: nothing, we ensure this t is deleted in the linked list. */void delTimer(Header h, Timer t){  assert(h);  assert(t);  Timer prevtodel = findTimerPrev(h, t->__inner.id);  unsigned int base = 0;  if(prevtodel)  {    if(prevtodel == (Timer)-1){      unsigned int res = (h->head)->sec;      if(res != 0)      {        base = res;      }      else      {        kill(getpid(),SIGALRM);        return;      }      h->head = (h->head)->next;      Timer tmp = (h->head);      while(tmp){        tmp->sec += base;        tmp = tmp->next;      }      return;    }    else    {            base = (prevtodel->next)->sec;      prevtodel->next = (prevtodel->next)->next;      Timer tmp = (prevtodel->next);            while(tmp){        tmp->sec += base;        tmp = tmp->next;      }      return;    }  }  return;}/* append Timer in appropriate place in linked list. * the appropriate place means all timers in linked list are arranged  * according their next alarm seconds. * The algorithm we use here is that the real left alarm seconds for this Timer  * is the sum of all the sec member in Timer in linked list prev to this Timer * plus its sec member. For example, we add 3 Timers to the linked list, * whose sec are 4, 3, 2 respectively. Then the linked list looks like: * 2 (real sec = 2) --> 1 (real sec = 2+1 = 3) --> 1 (real sec = 2+1+1 = 4) * The advantage is obviously, we dont need to remember how many seconds passed. * We always fetch the header to respond the alarm signal and set next alarm sec  * as the next timer in the linked list. (The real situation is a little bit more  * complex, for example if upcoming timers' sec equals 0, we need to call their * handler right away all together in a certain sequence. If its intersec is not  * zero, we need to append it to the linked list again as quick as possible) * note: delTimer also address this problem. If we delete any Timer, we need to  * recalculate the secs after this timer in the linked list.(simply to add sec to  * the next timer and delete this timer node) * return: only 0 if success, otherwise the hole process failed. */int appendTimer(Header h, Timer t){  assert(h);  assert(t);  delTimer(h, t);  if(h->head == NULL)  {    h->head = t;    return 0;  }  Timer tmp = h->head;  Timer prev = NULL;  unsigned int prevbase = 0;  unsigned int base = 0;  while(tmp)  {    prevbase = base;    base += tmp->sec;    if(t->sec < base){      break;    }    else{      prev = tmp;      tmp = tmp->next;    }        }  if(prev == NULL)  {    (h->head)->sec -= t->sec;    t->next = h->head;    h->head = t;    return 0;  }  if(tmp == NULL)    t->sec -=base;  else    t->sec -=prevbase;  prev->next = t;  t->next = tmp;  if(tmp)    tmp->sec -= t->sec;  return 0;}/* pop header timer in linked list. * return: its hander */Func* popTimer(Header h){  assert(h);  if(h->head == NULL)    return (Func *)-1;  Func *ret = (h->head)->__inner.sigactor;  Timer todel = h->head;  h->head = (h->head)->next;  // if its intersec greater than 0, we append it right away to the linked list  if(todel->__inner.intersec > 0)  {    todel->sec = todel->__inner.intersec;    appendTimer(h, todel);  }  return ret;}void printList(Header h){  assert(h);  if(h->head == NULL)    return;  Timer tmp = h->head;  while(tmp)  {    printf("timer[%d] = %u saved %u/n", tmp->__inner.id, tmp->sec, tmp->__inner.intersec);    tmp = tmp->next;  }}/* it's the real signal handler responding to every SIGALRM. */void sig_alarm_internal(int signo){   void funcWrapper(int signo, Func *func);  if(hdr_ptr->head == NULL)    return;  Func *recv;  if((recv = popTimer(hdr_ptr)) == (Func *)-1){    funcWrapper(SIGALRM, recv);  }   else  {    // signal ourself if next timer's sec = 0    if(hdr_ptr->head){      ((hdr_ptr->head)->sec > 0?alarm((hdr_ptr->head)->sec):kill(getpid(), SIGALRM));    }    funcWrapper(SIGALRM, recv);  }}/* Alarm function simulates native alarm function. * what if SIGALRM arrives when process is running in Alarm? * we just block the signal since there is no slow function in Alarm, * sig_alarm_internal will for sure address the signal very soon. */unsigned int Alarm(Header h, Timer mtimer){  sigset_t mask;  sigset_t old;  sigemptyset(&mask);  sigaddset(&mask, SIGALRM);  sigprocmask(SIG_BLOCK, &mask, &old);    unsigned int res = 0;  Timer t;  if((t = findTimerPrev(h, mtimer->__inner.id)) == NULL)    goto LL;  t = h->head;  while(t)  {    res += t->sec; // it's not precise, we should use alarm(0) for the first sec.            // However, its simple enough to implement.     if(t->__inner.id == mtimer->__inner.id)      break;    t = t->next;  }LL:  if(mtimer->sec == 0)  {    delTimer(h, mtimer);    sigprocmask(SIG_SETMASK, &old, NULL);    return res;  }     appendTimer(h, mtimer);  if(mtimer->__inner.id == (h->head)->__inner.id)    ((h->head)->sec > 0?alarm((h->head)->sec):kill(getpid(), SIGALRM));  sigprocmask(SIG_SETMASK, &old, NULL);  return res;}void initTimer(){  struct sigaction act;  act.sa_handler = sig_alarm_internal;  act.sa_flags = SA_RESTART|SA_NODEFER;  sigemptyset(&act.sa_mask);  sigaction(SIGALRM, &act, NULL);}void funcWrapper(int signo, Func *func){  sigset_t mask;  sigset_t old;  sigemptyset(&mask);  sigaddset(&mask, SIGALRM);  sigprocmask(SIG_UNBLOCK, &mask, &old);  func(signo);  sigprocmask(SIG_SETMASK, &old, NULL);}/************************* Thread facility *************************/Cursor live;Cursor dead;Thread_t pmain;void initCursor(Cursor *cur){  cur->total = 0;  cur->current = NULL;}Thread_t *findThread(Cursor *cur, int tid){  sigset_t mask,old;  sigemptyset(&mask);  sigaddset(&mask, SIGALRM);  sigprocmask(SIG_BLOCK, &mask, &old);  int counter = cur->total;  if(counter == 0){    sigprocmask(SIG_SETMASK, &old, NULL);    return NULL;  }      int i;  pNode *tmp = cur->current;  for (int i = 0; i < counter; ++i)  {    if((tmp->data)->tid == tid){      sigprocmask(SIG_SETMASK, &old, NULL);      return tmp->data;    }    tmp = tmp->next;  }  sigprocmask(SIG_SETMASK, &old, NULL);  return NULL;}int appendThread(Cursor *cur, Thread_t *pth){  sigset_t mask,old;  sigemptyset(&mask);  sigaddset(&mask, SIGALRM);  sigprocmask(SIG_BLOCK, &mask, &old);  if(cur->total == 0)  {    //note this never freed for simple implementation    cur->current = (pNode *)malloc(sizeof(pNode));    assert(cur->current);    (cur->current)->data = pth;    (cur->current)->prev = cur->current;    (cur->current)->next = cur->current;    cur->total++;    sigprocmask(SIG_SETMASK, &old, NULL);    return 0;  }  else  {    #define MAXTHREADS 5    if(cur->total > MAXTHREADS)    {      assert((cur->total == MAXTHREADS));      sigprocmask(SIG_SETMASK, &old, NULL);      return -1;    }    //freed at threadJoin for simple implementation    pNode *tmp = malloc(sizeof(pNode));    assert(tmp);    tmp->data = pth;    tmp->prev = cur->current;    tmp->next = (cur->current)->next;    ((cur->current)->next)->prev = tmp;    (cur->current)->next = tmp;    cur->total++;    sigprocmask(SIG_SETMASK, &old, NULL);    return 0;  }}pNode *deleteThread(Cursor *cur, int tid){  sigset_t mask,old;  sigemptyset(&mask);  sigaddset(&mask, SIGALRM);  sigprocmask(SIG_BLOCK, &mask, &old);  int counter = cur->total;  int i;  pNode *tmp = cur->current;  for (int i = 0; i < counter; ++i)  {    if((tmp->data)->tid == tid){      (tmp->prev)->next = tmp->next;      (tmp->next)->prev = tmp->prev;      if(tmp == cur->current)      {        cur->current = cur->current->next;      }       //free(tmp);      cur->total--;      assert(cur->total);      sigprocmask(SIG_SETMASK, &old, NULL);      return tmp;    }    tmp = tmp->next;  }  sigprocmask(SIG_SETMASK, &old, NULL);  return NULL;}void printThread(Thread_t *pth){  printf("pth tid: %d/n", pth->tid);  printf("pth stack top: %x/n", pth->stacktop);  printf("pth stack size: %u/n", pth->stacksize);  printf("pth state: %d/n", pth->state);  printf("pth errno: %d/n", pth->errno);  printf("pth retval: %p/n", pth->retval);  printf("pth sigmask: %u/n", pth->sigmask);  printf("pth priority: %d/n", pth->priority);  printf("pth tick: %d/n", pth->tick);  printf("EFLAGS: %x/t", pth->regs._eflags);  printf("EIP: %x/t", pth->regs._eip);  printf("EAX: %x/t", pth->regs._eax);  printf("ECX: %x/n", pth->regs._ecx);  printf("EDX: %x/t", pth->regs._edx);  printf("EBX: %x/t", pth->regs._ebx);  printf("ESP: %x/t", pth->regs._esp);  printf("EBP: %x/n", pth->regs._ebp);  printf("ESI: %x/t", pth->regs._esi);  printf("EDI: %x/n", pth->regs._edi);}void printLoop(Cursor *cur){  int count = 0;  pNode *tmp = cur->current;  assert(tmp);  do{    printThread(tmp->data);    tmp = tmp->next;    count ++;   }while(tmp != cur->current);  printf("real total: %d/n", count);  printf("total record:%d/n", cur->total);  assert(count == cur->total);}int fetchTID(){  static int tid;  return ++tid;}void real_entry(Thread_t *pth, void *(*start_rtn)(void *), void* args){  //printf("in real entry: %p/n", start_rtn);    pth->retval = (*start_rtn)(args);  //deleteThread(&live, pth->tid);  /* some clean job here */  //free(pth->stack);  //pth->stack = NULL;  //pth->stacktop = 0;  //pth->stacksize = 0;  #define DETACHED 1  deleteThread(&live, pth->tid);  appendThread(&dead, pth);  if(pth->state == DETACHED)    threadJoin(pth, NULL);  switch_to(-1);}int threadCreat(Thread_t **pth, void *(*start_rtn)(void *), void *arg){  sigset_t mask,old;  sigemptyset(&mask);  sigaddset(&mask, SIGALRM);  sigprocmask(SIG_BLOCK, &mask, &old);  //freed at threadJoin for simple implementation  *pth = malloc(sizeof(Thread_t));  #define PTHREAD_STACK_MIN 4096  //freed at threadJoin for simple implementation  (*pth)->stack = malloc(PTHREAD_STACK_MIN);  assert((*pth)->stack);  (*pth)->stacktop = (((int)(*pth)->stack + PTHREAD_STACK_MIN)&(0xfffff000));  (*pth)->stacksize = PTHREAD_STACK_MIN - (((int)(*pth)->stack + PTHREAD_STACK_MIN) - (*pth)->stacktop);  (*pth)->state = 0; // 0 JOINABLE 1 DETACHED  (*pth)->priority = 1; //one seconds  (*pth)->tick = (*pth)->priority;  (*pth)->tid = fetchTID();  sigprocmask(0,NULL,&((*pth)->sigmask));  /* set params */  void *dest = (*pth)->stacktop - 12;  memcpy(dest, pth, 4);  dest += 4;  memcpy(dest, &start_rtn, 4);  dest += 4;  memcpy(dest, &arg, 4);  (*pth)->regs._eip = &real_entry;  (*pth)->regs._esp = (*pth)->stacktop - 16;  (*pth)->regs._edi = 0;  (*pth)->regs._esi = 0;  (*pth)->regs._ebp = 0;  (*pth)->regs._eax = 0;  (*pth)->regs._ebx = 0;  (*pth)->regs._ecx = 0;  (*pth)->regs._edx = 0;  (*pth)->regs._eflags = 0;  appendThread(&live, (*pth));  sigprocmask(SIG_SETMASK, &old, NULL);  return 0;}int threadJoin(Thread_t *pth, void **rval_ptr){  sigset_t mask,old;  sigemptyset(&mask);  sigaddset(&mask, SIGALRM);  sigprocmask(SIG_BLOCK, &mask, &old);  Thread_t *find1, *find2;  find1 = findThread(&live, pth->tid);  find2 = findThread(&dead, pth->tid);    if((find1 == NULL)&&(find2 == NULL)){    sigprocmask(SIG_SETMASK, &old, NULL);    return -1;  }  if(find2){    if(rval_ptr != NULL)      *rval_ptr = find2->retval;    sigprocmask(SIG_SETMASK, &old, NULL);    return 0;  }  sigprocmask(SIG_SETMASK, &old, NULL);  while(1)  {    if((find2 = findThread(&dead, pth->tid))!= NULL){      if(rval_ptr!= NULL)        *rval_ptr = find2->retval;      pNode *tmp = deleteThread(&dead, pth->tid);      free(tmp);      free((Stack_t)find2->stack);      free(find2);      return 0;    }  }  return -1;}void init(){  initTimer();  initCursor(&live);  initCursor(&dead);  appendThread(&live, &pmain);  Alarm(hdr_ptr,mallocTimer(1, switch_to, 1, 1));}void switch_to(int signo){  sigset_t mask,old;  sigemptyset(&mask);  sigaddset(&mask, SIGALRM);  sigprocmask(SIG_BLOCK, &mask, &old);  Regs regs;  //printf("");  if(signo == -1)  {    regs = live.current->data->regs;    sigprocmask(SIG_SETMASK, &old, NULL);    JMP(regs);    assert(0);  }    int _edi;  int _esi;  int _ebp;  int _esp;  int _ebx;  int _edx;  int _ecx;  int _eax;  int _eip = &&_REENTERPOINT;  int _eflags;  live.current->data->__reenter = 0;   /* save current context */  SAVE();  /* save context in current thread */  live.current->data->regs._eflags = _eflags;  live.current->data->regs._eip = _eip;  live.current->data->regs._eax = _eax;  live.current->data->regs._ecx = _ecx;  live.current->data->regs._edx = _edx;  live.current->data->regs._ebx = _ebx;  live.current->data->regs._esp = _esp;  live.current->data->regs._ebp = _ebp;  live.current->data->regs._esi = _esi;  live.current->data->regs._edi = _edi;  if(!live.current->data->__reenter)  {    goto _END;  }_REENTERPOINT:  regs = live.current->data->regs;  if(live.current->data->__reenter){    live.current->data->__reenter = 0;    sigprocmask(SIG_SETMASK, &old, NULL);    return;  } _END:  live.current->data->__reenter = 1;  regs = live.current->next->data->regs;  live.current = live.current->next;  sigprocmask(SIG_SETMASK, &old, NULL);  JMP(regs);  assert(0);}/************************* Test *************************//** * Note: The implementation is really bugy, right now only support compute in thread. * Even standard I/O in the thread will cause I/O bus error or segmentation error because * all pthread-reentrant function is not guaranteed in our thread model. * (pthread_mutex_t cannot block thread in our model cause we modify eip directly) */void *sum1tod(void *d){  int i, k, j=0;  for (i = 0; i <= (int)d; ++i)  {      /* code */      j+=i;     }  return ((void *)j);}int main(int argc, char const *argv[]){  int res = 0;  int i;  init();  Thread_t *tid1, *tid2;  int *res1, *res2;  threadCreat(&tid1, sum1tod, 100);  threadCreat(&tid2, sum1tod, 100);  for (i = 0; i <= 100; ++i){    res+=i;  }  threadJoin(tid1, &res1);  threadJoin(tid2, &res2);  printf("parallel compute: %d = 5050 * 3/n", (int)res1+(int)res2+(int)res);  return 0;}

以上這篇C語言實現用戶態線程庫案例就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
91欧美精品成人综合在线观看| 亚洲午夜久久久影院| 亚洲人精选亚洲人成在线| 国产午夜精品视频免费不卡69堂| 国产亚洲成av人片在线观看桃| 夜夜嗨av色一区二区不卡| 国产成人精品电影| 青青久久av北条麻妃黑人| 欧美成人精品不卡视频在线观看| 欧美日韩国产区| 国产91露脸中文字幕在线| 成人精品一区二区三区电影黑人| 精品动漫一区二区| 日韩欧美中文免费| 91麻豆国产语对白在线观看| 国产日韩欧美在线| 欧美高清videos高潮hd| 国产999精品久久久| 欧美资源在线观看| 日韩视频永久免费观看| 精品久久久一区| 国产视频福利一区| 国产亚洲精品一区二区| 欧美激情在线视频二区| 色偷偷噜噜噜亚洲男人| 久久视频国产精品免费视频在线| 亚洲精品一区久久久久久| 色婷婷av一区二区三区久久| 欧美成人精品在线观看| 国产69精品久久久| 欧美另类精品xxxx孕妇| 国产成人一区二区在线| 日韩久久精品成人| 欧美一区二粉嫩精品国产一线天| 国产精品欧美激情在线播放| 国产精品极品美女粉嫩高清在线| 久久在线精品视频| 日韩中文有码在线视频| 国产精品色悠悠| 欧美乱妇40p| 国产做受高潮69| 久久五月天综合| 国产91色在线|| 91久久精品一区| 欧美电影免费看| 国产精品小说在线| 97精品国产91久久久久久| 亚洲精品ady| 亚洲美女黄色片| 亚洲人成电影在线播放| 亚洲专区在线视频| 精品国产美女在线| 亚洲精品国产精品国自产观看浪潮| 57pao国产精品一区| 亚洲精品资源美女情侣酒店| 51久久精品夜色国产麻豆| 色婷婷**av毛片一区| 欧洲美女7788成人免费视频| 色偷偷88888欧美精品久久久| 欧美成人精品激情在线观看| 欧美高清videos高潮hd| 欧美日韩国产色| www日韩中文字幕在线看| 亚洲欧美色图片| 国产专区欧美专区| 国产成人精品免高潮费视频| 亚洲精品网站在线播放gif| 欧美成人精品不卡视频在线观看| 欧美精品免费播放| 日本韩国在线不卡| 97精品在线视频| 久久久久亚洲精品国产| 亚洲xxxx3d| 日韩精品欧美国产精品忘忧草| 91久久国产综合久久91精品网站| 亚洲国产欧美一区二区三区同亚洲| 欧美裸身视频免费观看| 国产不卡在线观看| 国产一区在线播放| 久久成人精品电影| 成人免费自拍视频| 精品久久久香蕉免费精品视频| 欧美激情伊人电影| 热久久美女精品天天吊色| 上原亚衣av一区二区三区| 亚洲国产精品女人久久久| 国产99久久久欧美黑人| 亚洲精品v天堂中文字幕| 国产精品毛片a∨一区二区三区|国| 欧美激情久久久| 在线观看日韩www视频免费| 91国内免费在线视频| 免费不卡欧美自拍视频| 亚洲精品电影在线| 欧美日韩福利电影| 国产成人激情小视频| 欧美亚洲国产日本| 国产精品白丝jk喷水视频一区| 欧美日韩精品二区| 亚洲国产精品一区二区三区| 欧美电影在线播放| 91精品国产九九九久久久亚洲| 国产精品 欧美在线| 色婷婷综合成人av| 亚洲国产精品久久91精品| 亚洲欧洲中文天堂| 国产午夜精品免费一区二区三区| 在线观看不卡av| 91av在线免费观看视频| 成人午夜黄色影院| 一本色道久久88亚洲综合88| 国产日韩欧美日韩| 日韩av男人的天堂| 久久视频这里只有精品| 欧美日韩精品在线| 日本韩国欧美精品大片卡二| 欧美噜噜久久久xxx| 一区二区亚洲欧洲国产日韩| 久久99热精品这里久久精品| 国产色综合天天综合网| 亚洲精品成人av| 国产精品白嫩初高中害羞小美女| 国产手机视频精品| 色妞久久福利网| 国产欧美日韩丝袜精品一区| 久久精品视频一| 欧美成人激情视频免费观看| 欧美亚洲另类制服自拍| 精品精品国产国产自在线| 亚洲女人天堂视频| 在线播放亚洲激情| 欧美在线中文字幕| 国产精品专区一| 国产剧情日韩欧美| 久久夜精品va视频免费观看| 国产精品欧美日韩久久| 欧美情侣性视频| 国产精品日韩电影| 中文字幕日韩av综合精品| 国产91色在线免费| 精品久久久一区二区| 欧美激情aaaa| 日韩资源在线观看| 91九色蝌蚪国产| 国产精品丝袜久久久久久高清| 97欧美精品一区二区三区| 国产精品扒开腿爽爽爽视频| 亚洲欧美日韩国产中文专区| 日韩一区二区精品视频| 一区二区三区视频免费在线观看| 亚洲丁香婷深爱综合| 日韩小视频网址| 中文综合在线观看| 欧美在线视频网| 亚洲激情自拍图| 98精品国产自产在线观看| 欧美成人免费一级人片100| 伊人久久大香线蕉av一区二区| 国产美女被下药99| 亚洲黄色免费三级| 国模精品系列视频| 亚洲图片欧美午夜| 国产精品视频一区二区高潮| 97国产成人精品视频|