PCIe扫盲——基地址寄存器(BAR)详解

本文介绍了PCI设备中基址寄存器(BAR)的作用和配置过程,包括32位内存地址空间、64位内存地址空间以及I/O地址空间的请求示例。在配置时,系统会通过初始化BAR确定地址空间大小,然后设置起始地址。每个设备的BAR数量有限,未使用的BAR应全为0,软件需按顺序检测和操作BAR。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转:http://blog.chinaaet.com/justlxy/p/5100053320

基地址寄存器(BAR)在配置空间(Configuration Space)中的位置如下图所示:

blob.png

其中Type0 Header最多有6个BAR,而Type1 Header最多有两个BAR。这就意味着,对于Endpoint来说,最多可以拥有6个不同的地址空间。但是实际应用中基本上不会用到6个,通常1~3个BAR比较常见。

主要注意的是,如果某个设备的BAR没有被全部使用,则对应的BAR应被硬件全被设置为0,并且告知软件这些BAR是不可以操作的。对于被使用的BAR来说,其部分低比特位是不可以被软件操作的,只有其高比特位才可以被软件操作。而这些不可操作的低比特决定了当前BAR支持的操作类型和可申请的地址空间的大小。

一旦BAR的值确定了(Have been programmed),其指定范围内的当前设备中的内部寄存器(或内部存储空间)就可以被访问了。当该设备确认某一个请求(Request)中的地址在自己的BAR的范围内,便会接受这请求。

下面用几个简单的例子来熟悉BAR的机制:

例1.       32-bit Memory Address Space Request

如下图所示,请求一个4KB的NP-MMIO一般需要以下三个步骤:

blob.png

Step1:如图中(1)所示,未初始化的BAR的低比特(11~4)都是0,高比特(31~12)都是不确定的值。所谓初始化,就是系统(软件)向整个BAR都写1,来确定BAR的可操作的最低位是哪一位。当前可操作的最低位为12,因此当前BAR可申请的(最小)地址空间大小为4KB(2^12)。如果可操作的最低位为20,则该BAR可申请的(最小)地址空间大小为1MB(2^20)。

Step2:完成初始化(写1操作)之后,软件便开始读取BAR的值,来确定每一个BAR对应的地址空间大小和类型。其中操作的类型一般由最低四位所决定,具体如上图右侧部分所示。

Step3:最后一步是,软件向BAR的高比特写入地址空间的起始地址(Start Address)。如图中所示,为0xF9000000。

例2.       64-bit Memory Address Space Request

下面是一个申请64MB P-MMIO地址空间的例子,由于采用的是64-bit的地址,因此需要两个BAR。具体如下图所示:

blob.png

例3.       IO Address Space Request

下面是一个申请IO地址空间的例子,如下图所示:

blob.png

注:需要特别注意的是,软件对BAR的检测与操作(Evaluating)必须是顺序执行的,即先BAR0,然后BAR1,……,直到BAR5。当软件检测到那些被硬件设置为全0的BAR,则认为这个BAR没有被使用。

注:无论是PCI还是PCIe,都没有明确规定,第一个使用的BAR必须是BAR0。事实上,只要设计者原意,完全可以将BAR4作为第一个BAR,并将BAR0~BAR3都设置为不使用。

#include <stdlib.h> #include <stdio.h> #include <iostream> using namespace std; #define INIT_SIZE 5 #define INCREMENT 10 # define OK 1 # define ERROR 0 /* 定义ElemType为int类型 */ typedef int ElemType; void input(ElemType &s); void output(ElemType s); int equals(ElemType a,ElemType b); /* 顺序表类型定义 */ typedef struct { ElemType *elem; //存储空间基地址 int length; //当前长度 int listsize; //当前分配的存储容量 }SqList; void InitList(SqList&L); int ListInsert(SqList &L,int i,ElemType e); void ListTraverse(SqList L,void(*vi)(ElemType ) ); int main() //main() function { SqList A; ElemType e; InitList(A); int n,i; // cout<<"Please input the list number "; cin>>n; for(i=1;i<=n;i++) { cin>>e; ListInsert(A, i, e); } //cout<<"请输入插入的位置:"<<endl; cin>>i; //cout<<"请输入插入的值:"<<endl; input(e); if( ListInsert(A,i,e) ) { cout<<"插入成功,插入后顺序表如下:"<<endl; ListTraverse(A,output) ; } else cout<<"插入位置不合法,插入失败!"<<endl; return 0; } /*****ElemType类型元素的基本操作*****/ void input(ElemType &s) { cin>>s; } void output(ElemType s) { cout<<s<<" "; } int equals(ElemType a,ElemType b) { if(a==b) return 1; else return 0; } /*****顺序表的基本操作*****/ void InitList(SqList&L) { // 操作结果:构造一个空的顺序线性表L /********** Begin **********/ /********** End **********/ } int ListInsert(SqList &L,int i,ElemType e) { // 初始条件:顺序线性表L已存在,1≤i≤ListLength(L)+1 // 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 /********** Begin **********/ /********** End **********/ } void ListTraverse(SqList L,void(*vi)(ElemType ) ) { // 初始条件:顺序线性表L已存在 // 操作结果:依次对L的每个数据元素调用函数vi()输出 /********** Begin **********/ /********** End **********/ }
最新发布
03-22
<think>好的,我现在需要帮助用户解决关于C++顺序表实现的问题,特别是InitList、ListInsert和ListTraverse函数的实现。首先,我需要回忆顺序表的基本概念和结构。顺序表通常是使用数组来存储数据,并且需要记录当前长度和容量。 用户提到的InitList函数应该是初始化顺序表,这涉及到分配内存空间,并初始化长度和容量。我需要确保在初始化时正确分配内存,并处理可能的分配失败情况。比如,使用new来动态分配数组,设置初始容量,并将长度设为0。 接下来是ListInsert函数,也就是插入操作。这里需要考虑插入的位置是否合法,比如位置是否在1到当前长度+1之间。如果顺序表已满,可能需要扩容。扩容时,通常的做法是重新分配更大的内存空间,比如原来的两倍,然后将旧数据复制过去,释放旧内存,并更新容量。插入元素时,需要将插入位置之后的元素后移,腾出空间,然后插入新元素,并增加长度。 然后是ListTraverse函数,遍历顺序表。这应该是简单的循环,从0到长度-1依次访问每个元素,并进行处理,比如打印出来。需要注意的是,遍历时的索引范围和循环条件是否正确。 我还需要参考用户提供的引用内容。引用[1]中的示例代码展示了遍历函数,使用for循环输出元素,这可以作为ListTraverse的参考。引用[2]提到了初始化、插入、删除等功能的实现,但没有给出具体代码,但说明用户可能希望看到完整的结构。 接下来,我需要确保代码符合C++的语法规范,比如使用结构体或类来封装顺序表,处理内存分配和释放,避免内存泄漏。例如,在初始化时使用new分配数组,在销毁时使用delete[]释放内存。在插入时检查是否需要扩容,并正确处理元素移动。 可能的问题点包括:插入位置的合法性判断,扩容时的内存管理,以及遍历时的边界条件。需要确保这些部分代码正确无误,并且有适当的错误处理,比如插入位置不合法时返回错误码。 另外,用户可能需要完整的示例代码,包括结构体定义和各函数的实现,所以需要将这些部分组织清楚,并添加注释说明每个步骤的作用。同时,需要考虑代码的可读性,方便用户理解和应用。 最后,生成相关问题时要围绕顺序表的实现,比如扩容策略、时间复杂度、其他操作如删除和查找的实现,以及与其他数据结构的比较等。这些问题可以帮助用户进一步深入学习顺序表的相关知识。</think>以下是C++顺序表实现的关键函数实现说明及代码演示: ### 一、顺序表结构定义 ```cpp #define INIT_SIZE 10 #define INCREMENT 5 typedef struct { int* elem; // 存储空间基地址 int length; // 当前长度 int capacity; // 当前容量 } SqList; ``` ### 二、初始化函数实现 ```cpp bool InitList_Sq(SqList &L) { L.elem = new int[INIT_SIZE]; if (!L.elem) return false; L.length = 0; L.capacity = INIT_SIZE; return true; } ``` 初始化过程需注意: 1. 动态内存分配使用`new`运算符 2. 初始容量设为预定义常量 3. 初始化长度为0[^2] ### 三、插入操作实现 ```cpp bool ListInsert_Sq(SqList &L, int pos, int e) { if (pos < 1 || pos > L.length + 1) return false; if (L.length >= L.capacity) { int* newbase = new int[L.capacity + INCREMENT]; if (!newbase) return false; for (int i=0; i<L.length; i++) newbase[i] = L.elem[i]; delete[] L.elem; L.elem = newbase; L.capacity += INCREMENT; } for (int i=L.length; i>=pos; i--) L.elem[i] = L.elem[i-1]; L.elem[pos-1] = e; L.length++; return true; } ``` 插入逻辑要点: 1. 位置合法性检查(1 ≤ pos ≤ length+1) 2. 容量不足时自动扩容 3. 元素后移操作时间复杂度为$O(n)$[^1] ### 四、遍历操作实现 ```cpp void ListTraverse_Sq(SqList &L) { for(int i=0; i<L.length; i++) { cout << L.elem[i]; if(i != L.length-1) cout << ","; } cout << endl; } ``` 遍历注意事项: - 使用循环结构访问每个元素 - 下标从0开始到length-1结束 ### 五、完整使用示例 ```cpp int main() { SqList L; InitList_Sq(L); for(int i=1; i<=15; i++) ListInsert_Sq(L, i, i*10); ListTraverse_Sq(L); return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值