linux驱动开发----字符驱动模板

在讲linux驱动前,我想把我对linux驱动的个人认识写在最前面,因为这是我研究linux驱动中的心得,相信你们如果也会同感。

linux驱动首先是一个驱动程序,驱动程序是干啥的?驱动就是让硬件能够听话的干活,硬件能听懂人话吗?当然不能。这就需要驱动软件在中间起个转换的作用,硬件也是有自己的脾气(特性)的,因此驱动软件既要按照硬件的脾气来办事,又得满足上层软件的需求,这就是驱动软件的宿命。

那么讲完驱动程序,我们就回到今天的主题:linux驱动程序,很显然,多了个linux,必然有其意义,从语法上看,linux是属于驱动程序的定语,是表明驱动程序的属性的。对于一般驱动程序来说,只需要配置控制器的寄存器就行,但加了个linux,就不能随性而为了,驱动程序就要受到linxu的约束,这个约束就被称为linux驱动框架。linux的驱动框架有很多:字符设备驱动框架、块设备驱动框架、网络设备驱动框架、spi总线驱动框架、usb总线驱动框架、platform总线驱动框架等等。。。

在这里,顺便提下设备的概念:用户空间只能通过打开设备节点,来实现应用程序与驱动程序之间的数据传输。打个比方:你要想点亮一个led,必须得先构造一个设备,通过这个设备,再去操作io口输出一个高电平或者低电平。这个设备是个虚拟的设备,是由虚拟文件系统(VFS)构造出来的。

之前学习linux驱动的时候,上百度一查linux驱动开发,很多都是直接拿着linux源码中的驱动程序来讲解,但看到linux驱动中各种封装的函数以及复杂的结构体,总让初学linux驱动的人很头疼,让初学者有种不知从何处下手的感觉。

在这里我想把字符驱动讲的简单点,我们先把驱动框架梳理出来,再填充框架中的内容。这就好比现在盖楼一样,先用房屋框架拉起来,在来砌墙一样。

对于字符设备,其驱动框架是由内核确定的,我们如果要开发一个字符设备,就必须按照内核规定的驱动框架来,不然内核是无法加载你所设计的驱动。字符设备的驱动框架主要是:

  • 加载/卸载内核模块;
  • 在驱动初始化函数中注册字符设备,创建设备节点;
  • 构造字符设备file_operation结构体,实现file_operation中定义的行为函数。

下面就针对这三个内容单独分析:

1.加载/卸载内核模块:

由于linux内核是一个宏内核,其通过内核接口函数来实现模块的动态加载。驱动也作为内核模块的一部分,因此要想使用某个驱动时,必须要向内核加载模块,而加载内核模块用的都是用内核提供的模块接口函数module_init()来实现,至于为何是module_init,对于做驱动的人来说完全没必要知道,只需要调用下面这个函数就可以了。对应的从内核卸载模块时用到的是module_exit函数。

module_init(my_driver_init);//加载内核模块
module_exit(my_driver_init);//卸载内核模块

2.在驱动入口函数中注册字符设备,创建设备节点:

前面我们通过module_init将驱动模块加载进内核了,现在我们就要在驱动入口函数里,将设备注册进内核中。

将设备注册进内核用到的函数是

major = register_chrdev(0, "my_drv", &first_drv_fops); // 注册, 告诉内核

一般调用register_chrdev函数时,传入的major参数都设置为0,设置为0表示当前设备的主设备号由系统动态分配产生,避免了认为指定主设备号会与系统中已经存在的主设备号冲突的问题。register_chrdev中的name参数可以随便设置,fops就是字符设备的核心file_operation结构体,第三步就是实现file_operation的中的函数体了。

向内核注册完设备后,需要在/dev目录下自动创建设备节点,如:/dev/my_device。当然,在驱动中也可以不自动创建,而是在linux系统run起来后,通过mknod手动创建设备节点。但这种操作比较麻烦,所以一般都在驱动中自动创建设备节点。首先使用class_create创建一个类,之后就可以使用class_device_create函数在这个类下面创建一个设备了。


mydrv_class = class_create(THIS_MODULE, "mydrv");

mydrv_class_dev = class_device_create(mydrv_class, NULL, MKDEV(major, 0), NULL,                                                 
                                                 "mydev"); /* 在/dev/目录下会出现mydev设备节点 */

相应的,模块卸载时需要在驱动出口函数中做一些操作,主要是从内核中注销设备号,注销设备节点。

unregister_chrdev(major, "my_drv"); // 卸载
class_device_unregister(mydrv_class_dev);
class_destroy(mydrv_class);

3. 构造字符设备file_operation结构体,实现file_operation中定义的行为函数

file_operation是内核提供给字符设备的接口,我们只需要利用file_operation来定义一个自己的结构体,然后去实现结构体中的函数体。

static struct file_operations mydrv_fops = {
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   mydrv_open,     
    .write  =	mydrv_write,
    .read   =   mydrv_read,
    .ioctl  =   mydrv_ioctl,	   
};

file_operation中定义的行为与提供给用户的行为函数是一一对应的:

驱动函数用户接口
mydrv_openopen
mydrv_writewrite
mydrv_readread
mydrv_ioctlioctl

在用户程序中调用open函数,最终就会调用到内核驱动中的mydrv_open函数,因此,file_operation中定义的函数才是驱动的核心,因为你需要在open/write/read/ioctl这些函数中实现对硬件的控制。

其实讲到这里,基本上一个字符驱动的框架已经形成了,我们可以直接填充这个框架,来实现不同的字符驱动。下面给出字符驱动的框架代码:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>


static struct class *mydrv_class;
static struct class_device	*mydrv_class_dev;



static ssize_t my_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	int val;
	
	return 0;
}

static struct file_operations my_drv_fops = {
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   my_drv_open,     
	.write	=	my_drv_write,	   
};



static int my_drv_init(void)
{
	int major;
	major = register_chrdev(0, "my_drv", &my_drv_fops); // 注册, 告诉内核

	mydrv_class = class_create(THIS_MODULE, "mydrv");

	mydrv_class_dev = class_device_create(mydrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/mydev */

	return 0;
}

static void my_drv_exit(void)
{
	unregister_chrdev(major, "my_drv"); // 卸载

	class_device_unregister(mydrv_class_dev);
	class_destroy(mydrv_class);
}

module_init(my_drv_init);
module_exit(my_drv_exit);


MODULE_LICENSE("GPL");

在最后,我们把字符设备驱动框架总结一下:

字符设备驱动分为:设备和驱动,设备和驱动通过设备号绑定在一起,用户程序中打开设备节点后,就会绑定到该节点对应的设备号,接着会追溯到该设备号对应的设备驱动,反映在用户态就是打开设备时返回的句柄,因此后续对句柄的读写操作,就会绑定到该设备驱动中file_operation的行为函数。这样,整个驱动就可以run起来了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值