僵尸进程
在 U N I X UNIX UNIX/ L i n u x Linux Linux中,正常情况下,子进程是通过父进程创建的,子进程再创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程无法预测子进程到底什么时候结束。于是就产生了孤儿进程与僵尸进程。
孤儿进程:指一个父进程退出后,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被 i n i t init init进程(进程号为1)所收养,并由 i n i t init init进程对它们完成状态收集工作
僵尸进程:指一个进程使用 f o r k fork fork创建子进程,如果子进程退出,而父进程并没有调用 w a i t wait wait或 w a i t p i d waitpid waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这种进程称为僵尸进程。当一个进程完成它的工作终止之后,它的父进程需要调用 w a i t ( ) wait() wait()或 w a i t p i d ( ) waitpid() waitpid()系统调用取得子进程的终止状态
简单来讲,孤儿进程就是父进程已退出,而子进程未退出;僵尸进程是父进程未退出,而子进程已退出
例1:写一个僵尸进程:
/*************************************************************************
> File Name: wait_fork.cpp
> Author: ersheng
> Mail: ershengaaa@163.com
> Created Time: Fri 22 Feb 2019 04:51:17 PM CST
************************************************************************/
#include<iostream>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
using namespace std;
int main() {
pid_t pid = fork();
if (pid > 0) {
printf("in parent process, sleep for one miniute...zZ...\n");
sleep(3);
printf("after sleeping, and exit!\n");
}
else if (pid == 0) {
printf("in child process, and exit!\n");
exit(0);
}
return 0;
}
父进程中休眠3秒,让子进程先退出。而且并没有使用
w
a
i
t
(
)
wait()
wait()等系统调用函数,因此在子进程退出之后变成僵尸进程
在上述程序运行过程中,可以打开另一个终端,执行
p
s
ps
ps
a
u
x
aux
aux |
g
r
e
p
grep
grep -
w
w
w 'Z’命令:
可以看到,此时系统中都出了一个僵尸进程。等待父进程睡眠醒来并退出之后,再次查看系统进程信息,会发现刚才的僵尸进程不见了,如下图所示,。因为父进程退出后,该进程由僵尸进程转变为孤儿进程,被
i
n
i
t
init
init进程收养,而
i
n
i
t
init
init进程会周期性的调用
w
a
i
t
wait
wait系统调用来清除各个僵尸的子进程
wait()与waitpid()
wait()函数
头文件为:
#include <sys/types.h>
#include <sys/wait.h>
函数原型为:
pid_t wait(int *status);
进程一旦调用 w a i t ( ) wait() wait()函数,就立即阻塞自己,由 w a i t wait wait自动分析是否当前进程的某个子进程已经退出,如果子进程已经终止,并且是一个僵尸进程,则 w a i t wait wait立即返回并取得该子进程的状态,否则 w a i t wait wait使其调用者阻塞,直到一个子进程终止。
使用 w a i t ( ) wait() wait()函数,如果执行成功则返回子进程识别码( P I D PID PID);如果有错误发生则返回-1,并将失败原因存于 e r r n o errno errno中
例2:使用 w a i t ( ) wait() wait()函数回收子进程
/*************************************************************************
> File Name: wait_Sub.cpp
> Author: ersheng
> Mail: ershengaaa@163.com
> Created Time: Fri 22 Feb 2019 05:11:17 PM CST
************************************************************************/
#include<iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork error\n");
return 0;
} else if(pid > 0) {
printf("Parent process\n");
pid_t pr = wait(NULL);
printf("Parent process, I catched a child process with pid of %d\n", pr);
} else if (pid == 0) {
printf("Sub-process, PID: %u, PPID: %u\n", getpid(), getppid());
exit(0);
}
return 0;
}
关于
w
a
i
t
(
)
wait()
wait()函数参数
s
t
a
t
u
s
status
status
如果参数 s t a t u s status status的值不是 N U L L NULL NULL, w a i t wait wait就会把子进程退出时的状态取出并存放其中,这是一个整数值,指出了子进程是正常退出还是被非正常结束的,以及正常结束时的返回值或被哪一个信号结束的等信息。
由于这些信息被存放在一个整数的不同二进制位中,所以人们设计了一套宏;来完成这项工作,下面简单介绍常用的两个:
- 1) W I F E X I T E D WIFEXITED WIFEXITED ( s t a t u s status status),这个宏用来指出子进程是否为正常退出的,是则返回一个非零值
- 2) W E X I T S T A T U S WEXITSTATUS WEXITSTATUS( s t a t u s status status),当 W E X I T S T A T U S WEXITSTATUS WEXITSTATUS返回非零值时,可以用这个宏来提取子进程的返回值,如果子进程调用 e x i t ( 5 ) exit(5) exit(5)退出,那么 W E X I T S T A T U S WEXITSTATUS WEXITSTATUS( s t a t u s status status)就返回5
例3:利用 W E X I T S T A T U S WEXITSTATUS WEXITSTATUS获得子进程的返回码
/*************************************************************************
> File Name: get_status.cpp
> Author: ersheng
> Mail: ershengaaa@163.com
> Created Time: Fri 22 Feb 2019 05:27:09 PM CST
************************************************************************/
#include<iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
using namespace std;
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork error\n");
return 0;
}
else if (pid > 0) {
printf("Parent process\n");
int status = -1;
pid_t pr = wait(&status);
if (WIFEXITED(status)) { //指出是否正常返回,正常返回则调用WEXITTSTATUS回收返回码
printf("the child process %d exit normally.\n", pr);
printf("the return code is %d.\n", WEXITSTATUS(status));
} else {
printf("the child process %d exit abnormally.\n", pr);
}
} else if(pid == 0) {
printf("Sub-process, PID: %u, PPID: %u\n", getpid(), getppid());
sleep(10);
exit(3);
}
return 0;
}
waitpid()函数
从本质上讲, w a i t p i d waitpid waitpid是 w a i t wait wait的封装, w a i t p i d waitpid waitpid只是多出两个可由用户控制的参数 p i d pid pid和 o p t i o n s options options,为编程提供了灵活性。
头文件:
#include <sys/types.h>
#include <sys/wait.h>
函数原型:
pid_t waitpid(pid_t pid, int *status, int options);
w a i t p i d ( ) waitpid() waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用 w a i t p i d ( ) waitpid() waitpid()时子进程已经结束,则 w a i t p i d ( ) waitpid() waitpid()会立即返回子进程结束状态值。子进程的结束状态值会由参数 s t a t u s status status返回,而子进程的进程识别码也会一起返回。如果不在意结束状态值,则参数 s t a t u s status status可以设成 N U L L NULL NULL
(1)参数 p i d pid pid为欲等待的子进程识别码
- 1) p i d pid pid<-1:等待进程组识别码为 p i d pid pid绝对值的任何子进程
- 2) p i d pid pid=-1:等待任何子进程,相当于 w a i t wait wait
- 3) p i d pid pid=0:等待任何子进程识别码与目前进程相同的任何子进程
- 4) p i d pid pid>0:等待任何子进程识别码为 p i d pid pid的子进程
(2)参数 o p t i o n s options options的值有以下几种类型
- 1) o p t i o n s = W N O H A N G options=WNOHANG options=WNOHANG,即使没有子进程退出,它也会立即返回,不会像 w a i t wait wait那样永远等下去
- 2) o p t i o n s = W U N T R A C E D options=WUNTRACED options=WUNTRACED,则子进程进入暂停则马上返回,但结束状态不予以理会
L i n u x Linux Linux中只支持 W N O H A N G WNOHANG WNOHANG和 W U N T R A C E D WUNTRACED WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如:
ret=waitpid(-1, NULL, WNOHANG | WUNTRACED);
如果不想使用它们,也可以把 o p t i o n s options options设为0:
ret=waitpid(-1, NULL, 0);
(3) w a i t p i d waitpid waitpid的返回值如下所述:
- 1)当正常返回的时候 w a i t p i d waitpid waitpid返回收集到的子进程的进程 I D ID ID
- 2)如果设置了选项WNOHANG,而调用中 w a i t p i d waitpid waitpid发现没有已退出的子进程可收集,则返回0
- 3)如果调用中出错,则返回-1,这时 e r r n o errno errno会被设置成相应的值以指示错误所在
- 4)当 p i d pid pid所指示的子进程不存在时,或此进程存在,但不是调用进程的子进程, w a i t p i d waitpid waitpid就会出错返回,这时 e r r n o errno errno被设置为 E C H I L D ECHILD ECHILD
例4:使用 w a i t p i d waitpid waitpid函数收集子进程信息
/*************************************************************************
> File Name: waitpid.cpp
> Author: ersheng
> Mail: ershengaaa@163.com
> Created Time: Fri 22 Feb 2019 06:58:46 PM CST
************************************************************************/
#include<iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
int main() {
pid_t pid, pr;
pid = fork();
if (pid < 0) {
printf("Error occured on forking.\n");
}
else if (pid == 0) {
printf("Sub process will sleep for 10 seconds.\n");
sleep(10);
exit(0);
} else if (pid > 0) {
do {
//使用WNOHANG参数,waitpid不会在这里等待
pr = waitpid(pid, NULL, WNOHANG);
if (pr == 0) { //如果没有收集到子进程
printf("No child exited\n");
sleep(1);
}
}while (pr == 0); //没有收集到子进程,就继续回去尝试
if (pr == pid)
printf("successfully get child %d\n", pr);
else
printf("some error occured\n");
}
return 0;
}
守护进程
在 L i n u x Linux Linux或者 U N I X UNIX UNIX操作系统中在系统的引导的时候会开启很多服务,这些服务就叫作守护进程。
守护进程是脱离于终端并且在后台运行的进程。守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断。
守护进程是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件.守护进程常常在系统引导装入时启动,在系统关闭时终止。
如果想让某个进程不因为用户或终端或其他地变化而受到影响,那么就必须把这个进程变成一个守护进程
守护进程的创建步骤
(1)创建子进程,父进程退出。
经过这步以后,子进程就会成为孤儿进程(父进程先于子进程退出, 此时的子进程,成为孤儿进程,会被
i
n
i
t
init
init进程收养)。使用
f
o
r
k
(
)
fork()
fork()函数,如果返回值大于0,表示为父进程,
e
x
i
t
(
0
)
exit(0)
exit(0),父进程退出,子进程继续。
(2)在子进程中创建新会话,使当前进程成为新会话组的组长。
使用
s
e
t
s
i
d
(
)
setsid()
setsid()函数,如果当前进程不是进程组的组长,则为当前进程创建一个新的会话期,使当前进程成为这个会话组的首进程,成为这个进程组的组长。
(3)改变当前目录为根目录。
由于守护进程在后台运行,开始于系统开启,终止于系统关闭,所以要将其目录改为系统的根目录下。进程在执行时,其文件系统不能被卸下。
(4)重新设置文件权限掩码。
进程从父进程那里继承了文件创建掩码,所以可能会修改守护进程存取权限位,所以要将文件创建掩码清除,
u
m
a
s
k
(
0
)
umask(0)
umask(0);
(5)关闭文件描述符。
子进程从父进程那里继承了打开文件描述符。所以使用
c
l
o
s
e
close
close即可关闭。
例5:创建一个守护进程(每隔10s在/ t m p tmp tmp/ d a m e o n . l o g dameon.log dameon.log中写入一句话)
/*************************************************************************
> File Name: guard.cpp
> Author: ersheng
> Mail: ershengaaa@163.com
> Created Time: Sat 23 Feb 2019 04:05:29 PM CST
************************************************************************/
#include<iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
using namespace std;
#define MAXFILE 65535
int main(){
pid_t pc;
int i,fd,len;
char *buf="this is Dameon\n";
len = strlen(buf);
pc = fork(); /* 第一步*/
if (pc<0){
printf("error fork\n");
exit(1);
}else if(pc>0){
exit(0);
}
setsid(); /* 第二步*/
chdir("/"); /* 第三步 */
umask(0); /* 第四步 */
for (i = 0; i<MAXFILE;i++)
close(i); /* 第五步 */
while (1){
if ((fd=open("/tmp/dameon.log",O_CREAT | O_WRONLY | O_APPEND,0600))<0){
perror("open");
exit(1);
}
write(fd,buf,len+1);
close(fd);
sleep(10);
}
return 0;
}
执行编译后的目标文件,得到下列结果(看起来像执行后立即退出):
执行
p
s
ps
ps
−
e
f
-ef
−ef |
g
r
e
p
grep
grep
t
e
s
t
test
test命令,可以看到它还在运行,不过是在后台运行:
再查看/
t
m
p
tmp
tmp/
d
a
m
e
o
n
.
l
o
g
dameon.log
dameon.log文件,还在每隔10s就打印一行“this is a Dameon”:
如果不想这个
d
a
m
e
o
n
dameon
dameon程序继续进行,可以直接把进程杀掉,这样进程就可以结束了: