工作队列(workqueue)

2023-11-19

转载于:http://blog.csdn.net/angle_birds/article/details/8448070


项目需要,在驱动模块里用内核计时器timer_list实现了一个状态机。
郁闷的是,运行时总报错“Scheduling while atomic”,网上搜了一下:

"Scheduling while atomic" indicates that you've tried to sleep somewhere that you shouldn't - like within a spinlock-protected critical section or an interrupt handler.

改进程序,在计时器里使用了workqueue,搞定问题。顺便把workqueue的实现代码总结了一下


一、workqueue简介

workqueue与tasklet类似,都是允许内核代码请求某个函数在将来的时间被调用(抄《ldd3》上的),每个workqueue就是一个内核进程。

workqueue与tasklet的区别:
   1.tasklet是通过软中断实现的,在软中断上下文中运行,tasklet代码必须是原子的,workqueue是通过内核进程实现的,就没有上述限制的,最爽的是,工作队列函数可以休眠 
         
     PS: 我的驱动模块就是印在计时器中调用了可休眠函数,所以出现了cheduling while atomic告警
         内核计时器也是通过软中断实现的

   2.tasklet始终运行在被初始提交的同一处理器上,workqueue不一定


   3.tasklet不能确定延时时间(即使很短),workqueue可以设定延迟时间


二、workqueue的API

 
workqueue的API自2.6.20后发生了变化


#include <linux/workqueue.h>
struct workqueue_struct;
struct work_struct;

struct workqueue_struct *create_workqueue(const char *name);
void destroy_workqueue(struct workqueue_struct *queue);

INIT_WORK(_work, _func);
INIT_DELAYED_WORK(_work, _func);

int queue_work(struct workqueue_struct *wq, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *wq,struct delayed_work *dwork, unsigned long delay);

int queue_delayed_work_on(int cpu, struct workqueue_struct *wq,
            struct delayed_work *dwork, unsigned long delay);

int cancel_work_sync(struct work_struct *work);
int cancel_delayed_work_sync(struct delayed_work *dwork);

void flush_workqueue(struct workqueue_struct *wq);


Workqueue编程接口

序号

接口函数

说明

1

create_workqueue

用于创建一个workqueue队列,为系统中的每个CPU都创建一个内核线程。输入参数:

@name:workqueue的名称

2

create_singlethread_workqueue

用于创建workqueue,只创建一个内核线程。输入参数:

@name:workqueue名称

3

destroy_workqueue

释放workqueue队列。输入参数:

@ workqueue_struct:需要释放的workqueue队列指针

4

schedule_work

调度执行一个具体的任务,执行的任务将会被挂入Linux系统提供的workqueue——keventd_wq输入参数:

@ work_struct:具体任务对象指针

5

schedule_delayed_work

延迟一定时间去执行一个具体的任务,功能与schedule_work类似,多了一个延迟时间,输入参数:

@work_struct:具体任务对象指针

@delay:延迟时间

6

queue_work

调度执行一个指定workqueue中的任务。输入参数:

@ workqueue_struct:指定的workqueue指针

@work_struct:具体任务对象指针

7

queue_delayed_work

延迟调度执行一个指定workqueue中的任务,功能与queue_work类似,输入参数多了一个delay。



下面实例是不指定delay时间的workqueue

(代码基于2.6.24)

struct my_work_stuct{
    int test;
    struct work_stuct save;
};

struct my_work_stuct test_work; 
struct workqueue_struct *test_workqueue;

void do_save(struct work_struct *p_work)
{
    struct my_work_struct *p_test_work = container_of(p_work, struct my_work_stuct, save);
    printk("%d\n",p_test_work->test);
}
  
void test_init()
{
    test_workqueue = create_workqueue("test_workqueue");
    if (!test_workqueue)
        panic("Failed to create test_workqueue\n");

    INIT_WORK(&(test_work.save), do_save);

    queue_work(test_workqueue, &(test_work.save));
}

void test_destory(void)
{
    if(test_workqueue)
        destroy_workqueue(test_workqueue);
}


三、workqueue的实现


工作队列workqueue不是通过软中断实现的,它是通过内核进程实现的



首先,创建一个workqueue,实际上就是建立一个内核进程

create_workqueue("tap_workqueue")
--> __create_workqueue(“tap_workqueue”, 0, 0)
--> __create_workqueue_key((name), (singlethread), (freezeable), NULL, NULL){
         wq = kzalloc(sizeof(*wq), GFP_KERNEL);
         wq->cpu_wq = alloc_percpu(struct cpu_workqueue_struct);
         wq->name = name;
         wq->singlethread = singlethread;
         wq->freezeable = freezeable;
         INIT_LIST_HEAD(&wq->list);

         for_each_possible_cpu(cpu) {
             cwq = init_cpu_workqueue(wq, cpu);
             err = create_workqueue_thread(cwq, cpu);
             start_workqueue_thread(cwq, cpu);
         }
    }


create_workqueue_thread 建立了一个内核进程 worker_thread(linux_2_6_24/kernel/workqueue.c)

create_workqueue_thread(struct cpu_workqueue_struct *cwq, int cpu)
{
    struct workqueue_struct *wq = cwq->wq;

    const char *fmt = is_single_threaded(wq) ? "%s" : "%s/%d";
    struct task_struct *p;

    p = kthread_create(worker_thread, cwq, fmt, wq->name, cpu);

    if (IS_ERR(p))
        return PTR_ERR(p);

    cwq->thread = p;

    return 0;
}



内核进程worker_thread做的事情很简单,死循环而已,不停的执行workqueue上的work_list

(linux_2_6_24/kernel/workqueue.c)

int worker_thread (void *__cwq)
{
    struct cpu_workqueue_struct *cwq = __cwq;
    /*下面定义等待队列项*/
    DEFINE_WAIT(wait);

    /*下面freezeable一般为0*/
    if (cwq->wq->freezeable)
        set_freezable();

    /*提高优先级别*/
    set_user_nice(current, -5);

    for (;;) {
        /*在cwq->more_work上等待, 若有人调用queue_work,该函数将调用wake_up(&cwq->more_work) 激活本进程*/
        prepare_to_wait(&cwq->more_work, &wait, TASK_INTERRUPTIBLE);

        /*work队列空则切换出去*/
        if (!freezing(current) && !kthread_should_stop() && list_empty(&cwq->worklist))
            schedule();


        /*切换回来则结束等待 说明有人唤醒cwq->more_work上的等待 有work需要处理*/
        finish_wait(&cwq->more_work, &wait);

        /*下面空,因为没有定义电源管理*/
        try_to_freeze();

        if (kthread_should_stop())
            break;

        /*run_workqueue依次处理工作队列上所有的work*/
        run_workqueue(cwq);
    }
    return 0;
}


/*run_workqueue依次处理工作队列上所有的work*/
static void run_workqueue(struct cpu_workqueue_struct *cwq)
{
    spin_lock_irq(&cwq->lock);
    cwq->run_depth++;
    if (cwq->run_depth > 3) {
        /* morton gets to eat his hat */
        printk("%s: recursion depth exceeded: %d\n",
            __FUNCTION__, cwq->run_depth);
        dump_stack();
    }

    while (!list_empty(&cwq->worklist)) {
        struct work_struct *work = list_entry(cwq->worklist.next,
                        struct work_struct, entry);
        work_func_t f = work->func;
#ifdef CONFIG_LOCKDEP
        /*
         * It is permissible to free the struct work_struct
         * from inside the function that is called from it,
         * this we need to take into account for lockdep too.
         * To avoid bogus "held lock freed" warnings as well
         * as problems when looking into work->lockdep_map,
         * make a copy and use that here.
         */
        struct lockdep_map lockdep_map = work->lockdep_map;
#endif

        cwq->current_work = work;
        list_del_init(cwq->worklist.next);
        spin_unlock_irq(&cwq->lock);

        BUG_ON(get_wq_data(work) != cwq);
        work_clear_pending(work);
        lock_acquire(&cwq->wq->lockdep_map, 0, 0, 0, 2, _THIS_IP_);
        lock_acquire(&lockdep_map, 0, 0, 0, 2, _THIS_IP_);

        f(work); /*执行work项中的func*/
       
        lock_release(&lockdep_map, 1, _THIS_IP_);
        lock_release(&cwq->wq->lockdep_map, 1, _THIS_IP_);

        if (unlikely(in_atomic() || lockdep_depth(current) > 0)) {
            printk(KERN_ERR "BUG: workqueue leaked lock or atomic: "
                    "%s/0x%08x/%d\n",
                    current->comm, preempt_count(),
                        task_pid_nr(current));
            printk(KERN_ERR " last function: ");
            print_symbol("%s\n", (unsigned long)f);
            debug_show_held_locks(current);
            dump_stack();
        }

        spin_lock_irq(&cwq->lock);
        cwq->current_work = NULL;
    }
    cwq->run_depth--;
    spin_unlock_irq(&cwq->lock);
}


将一个work加入到指定workqueue的work_list中(文件linux_2_6_24/kernel/workqueue.c)

  int fastcall queue_work(struct workqueue_struct *wq, struct work_struct *work)
{
    int ret = 0;

    if (!test_and_set_bit(WORK_STRUCT_PENDING, work_data_bits(work))) {
        BUG_ON(!list_empty(&work->entry));
        __queue_work(wq_per_cpu(wq, get_cpu()), work);
        put_cpu();
        ret = 1;
    }
    return ret;
} 


/* Preempt must be disabled. */
static void __queue_work(struct cpu_workqueue_struct *cwq, struct work_struct *work)
{
    unsigned long flags;

    spin_lock_irqsave(&cwq->lock, flags);
    insert_work(cwq, work, 1);
    spin_unlock_irqrestore(&cwq->lock, flags);
}

static void insert_work(struct cpu_workqueue_struct *cwq,
                struct work_struct *work, int tail)
{
    set_wq_data(work, cwq);
    /*
     * Ensure that we get the right work->data if we see the
     * result of list_add() below, see try_to_grab_pending().
     */
    smp_wmb();
    if (tail)
        list_add_tail(&work->entry, &cwq->worklist);
    else
        list_add(&work->entry, &cwq->worklist);
    wake_up(&cwq->more_work);
}


四、共享队列

其实内核有自己的一个workqueue,叫keventd_wq,这个工作队列也叫做“共享队列”。
do_basic_setup --> init_workqueues --> create_workqueue("events"); 

若驱动模块使用的workqueue功能很简单的话,可以使用“共享队列”,不用自己再建一个队列

使用共享队列,有这样一套API

int schedule_work(struct work_struct *work)
{
    queue_work(keventd_wq, work);
}

int schedule_delayed_work(struct delayed_work *dwork,unsigned long delay)
{
    timer_stats_timer_set_start_info(&dwork->timer);
    return queue_delayed_work(keventd_wq, dwork, delay);
}

void flush_scheduled_work(void)
{
    flush_workqueue(keventd_wq);
}


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

工作队列(workqueue) 的相关文章

随机推荐

  • JS 字符串常用方法总结

    初识字符串 字符串可以是对象 var firstName new String Bill typeof firstName gt object new 关键字使代码复杂化 字符串方法 所有的字符串方法都是返回新的字符串 不会更改原始字符 l
  • erp故障处理流程图_工厂有了ERP,为什么还要上MES?

    制造车间的信息黑洞随着ERP在企业应用的深入 ERP系统逐渐显示出其局限性 当ERP下达的工单到生产现场后 产品制造是以产线 工单 或 批次 Batch 为线索的执行过程 在这个过程中 现场管理人员需要实时信息进行决策 比如 不同产线上目前
  • 计算机视觉之三维重建(一)(摄像机几何)

    针孔摄像机 添加屏障 使用针孔 o 光圈 针孔 摄像机中心 实现现实与成像一对一映射 减少模糊 其中针孔与像平面的距离为f 焦距 虚拟像平面位于针孔与真实物体之间 与像平面互为倒立关系 位置映射 利用相似三角形得到现实坐标在像平面上的映射坐
  • Win 10 +Ubuntu双系统

    我们的神器WSL 神器WSL在官方称为适用于Linux系统的Windows子系统 里面的各种命令操作跟原生的Linux系统操作是一样的 完全可以让自己应用上 安装 在安装前我们需要做下准备工作 首先确保我们的系统能进行这样的操作 准备工作
  • SSH常见面试题

    1 项目中为什么使用SSH框架 使用Struts是因为struts是基于MVC模式的 很好的将应用程序进行了分层 使开发者更关注于业务逻辑的实现 另外struts有着丰富的taglib 如能灵活运用 则能大大提高开发效率 使用Hiberna
  • 单片机检测信号通断通用电路(3.3V/5V直流信号,24V+直流信号,220V交流信号)

    在实际的电路设计中 往往需要用到单片机检测某些信号通断 检测电压有无 在一定的范围内 比如3 3V的直流信号 单片机的IO是可以直接连接信号检测的 但是往往实际信号各种各样 24V 48V直流 220V交流 甚至380V交流 单片机就无法直
  • pc连接用access,交换机互联用trunk

    实验目的 同一个里vlan下面的所有主机可以相连 vlan10和vlan100可以相通 实验前提 把pc的基本配置做完 让他们在一个网段里 给pc1和pc3按照要求用vlan10 pc2用vlan100 交换机1 首先让交换机创建10 10
  • html中使用js实现体彩11选5随机选号

    体彩11选5随机选号 页面预览 代码实现
  • OpenCV简单应用(一、摄像头拍照)

    1 首先安装OpenCV 当然所用电脑要有摄像头 且接入网络 比较简单粗暴的做法是到Python所在的目录下 Scripts下运行 pip install opencv python 2 以下代码是开启摄像头 按任意键退出 import c
  • QT2023新版安装教程

    目录 前言 一 QT在线安装包下载 1 官方网站 2 镜像 清华大学 二 QT安装 1 更换安装源 2 安装界面 3 组件选择 重点 参考 Qt2023新版保姆级 安装教程 前言 本文主要介绍2023新版QT安装过程 希望能帮到大家 一 Q
  • mybatisplus是什么?

    Mybatis Plus 简称为Mybatis 或MP 是一个开源的Mybatis增强框架 它是在Mybatis基础上进行了扩展和增强 Mybatis Plus旨在简化开发过程 提高代码的开发效率 并提供更多的便捷功能 Mybatis Pl
  • 仿网易云音乐小程序

    一 程序描述 仿网易云音乐小程序 调用网易云API真实接口 实现了音乐播放 音乐切换 歌词显示 搜索音乐 视频播放 用户登录退出等功能 以下是效果展示 仿网易云小程序 二 代码介绍 1 目录介绍 2 搜索 代码讲解 实现思路 先把搜索的静态
  • 台式电脑切换集成显卡和独立显卡

    台式电脑切换集成显卡和独立显卡 1 背景 2 认识 3 步骤 3 1 确认是否有两个显卡以及当前显示器连接显卡 3 2 更新驱动 3 3 确认显示器连接接口 3 4 显卡设置 4 总结 1 背景 发现电脑在处理画面时有非常卡顿 想着电脑装的
  • html中图片热区链接怎么设置,html图像热区链接做好之后怎么上传到网页上让别人打开?...

    html图像热区链接做好之后怎么上传到网页上让别人打开 以下文字资料是由 历史新知网www lishixinzhi com 小编为大家搜集整理后发布的内容 让我们赶快一起来看一下吧 图像热区链接做好之后怎么上传到网页上让别人打开 做的是静态
  • 使用session实现同一账号只能同时一个人使用

    使用session实现同一账号只能同时一个人使用 今天我们要讲的就是 实现同一个账号只能同一时间让一个人使用 实现起来也是非常的简单 其实我这里讲到的是我前几天做出来的一个大概核心代码和核心思路 我也是查遍了很多网站 看了很多人的源码然后都
  • QT绘图之自动缩放画线和点

    需求 用 作为画板 把纸条缩放到 上 纸条长度不定 宽度固定 纸条上任意位置画点或者线 点距或者线距不固定 点数和线数也不固定 要成比例映射到 上直观显示 话不多说 上代码 ool sprayer Widget eventFilter QO
  • 导入Unity 2D Animation插件没有生效

    导入Unity 2D Animation后 打开Sprite Editor发现没有Skinning Editor选项 这可能是因为导入插件后与项目原有的Plugin下的库产生冲突导致的 这时候点击Packages Editor Unity
  • 线程和线程池

    线程和线程池 并发和并行 并发是指在单个CPU核心上 多个线程占用不同的CPU时间片 但由于每个线程占用的CPU时间片非常短 比如10ms 看起来就像是多个线程在共同执行一样 但在微观物理上看其实还是串行执行的 这样的场景称作并发 并行是指
  • QQ小程序服务器内部错误,网络

    网络 使用说明 在小程序 小游戏中使用网络相关的 API 时 需要注意下列问题 请开发者提前了解 1 服务器域名配置 每个QQ小程序需要事先设置一个通讯域名 小程序只可以跟指定的域名与进行网络通信 包括普通 HTTPS 请求 qq requ
  • 工作队列(workqueue)

    转载于 http blog csdn net angle birds article details 8448070 项目需要 在驱动模块里用内核计时器timer list实现了一个状态机 郁闷的是 运行时总报错 Scheduling wh