momo zone

调核人的blog

Monthly Archives: 六月 2011

spin lock在kernel 2.4 和2.6 中的实现与改进

1. TAS lock (test-and-set)
这是最简单的spinlock,CPU会在硬件上提供一些指令来帮助OS实现spinlock,
比如x86就有xchg, LOCK指令前缀等指令。。。
test_and_set()可以利用这些指令对某个memory地址,来原子地完成:
写入true到这个地址,同时返回这个地址储存的旧的值。

void spin_lock(lock)
{
while (test_and_set(lock, true));
}

void spin_unlock(lock)
{
atomic_set(lock, false);
}

在SMP(shared bus)的环境里,TAS lock的性能非常差。
首先test_and_set一般都需要serialization,即在执行这条指令前,
CPU必须要完成前面所有对memory的访问指令(read and write)。
这是非常heavy的操作,使得CPU无法对指令进行reorder,从而优化执行。

其次,因为每一次test_and_set都必须读-写lock这个变量。这就要涉及到
多个CPU之间cache coherence的问题。

当CPU1读lock的时候,如果这个lock没有在CPU1的cache中,就需要从memory中读入,
因为CPU又需要写这个变量,所以在把这个变量读入cache的时候,如果其他CPU已经
cache了这个变量,就需要invalidate它们。这样在CPU1把lock读入自己的cache中时,
这块cacheline所cache的lock就是CPU1所独占的,CPU1就可以更改它的值了。

如果多个CPU在竞争这个spinlock的话,每一次test_and_set都需要完成以上的操作,
在系统总线上会产生大量的traffic,开销是非常大的,而且unlock的CPU还必须同其它正在
竞争spinlock的CPU去竞争cacheline ownership. 随着CPU数目的增多,性能会成衰减的非常快。

###################################################################

2. TTAS (test-and-test-and-set)

void spin_lock(lock)
{
while (test_and_set(lock, true))
while (lock != false);
}

实际代码(kernel 2.3):

注意这里lock初始值为0 ,也就是当该值为0时为未锁,为1时为上锁 。 该定义在2.4之后被翻转。 

extern inline void spin_lock(spinlock_t *plock) 

1: 
lock ; btsl $0,plock;  (btsl 指令:根据位偏移0值从位串plock中取出一位放入CF中,然后将位串中的该位置成1)
jc 2f; 

注意,下一条指令的物理位置其实不是testb,而是跳出该函数。因为下面定义了一个 专门的区(.text.lock)来存放一段代码。它们和前边的”js 2f”并不在一个区(section)里,原因是在大多数情况下,spin lock是能获取成功的,从.section 到.previous的这一段代码并不经常被调用,如果把它跟别的常用指令混在一起,会浪费指令 缓存的空间。
.section .text.lock,”ax” 

2: 
testb $1,plock; 
rep;nop; 

注意这里rep其实不一定根据ecx寄存器的值来做循环,编译器其实会把rep;nor翻译成pause 指令,告诉处理器所执行的代码序列是一个 spin-wait loop。处理器会根据这个提示而避开内存序列冲突(memory order violation),也就是说对 spin-wait loop 不做缓存,不做指令重新排序等动作。这样就可以大大的提高了处理器的性能。正是基于此,才建议在 spin-wait loops 中使用 pasuse 指令。PAUSE指令的另外一个功能是让 Pentium4 处理器在执行 spin-wait loop 时可以减少电源的消耗。
在等待资源而执行自旋锁等待时,Pentium4 处理器以极快的速度执行自旋等待时,将会消耗很多电能,但使用 pause 指令则可以极大的减少处理器的电能消耗。

jne 2b; 
jmp 1b; 
.previous 

TTAS lock的改进是,当有CPU(CPU0)已经抓住了这把锁的话,CPU1就只会以只读的方式
cache这个lock。这样做的好处好处就是,CPU1在等待这把锁的时候,不会在总线上
产生traffic,和其它CPU一起竞争cacheline的ownership。

第一次的test_and_set还是和TAS lock一样,需要invalidate CPU0的cacheline,
这样的话CPU1独占的拥有了cache lock变量的cacheline。当它发现锁已经被别人锁住了,
CPU1就开始进入第二层循环。

如果CPU2这时也想抢这把锁,它执行test_and_set时,会invalidate CPU1的cacheline。
它也发现锁已经被锁住了,进入第二层循环。

这时CPU1想读lock这个变量的时候,会cache miss,会把read的请求发到系统总线上,
从memory中读入lock的值,同时CPU2的cache controller会发现总线上的这次交易,它
会把自己cache了lock的cacheline的状态转为shared。
这样CPU1和CPU2的cache里都cache了lock,第二层循环就都只在CPU内部执行,不会产生
总线交易。

当CPU0释放锁的时候,会invalidate CPU1和CPU2的cacheline,CPU1/CPU2会cache miss,
重新读入lock,发现锁已经被释放了,就会进行一个test_and_set(),谁先完成就抢到了
锁,另一个就必须重新循环等待锁的释放。

TTAS lock在自己的local cache copy上spinning被称为local spinning。是设计高效
的spinlock非常重要的一个原理。

#######################################################################

3. TTAS with random backoff
TTAS lock有一个问题是在释放锁的时候,会产生大量的总线交易,因为所有在竞争的
CPU都会去作一个test_and_set().

在local spinning的时候,如果引入一定的延时(就像以太网的collision avoidance机制),
这样就会有效的降低在锁释放时系统总线的竞争。

在2.6.25之前,Linux kernel x86的spinlock的实现就是这一类型的。

static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
asm volatile(“\n

1:\t”
LOCK_PREFIX ” ; decb %0\n\t”

decb将lock->lock减1,它前边的lock指令表示在执行decb的时候,要锁住 
内存总线(memory bus),另外的CPU不能访问内存,以保证decb指令的原子性。 
注意,decb并不是原子操作(atomic operation),它需要将变量从内存读出来, 
放入寄存器(register),减1,再写入内存。如果在这时候另外的CPU也进行同样的操作的 
时候,那么decb的执行结果就会不确定,也就是说,操作的原子性遭到了破坏。另外这里不必担心decb到底执行多少次(负多少),因为在unlock时 ,lock->lock 会直接被写值为1。

“jns 3f\n”

如果decb的结果小于0,表示无法取得spin lock,则执行下一条指令(rep)。
如果decb的结果大于或等于0,表示已经获得spin lock,则跳到标签为3的指令,实际就是跳出整段代码,函数返回。

“2:\t”
“rep;nop\n\t”
“cmpb $0,%0\n\t”
“jle 2b\n\t”
“jmp 1b\n”
“3:\n\t”
: “+m” (lock->slock) : : “memory”);

memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存。这种技术叫做内存屏障(memory barrier)
}

首先是做一个test_and_set (LOCK; decb lock),如果发现已经被锁住了,就
random backoff (rep; nop),然后作local test (cmpb)。

static inline void __raw_spin_unlock(raw_spinlock_t *lock)
{
asm volatile(“movb $1,%0” : “+m” (lock->slock) :: “memory”);
}

######################################################################

4. FIFO ticket spinlock (solved the fairness problem)
TTAS with random backoff还有一个公平性的问题,当锁释放时,谁能抢到锁是随机的。并不是等待
最久的那个竞争者会得到锁。这样就可能造成一个thread会busy looping很长的时间而得不到锁。

Linux kernel x86的ticket spinlock是有Nick Piggin实现的,在2.6.25中被接受。
(git commit id is: 314cdbefd1fd0a7acf3780e9628465b77ea6a836)

LWN上有一篇文章介绍了ticket spinlock的原理(http://lwn.net/Articles/267968/)。

[ticket spinlock]
一个spinlock被分成了两个部分,owner是现在拥有锁的lock owner的ticket id,Next是下一个能拿到锁的ticket id,初始化的时候Owner = Next = 0。当current lock owner释放锁的时候,会把Owner域加1,这样当拿着Next的锁竞争者发现Owner已经变成自己的ticket id的时候,就认为自己拿到了锁了。

static __always_inline void __ticket_spin_lock(raw_spinlock_t *lock)
{
short inc = 0x0100;

asm volatile (
LOCK_PREFIX “xaddw %w0, %1\n”
“1:\t”
“cmpb %h0, %b0\n\t”
“je 2f\n\t”
“rep ; nop\n\t”
“movb %1, %b0\n\t”
/* don’t need lfence here, because loads are in-order */
“jmp 1b\n”
“2:”
: “+Q” (inc), “+m” (lock->slock)
:
: “memory”, “cc”);
}

1. 初始化 -> slock: owner = next = 0
2. CPU0第一个拿到了锁 -> slock: owner = 0, next = 1
3. 当CPU1也想竞争这把锁的时候,xaddw -> slock: owner = 0, next = 2 同时
inc: owner = 0, next = 1
它发现inc: owner != next (注意它是在测试inc这个变量),就等待(rep; nop),然后把s
lock的owner读入inc。如果CPU0释放了锁,它会把slock:owner加1。这样CPU1就会发现
inc:next = 1,owner = 1,它就认为自己拿到了锁。
4. 如果在CPU0释放锁之前,CPU2也来竞争这把锁的话,CPU2: slock: owner = 0, next = 3
inc: owner = 0, next = 2。当CPU0释放锁的时候,inc:owner = 1, next = 2,它仍然会
继续循环,等待CPU1释放锁。

references:
1. For SMP cache coherence, please see chapter 4 of Computer Architecture-A
Quantitative Approach.
2. Linux kernel source code.
3. For TAS, TTAS concept refer to chapter 7 of The Art of Multiprocessor
Programming.

 

 

关于intel cpu 中的总线lock :

从 P6 处理器开始,如果指令访问的内存区域已经存在于处理器的内部缓存中,则“lock” 前缀并不将引线 LOCK 的电位拉低,而是锁住本处理器的内部缓存,然后依靠缓存一致性协议保证操作的原子性。

4.2) IA32 CPU调用有lock前缀的指令,或者如xchg这样的指令,会导致其它的CPU也触发一定的动作来同步自己的Cache。
CPU的#lock引脚链接到北桥芯片(North Bridge)的#lock引脚,当带lock前缀的执行执行时,北桥芯片会拉起#lock
电平,从而锁住总线,直到该指令执行完毕再放开。 而总线加锁会自动invalidate所有CPU对 _该指令涉及的内存_
的Cache,因此barrier就能保证所有CPU的Cache一致性。

4.3) 接着解释。
lock前缀(或cpuid、xchg等指令)使得本CPU的Cache写入了内存,该写入动作也会引起别的CPU invalidate其Cache。
IA32在每个CPU内部实现了Snoopying(BUS-Watching)技术,监视着总线上是否发生了写内存操作(由某个CPU或DMA控
制器发出的),只要发生了,就invalidate相关的Cache line。 因此,只要lock前缀导致本CPU写内存,就必将导致
所有CPU去invalidate其相关的Cache line。

JTAG and TTL

JTAG(Joint Test Action Group)是1985年制定的检测PCB和IC芯片的一个串口标准,1990年被修改后成为IEEE的一个标准,即IEEE1149.1-1990。通过这个标准,可对具有JTAG口芯片的硬件电路进行边界扫描和故障检测。

更详尽的看这里:

http://en.wikipedia.org/wiki/Joint_Test_Action_Group

http://zh.wikipedia.org/wiki/JTAG

JTAG 主要有以下用途:

1. 对嵌入式系统进行debug

2.写flash rom (刷固件)

3. 边界检测

这个其实和软件的关系有点远了,他主要是用来测试硬件物理特性的,比如PCB 或焊点缺陷,IC内部逻辑块等。因为现在IC 的逻辑越来越复杂,IC之间的连接也会很多,所以如果用留一大堆测试引脚的方法肯定不行了,边界检测其实就是简化了测试的复杂程度,将测试对象的边界画出,对这个边界的一些参数进行检测。通过jtag检测时只要通过一个jtag 口即可完成对所有边界的检测。他的核心技术就是IC通过Daisy Chain的方式连接,支持测试信号的串进串出。更多详细看这里:http://blog.21ic.com/user1/5872/archives/2009/65450.html

具有JTAG口的芯片都有如下JTAG引脚定义:

TCK——测试时钟输入;

TDI——测试数据输入,数据通过TDI输入JTAG口;

TDO——测试数据输出,数据通过TDO从JTAG口输出;

TMS——测试模式选择,TMS用来设置JTAG口处于某种特定的测试模式。

可选引脚TRST——测试复位,输入引脚,低电平有效。

含有JTAG口的芯片种类较多,如CPU、DSP、CPLD等。

另外ISP接口也符合JTAG接口标准

############################################################

TTL:

TTL(Transistor Transistor Logic)即晶体管-晶体管逻辑,TTL电平信号由TTL器件产生。TTL器件是数字集成电路的一大门类,它采用双极型工艺制造,具有高速度、低功耗和品种多等特点。

 TTL接口属于并行方式传输数据的接口,采用这种接口时,不必在液晶显示器的驱动板端和液晶面板端使用专用的接口电路,而是由驱动板主控芯片输出的TTL数据信号经电缆线直接传送到液晶面板的输人接口。由于TTL接口信号电压高、连线多、传输电缆长,因此,电路的抗干扰能力比较差,而且容易产生电磁干扰(EMI)。
对于嵌入式系统,TTL 并不直接用来连接显示设备,而是用来连接终端设备(比如连接pc并口)。既可以输出也可以输入,在PC 的终端程序中可以像操作linux终端一样操作嵌入式设备。
TTL 一共有4针:Vcc ,RX,TX,GND

TTL也可以用来刷机,但限制要比jtag 多。 举个实例,针对DDWRT :

Flash 分区如下:
0x00000000-0x00040000 : “cfe” //引导
0x00040000-0x007f0000 : “linux” //内核
0x000d7cc8-0x00600000 : “rootfs” //根文件系统
0x007f0000-0x00800000 : “nvram” //参数保存的分区
0x00600000-0x007f0000 : “ddwrt” //jeffs2的分区

这里cfe就是bootloader,所以像MAC地址这样的变量也保存在这里。上述5个部分合起来叫wholeflash。从cfg启动开始就可以用TTL 来查看全部系统的输出,在出现console 登录提示时还可以进行输入操作。

当CFE启动到“Press any key to stop auto run”时按下任意键就可以进入CFE命令行模式,通过命令行启用CFE内部的HTTP服务,再通过WEB来上传固件刷机。 但如果flash是空的,就要用jtag来写入cfe, 然后再写其余部分。值得一提的是CFE自带TFTP,一般通过长按reset 键就可以启动,而不会继续引导固件。这样就有机会通过比较简单的tftp 来刷固件了。

可见没有了CFE的支持,那么很多原始作业都要通过jtag 来做了,比如刷入CFE或wholeflash。

JTAG 在很多板上一般都有4-12针,但其实只有4针有用,其他一般都是GND。TTL 中4针也只需要接3针即可,Vcc 不用接。

关于栈和堆

很久了,之前在学校图书馆里读到的,现在工作后发现不光C/C++ 遵守栈和堆的一些约定,而且SAP ABAP也是这样 。不奇怪 ,ABAP就是C实现的 。那么现在再来温习一遍。
一、预备知识—程序的内存分配
一个由C/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其
操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回
收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的
全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另
一块区域。 – 程序结束后由系统释放。
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。

二、例子程序
这是一个前辈写的,非常详细
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = “abc”; 栈
char *p2; 栈
char *p3 = “123456”; 123456在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得来得10和20字节的区域就在堆区。
strcpy(p1, “123456”); 123456放在常量区,编译器可能会将它与p3所指向的”123456″
优化成一个地方。
}

二、堆和栈的理论知识
2.1申请方式
stack:
由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空

heap:
需要程序员自己申请,并指明大小,在c中malloc函数
如p1 = (char *)malloc(10);
在C++中用new运算符
如p2 = new char[10];
但是注意p1、p2本身是在栈中的。

2.2
申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢
出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表
中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的
首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。
另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部
分重新放入空闲链表中。

2.3申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意
思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有
的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将
提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储
的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小
受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

2.4申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是
直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。

2.5堆和栈中的存储内容
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可
执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地
址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。

2.6存取效率的比较

char s1[] = “aaaaaaaaaaaaaaa”;
char *s2 = “bbbbbbbbbbbbbbbbb”;
aaaaaaaaaaa是在运行时刻赋值的;
而bbbbbbbbbbb是在编译时就确定的;
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:
#include
void main()
{
char a = 1;
char c[] = “1234567890”;
char *p =”1234567890″;
a = c[1];
a = p[1];
return;
}
对应的汇编代码
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到
edx中,再根据edx读取字符,显然慢了。

2.7小结:
堆和栈的区别可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就
走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自
由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由
度大。

关于fork的一道题

高通的一道笔试题 :

fork()&&fork()||fork()

结果共有几个进程?

这个其实考察fork 返回值的问题。执行fork后,会生成当前进程的一个子进程,由于是内核里完成的进程的copy ,所以该函数在返回时,两个进程都会返回。 不同的是子进程在返回时返回值为0,父进程在返回时返回值为子进程的pid。

先给这3个fork 编下号 :

fork()&&fork()||fork()

1                2             3

然后执行过程就能方便表示了

                fork3
      fork1      |-------------------  0
        |--------|-------------------
--------|--------------|-------------
                       |-------|-----  0
                      fork2    |-----
                              fork3

最后共5个进程

一个空格引发的杯具

幸好我手里没有N卡 ,也没有 core iX 处理器 ,否则我要和/usr目录说再见了 ,不多说自己看吧:

https://github.com/MrMEEE/bumblebee/commit/a047be85247755cdbe0acce6#diff-1

现在项目作者已经被广大中国网民围观了

关于 BSP

BSP是板级支持包,是介于主板硬件和操作系统之间的一层,应该说是属于操作系统的一部分,主要目的是为了支持操作系统,使之能够更好的运行于硬件主板。BSP是相对于操作系统而言的,不同的操作系统对应于不同定义形式的BSP,例如VxWorks的BSP和Linux的BSP相对于某一CPU来说尽管实现的功能一样,可是写法和接口定义是完全不同的,所以写BSP一定要按照该系统BSP的定义形式来写(BSP的编程过程大多数是在某一个成型的BSP模板上进行修改)。这样才能与上层OS保持正确的接口,良好的支持上层OS。

例如:
在VxWorks中的网卡驱动,首先在config.h中包含该网卡,然后将网卡含网卡的信息的参数放入数组END_TBL_ENTRY endDevTbl [] 中,系统通过函数muxDevLoad( )调用这个数组来安装网卡驱动。

而在Linux中的网卡驱动,是在space.c中声明该网络设备,再把网卡驱动的一些函数加到dev结构中,由函数ether_setup()来完成网卡驱动的安装。

纯粹的BSP所包含的内容一般说来是和系统有关的驱动和程序,如网络驱动和系统中网络协议有关,串口驱动和系统下载调试有关等等。离开这些驱动系统就不能正常工作。

Tornado中BSP的编译和上层应用程序不同,用命令行或直接在Tornado环境下Build,在Tornado下不能跟踪调试。

用户也可以添加自己的程序到BSP中,但严格来说不应该算BSP.一般来说这种做法不建议。因为一旦操作系统能良好运行于最终的主板硬件后,BSP也就固定了,不需要做任何改动。而用户自己在BSP中的程序还会不断的升级更新,这样势必对BSP有不好的影响,对系统造成影响,同时由于BSP调试编译环境较差,也不利于程序的编译调试。

上层程序

Tools – Applications

I/O System
VxWorks Libraries
TCP/IP

Wind Kernel

BSP 
SCSI Controllerr
Serial Controller
Clock Timer
Ethernet Controller
.. ..

硬件

BSP在嵌入式系统和Windows系统中的不同

其实运行与PC机上的windows或linux系统也是有BSP的。只是PC机均采用统一的X86体系架构,这样一定操作系统(windows,linux..)的BSP相对x86架构是单一确定的,不需要做任何修改就可以很容易支持OS在x86上正常运行,所以在PC机上谈论BSP这个概念也没什么意义了。

而对嵌入式系统来说情况则完全不同,目前市场上多种结构的嵌入式CPU(RISC)并存(PPC,ARM,MIPS….),为了性能的需要,外围设备也会有不同的选择和定义。一个嵌入式操作系统针对不同的CPU,会有不同的BSP,即使同一种CPU,由于外设的一点差别(如外部扩展DRAM的大小,类型改变),BSP相应的部分也不一样。
所以根据硬件设计编写和修改BSP,保证系统正常的运行是非常重要的。

BSP和PC机主板上的BIOS区别
BSP和PC机主板上的BIOS区别很大,BIOS主要是负责在电脑开启时检测、初始化系统设备(设置栈指针,中断分配,内存初始化..)、装入操作系统并调度操作系统向硬件发出的指令,它的Firmware代码是在芯片生产过程中固化的,一般来说用户是无法修改。其实是为下载运行操作系统做准备,把操作系统由硬盘加载到内存,并传递一些硬件接口设置给系统。在OS正常运行后,BIOS的作用基本上也就完成了,这就是为什么更改BIOS一定要从新关机开机。

PC机BIOS的作用更象嵌入式系统中的Bootloader(最底层的引导软件,初始化主板的基本设置,为接收外部程序做硬件上的准备)。与Bootloader不同的是BIOS在装载OS系统的同时,还传递一些参数设置(中断端口定义,…),而Bootloader只是简单的装载系统。

BSP是和操作系统绑在一起运行在主板上的,尽管BSP的开始部分和BIOS所做的工作类似,可是大部分和BIOS不同,作用也完全不同。此外BSP还包含和系统有关的基本驱动(串口,网口…),此外程序员还可以编程修改BSP,在BSP中任意添加一些和系统无关的驱动或程序,甚至可以把上层开发的统统放到BSP中。

而BIOS程序是用户不能更改,编译编程的,只能对参数进行修改设置。更不会包含一些基本的硬件驱动。

BSP在嵌入式开发中的位置和作用

BSP开发处于整个嵌入式开发的前期,是后面系统上应用程序能够正常运行的保证。

大概步骤如下:

1.硬件主板研制,测试。

2.操作系统的选定,BSP编程。

3.上层应用程序的开发。

BSP部分在硬件和操作系统,上层应用程序之间。所以这就要求BSP程序员对硬件,软件和操作系统都要有一定的了解。这样才能做好BSP编程。

熟悉工具方面:电表,示波器,逻辑分析仪。硬件仿真器,仿真调试环境。

SPI 接口

今天看BCM5352的datasheet 突然想到它如何于RFIC BCM2050 通讯的, 官方没有说,估计是SPI:

SPI(Serial Peripheral Interface–串行外设接口)总线系统是一种同步串行外设接口,它可以使MCU与各种外围设备以串行方式进行通信以交换信息。外围设置FLASHRAM、网络控制器、LCD显示驱动器、A/D转换器和MCU等。SPI总线系统可直接与各个厂家生产的多种标准外围器件直接接口,该接口一般使用4条线:串行时钟线(SCK)、主机输入/从机输出数据线MISO、主机输出/从机输入数据线MOSI和低电平有效的从机选择线SS(有的SPI接口芯片带有中断信号线INT或INT、有的SPI接口芯片没有主机输出/从机输入数据线MOSI)。
SPI接口的全称是”Serial Peripheral Interface”,意为串行外围接口,是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。

 

SPI接口是在CPU和外围低速器件之间进行同步串行数据传输,在主器件的移位脉冲下,数据按位传输,高位在前,地位在后,为全双工通信,数据传输速度总体来说比I2C总线要快,速度可达到几Mbps。

SPI接口是以主从方式工作的,这种模式通常有一个主器件和一个或多个从器件,其接口包括以下四种信号:

(1)MOSI – 主器件数据输出,从器件数据输入

(2)MISO – 主器件数据输入,从器件数据输出

(3)SCLK – 时钟信号,由主器件产生

(4)/SS – 从器件使能信号,由主器件控制

在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。
多个从器件硬件连接示意图

在多个从器件的系统中,每个从器件需要独立的使能信号,硬件上比I2C系统要稍微复杂一些。

SPI接口在内部硬件实际上是两个简单的移位寄存器,传输的数据为8位,在主器件产生的从器件使能信号和移位脉冲下,按位传输,高位在前,低位在后。
最后,SPI接口的一个缺点:没有指定的流控制,没有应答机制确认是否接收到数据。

SDIO:由6线SD卡接口控制,包括:CMD,CLK,DAT0-DAT3。

关于ipv6的几个站点

ipv6接入测试 :

http://test-ipv6.com/

ipv6 dns :
未知国家
2001:470:20::2

74.82.42.42

下面两个会返回据大陆近的ip

nameserver 216.218.221.6 ; tserv1.hkg1.ipv6.he.net. tserv20.hkg1.ipv6.he.net.
nameserver 74.82.46.6 ; tserv22.tyo1.ipv6.he.net.

更多dns 看这里:http://www.chaz6.com/files/resolv.conf
由于镰*刀斧*头 裆马上就要过生日了,所以ipv4 的dns肯定污染得越来越厉害了,所以还是用ipv6的dns吧。

btw:好像GFW还不完善有时后用216.218.221.6 可以返回ipv6 ,不过大部分时候是不返回的。呵呵

由于现在所有能够解析ipv6的dns都没有google 的权威应答,所以改hosts还是需要的,这里贴一下获得最新hosts的列表

https://docs.google.com/View?id=dfkdmxnt_61d9ck9ffq 或 http://code.google.com/p/ipv6-hosts/source/browse/hosts

Linux Graphics Driver Stack

很早之前我贴过 关于现代 X window 渲染流程图,现在再贴一张 显卡驱动的架构图 ,当然是非常大概的那种 。

xyz_drv.so 在 /usr/lib/xorg/modules/drivers/ 很明显他是唯一和X window有联系的库。

xyz_dri.so 在 /usr/lib/dri  看起来实际是Mesa/DRI 的一个接口库

libdrmxyz 在 /usr/lib/ 用以实现libdrm 的函数库

最后的xyz.ko 都是在/lib/modules 对应于内核版本的内核模块目录中

 

 

 

 

 

 

 

dd 速度慢?

前几天要用dd全零化整个磁盘,发现直接dd 非常慢。其实加一个参数就ok:

dd if=/dev/zero of=/dev/sda bs=2MB oflag=nonblock

速度能提高10倍多

nonblock: use non-blocking I/O  。非阻塞I/O 哦 ,dd不会等当前i/o块读写完成就返回(异步i/o),进行下一次i/o。

 

注意只有在dd 设备文件时才起作用,如果已经挂载文件系统到/mnt那么无论加不加flag 都一样很快。估计是挂载以后有文件系统的支持,将默认为异步i/o