驱动的认知
应用层进行open,read,write驱动程序的时候,linux系统调用过程
基于驱动框架代码编写
寻找一个驱动参考(字符设备驱动)
作为初学者我们可以选择别人写好的驱动作为参考
#include <linux/fs.h> //file_operations声明
#include <linux/module.h> //module_init module_exit声明
#include <linux/init.h> //__init __exit 宏定义声明
#include <linux/device.h> //class devise声明
#include <linux/uaccess.h> //copy_from_user 的头文件
#include <linux/types.h> //设备号 dev_t 类型声明
#include <asm/io.h> //ioremap iounmap的头文件
static struct class *pin4_class;
static struct device *pin4_class_dev;
static dev_t devno; //设备号
static int major = 231; //主设备号
static int minor = 0; //次设备号
static char *module_name = "pin4"; //模块名
//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
printk("pin4_open\n"); //内核的打印函数和printf类似
return 0;
}
//led_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
printk("pin4_write\n");
return 0;
}
static struct file_operations pin4_fops = {
.owner = THIS_MODULE,
.open = pin4_open,
.write = pin4_write,
};
int __init pin4_drv_init(void) //真实驱动入口
{
int ret;
devno = MKDEV(major, minor); //创建设备号
ret = register_chrdev(major, module_name, &pin4_fops); //注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中
pin4_class=class_create(THIS_MODULE, "myfirstdemo"); //用代码在dev自动生成设备
pin4_class_dev =device_create(pin4_class, NULL, devno, NULL, module_name); //创建设备文件
return 0;
}
void __exit pin4_drv_exit(void)
{
device_destroy(pin4_class, devno);
class_destroy(pin4_class);
unregister_chrdev(major, module_name); //卸载驱动
}
module_init(pin4_drv_init); //入口,内核加载该驱动(insmod)的时候,这个宏被使用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");
驱动框架设计过程
- 确定主设备号
- 定义自己的 file_operations 结构体
- 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
- 实现入口函数:安装驱动程序时,就会去调用这个入口函数,执行工作: ①把 file_operations 结构体告诉内核:注册驱动程序register_chrdev.
②创建类class_create.
③创建设备device_create.- 实现出口函数:卸载驱动程序时,就会去调用这个出口函数,执行工作: ①把 file_operations 结构体从内核注销:unregister_chrdev.
②销毁类class_create.
③销毁设备结点class_destroy.- 其他完善:GPL协议、入口加载
1.确定主设备号
static dev_t devno; //设备号
static int major = 231; //主设备号
static int minor = 0; //次设备号
static char *module_name = "hello_drv"; //模块名
2.定义自己的 file_operations 结构体
linux的file_operations 结构体
自己修改后(多添加了个read):
static const struct file_operations hello_drv = {
.owner = THIS_MODULE,
.open = hello_drv_open,
.write = hello_drv_write,
.read = hello_drv_read,
};
3.实现对应的 drv_open/drv_read/drv_write 等函数(函数应该添加在代码中结构体的上方,参考驱动就是把函数写在结构体上面的)
static ssize_t hello_drv_read (struct file * file, char __user * buf, size_t size, loff_t * offset){
printk("hello_drv_read\n");//printk打印在内核中的信息
return 0;
}
static ssize_t hello_drv_write (struct file * file, const char __user * buf, size_t size, loff_t * offset){
printk("hello_drv_write\n");
return 0;
}
static int hello_drv_open (struct inode * node, struct file * file){
printk("hello_drv_open\n");
return 0;
}
4.实现入口函数
int __init hello_drv_init(void) //真实驱动入口
{
int ret;
devno = MKDEV(major, minor); //创建设备号
ret = register_chrdev(major, module_name, &hello_drv); //注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中
hello_drv_class=class_create(THIS_MODULE, "hello class"); //创建类,用代码在dev自动生成设备
hello_drv_class_dev=device_create(hello_drv_class, NULL, devno, NULL, module_name); //创建设备文件
return 0;
}
实现出口函数
void __exit hello_drv_exit(void)
{//和入口函数的顺序相反
device_destroy(hello_drv_class, devno);
class_destroy(hello_drv_class);
unregister_chrdev(major, module_name); //卸载驱动
}
其他完善:GPL协议 入口加载
module_init(hello_drv_init); //入口,内核加载该驱动的时候,这个宏被使用
module_exit(hello_drv_exit);
MODULE_LICENSE("GPL v2");
修改结果
#include <linux/fs.h> //file_operations声明
#include <linux/module.h> //module_init module_exit声明
#include <linux/init.h> //__init __exit 宏定义声明
#include <linux/device.h> //class devise声明
#include <linux/uaccess.h> //copy_from_user 的头文件
#include <linux/types.h> //设备号 dev_t 类型声明
#include <asm/io.h> //ioremap iounmap的头文件
static struct class *hello_drv_class;
static struct device *hello_drv_class_dev;
/*1.确定主设备号*/
static dev_t devno; //设备号
static int major = 231; //主设备号
static int minor = 0; //次设备号
static char *module_name = "hello_drv"; //模块名
/*3.实现对应的 drv_open/drv_read/drv_write 等函数*/
static ssize_t hello_drv_read (struct file * file, char __user * buf, size_t size, loff_t * offset){
printk("hello_drv_read\n");//printk打印在内核中的信息
return 0;
}
static ssize_t hello_drv_write (struct file * file, const char __user * buf, size_t size, loff_t * offset){
printk("hello_drv_write\n");
return 0;
}
static int hello_drv_open (struct inode * node, struct file * file){
printk("hello_drv_open\n");
return 0;
}
/*2.定义自己的 file_operations 结构体*/
static const struct file_operations hello_drv = {
.owner = THIS_MODULE,
.open = hello_drv_open,
.write = hello_drv_write,
.read = hello_drv_read,
};
/*4.实现入口函数,安装驱动程序时,就会去调用这个入口函数*/
int __init hello_drv_init(void) //真实驱动入口
{
int ret;
devno = MKDEV(major, minor); //创建设备号
ret = register_chrdev(major, module_name, &hello_drv); //注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中
hello_drv_class=class_create(THIS_MODULE, "hello class"); //创建类,用代码在dev自动生成设备
hello_drv_class_dev=device_create(hello_drv_class, NULL, devno, NULL, module_name); //创建设备文件
return 0;
}
/*5.实现出口函数,卸载驱动程序时,就会去调用这个入口函数*/
void __exit hello_drv_exit(void)
{//和入口函数的顺序相反
device_destroy(hello_drv_class, devno);
class_destroy(hello_drv_class);
unregister_chrdev(major, module_name); //卸载驱动
}
/*其他完善:GPL协议 入口加载*/
module_init(hello_drv_init); //入口,内核加载该驱动的时候,这个宏被使用
module_exit(hello_drv_exit);
MODULE_LICENSE("GPL v2");
将驱动代码进行编译然后测试
编译阶段
-
进入目录:
/home/cyh/SYSTEM/linux-rpi-4.14.y/drivers/char
把修改好的代码拷贝到此位置
为了方便阅读,这里把mytext.c改一个名字
-
修改该文件夹下Makefile(为了让整个过程编译的时候编到它)
m: 表示块设备
3. 回到linux-rpi-4.14.y/编译驱动文件
在该目录执行ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
进行编译
测试阶段
- 到linux-rpi-4.14.y/drivers/char下把生成的驱动发送给树莓派
- 编写一个测试的程序,然后发给树莓派(也可以在树莓派上编写)
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd;
fd=open("/dev/hello_drv",O_RDWR);
if(fd==-1){
printf("open failed\n");
perror("reason");
}else{
printf("open success\n");
}
write(fd,"1",1);
return 0;
return 0;
}
- 驱动传到树莓派后,需要加载驱动(一定要sudo)
sudo insmod hello_drv.ko
驱动卸载:sudo rmmod hello_drv 不需要写ko
查看驱动模块:lsmod
成功加载到dev中:(如果没有加载,dev中将没有这个驱动)
4. 运行测试程序
运行后看不到任何关于驱动代码相关函数的结果,是因为我们看到的终端是上层界面,而驱动代码的函数打印的结果是在内核中打印的。
5. 查看运行结果
使用dmesg
查看
验证步骤总结
a.装载驱动
b.查看驱动装载后是否生成设备
c.运行测试程序调试驱动
d.内核的printk打印在内核层,通过dmesg查看内核打印信息
调用流程:
我们上层空间的open去查找dev下的驱动(文件名),文件名背后包含了驱动的主设备号和次设备号,此时用户open触发一个系统调用,系统调用经过vfs(虚拟文件系统),vfs根据文件名背后的设备号去调用sys_open去判断,找到内核中驱动链表的驱动位置,再去调用驱动里面自己的dev_open函数