Linux环境编程day02--系统IO调用

内存管理

用户层
STL 智能指针/容器 自动分配、释放 调用C++
C++ new/delete 调用C
C malloc/free 调用POSIX\Linux
POSIX brk/sbrk 调用内核
Linux mmap/munmap 调用内核
系统层
Kernal kmalloc/vmalloc 调用驱动
dirver get_free_page

进程映像

程序存储在磁盘上的可执行文件(脚本、二进制),当执行程序时,系统会把可执行文件加载到内存形成进程,一个程序可以同时加载出多个进程
进程在内存中的分布情况就是进程映像,从地地址到高地址依次是:
text 代码段: 二进制指令、常量(数值、"字符串字面值"、被const修饰过的原data的数据)
只读,强制修改会段错误
data 数据段: 初始化过的全局变量、初始化过的静态局部变量
bss 静态数据段:未初始化过的全局变量、未初始化过的静态局部变量
程序运行前会自动清0
heap 堆: 程序员手动管理的大量数据,管理麻烦、申请和释放受控,与指针配合使用,使用不当可能会内存泄漏、内存碎片
stack 栈: 局部变量、块变量
大小有限、自动申请、释放
environ 环境变量表:所有的环境变量
每个进程都有一份,修改不会影响系统真正的环境变量的值
argv 命令行参数:
程序执行时附带的参数
查看maps:/proc/进程ID/maps
查看进程ID:
命令:ps -aux 函数:getpid()

虚拟内存

1、32位系统会给每个进程分配4G的虚拟内存空间

32操作系统的地址总线为32,最大可寻址2^32(2的32次方)个地址;
内存中一个存储单元为一个字节,即1byte或者8bit;
1 Gb =2^10 Mb = 2^20 Kb = 2^30 byte = 2^33 bit;
32位操作系统的地址总线最大可寻址能力为2^32 个地址,每个地址指向一个内存单元,每个内存单元大小为8bit

2^32 个地址可以对应到的内存大小为:

2^32 * 8 bit = ​​​​​​​2^35 bit = 4 Gb

多于4Gb的内存地址,32位操作系统就无法表示了。

2、进程、用户只能使用访问虚拟内存,无法直接使用真实的物理内存

3、虚拟内存只有与物理内存进行映射后才能使用,否则会产生段错误

4、虚拟内存与物理内存的映射和对应使用都是由操作系统动态维护

5、虚拟内存技术一方面是为了让系统更加安全,可以不暴露真实的物理内存地址给用户,又可以让一个进程出错后不影响其他进程和系统的运行,另一方面可让进程使用比实际物理内存更大的空间

6、4G的虚拟内存地址分成两个部分
[0~3G) 用户空间
[3G~4G) 内核空间

7、当进程\线程运行在内核空间时,称该进程\线程处于内核态,当进程\线程运行在用户空间时,称该进程\线程处于用户态

8、在内核态下,进程运行在内核空间,此时CPU可以执行任何指令,此时运行的代码不受任何限制,可以自由访问任何有效的地址

9、在用户态下,进程运行在用户空间,此时进程不能直接访问内核空间的数据,可以通过系统调用(API 系统接口函数)的方式切换到内核态,间接地访问内核空间的数据

10、对虚拟内存越界访问(访问没有映射过的虚拟内存),导致段错误

映射虚拟内存与物理内存的函数

sbrk/brk/mmap/munmap
关于 malloc 获取虚拟内存实际调用POSIX还是Linux提供的函数受到操作系统、编译器、字节数的影响,大致逻辑:
1、如果申请小于128Kb调用sbrk\brk
2、如果申请大于128Kb调用mmap\munmap
注意:strace ./a.out 可以追踪程序的底层调用(用户层)
注意:系统内存映射是以页(1页=4096字节)为单位的
注意:sbrk、brk底层维护一个映射位置指针,该指针指向虚拟内存中映射过的内存的最后一个字节的下一个位置,可以通过移动该指针来实现映射内存和取消映射的效果

void * sbrk(intptr_t increment);

功能:通过增量increment来调整映射位置指针的位置,从而进行映射或取消映射
increment:增量(字节为单位)
0 获取映射位置指针的位置
>0 映射内存
<0 取消映射
返回值:映射指针原来的位置

int brk(void * addr);

功能:通过修改映射指针指向addr的地址,从而进行映射或取消映射
addr:
> 原来位置 映射
< 原来位置 取消映射
返回值:成功返回0 失败返回-1

#include <stdio.h>
#include <unistd.h>
int main(int argc,const char* argv[])
{
    int* arr = sbrk(0);

    for(int i=0; i<10; i++)
    {
        sbrk(4);
        arr[i] = i;
        printf("%d\n",arr[i]);
    }
  brk(arr);//sbrk(-40);
}

总结:sbrk、brk都属于POSIX标准中的内存映射函数,都是可以单独进行映射、取消映射,但是配合使用最方便(sbrk映射、brk取消映射)
#include <sys/mman.h>

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

功能:映射虚拟内存与物理内存
addr:映射内存的起始地址,可以自己指定,如果赋NULL则由操作系统指定
length:要映射的字节数

prot:映射后的权限
PROT_EXEC 执行权限 PROT_EXEX | PROT_READ 可以通过或,添加多个权限
PROT_READ 读权限
PROT_WRITE 写权限
PROT_NONE 没有权限

flags:映射标志
MAP_FIXED 如果提供的addr无法映射,则直接失败
MAP_ANONYMOUS 指定映射虚拟内存,不映射文件,fd、offset失效
MAP_SHARED 对映射后的内存可以共享给其他进程
MAP_PRIVATE 对映射后的内存只能当前进程使用
注意:flags中必须在MAP_SHARED、MAP_PRIVATE之间二选一

fd:文件描述符 可以让文件映射到物理内存,不需要映射文件直接写0
offset:文件的偏移位置,从该位置开始映射文件
返回值:成功返回映射后的内存首地址,失败返回(void*)-1

int munmap(void *addr, size_t length);

功能:取消映射
addr:映射的内存首地址
length:取消映射的字节数
返回值:成功返回0 失败返回-1

#include <stdio.h>
#include <sys/mman.h>

int main(int argc,const char* argv[])
{
    int* ptr = mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,0,0);//映射虚拟内存与物理内存    
    if((void*)(-1) == (void*)ptr)
    {
        perror("mmap");
        return -1;
    }
    for(int i=0; i<1024; i++)
    {
        ptr[i] = i+1;
        printf("%d ",ptr[i]);
    }
    if(munmap(ptr,4096))//取消映射
    {
        perror("munmap");    
    }
    printf("success\n");
}
Linux内存管理总结

1、mmap\munmap底层不维护任何东西,直接在堆内存中选择合适的内存进行映射,返回映射成功后的内存首地址
2、sbrk\brk底层维护一个映射位置指针,该指针记录了通过sbrk\brk映射内存的末尾位置,通过改变该指针的位置来映射和取消映射
3、malloc\free底层调用了sbrk\brk 或者 mmap\munmap,虚拟内存必须与物理内存建立映射关系后才能使用
4、每个进程都有4G(32位)的虚拟内存空间
5、内存管理的重点是理解Linux对内存管理机制,而不是sbrk\brk\mmap\munmap函数的使用

系统调用(系统API、系统函数)

系统调用指的是由操作系统提供的一些功能给程序员调用,这些功能已经被封装成C语言函数的形式,但是它们不属于标准C的一部分,也不真正的函数
一般的应用进程工作运行在用户态(03G),使用系统调用时进程会转入到内核态(34G)
常用的C标准库函数的大部分时间都工作在用户态,当底层进行系统调用时会偶尔转入内核态工作
通过time命令查看进程在两种状态下的运行时间情况:
real 0m0.001s 总执行时间
user 0m0.001s 用户态执行时间
sys 0m0.000s 内核态执行时间
系统调用的代码是内核的一部分,其外部接口以C函数的形式存储在共享库中(linux-gate.so.1 、 /lib/ld-linux.so.2),当调用这些外部接口时进程会以软中断的方式进入内核态执行真正的系统调用函数

一切皆文件

Linux/UNIX操作系统把所有的服务、设备、协议都抽象成文件的形式,提供了一套统一而简单的文件IO的系统调用,简称系统的文件IO
也就是说在UNIX\Linux中任何对象都可以被当做是某种特殊的文件,都可以像访问文件一样,访问这些对象
文件分类:
普通文件 - 包括纯文本文件、二进制文件、各种压缩文件
目录文件 d 必须有读权限才能进入目录
块设备文件 b 保存大块数据的硬件设备,例如磁盘
字符设备文件 c 操作字符相关的设备 例如键盘、鼠标等
socket文件(套接字文件) s 通常用于网络通信
管道文件 p 用于进程间通信
链接文件 l 类似Windows的快捷方式

文件相关的系统调用
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*,代表了一个打开的文件(>=0),负数表示失败

int open(const char *pathname, int flags, mode_t mode);

功能:创建文件
flags:O_CREAT
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 执行
注意:可给宏名 也可以给 权限掩码 (0xxx) 八进制

int creat(const char *pathname, mode_t mode);

功能:创建文件
mode:同上

strace ./a.out
ssize_t write(int fd, const void *buf, size_t count);

功能:把内存中的数据写入的文件中
fd:文件描述符 open creat的返回值
buf:待写入的内存的首地址
count:要写入的字节数
返回值:成功写入的字节数

ssize_t read(int fd, void *buf, size_t count);

功能:从文件中读取数据到内存中
fd:文件描述符 open creat的返回值
buf:存储读取出来的数据的内存首地址
count:要读取的字节数
返回值:成功读取的字节数

posted @   BigBig飞  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示