Rongfeng 的个人资料凤影渡霞照片日志列表更多 工具 帮助

日志


8月2日

PERFORMANCE-MONITORING

Performance-Monitoring 是Intel提供的可以监测统计CPU内部所产生事件的一组方法。在Intel的手册上介绍了两类CPU事件监测方法:architectural performance monitoring 和 non-architectural performance monitoring。Architectural performance monitoring与平台(CPU系列)无关但所能监测的事件少;non-architectural performance monitoring与平台密切相关,能监测大量事件。我仅关注architectural performance monitoring。
Architectural performance monitoring介绍
CPU通过两个寄存器来完成事件监测工作:事件选择寄存器IA32_PERFEVTSELx ( programming performance event select registers)和计数器IA32_PMCx (performance monitoring counter)。在计数前,设置事件选择寄存器并将计数器清零;计数结束时,读取计数器。
IA32_PERFEVTSELx与IA32_PMCx都是成对使用,共同完成计数工作。IA32_PMCx寄存器对应于从0xc1开始的一段连续地址,IA32_PERFEVTSELx寄存器对应于从0x186开始的一段连续地址。每种CPU的寄存器位数和可以使用的寄存器对数可能不一样,但可以通过CUPID.0AH:EAX指令来获取这些元信息:
Bits 07 - 00: Version ID of architectural performance monitoring, If >0, architectural performance monitoring capability is supported.
Bits 15- 08: Number of general-purpose performance monitoring counter per logical processor
Bits 23 - 16: Bit width of general-purpose, performance monitoring counter
Bits 31 - 24: Length of EBX bit vector to enumerate architectural performance monitoring events
事件选择寄存器IA32_PERFEVTSELx的配置
 未命名
Event select field (bits 0 through 7):事件选择码区填写需要监测的事件码,这些事件码都是事先定义好的,可以在Intel的手册中查找。
Unit mask (UMASK) field (bits 8 through 15):掩码区填写与事件选择码对应掩码,掩码与事件码共同使用来确定要监测的事件,掩码与事件码一样是事先定义好的,可在Intel手册上查找。
USR (user mode) flag (bit 16):标识是否统计CPU处于用户态(CPU处于特权级别为:1、2、3)下发生的事件。可以与下面的OS位配合使用。
OS (operating system mode) flag (bit 17):标识是否统计CPU处于系统态(CPU处于特权级别为0)下发生的事件。可以与上面的USR位配合使用。
EN (Enable Counters) Flag (bit 22):计数允许位。注意:在写计数器IA32_PMCx之前,必须清除计数允许位(设为0)。
Counter mask (CMASK) field (bits 24 through 31):计算器掩码,如它不为零,但事件发生是,只有它小于计数器的值,计数器计数才会增加1。
计数示例
下面代码是统计事件DTLB_MISSES.ANY,其事件码为0x08,掩码为0x01。
//寄存器地址码
#define IA32_PMC0 0xc1
#define IA32_PERFEVTSEL0 0x186
//事件码及其掩码
#define EVENT 0x08
#define MASK 0x01
Int nEventMask, nCount;
Int nEventRegisterHigh,nEventRegisterLow;
nEventMask = IA32_PERFEVTSEL0;
nCount = IA32_PMC0;
nEventRegisterHigh=nEventRegisterLow=0;
//设置事件码及掩码
nEventRegisterLow |= EVENT;
nEventRegisterLow |= MASK<<8;
//设置用户态系统态标识位
nEventRegisterLow |= 1<<16;
nEventRegisterLow |= 1<<17;
//清楚计数允许位
nEventRegisterLow &= ~(1<<22);
//设置事件选择寄存器
wrmsr(nEventMask, -(u32)( nEventRegisterLowl), -1);
//计数器清零
wrmsr(nCount, -(u32)( 0), -1);
//设置计数允许位
nEventRegisterLow |= 1<<22;
wrmsr(nEventMask, -(u32)( nEventRegisterLowl), -1);
。。。。//计数中
//读起计数结果
rdmsr(nCount, nEventRegisterLow, nEventRegisterHigh);
Non-architectural performance monitoring介绍
(还不太了解这方面的知识!)

Linux内存管理

Linux采用的非一致内存访问(NUMA)模型将内存划分为多个节点(node)。但在80x86结构中,使用一个单独的节点来管理所有物理内存。
 Linux将每个内存节点的物理内存划分为3个管理区:
  ZONE_DMA:   包含低于16M的内存页框,为外设准备的
  ZONE_NORMAL:  16M—896M内存页框
  ZONE_HIGHMEM:  高于896M的内存页框,内核直接寻址空间0—896M,高地址必须通过内存映射才能访问,如:永久内核映射、临时内核映射。
 当内核调用一个内存分配函数时必须指明请求页框所在分区。
 内核动态分配内存时,可能因为空闲内存不足而进行内存回收,进而导致内核控制路径的阻塞。但是有些情况下(如:中断、临界区)是不能阻塞内核控制路径的,为此Linux开辟了保留内存池来提高这种原子内存分配请求的成功率。保留内存池就是在每个管理区中划出一小块内存区域,一般的内存请求下不分配出去而留给原子性内存请求。当管理区空闲内存小于某个阈值时就启动页框回收算法,来保证保留内存池的安全。
伙伴系统(buddy system)
 伙伴系统关注大块内存分配,致力于解决外碎片问题。它将所有的空闲页框分成11组,保存在11个链表中,每个链表分别包含大小为1、2、4、8、16、32、64、128、256、512、1024个连续页框。
 分配算法:首先遍历不小于所需内存大小的最小链表,找到空闲块则分配出去;否则,遍历下一个链表(块大小刚好是上一个链表的两倍)找到空闲块,将空闲块一分为二,其一插入上一个链表,其二分配出去;若本链表也无空闲块,则遍历下一链表直到第11个链表(含有1024个连续页框,即每块4M);否则分配失败。
 回收算法:回收是分配的逆过程,试图合并两个连续的块成一个更大的块。
Slab分配器
(Slab分配器其中的细节还不甚了解)
 Slab分配器关注小块内存(<1K)分配,致力于解决内碎片问题。它将内存区看作对象,有构造函数与析构函数。这些对象的构造初始化耗费大量时间。slab分配器把那些析构后的对象保存在内存中,而不是将它释放掉,在内核下次分配请求时就可直接使用这些保存的对象,大大提高效率。
动态内存管理函数
struct page * alloc_pages(unsigned int gfp_mask, unsigned int order)
分配2order 个连续页,返回第一个页框描述符地址。可用通过以下函数得到内存地址:
void * page_address(struct page *page)
或者通过以下函数直接得到内存地址:
unsigned long __get_free_pages(unsigned int gfp_mask, unsigned int order)
如果只需分配一页内存可以调用以下两个函数:
struct page * alloc_page(unsigned int gfp_mask)
unsigned long __get_free_page(unsigned int gfp_mask)
以下函数可以获得初始化为0的一页内存:
unsigned long get_zeroed_page(unsigned int gfp_mask)
以上分配内存的函数对应的释放内存函数:
void __free_pages(struct page *page, unsigned int order)
void free_pages(unsigned long addr, unsigned int order)
void free_page(unsigned long addr)
上面的内存分配函数都是以页为单位,下面介绍两对以字节为单位的内核内存分配函数:
void * kmalloc(size_t size, int flags)/void kfree(const void *ptr)
分配物理上连续的内存区域;
void * vmalloc(unsigned long size)/ void vfree(void *addr)
分配在虚拟内存空间连续的内存区域,其对应物理内存不一定连续。