1. fork
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main (void)
{
pid_t pid;
int i = 3;
pid = fork();
switch(pid){
case 0:
while(i--){
printf("A background process, PID:%d, ParentID:%d\n", getpid(), getppid());
}
sleep(1);
break;
case -1:
perror("Process creation failed.\n");
exit(1);
default:
printf("I am parent process, my pid is %d\n", getpid());
exit(0);
}
return 0;
}
运行结果为:
在ubuntu的图形界面中,收养孤儿进程的并非init进程,而是交给了其子进程,所以显示的父进程id不是1。可以将系统转到字符界面(alt + shift + F1),再运行程序,会发现其父进程id变为1,即init进程收养了该孤儿进程。回到图形界面用alt + shift + F7
.
2. 孤儿进程与僵尸进程
上面说到了孤儿进程,所以再来总结下孤儿进程
与僵尸进程
的区别。
孤儿进程: 父进程退出,而其子进程仍在运行,则这些子进程将成为孤儿进程。孤儿进程被init进程收养(进程号为1),并完成对状态收集工作。就像没有父母而被收养的孤儿。
僵尸进程: 某子进程已退出,而父进程没有调用wait
或waitpid
来获取子进程的状态信息,所以子进程的进程描述符仍保存在系统中,占用系统资源。就像僵尸,已经died,仍要进行肢体活动。
为什么会产生僵尸进程呢?
unix为了保证父进程可以得到子进程结束时的状态信息,在每个进程退出的时候,内核释放该进程所有的资源(打开的文件,占用的内存等),但仍然为其保留一定的信息(进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。因此如果进程不调用wait / waitpid
的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用。僵尸进程过多就会导致无可用进程号。(kill等命令只可以杀死正在运行的进程,并不能清除僵尸进程的信息)
参考资料: http://www.cnblogs.com/Anker/p/3271773.html
3. 创建守护进程
1) 基本步骤:
- 让进程在后台执行:fork一个子进程,然后是父进程退出;
- 调用setsid创建新对话期,摆脱控制终端等的影响,成为无终端的会话组长;
- 禁止进程重新打开终端:fork一个新的子进程,使父进程退出;
- 关闭不需要的文件描述符,避免浪费系统资源;
- 将当前目录改为根目录,防止当前文件系统不能被拆卸;;
- 将文件屏蔽字设为0;
- 处理SIGCHLD信号,设为SIG_IGN,使子进程结束时不会产生僵尸进程。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <signal.h>
#include <syslog.h>
#include <time.h>
int init_daemon(void){
int pid;
int i;
/*忽略终端I/O信号,STOP信号*/
/*防止守护进程没有正常运行时,控制终端受干扰退出或挂起*/
signal(SIGTTOU, SIG_IGN); //Background Group的进程尝试写Terminal时
signal(SIGTTIN, SIG_IGN); //Background Group的进程尝试读取Terminal时
signal(SIGTSTP, SIG_IGN); //Suspend Key,一般是Ctrl+Z, 发送给所有Foreground Group的进程
signal(SIGHUP, SIG_IGN); //发送给具有Terminal的Controlling Process,当terminal被disconnect时
pid = fork();
if(pid > 0)
exit(0); //结束父进程,成为后台进程
else if(pid < 0)
return -1;
setsid();//新建进程组,脱离终端控制
/*新建进程,退出父进程,不再是进程组长,使其无法打开终端*/
pid = fork();
if(pid > 0)
exit(0);
else if(pid < 0)
return -1;
/*关闭父进程无用文件描述符*/
for(i = 0; i < NOFILE; close(i++))
;
chdir("/");//改变工作目录
umask(0);//将文件屏蔽字设为0
signal(SIGCHLD, SIG_IGN);//忽略SIGCHLD信号
return 0;
}
int main (void)
{
time_t now;
init_daemon();
syslog(LOG_USER|LOG_INFO, "测试守护进程!\n");
while(1){
sleep(8);
time(&now);
syslog(LOG_USER|LOG_INFO, "系统时间: \t%s\t\t\n", ctime(&now));//需要先配置日志文件
}
return 0;
}
4. fork返回值
一次调用,两次返回。
成功fork后,当前进程分裂为两个进程,父进程
和子进程
:一次返回值为0,表示子进程在运行;另一次返回值大于0,表示父进程在运行,返回值即子进程的进程ID。进程创建失败则返回-1.