linux内核中断底半部

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)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值