本文出自:http://www.nsfocus.com 维护:小四
8. Solaris内核编程相关问题
8.1 Solaris内核模块中如何getcwd
8.2
8.3 如何避免一个套接字进入TIME_WAIT状态
8.4 结构在优化编译中的对齐问题
8.5
8.6 如何得到非局部变量列表
8.7
8.8 如何单独获得Solaris编译环境
8.9 如何获取Solaris内核可调参数列表
8.10
8.11 如何页边界对齐式分配内存
8.12
8.13 compile()和step()怎么用
--------------------------------------------------------------------------
8. Solaris内核编程相关问题
8.1 Solaris内核模块中如何getcwd
Q: 在Solaris 7 64-bit内核模块中如何获知一个进程的当前工作目录(cwd),getcwd
并不是一个系统调用
A: Rich Teer
最好通过u->u_cdir获取当前工作目录(cwd)的vnode(v节点)。但这依赖于内核当前上
下文,curproc可能并不对应你期望的进程。
/usr/include/sys/user.h
typedef struct user
{
... ...
/*
* protected by p_lock
*/
struct vnode * u_cdir; /* current directory */
struct vnode * u_rdir; /* root directory */
8.3 如何避免一个套接字进入TIME_WAIT状态
Q: 我正在写一个unix server程序,不是daemon,经常需要在命令行上重启它,绝大
多数时候工作正常,但是某些时候会报告""bind: address in use"",于是重启失
败。
A: Andrew Gierth
server程序总是应该在调用bind()之前设置SO_REUSEADDR套接字选项。至于
TIME_WAIT状态,你无法避免,那是TCP协议的一部分。
Q: 如何避免等待60秒之后才能重启服务
A: Erik Max Francis
使用setsockopt,比如
--------------------------------------------------------------------------
int option = 1;
if ( setsockopt ( masterSocket, SOL_SOCKET, SO_REUSEADDR, &option,
sizeof( option ) ) < 0 )
{
die( ""setsockopt"" );
}
--------------------------------------------------------------------------
Q: 编写 TCP/SOCK_STREAM 服务程序时,SO_REUSEADDR到底什么意思?
A: 这个套接字选项通知内核,如果端口忙,但TCP状态位于 TIME_WAIT ,可以重用
端口。如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息,
指明""地址已经使用中""。如果你的服务程序停止后想立即重启,而新套接字依旧
使用同一端口,此时 SO_REUSEADDR 选项非常有用。必须意识到,此时任何非期
望数据到达,都可能导致服务程序反应混乱,不过这只是一种可能,事实上很不
可能。
一个套接字由相关五元组构成,协议、本地地址、本地端口、远程地址、远程端
口。SO_REUSEADDR 仅仅表示可以重用本地本地地址、本地端口,整个相关五元组
还是唯一确定的。所以,重启后的服务程序有可能收到非期望数据。必须慎重使
用 SO_REUSEADDR 选项。
Q: 在客户机/服务器编程中(TCP/SOCK_STREAM),如何理解TCP自动机 TIME_WAIT 状
态?
A: W. Richard Stevens <1999年逝世,享年49岁>
下面我来解释一下 TIME_WAIT 状态,这些在<>
中2.6节解释很清楚了。
MSL(最大分段生存期)指明TCP报文在Internet上最长生存时间,每个具体的TCP实现
都必须选择一个确定的MSL值。RFC 1122建议是2分钟,但BSD传统实现采用了30秒。
TIME_WAIT 状态最大保持时间是2 * MSL,也就是1-4分钟。
IP头部有一个TTL,最大值255。尽管TTL的单位不是秒(根本和时间无关),我们仍需
假设,TTL为255的TCP报文在Internet上生存时间不能超过MSL。
TCP报文在传送过程中可能因为路由故障被迫缓冲延迟、选择非最优路径等等,结果
发送方TCP机制开始超时重传。前一个TCP报文可以称为""漫游TCP重复报文"",后一个
TCP报文可以称为""超时重传TCP重复报文"",作为面向连接的可靠协议,TCP实现必须
正确处理这种重复报文,因为二者可能最终都到达。
一个通常的TCP连接终止可以用图描述如下:
client server
FIN M
close -----------------> (被动关闭)
ACK M+1
<-----------------
FIN N
<----------------- close
ACK N+1
----------------->
为什么需要 TIME_WAIT 状态?
假设最终的ACK丢失,server将重发FIN,client必须维护TCP状态信息以便可以重发
最终的ACK,否则会发送RST,结果server认为发生错误。TCP实现必须可靠地终止连
接的两个方向(全双工关闭),client必须进入 TIME_WAIT 状态,因为client可能面
临重发最终ACK的情形。
{
scz 2001-08-31 13:28
先调用close()的一方会进入TIME_WAIT状态
}
此外,考虑一种情况,TCP实现可能面临先后两个同样的相关五元组。如果前一个连
接处在 TIME_WAIT 状态,而允许另一个拥有相同相关五元组的连接出现,可能处理
TCP报文时,两个连接互相干扰。使用 SO_REUSEADDR 选项就需要考虑这种情况。
为什么 TIME_WAIT 状态需要保持 2MSL 这么长的时间?
如果 TIME_WAIT 状态保持时间不足够长(比如小于2MSL),第一个连接就正常终止了。
第二个拥有相同相关五元组的连接出现,而第一个连接的重复报文到达,干扰了第二
个连接。TCP实现必须防止某个连接的重复报文在连接终止后出现,所以让TIME_WAIT
状态保持时间足够长(2MSL),连接相应方向上的TCP报文要么完全响应完毕,要么被
丢弃。建立第二个连接的时候,不会混淆。
A: 小四
在Solaris 7下有内核参数对应 TIME_WAIT 状态保持时间
# ndd -get /dev/tcp tcp_time_wait_interval
240000
# ndd -set /dev/tcp tcp_time_wait_interval 1000
缺省设置是240000ms,也就是4分钟。如果用ndd修改这个值,最小只能设置到1000ms,
也就是1秒。显然内核做了限制,需要Kernel Hacking。
# echo ""tcp_param_arr/W 0t0"" | adb -kw /dev/ksyms /dev/mem
physmem 3b72
tcp_param_arr: 0x3e8 = 0x0
# ndd -set /dev/tcp tcp_time_wait_interval 0
我不知道这样做有什么灾难性后果,参看<>的声明。
Q: TIME_WAIT 状态保持时间为0会有什么灾难性后果?在普遍的现实应用中,好象也
就是服务器不稳定点,不见得有什么灾难性后果吧?
D: [email protected]
Linux 内核源码 /usr/src/linux/include/net/tcp.h 中
#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to successfully
* close the socket, about 60 seconds */
最好不要改为0,改成1。端口分配是从上一次分配的端口号+1开始分配的,所以一般
不会有什么问题。端口分配算法在tcp_ipv4.c中tcp_v4_get_port中。
8.4 结构在优化编译中的对齐问题
Q: 我正在写一个流模块,其中用到了#pragma pack(),当使用
gcc -D_KERNEL -c abc.c
ld -r -o abc abc.o
编译链接时,一切正常。为了获得64-bit模块,我必须使用Sun Workshop 5.0,
结果导致系统崩溃。访问
http://docs.sun.com/htmlcoll/coll.32.8/iso-8859-1/CPPPG/Pragmas.html#15434
上面说必须在编译链接应用程序的时候指定""-misalign"",所以我用了如下命令编译
/opt/SUNWspro/bin/cc -D_KERNEL -misalign -c abc.c
/usr/ccs/bin/ld -r -o abc abc.o
但是我不知道该如何在链接时指定""-misalign""。使用的是""/usr/ccs/bin/ld""。
A: Casper H.S. Dik - Network Security Engineer
""-misalign""仅仅用于应用程序,无法应用到内核编程中。""-misalign""使得编译
获得的代码增加了一些runtime glue,它们将指示内核模拟unaligned load(慢)。
作为内核编程,没有等效技术。
Q: 使用#pragma pack()是因为需要读取来自Windows客户端的报文,对端使用
#pragma pack(1)压缩了所使用的数据结构
#pragma pack(1)
typedef struct pkt_hdr_struct
{
uint8_t pkt_ver;
uint32_t pkt_type;
uint32_t pkt_len;
} pkt_hdr_t;
#pragma pack()
为了采用这个结构读取网络数据,Solaris端的服务程序需要强制转换匹配该结构,
但是一旦企图读取紧接在pkt_ver成员之后的pkt_type成员,崩溃了。尝试过其他
办法,首先用一个字符指针读取第一个字节,然后指针增一,把该指针强制类型
转换成( uint32_t * ),然后读取数据,依然崩溃。
此外,是否意味着无法在内核模块编程中使用#pragma pack()
A: Ed L Cashin
我想你可以单独写一个pkt_header_read()函数,单字节读取然后拼装成相应的数
据类型。如果你想避免函数调用,可以使用""inline""关键字。
A: Casper H.S. Dik - Network Security Engineer
你是否意识到pkt_hdr_t结构使得你必须自己转换字节序(对端是x86平台)
我不认为#pragma pack()是最好的解决办法,考虑定义如下结构
struct phs
{
char ver;
char type[4];
char len[4];
}
采用memcpy()读取数据
memcpy( &phs.type[0], &pkt.pkt_type, 4 );
A: Andrew Gabriel
采用字符指针是正确的,但是你犯了个错误,编写如下函数
int read_misaligned_int ( int * iptr )
{
int i;
int value;
char * ptr = ( char * )iptr;
char * vptr = ( char * )&value;
for ( i = 0; i < sizeof( int ); i++ )
{
*vptr++ = *ptr++;
}
return( value );
}
此外,既然你提到对端是x86平台,可能还需要考虑字节序转换的问题
A: W. Richard Stevens <1999年逝世,享年49岁>
/*
* return value:
* 1 big-endian
* 2 little-endian
* 3 unknow
* 4 sizeof( short ) != 2
*/
static int byte_order ( void )
{
union
{
short s;
char c[ sizeof( short ) ];
} un;
un.s = 0x0201;
if ( 2 == sizeof( short ) )
{
if ( ( 2 == un.c[0] ) && ( 1 == un.c[1] ) )
{
puts( ""big-endian"" );
return( 1 );
}
else if ( ( 1 == un.c[0] ) && ( 2 == un.c[1] ) )
{
puts( ""little-endian"" );
return( 2 );
}
else
{
puts( ""unknow"" );
return( 3 );
}
}
else
{
puts( ""sizeof( short ) = %d"", sizeof( short ) );
return( 4 );
}
return( 3 );
} /* end of byte_order */
D: CERNET 华中地区网络中心 程序设计版 集体讨论汇总
为了解决Unix自定义结构在GCC优化编译中对齐问题,一般解决办法是用如下宏封装
自定义结构
#pragma pack(1)
struct my_arphdr
{
};
#pragma pack()
如果是SPARC/Solaris,还可以这样
struct my_arphdr
{
} __attribute__ ((packed));
两种办法其实都可以用在Unix系统/GCC编译器中。
D: mbuf@smth
关于结构中字节对齐问题,相应编译器选项为
GCC/G++ : -fpack-struct
Sun Workshop cc/CC: -misalign
最好不这样做,会大大降低程序效率,特别在某些架构中。应该尝试用位操作来处理。
D: Unknown@smth
GCC可以这么解决
#ifdef __GCC__
#define PACKED __attribute__((__packed__))
#else
#define PACKED
#endif
struct msg
{
u_int16_t PACKED first;
...
};
还是 VC 简单,#include 就搞定了
A: gfh_nuaa
DEC : #pragma pack(1)
SUN : #pragma pack(1)
AIX : 编译时 -q align=packed
HP-UX : #pragma pack 1
D: Joe Durusau
在 Visual C++ 中,使用 ""-ZP1"" 就可以让编译器对自定义结构进行单字节对齐,实
际就是取消了对齐优化。
A: [email protected] 2001-12-20 13:09
1) 结构内部成员的pack
struct foo
{
char a;
int b __attribute__ ((packed));
};
2) 整个结构的pack
struct foo
{
char a;
int b;
}__attribute__ ((packed));
3) 文件范围的pack
#pragma pack(1)
struct foo
{
char a;
int b;
};
... ...
4) 编译选项的pack
-fpack-struct
但这是最危险的做法,因为这样做可能会使库函数和你的程序对结构内成员的偏移理
解不一致。
Q: 小四
#pragma pack(push)
#pragma pack(n)
... ...
#pragma pack(pop)
push/pop这个用法都谁支持啊
A: [email protected]
这个写法我没见过,VC和GCC都是这样写的
#pragma (push, N) // 把原来align设置压栈,并设新的pack为N
#pragma (pop) // align设置弹栈
8.6 如何得到非局部变量列表
Q: 什么工具可以从目标文件中提取非局部变量列表
A: Donald McLachlan
最简单的就是nm,假设你有一个目标文件(或者已链接过的可执行文件),nm -g将显
示所有""全局""变量。下面是一个Solaris的例子:
--------------------------------------------------------------------------
/* gcc -o junk junk.c */
int var1;
static int var2;
int main ( void )
{
int var3;
return( 0 );
} /* end of main */
--------------------------------------------------------------------------
$ nm -g junk
junk:
[Index] Value Size Type Bind Other Shndx Name
[66] | 133640| 0|OBJT |GLOB |0 |15 |_DYNAMIC
[61] | 133496| 0|OBJT |GLOB |0 |13 |_GLOBAL_OFFSET_TABLE_
[71] | 133528| 0|OBJT |GLOB |0 |14 |_PROCEDURE_LINKAGE_TABLE_
[69] | 0| 0|NOTY |WEAK |0 |UNDEF |__deregister_frame_info
[60] | 0| 0|NOTY |WEAK |0 |UNDEF |__register_frame_info
[70] | 133836| 0|OBJT |GLOB |0 |19 |_edata
[59] | 133872| 0|OBJT |GLOB |0 |20 |_end
[58] | 133864| 4|OBJT |GLOB |0 |20 |_environ
[72] | 67960| 0|OBJT |GLOB |0 |12 |_etext
[67] | 133600| 0|FUNC |GLOB |0 |UNDEF |_exit
[75] | 67936| 20|FUNC |GLOB |0 |11 |_fini
[64] | 67908| 28|FUNC |GLOB |0 |10 |_init
[73] | 67956| 4|OBJT |GLOB |0 |12 |_lib_version
[57] | 67380| 116|FUNC |GLOB |0 |9 |_start
[62] | 133576| 0|FUNC |GLOB |0 |UNDEF |atexit
[68] | 133864| 4|OBJT |WEAK |0 |20 |environ
[63] | 133588| 0|FUNC |GLOB |0 |UNDEF |exit
[74] | 67784| 24|FUNC |GLOB |0 |9 |main
[65] | 133868| 4|OBJT |GLOB |0 |20 |var1
$
注意到var2这样的""静态全局变量"",由于仅仅在单个源文件中有效,nm -g并未显示
它。如果不指定-g选项,将显示var2(当然会显示更多垃圾信息)。
$ nm junk
junk:
[Index] Value Size Type Bind Other Shndx Name
... ...
[65] | 133868| 4|OBJT |GLOB |0 |20 |var1
[46] | 133860| 4|OBJT |LOCL |0 |20 |var2
$
8.8 如何单独获得Solaris编译环境
Q: 我需要安装哪些包
A: Seán Boran
需要下列Solaris安装包:
SUNWbtool、SUNWsprot、SUNWtoo、SUNWhea、SUNWarc、SUNWlibm、SUNWlibms
可以用pkginfo [-l]检查是否安装了这些包
$ pkginfo SUNWbtool SUNWsprot SUNWtoo SUNWhea SUNWarc SUNWlibm SUNWlibms
system SUNWarc Archive Libraries
system SUNWbtool CCS tools bundled with SunOS
system SUNWhea SunOS Header Files
system SUNWlibm Sun WorkShop Bundled libm
system SUNWlibms Sun WorkShop Bundled shared libm
system SUNWsprot Solaris Bundled tools
system SUNWtoo Programming Tools
$
可以从Solaris CD中单独安装缺少的包(pkgadd)
象make这样的工具安装在/usr/ccs/bin,增加到$PATH环境变量中。但是这个make和
某些工具相冲突,比如BIND,此时应该安装GNU make,确认GNU make的搜索路径位于
/usr/ccs/bin/make之前。另外,$PATH环境变量中/usr/ccs/bin应该位于/usr/ucb之
前。
8.9 如何获取Solaris内核可调参数列表
Q: 谁有Solaris内核可调参数列表
A: Andrew Garman
执行
/usr/xpg4/bin/nm /platform/sun4u/kernel/unix | egrep ''OBJT |GLOB'' | more
显示结果中部分为Solaris内核可调参数,另外一些非可调内核参数。可以用ndd获取、
设置网络相关参数。
D: scz
可以考虑
/usr/ccs/bin/nm -nx /dev/ksyms | egrep ''OBJT |GLOB'' | more
不知道二者区别何在?第二个报告内容应该包含了后来动态加载内核模块输出的符号,
第一个才对应基本内核输出的符号。
8.11 如何页边界对齐式分配内存
Q: 我希望在页边界上分配大块内存,要求普通用户、非特权进程亦能使用此技术。
在mmap(2)手册页中没有明确表明返回地址边界对齐。它提到可以指定起始地址以
保证页边界对齐,但没有说明如果由系统选定起始地址时是否也是页边界对齐的。
MAP_ANON并非所有系统都支持,我需要在Solaris 2.x上运行。
A: Andrew Gierth
mmap(2)即可满足要求。某些系统提供了valloc或者memalign,但它们的实现机制是,
分配超过请求大小的内存,然后调整之,这相当浪费。
mmap(2)应该始终是页边界对齐的。
在那些不支持 MAP_ANON 的系统上,打开/dev/zero获取句柄,传递给mmap(2),效果
是一样的。
mmap(2)的可移植性足够好,不过""分配超过请求大小的内存并调整之""可能更具有可
移植性。
8.13 compile()和step()怎么用
Q: 我知道这两个函数是Solaris对正则表达式的支持函数,可到底怎么用呢?
A: microcat
--------------------------------------------------------------------------
/* gcc -Wall -O3 -o reg reg.c -lgen */
#include
#include
#include
int main ( int argc, char * argv[] )
{
char * expbuf = NULL;
if ( ( expbuf = compile( argv[1], NULL, NULL ) ) == NULL )
{
exit( EXIT_FAILURE );
}
if ( step( argv[2], expbuf ) )
{
printf( ""Match at: %s
"", loc1 );
}
else
{
printf( ""No match.
"" );
}
free( expbuf );
exit( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------
$ ./reg ''^.*inetd$'' ''/usr/sbin/inetd''
Match at: /usr/sbin/inetd
$