2008年12月29日星期一

Linux下如何监控程序具体使用内存情况

作者: Sam(甄峰) sam_code@hotmail.com

公司在Canmore下作游戏时,因为Canmore可用Memory不大,所以必须常常查看游戏具体使用多少内存。

Sam发现有以下几种方式可以查看当前程序内存使用量:

1. #ps
然后查看VSZ 项目。但这一项只是任务虚拟地址空间大小。并不代表真的使用如此多内存。
ps并没得出进程真实用了多少内存。它计算的是假设系统上只存在这唯一运行的进程时,此进程所占用的内存。事实是,几乎每时每刻系统上有一大堆进程在运行,也就是说ps报告的数据毫无疑问是“错”的。
Linux上大多数程序使用了共享库。例如KEdit使用了好几个KDE的,好几个Xlib的,还有好几个系统库。这些库,其中最常用的恐怕是libc库,同时被Linux系统中要多程序使用着。经过这种共享,Linux可以耍这么一个花招:可以把共享库拷入内存中然后到处引用给每一个程序。
很多工具程序没有考虑这一点。它们只是简单计算一下,而不考虑共享库起的作用。两个程序的共享库部分因此可能被统计了两次内存占用,这样多计算了一次可能导致很多误会。
实际上真实的内存占用是很难获得的。你不仅仅要知道系统如何工作,而且你必须在一些很难判断的问题上作出决定。如,被进程唯一使用的共享库是否应该被统计在内?被多个进程使用的共享库是否应该被统计多次,或者忽略不计?这里并没有一个严格的规则。在不同的情况下,你应该需要不同的选择。可想而知为什么ps在解决这个问题的时候,给出的答案比较含糊。


2. #top -d 1
然后查看VSZ 项目,也只是任务虚拟地址空间大小。并不代表真的使用如此多内存。
所以Sam常常使用如下方法:
在运行前后将比较used以及free和cached的用量。则可推断出实际使用量。
Mem: 33400K used, 160300K free, 0K shrd, 0K buff, 14056K cached
VSZ也会遇到与#ps出来的VSZ同样的问题。

注意:#top 时,会有一个cached值用来表示交换区域,这个区域大小总变化,很不利于我们通过两次的差值看大小。可以这样处理。
#echo 3 > /proc/sys/vm/drop_caches
#sync
然后 cached的值就会被固定。注意,echo 后面的值可以为:1,2,3


3. #free
然后查看2次运行的差值,这表明此程序内存使用量。



4. cat /proc/uid/statm
999 184 146 7 0 577 0
分别表示:
Size (total pages) 任务虚拟地址空间的大小 VmSize/4
Resident(pages) 应用程序正在使用的物理内存的大小 VmRSS/4
Shared(pages) 共享页数 0
Trs(pages) 程序所拥有的可执行虚拟内存的大小 VmExe/4




具体到一个程序中:内存使用量如何呢:
Sam试验表明:
如果某个程序什么都不作。
只是while(1)
则使用cat /proc/uid/statm。发现也会使用256K程序。这可能是分配给它的栈以及glibc库所占位置。

如果Sam这个程序代码什么都不作,但编译时添加 -lBTX, -lbluetooth, -lpthread.
则 #cat /proc/uid/statm发现多用100K物理内存。这表明Linux在计算内存使用量时,会把动态库所占内存包含进去。

2008年12月25日星期四

Intel CE3100内核启动文件研究

作者: Sam(甄峰) sam_code@hotmail.com


Intel CE3100中使用/etc下一些config来配置系统。用法很奇特。Sam在这学习之。

1. Canmore启动时,会到/etc/rc3.d里面去依次运行以S开头的config文件。运行顺序以S后面的数字为准。比如:S10Audio, S12Dispaly。则S10Audio先运行,S12Display后运行。这个应该是一个Script作的。但Sam暂时没找到这个Script。
(待解决)

2. Canmore使用/etc/platform_config中的config文件来配置Memory使用情况。这是如何做到的呢?
使用方法:
# vi /etc/platform_config/platform_config.hcfg
修改:
"media_base_address" from 0x10000000 to 0x0C300000
则可用Memory变大。
(待解决)

2008年12月22日星期一

ARM处理器模式介绍和模式切换

作者: Sam(甄峰) sam_code@hotmail.com


ARM体系结构支持7种处理器模式:
用户模式:正常程序工作模式,不能直接切换到其它模式。
快中断模式:支持高速数据传输及通道处理。FIR异常响应时,进入此模式
中断模式:通用中断处理。IRQ异常响应时,进入此模式。
管理模式:操作系统保护代码,系统复位和软件中断时,进入此模式。
中止模式:支持虚拟内存,存储器保护。
未定义模式:支持硬件协处理器和软件仿真。未定义指令异常响应时,进入此模式。
系统模式:用于支持OS的特权任务。与用户模式类似,但可以直接切换到其它模式。
用户模式外,其它都为特权模式。某些ARM内部寄存器和一些片内外设在硬件设计上只允许特权模式下访问。特权模式可以自由切换处理器模式。但用户模式不能直接切换到别的模式。


ARM7TDMI内核包含1个CPSR和5个供异常处理程序使用的SPSR.
ARM7TDMI内核所有处理器状态都保存在CPSR中。
CPSR包含:
4个条件代码标志。
2个中断禁止位。
5个队当前处理器模式进行编码的位。
1个用于只是当前执行指令(ARM还是Thumb)的位。

每个异常模式(快中断模式,中断模式,管理模式,中止模式,未定义模式)还带有一个程序状态保存寄存器 SPSR。用于保存任务在异常发生之前的CPSR.

4个条件代码标志:
N:运算结果的B31值。
Z:指令结果为0时,Z=1(通常表示比较结果相等) . 否则Z=0.
C: 使用加法运算时,B31位产生进位,C=1.否则C=0. 使用减法运算时,b31借位时,C=0,否则C=1。
V: 加减运算时,有符号溢出,V=1,否则V=0。

2个中断禁止位:
I: 置位,IRQ 中断被禁止。
F:置位,FIQ中断被禁止。

1个用于只是当前执行指令(ARM还是Thumb)的位:
置位:表示处理器在Thumb.
清零:处理器在ARM。

5个队当前处理器模式进行编码的位:
10000:用户。
10001:快中断。
10010:中断
10011:管理
10111:中止
11011:未定义
11111:系统。
注意,在不同模式下,可见的寄存器不同。


使用MRS指令可以读取状态寄存器指令。
MRS R1, CPSR
MRS R2,SPSR

使用MSR指令,可以写状态寄存器。

例1:使能IRQ中断:
MRS R0, CPSR //把cpsr内容放到Ro中
BIC R0, R0, #0x80 //将I 位清除。IRQ Enable
MSR CPSR_c ,R0
MOV PC, LR

注意:AXD中的Processor Registers项中,CPSR,SPSR显示模式如下:

nzCvqIFt_SVC
nzcv:分别表示4个标志位。小写字母表示0 , 大写字母表示1
IFt分别表示IRQ,FIQ,和执行指令标志位。
SVC表示工作模式为:管理模式。










2008年12月15日星期一

BTX中使用pthread_create造成内存泄露问题

作者: Sam(甄峰) sam_code@hotmail.com

Sam在创建BTX时,针对每个连接:
创建一个 poll thread. 每隔1s poll一下,看是否断掉了。当发现断掉时(PollHup),则当用户程序调用Read程序时,将HID,L2cap(0x11), L2cap(0x13)这3个连接close.
创建一个 read thread. 不断的去读手柄数据。

但Sam发现当手柄断开,然后再次连接时。内存使用会增大。开始怀疑是内存泄露,但当Sam将所有thread服务程序内容置为空时,还是会以每个thread 8M为单位增加内存使用量,并且thread 退出时不释放。所以Sam开始怀疑是 pthread系列function需要做什么设置,才可以在thread退出时释放内存。


使用pthread_create创建thread. thread与同进程中的其他thread共享数据,但拥有自己的栈空间,拥有独立的执行序列.所以当使用pthread_create()创建thread时,会使用大约8M内存,就是给这个thread给了8M的栈,但具体OS缺省thread有多大的栈呢?可以使用:
#ulimit -a
stack(kb) 8192 这项指出会有8M的栈空间。

但为什么thread 退出时没有释放这块栈空间呢?
Sam注意到pthread_create()中参数attr.
pthread_create()中的attr参数是一个结构指针,结构中的元素分别对应着新线程的运行属性,其中包括:
__detachstate,表示新线程是否与进程中其他线程脱离同步,如果置位则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到PTHREAD_CREATE_JOINABLE状态。

所以Sam决定使用pthread_detach()来设置此项。资源控制所有权交给子线程,让thread退出时自动释放stack.


另外:pthread_create()传递指针时,这个指针指向的值不要使用局部变量,否则很容易在thread还没得到参数值时,调用程序已经退出,那个局部变量被释放。所以要用static或者全局。



背景知识:
一. 堆和栈的区别:
一、预备知识—程序的内存分配
一个由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\0在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20); 分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}


二、堆和栈的理论知识
2.1申请方式
stack: 由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间
heap: 需要程序员自己申请,并指明大小,
在c中malloc函数
如p1 = (char *)malloc(10);
在C++中用new运算符
如p2 = (char *)malloc(10);
但是注意p1、p2本身是在栈中的。


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

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

2.4申请效率的比较
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便


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


更基础的堆栈知识:
C 语言有全局变量(Global)、本地变量(Local),静态变量(Static)、寄存器变量(Regeister)。每种变量都有不同的分配方式。
本地变量和全局/静态变量是分配在不 同类型的内存区域中。全局变量和静态变量分配在静态数据区,本地变量分配在动态数 据区。

对于一个进程的内存空间而言,可以在逻辑上分成3个部份:代码区,静态数据区和动态数据区
动态数据区一般就是“堆栈”。“栈 (stack)”和“堆(heap)”是两种不同的动态数据区,栈是一种线性结构,堆是一种链式结构。进程的每个线程都有私有的“栈”,所以每个线程虽然 代码一样,但本地变量的数据都是互不干扰。

2008年12月6日星期六

ARM ASM助记篇

作者: Sam(甄峰) sam_code@htomail.com

这篇文档作为Sam看ARM ASM时的参考手册。呵呵。有了这篇手册,看ARM ASM就会容易很多。有了这篇文档就能看懂ASM吗?一定可以,不信去问葛优。他一定说:(神州行)我看行!

1. 关键字:
1.1 AREA Example1, CODE, READONLY
AREA: 至少在ADS上编译ASM时,必须要有AREA关键字(其实也是个指令助记符)。它表明这个区域是Example1。是一个CODE区域。是一个READONLY区域。


1.2 ENTRY:
标识代码入口。没有这一项也没有任何问题。


1.3 CODE32
表明是32位ARM指令。



2. 助记指令:
2.1 MOV/MVN
:数据传送指令,将8位图操作数2传送到目标寄存器(操作数1)/将操作数2取反之后放到操作数1中。

MOV R1, #0x10 ;将直接数0x10放到R1寄存器去

MOV R0, R1 ; 将R1内容copy到R0去

MOV PC, LR ;PC=LR, 子程序返回

MOV R3,R1, LSL#2 R3=R1<<2>MVN R1,#0xFF ; R1=0xFFFFFF00





2.2 EQU: 赋值,用来定义变量

COUNT EQU 0x40003100 ; EOUNT赋值为0x40003100





2.3 LDR/STR: 加载存储指令

LDR:从内存中读取数据(单一字节或字)放入寄存器。

STR: 将单一字节或字存储到内存。



LDR R0,[R1,#0x12] ; 将R1+0x12处的数据读出,放到R0中去。

LDR R0, [R1,-R2] ;将R1-R2内存处的数据读出,放到R0 中去。

STR R0,[R1,-0x4] ;将R0内容放到内存R1-0x4处







2.4 CMP:比较指令

CMP R1,#10

CMP 将第一个参数减去第二个参数。针具结果更新CPSR中相应条件标志位。以便后面的指令根据相应的条件标志来判断是否执行。(寄存器CPSR的内容可以用AXD看到)如果R1-#10 <>


2008年12月5日星期五

Bluetooth Sniff 详解

作者:Sam(甄峰) sam_code@hotmail.com

前面讲了Blueooth Remote Control发送Sniff request,要求每5ms发送一个数据包。这篇详细介绍一下Bluetooth Sniff.

在Sniff mode, Slave活动的占空比可能会降低。如果一个ACL逻辑传输的Slave 处于Active Mode,它将在在每个Master 发送ACL Slot中监听。除非link处理为分散连接或者在Hold Mode。
在Sniff Mode,Slave监听的次数会减少。于是, Master 只在指定的Slot中传送数据给Slave.
Sniff 定位点间隔由T(Sniff)指定。

也就是说:Sniff Mode. Slave只在固定间隔中的某个Master 传输Slot监听。

Sam在读到这里,不禁想到一个问题。Bluetooth Remote Control在于Linux 连接时,是作为Slave连接的。Sniff Mode下,只是可以约定Slave(手柄)每5ms去listen data. 但我们的手柄却每5ms去发送数据给主机。这好像讲不通。
Ray回答说:因为Per Bluetooth spec, slave can not send data to master unless master poll slave。就是说:在Bluetooth协议中,Slave不能主动发送数据给Master除非Master向它发送Poll。
So, the slave have to listen to Master first, when it receive the poll or other packet from master, then it can send thedata to master。于是,双方约定Sniff Time后,Master 每5ms发送一个Poll。则Slave就可以发送数据给主机了。这就做到了手柄每5ms发送一个数据包给主机。



2008年12月4日星期四

使用BlueZ Interface修改ACL连接Supervision timeout

作者: Sam (甄峰) sam_code@hotmail.com

背景:
Sam在做Bluetooth USB Dongle与Bluetooth Remote Control连接的程序时。注意到当手柄断电时,需要20s时间才能够接收到Disconnect Event.(Error Code: timeout). Ray觉得这个时间太久。要求修改到5-6s.

实际上,因为Bluetooth是无线连接设备,所以有时会连接不上,Supervision的作用就是,ACL连接双方约定多长时间没有联系上算断开。

在读取文档:Host Controller Interface Functional Specification文档中HCI Commands and Events-〉Controler & BaseBand Command->Read Link Supervision Timeout+Write Link Supervision Timeout中。发现可以修改这个timeout.

1. 读取Supervision Timeout.
Command: HCI_Read_Link_Supervision_Timeout
OCF:0x0036
Command Parameters:Connection_Handle
Return Parameters: Status, Connection_Handle, Link_Supervision_Timeout
这个Command用来读取Link supervision timeout . 这是个基于ACL Connect的Command.

Command Param: Connect_Handle

Return Parameters:
Status: 0x00 : Success。
other: ErrorCode
Connection_Handle:ACL Connect Handle
Link_Supervision_Timeout:
0x00: No Link_Supervision_Timeout
N : BaseBand slots.一个BaseBand Slot等于0.625个ms.


2. 设置Supervision Timeout.
Command: HCI_Write_Link_Supervision_Timeout
OCF:0x0037
Command Parameters: Connection_Handle, Link_Supervision_Timeout
Return Parameters: Status, Connection_Handle
这个命令用来设置Supervision Timeout.

2008年12月3日星期三

通过Bluez提供的Interface发送HCI Command

作者: Sam(甄峰) sam_code@hotmail.com


背景:
Sam在做Bluetooth USB Dongle与Bluetooth Remote Control连接的程序时。因为Bluetooth Remote Control会在连接后发送 sniff request.约定双方的数据频率为5ms. 但因为Linux程序这端没有处理Sniff(缺省为disable). 所以这个request没有成功。造成双方的数据传输并不是定时传输,而是当channel忙时发数据少,channel不忙时发数据多。于是空中鼠标非常不稳定。Ray要求添加1.Enable Sniff. 2. set Sniff mode.

Sam于是创建了三个接口:
1. Read_LINK_POLICY() //read Link policy. sniff包含在其中
2. Wiret_LINK_POLICY() //write Link policy.
3. Set_Sniff_Mode() //设置sniff

如前面Blog中所说:可以使用BlueZ提供的Interface发送HCI Command。
int hci_send_req(int dd, struct hci_request *r, int to)



1. Command知识学习:
Sam首先查看:Host Controller Interface Functional Specification文档中HCI Commands and Events.->Link Policy Commands->Read Link Policy Settings Command.
Command: HCI_Read_Link_Policy_Settings
OCF: 0x000C
Command Parameters: Connection_Handle
Return Parameters: Status, Connection_Handle Link_Policy_Settings
说明:这个Command 从指定的Connection Handle读取Link Policy 设置。这个Connection Handle必须是ACL连接的。
Command Parameters:
1. Connection_Handle(Size:2 Octets):一个连接的句柄。
Retrun Parameters: 返回参数
返回参数1. Status: 0x00: 读取成功
0x01-0xFF: 可以从Error Code表中找到错误含义。
返回参数2. Connection_Handle: 一个连接的句柄。
返回参数3. Link_Policy_Settings:
0x0000: Disable All LM Modes Default.
0x0001: Enable Role Switch
0x0002: Enable Hold Mode.
0x0004: Enable Sniff Mode.
0x0008: Enable Park State.
Event Generate:
当 Read_link_Policy_Settings命令执行完毕时,会发送一个 Command Complete Event.

2.具体编程
Sam发现BlueZ提供的用户层Interface中(也就是BlueZ-lib中)并没有直接提供发送命令--Read Link Policy Settings 的接口。但提供了发送Read Remote Version Information的接口。这就很简单了,直接仿照这个接口写一个function即可。
BlueZ-lib中的Read Remote Version Information function:
int hci_read_remote_version(int dd, uint16_t handle, struct hci_version *ver, int to)
这个function的用法可以参考BlueZ-uitil。
参数描述:
int dd: 由hci_open_dev()返回的HCI Socket 句柄。
uint16_t handle: 一个连接的句柄。
struct hci_version *ver: 返回的版本结构体。
int to: 等待的时间。

这个function 的核心就是前文介绍的:hci_send_req(dd, &rq, to)
只需要读懂它的参数配置,我们的仿造function就算成功了。
其中,dd就是hci_open_dev() 返回的HCI Socket Handle。 to 就是等待时间。
重点是hci_request * rq
这个结构体如下:
struct hci_request
{
uint16_t ogf; //Command组编号
uint16_t ocf; // Command编号 。由这两个ID号组合,唯一确定Command.
int event; // Command 成功后将自动发送这个Event。
void *cparam; //Command Param.命令参数。
int clen; //命令参数长度
void *rparam; //response Param。返回参数
int rlen; //返回参数长度
};
其中
ogf, ocf很明显,填上OGF_LINK_POLICY,OCF_READ_LINK_POLICY就可以了。
event:因为文档中讲此Command成功的话,就会发送Command Complete Event. 所以不用指定了。
clen: 文档中讲了:2个字节。 BlueZ中hci.h中也指定了:
#define READ_LINK_POLICY_CP_SIZE 2

rparam: response 参数:文档中有讲,包括三项:BlueZ 中hci.h也定义了:
typedef struct
{
uint8_t status;
uint16_t handle;
uint16_t policy;
} __attribute__ ((packed)) read_link_policy_rp;

rlen: response 参数长度:#define READ_LINK_POLICY_RP_SIZE 5
cparam:Command 参数:如文档中所讲,结构体如下:
typedef struct
{
uint16_t handle;
} __attribute__ ((packed)) read_link_policy_cp;

综上所述,可以如下设置:

BTX_Rel hci_Read_Link_Policy(int dd, uint16_t handle, uint16_t *policy, int to)
{
struct hci_request HCI_Request;
read_link_policy_cp Command_Param;
read_link_policy_rp Response_Param;

memset(&HCI_Request, 0, sizeof(HCI_Request));
memset(&Command_Param, 0 , sizeof(Command_Param));
memset(&Response_Param, 0 , sizeof(Response_Param));
// 1.
set Command Param Command_Param.handle = handle;
// 2.
set hci_request HCI_Request.ogf = OGF_LINK_POLICY; //Command组ID
HCI_Request.ocf = OCF_READ_LINK_POLICY; //Command ID
HCI_Request.cparam = &Command_Param;
HCI_Request.clen = READ_LINK_POLICY_CP_SIZE;
HCI_Request.rparam = &Response_Param;
HCI_Request.rlen = READ_LINK_POLICY_RP_SIZE;
if (hci_send_req(dd, &HCI_Request, to)
{
perror("\nhci_send_req()");
return BTX_SEND_CMD_TO_REMOTE_ERR;
}
if (Response_Param.status)
{ return BTX_SEND_CMD_TO_REMOTE_ERR; }

*policy = Response_Param.policy;
return BTX_SUCCESS;
}


3. 用法:
1. hci socket的得到:
dd = hci_open_dev(dev_id);

if (dd < cr =" malloc(sizeof(*cr)">bdaddr, &des_bdaddr);
cr->type = ACL_LINK;
}
if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < connect_handle =" htobs(cr-">conn_info->handle);

3.调用
Rel_get_policy = hci_Read_Link_Policy(dd, Connect_handle, &polidy, 1000);
if(Rel_get_policy != BTX_SUCCESS)
{
printf("\nRead Link Policy Error[%d]", Rel_get_policy);
}
else
{
printf("\nPolicy is:[0x%x]", polidy);
}

4.关闭HCI Socket(这一步一定要做)
hci_close_dev(dd);

2008年11月30日星期日

BTX初建

作者: Sam(甄峰) sam_code@hotmail.com

1. 因为BTX是Sam设计的整个架构的基础,需要非常稳定。所以Sam决定每个接口都经过大量测试。
1.1. 单独的接口测试,包括[in]参数的边界测试,超出范围测试,[out]参数的参数为空,buffer不足够测试等。接口多次调用测试。
1.2. 多个接口组合测试,各个接口的各种常规和非常规组合测试。有意使用错误组合和参数测试。


2. BTX的具体实现:
2.1 BTX_Rel BTX_Init(uint16_t dev_id)
这个function 将 USB Bluetooth Dongle up起来。类似#hciconfig hci0 up
需要测试:
2.1.1. Bluetooth driver, protocol没有insmod时的反应。
2.1.2. Bluetooth USB Dongle没有插入时的反应。
2.1.3. 参数dev_id=0 (第一个USB Dongle) 和 dev_id = 1(第二个USB Dongle)时的反应。
2.1.4. 各种状况下 /proc/uid/fd/下察看终端。看是否有该释放的handle而没有释放的情况发生。注[1]。
常见的问题:打开hci socket后,遇到error,没有关闭socket就推出function.这样就会遗留一个socket.

2.2 BTX_Rel BTX_GetLocalDeviceInfo(uint16_t dev_id, bdaddr_t* pBdaddr, char* pBdName)
这个function的到本地Bluetooth 信息,包括 bdaddr, device name等。如果返回值不为BTX_SUCCESS。则说明Bluetooth USB Dongle没有Ready.
需要测试:
2.2.1:参数dev_id=0和dev_id=1时。
2.2.2:参数pBdaddr,pBdName没有分配实际的内存时,会出现Segmentation fault
Sam虽然在function 实现中添加了if(pBdaddr == NULL pBdName == NULL)...
但如果使用时不讲pBdaddr初始化为NULL。则它还是有一个值的。还是会造成Segmentation fault.这个问题等待未来解决。
2.2.3: 在调用此函数之前没有调用 BTX_Init():会返回BTX_DONGLE_NO_READY。但用perror()输出protocol未insmod.

2.3 BTX_Rel BTX_Scan_Device(uint16_t dev_id, unsigned int* pNum_rsp, bdaddr_t** ppBdaddr, uint16_t time);




注[1]:
在所有涉及到打开句柄或者socket的程序中,都应该察看 /proc/uid/fd中的信息。察看是否正常建立和销毁了句柄。

2008年11月29日星期六

ADS1.2下使用ARMulator软件调试以及ASM学习

作者: Sam (甄峰) sam_code@hotmail.com

使用ADS1.2 IDE--Codewarrior创建一个ARM Executable Image工程。
并在工程中建立一个ASM文件如下:
汇编程序1:
AREA Example1, CODE, READONLY ;声明代码段Example1
ENTRY ; 表明程序入口
CODE32 ;声明32位ARM指令
START MOV R0, #15 ;R0寄存器赋值为15
MOV R1, #8 ;R1寄存器为8
ADDS R0,R0,R1 ;R0=R0+R1
B START ;跳到START去
END
这个ASM程序非常简单。但我们可以从这个最简单的例子学会ARM Linker设置以及ARMulator软件仿真。

1.ARM Linker设置。
DebugRel Setting->ARM Linker页面中。选中Output分页。
Linktype的意思是:以何种方式Link。有3个选项:
Partia:
Simple: 使用简单的指定
Scattered:使用分散加载文件指定Image放置地点,栈,堆位置,程序从何处开始执行等

咱们首先讲Simple:
Simple Image中,
RO Base:表示程序放置的位置(或者是Read Only?)
RW Base:表示
Options: Image Entry Point:表示程序开始执行的位置。注意,这个位置必须在RO范围内,并且4字节对奇的头位置。
例1:
RO Base:0x40000000
RW Base:0x40003000
Image Entry Point: 0x40000000
则表示,程序放在0x40000000,且从0x40000000处开始执行。
使用F5进入AXD。则可以看到PC寄存器指向0x40000000(START处).可以一步步执行下去。
并看到Memory Start address: 0x40000000处有代码。
例2:
RO Base:0x40000000
RW Base:0x40003000
Image Entry Point: 0x40000004
则表示,程序放在0x40000000,且从0x40000004处开始执行。
进入AXD,则PC寄存器指向0x40000004(MOV R1, #8)处。
并看到Memory Start address: 0x40000000处有代码。


汇编程序2:
目的1:掌握LDR/STR读取和存储数据到Memory上。
目的2:继续深入掌握AXD.
COUNT EQU 0x40003100 ;定义一个变量,地址为0x40003100
AREA Example2,CODE,READONLY
ENTRY ;程序入口
CODE32 ;表明32位ARM指令
START LDR R1, =COUNT ;LDR从COUNT处读取数据到R1
MOV R0, #0; R0设为0
STR R0,[R1] ;R0的内容放置到R1所带表的地址中去

LOOP LDR R1, =COUNT ;R1 读取COUNT中内容
LDR R0,[R1] ;R0 读取R1所表示的内存区内容
ADD R0,R0,#1 R0=R0+1
CMP R0,#10 ;比较R0 和10
MOVHS R0,#0 如果R0>10 则执行这指令:将R0设为0
STR R0,[R1] 将R0内容写入R1代表的内存中去。
B LOOP
END

可以在AXD下面看Memory变化。R0,R1的Register变化等。

2008年11月27日星期四

Linux 下Bluetooth开发包--BTX的开发的背景介绍

作者:Sam(甄峰) sam_code@hotmail.com

BTX背景介绍:
Sam所在公司需要Linux下与Bluetooth Game Control连接,Linux为操作系统的嵌入式设备使用USB Bluetooth Dongle与Bluetooth Game Control连接,并收发数据。Sam评估后决定用以下结构:

1. HID,L2cap, hci 这三层protocol使用Linux下官方Bluetooth 协议栈--BlueZ. Bluez详细介绍见:[注1]

2. Bluetooth USB Dongle的驱动使用BlueZ提供的hci_usb 驱动。

3. 使用BlueZ提供的函数库和应用程序接口,创建一个针对手柄的连接库。即为BTX。这个手柄连接库提供的接口如下:
1>.Bluetooth USB Dongle初始化
2>.得到Bluetooth USB Dongle信息。
3>.查找周边Bluetooth Device设备。
4>.连接指定手柄。
5>.断开指定手柄。
6>.读取数据。
7>.写数据。
其中连接手柄是建立2个l2cap连接。PSM=0x11. 0x13.
这一层是提供给公司与所有手柄连接的库。任何一代手柄都将使用这个库作基本连接。

4. 使用BTX库和接口,开发一套针对某一代手柄所使用的库。BTRC--Bluetooth Remote Control.
因为每一代手柄连接过程中都有自己独特的加密与验证过程。并且数据格式也各不相同。所以使用这一层将这些不同与上层程序隔绝开来。

5.使用BTRC库,开发一套包含不同动作分析 的库供更上层程序使用。



注[1]:
BlueZ介绍:
Linux OS开放的bluetooth 协议栈主要包括IBM公司的BlueDrekar, Nokia公司的Affix, Axis公司的OpenBT和官方的协议栈BlueZ.
Bluetooth协议栈BlueZ分为两部分:内核代码和用户态程序及工具集。
其中内核代码由BlueZ核心协议驱动程序组成;Bluetooth协议实现在内核源代码 /net/bluetooth中。包括hci,l2cap,hid,rfcomm,sco,SDP,BNEP等协议的实现。驱动程序放在 /driver/bluetooth中,包含Linux kernel对各种接口的Bluetooth device的驱动。例如:USB接口,串口等。
用户态程序及工具集包括应用程序接口和BlueZ工具集。BlueZ提供函数库以及应用程序接口,便于程序员开发bluetooth应用程序。BlueZ utils是主要工具集,实现对bluetooth设备的初始化和控制。

ADS1.2上的软件仿真ARMulator

转载:

ARMulator 简介
ARMulator 是一个在 ARM 公司推出的集成开发环境 ADS (ARM Developer Suite)中提供的指令集模拟器。它与运行在通用计算机(通常是x86体系结构)上的调试器相连接,模拟 ARM 微处理器体系结构和指令集,提供了开发和调试 ARM 程序的软件仿真环境。ARMulator 不仅可以仿真 ARM 处理器的体系结构和指令集,还可以仿真存储器和处理器外围设备,例如中断控制器和定时器等,这样就模拟了一个进行嵌入式开发的最小子系统,另外使用者还可以扩展添加自己的外设。ARMulator 同时支持全部的标准C库函数,这样所有的C程序都可以在仿真器上运行。ARMulator 通过 Semihosting 方式来实现程序中输入/输出功能。Semihosting是指在 ARM 程序开发调试的过程中,目标系统还不具备输入/输出的功能,通过调试代理向调试器请求 I/O。ARMulator 就是这里所说的调试代理之一,其他还有 Angel、Multi-ICE也通过类似的方式来实现 I/O 功能。

ARMulator 在 Windows 平台上实现为动态链接库文件(.dll),在 Linux 或者 Solaris 系统上实现为共享库文件(.so)。前者的使用一般是通过 ARM 公司提供的图形界面调试器 AXD 中,选择目标系统为 ARMulate.dll,这样就设置了 ARMulator 作为调试代理。用户在进行调试开发时,对于底层使用的调试代理是透明的。


ARMulator 组成
ARMulator 本身是由一系列的模块(module)组成的,其中最主要的模块包括: ARM 处理器核心模块,用来仿真处理器的体系结构。 处理器使用的存储器模块,用来仿真存储系统。缺省的内存模型是 flatmem ,内存的大小不限制,理论上 4G 的地址空间都可以使用。同时,ARMulator 也包含了几种外设模块,用来仿真如下的外设模型: Interrupt controller(中断控制器)用于中断控制,包含状态寄存器、使能寄存器、状态位清除寄存器等。寄存器基址:Base=0x0a000000 Timer(定时器)ARMulator 提供了两个定时器。包含控制寄存器、计数值寄存器、中断清除寄存器等。定时器支持 free-running 和 periodic 两种工作方式,前者计数器从0xFFFF递减,到“0”后计数器溢出发生中断。后者计数器从计数值寄存器中读取计数值,然后递减至“0”后溢出发生中断。寄存器基址:Base=0x0a800000 Watchdog(看门狗)看门狗是为了防止用户程序出错导致系统死锁而采用的一种保护手段。一旦到了预定时间没有设置看门狗,看门狗就会停止ARMulator的运行,返回调试器中。看门狗使用2个定时器工作,第一个定时器在复位时启动,或者通过程序写入KeyValue 寄存器预先设定好的值来启动,当过了一定时钟周期(WatchPeriod)后,第一个定时器产生 IRQ 中断,并启动第二个定时器;如果在规定时间(IRQPeriod)内程序没有写KeyValue寄存器,则看门狗将停止 ARMulator ,返回调试器中。寄存器基址:Base=0xb0000000 KeyValue=0x12345678WatchPeriod=0x80000 IRQPeriod=3000 IntNumber=16 Stack tracker(栈跟踪器)栈跟踪器在每条指令执行后,检查栈指针(r13)寄存器的值。它记录下栈顶的最小值,从而可以计算出所用堆栈的最大长度。栈底地址:StackBase=0x80000000 栈空间限制:StackLimit=0x70000000 Tube(显示管)Tube 实际上是一个寄存器,它用来显示输出。一旦写入这个寄存器某个可以显示的字符,则调试器将会把它显示在控制台 Console 下。这样用来显示对某个指定地址的写操作记录。寄存器基址:Base=0x0d800020使用者也可以根据一定的接口规范编写自己的外设模块,或者通过修改已有模块的方式来适合自己的需求。比如定制自己的存储系统等。3.使用 ARMulator从调试器中可以配置 ARMulator 的一些属性,在调试器启动的时候,会将当前配置的部分信息输出显示。ADS 提供了6个 .ami 配置文件来定义当前 ARMulator 的工作行为。当 ARMulator 被调试器启动时,就会搜索环境变量 armconf 中的路径,读取所有的 .ami 配置文件来对 ARMulator 进行设置。ARMulator 还提供了跟踪(Tracer)功能,用来记录程序中指令和内存被访问的情况,也可以记录下各种事件的发生,例如 ARM 处理器事件、MMU 和 cache 事件等。另外,使用 ARMulator 的 profiling 记录,可以统计特定函数在执行过程中被调用的次数,包括子函数的调用在总的执行时间中所占的比例。使用 ARMulator 只要在调试器中设置了 target 就可以了,通过图形用户界面的调试器AXD ,可以查看 ARM 寄存器(包括通用寄存器和程序状态寄存器等),也可以查看任意内存单元的内容,还可以进行异常的处理,例如中断和程序中止等。通过对上面提到的定时器和中断控制器的设置,在 ARMulator 上甚至可以移植嵌入式实时操作系统 uC/OS-II。uC/OS-II 是一个实时操作系统内核,包含了任务调度、任务管理、时间管理、内存管理和任务间的通信与同步等基本功能。通过在 ARMulator 上移植和调试 uC/OS-II,以后就可以更快将其移植到自己的嵌入式硬件平台上。

2008年11月26日星期三

EasyARM2200开发板上使用LPC2138(使用Flash Magic烧入程序)

作者:Sam(甄峰) sam_code@hotmail.com

1. 使用Flash Magic进行ISP连接:
1.1. EasyARM2200开发板上有ISP使能跳线JP1。短接JP1进入ISP模式。
1.2. 使用串口线将PC与EasyARM2200 UART0 连接。
1.3. 打开Flash Magic. ISP->Read Device Signation....
则可以读取到 Device ID和Bootloader Version。
说明ISP连接成功。


2. 使用Flash Magic烧入程序:
2.1. EasyARM2200开发板上有ISP使能跳线JP1。短接JP1进入ISP模式。
22. 使用串口线将PC与EasyARM2200 UART0 连接。
2.3. 打开Flash Magic. ISP->Read Device Signation....
则可以读取到 Device ID和Bootloader Version。
说明ISP连接成功。
2.4. 设置Flash Magic:
2.4.1:COM:你所连接的COM口。
2.4.2:Baud rate: 波特率 19200
2.4.4:Device: LPC2138
2.4.5:Interface:None(ISP)
2.4.6:Oscillator Freq(MHz):12.000000
2.4.7:选中:Erase blocks used by Hex file(将烧入文件对应的区域Erase掉)
2.5:选中烧入目标文件:
xxx_Data\DebugInFLASH\xxx.i32文件
烧入时遇到问题。Sam先选择Erase all Flash+Code Rd Port,Erase所有的Flash。之后再回到Erase blocks used by Hex file。然后一切正常。


3. 使用ASD Codewarrior IDE打开之前的工程并编译:
3.1 进入Source code目录中xxx_data\DebugInFLASH目录。(因为Sam只编译DebugInFlash目标)
删除目录中所有文件。

3.2 打开mcp文件。选择DebugInFLASH。因为xxx_data\DebugInFLASH目录中所有东西都被删除了。所以显示所有文件都未被编译。F7 make. 会在xxx_data\DebugInFLASH目录声称xxx.i32文件。
Sam:也可以使用Codewarrior IDE 菜单中,project->Remote Object code...删除所有obj文件。就可以强行重新编译所有文件了。

3.3 可以改变声称的目标文件为hex文件。选择Setting中ARM FromELF。在Output file name中写入xxx.hex。则生成文件变为hex文件。

2008年11月19日星期三

ADS集成开发环境

ADS是ARM公司推出的ARM核微控制器集成开发环境。ARM Developer Suite.

1.ADS组成:
1.1代码生成工具,也就是编译器,链接器。
1.2集成开发环境:Codewarrior IDE.
1.3调试器:AXD
1.4指令模拟器:ARMulator,用来仿真调试。
1.5ARM开发包:底层例程,例如fromELF.
1.6ARM应用库。C,C++库等。

2. 关于ARM工程模板。
周立功网站提供了基于ADS和Keil的针对各种LPC芯片的工程模板。
例如:LPC2200系列的ARM7有6个工程模板。
工程模板有什么用呢?
因为ARM下程序与PC程序有所不同,也许需要将程序放到某个位置,这个位置是CPU启动后跳转的地点。所以ARM程序要做很多事,比如要设置中断向量表的位置。这块程序肯定是要ASM实现的。则工程模板会帮客户实现这些ASM程序。并且会提供分散加载文件(mem_a.scf,mem_b.scf, mem_c.scf).这些文件会指定程序会加载在何处。


3.LPC2200工程模板分散加载描述文件
当使用ARM Executable Image for lpc2200模板建立工程。则工程有4个生成目标(target system): DebugIExram, DebugInChipFlash, RelInChip,RelOutChip.

3.1 DebugInExram:片外RAM调试模式。程序放在片外RAM中。并且开发板设为片外RAM使用Bank0 。如果用户定制的设备不是按照RAM使用Bank0。则不能使用这个模式。
入口点地址:0x80000000
对应分散加载描述文件: mem_b.scf.
这个模式有个前提:片外RAM使用Bank0(即起始地址为0x80000000). 这一条不可修改。


3.2 DebugInChipFlash:片内Flash调试模式,程序放在片内Flash中。
入口点地址:0x00000000
对应分散加载描述文件:mem_c.scf

3.3 RelInChip: 片内Flash工作模式,程序放在片内Flash中。程序写入Flash之后即被加密。
入口点地址:0x00000000
对应分散加载描述文件:mem_c.scf

3.4 RelOutChip:片外Flash工作模式。程序放在片外Flash中。
入口点地址:0x80000000
对应分散加载描述文件:mem_a.scf

如何确定哪个模式对应的分散加载描述文件
在Codewarrior IDE中。
Setting中,Linker ---〉ARM Linker
其中Scatter:中指定了使用哪个分散加载描述文件。


分散加载描述文件解析:
ROM_EXEC:入口点地址
IRAM:片内RAM地址
STACKS:?
ERAM:片外RAM地址
HEAP:?

RFID开发软件安装以及LPC基础知识

作者:Sam(甄峰) sam_code@hotmail.com

Sam在做RFID项目时,使用的是ADS1.2开发环境。

1.安装ADS1.2。
1.1 RFID\tools\ADS_1.2\ads1.2\setup.exe
1.2.安装完毕后,选择"Install License"。选中tools\ADS_1.2\ads1.2\CRACK\LICENSE.DAT.则CRACK完成。ADS可以正常使用。

2.安装Flash Magic.
RFID\tools\FlashMagic下直接安装即可。

下面介绍LPC基础知识:
1.Flash,SRAM知识:
1.1 SRAM,DRAM知识简单介绍:
SRAM是英文Static RAM的缩写,它是一种具有静止存取功能的内存,不需要刷新电路即能保存它内部存储的数据。不像DRAM内存那样需要刷新电路,每隔一段时间,固定要对DRAM刷新充电一次,否则内部的数据即会消失,因此SRAM具有较高的性能,但是SRAM也有它的缺点,即它的集成度较低,相同容量的DRAM内存可以设计为较小的体积,但是SRAM却需要很大的体积。因为其存取速度快,所以常被用作CPU高速缓存(Cache Memory )。

1.2Non Flash,Nand Flash。
Non Flash存取速度慢,单元价格高。但可以直接执行。
Nand Flash存取速度快,价格低,但不能直接执行。要以块为单元读写。

1.3
LPC21xx, LPC22xx,LPC23xx系列芯片,大都带有片内Flash.片内SRAM。例如:lpc2138有512K片内高速Flash,32K片内RAM。

通常,可以按照需要添加片外Flash,片外SRAM.例如:EasyARM2200开发平台就有512K片外SRAM. 2M片外Flash。

1.4 片外Flash, 片外SRAM的地址空间分配:
EasyARM2200使用LPC芯片外部存储器接口的Bank0,Bank1扩展Flash(SST39VF160)和SRAM(IS61LV25616).他们各使用一个Bank. 并可以使用JP6设置Flash,RAM谁用哪个Bank.
当程序调适时,通常将Bank0分给SRAM。当程序固化与Flansh中时,则将Bank0分给Flash。因为Bank0可以用来引导程序运行。

1.5 片内Flash, 片外Flash谁来引导。
如果想要片外Flash引导程序执行:
1.必须将Bank0分给片外Flash。
2.必须设置从片外而不是片内Flash启动。
如果想要从片内Flash引导,则设置从片内Flash启动。

1.6 片内Flash, RAM 地址范围:
LPC 片内Flash通常从0x00000000开始。片内RAM从0x40000000开始。
也就是说如果选择从片内启动。则一定是从Flash启动。

2008年11月17日星期一

Intel CE3100(Canmore)多种方式启动

作者: Sam(甄峰) sam_code@hotmail.com

Intel Canmore嵌入式平台可以基于多种方式启动。

先介绍背景知识。
Intel CE3110 启动时,首先去执行Non flash上的一个称为CEFDK的程序。CEFDK做最初的初始化动作。然后将执行权交给redboot. redboot则将kernel和rootfs准备好并将执行权交给redboot.

CEFDK, redboot, kernel, rootfs放在不同的位置上,所以Canmore可以有多种启动方式。

方式1:
CEFDK, redboot,kernel,rootfs全部放入Non flash.
这个方式有个前提, 因为Non flash非常小。 所以 kernel,rootfs必须非常基础,非常小的。其实做不了什么事。基本很少使用这种方式。

方式2:
CEFDK,redboot放在Non flash. Kernel放在tftp根目录。rootfs放在nfs目录。
这种方式常用来在开发阶段使用。因为kernel,rootfs可以很容易的修改。

方式3:
CEFDK,redboot放在Non flash中,Kernel, rootfs放在SATA harddisk中。
这种方式是STB最终产品模式。

现在具体介绍不同方式的做法:

CEFDK在Canmore出厂时,就已经烧入到Non Flash中。所以需要做的就是把redboot, kernel,rootfs放到相应的位置。

方式2:
CEFDK,redboot放在Non flash. Kernel放在tftp根目录。rootfs放在nfs目录。
1.首先烧入redboot.
重新启动Canmore时,按住空格键,则进入CEFDK界面:
shell>
shell> ymodem 0x200000 0
在Transfer->send File 中选择ymodem. 并把redboot_flashboot.bin选中。
传输完成后,只是将image放到内存.还需要烧到Non flash中去。
shell>expflash burnFlash 0x200000 0x100000 0x20000
注:redboot_flashboot.bin表示是:这个版本的redboot是放在Non flash中的。
重新启动Canmore,按ctrl+c.则进入redboot界面:
RedBoot>

2.将kernel放到tftpboot中,将rootfs放到NFS目录中。
将kernel文件,bzImage放到server的/tftpboot中。(tftp要安装好)
将rootfs目录--fsroot放到NFS允许使用的目录中。(nfs服务要安装好 /etc/exports要允许Canmore IP地址使用这个目录)

3.在redboot中,设置kernel,rootfs的放置地点。
RedBoot> fconfig

>> load -v -r -m tftp -h 172.16.1.61 -b 0x200000 bzImage
>> exec -b 0x200000 -l 0x300000 -c "console=ttyS0,115200 root=/dev/nfs rw nfsroot=172.16.1.61:/home/sam/Intel/Canmore/fsroot_canmore_1073_new ip=dhcp mem=exactmap memmap=640k@0 memmap=95M@1M"
>>
Boot script timeout (1000ms resolution): 2Use BOOTP for network configuration: trueDefault server IP address: 172.16.1.70Console baud rate: 115200GDB connection port: 9000Force console for special debug messages: falseNetwork debug at boot time: falseUpdate RedBoot non-volatile configuration - continue (y/n)? y

之后重新启动,则redboot从172.16.1.61的tftpboot中拿kernel.
把172.16.1.61:/home/sam/Intel/Canmore/fsroot_canmore_1073_new 当作rootfs.



方式3: CEFDK,redboot放在Non flash中,Kernel, rootfs放在SATA harddisk中。
3.1.准备SATA harddisk.
3.1.1分区:
将SATA硬盘连接到开发机Linux上 ,而不是Canmore Linux上。
#fdisk -l

注意:/dev/sdb是4G固态硬盘。/dev/sda是Sam插入的40G SATA硬盘。
Sam想把SATA硬盘分2个区。第一个区用来放置redboot.第二个分区用来放kernel+rootfs.
注意:
#fdisk /dev/sda

Command (m for help): n
Command action e extended p primary partition (1-4)p
Partition number (1-4): 1
First cylinder (1-1022, default 1): Using default value 1
Last cylinder or +size or +sizeM or +sizeK (1-1022, default 1022): 1

Command (m for help): n
Command action e extended p primary partition (1-4)p
Partition number (1-4): 2
First cylinder (2-1022, default 2): Using default value 2
Last cylinder or +size or +sizeM or +sizeK (2-1022, default 1022): +1024M

Command (m for help): a
Partition number (1-4): 1
Command (m for help): w
The partition table has been altered!

n:新建partition.
p:逻辑分区
1,2:分区号。
First cylinder :第一个扇区号
Last cylinder or +size or +sizeM or +sizeK :最后一个扇区号.或者用+xxxM.

a: 制定boot flag.
w:执行以上动作。

3.1.2格式化/dev/sda2
#mkfs.ext3 /dev/sda2


3.2:放置redboot.bin到第一个分区。
首先使用tftpboot+nfs方式启动。
# dd conv=sync if=redboot.bin of=/dev/sda1 [注1]
224+1 records in
225+0 records out

3.3:放置kernel+rootfs到第二个分区。
# mkdir disk
# mount /dev/sda2 /disk/
#cp bzImage /disk
#cd fsroot
# cp * -rf /disk/

3.4:设置redboot:(Sam认为应该是Non Flash中的redboot)
先查看当前Redboot设置。
Redboot> fconfig -l

写Redboot:
Redboot> fconfig
>> load -v -r -m disk -b 0x200000 hda2:bzImage
>> exec -b 0x200000 -l 0x300000 -c "console=ttyS0,115200 root=/dev/sda2 rw mem=exactmap memmap=640K@0 memmap=95M@1M"
>>

重新启动。OK



大Memory设置:
在PR7之后的版本中,Canmore可以通过一系列设置,将用户空间大小提升到194M。
可以如下操作:
1.修改config文件:
#emacs /etc/platform_config/platform_config.hcfg
修改以下句子
media_base_address = 0x0C300000 /*0x10000000 sam Modify it*/
2. boot时修改redboot传入的参数:
load -v -r -m tftp -h 172.16.1.61 -b 0x200000 bzImage_canmore_1087exec -b 0x200000 -l 0x300000 -c "console=ttyS0,115200 root=/dev/nfs rw nfsroot=172.16.1.61:/home/sam/Intel/Canmore/TEST_TEAM_FSROOT/image4/fsroot_canmore_1087 ip=172.16.1.41 mem=exactmap memmap=640k@0 memmap=194M@1M"
之前是:memmap=95M@1M 表明从1M处到95M处。新写法表明从1M处到194M处。

注1:
Linux命令dd:
dd: convert and copy a file.转换和copy 文件。
conv= : convert the file as per the comma separated symbol list. 将文件转化为指定的格式
其中:conv = sync:把每个输入块填充到ibs个字节,不足部分用空(NUL)字符补齐。
if=:read from FILE instead of stdin。输入
of=:write to FILE instead of stdout。输出
# dd conv=sync if=redboot.bin of=/dev/sda1
所以,这句话:将redboot.bin放入/dev/sda1

2008年11月16日星期日

Intel CE3100(Canmore) PDK Install

作者:Sam(甄峰) sam_code@hotmail.com

2008/11/17. Intel 发布了PR-6-1073. Sam得到的PDK包为:ACC-1.0.0.1073 BASE-1.0.0.1073 GPL MPEG-1.0.0.1073。
按照经验,Sam首先安装BASE-1.0.0.1073.
BASE中的目录为:Canmore-BASE-1[1].0.0.1073.iso Canmore-BASE-1[1].0.0.1073.iso.md5 CE3100_SDK_ReleaseNotes_PR6.pdf readme.txt。

1.将ISO mount到目录中。
#mount -t iso9660 -o loop,ro Canmore-BASE-1\[1\].0.0.1073.iso ../mount_dir/

2.安装BASE.
#sh installer /home/intel/Intel_CE3110/Intel_CE3110_Dev_1073
显示PGL后正常安装。
之后发现Sam最关心的kernel竟然没有。于是察看readme.txt后。应该在GPL中。呵呵。

完全安装后一切正常。

#make all

2008年11月11日星期二

基于bluez程序编译时注意事项

作者:Sam(甄峰) sam_code@hotmail.com

Sam制作了一个基于bluez的,类似于BTW的库---BTX.在编写和使用这个库时,发现头文件包含容易出现的问题。

第一:不要用到哪个Bluez头文件中定义的内容就只去包含这个头文件。而应该将bluetooth/bluetooth.h最先加入。类似:
//==================================
//bluetooth header file
#include //BTPROTO_HCI
#include //struct hci_dev_info
#include //hci_devid()
#include //l2cap
#include //hidp


第二:因为Linux 下Bluetooth实现是通过socket来完成的。sa_family_t会被使用。
bluetooth/hci.h中就用到了。所以必须在include bluetooth header file之前,include socket header file.
#include //socket()
#include
否则就会出现类似以下问题:
bluetooth/hci.h:1091: error: expected specifier-qualifier-list before 'sa_family_t'

gcc与g++的区别(转贴)

Sam在上一篇帖子中提到:当使用xxx.c.并使用gcc编译时,会有重定义无法检测出来的现象。今天学习一下gcc与g++的区别。

Sam之前在leadtek作嵌入式时,与Milong谈过gcc与g++的区别,曾认为gcc就是C方式编译,g++就是C++方式编译。现在才知道是不对的。

误区一:gcc只能编译c代码,g++只能编译c++代码
实际上,gcc与g++都是既可以编译C程序,也可以编译C++程序
但需要注意的是:
1.后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序。
2.编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接统统用g++了,这就给人一种错觉,好像cpp程序只能用g++编译似的。


误区二:gcc不会定义__cplusplus宏,而g++会
这个宏只是标志着编译器将会把代码按C还是C++语法来解释,如上所述,如果后缀为.c,并且采用gcc编译器,则该宏就是未定义的,否则,就是已定义。


误区三:编译只能用gcc,链接只能用g++
严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。因为gcc命令不能自动和C++程序使用的库联接,所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价。


误区四:extern "C"与gcc/g++有关系
实际上并无关系,无论是gcc还是g++,用extern "c"时,都是以C的命名方式来为symbol命名,否则,都以c++方式命名。
Sam认为这句话值得商榷:也许对于.c文件,gcc会以C的命名方式为symbol命名。对于.cpp文件,gcc和g++都用C++方式命名。如果有了extern "C".则全部使用C方式命名。

另:gcc -S 可以生成.s文件,就可以看到symbol了。

2008年11月9日星期日

extern "C" 详解

Sam之前在提供BTX库时,需要提供一个 BTX.h文件。按照标准头文件的写法:
#ifndef _BTX_H
#define _BTX_H
#ifdef __cplusplus
extern "C" {
#endif

//xxxxxx
extern functionA();

#ifdef __cplusplus
}
#endif

#endif
也就是说:将所有function 声明和 struct定义全放在里面了。但corner建议只将function声明放在其中。并且,不需要再次声明:extern functionA();
所以研究一下#extern "C"

众所周知:
#ifndef _BTX_H
#define _BTX_H
//xxxx
#endif
编译宏用来防止重复引用。

下面将仔细研究#extern.(这部分引用CSDN:keen资源,觉得他讲的足够清楚了)


extern 作用1:声明外部变量
现代编译器一般采用按文件编译的方式,因此在编译时,各个文件中定义的全局变量是
互相透明的,也就是说,在编译时,全局变量的可见域限制在文件内部。

例1:
创建一个工程,里面含有A.cpp和B.cpp两个简单的C++源文件:
//A.cpp:
int iRI;
int main()
{
//.....
}

//B.cpp
int iRI;

gcc A.cpp -c
gcc B.cpp -c
编译出A.o, B.o都没有问题。
但当gcc A.o B.o -o test时,
main.o:(.bss+0x0): multiple definition of `iRI'
b.o:(.bss+0x0): first defined here
报错:重定义。
(但Sam有个非常意外的发现:当同样的代码,使用A.c B.c.并使用gcc编译时,竟然不会报重定义的错误,非常不明白是怎么回事。)
这就是说,在编译阶段,各个文件中定义的全局变量相互是透明的,编译A时觉察不到B中也定义了i,同样,编译B时觉察不到A中也定义了i。
但是到了链接阶段,要将各个文件的内容“合为一体”,因此,如果某些文件中定义的全局变量名相同的话,在这个时候就会出现错误,也就是上面提示的重复定义的错误。因此,各个文件中定义的全局变量名不可相同。


但如果用下列方式:在B.cpp中定义iRI;在A.cpp中直接使用。则编译A.cpp时就无法通过。
//A.cpp
int main()
{
iRI=64;
}

//B.cpp
int iRI;

gcc A.cpp -c
was not declared in this scope.
因为编译器按照文件方式编译,所以编译A.cpp时,并不知道B.cpp中定义了iRI。
也就是说:文件中定义的全局变量的可见性扩展到整个程序是在链接完成之后,而在编译阶段,他们的可见性仍局限于各自的文件。
解决方案如下:
编译器的目光不够长远,编译器没有能够意识到,某个变量符号虽然不是本文件定义的,但是它可能是在其它的文件中定义的。
虽然编译器不够远见,但是我们可以给它提示,帮助它来解决上面出现的问题。这就是extern的作用了。
extern的原理很简单,就是告诉编译器:“你现在编译的文件中,有一个标识符虽然没有在本文件中定义,但是它是在别的文件中定义的全局变量,你要放行!”
//A.cpp:
extern int iRI;
int main()
{
iRI = 64;
//.....
}

//B.cpp
int iRI;
这样编译就能够通过。
extern int iRI; //并未分配空间,只是通知编译器,在其它文件定义过iRI。


extern 作用2:在C++文件中调用C方式编译的函数
C方式编译和C++方式编译
相对于C,C++中新增了诸如重载等新特性。所以全局变量和函数名编译后的命名方式有很大区别。

int a;
int functionA();
对于C方式编译:
int a;=> _a
int functionA(); => _functionA
对于C++方式编译:
int a; =>xx@xxx@a
int functionA(); => xx@xx@functionA
可以看出,因为要支持重载,所以C++方式编译下,生成的全局变量名和函数名复杂很多。与C方式编译的加一个下划线不同。
于是就有下面几种情况:
例2:C++调用C++定义的全局变量
//A.cpp:
extern int iRI;
int main()
{
iRI = 64;
//.....
}
//B.cpp
int iRI;
gcc A.cpp -c
gcc B.cpp -c
gcc A.o B.o -o test
那么在编译链接时都没问题。

例3:C++调用C定义的全局变量
//A.cpp:
extern int iRI;
int main()
{
iRI = 64;
//.....
}
//B.c
int iRI;
编译时没有问题,
gcc A.cpp -c
gcc B.c -c
但链接时,gcc B.o A.o -o test
则会报iRI没有定义。为什么呢?
因为gcc看到A.cpp,就使用C++方式编译,看到B.c,就使用C方式编译。
所以在A.cpp中的iRI=>XXX@XXX_iRI;
而B.c中iRI=〉_iRI;
所以在链接时,A.cpp想找到XXX@XXX_iRI,当然找不到。所以就需要告诉编译器,iRI是使用C方式编译的。
//A.cpp:
extern "C"
{
int iRI;
}
int main()
{ iRI = 64;
//.....
}
//B.c
int iRI;
这样,当编译A.cpp时,编译器就知道iRI为C方式编译的。就会使用 _iRI。这样B.c提供的_iRI就可以被A.cpp找到了。

例4:C++调用C定义的function
//A.cpp
extern int functionA();

int main()
{
functionA();
}

//B.c
int functionA()
{
//....
}
gcc A.cpp -c
gcc B.c -c
都没有问题。但同样的,gcc A.o B.o -o test
则报错,找不到functionA();
这是因为gcc将A.cpp认为是C++方式编译,B.c是C方式编译。
所以functionA在B.c中为:_functionA. 在A.cpp中为:XX@XXX_functionA
所以在链接时A.cpp找不到XX@XX_function.
于是需要通知编译器,functionA()是C方式编译命名的。
//A.cpp
extern "C"
{
int functionA();
}

int main()
{
functionA();
}

//B.c
int functionA()
{
//....
}
于是,编译链接都可以通过。

Sam的具体应用:
另外:Sam这次是写了一个Linux 下Bluetooth库。libBTX.so. 这个库需要C,C++都可以使用。
但Sam这个库是C方式编译的。libBTX.so下,functionA()=>_functionA
所以C程序调用时没有问题,C++则会有问题。
所以需要在这个库提供的头文件中加入:
#ifndef _BTX_H
#define _BTX_H

#ifdef __cplusplus
extern "C" {
#endif


//xxxxxx


#ifdef __cplusplus
}
#endif
则当C程序下使用这个库时,
#include "BTX.h"
因为是C方式编译,所以不会进入#ifdef __cplusplus
当C++程序调用这个库,
#include "BTX.h"
则C++编译器可以进入#ifdef __cplusplus
所以extern "C"被启用。
这表示下面BTX所提供的function为C方式编译的。则C++程序也能够将functionA()编译为_functionA.这样就可以找到libBTX.so所提供的function.


最终结论:
Corner所得很对,只需要把function()放在extern "C" { ....} 内,但Sam的做法好象也没错。因为很多Linux程序也是这样做的。
同时,
extern "C"
{
functionA();
}//不止是声明,并且还指出:这个function请用C方式编译。所以不需要再次extern.
所以Corner的建议也是非常有道理的。
exter "C"
{
extern functionA();
}//这样做没什么太大意义。

2008年11月5日星期三

Linux下静态库,动态库的创建和使用,多个动态库使用

作者:Sam(甄峰) sam_code@hotmail.com

1.动态库和静态库的区别:
使用静态的程序库时,连接器会找出程序所需的函数,然后将它们拷贝到执行文件,由于这种拷贝是完整的,所以一旦连接成功,静态程序库也就不再需要了。
对动态库而言,就不是这样。动态库会在执行程序内留下一个标记指明当程序执行时,首先必须载入这个库。
由于动态库节省空间,Linux下进行连接的缺省操作是首先连接动态库,也就是说,如果同时存在静态和动态库,不特别指定的话,将与动态库相连接。





Linux下静态库的创建:
ar rv libxxx.a xxx.c
ar rv libxxx.a xxx.o
例如:
libBT.a: bluetooth_remote.o cmd_rpt.o AnalysisDirection.o
arm-linux-ar rv libBT.a $?
注解:
ar命令可以用来创建、修改库,也可以从库中提出单个模块。库是一单独的文件,里面包含了按照特定的结构组织起来的其它的一些文件(称做此库文件的member)。原始文件的内容、模式、时间戳、属主、组等属性都保留在库文件中。

r:在库中插入模块(替换)。当插入的模块名已经在库中存在,则替换同名的模块。如果若干模块中有一个模块在库中不存在,ar显示一个错误消息,并不替换其他同名模块。默认的情况下,新的成员增加在库的结尾处,可以使用其他任选项来改变增加的位置。

v:该选项用来显示执行操作选项的附加信息。



Linux下动态库的创建:
gcc -shared -fpic xxx.c -o libxxx.so
gcc -shared -fpic xxx.o -o libxxx.so
例如:
libBTX.so: BTX.o
$(CC) $(CFLAGS) -shared -fpic BTX.o -o libBTX.so



Linux下动态库的使用:
-lxxx
例1:某个动态库为 libBTX.so
gcc main.c -o bluetooth_test -L./ -lBTX

Linux下静态库的使用:
例2:某个静态库为 libBTX.a
gcc main.c libBTX.a -o bluetooth_test
gcc main.c -o bluetooth_test -lBTX
以上2个方法都可以链接到静态库


当几个动态库之间有依赖关系时:
例3:Sam在作libBTX.so动态库时,依赖了 libbluetooth.so这个动态库。
首先生成libBTX.so:
i686-linux-elf/bin/i686-cm-linux-gcc -D_SHOW -Wall -I../include -I/home/sam/work/current/Intel_CE_3110/Intel_Canmore/Canmore-1.1050/i686-linux-elf/include -shared -fpic BTX.o -o libBTX.so
因为依赖的是一个动态库--libbluetooth.so 所以在生成 libBTX.so时不必真的链接到libbluetooth.so

当要生成可执行文件时:
i686-linux-elf/bin/i686-cm-linux-gcc -D_SHOW -Wall -I../include -I/home/sam/work/current/Intel_CE_3110/Intel_Canmore/Canmore-1.1050/i686-linux-elf/include -L/home/sam/work/current/Intel_CE_3110/Intel_Canmore/Canmore-1.1050/i686-linux-elf/lib -L./ main.c -o BTX_Test -lpthread -lm -lBTX -lbluetooth
因为libBTX.so依赖 libbluetooth.所以需要将 libbluetooth.so也指定进来。否则libbluetooth.so所提供的function就无法找到。
注意:因为libBTX.so 依赖于libbluetooth.so.所以-lBTX 要放在 -lbluetooth之前。



静态库与动态库相依赖时与之类似。
例4:libBTX.a中依赖于libbluetooth.so
所以生成可执行文件时,也需要加入 -lbluetooth
gcc main.c -o bluetooth_test -lBTX -lbluetooth

静态库之间相互依赖:
例如,某程序需要使用 libBTX.a,libbluetooth_remote.a
则link时,需要把libbluetooth_remote.a放在libBTX.a之前。



当目录内同时存在动态库和静态库时指定使用哪个库:
-WI,-Bstatic:
这个特别的"-WI,-Bstatic"参数,实际上是传给了连接器ld。指示它与静态库连接,如果系统中只有静态库当然就不需要这个参数了。
如果某个程序需要link多个库,例如,BTX使用静态库,Bluetooth_remote使用动态库。则需要这样写:
gcc main.c -o BT_remote -WI,-Bstatic -lBTX -WI,-Bdynamic -lBluetooth_remote



编译时的 -L 选项与 环境变量 LD_LIBRARY_PATH之间的关系:
呵呵,其实他们之间没有关系,
-L 表明编译时该到什么地方去找动态,静态库。
例1:动态库libBTX.so放在 /usr/local/lib中,则 -L/usr/local/lib -lBTX
例2:动态库libBTX.so放在编译本目录中,则 -L./ -lBTX

export LD_LIBRARY_PATH则表示执行时在什么地方寻找动态库。
例3:动态库libBTX.so放在 /usr/lib/bluetooth中。则
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/bluetooth
程序执行时就会去 /usr/lib/bluetooth中寻找所需动态库。

2008年10月29日星期三

Bluetooth HCI Event的处理

作者:Sam(甄峰) sam_code@hotmail.com

1.当Bluetooth USB dongle插入USB接口时,driver/bluetooth/hci_usb.c中probe程序被调用。probe会调用hci_register_dev()--〉tasklet_init(&hdev->rx_task, hci_rx_task, (unsigned long) hdev);
将hci_rx_task() 连上rx_task->func.
2.同样在Bluetooth USB dongle插入时,hdev->open = hci_usb_open; 当USB Dongle被UP时,这个function被调用。它调用hci_usb_intr_rx_submit(),它注册URB完成处理程序,hci_usb_rx_complete。
当有URB出现时, hci_usb_rx_complete ->__recv_frame() ->hci_recv_frame()->hci_sched_rx(hdev);->hci_sched_rx() ->tasklet_schedule(&hdev->rx_task)
tasklet_schedule():
将这个tasklet放在 tasklet_vec链表的头部,并唤醒后台线程ksoftirqd。当后台线程ksoftirqd运行调用__do_softirq时,会执行在中断向量表softirq_vec里中断号TASKLET_SOFTIRQ对应的tasklet_action函数,然后tasklet_action遍历 tasklet_vec链表,调用每个tasklet的函数完成软中断操作。
也就是调用hci_rx_task()

也就是:当有URB时,会最终调用到hci_rx_task()

当包类型为 HCI Event时,则调用hci_event_packet()
在hci_event_packet()中,会判断是何种Event.并处理之。

下面以HCI_EV_DISCONN_COMPLETE为例子看Event如何被处理。这个Event表明连接断掉了。reason则表示断掉的原因。

HCI_EV_DISCONN_COMPLETE Event使用处理程序hci_disconn_complete_evt来处理,下面详细讲解:

1.hci_dev_lock():使用自旋锁得到资源。

2.hci_conn_hash_lookup_handle():从连接队列中找出对应连接。注:当有ACL或SOC连接时,这个连接就会被加入连接链表。

3.这个连接的state改为BT_CLOSED。

4.hci_proto_disconn_ind(): 调用全局变量hci_proto中的disconn_ind()。

注:在L2cap protocol insmod时,hci_register_proto()被调用。全局变量hci_proto则被赋值。disconn_ind=l2cap_disconn_ind;

所以当HCI层得到Disconnect时,会通知l2cap层做删除channel等工作。

且因为state改为BT_CLOSED. 所以在l2cap 层调用poll(最终调用bt_sock_poll)时会发现POLLHUP被置位。

这样l2cap应用程序就能够知道连接已经断开了。

2008年10月28日星期二

Linux BlueZ 下 Inquiry的实现

作者:Sam(甄峰) sam_code@hotmail.com

前面讲了使用 ioctl发送HCI Command的方法。但HCI protocol还有一些Command,比如Inquiry等,他们则不需要使用ioctl来发送。blueZ 直接提供了支持,虽然他们的最终都是用同样办法实现的。

在应用程序中:
hci_inquiry(dev_id, length, num_rsp, NULL, &info, flags);
这样需要包含blueZ 头文件如下:
#include //BTPROTO_HCI
#include //struct hci_dev_info
#include //hci_devid()
#include //l2cap
#include //hidp

hci_inquiry()的实现在 bluez-lib-xxx/lib/src/hci.c中。
哈哈,发现其实最终也是使用:
ioctl(dd, HCIINQUIRY, (unsigned long) buf);
这样,其实就和上一篇对应起来了。所有HCI Command最终都使用ioctl来发送。

2008年10月27日星期一

Linux下Bluetooth HCI Command的实现

作者:Sam(甄峰) sam_code@hotmail.com

Linux下写Bluetooth程序,首先接触到的就是使用HCI Command来设置Bluetooth Modules(USB Bluetooth dongle)。那这些HCI command在blueZ中是如何实现的呢?举例说明。


if ((ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)) < dev_id =" dev_id;">open(). 这个open function也同上,在USB dongle probe时给定。在hci_usb.c hci_usb_probe()中。=hci_usb_open;
hci_usb_open()中则设置hci_dev->flag=HCI_RUNNING.
并向USB Core提交一个中断URB。当有中断URB被USB Core出来完毕后,调用hci_usb_rx_complete()。 它则调用__recv_frame()。它处理所有USB Dongle通过中断URB送来的数据。
4.利用hci_init_req()发送request.
hci_init_req()调用hci_send_cmd()发送命令。
hci_send_cmd() 则调用skb_queue_tail()将命令skb添加到 发送队列。再调用hci_sched_cmd()去调用发送命令去发送。发送命令为:hci_cmd_task()
hci_cmd_task()-〉hci_send_frame()-〉hci_send_frame()-〉hdev->send(skb);=hci_usb_send_frame();发送命令给USB bluetooth dongle.

HCI就是通过这个途径发送Command到Dongle的。所以HCI Socket其实就是PC和Dongle之间的一个通道。注意,不是PC和远端bluetooth 设备的通道,而是本地Dongle。