|
|
※阅读文章※ |
Unix编程/应用问答中文版 ---12.日志相关问题 13.进程相关问题作者:不祥 [文章出自: www.fanqiang.com] 12. 日志相关问题 12.1 12.2 12.3 如何关闭cron的日志 12.4 -------------------------------------------------------------------------- 13. 进程相关问题 13.1 如何根据进程名获得PID 13.2 13.3 13.4 Solaris 7/8下ps输出中的问号 13.5 13.6 13.7 给定一个PID,如何知道它对应一个运行中的进程 13.8 Unix/Linux编程中所谓"僵尸进程"指什么 13.9 x86/FreeBSD 4.3-RELEASE的ptrace(2)手册页 13.10 Solaris下如何知道哪个进程使用了哪个端口 13.11 x86/FreeBSD如何快速获取指定用户拥有的进程数 -------------------------------------------------------------------------- 12.3 如何关闭cron的日志 Q: 有些时候cron的日志文件增长得如此之大,占用了大量磁盘空间,有什么办法彻 底关闭cron的日志吗 A: Sun Microsystems 1998-03-30 编辑/etc/default/cron,设置 CRONLOG 变量为 NO ,将关闭cron的日志 CRONLOG=NO 缺省是 CRONLOG=YES 13. 进程相关问题 13.1 如何根据进程名获得PID Q: 我知道ps、top等命令和grep相结合可以达到这个效果,但是我想在C程序中实现 这个功能,并且我不想用system()、popen()等方式。 D: Linux提供了一个命令,pidof(8) A: Andrew Gierth <andrew@erlenstar.demon.co.uk> 第一种办法是读取/proc接口提供的信息 -------------------------------------------------------------------------- /* gcc -Wall -O3 -o getpid getpid.c */ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/procfs.h> #include <unistd.h> #include <stropts.h> #include <dirent.h> #include <fcntl.h> static pid_t getpidbyname ( char * name, pid_t skipit ) { DIR * dirHandle; /* 目录句柄 */ struct dirent * dirEntry; /* 单个目录项 */ prpsinfo_t prp; int fd; pid_t pid = -1; if ( ( dirHandle = opendir( "/proc" ) ) == NULL ) { return( -1 ); } chdir( "/proc" ); /* 下面使用相对路径打开文件,所以必须进入/proc */ while ( ( dirEntry = readdir( dirHandle ) ) != NULL ) { if ( dirEntry->d_name[0] != '.' ) { /* fprintf( stderr, "%s\n", dirEntry->d_name ); */ if ( ( fd = open( dirEntry->d_name, O_RDONLY ) ) != -1 ) { if ( ioctl( fd, PIOCPSINFO, &prp ) != -1 ) { /* fprintf( stderr, "%s\n", prp.pr_fname ); */ if ( !strcmp( prp.pr_fname, name ) ) /* 这里是相对路径,而且 不带参数 */ { pid = ( pid_t )atoi( dirEntry->d_name ); if ( skipit != -1 && pid == skipit ) /* -1做为无效pid对 待 */ { pid = -1; } else /* 找到匹配 */ { close( fd ); break; /* 跳出while循环 */ } } } close( fd ); } } } /* end of while */ closedir( dirHandle ); 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权限,否则无法有效获取非自己拥有的进程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 ) 文章加入时间: 2004-11-17 14:53:11 责任编辑: w9 (2567 人次查阅) |