1、内核中断处理
如果CPU接收到一个中断,它会停止一切工作,调用中断处理函数,因为进程调度依赖中断,此时进程调度也会停止,所以就要求我们的中断处理一定要快。
使用中断时,需要注意:
不能使用导致睡眠的处理机制(信号量、等待队列等)
不能与用户空间交互数据(copy_to/from_user)
中断处理函数执行时间尽可能短
1.1 中断顶半部、底半部
在真实的系统中,当中断来临时,要完成的工作往往不能立即完成,需要大量的耗时处理。
所以将中断分为两部分:
顶半部:处理紧急的硬件操作
底半部:处理不紧急的耗时操作,执行过程中中断是使能的,可以被打断
1.2 中断底半部的实现
软中断:供内核使用的机制
微线程:通过软中断机制进行调度
工作队列:将工作交由一个内核线程处理
2、微线程
1.tasklet只可以在一个CPU上同步地执行,不同的tasklet可以在不同地CPU上同步地执行。
2.tasklet的实现是建立在两个软件中断的基础之上的,即HI_SOFTIRQ和TASKLET_SOFTIRQ,本质上没有什么区别,只不过HI_SOFTIRQ的优先级更高一些
3.由于tasklet是在软中断上实现的,所以像软中断一样不能睡眠、不能阻塞,处理函数内不能含有导致睡眠的动作,如减少信号量、从用户空间拷贝数据或手工分配内存等。
4.一个 tasklet 能够被禁止并且之后被重新使能; 它不会执行直到它被使能的次数与被禁止的次数相同.
5.tasklet的串行化使tasklet函数不必是可重入的,因此简化了设备驱动程序开发者的工作。
6.每个cpu拥有一个tasklet_vec链表,具体是哪个cpu的tasklet_vec链表,是根据当前线程是运行在哪个cpu来决定的。
2.2 内核微线程使用过程
1、 定义微线程结构体变量,编写处理函数
2、 初始化微线程(宏或初始化函数)
3、 允许微线程调度(可选)
4、 添加微线程进而调用处理函数
5、 终止微线程调度(可选)
6、 销毁微线程
2.3 主要方法
#include <linux/interrupt.h>
1.微线程创建
方法一(采用宏)
DECLARE_TASKLET(name, func, data);
参数:微线程名称、任务处理函数、任务处理函数的参数
方法二(使用初始化函数)
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data);
参数1:用户已定义tasklet_struct 变量的地址
参数2:tasklet的任务处理函数
参数3:func函数的参数
2.微线程调度函数
//允许微线程调度(默认为允许)
void tasklet_enable(struct tasklet_struct * );
//将tasklet加入微线程列表后,立即执行
void tasklet_schedule(struct tasklet_struct * );
3.微线程禁止:
//禁止微线程调度
void tasklet_disable(struct tasklet_struct * );
void tasklet_disable_nosync(struct tasklet_struct * );
4.微线程销毁函数
void tasklet_kill(struct tasklet_struct * );
2.4 demo 基于iTop4412
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <mach/regs-gpio.h>
#include <asm/io.h>
#define DEMO_DEBUG
#ifdef DEMO_DEBUG
#define dem_dbg(fmt, arg...) printk(KERN_WARNING fmt, ##arg)
#else
#define dem_dbg(fmt, arg...) printk(KERN_DEBUG fmt, ##arg)
#endif
//1 定义微线程结构体变量
static struct tasklet_struct tasklet;
static int key_value = -1;
static int dev_id = 9;
static unsigned int irq;
//open引用计数,打开一次计数值加一,关闭则相反
//enable_irq和disable_irq需要成对使用,否则会打印警告。
static int open_flag = 0;
static void init_irq(void)
{
int ret;
ret = gpio_request(EXYNOS4_GPX1(1), "EINT9");
if (ret) {
printk("request EINT9 failed, ret = %d\n", ret);
}
s3c_gpio_cfgpin(EXYNOS4_GPX1(1), S3C_GPIO_SFN(0xF));
s3c_gpio_setpull(EXYNOS4_GPX1(1), S3C_GPIO_PULL_UP);
gpio_free(EXYNOS4_GPX1(1));
}
static int demo_open (struct inode *pnode, struct file *filp)
{
printk("==>demo_open\n");
if(!open_flag)
enable_irq(irq); //打开中断
open_flag++;
return 0;
}
static ssize_t demo_read (struct file *filp, char __user *buf, size_t count, loff_t *offp)
{
int key;
unsigned long len = min(count, sizeof(key));
int retval;
key = key_value;
if(copy_to_user(buf, &key, len) != 0){
retval = -EFAULT;
goto cp_err;
}
key_value = -1;
return len;
cp_err:
return retval;
}
static int demo_release (struct inode *pnode, struct file *filp)
{
printk("==>demo_release\n");
open_flag--;
if(!open_flag)
disable_irq(irq); //关闭中断
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = demo_open,
.read = demo_read,
.release = demo_release,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.fops = &fops,
.name = "demo0",
};
static void key_taskhandler(unsigned long arg)
{
dem_dbg("==>%s do tasklet handler\n", __FUNCTION__);
//全局变量赋键值,这里可以是一个耗时操作
key_value = 1;
return;
}
//中断回调
static irqreturn_t eint9_interrupt(int irq, void *dev_id) {
printk("%s(%d)\n", __FUNCTION__, __LINE__);
//3 将tasklet添加到微线程的调度列表中并返回
tasklet_schedule(&tasklet);
return IRQ_HANDLED;
}
static int __init demo_init(void)
{
int res;
dem_dbg("==>demo_init\n");
init_irq();
irq = gpio_to_irq(EXYNOS4_GPX1(1));
res = request_irq(irq, eint9_interrupt, IRQ_TYPE_EDGE_FALLING /*IRQF_TRIGGER_FALLING*/, "eint9", (void *)&dev_id);
if (res < 0) {
printk("Request IRQ %d failed, %d\n", IRQ_EINT(9), res);
goto irq_err;
}
disable_irq(irq);
//2 初始化微线程,指定微线程处理函数和传递参数
tasklet_init(&tasklet, key_taskhandler, 0);
res = misc_register(&misc);
if(res < 0){
dem_dbg("register misc device failed!\n");
goto misc_err;
}
return 0;
misc_err:
free_irq(irq, (void *)&dev_id);
irq_err:
return res;
}
static void __exit demo_exit(void)
{
dem_dbg("==>demo_exit\n");
//4 终止未处理的微线程
tasklet_kill(&tasklet);
free_irq(irq, (void *)&dev_id);
misc_deregister(&misc);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("Dual BSD/GPL");
测试代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd = 0;
int key;
int retval;
fd = open("/dev/demo0", O_RDWR | O_NONBLOCK);
if(fd < 0){
perror("==>open /dev/demo0 err");
goto err;
}
while(1){
//成功返回实际读取字节数,失败返回负值
retval = read(fd, &key, sizeof(key));
if(retval < 0){
perror("==> not reading key value");
goto err;
}
printf("==> read bytes: %d read content(key =): %d\n", retval, key);
sleep(1);
}
return 0;
err:
if(fd > 0)
close(fd);
return -1;
}
3、工作队列
3.1 简介
工作队列(work queue)是另外一种将工作推后执行的形式,它和tasklet有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。
那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。
3.2 使用过程
1.定义一个工作
2.编写工作处理函数
3.初始化工作(给工作指定处理函数)
4.创建工作队列
5.在合适的时机(一般是中断服务程序中)将创建的工作加入到工作队列中
6.销毁工作队列、工作
3.3 主要方法
#include <workqueue.h> /*头文件包含*/
1.工作队列的创建及销毁:
//定义一个工作队列结构体指针
static struct workqueue_struct *key_workqueue;
//创建工作队列
struct workqueue_struct *create_workqueue(char *);
参数:工作队列的名称(字符串)
返回值:创建好的工作队列
//销毁工作队列,参数为待销毁的工作队列
void destroy_workqueue(struct workqueue_struct *);
2.工作的创建、初始化
//创建一个工作
struct work_struct work;
//工作初始化宏
INIT_WORK(&work, func);
参数1:用户已定义的work_struct变量(工作)
参数2:任务处理函数,用户实现(中断底半部)
3.添加工作到工作队列
int queue_work(struct workqueue_struct*wq,
struct work_struct *work);
参数1:第1部创建的工作队列
参数2:第2部创建的工作
4.从工作队列删除工作
//终止队列中的工作(即使处理程序已经在处理该任务)
int cancel_work_sync(struct work_struct *work);
//判断任务项目是否在进行中
int work_pending(struct work_struct work );
返回true表示正在运行,false表示停止
3.4
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <mach/regs-gpio.h>
#include <asm/io.h>
#define DEMO_DEBUG
#ifdef DEMO_DEBUG
#define dem_dbg(fmt, arg...) printk(KERN_WARNING fmt, ##arg)
#else
#define dem_dbg(fmt, arg...) printk(KERN_DEBUG fmt, ##arg)
#endif
//1 定义一个工作队列和一个工作变量
static struct workqueue_struct *key_workqueue;
static struct work_struct key_work;
static int key_value = -1;
static int dev_id = 9;
static unsigned int irq;
//open引用计数,打开一次计数值加一,关闭则相反
static int open_flag = 0;
static void init_irq(void)
{
int ret;
ret = gpio_request(EXYNOS4_GPX1(1), "EINT9");
if (ret) {
printk("request EINT9 failed, ret = %d\n", ret);
}
s3c_gpio_cfgpin(EXYNOS4_GPX1(1), S3C_GPIO_SFN(0xF));
s3c_gpio_setpull(EXYNOS4_GPX1(1), S3C_GPIO_PULL_UP);
gpio_free(EXYNOS4_GPX1(1));
}
static int demo_open (struct inode *pnode, struct file *filp)
{
if(!open_flag){
printk("==>demo_open\n");
enable_irq(irq); //打开中断
}
open_flag++;
return 0;
}
static ssize_t demo_read (struct file *filp, char __user *buf, size_t count, loff_t *offp)
{
int key;
unsigned long len = min(count, sizeof(key));
int retval;
key = key_value;
if(copy_to_user(buf, &key, len) != 0){
retval = -EFAULT;
goto cp_err;
}
key_value = -1;
return len;
cp_err:
return retval;
}
static int demo_release (struct inode *pnode, struct file *filp)
{
printk("==>demo_release\n");
open_flag--;
if(!open_flag)
disable_irq(irq); //关闭中断
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = demo_open,
.read = demo_read,
.release = demo_release,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.fops = &fops,
.name = "demo0",
};
//消息队列处理函数
static void key_workhandler(struct work_struct *work)
{
dem_dbg("==>%s do tasklet handler\n", __FUNCTION__);
//全局变量赋键值,这里可以是一个耗时操作
key_value = 1;
return;
}
//中断回调
static irqreturn_t eint9_interrupt(int irq, void *dev_id) {
printk("%s(%d)\n", __FUNCTION__, __LINE__);
//4 将work添加到工作队列workqueue中
queue_work(key_workqueue, &key_work);
return IRQ_HANDLED;
}
static int __init demo_init(void)
{
int res;
dem_dbg("==>demo_init\n");
init_irq();
irq = gpio_to_irq(EXYNOS4_GPX1(1));
res = request_irq(irq, eint9_interrupt,
IRQ_TYPE_EDGE_FALLING /*IRQF_TRIGGER_FALLING*/, "eint9", (void *)&dev_id);
if (res < 0) {
printk("Request IRQ %d failed, %d\n", IRQ_EINT(9), res);
goto irq_err;
}
disable_irq(irq);
//2 创建工作队列
key_workqueue = create_workqueue("key_workqueue");
if(IS_ERR(key_workqueue)){
dem_dbg("==>create workqueue failed!\n");
res = PTR_ERR(key_workqueue);
goto wq_err;
}
//3 初始化工作work, 指定处理函数key_workhandler
INIT_WORK(&key_work, key_workhandler);
res = misc_register(&misc);
if(res < 0){
dem_dbg("register misc device failed!\n");
goto misc_err;
}
return 0;
misc_err:
free_irq(irq, (void *)&dev_id);
irq_err:
wq_err:
return res;
}
static void __exit demo_exit(void)
{
dem_dbg("==>demo_exit\n");
//5 清空工作队列,并释放已创建的工作队列回收资源
flush_workqueue(key_workqueue);
destroy_workqueue(key_workqueue);
free_irq(irq, (void *)&dev_id);
misc_deregister(&misc);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("Dual BSD/GPL");
测试文件与微线程相同
4、异步数据处理 kfifo
kfifo是内核提供的一个双向循环队列,用于缓存数据,使用fifo的方式。
4.1 调用过程
1.定义kfifo变量
2.为kfifo开辟空间
3.把数据写入kfifo
4.从kfifo读出数据
5.释放kfifo
4.2 主要方法
#include <linux/kfifo.h> //头文件包含
//kfifo结构体类型
struct kfifo {
unsigned char *buffer; //存放数据的缓存区
unsigned int size; //buffer空间大小
unsigned int in; //指向buffer队头
unsigned int out; //指向buffer队尾
};
1.申请与释放Kfifo
//为fifo分配size大小的内存空间,返回0表示成功
int kfifo_alloc(struct kfifo *fifo, unsigned int size, gfp_t gfp_mask);
fifo:kfifo类型变量地址
size:分配内存空间的大小(单位字节)
gfp_mask:GFP_KERNEL (申请内存标志位)
//释放创建的FIFO
void kfifo_free(struct kfifo *fifo);
2.kfifo操作函数
//将数据放入kfifo内,返回值为实际写入的数据长度
unsigned int kfifo_in(struct kfifo *fifo, const void *from,unsigned int len)
//从kfifo读取数据,返回值为实际读出的数据长度
unsigned int kfifo_out(struct kfifo *fifo, void *to, unsigned int len)
参数1:用户定义的kfifo
参数2:读写数据的首地址
参数3:读写数据的大小
3.kfifo辅助检测函数:
//获取fifo内的已用数据个数
unsigned int kfifo_len(struct kfifo *fifo)
//获取fifo总大小
unsigned int kfifo_size(struct kfifo *fifo)
//检查kfifo是否为空
int kfifo_is_empty(struct kfifo *fifo)
//检查kfifo是否为满
int kfifo_is_full(struct kfifo *fifo)