中断的上半部执行紧要的任务,下半部则可以处理数据等次要的任务。tasklet也是基于softirq实现的。
不同于tasklet的是,softirq是kernel编译时静态分配的,所以如果动态创建softirq或者kill softirqs,需要修改内核代码,再编译替换掉内核。
概要
修改内核
如果想编写内核模块动态的使用softirq,需要修改内核导出一些函数符号才能使用,并且添加一个软中断TEST_SOFTIRQ
。
diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h
index ee8299eb1..7a249b64e 100644
--- a/include/linux/interrupt.h
+++ b/include/linux/interrupt.h
@@ -537,6 +537,7 @@ enum
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
+ TEST_SOFTIRQ, /* manually added for study kernel softirqs */
NR_SOFTIRQS
};
diff --git a/kernel/softirq.c b/kernel/softirq.c
index 09229ad82..887dd60d3 100644
--- a/kernel/softirq.c
+++ b/kernel/softirq.c
@@ -469,6 +469,7 @@ inline void raise_softirq_irqoff(unsigned int nr)
if (!in_interrupt())
wakeup_softirqd();
}
+EXPORT_SYMBOL(raise_softirq_irqoff);
void raise_softirq(unsigned int nr)
{
@@ -478,6 +479,7 @@ void raise_softirq(unsigned int nr)
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}
+EXPORT_SYMBOL(raise_softirq);
void __raise_softirq_irqoff(unsigned int nr)
{
@@ -490,6 +492,7 @@ void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
+EXPORT_SYMBOL(open_softirq);
/*
* Tasklets
softirq structure
内核中softirqs在代码层面使用结构体softirq_action来表示。
/* softirq mask and active fields moved to irq_cpustat_t in
* asm/hardirq.h to get better cache usage. KAO
*/
struct softirq_action
{
void (*action)(struct softirq_action *);
};
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
};
然后kernel/softirq.c
中定义了数组
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
其中NR_SOFTIRQS是enum枚举中定义的,所以如果需要增加softirq,就需要修改enum枚举变量。不过最多注册32个softirqs。
定义softirq
需要修改enum枚举,include/linux/interrupt.h
中,数值越小优先级越高,HI_SOFTIRQ优先级最高。
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 */
TEST_SOFTIRQ, /* manually added for study kernel softirqs */
NR_SOFTIRQS
};
这里也可以看出softirq的优先级,NET TX/RX > TASKLET > SCHED
。
创建软中断处理函数
需要按照以下参数格式创建,当触发软中断,kernel去调用对应的handler函数。
void softirq_handler(struct softirq_action *);
比如下面这个handler处理函数,树莓派上的GPIO操作。
/*
** This function is the softirq handler. We are toggling the LED.
*/
static void gpio_interrupt_softirq_handler(struct softirq_action *action)
{
led_toggle = (0x01 ^ led_toggle); // toggle the old value
gpio_set_value(GPIO_21_OUT, led_toggle); // toggle the GPIO_21_OUT
pr_info("Interrupt Occurred : GPIO_21_OUT : %d ",gpio_get_value(GPIO_21_OUT));
}
触发软中断
大名鼎鼎的**raise_softirq()**函数终于来了。参数nr
就是softirq入口,这里就是定义的TEST_SOFTIRQ
。
void raise_softirq(unsigned int nr)
{
unsigned long flags;
local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}
这里local_irq_save()会保存当前中断的开关状态,然后关闭当前处理的中断,然后local_irq_resoter()会恢复之前的中断状态(开或者关)。
raise_softirq()
调用之后会将TEST_SOFTIRQ挂起(pending),所以softirq handler处理函数将会在处理器下次调用do_softirq()
函数时才会被调用。如果interrupt确认已经被关了,可以直接使用下面的函数去触发软中断。
void __raise_softirq_irqoff(unsigned int nr)
{
lockdep_assert_irqs_disabled();
trace_softirq_raise(nr);
or_softirq_pending(1UL << nr);
}
softirq handler何时会被调用
当调用了raise_softirq()
或者raise_softirq_irqoff()
函数之后,SOFTIRQ(这里是TEST_SOFTIRQ)将被mark为pending状态(挂起),pending状态的softirq将会在以下几个场景中被执行:
- 硬中断代码处理返回(the return from hardware interrupt code(ISR))
- 内核ksoftirq线程
- 任何显性检查或执行pending状态的softirq代码,比如networking subsystem
一些建议
- 一个软中断永远不会抢占另一个软中断,事实上,唯一能抢占软中断的只有硬中断处理函数(interrupt handler)。同样的softirq可以在不同的处理器上被执行。
- softirqs一般是下半部中需要处理对时间比较苛刻的任务或者一些比较重要的任务。
- softirqs一般是在interrupt handlers中才会被raise触发。
示例
#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/delay.h>
#include <linux/uaccess.h> //copy_to/from_user()
#include <linux/gpio.h> //GPIO
#include <linux/interrupt.h>
/* Since debounce is not supported in Raspberry pi, I have addded this to disable
** the false detection (multiple IRQ trigger for one interrupt).
** Many other hardware supports GPIO debounce, I don't want care about this even
** if this has any overhead. Our intention is to explain the GPIO interrupt.
** If you want to disable this extra coding, you can comment the below macro.
** This has been taken from : https://raspberrypi.stackexchange.com/questions/8544/gpio-interrupt-debounce
**
** If you want to use Hardaware Debounce, then comment this EN_DEBOUNCE.
**
*/
#define EN_DEBOUNCE
#ifdef EN_DEBOUNCE
#include <linux/jiffies.h>
extern unsigned long volatile jiffies;
unsigned long old_jiffie = 0;
#endif
//LED is connected to this GPIO
#define GPIO_21_OUT (21)
//LED is connected to this GPIO
#define GPIO_25_IN (25)
//GPIO_25_IN value toggle
unsigned int led_toggle = 0;
//This used for storing the IRQ number for the GPIO
unsigned int GPIO_irqNumber;
//Interrupt handler for GPIO 25. This will be called whenever there is a raising edge detected.
static irqreturn_t gpio_irq_handler(int irq,void *dev_id)
{
#ifdef EN_DEBOUNCE
unsigned long diff = jiffies - old_jiffie;
if (diff < 20)
{
return IRQ_HANDLED;
}
old_jiffie = jiffies;
#endif
/* Raise the softirq */
raise_softirq( EMBETRONICX_SOFT_IRQ );
return IRQ_HANDLED;
}
/*
** This fuction is the softirq handler
*/
static void gpio_interrupt_softirq_handler(struct softirq_action *action)
{
led_toggle = (0x01 ^ led_toggle); // toggle the old value
gpio_set_value(GPIO_21_OUT, led_toggle); // toggle the GPIO_21_OUT
pr_info("Interrupt Occurred : GPIO_21_OUT : %d ",gpio_get_value(GPIO_21_OUT));
}
dev_t dev = 0;
static struct class *dev_class;
static struct cdev etx_cdev;
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);
/******************************************************/
//File operation structure
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 open the Device file
*/
static int etx_open(struct inode *inode, struct file *file)
{
pr_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)
{
pr_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)
{
uint8_t gpio_state = 0;
//reading GPIO value
gpio_state = gpio_get_value(GPIO_21_OUT);
//write to user
len = 1;
if( copy_to_user(buf, &gpio_state, len) > 0) {
pr_err("ERROR: Not all the bytes have been copied to user\n");
}
pr_info("Read function : GPIO_21 = %d \n", gpio_state);
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)
{
uint8_t rec_buf[10] = {0};
if( copy_from_user( rec_buf, buf, len ) > 0) {
pr_err("ERROR: Not all the bytes have been copied from user\n");
}
pr_info("Write Function : GPIO_21 Set = %c\n", rec_buf[0]);
if (rec_buf[0]=='1') {
//set the GPIO value to HIGH
gpio_set_value(GPIO_21_OUT, 1);
} else if (rec_buf[0]=='0') {
//set the GPIO value to LOW
gpio_set_value(GPIO_21_OUT, 0);
} else {
pr_err("Unknown command : Please provide either 1 or 0 \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){
pr_err("Cannot allocate major number\n");
goto r_unreg;
}
pr_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){
pr_err("Cannot add the device to the system\n");
goto r_del;
}
/*Creating struct class*/
if((dev_class = class_create(THIS_MODULE,"etx_class")) == NULL){
pr_err("Cannot create the struct class\n");
goto r_class;
}
/*Creating device*/
if((device_create(dev_class,NULL,dev,NULL,"etx_device")) == NULL){
pr_err( "Cannot create the Device \n");
goto r_device;
}
//Output GPIO configuration
//Checking the GPIO is valid or not
if(gpio_is_valid(GPIO_21_OUT) == false){
pr_err("GPIO %d is not valid\n", GPIO_21_OUT);
goto r_device;
}
//Requesting the GPIO
if(gpio_request(GPIO_21_OUT,"GPIO_21_OUT") < 0){
pr_err("ERROR: GPIO %d request\n", GPIO_21_OUT);
goto r_gpio_out;
}
//configure the GPIO as output
gpio_direction_output(GPIO_21_OUT, 0);
//Input GPIO configuratioin
//Checking the GPIO is valid or not
if(gpio_is_valid(GPIO_25_IN) == false){
pr_err("GPIO %d is not valid\n", GPIO_25_IN);
goto r_gpio_in;
}
//Requesting the GPIO
if(gpio_request(GPIO_25_IN,"GPIO_25_IN") < 0){
pr_err("ERROR: GPIO %d request\n", GPIO_25_IN);
goto r_gpio_in;
}
//configure the GPIO as input
gpio_direction_input(GPIO_25_IN);
/*
** I have commented the below few lines, as gpio_set_debounce is not supported
** in the Raspberry pi. So we are using EN_DEBOUNCE to handle this in this driver.
*/
#ifndef EN_DEBOUNCE
//Debounce the button with a delay of 200ms
if(gpio_set_debounce(GPIO_25_IN, 200) < 0){
pr_err("ERROR: gpio_set_debounce - %d\n", GPIO_25_IN);
//goto r_gpio_in;
}
#endif
//Get the IRQ number for our GPIO
GPIO_irqNumber = gpio_to_irq(GPIO_25_IN);
pr_info("GPIO_irqNumber = %d\n", GPIO_irqNumber);
if (request_irq(GPIO_irqNumber, //IRQ number
(void *)gpio_irq_handler, //IRQ handler
IRQF_TRIGGER_RISING, //Handler will be called in raising edge
"etx_device", //used to identify the device name using this IRQ
NULL)) { //device id for shared IRQ
pr_err("my_device: cannot register IRQ ");
goto r_gpio_in;
}
/* Assign gpio_interrupt_softirq_handler to the EMBETRONICX_SOFT_IRQ */
open_softirq( EMBETRONICX_SOFT_IRQ, gpio_interrupt_softirq_handler );
pr_info("Device Driver Insert...Done!!!\n");
return 0;
r_gpio_in:
gpio_free(GPIO_25_IN);
r_gpio_out:
gpio_free(GPIO_21_OUT);
r_device:
device_destroy(dev_class,dev);
r_class:
class_destroy(dev_class);
r_del:
cdev_del(&etx_cdev);
r_unreg:
unregister_chrdev_region(dev,1);
return -1;
}
/*
** Module exit function
*/
static void __exit etx_driver_exit(void)
{
free_irq(GPIO_irqNumber,NULL);
gpio_free(GPIO_25_IN);
gpio_free(GPIO_21_OUT);
device_destroy(dev_class,dev);
class_destroy(dev_class);
cdev_del(&etx_cdev);
unregister_chrdev_region(dev, 1);
pr_info("Device Driver Remove...Done!!\n");
}
module_init(etx_driver_init);
module_exit(etx_driver_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple device driver - SoftIRQ (GPIO Interrupt) ");
MODULE_VERSION("1.42");
reference
Linux Device Driver Tutorial – ch45