代码段:代码段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存中的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作——它是不可写的。
数据段:数据段用来存放可执行文件中已初始化全局变量,换句话说就是存放程序静态分配的变量和全局变量。
BSS段:BSS段包含了程序中未初始化的全局变量,在内存中 bss段全部置零。
堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)
栈:栈是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区
用一个小例子来展示上面所讲的各种内存区的差别与位置
#include<stdio.h>
#include<malloc.h>
#include<unistd.h>
int bss_var;
int data_var0=1;
int main(int argc,char **argv)
{
printf("below are addresses of types of process‘s mem\n");
printf("Text location:\n");
printf("\tAddress of main(Code Segment):%p\n",main);
printf("____________________________\n");
int stack_var0=2;
printf("Stack Location:\n");
printf("\tInitial end of stack:%p\n",&stack_var0);
int stack_var1=3;
printf("\tnew end of stack:%p\n",&stack_var1);
printf("____________________________\n");
printf("Data Location:\n");
printf("\tAddress of data_var(Data Segment):%p\n",&data_var0);
static int data_var1=4;
printf("\tNew end of data_var(Data Segment):%p\n",&data_var1);
printf("____________________________\n");
printf("BSS Location:\n");
printf("\tAddress of bss_var:%p\n",&bss_var);
printf("____________________________\n");
char *b = sbrk((ptrdiff_t)0);
printf("Heap Location:\n");
printf("\tInitial end of heap:%p\n",b);
brk(b+4);
b=sbrk((ptrdiff_t)0);
printf("\tNew end of heap:%p\n",b);
return 0;
}
得到的结果如下:
使用size命令查看:
Linux 提供了 glibc 库, 它封装了系统调用接口, 对上层更友好的提供服务, 系统调用最终都会通过 DO_CALL 发起, 这是一个宏定义, 其 32 位和 64 位的定义是不同的
32 位系统调用
用户态
- 将请求参数保存到寄存器
- 将系统调用名称转为系统调用号保存到寄存器 eax 中
- 通过软中断 ENTER_KERNEL 进入内核态
- 将用户态的寄存器保存到 pt_regs 中
- 在系统调用函数表 sys_call_table 中根据调用号找到对应的函数
- 执行函数实现, 将返回值写入 pt_regs 的 ax 位置
- 通过 INTERRUPT_RETURN 根据 pt_regs 恢复用户态进程
64 位系统调用
用户态
- 将请求参数保存到寄存器
- 将系统调用名称转为系统调用号保存到寄存器 rax 中
- 通过 syscall 进入内核态
- 将用户态的寄存器保存到 pt_regs 中
- 在系统调用函数表 sys_call_table 中根据调用号找到对应的函数
- 执行函数实现, 将返回值写入 pt_regs 的 ax 位置
- 通过 sysretq 返回用户态
- glibc 的 syscal.list 列出 glibc 函数对应的系统调用
- glibc 的脚本 make_syscall.sh 根据 syscal.list 生成对应的宏定义(函数映射到系统调用)
- glibc 的 syscal-template.S 使用这些宏, 定义了系统调用的调用方式(也是通过宏)
- 其中会调用 DO_CALL (也是一个宏), 32位与 64位实现不同
- 将调用参数放入寄存器中, 由系统调用名得到系统调用号, 放入 eax
- 执行 ENTER_KERNEL(一个宏), 对应 int $0x80 触发软中断, 进入内核
- 调用软中断处理函数 entry_INT80_32(内核启动时, 由 trap_init() 配置)
- entry_INT80_32 将用户态寄存器存入 pt_regs 中(保存现场以及系统调用参数), 调用 do_syscall_32_iraq_on
- do_syscall_32_iraq_on 从 pt_regs 中取系统调用号(eax), 从系统调用表得到对应实现函数, 取 pt_regs 中存储的参数, 调用系统调用
- entry_INT80_32 调用 INTERRUPT_RUTURN(一个宏)对应 iret 指令, 系统调用结果存在 pt_regs 的 eax 位置, 根据 pt_regs 恢复用户态进程
- 通过系统调用名得到系统调用号, 存入 rax; 不同中断, 执行 syscall 指令
- MSR(特殊模块寄存器), 辅助完成某些功能(包括系统调用)
- trap_init() 会调用 cpu_init->syscall_init 设置该寄存器
- syscall 从 MSR 寄存器中, 拿出函数地址进行调用, 即调用 entry_SYSCALL_64
- entry_SYSCALL_64 先保存用户态寄存器到 pt_regs 中
- 调用 entry_SYSCALL64_slow_pat->do_syscall_64
- do_syscall_64 从 rax 取系统调用号, 从系统调用表得到对应实现函数, 取 pt_regs 中存储的参数, 调用系统调用
- 返回执行 USERGS_SYSRET64(一个宏), 对应执行 swapgs 和 sysretq 指令; 系统调用结果存在 pt_regs 的 ax 位置, 根据 pt_regs 恢复用户态进程
- 32位 定义在 arch/x86/entry/syscalls/syscall_32.tbl
- 64位 定义在 arch/x86/entry/syscalls/syscall_64.tbl
- syscall_*.tbl 内容包括: 系统调用号, 系统调用名, 内核实现函数名(以 sys 开头)
- 内核实现函数的声明: include/linux/syscall.h
- 内核实现函数的实现: 某个 .c 文件, 例如 sys_open 的实现在 fs/open.c
- .c 文件中, 以宏的方式替代函数名, 用多层宏构建函数头
- 编译过程中, 通过 syscall_.tbl 生成 unistd_.h 文件
- unistd_*.h 包含系统调用与实现函数的对应关系
- syscall_.h include 了 unistd_.h 头文件, 并定义了系统调用表(数组)
硬件方面
- CPU性能指标,包括主频、外频、核心数等,CPU的架构设计,使用何种指令集等
- 内存性能指标,内存频率,读写速度,高速缓存大小,交换区大小等等
- I/O读写速度
软件方面
- 操作系统相关进程调度算法、I/O方式、内存分配机制、文件管理
- 编程语言、编译器、技术架构
- 代码本身
举例1
对于C程序员而言,永远要面临的一个问题就是内存泄漏,其本身就是影响程序性能的一个重要因素,下面是一个常见的内存泄漏情况
#include <stdio.h>
void getheap(int *p)//p是NULL的地址
{
p = malloc(sizeof(int) * 10); //p重新指向了分配在堆中的空间
}//形式参数int *p在栈空间内,函数结束后就释放了,malloc分配的空间也丢失了,同样也没有带回实参
int main()
{
int *p = NULL; //NULL就是(void *)0
printf("p=%p\n", p);//p是null的地址
printf("p=%p\n", &p);//&p是p本身的地址
getheap(p);//值传递,将NULL的地址传递给形参
p[0] = 10;
p[1] = 20;
printf("p[0]=%d,p[1]=%d\n",p[0],p[1]);
free(p);//p中不是堆中分配的空间的首地址,故free(p)也有问题
return 0;
对于更加复杂的程序,可以使用valgrind开源工具进行内存泄漏的检测
int main()
{
struct timeval tv;
int row = 6000;
int col = 200;
int arr[row][col];
for(int i = 0; i < row;i++) {
for(int j = 0; j < col;j++) {
arr[i][j] = 1;
}
}
gettimeofday(&tv,NULL);
int time1 = tv.tv_usec;
for(int i = 0; i < row;i++) {
for(int j = 0; j < col;j++) {
arr[i][j] = 2;
}
}
gettimeofday(&tv,NULL);
int time2 = tv.tv_usec;
for(int j = 0; j < col;j++) {
for(int i = 0; i < row;i++) {
arr[i][j] = 4;
}
}
gettimeofday(&tv,NULL);
int time3 = tv.tv_usec;
printf("row first time : %d\n",time2 - time1);
printf("col first time : %d\n",time3 - time2);
return 0;
}
结果如下:
这是由于数组在内存中是以行优先连续存储的,访问连续内存时cache命中率会更高,列优先遍历时会执行更多的内存读写操作和cache置换,而读写cache的速度比内存更快,因此行优先遍历性能要好于列优先。
原文:https://www.cnblogs.com/litianhao328/p/14753627.html