一、fork:
在linux中fork函数从已存在进程中,创建一个新进程。新进程为子进程,而原进程为父进程。
返回值:子进程中返回0,父进程返回子进程id,出错返回-1。
1、常规用法:
一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
进程调用fork,当控制转移到内核中的fork代码后,内核执行以下内容:
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中
- fork返回,开始调度器调度
当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。
fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。
2、写时拷贝:
通常,父子代码共享,父子都不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝(重新申请空间,进行拷贝,修改页表)的方式各自一份副本。
注:
父进程创建子进程的时候首先将自己的读写权限改成只读,然后再创建子进程。
当用户对某一批数据进行写入时,页表转换会因为权限问题而出错。此时操作系统就可以介入:
1、真的出错了(如越界(尝试写入代码区等))
2、不是出错,触发:重新申请内存,拷贝内容的策略机制(写时拷贝)
二、补充:makefile文件
1 cc=gcc
2 src=test.c
3 target=mybin
4
5 $(target):$(src)
6 gcc $^ -o $@ -g #加上-g表示以debug模式编译该程序
7 .PHONY:clean
8 clean:
9 rm -f $(target)
10
三、以模拟孤儿进程为例:
(以下包含进程创建)
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4
5 //模拟孤儿进程
6 int main()
7 {
8 pid_t id = fork();
9
10 if(id < 0)
11 {
12 return 1;
13 }
14 else if(id > 0)
15 {
16 int cnt = 5;
17 while(cnt)
18 {
19 printf("这是父进程,运行时间:%d\n",cnt--);
20 sleep(1);
21
22 }
23 printf("父进程dead!:%d\n",cnt--);
24 exit(2);
25 }
26 else
27 {
28 while(1)
29 {
30 printf("这是子进程,持续运行\n");
31 sleep(1);
32 }
33
34
35 }
36
37
38 return 0;
39 }
40
四、监视:
执行以下命令:
未执行该模拟孤儿进程的程序时:
执行该模拟孤儿进程的程序:
父进程未退出时:
可以看出父、子进程皆在运行,父进程PID是15379,子进程PID是15380,子进程的PPID是15379。
父进程退出后:
可以看出,父进程已经不在运行,只有子进程在运行。此时,子进程的PPID不再是它原来父进程的 PID,而是变为了1。(这种情况可以理解为托孤)
此时如果我们不想让该进程继续运行,我们可以通过 kill -9 15380 终止该子进程。
五、创建多进程
1、myprocess.c
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#define N 10
void worker()
{
int cnt = 10;
while(cnt)
{
printf("我是子进程,pid: %d, ppid:%d ,cnt: %d\n", getpid(),getppid(), cnt);
sleep(1);
cnt--;
}
}
typedef void (*call_back_t)();
void createSubProcess(int n ,call_back_t cb)
{
int i = 0;
for(i = 0; i < n; i++)
{
sleep(1);
pid_t id = fork();
if(id == 0)
{
printf("创建子进程:%d\n", i);
cb();
exit(0);
}
}
}
int main()
{
createSubProcess(N, worker);
sleep(100);
return 0;
}
2、运行myprocess.c