修改FreeBSD-5-stable的ppp(8)和pppoed(8)

yayj 发表于 2005-12-16 16:32:09

ppp(8)和pppoed(8)都是FreeBSD系统自己的用户级ppp处理程序,配合使用这两个程序可以使FreeBSD成为一个pppoe服务器。pppoed(8)作为daemon进程一直运行,当收到与之对应的PADI后,pppoed(8)会fork出ppp(8)来处理这个连接,每个ppp(8)进程只处理与这对应的ppp接口。

需求:
1. ppp(8)使用tunN作为ppp接口名,其中包括有一个adsl的接口,所以adsl接口的名称不固定,这对做NAT的pf(4)配置造成了困难。要求固定adsl接口的名称,并使其它的pppoe不能使用该接口名称。
2. ppp(8)不提供限制用户登录次数的功能,要求提供该功能。
3. ppp(8)能统计它对应接口的流量,但不提供统计用户流量的功能,要求提供该功能。

完成情况:
1. ppp(8)默认从tun0开始一直重试到打开tunN设备为止,而它的-unit N选项表示只使用tunN接口,所以只要修改-unit选项的意义即可。而pppoed(8)在调用ppp(8)时不会使用-unit选项,所以要为它加上这个功能。
pppoe.c.diffs如下:
--- pppoed.c.orig Sat Nov 15 23:26:34 2003
+++ pppoed.c Fri Dec 16 16:18:20 2005
@@ -61,7 +61,7 @@
#include


-#define DEFAULT_EXEC_PREFIX "exec /usr/sbin/ppp -direct "
+#define DEFAULT_EXEC_PREFIX "exec /usr/sbin/fixedppp -direct -unit "
#define HISMACADDR "HISMACADDR"
#define SESSION_ID "SESSION_ID"

@@ -503,7 +503,7 @@
struct ngm_connect ngc;
struct sigaction act;
int ch, cs, ds, ret, optF, optd, optn, sz, f;
- const char *pidfile;
+ const char *pidfile, *unit;

prog = strrchr(argv[0], '/');
prog = prog ? prog + 1 : argv[0];
@@ -512,14 +512,19 @@
label = NULL;
acname = NULL;
provider = "";
+ unit = "0 ";
optF = optd = optn = 0;

- while ((ch = getopt(argc, argv, "FP:a:de:l:n:p:")) != -1) {
+ while ((ch = getopt(argc, argv, "Fu:P:a:de:l:n:p:")) != -1) {
switch (ch) {
case 'F':
optF = 1;
break;

+ case 'u':
+ unit = optarg;
+ break;
+
case 'P':
pidfile = optarg;
break;
@@ -568,14 +573,16 @@
" must be given\n", prog);
return usage(prog);
}
- exec = (char *)alloca(sizeof DEFAULT_EXEC_PREFIX + strlen(label));
+ exec = (char *)alloca(sizeof DEFAULT_EXEC_PREFIX + strlen(label) + strlen(unit) + 1);
if (exec == NULL) {
fprintf(stderr, "%s: Cannot allocate %d bytes\n", prog,
(int)(sizeof DEFAULT_EXEC_PREFIX) + strlen(label));
return EX_OSERR;
}
strcpy(exec, DEFAULT_EXEC_PREFIX);
- strcpy(exec + sizeof DEFAULT_EXEC_PREFIX - 1, label);
+ strcat(exec, unit);
+ strcat(exec, " ");
+ strcat(exec, label);
}

if (acname == NULL) {
这个很简单,ppp(8)的更简单,把bundle.c的2019行中改为maxunit = -1;即可。

2. 这个需要实现每个ppp进程间的通信与同步。我现在的做法是以用户名作为文件名,把进程的pid存入该文件,并用文件锁来实现各个ppp进程间的同步。新 的ppp进程验证前先检查这个文件,如果文件不存在或者文件中的pid对应的进程不存在则表示用户没有登录,否则用户已经在线了。修改pap.c中的 pap_Input函数和chap.c中的chap_Input函数实现pid的存入操作,修改main.c中的AbortProgram函数实现清理工 作。

3. 还没写好。

体会:
1. alloca函数很好用。
2. 要看懂一个程序,必须先看懂它的数据结构。
3. 这次看程序全部使用cscope+vim完成,很好用,还有很多功能示发现。

收藏: QQ书签 del.icio.us 订阅: Google 抓虾

关于98.2

yayj 发表于 2005-12-16 16:14:59

虽然98.2还有很多问题,但维护了这么久,而且我可能最近会停止对它的维护工作,所以想写点关于它的东西。
再想想吧,手头还有很多东西没看完,pf(4)、netgraph(4)、ppp(8)、pppoed(8)、策略路由、动态DNS和负载均衡.....
关键词(Tag): freebsd 喃喃呓语 麻布袋
收藏: QQ书签 del.icio.us 订阅: Google 抓虾

众里寻她千百度,那人却在灯火栏栅处。

yayj 发表于 2005-12-02 17:17:10

用了这么久的FB,也找了这么久的编程风格,就简单的一个man style就解决了。
关键词(Tag): freebsd 麻布袋
收藏: QQ书签 del.icio.us 订阅: Google 抓虾

关于混合轮询

yayj 发表于 2005-11-29 19:33:46

混合轮询是一种适合于大量发出中断的高速I/O设备的工作模式。混合轮询将基于中断的处理方式转变为中断-轮询混合方式,在高负荷的情况下能够有效地提高系统的承载能力,并降低响应时间。

以下内容主要来自Luigi Rizzo(assisstant professor), Dipartimento di Ingegneria dell'Informazione of the Università di Pisa, Italy. 的文章。Luigi实现了FreeBSD中的polling。
---------------------------------------------------------------------------------
正如《操作系统》教科书上所说的,纯粹轮询方式不好(polling is bad)。
当然,并不是说轮询本身在任何情况下都不好。

FreeBSD中采用的DEVICE_POLLING不是纯粹轮询。当启用DEVICE_POLLING的时候,系统将网络设备的基于中断的处理替换为一 种混合的中断-轮询模式(简称“polling, 轮询”),在启用了轮询的网卡上,在每次时钟tick的等待循环中,以及(可选地,每次)系统调用时,将进行轮询。

此外,FreeBSD中的polling实现还允许精确地分配目态和管态的CPU周期比例。

由于减少了中断发生的次数,这可以节省一些时钟周期(我们知道,在发生中断时需要进行目态/管态,以及上下文的切换)。当网络繁忙时,网卡可以轻易地产生每秒钟数十甚至上百次的中断,并耗尽所有的CPU时间。

当然,更有用的特性(通过采用适当的控制算法)是,混合轮询允许管理员决定使用多大比例的CPU来处理设备操作。这样,即使在网络流量过大的情况下,用户 进程也不会饿死并最终导致“活锁(livelock)”,换言之,由于花费过多时间处理数据包而无法完成那些有用的(处理数据)工作,并最终只能把那些数 据包丢弃。

当然,混合轮询也有一些缺点。因为需要轮询网卡上的数据,因此在响应数据包时,将可能有微小的延迟(不超过1/HZ)。不过这通常并不是问题,在空闲的系 统上,轮询网卡数据将持续地进行,因此几乎没有任何延迟;而当系统忙时,额外的1ms延迟与由于其他处理造成的延迟相比,基本上可以忽略不计。

在繁忙的网络上运行的路由器或服务器上,如果可能发生由于网络流量造成的过载,则应该启用混合轮询。普通的*BSD和Linux都会由于没有启用混合轮询而发生活锁,更糟糕的是,吞吐量迅速下降到一个很低的水平。

此外,对于伪实时[pseudo-real-time]的工作站,也应采用混合轮询,因为这样调度器的行为将更容易预测。

在没有过载的服务器上,混合轮询不会带来明显的性能提升;当负载适中时,将有0-50%的性能提升,而在重负载的环境下,性能提升将比较明显。
---------------------------------------------------------------------------------

由于systm.h中对于DEVICE_POLLING与SMP的互斥限制,DEVICE_POLLING不能与SMP同时启用。但如果把那一行去掉,DEVICE_POLLING也可以在SMP上用(采用1个线程,尽管原则上SMP应该并行地处理中断)。

收藏: QQ书签 del.icio.us 订阅: Google 抓虾

疑邻盗斧

yayj 发表于 2005-11-08 00:32:37

这是《列子》中的一篇故事,原文如下:
人有亡斧者,意其邻之子,视其行步,窃斧也;颜色,窃斧也;言语,窃斧也;动作态度,无为而不窃斧也。俄而掘其谷而得其斧,他日复见其邻人之子,动作态度,无似窃斧者。
我最近也有点这种感觉,究竟是谁错了呢?我不知道,也许都没错。
前车之鉴,后事之师。慎重,但愿同样的事不要再次发生。

关键词(Tag): 喃喃呓语 麻布袋
收藏: QQ书签 del.icio.us 订阅: Google 抓虾

SystemV ABI笔记(Intel386处理器,第4版)

yayj 发表于 2005-10-29 20:37:36

1. 字节对齐
在结构或联合中,每个成员变量的首地址要与它对应类型的对齐方式相匹配。在

struct {

char c;

short s;

};

这个结构中c和s成员之间就有1个字节的填充区,sizeof的运算结果是4。

结构或联合的对齐字节数是等于成员变量中对齐字节数最大的一个。

union {

char c[3];

short s;

};

这个联合的对齐字节数应该是与short类型一样,sizeof的运算结果是4。

未显式声明为signed的bit域都认为是无符号的,如有char c:3,则cÎ[0,3]。

bit域必须放在它对应类型的字段内,并且这个字段必须是对齐的。在

struct {

short t:9;

short u:9;

};

这个结构中,t和u之间有7bit的填充区。而在

struct {

short s:9;

int i:9;

};

这个结构中,s和i之间就没有填充区。

未命名的bit域不会影响整个结构或联合的对齐字节数,但会影响成员的对齐。
2. 函数调用

栈是按字长(对Intel386来说是4字节)对齐的。参数入栈的时候也要保持栈的对齐,若参数的长度不足4字节或4字节的倍数,则需要在参数的后面添加填充区。

若函数使用了%ebp、%ebx、%edi、%esi和%esp这几个寄存器,则需要把它们保存在自己的栈帧(Stack Frame)后。

几个主要寄存器的意义:

%esp:栈顶指针,它始终指向当前栈顶,并且始终按字长对齐。

%ebp:栈帧指针(stack frame pointer),栈帧是可选的,gcc中可以用-fomit-frame-pointer取消栈帧。

%eax:若函数的返回值是整数(包括char、short等)或者指针时,函数一般用它来保存返回值。若返回值是一个结构或联合,函数会用它来保存结构或联合的地址(可能有编译器优化的问题)。其它时候%eax是可以任意读写的。

%ebx:当把程序编译为与位置无关的代码时,%ebx用来保存全局偏移表的地址;其它时候它只是用来作为函数的局部寄存器。

被调用的函数必须从堆栈中移出返回地址,让%esp恢复到call指令调用前的状态。意即入栈的参数是由调用者来清理的。

关于返回结构或联合的函数。

这个函数的调用者要在自己的栈中为这个返回值预留出空间,并在把所有参数压入堆栈后,再把这个空间的地址压入堆栈,相当于第0个参数。

函数要把返回值复制到地址所指向的对象中。

函数必须在它返回时把这个地址从栈中移出。意即虽然这个地址是由调用者压入堆栈的,但它却是由被调用者清除的。
关键词(Tag): 编译原理 abi 人模狗样
收藏: QQ书签 del.icio.us 订阅: Google 抓虾