目录
export MY_VALUE=12345678(正确加入操作)
进程概念
获取进程pid方式(概念)
1.获取所有的pid信息
部分获取ps ajx | head -1 && ps axj | grep 文件名
ps ajx | head -1 && ps axj | grep 文件名 | grep -v grep可以过滤掉grep中携带进程部分信息的内容
因为我没有运行test.o所以未开辟进程,所以没有进程信息
ps ajx | grep 文件名
2.示例代码
每个进程都有属于自己的PID(身份码)
在Linux系统里,PID即“Process IDentifier”,是进程标识符 ,以下是详细介绍:
基本概念
PID是系统为每个进程分配的独一无二的非负整数 。每个进程从创建起就被赋予特定PID,借此在系统中被唯一标识。就像现实中每辆车都有独特车牌号,方便交通管理,PID便于操作系统对进程进行管理和区分。
作用
- 进程管理:是进程的身份标识。系统借助PID识别、跟踪、控制进程,如使用 kill 命令通过指定PID向进程发送信号,实现进程终止(如 kill -9 <PID> 可强制终止进程 ); waitpid 函数能让父进程等待特定子进程结束 。
- 资源分配与调度:操作系统依据PID管理进程资源分配,决定CPU时间、内存等资源如何分配给各进程,保障系统高效稳定运行 。
- 进程间通信:进程可利用PID识别通信对象,实现进程间通信和同步协作 。
回顾上文我们简单的了解了进程,首先介绍的是fork.
在Linux中, fork 是一个用于创建新进程的系统调用函数 ,它在Unix和类Unix操作系统中被广泛使用。以下是关于它的详细介绍:
函数原型与头文件
- 原型: pid_t fork(void); , pid_t 实质是 int ,在 <sys/types.h> 中定义 。
- 头文件:使用时需包含 <unistd.h> ,有时也会用到 <sys/types.h> 。
工作原理
调用 fork 时,系统会创建一个与调用进程(父进程)几乎完全相同的新进程(子进程)。具体过程如下:
1. 资源分配:系统为新进程分配资源,如存储数据和代码的空间。
2. 数据复制:将父进程的很多属性复制到子进程,包括数据空间、堆、栈等资源的副本,但父子进程并不共享这些存储空间 。不过,代码段是共享的,即父子进程从 fork 函数调用之后的下一条指令开始执行。
返回值特点
fork 调用一次却返回两次,在不同进程中有不同返回值:
- 在父进程中:返回新创建子进程的进程ID(PID),是一个大于0的整数 。通过这个返回值,父进程能识别并后续操作子进程。
- 在子进程中:返回值为0 。子进程可据此判断自身身份。
- 出错时:返回 -1 ,可能原因有当前进程数达到系统上限( errno 设为 EAGAIN ) 或系统内存不足( errno 设为 ENOMEM )。
在上述代码中,执行 fork 后产生父子进程。子进程中 fpid 为0,打印子进程相关信息;父进程中 fpid 是子进程PID,打印父进程及子进程PID信息 。
应用场景
- 多进程并发服务器:有客户端连接时,父进程用 fork 创建子进程处理请求,自身继续监听新连接,提升并发处理能力 。
- 任务并行处理:将不同任务分发给子进程并行执行,加快任务完成速度,比如批量文件处理等场景 。
提出以下问题
1. 为什么 fork 要给子进程返回0,给父进程返回子进程 pid
- 子进程返回0:子进程通过返回0能方便地知道自己是子进程,因为0是一个特殊值,与任何有效的进程ID都不同。这样子进程可以在后续代码中根据这个返回值来执行特定于子进程的逻辑。
- 父进程返回子进程 pid :父进程得到子进程的 pid 后,就可以通过这个 pid 来对特定的子进程进行管理和控制,比如等待子进程结束、向子进程发送信号等。同时,父进程可以同时创建多个子进程,通过不同的 pid 来区分和处理各个子进程。
2. 一个函数是如何做到返回两次的
fork 函数会创建一个新的进程,即子进程。子进程是父进程的一个副本,它从 fork 函数调用处开始执行,就好像是父进程的一个“分身”。在 fork 函数执行时,内核会为子进程分配资源,并复制父进程的上下文,包括程序计数器、寄存器等。因此,看起来 fork 函数在父进程和子进程中都被执行了一次,也就有了两次返回。一次是在父进程中继续执行,返回子进程的 pid ;另一次是在子进程中执行,返回0。二者返回存在顺序问题,我们无法表象过去,个人认为父进程优先于子进程返回,因此先输出父进程内容,后输出子进程内容。
3. 一个变量怎么会有不同的内容?如何理解?
在 fork 之后,父进程和子进程拥有各自独立的地址空间。虽然它们在 fork 之前的变量值是相同的,但 fork 之后,对一个进程中变量的修改不会影响到另一个进程中的同名变量。这是因为它们的地址空间是相互独立的,每个进程都有自己的一份变量副本。例如,父进程中的一个全局变量 x ,在 fork 之后,父进程和子进程都有自己的 x ,父进程修改自己的 x 不会影响到子进程的 x ,反之亦然。
fork 后父子进程代码共享、数据单独开辟,主要有以下原因:
代码共享(写时拷贝)
- 提高内存利用效率:程序代码通常是只读的,父子进程执行相同的程序代码,若各自复制一份到内存,会浪费大量空间。共享代码段能让多个进程共用同一份代码,减少内存占用。
- 保证代码一致性:共享代码可确保父子进程执行的代码完全一致,避免因代码复制产生不一致或错误。
数据单独开辟
- 进程独立性要求:每个进程需有独立数据空间,保证其数据操作不影响其他进程。父子进程后续可能执行不同任务,对数据有不同修改需求,若数据不独立,一个进程对数据的修改会影响另一个进程,导致程序逻辑混乱。
- 数据安全与稳定:单独开辟数据空间为进程提供了安全稳定的运行环境。一个进程的数据损坏或异常不会波及其他进程,增强了系统的稳定性和可靠性。
4. fork 函数究竟是什么,干什么
fork 函数是UNIX和类UNIX系统中的一个系统调用,用于创建一个新的进程。它的主要作用是将当前进程(父进程)复制一份,生成一个新的进程(子进程)。子进程在许多方面与父进程相似,包括程序代码、数据段、堆、栈等,但它们是两个独立的进程,有各自的进程控制块(PCB)和独立的地址空间。这样,父进程和子进程可以并发执行,各自执行不同的任务,从而实现多任务处理。例如,一个服务器程序可以通过 fork 函数创建多个子进程来同时处理多个客户端的请求。
进程状态
操作系统学科进程状态(运行,阻塞,挂起)
进程状态与内存管理
在Linux系统中,进程状态丰富多样且与内存管理紧密相连。当进程处于阻塞状态时,往往是在等待特定事件,像从键盘读取数据。此时,进程会被挂起,其关键信息存储于 struct task_struct 结构体中。若操作系统内存资源严重匮乏,部分进程将被换出至交换分区(swap),以此节省内存,保障系统正常运转。待条件满足,比如所需数据准备就绪,进程又会被换入内存继续执行。
struct task_struct 是Linux内核用于描述进程的重要结构体,涵盖进程状态、优先级等众多属性。而 struct dev 则用于描述设备,包含设备类型、状态,以及指向相关进程的指针等信息。部分设备结构体还设有等待队列,用于管理那些等待该设备资源的进程。
进程调度与运行
系统中存在一个由调度器管理的运行队列( struct runqueue ) ,该队列有指向队头和队尾进程的指针。调度器的职责是从运行队列里挑选合适的进程,使其能在CPU上运行。进程进入运行队列,意味着它已准备就绪,随时可被调度。
进程在CPU上并非一直运行至结束才释放资源,而是引入了时间片的概念(如图中示例的10ms )。当进程的时间片耗尽,即便尚未执行完毕,也会被调度器从CPU上移除,排至运行队列末尾,等待下一轮调度。这种频繁的进程上下操作,即进程切换,让所有进程的代码在一个时间段内都能得以执行,达成并发执行的效果。比如,即便进程遭遇 while(1); 这样的死循环代码,时间片机制也能确保其他进程有机会获得CPU调度。
补充完善
进程状态转换十分复杂,除阻塞和运行状态外,还有就绪状态、睡眠状态等。就绪状态的进程已万事俱备,只待CPU资源,一旦CPU空闲且被调度器选中,便会转入运行状态。睡眠状态又细分为可中断睡眠与不可中断睡眠,前者可被信号唤醒,后者一般在等待特定I/O操作完成后才会苏醒。
调度器选择进程的依据是不同的调度算法。常见的有先来先服务(FCFS)、短作业优先(SJF)、时间片轮转(RR)、优先级调度等。Linux系统采用的完全公平调度器(CFS)更为复杂高效,它通过统计和比较进程的虚拟运行时间,公平分配CPU时间,防止某些进程长时间得不到调度。
内存管理方面,当内存资源不足时,操作系统在选择换出进程至交换分区时,会综合考量进程活跃度、内存使用量等因素。通常,不活跃进程更易被换出。进程换入内存时,需兼顾内存空间分配与进程需求,保障系统高效运行。此外,操作系统还借助页缓存等缓存机制,降低进程换入换出的性能开销,提升数据访问速度。
linux的维护方法
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
以下是对进程状态的解释:
- R (running):运行中,进程正在执行或等待 CPU 资源。
- S (sleeping):睡眠状态(可中断),等待事件完成(如 I/O 操作),可被信号唤醒。
- D (disk sleep):磁盘睡眠(不可中断),等待磁盘 I/O 完成,无法被信号唤醒。
- T (stopped):停止状态,进程被暂停(如收到 SIGSTOP 信号)。
- t (tracing stop):追踪停止,因调试被追踪器暂停。
- X (dead):死亡状态,进程已终止,即将被系统移除(内核内部状态,一般不可见)。
- Z (zombie):僵尸状态,进程已结束但父进程未读取其退出状态,资源未完全释放。
说明:状态值为对应下标,可通过位运算判断组合状态(如 S+D 对应 1|2=3 )。
Z(zombie)-僵尸进程
僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)
没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 1;
}
else if(id > 0){ //parent
printf("parent[%d] is sleeping...\n", getpid());
sleep(30);
}
else{
printf("child[%d] is begin Z...\n", getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
return 0;
}
僵尸进程危害
进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话
说,Z状态一直不退出,PCB一直都要维护?是的!
那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
内存泄漏?是的!
如何避免?后面讲
进程状态总结
至此,值得关注的进程状态全部讲解完成,下面来认识另一种进程
孤儿进程
父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
父进程先退出,子进程就称之为“孤儿进程”
孤儿进程被1号init进程领养,当然要有init进程回收喽。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 1;
}
else if(id == 0){//child
printf("I am child, pid : %d\n", getpid());
sleep(10);
}
else{//parent
printf("I am parent, pid: %d\n", getpid());
sleep(3);
exit(0);
}
return 0;
}
进程的删改
kill -9 pid(删除对应进程)
kill -19 暂停进程
kill -18 恢复 暂停进程
进程优先级
什么是优先级:对于资源的访问,谁先访问,谁后访问。
为什么有优先级:##因为资源是有限的,进程是多个的,注定了,进程间是竞争关系。
##操作系统必须保证大家的良性竞争,确认优先级
##如果进程长时间得不到cpu资源,该进程的代码长时间无法得到推进(饥饿问题)
优先级怎么设定
基本概念
cpu资源分配的先后顺序,就是指进程的优先权(priority)。
优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。
查看系统进程
在linux或者unix系统中,用ps –l命令则会类似输出以下几个内容:
我们很容易注意到其中的几个重要信息,有下:
UID : 代表执行者的身份
PID : 代表这个进程的代号
PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
PRI :代表这个进程可被执行的优先级,其值越小越早被执行
NI :代表这个进程的nice值
PRI and NI
PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高
那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值
PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
所以,调整进程优先级,在Linux下,就是调整进程nice值
nice其取值范围是-20至19,一共40个级别。
PRI vs NI
需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。
可以理解nice值是进程优先级的修正修正数据
在Linux中, nice 和 renice 命令用于调整进程的优先级。以下是它们的用法:
nice命令
- 功能:用于在启动进程时设置其优先级。优先级的范围是 -20(最高优先级)到 19(最低优先级),默认优先级是 0。
- 语法: nice [ -n 优先级值 ] 命令 。例如,要以较高优先级(优先级值为 -5)启动一个名为 myapp 的程序,可以使用 nice -n -5 myapp 。
renice命令
- 功能:用于调整已经在运行的进程的优先级。
- 语法: renice [ -n 优先级值 ] -p 进程ID 。例如,如果要将进程ID为 1234 的进程优先级调整为 5,可以使用 renice -n 5 -p 1234 。
需要注意的是,普通用户只能将进程的优先级调整为较低的值(即优先级数字更大),只有 root 用户可以将优先级调整为较高的值(即优先级数字更小)。
进程优先级查看操作
ps -l/-al/|grep pid
进程优先级改变操作
top(root用户)
在Linux系统中,使用 top 命令修改进程优先级的操作步骤如下:
前提说明
进程优先级范围是 -20(最高优先级)到19(最低优先级) ,普通用户只能降低自己进程的优先级(即让优先级数值变大),只有root用户能随意调整优先级。
操作步骤
1. 打开 top 界面:在终端输入 top 命令并回车 ,进入 top 交互界面,该界面实时显示系统中进程的运行状态等信息。
2. 选择要修改优先级的进程:在 top 界面中,找到需要修改优先级的进程,记住其PID(进程号,在 PID 列中显示 )。
3. 进入修改优先级模式:在 top 交互界面中,按下字母 r 键 ,此时 top 会提示你输入要调整优先级的进程PID。
4. 输入进程PID:输入目标进程的PID,然后回车 。
5. 设置新的优先级: top 会再次提示你输入新的nice值(即优先级值 )。根据需求输入 -20到19之间的数值,回车确认。比如想提高进程优先级,可输入较小的负数(如 -5);想降低优先级,输入正数(如5 )。
6. 退出 top (可选):完成优先级修改后,若想退出 top 界面,按 q 键即可。
示例:若有个进程PID为1234,想将其优先级调整为 -5 ,在 top 界面按 r ,输入 1234 回车,再输入 -5 回车就能完成调整。 另外,也可使用 renice 命令在 top 外修改进程优先级,格式为 sudo renice 优先级数值 -p 进程PID ( sudo 用于获取root权限 ) 。
操作系统利用位图进行基于优先级的调度
以Linux内核为例,步骤如下:
1. 数据结构与初始化:定义优先级位图相关数据结构,如 struct bitmap 含字符数组存储位信息。开始时,位图所有位清0 。
2. 进程状态映射:当某一优先级的进程准备执行,位图对应位置1 。如系统有140个优先级,用5个字符(40位 )的位图表示,每位对应特定优先级进程状态。
3. 查找最高优先级:通过快速查找算法(如 sched_find_first_bit() ),从位图最低位向最高位查找第一个置1的位,其位置对应最高优先级 。因优先级个数固定,查找时间不受进程数量影响。
4. 选择进程调度:找到最高优先级对应位后,从该优先级就绪队列获取进程控制块,调度此进程运行 。相同优先级进程,常按轮转方式调度 。
在其他操作系统(如μC/OS - II和RT - Thread )中,也采用位图调度。任务优先级值越小优先级越高,调度时查找第一个置1的位确定最高优先级,从对应队列获取任务控制块 。区别在于,多级队列形式一个优先级可对应多个任务,支持协作调度;纯位图形式一个优先级只能对应一个任务,确定性更好 。
环境变量
拓展
竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发
CPU 与进程相关
- CPU 图示:图中展示了一个CPU示意图,其中有一个标记为 eip 、值为 50 的区域 。
- 进程控制块(task_struct):包含 struct reg_info 结构体,结构体成员有 int eax 、 int ebc 、 int eip 等。
- 函数返回值与 CPU 寄存器:函数返回值通过CPU寄存器被外部拿到,如 return a -> mov eax 10 。
- 程序计数器(pc、eip):记录当前进程正在执行指令的下一行指令的地址,用于系统知晓进程执行到哪行代码。
进程切换时操作
- 上下文保存与恢复:进程从CPU上离开时,要将自己的上下文数据保存好(甚至带走),目的是为了未来恢复。进程在被切换的时候,需进行 1. 保存上下文;2. 恢复上下文 操作。
CPU 寄存器相关
- 寄存器分类
- 通用寄存器:eax、ebx、ecx、edx
- 栈帧寄存器:ebp、esp、eip
- 状态寄存器:status
- 寄存器作用:寄存器用于提高效率,存放高频数据。CPU内的寄存器保存的是进程相关的临时数据,即进程的上下文,可被访问或者修改。
环境变量PATH
echo PATH指令只会重复打印PATH
echo $PATH指令正确展示各路径
为什么 会有环境变量,他的作用是什么???
举例
在改变环境变量下路径之前只可运行./test.o,改变后则可以运行test.o指令
那么改变为什么PATH路径之前只可运行./test.o???
为什么改变后则可以运行test.o指令???
当你改变 PATH 路径后能运行 test.o 指令,是因为你把包含 test.o 文件的目录添加到了 PATH 环境变量中。
PATH 环境变量存储了一系列目录路径,当你在终端输入一个命令时,系统会按照 PATH 中列出的目录顺序依次查找该命令对应的可执行文件。当你将 test.o 所在目录添加到 PATH 后,系统在查找 test.o 命令时,就会在这个新添加的目录中找到该文件并执行它。
那么该怎么新增路径呢
1.PATH=路径(覆盖性追加)
后果:指令失效
解决方案:重进。
原因:资源由处理器统一调度分配,每次进入会重新分配资源
2.PATH=$PATH:路径(合并性追加)
环境变量HOME
在 Unix/Linux 系统中, echo $HOME 用于输出当前用户的主目录路径。
通常:
- 普通用户主目录多为 /home/用户名 (如 /home/user )。
- root 用户主目录为 /root 。
执行该命令会直接显示具体路径(需在终端运行)。
env指令
env 是 Unix/Linux 系统指令:
- 显示所有环境变量:直接输入 env 。
- 临时设置环境变量: env VAR=value (仅当前会话有效)。
- 为命令单独设置变量: env VAR=value command (不影响系统全局)。
获取环境变量方式
environ
getenv();
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
// ./mycmd /a/b/c/
int main()
{
char who[32];
strcpy(who, getenv("USER"));
if(strcmp(who, "root") == 0)
{
printf("让他做任何事情");
}
else
{
printf("你就是一个普通用户,受权限约束\n");
}
printf("PATH: %s\n", getenv("PATH"));
return 0;
}
此时我需要介绍一下命令行参数,为下文做铺垫
命令行参数
首先main()函数可传参
#include <stdio.h>
int main(int argc, char *argv[]) {
// argc:参数个数(包含程序名本身)
// argv:字符串数组,存储参数内容
printf("参数个数:%d\n", argc);
for (int i = 0; i < argc; i++) {
printf("参数 %d:%s\n", i, argv[i]);
}
return 0;
}
而可传参与命令行参数有什么关系??
上图可以发现ll操作和test.o操作极为相似。命令行参数雏形
这样设计的目的:是为指令,工具,软件提供命令行选择支持
父子进程环境变量关系
父进程环境变量
子进程环境变量
我们发现二者相同
结论:我们所运行的进程,都是子进程,bash本身在在启动的时候,会从操作系统配置文件中读取环境变量信息,子进程会继承父进程交给他的环境变量
证明:给父进程加入新的环境变量观察子进程变化
MY_VALUE=12345678(不行)环境变量表中不存在。
export MY_VALUE=12345678(正确加入操作)
父进程插入成功
子进程插入成功
因此结论正确
unset MY_VALUE(正确删除操作)
我们发现MY_VALUE已经不存在
本地变量
特性:只会在本BASH内部有效,不会被继承。
示例代码
问题来了,我们所提到的本地变量只会在本BASH内部有效,不会被继承。
可是为什么echo可以调用,难道他创建的进程不遵守该特性吗
指令分为两种
1.常规指令--通过子进程完成
2.内建指令--bash不创建子进程,而是由自己亲自执行,类似于bash调用自己写的,或者系统提供的函数。
--cd指令
--echo指令
chdir 函数
chdir 函数是C语言中的一个函数,用于改变当前工作目录。以下是其基本信息:
函数原型
#include <unistd.h>
int chdir(const char *path);
参数说明
- path :指向一个字符串,该字符串包含了要切换到的目标目录的路径。可以是绝对路径,也可以是相对路径。
返回值
- 成功时返回0;
- 失败时返回-1,并设置 errno 来指示错误原因,如 ENOENT (路径不存在)、 EACCES (权限不足)等。
示例
#include <stdio.h>
#include <unistd.h>
int main() {
char *newDir = "/home/user/Documents";
if (chdir(newDir) == 0) {
printf("成功切换到目录:%s\n", newDir);
} else {
perror("chdir");
}
return 0;
}
这段代码尝试将当前工作目录切换到 /home/user/Documents ,如果成功则输出提示信息,否则输出错误信息。
chdir 函数在不同的操作系统上可能存在一些细微的差异,在使用时需要根据具体的操作系统进行适当的调整。同时,使用该函数时要确保程序有足够的权限来访问和切换到指定的目录。
进程地址空间
地址空间图验证
示例代码
我们发现各空间存取数据满足地址变化规律 :从代码区向栈区地址逐渐增大
通过下图我们得知栈区向下增长,堆区向上增长
通过下图我们得知static修饰的局部变量,编译的时候已经被编译到全局数据区
地址依然发生改变
下图现象发现,父子进程中g_val地址形同(及共用一个变量),但是子进程g_val改变(100->200)而父进程始终是200
因此我们大胆下结论:
g_val变量地址不是物理地址。
因为如果是物理地址不会出现上述情况!g_val或许是虚拟地址&&线性地址。
我们平时写的C/C++使用的指针的地址全部不是物理地址。
间接性证明fork独立父子进程的原因
原理图
这张图围绕进程地址空间展开,核心是解释地址空间相关概念。图中左侧展示父进程创建子进程,父子进程都关联着 struct task_struct 结构体。进程地址空间为4G ,从低地址0000 0000到高地址FFFF FFFF ,又细分为内核空间(1G )和用户空间(3G ) ,用户空间中包含命令行参数环境变量、栈、共享区、堆等不同区域。
中间的页表是关键,它建立起虚拟地址和物理地址的映射关系。比如虚拟地址 0x40405c ,通过页表映射到物理内存中的地址(像 0x11223344 、 0x44332211 等 )。右侧是物理内存,存储实际的数据和代码,有具体物理地址标识。
图中还提到写时拷贝机制,操作系统自动完成,在对虚拟地址写入操作时,重新开辟空间,但虚拟地址本身不受影响 。 整体通过这些元素,阐述了进程地址空间、物理内存、页表之间的关联以及父子进程相关的内存管理机制。
什么是地址空间
什么是地址空间
在32位的地址和数据总线--->没跟总线会发出强信号(1)和弱信号(0)两种电信号,形成类似于二进制的信息,操作系统会根据数据导线传输的32信号访问内存中对应的地址,电信号共2^32种。
如何理解地址空间上的区域划分
课桌里的地址空间划分
小胖和小花共用100cm长的课桌,起初东西乱堆,常因争抢空间闹矛盾,像地址空间未划分时程序数据混乱。
后来他们用粉笔将课桌平分,每人50cm,类似地址空间做初步分区。但这还不够,课本、文具大小不同,空间使用仍不便。
于是,他们进一步细分:小胖把50cm划成文具区、课本区、杂物区;小花分出铅笔盒区、笔记本区、画笔区和临时区。从此,两人取用物品更高效,课桌井井有条,就像计算机将地址空间细分代码区、数据区、堆栈区后,程序得以稳定运行。
因此所谓的地址空间本质是一个描述进程可视范围的大小
地址空间内一定要存在各种区域划分,对线性地址进行start,和end即可
进程拥有独立地址空间。 task_struct 含指向地址空间相关结构的指针,用于内核管理; struct mm_struct 专门记录地址空间布局、维护映射关系 。三者协作,使进程在独立地址空间安全运行。
什么叫进程,以及进程地址空间--->为什么
进程=内核数据结构(task_struct&&mm_struct&&页表)+程序的代码和数据。
进程地址空间类似于富豪的资产,他的子女们互不认识,都认为遗产独属于自己虽然资产可视化,可以小额索取,但想要全部遗产会合理的被富豪(操作系统)拒绝物理内存,同时子女认为不全给很正常,进程地址空间似乎是一张看得见,摸得着,可以小吃一口的大饼。
让进程以统一视角看待内存,是为什么?
###增加进程虚拟地址空间可以让我们访问内存的时候,增加一个转换过程,在这个转化过程中,可以对我们的寻址请求进行审查,所以一旦访问异常,直接拦截,该请求不会到达物理地址,保护物理内存。
###因为有地址空间和页表的存在,将进程管理模板进行解耦合.