说说LINUX程序内存管理那些事

本文详细解析Linux系统中内存管理的策略与常见编程错误,包括延迟回收、自由指针误用及堆栈参数传递问题。通过实例演示如何避免内存错误,提供自定义内存管理函数库的方法,并讨论内存调试工具的应用。

说说LINUX程序内存管理那些事


LINUX系统中,在内存管理方面,为考虑到简便高效,就像UNIX那样,有时会出现这样的情形,刚刚释放的内存,这时内存已经是无效的了,但是仍然可以访问,这是因为暂时释放的区域实际上根本就没有真正地回收,不过可能过不一会儿就变成无效的了,这是一种延迟回收的策略,如果一直使用一个被释放了的指针,可能会刚开始时,一切都正常,可过会儿就出现地址越界而导致程序出错(在有的系统中,比如WindowsMinix中,如果刚刚释放了一块内存,内核马上修改一下刚刚释放的指针的前几个字节,这样的话会降低系统的性能,这种方式对编程习惯良好的程序员来说是不必要的,在LinuxFreeBSD系统中就没有采用)。


如果一个初学者写了程序,把释放的内存又拿来用,可能程序运行得畅通无阻,这是因为在LINUX中,如果用free()函数释放一个地址,系统只是简单标记一下这个地址已经变成可用状态了(available),先前状态则是busy(因为它正在被使用),但是涉及到地址是否可以读写保护等属性都不管了,然后内核转而去做更加正经的事了。如果一个程序分配内存频繁,内核找到刚才被释放的那块被标记了available的内存,然后把它分给调用malloc()的调用者,如果上次还在用那个释放了的内存,这是程序就会出现SengmentFault的错误了,有时会dump出程序的内存映射列表的信息,如果内存分配频率低,错误的程序可能仍然可以运行得畅通无阻。

所以对于程序员来说,mallocfree必须要成对出现,因为free释放内存是标记一下就立马走人。若指针不当使用可能就会出现这样的情况,看上去正常的程序其实是有错的,它只是暂时不崩溃,但是把它放到一个内存小一点的计算机上,或者本地计算机内存将待耗尽时,它就崩溃了。不良的指针使用就导致一个不确定的炸弹的产生,虽然不一定马上就炸,但是过会儿,甚至马上就炸。


下面一个小例子说明这种问题:

假如要实现这样一个函数:

根据所提供的参数size,即内存的大小,来调整内存,每次调用这个函数内存增加1K字节,并且要返回上次内存的尾端位置的指针。


代码如下:

void* incmem(void**ptr, long* size)
{
  void* retp = *ptr+ *size; /* 这是上次指针的尾端*/
  *size += 1024; /*1K字节*/
  *ptr = realloc(*size);
  return retp;
}

很明显上面的代码是有误的,因为realloc返回的指针不一定和上次相等,retp看似是尾端,但是下面可能已经把*ptr的值改掉了。而在LINUX系统可能上面的代码在运行时并没有任何异常。


如把代码改成这样:

void* incmem(void**ptr, long* size)
{
  long oldsize =*size; /* 原来内存的大小*/
  *size += 1024;
  *ptr = realloc(*size);
  return *ptr +oldsize;
}

才可保证程序代码的正确性。


LINUX中其实可以自己写个内存管理的辅助函数库,自己用代码实现mallocreallocfree等这些函数,或者把函数名字改下,改成据如_malloc_realloc_free等自定义又便于与标准函数对应的函数,申请内存时,调用自己的函数,自己用这些函数监控内存的使用情况,判断内存是否出错,并且给内存设置保护信息等,当确定自己的程序代码在内存申请与释放没有错误时,再用define把自定义的函数定义成标准的函数,

比如:

#ifndef	 DEBUG
  #define _malloc(s)malloc(s)
// ...
#else
  #include“mymalloc.h”
#endif

编写这些代码可以很简单,举个简单例子,就是仅仅改一下free()函数,让它每次释放内存后,就置原来的指针为0,但是如果涉及realloc()函数的话,如果free()时,置0的那个指针还有个备份,它仍然可以被误用,那这些方法就都不管用了。也可以把代码编写得很复杂,是个技术活,比如说自己写个类似系统内核内存管理的仿真器,在自己的仿真器中,监控这些内存,分配内存时,挖一块出来,释放内存后,添一块,并把它设置为无效的(需要结合系统的函数来实现,比如用mmap()来申请内存块,用mprotect()来给刚刚释放的内存设置为PROT_NONE,用munmap()释放,复杂一点,可以不急于释放而是用mprotect来标记,这样可以加快内存的申请速度,这就需要自己来管理内存的,用虚拟页方式调试内存错误是切实可行的,但是内存的分配效率是不及普通的malloc类函数的),也可以利用malloc_hook这样的函数,给这些函数安装钩子,也是可行的工具之一。在内存调试方面,有些现成的库,如MEMWATCHYAMDElectricFence等,如果感兴趣的话,可以在网上搜一下,然后拿来用。


下面再说个函数的堆栈参数或寄存器参数传递问题:


堆栈内存管理遵从先进后出的原则。

比如有如下代码:

long size = 1024;
char* ptr = (char*)malloc(size);
char* last = (char*)incmem(&ptr, &size);

这里只要涉及修改参数的都要用指针传递,否则的话在函数调用返回后,修改了的值仍然得不到保存,被从栈中弹出而废弃了。


如果有如下函数:

void* incmem(void*ptr, long size)
{
  long	oldsize =size;
  size += 1024;
  ptr =realloc(size);
  return ptr +oldsize;
}

这是一个存在错误的函数,函数虽正确修改了内存并掉整了大小。但是却把它放到了栈中,函数退出时,ptrsize都被自动废弃了。


如有调用代码:

long size = 1024;
char* ptr = (char*)malloc(size);
char* last = (char*)incmem(ptr, size);

用汇编代码描述如下:

32位的INTEL80x86平台下:

ptr: dword 0
size: dword 0

push ptr
push size
call incmem


ptrsize都临时放在了CPU的指令使用的栈中,函数修改了栈中的值,而没能更改ptrsize的值。


64位的AMD64平台下(或者INTELx86_64:

ptr: dword 0
size: qword 0

movq ptr, %rcx
movq size, %rdx
call incmem

用寄存器传递也一样,修改了的值是在寄存器里面,不在ptrsize上,所以仍然是错误的。


所以要想获得正确的结果,就必须传递要引用的参数的指针,因为传递指针时,堆栈或者寄存器中的值是表示ptrsize的地址,引用此地址的值将导致直接引用ptrsize,也就是callincmem上面的那两行处,函数调用返回后,pop出而废弃的两个地址数值是无关数值(其实这两个地址数并没有修改,修改的值是这两个地址数表示的地址处的数值)。


<think>我们正在回答用户关于Linux系统启动流程的问题。根据系统级指令,我们需要使用正确的LaTeX格式,使用中文回答,并在最后生成相关问题。同时,回答中引用的段落末尾要添加引用标识。 根据用户的问题,我们需要详细解释Linux系统的启动过程。结合我们已有的引用内容,我们可以将启动过程分为几个主要阶段: 1. BIOS/UEFI阶段 2. Boot Loader阶段(如GRUB) 3. 内核加载阶段 4. systemd初始化阶段 下面我们按照这些阶段详细说明: ### 1. BIOS/UEFI阶段 当计算机加电后,首先运行的是BIOS(基本输入输出系统)或UEFI(统一可扩展固件接口)固件。BIOS/UEFI会进行硬件自检(POST, Power-On Self-Test),检测关键硬件设备(如内存、CPU、存储设备等)是否正常工作。然后,BIOS/UEFI会根据配置的启动顺序(Boot Order)寻找第一个可启动的设备(如硬盘、U盘等)[^2]。 ### 2. Boot Loader阶段 在找到启动设备后,BIOS/UEFI会读取该设备的第一个扇区(MBR,主引导记录)或EFI系统分区中的启动加载程序(Boot Loader)。在Linux系统中,常用的Boot Loader是GRUB(GRand Unified Bootloader)。GRUB的主要功能是加载Linux内核。它通常提供一个菜单供用户选择不同的内核版本或操作系统[^2]。 GRUB的配置文件位于`/boot/grub/grub.cfg`(通常由`/etc/default/grub`和`/etc/grub.d/`下的文件生成)。用户可以通过编辑这些文件来修改启动选项,然后运行`update-grub`命令更新配置[^3]。 ### 3. 内核加载阶段 Boot Loader加载内核映像(通常位于`/boot/vmlinuz-<版本号>`)和初始内存文件系统(initramfs,通常为`/boot/initrd.img-<版本号>`)。initramfs是一个临时的根文件系统,它包含在挂载真实根文件系统之前所必需的驱动程序和工具。内核解压后,会初始化硬件设备,加载initramfs中的驱动程序,然后挂载真正的根文件系统(`/`)[^2][^3]。 ### 4. systemd初始化阶段 当内核完成初始化后,它会启动用户空间的第一个进程(PID=1)。在大多数现代Linux发行版中,这个进程是`systemd`。`systemd`负责初始化系统,包括挂载文件系统、启动服务、设置主机名等。具体流程如下: - **sysinit.target**:初始化系统,包括挂载文件系统、设置内核参数、加载额外的内核模块等。 - **basic.target**:准备操作系统,启动基本服务。 - **multi-user.target**:启动多用户环境下的服务,包括网络服务、数据库服务等。在这个阶段,还会执行`/etc/rc.d/rc.local`文件(如果存在且可执行),用于用户自定义的启动命令。 - **getty.target**:启动虚拟终端(tty)服务,提供登录界面。 - 如果配置了图形界面,还会启动**graphical.target**,进入图形登录界面[^2]。 在启动完成后,用户可以通过`systemd-analyze`命令分析启动时间,查看各个阶段的耗时[^3]。 ### 总结 Linux启动过程是一个复杂但有序的过程,从硬件初始化到用户空间服务的启动,每个阶段都紧密衔接。了解启动过程有助于我们诊断启动问题、优化启动速度和自定义启动环境。 §§相关问题§§ 1. 如何修改GRUB的默认启动内核? 2. initramfs的作用是什么?如何重建initramfs? 3. 如何设置服务在系统启动时自动运行? 4. 如何诊断Linux系统启动慢的问题? 5. systemd与传统init系统(如SysVinit)在启动流程上有何不同? [^1]: 参考引用1中关于Linux启动流程的概述。 [^2]: 参考引用2中关于操作系统启动流程的详细步骤。 [^3]: 参考引用3中关于启动时间分析和内核参数修改的方法。</think>### Linux系统启动流程详解 Linux系统的启动是一个分层递进的过程,主要分为以下阶段: #### 1. **BIOS/UEFI阶段** - **硬件自检**:加电后执行POST(Power-On Self-Test),检测CPU、内存、磁盘等硬件。 - **启动设备选择**:根据BIOS设置顺序(硬盘>U盘>网络)寻找首个可启动设备的MBR/GPT分区表。 - **关键操作**:加载MBR的前446字节(包含初始引导代码)[^2]。 #### 2. **Boot Loader阶段** - **引导加载**:MBR中的引导程序(如GRUB2)加载其核心文件(`/boot/grub/grub.cfg`)。 - **内核选择**:显示启动菜单供用户选择内核版本(配置文件:`/etc/default/grub`)。 - **内核加载**:将选定的内核(`vmlinuz-*`)和初始内存盘(`initramfs-*`)载入内存[^2][^3]。 ```bash # 修改GRUB配置后需更新 sudo update-grub ``` #### 3. **内核初始化阶段** - **硬件检测**:内核解压后检测硬件并加载驱动(部分驱动在`initramfs`中)。 - **挂载根文件系统**:`initramfs`提供临时根环境,加载真实根分区(`/`)所需的驱动(如LVM、RAID)。 - **进程切换**:内核启动第一个用户空间进程`systemd`(PID=1)[^1][^3]。 > 注:`initramfs`重建命令: > `mkinitramfs -o /boot/initrd.img-$(uname -r)` #### 4. **systemd初始化阶段** `systemd`按目标单元(target)层级启动服务: - **sysinit.target**:基础初始化(挂载文件系统、设置交换空间)。 - **basic.target**:启动基础服务(udev设备管理、系统日志)。 - **multi-user.target**: - 启动网络服务/SSH等 - 执行用户脚本(`/etc/rc.d/rc.local`) - **graphical.target**(可选):启动图形界面(如GNOME/Xorg)。 - **getty.target**:启动登录管理器(tty1~tty6)[^2]。 #### 5. **用户登录阶段** - 显示登录提示符(终端或图形登录界面)。 - 用户验证成功后加载Shell环境(如bash)或桌面环境。 #### 启动时间分析 ```bash systemd-analyze time # 查看总启动时间 systemd-analyze blame # 各服务耗时排名 systemd-analyze critical-chain # 关键路径分析 ``` [^3] > **流程图摘要**: > `BIOS/UEFI → Boot Loader (GRUB) → Kernel → initramfs → systemd (targets) → Login`
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值