00_前言
现在只剩下copyin、copyout、copyinstr
这三个函数了
01_copyin
函数
1.函数的作用
这个函数负责从给定的用户页表pagetable的虚拟地址srcva处拷贝长度为len字节的数据到地址dst处。
为什么内核目的地址dst使用指针来表示,而用户态的地址却使用uint64 srcva来代替?
- 内核中的指针(如
dst
)指向的是内核地址空间中的内存,可以直接通过MMU自动转换为物理地址。 - 而用户态的虚拟地址(如
srcva
)不能直接使用,因为内核态与用户态的地址空间不同。 - 为了读取用户态的数据,内核需要通过软件逻辑(如
walkaddr
)手动完成虚拟地址到物理地址的映射.
2.代码
// Copy from user to kernel.
// Copy len bytes to dst from virtual address srcva in a given page table.
// Return 0 on success, -1 on error.
// 译:从用户空间向内核空间拷贝数据
// 从页表pagetable的虚拟地址srcva中拷贝len长度的数据到dst
// 成功时返回0,出错时返回-1
int
copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len)
{
uint64 n, va0, pa0;
// 总共要复制len个字符之后再跳出循环
while(len > 0){
// 使用PGROUNDDOWN宏来找到srcva对应页的起始虚拟地址
va0 = PGROUNDDOWN(srcva);
// 找到va0对应的实际物理地址
pa0 = walkaddr(pagetable, va0);
// pa0 == 0表示walkaddr函数出错,返回-1
if(pa0 == 0)
return -1;
// 注意:这里的n是在本次迭代中要复制的字节数
// 考虑两种非对齐的情形:
// 1.srcva起始地址如果不是页对齐的,n的大小恰好等于第一个页面要复制的剩余的字节数量
// 在复制完第一个页面之后,页面会强制对齐,假设剩余len > PGSIZE
// 那么此时会有n = PGSIZE - (srcva - va0) = PGSIZE,这会复制一整个页面
n = PGSIZE - (srcva - va0);
// 这是为了收尾处理,防止复制多余数据
// 2.此时if(n > len)的判断可以保证不复制多余的数据
if(n > len)
n = len;
// 调用memmove完成本次复制
memmove(dst, (void *)(pa0 + (srcva - va0)), n);
// 更新len、dst、srcva
len -= n;
dst += n;
// 注意这里的对srcva的更新,无论一开始srcva是否是页对齐的
// 经过这里的更新后一定会是页对齐的,因为va是圆整后的页面起始地址,细细品味一下...
srcva = va0 + PGSIZE;
}
return 0;
}
02_copyinstr
函数
1.函数的作用
copyinstr
和copyin
函数作用类似,但是不同的是:copyinstr
复制的对象是字符串,所以复制结束的标志是遇到空字符(null-terminated)
。
2.代码
// Copy a null-terminated string from user to kernel.
// Copy bytes to dst from virtual address srcva in a given page table,
// until a '\0', or max.
// Return 0 on success, -1 on error.
// 译:从用户空间拷贝一个以空字符结尾的字符串到内核空间
// 从页表pagetable的srcva地址处以字节为单位拷贝数据到dst
// 直到遇见空字符或达到最大数量
// 成功时返回0,失败时返回-1
int
copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max)
{
uint64 n, va0, pa0;
// 是否遇到空字符的标志位,为0表示没有遇到
int got_null = 0;
// 如果没有遇到空字符或尚未达到最大字符限制
while(got_null == 0 && max > 0){
// 找出srcva对应页的起始物理地址
va0 = PGROUNDDOWN(srcva);
pa0 = walkaddr(pagetable, va0);
// 如果查询的物理地址为0,表示出错,返回-1
if(pa0 == 0)
return -1;
// 这里的逻辑和copyin一样,不再赘述
// n表示的是本次迭代要复制的字节数
n = PGSIZE - (srcva - va0);
if(n > max)
n = max;
// 得到本次复制源字符串起始的物理地址
char *p = (char *) (pa0 + (srcva - va0));
// 完成n个字节的复制
while(n > 0){
// 如果已经遇到了空字符
if(*p == '\0'){
// 则给dst处也放置一个空字符
// got_null标志位置为1,表示已经遇到空字符,并跳出循环
*dst = '\0';
got_null = 1;
break;
// 如果没有遇到空字符,则拷贝当前字符
} else {
*dst = *p;
}
// 更新循环变量
--n;
--max;
p++;
dst++;
}
// 强制对齐srcva到下一个页面开始位置
srcva = va0 + PGSIZE;
}
// 如果是因为遇到空字符而结束复制,则返回0表示正常
// 如果是因为超过了最大允许复制的字节数量,则返回-1表示出错
if(got_null){
return 0;
} else {
return -1;
}
}
03_copyout
函数
1.函数的作用
copyout
函数的主要作用是在内核态代码中,将数据从内核空间复制到用户进程的虚拟地址空间中。它会处理跨页的情况,并确保数据能够正确地写入用户空间的目标地址。
2.代码
// Copy from kernel to user.
// Copy len bytes from src to virtual address dstva in a given page table.
// Return 0 on success, -1 on error.
// 译:从内核空间向用户空间拷贝数据
// 从src拷贝len长度的字节到pagetable页表的dstva位置处
// 成功时返回0,错误时返回-1
int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{
uint64 n, va0, pa0;
// 当字符还没有复制完毕时
while(len > 0){
// 找到对应当前虚拟地址dstva的物理地址pa0
va0 = PGROUNDDOWN(dstva);
pa0 = walkaddr(pagetable, va0);
// 如果pa0为0,表示查找过程出错,返回-1
if(pa0 == 0)
return -1;
// 这里的逻辑和copyin一样,处理非对齐的情况和防止复制超出范围
// n表示的是当前循环可以复制的字节数量
n = PGSIZE - (dstva - va0);
if(n > len)
n = len;
memmove((void *)(pa0 + (dstva - va0)), src, n);
// 更新循环变量
// 强制对齐dstva,和copyin对齐srcva的做法一致,不再赘述
len -= n;
src += n;
dstva = va0 + PGSIZE;
}
return 0;
}
Comments | NOTHING