驱动开发
什么是Linux
驱动从字面上理解就是让它动起来,驱动的本质是电力驱动,而驱动代码只是给出了操作方法。
驱动程序在 Linux 内核里扮演着特殊的角色。 它们是截然不同的"黑盒子", 使硬件的特殊的一部分响应定义好的内部编程接口。它们完全隐藏了设备工作的细节。 用户的活动通过一套标准化的调用来进行, 这些调用与特别的驱动是独立的; 设备驱动的角色就是将这些调用映射到作用于实际硬件的和设备相关的操作上。 这个编程接口是这样,驱动可以与内核的其他部分分开建立, 并在需要的时候在运行时"插 入"。 这种模块化使得 Linux 驱动易写, 以致于目前有几百个驱动可用。
驱动程序由那些种类
-
字符设备驱动:
应用程序与驱动程序之间的数据交互是以字符(字节)为单位的,字符设备通常没有中间缓存。常见的字符设备有:led、key、camera、显卡、串口等
-
块设备驱动:
应用程序与驱动程序之间的数据交互是以块为单位的,主要针对的是存储设备,通常设有中间缓存,并且带有文件系统,可以任意位置访问。常见的块设备有:U盘、eMMC、SD卡等
-
网络设备驱动:
网络设备面向数据包的发送而设计,它并不对应于文件系统的节点。即不对应 /dev 目录下的设备文件,应用程序最终通过套接字socket 完成与网络设备的数据交互。常见的网络设备有网卡(无线网卡、有线网卡)。
驱动程序格式
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
ssize_t myread(struct file *file, char __user *ubuf,size_t size, loff_t *offs) //自己写的read函数
{
return size;
}
ssize_t mywrite(struct file *file, const char __user *ubuf,size_t size, loff_t *offs)
{
return size;
}
const struct file_operations fops = {//这个结构体为咱们自己写的,在注册驱动时被调用,这是APP层能调用驱动层上面咱们自己的的程序的关键
.open = myopen,//将自己写的open函数给到API内
.read = myread,//将自己写的read函数给到API内
.write = mywrite,//将自己写的write函数给到API内
.release = myclose,//将自己写的close函数给到API内
};
static int __init mycdev_init(void)
{
return 0;
}
static void __exit mycdev_exit(void)//出口
{
}
module_init(mycdev_init); //资源申请
module_exit(mycdev_exit); //资源释放
MODULE_LICENSE("GPL"); //许可证GPL
MODULE_INFO(intree, "Y"); //将模块标记位内核的一部分
编译内核时插入驱动
-
进入到内核的drivers目录中
cd dirvers
-
创建设备目录
mkdir <dirvers>
-
进入目录并添加文件
cd <drivers> mkdir <drivers>.c Makefile Kconfig
-
编辑Makefile
obj-${CONFIG_<DREVERS>} += <drivers>.o
-
编辑Kconfig
menu "<drivers> KM" comment "<drivers> kernel module" config <DREVERS> tristate "<drivers> module" default y help <drivers> kernel module test endmenu
-
修改drivers下的Kconfig
source "drivers/hello/Kconfig"
-
修改drivers/Makefile
obj-$(CONFIG_<DREVERS>) += <drivers>/
-
配置内核
Device————>Drivers最后选择字节添加的模块
-
编译内核
动态插入驱动
Makefile
obj-m += TimeGpio.o //驱动文件
KDIR := /home/yixinchi/rt_kernel/linux-imx-lf-6.1.y/ //内核路径
PWD ?= $(shell pwd) //将当前所在的目录路径赋值给变量`PWD`
all:
make -C $(KDIR) M=$(PWD) modules //编译驱动模块
//-C,表示在`$(KDIR)`所指定的路径下执行编译操作
//-M,将驱动模块的源代码路径传递给内核的Makefile,以告知内核模块的源代码位置。
//modules,表示只编译驱动模块,不编译其他类型的内核模块
clean:
make -C $(KDIR) M=$(PWD) clean //清理已编译的驱动模块
插入驱动
-
Linux下查看驱动
lsmod char /dev cat /proc/devices
-
Linux下安装驱动
sudo insmod xxx.ko
-
Linux下卸载驱动
sudo rmmod xxx
内核函数/宏
内核打印函数
-
printk
格式:printk(打印级别 "内容")
-
打印级别
0 ------ 7
最高的 最低的linux@ubuntu:~$ cat /proc/sys/kernel/printk 4 4 1 7 终端的级别 消息的默认级别 终端的最大级别 终端的最小级别
-
宏定义
#define KERN_EMERG “<0>” /* 系统不可用 */ #define KERN_ALERT “<1>” /* 必须立即采取行动 */ #define KERN_CRIT “<2>” /* 危急条件 */ #define KERN_ERR “<3>” /* 错误条件 */ #define KERN_WARNING “<4>” /* 警告条件 */ #define KERN_NOTICE “<5>” /* 正常但重要的状况 */ #define KERN_INFO “<6>” /* 信息 */ #define KERN_DEBUG “<7>” /* 调试级消息 */
消息回显
- dmesg 消息回显
- dmesg -c 清空回显之前显示
- dmesg -C清空回显 不显示
用户向内核传参
- module_param
- 原型:module_param(name, type, perm)
- 功能:用于在Linux内核模块中定义可由用户在加载模块时传递的参数。
- 参数;
name
:参数的名称,它将用作模块的参数变量名。type
:参数的数据类型,可以是整数型(int
)、长整型(long
)、布尔型(bool
)、字符型(charp
)等。可以根据需要选择适合的数据类型。perm
:参数的访问权限,用于指定参数在/sys/module/<module_name>/parameters/
目录下的访问权限。常见的权限值有S_IRUSR
(用户可读)、S_IWUSR
(用户可写)等。
为模块添加描述信息
- MODULE_PARM_DESC
- 原型:MODULE_PARM_DESC(name, desc)
- 功能:为Linux内核模块中的参数提供描述信息
- 参数:
name
:参数的名称,与之前定义的模块参数名称相对应。desc
:参数的描述信息,用于说明参数的作用、取值范围、默认值等。
接收命令行传递的数组
- module_param_array
- 原型:module_param_array(name, type, nump, perm)
- 功能:接收命令行传递的数组
- 参数:
name
:数组名type
:数组的类型nump
:参数的个数,变量的地址perm
:权限
导出符号表
- 使用
- 编译提供者源文件,得到符号表
- 将符号表拷贝到使用者文件下
- 编译使用者模块
- 安装提供者模块
- 安装使用者模块
- 卸载
- 卸载使用者模块
- 卸载提供者模块
- EXPORT_SYMBOL_GPL
- 原型:`EXPORT_SYMBOL_GPL(导出符号名称)
- 功能:导出符号表
字符设备驱动
-
步骤:
- 注册驱动设备——>返回值是否为设备号——>如果不是设备号小于零——>错误码
- 然后写注册驱动函数里第三个参数那个结构体函数
- 然后写自己的open read write close ,函数名根据自己情况编写
- 然后把自己写的open read write close函数,赋值给结构体内的open read write release
- 最后出口内写注销设备函数
-
register_chrdev
- 原型:int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
- 功能:注册一个字符设备驱动
- 参数:
- @major:主设备号
- 写大于0,它认为这个就是主设备号
- 写0,操作系统给你分配一个主设备号
- name :设备名
- fops :操作方法结构体
- @major:主设备号
- 返回值:
- major>0 ,
- 成功返回0,
- 失败返回错误码(负数)
- major=0
- 成功主设备号
- 失败返回错误码(负数)
- major>0 ,
-
unregister_chrdev
- 原型:void unregister_chrdev(unsigned int major, const char *name)
- 功能:注销一个字符设备驱动
- 参数:
- major:主设备号
- name:名字
- 返回值:无
应用程序将数据传给驱动
- **copy_from_user **
- 原型:int copy_from_user(void *to, const void __user *from, int n)
- 功能:从用户空间拷贝数据到内核层
- 参数
- to:内核空间首地址
- from:用户空间首地址
- n:拷贝数据长度
驱动将数据传给应用程序
- copy_to_user
- 原型:int copy_to_user(void __user *to,const void *from, unsigned long n)
- 功能:从内核层拷贝数据到用户层
- 参数:
- to:用户空间首地址
- from:内核空间首地址
- n:拷贝数据长度
操作寄存器
-
裸机操作寄存器是物理地址,linux内核启动后操作的是虚拟地址,需要将物理地址转化为虚拟地址,在驱动代码中操作的虚拟地址相当于西澳洲实际的物理地址
-
ioremap
- 原型:void * ioremap(phys_addr_t offset, unsigned long size)
- 功能:将物理地址映射成虚拟地址
- 参数:
- offset :要映射的物理地址
- size :大小(字节)
- 返回值:
- 成功:返回虚拟地址
- 失败:返回NULL;
-
iounmap
- 原型:void iounmap(void *addr)
- 功能:取消映射
- 参数:
- addr :虚拟地址
- 返回值:无
设备节点创建
- 手动创建
- sudo mknod <文件名称>c/b(字符设备 块设备) 主设备号 次设备号
- 自动创建
- 头文件:#include <linux/device.h>
- class_create:
- 原型:struct class * class_create(owner, name)
- 功能:向用户空间提交目录信息
- 参数:
- owner :THIS_MODULE
- name :目录名字
- 返回值 //if(IS_ERR(cls))
- 成功:struct class *指针
- 失败:错误码指针 int (-5)
- device_create
- 原型:struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, …)
- 功能:向用户空间提交文件信息
- 参数:
- class :目录名字 cls
- parent:NULL
- devt :设备号 MKDEV(major,0)
- drvdata :NULL
- fmt :文件的名字
- 返回值:
- 成功:struct device *指针
- 失败:错误码指针 int (-5)
内核定时器
-
普通定时器
-
初始化定时器
-
结构体
struct timer_list { unsigned long expires; //定时的时间,ms级别 void (*function)(unsigned long); //定时器的处理函数 unsigned long data; //向定时器处理函数中填写的值 };
-
初始化
mytimer.expries = jiffies + 1000; //1s mytimer.function = timer_function; mytimer.data = 0; init_timer(&mytimer); //内核帮你填充你未填充的对象
-
回调函数
void timer_function(unsigned long data) //定时器的处理函数 { }
-
-
添加定时器
- add_timer
- 原型:void add_timer(struct timer_list *timer);
- 功能:添加定时器,同一个定时器只能添加一次
- 参数:定时结构体
- add_timer
-
再次启动定时器
- mod_timer
- 原型:int mod_timer(struct timer_list *timer, unsigned long expires)
- 功能:再次启动定时器
- 参数:
- timer:定时结构体
- expires:再次定时实际
- mod_timer
-
删除定时器
- del_timer
- 原型:int del_timer(struct timer_list *timer)
- 功能:删除定时器
- 参数:
- timer:定时结构体
- del_timer
-
-
高精度定时器
-
头文件:
- #include <Linux/hrtimer.h>
- #include <linux/ktime.h>
-
添加定时时间
-
原型:struct timespec ktime_set(const time64_t secs, const s64 nsecs);
-
功能:创建一个ktime结构表示指定的时间
-
参数:
secs
:秒数。nsecs
:纳秒数。
-
返回值:返回一个ktime结构,表示指定的时间。
-
-
初始化定时器
-
原型:void hrtimer_init(struct hrtimer *timer, clockid_t clock_id, enum hrtimer_mode mode);
-
功能:初始化定时器
-
参数:
-
imer:hrtimer指针,
-
clock_id:
- CLOCK_REALTIME //实时时间,如果系统时间变了,定时器也会变
- CLOCK_MONOTONIC //递增时间,不受系统影响
-
mode:
- HRTIMER_MODE_ABS = 0x0, /* 绝对模式 * /
- HRTIMER_MODE_REL = 0x1, /* 相对模式 * /
- HRTIMER_MODE_PINNED = 0x02, /* 和CPU绑定 * /
- HRTIMER_MODE_ABS_PINNED = 0x02, /* 第一种和第三种的结合 * /
- HRTIMER_MODE_REL_PINNED = 0x03, /* 第二种和第三种的结合 */
-
-
返回值:
-
-
开启定时器
-
原型:hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode);
-
功能:启动一个高精度定时器
-
参数:
timer
:指向要启动的hrtimer结构的指针。tim
:相对于当前时间的定时器到期时间。mode
:定时器模式HRTIMER_MODE_REL
:相对时间- ``HRTIMER_MODE_ABS`:绝对时间
-
返回值:
- 成功启动时返回0,
- 失败返回错误代码。
-
-
再次开启定时
-
原型:`void hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval);
-
功能:将高精度定时器的到期时间向前移动一定的间隔
-
参数:
timer
:指向要移动的hrtimer结构的指针now
:当前时间,timer->base->get_timeinterval
:定时器到期时间的增量
-
返回值:无
-
-
删除定时器
-
原型:int hrtimer_cancel(struct hrtimer *timer);
-
功能:取消一个高精度定时器
-
参数:
timer
:指向要取消的hrtimer结构的指针。
-
返回值:
- 成功取消时返回0
- 失败返回错误代码
-
-
向地址读写值
- writel
- 原型:writel(v,c)
- 功能:向地址中写一个值
- 参数:
- v:写的值
- c:地址
- readl
- 原型:readl©
- 功能:读一个地址,将地址中的值给返回
- 参数:
- c:地址
使用驱动
- 使用文件IO,open、read、write、close进行对驱动使用
- open打开dev下对应的驱动设备
获取开发板硬件信息
获得内存信息
-
内存信息结构体
struct sysinfo { long uptime; /* 启动后的秒数 */ unsigned long loads[3]; /* 1、5 和 15 分钟负载平均值 */ unsigned long totalram; /* 总可用主内存大小 */ unsigned long freeram; /* 可用内存大小 */ unsigned long sharedram; /* 共享内存量 */ unsigned long bufferram; /* 缓冲区使用的内存 */ unsigned long totalswap; /* 总交换空间大小 */ unsigned long freeswap; /* 交换空间仍然可用 */ unsigned short procs; /* 当前进程数 */ char _f[22]; /* 焊盘结构为 64 字节 */ };
-
获取内存信息函数
- sysinfo
- 头文件:#include <sys/sysinfo.h>
- 原型:int sysinfo(struct sysinfo *info);
- 功能:返回有关内存和交换使用情况以及负载平均值的某些统计信息
- 参数:递一个指向
struct sysinfo
结构的指针来获取系统的信息 - 返回值:
- 成功:0
- 失败:-1,并更新errno
- sysinfo
内存总量
unsigned long total_memory = info.totalram * info.mem_unit;
内存利用率
- (总内存 - 可用内存) / 总内存 * 100
long long free_memory = info.freeram * info.mem_unit;
float memory_usage = ((total_memory - free_memory) / (float)total_memory) * 100;
获得CPU信息
CPU占用率
-
/proc/stat:提供了CPU统计信息
-
指标
cpu指标 含义 时间单位 备注 user 用户态时间 jiffies 一般/高优先级,仅统计nice<=0 nice nice用户态时间 jiffies 低优先级,仅统计nice>0 system 内核态时间 jiffies idle 空闲时间 jiffies 不包含IO等待时间 iowait I/O等待时间 jiffies 硬盘IO等待时间 irq 硬中断时间 jiffies softirq 软中断时间 jiffies steal 被盗时间 jiffies 虚拟化环境中运行其他操作系统上花费的时间 guest 来宾时间 jiffies 操作系统运行虚拟CPU花费的时间 guest_nice nice来宾时间 jiffies 运行一个带nice值的guest花费的时间 -
定义
CPU信息结构体typedef struct cpu_occupy_ { char name[20]; unsigned int user; //用户态时间 unsigned int nice; //nice用户态时间 unsigned int system; //内核态时间 unsigned int idle; //空闲时间 unsigned int iowait; //I/O等待时间 unsigned int irq; //硬中断时间 unsigned int softirq; //软中断时间 }cpu_occupy_t;
-
获得CPU信息
fd = fopen ("/proc/stat", "r"); if(fd == NULL) { perror("fopen:"); exit (0); } fgets (buff, sizeof(buff), fd); sscanf (buff, "%s %u %u %u %u %u %u %u", cpu_occupy.name, &cpu_occupy.user, &cpu_occupy.nice, &cpu_occupy.system, &cpu_occupy.idle, &cpu_occupy.iowait, &cpu_occupy.irq, &cpu_occupy.softirq);
-
计算利用率
((用总时间 - 空闲时间)* 100)/总时间
double cpu_usage = ((total - cpu_occupy.idle) * 100) / total;
实时性
什么是实时性
- 实时性是指系统或应用程序对事件或任务的处理能力在特定时间限制内的保证。在实时系统中,任务的响应时间是非常关键的,因为
任务必须在规定的时间内得到处理和响应
。 - 响应时间
- 硬实时性:几微秒 ~ 几毫秒
- 软实时性:几毫秒 ~ 几秒
什么是实时操作系统
- 实时操作系统(Real-Time Operating System,RTOS)是为了满足实时性要求而设计的操作系统。它能够保证任务在特定的时间约束下得到及时处理。
- RTOS通常具有以下特点:
- 硬实时性:对任务的处理时间有严格的限制,超过限制将导致系统故障。
- 软实时性:对任务的处理时间有一定的限制,但允许在某些情况下延迟。
- 可预测性:能够在特定时间内提供可预测的响应。
什么是实时内核
- 实时内核(Real-Time Kernel)是RTOS的核心部分,负责任务调度和管理实时任务。
- 实时内核通常具有以下特点:
- 快速响应时间:能够在短时间内响应外部事件。
- 高度可靠性:能够保证任务的处理时间,避免系统故障。
- 任务调度:能够合理地分配系统资源,调度任务的执行顺序。
- 中断处理:能够迅速响应硬件中断,并进行相应的处理。
什么是实时进程/线程
- 实时进程/线程是在实时操作系统中运行的任务单元。
- 实时进程/线程具有以下特点:
- 优先级:每个进程/线程都有一个优先级,高优先级的进程/线程会在低优先级的进程/线程之前得到处理。
- 响应时间:实时进程/线程必须在固定的时间内完成,以满足实时性要求。
- 预防阻塞:实时进程/线程应尽量避免引起阻塞,以免影响其他任务的执行。
引脚
名称 | 引脚 | 功能 |
---|---|---|
LED_G | gpio2_pin4 | 绿灯 |
LED_B | gpio2_pin12 | 蓝灯 |
LED_R | gpio2_pin13 | 红灯 |
Boot_Mode[0] | gpio1_pin5 | 拨码开关0号 |
Boot_Mode[1] | gpio1_pin7 | 拨码开关1号 |
Boot_Mode[2] | gpio1_pin11 | 拨码开关2号 |
Boot_Mode[3] | gpio1_pin13 | 拨码开关3号 |