在 Programming 分类下的文章

通过一道奇葩面试题来体验下awk的强大

面试题内容
需求:以"|"为分隔符,打印第一字段第一个字符是1,第二个字段第二个字符是2,第三个字段第三个字符是3,以此类推;倒数第二个字段前8个字符是当天日期如“20140610”。

源文件raw_data.txt:

$ date +%Y%m%d

我用Python实现的代码:
$ ./check_string.py

#!/usr/bin/env python

import datetime

def check_item(string):
    item_list = string.split('|')
    for item_id in range(0,len(item_list)):
        try:
            item_sub_list = list(item_list[item_id])
        except IndexError:
            return False

        special_item_id = len(item_list) - 2
        expect_item_sub_value = item_id + 1
        if item_id != special_item_id:
            try:
                if not item_sub_list[item_id] != expect_item_sub_value:
                    return False
            except IndexError:
                return False
        else:
            if not datetime.datetime.now().strftime("%Y%m%d") == ''.join(item_sub_list[0:8]):
                return False

    return True

if __name__=='__main__':
    filename = 'raw_data.txt'

    with open(filename) as fp:
        for line in fp:
            if check_item(line.replace('\n','')):
                print "Matched: {0}".format(line.replace('\n',''))

网友“运维@苏东”用awk实现的代码:
$ cat raw_data.txt | awk -F\| 'BEGIN{d=strftime("%Y%m%d")} { i=1;j=NF-1;k=0;while(i<j-1){if ( substr($i,i,1) == i ){k++;}; i++}; if (k==i-1 && substr($j,1,8) == d && substr($NF,NF,1) == NF) {print $0} }'

,

2 Comments

一道面试题

题目: 有一个长度是101的数组,存有1 ~ 100这100个数字,其中一个是重复的。请设计一个算法找出这个重复的数字

 
#!/usr/bin/env python
#-*- coding:utf-8 -*-

arr1 = [5,2,1,9,6,2,8]
arr2 = {}.fromkeys(arr1).keys()
print arr2
#效率高,但不符合题目要求

arr3 = list(set(arr1))
print arr3
#效率更高,但仍然不符合题目要求

arr4 = sorted(arr1)
i = len(arr4) - 1
for x in range(i):
    y = x + 1
    if arr4[x] == arr4[y]:
        print arr4[x]
#达到题目要求了,可惜面试时间已过
#当时怎么就没有想到用sorted这个方法呢
#还是自己技术太差了
#惭愧啊惭愧

有哪位前辈有效率更高的算法,还请告诉我这个菜鸟,谢谢。

12 Comments

在Python中使用Dict来传递参数

今天在帮Wianm修改一个Python脚本的时候,学到了一个有趣的东西,那就是如何在Python中使用Dict来传递参数。
在此之前我只会用{0} .format() 和 %s 的方式,将要传递的变量一个个的写在后面。

代码内容:

 
family = {"dad": "Dong", "mum": "Hong", "kid": "Yun"}
print "Dad:%(dad)s Mum:%(mum)s Kid:%(kid)s" % family 
print "Dad:%s Mum:%s Kid:%s" % tuple(family.values())

打印结果:

 
Dad:Dong Mum:Hong Kid:Yun
Dad:Dong Mum:Hong Kid:Yun

3 Comments

通过 thread dump 分析找到高CPU耗用与内存溢出的Java代码

首先,要感谢我的好朋友 钊花 的经验分享。

相信大家在实际的工作当中,肯定会遇到由代码所导致的高CPU耗用以及内存溢出的情况。
通常这种情况发生时,我们会认为这些问题理所当然的该由开发人员自己去解决,因为操作系统环境是没有任何问题的。

但实际上,我们是可以帮助他们的,效果好的话还可以定位到具体出问题的代码行数,思路如下:
1.通过对CPU与内存的耗用情况判断是否存在问题;
2.通过top命令找到可疑的线程的ID;
3.确认应用服务器的console信息的输出位置;
4.通过kill -3 ID 的方式获取thread dump信息;
5.备份console日志,并通过线程ID检查与代码相关的信息。

下面,我们开始具体的操作过程:

1.通过对CPU与内存的耗用情况判断是否存在问题;
判断CPU可以直接在top中查看CPU的 %us 数值就可以了,通常在50%以下都算正常,一旦超过60%甚至高到80-90且稳居不下,那么就肯定存在问题了;
判断内存时需要注意的是,直接看第一行Mem:的used数值是不准确的,因为通常在运行一段时间后,这个值都会非常大,这是因为Linux对内存的使用理念“内存就是拿来用的”,它会将空闲的内存尽可能的用于缓存,来提升性能,所以我们应该更多的参考 -/+ buffers/cache: 这一行的used数值,如果该值与第一行的used数值很接近且与内存总量相差很少的话,那么内存才算处于紧张的状态;
虚拟内存的耗用也值得参考,Linux只有在内存确实不够用的情况下才会大量使用虚拟内存,如果虚拟内存使用值为0或者100兆左右,那么可以确定物理内存尚且充足;
系统负载的数值也很值得参考,即top中的 load average,通常小于 1 代表非常良好,1-5之间代表正常,大于5 则表明系统处于负载的状态,超出的数值越大负载越大。

2.通过top命令找到可疑的线程的ID;
执行top命令,在这个Linux的“任务管理器”中输入大写的“H”来打开线程模式,然后所有线程会默认以CPU耗用高低排序;
如果想以内存进行排序,再敲入大写的“M”即可,其中“RES”看到的就是物理内存的耗用大小,但由于线程的共享内存原理,我们看到的每个线程的内存大小都和他们所属进程是一样的。

3.确认应用服务器的console信息的输出位置;
通常来说,应用服务器的console信息都会输出到log当中,比如Tomcat会输出到catalina.out文件中。
但jboss与weblogic就需要进行一些修改设置才行,其中:
jboss如果使用了官方的启动脚本,则需要修改启动脚本中的如下两行中的 /dev/null,将其改为指定的log文件位置,如 /opt/jboss_console.log,再重启jboss即可。
---------------------------------------------------------------------
JBOSS_CONSOLE="/dev/null"

JBOSS_CONSOLE=${JBOSS_CONSOLE:-"/dev/null"}
---------------------------------------------------------------------

weblogic则需要在console管理界面中的“域结构”-“环境”-“区域”-“服务器”-“AdminServer”-“日志记录”-“高级”- 勾选“已启动重定向标准输出日志记录”,再点击“激活更改”并重启weblogic即可。然后所有的console信息都会输出到logs目录下的AdminServer.log文件中。

4.通过kill -3 ID 的方式获取thread dump信息;
确认了应用服务器的console信息输出,并找到可疑的线程ID之后,执行 kill -3 ID 即可将该线程这一刻的所有thread dump信息输出到log当中。
建议多执行几次该命令,以便获取充足的信息。

5.备份console日志,并通过线程ID检查与代码相关的信息。
执行了thread dump信息输出之后,立刻使用cp命令备份log文件;
将log文件下载到本地,将进程ID换算成为十六进制,比如 29433 换算为十六进制就是 72f9;
打开log文件(建议使用UE或Notepad++等编辑器),搜索包含 72f9 的相关信息,然后仔细检查相关代码。

以我的实际情况为例:
下面这一段信息就包含了“nid=0x72f9”,表明与进程29433相关,所提到的 JIoEndpoint.java 则是实际的代码文件,后面的 442 则代表具体的行数。
---------------------------------------------------------------------
"http-0.0.0.0-80-78" daemon prio=10 tid=0x00000000686db800 nid=0x72f9 in Object.wait() [0x000000004cea6000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00002aab0bccd5b8> (a org.apache.tomcat.util.net.JIoEndpoint$Worker)
at java.lang.Object.wait(Object.java:485)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.await(JIoEndpoint.java:416)
- locked <0x00002aab0bccd5b8> (a org.apache.tomcat.util.net.JIoEndpoint$Worker)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:442)
at java.lang.Thread.run(Thread.java:619)
---------------------------------------------------------------------

到此,实际的工作才刚刚开始,需要开发人员仔细检查所有类似的信息,然后找出有问题的代码。

有资料说,thread dump 的作用其实还有很多,可以从中发现很多应用程序的运行问题,比如死锁等;且有一个Eclipse插件的可视化分析工具“lockness” 可以对该类日志进行更专业的分析。

No Comments

让SVN用户能够修改自身密码的PHP页面

在苦苦搜寻和反复实践之后,终于成功了,虽然该php程序的原作者已经无从知晓,但仍然要表示感谢,同时鄙视那些不经过实践就把文章转来转去的人。
因为那样做除了增加点可怜的PV,根本帮助不了任何人,只会浪费他人的时间。

源码安装SVN的过程请参考我的这篇文章:http://heylinux.com/archives/917.html

1.修改Apache配置文件
因为我在安装和配置SVN的时候,对Apache的配置文件进行过优化,将所有关于SVN的配置都写在了/opt/apache2/conf/extra/httpd-svn.conf中,然后再通过主配置文件/opt/apache2/conf/httpd.conf中Include conf/extra/httpd-svn.conf方式来调用。
因此,我这里需要修改的就是/opt/apache2/conf/extra/httpd-svn.conf文件,加入以下内容(其中第1,2,6行请根据实际情况进行修改):

Alias /svntools "/opt/apache2/htdocs/svntools"
<Directory "/opt/apache2/htdocs/svntools">
Require valid-user
AuthType Basic
AuthName "GridTeam`s subversion tools"
AuthUserFile "/opt/subversion/conf/svn_passwdfile"
</Directory>

2.创建修改自身密码的php页面
mkdir /opt/apache2/htdocs/svntools
cd /opt/apache2/htdocs/svntools

vim svnpass.php
输入以下内容(其中第135,136行请根据实际情况进行修改):
阅读全文 »

,

2 Comments

通过Perl脚本实现邮件客户端进行邮件交互测试

由于公司目前采用的自建邮件系统部署在内部机房,在网络链路上与国外,以及联通等网络存在着通讯的稳定性问题。

因此,随着企业邮箱使用的越来越频繁,这类问题反应越来越大,主要是国外出差的员工访问不了公司邮件系统,以及与其他公司通信时出现的邮件延迟甚至丢失情况。

因此,为了排除企业邮箱软件本身存在的Bug等造成的影响,特考虑进行一次大量的邮件交互测试,来确定软件本身是否存在问题。

在Linux下有很多办法可以实现,比如用Shell调用邮件本身的命令行指令等,但最好还是通过客户端的方式来实现,因为这种大量的测试行为会被其他邮箱认为是垃圾邮件行为的,把邮件服务器IP给纳入黑名单可不是好玩的。

后来,Cluster群里的好兄弟,UCWEB的罗学,给我共享了一个Perl的脚本,很简单,但是也很好用。利用这个脚本我完成了这样的一个测试工作,顺便提一下,结果很理想,呵呵,网易和搜狐成功接收1000封邮件,一封没落下(把发送邮件的地址加到白名单);而通过网易往回来发的时候到215封时测试机IP就被纳入黑名单了,不过这215封我们也全部成功接收了。

废话不多说了,进入正题:
首先,

通过root用户登录,执行
cpan

进入以后,再执行
install MIME::Lite
install Authen::SASL

这两个包,每个包大概要等个10来分钟的样子才会完全下载并安装好。

脚本内容:

#!/usr/bin/perl -w
use strict;
use MIME::Lite;
use MIME::Base64;
use Authen::SASL;

foreach my $a ( 1 .. 1000 ) {
  sleep 10;
  my $from = 'username@domain.com';
  my $passwd = 'password';
  my $to = 'username@163.com';
  my $messages = $a . ":" . "Hello Rainbow!";
  my $msg = MIME::Lite->new(
      From     =>  $from,
      To       =>  $to,
      Cc       =>  'username@sohu.com',
      Subject  =>  $a . ":" . 'Hello Rainbow!',
      Type     =>  'TEXT',
      Data     =>  $messages,
  );
  MIME::Lite->send('smtp','smtp.domain.com',
      Debug   =>'1',
      AuthUser=>$from,
      AuthPass=>$passwd,
  );
  $msg->send;
}

1 Comment

DOS下.bat批处理脚本编程学习笔记

DOS下.bat批处理脚本编程学习笔记
 
需要理解的一些概念:
1.什么是脚本?
脚本可以理解为是一种简单的程序(它的语法相对简单,不需要编译,是解释执行的)。
这一点和Linux下的shell是类似的,并且像php,Python这样流行的语言也属于脚本语言。
他们共同的特点是,不需要像C,Java那样需要经过编译成二进制文件后才可以运行,它们需要的,仅仅是一个专属的解释器。
2.什么是批处理?
我们可以理解为“批量处理”,将一些命令保存到一个文件中,然后一条条一次运行。当然,批处理的功能并不局限于此。
3.如何建立批处理程序?
新建一个文本文档,将代码写入后更改其扩展名为bat或cmd(NT中的另一种批处理文件)即可。
4.如何运行批处理程序?
无需编译等操作,直接在Windows下双击或DOS下输入批处理文件名即可运行。
5.批处理都有那些命令?
我们可以这样想,除了批处理专属的命令外,任何可以在DOS下使用的命令批处理都是可以使用的。
 
学习一门语言的最好的方法是:
1.有一本系统的、全面的、实用的、循序渐进的阶段性教程来进行指导;
《批处理阶段教程[英雄出品]》
2.有大量的源代码可供参考,通过对这些代码的分析、修改与借鉴来编写出属于自己的程序;
《70个批处理实用程序源码》
3.有一本相当实用的参考手册,可以在实际的代码编写中快速查阅所有命令的参数和实用方法等。
《DOS命令全集(中英文对照)》
 
还有,大家始终要记得批处理脚本的能力真的很有限,我们只需要用它来方便我们处理一些简单的问题就好了!
例如以下代码便是我根据日常工作需要编写的一个网速检测的脚本:

Windows XP版本:

 
@echo off
@echo 该程序用于对ping的返回结果进行分析判断
@echo 测试发送包大小为默认的32bytes
@echo ......................................
set /p SITE=请自定义测试网站地址(如www.baidu.com):
set /p TIMES=请自定义ping的次数(如10):
set /p MAX=请自定义可接受的最大延迟数,默认单位ms(如200):
set /p TIMEOUTMAX=请自定义可接受的最大掉包次数(小于%TIMES%):
@echo ......................................
 
goto FLUX
 
:FLUX ::定义模块,用于计算出当前电脑ping结果的最小值与掉包次数
ping -n %TIMES% %SITE% >ping.txt
find "Minimum" ping.txt >pingmin.txt
find "Lost" ping.txt >pingtimeout.txt
for /f "skip=2 tokens=3" %%M in (pingmin.txt) do set PING=%%M
echo %PING% >pingminnum.txt
for /f "tokens=1 delims=m" %%I in (pingminnum.txt) do set NUM=%%I
for /f "skip=2 tokens=10" %%J in (pingtimeout.txt) do set TIMEOUT=%%J
echo 最短:%NUM%ms 丢失:%TIMEOUT%/%TIMES%
if %TIMEOUT% GEQ %TIMEOUTMAX% (goto WARNING) 
if %NUM% GEQ %MAX% (goto WARNING) else goto CONTINUE
 
:WARNING
mshta vbscript:msgbox("网络已经差于预设值!请立刻采取相应措施!",64,"警告窗口")(window.close)
goto CONTINUE
 
:CONTINUE
goto FLUX ::从这里开始再次回到FLUX模块进行循环

Windows 7 版本:

@echo off
@echo 该程序用于对ping的返回结果进行分析判断
@echo 测试发送包大小为默认的32bytes
@echo ......................................
set /p SITE=请自定义测试网站地址(如www.baidu.com):
set /p TIMES=请自定义ping的次数(如10):
set /p MAX=请自定义可接受的最大延迟数,默认单位ms(如200):
set /p TIMEOUTMAX=请自定义可接受的最大掉包次数(小于%TIMES%):
@echo ......................................

goto FLUX
 
:FLUX ::定义模块,用于计算出当前电脑ping结果的最小值与掉包次数
ping -n %TIMES% %SITE% >ping.txt
find "最短" ping.txt >pingmin.txt
find "丢失" ping.txt >pingtimeout.txt
for /f "skip=2 tokens=3" %%M in (pingmin.txt) do set PING=%%M
echo %PING% >pingminnum.txt
for /f "tokens=1 delims=m" %%I in (pingminnum.txt) do set NUM=%%I
for /f "skip=2 tokens=8" %%J in (pingtimeout.txt) do set TIMEOUT=%%J
echo 最短:%NUM%ms 丢失:%TIMEOUT%/%TIMES%
if %TIMEOUT% GEQ %TIMEOUTMAX% (goto WARNING) 
if %NUM% GEQ %MAX% (goto WARNING) else goto CONTINUE
 
:WARNING
mshta vbscript:msgbox("网络已经差于预设值!请立刻采取相应措施!",64,"警告窗口")(window.close)
goto CONTINUE
 
:CONTINUE
goto FLUX ::从这里开始再次回到FLUX模块进行循环

No Comments

Python学习笔记 No.1

Python学习笔记  No.1
Python的相关介绍

--------------------------------------------------------------------------------

什么是Python?
Python(发音:[ 'paiθ(ə)n; (US) 'paiθɔn ]),是一种面向对象的解释性的计算机程序设计语言,也是一种功能强大而完善的通用型语言,已经具有十多年的发展历史,成熟且稳定。
Python 具有脚本语言中最丰富和强大的类库,足以支持绝大多数日常应用。
这种语言具有非常简捷而清晰的语法特点,适合完成各种高层任务,几乎可以在所有的操作系统中运行。
目前,基于这种语言的相关技术正在飞速的发展,用户数量急剧扩大,相关的资源非常多。
Python是如何诞生的?
Python的创始人为Guido van Rossum。1989年圣诞节期间,在阿姆斯特丹,Guido为了打发圣诞节的无趣,决心开发一个新的脚本解释程序,做为 ABC 语言的一种继承。
之所以选中 Python(大蟒蛇的意思)作为程序的名字,是因为他是一个Monty Python的飞行马戏团的爱好者。
ABC是由Guido参加设计的一种教学语言。就Guido本人看来,ABC 这种语言非常优美和强大,是专门为非专业程序员设计的。
但是ABC语言并没有成功,究其原因,Guido 认为是非开放造成的。Guido 决心在 Python 中避免这一错误(的确如此,Python 与其它的语言如C、C++和Java结合的非常好)。
同时,他还想实现在 ABC 中闪现过但未曾实现的东西。
就这样,Python在Guido手中诞生了。实际上,第一个实现是在Mac机上。
可以说,Python是从ABC发展起来,主要受到了Modula-3(另一种相当优美且强大的语言,为小型团体所设计的)的影响。并且结合了Unix shell和C的习惯。

Python在编程语言中的定位是什么?
虽然 Python 可能被粗略地分类为"脚本语言(scripting language)", 实际上一些大规模软件开发计划例如 Zope, Mnet 及 BitTorrent. Google也广泛地使用它。
 Python 的支持者较喜欢称它为一种高阶动态编程语言 , 原因是"脚本语言" 泛指单用作简单编程任务如 shell scripts ,而Python不能与JavaScript等只能处理简单任务的编程语言相提并论。

Python有哪些特色?
可扩充性可说是Python作为一种编程语言的特色。新的内置模块(module)可以用C 或 C++写成。而我们也可为现成的模块加上Python的接口。Python可以使用户避免过分的语法的羁绊而将精力主要集中到所要实现的程序任务上。
Python也被称为是一门清晰的语言。因为它的作者在设计它的时候,总的指导思想是,对于一个特定的问题,只要有一种最好的方法来解决就好了。
Python语言是一种清晰的语言的另一个意思是,它的作者有意的设计限制性很强的语法,使得不好的编程习惯(例如if语句的下一行不向右缩进)都不能通过编译。这样有意的强制程序员养成良好的编程习惯。其中很重要的一项就是Python的缩进规则。
一个和其他大多数语言(如C)的区别就是,一个模块的界限,完全是由每行的首字符在这一行的位置来决定的(而C语言是用一对花括号{}来明确的定出模块的边界的,与字符的位置毫无关系)。这一点曾经引起过争议。因为自从C这类的语言诞生后,语言的语法含义与字符的排列方式分离开来,曾经被认为是一种程序语言的进步。
不过不可否认的是,通过强制程序员们缩进(包括if,for和函数定义等所有需要使用模块的地方),Python确实使得程序更加清晰和美观。
另外Python在其他部分的设计上也坚持了清晰划一的风格,这使得Python称为一门易读性、易维护性好,并且被大量用户所欢迎的、用途广泛的语言。

Python目前有哪些局限?
虽然Python是一个非常成功的语言,但是也有必要明白它目前的局限。
1. 运行效率低下
目前为止,Python是所有主流脚本语言中速度比较慢的,这与其脚本引擎的设计思路有关。
如果你的应用对于速度有着较高的要求,就要考虑Python是否能满足需要。不过这一点可以通过使用C编写关键模块,然后由Python调用的方式加以部分解决。
2. 多线程支持欠佳
Python支持多线程,但是其运行效率也不高。
3. 适应其独特的语法
这也许不应该被称为局限,但是它用缩进来区分语句关系的方式还是给很多初学者带来了困惑。即便是很有经验的Python程序员,也可能陷入陷阱当中。最常见的情况是tab和空格的混用会导致错误,而这是用肉眼无法分别的。

Python的发展前景如何?
Python在编程领域的占有率一直处于稳步上升之中,根据最新的数据,Python排名第七。前六名分别是Java,C,VB,C++,PHP和Perl. 作为一个很年轻的语言,Python的位置已经相当令人振奋了。
随着微软将Python纳入.Net 平台,相信Python的将来会更加强劲发展。Python 很可能会成为.Net平台快速开发的主流语言。
著名的搜索引擎 Google 也大量使用Python。更加令人吃惊的是,在Nokia智能手机所采用的Symbian操作系统上,Python成为继C++,Java之后的第三个编程语言!可见Python的影响力之巨大。

--------------------------------------------------------------------------------

 
 
Python相关资料

--------------------------------------------------------------------------------

Python的几个著名社区:
1.http://www.python.org/ Python 的官方网站
2.http://python.cn/ Python 中文社区
3.http://www.woodpecker.org.cn/ 啄木鸟 Pythonic 开源社区
4.http://bbs.chinaunix.net/forum-55-1.html ChinaUnix Python版块
 
Python的开发环境:
1.Linux:
几乎所有的Linux和BSD发行版中都默认安装了Python,且vim也默认支持Python的语法高亮与彩色编码,通过在vim中安装官方的Python相关插件可以获得更好的效果。
2.Windows:
推荐Pythonwin,它很小巧也很实用,尤其适合初学者。
Pythonwin集成开发环境包含在由ActiveState出品的ActivePython中,ActivePython是一个二进制build。其中包括用于XML处理的expat模块,以及一系列windows工具。
提供的特性有:彩色编码、源代码折叠、单词完成及自动缩进。调试器支持事后诊断功能、标准的单步调试、断点设计及变量监视。
详情请访问http://aspn.activestate.com/activepython,下载后直接安装即可通过工具进行Python编程,无需单独安装Python。
 
Python的相关书籍:
《简明Python教程》《Python学习笔记》《Python入门》《Python编码规范》《Dive.Into.Python中文版》
  以上这些书籍都可以在啄木鸟Pythonic 开源社区中找到。

No Comments

想成为嵌入式程序员应知道的0x10个基本问题

转载引用地址:http://blog.chinaunix.net/u/13991/showart_109557.html
很经典的一套题目,作者 Nigel Jones 是住在 Maryland 的一名顾问。
-----------------------------------------------------------------------------------------------------------------------------------------
    C语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。
    这些年,我既参加也组织了许多这种测试,在这过程中我意识到这些测试能为面试者和被面试者提供许多有用信息。
    此外,撇开面试的压力不谈,这种测试也是相当有趣的。
    从被面试者的角度来讲,你能了解许多关于出题者或监考者的情况。
    这个测试只是出题者为显示其对ANSI标准细节的知识而不是技术技巧而设计吗?这是个愚蠢的问题吗?
    如要你答出某个字符的ASCII值。
    这些问题着重考察你的系统调用和内存分配策略方面的能力吗?这标志着出题者也许花时间在微机上而不是在嵌入式系统上。
    如果上述任何问题的答案是"是"的话,那么我知道我得认真考虑我是否应该去做这份工作。
    从面试者的角度来讲,一个测试也许能从多方面揭示应试者的素质:最基本的,你能了解应试者C语言的水平。
    不管怎么样,看一下这人如何回答他不会的问题也是满有趣。
    应试者是以好的直觉做出明智的选择,还是只是瞎蒙呢?
    当应试者在某个问题上卡住时是找借口呢,还是表现出对问题的真正的好奇心,把这看成学习的机会呢?
    我发现这些信息与他们的测试成绩一样有用。
    有了这些想法,我决定出一些真正针对嵌入式系统的考题,希望这些令人头痛的考题能给正在找工作的人一点帮助。
    这些问题都是我这些年实际碰到的。其中有些题很难,但它们应该都能给你一点启迪。
    这个测试适于不同水平的应试者,大多数初级水平的应试者的成绩会很差,经验丰富的程序员应该有很好的成绩。
    为了让你能自己决定某些问题的偏好,每个问题没有分配分数,如果选择这些考题为你所用,请自行按你的意思分配分数。

预处理器(Preprocessor)

--------------------------------------------------------------------------------

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

我在这想看到几件事情:
1) #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
2)懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
3) 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
4) 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。

2 . 写一个"标准"宏MIN ,这个宏输入两个参数并返回较小的一个。
 #define MIN(A,B) ((A) <= (B) ? (A) : (B)) 

这个测试是为下面的目的而设的:
1) 标识#define在宏中应用的基本知识。这是很重要的。因为在嵌入(inline)操作符变为标准C的一部分之前,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
2)三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。
3) 懂得在宏中小心地把参数用括号括起来.
4) 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?
        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 intege)
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有三个明显的作用:
1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。

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

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

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呢?我也如下的几下理由:

1) 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)
2) 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
3) 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

Volatile

--------------------------------------------------------------------------------

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

1) 并行设备的硬件寄存器(如:状态寄存器)
2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3) 多线程应用中被几个任务共享的变量

        回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。
        假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
1) 一个参数既可以是const还可以是volatile吗?解释为什么。
2) 一个指针可以是volatile 吗?解释为什么。
3) 下面的函数有什么错误:

int square(volatile int *ptr)
{
        return *ptr * *ptr;
}

下面是答案:
1)是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2); 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3) 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int *ptr)
{
    int a,b;
    a = *ptr;
    b = *ptr;
    return a * b;
}

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

long square(volatile int *ptr)
{
    int a;
    a = *ptr;
    return a * a;
}

位操作(Bit manipulation)

--------------------------------------------------------------------------------

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

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

1)不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
2) 用bit fields。Bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到 Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。
3) 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:

#define BIT3 (0x1 << 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;

一个较晦涩的方法是:

    *(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;
}

这个函数有太多的错误了,以至让人不知从何说起了:
1) ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
2) ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
3) 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
4) 与第三点一脉相承,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)
    puts("Got a null pointer");
else
    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

参考文献
1) Jones, Nigel, "In Praise of the #error directive," Embedded Systems Programming, September 1999, p. 114.
2) Jones, Nigel, " Efficient C Code for Eight-bit MCUs ," Embedded Systems Programming, November 1998, p. 66.

No Comments

共享一个自动格式化的C语言脚本

该脚本为我的上司--董哥亲情奉献(呵呵!),它大大减轻了我们的工作量,因为我们有时候会狂导硬盘,数量为100个以上。

 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
 FILE *fp;
 if((fp=popen("fdisk /dev/sda","w"))==NULL){
     printf("popen error! ");
     exit(0);
    }
 putc('n',fp);
 putc(' ',fp);
 sleep(1);
 putc('p',fp);
 putc(' ',fp);
 sleep(1);
 putc('1',fp);
 putc(' ',fp);
 sleep(1);
 putc(' ',fp);
 putc(' ',fp);
 sleep(1);
 putc('w',fp);
 putc(' ',fp);
 pclose(fp);
 return 0;
} 

 
保存为.c文件,执行 gcc 编译成.o文件运行即可。
它实现的功能是执行fdisk命令并自动接受相应的参数完成自动化的过程;
很仓促所以没有加入错误检查等功能,希望大家需要的可以参考,前辈们可以来不吝赐教。:)

No Comments