一、tasklet是什么?
tasklet是利用軟中斷實現的一種下半部機制。tasklet相比於軟中斷,其接口更加簡單方便,鎖保護要求較低。 tasklet由tasklet_struct結構體表示:
struct tasklet_struct
{
struct tasklet_struct *next; //鏈表中下一個tasklet
unsigned long state; //tasklet狀態
atomic_t count; //引用計數
void (*func)(unsigned long); //tasklet處理函數
unsigned long data; //給tasklet處理函數的參數
};
二、tasklet分类
tasklet還分為了高優先級tasklet與一般tasklet,前面分析軟中斷時softirq_init()註冊的兩個tasklet軟中斷。
void __init softirq_init(void)
{
......
//此處註冊兩個軟中斷
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
......
}
其處理函數分別為 tasklet_action()和tasklet_hi_action()。
三、tasklet_action函数
static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
local_irq_disable();
list = __get_cpu_var(tasklet_vec).head;
__get_cpu_var(tasklet_vec).head = NULL;
__get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head;
local_irq_enable();
while (list) {
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) { //判断TASKLET_STATE_RUN标记
if (!atomic_read(&t->count)) { //t->count為零才會調用task_struct裡的函數
if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
BUG();
t->func(t->data); //設置了TASKLET_STATE_SCHED標誌才會被遍歷到鏈表上對應的函數
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = NULL;
*__get_cpu_var(tasklet_vec).tail = t;
__get_cpu_var(tasklet_vec).tail = &(t->next);
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_enable();
}
}
四、tasklet_hi_action函数
static void tasklet_hi_action(struct softirq_action *a)
{
struct tasklet_struct *list;
local_irq_disable();
list = __get_cpu_var(tasklet_hi_vec).head;
__get_cpu_var(tasklet_hi_vec).head = NULL;
__get_cpu_var(tasklet_hi_vec).tail = &__get_cpu_var(tasklet_hi_vec).head;
local_irq_enable();
while (list) {
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) { //判断TASKLET_STATE_RUN标记
if (!atomic_read(&t->count)) {
if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
BUG();
t->func(t->data);
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = NULL;
*__get_cpu_var(tasklet_hi_vec).tail = t;
__get_cpu_var(tasklet_hi_vec).tail = &(t->next);
__raise_softirq_irqoff(HI_SOFTIRQ);
local_irq_enable();
}
}
五、tasklet_action和tasklet_hi_action主要逻辑
這兩個函數主要是做了如下動作:
1.禁止中斷,併為當前處理器檢索tasklet_vec或tasklet_hi_vec鏈表。
2.將當前處理器上的該鏈表設置為NULL,達到清空的效果。
3.循環遍歷獲得鏈表上的每一個待處理的tasklet。
4.如果是多處理器系統,通過檢查TASKLET_STATE_RUN來判斷這個tasklet是否正在其他處理器上運行。如果它正在運行,那麼現在就不要執行,跳 到下一個待處理的tasklet去。
5.如果當前這個tasklet沒有執行,將其狀態設置為TASKLETLET_STATE_RUN,這樣別的處理器就不會再去執行它了。
6.檢查count值是否為0,確保tasklet沒有被禁止。如果tasklet被禁止,則跳到下一個掛起的tasklet去。
7.現在可以確定這個tasklet沒有在其他地方執行,並且被我們設置為執行狀態,這樣它在其他部分就不會被執行,並且引用計數器為0,現在可以執行tasklet的處理程序了。
8.重複執行下一個tasklet,直至沒有剩餘的等待處理的tasklets。
六、如何注册使用tasklet
一般情況下,都是用tasklet來實現下半部,tasklet可以動態創建、使用方便、執行速度快。下面來看一下如何創建自己的tasklet呢?
1. 第一步,聲明自己的tasklet。
既可以靜態也可以動態創建,這取決於選擇是想有一個對tasklet的直接引用還是間接引用。靜態創建方法(直接引用),可以使用下列兩個宏的一個(在linux/interrupt.h中定義):
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
DECLARE_TASKLET(name,func,data)
DECLARE_TASKLET_DISABLED(name,func,data)
這兩個宏之間的區別在於引用計數器的初始值不同,前面一個把創建的tasklet的引用計數器設置為0,使其處於激活狀態,另外一個將其設置為1,處於禁止狀態。而動態創建(間接引用)的方式如下: tasklet_init(t,tasklet_handler,dev); 其實現代碼為:
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->next = NULL;
t->state = 0;
atomic_set(&t->count, 0);
t->func = func;
t->data = data;
}
2. 第二步,编写tasklet处理程序
tasklet處理函數類型是void tasklet_handler(unsigned long data)。因為是靠軟中斷實現,所以tasklet不能休眠,也就是說不能在tasklet中使用信號量或者其他什麼阻塞式的函數。由於tasklet 運行時允許響應中斷,所以必須做好預防工作,如果新加入的tasklet和中斷處理程序之間共享了某些數據額的話。兩個相同的tasklet絕不能同時執 行,如果新加入的tasklet和其他的tasklet或者軟中斷共享了數據,就必須要進行適當地鎖保護。
3. 第三步,调度tasklet
調用tasklet_schedule()(或tasklet_hi_schedule())函數,tasklet就會進入掛起狀態以便執行。如果在還沒有得到運行機會之前,如果有一個相同的tasklet又被調度了,那麼它仍然只會運行一次。如果這時已經開始運行,那麼這個新的tasklet會被重新調度並再次運行。一種優化策略是一個tasklet總在調度它的處理器上執行。
調用tasklet_disable()來禁止某個指定的 tasklet,如果該tasklet當前正在執行,這個函數會等到它執行完畢再返回。調用tasklet_disable_nosync()也是來禁止 的,只是不用在返回前等待tasklet執行完畢,這麼做不太安全,因為沒法估計該tasklet是否仍在執行。 tasklet_enable()激活一個tasklet。可以使用tasklet_kill()函數從掛起的對列中去掉一個tasklet。這個函數會 首先等待該tasklet執行完畢,然後再將其移去。當然,沒有什麼可以阻止其他地方的代碼重新調度該tasklet。由於該函數可能會引起休眠,所以禁止在中斷上下文中使用它。
static inline void tasklet_schedule(struct tasklet_struct* t)
{
//檢查tasklet的狀態是否為TASKLET_STATE_SCHED.如果是,說明tasklet已經被調度過了,函數返回。
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
__tasklet_schedule(t);
}
}
void __tasklet_schedule(struct tasklet_struct* t)
{
unsigned long flags;
//保存中斷狀態,然後禁止本地中斷。在執行tasklet代碼時,這麼做能夠保證處理器上的數據不會弄亂。
local_irq_save(flags);
//把需要調度的tasklet加到每個處理器一個的tasklet_vec鏈表或task_hi_vec鏈表的表頭上去。
t->next = NULL;
*__get_cpu_var(tasklet_vec).tail = t;
__get_cpu_var(tasklet_vec).tail = &(t->next);
//喚起TASKLET_SOFTIRQ或HI_SOFTIRQ軟中斷,這樣在下一次調用do_softirq()時就會執行該tasklet。
raise_softirq_irqoff(TASKLET_SOFTIRQ);
//恢復中斷到原狀態並返回。
local_irq_restore(flags);
}
七、tasklet实例——静态方法
/***************************************************************************//**
* \file driver.c
*
* \details Simple linux driver (Tasklet Static method)
*
* \author EmbeTronicX
*
* *******************************************************************************/
#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>
#define IRQ_NO 11
void tasklet_fn(unsigned long);
/* Init the Tasklet by Static Method */
DECLARE_TASKLET(tasklet,tasklet_fn, 1);
/*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(&tasklet);
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;
static int __init etx_driver_init(void);
static void __exit etx_driver_exit(void);
/*************** Driver Functions **********************/
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 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 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 sysfs 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((dev_class = class_create(THIS_MODULE,"etx_class")) == NULL){
printk(KERN_INFO "Cannot create the struct class\n");
goto r_class;
}
/*Creating device*/
if((device_create(dev_class,NULL,dev,NULL,"etx_device")) == NULL){
printk(KERN_INFO "Cannot create the Device 1\n");
goto r_device;
}
/*Creating a directory in /sys/kernel/ : kernel_kobj是kernel内定义的的一个kobj类型结构*/
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)
{
/*Kill the Tasklet */
tasklet_kill(&tasklet);
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("A simple device driver - Tasklet Static");
MODULE_VERSION("1.15");
obj-m += tasklet_tdriver.o
KDIR = /home/ldeng/Documents/runninglinuxkernel_4.0
all:
make -C $(KDIR) M=$(shell pwd) modules
clean:
make -C $(KDIR) M=$(shell pwd) clean
这个例子比较综合,涉及以下知识点:
1.字符设备驱动框架
2.共享中断以及中断注册
3.tasklet注册和触发
4.sysfs文件注册和读写
八、tasklet实例——动态方法
/****************************************************************************//**
* \file driver.c
*
* \details Simple linux driver (Tasklet Dynamic method)
*
* \author EmbeTronicX
*
* *******************************************************************************/
#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>
#define IRQ_NO 11
void tasklet_fn(unsigned long);
/* Tasklet by Dynamic Method */
struct tasklet_struct *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(tasklet);
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;
static int __init etx_driver_init(void);
static void __exit etx_driver_exit(void);
/*************** Driver Functions **********************/
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 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 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((dev_class = class_create(THIS_MODULE,"etx_class")) == NULL){
printk(KERN_INFO "Cannot create the struct class\n");
goto r_class;
}
/*Creating device*/
if((device_create(dev_class,NULL,dev,NULL,"etx_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("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 "etx_device: cannot register IRQ ");
goto irq;
}
/* Init the tasklet bt Dynamic Method */
tasklet = kmalloc(sizeof(struct tasklet_struct),GFP_KERNEL);
if(tasklet == NULL) {
printk(KERN_INFO "etx_device: cannot allocate Memory");
goto irq;
}
tasklet_init(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, &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)
{
/* Kill the Tasklet */
tasklet_kill(tasklet);
if(tasklet != NULL)
{
kfree(tasklet);
}
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("A simple device driver - Tasklet Dynamic");
MODULE_VERSION("1.16");