2007年01月05日

环境:x86, FC3(Linux 2.6.11), g++ 3.4.2

编译:
tar zxvf qtopia-opensource-src-4.2.0.tar.gz
cd qtopia-opensource-src-4.2.0
echo yes | ./configure
make
make install

运行:
cd qtopia-opensource-src-4.2.0
./scripts/runqvfb &
./scripts/runqtopia  &

2007-01-05 编译通过,没有错误。

国内镜像HTTP下载地址:
http://www.qtopia.org.cn/ftp/mirror/ftp.trolltech.com/

2006年12月13日

    早就耳闻线程是一个可怕的东西,弄不好会让人疯掉的,所以长期以来,我一向对线程相当谨慎,甚至有点敬而远之,我相信
    1)决不设计过于复杂的多线程代码;
    2)必须依靠多线程实现的逻辑,一定要能够相当简单。
    如果多线程逻辑复杂了,到时候出了错都不知道什么地方错了,而且错误无法复现,这是最要命的。

    最近需要用QtE做一个UI相关的库,问题是,使用这个UI库的程序是在非UI线程上调用这个库的,而QT的创作者Trolltech的技术人员谆谆教诲我们不要在非UI线程上做UI操作。说道这个要求,倒不是只有QT这么干的。因为UI操作最终要作用于存储显示内容的内存资源,如果容许多个线程能够操作这个资源,毫无疑问要想不出岔子,就需要用上线程同步,线程同步的代码谁来写呢?如果UI库自己来作同步,那么效率就很成问题,如果让使用UI库的programmer来做同步,那么programmer又会很难受,所以几乎所有的UI库干脆要求只有一个线程能够做UI操作,这个线程就是UI线程,其工作就是和一个抽水泵一样不停的接受事件然后分配给特定的widget。但是在QT上面,如果违反这个原则,在非UI线程上做UI操作,很有可能不会发生任何异常,但在某个时候,问题又会产生,“偷走你的银行存款,偷走你的女朋友“(Trolltech开发人员如此描述可能发生的问题),所以,要想保证质量,必须保证所有的UI操作都在UI线程上面进行。

    回到我的工作上来,为了达到能够在非UI线程(在QT的应用程序里,除了UI进程,其他的进程都叫“非UI线程“),我不得不用上postEvent,用这种方式,在非UI线程上抛出一个event,这个event会进入进程的eventloop,然后被UI线程的抽水泵抓住,这样,非UI线程就通过这种方式把UI操作转移到UI线程上。

    一切都进行得很好,单元测试也很顺利,但是使用我的库的程序员报告说调用一个函数的时候偶尔会出现SegmentationFault,因为不是总发生这种错误,第一反应就是和线程相关的逻辑有问题。为了复现这个错误,我编了一个只使用这个库里一个功能的程序,为了模拟在非UI线程中调用这个函数,我在一个死循环中通过pthread_create创建线程,在线程里面创建并显示一个widget,然后隐藏并释放它。这也算是一个压力测试。结果程序运行到261次循环的时候就被Killed了,这和报告的SegementationFault不一样,Killed一般意味着内存不足了,我仔细看了一下库的代码,逻辑很简单,new过的东西都被delete了,应该不会产生内存泄露,我有运行了一遍这个测试程序,还是被Killed了,而且还是杂261次循环的时候,我就心存疑问了,为什么总是在261次的时候,肯定是什么资源没有被释放,261次之后就消耗光了。当天没有找出什么端倪来,在回家的路上想到,pthread_create一般是需要被pthread_join的,会不会是因为没有pthread_join,所有有资源没有释放呢。第二天把程序修改了一下,不用pthread_join,可以把pthread的属性设成不用join。
   
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    pthread_t tid;
    pthread_create(&tid, &attr,&thread_show, this);
    pthread_attr_destroy(&attr);

    这样killed的问题没有,但是,Segementation Fault的问题出现了,经过反复运行测试程序,发现Fault出现的地方总是在delete一个widget之后,原来,我原来的代码中虽然创建和显示 widget的code保证是在UI线程上执行的,但是delete这个widget却很马虎的是从非UI线程上执行的。把这个修改过来之后, Segementation Fault就再没发生过。

    这个bug给了这样的教训
    1) pthread_create产生的线程占用资源,通过pthread_join回收资源,或者设成detached thread,这样就不需要pthread_join了,线程结束运行自动释放资源;
    2) 对UI的擦操作必须在UI线程上,不仅指的是widget的创建和显示,还包括widget的删除。

转自:http://blog.csdn.net/FallenOrc/archive/2005/05/06/372701.aspx

2006年11月08日

    今天开Apache,发现起不来。虽然前两天在WinXP上测试IIS6+PHP5了,但是我已经把IIS6的服务默认停止了的。而且,之后Apache还可以用的好好的。看看正在listen的端口,发现80端口已经被Skype占用了!打开Skype界面,工具-选项-连接,嗨!443端口也被他占用了。

2006年09月20日

WinAirCrackPack工具包是一款无线局域网扫描和密钥破解工具,主要包括airodump和aircrack等工具。它可以监视无线网络中传输的数据,收集数据包,并能计算出WEP/WPA密钥。

  编者按:最近,有媒体报道了无线局域网安全漏洞曝光的情况。在9月11日下午举行的无线局域网安全技术研讨会上,演示人员对迅驰笔记本电脑进行了安全测试,利用从网上下载的工具5分钟内破解了保护口令。在快速破解了迅驰无线网络密钥后,轻松连接到加密的无线局域网中,可获取合法用户的各种机密信息、电子邮件等,也能删除私密的文件、邮件,甚至传播电脑病毒。这一消息引起了广大网友的关注,究竟如何破解?相关专家发来一份整理出来的《迅驰密钥破解法》,详细介绍了破解的方法。

  1、破解软件简介

  WinAirCrackPack工具包是一款无线局域网扫描和密钥破解工具,主要包括airodump和aircrack等工具。它可以监视无线网络中传输的数据,收集数据包,并能计算出WEP/WPA密钥。

  2、实验环境系统组成

  2.1 硬件环境

  选用具有WEP和WPA加密功能的无线路由器或AP一台

  带有迅驰无线网卡的笔记本电脑两台(分别定义为STA1和STA2,作为合法无线接入用户)

  抓包无线网卡一块

  ü笔记本电脑一台(定义为STA3,作为入侵者)

  2.2 软件环境

  入侵者STA3 :WinAirCrackPack工具包,

  注意:STA3要开启在控制面板->管理工具->服务中开启Wireless Zero Config服务。

  3、实验拓扑图

  4、配置无线路由器(根据实际网络环境进行配置)

  (1)STA1连接上无线路由器(默认未加密)。右键单击屏幕下的 图标,选择“查看可用的无线网络”,弹出如图1所示的窗口。

  其中显示有多个可用的无线网络,双击TP-LINK连接无线路由器,一会儿就连接成功。

  (2)打开IE浏览器,输入IP地址:192.168.1.1(无线路由器默认局域网IP地址)。

  (3)登录无线路由器管理界面(用户名:admin,密码:admin)。

  单击界面左侧的“网络参数”下的“LAN口设置”选项,设置“IP地址”为192.168.1.8并保存,如图4所示。

  (4)打开IE浏览器,输入IP地址:192.168.1.8,重新登录无线路由器管理界面(注意本实验选择TP-LINK无线路由器,其他品牌产品如CISCO等均有类似配置选项),单击界面左侧的“无线设置”下的“基本设置”选项。

  1)选择“模式”为“54Mbps(802.11g)”;

  2)选择“密钥格式”为“ASCII码”;

  3)选择“密钥类型”为“64位”;

  4)设置“密钥1”为“pjwep”;

  5)单击“保存”。

  (5)当无线路由器设置好WEP密钥后,STA1需要重新连接上无线路由器(输入的密钥与无线路由器设置的密钥相同),一会儿就连接成功。

  (6)打开IE浏览器,输入IP地址:192.168.1.8,重新登录无线路由器管理界面,单击界面左侧的“DHCP服务器”下的“DHCP服务”选项,单击“不启用”并保存,如图8所示,单击“系统工具”下的“重启路由器”对路由器进行重启。

  5、破解WEP、WPA密钥的软件下载

  STA3从网上下载用于破解密钥的软件,具体操作步骤如下:

  (1)在Google搜索页面中输入“WinAircrackPack下载”进行搜索,如图9所示。

  点击上述页面中“安全焦点:安全工具-WinAircrackPack.zip”,弹出以下页面。

  (2)单击“下载”保存该软件,可解压到本地磁盘的任何位置(以下我们解压到E盘根目录下为例)。

  6、安装抓包无线网卡

  注:用于抓包无线网卡的驱动采用Atheros v4.2.1(可从www.wildpackets.com/support/downloads/driver_download/1下载),该卡必须采用Atheros AR5001, AR5002, AR5004, AR5005 或 AR5006芯片组,下表的网卡均可使用,本实验我们采用Netgear 的108M无线网卡(型号:WG511T)。

  (1)在STA3笔记本上安装抓包无线网卡驱动。插入无线网卡,弹出如图11所示的窗口。选择“否,暂时不”,单击“下一步”。

  (2) 选择“从列表或指定位置安装”,单击“下一步”。

  (3)选择“不要搜索”,单击“下一步”。

  (4)单击“从磁盘安装”,在弹出的窗口中单击“浏览”,选择E:\WinAircrackPack\atheros421@(注:atheros421@可以从www.wildpackets.com/support/downloads/driver_download/1下载)目录下的net5211文件,单击“打开”,然后单击“确定”,单击“下一步”,在安装的过程中弹出如图15所示的窗口。

  7、破解WEP密钥

  (1)让STA1和STA2重新连接上无线路由器。

  (2)在STA3笔记本电脑上运行airodump,该工具用来捕获数据包,按提示依次选择“16”:破解所用无线网卡的序号;

  “a”,选择芯片类型,这里选择atheros芯片;

  “6”,信道号,一般1、6、11是常用的信道号,选择“0”可以收集全部信道信息;

  “testwep”(该输入文件名可任意);

  “y”,破解WEP时候选择“y”,破解WPA时选择“n”。

  (3)回车后,进入以下界面。

  (4)当该AP的通信数据流量极度频繁(比如可以采用STA1与STA2对拷文件来产生数据流量),“Packets”所对应的数值增长速度就会越大。当大概抓到30万(如采用104位RC4加密需要抓到100万包)“Packets”时关闭airodump窗口,启动WinAircrack。

  (5)点击左侧的“General”进行设置,选择加密类型为“WEP”,添加捕获的文件(testwep.ivs)。

  (6)点击左侧的“Advanced”进行设置,选择“Aircrack”所在的位置。

  (7)全部设置完后,点击右下角的“Aircrack the key”按钮弹出以下窗口。

  (8)选择要破解网络的BSSID(本实验选择“1”),回车得到最终WEP密钥。。

  8、破解WPA密钥

  (1)修改无线路由器的加密类型和加密方法,并设置为WPA-PSK认证和TKIP加密方式。

  (2)在STA3笔记本上运行airodump,该工具用来捕获数据包,按提示依次选择“16”,“a”,“6”,“testwpa”(该输入文件名可任意),“n”。

  (3)回车后,进入以下界面

  (4)让STA1重新连接上无线路由器,airodump将捕获一个无线路由器与STA1四次握手的过程。

  (5)启动WinAircrack。

  (6)点击左侧的“General”进行设置,选择加密类型为“WPA-PSK”,添加捕获的文件(testwpa.cap)。

  (7)点击左侧的“Wpa”进行设置,选择一个字典文件(口令字典可下载:例如lastbit.com/dict.asp)。

  (8)全部设置完后,点击右下角的“Aircrack the key”按钮弹出以下窗口,可知已经捕获到一次握手的过程。

  (9)选择要破解网络的BSSID(本实验选择“2”),回车后经过几分钟的计算,最终得到WPA密钥。

  9、破解密钥后对网络的危害一例(伪造AP)

  一旦入侵者知道了无线网络的WEP或WPA密钥,就可以连接到本地局域网内,这样入侵者就享有与正常接入用户一样对整个网络访问的权限,进行深度攻击。入侵者可以利用IPBOOK(www.skycn.com/soft/11014.html)、SuperScan(www.skycn.com/soft/8061.html)等类似这样的工具扫描局域网内的计算机,计算机里面的文件、目录、或者整个的硬盘驱动器能够被复制或删除,甚至其他更坏的情况诸如键盘记录、特洛伊木马、间谍程序或其他的恶意程序等等能够被安装到你的系统中,这样后果是非常严重的。

  (1)简介

  当WEP或WPA密码被破解后,入侵者可能用该密码和其他无线接入点(AP)构造一个假网络,当伪装AP的信号强于正常AP或用户靠近伪装AP时,正常用户会很自然的接入到该假网络中,对于用户本身来说是感觉不到该网络的,就在用户正常收发邮件时,我们可以用类似CAIN( www.ttian.net/website/2005/0908/458.html)这样的工具进行POP3、telnet等口令的破解等攻击。

  (2)POP3口令破解

  1)打开CAIN。

  2)点击菜单栏“Configure”弹出以下窗口。

  3)选择一个将用于抓包的网络适配器,点击“确定”,选择“ ”和“ ”,然后点击“ ”开始监控抓包。

  4)正常用户开始收邮件,该软件可以对邮箱的登陆名和密码进行捕获。

  (3)被破解后的危害

  当黑客窃取了你的邮箱的用户名、密码、POP3服务器和SMTP服务器的IP地址后,就可以直接访问你的邮箱,你的邮件信息将完全暴露在黑客面前。

2006年09月19日

    WORD文档有两种密码,一种是打开密码,一种是文档保护密码。这个方法不能应用于“打开密码”。

    打开加密的word文档,文件->另存为->选择HTML格式,存为一个HTML文档,用记事本打开该HTML文件,搜索"<w:UnprotectPassword>",看到类似5BCECF7A的字样(如果密码是用的123的话)。
    接着我们用UltraEdit或其他类似的工具打开最初受保护的Word文档,搜索7ACFCE5B,搜索到后,都用8个0来代替,存盘退出。
    然后用Office打开Word文档,选项-安全性-取消文档保护,OK!
    MS还没有对这个漏洞提供补丁,短期可能还无法解决这个问题,建议以后的文档不要用这个东西进行保护了。

    2006-09-19,测试 Word2003 成功。

2006年09月08日

    昨天晚上终于通了。
    Ethernet连接Static Bus的cs2,受到了IDE的干扰(Yamon中配置了MEM_STCFG2[DTY]=6)。现在把Ethernet跳到了Static Bus的cs1。
 

2006年08月22日

预处理器(Preprocessor

1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)

#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL

我在这想看到几件事情:

?; #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)

?; 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。

?; 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。

?; 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。

2 . 写一个“标准”宏MIN ,这个宏输入两个参数并返回较小的一个。

#define MIN(A,B) ((A <= (B) ? (A) : (B))

这个测试是为下面的目的而设的:

?; 标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变为标准C的一部分,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。

?; 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。

?; 懂得在宏中小心地把参数用括号括起来

?; 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?

least = MIN(*p++, b);

3. 预处理器标识#error的目的是什么?

如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。

死循环(Infinite loops

4. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?

这个问题用几个解决方案。我首选的方案是:

while(1)

{

?}

 

一些程序员更喜欢如下方案:

for(;

{

?}

 

这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本答案是:“我被教着这样做,但从没有想到过为什么。”这会给我留下一个坏印象。

第三个方案是用 goto

Loop:

goto Loop;

应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。

数据声明(Data declarations

5. 用变量a给出下面的定义

a) 一个整型数(An integer

b)一个指向整型数的指针( A pointer to an integer

c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an integer

d)一个有10个整型数的数组( An array of 10 integers

e) 一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers

f) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers

g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer

h)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer

答案是:

a) int a; // An integer

b) int *a; // A pointer to an integer

c) int **a; // A pointer to a pointer to an integer

d) int a[10]; // An array of 10 integers

e) int *a[10]; // An array of 10 pointers to integers

f) int (*a)[10]; // A pointer to an array of 10 integers

g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer

h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer

人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?

Static

6. 关键字static的作用是什么?

这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:

?; 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。

?; 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。

?; 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。

大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。

Const

7关键字const有什么含意?

我只要一听到被面试者说:“const意味着常数”,我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出const意味着“只读”就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)

如果应试者能正确回答这个问题,我将问他一个附加的问题:

下面的声明都是什么意思?

const int a;

int const a;

const int *a;

int * const a;

int const * a const;

/******/

前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:

?; 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)

?; 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。

?; 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

Volatile

8. 关键字volatile有什么含意?并给出三个不同的例子。

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:

?; 并行设备的硬件寄存器(如:状态寄存器)

?; 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

?; 多线程应用中被几个任务共享的变量

回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。

假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。

?; 一个参数既可以是const还可以是volatile吗?解释为什么。

?; 一个指针可以是volatile 吗?解释为什么。

?; 下面的函数有什么错误:

int square(volatile int *ptr)

{

return *ptr * *ptr;

}

下面是答案:

?; 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

?; 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。

?; 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int *ptr)

{

int a,b;

a = *ptr;

b = *ptr;

return a * b;

}

由于*ptr的值可能被意想不到地该变,因此ab可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr)

{

int a;

a = *ptr;

return a * a;

}

位操作(Bit manipulation

9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置abit 3,第二个清除a bit 3。在以上两个操作中,要保持其它位不变。

对这个问题有三种基本的反应

?; 不知道如何下手。该被面者从没做过任何嵌入式系统的工作。

?; bit fieldsBit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到 Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。

?; #defines bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:

#define BIT3 (0×1 << 3)

static int a;

void set_bit3(void) {

a |= BIT3;

}

void clear_bit3(void) {

a &= ~BIT3;

}

一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=&=~操作。

访问固定的内存位置(Accessing fixed memory locations

10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。

这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:

int *ptr;

ptr = (int *)0x67a9;

*ptr = 0xaa55;

A more obscure approach is:

一个较晦涩的方法是:

*(int * const)(0x67a9) = 0xaa55;

即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。

中断(Interrupts

11. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。

__interrupt double compute_area (double radius)

{

double area = PI * radius * radius;

printf("\nArea = %f", area);

return area;

}

这个函数有太多的错误了,以至让人不知从何说起了:

?; ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。

?; ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。

?; 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。

?; 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。

*****

代码例子(Code examples

12 . 下面的代码输出是什么,为什么?

void foo(void)

{

unsigned int a = 6;

int b = -20;

(a+b > 6) ? puts("> 6") : puts("<= 6");

}

这个问题测试你是否懂得C语言中的整数自动转换原则,我发现有些开发者懂得极少这些东西。不管如何,这无符号整型问题的答案是输出是 >6。原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。如果你答错了这个问题,你也就到了得不到这份工作的边缘。

13. 评价下面的代码片断:

unsigned int zero = 0;

unsigned int compzero = 0xFFFF;

/*1’s complement of zero */

对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下:

unsigned int compzero = ~0;

这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里,好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避免的烦恼。

到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那么我就扔出下面的追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,而不是答案。不管如何,你就当是这个娱乐吧…

动态内存分配(Dynamic memory allocation

14. 尽管不像非嵌入式计算机那么常见,嵌入式系统还是有从堆(heap)中动态分配内存的过程的。那么嵌入式系统中,动态分配内存可能发生的问题是什么?

这里,我期望应试者能提到内存碎片,碎片收集的问题,变量的持行时间等等。这个主题已经在ESP杂志中被广泛地讨论过了(主要是 P.J. Plauger, 他的解释远远超过我这里能提到的任何解释),所有回过头看一下这些杂志吧!让应试者进入一种虚假的安全感觉后,我拿出这么一个小节目:

下面的代码片段的输出是什么,为什么?

char *ptr;

if ((ptr = (char *)malloc(0)) ==

NULL)

else

puts("Got a null pointer");

puts("Got a valid pointer");

这是一个有趣的问题。最近在我的一个同事不经意把0值传给了函数malloc,得到了一个合法的指针之后,我才想到这个问题。这就是上面的代码,该代码的输出是“Got a valid pointer”。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方法和你做决定的基本原理更重要些。

Typedef

:

15 Typedef C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:

#define dPS struct s *

typedef struct s * tPS;

以上两种情况的意图都是要定义dPS tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?

这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是:typedef更好。思考下面的例子:

dPS p1,p2;

tPS p3,p4;

第一个扩展为

struct s * p1, p2;

.

上面的代码定义p1为一个指向结构的指,p2为一个实际的结构,这也许不是你想要的。第二个例子正确地定义了p3 p4 两个指针。

晦涩的语法

16 . C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么?

int a = 5, b = 7, c;

c = a+++b;

这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成:

c = a++ + b;

因此, 这段代码持行后a = 6, b = 7, c = 12

如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题。

好了,伙计们,你现在已经做完所有的测试了。这就是我出的C语言测试题,我怀着愉快的心情写完它,希望你以同样的心情读完它。如果是认为这是一个好的测试,那么尽量都用到你的找工作的过程中去吧。天知道也许过个一两年,我就不做现在的工作,也需要找一个。

Nigel Jones 是一个顾问,现在住在Maryland,当他不在水下时,你能在多个范围的嵌入项目中找到他。 他很高兴能收到读者的来信,他的email地址是: NAJones@compuserve.com

References

?; Jones, Nigel, "In Praise of the #error directive," Embedded Systems Programming, September 1999, p. 114.

?; Jones, Nigel, " Efficient C Code for Eight-bit MCUs ," Embedded Systems Programming, November 1998, p. 66.

 

 

本文以RedHat9.0i386平台为例,剖析了从用户打开电源直到屏幕出现命令行提示符的整个Linux启动过程。并且介绍了启动中涉及到的各种文件。

  阅读Linux源代码,无疑是深入学习Linux的最好方法。在本文对Linux启动过程的介绍中,我们也尝试从源代码的视角来更深入的剖析Linux 的启动过程,所以其中也简单涉及到部分相关的Linux源代码,Linux启动这部分的源码主要使用的是C语言,也涉及到了少量的汇编。而启动过程中也执行了大量的shell(主要是bash shell)所写脚本。为了方便读者阅读,笔者将整个Linux启动过程分成以下几个部分逐一介绍,大家可以参考下图:

当用户打开PC 的电源,BIOS开机自检,按BIOS中设置的启动设备(通常是硬盘)启动,接着启动设备上安装的引导程序lilogrub开始引导Linux Linux首先进行内核的引导,接下来执行init程序,init程序调用了rc.sysinitrc等程序,rc.sysinitrc当完成系统初始化和运行服务的任务后,返回initinit启动了mingetty后,打开了终端供用户登录系统,用户登录成功后进入了Shell,这样就完成了从开机到登录的整个启动过程。

 

下面就将逐一介绍其中几个关键的部分:

第一部分:内核的引导(核内引导)

  Red Hat9.0可以使用lilogrub等引导程序开始引导Linux系统,当引导程序成功完成引导任务后,Linux从它们手中接管了CPU的控制权,然后CPU就开始执行Linux的核心映象代码,开始了Linux启动过程。这里使用了几个汇编程序来引导Linux,这一步泛及到Linux源代码树中的“arch/i386/boot”下的这几个文件:bootsect.Ssetup.Svideo.S等。

  其中 bootsect.S是生成引导扇区的汇编源码,它完成加载动作后直接跳转到setup.S的程序入口。setup.S的主要功能就是将系统参数(包括内存、磁盘等,由BIOS返回)拷贝到特别内存中,以便以后这些参数被保护模式下的代码来读取。此外,setup.S还将video.S中的代码包含进来,检测和设置显示器和显示模式。最后,setup.S将系统转换到保护模式,并跳转到 0×100000

  那么0×100000这个内存地址中存放的是什么代码?而这些代码又是从何而来的呢?

  0×100000这个内存地址存放的是解压后的内核,因为Red Hat提供的内核包含了众多驱动和功能而显得比较大,所以在内核编译中使用了“makebzImage”方式,从而生成压缩过的内核,在RedHat中内核常常被命名为vmlinuz,在Linux的最初引导过程中,是通过"arch/i386/boot/compressed/"中的head.S利用 misc.c中定义的decompress_kernel()函数,将内核vmlinuz解压到0×100000的。

  当CPU跳到 0×100000时,将执行"arch/i386/kernel/head.S"中的startup_32,它也是vmlinux的入口,然后就跳转到 start_kernel()中去了。start_kernel()"init/main.c"中的定义的函数,start_kernel()中调用了一系列初始化函数,以完成kernel本身的设置。start_kernel()函数中,做了大量的工作来建立基本的Linux核心环境。如果顺利执行完 start_kernel(),则基本的Linux核心环境已经建立起来了。

  在start_kernel()的最后,通过调用init ()函数,系统创建第一个核心线程,启动了init过程。而核心线程init()主要是来进行一些外设初始化的工作的,包括调用 do_basic_setup()完成外设及其驱动程序的加载和初始化。并完成文件系统初始化和root文件系统的安装。

  当 do_basic_setup()函数返回init()init()又打开了/dev/console设备,重定向三个标准的输入输出文件stdin stdoutstderr到控制台,最后,搜索文件系统中的init程序(或者由init=命令行参数指定的程序),并使用 execve()系统调用加载执行init程序。到此init()函数结束,内核的引导部分也到此结束了。

 

第二部分:运行init

 

  init的进程号是1,从这一点就能看出,init进程是系统所有进程的起点,Linux在完成核内引导以后,就开始运行init程序,。init程序需要读取配置文件/etc/inittabinittab是一个不可执行的文本文件,它有若干行指令所组成。在Redhat系统中,inittab的内容如下所示(以“###"开始的中注释为笔者增加的)

  #

  # inittab This file describes how the INIT process should set up

  # the system in a certain run-level.

  #

  # Author: Miquel van Smoorenburg, <miquels@drinkel.nl.mugnet.org>

  # Modified for RHS Linux by Marc Ewing and Donnie Barnes

  #

 

  # Default runlevel. The runlevels used by RHS are:

  # 0 – halt (Do NOT set initdefault to this)

  # 1 – Single user mode

  # 2 – Multiuser, without NFS (The same as 3, if you do not havenetworking)

  # 3 – Full multiuser mode

  # 4 – unused

  # 5 – X11

  # 6 – reboot (Do NOT set initdefault to this)

  #

  ###表示当前缺省运行级别为5(initdefault)

  id:5:initdefault:

 

  ###启动时自动执行/etc/rc.d/rc.sysinit脚本(sysinit)

  # System initialization.

  si::sysinit:/etc/rc.d/rc.sysinit

 

  l0:0:wait:/etc/rc.d/rc 0

  l1:1:wait:/etc/rc.d/rc 1

  l2:2:wait:/etc/rc.d/rc 2

  l3:3:wait:/etc/rc.d/rc 3

  l4:4:wait:/etc/rc.d/rc 4

  ###当运行级别为5时,以5为参数运行/etc/rc.d/rc脚本,init将等待其返回(wait)

  l5:5:wait:/etc/rc.d/rc 5

  l6:6:wait:/etc/rc.d/rc 6

 

  ###在启动过程中允许按CTRL-ALT-DELETE重启系统

  # Trap CTRL-ALT-DELETE

  ca::ctrlaltdel:/sbin/shutdown -t3 -r now

 

  # When our UPS tells us power has failed, assume we have a few minutes

  # of power left. Schedule a shutdown for 2 minutes from now.

  # This does, of course, assume you have powerd installed and your

  # UPS connected and working correctly.

  pf::powerfail:/sbin/shutdown -f -h +2 "Power Failure; System Shutting Down"

 

  # If power was restored before the shutdown kicked in, cancel it.

  pr:12345:powerokwait:/sbin/shutdown -c "Power Restored; Shutdown Cancelled"

 

  ###2345级别上以ttyX为参数执行/sbin/mingetty程序,打开ttyX终端用于用户登录,

  ###如果进程退出则再次运行mingetty程序(respawn)

  # Run gettys in standard runlevels

  1:2345:respawn:/sbin/mingetty tty1

  2:2345:respawn:/sbin/mingetty tty2

  3:2345:respawn:/sbin/mingetty tty3

  4:2345:respawn:/sbin/mingetty tty4

  5:2345:respawn:/sbin/mingetty tty5

  6:2345:respawn:/sbin/mingetty tty6

 

  ###5级别上运行xdm程序,提供xdm图形方式登录界面,并在退出时重新执行(respawn)

  # Run xdm in runlevel 5

  x:5:respawn:/etc/X11/prefdm -nodaemon

 

 以上面的inittab文件为例,来说明一下inittab的格式。其中以#开始的行是注释行,除了注释行之外,每一行都有以下格式:

 

  id:runlevel:action:process

 

  对上面各项的详细解释如下:

 

  1. id

 

  id是指入口标识符,它是一个字符串,对于gettymingetty等其他login程序项,要求idtty的编号相同,否则getty程序将不能正常工作。

 

  2. runlevel

 

  runlevelinit所处于的运行级别的标识,一般使用06以及Ss016运行级别被系统保留:其中0作为shutdown动作,1作为重启至单用户模式,6为重启;Ss意义相同,表示单用户模式,且无需inittab文件,因此也不在inittab中出现,实际上,进入单用户模式时,init直接在控制台(/dev/console)上运行/sbin/sulogin。在一般的系统实现中,都使用了2345几个级别,在 Redhat系统中,2表示无NFS支持的多用户模式,3表示完全多用户模式(也是最常用的级别),4保留给用户自定义,5表示XDM图形登录方式。7 9级别也是可以使用的,传统的Unix系统没有定义这几个级别。runlevel可以是并列的多个值,以匹配多个运行级别,对大多数action来说,仅当runlevel与当前运行级别匹配成功才会执行。

 

  3. action

 

  action是描述其后的process的运行方式的。action可取的值包括:initdefaultsysinitbootbootwait等:

 

  initdefault是一个特殊的action值,用于标识缺省的启动级别;当init由核心激活以后,它将读取inittab中的 initdefault项,取得其中的runlevel,并作为当前的运行级别。如果没有inittab文件,或者其中没有initdefault项, init将在控制台上请求输入runlevel

 

  sysinitbootbootwaitaction将在系统启动时无条件运行,而忽略其中的runlevel

 

  其余的action(不含initdefault)都与某个runlevel相关。各个action的定义在inittabman手册中有详细的描述。

 

  4. process

 

  process为具体的执行程序。程序后面可以带参数。

 

第三部分:系统初始化

 

  在init的配置文件中有这么一行:

 

  si::sysinit:/etc/rc.d/rc.sysinit

 

  它调用执行了/etc/rc.d/rc.sysinit,而rc.sysinit是一个bash shell的脚本,它主要是完成一些系统初始化的工作,rc.sysinit是每一个运行级别都要首先运行的重要脚本。它主要完成的工作有:激活交换分区,检查磁盘,加载硬件模块以及其它一些需要优先执行任务。

 

  rc.sysinit约有850多行,但是每个单一的功能还是比较简单,而且带有注释,建议有兴趣的用户可以自行阅读自己机器上的该文件,以了解系统初始化所详细情况。由于此文件较长,所以不在本文中列出来,也不做具体的介绍。

 

  当rc.sysinit程序执行完毕后,将返回init继续下一步。

 

第四部分:启动对应运行级别的守护进程

 

  在rc.sysinit执行后,将返回init继续其它的动作,通常接下来会执行到/etc/rc.d/rc程序。以运行级别3为例,init将执行配置文件inittab中的以下这行:

 

  l5:5:wait:/etc/rc.d/rc 5

 

  这一行表示以5为参数运行/etc/rc.d/rc/etc/rc.d/rc是一个Shell脚本,它接受5作为参数,去执行 /etc/rc.d/rc5.d/目录下的所有的rc启动脚本,/etc/rc.d/rc5.d/目录中的这些启动脚本实际上都是一些链接文件,而不是真正的rc启动脚本,真正的rc启动脚本实际上都是放在/etc/rc.d/init.d/目录下。而这些rc启动脚本有着类似的用法,它们一般能接受 startstoprestartstatus等参数。

  /etc/rc.d/rc5.d/中的rc启动脚本通常是KS开头的链接文件,对于以以S开头的启动脚本,将以start参数来运行。而如果发现存在相应的脚本也存在K打头的链接,而且已经处于运行态了( /var/lock/subsys/下的文件作为标志),则将首先以stop为参数停止这些已经启动了的守护进程,然后再重新运行。这样做是为了保证是当 init改变运行级别时,所有相关的守护进程都将重启。

  至于在每个运行级中将运行哪些守护进程,用户可以通过chkconfigsetup中的"System Services"来自行设定。常见的守护进程有:

  amd:自动安装NFS守护进程

  apmd:高级电源管理守护进程

  arpwatch:记录日志并构建一个在LAN接口上看到的以太网地址和IP地址对数据库

  autofs:自动安装管理进程automount,与NFS相关,依赖于NIS

  crondLinux下的计划任务的守护进程

  namedDNS服务器

  netfs:安装NFSSambaNetWare网络文件系统

  network:激活已配置网络接口的脚本程序

  nfs:打开NFS服务

  portmapRPC portmap管理器,它管理基于RPC服务的连接

  sendmail:邮件服务器sendmail

  smbSamba文件共享/打印服务

  syslog:一个让系统引导时起动syslogklogd系统日志守候进程的脚本

  xfsX Window字型服务器,为本地和远程X服务器提供字型集

Xinetd:支持多种网络服务的核心守护进程,可以管理wuftpsshdtelnet等服务

 

  这些守护进程也启动完成了,rc程序也就执行完了,然后又将返回init继续下一步。

 

第五部分:建立终端

 

  rc执行完毕后,返回init。这时基本系统环境已经设置好了,各种守护进程也已经启动了。init接下来会打开6个终端,以便用户登录系统。通过按Alt+Fn(n对应1-6)可以在这6个终端中切换。在inittab中的以下6行就是定义了6个终端:

  1:2345:respawn:/sbin/mingetty tty1

  2:2345:respawn:/sbin/mingetty tty2

  3:2345:respawn:/sbin/mingetty tty3

  4:2345:respawn:/sbin/mingetty tty4

  5:2345:respawn:/sbin/mingetty tty5

  6:2345:respawn:/sbin/mingetty tty6

  从上面可以看出在2345的运行级别中都将以respawn方式运行mingetty程序,mingetty程序能打开终端、设置模式。同时它会显示一个文本登录界面,这个界面就是我们经常看到的登录界面,在这个登录界面中会提示用户输入用户名,而用户输入的用户将作为参数传给login程序来验证用户的身份。

 

第六部分:登录系统,启动完成

 

  对于运行级别为5的图形方式用户来说,他们的登录是通过一个图形化的登录界面。登录成功后可以直接进入KDEGnome等窗口管理器。而本文主要讲的还是文本方式登录的情况:

  当我们看到mingetty的登录界面时,我们就可以输入用户名和密码来登录系统了。

  Linux的账号验证程序是loginlogin会接收mingetty传来的用户名作为用户名参数。然后login会对用户名进行分析:如果用户名不是root,且存在/etc/nologin文件,login将输出nologin文件的内容,然后退出。这通常用来系统维护时防止非root用户登录。只有/etc/securetty中登记了的终端才允许root用户登录,如果不存在这个文件,则root可以在任何终端上登录。 /etc/usertty文件用于对用户作出附加访问限制,如果不存在这个文件,则没有其他限制。

  在分析完用户名后,login将搜索/etc/passwd以及/etc/shadow来验证密码以及设置账户的其它信息,比如:主目录是什么、使用何种shell。如果没有指定主目录,将默认为根目录;如果没有指定shell,将默认为/bin/bash

  login程序成功后,会向对应的终端在输出最近一次登录的信息(/var/log/lastlog中有记录),并检查用户是否有新邮件( /usr/spool/mail/的对应用户名目录下)。然后开始设置各种环境变量:对于bash来说,系统首先寻找/etc/profile脚本文件,并执行它;然后如果用户的主目录中存在.bash_profile文件,就执行它,在这些文件中又可能调用了其它配置文件,所有的配置文件执行后后,各种环境变量也设好了,这时会出现大家熟悉的命令行提示符,到此整个启动过程就结束了。

希望通过上面对Linux启动过程的剖析能帮助那些想深入学习Linux用户建立一个相关Linux启动过程的清晰概念,进而可以进一步研究Linux接下来是如何工作的。

 

 

2006年03月09日

最近都在研究 Message Queue On Linux.

有点成果了。

2006年02月23日

http://blog.donews.com/Yolanda/archive/2005/01/12/236605.aspx
不知道这段话在说什么

#include QString locate("data",QString("amarok/images"));
可以返回amarok安装目录的绝对路径 /opt/kde3/share/apps/amarok/images
Qt QPalette QColorGroup
真是奇怪阿,Disabled状态竟然调的是QColorGroup::Text,让我猜了好久。
Qt setPaletteBackgroundPixmap();
可以使窗体擦除时无闪动重载
QPushButton::drawButton(QPainter *p);
QPushButton::paintEvent(QPainEvent *event);
可以加速画button的速度例如:
void CSkinButton::paintEvent(QPaintEvent * e)
 //It is this function that make the repaint very quick,why??? I don’t know very well.
{
 if(toggleType() == QButton::Toggle)
 {
  //QSharedDoubleBuffer buffer( this );
  drawButton( & QPainter(this) );
  //This is very important , maybe it make the area small
 }
}

void CSkinButton::drawButton(QPainter * painter)
{
 if(isEnabled())
 {
  if(toggleType() == QButton::SingleShot)
  {
   painter->drawPixmap(0,0,*m_item->m_normalImage);
  }
   else if(toggleType() == QButton::Toggle)
   {
    if(state() == QButton::On)
    {
     //setPaletteBackgroundPixmap(*m_item->m_activeImage);
     painter->drawPixmap(0,0,*m_item->m_activeImage);
    }
    else
    {
     //setPaletteBackgroundPixmap(*m_item->m_normalImage);
     painter->drawPixmap(0,0,*m_item->m_normalImage);
    }
   }
 }
  else
  {
   painter->drawPixmap(0,0,*m_item->m_disabledImage);
  }
}
      
 用小图来填充大图
 QPixmap mLabPix2("/root/mywork/qtapps/test/images/label2.png");
 QPixmap mLabBigPix(1000,mFancyLab->height());
 int i;
 int widthBig = 1000;
 //width()-mFancyLab->width();
 int widthSml = mLabPix2.width();
 for(i = 0 ; i < widthBig; i+=widthSml)
 {
  copyBlt(&mLabBigPix, i, 0, &mLabPix2, 0, 0,widthSml, mFancyLab->height() );
 }
 if(mLabPix2.isNull())
 {
  qWarning("Can’t find the pixmap for fancy label");
 }
 else
 {
  mExtLab->setPixmap(mLabBigPix);
 }
   
     QMainWindow::addDockWindow ( QDockWindow * dockWindow, Dock edge = DockTop, bool newLine = FALSE ) ;
     添加布局,组件在QMainWiddow