餐饮外哪个网站做推广,wordpress外网访问错误,软件公司简介,怎样建设网站啊什么是驱动#xff1a;
驱动就是对底层硬件设备的操作进行封装#xff0c;并向上层提供函数接口。
设备分类#xff1a; linux系统将设备分为3类#xff1a;字符设备、块设备、网络设备。
字符设备#xff1a;指只能一个字节一个字节读写的设备#xff0c;不能随机读取…什么是驱动
驱动就是对底层硬件设备的操作进行封装并向上层提供函数接口。
设备分类 linux系统将设备分为3类字符设备、块设备、网络设备。
字符设备指只能一个字节一个字节读写的设备不能随机读取设备内存中的某一数据读取数据需要按照先后顺序。字符设备是面向流的设备常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。块设备 指可以从设备的任意位置读取一定长度数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等。网络设备 网络设备可以是一个硬件设备,如网卡; 但也可以是一个纯粹的软件设备, 比如回环接口(lo).一个网络接口负责发送和接收数据报文。
用户态
是指用户编写程序、运行程序的层面用户态在开发时需要C的基础和C库C库讲到文件进程进程间通信线程网络界面GTk。C库是linux标准库一定有就是Clibary提供了程序支配内核干活的接口调用的openreadwriteforkpthreadsocket由此处封装实现由写的应用程序调用C库中的各种API调用的是内核态支配内核干活。
内核态
用户要使用某个硬件设备时需要内核态的设备驱动程序,进而驱动硬件干活就比如之前文章里面所提到的wiringPi库就是提供了用户操控硬件设备的接口在没有wiringPi库时就需要自己实现wiringPi库的功能就是自己写设备驱动程序。这样当我们拿到另一种类型的板子时同样也可以完成开发。在linux中一切皆文件各种的文件和设备比如鼠标、键盘、屏幕、flash、内存、网卡、如下图所示都是文件那既然是文件了就可以使用文件操作函数来操作这些设备。有一个问题open、read等这些文件操作函数是如何知道打开的文件是哪一种硬件设备呢①在open函数里面输入对应的文件名进而操控对应的设备。②通过设备号主设备号和次设备号。除此之外我们还要了解这些驱动程序的位置和如何实现这些驱动程序每一种硬件设备对应不同的驱动这些驱动有我们自己来实现。 Linux的设备管理是和文件系统紧密结合的各种设备都以文件的形式存放在/dev目录下称为设备文件。应用程序可以打开、关闭和读写这些设备文件完成对设备的操作就像操作普通的数据文件一样。为了管理这些设备系统为设备编了号每个设备号又分为主设备号和次设备号如下图所示。主设备号用来区分不同种类的设备而次设备号用来区分同一类型的多个设备。对于常用设备Linux有约定俗成的编号如硬盘的主设备号是3。 一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各设备。例如一个嵌入式系统有两个LED指示灯LED灯需要独立的打开或者关闭。那么可以写一个LED灯的字符设备驱动程序可以将其主设备号注册成5号设备次设备号分别为1和2。这里次设备号就分别表示两个LED灯。 驱动链表管理所有设备的驱动添加或查找 添加是发生在我们编写完驱动程序加载到内核。查找是在调用驱动程序由应用层用户空间去查找使用open函数。驱动插入链表的顺序由设备号检索就是说主设备号和次设备号除了能区分不同种类的设备和不同类型的设备还能起到将驱动程序加载到链表的某个位置在下面介绍的驱动代码的开发无非就是添加驱动添加设备号、设备名和设备驱动函数和调用驱动。综上所述如果想要打开dev下面的pin4引脚过程是用户态调用open“/de/pin4”,O_RDWR,对于内核来说上层调用open函数会触发一个软中断系统调用专用中断号是0x800x80代表发生了一个系统调用系统进入内核态并走到system_call可以认为这个就是此软中断的中断服务程序入口然后通过传递过来的系统调用号来决定调用相应的系统调用服务程序在这里是调用VFS中的sys_open。sys_open会在内核的驱动链表里面根据设备名和设备号查找到相关的驱动函数每一个驱动函数是一个节点驱动函数里面有通过寄存器操控IO口的代码进而可以控制IO口实现相关功能。system_call函数是怎么找到详细的系统调用服务例程的呢 通过系统调用号查找系统调用表sys_call_table软中断指令INT 0x80运行时系统调用号会被放入 eax 寄存器中system_call函数能够读取eax寄存器获取然后将其乘以4生成偏移地址然后以sys_call_table为基址。基址加上偏移地址就能够得到详细的系统调用服务例程的地址了然后就到了系统调用服务例程了。
补充
每个系统调用都对应一个系统调用号而系统调用号就对应内核中的相应处理函数。所有系统调用都是通过中断0x80来触发的。使用系统调用时通过eax 寄存器将系统调用号传递到内核系统调用的入参通过ebx、ecx……依次传递到内核和函数一样系统调用的返回值保存在eax中所有要从eax中取出
基于框架编写驱动代码
最简单的字符设备驱动框架
#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; //设备号devno是用来接收创建设备号函数的返回值销毁的时候需要传这个参数
static int major 231; //主设备号
static int minor 0; //次设备号
static char *module_namepin4; //模块名//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); //内核的打印函数和printf类似return 0;
}
//将上面的函数赋值给一个结构体中方便下面加载到到驱动链表中去
static struct file_operations pin4_fops {.owner THIS_MODULE,.open pin4_open,.write pin4_write,
};
/*
上面的代码等同于以下代码(但是在单片机的编译环境里面不允许以上写法)
static struct file_operations pin4_fops;pin4_fops.owner THIS_MODULE;pin4_fops.open pin4_open;pin4_fops.write pin4_write;
*/
//static限定这个结构体的作用仅仅只在这个文件。int __init pin4_drv_init(void) //真实的驱动入口
{int ret;devno MKDEV(major,minor); //创建设备号ret register_chrdev(major, module_name,pin4_fops); //注册驱动 告诉内核把这个驱动加入到内核驱动的链表中pin4_classclass_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); //入口内核加载驱动的时候这个宏不是函数会被调用去调用pin4_drv_init这个函数
module_exit(pin4_drv_exit);
MODULE_LICENSE(GPL v2);上面这个字符设备驱动代码里面有让代码自动的在dev下面生成设备除此之外我们还可以手动创建设备名。使用指令sudo mknod 设备名字 设备类型c表示字符设备驱动 主设备号次设备号 b create a block (buffered) pecial file。 c, u create a character (unbuffered) special file。 p create a FIFO删除手动创建的设备名直接rm就好。如下图所示
驱动模块代码编译模块的编译需要配置过的内核源码编译、连接后生成的内核模块后缀为.ko编译过程首先会到内核源码目录下读取顶层的Makefile文件然后再返回模块源码所在目录。
使用下面的的代码
#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_namepin4; //模块名//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{printk(pin4_open\n); //内核的打印函数和printf类似return 0;
}
//read函数
static int pin4_read(struct file *file,char __user *buf,size_t count,loff_t *ppos)
{printk(pin4_read\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); //内核的打印函数和printf类似return 0;
}static struct file_operations pin4_fops {.owner THIS_MODULE,.open pin4_open,.write pin4_write,.read pin4_read,
};
//static限定这个结构体的作用仅仅只在这个文件。
int __init pin4_drv_init(void) //真实的驱动入口
{int ret;devno MKDEV(major,minor); //创建设备号ret register_chrdev(major, module_name,pin4_fops); //注册驱动 告诉内核把这个驱动加入到内核驱动的链表中pin4_classclass_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); //入口内核加载驱动的时候这个宏会被调用去调用pin4_drv_init这个函数
module_exit(pin4_drv_exit);
MODULE_LICENSE(GPL v2);在导入虚拟机的内核代码中找到字符设备驱动的那一个文件夹/SYSTEM/linux-rpi-4.19.y/drivers/char将以上代码复制到一个文件中然后下一步要做的是就是将上面的代码编译生成模块就是修改Makefile这个文件。文件内容如下图所示-y表示编译进内核-m表示生成驱动模块CONFIG_表示是根据config生成的所以只需要将obj-m pin4drive.o添加到Makefile中即可。 然后使用指令ARCHarm CROSS_COMPILEarm-linux-gnueabihf- KERNELkernel7 make modules进行编译生成驱动模块。然后将生成的.ko文件发送给树莓派scp pin4drive.ko pi192.168.43.136:/home/pi编译生成驱动模块会生成以下几个文件 .o的文件是object文件.ko是kernel object与.o的区别在于其多了一些sections,比如.modinfo。.modinfo section是由kernel source里的modpost工具生成的包括MODULE_AUTHOR, MODULE_DESCRIPTION, MODULE_LICENSE, device ID table以及模块依赖关系等等。 depmod 工具根据.modinfo section生成modules.dep, modules.*map等文件以便modprobe更方便的加载模块。编译过程中经历了这样的步骤先进入Linux内核所在的目录并编译出pin4drive.o文件运行MODPOST会生成临时的pin4drive.mod.c文件而后根据此文件编译出pin4drive.mod.o之后连接pin4drive.o和pin4drive.mod.o文件得到模块目标文件pin4drive.ko最后离开Linux内核所在的目录。
操作驱动的上层代码pin4test:
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.hvoid main()
{int fd,data;fd open(/dev/pin4,O_RDWR);if(fd0){printf(open fail\n);perror(reson:);}else{printf(open successful\n);}fdwrite(fd,1,1);
}将以上代码进行交叉编译后发送给树莓派就可以看到pi目录下存在发送过来的.ko文件和pin4test这两个文件如下图所示 然后使用指令sudo insmod pin4drive.ko加载内核驱动相当于通过insmod调用了module_init这个宏然后将整个结构体加载到驱动链表中加载完成后就可以在dev下面看到名字为pin4的设备驱动这个和驱动代码里面static char *module_namepin4; //模块名这行代码有关设备号也和代码里面相关。lsmod可以查看驱动已经装进去了。 执行上层代码出现以下错误表示没有权限使用指令sudo chmod 666 /dev/pin4为pin4赋予权限让所有人都可以打开成功。 然后再次执行pin4test表面上看没有任何信息输出其实内核里面有打印信息只是上层看不到如果想要查看内核打印的信息可以使用指令dmesg |grep pin4。如下图所示表示驱动调用成功 在装完驱动后可以使用指令sudo rmmod 驱动名不需要写ko将驱动卸载。
为什么生成驱动模块需要在虚拟机上生成树莓派不行吗
生成驱动模块需要编译环境linux源码并且编译需要下载和系统版本相同的Linux内核源代码也可以在树莓派上面编译但在树莓派里编译效率会很低要非常久。这篇文章有讲树莓派驱动的本地编译。