2021-03-25

fork函数:
fork函数的作用是从调用进程中创建一个新的进程,新的进程相当于是调用进程的副本,称为子进程,而调用进程称为父进程。
本节主要讲解父子进程之间的联系和区别。
函数原型:

#include <unistd.h>
pid_t fork(void);
1
2
返回值:

在父进程中,fork返回新创建子进程的进程ID。
在子进程中,fork返回0。
如果出现错误,fork返回一个负值。
fork调用为什么会返回两次呢?
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,这也是fork难以理解的点。
由于子进程复制了父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。因此fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的。

fork函数的作用:
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程。子进程是父进程的副本,子进程获得父进程数据空间、栈和堆的副本,这是子进程所拥有的副本,但是父子进程并不共享这些存储空间部分。

接下来看看示例例子,看看实验结果是不是如上所说的。
示例程序:

/* fork.c*/

#include <unistd.h>
#include <stdio.h>
#include<stdlib.h>

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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
以上程序fork之后,父进程相当于执行如下代码:

#include <unistd.h>
#include <stdio.h>
#include<stdlib.h>
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
子进程相当于执行如下代码:

#include <unistd.h>
#include <stdio.h>
#include<stdlib.h>
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
实验结果:

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
1
2
3
4
5
6
7
以上例子可以看出,子进程和父进程的count变量是不共享的,是各自存储空间的,从打印出来的count变量的值不相同可以证明这一点。
下面再来看一个例子,可以初步证明子进程是父进程的副本。

/* fork2.c*/

#include <unistd.h>
#include <stdio.h>
#include<stdlib.h>

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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
实验结果:

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
1
2
3
4
5
6
7
从实验结果可以看出,fork2.c和fork.c效果是一致的,证明了fork.c中父进程printf代码会被子进程拷贝一个副本。一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的,这取决于内核所使用的调度算法。所以,这里我们让父进程sleep再打印log,是为了让子进程先执行打印log,方便我们对结果的分析。
还有一个重要的点是,子进程和父进程共享同一个文件偏移量。
下面用实例程序来测试文件偏移量是否共享。

/* open_fork.c*/

#include <unistd.h>
#include <stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#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;  

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
实验结果:

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
1
2
3
4
5
6
7
8
9
从上面的结果中,我们看到,子进程向文件file_fork写入字符串“11111”,然后父进程向file_fork向文件写入字符串“22222”,可以看到字符串“22222”是追加到字符串“11111”后面的,说明父子进程文件描述符、文件偏移量是有被共享的。
(以下内容参考《unix环境高级编程》)
这种共享文件的方式使父子进程对同一文件使用了一个文件偏移量,如果父子进程都向标准输出进行写操作,如果父进程的标准输出已经重定向,那么子进程写到标准输出时,它将更新与父进程共享的该文件的偏移量。比如当父进程等待子进程时,子进程写到标准输出,而在子进程终止后,父进程也写到标准输出,并且其输出会添加在子进程所写数据之后,如果父子进程不共享同一文件偏移量,这种形式的交互很难实现。除了打开文件之外,父进程的很多其他属性也由子进程继承,包括:

实际用户ID,实际组ID,有效用户ID,有效组ID。
附加组ID。
进程组ID。
session ID。
控制终端。
设置用户ID标志和设置组ID标志。
当前工作目录。
根目录。
文件模式创建屏蔽字。
信号屏蔽和安排。
针对任意开打文件描述符的在执行时关闭(close-on-exec)标志。
环境。
连接的共享存储段。
资源映射。
资源限制。
父子进程的区别是:

fork返回值。
进程ID不同。
两个进程具有不同的父进程ID。
子进程的tms_utime,tms_stime,tms_cutime以及tms_ustime均设置为0.
父进程设置的文件锁不会被子进程继承。
子进程的未处理alarm被清除。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值