Programming

显示 shell 函数定义

我们知道在 bash/zsh 中可以用 “typeset -f” 来显示某个函数的定义。如果不用这个内置命令的话我们应该怎么显示函数定义呢?

下面这个技巧就可以用来显示函数定义:
[bash]

!/bin/bash

def() {
eval “$1() $2”
eval “function_$1=”$1() $2””
}

def foo ‘{
echo bar
}’
[/bash]

其中 foo 是定义的函数,$function_foo 是函数 foo 的定义。不过缺点也很明显,每次定义函数必须使用”def”,而且后面用的单引号也是个问题。

又一个宏技巧

(本文为《C语言编程艺术》的一部分。)

这个技巧是从 Nick Bowler 的一封邮件中看来的,非常有意思,分享一下。

情况是这样的:在 Linux 内核中,有一个函数 kmap_atomic(),它之前带两个参数,而现在,它的第二个参数已经名存实亡了,可以直接去掉。所以问题就出来了,如何警告使用 kmap_atomic() 的人带有两个参数的形式是过时的?换句话说,怎么才能在用两个参数调用 kmap_atomic() 时发出警告而用一个参数调用时就没有警告?

所以下面的技巧就出来了。

[c]

define PASTE(a, b) a ## b

define PASTE2(a, b) PASTE(a, b)

define NARG_(_2, _1, n, …) n

define NARG(…) NARG(_VA_ARGS, 2, 1, :)

static inline void kmap_atomic(struct page page)
{
return __kmap_atomic(page);
}

static inline void __deprecated kmap_atomic_deprecated(struct page page,
enum km_type km)
{
return kmap_atomic(page);
}

define kmapatomic1(…) kmapatomic(__VA_ARGS)

define kmapatomic2(…) kmapatomic_deprecated(__VA_ARGS)

define kmapatomic(…) PASTE2(kmapatomic, NARG(VA_ARGS)(__VA_ARGS))

[/c]

这里最精妙的地方当属 NARGS 这个宏的定义,它通过参数个数来决定返回值,进而通过 PASTE2 来选择是 kmap_atomic1,还是 kmap_atomic2,而后者是过期的。如果用户用了两个参数,他就会得到类似下面的编译警告:

drivers/block/drbd/drbd_bitmap.c:973:3: warning: ‘kmap_atomic_deprecated’ is deprecated (declared at /home/wangcong/linux-2.6/include/linux/highmem.h:124)

非常聪明,不是吗? :)

推荐两本并行编程的书

1. 入门的,《The Little Book of Semaphores》

The Little Book of Semaphores is a free textbook that introduces the principles of synchronization for concurrent programming.

The approach of this book is to identify patterns that are useful for a variety of synchronization problems and then show how they can be assembled into solutions. After each problem, the book offers a hint before showing a solution, giving students a better chance of discovering solutions on their own.

The book covers the classical problems, including “Readers-writers,” “Producer-consumer”, and “Dining Philosophers.” In addition, it collects a number of not-so-classical problems, some written by the author and some by other teachers and textbook writers.
可以在它的官方网站下载:http://greenteapress.com/semaphores/

2. RCU 的作者,大牛 Paul E. McKenney 写的书,《Is Parallel Programming Hard, And, If So, What Can You Do About It?》。这个比较猛,我还没时间看。它也是免费的,英文版可以在这里下载:

git://git.kernel.org/pub/scm/linux/kernel/git/paulmck/perfbook.git

现在已经被翻译成中文了,叫《深入理解并行编程》:

http://download.csdn.net/detail/xiebaoyou/3552357

我在南京和布拉格的时候见过 Paul E. McKenney,他是一个非常和蔼、热心的人,没有一点儿牛人的架子。:-)

shell 重定向的一处妙用

偶然在 dracut 的代码中发现一个使用重定向很巧妙的地方。见 modules.d/90kernel-modules/module-setup.sh 文件。

之前的老代码是这样的:

[bash]

#

         local _f
         while read _f; do case "$_f" in
             *.ko)    [[ $(<         $_f) =~ $_blockfuncs ]] && echo "$_f" ;;
             *.ko.gz) [[ $(gzip -dc <$_f) =~ $_blockfuncs ]] && echo "$_f" ;;
             esac
         done

[/bash]

意思很清楚吧?就是在内核模块(注意是二进制格式)中匹配一些函数(字符串)。这样会很慢,因为 bash 要在庞大的二进制文件流中匹配一些指定字符串。

于是,就有人想了一个方法加速这个处理过程,把原来的单个数据流分成两个并行的数据流,同时进行匹配!很巧妙!

[bash]

#

         function bmf1() {
             local _f
             while read _f; do case "$_f" in
                 *.ko)    [[ $(<         $_f) =~ $_blockfuncs ]] && echo "$_f" ;;
                 *.ko.gz) [[ $(gzip -dc &${side2}; fi
             done 
             | bmf1     1>&${merge}    ) {side2}>&1 
             | bmf1  )      {merge}>&1

[/bash]

经过 refactor 之后的或许更好理解一些:

[bash]

        # subfunctions inherit following FDs
        local _merge=8 _side2=9
        function bmf1() {
            local _f
            while read _f; do case "$_f" in
                *.ko)    [[ $(<         $_f) =~ $_blockfuncs ]] && echo "$_f" ;;
                *.ko.gz) [[ $(gzip -dc &${_side2}
                fi
            done | bmf1 1>&${_merge}
        }
        # Use two parallel streams to filter alternating modules.
        eval "( ( rotor ) ${_side2}>&1 | bmf1 ) ${_merge}>&1"

[/bash]

Sleep sort

4chan BBS 上一个排序的程序火了,它叫休眠排序,很有意思。

[bash]

!/bin/bash

function f() {
sleep “$1”
echo “$1”
}
while [ -n “$1” ]
do
f “$1” &
shift
done
wait

[/bash]

其实它的原理很简单,就是,要对N个整数进行排序的话,启动N个进程(线程),每个进程休眠对应的整数指定的秒数,然后再打印该数,最后你在终端上看到的肯定是排序之后的结果了……看了之后你会不会也觉得这太坑爹了?!可是,它就是能工作,而且占用CPU很少!

值得一提的是底下回复中给出的OpenMP版本(如果要尝试的话需要安装openmpi)和Perl版本。

[c]
/*

  • @file sleepsort.c
  • @brief sorts numbers
    *
  • @compile gcc sleepsort.c -fopenmp -o sleepsort
    *
  • @author Gerald Jay Sussman (Massachvsetts Institvte of Technology)
    */

include

include

include

int main(int argc, char **argv) {
int i;

omp_set_num_threads(argc);

pragma omp parallel for

for (i = 0; i < argc - 1; i++) {
long int this = atol(argv[i+1]);

sleep(this);

printf(&quot;%ldn&quot;, this);
fflush(stdout);

}

return 0;
}

[/c]
[perl]
fork and sleep $_, say, last for @ARGV; 1 while 1 -wait
[/perl]

除了不能对浮点数和负数进行排序,它还有一个缺点,那就是其中某个进程需要睡眠最大的那个数指定的时间,然后才能得出最后结果。下面有人提出了改进,我试了试,没有一个完美的。理想的情况下它应该能够对正负整数、浮点数进行排序,而且最坏也不要花太多时间,感兴趣的同学可以自己改进一下。

又到愚人节

每年愚人节技术 geek 们都要一本正经的恶搞一次,所以每年的这天我都特别留意有没有一些新的恶搞。今年也没有失望。

1) LKML 上有人发了一个补丁,说引入一个新的布尔类型,除了“真”与“假”之外还定义了一个“或许”,其值为1/2。
[c]
enum _Wool {
w_false = 0,
w_true = 1,
w_maybe = 1 / 2,
};
[/c]

2) 鉴于 IPv6 就要来临,可 IPv6 还是有不少问题,于是有人提出引入 IPv4.1,把原来的4字节IP地址扩展为5字节,而且保证完全向后兼容 IPv4。以后再不够了再扩展 IPv4.2 就可以啦!我觉得这个主意不错,希望贵国早日把这拿来一本正经地搞一搞!再申请个专利什么的!搞啥子 IPv9 哟!

3) StackOverflow 上有人提问,为啥子我这个C++的 Hello world 程序连编译都通不过?我已经把C++标准全部读了两遍。更搞笑的是,底下还有人一本正经地给出解决方法,其中一个最牛逼了:

include <ChuckNorris>

绝对可以解决一切通不过编译的程序!

4) Debain、ArchLinux、Gentoo等将合并为超级发行版Canterbury

API 与 ABI

(本文亦是《C语言编程艺术》中的一部分,所以请勿用于商业用途。)

一些程序员居然对API和ABI这两个概念都不清楚,我感到有些惊讶。这里以 Linux 内核为例简单解释一下。

API,顾名思义,是编程的接口,换句话说也就是你编写“应用程序”时候调用的函数之类的东西。对于内核来说,它的“应用程序”有两种:一种是在它之上的,用户空间的真正的应用程序,内核给它们提供的是系统调用这种接口,比如 read(2),write(2);另一种就是内核模块了,它们和内核处于同一层,内核给它们提供的是导出的内核函数,比如 kmalloc(),printk()。这些接口都是你可以在编写程序的时候直接看到的,可以直接拿来用的。

而 ABI 是另一种形式的接口,二进制接口。除非你直接使用汇编语言,这种接口一般是不能直接拿来用的。比如,内核系统调用用哪些寄存器或者干脆用堆栈来传递参数,返回值又是通过哪个寄存器传递回去,内核里面定义的某个结构体的某个字段偏移是多少等等,这些都是二进制层面上的接口。这些接口是直接给编译好的二进制用的。换句话说,如果 ABI 保持稳定的话,你在之前版本上编译好的二进制应用程序、内核模块,完全可以无须重新编译直接在新版本上运行。另一种比较特殊的 ABI 是像 /proc,/sys 目录下面导出的文件,它们虽然不是直接的二进制形式,但也会影响编译出来的二进制,如果它里面使用到它们的话,因此这些“接口”也是一种 ABI。

你平时看到的什么 POSIX 标准啊,C99 标准啊,都是对 API 的规定。而规定 ABI 的标准就不多,而且也没那么强势,Linux 上面的 ABI 标准似乎只有 Linux Foundation 提供的一些标准

好了,从上面我们可以看出,其实保持一个稳定的 ABI 要比保持稳定的 API 要难得多。比如,在内核中 int register_netdevice(struct net_device *dev) 这个内核函数原型基本上是不会变的,所以保持这个 API 稳定是很简单的,但它的 ABI 就未必了,就算是这个函数定义本身没变,即 API 没变,而 struct net_device 的定义变了,里面多了或者少了某一个字段,它的 ABI 就变了,你之前编译好的二进制模块就很可能会出错了,必须重新编译才行。

你可能会感到意外,上游的 Linux 内核其实不光不保持稳定的 ABI,它就连稳定的 API 都不会保持!而且还牛逼哄哄地写了一个文档,叫 stable_api_nonsense.txt。这么做的道理是,内核一直在向前推进,而且速度很快,内核开发者们并不想因为 API 的限制而阻碍前进的脚步!毕竟我们不想成为下一个 Windows!:-)

所以,你的驱动在不同版本的内核上不经修改直接运行那几乎是不太可能的,就算是你允许重新编译也未必就能不经修改编译成功。即使在同一个大版本的不同发行版上也可能不行。

那你应该怎么办?最好的办法莫过于把你的驱动贡献到社区,汇入内核源代码树中,这样一旦内核的 API 有改动,改动这个 API 的人就有义务替你修改你的驱动的代码,你只需要 review 一下(或者这个也会有人帮你),也省去你不少时间,何乐而不为呢?另一种办法就是基于某个提供稳定 ABI 的内核,比如红帽的 RHEL (认为这是广告的人请使用 CentOS,谢谢!),红帽的企业版内核保证有稳定的 ABI,只要你没有跨大的版本,因为我们的源代码里会检测 ABI 的变化,为此我们实在付出了不少努力。

当然了,如何检查 ABI 变化那就是另一个有意思的话题了,以后有机会慢慢说这个问题。

gcc 太聪明了!

gcc 越来越聪明了,有时候为了故意不让它优化某一部分代码(但同时还要开启-O!)我真可是费尽了心思。

举两个简单的例子来说明一下。一个是exp()的问题,不带-lm编译下面这段代码:

[c]

include

include

int main(void)
{
printf(“%lf”, exp(4.0));
return 0;
}
[/c]

你会发现可以得出正确的结果,没任何链接错误。它被预处理掉了吗?gcc -E告诉我们没有。那到底怎么回事呢?看一下生成的汇编,你会发现里面根本就没有call exp这样的指令!也就是说它被gcc在编译的时候处理了!这是因为像exp(4.0)这样的常量表达式是可以在编译的时候优化的,它的结果是个常量,所以gcc编译的时候就已经把这个值计算出来了,并把计算结果作为一个常量放到那个位置去了!为我们省去了一个函数调用!:-) 所以,如果你把上面的4.0换成一个double变量传递给exp(),你就会得到链接错误了。

另外一个例子是一个非常真实的例子。我们知道内核中有个著名的结构体叫struct skbuff,而我们公司内部的一个补丁中在某个函数中错误地把skbuff拼写成了skbuf!但编译和运行没任何问题!这个问题是后来别人在code review的时候才发现的!为什么呢?这明明应该通不过编译啊!我花费了不少时间去追踪这个问题,最后发现了问题的根源。为了方便叙述,我把问题的关键部分抽象成了下面的代码:

[c]
int foo(struct non_exist *n)
{
if (n)
return 0;
else
return 1;
}
[/c]

我们这么编译:gcc -c foo.c,你会发现我们只有警告,没有错误!警告我们可以忽略,因为我们只关心为什么不会有错误,换句话说,为什么gcc依旧可以成功地编译出代码来?仔细想一下你会明白,其实在foo()里面,我们根本就不用关心n到底是一个什么样的指针,只要知道它是一个指针就可以了!因为我们一没有对它进行dereference操作,二没有进行++或者—运算!而所有指针在特定的机器上长度是一定的。这正是为什么它可以通得过编译,但警告是肯定不会少的。;-)

如果你读过-O(或以上)生成的汇编的话,你还会发现一大坨gcc优化的例子,这可苦了我这种读汇编的人了,每次都得去猜gcc到底做了那些优化,不过这也相当有乐趣!

关于内存泄漏

不少编写用户空间程序的人都关心内存泄漏到底会造成多少影响。这里简单澄清一下。

无论用户空间是通过malloc()也好,mmap()也好,最终都是通过Linux内核管理的。只要内核没有bug,这些和这个进程相关的页面都是会被记录的,而且会在进程exit()的时候由Linux内核全部释放掉。所以对于运行时间不长的应用程序,有一些内存泄漏一般是没多大问题的。作为用户程序的编写者,如果释放内存真的很麻烦,或者说会让你的某一块代码变得很难读,你完全可以直接exit()。虽然很多时候我并不建议你这么做,道理很简单,因为你申请了资源就应该自己去释放,这不光是资源浪费的问题,而且是你代码逻辑和你作为开发者职责的问题。

实际上,Linux内核根本就不会注意到用户空间的泄漏不泄漏,它只关心你申请的这个页面有没有被它记录,所以进程退出时所有和此相关的内存页面都会被内核释放掉,管你是因为泄漏留下的还是用作其它用途的。

但是,你也知道,有些进程是不会主动退出的,那就是后台进程。这样的程序要是存在内存泄漏的话,还是可能会很浪费系统的内存资源的,因为内核没机会去释放这些页面,而且对用户空间的泄漏情况完全不知,它只会不停地给用户空间分配内存,直到用户空间地址耗尽或者物理内存趋近用完。

Gcc Trampoline

gcc对trampoline的定义如下:

A trampoline is a small piece of code that is created at run time when the address of a nested function is taken. It normally resides on the stack, in the stack frame of the containing function.

trampoline是因nested function应运而生的。其基本原理描述如下:

The instructions in the trampoline must do two things: load a constant address into the static chain register, and jump to the real address of the nested function. On CISC machines such as the m68k, this requires two instructions, a move immediate and a jump. Then the two addresses exist in the trampoline as word-long immediate operands. On RISC machines, it is often necessary to load each address into a register in two parts. Then pieces of each address form separate immediate operands.

下面我们来看看它具体是怎么工作的。看这个例子
[c]

include

int function(int arg) {

    int nested_function(int nested_arg) {
            return arg + nested_arg;
    }

    return nested_function(arg);

}

int main(void)
{
printf(“%dn”, function(10));
}
[/c]
很明显我们使用到了一个nested function,但是我们看gcc生成的汇编:
[asm]
00000000004004c4 :
4004c4: 55 push %rbp
4004c5: 48 89 e5 mov %rsp,%rbp
4004c8: 89 7d fc mov %edi,-0x4(%rbp)
4004cb: 4c 89 d0 mov %r10,%rax
4004ce: 8b 00 mov (%rax),%eax
4004d0: 03 45 fc add -0x4(%rbp),%eax
4004d3: c9 leaveq
4004d4: c3 retq

00000000004004d5 :
4004d5: 55 push %rbp
4004d6: 48 89 e5 mov %rsp,%rbp
4004d9: 48 83 ec 10 sub $0x10,%rsp
4004dd: 89 f8 mov %edi,%eax
4004df: 89 45 f0 mov %eax,-0x10(%rbp)
4004e2: 8b 45 f0 mov -0x10(%rbp),%eax
4004e5: 48 8d 55 f0 lea -0x10(%rbp),%rdx
4004e9: 49 89 d2 mov %rdx,%r10
4004ec: 89 c7 mov %eax,%edi
4004ee: e8 d1 ff ff ff callq 4004c4
4004f3: c9 leaveq
4004f4: c3 retq
[/asm]

很明显这和普通的函数调用没什么区别嘛!(当然,除了汇编中的nest_function()的名字改变了。)没trampoline什么事。这是因为这时候还没必要使用trampoline,gcc优化还是很聪明的,它不到万不得已不会做一点儿额外的工作。

下面我们就取一下它的地址:
[c]

include

int function(int arg) {

    int nested_function(int nested_arg) {
            return arg + nested_arg;
    }

    return nested_function(arg);

}

int function2(int arg) {

    int nested_function(int nested_arg) {
            return arg + nested_arg;
    }

    int (*function_pointer)(int arg) = nested_function;

    return function_pointer(arg);

}

int main(void)
{
printf(“%dn”, function(10));
printf(“%dn”, function2(10));
return 0;
}
[/c]
这时trampoline就产生了,因为我们对它取了地址,这个地址在栈上,而且nested function()的栈必须被设为外面function2()的栈。
[asm]
00000000004004f5 :
4004f5: 55 push %rbp
4004f6: 48 89 e5 mov %rsp,%rbp
4004f9: 89 7d fc mov %edi,-0x4(%rbp)
4004fc: 4c 89 d0 mov %r10,%rax
4004ff: 8b 00 mov (%rax),%eax
400501: 03 45 fc add -0x4(%rbp),%eax
400504: c9 leaveq
400505: c3 retq

0000000000400506 :
400506: 55 push %rbp
400507: 48 89 e5 mov %rsp,%rbp
40050a: 48 83 ec 30 sub $0x30,%rsp
40050e: 89 f8 mov %edi,%eax
400510: 89 45 d0 mov %eax,-0x30(%rbp)
400513: 48 8d 45 d0 lea -0x30(%rbp),%rax
400517: 48 83 c0 04 add $0x4,%rax
40051b: 48 8d 55 d0 lea -0x30(%rbp),%rdx
40051f: b9 f5 04 40 00 mov $0x4004f5,%ecx
400524: 66 c7 00 41 bb movw $0xbb41,(%rax)
400529: 89 48 02 mov %ecx,0x2(%rax)
40052c: 66 c7 40 06 49 ba movw $0xba49,0x6(%rax)
400532: 48 89 50 08 mov %rdx,0x8(%rax)
400536: c7 40 10 49 ff e3 90 movl $0x90e3ff49,0x10(%rax)
40053d: 48 8d 45 d0 lea -0x30(%rbp),%rax
400541: 48 83 c0 04 add $0x4,%rax
400545: 48 89 45 f8 mov %rax,-0x8(%rbp)
400549: 8b 45 d0 mov -0x30(%rbp),%eax
40054c: 48 8b 55 f8 mov -0x8(%rbp),%rdx
400550: 89 c7 mov %eax,%edi
400552: ff d2 callq *%rdx
400554: c9 leaveq
400555: c3 retq
[/asm]
读生成的汇编,我们可以看出堆栈大体这样:


[ xxx ] <-rsp
[ eax ]
[ 0xbb41 ]
[ addr ]
[ 0xba49 ]
[ rsp ]
[0x90e3ff49]

[ xxx ] <- rbp

其中addr是指nested_function.2079的地址。这里最让我们困惑的应该那三个常数,它们是什么?根据上面的定义你应该可以猜出,是指令!到底是什么指令呢?我们手工反汇编一下:
[c]
unsigned short arrary[] = {0xbb41, 0xffff, 0xffff};
unsigned short array2[] = {0xba49, 0xffff, 0xffff};
unsigned short arrary3 [] = {0xff49, 0x90e3};
[/c]
其中0xffff仅仅是用作标记。把此文件保存为decode.c,然后:


cr0% gcc -c decode.c
cr0% objdump -D decode.o

我们可以得到,它们大体就是下面三条指令:


movl $nested_function.2079,%r11
movl %rsp, %r10
jmp *%r11
nop

根据nested_function.2079里的代码,我们可以看出%r10传递的是参数的地址,然后就直接jmp到nested_function.2079里去了。这就是传说中的trampoline!

注:此时的stack必须是可执行的,因为有代码放在了里面,查看其maps可以看出:

bfebe000-bfed3000 rwxp 00000000 00:00 0 [stack]

提到nested function,不能不提一个trick,就是用nested function来实现C的lambda!如下:
[c]

define lambda(l_ret_type, l_arguments, l_body)

({                                                           
  l_ret_type l_anonymous_functions_name l_arguments          
    l_body                                                   
  &amp;l_anonymous_functions_name;                               
})

qsort (array, sizeof (array) / sizeof (array[0]), sizeof (array[0]),
       lambda (int, (const void *a, const void *b),
               {
                 dump ();
                 printf ("Comparison %d: %d and %dn",
                         ++ comparison,
                         *(const int *) a, *(const int *) b);
                 return *(const int *) a - *(const int *) b;
               }));

[/c]
怎么样?很有意思吧。