unix系统,子unix查看进程占用内存退出,向父unix查看进程占用内存发送哪个信号

随笔 - 73&
评论 - 19&
&&&&&&&&&&&
刚看完 UNIX 第五章内容,我想按照自己的方式将自己获得的知识梳理一遍,以便日后查看!先贴上一段简单的 TCP 服务器端代码:
1 #include &sys/socket.h&
2 #include &netinet/in.h&
3 #include &stdio.h&
4 #include &error.h&
5 #include &unistd.h&
6 #include &string.h&
7 #include &stdlib.h&
9 #define MAXLINE 5
10 #define SA struct sockaddr
11 int main()
int listenfd,
int readn,
char buf[MAXLINE];
struct sockaddr_in servaddr,
//创建监听套接字
if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) & 0)
printf("socket() error!");
//先要对协议地址进行清零
bzero(&servaddr,sizeof(servaddr));
//设置为 IPv4 or IPv6
servaddr.sin_family = AF_INET;
//绑定本地端口号
servaddr.sin_port
= htons(9804);
//任何一个 IP 地址,让内核自行选择
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定套接口到本地协议地址
if(bind(listenfd, (SA *) &servaddr,sizeof(servaddr)) & 0)
printf("bind() error!");
//服务器开始监听
if(listen(listenfd,5) & 0)
printf("listen() error!");
clilen = sizeof(cliaddr);
//accept 的后面两个参数都是值-结果参数,他们的保留的远程连接电脑的信息,如果不管新远程连接电脑的信息,可以将这两个参数设置为 NULL
connfd = accept(listenfd, (SA *) &cliaddr, &clilen);
if(connfd & 0)
//我们采用 TCP 并发服务器模式,每个客户一个进程
if((childpid = fork()) == 0)
//子进程关闭 listenfd:因为父子进程都各拥有一个 listefd,而子进程不负责监听,所以关闭 listenfd,以免浪费资源!
close(listenfd);
//子进程做的事很简单,从套接字上读取数据然后再写回
while((readn = read(connfd, buf,MAXLINE)) & 0)
writen = write(connfd, buf, readn);
if(writen & 0)
printf("writen() error!");
printf("write %d bytes!\n", writen);
//父进程关闭 connfd,类似的父进程只负责监听,不需要 connfd
close(connfd);
  以上是一个最基本的 TCP 服务器端代码,其功能很简单:一旦一个客户发起连接,服务器端就 fork() 一个进程为其服务,该进程从其所拥有的连接套接字读数据然后再将数据写回套接字。客户端运行效果如下:
  当然,我们不是简简单单的要说明这样一个 TCP 服务器,稍微看过 TCP 套接口编程的都能写出以上程序。我们要讨论的是由该程序所涉及到的和即将要涉及到的各个知识点。
&TCP 三次握手协议和四次分手协议我已在以前的一篇博文中中描述过,这里我们不再赘述!我们现在用此实例来演示一遍 TCP 连接的建立与终止。我们在本机运行以上代码:
  我们查看本机 9804 端口的状态:netstat -a | grep 9804
  就会发现此端口处于 listen---监听状态。这时候的服务器端代码阻塞于 listen() 函数,等待客户端发起连接。我们再运行客户端,并输入测试字符,以说明连接正常。
  运行 netstat&查看与 9804 端口有关的端口的状态:netstat -a | grep 9804
  可以看到与 9804 端口有关的连接对都处于 ESTABLISHED 状态。说明客户端与服务器端已经完成 TCP 连接的建立,可以进行数据的发送了。此时我们查看进程信息,可以看到如下的服务器端为客户端创建了一个处理进程:
  其中 7290 与 7293 都是子进程,7009 是 ./myserver 进程的父进程号!现在我们演示 TCP 连接的终止:我们在同时按下 ctrl 和 D(相当于发送 EOF 给服务器进程),此时,我们再观察进程信息:
  我们会发现进程号为 7293 的进程的状态是 Z+,也就是所谓的僵尸进程。为什么?我们按下 CTRL 和 D向服务器发送 EOF ,服务器代码 的 read() 函数返回 0,此时 while 循环退出,执行 exit(0) 函数,该子进程终止,子进程终止的时候会向父进程发送 SIGCHLD 信号,但是我们在服务器代码中未捕获该信号,终止进程未被及时处理,即成为僵尸进程。僵尸进程由进程号为 1 的 Init 进程代为管理
  以上,我们可以大概的了解到 TCP 连接的建立与终止的过程以及僵尸进程形成的原因。我们知道,僵尸进程是无用的进程且其会消耗系统资源,所以我们应该及时处理僵尸进程。之前我们说过,子进程终止的时候会向父进程发送 SIGCHLD 信号,我们先说说什么是信号?以下摘抄自 UNP 5.8节;
  信号就是通知某个进程发生了某个事件,有时也成为软件中断。信号通常异步发送,也就是进程预先不知到信号准确发生时间。信号可以&
    . 由一个进程发送给另外一个进程。
    . 由内核发给某个进程。
  那么,进程收到信号该怎么办呢?有以下三种选择:
    1.提供一个函数,它将在特定信号发生的时候被调用,这样的函数我们称为信号处理,这种行为我们称为信号捕获。所有的信号处理函数原型都是:void handler(int singo)
    2.将该信号设置为 SIG_IGN 来忽略它。SIGKILL 和 SIGSTOP 这两个信号不能被忽略。
    3.可以将信号的处置设定为 SIG_DFI 来启用它的缺省处置。
  接下来,我们说下如何处理上面说的 SIGCHLD 信号:既然会形成僵尸进程,我们肯定不能再忽略该信号,而是选择捕获它。给该信号安装一个执行函数,那么该怎么安装呢?直接调用系统函数 signal(),该函数有两个参数,第一个参数就是你要监视的信号,第二个参数就是执行函数的函数指针。如是,我们修改服务器端代码如下:
1 #include &sys/socket.h&
2 #include &netinet/in.h&
3 #include &stdio.h&
4 #include &error.h&
5 #include &unistd.h&
6 #include &string.h&
7 #include &stdlib.h&
8 #include &sys/wait.h&
9 #include &signal.h&
10 #define MAXLINE 5
11 #define SA struct sockaddr
13 void sig_child(int signo)
pid = wait(&stat);
printf("child %d terminated\n", pid);
21 int main()
int listenfd,
int readn,
char buf[MAXLINE];
struct sockaddr_in servaddr,
//创建监听套接字
if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) & 0)
printf("socket() error!");
//先要对协议地址进行清零
bzero(&servaddr,sizeof(servaddr));
//设置为 IPv4 or IPv6
servaddr.sin_family = AF_INET;
//绑定本地端口号
servaddr.sin_port
= htons(9805);
//任何一个 IP 地址,让内核自行选择
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定套接口到本地协议地址
if(bind(listenfd, (SA *) &servaddr,sizeof(servaddr)) & 0)
printf("bind() error!");
//服务器开始监听
if(listen(listenfd,5) & 0)
printf("listen() error!");
signal(SIGCHLD, sig_child);
clilen = sizeof(cliaddr);
//accept 的后面两个参数都是值-结果参数,他们的保留的远程连接电脑的信息,如果不管新远程连接电脑的信息,可以将这两个参数设置为 NULL
connfd = accept(listenfd, (SA *) &cliaddr, &clilen);
if(connfd & 0)
//我们采用 TCP 并发服务器模式,每个客户一个进程
if((childpid = fork()) == 0)
//子进程关闭 listenfd:因为父子进程都各拥有一个 listefd,而子进程不负责监听,所以关闭 listenfd,以免浪费资源!
close(listenfd);
//子进程做的事很简单,从套接字上读取数据然后再写回
while((readn = read(connfd, buf,MAXLINE)) & 0)
writen = write(connfd, buf, readn);
if(writen & 0)
printf("writen() error!");
printf("write %d bytes!\n", writen);
//父进程关闭 connfd,类似的父进程只负责监听,不需要 connfd
close(connfd);
  对比之前版本,我们只增加了 13 - 20 行的信号处理函数与 55 行的信号捕获代码。再次运行服务器端代码(这次我们服务器程序取名叫:myserver9805),查看进程信息如下:
  可以看到两个 ./myserver9805 一个是父进程,其 pid 为 9101,另一个是子进程其 pid 为 9111,其父进程为 9101。其中的 ./myclient.0.1 是客户端进程。现在我们再次在客户端按下 CTRL + D:
  服务器端进程会调用捕获函数,打印
    child 9111 terminated
  再次查看进程信息,如下:
  可以看到,此时没有形成僵尸进程!(请忽略进程编号为 7293 的僵尸进程,这是之前留下的,囧!)。至此,SIGCHLD 信号处理完毕,其实也很简单嘛。现在我们回头看看信号捕获函数:
13 void sig_child(int signo)
pid = wait(&stat);
printf("child %d terminated\n", pid);
20 }  其中调用了 wait() 函数,该函数自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。而值-结果参数 stat 保留的就是 wait 函数收集到的进程信息。  说到 wait() 函数,我们不得不说一下 waitpid() 函数,因为实际过程中用该函数替代 wait() 函数。我们修改客户端程序,让其建立 5 个连接,也就是说服务器端会创建 5 个进程来处理这些连接,查看进程信息,如下:    可以看出,一共有 5 个进程,父进程 PID 是 9101。此时我们在客户端再按下 CTRL + D,再次查看进程信息:    发现,还是会有一个僵尸进程,为什么呢?因为当父进程同时收到很多 SIGCHLD 信号时 信号函数只执行一次或多次,但是不能肯定一定是 5 次,所以会导致还会有僵尸进程。基于此原因,我们用 waitpid() 代替 wait() 函数,修改信号捕获函数如下:void sig_child(int signo){  pid_    while(pid = waitpid(-1, &stat, WNOHANG) & 0)    printf("child %d terminated\n",pid);}  看到如上代码,可能会有人会说将上面的 waitpid 换成 wait 不也行。可我们不要忘了当有尚未终止的子进程时会阻塞,而 waitpid 函数可以通过设置第三个参数为 WNOHANG 来告知 waitpid ,当有尚未终止的子进程的时候不要阻塞,所以就可以通过循环处理所有终止的子进程,故不会有僵尸进程残留!  以上我们通过该简单 TCP 服务器代码简单的讲解了信号有关的知识点,让我们初窥信号,当然更具体的知识点应该去拜读 UNP 这本书,这里只是起个抛砖引玉的作用!接下来我们再依此介绍 I/O 复用模型。UNP 第六章介绍了五种 I/O 模型,具体如下:    1.阻塞 I/O 模型。    2.非阻塞 I/O 模型。    3.I/O 复用模型。     4.信号驱动模型。    5.异步 I/O 模型。  我们主要介绍 1 和 3,其他 3 中类型不在本文范围内。还是以上面那个 TCP 服务器代码作切入点:再次运行该代码,并运行客户端,发送 Hello,world!测试连接正常:    现在客户端停留在等待输入的界面,我们在另一个终端杀死服务器端的子进程:    发现客户端没有任何反映。现在,我们尝试在客户端再次输入: another line,结果返回 server terminated prematurely!:    为什么会这样子?这里我们先贴出客户端代码,借用 UNP 5.4 节提供的客户端代码的:
1 #include &sys/socket.h&
2 #include &netinet/in.h&
3 #include &stdio.h&
4 #include &error.h&
5 #include &unistd.h&
6 #include &string.h&
7 #include &stdlib.h&
8 #include &sys/wait.h&
9 #include &signal.h&
10 #define MAXLINE 5
11 #define SA struct sockaddr
13 void str_cli(FILE *fp, int sockfd)
char sendline[MAXLINE], recvline[MAXLINE];
while(fgets(sendline,MAXLINE,fp) != NULL)
writen(sockfd, sendline, strlen(sendline));
if(readline(sockfd, recvline, MAXLINE) == 0)
printf("str_cli:server terminated prematurely!\n");
fputs(recvline, stdout);
27 int main(int argc, char **argv)
struct sockaddr_
if(argc != 2)
printf("useage: tcpcli &IPaddress&");
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) & 0)
printf("socket() error!");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(9806);
if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) & 0)
printf("inet_pton() error!");
if(connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) & 0)
printf("inet_pton() error!");
str_cli(stdin, sockfd);
    1. 当我们杀死子进程,服务器子进程会向客户端发送一个 FIN,而客户端则响应一个 ACK。这是 TCP 连接终止的前一半工作。    2. SIGCHLD 信号发送给服务器进程,并得到正确处理。    3. 客户端上没有发生任何特殊的事情。虽然客户端响应了一个 ACK,然而客户端进程阻塞在 fgets 调用上 (见以上客户端代码),等待从终端接受一行文本。所以没有任何反应。    4. 此时,我们用 netstat 查看套接口状态:    (一个netstat 是在杀死子进程之前运行一个是在杀死子进程之后运行的)    上图很完全符合 TCP 连接终止过程的状态变化。因为只完成了 TCP 连接终止的前一半,所以主动关闭的一方(这里是服务器端)处于 FIN_WAIT2 状态,被动关闭一方(这里是客户端)处于 CLOSE_WAIT 状态。    5.我们在客户端上键入 another line ,返回: str_cli: server terminated prematurely 。当我们键入 another line 的时候,客户端调用 writen,客户 TCP 接着把数据发送给服务器。TCP 允许这么做,因为 TCP 收到 FIN 只是表示服务器进程已经关闭了连接端的服务器端,从而不再往其中发送任何数据而已。不代表连接的客户端不能往连接中发送数据。FIN 的接收并没有告知客户端 TCP 服务器端进程已经终止(本例中确实已经终止)。当服务器 TCP 接收到来自客户端的数据时,既然先前打开那个套接口的进程已经终止,于是响应一个 RST。    6.然而客户端进程看不到这个 RST ,因为它在调用 writen 后立即调用 readline,并且由于第 1 步接受的 FIN,所调用的 readline 立即返回 0(表示 EOF),所以执行 if 中代码,输出错误信息:server terminated prematurely(服务器进程过早终止)。然后退出!    7.客户端终止时,它所有打开着的描述字都被关闭。  从上个例子可以看出,服务器端终止的时候,客户端并没有及时的感觉到。问题在于:当 FIN 到达套接口的时候,客户正阻塞在 fgets 调用上。客户实际上在应对两个描述字---套接口描述字和用户数如,它不能单纯的阻塞在两个源中的某个特定源上。这时候,我们希望服务器进程需要一种预先告知内核内核的能力,使得进程指定的一个或多个 I/O 条件就绪(也就是说输入已经准备好读取,或者描述字已经能承接更多的输出),它就通知进程。这个能力称为 I/O 复用,是由 select 和 poll 和更高级的 epoll 支持的。到这里,我们终于搞清楚什么是 I/O 复用了。。。 I/O 复用典型使用下列网络应用场合:    1.当客户处理多个描述字(通常是交互式输入和网络套机口),必须使用 I/O 复用。    2.一个客户同时处理多个套接口是可能的,不过比较少见。    3.如果一个 TCP 服务器端既要处理监听套接字又要处理已连接套接口,一般要用 I/O 复用。    4.如果一个服务器既要处理 TCP,又要处理 UDP,一般使用 I/O 复用。    5.如果一个服务器要处理多个服务或者协议,一般就要用 I/O 复用。  限于篇幅,我们将会单独用一篇博客来写 select、poll 和 epoll 有关的内容。
阅读(...) 评论()新手园地& & & 硬件问题Linux系统管理Linux网络问题Linux环境编程Linux桌面系统国产LinuxBSD& & & BSD文档中心AIX& & & 新手入门& & & AIX文档中心& & & 资源下载& & & Power高级应用& & & IBM存储AS400Solaris& & & Solaris文档中心HP-UX& & & HP文档中心SCO UNIX& & & SCO文档中心互操作专区IRIXTru64 UNIXMac OS X门户网站运维集群和高可用服务器应用监控和防护虚拟化技术架构设计行业应用和管理服务器及硬件技术& & & 服务器资源下载云计算& & & 云计算文档中心& & & 云计算业界& & & 云计算资源下载存储备份& & & 存储文档中心& & & 存储业界& & & 存储资源下载& & & Symantec技术交流区安全技术网络技术& & & 网络技术文档中心C/C++& & & GUI编程& & & Functional编程内核源码& & & 内核问题移动开发& & & 移动开发技术资料ShellPerlJava& & & Java文档中心PHP& & & php文档中心Python& & & Python文档中心RubyCPU与编译器嵌入式开发驱动开发Web开发VoIP开发技术MySQL& & & MySQL文档中心SybaseOraclePostgreSQLDB2Informix数据仓库与数据挖掘NoSQL技术IT业界新闻与评论IT职业生涯& & & 猎头招聘IT图书与评论& & & CU技术图书大系& & & Linux书友会二手交易下载共享Linux文档专区IT培训与认证& & & 培训交流& & & 认证培训清茶斋投资理财运动地带快乐数码摄影& & & 摄影器材& & & 摄影比赛专区IT爱车族旅游天下站务交流版主会议室博客SNS站务交流区CU活动专区& & & Power活动专区& & & 拍卖交流区频道交流区
白手起家, 积分 1, 距离下一级还需 199 积分
论坛徽章:0
为什么父进程向子进程发送信号&&子进程没反应??&&我知道错在哪里?! 但是为什么会是这样呢?&&
那位帅哥帮忙解答撒!
#include &unistd.h&
#include &stdio.h&
#include &signal.h&
void handle(int s){
& & & & printf(&有信号!\n&);
void em_handle(int s,siginfo_t *info,void *d){
& & & & printf(&有信号,%d发送!传递的值:%d\n&,
& & & & & & & & & & & & & & & &&&info-&si_pid,info-&si_int);
& & & & pid_
& & & & if(pid=fork()){
& & & & & & & & //父进程
& & & & & & & &
& & & & & & & & val.sival_int=9999;
& & & & & & & &
& & & & & & & & while(1){
& & & & & & & & & & & & /*kill(pid,34);*/
& & & & & & & & & & & & sigqueue(pid,34,val);
& & & & & & & & & & & & sleep(1);
& & & & & & & & }
& & & & else{
& & & & & & & & //子进程
& & & & & & & &
& & & & & & & & //act.sa_handler=
& & & & & & & & /*
& & & & & & & & act.sa_sigaction=0;//em_
& & & & & & & & */
& & & & & & & & act.sa_flags=SA_SIGINFO;
& & & & & & & & sigemptyset(&act.sa_mask);
& & & & & & & & sigfillset(&act.sa_mask);
& & & & & & & &
& & & & & & & & sigaction(34,&act,0);
& & & & & & & & while(1);
, , , , , , ,
&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbspLinux/UNIX进程控制(1)
进程控制(1)
进程标识符
每个进程都有肺腑的整形表示唯一的进程ID。按一个进程终止后,其进程ID就可以再次使用了。如下是几个典型进程的ID及其类型和功能。
ID 进程名 中文名 类型 作用
0 swapper 交换进程 系统进程 它是内核一部分,不执行磁盘上的程序,是调度进程。
1 init init进程 用户进程 永远不会终止,启动系统,读取系统初始化的文件。
2 pagedaemon页精灵进程 系统进程 虚存系统的请页操作
除了进程ID,每个进程还有一些其他的标识符。下列函数返回这些标识符:
#include &sys/types.h&
#include &unistd.h&
pid_t getpid(void); //返回值:调用进程的进程ID
pid_t getppid(void); //返回值:调用进程的父进程ID
uid_t getuid(void); //返回值:调用进程的实际用户ID
uid_t geteuid(void); //返回值:调用进程的有效用户ID
gid_t getgid(void); //返回值:调用进程的实际组ID
gid_t getegid(void); //返回值:调用进程的有效组ID
#include &unistd.h&
pid_t fork(void);
一个现有进程可以调用fork创建一个新进程。
返回值:子进程中返回0,父进程中返回子进程ID,出错返回-1。如下
子进程是父进程的副本。例如:子进程获得父进程数据空间、堆和栈的副本。父子进程不共享这些存储空间部分。父子进程共享正文段。
由于fork之后经常归属exec,所以现在很多实现并不执行一个父进程数据段、栈和堆的完全复制。作为你替代,使用了写时复制(Copy-On-Write)技术。这些区域由父子进程共享,而且内核将他们的访问权限改变为只读的。如果父子进程中的任一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本。
下面的程序演示了fork函数,从中可以看出子进程对变量所作的改变并不去影响父进程中该变量的值。
#include &unistd.h&
#include &stdio.h&
/* externalvariable in initialized data */
buf[] = &a write to stdout\n&;
main(void)
/* automatic variable on the stack */
if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
perror(&write error&);
printf(&before fork\n&);
/* we don't flush stdout */
if ((pid = fork()) & 0) {
perror(&fork error&);
}else if (pid == 0) {
/* child */
/* modifyvariables */
/* parent*/
printf(&pid = %d, glob = %d, var = %d\n&, getpid(), glob,var);
执行及输出结果:
chen123@ubuntu:~/user/apue.2e$./a.out
awrite to stdout
beforefork
pid= 2755, glob = 7, var = 89
pid= 2754, glob = 6, var = 88
chen123@ubuntu:~/user/apue.2e$./a.out & temp.out
chen123@ubuntu:~/user/apue.2e$cat temp.out
awrite to stdout
beforefork
pid= 2758, glob = 7, var = 89
beforefork
pid= 2757, glob = 6, var = 88
一般来说fork之后父进程和子进程的执行顺序是不确定的,这取决于内核的调度算法。在上面的程序中,父进程是自己休眠2秒钟,以使子进程先执行。
程序中fork与I/O函数之间的关系:write是不带缓冲 的,因为在fork之前调用write,所以其数据只写到标准输出一次。标准I/O是缓冲的,如果标准输出到终端设备,则它是行缓冲,否则它是全缓冲。当以交互方式运行该程序时,只得到printf输出的行一次,因为标准输出到终端缓冲区由换行符冲洗。但将标准输出重定向到一个文件时,由于缓冲区是全缓冲,遇到换行符不输出,当调用fork时,其printf的数据仍然在缓冲区中,该数据将被复制到子进程中,该缓冲区也被复制到子进程中。于是父子进程的都有了带改行内容的标准I/O缓冲区,所以每个进程终止时,会冲洗其缓冲区中的数据,得到第一个printf输出两次。
fork的一个特性是父进程的所有打开文件描述符都被复制到子进程中。父子进程的每个相同的打开描述符共享一个文件表项。假设一个进程有三个不同的打开文件,在从fork返回时,我们有如下所示结构:
在fork之后处理的文件描述符有两种常见的情况:
1. 父进程等待子进程完成。在这种情况下,父进程无需对其描述符做任何处理。当子进程终止后,子进程对文件偏移量的修改已执行的更新。
2. 父子进程各自执行不同的程序段。这种情况下,在fork之后,父子进程各自关闭他们不需要使用的文件描述符,这样就不会干扰对方使用文件描述符。这种方法在网络服务进程中经常使用。
父子进程之间的区别:
1. fork的返回值
2. 进程ID不同
3. 具有不同的父进程ID
4. 子进程的tms_utime、tms_stime、tms_cutime及tms_ustime均被设置为0
5. 父进程设置的文件锁不会被子进程继承
6. 子进程的未处理闹钟被清除
7. 子进程的未处理信号集被设置为空集
fork有下面两种用法:
1. 一个父进程希望复制自己,使父子进程同时执行不用的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
2. 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
fork调用失败的原因:
1. 系统中有太多的进程
2. 实际用户的进程数超过了限制
vfork函数的调用序列和返回值与fork相同,但两者的语义不同。
vfork用于创建一个新进程,而该新进程的目的是exec一个新程序。vfork与fork都创建一个子进程,但它不将父进程的地址空间复制到子进程中,因为子进程会立即调用exec,于是不会存访问该地址空间。相反,在子进程调用exec或exit之前,它在父进程的空间中运行,也就是说会更改父进程的数据段、栈和堆。vfork和fork另一区别在于:vfork保证子进程先运行,在它调用exec之后父进程才可能被调度运行。
下面是vfork的使用程序:
#include&unistd.h&
#include&stdio.h&
/* external variable in initialized data*/
main(void)
/* automatic variableon the stack */
printf(&before vfork\n&);
/* we don't flush stdio */
if ((pid = vfork()) & 0) {
perror(&vfork error&);
} else if (pid == 0) {
/* child */
/* modify parent's variables*/
/* child terminates */
* Parent continues here.
printf(&pid = %d, glob = %d, var =%d\n&, getpid(), glob, var);
执行及输出结果如下所示:
chen123@ubuntu:~/user/apue.2e$./a.out
before vfork
pid = 2984, glob= 7, var = 89
可见子进程直接改变了父进程的变量值,因为子进程在父进程的地址空间中运行。
这里子进程调用_exit是因为_exit并不执行标准I/O缓冲的冲洗操作。如果调用exit,该程序结果不确定,依赖于标准I/O库的实现。因为exit有可能关闭标准I/O流,那么会使父进程不产生任何输出。
进程有5中正常终止方式,3中异常终止方式。(见上一篇文章)。
对于任意一种终止情形,我们都希望终止进程能够统治父进程它是如何终止的。对于三个终止函数(exit、_exit和_Exit),实现这一点的方法是,将其退出状态作为参数传送给函数。在异常终止情况下,内核产生一个指示其异常终止原因的终止状态。在任意一种情况下,该终止状态的父进程都能使用wait或waitpid函数取得其终止状态。
在调用_exit时,内核将进程的退出状态转换成终止状态。
对于父进程已经终止的所有进程,他们的父进程都改变为init进程。我们称这些进程有Init领养。一个init的子进程(包括领养进程)终止时,init会调用一个wait函数取得其终止状态。
对于一个已经终止、但其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放它仍占有的资源)的进程被称为僵尸进程。子进程终止时,虽然不在运行,但它仍然存在与系统中,进程表中代表子进程的表项不会立刻被释放,因为它的退出码还需要保存在进程表项中以备父进程今后的wait调用使用,也就是说终止子进程与父进程之间的关联还会保持,直到父进程也正常的终止或父进程调用wait才告结束。
wait和waitpid函数
当一个进程正常或异常终止时,内核就向其父进程发送一个SIGCHLD信号。因为子进程终止是一个异步事件,所以发生这种信号也是内核向父进程发的异步通知。父进程可以选择忽略该信号,或者提供一个该信号发生时即被调用执行的函数。对于这种信号的系统默认动作是忽略它。现在需要知道的是调用wait或waitpid的进程可能会发生什么情况:
1.如果其所有子进程都还在运行,则阻塞
2.如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
3.如果它没有任何子进程,则立即出错返回。
#include&sys/types.h&
#include&sys/wait.h&
pid_t wait(int*status);
pid_twaitpid(pid_t pid, int *status, int options);
如果进程由于接收到SIGCHLD而调用wait,则可期望wait会立即返回。但如果在任意时刻调用wait,则进程可能阻塞。
在一个子进程 终止前,wait使其调用者阻塞,而waitpid有一个选项,可使调用者不阻塞。
这两个函数的参数statloc是一个整形指针。如果statloc不是一个空指针,则终止进程的终止状态就存放在它所指的单元内。如果不关心终止状态,则可将该参数设为空指针。
可用以下宏来检查wait和waitpid所返回的终止状态:
WIFEXITED(status) 若为正常终止子进程返回的状态,则为真。
WEXITSTATUS(status) 若WIFEXITED非零,返回子进程退出码。
WIFSIGNAKED(status) 若为子进程异常终止返回状态(收到一个未捕捉的信号),则为真
WTERMSIG(status) 若WIFSIGNAKED非零,则返回一个信号编号
WIFSTOPPED 若为子进程意外终止,则为真
WSTOPSIG 若WIFSTOPPED非零,返回一个信号编号
waiptpid提供了wait没有提供的三个功能:
1. waitpid可等待一个特定的进程
2. waitpid提供了一个wait的非阻塞版本
3. waitpid支持作业控制
(window.slotbydup=window.slotbydup || []).push({
id: '2467140',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467141',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467142',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467143',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467148',
container: s,
size: '1000,90',
display: 'inlay-fix'

我要回帖

更多关于 hp unix 杀死僵尸进程 的文章

 

随机推荐