- 论坛徽章:
- 0
|
unix编程常见问题解答
6. 工具的使用
*************
6.1 我怎样调试fork函数产生的子进程?
====================================
根据可用的工具有两种不同的方法:
你的调试器(debugger)可能有允许你选择是否跟踪调用‘fork()’以后的父或子进程
的选项,对于某些目的来说那已经足够了。
替换方法是,你的调试器可能有一个选项允许你将它依附(attach)到一个正在执行
的程序。这样你可以依附调试器到一个已经开始执行的子进程。如果你不需要从
子进程一开始就开始测试,这通常已经足够。否则,你会希望在子进程的‘fork()’
调用后插入一个‘sleep()’调用,或者插入如下的循环:
{
volatile int f = 1;
while(f);
}
这样子进程将一直在此循环不往下执行直到你用调试器设定‘f’为0。
并且记住,使用调试器并非是找到你程序中错误的唯一方法;在很多Unix系统
上有一些工具程序可用来跟踪系统调用和信号,而且丰富的日志经常也是有
用的。
6.2 怎样通过其他库文件建立新的库文件?
======================================
前提是我们所说的是归档(archive)(静态)库,最简单的方法是将所有选择的库
文件使用‘ar x’命令在一个空目录里拆分成它们原始的单个目标文件(object),
然后再合并成一个。当然,文件名有可能会重复,但是如果这个库文件很大,
你也许一开始就不想将它们合并在一起。
6.3 怎样创建动态连接库(shared library)/dlls?
=============================================
创建动态连接库(shared libraries)的方法根据不同的系统有所不同。这个过程主要
分两步;第一步要求包括在动态连接库中的目标必须首先是编译好的,通常需
要某个编译选项指示这串代码是位置无关的(position-indepenent);第二步,是将
这些目标连接在一起形成一个库文件。
这里是一个演示以上道理的小程序:
/* shrobj.c 文件 */
const char *myfunc()
{
return "Hello World";
}
/* shrobj.c 结束 */
/* hello.c 文件 */
#include <stdio.h>;
extern const char *myfunc();
main()
{
printf("%s\n", myfunc());
return 0;
}
/* hello.c 结束 */
$ gcc -fpic -c shrobj.c
$ gcc -shared -o libshared.so shrobj.o
$ gcc hello.c libshared.so
$ ./a.out
Hello World
到目前为止,如果你希望库文件和它的创建过程是都可以移植的话,那么最好
的办法是使用GNU Libtool程序包。它是个小型的工具程序套件,这些工具程序
知道建立动态连接库的平台无关性;你可以只发布你的程序必要的部分,从而
当一个安装者配置你的软件包时,他能决定生成什么库。Libtool程序包在不支持
动态连接库的系统上也能工作得很好。它而且知道与GNU Autoconf程序和GNU
Automake程序挂钩(如果你使用这些工具来管理你程序的编译创建过程)。
如果你不想使用Libtool程序包,那么对于gcc以外的编译器,你需要按照下面所
列修改编译器参数:
AIX 3.2 使用 xlc (未证实)
取消‘-fpic’选项,以‘-bM:SRE -bE:libshared.exp’取代‘-shared’。你并且
需要创建一个名为‘libshared.exp’的文件保存一个所有输出符号(symbols to export)
的列表,比如以上的范例程序,就需要输出‘myfunc’符号。另外,在连接库
时使用‘-e _nostart’参数(在较新的AIX版本上,我相信应该将其变成‘-bnoentry’)。
SCO OpenServer 5 使用 SCO 开发系统(Development System) (未证实)
如果你使用ELF(译者注:ELF:执行与连接格式Executable and Linking Forrmat,
一种Unix可执行目标文件的格式)格式,那么共享库只能在OS5上可用,而它
需要‘-belf’选项。并以‘-Kpic’取代‘-fpic’,在连接时使用‘cc -belf -G’。
Solaris 使用 SparcWorks 编译器
以‘-pic’取代‘-fpic’,并以‘ld -G’取代‘gcc -shared’。
(鼓励大家提供更多的材料丰富上述列表)
其它要当心的问题:
* AIX和(我相信)Digital Unix不需要-fpic选项,因为所有代码都是位置无关的。
* AIX一般需要你创建一个‘输出文件’,即一个保存所有动态连接库中输出
符号的列表。一些连接器(linker)版本(可能只有SLHS连接器,是svld?)有一个
选项可以输出所有符号。
* 如果你对于连接器想使用普遍的‘-l’参数来引用你的动态连接库,你必须
理解你的系统在实际运行时是怎样寻找动态连接库的。最普通的方法是使用
‘LD_LIBRARY_PATH’环境变量,但是通常在连接时有一种其它选项可以
设定。
* 大多数实现方法是在程序内部记录所希望的动态连接库在运行时的位置。这
样把一个动态连接库从一个目录移到另一个目录将导致程序无法工作。许多
系统对于连接器有一个选项用以设定希望运行时动态连接库的位置(比如在
Solaris系统上是‘-R’连接器选项,或者是‘LD_RUN_PATH’环境变量)。
* ELF和a.out的实现方法可能有一个连接器选项‘-Bsymbolic’,它导致在库本
身内部的引用被解释好。否则,在这些系统上,所有符号的解释将推迟到最
后连接时再进行,这样在main程序中的单一函数将重载库中的对应函数。
6.4 我能更改一个动态连接库里的目标吗?
======================================
一般不行。
在大多数系统上(除了AIX),当你连接目标并形成一个动态连接库时,它就象连
接一个可执行程序;目标并不保留它们单一的特征。结果是,一般不能从一个动
态连接库里析取出或更换一个单一的目标。
6.5 我能在一个运行着的程序中生成堆栈映象吗?
============================================
一些系统提供库函数可以提取(unwinding)出堆栈,从而(比如)你可以在一个错误
处理函数中生成一个堆栈映象,但是只有一小部分系统有这些函数。
一个可能的变通方法(workaround)是让你的程序执行一个调试器调试*它自己* -
详细方法仍然根据不同系统稍有不同,但一般的概念是这样:
void dump_stack(void)
{
char s[160];
sprintf(s, "/bin/echo 'where\ndetach' | dbx -a %d", getpid());
system(s);
return;
}
你需要根据你不同的系统对dbx的参数和命令进行加工,或者甚至换另一个调试
器,比如‘gdb’,但这仍然是我见过的对于这种问题最普遍的解决方法。为此,
荣誉授予Ralph Corderoy。
下面列表包含在一些系统上需要用到的命令行:
大多数使用dbx的系统
`"/bin/echo 'where\ndetach' | dbx /path/to/program %d"'
AIX
`"/bin/echo 'where\ndetach' | dbx -a %d"'
IRIX
`"/bin/echo 'where\ndetach' | dbx -p %d"'
?
范例程序
********
捕获 SIGCHLD 信号
=================
#include <sys/types.h>; /* 在任何其它 sys 下的头文件之前引用这个头文件 */
#include <sys/wait.h>; /* waitpid()和一些不同的宏所需的头文件 */
#include <signal.h>; /* 信号函数的头文件 */
#include <stdio.h>; /* fprintf函数的头文件 */
#include <unistd.h>; /* fork函数的头文件 */
void sig_chld(int); /* 我们的 SIGCHLD 信号处理函数的原形(prototype) */
int main()
{
struct sigaction act;
pid_t pid;
/* 设定sig_chld函数作为我们SIGCHLD信号的处理函数 */
act.sa_handler = sig_chld;
/* 在这个范例程序里,我们不想阻塞其它信号 */
sigemptyset(&act.sa_mask);
/*
* 我们只关谋恢罩沟淖咏?蹋??皇潜恢卸? * 的子进程 (比如用户在终端上按Control-Z)
*/
act.sa_flags = SA_NOCLDSTOP;
/*
* 使这些设定的值生效. 如果我们是写一个真实的应用程序,
* 我们也许应该保存这些原有值,而不是传递一个NULL。
*/
if (sigaction(SIGCHLD, &act, NULL) < 0)
{
fprintf(stderr, "sigaction failed\n" ;
return 1;
}
/* fork */
switch (pid = fork())
{
case -1:
fprintf(stderr, "fork failed\n" ;
return 1;
case 0: /* 是子进程,直接结束 */
_exit(7); /* 退出状态 = 7 */
default: /* 父进程 */
sleep(10); /* 给子进程完成的时间 */
}
return 0;
}
/*
* 信号处理函数 -- 只有当接收到一个SIGCHLD信号才被调用,
* 即有一个子进程终止
*/
void sig_chld(int signo)
{
int status, child_val;
/* 非阻塞地等待任何子进程结束 */
if (waitpid(-1, &status, WNOHANG) < 0)
{
/*
* 不建议在信号处理函数中调用标准输入/输出函数,
* 但在一个类似这个的玩具程序里或许没问题
*/
fprintf(stderr, "waitpid failed\n" ;
return;
}
/*
* 我们现在有保存在‘status’变量中的子进程退出信息并可以使用
* wait.h中定义的宏对其进行操作
*/
if (WIFEXITED(status)) /* 子进程是正常退出吗? */
{
child_val = WEXITSTATUS(status); /* 获取子进程的退出状态 */
printf("child's exited normally with status %d\n", child_val);
}
}
读取进程表 - SUNOS 4 版
=======================
#define _KMEMUSER
#include <sys/proc.h>;
#include <kvm.h>;
#include <fcntl.h>;
char regexpstr[256];
#define INIT register char *sp=regexpstr;
#define GETC() (*sp++)
#define PEEKC() (*sp)
#define UNGETC(c) (--sp)
#define RETURN(pointer) return(pointer);
#define ERROR(val)
#include <regexp.h>;
pid_t
getpidbyname(char *name,pid_t skipit)
{
kvm_t *kd;
char **arg;
int error;
char *p_name=NULL;
char expbuf[256];
char **freeme;
int curpid;
struct user * cur_user;
struct user myuser;
struct proc * cur_proc;
if((kd=kvm_open(NULL,NULL,NULL,O_RDONLY,NULL))==NULL){
return(-1);
}
sprintf(regexpstr,"^.*/%s$",name);
compile(NULL,expbuf,expbuf+256,'\0');
while(cur_proc=kvm_nextproc(kd)){
curpid = cur_proc->;p_pid;
if((cur_user=kvm_getu(kd,cur_proc))!=NULL){
error=kvm_getcmd(kd,cur_proc,cur_user,&arg,NULL);
if(error==-1){
if(cur_user->;u_comm[0]!='\0'){
p_name=cur_user->;u_comm;
}
}
else{
p_name=arg[0];
}
}
if(p_name){
if(!strcmp(p_name,name)){
if(error!=-1){
free(arg);
}
if(skipit!=-1 && ourretval==skipit){
ourretval=-1;
}
else{
close(fd);
break;
}
break;
}
else{
if(step(p_name,expbuf)){
if(error!=-1){
free(arg);
}
break;
}
}
}
if(error!=-1){
free(arg);
}
p_name=NULL;
}
kvm_close(kd);
if(p_name!=NULL){
return(curpid);
}
return (-1);
}
读取进程表 - SYSV 版
====================
pid_t
getpidbyname(char *name,pid_t skipit)
{
DIR *dp;
struct dirent *dirp;
prpsinfo_t retval;
int fd;
pid_t ourretval=-1;
if((dp=opendir("/proc" )==NULL){
return -1;
}
chdir("/proc" ;
while((dirp=readdir(dp))!=NULL){
if(dirp->;d_name[0]!='.'){
if((fd=open(dirp->;d_name,O_RDONLY))!=-1){
if(ioctl(fd,PIOCPSINFO,&retval)!=-1){
if(!strcmp(retval.pr_fname,name)){
ourretval=(pid_t)atoi(dirp->;d_name);
if(skipit!=-1 && ourretval==skipit){
ourretval=-1;
}
else{
close(fd);
break;
}
}
}
close(fd);
}
}
}
closedir(dp);
return ourretval;
}
读取进程表 - AIX 4.2 版
=======================
#include <stdio.h>;
#include <procinfo.h>;
int getprocs(struct procsinfo *, int, struct fdsinfo *,
int, pid_t *, int);
pid_t getpidbyname(char *name, pid_t *nextPid)
{
struct procsinfo pi;
pid_t retval = (pid_t) -1;
pid_t pid;
pid = *nextPid;
while(1)
{
if(getprocs(&pi, sizeof pi, 0, 0, &pid, 1) != 1)
break;
if(!strcmp(name, pi.pi_comm))
{
retval = pi.pi_pid;
*nextPid = pid;
break;
}
}
return retval;
}
int main(int argc, char *argv[])
{
int curArg;
pid_t pid;
pid_t nextPid;
if(argc == 1)
{
printf("syntax: %s <program>; [program ...]\n",argv[0]);
exit(1);
}
for(curArg = 1; curArg < argc; curArg++)
{
printf(" rocess IDs for %s\n", argv[curArg]);
for(nextPid = 0, pid = 0; pid != -1; )
if((pid = getpidbyname(argv[curArg], &nextPid)) != -1)
printf("\t%d\n", pid);
}
}
使用popen函数和ps命令读取进程表
===============================
#include <stdio.h>; /* FILE, sprintf, fgets, puts */
#include <stdlib.h>; /* atoi, exit, EXIT_SUCCESS */
#include <string.h>; /* strtok, strcmp */
#include <sys/types.h>; /* pid_t */
#include <sys/wait.h>; /* WIFEXITED, WEXITSTATUS */
char *procname(pid_t pid)
{
static char line[133], command[80], *linep, *token, *cmd;
FILE *fp;
int status;
if (0 == pid) return (char *)0;
sprintf(command, "ps -p %d 2>;/dev/null", pid);
fp = popen(command, "r" ;
if ((FILE *)0 == fp) return (char *)0;
/* 读取标题行 */
if ((char *)0 == fgets(line, sizeof line, fp))
{
pclose(fp);
return (char *)0;
}
/* 从标题栏分析出命令名所在列。
* (BSD风格的系统将指示命令的"COMMAND"字符串放在第5列,SysV好象将
* 指示命令的“CMD”或“COMMAND”字符串放在第4列)
*/
for (linep = line; ; linep = (char *)0)
{
if ((char *)0 == (token = strtok(linep, " \t\n" ))
{
pclose(fp);
return (char *)0;
}
if (0 == strcmp("COMMAND", token) || 0 == strcmp("CMD", token))
{ /* 我们找到COMMAND所在列 */
cmd = token;
break;
}
}
/* 读取 ps(1) 输出行 */
if ((char *)0 == fgets(line, sizeof line, fp))
{
pclose(fp);
return (char *)0;
}
/* 抓COMMAND标题下面的词 ... */
if ((char *)0 == (token = strtok(cmd, " \t\n" ))
{
pclose(fp);
return (char *)0;
}
status = pclose(fp);
if (!WIFEXITED(status) || 0 != WEXITSTATUS(status))
return (char *)0;
return token;
}
int main(int argc, char *argv[])
{
puts(procname(atoi(argv[1])));
exit(EXIT_SUCCESS);
}
守护程序工具函数
================
#include <unistd.h>;
#include <stdlib.h>;
#include <fcntl.h>;
#include <signal.h>;
#include <sys/types.h>;
#include <sys/wait.h>;
#include <errno.h>;
/* closeall() -- 关闭所有>;=给定值的文件描述符 */
void closeall(int fd)
{
int fdlimit = sysconf(_SC_OPEN_MAX);
while (fd < fdlimit)
close(fd++);
}
/* daemon() - 将进程从用户端脱离并消失进入后台,若失败返回-1,
* 但是在那种情况下你只能退出,因为我们可能已经生成了子进程。
* 这是基于BSD的版本,所以调用方需负责类似umask等等其它的工作。
*/
/* 相信在所有Posix系统上都能工作 */
int daemon(int nochdir, int noclose)
{
switch (fork())
{
case 0: break;
case -1: return -1;
default: _exit(0); /* 原进程退出 */
}
if (setsid() < 0) /* 不应该失败 */
return -1;
/* 如果你希望将来获得一个控制tty,则排除(dyke)以下的switch语句 */
/* -- 正常情况不建议用于守护程序 */
switch (fork())
{
case 0: break;
case -1: return -1;
default: _exit(0);
}
if (!nochdir)
chdir("/" ;
if (!noclose)
{
closeall(0);
open("/dev/null",O_RDWR);
dup(0); dup(0);
}
return 0;
}
/* fork2() -- 类似fork函数,但子进程立刻变成孤儿进程
* (当它退出时不产生僵死进程)
* 返回1给父进程,不是任何有意义的进程号.
* 父进程不能使用wait函数等待子进程结束 (它们是无关的).
*/
/* 这个版本假设你没有捕获和忽略SIGCHLD信号. */
/* 如果你有设定,则不管怎样应使用fork函数 */
int fork2()
{
pid_t pid;
int rc;
int status;
if (!(pid = fork()))
{
switch (fork())
{
case 0: return 0;
case -1: _exit(errno); /* 假设错误码都小于256 */
default: _exit(0);
}
}
if (pid < 0 || waitpid(pid,&status,0) < 0)
return -1;
if (WIFEXITED(status))
if (WEXITSTATUS(status) == 0)
return 1;
else
errno = WEXITSTATUS(status);
else
errno = EINTR; /* 唉,类似这个 */
return -1;
}
一个使用以上函数的范例程序:
#include <sys/types.h>;
#include <sys/socket.h>;
#include <netinet/in.h>;
#include <stdio.h>;
#include <stdlib.h>;
#include <syslog.h>;
#include <errno.h>;
int daemon(int,int);
int fork2(void);
void closeall(int);
#define TCP_PORT 8888
void errexit(const char *str)
{
syslog(LOG_INFO, "%s failed: %d (%m)", str, errno);
exit(1);
}
void errreport(const char *str)
{
syslog(LOG_INFO, "%s failed: %d (%m)", str, errno);
}
/* 实际的子进程在此. */
void run_child(int sock)
{
FILE *in = fdopen(sock,"r" ;
FILE *out = fdopen(sock,"w");
int ch;
setvbuf(in, NULL, _IOFBF, 1024);
setvbuf(out, NULL, _IOLBF, 1024);
while ((ch = fgetc(in)) != EOF)
fputc(toupper(ch), out);
fclose(out);
}
/* 这是守护程序的主要工作 -- 侦听连接并生成子进程 */
void process()
{
struct sockaddr_in addr;
int addrlen = sizeof(addr);
int sock = socket(AF_INET, SOCK_STREAM, 0);
int flag = 1;
int rc = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
&flag, sizeof(flag));
if (rc < 0)
errexit("setsockopt");
addr.sin_family = AF_INET;
addr.sin_port = htons(TCP_PORT);
addr.sin_addr.s_addr = INADDR_ANY;
rc = bind(sock, (struct sockaddr *) &addr, addrlen);
if (rc < 0)
errexit("bind");
rc = listen(sock, 5);
if (rc < 0)
errexit("listen");
for (;;)
{
rc = accept(sock, (struct sockaddr *) &addr, &addrlen);
if (rc >;= 0)
switch (fork2())
{
case 0: close(sock); run_child(rc); _exit(0);
case -1: errreport("fork2"); close(rc); break;
default: close(rc);
}
}
}
int main()
{
if (daemon(0,0) < 0)
{
perror("daemon");
exit(2);
}
openlog("test", LOG_PID, LOG_DAEMON);
process();
return 0;
} |
|