MITJOS Lab2中关于boot_alloc初始化问题引发的memset问题

BSS段中end变量引发的惨案

问题的发现是本人在进行lab3实验时,有memset失败而进一步探索的结果,如果你的lab2成功pass,不代表不会存在以下问题,另外,与你的狗屎运也有点儿关系。
背景:lab3中的env.c文件已经填完了,发现boot_alloc存在问题,以下代码展示均在branch lab3下进行。

上案发现场:

// This simple physical memory allocator is used only while JOS is setting
// up its virtual memory system.  page_alloc() is the real allocator.
//
// If n>0, allocates enough pages of contiguous physical memory to hold 'n'
// bytes.  Doesn't initialize the memory.  Returns a kernel virtual address.
//
// If n==0, returns the address of the next free page without allocating
// anything.
//
// If we're out of memory, boot_alloc should panic.
// This function may ONLY be used during initialization,
// before the page_free_list list has been set up.
static void *
boot_alloc(uint32_t n)
{
	static char *nextfree;	// virtual address of next byte of free memory
	char *result;

	// Initialize nextfree if this is the first time.
	// 'end' is a magic symbol automatically generated by the linker,
	// which points to the end of the kernel's bss segment:
	// the first virtual address that the linker did *not* assign
	// to any kernel code or global variables.
	if (!nextfree) {
		extern char end[];
		nextfree = ROUNDUP((char *) end, PGSIZE);
	}

	// Allocate a chunk large enough to hold 'n' bytes, then update
	// nextfree.  Make sure nextfree is kept aligned
	// to a multiple of PGSIZE.
	//
	// LAB 2: Your code here.
	result = nextfree;
	// be kept aligned to a multiple of PGSIZE
	nextfree = ROUNDUP(nextfree + n, PGSIZE);
	if ((unsigned)nextfree - KERNBASE > (npages * PGSIZE)) // npages * PGSIZE = totalmem
	{	// since npages count from KERNBASE then when compare it should use KERNBASE
		panic("out of memory in boot_alloc\n");
	}

	return result;
}

命案现场并不是让我们自己填写的部分,而是系统默认给出的这一部分:

	// Initialize nextfree if this is the first time.
	// 'end' is a magic symbol automatically generated by the linker,
	// which points to the end of the kernel's bss segment:
	// the first virtual address that the linker did *not* assign
	// to any kernel code or global variables.
	if (!nextfree) {
		extern char end[];
		nextfree = ROUNDUP((char *) end, PGSIZE);
	}

首先分析它这一部分注释的问题,它说end变量是bss段中对后一个变量,如果它说的是正确的,那么我们可以使用end作为bss段的末尾,同时在roundup的作用下可以保证nextfree是绝对在bss段后面,而且是可以对4K取整的。
但是问题是,它说的“end是bss段的末尾”是不是正确的呢?那就瞅一瞅。
首先,查看下ELF中对各个段的定义

objdump -h obj/kern/kernel

在这里插入图片描述
上面是我的结果,先不用管具体的虚拟地址(我的是在lab3下,可能因人而异),看到bss段的虚拟地址是0xf0182100,段的大小为0xf14,那么计算下可知bss的范围为[0xf0182100, 0xf0183014) ,之后呢,我们使用gdb看一下,这个end的具体地址:
在mem_init中,第一次调用boot_alloc是在开辟页目录内存的位置,也就是下面的

	kern_pgdir = (pde_t *) boot_alloc(PGSIZE);

在这里插入图片描述
我们使用gdb跳转到上面代码的位置,然后进入boot_alloc的体内,再调到if判断的地方,si进入,可以明显的看到,end的地址是0xf0183000。
在这里插入图片描述
这时候有人会觉得,哎,这不是很正常吗,end是一个数组,所以后面还有20个元素?那继续往后看:
通过查阅代码我们可以知道,kern_pgdir也是一个全局变量,而且也没有进行初始化,那么他一定是在bss段中的。
在第一次boot_alloc处作一下更改:

	//
	// create initial page directory. !!!!!!!!!!!!!
	kern_pgdir = (pde_t *) boot_alloc(PGSIZE);
	cprintf("%x\n", &kern_pgdir);
	memset(kern_pgdir, 0, PGSIZE);

打印下信息
看结果:
在这里插入图片描述
没错,你没有看错,kern_pgdir所在的内存地址为0xf018300c。进一步验证下,使用以下的命令来查看全部表头信息:(这里输出到文件,好定位)

objdump -x obj/kern/kernel  > ~/work/test

在这里插入图片描述
除了kern_pgdir外,还有好多变量都是在end之后的,比如panicstr、npages。
然后说一下,这样就会导致一个什么样的问题:
以我遇到的问题为例,我的end所在的地址为0xf0183000,因此我通过

		nextfree = ROUNDUP((char *) end, PGSIZE);

获取到的result(也就是kern_pgdir)的值为0xf0183000(jtm离谱),也就是说kern_pgdir(0xf018300c)所指向的地址为0xf0183000,在之后的memset中,显然,会将期自身清空,导致,memset运行完后,kern_pgdir自身的值为0了,导致了后面一系列的错误。
进一步分析可知,原文中说的magic,其实一点儿都不magic,而是存在一定的问题,但是我觉得这个可能也跟编译器的编译顺序有关吧,就没有再深入的研究下,如果kern_pgdir等变量的存放地址在end之前的话就不会存在这个问题,或者说,如果你的end经过roundup之后,与nextfree之间有很大的gap,足以保存下其余的变量的话,也不会出现问题,当然这与你的运气有关。
明白了之后,直接上简单粗暴的解决方案:

// Initialize nextfree if this is the first time.
	// 'end' is a magic symbol automatically generated by the linker,
	// which points to the end of the kernel's bss segment:
	// the first virtual address that the linker did *not* assign
	// to any kernel code or global variables.
	if (!nextfree) {
		extern char end[];
		nextfree = ROUNDUP((char *) end, PGSIZE) + PGSIZE;
	}

即nextfree在初始化的时候加上个PGSIZE,虽然这样不会根治这个问题,当end后面的变量继续边多,直到后面变量占用的内存达到了4K,那么这里还是会出错。但是就目前而言,是足够了吧。如果有更好的解决方案欢迎在评论取留言,希望我的经验能够对看到这里的你有一点帮助。


如有错误,欢迎指正。

### 关于 `vb2_dc_alloc` 的使用 #### 函数概述 `vb2_dc_alloc` 是 Video Buffer Two (VB2) API 中的一个重要函数,用于分配一个视频缓冲区描述符对象。此函数主要用于初始化配置视频设备中的缓冲管理机制[^1]。 #### 参数列表 - **struct device \*dev**: 设备指针,指向要为其分配缓冲区的硬件设备。 - **unsigned int num_buffers**: 请求分配的缓冲区数量。 - **enum v4l2_memory memory_type**: 缓存内存类型,定义了如何映射物理缓存到用户空间。 ```c struct vb2_queue *vb2_dc_alloc(struct device *dev, unsigned int num_buffers, enum v4l2_memory memory_type); ``` #### 返回值 成功时返回新创建并已初始化好的队列结构体指针;失败则返回错误码(负数)。通常情况下,应该检查返回的结果以确保操作的成功执行。 #### 使用场景 当开发基于 Linux V4L2 框架下的多媒体应用或驱动程序时,可能会频繁调用 `vb2_dc_alloc` 来设置好相应的资源池以便后续处理图像数据流的任务。特别是在 Xilinx 平台上的 video_device 初始化过程中会涉及到此类功能模块的设定。 #### 错误排查提示 对于内核启动期间遇到的问题,比如卡在 "Starting kernel..." 这种情况,可以通过查看由 printk 输出至 __log_buf 日志缓冲区内保存的消息来进行调试分析。这些日志可以帮助识别潜在的原因所在,并提供有关系统状态的关键线索[^2]。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值