日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux多线程_Java+Linux,深入内核源码讲解多线程之进程

發布時間:2023/12/1 linux 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux多线程_Java+Linux,深入内核源码讲解多线程之进程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

之前寫了兩篇文章,都是針對Linux這個系統的,為什么?我為什么這么喜歡寫這個系統的知識,可能就是為了今天的內容多線程系列,現在多線程不是一個面試重點 啊,那如果你能深入系統內核回答這個知識點,面試官會怎么想?你會不會占據面試的主動權(我不會說今天被一個面試者驚艷到了的)今天,我就開始一個系列的內容,多線程--高并發,深入的給大家講解,我就不信講不明白這么個小東西,有問題的地方希望大家能夠指出,謝謝,大家一起成長

今天我們將第一個知識點:進程

Linux 內核如何描述一個進程?

1. Linux 的進程

進程的術語是 process,是 Linux 最基礎的抽象,另一個基礎抽象是文件。

最簡單的理解,進程就是執行中 (executing, 不等于running) 的程序。

更準確一點的理解,進程包括執行中的程序以及相關的資源 (包括cpu狀態、打開的文件、掛起的信號、tty、內存地址空間等)。

一種簡潔的說法:進程 = n*執行流 + 資源,n>=1。

Linux 進程的特點:

  • 通過系統調用 fork() 創建進程,fork() 會復制現有進程來創建一個全新的進程。
  • 內核里,并不嚴格區分進程和線程。
  • 從內核的角度看,調度單位是線程 (即執行流)。可以把線程看做是進程里的一條執行流,1個進程里可以有1個或者多個線程。
  • 內核里,常把進程稱為 task 或者 thread,這樣描述更準確,因為許多進程就只有1條執行流。
  • 內核通過輕量級進程 (lightweight process) 來支持多線程。1個輕量級進程就對應1個線程,輕量級進程之間可以共享打開的文件、地址空間等資源。
  • 2. Linux 的進程描述符

    2.1 task_struct

    內核里,通過 task_struct 結構體來描述一個進程,稱為進程描述符 (process descriptor),它保存著支撐一個進程正常運行的所有信息。

    每一個進程,即便是輕量級進程(即線程),都有1個 task_struct。

    ?(include\linux)??struct?task_struct?{??struct?thread_info?thread_info;??volatile?long?state;??void?*stack;??[...]??struct?mm_struct?*mm;??[...]??pid_t?pid;??[...]??struct?task_struct?*parent;??[...]??char?comm[TASK_COMM_LEN];??[...]??struct?files_struct?*files;??[...]??struct?signal_struct?*signal;??}?

    這是一個龐大的結構體,不僅有許多進程相關的基礎字段,還有許多指向其他數據結構的指針。

    它包含的字段能完整地描述一個正在執行的程序,包括 cpu 狀態、打開的文件、地址空間、掛起的信號、進程狀態等。

    作為初學者,先簡單地了解部分字段就好::

    • struct thread_info thread_info: 進程底層信息,平臺相關,下面會詳細描述。
    • long state: 進程當前的狀態,下面是幾個比較重要的進程狀態以及它們之間的轉換流程。
    • void *stack: 指向進程內核棧,下面會解釋。
    • struct mm_struct *mm: 與進程地址空間相關的信息都保存在一個叫內存描述符 (memory descriptor) 的結構體 (mm_struct) 中。

    pid_t pid: 進程標識符,本質就是一個數字,是用戶空間引用進程的唯一標識。

    struct?task_struct?*parent:?父進程的?task_struct。??char?comm[TASK_COMM_LEN]:?進程的名稱。??struct?files_struct *files:?打開的文件表。??struct?signal_struct?*signal:?信號處理相關。?

    其他字段,等到有需要的時候再回過頭來學習。

    當發生系統調用或者進程切換時,內核如何找到 task_struct ?

    對于 ARM 架構,答案是:通過內核棧 (kernel mode stack)。

    為什么要有內核棧?

    因為內核是可重入的,在內核中會有多條與不同進程相關聯的執行路徑。因此不同的進程處于內核態時,都需要有自己私有的進程內核棧 (process kernel stack)。

    當進程從用戶態切換到內核態時,所使用的棧會從用戶棧切換到內核棧。

    至于是如何切換的,關鍵詞是系統調用,這不是本文關注的重點,先放一邊,學習內核要懂得恰當的時候忽略細節。

    當發生進程切換時,也會切換到目標進程的內核棧。

    同上,關鍵詞是硬件上下文切換 (hardware context switch),忽略具體實現。

    無論何時,只要進程處于內核態,就會有內核棧可以使用,否則系統就離崩潰不遠了。

    ARM 架構的內核棧和 task_struct 的關系如下:

    內核棧的長度是 THREAD_SIZE,對于 ARM 架構,一般是 2 個頁框的大小,即 8KB。

    內核將一個較小的數據結構 thread_info 放在內核棧的底部,它負責將內核棧和 task_struct 串聯起來。thread_info 是平臺相關的,在 ARM 架構中的定義如下:

    //??(arch\arm\include\asm)??struct?thread_info?{??unsigned?long?flags;?/*?low?level?flags?*/??int?preempt_count;?/*?0?=>?preemptable,?<0?=>?bug?*/??mm_segment_t?addr_limit;?/*?address?limit?*/??struct?task_struct?*task;?/*?main?task?structure?*/??[...]??struct?cpu_context_save?cpu_context;?/*?cpu?context?*/??[...]??};?

    thread_info 保存了一個進程能被調度執行的最底層信息(low level task data),例如struct cpu_context_save cpu_context 會在進程切換時用來保存/恢復寄存器上下文。

    內核通過內核棧的棧指針可以快速地拿到 thread_info:

    //??(include\linux)??static?inline?struct?thread_info?*current_thread_info(void)??{??//?current_stack_pointer?是當前進程內核棧的棧指針??return?(struct?thread_info?*)??(current_stack_pointer?&?~(THREAD_SIZE?-?1));??}??然后通過?thread_info?找到?task_struct:??//?current.h?(include\asm-generic)??#define?current?(current_thread_info()->task)?

    內核里通過 current 宏可以獲得當前進程的 task_struct。

    2.3 task_struct 的分配和初始化

    當上層應用使用 fork() 創建進程時,內核會新建一個 task_struct。

    進程的創建是個復雜的工作,可以延伸出無數的細節。這里我們只是簡單地了解一下 task_struct 的分配和部分初始化的流程。

    fork() 在內核里的核心流程:

    dup_task_struct() 做了什么?

    至于設置內核棧里做了什么,涉及到了進程的創建與切換,不在本文的關注范圍內,以后再研究了。

    3. 實驗:打印 task_struct / thread_info / kernel mode stack

    實驗目的:

    • 梳理 task_struct / thread_info / kernel mode stack 的關系。

    實驗代碼:

    實驗代碼:?#include?<linux/?#include?<linux/?#include?<linux/>??static?void?print_task_info(struct?task_struct?*task)?{?????printk(KERN_NOTICE?"%10s?%5d?task_struct?(%p)?/?stack(%p~%p)?/?thread_info->task?(%p)",?????????task->comm,?????????task->pid,?????????task,?????????task->stack,?????????((unsigned?long?*)task->stack)?+?THREAD_SIZE,?????????task_thread_info(task)->task);?}??static?int?__init?task_init(void)?{?????struct?task_struct?*task?=?current;??????printk(KERN_INFO?"task?module?init\n");??????print_task_info(task);?????do?{?????????task?=?task->parent;?????????print_task_info(task);?????}?while?(task->pid?!=?0);??????return?0;?}?module_init(task_init);??static?void?__exit?task_exit(void)?{?????printk(KERN_INFO?"task?module?exit\n?");?}?module_exit(task_exit);?

    運行效果:

    task?module?init?????insmod??3123?task_struct?(edb42580)?/?stack(ed46c000~ed474000)?/?thread_info->task?(edb42580)???????bash??2393?task_struct?(eda13e80)?/?stack(c9dda000~c9de2000)?/?thread_info->task?(eda13e80)???????sshd??2255?task_struct?(ee5c9f40)?/?stack(c9d2e000~c9d36000)?/?thread_info->task?(ee5c9f40)???????sshd???543?task_struct?(ef15f080)?/?stack(ee554000~ee55c000)?/?thread_info->task?(ef15f080)????systemd?????1?task_struct?(ef058000)?/?stack(ef04c000~ef054000)?/?thread_info->task?(ef058000)?

    在程序里,我們通過 task_struct 找到 stack,然后通過 stack 找到 thread_info,最后又通過 thread_info->task 找到 task_struct。

    到這里,不知道你對進程的概念是不是有了一個清晰的理解

    但是上面是通過Linux進行了線程的展示,在日常的工作中,代碼的實現和編寫我們還是以Java為主,那我們來看一下Java進程

    進程的創建

    Java提供了兩種方法用來啟動進程或其它程序:

    • 使用Runtime的exec()方法
    • 使用ProcessBuilder的start()方法

    ProcessBuilder

    ProcessBuilder類是J2SE 1.5在中新添加的一個新類,此類用于創建操作系統進程,它提供一種啟動和管理進程(也就是應用程序)的方法。在J2SE 1.5之前,都是由Process類處來實現進程的控制管理。

    每個 ProcessBuilder 實例管理一個進程屬性集。start() 方法利用這些屬性創建一個新的 Process 實例。start() 方法可以從同一實例重復調用,以利用相同的或相關的屬性創建新的子進程。

    每個進程生成器管理這些進程屬性:

    • 命令 是一個字符串列表,它表示要調用的外部程序文件及其參數(如果有)。在此,表示有效的操作系統命令的字符串列表是依賴于系統的。例如,每一個總體變量,通常都要成為此列表中的元素,但有一些操作系統,希望程序能自己標記命令行字符串——在這種系統中,Java 實現可能需要命令確切地包含這兩個元素。
    • 環境 是從變量 到值 的依賴于系統的映射。初始值是當前進程環境的一個副本(請參閱 ())。
    • 工作目錄。默認值是當前進程的當前工作目錄,通常根據系統屬性 來命名。
    • redirectErrorStream 屬性。最初,此屬性為 false,意思是子進程的標準輸出和錯誤輸出被發送給兩個獨立的流,這些流可以通過 () 和 () 方法來訪問。如果將值設置為 true,標準錯誤將與標準輸出合并。這使得關聯錯誤消息和相應的輸出變得更容易。在此情況下,合并的數據可從 () 返回的流讀取,而從 () 返回的流讀取將直接到達文件尾。

    修改進程構建器的屬性將影響后續由該對象的 start() 方法啟動的進程,但從不會影響以前啟動的進程或 Java 自身的進程。大多數錯誤檢查由 start() 方法執行。可以修改對象的狀態,但這樣 start() 將會失敗。例如,將命令屬性設置為一個空列表將不會拋出異常,除非包含了 start()。

    注意,此類不是同步的。如果多個線程同時訪問一個 ProcessBuilder,而其中至少一個線程從結構上修改了其中一個屬性,它必須 保持外部同步。

    構造方法摘要

    ProcessBuilder(List?command)??利用指定的操作系統程序和參數構造一個進程生成器。??ProcessBuilder(String...?command)??利用指定的操作系統程序和參數構造一個進程生成器。?

    方法摘要

    List?command()??返回此進程生成器的操作系統程序和參數。??ProcessBuilder?command(List?command)??設置此進程生成器的操作系統程序和參數。??ProcessBuilder?command(String...?command)??設置此進程生成器的操作系統程序和參數。??File?directory()??返回此進程生成器的工作目錄。??ProcessBuilder?directory(File?directory)??設置此進程生成器的工作目錄。??Map?environment()??返回此進程生成器環境的字符串映射視圖。??boolean?redirectErrorStream()??通知進程生成器是否合并標準錯誤和標準輸出。??ProcessBuilder?redirectErrorStream(boolean?redirectErrorStream)??設置此進程生成器的?redirectErrorStream?屬性。??Process?start()??使用此進程生成器的屬性啟動一個新進程。?

    1.2 Runtime

    每個 Java 應用程序都有一個 Runtime 類實例,使應用程序能夠與其運行的環境相連接。可以通過 getRuntime 方法獲取當前運行時。

    應用程序不能創建自己的 Runtime 類實例。但可以通過 getRuntime 方法獲取當前Runtime運行時對象的引用。一旦得到了一個當前的Runtime對象的引用,就可以調用Runtime對象的方法去控制Java虛擬機的狀態和行為。

    Java代碼 收藏代碼

    void?addShutdownHook(Thread?hook)??注冊新的虛擬機來關閉掛鉤。??int?availableProcessors()??向?Java?虛擬機返回可用處理器的數目。??Process?exec(String?command)??在單獨的進程中執行指定的字符串命令。??Process?exec(String[]?cmdarray)??在單獨的進程中執行指定命令和變量。??Process?exec(String[]?cmdarray,?String[]?envp)??在指定環境的獨立進程中執行指定命令和變量。??Process?exec(String[]?cmdarray,?String[]?envp,?File?dir)??在指定環境和工作目錄的獨立進程中執行指定的命令和變量。??Process?exec(String?command,?String[]?envp)??在指定環境的單獨進程中執行指定的字符串命令。??Process?exec(String?command,?String[]?envp,?File?dir)??在有指定環境和工作目錄的獨立進程中執行指定的字符串命令。??void?exit(int?status)??通過啟動虛擬機的關閉序列,終止當前正在運行的?Java?虛擬機。??long?freeMemory()??返回?Java?虛擬機中的空閑內存量。??void?gc()??運行垃圾回收器。??InputStream?getLocalizedInputStream(InputStream?in)??已過時。?從?JDK??開始,將本地編碼字節流轉換為?Unicode?字符流的首選方法是使用?InputStreamReader?和?BufferedReader?類。??OutputStream?getLocalizedOutputStream(OutputStream?out)??已過時。?從?JDK??開始,將?Unicode?字符流轉換為本地編碼字節流的首選方法是使用?OutputStreamWriter、BufferedWriter?和?PrintWriter?類。??static?Runtime?getRuntime()??返回與當前?Java?應用程序相關的運行時對象。??void?halt(int?status)??強行終止目前正在運行的?Java?虛擬機。??void?load(String?filename)??加載作為動態庫的指定文件名。??void?loadLibrary(String?libname)??加載具有指定庫名的動態庫。??long?maxMemory()??返回?Java?虛擬機試圖使用的最大內存量。??boolean?removeShutdownHook(Thread?hook)??取消注冊某個先前已注冊的虛擬機關閉掛鉤。??void?runFinalization()??運行掛起?finalization?的所有對象的終止方法。??static?void?runFinalizersOnExit(boolean?value)??已過時。?此方法本身具有不安全性。它可能對正在使用的對象調用終結方法,而其他線程正在操作這些對象,從而導致不正確的行為或死鎖。??long?totalMemory()??返回?Java?虛擬機中的內存總量。??void?traceInstructions(boolean?on)??啟用/禁用指令跟蹤。??void?traceMethodCalls(boolean?on)??啟用/禁用方法調用跟蹤。?

    1.3 Process

    不管通過哪種方法啟動進程后,都會返回一個Process類的實例代表啟動的進程,該實例可用來控制進程并獲得相關信息。Process 類提供了執行從進程輸入、執行輸出到進程、等待進程完成、檢查進程的退出狀態以及銷毀(殺掉)進程的方法:

    void?destroy()??殺掉子進程。??一般情況下,該方法并不能殺掉已經啟動的進程,不用為好。??int?exitValue()??返回子進程的出口值。??只有啟動的進程執行完成、或者由于異常退出后,exitValue()方法才會有正常的返回值,否則拋出異常。??InputStream?getErrorStream()??獲取子進程的錯誤流。??如果錯誤輸出被重定向,則不能從該流中讀取錯誤輸出。??InputStream?getInputStream()??獲取子進程的輸入流。??可以從該流中讀取進程的標準輸出。??OutputStream?getOutputStream()??獲取子進程的輸出流。??寫入到該流中的數據作為進程的標準輸入。??int?waitFor()??導致當前線程等待,如有必要,一直要等到由該?Process?對象表示的進程已經終止。?

    2.多進程編程實例

    一般我們在java中運行其它類中的方法時,無論是靜態調用,還是動態調用,都是在當前的進程中執行的,也就是說,只有一個java虛擬機實例在運行。而有的時候,我們需要通過java代碼啟動多個java子進程。這樣做雖然占用了一些系統資源,但會使程序更加穩定,因為新啟動的程序是在不同的虛擬機進程中運行的,如果有一個進程發生異常,并不影響其它的子進程。

    在Java中我們可以使用兩種方法來實現這種要求。最簡單的方法就是通過Runtime中的exec方法執行java classname。如果執行成功,這個方法返回一個Process對象,如果執行失敗,將拋出一個IOException錯誤。下面讓我們來看一個簡單的例子。

    //?文件?import?.*;?public?class?Test?{? public?static?void?main(String[]?args)? {?FileOutputStream?fOut?=?new?FileOutputStream("c:\\");?fOut.close();?System.out.println("被調用成功!");? }?}???//??public?class?Test_Exec?{? public?static?void?main(String[]?args)? {?Runtime?run?=?();?Process?p?=?run.exec("java?test1");? }?}?

    通過java Test_Exec運行程序后,發現在C盤多了個文件,但在控制臺中并未出現"被調用成功!"的輸出信息。因此可以斷定,Test已經被執行成功,但因為某種原因,Test的輸出信息未在Test_Exec的控制臺中輸出。這個原因也很簡單,因為使用exec建立的是Test_Exec的子進程,這個子進程并沒有自己的控制臺,因此,它并不會輸出任何信息。

    如果要輸出子進程的輸出信息,可以通過Process中的getInputStream得到子進程的輸出流(在子進程中輸出,在父進程中就是輸入),然后將子進程中的輸出流從父進程的控制臺輸出。具體的實現代碼如下如示:

    //??import?.*;?public?class?Test_Exec_Out?{? public?static?void?main(String[]?args)? {?Runtime?run?=?();?Process?p?=?run.exec("java?test1");?BufferedInputStream?in?=?new?BufferedInputStream(());?BufferedReader?br?=?new?BufferedReader(new?InputStreamReader(in));?String?s;?while?((s?=?())?!=?null)? System.out.println(s);? }?}?

    從上面的代碼可以看出,在中通過按行讀取子進程的輸出信息,然后在Test_Exec_Out中按每行進行輸出。 上面討論的是如何得到子進程的輸出信息。那么,除了輸出信息,還有輸入信息。既然子進程沒有自己的控制臺,那么輸入信息也得由父進程提供。我們可以通過Process的getOutputStream方法來為子進程提供輸入信息(即由父進程向子進程輸入信息,而不是由控制臺輸入信息)。我們可以看看如下的代碼:

    //?文件?import?.*;?public?class?Test?{? public?static?void?main(String[]?args)? {?BufferedReader?br?=?new?BufferedReader(new?InputStreamReader(System.in));?System.out.println("由父進程輸入的信息:"?+?());? }?}???//??import?.*;?public?class?Test_Exec_In?{? public?static?void?main(String[]?args)? {?Runtime?run?=?();?Process?p?=?run.exec("java?test2");?BufferedWriter?bw?=?new?BufferedWriter(new?OutputStreamWriter(()));?("向子進程輸出信息");?();?bw.close();?//?必須得關閉流,否則無法向子進程中輸入信息?//?System.in.read();? }?}?

    從以上代碼可以看出,Test1得到由Test_Exec_In發過來的信息,并將其輸出。當你不加()和()時,信息將無法到達子進程,也就是說子進程進入阻塞狀態,但由于父進程已經退出了,因此,子進程也跟著退出了。如果要證明這一點,可以在最后加上.read(),然后通過任務管理器(在windows下)查看java進程,你會發現如果加上()和(),只有一個java進程存在,如果去掉它們,就有兩個java進程存在。這是因為,如果將信息傳給Test2,在得到信息后,Test2就退出了。在這里有一點需要說明一下,exec的執行是異步的,并不會因為執行的某個程序阻塞而停止執行下面的代碼。因此,可以在運行test2后,仍可以執行下面的代碼。

    exec方法經過了多次的重載。上面使用的只是它的一種重載。它還可以將命令和參數分開,如exec("")可以寫成exec("java", "test2")。exec還可以通過指定的環境變量運行不同配置的java虛擬機。

    除了使用Runtime的exec方法建立子進程外,還可以通過ProcessBuilder建立子進程。ProcessBuilder的使用方法如下:

    //??import?.*;?public?class?Test_Exec_Out?{? public?static?void?main(String[]?args)? {?ProcessBuilder?pb?=?new?ProcessBuilder("java",?"test1");?Process?p?=?();?…?…? }?}?

    在建立子進程上,ProcessBuilder和Runtime類似,不同的ProcessBuilder使用start()方法啟動子進程,而Runtime使用exec方法啟動子進程。得到Process后,它們的操作就完全一樣的。

    ProcessBuilder和Runtime一樣,也可設置可執行文件的環境信息、工作目錄等。下面的例子描述了如何使用ProcessBuilder設置這些信息。

    ProcessBuilder?pb?=?new?ProcessBuilder("Command",?"arg2",?"arg2",?''');?//?設置環境變量?Map<String,?String>?env?=?();?("key1",?"value1");?("key2");?("key2",?("key1")?+?"_test");?("..\abcd");?//?設置工作目錄?Process?p?=?();?//?建立子進程?

    【編輯推薦】

    【責任編輯:

    未麗燕

    TEL:(010)68476606】


    點贊 0

    總結

    以上是生活随笔為你收集整理的linux多线程_Java+Linux,深入内核源码讲解多线程之进程的全部內容,希望文章能夠幫你解決所遇到的問題。

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

    主站蜘蛛池模板: 成人毛片在线免费观看 | 亚洲一区动漫 | 香蕉狠狠爱视频 | 奇米四色在线观看 | 黄色工厂在线观看 | 欧美3p视频| 日韩欧美精品在线视频 | 亚洲免费一区视频 | 你懂的国产在线 | 青娱乐av| 日日噜噜夜夜狠狠久久丁香五月 | 国产毛片一区二区三区va在线 | 欧美日韩一区二区在线播放 | 成人av无码一区二区三区 | 日本三级欧美三级 | 午夜精品久久久久久久久久蜜桃 | 亚洲国产精品成人午夜在线观看 | 久久久久久影院 | 亚洲精品一区二区三区新线路 | 成人影视网址 | 亚洲欧美日韩综合 | 日本综合视频 | 国产在线成人 | 午夜欧美成人 | 91丝袜| 久久久香蕉网 | www.久久久久久久久久 | 麻豆md0049免费 | 午夜久久影院 | 欧美a视频在线观看 | 成年人免费小视频 | 日韩毛片av| 99国产精品一区二区三区 | 国产情侣在线播放 | 国产在线拍揄自揄拍无码 | 992tv成人免费视频 | 一个人在线观看www软件 | 老妇free性videosxx | www.国产在线视频 | 女优在线观看 | 黄网站免费视频 | 一级黄色网址 | 日韩av有码 | 亚洲自拍偷拍第一页 | 成人免费视频观看视频 | 久久久精品 | 亚洲制服无码 | 91中文字日产乱幕4区 | 日日干夜夜撸 | 亚洲精品字幕在线观看 | 中文字幕2区| 538任你躁在线精品免费 | 亚洲欧美网| 免看黄大片aa | 久久久久久久久久久综合 | 国产在线色站 | 国产女人18水真多18精品一级做 | 日本一区二区高清视频 | 性欧美video另类hd尤物 | 超碰人人在线观看 | 中国无码人妻丰满熟妇啪啪软件 | 中文字幕在线视频不卡 | 69久久夜色精品国产69 | 亚洲精品高清在线观看 | 五月婷婷婷婷 | 超碰免费在线播放 | 国产乱码精品一区二区三区中文 | 草草影院国产 | 国产av无码专区亚洲av麻豆 | 精品欧美乱码久久久久久1区2区 | 午夜免费片 | 韩国三级久久 | 久久久久久久女国产乱让韩 | 99精品视频一区二区 | 色妞综合网 | 成人一级视频在线观看 | 神马午夜伦理 | 国产av不卡一区二区 | 久久久免费av | 日本一区二区高清免费 | 1级黄色大片儿 | 九九精品视频在线 | 中文字幕11页中文字幕11页 | 天天干b| 伊人影院在线视频 | 好男人在线观看 | 亚洲精品蜜桃 | 欧美日韩成人在线观看 | 青青草久久伊人 | 奶水旺盛的少妇在线播放 | 中文字幕99| 香蕉视频久久久 | 另类小说婷婷 | 午夜精品久久久久久久久久久 | av剧情在线| 欧美日韩一区二区在线观看视频 | 少妇高潮毛片色欲ava片 | 色综合视频在线 | 久久2018|