制作第一个Linux驱动程序

学习目标:

  • 编写一个简单的Linux驱动程序,实现应用程序能通过系统调用,调用相应驱动函数,驱动程序中仅打印信息,不实现一些特定功能

 写第一个驱动程序需要以下几个步骤:

1)写出open、read等系统调用在进入内核空间中调用的对应函数xxx_open、xxx_read

2)定义file_operations类型结构体,用写好的xxx_open、xxx_read函数填充file_operations结构体成员

3)写一个xxx_init函数调用register_chardev函数注册填充好的file_operations类型结构体,目的时当调用它时将相关信息告诉内核

4)通过module_init()来修饰xxx_init入口函数

5)写驱动的xxx_exit出口函数,调用这个unregister_chrdev()函数卸载挂入内核中的file_operations类型结构体

6)通过module_exit()来修饰出口函数

7)模块许可证声明, 最常见的是以MODULE_LICENSE( "GPL v2" )来声明


1、创建一个驱动源文件first_drv.c

代码如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
//#include <asm/arch/regs-gpio.h>
//#include <asm/hardware.h>

int major;
static int first_drv_open(struct inode *inode, struct file *file); static ssize_t first_drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos); static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos); struct file_operations first_drv_fileop = { //---->① .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = first_drv_open, .read = first_drv_read, .write = first_drv_write, };
/*inode表示具体文件,file结构用来追踪运行时的信息 */
static int first_drv_open(struct inode *inode, struct file *file) { printk("first_drv_open\n"); /*内核的打印用printk,而不是printf函数) return 0; } static ssize_t first_drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { printk("first_drv_read\n"); return 0; } static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { printk("first_drv_read\n"); return 0; } static int first_drv_init(void) { major = register_chrdev(0, "first_drv", &first_drv_fileop); //---->② return 0; } static void first_drv_exit(void) { unregister_chrdev(major, "first_drv"); //---->③ } module_init(first_drv_init); //---->④ module_exit(first_drv_exit); //---->⑤ MODULE_LICENSE("GPL");

①定义file_operations结构体,并填充相关成员函数,如open时,通过系统调用,最终会调用到.open函数指针指向函数

②注册file_operation结构体,将相关信息告诉内核,只有注册后file_operations内部成员才有效。register_chrdev函数第一项参数设置为0,让系统自动分配主设备号

③将相关信息从内核中去除

④修饰函数,当执行insmod时,将调用first_drv_init函数(此例中,即调用register_chrdev函数)

⑤修饰函数,当执行rmmod时,将调用first_drv_exit函数(此例中,即调用unregister_chrdev函数)

2、创建一个编译驱动的Makefile

KERN_DIR = /home/book/self_learn/01_linux_develop/02_embeded_dir/02_kernel/linux-3.4.2 #----->①

all:
    make -C $(KERN_DIR) M=`pwd` modules #---->②

clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order

obj-m    += first_drv.o #----->③

①编译驱动时依赖内核存放的目录,内核必须是已经编译好的

②make -C切换到内核目录,调用内核目录的Makefile;M='pwd',当用户需要以内核为基础编译一个外部模块,需要将M='pwd'加入其中,表示到驱动的目录中查找编译源代码,modules是一个参数,就是告诉内核在编译是将驱动编译成模块,不编译进内核

③表示将first_drv.o编译成模块

3、执行make,编译生成first_drv.ko

4、将first_drv.ko拷贝到跟文件系统并加载驱动模块

执行insmod命令将first_drv.ko加载到内核

5、手动创建设备节点

在注册file_operations结构体时,我们选择了让系统自动分配主设备号,在创建设备节点之前,应先获取系统自动分配的主设备号。执行cat /proc/devices 命令可以看出系统自动分配主设备号

 

系统自动分配的主设备号时252,后面跟的是设备名称,设备名称是由register_chrdev传入的第二个参数决定的。获取主设备号之后,执行 mknod /dev/first c 252 0 命令在dev目录中创建设备节点

 

6、编写应用程序,进行驱动测试

应用代码first_drv_test.c:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    int fd;
    int val = 1;
    
    fd = open("/dev/first", O_RDWR);
    if(fd == -1)
    {
        printf("can't open...\n");
        exit(EXIT_FAILURE);
    }
    
    read(fd, &val, sizeof(val));
    
    exit(EXIT_SUCCESS);
}

执行arm-linux-gcc -o first_drv_test first_drv_test.c编译测试应该程序,并将编译成功的应用程序拷贝到网络文件系统中,执行应用程序

运行成功,由打印结果可以看出,应用程序中的open函数和read函数调用了对应驱动函数中的first_open和first_read,第一个驱动程序测试成功!

7、改进first_drv.c驱动程序

由上述操作结果可以看出,每加载一次驱动程序都要手动在/dev目录中创将相应设备节点,这样做太麻烦了。可以使用自动创建设备节点,Linux有udev、mdev的机制,而我们的ARM开发板上移植的busybox有mdev机制,然后mdev机制会通过/sys目录中class类来找到相应类的驱动设备来自动创建设备节点 (前提需要有mdev)

1)首先创建一个class设备类,class是C语言中的一个结构体,是软件开发的一个设备的高级视图,它抽象出低级的实现细节,然后在class类下,创建一个class_device,即类下面创建类的设备:

static struct class *first_drv_class;
static struct class_device    *first_drv_class_dev;

2)在first_drv_init后面添加如下代码

//创建类,它会在sys/class目录下创建firstdrv_class这个类
firstdrv_class= class_create(THIS_MODULE,"firstdrv"); //在该类下创建设备 firstdrv_class_devs=class_device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,"xyz");

注意:在高版本的linux内核中class_device_create用device_create函数代替

3)同理,在first_drv_exit中添加卸载类和设备的函数,在卸载驱动程序后,自动删除创建的设备节点

 class_device_unregister(firstdrv_class_devs);      //注销类设备,与class_device_create对应
 class_destroy(firstdrv_class);                    //注销类,与class_create对应

4)按照上述过程,重新编译驱动程序,并将编译好的驱动程序加载到内核(注意在加载新驱动程序时,先卸载之前驱动程序,并删除手动创建的设备节点)

由此可见,第一个简单驱动程序创建成功,值得注意的是,自动创建设备节点,必须在mdev机制使能时才能正常使用。

posted on 2020-07-18 21:09  quinoa  阅读(785)  评论(0编辑  收藏  举报