Linux内核学习7——中断下半部分的代码分析及应用

Linux将中断处理分为上下两部分,上半部分主要处理紧急的,必须马上处理的事情,下半部分处理不那么紧急的事情,linux内核也提供了相应的机制,这里使用理论课中的tasklet机制来进行实验。

本节代码是在上节的基础上稍微增加一部分代码即可。

一、程序示例

完整代码如下:interrupt2.c

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
 
static int irq;
static char * devname;
 
module_param(irq,int,0644);
module_param(devname,charp,0644);
 
struct myirq
{
        int devid;
};
 
struct myirq mydev={1119};
 
static struct tasklet_struct mytasklet;
 
static void mytasklet_handler(unsigned long data)  //下半部分处理函数
{
        printk("I am mytasklet_handler");
}
 
static irqreturn_t myirq_handler(int irq,void * dev)
{
//      struct myirq mydev;
//      static int count=1;
        static int count=0;
//      mydev = *(struct myirq*)dev;
//      printk("key: %d..\n",count);
//      printk("devid:%d ISR is working..\n",mydev.devid);
//      printk("ISR is leaving......\n");
        printk("count:%d\n",count+1);
        printk("I am myirq_handler\n");
        printk("The most of the interrupt work will be done by folling tasks\n");
        //将下半部分函数进行注册。最主要的是要将下半部分处理函数挂载上去
        tasklet_init(&mytasklet,mytasklet_handler,0);  
        tasklet_schedule(&mytasklet);   //调度
        count++;
        return IRQ_HANDLED;
}
 
static int __init myirq_init(void)
{
        printk("Module is working...\n");
        if(request_irq(irq,myirq_handler,IRQF_SHARED,devname,&mydev)!=0)
        {
                printk("%s request IRQ: %d failed...\n",devname,irq);
                return -1;
        }
        printk("%s request IRQ:%d success...\n",devname,irq);
        return 0;
}
 
static void __exit myirq_exit(void)
{
        printk("Module is leaving...\n");
        free_irq(irq,&mydev);
        tasklet_kill(&mytasklet);      //注销掉tasklet
        printk("Free the irq: %d..\n",irq);
}
 
MODULE_LICENSE("GPL");
module_init(myirq_init);
module_exit(myirq_exit);

首先在中断服务例程,即上半部分中添加tasklet的init函数,即将下半部分函数进行注册。最主要的是要将下半部分处理函数挂载上去。最后调用tasklet_schedule函数进行调度

最后用到tasklet_kill,用来注销掉我们的tasklet

Makefile 文件如下:

# Makefile文件注意:加入前面的.c文件起名为first.c,那么这里的Makefile文件中的.o文件就要起名first.o
# # 只有root用户才能加载和卸载模块
obj-m:=interrupt2.o                                     # 产生first模块的目标文件
# #目标文件   文件       要与模块名字相同
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL:=$(shell uname -r)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
#
all:
        make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules     # 编译模块
#       #[TAB]            内核的路径       当前目录编译完放哪里?    表明编译的是内核模块
#
clean:
        make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean     # 清理模块

输入make命令,进行编译

在这里插入图片描述

插入模块,还是使用1号中断线,

sudo insmod interrupt2.ko irq=1 devname=myirq

查看中断情况,发现设备已被正确插入

cat /proc/interrupts 

在这里插入图片描述

打印日志信息,dmesg

在这里插入图片描述

我们可以在这里看到,已经顺利的进入了我们下半部分的程序

退出模块

sudo rmmod interrupt2

在这里插入图片描述

在这里插入图片描述

已经顺利退出

二、源码分析

查看一下内核中的tasklet_init函数,它位于内核/kernel/softirq.c中,我们发现,内容基本上是字段的赋值,以及函数的挂载,

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;
}
EXPORT_SYMBOL(tasklet_init);

还有一个函数是tasklet_schedule函数,这个函数位于/include/linux/interrupt.h中,实际上是进入了__tasklet_schedule(t)

extern void __tasklet_schedule(struct tasklet_struct *t);
 
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文件中

void __tasklet_schedule(struct tasklet_struct *t)
{
	__tasklet_schedule_common(t, &tasklet_vec,
				  TASKLET_SOFTIRQ);
}
EXPORT_SYMBOL(__tasklet_schedule);
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);
}

实际上,它的调度就是把我们传入的tasklet挂入到了链表的尾部。接下来执行TASKLET_SOFTIRQ中断,调度不代表执行,真正的执行对于TASKLET_SOFTIRQ类型是tasklet_action函数,这个函数位于/kernel/softirq.c文件中。

这里面最核心的是while(list),也就是遍历tasklet链表,真正执行的函数是”t->func(t->data);",这里就是tasklet这个结构体的最后一个字段,其实是func的一个参数,然后继续执行下一个tasklet语句

static void tasklet_action_common(struct softirq_action *a,
				  struct tasklet_head *tl_head,
				  unsigned int softirq_nr)
{
	struct tasklet_struct *list;
 
	local_irq_disable();
	list = tl_head->head;
	tl_head->head = NULL;
	tl_head->tail = &tl_head->head;
	local_irq_enable();
 
	while (list) {
		struct tasklet_struct *t = list;
 
		list = list->next;
 
		if (tasklet_trylock(t)) {
			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;
		*tl_head->tail = t;
		tl_head->tail = &t->next;
		__raise_softirq_irqoff(softirq_nr);
		local_irq_enable();
	}
}

我们现在可以查看一下内核源码中驱动代码应用tasklet是如何应用的?

进入/net/mac802154/man.c, 可以看到有用到tasklet_init,对应的中断的下半部函数是ieee802154_tasklet_handler,

	tasklet_init(&local->tasklet,
		     ieee802154_tasklet_handler,
		     (unsigned long)local);
static void ieee802154_tasklet_handler(unsigned long data)
{
	struct ieee802154_local *local = (struct ieee802154_local *)data;
	struct sk_buff *skb;
 
	while ((skb = skb_dequeue(&local->skb_queue))) {
		switch (skb->pkt_type) {
		case IEEE802154_RX_MSG:
			/* Clear skb->pkt_type in order to not confuse kernel
			 * netstack.
			 */
			skb->pkt_type = 0;
			ieee802154_rx(local, skb);
			break;
		default:
			WARN(1, "mac802154: Packet is of unknown type %d\n",
			     skb->pkt_type);
			kfree_skb(skb);
			break;
		}
	}
}

之后,它的调度函数在/net/mac802154/rx.c中,tasklet_schedule(&local->tasklet)语句进行了调度

void
ieee802154_rx_irqsafe(struct ieee802154_hw *hw, struct sk_buff *skb, u8 lqi)
{
	struct ieee802154_local *local = hw_to_local(hw);
 
	mac_cb(skb)->lqi = lqi;
	skb->pkt_type = IEEE802154_RX_MSG;
	skb_queue_tail(&local->skb_queue, skb);
	tasklet_schedule(&local->tasklet);        //在这里调度
}

最后,在/net/mac802154/man.c, 最后的注销中,通过tasklet_kill(&local->tasklet),对tasklet进行注销。

void ieee802154_unregister_hw(struct ieee802154_hw *hw)
{
	struct ieee802154_local *local = hw_to_local(hw);
 
	tasklet_kill(&local->tasklet);        //注销
	flush_workqueue(local->workqueue);
 
	rtnl_lock();
 
	ieee802154_remove_interfaces(local);
 
	rtnl_unlock();
 
	destroy_workqueue(local->workqueue);
	wpan_phy_unregister(local->phy);
}

最后介绍一个小功能。这个小功能实际上是在内核源码的Documentation,也就是内核源码中的文档中所发现的一个小功能。值得一提的是,这个Documentation中的文件都是非常重要的,对我们很有帮助,在里面我们可以找到很多有意思的一些文件

比如说,在/Documentation/x86/boot.rst中可以找到关于启动时的一些说明。像一开始内存的分配

The traditional memory map for the kernel loader, used for Image or
zImage kernels, typically looks like::
 
		|			 |
	0A0000	+------------------------+
		|  Reserved for BIOS	 |	Do not use.  Reserved for BIOS EBDA.
	09A000	+------------------------+
		|  Command line		 |
		|  Stack/heap		 |	For use by the kernel real-mode code.
	098000	+------------------------+
		|  Kernel setup		 |	The kernel real-mode code.
	090200	+------------------------+
		|  Kernel boot sector	 |	The kernel legacy boot sector.
	090000	+------------------------+
		|  Protected-mode kernel |	The bulk of the kernel image.
	010000	+------------------------+
		|  Boot loader		 |	<- Boot sector entry point 0000:7C00
	001000	+------------------------+
		|  Reserved for MBR/BIOS |
	000800	+------------------------+
		|  Typically used by MBR |
	000600	+------------------------+
		|  BIOS use only	 |
	000000	+------------------------+

在/Documentation/process/changes.rst中可以找到编译内核时最少需要的工具有哪些

Current Minimal Requirements
****************************
 
Upgrade to at **least** these software revisions before thinking you've
encountered a bug!  If you're unsure what version you're currently
running, the suggested command should tell you.
 
Again, keep in mind that this list assumes you are already functionally
running a Linux kernel.  Also, not all tools are necessary on all
systems; obviously, if you don't have any PC Card hardware, for example,
you probably needn't concern yourself with pcmciautils.
 
====================== ===============  ========================================
        Program        Minimal version       Command to check the version
====================== ===============  ========================================
GNU C                  4.6              gcc --version
GNU make               3.81             make --version
binutils               2.21             ld -v
flex                   2.5.35           flex --version
bison                  2.0              bison --version
util-linux             2.10o            fdformat --version
kmod                   13               depmod -V
e2fsprogs              1.41.4           e2fsck -V
jfsutils               1.1.3            fsck.jfs -V
reiserfsprogs          3.6.3            reiserfsck -V
xfsprogs               2.6.0            xfs_db -V
squashfs-tools         4.0              mksquashfs -version
btrfs-progs            0.18             btrfsck
pcmciautils            004              pccardctl -V
quota-tools            3.09             quota -V
PPP                    2.4.0            pppd --version
nfs-utils              1.0.5            showmount --version
procps                 3.2.0            ps --version
oprofile               0.9              oprofiled --version
udev                   081              udevd --version
grub                   0.93             grub --version || grub-install --version
mcelog                 0.6              mcelog --version
iptables               1.4.2            iptables -V
openssl & libcrypto    1.0.0            openssl version
bc                     1.06.95          bc --version
Sphinx\ [#f1]_	       1.3		sphinx-build --version
====================== ===============  ========================================

/Documentation/IRQ.txt中介绍了,什么是IRQ 这里是开发人员的解释

===============
What is an IRQ?
===============
 
An IRQ is an interrupt request from a device.
Currently they can come in over a pin, or over a packet.
Several devices may be connected to the same pin thus
sharing an IRQ.
 
An IRQ number is a kernel identifier used to talk about a hardware
interrupt source.  Typically this is an index into the global irq_desc
array, but except for what linux/interrupt.h implements the details
are architecture specific.
 
An IRQ number is an enumeration of the possible interrupt sources on a
machine.  Typically what is enumerated is the number of input pins on
all of the interrupt controller in the system.  In the case of ISA
what is enumerated are the 16 input pins on the two i8259 interrupt
controllers.
 
Architectures can assign additional meaning to the IRQ numbers, and
are encouraged to in the case  where there is any manual configuration
of the hardware involved.  The ISA IRQs are a classic example of
assigning this kind of additional meaning.

相应的还有中断API介绍,位于/Documentation/core-api/genericirq.rst中,到这个文件的最后就可以看到前面涉及到的manage.c, chip.c, handle.c

Public Functions Provided
=========================
 
This chapter contains the autogenerated documentation of the kernel API
functions which are exported.
 
.. kernel-doc:: kernel/irq/manage.c
 
.. kernel-doc:: kernel/irq/chip.c
 
Internal Functions Provided
===========================
 
This chapter contains the autogenerated documentation of the internal
functions.
 
.. kernel-doc:: kernel/irq/irqdesc.c
 
.. kernel-doc:: kernel/irq/handle.c
 
.. kernel-doc:: kernel/irq/chip.c
 
Credits
=======
 
The following people have contributed to this document:
 
1. Thomas Gleixner tglx@linutronix.de
 
2. Ingo Molnar mingo@elte.hu

在/Documentation/IRQ-affinity.txt中,有一个特殊功能。 如果一个机器中有多个CPU,我们可以指定我们产生的中断在哪个CPU上进行处理,即绑定CPU。关于这里,应该根据生成环境的特点与应用的特点来平衡IRQ中断,这有助于提高整个系统的吞吐能力与性能,不同的场景下我们需要不同的设置,也不是说我们修改了某些参数就叫做性能优化,还需要通过大量的测试进行测试/观察/改进,如下为redhat下的演示。

首先系统已经运行irqbalance服务,这个服务就是平衡我们的中断到各个CPU,这个服务可能对我们的演示造成一定的影响,所以我们关闭这个服务

/etc/init.d/irqbalance stop

查看中断情况

/cat /proc/interrupts

这是一张网卡,网卡的中断由CPU0 114来处理,而CPU1没有进行处理,我们再来看一下这个文件

在这里插入图片描述

网卡的irq是19,我们看一下smp_affinity这个文件

在这里插入图片描述

看到这里是1,对于的二进制也是1,那就是仅仅在CPU0来处理我们的网卡中断,而CPU1不进行

在这里插入图片描述

现在我们进行修改,然后查看,2对应的二进制是10,即只使用CPU1进行处理网卡中断,而CPU0不响应

在这里插入图片描述

查看中断情况,发现CPU0/1对应的是156/14

在这里插入图片描述

再次查看中断情况,发现CPU0/1对应的是156/18

在这里插入图片描述

再次查看中断情况,发现CPU0/1对应的是156/21,达到了我们想要的效果。

在这里插入图片描述


如果以上内容对您有用,麻烦点赞、关注或者收藏一波哦~!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值