一、fork函数
在Linux中,fork函数具有非常重要的作用;它可以在原有进程(父进程)中创建一个新进程(子进程)。
fork函数所在的头文件即其返回值类型(上图)。
父进程的返回值为子进程id,子进程的返回值为0。
若进程出错则返回-1。
1.使用fork建立子进程
代码如下(示例):
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4
5 int main()
6 {
7 pid_t id=fork();
8 if(id==0){
9 printf("I am child\n");
10 sleep(3);
11 exit(0);
12
13 }
14 else{
15 printf("I am father\n");
16 sleep(4);
17 exit(0);
18 }
19
20 return 0;
21 }
运行结果(下图)
2.fork后
不同进程中的同一变量的虚拟地址
fork创建子进程后,对于同一个变量test,父子进程在运行时有何不同,如果对父进程的test进行修改,子进程的test会改变吗?
看下面代码
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4
5 int main()
6 {
7 int test=666;
8
9 pid_t id=fork();
10 if(id==0){
11 printf("I am child,test is %d,test_id:%p\n",test,&test);
12 sleep(3);
13 exit(0);
14
15 }
16 else{
17 test=999;
18 printf("I am father,test is %d,test_id:%p\n",test,&test);
19 sleep(3);
20 exit(0);
21 }
22
23 return 0;
24 }
其运行结果如下图
我们可以看到对父进程的test修改后,子进程的test并未改变,这是因为进程具有独立性。
但是为什么父子进程中变量test的地址相同呢?在同一个地址中为什么会有两个不同的值?
首先我们知道地址分为物理地址和虚拟地址,虚拟地址通过页表转换为物理地址。而我们所打印出来的地址属于虚拟地址,而两个test的值不同说明test所在的物理地址不同(说明不同进程的同一虚拟地址可以指向不同的物理地址)。
写时拷贝
写时拷贝是指在刚刚创建子进程时,父子进程的代码和数据都是共享的,如果子进程仅仅只有对数据的读操作那么只需要让子进程空间的指针指向原空间,只有当子进程进行“写操作”或者修改代码内容后才真正为子进程开辟新空间。
从上面我们知道,在创建子进程后,即使父子进程的内容发生改变,他们的虚拟地址也没有改变,因此写时拷贝时发生在物理层面上。
写时拷贝可以做到资源的按需分配和延时分配,更高效的使用内存空间。
二、进程替换
fork创建一个子进程后,为了让子进程执行不同的任务,我们需要让子进程调用一个exec函数来执行其他程序。
注意:调用exec函数并不是创建一个新进程,而是对原进程内容进行替换,其进程id不会变换。
返回值问题:调用exec函数后,如果调用成功,那么该函数不再返回;如果调用失败则返回-1.
man手册对exec函数的解释(下图)
l(list):参数采用列表形式
v(vector):参数采用数组形式
p(path):自动搜索环境变量PATH,不需要用户给出路径
e(env):自己维护环境变量
execl,execlp,execv,execvp调用ls -al命令
#include<stdlib.h>
#include <unistd.h>
int main()
{
char *const myargv[] = {"ls", "-la", NULL};
char *const myenvp[] = {"PATH=/bin", NULL};
//l表示参数采用列表形式
execl("/bin/ls", "ls","-l","-a" ,NULL);
//p无需写全路径
execlp("ls", "ls", "-la", NULL);
//v参数采用数组形式
execv("/bin/ls", myargv);
//p
execvp("ls", myargv);
exit(0);
}