精华内容
下载资源
问答
  • 经常在 Android 的 init.rc 文件中看到类似 insmod *.ko 这样的命令,为某个设备加载驱动程序,在 insmod 命令的实现过程中,会用到 Linux 系统的系统调用函数: int init_module(void *module_image, unsigned ...

    经常在 Android 的 init.rc 文件中看到类似 insmod *.ko 这样的命令,为某个设备加载驱动程序,在 insmod 命令的实现过程中,会用到 Linux 系统的系统调用函数:
    int init_module(void *module_image, unsigned long len,
    const char *param_values);

    int finit_module(int fd, const char *param_values,
    int flags);
    在Linux终端中,通过 man init_module 命令可以查看这两个函数的使用方法,以前不知道这两个函数,也就不确定 ko文件在 init.rc中是如何加载的,知道这两个函数,一切就清晰了,又涨知识啦~~~

    展开全文
  • 驱动程序加载方式

    2020-12-21 09:31:08
    Linux设备驱动程序有两种加载方式。...如果驱动程序代码源文件为 infrared_s3c2410.c,将infrared_s3c2410.c复制到内核代码的/drivers/char目录,并在该目录下 的Kconfig文件最后增加如下语句: config INFR

    Linux设备驱动程序有两种加载方式。一种是直接编译进Linux内核,在Linux启动时 加载;另一种是釆用内核模块方式,这种模块可动态加载与卸载。
    如果希望将新驱动程序编译进内核,需要修改内核代码和编译选项。下面以字符型设备 为例,说明如何在Linux内核中添加一个新的设备驱动程序。如果驱动程序代码源文件为 infrared_s3c2410.c,将infrared_s3c2410.c复制到内核代码的/drivers/char目录,并在该目录下 的Kconfig文件最后增加如下语句:
    config INFRARED REMOTE
    tristate “INFRARED Driver for REMOTE” depends on ARCH_S3C64XX || ARCH_S3C2410 default y
    在该目录下的Makefile中添加如下语句:
    Obj-$(CONFIG_INFRARED_REMOTE)+=infrared_s3c2410.o
    进入Linux内核源代码目录,执行make menuconfig命令后,选择[device drivers]-> [characterdevices],进入如图1-3所示的内核配置窗口,可见最后一行即新增的驱动:
    »
    < > GSM MUX line discipline support (EXPERIMENTAL) I I
    < > Trace data sink for MIPI Pl149. 7 cJTAG standard
    [♦] /dev/mem virtual device support
    [♦] /dev/tanem virtual device support Serial drivers >
    []ARM JTAG DCC console
    < > IPil top-level message handler
    <> Hardware Random Number Gentrator Core support >
    < > Siemens R3964 line discipline
    < > RAW driver (/dev/raw/rawN)
    < > TPM Hardware Support
    < > Xillybus generic FPGA interface
    <♦> IHFRARED Driver for REMOTE QffiW)
    在内核配置窗口中可以使用上下键、空格键和回车键进行选择、移动和取消选择。内核 配置窗口中以v >带头的行是内核模块的配置,以[]带头的行是内核功能的配置。选项前如 果为<
    >,表示相应的模块将被编译进内核。如果选项前是v >则表示不编译进内核。这里在 [INFRARED Driver fbr REMOTE]行前面设置为v>,则 infrared_s3c2410.o 将被编译进内 核。在使用makezlmage命令编译内核时所有设置为v>的项将被包含在内核映像中。
    釆用可加载模块方式让驱动程序的运行更加灵活,也更利于调试。可加载模块用于扩展 Linux操作系统的功能。使用内核模块的优点是可以按照需要进行加载,而且不需要重新编 译内核。这种方式控制了内核的大小,而模块一旦被插入内核,它就和内核其他部分一样, 可以访问内核的地址空间、函数和数据。可加载模块通常以.ko为扩展名。在图1-3中选项 前如果为vM>,表示编译成可加载模块。在使用make modules命令编译内核时,所有设置 为vM>的项将被编译。make modules结束后可以使用下面的命令安装内核中的可加载模块文 件到一个指定的目录:
    make modules install INSTALL_MOD_PATH=/home/usr/modules
    使用make命令编译内核相当于执行make zlmage和make modules两个命令。

    展开全文
  • 1、前言 在我们写完简单的驱动程序之后,...对缩写的 驱动程序进行测试,首先 通过insmod加载驱动程序到内核里,然后通过 dmesg 查看内核的输出信息。 ==> 将驱动函数insmod到内核中后,可以写一个相对应的测...

    1、前言

    在我们写完简单的驱动程序之后,实际上就是建立了以下一种连接关系: ** 设备号-设备描述-设备行为描述三者之间建立相应的联系! **
    在这里插入图片描述

    2、如何对已经写好的一个驱动函数进行测试呢? 需要注意哪些地方呢?

    对缩写的 驱动程序进行测试,首先 通过insmod加载驱动程序到内核里,然后通过 dmesg 查看内核的输出信息。
    ==> 将驱动函数insmod到内核中后,可以写一个相对应的测试程序对驱动程序进行验证: (相应的驱动程序)
    在这里插入图片描述

    2.1 将设备号与设备文件建立连接

    在终端使用如下指令进行操作:
    在这里插入图片描述
    示意图:
    在这里插入图片描述
    通过在终端中,执行测试程序生成的目标程序,进一步验证有没有调用到内核中的read write 函数!
    (需要具体的事例进行详细的操作来进行理解!)

    3、字符设备驱动的makefile分析

    (1)、 在编写完相应的驱动程序源码之后,在终端直接执行make指令就能够生成相应的 *.ko 可执行程序文件,这个是怎么做到的呢?
    在这里插入图片描述
    (2)、其实是由于有 Makefile文件的存在,才能使得直接执行一个make 指令就能够编译出可执行文件!

    ifneq ($(KERNELRELEASE),)
    obj-m :=charDev.o
    else
    PWD := $(shell pwd)                // 表示取当前的pwd值赋给PWD变量
    KDIR := /lib/modules/'uname -r'/build    // 取内核的build目录给KDIR
    all:
    	make -C $(KDIR) M=$(PWD)      // -C:表示change,change到内核源码里面去编译, M=$( ):指定了要编译驱动的源码目录;因此就该程序会再次进入我们这个makefile文件进行执行,接下来继续去判断 KERNELRELEASE 变量.......
    clean:
    	rm -rf *.o *.ko *.mod.c *.symvers *.c~ *~
    endif
    

    在进行Makefile编写的时候,完全可以根据这里的详细内容进行相应的修改!

    4、make menuconfig // make 指令来编译内核的实质:

    (1)、 所谓的make menuconfig就是配置Kconfig文件中相应的item的值,另外**配置完的make menuconfig之后,就会生成相应的 .config文件,可以通过 vim .config 来查看刚才通过图像界面进行的相关配置 **, .config文件是动态变化的,反应的是你执行make menuconfig之后进行的一些操作。
    (2)、make 的实质: 遍历所有目录下的makefile,并依照makefile的内容编译相关代码。
    (3)、对下面的这个进行相关的说明:

    obj- $(CONFIG_MINI210_LEDS)  += mini210_leds.o  
      // 在上一部分学到如何写makefile文件的时候,obj-*后面直接跟着的是m,而在这里是"$(CONFIG_MINI210_LEDS)",故这个跟在make menuconfig 中进行的操作有关, 如果你在make menuconfig 中选定了这个,故这里的$(CONFIG_MIN210_LEDS) 就为 Y,若没选定,在这里就为 M。  
    
    • obj-y : 编译到内核中;
    • obj-m:编译为驱动的形式。

    在这里插入图片描述
    接下来是整体的知识框图,用来说明整体上 make menuconfig 和 make 操作的实质
    在这里插入图片描述

    5、接下来将进行实践,将自己所编写的字符设备驱动程序,添加到内核里面。

    大致的技术思路:
    在这里插入图片描述
    (1)、linux 下,所有的驱动都会按照类型进行分类,比如说你编写的是一个字符设备驱动,其位于 /driver/char/下,所以要想添加charDev驱动到内核中,首先要做的就是为charDev在/drive/char/Kconfig里面添加一个config条目,比如 TEST_CHAR 。
    添加config 条目的书写规则

    config CONFIG_TEST_CHAR
    	tristate "my first char device"
    	help                    // 用来对你所写的这个驱动函数进行相应的说明
    	   My first char device 
    	
    

    (2)、然后需要修改 linux-2.6.32.2/drive/char/Makefile 文件,在这个文件里面添加如下代码:

    obj-$(CONFIG_TEST_CHAR) += charDev.o
    

    (3)、需要将 charDev源代码copy到 linux-2.6.32.2/drivers/char下面
    实质说明: 其实通过第二步第三步,将内核源码注册到内核源码树中!
    (4)、 make menuconfig ,选中 CONFIG_TEST_CHAR项目,设定为 [M] 或者 [Y] .
    在这里,通过 make menuconfig 指令进入到配置界面的时候,你刚才所添加的字符设备驱动位于 **”Character devices ----> “**中,就可以找到刚才所添加的配置信息。(编译完之后,可以通过vim .config查看有没有将该设备驱动进行相应的配置)
    (5)、重新编译即可。

    展开全文
  • 如何编写一个简单的linux内核模块和设备驱动程序。我将学习到如何内核模式下以三种不同的方式来打印hello world,这三种方式分别是: printk(),/proc文件,/dev下的设备文件。 准备:安装内核模块的编译环境 ...


    Linux设备驱动Hello World程序介绍

    如何编写一个简单的linux内核模块和设备驱动程序。我将学习到如何在内核模式下以三种不同的方式来打印hello world,这三种方式分别是: printk(),/proc文件,/dev下的设备文件。

    准备:安装内核模块的编译环境

    一个内核模块kernel module是一段能被内核动态加载和卸载的内核代码,因为内核模块程序是内核的一个部分,并且和内核紧密的交互,所以内核模块不可能脱离内核编译环境,至少,它需要内核的头文件和用于加载的配置信息。编译内核模块同样需要相关的开发工具,比如说编译器。为了简化,本文只简要讨论如何在Debian、Fedora和其他以.tar.gz形式提供的原版linux内核下进行核模块的编译。在这种情况下,你必须根据你正在运行内核相对应的内核源代码来编译你的内核模块kernel module(当你的内核模块一旦被装载到你内核中时,内核就将执行该模块的代码)

    必须要注意内核源代码的位置,权限:内核程序通常在/usr/src/linux目录下,并且属主是root。如今,推荐的方式是将内核程序放在一个非root用户的home目录下。本文中所有命令都运行在非root的用户下,只有在必要的时候,才使用sudo来获得临时的root权限。配置和使用sudo可以man sudo(8) visudo(8) 和sudoers(5)。或者切换到root用户下执行相关的命令。不管什么方式,你都需要root权限才能执行本文中的一些命令。

    在Debian下编译内核模块的准备

    使用如下的命令安装和配置用于在Debian编译内核模块的module-assitant包

    1
    $sudoapt-get installmodule-assistant

    以此你就可以开始编译内核模块,你可以在《Debian Linux Kernel Handbook》这本书中找到对Debian内核相关任务的更深度的讨论。

    Fedora的kernel-devel包包含了你编译Fedora内核模块的所有必要内核头文件和工具。你可以通过如下命令得到这个包。

    1
    $sudoyum installkernel-devel

    有了这个包,你就可以编译你的内核模块kernel modules。关于Fedora编译内核模块的相关文档你可以从Fedora release notes中找到。

    一般Linux 内核源代码和配置

    (译者注,下面的编译很复杂,如果你的Linux不是上面的系统,你可以使用REHL AS4系统,这个系统的内核就是2.6的内核,并且可以通过安装直接安装内核编译支持环境,从而就省下了如下的步骤。而且下面的步骤比较复杂,建议在虚拟机安装Linux进行实验。)

    如果你选择使用一般的Linux内核源代吗,你必须,配置,编译,安装和重启的你编译内核。这个过程非常复杂,并且本文只会讨论使用一般内核源代码的基本概念。

    linux的著名的内核源代码在http://kernel.org上都可以找到。最近新发布的稳定版本的代码在首页上。下载全版本的源代码,不要下载补丁代码。例如,当前发布稳定版本在url: http://kernel.org/pub/linux/kernel/v2.6/linux-2.6.21.5.tar.bz2上。如果需要更快速的下载,从htpp://kernel.org/mirrors上找到最近的镜像进行下载。最简单获得源代码的方式是以断点续传的方式使用wget。如今的http很少发生中断,但是如果你在下载过程中发生了中断,这个命令将帮助你继续下载剩下的部分。

    1
    $ wget -c  http://kernel.org/pub/linux/kernel/v2.6/linux-2.6.21.5.tar.bz2

    解包内核源代码

    1
    $tarxjvf linux-<version>.tar.bz2

    现在你的内核源代码位于linux-/目录下。转到这个目录下,并配置它:

    1
    2
    $cdlinux-<version>
    $makemenuconfig

    一些非常易用的编译目标make targets提供了多种编译安装内核的形式:Debian 包,RPM包,gzip后的tar文件 等等,使用如下命令查看所有可以编译的目标形式

    1
    $makehelp

    一个可以工作在任何linux的目标是:(译者注:REHL AS4上没有tar-pkg这个目标,你可以任选一个rpm编译,编译完后再上层目录可以看到有一个linux-.tar.gz可以使用)

    1
    $maketar-pkg

    当编译完成后,可以调用如下命令安装你的内核

    1
    $sudotar -C / -xvf linux-<version>.tar

    在标准位置建立的到内核源代码的链接

    1
    $sudoln -s <location of top-levelsourcedirectory> /lib/modules/'uname -r'/build

    现在已经内核源代码已经可以用于编译内核模块了,重启你的机器以使得你根据新内核程序编译的内核可以被装载。

    使用printk()函数打印”Hello World”

    我们的第一个内核模块,我们将以一个在内核中使用函数printk()打印”Hello world”的内核模块为开始。printk是内核中的printf函数。printk的输出打印在内核的消息缓存kernel message buffer并拷贝到/var/log/messages(关于拷贝的变化依赖于如何配置syslogd)

    下载hello_printk 模块的tar包 并解包:

    1
    $tarxzvf hello_printk.tar.gz

    这个包中包含两个文件:Makefile,里面包含如何创建内核模块的指令和一个包含内核模块源代码的hello_printk.c文件。首先,我们将简要的过一下这个Makefile 文件。

    1
    obj-m := hello_printk.o

    obj-m指出将要编译成的内核模块列表。.o格式文件会自动地有相应的.c文件生成(不需要显示的罗列所有源代码文件)

    1
    KDIR  := /lib/modules/$(shelluname-r)/build

    KDIR表示是内核源代码的位置。在当前标准情况是链接到包含着正在使用内核对应源代码的目录树位置。

    1
    PWD := $(shell pwd)

    PWD指示了当前工作目录并且是我们自己内核模块的源代码位置

    1
    2
    default:
         $(MAKE) -C $(KDIR) M=$(PWD) modules

    default是默认的编译连接目标;即,make将默认执行本条规则编译目标,除非程序员显示的指明编译其他目标。这里的的编译规则的意思是,在包含内核源代码位置的地方进行make,然后之编译$(PWD)(当前)目录下的modules。这里允许我们使用所有定义在内核源代码树下的所有规则来编译我们的内核模块。

    现在我们来看看hello_printk.c这个文件

    1
    2
    3
    4
    #include
        <linux/init.h>
    #include
        <linux/module.h>

    这里包含了内核提供的所有内核模块都需要的头文件。这个文件中包含了类似module_init()宏的定义,这个宏稍后我们将用到

    1
    2
    3
    4
    5
    staticint __init
    hello_init(void){
        printk("Hello, world!n");
        return0;
    }

    这是内核模块的初始化函数,这个函数在内核模块初始化被装载的时候调用。__init关键字告诉内核这个代码只会被运行一次,而且是在内核装载的时候。printk()函数这一行将打印一个”Hello, world”到内核消息缓存。printk参数的形式在大多数情况和printf(3)一模一样。

    1
    2
    module_init(hello_init);
    module_init()

    宏告诉内核当内核模块第一次运行时哪一个函数将被运行。任何在内核模块中其他部分都会受到内核模块初始化函数的影响。

    1
    2
    3
    4
    5
    staticvoid __exit
    hello_exit(void){
        printk("Goodbye, world!n");
    }
    module_exit(hello_exit);

    同样地,退出函数也只在内核模块被卸载的时候会运行一次,module_exit()宏标示了退出函数。__exit关键字告诉内核这段代码只在内核模块被卸载的时候运行一次。

    1
    2
    3
    4
    5
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Valerie Henson val@nmt.edu");
    MODULE_DESCRIPTION("Hello, world!" minimal module");
    MODULE_VERSION("printk");
    MODULE_LICENSE()

    宏告诉内核,内核模块代码在什么样的license之下,这将影响主那些符号(函数和变量,等等)可以访问主内核。GPLv2 下的模块(如同本例子中)能访问所有的符号。某些内核模块license将会损害内核开源的特性,这些license指示内核将装载一些非公开或不受信的代码。如果内核模块不使用MODULE_LICENSE()宏,就被假定为非GPLv2的,这会损害内核的开源特性,并且大部分Linux内核开发人员都会忽略来自受损内核的bug报告,因为他们无法访问所有的源代码,这使得调试变得更加困难。剩下的MODULE_*()这些宏以标准格式提供有用的标示该内核模块的信息(译者注:这里意思是,你必须使用GPLv2的license,否则你的驱动程序很有可能得不到Linux社区的开发者的支持 :))

    现在,开始编译和运行代码。转到相应的目录下,编译内核模块

    1
    2
    $cdhello_printk 
    $make

    接着,装载内核模块,使用insmod指令,并且通过dmesg来检查打印出的信息,dmesg是打印内核消息缓存的程序。

    1
    2
    $sudoinsmod ./hello_printk.ko 
    $ dmesg | tail

    你将从dmesg的屏幕输出中看见”Hello world!”信息。现在卸载使用rmmod卸载内核模块,并检查退出信息。

    1
    2
    $sudormmod hello_printk 
    $ dmesg | tail

    到此,你就成功地完成了对内核模块的编译和安装!

    使用/proc的Hello, World!

    一种用户程序和内核通讯最简单和流行的方式是通过使用/proc下文件系统进行通讯。/proc是一个伪文件系统,从这里的文件读取的数据是由内核返回的数据,并且写入到这里面的数据将会被内核读取和处理。在使用/proc方式之前,所用用户和内核之间的通讯都不得不使用系统调用来完成。使用系统调用意味着你将在要在查找已经具有你需要的行为方式的系统调用(一般不会出现这种情况),或者创建一种新的系统调用来满足你的需求(这样就要求对内核全局做修改,并增加系统调用的数量,这是通常是非常不好的做法),或者使用ioctl这个万能系统调用,这就要求要创建一个新文件类型供ioctl操作(这也是非常复杂而且bug比较多的方式,同样是非常繁琐的)。/proc提供了一个简单的,无需定义的方式在用户空间和内核之间传递数据,这种方式不仅可以满足内核使用,同样也提供足够的自由度给内核模块做他们需要做的事情。

    为了满足我们的要求,我们需要当我们读在/proc下的某一个文件时将会返回一个“Hello world!”。我们将使用/proc/hello_world这个文件。下载并解开hello proc这个gzip的tar包后,我们将首先来看一下hello_proc.c这个文件

    1
    2
    3
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/proc_fs.h>

    这次,我们将增加一个proc_fs头文件,这个头文件包括驱动注册到/proc文件系统的支持。当另外一个进程调用read()时,下一个函数将会被调用。这个函数的实现比一个完整的普通内核驱动的read系统调用实现要简单的多,因为我们仅做了让”Hello world”这个字符串缓存被一次读完。

    1
    2
    3
    4
    staticint
    hello_read_proc(char*buffer, char**start,off_t offset,
                    intsize, int*eof, void*data)
    {

    这个函数的参数值得明确的解释一下。buffer是指向内核缓存的指针,我们将把read输出的内容写到这个buffer中。start参数多用更复杂的/proc文件;我们在这里将忽略这个参数;并且我只明确的允许offset这个的值为0。size是指buffer中包含多字节数;我们必须检查这个参数已避免出现内存越界的情况,eof参数一个EOF的简写,用于返回文件是否已经读到结束,而不需要通过调用read返回0来判断文件是否结束。这里我们不讨论依靠更复杂的/proc文件传输数据的方法。这个函数方法体罗列如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
        char*hello_str = "Hello, world!\n";
        intlen = strlen(hello_str);/* Don't include the null byte. */
        /*     * We only support reading the whole string at once.     */
        if(size < len)
            return< -EINVAL;
        /*     * If file position is non-zero, then assume the string has
        * been read and indicate there is no more data to be read.
        */
        if(offset != 0)
            return0;
        /*     * We know the buffer is big enough to hold the string.     */
        strcpy(buffer, hello_str);
        /*     * Signal EOF.     */
        *eof = 1;
        returnlen;
    }

    下面,我们需将内核模块在初始化函数注册在/proc 子系统中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    staticint __init
    hello_init(void){
        /*
        * Create an entry in /proc named "hello_world" that calls
        * hello_read_proc() when the file is read.
        */
        if(create_proc_read_entry("hello_world", 0,
                            NULL, hello_read_proc, NULL) == 0) {
            printk(KERN_ERR
            "Unable to register "Hello, world!" proc filen");
            return-ENOMEM;
        }
        return0;
    }
    module_init(hello_init);

    当内核模块卸载时,需要在/proc移出注册的信息(如果我们不这样做的,当一个进程试图去访问/proc/hello_world,/proc文件系统将会试着执行一个已经不存在的功能,这样将会导致内核崩溃)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    staticvoid __exit
    hello_exit(void){
        remove_proc_entry("hello_world", NULL);
    }
    module_exit(hello_exit);
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Valerie Henson val@nmt.edu");
    MODULE_DESCRIPTION(""Hello, world!" minimal module");
    MODULE_VERSION("proc");

    下面我们将准备编译和装载模组

    1
    2
    3
    $cdhello_proc 
    $make 
    $sudoinsmod ./hello_proc.ko

    现在,将会有一个称为/proc/hello_world的文件,并且读这个文件的,将会返回一个”Hello world”字符串。

    1
    2
    $cat/proc/hello_world
    Hello, world!

    你可以为为同一个驱动程序创建多个/proc文件,并增加相应写/proc文件的函数,创建包含多个/proc文件的目录,或者更多的其他操作。如果要写比这个更复杂的驱动程序,可以使用seq_file函数集来编写是更安全和容易的。关于这些更多的信息可以看《Driver porting: The seq_file interface》

    Hello, World! 使用 /dev/hello_world

    现在我们将使用在/dev目录下的一个设备文件/dev/hello_world实现”Hello,world!” 。追述以前的日子,设备文件是通过MAKEDEV脚本调用mknod命令在/dev目录下产生的一个特定的文件,这个文件和设备是否运行在改机器上无关。到后来设备文件使用了devfs,devfs在设备第一被访问的时候创建/dev文件,这样将会导致很多有趣的加锁问题和多次打开设备文件的检查设备是否存在的重试问题。当前的/dev版本支持被称为udev,因为他将在用户程序空间创建到/dev的符号连接。当内核模块注册设备时,他们将出现在sysfs文件系统中,并mount在/sys下。一个用户空间的程序,udev,注意到/sys下的改变将会根据在/etc/udev/下的一些规则在/dev下创建相关的文件项。

    下载hello world内核模块的gzip的tar包,我们将开始先看一下hello_dev.c这个源文件。

    1
    2
    3
    4
    5
    #include <linux/fs.h>
    #include <linux/init.h>
    #include <linux/miscdevice.h>
    #include <linux/module.h>
    #include <asm/uaccess.h>

    正如我们看到的必须的头文件外,创建一个新设备还需要更多的内核头文件支持。fs.sh包含所有文件操作的结构,这些结构将由设备驱动程序来填值,并关联到我们相关的/dev文件。miscdevice.h头文件包含了对通用miscellaneous设备文件注册的支持。 asm/uaccess.h包含了测试我们是否违背访问权限读写用户内存空间的函数。hello_read将在其他进程在/dev/hello调用read()函数被调用的是一个函数。他将输出”Hello world!”到由read()传入的缓存。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    staticssize_t hello_read(structfile * file, char* buf, size_tcount, loff_t *ppos)
    {
        char*hello_str = "Hello, world!n";
        intlen = strlen(hello_str);/* Don't include the null byte. */
        /*     * We only support reading the whole string at once.     */
        if(count < len)
            return-EINVAL;
        /*
        * If file position is non-zero, then assume the string has
        * been read and indicate there is no more data to be read.
        */
        if(*ppos != 0)
            return0;
        /*
        * Besides copying the string to the user provided buffer,
        * this function also checks that the user has permission to
        * write to the buffer, that it is mapped, etc.
        */
        if(copy_to_user(buf, hello_str, len))
            return-EINVAL;
        /*
        * Tell the user how much data we wrote.
        */
        *ppos = len;
        returnlen;
    }

    下一步,我们创建一个文件操作结构file operations struct,并用这个结构来定义当文件被访问时执行什么动作。在我们的例子中我们唯一关注的文件操作就是read。

    1
    2
    3
    4
    staticconst struct file_operations hello_fops = {
        .owner        = THIS_MODULE,
        .read        = hello_read,
    };

    现在,我们将创建一个结构,这个结构包含有用于在内核注册一个通用miscellaneous驱动程序的信息。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    staticstruct miscdevice hello_dev = {
        /*
        * We don't care what minor number we end up with, so tell the
        * kernel to just pick one.
        */
        MISC_DYNAMIC_MINOR,
        /*    
        * Name ourselves /dev/hello.    
        */
        "hello",
        /*    
        * What functions to call when a program performs file
        * operations on the device.
        */
        &hello_fops
    };

    在通常情况下,我们在init中注册设备

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    staticint __init
    hello_init(void){
        intret;
        /*
        * Create the "hello" device in the /sys/class/misc directory.
        * Udev will automatically create the /dev/hello device using
        * the default rules.
        */
        ret = misc_register(&hello_dev);
        if(ret)
            printk(KERN_ERR
                "Unable to register "Hello, world!" misc devicen");
        returnret;
    }
    module_init(hello_init);

    接下是在卸载时的退出函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    staticvoid __exit
    hello_exit(void){
        misc_deregister(&hello_dev);
    }
    module_exit(hello_exit);
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Valerie Henson val@nmt.edu>");
    MODULE_DESCRIPTION(""Hello, world!" minimal module");
    MODULE_VERSION("dev");

    编译并加载模块:

    1
    2
    3
    $cdhello_dev 
    $make 
    $sudoinsmod ./hello_dev.ko

    现在我们将有一个称为/dev/hello的设备文件,并且这个设备文件被root访问时将会产生一个”Hello, world!”

    1
    2
    $sudocat /dev/hello
     Hello, world!

    但是我们不能使用普通用户访问他:

    1
    2
    3
    4
    5
    $cat/dev/hello
    cat:/dev/hello: Permission denied 
     
    $ls-l
    /dev/hellocrw-rw---- 1 root root 10, 61 2007-06-20 14:31 /dev/hello

    这是有默认的udev规则导致的,这个条规将标明当一个普通设备出现时,他的名字将会是/dev/,并且默认的访问权限是0660(用户和组读写访问,其他用户无法访问)。我们在真实情况中可能会希望创建一个被普通用户访问的设备驱动程序,并且给这个设备起一个相应的连接名。为达到这个目的,我们将编写一条udev规则。

    udev规则必须做两件事情:第一创建一个符号连接,第二修改设备的访问权限。

    下面这条规则可以达到这个目的:

    1
    KERNEL=="hello", SYMLINK+="hello_world", MODE="0444"

    我们将详细的分解这条规则,并解释每一个部分。KERNEL==”hello” 标示下面的的规则将作用于/sys中设备名字”hello”的设备(==是比较符)。hello 设备是我们通过调用misc_register()并传递了一个包含设备名为”hello”的文件操作结构file_operations为参数而达到的。你可以自己通过如下的命令在/sys下查看

    1
    $ls-d /sys/class/misc/hello//sys/class/misc/hello/

    SYMLINK+=”hello_world” 的意思是在符号链接列表中增加 (+= 符号的意思着追加)一个hello_world ,这个符号连接在设备出现时创建。在我们场景下,我们知道我们的列表的中的只有这个符号连接,但是其他设备驱动程序可能会存在多个不同的符号连接,因此使用将设备追加入到符号列表中,而不是覆盖列表将会是更好的实践中的做法。

    MODE=”0444″的意思是原始的设备的访问权限是0444,这个权限允许用户,组,和其他用户可以访问。

    通常,使用正确的操作符号(==, +=, or =)是非常重要的,否则将会出现不可预知的情况。

    现在我们理解这个规则是怎么工作的,让我们将其安装在/etc/udev目录下。udev规则文件以和System V初始脚本目录命名的同种方式的目录下,/etc/udeve/rules.d这个目录,并以字母/数字的顺序。和System V的初始化脚本一样,/etc/udev/rules.d下的目录通常符号连接到真正的文件,通过使用符号连接名,将使得规则文件已正确的次序得到执行。
    使用如下的命令,拷贝hello.rules文件从/hello_dev目录到/etc/udev目录下,并创建一一个最先被执行的规则文件链接在/etc/udev/rules.d目录下。

    1
    2
    $sudocp hello.rules /etc/udev/ 
    $sudoln -s ../hello.rules/etc/udev/rules.d/010_hello.rules

    现在我们重新装载驱动程序,并观察新的驱动程序项

    1
    2
    3
    4
    5
    $sudormmod hello_dev 
    $sudoinsmod ./hello_dev.ko 
    $ls-l /dev/hello
    cr--r--r-- 1 root root 10, 61 2007-06-19 21:21 /dev/hello 
    lrwxrwxrwx 1 root root      5 2007-06-19 21:21 /dev/hello_world-> hello

    最后,检查你可以使用普通用户访问/dev/hello_world设备.

    1
    2
    3
    4
    5
    $cat/dev/hello_world
    Hello, world! 
     
    $cat/dev/hello
    Hello, world!
    本文转自陈皓( 酷壳 – CoolShell.cn 


    展开全文
  • <br />对象名称来引用文件 内核模式设备驱动程序引用一个文件及其对象的名称。此名称是与该文件的完整路径一起 /DosDevices。例如对于 C:/Windows/Example.txt 文件的对象名称是 /DosDevices/C:/...
  • 05年本科毕业设计做的是Linux下驱动的剖析,当时就买了一本《Linux设备驱动程序(第二版)》,但是没有实现将最简单的helloworld程序编译成模块,加载到kernel里。不过,现在自己确实打算做一款芯片的Linux的驱动,...
  • 本节知识点:如何把自定义的设备驱动编译到内核中。 实际开发作用:以往测试驱动或者加载驱动通过insmod xxx.ko 我们一般会修改脚本使得上电自动加载驱动,但是我们一旦 不小心删除了指定路径下的驱动文件后就无法...
  • 虚拟SPI Linux内核驱动程序能够模拟故障/不可靠功能。 (c)Matthias Behr,2011年-2020年 目标/动机 开发该模块的目的是测试例如两个通过SPI从一个ECU / SOC另一个的通信模块。 该模块应有助于注入那些故障,...
  • O文件,然后用insmod加载到内核中,但是在2.6版本上会不可以,会报 格式不正确的错误,如何解决这个问题,由于版本差异,在2.6版本上运行驱动程序是以.ko格式存在的 下面以那个经典的例子: 先在/home里创建一...
  • 实用标准文案 本讲主要概述 Linux 设备驱动框架 驱动程序的配置文件及常用的加载驱动程序的方法 并 且介绍 Red Hat Linux 安装程序是如何加载驱动的通过了解这个过程我们可以自己将 驱动程序引导盘中安装完系统...
  • 首先说说应用层的调试吧.当我们在调试windows可执行程序的时候,通过将PE文件头中的ImageBase和AddressOfEntryPoint相加,从而得出第一条指令的地址....然而驱动程序运行在内核里面,所有的驱动程序共享一个地址空间.
  • 并且介绍Red Hat Linux安装程序是如何加载驱动的,通过了解这个过程, 我们可以自己将驱动程序引导盘中;安装完系统后,使用kudzu自动配置硬件程序。  Linux设备驱动概述  1. 内核和驱动模块  操作系统是...
  • 并且介绍Red Hat Linux安装程序是如何加载驱动的,通过了解这个过程, 我们可以自己将驱动程序引导盘中;安装完系统后,使用kudzu自动配置硬件程序。 Linux设备驱动概述 1. 内核和驱动模块 操作系统是通过各种...
  • 并且介绍Red Hat Linux安装程序是如何加载驱动的,通过了解这个过程, 我们可以自己将驱动程序引导盘中;安装完系统后,使用kudzu自动配置硬件程序。 Linux设备驱动概述 1. 内核和驱动模块 操作系统是通过各种...
  • linux 设备驱动程序

    2009-05-10 16:57:23
    第12章“加载块设备驱动程序”介绍了如何实现块设备驱动程序,强调和字符设备驱动程序的区别。接下来,第13章“Mmap和DMA”讲解了我们原先在内存管理中留下来的问题:mmap和DMA。此为止,关于字符设备和块设备驱动...
  • 以简单的hello world程序为例,我们如何把它写成一个驱动模块,并加载到Linux内核里面呢? 一开始,你要保证你的Linux系统有内核源码树。现在的Linux发行版安装时记得选择是Developer模式安装,基本上就...
  • 在许多情况下,例如尚未为较新内核中的某些设备准备好驱动程序,我们可能会选择使用安装系统中的较旧版本的内核。 在本文中,我们将逐步完成使系统引导加载程序grub(版本2)引导在Ubuntu 20.04上引导安装系统...
  • 编译和加载本章开头的 "hello world" 例子包含了一个简短的建立并加载模块系统中去的演示. 当然, 整个过程比我们目前看到的多. 本节提供了更多细节关于一个模块作者如何将源码转换成内核中的运行的子系统.2.4.1. ...
  • 思 ...们发现,内核加载了许多硬件设备的驱动,而搜索/etc目录,却没有发现任何脚本负责加载这些硬件设备驱动程序的模块。那么这些模块又是如何加载的 呢? 每一个设备都有Verdon
  • cat和echo测试驱动程序

    2016-09-13 05:22:23
    我用linuxC实现了read和write的阻塞方式,驱动文件编写之后,编译成模块,加载内核,mknod挂载设备节点文件(/dev/lee),之后用cat和echo测试读写结果: echo "hello" > /dev/lee cat /dev/lee 恳请大神...

空空如也

空空如也

1 2 3 4 5 ... 10
收藏数 200
精华内容 80
关键字:

驱动程序如何加载到内核