驱动——linux——中断处理

我们已经学习了中断,但当有更高优先级的中断处理时,会有中断嵌套的情况发送.ARM架构中使用中断的上下部处理机制,而是为了优化中断处理的效率和系统的响应性。将两种情况相结合。

一、中断上下部

  • 将中断处理过程分解为上半部和下半部。
  • 上半部通常执行紧急的、必须快速完成的操作,如保存中断上下文、标记中断发生等。
  • 下半部则执行可以稍后处理的、耗时较长的操作,如设备I/O操作、数据处理等。
  • 上下部处理机制旨在提高中断处理的效率和系统响应性,通过减少中断处理时间,避免长时间占用处理器资源

二、实现方法:

(1)硬件层面的支持

  1. 中断控制器:现代处理器通常配备有中断控制器(如ARM的GIC中断控制器),这些控制器能够识别和处理来自不同外设的中断请求。中断控制器通常具有优先级管理功能,能够确保高优先级的中断能够打断低优先级的中断处理过程。

  2. 中断屏蔽寄存器:处理器和中断控制器都包含中断屏蔽寄存器,用于控制哪些中断被允许或禁止。在中断处理过程中,可以通过修改这些寄存器的值来暂时禁止或允许中断,以避免中断嵌套导致的复杂性。

(2)、软件层面的实现

  1. 中断向量表:在操作系统的启动阶段,会设置一个中断向量表,该表包含了不同中断号对应的中断服务例程(ISR)的入口地址。当中断发生时,处理器会根据中断号跳转到对应的中断服务例程执行。

  2. 上半部处理

    • 紧急任务处理:在中断服务例程的开始部分,执行那些必须立即完成的紧急任务,如保存现场(即保存当前处理器的状态)、更新状态寄存器等。
    • 标记下半部:在紧急任务处理完成后,标记需要在下半部执行的任务或操作。这可以通过设置特定的标志位、将任务放入队列或触发特定的软中断等方式实现。
  3. 下半部处理

    • 软中断(softirq):在Linux内核中,下半部处理通常通过软中断实现。当上半部处理完成后,会触发一个软中断,该软中断的处理函数将在稍后(通常在系统调用返回或定时器中断发生时)被调用,以执行那些可以延迟的任务。
    • tasklet和工作队列:除了软中断外,Linux内核还提供了tasklet和工作队列两种机制来实现下半部处理。tasklet是一种基于软中断的轻量级底半部处理机制,而工作队列则允许将任务推迟到内核线程中执行,具有更高的灵活性和可预测性。
  4. 恢复现场和继续执行:在下半部处理完成后,会恢复之前保存的现场(即恢复处理器的状态),并继续执行被中断的代码或任务。 

三、相关函数

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");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值