3

我写了一个如下的简单程序并跟踪它。

#include<stdio.h>
int foo(int i)
{
    int k=9;
    if(i==10)
            return 1;
    else
            foo(++i);
    open("1",1);
}
int main()
{
    foo(1);
}

我这样做的目的是检查如何为堆栈上的函数中的变量(在本例中为 int k)分配内存。我使用了一个开放的系统调用作为标记。strace 的输出如下:

execve("./a.out", ["./a.out"], [/* 25 vars */]) = 0
brk(0)                                  = 0x8653000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or            directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =     0xb777e000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=95172, ...}) = 0
mmap2(NULL, 95172, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7766000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or     directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000\226\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1734120, ...}) = 0
mmap2(NULL, 1743580, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) =     0xb75bc000
mmap2(0xb7760000, 12288, PROT_READ|PROT_WRITE,     MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a4) = 0xb7760000
mmap2(0xb7763000, 10972, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7763000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =     0xb75bb000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb75bb900, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb7760000, 8192, PROT_READ)   = 0
mprotect(0x8049000, 4096, PROT_READ)    = 0
mprotect(0xb77a1000, 4096, PROT_READ)   = 0
munmap(0xb7766000, 95172)               = 0
open("1", O_WRONLY)                     = -1 ENOENT (No such file or     directory)
open("1", O_WRONLY)                     = -1 ENOENT (No such file or     directory)
open("1", O_WRONLY)                     = -1 ENOENT (No such file or directory)
open("1", O_WRONLY)                     = -1 ENOENT (No such file or     directory)
open("1", O_WRONLY)                     = -1 ENOENT (No such file or     directory)
open("1", O_WRONLY)                     = -1 ENOENT (No such file or     directory)
open("1", O_WRONLY)                     = -1 ENOENT (No such file or     directory)
open("1", O_WRONLY)                     = -1 ENOENT (No such file or directory)
open("1", O_WRONLY)                     = -1 ENOENT (No such file or directory)
exit_group(-1)                          = ?

在 strace 输出的末尾,您可以看到在打开的系统调用之间没有调用任何系统调用。那么,在没有系统调用的情况下,对于被调用的函数,内存是如何分配到堆栈上的呢?

4

5 回答 5

4

主线程的堆栈内存由内核在execve()系统调用期间分配。在此调用期间,还设置了可执行文件中定义的其他映射(也可能用于可执行文件中指定的动态链接器)。对于 ELF 文件,这是在fs/binfmt_elf.c.

其他线程的堆栈内存mmap()由线程支持库编辑,该库通常是 C 运行时库的一部分。

您还应该注意,在虚拟内存系统上,内核会增加主线程堆栈以响应页面错误,直至达到可配置的限制(如图所示ulimit -s)。

于 2013-10-28T16:48:53.827 回答
2

您的(单线程)程序堆栈大小是固定的,因此没有进一步的分配期望。

ulimit -s您可以使用该命令查询并增加此大小。

请注意,即使您将此限制设置为“无限制”,也总会有一个实际限制:

  • 对于 32 位进程,除非您的 RAM/swap 不足,否则虚拟内存空间限制将导致地址冲突

  • 对于 64 位进程,内存(RAM + 交换)耗尽会破坏您的系统并最终使您的程序崩溃。

无论如何,从来没有明确的系统调用会增加堆栈大小,它仅在程序启动时设置。

另请注意,堆栈内存的处理方式与堆内存完全相同,即只有已访问的部分映射到实际内存(RAM 或交换)。这意味着堆栈会按需增长,但除了标准虚拟内存管理之外没有其他机制可以处理这种情况。

于 2013-10-28T16:51:01.133 回答
1

open在递归“触底”之前,您的程序不会开始进行任何调用。此时,堆栈被分配,它刚刚从嵌套中弹出。

您为什么不使用调试器逐步完成它。

于 2013-10-28T18:13:12.810 回答
1

您想找出为函数创建的“堆栈帧”分配变量的位置吗?我已经修改了你的程序,向你展示了你的堆栈变量 k 的内存地址,以及一个参数变量 kk,

//Show stack location for a variable, k
#include <stdio.h>
int foo(int i)
{
    int k=9;
    if(i>=10) //relax the condition, safer
        return 1;
    else
        foo(++i);
    open("1",1);
    //return i;
}
int bar(int kk, int i)
{
    int k=9;
    printf("&k: %x, &kk: %x\n",&k,&kk); //address variable on stack, parameter
    if(i<10) //relax the condition, safer
        bar(k,++i);
    else
        return 1;
    return k;
}
int main()
{
    //foo(1);
    bar(0,1);
}

我的系统上的输出,

$ ./foo
&k: bfa8064c, &kk: bfa80660
&k: bfa8061c, &kk: bfa80630
&k: bfa805ec, &kk: bfa80600
&k: bfa805bc, &kk: bfa805d0
&k: bfa8058c, &kk: bfa805a0
&k: bfa8055c, &kk: bfa80570
&k: bfa8052c, &kk: bfa80540
&k: bfa804fc, &kk: bfa80510
&k: bfa804cc, &kk: bfa804e0
&k: bfa8049c, &kk: bfa804b0
于 2013-10-28T22:30:54.400 回答
1

堆栈使用和分配(至少在 Linux 上)是这样工作的:

  • 分配了一点堆栈。
  • 在程序的“其他”部分之后设置了一个保护范围,大约为地址空间的 1/4。
  • 如果堆栈用完它的顶部和上方,堆栈会自动增加。
  • 如果ulimit达到限制(和SIGSEGVs),或者如果不存在这样的限制,就会发生这种情况,直到它达到保护范围(然后得到 a SIGBUS)。
于 2013-10-29T05:38:08.803 回答