一、进程
进程:是一个正在运行的程序
PCB : 即是进程控制块,是进程存在的唯一标志。用来描述进程的属性信息,如进程的pid。
每一个进程都是通过fork复制而来的。
在执行fork之后,先将PCB复制一份给子进程,复制之前先申请一个pid,将复制给子进程的PCB中的pid修改为申请成功的pid;然后把当前的进程复制一份给子进程。父子进程并发运行。
二、进程的地址空间
以32位为例:
在32位的计算机系统中进程的地址空间是4G,如下图所示:
通常定义一个空指针时int *p = NULL;
,这个指针p指向的空就是指向的上图中的0x0000 0000
。
地址空间除了内核使用的1G,剩下的3G实际上能用的内存空间是小于3G的。
虽然一个进程的地址空间为4G,但是实际上在执行一个程序的时候并不会把4G的内存空间用完,其中代码段和数据段使用几个页面就足够了,而一个页面的内存是4K,所以执行一个程序实际上用到的内存比4G小很多,所以并不是16G的计算机只能运行4个进程。这个4G是规定一个进程最大占用4G的内存空间,也就是一个进程理论上拥有4G地址空间的权限,一般情况下不会用完。
在地址空间中,代码段是从0x0804 8000
开始,是一个固定值:
内核和用户使用空间的分界线的上限为0xc000 0000
,下限为0xbfff ffff
:
1.定义在栈上的变量
由于栈是从上往下增长,所以在栈上定义的局部变量的地址就离0xbfff ffff
非常接近。在栈上定义的变量每次运行的地址是不一样的,每次运行程序栈在分配的时候故意让局部变量的地址不一样,以确保程序的安全性。
2.在堆上申请动态内存
(1)malloc申请的内存空间当程序结束后会被系统自动回收
在Linux中,用malloc申请一块1G的内存空间,不用free释放也可以,不会发生内存泄漏,因为进程终止之后,所申请的内存空间就会被回收,但是当进程运行的时间长时,也是需要用free来释放的。在C语言中,malloc分配一段内存空间之后必须用free释放掉这块内存空间,不然会发生内存泄露。但是,我们一般在使用malloc之后是需要通过free释放空间的。
(2)malloc能申请多大的内存空间
32位系统内存空间的理论值小于3G,参考地址空间,如上图。
①当物理内存剩余的内存空间大于要申请的内存空间的时候是可以申请成功的。
比如现在物理内存剩余的内存空间为1.8G,要用malloc申请1G的内存空间是可以申请成功的。
②当前物理内存剩余的空间加虚拟内存剩余的空间能否满足申请要求。
当malloc申请内存2G内存空间,当物理内存剩余的空间加虚拟内存剩余的空间大于2G,就可以申请成功。如果分配在虚拟内存的空间不去使用,那么它就一直在虚拟内存,不会被调用回物理内存。如果物理内存的剩余空间加上虚拟内存的剩余空间不能满足申请内存空间的要求,那就申请失败,这里的满足还要考虑自身的特性,自身最多只能申请接近3G。如果没有虚拟内容就会申请失败。
③当前物理内存剩余的空间加虚拟内存剩余的空间比所申请的空间大,但是申请失败。
当malloc申请3G的内存空间,虽然物理内存和虚拟内存加起来超过了3G,但是不会申请成功,因为用户可以使用的内存空间为3G,除去代码段、数据段之后留给堆区的内存空间是不够3G的。用户最多只能申请接近3G
④malloc 与 fork,父进程堆区申请的空间复制后,子进程也会有一份,也需要释放吗?
父子进程对申请的堆空间都没有操作,代码如下:
父进程在堆区申请了5个字节大小为int型的内存,通过fork复制之后,产生一个子进程,并在父进程和子进程结束前都执行了free。
编译以上代码,并运行,编译运行并没有出错:
所以,我们可以知道,父进程在堆区申请的内存空间复制一份给子进程之后,子进程并不共享父进程的内存空间,父子进程在堆区都会有一份内存空间。因为如果这段内存空间是共享的,那么父进程对这段内存空间free一次,子进程再对这段内存空间free一次,对同一个内存空间free两次,编译运行会出现错误。而此时编译运行并没有出错,所以父子进程堆空间不共享(这里指的是每个进程的堆空间),哪怕父子进程对申请的堆空间都没有操作。
现在父子进程对申请的堆空间进行操作,代码如下:
上述代码,让子进程中的arr[0]等于10,让父进程中的arr[1]等于100。
编译并运行以上代码,结果如下:
结果分析:输出的结果中,第一行是父进程输出的结果,第二行是子进程输出的结果,可以看出父子进程对堆空间里的值进行修改,父进程对堆空间的值进行修改并没有导致对应子进程堆空间的值被修改,子进程对堆空间的值进行修改也没有导致对应父进程堆空间的值被修改。更加可以说明,父子进程是不共享堆上的内存空间的。子进程也需要释放从父进程复制过来的堆上的内存空间。
malloc申请的空间是对每个进程的物理地址而言的,只有当用到申请的空间,才会映射到底层真时物理地址。
【注意】
同一个程序在每次运行的时候,代码的逻辑地址不会变,物理地址会变,但是物理地址我们看不到,我们能看到的打印出来的地址都是逻辑地址。
如果一个函数没有被主函数调用也会给该函数分配内存空间。