进程创建:
解释一下fork函数:
在Linux中fork函数是很重要的函数,它从已经存在的进程中创建一个新进程,新进程称为子进程,原进程称为父进程。
#include<unistd.h>
pid_t fork(void); 返回值:子进程返回0,父进程返回子进程的pid,出错返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核的做法如下:
1.分配新的内存块和内核数据结构给子进程
2.将父进程部分数据结构内容拷贝至子进程
3.添加子进程到系统进程列表中
4.fork返回,调度器调度
当一个进程调用fork后,就会有两个二进制码相同的进程,而且他们运行到相同的地方,但是每个进程开始自己的旅程
例子:
#include<unistd.h>
#include<stdio.h>
int main()
{
pid_t pid;
printf("befor : pid is %d\n",getpid());
pid=fork();
printf("After:pid is %d, fork return %d\n", getpid(), pid);
sleep(1);
return 0;
}
运行结果如上, 这里输出了三行,before打印了一行,after打印了两行
由上面的例子可以看出,fork之前父进程独立执行,fork之后,父子进程分别执行
注意:fork之后谁先执行由调度器决定
fork函数返回值:子进程返回0;父进程返回的是子进程的pid
父子进程代码共享,数据各自开辟空间,私有(写时拷贝)
进程虚拟地址空间:
看一段代码
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int g_val=100;
int main()
{
pid_t id=fork();
if(id<0)
{
perror("fork");
return 0;
}
else if(id==0){
printf("child[%d]:%d:%p\n",getpid(),g_val,&g_val);}
else{
printf("parent[%d]:%d:%p\n",getpid(),g_val,&g_val);}
sleep(1);
return 0;
}
我们发现输入的变量和地址一样,因为子进程按照父进程为模板,父子并没有对变量进行任何修改
接下来在看一段代码,你可能会迷惑
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int g_val=100;
int main()
{
pid_t id=fork();
if(id<0)
{
perror("fork");
return 0;
}
else if(id==0){
g_val=200;
printf("child[%d]:%d:%p\n",getpid(),g_val,&g_val);}
else{
sleep(3);
printf("parent[%d]:%d:%p\n",getpid(),g_val,&g_val);}
sleep(1);
return 0;
}
这里在子进程中改变了 g_val的值,按理来说,数据改了,他两的地址不可能一样,因为一块地址上只能放一个数据
但是看一下运行结果:
我们发现父子进程的输出地址是一样的,变量内容不一样,得出以下结论:
1.变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
2.地址是一样的,说明该地址绝对不是物理地址
3.在linux中,这种地址叫做虚拟地址
4.我们在用c、c++语言所看到的地址都是虚拟地址,物理地址用户一概看不到,由OS统一管理
OS负责将虚拟地址转化为物理地址
创建一个进程时,操作系统会为该进程分配一个4GB大小的虚拟进程地址空间,之所以是4GB,是因为
在32位操作系统中,一个指针长度是4字节,而4字节的寻址能力是从0x00000000--0xFFFFFFFF
而0xFFFFFFFF表示的即为4GB大小的容量
2.每个进程只能访问自己虚拟地址空间的数据,无法访问别的进程中的数据,通过这种方法实现进程间的地址隔离
上面的图足以说明问题,同一个变量,地址相同,其实就是虚拟地址相同,内容不同其实是被映射到
不同物理地址
注意:1.进程的数据和代码在物理内存上 2.页表的存在是为了保护物理内存