要父进程知道子进程退出,这太容易了,但是要子进程知道父进程退出,可有点麻烦。
父进程如果退出,子进程如何知道呢,最笨的方法,父子进程之间建立socket连接,然后建立心跳,没隔1秒测试一把,当然太笨了,通过管道,可以吗?如何做?有更加简单的方法吗?
欢迎高手提出最简单的办法
rc_hz 回复于:2003-08-11 11:52:01
肯定不能这样实现,否则程序性能没办法保证
yuonunix 回复于:2003-08-11 12:27:55
我目前想的方法,只有用个管道,父进程每隔一秒向管道中写,子进程接收,子进程一段时间从管道中读不到字符,就认为父进程已经退出。但是这方法实在笨,想一想,又想不出好办法。要不,在子进程中使用ps命令,这方法也很土呀。
flw 回复于:2003-08-11 12:54:00
可以考虑在父进程退出时给子进程发送信号。SIGUSR1。
fieryfox 回复于:2003-08-11 12:57:06
你的需求到底是什么?父进程退出了,子进程也会退出,此时会收到SIGTERM信号。 如果在子进程里setpgrp,那可以用所有的IPC手段去通知子进程,flw的比较实用。
yuonunix 回复于:2003-08-11 13:08:26
引用:原帖由 "flw"]可以考虑在父进程退出时给子进程发送信号。SIGUSR1。 发表: 这只能处理父进程正常退出时的情况,如果父进程不是正常退出,而是遭遇了”kill -9 进程号“退出时,父进程就来不及用SIGUSR1通知子进程,就会退出,子进程照样不知道父进程的状态改变。因而这种方法好象没有办法实现“可靠地通知子进程”。
fieryfox 回复于:2003-08-11 13:11:30
呵呵,kill -9谁也处理不了的,只能认命。这是满门抄斩的招法。
qjlemon 回复于:2003-08-11 13:12:38
flw的办法还是挺实用的。 试试开个管道,子进程监视管道是否还开着。不需要实际的读写,只是监视它的状态 或者父进程锁一个文件,子进程如果"不“能得到锁,说明父进程它老人家还健在。
yuonunix 回复于:2003-08-11 13:15:48
引用:原帖由 "fieryfox" 发表: 你的需求到底是什么?父进程退出了,子进程也会退出,此时会收到SIGTERM信号。 如果在子进程里setpgrp,那可以用所有的IPC手段去通知子进程,flw的比较实用。
哦,你的意思是如果父进程退出,子进程如果没有setpgrp,就会收到SIGTERM信号,是吗?我马上尝试一下。谢谢了!
qjlemon 回复于:2003-08-11 13:33:29
引用:原帖由 "fieryfox" 发表: 你的需求到底是什么?父进程退出了,子进程也会退出,此时会收到SIGTERM信号。 如果在子进程里setpgrp,那可以用所有的IPC手段去通知子进程,flw的比较实用。 在BSD下试了一下似乎不中啊!
static void sig_term(int);
int
main(void)
{
pid_t pid;
if (signal(SIGTERM, sig_term) == SIG_ERR) {
printf("can't catch SIGTERM/n");
exit(1);
}
pid = fork();
if (pid >; 0) {
sleep(1);
exit(0);
}
for ( ; ; )
sleep(1);
}
static void
sig_term(int signo)
{
printf("received signal %d/n", signo);
return;
}
qjlemon 回复于:2003-08-11 13:38:03
父进程退出了,子进程也会退出???
qjlemon 回复于:2003-08-11 13:41:52
yuonunix 回复于:2003-08-11 14:17:12
引用:原帖由 "yuonunix" 发表:
哦,你的意思是如果父进程退出,子进程如果没有setpgrp,就会收到SIGTERM信号,是吗?我马上尝试一下。谢谢了!
为了验证这一点,我编了如下程序: #include <signal.h>; #include <sys/types.h>; #include <unistd.h>; #include <stdio.h>; int main(void) {
register int pid; if((pid = fork())<0) { printf("error/n"); } else if(pid!=0) { printf("begin sub progress/n"); execl("/export/home/appsvr/tt/main1",NULL); } while(1) { };
}
启动父进程后,去查子进程信息 ps -ef|grep main1去查,子进程的进程名称已经不是main1了,请问怎样去查子进程的信息。
yuonunix 回复于:2003-08-11 14:23:50
引用:原帖由 "qjlemon"] 发表: 父进程退出了,子进程也会退出,此时会收到SIGTERM信号。其意思是: 父进程退出时,子进程会收到SIGTERM信号。(子进程应该能够捕捉到SIGTERM信号)
而qjlemon的例子是:父进程退出,然后父进程捕捉SIGTERM信号,是否和fieryfox的意思不符合呀
yuxq 回复于:2003-08-11 14:34:59
good
gadfly 回复于:2003-08-11 14:39:12
父进程退出的时候,不会自动发送sigterm信号的。
qjlemon的例子是正确的。信号响应函数对子进程同样有效
yuonunix 回复于:2003-08-11 15:01:00
所以到现在为止,最简单的方法还是:
试试开个管道,子进程监视管道是否还开着。不需要实际的读写,只是监视它的状态 或者父进程锁一个文件,子进程如果"不“能得到锁,说明父进程它老人家还健在。
即,要实现准确的判断父进程状态,没有很好的方法,只能这样:
子进程每隔一秒检查一下锁(或者管道),而无法得到通知罗。
gadfly 回复于:2003-08-11 15:05:37
管道方式是不错, 不过不用不时的poll。
一般网络服务都用select监听io,加到fd_set里就好了。
如果是其它的模式,用异步io,响应sigio信号应该是可以的。
fieryfox 回复于:2003-08-11 15:45:15
faint,搞错了。当console了。 既然这样,那也不需要什么IPC,用getppid监控ppid的值,什么时候为1 了,父进程就退出了。
qjlemon 回复于:2003-08-11 15:51:18
试验通过 :) 不过这就只能循环测试了
fieryfox 回复于:2003-08-11 15:57:58
楼主要在kill -9的情况下都行,只能这样了。如果不是-9,还可以用信号USR1一类的方法。
yuonunix 回复于:2003-08-11 16:13:58
引用:原帖由 "fieryfox" 发表: faint,搞错了。当console了。 既然这样,那也不需要什么IPC,用getppid监控ppid的值,什么时候为1 了,父进程就退出了。
这个方法确实是所有方法中最简单的方法,如果求简单,就可以用这个了,如果求效率,可能就是如gadfly所言,用管道,然后select一把,所花的CPU应该是最小的了。
yuonunix 回复于:2003-08-12 11:28:16
我再问个相关问题,如果C是B的子进程,B是A的子进程,C可以通过getppid知道 B 的状态,B可以通过getppid知道A的状态,那么C怎样可以知道A的状态(A是否已经退出)呢?我现在想到的解决办法是getppid 和 kill -USR1相结合,
孙子直接获取祖父进程的状态,好像没有比“getppid + kill -USR1”更简单的方法,各位是否这样认为?
fieryfox 回复于:2003-08-12 11:46:29
你在做什么应用,怎么会有这样的需求?
yuonunix 回复于:2003-08-12 12:52:13
引用:原帖由 "gadfly" 发表: 父进程退出的时候,不会自动发送sigterm信号的。
qjlemon的例子是正确的。信号响应函数对子进程同样有效 你这句话只对了一半,信号响应函数对子进程不是全有效的。而在上面的例子里,恰好是无效的。看完下面的就会明白。 Signals set to the default action (SIG_DFL) in the calling process image are set to the default action in the new pro- cess image (see signal(3C)). Signals set to be ignored (SIG_IGN) by the calling process image are set to be ignored by the new process image. Sig- nals set to be caught by the calling process image are set to the default action in the new process image (see signal(3HEAD)). After a successful call to any of the exec functions, alternate signal stacks are not preserved and the SA_ONSTACK flag is cleared for all signals.
qjlemon 回复于:2003-08-12 12:55:54
你自己试一下那段代码就知道了。
yuonunix 回复于:2003-08-12 12:57:27
引用:原帖由 "fieryfox"]你在做什么应用,怎么会有这样的需求? 发表: 我在做点Solaris上的研究。 :)
qjlemon 回复于:2003-08-12 12:59:19
fork和exec是有重大区别的
yuonunix 回复于:2003-08-12 13:31:43
引用:原帖由 "qjlemon"]fork和exec是有重大区别的 发表: 我尝试了一下,发现如此注册的信号,的确对父亲进程和孩子进程都有效,不好意思,我混淆了exec、fork对信号的处理方式,谢谢qjlemon。
qjlemon 回复于:2003-08-12 13:33:53
不要光看书 :)
蓝色键盘 回复于:2003-08-12 18:34:48
这个问题以前好像有人问过。
偶目前采用的办法是:
在具有亲缘关系的进程之间建立无名管道。这些进程通过这些管
道传递简单的和少量的数据(也可以传递描述字)。然后,亲缘
关系的进程都采用select来处理管道上发生的读写条件。此时,
不论那个进程core了,或者有人kill
-9了。对方的进程的select读会成功返回,此时根据返回的数值
便可判断那个进程异常了,或者被他人干掉了。
这个方法偶一直用,觉得很好使,大家可以自己试试。
jimyao 回复于:2003-08-13 16:04:35
对于父进程已经终止的所有进程,它们都有一个叫init的进程领养,作为它的的父进程,只需要判断,这些进程的父进程的ID是否是1就可以了。
这个办法也行。children轮训自己的getppid()的结果,然后更具结果判断是否父亲被人干掉! :twisted:
codan 回复于:2003-08-20 18:24:44
看了关于那篇关于子进程如何知道父进程退出的帖子写了下面的代码(没有出错检测)。 下面的代码中的子进程将处于阻塞状态,不用线程可能让子进程能进行自己的工作吗(如:子进程输出不断输出一个字符'a',同时又观察父进程状态)?
#include<stdio.h>;
#include<unistd.h>;
#include<stdlib.h>;
int main()
{
pid_t pipes[2];
int fork_result;
int read_res;
pipe(pipes); /*建立管道*/
fork_result=fork();
if(fork_result==0){ /*子进程*/
close(pipes[1]); /*关闭管道写*/
read_res=read(pipes[0],0,1);
if(read_res==0)
printf("Parent is closed/n");
}else{ /*父进程5秒后退出*/
sleep(5);
exit(EXIT_SECCESS);
}
}
gadfly 回复于:2003-08-21 21:18:38
子进程可以定时去读呀。
或者循环的时候,在某个特定的时候检查。
如果是监听socket连接上的数据,也可以select来监听。
superchao 回复于:2003-10-30 18:44:05
首先子进程使用getppid可以知道父进程的PID, 使用kill ( PID, 0)如果返回<0则说明父进程不在了! 或者判断getppid()返回的是否初始化进程的进程号1, 如果是,则说明父进程不在了
superchao 回复于:2003-10-30 19:30:13
还是使用kill ( 父进程号,0)判断吧,
flyingbear 回复于:2003-10-30 20:12:08
getppid 如果父进程退出,则结果将 = 1.这个方案不知道有什么漏洞没
qjlemon 回复于:2003-10-31 07:51:38
这个时候ppid=1是正确的,因为该进程的父进程确确实实就是1号进程。 ———一个进程的父进程并不是永远不变的。
hbczjzc 回复于:2003-10-31 09:55:44
第一部分--基本知识 一.进程概念:
进程定义了一计算的基本单位,是一个执行某一特定程序的实体.它拥有自己的地址空间,执行堆栈,文件描述符表等。
二.创建进程:
1.fork()函数原型 #include <sys/types.h>; #include <unistd.h>; pid_t fork(viod);
2.返回值 子进程为0,父进程为子进程id,出错为-1.
3.fork()函数用法
两个用法: 一个父进程通过复制自己,使父子进程同时执行不同的代码段.这对网络服务进程是常见的:父进程等待客户的服务请求.当这种请求到达时,父进程调用fork() 函数,使子进程处理此请求.父进程则继续等待下一个客户服务请求. 每个进程要执行不同的程序,这会shell是常见的,在这种情况下,子进程在从fork()函数返回后立即调用exec()函数执行其他程序.
用法: #include <sys/types.h>; #include <unistd.h>; main() { pid_t pid;
if((pid=fork())>;0) { /*parent process*/ } else if(pid==0) { /*child process*/ exit(0); } else { printf("fork error/n"); eixt(0); } }
4.进程终止 两种可能 --父进程先于子进程终止. 所有子进程的父进程改变为init进程称为进程由init进程领养.
--子进程先于父进程终止. 父进程调用wait()函数或者waitpid()函数可以得到子进程的进程id,进程的终止状态,以及进程使用的cpu时间总量.
进程正常或者异常终止,系统内核就向其父进程发送SIGCHLD信号.
5.进程终止调用的函数
1.wait()函数 等待子进程返回并修改状态 #include <sys/types.h>; #include <sys/wait.h>; pid_t wait(int *stat_loc); 允许调用进程取得子进程的状态信息。调用进程将会挂起直到其一个子进程终止. 返回值为该子进程进程号,否则返回-1,同时stat_loc返回子进程的返回值. 用法: #include <sys/type.h>; #include <sys/wait.h>; main() { pid_t pid; if ((pid=fork())>;0) { /*parent process*/ int child_status; wait(&child_status); } else if(pid==0) { /*child process*/ exit(0); } else { printf("fork error/n"); exit(0); } }
2.waitpid()函数 等待指定进程的子进程返回,并修改状态. #include <sys/types.h>; #include <sys/wait.h>; pid_t waitpid(pid_t pid,int *stat_loc,int options); 当pid等于-1,options等于0时,该函数等同于wait()函数。
否则该函数的行为由参数pid和options决定。 pid指定了父进程要求知道那些子进程的状态,其中: =-1 要求知道任何一个子进程的返回状态; >;0 要求知道进程号为pid的子进程的状态; <-1 要求知道进程组号为pid的绝对值的子进程的状态.
options参数为以位方式表示的标志,它是各个标志以“或”运算组成的位图,每个标志以字节中某个位置1表示:
WUNTRACED 报告任何未知但已停止运行的指定进程的子进程状态,该进程的状态自停止运行时起就没有被报告过.
WCONTINUED 报告任何继续运行的指定进程的子进程状态,该子进程的状态自继续运行起就没有被报告过
WNONANG 若调用本函数时,指定进程的子进程状态,目前并不是立即有效的(即可被立即读取的),调用进程不被挂起执行。
WNOWAIT 保持那些将其状态设置在stat_loc()函数的进程处于可等待状态,该进程将直到下次被要求其返回状态值.
返回值为该子进程号,否则为-1,同时stat_loc()函数返回子进程的返回值 用法:
#include <sys/types.h>; #include <sys/wait.h>; main() { pid_t pid; if ((pid=fork())>;0) { /*parent process*/ int child_status; pid_t child_status; waitpid(child_pid,&child_status,0); } else if (pid==0) { /*child process*/ exit(0); } else { printf("fork error"); exit(0); } }
exit()函数 终止进程,并返回状态。 #include<stdlib.h>; viod eixt(int status); 函数终止调用进程,exit()函数会关闭所有进程打开的描述符,向其父进程发送 SIGCHLD信号,并返回状态,父进程可通过调用wait()或者waitpid()函数获得其状态
第二部分--多进程80banner扫描器的具体实现
一.思路:
1.多ip处理模块 利用strtok()函数来分离ip strtok(ip,"."); for来循环ip
2.usage提示模块 int usage(char *pro) { printf("fork 80 banner scanner"); printf("usage:%s [startip] [stopip]/n",pro); }
3.扫描模块 viod scanip(char sip[20])
4.多进程及处理模块 fork()
二.实现
/******************************************************************** **fork() 80 banner scanner ver1.0 * *to complie: *user$gcc -o 80scaner 80scanner.c * *to use: *user$./80scanner start_ip stop_ip (the best input c class ip otherwise *waste too many system resource ) * *coded by nightcat *june 2003
*********************************************************************/ /*所要的文件*/ #include <sys/types.h>; #include <sys/wait.h>; #include <stdio.h>; #include <string.h>; #include <sys/socket.h>; #include <netinet/in.h>; #include <errno.h>; #include <netdb.h>; #include <signal.h>;
/*声明函数*/ int usage(char *pro); void scanip(char *sip);
int main(int argc,char *argv[]) { /*声明变量*/ int child_status; pid_t child_id; pid_t pid; char *pro; int count;
char *start_ip,*stop_ip;
char *chge; char scan_ip[20];
int ip1,ip2,ip3,ip4; int end1,end2,end3,end4;
/*输入参数判断*/ if(argc<3){ usage(argv[0]); exit(1); }
/*处理ip*/ start_ip=argv[1]; chge=strtok(start_ip,"."); ip1=atoi(chge); chge=strtok(NULL,"."); ip2=atoi(chge); chge=strtok(NULL,"."); ip3=atoi(chge); chge=strtok(NULL,"."); ip4=atoi(chge);
stop_ip=argv[2]; chge=strtok(stop_ip,"."); end1=atoi(chge); chge=strtok(NULL,"."); end2=atoi(chge); chge=strtok(NULL,"."); end3=atoi(chge); chge=strtok(NULL,"."); end4=atoi(chge);
/*循环扫描*/ for(count=ip4;count<=end4;count++){ sprintf(scan_ip,"%d.%d.%d.%d",ip1,ip2,ip3,count); /*产生进程*/ if((pid=fork())==0){ scanip(scan_ip); exit(0); } } }
/*用法函数*/ int usage(char *pro){ printf("fork() 80 banner scanner/n"); printf("input c class ip"); printf("usage:%s [start_ip][stop_ip]/n",pro); }
/*扫描函数*/ void scanip(char sip[20]){ struct sockaddr_in addr; int s; char buffer[1024]; if((s=socket(AF_INET,SOCK_STREAM,0))<0){ perror("socket error/n"); exit(1); } addr.sin_family=AF_INET; addr.sin_addr.s_addr=inet_addr(sip); addr.sin_port=htons(80); if(connect(s,(struct sockaddr *)&addr,sizeof(addr))<0){ perror("connect error"); exit(1); } send(s,"HEAD / HTTP/1.0/n/n",17,0); recv(s,buffer,sizeof(buffer),0); printf("%s's http version:/n%s",sip,buffer); close(s); sleep(5); exit(0); }
三.总结:
调试的问题:
1.sprintf(scan_ip,"%d.%d.%d.%d",ip1,ip2,ip3,count);这条语句提示溢出,主要是scan_ip分配的内存空间不够,由原来char *scan_ip 改为char scan_ip[20] 问题解决
2.send(s,"HEAD / HTTP/1.0/n/n",17,0);这条语句发送后没有得到回归的信息,只要是HTTP/1.0少了两个回车/n/n,加上就可以啦!
其中新学的东西有: 1.ip的处理方法 2.多进程的使用方法
hbczjzc 回复于:2003-10-31 10:05:09
1. Process Control 进程控制 1.1 Creating new processes: fork() 创建新进程:fork函数 1.1.1 What does fork() do? fork函数干什么? 1.1.2 What's the difference between fork() and vfork()? fork函数 与 vfork函数的区别在哪里? 1.1.3 Why use _exit rather than exit in the child branch of a fork? 为何在一个fork的子进程分支中使用_exit函数而不使用exit函数? 1.2 Environment variables 环境变量 1.2.1 How can I get/set an environment variable from a program? 我怎样在程序中获得/设置环境变量? 1.2.2 How can I read the whole environment? 我怎样读取整个环境变量表? 1.3 How can I sleep for less than a second? 我怎样睡眠小于一秒? 1.4 How can I get a finer-grained version of alarm()? 我怎样得到一个更细分时间单位的alarm函数版本(译者注:希望alarm的时间小于一秒)? 1.5 How can a parent and child process communicate? 父子进程如何通信? 1.6 How do I get rid of zombie processes? 我怎样去除僵死进程? 1.6.1 What is a zombie? 何为僵死进程? 1.6.2 How do I prevent them from occuring? 我怎样避免它们的出现? 1.7 How do I get my program to act like a daemon? 我怎样使我的程序作为守护程序运行? 1.8 How can I look at process in the system like ps does? 我怎样象ps程序一样审视系统的进程? 1.9 Given a pid, how can I tell if it's a running program? 给定一个进程号(译者注:pid: process ID),我怎样知道它是个正在运行的程序? 1.10 What's the return value of system/pclose/waitpid? system函数,pclose函数,waitpid函数 的返回值是什么? 1.11 How do I find out about a process' memory usage? 我怎样找出一个进程的存储器使用情况? 1.12 Why do processes never decrease in size? 为什么进程的大小不缩减? 1.13 How do I change the name of my program (as seen by ****ps')? 我怎样改变我程序的名字(即“ps”看到的名字)? 1.14 How can I find a process' executable file? 我怎样找到进程的相应可执行文件? 1.14.1 So where do I put my configuration files then? 那么,我把配置文件放在哪里呢? 1.15 Why doesn't my process get SIGHUP when its parent dies? 为何父进程死时,我的进程未得到SIGHUP信号? 1.16 How can I kill all descendents of a process? 我怎样杀死一个进程的所有派生进程?
2. General File handling (including pipes and sockets) 一般文件操作(包括管道和套接字) 2.1 How to manage multiple connections? 怎样管理多个连接? 2.1.1 How do I use select()? 我怎样使用select()? 2.1.2 How do I use poll()? 我怎样使用poll() ? 2.1.3 Can I use SysV IPC at the same time as select or poll? 我是否可以将SysV 进程间通信 (译者注:IPC: Interprocess Communications) 与select或poll同 时使用? 2.2 How can I tell when the other end of a connection shuts down? 我怎么知道连接的另一端已关闭? 2.3 Best way to read directories? 读目录的最好方法? 2.4 How can I find out if someone else has a file open? 我怎么知道其他人已经打开一个文件? 2.5 How do I ****lock' a file? 我怎样锁定一个文件? 2.6 How do I find out if a file has been updated by another process? 我怎么知道一个文件是否已被其他进程更新? 2.7 How does the ****du' utility work? “du”工具程序是怎么工作的? 2.8 How do I find the size of a file? 我怎么知道一个文件的大小? 2.9 How do I expand ****~' in a filename like the shell does? 我怎样象shell程序一样将一个文件名中含有的“~”展开? 2.10 What can I do with named pipes (FIFOs)? 我能用有名管道(FIFOs)(译者注:FIFO: First In First Oout)干什么? 2.10.1 What is a named pipe? 什么是有名管道? 2.10.2 How do I create a named pipe? 我怎样创建一个有名管道? 2.10.3 How do I use a named pipe? 我怎样使用一个有名管道? 2.10.4 Can I use a named pipe across NFS? 我能基于网络文件系统(译者注:NFS:Network File System)使用有名管道吗? 2.10.5 Can multiple processes write to the pipe simultaneously? 多个进程能否同时向这个管道写执行写操作? 2.10.6 Using named pipes in applications 在应用程序中使用有名管道。
3. Terminal I/O 终端输入/输出(I/O:input/output) 3.1 How can I make my program not echo input? 我怎样使我的程序不回射输入? 3.2 How can I read single characters from the terminal? 我怎样从终端读取单个字符? 3.3 How can I check and see if a key was pressed? 我怎样检查是否一个键被摁下? 3.4 How can I move the cursor around the screen? 我怎样将光标在屏幕里移动? 3.5 What are pttys? pttys(pttys:Pseudo-teletypes)是什么? 3.6 How to handle a serial port or modem? 怎样控制一个串行口和调制解调器(译者注:modem: modulate-demodulate) 3.6.1 Serial device names and types 串行设备和类型 3.6.2 Setting up termios flags 设置termios的标志位 3.6.2.1 c_iflag 3.6.2.2 c_oflag 3.6.2.3 c_cflag 3.6.2.4 c_lflag 3.6.2.5 c_cc
4. System Information 系统信息 4.1 How can I tell how much memory my system has? 我怎样知道我的系统有多少存储器容量? 4.2 How do I check a user's password? 我怎样检查一个用户的口令? 4.2.1 How do I get a user's password? 我怎样得到一个用户的口令? 4.2.2 How do I get shadow passwords by uid? 我怎样通过用户号(译者注:uid: User ID)得到阴影口令文件中的口令? 4.2.3 How do I verify a user's password? 我怎样核对一个用户的口令?
5. Miscellaneous programming 编程杂技 5.1 How do I compare strings using wildcards? 我怎样使用通配字符比较字符串? 5.1.1 How do I compare strings using filename patterns? 我怎样使用文件名通配模式比较字符串? 5.1.2 How do I compare strings using regular expressions? 我怎样使用正则表达式比较字符串? 5.2 What's the best way to send mail from a program? 什么是在程序中发送电子邮件的最好方法? 5.2.1 The simple method: /bin/mail 简单方法:/bin/mail 5.2.2 Invoking the MTA directly: /usr/lib/sendmail 直接启动邮件传输代理(译者注:MTA: mail transfer agent):/usr/bin/sendmail 5.2.2.1 Supplying the envelope explicitly 显式提供收件人信息 5.2.2.2 Allowing sendmail to deduce the recipients 允许sendmail程序根据邮件内容分析出收件人
6. Use of tools 工具的使用 6.1 How can I debug the children after a fork? 我怎样调试fork函数产生的子进程? 6.2 How to build library from other libraries? 怎样通过其他库文件建立新的库文件? 6.3 How to create shared libraries / dlls? 怎样创建动态连接库/dlls? 6.4 Can I replace objects in a shared library? 我能更改一个动态连接库里的目标吗? 6.5 How can I generate a stack dump from within a running program? 我能在一个运行着的程序中生成堆栈映象吗?
1. 进程控制 ***********
1.1 创建新进程:fork函数 ========================
1.1.1 fork函数干什么? ----------------------
#include <sys/types.h>; #include <unistd.h>;
pid_t fork(void);
‘fork()’函数用于从已存在进程中创建一个新进程。新进程称为子进程,而原进程称为 父进程。你可以通过检查‘fork()’函数的返回值知道哪个是父进程,哪个是子进程。父 进程得到的返回值是子进程的进程号,而子进程则返回0。以下这个范例程序说明它的基本 功能:
pid_t pid;
switch (pid = fork()) { case -1: /* 这里pid为-1,fork函数失败 */ /* 一些可能的原因是 */ /* 进程数或虚拟内存用尽 */ perror("The fork failed!"); break;
case 0: /* pid为0,子进程 */ /* 这里,我们是孩子,要做什么? */ /* ... */ /* 但是做完后, 我们需要做类似下面: */ _exit(0);
default: /* pid大于0,为父进程得到的子进程号 */ printf("Child's pid is %d/n",pid); }
当然,有人可以用‘if() ... else ...’语句取代‘switch()’语句,但是上面的形式是 一个有用的惯用方法。
知道子进程自父进程继承什么或未继承什么将有助于我们。下面这个名单会因为 不同Unix的实现而发生变化,所以或许准确性有了水份。请注意子进程得到的是 这些东西的 *拷贝*,不是它们本身。
由子进程自父进程继承到:
* 进程的资格(真实(real)/有效(effective)/已保存(saved) 用户号(UIDs)和组号(GIDs))
* 环境(environment)
* 堆栈
* 内存
* 打开文件的描述符(注意对应的文件的位置由父子进程共享,这会引起含糊情况)
* 执行时关闭(close-on-exec) 标志 (译者注:close-on-exec标志可通过fnctl()对文件描 述符设置,POSIX.1要求所有目录流都必须在exec函数调用时关闭。更详细说明, 参见<<UNIX环境高级编程>;>; W. R. Stevens, 1993, 尤晋元等译(以下简称<<高级编 程>;>;, 3.13节和8.9节)
* 信号(signal)控制设定
* nice值 (译者注:nice值由nice函数设定,该值表示进程的优先级,数值越小,优 先级越高)
* 进程调度类别(scheduler class) (译者注:进程调度类别指进程在系统中被调度时所 属的类别,不同类别有不同优先级,根据进程调度类别和nice值,进程调度程序可计 算出每个进程的全局优先级(Global process prority),优先级高的进程优先执行)
* 进程组号
* 对话期ID(Session ID) (译者注:译文取自<<高级编程>;>;,指:进程所属的对话期 (session)ID, 一个对话期包括一个或多个进程组, 更详细说明参见<<高级编程>;>; 9.5节)
* 当前工作目录
* 根目录 (译者注:根目录不一定是“/”,它可由chroot函数改变)
* 文件方式创建屏蔽字(file mode creation mask (umask)) (译者注:译文取自<<高级编 程>;>;,指:创建新文件的缺省屏蔽字)
* 资源限制
* 控制终端
子进程所独有:
* 进程号
* 不同的父进程号(译者注:即子进程的父进程号与父进程的父进程号不同,父进 程号可由getppid函数得到)
* 自己的文件描述符和目录流的拷贝(译者注:目录流由opendir函数创建,因其为 顺序读取,顾称“目录流”)
* 子进程不继承父进程的进程,正文(text),数据和其它锁定内存(memory locks) (译者注:锁定内存指被锁定的虚拟内存页,锁定后,不允许内核将其在必要时 换出(page out),详细说明参见<<The GNU C Library Reference Manual>;>; 2.2版, 1999, 3.4.2节)
* 在tms结构中的系统时间(译者注:tms结构可由times函数获得,它保存四个数据 用于记录进程使用中央处理器(CPU:Central Processing Unit)的时间,包括:用户时 间,系统时间,用户各子进程合计时间,系统各子进程合计时间)
* 资源使用(resource utilizations)设定为0
* 阻塞信号集初始化为空集(译者注:原文此处不明确,译文根据fork函数手册页 稍做修改)
* 不继承由timer_create函数创建的计时器
* 不继承异步输入和输出
1.1.2 fork函数 与 vfork函数的区别在哪里里? -------------------------------------------
有些系统有一个系统调用‘vfork()’,它最初被设计成‘fork()’的较少额外支出 (lower-overhead)版本。因为‘fork()’包括拷贝整个进程的地址空间,所以非常 “昂贵”,这个‘vfork()’函数因此被引入。(在3.0BSD中)(译者注:BSD: Berkeley Software Distribution)
*但是*,自从‘vfork()’被引入,‘fork()’的实现方法得到了很大改善,最值得 注意的是“写操作时拷贝”(copy-on-write)的引入,它是通过允许父子进程可访问 相同物理内存从而伪装(fake)了对进程地址空间的真实拷贝,直到有进程改变内 存中数据时才拷贝。这个提高很大程度上抹杀了需要‘vfork()’的理由;事实上, 一大部份系统完全丧失了‘vfork()’的原始功能。但为了兼容,它们仍然提供 ‘vfork()’函数调用,但它只是简单地调用‘fork()’,而不试图模拟所有‘vfork()’ 的语义(semantics, 译文取自<<高级编程>;>;,指定义的内容和做法)。
结论是,试图使用任何‘fork()’和‘vfork()’的不同点是*很*不明智的。事实上, 可能使用‘vfork()’根本就是不明智的,除非你确切知道你想*干什么*。
两者的基本区别在于当使用‘vfork()’创建新进程时,父进程将被暂时阻塞,而 子进程则可以借用父进程的地址空间。这个奇特状态将持续直到子进程要么退 出,要么调用‘execve()’,至此父进程才继续执行。
这意味着一个由‘vfork()’创建的子进程必须小心以免出乎意料地改变父进程的 变量。特别的,子进程必须不从包含‘vfork()’调用的函数返回,而且必须不调 用‘exit()’(如果它需要退出,它需要使用‘_exit()’;事实上,对于使用正常 ‘fork()’创建的子进程这也是正确的)(译者注:参见1.1.3)
1.1.3 为何在一个fork的子进程分支中使用_exit函数而不使用exit函数? -----------------------------------------------------------------
‘exit()’与‘_exit()’有不少区别在使用‘fork()’,特别是‘vfork()’时变得很 突出。
‘exit()’与‘_exit()’的基本区别在于前一个调用实施与调用库里用户状态结构 (user-mode constructs)有关的清除工作(clean-up),而且调用用户自定义的清除程序 (译者注:自定义清除程序由atexit函数定义,可定义多次,并以倒序执行),相对 应,后一个函数只为进程实施内核清除工作。
在由‘fork()’创建的子进程分支里,正常情况下使用‘exit()’是不正确的,这是 因为使用它会导致标准输入输出(译者注:stdio: Standard Input Output)的缓冲区被 清空两次,而且临时文件被出乎意料的删除(译者注:临时文件由tmpfile函数创建 在系统临时目录下,文件名由系统随机生成)。在C++程序中情况会更糟,因为静 态目标(static objects)的析构函数(destructors)可以被错误地执行。(还有一些特殊情 况,比如守护程序,它们的*父进程*需要调用‘_exit()’而不是子进程;适用于绝 大多数情况的基本规则是,‘exit()’在每一次进入‘main’函数后只调用一次。)
在由‘vfork()’创建的子进程分支里,‘exit()’的使用将更加危险,因为它将影响 *父*进程的状态。
1.2 环境变量 ============
1.2.1 如何从程序中获得/设置环境变量? -------------------------------------- 获得一个环境变量可以通过调用‘getenv()’函数完成。
#include <stdlib.h>;
char *getenv(const char *name);
设置一个环境变量可以通过调用‘putenv()’函数完成。
#include <stdlib.h>;
int putenv(char *string);
变量string应该遵守"name=value"的格式。已经传递给putenv函数的字符串*不*能够被 释放或变成无效,因为一个指向它的指针将由‘putenv()’保存。这意味着它必须是 在静态数据区中或是从堆(heap)分配的。如果这个环境变量被另一个‘putenv()’的 调用重新定义或删除,上述字符串可以被释放。
/* 译者增加:
因为putenv()有这样的局限,在使用中经常会导致一些错 误,GNU libc 中还包括了两个BSD风格的函数: #include <stdlib.h>; int setenv(const char *name, const char *value, int replace); void unsetenv(const char *name);
setenv()/unsetenv()函数可以完成所有putenv()能做的事。setenv() 可以不受指针 限制地向环境变量中添加新值,但传入参数不能为空(NULL)。当replace为0时,如 果环境变量中已经有了name项,函数什么也不做(保留原项),否则原项被覆盖。 unsetenv()是用来把name项从环境变量中删除。注意:这两个函数只存在在BSD和GNU 库中,其他如SunOS系统中不包括它们,因此将会带来一些兼容问题。我们可以用 getenv()/putenv()来实现:
int setenv(const char *name, const char *value, int replace) { char *envstr;
if (name == NULL || value == NULL) return 1; if (getenv(name) !=NULL) { envstr = (char *) malloc(strlen(name) + strlen(value) + 2); sprintf (envstr, "%s=%s", name, value); if (putenv(envstr)); return 1; } return 0; } */
记住环境变量是被继承的;每一个进程有一个不同的环境变量表拷贝(译者注: 从core文件中我们可以看出这一点)。结果是,你不能从一个其他进程改变当前 进程的环境变量,比如shell进程。
假设你想得到环境变量‘TERM’的值,你需要使用下面的程序:
char *envvar;
envvar=getenv("TERM");
printf("The value for the environment variable TERM is "); if(envvar) { printf("%s/n",envvar); } else { printf("not set./n"); }
现在假设你想创建一个新的环境变量,变量名为‘MYVAR’,值为‘MYVAL’。 以下是你将怎样做:
static char envbuf[256];
sprintf(envbuf,"MYVAR=%s","MYVAL");
if(putenv(envbuf)) { printf("Sorry, putenv() couldn't find the memory for %s/n",envbuf); /* Might exit() or something here if you can't live without it */ }
1.2.2 我怎样读取整个环境变量表? --------------------------------
如果你不知道确切你想要的环境变量的名字,那么‘getenv()’函数不是很有用。 在这种情况下,你必须更深入了解环境变量表的存储方式。
全局变量,‘char **envrion’,包含指向环境字符串指针数组的指针,每一个字 符串的形式为‘“NAME=value”’(译者注:和putenv()中的“string”的格式相同)。 这个数组以一个‘空’(NULL)指针标记结束。这里是一个打印当前环境变量列表 的小程序(类似‘printenv’)。
#include <stdio.h>;
extern char **environ;
int main() { char **ep = environ; char *p; while ((p = *ep++)) printf("%s/n", p); return 0; }
一般情况下,‘envrion’变量作为可选的第三个参数传递给‘main()’;就是说, 上面的程序可以写成:
#include <stdio.h>;
int main(int argc, char **argv, char **envp) { char *p; while ((p = *envp++)) printf("%s/n", p); return 0; }
虽然这种方法被广泛的操纵系统所支持(译者注:包括DOS),这种方法事实上并 没有被POSIX(译者注:POSIX: Portable Operating System Interace)标准所定义。(一 般的,它也比较没用)
1.3 我怎样睡眠小于一秒? ========================
在所有Unix中都有的‘sleep()’函数只允许以秒计算的时间间隔。如果你想要更 细化,那么你需要寻找替换方法:
* 许多系统有一个‘usleep()’函数
* 你可以使用‘select()’或‘poll()’,并设置成无文件描述符并试验;一个普 遍技巧是基于其中一个函数写一个‘usleep()’函数。(参见comp.unix.questions FAQ 的一些例子)
* 如果你的系统有itimers(很多是有的)(译者注:setitimer和getitimer是两个操作 itimers的函数,使用“man setitimer”确认你的系统支持),你可以用它们自己撺一 个‘usleep()’。(参见BSD源程序的‘usleep()’以便知道怎样做)
* 如果你有POSIX实时(realtime)支持,那会有一个‘nanosleep()’函数。
众观以上方法,‘select()’可能是移植性最好的(直截了当说,它经常比 ‘usleep()’或基于itimer的方法更有效)。但是,在睡眠中捕获信号的做法会有 所不同;基于不同应用,这可以成为或不成为一个问题。
无论你选择哪条路,意识到你将受到系统计时器分辨率的限制是很重要的(一 些系统允许设置非常短的时间间隔,而其他的系统有一个分辨率,比如说10毫 秒,而且总是将所有设置时间取整到那个值)。而且,关于‘sleep()’,你设置 的延迟只是最小值(译者注:实际延迟的最小值);经过这段时间的延迟,会有 一个中间时间间隔直到你的进程重新被调度到。
1.4 我怎样得到一个更细分时间单位的alarm函数版本? ==================================================
当今Unix系统倾向于使用‘setitimer()’函数实现闹钟,它比简单的‘alarm()’函 数具有更高的分辨率和更多的选择项。一个使用者一般需要首先假设‘alarm()’ 和‘setitimer(ITIMER_REAL)’可能是相同的底层计时器,而且假设同时使用两 种方法会造成混乱。
Itimers可被用于实现一次性或重复信号;而且一般有3种不同的计时器可以用:
****ITIMER_REAL' 计数真实(挂钟)时间,然后发送‘SIGALRM’信号
****ITIMER_VIRTUAL' 计数进程虚拟(用户中央处理器)时间,然后发送‘SIGVTALRM’信号
****ITIMER_PROF' 计数用户和系统中央处理器时间,然后发送‘SIGPROF’信号;它供解释器 用来进行梗概处理(profiling)
然而itimers不是许多标准的一部份,尽管它自从4.2BSD就被提供。POSIX实时标 准的扩充定义了类似但不同的函数。
1.5 父子进程如何通信? ======================
一对父子进程可以通过正常的进程间通信的办法(管道,套接字,消息队列,共 享内存)进行通信,但也可以通过利用它们作为父子进程的相互关系而具有的一 些特殊方法。
一个最显然的方法是父进程可以得到子进程的退出状态。
因为子进程从它的父进程继承文件描述符,所以父进程可以打开一个管道的两端, 然后fork,然后父进程关闭管道这一端,子进程关闭管道另一端。这正是你从你的 进程调用‘popen()’函数运行另一个程序所发生的情况,也就是说你可以向 ‘popen()’返回的文件描述符进行写操作而子进程将其当作自己的标准输入,或 者你可以读取这个文件描述符来看子进程向标准输出写了什么。(‘popen()’函数 的mode参数定义你的意图(译者注:mode=“r”为读,mode=“w”为写);如果你 想读写都做,那么你可以并不困难地用管道自己做到)
而且,子进程继承由父进程用mmap函数映射的匿名共享内存段(或者通过映射特 殊文件‘/dev/zero’);这些共享内存段不能从无关的进程访问。
1.6 我怎样去除僵死进程? ========================
1.6.1 何为僵死进程? --------------------
当一个程序创建的子进程比父进程提前结束,内核仍然保存一些它的信息以便父 进程会需要它 - 比如,父进程可能需要检查子进程的退出状态。为了得到这些信 息,父进程调用‘wait()’;当这个调用发生,内核可以丢弃这些信息。
在子进程终止后到父进程调用‘wait()’前的时间里,子进程被称为‘僵死进程’ (‘zombie’)。(如果你用‘ps’,这个子进程会有一个‘Z’出现在它的状态区 里指出这点。)即使它没有在执行,它仍然占据进程表里一个位置。(它不消耗其 它资源,但是有些工具程序会显示错误的数字,比如中央处理器的使用;这是 因为为节约空间进程表的某些部份与会计数据(accounting info)是共用(overlaid)的。)
这并不好,因为进程表对于进程数有固定的上限,系统会用光它们。即使系统没 有用光 ,每一个用户可以同时执行的进程数有限制,它总是小于系统的限制。 顺便说一下,这也正是你需要总是 检查‘fork()’是否失败的一个原因。
如果父进程未调用wait函数而终止,子进程将被‘init’进程收管,它将控制子进 程退出后必须的清除工作。(‘init’是一个特殊的系统程序,进程号为1 - 它实际 上是系统启动后运行的第一个程序),
1.6.2 我怎样避免它们的出现? ----------------------------
你需要却认父进程为每个子进程的终止调用‘wait()’(或者‘waitpid()’, ‘wait3()’,等等); 或者,在某些系统上,你可以指令系统你对子进程的退出状 态没有兴趣。(译者注:在SysV系统上,可以调用signal函数,设置SIGCLD信号为 SIG_IGN,系统将不产生僵死进程, 详细说明参见<<高级编程>;>;10.7节)
另一种方法是*两次*‘fork()’,而且使紧跟的子进程直接退出,这样造成孙子进 程变成孤儿进程(orphaned),从而init进程将负责清除它。欲获得做这个的程序,参 看范例章节的函数‘fork2()’。
为了忽略子进程状态,你需要做下面的步骤(查询你的系统手册页以知道这是否正 常工作):
struct sigaction sa; sa.sa_handler = SIG_IGN; #ifdef SA_NOCLDWAIT sa.sa_flags = SA_NOCLDWAIT; #else sa.sa_flags = 0; #endif sigemptyset(&sa.sa_mask); sigaction(SIGCHLD, &sa, NULL);
如果这是成功的,那么‘wait()’函数集将不再正常工作;如果它们中任何一个被 调用,它们将等待直到*所有*子进程已经退出,然后返回失败,并且 ‘errno==ECHILD’。
另一个技巧是捕获SIGCHLD信号,然后使信号处理程序调用‘waitpid()’或 ‘wait3()’。参见范例章节的完整程序。
1.7 我怎样使我的程序作为守护程序运行? ======================================
一个“守护程序”进程通常被定义为一个后台进程,而且它不属于任何一个终端 会话,(terminal session)。许多系统服务由守护程序实施;如网络服务,打印等。
简单地在后台启动一个程序并非足够是这些长时间运行的程序;那种方法没有正 确地将进程从启动它的终端脱离(detach)。而且,启动守护程序的普遍接受的的方 法是简单地手工执行或从rc脚本程序执行(译者注:rc:runcom);并希望这个守护 程序将其*自身*安置到后台。
这里是成为守护程序的步骤:
1. 调用‘fork()’以便父进程可以退出,这样就将控制权归还给运行你程序的 命令行或shell程序。需要这一步以便保证新进程不是一个进程组头领进程(process group leader)。下一步,‘setsid()’,会因为你是进程组头领进程而失败。
2. 调用‘setsid()’ 以便成为一个进程组和会话组的头领进程。由于一个控制终端 与一个会话相关联,而且这个新会话还没有获得一个控制终端,我们的进程没 有控制终端,这对于守护程序来说是一件好事。
3. 再次调用‘fork()’所以父进程(会话组头领进程)可以退出。这意味着我们,一 个非会话组头领进程永远不能重新获得控制终端。
4. 调用‘chdir("/")’确认我们的进程不保持任何目录于使用状态。不做这个会导 致系统管理员不能卸装(umount)一个文件系统,因为它是我们的当前工作目录。
[类似的,我们可以改变当前目录至对于守护程序运行重要的文件所在目录]
5. 调用‘umask(0)’以便我们拥有对于我们写的任何东西的完全控制。我们不知 道我们继承了什么样的umask。
[这一步是可选的](译者注:这里指步骤5,因为守护程序不一定需要写文件)
6. 调用‘close()’关闭文件描述符0,1和2。这样我们释放了从父进程继承的标 准输入,标准输出,和标准错误输出。我们没办法知道这些文描述符符可能 已经被重定向去哪里。注意到许多守护程序使用‘sysconf()’来确认 ‘_SC_OPEN_MAX’的限制。‘_SC_OPEN_MAX’告诉你每个进程能够打 开的最多文件数。然后使用一个循环,守护程序可以关闭所有可能的文件描 述符。你必须决定你需要做这个或不做。如果你认为有可能有打开的文件描 述符,你需要关闭它们,因为系统有一个同时打开文件数的限制。
7. 为标准输入,标准输出和标准错误输出建立新的文件描述符。即使你不打算 使用它们,打开着它们不失为一个好主意。准确操作这些描述符是基于各自 爱好;比如说,如果你有一个日志文件,你可能希望把它作为标准输出和标 准错误输出打开,而把‘/dev/null’作为标准输入打开;作为替代方法,你可 以将‘/dev/console’作为标准错误输出和/或标准输出打开,而‘/dev/null’作 为标准输入,或者任何其它对你的守护程序有意义的结合方法。(译者注:一 般使用dup2函数原子化关闭和复制文件描述符,参见<<高级编程>;>;3.12节)
如果你的守护程序是被‘inetd’启动的,几乎所有这些步骤都不需要(或不建议 采用)。在那种情况下,标准输入,标准输出和标准错误输出都为你指定为网络 连接,而且‘fork()’的调用和会话的操纵不应做(以免使‘inetd’造成混乱)。只 有‘chdir()’和‘umask()’这两步保持有用。
1.8 我怎样象ps程序一样审视系统的进程? =======================================
你真的不该想做这个。
到目前为止,移植性最好的是调用‘popen(pscmd,"r")’并处理它的输出。(pscmd 应当是类似SysV系统上的‘“ps -ef”’,BSD系统有很多可能的显示选项:选 择一个。)
在范例章节有这个问题的两个完整解决方法;一个适用于SunOS 4,它需要root权 限执行并使用‘kvm_*’例程从内核数据结果读取信息;另一种适用于SVR4系统 (包括Sun OS 5),它使用‘/proc’文件系统。
在具有SVR4.2风格‘/proc’的系统上更简单;只要对于每一个感兴趣的进程号从 文件‘/proc/进程号/psinfo’读取一个psinfo_t结构。但是,这种可能是最清晰的方 法也许又是最不得到很好支持的方法。(在FreeBSD的‘/proc’上,你从 ‘/proc/进程号/status’读取一个半未提供文档说明(semi-undocumented)的可打印字 符串;Linux有一些与其类似的东西)
1.9 给定一个进程号,我怎样知道它是个正在运行的程序? =====================================================
使用‘kill()’函数,而已0作为信号代码(signal number)。
从这个函数返回有四种可能的结果:
* ‘kill()’返回0
- 这意味着一个给定此进程号的进程退出,系统允许你向它发送信号。该进 程是否可以是僵死进程与不同系统有关。
* ‘kill()’返回-1,‘errno == ESRCH’
- 要么不存在给定进程号的进程,要么增强的安全机制导致系统否认它的存 在。(在一些系统上,这个进程有可能是僵死进程。)
* ‘kill()’返回-1,‘errno == EPERM’
- 系统不允许你杀死(kill)这个特定进程。这意味着要么进程存在(它又可能是 僵死进程),要么严格的增强安全机制起作用(比如你的进程不允许发送信号 给*任何人*)。
* ‘kill()’返回-1,伴以其它‘errno’值
- 你有麻烦了!
用的最多的技巧是认为调用“成功”或伴以‘EPERM’的“失败”意味着进程存 在,而其它错误意味着它不存在。
如果你特别为提供‘/proc’文件系统的系统(或所有类似系统)写程序,一个替换 方法存在:检查‘proc/进程号’是否存在是可行的。
1.10 system函数,pclose函数,waitpid函数 的返回值是什么? ==========================================================
‘system()’,‘pclose()’或者‘waitpid()’的返回值不象是我进程的退出值(exit value)(译者注:退出值指调用exit() 或_exit()时给的参数)... 或者退出值左移了8 位...这是怎么搞的?
手册页是对的,你也是对的! 如果查阅手册页的‘waitpid()’你会发现进程的返回 值被编码了。正常情况下,进程的返回值在高16位,而余下的位用来作其它事。 如果你希望可移植,最后更新:2017-04-02 06:51:26
|