0x10814
0x10818
0x1081c
0x10820
0x10824
0x10828
0x1082c
0x10830
0x10834
0x10838
0x1083c
0x10840
0x10844
0x10848
0x1084c
0x10850
0x10854
0x10858
0x1085c
0x10860
End of assembler dump.
(gdb) i r pc
pc 0x1084c 67660
(gdb) i r o0
o0 0xffbefbdd -4260899
(gdb) x/3bx 0xffbefbdd
0xffbefbdd: 0x34 0x56 0x78
(gdb)
从C语言来说,执行"*q = 0x0000;"时导致SIGBUS了。从汇编指令来说,执行"clrh [%o0]"时导致SIGBUS了,寄存器%o0值为0xffbefbdd,这个地址未对齐在双字节边界上。
注意,gcc编译时并未指定-O
bus.c是显式的未对齐。程序员实际最容易面对的是隐式未对齐,主要来自指针的强制类型转换。下面举例说明这种情形。
-
/*
-
* Test: SPARC/Solaris 8 64-bit kernel mode
-
* gcc -Wall -pipe -g -o other_bus other_bus.c
-
*/
-
#include <stdio.h>
-
#include <stdlib.h>
-
-
-
int main ( int argc, char * argv[] )
-
{
-
unsigned int i = 0x12345678;
-
unsigned short int j = 0x0000;
-
-
-
j = *( ( unsigned short int * )( ( ( unsigned char * )&i ) + 1 ) );
-
return( EXIT_SUCCESS );
- } /* end of main */
总线错误 (core dumped)
$ gdb ./other_bus core
GNU gdb 5.0
#0 main (argc=1, argv=0xffbefc44) at other_bus.c:13
13 j = *( ( unsigned short int * )( ( ( unsigned char * )&i ) + 1 ) );
(gdb) disas main
Dump of assembler code for function main:
0x10810
0x10814
0x10818
0x1081c
0x10820
0x10824
0x10828
0x1082c
0x10830
0x10834
0x10838
0x1083c
0x10840
0x10844
End of assembler dump.
(gdb) i r pc
pc 0x1082c 67628
(gdb)
因此在SPARC架构上编程,一定要留神强制类型转换,务必清楚自己正在干什么,有没有隐患。
D: yuhuan@SMTH 2004-01-30 11:48
参Linux的mmap(2)手册页
--------------------------------------------------------------------------
使用映射可能涉及到如下信号
SIGSEGV
试图对只读映射区域进行写操作
SIGBUS
试图访问一块无文件内容对应的内存区域,比如超过文件尾的内存区域,或者以前有文件内容对应,现在为另一进程截断过的内存区域。
--------------------------------------------------------------------------
2.1 如何理解pstack的输出信息
Q: 080603a7 main (1, 80479b8, 80479c0) + d53
结尾的d53是什么
A: Roger A. Faulkner
在代码段绝对地址0x080603a7处,main()调用了一个函数,0x080603a7正是main + 0xd53,换句话说,从main()函数开始的0xd53偏移处。
2.3 Solaris中如何获取一个C程序的调用栈回溯
Q: 我想在Solaris 2.6及其后续版本上获取一个C程序的调用栈回溯,类似如下输出
(10) 0x00045e08 integ + 0x408 [./two_brn.e]
(11) 0x0006468c trajcem + 0x128 [./two_brn.e]
(12) 0x00055490 fly_traj + 0xf58 [./two_brn.e]
(13) 0x0004052c top_level + 0x14 [./two_brn.e]
(14) 0x000567e4 _start + 0x34 [./two_brn.e]
这样我就可以知道当程序崩溃、死锁的时候代码执行到了何处。在HP-UX和IRIX上,可以利用U_STACK_TRACE()和trace_back_stack_and_print(),Solaris上呢?
Q: 有没有办法显示当前堆栈中的数据(GNU/Linux系统)?我希望自己的异常处理程序在进程结束前dump整个栈区(stack),以便观察到栈顶是什么函数。对于调试意想不到的运行时错误而言,这很重要。
Q: Is it possible to unwind the stack on Solaris 8? Is there an API that I could use? I know that with TRU64(Digital UNIX) there are the exception
handling routines: except_virtual_unwind() and except_capture_context(). Basically, what I am trying to do is print out the stack on demand,
just as dbx or gdb would.
A: Bjorn Reese
用/usr/proc/bin/pstack [-F]
参看这个例子代码,http://home1.stofanet.dk/breese/debug/debug.tar.gz
Q: is there a way to access call stack information at run time from within
a program? i've been maintaining my own crude stack using __FUNCTION__
and linked lists but can't help but think there's gotta be a better
way...
A: Nate Eldredge
这依赖于你的系统,如果使用glibc 2.1或更新版本,可以使用backtrace()函数,
参看
注意,你所使用的办法可能是唯一能够保证跨平台使用的
A: Andrew Gabriel
下面是一个backtrace()的应用举例,如果你使用Solaris 2.4及其后续版本,那么这
个例子可以很好的工作。很可能无法工作在64-bit模式下,我没有尝试过,好像
Solaris 7已经提供了一个类似的演示程序。还可以增加某些功能,我没有时间了。
-
/*
-
* Produce a stack trace for Solaris systems.
-
*
-
* Copyright (C) 1995-1998 Andrew Gabriel <andrew@cucumber.demon.co.uk>
-
* Parts derived from Usenet postings of Bart Smaalders and Casper Dik.
-
*
-
*/
-
-
/* ......................................................................... */
-
-
#include <setjmp.h>
-
#include <sys/types.h>
-
#include <sys/reg.h>
-
#include <sys/frame.h>
-
#include <dlfcn.h>
-
#include <errno.h>
-
#include <unistd.h>
-
#include <stdio.h>
-
-
#if defined(sparc) || defined(__sparc)
-
#define FLUSHWIN() asm("ta 3");
-
#define FRAME_PTR_INDEX 1
-
#define SKIP_FRAMES 0
-
#endif
-
-
#if defined(i386) || defined(__i386)
-
#define FLUSHWIN()
-
#define FRAME_PTR_INDEX 3
-
#define SKIP_FRAMES 1
-
#endif
-
-
#if defined(ppc) || defined(__ppc)
-
#define FLUSHWIN()
-
#define FRAME_PTR_INDEX 0
-
#define SKIP_FRAMES 2
-
#endif
-
-
/* ......................................................................... */
-
-
static void print_address ( void * pc )
-
{
-
Dl_info info;
-
-
if ( dladdr( pc, &info ) == 0 )
-
{
-
/* not found */
-
fprintf( stderr, "*** %s:0x%x\n", "??", ( unsigned int )pc );
-
}
-
else
-
{
-
/* found */
-
fprintf( stderr, "*** %s:%s+0x%x\n", info.dli_fname, info.dli_sname,
-
( unsigned int )pc - ( unsigned int )info.dli_saddr );
-
}
-
return;
-
} /* end of print_address */
-
-
/* ......................................................................... */
-
-
static int validaddr ( void * addr )
-
{
-
static long pagemask = -1;
-
char c;
-
-
if ( pagemask == -1 )
-
{
-
pagemask = ~( sysconf( _SC_PAGESIZE ) - 1 );
-
}
-
addr = ( void * )( ( long )addr & pagemask );
-
if ( mincore( ( char * )addr, 1, &c ) == -1 && errno == ENOMEM )
-
{
-
return 0; /* invalid */
-
}
-
else
-
{
-
return 1; /* valid */
-
}
-
} /* end of validaddr */
-
-
/* ......................................................................... */
-
-
/*
-
* this function walks up call stack, calling print_addess
-
* once for each stack frame, passing the pc as the argument.
-
*/
-
-
static void print_stack ( void )
-
{
-
struct frame * sp;
-
jmp_buf env;
-
int i;
-
int * iptr;
-
-
FLUSHWIN();
-
-
setjmp( env );
-
iptr = ( int * )env;
-
-
sp = ( struct frame * )iptr[ FRAME_PTR_INDEX ];
-
-
for ( i = 0; i < SKIP_FRAMES && sp; i++ )
-
{
-
if ( !validaddr( sp ) || !validaddr( &sp->fr_savpc ) )
-
{
-
fprintf( stderr, "***[stack pointer corrupt]\n" );
-
return;
-
}
-
sp = ( struct frame * )sp->fr_savfp;
-
}
-
-
i = 100; /* looping check */
-
-
while ( validaddr( sp ) && validaddr( &sp->fr_savpc ) && sp->fr_savpc && --i )
-
{
-
print_address( ( void * )sp->fr_savpc );
-
sp = ( struct frame * )sp->fr_savfp;
-
}
-
} /* end of print_stack */
-
-
/* ......................................................................... */
-
-
void backtrace( void )
-
{
-
fprintf( stderr, "***backtrace...\n" );
-
print_stack();
-
fprintf( stderr, "***backtrace ends\n" );
-
}
-
- /* ......................................................................... */
SunOS usunnad01 5.8 Generic_108528-14 sun4u sparc SUNW,UltraAX-i2
假设有如下代码
-
caller_func ()
-
{
-
called_func();
-
}
-
-
-
called_func ()
-
{
-
printf( "called_func() is being called from %s\n", some_magic_func() );
- }
"called_func() is being called from caller_func()"
请问如何实现some_magic_func(),C或者汇编语言编程都可以。
D: Paul Pluzhnikov
看看mpatrol的源代码,其中有traceback()函数可以给出整个调用栈回溯,而不仅仅是主调函数。
D: Peter Ammon
可以考虑使用宏,这是一个例子
-
/*
-
* gcc -Wall -pipe -g -o test test.c
-
*/
-
#include <stdio.h>
-
-
-
#define CALL(x) (printf("Calling %s from %s\n", #x, __FUNCTION__), x)
-
-
-
int main ( void )
-
{
-
char buf[64];
-
-
-
while ( CALL( fgets( buf, sizeof( buff ), stdin ) ) != NULL )
-
{
-
CALL( puts( buf ) );
-
}
-
return( 0 );
- }
A: Sun Microsystems 2000-06-13
下面演示如何编程获取当前运行中线程调用栈回溯。惟一要做的就是在应用程序中调用csprintstack(),记得链接库选项-ldl。
-
/*
-
* For SPARC/Solaris 8
-
* gcc -D__sparc -Wall -pipe -g -o test test.c -ldl
-
*
-
* For x86/Solaris 9
-
* gcc -D__i386 -Wall -pipe -g -o test test.c -ldl
-
*/
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <ucontext.h>
-
#include <dlfcn.h>
-
#include <setjmp.h>
-
#include <sys/frame.h>
-
#include <sys/procfs_isa.h>
-
-
-
#if defined(sparc) || defined(__sparc)
-
#define FRAME_PTR_REGISTER REG_SP
-
#endif
-
#if defined(i386) || defined(__i386)
-
#define FRAME_PTR_REGISTER EBP
-
#endif
-
-
-
struct frame * csgetframeptr ( void )
-
{
-
ucontext_t u;
-
-
-
( void )getcontext( &u );
-
return( ( struct frame * )( ( struct frame * )u.uc_mcontext.gregs[FRAME_PTR_REGISTER] )->fr_savfp );
-
} /* end of csgetframeptr */
-
-
-
void cswalkstack ( struct frame *fp, int ( *operate_func ) ( void *, void * ), void *usrarg )
-
{
-
void *savpc;
-
-
-
while ( fp && ( savpc = ( void * )fp->fr_savpc )
-
&& ( *operate_func )( savpc, usrarg ) == 0 )
-
{
-
fp = ( void * )fp->fr_savfp;
-
}
-
} /* end of cswalkstack */
-
-
-
static int csprintaddress ( void *pc, void *usrarg )
-
{
-
Dl_info info;
-
char *func;
-
char *lib;
-
-
-
if ( dladdr( pc, &info ) == 0 )
-
{
-
func = "??";
-
lib = "??";
-
}
-
else
-
{
-
lib = ( char * )info.dli_fname;
-
func = ( char * )info.dli_sname;
-
}
-
fprintf( ( FILE * )usrarg, "%s:%s+0x%x\n", lib, func,
-
( unsigned int )pc - ( unsigned int )info.dli_saddr );
-
return( 0 );
-
} /* end of csprintaddress */
-
-
-
void csprintstack ( FILE *f )
-
{
-
cswalkstack( csgetframeptr(), csprintaddress, ( void * )f );
-
} /* end of csprintstack */
-
-
-
void call_2 ( void )
-
{
-
csprintstack( stderr );
-
} /* end of call_2 */
-
-
-
void call_1 ( void )
-
{
-
call_2();
-
} /* end of call_1 */
-
-
-
void call_0 ( void )
-
{
-
call_1();
-
} /* end of call_0 */
-
-
-
int main ( void )
-
{
-
call_0();
-
return( EXIT_SUCCESS );
- } /* end of main */
[scz@ /export/home/scz/src]> ./test
test:call_2+0xc
test:call_1+0x4
test:call_0+0x4
test:main+0x4
test:_start+0x5c
[scz@ /export/home/scz/src]> gcc -D__i386 -Wall -pipe -g -o test test.c -ldl
[scz@ /export/home/scz/src]> ./test
/export/home/scz/src/test:call_2+0x13
/export/home/scz/src/test:call_1+0xb
/export/home/scz/src/test:call_0+0xb
/export/home/scz/src/test:main+0x15
/export/home/scz/src/test:_start+0x5d
[scz@ /export/home/scz/src]>
2.4 如何编程获取栈底地址
Q: 虽然很多操作系统的用户进程栈底地址固定,但是我需要写一个可广泛移植C程序获取这个栈底地址。
A: tt
假设堆栈(stack)向低地址方向增长,则所谓栈底指堆栈(stack)最高地址
x86/Linux 栈底是0xC0000000 (栈底往低地址的4个字节总是零)
SPARC/Solaris 7/8 栈底是0xFFBF0000 (栈底往低地址的4个字节总是零)
SPARC/Solaris 2.6 栈底是0xF0000000 (栈底往低地址的4个字节总是零)
x86/Solaris 8 栈底是0x08048000
x86/FreeBSD 栈底是0xBFC00000 (栈底往低地址的4个字节总是零)
x86/NetBSD 1.5 栈底是0xBFBFE000
x86/OpenBSD 2.8/3.0 栈底是0xDFBFE000
AIX 4.3.3.0 栈底是0x2FF23000
D: jonah
对于NetBSD 1.5,栈底是0xBFC00000。根据源码,最高用户地址是0xBFBFE000,因为最后4MB(2^22)的最后两页(0x2000字节,一页4096字节)保留用做U区,但是目前不再使用这块内存。因此,0xBFBFE000才是真正的栈底。
tt在OpenBSD 2.8上测试结果,栈底是0xDFBFE000,注意和NetBSD 1.5相差很大。
A: tt
-
/*
-
* gcc -Wall -pipe -O3 -o gstack gstack.c
-
*
-
* A simple example to get the current stack bottom address
-
* warning3 <warning3@nsfocus.com>
-
* 2001-06-01
-
*
-
* Modified by scz <scz@nsfocus.com>
-
* 2001-06-02
-
*/
-
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <signal.h>
-
#include <unistd.h>
-
#include <setjmp.h>
-
-
/*
-
* for signal handlers
-
*/
-
typedef void Sigfunc ( int );
-
-
static char * get_stack_bottom ( void );
-
static Sigfunc * PrivateSignal ( int signo, Sigfunc * func );
-
static void segfault ( int signo );
-
static Sigfunc * Signal ( int signo, Sigfunc * func );
-
-
static sigjmp_buf jmpbuf;
-
static volatile sig_atomic_t canjump = 0;
-
static Sigfunc *seg_handler;
-
/*
-
* for xxxBSD
-
*/
-
static Sigfunc *bus_handler;
-
-
static char * get_stack_bottom ( void )
-
{
-
/*
-
* for autovar, must be volatile
-
*/
-
volatile char *c;
-
-
seg_handler = Signal( SIGSEGV, segfault );
-
bus_handler = Signal( SIGBUS, segfault );
-
c = ( char * )&c;
-
if ( sigsetjmp( jmpbuf, 1 ) != 0 )
-
{
-
Signal( SIGSEGV, seg_handler );
-
Signal( SIGBUS, bus_handler );
-
return( ( char * )c );
-
}
-
/*
-
* now sigsetjump() is OK
-
*/
-
canjump = 1;
-
while ( 1 )
-
{
-
*c = *c;
-
c++;
-
}
-
return( NULL );
-
} /* end of get_stack_bottom */
-
-
static Sigfunc * PrivateSignal ( int signo, Sigfunc * func )
-
{
-
struct sigaction act, oact;
-
-
act.sa_handler = func;
-
sigemptyset( &act.sa_mask );
-
act.sa_flags = 0;
-
if ( sigaction( signo, &act, &oact ) < 0 )
-
{
-
return( SIG_ERR );
-
}
-
return( oact.sa_handler );
-
} /* end of PrivateSignal */
-
-
static void segfault ( int signo )
-
{
-
if ( 0 == canjump )
-
{
-
/*
-
* unexpected signal, ignore
-
*/
-
return;
-
}
-
canjump = 0;
-
/*
-
* jump back to main, don't return
-
*/
-
siglongjmp( jmpbuf, signo );
-
} /* end of segfault */
-
-
/*
-
* for our signal() function
-
*/
-
static Sigfunc * Signal ( int signo, Sigfunc * func )
-
{
-
Sigfunc * sigfunc;
-
-
if ( SIG_ERR == ( sigfunc = PrivateSignal( signo, func ) ) )
-
{
-
exit( EXIT_FAILURE );
-
}
-
return( sigfunc );
-
} /* end of Signal */
-
-
int main ( int argc, char * argv[] )
-
{
-
fprintf
-
(
-
stderr,
-
"Current stack bottom is 0x%08x\n",
-
( unsigned int )get_stack_bottom()
-
);
-
return( EXIT_SUCCESS );
- } /* end of main */
W. Richard Stevens在<
这个程序的原理很简单,不断向栈底方向取值,越过栈底的地址访问会导致SIGSEGV信号,然后利用长跳转回到主流程报告当前c值,自然对应栈底。
tt测试表明,在x86/FreeBSD中导致SIGBUS信号。据jonah报告,不仅仅是FreeBSD,NetBSD 以及 OpenBSD 系统中上述程序越界访问也导致SIGBUS信号,而不是SIGSEGV信号。
非局部转移,比如函数间转移的时候考虑使用setjmp/longjmp。但是如果涉及到信号句柄与主流程之间的转移,就不能使用longjmp了。当捕捉到信号进入信号句柄,此时当前信号被自动加入进程的信号屏蔽字中,阻止后来产生的这种信号干扰此信号句柄。如果用longjmp跳出信号句柄,此时进程的信号屏蔽字状态未知,有些系统做了保存恢复,有些系统没有做,比如x86/Linux Kernel 2.4.7-10的setjmp/longjmp没有做信号屏蔽字的保存恢复。根据POSIX.1,此时应该使用sigsetjmp/siglongjmp函数。下面是来自SPARC/Solaris 7的setjmp(3C)
--------------------------------------------------------------------------
#include
int setjmp ( jmp_buf env );
int sigsetjmp ( sigjmp_buf env, int savemask );
void longjmp ( jmp_buf env, int val );
void siglongjmp ( sigjmp_buf env, int val );
--------------------------------------------------------------------------
如果savemask非0,sigsetjmp在env中保存进程当前信号屏蔽字,相应siglongjmp回来的时候从env中恢复信号屏蔽字。
数据类型sig_atomic_t由ANSI C定义,在写时不会被中断。它意味着这种变量在具有虚存的系统上不会跨越页边界,可以用一条机器指令对其存取。这种类型的变量总是与ANSI类型修饰符volatile一并出现,防止编译器优化带来的不确定状态。
在longjmp/siglongjmp中,全局、静态变量保持不变,声明为volatile的自动变量也保持不变。
无论是否使用了编译优化开关,为了保证广泛兼容性,都应该在get_stack_bottom()中声明c为volatile变量。
注意这里,必须使用长跳转,而不能从信号句柄中直接返回。因为导致信号SIGSEGV、SIGBUS分发的语句始终存在,直接从信号句柄中返回主流程,将回到引发信号的原指令处,而不是下一条指令(把这种情况理解成异常,而不是中断),于是立即导致下一次信号分发,出现广义上的死循环,所谓程序僵住。可以简单修改上述程序,不利用长跳转,简单对一个全局变量做判断决定是否继续循环递增c,程序最终僵住;如果在信号句柄中输出调试信息,很容易发现这个广义上的无限循环。
D: scz
在x86/Linux系统中用如下命令可以确定栈区所在
# cat /proc/1/maps <-- 观察1号进程init
... ...
bfffe000-c0000000 rwxp fffff000 00:00 0
#
在SPARC/Solaris 7中用/usr/proc/bin/pmap命令确定栈区所在
# /usr/proc/bin/pmap 1 <-- 观察1号进程init
... ...
FFBEC000 16K read/write/exec [ stack ]
#
16KB == 0x4000,0xFFBEC000 + 0x4000 == 0xFFBF0000
与前面tt介绍的SPARC/Solaris 7/8 栈底是0xffbf0000( 栈底往低地址的4个字节总是零 )相符合。
此外,在SPARC/Solaris 7下,可以这样验证之
# /usr/ccs/bin/nm -nx /dev/ksyms | grep "|_userlimit"
[7015] |0x0000100546f8|0x000000000008|OBJT |GLOB |0 |ABS |_userlimit
[8051] |0x000010054700|0x000000000008|OBJT |GLOB |0 |ABS |_userlimit32
# echo "_userlimit /J" | adb -k /dev/ksyms /dev/mem
physmem 3b72
_userlimit:
_userlimit: ffffffff80000000
# skd64 0x000010054700 8
byteArray [ 8 bytes ] ---->
0000000000000000 00 00 00 00 FF BF 00 00
# ~~~~~~~~~~~ 对于32-bit应用程序来说,这是用户
空间上限
如果编译64-bit应用程序,用户空间上限是_userlimit,也就是0xffffffff80000000
# /opt/SUNWspro/SC5.0/bin/cc -xarch=v9 -O -o gstack gstack.c
# ./gstack
Current stack bottom is at 0xffffffff80000000
#
对于SPARC/Solaris 2.6 32-bit kernel mode
# echo "_userlimit /X" | adb -k /dev/ksyms /dev/mem
physmem 3d24
_userlimit:
_userlimit: f0000000
Q: 在x86/Linux平台上如何定位栈区(stack)的栈底(高址)与栈顶(低址)位置。
D: "Andrew Gabriel"
试试getcontext(2)
A: "Shaun Clowes"
检查/proc/
如果使用getcontext(2),可以通过struct ucontext的uc_mcontext成员获取栈顶位置,参看/usr/include/sys/ucontext.h。不幸的是此时uc_stack成员未被设置,无法简单获取栈底位置,至少对于我所检测的版本而言,Redhat 2.4.18-3smp kernelwith glibc 2.2.5。
2.5 如何得到一个运行中进程的内存映像
A: Sun Microsystems 1998-03-30
有些时候必须得到一个运行中进程的内存映像而不能停止该进程,Solaris系统了这样的工具,gcore为运行中进程创建一个core文件。假设我的bash进程号是5347
# gcore 5347
gcore: core.5347 dumped
# file core.5347
core.5347: ELF 32-位 MSB core文件 SPARC 版本 1,来自'bash'
#
注意,只能获取属主是你自己的进程的内存映像,除非你是root。
2.6 调试器如何工作的
Q: 我想在一个自己编写的程序中单步运行另外一个程序,换句话说,那是一个调试器,该如何做?
A: Erik de Castro Lopo
这是一个操作系统相关的问题。最一般的回答是使用ptrace()系统调用,尽管我不确认究竟这有多么普遍。Linux man手册上说SVr4、SVID EXT、AT&T、X/OPEN和BSD 4.3都支持它。
为了使用ptrace(),你的程序应该调用fork(),然后在子进程中做如下调用:
ptrace( PTRACE_TRACEME, 0, 0, 0 );
接下来调用exec()家族的函数执行你最终企图跟踪的程序。
为了单步进入子进程,在父进程中调用:
ptrace( PTRACE_SINGLESTEP, 0, 0, 0 );
还有一些其他函数做恢复/设置寄存器、内存变量一类的工作。
GDB的源代码足以回答这个问题。
2.7 x86/Linux上如何处理SIGFPE信号
Q: 参看如下程序
-
/*
-
* gcc -Wall -pipe -O3 -o sigfpe_test_0 sigfpe_test_0.c
-
*
-
* 注意与下面的编译效果进行对比,去掉优化开关-O3
-
*
-
* gcc -Wall -pipe -o sigfpe_test_0 sigfpe_test_0.c
-
*/
-
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <string.h>
-
#include <signal.h>
-
#include <unistd.h>
-
#include <setjmp.h>
-
-
/*
-
* for signal handlers
-
*/
-
typedef void Sigfunc ( int );
-
-
Sigfunc * signal ( int signo, Sigfunc *func );
-
static Sigfunc * Signal ( int signo, Sigfunc *func );
-
static void on_fpe ( int signo );
-
-
Sigfunc * signal ( int signo, Sigfunc *func )
-
{
-
struct sigaction act, oact;
-
-
act.sa_handler = func;
-
sigemptyset( &act.sa_mask );
-
act.sa_flags = 0;
-
if ( signo == SIGALRM )
-
{
-
#ifdef SA_INTERRUPT
-
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
-
#endif
-
}
-
else
-
{
-
#ifdef SA_RESTART
-
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
-
#endif
-
}
-
if ( sigaction( signo, &act, &oact ) < 0 )
-
{
-
return( SIG_ERR );
-
}
-
return( oact.sa_handler );
-
} /* end of signal */
-
-
static Sigfunc * Signal ( int signo, Sigfunc *func )
-
{
-
Sigfunc *sigfunc;
-
-
if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )
-
{
-
perror( "signal" );
-
exit( EXIT_FAILURE );
-
}
-
return( sigfunc );
-
} /* end of Signal */
-
-
static void on_fpe ( int signo )
-
{
-
fprintf( stderr, "here is on_fpe\n" );
-
return;
-
} /* end of on_fpe */
-
-
int main ( int argc, char * argv[] )
-
{
-
unsigned int i;
-
-
Signal( SIGFPE, on_fpe );
-
i = 51211314 / 0;
-
/*
-
* 另外,增加这行后,再次对比有-O3和无-O3的效果
-
*
-
* fprintf( stderr, "i = %#X\n", i );
-
*/
-
return( EXIT_SUCCESS );
- } /* end of main */
D: 小四
在上述代码中,on_fpe()直接返回了,再次触发除零错,所以无休止输出。事实上在所有的计算器处理程序中,都会对SIGFPE信号做相应处理,前些日子看yacc/lex的时候又碰上过。正确的做法是,利用远跳转转移,让开触发除零错的代码。
代码修改如下
-
/*
-
* gcc -Wall -pipe -O3 -o sigfpe_test_1 sigfpe_test_1.c
-
*
-
* 注意与下面的编译效果进行对比,去掉优化开关-O3
-
*
-
* gcc -Wall -pipe -o sigfpe_test_1 sigfpe_test_1.c
-
*/
-
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <string.h>
-
#include <signal.h>
-
#include <unistd.h>
-
#include <setjmp.h>
-
-
/*
-
* for signal handlers
-
*/
-
typedef void Sigfunc ( int );
-
-
Sigfunc * signal ( int signo, Sigfunc *func );
-
static Sigfunc * Signal ( int signo, Sigfunc *func );
-
static void on_fpe ( int signo );
-
-
static sigjmp_buf jmpbuf;
-
static volatile sig_atomic_t canjump = 0;
-
-
Sigfunc * signal ( int signo, Sigfunc *func )
-
{
-
struct sigaction act, oact;
-
-
act.sa_handler = func;
-
sigemptyset( &act.sa_mask );
-
act.sa_flags = 0;
-
if ( signo == SIGALRM )
-
{
-
#ifdef SA_INTERRUPT
-
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
-
#endif
-
}
-
else
-
{
-
#ifdef SA_RESTART
-
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
-
#endif
-
}
-
if ( sigaction( signo, &act, &oact ) < 0 )
-
{
-
return( SIG_ERR );
-
}
-
return( oact.sa_handler );
-
} /* end of signal */
-
-
static Sigfunc * Signal ( int signo, Sigfunc *func )
-
{
-
Sigfunc *sigfunc;
-
-
if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )
-
{
-
perror( "signal" );
-
exit( EXIT_FAILURE );
-
}
-
return( sigfunc );
-
} /* end of Signal */
-
-
static void on_fpe ( int signo )
-
{
-
if ( canjump == 0 )
-
{
-
return; /* unexpected signal, ignore */
-
}
-
canjump = 0;
-
fprintf( stderr, "here is on_fpe\n" );
-
siglongjmp( jmpbuf, signo ); /* jump back to main, don't return */
-
return;
-
} /* end of on_fpe */
-
-
int main ( int argc, char * argv[] )
-
{
-
unsigned int i;
-
-
if ( sigsetjmp( jmpbuf, 1 ) != 0 )
-
{
-
fprintf( stderr, "c u later\n" );
-
return( EXIT_SUCCESS );
-
}
-
/*
-
* now sigsetjump() is OK
-
*/
-
canjump = 1;
-
Signal( SIGFPE, on_fpe );
-
i = 51211314 / 0;
-
/*
-
* 另外,增加这行后,再次对比有-O3和无-O3的效果
-
*
-
* fprintf( stderr, "i = %#X\n", i );
-
*/
-
return( EXIT_SUCCESS );
- } /* end of main */
2.8 GDB调试时没有符号表,如何设置断点
Q: 在OpenBSD 3.0下想用gdb跟踪/usr/bin/skeyaudit(一个setuid-to-root程序),却因没有符号表无法设置断点
A: tt
# objdump -f /usr/bin/skeyaudit
start address 0x00001020
# gdb /usr/bin/skeyaudit
(gdb) x/50i 0x1020
0x1020: push %ebp <-- 这是start
0x1021: mov %esp,%ebp
0x1023: sub $0xc,%esp
0x1026: push %edi
0x1027: push %esi
0x1028: push %ebx
0x1029: lea 0x4(%ebp),%esi
0x102c: lea 0x4(%esi),%edi
0x102f: mov (%esi),%eax
0x1031: shl $0x2,%eax
0x1034: lea 0x4(%eax,%edi,1),%eax
0x1038: mov %eax,0x33f8
0x103d: mov 0x4(%esi),%ebx
0x1040: test %ebx,%ebx
0x1042: je 0x106a
0x1044: add $0xfffffff8,%esp
0x1047: push $0x2f
0x1049: push %ebx
0x104a: call 0x1774 <-- 这是一个错误处理入口
0x104f: mov %eax,0x3190
0x1054: add $0x10,%esp
0x1057: test %eax,%eax
0x1059: jne 0x1064
0x105b: mov %ebx,0x3190
0x1061: jmp 0x106a
0x1063: nop
0x1064: inc %eax
0x1065: mov %eax,0x3190
0x106a: movl $0x3000,0xfffffffc(%ebp)
0x1071: mov 0xfffffffc(%ebp),%eax
0x1074: mov 0xfffffffc(%ebp),%eax
0x1077: test %eax,%eax
0x1079: je 0x108b
0x107b: add $0xfffffff4,%esp
0x107e: push $0x3000
0x1083: call 0x1150
0x1088: add $0x10,%esp
0x108b: sub $0x10,%esp
0x108e: pushl 0x33f8 <-- 可以先在OpenBSD用gcc编译一个带符号表的程序,找
0x1094: push %edi 出这种汇编指令特征
0x1095: pushl (%esi)
0x1097: call 0x1840 <-- 这就是main
0x109c: push %eax
0x109d: call 0x307c
0x10a2: pop %ecx
0x10a3: pop %eax
0x10a4: push %ecx
---Type
(gdb) x/20i 0x1840 <-- 这里不能用disas,因为没有符号表
0x1840: push %ebp
0x1841: mov %esp,%ebp
0x1843: sub $0x13c,%esp
0x1849: push %edi
0x184a: push %esi
0x184b: push %ebx
0x184c: mov 0x8(%ebp),%ebx
0x184f: mov 0xc(%ebp),%edi
0x1852: call 0x205c
0x1857: movl $0x0,0xfffffee0(%ebp)
0x1861: movl $0x0,0xfffffedc(%ebp)
0x186b: movl $0x0,0xfffffed8(%ebp)
0x1875: movl $0x0,0xfffffed4(%ebp)
0x187f: movl $0xc,0xfffffed0(%ebp)
0x1889: call 0x314c
0x188e: test %eax,%eax
0x1890: je 0x18a4
0x1892: add $0xfffffff8,%esp
0x1895: push $0x1798
0x189a: push $0x1
(gdb) (gdb) b *0x1840
Breakpoint 1 at 0x1840
(gdb) r
Starting program: /usr/bin/skeyaudit
Breakpoint 1, 0x1840 in ?? ()
(gdb) x/20i $pc
(gdb) q
The program is running. Quit anyway (and kill it)? (y or n) y
#
3. -lelf、-lkvm、-lkstat相关问题
3.1 如何判断可执行文件是否携带了调试信息
Q:某些时候需要知道编译可执行文件时是否携带了调试信息(比如是否指定了-g编译选项)。
A: Sun Microsystems 2000-05-15
检查可执行文件中是否包含".stab" elf section,".stab" section用于保存相关调试信息。
下面这个脚本演示如何判断可执行文件是否携带调试信息
- #! /bin/sh
- #
- # Script that test whether or not a given file has been built for
- # debug (-g option specified in the compilation)
- if [ $# -le 0 ]
- then
-
echo "Usage: $0
" - exit 1
- fi
- if [ ! -f $1 ]
- then
- echo "File $1 does not exist"
- exit 1
- fi
- /usr/ccs/bin/dump -hv $1 | /bin/egrep -s '.stab$'
- if [ $? -eq 0 ]
- then
- echo "File '$1' has been built for debug"
- exit 0
- else
- echo "File '$1' has not been built for debug"
- exit 1
- fi
D: scz@nsfocus
如果对ELF文件格式不熟悉,理解上述代码可能有点困难,参看:
http://www.digibel.org/~tompy/hacking/elf.txt
http://www.muppetlabs.com/~breadbox/software/ELF.txt
这是1.1版的ELF文件格式规范。
一般随着".stab"节的出现,还会出现".stabstr"节。似乎本办法只适用于Solaris,不适用于Linux?在Linux上测试时,要么是无论如何都不出现".stab"节,要么是无论如何都出现".stab"节,见鬼。我是用"objdump -h"测试的。
3.2 mprotect如何用
A: 小四
下面是SPARC/Solaris 2.6中的mprotect(2)手册页
--------------------------------------------------------------------------
系统调用 mprotect(2)
名字 mprotect - 设置内存权限
摘要
#include
int mprotect ( void *addr, size_t len, int prot );
描述
mprotect()设置范围[addr, addr + len),一个左闭右开区间。len将向上舍入到页大小的整数倍。可以利用sysconf(3C)获取页大小。
形参prot指定内存权限。prot的合法取值与mmap()使用的一致,定义在
PROT_READ /* page can be read */
PROT_WRITE /* page can be written */
PROT_EXEC /* page can be executed */
PROT_NONE /* page can not be accessed */
如果mprotect()失败原因不是EINVAL,则可能出现[addr, addr + len)中的部分页面内存权限修改成功,而另一部分修改失败。比如从addr2开始设置权限失败,则[addr, addr2]之间的页面设置权限成功
返回值
成功返回0。否则返回-1,检查errno。
错误码
EACCES The prot argument specifies a protection that violates the access permission the process has to the underlying memory object.
EINVAL 形参len小于等于0,或者形参addr不在页边界上
ENOMEM [addr, addr + len)不是进程的合法地址空间,或者指定了一个或多个尚未映射过的页面
EAGAIN [addr, addr + len)包含了一个或多个页面,这些页面在内存中被锁定并且是以MAP_PRIVATE方式映射的,形参prot带有PROT_WRITE设置,系统没有足够资源用于可能创建的私有页面。如果对以前不可写而现在可写的(刚设置成可写的)地址范围进行写操作,就会创建私有页面(写时复制,copy-on-write)。
参看
mmap(2)、plock(3C)、mlock(3C)、mlockall(3C)、sysconf(3C)
--------------------------------------------------------------------------
# truss prtconf 2>&1 | grep sysconf
sysconfig(_CONFIG_PAGESIZE) = 8192
sysconfig(_CONFIG_PHYS_PAGES) = 16384
#
由此可知当前系统页尺寸是8192字节。
-
/*
-
* gcc -Wall -pipe -g -static -o mtest mtest.c
-
*/
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <errno.h>
-
#include <sys/mman.h>
-
-
int main ( int argc, char * argv[] )
-
{
-
char *buf;
-
char c;
-
-
/*
-
* 分配一块内存,拥有缺省的rw-保护
-
*/
-
buf = ( char * )malloc( 1024 + 8191 );
-
if ( !buf )
-
{
-
perror( "malloc" );
-
exit( errno );
-
}
-
/*
-
* Align to a multiple of PAGESIZE, assumed to be a power of two
-
*/
-
buf = ( char * )( ( ( unsigned int )buf + 8191 ) & ~8191 );
-
c = buf[77];
-
buf[77] = c;
-
printf( "ok\n" );
-
/*
-
* Mark the buffer read-only.
-
*
-
* 必须保证这里buf位于页边界上,否则mprotect()失败,报告无效参数
-
*/
-
if ( mprotect( buf, 1024, PROT_READ ) )
-
{
-
perror( "\nmprotect" );
-
exit( errno );
-
}
-
c = buf[77];
-
/*
-
* Write error, program dies on SIGSEGV
-
*/
-
buf[77] = c;
-
-
exit( 0 );
- } /* end of main */
ok
段错误 (core dumped) <-- 内存保护起作用了
$
3.3 mmap如何用
A: 小四
下面是SPARC/Solaris 2.6中的mmap(2)手册页
--------------------------------------------------------------------------
系统调用 mmap(2)
名字 mmap - map pages of memory
摘要
#include
void * mmap ( void *addr, size_t len, int prot, int flags,
int fildes, off_t off );
描述
mmap()在进程地址空间和一个虚拟内存对象之间建立一种映射关系
pa = mmap( addr, len, prot, flags, fildes, off );
fildes是一个文件句柄,off是文件内偏移,len指明映射多少字节。返回值pa是实现相关的一个值。地址范围[pa, pa + len)和文件偏移范围[off, off + len)之间存在映射关系。
概念上,[pa, pa + len)可以越过文件尾,比如文件是新创建的或者被截断过。但是不能引用越过文件尾的地址,这会导致分发SIGBUS、SIGSEGV信号。换句话说,mmap()不能用于扩展文件长度。
在地址范围[pa, pa + len)上,后续的mmap()将替代前面的mmap()。
映射建立后,close(2)文件句柄并不会导致映射关系自动拆除。应该使用munmap(2)拆除映射关系。
prot形参指明映射页面的内存权限,参看
PROT_READ Page can be read.
PROT_WRITE Page can be written.
PROT_EXEC Page can be executed.
PROT_NONE Page can not be accessed.
PROT_WRITE 经常实现成rw-,PROT_EXEC则对应r-x。如果PROT_WRITE未被设置,肯定不可写。
flags形参也定义在
MAP_SHARED Share changes.
MAP_PRIVATE Changes are private.
MAP_FIXED Interpret addr exactly.
MAP_NORESERVE Don't reserve swap space.
如果指定 MAP_SHARED,内存写操作立即为映射过同一文件的其他进程所察觉。如果指定 MAP_PRIVATE,第一次内存写操作导致写时复制(copy-on-write),创建一个私有页面,映射过同一文件的其他进程无法察觉这次及其后续的内存写操作。注意,创建私有页面之前,以MAP_SHARED方式映射过同一文件的其他进程的写操作将立即为当前进程所察觉。必须指定 MAP_SHARED 或者 MAP_PRIVATE,但不能同时指定。fork()后这种设置依旧有效。
译注:指定MAP_SHARED才可能使写操作最终影响对象/文件,指定MAP_PRIVATE始终无法使写操作最终影响对象/文件,所做修改局限于内存区域,即使调用msync()也无意义。
指定MAP_SHARED,意味着内存读/写对应对象/文件I/O。
指定MAP_PRIVATE,意味着仅仅是内存读/写。
如果指定 MAP_FIXED,返回值pa等于形参addr,不鼓励指定MAP_FIXED,这可能导致无法最高效地利用系统资源。
如果指定 MAP_FIXED 的同时,形参addr与前次映射返回的pa相等,则首先拆除前次映射关系,然后建立新的映射关系。
如果未指定 MAP_FIXED ,分两种情况。如果形参addr不为零,系统参照形参addr、len自行挑选addr附近的最终映射地址pa。如果形参addr为零,则系统任意挑选pa,但绝不会在地址0处开始映射,不会替代任何现存映射,也不会映射到被认为是数据段(data)、堆栈段(stack)一部分的内存区域上去。
如果不指定 MAP_NORESERVE,以 MAP_PRIVATE 方式进行可写映射时,系统保留空间用于可能创建的私有页面,发生写操作时保留空间被用于创建私有页面。指定 MAP_NORESERVE 以 MAP_PRIVATE 方式进行可写映射,如果发生写操作,系统检查当前可用空间,能创建私有页面时写操作按计划进行,可用空间不足以创建私有页面时,分发SIGBUS或者 SIGSEGV 信号到做写操作的进程。MAP_NORESERVE 设置在fork()后为子进程所继承,但在fork()的瞬间,对于父进程中已经存在的私有页面(copy-on-write发生过了),在子进程中将为之保留空间,之后子进程的映射行为和前面描述的相一致。
形参off应该对齐在页边界上,给sysconf(3C)传入形参_SC_PAGESIZE或者_SC_PAGE_SIZE即可返回页大小。如果指定了 MAP_FIXED ,形参addr也必须对齐在页边界上。系统自动完成整页映射,形参len将向上舍入到页大小的整数倍(类似mprotect)。
系统总是用零填充最后一页越过文件尾的内存区域,即使修改了这个区域,也永远不会将该区域的任何内容写入文件。引用这个区域的地址导致分发 SIGBUS 或 SIGSEGV 信号。违背磁盘限额设置的时候,也会分发 SIGBUS 信号。
被映射文件的 st_atime 将被更新成mmap(2)和相应munmap(2)之间的某个时间。对映射内存区域第一次读/写时,如果这之前文件的st_atime 未被标记/更新,则这次读/写操作导致文件的 st_atime被标记/更新。
文件以 MAP_SHARED 方式可写映射,它的 st_ctime 和 st_mtime 将被更新成内存写操作和任一相关进程对被写内存区域msync(3C)调用(指定 MS_ASYNC 或者 MS_SYNC )之间的某个时间。如果未发生这样的msync(3C)调用,st_ctime 和 st_mtime 将被更新成有效内存写操作(指内存写操作最终影响了文件)之后的某个时间。
如果进程指定 MCL_FUTURE 标志调用过mlockall(3C),这之后mmap()映射上来的页面均被锁定在内存中(不被交换到磁盘上去)。此时如果没有足够内存满足锁定要求,mmap()调用失败,errno被设置成EAGAIN。
返回值
成功则返回最终映射地址pa,失败时返回MAP_FAILED并设置errno
错误码
EACCES 1) 文件句柄fildes非可读打开,无论prot如何指定
2) 文件句柄fildes非可写打开,却准备以MAP_SHARED 方式进行可写映射(PROT_WRITE)
EAGAIN 映射上来的页面无法锁定在内存中没有足够内存满足映射中所要求的保留空间
The file to be mapped is already locked using advisory or mandatory record locking. See fcntl(2).
EBADF 文件句柄fildes尚未打开
EINVAL 形参addr(指定 MAP_FIXED)或者off未对齐在页边界上(页大小从sysconf(3C)返回)
形参flags无效,既不是 MAP_PRIVATE 也不是MAP_SHARED
形参len小于等于0
EMFILE 映射内存区域的数量超过某个实现相关的限制(基于进程的或者基于整个系统的)
ENODEV 文件句柄fildes描述的对象对于mmap()而言无意义,比如终端。
ENOMEM 指定了MAP_FIXED,而[addr, addr + len)超出了进程地址空间
未指定MAP_FIXED,但是没有足够空间完成映射
The composite size of len plus the lengths of all previous mmappings exceeds RLIMIT_VMEM (see getrlimit(2)).
ENXIO 映射这个范围[off, off + len),对于对象(文件/设备)来说非法
EOVERFLOW The file is a regular file and the value of off plus len exceeds the offset maximum establish in the open file description associated with fildes.
用法
mmap()允许以内存操作方式访问对象,而不是read/write接口。
fildes = open(...)
lseek( fildes, offset, whence )
read( fildes, buf, len )
/* use data in buf */
下面是mmap()方式
fildes = open(...)
address = mmap( ( caddr_t )0, len, ( PROT_READ | PROT_WRITE ),
MAP_PRIVATE, fildes, offset )
/* use data at address */
The mmap() function has an explicit 64-bit equivalent. See interface64(5).
参看
close(2)、exec(2)、fcntl(2)、fork(2)、getrlimit(2)、mprotect(2)
munmap(2)、shmat(2)、lockf(3C)、mlockall(3C)、msync(3C)、
plock(3C)、sysconf(3C)、interface64(5)
--------------------------------------------------------------------------
下面写一个完成文件复制功能的小程序,利用mmap(2),而不是标准文件I/O接口。
-
/*
-
* gcc -Wall -pipe -O3 -o copy_mmap copy_mmap.c
-
*/
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <string.h> /* for memcpy */
-
#include <strings.h>
-
#include <sys/mman.h>
-
#include <sys/types.h>
-
#include <sys/stat.h>
-
#include <fcntl.h>
-
#include <unistd.h>
-
-
#define PERMS 0600
-
-
int main ( int argc, char * argv[] )
-
{
-
int src, dst;
-
void *sm, *dm;
-
struct stat statbuf;
-
-
if ( argc != 3 )
-
{
-
fprintf( stderr, " Usage: %s , argv[0] );
-
exit( EXIT_FAILURE );
-
}
-
if ( ( src = open( argv[1], O_RDONLY ) ) < 0 )
-
{
-
perror( "open source" );
-
exit( EXIT_FAILURE );
-
}
-
/*
-
* 为了完成复制,必须包含读打开,否则mmap()失败
-
*/
-
if ( ( dst = open( argv[2], O_RDWR | O_CREAT | O_TRUNC, PERMS ) ) < 0 )
-
{
-
perror( "open target" );
-
exit( EXIT_FAILURE );
-
}
-
if ( fstat( src, &statbuf ) < 0 )
-
{
-
perror( "fstat source" );
-
exit( EXIT_FAILURE );
-
}
-
/*
-
* 参看前面man手册中的说明,mmap()不能用于扩展文件长度。所以这里必须事
-
* 先扩大目标文件长度,准备一个空架子等待复制。
-
*/
-
if ( lseek( dst, statbuf.st_size - 1, SEEK_SET ) < 0 )
-
{
-
perror( "lseek target" );
-
exit( EXIT_FAILURE );
-
}
-
if ( write( dst, &statbuf, 1 ) != 1 )
-
{
-
perror( "write target" );
-
exit( EXIT_FAILURE );
-
}
-
/*
-
* 读的时候指定 MAP_PRIVATE 即可
-
*/
-
sm = mmap( 0, ( size_t )statbuf.st_size, PROT_READ,
-
MAP_PRIVATE | MAP_NORESERVE, src, 0 );
-
if ( MAP_FAILED == sm )
-
{
-
perror( "mmap source" );
-
exit( EXIT_FAILURE );
-
}
-
/*
-
* 这里必须指定 MAP_SHARED 才可能真正改变静态文件
-
*/
-
dm = mmap( 0, ( size_t )statbuf.st_size, PROT_WRITE,
-
MAP_SHARED, dst, 0 );
-
if ( MAP_FAILED == dm )
-
{
-
perror( "mmap target" );
-
exit( EXIT_FAILURE );
-
}
-
memcpy( dm, sm, ( size_t )statbuf.st_size );
-
/*
-
* 可以不要这行代码
-
*
-
* msync( dm, ( size_t )statbuf.st_size, MS_SYNC );
-
*/
-
return( EXIT_SUCCESS );
- } /* end of main */
-
/*
-
* gcc -Wall -pipe -g -static -o copy_io copy_io.c
-
*/
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <strings.h>
-
#include <sys/types.h>
-
#include <sys/stat.h>
-
#include <fcntl.h>
-
#include <unistd.h>
-
-
#define BUFSIZE 8192 /* SPARC/Solaris 2.6上是一页的大小 */
-
#define PERMS 0600
-
-
int main ( int argc, char * argv[] )
-
{
-
char iobuffer[ BUFSIZE ];
-
int src, dst, num;
-
-
if ( argc != 3 )
-
{
-
fprintf( stderr, " Usage: %s , argv[0] );
-
exit( EXIT_FAILURE );
-
}
-
if ( ( src = open( argv[1], O_RDONLY ) ) < 0 )
-
{
-
perror( "open source" );
-
exit( EXIT_FAILURE );
-
}
-
if ( ( dst = open( argv[2], O_WRONLY | O_CREAT | O_TRUNC, PERMS ) ) < 0 )
-
{
-
perror( "open target" );
-
exit( EXIT_FAILURE );
-
}
-
while ( ( num = read( src, iobuffer, BUFSIZE ) ) > 0 )
-
{
-
if ( write( dst, iobuffer, num ) != num )
-
{
-
perror( "write" );
-
exit( EXIT_FAILURE );
-
}
-
}
-
close( src );
-
close( dst );
-
exit( EXIT_SUCCESS );
- } /* end of main */
mmap()好处是处理大文件时速度明显快于标准文件I/O,无论读写,都少了一次用户空间与内核空间之间的复制过程。操作内存还便于设计、优化算法。
A: 小四
下面是x86/Linux RedHat 7.0中的mmap(2)手册页
--------------------------------------------------------------------------
MMAP(2) Linux程序员手册 MMAP(2)
名字
mmap、munmap - 将文件或设备映射到内存中
摘要
#include
#include
#ifdef _POSIX_MAPPED_FILES
void * mmap ( void *start, size_t length, int prot, int flags, int fd, off_t offset );
int munmap ( void *start, size_t length );
#endif
描述
length指定映射长度,以字节为单位。offset指明文件或其他设备对象偏移,fd对应文件句柄。形参start给内核一个建议的起始映射地址,并非强制指定。如果start为0,表示内核自由选取最终映射地址。mmap()返回值指明了最终映射地址。形参prot描述了期望的内存权限,必须和打开文件方式相匹配。
PROT_EXEC Pages may be executed.
PROT_READ Pages may be read.
PROT_WRITE Pages may be written.
PROT_NONE Pages may not be accessed.
形参flags可取值如下
MAP_FIXED
start此时为强制指定的映射地址,如果无法在指定地址上完成映射,mmap()失败返回。如果指定了 MAP_FIXED,start必须对齐在页边界上。
建议不要使用该设置。
MAP_SHARED
此种映射方式下,对内存区域的写操作等价于对文件的写操作,映射过同一文件的其他进程会立即察觉到这次写操作。磁盘文件可能并非立即更新,直到msync(2)或munmap(2)被调用。
MAP_PRIVATE
写时复制(copy-on-write)映射方式。此种映射方式下对内存的写操作不会影响磁盘文件。
必须指定 MAP_SHARED 和 MAP_PRIVATE 中的一个,但不能同时指定。
上述三个flags在POSIX.1b(以前的 POSIX.4)中有描述。Linux自己还支持MAP_DENYWRITE、MAP_EXECUTABLE、MAP_NORESERVE、MAP_LOCKED、
MAP_GROWSDOWN 以及 MAP_ANON(YMOUS)
形参offset通常应该位于页边界上,可由getpagesize(2)获取页尺寸。但是,这个形参可以不位于页边界上。
译注:这里和SPARC/Solaris 2.6不同,Solaris要求offset必须对齐在页边界上,否则返回EINVAL错误码。Linux下有所变化,注意这些差别。
munmap()拆除映射关系。无法引用被拆除映射关系的地址范围。进程终止时映射自动拆除。关闭文件句柄fd并不会导致映射关系拆除。
返回值
mmap()成功返回一个指针,失败返回MAP_FAILED(-1),并设置errno。
munmap()成功返回0,失败返回-1,并设置errno(可能是EINVAL)
错误码
EBADF 形参fd是个无效文件句柄(同时未指定MAP_ANONYMOUS)
EACCES 指定了MAP_PRIVATE,但是fd打开模式不包括读指定了MAP_SHARED可写映射(PROT_WRITE),但是fd打开模式不是O_RDWR
EINVAL 形参start、length或者offset有问题,比如太大范围、未对齐在页边界上等等
ETXTBUSY 指定了MAP_DENYWRITE,但是fd打开模式包括写
EAGAIN 文件被锁定,或者太多内存被锁定
ENOMEM 无足够内存可用
使用映射可能涉及到如下信号
SIGSEGV
试图对只读映射区域进行写操作
SIGBUS 试图访问一块无文件内容对应的内存区域,比如超过文件尾的内存区域,或者以前有文件内容对应,现在为另一进程截断过的内存区域。
遵从
SVr4、POSIX.1b(以前的POSIX.4)、4.4BSD
Svr4标准中包括额外的错误码 ENXIO 和 ENODEV
参看
getpagesize(2)、msync(2)、shm_open(2)
下面的测试程序在x86/Linux RedHat 7.0上调试通过
-
/*
-
* gcc -Wall -pipe -O3 -o procmem_mmap_2 procmem_mmap_2.c
-
*/
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <string.h> /* for memcpy */
-
#include <strings.h>
-
#include <sys/mman.h>
-
#include <sys/types.h>
-
#include <sys/stat.h>
-
#include <fcntl.h>
-
#include <unistd.h>
-
#include <errno.h>
-
#include <limits.h> /* for PAGESIZE */
-
-
#ifndef PAGESIZE
-
#define PAGESIZE 4096
-
#endif
-
-
static void outputBinary ( const unsigned char *byteArray, const size_t byteArrayLen )
-
{
-
size_t offset, k, j, i;
-
-
fprintf( stderr, "byteArray [ %u bytes ] ----> \n", byteArrayLen );
-
if ( byteArrayLen <= 0 )
-
{
-
return;
-
}
-
i = 0;
-
offset = 0;
-
for ( k = byteArrayLen / 16; k > 0; k--, offset += 16 )
-
{
-
fprintf( stderr, "%08X ", offset );
-
for ( j = 0; j < 16; j++, i++ )
-
{
-
if ( j == 8 )
-
{
-
fprintf( stderr, "-%02X", byteArray[i] );
-
}
-
else
-
{
-
fprintf( stderr, " %02X", byteArray[i] );
-
}
-
}
-
fprintf( stderr, " " );
-
i -= 16;
-
for ( j = 0; j < 16; j++, i++ )
-
{
-
/* if ( isprint( (int)byteArray[i] ) ) */
-
if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 )
-
&& ( byteArray[i] != 0x7f ) )
-
{
-
fprintf( stderr, "%c", byteArray[i] );
-
}
-
else
-
{
-
fprintf( stderr, "." );
-
}
-
}
-
fprintf( stderr, "\n" );
-
} /* end of for */
-
k = byteArrayLen - i;
-
if ( k <= 0 )
-
{
-
return;
-
}
-
fprintf( stderr, "%08X ", offset );
-
for ( j = 0 ; j < k; j++, i++ )
-
{
-
if ( j == 8 )
-
{
-
fprintf( stderr, "-%02X", byteArray[i] );
-
}
-
else
-
{
-
fprintf( stderr, " %02X", byteArray[i] );
-
}
-
}
-
i -= k;
-
for ( j = 16 - k; j > 0; j-- )
-
{
-
fprintf( stderr, " " );
-
}
-
fprintf( stderr, " " );
-
for ( j = 0; j < k; j++, i++ )
-
{
-
if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 )
-
&& ( byteArray[i] != 0x7f ) )
-
{
-
fprintf( stderr, "%c", byteArray[i] );
-
}
-
else
-
{
-
fprintf( stderr, "." );
-
}
-
}
-
fprintf( stderr, "\n" );
-
return;
-
} /* end of outputBinary */
-
-
int main ( int argc, char * argv[] )
-
{
-
int fd;
-
void *mem;
-
char buf[ PAGESIZE ];
-
off_t offset;
-
-
if ( ( fd = open( "/proc/self/mem", O_RDONLY ) ) < 0 )
-
{
-
perror( "open /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
fprintf( stderr, "main = %#x\n", ( unsigned int )&main );
-
outputBinary ( ( const unsigned char * )&main, 32 );
-
offset = ( off_t )&main + 1;
-
if ( lseek( fd, offset, SEEK_SET ) < 0 )
-
{
-
perror( "lseek /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
if ( read( fd, buf, PAGESIZE ) != PAGESIZE )
-
{
-
perror( "read /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
fprintf( stderr, "offset = %#x\n", ( unsigned int )offset );
-
outputBinary ( ( const unsigned char * )buf, 32 );
-
/*
-
* 读的时候指定 MAP_PRIVATE 即可
-
*/
-
mem = mmap( 0, PAGESIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, fd, offset );
-
if ( MAP_FAILED == mem )
-
{
-
perror( "mmap /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
fprintf( stderr, "mem = %#x\n", ( unsigned int )mem );
-
outputBinary ( ( const unsigned char * )mem, 32 );
-
offset = ( off_t )( ( ( unsigned int )&main & ~( PAGESIZE - 1 ) ) + PAGESIZE );
-
if ( lseek( fd, offset, SEEK_SET ) < 0 )
-
{
-
perror( "lseek /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
if ( read( fd, buf, PAGESIZE ) != PAGESIZE )
-
{
-
perror( "read /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
fprintf( stderr, "offset = %#x\n", ( unsigned int )offset );
-
outputBinary ( ( const unsigned char * )buf, 32 );
-
if ( munmap( mem, PAGESIZE ) < 0 )
-
{
-
perror( "munmap /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
close( fd );
-
return( EXIT_SUCCESS );
- } /* end of main */
main = 0x804886c
byteArray [ 32 bytes ] ---->
00000000 55 89 E5 57 56 53 81 EC-24 10 00 00 6A 00 68 81 U夊WVS侅$...j.h?
00000010 8B 04 08 E8 A4 FC FF FF-89 C3 83 C4 10 85 DB 79 ?.瑜?壝兡.呟y
offset = 0x804886d
byteArray [ 32 bytes ] ---->
00000000 89 E5 57 56 53 81 EC 24-10 00 00 6A 00 68 81 8B 夊WVS侅$...j.h亱
00000010 04 08 E8 A4 FC FF FF 89-C3 83 C4 10 85 DB 79 1B ..瑜?壝兡.呟y.
mem = 0x40017000
byteArray [ 32 bytes ] ---->
00000000 7F 45 4C 46 01 01 01 00-00 00 00 00 00 00 00 00 .ELF............
00000010 02 00 03 00 01 00 00 00-60 85 04 08 34 00 00 00 ........`?.4...
offset = 0x8049000
byteArray [ 32 bytes ] ---->
00000000 7F 45 4C 46 01 01 01 00-00 00 00 00 00 00 00 00 .ELF............
00000010 02 00 03 00 01 00 00 00-60 85 04 08 34 00 00 00 ........`?.4...
$
这个输出说明了几个问题。文件I/O操作/proc/self/mem不存在页边界对齐的问题。至少Linux的mmap()的最后一个形参offset并未强制要求页边界对齐,如果提供的值未对齐,系统自动向上舍入到页边界上。
D: 小四
下面是一个利用mmap()完成内存拷贝的演示程序,在RedHat 7.0下调试通过
-
/*
-
* gcc -Wall -pipe -O3 -o procmem_mmap_3 procmem_mmap_3.c
-
*/
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <string.h> /* for memcpy */
-
#include <strings.h>
-
#include <sys/mman.h>
-
#include <sys/types.h>
-
#include <sys/stat.h>
-
#include <fcntl.h>
-
#include <unistd.h>
-
#include <errno.h>
-
#include <limits.h> /* for PAGESIZE */
-
-
#ifndef PAGESIZE
-
#define PAGESIZE 4096
-
#endif
-
-
static void outputBinary ( const unsigned char *byteArray, const size_t byteArrayLen )
-
{
-
size_t offset, k, j, i;
-
-
fprintf( stderr, "byteArray [ %u bytes ] ----> \n", byteArrayLen );
-
if ( byteArrayLen <= 0 )
-
{
-
return;
-
}
-
i = 0;
-
offset = 0;
-
for ( k = byteArrayLen / 16; k > 0; k--, offset += 16 )
-
{
-
fprintf( stderr, "%08X ", offset );
-
for ( j = 0; j < 16; j++, i++ )
-
{
-
if ( j == 8 )
-
{
-
fprintf( stderr, "-%02X", byteArray );
-
}
-
else
-
{
-
fprintf( stderr, " %02X", byteArray );
-
}
-
}
-
fprintf( stderr, " " );
-
i -= 16;
-
for ( j = 0; j < 16; j++, i++ )
-
{
-
/* if ( isprint( (int)byteArray ) ) */
-
if ( ( byteArray >= ' ' ) && ( byteArray <= 255 )
-
&& ( byteArray != 0x7f ) )
-
{
-
fprintf( stderr, "%c", byteArray );
-
}
-
else
-
{
-
fprintf( stderr, "." );
-
}
-
}
-
fprintf( stderr, "\n" );
-
} /* end of for */
-
k = byteArrayLen - i;
-
if ( k <= 0 )
-
{
-
return;
-
}
-
fprintf( stderr, "%08X ", offset );
-
for ( j = 0 ; j < k; j++, i++ )
-
{
-
if ( j == 8 )
-
{
-
fprintf( stderr, "-%02X", byteArray );
-
}
-
else
-
{
-
fprintf( stderr, " %02X", byteArray );
-
}
-
}
-
i -= k;
-
for ( j = 16 - k; j > 0; j-- )
-
{
-
fprintf( stderr, " " );
-
}
-
fprintf( stderr, " " );
-
for ( j = 0; j < k; j++, i++ )
-
{
-
if ( ( byteArray >= ' ' ) && ( byteArray <= 255 )
-
&& ( byteArray != 0x7f ) )
-
{
-
fprintf( stderr, "%c", byteArray );
-
}
-
else
-
{
-
fprintf( stderr, "." );
-
}
-
}
-
fprintf( stderr, "\n" );
-
return;
-
} /* end of outputBinary */
-
-
int main ( int argc, char * argv[] )
-
{
-
int fd;
-
void *oldsrc, *olddst, *src, *dst, *mem;
-
-
if ( NULL == ( oldsrc = malloc( PAGESIZE + PAGESIZE ) ) )
-
{
-
perror( "malloc oldsrc" );
-
exit( EXIT_FAILURE );
-
}
-
src = ( void * )( ( ( unsigned int )oldsrc & ~( PAGESIZE - 1 ) ) + PAGESIZE );
-
if ( NULL == ( olddst = malloc( PAGESIZE + PAGESIZE ) ) )
-
{
-
perror( "malloc olddst" );
-
exit( EXIT_FAILURE );
-
}
-
dst = ( void * )( ( ( unsigned int )olddst & ~( PAGESIZE - 1 ) ) + PAGESIZE );
-
fprintf( stderr, "oldsrc = %#x\n", ( unsigned int )oldsrc );
-
fprintf( stderr, "src = %#x\n", ( unsigned int )src );
-
strcpy( ( char * )src, "NsFocus Security Team." );
-
outputBinary ( ( const unsigned char * )src, 32 );
-
fprintf( stderr, "olddst = %#x\n", ( unsigned int )olddst );
-
fprintf( stderr, "dst = %#x\n", ( unsigned int )dst );
-
outputBinary ( ( const unsigned char * )dst, 32 );
-
/* 后面没有写操作,也不做可写映射,所以这里可以只读打开 */
-
if ( ( fd = open( "/proc/self/mem", O_RDONLY ) ) < 0 )
-
{
-
perror( "open /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
/*
-
* 为了完成到指定地址的内存拷贝,需要指定 MAP_FIXED
-
* 我们必须自己保证相关形参不违反页边界对齐要求
-
*/
-
mem = mmap( dst, PAGESIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE | MAP_FIXED,
-
fd, ( off_t )src );
-
if ( MAP_FAILED == mem )
-
{
-
perror( "mmap /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
fprintf( stderr, "mem = %#x\n", ( unsigned int )mem );
-
outputBinary ( ( const unsigned char * )dst, 32 );
-
if ( munmap( mem, PAGESIZE ) < 0 )
-
{
-
perror( "munmap /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
/*
-
* 如果这里多这么一行代码
-
* outputBinary ( ( const unsigned char * )dst, 32 );
-
* Segmentation fault (core dumped)
-
*
-
* gdb跟踪显示这里引发了SIGSEGV信号
-
*
-
* 那么这种情况下发生了什么?一块内存不见了,后续的free( olddst )为什
-
* 么可以正常执行下去而不导致Segmentation fault,会出现内存泄露吗?
-
* munmap()是否可以操作非mmap()返回的指针?它和free()什么关系?
-
* 利用mmap()完成内存拷贝,意义何在,速度吗?
-
*/
-
close( fd );
-
free( olddst );
-
free( oldsrc );
-
return( EXIT_SUCCESS );
- } /* end of main */
oldsrc = 0x8049d50
src = 0x804a000
byteArray [ 32 bytes ] ---->
00000000 4E 73 46 6F 63 75 73 20-53 65 63 75 72 69 74 79 NsFocus Security
00000010 20 54 65 61 6D 2E 00 00-00 00 00 00 00 00 00 00 Team...........
olddst = 0x804bd58
dst = 0x804c000
byteArray [ 32 bytes ] ---->
00000000 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000010 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
mem = 0x804c000
byteArray [ 32 bytes ] ---->
00000000 4E 73 46 6F 63 75 73 20-53 65 63 75 72 69 74 79 NsFocus Security
00000010 20 54 65 61 6D 2E 00 00-00 00 00 00 00 00 00 00 Team...........
$
这个演示程序说明了另外几个问题。malloc()分配得到的地址不见得对齐在页边界上,其他问题请仔细参看上面的注释部分。个人觉得mmap()还是很容易掌握的,只要按照它的本质规律做。
要照注释里的情况,这种指定了MAP_FIXED的映射正常如何使用呢。如果上面的dst不是malloc()获取的地址空间,而是自动变量(stack区中),munmap()拆除映射后访问dst是否还会导致Segmentation fault (core dumped)?free()的内核实现很奇怪,这里居然能正常释放olddst指针。
A: scz
Linux下的/proc/self/mem对应进程自身的内存映像。参看proc(5)手册页。
-
/*
-
* gcc -Wall -pipe -O3 -o procmem_mmap procmem_mmap.c
-
*/
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <string.h> /* for memcpy */
-
#include <strings.h>
-
#include <sys/mman.h>
-
#include <sys/types.h>
-
#include <sys/stat.h>
-
#include <fcntl.h>
-
#include <unistd.h>
-
#include <errno.h>
-
#include <limits.h> /* for PAGESIZE */
-
-
#ifndef PAGESIZE
-
#define PAGESIZE 4096
-
#endif
-
-
static void outputBinary ( const unsigned char * byteArray, const size_t byteArrayLen )
-
{
-
size_t offset, k, j, i;
-
-
fprintf( stderr, "byteArray [ %u bytes ] ----> \n", byteArrayLen );
-
if ( byteArrayLen <= 0 )
-
{
-
return;
-
}
-
i = 0;
-
offset = 0;
-
for ( k = byteArrayLen / 16; k > 0; k--, offset += 16 )
-
{
-
fprintf( stderr, "%08X ", offset );
-
for ( j = 0; j < 16; j++, i++ )
-
{
-
if ( j == 8 )
-
{
-
fprintf( stderr, "-%02X", byteArray );
-
}
-
else
-
{
-
fprintf( stderr, " %02X", byteArray );
-
}
-
}
-
fprintf( stderr, " " );
-
i -= 16;
-
for ( j = 0; j < 16; j++, i++ )
-
{
-
/* if ( isprint( (int)byteArray ) ) */
-
if ( ( byteArray >= ' ' ) && ( byteArray <= 255 )
-
&& ( byteArray != 0x7f ) )
-
{
-
fprintf( stderr, "%c", byteArray );
-
}
-
else
-
{
-
fprintf( stderr, "." );
-
}
-
}
-
fprintf( stderr, "\n" );
-
} /* end of for */
-
k = byteArrayLen - i;
-
if ( k <= 0 )
-
{
-
return;
-
}
-
fprintf( stderr, "%08X ", offset );
-
for ( j = 0 ; j < k; j++, i++ )
-
{
-
if ( j == 8 )
-
{
-
fprintf( stderr, "-%02X", byteArray );
-
}
-
else
-
{
-
fprintf( stderr, " %02X", byteArray );
-
}
-
}
-
i -= k;
-
for ( j = 16 - k; j > 0; j-- )
-
{
-
fprintf( stderr, " " );
-
}
-
fprintf( stderr, " " );
-
for ( j = 0; j < k; j++, i++ )
-
{
-
if ( ( byteArray >= ' ' ) && ( byteArray <= 255 )
-
&& ( byteArray != 0x7f ) )
-
{
-
fprintf( stderr, "%c", byteArray );
-
}
-
else
-
{
-
fprintf( stderr, "." );
-
}
-
}
-
fprintf( stderr, "\n" );
-
return;
-
} /* end of outputBinary */
-
-
int main ( int argc, char * argv[] )
-
{
-
int fd;
-
void *mem;
-
char buf[ PAGESIZE ];
-
off_t offset;
-
unsigned int main_offset;
-
-
if ( ( fd = open( "/proc/self/mem", O_RDONLY ) ) < 0 )
-
{
-
perror( "open /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
offset = ( off_t )( ( unsigned int )&main & ~( PAGESIZE - 1 ) );
-
main_offset = ( unsigned int )&main & ( PAGESIZE - 1 );
-
fprintf( stderr, "main = %#x\n", ( unsigned int )&main );
-
outputBinary ( ( const unsigned char * )&main, 32 );
-
fprintf( stderr, "offset = %#x\n", ( unsigned int )offset );
-
fprintf( stderr, "main_offset = %#x\n", main_offset );
-
if ( lseek( fd, offset, SEEK_SET ) < 0 )
-
{
-
perror( "lseek /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
if ( read( fd, buf, PAGESIZE ) != PAGESIZE )
-
{
-
perror( "read /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
outputBinary ( ( const unsigned char * )buf + main_offset, 32 );
-
/* 读的时候指定 MAP_PRIVATE 即可 */
-
mem = mmap( 0, PAGESIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, fd, offset );
-
if ( MAP_FAILED == mem )
-
{
-
perror( "mmap /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
fprintf( stderr, "mem = %#x\n", ( unsigned int )mem );
-
outputBinary ( ( const unsigned char * )mem + main_offset, 32 );
-
if ( munmap( mem, PAGESIZE ) < 0 )
-
{
-
perror( "munmap /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
close( fd );
-
return( EXIT_SUCCESS );
- } /* end of main */
/proc/self/mem和/dev/kmem不同。root用户打开/dev/kmem就可以在用户空间访问到内核空间的数据,包括偏移0处的数据,系统提供了这样的支持。
普通用户可以执行procmem_mmap,不要求超级用户权限。
objdump -j .text -S procmem_mmap | more
/main <-- 搜索main函数
80487a4: 55 pushl %ebp
80487a5: 89 e5 movl %esp,%ebp
80487a7: 81 ec 08 10 00 00 subl $0x1008,%esp
80487ad: 57 pushl %edi
80487ae: 56 pushl %esi
80487af: 53 pushl %ebx
80487b0: 6a 00 pushl $0x0
80487b2: 68 01 8a 04 08 pushl $0x8048a01
80487b7: e8 f0 fc ff ff call 80484ac <_init+0xd0>
80487bc: 89 85 fc ef ff ff movl %eax,0xffffeffc(%ebp)
80487c2: 83 c4 08 addl $0x8,%esp
$ ./procmem_mmap
main = 0x80487a4
byteArray [ 32 bytes ] ---->
00000000 55 89 E5 81 EC 08 10 00-00 57 56 53 6A 00 68 01 U夊侅....WVSj.h.
00000010 8A 04 08 E8 F0 FC FF FF-89 85 FC EF FF FF 83 C4 ?.桊?墔?兡
offset = 0x8048000
main_offset = 0x7a4
byteArray [ 32 bytes ] ---->
00000000 55 89 E5 81 EC 08 10 00-00 57 56 53 6A 00 68 01 U夊侅....WVSj.h.
00000010 8A 04 08 E8 F0 FC FF FF-89 85 FC EF FF FF 83 C4 ?.桊?墔?兡
mem = 0x40015000
byteArray [ 32 bytes ] ---->
00000000 55 89 E5 81 EC 08 10 00-00 57 56 53 6A 00 68 01 U夊侅....WVSj.h.
00000010 8A 04 08 E8 F0 FC FF FF-89 85 FC EF FF FF 83 C4 ?.桊?墔?兡
$
可以用objdump -p <...>查看程序加载地址,加载地址之后的都可以正常文件I/O读取,加载地址之前的无法正常文件I/O读取。这个说法欠妥,应该是cat /proc/
D: scz
如果这里以 MAP_SHARED 方式可写映射/proc/self/mem,main()所在代码段是否可写了?换句话说,是否mmap()也可以达到mprotect()的效果呢。
此外,只要有足够权限,通过/proc/
下面的测试程序在RedHat Linux 6.2上调试通过
-
/*
-
* gcc -Wall -pipe -O3 -o procmem_mmap_1 procmem_mmap_1.c
-
*/
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <string.h> /* for memcpy */
-
#include <strings.h>
-
#include <sys/mman.h>
-
#include <sys/types.h>
-
#include <sys/stat.h>
-
#include <fcntl.h>
-
#include <unistd.h>
-
#include <errno.h>
-
#include <limits.h> /* for PAGESIZE */
-
-
#ifndef PAGESIZE
-
#define PAGESIZE 4096
-
#endif
-
-
static void outputBinary ( const unsigned char *byteArray, const size_t byteArrayLen )
-
{
-
size_t offset, k, j, i;
-
-
fprintf( stderr, "byteArray [ %u bytes ] ----> \n", byteArrayLen );
-
if ( byteArrayLen <= 0 )
-
{
-
return;
-
}
-
i = 0;
-
offset = 0;
-
for ( k = byteArrayLen / 16; k > 0; k--, offset += 16 )
-
{
-
fprintf( stderr, "%08X ", offset );
-
for ( j = 0; j < 16; j++, i++ )
-
{
-
if ( j == 8 )
-
{
-
fprintf( stderr, "-%02X", byteArray );
-
}
-
else
-
{
-
fprintf( stderr, " %02X", byteArray );
-
}
-
}
-
fprintf( stderr, " " );
-
i -= 16;
-
for ( j = 0; j < 16; j++, i++ )
-
{
-
/* if ( isprint( (int)byteArray ) ) */
-
if ( ( byteArray >= ' ' ) && ( byteArray <= 255 )
-
&& ( byteArray != 0x7f ) )
-
{
-
fprintf( stderr, "%c", byteArray );
-
}
-
else
-
{
-
fprintf( stderr, "." );
-
}
-
}
-
fprintf( stderr, "\n" );
-
} /* end of for */
-
k = byteArrayLen - i;
-
if ( k <= 0 )
-
{
-
return;
-
}
-
fprintf( stderr, "%08X ", offset );
-
for ( j = 0 ; j < k; j++, i++ )
-
{
-
if ( j == 8 )
-
{
-
fprintf( stderr, "-%02X", byteArray );
-
}
-
else
-
{
-
fprintf( stderr, " %02X", byteArray );
-
}
-
}
-
i -= k;
-
for ( j = 16 - k; j > 0; j-- )
-
{
-
fprintf( stderr, " " );
-
}
-
fprintf( stderr, " " );
-
for ( j = 0; j < k; j++, i++ )
-
{
-
if ( ( byteArray >= ' ' ) && ( byteArray <= 255 )
-
&& ( byteArray != 0x7f ) )
-
{
-
fprintf( stderr, "%c", byteArray );
-
}
-
else
-
{
-
fprintf( stderr, "." );
-
}
-
}
-
fprintf( stderr, "\n" );
-
return;
-
} /* end of outputBinary */
-
-
int main ( int argc, char * argv[] )
-
{
-
int fd;
-
void *mem;
-
char buf[ PAGESIZE ];
-
off_t offset;
-
unsigned int main_offset;
-
-
/*
-
* 读写打开,准备可写映射
-
*/
-
if ( ( fd = open( "/proc/self/mem", O_RDWR ) ) < 0 )
-
{
-
perror( "open /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
offset = ( off_t )( ( unsigned int )&main & ~( PAGESIZE - 1 ) );
-
main_offset = ( unsigned int )&main & ( PAGESIZE - 1 );
-
fprintf( stderr, "main = %#x\n", ( unsigned int )&main );
-
/*
-
* 原来的数据
-
*/
-
outputBinary ( ( const unsigned char * )&main, 32 );
-
/*
-
* MAP_SHARED 方式的可写映射,否则/proc/self/mem不会被修改
-
*/
-
mem = mmap( 0, PAGESIZE, PROT_WRITE, MAP_SHARED, fd, offset );
-
if ( MAP_FAILED == mem )
-
{
-
perror( "mmap /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
fprintf( stderr, "mem = %#x\n", ( unsigned int )mem );
-
outputBinary ( ( const unsigned char * )mem + main_offset, 32 );
-
/*
-
* 这里修改了代码段
-
*/
-
strcpy( ( char * )mem + main_offset, "NsFocus Security Team." );
-
fprintf( stderr, "offset = %#x\n", ( unsigned int )offset );
-
fprintf( stderr, "main_offset = %#x\n", main_offset );
-
if ( lseek( fd, offset, SEEK_SET ) < 0 )
-
{
-
perror( "lseek /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
if ( read( fd, buf, PAGESIZE ) != PAGESIZE )
-
{
-
perror( "read /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
outputBinary ( ( const unsigned char * )buf + main_offset, 32 );
-
fprintf( stderr, "main = %#x\n", ( unsigned int )&main );
-
/*
-
* 现在的数据
-
*/
-
outputBinary ( ( const unsigned char * )&main, 32 );
-
if ( munmap( mem, PAGESIZE ) < 0 )
-
{
-
perror( "munmap /proc/self/mem" );
-
exit( EXIT_FAILURE );
-
}
-
close( fd );
-
return( EXIT_SUCCESS );
- } /* end of main */
main = 0x8048814
byteArray [ 32 bytes ] ---->
00000000 55 89 E5 81 EC 08 10 00-00 57 56 53 6A 02 68 A1 U夊侅....WVSj.h
00000010 8A 04 08 E8 F0 FC FF FF-89 85 FC EF FF FF 83 C4 ?.桊?墔?兡
mem = 0x40015000
byteArray [ 32 bytes ] ---->
00000000 55 89 E5 81 EC 08 10 00-00 57 56 53 6A 02 68 A1 U夊侅....WVSj.h
00000010 8A 04 08 E8 F0 FC FF FF-89 85 FC EF FF FF 83 C4 ?.桊?墔?兡
offset = 0x8048000
main_offset = 0x814
byteArray [ 32 bytes ] ---->
00000000 4E 73 46 6F 63 75 73 20-53 65 63 75 72 69 74 79 NsFocus Security
00000010 20 54 65 61 6D 2E 00 FF-89 85 FC EF FF FF 83 C4 Team..墔?兡
main = 0x8048814
byteArray [ 32 bytes ] ---->
00000000 4E 73 46 6F 63 75 73 20-53 65 63 75 72 69 74 79 NsFocus Security
00000010 20 54 65 61 6D 2E 00 FF-89 85 FC EF FF FF 83 C4 Team..墔?兡
$
Linux下有/proc/
D: scz
Solaris 2.6下参看getpagesize(3C)手册页,关于如何获取页大小,一般是8192。
Linux下参看getpagesize(2)手册页,一般是4096。
3.4 getrusage如何用
A: 小四
在SPARC/Solaris 2.6/7下结论一致,只支持了ru_utime和ru_stime成员,其他成员被设置成0。修改头文件后在FreeBSD 4.3-RELEASE上测试,则不只支持ru_utime和
ru_stime成员。从FreeBSD的getrusage(2)手册页可以看到,这个函数源自4.2 BSD。
如此来说,至少对于SPARC/Solaris 2.6/7,getrusage(3C)并无多大意义。
3.5 setitimer如何用
D: scz
为什么要学习使用setitimer(2),因为alarm(3)属于被淘汰的定时器技术。
A: 小四
man -S 2 setitimer,下面是x86/FreeBSD 4.3-RELEASE的getitimer(2)手册页
--------------------------------------------------------------------------
GETITIMER(2) FreeBSD系统调用手册 GETITIMER(2)
名字
getitimer, setitimer - 获取/设置interval timer(怎么翻译这个)
库
标准C库(libc, -lc)
摘要
#include
#define ITIMER_REAL 0
#define ITIMER_VIRTUAL 1
#define ITIMER_PROF 2
int getitimer
(
int which,
struct itimerval *value
);
int setitimer
(
int which,
const struct itimerval *value,
struct itimerval *ovalue
);
描述
系统为每个进程提供了3种不同类型的interval timer,参看
getitimer()返回which对应的value,setitimer()设置which,如果ovalue不为NULL,则同时返回旧值。which取值0、1、2。
itimerval结构定义如下
struct itimerval
{
struct timeval it_interval; /* timer interval */
struct timeval it_value; /* current value */
};
如果it_value非零,指明定时器当前超时时间。如果it_interval非零,指明定时器超时后用该值重新设置it_value。如果it_value为零,将disable定时器,此时与it_interval无关。如果it_interval为零,导致定时器超时后被disable掉。
比系统时钟频率更小的时钟设置将被向上舍入到系统时钟频率,典型是10毫秒
ITIMER_REAL 定时器按照真实时钟递减,超时发生后分发SIGALRM信号
ITIMER_VIRTUAL 定时器按照进程虚拟时钟递减,仅当进程执行中运行。超时发生后分发SIGVTALRM信号
ITIMER_PROF 定时器在进程虚拟时钟中递减,当系统为进程利益而运行时也递减该时钟。解释器使用它统计被解释程序的执行效率。超时发生后,分发SIGPROF信号。该信号可能中断进程中正在进行的系统调用,使用这个定时器的程序必须考虑到重启被中断的系统调用。
it_interval 和 it_value 所允许的最大秒数是100000000
注意
在
#define timerclear(tvp) (tvp)->tv_sec = (tvp)->tv_usec = 0
#define timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec)
#define timercmp(tvp, uvp, cmp) (((tvp)->tv_sec == (uvp)->tv_sec) ? \
((tvp)->tv_usec cmp (uvp)->tv_usec) : \
((tvp)->tv_sec cmp (uvp)->tv_sec))
timerclear将时钟清零,timerisset测试时钟是否为零,timercmp比较两个时钟。
返回值
0 成功
-1 失败,设置全局变量errno
错误码
[EFAULT] 第二形参value指定了一个无效地址
[EINVAL] 第二形参value对应的时钟太大了,以致无法处理
参看
gettimeofday(2), select(2), sigvec(2), clocks(7)
历史
getitimer()从4.2 BSD开始出现
--------------------------------------------------------------------------
--------------------------------------------------------------------------
/*
* File : timer_sample.c
* Author : Unknown (Don't ask me anything about this program)
* Complie : gcc -Wall -pipe -O3 -o timer_sample timer_sample.c
* Platform : x86/FreeBSD 4.3-RELEASE
* Date : 2001-09-18 15:18
*/
/************************************************************************
* *
* Head File *
* *
************************************************************************/
#include
#include
#include
#include
/************************************************************************
* *
* Macro *
* *
************************************************************************/
/*
* for signal handlers
*/
typedef void Sigfunc ( int );
/************************************************************************
* *
* Function Prototype *
* *
************************************************************************/
static void Atexit ( void ( *func ) ( void ) );
static void init_signal ( void );
static void init_timer ( void );
static void on_alarm ( int signo );
static void on_terminate ( int signo );
static int Setitimer ( int which, const struct itimerval *value,
struct itimerval *ovalue );
Sigfunc * signal ( int signo, Sigfunc *func );
static Sigfunc * Signal ( int signo, Sigfunc *func );
static void terminate ( void );
/************************************************************************
* *
* Static Global Var *
* *
************************************************************************/
/************************************************************************/
static void Atexit ( void ( *func ) ( void ) )
{
if ( atexit( func ) != 0 )
{
perror( "atexit" );
exit( EXIT_FAILURE );
}
return;
} /* end of Atexit */
/*
* 初始化信号句柄
*/
static void init_signal ( void )
{
int i;
Atexit( terminate );
for ( i = 1; i < 9; i++ )
{
Signal( i, on_terminate );
}
Signal( SIGTERM, on_terminate );
Signal( SIGALRM, on_alarm );
return;
} /* end of init_signal */
static void init_timer ( void )
{
struct itimerval value;
value.it_value.tv_sec = 1;
value.it_value.tv_usec = 0;
value.it_interval = value.it_value;
Setitimer( ITIMER_REAL, &value, NULL );
return;
} /* end of init_timer */
static void on_alarm ( int signo )
{
static int count = 0;
/*
* 演示用,这很危险
*/
fprintf( stderr, "count = %u\n", count++ );
return;
}
static void on_terminate ( int signo )
{
/*
* 这次我们使用atexit()函数
*/
exit( EXIT_SUCCESS );
} /* end of on_terminate */
static int Setitimer ( int which, const struct itimerval *value,
struct itimerval *ovalue )
{
int ret;
if ( ( ret = setitimer( which, value, ovalue ) ) < 0 )
{
perror( "setitimer error" );
exit( EXIT_FAILURE );
}
return( ret );
} /* end of Setitimer */
Sigfunc * signal ( int signo, Sigfunc *func )
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset( &act.sa_mask );
act.sa_flags = 0;
if ( signo == SIGALRM )
{
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
#endif
}
else
{
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
#endif
}
if ( sigaction( signo, &act, &oact ) < 0 )
{
return( SIG_ERR );
}
return( oact.sa_handler );
} /* end of signal */
static Sigfunc * Signal ( int signo, Sigfunc *func )
{
Sigfunc *sigfunc;
if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )
{
perror( "signal" );
exit( EXIT_FAILURE );
}
return( sigfunc );
} /* end of Signal */
static void terminate ( void )
{
fprintf( stderr, "\n" );
return;
} /* end of terminate */
int main ( int arg, char * argv[] )
{
init_signal();
init_timer();
while ( 1 )
{
/*
* 形成阻塞,降低CPU占用率
*/
getchar();
}
return( EXIT_SUCCESS );
} /* end of main */
/************************************************************************/
--------------------------------------------------------------------------
下面是个x86/OpenBSD 3.0下的例子
--------------------------------------------------------------------------
/*
* File : timer_sample_1.c
* Complie : gcc -Wall -pipe -O3 -o timer_sample_1 timer_sample_1.c
* Platform : x86/OpenBSD 3.0
* Date : 2002-10-08 15:05
*/
/************************************************************************
* *
* Head File *
* *
************************************************************************/
#include
#include
#include
#include
#include
/************************************************************************
* *
* Macro *
* *
************************************************************************/
/*
* for signal handlers
*/
typedef void Sigfunc ( int );
/************************************************************************
* *
* Function Prototype *
* *
************************************************************************/
static void Atexit ( void ( *func ) ( void ) );
static void init_signal ( void );
static void init_timer ( void );
static void on_alarm ( int signo );
static void on_terminate ( int signo );
static void outputBinary ( const unsigned char *byteArray,
const size_t byteArrayLen );
static int Setitimer ( int which, const struct itimerval *value,
struct itimerval *ovalue );
Sigfunc * signal ( int signo, Sigfunc *func );
static Sigfunc * Signal ( int signo, Sigfunc *func );
static void terminate ( void );
/************************************************************************
* *
* Static Global Var *
* *
************************************************************************/
/************************************************************************/
static void Atexit ( void ( *func ) ( void ) )
{
if ( atexit( func ) != 0 )
{
perror( "atexit" );
exit( EXIT_FAILURE );
}
return;
} /* end of Atexit */
/*
* 初始化信号句柄
*/
static void init_signal ( void )
{
int i;
Atexit( terminate );
for ( i = 1; i < 9; i++ )
{
Signal( i, on_terminate );
}
Signal( SIGTERM, on_terminate );
Signal( SIGALRM, on_alarm );
return;
} /* end of init_signal */
static void init_timer ( void )
{
struct itimerval value;
value.it_value.tv_sec = 1;
value.it_value.tv_usec = 0;
value.it_interval = value.it_value;
Setitimer( ITIMER_REAL, &value, NULL );
return;
} /* end of init_timer */
static void on_alarm ( int signo )
{
static int count = 0;
/*
* 演示用,这很危险
*/
fprintf( stderr, "count = %u\n", count++ );
return;
}
static void on_terminate ( int signo )
{
/*
* 这次我们使用atexit()函数
*/
exit( EXIT_SUCCESS );
} /* end of on_terminate */
static void outputBinary ( const unsigned char *byteArray, const size_t byteArrayLen )
{
size_t offset, k, j, i;
fprintf( stderr, "byteArray [ %u bytes ] ----> \n", byteArrayLen );
if ( byteArrayLen <= 0 )
{
return;
}
i = 0;
offset = 0;
for ( k = byteArrayLen / 16; k > 0; k--, offset += 16 )
{
fprintf( stderr, "%08X ", offset );
for ( j = 0; j < 16; j++, i++ )
{
if ( j == 8 )
{
fprintf( stderr, "-%02X", byteArray );
}
else
{
fprintf( stderr, " %02X", byteArray );
}
}
fprintf( stderr, " " );
i -= 16;
for ( j = 0; j < 16; j++, i++ )
{
/*
* if ( isprint( (int)byteArray ) )
*/
if ( ( byteArray >= ' ' ) && ( byteArray <= 255 )
&& ( byteArray != 0x7f ) )
{
fprintf( stderr, "%c", byteArray );
}
else
{
fprintf( stderr, "." );
}
}
fprintf( stderr, "\n" );
} /* end of for */
k = byteArrayLen - i;
if ( k <= 0 )
{
return;
}
fprintf( stderr, "%08X ", offset );
for ( j = 0 ; j < k; j++, i++ )
{
if ( j == 8 )
{
fprintf( stderr, "-%02X", byteArray );
}
else
{
fprintf( stderr, " %02X", byteArray );
}
}
i -= k;
for ( j = 16 - k; j > 0; j-- )
{
fprintf( stderr, " " );
}
fprintf( stderr, " " );
for ( j = 0; j < k; j++, i++ )
{
if ( ( byteArray >= ' ' ) && ( byteArray <= 255 )
&& ( byteArray != 0x7f ) )
{
fprintf( stderr, "%c", byteArray );
}
else
{
fprintf( stderr, "." );
}
}
fprintf( stderr, "\n" );
return;
} /* end of outputBinary */
static int Setitimer ( int which, const struct itimerval *value,
struct itimerval *ovalue )
{
int ret;
if ( ( ret = setitimer( which, value, ovalue ) ) < 0 )
{
perror( "setitimer error" );
exit( EXIT_FAILURE );
}
return( ret );
} /* end of Setitimer */
Sigfunc * signal ( int signo, Sigfunc *func )
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset( &act.sa_mask );
act.sa_flags = 0;
if ( signo == SIGALRM )
{
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
#endif
}
else
{
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
#endif
}
if ( sigaction( signo, &act, &oact ) < 0 )
{
return( SIG_ERR );
}
return( oact.sa_handler );
} /* end of signal */
static Sigfunc * Signal ( int signo, Sigfunc *func )
{
Sigfunc *sigfunc;
if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )
{
perror( "signal" );
exit( EXIT_FAILURE );
}
return( sigfunc );
} /* end of Signal */
static void terminate ( void )
{
fprintf( stderr, "\n" );
return;
} /* end of terminate */
int main ( int arg, char * argv[] )
{
struct itimerval value;
int which;
init_signal();
init_timer();
while ( 1 )
{
/*
* 形成阻塞,降低CPU占用率
*/
getchar();
/*
* SIGALRM信号句柄做了特殊处理,并不重启被其中断的系统调用。下面的
* 代码无需在标准输入上提交数据就能够得到执行。getchar()形成的阻塞
* 是被SIGALRM信号打破的。
*/
for ( which = ITIMER_REAL; which <= ITIMER_PROF; which++ )
{
memset( &value, 0, sizeof( value ) );
if ( getitimer( which, &value ) < 0 )
{
perror( "getitimer error" );
return( EXIT_FAILURE );
}
outputBinary( ( const unsigned char * )&value, sizeof( value ) );
}
}
return( EXIT_SUCCESS );
} /* end of main */
/************************************************************************/
--------------------------------------------------------------------------
D: scz
讨论一个问题。getchar()的作用是降低CPU占用率,可用top命令查看。
timer_sample.c中换用ITIMER_PROF/SIGPROF后,你会发现上述程序无输出,我据此
认为getchar()形成的阻塞不计算在进程虚拟时钟中,也不认为系统正在为进程利益
而运行。
如果进一步将getchar()去掉,直接一个while()无限循环,即使换用
ITIMER_PROF/SIGPROF,程序还是有输出。不过top命令查看的结果让你吐血,CPU几
乎无空闲。
D: scz
setitimer( ITIMER_REAL, &value, NULL )导致分发SIGALRM信号,如果同时使用
alarm(),势毕造成冲突。此外注意sleep()、pause()等函数带来的冲突。
4. 系统资源相关问题
4.1 主流Unix操作系统上如何编程获取进程的内存、CPU利用状况
Q: Solaris下如何编程获知CPU占用率和内存占用信息呢,可移植吗?
Q: 我想写个程序遍历当前运行中的活动进程,Solaris提供相应系统调用了吗
A: Nicholas Dronen
不可移植。man -s 4 proc,man -s 3k kstat
如果不是编程,可以用top、mpstat、vmstat、sar(1)、cpustat(1M)等等,还有
/usr/ucb/ps -aux,对于Solaris来说,后者更直接精炼,top不是标准配置。
# /usr/bin/prstat (Solaris 8 prstat(1M)手册页)
# /usr/ucb/ps -aux | head (Solaris 2.x)
Q: 主流Unix操作系统上如何编程获取进程的内存、CPU利用状况,AIX、HP、SUN
process memory usage
process cpu time usage
A: Nate Eldredge
man -s 3C getrusage
D: 小四
在SPARC/Solaris 2.6/7下结论一致,只支持了ru_utime和ru_stime成员,其他成员
被设置成0。FreeBSD 4.3-RELEASE上测试,则不只支持ru_utime和ru_stime成员。从
FreeBSD的getrusage(2)手册页可以看到,这个函数源自4.2 BSD。
至少对于SPARC/Solaris 2.6/7,getrusage(3C)并无多大意义。
A: Robert Owen Thomas
对于Solaris,可以利用procfs接口,下面的例子获取指定进程的内存占用情况
--------------------------------------------------------------------------
/*
* @(#)memlook.c 1.0 10 Nov 1997
* Robert Owen Thomas robt@cymru.com
* memlook.c -- A process memory utilization reporting tool.
*
* gcc -Wall -pipe -O3 -o memlook memlook.c
*/
#pragma ident "@(#)memlook.c 1.0 10 Nov 1997 Robert Owen Thomas robt@cymru.com"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int counter = 10;
int showUsage ( const char * );
void getInfo ( int, int );
{
int fd, pid, timeloop = 0;
char pidpath[BUFSIZ]; /* /usr/include/stdio.h: #define BUFSIZ 1024 */
switch ( argc )
{
case 2:
break;
case 3:
timeloop = atoi( argv[2] );
break;
default:
showUsage( argv[0] );
break;
} /* end of switch */
pid = atoi( argv[1] );
sprintf( pidpath, "/proc/%-d", pid ); /* -表示向左靠 */
/*
* /proc/1/是目录,但在这种用法中,就是直接打开目录,不是打开文件
*/
if ( ( fd = open( pidpath, O_RDONLY ) ) < 0 )
{
perror( pidpath );
exit( 1 );
}
if ( 0 < timeloop )
{
for ( ; ; )
{
getInfo( fd, pid );
sleep( timeloop );
}
}
getInfo( fd, pid );
close( fd );
exit( 0 );
} /* end of main */
int showUsage ( const char * progname )
{
fprintf( stderr, "%s: usage: %s < PID > [time delay]\n", progname, progname );
exit( 3 );
} /* end of showUsage */
void getInfo ( int fd, int pid )
{
prpsinfo_t prp;
prstatus_t prs;
if ( ioctl( fd, PIOCPSINFO, &prp ) < 0 )
{
perror( "ioctl" );
exit( 5 );
}
if ( ioctl( fd, PIOCSTATUS, &prs ) < 0 )
{
perror( "ioctl" );
exit( 7 );
}
if ( counter > 9 )
{
fprintf( stdout, "PID\tIMAGE\t\tRSS\t\tHEAP\t\tSTACK\n" );
counter = 0;
}
fprintf( stdout, "%u\t%-9u\t%-9u\t%-15u\t%-15u\n", pid,
( unsigned int )prp.pr_bysize, ( unsigned int )prp.pr_byrssize,
( unsigned int )prs.pr_brksize, ( unsigned int )prs.pr_stksize );
counter++;
} /* end of getInfo */
--------------------------------------------------------------------------
4.2 Solaris下如何获知CPU速率
A: Philip Brown
psrinfo -v
psrinfo | grep on-line | wc -l 简单给出CPU数目
A: Philly Bob
在OK提示符下输入"module-info"获知CPU数目以及频率
A: scz
# /usr/platform/`uname -i`/sbin/prtdiag -v
# /usr/platform/`uname -m`/sbin/prtdiag -v
# /usr/bin/netstat -k cpu_info0
A: Tony Walton
如果你装了Sun Workshop,还可以尝试fpversion命令
# /opt/SUNWspro/bin/fpversion
A SPARC-based CPU is available.
CPU's clock rate appears to be approximately 266.1 MHz.
Kernel says CPU's clock rate is 270.0 MHz.
Kernel says main memory's clock rate is 90.0 MHz.
Sun-4 floating-point controller version 0 found.
An UltraSPARC chip is available.
FPU's frequency appears to be approximately 277.1 MHz.
Use "-xtarget=ultra2i -xcache=16/32/1:256/64/1" code-generation option.
Hostid = 0x80BC3CB3.
#
4.3 如何编程获取Solaris系统当前内存大小
Q: 如何编程(或者有什么现成命令)获取Solaris系统当前内存大小?
A: Nithyanandham
几个现成命令
/usr/platform/`uname -m`/sbin/prtdiag -v | grep Memory
prtconf -v | grep Memory
如果装了GNU top,也可以直接用top命令看到。
D: scz
truss prtconf的输出中有如下内容
sysconfig(_CONFIG_PAGESIZE) = 8192
sysconfig(_CONFIG_PHYS_PAGES) = 16384
Memory size: 128 Megabytes
# /usr/ccs/bin/nm -nx /dev/ksyms | grep "|sysconfig$"
10626] |0x0000100ec110|0x0000000001bc|FUNC |GLOB |0 |ABS |sysconfig
# find /usr/include -type f -name "*.h" | xargs grep -l _CONFIG_PAGESIZE
/usr/include/sys/sysconfig.h
# vi -R /usr/include/sys/sysconfig.h
/*
* cmd values for _sysconfig system call.
* WARNING: This is an undocumented system call,
* therefore future compatibility can not
* guaranteed.
*/
#define _CONFIG_PAGESIZE 6 /* system page size */
#define _CONFIG_PHYS_PAGES 26 /* phys mem installed in pages */
参看sysconf(3C)手册页。
_SC_PAGESIZE
_SC_PAGE_SIZE
_SC_PHYS_PAGES
A: Casper H.S. Dik
--------------------------------------------------------------------------
/*
* Program to determine the size installed physical memory on Suns.
*
* Casper Dik.
*/
#define MEGABYTE 0x00100000
#define MAXMEM 0x7ff00000
#define THEMEM "/dev/mem"
#include
#include
#include
#include
int main ( int argc, char * argv[] )
{
int fd = open( THEMEM, O_RDONLY );
char c;
unsigned long pos, mapstart = 0;
int totmb = 0;
if ( fd == -1 )
{
perror( THEMEM );
exit( 1 );
}
for ( pos = 0; pos < MAXMEM; pos += MEGABYTE )
{
if (lseek( fd, pos, 0 ) == -1 )
{
perror( "lseek" );
exit( 1 );
}
if ( read( fd, &c, 1 ) == -1 )
{
int size = ( pos - mapstart ) / MEGABYTE;
if ( size != 0 )
{
printf( "found %3d MB starting at 0x%p\n", size, ( void * )mapstart );
totmb += size;
}
mapstart = pos + MEGABYTE; /* start of next possible mapping */
}
}
printf( "Total memory size: %d MB\n", totmb );
exit( 0 );
}
--------------------------------------------------------------------------
在SPARC/Solaris 7 64-bit kernel mode下编译32-bit应用程序
$ gcc -Wall -pipe -O3 -o memcount memcount.c
$ /usr/ccs/bin/strip memcount
在SPARC/Solaris 7 64-bit kernel mode下编译64-bit应用程序
$ /opt/SUNWspro/SC5.0/bin/cc -xarch=v9 -O -o memcount memcount.c
$ /usr/ccs/bin/strip memcount
由于需要读访问/dev/mem,普通用户用户无法使用该程序。
5. 块设备相关问题
5.0 Solaris/FreeBSD/Linux中如何mount ISO文件
A: SMTH/Unix版 2003-04-11
xfgavin@SMTH
在Linux中
mount -o loop your.iso
bcl@SMTH
在FreeBSD中
vnconfig vn0 your.iso
mount_cd9660 /dev/vn0c
... ...
umount
vnconfig -u vn0
Soaris@SMTH
在FreeBSD 5.0中
mdconfig -a -t vnode -f your.iso -u 4 (then you have /dev/md4)
mount_cd9660 /dev/md4
A: Sun Microsystems
SunSolve CD-ROM Version 5.0.2 released August 2001包含一个BUG,影响了它的
安装使用。当mount上这张光盘后,没有期待中的普通文件、目录,只有一个名为
patch1或patch2的文件,这个文件实际是一个ISO映像文件。下面的操作在Solaris 8
上进行。
将SunSolve CD-ROM Version 5.0.2插入光驱,Volume Manager自动检测到光盘存在
并mount上它
$ su
# cd /cdrom/sunsolve_patch_cd_5_0_2_1
# ls
这里是进入包含ISO文件所在目录,将会看到patch1或patch2文件,如果没有,可能
光盘mount失败。可执行volcheck命令迫使Volume Manager检测光盘存在并mount它。
执行如下命令,假设/directory-path/filename确定ISO文件
# /usr/sbin/lofiadm -a /directory-path/filename
比如这里就是
# /usr/sbin/lofiadm -a /cdrom/sunsolve_patch_cd_5_0_2_1/patch1
与ISO文件相关的node number将被显示出来,比如
/dev/lofi/1
执行如下命令,指定上条命令所得到的node number做本条命令的参数
# /usr/sbin/mount -F hsfs node-number /mnt
比如这里就是
# /usr/sbin/mount -F hsfs /dev/lofi/1 /mnt
于是利用loopback file driver将ISO映像文件mount成一个块设备,现在你可以进入
/mnt目录正常使用其中的内容了。
# cd /mnt
# ls
其相应的反过程如下
$ su
# cd /
# /usr/sbin/umount /mnt
释放ISO映像文件所用node,执行如下命令,假设/directory-path/filename确定ISO
文件
# /usr/sbin/lofiadm -d /directory-path/filename
A: joerg@schily.isdn.cs.tu-berlin.de 1988-10
这是一个德国的Unix Kernel Hacker
http://www.fokus.gmd.de/research ... .schilling/private/
在这里他提供了如下驱动程序
ftp://ftp.fokus.gmd.de/pub/unix/kernel/fbk
fbk是一个伪设备驱动程序,用于在Solaris上mount那些包含文件系统的文件,比如
ISO文件(文件模拟块设备)。FreeBSD有类似的vnode driver,Linux则是loopback
driver。但是fbk最早于1988年10月就推出了。
5.1 CDROM设备究竟在哪里
Q: 为了mount光驱,需要哪些包
A: SUNWvolr SUNWcstl SUNWcstlx
D: Dennis Clarke
1) su - root
2) /etc/init.d/volmgt stop
3) ls -1 /dev/dsk/c*s2
4) mount -F hsfs -o ro /dev/dsk/c0t6d0s2 /cdrom
或者
1) /etc/init.d/volmgt stop
2) /etc/init.d/volmgt start
3) volcheck
4) eject
观察/etc/vold.conf
Q: 如何才能知道哪个设备文件对应CDROM(c0t2d0s0?)。如果有一张光盘在CDROM里,
可以用df命令看到对应的设备文件,但是没有光盘在光驱里的时候呢?
A: /dev/sr0 是一个指向最终设备文件的符号链接,仅对SPARC有效,不包括x86
A: Logan Shaw
$ uname -sri
SunOS 5.8 i86pc
$ ls -l /dev/sr*
lrwxrwxrwx /dev/sr0 -> dsk/c1t0d0s2
$
我想x86下是一样的
Q: E420R,Solaris 7 11/99,我从http://sunsolve.sun.com获得一些补丁并安装了,
结果现在我的光驱出问题了。似乎mount成功了,但是找不到文件,/etc/mnttab
中没有任何有关光驱的信息,插入一张光盘会弹出一个文件管理器窗口,但是没
有文件。
A: Danny Mann
检查是否打了如下Solaris 7内核补丁106541-13和 -14。这两个补丁有问题。解
决办法是禁止vold,手工mount光驱。
A: rschicht@my-deja.com
试试volrmmount -d命令。用patchadd -p检查是否安装了补丁106541-14,访问如
下链接
http://sunsolve.Sun.COM/pub-cgi/ ... atches/patch-access
获取补丁106541-14的说明,阅读NOTE 15。
A: 补丁106541-14的说明,NOTE 15
1. 首先禁止掉vold守护进程
# /etc/init.d/volmgt stop
2. 手工mount光驱(设备文件名可能不同)
# /etc/mount -F hsfs -o ro /dev/dsk/c0t2d0s0 /cdrom
查看/etc/vfstab、/dev/dsk确认光驱所在设备文件名。
5.2 如何弹出光驱
Q: 在安装Oracle 8i时,系统提示插入第二张光盘,但是此时无法成功eject第一张
光盘,终端挂起,杀掉Oracle 8i的安装进程也无济于事。唯一的办法是reset。
A: Sergey Kurganov
下面的操作或许有所帮助
1) 终止卷管理器
# /etc/init.d/volmgt stop
2) unmount光驱,手动eject
3) 重启卷管理器
# /etc/init.d/volmgt start
D: plane@SMTH 2002-02-26 01:03
装Oracle 9的时候,安装文档特意提醒要用绝对路径才能换盘。
5.3 如何利用超级块进行恢复工作
Q: Sun工作站在reboot时掉电了,用安装光盘启动进入单用户模式,执行fsck命令时
报错
Stop-A
ok boot cdrom -s
INIT: SINGLE USER MODE
# fsck -o b=32 /dev/rdsk/c0t5d0s*
Alternate super block location: 32.
** /dev/rdsk/c0t5d0s0
BAD SUPER BLOCK: MAGIC NUMBER WRONG
USE AN ALTERNATE SUPER-BLOCK TO SUPPLY NEEDED INFORMATION;
eg. fsck [-F ufs] -o b=# [special ...]
where # is the alternate super block. SEE fsck_ufs(1M).
Alternate super block location: 32.
** /dev/rdsk/c0t5d0s1
BAD SUPER BLOCK: MAGIC NUMBER WRONG
USE AN ALTERNATE SUPER-BLOCK TO SUPPLY NEEDED INFORMATION;
eg. fsck [-F ufs] -o b=# [special ...]
where # is the alternate super block. SEE fsck_ufs(1M).
Alternate super block location: 32.
** /dev/rdsk/c0t5d0s2
BAD SUPER BLOCK: MAGIC NUMBER WRONG
USE AN ALTERNATE SUPER-BLOCK TO SUPPLY NEEDED INFORMATION;
eg. fsck [-F ufs] -o b=# [special ...]
where # is the alternate super block. SEE fsck_ufs(1M).
Alternate super block location: 32.
A: Sree Mokkapati
正确的用法就在错误提示信息里,你应该使用另外的超级块进行恢复工作,32仅
仅是常用备份超级块之一。
fsck -F ufs -o b=32 device_name
此外如果想知道还有哪些备份超级块可用,执行
newfs -Nv device_name
(等价于 mkfs -F ufs -oN -m /dev/rdsk/cXtXdXsX )
先用df等命令确认原始device_name。
D: scz
SPARC/Solaris的硬盘损坏多半是文件系统根区被破坏,并不需要拆卸硬盘到其他
机器上mount后fsck,找一张Solaris安装光盘
Stop-A进入OBP状态,在ok提示符下输入
ok> boot cdrom -s
进入单用户模式。此时原有根文件系统并未mount上来,也不需要mount原有根文
件系统,直接
newfs -Nv /dev/rdsk/c0t0d0s0
找出原根文件系统所有备份超级块号
fsck -y -F ufs -o b=<任一备份超级块号> /dev/rdsk/c0t0d0s0
这里假设原根文件系统的原始设备名是/dev/rdsk/c0t0d0s0。其他文件系统的原
始设备名可以在系统完好时df -k获取,或者从/etc/vfstab中获取信息。比如:
/dev/rdsk/c0t0d0s0 /
/dev/rdsk/c0t0d0s1 /var
/dev/rdsk/c0t0d0s6 /usr
/dev/rdsk/c0t0d0s7 /export/home
根文件系统一定是/dev/rdsk/c0t0d0s0,可以先用fsck处理根文件系统,然后
mount -F ufs /dev/dsk/c0t0d0s0 /mnt
more /mnt/etc/vfstab
这样就能得到其它文件系统的原始设备名了。此外,fsck使用的是/dev/rdsk/...
而mount使用的是/dev/dsk/...,注意这个区别。
vfstab(4)解释得很模糊,回头我上www.google.com去找找其他资料。
The fsck pass value of 2 means that the file system will be checked,
but not sequentially
Q: /etc/vfstab轻微损坏,启动时只能输入root口令进入单用户模式,根文件系统被
只读mount。不想动用Solaris安装光盘,怎么办。
A: mount -F ufs -o remount,rw /dev/dsk/c0t0d0s0 /
5.4 Solaris root口令忘记了
Q: 忘记了root口令,怎么办
A: Steve Menard
启动时按Stop-A进入ok提示符
ok boot cdrom -s (放入启动安装光盘)
mount -F ufs /dev/dsk/c0t0d0s0 /mnt (这里指定原根区对应的设备名)
TERM=vt100;export TERM (这一步必须做,否则vi失败)
vi /mnt/etc/shadow
删除root口令加密串,比如
root:WxzL460hohWsU:10724::::::
删除WxzL460hohWsU,确认你还有8个冒号,重启动
或者 /usr/sbin/reboot -- "cdrom -s"
A: Philip Brown
使用vi有很多麻烦的地方,可以考虑sed
mount -F ufs /dev/dsk/c0t0d0s0 /mnt
sed 's/:WxzL460hohWsU:/::/' /mnt/etc/shadow > s
mv s /mnt/etc/shadow
或者使用ed
mount -F ufs /dev/dsk/c0t0d0s0 /mnt
ed /mnt/etc/shadow
1s/root:[^:]*:/root::/ (注意,前面是数字1,不是字母l)
w
q
5.5 如何使用fmthard
A: Seán Boran
如果希望对第二块物理硬盘的分区与第一块物理硬盘一样,考虑fmthard和prtvtoc的
结合使用,要比手工format快得多。比如,第一块物理硬盘是target 3,第二块物理
硬盘是target 1,我们希望第二块物理硬盘磁盘卷标是"mirror",做如下操作:
/usr/sbin/prtvtoc /dev/rdsk/c0t3d0s2 | /usr/sbin/fmthard -n mirror -s - /dev/rdsk/c0t1d0s2
man -s 1M fmthard了解更多细节。
5.6 如何从光盘恢复Solaris 7的引导扇区
A: paranoid@SMTH
在安装盘里有一个tools目录,进去后有一个命令叫做installboot
A: melonm@SMTH
比如
installboot /usr/platform/`uname -i`/lib/fs/ufs/bootblk /dev/rdsk/c0t1d0s0
5.7 Solaris支持类似微软autorun.inf文件的功能吗
Q: 我自己制作了一张光盘,同时用于Solaris和Windows。在Windows环境下,可以利
用autorun功能,当插入光盘的时候自动调用喜爱的浏览器打开一个文件。不知道
Solaris 7/8下是否存在类似功能。
A: hakteng
是的,从Solaris 8(CDE version 1.4)开始支持类似功能了
o 创建一个名为"volstart"的脚本文件,比如
--------------------------------------------------------------------------
#! /bin/ksh
#
# This is a CD volume start script. This start script is designed
# to be automatically run when the CD is inserted into a Solaris
# system's CDrom drive.
#
# Note: not all Solaris systems have an auto volstart ability. If this
# CD is inserted into a CDrom drive of a Solaris system without the
# volstart ability, volstart can also be run manually by executing it
# from either the desktop's file manager or from a Unix command line.
full_name=$0
dir_name=`/usr/bin/dirname $full_name`
if [[ -x /usr/dt/bin/dtaction ]]; then
# Run the CDrom's installer program
/usr/dt/bin/dtaction Run $dir_name/installer
fi
--------------------------------------------------------------------------
o 将"volstart"文件放在光盘根目录下
o /usr/dt/bin/sdtvolcheck脚本中存在如下语句
if [[ -x $mountPt/volstart ]];then exec $mountPt/volstart;
于是,当插入光盘的时候volstart脚本被执行,对于上例,最终导致installer被
执行
5.8 如何修改/dev/null的属性
Q: /devices/pseudo/mm@0:null的属性是0620 root tty,我想
chmod 666 /devices/pseudo/mm@0:null ,但是几分钟后,属性被修改回
0620 root tty,怎么办
A: Markus Mayer
查看/etc/minor_perm文件,
# grep -s null /etc/minor_perm
mm:null 0620 root tty
修改该文件中的这一行成"mm:null 0666 root sys"即可。
5.9 如何读取Solaris disk label信息
A: Sun Microsystems 1998-03-25
对于SPARC和X86架构来说,读取disk label信息的方式略有区别,主要原因在于分区
信息组织方式不同、设备名不同。
--------------------------------------------------------------------------
/***************************************************************/
/* */
/* gcc -DSPARC_ARC -Wall -pipe -O3 -o rdlabel rdlabel.c */
/* */
/* rdlabel -- Read the disk label */
/* */
/* Usage: */
/* rdlabel
/* */
/* Notes: on SPARC arch devices are /dev/rdsk/cXtYdZsS */
/* OTHER arch devices are /dev/rdsk/cXtYdZpN */
/* */
/* X = controller, */
/* Y = target, */
/* Z = disk #, */
/* N = fdisk partition */
/* S = 0 or 2 */
/* */
/***************************************************************/
#include
#include
#include
#include
#include
#include
#include
#ifdef SPARC_ARC
#define ZEXAMPLE "Example: rdlabel /dev/rdsk/c0t0d0s0\n"
#else
#define ZEXAMPLE "Example: rdlabel /dev/rdsk/c0t0d0p1\n"
#endif
void usage ( void )
{
fprintf( stderr, "Usage : rdlabel
fprintf( stderr, "Warning: You must be root to run rdlabel!\n" );
fprintf( stderr, ZEXAMPLE );
return;
}
int main ( int argc, char * argv[] )
{
#ifdef SPARC_ARC
int label_loc = ( 512 * DK_LABEL_LOC );
#else
int label_loc = 0;
#endif
int disk_fd;
struct dk_label label;
char buffer[80];
if ( argc < 2 )
{
fprintf( stderr, "Error: Argument expected!\n" );
usage();
exit( EXIT_FAILURE );
}
sprintf( buffer, "%s", argv[1] );
disk_fd = open( buffer, O_RDONLY );
if ( disk_fd < 0 )
{
perror( "Uable to open device " );
exit( EXIT_FAILURE );
}
else
{
lseek( disk_fd, label_loc, SEEK_SET );
( void )read( disk_fd, ( char * )&label, sizeof( label ) );
( void )close( disk_fd );
}
( void )printf( "Read label for device %s returned the following:\n", buffer );
( void )printf( "<%s>\n", label.dkl_asciilabel );
return( EXIT_SUCCESS );
}
--------------------------------------------------------------------------
由于访问了原始设备,必须以root身份执行rdlabel。下面分别是X86和SPARC架构上
的输出举例:
X86:
SPARC:
D: scz
下面是我实验机(SPARC/Solaris 7)上的输出
# rdlabel /dev/rdsk/c0t0d0s0
Read label for device /dev/rdsk/c0t0d0s0 returned the following:
# prtvtoc /dev/rdsk/c0t0d0s0 <-- 执行/usr/sbin/prtvtoc命令
* /dev/rdsk/c0t0d0s0 partition map
*
* Dimensions:
* 512 bytes/sector <-- 每个扇区512字节
* 63 sectors/track <-- 每个磁道63个扇区
* 15 tracks/cylinder <-- 每个柱面15个磁道
* 945 sectors/cylinder <-- 每个柱面945个扇区
* 8894 cylinders <-- 总共8894个柱面
* 8892 accessible cylinders <-- 8892个可用柱面
*
* Flags:
* 1: unmountable
* 10: read-only
*
* Unallocated space:
* First Sector Last
* Sector Count Sector
* 8401050 1890 8402939
*
* First Sector Last
* Partition Tag Flags Sector Count Sector Mount Directory
0 2 00 0 2457945 2457944 /
1 3 01 2457945 302400 2760344
2 5 00 0 8402940 8402939
6 4 00 2760345 3072195 5832539 /usr
7 8 00 5832540 2568510 8401049 /export/home
对比这些输出,参看prtvtoc(1M)手册页了解更多细节。
5.10 如何自己制作Solaris启动软盘
Q: 我知道可以去
http://soldc.sun.com/support/drivers/dca_diskettes/
下载启动软盘的映象文件,可我还想知道它最初是如何制作出来的
A: 小四
1) 用fdformt格式化软盘
2) 用newfs在软盘上创建新的文件系统
3) 将软盘mount上来
4) 用cp命令复制the second-level disk booter(boot或者ufsboot)到软盘,比如
/platform/sun4u/ufsboot。参看installboot(1M)、boot(1M)手册页
5) 用installboot命令安装boot block到软盘,比如
installboot /usr/platform/`uname -i`/lib/fs/ufs/bootblk /dev/rdsk/c1t0d0s0
6) 用cp命令复制必要的工具文件到软盘
7) unmount软盘
8) 用eject命令弹出软盘
5.11 x86/Solaris如何访问FAT32分区
A: Dan Anderson
mount -F pcfs /dev/dsk/c0t0d0p0:1 /mnt/<...> # SCSI
mount -F pcfs /dev/dsk/c0d0p0:1 /mnt/<...> # ATAPI
c0 控制器ID
t0 SCSI ID (对于ATAPI省略)
d0 对于SCSI总是0,对于ATAPI是硬盘号
p0 p0对应第一个主分区表项
:1 对应逻辑驱动器(c - z 或 1 - 24)
有些报告说如果FAT32分区不对应第一个主分区表项,mount失败,感觉x86/Solaris
对pcfs支持混乱。
A: spp(低音炮)
在SPARC/Solaris 7上df -k
# df -k
/dev/dsk/c0t0d0s0 /
/dev/dsk/c0t0d0s6 /usr
/dev/dsk/c0t0d0s7 /export/home
在x86/Solaris 8上df -k
# df -k
/dev/dsk/c0d0s0 /
/dev/dsk/c0d0s7 /export/home
c 硬盘控制器的位置,比如主板第二个IDE接口上的第一个硬盘(主盘)对应c1d0
t 只SPARC有,SCSI ID
d 某一确定硬盘控制器(c参数决定)上硬盘位置
p 只x86有,对应MS系统的Partition概念
s slice号,Solaris系统的概念,不太好解释,如果和p一起出现,可以理解成类似
MS逻辑驱动器的概念
假设x86架构上某硬盘在主引导扇区有两个主分区表项,第一个为FAT32分区,第二个
为Solaris分区,Solaris分区上划分了两个slice,一个为根文件系统/、一个为swap
区,则分别表示为/dev/dsk/c1d0p0:1(FAT32)、/dev/dsk/c1d0p1s0(/)、
/dev/dsk/c1d0p1s1(swap)
在mount FAT32分区时应该用
mount -F pcfs /dev/dsk/c1d0p0:1 /mnt/
D: 小四
注意,Solaris的slice概念和FreeBSD的slice概念不同,FreeBSD的slice概念就是MS
的partition概念,而Solaris的slice概念类似于MS扩展分区上的逻辑驱动器概念。
6. /etc/system可调资源限制
6.1 Solaris下如何限制每个用户可拥有的最大进程数
A: Casper Dik
在/etc/system设置
set maxuprc =
Q: maxusers参数究竟影响了什么
A: Casper Dik
下面以/etc/system语法格式举例说明:
*
set maxusers = <以MB为单位计的可用物理内存数量>
* 系统所允许的最大进程数,通常最多30000
set max_nprocs = 10 + 16 * maxusers
* 每个用户可以拥有的最大进程数(为超级用户保留5个)
set maxuprc = max_nprocs - 5;
# sysdef | sed -n '/System Configuration/,$p'
6.2 如何配置系统使之支持更多的伪终端
A: Argoth
不要试图通过'/usr/bin/adb -k'到达目的。
a. 如果Solaris版本小于7,修改/etc/system,增加如下行
set pt_cnt=
执行/usr/sbin/reboot -- -r,或者Stop-A,执行boot -r
b. 对于Solaris 8,支持的伪终端数目根据需要动态改变,系统依然有一个内部限制,
但是这个值非常大。如果"pt_cnt"变量小于这个内部限制,将被忽略。一般情况
下,不再需要指定"pt_cnt"变量。但还是有某些罕见的情形,需要设置"pt_cnt"
变量大于内部限制。
6.3 如何增加每个进程可打开文件句柄数
A: Casper H.S. Dik
从Solaris 2.4开始,可以通过修改/etc/system实现
* set hard limit on file descriptors
set rlim_fd_max = 4096
* set soft limit on file descriptors
set rlim_fd_cur = 1024
软限制超过256时,某些应用程序会出问题,尤其BCP程序。软限制超过1024时,那些
使用select()的应用程序可能会出问题。Solaris 7之前,select()使用的文件句柄
数不能超过1024。Solaris 2.6的RPC代码被重写过了,使用poll()代替select(),可
以使用超过1024的文件句柄。Solaris 2.6之前,如果软限制超过1024,所有RPC服务
很可能崩溃。
Solaris 7下select()可以使用最多达65536的文件句柄,64-bit应用程序缺省情况如
此。如果是32-bit应用程序,需要指定给FD_SETSIZE一个更大的值,重新编译。
如果程序使用标准输入/输出(stdio),或者调用那些使用stdio的库函数,当打开的
文件超过256时,程序可能会出问题,这个限制是stdio的限制。当程序需要大量文件
句柄时,应该想办法保留一些小数字的文件句柄,让stdio使用它们。
Solaris 7下64-bit应用程序不再受这个stdio限制的影响。如果你的确需要超过256
个FILE *,而又不能使用Solaris 7,或者需要运行32-bit代码,考虑使用来自AT&T
的SFIO(http://www.research.att.com/sw/tools/sfio/)。
A: qaz@SMTH
检查当前设置
# ulimit -H -n
1024
# ulimit -S -n
64
#
对于Solaris,建议修改/etc/system后重启
* set hard limit on file descriptors
set rlim_fd_max=0x8000
* set soft limit on file descriptors
set rlim_fd_cur=0x8000
然后 ulimit -S -n 8192
对于Linux
echo 65536 > /proc/sys/fs/file-max
然后 ulimit -S -n 8192
对于FreeBSD
编辑/etc/sysctl.conf文件(或者sysctl -w,参看SYSCTL.CONF(5))
kern.maxfiles=65536
kern.maxfilesperproc=32768
Q: Linux下如何加大系统可以打开的文件数
A: planck.bbs@bbs.nju.edu.cn
echo
6.5 做了setuid()这类调用的程序如何产生core dump
Q: 做了setuid()这类调用的程序不会产生core文件,可我需要调试这个程序。
--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -o suidtest suidtest.c
*/
#include
#include
#include
#include
#include
#include
int main ( int argc, char * argv[] )
{
int *ptr = NULL;
printf( "Current uid = %d euid = %d\n", ( int )getuid(), ( int )geteuid() );
printf( "Result of seteuid( 500 ) = %d\n", seteuid( 500 ) );
printf( "Current uid = %d euid = %d\n", ( int )getuid(), ( int )geteuid() );
creat( "/tmp/scz_blah", S_IRWXU );
printf( "Result of setuid( 0 ) = %d\n", setuid( 0 ) );
printf( "Current uid = %d euid = %d\n", ( int )getuid(), ( int )geteuid() );
*ptr = 0;
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------
# gcc -Wall -pipe -O3 -o suidtest suidtest.c
# strip suidtest
# file suidtest
suidtest: ELF 32-位 MSB 可执行 SPARC 版本 1,动态链接,除去
# ls -l suidtest
-rwxr-xr-x 1 root other 4988 6月 29 21:21 suidtest*
# ./suidtest
Current uid = 0 euid = 0
Result of seteuid( 500 ) = 0
Current uid = 0 euid = 500
Result of setuid( 0 ) = 0
Current uid = 0 euid = 0
段错误
# ls -l core
core: 无此文件或目录
#
这个程序应该core dump,但是现在没有core文件产生。注意,此时suidtest仅仅是
自己调用了setuid(),并非被"chmod u+s suidtest"过。有无/etc/system内核可配
置参数改变这种行为。
A: Sun Microsystems 2001-04-11
出于安全考虑,suid程序以及调用setuid()的程序缺省情况下不产生core dump。如
果确实需要产生core dump以便进行调试,修改/etc/system文件并重启系统
* 缺省该值为0,此时禁止suid程序以及调用setuid()的程序core dump
set allow_setid_core = 1
如果想立即生效(不重启系统),可以利用adb命令
# nm -nx /dev/ksyms | grep allow_setid_core
[8347] |0x0000104601e4|0x000000000004|OBJT |GLOB |0 |ABS |allow_setid_core
# echo "allow_setid_core /X" | adb -k /dev/ksyms /dev/mem
# echo "allow_setid_core /W 1" | adb -kw /dev/ksyms /dev/mem
对于Solaris 2.6,需要先打补丁105181-22或更高版本,才能使用上述技术。对于
7及其更高版本的Solaris操作系统,不需要任何补丁。
D: scz
见鬼,在SPARC/Solaris 7上测试了上述技术,无效!
core文件可能包含系统敏感信息,比如shadow片段。而且core文件通常巨大,容易导
致拒绝服务攻击。如果并不在服务器上调试程序或者根本不知道什么是core文件,考
虑在/etc/system文件中增加
set sys:coredumpsize = 0
参看limit(1)手册页了解更多细节。可我怎么利用adb获取这个内核参数的当前值呢?
D: scz
参看<<18.6 如何安装补丁>>,给SPARC/Solaris 2.6打补丁105181-28,并在
/etc/system中设置
set allow_setid_core = 1
重启系统后
# /usr/ccs/bin/nm -nx /dev/ksyms | grep "|allow_setid_core$"
[11740] |0x10428848|0x00000004|OBJT |GLOB |0 |ABS |allow_setid_core
# echo "allow_setid_core /X" | adb -k /dev/ksyms /dev/mem
physmem 3d1f
allow_setid_core:
allow_setid_core: 1
#
注意,在补丁105181-28未打之前,上述两个操作无法完成,没有内核符号
"allow_setid_core"。
# ./suidtest
Current uid = 0 euid = 0
Result of seteuid( 500 ) = 0
Current uid = 0 euid = 500
Result of setuid( 0 ) = 0
Current uid = 0 euid = 0
段错误 (core dumped) <-- 注意这里,core dump产生了
# ls -l core
-rw------- 1 root other 78992 6月 30 01:06 core
# rm core
# echo "allow_setid_core /W 0" | adb -kw /dev/ksyms /dev/mem
physmem 3d1f
allow_setid_core: 0x1 = 0x0
# ./suidtest
Current uid = 0 euid = 0
Result of seteuid( 500 ) = 0
Current uid = 0 euid = 500
Result of setuid( 0 ) = 0
Current uid = 0 euid = 0
段错误 <-- 注意这里,没有产生core dump
# ls -l core
core: 无此文件或目录
#
faint,为什么对于SPARC/Solaris 2.6,打了补丁之后,该技术居然有效。
A: 小四
对于SPARC/Solaris 7,修改/etc/system
set allow_setid_core = 1
结果在boot的时候看到提示信息说这个内核参数已经废弃(obsolte),推荐使用
coreadm(1M),下面是其SPARC/Solaris 7下的man手册页
--------------------------------------------------------------------------
维护命令 coreadm(1M)
名字
coreadm - core文件管理工具
摘要
coreadm [ -g pattern ] [ -i pattern ] [ -d option ... ]
[ -e option ... ]
coreadm [ -p pattern ] [ pid ... ]
coreadm -u
描述
coreadm命令用于指定进程异常结束产生core文件时用什么名字以及
存放在哪里,参看core(4)手册页。
摘要中第一种命令方式只能为超级用户所用,配置结果影响整个系统
范围。包括全局core文件名模板以及针对init(1M)进程的per-process
core文件名模板。所有这些设置在影响当前运行系统的同时,将存入
coreadm的配置文件/etc/coreadm.conf,系统重启时会读取这个配置
文件。参看init(1M)手册页。
普通用户可以执行第二种命令方式,指定per-process core文件名模
板,系统重启后失效。
超级用户才可以执行第三种命令方式,根据配置文件/etc/coreadm.conf
的内容更新系统范围内的设置。启动脚本/etc/init.d/coreadm会执
行该命令。
core文件名模板中可以使用如下变量
%p 进程PID
%u EUID
%g EGID
%f 可执行文件名
%n 系统结点名(uname -n)
%m 机器名(uname -m,比如sun4u)
%t 以十进制表示的time(2)返回值,从UTC 1970-01-01 00:00:00到现
在的秒数
%% %号自身
例如
/var/core/core.%f.%p,程序foo执行后PID为1234,如果产生core文
件,将是/var/core/core.foo.1234
不带任何参数执行coreadm命令,将报告当前系统设置
$ coreadm
global core file pattern: /var/core/core.%f.%p
init core file pattern: core
global core dumps: enabled
per-process core dumps: enabled
global setid core dumps: enabled
per-process setid core dumps: disabled
global core dump logging: disabled
$
带着一系列进程号执行coreadm命令,将顺序报告每个进程的
per-process core文件名模板,比如
$ coreadm 278 5678
278: core.%f.%p
5678: /home/george/cores/%f.%p.%t
$
只有进程属主或者超级用户可以这种方式使用coreadm命令
一个进程core dump时,操作系统可能产生两个core文件,全局core
文件和per-process core文件。
全局core文件的创建模式600,属主是超级用户,非超级用户无法检
查该文件。
per-process core文件创建模式600,属主与进程的credentials相符
合。进程属主可以检查该文件。
缺省情况下suid/sgid程序不允许core dump,避免泄露敏感信息。这
里需要这样理解,
进程最后一次exec(2)之后,假设某条代码即将导致core dump,而到
达该代码之前曾经拥有过超级用户权限,比如setuid-to-root、
setgid-to-root,甚至包括直接以root身份启动进程,中途调用
setuid(2)放弃特权这种情况,为了避免泄露敏感信息,系统缺省将
阻止产生core文件。利用coreadm -e可以允许此时产生core文件,模
式600,属主为超级用户。
选项
-g pattern
设置全局core文件名模板,模板必须以/开始,可以使用%
变量
只有超级用户可以使用该选项
-i pattern
针对init(1M)设置per-process core文件名模板,类似于
coreadm -p pattern 1,区别在于前者的设置即使重启动
也有效,因为会存入/etc/coreadm.conf中。
只有超级用户可以使用该选项
-e option...
打开指定的设置
global 允许core dump,并使用全局core文件名模板
process 允许core dump,并使用per-process core文
件名模板
global-setid
允许suid/sgid程序core dump,并使用全局
core文件名模板
proc-setid
允许suid/sgid程序core dump,并使用
per-process core文件名模板
log 试图产生一个全局core文件时,产生一个
syslog(3)消息
命令行上可以多次指定-e 和 -d选项,只有超级用户可以使用这两个
选项。
-d option...
关闭指定的设置。
-p pattern
设置per-process core文件名模板,可以使用%变量,不
必以/开始。如果未以/开始,以当前目录为根产生core文
件
普通用户使用-p选项时只能针对自己拥有的进程,超级用
户可以指定任意进程。per-process core文件名模板将被
受影响进程的子进程继承。参看fork(2)手册页。
-u 根据配置文件/etc/coreadm.conf更新系统范围的设置。
如果配置文件不存在,或者包含无效值,将使用缺省值。
虽然不建议手工修改/etc/coreadm.conf文件,但还是可
以这么做,然后执行coreadm -u进行同步。
只有超级用户可以使用该选项。
退出码
0 成功完成
1 读取/修改系统配置文件时发生致命错误
2 无效命令行选项
例子
$ coreadm -p core.$f.%p $$
$$ 是当前运行中shell的PID。per-process core文件名模板将被所
有子进程继承。可以在$HOME/.profile或$HOME/.login中增加该命令,
则登录会话中的所有进程都受此设置影响。
$ coreadm -p $HOME/corefiles/%n.%f.%p $$
相关文件
/etc/init.d/coreadm
/etc/coreadm.conf
参看
gcore(1)、init(1M)、exec(2)、fork(2)、setuid(2)、time(2)
syslog(3)、core(4)、attributes(5)
--------------------------------------------------------------------------
现在我们来看这样一个/etc/coreadm.conf文件
--------------------------------------------------------------------------
#
# coreadm.conf
#
# Parameters for system core file configuration.
# Do NOT edit this file by hand -- use coreadm(1) instead.
#
COREADM_GLOB_PATTERN=
COREADM_INIT_PATTERN=core
COREADM_GLOB_ENABLED=no
COREADM_PROC_ENABLED=yes
COREADM_GLOB_SETID_ENABLED=no
COREADM_PROC_SETID_ENABLED=no
COREADM_GLOB_LOG_ENABLED=no
--------------------------------------------------------------------------
回到我们的问题中来,对于SPARC/Solaris 7来说,为了方便调试,执行
coreadm -e proc-setid命令即可。
6.6 消息队列调整
Q: 在/etc/system中如何调整消息队列
A:
消息队列统一使用 msgsys:msginfo_ 前缀。你可以用sysdef获取一些缺省值,还可
以参看/usr/include/sys/msg.h头文件了解更多信息。此外不要忘记<
msgsys:msginfo_msgmap
default 100 max 2147483647
msgsys:msginfo_msgmax
default 2048 max 2147483647
msgsys:msginfo_msgmnb
default 4096 max 2147483647
msgsys:msginfo_msgmni
default 50 max 2147483647
msgsys:msginfo_msgssz
default 8 max 2147483647
msgsys:msginfo_msgtql
default 40 max 2147483647
msgsys:msginfo_msgseg
default 1024 max 32767
D: scz
/usr/include/sys/shm.h
struct shminfo
{
size_t shmmax, /* max shared memory segment size */
shmmin; /* min shared memory segment size */
int shmmni, /* # of shared memory identifiers */
shmseg; /* max attached shared memory */
/* segments per process */
};
在/etc/system文件中,共享内存统一使用 shmsys:shminfo_ 前缀
shmsys:shminfo_shmmax
shmsys:shminfo_shmmin
shmsys:shminfo_shmmni
shmsys:shminfo_shmseg
可用nm -nx /dev/ksyms | grep '|shminfo'进行观察,也可用adb查看当前设置
echo 'shminfo_shmmax/E' | adb -k (SPARC/Solaris 8 64-bit kernel mode)
echo 'shminfo_shmmni/D' | adb -k
/usr/include/sys/sem.h
/*
* Semaphore information structure
*/
struct seminfo
{
int semmap; /* # of entries in semaphore map */
int semmni; /* # of semaphore identifiers */
int semmns; /* # of semaphores in system */
int semmnu; /* # of undo structures in system */
int semmsl; /* max # of semaphores per id */
int semopm; /* max # of operations per semop call */
int semume; /* max # of undo entries per process */
int semusz; /* size in bytes of undo structure */
int semvmx; /* semaphore maximum value */
int semaem; /* adjust on exit max value */
};
在/etc/system文件中,信号灯统一使用 semsys:seminfo_ 前缀
semsys:seminfo_semmap
semsys:seminfo_semmni
semsys:seminfo_semmns
semsys:seminfo_semmnu
semsys:seminfo_semmsl
semsys:seminfo_semopm
semsys:seminfo_semume
semsys:seminfo_semusz
semsys:seminfo_semvmx
semsys:seminfo_semaem
可用nm -nx /dev/ksyms | grep '|seminfo'进行观察,也可用adb查看当前设置
echo 'seminfo_semvmx/D' | adb -k (SPARC/Solaris 8 64-bit kernel mode)
echo 'seminfo_semmni/D' | adb -k
/usr/include/sys/msg.h
/*
* Message information structure.
*/
struct msginfo
{
int msgmap; /* # of entries in msg map */
int msgmax; /* max message size */
int msgmnb; /* max # bytes on queue */
int msgmni; /* # of message queue identifiers */
int msgssz; /* msg segment size (should be word size */
/* multiple) */
int msgtql; /* # of system message headers */
ushort_t msgseg; /* # of msg segments (MUST BE < 32768) */
};
在/etc/system文件中,消息队列统一使用 msgsys:msginfo_ 前缀
msgsys:msginfo_msgmap
msgsys:msginfo_msgmax
msgsys:msginfo_msgmnb
msgsys:msginfo_msgmni
msgsys:msginfo_msgssz
msgsys:msginfo_msgtql
msgsys:msginfo_msgseg
在/etc/system文件中,设置语句形如
set shmsys:shminfo_shmmax = 0x2000000
7. DNS相关问题
7.1 如何进行DNS区传输
A: scz
用nslookup是最普遍适用的:
nslookup
> server ns.tsinghua.edu.cn
> set type=axfr
> ls -d tsinghua.edu.cn [> tsinghua.txt] (方括号里的可选)
有些系统提供了dig命令:
dig @ns.tsinghua.edu.cn axfr tsinghua.edu.cn
A: lgwu
有些系统提供了host命令,这个命令不太保险:
host -l net.tsinghua.edu.cn (后面指定域)
host -l ncic.ac.cn
7.2 如何获知权威名字服务器
Q:
bbs.whnet.edu.cn突然改换IP了,以前是202.112.20.132,现在是202.114.0.248。
我知道应该使用全称域名(FQDN)访问关键服务器,而非使用固定IP。问题在于刚刚改
换IP的那几个小时内,大部分地区的DNS Server因Cache的关系未完成同步,以致即
使我用全称域名(FQDN)访问,仍然指向原来的202.112.20.132。难道真要等待24小时
同步彻底完成吗?
A: scz 2005-03-15
假设你怀疑某个具有合法全称域名(FQDN)的关键服务器突然改换IP,并且客户机周围
的DNS Server未完成同步。此时可以直接向管辖该全称域名的权威名字服务器提交A
记录查询以获取最准确的当前IP。举例如下:
nslookup (在cmd.exe或bash中启动nslookup)
> server
> set type=ns
> whnet.edu.cn (获知管辖该域的权威名字服务器)
Non-authoritative answer:
whnet.edu.cn nameserver = server20.hust.edu.cn
whnet.edu.cn nameserver = dns.whnet.edu.cn
whnet.edu.cn nameserver = sea.whnet.edu.cn
server20.hust.edu.cn internet address = 202.114.0.242
dns.whnet.edu.cn internet address = 202.112.20.131
sea.whnet.edu.cn internet address = 202.112.20.133
> server 202.112.20.131 (切换到权威名字服务器)
Default Server: dns.whnet.edu.cn
Address: 202.112.20.131
> set type=a
> bbs.whnet.edu.cn
Name: bbs.whnet.edu.cn
Address: 202.114.0.248
Q:
一个顺便想到的问题。有时会碰上单IP上多虚拟主机的情形,为访问这些虚拟主机,
不能在URL中使用那个惟一的IP,必须使用具体的FQDN。实际上就是HTTP请求报文中
需要有效指定"Host:"字段。换句话说,对于虚拟主机来讲,这两种请求并不等价:
http://
http://
万一碰上DNS故障或其它原因造成的困境,该FQDN不能解析成有效IP,意味着即使那
台服务器IP未改换、虚拟主机设置未改换,我也无法以WWW形式访问它。
A: scz
作为临时应急手段,可以编辑%systemroot%\system32\drivers\etc\hosts(Windows
用户)或/etc/hosts(Unix用户)文件,在该文件尾部增加如下行:
202.114.0.248 bbs.whnet.edu.cn
对于缺省安装的Unix、Windows系统,hosts文件的优先级高于DNS。此时在浏览器地
址栏中输入http://bbs.whnet.edu.cn,这样发送出去的HTTP请求报文已经有效指定
"Host:"字段,可以成功访问虚拟主机。
当然,bbs.whnet.edu.cn本身不是虚拟主机,不需要这样做,可以直接通过IP访问主
页。
7.3 如何配置DNS的委托解析
Q: 我想把子域DNS解析下放到下面去,在我这里如何配置
A: zhangql@SMTH
子域 IN NS <负责子域DNS解析的IP>
7.4 如何获知BIND的版本号
Q: 如何识别当前运行的bind是什么版本
A: M. Zuber
dig @
或者
nslookup
server
set query=txt
set class=chaos
VERSION.BIND
但是这个返回结果可以通过/etc/named.conf自己设置,并不可靠。如果你正在运
行BIND 8,可以执行
/usr/sbin/ndc status
D: scz
BIND支持如下查询请求,参看src/bin/named/ns_req.c中的req_query()函数
dig @
dig @
A: backend
#! /bin/sh
# bv (Bind Version) script
# written by backend@nsfocus.com
USAGE="Usage: $0 "
if [ $# -ne 1 ] ; then
echo $USAGE
exit
fi
if [ ! -f /usr/bin/dig ]; then
echo -en "\\033[1;31mCan't find \"dig\" program.\\033[0;39m\n\n"
exit
fi
VER=`/usr/bin/dig @$1 VERSION.BIND chaos txt | grep "VERSION.BIND"`
if [ "x$VER" = "x" ]; then
echo -en "\\033[1;31mSorry. Can't get BIND version.\\033[0;39m\n\n"
else
echo -en "BIND version of \\033[1;33m$1\\033[0;39m = "
echo -en "\\033[1;33m"
echo $VER | awk '{print $5;}'
echo -en "\\033[0;39m\n"
fi
A: deepin
很多主机没有dig,最方便的办法是
nslookup -q=txt -class=chaos VERSION.BIND IP-addr
如果要美观一点,所以可以用这样的一个小脚本
#! /bin/sh
if [ $# = 0 ];then echo "useage: $0 IP-Addr."; exit 1;fi
VER=`nslookup -q=txt -class=chaos VERSION.BIND $1 | grep "VERSION.BIND"`
if [ $? = 0 ] ; then
echo -en "BIND version of \\033[1;33m$1\\033[0;39m = " `echo $VER | awk '{print $4,$5,$6;}'` "\\033[0;39m\n"
else
echo -en "\\033[1;31mSorry. Can't get BIND version.\\033[0;39m\n\n"
fi
命令行上直接指定IP,会进行反向域名解析,有可能失败,进入nslookup之后server
指定IP,则无此问题。
Q: Solaris上怎么查看BIND版本号
A: cc@SMTH 2002-04-03 10:33
/usr/sbin/in.named -d 1
然后Ctrl-C退出(如果不存在/etc/named.conf,它会自动退出)。在当前目录下多了
一个文件named.run,其中前几行就有你要的版本号(grep Version named.run)
可以先检查日志确定,grep BIND /var/adm/messages
7.5 Solaris/FreeBSD/Linux如何指定域名解析的顺序
Q: 如何在Solaris中使/etc/resolv.conf的设置生效
A: cp /etc/nsswitch.dns /etc/nsswitch.conf
或者
vi /etc/nsswitch.conf
hosts: files dns
Q: FreeBSD中有类似Solaris的/etc/nsswitch.conf的文件吗
A: /etc/host.conf
--------------------------------------------------------------------------
# First try the /etc/hosts file
hosts
# Now try the nameserver next.
# 如果不希望做反向域名解析,则注释掉下面这行
# bind
# If you have YP/NIS configured, uncomment the next line
# nis
--------------------------------------------------------------------------
Q: Linux中有类似Solaris的/etc/nsswitch.conf的文件吗
D: /etc/host.conf
--------------------------------------------------------------------------
order hosts, bind, nis
multi on
--------------------------------------------------------------------------
D: rai@SMTH Unix 2001-11-28 09:42
改了/etc/host.conf还是不行,后来试了一下/etc/nsswitch.conf就可以了,Linux
也有这个文件的,必须保证下一行中有dns
--------------------------------------------------------------------------
# hosts: db files nisplus nis dns
hosts: files nisplus nis dns
--------------------------------------------------------------------------
8. Solaris编程相关问题
8.0 Solaris多线程编程与errno全局变量
Q: 我正在进行Solaris多线程编程,如果errno是全局变量,则任意线程都可能修改
其值,而我需要判断errno,此时应该注意什么问题。
A: Casper H.S. Dik
使用gcc -D_REENTRANT,此时errno是每个线程相关的,不再是全局变量。当正确地
包含
extern int *___errno();
#define errno (*(___errno()))
这个函数返回一个指针,指向一个线程相关整数。注意,你仍然可以使用&errno。
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状态最大保持时间是2MSL,也就是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。
# /usr/ccs/bin/nm -nx /dev/ksyms | grep tcp_param_arr
[3682] |0x00001048e0e8|0x0000000004f8|OBJT |LOCL |0 |ABS |tcp_param_arr
# skd64 0x00001048e0e8 64
byteArray [ 64 bytes ] ---->
0000000000000000 00 00 03 E8 00 09 27 C0-00 00 03 E8 00 00 00 00
0000000000000010 00 00 00 00 10 48 F0 00-00 00 00 01 FF FF FF FF
0000000000000020 00 00 00 80 00 00 00 00-00 00 00 00 10 48 F0 18
0000000000000030 00 00 00 00 FF FF FF FF-00 00 04 00 00 00 00 00
# echo "tcp_param_arr/X" | adb -kw /dev/ksyms /dev/mem
physmem 3b72
tcp_param_arr:
tcp_param_arr: 3e8
#
注意到下限0x3e8,也就是1000ms,最大0x927c0,也就是600s,10分钟。修改下限为
0,然后就可以用ndd设置当前值为0了。
# 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: rain@bbs.whnet.edu.cn
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中。
D: scz 2003-12-22 12:58
对于Linux,如果存在/proc/sys/net/ipv4/tcp_fin_timeout文件,可以直接修改:
echo 5 > /proc/sys/net/ipv4/tcp_fin_timeout
缺省值是60
A: 小四
写三个脚本自动清除所有TIME_WAIT状态TCP连接:
SPARC/Solaris 8 64-bit kernel mode
--------------------------------------------------------------------------
#! /sbin/sh
ndd /dev/tcp tcp_status | nawk '{print $1 " " $2 " " $16 $17 " " $18}' | \
egrep 'TIME_WAIT' | cut -d' ' -f1 | while read tcpb_addr
do
adb -kw /dev/ksyms /dev/mem << NSFOCUS_EOF
$tcpb_addr+0x30/Z 0t6
$tcpb_addr+0x40/W -6
\$q
NSFOCUS_EOF
done
--------------------------------------------------------------------------
SPARC/Solaris 7 64-bit kernel mode
--------------------------------------------------------------------------
#! /sbin/sh
ndd /dev/tcp tcp_status | nawk '{print $1 " " $2 " " $16 $17 " " $18}' | \
egrep 'TIME_WAIT' | cut -d' ' -f1 | while read tcp_addr
do
adb -kw /dev/ksyms /dev/mem << NSFOCUS_EOF
$tcp_addr+0x50/Z 0t6
$tcp_addr+0x58/W -6
\$q
NSFOCUS_EOF
done
--------------------------------------------------------------------------
SPARC/Solaris 2.6 32-bit kernel mode
--------------------------------------------------------------------------
#! /sbin/sh
/usr/sbin/ndd /dev/tcp tcp_status | nawk '{print $1 " " $2 " " $16 $17 " " $18}' | \
egrep 'TIME_WAIT' | cut -d' ' -f1 | while read tcp_addr
do
adb -kw /dev/ksyms /dev/mem << NSFOCUS_EOF
$tcp_addr+0x28/W 0t6
$tcp_addr+0x2c/W -6
\$q
NSFOCUS_EOF
done
--------------------------------------------------------------------------
8.4 结构在优化编译中的对齐问题
Q: 什么是结构在优化编译中的对齐问题
A: 小四
看这样两个结构定义
struct xxx
{
unsigned char a;
unsigned int b;
};
struct yyy
{
unsigned char a;
unsigned int b;
} __attribute__ ((packed));
或者
#pragma pack(1)
struct yyy
{
unsigned char a;
unsigned int b;
};
#pragma pack()
假设是32-bit编译,则xxx占用8字节,而yyy占用5字节。xxx进行了所谓的结构成员
优化对齐。我们可以定义一个宏来获取xxx和yyy中b成员的偏移量,你会发现这个偏
移对于xxx是4,对于yyy则是1。
#define OFFSETOF(TYPE, MEMBER) ((size_t)&((TYPE)0)->MEMBER)
OFFSETOF( struct xxx *, b ) -> 4
OFFSETOF( struct yyy *, b ) -> 1
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/col ... /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岁>
--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -s -o byte_order byte_order.c
*/
#include
#include
/*
* return value:
* 1 big-endian
* 2 little-endian
* 3 unknow
* 4 sizeof( unsigned short int ) != 2
*/
static int byte_order ( void )
{
union
{
unsigned short int s;
unsigned char c[ sizeof( unsigned short int ) ];
} un;
un.s = 0x0201;
if ( 2 == sizeof( unsigned short int ) )
{
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
{
printf( "sizeof( unsigned short int ) = %u\n", ( unsigned int )sizeof( unsigned short int ) );
return( 4 );
}
return( 3 );
} /* end of byte_order */
int main ( int argc, char * argv[] )
{
printf( "byte_order() = %d\n", byte_order() );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------
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
2004-07-21 15:55 scz
正规用法应该是这样
#include
struct
{
... ...
};
#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: law@APUE 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: 谁支持push/pop这个用法
#pragma pack(push)
#pragma pack(n)
... ...
#pragma pack(pop)
A: law@APUE
我没见过这个写法,VC和GCC都是这样写的
#pragma (push, N) // 把原来align设置压栈,并设新的pack为N
#pragma (pop) // align设置弹栈
假设有如下定义:
int x __attribute__ ((aligned (16))) = 0;
编译器将在16字节边界上分配全局变量x的空间。
8.5 kvm编程举例: 如何编程读取shmsys:shminfo_shmmax的值
Q: 为了避免shmget()不必要的失败,想在C代码中获取shmsys:shminfo_shmmax的值。
但是不能读取/etc/system,那样很不可靠。
set shmsys:shminfo_shmmax = 0x2000000
A:
首先执行如下shell命令
# echo 'shminfo_shmmax/D' | adb -k (SPARC/Solaris 2.6 32-bit kernel mode)
physmem fddb
shminfo_shmmax:
shminfo_shmmax: 134217728
于是我们可以编写如下C代码
--------------------------------------------------------------------------
#include
#include
#include
#include
#include
#include
#define KERN_NAMELIST "/dev/ksyms"
/*
* should not be /dev/kmem
*/
#define KERN_CORE "/dev/mem"
int main ( int argc, char * argv[] )
{
kvm_t * krn = NULL;
struct nlist nms[2];
unsigned int val;
if ( ( krn = kvm_open( KERN_NAMELIST, KERN_CORE, NULL, O_RDONLY, argv[0] ) ) == NULL )
{
exit( -1 );
}
nms[0].n_name = "shminfo_shmmax";
nms[0].n_value = 0;
nms[0].n_type = 0;
nms[1].n_name = NULL;
nms[1].n_value = 0;
nms[1].n_type = 0;
if ( kvm_nlist( krn, nms ) == 0 )
{
if ( nms[0].n_type != 0 )
{
if ( kvm_read( krn, nms[0].n_value, ( char * )&val, sizeof( val ) ) != 4 )
{
fprintf( stderr, "Unable to fetch shminfo_shmmax.\n" );
}
else
{
fprintf( stdout, "shminfo_shmmax = %ld\n", val );
}
}
else
{
fprintf( stderr, "Unable to fetch shminfo_shmmax.\n" );
}
}
kvm_close( krn );
exit( 0 );
} /* end of main */
--------------------------------------------------------------------------
关于kvm_*()系列函数,可以man -s 3k kvm_nlist等等,但是Sun强烈反对利用
kvm_*()函数,几乎没有兼容性、可移植性可言。
个人不推荐在大型应用软件中使用kvm_*()系列函数。有些可配置系统参数可以通过
sysconf(3C)获取。
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.10 如何获取自Unix纪元以来的秒数,如何转换成可理解的表达方式
A: scz
问题是针对shell script以及SLKM编程来的,顺带给一个标准C应用层编程的说明
man -s 2 time(SPARC/Solaris 7)
--------------------------------------------------------------------------
系统调用 time(2)
名字
time - 获取时间
摘要
#include
#include
time_t time ( time_t *tloc );
描述
time()函数返回自UTC 1970-01-01 00:00:00以来的秒数
如果tloc非NULL,返回值同时存放在tloc所对应的空间中。如果tloc指针非法,
time()调用失败。注意,tloc可以为NULL。
返回值
成功则返回秒数
失败则返回( time_t )-1,同时设置errno
属性
异步信号安全的
参看
stime(2) ctime(3C) attributes(5)
--------------------------------------------------------------------------
A: scz
国际标准时间(Coordinated Universal Time)UTC 1970-01-01 00:00:00 称为
Unix Epoch(Unix纪元)。
在Red Hat Linux下
$ date -u --date='20010530 22:48:10' +%s
991234090 <-- 这个就是自Unix纪元以来的秒数
$ perl -le 'print scalar localtime 991234090'
Wed May 30 22:48:10 2001
$
在Solaris 7 64-bit kernel mode下
# netstat -k | grep boot_time
avenrun_5min 1 avenrun_15min 3 boot_time 959048950
# ~~~~~~~~~
这个也是自Unix纪元以来的秒数
# nm -nx /dev/ksyms | grep boot_time
[10161] |0x000010445090|0x000000000008|OBJT |GLOB |0 |ABS |boot_time
# skd64 0x10445090 8
byteArray [ 8 bytes ] ---->
0000000000000000 00 00 00 00 39 29 EC F6
# echo "boot_time /J" | adb -kw /dev/ksyms /dev/mem
physmem 3b72
boot_time:
boot_time: 3929ecf6 <-- 0x3929ecf6 == 959048950
# uptime
下午10:59 运行 8 天 4:31, 4 users, 平均负荷: 0.01, 0.01, 0.01
# date
2000年05月30日 星期二 23时00分12秒 GMT
# perl -le 'print scalar localtime 0x3929ecf6'
Mon May 22 18:29:10 2000
# nm -nx /dev/ksyms | grep "|gtime"
[9618] |0x0000100ed380|0x00000000000c|FUNC |GLOB |0 |ABS |gtime
#
如果要在程序中使用boot_time,可以
extern time_t boot_time; /* netstat -k | grep boot_time */
这个boot_time指明了系统最近一次重启成功的时间,以自Unix纪元以来的秒数表示。
/usr/include/sys/time.h 中定义了
typedef long time_t; /* time of day in seconds */
意味着在Solaris 7 64-bit kernel mode中,time_t是64-bit的,而在Solaris 2.6
中是32-bit的。
下面分别来自7和2.6的源码:
--------------------------------------------------------------------------
/* Solaris 7 */
time_t gtime ( void )
{
return( hrestime.tv_sec );
}
/* Solaris 2.6 */
int gtime ()
{
return( hrestime.tv_sec );
}
--------------------------------------------------------------------------
从SLKM调试信息中看到gtime()返回的是类似0x3934aea4这样的值,显然这是自Unix
纪元开始计的秒数。如果要在自己的SLKM里使用内核函数gtime(),可以这样:
#if SOLARIS2 == 7
extern time_t gtime ( void );
#elif SOLARIS2 == 6
extern int gtime ( void );
#endif
显然我并不推荐直接使用内核变量hrestime.tv_sec,尽管内核输出了hrestime变量。
uptime(1)命令最终也使用了内核函数gtime()。由于Solaris的date命令并不支持
$ date -u --date='20010530 22:48:10' +%s
这种用法是GNU扩展。为了广泛兼容地获取自Unix纪元以来的秒数:
在Solaris 7下
# truss -t time uptime 2>&1 | head -1 | awk '{print $3;}'
959758372 <-- 10进制
# echo c 959758372 16o p c | /usr/bin/dc
3934C024 <-- 16进制
# echo c 16i 3934C024 p c | /usr/bin/dc
959758372
#
在Redhat Linux下
# strace -e trace=time uptime 2>&1 | head -1 | awk '{print $3;}'
991235978
# echo c 991235978 16o p c | /usr/bin/dc
3B150F8A
# echo c 16i 3B150F8A p c | /usr/bin/dc
991235978
#
这篇QA的主要目的是介绍SLKM编程中可以使用gtime()获取当前时间,其他技巧顺带
介绍而已,希望对大家有所启发。
D: 小四
顺便记录一下各进制之间的转换
$ echo c 8 8o p c | /usr/bin/dc
10 <-- 10进制转8进制
$ echo c 17 16o p c | /usr/bin/dc
11 <-- 10进制转16进制
$ echo c 8i 11 12o p c | /usr/bin/dc
9 <-- 8进制转10进制
$ echo c 8i 17 20o p c | /usr/bin/dc
F <-- 8进制转16进制
$ echo c 16i FF Ao p c | /usr/bin/dc
255 <-- 16进制转10进制
$ echo c 16i 10 8o p c | /usr/bin/dc
20 <-- 16进制转8进制
$
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.12 Solaris下究竟如何使用setuid/seteuid/setreuid
Q: 我被setuid/seteuid/setreuid搞疯了,到底怎么使用它们?
D: tt
如果一个Solaris下的程序setuid-to-
而不是setuid(),因为这样最通用。尤其当setuid-to-
根本无法永久放弃特权。
如果一个Solaris下的程序setgid-to-
而不是setgid(),因为这样最通用。尤其当整个过程中无法满足EUID为0的时候,
setgid()根本无法永久放弃特权。
假设一个Solaris下的程序同时setuid、setgid过,较理想的释放顺序应该是先释放
setgid特权,后释放setuid特权。
据tt报告,Linux下setuid()实现和Solaris明显不同,无论如何都同时设置RUID、
EUID、SUID(假设权限允许),注意区分不同系统下系统调用setuid()的不同表现。
D: tt