2009年4月1日星期三

利用libtool自动生成动态库

libtool是什么?
使用libtool的好处在于该工具替user处理一些细节,最主要的是:
1。自动处理库的依赖关系;2。对于共享库,自动加入运行时搜索路径(在GNU/LINUX下面通过在
executable中hardcode一个路径名实现)。3,其他好处(我还不知道)。
而用户在链接生成executable的时候只需要使用libtool工具,并指定libtool库文件的名字作为参数就可以。

=============================================

生成库:
$ libtool --mode=compile gcc -c hello.c
$ libtool --mode=link gcc -g -O -o libhello.la foo.lo hello.lo
-rpath /usr/local/lib -lm
注意:
手册上说libtool库类型有三种:
1。libtool shared library,
必须加入-rpath选项(不能加入-static),同时也产生libtool static library.
2。libtool static library(好像又叫libtool static convenience
library),必须加入-rpath,同时加入-static选项。
3。convenience library
两个参数都不加,它与2。的区别在于convenience library具有PIC特性。


=======================================
安装库:(摘于手册)
在gnulinux下:libtool的安装过程如下:
a23# libtool --mode=install cp libhello.la
/usr/local/lib/libhello.la
cp libhello.la /usr/local/lib/libhello.la
cp .libs/libhello.a /usr/local/lib/libhello.a
ranlib /usr/local/lib/libhello.a
a23#
或者
burger# libtool --mode=install install -c libhello.la
/usr/local/lib/libhello.la
install -c .libs/libhello.so.0.0 /usr/local/lib/libhello.so.0.0
install -c libhello.la /usr/local/lib/libhello.la
install -c .libs/libhello.a /usr/local/lib/libhello.a
ranlib /usr/local/lib/libhello.a
burger#
注意:用户可以先安装再使用(即用executables
libhello.la来链接);好像也可以先链接再安装?!
如果最终使用不安装的libtool库文件,有时候会有问题的!

==========================================

使用库:

libtool --mode=link gcc -o hello main.c /home/szx/test/libadd.la

注意:如果在libtool链接命令中使用-L/-l参数,那么库名字首先被解释为la库文件(在当前路径下和-L指定的目录下搜索),
如果找不到再解释为.a或.so文件(此时libtool功能就退化成gcc或ld了)!所以为了避免二义性,最好使用绝对路径的形式,比如/home/szx/test/libadd.la


1 libtool的工作原理
libtool
是一个通用库支持脚本,将使用动态库的复杂性隐藏在统一、可移植的接口中;使用libtool的标准方法,可以在不同平台上创建并调用动态库。可以认为libtool是gcc的一个抽象,其包装了gcc(或者其他的编译器),用户无需知道细节,只要告诉libtool需要编译哪些库即可,libtool将处理库的依赖等细节。libtool只与后缀名为lo、la为的libtool文件打交道。
libtool主要的一个作用是在编译大型软件的过程中解决了库的依赖问题;将繁重的库依赖关系的维护工作承担下来,从而释放了程序员的人力资源。libtool提供统一的接口,隐藏了不同平台间库的名称的差异等细节,生成一个抽象的后缀名为la高层库libxx.la(其实是个文本文件),并将该库对其它库的依赖关系,都写在该la的文件中。该文件中的dependency_libs记录该库依赖的所有库(其中有些是以.la文件的形式加入的);libdir则指出了库的安装位置;library_names记录了共享库的名字;old_library记录了静态库的名字。
当编译过程到link阶段的时候,如果有下面的命令:
$libtool --mode=link gcc -o myprog -rpath /usr/lib –L/usr/lib –la
libtool会到/usr/lib路径下去寻找liba.la,然后从中读取实际的共享库的名字(library_names中记录了该名字,比如liba.so)和路径(lib_dir中记录了,比如libdir='/usr/lib'),返回诸如/usr/lib/liba.so的参数给激发出的gcc命令行。
如果liba.so依赖于库/usr/lib/libb.so,则在liba.la中将会有dependency_libs='-L/usr/lib
-lb'或者dependency_libs='/usr/lib/libb.la'的行,如果是前者,其将直接把"-L/usr/lib
–lb"当作参数传给gcc命令行;如果是后者,libtool将从/usr/lib/libb.la中读取实际的libb.so的库名称和路径,然后组合成参数"/usr/lib/libb.so"传递给gcc命令行。
当要生成的文件是诸如libmylib.la的时候,比如:
$libtool --mode=link gcc -o libmylib.la -rpath /usr/lib –L/usr/lib –la
其依赖的库的搜索基本类似,只是在这个时候会根据相应的规则生成相应的共享库和静态库。
注意:libtool在链接的时候只会涉及到后缀名为la的libtool文件;实际的库文件名称和库安装路径以及依赖关系是从该文件中读取的。
2 为何使用 -Wl,--rpath-link -Wl,DIR?
使用libtool解决编译问题看上去没什么问题:库的名称、路径、依赖都得到了很好的解决。但下结论不要那么着急,一个显而易见的问题就是:并不是所有的库都是用libtool编译的。
比如上面那个例子,
$libtool --mode=link gcc -o myprog -rpath /usr/lib –L/usr/lib –la
如果liba.so不是使用libtool工具生成的,则libtool此时根本找不到liba.la文件(不存在该文件)。这种情况下,libtool只会把"–L/usr/lib
–la"当作参数传递给gcc命令行。
考虑以下情况:要从myprog.o文件编译生成myprog,其依赖于库liba.so(使用libtool生成),liba.so又依赖于libb.so(libb.so的生成不使用libtool),而且由于某种原因,a对b的依赖并没有写入到liba.la中,那么如果用以下命令编译:
$libtool --mode=link gcc -o myprog -rpath /usr/lib –L/usr/lib –la
激发出的gcc命令行类似于下面:
gcc –o myprog /usr/lib/liba.so
由于liba.so 依赖于libb.so(这种依赖可以用readelf读liba.so的ELF文件看到),而上面的命令行中,并没有出现libb.so,于是,可能会出现问题。
说"可能",是因为如果在本地编译的情况下,gcc在命令行中找不到一个库(比如上面的liba.so)依赖的其它库(比如libb.so),链接器会按照某种策略到某些路径下面去寻找需要的共享库:
1. 所有由'-rpath-link'选项指定的搜索路径.
2. 所有由'-rpath'指定的搜索路径.
'-rpath'跟'-rpath_link'的不同之处在于,由'-rpath'指定的路径被包含在可执行文件中,并在运行时使用,
而'-rpath-link'选项仅仅在连接时起作用.
3. 在一个ELF系统中, 如果'-rpath'和'rpath-link'选项没有被使用,
会搜索环境变量'LD_RUN_PATH'的内容.它也只对本地连接器起作用.
4. 在SunOS上, '-rpath'选项不使用, 只搜索所有由'-L'指定的目录.
5. 对于一个本地连接器,环境变量'LD_LIBRARY_PATH'的内容被搜索.
6.
对于一个本地ELF连接器,共享库中的`DT_RUNPATH'和`DT_RPATH'操作符会被需要它的共享库搜索.
如果'DT_RUNPATH'存在了, 那'DT_RPATH'就会被忽略.
7. 缺省目录, 常规的,如'/lib'和'/usr/lib'.
8. 对于ELF系统上的本地连接器, 如果文件'/etc/ld.so.conf'存在,
这个文件中有的目录会被搜索.
从以上可以看出,在使用本地工具链进行本地编译情况下,只要库存在于某个位置,gcc总能通过如上策略找到需要的共享库。但在交叉编译下,上述八种策略,可以使用的仅仅有两个:-rpath-link,-rpath。这两个选项在上述八种策略当中优先级最高,当指定这两个选项时,如果链接需要的共享库找不到,链接器会优先到这两个选项指定的路径下去搜索需要的共享库。通过上面的描述可以看到:-rpath指定的路径将被写到可执行文件中;-rpath-link则不会;我们当然不希望交叉编译情况下使用的路径信息被写进最终的可执行文件,所以我们选择使用选项-rpath-link。
gcc的选项"-Wl,--rpath-link
–Wl,DIR"会把-rpath-link选项及路径信息传递给链接器。回到上面那个例子,如果命令行中没有出现libb.so,但gcc指定了"-Wl,--rpath-link
–Wl,DIR",则链接器找不到libb.so的时候,会首先到后面-rpath-link指定的路径去寻找其依赖的库。此处我们使用的编译命令的示例是使用unicore平台的工具链。
$ unicore32-linux-gcc –o myprog /usr/lib/liba.so \
-Wl,--rpath-link -Wl,/home/UNITY_float/install/usr/lib
这样,编译器会首先到"/home/UNITY_float/install/usr/lib"下面去搜索libb.so
libtool如何把选项"-Wl,--rpath-link
–Wl,DIR"传递给gcc?libtool中有一个变量"hardcode_libdir_flag_spec",该变量本来是传递"-rpath"选项的,但我们可以修改它,添加我们需要的路径,传递给unicore32-linux-gcc。
"hardcode_libdir_flag_spec"原来的定义如下:
hardcode_libdir_flag_spec="\${wl}--rpath \${wl}\$libdir"
我们修改后的定义如下:
hardcode_libdir_flag_spec="\${wl}—rpath-link \${wl}\$libdir \
-Wl,--rpath-link -Wl,/home/UNITY_float/install/usr/lib \
-Wl,--rpath-link
-Wl,/home/UNITY_float/install/usr/X11R6/lib "
这样,当libtool在"--mode=link"的模式下,就会把选项"-Wl,--rpath-link
–Wl,DIR"传递给gcc编译器了。


利用libtool自动生成动态库
#
1. autoscan命令在当前目录生成configure.scan文件, 内容为:
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.57)
AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)
AC_CONFIG_SRCDIR([src/bot.h])
AC_CONFIG_HEADER([config.h])
# Checks for programs.
AC_PROG_CXX
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([limits.h malloc.h stdlib.h string.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_HEADER_STDBOOL
AC_C_CONST
AC_C_INLINE
# Checks for library functions.
AC_FUNC_MALLOC
AC_FUNC_REALLOC
AC_CHECK_FUNCS([memset strcasecmp strchr strdup])
AC_OUTPUT
将其该名为configure.ac 然后修改:
configure.ac 文件是 autoconf 的输入文件,经过 autoconf 处理,展开里面的
m4宏,
输出的是 configure 脚本。
第 4 行声明本文件要求的 autoconf 版本,因为本例使用了新版本
2.57,所以在此注明。
第 5 行 AC_INIT 宏用来定义软件的名称和版本等信息
AC_INIT([test], 1.0,****@gmail.com)
增加版本信息(为生成lib库做准备)
lt_major=1
lt_age=1
lt_revision=12
dist_version=0.1.12
AM_INIT_AUTOMAKE(test, $dist_version) //自动生成Makefile文件
增加宏, 打开共享库
AC_PROG_LIBTOOL
# Check for dl
DL_PRESENT=""
AC_CHECK_LIB( dl, dlopen, DL_PRESENT="yes",, $DL_LIBS -ldl )
if test "x$DL_PRESENT" = "xyes"; then
AC_DEFINE(HAVE_LIBDL, 1, [Define if DL lib is present])
DL_LIBS="-ldl"
AC_SUBST(DL_LIBS)
fi
# Check for libm
M_PRESENT=""
AC_CHECK_LIB( m, sin, M_PRESENT="yes",, $M_LIBS -lm )
if test "x$M_PRESENT" = "xyes"; then
AC_DEFINE(HAVE_LIBM, 1, [Define if libm is present])
M_LIBS="-lm"
AC_SUBST(M_LIBS)
fi
增加依赖库
# Check for pthread
PTHREAD_PRESENT=""
AC_CHECK_LIB( pthread, pthread_create, PTHREAD_PRESENT="yes",,
$PTHREAD_LIBS
-lpthread )
if test "x$PTHREAD_PRESENT" = "xyes"; then
AC_DEFINE(HAVE_LIBPTHREAD, 1, [Define if libpthread is present])
PTHREAD_LIBS="-lpthread"
AC_SUBST(PTHREAD_LIBS)
fi
要生成项目工程目录和其它目录下的Makefile 文件, 必需加入
AM_CONFIG_FILES的宏:
例如: AC_CONFIG_FILES([Makefile
src/Makefile
data/Makefile
docs/Makefile])
修改完后Makefile.ac如下:
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.57)
AC_INIT([test],[1.0],[****@users.sourceforge.net])
AM_CONFIG_HEADER(config.h)
lt_major=1
lt_age=1
lt_revision=12
dist_version=0.1.12
AM_INIT_AUTOMAKE(test, $dist_version)
AC_SUBST(lt_major)
AC_SUBST(lt_revision)
AC_SUBST(lt_age)
# Checks for programs.
#AC_PROG_CC
#AC_PROG_INSTALL
#AC_PROG_LN_S
#AC_PROG_LIBTOOL
AM_PROG_LIBTOOL
# Checks for libraries.
pkg_modules="gtk+-2.0 >= 2.0.0"
PKG_CHECK_MODULES(GTK_PACKAGE, [$pkg_modules], HAVE_GTK2="yes",
HAVE_GTK2="no" )
AC_SUBST(GTK_PACKAGE_CFLAGS)
AC_SUBST(GTK_PACKAGE_LIBS)
# Check for dl
DL_PRESENT=""
AC_CHECK_LIB( dl, dlopen, DL_PRESENT="yes",, $DL_LIBS -ldl )
if test "x$DL_PRESENT" = "xyes"; then
AC_DEFINE(HAVE_LIBDL, 1, [Define if DL lib is present])
DL_LIBS="-ldl"
AC_SUBST(DL_LIBS)
fi
# Check for libm
M_PRESENT=""
AC_CHECK_LIB( m, sin, M_PRESENT="yes",, $M_LIBS -lm )
if test "x$M_PRESENT" = "xyes"; then
AC_DEFINE(HAVE_LIBM, 1, [Define if libm is present])
M_LIBS="-lm"
AC_SUBST(M_LIBS)
fi
# Check for pthread
(如示例中检测pthread,在生成的Makefile就会自动添加-lpthread)
PTHREAD_PRESENT=""
AC_CHECK_LIB( pthread, pthread_create, PTHREAD_PRESENT="yes",,
$PTHREAD_LIBS
-lpthread )
if test "x$PTHREAD_PRESENT" = "xyes"; then
AC_DEFINE(HAVE_LIBPTHREAD, 1, [Define if libpthread is present])
PTHREAD_LIBS="-lpthread"
AC_SUBST(PTHREAD_LIBS)
fi
# Checks for header files.
#AC_HEADER_DIRENT
#AC_HEADER_STDC
#AC_CHECK_HEADERS([fcntl.h stdlib.h string.h sys/time.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
#AC_TYPE_PID_T
#AC_TYPE_SIZE_T
#AC_HEADER_TIME
# Checks for library functions.
#AC_FUNC_CLOSEDIR_VOID
#AC_FUNC_MALLOC
#AC_CHECK_FUNCS([memset strstr])
AC_CONFIG_FILES([Makefile
src/Makefile
data/Makefile
doc/Makefile])
AC_OUTPUT

2.生成各目录下的Makefile.am文件
./Makefile.am //工程目录下
SUBDIR = src data doc
../src/Makefile.am //源码目录下
MAINTAINERCLEANFILES = Makefile.in
INCLUDES = -I../include
CPPFLAGS=-DINSTALL_PREFIX="\"$(prefix)\""
lib_LTLIBRARIES = libtest.la
libtest_la_LDFLAGS = -version-info @lt_major@:@lt_revision@:@lt_age@
libtest_la_SOURCES = \
test.c \
test_private.h \
check_match.c \
check_match.h \
test_helpers.c \
test_helpers.h \
debug.h
libtest_la_LIBADD = \
@DL_LIBS@ \
@M_LIBS@

3. 生成autogen.sh脚本, 内容
#! /bin/sh
set -x
aclocal
autoheader
automake --foreign --add-missing --copy
autoconf

保存后修改权限 chmod a+x autogen.sh

3.运行脚本./autogen.sh, 生成configure脚本. 这里可能会遇到错误,
可以根据错误提示作相应修改.

4.运行./configure脚本.自动生成src目录下的makefile文件

5. 切换到目录src, 运行make 自动在当前目录下建立.libs文件,
编程生成的库文件就保存在该目录下.
make install 安装在默认目录 /usr/local/lib/下.

6.如果要生成其它的安装目录,Makefile.am就要这样写
MAINTAINERCLEANFILES = Makefile.in
INCLUDES = -I../include
lib_LTLIBRARIES = libtt.la
libdir = $(prefix)/lib/test
libtt_la_LDFLAGS = -version-info @lt_major@:@lt_revision@:@lt_age@
libtt_la_LIBADD = @PTHREAD_LIBS@
libtt_la_SOURCES = \
tt.c \
video.c \
video.h

Static library & dynamic library -- linux系统下的静态库与动态库

关于实现动态库链接的makefile, 看了<<Advanced Linux
Programming>>,笔记如下:
我们知道,几乎所有的程序在运行时都会链接到一个或多个库。比如C语言的printf函数,调用此函数时就会用到c的标准输入输出库;而在GUI下面,会用到对应的图形库;调用数据库时,会用到数据库系统提供的相应的库,等等。
1.静态库
静态库(archive / static library)就是一些简单的目标文件整合而成的单个文件。可以暂且把它看作是windows下的.lib文件。当把这个库提供给某个文件作为某个程序的接口(不知道"接口"用在这里恰不恰当,个人理解的)时,链接就会把这个库解压,然后搜寻需要的接口,把它加载到程序中来。
linux中静态库的后缀是.a,前缀默认为lib。使用ar命令可以把一个或多个目标文件打包成一个文件。基本格式是:
ar -cr lib*.a opt1.o ... optn.o
其中 参数cr表示creat 和 replace,即创建并替换(这里的替换是指当文件创建成功时,如果有同名文件则替换之)。lib*.a是目标库文件。optx.o是目标文件列表。
生成了库文件后,需要该库的程序只需要在编译的时候加载改库即可。
比如定义了一个函数out(char *s);该函数输出对应的串
对应的文件如下:

out.h:
#ifndef OUT_H
#def OUT_H
void out(char *);
#endif

out.c
#include
#include"out.h"
void out(char *s){
  printf("%s\n",s);
}

main.c
#include"out.h"
int main(int argc,char ** argv){
  int i;
  for(i=1;i

系统性能优化思路

基础性工作:
1、 在编译动态库时,加上—version-map选项,控制导出函数。
2、 在编译动态库时,加上-fPIC选项,为以后使用Prelink优化做准备。
3、 将编译的动态库,全部放到一个指定目录下。
4、
将编译后的执行文件和动态库的符号,全部输出到指定目录,为以后分析做准备。
5、 使用Prelink,优化编译完成的执行文件和动态库。

特定目标优化:
1、 开机性能优化
* 了解当前系统启动时间,确定目标系统启动时间。
* 对开机过程的bash脚本进行优化,去除冗余,提高脚本自身的执行速度。
*
对开机过程中启动的进程所完成的任务要有所了解,对于一些不必要的进程要从启动过程中删掉。
*
缕清启动过程中进程之间的依赖关系,调整进程的启动次序,充分利用CPU资源。
*
了解启动过程中,各个进程所花费的时间,针对用时比较长的进程,考虑代码优化。
2、 进程启动优化
*
整理出由于无法满足进程响应时间,而将该进程改为守护进程的清单,对于这类进程努力提高其启动速度,将其从守护进程中删除。
* 整理出目前不满足用户输入响应时间要求的进程清单。
* 确认Prelink的方法生效。
* 优化动态库的搜索路径。
*
确认进程启动过程中,动态库加载的时间与用户代码响应时间。如果是动态库加载时间过长,考虑进程的动态库优化;如果是用户代码响应时间过长,考虑代码优化。
*
关于进程加载动态库的优化,可以考虑去除进程中不必要的动态库,动态库的合并与拆分。
* 对于GTK开发的应用来讲,可以采用调度的方法来节省掉其加载动态库的时间。
3、 系统守护进程优化
* 目标:减少守护进程的数量。
* 弄清当前系统内都有哪些守护进程及其原因。
* 由于进程启动时间达不到要求的守护进程,采用上面的方法进行优化。
* 将一些守护进程侦听事件部分,合并为一个进程,从而减少守护进程的数量。
4、 进程优化
* 整理应用程序中,都有哪些操作性能比较差,需要优化。
*
对于这些操作,分析其都完成了哪些操作,其性能瓶颈在什么地方,从软件逻辑上对其进行优化。
* 使用oprofile,查找底层热点函数,对于底层库函数实行代码优化。
*
了解系统中GTK的整体构架,查找带UI绘图的性能瓶颈,考虑采用GPU的硬加速的功能。
*
对于性能要求很高的多媒体应用来讲,先以逻辑优化为主,后期重点放在代码优化、硬加速方面。
5、 系统耗电量
* 系统耗电量的优化主要包括两部分:关闭不必要模块,降低CPU的频率。
* 确定系统大部分时间所处的状态,要争取其CPU处于较低的频率。
*
在确认这些状态之后,整理出在这些状态应用都做了哪些事情,对其进行逻辑优化。
* 使用oprofile,检测这些状态下热点函数情况,对其进行代码优化。

在嵌入式Linux系统中应用的GTK+和X分析

在嵌入式 Linux 下有很多图形界面系统 GUI,包括
Qt/Embedded,FLTK,Microwindows 和 GTK+
等。作为一个开发者,到底使用什么样的 GUI
系统呢?对一个系统,将它改造为符合你的需求,你要做多少修改呢?修改后的系统的尺寸一般会有多大呢?这些都是开发人员会遇到的问题。我们在这里讨论的就是要对这些内容做一个具体细致的分析,通过我们的讨论,大家会对基于
GTK+ 和 X 的 GUI 在嵌入式Linux 下的应用有一个确切的了解。

在嵌入式系统应用日益发展的今天,越来越多的应用都需要使用到 GUI
来进行开发,以此来获得更好的交互性。

嵌入式 Linux 下 GUI
的选择,对大多数开发人员来说是一个需要权衡对比的过程。选择 GTK+ 运行在 X
系统上,然后 X 系统运行在嵌入系统的 framebuffer 上,这会是一个很好的选择。

GTK+ 与 X 的优点

当然,GTK+ 与 X
一般都是被大家考虑为体积较大的桌面系统的好搭配,但实际上对于嵌入系统来说,它也有着诸多的优点:

1、 X-window 系统与 GTK+ 都非常稳定可靠,X-window
系统是经历了长期的开发及应用实践的,GTK+ 也是一个比较成熟的开放源代码项目;

2、 X-window 系统是一个灵活的 client/server
的模型结构,一个应用客户端的崩溃不会影响到图形系统的其他部分,这是一个很重要的特性,它有利于支持第三方应用的扩展开发,而不影响到主体部分;

3、 GTK+有两个重要的库:GDK和GLIB。GDK抽象了底层的窗口管理,要移植 GTK+
到另一个不同的窗口系统的话,我们只需要移植 GDK 就可以了。GLIB
是一个工具集合,它包括了数据类型,各种宏定义,类型转化,字符串处理,任何应用程序都可以链接这个
GLIB
库,使用其中的各种数据类型、方法,来避免重复代码,或者说避免开发人员重新发明轮子,这样有利于减少整个系统的尺寸;

4、 对 GTK+/X
的裁剪是很容易的,它们有着很好的可配置的选项,有着清晰的代码结构,可以保证安全正确地去掉大段的不需要的代码;

5、 GTK+ 有着大量的应用,GTK+ 已经被用在了很多重要的应用系统中;

6、 GTK+ 的授权是 LGPL 方式的,X 是 non-copyleft free license
的,第三方开发的系统都能与它们进行链接;

7、 GTK+/X 二者都是基于 C 代码的,而不是C++;

8、 GTK+ 使用 C 来实现了面向对象的架构;

其他 GUI 系统

其他可以选择的图形系统包括:Qt/Embedded,FLTK 和 Microwindows。

1、 Qt/E 是其中较高级的,它是一个完整的,基于 framebuffer 的 GUI 系统,由
Trolltech 公司开发;

2、 Qt/E 有着高效的图形渲染效果,还包括 TrueType 字体系统,及 alpha
blending 半透明处理;

3、 但 Qt/E 不是使用 LGPL 授权方式,而是使用两种授权方式:开发使用
GPL,而商用需要授权与版税;

4、 Qt/E 是用 C++ 编写的;

5、 Qt/E 非常大,一个 iPAQ QPE 就包括了 3.3MB 的 Qt/E 库和一个 718KB 的
QPE 库(和 Xlib 类似的一种库);

6、 Qt/E 不够稳定,QPE demo 不错,但出现过崩溃;

7、 FLTK (the Fast Light Toolkit) 是一个小型的 GUI 图形系统,它也是用 C++
写的,特性太少,应用范围较少,不够成熟;

8、 Microwindows 和 X-Window 相比也是一个不错的选择,它占用大约
100KB-600KB 大小的内存,和文件存储空间,虽然已经有了一个其上的 GTK+
移植,但还是不够成熟;

X-window:比你想象的要小很多

对于X-window系统,广大的网络开发者已经做了大量的工作来减小其的尺寸,最知名的有TinyX。可以通过对不需要的代码的裁剪及去除XLIB中静态数据来减少总体的尺寸,如:color管理系统,弧形,粗线条等。

在大多数开发人员的印象里,X 系统很庞大,但实际上,你听到的,是那些对 X
不够了解的人的一种误解。在经过裁剪后的情况下,GTK+/X 要比 GTK+/FB 与 Qt/E
还要来得有效,且 XLIB
对一般的应用程序有着更好的支持作用,应用程序的开发会变得更高效。

如何裁剪 GTK+

我们可以从标准的 GTK+
发行版本来裁剪,裁剪掉其中的不需要的,修改已经有的代码,并加入新的特性所需要的代码。裁剪的范围包括小的改动,也包括一些大的结构性的、核心的改变。

1、 去除 Widgets 窗口

最开始,我们把不需要的 Widgets 去除掉,比如:GtkGamma、GtkHRuler、过时了的
GtkList(被 GtkCList所替代了)、和我们不需要的 GtkFrame 边框。

2、 Widgets 窗口尺寸与绘制

接着,修改Widgets的大小与绘制方法,GTK+提供了一个主题引擎机制,来控制窗口的外观与效果。它允许在运行中设置字体,设置行间隔,设置绘制特性。这样的机制很不错,但不够灵活,代码中很多设置的地方都是使用硬编码的方式;另外,一种主题,就是一堆额外的代码段和参数,这样会增加整体的尺寸。

需要找出影响到窗口系统整体尺寸的内容,再来修改它。比如,一个按钮的大小与绘制,包括这样的参数:边框的宽度,x/y的位置(主题引擎需要的参数),缺省的间隔(常量),缺省的左上角的位置(常量),获得焦点。这些在嵌入系统中并不需要那么完整,我们可以根据实际的需求来简化代码,来避免GTK+的复杂性。

另外,使用面向对象的方法,来继承窗口Widgets的特性,作为子类也是一个有效的方法。

3、GtkWindow

GTK+总是假定一个窗口里面包含了另一个窗口,它们就是嵌套关系。但对于我们经常会碰到的有软键盘的应用时,就不完全正确了。软键盘虽然是属于一个窗口的,但却会超出那个窗口。所以为了突破这个假定,需要对GtkWindow增加一些特性,将软键盘处理成一种特殊的子窗口。
软键盘所在的窗口,需要处理软键盘的按键事件,并将按键转发给软键盘工具条。当软键盘按下,软键盘的回调函数就被注册到原始窗口上,这样软键盘就会响应按键事件。在GtkWindow上增加接口,可以创建,响应按键。

在小屏幕的嵌入系统中,可以将滚动条做得更简化些,去掉边框,使用单个滚动条。这些都更适合嵌入系统。

字体管理系统

在字体管理方面,要找到一个轻型的机制来在嵌入式系统显示各种字体,并不是那么简单,困难在于GTK+
的大型的 Widget 风格与 X 系统的老式的字体管理机制的结合所引起的问题。

前面提到的,主题引擎方式的GTK+
是用来控制窗口的样式与外观的。在一个窗口显示之前,它会得到一个式样对象,GtkStyle,它可以是一个指向父窗口的式样对象指针,或者是一个新的类型,这些式样对象将被应用到这个窗口及它的子窗口。这个式样由缺省值、rc
文本文件、应用来确定。

要改变一个窗口的字体,你必须克隆窗口的式样,并使用X字体加载一个新的字体,类似adobe-helvetica-bold-r-normal--12-*-*-*-p-*-iso8859-1。

但实际中会有些问题,GtkStyle是一个大的对象。如果一个屏幕上有很多种不同字体大小的多个窗口,每个都有一个唯一的GtkStyle对象,我们就会浪费大量的内存。到最后,X系统就不能支持类似字体的各种变化了。你甚至不能使X完成让某个字体变粗的操作,因为X系统是将不同外型的字体作为不同的字体的。X系统是假定你会硬编码一个希望的字体或者分析出一个字体名,改变字体及验证结果都将在字体服务器上。

还可以使用一个更好的方法来完善字体管理系统,即包装GtkStyle,这样开发者就可以通过属性来获得一个窗口的字体,这比直接使用
X
系统字体的名字要更灵活。比如要显示一个比基本字体要大一号,并且是黑体字就可以调用:

gtk_widget_set_font_bold (widget, TRUE);

gtk_widget_set_font_enlarge (widget, 1);

这是通过在 GtkWidget 结构中加入一个 GdkFont * font 来实现的,GtkWidget
是所有窗口类的父类。如果设置widget->font
那么就使用它,否则就使用widget->style->font。

窗口管理

在嵌入系统GUI中,还需要建立一个窗口管理器。我们可以选择一个开放代码的,轻量级的X管理器,Aewm。在嵌入系统中,我们会将最上层的窗口设置为获得焦点,并且只有对话框能移动,能显示其标题栏。

窗口管理器是一个交互端,它可以管理内部与外部的应用程序的窗口。每一个应用程序的窗口,都会建立一个
socket
连接,并取一个名字。一个应用可以把请求将自己放在窗口堆栈的最下面,或者将一个命名的应用往上移。如果一个对话框要在最上层的窗口上打开,那么窗口管理器就将告诉这个最上层的窗口它将不再获得焦点,而新对话框将获得焦点。

整体尺寸大小

经过一系列的改进后,我们就得到了一个稳定的,功能和性能都能满足嵌入系统要求的GUI了。在ARM系统下,得到的尺寸大小为:

其中 GTK+
里面仍然还有不需要的代码,可以将其再去除。如果再简化一下的话,GTK+
可以做到850KB,总体大小可以到 2.8M。

运行性能

将修改过的 GUI 运行在一个 ARM7 的系统上,CPU 为
100MHZ,运行时的效果还不错,窗口响应用户操作的速度很迅速,新的画面创建与显示的速度都能接受。

但是,启动时的时间却有些问题,比较慢。在这个 CPU
上,应用程序画面加载与显示的时间需要 2.4秒,其中 1.5 秒是花在了建立与显示
UI 上。

在较慢的 CPU 上,这样的启动速度是 GTK+ 运行在 X,X 再写到 framebuffer
上导致的。我们需要分析具体的瓶颈在什么地方。在深入的调试中,当使用PC机来运行我们的应用,而在ARM设备上显示时,初始化和显示的时间几乎可以忽略不计。同样,将应用运行在ARM设备上,而在PC机上显示时,性能也很好。所以数据包的传输,framebuffer上的显示及GTK+的运算速度都不是问题所在。速度慢的原因可能是这几个因素的先后顺序引起的。而且内存的占用上也存在问题。在初始阶段,GTK+构造了大量的对象,GTK+和X是使用共享内存来通讯的,X写到framebuffer,framebuffer也是不变地写到显存上。这些都是发生在一个较窄总线上相同的内存空间里,这个就会引起速度慢。

现在知道了X在启动时比较花费时间。在客户模式下的GTK+的应用,需要连接到X
server,通过了认证,得到位深及其他资源。当然,使用X系统的好处要远大于它的不足。X系统提供了一个灵活的client/server模型,这样更有利于应用的灵活加入。你可以在同一时间显示不用的应用窗口,而像GTK+/Fb等其他的GUI方式无法做到这一点,当然QPE是个例外。

2.4秒的延时,也并不能完全否定一切。在一个700MHZ的windows系统的PC上,Microsoft
Word, Excel and IE几乎都需要2秒的时间来启动。KEdit,
一个KDE的应用程序,在500MHZ的PIII上,加载的时间也需要1.37秒。

对于启动时间,需要采用其他的办法来加以改善。一个策略就是在用户等待的时候,显示一些东西来表示系统正在工作,这样会使用户忽略掉启动时间的缓慢。另一个策略就是可以预先在背后启动一些通用的程序,来有效减少集中启动的时间。这也是通常嵌入系统所惯用的做法。

在ARM7的系统上,由于没有浮点运算FPU,所以GTK+中的浮点运算部分最好是去掉,否则会大大影响性能。GTK+使用到的浮点变量只分布在少数的几个窗口中,并且去掉它们会带来3%到12%的性能提高。

高像素的应用会导致速度较慢,这大多是由于GTK+与X中对高像素的效率低下的处理有关。如涉及到的XPM,XPM
(X
pixmap)格式是被设计来做到较好的兼容性,而不是更加快速。X系统是一个像素一个像素地画到server的pixmap的。GTK+的像素处理也很低效,它是使用fgetc()来读取XPM文件的,这就会带来大量的上下文切换开销。

X窗口系统的结构也导致了像素的加载变慢。GTK+客户端需要加载,分析XPM文件,将像素值通过传输协议发送给server,然后server才将像素值放入framebuffer。如果客户端直接将数据写到framebuffer
server那将会有效很多。

处理的GTK+像素的办法就是,写一个临时的中间过程,取得render过的像素,使用这个原始数据来替换XPM数据,这个原始数据就可以直接强制写到X
server上。从结构上来看,这虽然不是一个很好的处理办法,但在效率上却要比使用XPM要快上80%。

总结

现在的消费电子大多需要一个美观,实用的图形界面系统GUI。在嵌入系统linux下,有很多种GUI可供选择。使用开放代码的GUI的优点就是你可以将其裁剪得满足你的各种各样的特殊需求。GTK+就是一个很好的选择,而X-window系统提供了一个稳定可靠的client/server模型。当你得到一个只有2.9M大小的定制过的GUI时,对大多数的嵌入系统还是很有参考价值的。

Linux中.a,.la,.o,.so文件的意义和编程实现

Linux下文件的类型是不依赖于其后缀名的,但一般来讲:
.o,是目标文件,相当于windows中的.obj文件
.so 为共享库,是shared object,用于动态连接的,和dll差不多
.a为静态库,是好多个.o合在一起,用于静态连接
.la为libtool自动生成的一些共享库,vi编辑查看,主要记录了一些配置信息。可以用如下命令查看*.la文件的格式
$file *.la

*.la: ASCII English text
所以可以用vi来查看其内容。
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
创建.a库文件和.o库文件:
[yufei@localhost perl_c2]$ pwd
/home/yufei/perl_c2

[yufei@localhost perl_c2]$ cat mylib.c
#include <stdio.h>
#include <string.h>
void hello(){
printf("success call from perl to c library\n");
}
[yufei@localhost perl_c2]$ cat mylib.h
extern void hello();


[yufei@localhost perl_c2]$ gcc -c mylib.c
[yufei@localhost perl_c2]$ dir
mylib.c mylib.h mylib.o
[yufei@localhost perl_c2]$ ar -r mylib.a mylib.o
ar: 正在创建 mylib.a
[yufei@localhost perl_c2]$ dir
mylib.a mylib.c mylib.h mylib.o


@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
动态链接库*.so的编译与使用- -


动态库*.so在Linux下用c和c++编程时经常会碰到,最近在网站找了几篇文章介绍动态库的编译和链接,总算搞懂了这个之前一直不太了解得东东,这里做个笔记,也为其它正为动态库链接库而苦恼的兄弟们提供一点帮助。

1、动态库的编译

下面通过一个例子来介绍如何生成一个动态库。这里有一个头文件:so_test.h,三个.c文件:test_a.c、test_b.c、test_c.c,我们将这几个文件编译成一个动态库:libtest.so。

so_test.h:

#include <stdio.h>
#include <stdlib.h>
void test_a();
void test_b();
void test_c();

test_a.c:

#include "so_test.h"
void test_a()
{
printf("this is in test_a...\n");
}

test_b.c:

#include "so_test.h"
void test_b()
{
printf("this is in test_b...\n");
}

test_c.c:

#include "so_test.h"
void test_c()
{
printf("this is in test_c...\n");
}

将这几个文件编译成一个动态库:libtest.so
$ gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so

2、动态库的链接

在1、中,我们已经成功生成了一个自己的动态链接库libtest.so,下面我们通过一个程序来调用这个库里的函数。程序的源文件为:test.c。
test.c:

#include "so_test.h"
int main()
{
test_a();
test_b();
test_c();
return 0;

}
l 将test.c与动态库libtest.so链接生成执行文件test:
$ gcc test.c -L. -ltest -o test

l 测试是否动态连接,如果列出libtest.so,那么应该是连接正常了

$ ldd test
l 执行test,可以看到它是如何调用动态库中的函数的。
3、编译参数解析
最主要的是GCC命令行的一个选项:
-shared
该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件

l
-fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。

l -L.:表示要连接的库在当前目录中

l
-ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称

l
LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。

l
当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用
/sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。
4、注意
调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录
通过 "-I"
include进来了,库所在文件通过"-L"参数引导,并指定了"-l"的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你
要作的就是通过修改LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题
了。

makefile里面怎么正确的编译和连接生成.so库文件,然后又是在其他程序的makefile里面如何编译和连接才能调用这个库文件的函数????
答:
你需要告诉动态链接器、加载器ld.so在哪里才能找到这个共享库,可以设置环境变量把库的路径添加到库目录/lib和/usr/lib,LD_LIBRARY_PATH=$(pwd),这种方法采用命令行方法不太方便,一种替代方法
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^注释^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LD_LIBRARY_PATH可以在/etc/profile还是 ~/.profile还是
./bash_profile里设置,或者.bashrc里,

改完后运行source /etc/profile或 . /etc/profile
更好的办法是添入/etc/ld.so.conf, 然后执行 /sbin/ldconfig

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^注释^^^^^^^^^^^^^^^^^^^^^^^^^^^^
是把库路径添加到/etc/ld.so.conf,然后以root身份运行ldconfig
也可以在连接的时候指定文件路径和名称 -I -L.

GCC=gcc
CFLAGS=-Wall -ggdb -fPIC
#CFLAGS=
all: libfunc test
libfunc:func.o func1.o
$(GCC) -shared -Wl,-soname,libfunc.so.1 -o libfunc.so.1.1 $<
ln -sf libfunc.so.1.1 libfunc.so.1
ln -sf libfunc.so.1 libfunc.so
***********************************************注释************************************************
ln
-s是用来创建软链接,也就相当于windows中的快捷方式,在当前目录中创建上一级目录中的文件ttt的命名为ttt2软链接的命令是ln
-s ../ttt ttt2,如果原文件也就是ttt文件删除的话,ttt2也变成了空文件。
ln
-d是用来创建硬链接,也就相当于windows中文件的副本,当原文件删除的时候,并不影响"副本"的内容。

编译目标文件时使用gcc的-fPIC选项,产生与位置无关的代码并能被加载到任何地址:
gcc –fPIC –g –c liberr.c –o liberr.o
使用gcc的-shared和-soname选项;
使用gcc的-Wl选项把参数传递给连接器ld;
使用gcc的-l选项显示的连接C库,以保证可以得到所需的启动(startup)代码,从而避免程序在使用不同的,可能不兼容版本的C库的系统上不能启动执行。
gcc –g –shared –Wl,-soname,liberr.so –o liberr.so.1.0.0 liberr.o –lc

建立相应的符号连接:
ln –s liberr.so.1.0.0 liberr.so.1;
ln –s liberr.so.1.0.0 liberr.so;

在MAKEFILE中:
$@
表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。
$%
仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。
$<
依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
$?
所有比目标新的依赖目标的集合。以空格分隔。
$^
所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
*********************************************注释***********************************************************************
test: test.o libfunc
$(GCC) -o test test.o -L. -lfunc
%.o:%.c
$(GCC) -c $(CFLAGS) -o $@ $<
clean:
rm -fr *.o
rm -fr *.so*
rm -fr test

要生成.so文件,cc要带-shared
参数;要调用.so的文件,比如libfunc.so,可以在cc命令最后加上-lfunc,还要视情况加上-L/usr/xxx
指出libfunc.so的路径;这样,在你要编译的源文件中就可以调用libfunc.so这个库文件的函数.
前面的都说的差不多了,最后提醒一下最好提供一个接口头文件
动态加载,用dlopen,dlclose,dlsym

提示“GPG签名验证错误“问题的解决

Ubuntu执行apt-get update时间报GPG签名验证错误

W: GPG签名验证错误: http://ppa.launchpad.net intrepid Release:
由于没有公钥,下列签名无法进行验证: NO_PUBKEY 19C98318F87FE1BD

解决方法从http://forum.ubuntu.org.cn/viewtopic.php?f=77&t=182819找到:
下载密匙:
gpg --keyserver subkeys.pgp.net --recv ********
导入密匙:
gpg --export --armor ******** | sudo apt-key add -
注: * 号是指密匙的后八位

重读TCP协议

TCP 的数据流
TCP的数据流大致可以分为两类,交互数据流与成块的数据流。交互数据流就是发送控制命令的数据流,比如relogin,telnet,ftp命令等等;成块数据流是用来发送数据的包,网络上大部分的TCP包都是这种包。
很明显,TCP在传输这两种类型的包时的效率是不一样的,因此为了提高TCP的传输效率,应该对这两种类型的包采用不同的算法。
总之,TCP的传输原则是尽量减少小分组传输的数量。
TCP的交互式数据流
Ø 经受时延的确认技术
TCP的交互式数据流通常使用"经过时延的确认"技术。通常Server在接收到从Client发送过来的数据时,并不马上发送ACK,而是等一小段时间,看看本机是否有数据要反馈给Client,如果有,就将数据包含在此ACK包中,以前发送给Client。一般情况下这个时延为200ms。需要注意的时这个
200ms的定时器时相对于内核的时钟滴答的,也就是jeffs的。加入一个数据分组到达后,此定时器已经pass了100ms,那么再过100ms
ACK才会被发送,如果在这100ms内有数据要反馈,则在100ms后ACK会和数据一起发送。

Ø Nagle算法分析。
Nagle算法主要用来预防小分组的产生。在广域网上,大量TCP小分组极有可能造成网络的拥塞。
Nagle时针对每一个TCP连接的。它要求一个TCP连接上最多只能有一个未被确认的小分组。在改分组的确认到达之前不能发送其他小分组。TCP会搜集这些小的分组,然后在之前小分组的确认到达后将刚才搜集的小分组合并发送出去。
有时候我们必须要关闭Nagle算法,特别是在一些对时延要求较高的交互式操作环境中,所有的小分组必须尽快发送出去。
我们可以通过编程取消Nagle算法,利用TCP_NODELAY选项来关闭Nagle算法。
TCP成块数据流
和TCP成块数据流相关的东西有很多,比如流量控制,紧急数据传输,数据窗口大小调整等等。
Ø 正常数据流
TCP通常不会对每个到达的数据分段进行确认操作,通常一个ACK报文可以确认多个成块数据段报文,通常情况下是两个成块数据报文段需要一个ACK报文确认。通常是由下面的原有造成的:当收到一个报文后,此TCP连接被标识未一个未完成的时延确认,当再次收到一个数据报文后,此连接有两个未确认的报文段,TCP马上发送一个ACK,当第三个数据报文到达后,第四个报文到达前,通常此TCP连接已经经过了200ms延时,因此一个ACK被发送,这样的循环周而复始,从而出现了一个ACK
确认两个数据报文的情况。当然,ACK的产生很大程度上和其接收数据报文段的时间紧密相关,也就是和Client段发送数据的频率相关,和网络拥塞程度相关,和Client与Server两端的处理能力相关,总是是一个多因素决定的结果。
Ø TCP的滑动窗口协议
TCP使用滑动窗口协议来进行流量控制。特别需要注意的是,滑动窗口是一个抽象的概念,它是针对每一个TCP连接的,而且是有方向的,一个TCP连接应该有两个滑动窗口,每个数据传输方向上有一个,而不是针对连接的每一端的。
窗口左边沿向右边滑动叫做窗口合拢,表示发送方发送了数据或者接收到了确认;窗口右边沿向右边滑动叫做窗口的张开,表示数据已经被用户空间进程接收并且释放了缓存;窗口左边沿向左移动则表明此ACK是重复ACK,应该丢弃;窗口右边沿向左移动叫做窗口收缩,一般不会有人这样做。
当左边沿和右边沿重合的时候表明窗口大小是0,此时发送方不应该在发送数据了,因为接收方的接收缓冲区已满,用户进程还没以接收。当用户进程接收完成后,接收方应该发送一个ACK,表明此时的接收窗口已经恢复,此ACK的序号同前一个win为0的ACK相同。
同样,在实现中,发送方不必发送一个全窗口的数据,但是它当然可以这样做。ACK总是将窗口向右边滑动,窗口的大小可以减小,接收方在发送ACK之前不必等待窗口被填满(即变为0),很多实现是收到两个数据报文段后立刻发送ACK。
Ø TCP窗口大小的调整
TCP窗口的大小通常由接收端来确认,也就是在TCP建立连接的第二个SYN+ACK报文的Win字段来确认。
当然,程序可以随时改变这个窗口(缓存)的大小。默认的窗口大小是4096字节,但是对于文件传输来说这并不是一个理想的数字,如果程序的主要目的是传输文件,那么最好将这个缓存设置到最大,但是这样可能会造成发送端连续发送多个数据报文段后,接收方才反馈一个ACK的情况,当然,这也没有什么不可以的,只要不超时,就不算错。
Ø TCP的PUSH包
PUSH是TCP报头中的一个标志位,发送方在发送数据的时候可以设置这个标志位。该标志通知接收方将接收到的数据全部提交给接收进程。这里所说的数据包括与此PUSH包一起传输的数据以及之前就为该进程传输过来的数据。
当Server端收到这些数据后,它需要立刻将这些数据提交给应用层进程,而不再等待是否还有额外的数据到达。
那么应该合适设置PUSH标志呢?实际上现在的TCP协议栈基本上都可以自行处理这个问题,而不是交给应用层处理。如果待发送的数据会清空发送缓存,那么栈就会自动为此包设置PUSH标志,源于BSD的栈一般都会这么做,而且,BSD
TCP STACK也从来不会将收到的数据推迟提交给应用程序,因此,在BSD TCP
STACK中,PUSH位是被忽略的,因为根本就没有用。
Ø TCP的慢启动(拥塞窗口)
TCP在局域网环境中的效率是很高的,但是到了广域网的环境中情况就不同了,在发送方和接收方之间可能存在多个Router以及一些速率比较慢的链路,而且一些中继路由器必须缓存分组,还可能分片,所以在广域网的环境中,TCP的效率可能出现问题。
为了解决这个问题,现在的TCP栈都支持"慢启动"算法,即拥塞窗口控制算法。该算法通过观察到新分组进入网络的速率与另一端返回ACK的速率相同而工作。其实,拥塞窗口是发送方使用的一种流量控制算法。
慢启动为TCP的发送方增加了一个拥塞窗口,当连接建立时,拥塞窗口被初始化为一个报文段大小,每收到一个ACK,拥塞窗口就会增加一个报文段,发送方取拥塞窗口与通过窗口的最小值作为发送的上限。
Ø TCP成块数据吞吐量
TCP窗口大小,窗口流量控制,慢启动对TCP的成块数据传输综合作用,可能对TCP的数据传输有意想不到的影响。
RTT(Round-Trip Time)
:往返时间。是指一个报文段从发出去到收到此报文段的ACK所经历的时间。通常一个报文段的RTT与传播时延和发送时延两个因素相关。
在发送的过程中有可能发生这样的情况,即TCP两端的传输"管道"被填满,即整个管道上都有数据在跑,此时不管拥塞窗口和通告窗口是多少,管道上都不能在容纳更多的数据了。此时每当接收方从网络上移去一个报文段,发送方就发送一个,但是管道上的ACK总是固定的,这种情况就是连接的理想稳定状态。
一般情况下带宽*时延就是一条线路的容量,因此吧RTT减小可以增加一条线路的容量,注意RTT加大的意思时传输时间减小!
当数据由一个大的管道向一个小的管道传输时,就有可能发生拥塞,例如,当若干输入流到达一个路由器,而此路由器的输出带宽小于这些输入流的带宽总和时,就会发生拥塞。这种情况普遍见于局域网与广域网的接口处。如果发送方处于局域网,而且不使用慢启动,使用局域网的带宽尽快的发送报文,那么返回的ACK之间的间隔与最慢的广域网链路一致。而且,由于路由器转发包速度慢,所以路由器就有可能主动丢失分组包。
Ø TCP的紧急方式
TCP提供了一种"紧急方式"的数据传输方式,TCP的一端可以告诉另一端有些具有某种方式的紧急数据被放在了普通的数据流中,接收方可以自行选择处理。紧急方式客厅通过设置TCP的URG标识位与紧急指针的偏移量来设置。这个紧急指针指向紧急数据的最后一个字节(也有可能是最后一个字节的下一个字节)。
现在有许多实现将紧急方式叫做"带外数据",其实这是不正确的。
目前紧急指针被用来禁止停止FTP的数据传输。不过总的来说,用的不多。

对于数据传输来说,如果用紧急数据来传输大量数据,这种方法显然是不可取的,再建立一个TCP连接不是更简单有效吗?

http://blog.csdn.net/baodi_z/archive/2008/03/25/2215798.aspx

Linux内核是如何工作的?

本文发表于Linux Format magazine杂志,作者从技术深度上解释了Linux Kernel是如何工作的。相信对Linux开发者来说有不小的帮助。
牛津字典中对"kernel"一词的定义是:"较软的、通常是一个坚果可食用的部分。"当然还有第二种定义:"某个东西核心或者最重要的部分。"对Linux来说,它的Kernel无疑属于第二种解释。让我们来看看这个重要的东西是如何工作的,先从一点理论说起。
广义地来说kernel就是一个软件,它在硬件和运行在计算机上的应用程序之间提供了一个层。严格点从计算机科学的角度来说,Linux中的Kernel指的是Linus Torvalds在90年代初期写的那点代码。
所有的你在Linux各版本中看到的其他东西--Bash shell、KDE窗口管理器、web浏览器、X服务器、Tux Racer以及所有的其他,都不过是运行在Linux上的应用而已,而不是操作系统自身的一部分。为了给大家一个更加直观的感觉,我来举个例子,比如RHEL5的安装大概要占据2.5GB的硬盘空间(具体多大当然视你的选择安装来定),在这其中,kernel以及它的各个模块组件,只有47MB,所占比例约为2%。
在kernel内部

那么kernel到底是如何工作的呢?如下面的图表。Kernel通过许多的进入端口也就是我们从技术角度所说的系统调用,来使得运行在它上面的应用程序可用。Kernel使用的系统调用比如"读"和"写"来提供你硬件的抽象(abstraction)。


从程序员的视角来看,这些看起来只是普通的功能调用,然而实际上系统调用在处理器的操作模式上,从用户空间到Kernel空间有一个明显的切换。同时,系统调用提供了一个"Linux虚拟机",可以被认为是对硬件的抽象。
Kernel提供的更明显的抽象之一是文件系统。举例来说,这里有一段短的程序是用C写的,它打开了一个文件并将内容拷贝到标准的输出:
#include <fcntl.h>
int main()
{
int fd, count; char buf[1000];
fd=open("mydata", O_RDONLY);
count = read(fd, buf, 1000);
write(1, buf, count);
close(fd);
}

在这里,你可以看到四个系统调用的例子:打开、读、写和关闭。不谈这段程序语法的细节,重点是:通过这些系统调用Linux Kernel提供了一个文件的"错觉",而实际上它不过是一堆数据有了个名字,这样一来你就不必去与硬件底层的堆栈、分区、头和指针、分区等交涉了,而是直接以例子中的方式与硬件"交流",这也就是我们所说的抽象(abstraction),将底层的东西以更易懂的方式表达出来。

台前幕后
系统文件是Kernel提供的较为明显的一种抽象。还有一些特性不是这么的明显,比如进程调度。任何一个时间,都可能有好几个进程或者程序等待着运行。
Kernel的时间调度给每个进程分配CPU时间,所以就一段时间内来说,我们会有种错觉:电脑同一时间运行好几个程序。这是另外一个C程序:
#include <stdlib.h>
main()
{
if (fork()) {
write(1, "Parent\n", 7);
wait(0);
exit(0);
}
else {
write(1, "Child\n", 6);
exit(0);
}
}

在这个程序中创建了一个新进程,而原来的进程(父进程)和新进程(子进程)都编写了标准输出然后结束。注意系统调用fork(), exit() 以及wait()执行程序的创建、结束和各自同步。这是进程管理和调度中最典型的简单调用。
Kernel还有一个更加不易见到的功能,连程序员都不易察觉,那就是存储管理。每个程序运行得都好像它有个自己的地址空间来调用一样,实际上它跟其他进程一样共享计算机的物理存储,如果系统运行的存储过低,它的地址空间甚至会被磁盘的交互区暂时寄用。存储管理的另外一个方面是防止一个进程访问其他进程的地址空间--对于多进程操作系统来说这是很必要的一个防范措施。
Kernel同样还配置网络链接协议比如IP、TCP和UDP等,它们在网络上提供机器对机器(machine-to-machine)和进程对进程(process-to-process)的通信。这里又会造成一种假象,即TCP在两个进程之间提供了一个固定连接--就好像连接两个电话的铜线一样,实际中却并没有固定的连接,特殊的引用协议比如FTP、DNS和HTTP是通过用户级程序来实施的,而并非Kernel的一部分。
Linux(像之前的Unix)在安全方面口碑很好,这是因为Kernel跟踪记录了每个运行进程的user ID和group ID,每次当一个应用企图访问资源(比如打开一个文件来写入)的时候,Kernel就会核对文件上的访问许可然后做出允许/禁止的命令。这种访问控制模式最终对整个Linux系统的安全作用很大。
Kernel还提供了一大套模块的集合,其功能包括如何处理与硬件设备交流的诸多细节、如何从磁盘读取一个分区、如果从网络接口卡获取数据包等。有时我们称这些为设备驱动。

模块化的Kernel
现在我们队Kernel是做什么的已经有了一些了解,让我们再来简单看下它的物理组成。早期版本的Linux Kernel是整体式的,也就是说所有的部件都静态地连接成一个(很大的)执行文件。
相比较而言,现在的Linux Kernel是模块化的:许多功能包含在模块内,然后动态地载入kernel中。这使得kernel的内核很小,而且在运行kernel时可以不必reboot就能载入和替代模块。
Kernel的内核在boot time时从位于/boot目录的一个文件加载进存储中,通常这个/boot
目录会被叫做KERNELVERSION,KERNELVERSION与kernel版本有关。(如果你想知道你的kernel版本是什么,运行命令行显示系统信息-r。)kernel的模块位于目录/lib/modules/KERNELVERSION之下,所有的组件都会在kernel安装时被拷贝。
管理模块
大部分情况下,Linux管理它的模块不需要你的帮忙,但是如果必要的时候有命令行可以来手动检查和管理模块。比如,为了查清楚当前到底哪个模块在载入kernel。这里有一个输出的例子:
# lsmod
pcspkr 4224 0
hci_usb 18204 2
psmouse 38920 0
bluetooth 55908 7 rfcomm,l2cap,hci_usb
yenta_socket 27532 5
rsrc_nonstatic 14080 1 yenta_socket
isofs 36284 0
输出的内容包括:模块的名字、大小、使用次数和依赖于它的模块列表。使用次数对防止卸载当前活跃的模块非常总要。Linux只允许使用次数为零的模块被移除。
你可以使用modprobe来手动加载和卸载模块,(还有两个命令行叫做insmod和rmmod,但modprobe更易于使用因为它自动移除了模块依赖)。比如lsmod的输出在我们的电脑上显示了一个名叫isofs的卸载模块,它的使用次数是零而且没有依赖模块,(isofs是一个模块,它支持CD上使用的ISO系统文件格式)这种情况下,kernel会允许我们卸载模块:
# modprobe -r isofs
现在,isofs不再显示在Ismod的输出中,kernel由此节省了36,284字节的存储。如果你放入CD并且让它自动安装,kernel将自动重新载入isofs模块,而且isofs的使用次数增加到1次。如果这时候你还试图移除模块,就不会成功了因为它正在被使用:
# modprobe -r isofs
FATAL: Module isofs is in use.

Lsmod只是列出了当前被载入的模块,modprobe则将列出所有可用的模块,它实际上输出了/lib/modules/KERNELVERSION目录下所有的模块,名单会很长!
实际上,使用modprobe来手动加载一个模块并不常见,但确实可以通过modprobe命令行来对模块设置参数,例如:
# modprobe usbcore blinkenlights=1
我们并不是在创建blinkenlights,而是usbcore模块的实参数。
那么如何知道一个模块会接受什么参数呢?一个比较好的方法是使用modinfo命令,它列出了关于模块的种种信息。这里有一个关于模块snd-hda-intel的例子
# modinfo snd-hda-intel
filename:
/lib/modules/2.6.20-16-generic/kernel/sound/pci/hda/snd-hda-intel.ko
description: Intel HDA driver
license: GPL
srcversion: A3552B2DF3A932D88FFC00C
alias: pci:v000010DEd0000055Dsv*sd*bc*sc*i*
alias: pci:v000010DEd0000055Csv*sd*bc*sc*i*
depends: snd-pcm,snd-page-alloc,snd-hda-codec,snd
vermagic: 2.6.20-16-generic SMP mod_unload 586
parm: index:Index value for Intel HD audio interface. (int)
parm: id:ID string for Intel HD audio interface. (charp)
parm: model:Use the given board model. (charp)
parm: position_fix:Fix DMA pointer (0 = auto, 1 = none, 2 =
POSBUF, 3 = FIFO size). (int)
parm: probe_mask:Bitmask to probe codecs (default = -1). (int)
parm: single_cmd:Use single command to communicate with codecs
(for debugging only). (bool)
parm: enable_msi:Enable Message Signaled Interrupt (MSI) (int)
parm: enable:bool
对我们来说比较有兴趣的以"parm"开头的那些部分:显示了模块所接受的参数。这些描述都比较简明,如果想要更多的信息,那就安装kernel的源代码,在类似于/usr/src/KERNELVERSION/Documentation的目录下你会找到。
里面会有一些有趣的东西,比如文件/usr/src/KERNELVERSION/Documentation/sound/alsa/ALSA-
Configuration.txt描述的是被许多ALSA声音模块承认的参数;/usr/src/KERNELVERSION
/Documentation/kernel-parameters.txt这个文件也很有用。
前几天在Ubuntu论坛有一个例子,说的是如何将参数传递到一个模块(详见https://help.ubuntu.com/community
/HdaIntelSoundHowto)。实际上问题的关键是snd-hda-intel参数在正确驱动声音硬件时需要一点操作,而且在boot time加载时会中止。解决方法的一部分是将probe_mask=1选项赋给模块,如果你是手动加载模块,你需要输入:
# modprobe snd-hda-intel probe_mask=1
更有可能,你在文件/etc/modprobe.conf中放置这样类似的一行:options
snd-hda-intel probe_mask=1
这"告诉"modprobe每次在加载snd-hda-intel模块时包含probe_mask=1选项。现在的有些Linux版本将这一信息分离进/etc/modprobe.d下的不同文件中了,而不是放入modprobe.conf中。
/proc系统文件
Linux kernel同样通过/proc系统文件来展示了许多细节。为了说明/proc,我们首先需要扩展我们对于文件的理解。除了认为文件就是存储在硬盘或者
CD或者存储空间上的持久信息之外,我们还应当把它理解为任何可以通过传统系统调用如:打开、读、写、关闭等访问的信息,当然它也可以被常见的程序访问。
/proc之下的"文件"完全是kernel虚拟的一个部分,给我们一个视角可以看到kernel内部的数据结构。实际上,许多Linux的报告工具均能够很好地呈现在/proc下的文件中寻到的格式化版本的信息。比如,一列/proc/modules将展示一列当前加载的模块。
同样的,/proc/meminfo提供了关于虚拟存储系统当前状态的更多细节信息,而类如vmstat的工具则是以一种更加可理解的方式提供了相同的一些信息;/proc/net/arp显示了系统ARP
cache的当前内容,从命令行来说,arp -a显示的也是相同的信息。
尤其有意思的是/proc/sys下的"文件"。/proc/sys/net/ipv4/ip_forward下的设置告诉我们kernel是否将转发IP数据包,也就是说是否扮演网关的作用。现在,kernel告诉我们这是关闭的:
# cat /proc/sys/net/ipv4/ip_forward
0
当你发现你可以对这些文件写入的时候,你会觉得更加有意思。继续举例来说:
# echo 1 > /proc/sys/net/ipv4/ip_forward
将在运行的kernel中打开IP 转发(IP forwarding)
除了使用cat和echo来检查和更正/proc/sys下的设置以外,你也可以使用sysctl命令:
# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 0
这等同于:
# cat /proc/sys/net/ipv4/ip_forward
0
也等同于:
# sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1
还等同于:
# echo 1 > /proc/sys/net/ipv4/ip_forward

需要注意的是,以这种方式你所做的设置改变只能影响当前运行的kernel的,当reboot的时候就不再有效。如果想让设置永久有效,将它们放置在 /etc/sysctl.conf文件中。在boot time时,sysctl将自动重新确定它在此文件下找到的任何设置。
/etc/sysctl.conf下的代码行大概是这样的:net.ipv4.ip_forward=1
性能调优(performance tuning)
有这样一个说法:/proc/sys下可写入的参数孕育了整个Linux性能调优的亚文化。我个人觉得这种说法有点过夸,但这里会有几个你确实很想一试的例子:Oracle
10g的安装说明(www.oracle.com/technology/obe/obe10gdb/install/linuxpreinst
/linuxpreinst.htm)要求你设置一组参数,包括:kernel.shmmax=2147483648
这将公用存储器的大小设置为2GB。(公用存储器是处理期内的通信机制,允许存储单元在多个进程的地址空间内同时可用)
IBM 'Redpaper'在Linux性能和调优方面的说明(www.redbooks.ibm.com/abstracts/redp4285.html)在调教/proc/sys下的参数方面给出了不少建议,包括:vm.swappiness=100
这个参数控制着存储页如何被交换到磁盘。
一些参数可以被设置从而提高安全性,如net.ipv4.icmp_echo_ignore_broadcasts=1
它"告诉"kernel不必响应ICMP请求,从而使得你的网络免受类如Smurf攻击之类的拒绝服务器(denial-of-service)型攻击。
net.ipv4.conf.all.rp_filter=1 则是"告诉"kernel加强入站过滤(ingress
filtering)和出站过滤(egress filtering)
那么有没有一个说明能涵盖这所有的参数?好吧,这有一行命令:# sysctl -a
它将展示所有的参数名字和当前值。列表很长,但是你无法知道这些参数是做什么的。另外比较有用的参考是Red Hat Enterprise Linux Reference Guide,对此有整章节的描述,你可以从www.redhat.com/docs/manuals/enterprise上下载。

网络新语

一、喝酒就象在喝汤,此人一定在工商! 喝酒就象喝凉水,此人肯定在建委!
人均一瓶不会剩,工作一定在财政! 喝酒谁也不用劝,工作肯定在法院!
举起酒杯一口干,工作必定是公安! 一口能干二两五,这人一定在国土!
喝掉八两都不醉,这人他妈是国税! 天天醉酒不受伤,老弟八成在镇乡!
白酒啤酒加红酒,肯定是个一把手! 酒后啥子都不怕,领导必定在人大!
成天喝酒不叫苦,哥们高就在政府! 一夜喝酒都不歇,保准任职在政协!
喝酒只准喝茅台,这位领导中央来!
二、资深高级白领教你如何把普桑开出宝马的境界:
1.手摇玻璃的时候精良的匀速,这样别人以为你用的是电动玻璃
2.停车的时候要一直踩着刹车,这样别人以为你的车是自动挡的
3.起步挂1档的时候要先进一下倒档,然后在挂前进,后车会认为你开的是自动档
4.刹车的时候配合手刹,这样别人看起来车是往下坐的,不会点头,和宝马一样
5.遇到沙尘暴天气把大灯打开,别人会以为你的车有自动感应系统
6.找一盘已经录好"前方700米转弯""1000米后掉头行驶"的磁带放到咔唑里面,别人以为车上有GPS导航
7.把自己可路的CD表面写上"MP3",别人会以为你的鸡头可以播放MP3
8.请开车时尽量放低身子,外面人会以为这车是无人驾驶。。。
9.把车顶锯掉,换成布的,别人以为是敞蓬车。。。
10、在车顶贴一块灰色塑料布,别人以为你是全景天窗
11.上车的时候,从右边门进,别人以为你有司机。也可能以为你的车是欧美系原装进口的。
12.拿个收破烂的喊话器对着窗子朝外喊:'前面车靠边;别人以为是开道车。
三、说河南人商业诈骗,广东人笑了;说河南人卖假货,浙江人笑了
说河南人贫穷落后,甘肃人笑了;说河南人斤斤计较,上海人笑了
说河南人夸夸其谈,北京人笑了;说河南人小偷小摸,新疆人笑了
说河南人都是河南人,山西人笑了;说河南人没思想,老子、庄子、墨子、韩非子都笑了
说河南人没文化,杜甫、韩愈、白居易、李贺、李商隐笑了;说河南人不懂艺术,吴道子笑了
说河南人不会武功,少林和尚笑了;
说河南人不爱运动,邓亚萍,刘国梁,孙甜甜都笑了,
说河南人不会打仗,刘秀、岳飞都笑了;说河南人只看脚下,张衡笑了
说河南人不刚烈,杨靖宇笑了;说河南人见义不勇为,李学生笑了
说河南人不会唱歌 ,陈明,李娜,黄鹤祥.,,,都笑了,
说河南人不出美女,林青霞,赵雅芝笑了
说河南小伙不帅?潘安笑了, 说河南人光会说河南话
央视海霞、沙桐、张泽群都笑了
说河南女人懦弱,花木兰笑了 说河南人不会做生意,双汇,新飞笑了
说中国没有诺贝尔奖获得者,崔崎笑了,说河南人近代文人骚客不多?姚雪垠,二月河笑了,
说河南人不懂C++?五笔字型输入法的发明人王永民笑了,
说河南省03年已连续7年全国经济总量(GDP)的排名在全国排名第五!
河南人说我们还要努力!!! 说河南不是华人发祥地,伏羲女娲笑了
伏羲女娲笑过了,轩辕黄帝也笑了;说河南人就这么多伟人,河南人都笑了
说河南人就说河南人,说俺变态也要说河南人;小日本和心理医生偷偷笑了...........
四、克隆涨停
1、ATM取出假钱--->银行无责
2、网上银行被盗--->储户责任
3、银行多给了钱--->储户义务归还
4、银行少给了钱--->离开柜台概不负责
5、ATM机出现故障少给钱--->用户负责
6、ATM机出现故障多给钱--->用户盗窃,被判无期
7、广东开平银行行长贪污4亿--->判12年
8、ATM多吐17万给老百姓许霆--->判无期
声明:转发不会送人民币,但如果觉得作为消费者,全中国十三亿储户不公平的就请顶起
五、记者问:啥叫"两会"?
农民代表答:会养猪、会交配。工人代表答:会挣钱、会消费。
民工代表答:会讨薪、会下跪。保姆代表答:会做饭、会叠被。
退休代表答:会健身、会养胃。小姐代表答:会上床、会收费。
艺人代表答:会炒作、会陪睡。文人代表答:会抄袭、会拼对。
商人代表答:会赚钱、会逃税。官员代表答:会撒谎、会受贿。
网民代表答:会忽悠、会聚会。祝您会逍遥、会陶醉
六、情人节到了:
GG问MM:你喜欢什么花? MM羞答答道:我喜欢两种花。
GG急切的问:哪两种?我送给你! MM低头小声说:有钱花,随便花!
GG傻傻的说:你真美!MM妩媚的问:我哪美?GG深情的说:想得美!
GG问MM:我们成家后,家务你没问题吧?
MM羞答答道:家务我还行,就两件事不会。
GG急切的问:哪两件? MM低头小声说:我这也不会,那也不会!
八、星期一,我上车,除了坐车的1块钱什么都没带。从起点站坐到终点站,自我感觉一路平静。但是在终点站下车时,发现裤子里多了张纸条:"一个大人出门一个子都不带,丢不丢人啊。--
"
星期二,我揣了个破钱包,里面装了1毛钱。到了终点站后,发现钱还在,钱包里被塞了张纸条:"我们不是乞丐,请不要侮辱我们的职业。--
"
星期三,我还是揣破钱包,里面装了100块假钞。到了终点站后,发现钱仍然还在,钱包里被塞了张纸条:"私藏大面值假币是违法行为,请自觉去相关部门上交。--
"
星期四,我拿了个信封,里面装了叠过期的海峡人才报。到了终点站后,发现信封还在,拿出报纸一看,报纸被换成了最新的海峡人才报,外带1张纸条:"现在是资讯时代,及时更新信息,才能把握机会,赢得成功!--
"
星期五,我在衣兜里放了个玩具手机。到了终点站后,手机仍在,多了张纸条:"请不要开此玩笑影响我公司正常工作。--
"
星期六,我拿了把玩具手枪插在腰上。到了终点站后,发现枪不见了,裤腰里被塞了张纸条:"最讨厌你们这些抢劫的,一点技术含量都没有!没收作案工具!--
"
星期日,我正准备上车,结果人太多没挤上去。正等下一班车的时候,我一摸口袋,发现多了20快钱,还有张纸条:"大哥,干我们这一行的整天风吹日晒的也不容易,敬上20块钱,您想去哪打车去吧,请您别再整我们。