fork函数:
fork函数的作用是从调用进程中创建一个新的进程,新的进程相当于是调用进程的副本,称为子进程,而调用进程称为父进程。
本节主要讲解父子进程之间的联系和区别。
函数原型:
#include
pid_t fork(void);
返回值:
fork调用为什么会返回两次呢?
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,这也是fork难以理解的点。
由于子进程复制了父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。因此fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的。
fork函数的作用:
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程。子进程是父进程的副本,子进程获得父进程数据空间、栈和堆的副本,这是子进程所拥有的副本,但是父子进程并不共享这些存储空间部分。
接下来看看示例例子,看看实验结果是不是如上所说的。
示例程序:
/* fork.c*/
#include
#include
#include
int main ()
{
pid_t pid;
int count=0;
if ((pid = fork()) < 0)
{
perror("fork");
exit(-1);
}
else if (pid == 0)
{
printf("child\n");
count++;
}
else
{
printf("parent\n");
sleep(1);
}
printf("pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count);
return 0;
}
以上程序fork之后,父进程相当于执行如下代码:
#include
#include
#include
int main ()
{
pid_t pid;
int count=0;
printf("parent\n");
sleep(1);
printf("pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count);
return 0;
}
子进程相当于执行如下代码:
#include
#include
#include
int main ()
{
pid_t pid; //pid表示fork函数返回的值
int count=0;
printf("child\n");
count++;
printf("pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count);
return 0;
}
实验结果:
ubuntu:~/test/process_test$ gcc fork.c -o fork
ubuntu:~/test/process_test$ ./fork
parent
child
pid = 88270,ppid = 88269, count = 1
//隔一秒才打印下面父进程的log
pid = 88269,ppid = 25872, count = 0
以上例子可以看出,子进程和父进程的count变量是不共享的,是各自存储空间的,从打印出来的count变量的值不相同可以证明这一点。
下面再来看一个例子,可以初步证明子进程是父进程的副本。
/* fork2.c*/
#include
#include
#include
int main ()
{
pid_t pid; //pid表示fork函数返回的值
int count=0;
if ((pid = fork()) < 0)
{
perror("fork");
exit(-1);
}
else if (pid == 0)
{
printf("child\n");
count++;
printf("child: pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count);
}
else
{
printf("parent\n");
sleep(1);
printf("parent:pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count);
}
return 0;
}
实验结果:
ubuntu:~/test/process_test$ gcc fork2.c -o fork2
ubuntu:~/test/process_test$ ./fork2
parent
child
child: pid = 88754,ppid = 88753, count = 1
//隔一秒才打印下面父进程的log
parent:pid = 88753,ppid = 25872, count = 0
从实验结果可以看出,fork2.c和fork.c效果是一致的,证明了fork.c中父进程printf代码会被子进程拷贝一个副本。一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的,这取决于内核所使用的调度算法。所以,这里我们让父进程sleep再打印log,是为了让子进程先执行打印log,方便我们对结果的分析。
还有一个重要的点是,子进程和父进程共享同一个文件偏移量。
下面用实例程序来测试文件偏移量是否共享。
/* open_fork.c*/
#include
#include
#include
#include
#include
#include
#define FILENAME "file_fork"
/*
*测试文件偏移量是否共享
*/
int main ()
{
pid_t pid; //pid表示fork函数返回的值
int count=0;
char write_child_buf[] = "11111";
char write_parent_buf[] = "22222";
int fd = open(FILENAME,O_RDWR | O_CREAT,0777);
if(fd < 0)
{
perror("open");
}
if ((pid = fork()) < 0)
{
perror("fork");
exit(-1);
}
else if (pid == 0)
{
printf("child\n");
if (write(fd,write_child_buf,sizeof(write_child_buf)))
{
perror("write");
}
count++;
printf("child: pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count);
}
else
{
printf("parent\n");
sleep(2);
if (write(fd,write_parent_buf,sizeof(write_parent_buf)))
{
perror("write");
}
printf("parent:pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count);
}
return 0;
}
实验结果:
ubuntu:~/test/process_test$ ./open_fork
parent
child
write: Success
child: pid = 6737,ppid = 6736, count = 1
write: Success
parent:pid = 6736,ppid = 25872, count = 0
ubuntu:~/test/process_test$ cat file_fork
1111122222chenting
从上面的结果中,我们看到,子进程向文件file_fork写入字符串“11111”,然后父进程向file_fork向文件写入字符串“22222”,可以看到字符串“22222”是追加到字符串“11111”后面的,说明父子进程文件描述符、文件偏移量是有被共享的。
(以下内容参考《unix环境高级编程》)
这种共享文件的方式使父子进程对同一文件使用了一个文件偏移量,如果父子进程都向标准输出进行写操作,如果父进程的标准输出已经重定向,那么子进程写到标准输出时,它将更新与父进程共享的该文件的偏移量。比如当父进程等待子进程时,子进程写到标准输出,而在子进程终止后,父进程也写到标准输出,并且其输出会添加在子进程所写数据之后,如果父子进程不共享同一文件偏移量,这种形式的交互很难实现。除了打开文件之外,父进程的很多其他属性也由子进程继承,包括:
父子进程的区别是: