国产无码免费,人妻口爆,国产V在线,99中文精品7,国产成人无码AA精品一,制度丝袜诱惑av,久久99免费麻辣视频,蜜臀久久99精品久久久久久酒店
        訂閱
        糾錯
        加入自媒體

        Linux設備驅動統(tǒng)一模型解析

        2021-04-13 09:48
        一口Linux
        關注

        2. 設備樹解析流程

         2.1.內核啟動并獲取設備樹

        在uboot引導內核的時候,會將設備樹在物理內存中的物理起始內存地址傳遞給Linux內核,然后Linux內核在unflattern_device_tree中解析設備鏡像,并利用掃描到的信息創(chuàng)建由device node構成的鏈表,全局變量of_allnodes指向鏈表的根節(jié)點,設備樹的每一個節(jié)點都由一個struct device_node與之對應。unflatten_device_tree的意思是解開設備樹,在這個函數(shù)里調用了__unflatten_device_tree這一函數(shù):


        * __unflatten_device_tree - create tree of device_nodes from flat blob

        * unflattens a device-tree, creating the
        * tree of struct device_node. It also fills the "name" and "type"
        * pointers of the nodes so the normal device-tree walking functions
        * can be used.
        * @blob: The blob to expand
        * @m(xù)ynodes: The device_node tree created by the call
        * @dt_alloc: An allocator that provides a virtual address to memory
        * for the resulting tree

        static void __unflatten_device_tree(struct boot_param_header *blob,
               struct device_node **mynodes,
               void * (*dt_alloc)(u64 size, u64 align))

        所以,現(xiàn)在為止,我們得到了一個名為of_allnodes的struct *device_node,它指向了設備樹展開后的device_node樹,后續(xù)的操作都是基于device_node樹。

        2.2.創(chuàng)建platform_device

        內核從啟動到創(chuàng)建設備的過程大致如下:在do_initcalls中會傳遞level給do_initcall_level來調用不同層次的初始化函數(shù),level的對應關系見linux-3.10/include/linux/init.h 第196行。在這個初始化過程中,會調用一個customize_machine的函數(shù)。

        2.3.Platform driver注冊流程

        此節(jié)分析Platform driver的注冊流程,以memctrl驅動的注冊為例分析。關于系統(tǒng)調用驅動初始化函數(shù)的流程分析,參考自動初始化機制章節(jié)。本章節(jié)分析從設備驅動文件的xxx_init函數(shù)開始分析。

        2.3.1. struct platform_driver

        platform_driver是在device_driver之上的一層封裝,其結構如下:

        struct platform_driver {
        int (*probe)(struct platform_device *);   探測函數(shù)
        int (*remove)(struct platform_device *);  驅動卸載時執(zhí)行
        void (*shutdown)(struct platform_device *);  關機時執(zhí)行函數(shù)
        int (*suspend)(struct platform_device *, pm_message_t state);  掛起函數(shù)
        int (*resume)(struct platform_device *);     恢復函數(shù)
        struct device_driver driver;           管理的driver對象
        const struct platform_device_id *id_table;   匹配時使用
        };

        2.3.2. struct device_driver

        struct device_driver是系統(tǒng)提供的基本驅動結構:

        struct device_driver {
        const char   *name;  驅動名稱
        struct bus_type   *bus; 所屬總線
        struct module   *owner; 模塊擁有者
        const char   *mod_name; 內建的模塊使用
        bool suppress_bind_attrs;  是否綁定到sysfs
        const struct of_device_id  *of_match_table; 設備樹匹配表
        const struct acpi_device_id  *acpi_match_table; ACPI匹配表
        int (*probe) (struct device *dev);  探測設備
        int (*remove) (struct device *dev); 與設備脫離時調用
        void (*shutdown) (struct device *dev); 在關機時關閉設備
        int (*suspend) (struct device *dev, pm_message_t state); 使設備進入睡眠模式調用
        int (*resume) (struct device *dev);  喚醒設備時調用
        const struct attribute_group **groups; 自動創(chuàng)建的默認屬性組
        const struct dev_pm_ops *pm;  設備的功耗管理
        struct driver_private *p; 驅動的私有數(shù)據(jù)
        };

        2.3.3. platform_driver_register

        Platform_driver的注冊接口是platform_driver_register,其定義如下:

        int platform_driver_register(struct platform_driver *drv)

        drv->driver.bus = &platform_bus_type;  設置總線類型
        if (drv->probe)    確認定義了probe函數(shù)    
         drv->driver.probe = platform_drv_probe;  里面實際調用的是drv的probe函數(shù)
        if (drv->remove)
         drv->driver.remove = platform_drv_remove;
        if (drv->shutdown)
         drv->driver.shutdown = platform_drv_shutdown;
        return driver_register(&drv->driver);

        platform_driver_register接口是為注冊總線驅動做一些準備工作,定義了總線類型,設置了driver的部分接口,最后driver_register會向總線注冊驅動

        2.3.4. driver_registerint driver_register(struct device_driver *drv)

        int ret;
        struct device_driver *other;
        BUG_ON(!drv->bus->p);
        if ((drv->bus->probe && drv->probe) ||
            (drv->bus->remove && drv->remove) ||
            (drv->bus->shutdown && drv->shutdown))
         printk(KERN_WARNING "Driver '%s' needs updating - please use "
          "bus_type methods", drv->name);
        other = driver_find(drv->name, drv->bus); 檢查驅動是否已經注冊
        if (other) {
         printk(KERN_ERR "Error: Driver '%s' is already registered, "
          "aborting...", drv->name);
         return -EBUSY;

        ret = bus_add_driver(drv);   driver_register的主要工作放在了這里
        if (ret)
         return ret;
        ret = driver_add_groups(drv, drv->groups); 主要是在sysfs添加驅動屬性
        if (ret) {
         bus_remove_driver(drv);
         return ret;

        kobject_uevent(&drv->p->kobj, KOBJ_ADD);   涉及到uevent,暫時不分析
        return ret;

        2.3.5. bus_add_driver

        由以上分析可知,驅動的注冊,重點在bus_add_driver()函數(shù),它會向總線添加驅動:

        Drivers/base/bus.c
        int bus_add_driver(struct device_driver *drv)

        struct bus_type *bus;
        struct driver_private *priv;  包含與驅動相關的kobject和klist結構
        int error = 0;
        bus = bus_get(drv->bus);  獲取設備所屬的總線類型
        if (!bus)
         return -EINVAL;
        pr_debug("bus: '%s': add driver %s", bus->name, drv->name);
        priv = kzalloc(sizeof(*priv), GFP_KERNEL);
        if (!priv) {
         error = -ENOMEM;
         goto out_put_bus;

        klist_init(&priv->klist_devices, NULL, NULL);
        priv->driver = drv;
        drv->p = priv;
        priv->kobj.kset = bus->p->drivers_kset;
        error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
                "%s", drv->name);
        if (error)
         goto out_unregister;
        klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
        if (drv->bus->p->drivers_autoprobe) { 如果設置了自動探測
         error = driver_attach(drv);
         if (error)
          goto out_unregister;

        module_add_driver(drv->owner, drv);
        error = driver_create_file(drv, &driver_attr_uevent);
        if (error) {
         printk(KERN_ERR "%s: uevent attr (%s) failed",
          __func__, drv->name);

        error = driver_add_attrs(bus, drv);
        if (error) {
          How the hell do we get out of this pickle? Give up
         printk(KERN_ERR "%s: driver_add_attrs(%s) failed",
          __func__, drv->name);

        if (!drv->suppress_bind_attrs) {
         error = add_bind_files(drv);
         if (error) {
           Ditto
          printk(KERN_ERR "%s: add_bind_files(%s) failed",
           __func__, drv->name);
         }

        return 0;
        out_unregister:
        kobject_put(&priv->kobj);
        kfree(drv->p);
        drv->p = NULL;
        out_put_bus:
        bus_put(bus);
        return error;

        2.3.6. driver_attach

        driver_attach會嘗試綁定設備和驅動。編譯總線上的所有設備,然驅動挨個嘗試匹配,如果driver_probe_device()返回0且@dev->driver被設置,就代表找到了一對兼容的設備驅動。

        int driver_attach(struct device_driver *drv)

        return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);

        EXPORT_SYMBOL_GPL(driver_attach);
        2.3.7. __driver_attach

        對于每一個總線的設備,driver_attach都會調用__driver_attach來嘗試與驅動匹配。

        static int __driver_attach(struct device *dev, void *data)

        struct device_driver *drv = data;

         * Lock device and try to bind to it. We drop the error
         * here and always return 0, because we need to keep trying
         * to bind to devices and some drivers will return an error
         * simply if it didn't support the device.
         *
         * driver_probe_device() will spit a warning if there
         * is an error.
         
        if (!driver_match_device(drv, dev))  匹配設備和驅動,這里調用的是platform_match
         return 0;
        if (dev->parent)  Needed for USB
         device_lock(dev->parent);
        device_lock(dev);  設置互斥鎖,防止其他進程訪問設備資源
        if (!dev->driver)  
        如果設備沒有驅動,則為設備探測驅動,這個函數(shù)與注冊設備調用的是同一個函數(shù)
         driver_probe_device(drv, dev);  
        device_unlock(dev);
        if (dev->parent)
         device_unlock(dev->parent);
        return 0;

        driver_probe_device里調用really_probe函數(shù),并在really_probe中調用驅動文件中的probe函數(shù),對于memctrl驅動而言,就是xxxx_memctrl_probe函數(shù)。至此,platfprm driver就注冊好了。

        2.4.Platform Bus的匹配原則

        由以上的代碼分析得知,注冊platform device時,會調用__device_attach -> driver_match_device,注冊platform driver時,會調用__driver_attach -> driver_match_device,也就是說設備和驅動都會調用到這個函數(shù):

        static inline int driver_match_device(struct device_driver *drv,
                 struct device *dev)

        return drv->bus->match ? drv->bus->match(dev, drv) : 1;

        drv->bus->match,這是驅動綁定的總線提供的匹配函數(shù),這里注冊的是platform總線設備,而platform總線的定義參考3.2.6 platform_bus_type。Platform對應的match函數(shù)為:platform_match:

        static int platform_match(struct device *dev, struct device_driver *drv)

        struct platform_device *pdev = to_platform_device(dev);
        struct platform_driver *pdrv = to_platform_driver(drv);
         Attempt an OF style match first
        if (of_driver_match_device(dev, drv))
         return 1;
         Then try ACPI style match
        if (acpi_driver_match_device(dev, drv))
         return 1;
         Then try to match against the id table
        if (pdrv->id_table)
         return platform_match_id(pdrv->id_table, pdev) != NULL;
         fall-back to driver name match
        return (strcmp(pdev->name, drv->name) == 0);

        2.4.1. of_driver_match_device

        根據(jù)驅動的of_match_table判斷是否有驅動與之匹配。對memctrl驅動而言,其of_match_table如下:

        static struct of_device_id xxxx_memctrl_of_match[] = {
        { .compatible = "xxxx,memctrl", },
          {},
        };

        of_driver_match_device的執(zhí)行流程如下:

        所以重點應該在__of_match_node函數(shù):

        2.4.1.1. __of_match_nodestatic const struct of_device_id *__of_match_node(const struct of_device_id *matches, const struct device_node *node)

        if (!matches)
         return NULL;
        while (matches->name[0] || matches->type[0] || matches->compatible[0]) {
         int match = 1;
         if (matches->name[0])   查找名字
              match &= node->name && !strcmp(matches->name, node->name);
         if (matches->type[0])   查找類型
              match &= node->type && !strcmp(matches->type, node->type);
         if (matches->compatible[0])  查找屬性,檢測節(jié)點的compatible是否與驅動的一致
              match &= __of_device_is_compatible(node, matches->compatible);
         if (match)
              return matches;
             matches++;

        return NULL;

        3. 使用設備資源  

        4. 自動初始化機制 

        4.1.編譯到內核

        4.1.1. module_init宏展開

        Linux中每一個模塊都有一個module_init函數(shù),并且有且只有一個,其定義如下:


        * module_init() - driver initialization entry point
        * @x: function to be run at kernel boot time or module insertion

        * module_init() will either be called during do_initcalls() (if
        * builtin) or at module insertion time (if a module).  There can only
        * be one per module.

        #define module_init(x) __initcall(x);
        __initcall(x)定義如下:
        #define __initcall(fn) device_initcall(fn)

        device_initcall(fn)定義如下:

        #define device_initcall(fn)       __define_initcall(fn, 6)

        __define_initcall的定義如下:

        initcalls are now grouped by functionality into separate
        * subsections. Ordering inside the subsections is determined
        * by link order.
        * For backwards compatibility, initcall() puts the call in
        * the device init subsection.

        * The `id' arg to __define_initcall() is needed so that multiple initcalls
        * can point at the same handler without causing duplicate-symbol build errors.


        #define __define_initcall(fn, id)
        static initcall_t __initcall_##fn##id __used
        __attribute__((__section__(".initcall" #id ".init"))) = fn

        Initcalls現(xiàn)在按照功能分組到單獨的子部分。子部分內部的順序由鏈接順序決定。為了向后兼容,initcall()將調用放到device init小節(jié)中。需要定義initcall()的’id’參數(shù),以便多個initcall可以指向同一個處理程序,而不會導致重復符號構建錯誤。若不理解上述代碼的用法,可以參考__attribute__的section用法和C語言宏定義中#和##的用法。所以將__define_initcall展開將會是下面的內容:

        假設__define_initcall(led_init, 6)
        Static initcall_t __initcall_led_init6 __used
        __attribute__((__section__(".initcall6.init"))) = led_init

        即是定義了一個類型為initcall_t的函數(shù)指針變量__initcall_led_init6,并賦值為led_init,該變量在鏈接時會鏈接到section(.initcall6.init)。

        4.1.2. 鏈接腳本

        在linux3.10/arch/arm/kernel/vmlinux.lds.S中:

        ......
        SECTIONS   line 54

        ......
        .init.data : {  line 202
        #ifndef CONFIG_XIP_KERNEL
         INIT_DATA
        #endif
         INIT_SETUP(16)
         INIT_CALLS
         CON_INITCALL
         SECURITY_INITCALL
         INIT_RAM_FS

        ......

        在linux3.10/include/asm-generic/vmlinux.lds.h中:

        #define VMLINUX_SYMBOL(x) __VMLINUX_SYMBOL(x)
        #define __VMLINUX_SYMBOL(x) x
        ......  line 664
        #define INIT_CALLS_LEVEL(level)      
         VMLINUX_SYMBOL(__initcall##level##_start) = .;  
         *(.initcall##level##.init)    
         *(.initcall##level##s.init)    
        #define INIT_CALLS      
         VMLINUX_SYMBOL(__initcall_start) = .;  
         *(.initcallearly.init)    
         INIT_CALLS_LEVEL(0)    
         INIT_CALLS_LEVEL(1)    
         INIT_CALLS_LEVEL(2)    
         INIT_CALLS_LEVEL(3)    
         INIT_CALLS_LEVEL(4)    
         INIT_CALLS_LEVEL(5)    
         INIT_CALLS_LEVEL(rootfs)    
         INIT_CALLS_LEVEL(6)    
         INIT_CALLS_LEVEL(7)    
         VMLINUX_SYMBOL(__initcall_end) = .;
        ......

        所以 INIT_CALLS_LEVEL(6)會展開為:

        __initcall6_start = .;  *(.initcall6.init)   *(.initcall6s.init)

        所以__initcall_led_init6會鏈接到

        section(.initcall6.init)
        4.1.3. 初始化

        內核啟動流程為:

        do_initcall_level的主要內容如下:

        linux3.10/init/main.c line 744
        static void __init do_initcall_level(int level)

        .....
        for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
                 do_one_initcall(*fn);

        由代碼可知,內核會依次調用level段存儲的初始化函數(shù)。比如對于模塊來說level等于6。

        4.2.動態(tài)加載的模塊(.ko)4.2.1. Module_init展開

        如果設置為編譯成動態(tài)加載的模塊(.ko),module_init的展開形式與編譯到內核不一樣。

        Each module must use one module_init().
        #define module_init(initfn)    
        static inline initcall_t __inittest(void)     檢查定義的函數(shù)是否符合initcall_t類型
        { return initfn; }    
        int init_module(void) __attribute__((alias(#initfn)));

        alias屬性是GCC的特有屬性,將定義init_module為函數(shù)initfn的別名,所以module_init(initfn)的作用就是定義一個變量名 init_module,其地址和initfn是一樣的。

        4.2.2. *mod.c文件

        編譯成module的模塊都會自動產生一個*.mod.c的文件,例如:

        struct module __this_module
        __attribute__((section(".gnu.linkonce.this_module"))) = {
        .name = KBUILD_MODNAME,
        .init = init_module,
        #ifdef CONFIG_MODULE_UNLOAD
        .exit = cleanup_module,
        #endif
        .arch = MODULE_ARCH_INIT,
        };

        即定義了一個類型為module的全局變量__this_module,其成員.init就是上文由module_init定義的init_module變量。并且__this_module會被鏈接到section(".gnu.linkonce.this_module")。

        4.2.3. 動態(tài)加載

        insmod是busybox提供的用戶層命令:路徑busybox/modutils/ insmod.c

        insmod_main
        bb_init_module
        init_module

        路徑busybox/modutils/modutils.c:

        #define init_module(mod, len, opts) .
        syscall(__NR_init_module, mod, len, opts)該系統(tǒng)調用對應內核層的sys_init_module函數(shù)

        路徑:kernel/module.c

        SYSCALL_DEFINE3(init_module,…)
        //加載模塊的ko文件,并解釋各個section,重定位
        mod = load_module(umod, len, uargs);
        //查找section(".gnu.linkonce.this_module")
        modindex = find_sec(hdr, sechdrs, secstrings,".gnu.linkonce.this_module");
        //找到Hello_module.mod.c定義的module數(shù)據(jù)結構
        mod = (void *)sechdrs[modindex].sh_addr;
        if (mod->init != NULL)
        ret = do_one_initcall(mod->init); //調用initfn.
        4.3.__attribute__的section用法

        __define_initcall使用了gcc的 __attribute__眾多屬性中的section子項,其使用方式為:

        __attribute__((__section__("section_name")))

        其作用是將作用的函數(shù)或數(shù)據(jù)放入指定的名為”section_name”的段。

        4.4. C語言宏定義中#和##的用法4.4.1. 一般用法

        我們使用#把宏參數(shù)變?yōu)橐粋字符串。

        #define PRINT(FORMAT,VALUE)
        printf("The value of"#VALUE"is " FORMAT"",VALUE)

        調用:printf("%d",x+3);     -->     打印:The value of x+3 is 20

        這是因為”The value of”#VALUE”is ” FORMAT””實際上是包含了”The value of “,#VALUE,”is “,F(xiàn)ORMAT,”” 五部分字符串,其中VALUE和FORMAT被宏參數(shù)的實際值替換了。

        用##把兩個宏參數(shù)貼合在一起

        #define ADD_TO_SUM(sum_number,val) sum##sum_bumber+=(val)

        調用:ADD_TO_SUM(2,100);     -->     打印:sum2+=(100)

        需要注意的是凡宏定義里有用'#'或'##'的地方宏參數(shù)是不會再展開。

        4.4.2. '#'和'##'的一些應用特例合并匿名變量名#define  ___ANONYMOUS1(type, var, line)  type  var##line
        #define  __ANONYMOUS0(type, line)  ___ANONYMOUS1(type, _anonymous, line)
        #define  ANONYMOUS(type)  __ANONYMOUS0(type, __LINE__)

        例:ANONYMOUS(static int);  即 static int _anonymous70;  70表示該行行號;第一層:ANONYMOUS(static int); -->  __ANONYMOUS0(static int, LINE);第二層:                           -->  ___ANONYMOUS1(static int, _anonymous, 70);第三層:                           -->  static int  _anonymous70;即每次只能解開當前層的宏,所以__LINE__在第二層才能被解開;

        填充結構#define  FILL(a)   {a, #a}
        enum IDD{OPEN, CLOSE};
        typedef struct MSG{
         IDD id;
         const char  msg;
        }MSG;
        MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};

        相當于:

        MSG _msg[] = {{OPEN, OPEN},
                     {CLOSE, CLOSE}};
        記錄文件名#define  _GET_FILE_NAME(f)   #f
        #define  GET_FILE_NAME(f)    _GET_FILE_NAME(f)
        static char  FILE_NAME[] = GET_FILE_NAME(__FILE__);
        得到一個數(shù)值類型所對應的字符串緩沖大小#define  _TYPE_BUF_SIZE(type)  sizeof #type
        #define  TYPE_BUF_SIZE(type)   _TYPE_BUF_SIZE(type)
        char  buf[TYPE_BUF_SIZE(INT_MAX)];
            --  char  buf[_TYPE_BUF_SIZE(0x7fffffff)];
            --  char  buf[sizeof 0x7fffffff];

        這里相當于:

        char  buf[11];
        - END -


        <上一頁  1  2  3  
        聲明: 本文由入駐維科號的作者撰寫,觀點僅代表作者本人,不代表OFweek立場。如有侵權或其他問題,請聯(lián)系舉報。

        發(fā)表評論

        0條評論,0人參與

        請輸入評論內容...

        請輸入評論/評論長度6~500個字

        您提交的評論過于頻繁,請輸入驗證碼繼續(xù)

        暫無評論

        暫無評論

          人工智能 獵頭職位 更多
          掃碼關注公眾號
          OFweek人工智能網(wǎng)
          獲取更多精彩內容
          文章糾錯
          x
          *文字標題:
          *糾錯內容:
          聯(lián)系郵箱:
          *驗 證 碼:

          粵公網(wǎng)安備 44030502002758號

          主站蜘蛛池模板: 精品成人av| 凌海市| 99r久久| 无码人妻一区二区三区线花季传件| 丁香五月激情图片| 伊人网视频| 91亚洲色图| 91在现观看| 尤物一区| 91pao| 99视频精品3| 伊人在线免费观看| 欧美精品XXX| 久久精品国产亚洲AV久| 国产黄色视频大全| 人操人人| 国产无码VA| 亚洲123区| 精品人妻V| 岛国免费AV| www97| 538av| 第一福利精品导航| 精品人妻伦九区久久AAA片| 国产又大又粗| 中文字幕有码无码| 欧美A∨| 亚洲成人在线网址| 91亚瑟视频| 剑川县| 绍兴市| 亚州脚交| 丹凤县| 万全县| jizz免费| freeAV爽视频| 岳乳丰满一区二区三区| 免费jizz| 久久99人妻无码精品一区| 成人硅胶娃做爰无码www| 精品123区|