Linux Application

关于网桥的IP地址

网桥的IP地址应该设置在网桥上面,而非网桥下面的从属设备。虽然内核完全允许你那么做,但那是不推荐的。

原因有两个:

一、最主要的原因是网桥本身是基于第二层,数据链路层的,网桥的作用是把从一个从属设备上接受来的数据包转发到合适的目标设备上,通过学习接受来的数据包中的目的物理地址。因此,如果网桥本身不需要和外界通信的话,任何IP地址都不需要!它只是一个透明的桥(不考虑STP),连接起了两个网络。

二、试想如果网桥和它下面从属的设备都有IP地址的话,那么路由就可能会改变发送的包的出口,干扰了网桥本身学习到的“知识”,如果正好到达一个错误的出口设备上,包就会无法到达目的地址。类似地,bonding在一起的设备也一样,IP地址应该设置在master上面。

关于 /proc/mounts

现在的 Linux 系统里一般都有这么三个文件:/etc/fstab,/etc/mtab,和 /proc/mounts,比较容易让人迷惑。简单解释一下。

/etc/fstab 是只读不写的,它提供的是系统上挂载设备的静态信息,比如 mount -a 就会挂载 /etc/fstab 里面指定的文件系统。

/etc/mtab 是供 mount/umount 进行读写的,是相对动态的。读的话,比如你在挂载一个文件系统时缺少一个参数,它就会自动去/etc/mtab 或者 /etc/fstab 里去查,如果找到的话,只要一个参数也够。写的话,比如你umount了一个文件系统,umount 就会删掉/etc/mtab 里面的相关记录。

看似上面的这两个文件已经够用了,但是新的情况出现了。Linux 内核引入了一个 mount namespace,是给container用的。因为这个的出现,Linux 不得不引入 /proc/mounts。为什么呢?因为记录 mount 信息的 /etc/mtab 是全局的,也就是说,就算你的某个进程有自己的 namespace,但只要还和外面共享同一个 /etc/mtab,那么,里面进行umount/mount操作的信息也会被记录到/etc/mtab里,外面也会看到!凌乱了!由此可见,我们不能有全局的mtab,肿么办呢?/proc/mounts 出来了,有人可能觉得它也是全局的啊!可你仔细看一下的话会发现,它其实是到 /proc/self/mounts 的一个符号链接!如此以来,container 里面的 /proc/mounts 和外面的当然就不会一样了!聪明啊!

所以,/etc/mtab 已经过时了,应该被抛弃,或者直接符号链接到/proc/mounts。同理,查看系统上挂载的文件系统的话,直接调用无参数的mount也是不妥的,因为那样也是读 /etc/mtab。我们应该使用 util-linux-ng 提供的一个新命令: findmnt,它是读的 /proc/self/mountinfo。

RHEL6上制作RHEL5 srpm

如果直接在rhel6上面制作rhel5的srpm的话,编译的时候会出错:

error: unpacking of archive failed on file /builddir/build/SOURCES/98-kexec.rules;4cea13ae: cpio: MD5 sum mismatch

google 了一下发现这是因为从 Fedora 12 开始 RPM 压缩算法改变了。一个解决方法是创建一个脚本 /usr/bin/rpmbuild-md5,其内容如下:

[bash]

!/bin/bash

rpmbuild —define “_source_filedigest_algorithm 1”
—define “_binary_filedigest_algorithm 1”
—define “_binary_payload w9.gzdio”
—define “_source_payload w9.gzdio”
—define “_default_patch_fuzz 2” “$@”
[/bash]

然后制作srpm的时候调用 rpmbuild-md5 就行了。

在Fedora上,fedora-packager 这个包已经提供了这个命令,但在RHEL6上面没有。

计时运行命令

见过好几个人问怎样可以计时运行某一个命令?比如我可能想运行某个交互式的命令(像fdisk),但我又不想让它一直永远等待输入,我就得给它指定一个时间,若没有输入则自动退出。

我之前也遇到过这个问题,当时找到的是timed-run。而最近我发现coreutils里面已经默默包含了一个等价的命令——timeout。

timed-run来自expect包,很明显是用expect脚本写的:

% cat /usr/bin/timed-run
#!/bin/sh
# 
exec expect -f "$0" ${1+"$@"}
# run a program for a given amount of time
# i.e. time 20 long_running_program

set timeout [lindex $argv 0]
eval spawn [lrange $argv 1 end]
expect

而timeout则来自coreutils,应该是用C写的。它们的用法都一样,后面第一个参数是指定的时间,在后面就是要运行的命令,比如:timeout 1 sleep 10。timeout还能接受一些选项,更强大一些。

分享一些 shell 函数和 alias

省略掉公司内部用的一些东西。其中的calc()和isdigit()是从网上抄来的。:-) 希望对你有用。

[bash]
alias gts=”git send-email —cc-cmd=’./scripts/get_maintainer.pl —norolestats’ —to linux-kernel@vger.kernel.org —smtp-server XXXX”
alias v=”vim -O”
alias e=”emacs -q -nw”
alias wpas=”wpa_supplicant -Dwext -iwlan0 -c/etc/wpa_supplicant.conf -B”
alias mutt=”LC_ALL=zh_CN.UTF-8 mutt”
alias wget=”wget —no-check-certificate”

function calc () { awk “BEGIN{ print $* }” ;}

function isdigit ()
{
[ $# -eq 1 ] || return 1;

case $1 in
[!0-9]|””) return 1;;
*) return 0;;
esac
}

function errno ()
{
if [ “$#” -eq 0 ]
then
echo “Usage: errno [ERRNO | N]”
return 1
fi
pattern=$1
if isdigit “$pattern”
then
msg=”$(awk ‘$3~// {print $0}’ /usr/include/asm-generic/errno-base.h /usr/include/asm-generic/errno.h)”
else
msg=”$(awk ‘$2~// {print $0}’ /usr/include/asm-generic/errno-base.h /usr/include/asm-generic/errno.h)”
fi

if [ -z "$msg" ]
then
    echo "BAD ERRNO!"
else
    echo "$(echo "$msg" | sed -e 's#.*/* (.*) */#1#g')"
fi

}

function kcgrep ()
{
local dest
local str
if [ -z $2 ]
then
dest=”.”
else
dest=”$2”
fi
str=$(echo “$1” | sed -e ‘s/^CONFIG_//‘)
find “$dest” -iname ‘kconfig*’ -exec grep “$str” -nwH ‘{}’ ;
}
[/bash]

从 grep 的用法说起

我本不想写这篇文章,首先我觉得这些东西大家肯定都知道,其次就算真不知道还有手册。

直到那天面试的时候我碰到一个哥们,我问他grep你都知道哪些选项?他告诉我,只有-r。我当时就乐了,既然grep是你常用的命令,你凭什么只知道-r?他回答说,他只想把精力放到内核上,没有时间来学习这些东西。我笑,正是因为你没有时间,所以你才应该提高你的工作效率,而熟悉命令行显然是Linux上一种提高工作效率的方式。有句老话说得好:“磨刀不误砍柴功。”

有的同学可能觉得我在提倡死记硬背了,事实上我比你更反对中国教育的那种死记硬背。我的观点很简单:既然你用得多,那么有些东西,比如grep的一些选项,自然而然就应该记住了,无须刻意去背。

所以,我在这里以grep为例来展示一下。注意,以下内容没有参考一眼grep的手册。

grep最常用的选项可能是-i,-n和-r了,至少于我如此。-i是忽略大小写,-n可以告诉你找到的东西在文件的哪一行。-r或者-R(看你愿不愿意多按个shift键了),可以去递归一个目录。

-A和-B,你可能就用的比较少了,前者制定显示匹配到的那一行之后的几行,后者指定之前的,比如grep -A 3 -B 4就是说也要显示匹配到的那一行之前的4行和之后的3行,可见A是After,B是Before。用得着刻意去记嘛?显然不用。

-I(大写i)可能用得就更不多了,可惜啊,它非常有用。比如你编译出的内核二进制和你的源代码在同一个目录时,你grep一些字符串的时候会发现一些.o文件中也可以找到,很明显它不是我们想要的,这时候-I就派上用场啦!它告诉grep去忽略二进制文件。

这让我想到了另一个—exclude(我记得似乎没有短选项与之对应),顾名思义,它是用来排除一些文件。比如你编译的内核目录下还有.cmd这种文件的话,它不是二进制,所以-I排除不掉,这时候你就可以用—exclude “.cmd”了。

-H,这个用得更少了,你一般不会需要它,因为grep默认就会输出文件名。然而,当grep与find联手的时候就不会了(至于为什么,留给你作为课后作业),这时候你就要用到-H了。比如,我在内核源代码目录下查找CONFIG_DEBUG_KMEMLEAK定义在哪,我通常这么做:find . -iname ‘kconfig*’ -exec grep -Hwn DEBUG_KMEMLEAK ‘{}’ ;

我上面用到了另一个选项-w,看见了嘛?它可能是另外一个被严重忽视了的选项,仅次于-I啊!它的意思是告诉grep我所查找的是一个完整的单词,也就是说,当你找DEBUG_KMEMLEAK的时候它不会给你找出DEBUG_KMEMLEAK_EARLY_LOG_SIZE,你懂的。这可以省去你手工去加b了。

噢,我差点儿忘了亲爱的-e,多数情况下你也不需要它,因为grep后面接的就是一个regrex嘛!-e神马时候派上用场呢?我建议你在这里仔细想想,一个很特殊的情况——我要搜索的字符串以”-“开头时!比如我要搜索”-wangcong”这个字符串,直接grep “-wangcong”显然是不行滴!grep会认为那是”-w -a -n -g -c -o -n -g”,无论你怎么加引号!一种解决方法是加反斜线,而另一种方法就是-e “-wangcong”了。顺便说一句,使用多次-e就相当于进行一个“或”操作,所以grep “vmx|svm”就等价于grep -e vmx -e svm。

-o我估计用的人也不多,它的意思是只输出匹配到的东西,同一行的其它东西不输出。例如,我想搜索printk.c中全部的C语言字符串时,我会这么做:grep -o ‘“.*”‘ kernel/printk.c,有点儿类似于strings kernel/printk.o。

-b,知道这个人的灰常少了吧?如果你知道,恭喜你晋级grep高手级别了!我承认我只用过它一次,是在一个脚本中,我想知道某个“字符串”出现在文件的第几个字节。我印象中这里是有bug的,因为我当时总得不到正确的结果,当然也有可能是我理解不对,没看手册而且没有再次实验,所以这个问题就留给你了。:-)

-q嘛,大家都知道,脚本中常用。

-c嘛,比如统计CPU个数:grep -c ‘^processor’ /proc/cpuinfo,实在没必要多一个管道去 grep ‘^processor’ /proc/cpuinfo | wc -l。

好了,我就想起来这么多了,欢迎补充。我觉得一个好的Linux程序员应该对他/她敲出的每一行命令的每一个选项都精益求精,不多不少。

我知道有些人对此不屑一顾,嗤之以鼻,我只想告诉这些人一句话:“天下大事必做于细,天下难事必做于易。”你要是认为我这是在教育你,你就错大了,我对教育人,尤其是教育你这种人,没任何兴趣!你倒找给我钱我都不会稀罕去教育你!有他妈的多远给我滚他妈的多远!

附我窜改的《见与不见》:

你知,或者不知它/命令就在那里/不悲不喜

你看,或者不看它/手册就在那里/不来不去

你爱,或者不爱它/程序就在那里/不吵不闹

你用,或者不用它/二进制就在你的机器上/不亢不卑

去了解它们/或者/让它住进你的心里

人机合一/融为一体

迁移 home 分区

之前装系统时用的是默认安装,结果/home没有单独分出。现在想装新的系统,所以备份/home里面的东西就成为一个问题了。后悔啊!还好,幸亏当时我用的是LVM,所以我可以动态调整分区。

我天真地以为直接用lvm调整即可,事实不是这样的,直接操作你会发现没有足够的PE,换句话说,我的lvm已经被占满了,需要先腾出一些空间来。

因为根分区是一个整体,所以必须得从别的系统进来然后进行操作。于是,我找了一个Fedora的LiveCD。首先检查已有的根分区:

e2fsck -f /dev/VolGroup00/LogVol00

然后缩小根分区的大小到120G:

resize2fs -f /dev/VolGroup00/LogVol00 120G

用LVM缩小根分区的逻辑卷大小到120G,这样就剩下100G给后面的/home:

lvm lvreduce -L120G /dev/VolGroup00/LogVol00

添加新的卷:

lvm lvcreate -L100G -nLogVol01 VolGroup00

好了,任务完成了一半,继续备份:

mount /dev/VolGroup00/LogVol00 /mnt/root

mount /dev/VolGroup00/LogVol01 /mnt/home

cp -R /mnt/root/home/wangcong /mnt/home

经过了漫长的等待之后终于完成,然后就可以安装新的系统了,要把新的LogVol01选为/home,而且不格式化。安装完毕后:

chown wangcong -R /home/wangcong

结束。

dogtail

fedora 那边一哥们在搞自动化测试的东西,今天过去看了看,发现这哥们在用一个叫 dogtail 的东西,一个 Python 模块,很酷的一个东东。看来我再一次 out 了,头一次听说这玩意儿。

这玩意儿说白了其实就是图形界面程序的自动化测试工具。我们知道图形化的程序很难自动化的,它不像命令行的程序,写个脚本就搞定了。根本原因在于,点击鼠标和敲击键盘得由人来完成。这玩意儿的目的就是代替人来完成这些功能,比如点击鼠标按某个按钮。我在前一个公司时见过一个同事搞这种测试,不过他是在 M$ 平台上测的,我一直以为 Linux 上面没这个替代品。今天看来我错了,Linux 不光有,而且还有 Python 模块。

它不光可以代替人输入,也可以输出,比如说你想读某个应用程序比如 firefox 文本框里面的文字,你也可以用它。如此一来,图形界面在很大程度上就和命令行界面变得一样“程序员友好”(相对于“用户友好”一词)了,你可以 grep 它的输出,你可以用脚本来对它进行输入。

如果你看一下它的用法,你会发现如果单单是只有接口,它也是很难用的,因为我感觉它其实是对图形程序的“元数据”(其实是叫AT-SPI objects)进行读取,在图形接口层上,所以接口很面向图形界面,除非是天天搞图形界面的开发者,其他开发者很难一下子搞清楚这些东西。但是它还提供了一个很牛的 sniff 工具,通过它你可以对已有的图形程序的“元数据”有一个很直观的浏览,你想读哪一个程序的哪一部分数据很容易找到。所以配合着 sniff,这个东西就相当好用了。

这个项目也是red hat的,其主页在 fedorahosted.org 上,也有文档示例。Red Hat Magazine 上也有一个系列来介绍它:

Automated GUI testing with Dogtail
Dogtail’s Python Modules (and how to use them)
Dogtail’s object oriented tree API (and how to use it)

symlink,chroot 和 mount

前两天在处理一个关于 selinux 的 bug 时遇到一个比较有意思的问题,顺着仔细往下想了想,发现更多相关的话题,遂整理成一篇文章记录一下。实际上我想到的这几个问题和 selinux 基本上毫无关系,先从简单的谈起,然后顺着我的思路一步步往下走。

最初的问题是,如果我在一个目录中建立了一个符号链接,那么我 chroot 进去之后会得到什么?这个问题比较简单,如果符号链接的目标文件路径在 chroot jail 里也存在,那么你就会得到 chroot jail 里面的那个文件;否则就无法访问到目标文件。这一点有个比较有趣的应用就是,你可以建立一个符号链接,让它在 chroot jail 里面和外面都有效,当你在 jail 外面时你就用外面那个,当你在 jail 里面时里面那个就会“立刻生效”。

接下来的一个问题是,要是我建立的是一个硬链接呢?这个也不难,硬链接会直接访问到目标文件,不存在目标文件存在不存在的问题。那么,如果一个 chroot jail 中有那么一个硬链接,那么你就可以逃出 jail 了?答案是否定的,因为硬链接是不允许建立在目录之上的!至少在 Linux 上如此。在 Linux 上,除了目录“.”“..”你是看不到其它任何硬链接的目录的,为什么?因为如果允许了对目录的硬链接,那么文件系统树状结构层次就会被打破,会出现环。你会说,软链接也不是一样出现环吗?是啊,可是我们对它可以选择 follow 还是不 follow,比如 find(1),默认的情况就是不去 follow 软链接。

呃,在继续向前走之前,这里还有另一个关于 chroot 的问题,那就是 chroot(2) 并不会自己去 chdir(2),也不会关闭任何在此之前打开的文件,无论其是在 jail 里还是 jail外!瞧瞧,Unix 的设计哲学运用得多么好,绝对不多做一件不属于自己的事!但这会带来问题,如果你忘记自己去 chdir(2) 的话你完全可以在 jail 之外通过“..”跳出 jail。这是意料之中的行为,Al Viro解释到:

“If you are within chroot jail and capable of chroot(), you can chdir to its root, then chroot() to subdirectory and you’ve got cwd outside of your new root. After that you can chdir all way out to original root. Again, this is standard behaviour. Changing it will not yield any security improvements, so kindly give that a rest.”
好了,继续向前走。硬链接有个天生的缺陷,就是无法跨文件系统,估计你也能隐约感觉到。想想如果我要 chroot 的是一个U盘目录,比如:/mnt/USB,那么我就无法在里面使用硬链接来链接到外面的文件了。为了解决这个问题,我们可以使用 Linux 上的 bind mount,也就是说把一个文件通过“挂载”的方式给“绑定”到一个地方,从而到达和硬链接一样的效果!但这个可以跨任何文件系统!我们完全可以chroot jail 里使用 bind mount 进来的文件。但是,它是有缺点的,相对于硬链接来说。一,它不会像硬链接那样“永久”存在,一重启就没有了,当然了,你可以修改 fstab,但这并不总是一个好办法;二,bind mount 需要 root 权限,即使两个文件的所有者都不是 root (内核邮件列表中有人提交过 unprivileged bind mounts 的补丁,不知道最后情况如何,至少我在 Fedora 上还是需要 root 权限的)。

从上面可以看出 mount 是个很有意思的东西,如果你觉得 bind mount 比较神奇,别慌,下面还有更神奇的呢。

mount 还提供一个 move 功能,可以帮助你把一个已经 mount 上来的东东移动到另一个地方去,免得你先 umount 再 mount。

还有呢,从 2.6. 15 之后mount 还提供了一个非常强大的功能,shared subtrees。这个稍微有些复杂,它的目的是这样的,比如我想在我的用户目录下克隆一个根目录的结构,通常我该怎么做呢?恩,cp -R,或者 rsync 一下。这么做有个明显的缺点:复制了东西,占用了更多硬盘空间。所以我们用 bind mount 比较合适,但是仅仅这么做还不完美:首先,我的用户目录本身也是在根目录下的,本身也会被 bind mount 进去;其次,如果后来我又在 /mnt 下挂载了新的东西呢?那在我的克隆目录中就看不到了。基于此,mount引入了个新玩意儿叫 shared subtrees,于是你可以这么做:

mount —bind /home/wangcong /home/wangcong
mount —make-unbindable /home/wangcong
mount —bind /mnt /mnt
mount —make-shared /mnt
如此一来,你就可以在 bind mount 上来的所有 /mnt 中看到新挂载的内容了。非常牛叉。