-
2021-05-08 23:22:39
测量一个进程占用了多少内存,Linux系统为我们提供了一个很方便的方法,/proc目录为我们提供了所有的信息,实际上top等工具也通过这里来获取相应的信息。/proc目录相关项解释:
/proc/meminfo 机器的内存使用信息
/proc/pid/maps pid为进程号,显示当前进程所占用的虚拟地址。
/proc/pid/statm 进程所占用的内存
654 57 44 0 0 334 0
statm输出解释:
参数 解释
Size (pages) 任务虚拟地址空间的大小 VmSize/4
Resident(pages) 应用程序正在使用的物理内存的大小 VmRSS/4
Shared(pages) 共享页数 0
Trs(pages) 程序所拥有的可执行虚拟内存的大小 VmExe/4
Lrs(pages) 被映像到任务的虚拟内存空间的库的大小 VmLib/4
Drs(pages) 程序数据段和用户态的栈的大小 (VmData+ VmStk )4
dt(pages) 04
查看机器可用内存:
total used free shared buffers cached
Mem: 1023788 926400 97388 0 134668 503688
-/+ buffers/cache: 288044 735744
Swap: 1959920 89608 1870312
我们通过free命令查看机器空闲内存时,会发现free的值很小。这主要是因为,在Linux系统中有这么一种思想,内存不用白不用,因此它尽可能的cache和buffer一些数据,以方便下次使用。但实际上这些内存也是可以立刻拿来使用的。所以 空闲内存=free+buffers+cached=total-used
linux下进程占用内存空间举例说明如下:
[root@pczou pczou]# cat ./prog.c
#include "stdio.h"
#define ONEM (1024*1024)
int func()
{
char s[16*ONEM];
char* p;
p = malloc(32*ONEM);
pause();
return 0;
}
int main()
{
printf("pid: %dn", getpid());
func();
return 0;
}
ps命令显示的结果:USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 4238 0.0 0.0 52396 352 pts/0 S 21:29 0:00 ./prog
VSZ指的是进程内存空间的大小,这里是52396KB;RSS指的是驻留物理内存中的内存大小,这里是352KB。
VSZ并不代表进程真正用到的内存,因为有些空间会仅在页表中挂个名,也就是说只是虚拟存在着,只有真正用到的时候内核才会把虚拟页面和真正的物理页面映射起来。比如,prog.c中用malloc()分配的32MB内存,由于程序中并没有用到这些内存,没有物理内存被分配,也就不应算到进程的帐上。
内存占用结果总结:
Virtual memory : 52396 KB
Effective VM : 52120 KB
Mapped : 352 KB
Effective mapped: 76.6 KB
Sole use : 72 KB
Per file memory use
ld-2.3.4.so : VM 94208 B, M 90112 B, S 8192 B
prog : VM 8192 B, M 8192 B, S 8192 B
libc-2.3.4.so : VM 1180 KB, M 221184 B, S 16384 B
更多相关内容 -
Linux 进程间通信 之 内存映射 [Linux高并发服务器开发]
2022-01-10 23:05:32内存映射 (Memory-mapped I/O)是将磁盘文件的数据映射到内存,用户通过修改内存从而修改磁盘文件。 对内存直接操作,因此效率比较高。 内存映射相关的系统调用: //建立内存映射 void *mmap(void* addr, size_t ...目录
一、内存映射原理
内存映射 (Memory-mapped I/O)是 将磁盘文件的数据映射到内存,用户通过修改内存从而修改磁盘文件。 对内存直接操作,因此效率比较高。
根据示意图,将文件的数据映射到虚拟地址空间, 进程的虚拟地址空间最终会对应实际的内存。 会映射到共享库,加载的内存区域。
可以只映射文件中的一部分数据。 修改内存中的数据,操作系统会将内存中的数据 同步到 文件中,从而通过修改内存数据,实现了修改文件中的数据。
另外的进程,可以通过读取文件的数据,读取到数据。 通过这种方式,可以实现进程间通信。实际的效率相对较高,因为是对内存的操作。
二、内存映射的操作
1.内存映射相关的系统调用:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
-功能:将一个文件或者设备的数据映射到内存中
-参数:
-void* addr: NULL, 由内核指定
-length:要映射的数据的长度,这个值不能为0。 建议使用文件的长度。
获取文件的长度:stat lseek
如果分配的长度没有达到内存分页的长度,会按照内存分页的长度进行创建。
-prot:对申请的内存映射区的操作权限
同时拥有读写的权限:PROT_READ | PROT_WRITE
PROT_EXEC Pages may be executed.
PROT_READ Pages may be read.
PROT_WRITE Pages may be written.
PROT_NONE Pages may not be accessed.
要操作映射区内存, 必须要有读的权限:
PROT_READ 或 PROT_READ | PROT_WRITE
-flags:
MAP_SHARED: Share this mapping.
映射区的数据会自动和磁盘文件进行同步,进程间通信,必须要设置这个选项。
MAP_PRIVATE: Create a private copy-on-write mapping.
不同步,内存映射区的数据改变了,对原来的文件不会修改,底层会重新创建一个新的文件(copy-on-write)
-fd:需要映射的那个文件的文件描述符
通过open得到,open的是一个磁盘文件
注意:文件的大小不能为0, open指定的权限不能和prot参数有冲突。
prot:PROT_READ open:只读 或 读写
prot:PROT_READ | PROT_WRITE open:读写
-offset:偏移量,一般不用,必须指定的是 4K 的整数倍。 0表示不偏移
- 返回值:
成功,返回映射的地址。On success, mmap() returns a pointer to the mapped area.
失败,On error, the value MAP_FAILED (that is, (void *) -1) is returned.
int munmap(void *addr, size_t length);
- 功能:释放内存映射
- 参数:
-addr:要释放的内存的首地址
-length:要释放的内存的大小,要和mmap函数中的length参数的值一样
-返回值:
成功,返回 0
失败,返回 -1
使用内存映射实现进程间通信
1.有关系的进程(父子进程)
- 还没有子进程的时候:
通过唯一的父进程,先创建内存映射区, 有了内存映射区以后,创建子进程, 父子进程共享创建的内存映射区
2.没有关系的进程间通信:
-准备一个大小不是0的磁盘文件
进程1通过磁盘文件创建内存映射区,得到一个操作这块内存的指针
进程2通过对同一个磁盘文件创建内存映射区,得到一个操作这块内存的指针
使用内存映射区通信
注意:内存映射区通信,是非阻塞的。
2.代码示例:
注意:
如下两个示例的程序,都需要先创建一个 test.txt文件,并且在文件中,随便填充一些字符。
2.1 有关系的进程 - 父子进程
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <wait.h> int main() { //1.打开一个文件 int fd = open("test.txt",O_RDWR); int size = lseek(fd,0,SEEK_END); //2.创建内存映射区 void* ptr = mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); if(ptr == MAP_FAILED) { perror("mmap"); exit(0); } //3.创建子进程 pid_t pid = fork(); if(pid>0) { //回收子进程的资源 wait(NULL); //父进程 char buf[64]; strcpy(buf,(char*)ptr); printf("read data:%s\n",buf); } else if(pid == 0) {//子进程 strcpy((char*)ptr, "my son,dear!"); } return 0; }
执行结果:
2.2 独立的没有关系的进程通信
process1
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <wait.h> int main() { //1.打开一个文件 int fd = open("test.txt",O_RDWR); int size = lseek(fd,0,SEEK_END); //2.创建内存映射区 void* ptr = mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); if(ptr == MAP_FAILED) { perror("mmap"); exit(0); } //读数据 char buf[64]; strcpy(buf,(char*)ptr); printf("read data:%s\n",buf); return 0; }
process2
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <wait.h> int main() { //1.打开一个文件 int fd = open("test.txt",O_RDWR); int size = lseek(fd,0,SEEK_END); //2.创建内存映射区 void* ptr = mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); if(ptr == MAP_FAILED) { perror("mmap"); exit(0); } //写数据 strcpy((char*)ptr, "my son well,dear!"); return 0; }
执行结果:
3.使用内存映射实现文件拷贝
思路:
1.对原始的文件进行内存映射
2.创建一个新文件(拓展该文件)
3.把新文件的数据映射到内存中
4.通过内存拷贝将第一个文件的内存数据拷贝到新的文件内存中
5.释放资源功能:将 englis.txt 中的内容拷贝到 cpy.txt 文件中
代码如下:
#include <stdio.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main() { // 1.对原始的文件进行内存映射 int fd = open("english.txt", O_RDWR); if(fd == -1) { perror("open"); exit(0); } // 获取原始文件的大小 int len = lseek(fd, 0, SEEK_END); // 2.创建一个新文件(拓展该文件) int fd1 = open("cpy.txt", O_RDWR | O_CREAT, 0664); if(fd1 == -1) { perror("open"); exit(0); } // 对新创建的文件进行拓展 truncate("cpy.txt", len); write(fd1, " ", 1); // 3.分别做内存映射 void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); void * ptr1 = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd1, 0); if(ptr == MAP_FAILED) { perror("mmap"); exit(0); } if(ptr1 == MAP_FAILED) { perror("mmap"); exit(0); } // 内存拷贝 memcpy(ptr1, ptr, len); // 释放资源 munmap(ptr1, len); munmap(ptr, len); close(fd1); close(fd); return 0; }
执行结果:
实现了 将文件 english.txt 的内容拷贝到了 cpy.txt 中。
三、内存映射的注意事项
1.如果对mmap的返回值(ptr)做++操作(ptr++), munmap是否能够成功?
void* ptr = mmap(...);
ptr++; 可以对其进行++操作
munmap(ptr,len); //错误,地址已经偏移了 ,需要保存最初的地址,才能正确释放内存2.如果open时O_RDONLY, mmap时prot参数指定PROT_READ | PROT_WRITE会怎样?
会产生错误,返回 MAP_FAILED.
open()函数中的权限建议和prot参数的权限保持一致3.如果文件偏移量为1000会怎样?
偏移量应该是4*1024的整数倍,返回MAP_FAILED
4.mmap什么情况下会调用失败?
第二个参数:length = 0
第三个参数:prot只指定了写权限,会失败 ; prot PROT_READ|PROT_WRITE 第5个参数fd通过open函数时指定的O_RDONLY|O_WRONLY5.可以open的时候O_CREAT一个新文件来创建映射区吗?
可以的,但是创建的文件的大小为0的话,不行。
可以对新文件进行扩展: lseek() , truncate()6.mmap后关闭文件描述符,对mmap映射有没有影响?
int fd = open("xxx",);
mmap(,,,,fd,0);
close(fd);
映射区还存在,创建映射区的fd被关闭,没有任何影响。7.对ptr越界操作会怎样?
void* ptr = mmap(NULL,100,,,,,);
4K
越界操作,操作的是非法内存,会产生段错误。 -
Linux内核反向映射机制的详细资料说明
2021-07-07 12:07:43为了系统的安全性,Linux内核将各个用户进程运行在各自独立的虚拟地址空间,用户进程之间通过虚拟地址空间相互隔离,不能相互访问,一个进程的奔溃不会影响到整个系统的异常也不会干扰到系统以及其他进程运行。Linux... -
Linux内核地址映射模型与Linux内核高端内存详解
2021-01-19 17:16:31进程代码中的地址为逻辑地址,经过段页式地址映射后,才真正访问物理内存。段页式机制如下图。linux内核地址空间划分通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间。注意这里是32位内核地址空间划分... -
linux 进程地址空间的创建过程-详解
2019-04-04 11:34:2132位系统的每个进程的地址空间都是0-4G,那相同的进程地址空间是怎么映射到不同的?请看这里 -
linux驱动-映射进程空间
2017-02-12 10:11:44简述:内核映射进程空,就是由进程分配好空间(属于进程独占资源)后,将用户空间虚拟地址,传递到内核,然后内核映射成内核虚拟地址直接访问,此时内核访问的物理空间是位于用户空间。这样的好处是,不再是内核将...简述:
内核映射进程空间,就是由进程分配好空间(属于进程独占资源)后,将用户空间虚拟地址,传递到内核,然后内核映射成内核虚拟地址直接访问,此时内核访问的物理空间是位于用户空间。这样的好处是,内核直接访问进程空间,减少copy动作。
接口:
- 接口要包含的头文件:
#include <linux/mm.h>
- 函数接口:
long get_user_pages(struct task_struct *tsk, struct mm_struct *mm, unsigned long start, unsigned long nr_pages, int write, int force, struct page **pages, struct vm_area_struct **vmas);
功能:内核用来映射进程内存空间
第一个参数: tsk是进程控制块指针,这个参数几乎一直作为 current 传递。
第二个参数: 一个内存管理结构的指针, 描述被映射的地址空间. mm_struct 结构是捆绑一个进程的虚拟地址空间所有部分. 对于驱动的使用, 这个参数应当一直是current->mm。
第三个参数: start 是(页对齐的)用户空间缓冲的地址。要映射进程空间虚拟地址的起始地址。
第四个参数: nr_pages是映射进程空间的大小,单位页。
第五个参数: write是内核映射这段进程空间来读还是写,非0(一般就是1)表示用来写,当然,对于用户空间就只能是读了;为0表示用来读,此时用户空间就只能是写了。
第六个参数: force 标志告知 get_user_pages 来覆盖在给定页上的保护, 来提供要求的
权限; 驱动应当一直传递 0 在这里。
第七个参数: pages是这个函数的输出参数,映射完成后,pages是指向进程空间的页指针数组,如pages[1],下标最大是nr_pages-1。注意,内核要用pages,还需要映射成内核虚拟地址,一般用kmap(),kmap_atomic()
第八个参数: vmas也是个输出参数,vm_area_struct结构体,是linux用来管理虚拟内存的,映射完成后关于虚拟内存的信息就在这结构体里面。如果驱动不用,vmas可以是NULL。
返回值: 返回实际映射的页数,只会小于等于nr_pages。映射:
- 映射的时候要上进程读者锁:
进程旗标(锁/信号量),通过current->mm->mmap_sem来获得。
如:
down_read(¤t->mm->mmap_sem); result = get_user_pages(current, current->mm, ...); up_read(¤t->mm->mmap_sem);
如果内核映射的空间,用来写,写的时候要上进程写锁,在写的时候去读,就会被阻塞:
down_write(current->mm->mmap_sem); ... //向映射的空间写数据 //up_write(current->mm->mmap_sem);
current->mm->page_table_lock.rlock也可以用这个自旋锁实现的读者写者,具体情况考虑。
释放映射:
if (! PageReserved(page)) SetPageDirty(page); page_cache_release(struct page *page);
PageReserved(): 判断是否为保留页,是保留页返回非0,不是返回0。在我们一般映射的页,没经过处理,都不是保留页,返回0。
SetPageDirty(): 简单来说,就是告诉系统这页被修改。PageReserved(),SetPageDirty()定义在include/linux/page-flags.h,对他们的作用理解,兵不是很深,在一般的驱动中可有可无,安全起见,就按照上面形式放在哪里。
page_cache_release()定义在include/linux/pagemap.h,只能是一次释放一个page,多个page用循环多次调用。
思考:
这种映射,主要是用在进程直接I/O,进程直接读写用户空间内存,就可以达到读写I/O。但是也要具体情况看,相对这种映射对系统开销,还是比较大的。
get_user_pages的用法例子,可以看drivers/scsi/st.c用这种映射来实现读者写者,进程是读者,驱动是写者。
在“linux驱动—file_operations异步读写aio_read、aio_write”这章,描述的异步例子基础上做,原始例子如下:
struct kiocb *aki; struct iovec *aiov; loff_t aio_off = 0; struct workqueue *aiowq; void data_a(struct work_struct *work); DECLARE_DELAYED_WORK(aio_delaywork,data_a); ssize_t d_read(struct file *f, char __user *buf, size_t n, loff_t *off) { } void data_a(struct work_struct *work) { int ret = 0; ret = d_read(aki->ki_filp,aiov->iov->iov_base,n,&off); aio_complete(aki,ret ,0); } ssize_t d_aio_read(struct kiocb *ki, const struct iovec *iovs, unsigned long n, loff_t off) { if(is_sync_kiocb(ki)) return d_read(ki->ki_filp,iovs->iov->iov_base,n,&off); else { aki = ki; aiov = iovs; aio_off = off; queue_delayed_work(aiowq,aio_delaywork,100); return -EIOCBQUEUED;//一般都返回这个, } } void init_aio() { aiowq= create_workqueue("aiowq"); }
用上get_user_pages()后,将变成:
#include <linux/list.h> #include <linux/aio.h> #include <linux/workqueue.h> #include <linux/mm.h> struct kiocb *aki; //struct iovec *aiov; //loff_t aio_off = 0; struct workqueue *aiowq; struct page **aiopages; LIST_HEAD(custom_aa); struct custom_async{ list_head list; task_struct *tsk; struct page **pg; long page_num; ssize_t size; }; void data_a(struct work_struct *work); DECLARE_DELAYED_WORK(aio_delaywork,data_a); DECLARE_DELAYED_WORK(aio_delaywork_c,async_d_writedata); struct file_operations { .owner = THIS_MODE, .read = d_read, .aio_read = d_aio_read, ... .. }; ssize_t d_read(struct file *f, char __user *buf, size_t n, loff_t *off) { } ssize_t async_d_writedata(struct work_struct *work) { int i,n; void *p = NULL; struct page **temp = NULL; struct custom_async *t = NULL; struct list_head *tmp = NULL; list_for_each(custom_aa,tmp){ t = container_of(tmp,custom_async,list); for(i=0;i<t->page_num;i++) { temp = t->pg; p = kmap(temp[i]); down_write(&t->tsk->mm->mmap_sem); ... //向p指定的空间写数据 , n为写了多少个字节数据 ... t->size = n; up_write(&t->tsk->mm->mmap_sem); kunmap(aio_pages[i]); } } queue_delayed_work(aiowq,aio_delaywork_c,100); } void data_a(struct work_struct *work) { int ret = 0; struct custom_async *t; //ret = d_read(aki->ki_filp,aiov->iov->iov_base,n,&off); t = container_of(custom_aa.next,custom_async,list); async_d_writedata(); ret = t->size; aio_complete(aki,ret ,0); } ssize_t d_aio_read(struct kiocb *ki, const struct iovec *iovs, unsigned long n, loff_t off) { if(is_sync_kiocb(ki)) return d_read(ki->ki_filp,iovs->iov->iov_base,n,&off); else { aki = ki; //aiov = iovs; //aio_off = off; struct custom_async *p; p = kmalloc(sizeof(struct custom_async)); memset(p,0,sizeof(struct custom_async)); INIT_LIST_HEAD(&p->list); list_add(&p->list,&custom_aa); p->tsk = current; down_read(¤t->mm->mmap_sem); pagenum = get_user_pages(current,current->mm,iovs->iov->iov_base, n/PAGE_SIZE+(n%PAGE_SIZE)?1:0, 1,0,p->pg,NULL); up_read(¤t->mm->mmap_sem); queue_delayed_work(aiowq,aio_delaywork,100); return -EIOCBQUEUED;//一般都返回这个, } } void init_aio() { aiowq= create_workqueue("aiowq"); INIT_LIST_HEAD(custom_aa); } void exit() { int i; ... .. for(i=0;i<pagenum;i++) { if (! PageReserved(aiopages[i])) SetPageDirty(aiopages[i]); page_cache_release(aiopages[i]); } ... .. }
上面的例子,进程都可以通过异步调用来申请映射,当进程收到一次异步调用完成后,不用再次异步调用,只需要读就可以,驱动会自动的向里面写数据。只要来异步调用申请过一次的进程,当有数据时,都会向每个进程空间写数据。典型的读者写者,进程是读者,驱动是写者。
上面例子,只是表达了处理逻辑,没有编译过,模块退出时,释放不完全。
对比一般的异步,内核是直接向进程空间写数据,不用在数据完成后,还需要一次copy,理论上会快些。
-
Linux下进程地址空间(初学者必备)
2022-04-03 15:38:18进程地址空间 一.程序地址空间 首先我们先通过一张图回顾一下c/c++中的程序地址空间: 下面简单的介绍一个这几个区域: 1.堆区: 堆数据区即heap区,在C程序中,该区域的分配和回收由malloc和free进行。...目录
一.程序地址空间
首先我们先通过一张图回顾一下c/c++中的程序地址空间:
下面简单的介绍一个这几个区域:
1.堆区:
堆数据区即heap区,在C程序中,该区域的分配和回收由
malloc
和free
进行。随着区域分配的进行,区域不断从低地址向高地址方向延伸。2.栈区:
stack区,程序运行时,函数调用产生的堆栈存放在该区域。该区域的开始地址是固定的(紧挨着内核内存区),随着调用函数时堆栈的产生,该区域不断从高地址向低地址方向延伸.
3.代码与数据区:
可执行程序文件的内容加载到该区域,该区域又分成2部分,低地址部分包含程序的代码及只读数据,为只读部分;另一区域存放可执行文件的可读写数据,为可读写区。
4.内核存储区:
内核虚拟内存区,在虚拟地址空间的最高地址处的一块地址空间内。该区域用户进程不能访问。
题目通过一段简单的代表对上图的地址空间分部进行验证:
#include<stdio.h> 2 #include<iostream> 3 using namespace std; 4 int g_unval; 5 int g_val=0; 6 int main() 7 { 8 printf("code addr:%p\n",main);//打印代码区的地址 9 const char*str="ddddddd"; 10 printf("string rdonly addr %p\n",str);//打印字符常量区的地址 11 //打印未初始化全局数据区的地址 12 printf("uninit addr: %p\n",&g_unval); 13 //打印已初始化全局数据区 14 printf("init addr: %p\n",&g_val); 15 //打印堆区的地址 16 int *p1=new int (1); 17 int *p2=new int(2); 18 int *p3=new int(3); 19 printf("heap addr: %p \n",p1); 20 printf("heap addr: %p \n",p2); 21 printf("heap addr: %p \n",p3); 22 23 24 //打印栈区的地址 25 int a1=10; 26 int a2=20; 27 int a3=30; 28 printf("stack addr : %p\n",&a1); 29 printf("stack addr : %p\n",&a2); 30 printf("stack addr : %p\n",&a3); 31 32 return 0; 33 } ~
允许结果:
我么发现分部确实是这样的。
下面我们来做一个实验:
#include<unistd.h> 2 #include<stdio.h> 3 #include<sys/types.h> 4 int g_val=10; 5 int main() 6 { 7 pid_t id=fork(); 8 if(id<0) 9 { 10 printf("fork fail"); 11 } 12 else if(id==0) 13 { 14 while(1) 15 { 16 printf(" I am a child pid:%d g_val:%d &g_val:%p\n",getpid(),g_val,&g_val); 17 sleep(2); 18 } 19 20 } 21 else 22 { 23 while(1) 24 { 25 printf("I am a parent pid:%d g_val :%d &g_val:%p\n",getpid(),g_val,&g_val); 26 sleep(2); 27 } 28 29 } 30 31 return 0; 32 } 33
我们将这个代码跑起来:
子进程和父进程的打印出来的g_val和&g_val是一样的没有问题这和我们之前说的父子进程在代码和数据是共享的(不发生修改的前提下).没有问题,下面我们再来看一段代码:
#include<unistd.h> 2 #include<stdio.h> 3 #include<sys/types.h> 4 int g_val=100; 5 int main() 6 { 7 pid_t id=fork(); 8 if(id<0) 9 { 10 printf("fork fail"); 11 } 12 else if(id==0) 13 { 14 int cnt=0; 15 while(1) 16 { 17 cnt++; 18 if(cnt==3) 19 { 20 g_val=10; 21 printf("子进程修改g_val\n"); 22 } 23 printf(" I am a child pid:%d g_val:%d &g_val:%p\n",getpid(),g_val,&g_val); 24 sleep(2); 25 } 26 27 } 28 else 29 { 30 while(1) 31 { 32 printf("I am a parent pid:%d g_val :%d &g_val:%p\n",getpid(),g_val,&g_val); 33 sleep(2); 34 } 35 36 } 37 38 return 0; 39 } 40
我们将这个程序跑起来:
我们发现子进程把g_val的值修改之后由于进程之间是独立的发生了写时拷贝,子进程和父进程打印出来的值不一样我们可以理解但是我们惊奇的发现他们的地址是一样的。如果这里是物理地址,怎么可能从同一块空间里面取里面的内容还不相同。所以我们可以得出:
这绝对不是物理地址。在Linux地址下,这种地址叫做虚拟地址,我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理。
二.进程地址空间
所以我们之前说‘程序的地址空间’是不准确的,准确的应该说成进程地址空间。进程地址空间是OS创建的一个结构题名字叫做mm_struct对虚拟地址空间进行划分其虚拟地址从(0x 00 00 00 00到 oxff ff ff ff)同样的别进程控制块PCB管理起来,每个进程对有自己的进程地址空间,也就是每个进程都认为自己是独占资源认为自己拥有4GB的空间(32位平台下)。所以说进程地址空间其实是一个虚拟地址空间。虚拟地址空间也可以认为是地址空间上进行区域划分时,对应的线性位置虚拟地址!。
虚拟地址空间每个进程都有虚拟地址空间,OS会将虚拟地址通过某种映射关系映射到物理地址上,从而对应到真正的物理地址上。
而这个做转换工作的就是传说之中的页表。下面我们就可以解释上面那种现象了,子进程的创建以父进程为模板,所以父子进程都有虚拟地址空间,且内容基本是一样的(部分数据不一样),且页表的映射关系子进程和父进程是一样的当子进程对数据进行修改时,OS会将子进程中断重新开辟一块空间将数据拷贝过来,然后让子进程修改新开辟的空间。虽然物理地址发生了改变但是虚拟地址没有发生变化,这是改变了子进程中虚拟地址和物理地址的映射关系。所以我们之前看到的地址相同本质时虚拟地址相同。
现在我们也就能够明白父子进程是如何做到独立的了。父子进程代码和数据共享,但是只要有一方尝试对其进行写入,那么也就会方式写时拷贝,修改页表中的映射到物理内存的关系,从而使得父子进程有属于自己的数据,达到独立
为什么要这么设计呢?
理由一:
有了虚拟地址空间,让进程访问物理内存不能直接访问物理内存,添加了一个中间层(页表),更利于管理内存的操作。这样一来每个进程就必须要通过虚拟地址空间和页表来访问对应的物理内存,在将虚拟地址转换为物理地址时操作系统可以进行插手,判断转换之后是不是合法的物理地址,从而保护了物理内存。
理由二:
将内存申请和内存使用的概念在时间上划分清楚,通过虚拟地址空间屏蔽底层申请的一系列操作,从而让OS更好的管理内存和进程。
理由三:
有了虚拟地址空间,每个进程认为自己独占内存资源,以相同的方式看待内存。这样就大大的提高了OS的管理效率
举个例子:CPU在执行代码时,首先要找到程序开始的位置即起始地址,有了虚拟地址空间就只需要查找固定的虚拟地址,不同的进程的进程地址空间有着不同的映射关系,所以这个固定的虚拟地址在不同的进程中会映射到不同的物理地址,查找进程相关的代码和数据,让CPU很快的就能过查找到程序的开始位置。
理由四:
虚拟地址空间可以让地址连续化,降低异常访问及越界的概率。
重新理解进程和进程的创建:
1.什么是进程?进程是被加载到内存中的程序,包括了代码,数据以及OS为之创建的数据结构(PCB(task_struct)+mm_struct(进程地址空间)+页表。而我们通过PCB能够找到对应的mm_struct.
-
Linux进程管理
2022-03-19 14:07:43关于进程,环境变量和程序地址空间的学习 -
Linux进程控制(精讲)
2021-09-23 21:55:48文章目录进程创建fork函数初始fork函数返回值写时拷贝fork常规用法fork调用失败的原因进程终止进程退出场景进程常见退出方法_exit函数exit函数return退出进程等待进程等待的必要性进程等待的方法wait方法waitpid方法... -
【Linux 内核 内存管理】内存映射原理 ① ( 物理地址空间 | 外围设备寄存器 | 外围设备寄存器的物理地址 ...
2022-04-09 23:47:18一、物理地址空间、 二、外围设备寄存器、 三、外围设备寄存器物理地址 映射到 虚拟地址空间、 -
Linux进程间通信
2022-03-28 23:12:32目录进程间通信介绍进程间通信的概念进程间通信目的进程间通信分类进程间通信的本质 进程间通信介绍 进程间通信的概念 进程间通信又称IPC(Inter-Process Communication),指多个进程之间相互通信,交换信息。 进程间... -
【Linux】Linux进程的创建与管理
2018-07-27 19:21:29在Linux系统中,除了系统启动之后的第一个进程由系统来创建,其余的进程都必须由已存在的进程来创建,新创建的进程叫做子进程,而创建子进程的...Linux进程创建的过程示意图如下所示: 子进程的创建 在Li... -
Linux内存映射实现
2022-01-16 17:09:421、内存管理架构图 分为用户空间,内核空间,硬件三个层面: ...虚拟内存管理,负责把用户地址映射成虚拟地址,从进程的虚拟地址空间分配虚拟页 sys_brk()用来扩大缩小堆, sys_mmap()用来内存映 -
Linux进程地址空间
2022-03-27 10:22:36文章目录Linux进程地址空间程序地址空间进程地址空间总结 Linux进程地址空间 程序地址空间 大家在以前学习C/C++的时候可能见过下面这幅图: 那么我现在有一个问题:这个是内存吗??? 这个根本就不是内存!!!你... -
【Linux】Linux进程简介及其五种状态
2018-07-24 21:42:36进程及其私有内存空间 进程类似于UCOSIII中的任务,它也是用户应用程序可执行代码在系统中的一个运行过程。系统中用来表示进程身份和存在的也是控制块,只不过叫做进程控制块。进程与UCOSIII任务之间最重要的一个... -
(渗透测试后期)Linux进程隐藏详解
2022-03-27 17:33:26文章目录(渗透测试后期)Linux进程隐藏详解前言Linux进程基础Linux进程侦查手段Linux进程隐藏手段一、基于用户态的进程隐藏方法1:小隐隐于/proc/pid——劫持readdir系统调用额外:加载至arm方法2:小隐隐于/proc/... -
Linux 进程空间
2018-04-12 15:53:13对于一个进程,其空间分布如下图所示: 程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码。初始化过的数据(Data):在程序运行初已经对变量进行初始化的数据。未初始化过的数据(BSS):在程序运行初未对... -
Linux进程
2021-07-03 16:35:18tags: [进程,Linux] categories: Linux 进程相关命令 解释 ps –aux 列出所有进程 ps -ef 列出所有进程 kill 发送一个特定的信号 (signal) 给 pid 的进程根据该信号而做特定的动作,若没有指定,预设是... -
Linux开发(一):Linux进程的内存布局
2022-03-04 13:57:59对于32位X86架构上运行的Linux进程而言, 其虚拟地址空间的寻址范围从0 ~ 4G,内核将这块空间划分为两个部分,将最高的1G字节0xC0000000 ~ 0xFFFFFFFF称为“内核空间”, 顾名思义是提供给内核使用;而将较低3G字节... -
【Linux操作系统】进程地址空间(虚拟内存、物理内存)
2022-03-20 15:41:27进程地址空间的分布,虚拟内存和物理内存的映射关系,页表 -
Linux内核空间映射到用户空间
2017-07-21 16:26:49两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁... -
Linux下进程间通信 之 共享内存
2021-12-14 17:19:05一、共享内存的概念 共享内存是在两个或多个正在运行的进程之间共享内存区域的一种进程间的通信...空间,各进程只是映射这段物理内存地址到自己的地址空间而已,因此不需要进行复制,可以直接 使用此段空间。就好... -
linux进程用户内存空间和内核空间
2020-06-15 20:02:52每个进程的页面目录就分成了两部分,第一部分为“用户空间”,用来映射其整个进程空间(0x0000 0000-0xBFFF FFFF)即3G字节的虚拟地址;第二部分为“系统空间”,用来映射(0xC000 0000-0xFFFF FFFF)1G字节的... -
Linux用户空间与内核空间
2021-03-02 06:09:52进程代码中的地址为逻辑地址,经过段页式地址映射后,才真正访问物理内存。段页式机制如下图。通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间。注意这里是32位内核地址空间划分,64位内核地址空间... -
Linux进程间通信—— 内存映射
2017-07-29 10:26:08两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁... -
Linux下进程间通信的八种方法
2021-12-14 17:04:57无名管道(pipe) ...=========================================================================(1) 无名管道(pipe):管道允许一个进程和另一个与它有共同祖先的进程之间进行通信。 (2) 命名管 -
【Linux系统编程】进程地址空间和虚拟地址空间
2019-11-08 16:57:36前面介绍过,虚拟地址空间只是一个中间层而已,它的功能是利用一种映射机制将虚拟地址空间映射到物理地址空间,所以,创建 4GB 虚拟地址空间其实并不是要真的创建空间,只是要创建那种映射机制所需要的数据结构而已... -
Linux进程概念(精讲)
2021-09-09 08:54:04文章目录基本概念描述进程-PCB组织进程查看进程通过系统调用获取进程的PID和PPID通过系统调用创建进程- fork初始进程状态运行状态-R浅度睡眠状态-S深度睡眠状态-D暂停状态-T僵尸状态-Z死亡状态-X僵尸进程僵尸进程的... -
【Linux篇】第七篇——进程地址空间(程序地址空间+虚拟地址空间)
2022-03-13 16:47:10⭐️这篇博客就要和大家介绍进程地址空间相关内容,学完这个部分,我们会对进程的地址空间有一个全选的了解 目录程序地址空间进程地址空间 程序地址空间 先看厦门下面一张图,在之前C/C+博客的内存管理中放过这...