流量控制的一个小问题记录
洗澡时突然回想起自强邮件列表上一个学弟提到的问题,初步问题是如何让不同的程序走不同的网络(Web浏览器和BT),深层次的问题是如何让不同的协议走不同的网络。
我初步提出的解决办法有两个:一是设置本地代理;二是使用iptables的l7-filter。
代理的实现原理是让代理选择出口,这样只要为两个程序开两个本地代理即可。这解决的是“初步问题”。
用l7-filter是为了解决“深层次问题”。我个人没有使用过l7-filter,我的想法是,只要能把流量标记出来,那么就可以处理。然后学弟说他了解到l7-filter主要是用来做流量控制的,没有这个功能。当时也有其他事情,没有多想。
现在回想起来,做还是可以做的,虽然我没有自己做实验,但是我已经想像到整个流程是怎么样的了。也怪我原来说得不够清楚,只提到了l7-filter。其实l7-filter只是完成标记流量的功能,真正的流量整形,还是得靠tc。
好吧,那么现在我们可以把深层次的要求看作一个设置QoS的问题,那么问题就很好办了。
流程有两步:1. iptables及l7-filter为流量设置标记(–set-mark);2. tc设置QoS参数,把不允许被mark数据流流出的接口对应数据流的速率设置为0(这话挺拗口的)。
具体我就不做了。找点参考文档放下面吧。
- http://blog.edseek.com/~jasonb/articles/traffic_shaping/scenarios.html,这里面有用tc设置速率的例子。
- http://lartc.org/,Linux高级路由及流量控制。
重置Gnome的Panel
为自己的Ubuntu装了个Global Menu,没有多想就把原来的两个panel都删除了,到想再加global menu的时候傻眼了——所有panel都没有了,也没有办法加上新的panel了。
Google之后找到了一个重置Gnome Panel的办法。
$ gconftool --recursive-unset /apps/panel
有必要的话再把~/.gconf/apps/panel清理一下,然后把gnome-panel进程重启,默认的panel就回来了。
下次不能再这样傻了。
特权高就是好
Virtual Machine Introspection就是说只看不改。要是改一下数据会怎么样?
今天就干了这事,在Xen的层面把程序的代码内容给改掉了。简单点说,这就是一种代码注入。
有趣的地方是,改了的机器码最后反映到程序映像之中了。想了一下,大概是因为代码段是以文件映射的方式映射到内存之中的,程序退出的时候会让磁盘上的文件和内存中的内容同步。
往好的方面想,这样的功能是目前最强的hot patch方式。一方面可以on the fly地改进程,另一方面顺带把映像也改了。
往不好的方面想,这样的功能就太可怕了,自己的程序被改了也不知道,重新启动也不成。
不过我原来没有料到会有映像的同步。我一向的理解是这样的只读映射最后只需要把内容丢弃就可以了,代码以后有机会再看吧。
Linux内核中的per_cpu变量实现
在init/main.c里面有一个setup_per_cpu_areas,会在start_kernel中调用。
这个函数的主要作用就是使用alloc_bootmem为每个CPU在内存中分配一段专属的内存,然后把使用DEFINE_PER_CPU得到的对象模板(存放在.data.percpu一节)拷贝n次(n为配置CPU的个数),再把每个CPU专属区段相对于__per_cpu_start的offset放在__per_cpu_offset数组中(该数组的下标是CPU的ID)。
每个CPU可以通过__per_cpu_start和__per_cpu_offset找到自己的专属区段。
其实挺简单的。
(完)
用一个月时间给自己一个交代
刚才把最后一个功能点测试完成,commit了。看了一下log,第一个commit是11月26日,最后这个commit是12月25日,一个月的时间。
对Xen的修改:
12 files changed, 1082 insertions(+), 24 deletions(-)
对Linux内核的修改:
5 files changed, 30 insertions(+), 5 deletions(-)
这一个月,把一个想法从无到有地实现出来,过中的难度只有自己知道。一直认为自己做事有头无尾,把这件事做完了,也算是给自己一个交代了。
内核级别的开发,不容易,但是也说不上很难——这是完成这件事之后最大的感受。与此同时,也对自己、对之后的计划有了一些新的认识和想法。
下面又要开始新的计划了。
(完)
GCC嵌入式ASM快速指南
本文从Inline assembly for x86 in Linux中摘译。
Table of Contents
1 简要GNU汇编语法
1.1 寄存器命名
寄存器名字前面应该加上%前缀。比如要使用eax,那么应该写为%eax。
1.2 来源和目的的顺序
在任何一条指令中,总是先写来源,然后再写目的。这与Intel汇编语法正好相反。
movl %eax, %ebx -- transfers the contents of eax to ebx
1.3 操作数的尺寸
根据操作数的长度是字节、字或者长整形,指令一般需要加上b、w或者l后缀。这不是强制要求的,GCC会根据操作数的尺寸自动加上合适后缀。但是手工加上后缀一方面加强了可读性,另一方面消除了GCC猜错的可能。
movb %al, %bl movw %ax, %bx movl %eax, %ebx
1.4 直接操作数
直接操作数需要加上$前缀。
movl $0xffff, %eax
1.5 直接内存引用
使用()去完成直接内存引用。
movb (%esi), %al -- will transfer the byte in the memory pointed by esi to al
2 内联汇编
GCC提供了一种特殊结构去创建联汇编,格式如下:
asm ( assembler template
: output operands
: input operands
: list of clobbered registers
);
模板由汇编指令构成。input operands是用来作为汇编指令输入操作数的C表达式。output operands依此类推。
asm ("movl %%cr4, %0\n" :"=r"(cr3val))
a %eax b %ebx c %ecx d %edx S %esi D %edi
2.1 内存操作数约束
当操作数位于内存中时,所有操作都会直接在内存位置执行。内存操作数约束的作用是当一个C变量需要被内联汇编操作时,程序员不需要显式地把它放到寄存器中。如:
asm ("sidt %0\n" : :"m"(loc));
2.2 匹配约束
在某些情况下,一个变量可能会同时输入和输出操作数。这样的情况可以使用匹配约束来完成。
asm ("incl %0" :"=a"(var):"0"(var));
在我们的匹配约束例子中,%eax同时作为输入和输出操作数。var先被读入到%eax,操作完成后结果被存回var。“0”指定了和0号输出变量同样约束。即是说,它指定了var的输出应该只被放到%eax。这类约束可以在如下的情况使用:
- 输入是一个变量然后输入到同一个变量;
- 不需要分别使用不同的变量去保存输入和输出。
使用匹配约束可以有效地使用寄存器。
2.3 常见内联汇编用例
下面的例子展示常见的用法。
2.3.1 “asm”和寄存器约束“r”
int main(void)
{
int x = 10, y;
asm ("movl %1, %%eax;"
"movl %%eax, %0;"
:"=r"(y) /* y is output operand */
:"r"(x) /* x is input operand */
:"%eax");/* eax is clobbered register */
return 0;
}
生成的汇编代码如下:
main:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl $10, -4(%ebp)
movl -4(%ebp), %edx /* x=10 is stored in %edx */
,#APP
movl %edx, %eax /* x is moved to eax */
movl %eax, %edx /* y is allocated in edx and updated */
,#NO_APP
movl %edx, -8(%ebp) /* value of y in the stack is updated with the value in edx */
“r”指明GCC可以采用任意的寄存器。在上例中,edx被先后用于存放x和y。
由于eax在clobbered list中,GCC不会使用它去存放数据。
在上例中,edx被先后用于输入和输出,这里有一个前提就是输入数据总是在输出之前就已经失效了。但是实际情况中有很多指令并不总是这样做的,所以我们可以加上“&”修饰符让输入输出使用不同的寄存器。
int main(void)
{
int x = 10, y;
asm ("movl %1, %%eax;"
"movl %%eax, %0;"
:"=r"(y) /* y is output operand, note the "&" modifier */
:"r"(x) /* x is input operand */
:"%eax");/* eax is clobbered register */
return 0;
}
生成下面代码:
main:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl $10, -4(%ebp)
movl -4(%ebp), %ecx /* x=10 is stored in %ecx */
,#APP
movl %ecx, %eax
movl %eax, %edx /* output is in %edx */
,#NO_APP
movl %edx, -8(%ebp)
2.3.2 指定寄存器约束
下面例子中,cpuid的输入由eax指定,输出到eax、ebx、ecx、edx四个寄存器中。
asm ("cpuid"
:"=a"(_eax),
"=b"(_ebx),
"=c"(_ecx),
"=d"(_edx)
:"a"(op));
然后生成如下的代码(这里假设eax等变量存放栈上):
movl -20(%ebp),%eax /* store 'op' in %eax -- input */
,#APP
cpuid
,#NO_APP
movl %eax,-4(%ebp) /* store %eax in _eax -- output */
movl %ebx,-8(%ebp) /* store other registers in
movl %ecx,-12(%ebp) respective output variables */
movl %edx,-16(%ebp)
strcpy可以用下面的方法实现:
asm ("cld\n rep\n movsb"
: /* no input */
:"S"(src), "D"(dst), "c"(count));
2.3.3 匹配约束
拿系统调用的代码做例子:
,#define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \
type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)),"S" ((long)(arg4))); \
__syscall_return(type,__res); \
}
通过使用匹配约束“0”,系统调用号被放入到eax中。
2.3.4 内存操作数约束
考虑如下的原子减一操作:
__asm__ __volatile__(
"lock; decl %0"
:"=m"(counter)
:"m"(counter));
会生成如下的汇编:
,#APP
lock
decl -24(%ebp)
,#NO_APP
上面的例子中,变量在内存中直接被减一。假如使用“r”的话,操作就没有原子性了。
2.3.5 clobbered约束
考虑如下的memcpy实现:
asm ("movl $count, %%ecx;
up: lodsl;
stosl;
loop up;"
:
:"S"(src), "D"(dst)
:"%ecx", "%eax");
lodsl和stosl隐式地使用eax,ecx被显式地加载到寄存器中。除非我们用clobbered约束告诉GCC这两个寄存器是可用的,它不会再使用它们去存放其他数据。esi和edi没有加入到clobbered列表中,因为它们已经出现在了输入操作数列表中。准则就是,例如一个寄存器在asm中使用了(无论是显式还是隐式),而又没有出现在输入、输出操作数列表中,那么它就必须出现在clobbered列表中。
3 相关资源
HTML generated by org-mode 6.21b in emacs 23
LD_PRELOAD的trick
ld.so(8)在为程序加载动态库时,会根据很多不同的环境变量而有不同的表现。这里关注一个LD_PRELOAD的环境变量,此环境变量指定的动态库可以优先于所有其他的动态库加载。
优先加载的动态库中的symbol会override后加载的symbol,所以LD_PRELOAD有一个比较好用的trick就是把一些程序中用的函数替换成自己的版本。
例如,要把malloc和free替换成为自己的实现,可以用:
$ LD_PRELOAD="path/to/my/malloc.so" program
据目前得到的资料来看,一些memory leak检测库、以及一些改进libc函数的库就是这样做的。
但是这个trick也有一定的安全隐患,一些关键函数被恶意、隐式地替换的话,可以想像后果有多严重,所以正常情况下是不推荐使用的。虽然对于setgid/setuid程序有一定的安全防范措施(For setuid/setgid ELF binaries, only libraries in the standard search directories that are also setgid will be loaded),但是一般程序是不会检查的。
Date: 2010-12-18 23:10:43
HTML generated by org-mode 6.33x in emacs 23