Linux内核之tasklet机制

tasklet机制用于queue up work等到未来某时执行。tasklet可以并行执行,但同一个tasklet在某一时刻只能运行于某一CPU上。为了cache优化,每个tasklet只会在调度它的CPU上运行。

tasklet就好比一个既没有stack也没有context的小巧thread,它会快速执行完毕

概要

使用tasklet需要注意一些原则:

  1. tasklets是原子操作的,所以不能调用sleep()等阻塞函数以及mutexes(互斥量),semaphores(信号量)等,不过可以使用自旋锁(spinlock)
  2. A tasklet only runs on the same core (CPU) that schedules it
  3. Different tasklets can be running in parallel. But at the same time, a tasklet cannot be called concurrently with itself, as it runs on one CPU only
  4. tasklets的调度机制必须是non-preemptive调度器,一个接一个的执行,不能抢占。所以可以用normal 和 high两种优先级调度它

tasklet structure

Create tasklet

include/linux/interrupt.h头文件中定义

/* Tasklets --- multithreaded analogue of BHs.

   This API is deprecated. Please consider using threaded IRQs instead:
   https://lore.kernel.org/lkml/20200716081538.2sivhkj4hcyrusem@linutronix.de

   Main feature differing them of generic softirqs: tasklet
   is running only on one CPU simultaneously.

   Main feature differing them of BHs: different tasklets
   may be run simultaneously on different CPUs.

   Properties:
   * If tasklet_schedule() is called, then tasklet is guaranteed
     to be executed on some cpu at least once after this.
   * If the tasklet is already scheduled, but its execution is still not
     started, it will be executed only once.
   * If this tasklet is already running on another CPU (or schedule is called
     from tasklet itself), it is rescheduled for later.
   * Tasklet is strictly serialized wrt itself, but not
     wrt another tasklets. If client needs some intertask synchronization,
     he makes it with spinlocks.
 */

struct tasklet_struct
{
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
	bool use_callback;
	union {
		void (*func)(unsigned long data);
		void (*callback)(struct tasklet_struct *t);
	};
	unsigned long data;
};

#define DECLARE_TASKLET(name, _callback)		\
struct tasklet_struct name = {				\
	.count = ATOMIC_INIT(0),			\
	.callback = _callback,				\
	.use_callback = true,				\
}

#define DECLARE_TASKLET_DISABLED(name, _callback)	\
struct tasklet_struct name = {				\
	.count = ATOMIC_INIT(1),			\
	.callback = _callback,				\
	.use_callback = true,				\
}

DECLARE_TASKLET()宏创建了一个tasklet结构体,并赋值其enable(count值为0表示enable,count值为非0,表示tasklet是disabled)。如果使用DECLARE_TASKLET_DISABLE()宏创建tasklet,该tasklet可以被调度,但其实disable的,需要enable之后才能实际运行。

Eanble/Disable
static inline void tasklet_disable(struct tasklet_struct *t)
{
	tasklet_disable_nosync(t);
	tasklet_unlock_wait(t);
	smp_mb();
}

static inline void tasklet_enable(struct tasklet_struct *t)
{
	smp_mb__before_atomic();
	atomic_dec(&t->count);
}

如果tasklet是disabled状态的,它是可以被加入queue然后被schedule的,但不会在CPU上运行,直至它被enable之后才会实际运行。如果某个tasklet被disable了n次, 那么对应的也需要enable它n次(count会计数),它才能真正被enabled

Schedule tasklet

当调度tasklet时,tasklet会被放入队列,一共是两种队列,normal和high两种优先级,单链表结构组织,每个CPU都有它自己的tasklet队列。

  • normal priority
  • high prioriy
tasklet_schedule

调度tasklet使用normal优先级

include/linux/interrupt.h

enum
{
	TASKLET_STATE_SCHED,	/* Tasklet is scheduled for execution */
	TASKLET_STATE_RUN	/* Tasklet is running (SMP only) */
};

static inline void tasklet_schedule(struct tasklet_struct *t)
{
	if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
		__tasklet_schedule(t);
}

kernel/softirq.c

/*
 * This function must run with irqs disabled!
 */
inline void raise_softirq_irqoff(unsigned int nr)
{
	__raise_softirq_irqoff(nr);

	/*
	 * If we're in an interrupt or softirq, we're done
	 * (this also catches softirq-disabled code). We will
	 * actually run the softirq once we return from
	 * the irq or softirq.
	 *
	 * Otherwise we wake up ksoftirqd to make sure we
	 * schedule the softirq soon.
	 */
	if (!in_interrupt() && should_wake_ksoftirqd())  /*会判断是否在中断环境中*/
		wakeup_softirqd();
}

static void __tasklet_schedule_common(struct tasklet_struct *t,
				      struct tasklet_head __percpu *headp,
				      unsigned int softirq_nr)
{
	struct tasklet_head *head;
	unsigned long flags;

	local_irq_save(flags);
	head = this_cpu_ptr(headp);
	t->next = NULL;
	*head->tail = t;
	head->tail = &(t->next);
	raise_softirq_irqoff(softirq_nr);
	local_irq_restore(flags);
}

void __tasklet_schedule(struct tasklet_struct *t)
{
	__tasklet_schedule_common(t, &tasklet_vec,
				  TASKLET_SOFTIRQ);
}
EXPORT_SYMBOL(__tasklet_schedule);
tasklet_schedule
__tasklet_schedule
__tasklet_schedule_common
raise_softirq_irqoff

调度tasklet最后是通过TASKLET_SOFTIRQ的软中断去触发ksoftirqd内核线程来完成调度。

tasklet_hi_schedule

调度tasklet使用high优先级

include/linux/interrupt.h

static inline void tasklet_hi_schedule(struct tasklet_struct *t)
{
	if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
		__tasklet_hi_schedule(t);
}

kernel/softirq.c

void __tasklet_hi_schedule(struct tasklet_struct *t)
{
	__tasklet_schedule_common(t, &tasklet_hi_vec,
				  HI_SOFTIRQ);
}
EXPORT_SYMBOL(__tasklet_hi_schedule);

调度tasklet进入high优先级调度队列是通过HI_SOFTIRQ软中断触发

Based on softirq

几个软中断定义在include/linux/interrupt.h

/* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
   frequency threaded job scheduling. For almost all the purposes
   tasklets are more than enough. F.e. all serial device BHs et
   al. should be converted to tasklets, not to softirqs.
 */

enum
{
	HI_SOFTIRQ=0,
	TIMER_SOFTIRQ,
	NET_TX_SOFTIRQ,
	NET_RX_SOFTIRQ,
	BLOCK_SOFTIRQ,
	IRQ_POLL_SOFTIRQ,
	TASKLET_SOFTIRQ,
	SCHED_SOFTIRQ,
	HRTIMER_SOFTIRQ,
	RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

	NR_SOFTIRQS
};
Kill tasklet
void tasklet_kill(struct tasklet_struct *t)
{
	if (in_interrupt())
		pr_notice("Attempt to kill tasklet from interrupt\n");

	while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
		wait_var_event(&t->state, !test_bit(TASKLET_STATE_SCHED, &t->state));

	tasklet_unlock_wait(t);
	tasklet_clear_sched(t);
}
EXPORT_SYMBOL(tasklet_kill);

示例

static method

通过DECLARE_TASKLET()宏构建tasklet

#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 <asm/hw_irq.h> 
 
#define IRQ_NO 11
 
void tasklet_fn(struct tasklet_struct *); 
/* Init the Tasklet by Static Method */
DECLARE_TASKLET(test_tasklet, tasklet_fn);
 
 
/*Tasklet Function*/
void tasklet_fn(struct tasklet_struct *t)
{
        printk(KERN_INFO "Executing Tasklet Function : tasklet count = %d\n", atomic_read(&t->count));
}
 
 
//Interrupt handler for IRQ 11. 
static irqreturn_t irq_handler(int irq,void *dev_id) {
        printk(KERN_INFO "Shared IRQ: Interrupt Occurred");
        /*Scheduling Task to Tasklet*/
        tasklet_schedule(&test_tasklet); 
        
        return IRQ_HANDLED;
}
 
volatile int tasklet_value = 0;
 
dev_t dev = 0;
static struct class *dev_class;
static struct cdev tasklet_cdev;
struct kobject *kobj_ref;
 
static int __init tasklet_driver_init(void);
static void __exit tasklet_driver_exit(void);
 
/*************** Driver Functions **********************/
static int tasklet_open(struct inode *inode, struct file *file);
static int tasklet_release(struct inode *inode, struct file *file);
static ssize_t tasklet_read(struct file *filp, 
                char __user *buf, size_t len,loff_t * off);
static ssize_t tasklet_write(struct file *filp, 
                const char *buf, size_t len, loff_t * off);
 
/*************** Sysfs Functions **********************/
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 tasklet_attr = __ATTR(tasklet_value, 0660, sysfs_show, sysfs_store);
 
/*
** File operation sturcture
*/
static struct file_operations fops =
{
        .owner          = THIS_MODULE,
        .read           = tasklet_read,
        .write          = tasklet_write,
        .open           = tasklet_open,
        .release        = tasklet_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", tasklet_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",&tasklet_value);
        return count;
}
/*
** This function will be called when we open the Device file
*/  
static int tasklet_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 tasklet_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 tasklet_read(struct file *filp, 
                char __user *buf, size_t len, loff_t *off)
{
        struct irq_desc *desc;
        printk(KERN_INFO "Read function\n");
        desc = irq_to_desc(11);
        if (!desc)
        {
            return -EINVAL;
        }
        __this_cpu_write(vector_irq[59], desc);
        asm("int $0x3B");  // Corresponding to irq 11
        return 0;
}
/*
** This function will be called when we write the Device file
*/
static ssize_t tasklet_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 tasklet_driver_init(void)
{
        /*Allocating Major number*/
        if((alloc_chrdev_region(&dev, 0, 1, "tasklet_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(&tasklet_cdev,&fops);
 
        /*Adding character device to the system*/
        if((cdev_add(&tasklet_cdev,dev,1)) < 0){
            printk(KERN_INFO "Cannot add the device to the system\n");
            goto r_class;
        }
 
        /*Creating struct class*/
        if((dev_class = class_create(THIS_MODULE,"tasklet_class")) == NULL){
            printk(KERN_INFO "Cannot create the struct class\n");
            goto r_class;
        }
 
        /*Creating device*/
        if((device_create(dev_class,NULL,dev,NULL,"tasklet_device")) == NULL){
            printk(KERN_INFO "Cannot create the Device 1\n");
            goto r_device;
        }
 
        /*Creating a directory in /sys/kernel/ */
        kobj_ref = kobject_create_and_add("tasklet_sysfs",kernel_kobj);
 
        /*Creating sysfs file for tasklet_value*/
        if(sysfs_create_file(kobj_ref,&tasklet_attr.attr)){
                printk(KERN_INFO"Cannot create sysfs file......\n");
                goto r_sysfs;
        }
        if (request_irq(IRQ_NO, irq_handler, IRQF_SHARED, "tasklet_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, &tasklet_attr.attr);
 
r_device:
        class_destroy(dev_class);
r_class:
        unregister_chrdev_region(dev,1);
        cdev_del(&tasklet_cdev);   
        return -1;
}
/*
** Module exit function
*/  
static void __exit tasklet_driver_exit(void)
{
        /*Kill the Tasklet */ 
        tasklet_kill(&test_tasklet);
        free_irq(IRQ_NO,(void *)(irq_handler));
        kobject_put(kobj_ref); 
        sysfs_remove_file(kernel_kobj, &tasklet_attr.attr);
        device_destroy(dev_class,dev);
        class_destroy(dev_class);
        cdev_del(&tasklet_cdev);
        unregister_chrdev_region(dev, 1);
        printk(KERN_INFO "Device Driver Remove...Done!!!\n");
}
 
module_init(tasklet_driver_init);
module_exit(tasklet_driver_exit);
 
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple device driver - Tasklet Static");
MODULE_VERSION("1.15");
dynamic method

通过tasklet_init()函数构建tasklet

#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 <asm/hw_irq.h>
 
 
#define IRQ_NO 11
 
void tasklet_fn(unsigned long); 
/* Tasklet by Dynamic Method */
struct tasklet_struct *dynamic_tasklet = NULL;
 
 
/*Tasklet Function*/
void tasklet_fn(unsigned long arg)
{
        printk(KERN_INFO "Executing Tasklet Function : arg = %ld\n", arg);
}
 
 
//Interrupt handler for IRQ 11. 
static irqreturn_t irq_handler(int irq,void *dev_id) {
        printk(KERN_INFO "Shared IRQ: Interrupt Occurred");
        /*Scheduling Task to Tasklet*/
        tasklet_schedule(dynamic_tasklet); 
        
        return IRQ_HANDLED;
}
 
 
volatile int tasklet_value = 0;
 
 
dev_t dev = 0;
static struct class *dev_class;
static struct cdev tasklet_cdev;
struct kobject *kobj_ref;
 
static int __init tasklet_driver_init(void);
static void __exit tasklet_driver_exit(void);
 
/*************** Driver Functions **********************/
static int tasklet_open(struct inode *inode, struct file *file);
static int tasklet_release(struct inode *inode, struct file *file);
static ssize_t tasklet_read(struct file *filp, 
                char __user *buf, size_t len,loff_t * off);
static ssize_t tasklet_write(struct file *filp, 
                const char *buf, size_t len, loff_t * off);
 
/*************** Sysfs Functions **********************/
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 tasklet_attr = __ATTR(tasklet_value, 0660, sysfs_show, sysfs_store);
/*
** File operation sturcture
*/
static struct file_operations fops =
{
        .owner          = THIS_MODULE,
        .read           = tasklet_read,
        .write          = tasklet_write,
        .open           = tasklet_open,
        .release        = tasklet_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", tasklet_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",&tasklet_value);
        return count;
}
/*
** This function will be called when we open the Device file
*/   
static int tasklet_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 tasklet_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 tasklet_read(struct file *filp, 
                char __user *buf, size_t len, loff_t *off)
{
        struct irq_desc *desc;
        printk(KERN_INFO "Read function\n");
        desc = irq_to_desc(11);
        if (!desc)
        {
            return -EINVAL;
        }
        __this_cpu_write(vector_irq[59], desc);
        asm("int $0x3B");  // Corresponding to irq 11
        return 0;
}
/*
** This function will be called when we write the Device file
*/
static ssize_t tasklet_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 tasklet_driver_init(void)
{
        /*Allocating Major number*/
        if((alloc_chrdev_region(&dev, 0, 1, "tasklet_Dev_dynamic")) <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(&tasklet_cdev,&fops);
 
        /*Adding character device to the system*/
        if((cdev_add(&tasklet_cdev,dev,1)) < 0){
            printk(KERN_INFO "Cannot add the device to the system\n");
            goto r_class;
        }
 
        /*Creating struct class*/
        if((dev_class = class_create(THIS_MODULE,"tasklet_class_dynamic")) == NULL){
            printk(KERN_INFO "Cannot create the struct class\n");
            goto r_class;
        }
 
        /*Creating device*/
        if((device_create(dev_class,NULL,dev,NULL,"tasklet_device_dynamic")) == NULL){
            printk(KERN_INFO "Cannot create the Device 1\n");
            goto r_device;
        }
 
        /*Creating a directory in /sys/kernel/ */
        kobj_ref = kobject_create_and_add("tasklet_sysfs_dynamic",kernel_kobj);
 
        /*Creating sysfs file for tasklet_value*/
        if(sysfs_create_file(kobj_ref,&tasklet_attr.attr)){
                printk(KERN_INFO"Cannot create sysfs file......\n");
                goto r_sysfs;
        }
        if (request_irq(IRQ_NO, irq_handler, IRQF_SHARED, "tasklet_device_dynamic", (void *)(irq_handler))) {
            printk(KERN_INFO "tasklet_device_dynamic: cannot register IRQ ");
            goto irq;
        }
        /* Init the tasklet bt Dynamic Method */
        dynamic_tasklet  = kmalloc(sizeof(struct tasklet_struct),GFP_KERNEL);
        if(dynamic_tasklet == NULL) {
            printk(KERN_INFO "tasklet_device_dynamic: cannot allocate Memory");
            goto irq;
        }
        tasklet_init(dynamic_tasklet,tasklet_fn,0);
 
        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, &tasklet_attr.attr);
 
r_device:
        class_destroy(dev_class);
r_class:
        unregister_chrdev_region(dev,1);
        cdev_del(&tasklet_cdev);        
        return -1;
}
/*
** Module exit function
*/ 
static void __exit tasklet_driver_exit(void)
{
        /* Kill the Tasklet */ 
        tasklet_kill(dynamic_tasklet);
        if(dynamic_tasklet != NULL)
        {
          kfree(dynamic_tasklet);
        }
        free_irq(IRQ_NO,(void *)(irq_handler));
        kobject_put(kobj_ref); 
        sysfs_remove_file(kernel_kobj, &tasklet_attr.attr);
        device_destroy(dev_class,dev);
        class_destroy(dev_class);
        cdev_del(&tasklet_cdev);
        unregister_chrdev_region(dev, 1);
        printk(KERN_INFO "Device Driver Remove...Done!!!\n");
}
 
module_init(tasklet_driver_init);
module_exit(tasklet_driver_exit);
 
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple device driver - Tasklet Dynamic");
MODULE_VERSION("1.16");

reference

Linux Device Driver Tutorial – ch20~21

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值