./test
是程序运行之后打印的 本质理解: 程序运行后执行的cout/printf
实际上是进程
在输出数据对程序的理解
1. int a = 10;
把字面常量10放到局部变量a
2. 单纯的字面常量放在代码李可以编译通过如:
"hello linux";
100;
'a';
验证程序地址空间划分
在堆区申请了一块空间 1. 释放时为什么只用将空间首地址传给free() 2. 差值为什么多了10个?
堆区申请x个字节 实际上c标准库给当前程序申请的比x多 多出来的空间 用来存储此次申请的属性信息 称作"Cokkie" 饼干数据 用来记录 什么时间申请的 申请的空间多大 等 上图中堆区数据字节差值为20也验证了这句话
总结:
来看一个比较牛马的场景
#include
#include
int g_val = 100;
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 0;
//child
while(1)
{
printf("I am child, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n",\
getpid(), getppid(), g_val, &g_val);
sleep(1);
cnt++;
if(cnt == 5)
{
g_val = 200;
printf(" child chage g_val 100 -> 200 success\n");
}
}
}
else
{
//father
while(1)
{
printf("I am father, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n",\
getpid(), getppid(), g_val, &g_val);
sleep(1);
}
}
}
同时访问同一个地址出现了不同的值[在3.5解释]
由此得出结论 这里的地址绝对不是物理内存的地址!那他是什么?
"地址"
概念不是物理地址而是虚拟地址拓展知识
磁盘/网卡/显卡等外设也有寄存器 外设保存数据的寄存器可以称为端口/串口[硬件级别]
了解虚拟地址
逻辑地址
线性地址
Makefile格式: target : prerequisties
目标文件: 先决条件
hello:hello.c hello.c1 hello.c2
gcc -o $@ $^
$@: 依赖方法对应的依赖关系中的目标文件即hello
$^: 所有的依赖文件 即hello.c hello.c1 hello.c2这一堆文件
$< 第一个依赖文件 即hello.c
$? 比目标还要新的依赖文件列表
富翁有10亿 底下有三个私生子 三个私生子互不知道对方的存在 富翁对他们三人分别承诺 他死后10亿就是他的 三人分别相信了 在富翁还存活时 三人找他要钱用 富翁也会给 但是如果要得太多 比如一次要了一亿 富翁就不给了 因为没有正当用途等原因
富翁 – OS 私生子 – 进程 老爹画的饼 – 地址空间
要注意的是:
内核中的地址空间 当未来和某一进程联系起来时 它实际上也是一种数据结构 因为他要对进程进行描述组织 即 富翁/OS 要对他画的饼/地址空间 进行组织 否则饼/进程太多了可能会露馅
我们首先要了解 计算机早期的设计是直接访问物理内存的 后来才引入了 线性地址/虚拟地址 显而易见引入虚拟地址是为了让计算机更好的工作 无论是安全问题或者是效率问题 看下面这种情况 就可以了解直接访问物理内存是极其危险的!
int* p = 乱码
进程1要对指针p进行访问/修改/删除操作 而这个指针恰好指向了进程2/3所在的内存 那么此时就芭比Q了[内存本身可以随时被读写]源码
解释之前讲的fork()函数一个返回值同时保存两个不同的值的问题
pid_t fork()
{
//创建子进程
return id;
}
pid_t Id = fork();
return id;
之前 子进程已经被创建出来 父子进程分别return 自己代码的id值return id;
在fork()函数即将返回 执行return语句时 对Id
值进行修改/写入 ===> 发生写时拷贝readelf的用法
readelf是一个Linux下的命令行工具,用于查看ELF格式的目标文件或可执行文件的信息。ELF(Executable and Linkable Format)是一种常见的二进制文件格式,用于在Linux系统中表示可执行文件、共享库、目标文件等。使用readelf命令可以查看这些文件的头部、节区、符号表、重定位表等信息。以下是readelf命令的一些常用选项和用法:
readelf -h <file>
readelf -S <file>
readelf -s <file>
readelf -r <file>
readelf -d <file>
readelf -p <section_name> <file>
例如,要查看可执行文件ls的头部信息,可以使用以下命令:
readelf -h /bin/ls
objdump的用法
objdump是一个二进制文件反汇编工具,可以用于查看二进制文件的汇编代码、符号表、重定位表等信息。在Linux下,可以使用objdump命令来进行反汇编操作。以下是一些常用的objdump命令:
objdump -d <binary_file>
其中,-d表示反汇编操作,
objdump -t <binary_file>
其中,-t表示查看符号表。
objdump -r <binary_file>
其中,-r表示查看重定位表。
objdump -x <binary_file>
其中,-x表示查看头部信息。
objdump
是一个二进制文件分析工具,可以用来查看二进制文件的汇编代码、符号表、重定位表等信息。-a
选项表示显示所有信息,-f
选项表示显示文件头信息,-h
选项表示显示节头信息。
在Linux中,可以使用以下命令来查看二进制文件的所有信息:
objdump -afh <filename>
其中,
是要查看的二进制文件的文件名。执行该命令后,会输出该二进制文件的所有信息,包括文件头信息、节头信息、符号表、重定位表等。
举个例子,如果要查看可执行文件/bin/ls
的所有信息,可以执行以下命令:
objdump -afh /bin/ls
程序编译形成可执行程序 没有加载到内存时 在程序内部实际上已经有地址 – 可执行程序编译时内部已经有地址
什么叫非法的访问/映射?
int main()
{
char* str = "hello linux!\n";
*str = 'H';
明显上述代码会报错 str存在于栈上 字符串存在只读常量区 不可修改 页表不仅会把虚拟地址映射为物理地址 还会有权限的检查 如果不具有写的权限 就终止 内存可以随时任意读写 地址空间和页表的存在使得它不在可以那么随意了!
地址空间 + 页表的映射 使得在物理内存中可以对未来加入内存的数据进行任意位置的加载(前提是有空间) 使得物理内存分配就和进程管理的工作分离 即内存管理模块和进程管理模块完成了解耦合
C/C++语言中父进程malloc/new空间时,本质是在虛拟地址空间申请的 优势:
代码写完形成可执行程序 这个程序可能不是马上运行 如果在写代码或者形成可执行程序时就为其申请了空间 那么程序不运行它不用这个空间 别的程序也没法用 这是一种极大的浪费 且 会造成效率大大降低
有地址空间的存在,上层申请空间是在地址空间上申请的,物理内存可以/甚至一个字节都不给(此时的申请的空间其实压根就不是空间只不过是编译器按照进程地址空间划分为每一句代码都生成了虚拟地址申请的空间也为他们生成了虚拟地址 当这个程序运行成为进程时通过映射才会真正的去物理内存申请空间)
当进行对物理地址空间访问的时候,才执行内存的相关管理算法缺页中断==>[操作系统自动完成用户和进程,完全0感知]然后在进行内存的访问
申请了物理空间,不立马使用是空间的浪费 通过延迟分配的策略来提高整机的效率 使得内存的有效使用几乎100%
进程 = 进程内核数据结构(PCB) + 进程对应的磁盘上的可执行程序(代码+数据)
现在我们了解到进程内核数据结构不仅仅有task_struct
还有task_struct
内的mm_struct* mm
指针指向的mm_struct
现在我们对进程的认识是进程 = 进程内核数据结构task_struct/mm_struct/页表 + 进程对应的磁盘上的可执行程序(代码+数据)
task_struct/mm_struct
被创建出来了 页表映射关系/代码和数据加载到内存 工作都没有完成 这个只创建了task_struct/mm_struct
的状态叫新建状态当真正运行这个程序时 代码和数据才被加载到内存