fork():
1.功能:创建子进程
2.返回:失败返回-1, 成功,父进程返回子进程id,子进程返回0;
返回失败的原因:
1.进程数达到系统规定上限,errno被置为EAGAIN,
2. 系统内存不足,errno被置为ENOMEM
3.调用fork之后通常要用if进行分流
fork()一般用法:
1.父进程希望复制自己,是父子进程能同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
2.一个进程要执行一个不同的程序。例如,子进程从fork()返回后,调用exec函数。
fork()注意:
1.一次调用两个返回值,父进程返回子进程pid,子进程返回0;
2.父进程和子进程都从fork()执行结束之后的位置继续执行;
3.子进程以父进程为模板(PCB,数据,代码),写时拷贝;
4.调用fork后, 父子进程运行顺序不定
5.如果父进程先死,子进程变为孤儿进程,,孤儿进程会被1号进程收养
6.如果子进程先死,就会变成僵尸进程。 处理僵尸进程-----结束父进程
僵尸进程:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
exit(1);
}
else if(0 == pid) //子进程先运行结束,而父进程对此没有进行处理
{
printf("child:[%d]\n", getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
else
{
printf("parent:[%d]\n", getpid());
sleep(20);
}
return 0;
}
孤儿进程:
#include <stdio.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
exit(1);
}
else if(0 == pid)
{
printf("child:[%d]\n", getpid());
sleep(20);
exit(EXIT_SUCCESS);
}
else //父进程先运行结束,子进程被1号进程收养
{
printf("parent:[%d]\n", getpid());
sleep(5);
}
return 0;
}
进程创建:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 10;
int main()
{
pid_t pid = fork(); //创建子进程
if(pid < 0)
{
perror("fork");
return 0;
}
else if(pid > 0){ //父进程
printf("parent: id = %d: %d : %p\n", getpid(), g_val,&g_val);
}
else{ //子进程
printf("child: id = %d: %d : %p\n", getpid(), g_val,&g_val);
}
sleep(1);
return 0;
}
子进程继承父进程的那些东西?
1.地址空间2.进程上下文(除PCB外的)
3.进程堆栈 写时拷贝
4.内存信息
5.文件描述符 --- 父进程打开的文件也要继承
6.信号设置情况
7.父进程的调度优先级 缺省为0 , -20 到 19 nice renice修正
8.当前路径
9.根路径
10.控制终端
11.进程组 --- 父进程和子进程缺省为同一进程组
12.资源限制
哪些不继承?
父进程的锁子进程不继承
父进程ID子进程不继承
父进程未决的信号,子进程清除掉
fork的写时拷贝
Linux的fork通过写时拷贝实现,即内核并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间,只是在需要写入的时候才会复制地址空间,从而使父子进程拥有各自独立的地址空间。(即资源的复制在需要写入时才发生,否则以只读的方式共享)。
父子进程的执行顺序:
一般来说,子进程和父进程到底谁先执行取决于内核所使用的调度算法
vfork():尽量不要使用
1、不进行空间拷贝(写时都不拷贝),子进程pcb直接指向父进程pcb指向的位置
2、父进程一定是等到子进程执行完毕以后在执行
3、必须调用exit 或exec系列的函数,否则就会出现段错误
4、任何一个系统上实现的vfork或多过少的都有问题,尽量 不要使用
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid = vfork();
if(pid < 0)
{
perror("vfork");
exit(1);
}
else if(pid == 0 )
{
sleep(2);
printf("i am child!\n");
exit(0);
}
else
{
printf("i am father!\n");
}
return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 10;
int main()
{
pid_t pid = vfork();
if(pid < 0){
perror("vfork");
exit(1);
}
else if(pid == 0)
{
g_val = 20;
printf("child[%d] : g_val = %d: %p \n", getpid(), g_val, &g_val);
exit(0);
}
else{
printf("parent[%d] : g_val = %d: %p \n", getpid(), g_val, &g_val);
}
sleep(1);
return 0;
}
vfork()注意:
子进程先执行,不能return返回,return是在堆栈上进行操作,return返回相当于压栈一次,出栈两次,即子进程释放了main的栈,父进程再去释放就形成了段错误。
总结:fork和vfork的区别
1.调用fork后, 父子进程运行顺序不定,而vfork子进程一定先运行。
2.fork存在写时拷贝的机制,子进程拷贝父进程地址空间。 vfork子进程(在调用exec(进程替换)或者exit之前)共享父进程的地址空间(一改都改)。
3.vfork必须调用exit返回,否则会引发段错误。
fork()练习:
1.创建出三个进程,关系是父亲,兄弟进程。
2.创建出三个进程,关系是父亲,儿子,孙子
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
//if(pid > 0 ){ //创建三个进程, 一个父进程,两个兄弟进程
// fork();
// printf("mypid = %d, myppid = %d\n", getpid(), getppid());
//}
if(pid == 0 ){ //创建三个进程,一个父进程, 一个子进程,一个孙子进程
fork();
printf("mypid = %d, myppid = %d\n", getpid(), getppid());
}
return 0;
}
3.
#include <stdio.h>
#include <unistd.h>
int main()
{
// printf("printf"); //没有刷新缓冲区会打印两次
// fork();
// printf("printf\n"); //刷新缓冲区,打印一次
// fork();
fork();
fork();
fork();
printf("1"); //8个1,一生二,二生四, 四生八
return 0;
}
4.
int main()
{
int i = 0;
for (; i < 2; i++)
{
fork();
printf("$"); //这里打印8个
//flush(stdout); //这里对缓冲区进行刷新,应该打印6个
}
return 0;
}