不知道大家有没有听说过一个东西,叫C/C++地址空间。
给大家画一个图这个图,作为C/C++程序员应该比较熟悉,有人一个会有人把这个图叫做C/C++地址空间,我觉得大家应该比较陌生,我也是刚刚学完,大家如果感兴趣的,可以看看,看看我有没有什么地方说错没。
我当时在学语言的时候,只知道记住它, 就意识到我自己在C里面,定义局部变量,定义static变量,定义全局变量。那么malloc那么大概每一个对应的变量大概在什么位置,给我们去开辟空间呢?在当时学的时候,是我们有了一个这个图之后呢,那么对于C/C++语言当中的变量或者对象,它的内存分布情况,我们脑海里就有了一个基座。
上面这个图,请问你认为这个地质空间它是什么呢?是内存吗?答案不是内存。
让大家看看一个现象。这是这个创建子进程的代码。
但我知道。在系统当中,只要是一个进程,它就要被操作系统管理,只要被操作系统管理,那么你就必须得怎么办呢啊?你就必须得进行我们对应的创建,指定层时要拷贝附近层的内核数据结构。比如说你指定层得有个PCB吧,对不对?没有PCB的话,你你拿什么来做进程管理呢?所以有俩进程,父子进程。说白了创建一个子进程不就系统里多了个进程吗?系统里多了个进程,我们操作系统要管理,它要管理怎么管理,先描述再组织,所以我们的子进程也一定有自己的PCB结构。
如果我们fork之后啊,父子进程呢,那么它们两个谁先运行呢?答案是不确定它们两个进是谁先运行,不确定。具体谁先跑的时候完完全全是由我们的调度器决定的。运行看看,所以运行系统决定的。
现在做一小实验。在代码最前面加了一个全局变量,父子进程都来打印这个变量和变量的地址号值,运行一定时间,用子进程修改g_value的值
代码如下。运行起来看看效果。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int g_value = 100;
int main()
{
pid_t pid;
pid = fork();
if (pid < 0)
{
return -1;
}
else if (pid == 0)
{
int cnt = 0;
// 子进程
while (1)
{
printf("我是子进程 pid: %d, ppid = %d | g_value = %d g_vlue地址 =%p \n", getpid(), getppid(), g_value, &g_value);
sleep(1);
cnt++;
if (cnt == 5)
{
g_value = 500;
printf("子进程改了 g_value的值改变了");
}
}
}
else
{
// 父
while (1)
{
printf("我是父进程 pid: %d, ppid = %d | g_value = %d g_vlue地址 =%p \n", getpid(), getppid(), g_value, &g_value);
sleep(1);
}
}
return 0;
}
看结果,我们曾经讲过父子进程必须保证父。父子进程的独立性啊,因为子进程改,因为独立性,所以不能改变父的值,这个我能明白,但是有一一点不理解的就是,g_value是一个全局变量,为什么,子进程改了,他们两的地址还是相同的?
可是让我们感觉到最震惊的其实不是它的值。而是最让我们感觉到震惊的是,这个地址大家竟然是一样的同学们啊,那么这个地址竟然是一样的。怎么可能啊,多进程在读取同一个叫做地址的时候。怎么可能出现不同的结果?
那么所以呢啊,实践是检验真知的唯一标准,出现了一个和我们之前认知不一样的结果,首先肯定不是操作系统错了,一定是我们有哪些知识,我们理解错了。或者哪些知识我们理解的并不全面。
它们两个的值肯定是一样的,但结果却不一样。那么,所以因为地址没变,所以我们首先可以推导出来的第一个结论就叫做肯定不是物理地址,如果是物理地址读出来的值一定是一样的。在学习的语言级别的地址,包括我们叫做我们所谓的指针。这样的概念,它最终对应的不是物理地址。
所以先把概念抛出啦,而叫做虚拟地址,或者我们叫线性地址,在linux上,也有一种叫法叫做逻辑地址啊。
在C语言上面打印出来的指针地址,全都是虚拟地址,也称为虚拟地址空间,所以根本不是内存,还在软件上面,不是在硬件地址。
下面就开始来研究。先建立共识
那么进程呢?认为自己是啊,独占系统资源的,事实上,它并不是啊,但是进程那么认为自己想用内存,它就想用。想用CPU,那么进程上下文切换的时候把一个进程投入休眠了,这个进程也不知道自己休眠了。虽然我们那么在上帝视角一个进程,在一个时间段内不断在并发来回切换。但你如果是这个进程,你都休眠了,那你就相当于你眼睛闭上了。你像比如说你晚上睡觉啊,晚上睡觉的时候呢,别人把你就是或者是把你挪到其他位置,把你从你宿舍挪出去,然后人家只要你睁开眼之前,人家把你挪回宿舍,那么此时在你看来,你就认为你自己永远都在宿舍。所以对我们来讲你会认为自己独占性的资源。
在举一个例子,美国一个大富翁,有三个私生儿子
这个在美国的这个富翁呢?他非常有钱啊,比如说十亿美金啊。但是富翁呢?他没有结婚。啊,那么他总是认为总有刁民想害朕啊 。
每一个人都有他自己的工作啊,比如说这个那么大儿子啊,一个工厂的老板啊。好,假设这个工厂呢,管了一个工厂。我儿子二,做CEO啊,三儿子念书。
竟然是私生子,3个儿子,那么肯定不知道对方的存在。大富翁想管理好这三个儿子,为了让他们未来在做自己的事情的时候,那么更游刃有余。
所以呢,大富翁呢,就给儿子说什么呢?说儿子。你好好把这个工厂经营好。那么未来等我嗯。驾鹤西去了。我有十个亿,这十个亿就交给你去继承了。大儿子一听,非常高兴,就相当于有日没夜的,就天天在工厂里面。做着各种事情。
然后呢?又找到二儿子啊?给二儿子说。说儿子啊啊,你好好的帮我把那么我们的家族的这个金融的那么投资类的事情,你给我做好。将来等我驾鹤西去的时候,这十个亿啊,我们整个的家族产业就交给你了啊。所以二儿子也很高兴啊,
然后呢?那么又跑过去找三儿子,他给三儿子说。儿子啊,我就你一个儿子啊,你好好念书啊,等你把书念出来了,那老爹给你把工作什么安排好?然后等老爹就是驾鹤西去了,那么我们家族这十个亿的那么呃现金啊,包括我们的产业都交给你了。所以那么三儿子也很高兴啊
大富翁就给每一个儿子都画了一个大饼,因为三个儿子都不在互相存在,所以认为自己独占10亿。但是他们不可能会一次就要10亿,想什么呢,大富翁都还在,比如说大儿子要建工厂,问老爸那50w美金或者发不出工资了,又问几十万美金,到了二子,问大富翁拿几千美金买一个手表,撑门面,或者几十万美金,买台车。三儿子老爸,开学了,要几千美金交学费或者生活费,这三个儿子要的,老爹都会给,那么其中在这样的工作方式之下呢?那么其中。这三个儿子和这个老爹相处的非常非常好。
其中这里的大富翁,它对应的就是我们的操作系统啊。它要划分的十亿美金相当于内存啊,然后呢,这三个儿子你看这德行,你看这三个儿子一伸手要。你自己平时写代码时,你自己调malloc,你自己定义各种变量,你有各种对象。你不就是在做那个儿子的工作吗?你在向系统要资源呐,所以我们都是儿子对吧?我们对应的三个儿子就是我们所谓的进程,那么这一批东西我们相当于我们进程。我们进程所对应给我分配的内存,或者在于语言上,我们叫做对象空间啊,你以前申请内存,你会不会直接说啊?我内存16个g会不会?你基本上不会啊,一次申请一性能要16个g,而是那么慢慢要啊。
那么其中我们站在进程角度呢?他认为自己独占资源啊,但是那么操作系统早就料定了你这每一个儿子,你不可能怎么要你要了,我可以拒绝你啊,我不给你,有一天,那个大儿子跑去找他老爹说,我跟你要了这么多次钱啊,累计已经要了好几百万了。你这样吧,你到时候把你那十个亿一块给我吧啊,反正我也花也是花,来回要也麻烦啊,那么怎么办呢?这大富翁立马就可以把这儿子揍一顿。立马拒绝他啊,你赶紧回去吧啊,想都别想啊。
那么其中给每个儿子画的这一个大饼,我们将其称之为地址空间啊。严格意义上来讲的,叫做进程地址空间。
下一篇聊,操作系统如何给进程画的饼,