什么是linux驱动

一、设备驱动:就是一个建立应用通往硬件的通道。

1、什么是设备驱动?

(1)对于驱动可能大家都知道是使硬件工作的。但是只知道这个似乎没有什么卵用,驱动还是很模糊,重点是几乎什么也做不了。
(2)对于操作硬件的人员(应用开发人员)来说,只会想我怎么使用它,如何操作它。
(3)因此,先要确定驱动的目标是什么?在我理解来看,就是建立一个从硬件通往应用的通道。
(4)建立通道就要进行操作,可以思考一下,要想连接两点需要三个因素,分为三个层次,总结起来可以理解为两点一线。
(5)第一个点在底层,这个就是驱动硬件需要准备什么工作?
(6)一线就是如何连接底层和应用层?
(7)第二个点在应用层,这个就是提供给使用的人操作的把柄。

2、驱动开发首先需要知道的

(1)操作系统已经给我们提供了一个机制,也就是说已经有一个驱动的模型了,这个模型是大神写好了的,驱动开发是需要根据这个模板来写,若是修改的话,也是根据驱动模型修改。
(2)这个机制很健全,健全到什么样的程度,驱动开发基本只需要进行相应的填充操作就可以了。当然首先你需要正确的填充。
(3)因此,学习linux驱动,就是学习这套机制,当然这个机制也在变得更美,所以我们要用发展的思维来学习这个机制。
(4)首先是驱动是一个单独的模块了,可安装、可卸载,安装会自动进行函数调用,当然卸载也会自动进行函数调用。
(5)其次,安装的时候要进行相关操作。注册和搭桥,初始化相关操作,进行申请资源。
(6)最后,卸载的时候也要进行相关操作。注销,释放资源。

3、驱动模块机制

1、根据自己的理解画了如下结构图:
在这里插入图片描述1、模块的安装:insmod driver.ko,首先会运行这个函数:module_init;
2、模块的卸载:rmmod driver.ko,不使用驱动时,退出模块时会运行这个函数:module_exit;
3、设备文件的创建:mknod /dev/xxx c 主设备号 次设备号,创建这个文件供应用打开操作。

总结:

1、以上就是驱动框架的机制,这个是最简单的,但是所包含的基本都有。linux内核已经提供了这一套的机制,可以说我们再次基础上进行再次开发。
2、那么为什么还需要进行再次开发?直接将驱动框架完善打包不就可以了嘛。实际上因为各种硬件产品的不同,以及应用场景的不同,几乎不可能做到只需要一套驱动就可以了。因此,驱动框架在不断的完善,模块持续的在优化,功能也更加的强大,使我们编写驱动和修改的地方越来越少。所以说我们学习驱动,就是在学习越来越好的框架。
3、基本的框架有了,开发者在这框架里进行填充功能,修改完善功能。

一、驱动实现:定制,使这个通道合法化、唯一化。

1、驱动模块安装需要操作什么?模块卸载又需要操作什么?而创建设备文件的设备号又是怎么来的等等。对于不同的硬件,需要不同的操作,因此需要针对不同的硬件进行设定区分。
2、对于驱动机制要有资源的概念,使用任何资源都需要先进行申请,在使用,使用完成后进行释放。就是说,系统内的资源不可以随便使用。
3、驱动框架的填充:
第一步:注册驱动
(1)、注册字符设备驱动:register_chrdev,向内核注册自己的主设备号(标识自己)、设备名(起名)、设备操作接口(一个大结构体)。
a、那么问题来了,在什么时候注册呢?当然是在一开始安装启动的时候就注册,那就是在模块安装函数中进行注册。
b、设备号是怎么来的:根据设备注册函数来看:一种是自定义,但是你需要每次去查看,哪些设备号没被使用,然后你再去注册;另一种是由系统给我们分配:根据设备注册函数的返回值来确定。
(2)、操作结构体:struct file_operations,注册是告诉内核,我是一个设备驱动,但是应用如何操作驱动了,首先要有接口去操作。
这就是大结构体的作用了。这个结构体定义了很多的操作方法,基本上满足我们操作设备。需要我们去填充实现:
static const struct file_operations test_fops = {
.owner = THIS_MODULE, // 惯例,直接写即可
.open = test_chrdev_open, // 将来应用open打开这个设备时实际调用的就是这个.open对应的函数
.release = test_chrdev_release, // 将来应用close关闭这个设备时实际调用的就是这个.release对应的函数
.write = test_chrdev_write, // 将来应用write写这个设备时实际调用的就是这个.write对应的函数
.read = test_chrdev_read, // 将来应用read读这个设备时实际调用的就是这个.read对应的函数
};
这个结构体将应用和驱动一一对应起来。就是一线,应用要和底层驱动联系起来,此时正在搭桥,通过函数指针将他们联系起来。

第二步:创建驱动文件
(1)、如何去使用驱动,现在内核已经注册有驱动了,应用也知道有驱动了;但是应用不知道怎么联系上驱动,干着急,应用是没办法了;而驱动也会等着应用调用了。现在的问题应用怎么和驱动接头,现在我们来思考一下,应该如何接头呢?
(2)、当然靠感觉是不行的,linux下遵循一切皆文件,而应用可以操作文件,那我们是否可以通过文件将他们关联起来呢?当然可以,那我们就可以通过创建一个设备文件来供应用使用。
(3)、使用mknod创建设备文件:mknod /dev/xxx c 主设备号 次设备号。
(4)、然后应用操作设备文件就可以了,对其进行打开、读写、关闭。

4、驱动基本代码

#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>

#define MYMAJOR		200
#define MYNAME		"testchar"

int mymajor;

static int test_chrdev_open(struct inode *inode, struct file *file)
{
    
    
	// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
	// 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来做代表。
	printk(KERN_INFO "test_chrdev_open\n");
	return 0;
}

static int test_chrdev_release(struct inode *inode, struct file *file)
{
    
    
	printk(KERN_INFO "test_chrdev_release\n");
	return 0;
}

ssize_t test_chrdev_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
    
    
	printk(KERN_INFO "test_chrdev_read\n");
	return 0;
}

// 写函数的本质就是将应用层传递过来的数据先复制到内核中,然后将之以正确的方式写入硬件完成操作。
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,
	size_t count, loff_t *ppos)
{
    
    
	printk(KERN_INFO "test_chrdev_write\n");	
	return 0;
}

// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {
    
    
	.owner		= THIS_MODULE,				// 惯例,直接写即可
	.open		= test_chrdev_open,			// 将来应用open打开这个设备时实际调用的就是这个.open对应的函数
	.release	= test_chrdev_release,		// 将来应用close关闭这个设备时实际调用的就是这个.release对应的函数
	.write 		= test_chrdev_write,		// 将来应用write写这个设备时实际调用的就是这个.write对应的函数
	.read		= test_chrdev_read,			// 将来应用read读这个设备时实际调用的就是这个.read对应的函数
};

// 模块安装函数
static int __init chrdev_init(void)
{
    
    	
	printk(KERN_INFO "chrdev_init helloworld init\n");

	// 在module_init宏调用的函数中去注册字符设备驱动
	// major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号
	// 内核如果成功分配就会返回分配的主设备好;如果分配失败会返回负数
	mymajor = register_chrdev(0, MYNAME, &test_fops);
	if (mymajor < 0)
	{
    
    
		printk(KERN_ERR "register_chrdev fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);
	return 0;
}

// 模块下载函数
static void __exit chrdev_exit(void)
{
    
    
	printk(KERN_INFO "chrdev_exit helloworld exit\n");
	// 在module_exit宏调用的函数中去注销字符设备驱动
	unregister_chrdev(mymajor, MYNAME);
}

module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");				// 描述模块的许可证
MODULE_AUTHOR("driver");			// 描述模块的作者
MODULE_DESCRIPTION("module test");	// 描述模块的介绍信息
MODULE_ALIAS("LED driver");			// 描述模块的别名信息

猜你喜欢

转载自blog.csdn.net/weixin_46089486/article/details/109072176
今日推荐