嵌入式Linux进程信息及内存布局

2019-07-12 14:16发布

 Linux进程信息及内存分布简介 一、linux进程信息 本文以thread进程为例,简单创建两个线程。   1、获取进程状态cat/proc//status(海思平台和ST平台差不多) Name:  thread State: S (sleeping) Tgid:  1199 Pid:   1199 PPid:  1195 TracerPid:      0 Uid:   0       0       0      0 Gid:   0       0       0      0 FDSize: 32 Groups: 0 VmPeak:   13156 kB VmSize:   13156 kB VmLck:         0 kB VmHWM:      256 kB VmRSS:      256 kB VmData:   12448 kB VmStk:      160 kB VmExe:      544 kB VmLib:         0 kB VmPTE:        12 kB VmSwap:        0 kB Threads:        3 SigQ:  0/1223 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: 0000000000000000 SigIgn: 0000000000000004 SigCgt: 0000000180000000 CapInh: 0000000000000000 CapPrm: ffffffffffffffff CapEff: ffffffffffffffff CapBnd: ffffffffffffffff Cpus_allowed:   1 Cpus_allowed_list:      0 voluntary_ctxt_switches:        5   Name: //进程名字 State: //任务状态, 运行/睡眠/僵死/ Tgid:  //线程组号 Pid:  //任务ID PPid: //父进程ID TracerPid: //接收跟踪该进程信号的进程ID号 FDSize: //文件描述符最大个数 Groups: //启动该进程用户所属的组ID VmPeak:         //进程地址空间的大小,进程运行过程中占用内存的峰值,代表占用的最大内存空间 VmSize:        //虚拟地址空间大小(total_vm进程地址空间大小 -reserved_vm进程在预留的内存间物理页) VmLck:            //已经锁定的物理内存大小(loack_vm) VmHWM:             //应用程序使用物理内存大小峰值 VmRSS:        //应用程序正在使用的物理内存大小 VmData:      //程序数据段的大小(所占虚拟内存大小),存放了初始化数据(total_vm - shared_vm - stack_vm) VmStk:              //任务用户堆栈大小(stack_vm) VmExe:      //程序拥有的可执行虚拟内存大小,代码段,不包括任务使用的库(end_code_start_code) VmLib:            //被映像到任务的虚拟内存空间的库大小(exec_lib) VmPTE:            //该进程页表大小 Threads:           //线程个数 SigQ:  0/1935                                             //待处理信号个数 SigPnd: 0000000000000000    //屏蔽位,存储了该线程的待处理信号 ShdPnd: 0000000000000000   //屏蔽位,存储了该线程组的待处理信号 SigBlk: 0000000000000000     //存放被阻塞的信号 SigIgn: 0000000000000001     //存放被忽略信号 SigCgt: 00000001e0001cfe      //存放被俘获到的信号 CapInh: 0000000000000000   //能被当前进程执行的程序的继承能力 CapPrm: ffffffffffffffff        //进程能够使用的能力 CapEff: ffffffffffffffff //进程有效能力 CapBnd: ffffffffffffffff Cpus_allowed:   3 Cpus_allowed_list:      0-1 voluntary_ctxt_switches:        1125 nonvoluntary_ctxt_switches:     301   2、获取进程内存空间映射cat/proc//maps(海思平台) 反应进程占用的内存区域,每行数据意思: 开始——结束         访问权限         偏移         主设备号:次设备号        i节点       文件   00008000-00090000r-xp 00000000 00:0f 1014855   /home/thread 00090000-00091000rw-p 00088000 00:0f 1014855   /home/thread 00091000-00095000 rw-p00000000 00:00 0 0137d000-0139f000rw-p 00000000 00:00 0          [heap] 2ab52000-2ab53000rw-p 00000000 00:00 0 2ab71000-2ab72000 ---p00000000 00:00 0 2ab72000-2b171000 rw-p00000000 00:00 0 2b218000-2b219000---p 00000000 00:00 0 2b219000-2b818000rw-p 00000000 00:00 0 7e973000-7e99a000 rw-p 00000000 00:00 0          [stack] ffff0000-ffff1000 r-xp 00000000 00:000          [vectors]     3、获取进程内存空间映射cat/proc//mapsST平台) 00008000-00078000r-xp 00000000 00:13 1047568   /home/n7/thread 0007f000-00081000rw-p 0006f000 00:13 1047568   /home/n7/thread 00081000-00084000rw-p 00000000 00:00 0 01499000-014bb000rw-p 00000000 00:00 0          [heap] 75fe4000-75fe5000 ---p00000000 00:00 0 75fe5000-767e4000 rw-p00000000 00:00 0          [stack:437] 767e4000-767e5000---p 00000000 00:00 0 767e5000-76fe5000rw-p 00000000 00:00 0         [stack:436] 7e9f7000-7ea1e000 rw-p 00000000 00:00 0          [stack] ffff0000-ffff1000 r-xp 00000000 00:000          [vectors]     4、进程maps解析 从maps可以得到进程的一个内存布局图,不同颜 {MOD}代表程序不同段映射: (1)代码段 00008000-00090000r-xp 00000000 00:0f 1014855   /home/thread (2)数据和BSS段 00090000-00091000rw-p 00088000 00:0f 1014855   /home/thread 00091000-00095000rw-p 00000000 00:00 0 数据和bss段与代码段基本都是紧挨着的。 (3)堆heap 01499000-014bb000rw-p 00000000 00:00 0          [heap] 向上增长 (4)mmap区域 thread进程主要是创建了两个线程,每个线程的堆栈大小为6M。 2ab71000-2ab72000 ---p00000000 00:00 0 //4K 2ab72000-2b171000 rw-p00000000 00:00 0         //6M 2b218000-2b219000---p 00000000 00:00 0 //4K 2b219000-2b818000rw-p 00000000 00:00 0         //6M A.     海思与ST平台对比发现创建线程时mmap增长方向刚好相反,见第四节。 B.     线程通过mmap方式映射,为啥每个线程都多一个4K,见第三节。   (5)stack区域 7e973000-7e99a000 rw-p 00000000 00:00 0          [stack] 位于地址高位,向下增长。   二、进程镜像信息与maps关系 使用objdump工具可以查找thread二进制文件的地址和符号信息,和maps内容对比发现基本是一致的。 1、代码段 arm-hisiv200-linux-objdump -t thread | grep".text"得到如下信息: …… 0004e140 g     F .text 00000008__wmemcpy 0001b810 g     F .text 00000008_IO_iter_next 0006e34c g     F .text 00000cb0_dl_close_worker 00022cd0 g     F .text 00000344__valloc 0000929c g     F .text 00000198__pthread_init_static_tls 00055624 g     F .text 00000014__geteuid 00026f68 g     F .text  0000013c_wordcopy_bwd_aligned ……. 从第一列地址看,代码段的地址范围与上面的maps获取到的地址范围是一致的。   2、数据段: arm-hisiv200-linux-objdump -t thread | grep".data"得到如下信息: 00090024 l    d .data.rel.ro       00000000.data.rel.ro 000900c8 l    d .data         00000000 .data 000900d0 l     O .data 00000008stack_used …………….. 0009056c g     O .data         00000004_dl_make_stack_executable_hook 00090568 g     O .data        00000004_dl_correct_cache_id 00090538 w    O .data      00000004 __memalign_hook 00090240 g     O .data        000000a0_IO_2_1_stderr_ 00090550 g     O .data        00000004__progname_full 数据地址与maps一致   3、bss段 arm-hisiv200-linux-objdump -t thread | grep".bss"得到如下信息 000907d0 l    d .bss  00000000 .bss 000907d0 l     O .bss   00000001completed.5624 000907d4 l     O .bss   00000004in_flight_stack 000907d8 l     O .bss   00000004stack_cache_actsize 000907e4 l     O .bss   00000004stack_cache_lock 000907e8 l     O .bss   00000008__nptl_threads_events 000907f0 l     O .bss    00000004__nptl_last_event 000907f8 l     O .bss    00000001__nptl_initial_report_events 00092800 l     O .bss   00000208static_slotinfo 00092a08 l     O .bss   00000200static_dtv ….. 000941d4 g     O .bss 00000004_dl_osversion 000941d8 g     O .bss 00000004_dl_inhibit_rpath 000941dc g     O .bss  00000004_dl_pagesize 000941e0 g     O .bss 0000004c_dl_ns 地址同maps对比发现,最开始的地方使用了数据段的地址空间。   三、strace跟踪进程          通过strace工具跟踪thread创建两个线程的过程: /home/n5x/strace home/n5x/thread 2: execve("home/n5x/thread",["home/n5x/thread", "2"], [/* 8 vars */]) = 0 uname({sys="Linux",node="host", ...})  = 0 brk(0)                                  = 0x163e000 brk(0x163ed04)                          = 0x163ed04 set_tls(0x163e4c0, 0x90014, 0xffffffe0, 0x14,0x92800) = 0 set_tid_address(0x163e068)              = 1199 set_robust_list(0x163e070, 0xc)         = 0 futex(0x7e85cd14, FUTEX_WAKE_PRIVATE, 1) =0 futex(0x7e85cd14,FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 1, NULL, 163e08c) = -1 EAGAIN(Resource temporarily unavailable) rt_sigaction(SIGRTMIN, {0xd870, [],SA_SIGINFO|0x4000000}, NULL, 8) = 0 rt_sigaction(SIGRT_1, {0xd714, [],SA_RESTART|SA_SIGINFO|0x4000000}, NULL, 8) = 0 rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1],NULL, 8) = 0 getrlimit(RLIMIT_STACK,{rlim_cur=6144*1024, rlim_max=6144*1024}) = 0 brk(0x165fd04)                          = 0x165fd04 brk(0x1660000)                          = 0x1660000 fstat64(1, {st_mode=S_IFCHR|0600,st_rdev=makedev(204, 64), ...}) = 0 ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICEor TCGETS, {B115200 opost isig icanon echo ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2ab02000 write(1, "stacksize 6291456 ",18stacksize 6291456 )    = 18 mmap2(NULL, 6291456, PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2ab5a000 mprotect(0x2ab5a000, 4096, PROT_NONE)   = 0 clone(child_stack=0x2b158e38,flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID,parent_tidptr=0x2b159368, tls=0x2b1597c0, child_tidptr=0x2b159368) = 1200 write(1, "stacksize 6291456 ",18stacksize 6291456 )    = 18 mmap2(NULL, 6291456,PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2b166000 mprotect(0x2b166000, 4096, PROT_NONE)   = 0 clone(child_stack=0x2b764e38,flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID,parent_tidptr=0x2b765368, tls=0x2b7657c0, child_tidptr=0x2b765368) = 1201 write(1, "i=2 ", 4i=2 )                    = 4 write(1, "wait a moment, ",16wait a moment, )      = 16 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) =0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [],0}, 8) = 0   上面的蓝 {MOD}和紫 {MOD}分别创建了一个线程,其中有一个mprotect函数创建了4KB与mmap对应的内存保护区,该区域用户无法操作,如果意外操作就会出错。这就是为什么在maps中每个线程会有一个4KB的原因。     四、不同内核进程内存布局          对比海思(3.0.8)和ST(3.4.7)平台,maps中线程mmap增长方向是相反的。说明不同的内核版本采用不同的内存布局,对于早期的内核采用3.0.8的经典内存布局,最新的都是3.4.7的灵活内存布局。 1、经典内存布局          经典布局mmap的增长方向:向上增长,如图:            对于经典模式,heap和mmap都是向上增长的,于是在内核中就为heap保留了一定空间:heap ~mmap起始地址。          mmap起始地址计算方法: 在内核代码/arch/arm/mm/mmap.c start_addr = TASK_UNMAPPED_BASE #define TASK_UNMAPPED_BASE (UL(CONFIG_PAGE_OFFSET) / 3)                    CONFIG_PAGE_OFFSET= 0xc0000000(对于2G的就是0x80000000) 因此,在这种情况下,mmap可用区域并不是3G,而是2*( CONFIG_PAGE_OFFSET) /3。   2、灵活内存布局          灵活内存布局的增长方向:向下增长,如图:
         从图中可以看出,栈至顶向下扩展,并且栈是有界的。对至底向上增长,mmap映射区域至顶向下扩展,直至耗尽虚拟地址空间的剩余区域。          当栈中压入数据超出其容量就会耗尽栈所对应的内存区域,这将触发一个页故障(page fault),并被linux的expand_stack()处理,调用acct_stack_growth来检查是否有合适的地方用于栈的增长。如果栈的大小低于RLIMIT_STACK,那么一般情况下栈会加长。          另外,可以看出栈和mmap映射区域不是从一个固定的地址开始,而是程序启动时有一个随机的偏移,这样使得使用缓冲区溢出攻击的方法更加困难。如果想使地址固定,设置 /proc/sys/kernel/randomize_va_space为0。          也可以将灵活内存布局设置为经典内存布局,设置变量/proc/sys/vm /legacy_va_layout为1。     五、其他补充