[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9nE9r4mQ-1616393529705)(C:\Users\Administrator\Desktop\interview_python-master\img\linux体系结构.png)]
操作系统运行过程中,时刻进行着各种上下文的相互切换:
malloc
函数,调用sbrk()
的时候就涉及一次从用户态到内核态的切换fork
出来的,但Linux
系统初始化时创建的都是内核线程,因此还有一种场景是1号内核线程演变为1号用户进程时,会装载init
程序,然后由内核空间切换到用户空间,从而形成1号用户进程。0
到3
四个特权级,数字越小,特权越高。Linux操作系统中主要采用了0和3两个特权级,分别对应的就是内核态和用户态。内核态和用户态有自己的内存映射,即自己的地址空间。
move_to_user_mode()
函数将内核从内核态切换到用户态上下文context: 上下文简单说来就是一个环境。
进程上下文:用户进程传递给内核的这些参数以及内核要保存的那一整套的变量和寄存器值和当时的环境等。
IP
)、处理器状态寄存器(EFLAGS
)、栈指针(ESP
);task_struct
、内存管理信息(mm_struct
、vm_area_struct
、pgd
、pte
)、内核栈。硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。
运行在中断上下文的代码就要受一些限制,不能做下面的事情(运行于内核态的代码也受同样的限制):
中断:在CPU正常运行期间,由于内,外部事件,由程序预先安排的事件引起的CPU暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。
0
开始的1KB
空间作为一个中断向量表。
中断描述符表
(IDT)。在CPU中增加了一个用来描述中断描述符表寄存器(IDTR
),用来保存中断描述符表的起始地址。Linux中断分为
buttom half
),也称为软中断,代表的系统不必马上处理的、没那么紧急的任务。
I/O
请求,这些请求会调用内核中可以调度I/O
的程序Linux中断处理
系统中断号:系统中断向量表中共可保存256个中断向量入口,即IDT
中包含的256个中断描述符(对应256个中断向量)。
0-31
号中断向用来处理异常事件,不能另作它用。
中断向量号 | 异常事件 | Linux的处理程序 |
---|---|---|
0 | 除法错误 | Divide_error |
1 | 调试异常 | Debug |
2 | NMI中断 | Nmi |
3 | 单字节,int 3 | Int3 |
4 | 溢出 | Overflow |
5 | 边界监测中断 | Bounds |
6 | 无效操作码 | Invalid_op |
7 | 设备不可用 | Device_not_available |
8 | 双重故障 | Double_fault |
9 | 协处理器段溢出 | Coprocessor_segment_overrun |
10 | 无效TSS | Incalid_tss |
11 | 缺段中断 | Segment_not_present |
12 | 堆栈异常 | Stack_segment |
13 | 一般保护异常 | General_protection |
14 | 页异常 | Page_fault |
15 | (intel保留) | Spurious_interrupt_bug |
16 | 协处理器出错 | Coprocessor_error |
17 | 对齐检查中断 | Alignment_check |
中断请求
中断请求:外部设备当需要操作系统做相关的事情的时候,会产生相应的中断。
过程:
IRQ
。其次,通过中断申请函数申请相应的IRQ
。最后,根据申请结果查看中断是否能够被执行。中断相关结构
中断中核心处理数据结构为irq_desc
,它完整的描述了一条中断线,Linux 2.6。22.6中源码如下。
struct irqaction {
irq_handler_t handler;
unsigned long flags;
cpumask_t mask;
const char *name;
void *dev_id;
struct irqaction *next;
int irq;
struct proc_dir_entry *dir;
};
中断请求实现---------上下半部机制
tasklet
实现和工作队列实现。上下半部划分原则
软中断
share memory processor
)的出现应运而生的,它也是tasklet
实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制)。tasklet
。它的出现就是因为要满足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,软中断执行中断处理程序留给它去完成的剩余任务,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。它的特性包括:
tasklet
tasklet
是通过软中断实现的,所以它本身也是软中断。
Tasklet
采用无差别的队列机制,有中断时才执行,免去了循环查表之苦。Tasklet
作为一种新机制,显然可以承担更多的优点。正好这时候SMP
越来越火了,因此又在tasklet
中加入了SMP
机制,保证同种中断只能在一个cpu上执行。在软中断时代,显然没有这种考虑。因此同一种软中断可以在两个cpu上同时执行,很可能造成冲突。下半部实现机制之工作队列(work queue)
Linux软中断和工作队列的作用是什么
CPU内部有一个系统的内部定时器RTC
,上电时调用mktime
函数算出从1970年1月1日0时开始当前开机的时间所过的秒数,给mktime
函数传来的时间结构体赋值。是由初始化时从RTC(coms)
中读出的参数,转化为时间存入全局变量中,并且会为JIFFIES所用
JIFFIES 是一个系统的时钟滴答,一个系统滴答是10ms,定时器
每隔10ms会引发一个定时器中断—timer_interrupt
中断服务函数中首先进行了jiffies
的自加,调用do_timer
函数。
do_timer
if (cpl)//CPL变量是内核中用来指示被终端程序的特权,0表示内核进程,1为用户进程
current->utime++;//utime用户程序运行时间
else
current->stime++;//内核程序运行时间
next_timer
是嫁接与jiffies
变量的所有定时器的事件链表
counter->current
,即进程时间片
task-struck
一个进程 task_struck[]
进程的向量表,每个进程中都有counter
时间片。
counter
-----在哪儿用:进程的调度,即task_struck[]
进程链表中的检索,找时间片counter
最大的进程对象,然后进行调用,直到时间片counter
为0,退出,之后进行新一轮的调用
counter
----在哪里被设置。当全部的task_struck[](task[])
所有的进程的counter
都为0,就进行新一轮的时间片分配
分配方式-----优先级分配 :优先级低的时间片小,优先级高的时间片大
(*p)-counter = ((*p)->counter>>1)+(*p)->priority
优先级时间片轮转调度算法:优先级最大分为64级
task_struck
:进程结构体:
state
:进程状态counter
:时间片counter
= counter
/ 2 +priority
priority
:进程优先级
LDT
局部描述符:保存进程所运行的代码段、数据段、TSS
进程的状态描述符:在进程运行的过程中,CPU需要知道的进程的状态标识在Linux系统中,除了系统启动之后的第一个进程由系统来创建,其余的进程都必须由已存在的进程来创建。
新创建的进程叫做子进程,而创建子进程的进程叫做父进程。那个在系统启动及完成初始化之后,Linux自动创建的进程叫做根进程。根进程是Linux中所有进程的祖宗,其余进程都是根进程的子孙。具有同一个父进程的进程叫做兄弟进程。
继承的创建就是对0号进程或者是当前进程的复制
task[0]
对应的task_struck
复制给新创建的task_struck
、Linux在初始化的过程中会进行0号进程的创建。fork
函数
在内核初始化过程中,会动手创建0号进程。0号进程是所有进程的父继承。
/etc/rc
文件shell
程序,/bin/sh
for(;;)
,pause()
fork
函数
task
链表中找一个进程空位存放当前的进程task_struck
task_struck
继承的创建是系统中断,调用fork
函数
sys_fork:
call find_empty_process
....
call copy_process
创建进程过程
find_empty_process
task_struct
结构体task_struck
的属性
fork
出子进程后,子进程都会继承父进程以下信息;
tms_utime
,tms_stime
,tms_sutime
,tms_ustime
设置为0./a.out
底层指向过程
execve
系统调用启动加载器。加载器删除子进程已有的虚拟存储段,并创建一组新的代码、数据、堆、栈段,新的堆和栈被初始化为零。通过将虚拟地址空间中的页映射到可执行文件的页大小组块,新的代码和数据段被初始化为可执行文件的内容,最后将CUP指令寄存器设置成可执行文件入口,启动运行。0x08048000
,刚好是代码段的起始地址。当CPU打算执行这个地址的指令时,发现页面0x8048000~0x08049000
(一个页面一般是4K)是个空页面,于是它就认为是个页错误。此时操作系统根据虚拟地址空间与可执行文件间的映射关系找到页面在可执行文件中的偏移,然后在物理内存中分配一个物理页面,并在虚拟地址页面与物理页面间建立映射,最后把文件中页面拷贝到物理页面,进程重新开始执行。进程的状态:
运行状态:可以被运行,就绪状态,进程切换只有在运行状态
可中断睡眠状态:可以被信号中断,使其变为RUNNING
不可中断睡眠状态:只能被wakeup
唤醒变为RUNNING
暂停状态:收到信号SIGSTOP
等信号
僵死状态:进程停止运行了,但是父进程没有
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define TASK_ZOMBIE 3
#define TASK_STOPPD 4
switch_to()
进程切换函数
schedule
进程调度函数
进程切换
什么时候会切换?
schedule()
的时候。都要切换什么?
硬件上下文是什么?
exit()
是销毁函数-------系统中断调用函数------do_exit()
SIGCHLD
信号wait
,waitpid
这两个函数
SIGCHLD
信号时,父进程会终止僵死状态的子进程:
pipe
),流管道(s_pipe
和有名管道(FIFO
)管道
管道(pipe
)是Unix/Linux
中最常见的进程间通信方式之一,它在两个进程之间实现一个数据流通的通道,数据以一种数据流的方式在进程间流动。
在系统中,管道相当于文件系统上的一个文件,用于缓存所要传输的数据。
在某些特性上又不同于文件,例如当数据读出后,管道中就没有数据了,但文件没有这个特性。管道有两个特点:
为用户在shell中提供了相应的管道操作符“|
”。操作符“|
”将其前后两个命令连接到一起,前一个命令的输出成为后一个命令的输入,且可以支持使用多个“|
”连接多个命令
管道通信的限制:
实现
/**
* struct pipe_buffer - a linux kernel pipe buffer
* @page: the page containing the data for the pipe buffer
* @offset: offset of data inside the @page
* @len: length of data inside the @page
* @ops: operations associated with this buffer. See @pipe_buf_operations.
* @flags: pipe buffer flags. See above.
* @private: private data owned by the ops.
**/
struct pipe_buffer {
struct page *page;
unsigned int offset, len;
const struct pipe_buf_operations *ops;
unsigned int flags;
unsigned long _private;
};
/**
* struct pipe_inode_info - a linux kernel pipe
* @mutex: mutex protecting the whole thing
* @wait: reader/writer wait point in case of empty/full pipe
* @nrbufs: the number of non-empty pipe buffers in this pipe
* @buffers: total number of buffers (should be a power of 2)
* @curbuf: the current pipe buffer entry
* @tmp_page: cached released page
* @readers: number of current readers of this pipe
* @writers: number of current writers of this pipe
* @files: number of struct file referring this pipe (protected by ->i_lock)
* @waiting_writers: number of writers blocked waiting for room
* @r_counter: reader counter
* @w_counter: writer counter
* @fasync_readers: reader side fasync
* @fasync_writers: writer side fasync
* @bufs: the circular array of pipe buffers
* @user: the user who created this pipe
**/
struct pipe_inode_info {
struct mutex mutex;
wait_queue_head_t wait;
unsigned int nrbufs, curbuf, buffers;
unsigned int readers;
unsigned int writers;
unsigned int files;
unsigned int waiting_writers;
unsigned int r_counter;
unsigned int w_counter;
struct page *tmp_page;
struct fasync_struct *fasync_readers;
struct fasync_struct *fasync_writers;
struct pipe_buffer *bufs;
struct user_struct *user;
};
当进程创建一个匿名管道时,Linux内核为匿名管道准备了两个文件描述符:一个用于管道的输入,即在管道中写入数据;另一个用于管道的输出,也就是从管道中读出数据。
Linux的管道操作
#include
#include
#include
#include
#include
int main()
{
int fd[2];
pid_t pid;
char buf[20];
if (pipe(fd) < 0)
{
std::cerr << "创建管道失败\n";
exit(0);
}
if ((pid = fork()) < 0)
{
std::cerr << "创建子进程失败\n";
exit(0);
}
if (pid == 0) //子进程
{
close(fd[1]); //关闭写入端
read(fd[0], buf, sizeof(buf));
std::cout << buf;
exit(0);
}
else
{
close(fd[0]); //关闭读出端
write(fd[1], "I'm your father.\n", 17);
if (waitpid(pid, NULL, 0) != pid)
{
std::cerr << "销毁进程失败\n";
}
}
exit(0);
}
#include
#include
#include
int main()
{
int fd[2]; //创建文件描述符数组
char writebuf[20] = {"Hello World!"}; //写缓冲区
char readbuf[20]; //读缓冲区
if (pipe(fd) < 0)
{
std::cerr << "创建管道失败\n";
exit(0);
}
write(fd[1], writebuf, sizeof(writebuf)); //向管道写入端写入数据
read(fd[0], readbuf, sizeof(readbuf)); //向管道读出端读出数据
std::cout << readbuf << "\n";
std::cout << "管道的读fd是" << fd[0] << "\n写fd是" << fd[1] << std::endl;
return 0;
}
named pipe
或FIFO
)**命名管道(Named Pipe):不仅可在同一台计算机的任意不同进程之间通信,而且还可以在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信。
命名管道不同于管道之处在于它提供一个路径名与之关联,以命名管道的文件形式存在于文件系统中。
虽然管道和命名管道都是实实在在的文件,但前者没有公开的文件名,用户在文件系统中不能直接观察并访问到它;命名管道则是存在于文件系统中的,任何具有访问权限的进程都可以访问。
管道和命名管道的区别总结如下:
需要注意的是,命名管道是一种严格遵循FIFO规则,不支持lseek函数等文件定位操作。
读写规则
#include
#include
#include
#include
int main(int argc, char *argv[])
{
if (argc < 2)
{
std::cerr << "参数错误\n";
exit(1);
}
std::ifstream fp(*(argv+1), std::ios::in);
std::string s;
if (fp.is_open())
{
std::cerr << "文件打开失败\n";
exit(1);
}
fp >> s;
std::cout << s << std::endl;
fp.close();
return 0;
}
消息队列是一种以链表为结构组织的数据,存放在Linux内核中,是由各进程通过消息队列标识符来引用的一种数据传送方式。每个消息队列都有一个队列头,利用结构struct msg_queue
来描述。
msqid_ds
用来设置或返回消息队列的信息,存在于用户空间中,结构定义如下struct msg_queue
{
struct ipc_perm q_perm;
time_t q_stime; //上一条消息的发送时间
time_t q_rtime; //上一条消息的接收时间
time_t q_ctime; //上一次修改时间
unsigned long q_cbytes; //当前队列中的字节数据
unsigned long q_qnum; //队列中的消息数量
unsigned long q_qbytes; //队列的最大字节数
pid_t q_lspid; //上一条发送消息的PID
pid_t q_lrpid; //上一条接收消息的PID
struct list_head q_messages;
struct list_head q_receivers;
struct list_head q_senders;
};
struct msqid_ds
{
struct ipc_perm msg_perm;
struct msg *msg_first; //队列中的第一条消息
struct mag *msg_last; //队列中的最后一条消息
time_t q_stime; //上一条消息的发送时间
time_t q_rtime; //上一条消息的接收时间
time_t q_ctime; //上一次修改时间
unsigned long q_cbytes; //当前队列中的字节数据
unsigned long q_qnum; //队列中的消息数量
unsigned long q_qbytes; //队列的最大字节数
pid_t q_lspid; //上一条发送消息的PID
pid_t q_lrpid; //上一条接收消息的PID
};
struct ipc_perm
{
//内核中用于记录消息队列的全局数据结构msg_ids能够访问到该结构
key_t key; //该键值唯一对应一个消息队列
uid_t uid; //所有者的有效用户ID
gid_t gid; //所有者的有效组ID
uid_t cuid; //创建者的有效用户ID
gid_t cgid; //创建者的有效组ID
mode_t mode; //此对象的访问权限
unsigned long seq; //对象的序号
};
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
if (argc < 2)
{
std::cerr << "参数错误\n";
exit(1);
}
key_t key = ftok(*(argv+1), 1); //ftok函数生成队列键值
if (key < 0)
{
std::cerr << "获取消息队列键值失败\n";
exit(1);
}
int qid = msgget(key, IPC_CREATE|0666); //打开或创建队列
if (qid < 0)
{
std::cerr << "创建消息队列出错\n";
exit(1);
}
else
{
std::cout << "创建消息队列成功\n";
}
return 0;
}
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//消息队列的发送
int msgrcv(int msqid, const void *msgp, size_t msgsz, long msgtyp, int msgflg);//消息队列的接收
int msgctl(int msgqid, int cmd, struct msqid_ds *buf);
/*
*参数msqid为消息队列ID(msgget返回的值)
*参数cmd为指定要求的操作
*/
/*
函数msgctl可以对消息队列进行以下操作:
查看消息队列相连的数据结构
改变消息队列的许可权限
改变消息队列的拥有者
改变消息队列的字节大小
删除一个消息队列
*/
共享内存是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存,
共享内存的特点:
常用函数
创建共享内存
/*所需头文件:
#include
#include
int shmget(key_t key, size_t size,int shmflg);
功能:
创建或打开一块共享内存区。
参数:
key:进程间通信键值,ftok() 的返回值。
size:该共享存储段的长度(字节)。
shmflg:标识函数的行为及共享内存的权限,其取值如下:
IPC_CREAT:如果不存在就创建
IPC_EXCL: 如果已经存在则返回失败
位或权限位:共享内存位或权限位后可以设置共享内存的访问权限,格式和 open() 函数的 mode_t 一 样(open() 的使用请点此链接),但可执行权限未使用。
返回值:
成功:共享内存标识符。
失败:-1。
示例代码如下:
*/
#include
#include
#include
#include
#include
#include
#include
#define BUFSZ 512
int main(int argc, char *argv[])
{
int shmid;
int ret;
key_t key;
char *shmadd;
//创建key值
key = ftok("../", 2015);
if(key == -1)
{
perror("ftok");
}
system("ipcs -m"); //查看共享内存
//打开共享内存
shmid = shmget(key, BUFSZ, IPC_CREAT|0666);
if(shmid < 0)
{
perror("shmget");
exit(-1);
}
//映射
shmadd = shmat(shmid, NULL, 0);
if(shmadd < 0)
{
perror("shmat");
exit(-1);
}
//读共享内存区数据
printf("data = [%s]\n", shmadd);
//分离共享内存和当前进程
ret = shmdt(shmadd);
if(ret < 0)
{
perror("shmdt");
exit(1);
}
else
{
printf("deleted shared-memory\n");
}
//删除共享内存
shmctl(shmid, IPC_RMID, NULL);
system("ipcs -m"); //查看共享内存
return 0;
}
/
PC机中的BIOS
:初始化处理器及外设,然后调用 Linux 内核。**类似于 PC 机上的 BIOS **BIOS
(0xFFFF0
是BIOS
存储的总线地址)把bootsect.s
从某个固定的地址拿到内存的固定地址(0x90000
),并且进行了一系列的硬件初始化和参数设置.
setup.s
代码从磁盘中加载到紧接着bootsect.s
的地方。loading system
,再将system
(操作系统)模块加载到0x10000
的地方setup.s
中去执行setup.s
BIOS
程序中读取系统信息LDT
(局部描述符),IDT
(中断描述符寄存器),全局描述符system
模块的最前面的代码运行(head.s
)head.s
main.c
开始运行文件系统
宏观:
glibc
,OpenGL
media
,Framework
shell
的实现:/bin
文件目录作用
文件系统,也叫应用程序,系统的界面,系统的开机画面,系统ROM
,系统的功能,预装的软件均属于文件系统。
在 Linux 操作系统中,所有被操作系统管理的资源,例如网络接口卡、磁盘驱动器、打印机、输入输出设备、普通文件或是目录都被看作是一个文件。
Linux 支持 5 种文件类型,如下图所示:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nX7IvPYA-1616393529712)(C:\Users\Administrator\Desktop\interview_python-master\img\文件类型.png)]
Linux 文件系统的结构层次鲜明,就像一棵倒立的树,最顶层是其根目录:
常见目录说明:
shell
命令硬盘驱动
PATA
, SATA
和AHCI
等,drivers/ata
中,General Block Device Layer通用块设备层:I/O体系结构,与外设的通信通常称为输入输出,缩写为I/O。在实现外设的I/O时,内核需要处理好3个问题、
文件系统
虚拟文件系统(VFS):是Linux内核的子系统之一,它为用户程序提供文件和文件系统操作的统一接口,屏蔽不同文件系统的差异和操作细节。借助VFS可以直接使用open()
、read()
、write()
这样的系统调用操作文件,而无须考虑具体的文件系统和实际的存储介质。
Linux启动时,第一个必须挂载的是根文件系统;若系统不能从指定设备上挂载根文件系统,则系统会出错而退出启动。之后可以自动或手动挂载其他的文件系统。因此,一个系统中可以同时存在不同的文件系统。
Linux为了实现这种VFS系统,采用面向对象的设计思路,主要抽象了四种对象类型:
inode
(索引节点)
Sector
)。每个扇区储存512字节(相当于0.5KB)。操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个"块"(block
)。这种由多个扇区组成的"块",是文件存取的最小单位。“块"的大小,最常见的是4KB,即连续八个 sector
组成一个 block
。inode
中(i 节点)中,每个inode
都有自己的编号。每个文件各占用一个 inode
。不仅如此,inode 中还记录着文件数据所在 block 块的编号;block
中(数据块),类似衣柜的隔断,用来真正保存衣物。每个 block 都有属于自己的编号。当文件太大时,可能会占用多个 block 块。super block
(超级块)用于记录整个文件系统的整体信息,包括 inode 和 block 的总量、已经使用量和剩余量,以及文件系统的格式和相关信息等。inode
都有一个号码,操作系统用inode
号码来识别不同的文件。
inode本质上是一个结构体,定义如下
struct inode {
struct hlist_node i_hash; /* 哈希表 */
struct list_head i_list; /* 索引节点链表 */
struct list_head i_dentry; /* 目录项链表 */
unsigned long i_ino; /* 节点号 */
atomic_t i_count; /* 引用记数 */
umode_t i_mode; /* 访问权限控制 */
unsigned int i_nlink; /* 硬链接数 */
uid_t i_uid; /* 使用者id */
gid_t i_gid; /* 使用者id组 */
kdev_t i_rdev; /* 实设备标识符 */
loff_t i_size; /* 以字节为单位的文件大小 */
struct timespec i_atime; /* 最后访问时间 */
struct timespec i_mtime; /* 最后修改(modify)时间 */
struct timespec i_ctime; /* 最后改变(change)时间 */
unsigned int i_blkbits; /* 以位为单位的块大小 */
unsigned long i_blksize; /* 以字节为单位的块大小 */
unsigned long i_version; /* 版本号 */
unsigned long i_blocks; /* 文件的块数 */
unsigned short i_bytes; /* 使用的字节数 */
spinlock_t i_lock; /* 自旋锁 */
struct rw_semaphore i_alloc_sem; /* 索引节点信号量 */
struct inode_operations *i_op; /* 索引节点操作表 */
struct file_operations *i_fop; /* 默认的索引节点操作 */
struct super_block *i_sb; /* 相关的超级块 */
struct file_lock *i_flock; /* 文件锁链表 */
struct address_space *i_mapping; /* 相关的地址映射 */
struct address_space i_data; /* 设备地址映射 */
struct dquot *i_dquot[MAXQUOTAS]; /* 节点的磁盘限额 */
struct list_head i_devices; /* 块设备链表 */
struct pipe_inode_info *i_pipe; /* 管道信息 */
struct block_device *i_bdev; /* 块设备驱动 */
unsigned long i_dnotify_mask; /* 目录通知掩码 */
struct dnotify_struct *i_dnotify; /* 目录通知 */
unsigned long i_state; /* 状态标志 */
unsigned long dirtied_when; /* 首次修改时间 */
unsigned int i_flags; /* 文件系统标志 */
unsigned char i_sock; /* 可能是个套接字吧 */
atomic_t i_writecount; /* 写者记数 */
void *i_security; /* 安全模块 */
__u32 i_generation; /* 索引节点版本号 */
union {
void *generic_ip; /* 文件特殊信息 */
} u;
};
inode
号码是"一一对应"关系,每个inode号码对应一个文件名。Unix/Linux系统允许,多个文件名指向同一个inode
号码。
ln 源文件 目标文件
inode
号码虽然不一样,但是文件A的内容是文件B的路径。读取文件A时,系统会自动将访问者导向文件B。文件A就称为文件B的"软链接"(soft link)或者"符号链接(symbolic link)。软链接类似于Windows系统下的快捷方式。ln -s 源文件或目录 目标文件或目录
inode
号码,文件B的inode
号码也不会因为软链接的存在而增加。每个进程有独立的虚拟地址空间,进程访问的虚拟地址空间并不是真正的物理地址
虚拟地址可通过每个进程上页表与物理地址进行映射,获得真正的物理地址
如果虚拟地址所对应的物理地址不在物理内存中,则产生缺页中断,真正分配物理地址,同时更新进程的页表;如果此时物理内存已经耗尽,则根据内存替换算法淘汰部分页面至物理磁盘中。
查看内存状态命令:top
,vmstat
,free
MemTotal
:一般的是 **MemTotal = MemFree+MemUsed **, MemTotal
是去掉kenel
占用的剩下的
MemAvailable
:有些应用程序会根据系统的可用内存大小自动调整内存申请的多少,所以需要一个记录当前可用内存数量的统计值,因为MemFree不能代表全部可用的内存,系统中有些内存虽然已被使用但是可以回收的,比如cache/buffer
、slab
(内核分配的内存)都有一部分可以回收,所以这部分可回收的内存加上MemFree
才是系统可用的内存,即MemAvailable
Bounce:有些设备只能访问低端内存,比如16M以下的内存,当应用程序发出一个I/O
请求,DMA
的目的地址却是高端内存时(比如在16M以上),内核将在低端内存中分配一个临时buffer
作为跳转,把位于高端内存的缓存数据复制到此处。这种额外的数据拷贝被称为**bounce buffering
**,会降低I/O 性能。大量分配的bounce buffers 也会占用额外的内存。
Buffers:表示块设备(block device)
所占用的缓存页,包括:直接读写块设备、以及文件系统元数据(metadata)比如SuperBlock所使用的缓存页。它与“Cached”的区别在于,”Cached”表示普通文件所占用的缓存页。“Buffers”所占的内存同时也在LRU list中,被统计在Active(file)或Inactive(file)。
有些应用程序会根据系统的可用内存大小自动调整内存申请的多少,所以需要一个记录当前可用内存数量的统计值,MemFree并不适用,因为MemFree不能代表全部可用的内存,系统中有些内存虽然已被使用但是可以回收的,比如cache/buffer、slab都有一部分可以回收,所以这部分可回收的内存加上MemFree才是系统可用的内存,即MemAvailable。/proc/meminfo中的MemAvailable是内核使用特定的算法估算出来的,要注意这是一个估计值,并不精确。
作者:zjfclimin
链接:https://www.jianshu.com/p/391f42f8fb0d
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Linxu
管理内存的一种技术,它使得每个应用程序都认为自己拥有独立且连续完整的可用内存空间,而实际上,它通常是被映射到多个物理内存段,还有部分暂时存储在外部磁盘存储器上,在需要时再加载到内存中来。4G
,64位为2^64
,当然实际的物理内存大小可能远远小于虚拟地址空间的大小,注:虚拟地址空间大小并不等同于交换空间,交换空间只能算其中的一部分。
进程X 进程Y
+-------+ +-------+
| VPFN7 |--+ | VPFN7 |
+-------+ | 进程X的 进程Y的 +-------+
| VPFN6 | | Page Table Page Table +-| VPFN6 |
+-------+ | +------+ +------+ | +-------+
| VPFN5 | +----->| .... |---+ +-------| .... |<---+ | | VPFN5 |
+-------+ +------+ | +------+ | +------+ | | +-------+
| VPFN4 | +--->| .... |---+-+ | PFN4 | | | .... | | | | VPFN4 |
+-------+ | +------+ | | +------+ | +------+ | | +-------+
| VPFN3 |--+ | | .... | | | +--->| PFN3 |<---+ +----| .... |<---+--+ | VPFN3 |
+-------+ | | +------+ | | | +------+ | +------+ | +-------+
| VPFN2 | +-+--->| .... |---+-+-+ | PFN2 |<------+ | .... | | | VPFN2 |
+-------+ | +------+ | | +------+ +------+ | +-------+
| VPFN1 | | | +----->| FPN1 | +----| VPFN1 |
+-------+ | | +------+ +-------+
| VPFN0 |----+ +------->| PFN0 | | VPFN0 |
+-------+ +------+ +-------+
虚拟内存 物理内存 虚拟内存
PFN(the page frame number): 页编号
为了获取到实际的指令和数据,cpu需要借助进程的页表(page table)将虚拟地址转换为物理地址,
页表里面的数据由操作系统维护。
注意:linux内核代码访问内存用的都是实际的物理地址,不存在虚拟地址到物理地址的转换,只有应用程序才需要。
为了方便转换,Linux将虚拟内存和物理内存的page
都拆分为固定大小的页,一般是4k
,每个页都会分配一个唯一的编号,就是页编号PFN
。
虚拟内存和物理内存的page
之间通过page table进行映射。
page
都有对应的Page Table
相关联,只有虚拟地址被分配给进程后,也即进程调用类似malloc函数之后,系统才会为相应的虚拟地址在Page Table
中添加记录,如果进程访问一个没有和Page Table
关联的虚拟地址,系统将会抛出SIGSEGV
信号,导致进程退出,换句话说,虽然每个进程都有4G(32位系统)的虚拟地址空间,但只有向系统申请了的那些地址空间才能用,访问未分配的地址空间将会出segmentfault
错误。Linux会将虚拟地址0不映射到任何地方,这样我们访问空指针就一定会报segmentfault
错误。page table
可以简单的理解为一个memory mapping
的链表(当然实际结构很复杂),里面的每个memory mapping
都将一块虚拟地址映射到一个特定的资源(物理内存或者外部存储空间)。每个进程拥有自己的page table
,和其它进程的page table
没有关系。
Page Table
与Page Frame(页帧)
区分开,物理内存的最小单位是page frame
,每个物理页对应一个描述符(struct page),在内核的引导阶段就会分配好、保存在mem_map[]
数组中,mem_map[]
所占用的内存被统计在dmesg显示的reserved中,/proc/meminfo的MemTotal是不包含它们的。(在NUMA系统上可能会有多个mem_map数组,在node_data中或mem_section中)。memory mapping
就是对一段虚拟内存的描述,包括虚拟地址的起始位置,长度,权限(比如这段内存里的数据是否可读、写、执行), 以及关联的资源(如物理内存page,swap空间上的page,磁盘上的文件内容等)。
memory mapping
并将它放入page table
,但这时系统不一定会分配相应的物理内存,系统一般会在进程真正访问这段内存的时候才会分配物理内存并关联到相应的memory mapping,。每个memory mapping
都有一个标记,用来表示所关联的物理资源类型,一般分两大类,那就是anonymous
和file backed
。file backed
(有文件背景的页面):
offset
、rwx
权限等。当进程第一次访问对应的虚拟page的时候,由于在memory mapping
中找不到对应的物理内存,CPU会报page fault
中断,然后操作系统就会处理这个中断并将文件的内容加载到物理内存中,然后更新memory mapping
,这样下次CPU就能访问这块虚拟地址了。page cache
中。一般程序的可执行文件,动态库都是以这种方式映射到进程的虚拟地址空间的。read
也可以通过memory mapping
去读。当你通过任何一种方式从磁盘读文件时,内核都会给你申请一个page cache
,来缓存硬盘上的内容。这样的话,读过一遍的数据,本进程或其他进程下次再读的时候就直接从page cache里去拿,就很快了,提升系统的整体性能。因此用户的read/write
实际上是跟page cache的相互拷贝。
memory mapping
则会将一段虚拟地址(3G
)以下映射到page cache
上,这样的话,用户就可以通过读写这段虚拟地址来修改文件内容,省去了内核和用户之间的拷贝。anonymous
类型(匿名页): 程序自己用到的数据段和堆栈空间,以及通过mmap
分配的共享内存,它们在磁盘上找不到对应的文件,所以这部分内存页被叫做anonymous page
。比如用户进程通过malloc()
申请的内存页
anonymous page
和file backed
最大的差别是当内存吃紧时,系统会直接删除掉file backed
对应的物理内存,因为下次需要的时候还能从磁盘加载到内存,但anonymous page
不能被删除,只能被swap out
。swapping
换页,它们没有关联的文件进行回写,所以只能写入到交换区里。
swap cache
,可以把swap cache理解为交换区设备的”page cache
”:
page cache
对应的是一个个文件,swap cache
对应的是一个个交换区设备,kernel管理swap cache与管理page cache一样,用的都是radix-tree,page cache
与文件的对应关系在打开文件时就确定了,而一个匿名页只有在即将被swap-out
的时候才决定它会被放到哪一个交换区设备,即匿名页与swap cache
的对应关系在即将被swap-out时才确立。swap-out
时会先被放进swap cache
,但通常只存在很短暂的时间,因为紧接着在page out
完成之后它就会从`swap cache 中删除,swap-out
现在又被swap-in
的匿名页会在swap cache中,直到页面中的内容发生变化、或者原来用过的交换区空间被回收为止。shared
: 不同进程的page table
里面的多个memory mapping
可以映射到相同的物理地址,通过虚拟地址可以访问到相同的内容,当一个进程修改内存的内容后,在另一个进程可以立即读取到,这种方式一般用来实现进程间高速的共享数据。
shared
的memory mapping
被删除回收时,需要更新物理page上的引用计数,当物理page的计数变0后被回收。memory mapping
,接着执行写操作,linux很多功能都依赖于copy on write技术来提高性能,最常见的是fork。Swap
分区可以将不活跃的页交换到硬盘中,缓解内存紧张。
swap
,即第1点的磁盘和内存之间的交换。回收时机
有文件背景的数据实际上就是page cache,内核中有一个水位控制的机制,在系统内存不够用的时候,会触发页面回收。
对于没有文件背景的页面即匿名页,比如堆、栈、数据段,如果没有swap分区,不能与磁盘交换,就要常驻内存了。但是常驻内存的话,就会吃内存,可以通过给硬盘搞一个swap分区或硬盘中创建一个交换文件(swapfile)让匿名页也能交换到磁盘上。可认为是为匿名页伪造的文件背景。swap分区或swap文件实际上最终是到达了增大内存的效果。
内核通过kswapd
内核线程慢慢回收,回收的时机由水位控制。
水位(watermark)控制
内核中有三个水位:
kswapd
线程的内存回收。kernel
会直接在这个进程的进程上下文里面做内存回收(direct reclaim
)。回收的过程是依据LRU
,即最近最少使用的页会被回收,Linux内核一直在评估哪些是LRU的页面即最不活跃的页面。
root@none:~# cat /proc/meminfo
MemTotal: 254316 kB
MemFree: 185748 kB
Buffers: 6676 kB
Cached: 22716 kB
SwapCached: 0 kB
Active: 25472 kB <----
Inactive: 23164 kB <----
Active(anon): 19684 kB <----
Inactive(anon): 456 kB <----
Active(file): 5788 kB <----
Inactive(file): 22708 kB <----
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 0 kB
Writeback: 0 kB
AnonPages: 19272 kB
…… ……
Linux使用虚拟地址空间,大大增加了进程的寻址空间,由低地址到高地址分别是:
可以通过以下代码验证进程的地址空间分布,其中sbrk(0)函数用于返回栈顶指针。
#include
#include
#include
#include
int global_num = 0;
char global_str_arr[65536] = { 'a' };
int main(int argc, char** argv)
{
char* heap_var = NULL;
int local_var = 0;
printf("Address of function main 0x%lx\n", main);
printf("Address of global_num 0x%lx\n", &global_num);
printf("Address of global_str_arr 0x%lx ~ 0x%lx\n", &global_str_arr[0], &global_str_arr[65535]);
printf("Top of stack is 0x%lx\n", &local_var);
printf("Top of heap is 0x%lx\n", sbrk(0));
heap_var = malloc(sizeof(char)* 127 * 1024);
printf("Address of heap_var is 0x%lx\n", heap_var);
printf("Top of heap after malloc is 0x%lx\n", sbrk(0));
free(heap_var);
heap_var = NULL;
printf("Top of heap after free is 0x%lx\n", sbrk(0));
return 1;
}
32位系统的结果如下,
Address of function main 0x8048474
Address of global_num 0x8059904
Address of global_str_arr 0x8049900 ~ 0x80598ff
Top of stack is 0xbfd0886c
Top of heap is 0x805a000
Address of heap_var is 0x805a008
Top of heap after malloc is 0x809a000
Top of heap after free is 0x807b000
64位系统的结果400594
Address of global_num 0x610b90
Address of global_str_arr 0x600b80 ~ 0x610b7f
Top of stack is 0x7fff2e9e4994
Top of heap is 0x8f5000
Address of heap_var is 0x8f5010
Top of heap after malloc is 0x935000
Top of heap after free is 0x916000
malloc
是glibc
中的内存分配函数,也是最常用的动态内存分配函数,其内存必须通过free
进行释放,否则导致内存泄漏。关于malloc
获得虚拟空间的表现,与glibc的版本有关,但大体逻辑上:
sbrk()
就是修改栈顶指针位置,而mmap
可用于生成文件的映射以及修改匿名页面的内存,这里指的是匿名页面。而这个128K,是glibc
的默认配置,可通过函数mallopt
来设置,可通过以下例子来说明:
#include
#include
#include
#include
#include
#include
void print_info(
char* var_name,
char* var_ptr,
size_t size_in_kb
)
{
printf("Address of %s(%luk) 0x%lx, now heap top is 0x%lx\n",
var_name, size_in_kb, var_ptr, sbrk(0));
}
int main(int argc, char** argv)
{
char *heap_var1, *heap_var2, *heap_var3 ;
char *mmap_var1, *mmap_var2, *mmap_var3 ;
char *maybe_mmap_var;
printf("Orginal heap top is 0x%lx\n", sbrk(0));
heap_var1 = malloc(32*1024);
print_info("heap_var1", heap_var1, 32);
heap_var2 = malloc(64*1024);
print_info("heap_var2", heap_var2, 64);
heap_var3 = malloc(127*1024);
print_info("heap_var3", heap_var3, 127);
printf("\n");
maybe_mmap_var = malloc(128*1024);
print_info("maybe_mmap_var", maybe_mmap_var, 128);
//mmap
mmap_var1 = malloc(128*1024);
print_info("mmap_var1", mmap_var1, 128);
// set M_MMAP_THRESHOLD to 64k
mallopt(M_MMAP_THRESHOLD, 64*1024);
printf("set M_MMAP_THRESHOLD to 64k\n");
mmap_var2 = malloc(64*1024);
print_info("mmap_var2", mmap_var2, 64);
mmap_var3 = malloc(127*1024);
print_info("mmap_var3", mmap_var3, 127);
return 1;
}
下面是 Linux 64 位机器的执行结果(后文所有例子都是通过 64 位机器上的测试结果):
Orginal heap top is 0x17da000
Address of heap_var1(32k) 0x17da010, now heap top is 0x1803000
Address of heap_var2(64k) 0x17e2020, now heap top is 0x1803000
Address of heap_var3(127k) 0x17f2030, now heap top is 0x1832000
Address of maybe_mmap_var(128k) 0x1811c40, now heap top is 0x1832000
Address of mmap_var1(128k) 0x7f4a0b1f2010, now heap top is 0x1832000
set M_MMAP_THRESHOLD to 64k
Address of mmap_var2(64k) 0x7f4a0b1e1010, now heap top is 0x1832000
Address of mmap_var3(127k) 0x7f4a0b1c1010, now heap top is 0x1832000
malloc分配的内存是虚拟地址空间,而虚拟地址空间和物理地址空间使用进程页表进行映射,那么分配了空间就是占用物理内存空间了吗?
首先,进程使用了多少内存可通过ps -aux
命令查看,其中关键的两个信息(第五,六列)为:
VSZ,virtual memory size,表示进程总共使用的虚拟地址空间大小,包括进程地址空间的代码段,数据段,堆,文件映射区域,栈,内核空间等所有虚拟地址使用的总和,单位为K。
RSS,resident set size,表示进程实际使用的物理空间大小,RSS总小于VSZ。
#include
#include
#include
#include
#include
#include
char ps_cmd[1024];
void print_info(
char* var_name,
char* var_ptr,
size_t size_in_kb
)
{
printf("Address of %s(%luk) 0x%lx, now heap top is 0x%lx\n",
var_name, size_in_kb, var_ptr, sbrk(0));
system(ps_cmd);
}
int main(int argc, char** argv)
{
char *non_set_var, *set_1k_var, *set_5k_var, *set_7k_var;
pid_t pid;
pid = getpid();
sprintf(ps_cmd, "ps aux | grep %lu | grep -v grep", pid);
non_set_var = malloc(32*1024);
print_info("non_set_var", non_set_var, 32);
set_1k_var = malloc(64*1024);
memset(set_1k_var, 0, 1024);
print_info("set_1k_var", set_1k_var, 64);
set_5k_var = malloc(127*1024);
memset(set_5k_var, 0, 5*1024);
print_info("set_5k_var", set_5k_var, 127);
set_7k_var = malloc(64*1024);
memset(set_1k_var, 0, 7*1024);
print_info("set_7k_var", set_7k_var, 64);
return 1;
}
执行结果为:
Address of non_set_var(32k) 0x502010, now heap top is 0x52b000
mysql 12183 0.0 0.0 2692 452 pts/3 S+ 20:29 0:00 ./test_vsz
Address of set_1k_var(64k) 0x50a020, now heap top is 0x52b000
mysql 12183 0.0 0.0 2692 456 pts/3 S+ 20:29 0:00 ./test_vsz
Address of set_5k_var(127k) 0x51a030, now heap top is 0x55a000
mysql 12183 0.0 0.0 2880 464 pts/3 S+ 20:29 0:00 ./test_vsz
Address of set_7k_var(64k) 0x539c40, now heap top is 0x55a000
mysql 12183 0.0 0.0 2880 472 pts/3 S+ 20:29 0:00 ./test_vsz
由以上结果可知:
因此,不是malloc后马上占用实际内存,而是第一次使用时发现虚存对应的物理页面未分配,产生缺页中断,才真正分配物理页面,同时更新进程页表的映射关系。这也是Linux虚拟内存管理的核心概念之一。
Linux提供了pmap
命令来查看这些信息,通常使用pmap -d pid
(高版本可提供pmap -x pid
)查询,如下所示:
mysql@ TLOG_590_591:~/vin/test_memory> pmap -d 17867
17867: test_mmap
START SIZE RSS DIRTY PERM OFFSET DEVICE MAPPING
00400000 8K 4K 0K r-xp 00000000 08:01 /home/mysql/vin/test_memory/test_mmap
00501000 68K 8K 8K rw-p 00001000 08:01 /home/mysql/vin/test_memory/test_mmap
00512000 76K 0K 0K rw-p 00512000 00:00 [heap]
0053e000 256K 0K 0K rw-p 0053e000 00:00 [anon]
2b3428f97000 108K 92K 0K r-xp 00000000 08:01 /lib64/ld-2.4.so
2b3428fb2000 8K 8K 8K rw-p 2b3428fb2000 00:00 [anon]
2b3428fc1000 4K 4K 4K rw-p 2b3428fc1000 00:00 [anon]
2b34290b1000 8K 8K 8K rw-p 0001a000 08:01 /lib64/ld-2.4.so
2b34290b3000 1240K 248K 0K r-xp 00000000 08:01 /lib64/libc-2.4.so
2b34291e9000 1024K 0K 0K ---p 00136000 08:01 /lib64/libc-2.4.so
2b34292e9000 12K 12K 12K r--p 00136000 08:01 /lib64/libc-2.4.so
2b34292ec000 8K 8K 8K rw-p 00139000 08:01 /lib64/libc-2.4.so
2b34292ee000 1048K 36K 36K rw-p 2b34292ee000 00:00 [anon]
7fff81afe000 84K 12K 12K rw-p 7fff81afe000 00:00 [stack]
ffffffffff600000 8192K 0K 0K ---p 00000000 00:00 [vdso]
Total: 12144K 440K 96K
malloc使用mmap分配的内存(大于128K),free会调用unmap系统调用马上还给OS,实现真正释放。
堆内的内存,只有释放堆顶的空间,同时堆顶总连续空间大于128K才使用sbrk(-SIZE)回收内存,真正归还OS。
堆内的空闲空间,是不会归还给OS的。
mmap
分配的内存可以通过unmap
进行free
,实现真正释放。既然堆内碎片不能直接释放,导致疑似“内存泄漏”问题,sbrk
/mmap
/unmap
都是系统调用,频繁的系统调用都比较消耗系统资源。并且,mmap
申请的内存被unmap
后,重新申请会产生更多的缺页中断。缺页中断属于内核行为,会导致内核态 CPU 消耗较大。ps -o majflt,minflt -C
ps -o majflt,minflt -p
glibc
提供了以下结构和接口来查看堆内内存和 mmap 的使用情况:
struct mallinfo {
int arena; /* non-mmapped space allocated from system */
int ordblks; /* number of free chunks */
int smblks; /* number of fastbin blocks */
int hblks; /* number of mmapped regions */
int hblkhd; /* space in mmapped regions */
int usmblks; /* maximum total allocated space */
int fsmblks; /* space available in freed fastbin blocks */
int uordblks; /* total allocated space */
int fordblks; /* total free space */
int keepcost; /* top-most, releasable (via malloc_trim) space */
};
/* 返回 heap(main_arena) 的内存使用情况,以 mallinfo 结构返回 */
struct mallinfo mallinfo();
/* 将 heap 和 mmap 的使用情况输出到 stderr */
void malloc_stats();
可通过以下例子来验证 mallinfo 和 malloc_stats 输出结果:
#include
#include
#include
#include
#include
#include
size_t heap_malloc_total, heap_free_total,
mmap_total, mmap_count;
void print_info()
{
struct mallinfo mi = mallinfo();
printf("count by itself:\n");
printf("\theap_malloc_total=%lu heap_free_total=%lu heap_in_use=%lu\n\
\tmmap_total=%lu mmap_count=%lu\n",
heap_malloc_total*1024, heap_free_total*1024, heap_malloc_total*1024 - heap_free_total*1024,
mmap_total*1024, mmap_count);
printf("count by mallinfo:\n");
printf("\theap_malloc_total=%lu heap_free_total=%lu heap_in_use=%lu\n\
\tmmap_total=%lu mmap_count=%lu\n",
mi.arena, mi.fordblks, mi.uordblks,
mi.hblkhd, mi.hblks);
printf("from malloc_stats:\n");
malloc_stats();
}
#define ARRAY_SIZE 200
int main(int argc, char** argv)
{
char** ptr_arr[ARRAY_SIZE];
int i;
for( i = 0; i < ARRAY_SIZE; i++) {
ptr_arr[i] = malloc(i * 1024);
if ( i < 128)
heap_malloc_total += i;
else {
mmap_total += i;
mmap_count++;
}
}
print_info();
for( i = 0; i < ARRAY_SIZE; i++) {
if ( i % 2 == 0)
continue;
free(ptr_arr[i]);
if ( i < 128)
heap_free_total += i;
else {
mmap_total -= i;
mmap_count--;
}
}
printf("\nafter free\n");
print_info();
return 1;
}
下面是一个执行结果:
count by itself:
heap_malloc_total=8323072 heap_free_total=0 heap_in_use=8323072
mmap_total=12054528 mmap_count=72
count by mallinfo:
heap_malloc_total=8327168 heap_free_total=2032 heap_in_use=8325136
mmap_total=12238848 mmap_count=72
from malloc_stats:
Arena 0:
system bytes = 8327168
in use bytes = 8325136
Total (incl. mmap):
system bytes = 20566016
in use bytes = 20563984
max mmap regions = 72
max mmap bytes = 12238848
after free
count by itself:
heap_malloc_total=8323072 heap_free_total=4194304 heap_in_use=4128768
mmap_total=6008832 mmap_count=36
count by mallinfo:
heap_malloc_total=8327168 heap_free_total=4197360 heap_in_use=4129808
mmap_total=6119424 mmap_count=36
from malloc_stats:
Arena 0:
system bytes = 8327168
in use bytes = 4129808
Total (incl. mmap):
system bytes = 14446592
in use bytes = 10249232
max mmap regions = 72
max mmap bytes = 12238848
heap_free_total
表示堆内已释放的内存碎片总和。mallinfo
结构中的fsmblks
、smblks
、 ordblks
值得到,这些值表示不同大小区间的碎片总个数,这些区间分别是0~80
字节, 80~512
字节, 512~128
k 。fsmblks
、 smblks
的值过大,那碎片问题可能比较严重了。mallinfo
结构有一个很致命的问题,就是其成员定义全部都是 int
,在 64 位环境中,其结构中的 uordblks/fordblks/arena/usmblks
很容易就会导致溢出。