进程地址空间

在这里插入图片描述

进程地址空间介绍

可执行程序会被加载到内存中,和pcb形成了进程,在之前的介绍中,我们没有对进程的存储形式进行描述,而进程地址空间就是对进程在内存中的存储形式进行描述的一个数据结构。
所有可执行都要被加载到内存中,每个程序的大小,截然不同,为了我们更加方便管理进程,我们对进程进行了内存级别的划分。
在这里插入图片描述

可以看到,所有进程所占有的内存空间都被划分成了以上几个部分,上述部分我们并不陌生,在我们进行编程的时候,我们就是按照上述规则划分内存的,但是真实的物理内存也是这样划分的吗?

答案是错误的,上述内存区域,以及我们在语言层面进行的如取地址的操作,我们所取出/操作的地址都是虚拟地址。 每个进程都维护一个系统级的页表,页表的作用之一就是将虚拟地址映射到真实的物理地址。

  • 这就能解释之前的一个困惑了,之前在讲述父子进程的时候,我们会发现如果我们在子进程中修改了变量,则子进程和父进程中获得的相同变量的值不同,但是地址却是相同的, 实际上只是虚拟地址相同,在子进程要修改变量时,会发生写时拷贝,当操作系统检测到我们要进行写入操作时,操作系统就会另外开辟一块空间,将父进程内的数据拷贝到子进程新开辟的空间中,然后对新空间进行写入操作,但是与物理地址对应的虚拟地址却没有发生变化,这就是为什么地址相同,但是值却不同的原因。
  • 在我们进行动态内存管理时,我们申请内存的函数/操作符,如malloc,new我们最先申请的也是虚拟内存,这是为了资源利用率最大化,如果直接申请物理内存,但却不即使使用,则必然会导致操作系统的空间利用率下降,所以在malloc,new时先只申请虚拟地址,在使用地址的时候操作系统会检测到虚拟地址无物理地址对应的错误,于是在申请物理地址。

页表初识

页表采用了哈希表这种数据结构,将虚拟地址一一映射到物理地址中,页表中存储的不止只有地址数据,还有权限,在每个地址数据后,都会存在一个权限,如r,w,rw,x等等,这些权限是决定我们能对该地址数据做什么,举个例子。

我们对一个字符串常量进行修改,这是不可以的,在之前的学习中,我们将其解释为字符串常量存储在常量区,常量区的数据无法修改,但在现在,我们就能对此有更深入的了解,我们不能对字符串常量写入数据的根本原因在于字符串常量数据没有写权限
同时,我们还可以深度理解const关键字,const关键字并非是将数据移至常量区,实际上被const修饰的数据如果我们进行了修改,则编译器会在编译过程中就会报错,若不使用const,则会在执行过程报错,const关键字将报错的环节提前了,让我们更容易察觉到错误

为什么要存在进程地址空间和页表

1.进程地址空间的存在让进程可以以统一的,有序的视角看待所有在内存中的进程实体。
2.进程地址空间和页表使得关于内存的资源管理和进程的资源管理耦合度降低了。
3.屏蔽了进程直接访问内存的方式,保护了内存的安全。(如若没有页表则可能导致进程非法访问内存)

进程控制周边

进程终止,要么处于阻塞状态,需要某种资源,要么处于僵尸状态,父进程没对该子进程pcb信息进行回收,为了更好的了解进程等待,我们需要了解进程终止。

进程终止

进程是加载到内存中的可执行程序,程序是由计算机语言写出来的,我们以C/C++为例,我们在写代码的时候,主函数内的return语句其实是该进程的退出码,我们一般返回0,这代表该代码没有发生错误,返回非0,则代表了有错误产生,所有的错误都被errno这个变量维护,每一个错误码,代表了一个错误。
在这里插入图片描述
我们可以打印以下每个错误码代表什么错误。
在这里插入图片描述
main函数也是函数,对于其他函数来说,返回值代表错误码,可以通过返回值来判断该函数出现了 什么错误,同时也可以用返回值来传递该函数的运行结果。
还有一个函数,exit,该函数的作用是结束当前进程,和return 一样,返回值依旧是推出码。

我们可以使用echo $?来打印出最进的退出码。
在这里插入图片描述

这是在代码执行结束的时候,及进程执行完毕的时候会报出的错误,其实有可能在进程未执行完毕时就会报出错误,这些错误都被维护成了各种宏,被称为信号编号,信号编号也是数字,每个数字代表一个运行异常。
通过kill - l,来列出所有的信号选项,我们之前所使用的9号信号就是杀死进程的信号。
在这里插入图片描述
综上,进程的错误信息一共有两种,一种时退出码,一种时信号,父进程需要收集这些信息,以辨别其子进程是否正常运行结束。

进程等待

父进程往往需要收集子进程的信息,否则子进程就会变为僵尸进程,僵尸进程已经处于死亡状态,但是其pcb一直占用内存空间会造成资源浪费,我们如何让父进程收集子进程的信息呢?

wait函数,让进程处于等待状态,并且收集任意子进程的信息,返回值>0则代表pid为该值的进程回收成功,否则代表回收失败。
在这里插入图片描述

在这里插入图片描述
这是我们进行wait函数检测的函数,status可以带回错误信息,我们来看看结果。
在这里插入图片描述

由结果可以得出,wait函数若收集信息成功确实是返回任意子进程的pid,但是在子进程中,我们设置的退出码明明是1,但是为什么status返回的值是256呢?

status要带回2个信息,一个是退回码,一个是信号编号,但是一个整形如何带回2个数值呢?
其实status是有格式的。
在这里插入图片描述
status的前16位无用,后16位的前8个比特位代表退出码,退出码的后一个比特位代表core dump表示,最后7个比特位代表信号编码,这就能解释为什么status返回的是256了,代表没有终止信号,且退出码为1.

  • 我们可以通过位运算得到退出码和终止信号。
    1.status & 0x7f得到终止信号。
    2.status >> 8 & 0x7f得到退出码。
  • 我们一般不会这么做,C提供了两个宏函数WIFEXITED,WEXITSTATUS。
    在这里插入图片描述
    WIFEXITED子进程未收到终止信号而退出,则该宏函数为真。
    WEXITSTATUS函数返回退出码。
    前五秒
    在这里插入图片描述
    后五秒
    在这里插入图片描述

waitpid函数与wait函数的差别在于第一个参数和第三个参数,第一个参数可以指定某一个子进程等待,第三个参数是一个选项,这个选项代表了父进程对子进程的状态。

option = 0,则代表父进程进行等待,直到收集到子进程才返回,是阻塞调用。
在这里插入图片描述
该程序运行后,前五秒只打印前2个printf内容,因为当wait的option参数为0时,父进程会等待子进程,知道子进程结束。
前五秒
在这里插入图片描述
后五秒
在这里插入图片描述


option = WNOHANG这个宏时父进程会进行轮询访问,是为阻塞调用。
在这里插入图片描述
运行后会打印出前两行printf,随后会打印父进程的while循环内的printf,并且此时child_pid=0,5秒后父进程收集到子进程,打印最后的结果。
五秒前
在这里插入图片描述
五秒后
在这里插入图片描述
该函数的返回值代表了几个方面,首先返回0代表函数调用成功,但是没回收到子进程,返回大于0的数,代表回收到子进程了,小于0代表函数没调用成功

  • 14
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值