linux tmp目录与内存解析

linux tmp目录与内存解析,第1张

使用free可以获取到设备当前的内存

其中,各项表示的含义如下所示:

total: 总计物理内存大小。

used: 已使用内存大小。

free: 可使用内存大小。

shared: 多个进程共享的内存总额。

buffers/cached: 磁盘缓存大小。

单位都为KB。

对于系统而言,buffers和cached都是被使用的,所以可用内存为1037880KB。

对于应用程序而言,buffers和cached是可用的。当应用程序需要内存的时候,buffers和cached会被回收。从应用程序的角度而言,可用内存=free memory+buffers+cached。按上面的例子,即可用内存为1037880+18864+123656=1180400。

使用cat /proc/meminfo可以查看更详细的内存信息。

/proc/iomem:查看物理设备在物理内存中的映射关系

/proc/slabinfo:内核对象的当前使用状态

/proc/vmstat:虚拟内存统计信息。可以使用vmstat - Report virtual memory statistics 打印虚拟内存状态。

如上所示,/tmp为内存文件系统,内存挂载为内存文件系统(tmpfs)。使用的物理空间不是磁盘,而是内存条。设备重启后,/tmp下文件全清空。

tmpfs文件系统产生原因是为了提高性能。程序运行时候产生的临时文件放在磁盘会影响性能,于是tmpfs作为虚拟内存子系统来储存文件。POSIX共享内存也是基于tmpfs来实现的。

tmpfs的最大空间由RM(Real Memory,即物理内存)和swap(硬盘虚拟的内存空间)组成。

查看挂载信息,可知 tmpfs 文件系统的挂载点有两个,一个/dev/shm,另一个为/tmp。默认情况下, /tmp 将最多使用一半内存。

如果往/tmp文件夹加入大量文件,也会造成系统内存不足。

使用ps可以查看进程的状态

其中与内存相关项含义如下所示:

VSZ:虚拟内存大小。virtual memory size of the process in KiB (1024-byte units) Device mappings are currently excluded; this is subject to change (alias vsize)

RSS:实际使用物理内存。resident set size, the non-swapped physical memory that a task has used (in kiloBytes) (alias rssize, rsz)

虚拟内存是对进程而言使用的内存,会比较大,可以理解为一个内存布局,建立虚拟内存和磁盘文件的映射关系。在进程调用的时候,查找虚拟内存,将虚拟内存对应磁盘文件拷贝到物理内存,进行调度寻址等 *** 作。

进程使用的物理内存为RSS表示的物理内存大小,其中包括链接的动态库使用的内存,不只是单独进程使用的物理内存大小。

也可以通过/proc/下去查找对应进程id的相关内存信息。

/proc/pid/maps pid为进程号,显示当前进程所占用的虚拟地址

/proc/pid/statm 进程所占用的内存。

首先你要知道编译器给union的分配的内存空间是按占最大空间的变量决定,其他成员都是共享这段内存。

你的union中long b最大,int a和uchar c是共享这块内存;

所以给b赋值,自然a和c也分别有值了。

int占4个字节,所以是0x12345678,而c占一个字节,则是0x78

TIP:0x12 34 56 78是四个字节

而且根据你这个C输出结果来看你的系统应该是小端模式(比如X86),若是在ARM等处理器的环境下你的C输出估计是0x12,这个你以后学习socket编程的时候,肯定要了解的一个知识点(详细可以搜索一下大端模式与小端模式)

您问的具体是什么?

(1)是地址编号和集成电路里面(用显微镜看)各个单元的位置次序之间的关系?

(2)还是问程序中各个指令代码执行的次序和地址编号之间的关系?

(3)还是问程序中各个变量的次序和地址编号之间的关系?

如果是(1),那么

集成电路里面各个单元的位置次序,一般是不公开的。所以人们不知道它的次序是从左到右还是从右到左还是别的方式。据说,现在的布局大多是交叉分散排列的,因为程序中经常出现连续访问连续地址的 *** 作,如此分散排列,可以使功耗分散,减小局部温升,延长器件寿命。

如果是(2),那么

一般的指令,除了跳转指令和调用、返回指令以外,普通指令都是按照地址连续增加的次序,连续排列的。而且,汇编语言中书写程序清单的次序,除了使用特殊伪指令规定地址(如ORG指令)处以外,都是按照地址编号连续增加的次序书写的。如此,除跳转、调用、返回指令外,书写的次序就是执行的次序。

如果是(3),那么

用汇编语言设计程序时,你可以随自己习惯,觉得怎么安排方便,就怎么安排。

如果是高级语言,那么,不同的编译程序,可以有所不同。

不过,如果是C语言,那么数组内部各个下标变量的地址,必须是按照下标由小到大地址也由小到大的次序连续安排。这是因为,C语言中,对指针的运算有严格规定。

例如p是指向整数的指针,则p+2就应该等于指向p所指的整数变量后面第二个整数变量的指针。于是(p+2)相应的物理地址,就应该等于p相应的物理地址加上2倍int变量的长度。 而对于数组,又是按照指针的概念来规定的。例如:a[2]就和(a+2)完全等效。

后台执行。

JSP的工作原理。

(1)当用户访问一个JSP页面时,回想一个Servlet容器(Tomcat)发出请求;

(2)如果是第一次请求页面,或页面有所改动,则servlet容器首先要把JSP页面(假设为testjsp)转化为Servlet代码(testjava),再将其转化为(testclass文件);

(3)JSP容器负责调用从JSP转换来的servlet,这些servlet负责提供服务相应用户请求(比如客户端发送表单,要求servlet:formprocessorjava来处理,则容器会建立一个线程,调用formprocessorjava来处理该请求);如果用户有多个请求,则容器会建立多个线程处理多个请求;

(4)容器执行字节码文件(包括调用的servlet:formprocessorjava字节吗),并将其结果返回到客户端;(返回的最终方式是有servlet输出html格式的文件流)

所以java的代码编译都是后台编译执行的。

 [root@scs-2 tmp]# free

total used free shared buffers cached

Mem: 3266180 3250004 16176 0 110652 2668236

-/+ buffers/cache: 471116 2795064

Swap: 2048276 80160 1968116

下面是对这些数值的解释:

total:总计物理内存的大小。

used:已使用多大。

free:可用有多少。

Shared:多个进程共享的内存总额。

Buffers/cached:磁盘缓存的大小。

第三行(-/+ buffers/cached):

used:已使用多大。

free:可用有多少。

第四行就不多解释了。

区别:第二行(mem)的used/free与第三行(-/+ buffers/cache) used/free的区别。 这两个的区别在于使用的角度来看,第一行是从OS的角度来看,因为对于OS,buffers/cached 都是属于被使用,所以他的可用内存是16176KB,已用内存是3250004KB,其中包括,内核(OS)使用+Application(X, oracle,etc)使用的+buffers+cached

第三行所指的是从应用程序角度来看,对于应用程序来说,buffers/cached 是等于可用的,因为buffer/cached是为了提高文件读取的性能,当应用程序需在用到内存的时候,buffer/cached会很快地被回收。

所以从应用程序的角度来说,可用内存=系统free memory+buffers+cached。

如上例:

2795064=16176+110652+2668236

接下来解释什么时候内存会被交换,以及按什么方交换。 当可用内存少于额定值的时候,就会开会进行交换。

如何看额定值:

cat /proc/meminfo

[root@scs-2 tmp]# cat /proc/meminfo

MemTotal: 3266180 kB

MemFree: 17456 kB

Buffers: 111328 kB

Cached: 2664024 kB

SwapCached: 0 kB

Active: 467236 kB

Inactive: 2644928 kB

HighTotal: 0 kB

HighFree: 0 kB

LowTotal: 3266180 kB

LowFree: 17456 kB

SwapTotal: 2048276 kB

SwapFree: 1968116 kB

Dirty: 8 kB

Writeback: 0 kB

Mapped: 345360 kB

Slab: 112344 kB

Committed_AS: 535292 kB

PageTables: 2340 kB

VmallocTotal: 536870911 kB

VmallocUsed: 272696 kB

VmallocChunk: 536598175 kB

HugePages_Total: 0

HugePages_Free: 0

Hugepagesize: 2048 kB

用free -m查看的结果:

[root@scs-2 tmp]# free -m

total used free shared buffers cached

Mem: 3189 3173 16 0 107 2605

-/+ buffers/cache: 460 2729

Swap: 2000 78 1921

查看/proc/kcore文件的大小(内存镜像):

[root@scs-2 tmp]# ll -h /proc/kcore

-r——– 1 root root 41G Jun 12 12:04 /proc/kcore

备注:

占用内存的测量

测量一个进程占用了多少内存,linux为我们提供了一个很方便的方法,/proc目录为我们提供了所有的信息,实际上top等工具也通过这里来获取相应的信息。

/proc/meminfo 机器的内存使用信息

/proc/pid/maps pid为进程号,显示当前进程所占用的虚拟地址。

/proc/pid/statm 进程所占用的内存

[root@localhost ~]# cat /proc/self/statm

654 57 44 0 0 334 0

输出解释

CPU 以及CPU0。。。的每行的每个参数意思(以第一行为例)为:

参数 解释 /proc//status

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

查看机器可用内存

/proc/28248/>free

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

许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐,而这个k则被称为该数据类型的对齐模数(alignment modulus)。 当一种类型S的对齐模数与另一种类型T的对齐模数的比值是大于1的整数,我们就称类型S的对齐要求比T强(严格),而称T比S弱(宽松)。这种强制的要求一来简化了处理器与内存之间传输系统的设计,二来可以提升读取数据的速度。比如这么一种处理器,它每次读写内存的时候都从某个8倍数的地址开始,一次读出或写入8个字节的数据,假如软件能保证double类型的数据都从8倍数地址开始,那么读或写一个double类型数据就只需要一次内存 *** 作。否则,我们就可能需要两次内存 *** 作才能完成这个动作,因为数据或许恰好横跨在两个符合对齐要求的8字节内存块上。某些处理器在数据不满足对齐要求的情况下可能会出错,但是Intel的IA32架构的处理器则不管数据是否对齐都能正确工作。不过Intel奉劝大家,如果想提升性能,那么所有的程序数据都应该尽可能地对齐。

Win32平台下的微软C编译器(clexe for 80x86)在默认情况下采用如下的对齐规则:任何基本数据类型T的对齐模数就是T的大小,即sizeof(T)。比如对于double类型(8字节),就要求该类型数据的地址总是8的倍数,而char类型数据(1字节)则可以从任何一个地址开始。Linux下的GCC奉行的是另外一套规则(在资料中查得,并未验证,如错误请指正):任何2字节大小(包括单字节吗)的数据类型(比如short)的对齐模数是2,而其它所有超过2字节的数据类型(比如Long和double)都以4为对齐模数。

现在回到我们关心的struct上来。ANSIC规定一种结构类型的大小是它所有字段的大小以及字段之间或字段尾部的填充区大小之和。填充区就是为了使结构体字段满足内存对齐要求而额外分配给结构体的空间。那么结构体本身也有对齐要求,ANSIC标准规定结构体类型的对齐要求不能比它所有字段中要求最严格的那个宽松,可以更严格(但此非强制要求,VC71就仅仅是让它们一样严格)。我们来看一个例子(以下所有试验的环境是Intel Celeron 24G + WIN2000 PRO +vc71,内存对齐编译选项是"默认",即不指定/Zp与/pack选项):

typedef struct ms1 { char a; int b; } MS1;

MS1中有最强对齐要求的是b字段(int),所以根据编译器的对齐规则以及ANSIC标准,该结构体的内存布局图如下:

这个方案在a与b之间多分配了3个填充(padding)字节,这样当整个struct对象首地址满足4字节的对齐要求时,b字段也一定能满足int型的4字节对齐规定。那么sizeof(MS1)显然就应该是8,而b字段相对于结构体首地址的偏移就是4。非常好理解,对吗?现在我们把MS1中的字段交换一下顺序:

typedef struct ms2 { int a; char b; } MS2;

或许你认为MS2比MS1的情况要简单,它的布局应该就是 因为MS2对象同样要满足4字节对齐规定,而此时a的地址与结构体的首地址相等,所以它一定也是4字节对齐。可是却不全面。让我们来考虑一下定义一个MS2类型的数组会出现什么问题。C标准保证,任何类型(包括自定义结构类型)的数组所占空间的大小一定等于一个单独的该类型数据的大小乘以数组元素的个数。换句话说,数组各元素之间不会有空隙。按照上面的方案,一个MS2数组array的布局就是:当数组首地址是4字节对齐时,array[1]a也是4字节对齐,可是array[2]a呢?array[3]a 呢?可见这种方案在定义结构体数组时无法让数组中所有元素的字段都满足对齐规定,必须修改成如下形式:

现在无论是定义一个单独的MS2变量还是MS2数组,均能保证所有元素的所有字段都满足对齐规定。那么sizeof(MS2)仍然是8,而a的偏移为0,b的偏移是4。尝试分析一个稍微复杂点的类型。 typedef struct ms3 { char a; short b; double c; } MS3; 我想你一定能得出如下正确的布局图: sizeof(short)等于2,b字段应从偶数地址开始,所以a的后面填充一个字节,而sizeof(double)等于8,c字段要从8倍数地址开始,前面的a、b字段加上填充字节已经有4 bytes,所以b后面再填充4个字节就可以保证c字段的对齐要求了。sizeof(MS3)等于16,b的偏移是2,c的偏移是8。接着看看结构体中字段还是结构类型的情况: typedef struct ms4 { char a; MS3 b; } MS4; MS3中内存要求最严格的字段是c,那么MS3类型数据的对齐模数就与double的一致(为8),a字段后面应填充7个字节,因此MS4的布局应该是: 显然,sizeof(MS4)等于24,b的偏移等于8。

在实际开发中,我们可以通过指定/Zp编译选项或者在代码中用#pragma pack指令来更改编译器的对齐规则。比如指定/Zpn(VC71中n可以是1、2、4、8、16)就是告诉编译器最大对齐模数是n。或者定义结构时

#pragma pack(push, n)

typedef struct ms3 { char a; short b; double c; } MS3;

#pragma pack(pop);

在这种情况下,所有小于等于n字节的基本数据类型的对齐规则与默认的一样,但是大于n个字节的数据类型的对齐模数被限制为n。如果n = 1,那么结构体的大小就是各个字段的大小之和,在Moses中定义结构体的地方随处可见,这样做可以减少结构体所占用的内存空间。

VC71的默认对齐选项就相当于/Zp8。仔细看看MSDN对这个选项的描述,会发现它郑重告诫了程序员不要在MIPS和Alpha平台上用/Zp1和/Zp2选项,也不要在16位平台上指定/Zp4和/Zp8(想想为什么?)。 结构体的内存布局依赖于CPU、 *** 作系统、编译器及编译时的对齐选项,而你的程序可能需要运行在多种平台上,你的源代码可能要被不同的人用不同的编译器编译(试想你为别人提供一个开放源码的库),那么除非绝对必需,否则你的程序永远也不要依赖这些诡异的内存布局。顺便说一下,如果一个程序中的两个模块是用不同的对齐选项分别编译的,那么它很可能会产生一些非常微妙的错误。如果你的程序确实有很难理解的行为,不防仔细检查一下各个模块的编译选项。

---------------------------------------------------------------------------------

问题:下面的试验,请问如何解释?

#pragma pack(push, 2)

struct s

{

char a;

};

#pragma pack (pop)

void TestPack()

{

s c[2];

assert(sizeof(s)==1);

assert(sizeof(c)==2);

}

int _tmain(int argc, _TCHAR argv[])

{

TestPack();

return 0;

}

解答:

每一种基本的数据类型都有该数据类型的对齐模数(alignment modulus)。Win32平台下的微软C编译器(clexe for 80x86)在默认

情况下: 任何基本数据类型T的对齐模数就是T的大小,即sizeof(T)。

一组可能的对齐模数数据如下:

数据类型 模数

------------------

char 1

shor 2

int 4

double 8

ANSI C规定一种结构类型的大小是它所有字段的大小以及字段之间或字段尾部的填充区大小之和。

注:填充区就是为了使结构体字段满足内存对齐要求而额外分配给结构体的空间。

产生填充区的条件:

当结构体中的成员一种类型S的对齐模数与另一种类型T的对齐模数不一致的时候,才可能产生填充区。

我们通过编译选项设置/zpn 或#pragma pack(push, n) 来设置内存对齐模数时,当结构体中的某中基本数据类型的对齐模数大于n时才会影响填充区的大小,否则将会按照基本数据类型的对齐模数进行对齐。

例子:

当n = 1时:

#pragma pack(push,1)

typedef struct ms3{char a; short b; double c; } MS3;

#pragma pack(pop)

这时n=1,此结构中基本数据类型short的对齐模数为2,double为8,大于n 所以将会影响这两个变量存储时地址的偏移量,必须是n的整数倍,而char的对齐模数是1,小于等于n,将会按照其自身的对齐模数1进行对齐

因为n=1,所以这三个变量在内存中是连续的而不存在填充区内存布局如下:

___________________________

| a | b | c |

+-------------------------+

Bytes: 1 2 8

sizeof(MS3) = 11

当n = 2时:

#pragma pack(push,2)

typedef struct ms3{char a; short b; double c; } MS3;

#pragma pack(pop)

这时n=2,此结构中基本数据类型double的对齐模数为8,大于n, 所以将会影响这个变量存储时地址的偏移量,必须是n的整数倍,而char 和 short 的对齐模数小于等于n, 将会按照其自身的对齐模数分别是1,2进行对齐内存布局如下:

____________________________

| a |\| b | c |

+---------------------------+

Bytes: 1 1 2 8

此时变量c的存储地址偏移是4,是n=2的整数倍,当然偏移为6,8等等时也满足这个条件,但编译器不至于愚蠢到这种地步白白浪费空间,呵。

sizeof(MS3) = 12

当n = 4时:与n=2时结果是一样的

当n = 8时:

#pragma pack(push,8)

typedef struct ms3{char a; short b; double c; } MS3;

#pragma pack(pop)

这时n=8,此结构中char ,short ,double的对齐模数为都,小于等于n,将会按照其自身的对齐模数分别是1,2,8进行对齐即:short变量存储时地址的偏移量是2的倍数;double变量存储时地址的偏移量是8的倍数

内存布局如下:

_______________________________________

| a |\| b |\padding\| c |

+-------------------------------------+

Bytes: 1 1 2 4 8

此时变量a的存储地址偏移是0,当然也是char型对齐模数1的整数倍了

变量b的存储地址偏移要想是short型对齐模数2的整数倍,因为前面a占了1 个byte ,所以至少在a 与b之间再加上1 个byte的padding才能满足条件。

变量c的存储地址骗移要想是double型对齐模数8的整数倍,因为前面a 和b 加 1个byte 的padding,共4 bytes所以最少还需要4 bytes的padding才能满足条件。

sizeof(MS3) = 16

当n = 16时:与n=8时结果是一样的

====================================================

根据上面的分析,如下定义的结构

#pragma pack(push, 2)

struct s

{

char a;

};

#pragma pack (pop)

因为char 的对齐模数是1,小于n=2,所以将按照自身的对齐模数对齐。根本就不会存在填充区,所以sizeof(s) = 1对于s c[2]; sizeof(c)==2 也是必然的。

再看下面的结构:

#pragma pack(push, n)//n=(1,2,4,8,16)

struct s

{

double a;

double b;

double c;

};

#pragma pack (pop)

对于这样的结构无论pack设置的对齐模数为几都不会影响其大小,即无padding

double 类型的对齐模数为8

当n<8时,虽然满足前面讲的规则:当结构体中的某中基本数据类型的对齐模数大于n时才会影响填充区的大小。但这个时候无论n等于几(1,2,4),double 变量存储时地址的偏移量都是n的整数倍,所以根本不需要填充区。当n>=8时,自然就按照double的对齐模数进行对齐了因为类型都一样所以变量之间在内存中不会存在填充区

---------------------------------------------------------------------------------------------------------------------

补充一点:

如果在定义结构的时候,仔细调整顺序,适当明确填充方式,则最终内存结果可以与编译选项/Zpn 或 pack无关。

举个例子:

typedef struct ms1{ char a; char b; int c; short d; } MS1;

在不同的 /Zpn下,sizeof(MS1)的长度可能不同,也就是内存布局不同。

如果改成

typedef struct ms2{ char a; char b; short d; int c; } MS2;

即便在不同的/Zpn或pack方式下,编译生成的内存布局总是相同的;

再比如:

typedef struct ms3{ char a; char b; int c; } MS3;

可以改写成:

typedef struct ms4{ char a; char b; short padding; int c; } MS4; 显式地写上 padding

(通过源代码本身来消除隐患,要比依赖编译选项更加可靠,并易于移植,优质的代码应该做到这一点)

(减少隐含padding的另外一个好处是少占内存,当结构的实例数量很大时,内存的节省量是非常可观的)

(以上的变量/结构命名没有遵循命名规范,只为说明用,不可模仿)

程序如果要被CPU执行,就得编译成CPU可以执行的指令,一大堆的程序就变成了一堆的指令。

一个 *** 作系统它也是一堆程序组成的,可以想象CPU的指令是很多的,但是这么多的指令中,有些指令涉及到系统底层的东西,如果有些指令错用或者使用不当是非常危险的,比如清内存、设置时钟、修改用户访问权限、分配系统资源等等,可能导致系统崩溃。

CPU将这些指令进行了分类,分为 特权指令 非特权指令 ,不让所有程序都能使用所有指令,如果所有程序都能使用,那系统崩溃就会变得非常常见了。

*** 作系统的核心是内核,它是独立于普通的应用程序,负责管理系统的进程、内存、设备驱动程序、文件和网络系统,决定着系统的性能和稳定性,所以一定要保证内核的安全。

为了保护内核的安全, *** 作系统一般都限制用户进程不能直接 *** 作内核,在32位 *** 作系统总的地址空间4G(2^32 = 4GB),实现这个限制的方式就是 *** 作系统将总的地址空间分为两个部分,对于Linux *** 作系统:

你该知道你写的程序的内存布局

总之,有1G的内核空间是每个进程共享的,剩下的3G是进程自己使用的。

在内核态下,CPU可以执行指令系统的全集,也就是说内核态进程可以调用系统的一切资源,但是特权指令只能在内核态下执行,它不直接提供给用户使用,用户态下只能使用非特权指令,也就是说用户态进程只能执行简单运算,不能直接调用系统资源。

那么CPU如何知道当前是否可以使用特权指令?

Linux *** 作系统通过区分内核空间和用户空间的这种设计,将 *** 作系统代码和用户程序代码分开,这样即使在某一个应用程序出错,也不会影响到 *** 作系统,再说,Linux *** 作系统是多任务系统,其它应用程序不也还能运行。

现代 *** 作系统基本上都是分内核空间和用户空间的做法,来 保护 *** 作系统自身的安全性和稳定性,这也是区分内核空间和用户空间的本质。

你也可以继续阅读 点击 以下文章,下面是我推荐给大家的几篇文章:

1《 竟然把通信协议讲的如此通俗?

2《 c++如何学习?赶紧收藏这些好书

3《 select和epoll的前世今生

4《 彻底明白Linux硬链接和软链接

以上就是关于linux tmp目录与内存解析全部的内容,包括:linux tmp目录与内存解析、想请问一下关于C语言的一道程序运行题目,为什么运行结果是12345678和78、C语言的内存地址是按什么顺序排列的:比如是按从大到小还是内存自动分配的,请举例等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zz/9291519.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-26
下一篇 2023-04-26

发表评论

登录后才能评论

评论列表(0条)

保存