work queue, kthread和timer对比

kernel中有三种方式可以启动一个异步作业:
work queue, kthread和timer。
这几种方式有各自的使用场景。
本文就这几种方式的用法和使用场景,进行了简单的总结。

workqueue, kthread, timer使用方法与使用场景对比:
1. queue_work
2. kthread_create()
3. add_timer

1. work queue
    参考链接:
        https://www.cnblogs.com/vedic/p/11069249.html
    创建一个work函数,在特定时机将其queue到队列中,以触发work函数被执行。

1.1 用法
    创建一个work queue:
        struct workqueue_struct *workqueue_test = alloc_workqueue("workqueue_test", 0, 0);
    初始化一个work,其中work_test_func是自己定义的work function:
        struct work_struct work_test;
        INIT_WORK(&work_test, work_test_func);
    将work添加到work queue中:
        queue_work(workqueue_test, &work_test);
    
    也可以使用系统预定义的work queue,好处是节省资源,不用再追加一个新的work queue定义;坏处是很多work可能都会挂到这个work queue上,自己的work响应不是那么及时。
    系统预定义的work queue:
        system_wq = alloc_workqueue("events", 0, 0);
        system_highpri_wq = alloc_workqueue("events_highpri", WQ_HIGHPRI, 0);
        system_long_wq = alloc_workqueue("events_long", 0, 0);
        system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND,
                            WQ_UNBOUND_MAX_ACTIVE);
        system_freezable_wq = alloc_workqueue("events_freezable",
                              WQ_FREEZABLE, 0);
        system_power_efficient_wq = alloc_workqueue("events_power_efficient",
                              WQ_POWER_EFFICIENT, 0);
        system_freezable_power_efficient_wq = alloc_workqueue("events_freezable_power_efficient",
                              WQ_FREEZABLE | WQ_POWER_EFFICIENT,
                              0);
    
    早期内核,会为每个work queue创建一个线程,处理其上的work。如果大家任意的创建work queue,就会导致线程过多的问题,导致资源浪费并影响效率。
    
    3.x内核之后,会为每个cpu创建两个worker pool,一个高优先级(nice -20),一个普通优先级(nice 0);并为online的cpu的每个worker pool创建一个worker线程。
    针对unbound和ordered的work queue,会为每个work queue创建一个woker pool,并为每个worker pool创建一个worker线程。
    
    也就是说,早期的内核,每个生产线,对应一个工人,负责处理生产线上的产品。
    后来,发现这样造成人力资源浪费,工人太多,但每个工人负载不高,或者不均衡。
    3.x之后,将生产线与工人进行剥离,中间追加了一个缓冲池。每个车间(cpu)负责出两个人,这两个人每人负责处理一个产品池的产品,各生产线的产品根据配置,到达不同的池子。
    当然,也有一部分特殊的生产线,比如特殊定制、加急的(unbound, ordered),针对这样的生产线,进行特殊照顾,每个生产线分配一个池子,并对应地分配一个工人。

1.2 使用场景
    一个独立的处理作业,可能会被执行多次,并且对时效性要求不高。
    特别是对于触发的多次作业,需要顺序执行情况。
    由于有单独的线程可以调度,所以work中可以睡眠。
    
2. kthread
    定义一个线程函数,并使用该函数创建一个线程,然后再唤醒线程,定义的线程函数就可以开始干活了。

2.1 用法
    创建线程:
        kthread_create
    唤醒线程:
        wake_up_process
    创建+唤醒线程:
        kthread_run
    停止线程:
        kthread_stop
        
    注意:
        1) 创建后的线程,必须显示唤醒,或者直接使用kthread_run。
        2) 线程函数中需要调用kthread_should_stop,以便其他线程调用kthread_stop后,本线程可以退出。
        3) 线程函数每次循环中,需要将cpu让出:schedule_timeout(HZ);
        4) 线程唤醒后会一直运行,除非被调用kthread_stop。
        5) 调用kthread_stop时,需要保证线程未退出,否则会Oops。
        6) 调用kthread_stop时,需要确保线程未结束运行,否则会一直等待,无法返回。

2.2 使用场景
    需要循环执行的作业,例如监控某个状态等。

3. timer
    参考链接:
        https://blog.csdn.net/jidonghui/article/details/7449546
        https://www.cnblogs.com/hjj801006/p/4551378.html
    定义一个timer结构体对象,初始化该结构体对象,包括timer到了要执行的函数,函数的参数,超时时间。
    然后将其添加到系统中。
    
    有两个概念需要说明一下,jiffies和HZ。
        系统中,不论更新时间还是进行调度,都需要时钟中断。时钟中断是怎样产生的呢,是有芯片内的计数器产生。
        时钟中断的产生频率是多少呢,是通过HZ来配置的。HZ的意思就是1秒钟产生的时钟中断的个数。
        也就是说,一个时钟周期是1/HZ。所以,这也是调度器的最小时间单位。
        jiffies是什么呢,是从启动到现在发生的时钟中断数量。
        timer的超时单位,就是一个时钟周期。

3.1 用法
    内核定时器的数据结构
    struct timer_list {
        struct list_head entry;
        unsigned long expires;    // 超时的时间点,以时钟中断数为单位
        void (*function)(unsigned long);
        unsigned long data;    // 需要传递给function的参数
        struct tvec_base *base;
        /* ... */
    };
    
    基本用法:
        定义结构体:
            struct timer_list mytimer;
        初始化结构体:
            init_timer(&mytimer);   
        给结构体成员赋值:
            mytimer ->timer.expires = jiffies + 5*HZ;
            mytimer ->timer.data = (unsigned long) dev;
            mytimer ->timer.function = &corkscrew_timer; /* timer handler */
        添加到系统:
            add_timer(&mytimer);
    使用初始化宏:
        DEFINE_TIMER(timer_name, function_name, expires_value, data);
        其中timer_name为后面可操作的结构体对象。
        添加到系统:
            add_timer(&timer_name);
    使用初始函数:
        struct timer_list mytimer;
        setup_timer(&mytimer, (*function)(unsigned long), unsigned long data);
        mytimer.expires = jiffies + 5*HZ;
        添加到系统:
            add_timer(&mytimer);
            
3.2 使用场景:
    对时间精度要求较高的作业。
    
3.3 注意点:
    1、是通过软中断实现,软中断的限制必须都满足,例如无进程上下文,不能睡眠等。
    2、timer运行一次之后,即被从timer list上删除。如果要反复运行的话,需要在其执行函数中,重新添加到timer list。
    3、在SMP系统中,timer总是在注册它的cpu上被运行,以尽可能的获取缓存的局域性。
    4、一个timer,无论是否有被执行过,都可以通过函数mod_timer,对其进行修改,并重新注册。
    5、通过函数del_timer / del_timer_sync(SMP)可以注销一个timer,如果一个timer已经执行,注销其实不做什么事情。另外,如果调用del_timer_sync时,timer正在另外一个cpu上运行,会等待其运行结束。
    6、当一个定时器函数即将要被运行前,内核会把相应的定时器从内核链表中删除(相当于注销)。

你可能感兴趣的:(Linux,Linux,Driver,linux)