5、2 字符设备驱动程序_LED驱动程序_编写编译 + 5、3测试改进

一、编写驱动框架

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

static int first_drv_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
{
	printk("first_drv_write\n");
	return 0;

是从上到下找到驱动程序的,所以要告诉内核有这么个东西:

1、定义结构体,填充结构体:

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

2、通过注册函数,把这个结构告诉内核:
主设备号 , 名字, 结构

register_chrdev(0, "first_drv", &first_drv_fops);//注册,告诉内核有这么个结构体

关于主设备号和次设备号的概念,我们可以在主机上查看下:
shell ls /dev -l
在这里插入图片描述

app的open、wirite通过主设备号和名字,在chrdev数组里找到file_operations结构体:
在这里插入图片描述

3、入口函数

①写一个驱动入口让人调用
A、手动分配主设备号:

需要去开发板上查看有,111是否被占用:

shell cat /proc/device
在这里插入图片描述

register_chrdev(111, “first_drv”, &first_drv_fops);

B、系统自动分配:
major = register_chrdev(0, “first_drv”, &first_drv_fops);

static int first_drv_init(void)
{
	register_chrdev(111, "first_drv", &first_drv_fops);//注册,告诉内核有这么个结构体
	return 0;
}

②有那么多入口函数,通过module_init去找到想要的入口函数
module_init 是一个宏,定义一个指针,指针指向该函数

module_init(first_drv_init);

4、出口函数
①把file_operations从 chrdev里删了

static void first_drv_exit(void)
{
	unregister_chrdev(111, "first_drv");
}

②怎么告诉有这么个出口函数?

module_exit(first_drv_exit);

二、完整代码:

#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>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

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

static int first_drv_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
{
	printk("first_drv_write\n");
	return 0;


static struct file_operations first_drv_fops
{
	.owner = THIS_MODULE,/* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
	.open  = first_drv_open,
	.write = first_drv_write,
};
	
static int first_drv_init(void)
{
	register_chrdev(111, "first_drv", &first_drv_fops);//注册,告诉内核有这么个结构体
	return 0;
}

static void first_drv_exit(void)
{
	unregister_chrdev(111, "first_drv");
}

module_init(first_drv_init);
module_exit(first_drv_exit);

编译:
在这里插入图片描述
三、加载驱动程序:

(1)查看下都有什么设备,前面的数字就是chrdev的数字:

cat /proc/device

在这里插入图片描述
(2装载驱动

insmod first_drv.ko

在这里插入图片描述
(3)可以看到已经装载进来了:
在这里插入图片描述
三、编写测试程序来测试驱动程序:
1、测试程序
在这里插入图片描述
编译:
在这里插入图片描述
2、创建设备文件,并且设置属性c ,设置主设备号111,设置次设备号0
创建:

mknod /dev/xxx c 111 0

运行:

./firstdrvtest

在这里插入图片描述
应用程序打开设备驱动文件,然后调用我们自己写的 first_drv_open。
应用程序调用wirite函数的时候,就会去调用驱动程序的 first_drv_write。

四、改进驱动程序:

1、不设置设备号111,让系统自动分配设备号
(1)修改代码

major = register_chrdev(0, "first_drv", &first_drv_fops);

(2)在开发板上:

lsmod 查看驱动列表
rmmod  first_drv 卸载原先加载的驱动

在这里插入图片描述

cat /proc/device 再查看下,没有111的设备了:

在这里插入图片描述

insmod first_drv.ko 重新加载驱动
cat /proc/device 查看到系统分配的驱动主设备号为252:

在这里插入图片描述
再次执行测试程序:

./firstdrvtest

运行失败,查看下文件节点,发现是有/dev/XXX的文件节点,但是不是主设备号不是252:
在这里插入图片描述
说明文件节点还是需要我们自己创建的:

rm /dev/xxx
mknod /dev/XXX 252 0

再次运行就能通过了。

4、让系统自动分配,文件结点

根据以上的操作,我们可以知道,每次都需要查看主设备号,才能分配文件结点,实在太麻烦了。

应用 open("/dev/xxx") /dev/xxx的主设备结点:
①手动创建:mknod /dev/xxx c 主 次
②自动创建:udev机制 mdev机制 根据系统信息创建设备节点

udev一般用在PC上的linux中
mdev是udev的简化版本,是busybox中所带的程序

(1)原理
在这里插入图片描述
在/sys/系统目录下有很多信息, 在我们注册驱动的时候, 会自动的去sys目录下注册信息, mdev机制就是去/sys/下去找信息然后创建设备节点。

(2)编写代码

static struct class *firstdrv_class;
static struct class_device	*firstdrv_class_dev;
int major;
static int first_drv_init(void)
{
	major = register_chrdev(0, "first_drv", &first_drv_fops);//注册,告诉内核有这么个结构体

	firstdrv_class = class_create(THIS_MODULE, "firstdrv");//创建类
	
	firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xxx"); /* /dev/xxx */ //创建设备

	return 0;
}

static void first_drv_exit(void)
{
	unregister_chrdev(major, "first_drv");
	
	class_device_unregister(firstdrv_class_dev);
	
	class_destroy(firstdrv_class);
}

(3)验证

在此目录下找不到的话, 就要去/lib/modules这里找,也找不到就报错,所以我们现在是自动创建的, 就要把目录转到根目录下:
在这里插入图片描述
再次装载说明函数未定义:
在这里插入图片描述
解决方法:

module_init(first_drv_init);
module_exit(first_drv_exit);

MODULE_LICENSE("GPL");

运行成功后, 我们去看下系统信息:
在这里插入图片描述
为什么一装载驱动, 就能创捷文件节点呢?
因为/etc/init.c/rsS 这个脚本文件
在这里插入图片描述

发布了56 篇原创文章 · 获赞 3 · 访问量 2386

猜你喜欢

转载自blog.csdn.net/qq_40674996/article/details/101014027