fork()函数:
一:介绍
fork是在进程管理模块中的一个重要的函数。那么怎样来创建进程呢?
①:程序----- (执行)-----》进程 ./main |
②:由程序创建进程 pid_t fork(void) |
二:函数简介
一个现有进程可以调用fork函数创建一个新的进程。
1.描述: | 一个现有进程可以调用fork函数创建一个新的进程。 |
2.头文件: | #include<unistd.h> |
3.使用方法: | :pid_t n = fork() |
4.返回值: | fork()函数被调用一次返回两次。 两次返回值的区别是:子进程返回值是0,父进程返回子进程ID。 出错返回0 |
三:函数特点
1.为什么要将子进程的ID返回给父进程?
因为一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有子进程的ID。
2.fork使子进程得到的返回值为0的理由是?
一个进程只会有一个父进程,所以子进程总可以调用getppid以获得其父进程的ID(进程ID为0 是由内核交换进程使用,所以 一个子进程的进程ID不可能为0)。
3.调用特点:父子进程继续执行fork调用后的指令,子进程是父进程的副本。
例:子进程获得父进程的数据空间,堆,栈的副本,注意这是子进程所拥有的副本,父子进程不共享这些存储空间部分-》所以父子进程的这些空间不再同一个位置。
父子进程共享正文段。
4.写时拷贝技术:fork之后父子进程共享所有的空间(数据段,栈,和堆),而且内核将他们的访问权限变为只读的。if父子进程中的任意一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常为虚拟机存储器中的一个“页”。
作用:①提高fork的效率;②fork之后,子进程往往会调用exec进行进程切换。
5.运行特点:
父子进程是并发运行的:父进程创建出子进程后,两个进程就是独立的个体,各自运行互不干扰。
父子进程谁先运行不由fork来决定,由系统当前环境和进程调度算法决定。
6.父子进程是否共享fork之前打开的文件描述符?
共享:子进程能够使用fork之前打开的文件描述符访问文件内容。但是父子进程通过fork之前打开的文件描述符访问文件时,是互相影响的----》读写偏移量。
四:代码解析
#include"apue.h"
int globvar = 6;
char buf[] = "a write to stdout\n";
int main(int argc, char* argv[])
{
int var = 88;
pid_t pid;
if (write(STDOUT_FILRNO, buf, sizeof(buf) - 1) != sizeof(buf) - 1)
err_sys("write error");
printf("before fork\n");//we do not flush stdout
if ((pid = fork()) < 0)
{
err_sys("fork error:\n");
}
else if (pid == 0)//child process
{
globvar++;
var++;
}
else//father process
{
sleep(2);
}
printf("pid = %ld,glob = %d,var = %d\n", (long)getpid(), globvar, var);
exit(0);
}
:fork(之后谁先执行是不确定的,这取决于内核的调度算法。如果想使父子进程之间相互异步或者同步,则要求某种形式的进程之间的通信,在上述程序中,父进程使用了sleep(2),睡眠两秒钟的时间来使子进程先执行,但是并不能够保证2s已经足够,在后续中会补充讲解到。
当标准输出时,我们将buf的长度减去1作为输出字节数,这是为了避免将终止字符null输写出。strlen计算时不包含终止null字节的字符串的长度,而sizeof则计算包括终止字符null字节的缓冲区的长度。另外两者之间的差别还有,使用strlen需进行一次函数调用,而对于sizeof来说,因为缓冲已经用已知字符串进行初始化了,其长度是固定的,所以sizeof是在编译时计算缓冲区的长度。
$ ./a.out
a write to stdout
before fork
pid = 432, glob = 7,var = 89 子进程的变量值改变了
pid = 429, glob = 6,var = 88父进程的变量值没有改变
主要是因为写诗拷贝技术的存在。
五:代码分析
程序一:
int main()
{
pid_t n = fork();
assert(n != -1);
if (n == 0)
{
printf("child fork is running:!\n");
}
else
{
printf("father fork is running:!\n");
}
exit(0);
}
解析:
fork()函数的调用父进程返回子进程的ID,子进程返回0,所以父进程执行else,子进程执行if。
程序二:
int main()
{
if (fork() && fork())
{
printf("A \n");
}
else
{
printf("B \n");
}
exit(0);
}
解析:
考察fork()函数返回值 和 子进程保留父进程转态,调用第一个fork()函数时,子进程1返回0,不执行逻辑与后面的判断,直接执行else,输出B。而父进程返回子进程1的ID,在判断第二个fork()函数,其子进程2返回0,则执行lese输出B,父进程返回子进程2的ID,执行if输出A。
输出两个B一个A。
程序三:
int main()
{
int i = 0;
for (; i < 2; ++i)
{
if (fork() == 0)
printf("A \n");
else
printf("B \n");
}
exit(0);
}
解析:
由于这里面涉及到,循环所以我们要搞清楚循环过程中的父子进程直接的关系!
这里会输出3个A和3个B.
程序四:问将程序3的两个printf中的\n去掉,会出现什么样的结果呢?