Linux驱动模版

      本节课,我们就开始来编写点灯的驱动程序了。我们首先创建一个.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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值