一、概述  

      本文首先描述了一个可以实际测试运行的简单驱动实例,然后由此去讨论Linux下驱动模板的要素,以及Linux上应用程序到驱动的执行过程。这个简单的驱动实例运行直接运行在虚拟机Linux系统上,不需要借助于开发板验证。

      测试环境: VMware+Ubuntu12.04。

二、驱动程序实例

驱动文件hello.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>

#define    HELLO_MAJOR     231
#define    DEVICE_NAME     "HelloModule"

static int hello_open(struct inode *inode, struct file *file){
    printk(KERN_EMERG "hello open.\n");
    return 0;
}

static int hello_write(struct file *file, const char __user * buf, size_t count, loff_t *ppos){
    printk(KERN_EMERG "hello write.\n");
    return 0;
}

static struct file_operations hello_flops = {
    .owner  =   THIS_MODULE,
    .open   =   hello_open,     
    .write  =   hello_write,
};

static int __init hello_init(void){
    int ret;
    
    ret = register_chrdev(HELLO_MAJOR,DEVICE_NAME, &hello_flops);
    if (ret < 0) {
      printk(KERN_EMERG DEVICE_NAME " can't register major number.\n");
      return ret;
    }
    printk(KERN_EMERG DEVICE_NAME " initialized.\n");
    return 0;
}

static void __exit hello_exit(void){
    unregister_chrdev(HELLO_MAJOR, DEVICE_NAME);
    printk(KERN_EMERG DEVICE_NAME " removed.\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

  驱动文件主要包括函数hello_open、hello_write、hello_init、hello_exit,测试案例中并没有赋予驱动模块具有实际意义的功能,只是通过打印日志的方式告知控制台一些调试信息,这样我们就可以把握驱动程序的执行过程。

  在使用printk打印的时候,在参数中加上了“KERN_EMERG”尽可能打印信息输出到控制台上,如果在控制台上看不到调试信息,可以利用dmesg命令查看。(由于printk打印分8个等级,等级高的被打印到控制台上,而等级低的却输出到日志文件中。

编译驱动所需的Makefile

ifneq ($(KERNELRELEASE),)
MODULE_NAME = hellomodule
$(MODULE_NAME)-objs := hello.o
obj-m := $(MODULE_NAME).o
else
KERNEL_DIR = /lib/modules/`uname -r`/build
MODULEDIR := $(shell pwd)

.PHONY: modules
default: modules

modules:
    make -C $(KERNEL_DIR) M=$(MODULEDIR) modules

clean distclean:
    rm -f *.o *.mod.c .*.*.cmd *.ko
    rm -rf .tmp_versions
endif

  编译驱动文件需要一个合适的Makefile,因为编译驱动的时候需要知道内核头文件,编译规则等。

测试驱动的上层应用代码hellotest.c

#include <fcntl.h>
#include <stdio.h>

int main(void)
{
    int fd;
    int val = 1;
    fd = open("/dev/hellodev", O_RDWR);
    if(fd < 0){
        printf("can't open!\n");
    }
    write(fd, &val, 4);
    return 0;
}

  上层测试案例中,首先打开设备文件,然后向设备中写入数据。如此,则会调用驱动中对应的xxx_open和xxx_write函数,通过驱动程序的打印信息可以判断是否真的如愿执行了对应的函数。

三、驱动实例测试

  测试的方法整体来说就是,编译驱动和上层测试应用;加载驱动,通过上层应用调用驱动;最后,卸载驱动。

1、编译驱动

#make

  make命令,直接调用Makefile编译hello.c,最后会生成“hellomodule.ko”。

2、编译上层应用

#gcc hellotest.c -o hellotest

  通过这条命令,就能编译出一个上层应用hellotest。

3、加载驱动

#insmod hellomodule.ko

  insmod加载驱动的时候,会调用函数hello_init(),打印的调试信息如下。

  此外,在"/proc/devices"中可以看到已经加载的模块。

4、创建节点

  虽然已经加载了驱动hellomodule.ko,而且在/proc/devices文件中也看到了已经加载的模块HelloModule,但是这个模块仍然不能被使用,因为在设备目录/dev目录下还没有它对应的设备文件。所以,需要创建一个设备节点。

#mknod /dev/hellodev c 231 0

  在/proc/devices中看到HelloModule模块的主设备号为231,创建节点的时候就是将设备文件/dev/hellodev与主设备号建立连接。这样在应用程序操作文件/dev/hellodev的时候,就会定位到模块HelloModule。

/proc/devices 与 /dev的区别

  • /proc/devices中的设备是驱动程序生成的,它可产生一个major供mknod作为参数。这个文件中的内容显示的是当前挂载在系统的模块。当加载驱动HelloModule的时候,并没有生成一个对应的设备文件来对这个设备进行抽象封装,以供上层应用访问。
  • /dev下的设备是通过mknod加上去的,用户通过此设备名来访问驱动。我以为可以将/dev下的文件看做是硬件模块的一个抽象封装,Linux下所有的设备都以文件的形式进行封装。

5、上层应用调用驱动

#./hellotest

  hellotest应用程序先打开文件“/dev/hellodev”,然后向此文件中写入一个变量val。期间会调用底层驱动中的hello_open和hello_write函数,hellotest的运行结果如下所示。

6、卸载驱动

#rmmod hellomodule

  insmod卸载驱动的时候,会调用函数hello_exit(),打印的调试信息如下。

总结一个模块的操作流程:

  (1)通过insmod命令注册module

  (2)通过mknod命令在/dev目录下建立一个设备文件"xxx",并通过主设备号与module建立连接

  (3)应用程序层通过设备文件/dev/xxx对底层module进行操作

 

参考资料:

             linux驱动开发框架

 
posted on 2015-10-27 15:57  amanlikethis  阅读(35283)  评论(6)    收藏  举报