`

[精彩] 子进程及时知道父进程已经退出的最简单方案

阅读更多

[精彩] 子进程及时知道父进程已经退出的最简单方案?


http://www.chinaunix.net 作者:yuonunix
<!-- 正文begin -->

要父进程知道子进程退出,这太容易了,但是要子进程知道父进程退出,可有点麻烦。

父进程如果退出,子进程如何知道呢,最笨的方法,父子进程之间建立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下试了一下似乎不中啊!

staticvoidsig_term(int);

int
main(void)
{
pid_tpid;

if(signal(SIGTERM,sig_term)==SIG_ERR){
printf("can'tcatchSIGTERM\n");
exit(1);
}

pid=fork();
if(pid>;0){
sleep(1);
exit(0);
}

for(;;)
sleep(1);
}

staticvoid
sig_term(intsigno)
{
printf("receivedsignal%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>;
intmain(void)
{


registerintpid;
if((pid=fork())<0)
{
printf("error\n");
}
elseif(pid!=0)

{
printf("beginsubprogress\n");
execl("/export/home/appsvr/tt/main1",NULL);
}
while(1)
{
};

}

启动父进程后,去查子进程信息
ps-ef|grepmain1去查,子进程的进程名称已经不是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的例子是正确的。信号响应函数对子进程同样有效


你这句话只对了一半,信号响应函数对子进程不是全有效的。而在上面的例子里,恰好是无效的。看完下面的就会明白。
Signalssettothedefaultaction(SIG_DFL)inthecalling
processimagearesettothedefaultactioninthenewpro-
cessimage(seesignal(3C)).
Signalssettobeignored(SIG_IGN)bythecallingprocess
imagearesettobeignoredbythenewprocessimage.Sig-
nalssettobecaughtbythecallingprocessimageareset
tothedefaultactioninthenewprocessimage(see
signal(3HEAD)).Afterasuccessfulcalltoanyoftheexec
functions,alternatesignalstacksarenotpreservedandthe
SA_ONSTACKflagisclearedforallsignals.


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>;

intmain()
{
pid_tpipes[2];
intfork_result;
intread_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("Parentisclosed\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_tfork(viod);

2.返回值
子进程为0,父进程为子进程id,出错为-1.

3.fork()函数用法

两个用法:
一个父进程通过复制自己,使父子进程同时执行不同的代码段.这对网络服务进程是常见的:父进程等待客户的服务请求.当这种请求到达时,父进程调用fork()
函数,使子进程处理此请求.父进程则继续等待下一个客户服务请求.
每个进程要执行不同的程序,这会shell是常见的,在这种情况下,子进程在从fork()函数返回后立即调用exec()函数执行其他程序.

用法:
#include<sys/types.h>;
#include<unistd.h>;
main()
{
pid_tpid;

if((pid=fork())>;0)
{
/*parentprocess*/
}
elseif(pid==0)
{
/*childprocess*/
exit(0);
}
else
{
printf("forkerror\n");
eixt(0);
}
}

4.进程终止
两种可能
--父进程先于子进程终止.
所有子进程的父进程改变为init进程称为进程由init进程领养.

--子进程先于父进程终止.
父进程调用wait()函数或者waitpid()函数可以得到子进程的进程id,进程的终止状态,以及进程使用的cpu时间总量.


进程正常或者异常终止,系统内核就向其父进程发送SIGCHLD信号.

5.进程终止调用的函数

1.wait()函数
等待子进程返回并修改状态
#include<sys/types.h>;
#include<sys/wait.h>;
pid_twait(int*stat_loc);
允许调用进程取得子进程的状态信息。调用进程将会挂起直到其一个子进程终止.
返回值为该子进程进程号,否则返回-1,同时stat_loc返回子进程的返回值.
用法:
#include<sys/type.h>;
#include<sys/wait.h>;
main()
{
pid_tpid;
if((pid=fork())>;0)
{
/*parentprocess*/
intchild_status;
wait(&child_status);
}
elseif(pid==0)
{
/*childprocess*/
exit(0);
}
else
{
printf("forkerror\n");
exit(0);
}
}

2.waitpid()函数
等待指定进程的子进程返回,并修改状态.
#include<sys/types.h>;
#include<sys/wait.h>;
pid_twaitpid(pid_tpid,int*stat_loc,intoptions);
当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_tpid;
if((pid=fork())>;0)
{
/*parentprocess*/
intchild_status;
pid_tchild_status;
waitpid(child_pid,&child_status,0);
}
elseif(pid==0)
{
/*childprocess*/
exit(0);
}
else
{
printf("forkerror");
exit(0);
}
}

exit()函数
终止进程,并返回状态。
#include<stdlib.h>;
viodeixt(intstatus);
函数终止调用进程,exit()函数会关闭所有进程打开的描述符,向其父进程发送SIGCHLD信号,并返回状态,父进程可通过调用wait()或者waitpid()函数获得其状态


第二部分--多进程80banner扫描器的具体实现


一.思路:

1.多ip处理模块
利用strtok()函数来分离ip
strtok(ip,".");
for来循环ip

2.usage提示模块
intusage(char*pro)
{
printf("fork80bannerscanner");
printf("usage:%s[startip][stopip]\n",pro);
}

3.扫描模块
viodscanip(charsip[20])

4.多进程及处理模块
fork()


二.实现

/********************************************************************
**fork()80bannerscannerver1.0
*
*tocomplie:
*user$gcc-o80scaner80scanner.c
*
*touse:
*user$./80scannerstart_ipstop_ip(thebestinputcclassipotherwise*wastetoomanysystemresource)
*
*codedbynightcat
*june2003

*********************************************************************/
/*所要的文件*/
#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>;

/*声明函数*/
intusage(char*pro);
voidscanip(char*sip);

intmain(intargc,char*argv[])
{
/*声明变量*/
intchild_status;
pid_tchild_id;
pid_tpid;
char*pro;
intcount;


char*start_ip,*stop_ip;

char*chge;
charscan_ip[20];

intip1,ip2,ip3,ip4;
intend1,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);
}
}
}

/*用法函数*/
intusage(char*pro){
printf("fork()80bannerscanner\n");
printf("inputcclassip");
printf("usage:%s[start_ip][stop_ip]\n",pro);
}

/*扫描函数*/
voidscanip(charsip[20]){
structsockaddr_inaddr;
ints;
charbuffer[1024];
if((s=socket(AF_INET,SOCK_STREAM,0))<0){
perror("socketerror\n");
exit(1);
}
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=inet_addr(sip);
addr.sin_port=htons(80);
if(connect(s,(structsockaddr*)&addr,sizeof(addr))<0){
perror("connecterror");
exit(1);
}
send(s,"HEAD/HTTP/1.0\n\n",17,0);
recv(s,buffer,sizeof(buffer),0);
printf("%s'shttpversion:\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改为charscan_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.ProcessControl进程控制
1.1Creatingnewprocesses:fork()创建新进程:fork函数
1.1.1Whatdoesfork()do?fork函数干什么?
1.1.2What'sthedifferencebetweenfork()andvfork()?fork函数与vfork函数的区别在哪里?
1.1.3Whyuse_exitratherthanexitinthechildbranchofafork?为何在一个fork的子进程分支中使用_exit函数而不使用exit函数?
1.2Environmentvariables环境变量
1.2.1HowcanIget/setanenvironmentvariablefromaprogram?我怎样在程序中获得/设置环境变量?
1.2.2HowcanIreadthewholeenvironment?我怎样读取整个环境变量表?
1.3HowcanIsleepforlessthanasecond?我怎样睡眠小于一秒?
1.4HowcanIgetafiner-grainedversionofalarm()?我怎样得到一个更细分时间单位的alarm函数版本(译者注:希望alarm的时间小于一秒)?
1.5Howcanaparentandchildprocesscommunicate?父子进程如何通信?
1.6HowdoIgetridofzombieprocesses?我怎样去除僵死进程?
1.6.1Whatisazombie?何为僵死进程?
1.6.2HowdoIpreventthemfromoccuring?我怎样避免它们的出现?
1.7HowdoIgetmyprogramtoactlikeadaemon?我怎样使我的程序作为守护程序运行?
1.8HowcanIlookatprocessinthesystemlikepsdoes?我怎样象ps程序一样审视系统的进程?
1.9Givenapid,howcanItellifit'sarunningprogram?给定一个进程号(译者注:pid:processID),我怎样知道它是个正在运行的程序?
1.10What'sthereturnvalueofsystem/pclose/waitpid?system函数,pclose函数,waitpid函数的返回值是什么?
1.11HowdoIfindoutaboutaprocess'memoryusage?我怎样找出一个进程的存储器使用情况?
1.12Whydoprocessesneverdecreaseinsize?为什么进程的大小不缩减?
1.13HowdoIchangethenameofmyprogram(asseenby****ps')?我怎样改变我程序的名字(即“ps”看到的名字)?
1.14HowcanIfindaprocess'executablefile?我怎样找到进程的相应可执行文件?
1.14.1SowheredoIputmyconfigurationfilesthen?那么,我把配置文件放在哪里呢?
1.15Whydoesn'tmyprocessgetSIGHUPwhenitsparentdies?为何父进程死时,我的进程未得到SIGHUP信号?
1.16HowcanIkillalldescendentsofaprocess?我怎样杀死一个进程的所有派生进程?

2.GeneralFilehandling(includingpipesandsockets)一般文件操作(包括管道和套接字)
2.1Howtomanagemultipleconnections?怎样管理多个连接?
2.1.1HowdoIuseselect()?我怎样使用select()?
2.1.2HowdoIusepoll()?我怎样使用poll()?
2.1.3CanIuseSysVIPCatthesametimeasselectorpoll?我是否可以将SysV进程间通信(译者注:IPC:InterprocessCommunications)与select或poll同
时使用?
2.2HowcanItellwhentheotherendofaconnectionshutsdown?我怎么知道连接的另一端已关闭?
2.3Bestwaytoreaddirectories?读目录的最好方法?
2.4HowcanIfindoutifsomeoneelsehasafileopen?我怎么知道其他人已经打开一个文件?
2.5HowdoI****lock'afile?我怎样锁定一个文件?
2.6HowdoIfindoutifafilehasbeenupdatedbyanotherprocess?我怎么知道一个文件是否已被其他进程更新?
2.7Howdoesthe****du'utilitywork?“du”工具程序是怎么工作的?
2.8HowdoIfindthesizeofafile?我怎么知道一个文件的大小?
2.9HowdoIexpand****~'inafilenameliketheshelldoes?我怎样象shell程序一样将一个文件名中含有的“~”展开?
2.10WhatcanIdowithnamedpipes(FIFOs)?我能用有名管道(FIFOs)(译者注:FIFO:FirstInFirstOout)干什么?
2.10.1Whatisanamedpipe?什么是有名管道?
2.10.2HowdoIcreateanamedpipe?我怎样创建一个有名管道?
2.10.3HowdoIuseanamedpipe?我怎样使用一个有名管道?
2.10.4CanIuseanamedpipeacrossNFS?我能基于网络文件系统(译者注:NFS:NetworkFileSystem)使用有名管道吗?
2.10.5Canmultipleprocesseswritetothepipesimultaneously?多个进程能否同时向这个管道写执行写操作?
2.10.6Usingnamedpipesinapplications在应用程序中使用有名管道。

3.TerminalI/O终端输入/输出(I/O:input/output)
3.1HowcanImakemyprogramnotechoinput?我怎样使我的程序不回射输入?
3.2HowcanIreadsinglecharactersfromtheterminal?我怎样从终端读取单个字符?
3.3HowcanIcheckandseeifakeywaspressed?我怎样检查是否一个键被摁下?
3.4HowcanImovethecursoraroundthescreen?我怎样将光标在屏幕里移动?
3.5Whatarepttys?pttys(pttys:Pseudo-teletypes)是什么?
3.6Howtohandleaserialportormodem?怎样控制一个串行口和调制解调器(译者注:modem:modulate-demodulate)
3.6.1Serialdevicenamesandtypes串行设备和类型
3.6.2Settinguptermiosflags设置termios的标志位
3.6.2.1c_iflag
3.6.2.2c_oflag
3.6.2.3c_cflag
3.6.2.4c_lflag
3.6.2.5c_cc

4.SystemInformation系统信息
4.1HowcanItellhowmuchmemorymysystemhas?我怎样知道我的系统有多少存储器容量?
4.2HowdoIcheckauser'spassword?我怎样检查一个用户的口令?
4.2.1HowdoIgetauser'spassword?我怎样得到一个用户的口令?
4.2.2HowdoIgetshadowpasswordsbyuid?我怎样通过用户号(译者注:uid:UserID)得到阴影口令文件中的口令?
4.2.3HowdoIverifyauser'spassword?我怎样核对一个用户的口令?

5.Miscellaneousprogramming编程杂技
5.1HowdoIcomparestringsusingwildcards?我怎样使用通配字符比较字符串?
5.1.1HowdoIcomparestringsusingfilenamepatterns?我怎样使用文件名通配模式比较字符串?
5.1.2HowdoIcomparestringsusingregularexpressions?我怎样使用正则表达式比较字符串?
5.2What'sthebestwaytosendmailfromaprogram?什么是在程序中发送电子邮件的最好方法?
5.2.1Thesimplemethod:/bin/mail简单方法:/bin/mail
5.2.2InvokingtheMTAdirectly:/usr/lib/sendmail直接启动邮件传输代理(译者注:MTA:mailtransferagent):/usr/bin/sendmail
5.2.2.1Supplyingtheenvelopeexplicitly显式提供收件人信息
5.2.2.2Allowingsendmailtodeducetherecipients允许sendmail程序根据邮件内容分析出收件人

6.Useoftools工具的使用
6.1HowcanIdebugthechildrenafterafork?我怎样调试fork函数产生的子进程?
6.2Howtobuildlibraryfromotherlibraries?怎样通过其他库文件建立新的库文件?
6.3Howtocreatesharedlibraries/dlls?怎样创建动态连接库/dlls?
6.4CanIreplaceobjectsinasharedlibrary?我能更改一个动态连接库里的目标吗?
6.5HowcanIgenerateastackdumpfromwithinarunningprogram?我能在一个运行着的程序中生成堆栈映象吗?


1.进程控制
***********

1.1创建新进程:fork函数
========================

1.1.1fork函数干什么?
----------------------

#include<sys/types.h>;
#include<unistd.h>;

pid_tfork(void);

‘fork()’函数用于从已存在进程中创建一个新进程。新进程称为子进程,而原进程称为
父进程。你可以通过检查‘fork()’函数的返回值知道哪个是父进程,哪个是子进程。父
进程得到的返回值是子进程的进程号,而子进程则返回0。以下这个范例程序说明它的基本
功能:

pid_tpid;

switch(pid=fork())
{
case-1:
/*这里pid为-1,fork函数失败*/
/*一些可能的原因是*/
/*进程数或虚拟内存用尽*/
perror("Theforkfailed!");
break;

case0:
/*pid为0,子进程*/
/*这里,我们是孩子,要做什么?*/
/*...*/
/*但是做完后,我们需要做类似下面:*/
_exit(0);

default:
/*pid大于0,为父进程得到的子进程号*/
printf("Child'spidis%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函数设定,该值表示进程的优先级,数值越小,优
先级越高)

*进程调度类别(schedulerclass)(译者注:进程调度类别指进程在系统中被调度时所
属的类别,不同类别有不同优先级,根据进程调度类别和nice值,进程调度程序可计
算出每个进程的全局优先级(Globalprocessprority),优先级高的进程优先执行)

*进程组号

*对话期ID(SessionID)(译者注:译文取自<<高级编程>;>;,指:进程所属的对话期
(session)ID,一个对话期包括一个或多个进程组,更详细说明参见<<高级编程>;>;
9.5节)

*当前工作目录

*根目录(译者注:根目录不一定是“/”,它可由chroot函数改变)

*文件方式创建屏蔽字(filemodecreationmask(umask))(译者注:译文取自<<高级编
程>;>;,指:创建新文件的缺省屏蔽字)

*资源限制

*控制终端

子进程所独有:

*进程号

*不同的父进程号(译者注:即子进程的父进程号与父进程的父进程号不同,父进
程号可由getppid函数得到)

*自己的文件描述符和目录流的拷贝(译者注:目录流由opendir函数创建,因其为
顺序读取,顾称“目录流”)

*子进程不继承父进程的进程,正文(text),数据和其它锁定内存(memorylocks)
(译者注:锁定内存指被锁定的虚拟内存页,锁定后,不允许内核将其在必要时
换出(pageout),详细说明参见<<TheGNUCLibraryReferenceManual>;>;2.2版,
1999,3.4.2节)

*在tms结构中的系统时间(译者注:tms结构可由times函数获得,它保存四个数据
用于记录进程使用中央处理器(CPU:CentralProcessingUnit)的时间,包括:用户时
间,系统时间,用户各子进程合计时间,系统各子进程合计时间)

*资源使用(resourceutilizations)设定为0

*阻塞信号集初始化为空集(译者注:原文此处不明确,译文根据fork函数手册页
稍做修改)

*不继承由timer_create函数创建的计时器

*不继承异步输入和输出

1.1.2fork函数与vfork函数的区别在哪里里?
-------------------------------------------

有些系统有一个系统调用‘vfork()’,它最初被设计成‘fork()’的较少额外支出
(lower-overhead)版本。因为‘fork()’包括拷贝整个进程的地址空间,所以非常
“昂贵”,这个‘vfork()’函数因此被引入。(在3.0BSD中)(译者注:BSD:
BerkeleySoftwareDistribution)

*但是*,自从‘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-modeconstructs)有关的清除工作(clean-up),而且调用用户自定义的清除程序
(译者注:自定义清除程序由atexit函数定义,可定义多次,并以倒序执行),相对
应,后一个函数只为进程实施内核清除工作。

在由‘fork()’创建的子进程分支里,正常情况下使用‘exit()’是不正确的,这是
因为使用它会导致标准输入输出(译者注:stdio:StandardInputOutput)的缓冲区被
清空两次,而且临时文件被出乎意料的删除(译者注:临时文件由tmpfile函数创建
在系统临时目录下,文件名由系统随机生成)。在C++程序中情况会更糟,因为静
态目标(staticobjects)的析构函数(destructors)可以被错误地执行。(还有一些特殊情
况,比如守护程序,它们的*父进程*需要调用‘_exit()’而不是子进程;适用于绝
大多数情况的基本规则是,‘exit()’在每一次进入‘main’函数后只调用一次。)

在由‘vfork()’创建的子进程分支里,‘exit()’的使用将更加危险,因为它将影响
*父*进程的状态。

1.2环境变量
============

1.2.1如何从程序中获得/设置环境变量?
--------------------------------------
获得一个环境变量可以通过调用‘getenv()’函数完成。

#include<stdlib.h>;

char*getenv(constchar*name);

设置一个环境变量可以通过调用‘putenv()’函数完成。

#include<stdlib.h>;

intputenv(char*string);

变量string应该遵守"name=value"的格式。已经传递给putenv函数的字符串*不*能够被
释放或变成无效,因为一个指向它的指针将由‘putenv()’保存。这意味着它必须是
在静态数据区中或是从堆(heap)分配的。如果这个环境变量被另一个‘putenv()’的
调用重新定义或删除,上述字符串可以被释放。

/*译者增加:

因为putenv()有这样的局限,在使用中经常会导致一些错
误,GNUlibc中还包括了两个BSD风格的函数:
#include<stdlib.h>;
intsetenv(constchar*name,constchar*value,intreplace);
voidunsetenv(constchar*name);

setenv()/unsetenv()函数可以完成所有putenv()能做的事。setenv()可以不受指针
限制地向环境变量中添加新值,但传入参数不能为空(NULL)。当replace为0时,如
果环境变量中已经有了name项,函数什么也不做(保留原项),否则原项被覆盖。
unsetenv()是用来把name项从环境变量中删除。注意:这两个函数只存在在BSD和GNU
库中,其他如SunOS系统中不包括它们,因此将会带来一些兼容问题。我们可以用
getenv()/putenv()来实现:

intsetenv(constchar*name,constchar*value,intreplace)
{
char*envstr;

if(name==NULL||value==NULL)
return1;
if(getenv(name)!=NULL)
{
envstr=(char*)malloc(strlen(name)+strlen(value)+2);
sprintf(envstr,"%s=%s",name,value);
if(putenv(envstr));
return1;
}
return0;
}
*/

记住环境变量是被继承的;每一个进程有一个不同的环境变量表拷贝(译者注:
从core文件中我们可以看出这一点)。结果是,你不能从一个其他进程改变当前
进程的环境变量,比如shell进程。

假设你想得到环境变量‘TERM’的值,你需要使用下面的程序:

char*envvar;

envvar=getenv("TERM");

printf("ThevaluefortheenvironmentvariableTERMis");
if(envvar)
{
printf("%s\n",envvar);
}
else
{
printf("notset.\n");
}

现在假设你想创建一个新的环境变量,变量名为‘MYVAR’,值为‘MYVAL’。
以下是你将怎样做:

staticcharenvbuf[256];

sprintf(envbuf,"MYVAR=%s","MYVAL");

if(putenv(envbuf))
{
printf("Sorry,putenv()couldn'tfindthememoryfor%s\n",envbuf);
/*Mightexit()orsomethinghereifyoucan'tlivewithoutit*/
}

1.2.2我怎样读取整个环境变量表?
--------------------------------

如果你不知道确切你想要的环境变量的名字,那么‘getenv()’函数不是很有用。
在这种情况下,你必须更深入了解环境变量表的存储方式。

全局变量,‘char**envrion’,包含指向环境字符串指针数组的指针,每一个字
符串的形式为‘“NAME=value”’(译者注:和putenv()中的“string”的格式相同)。
这个数组以一个‘空’(NULL)指针标记结束。这里是一个打印当前环境变量列表
的小程序(类似‘printenv’)。

#include<stdio.h>;

externchar**environ;

intmain()
{
char**ep=environ;
char*p;
while((p=*ep++))
printf("%s\n",p);
return0;
}

一般情况下,‘envrion’变量作为可选的第三个参数传递给‘main()’;就是说,
上面的程序可以写成:

#include<stdio.h>;

intmain(intargc,char**argv,char**envp)
{
char*p;
while((p=*envp++))
printf("%s\n",p);
return0;
}

虽然这种方法被广泛的操纵系统所支持(译者注:包括DOS),这种方法事实上并
没有被POSIX(译者注:POSIX:PortableOperatingSystemInterace)标准所定义。(一
般的,它也比较没用)

1.3我怎样睡眠小于一秒?
========================

在所有Unix中都有的‘sleep()’函数只允许以秒计算的时间间隔。如果你想要更
细化,那么你需要寻找替换方法:

*许多系统有一个‘usleep()’函数

*你可以使用‘select()’或‘poll()’,并设置成无文件描述符并试验;一个普
遍技巧是基于其中一个函数写一个‘usleep()’函数。(参见comp.unix.questions
FAQ的一些例子)

*如果你的系统有itimers(很多是有的)(译者注:setitimer和getitimer是两个操作
itimers的函数,使用“mansetitimer”确认你的系统支持),你可以用它们自己撺一
个‘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’出现在它的状态区
里指出这点。)即使它没有在执行,它仍然占据进程表里一个位置。(它不消耗其
它资源,但是有些工具程序会显示错误的数字,比如中央处理器的使用;这是
因为为节约空间进程表的某些部份与会计数据(accountinginfo)是共用(overlaid)的。)

这并不好,因为进程表对于进程数有固定的上限,系统会用光它们。即使系统没
有用光,每一个用户可以同时执行的进程数有限制,它总是小于系统的限制。
顺便说一下,这也正是你需要总是检查‘fork()’是否失败的一个原因。

如果父进程未调用wait函数而终止,子进程将被‘init’进程收管,它将控制子进
程退出后必须的清除工作。(‘init’是一个特殊的系统程序,进程号为1-它实际
上是系统启动后运行的第一个程序),

1.6.2我怎样避免它们的出现?
----------------------------

你需要却认父进程为每个子进程的终止调用‘wait()’(或者‘waitpid()’,
‘wait3()’,等等);或者,在某些系统上,你可以指令系统你对子进程的退出状
态没有兴趣。(译者注:在SysV系统上,可以调用signal函数,设置SIGCLD信号为
SIG_IGN,系统将不产生僵死进程,详细说明参见<<高级编程>;>;10.7节)

另一种方法是*两次*‘fork()’,而且使紧跟的子进程直接退出,这样造成孙子进
程变成孤儿进程(orphaned),从而init进程将负责清除它。欲获得做这个的程序,参
看范例章节的函数‘fork2()’。

为了忽略子进程状态,你需要做下面的步骤(查询你的系统手册页以知道这是否正
常工作):

structsigactionsa;
sa.sa_handler=SIG_IGN;
#ifdefSA_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我怎样使我的程序作为守护程序运行?
======================================

一个“守护程序”进程通常被定义为一个后台进程,而且它不属于任何一个终端
会话,(terminalsession)。许多系统服务由守护程序实施;如网络服务,打印等。

简单地在后台启动一个程序并非足够是这些长时间运行的程序;那种方法没有正
确地将进程从启动它的终端脱离(detach)。而且,启动守护程序的普遍接受的的方
法是简单地手工执行或从rc脚本程序执行(译者注:rc:runcom);并希望这个守护
程序将其*自身*安置到后台。

这里是成为守护程序的步骤:

1.调用‘fork()’以便父进程可以退出,这样就将控制权归还给运行你程序的
命令行或shell程序。需要这一步以便保证新进程不是一个进程组头领进程(process
groupleader)。下一步,‘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系统有很多可能的显示选项:选
择一个。)

在范例章节有这个问题的两个完整解决方法;一个适用于SunOS4,它需要root权
限执行并使用‘kvm_*’例程从内核数据结果读取信息;另一种适用于SVR4系统
(包括SunOS5),它使用‘/proc’文件系统。

在具有SVR4.2风格‘/proc’的系统上更简单;只要对于每一个感兴趣的进程号从
文件‘/proc/进程号/psinfo’读取一个psinfo_t结构。但是,这种可能是最清晰的方
法也许又是最不得到很好支持的方法。(在FreeBSD的‘/proc’上,你从
‘/proc/进程号/status’读取一个半未提供文档说明(semi-undocumented)的可打印字
符串;Linux有一些与其类似的东西)

1.9给定一个进程号,我怎样知道它是个正在运行的程序?
=====================================================

使用‘kill()’函数,而已0作为信号代码(signalnumber)。

从这个函数返回有四种可能的结果:

*‘kill()’返回0

-这意味着一个给定此进程号的进程退出,系统允许你向它发送信号。该进
程是否可以是僵死进程与不同系统有关。

*‘kill()’返回-1,‘errno==ESRCH’

-要么不存在给定进程号的进程,要么增强的安全机制导致系统否认它的存
在。(在一些系统上,这个进程有可能是僵死进程。)

*‘kill()’返回-1,‘errno==EPERM’

-系统不允许你杀死(kill)这个特定进程。这意味着要么进程存在(它又可能是
僵死进程),要么严格的增强安全机制起作用(比如你的进程不允许发送信号
给*任何人*)。

*‘kill()’返回-1,伴以其它‘errno’值

-你有麻烦了!

用的最多的技巧是认为调用“成功”或伴以‘EPERM’的“失败”意味着进程存
在,而其它错误意味着它不存在。

如果你特别为提供‘/proc’文件系统的系统(或所有类似系统)写程序,一个替换
方法存在:检查‘proc/进程号’是否存在是可行的。

1.10system函数,pclose函数,waitpid函数的返回值是什么?
==========================================================

‘system()’,‘pclose()’或者‘waitpid()’的返回值不象是我进程的退出值(exit
value)(译者注:退出值指调用exit()或_exit()时给的参数)...或者退出值左移了8
位...这是怎么搞的?

手册页是对的,你也是对的!如果查阅手册页的‘waitpid()’你会发现进程的返回
值被编码了。正常情况下,进程的返回值在高16位,而余下的位用来作其它事。
如果你希望可移植,你就不能凭借这个,而建议是你该使用提供的宏。这些宏总
是在‘wait()’或‘wstat’的文档中说明了。

为了不同目的定义的宏(在‘<sys/wait.h>;’)包括(stat是‘waitpid()’返回的值):

****WIFEXITED(stat)'
如果子进程正常退出则返回非0

****WEXITSTATUS(stat)'
子进程返回的退出码

****WIFSIGNALED(stat)'
如果子进程由与信号而终止则返回非0

****WTERMSIG(stat)'
终止子进程的信号代码

****WIFSTOPPED(stat)'
如果子进程暂停(stopped)则返回非0

****WSTOPSIG(stat)'
使子进程暂停的信号代码

****WIFCONTINUED(stat)'
如果状态是表示子进程继续执行则返回非0

****WCOREDUMP(stat)'
如果‘WIFSIGNALED(stat)’为非0,而如果这个进程产生一个内存映射文件
(coredump)则返回非0

1.11我怎样找出一个进程的存储器使用情况?
=========================================

如果提供的话,参看‘getrusage()’手册页

1.12为什么进程的大小不缩减?
=============================

当你使用‘free()’函数释放内存给堆时,几乎所有的系统都*不*减少你程序的
对内存的使用。被‘free()’释放的内存仍然属于进程地址空间的一部份,并将
被将来的‘malloc()’请求所重复使用。

如果你真的需要释放内存给系统,参看使用‘mmap()’分配私有匿名内存映射
(privateanonymousmappings)。当这些内存映射被取消映射时,内存真的将其释放给
系统。某些‘malloc()’的实现方法(比如在GNUC库中)在允许时自动使用‘mmap()’
实施大容量分配;这些内存块(blocks)随着‘free()’被释放回系统。

当然,如果你的程序的大小增加而你认为它不应该这样,你可能有一个‘内存泄
露’(‘memoryleak’)-即在你的的程序中有缺陷(bug)导致未用的内存没释放。

1.13我怎样改变我程序的名字(即“ps”看到的名字)?
=================================================

在BSD风格的系统中,‘ps’程序实际上审视运行进程的地址空间从而找到当前
的‘argv[]’,并显示它。这使得程序可以通过简单的修改‘argv[]’以改变它的
名字。

在SysV风格的系统中,命令的名字和参数的一般头80字节是存放在进程的u-区(
u-area),所以不能被直接修改。可能有一个系统调用用来修改它(不象是这样),
但是其它的话,只有一个方法就是实施一个‘exec()’,或者些内核内存(危险,
而且只有root才有可能)。

一些系统(值得注意的是Solaris)可以有‘ps’的两种不同版本,一种是在
‘/usr/bin/ps’拥有SysV的行为,而另一种在‘/usr/ucb/ps’拥有BSD的行为。在
这些系统中,如果你改变‘argv[]’,那么BSD版的‘ps’将反映这个变化,而
SysV版将不会。

检查你的系统是否有一个函数‘setproctitle()’。

1.14我怎样找到进程的相应可执行文件?
=====================================

这个问题可以作为‘常见未回答问题’(‘FrequentlyUnansweredQuestions’)的一
个好候选,因为事实上提出这个问题经常意味着程序的设计有缺陷。

你能作的‘最佳猜测’(‘bestguess’)是通过审视‘argv[0]’的值而获得。如果
它包括一个‘/’,那么它可能是可执行程序的绝对或相对(对于在程序开始时的
当前目录而言)路径。如果不包括,那么你可以仿效shell对于‘PATH’变量的查
询来查找这个程序。但是,不能保证成功,因为有可能执行程序时‘argv[0]’是
一些任意值,也不排除这个可执行文件在执行后可能已经被更名或删除的情况。

如果所有你想做的只是能打印一个和错误消息一起出现的合适的名字,那么最好
的方法在‘main()’函数中将‘argv[0]’的值保存在全局变量中以供整个程序使
用。虽然没有保证说‘argv[0]’的值总是有意义,但在大多数情况下它是最好的
选择。

人们询问这个问题的最普通原因是意图定位他们程序的配置文件。这被认为是
不好的形式;包含可执行文件的目录应当*只*包含可执行文件,而且基于管理的
要求经常试图将配置文件放置在和可执行文件不同的文件系统。

试图做这个的一个比较不普通但更正规的理由是允许程序调用‘exec()’执行它
自己;这是一种用来完全重新初始化进程(比如被用于一些‘sendmail’的版本)的
办法(比如当一个守护程序捕获一个‘SIGHUP’信号)。

1.14.1SowheredoIputmyconfigurationfilesthen?
-----------------------------------------------------
1.14.1那么,我把配置文件放在哪里里呢?

为配置文件安排正确的目录总是取决于你使用的Unix系统的特点;
‘/var/opt/PACKAGE’,‘/usr/local/lib’,‘/usr/local/etc’,或者任何其它一
些可能的地方。用户自定义的配置文件通常是在‘$HOME’下的以“.”开始的隐藏文件(
比如‘$HOME/.exrc’)。

从一个在不同系统上都能使用的软件包(package)的角度看,它通常意味着任何站
点范围(sitewide)的配置文件的位置有个已设定的缺省值,可能情况是使用一个在
配置脚本程序里的‘--prefix’选项(Autoconf脚本程序集做这个工作)。你会希望允
许这个缺省值在程序执行时被一个环境变量重载。(如果你没使用配置脚本程序,
那么在编译时,将这个位置缺省值作为‘-D’选项放入项目文件(Makefile),或者
将其放入一个‘config.h’头文件,或做其它类似的工作)

--

用户自定义配置需要放置于一个在‘$HOME’下的文件名“.”打头的文件,或者
在需要多个配置文件时,建立文件名“.”打头的子目录。(在列目录时,文件名以
“.”打头的文件或目录缺省情况下被忽略。)避免在‘$HOME’建立多个文件,因
为这会造成非常杂乱的情况。当然,你也应该允许用户通过一个环境变量重载这个
位置。即使不能找到某个用户的配置文件,程序仍应当以适宜的方式执行。

1.15
分享到:
评论

相关推荐

    linux 创建守护进程的相关知识

    创建子进程,父进程退出 这是编写守护进程的第一步。由于守护进程是脱离控制终端的,因此,完成第一步后就会在Shell终端里造成一程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在Shell终端里则...

    linux系统编程之线程.zip

    start_routine返回时,这个线程就退出了,其它线程可以调用pthread_join得到start_routine的返回值,类似于父进程调用wait(2)得到子进程的退出状态,稍后详细介绍pthread_join。 pthread_create成功返回后,新创建的...

    linux内核 0.11版本源码 带中文注释

    #include &lt;linux/head.h&gt; // head 头文件,定义了段描述符的简单结构,和几个选择符常量。 #include &lt;asm/system.h&gt; // 系统头文件。以宏的形式定义了许多有关设置或修改 // 描述符/中断门等的嵌入式汇编子程序。...

    易语言 茶凉专用模块

    子程序 窗口置父, 整数型, 公开, 指定一个窗口的新父(返回前一个父窗口的句柄) .参数 窗口句柄, 整数型, , 子窗口句柄 .参数 新父, 整数型, 可空, 新的父窗口句柄 默认为0:置顶级窗口 -1:嵌入桌面 .子程序 窗口置...

    Windows 系统错误代码简单分析

     0128 没有要等候的子进程。  0129 模式下运行。  0130 试图使用操作(而非原始磁盘I/O)的已打开磁盘分区的文件句柄。  0131 试图将文件指针移至文件开头之前。  0132 无法在指定的设备或文件中设置文件...

    linux大作业报告

    ”字符串,主进程休眠一段时间后,在主进程中结束子进程,随后主进程也退出。 9、编写程序,在程序中使用命令行形式显示程序所在当前文件夹下的内容。 10、编写程序,得到当前进程的标识号,并将它打印输出,随后...

    Linux使用教程(教程)

    1.3 父进程和子进程; 69 2、进程管理; 69 2.1 ps 监视进程工具; 69 2.2 pgrep 71 3、终止进程的工具 kill 、killall、pkill、xkill; 71 4、top 监视系统任务的工具; 73 5、进程的优先级:nice和renice; 73 八...

    API之网络函数---整理网络函数及功能

    其中包括文件、文件映射、进程、线程、安全和同步对象等 CompareFileTime 对比两个文件的时间 CopyFile 复制文件 CreateDirectory 创建一个新目录 CreateFile 打开和创建文件、管道、邮槽、通信服务、设备以及...

    Qt Creator 的安装和hello world 程序+其他程序的编写--不是一般的好

    器的进程中将该进程结束,而后再次运行就没问题了,当然先关闭Qt Creator, 而后再重新打开,这样也能解决问题。 如果把程序改为这样: #include #include "widget.h" #include "mydlg.h" int main(int argc, char ...

    超级有影响力霸气的Java面试题大全文档

    如果该值为false,说明程序已经处于不正确的状态下,系统将给出警告或退出。一般来说,assertion用于保证程序最基本、关键的正确性。assertion检查通常在开发和测试时开启。为了提高性能,在软件发布后,assertion...

    java 面试题 总结

    如果该值为false,说明程序已经处于不正确的状态下,系统将给出警告或退出。一般来说,assertion用于保证程序最基本、关键的正确性。assertion检查通常在开发和测试时开启。为了提高性能,在软件发布后,assertion...

    网管教程 从入门到精通软件篇.txt

    网管教程 从入门到精通软件篇 ★一。★详细的xp修复控制台命令和用法!!! 放入xp(2000)的光盘,安装时候选R,修复! Windows XP(包括 Windows 2000)的控制台命令是在系统出现一些意外情况下的一种非常有效的...

    LINGO软件的学习

    一个模型可以没有集部分,或有一个简单的集部分,或有多个集部分。一个集部分可以放置于模型的任何地方,但是一个集及其属性在模型约束中被引用之前必须定义了它们。 2.3.1 定义原始集 为了定义一个原始集,必须详细...

    JAVA面试题最全集

    如何列出某个目录下的所有子目录 判断一个文件或目录是否存在 如何读写文件 7.Java多态的实现(继承、重载、覆盖) 8.编码转换,怎样实现将GB2312编码的字符串转换为ISO-8859-1编码的字符串。 9.Java中访问...

Global site tag (gtag.js) - Google Analytics