linux驱动加载 动态加载 静态加载 自动加载

您所在的位置:网站首页 加载的意思 linux驱动加载 动态加载 静态加载 自动加载

linux驱动加载 动态加载 静态加载 自动加载

2024-03-14 10:04| 来源: 网络整理| 查看: 265

概述

在咱们工作中,编译驱动,加载驱动时最常见的事。但是内核是如何加载驱动的,有些事编译到内核里面,有些事编译成ko,有些还能放到root下,让系统自动加载。

总的说来,在Linux下可以通过两种方式加载驱动程序:静态加载和动态加载。

静态加载就是把驱动程序直接编译进内核,系统启动后可以直接调用。动态加载利用了Linux的module特性,可以在系统启动后用insmod命令添加模块(.ko),在不需要的时候用rmmod命令卸载模块。

驱动加载 静态加载过程

将模块的程序编译到Linux内核中,也就是咱们在编译内核时选择Y的模块,静态由do_initcall函数加载。先来看看initcall在哪里:

核心进程(/init/main.c)

start_kernel()//内核启动的入口,负责初始化调度,中断,内存,最后启动1号进程 和2号进程

     rest_init()

          kernel_init()//这是谁? linux 1号进程,top一下系统就能找到1号进程了。

              do_basic_setup()

                    do_initcalls()

do_initcalls中会定义的各个模块加载顺序,加载顺序分为16个等级,加载时按照16个等级依次加载内核驱动。关于每个等级的定义参考/include/linux/init.h。举个例子,在2.6.24的内核 中:gianfar_device使用的是arch_initcall,而gianfar_driver使用的是module_init,因为 arch_initcall的优先级大于module_init,所以gianfar设备驱动的device先于driver在总线上添加。init.h 截取如下:

#define pure_initcall(fn)                __define_initcall(fn, 0)

#define core_initcall(fn)                __define_initcall(fn, 1)

#define core_initcall_sync(fn)                __define_initcall(fn, 1s)

#define postcore_initcall(fn)                __define_initcall(fn, 2)

#define postcore_initcall_sync(fn)        __define_initcall(fn, 2s)

#define arch_initcall(fn)                __define_initcall(fn, 3)

#define arch_initcall_sync(fn)                __define_initcall(fn, 3s)

#define subsys_initcall(fn)                __define_initcall(fn, 4)

#define subsys_initcall_sync(fn)        __define_initcall(fn, 4s)

#define fs_initcall(fn)                        __define_initcall(fn, 5)

#define fs_initcall_sync(fn)                __define_initcall(fn, 5s)

#define rootfs_initcall(fn)                __define_initcall(fn, rootfs)

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

#define device_initcall_sync(fn)        __define_initcall(fn, 6s)

#define late_initcall(fn)                __define_initcall(fn, 7)

#define late_initcall_sync(fn)                __define_initcall(fn, 7s)

动态加载过程

将模块的程序编译成KO,也就是咱们在编译内核时选择M的模块,通过insmod

、modprobe 或者udev动态加载到内核中。 insmod加绝对路径/××.ko,而modprobe ××即可,不用加.ko或.o后缀,也不用加路径;最重要的一点是:modprobe同时会加载当前模块所依赖的其它模块。

来看看insmod都做了什么:

Insmod

    insmod_main

        bb_init_module

            init_module

                sys_init_module(进入系统调用)

                      sys_init_module()系统调用会调用module_init指定的函数进行模块的初始化,至此ko加载完成。

驱动匹配 硬件固件设计

以PCI为例,所有PCI硬件上必须设计一系列寄存器,PCI通过这些寄存器获取硬件信息,称为PCI配置空间,PCI配置空间格式如下:

PCI标准规定每个设备的配置寄存器组最多可以有256字节的连续空间,其中开头的64字节的用途和格式是标准的,成为配置寄存器组的“头部”,这样的头部又有两种,“0型”头部用于一般的PCI设备,“1型”头部用于PCI桥,无论是“0型”还是“1型”,其开头的16个字节的用途和格式是共同的。其中Device ID 和 Vendor ID 位于前4个字节。

在系统中运行LSPCI -NN,会得到类似如下输出:

 Ethernet controller [0200]: Intel Corporation 82545EM Gigabit Ethernet Controller (Copper) [8086:100f] (rev 01)

其中的8086是vendor ID,100f是Device ID。所以咱们常用的LSCPI实质上就是去硬件上读取硬件的寄存器值,并且显示出来。

软件驱动设计

每一个硬件设备都有Verdon ID, Device ID, SubVendor ID等信息。所以每一个设备驱动程序,必须说明自己能够为哪些Verdon ID, DevieceID, SubVendor ID的设备提供服务。以PCI设备为例,它是通过一个pci_device_id的数据结构来实现这个功能的。例如:RTL8139驱动的pci_device_id定义为:

Verdon ID可以理解为厂商ID,8086 代表Intel,1249代表三星等,Device ID可以理解为产品ID,每款产品的ID不同。上面的信息说明,Verdon ID为0x10EC, Device ID为0x8139, 0x8138的PCI设备(SubVendor ID和SubDeviceID为PCI_ANY_ID,表示不限制。),都可以使用这个驱动程序(8139too)。

下面是内核设备与驱动匹配的代码:

自动加载过程

自动加载属于动态加载,加载的是ko文件,许多同学也没搞清楚,系统启动之后ko在什么时候,通过什么程序自动加载上的。

构建自动加载环境

编译内核时,通过make modules_install INSTALL_MOD_PATH=XXX,会将所有选项为M的模块编译为KO, 并且发布到xxx/lib/modules/uname-r/下面。在模块安装的时候,depmod会根据模块中的rtl8139_pci_tbl的信息,生成下面的信息,保存到/lib/modules/uname-r/modules.alias文件中,其内容如下:

alias pci:v000010ECd00008138sv*sd*bc*sc*i* 8139too alias pci:v000010ECd00008139sv*sd*bc*sc*i* 8139too ......

v后面的000010EC说明其Vendor ID为10EC,d后面的00008138说明Device ID为8139,而sv,和sd为SubVendor ID和SubDevice ID,后面的星号表示任意匹配。

另外在/lib/modules/uname-r/modules.dep文件中还保存这模块之间的依赖关系,其内容如下:

8139too.ko:mii.ko

modules.dep由depmod工具生成,在使用 modprobe xxx加载驱动时, modprobe需要借助modules.dep文件来分析模块之间的依赖关系,先加载依赖的ko,再加载真正需要加载的ko。

PCI扫描自动加载驱动

在内核启动过程中,总线驱动程序会会总线协议进行总线枚举(总线驱动程序总是集成在内核之中,不能够按模块方式加载,你可以通过make menuconfig进入Busoptions,这里面的各种总线,你只能够选择Y或N,而不能选择M.),并且为每一个设备建立一个设备对象。每一个总线对象有一个kset对象,每一个设备对象嵌入了一个kobject对象,kobject连接在kset对象上,这样总线和总线之间,总线和设备设备之间就组织成一颗树状结构。当总线驱动程序为扫描到的设备建立设备对象时,会初始化kobject对象,并把它连接到设备树中,同时会调用kobject_uevent()把这个(添加新设备的)事件,以及相关信息(包括设备的VendorID,DeviceID等信息)通过netlink发送到用户态中。

来看看这个过程:

subsys_initcall (前面讲到过驱动的16级加载机制,subsys_initcall处于16级中的第8级,arch\x86\pci\legacy.c 参照内核4.19)

pci_legacy_init

pcibios_scan_root

pci_scan_root_bus

pci_scan_slot

pci_scan_single_device

pci_device_add

device_add

device_add有两个流程:

bus_probe_device 匹配内核中已有驱动kobject_uevent 发消息到udev,让udev去/lib/modules/下面加载驱动

在用户态的udevd检测到这个事件,就可以根据这些信息,打开/lib/modules/uname-r/modules.alias文件,根据

alias pci:v000010ECd00008138sv*sd*bc*sc*i* 8139too

得知这个新扫描到的设备驱动模块为8139too。于是modprobe就知道要加载8139too这个模块了,同时modprobe根据 modules.dep文件发现,8139too依赖于mii.ko,如果mii.ko没有加载,modprobe就先加载mii.ko,接着再加载 8139too.ko。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3