中断处理分为上半部和下半部
一般来说中断处理的上半部和下半部都是不允许出现睡眠和阻塞的。但是对于下半部,并不是一刀切,下半部的实现方式有软中断和tasklet(不允许睡眠和阻塞)以及工作队列(允许睡眠和阻塞)。
上半部:一般中断的中断处理函数为上半部,要求做耗时少的动作,尽量迅速,一定不能休眠和阻塞。
下半部:由于上半部只能执行耗时少的操作,所以耗时长的操作就放在下半部,两个的界限并不是很明显,取决于我们要将哪个操作放在上半部还是下半部。
下面讨论一下工作队列的原理及实现
实现原理:工作队列(workqueue)是一种将工作推后执行的形式,工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。最重要的就是工作队列允许被重新调度甚至是睡眠。工作队列的本质是创建一个一个普通的内核线程,我们称为工作者线程。
以下是几个工作队列抽象出来的数据结构
1:struct rt_workqueue表示一个工作队列对象(包含一个工作者线程)。
2:struct rt_work为工作队列上的任务。当我们将一个任务加入一个工作队列时候,会将该任务挂接到struct rt_workqueue的任务链表上,当工作线程未就绪的时候,这个工作者线程就会唤醒,去遍历这个链表上的所有任务,执行完后,将该任务从链表上拿走,工作者线程继续休眠。
3:双向链表操作原理请查看文章(数据结构-------双向链表),内存申请释放请查看文章(rtthread之小内存算法)。
数据结构定义
//内存申请,实现及原理请参考rtthread之小内存算法
#ifndef RT_KERNEL_MALLOC
#define RT_KERNEL_MALLOC(sz) rt_malloc(sz)
#endif
//内存释放
#ifndef RT_KERNEL_FREE
#define RT_KERNEL_FREE(ptr) rt_free(ptr)
#endif
//工作队列对象,包含工作者线程
struct rt_workqueue
{
rt_list_t work_list;
rt_thread_t work_thread;
};
//任务对象,包含工作任务及数据
struct rt_work
{
rt_list_t list;
void (*work_func)(struct rt_work* work, void* work_data);
void *work_data;
};
原理实现
1.工作者线程
//工作者线程,处理挂接到该工作队列上的任务或者工作
static void _workqueue_thread_entry(void* parameter)
{
struct rt_work* work;
struct rt_workqueue* queue;
uint32_t level = 0;
uint8_t workqueue_empty = 0;
queue = (struct rt_workqueue*) parameter;
RT_ASSERT(queue != RT_NULL);
while (1)
{
level = rt_hw_interrupt_disable();
//判断工作队列上是否有任务,没有则挂起工作者线程
workqueue_empty = rt_list_isempty(&(queue->work_list));
rt_hw_interrupt_enable(level);
if (workqueue_empty)
{
//没有任务,则挂起工作者线程
rt_thread_suspend(rt_thread_self());
rt_schedule();
}
level = rt_hw_interrupt_disable();
//有任务,则获取对应任务的信息
work = rt_list_entry(queue->work_list.next, struct rt_work, list);
//从工作队列任务表中删除当前任务
rt_list_remove(&(work->list));
rt_hw_interrupt_enable(level);
//执行任务
work->work_func(work, work->work_data);
}
}
2.工作队列初始化及创建工作者线程
//创建及初始化工作队列对象,创建工作者线程,返回工作队列对象
struct rt_workqueue *rt_workqueue_create(const char* name, rt_uint16_t stack_size, rt_uint8_t priority)
{
struct rt_workqueue *queue = RT_NULL;
//申请一个工作队列对象句柄
queue = (struct rt_workqueue*)RT_KERNEL_MALLOC(sizeof(struct rt_workqueue));
if (queue != RT_NULL)
{
//初始化工作队列任务链表,指向自己
rt_list_init(&(queue->work_list));
//创建工作者处理线程
queue->work_thread = rt_thread_create(name, _workqueue_thread_entry, queue, stack_size, priority, 10);
if (queue->work_thread == RT_NULL)
{
RT_KERNEL_FREE(queue);
return RT_NULL;
}
//开启工作者线程
rt_thread_startup(queue->work_thread);
}
return queue;
}
3.向工作队列添加任务
//将任务加入到工作队列中
rt_err_t rt_workqueue_dowork(struct rt_workqueue* queue, struct rt_work* work)
{
uint32_t level = 0;
RT_ASSERT(queue != RT_NULL);
RT_ASSERT(work != RT_NULL);
level = rt_hw_interrupt_disable();
//将任务加入到工作队列任务链表中
rt_list_remove(&(work->list));
rt_list_insert_after(queue->work_list.prev, &(work->list));
//如果当前工作者线程处于挂起态,则唤醒该工作者线程
if (queue->work_thread->stat != RT_THREAD_READY)
{
rt_hw_interrupt_enable(level);
rt_thread_resume(queue->work_thread);
rt_schedule();
}
else
rt_hw_interrupt_enable(level);
return RT_EOK;
}
4.删除工作队列
//删除工作队列
rt_err_t rt_workqueue_destroy(struct rt_workqueue* queue)
{
RT_ASSERT(queue != RT_NULL);
//删除工作者线程
rt_thread_delete(queue->work_thread);
//释放工作队列对象空间
RT_KERNEL_FREE(queue);
return RT_EOK;
}
5.将任务从工作队列中删除
//将任务从工作队列中删除
rt_err_t rt_workqueue_cancel_work(struct rt_workqueue* queue, struct rt_work* work)
{
uint32_t level = 0;
RT_ASSERT(queue != RT_NULL);
RT_ASSERT(work != RT_NULL);
level = rt_hw_interrupt_disable();
//从工作队列中移除任务
rt_list_remove(&(work->list));
rt_hw_interrupt_enable(level);
return RT_EOK;
}
应用:
在使用spi flash模拟u盘开发过程中,由于spi flash读写速度操作比较慢耗时,且在flash操作中封装了互斥操作,对于操作系统不能在中断中使用互斥量等操作,需要使用中断下半部的思想,将读取操作添加到工作队列中,从而实现模拟u盘的操作。
修改步骤:
1.创建工作队列
struct rt_workqueue *usb_irq_workqueue = NULL;
void USB_CDC_VCP_Thread(void* parameter)
{
.......
if(usb_irq_workqueue == NULL)
{
usb_irq_workqueue = rt_workqueue_create("usb_workqueue", 4096, 1);
rt_kprintf("[USB] usb irq workqueque creat %s\r\n",(usb_irq_workqueue != NULL)?"ok":"failed");
}
.......
}
2.修改usb中断操作,将任务添加到工作队列
struct usb_work_data
{
USB_OTG_CORE_HANDLE *pdev;
uint8_t epnum;
};
//修改Usb输入端点处理任务函数
void usb_msc_data_in(struct rt_work* work, void* work_data)
{
struct usb_work_data *usb_data = NULL;
usb_data = (struct usb_work_data *)work_data;
USBD_DCD_INT_fops->DataInStage(usb_data->pdev , usb_data->epnum);
}
//修改Usb输出端点处理任务函数
void usb_msc_data_out(struct rt_work* work, void* work_data)
{
struct usb_work_data *usb_data = NULL;
usb_data = (struct usb_work_data *)work_data;
USBD_DCD_INT_fops->DataOutStage(usb_data->pdev , usb_data->epnum);
}
extern struct rt_workqueue *usb_irq_workqueue;
static struct usb_work_data usb_data;
static struct rt_work usb_msc_work;
static uint32_t DCD_HandleInEP_ISR(USB_OTG_CORE_HANDLE *pdev)
{
......
if(doepint.b.xfercompl)
{
......
if(usb_irq_workqueue != NULL)
{
//将任务参数及任务函数加入到工作队列中
usb_data.pdev = pdev;
usb_data.epnum = epnum;
usb_msc_work.work_data = (void*)&usb_data;
usb_msc_work.work_func = usb_msc_data_in;
rt_workqueue_dowork(usb_irq_workqueue,&usb_msc_work);
}
......
}
......
}
static uint32_t DCD_HandleOutEP_ISR(USB_OTG_CORE_HANDLE *pdev)
{
......
if(doepint.b.xfercompl)
{
......
if(usb_irq_workqueue != NULL)
{
//将任务参数及任务函数加入到工作队列中
usb_data.pdev = pdev;
usb_data.epnum = epnum;
usb_msc_work.work_data = (void*)&usb_data;
usb_msc_work.work_func = usb_msc_data_out;
rt_workqueue_dowork(usb_irq_workqueue,&usb_msc_work);
}
......
}
......
}