Linux地址空间
先来看一个现象:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int g_val = 0;int main()
{pid_t id = fork();if(id < 0){perror("fork");return 0;}else if(id == 0){ g_val=1;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;
}
输出:
很神奇,它们俩的g_val竟然是一样的地址,但是值不同,这是因为这里显示的是虚拟地址。
task_struct创建的时候会有一个地址空间,地址空间本质是内核中的一个结构体对象:
为了把虚拟地址转换为物理地址,会创建一个页表,我们所看到的是虚拟地址,页表根据虚拟地址找到物理地址(CPU中MMU工作)。
子进程创建的时候会把父进程的地址空间拷贝一份,因此子页表也是来自父进程的(浅拷贝),子进程想要更改父进程的数据,会对父进程造成影响,因此操作系统会重新开辟一块空间,拷贝这个要更改的数据,然后进行修改,子进程的页表映射的物理地址也更改成新的地址(写时拷贝,通过调整拷贝的时间顺序,达到节省空间的目的)。
子进程只有父进程页表中读权限,没有写权限。
如果父子进程不改变全局变量,那么会共享同一块空间,不会造成数据分离。
地址空间
地址空间本质是一个内核的struct结构体,内部有很多属性(mm_struct),这个结构体成员变量规定堆栈等区域的起始地址和终止地址。
当进程需要空间的时候会申请,便于减少浪费;页表里数据由可执行程序加载来的(平坦模式)。
1.进程看待物理内存有序
2.进程管理块和内存管理模块进行解耦合(中间层)
3.便于拦截非法请求,保护物理内存,比如越界,就是映射关系不存在
Linux内核进程调度O(1)轮转调度算法
每一个CPU都有一个一个runqueue队列用来进程调度:
其中,queue存放的就是进程的task_struct,但是只放在[100,139]刚好40个区域。在润queue中还有一个bitmap[5]位图,用来映射140个区域是否存在队列,1表示存在,0 表示不存在,操作系统根据对应的值来判断是否要执行。
进程调度O(1)算法:
每一个runqueue都有两个这样的结构体,一个是活跃进程,由*active指向;另一个是过期进程,由*expired指向。其中,活跃进程只出不进,过期进程只进不出,当活跃进程为空的时候,两指针指向交换。
当有新的进程时,会插入过期进程队列中,如果由进程优先级更改,当它的时间片执行完后会把它插入到过期队列,等待活跃进程清空,指针交换后,原先的过期进程队列就变成活跃进程队列了。