• 中断下半部之 work queue


    作用

    work queue 和 softirq 和 tasklet 一样,是常用的下半部机制之一。不过,和 softirq 以及 tasklet 不同的是,work queue 的本质是把 work 交给一个内核线程,在进程上下文调度的时候执行。因为这个特点,work queue 允许重新调度和睡眠。这种异步执行的进程上下文,能解决因为 softirq 和 tasklet 执行时间长而导致的系统实时性下降等问题。
    当驱动程序在进程上下文中有异步执行的工作任务时,可以用 work 来描述工作任务。把 work 添加到一个链表 worklist 中,然后由一个内核线程 worker 遍历链表,串行地执行挂入worklist 中的所有 work,如果 worklist 中没有 work,那么内核线程 worker 就会变成 IDLE 状态,如果有 work,则执行 work 中的回调函数。

    数据结构

    work_struct :
    描述一个 work

    struct work_struct {
    	atomic_long_t data;
    	struct list_head entry;
    	work_func_t func;
    #ifdef CONFIG_LOCKDEP
    	struct lockdep_map lockdep_map;
    #endif
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • data:低 bit 存放状态位,高 bit 存放 worker_pool 的 ID 或者 pool_workqueue 的指针
    • entry:用于添加到其它队列上
    • func:work 的处理函数,在内核线程 work 中被调用执行

    使用

    一、初始化
    内核推荐驱动开发者使用默认的 work queue,而不是新建 work queue。要使用系统默认的 work queue,首先需要初始化 work,内核提供了相应的宏 INIT_WORK()。
    二、调度
    初始化 work 后,就可以调用 schedule_work() 函数把 work 挂入系统默认的 work queue 中。

    示例

    irq_workqueue.c

    #include 
    #include 
    #include 
    #include 
    
    #define BUTTON_PIN 12 /* GPIO 12 */
    
    int flag = 0;
    
    static struct work_struct work;
    
    void workqueue_fn(struct work_struct *work)	 // 下半部
    {
    	printk("%s(), %s\n", __func__, current->comm);
    }
    
    static irqreturn_t irq_handler(int irq, void *dev)	// 上半部
    {
    	printk("%s(): enter\n", __FUNCTION__);
    
    	schedule_work(&work);
    
    	printk("%s(): exit\n", __FUNCTION__);
    
    	return IRQ_HANDLED;
    }
    
    static int led_init(void)
    {
    	int err;
    	int irq;
    
    	printk("%s()\n", __FUNCTION__);
    
    	INIT_WORK(&work, workqueue_fn);
    
    	err = gpio_request_one(BUTTON_PIN, GPIOF_IN, "Button");
    	if (err)
    		return err;
    
    	irq = gpio_to_irq(BUTTON_PIN);
    	// enable_irq(irq); // why crash ?
    	err = request_irq(irq, irq_handler, IRQ_TYPE_EDGE_BOTH, "LED Test", NULL);
    	if (err < 0) {
    		printk("request irq (%d) failed!\n", irq);
    		return err;
    	}
    
    	printk("request irq (%d) success!\n", irq);
    
    	flag = 1;
    
    	return 0;
    }
    
    static void led_exit(void)
    {
    	printk("%s()\n", __FUNCTION__);
    
    	if (flag)
    		free_irq(gpio_to_irq(BUTTON_PIN), NULL);
    
    	gpio_free(BUTTON_PIN);
    
    	return;
    }
    
    module_init(led_init);
    module_exit(led_exit);
    
    MODULE_LICENSE("Dual BSD/GPL");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    Makefile

    obj-m = irq_workqueue.o
    
    KDIR=/home/liyongjun/project/board/buildroot/RPi3/build/linux-custom
    CROSS_COMPILE=/home/liyongjun/project/board/buildroot/RPi3/host/bin/arm-buildroot-linux-gnueabihf-
    
    all:
    	make -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) modules
    
    clean:
    	make -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) clean
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    gpioout.c

    #include "bcm2835.h"
    #include 
    
    int main(int argc, char *argv[])
    {
    	int n = atoi(argv[1]);
    	bcm2835_init();
    	bcm2835_gpio_fsel(21, BCM2835_GPIO_FSEL_OUTP);
    	while (n--) {
    		bcm2835_gpio_set(21);
    		sleep(1);
    		bcm2835_gpio_clr(21);
    		sleep(1);
    	}
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    /home/liyongjun/project/board/buildroot/RPi3/host/bin/arm-buildroot-linux-gnueabihf-gcc gpioout.c -o gpioout.out -L ../bcm2835-1.71/src/ -I ../bcm2835-1.71/src/ -lbcm2835
    
    • 1

    测试

    在树莓派上执行,树莓派 GPIO12 引脚和 GPIO21 使用杜邦线连接。gpioout.out 会操作 21 引脚,使其输出一次高电平和一次低电平,通过杜邦线连接到 12 引脚,进而触发 12 引脚的边沿中断。
    产生中断后,会调用中断处理函数 irq_handler(),在中断处理函数中调用 schedule_work(&work) 将 work 挂入系统默认的 worklist,最终由 worker 内核线程执行。

    # 
    # insmod irq_workqueue.ko 
    [ 3907.271811] led_init()
    [ 3907.275666] request irq (200) success!
    # 
    # ./gpioout.out 1
    [ 3911.812154] irq_handler(): enter
    [ 3911.816768] irq_handler(): exit
    [ 3911.821380] workqueue_fn(), kworker/0:1
    [ 3912.812235] irq_handler(): enter
    [ 3912.816763] irq_handler(): exit
    [ 3912.821208] workqueue_fn(), kworker/0:1
    # 
    # rmmod irq_workqueue.ko 
    [ 3916.560012] led_exit()
    # 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
  • 相关阅读:
    建设基础设施Terraform
    棋盘格测距-单目相机(OpenCV/C++)
    [node.js] node.js下载与安装;nodejs模块化介绍;同步与异步区别try-catch捕捉异常;fs模块;path路径模块;http模块
    接口测试--Postman常用断言
    【计算机毕业设计】75.教师工作考核绩效管理系统源码
    Python之函数、模块、包库
    tiup cluster template
    详细学习Mybatis(1)
    Spring Boot集成antlr实现词法和语法分析
    huggingface下载文件,可以看下载进度
  • 原文地址:https://blog.csdn.net/lyndon_li/article/details/127942839