• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            興海北路

            ---男兒仗劍自橫行
            <2008年3月>
            2425262728291
            2345678
            9101112131415
            16171819202122
            23242526272829
            303112345

            統計

            • 隨筆 - 85
            • 文章 - 0
            • 評論 - 17
            • 引用 - 0

            常用鏈接

            留言簿(6)

            隨筆分類

            隨筆檔案

            收藏夾

            全是知識啊

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            shell編程范例之進程的操作
            by falcon<zhangjinw@gmail.com>
            2008-02-21

                這一小節寫了很久,到現在才寫完。本來關注的內容比較多,包括程序開發過程的細節、ELF格式的分析、進程的內存映像等,后來搞得“雪球越滾越大”,甚至 脫離了shell編程關注的內容。所以呢,想了個小辦法,“大事化小,小事化了”,把涉及到的內容分成如下幾個部分:
                1、把VIM打造成源代碼編輯器(源代碼編輯過程:用VIM編輯代碼的一些技巧)
                2、GCC編譯的背后 第一部分:預處理和編譯 第二部分:匯編和鏈接(編譯過程:預處理、編譯、匯編、鏈接)
                3、程序執行的那一剎那 (執行過程:當我們從命令行輸入一個命令之后)
                4、進程的內存映像 (進程加載過程:程序在內存里是個什么樣子)
                5、動態符號鏈接的細節(動態鏈接過程:函數puts/printf的地址在哪里)
                6、代碼測試、調試與優化小結(程序開發過后:內存溢出了嗎?有緩沖區溢出?代碼覆蓋率如何測試呢?怎么調試匯編代碼?有哪些代碼優化技巧和方法呢?)
                7、    8、進程和進程的基本操作(本小節)
                呵呵,好多。終于可以一部分一部分地完成了,不會再有一種對著一個大蛋糕卻不知道如何下口的尷尬了。
                進程作為程序真正發揮作用時的“形態”,我們有必要對它的一些相關操作非常熟悉,這一節主要描述進程相關的概念和操作,將介紹包括程序、進程、作業等基本概念以及進程狀態查詢、進程通信等相關的基本操作等。

            1、什么是程序,什么又是進程

                程序是指令的集合,而進程則是程序執行的基本單元。為了讓程序完成它的工作,我們必須讓程序運行起來成為進程,進而利用處理器資源,內存資源,進行各種I/O操作,從而完成某項指定的工作。
                在這個意思上說,程序是靜態的,而進程則是動態的。
                而進程有區別于程序的地方還有,進程除了包含程序文件中的指令數據意外,還需要在內核中有一個數據結構用以存放特定進程的相關屬性,以便內核更好的管理和調度進程,從而完成多進程協作的任務。因此,從這個意義上可以說“高于”程序,超出了程序指令本身。
                如果進行過多進程程序的開發,你又會發現,一個程序可能創建多個進程,通過多個進程的交互完成任務。在Linux下,多進程的創建通常是通過fork系統調用實現的。從這個意義上來說程序則”包含”了進程。
                另外一個需要明確的是,程序可以由多種不同的程序語言描述,包括C語言程序、匯編語言程序和最后編譯產生的機器指令等。
               
                下面我們簡單討論一下Linux下面如何通過shell進行進程的相關操作。

            2、進程的創建

                通常在命令行鍵入某個程序文件名以后,一個進程就被創建了。例如,

            Quote:

            $ sleep 100 &            #讓sleep程序在后臺運行
            [1] 9298
            $ pidof sleep             #用pidof可以查看指定程序名的進程ID
            9298
            $ cat /proc/9298/maps    #查看進程的內存映像
            08048000-0804b000 r-xp 00000000 08:01 977399     /bin/sleep
            0804b000-0804c000 rw-p 00003000 08:01 977399     /bin/sleep
            0804c000-0806d000 rw-p 0804c000 00:00 0          [heap]
            b7c8b000-b7cca000 r--p 00000000 08:01 443354    
            ...
            bfbd8000-bfbed000 rw-p bfbd8000 00:00 0          [stack]
            ffffe000-fffff000 r-xp 00000000 00:00 0          [vdso]


                當一個程序被執行以后,程序被加載到內存中,成為了一個進程。上面顯示了該進程的內存映像(虛擬內存),包括程序指令、數據,以及一些用于存放程命令行參數、環境變量的棧空間,用于動態內存申請的堆空間都被分配好了。
                關于程序在命令行執行過程的細節,請參考《Linux命令行下程序執行的那一剎那》。

                實際上,創建一個進程,也就是說讓程序運行,還有其他的辦法,比如,通過一些配置讓系統啟動時自動啟動我們的程序(具體參考"man init"),或者是通過配置crond(或者at)讓它定時啟動我們的程序。除此之外,還有一個方式,那就是編寫shell腳本,把程序寫入一個腳本文 件,當執行腳本文件時,文件中的程序將被執行而成為進程。這些方式的細節就不介紹了,下面介紹如何查看進程的屬性。

                需要補充一點的是,在命令行下執行程序時,我們可以通過ulimit內置命令來設置進程可以利用的資源,比如進程可以打開的最大文件描述符個數,最大的棧空間,虛擬內存空間等。具體用法見"help ulimit"。

            3、查看進程的屬性和狀態

                我們可以通過ps命令查看進程的相關屬性和狀態,這些信息包括進程所屬用戶,進程對應的程序,進程對cpu和內存的使用情況等信息。熟悉如何查看它們有助于我們進行相關的統計分析和進一步的操作。

            Quote:

            $ ps -ef #查看系統所有當前進程的屬性
            $ ps -C init #查看命令中包含某個指定字符的程序對應的進程,進程ID是1,TTY為?表示和終端沒有關聯
              PID TTY          TIME CMD
                1 ?        00:00:01 init
            $ ps -U falcon #選擇某個特定用戶啟動的進程
            $ ps -e -o "%C %c"  #可以按照指定格式輸出指定內容,這里會輸出命令名和cpu使用率
            $ ps -e -o "%C %c" | sort -u -k1 -r | head -5  #這樣則會打印cpu使用率最高的前4個程序
             7.5 firefox-bin
             1.1 Xorg
             0.8 scim-panel-gtk
             0.2 scim-bridge
            $ ps -e -o "%z %c" | sort -n -k1 -r | head -5  #使用虛擬內存最大的5個進程
            349588 firefox-bin
             96612 xfce4-terminal
             88840 xfdesktop
             76332 gedit
             58920 scim-panel-gtk


                由于系統所有進程之間都有“親緣”關系,所以可以通過pstree查看這種關系,
            Quote:

            $ pstree #打印系統進程調用樹,可以非常清楚地看到當前系統中所有活動進程之間的調用關系


                動態查看進程信息,  
            Quote:

            $ top


                該命令最大的特點是可以動態的查看進程的信息,當然,它還提供了一些有用的參數,比如-S可以按照累計執行時間的大小排序查看,也可以通過-u查看指定用戶啟動的進程等。

                感覺有上面幾個命令來查看進程的信息就差不多了,下面來討論一個有趣的問題:如何讓一個程序在同一時間只有一個在運行。
                這意味著當一個程序正在被執行時,它將不能再被啟動。那該怎么做呢?
                假如一份相同的程序被復制成了很多份,并且具有不同的文件名被放在不同的位置,這個將比較糟糕,所以我們考慮最簡單的情況,那就是這份程序在整個系統上是唯一的,而且名字也是唯一的。這樣的話,我們有哪些辦法來回答上面的問題呢?
                總的機理是:在這個程序的開頭檢查自己有沒有執行,如果執行了則停止否則繼續執行后續代碼。
                策略則是多樣的,由于前面的假設已經保證程序文件名和代碼的唯一性,所以
          1. 通過ps命令打印找出當前的所有進程對應的程序名,逐個與自己的程序名比較,如果已經有,那么說明自己已經運行了。


            Code:

            [Ctrl+A Select All]


          2. 每次運行時先在指定位置檢查是否存在一個保存自己進程ID的文件,如果不存在,那么繼續執行,如果存在,那么查看該進程ID是否正在運行,如果在,那么退出,否則往該文件重新寫的新的進程ID,并繼續。



            Code:

            [Ctrl+A Select All]


                更多實現策略自己盡情的發揮吧!

            4、調整進程的優先級

                在保證每個進程都能夠順利執行外,為了讓某些任務優先完成,那么系統在進行進程調度時就會采用一定的調度辦法,比如常見的有按照優先級的時間片輪轉的調度算法。這種情況下,我們可以通過renice調整正在運行的程序的優先級,例如,

            Quote:

            $ ps -e -o "%p %c %n" | grep xfs #打印的信息分別是進程ID,進程對應的程序名,優先級
             5089 xfs               0
            $ renice 1 -p 5089
            renice: 5089: setpriority: Operation not permitted
            $ sudo renice 1 -p 5089   #需要權限才行
            [sudo] password for falcon:
            5089: old priority 0, new priority 1
            $ ps -e -o "%p %c %n" | grep xfs  #再看看,優先級已經被調整過來了
             5089 xfs               1



            5、結束進程

                既然可以通過命令行執行程序,創建進程,那么也有辦法結束它。我們可以通過kill命令給用戶自己啟動的進程發送一定信號讓進程終止,當然“萬能”的root幾乎可以kill所有進程(除了init之外)。例如,

            Quote:

            $ sleep 50 &   #啟動一個進程
            [1] 11347
            $ kill 11347


                kill命令默認會發送終止信號(SIGTERM)給程序,讓程序退出,但是kill還可以發送其他的信號,這些信號的定義我們可以通過man 7 signal查看到,也可以通過kill -l列出來。
            Quote:

            $ man 7 signal
            $ kill -l
             1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL
             5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE
             9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2
            13) SIGPIPE     14) SIGALRM     15) SIGTERM     16) SIGSTKFLT
            17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
            21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU
            25) SIGXFSZ     26) SIGVTALRM   27) SIGPROF     28) SIGWINCH
            29) SIGIO       30) SIGPWR      31) SIGSYS      34) SIGRTMIN
            35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3  38) SIGRTMIN+4
            39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
            43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12
            47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14
            51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10
            55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6
            59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
            63) SIGRTMAX-1  64) SIGRTMAX


                例如,我們用kill命令發送SIGSTOP信號給某個程序,讓它暫停,然后發送SIGCONT信號讓它繼續運行。

            Quote:

            $ sleep 50 &
            [1] 11441
            $ jobs
            [1]+  Running                 sleep 50 &
            $ kill -s SIGSTOP 11441   #這個等同于我們對一個前臺進程執行CTRL+Z操作
            $ jobs
            [1]+  Stopped                 sleep 50
            $ kill -s SIGCONT 11441   #這個等同于之前我們使用bg %1操作讓一個后臺進程運行起來
            $ jobs
            [1]+  Running                 sleep 50 &
            $ kill %1                  #在當前會話(session)下,也可以通過作業號控制進程
            $ jobs
            [1]+  Terminated              sleep 50


                可見kill命令為我們提供了非常好的功能,不過kill命令只能根據進程的ID或者作業來控制進程,所以pkill和killall給我們提供了更多選擇,它們擴展了通過程序名甚至是進程的用戶名來控制進程的方法。更多用法請參考它們的手冊。

                當一個程序退出以后,如何判斷這個程序是正常退出還是異常退出呢?還記得Linux下,那個經典"hello,world"程序嗎?在代碼的最后總是有條 “return 0”語句。這個“return 0”實際上是為了讓程序員來檢查進程是否正常退出的。如果進程返回了一個其他的數值,那么我們可以肯定的說這個進程異常退出了,因為它都沒有執行到 “return 0”這條語句就退出了。
                那怎么檢查進程退出的狀態,即那個返回的數值呢?
                在shell程序中,我們可以檢查這個特殊的變量$?,它存放了上一條命令執行后的退出狀態。

            Quote:

            $ test1
            bash: test1: command not found
            $ echo $?
            127
            $ cat ./test.c | grep hello
            $ echo $?
            1
            $ cat ./test.c | grep hi
                    printf("hi, myself!\n");
            $ echo $?
            0


                貌似返回0成為了一個潛規則,雖然沒有標準明確規定,不過當程序正常返回時,我們總是可以從$?中檢測到0,但是異常時,我們總是檢測到一個非0的值。這 就告訴我們在程序的最后我們最好是跟上一個exit 0以便任何人都可以通過檢測$?確定你的程序是否正常結束。如果有一天,有人偶爾用到你的程序,試圖檢查你的程序的退出狀態,而你卻在程序的末尾莫名的返 回了一個-1或者1,那么他將會很苦惱,會懷疑自己的程序哪個地方出了問題,檢查半天卻不知所措,因為他太信任你了,竟然從頭至尾都沒有懷疑你的編程習慣 可能會與眾不同。

            6、進程通信

                為了便于設計和實現,通常一個大型的任務都被劃分成較小的模塊。不同模塊之間啟動后成為進程,它們之間如何通信以便交互數據,協同工作呢?在《UNIX環 境高級編程》一書中提到很多方法,諸如管道(無名管道和有名管道)、信號(signal)、報文(Message)隊列(消息隊列)、共享內存 (mmap/munmap)、信號量(semaphore,主要是同步用,進程之間,進程的不同線程之間)、套接口(Socket,支持不同機器之間的進 程通信)等,而在shell編程里頭,我們通常直接用到的就有管道和信號等。下面主要介紹管道和信號機制在shell編程時候的一些用法。
               
               
          3. 無名管道(pipe):

                在Linux下,你可以通過"|"連接兩個程序,這樣就可以用它來連接后一個程序的輸入和前一個程序的輸出,因此被形象地叫做個管道。在C語言里頭,創建 無名管道非常簡單方便,用pipe函數,傳入一個具有兩個元素的int型的數組就可以。這個數組實際上保存的是兩個文件描述符,父進程往第一個文件描述符 里頭寫入東西后,子進程可以從第一個文件描述符中讀出來。

                如果用多了命令行,這個管子"|"應該會經常用。比如我們在上面的演示中把ps命令的輸出作為grep命令的輸入,從而可以過濾掉一些我們感興趣的信息:

            Quote:

            $ ps -ef | grep init



                也許你會覺得這個“管子”好有魔法,竟然真地能夠鏈接兩個程序的輸入和輸出,它們到底是怎么實現的呢?實際上當我們輸入這樣一組命令的時候,當前解釋程序 會進行適當的解析,把前面一個進程的輸出關聯到管道的輸出文件描述符,把后面一個進程的輸入關聯到管道的輸入文件描述符,這個關聯過程通過輸入輸出重定向 函數dup(或者fcntl)來實現。

               
          4. 有名管道(named pipe):

                有名管道實際上是一個文件(無名管道也像一個文件,雖然關系到兩個文件描述符,不過只能一邊讀另外一邊寫),不過這個文件比較特別,操作時要滿足先進先 出,而且,如果試圖讀一個沒有內容的有名管道,那么就會被阻塞,同樣地,如果試圖往一個有名管道里頭寫東西,而當前沒有程序試圖讀它,也會被阻塞。下面看 看效果。

            Quote:

            $ mkfifo fifo_test    #通過mkfifo命令可以創建一個有名管道
            $ echo "fewfefe" > fifo_test    #試圖往fifo_test文件中寫入內容,但是被阻塞,要另開一個終端繼續下面的操作
            $ cat fifo_test        #另開一個終端,記得,另開一個。試圖讀出fifo_test的內容
            fewfefe



                在這里echo和cat是兩個不同的程序,在這種情況下,通過echo和cat啟動的兩個進程之間并沒有父子關系。不過它們依然可以通過有名管道通信。這 樣一種通信方式非常適合某些情況:例如有這樣一個架構,這個架構由兩個應用程序構成,其中一個通過一個循環不斷讀取fifo_test中的內容,以便判 斷,它下一步要做什么。如果這個管道沒有內容,那么它就會被阻塞在那里,而不會死循環而耗費資源,另外一個則作為一個控制程序不斷地往fifo_test 中寫入一些控制信息,以便告訴之前的那個程序該做什么。下面寫一個非常簡單的例子。我們可以設計一些控制碼,然控制程序不斷的往fifo_test里頭寫 入,然后應用程序根據這些控制碼完成不同的動作。當然,也可以往fifo_test傳入除控制碼外的不同的數據。

            Quote:

            $ cat app.sh    #應用程序的代碼
            #!/bin/bash

            FIFO=fifo_test
            while :;
            do
                            CI=`cat $FIFO`  #CI --> Control Info
                            case $CI in
                                            0) echo "The CONTROL number is ZERO, do something ..."
                                                    ;;
                                            1) echo "The CONTROL number is ONE, do something ..."
                                                    ;;
                                            *) echo "The CONTROL number not recognized, do something else..."
                                                    ;;
                            esac
            done
            $ cat control.sh    #控制程序的代碼
            #!/bin/bash

            FIFO=fifo_test
            CI=$1

            [ -z "$CI" ] && echo "the control info should not be empty" && exit

            echo $CI > $FIFO
            $ chmod +x app.sh control.sh    #修改這兩個程序的可執行權限,以便用戶可以執行它們
            $ ./app.sh            #在一個終端啟動這個應用程序,在通過./control.sh發送控制碼以后查看輸出
            The CONTROL number is ONE, do something ...    #發送1以后
            The CONTROL number is ZERO, do something ...    #發送0以后
            The CONTROL number not recognized, do something else...    #發送一個未知的控制碼以后
            $ ./control.sh 1            #在另外一個終端,發送控制信息,控制應用程序的工作
            $ ./control.sh 0            
            $ ./control.sh 4343



                這樣一種應用架構非常適合本地的多程序任務的設計,如果結合web cgi,那么也將適合遠程控制的要求。引入web cgi的唯一改變是,要把控制程序./control.sh放到web的cgi目錄下,并對它作一些修改,以使它符合CGI的規范,這些規范包括文檔輸出 格式的表示(在文件開頭需要輸出content-tpye: text/html以及一個空白行)和輸入參數的獲取(web輸入參數都存放在QUERY_STRING環境變量里頭)。因此一個非常簡單的CGI形式控 制程序將類似下面。



            Code:

            [Ctrl+A Select All]



                在實際使用的時候,請確保control.sh能夠訪問到fifo_test管道,并且有寫權限。這樣我們在瀏覽器上就可以這樣控制app.sh了。

            http://ipaddress_or_dns/cgi-bin/control.sh?0

                問號(?)后面的內容即QUERY_STRING,類似之前的$1。

                這樣一種應用對于遠程控制,特別是嵌入式系統的遠程控制很有實際意義。在去年的暑期課程上,我們就通過這樣一種方式來實現馬達的遠程控制。首先,我們實現 了一個簡單的應用程序以便控制馬達的轉動,包括轉速,方向等的控制。為了實現遠程控制,我們設計了一些控制碼,以便控制馬達轉動相關的不同屬性。

                在C語言中,如果要用有名管道,和shell下的類似,只不過在讀寫數據的時候用read,write調用,在創建fifo的時候用mkfifo函數調用。

               
          5. 信號(Signal):

                信號是軟件中斷,在Linux下面用戶可以通過kill命令給某個進程發送一個特定的信號,也可以通過鍵盤發送一些信號,比如CTRL+C可能觸發 CGIINT信號,而CTRL+\可能觸發SGIQUIT信號等,除此之外,內核在某些情況下也會給進程發送信號,比如在訪問內存越界時產生 SGISEGV信號,當然,進程本身也可以通過kill,raise等函數給自己發送信號。對于Linux下支持的信號類型,大家可以通過"man 7 signal"或者"kill -l"查看到相關列表和說明。

                對于有些信號,進程會有默認的響應動作,而有些信號,進程可能直接會忽略,當然,用戶還可以對某些信號設定專門的處理函數。在shell程序中,我們可以 通過trap命令(shell的內置命令)來設定響應某個信號的動作(某個命令或者是你定義的某個函數),而在C語言里頭可以通過signal調用注冊某 個信號的處理函數。這里僅僅演示trap命令的用法。

            Quote:

            $ function signal_handler {    #定一個signal_handler的函數,>是按下換行符號自動出現的
            > echo "hello, world"
            > }
            $ trap signal_handle SIGINT    #執行該命令設定:當發生SIGINT信號時將打印hello。
            $ hello, world            #按下CTRL+C,可以看到屏幕上輸出了hello, world字符串



                類似地,如果設定信號0的響應動作,那么就可以用trap來模擬C語言程序中的atexit程序終止函數的登記,即通過trap signal_handler SIGQUIT設定的signal_handler函數將在程序退出的時候被執行。信號0是一個特別的信號,在POSIX.1中把信號編號0定義為空信 號,這將常被用來確定一個特定進程是否仍舊存在。當一個程序退出時會觸發該信號。

            Quote:

            $ cat sigexit.sh
            #!/bin/bash

            function signal_handler {
                    echo "hello, world"
            }
            trap signal_handler 0
            $ chmod +x sigexit.sh
            $ ./sigexit.sh    #實際上在shell編程時,會用這種方式在程序退出時來做一些清理臨時文件的收尾工作
            hello, world

              

            7、作業和作業控制

                當我們為完成一些復雜的任務而將多個命令通過|,>,<, ;, (, )等組合在一起的時候,通常這樣個命令序列會啟動多個進程,它們之間通過管道等進行通信。而有些時候,我們在執行一個任務的同時,還有其他的任務需要處 理,那么就經常會在命令序列的最后加上一個&,或者在執行命令以后,按下CTRL+Z讓前一個命令暫停。以便做其他的任務。等做完其他一些任務以 后,再通過fg命令把后臺的任務切換到前臺。這樣一種控制過程通常被成為作業控制,而那些命令序列則被成為作業,這個作業可能涉及一個或者多個程序,一個 或者多個進程。下面演示一下幾個常用的作業控制操作。

            Quote:

            $ sleep 50 &      #讓程序在后臺運行,將打印[作業號]和進程號
            [1] 11137
            $ fg %1            #使用shell內置命令fg把作業1調到前臺運行,然后按下CTRL+Z讓該進程暫停
            sleep 50

            [1]+  Stopped                 sleep 50
            $ jobs            #查看當前作業情況,有一個作業停止
            [1]+  Stopped                 sleep 50
            $ sleep 100 &       #讓另外一個作業在后臺運行
            [2] 11138        
            $ jobs            #查看當前作業情況,一個正在運行,一個停止
            [1]+  Stopped                 sleep 50
            [2]-  Running                 sleep 100 &
            $ bg %1            #啟動停止的進程并讓它在后臺運行
            [2]+ sleep 50 &



                不過,要在命令行下使用作業控制,需要當前shell,內核終端驅動等對作業控制支持才行。

            參考資料

            <UNIX環境高級編程>相關章節

          6. posted on 2008-03-14 15:34 隨意門 閱讀(1014) 評論(0)  編輯 收藏 引用

            久久精品中文闷骚内射| 久久久久国产一级毛片高清板| 蜜桃麻豆www久久国产精品| 久久中文字幕一区二区| 久久久久久av无码免费看大片| 狠狠久久亚洲欧美专区| 人人狠狠综合久久亚洲婷婷| 99久久成人18免费网站| 亚洲人AV永久一区二区三区久久| 久久久精品久久久久特色影视| 99久久精品九九亚洲精品| 久久精品国产清自在天天线| 久久国产精品波多野结衣AV| 狠狠综合久久AV一区二区三区| 精品综合久久久久久97超人| 久久国产高清一区二区三区| 精品久久久久成人码免费动漫 | 国产精品美女久久久久| 久久国产高潮流白浆免费观看| 欧美国产精品久久高清| 天天躁日日躁狠狠久久| 久久精品?ⅴ无码中文字幕| 国内精品久久久久久久97牛牛| 久久涩综合| 狠狠色婷婷综合天天久久丁香| 久久综合精品国产一区二区三区 | 91久久婷婷国产综合精品青草| 99久久精品国产一区二区| 久久国产欧美日韩精品免费| 久久99精品国产一区二区三区 | 久久精品国产91久久综合麻豆自制 | 久久综合精品国产一区二区三区 | 久久只有这里有精品4| 91久久精品电影| 亚洲AV无码1区2区久久| 久久久久亚洲av综合波多野结衣| 欧美国产精品久久高清| 久久精品国产亚洲网站| 久久久精品国产sm调教网站| 久久亚洲私人国产精品| 久久久久亚洲AV成人片|