久综合色-久综合网-玖草影视-玖草资源在线-亚洲黄色片子-亚洲黄色片在线观看

Hello! 歡迎來到小浪云!


深入理解Linux中進程控制(精講)


avatar
小浪云 2025-01-03 121

一、進程生成

初探fork函數

linux系統中,fork函數扮演著至關重要的角色,它能夠從已存在的進程中衍生出一個全新的進程。這個新進程被稱為子進程,而原始進程則成為父進程。

返回值解析:

當fork函數在子進程中執行時,它會返回0;而在父進程中,它則會返回新生成子進程的PID。如果子進程的創建失敗,fork函數將返回-1。

當進程調用fork函數并將控制權轉移到內核中的fork代碼段時,內核會執行以下操作:

  • 為子進程分配新的內存塊和必要的內核數據結構。
  • 將父進程的部分數據結構內容復制到子進程中。
  • 將子進程添加到系統進程列表中。
  • fork函數返回,調度器開始調度新的進程。

在執行fork函數之后,父子進程會共享相同的代碼段。舉個例子:

深入理解Linux中進程控制(精講)

運行結果如下:

深入理解Linux中進程控制(精講)

這里可以看到,Before只輸出了一次,而After輸出了兩次。其中,Before是由父進程打印的,而調用fork函數之后打印的兩個After,則分別由父進程和子進程兩個進程執行。也就是說,fork之前父進程獨立執行,而fork之后父子兩個執行流分別執行。

注意: fork之后,父進程和子進程誰先執行完全由調度器決定。

fork函數返回值

?

fork函數為什么要給子進程返回0,給父進程返回子進程的PID?

一個父進程可以創建多個子進程,而一個子進程只能有一個父進程。因此,對于子進程來說,父進程是不需要被標識的;而對于父進程來說,子進程是需要被標識的,因為父進程創建子進程的目的是讓其執行任務的,父進程只有知道了子進程的PID才能很好的對該子進程指派任務。

?

為什么fork函數有兩個返回值?

父進程調用fork函數后,為了創建子進程,fork函數內部將會進行一系列操作,包括創建子進程的進程控制塊、創建子進程的進程地址空間、創建子進程對應的頁表等等。子進程創建完畢后,操作系統還需要將子進程的進程控制塊添加到系統進程列表當中,此時子進程便創建完畢了。

深入理解Linux中進程控制(精講)

也就是說,在fork函數內部執行return語句之前,子進程就已經創建完畢了,那么之后的return語句不僅父進程需要執行,子進程也同樣需要執行,這就是fork函數有兩個返回值的原因。

寫時拷貝

當子進程剛剛被創建時,子進程和父進程的數據和代碼是共享的,即父子進程的代碼和數據通過頁表映射到物理內存的同一塊空間。只有當父進程或子進程需要修改數據時,才將父進程的數據在內存當中拷貝一份,然后再進行修改。

深入理解Linux中進程控制(精講)

這種在需要進行數據修改時再進行拷貝的技術,稱為寫時拷貝技術。

1、為什么數據要進行寫時拷貝?

進程具有獨立性。多進程運行,需要獨享各種資源,多進程運行期間互不干擾,不能讓子進程的修改影響到父進程。

2、為什么不在創建子進程的時候就進行數據的拷貝?

子進程不一定會使用父進程的所有數據,并且在子進程不對數據進行寫入的情況下,沒有必要對數據進行拷貝,我們應該按需分配,在需要修改數據的時候再分配(延時分配),這樣可以高效的使用內存空間。

3、代碼會不會進行寫時拷貝?

90%的情況下是不會的,但這并不代表代碼不能進行寫時拷貝,例如在進行進程替換的時候,則需要進行代碼的寫時拷貝。

fork常規用法

  1. 一個進程希望復制自己,使子進程同時執行不同的代碼段。例如父進程等待客戶端請求,生成子進程來處理請求。
  2. 一個進程要執行一個不同的程序。例如子進程從fork返回后,調用exec函數。

fork調用失敗的原因

fork函數創建子進程也可能會失敗,有以下兩種情況:

  1. 系統中有太多的進程,內存空間不足,子進程創建失敗。
  2. 實際用戶的進程數超過了限制,子進程創建失敗。

二、進程終止

進程退出場景

進程退出只有三種情況:

  1. 代碼運行完畢,結果正確。
  2. 代碼運行完畢,結果不正確。
  3. 代碼異常終止(進程崩潰)。

進程退出碼

我們都知道main函數是代碼的入口,但實際上main函數只是用戶級別代碼的入口,main函數也是被其他函數調用的,例如在VS2013當中main函數就是被一個名為__tmainCRTStartup的函數所調用,而__tmainCRTStartup函數又是通過加載器被操作系統所調用的,也就是說main函數是間接性被操作系統所調用的。

既然main函數是間接性被操作系統所調用的,那么當main函數調用結束后就應該給操作系統返回相應的退出信息,而這個所謂的退出信息就是以退出碼的形式作為main函數的返回值返回,我們一般以0表示代碼成功執行完畢,以非0表示代碼執行過程中出現錯誤,這就是為什么我們都在main函數的最后返回0的原因。

當我們的代碼運行起來就變成了進程,當進程結束后main函數的返回值實際上就是該進程的進程退出碼,我們可以使用echo $?命令查看最近一次進程退出的退出碼信息。

例如,對于下面這個簡單的代碼:

深入理解Linux中進程控制(精講)

代碼運行結束后,我們可以查看該進程的進程退出碼。

[cl@VM-0-15-centos?procTermination]$?echo?$? 

深入理解Linux中進程控制(精講)

這時便可以確定main函數是順利執行完畢了。

?

為什么以0表示代碼執行成功,以非0表示代碼執行錯誤?

因為代碼執行成功只有一種情況,成功了就是成功了,而代碼執行錯誤卻有多種原因,例如內存空間不足、非法訪問以及溢出等等,我們就可以用這些非0的數字分別表示代碼執行錯誤的原因。

c語言當中的strerror函數可以通過錯誤碼,獲取該錯誤碼在C語言當中對應的錯誤信息:

深入理解Linux中進程控制(精講)

運行代碼后我們就可以看到各個錯誤碼所對應的錯誤信息:

深入理解Linux中進程控制(精講)

實際上Linux中的ls、pwd等命令都是可執行程序,使用這些命令后我們也可以查看其對應的退出碼。

可以看到,這些命令成功執行后,其退出碼也是0。

深入理解Linux中進程控制(精講)

但是命令執行錯誤后,其退出碼就是非0的數字,該數字具體代表某一錯誤信息。

深入理解Linux中進程控制(精講)

注意: 退出碼都有對應的字符串含義,幫助用戶確認執行失敗的原因,而這些退出碼具體代表什么含義是人為規定的,不同環境下相同的退出碼的字符串含義可能不同。

進程正常退出

return退出

在main函數中使用return退出進程是我們常用的方法。

例如,在main函數最后使用return退出進程。

深入理解Linux中進程控制(精講)

運行結果:

深入理解Linux中進程控制(精講)

exit函數

使用exit函數退出進程也是我們常用的方法,exit函數可以在代碼中的任何地方退出進程,并且exit函數在退出進程前會做一系列工作:

  1. 執行用戶通過atexit或on_exit定義的清理函數。
  2. 關閉所有打開的流,所有的緩存數據均被寫入。
  3. 調用_exit函數終止進程。

例如,以下代碼中exit終止進程前會將緩沖區當中的數據輸出。

深入理解Linux中進程控制(精講)

運行結果:

深入理解Linux中進程控制(精講)

_exit函數

使用_exit函數退出進程的方法我們并不經常使用,_exit函數也可以在代碼中的任何地方退出進程,但是_exit函數會直接終止進程,并不會在退出進程前會做任何收尾工作。

例如,以下代碼中使用_exit終止進程,則緩沖區當中的數據將不會被輸出。

深入理解Linux中進程控制(精講)

運行結果:

深入理解Linux中進程控制(精講)

return、exit和_exit之間的區別與聯系

?

return、exit和_exit之間的區別

只有在main函數當中的return才能起到退出進程的作用,子函數當中return不能退出進程,而exit函數和_exit函數在代碼中的任何地方使用都可以起到退出進程的作用。

使用exit函數退出進程前,exit函數會執行用戶定義的清理函數、沖刷緩沖,關閉流等操作,然后再終止進程,而_exit函數會直接終止進程,不會做任何收尾工作。

深入理解Linux中進程控制(精講)

?

return、exit和_exit之間的聯系

執行return num等同于執行exit(num),因為調用main函數運行結束后,會將main函數的返回值當做exit的參數來調用exit函數。

深入理解Linux中進程控制(精講)

使用exit函數退出進程前,exit函數會先執行用戶定義的清理函數、沖刷緩沖,關閉流等操作,然后再調用_exit函數終止進程。

進程異常退出

情況一:向進程發生信號導致進程異常退出。

例如,在進程運行過程中向進程發生kill -9信號使得進程異常退出,或是使用Ctrl+C使得進程異常退出等。

情況二:代碼錯誤導致進程運行時異常退出。

例如,代碼當中存在野指針問題使得進程運行時異常退出,或是出現除0的情況使得進程運行時異常退出等。

三、進程等待

進程等待的必要性

  1. 子進程退出,父進程如果不讀取子進程的退出信息,子進程就會變成僵尸進程,進而造成內存泄漏。
  2. 進程一旦變成僵尸進程,那么就算是kill -9命令也無法將其殺死,因為誰也無法殺死一個已經死去的進程。
  3. 對于一個進程來說,最關心自己的就是其父進程,因為父進程需要知道自己派給子進程的任務完成的如何。
  4. 父進程需要通過進程等待的方式,回收子進程資源,獲取子進程的退出信息。

獲取子進程status

下面進程等待所使用的兩個函數wait和waitpid,都有一個status參數,該參數是一個輸出型參數,由操作系統進行填充。

如果對status參數傳入NULL,表示不關心子進程的退出狀態信息。否則,操作系統會通過該參數,將子進程的退出信息反饋給父進程。

status是一個整型變量,但status不能簡單的當作整型來看待,status的不同比特位所代表的信息不同,具體細節如下(只研究status低16比特位):

深入理解Linux中進程控制(精講)

在status的低16比特位當中,高8位表示進程的退出狀態,即退出碼。進程若是被信號所殺,則低7位表示終止信號,而第8位比特位是core dump標志。

深入理解Linux中進程控制(精講)

我們通過一系列位操作,就可以根據status得到進程的退出碼和退出信號。

exitCode?=?(status?>>?8)?&?0xFF;?//退出碼 exitSignal?=?status?&?0x7F;??????//退出信號 

對于此,系統當中提供了兩個宏來獲取退出碼和退出信號。

  • WIFEXITED(status):用于查看進程是否是正常退出,本質是檢查是否收到信號。
  • WEXITSTATUS(status):用于獲取進程的退出碼。
exitNormal?=?WIFEXITED(status);??//是否正常退出 exitCode?=?WEXITSTATUS(status);??//獲取退出碼 

需要注意的是,當一個進程非正常退出時,說明該進程是被信號所殺,那么該進程的退出碼也就沒有意義了。

進程等待的方法

wait方法

函數原型:pid_t wait(int* status);

作用:等待任意子進程。

返回值:等待成功返回被等待進程的pid,等待失敗返回-1。

參數:輸出型參數,獲取子進程的退出狀態,不關心可設置為NULL。

例如,創建子進程后,父進程可使用wait函數一直等待子進程,直到子進程退出后讀取子進程的退出信息。

#include? #include? #include? #include? #include? int?main() { ?pid_t?id?=?fork(); ?if(id?==?0){ ??//child ??int?count?=?10; ??while(count--){ ???printf("I?am?child...PID:%d,?PPID:%d ",?getpid(),?getppid()); ???sleep(1); ??} ??exit(0); ?} ?//father ?int?status?=?0; ?pid_t?ret?=?wait(&status); ?if(ret?>?0){ ??//wait?success ??printf("wait?child?success... "); ??if(WIFEXITED(status)){ ???//exit?normal ???printf("exit?code:%d ",?WEXITSTATUS(status)); ??} ?} ?sleep(3); ?return?0; } 

我們可以使用以下監控腳本對進程進行實時監控:

[cl@VM-0-15-centos?procWait]$?while?:;?do?ps?axj?|?head?-1?&&?ps?axj?|?grep?proc?|?grep?-v?grep;echo?"######################";sleep?1;done 

這時我們可以看到,當子進程退出后,父進程讀取了子進程的退出信息,子進程也就不會變成僵尸進程了。

深入理解Linux中進程控制(精講)

waitpid方法

函數原型:pid_t waitpid(pid_t pid, int* status, int options);

作用:等待指定子進程或任意子進程。

返回值:

1、等待成功返回被等待進程的pid。

2、如果設置了選項WNOHANG,而調用中waitpid發現沒有已退出的子進程可收集,則返回0。

3、如果調用中出錯,則返回-1,這時errno會被設置成相應的值以指示錯誤所在。

參數:

1、pid:待等待子進程的pid,若設置為-1,則等待任意子進程。

2、status:輸出型參數,獲取子進程的退出狀態,不關心可設置為NULL。

3、options:當設置為WNOHANG時,若等待的子進程沒有結束,則waitpid函數直接返回0,不予以等待。若正常結束,則返回該子進程的pid。

例如,創建子進程后,父進程可使用waitpid函數一直等待子進程(此時將waitpid的第三個參數設置為0),直到子進程退出后讀取子進程的退出信息。

#include? #include? #include? #include? #include? int?main() { ?pid_t?id?=?fork(); ?if?(id?==?0){ ??//child?????????? ??int?count?=?10; ??while?(count--){ ???printf("I?am?child...PID:%d,?PPID:%d ",?getpid(),?getppid()); ???sleep(1); ??} ??exit(0); ?} ?//father??????????? ?int?status?=?0; ?pid_t?ret?=?waitpid(id,?&status,?0); ?if?(ret?>=?0){ ??//wait?success???????????????????? ??printf("wait?child?success... "); ??if?(WIFEXITED(status)){ ???//exit?normal????????????????????????????????? ???printf("exit?code:%d ",?WEXITSTATUS(status)); ??} ??else{ ???//signal?killed?????????????????????????????? ???printf("killed?by?siganl?%d ",?status?&?0x7F); ??} ?} ?sleep(3); ?return?0; } 

在父進程運行過程中,我們可以嘗試使用kill -9命令將子進程殺死,這時父進程也能等待子進程成功。

深入理解Linux中進程控制(精講)

注意: 被信號殺死而退出的進程,其退出碼將沒有意義。

多進程創建以及等待的代碼模型

上面演示的都是父進程創建以及等待一個子進程的例子,實際上我們還可以同時創建多個子進程,然后讓父進程依次等待子進程退出,這叫做多進程創建以及等待的代碼模型。

例如,以下代碼中同時創建了10個子進程,同時將子進程的pid放入到ids數組當中,并將這10個子進程退出時的退出碼設置為該子進程pid在數組ids中的下標,之后父進程再使用waitpid函數指定等待這10個子進程。

#include? #include? #include? #include? #include? int?main() { ?pid_t?ids[10]; ?for?(int?i?=?0;?i?if?(id?==?0){ ???//child ???printf("child?process?created?successfully...PID:%d ",?getpid()); ???sleep(3); ???exit(i);?//將子進程的退出碼設置為該子進程PID在數組ids中的下標 ??} ??//father ??ids[i]?=?id; ?} ?for?(int?i?=?0;?i?if?(ret?>=?0){ ???//wait?child?success ???printf("wiat?child?success..PID:%d ",?ids[i]); ???if?(WIFEXITED(status)){ ????//exit?normal ????printf("exit?code:%d ",?WEXITSTATUS(status)); ???} ???else{ ????//signal?killed ????printf("killed?by?signal?%d ",?status?&?0x7F); ???} ??} ?} ?return?0; } 

運行代碼,這時我們便可以看到父進程同時創建多個子進程,當子進程退出后,父進程再依次讀取這些子進程的退出信息。

深入理解Linux中進程控制(精講)

基于非阻塞接口的輪詢檢測方案

上述所給例子中,當子進程未退出時,父進程都在一直等待子進程退出,在等待期間,父進程不能做任何事情,這種等待叫做阻塞等待。

實際上我們可以讓父進程不要一直等待子進程退出,而是當子進程未退出時父進程可以做一些自己的事情,當子進程退出時再讀取子進程的退出信息,即非阻塞等待。

?

做法很簡單,向waitpid函數的第三個參數potions傳入WNOHANG,這樣一來,等待的子進程若是沒有結束,那么waitpid函數將直接返回0,不予以等待。而等待的子進程若是正常結束,則返回該子進程的pid。

例如,父進程可以隔一段時間調用一次waitpid函數,若是等待的子進程尚未退出,則父進程可以先去做一些其他事,過一段時間再調用waitpid函數讀取子進程的退出信息。

#include? #include? #include? #include? #include? int?main() { ?pid_t?id?=?fork(); ?if?(id?==?0){ ??//child ??int?count?=?3; ??while?(count--){ ???printf("child?do?something...PID:%d,?PPID:%d ",?getpid(),?getppid()); ???sleep(3); ??} ??exit(0); ?} ?//father ?while?(1){ ??int?status?=?0; ??pid_t?ret?=?waitpid(id,?&status,?WNOHANG); ??if?(ret?>?0){ ???printf("wait?child?success... "); ???printf("exit?code:%d ",?WEXITSTATUS(status)); ???break; ??} ??else?if?(ret?==?0){ ???printf("father?do?other?things... "); ???sleep(1); ??} ??else{ ???printf("waitpid?error... "); ???break; ??} ?} ?return?0; } 

運行結果就是,父進程每隔一段時間就去查看子進程是否退出,若未退出,則父進程先去忙自己的事情,過一段時間再來查看,直到子進程退出后讀取子進程的退出信息。

深入理解Linux中進程控制(精講)

四、進程程序替換

替換原理

用fork創建子進程后,子進程執行的是和父進程相同的程序(但有可能執行不同的代碼分支),若想讓子進程執行另一個程序,往往需要調用一種exec函數。

當進程調用一種exec函數時,該進程的用戶空間代碼和數據完全被新程序替換,并從新程序的啟動例程開始執行。

深入理解Linux中進程控制(精講)

?

當進行進程程序替換時,有沒有創建新的進程?

進程程序替換之后,該進程對應的PCB、進程地址空間以及頁表等數據結構都沒有發生改變,只是進程在物理內存當中的數據和代碼發生了改變,所以并沒有創建新的進程,而且進程程序替換前后該進程的pid并沒有改變。

?

子進程進行進程程序替換后,會影響父進程的代碼和數據嗎?

子進程剛被創建時,與父進程共享代碼和數據,但當子進程需要進行進程程序替換時,也就意味著子進程需要對其數據和代碼進行寫入操作,這時便需要將父子進程共享的代碼和數據進行寫時拷貝,此后父子進程的代碼和數據也就分離了,因此子進程進行程序替換后不會影響父進程的代碼和數據。

替換函數

替換函數有六種以exec開頭的函數,它們統稱為exec函數:

一、int execl(const char *path, const char *arg, …);

第一個參數是要執行程序的路徑,第二個參數是可變參數列表,表示你要如何執行這個程序,并以NULL結尾。

例如,要執行的是ls程序。

execl("/usr/bin/ls",?"ls",?"-a",?"-i",?"-l",?NULL); 

二、int execlp(const char *file, const char *arg, …);

第一個參數是要執行程序的名字,第二個參數是可變參數列表,表示你要如何執行這個程序,并以NULL結尾。

例如,要執行的是ls程序。

execlp("ls",?"ls",?"-a",?"-i",?"-l",?NULL); 

三、int execle(const char *path, const char *arg, …, char *const envp[]);

第一個參數是要執行程序的路徑,第二個參數是可變參數列表,表示你要如何執行這個程序,并以NULL結尾,第三個參數是你自己設置的環境變量。

例如,你設置了MYVAL環境變量,在mycmd程序內部就可以使用該環境變量。

char*?myenvp[]?=?{?"MYVAL=2021",?NULL?}; execle("./mycmd",?"mycmd",?NULL,?myenvp); 

四、int execv(const char *path, char *const argv[]);

第一個參數是要執行程序的路徑,第二個參數是一個指針數組,數組當中的內容表示你要如何執行這個程序,數組以NULL結尾。

例如,要執行的是ls程序。

char*?myargv[]?=?{?"ls",?"-a",?"-i",?"-l",?NULL?}; execv("/usr/bin/ls",?myargv); 

五、int execvp(const char *file, char *const argv[]);

第一個參數是要執行程序的名字,第二個參數是一個指針數組,數組當中的內容表示你要如何執行這個程序,數組以NULL結尾。

例如,要執行的是ls程序。

char*?myargv[]?=?{?"ls",?"-a",?"-i",?"-l",?NULL?}; execvp("ls",?myargv); 

六、int execve(const char *path, char *const argv[], char *const envp[]);

第一個參數是要執行程序的路徑,第二個參數是一個指針數組,數組當中的內容表示你要如何執行這個程序,數組以NULL結尾,第三個參數是你自己設置的環境變量。

例如,你設置了MYVAL環境變量,在mycmd程序內部就可以使用該環境變量。

char*?myargv[]?=?{?"mycmd",?NULL?}; char*?myenvp[]?=?{?"MYVAL=2021",?NULL?}; execve("./mycmd",?myargv,?myenvp); 

函數解釋

  • 這些函數如果調用成功,則加載指定的程序并從啟動代碼開始執行,不再返回。
  • 如果調用出錯,則返回-1。

也就是說,exec系列函數只要返回了,就意味著調用失敗。

命名理解

這六個exec系列函數的函數名都以exec開頭,其后綴的含義如下:

  • l(list):表示參數采用列表的形式,一一列出。
  • v(vector):表示參數采用數組的形式。
  • p(path):表示能自動搜索環境變量PATH,進行程序查找。
  • e(env):表示可以傳入自己設置的環境變量。

深入理解Linux中進程控制(精講)

事實上,只有execve才是真正的系統調用,其它五個函數最終都是調用的execve,所以execve在man手冊的第2節,而其它五個函數在man手冊的第3節,也就是說其他五個函數實際上是對系統調用execve進行了封裝,以滿足不同用戶的不同調用場景的。

下圖為exec系列函數族之間的關系:

深入理解Linux中進程控制(精講)

做一個簡易的shell

shell也就是命令行解釋器,其運行原理就是:當有命令需要執行時,shell創建子進程,讓子進程執行命令,而shell只需等待子進程退出即可。

深入理解Linux中進程控制(精講)

其實shell需要執行的邏輯非常簡單,其只需循環執行以下步驟:

  1. 獲取命令行。
  2. 解析命令行。
  3. 創建子進程。
  4. 替換子進程。
  5. 等待子進程退出。

其中,創建子進程使用fork函數,替換子進程使用exec系列函數,等待子進程使用wait或者waitpid函數。

于是我們可以很容易實現一個簡易的shell,代碼如下:

#include? #include? #include? #include? #include? #include? #include? #define?LEN?1024?//命令最大長度 #define?NUM?32?//命令拆分后的最大個數 int?main() { ?char?cmd[LEN];?//存儲命令 ?char*?myargv[NUM];?//存儲命令拆分后的結果 ?char?hostname[32];?//主機名 ?char?pwd[128];?//當前目錄 ?while?(1){ ??//獲取命令提示信息 ??struct?passwd*?pass?=?getpwuid(getuid()); ??gethostname(hostname,?sizeof(hostname)-1); ??getcwd(pwd,?sizeof(pwd)-1); ??int?len?=?strlen(pwd); ??char*?p?=?pwd?+?len?-?1; ??while?(*p?!=?'/'){ ???p--; ??} ??p++; ??//打印命令提示信息 ??printf("[%s@%s?%s]$?",?pass->pw_name,?hostname,?p); ??//讀取命令 ??fgets(cmd,?LEN,?stdin); ??cmd[strlen(cmd)?-?1]?=?'

主站蜘蛛池模板:
欧美成年黄网站色高清视频
|
aaa一级毛片|
欧美成人毛片免费网站
|
久久久精品免费视频
|
亚洲一区二区三区四区在线
|
成人伊人
|
欧产日产国产精品精品
|
精品久久久久中文字幕日本
|
一级毛片在播放免费
|
青草青99久久99九九99九九九
|
免费在线黄色网址
|
快色网站
|
欧美日韩亚洲成色二本道三区
|
欧美一区二区三区gg高清影视
|
免费在线观看a级片
|
欧美高清一区二区三区欧美
|
成人在线观看一区
|
国产第一草草影院
|
美国全免费特一级毛片
|
99久久免费观看
|
男女无遮挡拍拍拍免费1000
|
国产精品欧美一区二区
|
精品国产杨幂在线观看福利
|
国产成人精品久久一区二区小说
|
成人一级片在线观看
|
成人精品一级毛片
|
国产免费成人在线视频
|
国产精品久久久久免费
|
亚洲一级片在线播放
|
国产永久高清免费动作片www
|
亚洲欧美国产高清va在线播放
|
国产欧美视频在线观看
|
一区二区不卡视频在线观看
|
亚洲精品一区二区三区四区手机版
|
亚洲一区二区三区免费看
|
欧美视频一二三区
|
精品久久久久不卡无毒
|
在线网站黄色
|
国产亚洲精品久久久久久久网站
|
亚洲精品国自产拍影院
|
毛片高清一区二区三区
|