do_brk()函数分析

系统调用brk()的作用是扩展进程的“堆”,在其实现代码里,最后会调到do_brk()函数来完成,do_brk()函数在载入elf文件时也会调用。

do_brk()函数的声明如下:

[c]

unsigned long do_brk(unsigned long addr, unsigned long len)
[/c]

就像在上面的注释中所提到的,do_brk()是一个简化版的do_mmap(),因为在这里只需要考虑匿名映射,与文件无关。do_brk()有两个参数,addr是要扩展到的目标区域的开始地址,len是目标区域的长度。

与mm/mmap.c文件中其它大多数函数的一样,do_brk()函数所做的是处理vm area,目标是在这个函数完成的时候,在进程空间中有一个匿名vm area能映射到[addr, addr len)这段区域。下面是主要代码的分析:

[c]
error = get_unmapped_area(NULL, addr, len, 0, MAP_FIXED);
if (error & ~PAGE_MASK)
return error;
[/c]

调用get_unmapped_area()函数在当前进程的用户空间中获得一个未映射区间的起始地址。PAGE_MASK的值为0xFFFFF000,因此,如果 (error & ~PAGE_MASK)为非0,说明addr最低12位非0,addr就不是一个有效的地址,就以这个地址作为返回值;否则,addr就是一个有效的地址(最低12位为0)

[c]
vma = find_vma_prepare(mm, addr, &prev, &rb_link, &rb_parent);
if (vma && vma->vm_start < addr len) {
if (do_munmap(mm, addr, len))
return -ENOMEM;
goto munmap_back;
}
[/c]

调用find_vma_prepare()查找是不是已经存在某个vma覆盖了地址addr,如果是的话,这个已经存在的vma可能只是覆盖了部分[addr, addr len)区域,也可能覆盖了整个区域。这时就需要调用do_munmap()来把被覆盖地部分清除。已有的vma可能会被切分,与[addr, addr len)区域有覆盖关系的部分会被清除,在此以外的部分将被保留。

至此,已经可以保证,区域[addr, addr len)是空白了,没有任何vma覆盖这一区域。

接下来,首先尝试看能不能把前面的一个vma做一个扩展,扩展到addr len处,这是通过vma_merge()来进行的(vma_merge()中会通过调用vma_adjust()来调整前一个vma的大小):

[c]
    
vma = vma_merge(mm, prev, addr, addr len, flags,
NULL, NULL, pgoff, NULL);
if (vma)
goto out;

[/c]

如果没有成功的话,那么就只能新建一个vma,并把它连接到所有vma的链表和红黑树中去:

[c]
  
vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
if (!vma) {
vm_unacct_memory(len >> PAGE_SHIFT);
return -ENOMEM;
}

INIT_LIST_HEAD(&vma->anon_vma_chain);
vma->vm_mm = mm;
vma->vm_start = addr;
vma->vm_end = addr len;
vma->vm_pgoff = pgoff;
vma->vm_flags = flags;
vma->vm_page_prot = vm_get_page_prot(flags);
vma_link(mm, vma, prev, rb_link, rb_parent);

[/c]

到这里,代码的主要部分已经结束,区域[addr, addr len)已经被映射到了某一个vma中。

这里顺便再接着上一篇讲elf_load_binary()函数的话题说一下在那里对do_brk()的调用。代码如下:

[c]
elf_load_binary():
......

retval = set_brk(elf_bss, elf_brk);
......
static int set_brk(unsigned long start, unsigned long end):
......
addr = do_brk(start, end - start);
......
[/c]

这一处调用发生的场景,是ELF文件中的所有可装载段都已经解析完,进程中各区域(代码区、数据区、BSS区,堆区)的地址都已经计算完成。elf_bss和elf_brk就分别是BSS和堆的起始地址。而因为BSS与堆是前后相连的,所以它们之间的距离正好是BSS区的大小。do_brk()的两个参数正是BSS区的起始地址和长度,原来这里是在用do_brk()来映射BSS区。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以的,你可以在 `TIM1_BRK_IRQHandler` 中添加定时器中断处理函数。需要注意的是,你需要在 `main` 函数中先对定时器进行初始化和启动,同时将中断使能。具体代码如下: 首先,在 `main.c` 中初始化定时器,并启动定时器和定时器中断: ``` TIM_HandleTypeDef htim1; uint32_t timerCount = 0; int main(void) { // ... // 初始化定时器 htim1.Instance = TIM1; htim1.Init.Prescaler = 999; htim1.Init.CounterMode = TIM_COUNTERMODE_UP; htim1.Init.Period = 7199; htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; if (HAL_TIM_Base_Init(&htim1) != HAL_OK) { Error_Handler(); } // 启动定时器 if (HAL_TIM_Base_Start_IT(&htim1) != HAL_OK) { Error_Handler(); } // 使能定时器中断 HAL_NVIC_EnableIRQ(TIM1_BRK_IRQn); // ... } ``` 然后在 `TIM1_BRK_IRQHandler` 中添加定时器中断处理函数: ``` void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim == &htim1) { // 判断是哪个定时器的中断 timerCount++; // 定时器计数器加1 if (timerCount == 10) { // 如果定时器计数器为10,即1秒 // 执行需要的操作 // ... timerCount = 0; // 将计数器清零 } } } void TIM1_BRK_IRQHandler(void) { /* USER CODE BEGIN TIM1_BRK_IRQn 0 */ /* USER CODE END TIM1_BRK_IRQn 0 */ HAL_TIM_IRQHandler(&htim1); /* USER CODE BEGIN TIM1_BRK_IRQn 1 */ HAL_TIM_PeriodElapsedCallback(&htim1); // 定时器中断处理函数 /* USER CODE END TIM1_BRK_IRQn 1 */ } ``` 在上述代码中,定时器的频率为72MHz/1000=72kHz,即每个计数器单位为1/72kHz秒。定时器的周期为7199,因此定时器周期为7199*(1/72kHz)=0.1秒,即每0.1秒进入一次定时器中断处理函数。在中断处理函数中,定时器计数器加1,当计数器为10时,即1秒时,执行需要的操作,然后将计数器清零。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值