精华内容
下载资源
问答
  • 进程代码中的地址为逻辑地址,经过段页式地址映射后,才真正访问物理内存。段页式机制如下图。linux内核地址空间划分通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间。注意这里是32位内核地址空间划分...
  • Linux进程间通信—— 内存映射

    千次阅读 2017-07-29 10:26:08
    两个不同进程A、B共享内存的意思是,同一块物理内存映射进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁...

    Linux环境进程间通信(五): 共享内存(上)

    共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。

    采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据[1]:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。

    Linux的2.2.x内核支持多种共享内存方式,如mmap()系统调用,Posix共享内存,以及系统V共享内存。linux发行版本如Redhat 8.0支持mmap()系统调用及系统V共享内存,但还没实现Posix共享内存,本文将主要介绍mmap()系统调用及系统V共享内存API的原理及应用。

    一、内核怎样保证各个进程寻址到同一个共享内存区域的内存页面

    1、page cache及swap cache中页面的区分:一个被访问文件的物理页面都驻留在page cache或swap cache中,一个页面的所有信息由struct page来描述。struct page中有一个域为指针mapping ,它指向一个struct address_space类型结构。page cache或swap cache中的所有页面就是根据address_space结构以及一个偏移量来区分的。

    2、文件与address_space结构的对应:一个具体的文件在打开后,内核会在内存中为之建立一个struct inode结构,其中的i_mapping域指向一个address_space结构。这样,一个文件就对应一个address_space结构,一个address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。因此,当要寻址某个数据时,很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面。

    3、进程调用mmap()时,只是在进程空间内新增了一块相应大小的缓冲区,并设置了相应的访问标识,但并没有建立进程空间到物理页面的映射。因此,第一次访问该空间时,会引发一个缺页异常。

    4、对于共享内存映射情况,缺页异常处理程序首先在swap cache中寻找目标页(符合address_space以及偏移量的物理页),如果找到,则直接返回地址;如果没有找到,则判断该页是否在交换区(swap area),如果在,则执行一个换入操作;如果上述两种情况都不满足,处理程序将分配新的物理页面,并把它插入到page cache中。进程最终将更新进程页表。 
    注:对于映射普通文件情况(非共享映射),缺页异常处理程序首先会在page cache中根据address_space以及数据偏移量寻找相应的页面。如果没有找到,则说明文件数据还没有读入内存,处理程序会从磁盘读入相应的页面,并返回相应地址,同时,进程页表也会更新。

    5、所有进程在映射同一个共享内存区域时,情况都一样,在建立线性地址与物理地址之间的映射之后,不论进程各自的返回地址如何,实际访问的必然是同一个共享内存区域对应的物理页面。 
    注:一个共享内存区域可以看作是特殊文件系统shm中的一个文件,shm的安装点在交换区上。

    上面涉及到了一些数据结构,围绕数据结构理解问题会容易一些。

    二、mmap()及其相关系统调用

    mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。

    注:实际上,mmap()系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。

    1、mmap()系统调用形式如下:

    void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset ) 
    参数fd为即将映射到进程空间的文件描述字,一般由open()返回,同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)。len是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起。prot 参数指定共享内存的访问权限。可取如下几个值的或:PROT_READ(可读) , PROT_WRITE (可写), PROT_EXEC (可执行), PROT_NONE(不可访问)。flags由以下几个常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。offset参数一般设为0,表示从文件头开始映射。参数addr指定文件应被映射到进程空间的起始地址,一般被指定一个空指针,此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址。这里不再详细介绍mmap()的参数,读者可参考mmap()手册页获得进一步的信息。

    2、系统调用mmap()用于共享内存的两种方式:

    (1)使用普通文件提供的内存映射:适用于任何进程之间; 此时,需要打开或创建一个文件,然后再调用mmap();典型调用代码如下:

    	fd=open(name, flag, mode);
    if(fd<0)
    	...

    ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通过mmap()实现共享内存的通信方式有许多特点和要注意的地方,我们将在范例中进行具体说明。

    (2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间; 由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。 
    对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可,参见范例2。

    3、系统调用munmap()

    int munmap( void * addr, size_t len ) 
    该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小。当映射关系解除后,对原来映射地址的访问将导致段错误发生。

    4、系统调用msync()

    int msync ( void * addr , size_t len, int flags) 
    一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。

    三、mmap()范例

    下面将给出使用mmap()的两个范例:范例1给出两个进程通过映射普通文件实现共享内存通信;范例2给出父子进程通过匿名映射实现共享内存。系统调用mmap()有许多有趣的地方,下面是通过mmap()映射普通文件实现进程间的通信的范例,我们通过该范例来说明mmap()实现共享内存的特点及注意事项。

    范例1:两个进程通过映射普通文件实现共享内存通信

    范例1包含两个子程序:map_normalfile1.c及map_normalfile2.c。编译两个程序,可执行文件分别为map_normalfile1及map_normalfile2。两个程序通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。map_normalfile2试图打开命令行参数指定的一个普通文件,把该文件映射到进程的地址空间,并对映射后的地址空间进行写操作。map_normalfile1把命令行参数指定的文件映射到进程地址空间,然后对映射后的地址空间执行读操作。这样,两个进程通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。

    下面是两个程序代码:

    /*-------------map_normalfile1.c-----------*/
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <unistd.h>
    typedef struct{
      char name[4];
      int  age;
    }people;
    main(int argc, char** argv) // map a normal file as shared mem:
    {
      int fd,i;
      people *p_map;
      char temp;
      
      fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
      lseek(fd,sizeof(people)*5-1,SEEK_SET);
      write(fd,"",1);
      
      p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,
            MAP_SHARED,fd,0 );
      close( fd );
      temp = 'a';
      for(i=0; i<10; i++)
      {
        temp += 1;
        memcpy( ( *(p_map+i) ).name, &temp,2 );
        ( *(p_map+i) ).age = 20+i;
      }
      printf(" initialize over \n ");
      sleep(10);
      munmap( p_map, sizeof(people)*10 );
      printf( "umap ok \n" );
    }
    /*-------------map_normalfile2.c-----------*/
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <unistd.h>
    typedef struct{
      char name[4];
      int  age;
    }people;
    main(int argc, char** argv)  // map a normal file as shared mem:
    {
      int fd,i;
      people *p_map;
      fd=open( argv[1],O_CREAT|O_RDWR,00777 );
      p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,
           MAP_SHARED,fd,0);
      for(i = 0;i<10;i++)
      {
      printf( "name: %s age %d;\n",(*(p_map+i)).name, (*(p_map+i)).age );
      }
      munmap( p_map,sizeof(people)*10 );
    }

    map_normalfile1.c首先定义了一个people数据结构,(在这里采用数据结构的方式是因为,共享内存区的数据往往是有固定格式的,这由通信的各个进程决定,采用结构的方式有普遍代表性)。map_normfile1首先打开或创建一个文件,并把文件的长度设置为5个people结构大小。然后从mmap()的返回地址开始,设置了10个people结构。然后,进程睡眠10秒钟,等待其他进程映射同一个文件,最后解除映射。

    map_normfile2.c只是简单的映射一个文件,并以people数据结构的格式从mmap()返回的地址处读取10个people结构,并输出读取的值,然后解除映射。

    分别把两个程序编译成可执行文件map_normalfile1和map_normalfile2后,在一个终端上先运行./map_normalfile2 /tmp/test_shm,程序输出结果如下:

    initialize over
    umap ok

    在map_normalfile1输出initialize over 之后,输出umap ok之前,在另一个终端上运行map_normalfile2 /tmp/test_shm,将会产生如下输出(为了节省空间,输出结果为稍作整理后的结果):

    name: b	age 20;	name: c	age 21;	name: d	age 22;	name: e	age 23;	name: f	age 24;
    name: g	age 25;	name: h	age 26;	name: I	age 27;	name: j	age 28;	name: k	age 29;

    在map_normalfile1 输出umap ok后,运行map_normalfile2则输出如下结果:

    name: b	age 20;	name: c	age 21;	name: d	age 22;	name: e	age 23;	name: f	age 24;
    name:	age 0;	name:	age 0;	name:	age 0;	name:	age 0;	name:	age 0;

    从程序的运行结果中可以得出的结论

    1、 最终被映射文件的内容的长度不会超过文件本身的初始大小,即映射不能改变文件的大小;

    2、 可以用于进程通信的有效地址空间大小大体上受限于被映射文件的大小,但不完全受限于文件大小。打开文件被截短为5个people结构大小,而在map_normalfile1中初始化了10个people数据结构,在恰当时候(map_normalfile1输出initialize over 之后,输出umap ok之前)调用map_normalfile2会发现map_normalfile2将输出全部10个people结构的值,后面将给出详细讨论。 
    注:在linux中,内存的保护是以页为基本单位的,即使被映射文件只有一个字节大小,内核也会为映射分配一个页面大小的内存。当被映射文件小于一个页面大小时,进程可以对从mmap()返回地址开始的一个页面大小进行访问,而不会出错;但是,如果对一个页面以外的地址空间进行访问,则导致错误发生,后面将进一步描述。因此,可用于进程间通信的有效地址空间大小不会超过文件大小及一个页面大小的和。

    3、 文件一旦被映射后,调用mmap()的进程对返回地址的访问是对某一内存区域的访问,暂时脱离了磁盘上文件的影响。所有对mmap()返回地址空间的操作只在内存中有意义,只有在调用了munmap()后或者msync()时,才把内存中的相应内容写回磁盘文件,所写内容仍然不能超过文件的大小。

    范例2:父子进程通过匿名映射实现共享内存

    #include <sys/mman.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <unistd.h>
    typedef struct{
      char name[4];
      int  age;
    }people;
    main(int argc, char** argv)
    {
      int i;
      people *p_map;
      char temp;
      p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,
           MAP_SHARED|MAP_ANONYMOUS,-1,0);
      if(fork() == 0)
      {
        sleep(2);
        for(i = 0;i<5;i++)
          printf("child read: the %d people's age is %d\n",i+1,(*(p_map+i)).age);
        (*p_map).age = 100;
        munmap(p_map,sizeof(people)*10); //实际上,进程终止时,会自动解除映射。
        exit();
      }
      temp = 'a';
      for(i = 0;i<5;i++)
      {
        temp += 1;
        memcpy((*(p_map+i)).name, &temp,2);
        (*(p_map+i)).age=20+i;
      }
      sleep(5);
      printf( "parent read: the first people,s age is %d\n",(*p_map).age );
      printf("umap\n");
      munmap( p_map,sizeof(people)*10 );
      printf( "umap ok\n" );
    }

    考察程序的输出结果,体会父子进程匿名共享内存:

    child read: the 1 people's age is 20
    child read: the 2 people's age is 21
    child read: the 3 people's age is 22
    child read: the 4 people's age is 23
    child read: the 5 people's age is 24
    parent read: the first people,s age is 100
    umap
    umap ok

    四、对mmap()返回地址的访问

    前面对范例运行结构的讨论中已经提到,linux采用的是页式管理机制。对于用mmap()映射普通文件来说,进程会在自己的地址空间新增一块空间,空间大小由mmap()的len参数指定,注意,进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。简单的说,能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始,能够有效访问的地址空间大小。超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程。可用如下图示说明:

    图 1

    注意:文件被映射部分而不是整个文件决定了进程能够访问的空间大小,另外,如果指定文件的偏移部分,一定要注意为页面大小的整数倍。下面是对进程映射地址空间的访问范例:

    #include <sys/mman.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <unistd.h>
    typedef struct{
    	char name[4];
    	int  age;
    }people;
    main(int argc, char** argv)
    {
    	int fd,i;
    	int pagesize,offset;
    	people *p_map;
    	
    	pagesize = sysconf(_SC_PAGESIZE);
    	printf("pagesize is %d\n",pagesize);
    	fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
    	lseek(fd,pagesize*2-100,SEEK_SET);
    	write(fd,"",1);
    	offset = 0;	//此处offset = 0编译成版本1;offset = pagesize编译成版本2
    	p_map = (people*)mmap(NULL,pagesize*3,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset);
    	close(fd);
    	
    	for(i = 1; i<10; i++)
    	{
    		(*(p_map+pagesize/sizeof(people)*i-2)).age = 100;
    		printf("access page %d over\n",i);
    		(*(p_map+pagesize/sizeof(people)*i-1)).age = 100;
    		printf("access page %d edge over, now begin to access page %d\n",i, i+1);
    		(*(p_map+pagesize/sizeof(people)*i)).age = 100;
    		printf("access page %d over\n",i+1);
    	}
    	munmap(p_map,sizeof(people)*10);
    }

    如程序中所注释的那样,把程序编译成两个版本,两个版本主要体现在文件被映射部分的大小不同。文件的大小介于一个页面与两个页面之间(大小为:pagesize*2-99),版本1的被映射部分是整个文件,版本2的文件被映射部分是文件大小减去一个页面后的剩余部分,不到一个页面大小(大小为:pagesize-99)。程序中试图访问每一个页面边界,两个版本都试图在进程空间中映射pagesize*3的字节数。

    版本1的输出结果如下:

    pagesize is 4096
    access page 1 over
    access page 1 edge over, now begin to access page 2
    access page 2 over
    access page 2 over
    access page 2 edge over, now begin to access page 3
    Bus error		//被映射文件在进程空间中覆盖了两个页面,此时,进程试图访问第三个页面

    版本2的输出结果如下:

    pagesize is 4096
    access page 1 over
    access page 1 edge over, now begin to access page 2
    Bus error		//被映射文件在进程空间中覆盖了一个页面,此时,进程试图访问第二个页面

    结论:采用系统调用mmap()实现进程间通信是很方便的,在应用层上接口非常简洁。内部实现机制区涉及到了linux存储管理以及文件系统等方面的内容,可以参考一下相关重要数据结构来加深理解。在本专题的后面部分,将介绍系统v共享内存的实现。


    Linux环境进程间通信(五): 共享内存(下)

    在共享内存(上)中,主要围绕着系统调用mmap()进行讨论的,本部分将讨论系统V共享内存,并通过实验结果对比来阐述两者的异同。系统V共享内存指的是把所有共享数据放在共享内存区域(IPC shared memory region),任何想要访问该数据的进程都必须在本进程的地址空间新增一块内存区域,用来映射存放共享数据的物理内存页面。

    系统调用mmap()通过映射一个普通文件实现共享内存。系统V则是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信。也就是说,每个共享内存区域对应特殊文件系统shm中的一个文件(这是通过shmid_kernel结构联系起来的),后面还将阐述。

    1、系统V共享内存原理

    进程间需要共享的数据被放在一个叫做IPC共享内存区域的地方,所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去。系统V共享内存通过shmget获得或创建一个IPC共享内存区域,并返回相应的标识符。内核在保证shmget获得或创建一个共享内存区,初始化该共享内存区相应的shmid_kernel结构注同时,还将在特殊文件系统shm中,创建并打开一个同名文件,并在内存中建立起该文件的相应dentry及inode结构,新打开的文件不属于任何一个进程(任何进程都可以访问该共享内存区)。所有这一切都是系统调用shmget完成的。

    注:每一个共享内存区都有一个控制结构struct shmid_kernel,shmid_kernel是共享内存区域中非常重要的一个数据结构,它是存储管理和文件系统结合起来的桥梁,定义如下:

    struct shmid_kernel /* private to the kernel */
    {	
    	struct kern_ipc_perm	shm_perm;
    	struct file *		shm_file;
    	int			id;
    	unsigned long		shm_nattch;
    	unsigned long		shm_segsz;
    	time_t			shm_atim;
    	time_t			shm_dtim;
    	time_t			shm_ctim;
    	pid_t			shm_cprid;
    	pid_t			shm_lprid;
    };

    该结构中最重要的一个域应该是shm_file,它存储了将被映射文件的地址。每个共享内存区对象都对应特殊文件系统shm中的一个文件,一般情况下,特殊文件系统shm中的文件是不能用read()、write()等方法访问的,当采取共享内存的方式把其中的文件映射到进程地址空间后,可直接采用访问内存的方式对其访问。

    这里我们采用[1]中的图表给出与系统V共享内存相关数据结构:

    正如消息队列和信号灯一样,内核通过数据结构struct ipc_ids shm_ids维护系统中的所有共享内存区域。上图中的shm_ids.entries变量指向一个ipc_id结构数组,而每个ipc_id结构数组中有个指向kern_ipc_perm结构的指针。到这里读者应该很熟悉了,对于系统V共享内存区来说,kern_ipc_perm的宿主是shmid_kernel结构,shmid_kernel是用来描述一个共享内存区域的,这样内核就能够控制系统中所有的共享区域。同时,在shmid_kernel结构的file类型指针shm_file指向文件系统shm中相应的文件,这样,共享内存区域就与shm文件系统中的文件对应起来。

    在创建了一个共享内存区域后,还要将它映射到进程地址空间,系统调用shmat()完成此项功能。由于在调用shmget()时,已经创建了文件系统shm中的一个同名文件与共享内存区域相对应,因此,调用shmat()的过程相当于映射文件系统shm中的同名文件过程,原理与mmap()大同小异。

    2、系统V共享内存API

    对于系统V共享内存,主要有以下几个API:shmget()、shmat()、shmdt()及shmctl()。

    #include <sys/ipc.h>
    #include <sys/shm.h>

    shmget()用来获得共享内存区域的ID,如果不存在指定的共享区域就创建相应的区域。shmat()把共享内存区域映射到调用进程的地址空间中去,这样,进程就可以方便地对共享区域进行访问操作。shmdt()调用用来解除进程对共享内存区域的映射。shmctl实现对共享内存区域的控制操作。这里我们不对这些系统调用作具体的介绍,读者可参考相应的手册页面,后面的范例中将给出它们的调用方法。

    注:shmget的内部实现包含了许多重要的系统V共享内存机制;shmat在把共享内存区域映射到进程空间时,并不真正改变进程的页表。当进程第一次访问内存映射区域访问时,会因为没有物理页表的分配而导致一个缺页异常,然后内核再根据相应的存储管理机制为共享内存映射区域分配相应的页表。

    3、系统V共享内存限制

    在/proc/sys/kernel/目录下,记录着系统V共享内存的一下限制,如一个共享内存区的最大字节数shmmax,系统范围内最大共享内存区标识符数shmmni等,可以手工对其调整,但不推荐这样做。

    在[2]中,给出了这些限制的测试方法,不再赘述。

    4、系统V共享内存范例

    本部分将给出系统V共享内存API的使用方法,并对比分析系统V共享内存机制与mmap()映射普通文件实现共享内存之间的差异,首先给出两个进程通过系统V共享内存通信的范例:

    /***** testwrite.c *******/
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <sys/types.h>
    #include <unistd.h>
    typedef struct{
    	char name[4];
    	int age;
    } people;
    main(int argc, char** argv)
    {
    	int shm_id,i;
    	key_t key;
    	char temp;
    	people *p_map;
    	char* name = "/dev/shm/myshm2";
    	key = ftok(name,0);
    	if(key==-1)
    		perror("ftok error");
    	shm_id=shmget(key,4096,IPC_CREAT);	
    	if(shm_id==-1)
    	{
    		perror("shmget error");
    		return;
    	}
    	p_map=(people*)shmat(shm_id,NULL,0);
    	temp='a';
    	for(i = 0;i<10;i++)
    	{
    		temp+=1;
    		memcpy((*(p_map+i)).name,&temp,1);
    		(*(p_map+i)).age=20+i;
    	}
    	if(shmdt(p_map)==-1)
    		perror(" detach error ");
    }
    /********** testread.c ************/
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <sys/types.h>
    #include <unistd.h>
    typedef struct{
    	char name[4];
    	int age;
    } people;
    main(int argc, char** argv)
    {
    	int shm_id,i;
    	key_t key;
    	people *p_map;
    	char* name = "/dev/shm/myshm2";
    	key = ftok(name,0);
    	if(key == -1)
    		perror("ftok error");
    	shm_id = shmget(key,4096,IPC_CREAT);	
    	if(shm_id == -1)
    	{
    		perror("shmget error");
    		return;
    	}
    	p_map = (people*)shmat(shm_id,NULL,0);
    	for(i = 0;i<10;i++)
    	{
    	printf( "name:%s\n",(*(p_map+i)).name );
    	printf( "age %d\n",(*(p_map+i)).age );
    	}
    	if(shmdt(p_map) == -1)
    		perror(" detach error ");
    }

    testwrite.c创建一个系统V共享内存区,并在其中写入格式化数据;testread.c访问同一个系统V共享内存区,读出其中的格式化数据。分别把两个程序编译为testwrite及testread,先后执行./testwrite及./testread 则./testread输出结果如下:

    name: b	age 20;	name: c	age 21;	name: d	age 22;	name: e	age 23;	name: f	age 24;
    name: g	age 25;	name: h	age 26;	name: I	age 27;	name: j	age 28;	name: k	age 29;

    通过对试验结果分析,对比系统V与mmap()映射普通文件实现共享内存通信,可以得出如下结论:

    1、 系统V共享内存中的数据,从来不写入到实际磁盘文件中去;而通过mmap()映射普通文件实现的共享内存通信可以指定何时将数据写入磁盘文件中。 注:前面讲到,系统V共享内存机制实际是通过映射特殊文件系统shm中的文件实现的,文件系统shm的安装点在交换分区上,系统重新引导后,所有的内容都丢失。

    2、 系统V共享内存是随内核持续的,即使所有访问共享内存的进程都已经正常终止,共享内存区仍然存在(除非显式删除共享内存),在内核重新引导之前,对该共享内存区域的任何改写操作都将一直保留。

    3、 通过调用mmap()映射普通文件进行进程间通信时,一定要注意考虑进程何时终止对通信的影响。而通过系统V共享内存实现通信的进程则不然。 注:这里没有给出shmctl的使用范例,原理与消息队列大同小异。

    结论:

    共享内存允许两个或多个进程共享一给定的存储区,因为数据不需要来回复制,所以是最快的一种进程间通信机制。共享内存可以通过mmap()映射普通文件(特殊情况下还可以采用匿名映射)机制实现,也可以通过系统V共享内存机制实现。应用接口和原理很简单,内部机制复杂。为了实现更安全通信,往往还与信号灯等同步机制共同使用。

    共享内存涉及到了存储管理以及文件系统等方面的知识,深入理解其内部机制有一定的难度,关键还要紧紧抓住内核使用的重要数据结构。系统V共享内存是以文件的形式组织在特殊文件系统shm中的。通过shmget可以创建或获得共享内存的标识符。取得共享内存标识符后,要通过shmat将这个内存区映射到本进程的虚拟地址空间。


    展开全文
  • pmap命令用于显示一个或多个进程内存状态,报告进程地址空间和内存状态信息。 一般使用 pmap pid 一般参数选项如下 -x extended显示扩展格式 -d device显示设备格式 -q quiet不显示header/footer行 -V ...

    1 问题

    我们怎么知道进程占用的内存多大

     

     

     

     

     

     

     

     

     

     

    2 我们可以使用pmap命令

    pmap命令用于显示一个或多个进程的内存状态,报告进程的地址空间和内存状态信息。

    一般使用

    pmap pid

    一般参数选项如下 

    -x extended显示扩展格式
    -d device显示设备格式
    -q quiet不显示header/footer行
    -V 显示版本信息

    我们先用top命令查看占用CPU高的进程,这里我们知道有个httpd进行的PId是26858

    我们先pmap 26858试下,部分效果如下,我们知道内存映射所占空间大小是2369116K

     

    我们用-x参数使用下部分效果如下

    pmap -x 26858
    展开全文
  • 详解mmap、malloc在内核态的实现,原理是什么,有详细的描述和加的内核源码注释,可完全理解用户进程申请内存是怎么一回事
  • Linux 查看进程消耗内存情况总结

    万次阅读 2018-09-01 00:09:00
    Linux中,有很多命令或工具查看内存使用情况,今天我们来看看如何查看进程消耗、占用的内存情况,Linux内存管理和相关概念要比Windows复杂一些。在此之前,我们需要了解一下Linux系统下面有关内存的专用名词和...

     

    在Linux中,有很多命令或工具查看内存使用情况,今天我们来看看如何查看进程消耗、占用的内存情况,Linux的内存管理和相关概念要比Windows复杂一些。在此之前,我们需要了解一下Linux系统下面有关内存的专用名词和专业术语概念:

     

     

    物理内存和虚拟内存

     

    物理内存:就是系统硬件提供的内存大小,是真正的内存,一般叫做内存条。也叫随机存取存储器(random access memory,RAM)又称作随机存储器,是与CPU直接交换数据的内部存储器,也叫主存(内存)。

     

    虚拟内存:相对于物理内存,在Linux下还有一个虚拟内存的概念,虚拟内存就是为了满足物理内存的不足而提出的策略,它是利用磁盘空间虚拟出的一块逻辑内存,用作虚拟内存的磁盘空间被称为交换空间(Swap Space)。Linux会在物理内存不足时,使用虚拟内存,内核会把暂时不用的内存块信息写到虚拟内存,这样物理内存就得到了释放,这块儿内存就可以用于其他目的,而需要用到这些内容的时候,这些信息就会被重新从虚拟内存读入物理内存。

     

     

    Linuxbufferscached

     

    在Linux中经常发现空闲的内存很少,似乎所有的内存都被消耗殆尽了,表面上看是内存不够用了,很多新手看到内存被消耗殆尽非常紧张,其实这个是因为Linux系统将空闲的内存用来做磁盘文件数据的缓存。这个导致你的系统看起来处于内存非常紧急的状况。但是实际上不是这样。这个区别于Windows的内存管理。Linux会利用空闲的内存来做cached & buffers。

     

     

    buffers是指用来给块设备做的缓冲大小(块设备的读写缓冲区),它只记录文件系统的metadata以及 tracking in-flight pages.

     

    Buffers are associated with a specific block device, and cover caching of filesystem metadata as well as tracking in-flight pages. The cache only contains parked file data. That is, the buffers remember what's in directories, what file permissions are, and keep track of what memory is being written from or read to for a particular block device. The cache only contains the contents of the files themselves.

     

      

    cached是作为page cache的内存, 文件系统的cache。你读写文件的时候,Linux内核为了提高读写性能与速度,会将文件在内存中进行缓存,这部分内存就是Cache Memory(缓存内存)。即使你的程序运行结束后,Cache Memory也不会自动释放。这就会导致你在Linux系统中程序频繁读写文件后,你会发现可用物理内存会很少。其实这缓存内存(Cache Memory)在你需要使用内存的时候会自动释放,所以你不必担心没有内存可用

     

    Cached is the size of the page cache. Buffers is the size of in-memory block I/O buffers. Cached matters; Buffers is largely irrelevant.

     

    Cached is the size of the Linux page cache, minus the memory in the swap cache, which is represented by SwapCached (thus the total page cache size is Cached + SwapCached). Linux performs all file I/O through the page cache. Writes are implemented as simply marking as dirty the corresponding pages in the page cache; the flusher threads then periodically write back to disk any dirty pages. Reads are implemented by returning the data from the page cache; if the data is not yet in the cache, it is first populated. On a modern Linux system, Cached can easily be several gigabytes. It will shrink only in response to memory pressure. The system will purge the page cache along with swapping data out to disk to make available more memory as needed.

    Buffers are in-memory block I/O buffers. They are relatively short-lived. Prior to Linux kernel version 2.4, Linux had separate page and buffer caches. Since 2.4, the page and buffer cache are unified and Buffers is raw disk blocks not represented in the page cache—i.e., not file data. The Buffers metric is thus of minimal importance. On most systems, Buffers is often only tens of megabytes.

     

     

    Linux共享内存

     

    共享内存是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个。其实所谓共享内存,就是多个进程间共同地使用同一段物理内存空间,它是通过将同一段物理内存映射到不同进程的虚拟空间来实现的。由于映射到不同进程的虚拟空间中,不同进程可以直接使用,不需要像消息队列那样进行复制,所以共享内存的效率很高。共享内存可以通过mmap()映射普通文件机制来实现,也可以System V共享内存机制来实现,System V是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信,也就是说每个共享内存区域对应特殊文件系统shm中的一个文件。

     

     

    另外,我们还必须了解RSS、PSS、USS等相关概念:

     

         VSS Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)

          RSS Resident Set Size 实际使用物理内存(包含共享库占用的内存)

          PSS Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)

          USS Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)

     

    RSS(Resident set size),使用top命令可以查询到,是最常用的内存指标,表示进程占用的物理内存大小。但是,将各进程的RSS值相加,通常会超出整个系统的内存消耗,这是因为RSS中包含了各进程间共享的内存。

     

    PSS(Proportional set size)所有使用某共享库的程序均分该共享库占用的内存时,每个进程占用的内存。显然所有进程的PSS之和就是系统的内存使用量。它会更准确一些,它将共享内存的大小进行平均后,再分摊到各进程上去。

     

    USS(Unique set size )进程独自占用的内存,它是PSS中自己的部分,它只计算了进程独自占用的内存大小,不包含任何共享的部分。

         

              

    所以下面介绍的命令,有些查看进程的虚拟内存使用,有些是查看进程的RSS或实际物理内存。在讲述的时候,我们会标注这些信息。

     

     

     

    top命令查看

     

    执行top命令后,执行SHIFT +F ,可以选择按某列排序,例如选择n后,就会按字段%MEM排序

     

    clip_image001

     

     

    当然也可以使用shift+m 或大写键M 让top命令按字段%MEM来排序,当然你也可以按VIRT(虚拟内存)、SWAP(进程使用的SWAP空间)、RES(实际使用物理内存,当然这里由于涉及共享内存缘故,你看到的实际内存非常大)

     

     

    %MEM -- Memory usage (RES)

     

         A task's currently used share of available physical memory

         

    VIRT -- virtual memory 

       

        The  total  amount  of virtual memory used by the task.  It includes all code, data and shared libraries plus pages that have been swapped out. (Note: you can define the STATSIZE=1 environment variable and the VIRT will be calculated from the /proc/#/state VmSize field.)

     

        VIRT = SWAP + RES

     

    SWAP  --  Swapped size (kb)

     

       The swapped out portion of a tasks total virtual memory image.

     

    RES  --  Resident size (kb)

           

        RES = CODE + DATA.

     

             

     

    是否有人会觉得奇怪,为什么%MEM这一列的值加起来会大于100呢? 这个是因为这里计算的时候包含了共享内存的缘故,另外由于共享内存的缘故,你看到进程使用VIRT或RES都非常高。由于大部分的物理内存通常在多个应用程序之间共享,名为实际使用物理内存(RSS,对应top命令里面的RES)的这个标准的内存耗用衡量指标会大大高估内存耗用情况。

     

    clip_image002

     

     

     

    ps命令查看

     

     

     

    使用ps命令找出占用内存资源最多的20个进程(数量可以任意设置)

     

     

    # ps aux | head -1;ps aux |grep -v PID |sort -rn -k +4 | head -20
    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    oracle   32147 11.0 51.2 13252080 12666320 ?   Rs   Aug24 163:16 ora_s000_SCM2
    oracle   32149 14.2 50.9 13250344 12594264 ?   Ss   Aug24 210:41 ora_s001_SCM2
    oracle   32153  4.2 49.6 13250820 12279432 ?   Ss   Aug24  62:27 ora_s003_SCM2
    oracle   32155  2.5 48.6 13250268 12040732 ?   Ss   Aug24  38:21 ora_s004_SCM2
    oracle   32157  1.2 44.5 13250296 11011708 ?   Ss   Aug24  18:31 ora_s005_SCM2
    oracle   32151  2.7 39.7 13350436 9829944 ?    Ss   Aug24  41:18 ora_s002_SCM2
    oracle   32159  0.5 38.9 13250704 9625764 ?    Ss   Aug24   8:18 ora_s006_SCM2
    oracle   32161  0.2 26.3 13250668 6507244 ?    Ss   Aug24   3:38 ora_s007_SCM2
    oracle   32129  0.0 25.5 13299084 6324644 ?    Ss   Aug24   1:25 ora_dbw0_SCM2
    oracle   32181  0.0 15.8 13250152 3913260 ?    Ss   Aug24   0:56 ora_s017_SCM2
    oracle   32145  2.7 15.3 13255256 3786456 ?    Ss   Aug24  40:11 ora_d000_SCM2
    oracle   32127  0.0 15.2 13248996 3762860 ?    Ss   Aug24   0:05 ora_mman_SCM2
    oracle   32163  0.0 14.2 13250108 3525160 ?    Ss   Aug24   1:04 ora_s008_SCM2
    oracle   32165  0.0  8.1 13250172 2007704 ?    Ss   Aug24   0:37 ora_s009_SCM2
    oracle   32169  0.0  6.6 13250060 1656864 ?    Ss   Aug24   0:08 ora_s011_SCM2
    oracle   32177  0.0  6.0 13250148 1498760 ?    Ss   Aug24   0:12 ora_s015_SCM2
    oracle   32187  0.0  5.1 13250084 1267384 ?    Ss   Aug24   0:06 ora_s020_SCM2
    oracle   32179  0.0  5.1 13250584 1280156 ?    Ss   Aug24   0:05 ora_s016_SCM2
    oracle   32167  0.0  5.0 13250060 1248668 ?    Ss   Aug24   0:08 ora_s010_SCM2
    oracle   32175  0.0  3.4 13250596 857380 ?     Ss   Aug24   0:03 ora_s014_SCM2

     

     

    #ps -eo pmem,pcpu,rss,vsize,args | sort -k 1 -n -r | less

     

    查看进程占用的实际物理内存(与smem看到实际物理内存大小有出入,这里解释一下:SIZE: 进程使用的地址空间, 如果进程映射了100M的内存, 进程的地址空间将报告为100M内存. 事实上, 这个大小不是一个程序实际使用的内存数. 所以这里看到的内存跟smem看到的大小有出入)

     

    ps -eo size,pid,user,command --sort -size | awk '{ hr=$1/1024 ; printf("%13.2f Mb ",hr) } { for ( x=4 ; x<=NF ; x++ ) { printf("%s ",$x) } print "" }' |cut -d "" -f2 | cut -d "-" -f1

     

    clip_image003

     

     

    ps aux  | awk '{print $6/1024 " MB\t\t" $11}'  | sort -n

     

     

     

     

    smem命令查看

     

     

    关于smem命令,这里不做介绍,直接参考链接Linux监控工具介绍系列——smem

     

     

    #smem -rs pss

     

    clip_image004

     

     

     

     

    pmap命令查看

     

     

    # ps -ef | grep tomcat

    # pmap 32341

     

    clip_image005

     

    # pmap -x  32341

     

    The -x option can be used to provide information about the memory allocation and mapping types per mapping. The amount of resident, non-shared anonymous, and locked memory is shown for each mapping

     

    clip_image006

     

     

     

     

    python脚本查看

     

     

    网上有个python脚本计算程序或进程的内存使用情况,地址位于https://raw.githubusercontent.com/pixelb/ps_mem/master/ps_mem.py

     

    python ps_mem.py

     

    clip_image007

     

     

    [root@mylnx03 ~]# python ps_mem.py -h
    Usage: ps_mem [OPTION]...
    Show program core memory usage
     
      -h, -help                   Show this help
      -p <pid>[,pid2,...pidN]     Only show memory usage PIDs in the specified list
      -s, --split-args            Show and separate by, all command line arguments
      -t, --total                 Show only the total value
      -d, --discriminate-by-pid   Show by process rather than by program
      -S, --swap                  Show swap information
      -w <N>                      Measure and show process memory every N seconds
    [root@mylnx03 ~]# python ps_mem.py  -p 32341
     Private  +   Shared  =  RAM used       Program
     
    411.2 MiB + 184.0 KiB = 411.4 MiB       java
    ---------------------------------
                            411.4 MiB
    =================================

     

    参考资料:

     

     

    https://stackoverflow.com/questions/131303/how-to-measure-actual-memory-usage-of-an-application-or-process

    http://www.cnblogs.com/kerrycode/p/5079319.html

    https://raw.githubusercontent.com/pixelb/ps_mem/master/ps_mem.py

    展开全文
  • Linux进程地址空间和进程的内存分布

    万次阅读 多人点赞 2018-05-15 20:13:18
    原网址为:https://blog.csdn.net/yusiguyuan/article/details/45155035一 进程空间分布概述 对于一个进程,其空间分布如下图所示: 程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码。初始化过的...

    本文为转载的!!! 

    我只是为了加强自己的记忆,便于查看资料,才转载的。如有不妥,请原作者联系我,我删除。

    原网址为:https://blog.csdn.net/yusiguyuan/article/details/45155035


    一 进程空间分布概述


        对于一个进程,其空间分布如下图所示:

                                         



    程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码。

    初始化过的数据(Data):在程序运行初已经对变量进行初始化的数据。

    未初始化过的数据(BSS):在程序运行初未对变量进行初始化的数据。

    栈 (Stack):存储局部、临时变量,函数调用时,存储函数的返回指针,用于控制函数的调用和返回。在程序块开始时自动分配内存,结束时自动释放内存,其操作方式类似于数据结构中的栈。

    堆 (Heap):存储动态内存分配,需要程序员手工分配,手工释放.注意它与数据结构中的堆是两回事,分配方式类似于链表。



    注:1.Text, BSS, Data段在编译时已经决定了进程将占用多少VM
            可以通过size,知道这些信息:

              2. 正常情况下,Linux进程不能对用来存放程序代码的内存区域执行写操作,即程序代码是以只读的方式加载到内存中,但它可以被多个进程安全的共享。

    二  内核空间和用户空间

              Linux的虚拟地址空间范围为0~4G,Linux内核将这4G字节的空间分为两部分, 将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF)供内核使用,称为“内核空间”。而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF)供各个进程使用,称为“用户空间。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。
        Linux使用两级保护机制:0级供内核使用,3级供用户程序使用,每个进程有各自的私有用户空间(0~3G),这个空间对系统中的其他进程是不可见的,最高的1GB字节虚拟内核空间则为所有进程以及内核所共享。
        内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。 虽然内核空间占据了每个虚拟空间中的最高1GB字节,但映射到物理内存却总是从最低地址(0x00000000),另外, 使用虚拟地址可以很好的保护 内核空间被用户空间破坏,虚拟地址到物理地址转换过程有操作系统和CPU共同完成(操作系统为CPU设置好页表,CPU通过MMU单元进行地址转换)。
          
            多任务操作系统中的每一个进程都运行在一个属于它自己的内存沙盒中,这个 沙盒就是虚拟地址空间(virtual address space),在32位模式下,它总是一个4GB的内存地址块。这些虚拟地址通过页表(page table)映射到物理内存,页表由操作系统维护并被处理器引用。每个进程都拥有一套属于它自己的页表。

      进程内存空间分布如下图所示:

                               


           通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间

         : 1.这里是32位内核地址空间划分,64位内核地址空间划分是不同的
              2. 现代的操作系统都处于32位保护模式下。每个进程一般都能寻址4G的物理空间。但是我们的物理内存一般都是几百M,进程怎么能获得4G 的物理空间呢?这就是使用了虚拟地址的好处,通常我们使用一种叫做虚拟内存的技术来实现,因为可以使用硬盘中的一部分来当作内存使用 
                                                    
        
            Linux系统对自身进行了划分,一部分核心软件独立于普通应用程序,运行在较高的特权级别上,它们驻留在被保护的内存空间上,拥有访问硬件设备的所有权限,Linux将此称为内核空间。
            相对地,应用程序则是在“用户空间”中运行。运行在用户空间的应用程序只能看到允许它们使用的部分系统资源,并且不能使用某些特定的系统功能,也不能直接访问内核空间和硬件设备,以及其他一些具体的使用限制。
            将用户空间和内核空间置于这种非对称访问机制下有很好的安全性,能有效抵御恶意用户的窥探,也能防止质量低劣的用户程序的侵害,从而使系统运行得更稳定可靠。
                 内核空间在页表中拥有较高的特权级(ring2或以下),因此只要用户态的程序试图访问这些页,就会导致一个页错误(page fault)。在Linux中,内核空间是持续存在的,并且在所有进程中都映射到同样的物理内存,内核代码和数据总是可寻址的,随时准备处理中断和系统调用。与之相反,用户模式地址空间的映射随着进程切换的发生而不断的变化,如下图所示:

                                               

          上图中蓝色区域表示映射到物理内存的虚拟地址,而白色区域表示未映射的部分。可以看出,Firefox使用了相当多的虚拟地址空间,因为它占用内存较多。


     三  进程内存布局

           Linux进程标准的内存段布局,如下图所示,地址空间中的各个条带对应于不同的内存段(memory segment),如:堆、栈之类的。

                                    

                                                      q
           
              这些段只是简单的虚拟内存地址空间范围,与Intel处理器的段没有任何关系。

           几乎每个进程的虚拟地址空间中各段的分布都与上图完全一致, 这就给远程发掘程序漏洞的人打开了方便之门。一个发掘过程往往需要引用绝对内存地址:栈地址,库函数地址等。远程攻击者必须依赖地址空间分布的一致性,来探索出这些地址。如果让他们猜个正着,那么有人就会被整了。因此,地址空间的随机排布方式便逐渐流行起来,Linux通过对栈、内存映射段、堆的起始地址加上随机的偏移量来打乱布局。但不幸的是,32位地址空间相当紧凑,这给随机化所留下的空间不大,削弱了这种技巧的效果。

         进程地址空间中最顶部的段是栈,大多数编程语言将之用于存储函数参数和局部变量。调用一个方法或函数会将一个新的栈帧(stack frame)压入到栈中,这个栈帧会在函数返回时被清理掉。由于栈中数据严格的遵守FIFO的顺序,这个简单的设计意味着不必使用复杂的数据结构来追踪栈中的内容,只需要一个简单的指针指向栈的顶端即可,因此压栈(pushing)和退栈(popping)过程非常迅速、准确。进程中的每一个线程都有属于自己的栈。

          通过不断向栈中压入数据,超出其容量就会耗尽栈所对应的内存区域,这将触发一个页故障(page fault),而被Linux的expand_stack()处理,它会调用acct_stack_growth()来检查是否还有合适的地方用于栈的增长。如果栈的大小低于RLIMIT_STACK(通常为8MB),那么一般情况下栈会被加长,程序继续执行,感觉不到发生了什么事情。这是一种将栈扩展到所需大小的常规机制。然而,如果达到了最大栈空间的大小,就会栈溢出(stack overflow),程序收到一个段错误(segmentation fault)


         :动态栈增长是唯一一种访问未映射内存区域而被允许的情形,其他任何对未映射内存区域的访问都会触发页错误,从而导致段错误。一些被映射的区域是只读的,因此企图写这些区域也会导致段错误

    内存映射段      
         在栈的下方是内存映射段内核将文件的内容直接映射到内存。任何应用程序都可以通过Linux的mmap()系统调用或者Windows的CreateFileMapping()/MapViewOfFile()请求这种映射。内存映射是一种方便高效的文件I/O方式,所以它被用来加载动态库。创建一个不对应于任何文件的匿名内存映射也是可能的,此方法用于存放程序的数据。在Linux中,如果你通过malloc()请求一大块内存,C运行库将会创建这样一个匿名映射而不是使用堆内存。“大块”意味着比MMAP_THRESHOLD还大,缺省128KB,可以通过mallocp()调整。

          与栈一样,堆用于运行时内存分配;但不同的是,堆用于存储那些生存期与函数调用无关的数据。大部分语言都提供了堆管理功能。在C语言中,堆分配的接口是malloc()函数。如果堆中有足够的空间来满足内存请求,它就可以被语言运行时库处理而不需要内核参与,否则,堆会被扩大,通过brk()系统调用来分配请求所需的内存块。堆管理是很复杂的,需要精细的算法来应付我们程序中杂乱的分配模式,优化速度和内存使用效率。处理一个堆请求所需的时间会大幅度的变动。实时系统通过特殊目的分配器来解决这个问题。堆在分配过程中可能会变得零零碎碎,如下图所示:
                              


            一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式类似于链表。
             

    BBS和数据段
          在C语言中,BSS和数据段保存的都是静态(全局)变量的内容。区别在于BSS保存的是未被初始化的静态变量内容,他们的值不是直接在程序的源码中设定的。BSS内存区域是匿名的,它不映射到任何文件。如果你写static intcntActiveUsers,则cntActiveUsers的内容就会保存到BSS中去。
          数据段保存在源代码中已经初始化的静态变量的内容。数据段不是匿名的,它映射了一部分的程序二进制镜像,也就是源代码中指定了初始值的静态变量。所以,如果你写static int cntActiveUsers=10,则cntActiveUsers的内容就保存在了数据段中,而且初始值是10。尽管数据段映射了一个文件,但它是一个私有内存映射,这意味着更改此处的内存不会影响被映射的文件。

          你可以通过阅读文件/proc/pid_of_process/maps来检验一个Linux进程中的内存区域。记住:一个段可能包含许多区域。比如,每个内存映射文件在mmap段中都有属于自己的区域,动态库拥有类似BSS和数据段的额外区域。有时人们提到“数据段”,指的是全部的数据段+BSS+堆。

         你还可以通过nm和objdump命令来察看二进制镜像,打印其中的符号,它们的地址,段等信息。最后需要指出的是,前文描述的虚拟地址布局在linux中是一种“灵活布局”,而且作为默认方式已经有些年头了,它假设我们有值RLIMT_STACK。但是,当没有该值得限制时,Linux退回到“经典布局”,如下图所示:

                                       


    C语言程序实例分析如下所示:


    [cpp]  view plain  copy
    1.  #include<stdio.h>    
    2.  #include <malloc.h>    
    3.      
    4.  void print(char *,int);    
    5.  int main()    
    6. {    
    7.       char *s1 = "abcde";  //"abcde"作为字符串常量存储在常量区 s1、s2、s5拥有相同的地址  
    8.       char *s2 = "abcde";    
    9.       char s3[] = "abcd";    
    10.       long int *s4[100];    
    11.       char *s5 = "abcde";    
    12.       int a = 5;    
    13.       int b =6;//a,b在栈上,&a>&b地址反向增长    
    14.      
    15.      printf("variables address in main function: s1=%p  s2=%p s3=%p s4=%p s5=%p a=%p b=%p \n",     
    16.              s1,s2,s3,s4,s5,&a,&b);   
    17.      printf("variables address in processcall:n");    
    18.         print("ddddddddd",5);//参数入栈从右至左进行,p先进栈,str后进 &p>&str    
    19.      printf("main=%p print=%p \n",main,print);    
    20.      //打印代码段中主函数和子函数的地址,编译时先编译的地址低,后编译的地址高main<print    
    21.  }    
    22.   
    23.  void print(char *str,int p)    
    24. {    
    25.      char *s1 = "abcde";  //abcde在常量区,s1在栈上    
    26.      char *s2 = "abcde";  //abcde在常量区,s2在栈上 s2-s1=6可能等于0,编译器优化了相同的常量,只在内存保存一份    
    27.      //而&s1>&s2    
    28.      char s3[] = "abcdeee";//abcdeee在常量区,s3在栈上,数组保存的内容为abcdeee的一份拷贝    
    29.     long int *s4[100];    
    30.      char *s5 = "abcde";    
    31.      int a = 5;    
    32.      int b =6;    
    33.      int c;    
    34.      int d;           //a,b,c,d均在栈上,&a>&b>&c>&d地址反向增长    
    35.     char *q=str;   
    36.     int m=p;           
    37.     char *r=(char *)malloc(1);    
    38.     char *w=(char *)malloc(1) ;  // r<w 堆正向增长    
    39.     
    40.     printf("s1=%p s2=%p s3=%p s4=%p s5=%p a=%p b=%p c=%p d=%p str=%p q=%p p=%p m=%p r=%p w=%p \n",    
    41.             s1,s2,s3,s4,s5,&a,&b,&c,&d,&str,q,&p,&m,r,w);   
    42.     /* 栈和堆是在程序运行时候动态分配的,局部变量均在栈上分配。 
    43.         栈是反向增长的,地址递减;malloc等分配的内存空间在堆空间。堆是正向增长的,地址递增。   
    44.         r,w变量在栈上(则&r>&w),r,w所指内容在堆中(即r<w)。*/   
    45.  }    
    46.    
    附录:

    栈与堆的区别
               
    展开全文
  • mmap系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式...普通文件被映射进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。
  • 一、简介 正如其名(Memory Map),mmap 可以将某个设备或者文件映射到应用进程的内存空间中。通过直接的内存操作即可完成对设备或文件的读写。. 通过映射同一块物理内存,来实现共享...2. 创建内存映射 void *mm...
  • Linux查看进程内存

    千次阅读 2018-03-23 16:40:09
    Linux 上进行开发和运营维护的时候,免不了要查看某一个程序所占用内存的情况。有很多个命令都可以达到我们的需求,这里给大家列举几个: 1: top -p pid 查看程序的情况,一些简单的占用情况。 2: ps -...
  • linux mmap内存文件映射

    千次阅读 2019-04-12 14:55:29
    一、传统文件访问 unix访问文件的传统方法使用open打开他们,如果有多个进程访问一个文件,则每一个进程在再记得地址空间都包含有该文件的副本,这...二、共享内存映射 现在考虑林一种处理方法:进程A和进程B都将...
  • linux内核虚拟内存和物理内存映射

    千次阅读 2018-08-24 15:06:16
    内存访问分为两种体系结构:一致性内存访问(UMA)和非一致性内存访问(NUMA)。NUMA指CPU对不同内存单元的访问时间可能不一样,因而这些物理内存被划分为几个节点,每个节点里的内存访问时间一致,NUMA体系结构主要...
  • Linux进程间通信--内存映射

    千次阅读 2015-04-20 20:22:19
    内存映射概述    从原理上讲,Linux系统利用已有的存储管理机制可以很自然的实现进程间的共享存储。对于一段物理存储空间,只需通过进程的虚存管理机构就可以映射到各自的3G用户地址空间中。通过这种映射,在...
  • linux内存映射(一)

    千次阅读 2019-06-07 11:30:21
    内存映射原理 由于所有用户进程总的虚拟地址空间比可用的物理内存大很多,因此只有最常用的部分才与物理页帧关联。这不是问题,因为大多数程序只占用实际可用内存的一小部分。在将磁盘上的数据映射到进程的虚拟...
  • 内核空间映射到物理内存是从最低地址0x00000000开始,如下图所示 假设内核地址空间的简单线性地址转换关系为:物理地址 = 虚拟地址–  0xC0000000,内核虚拟地址空间对应的物理内存范围0x00000000...
  • 本文完整阐述了内存映射的机理。对于提供了MMU(存储管理器,辅助操作系统进行内存管理,提供虚实地址转换等硬件支持)的处理器而言,Linux提供了复杂的存储管理系统,使得进程所能访问的内存达到4GB。  进程的4GB...
  • linux进程中的内存分布

    千次阅读 多人点赞 2020-06-18 11:10:48
    程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码。 初始化过的数据(Data):在程序运行初已经对变量进行初始化的数据。 未初始化过的数据(BSS):在程序运行初未对变量进行初始化的数据。 栈 (Stack):...
  • 使用每行的字符个数统计和 MD5 两个算法比较内存映射和分块直接读取文件的速度。
  • Linux地址映射

    千次阅读 2019-09-04 17:10:46
    一、地址映射 .class文件→ jvm → linux 用【javap -c 字节码名】 可以打印出jvm的指令,最终转成linux系统平台的汇编指令来执行 c/c++,java,python,php,go语言都是指令(text)和数据(data) 在程序还没有...
  • Linux进程内存分析和内存泄漏定位

    千次阅读 2017-08-21 10:35:10
    linux本身提供了一些工具方便我们达成这些需求,查看进程实时资源top工具,更详细的进程内存堆栈情况,pmap工具,Linux进程运行时状态信息也会保存在proc目录下,相应进程ID目录下,这里有很丰富的信息,先讨论进程...
  • 查看LINUX进程内存占用情况

    万次阅读 2018-01-05 14:38:27
    可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令:  (1)top  top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于...
  • Linux内存映射机制

    千次阅读 2019-04-24 21:13:27
    没有启动或者没有MMU时,外设(包括物理内存)等所有部件使用的都是物理地址,cpu通过物理地址来访问外设(包括物理内存)。启动MMU后,CPU核心对外发出虚拟地址给MMU,MMU把虚拟地址转换为物理地址,最后使用物理...
  • LINUX程序(进程)在内存中的布局

    千次阅读 2018-03-04 23:50:20
    http://blog.csdn.net/mumumuwudi/article/details/47141291翻译自: ... 内存管理是操作系统的核心; 是编程和系统管理的关键部分,在接下来的几篇文章中会从...
  • linux进程虚拟内存

    千次阅读 2018-12-06 23:30:17
    http://engineering.pivotal.io/post/virtual_memory_settings_in_linux_-_the_problem_with_overcommit/ https://manybutfinite.com/post/anatomy-of-a-program-in-mem...
  • linux查看进程占用cpu、内存、io信息

    千次阅读 2017-11-22 20:33:00
    1、查看CPU信息psps aux | sort -k3nr |head -n 10 上面显示按照按照消耗CPU前10排序的进程。top 命令然后界面输入大写的P,进程按照CPU消耗动态排序strace ...2、查看内存信息psps aux | sort -k4nr |head -n 10 ...
  • Linux操作系统中的内存映射

    千次阅读 2018-05-11 17:29:44
    内存映射Linux操作系统中与高效的跨进程通信 &amp; 文件操作息息相关。 定义 关联 进程中的1个虚拟内存区域 &amp; 1个磁盘上的对象,使得二者存在映射关系 被映射的对象称为:共享对象(普通文件 /...
  • linux 进程占用内存详解

    千次阅读 2014-07-28 10:46:16
    想必在linux上写过程序的同学都有分析进程占用多少内存的经历,或者被问到这样的问题——你的程序在运行时占用了多少内存(物理内存)?通常我们可以通过top命令查看进程占用了多少内存。这里我们可以看到VIRT、RES...
  • Linux驱动中mmap内存映射详解

    千次阅读 2019-07-01 17:14:15
    但是,由于应用程序不能直接操作设备硬件地址,所以操作系统提供了这样的一种机制——内存映射,把设备地址映射到进程虚拟地址,mmap就是实现内存映射的接口。 操作设备还有很多方法,如ioctl...
  • 想必在linux上写过程序的同学都有分析进程占用多少内存的经历,或者被问到这样的问题——你的程序在运行时占用了多少内存(物理内存)?通常我们可以通过top命令查看进程占用了多少内存。这里我们可以看到VIRT、RES...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 130,336
精华内容 52,134
关键字:

linux查看进程内存地址映射

linux 订阅