Unix编程/应用问答中文版

时间:2007-04-18 08:33:35   来源:  作者:  点击:次  出处:技术无忧
关键字:unix FAQ 编程 应用 问答

这种技术要求运行者拥有root权限,否则无法有效获取非自己拥有的进程PID。注意
下面的演示

# ps -f -p 223

UID   PID  PPID  C    STIME TTY      TIME CMD
root   223     1  0   3月 09 ?        0:00 /usr/sbin/vold

# ./getpid /usr/sbin/vold  <-- 这个用法无法找到匹配
# ./getpid vold            <-- 只能匹配相对路径
[ vold ] is: <223>

当然你可以自己修改、增强程序,使之匹配各种命令行指定,我就不替你做了。上述
程序在32-bit kernel的Solaris 2.6和64-bit kernel的Solaris 7上均测试通过。

D: microcat <rotm@263.net>

在介绍第二种办法之前,先看一下microcat提供的这个程序

--------------------------------------------------------------------------
/*
* gcc -Wall -DSOLARIS=6 -O3 -o listpid listpid.c -lkvm
*
* /opt/SUNWspro/SC5.0/bin/cc -xarch=v9 -DSOLARIS=7 -O -o listpid listpid.c -lkv
m
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <kvm.h>
#include <fcntl.h>

int main ( int argc, char * argv[] )
{
   kvm_t *       kd;
   struct proc * p;
   struct pid    pid;

   if ( ( kd = kvm_open( NULL, NULL, NULL, O_RDONLY, NULL ) ) == NULL )
   {
       perror( "kvm_open" );
       exit( EXIT_FAILURE );
   }
   while ( ( p = kvm_nextproc( kd ) ) )  /* 遍历P区 */
   {
#if SOLARIS == 7
       if ( kvm_kread( kd, ( uintptr_t )p->p_pidp, &pid, sizeof( pid ) ) < 0 )
#elif SOLARIS == 6
       if ( kvm_kread( kd, ( unsigned long )p->p_pidp, ( char * )&pid, sizeof(
pid ) ) < 0 )
#endif
       {
           perror( "kvm_kread" );
       }
       else
       {
           printf( "PID: %d\n", ( int )pid.pid_id );
       }
   }  /* end of while */
   kvm_close( kd );
   exit( EXIT_SUCCESS );
}  /* end of main */
--------------------------------------------------------------------------

A: Andrew Gierth <andrew@erlenstar.demon.co.uk>

第二种办法是使用kvm_*()函数

--------------------------------------------------------------------------
#define _KMEMUSER  /* 必须定义这个宏 */

#include <stdio.h>
#include <stdlib.h>
#include <regexpr.h>
#include <sys/proc.h>
#include <kvm.h>
#include <fcntl.h>

/*
static void argv_free ( char ** argv )
{
   size_t i;
   for ( i = 0; argv[i] != NULL; i++ )
   {
       free( argv[i] );
       argv[i] = NULL;
   }
   free( argv );
}
*/

static pid_t getpidbyname ( char * name, pid_t skipit )
{
   kvm_t *       kd;
   int           error;
   char **       argv   = NULL;
   char *        p_name = NULL;
   pid_t         pid    = -1;
   char          expbuf[256];
   char          regexp_str[256];
   struct user * cur_user;
   struct proc * cur_proc;
   struct pid    p;

   sprintf( regexp_str, "^.*%s$", name );
   if ( compile( regexp_str, expbuf, expbuf + 256 ) == NULL )  /* 正则表达式 */
   {
       perror( "compile" );
       return( -1 );
   }
   if ( ( kd = kvm_open( NULL, NULL, NULL, O_RDONLY, NULL ) ) == NULL )
   {
       perror( "kvm_open" );
       return( -1 );
   }
   while ( ( cur_proc = kvm_nextproc( kd ) ) )  /* 遍历P区 */
   {
#if SOLARIS == 7
       if ( kvm_kread( kd, ( uintptr_t )cur_proc->p_pidp, &p, sizeof( p ) ) < 0
)
#elif SOLARIS == 6
       if ( kvm_kread( kd, ( unsigned long )cur_proc->p_pidp, ( char * )&p, siz
eof( p ) ) < 0 )
#endif
       {
           perror( "kvm_kread" );
           continue;
       }
       pid = p.pid_id;
       if ( ( cur_user = kvm_getu( kd, cur_proc ) ) != NULL )
       {
           /* fprintf( stderr, "cur_proc = %p cur_user = %p\n", cur_proc, cur_u
ser ); */
           error = kvm_getcmd( kd, cur_proc, cur_user, &argv, NULL );
           /*
            * fprintf( stderr, "[ %s ] is: <%u>\n", cur_user->u_comm, ( unsigne
d int )pid );
            *
            * 比如in.telnetd、syslogd、bash、login
            */
           if ( error == -1 )  /* 失败,比如argv[]已经被进程自己修改过 */
           {
               if ( cur_user->u_comm[0] != '\0' )
               {
                   p_name = cur_user->u_comm;  /* 从另外一个地方获取信息 */
               }
           }
           else  /* 成功 */
           {
               /*
                * fprintf( stderr, "[ %s ] is: <%u>\n", argv[0], ( unsigned int
)pid );
                *
                * 比如-bash、login、in.telnetd、/usr/sbin/syslogd
                */
               p_name = argv[0];
           }
       }
       if ( p_name )
       {
           if ( ( strcmp( p_name, name ) == 0 ) || step( p_name, expbuf ) )
           {
               if ( skipit != -1 && pid == skipit )  /* -1做为无效pid对待 */
               {
                   pid = -1;
               }
               else  /* 找到匹配,返回pid */
               {
                   break;  /* 跳出while循环 */
               }
           }
       }
       if ( argv != NULL )
       {
           /* argv_free( argv ); */
           free( argv );
           argv = NULL;
       }
       p_name = NULL;  /* 必须增加这条,否则流程有问题 */
   }  /* end of while */
   if ( argv != NULL )
   {
       /* argv_free( argv ); */
       free( argv );
       argv = NULL;
   }
   kvm_close( kd );
   return( pid );
}  /* end of getpidbyname */

static void usage ( char * arg )
{
   fprintf( stderr, " Usage: %s <proc_name>\n", arg );
   exit( EXIT_FAILURE );
}  /* end of usage */

int main ( int argc, char * argv[] )
{
   pid_t pid;

   if ( argc != 2 )
   {
       usage( argv[0] );
   }
   pid = getpidbyname( argv[1], -1 );
   if ( pid != -1 )
   {
       fprintf( stderr, "[ %s ] is: <%u>\n", argv[1], ( unsigned int )pid );
       exit( EXIT_SUCCESS );
   }
   exit( EXIT_FAILURE );
}  /* end of main */
--------------------------------------------------------------------------

这个程序同样必须以root身份运行,在SPARC/Solaris 2.6/7上测试通过

13.4 Solaris 7/8下ps输出中的问号

Q: 比如ps -el的输出中有很多问号,可我觉得它们应该有一个确定的值

A: Michael Shapiro <mws@poptart.Sun.Com>

  有些时候ps(1)输出的单行过于长了,为了输出美观,某些列的值用问号代替,尤
  其64-bit内核下ADDR列。可以用-o参数指定要显示的列,比如

  # ps -o pid,tty,addr,wchan,fname -p $$
  PID TT                  ADDR            WCHAN COMMAND
  2602 pts/4        30000a154b8      30000a15578 bash
  # ps -e -o pid,tty,addr,wchan,fname

13.7 给定一个PID,如何知道它对应一个运行中的进程

A: Andrew Gierth <andrew@erlenstar.demon.co.uk>

这个回答来自著名的<<Unix Programming FAQ ver 1.37>>,由Andrew Gierth负责维
护,其它细节请参看原文。

kill( pid, 0 ),此时有四种可能的返回值

1) kill()返回0

   意味着指定PID的确对应着一个运行中的进程,系统允许你向该进程发送信号。
   至于该进程能否是zombie process(僵尸进程),是系统相关的。

2) kill()返回-1,errno == ESRCH

   指定PID并不对应一个运行中的进程,或者权限不够无法完成判断。某些系统上,
   如果对应进程是僵尸进程时,也如此返回。

3) kill()返回-1,errno == EPERM

   系统不允许你kill指定进程,进程存在(可能是zombie),权限不够。

4) kill()返回-1,errno是其它值

   你麻烦来了(嘿嘿)

最有用的技术,假设成功表示进程存在,EPERM失败也表示进程存在,其它失败表示
指定PID不对应一个运行中的进程。

此外如果系统支持proc伪文件系统,检查/proc/<pid>是否存在,存在表明指定PID对
应运行中的进程。

13.8 Unix/Linux编程中所谓"僵尸进程"指什么

Q: Unix/Linux编程中所谓"僵尸进程"指什么,什么情况下会产生僵尸进程,如何杀
  掉僵尸进程。

A: 在fork()/execve()过程中,假设子进程结束时父进程仍存在,而父进程fork()之
  前既没安装SIGCHLD信号处理函数调用waitpid()等待子进程结束,又没有显式忽
  略该信号,则子进程成为僵尸进程,无法正常结束,此时即使是root身份kill -9
  也不能杀死僵尸进程。补救办法是杀死僵尸进程的父进程(僵尸进程的父进程必然
  存在),僵尸进程成为"孤儿进程",过继给1号进程init,init始终会负责清理僵
  尸进程。

13.9 x86/FreeBSD 4.3-RELEASE的ptrace(2)手册页

A: scz <scz@nsfocus.com>

下面来看一个简单的ptrace(2)演示,x86/FreeBSD 4.3-RELEASE

--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -o target target.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>

int main ( int argc, char * argv[] )
{
   write( STDERR_FILENO, "Hello world\n", 12 );
   return( EXIT_SUCCESS );
}  /* end of main */
--------------------------------------------------------------------------

--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -o ptracetest ptracetest.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <errno.h>

int main ( int argc, char * argv[] )
{
   pid_t p;

   p = fork();
   if ( p < 0 )
   {
       perror( "fork error" );
       exit( EXIT_FAILURE );
   }
   else if ( p == 0 )
   {
       /*
        * 子进程
        */
       errno = 0;
       ptrace( PT_TRACE_ME, 0, 0, 0 );
       if ( errno != 0 )
       {
           perror( "child process ptrace error" );
           exit( EXIT_FAILURE );
       }
       else
       {
           char * name[2];

           name[0] = "./target";
           name[1] = NULL;
           /*
            * 切换进程映像时停止执行
            */
           execve( name[0], name, NULL );
           perror( "child process execve error" );
           exit( EXIT_FAILURE );
       }
   }
   else
   {
       /*
        * 父进程
        */
       fprintf( stderr, "Having a child process <%d>\n", ( int )p );
       /*
        * 阻塞式waitpid()
        */
       waitpid( p, NULL, 0 );
       fprintf( stderr, "Now in parent process, "
                        "please enter [CR] to continue ... ...\n" );
       getchar();
       errno = 0;
       ptrace( PT_CONTINUE, p, ( caddr_t )1, 0 );
       if ( errno != 0 )
       {
           perror( "parent process ptrace error" );
           exit( EXIT_FAILURE );
       }
       /*
        * 作为ptrace(2)演示,这里必须等待子进程先结束,否则由于父进程终止
        * 而杀死子进程
        */
       fprintf( stderr, "Waiting the child process terminate ... ...\n" );
       getchar();
   }
   return( EXIT_SUCCESS );
}  /* end of main */
--------------------------------------------------------------------------

13.10 Solaris下如何知道哪个进程使用了哪个端口

Q: netstat -na -P tcp告诉我哪些端口是打开的,但它没有报告是哪个进程打开的。
  lsof可以满足我的需求,可我不想用lsof,它不是缺省安装的

D: FreeBSD 4.3-RELEASE中

  netstat -s -p tcp 查香tcp协议的统计量

  netstat -na | grep tcp4 才能达到类似Solaris下netstat -na -P tcp的效果

  FreeBSD 4.4-RELEASE中

  netstat -na -p tcp效果类似于Solaris下netstat -na -P tcp

A: Vitaly Filatov <vitaly@royint.com> & scz <scz@nsfocus.com>

对于Solaris 8,可以使用这个演示脚本,如果不能满足你的需要,请自行修改

--------------------------------------------------------------------------
#! /bin/sh
# find_socket_proc.sh for x86/SPARC Solaris 8

#
# File     : find_socket_proc.sh
# Author   : Vitaly Filatov <vitaly@royint.com>
# Fix      : scz <scz.nsfocus.com>
# Platform : x86/SPARC Solaris 8
# Version  : 1.00 aleph
# Usage    :
# Date     : 2001-10-28 00:32
# Modify   :
#

PLATFORM="`uname -p`"
if [ "${PLATFORM}" = "sparc" ] ; then
   PREFIX=""
elif [ "${PLATFORM}" = "i386" ] ; then
   PREFIX="/usr"
fi

EGREP="${PREFIX}/bin/egrep"
NAWK="${PREFIX}/bin/nawk"
PFILES="/usr/proc/bin/pfiles"
PS="${PREFIX}/bin/ps"
SED="${PREFIX}/bin/sed"

PROCLIST="`${PS} -ef | ${NAWK} 'NR > 1 {print $2}'`"

for PID in ${PROCLIST} ; do
   if [ -n "`${PFILES} ${PID} 2>/dev/null | ${EGREP} S_IFSOCK`" ] ; then
       LINE_1="`${PS} -o pid,args -p ${PID} | ${NAWK} 'NR > 1 {print $0}'`"
       PORTLIST="`${PFILES} ${PID} 2>/dev/null | ${EGREP} 'sockname:' | \
                ${SED} -e 's/.*port: \(.*\)/\1/g'`"
       for PORT in ${PORTLIST} ; do
           echo "${LINE_1} port-->${PORT}"
       done
   fi
done
--------------------------------------------------------------------------

如果你以普通用户身份运行,只能检查自己的进程,如果以root身份运行,可以检查
所有用户的进程。

13.11 x86/FreeBSD如何快速获取指定用户拥有的进程数

Q: 谁能给我一段C代码,快速统计出一个指定用户所拥有的进程数。我想修改Apache
  以阻止它超过kern.maxprocperuid限制后继续fork()产生新进程。如果Apache以
  sudo方式启动,就可能出现这种情况。我该看ps(1)的源代码吗?

A: Maxim Konovalov <maxim@macomnet.ru>

参看src/usr.bin/killall/killall.c,这里用了sysctl()接口

A: Andrew <andrew@ugh.net.au>

可以试试kvm_getprocs( KERN_PROC_UID )

14.3 只在本地文件系统上查找

Q: 我不想在NFS AUTOFS CACHEFS这些文件系统上查找文件

A: Sun Microsystems 2001-02-12

下面举例演示如何不在NFS文件系统上查找文件

# find / -name su -type f -print -o -fstype nfs -prune

下例表示不在PROC文件系统中查找文件(参看/etc/vfstab文件)

# find / -name su -type f -print -o -fstype proc -prune

15. 32-bit/64-bit相关问题

15.1 Solaris下如何识别当前内核版本

Q: 我编写了一个内核模块,在Solaris 7/8下编译通过,模块中如何识别当前正在运
  行内核版本

A: Andrew Gabriel

  参看adb使用的宏"utsname",32-bit的位于usr/lib/adb/utsname,此外可以参看
  /usr/include/sys/utsname.h文件,但是这些不是DDI/DKI兼容的。

  sysdef | more 看看

A: scz <scz@nsfocus.com>

非DDI/DKI兼容意味着丧失良好的可移植性,未来版本的Solaris可能不再提供相应的
支持,但是就Kernel Hacking而言是可以一试的。

adb -P 'sun>' -k /dev/ksyms /dev/mem

sun>$</usr/lib/adb/sparcv9/utsname  <-- 使用64-bit宏

utsname:
utsname:        sys     SunOS
utsname+0x101:  node    sun27
utsname+0x202:  release 5.7
utsname+0x303:  version Generic_106541-08
utsname+0x404:  machine sun4u
sun>$q

cat /usr/lib/adb/sparcv9/utsname

utsname+0=""       <-- 指定当前地址.为utsname
+/"sys"8t257c      <-- t表示tab,c表示以字符方式显示1个字节
+/"node"8t257c
+/"release"8t257c  <-- +表示当前地址.递增
+/"version"8t257c
+/"machine"8t257c

more /usr/include/sys/utsname.h

#define _SYS_NMLN 257  /*
                       * 4.0 size of utsname elements
                       * Must be at least 257 to
                       * support Internet hostnames.
                       */
#define SYS_NMLN  _SYS_NMLN

struct utsname
{
   char sysname[_SYS_NMLN];
   char nodename[_SYS_NMLN];
   char release[_SYS_NMLN];
   char version[_SYS_NMLN];
   char machine[_SYS_NMLN];
};

显然adb下的命令是针对struct utsname结构来的。简单地truss uname -a,可以看
到如下输出

ioctl(1, TCGETA, 0xFFBEE5DC)                    = 0
sysinfo(SI_ARCHITECTURE, "sparc", 257)          = 6
sysinfo(SI_PLATFORM, "SUNW,Ultra-5_10", 257)    = 16

暂时没有跟踪这几个系统调用在做什么,想必类似/usr/lib/adb/sparcv9/utsname宏。

15.2 如何启动Solaris 32-bit/64-bit内核

Q: Algos@Unix 水木清华 2001-12-04 18:12

对于UltraSPARC-I/Solaris 8,可以修改/platform/sun4u/boot.conf文件,使操作
系统运行在64位或者32位,但对于其他机型呢,比如E420、E450、E4500之类服务器
怎么改?好像起来后就是64位的,怎么才能改成32位的呢?

Q: 显然有一些32-bit驱动程序以及一些应用软件不能工作在64-bit内核下,比如gcc
  编译的32-bit IP Filter。我必须启动到32-bit内核模式下,怎么办?

A: dkoleary@mediaone.net

32-bit : ok boot disk kernel/unix
64-bit : ok boot disk kernel/sparcv9/unix

为了设置成缺省启动内核模式

32-bit : ok setenv boot-file kernel/unix
64-bit : ok setenv boot-file kernel/sparcv9/unix

为了确定你所启动的内核模式

isainfo -b

根据你所启动的内核模式,该命令分别返回32、64

A: Will Wang <willcyw@kimo.com.tw> 2001-06-09 02:17

一个办法就是启动时按Stop-A进入OK模式,输入

ok> setenv boot-file kernel/unix
ok> boot

另一个办法是已经在shell状态下了,执行命令

# eeprom "boot-file=kernel/unix"

系统重启之后将自动加载32-bit内核

15.3 gcc支持64-bit编译吗

Q: gcc -v显示版本2.95.2,isainfo -kv显示64-bit sparcv9 kernel modules,我
  企图通过指定"-mcpu=v9 -m64"获得64-bit代码,提示m64未被支持,仅仅指定
  mcpu=v9,在汇编阶段报告""v8 can't generate v9 code",我使用的汇编器是
  /usr/ccs/bin/as,随Solaris 7/8提供的。

  这是什么问题,我需要一个64-bit汇编器吗,从哪里获取呢?

A: Robert Banniza <robert@rootprompt.net>

  我并不认为gcc 2.95.2已经开始支持64-bit编译模式,或许你应该考虑采用
  Sun WorkShop Compiler SPARC 5.0/6.0。

15.4 Solaris启动时内核文件找不到了

Q: 我的Solaris 7莫名其妙死机了,只好关电源,再开,发现错误
  boot with command:boot now
  cann't open now
  enter filename[now]:
  怎么办

A: dkoleary@mediaone.net

启动时按Stop-A进入ok状态,在这里输入

32-bit : ok boot disk kernel/unix
64-bit : ok boot disk kernel/sparcv9/unix

为了设置成缺省启动内核模式

32-bit : ok setenv boot-file kernel/unix
64-bit : ok setenv boot-file kernel/sparcv9/unix

A: liqun.bbs@bbs.gznet.edu.cn

试试这个,启动时按Stop-A进入OK状态

OK> setenv boot-file kernel/unix
OK> reset

15.5 64-bit驱动程序无法在8下关联,但在7下工作正常

Q: 一个64-bit驱动程序在Solaris 7下加载、关联(load & attach)成功,但在8下加
  载(load)成功、关联(attach)失败。

A: Sun Microsystems 1998-06-13

从Solaris 8开始,64-bit驱动程序必须位于"sparcv9/"目录中。而在Solaris 7中,
尽管不提倡,但即使64-bit驱动程序不在"sparcv9/"目录中,也可以加载并关联成功。

16. 库相关问题

16.1 在Solaris 7下编写网络程序需要链接哪些库

Q: inet_pton()是什么库里的,为什么man手册里无对应内容

A: scz <scz@nsfocus.com>

这个函数比较新,还有另外几个,比如inet_ntop()。关于它们的详细介绍参看
<<Unix Network Programming>> 3.7 小节。文件/usr/include/arpa/inet.h中定义
有:

extern int inet_pton ( int, const char *, void * );

用/usr/ccs/bin/nm工具观察三个动态链接库libresolv.so、libsocket.so、
libnsl.so提供的全局函数

显然,如果涉及RPC编程,必然需要libnsl.so,而inet_pton()来自libresolv.so。
总结一下,实在不能确定的时候,编译时指定链接开关如下:

-lsocket -lnsl -lresolv

16.2 SUID设置和LD_LIBRARY_PATH环境变量

Q: RedHat Linux 6.1/6.2,C编程,还有一些脚本

  execl()以及其他exec...()执行一个SUID程序的时候,出于安全考虑,会清除
  LD_LIBRARY_PATH环境变量,仅仅依靠系统全局设置搜索共享库。参看如下URL

  http://spdoc.pdc.kth.se/doc_link/C/a_doc_lib/libs/basetrf1/exec.htm

  现在有一个程序,需要一个正确的LD_LIBRARY_PATH环境变量设置才能运行,可是
  由于某些原因必须做SUID设置,结果最终运行失败。我尝试在程序中putenv()、
  setenv(),失败,显然LD_LIBRARY_PATH环境变量需要在程序加载过程中由动态链
  接器使用,程序中的putenv()、setenv()为时已晚。

  于是我写了一个脚本,在脚本中设置LD_LIBRARY_PATH环境变量,调用C程序,对
  脚本做SUID设置。但是脚本的SUID设置并没有传递给子进程(这里就是那个C程序)

A: Paul Sack <paul-sackun@jefe.eyep.net>

www.google.com用"suid shell scripts race conditions"进行搜索,查看
BugTraq相关讨论。安全的解决办法是用C写一个SUID WRAPPER去exec...()你的C程序,
在SUID WRAPPER中设置LD_LIBRARY_PATH环境变量。

A: Andrew Gierth <andrew@erlenstar.demon.co.uk>

如果一个程序是SUID过的,将导致LD_LIBRARY_PATH环境变量被忽略,但是这不是问
题本质所在,本质原因在于ruid不等于euid(或者rgid不等于egid)。所以wrapper中
仅仅重置环境变量是不够的,必须想办法修改ruid等于euid。最好还是重新编译程序,
使之不依赖于LD_LIBRARY_PATH环境变量。

16.3 链接过程中库的顺序

Q: 有几个库文件A.a、B.a、common.a,前两者用到了定义在后者中的例程,如果把
  common.a放在前面,链接器报告存在无法解析的符号名,放在最后则无问题。

A: Floyd Davidson <floyd@ptialaska.net>

  链接器按照命令行上指定顺序搜索库文件和目标文件(.a .o),二者之间的区别在
  于.o文件被全部链接进来,而只从库文件中析取所需模块,仅当某个模块可以解
  析当前尚未成功解析的符号时,该模块被析取后链接进来。如果库文件无法解析
  任何当前尚未成功解析的符号,不从中析取也不发生链接。

  Unix编程新手的常见问题是数学函数并不在标准C库中,而是在libm.a中

  cc -lm foo.c

  这里foo.c用到了数学库中的符号,但是链接器无法正确解析。当搜索到libm.a时,
  来自foo.c的数学函数符号尚未出现,因此不需要析取libm.a的任何模块。接下来
  foo.o链接进来,增加了一批尚未成功解析的符号,但已经没有libm.a可供使用了,
  因此数学库必须在foo.o之后被搜索到。

  cc foo.c -lm

  在你的问题中,如果common.a首先被搜索到,因为不匹配尚未成功解析的符号,
  而被丢弃。结果A.a和B.a真正链接进来的时候,已经没有库可以解析符号了。

16.6 /usr/lib/ld.so.1损坏或丢失

Q: 意外地覆盖了ld.so.1,幸运的是有一个原始备份,可我没有一个静态链接版本的
  命令去恢复它。

Q: 我在Solaris 2.6中做了"mv /usr/lib /usr/lib1",本意是想使用自己的库,但
  是现在所有程序都报告"找不到/usr/lib/ld.so.1",怎么办

A: scz <scz@nsfocus.com>

不要重启动,立即用/usr/sbin/static/mv、/usr/sbin/static/cp命令恢复

# ls /usr/sbin/static
cp*   ln*   mv*   rcp*  tar*
#

Q: 那如果此时/usr被改名了,怎么办?

A: faint,谁这么变态。假设/usr改名成了/faint,

1) /faint/sbin/static/cp /faint/sbin/static/mv /tmp/mv
2) /tmp/mv /faint /usr

我不确定

1) /faint/sbin/static/mv /faint /usr

能否成功,你可以自己测试一下效果。或者

ok boot cdrom -s (放入启动安装光盘)
mount /dev/dsk/c0t0d0s0 /mnt (这里指定原根区对应的原始设备名)
mv /mnt/faint /mnt/usr

D: cirrus@SMTH

建议把/usr/sbin/static下的东西拷一份到/sbin下或者其它比较可信的跟/在同一个
fs的目录下。装机器的时候,不管什么OS,/usr都是单独一个fs的。

16.9 Solaris 8下如何配置运行时链接环境

Q: 在Linux下我知道用ldconfig(8)配置运行时链接环境,但是在Solaris 8下呢

A: <cypher@punk.net>

你总是可以利用 LD_LIBRARY_PATH 环境变量,对于Solaris 8,还可以参看crle(1)
手册页。

A: Logan Shaw <logan@cs.utexas.edu>

如果在链接时使用了"-R"和"-L"选项,则相关动态库的路径将保存在ELF文件中,于
是以后的运行中不再需要设置环境变量去定位动态库。比如,有一个
/usr/local/lib/libfoo.so,而你的bar程序需要这个libfoo.so,编译、链接时最好
这样

gcc -Wall -pipe -O3 -o bar -R/usr/local/lib -L/usr/local/lib bar.c -lfoo

17. 文件查看问题

17.1 如何直接查看man文件

A: scz <scz@nsfocus.com>

下面几种方法都可以

/bin/nroff -man <your man doc> | more -s
groff -Tlatin1 -mandoc <your man doc> | less
groff -s -p -t -e -Tascii -mandoc <your man doc> | less

有less的时候建议使用less,而不是more。

在Linux下更简单,不用这样麻烦,比如

ls /usr/man/man5/nologin.5.gz
man /usr/man/man5/nologin.5.gz
man /usr/man/man1/finger6.1

无论什么系统,总是可以利用MANPATH环境变量的

$ mkdir man1
$ cp /usr/man/man1/proc.1 man1
$ man -s 1 -M . proc

17.2 .tex文件怎么读

A: shuoshu.bbs@bbs.whnet.edu.cn

用 latex *.tex 编译生成dvi文件,然后用 xdvi 看

17.3 Solaris下怎么看.ps文件

A: lose@水木清华 Unix

/usr/dt/bin/sdtimage *.ps

18. 补丁相关问题

18.1 如何根据补丁号从Sun主站下载补丁

Q: 已经知道补丁号,可我如何从Sun主站下载这个补丁呢。有些补丁不是安全补丁,
  Sun公司并未在主站上提供非安全补丁的下载链接。

A: scz <scz@nsfocus.com>

首先去http://sunsolve.sun.com申请一个帐号(免费),其次假设补丁号是107589,
参看如下链接

http://sunsolve.sun.com/private-cgi/retrieve.pl?type=0&doc=patches/107589

wget -O 107589.tar.Z http://username:password@sunsolve.sun.com/private-cgi
/patchDownload.pl?target=107589&method=h

然后用"file 107589.tar.Z"命令确认这个文件格式,选择不同的解压工具。补丁版
本号在压缩包内有体现。这个方法适用于所有补丁,包括非安全补丁。这样下载获得
的补丁是最新版。

如果你不但知道补丁号,还知道补丁版本号,也可以这样下载

wget http://username:password@sunsolve.sun.com/private-cgi
/patches/107589-06.zip

一般只提供最新版补丁,旧版补丁将被删除。

18.5 已知补丁号,如何最快判断系统中是否已经安装该补丁

A: scz <scz@nsfocus.com>

# showrev -p | grep "^Patch: 105181"
Patch: 105181-05 Obsoletes: 105636-01, 105776-01 Requires: ...
#

18.6 如何安装补丁

A: scz <scz@nsfocus.com>

mkdir -p /tmp/patch

cd /tmp/patch

wget -O 105181.tar.Z http://...

wget -O 106429.tar.Z http://...

zcat 105181.tar.Z | tar xvf -
zcat 106429.tar.Z | tar xvf -
rm 105181.tar.Z
rm 106429.tar.Z

patchadd -M /tmp/patch 106429-02 105181-28

检查补丁安装日志

/var/sadm/patch/106429-02/log
/var/sadm/patch/105181-28/log

cd /
rm -rf /tmp/patch

# showrev -p | grep "^Patch: 105181"
# init 6 (重启系统)

19. 终端相关问题

19.1 如何使Backspace键做删除操作,而不是显示^H

Q: Backspace键并未删除光标左面那个字符,仅仅显示^H,而DEL键完成了删除操作

A: Sun Microsystems 2001-03-08

执行"stty -a"将看到"erase = ^?",表示此时DEL键对应删除操作。

如果正在使用xterm,可以用"tset"命令设置控制字符对应的操作。其他窗口中,假
设目前使用/sbin/sh,尝试

$ stty erase ^H

这里^H的输入是Ctrl-H,某些时候可能需要Ctrl-V、Ctrl-H输入,还可以尝试

$ stty erase "^h"
$ stty erase "^H"  (大小写不敏感)

这里输入"^H",就是两个字符,一个^,一个H。

同样,如果想恢复到DEL删除

$ stty erase ^?

这里^?的输入是DEL,某些时候可能需要Ctrl-V、DEL输入,还可以尝试

$ stty erase "^?"

这里输入"^?",就是两个字符,一个^,一个?。

为了永久保留这个设置,在所使用的shell初始化文件中增加设置命令,比如c shell
的".cshrc",其他shell的".login"。

19.3 如何清空stdin的缓冲

A: law@apue.dhs.org

stdin->_IO_read_ptr = stdin->_IO_read_end;

不过这个办法实在不怎么样。一是只对glibc有效,不可移植。二是违背流的思想,
老老实实用fgets()好了。

19.4 Linux Console下一按错键就叫,怎么关

A: windtear@bbs.tsinghua.edu.cn Linux版

有个1050110 背一下就可以了

echo -e "\\33[10;50]\\33[11;0]"

             10 50      11 0

放到那些登录言启动脚本里

Q: 输完命令后是没声了,可从KDE回来之后又有了,请问能彻底关掉吗

A: TheCool@bbs.tsinghua.edu.cn Linux版

setterm -blength 0 -bfreq 0

20. shell script问题

20.1 如何获取一个字符串的长度

A: Andrei Ivanov <iva@racoon.riga.lv>

expr `echo $string | wc -c` - 1

echo $string | awk '{ print length( $0 ); }'

/usr/ucb/expr length "$string"

expr "$string" : ".*"

echo "$string" | sed 's/./1+/g;s/+/ /;s/$/p/' | dc

A: http://www.linuxforum.net

假设是bash

$ string='1234567890'
$ echo ${#string}
10
$

20.2 读超时自动使用缺省值

Q: shell script编程,不介入expect、perl、tcl等类似工具。读等待60秒,超时则
  自动使用缺省值。可以使用系统缺省外部命令,要求能广泛移植在常用Unix平台
  上

A: CERNET 华中地区网络中心 PUE(UNIX环境程序设计)版 lookout

参看comp.unix.shell新闻组,下面以SPARC/Solaris 2.6为例

--------------------------------------------------------------------------
#! /sbin/sh
stty -icanon min 0 time 255
while true
do
   /usr/bin/echo "Press a key or press ENTER to exit:\c"
   read key
   if [ "$key" = "" ] ; then
       echo "\nYou press Enter or timeout"
       break
   else
       echo "You press the key $key"
   fi
done
stty sane
--------------------------------------------------------------------------

20.4 BASH中如何得到一个字符串的子串

A: loginlog@SMTH

BASH 2.0.3 以上版本

${var:offset:length}

20.8 使用tr命令加密文件

A: 水木清华 TheCool

著名的 rot13 密码, 通过把字母移动13个位置实现对文本的加密

tr "[a-m][n-z][A-M][N-Z]" "[n-z][a-m][N-Z][A-M]" < message > newmessage

然后可以用同样的命令进行解密

tr "[a-m][n-z][A-M][N-Z]" "[n-z][a-m][N-Z][A-M]" < newmessage > message

20.9 有哪些命令用于查找定位

A: 小四

type -a telnet
whereis telnet
which telnet
whatis telnet <=> man -k telnet

20.11 如何将大写文件名转换为小写文件名

A: 小四 <cloudsky@263.net>

如果要处理整个目录树的话,可以这样

find <target_dir> -exec sh -c 'mv -f "$0" `echo "$0" | tr "[A-Z]" "[a-z]"` > /de
v/null 2>&1' {} \;

同理,将小写文件名转换为大写文件名如下

find <target_dir> -exec sh -c 'mv -f "$0" `echo "$0" | tr "[a-z]" "[A-Z]"` > /de
v/null 2>&1' {} \;

这个办法有待修正,处理多层目录名本身带有大写字母的情况,有问题。比如存在如
下目录的时候,./A/B/C/D.txt。

A: Potash@www.linuxforum.net 2002-02-05 18:58

--------------------------------------------------------------------------
#! /bin/sh
# Usage: ./loworup.sh <-l | -u> <target_directory>

#
# 第二形参必须是目录,第一形参指定-l或-u
#
if [ $# -ne 2 ] ; then
   echo "Usage: ${0} <-l | -u> <target_directory>"
   exit 1
fi

if [ ! -d ${2} -o "${1}" != "-l" -a "${1}" != "-u" ] ; then
   echo "Usage: ${0} <-l | -u> <target_directory>"
   exit 1
fi

exec 1>/dev/null 2>&1

dir=`dirname "${2}"`
cd ${dir}

if [ "${1}" = "-l" ] ; then
   base=`basename "${2}" | tr "[A-Z]" "[a-z]"`
else
   base=`basename "${2}" | tr "[a-z]" "[A-Z]"`
fi

mv -f "`basename ${2}`" "${base}"

for entry in `find ${base}`
do
   before="."
   #
   # 这个办法依赖for in语法,用空格做分隔符,所以不能处理那些本身名字带空
   # 格的目录项,属于小BUG
   #
   for after in `echo "${entry}" | sed -e 's,/, ,g'`
   do
       tmp_entry="${before}/${after}"
       if [ "${1}" = "-l" ] ; then
           before=`echo "${tmp_entry}" | tr "[A-Z]" "[a-z]"`
       else
           before=`echo "${tmp_entry}" | tr "[a-z]" "[A-Z]"`
       fi
       mv -f "${tmp_entry}" "${before}"
   done
done
--------------------------------------------------------------------------

21.2 如何将一个512字节的文件写入主引导扇区

A: All of DOS Programmers 2001-10-16 18:05

这个问题如果在90年代初MS-DOS盛行的时候出现,是要被人砍死的,如今时过境迁,
居然能进入这份Unix文档,权当是一种追忆吧。所谓主引导扇区就是硬盘0柱面、0磁
头、1扇区。启动DEBUG,

-f 0200 l 0200 0        <-- 从0200h处开始清零,长512字节
-n mbr                  <-- 假设我们的要处理的文件名为mbr
-l 0200                 <-- 读到0200h处
-d 03be 03ff            <-- 检查分区表
XXXX:03B0                                            00 00                 ..
XXXX:03C0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
XXXX:03D0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
XXXX:03E0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
XXXX:03F0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 55 AA   ..............U.
-

-a 100                  <-- 读主引导扇区的汇编代码
XXXX:0100 mov ax, 0201  <-- 读取一个扇区,ah=02(功能码),al=01(扇区总数)
XXXX:0103 mov bx, 0400  <-- 读取后存放在0400h处,长512字节
XXXX:0106 mov cx, 0001  <-- ch=00(柱面号,10bit),cl=01(扇区号,6bit)
XXXX:0109 mov dx, 0080  <-- dh=00(磁头号),dl=80h(驱动器号)
XXXX:010C int 13        <-- int 13h 磁盘I/O BIOS
XXXX:010E int 3         <-- 单步中断,可以换成int 20h
XXXX:010F
-g=100                  <-- 从0100h处开始执行

AX=0050  BX=0400  CX=0001  DX=0080  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=XXXX  ES=XXXX  SS=XXXX  CS=XXXX  IP=010E   NV UP EI PL NZ NA PO NC
XXXX:010E CC            INT     3
-d 05be 05ff            <-- 检查分区表
XXXX:05B0                                            80 01                 ..
XXXX:05C0  01 00 06 FE 3F 7F 3F 00-00 00 41 60 1F 00 00 00   ....?.?...A`....
XXXX:05D0  01 80 0F FE FF FF 80 60-1F 00 22 3C A0 01 00 00   .......`.."<....
XXXX:05E0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
XXXX:05F0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 55 AA   ..............U.
-

-m 05be l 40 03be       <-- 复制分区表
-d 03be l 40            <-- 确认分区表复制成功
XXXX:03B0                                            80 01                 ..
XXXX:03C0  01 00 06 FE 3F 7F 3F 00-00 00 41 60 1F 00 00 00   ....?.?...A`....
XXXX:03D0  01 80 0F FE FF FF 80 60-1F 00 22 3C A0 01 00 00   .......`.."<....
XXXX:03E0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
XXXX:03F0  00 00 00 00 00 00 00 00-00 00 00 00 00 00         ..............
-a 100                  <-- 写主引导扇区的汇编代码
XXXX:0100 mov ax, 0301  <-- 写一个扇区,ah=03(功能码),al=01(扇区总数)
XXXX:0103 mov bx, 0200  <-- 待写入数据存放在0200h处,长512字节
XXXX:0106 mov cx, 0001  <-- ch=00(柱面号,10bit),cl=01(扇区号,6bit)
XXXX:0109 mov dx, 0080  <-- dh=00(磁头号),dl=80h(驱动器号)
XXXX:010C int 13        <-- int 13h 磁盘I/O BIOS
XXXX:010E int 3         <-- 单步中断,可以换成int 20h
XXXX:010F
-g=100                  <-- 从0100h处开始执行
-q                      <-- 退出DEBUG

第2个硬盘驱动器号是81h,修改DX寄存器赋值语句即可。

A: All of Solaris/FreeBSD/Linux Users

dd if=<path to file> of=/dev/... bs=512 count=1
                       ^^^^^^^^ 对应要处理的物理硬盘设备

21.6 x86/FreeBSD 4.x下不能cp覆盖/kernel

Q: 重新编译内核后用cp命令无法覆盖/kernel

A: deepin <deepin@nsfocus.com>

# ls -lo /kernel
-rwxr-xr-x  1 root  wheel  schg /kernel*
                          ^^^^注意这里,类似Linux的chattr那些东西
# chflags noschg /kernel

参看CHFLAGS(1)、INSTALL(1)手册页。这样修改后可以cp覆盖/kernel了。最后恢复
chflags设置

# chflags schg /kernel

21.7 x86/FreeBSD下如何设置路由

A: backend <backend@nsfocus.com> 2001-10-25 11:33

/etc/defaults/rc.conf或者/etc/rc.conf中会有这样的设置

--------------------------------------------------------------------------
defaultrouter="NO"              # Set to default gateway (or NO).
static_routes=""                # Set to static route list (or leave empty).
--------------------------------------------------------------------------

下面分析static_routes的用法,从/etc/rc.network脚本中可以看到这样的处理

--------------------------------------------------------------------------
# Configure routing
#
case ${defaultrouter} in
[Nn][Oo] | '')
       ;;
*)
       static_routes="default ${static_routes}"
       route_default="default ${defaultrouter}"
       ;;
esac

# Set up any static routes.  This should be done before router discovery.
#
if [ -n "${static_routes}" ]; then
       for i in ${static_routes}; do
               eval route_args=\$route_${i}
               route add ${route_args}
       done
fi
--------------------------------------------------------------------------

注意eval命令导致二次变量替换,对上述脚本分析后可知static_routes用法如下

--------------------------------------------------------------------------
defaultrouter="<IP>"
static_routes="<name1> <name2> ..."
route_<name1>="符合route add命令的语法格式"
route_<name2>="符合route add命令的语法格式"
... ...
--------------------------------------------------------------------------

举例说明

--------------------------------------------------------------------------
defaultrouter="192.168.0.1"
static_routes="entry1 entry2"
route_entry1="-net 10.10.1.0 -netmask 255.255.255.0 -gateway 192.168.254.1"
route_entry2="-net 10.10.2.0 -netmask 255.255.255.0 -gateway 192.168.254.2"
--------------------------------------------------------------------------

当然,你可以不用两个rc.conf文件,而是在/etc/rc.local中直接用route命令增加
路由。

21.9 什么是locale

A: Shen Chuan-Hsing <statue@freebsd.sinica.edu.tw>

locale 指定一组C语言处理自然语言(文字)的方式,也可以简单地说,locale反映了
一组"地区性语言"的配置信息

LC_ALL      代表所有的locale(如下)

LC_CTYPE    字符定义(包含字符分类与转换规则)

LC_MESSAGES 信息显示

LC_TIME     时间格式

LC_NUMERIC  数字格式

LC_MONETARY 货币格式

LC_COLLATE  字母顺序与特殊字符比较顺序

其中与一般使用者息息相关的是是LC_CTYPE与LC_MESSAGES。LC_CTYPE直接关系到某
些字符或內码在目前locale下是否可显示?要如何转换编码?对应到哪一个字?等等。
LC_MESSAGES则关系到软件的信息输出是否符合地域性,例如:我们需要的是中文。
而一个真正完整支持locale系统,是当使用者在shell prompt下,直接设置好环境变
量后就马上切换到那种语言了,例如:

% export LC_CTYPE=zh_TW.Big5

设置locale的字符定义为台湾地区的Big5繁体中文码定义。有了正确的locale定义后,
使得任何地区的的文字,只要在加入适当的locale data之后,C Library就能正确地
处理软件显示信息,而我们使用的[中文]当然也不例外。

21.10 用cvsup安装vim

A: deepin <deepin@nsfocus.com> & scz <scz@nsfocus.com> 2001-11-20 09:42

0) vim主站在http://www.vim.org/

1) # which cvsup
  /usr/local/bin/cvsup

  如果没有,就用www.google.com去搜一个好了,以"cvsup-bin tgz"做关键字

  # wget http://people.freebsd.org/~jdp/s1g/i386-nogui/cvsup-16.1e.tgz
  # pkg_add cvsup-16.1e.tgz

2) # cd /usr/share/examples/cvsup
  # cp ports-supfile scz
  # vi scz
  # cvsup -g -L 2 scz

--------------------------------------------------------------------------
#
# cvsup配置文件
#
*default host=cvsup.cn.FreeBSD.org
*default base=/usr
*default prefix=/usr
*default release=cvs tag=.
*default delete use-rel-suffix
*default compress

#ports-all

ports-editors
--------------------------------------------------------------------------

3) # cd /usr/ports/editors/vim
  # make -DWITHOUT_X11 install  <-- 否则必须在X下使用vim
  # whic vim
  /usr/local/bin/vim            <-- vim直接支持输入中文

21.11 FreeBSD下vi输入中文会显示\x??\x??

A: Shen Chuan-Hsing <statue@freebsd.sinica.edu.tw>

这通常都是设定了LC_CTYPE为zh_TW.Big5(对大陆是zh_CN.EUC)或是没设定LC_CTYPE
才会发生的问题,在~/.cshrc中加上下面的alias即可:

alias vi 'env LC_CTYPE=en_US.ISO_8859-1 vi'

参看PRINTENV(1)手册页了解更多env命令细节。直接改用vim也可以支持中文。

21.15 UDMA ICRC error是什么意思

Q: 在console上出现错误信息"UDMA ICRC error writing... ...",什么意思

A: tt <warning3@nsfocus.com>

通常是使用了40线的IDE硬盘线,然而硬盘被设置成使用DMA模式,这种模式需要80线
硬盘线。也有可能是您的硬盘不支持DMA方式。解决方法有几种

1) 换用一根80线的IDE硬盘线(没干过)
2) 在CMOS BIOS中关闭对UDMA的支持
3) 在FreeBSD中关闭对UDMA的支持

  vi /etc/sysctl.conf
  hw.atamodes=pio,pio,pio,pio,

这样做,可能会降低硬盘速率。

21.16 Limiting closed port RST response什么意思

Q: console上出现"Limiting closed port RST response",什么意思

A: tt <warning3@nsfocus.com>

某些主机快速访问你的主机上一些没有开放的端口,你的主机正在回复RST报文,这
是正常反应。但FreeBSD内核限制了每秒钟回复RST报文的数量,以防止发生可能的
DoS攻击。例如,如果攻击者通过伪造源IP来向你的未开端口发送大量连接请求,就
可能诱使你的主机向该主机发送RST报文。这可能导致受害主机所在网络的带宽占用。
如果你不想看到上述信息,可以打开黑洞模式来停止响应RST报文。这也可以减缓远
程攻击者对你的主机的扫描速度。

# sysctl -w net.inet.tcp.blackhole=2
# sysctl -w net.inet.udp.blackhole=1

也可以在/etc/sysctl.conf中增加下列选项使黑洞模式每次启动后都生效

net.inet.tcp.blackhole=2
net.inet.udp.blackhole=1

22. Linux Kernel Programming

22.1 直接访问内存[显存]地址

Q: 现在在修改linux内核,希望能访问一段地址(其实是显存)。但发觉不能直接访问

A: Kongming <ymwei@263.net> (Luther <Luther@pku.edu> 整理)

通过/dev/mem设备文件和mmap系统调用,可以将线性地址描述的物理内存映射到进程
的地址空间,然后就可以直接访问这段内存了。

比如,标准VGA 16色模式的实模式地址是A000:0000,而线性地址则是A0000。设定显
存大小为0x10000,则可以如下操作

   mem_fd  = open( "/dev/mem", O_RDWR );
   vga_mem = mmap( 0, 0x10000, PROT_READ | PROT_WRITE, MAP_SHARED,
                   mem_fd, 0xA0000 );
   close( mem_fd );

然后直接对vga_mem进行访问,就可以了。当然,如果是操作VGA显卡,还要获得I/O
端口的访问权限,以便进行直接的I/O操作,用来设置模式/调色板/选择位面等等

在工控领域中还有一种常用的方法,用来在内核和应用程序之间高效传递数据:

1) 假定系统有64M物理内存,则可以通过lilo通知内核只使用63M,而保留1M物理内
  存作为数据交换使用(使用 mem=63M 标记)。
2) 然后打开/dev/mem设备,并将63M开始的1M地址空间映射到进程的地址空间。


文章评论

共有 0 位网友发表了评论 此处只显示部分留言 点击查看完整评论页面

300x250广告位招租