本系列文章主要讲Linux中的中断和时间管理,文章机构如下:
01 - 驱动中的中断处理
02 - 中断下半部 tasklet
03 - 中断的下半部 workqueue
04 - Linux中的延时操作函数
05 - Linux硬件定时 jiffies
06 - Linux 低分辨率定时器
07 - Linux高分辨率定时器
1. 中断的上下部
在上个例程提到,中断处理函数应该尽快完成,否则会影响对其他中断的及时响应,影响系统的性能。但有时候这些耗时的操作又避免不了,比如电容触摸屏通过中断通知 SOC 有触摸事件发生,SOC 响应中断,然后通过 IIC 接口读取触摸坐标值并将其上报给系统。但是我们都知道 IIC 的速度最高也只有400Kbit/S,所以在中断中通过 IIC 读取数据就会浪费时间。为了解决这个问题,Linux将中断分为了两部分,上半部和下半部,上半部能完成紧急但很快能完成的事情,下半部完成不紧急但比较耗时的事情。对于电容触摸屏来讲可以将通过 IIC 读取触摸数据的操作暂后执行,中断处理函数仅仅相应中断,然后清除中断标志位即可。
中断上半部和下半部的区分可参考如下:
①、如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
②、如果要处理的任务对时间敏感,可以放到上半部。
③、如果要处理的任务与硬件有关,可以放到上半部
④、除了上述三点以外的其他任务,优先考虑放到下半部。
2. 软中断
在学习 tasklet 之前,首先了解一下软中断,软中断是中断下半部机制的一种,目前内核中定义软中断的编号如下:
enum
{
HI_SOFTIRQ=0, // 高优先级软中断
TIMER_SOFTIRQ, // 定时器软中断
NET_TX_SOFTIRQ, // 网络数据发送软中断
NET_RX_SOFTIRQ, // 网络数据接收软中断
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ, // tasklet 软中断
SCHED_SOFTIRQ, // 调度软中断
HRTIMER_SOFTIRQ, // 高精度定时器软中断
RCU_SOFTIRQ, // RCU 软中断
NR_SOFTIRQS
};
3. tasklet
软中断通常由内核的开发者设计,内核开发者保留了一个软中断给驱动的开发者,他就是 TASKLET_SOFTIRQ(tasklet 软中断)内核中对 tasklet 的处理函数和宏定义如下:
struct tasklet_struct
{
struct tasklet_struct *next; // 构成链表的指针
unsigned long state; // tasklet被调度的状态,已经被调度还是已经在执行
atomic_t count; // 用于禁止tasklet执行(非0时禁止执行)
void (*func)(unsigned long); // tasklet下半部的函数
unsigned long data; // 传递给下半部的参数
};
内核中定义的tasklet相关函数如下:
DECLARE_TASKLET(name, func, data) // 静态定义一个 tasklet_struct 结构体,名字为name,下半部函数为func,传递的数据为data
DECLARE_TASKLET_DISABLED(name, func, data) // 定义 tasklet_struct 结构体,count被赋值为1,不能被执行,需要调用 tasklet_enable 使能
// 宏定义的原型如下
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
void tasklet_init(struct tasklet_struct * t, void(* func)(unsigned long), unsigned long data) // 初始化动态分配 tasklet_struct 结构体
inline void tasklet_schedule(struct tasklet_struct * t) // 将指定的 tasklet_struct 结构体对象加入到tasklet的链表中,下半部函数将会在未来的某个时间被调度
4. 示例代码
4.1 demo.c
在代码的22行静态定义一个tasklet,在代码的55行将指定的tasklet_struct 结构体对象加入到tasklet的链表中,下半部函数将会在未来的某个时间被调度,在函数 func_tasklet 中将原本属于中断函数处理的内容交给中断的下半部函数处理
#include <linux/module.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/atomic.h>
#include <linux/slab.h> // kzalloc和kfree的头文件
#include <linux/interrupt.h> // 中断相关的头文件
typedef struct
{
dev_t dev_no;
char devname[20];
char classname[20];
struct cdev demo_cdev;
struct class *cls;
struct device *dev;
struct mutex my_mutex; // 定义一个互斥体,在init函数中进行初始化
}demo_struct;
demo_struct my_demo_dev; // 定义一个设备结构体指针,指向NULL
static void func_tasklet(unsigned long args); // 中断下半部函数的声明
DECLARE_TASKLET(tasklet_test, func_tasklet, (unsigned long)&my_demo_dev); // 静态定义一个tasklet
static int demo_open(struct inode *inode, struct file *filp)
{
/* 首先判断设备是否可用 */
if( mutex_lock_interruptible(&my_demo_dev.my_mutex) ) // 访问共享资源之前获取互斥体,成功获取返回0
{
return -ERESTARTSYS; // 不能获取返回错误码
}
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
return 0;
}
static int demo_release(struct inode *inode, struct file *filp)
{
/* 释放互斥量 */
mutex_unlock(&my_demo_dev.my_mutex);
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
return 0;
}
struct file_operations demo_ops = {
.open = demo_open,
.release= demo_release,
};
irqreturn_t my_irq_handler(int irq, void *dev_id)
{
tasklet_schedule(&tasklet_test); // 将指定的 tasklet_struct 结构体对象加入到tasklet的链表中,下半部函数将会在未来的某个时间被调度
return IRQ_RETVAL(IRQ_HANDLED); // 中断处理函数常用的返回值
}
static void func_tasklet(unsigned long args)
{
/*
将上半部的事情放到下半部执行
*/
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
}
static int __init demo_init(void)
{
int ret;
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
strcpy(my_demo_dev.devname, "demo_chrdev"); // 给设备名字赋值
strcpy(my_demo_dev.classname, "demo_class"); // 给设备类的名字赋值
mutex_init(&my_demo_dev.my_mutex); // 初始化互斥体
ret = alloc_chrdev_region(&my_demo_dev.dev_no, 0, 0, my_demo_dev.devname);
if (ret)
{
printk("alloc_chrdev_region failed.\n");
goto region_err;
}
cdev_init(&my_demo_dev.demo_cdev, &demo_ops);
ret = cdev_add(&my_demo_dev.demo_cdev, my_demo_dev.dev_no, 1);
if (ret < 0)
{
printk("cdev_add failed.\n");
goto add_err;
}
my_demo_dev.cls = class_create(THIS_MODULE, my_demo_dev.classname); /* 在目录/sys/class/.. */
if ( IS_ERR(my_demo_dev.cls) )
{
ret = PTR_ERR(my_demo_dev.cls);
printk("class_create failed.\n");
goto cls_err;
}
my_demo_dev.dev = device_create(my_demo_dev.cls, NULL, my_demo_dev.dev_no, NULL, "chrdev%d", 0); /* 在目录/dev/.. */
if ( IS_ERR(my_demo_dev.dev) )
{
ret = PTR_ERR(my_demo_dev.dev);
printk("device_create failed.\n");
goto dev_err;
}
// 注册中断,因为是共享中断所以必须传递dev_id
ret = request_irq(123, my_irq_handler, IRQF_TRIGGER_RISING | IRQF_SHARED, "irq_test", &my_demo_dev);
if ( ret ) // 成功返回0,失败返回错误码
{
printk("request_irq failed.\n");
goto irq_err;
}
return 0;
irq_err:
device_destroy(my_demo_dev.cls, my_demo_dev.dev_no);
dev_err:
class_destroy(my_demo_dev.cls);
cls_err:
cdev_del(&my_demo_dev.demo_cdev);
add_err:
unregister_chrdev_region(my_demo_dev.dev_no, 1);
region_err:
return ret;
}
static void __exit demo_exit(void)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
free_irq(123, &my_demo_dev); // 注销中断
device_destroy(my_demo_dev.cls, my_demo_dev.dev_no);
class_destroy(my_demo_dev.cls);
cdev_del(&my_demo_dev.demo_cdev);
unregister_chrdev_region(my_demo_dev.dev_no, 1);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
4.2 test.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd;
fd = open("/dev/chrdev0", O_RDWR, 0666);
if (fd < 0)
{
perror("open");
return -1;
}
sleep(60);
close(fd);
return 0;
}
4.3 Makefile
KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-04.00.00.04/board-support/linux-4.9.28/
PWD := $(shell pwd)
all:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
arm-linux-gnueabihf-gcc test.c -o app
install:
sudo cp *.ko app /tftpboot
clean:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
rm app
obj-m += demo.o