一、编写驱动框架
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 这个脚本文件