【workqueue】workqueue原理和机制

简介

工作队列(workqueue)是一种转移任务执行环境的工具,当系统产生一个中断时,可以在中断处理函数里做一些紧急地操作,然后将另外一些不那么特别紧急,而且需要一定时间的任务封装成函数交给工作队列执行,这是函数的执行环境就从中断环境变成了线程环境,这就是Linux里经常提及的中断处理“下半部”。

工作队列执行原理图:

img

工作队列内部有一个工作链表(worklist),链表上有很多工作项(work item)节点,可以将工作项简单理解为函数。工作队列内有一个线程一直在轮询工作链表,每次都从工作链表中取出一个工作项,并执行其相关联的函数。当工作队列为空时,线程会被挂起。

RT-Thread中的workqueue

RT-Thread中有workqueue组件,导入头文件#include <ipc/workqueue.h>即可使用。

workqueue结构体介绍

struct rt_workqueue
{
    rt_list_t work_list;              // 工作队列中的工作链表
    rt_list_t delayed_list;
    struct rt_work *work_current;     // 获取work
    
    struct rt_semaphore sem;
    rt_thread_t work_thread;          // 工作队列中的线程
};

工作项结构体

struct rt_work
{
       rt_list_t  list;           // 将该工作项挂载到工作链表上

       void (*work_func)(struct rt_work *work,void *work_data);     // 工作项绑定的函数指针
       void *work_data;                                                                     // 用户自定义数据,当工作项执行时会调用此函数
       rt_uint16_t flags;
       rt_uint16_t type;
       struct rt_timer timer;
       struct rt_workqueue *workqueue;
};

workqueue接口简介

初始化工作项

每一个工作项在被提交到工作队列之前,都需要调用接口初始化:

rt_inline void rt_work_init(struct rt_work *work, void (*work_func)(struct rt_work *work, void *work_data), void *work_data);

work:工作项结构体指针
work_func : 回调函数,工作项执行时调用
work_data : 用户自定义数据,回调函数参数
返回值:无

此接口初始化work指针指向的工作项,并绑定回调函数work_func以及用户自定义数据work_data。若使用自定义数据,需要保证数据对象为静态存储。

使用系统工作队列

开启系统工作队列后,就可以向系统工作队列提交一个工作项:

rt_err_t rt_work_submit(struct rt_work *work,rt_tick_t time);

work:工作项结构体
time: 提交延时,以tick为单位,需小于RT_TICK_MAX/2
返回值:RT_EOK(提交成功),-RT_EBUSY(该工作项正在执行),-RT_ERROR(time 参数错误)

取消提交过的工作项:

rt_err_t rt_work_cancel(struct rt_work *work);

此接口会在系统工作队列移除work指向的工作项。

创建销毁工作队列

除了使用系统的工作队列,用户也可以创建属于自己的工作队列:

struct rt_workqueue *rt_workqueue_create(const char *name,rt_uint16_t stack_size,rt_uint8_t priority);

name: 线程名字
stack_size: 线程栈大小
priority:线程优先级
返回值:RT_NULL(创建失败),rt_workqueue结构体指针(创建成功)

此函数创建并初始化一个工作队列,利用参数name,stack_size和priority创建工作队列内部线程,最终返回创建的工作队列。

若不再使用工作队列,也可以销毁:

rt_err_t rt_workqueue_destroy(struct rt_workqueue *queue);

此函数还原了内部所有状态,删除了内部线程,并释放了queue所指向的结构体空间,调用此函数后,不能再使用queue。

提交工作项

向自己创建的工作队列提交工作项有三个接口,根据是否允许工作项延时提交可分为:允许工作项延时提交和不允许工作项延时提交

允许工作项延时提交接口:

rt_err_t rt_workqueue_submit_work(struct rt_workqueue *queue,struct rt_work *work,rt_tick_t time);

queue: 工作队列结构体指针
work: 工作项结构体指针
time: 提交延时,以tick为单位,需小于RT_TICK_MAX/2
返回值:RT_EOK(提交成功),-RT_EBUSY(该工作项正在执行),-RT_ERROR(time参数错误)

此函数将work指向的工作项提交到queue指向的工作队列中,若time大于0,则提交延时time个tick之后执行。

不允许工作项延时提交的接口:

rt_err_t rt_workqueue_dowork(struct rt_workqueue *queue,struct rt_work *work);

queue: 工作队列结构体指针
work: 工作项结构体指针
返回值:RT_EOK(提交成功),-RT_EBUSY(该工作项正在执行)

此函数立即将work指向的工作项提交到queue指向的工作队列尾部。

类似接口:

rt_err_t rt_workqueue_critical_work(struct rt_workqueue *queue,struct rt_work *work);

queue:工作队列结构体指针
work: 工作项结构体指针
返回值:RT_EOK(提交成功),-RT_EBUSY(该工作项正在执行)

此函数立即将work指向的工作项提交到queue指向的工作队列头部,因此使用此函数提交的工作项在当前工作项执行完毕后会被立即执行,适用于一些比较紧急(critical)的任务。而通过dowork接口提交的工作项会挂载到工作队列尾部,适用于不紧急的任务。

取消工作项

取消指定工作项:

rt_err_t rt_workqueue_cancel_work(struct rt_workqueue *queue,struct rt_work *work);

queue:工作队列结构体指针
work:工作项结构体指针

此函数会从queue指向的工作对列中将work指向的工作项移除,这样该工作项就不会被执行。当该工作项正在执行时,返回-RT_EBUSY的错误。

还有一类sync接口不会返回错误,而是等待该工作项执行完毕:

rt_err_t rt_workqueue_cancel_work_sync(struct rt_workqueue *queue,struct rt_work *work);

queue:工作对列结构体指针
work:工作项结构体指针
返回值:RT_EOK(提交成功)

此函数会从queue指向的工作队列中将work指向的工作项移除,这样该工作项就不会被执行了。当工作项正在执行时,接口内部会阻塞等待该工作项执行完毕。

一次性取消所有工作项:

rt_err_t rt_workqueue_cancel_call_work(struct rt_workqueue *queue);

queue: 工作队列结构体指针
work: 工作项结构体指针

此函数取消queue指向的工作队列里的所有工作项。

workqueue使用实例

#include <rtthread.h>
#include <ipc/workqueue.h>

struct rt_work work1;
int work1_data = 1;

struct rt_work work2;
int work2_data = 2;

void work_func(struct rt_work *work,void *workdata)
{
    int data = *(int *)workdata;
    rt_kprintf("recv work data: %d\n", data);
}

int workqueue_example(void)
{
    printf("hello rt-thread!\n");

    struct rt_workqueue *wq = rt_workqueue_create("my_wq",2048,20);
    RT_ASSERT(wq);

    rt_work_init(&work1,work_func,&work1_data);
    rt_work_init(&work2,work_func,&work2_data);

    rt_workqueue_submit_work(wq,&work1,2);     // 工作项延时提交2 tick
    rt_workqueue_submit_work(wq,&work2,0);

    return 0;

}

运行结果如下,与工作项绑定的任务被异步执行,work1延迟了2个tick才执行:

hello rt-thread!
recv work data:2
recv work data:1

Linux中的workqueue

在Linux的环境下,workqueue是除了softirq和tasklet以外比较常用的中断下半部(中断触发后)机制。workqueue是一种将工作推后执行的形式,与tasklet(小任务机制)有所不同。workqueue本质是把work交给一个内核线程,在进程上下文调度的时候执行,这样通过工作队列执行的代码能占尽进程上下文的所有优势,比如工作队列允许被重新调度甚至休眠。这种异步执行的进程上下文,能解决因为softirq和tasklet执行时间长而导致系统实时性下降等问题。

workqueue的使用场景

关于工作队列和tasklet的使用场景,如果推后执行的任务需要睡眠,就可以选择工作队列;如果推后执行的任务不需要睡眠,就选用tasklet。另外,如果需要用一个可以重新调度的实体来执行下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现机制,也只有它才可以睡眠。这意味着在需要大量的内存和获取信号量时,在需要执行阻塞式的I/O操作时,workqueue都非常有用,如果不需要一个内核线程来推后执行工作,那么就考虑使用tasklet。

工作队列位于进程上下文,与软中断,tasklet有所区别,工作队列里允许延时,睡眠操作,而软中断,tasklet位于中断上下文,不允许睡眠和延时操作。

workqueue的数据结构


work_struct

工作项。初始化一个work并添加到工作队列后,将会将其传递到合适的内核线程进行处理,它是用来调度的最小单位。

struct work_struct {
    atomic_long_t data;       // 低bit位存放状态位,高比特位存放worker_poll的ID和poll_workqueue的指针
    struct list_head entry;   // 用于添加到其他队列上 
    work_func_t func;         // 工作任务的处理函数,在内核线程中回调
    #ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
    #endif
};

workqueue_struct

工作项的集合。workqueue和work是一对多的关系。内核中工作队列分为两种:

1.bound:绑定处理器工作队列,每个worker创建的内核线程绑定到特定的CPU上运行。
2.unbound:不绑定处理器的工作队列,创建的时候需要制定WQ_UNBOUND标志,内核线程可以在多核间迁移。

struct workqueue_struct 
{
    struct list_head pwqs;  /* 所有的pool_workqueue都添加到本链表中 */   
    struct list_head list;  /* 用于将工作队列添加到全局链表workqueues中 */  

    struct list_head maydays; /* rescue状态下的 pool_workqueue 添加到本链表中*/    
    struct worker  *rescuer; /* rescuer 内核线程,用于处理内存紧张时创建工作线程失败的情况*/  

    struct pool_workqueue *dfl_pwq; /* PW: only for unbound wqs */

    char   name[WQ_NAME_LEN]; /* I: workqueue name */

    /* hot fields used during command issue, aligned to cacheline */
    unsigned int  flags ____cacheline_aligned; /* WQ: WQ_* flags */
    struct pool_workqueue __percpu *cpu_pwqs; /* Per-CPU 创建 pool_workqueue */    
    struct pool_workqueue __rcu *numa_pwq_tbl[]; /* Per-Node 创建 pool_workqueue*/    
        ...
};

worker

工作者,worker对应一个work_thread()内核线程。

struct worker {
    /* on idle list while idle, on busy hash table while busy */
    union {
    struct list_head entry; /* 用于添加到worker_pool的空闲链表中 */     
    struct hlist_node hentry; /*用于添加到worker_pool的忙碌链表中 */ 
    };

    struct work_struct *current_work; /* 当前正在处理的work */  
    work_func_t  current_func; /* 当前正在执行的work回调函数*/                
    struct pool_workqueue *current_pwq; /* 指向当前work所属的pool_workqueue */   

    struct list_head scheduled; /* 所有被调度执行的work都将添加到该链表中 */   
    
    /* 64 bytes boundary on 64bit, 32 on 32bit */

    struct task_struct *task;  /* 指向内核线程 */   
    struct worker_pool *pool;  /* 该worker所属的worker pool */   
        /* L: for rescuers */
    struct list_head node;   /* 添加到worker_pool->workers链表中 */
        
        ...
};

pool_workqueue

工作队列池,负责建立起workqueue和worker_pool之间的关系。workqueue和pool_workqueue是一对多的关系。

struct pool_workqueue {
    struct worker_pool *pool;  /* 指向worker_pool */    
    struct workqueue_struct *wq;  /* 指向所属的 workqueue */   

    int   nr_active; /* 活跃的 work 数量 */    
    int   max_active; /* 活跃的最大 work 数量 */   
    struct list_head delayed_works; /*延迟执行的 work 挂入本链表*/     
    struct list_head pwqs_node; /* 用于添加到 workqueue 链表中 */    
    struct list_head mayday_node; /* 用于添加到workqueue链表中 */   
    ...
} __aligned(1 << WORK_STRUCT_FLAG_BITS);

worker_pool

工作者集合。pool_workqueue和worker_pool是一对一的关系,worker_pool和worker是一对多的关系。

1.bound类型的工作队列:worker_pool是Per-CPU创建,每一个CPU都有两个worker_pool,对应不同的优先级,nice值分别为0和-20
2.unbound类型的工作队列:worker_pool创建后添加到unbound_pool_hash哈希表中。

struct worker_pool {
    spinlock_t  lock;  /* the pool lock */
    int   cpu;  /* 绑定CPU的workqueue,代表CPU ID */     
    int   node;  /* 非绑定类型的workqueue,代表内存Node ID*/ 
    int   id;  /* I: pool ID */
    unsigned int  flags;  /* X: flags */

    unsigned long  watchdog_ts; /* L: watchdog timestamp */

    struct list_head worklist; /*pending状态的work添加到本链表 */  
    int   nr_workers; /* worker的总数量 */   

    /* nr_idle includes the ones off idle_list for rebinding */
    int   nr_idle; /* L: currently idle ones */

    struct list_head idle_list; /* 处于IDLE状态的worker添加到本链表 */  
    struct timer_list idle_timer; /* L: worker idle timeout */
    struct timer_list mayday_timer; /* L: SOS timer for workers */

    /* a workers is either on busy_hash or idle_list, or the manager */
    DECLARE_HASHTABLE(busy_hash, BUSY_WORKER_HASH_ORDER);   /* 工作状态的worker添加本哈希表中 */

    /* see manage_workers() for details on the two manager mutexes */
    struct worker  *manager; /* L: purely informational */
    struct mutex  attach_mutex; /* attach/detach exclusion */
    struct list_head workers; /* worker_pool管理的worker添加到本链表中 */   
    struct completion *detach_completion; /* all workers detached */

    struct ida  worker_ida; /* worker IDs for task name */

    struct workqueue_attrs *attrs;  /* I: worker attributes */
    struct hlist_node hash_node; /* 用于添加到unbound_pool_hash中 */    
        ...
} ____cacheline_aligned_in_smp;

结构关系如图:

img

workqueue的初始化

内核在启动的时候会对workqueue做初始化,workqueue的初始化包含两个阶段,分别是workqueue_init_early和workqueue_init。

workqueue_init_early

  • 分配worker_pool,并且对该结构中的字段进行初始化操作
  • 分配workqueue_struct,并且对该结构中的字段进行初始化操作
  • alloc_and_link_pwqs:分配pool_workqueue,将workqueue_struct和worker_pool关联起来

workqueue_init

主要工作是给之前创建好的worker_pool,添加一个初始的worker,然后利用函数create_worker,创建名字为kworker/XX:YY或kworker/uXX:YY的内核线程。其中XX表示worker_pool的编号,YY表示worker的编号,u表示unbound

img

  • 分配worker,并且对改接够的字段进行初始化操作
  • 为worker创建内核线程worker_thread
  • 将worker添加到worker_pool中
  • worker进入IDLE状态

经过这两个阶段的初始化,workqueue子系统基本就已经将数据结构的关联建立好了,当有work来进行调度的时候,就可以进行处理了。

workqueue的使用

内核推荐驱动开发者使用默认的workqueue,而不是新建workqueue。要使用系统默认的workqueue,首先需要初始化work,内核提供相关的INIT_WORK。

初始化work:

#define INIT_WORK(_work, _func)      \
 __INIT_WORK((_work), (_func), 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);   \
  (_work)->func = (_func);    \
 } while (0)

初始化work后就可以调用shedule_work函数把work挂入系统默认的workqueue中。

work调度

img

  • 将work添加到系统的system_wq工作队列中
  • 判断workqueue的类型,如果是bound类型,根据CPU来获取pool_workqueue。如果是unbound类型,通过node号来获取pool_workqueue。
  • 判断pool_workqueue活跃的work数量,少于最大限值将work加入到pool->worklist中,否则加入到pwq->delayed_works链表中。
  • 如果__need_more_worker判断没有worker在执行,则通过wake_up_worker唤醒worker内核线程。

worker_thread

worker内核线程的执行函数是worker_thread。

img

  • 设置标志位PF_WQ_WORKER,调度器在进行调度处理时会对task进行判断,针对workerqueue worker有特殊的处理
  • worker被唤醒的时候,跳转到woke_up执行。
  • woke_up中,如果此时worker是需要销毁的,那就进行清理工作并返回。否则离开IDLE状态,并进入recheck模块执行
  • recheck中,判断是否需要更多的worker来处理,如果没有任务处理,跳转到sleep地方进行睡眠。如果有任务需要处理时,遍历工作链表,对链表中的每个节点调用process_one_work来执行work的回调函数,即INIT_WORK里的回调函数。

img

sleep中,没有任务处理时,worker进入空闲状态,并将当前的内核线程设置成睡眠状态,并让出cpu。

workqueue使用实例

使用workqueue需要包含以下头文件:

#include <linux/workqueue.h>

工作队列相关API


// 定义工作项
struct work_struct work;

// 初始化(工作项,工作项执行函数)
INIT_WORK(struct work_struct *work,void (*func)(struct work_struct *work));

// 定义并初始化
DECLARE_WORK(name,void (*func)(struct work_struct *work));

// 调度工作项
int schedule_work(struct work_struct *work);

// 延迟调度工作项
int schedule_delayed_work(struct work_struct *work,unsigned long delay);

// 创建新队列和新工作者队列
struct workqueue_struct *create_workqueue(const char *name);

// 调度指定队列
int queue_work(struct workqueue_struct *wq,struct work_struct *work);

// 延迟调度指定队列:
int queue_delayed_work(struct workqueue_struct *wq,struct work_struct *work,unsigned long delay);

// 销毁队列
void destroy_workqueue(struct workqueue_struct *wq);

注意创建一个工作队列就会有一个内核线程,一般不要轻易创建队列,最好使用默认工作队列。

代码示例

Demo1:在以下源码中,当读取/dev/etx_devices时,中断将命中。每当发生中断时,都会将work添加到workqueue。

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include<linux/slab.h>                 //kmalloc()
#include<linux/uaccess.h>              //copy_to/from_user()
#include<linux/sysfs.h> 
#include<linux/kobject.h> 
#include <linux/interrupt.h>
#include <asm/io.h>
#include <linux/workqueue.h>            // Required for workqueues
#include <linux/err.h>
 
#define IRQ_NO 11
 
 
void workqueue_fn(struct work_struct *work); 
 
/*Creating work by Static Method */
DECLARE_WORK(workqueue,workqueue_fn);
 
/*Workqueue Function*/
void workqueue_fn(struct work_struct *work)
{
        printk(KERN_INFO "Executing Workqueue Function\n");
}
 
 
//Interrupt handler for IRQ 11. 
static irqreturn_t irq_handler(int irq,void *dev_id) {
        printk(KERN_INFO "Shared IRQ: Interrupt Occurred");
        schedule_work(&workqueue);
        
        return IRQ_HANDLED;
}
 
 
volatile int etx_value = 0;
 
 
dev_t dev = 0;
static struct class *dev_class;
static struct cdev etx_cdev;
struct kobject *kobj_ref;

/*
** Function Prototypes
*/
static int __init etx_driver_init(void);
static void __exit etx_driver_exit(void);
 
/*************** Driver Fuctions **********************/
static int etx_open(struct inode *inode, struct file *file);
static int etx_release(struct inode *inode, struct file *file);
static ssize_t etx_read(struct file *filp, 
                char __user *buf, size_t len,loff_t * off);
static ssize_t etx_write(struct file *filp, 
                const char *buf, size_t len, loff_t * off);
 
/*************** Sysfs Fuctions **********************/
static ssize_t sysfs_show(struct kobject *kobj, 
                struct kobj_attribute *attr, char *buf);
static ssize_t sysfs_store(struct kobject *kobj, 
                struct kobj_attribute *attr,const char *buf, size_t count);
 
struct kobj_attribute etx_attr = __ATTR(etx_value, 0660, sysfs_show, sysfs_store);

/*
** File operation sturcture
*/
static struct file_operations fops =
{
        .owner          = THIS_MODULE,
        .read           = etx_read,
        .write          = etx_write,
        .open           = etx_open,
        .release        = etx_release,
};

/*
** This function will be called when we read the sysfs file
*/ 
static ssize_t sysfs_show(struct kobject *kobj, 
                struct kobj_attribute *attr, char *buf)
{
        printk(KERN_INFO "Sysfs - Read!!!\n");
        return sprintf(buf, "%d", etx_value);
}

/*
** This function will be called when we write the sysfsfs file
*/
static ssize_t sysfs_store(struct kobject *kobj, 
                struct kobj_attribute *attr,const char *buf, size_t count)
{
        printk(KERN_INFO "Sysfs - Write!!!\n");
        sscanf(buf,"%d",&etx_value);
        return count;
}

/*
** This function will be called when we open the Device file
*/  
static int etx_open(struct inode *inode, struct file *file)
{
        printk(KERN_INFO "Device File Opened...!!!\n");
        return 0;
}

/*
** This function will be called when we close the Device file
*/  
static int etx_release(struct inode *inode, struct file *file)
{
        printk(KERN_INFO "Device File Closed...!!!\n");
        return 0;
}

/*
** This function will be called when we read the Device file
*/
static ssize_t etx_read(struct file *filp, 
                char __user *buf, size_t len, loff_t *off)
{
        printk(KERN_INFO "Read function\n");
        asm("int $0x3B");  // Corresponding to irq 11
        return 0;
}

/*
** This function will be called when we write the Device file
*/
static ssize_t etx_write(struct file *filp, 
                const char __user *buf, size_t len, loff_t *off)
{
        printk(KERN_INFO "Write Function\n");
        return len;
}
 
/*
** Module Init function
*/
static int __init etx_driver_init(void)
{
        /*Allocating Major number*/
        if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) <0){
                printk(KERN_INFO "Cannot allocate major number\n");
                return -1;
        }
        printk(KERN_INFO "Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev));
 
        /*Creating cdev structure*/
        cdev_init(&etx_cdev,&fops);
 
        /*Adding character device to the system*/
        if((cdev_add(&etx_cdev,dev,1)) < 0){
            printk(KERN_INFO "Cannot add the device to the system\n");
            goto r_class;
        }
 
        /*Creating struct class*/
        if(IS_ERR(dev_class = class_create(THIS_MODULE,"etx_class"))){
            printk(KERN_INFO "Cannot create the struct class\n");
            goto r_class;
        }
 
        /*Creating device*/
        if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"etx_device"))){
            printk(KERN_INFO "Cannot create the Device 1\n");
            goto r_device;
        }
 
        /*Creating a directory in /sys/kernel/ */
        kobj_ref = kobject_create_and_add("etx_sysfs",kernel_kobj);
 
        /*Creating sysfs file for etx_value*/
        if(sysfs_create_file(kobj_ref,&etx_attr.attr)){
                printk(KERN_INFO"Cannot create sysfs file......\n");
                goto r_sysfs;
        }
        if (request_irq(IRQ_NO, irq_handler, IRQF_SHARED, "etx_device", (void *)(irq_handler))) {
            printk(KERN_INFO "my_device: cannot register IRQ ");
                    goto irq;
        }
        printk(KERN_INFO "Device Driver Insert...Done!!!\n");
        return 0;
 
irq:
        free_irq(IRQ_NO,(void *)(irq_handler));
 
r_sysfs:
        kobject_put(kobj_ref); 
        sysfs_remove_file(kernel_kobj, &etx_attr.attr);
 
r_device:
        class_destroy(dev_class);
r_class:
        unregister_chrdev_region(dev,1);
        cdev_del(&etx_cdev);
        return -1;
}

/*
** Module exit function
*/ 
static void __exit etx_driver_exit(void)
{
        free_irq(IRQ_NO,(void *)(irq_handler));
        kobject_put(kobj_ref); 
        sysfs_remove_file(kernel_kobj, &etx_attr.attr);
        device_destroy(dev_class,dev);
        class_destroy(dev_class);
        cdev_del(&etx_cdev);
        unregister_chrdev_region(dev, 1);
        printk(KERN_INFO "Device Driver Remove...Done!!!\n");
}
 
module_init(etx_driver_init);
module_exit(etx_driver_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeTronicX <embetronicx@gmail.com>");
MODULE_DESCRIPTION("Simple Linux device driver (Global Workqueue - Static method)");
MODULE_VERSION("1.10");

Demo2:当我们按下按键的时候,进入外部中断服务函数,此时task_fuc先被调用,然后调用到mywork_fuc,并打印了mywork_fuc里面的信息,从这里我们用程序验证了,工作队列是位于进程上下文,而不是中断上下文,和tasklet是有所区别的。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/timer.h>  /*timer*/
#include <asm/uaccess.h>  /*jiffies*/
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
struct tasklet_struct task_t ; 
struct workqueue_struct *mywork ;
//定义一个工作队列结构体
struct work_struct work;
static void task_fuc(unsigned long data)
{
    if(in_interrupt()){
             printk("%s in interrupt handle!\n",__FUNCTION__);
        }
}
//工作队列处理函数
static void mywork_fuc(struct work_struct *work)
{
    if(in_interrupt()){
             printk("%s in interrupt handle!\n",__FUNCTION__);
        }
    msleep(2);
    printk("%s in process handle!\n",__FUNCTION__);
}

static irqreturn_t irq_fuction(int irq, void *dev_id)
{    
    tasklet_schedule(&task_t);
    //调度工作
    schedule_work(&work);
    if(in_interrupt()){
         printk("%s in interrupt handle!\n",__FUNCTION__);
    }
    printk("key_irq:%d\n",irq);
    return IRQ_HANDLED ;
}

static int __init tiny4412_Key_irq_test_init(void) 
{
    int err = 0 ;
    int irq_num1 ;
    int data_t = 100 ;
    //创建新队列和新工作者线程
    mywork = create_workqueue("my work");
    //初始化
    INIT_WORK(&work,mywork_fuc);
    //调度指定队列
    queue_work(mywork,&work);
    tasklet_init(&task_t,task_fuc,data_t);
    printk("irq_key init\n");
    irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2));
    err = request_irq(irq_num1,irq_fuction,IRQF_TRIGGER_FALLING,"tiny4412_key1",(void *)"key1");
    if(err != 0){
        free_irq(irq_num1,(void *)"key1");
        return -1 ;
    }
    return 0 ;
}

static void __exit tiny4412_Key_irq_test_exit(void) 
{
    int irq_num1 ;
    printk("irq_key exit\n");
    irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2));
    //销毁一条工作队列
    destroy_workqueue(mywork);
    free_irq(irq_num1,(void *)"key1");
}

module_init(tiny4412_Key_irq_test_init);
module_exit(tiny4412_Key_irq_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("YYX");
MODULE_DESCRIPTION("Exynos4 KEY Driver");

将程序编译完,将zImage下到板子上,重新启动会看到内核打印信息

irq_function in interrupt handle!
key_irq:378
task_fuc in interrupt handle!
mywork_fuc in process handle!

总结

Linux环境下中断触发完整过程图:

img










参考文章:
https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/workqueue/workqueue

扒开 Linux 中断的底裤之 workqueue - 腾讯云开发者社区-腾讯云 (tencent.com)
https://www.cnblogs.com/dream397/p/15606328.html#:~:text=Linux设备驱动workqueue (工作队列)案例实现 1 1、需要包含的头文件 2 2、工作队列相关的数据结构,(各个版本内核可能不同,这里用的是3.5) 3 3、操作工作队列相关的API 4 4、Demo实现 (基于Tiny4412 Linux3.5内核)

posted @ 2023-01-07 15:31  Emma1111  阅读(1579)  评论(0编辑  收藏  举报