![]() | 第 2 帖 | |
|
标题: Unix编程常见问题解答《6 工具的使用》
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("Process 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; } 调制解调器控制范例程序 ====================== /* 发出一些简单调制解调器命令 * 需要串行设备的设备名 (最好是拨出设备, * 或者是非调制解调器控制设备) 作为它唯一的参数. * 如果你没有可共使用的拨出设备, 那么以CFLAGS_TO_SET取代CLOCAL。 */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <sys/types.h> #include <sys/time.h> #include <sys/ioctl.h> /* 也许需要;和系统有关 */ #include <termios.h> #include <errno.h> #include <string.h> #include <ctype.h> #define CFLAGS_TO_SET (CREAD | HUPCL) #define CFLAGS_TO_CLEAR (CSTOPB | PARENB | CLOCAL) enum flowmode { NoFlow, HardFlow, SoftFlow }; /* 和系统有关 */ #define CFLAGS_HARDFLOW (CRTSCTS) #define EXAMPLE_BAUD B19200 #define EXAMPLE_FLOW HardFlow static void die(const char *msg) { fprintf(stderr, "%s/n", msg); exit(1); } static int close_and_complain(int fd, const char *msg, int err) { fprintf(stderr, "%s: %s/n", msg, strerror(err)); if (fd >= 0) close(fd); errno = err; return -1; } int open_port(const char *name, speed_t baud, enum flowmode flow) { int flags; struct termios attr; int fd = open(name, O_RDWR | O_NONBLOCK | O_NOCTTY); if (fd < 0) return close_and_complain(-1, "open", errno); /* 设定一些不明确是否敏感的值 */ if (tcgetattr(fd, &attr) < 0) return close_and_complain(fd, "tcgetattr", errno); /* 无特殊输入或输出处理 */ attr.c_iflag = (flow == SoftFlow) ? (IXON | IXOFF) : 0; attr.c_oflag = 0; /* 设定8位字符宽和一些杂项控制模式 */ attr.c_cflag &= ~(CSIZE | CFLAGS_TO_CLEAR | CFLAGS_HARDFLOW); attr.c_cflag |= (CS8 | CFLAGS_TO_SET); if (flow == HardFlow) attr.c_cflag |= CFLAGS_HARDFLOW; /* 本机模式 */ attr.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ISIG); /* 特殊字符 -- 许多已被先前的设定取消 */ { int i; #ifdef _POSIX_VDISABLE attr.c_cc[0] = _POSIX_VDISABLE; #else attr.c_cc[0] = fpathconf(fd, _PC_VDISABLE); #endif for (i = 1; i < NCCS; i++) attr.c_cc[i] = attr.c_cc[0]; } attr.c_cc[VSTART] = 0x11; attr.c_cc[VSTOP] = 0x13; /* 对read()函数的计时控制 */ attr.c_cc[VMIN] = 1; attr.c_cc[VTIME] = 0; /* 波特律 */ cfsetispeed(&attr, baud); cfsetospeed(&attr, baud); /* 写入设定 */ if (tcsetattr(fd, TCSANOW, &attr) < 0) return close_and_complain(fd, "tcsetattr", errno); /* 如果系统记住了先前的O_NONBLOCK设定,就取消它 */ flags = fcntl(fd, F_GETFL, 0); if (flags < 0) return close_and_complain(fd, "fcntl(GETFL)", errno); if (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0) return close_and_complain(fd, "fcntl(SETFL)", errno); return fd; } /* 一些简单的计时工具函数 */ /* 向*TV加 SECS 和USECS */ static void timeradd(struct timeval *tv, long secs, long usecs) { tv->tv_sec += secs; if ((tv->tv_usec += usecs) >= 1000000) { tv->tv_sec += tv->tv_usec / 1000000; tv->tv_usec %= 1000000; } } /* 设定 *RES = *A - *B, 返回结果的符号 */ static int timersub(struct timeval *res, const struct timeval *a, const struct timeval *b) { long sec = a->tv_sec - b->tv_sec; long usec = a->tv_usec - b->tv_usec; if (usec < 0) usec += 1000000, --sec; res->tv_sec = sec; res->tv_usec = usec; return (sec < 0) ? (-1) : ((sec == 0 && usec == 0) ? 0 : 1); } /* 这个函数不试图处理非正常的字符串 (比如 ababc) * 超时以微妙计 * 一个更通常的做法是使用alarm()函数处理超时. * 这个函数为简便起见不使用信号处理并试图提供一种替换方法 */ int expect(int fd, const char *str, int timeo) { int matchlen = 0; int len = strlen(str); struct timeval now,end,left; fd_set fds; char c; gettimeofday(&end, NULL); timeradd(&end, timeo/1000, timeo%1000); while (matchlen < len) { gettimeofday(&now, NULL); if (timersub(&left, &end, &now) <= 0) return -1; FD_ZERO(&fds); FD_SET(fd, &fds); if (select(fd+1, &fds, NULL, NULL, &left) <= 0) return -1; if (read(fd, &c, 1) != 1) return -1; if (isprint((unsigned char)c) || c == '/n' || c == '/r') putchar(c); else printf("//x%02x", c); if (c == str[matchlen]) ++matchlen; else matchlen = 0; } return 0; } int main(int argc, char **argv) { int fd; unsigned char c; if (argc < 2) die("no port specified"); setvbuf(stdout, NULL, _IONBF, 0); fd = open_port(argv[1], EXAMPLE_BAUD, EXAMPLE_FLOW); if (fd < 0) die("cannot open port"); write(fd, "AT/r", 3); if (expect(fd, "OK", 5000) < 0) { write(fd, "AT/r", 3); if (expect(fd, "OK", 5000) < 0) { tcflush(fd, TCIOFLUSH); close(fd); die("no response to AT"); } } write(fd, "ATI4/r", 5); expect(fd, "OK", 10000); putchar('/n'); tcflush(fd, TCIOFLUSH); close(fd); return 0; } 事务控制范例程序 ================ /* 生成前台/后台事务的函数 */ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> #include <errno.h> /* 一些下面的函数会因为无法定位控制tty和调用方不在前台而失败。 * 第一种情况时,我们假设一个前台程序会有为标准输入,标准输出或标准错误输出打开的ctty, * 而如果没有则返回ENOTTY。 * 第二种情况时,除foreground_self()函数的特殊情况以外, * 若一个非前台程序打算输出一些东西到前台,我们返回EPERM。 * (也许想得太多了) */ /* 为给定的pgrp安排一个终端 (打开一个ctty) . * 这个tcsetpgrp()外壳程序只是因为POSIX中特别错误(bogusity)的地方而需要; * 遵照标准的系统在一个非前台进程调用tcsetpgrp函数时传递SIGTTOU * 信号(差不多总是这样)。这是虚假的一致性之于一般想法的胜利。 */ int assign_terminal(int ctty, pid_t pgrp) { sigset_t sigs; sigset_t oldsigs; int rc; sigemptyset(&sigs); sigaddset(&sigs,SIGTTOU); sigprocmask(SIG_BLOCK, &sigs, &oldsigs); rc = tcsetpgrp(ctty, pgrp); sigprocmask(SIG_SETMASK, &oldsigs, NULL); return rc; } /* 类似fork函数,但做事务控制。如果新建立的进程放在前台则设fg为真。 * (这样隐式地将调用方进程放置到后台,所以做完这个后要当心tty的输入/输出) * 设定pgrp为-1以创建一个新事务,在此情况下返回的进程号即是新事务的进程组号, * 或者设定一个同一会话中存在的事务(一般只用来启动管道操作的第二个或第二个以后 * 的进程)。 */ pid_t spawn_job(int fg, pid_t pgrp) { int ctty = -1; pid_t pid; /* 如果生成一个*新*的前台事务,起码要求标准输入,标准输出或 * 标准错误输出的其中一个指向的是控制tty,并且当前进程在前台。 * 只有当在存在事务中开始一个新前台进程时才检查控制中的tty。 * 一个没有控制tty的会话只能有后台事务。 */ if (fg) { pid_t curpgrp; if ((curpgrp = tcgetpgrp(ctty = 2)) < 0 && (curpgrp = tcgetpgrp(ctty = 0)) < 0 && (curpgrp = tcgetpgrp(ctty = 1)) < 0) return errno = ENOTTY, (pid_t)-1; if (pgrp < 0 && curpgrp != getpgrp()) return errno = EPERM, (pid_t)-1; } switch (pid = fork()) { case -1: /* fork失败 */ return pid; case 0: /* 子进程 */ /* 建立新进程组, 如果需要则将我们放到前台 * 不知道如果setpgid函数调用失败该怎么办(“不会发生”) */ if (pgrp < 0) pgrp = getpid(); if (setpgid(0,pgrp) == 0 && fg) assign_terminal(ctty, pgrp); return 0; default: /* 父进程 */ /* 这里也建立自进程组. */ if (pgrp < 0) pgrp = pid; setpgid(pid, pgrp); return pid; } /*不会执行到这里*/ } /* 用SIGNO表示的信号杀死PGRP表示的事务 */ int kill_job(pid_t pgrp, int signo) { return kill(-pgrp, signo); } /* 中断PGRP表示的事务 */ int suspend_job(pid_t pgrp) { return kill_job(pgrp, SIGSTOP); } /* 继续在后台执行PGRP表示的事务 */ int resume_job_bg(pid_t pgrp) { return kill_job(pgrp, SIGCONT); } /* 继续在前台执行PGRP表示的事务 */ int resume_job_fg(pid_t pgrp) { pid_t curpgrp; int ctty; if ((curpgrp = tcgetpgrp(ctty = 2)) < 0 && (curpgrp = tcgetpgrp(ctty = 0)) < 0 && (curpgrp = tcgetpgrp(ctty = 1)) < 0) return errno = ENOTTY, (pid_t)-1; if (curpgrp != getpgrp()) return errno = EPERM, (pid_t)-1; if (assign_terminal(ctty, pgrp) < 0) return -1; return kill_job(pgrp, SIGCONT); } /* 将我们自己放置到前台,比如在中断一个前台事务之后调用 */ int foreground_self() { pid_t curpgrp; int ctty; if ((curpgrp = tcgetpgrp(ctty = 2)) < 0 && (curpgrp = tcgetpgrp(ctty = 0)) < 0 && (curpgrp = tcgetpgrp(ctty = 1)) < 0) return errno = ENOTTY, (pid_t)-1; return assign_terminal(ctty, getpgrp()); } /* closeall() - 关闭所有>=给定FD的文件描述符 */ void closeall(int fd) { int fdlimit = sysconf(_SC_OPEN_MAX); while (fd < fdlimit) close(fd++); } /* 类似system()函数,但将给定的命令作为后台事务执行,返回shell进程 * 的进程号(并且也是这个事务的进程组号,适用于kill_job等等)。 * 如果参数INFD,OUTFD或ERRFD为非NULL,则打开一个管道和一个文件描述 * 符保存与该管道有关的父进程端,然后在子进程中将被从定向到/dev/null。 * 并且在子进程中关闭所有>2的文件描述符(一个经常过份估计的工作) */ pid_t spawn_background_command(const char *cmd, int *infd, int *outfd, int *errfd) { int nullfd = -1; int pipefds[3][2]; int error = 0; if (!cmd) return errno = EINVAL, -1; pipefds[0][0] = pipefds[0][1] = -1; pipefds[1][0] = pipefds[1][1] = -1; pipefds[2][0] = pipefds[2][1] = -1; if (infd && pipe(pipefds[0]) < 0) error = errno; else if (outfd && pipe(pipefds[1]) < 0) error = errno; else if (errfd && pipe(pipefds[2]) < 0) error = errno; if (!error && !(infd && outfd && errfd)) { nullfd = open("/dev/null",O_RDWR); if (nullfd < 0) error = errno; } if (!error) { pid_t pid = spawn_job(0, -1); switch (pid) { case -1: /* fork失败 */ error = errno; break; case 0: /* 子进程 */ dup2(infd ? pipefds[0][0] : nullfd, 0); dup2(outfd ? pipefds[1][1] : nullfd, 1); dup2(errfd ? pipefds[2][1] : nullfd, 2); closeall(3); execl("/bin/sh","sh","-c",cmd,(char*)NULL); _exit(127); default: /* 父进程 */ close(nullfd); if (infd) close(pipefds[0][0]), *infd = pipefds[0][1]; if (outfd) close(pipefds[1][1]), *outfd = pipefds[1][0]; if (errfd) close(pipefds[2][1]), *errfd = pipefds[2][0]; return pid; } } /* 只在错误时执行到这里 */ { int i,j; for (i = 0; i < 3; ++i) for (j = 0; j < 2; ++j) if (pipefds[i][j] >= 0) close(pipefds[i][j]); } if (nullfd >= 0) close(nullfd); return errno = error, (pid_t) -1; } /*---------------------------------------*/ /* 这里是使用上述函数一个小例子. */ pid_t bgjob = -1; volatile int signo = 0; #ifndef WCOREDUMP /* 如果没有 WCOREDUMP, 你也许会希望在你的平台上为它设置一个准确的定义 * (这通常是(status & 0x80) 但也不总是这样),或者就赌没有core dumps( * 就象这个程序所做) */ # define WCOREDUMP(status) (0) #endif int check_children() { pid_t pid; int status; int count = 0; while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { if (pid == bgjob && !WIFSTOPPED(status)) bgjob = -1; ++count; if (WIFEXITED(status)) fprintf(stderr,"Process %ld exited with return code %d/n", (long)pid, WEXITSTATUS(status)); else if (WIFSIGNALED(status)) fprintf(stderr,"Process %ld killed by signal %d%s/n", (long)pid, WTERMSIG(status), WCOREDUMP(status) ? " (core dumped)" : ""); else if (WIFSTOPPED(status)) fprintf(stderr,"Process %ld stopped by signal %d/n", (long)pid, WSTOPSIG(status)); else fprintf(stderr,"Unexpected status - pid=%ld, status=0x%x/n", (long)pid, status); } return count; } void sighandler(int sig) { if (sig != SIGCHLD) signo = sig; } int main() { struct sigaction act; int sigcount = 0; act.sa_handler = sighandler; act.sa_flags = 0; sigemptyset(&act.sa_mask); sigaction(SIGINT,&act,NULL); sigaction(SIGQUIT,&act,NULL); sigaction(SIGTERM,&act,NULL); sigaction(SIGTSTP,&act,NULL); sigaction(SIGCHLD,&act,NULL); fprintf(stderr,"Starting background job 'sleep 60'/n"); bgjob = spawn_background_command("sleep 60", NULL, NULL, NULL); if (bgjob < 0) { perror("spawn_background_command"); exit(1); } fprintf(stderr,"Background job started with id %ld/n", (long)bgjob); while (bgjob >= 0) { if (signo) { fprintf(stderr,"Signal %d caught/n", signo); if (sigcount++) kill_job(bgjob, SIGKILL); else { kill_job(bgjob, SIGTERM); kill_job(bgjob, SIGCONT); } } if (!check_children()) pause(); } fprintf(stderr,"Done - exiting/n"); return 0; } |
unix编程常见问题
最新推荐文章于 2024-07-25 20:45:01 发布