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);