RSS Feed

January, 2012

  1. 程序的退出码

    January 8, 2012 by xudifsd

    在Linux下编程时,退出码是非常讲究的,并不能仅仅用0来表示正常,非0来表示不正常来简单地处理。这几天做了一些实验,记录下来,方便以后查看。

    shell以0为真!

    有下面一段C语言程序:

    #include <stdio.h>
    #include <stdlib.h>
    int main(){
    	exit(1);
    }

    它什么事也不做,只是将1返回给调用者,编译后再写一段shell脚本:

    #!/bin/bash
    if [ `./a.out` ];then
    echo “shell treat 1 as true”
    else
    echo “shell treat 0 as true”
    fi

    评论指出上面的程序是有问题的,在if中用反引号来执行命令不是判断命令的退出码,而是判断命令的输出。但是结论仍然是正确的,将脚本改成下面的代码即可:

    #!/bin/bash
    ./a.out && echo "shell treat 1 as true" || echo "shell treat 0 as true"

    运行结果竟然是“shell treat 0 as true”。大部分程序语言都把0作为假,把非0作为真,但是shell脚本却相反,这是因为Unix下的正经程序都是把0退出码当作程序正确运行的标志,而非0的值则可以指示错误的原因。这也是“cd foo && ls bar”这种命令能运行的原因,如果不知道shell把0当真,这种写法简直完全看不懂。

    退出码的范围

    可以将退出码分为三种类型:一种是返回0用于指示程序正常运行;一种是应用程序自定义的退出码,这种类型的退出码范围为1~127;还有一种会被解释为程序因为信号而退出,这种类型的退出码范围为129~255(唯一弄不懂的一点是如果使用不存在的参数调用git,例如“git add –foo”,退出码竟然是129!这应该是SIGHUP信号的退出码,而一般其他程序对于未知参数都是返回2或者1。根据邮件列表上的回复,git出现这个原因似乎是纯属偶然)。更详细的退出码范围列表在,虽然这是shell脚本的,不过还比较通用。 为什么exit(3)的原型的参数是int型,但是退出码的范围只有0~255呢?查看手册可知,exit会对它的参数与0377取并(0377是8进制数,也就是十进制的255),取并后就会只得到最后一个字节的值,所以范围只有0~255。 所以对于下面一段程序:

    #include <stdio.h>
    #include <stdlib.h>
    int main(){
    	exit(256);
    }

    编译运行后再使用“echo $?”会得到0而非256。
    也就是说父进程只能得到子进程一个字节的返回值,并且这个字节的最高位用于指示程序是否由信号而退出。

    但是如果应用程序使用了信号处理函数,那么在处理完清洁工作后需要自己调用exit(3),参数如何决定?信号处理函数的原型为:

    typedef void (*sighandler_t)(int);

    处理函数的参数用于指示信号的类型,所以在信号处理函数中可以用信号值加上128来告诉父进程自己是由于信号而退出的。


  2. Linux学习总结

    January 5, 2012 by xudifsd

    总是有些同学会问我一些Linux的问题,而且很多情况还要教实验室低年级的同学,但是授之以鱼不如授之以渔,所以总结一下我学Linux的经验,也方便别人查看。

    学习Linux的好处

    我总是觉得自己很幸运,因为我接触的第一个和计算机学科有关的就是Linux,在这之前从来没有接触过编程等东西,只是用电脑玩游戏而已。因此之后所有的计算机学习全是在Linux之下,包括编程,包括系统管理。为什么这样是幸运呢?我觉得主要有这些原因:

    1. Windows并不适合学习编程。
    2. 几乎所有的服务器都是使用Linux系统,这样一直使用Linux省去了还要学习的麻烦。
    3. 很多优秀的开源的软件都是最先支持Linux系统,之后再移植到各个平台上,而学习和使用这些软件可以很好地了解一些最新的技术,相反在Windows下就没有这么多的机会接触这些东西。

    说明一下第一点:虽然国内的一些技术书籍都是以Windows为平台,但是看了个讽刺你应该能了解一些吧。我觉得主要原因就是Windows下没有一个好的编辑器和编译器(虽然有好的IDE)。我强烈的觉得要想真正了解如何编程就必须了解程序如何编译和执行,而编辑器和编译器的分离可以强迫人们去了解这些,与这个分离策略相反的就是集成的开发环境,像VC++6.0,Eclipse,NetBeans,这些工具使用起来都是非常方便的,适合于进行正经的开发,但却不适合初学者,如果给刚接触编程的人装上这些IDE后让他们学习使用,他们肯定都能很快学会,因为只需要打上源代码,按下IDE上的编译、运行键就可以了,但是这样他们不知道程序到底怎么产生。上次就有个同学问我NetBeans怎么连接数据库啊?当时跟他解释半天不是NetBeans连接数据库而是Java连接而已。但是如果你在Linux中使用vim或emacs编辑再用gcc或者javac编译,那么你很容易就能知道编译的过程。通过知道这些细节可以了解程序如何运行,也能更好地理解程序的一些行为了,从而更好地学习编程。

    第二点:一般的用户完全不知道Linux是什么,因为Windows占85%以上的市场,在中国90%以上,而Linux只占1%,在中国就更少了,这些是桌面用户的统计,但是服务器方面完全相反,在Top500中使用Windows的系统也就一两台,其他的都是Unix系的,一般来说服务器都是使用Linux系统,为什么要在去管理服务器时再学Linux呢?直接把Linux作为主力操作系统完全可行,甚至连玩魔兽争霸都可以直接在Linux下用wine模拟。这样在日常使用中就可以学会Linux的系统维护,而不用再临时学习了。

    第三点:如果你看过github上的一些项目,很多项目都是支持直接在Linux上从源码进行安装,但是很少有项目都提供Windows的exe安装包,而直接从源码安装又会出现很多兼容性问题,甚至根本无法安装。就因为这一点,在Linux下可以尝试很多最新的开源软件,但是Windows绝对没有这么方便。一旦缺少这个条件你会错过很多好的学习机会。

    Shell

    Shell是Linux最重要的东西,在图形界面下一般叫做终端或者模拟终端,个回复指出了shell和terminal的区别,不过我的本意是指图形界面下叫做terminal这个名字的软件,它是个模拟终端,如果不去深究那么也可以叫它shell,不过真正的shell在现代的Linux系统也有,在Ubunut下可以使用Ctrl+Alt+F[1..7]转换,F7就是图行界面,通过ssh也可以直接连接远程shell),这个和Windows的cmd.exe很像,但是比cmd.exe要强大得多,很多Linux的工作都需要shell来完成,能够熟练使用shell可以说是非常重要的Linux技巧,这些技巧包括用管道连接各种小工具的输入输出,查询命令的参数,命令补齐,shell脚本编程等,举个例子吧,下面的代码是个shell脚本,总共不超过10行代码,它是干什么的呢?

    #!/bin/bash
    tr -cs A-Za-z\' '\n' |
    	tr A-Z a-z |
    	sort |
    	uniq -c |
    	sort -k1,1nr -k2|
    	head -n 10

    这个脚本只使用了最基本的Linux命令再加上管道实现了词频统计,输出出现次数最多的10个单词,而且速度也相当快,如果使用C语言想要这么快就必须自己实现一个能快速插入的数据结构,如果用高级点的语言如python又要非常了解库函数才能实现。所以shell脚本使用管道加上重定向可以非常快速地实现一些功能。甚至在git的早期开发中对于一些高级的功能如add,commit等都是使用shell脚本来调用C语言实现的命令来实现的,之后出于效率和可移植性的考虑才将这些shell实现用C语言重写。这个例子的意思就是:如果你能熟练掌握shell脚本,重定向,管道以及一些基本的命令就能实现很多功能,这些功能对于系统管理已经非常足够了,加上一些其他编程语言甚至还能做出一些软件的原型。

    软件的安装

    Linux安装软件和Windows完全不同,通常在Windows下找一个软件要自己上网搜索,找到华军等下载站,跳过一大堆广告,忍受一大堆弹窗广告,在广告群中找到不起眼的下载链接再下载安装,安装时还要注意不要安装附带的垃圾流氓软件,在Windows下安装软件就是这么麻烦。在Linux下如何呢?假设我要在Ubuntu下安装git软件,那么一条命令足已:

    sudo apt-get install git

    在Linux下只要知道软件名就可以直接用命令安装,所有下载安装过程全部全自动。而搜索软件呢?如果你不知道软件名,那么你到Google上搜索你要的功能,之后加个“Linux”就行,比如你要找个将rm视频格式转换成其他格式的软件,那么只要在Google搜索“视频转码 Linux”就行,只要找到软件的名字和软件使用时的参数就行。

    Linux软件安装这么方便就是因为Linux的一些发行版本有很强大的包管理软件。Linux可以使用的软件成千上万,如果都从源代码安装会耗费很多的编译时间,并且每个软件的编译、安装方法都不一样,包管理软件就是为了解决这个问题而存在的。Linux的发行版本有两种包管理方式,一种是Debian系统及衍生系统(Ubuntu等)使用的apt系统,另一种是红帽公司出的系统(RedHat,Fedora等)使用yum。具体的用法就不介绍了。

    POSIX标准

    POSIX标准的全称是Portable Operating System Interface,为什么这个标准重要呢?因为它规定了操作系统的系统调用的接口,其他任何软件能做的事情都不可能超过这个标准规定的接口所能做的事情,换句话说,这个标准几乎是规定了编程的能力,任何软件都不可能超过这个能力,因为软件都是运行在内核之上的。不过这个标准的本意并不是为了规定编程能力的边界,而是为了让所有的操作系统的内核都提供相应的接口,好让应用软件能够比较好的相互移植。在Linux下常常使用C编程就会非常熟练的使用这个标准规定的接口,就算是一些高级语言也都会提供这样类似的接口,它们也都是用这个接口来实现多进程,IO和进程间通信等功能。在Windows下用高级语言编程还好,能够接触到这些东西,但是用C恐怕根本接触不到这个标准,这样也丧失了可移植性。如果你编写的软件非常符合这个标准那么你的软件就能比较方便地移植到其他平台上。

    书籍

    说了这么多,还是那句话,授之以鱼不如授之以渔。自己看书学习比读别人博客吃别人嚼碎了的东西更有营养。这里列出我学Linux时看的一些书,希望对你们有用。

    The Linux Command Line 这本书非常适合初学Linux命令行的人看,由著名的 LinuxCommand.org网站的维护者编写,非常详细地介绍了Linux的命令行。
    鸟哥的Linux私房菜 基础学习篇 这是我看的第一本计算机相关书籍,在看这本书之前几乎不懂电脑,所以这本书一定非常适合Linux初学者,但是不像《The Linux Command Line》,它几乎介绍了所有的Linux相关主题,而不仅仅只是命令行。
    Advanced Bash-Scripting Guide 这本书更加详细地介绍了shell脚本,而不像《The Linux Command Line》只是简单介绍,里面也有很多的技巧,但是这本书很多都只是举例,分析,没什么系统性,所以读起来比较吃力,不过可以当查询的好去处。
    UNIX环境高级编程 这本书非常详细地介绍了UNIX系各个实现(BSD,Linux,Solaris)之间的差别,并且介绍了实现的原理以及POSIX标准,可以非常好地理解C语言系统调用的作用。
    The Linux Programming Interface 这本书和《UNIX环境高级编程》很像,只是更专注于Linux系统。
    Linux内核完全注释 这本书以早期内核版本作为标本,对几乎所有的内核代码都有注释,而且有非常详细的解释,如果你想了解内核实现的原理,这本书就适合你。