精华内容
下载资源
问答
  • 中断下半部之工作队列

    千次阅读 2016-09-24 16:03:49
    一、中断的顶半部和底半部  设备的中断会打断内核中进程的正常调度和运行,而系统对更高吞吐率的追求势必要求中断服务程序尽可能地短小精悍。但是,这个良好的愿望往往与现实并不吻合。在大多数真实的系统中,当...
    一、中断的顶半部和底半部
    
        设备的中断会打断内核中进程的正常调度和运行,而系统对更高吞吐率的追求势必要求中断服务程序尽可能地短小精悍。但是,这个良好的愿望往往与现实并不吻合。在大多数真实的系统中,当中断到来时,要完成的工作往往并不会是短小的,它可能要进行较大量的耗时处理,由于中断的优先级最高,这时候就会影响到其他进程的实时性。
        为了在中断执行时间尽可能短和中断处理需完成大量工作之间找到一个平衡点,Linux 将中断处理程序分解为两个半部:顶半部(top  half)和底半部(bottom half)。
        顶半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行“登记中断”的工作,“登记中断”意味着将底半部处理程序挂到该设备的下半部执行队列中去。这样,顶半部执行的速度就会很快,可以服务更多的中断请求。
        现在,中断处理工作的重心就落在了底半部的头上,它来完成中断事件的绝大多数任务。底半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是底半部和顶半部的最大不同。
        尽管顶半部、底半部的结合能够改善系统的响应能力,但是,僵化地认为 Linux设备驱动中的中断处理一定要分两个半部则是不对的。如果中断要处理的工作本身很少,则完全可以直接在顶半部全部完成。
        
    二、中断底半部的实现机制
        Linux 系统实现底半部的机制主要有tasklet,工作队列和软中断。tasklet 是基于软中断实现的(内核定时器也依靠软中断实现),一般都使用tasklet或者工作队列来实现中断下半部。
        通常,在工作队列和软中断/tasklet中作出选择非常容易。可使用以下规则:
        (1)如果推后执行的任务需要在一个tick(1/HZ,即每隔多久发生一次时钟中断,这个‘多久’便是一个tick)之内处理,则使用软中断或tasklet,因为其可以抢占普通进程和内核线程。
        (2)如果推后执行的任务需要睡眠,那么只能选择工作队列;
        (3)如果推后执行的任务需要延时指定的时间再触发,那么使用工作队列,因为其可以利用timer延时,当然使用内核定时器也可以。
        (4)如果推后执行的任务对延迟的时间没有任何要求,此时通常为无关紧要的任务,那么使用工作队列;
        (5)如果你需要用一个可以重新调度的实体来执行你的下半部处理,你应该使用工作队列。它是惟一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在你需要获得大量的内存时、在你需要获取信号量时,在你需要执行阻塞式的I/O操作时,它都会非常有用。
        
    三、工作队列详解
        (1)工作队列概述
            工作队列可以把工作推后,交由一个内核线程去执行,这个下半部分总是会在进程上下文执行,但由于是内核线程,其不能访问用户空间。最重要的特点是工作队列允许重新调度甚至是睡眠。
            实际上,工作队列的本质就是将工作交给内核线程events处理,因此其可以用内核线程替换。但是内核线程的创建和销毁对编程者的要求较高,而工作队列实现了内核线程的封装,不易出错,所以我们也推荐使用工作队列。
            我们把推后执行的任务叫做工作(work),描述它的数据结构为work_struct。这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct。而工作线程就是负责执行工作队列中的工作。系统默认的工作者线程为events,当然,自己也可以创建自己的工作者线程。这些work_struct结构被连接成链表,当一个工作线程被唤醒时,它会执行它的链表上的所有工作。工作被执行完毕,它就将相应的work_struct对象从链表上移去。当链表上不再有对象的时候,工作线程就会继续休眠。其结构如下图所示。

        (2)工作队列的使用
            工作队列是2.6内核开始引入的机制,在2.6.20之后,工作队列的数据结构发生了一些变化,本文只对2.6.20之后的版本进行介绍。

             a、静态地创建一个名为n,待执行函数为f的work_struct结构。
            DECLARE_WORK(n, f)

            #ifdef CONFIG_LOCKDEP
            /*
             * NB: because we have to copy the lockdep_map, setting _key
             * here is required, otherwise it could get initialised to the
             * copy of the lockdep_map!
             */
            #define __WORK_INIT_LOCKDEP_MAP(n, k) \
                .lockdep_map = STATIC_LOCKDEP_MAP_INIT(n, k),
            #else
            #define __WORK_INIT_LOCKDEP_MAP(n, k)
            #endif

            #define __WORK_INITIALIZER(n, f) {                    \
                .data = WORK_DATA_STATIC_INIT(),                \
                .entry    = { &(n).entry, &(n).entry },                \
                .func = (f),                            \
                __WORK_INIT_LOCKDEP_MAP(#n, &(n))                \
                }

            #define __DELAYED_WORK_INITIALIZER(n, f, tflags) {            \
                .work = __WORK_INITIALIZER((n).work, (f)),            \
                .timer = __TIMER_INITIALIZER(delayed_work_timer_fn,        \
                                 0, (unsigned long)&(n),        \
                                 (tflags) | TIMER_IRQSAFE),        \
                }

            #define DECLARE_WORK(n, f)                        \
                struct work_struct n = __WORK_INITIALIZER(n, f)

            b、动态初始化一个work_struct结构。
            INIT_WORK(_work, _func)

            #define PREPARE_WORK(_work, _func)                \
                do {                            \
                    (_work)->func = (_func);            \
                } while (0)

            #define __INIT_WORK(_work, _func, _onstack)                \
                do {                                \
                    __init_work((_work), _onstack);                \
                    (_work)->data = (atomic_long_t) WORK_DATA_INIT();    \
                    INIT_LIST_HEAD(&(_work)->entry);            \
                    PREPARE_WORK((_work), (_func));                \
                } while (0)
            #endif

            #define INIT_WORK(_work, _func)                    \
                do {                            \
                    __INIT_WORK((_work), (_func), 0);        \
                } while (0)

            c、使用内核缺省工作者线程的API
            int schedule_work(struct work_struct *work);
            work马上就会被调度,一旦其所在的处理器上的内核工作线程被唤醒,这个work就会被执行。

            int schedule_delayed_work(struct work_struct *work, unsigned long delay);
            work指向的work_struct直到delay指定的时钟节拍用完以后才会执行。

            void flush_scheduled_work(void);
            该函数的作用,是为了防止有竞争条件的出现。一般情况下cancel_delayed_work之后您都得调用flush_scheduled_work()这个函数,特别是对于内核模块。

            int cancel_delayed_work(struct work_struct *work);
            取消相应的delayed_work。直接返回。
            返回非 0:内核会确保不会初始化给定入口项的执行,即终止该工作。
            返回0:则说明该工作已经在其他处理器上运行
            因此在cancel_delayed_work返回后可能仍在运行,怎么办?

            int cancel_work_sync(struct work_struct *work);
            取消相应的delayed_work。但是,如果这个delayed_work已经在运行,那么,cancel_delayed_work_sync会阻塞,直到delayed_work完成并取消相应的delayed_work。

            d、自己创建工作者线程API
            struct workqueue_struct *create_workqueue(const char *name);
            创建一个名字为name的工作队列,为系统每个cpu都分配一个内核线程。

            int queue_work(struct workqueue_struct *wq, struct work_struct *work);
            类似于schedule_work,区别在于queue_work把给定工作提交给创建的工作队列wq而不是缺省队列。

            int queue_work_on(int cpu, struct workqueue_struct *wq, struct work_struct *work)
            与queue_work的区别是多了一个cpu参数,即指定在哪个CPU上运行。

            int queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *work, unsigned long delay);
            类似于schedule_delayed_work,区别在于queue_delay_work把给定工作提交给创建的工作队列wq而不是缺省队列。

            int queue_delayed_work_on(int cpu, struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay)
            与queue_delayed_work的区别是多了一个cpu参数,即指定在哪个CPU上运行。

            void flush_workqueue(struct workqueue_struct *wq);
            刷新调度执行指定工作队列里的所有work。并阻塞直到所有work执行完毕后,返回退出。若所有work都已经执行完毕,则直接返回。实际上,它只是等待(睡眠),直到缺省工作队列上的工作被执行。

            void destroy_workqueue(struct workqueue_struct *wq);

            释放创建的工作队列。


    四、例子(使用工作队列来对按键进行消抖)

            button.c文件如下:

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/types.h>
    #include <linux/fcntl.h>
    #include <linux/mm.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/errno.h>
    #include <linux/init.h>
    #include <linux/device.h>
    #include <linux/init.h>
    #include <linux/major.h>
    #include <linux/delay.h>
    #include <linux/io.h>
    #include <asm/uaccess.h>
    #include <linux/poll.h>
    #include <linux/irq.h>
    #include <asm/irq.h>
    #include <linux/interrupt.h>
    #include <asm/uaccess.h>
    #include <linux/platform_device.h>
    #include <linux/cdev.h>
    #include <linux/miscdevice.h>
    #include <linux/sched.h>
    #include <linux/gpio.h>
    #include <asm/gpio.h>
    
    #define BUTTON_NAME "poll_button"
    #define BUTTON_GPIO 140
    
    static int button_major = 0;     					     
    static int button_minor = 0;
    static struct cdev button_cdev;                               
    static struct class *p_button_class = NULL;			
    static struct device *p_button_device = NULL;	
    
    static struct work_struct button_wq;
    static volatile int ev_press = 0;
    static volatile char key_value[] = {0};
    static volatile int flag_interrupt = 1;
    static int old_value;
    static int Button_Irq = 0;
    
    static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
    
    static irqreturn_t buttons_interrupt(int irq, void *dev_id)
    {
    	if(flag_interrupt) {
    		flag_interrupt = 0;
    		old_value = gpio_get_value(BUTTON_GPIO);
    		schedule_work(&button_wq);
    	}
    	
    	return IRQ_RETVAL(IRQ_HANDLED);
    }
    
    static void buttonwq_callback(struct work_struct *work)
    {
        msleep(10);        //消抖时间10ms
        
        key_value[0] = gpio_get_value(BUTTON_GPIO);
        if(key_value[0] == old_value) {
            printk("button pressed! key_value[0] = %d\n", key_value[0]);
            ev_press= 1;
            wake_up_interruptible(&button_waitq);
        } 
        
        flag_interrupt = 1;
        
        return;
    }
    
    static int button_irqcfg(void)
    {
        Button_Irq = gpio_to_irq(BUTTON_GPIO);
        enable_irq(Button_Irq);
    
    	if(request_irq(Button_Irq, buttons_interrupt, IRQF_TRIGGER_FALLING, "BUTTON_IRQ", NULL) != 0) {
    		printk("request irq failed !!! \n");
    	    disable_irq(Button_Irq);
    		free_irq(Button_Irq, NULL);
    		return -EBUSY;
    	}
    	    
        return 0;
    }
    
    static int button_open(struct inode *inode,struct file *file)
    {
    	//button_irqcfg();
    	return 0;
    }
    
    static int button_close(struct inode *inode, struct file *file)
    {
    	//free_irq(Button_Irq, NULL);
    	return 0;
    }
    
    
    static int button_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
    {
    	unsigned long err;
    
        if (filp->f_flags & O_NONBLOCK) {        
            /*nothing to do*/
            //如果应用程序设置了非阻塞O_NONBLOCK,那么驱动这里就不使用等待队列进行等待。
        } else {
            wait_event_interruptible(button_waitq, ev_press);
        }
        
    	err = copy_to_user(buff, (const void *)key_value, min(sizeof(key_value), count));
    	key_value[0] = 0;
    	ev_press = 0;
    	
    	return err ? -EFAULT : min(sizeof(key_value), count);	
    }
    
    static const struct file_operations button_fops = {
    	.owner = THIS_MODULE,
    	.open = button_open,
    	.release = button_close,
    	.read = button_read,
    	//.poll = button_poll,
    	//.write = button_write,
    	//.ioctl = button_ioctl
    };
    
    static int button_setup_cdev(struct cdev *cdev, dev_t devno)
    {
    	int ret = 0;
    
    	cdev_init(cdev, &button_fops);
    	cdev->owner = THIS_MODULE;
    	ret = cdev_add(cdev, devno, 1);
    
    	return ret;
    }
    
    static int __init button_init(void)
    {
    	int ret;
    	dev_t devno;
    	
    	printk("button driver init...\n");
    
        INIT_WORK(&button_wq, buttonwq_callback);	
        button_irqcfg();
            	
    	if(button_major) {
    		devno = MKDEV(button_major, button_minor);
    		ret = register_chrdev_region(devno, 1, BUTTON_NAME);
    	} else {
    		ret = alloc_chrdev_region(&devno, button_minor, 1, BUTTON_NAME);
    		button_major = MAJOR(devno);		
    	}
    	
    	if(ret < 0) {
    		printk("get button major failed\n");
    		return ret;
    	}
    
    	ret = button_setup_cdev(&button_cdev, devno);
    	if(ret) {
    		printk("button setup cdev failed, ret = %d\n",ret);
    		goto cdev_add_fail;
    	}
    
    	p_button_class = class_create(THIS_MODULE, BUTTON_NAME);
    	ret = IS_ERR(p_button_class);
    	if(ret) {
    		printk(KERN_WARNING "button class create failed\n");
    		goto class_create_fail;
    	}
    	p_button_device = device_create(p_button_class, NULL, devno, NULL, BUTTON_NAME);
    	ret = IS_ERR(p_button_device);
    	if (ret) {
    		printk(KERN_WARNING "button device create failed, error code %ld", PTR_ERR(p_button_device));
    		goto device_create_fail;
    	}
    
    	return 0;
    	
    device_create_fail:
    	class_destroy(p_button_class);
    class_create_fail:
    	cdev_del(&button_cdev);
    cdev_add_fail:
    	unregister_chrdev_region(devno, 1);
    	return ret;
    }
    
    static void __exit button_exit(void)
    {
    	dev_t devno;
    
    	printk("button driver exit...\n");
    		
    	devno = MKDEV(button_major, button_minor);	
    	device_destroy(p_button_class, devno);
    	class_destroy(p_button_class);
    	cdev_del(&button_cdev);
    	unregister_chrdev_region(devno, 1);
    	free_irq(Button_Irq, NULL);
    }
    
    module_init(button_init);
    module_exit(button_exit);
    
    MODULE_AUTHOR("Jimmy");
    MODULE_DESCRIPTION("button Driver");
    MODULE_LICENSE("GPL");
    

            Makefile文件如下:

    ifneq ($(KERNELRELEASE),)
    obj-m := button.o
    else
    KERNELDIR ?= /ljm/git_imx6/linux-fsl/src/linux-3-14-28-r0
    TARGET_CROSS = arm-none-linux-gnueabi-
    
    PWD := $(shell pwd)
    
    default:
    	$(MAKE) ARCH=arm CROSS_COMPILE=$(TARGET_CROSS) -C $(KERNELDIR) M=$(PWD) modules
    
    endif
    
    install:
    	$(MAKE) ARCH=arm CROSS_COMPILE=$(TARGET_CROSS) -C $(KERNELDIR) M=$(PWD) modules_install
    
    clean:
    	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.symvers *.order

    展开全文
  • 5-1 Linux内核中断机制  中断处理流程  中断源   设备驱动中中断处理例程的实现。 内核中实现计时、延时操作的函数。 1、  什么是中断? 2、  中断的分类: 2.1按中断源分类:内部中断、外部中断 2.2按中断是否...

    5-1 Linux内核中断机制

    中断处理流程

    中断源

     

    设备驱动中中断处理例程的实现。

    内核中实现计时、延时操作的函数。

    1、  什么是中断?

    2、  中断的分类:

    2.1按中断源分类:内部中断、外部中断

    2.2按中断是否可屏蔽分类:可屏蔽中断、不可屏蔽中断(NMI

    2.3按中断入口跳转方法的不同分类:向量中断、非向量中断。

    3、申请和释放IRQ

    int request_irq(unsigned int irq, //要申请的中断号。

                 Irqreturn_t (*handler)(int,void*,struct pt_regs*),//要安装的中断处理函数的指针

                 Unsigned long flags,  //填写中断类型

                 Const char *dev_name,

                 Void *dev_id); //用于共享的中断数据线。它是用来唯一的标识设备。

    void free_irq(unsigned int irq,void *dev_id);//释放,

    注:flags:  0:普通外部中断   SA_INTERRUPT:快速中断   SA_SHIRQ:共享中断

     

    申请和释放实例:

    /*中断处理函数*/

    irqreturn_t xxx_interrupt(int irq,void* dev_id,struct pt_regs *regs)

    {

    ……

    中断的具体的内容

    ……
    }

     

    /*设备启动模块的加载函数*/

    int __init xxx_init(void)

    {

     

     /*申请中断*/

     result=request_ir(xxx_irq,xxx_interrupt,SA_INTERRUPT,”xxx”,NULL);//xxx_irq取多少要根据芯片手册来决定。

    }

     

    /*设备驱动模块卸载函数*/

    void __exit xxx_exit(void)

    {

    ……

    /*释放中断*/

    free_irp(xxx_irq,NULL);

    }

    4、  Linux中断处理流程

    产生中断

    ->跳转到中断向量表入口地址(一般放在高位,在arch/arm/kernel/entry-armv.S汇编级的工作,主要保存上下文状态)

    ->asm_do_IRQ(中断处理公共段在arch/arm/kernel/irq.c根据irq编号从已申请的中断(通过request_irq)找到irq编号对应的中断处理函数)

    ->执行对应的中断处理函数 (自己编写的处理函数)

    ->返回到asm_do_IRQ()  

    ->返回到entry-armv.S 恢复到中断前的上下文状态,结束中断处理,继续执行中断发生前的程序)

    5、使能和屏蔽中断

      5.1禁止/使能单个中断

          void disable_irq(int irq);//等待目前的中断处理完成,禁用该IRQ

          void disable_irq_nosync(int irq);//禁用并立即返回

          void enable_irq(int irq);

      5.2禁止/使能所有的中断

          void local_irq_disable(void);//所有的中断都禁用

          void local_irq_enable(void); //允许所有中断

          

          void local_irq_save(unsigned long flags);//把中断状态保存到flags中,禁用所有中断。

          void local_irq_restore(unsigned long flags);//把中断状态flags恢复,允许所有中断。

    6、  为什么将中断处理程序分成顶半部和低半部。

    6.1需求:

       中断处理要求尽快结束,而不能使中断阻塞的时间过长。而有些处理例程要完成耗时的任务。

    6.2:解决方案:

    顶半部:让中断尽可能的短。

    低半部:完成耗时的任务。

    7、  顶半部:是实际的中断例程,用request_irq注册的中断例程,它在很短的时间内完成。

    8、  低半部:被顶半部调度,并在稍后更安全的时间执行的例程。

    9、  实现低半部的机制 1.tasklet(任队列) 2.Work queue(工作队列)

    9.1  tasklet(任队列): 速度快,优先选择。原子操作,运行于中断上下文。

    9.2 Work queue(工作队列):高延时,运行休眠,运行于上下文

    10tasklet的使用

    10.1声明tasklet

        DECLARE_TASKLET(name,function,data);//低半部执行的函数

        name:tasklet的名字

        funciton:执行tasklet时调用的函数(低半部的函数入口地址)

        data:function函数的参数)一个用来传递给tasklet函数的unsigned long 类型的值。

        如:DECLARE_TASKLECT(xxx_tasklet,xxx_do_tasklet,0);

    10.2调度tasklet

    void tasklet_schedule(struct tasklet_struct *t)//执行时,它不会直接直接调用function,而是调用tasklet_schedule()函数。然后它自己会间接地调用function()函数。

    11tasklet使用模板。

    /*定义tasklet和低半部函数并关联*/

    void xxx_do_tasklet(unsigned long);//低半部函数的声明

    DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0);//定义一个xxx_tasklet对象,xxx_do_tasklet是一个函数。

     

    /*中断处理低半部*/

    void xxx_do_taskle(unsigned long)

    {

    ……

    }

     

    /*中断处理顶半部*/

    irqreturn_t  xxx_interrupt(int irq,void *dev_id,struct pt_regs* reg)

    {

    ……

    tasklet_schedule(&xxx_tasklet);
    }

     

    12Workqueue工作队列

    12.1创建新的工作队列

    struct  workqueue_struct*  create_workqueue(const char* name); //一个工作队列可对应多个“内核线程”

    struct  workqueue_struct*  create_singlethread_workqueue(const char* name);//一个工作队列对应单个线程。

    12.2 向工作队列提交任务,首先填充一个work_struct结构(work_stuct是干什么用的)。

    DECLARE_WORK(name, void(*function)(void*), void *data);

    INIT_WORK(struct  work_struct* work, void(* function)(void *),  void* data); //要填充work的,并与低半部函数function关联起来。

    PREPARE_WORK(struct work_struct* work,  void(* function)(void* ),  void* data);//不会初始化用来将work_struct结构连接到工作队列的指针,一般适用于任务已经提交,只是修改了任务时使用PREPARE_WORK.

    12.3、提交任务

    int queue_work(struct  workqueue_struct* queue,struct  work_sturct* work );

    int queue_delayed_work(struct workqueue_struct* queue,struct work_struct *work,unsigned      long delay);//实际的工作至少会在经过指定的jiffies(delay指定)之后才会被执行

    12.4、取消某个队列的入口项

    int cancel_delayed_work(struct work_struct *work);

    // 返回!=0:内核会确保不会初始化给定入口项的执行。

    // 返回==0:则说明该入口项已经在其他的处理器上运行(工作队列可用于多处理器,而tasklet只用于单处理器),因此在cancel_delayed_work返回后可能人在运行,怎么办?使用下面那个函数

    需要强制刷新工作队列:void flush_workqueue(struct workqueue_struct* queue);

    12.5、销毁工作队列

    void destroy_workqueue(struct workqueue_struct *queue);

     

    上面的工作队列会比较繁杂。(下面有简单的)

     

    13、共享工作队列

    13.1在许多情况下,驱动不需要有自己的工作队列,只是偶然地向工作队列添加任务。

    13.2使用内核提供的共享的默认工作队列

    13.3不应该长期独占该队列,即不能长时间休眠。

    13.4我们的任务可能需要更长的时间延时才能获得处理器时间。

    14、使用共享队列

    14.1初始化

    INIT_WORK(struct work_stuct* work,void (*function)(void),void *data);//自定义一个任务结构体对象。也是要填充work的,并与低半部函数function关联起来。

    14.2 调度

    int schedule_work(struct work_struct *work);//立即调用

    int schedule_delayed_work(struct work_struct *work,unsigned long delay);//延时调用

    14.3取消共享工作队列的一个入口项(即一个工作任务work

    int cancel_delay_work(struct work_sruct *work);

    14.4刷新共享工作队列

    void flush_scheduled_work(void);

     

    14.5共享队列的实例:

    Work queue的使用模板

    /*定义工作队列和关联函数*/

    struct work_struct  xxx_wq;//要使用共享工作队列,首先要定义一个工作任务结构对象。

    void  xxx_do_work(unsigned long);//低半部函数的声明

    /*中断处理低半部*/

    void  xxx_do_work(unsigned long)

    {

    }

     

    /*中断处理顶半部*/

    irqreturn_t  xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)

    {

       

       shedule_work(&xxx_wq);//调用shedule_work()函数实现顶半部跳转到底半部函数。

       

    }

    /*设备驱动模块加载函数*/

    int__init  xxx_init(void)

    {

       ……

       /*申请中断,当然申请的顶半部的函数*/

       result=request_irq(xxx_irq,xxx_interrupt,SA_INTERRUPT,”xxx”,NULL);//顶半部函数

      /*初始化工作队列*/

      INIT_WORK(&xxx_wq,(void (*)(void *)) xxx_do_work,NULL);//任务结构,低半部函数

       ……

    }

     

    15、中断共享

    15.1 Linux中断共享:多个设备使用同一个中断线号,同一个中断设备线号的所有处理程序链接成一个链表。

    15.2 共享中断的多个设备在申请中断时都应使用SA_SHIRQ标志。

    15.3、设备结构指针可以作为request_irq(…,void *dev_id)的最好一个参数dev_id传入,dev_id这个参数必须是唯一的,用来标志一个唯一的设备

    15.4、在中断到来时,对应链表的所有共享该中断的中断处理程序都被执行,他们会检查dev_id参数信息,并根据硬件中断寄存器中的信息判断是否本设备的中断,如果不是,应迅速返回,如果是,则处理完成,如果链表中没有一个是,则说明出现错误。

    15.5、中断共享模板

    /*设备驱动模块加载函数*/

    int  xxx_init(void)

    {

    ……

    /*申请共享中断*/

    result=request_irq(sh_irq,xxx_interrupt,SA_SHIRQ,”xxx”,xxx_dev);

    ……
    }

     

    /*设备驱动模块卸载函数*/

    void __exit xxx_exit(void)

    {

     

      /*释放中断*/

      free_irq(xxx_irq,xxx_dev);

    }

     

    /*中断处理顶部*/

    irqreturn_t  xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)

    {

     

      int status=read_int_status();//获取中断源

      if(!is_myint(dev_int,status))//判断是否本设备中断

      {

         return IRQ_NONE;//通知内核该中断不需要自己处理

    }

    return IRQ_HANDLED; //通知内核处理该中断

    }

     

    声明:本文非原创,整理自申嵌
    展开全文
  • 中断处理的工作队列机制

    千次阅读 2016-04-08 11:44:16
    工作队列(work queue)
    工作队列(work queue)是另外一种将工作推后执行的形式 ,它和我们前面讨论的所有其他形式都有不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。
    
    那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。
    
    
    
    1.      工作、工作队列和工作者线程
    
    如前所述,我们把推后执行的任务叫做工作(work),描述它的数据结构为work_struct,这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct,而工作线程就是负责执行工作队列中的工作。系统默认的工作者线程为events,自己也可以创建自己的工作者线程。
    
    2.      表示工作的数据结构
    
       工作用<linux/workqueue.h>中定义的work_struct结构表示:
    
    struct  work_struct{
    
        unsigned long pending;          /* 这个工作正在等待处理吗?*/
    
        struct list_head entry;         /* 连接所有工作的链表 */ 
    
        void (*func) (void *);          /* 要执行的函数 */
    
        void *data;                     /* 传递给函数的参数 */
    
        void *wq_data;                  /* 内部使用 */
    
        struct timer_list timer;        /* 延迟的工作队列所用到的定时器 */
    
    };
    
    这些结构被连接成链表。当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。工作被执行完毕,它就将相应的work_struct对象从链表上移去。当链表上不再有对象的时候,它就会继续休眠。
    
    3. 创建推后的工作
    
    要使用工作队列,首先要做的是创建一些需要推后完成的工作。可以通过DECLARE_WORK在编译时静态地建该结构:
    
    DECLARE_WORK(name, void (*func) (void *), void *data);
    
    这样就会静态地创建一个名为name,待执行函数为func,参数为data的work_struct结构。
    
    同样,也可以在运行时通过指针创建一个工作:
    
    INIT_WORK(struct work_struct *work, woid(*func) (void *), void *data);
    
    这会动态地初始化一个由work指向的工作。
    
    4. 工作队列中待执行的函数
    
    工作队列待执行的函数原型是:
    
    void work_handler(void *data)
    
    这个函数会由一个工作者线程执行,因此,函数会运行在进程上下文中。默认情况下,允许响应中断,并且不持有任何锁。如果需要,函数可以睡眠。需要注意的是,尽管该函数运行在进程上下文中,但它不能访问用户空间,因为内核线程在用户空间没有相关的内存映射。通常在系统调用发生时,内核会代表用户空间的进程运行,此时它才能访问用户空间,也只有在此时它才会映射用户空间的内存。
    
    5. 对工作进行调度
    
    现在工作已经被创建,我们可以调度它了。想要把给定工作的待处理函数提交给缺省的events工作线程,只需调用
    
    schedule_work(&work);
    
    work马上就会被调度,一旦其所在的处理器上的工作者线程被唤醒,它就会被执行。
    
    有时候并不希望工作马上就被执行,而是希望它经过一段延迟以后再执行。在这种情况下,可以调度它在指定的时间执行:
    
    schedule_delayed_work(&work, delay);
    
    这时,&work指向的work_struct直到delay指定的时钟节拍用完以后才会执行。
    
    6. 工作队列的简单应用
    
     #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/workqueue.h>
    
    static struct workqueue_struct *queue = NULL;
    static struct work_struct work;
    
    static void work_handler(struct work_struct *data)
    {
            printk(KERN_ALERT “work handler function.\n”);
    }
    
    static int __init test_init(void)
    {
            queue = create_singlethread_workqueue(“helloworld”); /*创建一个单线程的工作队列*/
            if (!queue)
                    goto err;
    
            INIT_WORK(&work, work_handler);
            schedule_work(&work);
    
            return 0;
    err:
            return1;
    }
    
    static void __exit test_exit(void)
    {
            destroy_workqueue(queue);
    }
    MODULE_LICENSE(“GPL”);
    module_init(test_init);
    module_exit(test_exit);
    展开全文
  • 中断 和工作队列的应用

    千次阅读 2014-03-05 10:12:15
    设备需要与外部世界打交道,如旋转的磁盘,绕卷的磁带,远距离连接的电缆等。这些设备的许多工作通常是在与处理器完全不同的时间周期内完成的,并且总是要比处理器慢。这种让处理器等待外部事件的情况总是不能...

    转自http://my.oschina.net/fzliu/blog/33028

    尽管有些设备仅通过它们的I/O寄存器就可以得到控制,但现实中的大部分设备却比这复杂一些。设备需要与外部世界打交道,如旋转的磁盘,绕卷的磁带,远距离连接的电缆等。这些设备的许多工作通常是在与处理器完全不同的时间周期内完成的,并且总是要比处理器慢。这种让处理器等待外部事件的情况总是不能令人满意,所以必须有一种方法可以让设备在产生某个事件时通知处理器,这种方法就是中断。在大多数情况下,一个驱动程序只需要为它自己设备的中断注册一个处理例程,并在中断到达时进行正确处理。从本质上讲,中断处理例程和其它代码并发运行,对并发控制技术的透彻理解对处理中断来讲非常重要——引自linux device driver。

    安装中断处理例程:

    int request_irq(unsigned int irq, irq_handler_t handler,  unsigned long irqflags, const char *devname, void *dev_id)

    释放中断号:

    void free_irq(unsigned int irq, void *dev_id)

    irq为中断号使用cat /proc/interrupts 可查看当前已经使用的中断号。 handler中断处理函数,irqflags设置中断方式,如上升沿,下降沿,低电平触发等,devname为中断的名字(随意)。dev_id 用于共享的中断信号线,必须唯一,可以用它来指向驱动程序的私有数据区(用来识别哪个设备产生中断),可以设置为NULL。

    其中,irqflags定义于<linux/irq.h>,irq定义于 arch/arm/mach-s3c2410/include/mach/irqs.h(对于s3c2410来说)。 

    中断处理例程可在驱动程序初始化时或者第一次打开设备时进行安装,但为断信号线的数量是非常有限的,如果一个模块在初始化时请求了IRQ,那么即使驱动程序只是占用它而从未使用,也将阻止其它驱动使用该中断,而在打开设备时申请中断,则可以其享这些有限的中断资源。

    调用request_irq的正确位置应该是在设备第一次打开,硬件被告知产生中断之前。调用free_irq的位置是最后一次关闭设备,硬件被告知不用再中断处理器之后。

    中断处理例程的实现:

    static irqreturn_t key_interrupt(int irq, void *dev_id,sturct pt_regs *regs);通常使用下面的方式,只传入两个参数。

    static irqreturn_t key_interrupt(int irq, void *dev_id);

    中断处理例程应该返回一个值,用来指明是否真正处理了一个中断,如果处理例程确实发现其设备的确处理,应该返回IRQ_HANDLED;否则返回IRQ_NONE。可使用宏IRQ_RETVAL(handle)来产生这个返回值。

    中断处理例程是在中断时间内运行的,它的行为会受到一些限制。处理例程不能向用户空间发送或者接收数据,因为它不是任何进程的上下文中执行的,处理例程不能做任何可能发生休眠的操作,例如调用wait_event,使用不带GFP_ATOMIC标志的内存分配操作,或者锁住一个信号量等等,不能调用schdule函数。

    中断处理例程的一个典型任务是:如果中断通知进程所等待的事件已发生,就会唤醒在该设备上休眠的进程。

    中断处理例程应该尽可能的短,执行一个长时间的任务时最好的方法是使用底半部机制。

    Linux内核中断机制:为了在中断执行时间尽可能短和中断处理需要完成大量工作之间找到一个平衡点,Linux将中断处理程序分解为两个半部,顶半部和底半部。

    顶半部完成尽可能少的比较紧急的任务,它往往只是简单地读取寄存器中的中断状态并清除中断标志位就进行“登记工作”,将底半部处理程序挂到该设备的底半部执行队列中去。

    Linux实现下半部的机制主要有tasklet和工作队列。

    一:tasklet

    记住tasklet是一个可以在由系统决定的安全时刻在软件中断上下文被调度运行的特殊函数,它们可以被多次调度运行,但tasklet不会积累,也就是说,实际只会运行一次。不会有同一个tasklet的多个实例并行的运行,因为它们只运行一次,但是tasklet可以与其它的tasklet并行的运行在对称多处理器(SMP)系统上。如果驱动程序中有多个tasklet,它们必须使用某种机制锁来避免冲突。tasklet在中断处理例程结束前不会开始运行,tasklet运行时,可以有其它的中断发生。tasklet通常是底半部处理的优选机制,因为这种机制非常快,但是所有的tasklet都必须是原子的。

    tasklet使用相当简单,我们只需要定义tasklet及其处理函数并将二者关联:
      
    void my_tasklet_func(unsigned long); //定义一个处理函数:
    DECLARE_TASKLET(my_tasklet,my_tasklet_func,data); //定义一个tasklet结构my_tasklet,与
    my_tasklet_func(data)函数相关联 。data为传入处理函数的参数。

    然后,在需要调度tasklet的时候引用一个简单的API就能使系统在适当的时候进行调度运行:  tasklet_schedule(&my_tasklet);

    实例: 

    01 //定义与绑定tasklet函数
    02  void test_tasklet_action(unsigned long t);
    03  DECLARE_TASKLET(test_tasklet, test_tasklet_action, 0);
    04  //中断处理底半部
    05  void test_tasklet_action(unsigned long t)
    06  {
    07      printk("tasklet is executing\n");
    08  }
    09  
    10 /*中断处理顶半部*/
    11  
    12 static irqreturn_t xxx_interrupt(int irq, void *dev_id)
    13 {
    14     .....
    15     tasklet_schedule(&test_tasklet);
    16     .....
    17 }
    18  
    19 /*设备驱动加载模块*/
    20  
    21 int __init xxx_init(void)
    22 {
    23     ......
    24     request_irq(IRQ_EINT0,xxx_interrupt, IRQ_TYPE_LEVEL_LOW, "xxx", NULL);
    25     ......
    26 }
    27  
    28 /*设备驱动卸载模块*/
    29  
    30 void __exit xxx_exit(void)
    31 {
    32     ......
    33     free(IRQ_EINT0,NULL);
    34     ......
    35  
    36 }

    二:工作队列

    工作队列会在将来某个时间,在某个特殊的工作者进程上下文中调用一个函数,因为工作队列函数运行在进程上下文中,因此可在必要时进行休眠,但是我们不能从工作队列向用户空间复制数据,要知道工作者进程无法访问其它任何进程的地址空间,除非使用进程间工享技术。

    struct work_struct my_wq;/*定义一个工作队列*/

    void my_wq_func(unsigned long);/*定义一个处理函数*/

    INIT_WORK(&my_wq,(void (*)(void *))my_wq_func,NULL);/*初始化工作队列并将其与处理函数绑定*/

    示例:

    01 struct work_struct my_wq;/*定义一个工作队列*/
    02  
    03 void my_wq_func(unsigned long);/*定义一个处理函数*/
    04  
    05 //中断处理底半部
    06  void my_wq_func(unsigned long t)
    07 {
    08     .......
    09 }
    10  
    11 /*中断处理顶半部*/
    12  
    13 static irqreturn_t xxx_interrupt(int irq, void *dev_id)
    14 {
    15     .....
    16     schedule_work(& my_wq);
    17     .....
    18  
    19 }
    20  
    21 /*设备驱动加载模块*/
    22  
    23 int __init xxx_init(void)
    24 {
    25     ......
    26     request_irq(IRQ_EINT0,xxx_interrupt, IRQ_TYPE_LEVEL_LOW, "xxx", NULL);
    27     INIT_WORK(&my_wq,(void (*)(void*))my_wq_func,NULL);/*初始化工作队列并将其与处理函数绑定*/
    28     ......
    29 }
    30  
    31 /*设备驱动卸载模块*/
    32  
    33 void __exit xxx_exit(void)
    34 {
    35     ......
    36     free(IRQ_EINT0,NULL);
    37     ......
    38 }

    结合实例可以更好的理解:下载网站:http://download.csdn.net/detail/zlcchina/6994283
    展开全文
  • linux中断处理下文: 工作队列分析

    千次阅读 2014-03-10 15:40:54
    工作队列(work queue)是另外一种将工作推后执行的形式,它和前面讨论的tasklet有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列...
  • 通过消息队列可以非常方便的实现分布式, 上篇文章使用Python的pika搭建的"生产者-消费者"...当客户端与消息队列的消息代理建立连接后, 客户端隔一定时间就会发送一个心跳检测包, 如果消息代理
  • 中断服务下半部之工作队列详解

    千次阅读 2011-11-28 11:33:45
    【转贴】中断服务下半部之工作队列详解 2009-11-01 23:43 Sailor_forever sailing_9806@163.com 转载请注明 http://blog.csdn.net/sailor_8318/archive/2008/07/16/2657294.aspx 【摘要】本文详解了中断...
  • 中断服务下半部之工作队列详解

    千次阅读 2010-11-19 10:28:00
    摘要】本文详解了中断服务下半部之工作队列实现机制。介绍了工作队列的特点、其与tasklet和softirq的区别以及其使用场合。接着分析了工作队列的三种数据结构的组织形式,在此基础之上分析了工作队列执行流程。最后...
  • linux内核中断处理的工作队列workqueue机制 工作队列(workqueue)是另外一种将工作 推后执行的形式,它和我们前面讨论的小任务有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分...
  • Linux 内核需要对连接到计算机上的所有硬件设备进行管理,毫无疑问这是它的份内事。如果要管理这些设备,首先得和它们互相通信才行,一般有两种方案可实现这种功能: 中断(interrupt) 让硬件在需要的时候向内核...
  • 前几天博主遇到一个很狗屎的bug,RabbitMQ本来运行的好好突然所有的消息队列都不消费了,看了一下 Connections连接,发现全部都发生阻塞了,导致线上的队列堆积如山,情况万分危急。 推测一:生产者和消费者问题 ...
  • 等待队列

    2013-06-07 09:20:23
    等待队列有很多用途,尤其用在中断处理、进程同步以及定时。等待队列实现了再事件上的条件等待:希望等待特定事件的进程把自己放进合适的等待队列,并放弃控制权。因此,等待队列表示一组睡眠的进程,当某一条件为真...
  • ubuntu系统网络连接两分钟左右就中断 (1)这是ifconfig的内容 lo Link encap:本地环回 inet 地址:127.0.0.1 掩码:255.0.0.0 inet6 地址: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 跃点数:1 接收...
  • CentOS 配置网卡多队列

    千次阅读 2020-04-22 16:22:42
    单台实例vCPU处理网络中断存在性能瓶颈时,您可以将实例中的网络中断分散给不同的CPU处理,从而提升性能。 前提条件 您的实例规格必须支持网卡多队列功能。支持多队列的实例规格请参见实例规格族,多队列数值...
  • 在springboot项目里使用redis做队列的,教程网上很多不赘述了,使用过程中会遇到短时间不操作(五分钟左右),redis连接就断开了,再使用redis时会报连接超时,此次操作会失败,然后几秒内会重连,重新连上之后又...
  • 一、中断处理的tasklet(小任务)机制中断服务程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU就不能及时响应其他的...
  • 一、概述:1、把发送者发送的信息全部封装在blockqueue队列里,然后使用connManager把队列里的信息取出,分发出去 2、原理图: 二、实现:/** * @描述 使用socket实现长连接 * @项目名称 App_Chat * @包名 ...
  • //则在到达指定的等待时间之前等待可用的空间,该方法可中断 boolean offer ( E e , long timeout , TimeUnit unit ) throws InterruptedException ; //将指定的元素插入此队列的尾部,如果该队列...
  • linux 中断中断处理

    千次阅读 2018-07-30 15:47:16
    物理学角度看,中断是一种电信号,中断是硬件发出,送入中断控制器的输入引脚中,中断控制器是个简单的电子芯片,其作用是将多路中断管线,采用复用技术只通过一个和处理器连接的管线与处理器通信。当接受到一个...
  • 中断服务下半部之工作队列详解 Sailor_forever sailing_9806@163.com 转载请注明http://blog.csdn.net/sailor_8318/archive/2008/07/16/2657294.aspx 【摘要】本文详解了中断服务下半部之工作队列实现机制。...
  • 默认情况下,rabbitmq集群中的队列内容位于单个节点(声明队列的节点)上。这与交换器和绑定形成了对比,交换器和绑定始终可视为位于所有节点上。队列可以选择性地跨多个节点进行镜像。 每个镜像队列由一个master和...
  • Linux将中断分为中断上半部和下半部。上半部用来处理紧急的和硬件操作相关的,此时所有当前中断线都被禁止,包括其它CPU。下半部用来处理能够被允许推迟完成的中断处理部分,此时中断是开启的。上下半部之间的界限依...
  • 工作队列(work queue)是另外一种将工作推后执行的形式.他和其他形式都不相同....通常,在工作队列和软中断/tasklet中做出选择非常容易.如果推后执行的任务需要睡眠,那么就选择工作队列,如果不需要睡眠,就选
  • 比如: 1)信息的发送者和接收者如何维持这个连接,如果一方的连接中断,这期间的数据如何方式丢失? 2)如何降低发送者和接收者的耦合度? 3)如何让Priority高的接收者先接到数据? 4)如何做到load balance?有效...
  • RabbitMQ (二)工作队列

    万次阅读 多人点赞 2014-07-10 00:41:02
    本系列教程主要来自于官网...这篇中我们将会创建一个工作队列用来在工作者(consumer)间分发耗时任务。 工作队列的主要任务是:避免立刻执行资源密集型任务,然后必须等待其完成。相反地,我们进行任务调度:我们把
  • RabbitMQ消息队列常见面试题总结

    万次阅读 2021-03-22 02:46:39
    RabbitMQ消息队列常见面试题总结; 1、什么是消息队列?消息队列的优缺点? 2、Kafka、ActiveMQ、RabbitMQ、RocketMQ的区别? 3、如何保证消息不被重复消费? 4、如何保证消息不丢失,进行可靠性传输? 5、如何保证...
  • RabbitMQ 消息队列

    千次阅读 2018-10-23 14:43:28
    RabbitMQ 消息队列 应用 安装参考 详细介绍 学习参考 RabbitMQ 消息队列  RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统。他遵循Mozilla Public License开源协议。 MQ全称为Message Queue, 消息队列(MQ...
  • linux 等待队列 PK linux 等待队列

    千次阅读 2012-02-20 16:06:38
    Linux内核的等待队列是以双循环链表为基础数据结构,与...等待队列头和等待队列项中都包含一个list_head类型的域作为"连接件"。它通过一个双链表和把等待tast的头,和等待的进程列表链接起来。上图可以清晰看到。所

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 89,930
精华内容 35,972
关键字:

从队列中断开连接