当前位置: 首页 > news >正文

【Linux】--- 进程的概念

【Linux】--- 进程的概念

  • 一、进程概念
  • 二、PCB
    • 1.什么是PCB
    • 2.什么是task_struct(重点!)
    • 3.task_struct包含内容
  • 三、task_struct内容详解
    • 1.查看进程
      • (1)通过系统目录查看
      • (2)通过ps命令查看
      • (3)通过top命令查看
      • (4)通过系统调用获取进程PID和父进程PPID
        • ① 获取进程ID函数getpid和getppid
        • ② 获取当前进程ID
        • ③ 获取父进程ID
    • 2.状态
    • 3.优先级
    • 4.上下文数据(重点!)
  • 四、通过系统调用创建进程
    • 1.使用fork创建子进程
    • 2.理解fork创建子进程(重点!)
    • 3.fork后的数据修改 (重点!)
    • 4.fork的返回值(重点!)
      • (1)fork返回值含义
      • (2)根据fork返回值让父子进程执行不同的功能
  • 五、进程状态
    • 1.进程状态定义
    • 2.进程状态分类
      • (1)R-运行状态
      • (2)S-浅睡眠状态
      • (3)D-深睡眠状态
      • (4)T-停止状态
      • (5)Z-僵尸状态
      • (6)X-死亡状态
    • 3.僵尸进程危害
  • 六、孤儿进程
  • 七、关于进程状态的补充
    • 1、kill
    • 2、僵尸进程 和 孤儿进程
    • 3、进程的状态:运行;挂起;阻塞;
  • 八、进程优先级
    • 1.概念
    • 2.为什么要有进程优先级
    • 3. 查看系统进程
    • 4.PRI和NI
    • 5.使用top命令更改进程优先级
      • (1)更改NI值
      • (2)NI的取值范围
      • (3)NI取值范围较小的原因
  • 九、环境变量
    • 1.概念
    • 2.常见环境变量
    • 3.如何查看环境变量
    • 4.和环境变量相关的命令
    • 5.环境变量的全局属性
    • 6.本地变量
  • 十、程序地址空间
    • 1.程序地址空间分布
    • 2.程序地址空间是虚拟地址
    • 3.虚拟地址
      • (1)页表
      • (2)如何理解地址空间?
      • (2)为什么要存在地址空间?
    • 4.写时拷贝

一、进程概念

课本概念:言简意赅的说就是,一个正在执行的程序
内核观点:进程是承担系统资源(CPU、内存)的实体

当我们写完代码之后,编译连接就形成一个可执行程序.exe,本质是二进制文件,在磁盘上存放着。双击这个.exe文件把程序运行起来就是把程序从磁盘加载到内存,然后CPU才能执行其代码语句。当把exe文件加载到内存后,此时这个exe文件形成的这程序就叫做:进程。所有启动程序的过程,本质都是在系统上创建进程,双击.exe文件也不例外:

在这里插入图片描述

二、PCB

1.什么是PCB

操作系统描述进程的时候就是PCB,PCB(process control block)

2.什么是task_struct(重点!)

在Linux中的PCB叫做:task_struct(task_struct就是PCB的一种)
创建进程不仅仅是,把代码和数据加载到内存里,还要为进程创建task_struct
所以:进程 = task_struct + 代码和数据

在这里插入图片描述

根据:“先描述,再组织”
①描述:task_struct在Linux内核中就是一种:结构体(里面包含着进程的相关信息)
②组织:系统里的进程都以task_struct 链表 的形式储存在内核里!

在这里插入图片描述

3.task_struct包含内容

标示符: 描述本进程的唯一标示符,用来区别其他进程。(PID)
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
上下文数据: 进程执行时处理器的寄存器中的数据。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。

三、task_struct内容详解

1.查看进程

(1)通过系统目录查看

proc是一个系统文件夹,在根目录下,通过ls可以看到该文件夹:
在这里插入图片描述
可以通过

ls /proc

命令查看进程的信息,数字是PID:
在这里插入图片描述

如果想查看进程信息,比如查看PID为989的进程信息,使用命令

ls /proc/PID

在这里插入图片描述

(2)通过ps命令查看

使用

ps aux   //查看所有进程

在这里插入图片描述
如果结合grep可以查看某一个进程:

比如想查看包含proc的进程,可以使用如下命令:

ps aux | head -1 && ps aux | grep proc | grep -v grep

在这里插入图片描述

在这里插入图片描述

(3)通过top命令查看

也可以通过top查看进程:

top

在这里插入图片描述

(4)通过系统调用获取进程PID和父进程PPID

① 获取进程ID函数getpid和getppid

获取进程ID和获取父进程ID可以通过以下方式进行获取,其中pid_t是short类型变量:

#include <sys/types.h>
#include <unistd.h>pid_t getpid(void);//获取当前进程ID
pid_t getppid(void);//获取当前进程的父进程ID
② 获取当前进程ID

获取当前进程,test_blog.c

#include<sys/types.h>
#include<stdio.h>
#include<unistd.h>int main()
{while(1){printf("i am a process! :pid:%d\n",getpid();//获取当前进程IDsleep(1);}return 0;
}

Makefile:

test_blog:test_blog.c                                                                                             gcc -o $@ $^ -g
.PHONY:clean
clean:rm -rf test_blog  

在这里插入图片描述

③ 获取父进程ID
#include<sys/types.h>
#include<stdio.h>
#include<unistd.h>int main()
{while(1){printf("i am a process! :pid:%d\n,my parent id is->ppid:%d\n",getpid(),getppid());//获取当前进程ID,和ppidsleep(1);}return 0;
}

Makefile:

test_blog:test_blog.c                                                                                             gcc -o $@ $^ -g
.PHONY:clean
clean:rm -rf test_blog

在这里插入图片描述

2.状态

之前写代码的返回值是0 ,这个0是进程退出时的退出码,这个退出码是要被父进程拿到的,返回给系统,父进程通过系统拿到。比如以下代码的退出码是0

#include<stdio.h>
int main()
{printf("hello linux!\n");return 0;
}

那么使用

echo $?

在这里插入图片描述
假如将退出码改为99 :
在这里插入图片描述
在这里插入图片描述
所以,状态的作用是输出最近执行的命令的退出码。

3.优先级

权限指的是能不能,而优先级指的是已经能了,有权限了,但是至于什么时候执行得先排队 ,这就像在餐馆点餐结帐出小票之后,已经可以拿到餐食了,但是什么时候能拿到呢?需要排队,在这个过程中,是否出小票就代表是否有权限,排队取餐就代表的是优先级。

4.上下文数据(重点!)

当操作系统维护进程队列时,由于进程代码可能不会在很短时间就能执行完毕,假如操作系统也不会在执行一个进程时,让其他进程一直等待,直到当前进程执行完毕,那可能当前进程需要执行很久才执行完毕,其他进程会一直处于等待状态,这不合理。那么操作系统在实际执行进程调度时,按时间片分配执行时间,时间片一到,就切换下一个进程(多管齐下)
时间片是一个进程单次运行的最长时间。
在这里插入图片描述
比如有4个进程,在40ms之内先让第一个进程运行10ms,时间一到就算没有运行完毕,就把第一个进程从队列头移动到队列尾,再让第二个进程运行10ms。40ms后,使得用户感知到这4个进程都推进了,其实本质上是通过CPU的快速切换完成的。

有可能在一个进程的生命周期内被调度成百上千次。比如CPU有5个寄存器,进程A正在运行时时间片到了,被切走的时候,会把CPU里和进程A相关的保存到寄存器里面的临时数据带走。当进程B调度完后,再次调度进程A的时候,会把进程A里面保存的临时数据再恢复到CPU的寄存器当中,继续上次切走时的状态继续运行,因此保护上下文能够保证多个进程切换时共享CPU。

保存上下文数据,实际上就是,保存:某一个进程在用完时间片后,剩下该进程没有被执行的代码和数据

四、通过系统调用创建进程

1.使用fork创建子进程

fork用来创建子进程:

#include <unistd.h>
pid_t fork(void);//通过复制调用进程创建一个新进程。新进程称为子进程。调用进程称为父进程。

看一个奇奇怪怪的代码:

#include<unistd.h>
#include<stdio.h>int main()
{int ret = fork();if(ret > 0){printf("I am child\n");sleep(1);}else{printf("I am father\n");sleep(1);}return 0;
}                    

发现两句话都打印了,也就是既执行了if又执行了else:
在这里插入图片描述
再看代码:

#include<stdio.h>
#include<unistd.h>int main()
{int ret = fork();while(1){printf("I am a process ,pid = %d,ppid = %d\n",getpid(),getppid());sleep(1);}return 0;
}

发现有两个pid和ppid:
在这里插入图片描述

这说明执行while死循环不只一个执行流在执行, 而是两个执行流在执行,每一行两个id都是父子关系。这是因为fork之后有两个执行流同时执行while循环。

2.理解fork创建子进程(重点!)

./可执行程序、命令行、fork,站在操作系统角度,创建进程的方式没有差别,都是系统中多了个进程。fork创建出来的子进程,和父进程不一样,父进程在磁盘上是有可执行程序的,运行可执行程序时会把对应的代码和数据加载到内存中去运行

但是子进程只是被创建出来的,没有进程的代码和数据,默认情况下,子进程会继承父进程的代码和数据,子进程的数据结构task_struct也会以父进程的task_struct为模板来初始化子进程的task_struct因此子进程会执行父进程fork之后的代码,来访问父进程的数据
在这里插入图片描述

3.fork后的数据修改 (重点!)

(1)代码是不可以被修改的。 那么数据呢?子进程和父进程共享数据,当父进程修改数据时,子进程看到的数据也被修改了,那么父进程就会影响子进程。那这两个进程还具有独立性吗?

(2)当父子进程都只读不写数据时,数据是共享的。

但是这两个进程中的任何一个进程要修改数据,都会对对方造成影响,这时候作为进程管理者:操作系统就要站出来干涉了。

父子进程修改数据时,操作系统会在内存中重新开辟一块空间,把这部分数据拷贝过去之后再做修改,而不是在原数据上做修改,这叫做->写时拷贝。

在这里插入图片描述
(3)写时拷贝,存在的意义:维护进程的独立性!(提高空间利用率)

对空间利用率的理解:而在创建子进程时不会让子进程把父进程的所有数据全部都拷贝一份,因为并不是所有情况下都可能产生数据写入/修改,所以这就避免了fork时的效率降低和浪费更多空间的问题。因此只有写入数据时再开辟空间才是合理的。

4.fork的返回值(重点!)

(1)fork返回值含义

fork出子进程后,根据fork()返回值的不同,一般会让子进程和父进程去干不同的事情,这时候如何区分父子进程呢?fork函数的返回值如下:
在这里插入图片描述
打印一下fork的返回值:

在这里插入图片描述

在这里插入图片描述
这说明:

  1. fork准备return的时候子进程被创建出来了。
  2. 这里有两个返回值,由于函数的返回值是通过寄存器写入的, 函数返回时把变量值写入到保存数据的空间。所以当父子执行流执行完毕以后,有两次返回,就有两个不同的返回值,就要进行写入,谁先返回谁就先写入,即发生写时拷贝。
  3. 给父进程返回子进程的pid的原因是,一个父进程可能有多个子进程,子进程必须得用pid来进行标识区分,所以一般给父进程返回子进程的pid来控制子进程。子进程想知道父进程pid可以通过get_ppid( )来获取。这样就可以维护父子进程了。

(2)根据fork返回值让父子进程执行不同的功能

通过返回值来让父子进程分流,去执行不同的功能:

#include<stdio.h>
#include<unistd.h>int main()
{pid_t ret = fork();//通过if else来分流if(ret == 0)//child{while(1){printf("I am child, pid = %d,ppid = %d\n",getpid(),getppid());sleep(1);}}else if(ret > 0)//parent{while(1){printf("I am parent, pid = %d,ppid = %d\n",getpid(),getppid());sleep(3);}}else{}return 0;
}

在这里插入图片描述

五、进程状态

1.进程状态定义

一个进程从创建而产生至撤销而消亡的整个生命期间,有时占有处理器执行,有时虽可运行但分不到处理器、有时虽有空闲处理器但因等待某个事件的发生而无法执行,这说明进程和程序不相同,它是活动的且有状态变化的,能够体现一个进程的生命状态,可以用一组状态来描述:
在这里插入图片描述
内核源代码里面的状态定义:

/*
* 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 */
};

使用如下两条命令都可以查看进程当前状态:

ps aux
ps axj

在这里插入图片描述

在这里插入图片描述

2.进程状态分类

(1)R-运行状态

R(Running):要么在运行中,要么在运行队列里,所以R状态并不意味着进程一定在运行中,因此系统中可能同时存在多个R状态进程。
在这里插入图片描述
如果运行时在后面加&,就会在后台运行,就变成R状态了:

在这里插入图片描述
在这里插入图片描述

(2)S-浅睡眠状态

S(Sleeping) :进程正在等待某事件完成,可以被唤醒,也可被杀死,浅睡眠状态也叫做可中断睡眠。

比如如下代码:

status.c

int main()
{printf("hello linux\n");sleep(20);return 0;
}

在运行后20s内查看status进程的状态,发现为S+,执行kill命令后,该进程被杀死
在这里插入图片描述

(3)D-深睡眠状态

D(Disk sleep):进程正在等待IO,不能被杀死,必须自动唤醒才能恢复,也叫不可中断睡眠状态。

进程等待IO时,比如对磁盘写入,正在写入时,进程处于深度睡眠状态,需要等待磁盘将是否写入成功的信息返回给进程,因此此时进程不会被杀掉

(4)T-停止状态

T(Stopped):可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送SIGCONT 信号让进程继续运行。

运行起来的status进程,通过SIGSTOP信号被暂停了,状态由S+变为T:
在这里插入图片描述
又通过SIGCONT信号恢复了,状态由T变为S:
在这里插入图片描述
kill -l命令可列出操作系统中所有信号,其中18就是SIGCONT信号,19就是SIGSTOP信号:在这里插入图片描述
因此上述kill SIGCONT 进程号 也可以用kill -18 进程号来代替,kill SIGSTOP 进程号 也可以写成kill -19 进程号来代替。

(5)Z-僵尸状态

概念:
僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且 父进程 没有读取到 子进程 退出的返回代码时就会产生僵尸进程
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

在这里插入图片描述
在这里插入图片描述

(6)X-死亡状态

这个状态只是一个返回状态,在任务列表里看不到这个状态。因为当进程退出时,释放进程所占用的资源时一瞬间就释放完了,所以死亡状态看不到。

3.僵尸进程危害

(1)进程的退出状态必须被维持下去,因为它要把退出信息告诉父进程,如果父进程一直不读取,那么子进程就一直处于僵尸状态

(2)由于进程基本信息是保存在task_struct中的,如果僵尸状态一直不退出,只要父进程没有读取子进程退出信息,那么PCB一直都需要维护。

(3)如果一个父进程创建了多个子进程,并且不回收,那么就要维护多个task_struct 数据结构,会造成内存资源的浪费

(4)僵尸进程申请的资源无法进行回收,那么僵尸进程越多,实际可用的资源就越少,也就是说,僵尸进程会导致内存泄漏

六、孤儿进程

僵尸进程是子进程先退出,但是父进程没有读取子进程的退出信息。

假如父进程先退出,子进程后退出,此时子进程处于孤儿状态,没有父进程来读取它的退出信息,此时子进程就称为孤儿进程。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
孤儿进程一般会被1号进程回收/领养(1号进程就是OS本身)!

七、关于进程状态的补充

1、kill

kill 命令:可以向指定进程发起命令!
① kill -9 XXX :杀掉进程
②kill -19 XXX :暂停进程
在这里插入图片描述
③kill -18 XXX:继续进程
在这里插入图片描述

2、僵尸进程 和 孤儿进程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、进程的状态:运行;挂起;阻塞;

(1)一个进程的运行会在cpu内存中的一个进程队列里面,只要一个进程在队列里面或者已经准备好进入进程队列,他们就是R运行状态。
在这里插入图片描述
(2)并发、并行的概念:
在这里插入图片描述
(3)阻塞态:
①操作系统如何对外设(显示器、键盘、磁盘、网卡…)进行资源管理???
实际上就是对外设的数据进行管理,所有的外设都自己task_struct 里面的数据进行管理即可!

注意:不仅只有CPU有进程队列,各种外设也有自己的wait_queue!!!

在这里插入图片描述
②让一个进程变为阻塞,在内核视角就是:把一个进程从它的运行队列剥离下来,连接到外设的task_struct的wait_queue()里面,此时的进程就变为了 阻塞态

在这里插入图片描述
注意:入队列的 永远只是进程的 task_struct,而不是代码和数据!!!

(4)挂起态:

在这里插入图片描述
注意:但是频繁地唤入唤出,会导致效率问题!!!

八、进程优先级

1.概念

进程的优先级就是CPU资源分配的先后顺序 ,即进程的优先权,优先权高的进程有优先执行权力。

其他概念:

  1. 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发
  2. 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行

2.为什么要有进程优先级

因为CPU资源是有限的,一个CPU只能同时运行一个进程,当系统中有多个进程时,就需要进程优先级来确定进程获取CPU资源的能力。

3. 查看系统进程

ps -l

在这里插入图片描述
可以看到

UID : 代表执行者的身份,表明该进程由谁启动
PID : 代表这个进程的代号
PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
PRI :代表这个进程可被执行的优先级,其值越小越早被执行
NI :代表这个进程的nice值

4.PRI和NI

PRI是进程的优先级,也就是就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高

  1. NI就是nice值,表示进程可被执行的优先级的修正数值
  2. PRI值越小越快被执行,加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
  3. 当nice值为负值时,该程序优先级值将变小,即其优先级会变高,则其越快被执行
  4. Linux下调整进程优先级,就是调整进程nice值
  5. nice其取值范围是-20至19,一共40个级别。

注意: nice值不是进程的优先级,是进程优先级的修正数据,会影响到进程的优先级变化。

5.使用top命令更改进程优先级

(1)更改NI值

先运行一个进程,使用

ps -l

查看进程号、优先级及NI值,比如执行./forkProcess_getpid进程:
在这里插入图片描述

可以查看到优先级为80,NI值为0:
在这里插入图片描述
在运行top命令之后,输入r,就会有PID to renice,此时输入进程号5255,再输入NI值,此处设为10:
然后查看进程的优先级和NI值,优先级变成了90,NI值变成了10:
在这里插入图片描述
说明优先级和NI值已经被改了。由此也能验证:

PRI(new) = PRI(old)+nice

PRI(old)一般都是80,这就是为什么没有修改NI值之前,用ps -al命令查看到的进程的PRI都是80的原因。

(2)NI的取值范围

NI的取值范围为-20~19,一共40个级别。

(3)NI取值范围较小的原因

因为优先级再怎么设置,也只能是一种相对的优先级,不能出现绝对的优先级,否则会出现很严重的进程“饥饿问题”,即某个进程长时间得不到CPU资源,而调度器需要较为均衡地让每个进程享受到CPU资源。

九、环境变量

1.概念

  • 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
  • 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找
  • 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性

2.常见环境变量

  1. PATH : 指定命令的搜索路径
  2. HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
  3. SHELL : 当前Shell,它的值通常是/bin/bash。
    在这里插入图片描述

3.如何查看环境变量

echo $PATH

在这里插入图片描述
系统通过PATH进行路径查找,查找规则就是,在PATH中先在第一个路径中找,找不到就在第二个路径中找,再找不到就在第三个路径中找……,如果找到了就不往下找了,直接将找到的路径下的程序运行起来,这就完成了路径查找。即系统执行命令时,操作系统通过环境变量PATH,去搜索对应的可执行程序路径。

如何让一个可执行程序:Progress执行时不带./,跟执行系统命令一样,有2种做法:

  1. 把Progress命令拷贝到以上5种任意一个路径里,不过这种做法不推荐,会污染命令池
  2. 把当前路径添加到PATH环境变量中

平时安装软件,就是把软件拷贝到系统环境变量中特定的命令路径下就完成了,安装的过程其实就是拷贝的过程。

不能直接把当前路径赋值给PATH,否则上面的6种路径就全没了。可以使用export导入环境变量:

export PATH=$PATH:程序路径	

查找到 forkProcess的路径:
在这里插入图片描述
添加环境变量:
在这里插入图片描述
现在在其他路径下也可以执行该可执行程序了,比如在家目录下执行:
在这里插入图片描述

4.和环境变量相关的命令

环境变量的本质是操作系统在内存/磁盘上开辟的空间,用来保存系统相关的数据。在语言上定义环境变量的本质是在内存中开辟空间,存放key、value值,即变量名和数据。

  1. echo:显示某个环境变量值
  2. export:设置一个新的环境变量
  3. env:显示所有环境变量
  4. set:显示本地定义的shell变量和环境变量
  5. unset:清除环境变量

1、echo显示某个环境变量值
在这里插入图片描述
2、export设置一个新的环境变量
在这里插入图片描述
3、env显示所有环境变量
在这里插入图片描述

4、set显示本地定义的shell变量和环境变量
在这里插入图片描述
5、unset清除环境变量
在这里插入图片描述

5.环境变量的全局属性

环境变量通常具有全局属性,可以被子进程继承。
如下代码:

geteEnvironment.c

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>int main()
{printf("pid = %d,ppid = %d\n",getpid(),getppid());return 0;
}

发现每次运行该程序,子进程的ID都不相同,但是父进程的ID都相同
在这里插入图片描述
命令行上启动的进程,父进程都是bash,bash的环境变量是从系统里读的,系统的环境变量就在系统配置中,bash登陆时,bash就把系统的配置导入到自己的上下文当中。子进程的环境变量是系统给的,也就是父进程bash给的。环境变量一旦导出是可以影响子进程的

6.本地变量

与环境变量相对的还有本地变量,针对当前用户的当前进程生效,是一种临时变量,退出本次登陆后就失效了。

如下,变量value的值在没有退出登录前,打印到是5,ctrl+d退出登录后
在这里插入图片描述
退出重新登录后,就不存在了

在这里插入图片描述
本地变量能被子进程继承吗?用env查看,发现shell的上下文中是没有的:

在这里插入图片描述

说明本地变量是不能被继承的,只能bash自己用。

十、程序地址空间

1.程序地址空间分布

C/C++程序地址空间:在这里插入图片描述

2.程序地址空间是虚拟地址

先看一段下面的代码,子进程在运行过程中修改了全局变量的值:

printfFork.c

#include<stdio.h>
#include<string.h>
#include<unistd.h>int g_Value = 1;int main()
{//发生写时拷贝时,数据是父子进程各自私有一份if(fork() == 0)//子进程{int count = 5;while(count){printf("child,times:%d,g_Value = %d,&g_Value = %p\n",count,g_Value,&g_Value);count--;sleep(1);if(count == 3){printf("############child开始更改数据############\n");g_Value = 5;printf("############child数据更改完成############\n");}}}else//父进程{while(1){printf("father:g_Value = %d,&g_Value = %p\n",g_Value,&g_Value);sleep(1);}}return 0;
}

但是打印时却发现,同一个地址,g_Value值却不一样:
在这里插入图片描述
如果写时拷贝访问的是同一个物理地址的话,为什么得到的g_Value是不一样的值呢?所以程序地址空间使用的不是物理地址,而是虚拟地址。

3.虚拟地址

(1)页表

页表是一种数据结构,记录页面和页框的对应关系,本质是映射表,增加了权限管理,隔离了地址空间,能够将虚拟地址转换成物理地址。操作系统为每个进程维护一张页表。

(2)如何理解地址空间?

地址空间本质:是内核的一个struct结构体,包含的都是各个区域的start和end

(2)为什么要存在地址空间?

①地址空间保证了每个进程以统一的视角(有序的区域划分)进行管理,从而实现了进程的独立性。

在这里插入图片描述
②可以对:进程管理模块 和 物理管理模块 进行解耦!
具体来说,操作系统可以在加载进程时确定映射关系,之后物理内存的分配和进程的管理就可以各自独立进行,互不影响。

在这里插入图片描述

③保护数据安全,拦截非法请求!
例如,当某个进程试图访问其未被授予权限的内存区域时,操作系统能够检测到这种非法访问,并采取相应的措施,如终止该进程,以防止数据泄露或损坏。

4.写时拷贝

深度理解写时拷贝:
写车拷贝针对于父进程和子进程,或者多个子进程之间:指向同一块物理内存其数据也相同,当某一个进程想要修改数据或者写入的时候,操作系统就会在物理内存里面重新开辟一块空间,然后会把原来的数据原模原样地拷贝到新开物理内存空间中,然后再经过页表对物理地址的重新进行了映射,然后将你想要修改或者写入的数据,通过页表新映射关系找到那块开的物理内存空间,然后对数据进行修改或者写入!

(注意:1、写时拷贝是因为当你想要入的数据被按下了暂停键,因为操作系统需要在物理内存中新开一块空间+修改页表的映射关系,所以叫做写时拷贝 2、你写入或者修改的数据不是直接进了新开物理内存,而是操作系统在新开这块物理内存的时候,是将原来的数据拷贝到新开物理空间,之后才会修改或者写入 3、写时拷贝只会在你100%确实要写入或者修改数据的时候才会发生,如果你不写是不会发生的)

相关文章:

【Linux】--- 进程的概念

【Linux】--- 进程的概念 一、进程概念二、PCB1.什么是PCB2.什么是task_struct&#xff08;重点&#xff01;&#xff09;3.task_struct包含内容 三、task_struct内容详解1.查看进程&#xff08;1&#xff09;通过系统目录查看&#xff08;2&#xff09;通过ps命令查看&#xf…...

Unity NTPComponent应用, 实现一个无后端高效获取网络时间的组件

无后端高效获取网络时间的组件 废话不多说&#xff0c;直接上源码m_NowSerivceTime 一个基于你发行游戏地区的时间偏移&#xff0c; 比如北京时区就是 8, 巴西就是-3&#xff0c;美国就是-5using Newtonsoft.Json; 如果这里报错&#xff0c; 就说明项目没有 NewtonsoftJson插件…...

go语言使用zlib压缩[]byte

在Go语言中&#xff0c;可以使用compress/flate和compress/zlib包来实现对[]byte数据的Zlib压缩。下面是一个简单的示例&#xff0c;展示如何使用这些包来压缩一个字节切片&#xff1a; go package main import ( "bytes" "compress/zlib" "fmt"…...

Windows 配置 Tomcat环境

Windows配置Tomcat 1. 介绍 Tomcat是一个开源的、轻量级的Java应用服务器&#xff0c;在Java Web开发领域应用广泛。以下是关于它的详细介绍&#xff1a; 一、基本概念与背景 定义&#xff1a;Tomcat是Apache软件基金会&#xff08;Apache Software Foundation&#xff09;下…...

【python从入门到精通】-- 第六战:列表和元组

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;重生之我在学Linux&#xff0c;C打怪之路&#xff0c;python从入门到精通&#xff0c;数据结构&#xff0c;C语言&#xff0c;C语言题集&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持…...

Python | 数据可视化中常见的4种标注及示例

在Python的数据可视化中&#xff0c;标注&#xff08;Annotation&#xff09;技术是一种非常有用的工具&#xff0c;它可以帮助用户更准确地解释图表中的数据和模式。在本文中&#xff0c;将带您了解使用Python实现数据可视化时应该了解的4种标注。 常见的标注方式 文本标注箭…...

LearnOpenGL学习(高级OpenGL -> 高级GLSL,几何着色器,实例化)

完整代码见&#xff1a;zaizai77/Cherno-OpenGL: OpenGL 小白学习之路 高级GLSL 内建变量 顶点着色器 gl_PointSoze : float 输出变量&#xff0c;用于控制渲染 GL_POINTS 型图元时&#xff0c;点的大小。可用于粒子系统。将其设置为 gl_Position.z 时&#xff0c;可以使点…...

Scala学习记录

dao --------> 数据访问 mode ------> 模型 service ---->业务逻辑 Main -------> UI:用户直接操作&#xff0c;调用Service 改造UI层&#xff1a;...

vue使用pdfh5.js插件,显示pdf文件白屏

pdfh5&#xff0c;展示文件白屏&#xff0c;无报错 实现效果图解决方法(降版本)排查问题过程发现问题查找问题根源1、代码写错了&#xff1f;2、预览文件流的问题&#xff1f;3、pdfh5插件更新了&#xff0c;我的依赖包没更新&#xff1f;4、真相大白 彩蛋 实现效果图 解决方法…...

docker login 出错 Error response from daemon

在自己的Linux服务器尝试登陆docker出错 输入完用户密码之后错误如下&#xff1a; 解决方案 1.打开daemo文件&#xff1a; vim/etc/docker/daemon.json 2.常用的国内Docker 镜像源地址 网易云 Docker 镜像&#xff1a;http://hub-mirror.c.163.com 百度云 Docker 镜像&#x…...

Web 身份认证 --- Session和JWT Token

Web 身份认证 --- Session和JWT Token 方法一: 通过使用Session进行身份认证方法二: 通过JWT token进行身份认证什么是JWTJWT完整流程JWT攻防JWT 如何退出登录JWT的续签 方法一: 通过使用Session进行身份认证 用户第一次请求服务器的时候&#xff0c;服务器根据用户提交的相关信…...

UE5制作倒计时功能

设置画布和文本 文本绑定 格式化时间 转到事件图表&#xff0c;计算时间&#xff0c;时间结束后面的事件可以按自己需求写 进入关卡蓝图&#xff0c;添加倒计时UI...

Linux去除注释和空行

平时查看某些配置文件的时&#xff0c;我们会发现有很多注释&#xff08;如&#xff1a;"#"开头的行&#xff09;&#xff0c;中间还有很多空行&#xff0c;看起来非常费劲&#xff0c;所以在这里总结下如何去除注释和空行的方法。 举例说明 这里选个简单点的文件&a…...

Elasticsearch 7.x入门学习-Spring Data Elasticsearch框架

1 Spring Data框架 Spring Data 是一个用于简化数据库、非关系型数据库、索引库访问&#xff0c;并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷&#xff0c;并支持 map-reduce 框架和云计算数据服务。 Spring Data 可以极大的简化 JPA的写法&#xff0c;…...

网络层IP协议(TCP)

IP协议&#xff1a; 在了解IP协议之前&#xff0c;我们市面上看到的"路由器"其实就是工作在网络层。如下图&#xff1a; 那么网络层中的IP协议究竟是如何发送数据包的呢&#xff1f; IP报头&#xff1a; IP协议的报头是比较复杂的&#xff0c;作为程序猿只需要我们重…...

计算机视觉中的边缘检测算法

摘要&#xff1a; 本文全面深入地探讨了计算机视觉中的边缘检测算法。首先阐述了边缘检测的重要性及其在计算机视觉领域的基础地位&#xff0c;随后详细介绍了经典的边缘检测算法&#xff0c;包括基于梯度的 Sobel 算子算法、Canny 边缘检测算法等&#xff0c;深入剖析了它们的…...

js 常用扩展方法总结+应用

文章目录 js 常用扩展方法总结扩展方法应用选择大型项目 中扩展方法应用选择小型项目中 扩展应用 js 常用扩展方法总结 函数原型&#xff08;prototype&#xff09;扩展方法 介绍&#xff1a;在JavaScript中&#xff0c;通过修改函数的prototype属性可以为该函数创建的所有对象…...

数据结构---图(Graph)

图&#xff08;Graph&#xff09;是一种非常灵活且强大的数据结构&#xff0c;用于表示实体之间的复杂关系。在图结构中&#xff0c;数据由一组节点&#xff08;或称为顶点&#xff09;和连接这些节点的边组成。图可以用于表示社交网络、交通网络、网络路由等场景。 1. 基本概…...

前端解析超图的iserver xml

前端解析超图的iserver xml const res await axios.get(url)const xmlDom new DOMParser().parseFromString(res.data, text/xml);// 获取versionconst version xmlDom.getElementsByTagNameNS(*, ServiceTypeVersion)[0].textContent// 获取layerconst layerDom xmlDom.ge…...

LocalForage 使用指南:统一管理 LocalStorage、WebSQL 和 IndexedDB

前言 在前端开发中&#xff0c;客户端数据存储是一个至关重要的环节。无论是用户偏好设置、缓存内容&#xff0c;还是表单数据&#xff0c;都需要一个高效、可靠的存储方案。浏览器原生提供的 LocalStorage、SessionStorage 和 IndexedDB 等 API 虽然功能强大&#xff0c;但使…...

代码随想录算法训练营第五天-哈希-242.有效的字母异位词

这道题的总体感觉不是很难&#xff0c;但是其完成的思想还是很有趣的利用数据下标来代表字母序列然后遍历两个字符串每个字符&#xff0c;给对应字母下标的数组中一个自增&#xff0c;另一个自减通过查看最后的数组内容是不是0&#xff0c;来判断是不是异位词 #include <io…...

学习maven(maven 项目模块化,继承,聚合)

前言 本篇博客的核心&#xff1a;理解maven 项目模块化&#xff0c;继承&#xff0c;聚合 的含义 maven 项目模块化 含义 maven项目模块化&#xff1a;使用maven 构建项目&#xff0c;管理项目的方式&#xff0c;我们可以将maven项目根据内在的关系拆分成很多个小项目【模块】…...

KDD 2025预讲会:10位一作的论文分享与话题思辨|12月18日全天直播

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 圆桌思辨&#xff1a;一作们的KDD 2025投稿经验分享与热点探讨 1. KDD 2025 与往年相比有哪些新变化&#xff1f;两次投稿周期的新规则有哪些影响&#xff1f; 2. 第一篇KDD的工作是如何成功被接收的&#xff1…...

掌握特征提取:机器学习中的 PCA、t-SNE 和 LDA模型

文章目录 一、说明二、既然有 PCA 技术降维&#xff0c;为什么还要学习 t-SNE&#xff1f;2.1 t-SNE的核心思想&#xff1a;2.2 保持点之间的局部关系有什么意义&#xff1f;2.3 t-SNE 的几何直觉&#xff1a; 三、t-SNE 的数学公式&#xff1a;四、目标函数&#xff1a;五、梯…...

JAVA基础:注释

JAVA基础:注释 作用 使得代码中的一段文本不被执行,起到解释说明的作用。 分类 JAVA中的注释有三种: 单行注释 //单行注释多行注释 /* 多 行 注 释 */文档注释 /***@deprecated comments* @author lhy*/文档注释可以添加一些参数作为说明。 有趣的代码注释 卡车/* * *…...

从源码构建安装Landoop kafka-connect-ui

背景 部署Landoop kafka-connect-ui最简单的办法还是通过docker来部署&#xff0c;我们之前的kafka-connect-ui就是通过docker部署的&#xff0c;但是&#xff0c;最近发现个问题&#xff1a;当使用docker部署且防火墙使用的是firewalld的情况下&#xff0c;就会出现端口冲突。…...

【自动驾驶】Ubuntu22.04源码安装Autoware Core/Universe

【自动驾驶】Ubuntu22.04源码安装Autoware Core/Universe 官方源码安装教程前置条件安装ROS2 Humble安装Autoware Core/Universe配置开发环境配置工作空间设置控制台 官方源码安装教程 链接&#xff1a;https://autowarefoundation.github.io/autoware-documentation/main/ins…...

使用Nexus3搭建npm私有仓库

一、npm介绍 npm的全称是Node Package Manager&#xff0c;它是一个开放源代码的命令行工具&#xff0c;用于安装、更新和管理Node.js模块。npm是Node.js的官方模块管理器&#xff0c;它允许用户从一个集中的仓库中下载和安装公共的Node.js模块&#xff0c;并将这些模块集成到…...

OpenHarmony和OpenVela的技术创新以及两者对比

两款有名的国内开源操作系统&#xff0c;OpenHarmony&#xff0c;OpenVela都非常的优秀。本文对二者的创新进行一个简要的介绍和对比。 一、OpenHarmony OpenHarmony具有诸多有特点的技术突破和重要贡献&#xff0c;以下是一些主要方面&#xff1a; 架构设计创新 分层架构…...

【LeetCode每日一题】Leetcode 1071.字符串的最大公因子

Leetcode 1071.字符串的最大公因子 题目描述&#xff1a; 对于字符串 s 和 t&#xff0c;只有在 s t t t … t t&#xff08;t 自身连接 1 次或多次&#xff09;时&#xff0c;我们才认定 t 能除尽 s。 给定两个字符串 str1 和 str2 。返回 最长字符串 x&#xff0c;要…...