环境:
lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.3 LTS
Release: 22.04
Codename: jammy
uname -r
6.2.0-26-generic
gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/11/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: …/src/configure -v --with-pkgversion=‘Ubuntu 11.4.0-1ubuntu1~22.04’ --with-bugurl=file:///usr/share/doc/gcc-11/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-11 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --enable-cet --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-11-XeT9lY/gcc-11-11.4.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-11-XeT9lY/gcc-11-11.4.0/debian/tmp-gcn/usr --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-serialization=2
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04)
在完成exec之后,实现加载磁盘上的程序然后执行,遇到了一些bug。
在完成加载磁盘上的文件执行时,首先你需要编译源程序,然后根据源程序的大小,将其写到磁盘中,同时需要修改kernel/main.c,因为我们编译后的程序大小和书中给的程序大小不一样
gcc -Wall -c -g -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes -Wsystem-headers -I ../lib -o prog_no_arg.o prog_no_arg.c -m32
ld -e main prog_no_arg.o ../build/string.o ../build/syscall.o ../build/stdio.o ../build/assert.o -o prog_no_arg -melf_i386
dd if=command/prog_no_arg of=/home/cccmmf/mycode/os.img bs=512 count=100 seek=220 conv=notrunc
uint32_t file_size = 23120;
uint32_t sec_cnt = DIV_ROUND_UP(file_size, 512);
struct disk* sda = &channels[0].devices[0];
void* prog_buf = sys_malloc(sec_cnt * SECTOR_SIZE);
ide_read(sda, 220, prog_buf, sec_cnt);
int32_t fd = sys_open("/prog_no_arg", O_CREAT|O_RDWR);
if (fd != -1) {
if(sys_write(fd, prog_buf, file_size) == -1) {
printk("file write error!\n");
while(1);
}
}
sys_close(fd);
当我启动我们的内核然后执行prog_no_arg时遇到了Page Fault,现象如下
由于遇到了Page-Fault,接下来我们使用gdb进行debug,经过我一系列的debug,发现我们的Page Fault发生在sys_malloc中,当执行完下图的349行就会发生Page-Fault
我们使用s进入该函数
接下来我们打印elem各个指针域的值
发现elem的指针域都是0,那么在执行list_remove(elem)的时候肯定会出现Page-Fault,因为在list_remove中修改了,elem->prev->next和elem->next->prev,list_remove代码如下
在我们进行本次sys_malloc的时候,并未进入下图323行的判断之中,也就意味着free_list不为空,但是free_list是存在问题的,那也就意味着可能是上次sys_free出现了问题,接下来我们定位到上次执行sys_free的地方。
注(非常重要,必看!!!!):当我们在我们的操作系统终端中输入./prog_no_arg,我们的shell会执行execv,然后最终进入到userprog/exec.c中的sys_execv中,在sys_execv中我们调用了load函数,我们的Page Fault是发生在该load函数中的,经过我的不懈努力发现在load函数中,遍历所有程序头时,第五次执行sys_read时发生了Page Fault(第五次执行如下第一个图中的135行发生了Page Fault),在sys_read中调用了file_read(如下第二个图的425行),在file_read调用了sys_malloc(如下第三个图的430行),也就是在如下第三个图的第430行发生了Page Fault。借此我们可以往回推,既然在load函数遍历所有程序头时,第五次循环执行sys_read时发生了Page Fault,也就意味着在遍历所有程序头时,第四次循环执行segment_load函数时,在该函数内部sys_free存在问题。因为sys_lseek和memset函数不存在sys_malloc,哪里来的sys_free,再往前推,就只能是在第四次循环执行segment_load有问题了。
在我们第四次循环时,我们使用s进入segment_load函数
在segment_load函数中,第92行有sys_read,我们使用s进入该函数
在sys_read中第425行调用了file_read,我们使用s进入file_read
在file_read的430行中调用了sys_malloc(为io_buf分配内存),我们使用s进入sys_malloc,如下图所示
我们在sys_malloc中,进入了323行的判断,也就意味着free_list为空,如下图所示
接下来我们继续向下执行,当我们执行到下图354行时,打印一些值
当sys_malloc执行完后,回到了file_read,如下图所示
接下来我们使用s进入434行的sys_malloc(为all_blocks分配内存的那个sys_malloc),此次malloc也进入了323行的判断,也就意味着free_list为空
当我们执行到如下图354行时,打印一些变量
然后执行完该sys_malloc返回到file_read
我们接下来直接在函数结尾的sys_free打断点直接执行到sys_free,使用s进入该sys_free
执行到如下图482行打印一些值
一切正确,而且a->cnt和a->desc->blocks_per_arena的值想等,可以回收该页,接下来我们执行完此次sys_free回到file_read。再次使用s进入sys_free,如下图所示
在执行到如下图480行打印一些变量的值如下图所示,按道理说我们执行到如下图的480行时,a->cnt的值应为6,a->desc->block_size的值应为512,a->blocks_per_arena的值应为7,因为我们全程都没有修改过结构体a,然后在执行完a->cnt++之后,a->cnt的值变为7,和a->blocks_per_arena的值一样,然后可以正常回收整个页,可偏偏结果差强人意。
接下来我们继续debug,寻找一下到底是在哪里修改了结构体a。我们重新启动内核运行到如下440行,然后每运行一行就打印一下a->cnt
最终发现在执行完如下456行的ide_read后a->cnt的值被修改了
我们使用s进入ide_read,最终发现执行完如下176行a->cnt被修改了,我们使用s进入如下176行的sema_down
最终发现执行完32行thread_block后a->cnt被修改了,使用s进入
最终发现在执行完如下186行的schedule后a->cnt被修改
我也跟踪进入了schedule,然后切换到了其他线程,其他线程并没有sys_malloc和sys_free操作,而且进入其他线程会切换页表,没有办法继续追踪((struct arena *)0x804b000)->cnt,就是不知道为什么执行完schedule切回来之后((struct arena *)0x804b000)->cnt的值就被修改了,希望哪个大神可以解答一下。最终我选择在load函数中每次执行完segment_load都使用block_desc_init(cur->u_block_desc);函数来清空一下free_list,来解决Page Fault,但是该方法可能导致永久的内存泄漏,代码如下,只需要在while之前加一行和segment_load后加一行即可
struct task_struct *cur = running_thread();
/* 遍历所有程序头 */
uint32_t prog_idx = 0;
while (prog_idx < elf_header.e_phnum) {
memset(&prog_header, 0, prog_header_size);
/* 将文件的指针定位到程序头 */
sys_lseek(fd, prog_header_offset, SEEK_SET);
/* 只获取程序头 */
if (sys_read(fd, &prog_header, prog_header_size) != prog_header_size) {
ret = -1;
goto done;
}
/* 如果是可加载段就调用segment_load加载到内存 */
if (PT_LOAD == prog_header.p_type) {
if (!segment_load(fd, prog_header.p_offset, prog_header.p_filesz,
prog_header.p_vaddr)) {
ret = -1;
goto done;
}
block_desc_init(cur->u_block_desc);
}
/* 更新下一个程序头的偏移 */
prog_header_offset += elf_header.e_phentsize;
prog_idx++;
}
好了终于能跑了