1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        一文帶你掌握Linux字符設(shè)備架構(gòu)

        共 17378字,需瀏覽 35分鐘

         ·

        2021-02-24 12:36


        一、Linux設(shè)備分類

        Linux系統(tǒng)為了管理方便,將設(shè)備分成三種基本類型:

        • 字符設(shè)備
        • 塊設(shè)備
        • 網(wǎng)絡(luò)設(shè)備

        字符設(shè)備:

        字符(char)設(shè)備是個能夠像字節(jié)流(類似文件)一樣被訪問的設(shè)備,由字符設(shè)備驅(qū)動程序來實(shí)現(xiàn)這種特性。字符設(shè)備驅(qū)動程序通常至少要實(shí)現(xiàn)open、close、read和write的系統(tǒng)調(diào)用。

        字符終端(/dev/console)和串口(/dev/ttyS0以及類似設(shè)備)就是兩個字符設(shè)備,它們能很好的說明“流”這種抽象概念。

        字符設(shè)備可以通過文件節(jié)點(diǎn)來訪問,比如/dev/tty1和/dev/lp0等。這些設(shè)備文件和普通文件之間的唯一差別在于對普通文件的訪問可以前后移動訪問位置,而大多數(shù)字符設(shè)備是一個只能順序訪問的數(shù)據(jù)通道。然而,也存在具有數(shù)據(jù)區(qū)特性的字符設(shè)備,訪問它們時可前后移動訪問位置。例如framebuffer就是這樣的一個設(shè)備,app可以用mmap或lseek訪問抓取的整個圖像。

        在/dev下執(zhí)行l(wèi)s -l ,可以看到很多創(chuàng)建好的設(shè)備節(jié)點(diǎn):

        字符設(shè)備文件(類型為c),設(shè)備文件是沒有文件大小的,取而代之的是兩個號碼:主設(shè)備號5 +次設(shè)備號1 。

        塊設(shè)備:

        和字符設(shè)備類似,塊設(shè)備也是通過/dev目錄下的文件系統(tǒng)節(jié)點(diǎn)來訪問。塊設(shè)備(例如磁盤)上能夠容納filesystem。在大多數(shù)的Unix系統(tǒng)中,進(jìn)行I/O操作時塊設(shè)備每次只能傳輸一個或多個完整的塊,而每塊包含512字節(jié)(或2的更高次冪字節(jié)的數(shù)據(jù))。

        Linux可以讓app像字符設(shè)備一樣地讀寫塊設(shè)備,允許一次傳遞任意多字節(jié)的數(shù)據(jù)。因此,塊設(shè)備和字符設(shè)備的區(qū)別僅僅在于內(nèi)核內(nèi)部管理數(shù)據(jù)的方式,也就是內(nèi)核及驅(qū)動程序之間的軟件接口,而這些不同對用戶來講是透明的。在內(nèi)核中,和字符驅(qū)動程序相比,塊驅(qū)動程序具有完全不同的接口。

        塊設(shè)備文件(類型為b):

        網(wǎng)絡(luò)設(shè)備:

        任何網(wǎng)絡(luò)事物都需要經(jīng)過一個網(wǎng)絡(luò)接口形成,網(wǎng)絡(luò)接口是一個能夠和其他主機(jī)交換數(shù)據(jù)的設(shè)備。接口通常是一個硬件設(shè)備,但也可能是個純軟件設(shè)備,比如回環(huán)(loopback)接口。

        網(wǎng)絡(luò)接口由內(nèi)核中的網(wǎng)絡(luò)子系統(tǒng)驅(qū)動,負(fù)責(zé)發(fā)送和接收數(shù)據(jù)包。許多網(wǎng)絡(luò)連接(尤其是使用TCP協(xié)議的連接)是面向流的,但網(wǎng)絡(luò)設(shè)備卻圍繞數(shù)據(jù)包的傳送和接收而設(shè)計(jì)。網(wǎng)絡(luò)驅(qū)動程序不需要知道各個連接的相關(guān)信息,它只要處理數(shù)據(jù)包即可。

        由于不是面向流的設(shè)備,因此將網(wǎng)絡(luò)接口映射到filesystem中的節(jié)點(diǎn)(比如/dev/tty1)比較困難。

        Unix訪問網(wǎng)絡(luò)接口的方法仍然是給它們分配一個唯一的名字(比如eth0),但這個名字在filesystem中不存在對應(yīng)的節(jié)點(diǎn)。內(nèi)核和網(wǎng)絡(luò)設(shè)備驅(qū)動程序間的通信,完全不同于內(nèi)核和字符以及塊驅(qū)動程序之間的通信,內(nèi)核調(diào)用一套和數(shù)據(jù)包相關(guān)的函數(shù)socket,也叫套接字。

        查看網(wǎng)絡(luò)設(shè)備使用命令ifconfig:

        二、字符設(shè)備架構(gòu)是如何實(shí)現(xiàn)的?

        在Linux的世界里面一切皆文件,所有的硬件設(shè)備操作到應(yīng)用層都會被抽象成文件的操作。我們知道如果應(yīng)用層要訪問硬件設(shè)備,它必定要調(diào)用到硬件對應(yīng)的驅(qū)動程序。Linux內(nèi)核中有那么多驅(qū)動程序,應(yīng)用層怎么才能精確的調(diào)用到底層的驅(qū)動程序呢?

        在這里我們字符設(shè)備為例,來看一下應(yīng)用程序是如何和底層驅(qū)動程序關(guān)聯(lián)起來的。必須知道的基礎(chǔ)知識:

        • 1.在Linux文件系統(tǒng)中,每個文件都用一個struct inode結(jié)構(gòu)體來描述,這個結(jié)構(gòu)體里面記錄了這個文件的所有信息,例如:文件類型,訪問權(quán)限等。

        • 2.在Linux操作系統(tǒng)中,每個驅(qū)動程序在應(yīng)用層的/dev目錄下都會有一個設(shè)備文件和它對應(yīng),并且該文件會有對應(yīng)的主設(shè)備號和次設(shè)備號。

        • 3.在Linux操作系統(tǒng)中,每個驅(qū)動程序都要分配一個主設(shè)備號,字符設(shè)備的設(shè)備號保存在struct cdev結(jié)構(gòu)體中。

        ?struct?cdev?{
        ????????struct?kobject?kobj;
        ????????struct?module?*owner;
        ????????const?struct?file_operations?*ops;//接口函數(shù)集合
        ????????struct?list_head?list;//內(nèi)核鏈表
        ????????dev_t?dev;????//設(shè)備號
        ????????unsigned?int?count;//次設(shè)備號個數(shù)
        ????};
        • 4.在Linux操作系統(tǒng)中,每打開一次文件,Linux操作系統(tǒng)在VFS層都會分配一個struct file結(jié)構(gòu)體來描述打開的這個文件。該結(jié)構(gòu)體用于維護(hù)文件打開權(quán)限、文件指針偏移值、私有內(nèi)存地址等信息。

        注意:

        常常我們認(rèn)為struct inode描述的是文件的靜態(tài)信息,即這些信息很少會改變。而struct file描述的是動態(tài)信息,即在對文件的操作的時候,struct file里面的信息經(jīng)常會發(fā)生變化。典型的是struct file結(jié)構(gòu)體里面的f_pos(記錄當(dāng)前文件的位移量),每次讀寫一個普通文件時f_ops的值都會發(fā)生改變。

        這幾個結(jié)構(gòu)體關(guān)系如下圖所示:

        通過上圖我們可以知道,如果想訪問底層設(shè)備,就必須打開對應(yīng)的設(shè)備文件。也就是在這個打開的過程中,Linux內(nèi)核將應(yīng)用層和對應(yīng)的驅(qū)動程序關(guān)聯(lián)起來。

        • 1.當(dāng)open函數(shù)打開設(shè)備文件時,可以根據(jù)設(shè)備文件對應(yīng)的struct inode結(jié)構(gòu)體描述的信息,可以知道接下來要操作的設(shè)備類型(字符設(shè)備還是塊設(shè)備)。還會分配一個struct file結(jié)構(gòu)體。

        • 2.根據(jù)struct inode結(jié)構(gòu)體里面記錄的設(shè)備號,可以找到對應(yīng)的驅(qū)動程序。這里以字符設(shè)備為例。在Linux操作系統(tǒng)中每個字符設(shè)備有一個struct cdev結(jié)構(gòu)體。此結(jié)構(gòu)體描述了字符設(shè)備所有的信息,其中最重要一項(xiàng)的就是字符設(shè)備的操作函數(shù)接口。

        • 3.找到struct cdev結(jié)構(gòu)體后,Linux內(nèi)核就會將struct cdev結(jié)構(gòu)體所在的內(nèi)存空間首地記錄在struct inode結(jié)構(gòu)體的i_cdev成員中。將struct cdev結(jié)構(gòu)體的中記錄的函數(shù)操作接口地址記錄在struct file結(jié)構(gòu)體的f_op成員中。

        • 4.任務(wù)完成,VFS層會給應(yīng)用層返回一個文件描述符(fd)。這個fd是和struct file結(jié)構(gòu)體對應(yīng)的。接下來上層的應(yīng)用程序就可以通過fd來找到strut file,然后在由struct file找到操作字符設(shè)備的函數(shù)接口了。

        三、字符驅(qū)動相關(guān)函數(shù)分析

        /**
        ?*?cdev_init()?-?initialize?a?cdev?structure
        ?*?@cdev:?the?structure?to?initialize
        ?*?@fops:?the?file_operations?for?this?device
        ?*
        ?*?Initializes?@cdev,?remembering?@fops,?making?it?ready?to?add?to?the
        ?*?system?with?cdev_add().
        ?*/

        void?cdev_init(struct?cdev?*cdev,?const?struct?file_operations?*fops)
        功能:
        ??初始化cdev結(jié)構(gòu)體
        參數(shù):
        ??@cdev?cdev結(jié)構(gòu)體地址
        ??@fops?操作字符設(shè)備的函數(shù)接口地址
        返回值:
        ??無
        /**
        ?*?register_chrdev_region()?-?register?a?range?of?device?numbers
        ?*?@from:?the?first?in?the?desired?range?of?device?numbers;?must?include
        ?*????????the?major?number.
        ?*?@count:?the?number?of?consecutive?device?numbers?required
        ?*?@name:?the?name?of?the?device?or?driver.
        ?*
        ?*?Return?value?is?zero?on?success,?a?negative?error?code?on?failure.
        ?*/
        ??????????????????????????????????????????????
        int?register_chrdev_region(dev_t?from,?unsigned?count,?const?char?*name)
        功能:
        ??注冊一個范圍()的設(shè)備號
        參數(shù):
        ??@from?設(shè)備號
        ??@count?注冊的設(shè)備個數(shù)
        ??@name?設(shè)備的名字
        返回值:
        ??成功返回0,失敗返回錯誤碼(負(fù)數(shù))
        /**
        ?*?cdev_add()?-?add?a?char?device?to?the?system
        ?*?@p:?the?cdev?structure?for?the?device
        ?*?@dev:?the?first?device?number?for?which?this?device?is?responsible
        ?*?@count:?the?number?of?consecutive?minor?numbers?corresponding?to?this
        ?*?????????device
        ?*
        ?*?cdev_add()?adds?the?device?represented?by?@p?to?the?system,?making?it
        ?*?live?immediately.??A?negative?error?code?is?returned?on?failure.
        ?*/

        int?cdev_add(struct?cdev?*p,?dev_t?dev,?unsigned?count)
        功能:
        ??添加一個字符設(shè)備到操作系統(tǒng)
        參數(shù):
        ??@p?cdev結(jié)構(gòu)體地址
        ??@dev?設(shè)備號
        ??@count?次設(shè)備號個數(shù)
        返回值:
        ??成功返回0,失敗返回錯誤碼(負(fù)數(shù))
        /**
        ?*?cdev_del()?-?remove?a?cdev?from?the?system
        ?*?@p:?the?cdev?structure?to?be?removed
        ?*
        ?*?cdev_del()?removes?@p?from?the?system,?possibly?freeing?the?structure
        ?*?itself.
        ?*/

        void?cdev_del(struct?cdev?*p)
        功能:
        ??從系統(tǒng)中刪除一個字符設(shè)備
        參數(shù):
        ??@p?cdev結(jié)構(gòu)體地址
        返回值:
        ??無
        static?inline?int?register_chrdev(unsigned?int?major,?const?char?*name,
        ??????????const?struct?file_operations?*fops)


        功能:
        ??注冊或者分配設(shè)備號,并注冊fops到cdev結(jié)構(gòu)體,
        ??如果major>0,功能為注冊該主設(shè)備號,
        ??如果major
        =0,功能為動態(tài)分配主設(shè)備號。
        參數(shù):
        ??@major?:?主設(shè)備號
        ??@name?:?設(shè)備名稱,執(zhí)行?cat?/proc/devices顯示的名稱
        ??@fops??:?文件系統(tǒng)的接口指針
        返回值
        ??如果major>0???成功返回0,失敗返回負(fù)的錯誤碼
        ??如果major=0??成功返回主設(shè)備號,失敗返回負(fù)的錯誤碼

        該函數(shù)實(shí)現(xiàn)了對cdev的初始化和注冊的封裝,所以調(diào)用該函數(shù)之后就不需要自己操作cdev了。

        相對的注銷函數(shù)為unregister_chrdev

        static?inline?void?unregister_chrdev(unsigned?int?major,?const?char?*name)

        四、如何編寫字符設(shè)備驅(qū)動

        參考上圖,編寫字符設(shè)備驅(qū)動步驟如下:

        1. 實(shí)現(xiàn)模塊加載和卸載入口函數(shù)

        module_init?(hello_init);
        module_exit?(hello_exit);

        2. 申請主設(shè)備號

        申請主設(shè)備號 ?(內(nèi)核中用于區(qū)分和管理不同字符設(shè)備)

        register_chrdev_region?(devno,?number_of_devices,?"hello");

        3. 創(chuàng)建設(shè)備節(jié)點(diǎn)

        創(chuàng)建設(shè)備節(jié)點(diǎn)文件 (為用戶提供一個可操作到文件接口--open()) 創(chuàng)建設(shè)備節(jié)點(diǎn)有兩種方式:手動方式創(chuàng)建,函數(shù)自動創(chuàng)建。手動創(chuàng)建:

        mknod?/dev/hello?c?250?0

        自動創(chuàng)建設(shè)備節(jié)點(diǎn)

        除了使用mknod命令手動創(chuàng)建設(shè)備節(jié)點(diǎn),還可以利用linux的udev、mdev機(jī)制,而我們的ARM開發(fā)板上移植的busybox有mdev機(jī)制,那么就使用mdev機(jī)制來自動創(chuàng)建設(shè)備節(jié)點(diǎn)。

        在etc/init.d/rcS文件里有一句:

        echo?/sbin/mdev?>?/proc/sys/kernel/hotplug

        該名命令就是用來自動創(chuàng)建設(shè)備節(jié)點(diǎn)。

        udev 是一個工作在用戶空間的工具,它能根據(jù)系統(tǒng)中硬件設(shè)備的狀態(tài)動態(tài)的更新設(shè)備文件,包括設(shè)備文件的創(chuàng)建,刪除,權(quán)限等。這些文件通常都定義在/dev 目錄下,但也可以在配置文件中指定。udev 必須有內(nèi)核中的sysfs和tmpfs支持,sysfs 為udev 提供設(shè)備入口和uevent 通道,tmpfs 為udev 設(shè)備文件提供存放空間。

        udev 運(yùn)行在用戶模式,而非內(nèi)核中。udev 的初始化腳本在系統(tǒng)啟動時創(chuàng)建設(shè)備節(jié)點(diǎn),并且當(dāng)插入新設(shè)備——加入驅(qū)動模塊——在sysfs上注冊新的數(shù)據(jù)后,udev會創(chuàng)新新的設(shè)備節(jié)點(diǎn)。

        注意,udev 是通過對內(nèi)核產(chǎn)生的設(shè)備文件修改,或增加別名的方式來達(dá)到自定義設(shè)備文件的目的。但是,udev 是用戶模式程序,其不會更改內(nèi)核行為。也就是說,內(nèi)核仍然會創(chuàng)建sda,sdb等設(shè)備文件,而udev可根據(jù)設(shè)備的唯一信息來區(qū)分不同的設(shè)備,并產(chǎn)生新的設(shè)備文件(或鏈接)。

        例如:

        如果驅(qū)動模塊可以將自己的設(shè)備號作為內(nèi)核參數(shù)導(dǎo)出,在sysfs文件中就有一個叫做uevent文件記錄它的值。

        由上圖可知,uevent中包含了主設(shè)備號和次設(shè)備號的值以及設(shè)備名字。

        在Linux應(yīng)用層啟動一個udev程序,這個程序的第一次運(yùn)行的時候,會遍歷/sys目錄,尋找每個子目錄的uevent文件,從這些uevent文件中獲取創(chuàng)建設(shè)備節(jié)點(diǎn)的信息,然后調(diào)用mknod程序在/dev目錄下創(chuàng)建設(shè)備節(jié)點(diǎn)。結(jié)束之后,udev就開始等待內(nèi)核空間的event。這個設(shè)備模型的東西,我們在后面再詳細(xì)說。這里大就可以這樣理解,在Linux內(nèi)核中提供了一些函數(shù)接口,通過這些函數(shù)接口,我們可在sysfs文件系統(tǒng)中導(dǎo)出我們的設(shè)備號的值,導(dǎo)出值之后,內(nèi)核還會向應(yīng)用層上報(bào)event。此時udev就知道有活可以干了,它收到這個event后,就讀取event對應(yīng)的信息,接下來就開始創(chuàng)建設(shè)備節(jié)點(diǎn)啦。

        如何創(chuàng)建一個設(shè)備類?

        第一步 :通過宏class_create() 創(chuàng)建一個class類型的對象;

        /*?This?is?a?#define?to?keep?the?compiler?from?merging?different
        ?*?instances?of?the?__key?variable?*/

        #define?class_create(owner,?name)????\
        ({????????????\
        ??static?struct?lock_class_key?__key;??\
        ??__class_create(owner,?name,?&__key);??\
        })


        參數(shù):
        ??@owner??THIS_MODULE
        ??@name???類名字
        返回值
        ??可以定義一個struct?class的指針變量cls接受返回值,然后通過IS_ERR(cls)判斷
        ??是否失敗,如果成功這個宏返回0,失敗返回非9值(可以通過PTR_ERR(cls)來獲得
        ??失敗返回的錯誤碼)

        在Linux內(nèi)核中,把設(shè)備進(jìn)行了分類,同一類設(shè)備可以放在同一個目錄下,該函數(shù)啟示就是創(chuàng)建了一個類,例如:

        第二步:導(dǎo)出我們的設(shè)備信息到用戶空間

        /**
        ?*?device_create?-?creates?a?device?and?registers?it?with?sysfs
        ?*?@class:?pointer?to?the?struct?class?that?this?device?should?be?registered?to
        ?*?@parent:?pointer?to?the?parent?struct?device?of?this?new?device,?if?any
        ?*?@devt:?the?dev_t?for?the?char?device?to?be?added
        ?*?@drvdata:?the?data?to?be?added?to?the?device?for?callbacks
        ?*?@fmt:?string?for?the?device's?name
        ?*
        ?*?This?function?can?be?used?by?char?device?classes.??A?struct?device
        ?*?will?be?created?in?sysfs,?registered?to?the?specified?class.
        ?*
        ?*?A?"dev"?file?will?be?created,?showing?the?dev_t?for?the?device,?if
        ?*?the?dev_t?is?not?0,0.
        ?*?If?a?pointer?to?a?parent?struct?device?is?passed?in,?the?newly?created
        ?*?struct?device?will?be?a?child?of?that?device?in?sysfs.
        ?*?The?pointer?to?the?struct?device?will?be?returned?from?the?call.
        ?*?Any?further?sysfs?files?that?might?be?required?can?be?created?using?this
        ?*?pointer.
        ?*
        ?*?Returns?&struct?device?pointer?on?success,?or?ERR_PTR()?on?error.
        ?*
        ?*?Note:?the?struct?class?passed?to?this?function?must?have?previously
        ?*?been?created?with?a?call?to?class_create().
        ?*/

        struct?device?*device_create(struct?class?*class,?struct?device?*parent,
        ???????????dev_t?devt,?void?*drvdata,?const?char?*fmt,?...)

        自動創(chuàng)建設(shè)備節(jié)點(diǎn)使用實(shí)例:

        static?struct?class?*cls;
        static?struct?device?*test_device;

        ??devno?=?MKDEV(major,minor);
        ??cls?=?class_create(THIS_MODULE,"helloclass");
        ??if(IS_ERR(cls))
        ??{
        ????unregister_chrdev(major,"hello");
        ????return?result;
        ??}
        ??test_device?=?device_create(cls,NULL,devno,NULL,"hellodevice");
        ??if(IS_ERR(test_device?))
        ??{
        ????class_destroy(cls);
        ????unregister_chrdev(major,"hello");
        ????return?result;
        ??}

        4 實(shí)現(xiàn)file_operations

        static?const?struct?file_operations?fifo_operations?=?{
        ????.owner?=???THIS_MODULE,
        ????.open?=???dev_fifo_open,
        ????.read?=???dev_fifo_read,
        ????.write?=???dev_fifo_write,
        ????.unlocked_ioctl?=???dev_fifo_unlocked_ioctl,
        };

        open、release對應(yīng)應(yīng)用層的open()、close()函數(shù)。實(shí)現(xiàn)比較簡單,

        直接返回0即可。其中read、write、unloched_ioctrl 函數(shù)的實(shí)現(xiàn)需要涉及到用戶空間 和內(nèi)存空間的數(shù)據(jù)拷貝。

        在Linux操作系統(tǒng)中,用戶空間和內(nèi)核空間是相互獨(dú)立的。也就是說內(nèi)核空間是不能直接訪問用戶空間內(nèi)存地址,同理用戶空間也不能直接訪問內(nèi)核空間內(nèi)存地址。

        如果想實(shí)現(xiàn),將用戶空間的數(shù)據(jù)拷貝到內(nèi)核空間或?qū)?nèi)核空間數(shù)據(jù)拷貝到用戶空間,就必須借助內(nèi)核給我們提供的接口來完成。

        1. read接口實(shí)現(xiàn)

        用戶空間-->內(nèi)核空間

        字符設(shè)備的write接口定義如下:

        ssize_t?(*write)(struct?file?*filp,?const?char?__user?*buf,?size_t?count,?loff_t?*f_pos);
        參數(shù):
        ??filp:待操作的設(shè)備文件file結(jié)構(gòu)體指針
        ??buf:待寫入所讀取數(shù)據(jù)的用戶空間緩沖區(qū)指針
        ??count:待讀取數(shù)據(jù)字節(jié)數(shù)
        ??f_pos:待讀取數(shù)據(jù)文件位置,寫入完成后根據(jù)實(shí)際寫入字節(jié)數(shù)重新定位
        返回:
        ??成功實(shí)際寫入的字節(jié)數(shù),失敗返回負(fù)值

        如果該操作為空,將使得write系統(tǒng)調(diào)用返回負(fù)EINVAL失敗,正常返回實(shí)際寫入的字節(jié)數(shù)。

        用戶空間向內(nèi)核空間拷貝數(shù)據(jù)需要使用copy_from_user函數(shù),該函數(shù)定義在arch/arm/include/asm/uaccess.h中。

        static?inline?int?copy_from_user(void?*to,?const?void?__user?volatile?*from,unsigned?long?n)
        參數(shù):
        ??to:目標(biāo)地址(內(nèi)核空間)
        ??from:源地址(用戶空間)
        ??n:將要拷貝數(shù)據(jù)的字節(jié)數(shù)
        返回:
        ??成功返回0,失敗返回沒有拷貝成功的數(shù)據(jù)字節(jié)數(shù)

        還可以使用get_user宏:

        int?get_user(data,?ptr);
        參數(shù):
        ??data:可以是字節(jié)、半字、字、雙字類型的內(nèi)核變量
        ??ptr:用戶空間內(nèi)存指針
        返回:
        ??成功返回0,失敗返回非0

        2. write接口實(shí)現(xiàn)

        內(nèi)核空間-->用戶空間

        字符設(shè)備的read接口定義如下:

        ssize_t?(*read)(struct?file?*filp,?char?__user?*buf,?size_t??count,?lofft?*f_pos);
        參數(shù):
        ??filp:?待操作的設(shè)備文件file結(jié)構(gòu)體指針
        ??buf:??待寫入所讀取數(shù)據(jù)的用戶空間緩沖區(qū)指針
        ??count:待讀取數(shù)據(jù)字節(jié)數(shù)
        ??f_pos:待讀取數(shù)據(jù)文件位置,讀取完成后根據(jù)實(shí)際讀取字節(jié)數(shù)重新定位
        ??__user :是一個空的宏,主要用來顯示的告訴程序員它修飾的指針變量存放的是用戶空間的地址。

        返回值:
        ??成功實(shí)際讀取的字節(jié)數(shù),失敗返回負(fù)值

        注意:如果該操作為空,將使得read系統(tǒng)調(diào)用返回負(fù)EINVAL失敗,正常返回實(shí)際讀取的字節(jié)數(shù)。

        用戶空間從內(nèi)核空間讀取數(shù)據(jù)需要使用copy_to_user函數(shù):

        ?static?inline?int?copy_to_user(void?__user?volatile?*to,?const?void?*from,unsigned?long?n)
        參數(shù):
        ??to:目標(biāo)地址(用戶空間)
        ??from:源地址(內(nèi)核空間)
        ??n:將要拷貝數(shù)據(jù)的字節(jié)數(shù)
        返回:
        ??成功返回0,失敗返回沒有拷貝成功的數(shù)據(jù)字節(jié)數(shù)
        在這里插入圖片描述

        還可以使用put_user宏:

        int?put_user(data,?prt)
        參數(shù):
        ??data:可以是字節(jié)、半字、字、雙字類型的內(nèi)核變量
        ??ptr:用戶空間內(nèi)存指針
        返回:
        ??成功返回0,?失敗返回非0

        這樣我們就可以實(shí)現(xiàn)read、write函數(shù)了,實(shí)例如下:

        ssize_t?hello_read?(struct?file?*filp,?char?*buff,???size_t?count,?loff_t?*offp)
        {
        ??ssize_t???result?=?0;

        ??if?(count???>?127)?
        ????count?=?127;

        ??if???(copy_to_user?(buff,?data,?count))
        ??{
        ????result?=???-EFAULT;
        ??}
        ??else
        ??{
        ????printk???(KERN_INFO?"wrote?%d?bytes\n",?count);
        ????result?=???count;
        ??}?
        ??return???result;
        }
        ssize_t?hello_write?(struct?file?*filp,const?char?*buf,?size_t?count,?loff_t?*f_pos)
        {
        ??ssize_t?ret???=?0;
        ??//printk???(KERN_INFO?"Writing?%d?bytes\n",?count);
        ??if?(count???>?127)?return?-ENOMEM;

        ??if???(copy_from_user?(data,?buf,?count))?{
        ????ret?=???-EFAULT;
        ??}
        ??else?{
        ????data[count]?=?'\0';
        ????printk???(KERN_INFO"Received:?%s\n",?data);
        ????ret?=???count;
        ??}
        ??return?ret;
        }

        3. unlocked_ioctl接口實(shí)現(xiàn)

        (1)為什么要實(shí)現(xiàn)xxx_ioctl ?

        前面我們在驅(qū)動中已經(jīng)實(shí)現(xiàn)了讀寫接口,通過這些接口我們可以完成對設(shè)備的讀寫。但是很多時候我們的應(yīng)用層工程師除了要對設(shè)備進(jìn)行讀寫數(shù)據(jù)之外,還希望可以對設(shè)備進(jìn)行控制。例如:針對串口設(shè)備,驅(qū)動層除了需要提供對串口的讀寫之外,還需提供對串口波特率、奇偶校驗(yàn)位、終止位的設(shè)置,這些配置信息需要從應(yīng)用層傳遞一些基本數(shù)據(jù),僅僅是數(shù)據(jù)類型不同。

        通過xxx_ioctl函數(shù)接口,可以提供對設(shè)備的控制能力,增加驅(qū)動程序的靈活性。

        (2)如何實(shí)現(xiàn)xxx_ioctl函數(shù)接口?

        增加xxx_ioctl函數(shù)接口,應(yīng)用層可以通過ioctl系統(tǒng)調(diào)用,根據(jù)不同的命令來操作dev_fifo。

        kernel 2.6.35 及之前的版本中struct file_operations 一共有3個ioctl :ioctl,unlocked_ioctl和compat_ioctl 現(xiàn)在只有unlocked_ioctl和compat_ioctl 了

        在kernel 2.6.36 中已經(jīng)完全刪除了struct file_operations 中的ioctl 函數(shù)指針,取而代之的是unlocked_ioctl 。

        · ? ? ? ? 2.6.36 之前的內(nèi)核

        long?(ioctl)?(struct?inode?node?,struct?file*?filp,?unsigned?int?cmd,unsigned?long?arg)

        · ? ? ? ? 2.6.36之后的內(nèi)核

        long?(*unlocked_ioctl)?(struct?file?*filp,?unsigned?int?cmd,?unsigned?long?arg)

        參數(shù)cmd: 通過應(yīng)用函數(shù)ioctl傳遞下來的命令

        先來看看應(yīng)用層的ioctl和驅(qū)動層的xxx_ioctl對應(yīng)關(guān)系:<1>應(yīng)用層ioctl參數(shù)分析

        int?ioctl(int?fd,?int?cmd,?...);
        參數(shù):
        @fd:打開設(shè)備文件的時候獲得文件描述符?
        @?cmd:第二個參數(shù):給驅(qū)動層傳遞的命令,需要注意的時候,驅(qū)動層的命令和應(yīng)用層的命令一定要統(tǒng)一
        @第三個參數(shù):?"..."在C語言中,很多時候都被理解成可變參數(shù)。
        返回值
        ???????成功:0
        ???????失?。?span style="color: #d19a66;line-height: 26px;">-1,同時設(shè)置errno

        小貼士:

        當(dāng)我們通過ioctl調(diào)用驅(qū)動層xxx_ioctl的時候,有三種情況可供選擇:

        1:?不傳遞數(shù)據(jù)給xxx_ioctl?
        2:?傳遞數(shù)據(jù)給xxx_ioctl,希望它最終能把數(shù)據(jù)寫入設(shè)備(例如:設(shè)置串口的波特率)
        3:?調(diào)用xxxx_ioctl希望獲取設(shè)備的硬件參數(shù)(例如:獲取當(dāng)前串口設(shè)備的波特率)
        這三種情況中,有些時候需要傳遞數(shù)據(jù),有些時候不需要傳遞數(shù)據(jù)。在C語言中,是
        無法實(shí)現(xiàn)函數(shù)重載的。那怎么辦?用"..."來欺騙編譯器了,"..."本來的意思是傳
        遞多參數(shù)。在這里的意思是帶一個參數(shù)還是不帶參數(shù)。

        參數(shù)可以傳遞整型值,也可以傳遞某塊內(nèi)存的地址,內(nèi)核接口函數(shù)必須根據(jù)實(shí)際情況
        提取對應(yīng)的信息。

        <2>驅(qū)動層xxx_ioctl參數(shù)分析

        long?(*unlocked_ioctl)?(struct?file?*file,?unsigned?int?cmd,?unsigned?long?arg);
        參數(shù):
        @file:???vfs層為打開字符設(shè)備文件的進(jìn)程創(chuàng)建的結(jié)構(gòu)體,用于存放文件的動態(tài)信息?
        @?cmd:?用戶空間傳遞的命令,可以根據(jù)不同的命令做不同的事情
        @第三個參數(shù):?用戶空間的數(shù)據(jù),主要這個數(shù)據(jù)可能是一個地址值(用戶空間傳遞的是一個地址),也可能是一個數(shù)值,也可能沒值
        返回值
        ???????成功:0
        ???????失敗:帶錯誤碼的負(fù)值

        <3>如何確定cmd 的值。

        該值主要用于區(qū)分命令的類型,雖然我只需要傳遞任意一個整型值即可,但是我們盡量按照內(nèi)核規(guī)范要求,充分利用這32bite的空間,如果大家都沒有規(guī)矩,又如何能成方圓?

        現(xiàn)在我就來看看,在Linux 內(nèi)核中這個cmd是如何設(shè)計(jì)的吧!

        具體含義如下:

        設(shè)備類型類型或叫幻數(shù),代表一類設(shè)備,一般用一個字母或者1個8bit的數(shù)字
        序列號代表這個設(shè)備的第幾個命令
        方 向表示是由內(nèi)核空間到用戶空間,或是用戶空間到內(nèi)核空間,入:只讀,只寫,讀寫,其他
        數(shù)據(jù)尺寸表示需要讀寫的參數(shù)大小

        由上可以一個命令由4個部分組成,每個部分需要的bite都不完全一樣,制作一個命令需要在不同的位域?qū)懖煌臄?shù)字,Linux 系統(tǒng)已經(jīng)給我們封裝好了宏,我們只需要直接調(diào)用宏來設(shè)計(jì)命令即可。

        在這里插入圖片描述

        通過Linux 系統(tǒng)給我們提供的宏,我們在設(shè)計(jì)命令的時候,只需要指定設(shè)備類型、命令序號,數(shù)據(jù)類型三個字段就可以了。

        Linux 系統(tǒng)中已經(jīng)設(shè)計(jì)了一場用的命令,可以通過查閱Linux 源碼中的Documentation/ioctl/ioctl-number.txt文件,看哪些命令已經(jīng)被使用過了。

        <4> 如何檢查命令?

        可以通過宏_IOC_TYPE(nr)來判斷應(yīng)用程序傳下來的命令type是否正確;

        可以通過宏_IOC_DIR(nr)來得到命令是讀還是寫,然后再通過宏access_ok(type,addr,size)來判斷用戶層傳遞的內(nèi)存地址是否合法。

        使用方法如下:

        ??if(_IOC_TYPE(cmd)!=DEV_FIFO_TYPE){
        ????pr_err("cmd???%u,bad?magic?0x%x/0x%x.\n",cmd,_IOC_TYPE(cmd),DEV_FIFO_TYPE);
        ????return-ENOTTY;
        ??}
        ??if(_IOC_DIR(cmd)&_IOC_READ)
        ????ret=!access_ok(VERIFY_WRITE,(void?__user*)arg,_IOC_SIZE(cmd));
        ??else?if(?_IOC_DIR(cmd)&_IOC_WRITE?)
        ????ret=!access_ok(VERIFY_READ,(void???__user*)arg,_IOC_SIZE(cmd));
        ??if(ret){
        ????pr_err("bad???access?%ld.\n",ret);
        ????return-EFAULT;
        ??}

        5 注冊cdev

        定義好file_operations結(jié)構(gòu)體,就可以通過函數(shù)cdev_init()、cdev_add()注冊字符設(shè)備驅(qū)動了。

        實(shí)例如下:

        static?struct?cdev?cdev;

        cdev_init(&cdev,&hello_ops);
        error?=?cdev_add(&cdev,devno,1);

        注意如果使用了函數(shù)register_chrdev(),就不用了執(zhí)行上述操作,因?yàn)樵摵瘮?shù)已經(jīng)實(shí)現(xiàn)了對cdev的封裝。

        五、實(shí)例

        千言萬語,全部匯總在這一個圖里,大家可以對照相應(yīng)的層次來學(xué)習(xí)。

        六、實(shí)例

        好了,現(xiàn)在我們可以來實(shí)現(xiàn)一個完整的字符設(shè)備框架的實(shí)例,包括打開、關(guān)閉、讀寫、ioctrl、自動創(chuàng)建設(shè)備節(jié)點(diǎn)等功能。

        #include?
        #include?
        #include?
        #include?
        #include?
        #include?
        #include?
        #include?"dev_fifo_head.h"

        //指定的主設(shè)備號
        #define???MAJOR_NUM?250

        //自己的字符設(shè)備
        struct?mycdev
        {

        ????int?len;
        ????unsigned???char?buffer[50];
        ????struct???cdev?cdev;
        };

        MODULE_LICENSE("GPL");
        //設(shè)備號
        static?dev_t???dev_num?=?{0};
        //全局gcd
        struct?mycdev?*gcd;
        //設(shè)備類
        struct?class?*cls;
        //獲得用戶傳遞的數(shù)據(jù),根據(jù)它來決定注冊的設(shè)備個數(shù)
        static?int?ndevices?=?1;
        module_param(ndevices,?int,?0644);
        MODULE_PARM_DESC(ndevices,?"The?number?of?devices?for?register.\n");

        //打開設(shè)備
        static?int?dev_fifo_open(struct???inode?*inode,???struct?file?*file)
        {
        ????struct???mycdev?*cd;??

        ????printk("dev_fifo_open???success!\n");??
        ????//用struct?file的文件私有數(shù)據(jù)指針保存struct?mycdev結(jié)構(gòu)體指針
        ????cd???=?container_of(inode->i_cdev,struct???mycdev,cdev);
        ????file->private_data?=???cd;??
        ????return???0;
        }

        //讀設(shè)備
        static?ssize_t???dev_fifo_read(struct?file?*file,?char???__user?*ubuf,???size_t
        size,?loff_t?*ppos)

        {
        ????int?n;
        ????int?ret;
        ????char???*kbuf;
        ????struct???mycdev?*mycd?=???file->private_data;

        ????printk("read?*ppos?:???%lld\n",*ppos);?

        ????if(*ppos?==?mycd->len)
        ????????return???0;

        ????//請求大大小?>?buffer剩余的字節(jié)數(shù)???:讀取實(shí)際記得字節(jié)數(shù)
        ????if(size?>?mycd->len?-?*ppos)
        ????????n?=?mycd->len?-?*ppos;
        ????else
        ????????n?=?size;

        ????printk("n?=???%d\n",n);
        ????//從上一次文件位置指針的位置開始讀取數(shù)據(jù)
        ????kbuf???=?mycd->buffer???+?*ppos;
        ????//拷貝數(shù)據(jù)到用戶空間
        ????ret???=?copy_to_user(ubuf,kbuf,?n);
        ????if(ret?!=?0)
        ????????return???-EFAULT;

        ????//更新文件位置指針的值
        ????*ppos?+=?n;
        ????printk("dev_fifo_read???success!\n");
        ????return???n;
        }
        //寫設(shè)備
        static?ssize_t???dev_fifo_write(struct?file?*file,?const?char?__user?*ubuf,size_t?size,?loff_t?*ppos)
        {
        ????int?n;
        ????int?ret;
        ????char???*kbuf;
        ????struct???mycdev?*mycd?=???file->private_data;

        ????printk("write?*ppos?:???%lld\n",*ppos);
        ????//已經(jīng)到達(dá)buffer尾部了
        ????if(*ppos?==?sizeof(mycd->buffer))
        ???????return???-1;
        ????//請求大大小?>?buffer剩余的字節(jié)數(shù)(有多少空間就寫多少數(shù)據(jù))
        ????if(size?>?sizeof(mycd->buffer)?-?*ppos)
        ????????n?=?sizeof(mycd->buffer)?-?*ppos;
        ????else
        ????????n?=?size;
        ????//從上一次文件位置指針的位置開始寫入數(shù)據(jù)

        ????kbuf???=?mycd->buffer???+?*ppos;
        ????//拷貝數(shù)據(jù)到內(nèi)核空間
        ????ret???=?copy_from_user(kbuf,?ubuf,?n);
        ????if(ret?!=?0)
        ????????return???-EFAULT;

        ????//更新文件位置指針的值
        ????*ppos?+=?n;
        ????//更新dev_fifo.len
        ????mycd->len?+=?n;
        ????printk("dev_fifo_write???success!\n");
        ????return???n;
        }

        //linux?內(nèi)核在2.6以后,已經(jīng)廢棄了ioctl函數(shù)指針結(jié)構(gòu),取而代之的是

        long???dev_fifo_unlocked_ioctl(struct?file?*file,???unsigned?int?cmd,
        ????unsigned???long?arg)

        {
        ??int?ret?=?0;
        ??struct?mycdev?*mycd???=?file->private_data;

        ??if(_IOC_TYPE(cmd)!=DEV_FIFO_TYPE){
        ????pr_err("cmd???%u,bad?magic?0x%x/0x%x.\n",cmd,_IOC_TYPE(cmd),DEV_FIFO_TYPE);
        ????return-ENOTTY;
        ??}
        ??if(_IOC_DIR(cmd)&_IOC_READ)
        ????ret=!access_ok(VERIFY_WRITE,(void?__user*)arg,_IOC_SIZE(cmd));
        ??else?if(?_IOC_DIR(cmd)&_IOC_WRITE?)
        ????ret=!access_ok(VERIFY_READ,(void???__user*)arg,_IOC_SIZE(cmd));
        ??if(ret){
        ????pr_err("bad???access?%ld.\n",ret);
        ????return-EFAULT;
        ??}?
        ????switch(cmd)
        ????{
        ??????case?DEV_FIFO_CLEAN:
        ?????????printk("CMD:CLEAN\n");
        ??????memset(mycd->buffer,?0,?sizeof(mycd->buffer));
        ?????????break;
        ??????case?DEV_FIFO_SETVALUE:
        ?????????printk("CMD:SETVALUE\n");
        ?????????mycd->len?=?arg;
        ?????????break;
        ??????case?DEV_FIFO_GETVALUE:
        ?????????printk("CMD:GETVALUE\n");
        ?????????ret???=?put_user(mycd->len,?(int?*)arg);
        ?????????break;
        ??????default:
        ?????????return???-EFAULT;
        ????}
        ????return???ret;
        }

        //設(shè)備操作函數(shù)接口

        static?const?struct?file_operations?fifo_operations?=?{
        ????.owner?=???THIS_MODULE,
        ????.open?=???dev_fifo_open,
        ????.read?=???dev_fifo_read,
        ????.write?=???dev_fifo_write,
        ????.unlocked_ioctl?=???dev_fifo_unlocked_ioctl,
        };
        //模塊入口
        int?__init?dev_fifo_init(void)
        {
        ????int?i?=?0;
        ????int?n?=?0;
        ????int?ret;

        ????struct???device?*device;
        ??gcd???=?kzalloc(ndevices???*?sizeof(struct???mycdev),?GFP_KERNEL);

        ????if(!gcd){
        ????????return???-ENOMEM;
        ????}

        ????//設(shè)備號?:?主設(shè)備號(12bit)?|?次設(shè)備號(20bit)
        ????dev_num???=?MKDEV(MAJOR_NUM,?0);
        ????//靜態(tài)注冊設(shè)備號
        ????ret???=?register_chrdev_region(dev_num,ndevices,"dev_fifo");
        ????if(ret?0){
        ????//靜態(tài)注冊失敗,進(jìn)行動態(tài)注冊設(shè)備號
        ?????ret???=alloc_chrdev_region(&dev_num,0,ndevices,"dev_fifo");
        ??????if(ret?0){
        ????????printk("Fail?to?register_chrdev_region\n");
        ????????goto???err_register_chrdev_region;
        ??????}
        ????}
        ????//創(chuàng)建設(shè)備類
        ????cls???=?class_create(THIS_MODULE,?"dev_fifo");
        ????if(IS_ERR(cls)){
        ????????ret???=?PTR_ERR(cls);
        ????????goto???err_class_create;
        ????}
        ????printk("ndevices?:???%d\n",ndevices);
        ????for(n?=?0;n?????{
        ??????//初始化字符設(shè)備
        ??????cdev_init(&gcd[n].cdev,&fifo_operations);
        ??????//添加設(shè)備到操作系統(tǒng)
        ??????ret???=?cdev_add(&gcd[n].cdev,dev_num?+?n,1);
        ??????if?(ret?0)
        ??????{
        ?????????goto???err_cdev_add;
        ??????}
        ?????//導(dǎo)出設(shè)備信息到用戶空間(/sys/class/類名/設(shè)備名)
        ??????device???=?device_create(cls,NULL,dev_num?+n,NULL,"dev_fifo%d",n);
        ??????if(IS_ERR(device)){
        ?????????ret???=?PTR_ERR(device);
        ?????????printk("Fail?to?device_create\n");
        ?????????goto???err_device_create;????
        ??????}
        ????}
        ????printk("Register???dev_fito?to?system,ok!\n");
        ????return???0;
        err_device_create:

        ????//將已經(jīng)導(dǎo)出的設(shè)備信息除去
        ????for(i?=?0;i?????{
        ???????device_destroy(cls,dev_num?+?i);????
        ????}
        err_cdev_add:
        ????//將已經(jīng)添加的全部除去
        ????for(i?=?0;i?????{
        ???????cdev_del(&gcd[i].cdev);
        ????}
        err_class_create:
        ????unregister_chrdev_region(dev_num,???ndevices);
        err_register_chrdev_region:
        ????return???ret;
        }
        void?__exit?dev_fifo_exit(void)
        {
        ????int?i;
        ????//刪除sysfs文件系統(tǒng)中的設(shè)備
        ????for(i?=?0;i?????{
        ????????device_destroy(cls,dev_num?+?i);????
        ????}
        ????//刪除系統(tǒng)中的設(shè)備類
        ????class_destroy(cls);
        ????//從系統(tǒng)中刪除添加的字符設(shè)備
        ????for(i?=?0;i?????{
        ???????cdev_del(&gcd[i].cdev);
        ????}?
        ????//釋放申請的設(shè)備號
        ????unregister_chrdev_region(dev_num,???ndevices);
        ????return;
        }
        module_init(dev_fifo_init);
        module_exit(dev_fifo_exit);???

        頭文件內(nèi)容:

        dev_fifo_head.h

        #ifndef?_DEV_FIFO_HEAD_H
        #define?_DEV_FIFO_HEAD_H
        #define?DEV_FIFO_TYPE?'k'
        #define?DEV_FIFO_CLEAN?_IO(DEV_FIFO_TYPE,0x10)
        #define?DEV_FIFO_GETVALUE?_IOR(DEV_FIFO_TYPE,0x11,int)
        #define?DEV_FIFO_SETVALUE?_IOW(DEV_FIFO_TYPE,0x12,int)
        #endif

        Makefile :

        ifeq?($(KERNELRELEASE),)
        KERNEL_DIR??=/lib/modules/$(shell?uname?-r)/build??
        PWD?:=$(shell?pwd)
        modules:
        ????$(MAKE)?-C?$(KERNEL_DIR)???M=$(PWD)?modules
        .PHONY:modules?clean
        clean:
        ????$(MAKE)?-C?$(KERNEL_DIR)???M=$(PWD)?clean
        else
        ????obj-m?:=?dev_fifo.o??
        endif

        應(yīng)用程序:

        #include?
        #include?
        #include?
        #include?
        #include?
        #include?

        int?main(int?argc,?const?char?*argv[])
        {
        ????int?fd?;
        ????int?n;
        ????char?buf[1024]?=?"hello???word";
        ????
        ????fd?=?open("/dev/dev_fifo0",O_RDWR);
        ????if(fd?0){
        ????????perror("Fail???ot?open");
        ????????return???-1;
        ????}
        ????printf("open???successful?,fd?=?%d\n",fd);
        ????n?=?write(fd,buf,strlen(buf));
        ????if(n?0){
        ????????perror("Fail???to?write");
        ????????return???-1;
        ????}
        ????printf("write???%d?bytes!\n",n);
        ????n?=?write(fd,buf,strlen(buf));
        ????if(n?0){
        ????????perror("Fail???to?write");
        ????????return???-1;
        ????}
        ????printf("write???%d?bytes!\n",n);
        ????return?0;
        }

        測試步驟:

        (1) ? 加載模塊

        sudo?insmod?hello.ko

        (2) ? 創(chuàng)建設(shè)備節(jié)點(diǎn)

        sudo?mknod?/dev/hello?c?250?0

        如果代碼中增加了自動創(chuàng)建設(shè)備節(jié)點(diǎn)的功能,這個步驟不要執(zhí)行。

        (3) ? 測試字符設(shè)備

        gcc?test.c?-o?run
        sudo?./run
        ?

        良許個人微信


        添加良許個人微信即送3套程序員必讀資料


        → 精選技術(shù)資料共享

        → 高手如云交流社群





        本公眾號全部博文已整理成一個目錄,請?jiān)诠娞柪锘貜?fù)「m」獲?。?/span>

        推薦閱讀:

        蘋果開源代碼中驚現(xiàn)“wechat”,老外注釋的吐槽亮了!

        Nginx 從入門到實(shí)踐,萬字詳解!

        17個在 Linux 運(yùn)維中定要掌握的實(shí)用技巧


        5T技術(shù)資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機(jī),樹莓派,等等。在公眾號內(nèi)回復(fù)「1024」,即可免費(fèi)獲?。?!


        瀏覽 37
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            日韩欧美在线中文| 性爱免费视频网站| 熟妇操逼视频| 亚洲成人网站视频| 黄片免费视频| 免费A视频| 无码免费一区| 91在线无码精品秘国产-百度| 毛片网站在线| 国产日韩欧美综合在线| 三级AV网站| 99免费小视频| 黄色福利网| 亚洲精品图片| 美女大吊,网站视频| 成人自拍偷拍视频| 无码中文av| 亭亭五月丁香| 人人操人人摸人人爱| 肏屄一区| 久久久久久黄色| 99国产综合| 国产一精品一aⅴ一免费| 波多野结衣高潮| 中文字幕中文字幕一区| 亚洲AV无码乱码| 青青草视频在线免费观看| 欧美日韩高清无码| 亚洲成人久久久| 成人丁香| 男人的天堂免费视频| 性BBW| 精品视频免费在线| 亚洲日本中文字幕在线| 久久天堂一区| 亚洲AV片一区二区三区| 成人性视频Aⅴ| 2025四虎在线视频观看| 91性爱视频| 一级片免费观看| 天天拍夜夜操| 乱伦三区| 国产精品美女毛片真酒店| 黄色视频网站在线观看免费| 春宵福利导航| 国产精品欧美一区二区三区苍井空| 91色秘乱码一区二区| 大鷄巴成人A片| 免费黄色福利视频| 在线se| 国产精品秘国产精品88| 久久久WWW成人免费无遮挡大片| 99性爱网| 欧美久色| 黄片网址在线观看| 熟妇高潮一区二区高潮| 成人亚洲性情网站www在线| 青草久在线| 日韩视频一二三| 影音先锋中文字幕资源| 99精品六月婷婷综合在线| 一级黄色大片| 丁香六月婷婷久久综合| 蜜桃传媒一区二区| 日本天堂Tv视频在线观看 | www.色欲av| 国产18欠欠欠一区二区| 影音先锋中文字幕资源| 日本少妇黄色视频| 免费一级婬片AAA片毛片A级| 美女做爱在线观看| 99久久爱re热6在播放| 欧美午夜精品一区二区蜜桃| 三级片青青草| 免费观看久久久| 91麻豆免费看| 少妇性视频| 亚洲vs天堂vs成人vs无码| 青草伊人网| 亚洲精品中文字幕在线| 999成人电影| 在线观看免费欧美操逼视频| 综合激情五月婷婷| 激情中文网| 丁香花中文字幕| 特黄A级毛片| 亚洲日韩毛片| 91久久久久久久久| 综合自拍偷拍| 欧美日韩成人网站| 精品丰满人妻一区二区三区免费观 | 精品国产久久久| 色情五月| 免费A级毛片| 亚洲成人中文字幕| 阿宾MD0165麻豆沈娜娜| A区性愛社区| 中文字幕黑人无码| 草逼A片| 久久综合婷婷| 国产精品久久7777777精品无码| 中文字幕丰满的翔田千里| 国产第七页| www.xxx国产| 成人做爰黄A片免费| 日韩AV无码成人精品| 91国内精品视频| 十八禁视频在线观看网站.www| 逼逼网| 思思热视频在线观看| 99在线精品视频免费观看20| 中文字幕一区二区三区四区在线视频 | 成人黄色大片| 成人毛片在线播放免费| 日韩一级爱爱| 日韩一级在线视频| 黄色视频在线观看| 亚洲天堂在线视频播放| 停停五月天| 免费中文字幕av| 手机在线观看AV| 福利视频二区| 一级特黄大片录像i| 俺去夜| 大香蕉久| 一本一道久久a久久精品综合| 亚洲免费在线看| 亚洲成人电影天堂| 97视频网站| 狠狠色噜噜狠狠狠7777| 一道本无码在线观看| 91丨九色丨老农村| 99精品人妻| 久久人妻熟女中文字幕av蜜芽| 草逼com| 韩国色情中文字幕| 99在线视频播放| 久久免费成人电影| 日韩欧美小电影| 少妇无码在线观看| 国产女人18毛片水18精| 亚洲操逼片| 夜夜国自一区| 91麻豆精品A片国产在线观看| 先锋影音男人| 狠狠91| 国产A级视频| 在线观看黄色网| 日韩成人无码人妻| 亚洲秘无码一区二区三区av| 日韩欧美一区二区三区| 麻豆疯狂做受XXXX高潮视频 | 色婷婷精品视频| 亚洲无码精品一区二区| 人妻熟女字幕一区二区| 在线播放中文字幕| 91人妻无码视频| 国产精品无码不卡| 国产精品精品精品| 911精品人妻一区二区三区A片 | 日韩欧美高清第一期| 亚洲一级黄色| 久久久精品黄色网址| 国产ts视频| 啪啪91| 免费中文字幕av| 亚洲AV免费看| 日本人妻视频| 亚洲一区二区网站| 国产一级A片在线观看| 免费看三级网站| 狠狠色av| 亚洲黄在线观看| 国产又大又粗| 激情网站在线观看| gogogo视频在线观看黑人| 超碰在线免费播放| 爱搞搞爱干干| 日本熟妇高潮BBwBBwBBw| 国产偷拍网站| 国产精品视频免费观看| 日韩大吊| 黄色一级视频在线观看| 亚洲黄色视频在线| 97久久精品国产熟妇高清网| 小佟丽娅大战91哥| 北条麻妃成人视频| 日本一级视频| 国产porn| 91乱伦| 操B视频在线| 88无码| 中文字幕免费高清网站| 狠狠操综合网| 亚洲无码电影视频| 成人激情四射网| 老熟女导航| 高清国产mv在线观看| 国产高清秘成人久久| 大香蕉亚洲网| 91大神在线观看入口| 午夜电影无码| 成人国产精品秘久久久网站| 高清无码做爱视频| 国产免费AV在线| 成人一级精品| 西西人体444www| 青春草在线免费视频| 麻豆二区| 波多野结衣高清无码| 国产一毛a一毛a在线观看| 亚洲欧洲精品视频| 青娱乐AV在线| 日本精品人妻无码77777| 亚洲一本色道中文无码| 九一亚洲精品| 国产二区三区| 国产黄色大片| 中国老女人日逼| 久久女人网| 久久草草热国产精品| 精品91美女| 人人操人人妻人人看| 天堂网中文| 欧美成人性爱网| 四虎在线视频观看96| 成人在线H| AV天堂无码| 精品成人| 先锋影音男人资源站| 天天看片天天爽| 双飞人妻13p| 国产在线一二三| 日韩精品在线视频观看| 欧美一道本| 国产又粗又长又硬黄色一级片| 国精品无码A区一区二区| 黄网免费观看| 99久久九九| 天天操大香蕉| 欧美肥臀| 亚洲在线看| 国产人妻人伦精品1国产丝袜| 丁香五月亚洲| 欧美一级一级| 国产AAA片| 大香蕉网伊人在线| 国产精品一二区| 西西www444无码大胆| 婷婷午夜精品久久久久久| 无码精品一区二区| 日韩高清久久| 91蜜桃传媒在线观看| 安徽妇搡BBB搡BBBB户外老太太| 成人女人18女人毛片| 国内自拍视频网| 成人做爰黄AAA片免费直播岛国| S28AV| 中文字幕第一页av| 欧美亚洲在线| 蜜桃影视| 三级片男人的天堂| 爱爱一区| 人与鲁牲交| 久久久精品午夜人成欧洲亚洲韩国 | 少妇搡BBBB搡BBB搡造水爽| 中文字幕淫乱视频欧美| 91逼站| 欧美国产日韩在线| 日屄免费视频| 99久久99久久精品免费看小说。 | 十八禁无码网站在线观看| 中文字幕色| 最新中文| 人人妻人人爱人人操| 六月婷婷深爱| 午夜在线免费视频| 91视频一区二区| 夜夜操夜夜撸| 国模一区二区| 五月天青青草超碰免费公开在线观看| 在线色| 99久久99九九99九九九| 爱搞在线观看wwww| 大鸡巴免费视频| 大香蕉精品在线| 毛片大香蕉| 在线有区别亚洲| 亚洲AV无码久| 三级片网站在线观看| 中文字幕av久久久久久欧洲尺码 | 黄色欧美视频| 日韩欧美大片在线观看| 欧美日韩一区在线| 操逼色| 大香久久| 亚洲黄色电影| 国产视频h| 无码中文av| 成人片网站在线观看| x88AV吊钟奶熟女| 午夜AV大片| 亚洲无码久久精品| 天堂一区二区三区| 2016超碰| 国产逼| 日本少妇中文字幕| 欧美一区不卡| av三级片在线播放| 久久久久9| 日本大胆中出| 91精品啪| 操东北老女人| 91综合视频在线播放| 黄色特级片| 亚洲视频精选| 99热黄| 91丨PORNY丨在线中文| 极品久久| 六月丁香五月天| 黄A网站| 色悠悠久久| 日韩黄色一级| 中文字幕欧美日韩| 3p视频网站| 国产综合第一页| 亚洲天堂AV在线观看| 日韩爱爱视频| 亚洲精品美女视频| 欧美老逼| 国产真人无码| 欧美久久网| 欧美va视频| 高清无码在线观看免费| 99精品一区二区三区| 亚洲黄色小视频| 插吧插吧综合网| 97黄片| 91成人毛片| 北条麻妃中文字幕在线观看| 中韩一区二区| 国产精品视频免费在线观看| 瘦精品无码一区二区三区四区五区六区七区八区 | 中文字幕无码A片久久| 久久与婷婷| 五月天乱伦网| 三根一起进菊眼| 少妇熟女视频| 久操免费在线视频| h在线网站| 亚洲无aV在线中文字幕| 91天天在线| 人人操人人摸人人| 蜜桃AV在线观看| 日韩精品人妻中文字幕有码| 操逼超碰| 99热视| 操操操无码| jizz亚洲| 97亚洲综合| 色逼高清| 北条麻妃在线中文字幕| 国产人成视频免费观看| 欧美中文日韩| 大香焦伊人国产| av在线一区二区| 高清无码直接看| 三级网址在线观看| 亚洲AV成人电影| 山东熟妇搡BBBB搡BBBB| 在线免费观看网站| 中文字幕观看av| 蜜芽成人网站| 人妻人人干| av无码电影| 在线一区观看| 五月丁香影院| www.日逼| 亚洲激情| 毛片毛片毛片毛片毛片| 精品亚洲一区二区三区| 四川乱子伦95视频国产| 2020无码| 日韩无码影院| 一级a免一级a做片免费| 国产精品18在线| 熟睡侵犯の奶水授乳在线| AV超碰| 老司机AV| 免费黄色一级视频| 日韩在线视频免费观看| 丰满人妻一区二区三区免费| 欧美一级夜夜爽| 秋霞中文字幕| av天堂一区| 成人网站在线免费| 中文字幕在线视频第一页| 日本的黄色视频| 亚洲性爱一级片| 在线观看一级片| 日韩精品免费| av一级| 两根茎一起进去好爽A片在线观看 日本三级AAA三级AAAA97 | 亚洲成人情趣大香蕉| 欧美性爱无码| 天堂色综合| 成人网肏逼视频| 秋霞午夜视频| 黄色三极片| 日韩色情片| 影音先锋男人资源网| 人妻少妇偷人精品无码免费| 日韩AV中文字幕在线| 人人上人人操| 亚洲人成在线观看| 亚洲高清成人| 国产一区视频在线| a在线免费观看| 免费无码网站| 欧美www| 欧美日韩综合| 精品无码一区二区三区在线| 九九免费视频| 性爱视频无码| 亚洲熟妇在线观看一区二区| 北条麻妃无码在线观看| 欧美精品日韩| 欧美日韩免费观看视频| 黄片伊人| 日韩欧美第一页| 操B视频在线免费观看| 欧美不卡在线| 天天干在线观看| 亚洲aaa在线| 五月天最新网址| 国产日女人| 久草资源视频| 夜夜爽久久精品91| 亚洲AV无码专区在线播放中文| 国产精品久久久久久久久A| 亚洲精品成人无码毛片| 色噜噜人妻av中文字幕| 日韩视频一二三| 另类日韩| 中文字幕免费AV| 1024国产在线| 免费看无码一级A片在线播放| 免费看黄色视频的网站| 特一级黄色电影| 亚洲一二三四| 日韩WWW| 国产乱子伦-区二区| 9I成人免费版| 热久在线| 肏屄在线观看| 欧美日韩免费在线播放电影在线播放电影在线播放电影免费 | 欧美中出| 免费国产在线视频| 精品一区二区三区av| 在线天堂视频| 欧美日韩成人一区二区三区| 男人操女人视频网站| 日韩天天干| 婷婷五月丁香激情| P站免费版-永久免费的福利视频平台| 韩国GOGOGO高清| 中文字幕国产在线| 婷婷伊人中文字幕| 丁香花中文字幕| 日韩精品免费| 天堂在线www| 亚洲瑟瑟| 天天玩夜夜玩天天玩国产99| 99伊人| 免费啪啪网| 色婷婷欧美在线播放内射| 91视频观看| 无码精品电影| 亚洲人免费视频| 污导航在线| 韩日在线| 亚洲无码观看视频| 超碰在线图片| 一级片黄色电影| 大鸡吧在线观看| 伊人大综合| 国产精品999999| 婷婷综合缴情亚洲另类在线| 国产一区二区AV| 午夜福利视频91| 日韩无码A| 黄色3A片在线观看| 女人的天堂av| 日韩性爱区| 激情网站在线| 久久草草热国产精| 超碰狠狠操| www.狠狠干| AV自拍| 中文字幕精品久久久久人妻红杏Ⅰ | 激情视频网站| 玖玖成人电影| 麻豆视频免费观看| 九九成人免费视频| 偷偷撸在线| 日本无码电影| 亚洲区成人777777精品| 欧美性生交18XXXXX无码| 日屄免费视频| 国产黄色录像| 99久久久久久久| 中国老女人性爱视频| 人人妻人人躁人人DVD| 亚洲高清无码免费在线观看| 欧美三P囗交做爰| 亚洲成人AV在线观看| 亚洲精品国产成人AV在线| 无码在线视频播放| 中文字幕你懂的在线三级| 免费观看av| 婷婷五月丁香激情| 天天干天天射天天爽| 最新中文字幕AV| 超碰天天干天天摸| 91九九| 婷婷五月天国产| 成人福利在线观看| 欧美级黑寡妇毛片app| 亚洲中文字幕在线观看免费| 麻豆三级电影| 五十路av| 国产精品视频免费在线观看| 18禁一区二区| 91视频电影| 最新AV在线播放| 久久久999| 精品av| 亚洲逼逼| 国产深喉视频| 天天干婷婷五月天| 国产免费乱伦| 婷婷五月综合久久中文字幕| 欧亚av| 91啦丨露脸丨熟女色啦| 三级片亚洲无码| 夜夜撸一撸| 超碰护士| 亚洲视频一区| 亚洲视频在线看| 黄色AV免费| 国产精品不卡一区二区三区| 大香蕉综合在线观看| 亚洲中文字幕免费观看视频| 天天玩夜夜玩天天玩国产99| 亚洲av电影在线观看| 国产一二三四区| 国产一级二级在线观看| 神马午夜视频| 免费一区视频| 精品乱子伦一区二区在线播放| 色综合视频| 看毛片网站| 精精品人妻一区二区三区| 丰满人妻精品一区二区在线| 婷婷五月开心五月| 免费一级AAAAA片在线播放| 天堂中文在线a| 999久久久久| 蕉久中文字慕| 久久精品6| 亚洲高清无码一区| 亚洲欧美日韩电影| 国产h在线| 天天干天天日| 国产A∨| 日少妇视频| 在线看片国产| 激情性爱婷婷色五月| 成人视频网站在线观看| 久久精品三级片| 久久精品9| 日本五十路熟女视频| 亚洲视频欧美视频| 欧美熟妇搡BBBB搡BBBBB| 国产最新福利| 欧洲无码一区二区三区| A片在线免费播放| 国产顶级理伦| 国产三级视频| 先锋影音在线资源| 无码av在线播放| 亚洲五月婷| 久久AV秘一区二区三区水生| AV无码免费一区二区三区不卡| 亚洲高清超级无码在线视频观看 | 亚洲日韩影院| JLZZJLZZ亚洲女人| 欧美成人福利| 韩日一区二区三区| 高潮喷水视频| 色色视频网| 草b在线| 婷婷色色五月| 婷婷二区| 噜噜噜网| 免费看三级网站| 操骚B| 日本成人不卡视频| 精品视频在线观看| 69无码| 欧美激情综合| 无码av在线观看| 色播五月婷婷| 九色影院| 五月婷婷丁香综合| 日韩成人无码人妻| 爱搞搞网| 精品国产污污免费网站入口| 亚色视频| 一级A片免费| 九九精品热播| 91香蕉视频免费在线观看| 99热日本| 精品一区二区三区四| 人人看,人人摸| 国产一区二区三区四区在线观看| 欧美日韩中| 91成人电影院| 少妇BBBB| 91破处网站| 日少妇视频| 四川性BBB搡BBB爽爽爽小说| 黄色片在线视频| 在线日韩一区二区| 超碰91免费在线观看| 黄色视频免费在线看| AV女优天堂| 色五月婷婷视频| 日韩欧美片| 欧美亚洲小说| 欧美激情xxx| 性欧美一区二区| 狼人综合影院| 亚洲欧美日韩在线| 四川少妇bbbb| 在线观看AⅤ| 五月天视频网| 欧美日韩国产在线| 97人妻一区二区精品免费视频| 人人av在线| 撸撸综合网| 欧美日韩在线观看一区二区三区| 18成人在线观看| 无码孕妇| 操人在线观看| 亚洲欧美日韩成人| 在线观看三级| 韩国无码视频在线观看| 日本精品视频一区二区| 99免费热视频在线| 一区免费在线| 成人在线三级片| 国产农村妇女精品一二区| 亚洲精品成人无码AV在线| 露脸老熟女91集合| 精品欧美乱码久久久久久| 日逼91| 国产精品夜夜爽7777777| av资源网站| 成人A片免费看| 黄网在线免费观看| 日本一级黃色大片看免费| 蜜桃久久99精品久久久酒店| 午夜18视频在线观看| 婷婷操逼| 婷婷激情五月| 亚洲天堂天天| 91re| 色色网站视频| 波多野结衣福利视频| 黄色国产视频在线观看| 有码视频在线观看| 精品少妇人妻一区二区| 日本中文字幕网站| 一级二级三级无码| 999国产精品| 操逼观看| 精品人妻一区二区三区-国产精品| 毛片一区二区三区| 操逼精品| 99热精品免费在线观看| 色色一级| 欧美天天性| 最近中文字幕中文翻译歌词| av网站免费在线观看| 黄色午夜| 无码在线观看免费视频| 丁香五月天AV| 久久免费视频观看| 亚洲无码在线播放| 欧美日韩国产在线观看| 中文字幕免费视频在线播放| 亚洲国产女人| 国产又黄又大又粗的视频| 国精品无码一区二区三区在线秋菊 | 牛牛在线视频| 99在线观看| 亚洲国产精品一区二区三区| 精品视频中文字幕| 久久久国产AV| 狠狠热视频| 国产成人精品麻豆| 特级西西444WWW视频| 少妇嫩搡BBBB搡BBBB| 尤物视频网址| 中文字幕++中文字幕明步| 午夜福利av在线| 操国产美女| 男女啪啪啪网站| 在线啊啊啊| 久久男人| 淫淫五月天| 国产免费AV片| 麻豆一区二区| 国产免费观看av| 性欧美日韩| 精品无码一区二区三区在线| 亚洲婷婷网| 岛国无码破解AV在线播放| 操一操影院| 在线黄网| 亚洲天堂av在线观看| 操B视频在线| 麻豆一区在线| 日韩一级片免费观看| 91蝌蚪视频在线观看| 99热66| 三级视频在线观看| 国产主播av| 黑种人配中国少妇HD| 中文字幕乱码无码人妻系列蜜桃| 中文字幕人妻系列| 在线少妇| 国产三级在线播放| 久久亚洲日韩天天做日日做综合亚洲 | 一级毛AA片| 午夜视频无码| 国产尤物| 亚洲免费黄色视频| 精品人妻一区二区免费蜜桃视频 | 天天插天天干| 色人阁人妻中文字幕| 女女女女女女BBBBBB手| 男人操女人免费网站| 亚洲综合成人网| 国产一区二区三区无码| 中文字幕有码在线| 国产黄色AV片| 一级片免费观看视频| 一级a免一级a做片免费| 一区二区三区电影| 中文字幕偷拍| 国产无套在线| 大香蕉九九| 色婷婷激情综合网| 日本毛片视频| 成人在线免费视频观看| 欧美日逼小视频| 操逼爆奶网站| 欧美成人福利在线观看| 亚洲精品无码中文字幕| 日韩精品一区二区三区使用方法| 丝袜制服中文字幕无码专区| 91久久精品一区二区三区| 男女av网站| 小泬BBBBBB免费看| 国产传媒自拍| 激情导航| AV电影在线免费观看| 中文字幕av免费观看| 激情视频国产| 黄色成人视频在线观看| 欧美性猛交XXXX乱大交| 屁屁影院CCYYCOM国产| 国产三级片精品| 无码视频一区| 黄色大片久草| 西西人体444www| 狠狠干婷婷| 日韩中出| 人人摸人人爱| 在线看一区二区三区| 在线免费观看黄色视频网站| 懂色av粉嫩av蜜臀av| 操老女人逼| 69av视频在线观看| 久草免费福利| 国产日韩欧美综合在线| 欧美一区二区在线| 亚洲va欧美va天堂v国产综合| 男人天堂视频在线| www.国产| 尤物视频网址| 蜜桃视频网站在线观看| 日本一级特黄大片AAAAA级| 站街大龄熟女x| 美女天天操| 午夜三级视频| 一区二区成人视频| 小黄片免费在线观看| 高清无码一区| 91成人三级| 囯产精品久久久久久久久免费无码 | 亚洲天堂精品在线观看| 国产18欠欠欠一区二区| 亚洲日本三级| 亚洲一区二区在线视频| 91人妻无码一区二区三区| 五月天丁香成人| 成人免费A片在线观看直播96 | 大香蕉视频在线观看| 免费的黄色片| 四川少妇bbbbbbbbb| 国产又粗又长的视频| 国产精品欧美综合在线| 亚洲3p| 高清无码波多野结衣| 99国产精品| 丁香花免费高清视频小说完整| 北条麻妃波多波多野结衣| 国产又粗又长的视频| 国外成人在线视频老鸭窝| av手机在线| 日韩免费观看视频| 欧美一级a| 久久久国产AV| 久操视频网站| 免费看操逼| 成人天天爽| 亚洲A网| 亚洲综合日韩在线| 爱爱高清视频| 69成人视频| 中文字幕第5页| 天天爽天天爽夜夜爽| 一本到免费视频| 污污污www精品国产网站| 亚洲最新视频| 极品少妇久久久| 欧美日韩成人视频| 最新人妻| 一级a片免费| 成人做爰黄AA片免费看三区| 91国黄色毛片在线观看| 欧美亚洲在线观看| 亚洲欧美日韩电影| 三级丁香在线| 北条麻妃青青久久| 美女天天肏| 色妞一區| 在线免费中文字幕| 日韩一级电影在线观看| 美日韩一区二区| 欧美日韩在线视频一区| 7777av| 肏逼在线观看| 开心五月激情网| 91视频一区二区三区| 伊人综合成人网| 2026国产精品视频| 欧美成人无码片免费看A片秀色| 老熟女--91XX| 国产精品无码专区AV免费播放| 午夜成人福利| AV手机天堂| 国产日韩欧美视频| 德美日三级片在线观看| 在线se| 一本一道无码| 毛片黄色片| 亚洲天堂高清| www.黄| PORNY九色视频9l自拍| 久一精品| 日本成人午夜福利| 中文字幕无码在线观看视频| 亚洲午夜视频在线观看| 日韩大吊| 激情五月天导航| 一区二区三区在线播放| 国产精品福利在线播放| 草逼小视频| 国际精品久久久| 欧美三级网站在线观看| 欧美性爱一区| 开心黄色网| 久久免费视频观看| 日韩丰满人妻| 中文字幕高清无码在线观看| 亚洲男人天堂网|