五、共享文件
fork()的一个特性是在父进程中打开的文件描述符都会被复制到子进程中,父进程和子进程共享同一
个文件偏移量。
如果父进程和子进程写同一描述符指向的文件,但又没有任何形式的同步,那么它们的输出可能会相
互混合。
示例:
#include <iostream>
#include <fstream>
#include <unistd.h>
using namespace std;
int main()
{
ofstream fout;
fout.open("/tmp/tmp.txt"); // 打开文件。
fork();
for (int ii=0;ii<10000000;ii++) // 向文件中写入一千万行数据。
{
fout << "进程" << getpid() << "西施" << ii << "极漂亮" << "\n"; // 写入的
内容无所谓。
}
fout.close(); // 关闭文件。
}
六、vfork()函数
vfork()函数的调用和返回值与 fork()相同,但两者的语义不同。
vfork()函数用于创建一个新进程,而该新进程的目的是 exec 一个新程序,它不复制父进程的地址空
间,因为子进程会立即调用 exec,于是也就不会使用父进程的地址空间。如果子进程使用了父进程的地
址空间,可能会带来未知的结果。
vfork()和 fork()的另一个区别是:vfork()保证子进程先运行,在子进程调用 exec 或 exit()之后父进
程才恢复运行。
316、僵尸进程
如果父进程比子进程先退出,子进程将被 1 号进程托管(这也是一种让程序在后台运行的方法)。
如果子进程比父进程先退出,而父进程没有处理子进程退出的信息,那么,子进程将成为僵尸进程。
僵尸进程有什么危害?内核为每个子进程保留了一个数据结构,包括进程编号、终止状态、使用 CP
U 时间等。父进程如果处理了子进程退出的信息,内核就会释放这个数据结构,父进程如果没有处理子进
程退出的信息,内核就不会释放这个数据结构,子进程的进程编号将一直被占用。系统可用的进程编号是
有限的,如果产生了大量的僵尸进程,将因为没有可用的进程编号而导致系统不能产生新的进程。
僵尸进程的避免:
1)子进程退出的时候,内核会向父进程发头 SIGCHLD 信号,如果父进程用 signal(SIGCHLD,SIG_I
GN)通知内核,表示自己对子进程的退出不感兴趣,那么子进程退出后会立即释放数据结构。
2)父进程通过 wait()/waitpid()等函数等待子进程结束,在子进程退出之前,父进程将被阻塞待。
pid_t wait(int *stat_loc);
pid_t waitpid(pid_t pid, int *stat_loc, int options);
pid_t wait3(int *status, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage);
返回值是子进程的编号。
stat_loc 是子进程终止的信息:a)如果是正常终止,宏 WIFEXITED(stat_loc)返回真,宏 WEXITST
ATUS(stat_loc)可获取终止状态;b)如果是异常终止,宏 WTERMSIG(stat_loc)可获取终止进程的信号。
3)如果父进程很忙,可以捕获 SIGCHLD 信号,在信号处理函数中调用 wait()/waitpid()。
示例一:
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main()
{
if (fork()>0)
{ // 父进程的流程。
int sts;
pid_t pid=wait(&sts);
cout << "已终止的子进程编号是:" << pid << endl;
if (WIFEXITED(sts)) { cout << "子进程是正常退出的,退出状态是:" << WEXITSTATUS(sts)
<< endl; }
else { cout << "子进程是异常退出的,终止它的信号是:" << WTERMSIG(sts) << endl; }
}
else
{ // 子进程的流程。
//sleep(100);
int *p=0; *p=10;
exit(1);
}
}
示例二:
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
void func(int sig) // 子进程退出的信号处理函数。
{
int sts;
pid_t pid=wait(&sts);
cout << "已终止的子进程编号是:" << pid << endl;
if (WIFEXITED(sts)) { cout << "子进程是正常退出的,退出状态是:" << WEXITSTATUS(sts) <<
endl; }
else { cout << "子进程是异常退出的,终止它的信号是:" << WTERMSIG(sts) << endl; }
}
int main()
{
signal(SIGCHLD,func); // 捕获子进程退出的信号。
if (fork()>0)
{ // 父进程的流程。
while (true)
{
cout << "父进程忙着执行任务。\n";
sleep(1);
}
}
else
{ // 子进程的流程。
sleep(5);
// int *p=0; *p=10;
exit(1);
}
}
317、多进程与信号
在多进程的服务程序中,如果子进程收到退出信号,子进程自行退出,如果父进程收到退出信号,则
应该先向全部的子进程发送退出信号,然后自己再退出。
示例:
#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;
void FathEXIT(int sig); // 父进程的信号处理函数。
void ChldEXIT(int sig); // 子进程的信号处理函数。
int main()
{
// 忽略全部的信号,不希望被打扰。
for (int ii=1;ii<=64;ii++) signal(ii,SIG_IGN);
// 设置信号,在 shell 状态下可用 "kill 进程号" 或 "Ctrl+c" 正常终止些进程
// 但请不要用 "kill -9 +进程号" 强行终止
signal(SIGTERM,FathEXIT); signal(SIGINT,FathEXIT); // SIGTERM 15 SIGINT 2
while (true)
{
if (fork()>0) // 父进程的流程。
{
sleep(5); continue;
}
else // 子进程的流程。
{
// 子进程需要重新设置信号。
signal(SIGTERM,ChldEXIT); // 子进程的退出函数与父进程不一样。
signal(SIGINT ,SIG_IGN); // 子进程不需要捕获 SIGINT 信号。
while (true)
{
cout << "子进程" << getpid() << "正在运行中。\n"; sleep(3); continue;
}
}
}
}
// 父进程的信号处理函数。
void FathEXIT(int sig)
{
// 以下代码是为了防止信号处理函数在执行的过程中再次被信号中断。
signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);
cout << "父进程退出,sig=" << sig << endl;
kill(0,SIGTERM); // 向全部的子进程发送 15 的信号,通知它们退出。
// 在这里增加释放资源的代码(全局的资源)。
exit(0);
}
// 子进程的信号处理函数。
void ChldEXIT(int sig)
{
// 以下代码是为了防止信号处理函数在执行的过程中再次被信号中断。
signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);
cout << "子进程" << getpid() << "退出,sig=" << sig << endl;
// 在这里增加释放资源的代码(只释放子进程的资源)。
exit(0);
}