我们已经学习了中断,但当有更高优先级的中断处理时,会有中断嵌套的情况发送.ARM架构中使用中断的上下部处理机制,而是为了优化中断处理的效率和系统的响应性。将两种情况相结合。
一、中断上下部
- 将中断处理过程分解为上半部和下半部。
- 上半部通常执行紧急的、必须快速完成的操作,如保存中断上下文、标记中断发生等。
- 下半部则执行可以稍后处理的、耗时较长的操作,如设备I/O操作、数据处理等。
- 上下部处理机制旨在提高中断处理的效率和系统响应性,通过减少中断处理时间,避免长时间占用处理器资源
二、实现方法:
(1)硬件层面的支持
-
中断控制器:现代处理器通常配备有中断控制器(如ARM的GIC中断控制器),这些控制器能够识别和处理来自不同外设的中断请求。中断控制器通常具有优先级管理功能,能够确保高优先级的中断能够打断低优先级的中断处理过程。
-
中断屏蔽寄存器:处理器和中断控制器都包含中断屏蔽寄存器,用于控制哪些中断被允许或禁止。在中断处理过程中,可以通过修改这些寄存器的值来暂时禁止或允许中断,以避免中断嵌套导致的复杂性。
(2)、软件层面的实现
-
中断向量表:在操作系统的启动阶段,会设置一个中断向量表,该表包含了不同中断号对应的中断服务例程(ISR)的入口地址。当中断发生时,处理器会根据中断号跳转到对应的中断服务例程执行。
-
上半部处理:
- 紧急任务处理:在中断服务例程的开始部分,执行那些必须立即完成的紧急任务,如保存现场(即保存当前处理器的状态)、更新状态寄存器等。
- 标记下半部:在紧急任务处理完成后,标记需要在下半部执行的任务或操作。这可以通过设置特定的标志位、将任务放入队列或触发特定的软中断等方式实现。
-
下半部处理:
- 软中断(softirq):在Linux内核中,下半部处理通常通过软中断实现。当上半部处理完成后,会触发一个软中断,该软中断的处理函数将在稍后(通常在系统调用返回或定时器中断发生时)被调用,以执行那些可以延迟的任务。
- tasklet和工作队列:除了软中断外,Linux内核还提供了tasklet和工作队列两种机制来实现下半部处理。tasklet是一种基于软中断的轻量级底半部处理机制,而工作队列则允许将任务推迟到内核线程中执行,具有更高的灵活性和可预测性。
-
恢复现场和继续执行:在下半部处理完成后,会恢复之前保存的现场(即恢复处理器的状态),并继续执行被中断的代码或任务。
三、相关函数
tasklet 相关函数
DECLARE_TASKLET(name, func, data)
: 声明一个tasklet,其中name
是tasklet的名称,func
是tasklet的回调函数,data
是传递给回调函数的参数。tasklet_init(t, func, data)
: 初始化一个tasklet,与DECLARE_TASKLET
类似,但允许动态初始化。tasklet_schedule(t)
: 调度tasklet执行。如果tasklet已经在执行队列中,则此调用不会再次添加它。tasklet_disable(t)
: 禁用tasklet,防止其被调度执行。tasklet_enable(t)
: 启用tasklet,允许其被调度执行。tasklet_kill(t)
: 终止tasklet的执行,并将其从执行队列中移除。
工作队列(Workqueues)
工作队列是Linux内核中用于处理延迟任务的更通用、更灵活的机制。它们允许内核在内核线程中异步执行任务,这些任务可以睡眠(即等待某些条件成立)。工作队列通常用于那些不能或不应该在中断上下文中执行的任务。
工作队列 相关函数
create_workqueue(name)
: 创建一个新的工作队列,name
是工作队列的名称。alloc_workqueue(name, flags, max_active, max_concurrent)
: 创建一个新的工作队列,允许更详细的配置,如并发级别等。INIT_WORK(work, func)
: 初始化一个工作项(work_struct),work
是工作项的指针,func
是当工作项被执行时要调用的函数。schedule_work(work)
: 将工作项加入到工作队列中,以便稍后执行。schedule_delayed_work(work, delay)
: 将工作项延迟一段时间后再加入到工作队列中。cancel_work_sync(work)
: 取消工作项的执行,并等待它完成(如果它已经开始执行)。destroy_workqueue(wq)
: 销毁工作队列,并等待所有工作项完成。
四、具体实现‘
1、tasklet
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <asm/io.h>
#include <asm/string.h>
#include <asm/uaccess.h>
#include <linux/miscdevice.h>
#include <asm-generic/errno-base.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/ioctl.h>
#define DEV_NAME "adc"
#define ADCCON 0x58000000
#define ADCDAT0 0x5800000C
#define CLKCON 0x4C00000C
static volatile unsigned long * adccon;
static volatile unsigned long * adcdat0;
static volatile unsigned long * clkcon;
static wait_queue_head_t wq;
static int condition = 0;
static struct tasklet_struct tsk;
#define ADC_MAGIC_NUM 'x'
#define ADC_SET_CHANNEL 2
#define CMD_ADC_SET_CHANNEL _IOW(ADC_MAGIC_NUM, ADC_SET_CHANNEL, unsigned char)
static void tasklet_handle(unsigned long arg)
{
condition = 1;
wake_up_interruptible(&wq);
printk("tasklet_schedule arg = %ld ...\n", arg);
}
static irqreturn_t irq_handler(int num, void * arg)
{
tasklet_schedule(&tsk);
printk("num = %d arg = %d\n", num, *(int *)arg);
return IRQ_HANDLED;
}
static void init_adc(void)
{
*adccon = (1 << 14) | (49 << 6);
}
static void start_adc(void)
{
*adccon |= (1 << 0);
}
static unsigned short read_adc(void)
{
unsigned short data = *adcdat0 & 0x3ff;
return data;
}
static int set_channel(unsigned char channel)
{
if(channel < 0 || channel > 7)
return -EINVAL;
*adccon &= ~(0x7 << 3);
*adccon |= (channel <<3);
return 0;
}
static int open (struct inode * inode, struct file * file)
{
init_adc();
printk("adc open ...\n");
return 0;
}
static ssize_t read (struct file * file, char __user * buf, size_t len, loff_t * offset)
{
//copy_to_user(buf, &value, sizeof(value));
unsigned short value = 0;
printk("adc read start ...\n");
condition = 0;
start_adc();
wait_event_interruptible(wq, condition);
value = read_adc();
copy_to_user(buf, &value, sizeof(value));
printk("adc read ...\n");
return sizeof(value);
}
static ssize_t write (struct file * file, const char __user * buf, size_t len, loff_t * offset)
{
return 0;
}
static long ioctl(struct file * file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
unsigned char args = 0;
switch(cmd)
{
case CMD_ADC_SET_CHANNEL:
copy_from_user(&args, (unsigned char *)arg, _IOC_SIZE(CMD_ADC_SET_CHANNEL));
ret = set_channel(args);
break;
default :
ret = -EINVAL;
}
return ret;
}
static int close (struct inode * inode, struct file * file)
{
printk("adc close ...\n");
return 0;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.unlocked_ioctl = ioctl,
.release = close
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
static int arg = 100;
static int __init adc_init(void)
{
int ret = misc_register(&misc);
if(ret < 0)
goto err_misc_register;
ret = request_irq(IRQ_ADC, irq_handler, IRQF_TRIGGER_FALLING | IRQF_DISABLED, "adc_irq", &arg);
if(ret < 0)
goto err_request_irq;
adccon = ioremap(ADCCON, sizeof(*adccon));
adcdat0 = ioremap(ADCDAT0, sizeof(*adcdat0));
clkcon = ioremap(CLKCON, sizeof(*clkcon));
*clkcon |= (1 << 15);
printk("clkcon = 0x%lx\n", *clkcon);
init_waitqueue_head(&wq);
tasklet_init(&tsk, tasklet_handle, 200);
printk("adc_init ...\n");
return ret;
err_misc_register:
misc_deregister(&misc);
printk("adc misc_register faiadc\n");
return ret;
err_request_irq:
disable_irq(IRQ_ADC);
free_irq(IRQ_ADC, &arg);
return ret;
}
static void __exit adc_exit(void)
{
iounmap(clkcon);
iounmap(adcdat0);
iounmap(adccon);
disable_irq(IRQ_ADC);
free_irq(IRQ_ADC, &arg);
misc_deregister(&misc);
printk("adc_exit ###############################\n");
}
module_init(adc_init);
module_exit(adc_exit);
MODULE_LICENSE("GPL");
2workqueue
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <asm/io.h>
#include <asm/string.h>
#include <asm/uaccess.h>
#include <linux/miscdevice.h>
#include <asm-generic/errno-base.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/ioctl.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#define DEV_NAME "adc"
#define ADCCON 0x58000000
#define ADCDAT0 0x5800000C
#define CLKCON 0x4C00000C
static volatile unsigned long * adccon;
static volatile unsigned long * adcdat0;
static volatile unsigned long * clkcon;
static wait_queue_head_t wq;
static int condition = 0;
static struct work_struct work;
#define ADC_MAGIC_NUM 'x'
#define ADC_SET_CHANNEL 2
#define CMD_ADC_SET_CHANNEL _IOW(ADC_MAGIC_NUM, ADC_SET_CHANNEL, unsigned char)
static void work_handle(struct work_struct *work)
{
condition = 1;
ssleep(2);
wake_up_interruptible(&wq);
printk("work_handle ...\n");
}
static irqreturn_t irq_handler(int num, void * arg)
{
schedule_work(&work);
printk("num = %d arg = %d\n", num, *(int *)arg);
return IRQ_HANDLED;
}
static void init_adc(void)
{
*adccon = (1 << 14) | (49 << 6);
}
static void start_adc(void)
{
*adccon |= (1 << 0);
}
static unsigned short read_adc(void)
{
unsigned short data = *adcdat0 & 0x3ff;
return data;
}
static int set_channel(unsigned char channel)
{
if(channel < 0 || channel > 7)
return -EINVAL;
*adccon &= ~(0x7 << 3);
*adccon |= (channel <<3);
return 0;
}
static int open (struct inode * inode, struct file * file)
{
init_adc();
printk("adc open ...\n");
return 0;
}
static ssize_t read (struct file * file, char __user * buf, size_t len, loff_t * offset)
{
//copy_to_user(buf, &value, sizeof(value));
unsigned short value = 0;
printk("adc read start ...\n");
condition = 0;
start_adc();
wait_event_interruptible(wq, condition);
value = read_adc();
copy_to_user(buf, &value, sizeof(value));
printk("adc read ...\n");
return sizeof(value);
}
static ssize_t write (struct file * file, const char __user * buf, size_t len, loff_t * offset)
{
return 0;
}
static long ioctl(struct file * file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
unsigned char args = 0;
switch(cmd)
{
case CMD_ADC_SET_CHANNEL:
copy_from_user(&args, (unsigned char *)arg, _IOC_SIZE(CMD_ADC_SET_CHANNEL));
ret = set_channel(args);
break;
default :
ret = -EINVAL;
}
return ret;
}
static int close (struct inode * inode, struct file * file)
{
printk("adc close ...\n");
return 0;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.unlocked_ioctl = ioctl,
.release = close
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
static int arg = 100;
static int __init adc_init(void)
{
int ret = misc_register(&misc);
if(ret < 0)
goto err_misc_register;
ret = request_irq(IRQ_ADC, irq_handler, IRQF_TRIGGER_FALLING | IRQF_DISABLED, "adc_irq", &arg);
if(ret < 0)
goto err_request_irq;
adccon = ioremap(ADCCON, sizeof(*adccon));
adcdat0 = ioremap(ADCDAT0, sizeof(*adcdat0));
clkcon = ioremap(CLKCON, sizeof(*clkcon));
*clkcon |= (1 << 15);
printk("clkcon = 0x%lx\n", *clkcon);
init_waitqueue_head(&wq);
INIT_WORK(&work, work_handle);
printk("adc_init ...\n");
return ret;
err_misc_register:
misc_deregister(&misc);
printk("adc misc_register faiadc\n");
return ret;
err_request_irq:
disable_irq(IRQ_ADC);
free_irq(IRQ_ADC, &arg);
return ret;
}
static void __exit adc_exit(void)
{
iounmap(clkcon);
iounmap(adcdat0);
iounmap(adccon);
disable_irq(IRQ_ADC);
free_irq(IRQ_ADC, &arg);
misc_deregister(&misc);
printk("adc_exit ###############################\n");
}
module_init(adc_init);
module_exit(adc_exit);
MODULE_LICENSE("GPL");