<?xml version="1.0" encoding="gb2312"?>
<rss version="2.0">
<channel>
 <title><![CDATA[华镭博客]]></title>
 <link><![CDATA[http://blog.openrays.org]]></link>
 <description><![CDATA[Latest 20 blogs of comcat]]></description>
 <copyright><![CDATA[Copyright(C) 华镭博客]]></copyright>
 <generator><![CDATA[PHPWind BLOG by PHPWind Studio]]></generator>
 <lastBuildDate><![CDATA[Sat, 19 May 2012 13:28:55 +0000]]></lastBuildDate>
  <image>
 <url><![CDATA[http://blog.openrays.org/image/rss.gif]]></url>
 <title><![CDATA[PHPWind BLOG]]></title>
 <link><![CDATA[http://blog.openrays.org]]></link>
 <description><![CDATA[华镭博客]]></description>
  </image>
<item>
 <title><![CDATA[MIPS Cache Architecture 分析文档]]></title>
 <description><![CDATA[文档见： [url]http://people.openrays.org/~comcat/mydoc/mips.cache.arch.pdf[/url] 

包括 MIPS Linux Cache 管理的关键代码分析]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=481]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Sat, 28 Jun 2008 17:15:59 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[Power5 microarchitecture notes]]></title>
 <description><![CDATA[0. both binary and structural compatibility with existing Power4

1. CMP(Chip Multi-Processor), 2 cores

2. SMT(Simultaneous Multi-Threading), 2 threads per core (greater use of chip resources but consumes greater power)

3. Interconnections

 &nbsp; Shared L2 cache, with crossbar-like switch between cores and L2(CIU, Core Interface Unit)

 &nbsp; Shared Bus Fabric, to maintain coherence between L2, memory and I/O units.

4. 4 intructions issues per clock per core

5. Cache

 &nbsp; L1: 64/32KB per core
 &nbsp; L2: 1.9MB shared
 &nbsp; L3: 36MB (off-chip)

6. 17.2 GB/s Peak memory bandwidth

7. 1.9GHz Clock rate

8. 276M Transistors

9. 389 mm^2

10. 125W Power
]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=466]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Sun, 25 Nov 2007 23:56:32 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[Linux 上下文切换分析笔记 (MIPS)]]></title>
 <description><![CDATA[[b]1. 内核栈切换 (MIPS)[/b]

调度切换至一个进程时，根据 task_struct-&gt;thread_info 的值设置 *kernelsp（当前正在运行进程之内核栈栈底），其值为 thread_info + THREAD_SIZE - 32（MIPS 下，使用 set_saved_sp 宏）。


[b]2. 异常、中断寄存器的保存 (MIPS)[/b]

使用SAVE_SOME 保存上下文时，如发现从用户态切入核心态，则首先用 get_saved_sp 宏，将*kernelsp 置入sp。然后在内核栈上分配 PT_SIZE（=sizeof(struct pt_regs)） 大小的空间，作为上下文的保存空间。保存时所有数据精心组织，最后就是一个 struct pt_regs 结构。

若是用户态 --&gt; 内核态，则 k0 = sp, sp = *kernelsp - PT_SIZE，store k0, PT_R29(sp)，保存其它寄存器。

若是内核态 --&gt; 内核态，直接 k0 = sp, sp = sp - PT_SIZE，store k0, PT_R29(sp)，然后保存其它寄存器。


[b]3. 任务切换上下文的保存 (MIPS)[/b]

时钟中断后使用 SAVE_SOME 在内核栈/用户栈（取决于当时所在模式）上保存 $0, $2, $3, $4~$7, $8~$9(64bit), $25, $28, $29, $31, STATUS, CAUSE, EPC。

后在 switch_to 中保存正在运行任务的上下文：

保存 STATUS，使用 cpu_save_nonscratch 保存$16~$23, $29(sp), $30，以及 $31, 有FPU还要 fpu_save_double 保存FPU的寄存器。所有都保存于thread_struct 结构中，该结构为 task_struct 的一部分。

这些保存的是 switch_to 前后的上下文


然后将将要运行的任务上下文加载：

$28 &lt;---- &thread_info
cpu_restore_nonscratch 恢复 $16~$23, $29(sp), $30
*(kernelsp) &lt;---- &thread_info + THREAD_SIZE - 32
恢复 thread_struct 中保存的 STATUS（bit 0, bit 8~15 用当前STATUS值替换）

现在恢复时也在 switch_to 前后，神不知鬼不觉的替换了，所有操作都是由switch_to调用叶函数resume完成。

do_IRQ 返回后，sp恢复（减多少，对称的加多少，因此与初值无关，最终指向新进程的 pt_regs 结构）ref_from_irq 则时钟中断返回（当时被中断时的环境），然后 eret 跳回到用户态（或者被时钟中断的核心态）继续运行。


[b]4. switch_to 为何不需保存$0~$15 $24~$27 (MIPS)[/b]

假如内核要从进程A切换到进程B，流程大概是这样：

进程A --&gt; 时钟中断 --&gt; schedule --&gt; switch_to(resume) --&gt; schedule 返回 --&gt; ret_from_irq --&gt; 进程B

switch_to 保存于 A task_struct-&gt;thread_struct 中的状态是整个调用链中的 switch_to 宏附近的处理器状态

因此将 sp 指向保存于 B task_struct-&gt;thread_struct 中的 sp 时，实际上就相当于恢复到当时进程B在switch_to前后的状态：

进程B --&gt; 时钟中断 --&gt; schedule --&gt; switch_to 

switch_to 是一个宏，其中调用了，位于 arch/mips/kernel/r4k_switch.S 中的一个叶函数（不改变静态寄存器的值，不用压栈、出栈）resume，因此进入 resume 前，ABI 规定的一些非静态寄存器的值就再也不用了，故这些非静态值无需保存。

至于静态寄存器的值，函数用之前都会保存于栈上，最后恢复之，子函数调用不会改变其值。因此静态寄存器保存的是当时运行状态的一部分。如这种情况：

schedule 中编译器用 s0 保存一个重要的状态变量，因此进入schedule首先保存s0的值，使用 s0 参与运算，switch_to 后，又要根据 s0 判断进一步的动作。

这个时候就要将 s0 恢复为进程B当时在此点的值。总之注意，switch_to 后所有操作延续的是进程B的：

 schedule 返回 --&gt; ret_from_irq --&gt; 进程B  


[b]5. 中断处理时可否睡眠问题[/b]

Linux 设计中，中断处理时不能睡眠，这个内核中有很多保护措施，一旦检测到内核会异常。

当一个进程A因为中断被打断时，中断处理程序会使用 A 的内核栈来保存上下文，因为是“抢”的 A 的CPU，而且用了 A 的内核栈，因此中断应该尽可能快的结束。如果 do_IRQ 时又被时钟中断打断，则继续在 A 的内核栈上保存中断上下文，如果发生调度，则 schedule 进 switch_to，又会在 A 的 task_struct-&gt;thread_struct 里保存此时时种中断的上下文。

假如其是在睡眠时被时钟中断打断，并 schedule 的话，假如选中了进程 A，并 switch_to 过去，时钟中断返回后则又是位于原中断睡眠时的状态，抛开其扰乱了与其无关的进程A的运行不说，这里的问题就是：该如何唤醒之呢？？

另外，和该中断共享中断号的中断也会受到影响。
]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=455]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Mon, 24 Sep 2007 16:29:21 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[发布MIPS Linux 异常中断代码分析文档]]></title>
 <description><![CDATA[以龙芯2E（福珑mini PC平台）为例，详细分析了MIPS Linux 下的异常处理、中断处理、系统调用。

这里下载之： [url]http://people.openrays.org/~comcat/mydoc/mips.linux.inter.pdf[/url]]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=450]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Mon, 10 Sep 2007 19:06:46 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[FULONG MINI-PC Architecture]]></title>
 <description><![CDATA[see figure:

 [p_w_upload=102] ]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=448]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Tue, 04 Sep 2007 16:47:53 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[MIPS 下非对齐访问的问题]]></title>
 <description><![CDATA[1. 问题

MIPS 下使用访存指令读取或写入数据单元时，目标地址必须是所访问之数据单元字节数的整数倍，这个叫做地址对齐。

比如在 MIPS 平台上，lh 读取一个半字时，存储器的地址必须是 2 的整数倍； lw 读取一个字时，存储器的地址必须是 4的整数倍； sd 写入一个双字时，存储器的地址必须是 8 的整数倍。倘若访存时，目标地址不对齐，则会引起异常,典型的是系统提示“总线错误”后，直接杀死进程。

看一个测试程序（龙芯2E平台）：

#include &lt;stdio.h&gt;
#include &lt;sys/sysmips.h&gt;

unsigned short data[] = {
 &nbsp;  0x1, 0x2, 0x3, 0x4,
 &nbsp;  0x55aa, 0x66bb, 0x77cc, 0x0000,
};

inline void unaligned_access(unsigned short * const row)
{
 &nbsp;  asm volatile
 &nbsp;  &nbsp;   (
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set mips3\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set noreorder\\n\\t&quot;

 &nbsp;  &nbsp;  &nbsp;  &nbsp; //&quot;lwr $10, 1(%1)\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; //&quot;lwl $11, 4(%1)\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; //&quot;or  $10, $11\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;ld $10, 2(%1)\\n\\t&quot; &nbsp;  &nbsp; /* %1 is double word aligned, %1+2 is double word unaligned */
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;sd $10, %0\\n\\t&quot;

 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set reorder\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set mips0\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; : &quot;=m&quot;(*(row))
 &nbsp;  &nbsp;  &nbsp;  &nbsp; : &quot;r&quot;(row+4)
 &nbsp;  &nbsp;  &nbsp;  &nbsp; : &quot;$8&quot;, &quot;$9&quot;, &quot;$10&quot;
 &nbsp;  &nbsp;   );
}

int main()
{
 &nbsp;  printf(&quot;---------------------------------------------------------\\n&quot;);
 &nbsp;  printf(&quot;  Testing Godson2 unaligned access Instruction \\n&quot;);
 &nbsp;  printf(&quot;---------------------------------------------------------\\n\\n&quot;);

//  sysmips(MIPS_FIXADE, 0);

 &nbsp;  unaligned_access(data);

 &nbsp;  printf(&quot;result is: 0x%04x %04x %04x %04x\\n&quot;, data[3], data[2], data[1], data[0]);

}

程序运行后系统提示“非法指令”后退出。


CISC 下（如x86）访存时，如果目标地址不对齐，CPU 不会陷入异常，因为其内部有处理非对齐访问的微程序。


2. 解决

高级语言中一般不会遇到这种问题，编译器常常会处理好数据类型的对齐。但万一遇到、抑或在汇编里遇到，避不开怎么办？

可以使用 MIPS 的指令集里提供的 lwr/lwl, swr/srl, ldr/ldl, sdr/sdl 指令对。关于他们的原理可以用下图来简单的示意一下（以ldr/ldl 为例，其他类似）：

 [p_w_upload=99] 


上图解释的是小端模式下的情况，大端模式的情况则相反：首先 ldl  t0, 0(t1)，然后再 ldr  t0, 7(t1)。

可以看到无论大端模式还是小端模式，非对齐访问的解决都是将原来的一条指令（对齐访问）完成的事分两步完成，即首先取始地址 addr 到下一个对齐地址处的部分数据，置入寄存器右部（小端），（大端置入左部(高位)），然后取从该对齐地址到 addr + len - 1 处的部分数据（len 为数据单元长度，半字为2, 双字为8），置入寄存器左部（小端）。

如小端机器上，始地址为 t1 = 0x1022，则：

 ldr  t0, 0(t1) 取 0x1022~0x1027 到 t0 的右部
 ldl &nbsp; t0, 7(t1) 取 0x1028~0x1029 到 t0 的左部

注意上述指令的后缀 r(right), l(left) 都是相对寄存器而言，load 操作是把取到的部分数据，置入寄存器的 left 或者 right， store 操作是将寄存器中数据的 left 或者 right 部分，写入目标地址而已。无论大端和小端寄存器的格式都是固定的，即右端为低位，左端为高位。任意第一条ldr/ldl/lwr/lwl/sdr/sdl/swr/swl 只能访问内存的始地址到下一个对齐地址处。

]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=438]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Mon, 23 Jul 2007 17:43:45 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[16KB页，32bit 地址的划分有些问题]]></title>
 <description><![CDATA[32 bit kernel， 16KB 页大小，龙芯2E平台

tlb_refill_handler为：

lui &nbsp;  &nbsp;  &nbsp; k1, %hi(pgd_current)
mfc0 &nbsp;  k0, C0_BADVADDR
lw &nbsp;  &nbsp;  &nbsp; k1, %lo(pgd_current)(k1)
srl &nbsp;  &nbsp;   k0, k0, PGDIR_SHIFT
sll &nbsp;  &nbsp;  &nbsp; k0, k0, PGD_T_LOG2
addu &nbsp;  k1, k1, k0

mfc0 &nbsp;  k0, C0_CONTEXT
lw &nbsp;  &nbsp;  &nbsp; k1, 0(k1)
srl &nbsp;  &nbsp;   k0, k0, 3
andi &nbsp;  &nbsp; k0, k0, (PTRS_PER_PTE/2-1) &lt;&lt; (PTE_T_LOG2 + 1)
addu &nbsp;  k1, k1, k0

lw &nbsp;  &nbsp;  &nbsp; k0, 0(k1)
lw &nbsp;  &nbsp;  &nbsp; k1, sizeof(pte_t)(k1)
srl &nbsp;  &nbsp;   k0, k0, 6
mtc0 &nbsp;  k0, C0_ENTRYLO0
srl &nbsp;  &nbsp;   k1, k1, 6
mtc0 &nbsp;  k1, C0_ENTRYLO1
tlbwr
eret

相应的，直接从正在运行的 kernel 里获取的 tlb_refill_handler 为：

 &nbsp;  &nbsp;  0: &nbsp; 52801b3c &nbsp;  lui k1,0x8052
 &nbsp;  &nbsp;  4: &nbsp; 00401a40 &nbsp;  mfc0 &nbsp;  k0,$8 &nbsp;  &nbsp;  &nbsp; /* 从 BadVaddr 获取转换失败的 VA */
 &nbsp;  &nbsp;  8: &nbsp; 18007b8f &nbsp;  lw  k1,24(k1) &nbsp;  &nbsp;  &nbsp;  /* 读取 PGD 所在基地址 */
 &nbsp;  &nbsp;  c: &nbsp; 82d51a00 &nbsp;  srl k0,k0,0x16 &nbsp;  &nbsp;  /* VA 右移22位 */
 &nbsp;  &nbsp; 10: &nbsp; 80d01a00 &nbsp;  sll k0,k0,0x2
 &nbsp;  &nbsp; 14: &nbsp; 21d87a03 &nbsp;  addu &nbsp;  k1,k1,k0

 &nbsp;  &nbsp; 18: &nbsp; 00201a40 &nbsp;  mfc0 &nbsp;  k0,$4 &nbsp;  &nbsp;  &nbsp;   /* 读取 Context 值 */
 &nbsp;  &nbsp; 1c: &nbsp; 00007b8f &nbsp;  lw  k1,0(k1)
 &nbsp;  &nbsp; 20: &nbsp; c2d01a00 &nbsp;  srl k0,k0,0x3 &nbsp;  &nbsp;  &nbsp;  &nbsp; 
 &nbsp;  &nbsp; 24: &nbsp; f83f5a33 &nbsp;  andi &nbsp;  k0,k0,0x3ff8
 &nbsp;  &nbsp; 28: &nbsp; 21d87a03 &nbsp;  addu &nbsp;  k1,k1,k0

 &nbsp;  &nbsp; 2c: &nbsp; 00007a8f &nbsp;  lw  k0,0(k1)
 &nbsp;  &nbsp; 30: &nbsp; 04007b8f &nbsp;  lw  k1,4(k1)
 &nbsp;  &nbsp; 34: &nbsp; 82d11a00 &nbsp;  srl k0,k0,0x6
 &nbsp;  &nbsp; 38: &nbsp; 00109a40 &nbsp;  mtc0 &nbsp;  k0,$2
 &nbsp;  &nbsp; 3c: &nbsp; 82d91b00 &nbsp;  srl k1,k1,0x6
 &nbsp;  &nbsp; 40: &nbsp; 00189b40 &nbsp;  mtc0 &nbsp;  k1,$3
 &nbsp;  &nbsp; 44: &nbsp; 06000042 &nbsp;  tlbwr
 &nbsp;  &nbsp; 48: &nbsp; 18000042 &nbsp;  eret

Context 之格式为：

63 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;   23 22 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;   4 3 &nbsp;  &nbsp;  0
| &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; | &nbsp;  &nbsp;  BadVPN2 &nbsp;   | &nbsp;  0 &nbsp;  |  

22~4 共 19 位对应导致 TLB miss 之虚地址的31~13（VA[31:13]），因此如下指令

 &nbsp;  &nbsp; 20: &nbsp; c2d01a00 &nbsp;  srl k0,k0,0x3 &nbsp;  &nbsp;  &nbsp;  &nbsp; 
 &nbsp;  &nbsp; 24: &nbsp; f83f5a33 &nbsp;  andi &nbsp;  k0,k0,0x3ff8 &nbsp;  &nbsp;   /* 获取 VA[25:15] 共11位，低3位为0 */

的意图旨在获取 VA[25:15]，并以 2^3=8 字节为单位索引PageTable，实际的索引范围为 2^12 = 4K ，这个与每页容纳的项数相一致(16K/4=4k)。实质上使用 VA[25:14]索引 PageTable，因每次取一对，故而VA[14] 不用。

VA[13:0] 页内位移，没有问题。

但使用 VA[31:22] 索引 PGD 就让人困惑了。32bit kernel 中定义 PGDIR_SHIFT 始终为 22 ，不管页大小为何，这个在4KB页大小下，是正好的，但在16KB下，由于使用 VA[25:15] 索引PageTable，因此用于索引 PGD 的域与索引 PT 的域重叠了4位（25~22）！

如果有2个虚址，VA1[22] = 1，VA2[23] = 1 ，当 TLB miss 时，当前内核的 tlb_miss_handler 是向2个不同的 PGD 入口找寻 PTE 的，尽管按照地址划分他们应该在同一个 PGD 入口才是。

关于地址的划分，应该从低位往高位切分才是，即：首先切取PAGE_SIZE所需位，然后根据PAGE_SIZE，确定索引 PT 的位数。如4KB，则为4K/4=1K, 10 位，16KB则为12 位，64KB 则为14位。

余下的高位作为索引PGD 的位数才是。因此应该是 6 | 12 | 14 才对。

－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－

32 bit 模式下， PGD 索引位与PT 索引位部分重叠并不会导致灾难性的后果，只是会造成核心分配给PT的页过多，浪费空间。

测了一下，改动后的 kernel，启动后 nr_page_table_pages 为 63 左右，而原有kernel 在101左右，多分配了 (101-63)*16KB 作为 PageTable。

另外我写了一个测试程序，使用mmap 映射一个40MB左右的文件在地址 0x2ac94000　附近，然后随机访问该文件中的数据 1亿次，新旧对比，原 kernel 要多分配 13*16KB 作为 PageTable（cat /proc/vmstat）。

－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－

后记：该问题已经提交到linux-mips mail list，最新内核的 PGDIR_SHIFT 定义已经修改为： (2 * PAGE_SHIFT + PTE_ORDER - PTE_T_LOG2)（原来写死为 22），现在可以根据 PAGE_ SHIFT 动态调整了。

]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=435]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Fri, 20 Jul 2007 16:40:07 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[龙芯版 memcpy 的实现]]></title>
 <description><![CDATA[memcpy 是为最常用之函数，多媒体编解码过程中调用频繁，属调用密集型函数，对其性能优化很有意义。

1. 概述

memcpy 所做的操作是把内存中的一块数据复制到内存的另一个地方，也就是内存到
内存的数据拷贝，这个过程需要CPU的参与，即：先从内存取数据到CPU的寄存器，然
后再从寄存器写到内存中。可以用类似如下C 代码实现：

char *dest = (char *)to;
char *src = (char *)from;
int i = size -1;

while(i &gt;= 0)
{
 &nbsp;  &nbsp;  *(dest + i) = *(src + i);
 &nbsp;  &nbsp;  i--;
}

这个在size 比较小时，性能尚可。倘若size 很大，这种每次取一个字节的方式，远
没有充分发挥CPU的数据带宽。作为一种改进方式，我们可以每次取4个字节进行写入，
不足4字节的部分，依然每次取一个字节写入：

int *dest = (int *)to;
int *src = (int *)from;

int word_num = size/4 - 1;

int slice = size%4 - 1;

while(word_num &gt;= 0)
{
 &nbsp;  &nbsp;  *dest= *src;
 &nbsp;  &nbsp;  dest += 4;
 &nbsp;  &nbsp;  src += 4;
 &nbsp;  &nbsp;  word_num--;
}

while(slice &gt;= 0)
{
 &nbsp;  &nbsp;  *((char *)dest + slice) = *((char *)src + slice);
 &nbsp;  &nbsp;  slice--;
}

上面这个就是 memcpy 优化的基本思想。

龙芯2E因为是64位，可以使用ld指令，每次取8个字节写入目标地址。

上面的例子，为了说明问题的方便，没有考虑指针的是否对齐。因为不对齐访问会触发
异常，所以使用汇编码写高性能 memcpy 时，对指针是否对齐要分情况予以周密的处理。


2. glibc 对memcpy的优化分析 

先看看glibc 对memcpy的实现。


 &nbsp;   1 &nbsp;  &nbsp;  #include &lt;endian.h&gt;
 &nbsp;   2 &nbsp;  &nbsp;  #include &lt;sys/asm.h&gt;
 &nbsp;   3 &nbsp;  &nbsp;  #include &quot;regdef.h&quot;


 &nbsp;   4 &nbsp;  &nbsp;  /* void *memcpy(void *dest, const void *src, size_t n);
 &nbsp;   5 &nbsp;  &nbsp;  &nbsp;  a0 &lt;--- dest;  a1 &lt;--- src;  a2 &lt;--- n &nbsp;  &nbsp;  
 &nbsp;   6 &nbsp;  &nbsp;   */


 &nbsp;   7 &nbsp;  &nbsp;  &nbsp;  &nbsp;   .global &nbsp;  &nbsp;  memcpy
 &nbsp;   8 &nbsp;  &nbsp;  &nbsp;  &nbsp;   .ent &nbsp;  &nbsp;  memcpy

 &nbsp;   9 &nbsp;  &nbsp;  memcpy:
 &nbsp;  10 &nbsp;  &nbsp;  &nbsp;  &nbsp;   .set &nbsp;  &nbsp;  noreorder
 &nbsp;  11 &nbsp;  &nbsp;  &nbsp;  &nbsp;   .set &nbsp;  &nbsp;  mips3

 &nbsp;  12 &nbsp;  &nbsp;  &nbsp;  &nbsp;   slti &nbsp;  &nbsp;  t0, a2, 16 &nbsp;  &nbsp;  &nbsp;  &nbsp;   # 小于16字节，则跳转到last16处，由其进行处理
 &nbsp;  13 &nbsp;  &nbsp;  &nbsp;  &nbsp;   bne &nbsp;  &nbsp;  t0, zero, last16
 &nbsp;  14 &nbsp;  &nbsp;  &nbsp;  &nbsp;   move &nbsp;  &nbsp;  v0, a0 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # 目标指针作为返回值写入v0，注意此指令位于延迟槽中

 &nbsp;  15 &nbsp;  &nbsp;  &nbsp;  &nbsp;   xor &nbsp;  &nbsp;  t0, a1, a0 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # 都对齐，或者都不对齐（低3位相同）
 &nbsp;  16 &nbsp;  &nbsp;  &nbsp;  &nbsp;   andi &nbsp;  &nbsp;  t0, 0x7
 &nbsp;  17 &nbsp;  &nbsp;  &nbsp;  &nbsp;   bne &nbsp;  &nbsp;  t0, zero, shift &nbsp;  &nbsp;  &nbsp;  &nbsp;   # 低3位不相同，则跳转。则肯定有一个不对齐，或者都不对齐（低3位不同）
 &nbsp;  18 &nbsp;  &nbsp;  &nbsp;  &nbsp;   subu &nbsp;  &nbsp;  t1, zero, a1

 &nbsp;  19 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;   # 都对齐，或者都不对齐（低3位相同）
 &nbsp;  20 &nbsp;  &nbsp;  &nbsp;  &nbsp;   andi &nbsp;  &nbsp;  t1, 0x7 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # t1 的值实际上为 8 - (a1 & 0x7)，即a1 加上 t1 就对齐了。
 &nbsp;  21 &nbsp;  &nbsp;  &nbsp;  &nbsp;   beq &nbsp;  &nbsp;  t1, zero, chk8w &nbsp;  &nbsp;  &nbsp;  &nbsp;   # a1 aligned, then branch 

 &nbsp;  22 &nbsp;  &nbsp;  &nbsp;  &nbsp;   subu &nbsp;  &nbsp;  a2, t1 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # 不跳转则 a1 不对齐，此时 a0 肯定不对齐，而且他们的低3位相同
 &nbsp;  23 &nbsp;  &nbsp;  &nbsp;  &nbsp;   ldr &nbsp;  &nbsp;  t0, 0(a1) &nbsp;  &nbsp;  &nbsp;  &nbsp;   # 把低 t1 个字节，使用非对齐访问指令特别照顾 
 &nbsp;  24 &nbsp;  &nbsp;  &nbsp;  &nbsp;   addu &nbsp;  &nbsp;  a1, t1 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # 指针前移，a1 对齐矣 &nbsp;  &nbsp;  
 &nbsp;  25 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sdr &nbsp;  &nbsp;  t0, 0(a0) &nbsp;  &nbsp;  &nbsp;  &nbsp;   # 把低 t1 个字节，写到 a0 开始处的 t1 字节的空间里
 &nbsp;  26 &nbsp;  &nbsp;  &nbsp;  &nbsp;   addu &nbsp;  &nbsp;  a0, t1 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # 指针前移，a0 对齐矣

 &nbsp;  27 &nbsp;  &nbsp;  chk8w:
 &nbsp;  28 &nbsp;  &nbsp;  &nbsp;  &nbsp;   andi &nbsp;  &nbsp;  t0, a2, 0x3f &nbsp;  &nbsp;  &nbsp;  &nbsp;   # t0 = a2 % 64
 &nbsp;  29 &nbsp;  &nbsp;  &nbsp;  &nbsp;   beq &nbsp;  &nbsp;  t0, a2, chk1w &nbsp;  &nbsp;  &nbsp;  &nbsp;   # 小于 64 字节则跳转到chk1w
 &nbsp;  30 &nbsp;  &nbsp;  &nbsp;  &nbsp;   subu &nbsp;  &nbsp;  a3, a2, t0 &nbsp;  &nbsp;  &nbsp;  &nbsp;   # a3 = a2 / 64
 &nbsp;  31 &nbsp;  &nbsp;  &nbsp;  &nbsp;   addu &nbsp;  &nbsp;  a3, a1 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # a3 = end address of loop
 &nbsp;  32 &nbsp;  &nbsp;  &nbsp;  &nbsp;   move &nbsp;  &nbsp;  a2, t0 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # a2 = what will be left after loop，是为不足64字节部分
 &nbsp;  33 &nbsp;  &nbsp;  lop8w: &nbsp;  &nbsp;  
 &nbsp;  34 &nbsp;  &nbsp;  &nbsp;  &nbsp;   ld &nbsp;  &nbsp;  t0, 0(a1) &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;   # 每次循环处理64个字节
 &nbsp;  35 &nbsp;  &nbsp;  &nbsp;  &nbsp;   ld &nbsp;  &nbsp;  t1, 8(a1)
 &nbsp;  36 &nbsp;  &nbsp;  &nbsp;  &nbsp;   ld &nbsp;  &nbsp;  t2, 16(a1)
 &nbsp;  37 &nbsp;  &nbsp;  &nbsp;  &nbsp;   ld &nbsp;  &nbsp;  t3, 24(a1)
 &nbsp;  38 &nbsp;  &nbsp;  &nbsp;  &nbsp;   ld &nbsp;  &nbsp;  ta0, 32(a1)
 &nbsp;  39 &nbsp;  &nbsp;  &nbsp;  &nbsp;   ld &nbsp;  &nbsp;  ta1, 40(a1)
 &nbsp;  40 &nbsp;  &nbsp;  &nbsp;  &nbsp;   ld &nbsp;  &nbsp;  ta2, 48(a1)
 &nbsp;  41 &nbsp;  &nbsp;  &nbsp;  &nbsp;   ld &nbsp;  &nbsp;  ta3, 56(a1)
 &nbsp;  42 &nbsp;  &nbsp;  &nbsp;  &nbsp;   addiu &nbsp;  &nbsp;  a0, 64 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # 指针前移 64 字节
 &nbsp;  43 &nbsp;  &nbsp;  &nbsp;  &nbsp;   addiu &nbsp;  &nbsp;  a1, 64 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # 同上
 &nbsp;  44 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sd &nbsp;  &nbsp;  t0, -64(a0)
 &nbsp;  45 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sd &nbsp;  &nbsp;  t1, -56(a0)
 &nbsp;  46 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sd &nbsp;  &nbsp;  t2, -48(a0)
 &nbsp;  47 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sd &nbsp;  &nbsp;  t3, -40(a0)
 &nbsp;  48 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sd &nbsp;  &nbsp;  ta0, -32(a0)
 &nbsp;  49 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sd &nbsp;  &nbsp;  ta1, -24(a0)
 &nbsp;  50 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sd &nbsp;  &nbsp;  ta2, -16(a0)
 &nbsp;  51 &nbsp;  &nbsp;  &nbsp;  &nbsp;   bne &nbsp;  &nbsp;  a1, a3, lop8w
 &nbsp;  52 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sd &nbsp;  &nbsp;  ta3, -8(a0)

 &nbsp;  53 &nbsp;  &nbsp;  chk1w:
 &nbsp;  54 &nbsp;  &nbsp;  &nbsp;  &nbsp;   andi &nbsp;  &nbsp;  t0, a2, 0x7 &nbsp;  &nbsp;  &nbsp;  &nbsp;   # 8 or more bytes left?
 &nbsp;  55 &nbsp;  &nbsp;  &nbsp;  &nbsp;   beq &nbsp;  &nbsp;  t0, a2, last16 &nbsp;  &nbsp;  &nbsp;  &nbsp;   # less than 8 bytes, then jump to last16
 &nbsp;  56 &nbsp;  &nbsp;  &nbsp;  &nbsp;   subu &nbsp;  &nbsp;  a3, a2, t0 &nbsp;  &nbsp;  &nbsp;  &nbsp;   # Yes, handle them one dword at a time
 &nbsp;  57 &nbsp;  &nbsp;  &nbsp;  &nbsp;   addu &nbsp;  &nbsp;  a3, a1 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # a3 again end address
 &nbsp;  58 &nbsp;  &nbsp;  &nbsp;  &nbsp;   move &nbsp;  &nbsp;  a2, t0
 &nbsp;  59 &nbsp;  &nbsp;  lop1w:
 &nbsp;  60 &nbsp;  &nbsp;  &nbsp;  &nbsp;   ld &nbsp;  &nbsp;  t0, 0(a1)
 &nbsp;  61 &nbsp;  &nbsp;  &nbsp;  &nbsp;   addiu &nbsp;  &nbsp;  a0, 8
 &nbsp;  62 &nbsp;  &nbsp;  &nbsp;  &nbsp;   addiu &nbsp;  &nbsp;  a1, 8
 &nbsp;  63 &nbsp;  &nbsp;  &nbsp;  &nbsp;   bne &nbsp;  &nbsp;  a1, a3, lop1w
 &nbsp;  64 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sd &nbsp;  &nbsp;  t0, -8(a0)

 &nbsp;  65 &nbsp;  &nbsp;  last16: &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # 可改进
 &nbsp;  66 &nbsp;  &nbsp;  &nbsp;  &nbsp;   blez &nbsp;  &nbsp;  a2, lst16e &nbsp;  &nbsp;  &nbsp;  &nbsp;   # Handle last 16 bytes, one at a time
 &nbsp;  67 &nbsp;  &nbsp;  &nbsp;  &nbsp;   addu &nbsp;  &nbsp;  a3, a2, a1 &nbsp;  &nbsp;  &nbsp;  &nbsp;   # a3 为终止标志
 &nbsp;  68 &nbsp;  &nbsp;  lst16l:
 &nbsp;  69 &nbsp;  &nbsp;  &nbsp;  &nbsp;   lb &nbsp;  &nbsp;  t0, 0(a1)
 &nbsp;  70 &nbsp;  &nbsp;  &nbsp;  &nbsp;   addiu &nbsp;  &nbsp;  a0, 1
 &nbsp;  71 &nbsp;  &nbsp;  &nbsp;  &nbsp;   addiu &nbsp;  &nbsp;  a1, 1
 &nbsp;  72 &nbsp;  &nbsp;  &nbsp;  &nbsp;   bne &nbsp;  &nbsp;  a1, a3, lst16l
 &nbsp;  73 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sb &nbsp;  &nbsp;  t0, -1(a0)
 &nbsp;  74 &nbsp;  &nbsp;  lst16e:
 &nbsp;  75 &nbsp;  &nbsp;  &nbsp;  &nbsp;   jr &nbsp;  &nbsp;  ra &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  # Bye, bye
 &nbsp;  76 &nbsp;  &nbsp;  &nbsp;  &nbsp;   nop

 &nbsp;  77 &nbsp;  &nbsp;  shift: &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;   # 为何没有考虑 a1 对齐，a0 不对齐的情况？？
 &nbsp;  78 &nbsp;  &nbsp;  &nbsp;  &nbsp;   subu &nbsp;  &nbsp;  a3, zero, a0 &nbsp;  &nbsp;  &nbsp;  &nbsp;   # a1 不对齐，a0 可能对齐，可能不对齐 
 &nbsp;  79 &nbsp;  &nbsp;  &nbsp;  &nbsp;   andi &nbsp;  &nbsp;  a3, 0x7 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; #  (unoptimized case...)
 &nbsp;  80 &nbsp;  &nbsp;  &nbsp;  &nbsp;   beq &nbsp;  &nbsp;  a3, zero, shft1 &nbsp;  &nbsp;  &nbsp;  &nbsp;   # a0 对齐，a1 不对齐，则跳转到shft1处

 &nbsp;  81 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # 此时 a0不对齐，a1 未知，且低3位不同
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;   # 当a1对齐时，可否直接对a1使用对齐访问，a0使用不对齐访问
 &nbsp;  82 &nbsp;  &nbsp;  &nbsp;  &nbsp;   subu &nbsp;  &nbsp;  a2, a3 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # a2 = bytes left
 &nbsp;  83 &nbsp;  &nbsp;  &nbsp;  &nbsp;   ldr &nbsp;  &nbsp;  t0, 0(a1) &nbsp;  &nbsp;  &nbsp;  &nbsp;   # Take care of first odd part
 &nbsp;  84 &nbsp;  &nbsp;  &nbsp;  &nbsp;   ldl &nbsp;  &nbsp;  t0, 7(a1)
 &nbsp;  85 &nbsp;  &nbsp;  &nbsp;  &nbsp;   addu &nbsp;  &nbsp;  a1, a3
 &nbsp;  86 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sdr &nbsp;  &nbsp;  t0, 0(a0)
 &nbsp;  87 &nbsp;  &nbsp;  &nbsp;  &nbsp;   addu &nbsp;  &nbsp;  a0, a3 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # 到这里，a0 对齐了，a1 反正也不想使其对齐

 &nbsp;  88 &nbsp;  &nbsp;  shft1: &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # 此种情况亦可添加 64 字节为单位的处理块
 &nbsp;  89 &nbsp;  &nbsp;  &nbsp;  &nbsp;   andi &nbsp;  &nbsp;  t0, a2, 0x7
 &nbsp;  90 &nbsp;  &nbsp;  &nbsp;  &nbsp;   subu &nbsp;  &nbsp;  a3, a2, t0 &nbsp;  &nbsp;  &nbsp;  &nbsp;   # a3 为8的倍数
 &nbsp;  91 &nbsp;  &nbsp;  &nbsp;  &nbsp;   addu &nbsp;  &nbsp;  a3, a1 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # a3 = end address of loop
 &nbsp;  92 &nbsp;  &nbsp;  shfth:
 &nbsp;  93 &nbsp;  &nbsp;  &nbsp;  &nbsp;   ldr &nbsp;  &nbsp;  t1, 0(a1) &nbsp;  &nbsp;  &nbsp;  &nbsp;   # Limp through, dword by dword
 &nbsp;  94 &nbsp;  &nbsp;  &nbsp;  &nbsp;   ldl &nbsp;  &nbsp;  t1, 7(a1)
 &nbsp;  95 &nbsp;  &nbsp;  &nbsp;  &nbsp;   addiu &nbsp;  &nbsp;  a0, 8
 &nbsp;  96 &nbsp;  &nbsp;  &nbsp;  &nbsp;   addiu &nbsp;  &nbsp;  a1, 8
 &nbsp;  97 &nbsp;  &nbsp;  &nbsp;  &nbsp;   bne &nbsp;  &nbsp;  a1, a3, shfth
 &nbsp;  98 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sd &nbsp;  &nbsp;  t1, -8(a0)
 &nbsp;  99 &nbsp;  &nbsp;  &nbsp;  &nbsp;   b &nbsp;  &nbsp;  last16 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # Handle anything which may be left
 &nbsp; 100 &nbsp;  &nbsp;  &nbsp;  &nbsp;   move &nbsp;  &nbsp;  a2, t0

 &nbsp; 101 &nbsp;  &nbsp;  &nbsp;  &nbsp;   .set &nbsp;  &nbsp;  reorder
 &nbsp; 102 &nbsp;  &nbsp;  &nbsp;  &nbsp;   .end &nbsp;  &nbsp;  memcpy

下面详细分析之：

1. 12-14行，首先对要复制的数据大小进行判断，小于 16 字节的直接跳转到 last16 处进行处理。

2. 15-17行，在于区分 dest 与 src 指针对齐的情况。两者低 3 位相同（异或为0），则要么都对齐，
 &nbsp; 要么都不对齐，这两种情况可以合并起来处理。若两者低 3 位不同（异或不为0），则肯定有一个
 &nbsp; 不对齐，或者都不对齐，这种情况要跳转到 shift 处去处理。

3. 18-21行，对 dest 和 src 都对齐或都不对齐的情况予以区分，因为二者对齐情况相同，故而只判断
 &nbsp; a1 的情况即可。如果 a1 对齐，则直接跳转到 chk8w 处，先以 8 字节为单位，取数据写入之。
 &nbsp; 注意 18 行执行完了，t1 中是 -a1 的补码，等价于 ~a1 + 1，只有 a1 低 3 位都为 0 时，t1 的低
 &nbsp; 3 位才都为 0。

4. 22-26行，处理 dest 和 src 都不对齐的情况。注意此时 dest 与 src 的低 3 位是相同的。直接使用
 &nbsp; 非对齐访问指令，获取 t1 个字节，写入目的地。则 dest 与 src 就可以跨到第一个对齐地址处(dest
 &nbsp; +t1, src+t1)，此后的处理方式就和对齐的指针一样了。

5. 27-52行，处理数据块大于64字节的情况，每次循环写入 64 字节的数据。a0，a1，a2 同步移动，该
 &nbsp; 程序块运行后，不足64字节部分交由 chk1w 处理。

6. 53-64行，处理数据块小于64字节的情况，每次循环写入 8字节。a0，a1，a2 同步移动。经其处理后，
 &nbsp; 不足8字节部分，则交由 last16 开始的程序块处理。

7. 65-76行，以字节为单位写入数据。负责处理最后的16字节。可以将循环展开，优化之。

8. 78-80行，判断 a0 是否对齐，对齐则跳转到 shft1 处执行。

9. 81-87行，使a0对齐。

10. 88-100行，以8字节为单位，处理 a0 对齐，a1不对齐的情况。不足 8 字节的部分，交由 last16 处理。

注意 77-100 行这一块，是对a0，a1低3位不同的情况进行的处理。当a0，a1低3位不同时，可能有以下几
种情况：

I. a0 对齐，a1 不对齐
II. a0 不对齐，a1 不对齐（低3位不同）
III. a0 不对齐，a1 对齐

特别留意一下，其对第三种情况的处理，是首先将其转化为第一种情况，然后再对其进行处理的。

对第二种情况的处理也是这样的。



3. 针对龙芯的改进

A. 细化各种情况

81行处，当a0不对齐，a1对齐时，可直接对a1使用对齐访问，a0使用不对齐访问

这样会减少82-87行的6条指令，多一条分支判断语句。

这样，取数据使用对齐访问指令，写数据使用非对齐访问指令对，效率应该和取数据使用非对齐访问指令对(93,
94 行)，写数据使用对齐访问指令相当(97 行)。


B. 短循环展开

该改进的思想参见《龙芯汇编语言的艺术》

65-73 行负责处理小于16字节的部分，可以展开成如下代码：

 &nbsp;  &nbsp;  last16:
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   blez &nbsp;  &nbsp;  a2, 2f
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   addiu &nbsp;  &nbsp;  a2, a2, -1
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sll &nbsp;  &nbsp;  &nbsp;  &nbsp;   a2, a2, 2 &nbsp;  &nbsp;  &nbsp;  &nbsp;   # a2 &lt;-- a2 * 4

 &nbsp;  &nbsp;  &nbsp;  &nbsp;   la &nbsp;  &nbsp;  &nbsp;  &nbsp;   a3, 1f
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   subu &nbsp;  &nbsp;  a3, a3, a2

 &nbsp;  &nbsp;  &nbsp;  &nbsp;   lb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $15, 0(a1)

 &nbsp;  &nbsp;  &nbsp;  &nbsp;   jr &nbsp;  &nbsp;  &nbsp;  &nbsp;   a3
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  addiu a3, 2f-1f 
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   lb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $16, 15(a1)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   lb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $17, 14(a1)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   lb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $18, 13(a1)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   lb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $19, 12(a1)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   lb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $20, 11(a1)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   lb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $21, 10(a1)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   lb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $22, 9(a1)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   lb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $23, 8(a1)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   lb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $8, 7(a1)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   lb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $9, 6(a1)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   lb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $10, 5(a1)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   lb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $11, 4(a1)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   lb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $12, 3(a1)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   lb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $13, 2(a1)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   lb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $14, 1(a1)
 &nbsp;  &nbsp;  1: &nbsp;  &nbsp;  jr &nbsp;  &nbsp;  &nbsp;  &nbsp;   a3
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  sw &nbsp;  $15, 0(a0)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $16, 15(a0)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $17, 14(a0)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $18, 13(a0)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $19, 12(a0)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $20, 11(a0)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $21, 10(a0)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $22, 9(a0)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $23, 8(a0)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $8, 7(a0)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $9, 6(a0)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $10, 5(a0)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $11, 4(a0)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $12, 3(a0)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $13, 2(a0)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   sb &nbsp;  &nbsp;  &nbsp;  &nbsp;   $14, 1(a0)
 &nbsp;  &nbsp;  2:  jr &nbsp;  &nbsp;  &nbsp;  &nbsp;   ra
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   nop

注意：16~23 号寄存器，使用前需保存，完了要恢复。


C. 提升不对齐时大块数据复制的效率

原有实现当检测到a0、a1都不对齐时（低3位不同），将a0整对齐后，直接以8字节为单位，复制数据（见92-98行）
这个应该亦可以引入先以64字节为单位复制数据，然后再进入以8字节为单位的处理流程，这个应该可以提升大快数据复制时的效率，目前这个也是猜想，需要进一步的大量测试。


注： 目前只测试了last16处短循环展开的改进，功能正确，效率尚未评测。测试程序见 [url]http://people.openrays.org/~comcat/misc/memcpy.tar[/url]，内有 2 个文件 godson_memcpy.S mem_test.c，如下命令编译之：

gcc godson_memcpy.S mem_test.c -o mtest

./mtest 看输出是否正确

]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=423]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Fri, 15 Jun 2007 17:50:43 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[绕过 libc 直接使用系统调用]]></title>
 <description><![CDATA[系统调用是用户态进程切入内核态的唯一入口，是内核为用户态进程提供服务的接口。Linux Kernel 提供了大约300个左右的系统调用，作为用户空间进程访问内核的 API。

C 语言环境下，经常使用的（如getpid）是系统基础库 libc 封装过的。在其后面究竟隐藏着什么样的秘密呢？


1. 先看一个例子：

#include &lt;stdio.h&gt;

int main()
{
 &nbsp;  &nbsp;  long ID1;

 &nbsp;  &nbsp;  ID1 = getpid();
 &nbsp;  &nbsp;  printf(&quot;pid=%d\\n&quot;, ID1);

 &nbsp;  &nbsp;  return 0;
}

存为 sc.c，如下命令编译之：

gcc sc.c -o sc -static


看看它的汇编码：

objdump -d sc &gt; sc.S

查看 sc.S 我们发现 getpid 被 libc 替换成 __getpid，而这个函数只有下面的 3 行指令：

0804e0d0 &lt;__getpid&gt;:
 804e0d0: &nbsp; b8 14 00 00 00 &nbsp;  &nbsp;  &nbsp;  mov &nbsp;  $0x14,%eax
 804e0d5: &nbsp; cd 80 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  int &nbsp;  $0x80
 804e0d7: &nbsp; c3 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  ret

将 0x14 置于 eax 后，触发 0x80 号软件中断。

这个 0x80 号软件中断服务程序，就是内核系统调用的统一入口。

至于如何区分具体的是哪个系统调用，则通过 eax 传过来的整型值来区分。内核定义了一个系统调用表，表的每项是一个函数指针，指向具体的实现函数，每个系统调用函数在表中的位置固定，其序号 （从 0 开始计数）就是该系统调用的系统调用号。实际上就是一个元素固定的数组。如 i386 内核，系统调用表名为 sys_call_table，则 sys_call_table[20] 就是 getpid 的函数指针:

[arch/i386/kernel/syscall_table.S]

ENTRY(sys_call_table)
 &nbsp;  .long sys_restart_syscall &nbsp; /* 0 - old &quot;setup()&quot; system call, used for restarting */
 &nbsp;  .long sys_exit
 &nbsp;  .long sys_fork
 &nbsp;  .long sys_read
 &nbsp;  .long sys_write
 &nbsp;  .long sys_open &nbsp;  &nbsp; /* 5 */
 &nbsp;  .long sys_close
 &nbsp;  .long sys_waitpid
 &nbsp;  .long sys_creat
 &nbsp;  .long sys_link
 &nbsp;  .long sys_unlink &nbsp;  /* 10 */
 &nbsp;  .long sys_execve
 &nbsp;  .long sys_chdir
 &nbsp;  .long sys_time
 &nbsp;  .long sys_mknod
 &nbsp;  .long sys_chmod &nbsp;   /* 15 */
 &nbsp;  .long sys_lchown16
 &nbsp;  .long sys_ni_syscall &nbsp;  /* old break syscall holder */
 &nbsp;  .long sys_stat
 &nbsp;  .long sys_lseek
 &nbsp;  .long sys_getpid &nbsp;  /* 20 */
 &nbsp;  .long sys_mount

...

int 0x80 触发，使内核进入相应的处理程序，在保存了寄存器后，就使用 eax 的值索引 sys_call_table，得到地址后，跳转过去就进入了实质的服务函数。

因此，我们可以改写上面的程序为：

#include &lt;stdio.h&gt;

int main()
{
 &nbsp;  &nbsp;  long ID1;

 &nbsp;  &nbsp;  //ID1 = getpid();
 &nbsp;  &nbsp;  asm volatile(
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;int $0x80\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; : &quot;=a&quot; (ID1) &nbsp;  &nbsp;  /* eax 亦是返回值所在，因此要与ID1 对应 */
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; : &quot;0&quot; (20) &nbsp;  &nbsp;  &nbsp;  &nbsp;   /* 20 置入 eax */
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   );

 &nbsp;  &nbsp;  printf(&quot;pid=%d\\n&quot;, ID1);

 &nbsp;  &nbsp;  return 0;
}

编译 gcc sc.c -o sc，执行之，结果是一样的。

上面简单的系统调用不需要传递参数，当需要传递 3 个参数时，那么如何处理呢？


2. 参数传递 

i386 内核实现系统调用时有一个参数传递约定:

ebx --- 置第一个参数
ecx --- 置第二个参数
edx --- 置第三个参数
esi --- 置第四个参数
edi --- 置第五个参数
ebp --- 置第六个参数 （系统调用最大参数个数为6）

特别注意，使用 ebp 前要先将其进栈， int 0x80 返回后，要恢复之。



3. 使用标准宏

上面使用嵌入式汇编不是很方便，可以使用 POSIX 标准约定的一系列宏：
 &nbsp;  &nbsp;  
 &nbsp;  &nbsp;  #include &lt;linux/unistd.h&gt;

 &nbsp;  &nbsp;   _syscallX(type, name, type1, arg1, type2, arg2, ...)

其中 X 取值 0~5 表示系统调用的参数个数
name　为系统调用的名字，如 getpid, read 等
type1 为第一个参数类型
arg1 为第一个参数值
type2 为第二个参数类型
arg2 为第二个参数值
...

很显然，使用这些宏，要对所用之系统调用的参数个数和类型有清晰的了解。

如上面的程序改写为：

#include &lt;stdio.h&gt;
#include &lt;linux/unistd.h&gt;

_syscall0(int, getpid);

int main()
{
 &nbsp;  &nbsp;  long id = getpid();
 &nbsp;  &nbsp;  printf(&quot;id = %d\\n&quot;, id);

 &nbsp;  &nbsp;  return 0;
}

就绕过了 libc。可以使用 gcc -E sc.c &gt; sc.C 看看预处理后的结果，就知道这个_syscall 宏做了什么事了。

这些宏定义于内核源码目录的 include/asm-i386/unistd.h 下，抑或 /usr/include/asm-i486/unistd.h 下。


4. 查看系统调用号

可以查看内核源码目录的 include/asm-i386/unistd.h，该文件中定义了一系列以 __NR_ 开头的常量，紧随其后的即为系统调用名，相应的即为系统调用号。

更多的参考： [url]http://people.openrays.org/~comcat/mydoc/syscall_ref.html[/url]

]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=422]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Fri, 15 Jun 2007 14:40:43 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[使用 graphviz 形象化有向图]]></title>
 <description><![CDATA[将有向图数字化，可以用一个邻接矩阵表示。

计算机对这个图进行一系列的处理后（比如求闭包），如果要看看处理后的图，可以借助于 graphviz 这个强大的开源工具。


1. Graphviz 简介

Graphviz 是 AT&T Labs-Research 开发的自动图形绘制工具, 可以很方便的可视化结构信息，把抽象的图和网络用几何的方式表现出来。支持多种格式输出，如 jpg, png, gif, svg, dia, ps 等。


1.1 安装

debian 下直接 apt-get install graphviz

其他平台下可到其官方网站下载： [url]http://www.graphviz.org/Download..php[/url]


1.2 简单使用

要产生一个图形，首先得使用 Graphviz 定义的 DOT 语言来描述你的结构化图形：

digraph vis{
 &nbsp;  &nbsp;   A -&gt; B
 &nbsp;  &nbsp;   C -&gt; B
}

保存为 hw.dot

执行 dot -Tsvg hw.dot &gt; hw.svg 即会产生 SVG 格式的图形，如下所示：

 [p_w_upload=92]

dot 一般用于绘制有向图，无向图则可用 neato 或者 fdp，可以互相通用，差别在于 dot 生成的有向图效果要好点， neato 和 fdp 生成的无向图效果要好点。其他的如辐射状的图形可用 twopi，圆形的可用 circo。他们的命令行参数都是一致的，只是所用之算法不一而已。


如对于如下无向图：

graph G {
 &nbsp;  &nbsp;   run -- intr;
 &nbsp;  &nbsp;   intr -- runbl;
 &nbsp;  &nbsp;   runbl -- run;
 &nbsp;  &nbsp;   run -- kernel;
 &nbsp;  &nbsp;   kernel -- zombie;
 &nbsp;  &nbsp;   kernel -- sleep;
 &nbsp;  &nbsp;   kernel -- runmem;
 &nbsp;  &nbsp;   sleep -- swap;
 &nbsp;  &nbsp;   swap -- runswap;
 &nbsp;  &nbsp;   runswap -- new;
 &nbsp;  &nbsp;   runswap -- runmem;
 &nbsp;  &nbsp;   new -- runmem;
 &nbsp;  &nbsp;   sleep -- runmem;
}

neato -Tsvg proc.dot -o proc.svg 则效果好点， fdp 生成的则差不多，图更紧凑点。其他的生成之图，就比较差。

neato 生成：

 [p_w_upload=93]

fdp 生成：

 [p_w_upload=94]


产生一个有限自动机简直就是小儿科：

digraph finite_state_machine {
 &nbsp;  &nbsp;  rankdir=LR;
 &nbsp;  &nbsp;  size=&quot;8,5&quot;
 &nbsp;  &nbsp;  node [shape = doublecircle]; LR_0 LR_3 LR_4 LR_8;
 &nbsp;  &nbsp;  node [shape = circle];
 &nbsp;  &nbsp;  LR_0 -&gt; LR_2 [ label = &quot;SS(B)&quot; ];
 &nbsp;  &nbsp;  LR_0 -&gt; LR_1 [ label = &quot;SS(S)&quot; ];
 &nbsp;  &nbsp;  LR_1 -&gt; LR_3 [ label = &quot;S($end)&quot; ];
 &nbsp;  &nbsp;  LR_2 -&gt; LR_6 [ label = &quot;SS(b)&quot; ];
 &nbsp;  &nbsp;  LR_2 -&gt; LR_5 [ label = &quot;SS(a)&quot; ];
 &nbsp;  &nbsp;  LR_2 -&gt; LR_4 [ label = &quot;S(A)&quot; ];
 &nbsp;  &nbsp;  LR_5 -&gt; LR_7 [ label = &quot;S(b)&quot; ];
 &nbsp;  &nbsp;  LR_5 -&gt; LR_5 [ label = &quot;S(a)&quot; ];
 &nbsp;  &nbsp;  LR_6 -&gt; LR_6 [ label = &quot;S(b)&quot; ];
 &nbsp;  &nbsp;  LR_6 -&gt; LR_5 [ label = &quot;S(a)&quot; ];
 &nbsp;  &nbsp;  LR_7 -&gt; LR_8 [ label = &quot;S(b)&quot; ];
 &nbsp;  &nbsp;  LR_7 -&gt; LR_5 [ label = &quot;S(a)&quot; ];
 &nbsp;  &nbsp;  LR_8 -&gt; LR_6 [ label = &quot;S(b)&quot; ];
 &nbsp;  &nbsp;  LR_8 -&gt; LR_5 [ label = &quot;S(a)&quot; ];
}

dot -Tpng fsm.dot -o fsm.png

 [p_w_upload=95]


其他更精彩的使用可以参看 [url]http://www.graphviz.org/Gallery.php[/url]，绝对能让你惊奇。


2. 邻接矩阵形象化

考虑下面这个邻接矩阵：

#define NUM &nbsp;   6

int b[NUM][NUM] =
{
 &nbsp;  0, 1, 0, 0, 1, 0,
 &nbsp;  0, 0, 0, 0, 0, 1,
 &nbsp;  0, 0, 0, 0, 0, 0,
 &nbsp;  0, 1, 0, 0, 0, 0,
 &nbsp;  0, 0, 0, 1, 0, 0,
 &nbsp;  0, 0, 1, 0, 0, 0
};


要将其形象化，可以将其先转化为 DOT 语言描述，鉴于 DOT 语言描述这种结构有天然的优势，可以很容易的写下如下C 函数转化之：

void gen_graphic(const char *fname)
{
 &nbsp;  FILE *fp;
 &nbsp;  int k, j;
 &nbsp;  char name[255];
 &nbsp;  char cmd[255];

 &nbsp;  sprintf(name, &quot;%s.dot&quot;, fname);
 &nbsp;  fp = fopen(name, &quot;wb&quot;);

 &nbsp;  fputs(&quot;digraph vis {\\n&quot;, fp);
 &nbsp;  fputs(&quot;\\tsize=\\&quot;5,3\\&quot;;\\t/* set img width*height in inch*/\\n&quot;, fp);

 &nbsp;  for(k=0; k&lt;NUM; k++)
 &nbsp;  &nbsp;   for(j=0; j&lt;NUM; j++)
 &nbsp;  &nbsp;  &nbsp;  &nbsp; if(b[k][j])
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  fprintf(fp, &quot;\\t%d -&gt; %d;\\n&quot;, k+1, j+1);

 &nbsp;  fputs(&quot;}\\n&quot;, fp);
 &nbsp;  fclose(fp);

 &nbsp;  sprintf(cmd, &quot;circo -Tsvg %s.dot &gt; %s.svg&quot;, fname, fname);
 &nbsp;  system(cmd);
}

多次尝试发现，这个图使用 circo 效果要好点，尽管官方建议的有向图产生工具是 dot。

调用该函数 gen_graphic(vis_src); 产生的邻接矩阵图为：

 [p_w_upload=96]


闭包运算后的图为：

  [p_w_upload=98] 

]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=420]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Fri, 15 Jun 2007 11:33:06 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[酷睿微体系结构笔记]]></title>
 <description><![CDATA[酷睿微体系结构，基于Pentium M的微体系结构，是片内多个核(cmp)的实现。

设计的原则是平衡能耗的基础上尽可能的提高性能，即提高每瓦特性能（能效比）。


相比前代的主要改进：


宽位动态执行（Wide Dynamic Execution）

宽位动态执行实际上就是提高IPC，从而提高性能。(Perf = Freq x IPC)

增加了一组解码器。拥有4组解码器，比上代Pentium Pro (P6) / Pentium II / Pentium III / Pentium M架构拥有的3组可多处理一组指令。实际上就是从3发射升级到了4发射。

在提升每个时钟周期的指令数方面做了很多努力，典型的就是新引入的宏融合(Macro-Fusion)技术，它可以让处理器在解码的同时，将同类的指令融合为单一的指令，这样可以减少处理的指令总数，让处理器在更短的时间内处理更多的指令。为此亦改良了ALU （算术逻辑单元）以支持宏融合技术。



高级智能高速缓存（Advanced Smart Cache）

酷睿微结构体系结构采用了共享二级缓存的做法。这样的好处是，两个核心可以共享二级缓存，大幅提高了二级高速缓存的命中率，从而可以较少通过前端串行总线和北桥进行外围交换。

而以往的多核心处理器，其每个核心的二级缓存是各自独立的，这就造成了二级缓存不能够被充分利用，并且两个核心之间的数据交换路线也更为冗长，必须要通过共享的前端串行总线和北桥来进行数据交换，影响了处理器工作效率。

每个核心都可以动态支配全部二级高速缓存。当某一个内核当前对缓存的利用较低时，另一个内核就可以动态增加占用二级缓存的比例。甚至当其中的一个内核关闭时，仍可以保持全部缓存在工作状态，另外也可以根据需求关闭部分缓存来降低功耗。

这样可以降低二级缓存的命中失误，减少数据延迟，改进处理器效率。



智能功率能力（Intelligent Power Capability）

在制程技术方面做了优化，采用先进的65nm应变硅技术、加入低K栅介质及增加金属层，相比上代90nm制程减少漏电达1000倍。

值得注意的是，英特尔加入了超精细的逻辑控制机能独立开关各运算单元，具体来讲，酷睿微体系结构采用先进的功率门控技术。以往功率门控技术实现起来十分困难，因为元件开关过程需要消耗一定的能源，而且由休眠到恢复工作也会出现延迟，但酷睿微体系结构已经解决这些问题。

通过该特性，可以智能地打开当前需要运行的子系统，而其他部分则处于休眠状态，这样将大幅降低处理器的功耗及发热。


智能内存访问（Smart Memory Access）

通过缩短内存延迟来优化内存数据访问。智能内存访问能够预测系统的需要，提前载入或预取数据。

以前我们要从内存中读取数据，就需要等待处理器完成前面的所以指令后才可以进行，这样的效率显然是低下的。酷睿微体系结构中引入一项名为内存消歧的能力，它可以对内存读取顺序做出分析，智能地预测和装载下一条指令所需要的数据，这样能够减少处理器的等待时间，减少闲置，同时降低内存读取的延迟，而且它可以侦测出冲突并重新读取正确的资料及重新执行指令，保证运算结果不会出错误，大大提高了执行效率。



高级数字媒体增强（Advanced Digital Media Boost）

高级数字媒体增强实际上就是提高IPC，其可以提高 SIMD指令（SSE/SSE2/SSE3）的执行效率。

酷睿微体系结构则拥有 128位的SIMD执行能力，一个时钟周期就可以完成一条指令，而前代处理器需要两个时钟周期来处理一条完整指令，效率提高了一倍。

]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=418]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Sun, 10 Jun 2007 11:57:32 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[oprofile for godson2e 移植手记]]></title>
 <description><![CDATA[0. 概述

oprofile 在Linux 上分两部分，一个是内核模块(oprofile.ko)，一个为用户空间的守护进程(oprofiled)。
前者负责访问性能计数器或者注册基于时间采样的函数(使用register_timer_hook注册之，使时钟中断处理
程序最后执行profile_tick 时可以访问之)，并采样置于内核的缓冲区内。后者在后台运行，负责从内核空
间收集数据，写入文件。二者都需要添加对龙芯2E 的支持。


1. kernel patch

完整之 patch 位于： [url]http://people.openrays.org/~comcat/patch/patch-2.6.18-oprofile-comcat[/url]

主要是当计数器溢出（31位为1）时，触发IP6 中断，其处理程序的 godson2e_pmc_handler 的编写。

注意一下在用户空间设置的样本计数，在内核空间是怎么处理的。例如使用

opcontrol --setup --event=CYCLES:1000 设置样本计数为1000，

则计数器被初始化的值为0x8000 0000 - 1000
即： CYCLES 事件发生 1000 次后，计数溢出，触发处理程序 godson2e_pmc_handler 添加一个样本 add_sample。

diff -uNr src/arch/mips/oprofile/op_model_godson2e.c pmc/arch/mips/oprofile/op_model_godson2e.c
--- src/arch/mips/oprofile/op_model_godson2e.c &nbsp;  &nbsp;  1970-01-01 08:00:00.000000000 +0800
+++ pmc/arch/mips/oprofile/op_model_godson2e.c &nbsp;  &nbsp;  2007-05-25 14:59:54.000000000 +0800
@@ -0,0 +1,171 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file &quot;COPYING&quot; in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2007 by comcat &lt;[email]jiankemeng@gmail.com[/email]&gt;
+ */
+
+#include &lt;linux/init.h&gt;
+#include &lt;linux/oprofile.h&gt;
+#include &lt;linux/interrupt.h&gt;
+#include &lt;linux/smp.h&gt;
+
+#include &quot;op_impl.h&quot;
+
+#define GODSON2E_PERFCTL_EXL &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; (1UL &nbsp;  &lt;&lt;  0)
+#define GODSON2E_PERFCTL_KERNEL &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; (1UL &nbsp;  &lt;&lt;  1)
+#define GODSON2E_PERFCTL_SUPERVISOR &nbsp;  &nbsp;  &nbsp;  &nbsp;   (1UL &nbsp;  &lt;&lt;  2)
+#define GODSON2E_PERFCTL_USER &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; (1UL &nbsp;  &lt;&lt;  3)
+#define GODSON2E_PERFCTL_INTERRUPT_ENABLE &nbsp;  &nbsp;  (1UL &nbsp;  &lt;&lt;  4)
+#define GODSON2E_PERFCTL_OVERFLOW &nbsp;  &nbsp;  &nbsp;  &nbsp;   (1ULL &nbsp;  &nbsp;  &lt;&lt; 31)
+
+#define GODSON2E_COUNTER1_EVENT(event) &nbsp;  &nbsp;  ((event) &lt;&lt; 5)
+#define GODSON2E_COUNTER2_EVENT(event) &nbsp;  &nbsp;  ((event) &lt;&lt; 9)
+
+
+/* IP6 --- performance counter overflow */
+static int godson2e_pmc_irq = 56 + 6;
+
+
+static struct godson2e_register_config {
+ &nbsp;  &nbsp;  unsigned int control;
+ &nbsp;  &nbsp;  unsigned int reset_counter1;
+ &nbsp;  &nbsp;  unsigned int reset_counter2;
+ &nbsp;  &nbsp;  int c1_enabled;
+ &nbsp;  &nbsp;  int c2_enabled;
+} reg;
+
+/* Compute all of the registers in preparation for enabling profiling.  */
+
+static void godson2e_reg_setup(struct op_counter_config *ctr)
+{
+ &nbsp;  &nbsp;  unsigned int control = 0;
+
+ &nbsp;  &nbsp;  /* Compute the performance counter control word.  */
+ &nbsp;  &nbsp;  /* For now count kernel and user mode */
+ &nbsp;  &nbsp;  if (ctr[0].enabled)
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;   control |= GODSON2E_COUNTER1_EVENT(ctr[0].event) |
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  GODSON2E_PERFCTL_INTERRUPT_ENABLE;
+ &nbsp;  &nbsp;  else
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;   control |= GODSON2E_COUNTER1_EVENT(0xe);
+
+ &nbsp;  &nbsp;  if (ctr[1].enabled)
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;   control |= GODSON2E_COUNTER2_EVENT(ctr[1].event) |
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  GODSON2E_PERFCTL_INTERRUPT_ENABLE;
+ &nbsp;  &nbsp;  else
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;   control |= GODSON2E_COUNTER2_EVENT(0x3);
+
+ &nbsp;  &nbsp;  if (ctr[0].kernel || ctr[1].kernel)
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;   control |= GODSON2E_PERFCTL_KERNEL;
+ &nbsp;  &nbsp;  else
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;   control &= ~GODSON2E_PERFCTL_KERNEL;
+
+ &nbsp;  &nbsp;  if (ctr[0].user || ctr[1].user)
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;   control |= GODSON2E_PERFCTL_USER;
+ &nbsp;  &nbsp;  else
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;   control &= ~GODSON2E_PERFCTL_USER;
+
+ &nbsp;  &nbsp;  if (ctr[0].exl || ctr[1].exl)
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;   control |= GODSON2E_PERFCTL_EXL;
+ &nbsp;  &nbsp;  else
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;   control &= ~GODSON2E_PERFCTL_EXL;
+
+ &nbsp;  &nbsp;  reg.control = control;
+
+ &nbsp;  &nbsp;  reg.c1_enabled = ctr[0].enabled;
+ &nbsp;  &nbsp;  reg.c2_enabled = ctr[1].enabled;
+ &nbsp;  &nbsp;  reg.reset_counter1 = ctr[0].count ? 0x80000000 - ctr[0].count : 0;
+ &nbsp;  &nbsp;  reg.reset_counter2 = ctr[1].count ? 0x80000000 - ctr[1].count : 0;
+}
+
+static void godson2e_cpu_setup (void *args)
+{
+ &nbsp;  &nbsp;  uint64_t perfcount;
+
+ &nbsp;  &nbsp;  perfcount = ((uint64_t) reg.reset_counter2 &lt;&lt; 32) | reg.reset_counter1;
+ &nbsp;  &nbsp;  write_c0_pmc_count(perfcount);
+}
+
+static void godson2e_cpu_start(void *args)
+{
+ &nbsp;  &nbsp;  /* Start all counters */
+ &nbsp;  &nbsp;  write_c0_pmc_control(reg.control);
+}
+
+static void godson2e_cpu_stop(void *args)
+{
+ &nbsp;  &nbsp;  /* Stop all counters */
+ &nbsp;  &nbsp;  write_c0_pmc_control(0);
+}
+
+
+static irqreturn_t godson2e_pmc_handler(int irq, void * dev_id,
+ &nbsp;  &nbsp;  struct pt_regs *regs)
+{
+ &nbsp;  &nbsp;  uint32_t counter1, counter2;
+ &nbsp;  &nbsp;  uint64_t counters;
+ &nbsp;  &nbsp;  uint64_t tmp = 0x0;
+
+ &nbsp;  &nbsp;  /*
+ &nbsp;  &nbsp;   * Godson2e combines two 32-bit performance counters into a single
+ &nbsp;  &nbsp;   * 64-bit coprocessor zero register.  To avoid a race updating the
+ &nbsp;  &nbsp;   * registers we need to stop the counters while we&#39;re messing with
+ &nbsp;  &nbsp;   * them ...
+ &nbsp;  &nbsp;   */
+
+ &nbsp;  &nbsp;  write_c0_pmc_control(tmp);
+
+ &nbsp;  &nbsp;  counters = read_c0_pmc_count();
+ &nbsp;  &nbsp;  counter1 = counters;
+ &nbsp;  &nbsp;  counter2 = counters &gt;&gt; 32;
+
+ &nbsp;  &nbsp;  if (reg.c2_enabled && counter2 & GODSON2E_PERFCTL_OVERFLOW) {
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;   oprofile_add_sample(regs, 1);
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;   counter2 = reg.reset_counter2;
+ &nbsp;  &nbsp;  }
+
+ &nbsp;  &nbsp;  if (reg.c1_enabled && counter1 & GODSON2E_PERFCTL_OVERFLOW) {
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;   oprofile_add_sample(regs, 0);
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;   counter1 = reg.reset_counter1;
+ &nbsp;  &nbsp;  }
+
+ &nbsp;  &nbsp;  counters = ((uint64_t)counter2 &lt;&lt; 32) | counter1;
+
+ &nbsp;  &nbsp;  write_c0_pmc_count(counters);
+ &nbsp;  &nbsp;  write_c0_pmc_control(reg.control);
+
+ &nbsp;  &nbsp;  return IRQ_HANDLED;
+}
+
+static int __init godson2e_init(void)
+{
+ &nbsp;  &nbsp;  uint64_t tmp = 0;
+ &nbsp;  &nbsp;  write_c0_pmc_control(0);
+ &nbsp;  &nbsp;  write_c0_pmc_count(tmp);
+
+ &nbsp;  &nbsp;  return request_irq(godson2e_pmc_irq, godson2e_pmc_handler,
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;   IRQF_DISABLED, &quot;Perfcounter&quot;, NULL);
+}
+
+static void godson2e_exit(void)
+{
+ &nbsp;  &nbsp;  uint64_t tmp = 0;
+ &nbsp;  &nbsp;  write_c0_pmc_control(0);
+ &nbsp;  &nbsp;  write_c0_pmc_count(tmp);
+
+ &nbsp;  &nbsp;  free_irq(godson2e_pmc_irq, NULL);
+}
+
+struct op_mips_model op_model_godson2e_ops = {
+ &nbsp;  &nbsp;  .reg_setup &nbsp;  &nbsp;  = godson2e_reg_setup,
+ &nbsp;  &nbsp;  .cpu_setup &nbsp;  &nbsp;  = godson2e_cpu_setup,
+ &nbsp;  &nbsp;  .init &nbsp;  &nbsp;  &nbsp;  &nbsp;   = godson2e_init,
+ &nbsp;  &nbsp;  .exit &nbsp;  &nbsp;  &nbsp;  &nbsp;   = godson2e_exit,
+ &nbsp;  &nbsp;  .cpu_start &nbsp;  &nbsp;  = godson2e_cpu_start,
+ &nbsp;  &nbsp;  .cpu_stop &nbsp;  &nbsp;  = godson2e_cpu_stop,
+ &nbsp;  &nbsp;  .cpu_type &nbsp;  &nbsp;  = &quot;mips/godson2e&quot;,
+ &nbsp;  &nbsp;  .num_counters &nbsp;  &nbsp;  = 2
+};
+
+

IRQ 56~63 共8个号分配给 MIPS_CPU 内部中断使用，对应于CAUSE寄存器的IP0~IP7。
实际用到的只有63号，对应于时钟中断，注意MIPS下时钟中断的产生方式，其是由
CPU 内部产生的（参见用户手册对Count与Compare寄存器的描述）。56、57、59、60
保留未用，58、61 对应 IP2　与 IP5　分别连接到 I8259A 和北桥内的中断控制器，
因而实际并未使用，但是为了防止其他驱动 request_irq 时申请之，故而使用 cascade
填充之（可以通过 cat /proc/interrupts 查看）。

因为上面使用了 62 作为计数器溢出的中断请求号，因此当内核检测到 cause 之IP6 为
１时，应该 do_IRQ(62, regs)：

diff -uNr src/arch/mips/godson/lm2e/irq.c pmc/arch/mips/godson/lm2e/irq.c
--- src/arch/mips/godson/lm2e/irq.c &nbsp;  &nbsp;  2006-12-01 18:10:11.000000000 +0800
+++ pmc/arch/mips/godson/lm2e/irq.c &nbsp;  &nbsp;  2007-04-29 10:30:24.000000000 +0800
@@ -113,6 +113,8 @@
 
 &nbsp;  &nbsp;   if (pending & CAUSEF_IP7) {
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; do_IRQ(63, regs);
+ &nbsp;  &nbsp;  } else if (pending & CAUSEF_IP6) {
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;   do_IRQ(62, regs);
 &nbsp;  &nbsp;   } else if (pending & CAUSEF_IP5) {
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; i8259_irqdispatch(regs);
 &nbsp;  &nbsp;   } else if (pending & CAUSEF_IP2) {


添加在32位核心模式下，访问64位寄存器的支持。此中缘由参见
《32位模式下使用64位寄存器注意事项》的第3部分。

diff -uNr src/include/asm-mips/mipsregs.h pmc/include/asm-mips/mipsregs.h
--- src/include/asm-mips/mipsregs.h &nbsp;  &nbsp;  2006-09-20 11:42:06.000000000 +0800
+++ pmc/include/asm-mips/mipsregs.h &nbsp;  &nbsp;  2007-05-22 16:01:48.000000000 +0800
@@ -650,6 +650,57 @@
 
 /*
+ * Macros to access the godson2e system control coprocessor
+ */
+#define __read_64bit_c0_pmc_split(source, sel) &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  \\
+({ &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; \\
+ &nbsp;  &nbsp;  unsigned long long val; &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; \\
+ &nbsp;  &nbsp;  unsigned long flags; &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; \\
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; \\
+ &nbsp;  &nbsp;  local_irq_save(flags); &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; \\
+ &nbsp;  &nbsp;  if (sel == 0) &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  \\
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;   __asm__ __volatile__( &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;   \\
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set\\tmips64\\n\\t&quot; &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  \\
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dmfc0\\t%M0, &quot; #source &quot;\\n\\t&quot; &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; \\
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dsll\\t%L0, %M0, 32\\n\\t&quot; &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; \\
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dsra\\t%M0, %M0, 32\\n\\t&quot; &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; \\
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dsra\\t%L0, %L0, 32\\n\\t&quot; &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; \\
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set\\tmips0&quot; &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;   \\
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; : &quot;=r&quot; (val)); &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;   \\
+ &nbsp;  &nbsp;  else &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;   \\
+ &nbsp;  &nbsp;  ...
+

+
+/* Godson2e PMC counter register */
+#define read_c0_pmc_count() &nbsp;  &nbsp;  __read_64bit_c0_pmc($25, 0)
+#define write_c0_pmc_count(val) &nbsp;  &nbsp;  __write_64bit_c0_register($25, 0, val)
+


这里添加一个判断，用于支持在模块手动加载或者直接编入内核但没有使用 opcontrol --init
初始化oprofile，用户手动设置CP0_24（中断使能位为1）使用性能计数器的情形。由
于用户没有使用opcontrol通知内核空间的oprofile完成一些初始化的工作，因此一段时
间后计数器溢出，触发处理程序 godson2e_pmc_handler 在其中 oprofile_add_sample 
时，由于没有初始化，下面的 entry 会为 NULL，没有判断会出现非法内存访问，表现为
内核输出Oop 消息后即死机。

diff -uNr src/drivers/oprofile/cpu_buffer.c pmc/drivers/oprofile/cpu_buffer.c
--- src/drivers/oprofile/cpu_buffer.c &nbsp;  &nbsp;  2006-09-20 11:42:06.000000000 +0800
+++ pmc/drivers/oprofile/cpu_buffer.c &nbsp;  &nbsp;  2007-04-30 15:39:23.000000000 +0800
@@ -148,6 +148,10 @@
 &nbsp;  &nbsp;  &nbsp;  &nbsp; unsigned long pc, unsigned long event)
 {
 &nbsp;  &nbsp;   struct op_sample * entry = &cpu_buf-&gt;buffer[cpu_buf-&gt;head_pos];
+
+ &nbsp;  &nbsp;  if(!entry)
+ &nbsp;  &nbsp;  &nbsp;  &nbsp;   return;
+
 &nbsp;  &nbsp;   entry-&gt;eip = pc;
 &nbsp;  &nbsp;   entry-&gt;event = event;
 &nbsp;  &nbsp;   increment_head(cpu_buf);



2. 用户空间工具集 patch


用户空间工具与具体的体系结构关系并不密切，因此移植起来要容易很多。

用户空间工具用途主要在于CPU计数器事件的设置、取样数据的保存、数据的分析等。与具
体体系结构相关的就是CPU计数事件的设置。因为不同的CPU，其计数时间的定义是不一样的。

Oprofile 用户空间工具安装后，在 /usr/share/oprofile/ 目录下保存有所有他所支持的体系
结构CPU的计数事件文件。当其需要设置计数事件时，首先读取 /dev/oprofile/cpu_type 中
的字符串，比如pentium IV 为 &quot;i386/p4&quot;，则oprofile就以 /usr/share/oprofile/i386/p4/
下的 events 文件作为当前平台所用之计数事件文件。

因此对龙芯2E 的支持，需要在 /usr/share/oprofile/ 下加入龙芯2E 计数事件文件，具体的
位于哪个目录，则要与上面内核空间导出的 /dev/oprofile/cpu_type值相一致。

因为上面在 op_model_godson2e.c 的结构体op_model_godson2e_ops 里已经添加龙芯2E 的cpu_type
字符串为： mips/godson2e，则须在源码目录的 events/mips/ 下添加 godson2e/events 和 
godson2e/unit_masks。

下面是最主要的 events 文件，具体事件描述参见2E用户手册：

diff -Nru oprofile-0.9.2/events/mips/godson2e/events oprofile-0.9.2-godson/events/mips/godson2e/events
--- oprofile-0.9.2/events/mips/godson2e/events &nbsp;  &nbsp;  1970-01-01 08:00:00.000000000 +0800
+++ oprofile-0.9.2-godson/events/mips/godson2e/events &nbsp;  &nbsp;  2007-04-23 16:17:10.000000000 +0800
@@ -0,0 +1,38 @@
+#
+# GODSON2E events
+#
+# The same event numbers mean different things on the two counters
+#
+event:0x00 counters:0 um:zero minimum:500 name:CYCLES : Cycles
+event:0x01 counters:0 um:zero minimum:500 name:BRANCH_NUMBER : Branch instruction number 
+event:0x02 counters:0 um:zero minimum:500 name:JR_NUMBER : JR instruction number
+event:0x03 counters:0 um:zero minimum:500 name:JR31_NUMBER : JR 31 number 
+event:0x04 counters:0 um:zero minimum:500 name:ICACHE_MISSES : Instruction Cache misses number 
+event:0x05 counters:0 um:zero minimum:500 name:ALU1_ISSUED : ALU1 unit instruction count
+event:0x06 counters:0 um:zero minimum:500 name:MEM_ISSUED : MEM unit instruction count 
+event:0x07 counters:0 um:zero minimum:500 name:FALU1_ISSUED : FALU1 unit intruction count
+event:0x08 counters:0 um:zero minimum:500 name:BHT : BHT intruction count
+event:0x09 counters:0 um:zero minimum:500 name:MEM_READ : memory read count
+event:0x0a counters:0 um:zero minimum:500 name:FIX_QUEUE_FULL : Fix queue full count
+event:0x0b counters:0 um:zero minimum:500 name:REORDER_QUEUE_FULL : Reorder queue full count
+event:0x0c counters:0 um:zero minimum:500 name:CP0_QUEUE_FULL : CP0 queue full count
+event:0x0d counters:0 um:zero minimum:500 name:TLB_REFILL : TLB refill count
+event:0x0e counters:0 um:zero minimum:500 name:INTERRUPT : Interrupt count
+event:0x0f counters:0 um:zero minimum:500 name:INTERNAL_EXCEPTION : Internal exception count 
+
+event:0x00 counters:1 um:zero minimum:500 name:COMMITTED_ISSUED : Committed instruction count
+event:0x01 counters:1 um:zero minimum:500 name:BRANCHES_MISPREDICTED : Branches mispredicted count
+event:0x02 counters:1 um:zero minimum:500 name:JR_MISPREDICTED : JR mispredicted count
+event:0x03 counters:1 um:zero minimum:500 name:JR31_MISPREDICTED : JR mispredicted count
+event:0x04 counters:1 um:zero minimum:500 name:DCACHE_MISSES : Data cache misses count
+event:0x05 counters:1 um:zero minimum:500 name:ALU2_ISSUED : ALU2 unit instructions count 
+event:0x06 counters:1 um:zero minimum:500 name:FALU2_ISSUED : FALU2 unit instruction count
+event:0x07 counters:1 um:zero minimum:500 name:UNCACHE : Uncache operation count
+event:0x08 counters:1 um:zero minimum:500 name:BHT_MISSES : BHT misses
+event:0x09 counters:1 um:zero minimum:500 name:STORE_ISSUED : Store operation count
+event:0x0a counters:1 um:zero minimum:500 name:FLOAT_POINTING_QUEUE_FULL : Floatpointing queue full count
+event:0x0b counters:1 um:zero minimum:500 name:BRANCH_QUEUE_FULL : Branch queue full count
+event:0x0c counters:1 um:zero minimum:500 name:ITLB_MISSES : Instruction tlb misses count
+event:0x0d counters:1 um:zero minimum:500 name:EXCEPTION : Total exception count
+event:0x0e counters:1 um:zero minimum:500 name:LOAD_SPECULATION : Load speculation count
+event:0x0f counters:1 um:zero minimum:500 name:CP0_FORWARD : CP0 forward count


其他细微处的修改，参见具体的patch 文件： [url]http://people.openrays.org/~comcat/patch/oprofile-0.9.2-godson.patch[/url]
]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=416]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Thu, 07 Jun 2007 17:26:43 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[使用oprofile分析性能瓶颈]]></title>
 <description><![CDATA[使用oprofile分析性能瓶颈


1. 概述

oprofile 是 Linux 平台上，类似 INTEL VTune 的一个功能强大的性能分析工具。

其支持两种采样(sampling)方式：基于事件的采样(event based)和基于时间的采样(time based)。

基于事件的采样是oprofile只记录特定事件（比如L2 cache miss）的发生次数，当达到用户设定的
定值时oprofile 就记录一下（采一个样）。这种方式需要CPU 内部有性能计数器(performace counter)。
现代CPU内部一般都有性能计数器，龙芯2E内部亦内置了2个性能计数器。

基于时间的采样是oprofile 借助OS 时钟中断的机制，每个时钟中断 oprofile 都会记录一次(采一次样）。
引入的目的在于，提供对没有性能计数器 CPU 的支持。其精度相对于基于事件的采样要低。因为要借助 OS
时钟中断的支持，对禁用中断的代码oprofile不能对其进行分析。

oprofile 在Linux 上分两部分，一个是内核模块(oprofile.ko)，一个为用户空间的守护进程(oprofiled)。
前者负责访问性能计数器或者注册基于时间采样的函数(使用register_timer_hook注册之，使时钟中断处理
程序最后执行profile_tick 时可以访问之)，并采样置于内核的缓冲区内。后者在后台运行，负责从内核空
间收集数据，写入文件。


2. oprofile 的安装

以龙芯2E平台为例，要使用oprofile 首先得采用打开oprofile支持的内核启动。然后安装下面3个软件包：
oprofile, oprofile-common, oprofile-gui，其中核心软件包是oprofile-common，其包括以下工具集：

 &nbsp;  &nbsp;  /usr/bin/oprofiled &nbsp;  &nbsp;  &nbsp;  &nbsp;   守护进程
 &nbsp;  &nbsp;  /usr/bin/opcontrol &nbsp;  &nbsp;  &nbsp;  &nbsp;   控制前端，负责控制与用户交互，用得最多 &nbsp;  &nbsp;  
 &nbsp;  &nbsp;  /usr/bin/opannotate &nbsp;  &nbsp;  &nbsp;  &nbsp;   根据搜集到的数据，在源码或者汇编层面上注释并呈现给用户
 &nbsp;  &nbsp;  /usr/bin/opreport &nbsp;  &nbsp;  &nbsp;  &nbsp;   生成二进制镜像或符号的概览
 &nbsp;  &nbsp;  /usr/bin/ophelp &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; 列出oprofile支持的事件
 &nbsp;  &nbsp;  /usr/bin/opgprof &nbsp;  &nbsp;  &nbsp;  &nbsp;   生成gprof格式的剖析数据
 &nbsp;  &nbsp;  ...


目前oprofile 在龙芯2E上已经移植好了，包括用户空间的工具集软件包，亦可用矣。


3. oprofile 快速上手

a. 初始化

 &nbsp;  &nbsp;  opcontrol --init

 &nbsp;  &nbsp;  该命令会加载oprofile.ko模块，mount oprofilefs。成功后会在/dev/oprofile/目录下导出
 &nbsp;  &nbsp;  一些文件和目录如： cpu_type, dump, enable, pointer_size, stats/

b. 配置

 &nbsp;  &nbsp;  主要设置计数事件和样本计数，以及计数的CPU模式（用户态、核心态）
 &nbsp;  &nbsp;  
 &nbsp;  &nbsp;  opcontrol --setup --event=CYCLES:1000::0:1

 &nbsp;  &nbsp;  则是设置计数事件为CYCLES，即对处理器时钟周期进行计数
 &nbsp;  &nbsp;  样本计数为1000，即每1000个时钟周期，oprofile 取样一次。
 &nbsp;  &nbsp;  处理器运行于核心态则不计数
 &nbsp;  &nbsp;  运行于用户态则计数

 &nbsp;  &nbsp;  --event=name:count:unitmask:kernel:user

 &nbsp;  &nbsp; name: &nbsp;   event name, e.g. CYCLES or ICACHE_MISSES 
 &nbsp;  &nbsp; count: &nbsp;  reset counter value e.g. 100000
 &nbsp;  &nbsp; unitmask: hardware unit mask e.g. 0x0f
 &nbsp;  &nbsp; kernel: &nbsp; whether to profile kernel: 0 or 1
 &nbsp;  &nbsp; user: &nbsp;   whether to profile userspace: 0 or 1

c. 启动

 &nbsp;  &nbsp;  opcontrol --start


d. 运行待分析之程序

 &nbsp;  &nbsp;  ./ffmpeg -c cif -vcodec mpeg4 -i /root/paris.yuv paris.avi

e. 取出数据

 &nbsp;  &nbsp;  opcontrol --dump
 &nbsp;  &nbsp;  opcontrol --stop

f. 分析结果

 &nbsp;  &nbsp;  opreport -l ./ffmpeg


则会输出如下结果：

CPU: GODSON2E, speed 0 MHz (estimated)
Counted CYCLES events (Cycles) with a unit mask of 0x00 (No unit mask) count 10000
samples  % &nbsp;  &nbsp;   symbol name
11739 &nbsp;  27.0148  pix_abs16_c
6052 &nbsp;   13.9274  pix_abs16_xy2_c
4439 &nbsp;   10.2154  ff_jpeg_fdct_islow
2574 &nbsp;  &nbsp; 5.9235  pix_abs16_y2_c
2555 &nbsp;  &nbsp; 5.8798  dct_quantize_c
2514 &nbsp;  &nbsp; 5.7854  pix_abs8_c
2358 &nbsp;  &nbsp; 5.4264  pix_abs16_x2_c
1388 &nbsp;  &nbsp; 3.1942  diff_pixels_c
964 &nbsp;  &nbsp;  2.2184  ff_estimate_p_frame_motion
852 &nbsp;  &nbsp;  1.9607  simple_idct_add
768 &nbsp;  &nbsp;  1.7674  sse16_c
751 &nbsp;  &nbsp;  1.7283  ff_epzs_motion_search
735 &nbsp;  &nbsp;  1.6914  pix_norm1_c
619 &nbsp;  &nbsp;  1.4245  pix_sum_c
561 &nbsp;  &nbsp;  1.2910  mpeg4_encode_blocks
558 &nbsp;  &nbsp;  1.2841  encode_thread
269 &nbsp;  &nbsp;  0.6190  put_no_rnd_pixels16_c
255 &nbsp;  &nbsp;  0.5868  dct_unquantize_h263_inter_c

......


4. 例子

oprofile 可以分析处理器周期、TLB 失误、分支预测失误、缓存失误、中断处理程序，等等。
你可以使用 opcontrol --list-events 列出当前处理器上可监视事件列表。

下面分析一个编写不当的例子：


[带有cache问题的代码cache.c]
+++++++++++++++++++++++++++++++++++++++++++++++

int matrix[2047][7];


void bad_access()
{
 &nbsp;  int k, j, sum = 0;

 &nbsp;  for(k = 0; k &lt; 7; k++)
 &nbsp;  &nbsp;   for(j = 0; j &lt; 2047; j++)
 &nbsp;  &nbsp;  &nbsp;  &nbsp; sum += matrix[j][k] * 1024;

}

int main()
{
 &nbsp;  &nbsp;  int i;

 &nbsp;  &nbsp;  for(i = 0; i&lt; 100000; i++)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   bad_access();

 &nbsp;  &nbsp;  return 0;

}

+++++++++++++++++++++++++++++++++++++++++++++++


编译之： gcc -g cache.c -o cache


使用oprofile 分析之：

opcontrol --init

opcontrol --setup --event=DCACHE_MISSES:500::0:1

opcontrol --start && ./cache && opcontrol --dump && opcontrol --stop


使用 opannotate 分析结果为：

/*
 * Command line: opannotate --source ./cachee
 *
 * Interpretation of command line:
 * Output annotated source file with samples
 * Output all files
 *
 * CPU: GODSON2E, speed 0 MHz (estimated)
 * Counted ICACHE_MISSES events (Instruction Cache misses number ) with a unit mask of 0x00 (No unit mask) count 500
 */
/*
 * Total samples for file : &quot;/comcat/test/pmc.test/cachee.c&quot;
 *
 * &nbsp;   34 100.000
 */


 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; :int matrix[2047][7];
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; :
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; :void bad_access()
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; :{ /* bad_access total: &nbsp;   33 97.0588 */
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; : &nbsp;  int k, j, sum = 0;
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; :
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; : &nbsp;  for(k = 0; k &lt; 7; k++)
 &nbsp;  33 97.0588 : &nbsp;  &nbsp;   for(j = 0; j &lt; 2047; j++)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; : &nbsp;  &nbsp;  &nbsp;  &nbsp; sum += matrix[j][k] * 1024;
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; :
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; :}
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; :
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; :int main()
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; :{ /* main total: &nbsp;  &nbsp; 1  2.9412 */
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; : &nbsp;  int i;
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; :
 &nbsp;   1  2.9412 : &nbsp;  for(i = 0; i&lt; 10000; i++)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; : &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  bad_access();
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; :
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; : &nbsp;  return 0;
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; :
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; :}
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; :


opreport 解析的结果为：

GodSonSmall:/comcat/test/pmc.test# opreport -l ./cache
CPU: GODSON2E, speed 0 MHz (estimated)
Counted ICACHE_MISSES events (Instruction Cache misses number ) with a unit mask of 0x00 (No unit mask) count 500
samples  % &nbsp;  &nbsp;   symbol name
33 &nbsp;  &nbsp;  97.0588  bad_access
1 &nbsp;  &nbsp;  &nbsp; 2.9412  main

可以看到bad_access() cache miss 事件的样本共有33个，占总数的97%


改进 bad_access() 为 good_access() 后：

void good_access()
{
 &nbsp;  int k, j, sum = 0;

 &nbsp;  for(k = 0; k &lt; 2047; k++)
 &nbsp;  &nbsp;   for(j = 0; j &lt; 7; j++)
 &nbsp;  &nbsp;  &nbsp;  &nbsp; sum += matrix[k][j] * 1024;

}


CPU: GODSON2E, speed 0 MHz (estimated)
Counted ICACHE_MISSES events (Instruction Cache misses number ) with a unit mask of 0x00 (No unit mask) count 500
samples  % &nbsp;  &nbsp;   symbol name
22 &nbsp;  &nbsp;  95.6522  good_access
1 &nbsp;  &nbsp;  &nbsp; 4.3478  main

可以看到改进后 cache miss 事件的样本减少为22个，占总数的95%
]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=405]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Fri, 25 May 2007 17:59:19 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[Godson2e 平台下 I/O 端口的读写]]></title>
 <description><![CDATA[龙芯2E下，I/O端口是直接映射到地址空间的，不像x86下有专用的 I/O 空间，使用专用的 in/out 指令访问之。

故而龙芯上访问I/O端口直接使用访存指令访问对应的地址即可。（其他MIPS类似）


如：使用C语言读写8259A的0x20 端口

char *p_port;
char data;

p_port = (char *)(0xbfd00000+0x20); &nbsp;  /* 64位模式下为 0xffff ffff bfd0 0000 */

data = *p_port; &nbsp;  &nbsp; /* read io port */

data |= 0x1;

*p_port = data; &nbsp;  &nbsp; /* write io port */


现龙芯2E的IO基地址映射在0xbfd00000（64位为0xffffffffbfd00000）处，为了和IBM PC的IO端口地址空间保持某种程度上的对应，常用的控制器的IO端口地址只要在原来的基础上加上这个基地址就可以了。

如8042键盘控制器的0x60,0x61,0x64端口，CMOS RTC/RAM（兼容MC146818)的 0x70,0x71端口，8259a的0x20,0x21,0xa0,0xa1等等，现在loongson2e下访问之，只要加上0xbfd00000，就是其IO地址。读写端口方式与内存读写方式一致。

另外，由于龙芯2E下IO地址空间映射在KSEG1(0xA0000000~0xC0000000)，访问该地址空间需要在核心态下。


PS: 北桥bonito内的一些控制器映射在0xBFE00000处。

如 0xBFE00000+0x130 处映射为bonito之INTENSET寄存器

0xBFE00000+0x134 处映射为bonito之INTENCLR寄存器

0xBFE00000+0x138 处映射为bonito之INTEN寄存器

0xBFE00000+0x13c 处映射为bonito之INTISR寄存器

尚有INTEDGE(映射到0xBFE00124)、INTSTEER(映射到0xBFE00128)、INTPOL(映射到0xBFE0012C) 

详细用途参见： 《龙芯2E体系结构之异常、中断》
]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=403]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Tue, 22 May 2007 19:22:45 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[32位模式下使用64位寄存器注意事项]]></title>
 <description><![CDATA[1. 汇编环境

龙芯2E平台32位OS模式下，要使用64位寄存器可以在汇编代码里直接用，运算时使用d开头的指令(double-word, 64bit)，作用于寄存器即可。如：dadd, dsub, dmult, dmultu, ddiv, dsll, dsrl, dsra 等等。

访问存储器可以直接使用ld/sd, ldc1/sdc1

使用这些指令前，先用伪操作 .set mips3 告诉汇编器下面的指令是MIPS IV（64位指令集，兼容32位指令）中的指令。


2. C语言环境

可以内嵌汇编，在汇编中使用dadd, dsub, dmult, dmultu, ddiv, dsll, dsrl, dsra 等指令

内嵌汇编与C语言之间的数据方式要特别注意，如读取CP0 25 寄存器（64位,高低32位各为一个计数器）的值，使用如下代码，获取的高32位的值是不正确的：

 &nbsp;  long long counter;
 &nbsp;  int c0, c1;

 &nbsp;  asm(
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set mips3\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dmfc0 %0, $25\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set mips1\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; : &quot;=r&quot; (counter)
 &nbsp;  &nbsp;  );

 &nbsp;  c0 = counter;
 &nbsp;  c1 = counter &gt;&gt; 32;

 &nbsp;  printf(&quot;low is 0x%x\\n&quot;, c0);
 &nbsp;  printf(&quot;high is 0x%x\\n&quot;, c1);


可以与如下代码获取的值比较下就知道了：

unsigned int counter0, counter1;
unsigned int control;

void read_pmc()
{
 &nbsp;  __asm__ volatile (
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set mips3 &nbsp;   \\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dmfc0 %0, $24  \\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set mips0 &nbsp;   \\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; :&quot;=r&quot;(control)
 &nbsp;  &nbsp;  &nbsp;  &nbsp; );

 &nbsp;  __asm__ volatile (
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set mips3 &nbsp;   \\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dmfc0 %0, $25  \\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set mips0 &nbsp;   \\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; :&quot;=r&quot;(counter0)
 &nbsp;  &nbsp;  &nbsp;  &nbsp; );

 &nbsp;  __asm__ volatile (
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set mips3 &nbsp;   \\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dmfc0 $8, $25  \\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dsrl %0, $8, 32 \\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set mips0 &nbsp;   \\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; :&quot;=r&quot;(counter1)
 &nbsp;  &nbsp;  &nbsp;  &nbsp; );
}


int main()
{
 &nbsp;  read_pmc();

 &nbsp;  printf(&quot;control register is(cp0_24): 0x%016x\\n&quot;, control);
 &nbsp;  printf(&quot;counter0 register is(cp0_25_low): 0x%08x\\n&quot;, counter0);
 &nbsp;  printf(&quot;counter1 register is(cp0_25_high): 0x%08x\\n&quot;, counter1);

}


如果一定要把CP0_25的值放在一个long long 类型的变量里，则：

 &nbsp;  long long counter;
 &nbsp;  int c0, c1;

 &nbsp;  asm(
 &nbsp;  &nbsp;   &quot;.set mips3\\n\\t&quot;
 &nbsp;  &nbsp;   &quot;dmfc0 %M0, $25\\n\\t&quot;
 &nbsp;  &nbsp;   &quot;dsll &nbsp; %L0, %M0, 32\\n\\t&quot;
 &nbsp;  &nbsp;   &quot;dsrl &nbsp; %M0, %M0, 32\\n\\t&quot;
 &nbsp;  &nbsp;   &quot;dsrl &nbsp; %L0, %L0, 32\\n\\t&quot;
 &nbsp;  &nbsp;   &quot;.set mips0\\n\\t&quot;
 &nbsp;  &nbsp;   : &quot;=r&quot; (counter)
 &nbsp;  );

 &nbsp;  c0 = counter;
 &nbsp;  c1 = counter &gt;&gt; 32;

 &nbsp;  printf(&quot;low is 0x%x\\n&quot;, c0);
 &nbsp;  printf(&quot;high is 0x%x\\n&quot;, c1);


同样的要将一个long long的值写入,需用如下代码方能正确写入：

  asm(
 &nbsp;  &nbsp;   &quot;.set mips3\\n\\t&quot;
 &nbsp;  &nbsp;   &quot;dsll &nbsp; %L0, %L0, 32\\n\\t&quot;
 &nbsp;  &nbsp;   &quot;dsrl &nbsp; %L0, %L0, 32\\n\\t&quot;
 &nbsp;  &nbsp;   &quot;dsll &nbsp; %M0, %M0, 32\\n\\t&quot;
 &nbsp;  &nbsp;   &quot;or &nbsp; %L0, %L0, %M0\\n\\t&quot;
 &nbsp;  &nbsp;   &quot;dmtc0 %L0, $25\\n\\t&quot;
 &nbsp;  &nbsp;   &quot;.set mips0\\n\\t&quot;
 &nbsp;  &nbsp;   ::&quot;r&quot; (counter)
 &nbsp;  );

其中counter得定义为 long long

如果要用上面的代码写入0，则一定要这样 long long counter = 0 才可，直接传常数给他是不行的。


3. 常见问题

试看如下代码：

1 &nbsp;  long long counter;
2 &nbsp;  int c0, c1;

3 &nbsp;  asm(
4 &nbsp;  &nbsp;   &quot;.set mips3\\n\\t&quot;
5 &nbsp;  &nbsp;   &quot;dmfc0 %M0, $25\\n\\t&quot;
6 &nbsp;  &nbsp;   &quot;dsll &nbsp; %L0, %M0, 32\\n\\t&quot;
7 &nbsp;  &nbsp;   &quot;dsrl &nbsp; %M0, %M0, 32\\n\\t&quot;
8 &nbsp;  &nbsp;   &quot;dsrl &nbsp; %L0, %L0, 32\\n\\t&quot;
9 &nbsp;  &nbsp;   &quot;.set mips0\\n\\t&quot;
10 &nbsp;  &nbsp;   : &quot;=r&quot; (counter)
11 &nbsp;  );

12 &nbsp;  c0 = counter;
13 &nbsp;  c1 = counter &gt;&gt; 32;

14 &nbsp;  if(c0 &lt; 0)
15 &nbsp;  &nbsp;   printf(&quot;low is 0x%x\\n&quot;, c0);

16 &nbsp;  if(c1 &lt; 0)
17 &nbsp;  &nbsp;   printf(&quot;high is 0x%x\\n&quot;, c1);


其意图是在任何一个计数器溢出时（最高位为1，则补码表示即为负数），打印其值。

gcc 不带优化选项 -O 编译之，运行是没有问题的。

倘若加-O 编译之，运行时并非如我们所期待的行为，最高位为1时，他也不打印，逻辑关系完全错了。



这个问题我们来对比下gcc 生成的汇编码就清楚了：

不加-O 选项生成的代码为：

 &nbsp;  .set mips3
 &nbsp;  dmfc0 $3, $25
 &nbsp;  dsll &nbsp; $2, $3, 32
 &nbsp;  dsrl &nbsp; $3, $3, 32
 &nbsp;  dsrl &nbsp; $2, $2, 32
 &nbsp;  .set mips0

 &nbsp;  sw  $2,32($fp)
 &nbsp;  sw  $3,36($fp)
 &nbsp;  lw  $2,32($fp)

 &nbsp;  sw  $2,28($fp)
 &nbsp;  lw  $4,36($fp)
 &nbsp;  lw  $5,36($fp)

 &nbsp;  sra $2,$4,0
 &nbsp;  sra $3,$5,31

 &nbsp;  sw  $2,24($fp)
 &nbsp;  lw  $2,28($fp)

 &nbsp;  bgez &nbsp;  $2,$L2
 &nbsp;  lw  $2,%got($LC0)($28)

 &nbsp;  addiu &nbsp; $4,$2,%lo($LC0)
 &nbsp;  lw  $5,28($fp)
 &nbsp;  lw  $25,%call16(printf)($28)

 &nbsp;  jalr &nbsp;  $25
 &nbsp;  lw  $28,16($fp)
 &nbsp;  .....

加 -O 选项生成的代码为：

 &nbsp;  .set mips3
 &nbsp;  dmfc0 $17, $25
 &nbsp;  dsll &nbsp; $16, $17, 32
 &nbsp;  dsrl &nbsp; $17, $17, 32
 &nbsp;  dsrl &nbsp; $16, $16, 32
 &nbsp;  .set mips0

 &nbsp;  bgez &nbsp;  $16,$L2
 &nbsp;  lw  $4,%got($LC0)($28)

 &nbsp;  addiu &nbsp; $4,$4,%lo($LC0)
 &nbsp;  lw  $25,%call16(printf)($28)
 &nbsp;  .set &nbsp;  noreorder
 &nbsp;  .set &nbsp;  nomacro
 &nbsp;  jalr &nbsp;  $25
 &nbsp;  move &nbsp;  $5,$16
 &nbsp;  .set &nbsp;  macro
 &nbsp;  .set &nbsp;  reorder
 &nbsp; .....


可以看到使用 -O 时，gcc 将原两条 sra 指令合并入 dsll-dsrl-dsrl 中了，那么由dsrl 作用得到的counter0 （对应$16）、counter1 （对应$17），其高32位是都为0的，如果counter0的值为 0x8000 0012，则此时它在$16中的全值为0x0000 0000 8000 0012，而按照运行于32位兼容模式下的定义，低32位的值应该高位符号扩展的，$16中的值是这样 0xffff ffff 8000 0012 才表示为负数。故而在下面 bgez 判断的时候就会出现差错。

从这一点可以看到，分支转移指令的作用范围还是整个64位的。

解决的方法是将嵌入的汇编中的 dsrl 换成 dsra。

代码中的这种问题极其隐蔽，且很难发现，需要谨慎在意才是。


另外，内核中 include/asm-mips/mipsregs.h 中定义的__read_64bit_c0_split 宏就有这个问题，使用时要小心在意，因为内核编译时，是打开 -O 选项的。


]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=401]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Tue, 22 May 2007 18:39:23 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[龙芯汇编语言编程艺术]]></title>
 <description><![CDATA[分析系统调用的实现时看到这么一段代码，令人不禁拍案叫绝。

系统调用的参数传递，前4个参数通过a0~a3传，后面的参数要通过栈来传，目前内核
系统调用最长的参数个数为8。

用栈传递参数时，涉及到要将位于用户空间的参数先复制到内核空间（内核栈）。

因为系统调用的参数个数不定，因此就需要判断参数个数为5、6、7、8 不同情况时，
相应的复制操作个数。5个参数时需要复制个数为1，6个时为2，以此类推。

通常的解决方法是用switch语句，这个编译出来，得要四条分支判断语句吧

看看内核的一些牛人怎么实现：



 &nbsp;  la  t1, 5f &nbsp;  &nbsp;  &nbsp;  # 标号5所示之地址入t1
 &nbsp;  subu &nbsp;  t1, t3 &nbsp;  &nbsp; # t3 的内容为当前系统调用参数个数减去5，再乘以4
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; #  这个已经预先计算好，保存于系统调用表每项的第二个
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; #  字段，用时直接载入。

 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; # 另外，此前已经判断过，t3 的内容大于等于0

1:  lw  t5, 16(t0) &nbsp;  &nbsp; # t0 的内容为用户空间第一个参数的地址

 &nbsp;  .set &nbsp;  push
 &nbsp;  .set &nbsp;  noreorder
 &nbsp;  .set &nbsp;  nomacro

 &nbsp;  jr  t1
 &nbsp;   addiu  t1, 6f - 5f &nbsp; # 妙用分支延迟槽

2:  lw  t8, 28(t0) &nbsp;  &nbsp; # 取用户空间第8个参数
3:  lw  t7, 24(t0) &nbsp;  &nbsp; # 取用户空间第7个参数
4:  lw  t6, 20(t0) &nbsp;  &nbsp; # 取用户空间第6个参数
5:  jr  t1
 &nbsp;   sw t5, 16(sp) &nbsp;  &nbsp; # 第5个参数进入内核栈

C:  sw  t8, 28(sp) &nbsp;  &nbsp; # 第8个参数进入内核栈
B:  sw  t7, 24(sp) &nbsp;  &nbsp; # 第7个参数进入内核栈
A:  sw  t6, 20(sp) &nbsp;  &nbsp; # 第6个参数进入内核栈
6:  j &nbsp; stack_done &nbsp;  &nbsp; # 参数复制完毕，返回
 &nbsp;   nop


只用两条 jr 指令，效率大大提高！简直妙不可言！

下面详细分析之：

(1) t3 的值，在参数个数为5 时，t3 为0，6 时为4，7 时为8，8 时为12
 &nbsp;  这个 t3 在这里，实际上是用来表示相对于标号5处的地址偏移！ 因为mips/godson下，
 &nbsp;  指令的长度都是4个字节。因此 t3 值为4（参数个数为6）时，第一个 jr t1 是跳转到
 &nbsp;  标号4处开始执行的。

(2) 第一个 jr t1 用来解决从用户空间复制数据操作的个数问题，相对应的，则是解决写入
 &nbsp;  操作的个数问题，这个用第二个 jr t1 来解决。在此之前，更新 t1 的指令(addiu)的
 &nbsp;  位置放的很巧妙，置于第一个 jr t1 的延迟槽中，不占用标号4与标号5之间的空间。

(3) 可以看到参数个数为5时，第二个 jr t1 直接跳转到标号6处执行，将第5个参数写入内
 &nbsp;  核栈的操作置于延迟槽中；参数个数为6时，会跳转到标号A处执行；参数个数为8时，会
 &nbsp;  跳转到标号C处执行，依次完成第5、8、7、6参数的写入。


结论：

该段程序的作者对MIPS平台下的延迟槽有深刻的理解，故而才能有如此神乎其技的妙用。



]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=368]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Fri, 27 Apr 2007 17:49:22 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[mplayer 中收集视频播放数据]]></title>
 <description><![CDATA[Mplayer 中有 -benchmark 参数可以收集视频播放的数据


1. 常用命令

mplayer -benchmark foreman.avi 


如果不需要视频输出，可以这样：

mplayer -benchmark -vo null foreman.avi


一般情况下，我们测试某个视频解码器的效果，也不需要音频的数据，则：

mplayer -nosound -vo null -benchmark foreman.avi



2. 结果分析

foreman.avi 这段视频播放完了，mplayer 会输出类似如下信息：

BENCHMARKs: VC: 97.821s VO: 0.000s A: 0.000s Sys: 7.679s = 105.500s
BENCHMARK%: VC: 92.7213% VO: 0.0000% A: 0.0000% Sys: 7.2787% = 100.0000%

第一行是以秒为单位的统计，第二行是以百分比为单位的统计。每行最后等号后面是总计。

各主要字段的含义如下：

VC: 视频解码所需时间
VO: 视频输出所需时间
A:  音频解码所需时间

以上为用户空间程序执行所花时间

Sys: 花在内核空间的时间


则：播放该视频数据解析如下：

视频解码时间为 97.821s
视频输出的时间为 0.000s
音频输出的时间为 0.000s
用于核心态代码的时间为7.679s 
总时间为  105.500s


3. 福珑（内置龙芯2E）上实际测试

测试视频使用新闻联播报道龙梦的一段，mpeg4 格式

音视频信息 mplayer 报：

VIDEO:  [DIV3]  720x576  24bpp  25.000 fps  944.1 kbps (115.2 kbyte/s)
AUDIO: 44100 Hz, 2 ch, s16le, 1411.2 kbit/100.00% (ratio: 176400-&gt;176400)


I. mplayer -benchmark test.avi

AO: [oss] 44100Hz 2ch s16le (2 bytes per sample)
VO: [xv] 720x576 =&gt; 720x576 Planar YV12

A: 159.7 V: 159.4 A-V:  0.267 ct:  6.984 3987/3987 121% 39%  2.9% 2251 0

BENCHMARKs: VC: 193.669s VO:  63.772s A: &nbsp; 4.612s Sys:  10.932s =  272.985s
BENCHMARK%: VC: 70.9451% VO: 23.3610% A:  1.6895% Sys:  4.0045% = 100.0000%


II. mplayer -benchmark test.avi -vo xvidix （使用优化过的视频输出组件）

AO: [oss] 44100Hz 2ch s16le (2 bytes per sample)
VO: [xvidix] 720x576 =&gt; 720x576 Planar YV12

A: 159.2 V: 159.4 A-V: -0.287 ct: -0.040 3987/3987 44%  0%  1.3% 56 0

BENCHMARKs: VC:  71.352s VO: &nbsp; 0.161s A: &nbsp; 2.062s Sys:  96.128s =  169.704s
BENCHMARK%: VC: 42.0451% VO:  0.0951% A:  1.2153% Sys: 56.6445% = 100.0000%


测试I 花费在视频输出上的时间为 63.772s，占总时间的 23.3610%

且因为视频输出的速度跟不上，造成测试视频解码的时间是测试II 的2倍多。


mplayer 默认的视频输出组件 xv ，不知是不是与 Xorg 有关？

]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=357]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Tue, 24 Apr 2007 15:30:31 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[FFMPEG 笔记]]></title>
 <description><![CDATA[1. encode yuv file

ffmpeg -s cif -vcodec mpeg4 -i paris.yuv paris.avi

-s 指定帧大小 cif 为 352x288，qcif 为 176x144，4cif 为 704x576
-vcodec 指定采用的编码器
-i 指定输入文件


2. output raw YUV420P file

ffmpeg -i paris.avi paris0.yuv


3. 将一段视频输出为图片序列

ffmpeg -i 1.avi cat%d.png -vcode png

-vcodec mjpeg
-vcodec ppm


ffmpeg -i 1.avi cat%04d.jpg -vcodec mjpeg -ss 0:1:2 -t 0:0:1

将1.avi视频 1分02秒 处开始，持续1秒长的视频输出为jpg的序列

ffmpeg -vcodec mjpeg -i 1.flv  test%02d.jpg  -ss 0:0:2 -t 0.001

-t 表示持续时间为0.001秒，这个命令相当于截取开始2秒处的一幅jpeg的图片


4. 多输入单输出

ffmpeg -i /tmp/a.wav -s 640x480 -i /tmp/a.yuv /tmp/a.mpg


5. 单输入多输出

ffmpeg -i /tmp/a.wav -ab 64 /tmp/a.mp2 -ab 128 /tmp/b.mp2 -map 0:0 -map 0:0

-map file:stream_index 指定哪一个输入流用于输出流，顺序对应


6. DVD to mpeg4

ffmpeg -i snatch_1.vob -f avi -vcodec mpeg4 -b 800 -g 300 -bf 2 -acodec mp2 -ab 128 snatch.avi

压制高品质mp4的参考参数：

&#39;-mbd rd -flags +4mv+trell+aic -cmp 2 -subcmp 2 -g 300 -pass 1/2&#39; 

可以试试： &#39;-bf 2&#39;, &#39;-flags qprd&#39;, &#39;-flags mv0&#39;, &#39;-flags skiprd&#39;


7. encode mpeg1/mpeg2

ffmpeg -i 1.avi -vcodec mpeg2video 2.mpg

注意mpeg2的codec为 mpeg2video

其他codec可以使用 ffmpeg -formats 查看

压制高品质mp1/mp2的参考参数：

&#39;-mbd rd -flags +trell -cmp 2 -subcmp 2 -g 100 -pass 1/2&#39; 

注意，加 &#39;-g 100&#39; 可能会使某些解码器没法解码 

可以试试： &#39;-bf 2&#39;, &#39;-flags qprd&#39;, &#39;-flags mv0&#39;, &#39;-flags skiprd&#39;


8. encode flv

ffmpeg -i 1.avi -ab 56 -ar 22050 -b 500 -r 15 1.flv


9. X 屏幕录像

FFmpeg can grab the X11 display.

ffmpeg -f x11grab -i :0.0 /tmp/out.mpg

0.0 is display.screen number of your X11 server, same as the DISPLAY environment variable.

ffmpeg -f x11grab -i :0.0+10,20 /tmp/out.mpg

0.0 is display.screen number of your X11 server, same as the DISPLAY environment variable. 
10 is the x-offset and 20 the y-offset for the grabbing. 


10. 音视频采集

ffmpeg -f audio_device -i /dev/dsp -f video4linux2 -i /dev/video0 /tmp/out.mpg

Note that you must activate the right video source and channel before launching FFmpeg with 
any TV viewer such as xawtv ([url]http://bytesex.org/xawtv/[/url]) by Gerd Knorr. You also have to set 
the audio recording levels correctly with a standard mixer.


11. 常用选项

-i filename 输入文件

-f fmt 强迫采用格式fmt

-y 覆盖输出文件

-ss position 搜索到指定的时间处开始 [-]hh:mm:ss[.xxx]的格式也支持

-b bitrate 设置比特率，缺省200kb/s

-r fps 设置帧频 缺省25

-s size 设置帧大小 格式为WXH 缺省160X128.下面的简写也可以直接使用：
 &nbsp;  &nbsp;  sqcif 128X96 qcif 176X144 cif 352X288 4cif 704X576

-vcodec codec 强制使用codec编解码方式。 如果用copy表示原始编解码数据必须被拷贝。

-sameq 使用同样视频质量作为源（VBR）

-g gop_size 设置图像组大小

-intra 仅适用帧内编码

-bf frames 使用frames B 帧，支持mpeg1,mpeg2,mpeg4

-ab bitrate 设置音频码率

-ar freq 设置音频采样率

-ac channels 设置通道 缺省为1

-an 不使能音频纪录

-acodec codec 使用codec编解码

-benchmark 为基准测试加入时间

-hex 倾倒每一个输入包
]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=351]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Fri, 20 Apr 2007 18:42:37 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[MIPS GCC 嵌入式汇编（龙芯适用）]]></title>
 <description><![CDATA[当前版本： 0.1


1. GCC 内嵌汇编的基本格式

asm(&quot;assembly code&quot;);

如：

 &nbsp;  &nbsp;  asm(&quot;syscall&quot;); &nbsp;  &nbsp;  //触发一个系统调用


如果有多条指令，则需在指令尾部添加&#39;\\t&#39;和&#39;\\n&#39;，如：

 &nbsp;  &nbsp;  asm(&quot;li &nbsp;  &nbsp;  v0, 4011\\t\\n&quot; &quot;syscall&quot;);


括号里的字符串 GCC 前端不作分析，直接传给汇编器 as ，故而相联指令间需插入换行符。

&#39;\\t&#39; 加入只为排版对齐一些而已，可以使用 gcc -S tst.c -o tst.s 查看生成的 tst.s

因为 GCC 并不对 asm 后括号中的指令作分析，故而如果指令修改一些的寄存器的值，GCC是
不知道的，这个会引入一些问题。

另外 asm 可以替换为 __asm__ ，效果等价。__asm__ 一般用于头文件中，防止关键字 asm 
可能与一些变量、函数名冲突。 &nbsp;  &nbsp;  

内嵌汇编如何与 C 变量交换数据？


2. GCC 内嵌汇编扩展格式

asm ( &nbsp;  &nbsp;   
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   &quot;assembly code&quot; 
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   : output_operand &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; /* 输出参数列表 */ 
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   : input_operand &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  /* 输入参数列表 */ 
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   : clobbered_operand &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; /* 被改变的操作对象列表 */ 
 &nbsp;  &nbsp;  );


以一个例子来说明：

如果我们要读取CP0 25 号硬件计数寄存器的值，并返回之，可以这样：

int get_counter()
{
 &nbsp;  &nbsp;  int rst;

 &nbsp;  &nbsp;  asm( &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  /* mfc0 为取cp0 寄存器值的指令 */
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   &quot;mfc0 &nbsp;  &nbsp;  %0, $25\\t\\n&quot; &nbsp;  &nbsp;  &nbsp;  &nbsp;   /* %0 表示列表开始的第一个寄存器 */
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   : &quot;=r&quot; (rst) &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  /* 告诉gcc 让rst对应一个通用寄存器 */
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   );

 &nbsp;  &nbsp;  return rst;
}


&quot;=r&quot; 中，&#39;=&#39; 为修饰符，表示该操作对象只写，一般用于修饰输出参数列表中。&#39;r&#39; 表示任意
一个通用寄存器。

由于我们只要取得一个值，故而只用到了输出列表。代码中也没修改一些寄存器的值gcc不知道，
（输出、输入列表中的寄存器gcc是知道的）故而被改变的操作对象列表亦可省去。


如果我们要重设CP0 24 号硬件计数器之控制寄存器的值，则：

 &nbsp;  unsigned int op = 0x80f;

 &nbsp;  &nbsp;  asm volatile(
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;   &quot;mtc0 %0, $24&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;   : &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  /* 没有输出，列表为空 */
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;   :&quot;r&quot;(op) &nbsp;  &nbsp;  &nbsp;  &nbsp;   /* 输入参数，告诉gcc 让op对应一个通用寄存器 */
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  );


volatile 关键字表示让GCC优化生成代码时，不要移动、删除我们的汇编码。
另外 __volatile__与其含义相同，引入的目的与__asm__是一样的。


如果我们重设后，立即读取CP0 24号寄存器的值，则：

 &nbsp;  &nbsp;  unsigned int rst;
 &nbsp;  unsigned int op = 0x80f;

 &nbsp;  &nbsp;  asm volatile(
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;   &quot;mtc0 &nbsp;  &nbsp;  %1, $24\\t\\n&quot; &nbsp;  &nbsp;  /* %1 表示 op 对应的寄存器 */
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;   &quot;mfc0 &nbsp;  &nbsp;  %0, $25\\t\\n&quot; &nbsp;  &nbsp;  /* %0 表示 rst 对应的寄存器 */
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;   : &quot;=r&quot; (rst)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;   : &quot;r&quot; (op)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  );



如果我们要操作的对象位于存储器中，我们可以使用 &#39;m&#39; 来修饰输入输出参数，如：

unsigned short data[] = {
 &nbsp;  0x0, 0x0, 0x0, 0x0,
 &nbsp;  0x1, 0x3, 0x5, 0x7,
 &nbsp;  0x1, 0x3, 0x5, 0x7,
};

void pmullh()
{
 &nbsp;  &nbsp;  asm volatile
 &nbsp;  &nbsp;  &nbsp; (
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   &quot;.set mips3\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   &quot;.set noreorder\\n\\t&quot;

 &nbsp;  &nbsp;  &nbsp;  &nbsp;   &quot;ldc1 $f0, %1\\n\\t&quot; &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; /* 取 data+4 处的四个数组元素值到 f0 中 */
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   &quot;ldc1 $f2, %2\\n\\t&quot; &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; /* 对应输入列表的 *(data+8) &nbsp;  &nbsp;  */
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; /* %2 编译后会替换成类似 16($12) 的形式 */
 &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; 

 &nbsp;  &nbsp;  &nbsp;  &nbsp;   &quot;pmullh $f2, $f2, $f0\\n\\t&quot; &nbsp;  &nbsp;  /* 按16位为单位数据相乘，取结果的低位 */

 &nbsp;  &nbsp;  &nbsp;  &nbsp;   &quot;sdc1 $f2, %0\\n\\t&quot; &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp; /* 将结果写入data的前四个位置 */

 &nbsp;  &nbsp;  &nbsp;  &nbsp;   &quot;.set reorder\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   &quot;.set mips0\\n\\t&quot;

 &nbsp;  &nbsp;  &nbsp;  &nbsp;   : &quot;=m&quot;(*data)
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   : &quot;m&quot;(*(data+4)), &quot;m&quot;(*(data+8))
 &nbsp;  &nbsp;  &nbsp;  &nbsp;   : &quot;$f0&quot;, &quot;$f2&quot;, &quot;memory&quot;
 &nbsp;  &nbsp;  &nbsp; );
}


注意到使用&#39;m&#39;修饰的操作数，后面括号里跟的不是指针，而是开始的第一个元素值。
%0，%1, %2 依次对应输出列表的一个，输入列表的两个操作数，编译后会被gcc替换
成类似 0($12)，8($12)，16($12)的形式，其中$12置数组首地址，即: %0等价于0($12)

由于我们嵌入的代码改变了 $f0, $f2 的值，而他们不在输出、输入列表中，故而要将其陈列
于被改变操作数列表(clobbered operand list)中，以告诉gcc 我们改变了他们的值，以免gcc 误判。

因为代码中我们改变了内存中数据的值，如果此前gcc生成的代码读取了该内存处的值，并保
存于寄存器中的话，由于我们更新了这段数据，所以需要告诉gcc在后面要重新加载数据，这
个需要在被改变操作数列表中(clobbered operand list)写入 &quot;memory&quot;。


3. 修饰符

= &nbsp;  &nbsp;   只写，常用于修饰所有输出操作数 &nbsp;  &nbsp;  
+ &nbsp;  &nbsp;   只读
& &nbsp;  &nbsp;   只用于输出，一般和&#39;=&#39;一起用，如：&quot;=&r&quot; (val)


4. 其他对输入、输出对象的操作符

可以参看gcc doc 之 5.36 节（Constraints for `asm&#39; Operands）获取更多信息，下面列出MIPS 平台专用的操作符：

 &nbsp;  `d&#39;
 &nbsp;  &nbsp;  &nbsp;  General-purpose integer register

 &nbsp;  `f&#39;
 &nbsp;  &nbsp;  &nbsp;  Floating-point register (if available)

 &nbsp;  `h&#39;
 &nbsp;  &nbsp;  &nbsp;  `Hi&#39; register

 &nbsp;  `l&#39;
 &nbsp;  &nbsp;  &nbsp;  `Lo&#39; register

 &nbsp;  `x&#39;
 &nbsp;  &nbsp;  &nbsp;  `Hi&#39; or `Lo&#39; register

 &nbsp;  `y&#39;
 &nbsp;  &nbsp;  &nbsp;  General-purpose integer register

 &nbsp;  `z&#39;
 &nbsp;  &nbsp;  &nbsp;  Floating-point status register

 &nbsp;  `I&#39;
 &nbsp;  &nbsp;  &nbsp;  Signed 16-bit constant (for arithmetic instructions)

 &nbsp;  `J&#39;
 &nbsp;  &nbsp;  &nbsp;  Zero

 &nbsp;  `K&#39;
 &nbsp;  &nbsp;  &nbsp;  Zero-extended 16-bit constant (for logic instructions)

 &nbsp;  `L&#39;
 &nbsp;  &nbsp;  &nbsp;  Constant with low 16 bits zero (can be loaded with `lui&#39;)

 &nbsp;  `M&#39;
 &nbsp;  &nbsp;  &nbsp;  32-bit constant which requires two instructions to load (a
 &nbsp;  &nbsp;  &nbsp;  constant which is not `I&#39;, `K&#39;, or `L&#39;)


 &nbsp;  `N&#39;
 &nbsp;  &nbsp;  &nbsp;  Negative 16-bit constant

 &nbsp;  `O&#39;
 &nbsp;  &nbsp;  &nbsp;  Exact power of two

 &nbsp;  `P&#39;
 &nbsp;  &nbsp;  &nbsp;  Positive 16-bit constant

 &nbsp;  `G&#39;
 &nbsp;  &nbsp;  &nbsp;  Floating point zero

 &nbsp;  `Q&#39;
 &nbsp;  &nbsp;  &nbsp;  Memory reference that can be loaded with more than one
 &nbsp;  &nbsp;  &nbsp;  instruction (`m&#39; is preferable for `asm&#39; statements)

 &nbsp;  `R&#39;
 &nbsp;  &nbsp;  &nbsp;  Memory reference that can be loaded with one instruction (`m&#39;
 &nbsp;  &nbsp;  &nbsp;  is preferable for `asm&#39; statements)

 &nbsp;  `S&#39;
 &nbsp;  &nbsp;  &nbsp;  Memory reference in external OSF/rose PIC format (`m&#39; is
 &nbsp;  &nbsp;  &nbsp;  preferable for `asm&#39; statements)


5. 32位下传递64位数据

 &nbsp;  A. 读取：

 &nbsp;  long long counter;
 &nbsp;  asm(
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set mips3\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dmfc0  %M0, $25\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dsll &nbsp; %L0, %M0, 32\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dsrl &nbsp; %M0, %M0, 32\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dsrl &nbsp; %L0, %L0, 32\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set mips0\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; : &quot;=r&quot; (counter)
 &nbsp;  &nbsp;  );


 &nbsp;  B. 写入

 &nbsp;  long long counter = 0x0000001000000100;

 &nbsp;  asm(
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set mips3\\n\\t&quot;

 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dsll &nbsp; %L0, %L0, 32\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dsrl &nbsp; %L0, %L0, 32\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dsll &nbsp; %M0, %M0, 32\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;or &nbsp;  %L0, %L0, %M0\\n\\t&quot; 
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dmtc0  %L0, $25\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set mips0\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; : &quot;=r&quot; (counter)
 &nbsp;  &nbsp;  );



]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=329]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Mon, 09 Apr 2007 18:06:03 +0000]]></pubdate>
</item>
<item>
 <title><![CDATA[龙芯2E平台程序性能分析]]></title>
 <description><![CDATA[1. 分析一段代码的性能，最常用的方法是测量这段代码的运行时间。假如我要分析下面这两段代码的性能差异，可以在代码前后插入2个变量，分别记录运行前后的时间，相减即可：

代码一：

[code]
int matrix[2047][7];


int main()
{
 &nbsp;  int i, j, sum = 0;

 &nbsp;  unsigned int beg, end;

A1:
 &nbsp;  for(i = 0; i &lt; 7; i++)
 &nbsp;  &nbsp;   for(j = 0; j &lt; 2047; j++)
 &nbsp;  &nbsp;  &nbsp;  &nbsp; sum += matrix[j][i] * 1024;

B1:

}
[/code]


代码二：

[code]
int matrix[2047][7];

int main()
{
 &nbsp;  int i, j, sum = 0;
 &nbsp;  unsigned int beg, end;

A2:
 &nbsp;  for(i = 0; i &lt; 2047; i++)
 &nbsp;  &nbsp;   for(j = 0; j &lt; 7; j++)
 &nbsp;  &nbsp;  &nbsp;  &nbsp; sum += matrix[i][j] * 1024;
B2:

}
[/code]

则可以在A1,A2处插入 beg = times(NULL); // 返回以过去某点为基准点的时钟滴答数
B1, B2处插入 end = times(NULL); &nbsp;  &nbsp;   // 需要包含头文件 sys/times.h

相减即可得到该段代码的运行时间。

当前linux系统的每秒经过的时钟滴答数可以使用sysconf(_SC_CLK_TCK)获得，我在linux-2.6.18 for loongson2e的环境下获取的值为 100，即最小测量的精度是10ms。这个精度要远远高于time() 1s的精度，但是这个精度还是无法满足上面测量要求，因为分别编译运行后，两段代码的运行时间是一致的，这个结果是不能被接收的，从定性的分析开看，代码二的性能肯定是优于代码一的。



2. 使用龙芯2E系统协处理器(CP0)中的两个性能计数器(performance counter)

龙芯2E之系统协处理器中引入了两个性能计数器，对应CP0寄存器的25号，长度为64位，低32位作为Counter 0，高32位作为Counter 1。其中CP0的24号寄存器作为2个计数器的控制寄存器，亦是64位，可以设置它，分别控制两个计数器所记录的事件。

CP0的24号寄存器简单描述：3~0 位作为使能位目前置1；第5位作为中断使能位目前置0；8~5为Counter 0的计数事件，12~9 作为Counter 1的计数事件，其余位置0。

比如我要控制 Counter 0对时钟周期进行计数，Counter 1对一级数据缓存不命中进行计数，可以将CP0D的24号寄存器置值为 0x80f(01000 0000 1111)，即Counter 0的计数事件为0000，Counter 1的计数事件为0100，这个值可以查看龙芯2E用户手册。


3. 设置性能计数器的计数事件

Counter 0常用计数事件（4位）：

0000： 时钟周期计数
0001： 分支指令计数
0100： 一级I-cache不命中计数
1001： 从主存中读计数
1110： TLB重填例外计数


Counter 1常用计数事件（4位）：

0001： 分支预测失败计数
0100： 一级D-cache不命中计数
0111： 访问未缓存计数
1001： 写到主存计数
1100： ITLB不命中计数

目前的内核起起来后，CP0的24号寄存器为 0x1ef，故而我要重设24号寄存器的值 0x80f(01000 0000 1111，Counter 0的计数事件为0000，Counter 1的计数事件为0100).

对龙芯2E系统协处理器的寄存器执行写操作在用户空间是没有写权限的。我们可以写一个简单的模块，把设置的操作放在模块初试化函数中来完成：

[code]
[set_event.c]

#include &lt;linux/init.h&gt;
#include &lt;linux/module.h&gt;

MODULE_LICENSE(&quot;GPL&quot;);

static int set_init(void)
{
 &nbsp;  unsigned long op = 0x80f;
 &nbsp;  asm volatile(&quot;mtc0 %0, $24&quot;::&quot;r&quot;(op));
 &nbsp;  printk(KERN_ALERT &quot;cp0_24 set complete\\n&quot;);
 &nbsp;  return 0;
}

static void set_exit(void)
{
 &nbsp;  unsigned int val = 0;
 &nbsp;  asm volatile(&quot;mfc0 %0, $24&quot;:&quot;=r&quot;(val));
 &nbsp;  printk(KERN_EMERG &quot;current cp0_24 value is 0x%x\\n&quot;, val);
}

module_init(set_init);
module_exit(set_exit);
[/code]

相应的Makefile为：
[code]
[Makefile]
ifeq ($(KERNELRELEASE),)

 &nbsp;  KERNELDIR ?= /lib/modules/$(shell uname -r)/build
 &nbsp;  PWD := $(shell pwd)

modules:
 &nbsp;  $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:
 &nbsp;  $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install

clean:
 &nbsp;  rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

.PHONY: modules modules_install clean

else
 &nbsp;  # called from kernel build system: just declare what our modules are
 &nbsp;  obj-m := set_event.o
endif
[/code]


4. 解决问题

在设置好两个计数器的计数事件后，我们依然在A1，A2插入代码，读取两个计数器的值，并记录：

 &nbsp;  unsigned int cache_miss_beg, cache_miss_end;

 &nbsp;  asm volatile
 &nbsp;  &nbsp;   (
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set mips3\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dmfc0  %0, $25\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dsra &nbsp; %0, 32\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; :&quot;=r&quot;(cache_miss_beg) &nbsp;  &nbsp;  &nbsp;  &nbsp; //记录运行前Counter 1的值，L1 D-cache的事件
 &nbsp;  &nbsp;   );
 &nbsp;  asm volatile(&quot;mfc0 %0, $25&quot;:&quot;=r&quot;(beg));  //记录运行前Counter 0的值，CPU时钟周期事件


在B1，B2插入：

 &nbsp;  asm volatile(&quot;mfc0 %0, $25&quot;:&quot;=r&quot;(end));  //记录运行后Counter 0的值，CPU时钟周期事件
 &nbsp;  asm volatile
 &nbsp;  &nbsp;   (
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;.set mips3\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dmfc0  %0, $25\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; &quot;dsra &nbsp; %0, 32\\n\\t&quot;
 &nbsp;  &nbsp;  &nbsp;  &nbsp; :&quot;=r&quot;(cache_miss_end) &nbsp;  &nbsp;  &nbsp;  &nbsp; //记录运行后Counter 1的值，L1 D-cache的事件
 &nbsp;  &nbsp;   );

 &nbsp;  printf(&quot;Total CPU Cycles is: %d\\n&quot;, end - beg);
 &nbsp;  printf(&quot;The number of L1 D-Cache miss is: %d\\n&quot;, cache_miss_end - cache_miss_beg);


基于计数器的测量方法，精度很高，可以得到程序执行期间所经过的时钟周期数。但是影响其准确性的因素也很多，主要有因进程上下文切换，致使其他进程占用处理器，影响计数器的计数，这个可以通过在低负载的机器上多次运行求平均值的方法来弱化。

如上面的例子我们执行 init 1 进入单用户模式下，此时系统仅有一个bash进程，分别执行5次求得平均值：

代码一所需时钟周期为： &nbsp;  &nbsp;  &nbsp;  &nbsp; 1035200
代码一一级数据cache缺失次数为： &nbsp; 2431

代码二所需时钟周期为： &nbsp;  &nbsp;  &nbsp;  &nbsp; 735310
代码二一级数据cache缺失次数为： &nbsp; 2092

代码一所需时钟周期是代码二的 1.4 倍
代码一一级数据cache缺失次数是代码二的 1.16 倍

可以看到，代码一访问数组按行访问（C语言中，数组存放于内存是按列存放），这样CPU对数据的访问就会呈“跳跃”的形态，违背了空间局部性，致使cache命中率下降，影响了整个代码的性能。



测试代码： [url]http://people.openrays.org/~comcat/misc/pmc.tar.gz[/url]


]]></description>
 <link><![CDATA[http://blog.openrays.org/blog.php?do=showone&tid=316]]></link>
 <author><![CDATA[comcat]]></author>
 <category><![CDATA[华镭社区博客]]></category>
 <pubdate><![CDATA[Tue, 27 Mar 2007 18:12:48 +0000]]></pubdate>
</item>
</channel></rss>
