中断底半部实现方法:
1. 软中断
2. tasklet
3. workqueue
解释
workqueue和tasklet是Linux内核中用于处理中断后续任务的两种机制,它们在中断处理流程中扮演着重要的角色。下面是对它们的详细解释:
Tasklet
定义与作用:
Tasklet是Linux内核中一种快速执行的单线程下半部(bottom half)处理机制。它的主要作用是在中断处理完成后,继续处理那些不应该在中断服务程序(ISR)中执行的时间消耗较大的任务。Tasklet是由softirq机制驱动的,能够在不同的CPU上并行执行,但它并不睡眠(即不会调用会导致当前线程睡眠的函数),这保证了其执行的高效性。
特点:
- 短小精悍:Tasklet的代码应当简短且高效,因为它不应该包含任何会显著
延长执行时间的操作。 - 可以被打断:与其他中断处理过程类似,Tasklet的执行过程也可能被新的中断打断。
- 执行高效:由于Tasklet的设计初衷是为了快速执行,它通常能够非常高效地处理那些需要在中断服务程序之外完成的任务。
Workqueue
定义与作用:
Workqueue是Linux内核中另一种用于执行耗时任务的机制。与Tasklet不同,Workqueue可以睡眠,即它可以在执行过程中 调用 可能导致 当前线程睡眠的函数。这使得Workqueue更加适合那些需要执行复杂或耗时操作的场景。
特点:
- 可以做耗时操作:由于Workqueue允许睡眠,因此它可以执行那些无法在中断服务程序或Tasklet中执行的耗时操作。
- 灵活的执行:Workqueue的工作项可以被添加到不同的工作队列中,并根据需要进行调度执行。这使得Workqueue在处理多种类型的耗时任务时非常灵活。
- 可以被硬中断打断:尽管Workqueue的执行可能会睡眠,但它仍然可以被硬中断打断。然而,在Workqueue中的任务被打断后,内核会负责在适当的时候重新调度这些任务继续执行。
为什么要加这个?
加入Tasklet和Workqueue这两种机制,主要是为了解决中断处理过程中的一个关键问题:如何在中断服务程序(ISR)中高效且安全地处理耗时任务。中断服务程序需要快速响应并尽可能快地完成处理,以避免影响系统的响应性。然而,在某些情况下,中断处理可能需要执行一些耗时较长的操作,这些操作如果直接在中断服务程序中执行,会显著增加中断的响应时间,并可能导致其他中断被延迟处理。通过引入Tasklet和Workqueue这两种机制,可以将耗时任务从中断服务程序中分离出来,并在系统的适当时候以异步的方式执行,从而在保证系统响应性的同时,完成这些必要的耗时操作。
新概念
中断上下文(代码):中断处理相关的 程序(中断服务程序、tasklet)
进程上下文:进程相关的程序(open、read、write)、workqueue ---->内核上的,不包括应用层的
一、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");
二、workqueue
#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");
三、platform平台总线
设备资源和驱动方法分开管理
device
driver
vi drivers/char/adc_device.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm-generic/errno-base.h>
#include <linux/platform_device.h>
#include <mach/irqs.h>
#define DEV_NAME "adc"
#define ADCCON 0x58000000
#define ADCDAT0 0x5800000C
#define CLKCON 0x4C00000C
#define IRQ_NUM IRQ_ADC
static struct resource res[] =
{
[0] =
{
.start = ADCCON,
.end = ADCCON + 4 - 1,
.name = "adccon",
.flags = IORESOURCE_IO
},
[1] =
{
.start = ADCDAT0,
.end = ADCDAT0 + 4 - 1,
.name = "adcdat0",
.flags = IORESOURCE_IO
},
[2] =
{
.start = CLKCON,
.end = CLKCON + 4 - 1,
.name = "clkcon",
.flags = IORESOURCE_IO
},
[3] =
{
.start = IRQ_NUM,
.end = IRQ_NUM,
.name = "irq_adc",
.flags = IORESOURCE_IRQ
}
};
static void release(struct device *dev){}
static struct platform_device dev =
{
.name = DEV_NAME,
.id = -1,
.dev =
{
.release = release
},
.num_resources = sizeof(res) / sizeof(res[0]),
.resource = res
};
static int __init adc_device_init(void)
{
int ret = platform_device_register(&dev);
if(ret < 0)
goto err_platform_register;
printk("platform_device_register ...\n");
return 0;
err_platform_register:
platform_device_unregister(&dev);
printk("platform_device_register failed ...\n");
return ret;
}
static void __exit adc_device_exit(void)
{
platform_device_unregister(&dev);
printk("platform_device_unregister ...\n");
}
module_init(adc_device_init);
module_exit(adc_device_exit);
MODULE_LICENSE("GPL");
vi drivers/char/adc_driver.c
#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/platform_device.h>
#define DEV_NAME "adc"
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 int arg = 100;
#define ADC_MAGIC_NUM 'x'
#define ADC_SET_CHANNEL 2
#define CMD_ADC_SET_CHANNEL _IOW(ADC_MAGIC_NUM, ADC_SET_CHANNEL, unsigned char)
irqreturn_t irq_handler(int num, void * arg)
{
printk("num = %d arg = %d\n", num, *(int *)arg);
condition = 1;
wake_up_interruptible(&wq);
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 probe(struct platform_device * pdev)
{
int ret = misc_register(&misc);
if(ret < 0)
goto err_misc_register;
ret = request_irq(pdev->resource[3].start, irq_handler, IRQF_DISABLED, "adc_irq", &arg);
if(ret < 0)
goto err_request_irq;
adccon = ioremap(pdev->resource[0].start, pdev->resource[0].end - pdev->resource[0].start + 1);
adcdat0 = ioremap(pdev->resource[1].start, pdev->resource[1].end - pdev->resource[1].start + 1);
clkcon = ioremap(pdev->resource[2].start, pdev->resource[2].end - pdev->resource[2].start + 1);
*clkcon |= (1 << 15);
init_waitqueue_head(&wq);
printk("adc_probe ...\n");
return ret;
err_misc_register:
misc_deregister(&misc);
printk("adc misc_register failed\n");
return ret;
err_request_irq:
disable_irq(pdev->resource[3].start);
free_irq(pdev->resource[3].start, &arg);
return ret;
}
static int remove(struct platform_device * pdev)
{
iounmap(clkcon);
iounmap(adcdat0);
iounmap(adccon);
disable_irq(pdev->resource[3].start);
free_irq(pdev->resource[3].start, &arg);
misc_deregister(&misc);
printk("adc_remove ...\n");
return 0;
}
static struct platform_driver dri =
{
.probe = probe,
.remove = remove,
.driver =
{
.name = DEV_NAME
}
};
static int __init adc_driver_init(void)
{
int ret = platform_driver_register(&dri);
if(ret < 0)
goto err_platform_register;
printk("adc platform_driver_register ...\n");
return 0;
err_platform_register:
platform_driver_unregister(&dri);
printk("adc platform_driver_register failed\n");
return ret;
}
static void __exit adc_driver_exit(void)
{
platform_driver_unregister(&dri);
printk("adc platform_driver_unregister ...\n");
}
module_init(adc_driver_init);
module_exit(adc_driver_exit);
MODULE_LICENSE("GPL");
应用层adc_app.c
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#define ADC_MAGIC_NUM 'x'
#define ADC_SET_CHANNEL 2
#define CMD_ADC_SET_CHANNEL _IOW(ADC_MAGIC_NUM,ADC_SET_CHANNEL,unsigned char)
int main(int argc, const char *argv[])
{
int fd = open("/dev/adc",O_RDWR);
if(fd < 0)
{
perror("open fail");
return -1;
}
unsigned char arg = 0;
unsigned short value = 0;
while(1)
{
arg = 0;
int ret = ioctl(fd,CMD_ADC_SET_CHANNEL,&arg);
printf("ioctl ret = %d\n",ret);
ret = read(fd,&value,sizeof(value));
float v = 3.3 * value / 1024.0;
printf("ret= %d value = %d v = %.3f\n",ret ,value,v);
sleep(2);
arg = 1;
ret = ioctl(fd,CMD_ADC_SET_CHANNEL,&arg);
printf("ioctl ret = %d\n",ret);
ret = read(fd,&value,sizeof(value));
v = 3.3 * value / 1024.0;
printf("ret= %d value = %d v = %.3f\n",ret ,value,v);
sleep(2);
}
close(fd);
return 0;
}