七、内存

1、虚拟内存、物理内存、半导体内存和换页文件

虚拟内存:地址空间,虚拟的存储区域,应用程序所访问的都是虚拟内存。
物理内存:存储空间,实际的存储区域,只有系统内核可以访问物理内存。
虚拟内存和物理内存之间存在对应关系,当应用程序访问虚拟内存时,系统内核会依据这种对应关系找到与之相应的物理内存。上述对应关系存储在内核中的内存映射表中。
物理内存包括半导体内存和换页文件两部分。
当半导体内存不够用时,可以把一些长期闲置的代码和数据从半导体内存中缓存到换页文件中,这叫页面换出,一旦需要使用被换出的代码和数据,再把它们从换页文件恢复到半导体内存中,这叫页面换入。因此,系统中的虚拟内存比半导体内存大得多。

2、进程映射(Process Maps)

每个进程都拥有独立的4G字节的虚拟内存,分别被映射到不同的物理内存区域。
内存映射和换入换出都是以页为单位,1页=4096字节。
4G虚拟内存中高地址的1G被映射到内核的代码和数据区,这1个G在各个进程间共享。用户的应用程序只能直接访问低地址的3个G虚拟内存,因此该区域被称为用户空间,而高地址的1个G虚拟内存则被称为内核空间。用户空间中的代码只能直接访问用户空间的数据,如果要想访问内核空间中的代码和数据必须借助专门的系统调用完成。
用户空间的3G虚拟内存可以进一步被划分为如下区域:
------------------
系统内核(1G)
高地址------------------
命令行参数
和环境变量
------------------
栈区:非静态局部变量
- - - - - - - -
v
3G
^
- - - - - - - -
堆区:动态内存分配(malloc函数族)
-----------------
BSS区:无初值的全局和静态局部变量
-----------------
数据区:非const型有初值的全局和静态局部变量
-----------------
只读常量:字面值常量,const型有初值的全局
和静态局部变量
代码区(正文段):可执行指令
低地址-----------------
代码:maps.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
const int const_global = 10; // 常全局变量
int init_global = 10; // 初始化全局变量
int uninit_global; // 未初始化全局变量
int main(int argc, char* argv[]) {// 常静态变量const static int const_static = 10;// 初始化静态变量static int init_static = 10;// 未初始化静态变量static int uninit_static;// 常局部变量const int const_local = 10;int prev_local; // 前局部变量int next_local; // 后局部变量// 前堆变量int* prev_heap = malloc(sizeof(int));// 后堆变量int* next_heap = malloc(sizeof(int));// 字面值常量char const* literal = "literal";extern char** environ; // 环境变量printf("--------------高地址 ------------\n");printf("---- 命令行参数与环境变量 ----\n");printf("         环境变量:%p\n", environ);printf("       命令行参数:%p\n", argv);printf("-------------- 栈 ------------\n");printf("       后局部变量:%p\n",&next_local);printf("       前局部变量:%p\n", &prev_local);printf("       常局部变量:%p\n", &const_local);printf("-------------- 堆 ------------\n");printf("         后堆变量:%p\n", next_heap);printf("         前堆变量:%p\n", prev_heap);printf("------------- BSS ------------\n");printf(" 未初始化全局变量:%p\n",&uninit_global);printf(" 未初始化静态变量:%p\n",&uninit_static);printf("------------ 数据 ------------\n");printf("   初始化静态变量:%p\n", &init_static);printf("   初始化全局变量:%p\n", &init_global);printf("------------ 代码 ------------\n");printf("       常静态变量:%p\n",&const_static);printf("       字面值常量:%p\n", literal);printf("       常全局变量:%p\n",&const_global);printf("             函数:%p\n",main);printf("---------------低地址---------------\n");return 0;
}

命令:size
通过size命令查看一个可执行程序的代码区、数据区和BSS区的大小。
每个进程的用户空间都拥有独立的从虚拟内存到物理内存的映射,为之进程间的内存壁垒。
代码:vm.c

#include <stdio.h>
int g_vm = 0;
int main(void) {printf("虚拟内存地址:%p\n", &g_vm);printf("输入一个整数:");scanf("%d%*c", &g_vm);//%*c的意思是清理丢弃回车printf("启动另一个进程,输入不同数据,""按<回车>键继续...");getchar();printf("虚拟内存数据:%d\n", g_vm);return 0;
}

代码:vm2.c

#include <stdio.h>
int g_vm = 0;
int main(void) {printf("虚拟内存地址:%p\n", &g_vm);printf("输入一个整数:");scanf("%d%*c", &g_vm);//%*c的意思是清理丢弃回车printf("启动另一个进程,输入不同数据,""按<回车>键继续...");getchar();printf("虚拟内存数据:%d\n", g_vm);return 0;
}

3、内存的分配与释放

malloc/calloc/realloc/free
|
v
brk/sbrk
|
v
mmap/munmap
|
v
kmalloc/kfree
以增加方式分配或释放虚拟内存
分配:映射+占有
| ____________
在地址空间(虚拟内存)和
存储空间(物理内存)之间 指定内存空
建立映射关系 间的归属性
释放:放弃占有+解除映射
| |
解除对内存空 消除地址空间(虚拟内存)和存储
间的归属约束 空间(物理内存)之间的映射关系
#include <unistd.h>
void* sbrk(intptr_t increment);
堆顶->- - - - - - - -
堆区
-----------------
sbrk(10)
堆顶->- - - - - - - -
10字节
- - - - - - - -<-返回值
堆区
-----------------
sbrk(-10)
- - - - - - - -<-返回值
10字节
堆顶->- - - - - - - -
堆区
-----------------
成功返回调用该函数之前的堆顶指针,失败返回-1。
increment
>0 - 堆顶指针上移,增大堆空间,分配虚拟内存
<0 - 堆顶指针下移,缩小堆空间,释放虚拟内存
=0 - 不分配也不释放虚拟内存,仅仅返回当前堆顶指针
系统内核维护一个指针,指向堆内存的顶端,即有效堆内存中最后一个字节的下一个位置。sbrk函数根据增量参数increment调整该指针的位置,同时返回该指针原来的位置,期间若发生内存耗尽或空闲,则自动追加或取消相应内存页的映射。
123____~~~~~~~~…
^ ^ ^ ^
堆顶
代码:sbrk.c

#include <stdio.h>
#include <unistd.h>
int main(void) {setbuf(stdout, NULL);int* p1 = (int*)sbrk(sizeof(int));if (p1 == (int*)-1) {perror("sbrk");return -1;}*p1 = 0;printf("%d\n", *p1);double* p2 = (double*)sbrk(sizeof(double));if (p2 == (double*)-1) {perror("sbrk");return -1;}*p2 = 1.2;printf("%g\n", *p2);char* p3 = (char*)sbrk(256 * sizeof(char));if (p3 == (char*)-1) {perror("sbrk");return -1;}sprintf(p3, "Hello, World!");printf("%s\n", p3);/*if (sbrk(-(256 * sizeof(char) +sizeof(double) +sizeof(int))) == (void*)-1) {perror("sbrk");return -1;}*/if (brk(p1) == -1) {perror("brk");return -1;}return 0;

以绝对地址的方式分配或释放虚拟内存
int brk(void* end_data_segment);
成功返回0,失败返回-1。
end_data_segment
>当前堆顶,分配虚拟内存
<当前堆顶,释放虚拟内存
=当前堆顶,空操作
堆顶->- - - - - - - -<-void* p = sbrk(0);
堆区
-----------------
brk(p+10)
- - - - - - - -<-p+10
10字节
堆顶->- - - - - - - -
堆区
-----------------
brk§
堆顶->- - - - - - - -<-p
堆区
-----------------
系统内核维护一个指针,指向当前堆顶,brk函数根据指针参数end_data_segment设置堆顶的新位置,期间若发生内存耗尽或空闲,则自动追加或取消相应内存页的映射。
代码:brk.c

#include <stdio.h>
#include <unistd.h>
/** xxxIIIIDDDDDDDDCC...C*    ^   ^       ^     ^*    p1  p2      p3*/
int main(void) {setbuf(stdout, NULL);//setbuf函数用于打开和关闭缓冲机制,因为没遇到换行也//没有填满缓冲区,所以会导致printf()打印不出来,//将标准输出设置为不带缓冲的.printf()也有缓冲区,//它的缓冲区也在堆内分布。int* p1 = (int*)sbrk(0);if (p1 == (int*)-1) {perror("sbrk");return -1;}double* p2 = (double*)(p1 + 1);if (brk(p2) == -1) {perror("brk");return -1;}*p1 = 0;printf("%d\n", *p1);char* p3 = (char*)(p2 + 1);if (brk(p3) == -1) {perror("brk");return -1;}*p2 = 1.2;printf("%g\n", *p2);void* p4 = p3 + 256;if (brk(p4) == -1) {perror("brk");return -1;}sprintf(p3, "Hello, World!");printf("%s\n", p3);if (brk(p1) == -1) {perror("brk");return -1;}return 0;
}

void* mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset);

建立虚拟内存到物理内存或文件的映射
#include <sys/mman.h>
void* mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset);
成功返回映射区虚拟内存的起始地址,失败返回MAP_FAILED(void类型的-1)。
start - 映射区虚拟内存的起始地址,NULL表示自动选择
length - 映射区的字节数,自动按页取整
prot - 访问权限,可取以下值:
PROT_READ - 可读
PROT_WRITE - 可写
PROT_EXEC - 可执行
PROT_NONE - 不可访问
flags - 映射标志,可取以下值:
MAP_ANONYMOUS - 匿名映射,将虚拟内存映射到物理内存,函数的最后两个参数fd和offset被忽略
MAP_PRIVATE - 私有映射,将虚拟内存映射到文件的内存缓冲区中而非磁盘文件
MAP_SHARED - 共享映射,将虚拟内存映射到磁盘文件中
MAP_DENYWRITE - 拒写映射,文件中被映射区域不能存在其它写入操作
MAP_FIXED - 固定映射,若在start上无法创建映射,则失败(无此标志系统会自动调整)
MAP_LOCKED - 锁定映射,禁止被换出到换页文件
fd - 文件描述符
offset - 文件偏移量,自动按页对齐
解除虚拟内存到物理内存或文件的映射
int munmap(void
start, size_t length);
成功返回0,失败返回-1。
start - 映射区的起始地址
length - 映射区的字节数
代码:mmap.c

#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
int main(void) {char* psz = mmap(NULL, 8192,PROT_READ | PROT_WRITE,MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);if (psz == MAP_FAILED) {perror("mmap");return -1;}sprintf(psz, "第一页");sprintf(psz + 4096, "第二页");printf("%s\n", psz);printf("%s\n", psz + 4096);if (munmap(psz, 4096) == -1) {perror("munmap");return -1;}//printf("%s\n", psz);printf("%s\n", psz + 4096);if (munmap(psz + 4096, 4096) == -1) {perror("munmap");return -1;}return 0;
}

八、系统调用

应用程序--------------+
vi/emacs/gftp/firefox |
| |
标准库、第三方库 |
C/C++/Qt/X11 |
| |
系统调用<------------+
brk/sbrk/mmap/munmap
1.Linux系统内核提供了一套用于实现各种系统功能的子程序,谓之系统调用。程序编写者可以象调用普通C语言函数一样调用这些系统调用函数,以访问系统内核提供的各种服务。
2.系统调用函数在形式上与普通C语言函数并无差别。二者的不同之处在于,前者工作在内核态,而后者工作在用户态。
3.在Intel的CPU上运行代码分为四个安全级别:Ring0、Ring1、Ring2和Ring3。Linux系统只使用了Ring0和Ring3。用户代码工作在Ring3级,而内核代码工作在Ring0级。一般而言用户代码无法访问Ring0级的资源,除非借助系统调用,使用户代码得以进入Ring0级,使用系统内核提供的功能。
4.系统内核内部维护一张全局表sys_call_table,表中的每个条目记录着每个系统调用在内核代码中的实现入口地址。
5.当用户代码调用某个系统调用函数时,该函数会先将参数压入堆栈,将系统调用标识存入eax寄存器,然后通过int 80h指令触发80h中断。
6.这时程序便从用户态(Ring3)进入内核态(Ring0)。
7.工作系统内核中的中断处理函数被调用,80h中断的处理函数名为system_call,该函数先从堆栈中取出参数,再从eax寄存器中取出系统调用标识,然后再从sys_call_table表中找到与该系统调用标识相对应的实现代码入口地址,挈其参数调用该实现,并将处理结果逐层返回到用户代码中。

Linux 网络编程笔记3 | 内存 系统调用相关推荐

  1. linux sockaddr结构体,linux网络编程笔记 sockaddr_in结构体[转]

    struct sockaddr { unsigned short sa_family; char sa_data[14]; }; 此数据结构用做bind.connect.recvfrom.sendto ...

  2. 【Linux网络编程笔记】TCP短连接产生大量TIME_WAIT导致无法对外建立新TCP连接的原因及解决方法—实践篇

    1. 查看系统网络配置和当前TCP状态         在定位并处理应用程序出现的网络问题时,了解系统默认网络配置是非常必要的.以x86_64平台Linux kernelversion 2.6.9的机 ...

  3. Linux网络编程笔记

    1.C/S 2.各函数: 网络字节序: 小端法:(pc本地存储)    高位存高地址.地位存低地址.    int a = 0x12345678 大端法:(网络存储)    高位存低地址.地位存高地址 ...

  4. Linux网络编程笔记 - 05 地址转换函数 32位整数,转换为点分十进制

    #include <arpa/inet.h> const char *inet_ntop(int af, const void *src,char *dst, socklen_t size ...

  5. [Linux网络编程学习笔记]索引

    一.Linux基本知识 [学习笔记]Linux平台的文件I/O操作 [学习笔记]Linux平台的文件,目录及操作 [Linux学习笔记]标准输入输出 [Linux学习笔记]进程概念及控制 [Linux ...

  6. Linux网络编程——黑马程序员笔记

    01P-复习-Linux网络编程 02P-信号量生产者复习 03P-协议 协议: 一组规则. 04P-7层模型和4层模型及代表协议 分层模型结构: OSI七层模型: 物.数.网.传.会.表.应TCP/ ...

  7. Linux网络编程——千峰物联网笔记

    B站视频:千峰物联网学科linux网络编程 网址:https://www.bilibili.com/video/BV1RJ411B761?p=1 目录 第一章:计算机网络概述 1.1计算机网络发展简史 ...

  8. 编程开发:Linux网络编程学习笔记

    非常全面.通俗易懂.值得借鉴的Linux网络编程学习笔记.关键字:linux linux编程 网络编程 linux网络编程 下载地址:点我下载 特别说明:本资源收集于网络,版权归原作者及版权商所有,仅 ...

  9. Linux网络编程学习笔记

    声明:1.未经过原作者许可,不可用于商业行为:2.本笔记仅用于知识学习,如有侵权,立即删除. 1.学习链接 黑马程序员-Linux网络编程:https://www.bilibili.com/video ...

最新文章

  1. Composer使用
  2. 精华阅读第 10 期 |解开阿尔法狗(AlphaGo)人工智能的画皮
  3. java的知识点45——事务||测试时间处理(java.sql.date,time,timestamp)
  4. jar 包的认识与处理、jar 文件 war 文件以及 ear 文件
  5. python软件如何下载-python软件怎么样?实际的操作方法来了
  6. Mac出现启动问题怎么办
  7. 解决响应式布局border带来的麻烦
  8. ComponentOne 2016 年产品规划
  9. 【三维路径规划】基于matlab A_star算法机器人栅格地图三维路径规划【含Matlab源码 190期】
  10. CorelDRAWX4的VBA插件开发(十一)弹窗界面和一键导出图片
  11. 电子设计大赛-运算放大器
  12. python微信群管理开禁言_微信群主怎么禁言别人?微信群怎么让群员禁言?
  13. 天下一品茗介绍:小户赛茶叶的特点是什么
  14. linux用户密码原则,linux系统普通用户设置密码
  15. 2019.5.11 提高B组 T3 nssl-1322 清兵线
  16. css样式 向下补白,CSS尺寸与补白
  17. 【C语言程序设计】实验 2
  18. 手游运营,怎么做一份数据日报?
  19. 墨者学院-SQL注入漏洞测试(报错盲注)
  20. 面向对象之静态方法(static)和实例化方法的区别

热门文章

  1. 关键词怎么布局,什么样的内容会参与排名【超越竞争对手】
  2. Android原生TTS(TextToSpeech)无效问题
  3. [教程] 新手安装指南:一步一步在Windows安装苹果雪豹系统
  4. 调试IP地址和DNS——NetSetMan
  5. 高级软件工程-课程总结
  6. Redisson 延时队列 原理 详解
  7. 微信小程序开发及相关设置小结
  8. Cell Trends综述精选:人工智能在生物医学领域的应用
  9. Veriolg R'S'锁存器
  10. I/O接口和外部通信接口——嵌入式系统 GPIO/IIC/SPI/UART/USB/HDMI/RS-232/RS-485/CAN