目录
- 1. 前言
- 2. Linux软件定时器
- 2.1 内核频率选择
- 2.2 重要的API函数
- 2.3 Linux软件定时器的使用配置流程
- 4. Linux中断
- 4.1 简单中断使用
- 4.1.1 简要说明
- 4.1.2 重要的API函数
- 4.1.3 中断的简要配置流程
- 4.2. 中断的上半部和下半部
- 4.2.1 tasklet实现下半部
- 4.2.2 work实现下半部
1. 前言
关于定时器和中断是我们老生常谈的外设,也是每一款单片机基本必备的基础外设,由于之前都是在裸机层面对单片机的定时器和中断进行,我之前也对IMX6ULL的裸机中断系统进行了简要的介绍,连接如下:linux-IMX6ULL中断配置流程,同样我也对IMX6ULL中的定时器进行了简要的分析和介绍,连接如下:linux-IMX6ULL-定时器-GPT-串口配置流程-思路,但是这次进行定时器的操作是在系统层面进行配置,和之前的思路不太一样,我们之前使用的PIT等硬件定时器,但是我在内核中使用的是软件定时器,这种定时器的基准是由硬件产生并通过底层层层上报给内核,我们可以通过内核图形化配置选择这个最终的定时器频率是多少,而且注意的是内核提供的定时器是没有自动重装的模式,我们每次打开一个定时器后,这个定时器结束后就没了,如果要周期性的触发类似中断的效果,就要不断的在定时器服务函数结束的末尾重新启动定时器,并给写入定时周期;
2. Linux软件定时器
2.1 内核频率选择
我们可以在内核的图形化配置中选择定时器的基准频率,如下:
我们也称为节拍率,这里我们选择100hz,当然这种选择是根据每个人的实际情况和硬件的性能进行选择的,如果选择1000hz,那么对于硬件来说进入定时器服务函数的频率就会更加频繁,处理负担也是近10倍的增加;
Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动的时候会将 jiffies 初始化为 0,jiffies 定义在文件 include/linux/jiffies.h 中,并且对于64位的jiffies而言我们不用考虑绕回的问题,但是对于32位的 jiffies我们就要考虑绕回的问题;
2.2 重要的API函数
- 时间转换类
int jiffies_to_msecs(const unsigned long j)
int jiffies_to_usecs(const unsigned long j)
u64 jiffies_to_nsecs(const unsigned long j)
long msecs_to_jiffies(const unsigned int m)
long usecs_to_jiffies(const unsigned int u)
unsigned long nsecs_to_jiffies(u64 n)
- 定时器初始化类
void init_timer(struct timer_list *timer)
void add_timer(struct timer_list *timer)
int del_timer(struct timer_list * timer)
int del_timer_sync(struct timer_list *timer)
int mod_timer(struct timer_list *timer, unsigned long expires)
:重新激活定时器
- 内核短延时类
void ndelay(unsigned long nsecs)
void udelay(unsigned long usecs)
void mdelay(unsigned long mseces)
2.3 Linux软件定时器的使用配置流程
定时器也相当于一个外设,我们是在驱动层面对定时器进行配置,因此我们的基础框架还是字符设备的那一套,总的来说就是在字符设备的基础上添加上我们的定时器配置,例如我们要控制LED定时的开启和关闭,那么对于我们而言我们只需在LED字符设备的基础上添加我们的定时器配置,一般定时器的配置流程如下:
我们在文件操作函数中可以使用long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
对定时器的相关操作进行写入,当然用write应该也是可以实现这个功能的,不过这里有注意事项,用unlocked_ioctl这个函数进行写入定时器的功能时要进行宏定义:如下图所示:
4. Linux中断
4.1 简单中断使用
4.1.1 简要说明
在裸机中对于中断是频繁使用的,在裸机中对中断的配置流程也是相对比较繁琐,例如要比如配置寄存器,使能 IRQ,注册中断服务函数 等等,详情可以参考我前言中的IMX6ULL的中断系统的介绍,这里我们已经用上了Linux操作系统,对于中断的使用就变得比较简单,Linux内核已经提供了比较完善的中断框架,我们只需要申请中断然后再注册中段服务函数即可,不需要一系列复杂的寄存器配置;这里就不进行一些理论的详细说明,我们已经再裸机中对中断的配置进行了底层的操控,对于Linux系统而言,就是通过一系列复杂的方式把底层这些内容给整合成了一个个框架,我们这里学习的就是linux中断配置框架;作为初学者过于深入的了解可能会适得其反;
4.1.2 重要的API函数
获取中断号
static inline int gpio_to_irq(申请的GPIO号)
:GPIO中断专用static inline unsigned int irq_of_parse_and_map(设备节点地址,int index)
:通用
中断服务函数
static irqreturn_t 中断函数名(int irq, void *dev_id)
中断申请和释放
request_irq(中断号, 中断函数名,中断触发方式,中断名字,向中断服务函数传入的结构体地址);
free_irq(中断号,向中断服务函数传入的结构体地址);
4.1.3 中断的简要配置流程
这里的中断简要配置思路就是:获取中断号->申请中断->编写中断服务函数
,可以看到还是非常简单的,这里的字符驱动框架是正点原子的key按键驱动框架,加上中断的内容配置如下图:
4.2. 中断的上半部和下半部
对于Linux系统而言,我们要求中断是快进快出,对于一些对时间要求比较严格和处理耗时短暂的程序我们可以放在中断函数中直接进行处理,这个过程称为中断的上半部
,因此对于一些耗时的处理内容,我们如果把其直接放在中断函数中去处理,就与前面的中断快进快出的理念相违背,因此我们把这部分内容放到中断外面,中断里面只是起到通知的作用,这样我们就实现了中断的快进快出以及耗时处理内容也放在了中断外面进行处理,这个过程我们称为中断的下半部
;
但是对于下半部我们想法很好,但是咋样实现这个通知的过程呢?如果想详细了解可以产靠内核源码,内核大神们以及把这一切都封装成立API,一般有三种方式,软中断、tasklet、工作队列等;
4.2.1 tasklet实现下半部
主要就是在前面简单中断的基础上进行如下流程:
每个步骤比较重要的API:
- 初始化tasklet:
tasklet_init(&dev->irqkey[i].tasklet,调度的函数名,(unsigned long)dev);
- 中断服务函数中调度通知tasklet:
tasklet_schedule(&dev->irqkey[0].tasklet);
- 调度的函数:
static void 调度的函数名(unsigned long data)
4.2.2 work实现下半部
使用work来实现下半部和tasklet是类似的,就是换个函数名,操作步骤都是一样的如下:
- 初始化work:
INIT_WORK(&dev->irqkey[i].work,调度的函数名);
- 中断服务函数中调度通知work:
schedule_work(&dev->irqkey[0].work);
- 调度的函数:
static void 调度的函数名(unsigned long data)