孤儿进程与僵尸进程
孤儿进程:
如果父进程先退出,子进程还没退出那么子进程成为孤儿进程,此时子进程的父进程将变为init进程(托孤给了init进程)。(注:任何一个进程都必须有父进程)
//生成孤儿进程
int main(int argc, char *argv[])
{
pid_t pid = fork();
if (pid < 0)
err_exit("fork error");
else if (pid > 0)
exit(0);
else
{
sleep(10);
cout << "Child, ppid = " << getppid() << endl;
}
exit(0);
}
僵尸进程:
如果子进程先退出,父进程还没退出,那么子进程必须等到父进程捕获到了子进程的退出状态才真正结束,否则这个时候子进程就成为僵尸进程。
//生成僵尸进程
int main(int argc, char *argv[])
{
pid_t pid = fork();
if (pid < 0)
err_exit("fork error");
else if (pid == 0)
exit(0);
else
{
sleep(50);
}
exit(0);
}
查询父子进程状态
ps -le | grep main
避免僵尸进程
signal(SIGCHLD, SIG_IGN);
通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。
//示例: 避免僵尸进程
int main(int argc, char *argv[])
{
signal(SIGCHLD, SIG_IGN);
pid_t pid = fork();
if (pid < 0)
err_exit("fork error");
else if (pid == 0)
exit(0);
else
{
sleep(50);
}
exit(0);
}
文件共享
父进程的所有文件描述符都被复制到子进程中, 就好像调用了dup函数, 父进程和子进程每个相同的打开文件描述符共享一个文件表项(因此, 父子进程共享同一个文件偏移量);
//根据上图: 理解下面这段程序和下图的演示
int main(int argc, char *argv[])
{
signal(SIGCHLD, SIG_IGN);
int fd = open("test.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
if (fd == -1)
err_exit("file open error");
cout << "We Don`t flash memory\n";
char buf[BUFSIZ];
bzero(buf, sizeof(buf));
pid_t pid = fork();
if (pid < 0)
err_exit("fork error");
else if (pid > 0)
{
strcpy(buf, "Parent...");
write(fd, buf, strlen(buf));
close(fd);
cout << "fd = " << fd << endl;
exit(0);
}
else if (pid == 0)
{
strcpy(buf, "Child...");
write(fd, buf, strlen(buf));
close(fd);
cout << "fd = " << fd << endl;
exit(0);
}
}
fork与vfork
在UNIX/Linux中的fork还没实现copy on write(写时复制)技术之前。Unix设计者很关心fork之后立刻执行exec所造成的地址空间浪费,所以引入了vfork系统调用。其中,vfork子进程与父进程共享数据段,并不真正复制父进程内存,因此在vfork之后执行exec系列函数,并不会导致地址空间浪费以及无用的空间复制时间.而且,即使fork实现了copy on write,效率也没有vfork高.
但是,vfork有个限制,子进程必须立刻执行_exit或者exec系列函数。因此我们不推荐使用vfork,因为几乎每一个vfork的实现,都或多或少存在一定的问题。
区别:
1、fork()用于创建一个新进程。由fork()创建的子进程是父进程的副本。即子进程获取父进程数据段、堆和栈的副本。父子进程之间不共享这些存储空间的部分。而vfork()创建的进程并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec (或exit)于是也就不会存放该地址空间。相反,在子进程调用exec或_exit之前,它在父进程的空间进行。
2、vfork()与fork()另一个区别就是:vfork保证子进程先运行,在调用exec或_exit之前与父进程数据是共享的,在它调用exec或_exit之后父进程才可能被调度运行。(如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。)
相同:
1、两者被调用一次,但是返回两次。两次返回的唯一区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程ID。
//示例1:vfork出错情况
//在Linux 2.6内核上会持续执行,不会退出
//而在Linux 3.13内核上, 则会引发core dump
int main()
{
int iNumber = 0;
pid_t pid = vfork();
if (pid == -1)
{
perror("fork");
return -1;
}
else if (pid > 0)
{
cout << "In Parent Program..." << endl;
cout << "iNumber = " << iNumber << endl;
cout << "pid = " << static_cast<int>(getpid());
cout << "\t ppid = " << static_cast<int>(getppid()) << endl;
//_exit(0);
}
else if (pid == 0)
{
iNumber ++;
cout << "In Child Program..." << endl;
cout << "iNumber = " << iNumber << endl;
cout << "pid = " << static_cast<int>(getpid());
cout << "\t ppid = " << static_cast<int>(getppid()) << endl;
//_exit(0);
}
return 0;
}
//示例2: 父进程/子进程修改全局数据的情况
int main()
{
int iNumber = 10;
cout << "Before vfork, pid = " << getpid() << endl;
//对比fork()
pid_t pid = vfork();
if (pid == -1)
err_exit("fork");
else if (pid > 0)
{
sleep(4);
cout << "Parent, iNumber: " << iNumber << endl;
}
else if (pid == 0)
{
++ iNumber;
cout << "Child, iNumber = " << iNumber << endl;
_exit(0);
}
return 0;
}
//示例3:用vfork执行当前目录下的hello程序
int main()
{
int iNumber = 0;
pid_t pid = vfork();
if (pid == -1)
{
perror("fork");
return -1;
}
else if (pid > 0)
{
cout << "In Parent Program..." << endl;
cout << "iNumber = " << iNumber << endl;
cout << "pid = " << static_cast<int>(getpid());
cout << "\t ppid = " << static_cast<int>(getppid()) << endl;
}
else if (pid == 0)
{
iNumber ++;
cout << "In Child Program..." << endl;
cout << "iNumber = " << iNumber << endl;
cout << "pid = " << static_cast<int>(getpid());
cout << "\t ppid = " << static_cast<int>(getppid()) << endl;
//将自己写的程序启动起来
execve("./hello",NULL,NULL);
_exit(0);
}
return 0;
}
//测试4,用vfork执行系统命令
int main()
{
int iNumber = 0;
pid_t pid = vfork();
if (pid == -1)
{
perror("fork");
return -1;
}
else if (pid > 0)
{
cout << "In Parent Program..." << endl;
cout << "iNumber = " << iNumber << endl;
cout << "pid = " << static_cast<int>(getpid());
cout << "\t ppid = " << static_cast<int>(getppid()) << endl;
}
else if (pid == 0)
{
iNumber ++;
cout << "In Child Program..." << endl;
cout << "iNumber = " << iNumber << endl;
cout << "pid = " << static_cast<int>(getpid());
cout << "\t ppid = " << static_cast<int>(getppid()) << endl;
//将ls命令启动起来,注意:由于C++严格的类型转换机制,需要在字符串前加(char*)
char *const args[] = {(char *)"/bin/ls", (char *)"-l", NULL};
int res = execve("/bin/ls",args,NULL);
if (res == -1)
{
perror("execve");
_exit(1);
}
_exit(0);
}
return 0;
}