目录

1、node

2、pci_request_mem_regions

3、pci_resource_len、pci_resource_start

4、work_queue

5、dma_pool_create、dma_pool_alloc

6、mempool_create_node、mempool_alloc

7、mb()/rmb()/wmb()

8、ida_simple_get

9、async_schedule

10、put_device 、get_device


1、node

共享存储型多处理机有两种模型

  • 均匀存储器存取(Uniform-Memory-Access,简称UMA)模型

  • 非均匀存储器存取(Nonuniform-Memory-Access,简称NUMA)模型

Linux 为什么要用node来描述内存?
每个处理器CPU与一个本地内存直接相连, 而不同处理器之前则通过总线进行进一步的连接, 因此相对于任何一个CPU访问本地内存的速度比访问远程内存的速度要快。

Linux适用于各种不同的体系结构, 而不同体系结构在内存管理方面的差别很大. 因此linux内核需要用一种体系结构无关的方式来表示内存。

因此linux内核把物理内存按照CPU节点划分为不同的node, 每个node作为某个cpu结点的本地内存, 而作为其他CPU节点的远程内存, 而UMA结构下, 则任务系统中只存在一个内存node, 这样对于UMA结构来说, 内核把内存当成只有一个内存node节点的伪NUMA。

相关接口:

kzalloc_node 、kcalloc_node,根据设备从不同的内存中分配内存。

最终会调用到函数alloc_pages()调用numa_node_id()返回与当前正在运行的CPU相关联的节点的逻辑ID。 这个NID被传递给_alloc_pages(),它以NID作为参数调用NODE_DATA()。

在UMA体系结构上,这将无条件地返回contig_page_data,但NUMA体系结构改为设置一个NODE_DATA()使用NID作为偏移量的数组。 换句话说,体系结构负责将一个CPU ID设置为NUMA内存节点映射。

这实际上仍然是2.4中使用的节点本地分配策略,但是它的定义更加清晰。


2、pci_request_mem_regions

获取pci设备所有bar的地址,标记mem类型的bar地址对应的存储器域地址已被使用,标记后,可在/proc/iomem中可以看到该段地址


3、pci_resource_len、pci_resource_start

pci_resource_len:获取bar空间地址长度

pci_resource_start:获取bar空间地址基地址(存储器域)


4、work_queue

a、工作结构体

struct work_struct  work;

struct workqueue_struct *wq;

b、编写要在工作队列中被调用的函数,函数原形如下:

typedef void (*work_func_t)(struct work_struct *work);

c、创建一个专用的内核线程来执行提交到工作队列中的函数

wq = create_singlethread_workqueue(const char *name);

d、初始化数据结构

INIT_WORK(struct work_struct *work, work_func_t func);

e、将任务提交到工作队列

queue_work(struct workqueue_struct *wq, struct wor_struct  *work);

5、dma_pool_create、dma_pool_alloc

struct dma_pool* dma_pool_create(const char *name, struct device *dev, size_t size, size_t align, size_t alloc); 创建DMA一致性内存的内存池,必须在可睡眠的上下文中执行。align必须2的幂次方,如果等于0则为1,size最小为4

void *dma_pool_alloc(struct dma_pool *pool, gfp_t gfp_flags, dma_addr_t *dma_handle);从内存池中分配内存,返回的内存同时满足申请的大小及对齐要求。如果内存池中内存不够,则会自动kmalloc内存并放入到pool中


6、mempool_create_node、mempool_alloc

mempool_t *mempool_create_node(int min_nr, mempool_alloc_t *alloc_fn,
            mempool_free_t *free_fn, void *pool_data,
            gfp_t gfp_mask, int nid);新建一个缓存池

void *mempool_alloc(mempool_t *pool, gfp_t gfp_mask);//调用创建内存池时提供的alloc_fn和pool_data来分配内存,一般pool_data会使用一个数值,用于指定分配大小

void mempool_free(void *element, mempool_t *pool);


7、mb()/rmb()/wmb()

内存屏障主要解决的问题是编译器的优化和CPU的乱序执行。
编译器在优化的时候,生成的汇编指令可能和c语言程序的执行顺序不一样,在需要程序严格按照c语言顺序执行时,需要显式的告诉编译不需要优化,这在linux下是通过barrier()宏完成的,它依靠volidate关键字和memory关键字,前者告诉编译barrier()周围的指令不要被优化,后者作用是告诉编译器汇编代码会使内存里面的值更改,编译器应使用内存里的新值而非寄存器里保存的老值。
同样,CPU执行会通过乱序以提高性能。汇编里的指令不一定是按照我们看到的顺序执行的。linux中通过mb()系列宏来保证执行的顺序。具体做法是通过mfence/lfence指令(它们是奔4后引进的,早期x86没有)以及x86指令中带有串行特性的指令(这样的指令很多,例如linux中实现时用到的lock指令,I/O指令,操作控制寄存器、系统寄存器、调试寄存器的指令、iret指令等等)。

如果在程序某处插入了mb()/rmb()/wmb()宏,则宏之前的程序保证比宏之后的程序先执行,从而实现串行化。wmb的实现和barrier()类似,是因为在x86平台上,写内存的操作不会被乱序执行。
实际上在RSIC平台上,这些串行工作都有专门的指令由程序员显式的完成,比如在需要的地方调用串行指令,而不像x86上有这么多隐性的带有串行特性指令(例如lock指令)。所以在risc平台下工作的朋友通常对串行化操作理解的容易些。


8、ida_simple_get

IDA 是一种基于 Radix 的 ID 分配机制。IDA 与 IDA 类似,也是基于 Radix-tree 的数 据结构。Radix-tree 是一种实现长整数与指针绑定的机制。其中 IDA 是将一个 ID 与指针 进行绑定。IDA 只不过是对 IDA 的一个封装, IDA 不需要记录指针, 仅仅是分配与管理 ID. 因此 IDA 的树叶的每个分支指向一个 struct ida_bitmap 结构或一个 bitmap, 这个 bitmap 中的每一个 bit 负责记录 ID, 置 1 表示已分配, 置 0 表示未分配. ID 号由对应 的 bit 在 IDA bitmap 中的位置以及 IDA bitmap 在整个树中的位置表示.内核中经常使用 IDA 进行 ID 分配。在Linux驱动中同一个驱动多个设备分配设备号时会使用到此机制

DEFINE_IDA 用于定义并初始化一个dia结构。

#define XARRAY_INIT(name, flags) {                \.xa_lock = __SPIN_LOCK_UNLOCKED(name.xa_lock),        \.xa_flags = flags,                    \.xa_head = NULL,                    \
}
#define IDA_INIT(name)    {                        \.xa = XARRAY_INIT(name, IDA_INIT_FLAGS)                \
}
#define DEFINE_IDA(name)    struct ida name = IDA_INIT(name)#define ida_simple_get(ida, start, end, gfp)    \ida_alloc_range(ida, start, (end) - 1, gfp)
#define ida_simple_remove(ida, id)    ida_free(ida, id)

#define ida_simple_get(ida, start, end, gfp) 获取id,如果start == end,不限制范围
#define ida_simple_remove(ida, id) 释放id


9、async_schedule

static inline async_cookie_t async_schedule(async_func_t func, void *data);开启一个内核线程来完成工作,工作执行体为func,func的传入参数为data。


10、put_device 、get_device

extern struct device *get_device(struct device *dev);设备引用加1
extern void put_device(struct device *dev);;设备引用减1


11、blk_sync_queue

void blk_sync_queue(struct request_queue *q)

块层可以在队列中执行异步回调活动,比如在超时后调用unplug函数。块设备可以调用blk_sync_queue来确保任何这样的活动被取消,从而允许它释放回调函数资源。在调用这个函数之前,调用者必须已经确保它的q->make_request_fn不会重新添加插入。


12、radix_tree

Linux radix树最广泛的用途是用于内存管理,结构address_space通过radix树跟踪绑定到地址映射上的核心页,该radix树允许内存管理代码快速查找标识为dirty或writeback的页。Linux radix树的API函数在lib/radix-tree.c中实现。

Linux基数树(radix tree)是将指针与long整数键值相关联的机制,它存储有效率,并且可快速查询,用于指针与整数值的映射(如:IDR机制)、内存管理等。

初始化函数

初始化一个名字是name的树根。mask是gfp相关的掩码,在内存管理的时候用到。
#define RADIX_TREE(name, mask)
struct radix_tree_root my_tree;
INIT_RADIX_TREE(my_tree, gfp_mask);

插入条目

int radix_tree_insert(struct radix_tree_root *root, unsigned long, void *item);
函数radix_tree_insert插入条目item到树root中,如果插入条目中内存分配错误,将返回错误-ENOMEM。该函数不能覆盖写正存在的条目。如果索引键值index已存在于树中,返回错误-EEXIST。插入操作成功返回0。
对于插入条目操作失败将引起严重问题的场合,下面的一对函数可避免插入操作失败:
int radix_tree_preload(gfp_t gfp_mask);
void radix_tree_preload_end(void);
函数radix_tree_preload尝试用给定的gfp_mask分配足够的内存,保证下一个插入操作不会失败。在调用插入操作函数之前调用此函数,分配的结构将存放在每CPU变量中。函数radix_tree_preload操作成功后,将完毕内核抢占。因此,在插入操作完成之后,用户应调用函数radix_tree_preload_end打开内核抢占。
删除条目
void *radix_tree_delete(struct radix_tree_root *root, unsigned long index)
函数radix_tree_delete删除与索引键值index相关的条目,如果删除条目在树中,返回该条目的指针,否则返回NULL。
查询条目
/*在树中查找指定键值的条目,查找成功,返回该条目的指针,否则,返回NULL*/
void *radix_tree_lookup(struct radix_tree_root *root, unsigned long index);
/*返回指向slot的指针,该slot含有指向查找到条目的指针*/
void **radix_tree_lookup_slot(struct radix_tree_root *root, unsigned long index);
/*多键值查找,max_items为需要查找的item个数,results表示查询结果。查询时键值索引从first_index开始*/
radix_tree_gang_lookup(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items);

标签操作
/*将键值index对应的条目设置标签tag,返回值为设置标签的条目*/
void *radix_tree_tag_set(struct radix_tree_root *root, unsigned long index, unsigned int tag);
/*从键值index对应的条目清除标签tag,返回值为清除标签的条目*/
void *radix_tree_tag_clear(struct radix_tree_root *root, unsigned long index, unsigned int tag);
/*检查键值index对应的条目tag是否设置。tag参数为0或者1,表示Dirty位或者WB位
如果键值不存在,返回0,如果键值存在,但标签未设置,返回-1;如果键值存在,且标签已设置,返回1*/
int radix_tree_tag_get(struct radix_tree_root *root, unsigned long index, unsigned int tag);
/*从first_index起查询树root中标签值为tag的条目,在results中返回*/
unsigned int radix_tree_gang_lookup_tag(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items, unsigned int tag);
/*如果树root中有任何条目使用tag标签,返回键值*/
int radix_tree_tagged(struct radix_tree_root *root, unsigned int tag);

遍历

radix_tree_for_each_slot() 函数用于遍历 radix tree 中所有的 slot


13、module_param

module_param(name,type,perm);

功能:指定模块参数,用于在加载模块时或者模块加载以后传递参数给模块。

参数:

name:模块参数的名称

type: 模块参数的数据类型

perm: 模块参数的访问权限


14、Block multi-queue

Linux块设备层已逐步切换到multiqueue , Linux5.0以后单队列代码已被完全移除。multiqueue核心思路是为每个CPU分配一个软件队列,为存储设备的每个硬件队列分配一个硬件派发队列,再将软件队列与硬件派发队列做绑定,减少了IO请求过程中对锁的竞争,从而提高IO性能。


15、bio

block层的IO最小单元,负责在block层以及其上下层之间传递数据。bio数据结构中除了包含IO的一些基础信息,操作类型(读/写/discard...),作用的磁盘/分区,IO优先级,状态等等。bio最重要的信息是描述了磁盘上一段连续数据与内存页的映射关系,主要由bi_vcnt,bi_io_vec,bi_iter 描述。

struct bio {
    struct bio        *bi_next;   //一个request有多个bio时,以单向链表组织bio
    struct gendisk        *bi_disk;
    ...
    struct bvec_iter    bi_iter;    //bvec迭代器
    ...
    unsigned short        bi_vcnt;    /* how many bio_vec's */
    unsigned short        bi_max_vecs;    /* max bvl_vecs we can hold */
    atomic_t        __bi_cnt;    /* pin count */  //bio 引用计数
    struct bio_vec        *bi_io_vec;    /* the actual vec list */
    struct bio_set        *bi_pool;
    struct bio_vec        bi_inline_vecs[0];//用于存放小于4个的bi_io_vec
};

struct bio_vec {
    struct page    *bv_page;//所属页的指针
    unsigned int    bv_len;//数据长度,单位byte
    unsigned int    bv_offset;//页内偏移
};

struct bio_vec :是描述磁盘数据与内存页映射的最小单元,每个bio_vec以offset & size形式描述一个内存页中的一段数据。每个bio 通常有多个bio_vec,bio_vec以数组的形式存储,*bi_io_vec 指向数组头,bi_vcnt记录了其个数。bi_inline_vecs是为每个bio预分配的bio_vec数组,包括4个bio_vec,若bio需要分配的bio_vec小于4可直接使用bi_inline_vecs,不需要再额外申请内存,此时bi_io_vec会指向bi_inline_vecs。

struct bio_vec {
    struct page    *bv_page;
    unsigned int    bv_len;
    unsigned int    bv_offset;
};

struct bvec_iter:用于遍历bi_io_vec数组,可由bio_for_each_segment等宏来使用。同时bio内的bvec_iter也记录了当前IO请求在磁盘上的起始扇区以及处理进度。

struct bvec_iter {
    sector_t               bi_sector;    /* device address in 512 byte sectors */
    unsigned int        bi_size;    /* residual I/O count */
    unsigned int        bi_idx;        /* current index into bvl_vec */
    unsigned int        bi_bvec_done;    /* number of bytes completed in current bvec */
};


16、request

IO调度的最小单元,描述一段物理地址连续的IO请求,对应一个或者多个连续的bio。操作类型,作用磁盘,优先级等数据由相关的bio转换而来,还包含了调度器及软硬件队列管理request时需要使用的链表节点结构。

struct request {
    struct request_queue *q;        //所属的request_queue
    struct blk_mq_ctx *mq_ctx;        //所属的软件队列,在分配request时根据当前cpu编号从request_queue->queue_ctx中获取
    struct blk_mq_hw_ctx *mq_hctx;    //所属的硬件队列,与mq_ctx存在匹配关系
 
    unsigned int cmd_flags;        /* op and common flags */
    req_flags_t rq_flags;
 
    int tag;
    int internal_tag;
 
    /* the following two fields are internal, NEVER access directly */
    unsigned int __data_len;    /* total data len */   //所有bio请求数据之和 
    sector_t __sector;        /* sector cursor */    //请求的起始扇区,等于第一个bio的起始扇区
 
    struct bio *bio;        //起始bio
    struct bio *biotail;

struct list_head queuelist;    //用于在各个队列链表中索引排序
    ...
}

__sector,__data_len: 记录当前request请求的IO在磁盘上的起始扇区以及数据长度,与所包含的bio对应。

*bio:记录request第一个bio,最初bio转换成request时,request内只有一个bio,随着后续可能出现的合并,一个request可能会包含多个bio,__sector,__data_len也跟随更新。当request内有多个bio时,以bio->bi_next组成单向链表管理。


17、request_queue

这是一个庞大的数据结构,用于描述块设备的请求队列,创建块设备时通常调用blk_mq_init_queue()初始化。其中包含该块设备的软硬件队列,调度器,为上层提供的IO请求入口函数,各种限制参数。

struct request_queue {
    struct request        *last_merge;
    struct elevator_queue    *elevator;    //调度器
 
    struct blk_queue_stats    *stats;
    struct rq_qos        *rq_qos;
 
    make_request_fn        *make_request_fn;    //io请求入口函数
    dma_drain_needed_fn    *dma_drain_needed;
 
    const struct blk_mq_ops    *mq_ops;    //驱动层需要实现的操作集合
 
    /* sw queues */
    struct blk_mq_ctx __percpu    *queue_ctx;    //软件队列数组,初始化时用alloc_percpu为每个cpu分配空间,并初始化
    unsigned int        nr_queues;
 
    unsigned int        queue_depth;
 
    /* hw dispatch queues */
    struct blk_mq_hw_ctx    **queue_hw_ctx;    //硬件队列指针数组,初始化时根据硬件设备支持的队列个数分配空间并初始化
    unsigned int  nr_hw_queues;  //硬件队列数量与硬件及驱动相关,目前大部分存储设备是1
    ...
    unsigned long        nr_requests;    /* Max # of requests */
    ...
    struct queue_limits    limits;    //根据底层驱动的limits设置
    ...
    struct list_head    requeue_list;
    ...
    struct blk_mq_tag_set    *tag_set;    //描述底层硬件
    struct list_head    tag_set_list;
    struct bio_set        bio_split;
    ...
}

elevator:IO调度器实例,创建块设备或切换IO调度器时,调用所选调度器init_sched()方法初始化。elevator主要包含其对应的调度器类,以及私有数据。

queue_ctx:描述软件队列,创建request_queue时在blk_mq_init_queue -> blk_mq_init_allocated_queue -> blk_mq_alloc_ctxs中初始化。主要信息是记录了其关联的硬件队列。State for a software queue facing the submitting CPUs

queue_hw_ctx:描述硬件队列,创建request_queue时在blk_mq_init_queue -> blk_mq_init_allocated_queue中初始化。主要记录了其关联的软件队列,分配及管理rq的blk_mq_tags,以及一些调度用到的状态数据。State for a hardware queue facing the hardware block device

tag_set:描述一个块设备硬件相关的数据结构,包含硬件队列的数量,队列深度,分配给每个硬件队列的rq管理集合,还包含了软件队列与硬件队列的映射表。tag_set 可以被多个request_queue共享,其中也包含了共享tag_set的request_queue链表

struct blk_mq_tag_set {
    struct blk_mq_queue_map    map[HCTX_MAX_TYPES];        //软硬件队列映射表
    unsigned int        nr_maps;    /* nr entries in map[] */    //映射表数量
    const struct blk_mq_ops    *ops;        //驱动实现的操作集合,会被request_queue继承
    unsigned int        nr_hw_queues;    /* nr hw queues across maps */    //硬件队列数量
    unsigned int        queue_depth;    /* max hw supported */       //硬件队列深度
    ...
    struct blk_mq_tags    **tags;           //为每个硬件队列分配一个rq集合
 
    struct mutex        tag_list_lock;
    struct list_head    tag_list;        //使用该tag_set的request_queue 链表
};

map:  描述软硬件队列映射关系,创建块设备时在blk_mq_alloc_tag_set()中初始化,通过blk_mq_map_queues()设置映射关系。大致策略是:

cpu数大于硬件队列数:多个软件队列映射到一个硬件队列。
        cpu数等于硬件队列数:一对一映射。
        cpu数小于硬件队列数:nr_hw_queues设置为cpu数,一对一映射。


18、alloc_percpu

支持SMP的现代操作系统使用每个cpu上的数据,对于给定的处理器其数据是唯一的;一般来说,每个cpu的数据存放在一个数组中,数组总的每一项对应着系统上的一个存在的处理器;按当前处理器号确定这个数组的当前元素;使用方式如下:

unsigned long my_percpu[NR_CPUS];
int cpu;cpu = get_cpu(); /* 获取当前处理器,并禁止抢占 */
my_percpu[cpu]++; /* 对变量做处理 */
put_cpu(); /* 激活内核抢占 */

上面代码并没有出现锁,这是因为所操作的数据对当前处理器来说是唯一的;

#define get_cpu()        ({ preempt_disable(); __smp_processor_id(); })cpu编号
#define put_cpu()        preempt_enable()

preempt_disable:禁用抢占,这意味着,当线程在preempt_disable <-> preemt_enable范围内执行时,调度程序不会将其置于睡眠状态。如果在当前线程位于该作用域内时发生system-timer-interrupt,则它可能会更新调度程序的帐户表,但不会将上下文切换到另一个线程。

alloc_percpu:给系统中每个处理器分配一个指定类型对象的实例,它是__alloc_percpu的一个封装,原始函数接收两个参数:一个是要分配的实际字节数,一个是分配时要按多少字节对齐;而封装后的alloc_percpu()是按照字节对齐–按照给定的类型的自然边界对齐;

free_percpu:将释放所有处理器上指定的每个cpu数据;

per_cpu_ptr:获取cpu对应的数据


19、raise_softirq_irqoff 、raise_softirq 软中断

当硬件队列只有一个时,在中断中会调用__blk_complete_request函数,并唤醒软中断处理函数blk_done_softirq。

软中断:

软中断标志位:__softirq_pending,每一个在枚举结构中定义的变量软中断都对应该变量中的一位,因为这个变量是 32 位的,所以系统目前最多支持 32 个软中断。

ipi_irqs:这是 IPI 类型的软中断,属于 cpu 与 cpu 之间的中断,在 SMP 系统中,cpu 可以向另一个 cpu 发送中断信号(arm中通过写 gic 触发 SGI 中断),这个中断信号并非硬件上的,所以也属于软中断的一种,IPI 类型的中断可以参考linux内核的中断处理。

当开发者需要执行自己的软中断时,调用 raise_softirq(nr) 函数,该函数就会把需要执行的 irq 标志位置位,它的实现是这样的:

void raise_softirq(unsigned int nr)
{unsigned long flags;local_irq_save(flags);raise_softirq_irqoff(nr);local_irq_restore(flags);
}inline void raise_softirq_irqoff(unsigned int nr)
{//设置标志位__raise_softirq_irqoff(nr);//如果不是在中断环境下调用,使用内核线程来处理if (!in_interrupt())wakeup_softirqd();
}

在blk驱动中的软中断标志为BLOCK_SOFTIRQ = 4,在blk_softirq_init函数中被初始化,该函数通过宏subsys_initcall自动完成调用,中断处理函数为blk_done_softirq


20、nvme_identify_ctrl

  • core.c: nvme_identify_ctrl -> nvme_submit_sync_cmd -> __nvme_submit_sync_cmd -> nvme_alloc_request
  • blk_map.c: blk_rq_map_kern
  • blk_exec.c: blk_execute_rq -> blk_execute_rq_nowait ->  
    • blk_mq_sched.c: blk_mq_sched_insert_request ->

      • blk_map.c: blk_mq_run_hw_queue -> __blk_mq_delay_run_hw_queue ->>kblockd_mod_delayed_work_on (blk_mq_hctx_next_cpu(hctx), &hctx->run_work,msecs_to_jiffies(msecs));hctx->run_work在blk_mq_init_queue()初始化硬件队列时绑定为blk_mq_run_work_fn ->

        • blk_mq_run_work_fn -> __blk_mq_run_hw_queue ->

          • blk_mq_sched.c: blk_mq_sched_dispatch_requests -> blk_mq_do_dispatch_sched or blk_mq_do_dispatch_ctx  ->

            • blk_map.c: blk_mq_dispatch_rq_list -> [q->mq_ops->queue_rq(hctx, &bd)];nvme的驱动中queue_rq都指向了nvme_queue_rq ->,初始化如下,
              static const struct blk_mq_ops nvme_mq_admin_ops = {
                  .queue_rq    = nvme_queue_rq,
                  .complete    = nvme_pci_complete_rq,
                  .init_hctx    = nvme_admin_init_hctx,
                  .exit_hctx      = nvme_admin_exit_hctx,
                  .init_request    = nvme_init_request,
                  .timeout    = nvme_timeout,
              };​​​​​​​
              static const struct blk_mq_ops nvme_mq_ops = {
                  .queue_rq    = nvme_queue_rq,
                  .complete    = nvme_pci_complete_rq,
                  .commit_rqs    = nvme_commit_rqs,
                  .init_hctx    = nvme_init_hctx,
                  .init_request    = nvme_init_request,
                  .map_queues    = nvme_pci_map_queues,
                  .timeout    = nvme_timeout,
                  .poll        = nvme_poll,
              };

              • ​​​​​​​​​​​​​​nvme/host/​​​​​​​​​​​​​​​​​​​​​pci.c: nvme_queue_rq

                • ​​​​​​​​​​​​​​nvme_setup_cmd 使用rq中的cmd拷贝给新的cmd变量,并根据rq->cmd_flags对cmd做调整,最后将cmd-> common.command_id = rq->tag;rq->tag在nvme_alloc_request -> blk_mq_alloc_request -> blk_mq_get_request -> blk_mq_get_tag -> __blk_mq_get_tag -> __sbitmap_queue_get中分配,然后在 blk_mq_rq_ctx_init 函数中赋值给rq->tag。
                • nvme_map_data  组装数据
                • nvme_submit_cmd 拷贝cmd到sq的对应的dma地址nvmeq->sq_cmds中,并写doorbell通知设备读取sq命令

21、_sbitmap_queue_get、 struct sbitmap_queue

上述中tag最终通过__sbitmap_queue_get(hctx->tags->bitmap_tags)分配出来。

hctx->tags->bitmap_tags的初始化:在blk_mq_init_bitmap_tags接口中使用bt_alloc(&tags->bitmap_tags, depth, round_robin, node)完成初始化。


22、blk_mq_rq_to_pdu 、 nvme_req

/** Driver command data is immediately after the request. So subtract request* size to get back to the original request, add request size to get the PDU.*/
static inline struct request *blk_mq_rq_from_pdu(void *pdu)
{return pdu - sizeof(struct request);
}
static inline void *blk_mq_rq_to_pdu(struct request *rq)
{return rq + 1;
}
static inline struct nvme_request *nvme_req(struct request *req)
{return blk_mq_rq_to_pdu(req);
}

为何rq + 1的地址就变成了struct nvme_request *了 呢,因为在nvme_alloc_admin_tags里的dev->admin_tagset.cmd_size = nvme_cmd_size(struct nvme_iod);可知,分配空间的时候,每个request都是配备有额外的空间的,大小就是通过cmd_size来指定的。所以在request之后紧跟着的就是额外的空间用于放nvme_iod。

struct nvme_iod {struct nvme_request req;struct nvme_queue *nvmeq;bool use_sgl;int aborted;int npages;       /* In the PRP list. 0 means small pool in use */int nents;      /* Used in scatterlist */dma_addr_t first_dma;unsigned int dma_len; /* length of single DMA segment mapping */dma_addr_t meta_dma;struct scatterlist *sg;
};

基于Linux 5.4.18的nvme驱动学习 - Linux相关概念 (一)相关推荐

  1. linux设备驱动学习,linux设备驱动学习4

    Linux设备驱动程序学习(4) -高级字符驱动程序操作[(1)ioctl and llseek] 今天进入<Linux设备驱动程序(第3版)>第六章高级字符驱动程序操作的学习. 一.io ...

  2. hp chromebook11 linux,又有18款Chromebook即将支持Linux应用

    Acer的Chromebook 13和Chromebook Spin 13将会成为出厂预装支持Linux应用的两款设备,而惠普的Chromebook X2会成为首款支持Linux应用的可分离Chrom ...

  3. Unix/Linux操作系统分析实验四 设备驱动: Linux系统下的字符设备驱动程序编程

    Unix/Linux操作系统分析实验一 进程控制与进程互斥 Unix/Linux操作系统分析实验二 内存分配与回收:Linux系统下利用链表实现动态内存分配 Unix/Linux操作系统分析实验三 文 ...

  4. linux管道符查看家目录,深入学习Linux之Linux中的管道符 | 和grep,awk,cut命令

    学习Linux期间最常用三个命令和一个符号,管道符 | 和grep,awk,cut,并且它们几个经常一起使用,简直就是亲兄弟呀.所以我将他们单独整理 为了让大家更容易的看清楚示例,我将附上测试文件,文 ...

  5. 欢迎关注我的微信公众账号:Linux技巧(微信号:irefactoring),一起学习Linux知识~~~...

    最近很少写博客了,一方面是近期工作比较繁忙,第二是把精力都放在GitHub和读源码去了. 申请了一个微信公众账号:Linux技巧 微信ID:irefactoring(意思是爱重构) ========= ...

  6. linux系统最小化快捷键,如果你在学习linux,Unix那么快捷键你知道多少个

    我们学习Linux的朋友都知道,使用命令是快速操作一个系统的快捷方式,其实无论是在操作Windows或者是linux熟悉掌握好相应的命令都可以让你更好的提高办事效率,如果你想成为一个电脑大神,那么就需 ...

  7. linux安装两个独立显卡驱动,Manjaro Linux 双显卡安装步骤及独立显卡运行游戏(Nvidia GeForce GTX 980m)...

    manjaro是基于Arch Linux的一个发行版,继承了Arch滚动更新的特点,也可用使用AUR上大量的软件,开箱即用. 安装manjaro是十分简单的事,但对于双显卡的支持来说,还是存在BUG, ...

  8. linux系统编程课程改革,项目驱动的Linux操作系统课程教学改革

    摘 要 <Linux操作系统>是一门应用性很强的课程,在技师学院已被列为计算机网络技术专业的教学计划.在传统的教学模式中,教师往往按照教材编排的顺序,把知识介绍给学生,而不是把知识的应用方 ...

  9. 主板linux驱动程序,nVIDIA nForce系列主板芯片组驱动For Linux 下载_驱动下载_太平洋下载中心...

    nVIDIA nForce系列主板芯片组最新驱动1.23版For Linux(2007年9月2日发布)目前Linux的用户群不断壮大,各种版本的Liunx系统如雨后春笋般的出现.各大硬件设备供应商都积 ...

最新文章

  1. linux命令使用示例:查看某目录属于哪个分区
  2. Visual Studio 2017全面上市
  3. 基于Passthru的NDIS开发的个人理解
  4. STM32 system_stm32f10x.c文件分析
  5. path、classpath理解
  6. java test20006_java 数组 (数组个数小于2000)
  7. Lotus Domino服务器及其应用系统的高级管理(2)
  8. 转iPhone开发的门槛
  9. SDL2源码分析1:初始化(SDL_Init())
  10. R语言安装教程 | 图文介绍超详细
  11. 机器学习基石 作业一
  12. 用python自动制作ppt——第四讲——插入图片
  13. Android 版本4.12 微信,安卓4.12微信下载
  14. 5.5 时间序列预测
  15. 【排序专训】练习题 士兵站队(中位数应用) 解题报告
  16. 6.3 Annihilating Polynomials
  17. 李开复写给中国大学生的七封信(6/7)
  18. MP4/QuickTime的“ftyp” 名称完整列表
  19. 赖床星人的枕头arduino
  20. SQL:统计每5min在线人数思路

热门文章

  1. 弘玑|数字员工赋能金融转型,迈向更加高效灵活的运营模式
  2. 录制课程用什么软件好?3款超好用的课程视频录课软件
  3. 从显示一张图片开始学习OpenGL ES
  4. Flutter Dio的简易封装和demo
  5. java独步寻花,江畔独步寻花
  6. 任意大小 内存池 c语言,C语言内存池使用模型-1 - Mr.南柯 - 51Testing软件测试网 51Testing软件测试网-软件测试人的精神家园...
  7. 雷达威力计算 matlab,威力雷达指标
  8. 腾讯2018春招实习生和秋招面试问题
  9. 各大互联网软件公司校招时间表大盘点
  10. 简单内存泄漏检测方法,解决Detected memory leaks!问题 .