青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

興海北路

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

統(tǒng)計(jì)

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

常用鏈接

留言簿(6)

隨筆分類

隨筆檔案

收藏夾

全是知識(shí)啊

搜索

  •  

最新評論

閱讀排行榜

評論排行榜

Linux命令行上程序執(zhí)行的那一剎那!
by falcon<zhangjinw@gmail.com>
2008-02-15

    (這一小節(jié)應(yīng)該是作為《shell編程范例之進(jìn)程操作》的一些補(bǔ)充性質(zhì)的內(nèi)容。)

    當(dāng)我們在Linux下的命令行輸入一個(gè)命令之后,這背后發(fā)生了什么?

1、什么是命令行接口

    用戶使用計(jì)算機(jī)有兩種常見的方式,一種是圖形化的接口(GUI),另外一種則是命令行接口(CLI)。對于圖形化的接口,用戶點(diǎn)擊某個(gè)圖標(biāo)就可啟動(dòng)后 臺(tái)的某個(gè)程序;對于命令行的接口,用戶鍵入某個(gè)程序的名字就可啟動(dòng)某個(gè)程序。這兩者的基本過程是類似的,都需要查找程序文件在磁盤上的位置,加載到內(nèi)存并 通過不同的解釋器進(jìn)行解析和運(yùn)行。下面以命令行為例來介紹程序執(zhí)行那一剎那發(fā)生的一些事情。
    首先來介紹什么是命令行?命令行就是command line,很直觀的概念就是系統(tǒng)啟動(dòng)后的那個(gè)黑屏幕:有一個(gè)提示符,并有光標(biāo)在閃爍的那樣一個(gè)終端,一般情況下可以用CTRL+ALT+F1-6切換到不同的終端;在GUI界 面下也會(huì)有一些偽終端,看上去和系統(tǒng)啟動(dòng)時(shí)的那個(gè)終端沒有什么區(qū)別,也會(huì)有一個(gè)提示符,并有一個(gè)光標(biāo)在閃爍。就提示符和響應(yīng)用戶的鍵盤輸入而言,它們兩者 在功能上是一樣的,實(shí)際上它們就是同一個(gè)東西,你用下面的命令就可以把它們打印出來。
Quote:

$ echo $SHELL   #打印當(dāng)前SHELL,當(dāng)前運(yùn)行的命令行接口程序
/bin/bash
$ echo $$   #該程序?qū)?yīng)的進(jìn)程ID,$$是一個(gè)比較特殊的環(huán)境變量,它存放了當(dāng)前進(jìn)程ID
1481
$ ps -C bash   #通過PS命令查看
  PID TTY          TIME CMD
 1481 pts/0    00:00:00 bash


    從上面的操作結(jié)果可以看出,當(dāng)前命令行接口實(shí)際上是一個(gè)程序,那就是/bin/bash,它是一個(gè)實(shí)實(shí)在在的程序,它打印提示符,接受用戶輸入的命令,分 析命令序列并執(zhí)行然后返回結(jié)果。不過/bin/bash僅僅是當(dāng)前使用的命令行程序之一,還有很多具有類似功能的程序,比如/bin/tcsh, bin/ash等。不過這里主要來討論bash了,討論它自己是怎么啟動(dòng)的,它怎么樣處理用戶的輸入命令等后臺(tái)細(xì)節(jié)?

1.2 /bin/bash是什么時(shí)候啟動(dòng)的

1.2.1 /bin/bash

   先通過CTRL+ALT+F1切換到一個(gè)普通終端下面,一般情況下看到的是XXX login: 提示輸入用戶名,接著是提示輸入密碼,然后呢?就直接登錄到了我們的命令 行接口。實(shí)際上正是你輸入正確的密碼后,那個(gè)程序把/bin/bash給啟動(dòng)了。那是什么東西提示"XXX login:"的呢?正是/bin/login程序,那/bin/login程序怎么知道要啟動(dòng)/bin/bash,而不是其他的/bin/tcsh呢?
    /bin/login程序?qū)嶋H上會(huì)檢查我們的/etc/passwd文件,在這個(gè)文件里頭包含了用戶名、密碼和該用戶的登錄shell。密碼和用戶名匹配用戶的登錄,而登錄shell則作為用戶登錄后的命令行程序。看看/etc/passwd中典型的這么一行:
Quote:

$ cat /etc/passwd | grep falcon
falcon:x:1000:1000:falcon,,,:/home/falcon:/bin/bash


    這個(gè)是我用的帳號的相關(guān)信息哦,看到最后一行沒?/bin/bash,這正是我登錄用的命令行解釋程序。至于密碼呢,看到那個(gè)x沒?這個(gè)x說明我的密碼被 保存在另外一個(gè)文件里頭/etc/shadow,而且密碼是經(jīng)過加密的。至于這兩個(gè)文件的更多細(xì)節(jié),看manual吧。
    我們怎么知道剛好是/bin/login打印了"XXX login"呢?現(xiàn)在回顧一下很早以前學(xué)習(xí)的那個(gè)strace命令。我們可以用strace命令來跟蹤/bin/login程序的執(zhí)行。
    跟上面一樣,切換到一個(gè)普通終端,并切換到root用戶,用下面的命令:
Quote:

$ strace -f -o strace.out /bin/login


    退出以后就可以打開strace.out文件,看看到底執(zhí)行了哪些文件,讀取了哪些文件。從中我們可以看到正是/bin/login程序用execve調(diào) 用了/bin/bash命令。通過后面的演示,我們發(fā)現(xiàn)/bin/login只是在子進(jìn)程里頭用execve調(diào)用了/bin/bash,因?yàn)樵趩?dòng) /bin/bash后,我們發(fā)現(xiàn)/bin/login并沒有退出。

1.2.2 /bin/login

    那/bin/login又是怎么起來的呢?
    下面再來看一個(gè)演示。先在一個(gè)可以登陸的終端下執(zhí)行下面的命令。
Quote:

$ getty 38400 tty8 linux


    getty命令停留在那里,貌似等待用戶的什么操作,現(xiàn)在切回到第8個(gè)終端,是不是看到有"XXX login:"的提示了。輸入用戶名并登錄,之后退出,回到第一個(gè)終端,發(fā)現(xiàn)getty命令已經(jīng)退出。
    類似地,我們也可以用strace命令來跟蹤getty的執(zhí)行過程。在第一個(gè)終端下切換到root用戶。執(zhí)行如下命令,
Quote:

$ strace -f -o strace.out getty 38400 tty8 linux


    同樣在strace.out命令中可以找到該命令的相關(guān)啟動(dòng)細(xì)節(jié)。比如,我們可以看到正是getty程序用execve系統(tǒng)調(diào)用執(zhí)行了 /bin/login程序。這個(gè)地方,getty是在自己的主進(jìn)程里頭直接執(zhí)行了/bin/login,這樣/bin/login將把getty的進(jìn)程空 間替換掉。

1.2.3 /sbin/getty

    這里涉及到一個(gè)非常重要的東西了:/sbin/init,通過man init你可以查看到該命令的作用,它可是“萬物之王”(init  is  the  parent of all processes on the system)哦。它是Linux系統(tǒng)默認(rèn)啟動(dòng)的第一個(gè)程序,負(fù)責(zé)進(jìn)行Linux系統(tǒng)的一些初始化工作,而這些初始化工作的配置則是通過 /etc/inittab來做的。那么來看看/etc/inittab的一個(gè)簡單的example吧,可以通過man inittab查看相關(guān)幫助。
    整個(gè)配置文件的語法非常簡單,就是下面一行的重復(fù),
Quote:

id:runlevels:action:process


   
  • id就是一個(gè)唯一的編號,不用管它,一個(gè)名字而言,無關(guān)緊要。
       
  • runlevels是運(yùn)行級別,整個(gè)還是比較重要的,理解運(yùn)行級別的概念很有必要,它可以有如下的取值,
    Quote:

    0 is halt.
    1 is single-user.
    2-5 are multi-user.
    6 is reboot.


        不過,真正在配置文件里頭用的是1-5了,而0和6非常特別,除了用它作為init命令的參數(shù)關(guān)機(jī)和重啟外,似乎沒有哪個(gè)“傻瓜”把它寫在系統(tǒng)的配置文件 里頭,讓系統(tǒng)啟動(dòng)以后就關(guān)機(jī)或者重啟。1代表單用戶,而2-5則代表多用戶。對于2-5可能有不同的解釋,比如在slackware 12.0上,2,3,5被用來作為多用戶模式,但是默認(rèn)不啟動(dòng)X windows(GUI接口),而4則作為啟動(dòng)X windows的運(yùn)行級別。
       
  • action是動(dòng)作,它也有很多選擇,我們關(guān)心幾個(gè)常用的
        initdefault 用來指定系統(tǒng)啟動(dòng)后進(jìn)入的運(yùn)行級別,通常在/etc/inittab的第一條配置,如
    Quote:

            id:3:initdefault:


        這個(gè)說明默認(rèn)運(yùn)行級別是3,即多用戶模式,但是不啟動(dòng)X window的那種。
        sysinit 指定那些在系統(tǒng)啟動(dòng)時(shí)將被執(zhí)行的程序,例如
    Quote:

        si:S:sysinit:/etc/rc.d/rc.S
    [quote]
        在man inittab中提到,對于sysinit,boot等動(dòng)作,runlevels選項(xiàng)是不用管的,所以我們可以很容易解讀這條配置:它的意思是系統(tǒng)啟動(dòng)時(shí) 將默認(rèn)執(zhí)行/etc/rc.d/rc.S文件,在這個(gè)文件里你可直接或者間接的執(zhí)行你想讓系統(tǒng)啟動(dòng)時(shí)執(zhí)行的任何程序,完成系統(tǒng)的初始化。
        wait,當(dāng)進(jìn)入某個(gè)特別的運(yùn)行級別時(shí),指定的程序?qū)⒈粓?zhí)行一次,init將等到它執(zhí)行完成,例如
    [quote]
    rc:2345:wait:/etc/rc.d/rc.M


        這個(gè)說明無論是進(jìn)入運(yùn)行級別2,3,4,5中哪一個(gè),/etc/rc.d/rc.M將被執(zhí)行一次,并且有init等待它執(zhí)行完成。
        ctrlaltdel,當(dāng)init程序接收到SIGINT信號時(shí),某個(gè)指定的程序?qū)⒈粓?zhí)行,我們通常通過按下CTRL+ALT+DEL,這個(gè)默認(rèn)情況下將 給init發(fā)送一個(gè)SIGINT信號。如果我們想在按下這幾個(gè)鍵時(shí),系統(tǒng)重啟,那么可以在/etc/inittab中寫入,
    Quote:

    ca::ctrlaltdel:/sbin/shutdown -t5 -r now


        respawn,這個(gè)指定的進(jìn)程將被重啟,任何時(shí)候當(dāng)它退出時(shí)。這意味著你沒有辦法結(jié)束它,除非init自己結(jié)束了。例如,
    Quote:

        c1:1235:respawn:/sbin/agetty 38400 tty1 linux


        這一行的意思非常簡單,就是系統(tǒng)運(yùn)行在級別1,2,3,5時(shí),將默認(rèn)執(zhí)行/sbin/agetty程序(這個(gè)類似于上面提到的getty程序),這個(gè)程序非常有意思,就是無論什么時(shí)候它退出,init將再次啟動(dòng)它。這個(gè)有幾個(gè)比較有意思的問題:
        在slackware 12.0下,當(dāng)你把默認(rèn)運(yùn)行級別修改為4的時(shí)候,只有第6個(gè)終端可以用。原因是什么呢?因?yàn)轭愃粕厦娴呐渲茫驗(yàn)槟抢镏挥?235,而沒有4,這意味著當(dāng) 系統(tǒng)運(yùn)行在第4級別時(shí),其他終端下的/sbin/agetty沒有啟動(dòng)。所以,如果想讓其他終端都可以用,把1235修改為12345即可。
        另外一個(gè)有趣的問題就是:正是init程序在讀取這個(gè)配置行以后啟動(dòng)了/sbin/agetty,這就是我們的/sbin/agetty的秘密。
        還有一個(gè)問題:無論我們退出哪個(gè)終端,那個(gè)"XXX login:"總是會(huì)被打印,原因是respawn動(dòng)作有趣的性質(zhì),因?yàn)樗嬖Vinit,無論/sbin/agetty什么時(shí)候退出,重新把它啟動(dòng)起來, 那跟"XXX login:"有什么關(guān)系呢?從前面的內(nèi)容,我們發(fā)現(xiàn)正是/sbin/getty(同agetty)啟動(dòng)了/bin/login,而/bin/login 有啟動(dòng)了/bin/bash,即我們的命令行程序。
        而init程序作為“萬物之王”,它是所有進(jìn)程的“父”(也可能是祖父……)進(jìn)程,那意味著其他進(jìn)程最多只能是它的兒子進(jìn)程。而這個(gè)子進(jìn)程是怎么創(chuàng)建的, fork調(diào)用,而不是之前提到的execve調(diào)用。前者創(chuàng)建一個(gè)子進(jìn)程,后者則會(huì)覆蓋當(dāng)前進(jìn)程。因?yàn)槲覀儼l(fā)現(xiàn)/sbin/getty運(yùn)行時(shí),init并沒 有退出,因此可以判斷是fork調(diào)用創(chuàng)建一個(gè)子進(jìn)程后,才通過execve執(zhí)行了/sbin/getty。
        因此,我們可以總結(jié)出這么一個(gè)調(diào)用過程:
    Quote:

        fork   execve          execve         fork            execve 
    init --> init --> /sbin/getty --> /bin/login --> /bin/login --> /bin/bash


        這里的execve調(diào)用以后,后者將直接替換前者,因此當(dāng)我們鍵入exit退出/bin/bash以后,也就相當(dāng)于/sbin/getty都已經(jīng)結(jié)束了, 因此最前面的init程序判斷/sbin/getty退出了,又會(huì)創(chuàng)建一個(gè)子進(jìn)程把/sbin/getty啟動(dòng),進(jìn)而又啟動(dòng)了/bin/login,又看 到了那個(gè)"XXX login:"。
        通過ps和pstree命令看看實(shí)際情況是不是這樣,前者打印出進(jìn)程的信息,后者則打印出調(diào)用關(guān)系。
    Quote:

    $ ps -ef | egrep "/sbin/init|/sbin/getty|bash|/bin/login"
    root         1     0  0 21:43 ?        00:00:01 /sbin/init
    root      3957     1  0 21:43 tty4     00:00:00 /sbin/getty 38400 tty4
    root      3958     1  0 21:43 tty5     00:00:00 /sbin/getty 38400 tty5
    root      3963     1  0 21:43 tty3     00:00:00 /sbin/getty 38400 tty3
    root      3965     1  0 21:43 tty6     00:00:00 /sbin/getty 38400 tty6
    root      7023     1  0 22:48 tty1     00:00:00 /sbin/getty 38400 tty1
    root      7081     1  0 22:51 tty2     00:00:00 /bin/login --      
    falcon    7092  7081  0 22:52 tty2     00:00:00 -bash


        我們過濾了一些不相干的數(shù)據(jù)。從上面的結(jié)果可以看到,除了tty2被替換成/bin/login外,其他終端都運(yùn)行著/sbin/getty,說明終端2 上的進(jìn)程是/bin/login,它已經(jīng)把/sbin/getty替換掉,另外,我們看到-bash進(jìn)程的父進(jìn)程是7081剛好是/bin/login程 序,這說明/bin/login啟動(dòng)了-bash,但是它并沒有替換掉/bin/login,而是成為了/bin/login的子進(jìn)程,這說明 /bin/login通過fork創(chuàng)建了一個(gè)子進(jìn)程并通過execve執(zhí)行了-bash(后者通過strace跟蹤到)。而init呢,其進(jìn)程ID是1, 是/sbin/getty和/bin/login的父進(jìn)程,說明init啟動(dòng)或者間接啟動(dòng)了它們。下面通過pstree來查看調(diào)用樹,更清晰的看出上述關(guān) 系。
    Quote:

    $ pstree | egrep "init|getty|\-bash|login"
    init-+-5*[getty]
         |-login---bash
         |-xfce4-terminal-+-bash-+-grep


        結(jié)果顯示init是5個(gè)getty程序,login程序和xfce4-terminal的父進(jìn)程,而后兩者則是bash的父進(jìn)程,另外我們執(zhí)行的grep命令則在bash上運(yùn)行,是bash的子進(jìn)程,這個(gè)將是我們后面關(guān)心的問題。
        從上面的結(jié)果發(fā)現(xiàn),init作為所有進(jìn)程的父進(jìn)程,它的父進(jìn)程ID饒有興趣的是0,它是怎么被啟動(dòng)的呢?誰才是真正的“造物主”?

    1.2.4 誰啟動(dòng)了/sbin/init

        如果用過Lilo或者Grub這兩個(gè)操作系統(tǒng)引導(dǎo)程序,你可能會(huì)用到Linux內(nèi)核的一個(gè)啟動(dòng)參數(shù)init,當(dāng)你忘記密碼時(shí),可能會(huì)把這個(gè)參數(shù)設(shè)置成/bin/bash,讓系統(tǒng)直接進(jìn)入命令行,而無須輸入帳號和密碼,這樣就可以方便地登錄密碼修改掉。
        這個(gè)init參數(shù)是個(gè)什么東西呢?通過man bootparam會(huì)發(fā)現(xiàn)它的秘密,init參數(shù)正好指定了內(nèi)核啟動(dòng)后要啟動(dòng)的第一個(gè)程序,而如果沒有指定該參數(shù),內(nèi)核將依次查找/sbin/init, /etc/init, /bin/init, /bin/sh,如果找不到這幾個(gè)文件中的任何一個(gè),內(nèi)核就要恐慌(panic)了,并呆在那里一動(dòng)不動(dòng)了。
        因此/sbin/init就是Linux內(nèi)核啟動(dòng)的。而Linux內(nèi)核呢?是通過Lilo或者Grub等引導(dǎo)程序啟動(dòng)的,Lilo和Grub都有相應(yīng)的配 置文件,一般對應(yīng)/etc/lilo.conf和/boot/grub/menu.lst,通過這些配置文件可以指定內(nèi)核映像文件、系統(tǒng)根目錄所在分區(qū)、 啟動(dòng)選項(xiàng)標(biāo)簽等信息,從而能夠讓它們順利把內(nèi)核啟動(dòng)起來。
        那Lilo和Grub本身又是怎么被運(yùn)行起來的呢?還記得以前介紹的MBR不?MBR就是主引導(dǎo)扇區(qū),一般情況下這里存放著Lilo和Grub的代碼,而 誰知道正好是這里存放了它們呢?BIOS,如果你用光盤安裝過操作系統(tǒng)的話,那么應(yīng)該修改過BIOS的默認(rèn)啟動(dòng)設(shè)置,通過設(shè)置你可以讓系統(tǒng)從光盤、硬盤甚 至軟盤啟動(dòng)。正是這里的設(shè)置讓BIOS知道了MBR處的代碼需要被執(zhí)行。
        那BIOS又是什么時(shí)候被起來的呢?加電自檢就執(zhí)行到了這里。
        更多系統(tǒng)啟動(dòng)的細(xì)節(jié),看看 "man boot-scripts" 吧。

        到這里,/bin/bash的神秘面紗就被揭開了,它只是系統(tǒng)啟動(dòng)后運(yùn)行的一個(gè)程序而已,只不過這個(gè)程序可以響應(yīng)用戶的請求,那它到底是如何響應(yīng)用戶請求的呢?

    1.3 /bin/bash如何處理用戶鍵入的命令

    1.3.0 預(yù)備知識(shí)

        在執(zhí)行磁盤上某個(gè)程序時(shí),我們通常不會(huì)指定這個(gè)程序文件的絕對路徑,比如要執(zhí)行echo命令時(shí),我們一般不會(huì)輸入/bin/echo,而僅僅是輸入 echo。那為什么這樣bash也能夠找到/bin/echo呢?原因是Linux操作系統(tǒng)支持這樣一種策略:shell的一個(gè)環(huán)境變量PATH里頭存放 了程序的一些路徑,當(dāng)shell執(zhí)行程序時(shí)有可能去這些目錄下查找。which作為shell(這里特指bash)的一個(gè)內(nèi)置命令,如果用戶輸入的命令是 磁盤上的某個(gè)程序,它會(huì)返回這個(gè)文件的全路徑。

        有三個(gè)東西和終端的關(guān)系很大,那就是標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤,它們是三個(gè)文件描述符,一般對應(yīng)描述符0,1,2。在C語言程序里頭,我們可以把它們 當(dāng)作文件描述符一樣進(jìn)行操作。在命令行下,則可以使用重定向字符>,<等對它們進(jìn)行操作。對于標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤,都默認(rèn)輸出到終端,對于標(biāo) 準(zhǔn)輸入,也同樣默認(rèn)從終端輸入。

    1.3.1 哪種命令先被執(zhí)行

        在C語言里頭要寫一段輸入字符串的命令很簡單,調(diào)用scanf或者fgets就可以。這個(gè)在bash里頭應(yīng)該是類似的。但是它獲取用戶的命令以后,如何分析命令,如何響應(yīng)不同的命令呢?
        首先來看看bash下所謂的命令,用最常見的test來作測試。
    Quote:

    $ test1   #隨便鍵入一個(gè)字符串test1,bash發(fā)出響應(yīng),告訴我們找不到這個(gè)程序
    bash: test1: command not found
    $ test    #當(dāng)我們鍵入test的時(shí)候,看不到任何輸出,唯一的響應(yīng)是,新的命令提示符被打印了
    $ type test
    test is a shell builtin
    #查看test這個(gè)命令的類型,即查看test將被如何解釋,type告訴我們test是一個(gè)內(nèi)置命令,如果沒有理解錯(cuò),test應(yīng)該是利用諸如case "test": do something;break;這樣的機(jī)制實(shí)現(xiàn)的。
    $ which test   #通過which查到/usr/bin下有一個(gè)test命令文件,在鍵入test時(shí),到底哪一個(gè)被執(zhí)行了呢?
    /usr/bin/test
    $ /usr/bin/test   #執(zhí)行這個(gè)呢?也沒什么反應(yīng),到底誰先被執(zhí)行了?


        從上面的演示中發(fā)現(xiàn)一個(gè)問題?如果輸入一個(gè)命令,這個(gè)命令要么就不存在,要么可能同時(shí)是shell的內(nèi)置命令、也有可能是磁盤上環(huán)境變量PATH所指定的目錄下的某個(gè)程序文件。
        考慮到test內(nèi)置命令和/usr/bin/test命令的響應(yīng)結(jié)果一樣,我們無法知道哪一個(gè)先被執(zhí)行了,怎么辦呢?把/usr/bin/test替換成 一個(gè)我們自己的命令,并讓它打印一些信息(比如hello,world!),這樣我們就知道到底誰被執(zhí)行了。寫完程序,編譯好,命名為test放到 /usr/bin下(記得備份原來那個(gè))。開始測試:
    Quote:

    $ test #鍵入test,還是沒有效果
    $ /usr/bin/test   #而鍵入絕對路徑呢,則打印了hello, world!誒,那默認(rèn)情況下肯定是內(nèi)置命令先被執(zhí)行了
    hello, world!


        總結(jié):內(nèi)置命令比磁盤文件中的程序優(yōu)先被bash執(zhí)行

        下面看看更多有趣的東西,鍵盤鍵入的命令還有可能是什么呢?因?yàn)閎ash支持別名和函數(shù),所以還有可能是別名和函數(shù),另外,如果PATH環(huán)境變量指定的不同目錄下有相同名字的程序文件,那到底哪個(gè)被優(yōu)先找到呢?
        下面再作一些實(shí)驗(yàn),
    Quote:

    $ alias test="ls -l"   #把test命名為ls -l的別名
    $ test                 #再執(zhí)行test,竟然執(zhí)行了ls -l,而不是什么也沒有,說明alias比內(nèi)置命令更優(yōu)先
    total 9488
    drwxr-xr-x 12 falcon falcon    4096 2008-02-21 23:43 bash-3.2
    -rw-r--r--  1 falcon falcon 2529838 2008-02-21 23:30 bash-3.2.tar.gz
    $ function test { echo "hi, I'm a function"; }   #定義一個(gè)名叫test的函數(shù)
    $ test   #執(zhí)行一下,發(fā)現(xiàn),還是執(zhí)行了ls -l,說明function沒有alias優(yōu)先級高
    total 9488
    drwxr-xr-x 12 falcon falcon    4096 2008-02-21 23:43 bash-3.2
    -rw-r--r--  1 falcon falcon 2529838 2008-02-21 23:30 bash-3.2.tar.gz
    $ unalias test   #把別名給去掉
    $ test         #現(xiàn)在執(zhí)行的是函數(shù),說明函數(shù)的優(yōu)先級比內(nèi)置命令也要高
    hi, I'm a function
    $ builtin test #如果在命令之前跟上builtin,那么將直接執(zhí)行內(nèi)置命令
    $ unset test   #要去掉某個(gè)函數(shù)的定義,這樣就可以


        通過這個(gè)實(shí)驗(yàn)我們得到一個(gè)命令的別名(alias)、函數(shù)(function),內(nèi)置命令(builtin)和程序(program)的執(zhí)行優(yōu)先次序:
    Quote:

    先    alias --> function --> builtin --> program   后


        實(shí)際上,type命令會(huì)告訴我們這些細(xì)節(jié),type -a會(huì)按照bash解析的順序依次打印該命令的類型,而type -t則會(huì)給出第一個(gè)將被解析的命令的類型,之所以要做上面的實(shí)驗(yàn),是為了讓大家加印象。
    Quote:

    $ type -a test
    test is a shell builtin
    test is /usr/bin/test
    $ alias test="ls -l"
    $ function test { echo "I'm a function"; }
    $ type -a test
    test is aliased to `ls -l'
    test is a function
    test ()
    {
        echo "I'm a function"
    }
    test is a shell builtin
    test is /usr/bin/test
    $ type -t test
    alias


        下面再看看PATH指定的多個(gè)目錄下有同名程序的情況。再寫一個(gè)程序,打印“hi, world!”,以示和"hello, world!"的區(qū)別,放到PATH指定的另外一個(gè)目錄/bin下,為了保證測試的說服力,再寫一個(gè)放到另外一個(gè)叫/usr/local/sbin的目錄 下。
        先看看PATH環(huán)境變量,確保它有/usr/bin,/bin和/usr/local/sbin這幾個(gè)目錄,然后通過type -P(-P參數(shù)強(qiáng)制到PATH下查找,而不管是別名還是內(nèi)置命令等,可以通過help type查看該參數(shù)的含義)查看,到底哪個(gè)先被執(zhí)行。

    Quote:

    $ echo $PATH
    /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
    $ type -P test   #可以看到/usr/local/sbin下的先被找到
    /usr/local/sbin/test
    $ rm /usr/local/sbin/test #把/usr/local/sbin/test下的給刪除掉
    $ type -P test   #現(xiàn)在/usr/bin下的先被找到
    /usr/bin/test
    $ type -a test #type -a也顯示類似的結(jié)果
    test is aliased to `ls -l'
    test is a function
    test ()
    {
        echo "I'm a function"
    }
    test is a shell builtin
    test is /usr/bin/test
    test is /bin/test


        可以找出這么一個(gè)規(guī)律:shell從PATH列出的路徑中依次查找用戶輸入的命令。考慮到程序的優(yōu)先級最低,如果想優(yōu)先執(zhí)行磁盤上的程序文件test呢?那么就可以用test -P找出這個(gè)文件并執(zhí)行就可以了。

    補(bǔ)充:對于shell的內(nèi)置命令,可以通過help command的方式獲得幫助,對于程序文件,可以查看用戶手冊(當(dāng)然,這個(gè)需要安裝,一般叫做xxx-doc),man command。關(guān)于用戶手冊安裝辦法見在Linux下學(xué)會(huì)查看Man文檔

    1.3.2 這些特殊字符是如何解析的:|, >, <, &

        在命令行上,除了輸入各種命令以及一些參數(shù)外,比如上面type命令的各種參數(shù)-a, -P等,對于這些參數(shù),是傳遞給程序本身的,非常好處理,比如if,else條件分之或者switch, case都可以處理。當(dāng)然,在bash里頭可能使用專門的參數(shù)處理函數(shù)getopt和getopt_long來處理它們。
        而|,>,<,&等字符,則比較特別,shell是怎么處理它們的呢?它們也被傳遞給程序本身嗎?可我們的程序內(nèi)部一般都不處理這些字符的,所以應(yīng)該是shell程序自己解析了它們。
        先來看看這幾個(gè)字符在命令行的常見用法,
    Quote:

    $ cat < ./test.c  #<字符表示:把test.c文件重定向?yàn)闃?biāo)準(zhǔn)輸入,作為cat命令的輸入,而cat默認(rèn)把內(nèi)容輸出到標(biāo)準(zhǔn)輸出。
    #include <stdio.h>

    int main(void)
    {
            printf("hi, myself!\n");
            return 0;
    }
    $ cat < ./test.c > test_new.c #>表示把標(biāo)準(zhǔn)輸出重定向?yàn)槲募est_new.c,結(jié)果內(nèi)容輸出到test_new.c


        對于>,<,>>,<<,<>我們都稱之為重定向(redirect),shell到底是怎么進(jìn)行所謂的“重定向”的呢?
        這主要?dú)w功于dup/fcntl等函數(shù),它們可以實(shí)現(xiàn):復(fù)制文件描述符,讓多個(gè)文件描述符共享同一個(gè)文件表項(xiàng)。比如,當(dāng)把文件test.c重定向?yàn)闃?biāo)準(zhǔn)輸 入時(shí)。假設(shè)之前用以打開test.c的文件描述符是5,現(xiàn)在就把5復(fù)制為了0,這樣當(dāng)cat試圖從標(biāo)準(zhǔn)輸入讀出內(nèi)容時(shí),也就訪問了文件描述符5指向的文件 表項(xiàng),接著讀出了文件內(nèi)容。輸出重定向與此類似。其他的重定向,諸如>>, <<, <>等雖然和>,<的具體實(shí)現(xiàn)功能不太一樣,但本質(zhì)是一樣的,都是文件描述符的復(fù)制,只不過可能對文件操作有一些附加的限制,比 如>>在輸出時(shí)追加到文件末尾,而>則會(huì)從頭開始寫入文件,前者意味著文件的大小會(huì)增長,而后者則意味文件被重寫。

        那么|呢?"|"被形象地稱為“管道”,實(shí)際上它就是通過C語言里頭的無名管道來實(shí)現(xiàn)的。先看一個(gè)例子,

    Quote:

    $ cat < ./test.c  | grep hi
            printf("hi, myself!\n");



        在這個(gè)例子中,cat讀出了test.c文件中的內(nèi)容,并輸出到標(biāo)準(zhǔn)輸出上,但是實(shí)際上輸出的內(nèi)容卻只有一行,原因是這個(gè)標(biāo)準(zhǔn)輸出被“接到”了grep命令的標(biāo)準(zhǔn)輸入上,而grep命令只打印了包含“hi”字符串的一行。
        這是怎么被“接”上的。cat和grep作為兩個(gè)單獨(dú)的命令,它們本身沒有辦法把兩者的輸入和輸出“接”起來。這正是shell自己的“杰作”,它通過C 語言里頭的pipe函數(shù)創(chuàng)建了一個(gè)管道(一個(gè)包含兩個(gè)文件描述符的整形數(shù)組,一個(gè)描述符用于寫入數(shù)據(jù),一個(gè)描述符用于讀入數(shù)據(jù)),并且通過 dup/fcntl把cat的輸出復(fù)制到了管道的輸入,而把管道的輸出則復(fù)制到了grep的輸入。這真是一個(gè)奇妙的想法。
       
        那&呢?當(dāng)你在程序的最后跟上這個(gè)奇妙的字符以后就可以接著做其他事情了,看看效果,
    Quote:

    $ sleep 50 &   #讓程序在后臺(tái)運(yùn)行
    [1] 8261
    $ fg %1      #提示符被打印出來,可以輸入東西,讓程序到前臺(tái)運(yùn)行,無法輸入東西了,按下CTRL+Z,再讓程序到后臺(tái)運(yùn)行
    sleep 50

    [1]+  Stopped                 sleep 50
    $ fg %1   #再調(diào)到前臺(tái)
    sleep 50


        實(shí)際上&正是shell支持作業(yè)控制的表征,通過作業(yè)控制,用戶在命令行上可以同時(shí)作幾個(gè)事情(把當(dāng)前不做的放到后臺(tái),用&或者CTRL +Z或者bg)并且可以自由的選擇當(dāng)前需要執(zhí)行哪一個(gè)(用fg調(diào)到前臺(tái))。這在實(shí)現(xiàn)時(shí)應(yīng)該涉及到很多東西,包括終端會(huì)話(session)、終端信號、前 臺(tái)進(jìn)程、后臺(tái)進(jìn)程等。而在命令的后面加上&后,該命令將被作為后臺(tái)進(jìn)程執(zhí)行,后臺(tái)進(jìn)程是什么呢?這類進(jìn)程無法接收用戶發(fā)送給終端的信號(如 SIGHUP,SIGQUIT,SIGINT),無法響應(yīng)鍵盤輸入(被前臺(tái)進(jìn)程占用著),不過可以通過fg切換到前臺(tái)而享受作為前臺(tái)進(jìn)程具有的特權(quán)。
        因此,當(dāng)一個(gè)命令被加上&執(zhí)行后,shell必須讓它具有后臺(tái)進(jìn)程的特征,讓它無法響應(yīng)鍵盤的輸入,無法響應(yīng)終端的信號(意味忽略這些信號),并 且比較重要的是新的命令提示符得打印出來,并且讓命令行接口可以繼續(xù)執(zhí)行其他命令,這些就是shell對&的執(zhí)行動(dòng)作。

        還有什么神秘的呢?你也可以寫自己的shell了,并且可以讓內(nèi)核啟動(dòng)后就執(zhí)行它l,在lilo或者grub的啟動(dòng)參數(shù)上設(shè)置init= /path/to/your/own/shell/program就可以。當(dāng)然,也可以把它作為自己的登錄shell,只需要放到/etc/passwd 文件中相應(yīng)用戶名所在行的最后就可以。不過貌似到現(xiàn)在還沒介紹shell是怎么執(zhí)行程序,是怎樣讓程序變成進(jìn)程的,所以繼續(xù)。

    1.3.3 /bin/bash用什么魔法讓一個(gè)普通程序變成了進(jìn)程

        當(dāng)我們從鍵盤鍵入一串命令,shell奇妙的響應(yīng)了,對于內(nèi)置命令和函數(shù),shell自身就可以解析了(通過switch case之類的C語言語句)。但是,如果這個(gè)命令是磁盤上的一個(gè)文件呢。它找到該文件以后,怎么執(zhí)行它的呢?
        還是用strace來跟蹤一個(gè)命令的執(zhí)行過程看看。
    Quote:

    $ strace -f -o strace.log /usr/bin/test
    hello, world!
    $ cat strace.log | sed -ne "1p"   #我們對第一行很感興趣
    8445  execve("/usr/bin/test", ["/usr/bin/test"], [/* 33 vars */]) = 0


        從跟蹤到的結(jié)果的第一行可以看到bash通過execve調(diào)用了/usr/bin/test,并且給它傳了33個(gè)參數(shù)。這33個(gè)vars是什么呢?看看 declare -x的結(jié)果(這個(gè)結(jié)果只有32個(gè),原因是vars的最后一個(gè)變量需要是一個(gè)結(jié)束標(biāo)志,即NULL)。

    Quote:

    $ declare -x | wc -l   #declare -x聲明的環(huán)境變量將被導(dǎo)出到子進(jìn)程中
    32
    $ export TEST="just a test"   #為了認(rèn)證declare -x和之前的vars的個(gè)數(shù)的關(guān)系,再加一個(gè)
    $ declare -x | wc -l
    33
    $ strace -f -o strace.log /usr/bin/test   #再次跟蹤,看看這個(gè)關(guān)系
    hello, world!
    $ cat strace.log | sed -ne "1p"   
    8523  execve("/usr/bin/test", ["/usr/bin/test"], [/* 34 vars */]) = 0



        通過這個(gè)演示發(fā)現(xiàn),當(dāng)前shell的環(huán)境變量中被設(shè)置為export的變量被復(fù)制到了新的程序里頭。不過雖然我們認(rèn)為shell執(zhí)行新程序時(shí)是在一個(gè)新的 進(jìn)程里頭執(zhí)行的,但是strace并沒有跟蹤到諸如fork的系統(tǒng)調(diào)用(可能是strace自己設(shè)計(jì)的時(shí)候并沒有跟蹤fork,或者是在fork之后才跟 蹤)。但是有一個(gè)事實(shí)我們不得不承認(rèn):當(dāng)前shell并沒有被新程序的進(jìn)程替換,所以說shell肯定是先調(diào)用fork(也有可能是vfork)創(chuàng)建了一 個(gè)子進(jìn)程,然后再調(diào)用execve執(zhí)行新程序的。如果你還不相信,那么直接通過exec執(zhí)行新程序看看,這個(gè)可是直接把當(dāng)前shell的進(jìn)程替換掉的。

    Quote:

    exec /usr/bin/test


        應(yīng)該可以看到當(dāng)前shell“嘩”(聽不到,突然沒了而已)的一下就沒有了。
        下面來模擬一下shell執(zhí)行普通程序。multiprocess相當(dāng)于當(dāng)前shell,而/usr/bin/test則相當(dāng)于通過命令行傳遞給shell的一個(gè)程序。這里是代碼:



    Code:

    [Ctrl+A Select All]


        運(yùn)行看看,
    Quote:

    $ make multiprocess
    $ ./multiprocess
    child: my pid is 2251
    child: my parent's pid is 2250
    hello, world!
    parent: my pid is 2250
    parent: wait for my child exit successfully!


        從執(zhí)行結(jié)果可以看出,/usr/bin/test在multiprocess的子進(jìn)程中運(yùn)行并不干擾父進(jìn)程,因?yàn)楦高M(jìn)程一直等到了/usr/bin/test執(zhí)行完成。
        再回頭看看代碼,你會(huì)發(fā)現(xiàn)execlp并沒有傳遞任何環(huán)境變量信息給/usr/bin/test,到底是怎么把環(huán)境變量傳送過去的呢?通過man exec我們可以看到一組exec的調(diào)用,在里頭并沒有發(fā)現(xiàn)execve,但是通過man execve可以看到該系統(tǒng)調(diào)用。實(shí)際上exec的那一組調(diào)用都只是libc庫提供的,而execve才是真正的系統(tǒng)調(diào)用,也就是說無論使用exec調(diào)用 中的哪一個(gè),最終調(diào)用的都是execve,如果使用execlp,那么execlp將通過一定的處理把參數(shù)轉(zhuǎn)換為execve的參數(shù)。因此,雖然我們沒有 傳遞任何環(huán)境變量給execlp,但是默認(rèn)情況下,execlp把父進(jìn)程的環(huán)境變量復(fù)制給了子進(jìn)程,而這個(gè)動(dòng)作是在execlp函數(shù)內(nèi)部完成的。
        現(xiàn)在,總結(jié)一下execve,它有有三個(gè)參數(shù),
        第一個(gè)是程序本身的絕對路徑,對于剛才使用的execlp,我們沒有指定路徑,這意味著它會(huì)設(shè)法到PATH環(huán)境變量指定的路徑下去尋找程序的全路徑。
        第二個(gè)參數(shù)是一個(gè)將傳遞給被它執(zhí)行的程序的參數(shù)數(shù)組指針。正是這個(gè)參數(shù)把我們從命令行上輸入的那些參數(shù),諸如grep命令的-v等傳遞給了新程序,可以通過main函數(shù)的第二個(gè)參數(shù)char *argv[]獲得這些內(nèi)容。
        第三個(gè)參數(shù)是一個(gè)將傳遞給被它執(zhí)行的程序的環(huán)境變量,這些環(huán)境變量也可以通過main函數(shù)的第三個(gè)變量獲取,只要定義一個(gè)char *env[]就可以了,只是通常不直接用它罷了,而是通過另外的方式,通過extern char **environ全局變量(環(huán)境變量表的指針)或者getenv函數(shù)來獲取某個(gè)環(huán)境變量的值。
        當(dāng)然,實(shí)際上,當(dāng)程序被execve執(zhí)行后,它被加載到了內(nèi)存里,包括程序的各種指令、數(shù)據(jù)以及傳遞給它的各種參數(shù)、環(huán)境變量等都被存放在系統(tǒng)分配給該程序的內(nèi)存空間中。
        我們可以通過/proc/<pid>/maps把一個(gè)程序?qū)?yīng)的進(jìn)程的內(nèi)存映象看個(gè)大概。
    Quote:

    $ cat /proc/self/maps   #查看cat程序自身加載后對應(yīng)進(jìn)程的內(nèi)存映像
    08048000-0804c000 r-xp 00000000 03:01 273716     /bin/cat
    0804c000-0804d000 rw-p 00003000 03:01 273716     /bin/cat
    0804d000-0806e000 rw-p 0804d000 00:00 0          [heap]
    b7c46000-b7e46000 r--p 00000000 03:01 87528      /usr/lib/locale/locale-archive
    b7e46000-b7e47000 rw-p b7e46000 00:00 0
    b7e47000-b7f83000 r-xp 00000000 03:01 466875     /lib/libc-2.5.so
    b7f83000-b7f84000 r--p 0013c000 03:01 466875     /lib/libc-2.5.so
    b7f84000-b7f86000 rw-p 0013d000 03:01 466875     /lib/libc-2.5.so
    b7f86000-b7f8a000 rw-p b7f86000 00:00 0
    b7fa1000-b7fbc000 r-xp 00000000 03:01 402817     /lib/ld-2.5.so
    b7fbc000-b7fbe000 rw-p 0001b000 03:01 402817     /lib/ld-2.5.so
    bfcdf000-bfcf4000 rw-p bfcdf000 00:00 0          [stack]
    ffffe000-fffff000 r-xp 00000000 00:00 0          [vdso]


        關(guān)于程序加載和進(jìn)程內(nèi)存映像的更多細(xì)節(jié)請參考《C語言程序緩沖區(qū)注入分析》。

        到這里,關(guān)于命令行的秘密都被“曝光”了,可以開始寫自己的命令行解釋程序了。
        關(guān)于進(jìn)程的相關(guān)操作請參考《shell編程范例之進(jìn)程操作》。

    補(bǔ)充:上面沒有討論到一個(gè)比較重要的內(nèi)容,那就是即使execve找到了某個(gè)可執(zhí)行文件,如果該文件屬主沒有運(yùn)行該程序的權(quán)限,那么也沒有辦法運(yùn)行程序。可通過ls -l查看程序的權(quán)限,通過chmod添加或者去掉可執(zhí)行權(quán)限。

  • 文件屬主具有可執(zhí)行權(quán)限時(shí)才可以執(zhí)行某個(gè)程序
    Quote:

    whoami
    falcon
    $ ls -l hello  #查看用戶權(quán)限(第一個(gè)x表示屬主對該程序具有可執(zhí)行權(quán)限
    -rwxr-xr-x 1 falcon users 6383 2000-01-23 07:59 hello*
    $ ./hello
    Hello World
    $ chmod -x hello  #去掉屬主的可執(zhí)行權(quán)限
    $ ls -l hello
    -rw-r--r-- 1 falcon users 6383 2000-01-23 07:59 hello
    $ ./hello
    -bash: ./hello: Permission denied


       
    參考的資料:

    [1] man boot-scripts
    Linux啟動(dòng)過程
    [2] man bootparam
    Linux內(nèi)核啟動(dòng)參數(shù)
    [3] man 5 passwd
    [4] man shadow
    [5] "UNIX環(huán)境高級編程",進(jìn)程關(guān)系一章
    [6] 2006,2007 Summer School hold by DSLab
    http://dslab.lzu.edu.cn/docs/2007summerschool/index.html
    http://dslab.lzu.edu.cn/docs/2006summerschool/index.html
  • posted on 2008-03-14 15:27 隨意門 閱讀(3640) 評論(1)  編輯 收藏 引用

    評論

    # re: Linux命令行上程序執(zhí)行的那一剎那! 2014-09-02 01:08 chaosink

    寫的太好了,贊!
      回復(fù)  更多評論    

    只有注冊用戶登錄后才能發(fā)表評論。
    網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


    青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            欧美一区二区三区日韩| 国内一区二区三区在线视频| 亚洲福利在线观看| 亚洲国产欧美日韩精品| 国产精品av免费在线观看| 久久疯狂做爰流白浆xx| 香蕉国产精品偷在线观看不卡| 亚洲人www| 亚洲视频播放| 亚洲欧美激情一区| 欧美专区福利在线| 久久精品国产77777蜜臀| 久久精品一区| 久久激情综合网| 欧美永久精品| 久久精品毛片| 久久成人18免费网站| 久热成人在线视频| 免费成人av| 欧美夫妇交换俱乐部在线观看| 亚洲福利视频二区| 亚洲欧美欧美一区二区三区| 久久综合婷婷| 国产精品乱码一区二区三区| 国产嫩草一区二区三区在线观看 | 美女脱光内衣内裤视频久久影院| 久久久久久午夜| 欧美高清视频一区二区三区在线观看| 欧美暴力喷水在线| 一区二区三区四区在线| 免费成人av资源网| 国产精品一区在线观看你懂的| 亚洲国产成人不卡| 久久精品毛片| 亚洲一区日韩| 国产精品久久久久91| 9久草视频在线视频精品| 久久精品亚洲热| 亚洲欧美伊人| 亚洲免费观看高清在线观看| 久久一综合视频| 国产欧美日韩在线播放| 亚洲欧美国产精品专区久久| 亚洲免费观看高清完整版在线观看| 久久久欧美一区二区| 黄网动漫久久久| 欧美14一18处毛片| 免费成人性网站| 亚洲精品国产无天堂网2021| 亚洲国产小视频| 欧美精品一区二区在线观看| 国外精品视频| 亚洲人成在线观看一区二区| 欧美日韩四区| 久久久999| 欧美激情综合色综合啪啪| 欧美一区二区在线免费观看 | 亚洲国产精品日韩| 欧美国产日韩二区| 亚洲欧美激情诱惑| 久久综合九色九九| 亚洲午夜激情在线| 久久国产夜色精品鲁鲁99| 亚洲精选国产| 久久深夜福利免费观看| 亚洲一区二区三区精品动漫| 久久久噜噜噜久久人人看| 亚洲无线视频| 欧美成人久久| 免费一级欧美片在线播放| 欧美午夜宅男影院| 欧美成人免费一级人片100| 欧美日韩国产小视频在线观看| 久久久久久久久伊人| 国产伦精品一区二区三区| 亚洲乱亚洲高清| 洋洋av久久久久久久一区| 美女性感视频久久久| 麻豆精品在线观看| 激情丁香综合| 久久久久国产免费免费| 久久电影一区| 国产一区二区三区久久悠悠色av| 亚洲一区欧美二区| 欧美在线不卡| 精品9999| 欧美成人在线网站| 亚洲日本中文字幕区| 夜夜嗨av一区二区三区四季av| 欧美va天堂在线| 99成人精品| 国产日韩一区二区三区在线| 亚洲天天影视| 久久综合色一综合色88| 亚洲福利视频二区| 欧美精品午夜| 久久成人免费电影| 亚洲区第一页| 欧美亚洲专区| 在线性视频日韩欧美| 国产在线乱码一区二区三区| 另类酷文…触手系列精品集v1小说| 亚洲免费大片| 久久亚洲国产精品日日av夜夜| 1024国产精品| 国产精品入口福利| 欧美精品成人一区二区在线观看 | 久久偷看各类wc女厕嘘嘘偷窃| 欧美成人精品一区| 欧美亚洲一区在线| 亚洲午夜视频| 亚洲免费视频一区二区| 亚洲精品一区二区三区av| 狠狠色噜噜狠狠狠狠色吗综合| 国产精品久久毛片a| 国产精品国产精品国产专区不蜜| 免费在线欧美黄色| 美日韩在线观看| 久久性色av| 欧美黄色aa电影| 欧美日韩国产在线| 欧美日本在线播放| 欧美日本久久| 久久午夜视频| 在线中文字幕一区| 欧美日韩国产在线播放网站| 欧美精品日韩一区| 欧美日本国产在线| 亚洲人成人一区二区三区| 欧美性大战久久久久久久蜜臀| 亚洲国产精品久久久久秋霞蜜臀| 亚洲国产精品一区| 国产精品有限公司| 欧美xxx在线观看| 国产一区二区高清| 亚洲欧美国产精品桃花| 亚洲一区二区黄| 欧美日韩一级黄| 日韩午夜av电影| 亚洲精选视频免费看| 欧美二区在线观看| 亚洲国产国产亚洲一二三| 亚洲国产专区| 欧美国产第一页| 亚洲精品自在久久| 一区二区三区国产在线观看| 欧美日韩国产色综合一二三四| 亚洲精品无人区| 亚洲伊人网站| 国产色综合久久| 久久精品一区二区三区不卡| 欧美成人a视频| 一二三区精品福利视频| 欧美日韩一区综合| 亚洲性线免费观看视频成熟| 欧美专区在线播放| 在线精品福利| 欧美片在线播放| 亚洲专区一区| 美女主播一区| 亚洲视频axxx| 国产偷国产偷精品高清尤物| 久久综合色综合88| 99视频在线精品国自产拍免费观看| 亚洲天堂av在线免费观看| 国产午夜精品福利 | 国产欧美不卡| 性xx色xx综合久久久xx| 欧美成人一品| 亚洲一区二区三区精品在线观看 | 亚洲三级影片| 性做久久久久久| 亚洲高清视频在线观看| 国产精品爱啪在线线免费观看 | 亚洲欧美日韩国产一区二区三区 | 国产麻豆精品theporn| 久久精品日韩一区二区三区| 亚洲日本电影| 久久午夜精品一区二区| 夜夜精品视频一区二区| 国产欧美一区二区精品秋霞影院 | 欧美日韩国产一区二区三区地区| 亚洲人成在线播放| 国产精品久久久久久久久婷婷| 欧美专区日韩视频| 一级成人国产| 欧美激情一区二区三区在线视频 | 亚洲欧美美女| 亚洲美女诱惑| 欧美 日韩 国产精品免费观看| 亚洲欧美国产不卡| 99精品视频免费| 一区二区三区在线免费视频| 国产精品久久久久久妇女6080 | 欧美二区在线看| 久久精品国产一区二区三区| 亚洲欧美综合网| 欧美精品一区二区三| 久久久欧美精品| 欧美亚洲综合在线|