linux 中nvme 的中断申请及处理
/**
* struct irq_desc - interrupt descriptor
* @irq_data: per irq and chip data passed down to chip functions
* @kstat_irqs: irq stats per cpu
* @handle_irq: highlevel irq-events handler
* @preflow_handler: handler called before the flow handler (currently used by sparc)
* @action: the irq action chain
* @status: status information
* @core_internal_state__do_not_mess_with_it: core internal status information
* @depth: disable-depth, for nested irq_disable() calls
* @wake_depth: enable depth, for multiple irq_set_irq_wake() callers
* @irq_count: stats field to detect stalled irqs
* @last_unhandled: aging timer for unhandled count
* @irqs_unhandled: stats field for spurious unhandled interrupts
* @lock: locking for SMP
* @affinity_hint: hint to user space for preferred irq affinity
* @affinity_notify: context for notification of affinity changes
* @pending_mask: pending rebalanced interrupts
* @threads_oneshot: bitfield to handle shared oneshot threads
* @threads_active: number of irqaction threads currently running
* @wait_for_threads: wait queue for sync_irq to wait for threaded handlers
* @dir: /proc/irq/ procfs entry
* @name: flow handler name for /proc/interrupts output
*/
struct irq_desc {
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;///* irq的统计信息,在proc中可查到 */
irq_flow_handler_t handle_irq;
/* 回调函数,当此中断产生中断时,会调用handle_irq,在handle_irq中进行遍历irqaction链表*/
/* handle_simple_irq 用于简单处理;
* handle_level_irq 用于电平触发中断的流控处理;
* handle_edge_irq 用于边沿触发中断的流控处理;
* handle_fasteoi_irq 用于需要响应eoi的中断控制器;
* handle_percpu_irq 用于只在单一cpu响应的中断;
* handle_nested_irq 用于处理使用线程的嵌套中断;
*/
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
irq_preflow_handler_t preflow_handler;
#endif
struct irqaction *action; /* IRQ action list */ unsigned int status_use_accessors;
unsigned int core_internal_state__do_not_mess_with_it;
unsigned int depth; /* nested irq disables */ /* 嵌套深度,中断线被激活显示0,如果为正数,表示被禁止次数 */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs *//* 此中断线上发生的中断次数 */
unsigned long last_unhandled; /* Aging timer for unhandled count */ /* 上次发生未处理中断时的jiffies值 */
unsigned int irqs_unhandled;/* 中断线上无法处理的中断次数,如果当第100000次中断发生时,有超过99900次是意外中断,系统会禁止这条中断线 */
raw_spinlock_t lock;
struct cpumask *percpu_enabled;
#ifdef CONFIG_SMP
const struct cpumask *affinity_hint; /* CPU亲和力关系,其实就是每个CPU是占一个bit长度,某CPU上置为1表明该CPU可以进行这个中断的处理 */
struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask; /* 用于调整irq在各个cpu之间的平衡 */
#endif
#endif
unsigned long threads_oneshot;
atomic_t threads_active;
wait_queue_head_t wait_for_threads; /* 用于synchronize_irq(),等待该irq所有线程完成 */
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir; /* 指向与IRQn相关的/proc/irq/n目录的描述符 */
#endif
int parent_irq;
struct module *owner;
const char *name;/* 在/proc/interrupts所显示名称 */
} ____cacheline_internodealigned_in_smp;
中断的描述符如上所述。作为背景知识,可以理解下面的内容,本文讨论基于的内核版本信息如下:
uname -a
Linux localhost.localdomain 3.10.
我们知道,nvme的多队列,默认按照核数的多少来设置,目前nvme的队列有两种,admin队列,IO队列,两者都属于nvme_queue对象,submit queue,complete queue是一个nvme_queue对象的一个成员,其中submit queue在代码中会简写为sq,complete queue会简写成cq。两者是Queue Pair(QP),也就是submitqueue·completequeue和admin queue不是同一个级别的对象,对于admin队列来说,它也有自己的submitquque和completequeue,第一次看代码时往往容易混淆。
首先,我们来看一下nvme总共用的中断数。
# cat /proc/interrupts |grep nvme |wc -l
320
该系统上一共4块盘,80个核,就有320个中断,一个核对应一个队列,一个中断号。按道理IOqueue有80个,adminqueue也需要用中断,
那么中断数应该是81*4=324才对。
# cat /proc/interrupts |grep -i nvme[-]q0|awk '{print $1,$(NF-1),$NF}'
: nvme2q0, nvme2q1
: nvme3q0, nvme3q1
: nvme0q0, nvme0q1
: nvme1q0, nvme1q1
我们发现,nvme0q0 和 nvme0q1 是共享中断的。而其他的sq都是虽然带的参数也是共享,但是从实际情况看,是独占的。所以数量是320个。
nvme0q0 就是我们可爱的admin queue,从申请的角度看,我们可以看出来,一开始adminqueue申请,用的是裸命令,后面的ioqueues申请,利用的是admin queue的队列。
由于admin的queue是最先申请的,所以包括中断号也是单独申请的,nvme_configure_admin_queue 中,调用静态函数 queue_request_irq来初始化admin的队列的中断,而且它传入的参数是共享的,也就是不需要独占中断,IRQF_SHARED。admin的队列编号是0。
static int nvme_configure_admin_queue(struct nvme_dev *dev)
{
int result;
u32 aqa;
u64 cap = lo_hi_readq(dev->bar + NVME_REG_CAP);
struct nvme_queue *nvmeq; dev->subsystem = readl(dev->bar + NVME_REG_VS) >= NVME_VS(, , ) ?
NVME_CAP_NSSRC(cap) : ; if (dev->subsystem &&
(readl(dev->bar + NVME_REG_CSTS) & NVME_CSTS_NSSRO))
writel(NVME_CSTS_NSSRO, dev->bar + NVME_REG_CSTS); result = nvme_disable_ctrl(&dev->ctrl, cap);
if (result < )
return result; nvmeq = dev->queues[];//admin是第一个queue,队列编号肯定是0
if (!nvmeq) {//admin的queue,深度为256
nvmeq = nvme_alloc_queue(dev, , NVME_AQ_DEPTH);//
if (!nvmeq)
return -ENOMEM;
} aqa = nvmeq->q_depth - ;
aqa |= aqa << ;
//将sq_dma_addr 和 cq_dma_addr 分别系到bar 空间偏移为NVME_REG_ASQ和NVME_REG_ACQ,这个地址都是在nvme_alloc_queue 中申请的.
writel(aqa, dev->bar + NVME_REG_AQA);
lo_hi_writeq(nvmeq->sq_dma_addr, dev->bar + NVME_REG_ASQ);
lo_hi_writeq(nvmeq->cq_dma_addr, dev->bar + NVME_REG_ACQ); result = nvme_enable_ctrl(&dev->ctrl, cap);
if (result)
return result; nvmeq->cq_vector = ;
result = queue_request_irq(dev, nvmeq, nvmeq->irqname);//为admin队列申请中断,这个是nvme驱动最先申请的中断号
if (result) {
nvmeq->cq_vector = -;
return result;
} return result;
}
而ioquue,都是调用的nvme_pci_enable来完成中断的申请,
中断注册,也是在队列创建的时候完成,nvme_create_queue ,其中需要注意的是,admin的中断,会先注册,然后再取消注册,然后再注册一次。先注册的目的是为了借助这个中断来返回处理创建sq和cq等命令的结果。
nvme_create_io_queues---|nvme_alloc_queue----分配nvmeq结构体,并记录到dev->queues[]数组中,并分配submit queue 和complete queue命令所需要的空间。
---|nvme_create_queue---|adapter_alloc_cq----构建cmd,利用admin 的queue发送控制消息,分配sq相关信息
---|adapter_alloc_sq----这个是分配submitqueue队列的相关信息,与cq类似。
---|queue_request_irq---这个是申请中断
---|nvme_init_queue---初始化队列
下面,重点了解下queue_request_irq 的传入参数:
static int queue_request_irq(struct nvme_dev *dev, struct nvme_queue *nvmeq,
const char *name)
{
if (use_threaded_interrupts)//中断线程化使能,默认没有开启
return request_threaded_irq(dev->entry[nvmeq->cq_vector].vector,
nvme_irq_check, nvme_irq, IRQF_SHARED,
name, nvmeq);
return request_irq(dev->entry[nvmeq->cq_vector].vector, nvme_irq,
IRQF_SHARED, name, nvmeq);
} static int use_threaded_interrupts;默认就是0了。
也就是nvme驱动默认没有使能中断线程化功能。request_irq 是中断的申请接口了,定义在interrupt.h中,调用request_threaded_irq,其中第三个传入传入的是NULL
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
request_threaded_irq定义在manager.c中,后面就是中断的通用流程了,我们主要针对传入的参数分析一下:
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
{.....
......
action->handler = handler;---------就是我们的nvme_irq
action->thread_fn = thread_fn;-----nvme中传入的是NULL
action->flags = irqflags;
action->name = devname;-------------nvmeq->irqname,中断名称,就是/proc/interrupts每行的最后那列,当中断共享的时候,会显示注册的多个名称。
action->dev_id = dev_id;------------nvmeq,作为对象,会在thread_fn 作为最后一个参数传回来
.....
.....
}
static irqreturn_t nvme_irq(int irq, void *data)------处理nvme中断
{
irqreturn_t result;
struct nvme_queue *nvmeq = data;--------回调nvme_irq的时候,传入的data就是之前注册的时候传入的dev_id
spin_lock(&nvmeq->q_lock);-------------由于默认每个队列是在一个cpu上,所以这里自旋锁的消耗很少,是一种无锁设计的保护而已。
nvme_process_cq(nvmeq);
result = nvmeq->cqe_seen ? IRQ_HANDLED : IRQ_NONE;
nvmeq->cqe_seen = ;
spin_unlock(&nvmeq->q_lock);
return result;
}
去掉包裹函数,真正干活的就是nvme_process_cq 了,又看到了熟悉的head,tail标志,这个机制的描述在网上已经烂大街了,借用一下:
Head/Tail机制
Submission Queue使用Tail,Completion Queue使用Head,两者均由Host操作。处理完一个Command,Tail或Head加1,当大于Queue Depth时,则回到0。通过对比Head和Tail的值,就知道一个Queue中有多少未处理的Submission Command。下面的图摘自NVMe Spec,有兴趣的同学可以据此琢磨下Empty Queue和Full Queue的定义。
static int nvme_process_cq(struct nvme_queue *nvmeq)
{
u16 head, phase; head = nvmeq->cq_head;
phase = nvmeq->cq_phase; while (nvme_cqe_valid(nvmeq, head, phase)) {
struct nvme_completion cqe = nvmeq->cqes[head];
struct request *req; if (++head == nvmeq->q_depth) {
head = ;
phase = !phase;
} if (unlikely(cqe.command_id >= nvmeq->q_depth)) {
dev_warn(nvmeq->dev->ctrl.device,
"invalid id %d completed on queue %d\n",
cqe.command_id, le16_to_cpu(cqe.sq_id));
continue;
} /*
* AEN requests are special as they don't time out and can
* survive any kind of queue freeze and often don't respond to
* aborts. We don't even bother to allocate a struct request
* for them but rather special case them here.
*/
if (unlikely(nvmeq->qid == &&
cqe.command_id >= NVME_AQ_BLKMQ_DEPTH)) {
nvme_complete_async_event(&nvmeq->dev->ctrl,
cqe.status, &cqe.result);
continue;
} req = blk_mq_tag_to_rq(*nvmeq->tags, cqe.command_id);
nvme_req(req)->result = cqe.result;
blk_mq_complete_request(req, le16_to_cpu(cqe.status) >> );
} if (head == nvmeq->cq_head && phase == nvmeq->cq_phase)
return ; if (likely(nvmeq->cq_vector >= ))
writel(head, nvmeq->q_db + nvmeq->dev->db_stride);
nvmeq->cq_head = head;
nvmeq->cq_phase = phase; nvmeq->cqe_seen = ;
return ;
}
linux 中nvme 的中断申请及处理的更多相关文章
- 如何理解 Linux 中的 load averages
原文:https://mp.weixin.qq.com/s?src=11×tamp=1533697106&ver=1047&signature=poqrJFfcNAB ...
- 向linux内核中添加外部中断驱动模块
本文主要介绍外部中断驱动模块的编写,包括:1.linux模块的框架及混杂设备的注册.卸载.操作函数集.2.中断的申请及释放.3.等待队列的使用.4.工作队列的使用.5.定时器的使用.6.向linux内 ...
- linux中断申请之request_threaded_irq
转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=21977330&id=3755609 在linux里,中断处理分 ...
- linux中断申请之request_threaded_irq【转】
转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=21977330&id=3755609 在linux里,中断处理分 ...
- linux中断申请之request_threaded_irq 【转】
转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=21977330&id=3755609 在linux里,中断处理分 ...
- Linux就这个范儿 第15章 七种武器 linux 同步IO: sync、fsync与fdatasync Linux中的内存大页面huge page/large page David Cutler Linux读写内存数据的三种方式
Linux就这个范儿 第15章 七种武器 linux 同步IO: sync.fsync与fdatasync Linux中的内存大页面huge page/large page David Cut ...
- Linux内核:关于中断你须要知道的
1.中断处理程序与其它内核函数真正的差别在于,中断处理程序是被内核调用来对应中断的,而它们执行于中断上下文(原子上下文)中,在该上下文中执行的代码不可堵塞. 中断就是由硬件打断操作系统. 2.异常与中 ...
- 基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(1)
作者:彭东林 邮箱:pengdonglin137@163.com QQ:405728433 平台 tiny4412 ADK Linux-4.9 概述 前面几篇博文列举了在有设备树的时候,gpio中断的 ...
- 蜕变成蝶~Linux设备驱动之中断与定时器
“我叮咛你的 你说 不会遗忘 你告诉我的 我也全部珍藏 对于我们来说 记忆是飘不落的日子 永远不会发黄 相聚的时候 总是很短 期待的时候 总是很长 岁月的溪水边 捡拾起多少闪亮的诗行 如果你要想念我 ...
随机推荐
- Hibernate学习笔记(1)---hibernate快速上手与准备工作
持久层介绍 持久化:将内存中的数据保存在磁盘等存储设备中. 持久化对象:指已经存储在数据库护着磁盘的业务对象 经典的软件应用体系结构(三层结构) 在三层结构中,由于业务逻辑除了负责业务逻辑以外,还要负 ...
- HashMap分析之红黑树树化过程
概述 HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型.随着JDK(Java Developmet Kit)版本的更新,JDK1.8对HashMap底层的实现进行了优化,例 ...
- rtx web 分级管理系统 二次开发
fineui + ASP.NET+rtx server sdk 修正 rtx管理器 修改用户后分级目录出错问题. 加入 单用户多个部门 添加授权关闭部分采用 rsa加密.
- while100以内的偶数
#显示100以内的偶数 #声明i i = 1 #开始循环条件为i不等于100,执行while代码块 while i != 100: #给i加1 i +=1 #如果循环到此时i的取余运算为0则打印i i ...
- Linux下SVN提交时强制写日志
Linux版本: 1.在svn的hooks目录下新建一个名为pre-commit的文件并为其添加执行权限(用vi pre-commit直接创建) 2.pre-commit文件的内容如下: #!/bin ...
- HTML知识点总结之img、scirpt、link标签
<img>元素 使用<img>可以在网页插入一个图片,但实际上<img>标签并不会在网页中直接插入图像,而是从网页上链接图像. <img>的主要属性 ( ...
- 安装cocoapods遇到的问题
1.终端报下面的错误 ERROR: While executing gem ... (Errno::EPERM) Operation not permitted - /usr/bin/pod 解决方 ...
- Nginx配置文件(2)
一.配置文件结构 1.全局块:配置影响nginx全局的指令.一般有运行nginx服务器的用户组,nginx进程pid存放路径,日志存放路径,配置文件引入,允许生成worker process数等. 2 ...
- 认识Java中的字符串
Java 中 String 类的常用方法 Ⅰ String 类提供了许多用来处理字符串的方法,例如,获取字符串长度.对字符串进行截取.将字符串转换为大写或小写.字符串分割等,下面我们就来领略它的强大之 ...
- 深透清晰理解Java高并发概述
1.多线程安全性 多线程安全性的定义可能众说纷纭,但是其最核心的一点就是正确性,也就是程序的行为结果和预期一致. 当多个线程访问某个类时,不管运行环境采用何种线程调度算法或者这些线程如何交替执行,且不 ...