本节课,我们就开始来编写点灯的驱动程序了。我们首先创建一个.c文件,叫led_drv.c。然后再在这个led_drv.c里面写代码。我们先想一下,作为一个驱动,我们要怎么做什么事情,首先我们要提供基本的接口。既然应用层的人使用open,write,read这些库函数,那我们最终也要提供对应他们的接口,led_drv_open,led_drv_wirte和led_drv_read。
这样,我们这三个函数就写完了。但其实,我们还需要加点东西,这样才符合它的这个接口:
int led_drv_open(struct inode *inode, struct file *file)
{
return 0;
}
int led_drv_write(struct file *file,
const char __user *buf,
size_t count, loff_t * ppos)
{
return 0;
}
int led_drv_read(struct file *filp,
char __user *buff,
size_t count, loff_t *offp)
{
return 0;
}
这样,我们就写完这个三个函数了。但是,我们怎么把这几个函数告诉内核,这样它才知道怎么来调用我们呢?这里,我们还需要一个函数,我们把它称做led_init函数,在这个led_init函数里我们要做的,就是把我们这些led_open、led_read、led_open函数告诉内核。那么,我们又该怎么做呢?这里,我们需要用到一个结构体,叫作file_operations结构体:
顾名思义,这是一个文件操作结构体。我们看到在这个结构体里,有很多的函数指针的成员。其中这些函数指针名是和我们库函数名对应的,如read,write,open等,我们使用这些库函数时,最终会根据这个结构体调用到相应的函数。这里,由于我们只写了三个函数,所以我们定义这么一个结构体实例时,就初始化open,read,write部分就可以了:
struct file_operations led_drv_ops
{
.open=led_drv_open;
.read=led_drv_read;
.write=led_drv_write;
}
接着,我们需要在led_init函数里调用resgiter_chrdev函数,这个函数的作用是注册一个字符设备驱动,我们可以看一下它所需要的参数:
我们看到,它的第一个参数是major,也就是主设备号,第二个参数是name,也就是你的设备名称。第三个就是我们要传入的文件操作结构体指针。主设备号的话是从0到256,这里我不想直接填一个具体的数字,因为我不知道有那些序号还给我留着,所以我希望它给我分配一个,所以我们在major这里就可以填0。name的话我们就填led_drv吧,最后再把这个结构体传进去就好了。它分配的设备号最后面是通过返回值来返回的,我们用一个全局变量接收它保存起来就好。同时还要判断一下它是否合理,该函数发生错误时返回的就是一个负值。这样,我们就基本写完了。但这里还有一个疑问,我们的init函数又怎么让内核发现呢?这里,我们需要使用一个带参宏:module_init()。这个带参宏究竟是个什么东西呢?这里我就不分析了,大家可以自己去分析一下,它和我们之前分析U-Boot,内核的时候的那些结构体挺相似的,它把一堆函数强制的规定在某个段,最后面在init/main.c这个函数里会分别对他们进行一一调用。所以,其实我们使用这个带参宏后,就可以把它的初始化函数加到内核初始化函数中。
同样的,既然有加载函数,那肯定就有卸载函数。卸载函数和加载函数写法差不多,我们需要在该函数里调用unregister_chrdev这个函数,并调用module_exit的带参宏。
写完后,我们就可以进行编译生成了。编译的时候,我们需要内核源码里的Makefile才能帮我们编译程序,所以这里我们要写一个Makefile:【这里的Makefile更像超链接,去链接内核源码里的Makefile】
#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
KERN_VER = $(shell uname -r)
KERN_DIR = /lib/modules/$(KERN_VER)/build
# 开发板的linux内核的源码树目录
#KERN_DIR = /root/driver/kernel
obj-m += led_drv.o
all:
make -C $(KERN_DIR) M=`pwd` modules
cp:
cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
我大概讲一下它的意思,KERN_DIR是内核源码目录,-C加上这个变量的值表示进入到这个目录的顶层Makefile中去执行,M=`pwd`则会记录下你当前的目录,等执行完后就可以返回原来的目录的Makefile(也就是这个)去执行。modules是内核Makefile的目标,表示编译模块。
写完后,我们就可以把Makefile和源码文件上传到Linux虚拟机上去编译了:
我们看到,已经编译成功了,接着我们就可以加载这个ko文件了。使用insmod(insert module)命令去加载:insmod led_drv.ko,这里我放到我们的虚拟机上的Linux去加载。
我们看到,没有出现任何错误提示,表明第一步初步完成,这时候我们可以通过lsmod命令查看有没有加载成功。
我们还可以在proc目录下的devices中查看所加载的设备程序:
为了使用驱动,我们还得在/dev目录下创建它的对应设备文件了。使用mknod命令:
创建完节点后,我们就可以写个实例程序来实验它的效果了:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(void)
{
int fd;
int val;
fd = open("/dev/led_drv",O_RDWR);
if(fd < 0)
{
printf("can't open\n");
}
write(fd,&val,4);
return 0 ;
}
我们把程序编译,然后运行看看结果:【虚拟机下执行程序后,还得执行dmesg命令才能显示结果】
可以看到,这里已经是调用成功了,我让这些函数打印了一些相关语句来进行调试。
至此,我们的一个最简单的驱动程序就做完了。我们的所有函数,都应该加上static关键字,表示仅在该文件中可以使用。下面给出该驱动程序的代码:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
static int led_drv_open(struct inode *inode, struct file *file);
static int led_drv_write(struct file *file, const char __user *buf,size_t count, loff_t * ppos);
static int led_drv_read(struct file *filp, char __user *buff, size_t count, loff_t *offp);
struct file_operations led_drv_ops={
.open=led_drv_open,
.read=led_drv_read,
.write=led_drv_write
};
int dev_num=0;
static int led_drv_open(struct inode *inode, struct file *file)
{
printk("led_drv open\n");
return 0;
}
static int led_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
printk("led_drv_write\n");
return 0;
}
static int led_drv_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
printk("led_drv_read\n");
return 0;
}
static int led_drv_init(void)
{
dev_num=register_chrdev(0,"led_drv",&led_drv_ops);
if(dev_num<0)
{
printk("Sorry,register char device failed!");
return -1;
}
printk("initialized!\n");
}
int led_drv_exit(void)
{
unregister_chrdev(dev_num,"led_drv");
printk("led_drv exit!\n");
return 0;
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
原文链接:https://blog.csdn.net/xiaokangdream/article/details/80498171