赞
踩
一、进程映像
程序是存储在磁盘上的可执行文件(二进制文件、脚本文件)
当执行程序时,系统会自动将该文件加载到内存中,在内存中的分布情况称为进程映像
从低地址到高地址的分区:
text:代码段
data:数据段
bss:静态数据段
heap:堆
stack:栈,数组的话整体往下,成员往上
environ:环境变量表
argv:命令行参数
总结:
1、栈内存的增长方向受操作系统影响,大部分是从高地址向低地址增长,但也有些系统,例如Ubuntu就是从低地址向高地址增长*
2、如果是栈内存存储数据,数组元素的增长方向一定是从低地址向高地址
ps -aux 查看当前所有进程的详细信息,可以查看进程号,要程序一直运行,死循环
作业:打印出每个内存段中的数据所在的地址,然后与该进程的内存信息记录文件中的地址对比
/proc/进程号/maps
二、虚拟内存:
1、操作系统会为每个进程分配4G虚拟内存**8个1,4096MB
2、用户只能使用虚拟内存,不能直接使用物理内存
3、虚拟内存要与物理内存进行映射后才能被用户使用,虚拟内存经过内核到物理内存,如果使用了没有映射的虚拟内存(段错误)就会产生段错误
4、虚拟内存与物理内存的映射由操作系统(MMU)动态维护
arr[10],实际内存不一定连续
5、虚拟内存能让系统使用更安全,不会暴露真实的物理内存地址,另一方面,操作系统可以让进程使用比实际物理内存更大的地址空间
6、4G的虚拟内存地址分为两个部分
[0G~3G) 用户空间
[3G~4G) 内核空间
7、当进程/线程运行在用户空间时称进程处于用户态,当进程/线程运行在内核空间时称进程处于内核态
8、当进程处于内核态时,进程运行存储使用在内核空间,此时CPU可以发出任何指令,运行的代码不受任何限制,可以自由的访问任意有效的地址,也可以直接访问接口
9、当进程处于用户态时,进程运行存储在用户空间此时被执行的代码要受到CPU很多的检查,例如用户进程只能访问自己映射过的虚拟内存
10、所有进程的内核空间的代码数据都是映射在同一块物理内存中,由内核负责维护
11、用户空间的代码不能直接访问内核空间的代码和数据,可以通过系统调用(API调用系统接口函数)来切换到内核态间接的访问内核、与内核交换数据
映射虚拟内存与物理内存的函数:
sbrk/brk/mmap/munmap
关于malloc获区虚拟内存空间的底层实现,跟libc.so版本有关
大概逻辑:1、如果分配的内存小于128k时,调用sbrk、brk
2、如果大于128k时,调用mmap,munmap
注意:系统映射内存是以页(一页=4096字节)为最小单位的
注意:sbrk、brk底层共同维护一个映射位置指针,该指针指向映射过的内存的下一个位置,或者说是未映射的内存的第一个位置
sbrk:
void *sbrk(intptr_t increment);
sbrk(4)/sbrk(-4)
功能:通过increment参数调整映射位置指针的位置,既可以映射内存也可以取消映射
increment:按字节为单位
0 获取当前指针的位置
>0 映射内存
<0 取消映射
返回值:该指针映射前原来的位置指针
brk:
int brk(void *addr);
功能:直接使用addr地址修改映射位置指针的位置
addr:
>原来位置指针的位置 映射内存
<原来位置指针的位置 取消映射
返回值:成功0 失败-1
int *base=sbrk(0)
int ret=brk(base+1)
*base=100;
printf("%dret=%d\n",*base,ret)
addr取消映射:brk(arr);
注意:bbrk、brk都可以单独进行映射和取消映射的操作,但是一般习惯配合使用:sbrk负责记录映射前的位置+映射操作,brk负责取消整个映射
练习:计算出前1000个素数,尽可能不要浪费内存
注意mmap、munmap底层不维护任何东西
void *mmap(void *addr,size_t length,int prot,int flags,int fd,off_t offset);
功能:映射虚拟内存与物理内存
addr:指定要映射的虚拟内存首地址,可以手动指定,如果是NULL,则是系统自动指定
length:映射的长度,字节为单位
prot:映射后的权限
PROT_EXEC 执行权限
PROT_READ 读权限
PORT_WRITE 写权限
PORT_READ|PORT_WRITE 又有读又有写1 2 3
flags:映射方式标志
MAP_ANONYMOUS 映射到虚拟内存,而不是映射文件,会忽略fd和offset的参数
MAP_FIXED 如果手动提供的addr无法进行映射,默认情况下会映射一个正确的地址,但加了该标志,则不会调整,直接执行失败
MAP_SHARED 映射内存与文件内容后,如果修改内存中的数据,文件内容会随之改变
MAP_PRIVATE 映射内存与文件内容后,如果修改内存中的数据,文件内容不会随之改变,与MAP_SHARED互斥
fd:文件描述符,如果不映射则给0
offset:文件的偏移位置。不用给0即可
返回值:成功返回映射后的内存首地址,失败返回(void*)-1/0xffffffff
int munmap(void* addr,size_t length)
功能:取消映射
addr:要取消映射的首地址
length:取消的字节数
返回值:成功0,失败-1
注意:可以使用strace ./a.out可以大概的追踪代码底层的指向情况。sbrk底层调用了brk
总结:1、重点是理解Linux内存管理的机制(虚拟内存),而不是brk/sbrk/mmap/munmap的使用
2、brk/sbrk底层维护一个虚拟内存位置指针,通过移动该指针来建立映射关系和取消映射关系
3、mmap和munmap底层不维护任何东西,值返回一个映射后的虚拟内存地址
4、malloc和free底层调用的是sbrk/brk或者mmap/munmap
系统调用(系统API)
系统调用就是操作系统提供的一些功能以函数的形式给程序员使用,但是注意虽然格式很像标准C的函数,但是它们不是
标准C的内容,也不是真正的函数
Linux文件IO:
一切皆文件
UNIX/Linux系统把所有的服务程序、设备都抽象成文件看待并提供了一套简单而统一的系统接口,这部分系统接口就是所谓的
系统文件读写,简称系统IO
如果使用的是标准C库中的文件读写函数,简称为标准IO
文件的分类:
普通文件 -:包括纯文本文件、二进制文件、压缩文件
目录文件 d:必须有x权限才能进入目录
块设备文件 b:用于存储大块数据的设备,例如硬盘
字符设备文件 c:例如鼠标,键盘
管道文件 p:有名管道为主,FIFO,队列
链接文件 l:类似于Windows的快捷方式
Socket文件 s:通常用于网络设备之间数据的连接交互
文件操作相关的系统调用:
文件操作相关的系统调用:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
功能:打开文件
pathname:文件的路径
flags:文件的打开方式
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写
O_APPEND 追加
O_CREAT 文件不存在则创建
O_EXCL 文件存在,配合O_CREAT则失败
O_TRUNC 文件存在则清空打开
返回值:文件描述符,成功返回一个非负整数,类似FILE*,代表了打开后的文件
int open(const char *pathname,int flags,mode_t mode);
功能:创建文件
flags:必须为O_CREAT, mode参数就需要给
mode:
S_IRWXU 00700 用户 读写执行权限
S_IRUSR 00400 读权限
S_IWUSR 00200 写权限
S_IXUSR 00100 执行权限
S_IRWXG 00070 组 读写执行权限
S_IRGRP 00040 读权限
S_IWGRP 00020 写权限
S_IXGRP 00010 执行
S_IRWXO 00007 其他用户 读写执行权限
S_IROTH 00004 读权限
S_IWOTH 00002 写
S_IXOTH 00001 执行
注意:可以直接使用八进制数表示三组权限,例如
0644 0664 0775
返回值:文件描述符,成功返回一个非负整数,类似FILE*,代表了打开后的文件
int creat(const char *pathname, mode_t mode);
功能:创建文件
mode:同上
试验:fopen的各种打开方式的open底层调用的参数分别是什么
strace ./a.out
ssize_t write(int fd, const void *buf, size_t count);
功能:把内存中的数据写入文件中
fd:文件描述符,open的返回值
buf:待写入的内存首地址
count:要写入的字节数
返回值:成功写入的字节数
ssize_t read(int fd, void *buf, size_t count);
功能:从文件中读取数据到内存中
fd:文件描述符,open的返回值
buf:存储数据的内存首地址
count:想要读取的字节数
返回值:实际读取到的字节数
int close(int fd);
功能:关闭文件
注意:write、read是fwrite和fread的底层调用
练习2:分别使用标准IO和系统IO写入一百万个整数到文件中,测试它们的速度谁更快?time ./a.out
结论:正常情况下,标准IO比系统IO的速度更快
原因:标准IO中有缓冲区机制,在写数据时不是每次都调用系统IO,而是把写入的数据存储在临时的缓冲区中,当缓冲区满时,才会调用一次系统IO写入数据到文件中
而且直接使用系统IO时会频繁地切换内核态和用户态,非常耗时
如果给系统IO增加缓冲区,它的速度一定会比标准IO快
int buf[1024]
随机读写:
系统IO下,每个打开的文件都有一个读写的位置指针,记录了从哪个字节开始进行读写文件,并且对文件进行读写操作时,它会自动往后移动
当想要在任意位置进行读写文件时,可以通过改变该位置指针的位置来进行随机读写
//标准IO
fseek
返回值:成功0 失败-1 借助ftell
//系统IO
off_t lseek(int fd, off_t offset, int whence);
offset:偏移值 字节为单位
whence: 基础位置
SEEK_SET 文件开头
SEEK_CUR 当前位置
SEEK_END 文件末尾
返回值:调整后位置指针所在文件的第几个字节
注意:当文件位置指针越过末尾后再写入数据时,越过的地方会形成"黑洞",该段"黑洞"会计算入文件大小中,但是不占用磁盘空间
作业:
使用系统IO实现一个带覆盖提醒的cp命令
./a.out dest src
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。