alloc_proc 内核应用层,作用是分配一个进程,方法是循环所有p,然后进行操作
uvmcreate是创建一个用户页表,创建页表就是kalloc,只不过当使用kalloc的时候,我们是分配一个地址,这是一个工具使用。但是uvmcreate是kalloc的包装,特点就是将那个地址看作是页表。因此kalloc是一个底层函数,而uvmcreate则是包装,返回的是页表。
uvmalloc是一个分配PTE和物理内存以将进程从oldsz扩展到newsz的函数,它不需要页面对齐。返回的会是新的大小。方法是先kalloc一个内存块,然后对需要新增的部分进行扩展
uvmdealloc是一个包装函数,作用是取消分配用户页面以将进程大小从oldsz调整为newsz。oldsz和newsz不需要页面对齐,newsz也不需要小于oldsz。oldsz可以大于实际进程大小。最后返回的是新的进程大小
内部封装的是这个函数uvmunmap,它的作用是删除从va开始的映射的npage。va必须与页面对齐。映射必须存在。
kalloc是一个底层函数,作用是分配一个内存块,这是通过对kmem这个部分进行操作得到的,操作的部分就是end,其中end是在kernel.ld中得到的,如何得到,需要看下边的这个部分
既然是操作,就一定要对名字有对应的操作的概念,其中kernbase就是,默认的一个最小值,phystop是一个最大值,这一部分对应的是RAM
链接会将多个输入文件变成输出文件,且格式是可输入文件elf,这应该是一种规则的书写。
freerange是一个内存的调用函数,特点就是直接处理物理内存,物理内存其实就是通过range处理的。所以range代指物理地址。这是一个应用层,会对所有的物理地址进行kfree。
kfree是真正的底层函数,作用是对每个块的物理内存使用memset分配新内存
kvminit是一个对kvmmake包装的函数,目的是获得页表,因此算是个中间层(中间的是内核页表部分)
kvmmake是对kvmmap的包装,是多个kvmmap的组合。
kvmmap这是一个map映射,看到kvm想来可以想到,其实这还是一个应用层(系统),关键是下一步,也就是mappages,mappages是那个公用的工具函数。
mappages是一个映射函数,这个函数会为从va开始的虚拟地址创建PTE,这些地址指的是从pa开始的物理地址。va和大小可能不对齐。先对虚拟地址获得两个对齐的内存,然后对每个pte进行获得,然后对a和pa都加上对应值。
wakeup是将所有的睡眠的进程(在链上的)进行唤醒
push_off是一个工具函数,他的作用是,push是放,是放一个中断,意思是现在这个是不是一个中断。
pop_off是弹出一个中断
可以说push和pop是对intr的封装,封装除了汇编结果,还有很多其他的东西,比如加上一层判断,然后判断这是不是最后一层。
intr_get是获得中断的状态。
intr_off和intr_on是一对工具函数,更深层的在汇编层面开关中断
bread所有b开头的都是bio的,也就是缓冲层buffer cacah层的!
fsinit是一个文件系统的初始化,这玩意初始化是在什么时候开始的呢?答案是是在forkret的时候,也就是说,我们使用了fork,然后return了,这时候我们得到的就是一个文件系统了。
同样是b这个层级,bget比bget更加底层一些。
所有的syscalls,都会在用户层面先进行一次定义,这个定义的意义很简单,那就是告诉我们用户层面会使用的系统内核层
atoi是一个工具函数,这个工具函数是为了处理字符串的字符转数字
argint是一个工具函数,这个工具函数是为了得到int,也就是整数个参数
argint 利用用户空间的 %esp 寄存器定位第 n 个参数:%esp 指向系统调用结束后的返回地址。参数就恰好在 %esp 之上(%esp+4)。因此第 n 个参数就在 %esp+4+4*n。这是因为esp指向的就是对应的地址
argraw是最后的初始化的部分,argraw是raw,raw是寄存器的意思。最后他会得到的是一个寄存器的值。
argint 调用 argraw从用户内存地址读取值到 *ip。argraw可以简单地将这个地址直接转换成一个指针,因为用户和内核共享同一个页表,但是内核必须检验这个指针的确指向的是用户内存空间的一部分。内核已经设置好了页表来保证本进程无法访问它的私有地址以外的内存:如果一个用户尝试读或者写高于(包含)p->sz的地址,处理器会产生一个段中断,这个中断会杀死此进程,正如我们之前所见。但是现在,我们在内核态中执行,用户提供的任何地址都是有权访问的,因此必须要检查这个地址是在 p->sz 之下的。
argstr 是最后一个用于获取系统调用参数的函数。它将第 n 个系统调用参数解析为指针。它确保这个指针是一个 NUL 结尾的字符串并且整个完整的字符串都在用户地址空间中。
argaddr,参数是n和ip,他可以让ip指针指向的值赋值为an寄存器的值,这个an不是寄存器的地址,而是他的值
fetchaddr参数是addr,buf和max,作用是获取addr地址处以nul结尾的字符串
copyinstr作用是模拟mmu的流程,copyin也基本上是,是一个从用户复制到内核的函数,从srcva这个地址复制len个字节,页表是pagetable,底层调用时memmove
sysproc就是所有的系统调用。且是进程的东西
sys的系统调用就会调用真正的功能,其中sys的调用会带动真正的实现,真正的实现是在proc中的。
begin_op()是每个文件系统开始时会调用的,作用是
scheduler是一个在main时就被调用的函数,由于每个cpu都会有scheduler,所以,每次都会进行的操作是,intr这个东西就是中断,然后进行swtch
walkaddr是walk的一层封装,作用是walk(也就是步行,结果是查找)特定虚拟地址va的物理地址pa
现在仔细分析一下walk,它的底层是真正的pagetable的操作,利用PX将va变成三层的地址,然后将pte转变为pa,因为pte是我们的那三个九位,所以这就是连续的找到不同的九位对应的页表,最终进行处理
PX(level, va)是一个va的移动宏,作用是将va右移pxshift(level)
PXSHIFT(level) 是一个pgshift,也就是12加上多少个level(每个level会乘上9,因为每个pte是9位的)
pgshift表示的是地址的位移,其值是12,也就是说每一个pte的12位的字节偏移
Reg(reg) 是一个宏,它通过将reg,其中reg是一个值,而通过char* 和volatile的方法,防止了优化,同时保证了将值变为指针(地址)。
#define ReadReg(reg) (*(Reg(reg))) 第一个是将地址的值拿出来
#define WriteReg(reg, v) (*(Reg(reg)) = (v)) 第二个是将v赋值给地址的值
exec是一个函数,他的流程首先是第一步,进行begin_op,即log记录,然后,使用namei获取文件,之后对文件进行读取,作用是检查文件是否是elf的。之后将program加载到memory中。先获得进程,然后获得页表。
either_copyin是一个复制地址的函数,最终结果是调用copyin
loadseg是将程序段加载到虚拟地址va的页表中,要求va必须页面对齐,并且从va到va+sz的页面必须已经被映射
这里就可以解决另一个好玩的问题了,也就是什么是context,也就是上下文,它保存了ra,sp,以及很多其他的寄存器
、
都是写好的,这应该是真实地址(我们是用数值在os中定义了真实地址的),也可以看到确实是4096对齐的。
每次增加PGSIZE,可以看到结果是4096 = 16的三次方,所以是每次后边三个0对齐就行了。看来所有的pa都是通过操作系统定义的。
可以看到这就是我们的mem他最开始的定义,他是分配内存的,但是这个内存是由我们的linux操作系统定义的,是底层的内存系统。
我猜测是因为我们的内存条是一个硬件,而他的地址写在了bios中,这样启动的时候就能知道各个的地址了。猜测而已!