精华内容
下载资源
问答
  • 这些驱动程序源码可以修改到内核中,也可以把他们编译成模块形势,在需要的时候动态加载。Linux内核是一个整体是结构,因此向内核添加任何东西,或者删除某些功能,都十分困难。为了解决这个问题引入了内核机制。  ...
  • 内核驱动程序加载器使用易受攻击的千兆字节驱动程序( )加载未签名的驱动程序。 用法 以管理员身份打开命令提示符 gdrv-loader.exe gdrv.sys driver.sys加载未签名的驱动程序 gdrv-loader.exe driver.sys卸载未签名...
  • 这些驱动程序源码可以修改到内核中,也可以把他们编译成模块形势,在需要的时候动态加载。Linux内核是一个整体是结构,因此向内核添加任何东西,或者删除某些功能,都十分困难。为了解决这个问题引入了内核机制。  ...
  • 驱动加载软件,在ring3的exe中加载内核ring0的驱动程序.sys
  • 驱动模块可以内核编译好后动态加载进去,也可以在编译内核的时候就直接添加。
  • Linux驱动的两种加载方式过程分析

    千次阅读 2018-10-22 10:18:24
    静态加载就是把驱动程序直接编译进内核,系统启动后可以直接调用。静态加载的缺点是调试起来比较麻烦,每次修改一个地方都要重新编译和下载内核,效率较低。若采用静态加载的驱动较多,会导致内核容量很大,浪费存储...

    原文:https://www.linuxidc.com/Linux/2014-06/103569p2.htm

    一、概念简述

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

    静态加载就是把驱动程序直接编译进内核,系统启动后可以直接调用。静态加载的缺点是调试起来比较麻烦,每次修改一个地方都要重新编译和下载内核,效率较低。若采用静态加载的驱动较多,会导致内核容量很大,浪费存储空间。

    动态加载利用了Linux的module特性,可以在系统启动后用insmod命令添加模块(.ko),在不需要的时候用rmmod命令卸载模块,采用这种动态加载的方式便于驱动程序的调试,同时可以针对产品的功能需求,进行内核的裁剪,将不需要的驱动去除,大大减小了内核的存储容量。

    在台式机上,一般采用动态加载的方式;在嵌入式产品里,可以先采用动态加载的方式进行调试,调试成功后再编译进内核。

    Linux下PCI设备驱动程序之注册详解 http://www.linuxidc.com/Linux/2014-02/97074.htm

    裸机驱动与Linux设备驱动的区别 http://www.linuxidc.com/Linux/2013-08/88799.htm

    Linux设备驱动开发详解(第2版)源代码 下载 http://www.linuxidc.com/Linux/2013-07/86977.htm

    Linux设备驱动开发详解(第2版)高清PDF http://www.linuxidc.com/Linux/2013-07/86976.htm

    二、实例分析

    下面以Linux下音频驱动的加载为例,分析两种方式的加载过程。

    1、静态加载

    1)解压内核,修改硬件架构和编译器;

    将内核压缩文件linux-2.6.8.1-zzm.tar.bz2解压到/home/sxy/目录下,命令是,解压后得到内核源码目录文件linux-2.6.8.1-zzm,进入该目录,编辑Makefile文件,将ARCH改为arm,CROSS_CPMPILE改为arm-linux-,如下图所示:

    保存后退出。

    2)配置内核;

    在内核源码树目录下,输入make menuconfig命令,进入内核配置界面,进入“Load an Alternate Configuration File”选项,载入配置文件kernel_2410.cfg,保存退出,过程如下图所示:

    再次输入make menuconfig命令,编辑sound选项,将其编译进内核(*),结果如下图所示,最后保存配置,退出。

    3)编译内核;

    在源码树目录下输入make zImage命令,编译完成后可以在/arch/arm/boot/目录下生成zImage镜像文件。

    4)下载内核

    将内核镜像文件zImage下载到开发板上,当串口终端显示如下信息时,表示驱动加载成功。

    2、动态加载

    1)解压内核,过程与静态编译时一样,略;

    2) 配置内核,前面过程与静态编译时一样,再次输入命令make menuconfig,配置sound选项时,将其编译成模块(M),结果如下图所示,最后保存配置,退出;

    这样就将声卡驱动编译成模块,可以动态选择是否加载到内核中。

    3)下载内核

    将内核镜像文件zImage下载到开发板上,验证能否驱动声卡的过程如下:

    说明:首先,将虚拟机下的/home/目录挂载到开发板上的/tmp/目录下,然后先后加载soundcore.ko和s3c2410-oss.ko两个模块,最后通过lsmod命令查看是否加载上声卡驱动,结果显示加载成功,这样就可以在应用空间编程,实现音频的录放等操作。

    PS:①采用make menuconfig命令时,选项*代表Y,表示将驱动编译进内核;M表示将驱动编译成模块;空代表N,表示不编译;

    ②内核文件与模块两者有很多东西必须匹配,编译器版本、源码版本、编译时的配置等,所以当内核文件修改了,譬如修改了驱动的编译选项(Y、M、N),那么就必须重新编译和下载内核,否则会出错。

    三、遇到的问题

    问题:动态加载过程中,出现下面错误:

    错误:注册和注销设备的符号未知。

    解决方法:寻找依赖关系,查看几个符号的定义,发现在soundcore.c文件中定义了以上几个函数,同时导出了符号,以register_sound_dsp为例,如下图所示:

    所以应该先加载soundcore.ko,后加载s3c2410-oss.ko。

    注意:在Kconfig和Makefile文件中定义了依赖关系,也可以查找到问题的原因。

    展开全文
  • Windows 的 C++ 中的驱动程序加载程序/注入/Rootkit 介绍 我在 2011 年写了这个项目,当时我在玩注射。 该类用于将驱动程序/Rootkit 注入 Windows 内核。 CDriver_Loader 具有从 Windows 内核加载和弹出的方法。 ...
  • 编译Linux驱动程序,模块,自定义内核和引导程序
  • 驱动程序的使用可以按照两种方式编译,一种是静态编译进内核,另一种是编译成模块以供动态加载。由于uClinux不支持模块动态加载,而且嵌入式Linux不能够象桌面Linux那样灵活的使用insmod/rmmod加载卸载设备驱动程序...
  • HelloWorld 程序编写及编入内核驱动, 创建一个 hello 的驱动,在开机的时候加载,并打印"Hello world",相关简介: 1,增加驱动文件 hello.c 和对应的 Makefile、Kconfig 2,修改上一级的 Makefile 与 Kconfig 3,...
  • 确保将旧版rtl8192cu驱动程序列入黑名单,否则默认情况下某些发行版似乎已加载驱动程序。 某些设备要求在NetworkManager中禁用电源管理。 按照进一步的说明在NetworkManager中禁用电源管理。 典型的症状是设备...
  • 使用 WDF 框架构建的 Windows 内核模式类筛选器驱动程序 (KMDF)。 为每个鼠标反转滚轮 随附驱动程序安装程序应用程序 MouseTrapInstaller.exe 必须与打包文件在同一路径 以管理员身份安装“MouseTrapInstaller.exe...
  • 介绍Linux 内核中USB 驱动的框架,图文并茂,设备的加载流程等等。
  • 驱动程序直接编译进内核

    千次阅读 2016-04-07 19:41:03
    hello程序直接编译进内核: 第一;先将hello.c拷贝在内核源代码中drivers/char/,vi /drivers/char/Kconfig 添加 config HELLO_DRIVER  bool(tristate) "my hello driver"。 然后回到源代码目录下make ...

    hello程序直接编译进内核:

    第一;先将hello.c拷贝到在内核源代码中drivers/char/,vi  /drivers/char/Kconfig

    添加 config HELLO_DRIVER

                        bool(tristate)  "my hello driver"。

    然后回到源代码目录下make menuconfig,在文本菜单Device Drivers——>character devices就可以发现my hello driver的选项。如果是bool就有两个选项(*和空),如果是tristate就有三个选项(*、空和m),选择空就可在源代码目录vi .config文件查找HELLO(/HELLO),发现CONFIG_HELLO_DRIVER is not set(选*则=y,选m=m)。

    第二;在内核源代码中vi  /drivers/char/Makefile,添加obj-$(CONFIG_HELLO_DRIVER)        +=hello.o。这样就可以将hello.c驱动程序直接编译进内核

     

     

    Kconfig文件的作用

    内核源码树的目录下都有两个文件Kconfig(2.4版本是Config.in)和Makefile。分布到各目录的Kconfig构成了一个分布式的内核配置数据库,每个Kconfig分别描述了所属目录源文件相关的内核配置菜单。在内核配置make menuconfig(或xconfig等)时,从Kconfig中读出菜单,用户选择后保存到.config的内核配置文件中。在内核编译时,主Makefile调用这个.config,就知道了用户的选择。
    *上面的内容说明了,Kconfig就是对应着内核的配置菜单。如果要想添加新的驱动到内核的源码中,可以修改Kconfig,这样就可以选择这个驱动,如果想使这个驱动被编译,要修改Makefile


    so添加新的驱动时需要修改的文件有两种(注意不只是两个)
    *Kconfig
    *Makefile

    要想知道怎么修改这两种文件,就要知道两种文件的语法结构
    Kconfig
    每个菜单都有一个关键字标识,最常见的就是config
    语法:
    config
    symbol是一个新的标记的菜单项,options是在这个新的菜单项下的属性和选项
    其中options部分有:


    1、类型定义:
    每个config菜单项都要有类型定义,bool布尔类型、 tristate三态:内建、模块、移除 string字符串、 hex十六进制、 integer整型
    例如config HELLO_MODULE
    bool "hello test module"
    bool类型的只能选中或不选中,tristate类型的菜单项多了编译成内核模块的选项,如果选择编译成内核模块,则会在.config中生成一个CONFIG_HELLO_MODULE=m的配置,如果选择内建,就是直接编译成内核影响,就会在.config中生成一个CONFIG_HELLO_MODULE=y的配置.


    2、依赖型定义depends on或requires
    指此菜单的出现与否依赖于另一个定义
    config HELLO_MODULE
    bool "hello test module"
    depends on ARCH_PXA
    这个例子表明HELLO_MODULE这个菜单项只对XScale处理器有效。


    3、帮助性定义
    只是增加帮助用关键字help或者---help---
    内核的Makefile

    在linux2.6.x/Documentation/kbuild目录下有详细的介绍有关kernel makefile的知识。
    内核的Makefile分为5个组成部分:
    Makefile     最顶层的Makefile
    .config        内核的当前配置文件,编译时成为定层Makefile的一部分
    arch/$(ARCH)/Makefile    与体系结构相关的Makefile
    s/ Makefile.*      一些Makefile的通用规则
    kbuild Makefile           各级目录下的大概约500个文件,编译时根据上层Makefile传下来的宏定义和其他编译规则,将源代码编译成模块或者编入内核
    顶层的Makefile文件读取 .config文件的内容,并总体上负责build内核和模块。Arch Makefile则提供补充体系结构相关的信息。 s目录下的Makefile文件包含了所有用来根据kbuild Makefile 构建内核所需的定义和规则。
    (其中.config的内容是在make menuconfig的时候,通过Kconfig文件配置的结果。
    举个例子:
        假设想把自己写的一个flash的驱动程序加载到工程中,而且能够通过menuconfig配置内核时选择该驱动该怎么办呢?可以分三步:
      第一:将你写的flashtest.c 文件添加到/driver/mtd/maps/ 目录下。
      第二:修改/driver/mtd/maps目录下的kconfig文件:
            
    config MTD_flashtest
                 tristate “ap71 flash"

             这样当make menuconfig时 ,将会出现 ap71 flash选项。
    第三:修改该目录下makefile文件。
    添加如下内容:obj-$(CONFIG_MTD_flashtest)       += flashtest.o
    这样,当你运行make menucofnig时,你将发现ap71 flash选项,如果你选择了此项。该选择就会保存在.config文件中。当你编译内核时,将会读取.config文件,当发现ap71 flash 选项为yes 时,系统在调用/driver/mtd/maps/下的makefile 时,将会把 flashtest.o 加入到内核中。即可达到你的目的

     

     

    ************************************************************************************************************************************************************************************************************************************************************************不加自己目录的情况

    1)把我们的驱动源文件(xxoo.c)放到对应目录下,具体放到哪里需要根据驱动的类型和特点。这里假设我们放到./driver/char下。

    2)然后我们修改./driver/char下的Kconfig文件,依葫芦添加即可,如下所示:


    注意这里的LT_XXOO这个名字可以随便写,但需要保持这个格式,他并不需要跟驱动源文件保持一致,但最好保持一致,等下我们在修改Makefile时会用到这个名字,他将会变成CONFIG_LT_XXOO,那个名字必须与这个名字对应。如上所示,tristate定义了这个配置选项的可选项有几个,help定义了这个配置选项的帮助信息,具体更多的规则这里不讲了。

    3)然后我们修改./driver/char下的Makefile文件,如下所示:


    这里我们可以看到,前面Kconfig里出现的LT_XXOO,在这里我们就需要使用到CONFIG_XXOO,实际上逻辑是酱汁滴:在Kconfig里定义了LT_XXOO,然后配置完成后,在顶层的.config里会产生CONFIG_XXOO,然后这里我们使用这个变量。

    到这里第一种情况下的添加方式就完成了。

    添加自己目录的情况

    1)在源码的对应目录下建立自己的目录(xxoo),这里假设为/drivers/char/xxoo 

    2) 把驱动源码放到新建的xxoo目录下,并在此目录下新建KconfigMakefile文件。然后给新建的KconfigMakefile添加内容。

    Kconfig下添加的内容如下:


    这个格式跟之前在Kconfig里添加选项类似。

    Makefile里写入的内容就更少了:

    添加这一句就可以了。

    3)第三也不复杂,还是依葫芦画瓢就可以了。

    我们在/drivers/char目录下添加了xxoo目录,我们总得在这个配置系统里进行登记吧,哈哈,不然配置系统怎么找到们呢。由于整个配置系统是递归调用滴,所以我们需要在xxoo的父目录也即char目录的KconfigMakefile文件里进行登记。具体如下:

    a). drivers/char/Kconfig中加入:source drivers/char/xxoo/Kconfig

    b). drivers/char/Makefile中加入:obj-$(CONFIG_LT_XXOO) += xxoo/

    添加过程依葫芦画瓢就可以了,灰常滴简单。

    展开全文
  • 文章摘录自《Windows内核原理与实现》一书。 设备驱动程序是操纵设备的内核模块,I/O 管理器、即插即用管理器和电源...Windows 可以动态地加载或卸载设备驱动程序,通过这些驱动程序来调整或扩展内核的功能。 Win...

    文章摘录自《Windows内核原理与实现》一书。

    设备驱动程序是操纵设备的内核模块,I/O 管理器、即插即用管理器和电源管理器都需要与设备的驱动程序打交道。

    在Windows I/O 系统中,设备驱动程序不仅为操作系统提供了支持各种 I/O 设备的能力, 也是 Windows 内核本身扩展的基础。Windows 可以动态地加载或卸载设备驱动程序,通过这些驱动程序来调整或扩展内核的功能。 Windows I/O 系统规定了设备驱动程序应遵循的接口, 这组接口是通用的,可适用于所有的内核模式驱动程序。设备驱动程序依据用途不同,可以分为以下三类:

    1.即插即用驱动程序,也称为 WDM驱动程序。 它们通常为了驱动硬件设备而由硬件厂商提供,与 Windows的 I/O 管理器、即插即用管理器和电源管理器一起工作。Windows 自身携带了大量即插即用驱动程序,用于支持各种常见的存储设备、视频适配器、网络适配器以及输入设备等。

    2.内核扩展驱动程序,也称为非即插即用驱动程序。它们扩展内核功能,或者提供了访问内核模式代码和数据的一种途径。它们并没有集成到即插即用管理器和电源管理器的管理框架中。早期的 Windows NT 驱动程序(在引入即插即用机制以前)都属于这一类类型,现在仍然有大量的内核扩展驱动程序。

    3.文件系统驱动程序。它们接收针对文件的 I/O 请求,再进一步将这些请求转变成正真对于储存设备或网络设备的 I/O 请求,从而满足客户的原始请求。文件系统的驱动程序被放在对象管理器的\FileSystem 目录下,其他的驱动程序放在\Driver 目录下。

    Windows 驱动程序模型 (WDM) 在 I/O 模型中增加了对即插即用、电源管理和 Windows 管理规范(WMI)的支持。而且,遵从 WDM 的驱动程序在 Windows 平台(包括 Windows 98/Windows 2000 以后的所有版本) 上至少是源代码兼容的,甚至也可能是二进制兼容的。WDM驱动程序又进一步划分成以下三类

    1.总线驱动程序。顾名思义,总线驱动程序管理一个总线设备,它负责检测总线上附载的所有设备,并通知即插即用管理器关于这些设备情况。总线驱动程序也负责总线的电源管理。

    2.功能驱动程序。功能驱动程序管理具体的设备,在一个设备的设备栈中,功能驱动程序创建的设备对象(即 FDO)相当于操作系统控制该设备的逻辑接口。功能驱动程序是实际管理该设备的功能模块。

    3.过滤驱动程序。在设备栈中,过滤驱动程序位于功能驱动程序之上或之下,它的用途是:监视一个设备的 I/O 请求以及这些请求的处理情况,或者,增加或改变一个设备或另一个驱动程序的行为。例如,病毒扫描工具常常利用过滤驱动来监视被读写的文件数据。

    在WDM中,总线式可供其他设备附载的设备,其中既有像 PCI 和 SCSI 这样的物理总线设备,也有像 HAL这样的虚拟总线设备。总线驱动程序负责检测总线上的设备,并且协助即插即用管理器列举这些设备,而且它也控制该总线的物理配置。相反地,功能驱动程序要简单得多,通常只是控制一个设备的硬件而已。

    在即插即用管理器列举得到的设备树中,每个设备节点都包含一个设备栈,设备栈中的各个设备对象分别由对应的驱动程序创建和实现。图 6.12 显示了不同类型WDM驱动程序在设备栈中的角色,以及它们之间的关系。

    每个设备对象都是由对应的驱动程序创建的,设备的 PDO 是由总线驱动程序创建的,而 FDO 是由功能驱动程序创建的。在PDO和FDO之间,紧挨着PDO,可以由零个或多个总线过滤设备对象,他们由相应的总线过滤驱动程序创建。在总线过滤设备对象和 FDO之间,可以有零个或多个下层过滤设备对象。而在 FDO 之上, 则可以有零个或多个上层过滤设备对象。这些下层或上层过滤设备对象分别由相应的下层或上层过滤驱动程序创建。WDM规定了这样的设备栈结构,即合理地让参与其中的每个驱动程序实现它所负责的那部分功能,也提供了足够的扩展性,允许过滤驱动程序在不同层次上监视或修正一个设备的工作方式。

    对于一个具体的设备而言,FDO代表了它对操作系统的逻辑接口。功能驱动程序往往会创建一个代表相应 PDO 的设备接口(通过I/O 管理器函数 IoRegisterDeviceInterface),因而应用程序或内核其他部分可以通过此设备接口与该设备打交道。对于一些复杂而又通用的设备,例如磁盘设备和网络适配器等,功能驱动程序又被进一步分成多个独立的驱动程序,由它们联合起来管理该 FDO 的 I/O 请求。在这一层意义上, WDM 驱动程序对硬件的支持可以进一步划分为类驱动程序(class driver)和端口驱动程序 (port driver),以及小端口驱动程序(miniport driver).类驱动程序实现了某一种类型的设备的 I/O 处理。对于已经标准化的设备类型,比如磁盘、网络适配器等,提供一个类驱动程序可以为不同厂商生产的设备实现通用的服务。端口驱动程序实现了与某一类型 I/O 端口相关的 I/O 处理,它们并不遵从驱动程的接口请求,而只是一些内核模式的辅助例程。小端口驱动程序则实现了驱动某一特定设备而需要的 I/O 服务。类驱动程序和小端口驱动程序的分工可以看成是针对一个或一类设备的 "通用" 和 "特殊" 的功能部分。类驱动程序实现的是 "通用" 或 "公共" 的 I/O 服务,而小端口驱动程序实现的是针对某一特定设备的 “特殊” 功能部分。

    展开全文
  • windows内核 x64位驱动程序开发 3.应用程序加载驱动
  • 本文主要介绍在Ubuntu上为Android HAL模块访问Linux内核驱动程序,这里给大家提供方法和一个小的测试程序代码,以及常遇到的问题和解决方法,有需要的小伙伴可以参考下
  • Linux内核模块(Linux Kernel Module)和硬件驱动程序(Hardware Driver)ioctl写一个简单的内核模块内核模块HelloWorld内核模块Makefile编写一个提供ioctl接口的设备驱动程序内核模块)Linux的设备设备的分类设备...

    什么是Linux内核模块、驱动程序和ioctl?

    Linux内核模块(Linux Kernel Module)和硬件驱动程序(Hardware Driver)

    Linux使用可加载的内核模块(loadable kernel module, LKM)来在运行时动态的添加(或删除)Linux内核的代码。
    它有几点好处:
    体积:模块化的系统可以减少Linux内核的体积,需要用到的模块用时再加载。
    可扩展+安全:内核模块是运行在内核态的,它与用户态应用分离,增强了安全性,又保证了内核的可扩展性。

    一个比较典型的Linux可加载内核模块就是Linux中设备的驱动程序。
    驱动程序如下图所示,它向下直接与硬件沟通,向上为系统调用提供具体实现。
    经过驱动程序的这一层封装,让用户态的程序无需关心硬件运行细节,而直接使用其提供的API,降低了(用户态)程序开发者的开发难度。
    Linux kernel space and user space

    ioctl

    刚才提到,用户态程序是无法直接调用内核态API的,那么应用程序开发者如何与硬件沟通?
    Linux就提供了一系列的系统调用,用来让开发者在用户态与系统沟通,进而与硬件沟通,其中就包括了我们常用的open、close、read、write和ioctl。

    用户态的ioctl原型为

    int ioctl(int fd, int cmd, ...)
    

    而陷入内核之后,ioctl的函数原型为

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

    他们之间的关系如下图所示,fd被转换为两个结构体,用来标识操作的设备文件,cmd被原封不动的传入了驱动中。ioctl

    写一个简单的内核模块

    内核模块HelloWorld

    我们结合代码来看一下一个最简单的内核模块的例子

    #include <linux/init.h>             // 包含了__init和__exit函数的宏定义
    #include <linux/module.h>           // 可加载内核模块的核心的头文件
    #include <linux/kernel.h>           // 包含了kernel中的类型、宏和函数等
    
    MODULE_LICENSE("GPL");              //此模块使用的licence,如果不用GPL的话编译时会出warning
    MODULE_AUTHOR("StayrealS");      ///模块作者,加载后使用modeinfo可以在系统中看到
    MODULE_DESCRIPTION("A simple Linux driver.");  //模块信息,加载后使用modeinfo可以在系统中看到
    MODULE_VERSION("0.1");              ///模块版本,加载后使用modeinfo可以在系统中看到
    
    
    /** @brief Module初始化函数,作为module的入点,使用insmod加载时会调用本函数。
     */
    static int __init helloHW_init(void){
       printk(KERN_INFO "HW: HelloWorld from the BBB LKM!\n");
       return 0;
    }
    
    /** @brief 与init函数相似,在rmmod卸载本模块的时候会调用本函数进行清理等工作。
     */
    static void __exit helloHW_exit(void){
       printk(KERN_INFO "HW: Goodbye from the HW LKM!\n");
    }
    
    /** @brief 一个模块必需使用module_init() 和module_exit() 宏来
     *  定义module的入点和出点
     */
    module_init(helloHW_init);
    module_exit(helloHW_exit);
    

    内核模块Makefile

    另外,还需要Makefile

    obj-m+=hello.o
    
    all:
    	make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules # 使用M=本文件夹的方式编译本文件夹下的.o为内核模块.ko
    clean:
    	make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean
    

    其中$shell uname -r用来打印本机linux 内核的版本信息,用于找寻linux内核关于编译module用到的工具。接下来,只需要使用make就可以将hello world编译成为后缀名为.ko的可加载module。
    接下来,使用

    insmod hello.ko # 加载模块
    lsmod # 查看已加载的模块
    rmmod hello.ko  # 卸载模块
    

    来加载或卸载编写好的模块。

    刚才在编写模块的时候打印是使用了printk,它是printf的内核态版本。
    printk的输出不会打印在linux终端上,但是会打印到内核缓冲区中,可以使用

    dmesg # 查看内核缓冲区
    dmesg -c # 清空内核缓冲区
    

    编写一个提供ioctl接口的设备驱动程序(内核模块)

    如第一部分中所说,设备驱动程序是内核模块的一个应用案例。本节我们将编写一个提供ioctl接口的设备驱动程序。

    Linux的设备

    设备的分类

    Linux中I/O设备分为两类:字符设备和块设备。两种设备本身没有严格限制,但是,基于不同的功能进行了分类。

    • 字符设备:提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。相反,此类设备支持按字节/字符来读写数据。举例来说,键盘、串口、调制解调器都是典型的字符设备。
    • 块设备:应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。硬盘、软盘、CD-ROM驱动器和闪存都是典型的块设备,应用程序可以寻址磁盘上的任何位置,并由此读取数据。此外,数据的读写只能以块(通常是512B)的倍数进行。与字符设备不同,块设备并不支持基于字符的寻址。

    这两种设备都可以通过访问文件系统中的设备文件(在/dev下)来访问。例如,接下来我们要编写的一个虚拟字符设备,就被挂载在/dev/test处。

    设备的主编号(major number)与副编号(minor number)

    设备的主编号是为了让操作系统区分不同种类的设备的。例如/dev/ram0/dev/null的主编号都是1。
    设备的副编号是为了区分同种类的不同设备的,例如以上两个设备的副编号一定不同。
    设备的主副编号可以在/dev文件夹下使用ls -l查看。如下图:第一个设备apm_bios的主编号为10,副编号为134。
    主编号副编号

    编写驱动程序

    #include <linux/module.h>    // included for all kernel modules
    #include <linux/kernel.h>    // included for KERN_INFO
    #include <linux/init.h>      // included for __init and __exit macros
    #include <linux/scpi_protocol.h>
    #include <asm/io.h>
    #include <linux/slab.h>
    #include <linux/fs.h>        // file_operation is defined in this header
    
    #include <linux/device.h>
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("StayrealS");
    MODULE_DESCRIPTION("Driver as a test case");
    
    static int      majorNumber;
    static struct   class*  test_module_class = NULL;
    static struct   device* test_module_device = NULL;
    #define DEVICE_NAME "test"      //define device name
    #define CLASS_NAME  "test_module"
    
    //函数原型
    static long test_module_ioctl(struct file *, unsigned int, unsigned long);
    static int __init test_init(void);
    static void __exit test_exit(void);
    
    ///linux/fs.h中的file_operations结构体列出了所有操作系统允许的对设备文件的操作。在我们的驱动中,需要将其中需要的函数进行实现。下面这个结构体就是向操作系统声明,那些规定好的操作在本模块里是由哪个函数实现的。例如下文就表示unlocked_ioctl是由本模块中的test_module_ioctl()函数实现的。
    static const struct file_operations test_module_fo = {
            .owner = THIS_MODULE,
            .unlocked_ioctl = test_module_ioctl,
    };
    
    //本模块ioctl回调函数的实现
    static long test_module_ioctl(struct file *file,        /* ditto */
                     unsigned int cmd,      /* number and param for ioctl */
                     unsigned long param)
    {
            /* ioctl回调函数中一般都使用switch结构来处理不同的输入参数(cmd) */
            switch(cmd){
            case 0:
            {
                    printk(KERN_INFO "[TestModule:] Inner function (ioctl 0) finished.\n");
                    break;
            }
            default:
                    printk(KERN_INFO "[TestModule:] Unknown ioctl cmd!\n");
                    return -EINVAL;
            }
            return 0;
    }
    
    
    static int __init test_init(void){
            printk(KERN_INFO "[TestModule:] Entering test module. \n");
            // 在加载本模块时,首先向操作系统注册一个chrdev,也即字节设备,三个参数分别为:主设备号(填写0即为等待系统分配),设备名称以及file_operation的结构体。返回值为系统分配的主设备号。
            majorNumber = register_chrdev(0, DEVICE_NAME, &test_module_fo);
            if(majorNumber < 0){
                    printk(KERN_INFO "[TestModule:] Failed to register a major number. \n");
                    return majorNumber;
            }
            printk(KERN_INFO "[TestModule:] Successful to register a major number %d. \n", majorNumber);
    
            //接下来,注册设备类
            test_module_class = class_create(THIS_MODULE, CLASS_NAME);
            if(IS_ERR(test_module_class)){
                    unregister_chrdev(majorNumber, DEVICE_NAME);
                    printk(KERN_INFO "[TestModule:] Class device register failed!\n");
                    return PTR_ERR(test_module_class);
            }
            printk(KERN_INFO "[TestModule:] Class device register success!\n");
    
            //最后,使用device_create函数注册设备驱动
            test_module_device = device_create(test_module_class, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME);
            if (IS_ERR(test_module_device)){               // Clean up if there is an error
                    class_destroy(test_module_class);           // Repeated code but the alternative is goto statements
                    unregister_chrdev(majorNumber, DEVICE_NAME);
                    printk(KERN_ALERT "Failed to create the device\n");
                    return PTR_ERR(test_module_device);
            }
            printk(KERN_INFO "[TestModule:] Test module register successful. \n");
            return 0;
    }
    
    
    static void __exit test_exit(void)
    {
    		//退出时,依次清理生成的device, class和chrdev。这样就将系统/dev下的设备文件删除,并自动注销了/proc/devices的设备。
            printk(KERN_INFO "[TestModule:] Start to clean up module.\n");
            device_destroy(test_module_class, MKDEV(majorNumber, 0));
            class_destroy(test_module_class);
            unregister_chrdev(majorNumber, DEVICE_NAME);
            printk(KERN_INFO "[TestModule:] Clean up successful. Bye.\n");
    
    }
    
    module_init(test_init);
    module_exit(test_exit);
    

    相信读者也发现了,在我的test_module_fo结构体中,没有直接使用.ioctl=test_module_ioctl而是使用了.unlocked_ioctl=test_module_ioctl。这是因为在较新的Linux内核中,已经修改了系统调用ioctl的内部实现为unlocked_ioctl,实现时要注意这一点。最新的file_operation结构体定义如下:(我使用的内核版本在/usr/src/linux-headers-4.4.145-armv7-x16/include/linux/fs.h中)

     // Note: __user refers to a user-space address.
    struct file_operations {
       struct module *owner;                             // Pointer to the LKM that owns the structure
       loff_t (*llseek) (struct file *, loff_t, int);    // Change current read/write position in a file
       ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);    // Used to retrieve data from the device
       ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);   // Used to send data to the device
       ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);  // Asynchronous read
       ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); // Asynchronous write
       ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);            // possibly asynchronous read
       ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);           // possibly asynchronous write
       int (*iterate) (struct file *, struct dir_context *);                // called when VFS needs to read the directory contents
       unsigned int (*poll) (struct file *, struct poll_table_struct *);    // Does a read or write block?
       long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); // Called by the ioctl system call
       long (*compat_ioctl) (struct file *, unsigned int, unsigned long);   // Called by the ioctl system call
       int (*mmap) (struct file *, struct vm_area_struct *);                // Called by mmap system call
       int (*mremap)(struct file *, struct vm_area_struct *);               // Called by memory remap system call 
       int (*open) (struct inode *, struct file *);             // first operation performed on a device file
       int (*flush) (struct file *, fl_owner_t id);             // called when a process closes its copy of the descriptor
       int (*release) (struct inode *, struct file *);          // called when a file structure is being released
       int (*fsync) (struct file *, loff_t, loff_t, int datasync);  // notify device of change in its FASYNC flag
       int (*aio_fsync) (struct kiocb *, int datasync);         // synchronous notify device of change in its FASYNC flag
       int (*fasync) (int, struct file *, int);                 // asynchronous notify device of change in its FASYNC flag
       int (*lock) (struct file *, int, struct file_lock *);    // used to implement file locking};
    

    同理,仿照上文编写makefile、使用insmod将设备(模块)加载。
    接下来,我们可以编写应用程序来访问这个新添加的设备了。

    #include <stdlib.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/ioctl.h>
    
    
    int main(){
            int fd;
            fd = open("/dev/test", O_RDWR); //我们的设备挂载在/dev/test处
            if (fd < 0){
                    perror("Failed to open the device...");
                    return errno;
            }else{
                    printf("Open device successful!\n");
            }
            ioctl(fd, 0);
            printf("Called ioctl with parameter 0!\n");
    }
    

    注意,运行本程序时需要使用sudo命令,否则可能因为权限问题访问设备文件失败。可以使用/etc/udev/rules.d中的规则将这个新设备文件调整权限,具体请参阅其他文章。

    Q:我们并没有实现open函数,操作系统也能将它打开吗?
    A:答案是是的。操作系统默认帮我们实现了基础的open、close函数,方便开发者进行开发。

    参考文献

    Linux设备文件系统
    IOCTL
    derekmolly
    Linux Driver Tutorial
    字符驱动框架入门

    展开全文
  • Android系统篇之----编写简单的驱动程序并且将其编译到内核源码中
  • Linux 操作系统因为其高效安全可动态加载及源代码开放等特点深受设备驱动程序 开发人员的喜爱系统内核大部分独立于底层硬件运行用户无需关心硬件问题而用户 操作是通过一组标准化的调用来完成设备驱动程序的任务是将...
  • 由于存在加载到内核并与用户模式应用程序交换数据的驱动程序,因此在较低级别与操作系统本身进行交互非常有用。 可用的插件 流程浏览器:列出流程信息,直接从EPROCESS结构中提取一些数据 线程资源管理器:按进程ID...
  • Linux驱动程序如何编译进内核

    千次阅读 2016-05-28 22:22:55
    Linux驱动程序如何编译进内核 2014-04-17 17:48 241人阅读 评论(0) 收藏 举报  分类:   linux 驱动(8)  版权声明:本文为博主原创文章,未经博主允许不得转载。  很多刚接触...
  • Windows内核驱动

    2016-11-09 15:22:12
    VS2013+WDK8.1开发的Windows NT型驱动程序加载和卸载,以及和用户层的交互.先装VS20113之后,再打开VS2013,然后再装WDK8.1,下载一个DebugView进行内核调试即可,最好安装一个虚拟机,在虚拟机中调试
  • Driver.NET功能强大,简单轻便的库,用于在Windows上创建服务以及与内核驱动程序进行加载/通信。 用法 通过连接\\.\Disk (disk.sys)驱动程序来检索磁盘设备序列号的示例在存储库中可用,该项目称为“ Driver...
  • Linux内核FL2000DX / IT66121FN软件狗DRM驱动程序 完全重新实现了FrescoLogic FL2000DX DRM驱动程序和ITE Tech IT66121F驱动程序,从而允许基于Linux中此类芯片的启用完整的显示控制器功能。 建筑司机 签出代码并...
  • Linux内核驱动加载过程

    千次阅读 2016-01-29 11:30:48
    静态加载的方法是把驱动程序直接编译进内核,然后内核在启动过程中由do_initcall()函数加载。 do_initcalls()函数路径在/init/main.c 过程如下: start_kernel()--->rest_init()--->kernel_init()--->do_basic_se
  • 1编写驱动程序driver_insmod.c 头文件   2 编写Makefile文件   pwd:当前所在路径 uname -r: 显示操作系统的发行版号 3 运行结果   4动态加载驱动 未安装前执行lsmod命令   安装驱动   安装...
  • 深入Linux设备驱动程序内核机制

    千次阅读 2012-02-21 20:24:16
    深入Linux设备驱动程序内核机制 陈学松 著 ISBN978-7-121-15052-4 2012年1月出版 定价:98.00元 16开 540页 内 容 简 介 这是一本系统阐述Linux设备驱动程序技术内幕的专业书籍,它的侧重点不是讨论如何在...
  • 虚拟SPI Linux内核驱动程序能够模拟故障/不可靠功能。 (c)Matthias Behr,2011年-2020年 目标/动机 开发该模块的目的是测试例如两个通过SPI从一个ECU / SOC另一个的通信模块。 该模块应有助于注入那些故障,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 95,294
精华内容 38,117
关键字:

驱动程序如何加载到内核