进程地址空间的收尾
task_struct有一个结构体成员叫mm_struct,也就是进程地址空间。
为什么要有进程地址空间:进程内存地址管理,保护物理内存,进行权限审查,从无序变有序,让我们从统一的视角看待进程代码和数据。
mm_struct里面有这些东西。
这些起始和结束划分了很多区域。也就是我们用户区的那3个G的空间:
vm_area_struct里面有自己的成员
其中vm_mm是一个指向自己的指针。
start和end指定了一段范围。
也就是内核区那1个G的空间:
进程控制
fork()创建进程
fork有返回值,返回值的本质就是写入。
所以父进程要创建一个变量 pid id=fork()。
返回时就是向该变量进行写入。
子进程和父进程同享这一个变量,为了子进程修改该变量时不影响父进程,因此就有了写实拷贝。
因此父子进程的该变量值不同,但是逻辑地址相同。
这在上文已经讲过,过了。
创建子进程之后会做什么事情
写实拷贝
数据区本身是可读写的,但是在fork()创建完子进程之后变为只读了。
原因
子进程要写入就要产生写实拷贝,写实拷贝就要重新开辟一块空间,然后进行拷贝,修改页表。
这些工作都要操作系统来做。但是操作系统要怎么介入进来呢?
此时子进程正在写入,操作系统会先在要创建子进程时把父进程的数据区变为只读权限,这时候创建完子进程之后复制父进程的代码区,子进程也为只读权限。
这个工作是操作系统完成的,用户是看不见的,也就是用户是不知道的,那么用户就有修改子进程的可能。
因为页表此刻是只读权限,用户写入就会因为页表转换而报错。
操作系统此时就会过来,因为有人对只读数据区写入,操作系统此刻会介入,因为父子进程共享一块空间,所以子进程的修改就会影响父进程,为了不影响父进程,操作系统会给子进程重新开一块空间。
页表转换出错的两种可能
第一种就是写入数据,正好地址落在了code starth ~code end之间,也就是代码区,这些区本身就只能读,这时候非要写就会报错。
第二种情况就是地址落在了数据区,这些区平时本身就有读写权限,此刻却只有读权限,这时候用户写入不是错误,而是操作系统为了介入的一种策略机制。