linux内核中断实践4:软中断Softirq

前言

       本次实验也是在定时器处理函数中进行的,修改了Interrupt.h (include\linux)和文件Softirq.c (kernel)文件,用来增加新的软中断。

        实验结果可靠,没问题。        

        如果使用硬件中断的话需要配置设备树,然后从设备树获取irq号,然后就是使用request_irq函数。剩下的就都一样了。

Linux实现底半部的机制主要有tasklet、工作队列、软中断和线程化irq。

在proc文件系统中可以查询当前系统中的softirq,如下所示

root@hehe:/proc# cat softirqs
                    CPU0
          HI:          1
       TIMER:       8329
      NET_TX:          0
      NET_RX:        429
       BLOCK:      26691
BLOCK_IOPOLL:          0
     TASKLET:          0
       SCHED:        200
     HRTIMER:          0
         RCU:          0
      (null):       2833
root@hehe:/proc#

一 软中断(Softirq)

        软中断(Softirq)也是一种传统的底半部处理机制,它的执行时机通常是顶半部返回的时候,tasklet是基于软中断实现的,因此也运行于软中断上下文。

        在Linux内核中,用softirq_action结构体表征一个软中断,这个结构体包含软中断处理函数指针和传递给该函数的参数。使用open_softirq()函数可以注册软中断对应的处理函数,而raise_softirq()函数可以触发一个软中断。

/* 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 *);
};
extern void open_softirq(int nr, void (*action)(struct softirq_action *));
extern void raise_softirq(unsigned int nr);

    

/* 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,
    BLOCK_IOPOLL_SOFTIRQ,
    TASKLET_SOFTIRQ,
    SCHED_SOFTIRQ,
    HRTIMER_SOFTIRQ,
    RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

    NR_SOFTIRQS
};

        大概意思,也是不要使用softirqs,用tasklets就够了。

        软中断和tasklet运行于软中断上下文,仍然属于原子上下文的一种,而工作队列则运行于进程上下文。因此,在软中断和tasklet处理函数中不允许睡眠,而在工作队列处理函数中允许睡眠。local_bh_disable()和local_bh_enable()是内核中用于禁止和使能软中断及tasklet底半部机制的函数。

static inline void local_bh_disable(void)
{
	__local_bh_disable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);
}

/*
 * Special-case - softirqs can safely be enabled in
 * cond_resched_softirq(), or by __do_softirq(),
 * without processing still-pending softirqs:
 */
void _local_bh_enable(void)
{
	WARN_ON_ONCE(in_irq());
	__local_bh_enable(SOFTIRQ_DISABLE_OFFSET);
}
EXPORT_SYMBOL(_local_bh_enable);

static void __local_bh_enable(unsigned int cnt)
{
	WARN_ON_ONCE(!irqs_disabled());

	if (softirq_count() == (cnt & SOFTIRQ_MASK))
		trace_softirqs_on(_RET_IP_);
	preempt_count_sub(cnt);
}

/*
 * This one is for softirq.c-internal use,
 * where hardirqs are disabled legitimately:
 */
#ifdef CONFIG_TRACE_IRQFLAGS
void __local_bh_disable_ip(unsigned long ip, unsigned int cnt)
{
	unsigned long flags;

	WARN_ON_ONCE(in_irq());

	raw_local_irq_save(flags);
	/*
	 * The preempt tracer hooks into preempt_count_add and will break
	 * lockdep because it calls back into lockdep after SOFTIRQ_OFFSET
	 * is set and before current->softirq_enabled is cleared.
	 * We must manually increment preempt_count here and manually
	 * call the trace_preempt_off later.
	 */
	__preempt_count_add(cnt);
	/*
	 * Were softirqs turned off above:
	 */
	if (softirq_count() == (cnt & SOFTIRQ_MASK))
		trace_softirqs_off(ip);
	raw_local_irq_restore(flags);

	if (preempt_count() == cnt) {
#ifdef CONFIG_DEBUG_PREEMPT
		current->preempt_disable_ip = get_parent_ip(CALLER_ADDR1);
#endif
		trace_preempt_off(CALLER_ADDR0, get_parent_ip(CALLER_ADDR1));
	}
}
EXPORT_SYMBOL(__local_bh_disable_ip);
#endif /* CONFIG_TRACE_IRQFLAGS */

        内核中采用softirq的地方包括HI_SOFTIRQ、TIMER_SOFTIRQ、NET_TX_SOFTIRQ、NET_RX_SOFTIRQ、SCSI_SOFTIRQ、TASKLET_SOFTIRQ等,一般来说,驱动的编写者不会也不宜直接使用softirq。

schedule_work(&my_wq);     /* 调度工作队列执行 */

/**
 * schedule_work - put work task in global workqueue
 * @work: job to be done
 *
 * Returns %false if @work was already on the kernel-global workqueue and
 * %true otherwise.
 *
 * This puts a job in the kernel-global workqueue if it was not already
 * queued and leaves it in the same position on the kernel-global
 * workqueue otherwise.
 */
static inline bool schedule_work(struct work_struct *work)
{
	return queue_work(system_wq, work);
}

        与tasklet实践对应的使用工作队列处理中断底半部的设备驱动程序模板如下所示(仅包含与中断相关的部分)。

二 在一个定时器中测试工作队列实例

假如要设计自己的softirq

首先,在Interrupt.h (include\linux)文件,找到TIMER_SOFTIRQ定义的地方,在气候添加

MYTEST_SOFTIRQ,如下图

         再看看open_softirq函数原型:就是将传入的函数指针赋值非nr为索引的数组的原来的指针。假如这nr已经被赋值了,就覆盖了原来的值。假如我们给nr一个TIMER_SOFTIRQ,可能定时器就不能工作了,系统也可能会死掉。

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
	softirq_vec[nr].action = action;
}

softirq_vec就是软中断数组,这说明整个系统中最多,也就NR_SOFTIRQS数量个软中断。其中的MYTEST_SOFTIRQ是我刚加的。

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

	NR_SOFTIRQS
};
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

然后为了让软中断函数能工作,需要在定时器中断中调用:

raise_softirq(MYTEST_SOFTIRQ);

完整测试源码:csi_softirq.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>

#define DEBUG_SQ(format, ...)                                                                     \
  printk ("%s,line=%d:" format "\n", __func__, __LINE__, ##__VA_ARGS__)

struct cssq_dev_{
	struct timer_list timer;
};
struct cssq_dev_ global_cssq_dev;

void cssq_action(struct softirq_action *sq)
{
	static int count = 0;
	DEBUG_SQ("count = %d",++count);
}

static void cssq_timer_function(unsigned long arg)
{
	struct cssq_dev_ *cssq_dev = (struct cssq_dev_ *)arg;
	raise_softirq(MYTEST_SOFTIRQ);
	DEBUG_SQ("");
	mod_timer(&cssq_dev->timer,jiffies + msecs_to_jiffies(1000));
}

static int __init cssq_init(void)
{
	struct cssq_dev_ *cssq_dev = &global_cssq_dev;
	init_timer(&cssq_dev->timer);
	cssq_dev->timer.data = (unsigned long)cssq_dev;
	cssq_dev->timer.function = cssq_timer_function;	

	open_softirq(MYTEST_SOFTIRQ,cssq_action);
	
	mod_timer(&cssq_dev->timer,jiffies + msecs_to_jiffies(1000));
	DEBUG_SQ("init ok");	
	return 0;
}

static void __exit cssq_exit(void)
{
	struct cssq_dev_ *cssq_dev = &global_cssq_dev;
	del_timer_sync(&cssq_dev->timer);
	DEBUG_SQ("exit ok");	
}

module_init(cssq_init);
module_exit(cssq_exit);
MODULE_LICENSE("GPL");

Makefile文件

export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
KERN_DIR = /home/lkmao/imx/linux/linux-imx

FILE_NAME=csi_softirq
obj-m += $(FILE_NAME).o

all:
    make -C $(KERN_DIR) M=$(shell pwd) modules

.PHONY:clean
clean:
    make -C $(KERN_DIR) M=$(shell pwd) clean

测试:

        首先编译一遍内核,因为修改了Interrupt.h (include\linux)文件,这个编译的很久。并使用新编译的内核启动系统,前提的能起来。如果起不来,记得将Interrupt.h (include\linux)文件修改回原来的状态。就洗洗睡吧。

        系统如果成功启动了,执行make,编译自己的模块csi_softirq.ko。编译的时候有警告:

lkmao@ubuntu:/big/csi_driver/csi_softirq$ make
make -C /home/lkmao/imx/linux/linux-imx M=/big/csi_driver/csi_softirq modules
make[1]: Entering directory '/home/lkmao/imx/linux/linux-imx'
  CC [M]  /big/csi_driver/csi_softirq/csi_softirq.o
  Building modules, stage 2.
  MODPOST 1 modules
WARNING: "open_softirq" [/big/csi_driver/csi_softirq/csi_softirq.ko] undefined!
WARNING: "raise_softirq" [/big/csi_driver/csi_softirq/csi_softirq.ko] undefined!
  CC      /big/csi_driver/csi_softirq/csi_softirq.mod.o
  LD [M]  /big/csi_driver/csi_softirq/csi_softirq.ko
make[1]: Leaving directory '/home/lkmao/imx/linux/linux-imx'
sudo cp csi_softirq.ko /big/nfsroot/jiaocheng_rootfs/home/root/
[sudo] lkmao 的密码:
lkmao@ubuntu:/big/csi_driver/csi_softirq$

        然后insmod。

root@hehe:~# insmod csi_softirq.ko
[  110.834509] csi_softirq: Unknown symbol raise_softirq (err 0)
[  110.840304] csi_softirq: Unknown symbol open_softirq (err 0)
insmod: ERROR: could not insert module csi_softirq.ko: Unknown symbol in module
root@hehe:~#

        这说明内核不让用啊,这两个函数没有被符号导出,好的,修改Softirq.c (kernel) ,将这两个符号导出。在Softirq.c (kernel) 文件尾部添加如下内容:

EXPORT_SYMBOL(open_softirq);
EXPORT_SYMBOL(raise_softirq);

       重新编译内核,然后重新编译模块:然后insmod csi_softirq.ko

root@hehe:~# insmod csi_softirq.ko
[  102.263731] cssq_init,line=39:init ok
root@hehe:~# [  103.263319] cssq_timer_function,line=25:
[  103.267270] cssq_action,line=18:count = 1
[  104.263304] cssq_timer_function,line=25:
[  104.267245] cssq_action,line=18:count = 2
[  105.263302] cssq_timer_function,line=25:
[  105.267239] cssq_action,line=18:count = 3
[  106.263316] cssq_timer_function,line=25:
[  106.267268] cssq_action,line=18:count = 4
[  107.263313] cssq_timer_function,line=25:
[  107.267264] cssq_action,line=18:count = 5
[  108.263298] cssq_timer_function,line=25:
[  108.267239] cssq_action,line=18:count = 6
[  109.263301] cssq_timer_function,line=25:
[  109.267240] cssq_action,line=18:count = 7
[  110.263297] cssq_timer_function,line=25:
[  110.267237] cssq_action,line=18:count = 8
[  111.263297] cssq_timer_function,line=25:
[  111.267236] cssq_action,line=18:count = 9
[  112.263311] cssq_timer_function,line=25:
[  112.267253] cssq_action,line=18:count = 10
... ...

rmmod csi_wq.ko

... ...
[  123.267266] cssq_action,line=18:count = 21
[  124.263311] cssq_timer_function,line=25:
[  124.267265] cssq_action,line=18:count = 22
root@hehe:~# rmmod csi_wq.ko
[  124.979652] cssq_exit,line=47:exit ok
root@hehe:~#

总结

        测试完毕后,记得将Interrupt.h (include\linux)和文件Softirq.c (kernel)修改回原来的状态。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千册

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值