作者:Sam(甄峰) sam_code@hotmail.com
Sam需要看看2.6 kernel中USB Mouse的代码。顺便谈谈Kernel中Makefile和Kconfig文件的关系以及配合使用。
背景知识:
背景知识一:Kconfig介绍:
在#make menuconfig 时,所显示的Menu list是由各层Kconfig组成的。
最底层Kconfig存放在 ~/arch/i386/Kconfig. 以此为头,它会一层层使用source来把需要加入的各个目录中Keconfig添加近来。
例如:source "drivers/Kconfig"
则将~/drivers/Kconfig添加进Menu list中。
背景知识二:Kconfig写法语义:
config HID
tristate "Generic HID support"
depends on INPUT
default y
---help---
A human interface device (HID) is a type of computer device that interacts directly with and takes input from humans. The term "HID" most commonly used to refer to the USB-HID specification, but other devices (such as, but not strictly limited to, Bluetooth) are designed using HID specification (this involves certain keyboards, mice, tablets, etc). This option compiles into kernel the generic HID layer code (parser, usages, etc.), which can then be used by transport-specific HID implementation (like USB or Bluetooth).
For docs and specs, see http://www.usb.org/developers/hidpage/
If unsure, say Y
解释如下:
config HID :表示此条目与CONFIG-HID对应。CONFIG-HID会在Makefile中用到。
tristate "Generic HID support" 引号内的内容是会显示到Menu list中的。tristate表示这一项是三态的。
depends on INPUT:依赖于INPUT这一项。如果没有选中INPUT,则Menu list不会显示这项。
default y :缺省被选中。
背景知识三:built-in.o
vmlinux是Linux源码编译后未压缩的内核, vmlinux是由arch/i386/kernel/head.o和arch/i386/kernel/init_task.o以及各个相关子目录下的built-in.o链接而成的。
背景知识四:Kernel Makefile
Kernel中Makefile的体系以及如何编译的,其实Sam一直是一知半解的。
其中,kernel目录中的Makefile被称为底层Makefile。
当使用类似#make menuconfig配置内核成功后,会生成 .config文件。
换句话说:make menuconfig 时,Makefile会从~/arch/i386/Kconfig读取Kconfig.然后根据用户的选择。生成.config文件。
例如:在drivers/hid/Kconfig:
config HID
tristate "Generic HID support"
如果用户选中Y,则在.config中会反映出来:
CONFIG_HID=y
则在~/drivers/Makefile中可以看到:
obj-$(CONFIG_HID) += hid/
表明:如果CONFIG_HID是Y,则把hid目录添加到要编译的目录中了。
进入到/driver/hid目录,则看到:
hid-objs := hid-core.o hid-input.o
表明这两个.o文件是一定会被编译出的。
obj-$(CONFIG_HID) += hid.o
表明:如果CONFIG_HID是Y,则hid.o会被编译出来。并built-in.
如果是 =m. 则hid.o被编译出来,但最后被做成modules(ko)
背景知识五:KBuild Make:
Linux内核的Makefile与我们平时写的Makefile有所不同,它由五部分组成:
1.Makefile : 顶层Makefile。
2. .config: kernel配置文件。
3. arch/xxx/Makefile: 具体架构的Makefile。
4. scripts/Makefile.xxx : 通用规则。
5. kbuild Makefile: 整个kernel中大约有数百个这种文件。
#make menuconfig后,生成 kernel配置文件: .config。
顶层Makefile读取.config.
顶层Makefile通过解析 .config来决定递归访问哪些目录中的Kbuild Makefile .
这个过程中,Kbuild Makefile会按.config的设置,逐个添加文件列表,以供最后的编译使用。
最简单的KBuild Makefile如下:
obj-y += foo.o
表明:Kbuild在这目录里,有一个名为foo.o的目标文件。foo.o将从foo.c或foo.S文件编译得到。并且它会被包入built-in中去。
所有编译进内核的目标文件都存在$(obj-y)列表中。而这些列表依赖内核的配置。Kbuild编译所有的$(obj-y)文件。然后,调用"$(LD) -r"将它们合并到一个build-in.o文件中。稍后,该build-in.o会被其父Makefile联接进vmlinux中。
如果foo.o要编译成一模块,那就要用obj-m了。所采用的形式如下:
obj-m += foo.o
例一:
在 ~/driver/hid/hid-core.c中,有以下语句,即内核insmod接口hid_init.
module_init(hid_init);
也就是说,当此模块被buildin或者作为module insmod时,kernel会自动调用hid_init.
然后查看 ~/driver/hid/Makefile,发现
hid-objs := hid-core.o hid-input.o
表明只要hid这个目录被加入,就会生成hid-core.o.
只好去看上一层目录中怎样会进入hid目录:
obj-$(CONFIG_HID) += hid/
表明只要CONFIG_HID=Y,m. 则hid目录被加入。
但用户作了什么,hid目录被加入编译呢?则看~/drivers/hid/Kconfig
config HID
tristate "Generic HID support"
以此得之只要在make menuconfig中选中此项,则hid-core.o被编译出来。
例2:
Sam想要研究USB Keyboard & Mouse driver. 在 make menuconfig时,需要选中:
config USB_HID
tristate "USB Human Interface Device (full HID) support"
则查看Makefile。发现只要选中CONFIG_USB_HID.则会编译出usb_hid.o
但~/drivers/hid/usbhid目录中却没有usbhid.c。那usbhid.o如何生成的呢?
drivers/hid/usbhid目录中,有个.usbhid.o.cmd文件。
2009年4月28日星期二
2009年4月20日星期一
X5平台上几个问题的思考
作者:Sam(甄峰) sam_code@hotmail.com
问题1:
Sam在使用X5平台时,发现如果kernel升级一次(kernel版本没有变化),则之前编译好的modules insmod时会出现错误:
Unknown symbol in module (-1): No such file or directory
Sam刚开始想当然的认为是因为有人对kernel中版本信息作了变化。于是打开:include/linux/version.h。却发现其中UTS_RELEASE并没变化。
感觉非常奇怪。这个问题等待之后解决。
问题2:
Sam在使用X5平台时。通常是使用芯片提供商提供的Makefile直接编译出kernel.后来有一次无意中自己进入kernel目录,make menuconfig;make clean;make dep;make bzImage
得到的Image直接烧入,发现出错。
HiBoot 1.1.0.0 (Jan 8 2009 - 12:16:13)
HiBoot code: 60E00000 -> 60E37254
BSS: -> 60E62888
RAM Configuration: Bank #0: 60000000 128 MB Flash: 16 MB MAC: 00-00-00-00-00-02
Hit any key to stop autoboot: 0
Bad Magic Number
Sam检查多次,也没发现自己做错了什么。于是去查看芯片提供商给的外部Makefile.
其实很简单,就是copy 他们的config到linux-2.6.14/.config
然后编译kernel.最后则作了个特殊处理:
#mk-ulinux $(LINUXDIR) 0x60a00000 kernel-2.6.14-hi
Sam怀疑这是对kernel Image做了重新编排,加入一些标志然后作成kernel-2.6.14-hi。然后在u-boot中检查这些标志。
这是嵌入式系统中常用的一些做法,Sam竟然没想到,真不应该。
问题3:
在X5平台上,Sam使用动态库,编译时没问题,但运行时会报错:
Can't modify xxx.so's text section. Use GCC option -fPIC for shared objects, please.
但Sam其实在编译SO时使用了参数 -shared -fpic.
所以怀疑是编译器的问题。
Sam之前做交叉编译器都是用工具作的,对里面很多东西并不了解。看来什么时候需要具体作一个了。把类似:缺省 -I, -L目录,CPU类型等等全搞清楚。
问题1:
Sam在使用X5平台时,发现如果kernel升级一次(kernel版本没有变化),则之前编译好的modules insmod时会出现错误:
Unknown symbol in module (-1): No such file or directory
Sam刚开始想当然的认为是因为有人对kernel中版本信息作了变化。于是打开:include/linux/version.h。却发现其中UTS_RELEASE并没变化。
感觉非常奇怪。这个问题等待之后解决。
问题2:
Sam在使用X5平台时。通常是使用芯片提供商提供的Makefile直接编译出kernel.后来有一次无意中自己进入kernel目录,make menuconfig;make clean;make dep;make bzImage
得到的Image直接烧入,发现出错。
HiBoot 1.1.0.0 (Jan 8 2009 - 12:16:13)
HiBoot code: 60E00000 -> 60E37254
BSS: -> 60E62888
RAM Configuration: Bank #0: 60000000 128 MB Flash: 16 MB MAC: 00-00-00-00-00-02
Hit any key to stop autoboot: 0
Bad Magic Number
Sam检查多次,也没发现自己做错了什么。于是去查看芯片提供商给的外部Makefile.
其实很简单,就是copy 他们的config到linux-2.6.14/.config
然后编译kernel.最后则作了个特殊处理:
#mk-ulinux $(LINUXDIR) 0x60a00000 kernel-2.6.14-hi
Sam怀疑这是对kernel Image做了重新编排,加入一些标志然后作成kernel-2.6.14-hi。然后在u-boot中检查这些标志。
这是嵌入式系统中常用的一些做法,Sam竟然没想到,真不应该。
问题3:
在X5平台上,Sam使用动态库,编译时没问题,但运行时会报错:
Can't modify xxx.so's text section. Use GCC option -fPIC for shared objects, please.
但Sam其实在编译SO时使用了参数 -shared -fpic.
所以怀疑是编译器的问题。
Sam之前做交叉编译器都是用工具作的,对里面很多东西并不了解。看来什么时候需要具体作一个了。把类似:缺省 -I, -L目录,CPU类型等等全搞清楚。
2009年3月25日星期三
GCC,glibc, uclibc,stdc的一些基本概念
作者:Sam(甄峰) sam_code@hotmail.com
Sam有一个C++程序,其中用到:
std::wstring mbstowcs(std::string str);
使用X5平台的编译器编译时,遇到如下问题:
error: expected constructor, destructor, or type conversion before "mbstowcs"
error: expected `,' or `;' before "mbstowcs"
Sam就开始寻找原因。顺便把gcc,glibc,uclibc,stdc++等东西的概念性东西放在这里。
GCC:gcc(gnu collect compiler)是一组编译工具的总称。它主要完成的工作任务是“预处理”和“编译”,以及提供了与编译器紧密相关的运行库的支持,如libgcc_s.so、libstdc++.so等。
glibc:glibc是gnu发布的libc库,也即c运行库。glibc是linux系统中最底层的api,几乎其它任何的运行库都会倚赖于glibc。glibc除了封装linux操作系统所提供的系统服务外,它本身也提供了许多其它一些必要功能服务的实现.
uclibc:uclibc是另一c运行库,与glibc对应。它比glibc小。虽然uClibc和Glibc在已有的接口上是兼容的,但有些接口并没有实现。
libstdc++:libstdc++ 是GNU C++ standard Library .
Sam觉得既然编译就通不过,那说明X5的编译器本身就不认识,有可能是X5平台的交叉编译器在创建时没有添加stdc支持?后来觉得不是这样,因为STL完全是以头文件形式提供的。
1.所以只需要指定头文件路径,就应该可以编译。于是Sam添加了:
-I/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include/c++/3.4.3/
(Sam觉得海思的交叉编译器做得不是特别规范,为什么呢。因为它放头文件,库文件的地点多变)
添加这个之后,理论上应该是可以编译了。
可发现还是不认识wstring.
2.于是查看/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include/c++/3.4.3/bits/stringfwd.h。
发现要定义_GLIBCXX_USE_WCHAR_T才会有wstring.
3.于是在编译程序时添加了 -D_GLIBCXX_USE_WCHAR_T
还是通不过,说没有wint_t。
4.于是又查STL. 添加了 -D__WINT_TYPE__
又通不过, 说没有 btowc,Sam查遍了toolchain. 也没找到这个类型的定义。感觉很奇怪,就去查/usr/include. 发现这个类型有定义。但toolchain中没有对应的头文件。
所以,Sam觉得很多做嵌入式程序的工程师都不愿使用STL,喜欢用标准C,是有原因的,因为toolchain限制太大。很多提供toolchain的公司对类似STL的提供都不是很全。
Sam有一个C++程序,其中用到:
std::wstring mbstowcs(std::string str);
使用X5平台的编译器编译时,遇到如下问题:
error: expected constructor, destructor, or type conversion before "mbstowcs"
error: expected `,' or `;' before "mbstowcs"
Sam就开始寻找原因。顺便把gcc,glibc,uclibc,stdc++等东西的概念性东西放在这里。
GCC:gcc(gnu collect compiler)是一组编译工具的总称。它主要完成的工作任务是“预处理”和“编译”,以及提供了与编译器紧密相关的运行库的支持,如libgcc_s.so、libstdc++.so等。
glibc:glibc是gnu发布的libc库,也即c运行库。glibc是linux系统中最底层的api,几乎其它任何的运行库都会倚赖于glibc。glibc除了封装linux操作系统所提供的系统服务外,它本身也提供了许多其它一些必要功能服务的实现.
uclibc:uclibc是另一c运行库,与glibc对应。它比glibc小。虽然uClibc和Glibc在已有的接口上是兼容的,但有些接口并没有实现。
libstdc++:libstdc++ 是GNU C++ standard Library .
Sam觉得既然编译就通不过,那说明X5的编译器本身就不认识,有可能是X5平台的交叉编译器在创建时没有添加stdc支持?后来觉得不是这样,因为STL完全是以头文件形式提供的。
1.所以只需要指定头文件路径,就应该可以编译。于是Sam添加了:
-I/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include/c++/3.4.3/
(Sam觉得海思的交叉编译器做得不是特别规范,为什么呢。因为它放头文件,库文件的地点多变)
添加这个之后,理论上应该是可以编译了。
可发现还是不认识wstring.
2.于是查看/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include/c++/3.4.3/bits/stringfwd.h。
发现要定义_GLIBCXX_USE_WCHAR_T才会有wstring.
3.于是在编译程序时添加了 -D_GLIBCXX_USE_WCHAR_T
还是通不过,说没有wint_t。
4.于是又查STL. 添加了 -D__WINT_TYPE__
又通不过, 说没有 btowc,Sam查遍了toolchain. 也没找到这个类型的定义。感觉很奇怪,就去查/usr/include. 发现这个类型有定义。但toolchain中没有对应的头文件。
所以,Sam觉得很多做嵌入式程序的工程师都不愿使用STL,喜欢用标准C,是有原因的,因为toolchain限制太大。很多提供toolchain的公司对类似STL的提供都不是很全。
2009年3月19日星期四
scons 学习
作者:Sam(甄峰) sam_code@hotmail.com
http://www.scons.org/
Sam有个好朋友是做游戏的,整天嘲笑做嵌入式的用的工具多土多原始。gdb不是图形化,DDD又没VC调试好用,只会写Makefile等等。
刚好最近有需要使用scons。所以Sam准备学习之以回击这种挑衅。呵呵。
scons简介:
scons是一个Python写的自动化构建工具,从构建这个角度说,它跟GNU make是同一类的工具。
scons与其它工具最显著的差别就是:scons配置文件是Python script.
它的思想是跟GNU make完全不同的。GNU make的核心是“依赖关系”,我要做的事情,就是告诉系统,一个目标依赖什么东西,并且,当被依赖的东西发生变化时,我要做什么。
例如:
all: main.c
$(CC) $(CFLAGS) $(LFLAGS) main.c -o BT_remote -lbluetooth -lBluetooth_remote -lm -lpthread
意思是:all依赖于main.c。 如果main.c有了变化,作下面这些事。
这样做可以解决相当多的问题,但是也带来了一个最大的问题:我如何判别这个目标依赖什么?
对于一个两个,甚至十几个文件,我当然还比较容易搞清楚,谁依赖谁。但是当文件有成百上千个时,要分清楚谁依赖谁可就没这么容易了。尤其是C/C++头文件的依赖,如果手工分析的话,工程量可是不小。为了解决这个问题,GNU又提供了另外一套工具:Automake,使用程序来分析依赖性,然后辅助你产生makefile。
于是乎,就有人想了,既然如此,我干吗费那劲,用程序分析依赖性,然后生成一个文件,再交给另外一个程序去处理呢?既然依赖性需要用程序来分析,那么就直接交给构建工具本身去做不就好了吗?对的,这是一个非常自然的思路,于是,Java世界有了Ant,而Python世界有了scons。
scons就是这样一个构建工具:你告诉它要做的任务,以及完成这个任务需要的输入,以及这个任务产生的输出,怎么做这个任务(当然其中就包括依赖性分析),就交给工具本身完成。
例1:
只有一个 test.c文件,需要编译成可执行文件。
创建SConstruct,内容为:
Program('test.c')
这句话透露2层意思:
1.编译方法:想要编译成什么类型。(Program:可执行文件)
2.哪个东西想编译(test.c)
#scons
% scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
cc -o test.o -c test.c
cc -o test test.o
scons: done building targets.
clean:
#scons -c
则把编译出的东西又删除了,类似make clean
例2:
只有一个文件,想编译成object文件。
Object('test.c')
1.编译方法:想要编译成什么类型。(Object: .o文件)
2.哪个东西想编译(test.c)
#scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
cc -o hello.o -c hello.c
scons: done building targets.
清空:
#scons -c
则把编译出的东西又删除了,类似make clean
例3:
有多个.c文件,并想指定生成的程序名:
Program('program', ['prog.c', 'file1.c', 'file2.c'])
生成可执行文件 program, 由prog.c file1.c file2.c 生成。
#scons -Q
cc -o file1.o -c file1.c
cc -o file2.o -c file2.c
cc -o prog.o -c prog.c
cc -o program prog.o file1.o file2.o
甚至可以这样写:
common_sources = ['file1.c', 'file2.c']
Program('program2', common_sources + ['program2.c'])
例4:
有很多个.c文件,不想一个一个指定,类似Makefile中 *.o: *.c
Program('test', Glob('*.c'))
不管使用common_sources = ['file1.c', 'file2.c'] 还是Program('test1.c'....'testn.c')
每个文件名都需要被单或者双引号包起来。这太不方便了。
SCons 提供了Split函数
Program('program', Split('main.c file1.c file2.c'))
或:
src_files = Split('main.c file1.c file2.c')
Program('program', src_files)
例5:
SCons还对output file和source file有关键字,可以用来指定
src_files = Split('main.c file1.c file2.c')
Program(target = 'program', source = src_files)
因为有关键字指定,多以可以换前后:
Program(source = src_files, target = 'program')
例6:
创建库:
Library('foo', ['f1.c', 'f2.o', 'f3.c', 'f4.o'])
#scons -Q
cc -o f1.o -c f1.c
cc -o f3.o -c f3.c
ar rc libfoo.a f1.o f2.o f3.o f4.o
ranlib libfoo.a
例7:建立静态和动态库:
静态库:其实使用Library也是建立静态库。
StaticLibrary('foo', ['f1.c', 'f2.c', 'f3.c'])
动态库:
SharedLibrary('foo', ['f1.c', 'f2.c', 'f3.c'])
% scons -Q
cc -o f1.os -c f1.c
cc -o f2.os -c f2.c
cc -o f3.os -c f3.c
cc -o libfoo.so -shared f1.os f2.os f3.os
例8:使用静态和动态库:
使用$LIBS 来指定库,使用$LIBPATH 指定库位置。
Library('foo', ['f1.c', 'f2.c', 'f3.c'])
Program('prog.c', LIBS=['foo', 'bar'], LIBPATH='.')
% scons -Q
cc -o f1.o -c f1.c
cc -o f2.o -c f2.c
cc -o f3.o -c f3.c
ar rc libfoo.a f1.o f2.o f3.o
ranlib libfoo.a
cc -o prog.o -c prog.c
cc -o prog prog.o -L. -lfoo -lbar
Program('prog.c', LIBS = ['m'], LIBPATH = ['/usr/lib', '/usr/local/lib'])
% scons -Q
cc -o prog.o -c prog.c
cc -o prog prog.o -L/usr/lib -L/usr/local/lib -lm
以BTX为例使用scons:
Sam创建了一个SConstruct,(类似于GNU make的Makefile文件,是scons的默认文件名).
Sam首先想生成BTX library.
然后想将library 和 main.c和成一个可执行文件:BTX_Test
内容如下:
#指定生成文件名为:libBTX.a. 由BTX.c生成的。-I'/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include -I../include
StaticLibrary('BTX', ['BTX.c'], CPPPATH=['/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include', '../include'])
Program('BTX_Test', ['main.c'], CPPPATH=['/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include', '../include'], LIBS=['BTX','blu\etooth','pthread'], LIBPATH=['.', '/usr/lib'])
#scons
提示之不到bluetooth库,Sam在/usr/lib中将 libbluetooth.so 软连接到libbluetooth.so.2。
于是编译成功,所以,Sam觉得Scons和Makefile很类似,只需要编译时指定-I ,-L, -l就好了。对应关系如下:
-I:CPPPATH=['/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include', '../include']
-L:LIBPATH=['.', '/usr/lib']
-l: LIBS=['BTX','bluetooth','pthread']
CPPPATH,LIBPATH,LIBS,CC等从:
http://www.scons.org/doc/1.2.0/HTML/scons-user/a4774.html#cv-CPPPATH 中找到
同时请注意,最好在每个关键字赋值后面都跟 [],里面有多个时用逗号分开。
X5 Version:
StaticLibrary('BTX', ['BTX.c'], CC=['/opt/hisilicon/toolchains/arm-uclibc-linux-soft/bin/arm-uclibc-linux-gcc'], CPPPATH=['/opt/his\ilicon/toolchains/arm-uclibc-linux-soft/include', '../include'])
Program('BTX_Test', ['main.c'], CC=['/opt/hisilicon/toolchains/arm-uclibc-linux-soft/bin/arm-uclibc-linux-gcc'], CPPPATH=['/opt/his\ilicon/toolchains/arm-uclibc-linux-soft/include', '../include'], LIBS=['BTX','bluetooth','pthread'], LIBPATH=['./', '../resource/']\)
scons只需要描述任务,并不需要指定依赖关系,甚至我们都没有指出头文件,但是你修改BTX.h的时候仍然会触发构建。这是因为scons内部有个scanner,可以帮助扫描包含文件的关系。
我们还发现,这里我们根本没有指定编译器,也没有指定编译选项,但scons仍然很聪明的选择了gcc. 这是因为scons内置提供了很多编译器及其对应选项的选择,然后对于不同的平台,会有一个默认项。
Environments:
Scons有3种环境变量:
1. External Environment
2. Construction Environment
3. Execution Environment
当想要使用External Environment,必须通过os.environ。这意味着首先要加入:
import os
之后就可以在SConscript文件中使用os.environ来初始化construction environments 为用户环境变量了。
1.创建Construction Environment
env = Environment()
例如:
import os
env = Environment(CC = 'gcc', CCFLAGS = '-O2')
env.Program('foo.c')
最重要的环境变量是:Construction Environment
它在复杂的工程中非常重要,例如,不同的源文件需要不同的编译选项。或者不同的可执行程序需要链接不同的库。scons提供Construction Environment。让我们可以创建不同的Construction Environment来创建不同的编译选项。
1. 创建Construction Environment ,使用Environment function。
env = Environment() 所有的Construction Environment 项目值全用scons自己找到的。
env = Environment(CC = 'gcc', CCFLAGS = '-O2') ,其他的与上一个相同,但只有CC和CCFLAGS 自定义。
2. 察看Construction Environment 每个项目值:注[1]
需要使用Dictionary() function
env = Environment()
dict = env.Dictionary()
keys = dict.keys()
keys.sort()
for key in keys:
print "construction variable = '%s', value = '%s'" % (key, dict[key])
打印所有Construction Environment项目。
scons中Python模块的导入:
1. import os
os模块包含普遍的操作系统功能。如果你希望你的程序能够与平台无关的话,这个模块是尤为重要的。即它允许一个程序在编写后不需要任何改动,也不会发生任何问题,就可以在Linux和Windows下运行。一个例子就是使用os.sep可以取代操作系统特定的路径分割符。
os.name字符串指示你正在使用的平台。比如对于Windows,它是'nt',而对于Linux/Unix用户,它是'posix'。
os.getcwd()函数得到当前工作目录,即当前Python脚本工作的目录路径。
os.getenv()和os.putenv()函数分别用来读取和设置环境变量。
os.listdir()返回指定目录下的所有文件和目录名。
os.remove()函数用来删除一个文件。
os.system()函数用来运行shell命令。
os.linesep字符串给出当前平台使用的行终止符。例如,Windows使用'\r\n',Linux使用'\n'而Mac使用'\r'。
os.path.split()函数返回一个路径的目录名和文件名。
os.environ Shell variables
这里重点说os.environ:
它应该是Linux环境变量。
例1:
想要得到当前Linux环境变量PATH,LD_LIBRARY_PATH,HOME等。(这与前面讲的环境变量对应起来了)
#导入OS模块
import os
#创建一个Construction Environment
env=Environment()
print "HOME: ", os.environ["HOME"]
则打印结果为HOME:/home/sam。这样就得到了Linux环境变量。
例2:
想要用当前Linux环境变量ARMGCC来设置toolchain.
首先:在Linux下:
export ARMGCC=arm-linux-gcc
之后,修改config
#导入OS模块
import os
#创建一个Construction Environment
env=Environment()
if os.environ.has_key('ARMGCC'):
env.Replace(CC = os.environ['ARMGCC'])
则使用arm-linux-gcc取代了Construction Environment中的CC。
2. import sys
sys模块包含系统对应的功能.sys模块包含了与Python解释器和它的环境有关的函数.
import sys
if sys.byteorder == 'little' or env['PLATFORM'] == 'win32':
env.Append(CPPDEFINES = ['LSB_FIRST'])
3. import platform
platform得到编译信息:
platform.processor() ---i686
platform.python_build()---('r251:54863', 'Jun 4 2007 15:03:52')
platform.python_compiler()----'GCC 3.4.4 20050721 (Red Hat 3.4.4-2)'
platform.python_version()----'2.5.1'
platform.release()----'2.6.9-22.ELsmp'
platform.system()----'Linux'
platform.version()----'#1 SMP Mon Sep 19 18:32:14 EDT 2005'
platform.uname()----('Linux', 'nec', '2.6.9-22.ELsmp', '#1 SMP Mon Sep 19 18:32:14 EDT 2005', 'i686' , 'i686')
platform.machine()----'i686'
platform.libc_ver()----('glibc', '2.0')
注[1]:
所有Construction Variables可以在下面找到:
Construction Variables可用在:
1. CPPPATH,LIBS,LIBPATH。(在BTX例子中可见)
2. Construction Environment(env=Environment)中的项目。
Appendix A. Construction Variables
http://www.scons.org/
Sam有个好朋友是做游戏的,整天嘲笑做嵌入式的用的工具多土多原始。gdb不是图形化,DDD又没VC调试好用,只会写Makefile等等。
刚好最近有需要使用scons。所以Sam准备学习之以回击这种挑衅。呵呵。
scons简介:
scons是一个Python写的自动化构建工具,从构建这个角度说,它跟GNU make是同一类的工具。
scons与其它工具最显著的差别就是:scons配置文件是Python script.
它的思想是跟GNU make完全不同的。GNU make的核心是“依赖关系”,我要做的事情,就是告诉系统,一个目标依赖什么东西,并且,当被依赖的东西发生变化时,我要做什么。
例如:
all: main.c
$(CC) $(CFLAGS) $(LFLAGS) main.c -o BT_remote -lbluetooth -lBluetooth_remote -lm -lpthread
意思是:all依赖于main.c。 如果main.c有了变化,作下面这些事。
这样做可以解决相当多的问题,但是也带来了一个最大的问题:我如何判别这个目标依赖什么?
对于一个两个,甚至十几个文件,我当然还比较容易搞清楚,谁依赖谁。但是当文件有成百上千个时,要分清楚谁依赖谁可就没这么容易了。尤其是C/C++头文件的依赖,如果手工分析的话,工程量可是不小。为了解决这个问题,GNU又提供了另外一套工具:Automake,使用程序来分析依赖性,然后辅助你产生makefile。
于是乎,就有人想了,既然如此,我干吗费那劲,用程序分析依赖性,然后生成一个文件,再交给另外一个程序去处理呢?既然依赖性需要用程序来分析,那么就直接交给构建工具本身去做不就好了吗?对的,这是一个非常自然的思路,于是,Java世界有了Ant,而Python世界有了scons。
scons就是这样一个构建工具:你告诉它要做的任务,以及完成这个任务需要的输入,以及这个任务产生的输出,怎么做这个任务(当然其中就包括依赖性分析),就交给工具本身完成。
例1:
只有一个 test.c文件,需要编译成可执行文件。
创建SConstruct,内容为:
Program('test.c')
这句话透露2层意思:
1.编译方法:想要编译成什么类型。(Program:可执行文件)
2.哪个东西想编译(test.c)
#scons
% scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
cc -o test.o -c test.c
cc -o test test.o
scons: done building targets.
clean:
#scons -c
则把编译出的东西又删除了,类似make clean
例2:
只有一个文件,想编译成object文件。
Object('test.c')
1.编译方法:想要编译成什么类型。(Object: .o文件)
2.哪个东西想编译(test.c)
#scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
cc -o hello.o -c hello.c
scons: done building targets.
清空:
#scons -c
则把编译出的东西又删除了,类似make clean
例3:
有多个.c文件,并想指定生成的程序名:
Program('program', ['prog.c', 'file1.c', 'file2.c'])
生成可执行文件 program, 由prog.c file1.c file2.c 生成。
#scons -Q
cc -o file1.o -c file1.c
cc -o file2.o -c file2.c
cc -o prog.o -c prog.c
cc -o program prog.o file1.o file2.o
甚至可以这样写:
common_sources = ['file1.c', 'file2.c']
Program('program2', common_sources + ['program2.c'])
例4:
有很多个.c文件,不想一个一个指定,类似Makefile中 *.o: *.c
Program('test', Glob('*.c'))
不管使用common_sources = ['file1.c', 'file2.c'] 还是Program('test1.c'....'testn.c')
每个文件名都需要被单或者双引号包起来。这太不方便了。
SCons 提供了Split函数
Program('program', Split('main.c file1.c file2.c'))
或:
src_files = Split('main.c file1.c file2.c')
Program('program', src_files)
例5:
SCons还对output file和source file有关键字,可以用来指定
src_files = Split('main.c file1.c file2.c')
Program(target = 'program', source = src_files)
因为有关键字指定,多以可以换前后:
Program(source = src_files, target = 'program')
例6:
创建库:
Library('foo', ['f1.c', 'f2.o', 'f3.c', 'f4.o'])
#scons -Q
cc -o f1.o -c f1.c
cc -o f3.o -c f3.c
ar rc libfoo.a f1.o f2.o f3.o f4.o
ranlib libfoo.a
例7:建立静态和动态库:
静态库:其实使用Library也是建立静态库。
StaticLibrary('foo', ['f1.c', 'f2.c', 'f3.c'])
动态库:
SharedLibrary('foo', ['f1.c', 'f2.c', 'f3.c'])
% scons -Q
cc -o f1.os -c f1.c
cc -o f2.os -c f2.c
cc -o f3.os -c f3.c
cc -o libfoo.so -shared f1.os f2.os f3.os
例8:使用静态和动态库:
使用$LIBS 来指定库,使用$LIBPATH 指定库位置。
Library('foo', ['f1.c', 'f2.c', 'f3.c'])
Program('prog.c', LIBS=['foo', 'bar'], LIBPATH='.')
% scons -Q
cc -o f1.o -c f1.c
cc -o f2.o -c f2.c
cc -o f3.o -c f3.c
ar rc libfoo.a f1.o f2.o f3.o
ranlib libfoo.a
cc -o prog.o -c prog.c
cc -o prog prog.o -L. -lfoo -lbar
Program('prog.c', LIBS = ['m'], LIBPATH = ['/usr/lib', '/usr/local/lib'])
% scons -Q
cc -o prog.o -c prog.c
cc -o prog prog.o -L/usr/lib -L/usr/local/lib -lm
以BTX为例使用scons:
Sam创建了一个SConstruct,(类似于GNU make的Makefile文件,是scons的默认文件名).
Sam首先想生成BTX library.
然后想将library 和 main.c和成一个可执行文件:BTX_Test
内容如下:
#指定生成文件名为:libBTX.a. 由BTX.c生成的。-I'/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include -I../include
StaticLibrary('BTX', ['BTX.c'], CPPPATH=['/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include', '../include'])
Program('BTX_Test', ['main.c'], CPPPATH=['/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include', '../include'], LIBS=['BTX','blu\etooth','pthread'], LIBPATH=['.', '/usr/lib'])
#scons
提示之不到bluetooth库,Sam在/usr/lib中将 libbluetooth.so 软连接到libbluetooth.so.2。
于是编译成功,所以,Sam觉得Scons和Makefile很类似,只需要编译时指定-I ,-L, -l就好了。对应关系如下:
-I:CPPPATH=['/opt/hisilicon/toolchains/arm-uclibc-linux-soft/include', '../include']
-L:LIBPATH=['.', '/usr/lib']
-l: LIBS=['BTX','bluetooth','pthread']
CPPPATH,LIBPATH,LIBS,CC等从:
http://www.scons.org/doc/1.2.0/HTML/scons-user/a4774.html#cv-CPPPATH 中找到
同时请注意,最好在每个关键字赋值后面都跟 [],里面有多个时用逗号分开。
X5 Version:
StaticLibrary('BTX', ['BTX.c'], CC=['/opt/hisilicon/toolchains/arm-uclibc-linux-soft/bin/arm-uclibc-linux-gcc'], CPPPATH=['/opt/his\ilicon/toolchains/arm-uclibc-linux-soft/include', '../include'])
Program('BTX_Test', ['main.c'], CC=['/opt/hisilicon/toolchains/arm-uclibc-linux-soft/bin/arm-uclibc-linux-gcc'], CPPPATH=['/opt/his\ilicon/toolchains/arm-uclibc-linux-soft/include', '../include'], LIBS=['BTX','bluetooth','pthread'], LIBPATH=['./', '../resource/']\)
scons只需要描述任务,并不需要指定依赖关系,甚至我们都没有指出头文件,但是你修改BTX.h的时候仍然会触发构建。这是因为scons内部有个scanner,可以帮助扫描包含文件的关系。
我们还发现,这里我们根本没有指定编译器,也没有指定编译选项,但scons仍然很聪明的选择了gcc. 这是因为scons内置提供了很多编译器及其对应选项的选择,然后对于不同的平台,会有一个默认项。
Environments:
Scons有3种环境变量:
1. External Environment
2. Construction Environment
3. Execution Environment
当想要使用External Environment,必须通过os.environ。这意味着首先要加入:
import os
之后就可以在SConscript文件中使用os.environ来初始化construction environments 为用户环境变量了。
1.创建Construction Environment
env = Environment()
例如:
import os
env = Environment(CC = 'gcc', CCFLAGS = '-O2')
env.Program('foo.c')
最重要的环境变量是:Construction Environment
它在复杂的工程中非常重要,例如,不同的源文件需要不同的编译选项。或者不同的可执行程序需要链接不同的库。scons提供Construction Environment。让我们可以创建不同的Construction Environment来创建不同的编译选项。
1. 创建Construction Environment ,使用Environment function。
env = Environment() 所有的Construction Environment 项目值全用scons自己找到的。
env = Environment(CC = 'gcc', CCFLAGS = '-O2') ,其他的与上一个相同,但只有CC和CCFLAGS 自定义。
2. 察看Construction Environment 每个项目值:注[1]
需要使用Dictionary() function
env = Environment()
dict = env.Dictionary()
keys = dict.keys()
keys.sort()
for key in keys:
print "construction variable = '%s', value = '%s'" % (key, dict[key])
打印所有Construction Environment项目。
scons中Python模块的导入:
1. import os
os模块包含普遍的操作系统功能。如果你希望你的程序能够与平台无关的话,这个模块是尤为重要的。即它允许一个程序在编写后不需要任何改动,也不会发生任何问题,就可以在Linux和Windows下运行。一个例子就是使用os.sep可以取代操作系统特定的路径分割符。
os.name字符串指示你正在使用的平台。比如对于Windows,它是'nt',而对于Linux/Unix用户,它是'posix'。
os.getcwd()函数得到当前工作目录,即当前Python脚本工作的目录路径。
os.getenv()和os.putenv()函数分别用来读取和设置环境变量。
os.listdir()返回指定目录下的所有文件和目录名。
os.remove()函数用来删除一个文件。
os.system()函数用来运行shell命令。
os.linesep字符串给出当前平台使用的行终止符。例如,Windows使用'\r\n',Linux使用'\n'而Mac使用'\r'。
os.path.split()函数返回一个路径的目录名和文件名。
os.environ Shell variables
这里重点说os.environ:
它应该是Linux环境变量。
例1:
想要得到当前Linux环境变量PATH,LD_LIBRARY_PATH,HOME等。(这与前面讲的环境变量对应起来了)
#导入OS模块
import os
#创建一个Construction Environment
env=Environment()
print "HOME: ", os.environ["HOME"]
则打印结果为HOME:/home/sam。这样就得到了Linux环境变量。
例2:
想要用当前Linux环境变量ARMGCC来设置toolchain.
首先:在Linux下:
export ARMGCC=arm-linux-gcc
之后,修改config
#导入OS模块
import os
#创建一个Construction Environment
env=Environment()
if os.environ.has_key('ARMGCC'):
env.Replace(CC = os.environ['ARMGCC'])
则使用arm-linux-gcc取代了Construction Environment中的CC。
2. import sys
sys模块包含系统对应的功能.sys模块包含了与Python解释器和它的环境有关的函数.
import sys
if sys.byteorder == 'little' or env['PLATFORM'] == 'win32':
env.Append(CPPDEFINES = ['LSB_FIRST'])
3. import platform
platform得到编译信息:
platform.processor() ---i686
platform.python_build()---('r251:54863', 'Jun 4 2007 15:03:52')
platform.python_compiler()----'GCC 3.4.4 20050721 (Red Hat 3.4.4-2)'
platform.python_version()----'2.5.1'
platform.release()----'2.6.9-22.ELsmp'
platform.system()----'Linux'
platform.version()----'#1 SMP Mon Sep 19 18:32:14 EDT 2005'
platform.uname()----('Linux', 'nec', '2.6.9-22.ELsmp', '#1 SMP Mon Sep 19 18:32:14 EDT 2005', 'i686' , 'i686')
platform.machine()----'i686'
platform.libc_ver()----('glibc', '2.0')
注[1]:
所有Construction Variables可以在下面找到:
Construction Variables可用在:
1. CPPPATH,LIBS,LIBPATH。(在BTX例子中可见)
2. Construction Environment(env=Environment)中的项目。
Appendix A. Construction Variables
2009年3月11日星期三
Linux下Framebuffer学习

作者:Sam(甄峰) sam_code@hotmail.com
Sam之前写过vfb(虚拟framebuffer) driver. 但现在什么都不记得了。 现在需要在X5平台上使用framebuufer. 只好再学习一次。
零:Framebuffer 简介:
Frame Buffer设备为图形硬件提供抽象。它表示一些视频硬件的Frame Buffer,允许应用软件通过定义好的接口访问图形硬件,所以这些软件不必知道任何关于低层(硬件寄存器)的知识。
使用frame buffer设备的应用程序(例如 X server)默认使用/dev/fb0(老一点的软件使用 /dev/fb0current)。你可以通过设定环境变量$FRAMEBUFFER等于frame buffer设备的路径名来改变frame buffer设备,例如(针对 sh/bash用户):export FRAMEBUFFER=/dev/fb1或者(针对 csh 用户):setenv FRAMEBUFFER /dev/fb1之后,X server将使用第二个frame buffer。
大家都已经知道,frame buffer设备是一个内存设备,就像/dev/mem, 所以他拥有相同的特征。你可以读他,写他,在他里面定位和mmap他(主要用途)。
/dev/fb*也允许在他之上做一些ioctls操作,通过这种方法硬件的许多信息可以获取和设置。颜色映射处理(the color map handling)也通过ioctls工作。
- 你可以取得硬件的不可更改信息,例如名字,屏幕内存组织方式(位面,象素格式,……)和地址和屏幕内存的长度。
- 你可以获取和修改硬件的可变信息,例如可见,虚拟几何,深度,颜色映射格式,定时,等等。如果你试图修改这些信息,驱动可能规整一些值以适应硬件的能力(或者返回EINVAL 如果这个操作不可能) (译注:the driver maybe will round up some values to meet the hardware's capabilities)
- 你可以取得和设置部分颜色映射。通信使用16位每一颜色(红,绿,蓝,透明)来支持所有存在的硬件。驱动处理所有应用于硬件的计算(截取他们到较少的位,可能会舍弃透明)。
视频模式计时:(非常重要)
显示器使用电子束(彩色模式使用3个电子束,单色模式使用1个电子束)绘制屏幕上的图像。屏幕的前面被颜色荧光(象素)覆盖.如果荧光被电子击中,它就会发出光子从而可见.
电子束从左至右绘制水平行,从上到下绘制整个屏幕.通过改变电子束的亮度,便可以显示各种颜色和亮度的像素
每条扫描线完成后电子束必须重新移回到下一行的左端:这称做水平回扫.等到完成整个屏幕(帧)的绘制,电子束会移回到左上方点:这被称做垂直回扫.在水平和垂直回扫期间,电子束被关闭(空).
电子束绘制像素的速度由图形板的时钟决定(译注:dotclock).
例如:时钟是28.37516MHZ(每秒百万周期)。也就是说每秒可以绘制28.37516M像素。
每像素需35242 ps(微秒):1/(28.37516E6 HZ) = 35.242E-9 s
如果屏幕的分辨率是640x480, 将需要640*35.242E-9 s = 22.555E-6 s来绘制一条扫描线上640(xres)个像素.但是水平回扫也需要时间(例如 272 像素), 所以一条完整的水平扫描线需要:(640+272)*35.242E-9 s = 32.141E-6 s
我们称水平刷新率约是31 kHz:1/(32.141E-6 s) = 31.113E3 Hz
整个屏幕有480(yres)条扫描线,但是我们必须还要考虑垂直回扫(例如 49 条扫描线). 所以完整的一屏需要:(480+49)*32.141E-6 s = 17.002E-3 s垂直刷新率约是 59 Hz:1/(17.002E-3 s) = 48.815 Hz这意味着屏幕上的数据每秒钟刷新约59。
从最上面的图可以看出:
水平回扫时间是左边缘(left_margin),右边缘(right_margin)和 hsync 长度(hsync_len)的总和. 而垂直回扫时间是上边缘(upper_margin),下边缘(lower_margin)和 vsync长度(vsync_len)的总和。这些值与fb_var_screeninfo结构体中的项目对应。
frame buffer设备期待所有的水平计时以时钟(以picoseconds, 1E-12 s)为单位,垂直计时则以扫描线数目为单位.
pixclock(以ps为单位的像素时钟)
- pixclock: 以ps为单位的像素时钟(pico seconds)
- left_margin: 从sync到画面的时间
- right_margin: 从画面到sync的时间
- upper_margin: 从sync到画面的时间
-lower_margin: 从画面到sync的时间
- hsync_len: 水平sync的长度
- vsync_len: 垂直sync的长度
一. 基本使用方法:
(之后再补上)
二. 最重要的结构体:
fb_var_screeninfo
简单的项如下:
__u32 xres; /* visible resolution */
__u32 yres; //可见部分的长宽
__u32 xres_virtual; /* virtual resolution */
__u32 yres_virtual; //虚拟部分的长宽
__u32 xoffset; /* offset from virtual to visible */ //可见和虚拟部分的offset
__u32 yoffset; /* resolution */
__u32 bits_per_pixel; //每个象素占多少位
__u32 grayscale; //不等于0的话,灰度
struct fb_bitfield red; /* bitfield in fb mem if true color, */
struct fb_bitfield green; /* else only length is significant */
struct fb_bitfield blue;
struct fb_bitfield transp; /* transparency */
fb_bitfield:结构很重要:
struct fb_bitfield
{
__u32 offset; /* beginning of bitfield */
__u32 length; /* length of bitfield */
__u32 msb_right; /* != 0 : Most significant bit is */
};
像素详解:
以上的结构其实与像素有关,这个结构是说:这种色值(RGB或者transp=alpha)在一个像素点结构中的offset,length 。
---------------- 一个像素点
R G B a
这样,上面这个结构就明白了吧。
其它机构的含义,请看上面的视频模式部分。呵呵
2009年2月23日星期一
嵌入式系统中的usleep延迟
作者:Sam(甄峰) sam_code@hotmail.com
Sam在作嵌入式系统中,发现类似usleep这样的函数在不同平台上延迟并不相同。所以如果想要延迟一段时间,尤其是比较精确的延迟,应慎重使用类似函数。
举例而言:
例1:
usleep(5000); //延迟5ms.
在PC-linux中,延迟时间大约在5020-5030微秒之间。
在Canmore(800M CPU)中,延迟则会大很多, 在8010微秒左右。
在X5平台(300M CPU)中, 延迟竟然大到20000微秒左右。
当然,这不排除Sam使用gettimeofday() 函数所用时间,但可以肯定的是,在不同CPU速度下,usleep() 延迟造成的效果应该差别很大。
例2:
usleep(50); //延迟50微秒
在PC-linux中,延迟时间大约在59-60微秒之间。
在Canmore(800M CPU)中,延迟则会大很多, 在4000微秒左右。
在X5平台(300M CPU)中, 延迟竟然大到20000微秒左右。
例3:
usleep(50000); //延迟50000微秒
在PC-linux中,延迟时间大约在50076微秒左右。
在Canmore(800M CPU)中,延迟则会大很多, 在56047微秒左右。
在X5平台(300M CPU)中, 延迟到60000微秒左右。
从上面的数据看,在嵌入式系统中,最好不用时用usleep这类函数,因为他们特别不准确。当延迟比较大时,稍微准确一些,但延迟小到一定程度时,延迟会非常不准确。好像有一个延迟最低点。低于这个数据后,不管你想要延迟多少,系统都会延迟这么久。例如X5平台的20000微秒。
Sam在作嵌入式系统中,发现类似usleep这样的函数在不同平台上延迟并不相同。所以如果想要延迟一段时间,尤其是比较精确的延迟,应慎重使用类似函数。
举例而言:
例1:
usleep(5000); //延迟5ms.
在PC-linux中,延迟时间大约在5020-5030微秒之间。
在Canmore(800M CPU)中,延迟则会大很多, 在8010微秒左右。
在X5平台(300M CPU)中, 延迟竟然大到20000微秒左右。
当然,这不排除Sam使用gettimeofday() 函数所用时间,但可以肯定的是,在不同CPU速度下,usleep() 延迟造成的效果应该差别很大。
例2:
usleep(50); //延迟50微秒
在PC-linux中,延迟时间大约在59-60微秒之间。
在Canmore(800M CPU)中,延迟则会大很多, 在4000微秒左右。
在X5平台(300M CPU)中, 延迟竟然大到20000微秒左右。
例3:
usleep(50000); //延迟50000微秒
在PC-linux中,延迟时间大约在50076微秒左右。
在Canmore(800M CPU)中,延迟则会大很多, 在56047微秒左右。
在X5平台(300M CPU)中, 延迟到60000微秒左右。
从上面的数据看,在嵌入式系统中,最好不用时用usleep这类函数,因为他们特别不准确。当延迟比较大时,稍微准确一些,但延迟小到一定程度时,延迟会非常不准确。好像有一个延迟最低点。低于这个数据后,不管你想要延迟多少,系统都会延迟这么久。例如X5平台的20000微秒。
2009年2月4日星期三
GDB学习及嵌入式下如何使用Debug
作者: Sam(甄峰) sam_code@hotmail.com
前段时间和朋友聊天,谈到工作中使用什么调试工具,Sam很不好意思地讲,我只用printf打印信息调试,被狠狠地嘲笑了一通。Sam一直作嵌入式程序或kernel层程序,在这个领域内,用调试工具的人好像还真不算多。但Sam很认同朋友的话,调试工具能够极大的提高效率。所以决定借当前的移植工作把GDB用起来。
GDB(GNU symbolic debugger)
GDB可以让你调试一个程序,包括让程序在你希望的地方停下,此时你可以查看变量,寄存器,内存及堆栈。更进一步你可以修改变量及内存值。
Sam首先用BT_remote来做GDB试验。
1. 编译时需要加入选项 -g
2. 使用GDB 启动程序。
#gdb BT_remote
GNU gdb Red Hat Linux (6.6-35.fc8rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you arewelcome to change it and/or distribute copies of it under certain conditions.Type "show copying" to see the conditions.There is absolutely no warranty for GDB. Type "show warranty" for details.This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) l //相当于list,列出代码
166 int main(int argc, char** argv)
167 {
168 BOOL bDongle_Ready = FALSE;
169 BTRC_Rel BTRC_Rel_all;
170 int iRet_pthread_create = 0;
171 pthread_t ntid;
172 UINT8 ID;
173 UINT16 SetAudioFile_rel;
174 int i = 0;
175 BT_CONNECTION_STATUS status;
176
177
178
179 BTRC_Rel_all = BluetoothDongleInit();
180 if(BTRC_Rel_all != BTRC_SUCCESS)
181 {
182 printf("\nBluetooth Dongle Init fail. Error Code:[%d]\n", BTRC_Rel_all);
183 return -1;
184 }
(gdb) break 169 //在168行设置断点
(gdb) break BluetoothDongleInit //在函数BluetoothDongleInit处加断点
(gdb) info break //察看断点
Num Type Disp Enb Address What
1 breakpoint keep yBluetoothDongleInit
2 breakpoint keep y 0x0804925c in main at main.c:169
(gdb)clear169 //清除Line 169断点
(gdb)watch iBluetooth_Rel_all //将变量设为观察点,当此变量有变化时,程序立刻停住
(gdb)info watchpoints //显示观察点
(gdb) r //run,开始run程序
到了第一个断点,则停下来,可以用 l 命令看其前后代码,也可以用p 来看某个变量的值
(gdb) p bDongle_Ready
$1 = FALSE
(gdb) n //next
(gdb) c //继续执行
主题二:
嵌入式下如何使用GDB:
1. 下载GDB.
http://ftp.gnu.org/gnu/gdb/
2. 编译:
(注意:因为此gdbserver是为Canmore使用的,所以不需要类似:CC=arm-linux-gcc ./configure --host=.... --target==... 这样的东西,如果是其它ARM平台,则需要这样做)
2.1 编译GDB
#./configure
#make clean;make
2.2 编译gdbserver
#cd gdb/gdbserver
#./configure
#make clean;make
将生成的gdbserver放到嵌入式板子上去。
3. copy 所需库:
将libthread_db copy到嵌入式平台。
4. 使用
4.1在嵌入式平台:
#gdbserver 172.16.1.61:1234 BT_remote
Process BT_remote created; pid = 716
Listening on port 1234
这个意思是:debug BT_remote这个程序。对方是172.16.1.61. 端口为:1234。
4.2在Host 端:
#gdb BT_remote
GNU gdb Red Hat Linux (6.6-35.fc8rh)Copyright (C) 2006 Free Software Foundation, Inc.GDB is free software, covered by the GNU General Public License, and you arewelcome to change it and/or distribute copies of it under certain conditions.Type "show copying" to see the conditions.There is absolutely no warranty for GDB. Type "show warranty" for details.This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) target remote 172.16.1.31:1234
Undefined target command: "172.16.1.31:1234 ". Try "help target".(gdb) target remote 172.16.1.31:1234`system-supplied DSO at 0x110000' has disappeared; keeping its symbols.Remote debugging using 172.16.1.31:1234[New Thread 716]
warning: Missing the separate debug info file: /usr/lib/debug/.build-id/ac/2eeb206486bb7315d6ac4cd64de0cb50838ff6.debug0xb7f17810 in _start () from /lib/ld-linux.so.2
此时发现嵌入式板子上有反应:
Remote debugging from host 172.16.1.61
则表明嵌入式板子和Host Debug链接成功,则可以在Host上调试嵌入式板子上的程序了。
但请注意,在Host上,不能使用 r 来运行,而应该使用 c来运行,因为这个程序已经在运行了。
则可以正常使用了。
嵌入式平台上GDB调试实例:
Sam想在X5平台上使用GDB来调试程序。
分析:
1. 需要gdbserver
2. 客户端需要gdb程序。
3. 需要重新编译,加入 -g.
1.编译gdbserver:
#cd gdb-6.8/gdb/gdbserver
#CC=/opt/hisilicon/toolchains/arm-uclibc-linux-soft/bin/arm-uclibc-linux-gcc ./configure --host=arm-linux
#make clean;make all
则gdbserver生成成功。Sam把它copy到板子的/bin目录中
2. 客户端gdb程序。
直接使用交叉编译器中的arm-uclibc-linux-gdb
3.重新编译程序。
Sam是想调试SDL例子程序,所以在SDL库生成过程和test生成过程中均加入 -g.
实际操作:
1. 嵌入式板子中:
#gdbserver 172.16.1.61:1234 testsprite
2. Host
2.1
#arm-uclibc-linux-gdb testsprite
2.2
(gdb) target remote 172.16.1.45:1234
2.3
(gdb) c
之后和本地调试类似。
前段时间和朋友聊天,谈到工作中使用什么调试工具,Sam很不好意思地讲,我只用printf打印信息调试,被狠狠地嘲笑了一通。Sam一直作嵌入式程序或kernel层程序,在这个领域内,用调试工具的人好像还真不算多。但Sam很认同朋友的话,调试工具能够极大的提高效率。所以决定借当前的移植工作把GDB用起来。
GDB(GNU symbolic debugger)
GDB可以让你调试一个程序,包括让程序在你希望的地方停下,此时你可以查看变量,寄存器,内存及堆栈。更进一步你可以修改变量及内存值。
Sam首先用BT_remote来做GDB试验。
1. 编译时需要加入选项 -g
2. 使用GDB 启动程序。
#gdb BT_remote
GNU gdb Red Hat Linux (6.6-35.fc8rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you arewelcome to change it and/or distribute copies of it under certain conditions.Type "show copying" to see the conditions.There is absolutely no warranty for GDB. Type "show warranty" for details.This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) l //相当于list,列出代码
166 int main(int argc, char** argv)
167 {
168 BOOL bDongle_Ready = FALSE;
169 BTRC_Rel BTRC_Rel_all;
170 int iRet_pthread_create = 0;
171 pthread_t ntid;
172 UINT8 ID;
173 UINT16 SetAudioFile_rel;
174 int i = 0;
175 BT_CONNECTION_STATUS status;
176
177
178
179 BTRC_Rel_all = BluetoothDongleInit();
180 if(BTRC_Rel_all != BTRC_SUCCESS)
181 {
182 printf("\nBluetooth Dongle Init fail. Error Code:[%d]\n", BTRC_Rel_all);
183 return -1;
184 }
(gdb) break 169 //在168行设置断点
(gdb) break BluetoothDongleInit //在函数BluetoothDongleInit处加断点
(gdb) info break //察看断点
Num Type Disp Enb Address What
1 breakpoint keep y
2 breakpoint keep y 0x0804925c in main at main.c:169
(gdb)clear169 //清除Line 169断点
(gdb)watch iBluetooth_Rel_all //将变量设为观察点,当此变量有变化时,程序立刻停住
(gdb)info watchpoints //显示观察点
(gdb) r //run,开始run程序
到了第一个断点,则停下来,可以用 l 命令看其前后代码,也可以用p 来看某个变量的值
(gdb) p bDongle_Ready
$1 = FALSE
(gdb) n //next
(gdb) c //继续执行
主题二:
嵌入式下如何使用GDB:
1. 下载GDB.
http://ftp.gnu.org/gnu/gdb/
2. 编译:
(注意:因为此gdbserver是为Canmore使用的,所以不需要类似:CC=arm-linux-gcc ./configure --host=.... --target==... 这样的东西,如果是其它ARM平台,则需要这样做)
2.1 编译GDB
#./configure
#make clean;make
2.2 编译gdbserver
#cd gdb/gdbserver
#./configure
#make clean;make
将生成的gdbserver放到嵌入式板子上去。
3. copy 所需库:
将libthread_db copy到嵌入式平台。
4. 使用
4.1在嵌入式平台:
#gdbserver 172.16.1.61:1234 BT_remote
Process BT_remote created; pid = 716
Listening on port 1234
这个意思是:debug BT_remote这个程序。对方是172.16.1.61. 端口为:1234。
4.2在Host 端:
#gdb BT_remote
GNU gdb Red Hat Linux (6.6-35.fc8rh)Copyright (C) 2006 Free Software Foundation, Inc.GDB is free software, covered by the GNU General Public License, and you arewelcome to change it and/or distribute copies of it under certain conditions.Type "show copying" to see the conditions.There is absolutely no warranty for GDB. Type "show warranty" for details.This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) target remote 172.16.1.31:1234
Undefined target command: "172.16.1.31:1234 ". Try "help target".(gdb) target remote 172.16.1.31:1234`system-supplied DSO at 0x110000' has disappeared; keeping its symbols.Remote debugging using 172.16.1.31:1234[New Thread 716]
warning: Missing the separate debug info file: /usr/lib/debug/.build-id/ac/2eeb206486bb7315d6ac4cd64de0cb50838ff6.debug0xb7f17810 in _start () from /lib/ld-linux.so.2
此时发现嵌入式板子上有反应:
Remote debugging from host 172.16.1.61
则表明嵌入式板子和Host Debug链接成功,则可以在Host上调试嵌入式板子上的程序了。
但请注意,在Host上,不能使用 r 来运行,而应该使用 c来运行,因为这个程序已经在运行了。
则可以正常使用了。
嵌入式平台上GDB调试实例:
Sam想在X5平台上使用GDB来调试程序。
分析:
1. 需要gdbserver
2. 客户端需要gdb程序。
3. 需要重新编译,加入 -g.
1.编译gdbserver:
#cd gdb-6.8/gdb/gdbserver
#CC=/opt/hisilicon/toolchains/arm-uclibc-linux-soft/bin/arm-uclibc-linux-gcc ./configure --host=arm-linux
#make clean;make all
则gdbserver生成成功。Sam把它copy到板子的/bin目录中
2. 客户端gdb程序。
直接使用交叉编译器中的arm-uclibc-linux-gdb
3.重新编译程序。
Sam是想调试SDL例子程序,所以在SDL库生成过程和test生成过程中均加入 -g.
实际操作:
1. 嵌入式板子中:
#gdbserver 172.16.1.61:1234 testsprite
2. Host
2.1
#arm-uclibc-linux-gdb testsprite
2.2
(gdb) target remote 172.16.1.45:1234
2.3
(gdb) c
之后和本地调试类似。
2009年1月3日星期六
Linux下显卡驱动的安装
作者:Sam(甄峰) sam_code@hotmail.com
Sam拿到一台MinPC.
Intel 965系列芯片组,INTEL965是其为Conroe处理器量身定做的芯片组。Intel 965系列家族的研发代号为Broadwater,主要包括了以下四款系列芯片,它们分别是P965、G965、Q963和Q965。其中P965和G965面向主流消费者;Q963和Q965则主要面向企业用户,用来构建Averill平台。
主板是Intel集成芯片组G965,集成的显卡就是GMA X3000或X3100。
现在Sam想在Fedora8上安装此显卡驱动。
Sam在网上查到此驱动包含在xf86-video-intel中,于是在http://lists.freedesktop.org/archives/xorg/2008-April/034611.html 中下载了此包。
但Sam在./configure的时候,pkg-cofig提示找不到xorg-server. Sam也在/usr/lib/pkgconfig/中确实没找到xorg-server.pc 。
之后运行 #yum install xorg-x11-server-sdk .然后发现xorg-server.pc被建立。
Sam拿到一台MinPC.
Intel 965系列芯片组,INTEL965是其为Conroe处理器量身定做的芯片组。Intel 965系列家族的研发代号为Broadwater,主要包括了以下四款系列芯片,它们分别是P965、G965、Q963和Q965。其中P965和G965面向主流消费者;Q963和Q965则主要面向企业用户,用来构建Averill平台。
主板是Intel集成芯片组G965,集成的显卡就是GMA X3000或X3100。
现在Sam想在Fedora8上安装此显卡驱动。
Sam在网上查到此驱动包含在xf86-video-intel中,于是在http://lists.freedesktop.org/archives/xorg/2008-April/034611.html 中下载了此包。
但Sam在./configure的时候,pkg-cofig提示找不到xorg-server. Sam也在/usr/lib/pkgconfig/中确实没找到xorg-server.pc 。
之后运行 #yum install xorg-x11-server-sdk .然后发现xorg-server.pc被建立。
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在计算内存使用量时,会把动态库所占内存包含进去。
公司在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变大。
(待解决)
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月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)”是两种不同的动态数据区,栈是一种线性结构,堆是一种链式结构。进程的每个线程都有私有的“栈”,所以每个线程虽然 代码一样,但本地变量的数据都是互不干扰。
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)”是两种不同的动态数据区,栈是一种线性结构,堆是一种链式结构。进程的每个线程都有私有的“栈”,所以每个线程虽然 代码一样,但本地变量的数据都是互不干扰。
订阅:
博文 (Atom)