Linux内存点滴 用户进程内存空间

经常使用top命令了解进程信息,其中包括内存方面的信息。命令top帮助文档是这么解释各个字段的。

VIRT, Virtual Image (kb)

RES, Resident size (kb) 实际使用

SHR, Shared Mem size (kb)

%MEM, Memory usage(kb)

SWAP, Swapped size (kb)

CODE, Code size (kb)

DATA, Data+Stack size (kb)

nFLT, Page Fault count

nDRT, Dirty Pages count

尽管有注释,但依然感觉有些晦涩,不知所指何意?

进程内存空间

正在运行的程序,叫进程。每个进程都有完全属于自己的,独立的,不被干扰的内存空间。此空间,被分成几个段(Segment),分别是Text, Data, BSS, Heap, Stack。用户进程内存空间,也是系统内核分配给该进程的VM(虚拟内存),但并不表示这个进程占用了这么多的RAM(物理内存)。这个空间有多大?命令top输出的VIRT值告诉了我们各个进程内存空间的大小(进程内存空间随着程序的执行会增大或者缩小)。你还可以通过/proc//maps,或者__pmap -d__了解某个进程内存空间都分布,比如:

[root@jiangyi01.sqa.zmf /home/ahao.mah]
#cat /proc/58794/maps
00400000-004dd000 r-xp 00000000 08:02 3151171 /usr/bin/bash
006dc000-006dd000 r--p 000dc000 08:02 3151171 /usr/bin/bash
006dd000-006e6000 rw-p 000dd000 08:02 3151171 /usr/bin/bash
006e6000-006ec000 rw-p 00000000 00:00 0
01d01000-01d29000 rw-p 00000000 00:00 0 [heap]
7f20b30a3000-7f20f30a4000 rw-p 00000000 00:00 0
7f21070ac000-7f21870ad000 rw-p 00000000 00:00 0
7f21870ad000-7f21870b9000 r-xp 00000000 08:02 5211 /usr/lib64/libnss_files-2.17.so
7f21870b9000-7f21872b8000 ---p 0000c000 08:02 5211 /usr/lib64/libnss_files-2.17.so
7f21872b8000-7f21872b9000 r--p 0000b000 08:02 5211 /usr/lib64/libnss_files-2.17.so
7f21872b9000-7f21872ba000 rw-p 0000c000 08:02 5211 /usr/lib64/libnss_files-2.17.so
7f21872ba000-7f21872c0000 rw-p 00000000 00:00 0
7f21872c0000-7f218d7e7000 r--p 00000000 08:02 3154583 /usr/lib/locale/locale-archive
7f218d7e7000-7f218d99d000 r-xp 00000000 08:02 6438 /usr/lib64/libc-2.17.so
7f218d99d000-7f218db9d000 ---p 001b6000 08:02 6438 /usr/lib64/libc-2.17.so
7f218db9d000-7f218dba1000 r--p 001b6000 08:02 6438 /usr/lib64/libc-2.17.so
7f218dba1000-7f218dba3000 rw-p 001ba000 08:02 6438 /usr/lib64/libc-2.17.so
7f218dba3000-7f218dba8000 rw-p 00000000 00:00 0
7f218dba8000-7f218dbab000 r-xp 00000000 08:02 6361 /usr/lib64/libdl-2.17.so
7f218dbab000-7f218ddaa000 ---p 00003000 08:02 6361 /usr/lib64/libdl-2.17.so
7f218ddaa000-7f218ddab000 r--p 00002000 08:02 6361 /usr/lib64/libdl-2.17.so
7f218ddab000-7f218ddac000 rw-p 00003000 08:02 6361 /usr/lib64/libdl-2.17.so
7f218ddac000-7f218ddd1000 r-xp 00000000 08:02 6208 /usr/lib64/libtinfo.so.5.9
7f218ddd1000-7f218dfd1000 ---p 00025000 08:02 6208 /usr/lib64/libtinfo.so.5.9
7f218dfd1000-7f218dfd5000 r--p 00025000 08:02 6208 /usr/lib64/libtinfo.so.5.9
7f218dfd5000-7f218dfd6000 rw-p 00029000 08:02 6208 /usr/lib64/libtinfo.so.5.9
7f218dfd6000-7f218dff7000 r-xp 00000000 08:02 6760 /usr/lib64/ld-2.17.so
7f218e1e8000-7f218e1eb000 rw-p 00000000 00:00 0
7f218e1ef000-7f218e1f6000 r--s 00000000 08:02 133187 /usr/lib64/gconv/gconv-modules.cache
7f218e1f6000-7f218e1f7000 rw-p 00000000 00:00 0
7f218e1f7000-7f218e1f8000 r--p 00021000 08:02 6760 /usr/lib64/ld-2.17.so
7f218e1f8000-7f218e1f9000 rw-p 00022000 08:02 6760 /usr/lib64/ld-2.17.so
7f218e1f9000-7f218e1fa000 rw-p 00000000 00:00 0
7ffe99384000-7ffe993a5000 rw-p 00000000 00:00 0 [stack]
7ffe993f2000-7ffe993f4000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
[root@jiangyi01.sqa.zmf /home/ahao.mah]
#pmap -d 58794
58794: /usr/bin/bash /usr/libexec/ee
Address Kbytes Mode Offset Device Mapping
0000000000400000 884 r-x-- 0000000000000000 008:00002 bash
00000000006dc000 4 r---- 00000000000dc000 008:00002 bash
00000000006dd000 36 rw--- 00000000000dd000 008:00002 bash
00000000006e6000 24 rw--- 0000000000000000 000:00000 [ anon ]
0000000001d01000 160 rw--- 0000000000000000 000:00000 [ anon ]
00007f20f30a4000 1572872 rw--- 0000000000000000 000:00000 [ anon ]
00007f21670ac000 524292 rw--- 0000000000000000 000:00000 [ anon ]
00007f21870ad000 48 r-x-- 0000000000000000 008:00002 libnss_files-2.17.so
00007f21870b9000 2044 ----- 000000000000c000 008:00002 libnss_files-2.17.so
00007f21872b8000 4 r---- 000000000000b000 008:00002 libnss_files-2.17.so
00007f21872b9000 4 rw--- 000000000000c000 008:00002 libnss_files-2.17.so
00007f21872ba000 24 rw--- 0000000000000000 000:00000 [ anon ]
00007f21872c0000 103580 r---- 0000000000000000 008:00002 locale-archive
00007f218d7e7000 1752 r-x-- 0000000000000000 008:00002 libc-2.17.so
00007f218d99d000 2048 ----- 00000000001b6000 008:00002 libc-2.17.so
00007f218db9d000 16 r---- 00000000001b6000 008:00002 libc-2.17.so
00007f218dba1000 8 rw--- 00000000001ba000 008:00002 libc-2.17.so
00007f218dba3000 20 rw--- 0000000000000000 000:00000 [ anon ]
00007f218dba8000 12 r-x-- 0000000000000000 008:00002 libdl-2.17.so
00007f218dbab000 2044 ----- 0000000000003000 008:00002 libdl-2.17.so
00007f218ddaa000 4 r---- 0000000000002000 008:00002 libdl-2.17.so
00007f218ddab000 4 rw--- 0000000000003000 008:00002 libdl-2.17.so
00007f218ddac000 148 r-x-- 0000000000000000 008:00002 libtinfo.so.5.9
00007f218ddd1000 2048 ----- 0000000000025000 008:00002 libtinfo.so.5.9
00007f218dfd1000 16 r---- 0000000000025000 008:00002 libtinfo.so.5.9
00007f218dfd5000 4 rw--- 0000000000029000 008:00002 libtinfo.so.5.9
00007f218dfd6000 132 r-x-- 0000000000000000 008:00002 ld-2.17.so
00007f218e1e8000 12 rw--- 0000000000000000 000:00000 [ anon ]
00007f218e1ef000 28 r--s- 0000000000000000 008:00002 gconv-modules.cache
00007f218e1f6000 4 rw--- 0000000000000000 000:00000 [ anon ]
00007f218e1f7000 4 r---- 0000000000021000 008:00002 ld-2.17.so
00007f218e1f8000 4 rw--- 0000000000022000 008:00002 ld-2.17.so
00007f218e1f9000 4 rw--- 0000000000000000 000:00000 [ anon ]
00007ffe99384000 132 rw--- 0000000000000000 000:00000 [ stack ]
00007ffe993f2000 8 r-x-- 0000000000000000 000:00000 [ anon ]
ffffffffff600000 4 r-x-- 0000000000000000 000:00000 [ anon ]
mapped: 2212432K writeable/private: 2097604K shared: 28K

VM分配与释放

“内存总是被进程占用”,这句话换过来可以这么理解:进程总是需要内存。当fork()或者exec()一个进程的时候,系统内核就会分配一定量的VM给进程,作为进程的内存空间,大小由BSS段,Data段的已定义的全局变量、静态变量、Text段中的字符直接量、程序本身的内存映像等,还有Stack段的局部变量决定。当然,还可以通过malloc()等函数动态分配内存,向上扩大heap。

动态分配与静态分配,二者最大的区别在于:1. 直到Run-Time的时候,执行动态分配,而在compile-time的时候,就已经决定好了分配多少Text+Data+BSS+Stack。2.通过malloc()动态分配的内存,需要程序员手工调用free()释放内存,否则容易导致内存泄露,而静态分配的内存则在进程执行结束后系统释放(Text, Data), 但Stack段中的数据很短暂,函数退出立即被销毁。

我们使用几个示例小程序,加深理解

/* @filename: example-2.c */
#include <stdio.h> int main(int argc, char *argv[])
{
char arr[] = "hello world"; /* Stack段,rw--- */
char *p = "hello world"; /* Text段,字符串直接量, r-x-- */
arr[1] = 'l';
*(++p) = 'l'; /* 出错了,Text段不能write */
return 0;
}

PS:变量p,它在Stack段,但它所指的”hello world”是一个字符串直接量,放在Text段。

/* @filename:example_2_2.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h> char *get_str_1()
{
char str[] = "hello world";
return str;
} char *get_str_2()
{
char *str = "hello world";
return str;
} char *get_str_3()
{
char tmp[] = "hello world";
char *str;
str = (char *)malloc(12 * sizeof(char));
memcpy(str, tmp, 12);
return str;
} int main(int argc, char *argv[])
{
char *str_1 = get_str_1(); //出错了,Stack段中的数据在函数退出时就销毁了
char *str_2 = get_str_2(); //正确,指向Text段中的字符直接量,退出程序后才会回收
char *str_3 = get_str_3(); //正确,指向Heap段中的数据,还没free()printf("%s\n", str_1);printf("%s\n", str_2);printf("%s\n", str_3);
if (str_3 != NULL)
{
free(str_3);
str_3 = NULL;
}
return 0;
}

PS:函数get_str_1()返回Stack段数据,编译时会报错。Heap中的数据,如果不用了,应该尽早释放free()。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> char data_var = '1';
char *mem_killer()
{
char *p;
p = (char *)malloc(1024*1024*4);
memset(p, '\0', 1024*1024*4);
p = &data_var; //危险,内存泄露
return p;
} int main(int argc, char *argv[])
{
char *p;
for (;;)
{
p = mem_killer(); // 函数中malloc()分配的内存没办法free()printf("%c\n", *p);
sleep(20);
}
return 0;
}

PS:使用malloc(),特别要留意heap段中的内存不用时,尽早手工free()。通过top输出的VIRT和RES两值来观察进程占用VM和RAM大小。

本节结束之前,介绍工具size。因为Text, BSS, Data段在编译时已经决定了进程将占用多少VM。可以通过size,知道这些信息。

# gcc example_2_3.c -o example_2_3
# size example_2_3 textdatabssdec hexfilename 140327281683693example_2_3

malloc()

编码人员在编写程序之际,时常要处理变化数据,无法预料要处理的数据集变化是否大(phper可能难以理解),所以除了变量之外,还需要动态分配内存。GNU libc库提供了二个内存分配函数,分别是malloc()和calloc()。调用malloc(size_t size)函数分配内存成功,总会分配size字节VM(再次强调不是RAM),并返回一个指向刚才所分配内存区域的开端地址。分配的内存会为进程一直保留着,直到你显示地调用free()释放它(当然,整个进程结束,静态和动态分配的内存都会被系统回收)。开发人员有责任尽早将动态分配的内存释放回系统。记住一句话:尽早free()!

我们来看看,malloc()小示例。

/* @filename:example_2_4.c */
#include <stdio.h>
#include <stdlib.h> int main(int argc, char *argv[])
{
char *p_4kb, *p_128kb, *p_300kb;
if ((p_4kb = malloc(4*1024)) != NULL)
{
free(p_4kb);
}
if ((p_128kb = malloc(128*1024)) != NULL)
{
free(p_128kb);
}
if ((p_300kb = malloc(300*1024)) != NULL)
{
free(p_300kb);
}
return 0;
}
#gcc example_2_4.c -o example_2_4
#strace -t ./example_2_4

00:02:53 brk(0) = 0x8f58000
00:02:53 brk(0x8f7a000) = 0x8f7a000
00:02:53 brk(0x8f79000) = 0x8f79000
00:02:53 mmap2(NULL, 311296, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb772d000
00:02:53 munmap(0xb772d000, 311296) = 0

PS:系统调用brk(0)取得当前堆的地址,也称为断点。

通过跟踪系统内核调用,可见glibc函数malloc()总是通过brk()或mmap()系统调用来满足内存分配需求。函数malloc(),根据不同大小内存要求来选择brk(),还是mmap(), 128Kbytes是临界值。小块内存()。留意紫圈标注

示意图:函数malloc(1024*1024),大于128kbytes,在heap与stack之间。留意紫圈。PS:图中的Data Segment泛指BSS, Data, Heap。有些文档有说明:数据段有三个子区域,分别是BSS, Data, Heap。

缺页异常(Fault Page)

每次调用malloc(),系统都只是给进程分配线性地址(VM),并没有随即分配页框(RAM)。系统尽量将分配页框的工作推迟到最后一刻—用到时缺页异常处理。这种页框按需延迟分配策略最大好处之一:充分有效地善用系统稀缺资源RAM。

当指针引用的内存页没有驻留在RAM中,即在RAM找不到与之对应的页框,则会发生缺页异常(对进程来说是透明的),内核便陷入缺页异常处理。发生缺页异常有几种情况:1.只分配了线性地址,并没有分配页框,常发生在第一次访问某内存页。2.已经分配了页框,但页框被回收,换出至磁盘(交换区)。3.引用的内存页,在进程空间之外,不属于该进程,可能已被free()。我们使用一段伪代码来大致了解缺页异常。

/* @filename: example_2_5.c */

demo()
{
char *p;
//分配了100Kbytes线性地址
if ((p = malloc(1024*100)) != NULL) // L0
{
*p = ‘t’; // L1
… //过去了很长一段时间,不管系统忙否,长久不用的页框都有可能被回收
*p = ‘m’; // L2
p[4096] = ‘p’; // L3

free(p); //L4
if (p == NULL)
{
*p = ‘l’; // L5
}
}
}

L0,函数malloc()通过brk()给进程分配了100Kbytes的线性地址区域(VM).然而,系统并没有随即分配页框(RAM)。即此时,进程没有占用100Kbytes的物理内存。这也表明了,你时常在使用top的时候VIRT值增大,而RES值却不变的原因。

L1,通过*p引用了100Kbytes的第一页(4Kbytes)。因为是第一次引用此页,在RAM中找不到与之相对应的页框。发生缺页异常(对于进程而言缺页异常是透明的),系统灵敏地捕获这一异常,进入缺页异常处理阶段:接下来,系统会分配一个页框(RAM)映射给它。我们把这种情况(被访问的页还没有被放在任何一个页框中,内核分配一新的页框并适当初始化来满足调用请求),也称为Demand Paging。

L2,过了很长一段时间,通过*p再次引用100Kbytes的第一页。若系统在RAM找不到它映射的页框(可能交换至磁盘了)。发生缺页异常,并被系统捕获进入缺页异常处理。接下来,系统则会分配一页页框(RAM),找到备份在磁盘的那“页”,并将它换入内存(其实因为换入操作比较昂贵,所以不总是只换入一页,而是预换入多页。这也表明某些文档说:”vmstat某时出现不少si并不能意味着物理内存不足”)。凡是类似这种会迫使进程去睡眠(很可能是由于当前磁盘数据填充至页框(RAM)所花的时间),阻塞当前进程的缺页异常处理称为主缺页(major falut),也称为大缺页(参见下图)。相反,不会阻塞进程的缺页,称为次缺页(minor fault),也称为小缺面。

L3,引用了100Kbytes的第二页。参见第一次访问100Kbytes第一页, Demand Paging。

L4,释放了内存:线性地址区域被删除,页框也被释放。

L5,再次通过*p引用内存页,已被free()了(用户进程本身并不知道)。发生缺页异常,缺面异常处理程序会检查出这个缺页不在进程内存空间之内。对待这种编程错误引起的缺页异常,系统会杀掉这个进程,并且报告著名的段错误(Segmentation fault)。

页框回收PFRA

随着网络并发用户数量增多,进程数量越来越多(比如一般守护进程会fork()子进程来处理用户请求),缺页异常也就更频繁,需要缓存更多的磁盘数据(参考下篇OS Page Cache),RAM也就越来越紧少。为了保证有够用的页框供给缺页异常处理,Linux有一套自己的做法,称为PFRA。PFRA总会从用户态进内存程空间和页面缓存中,“窃取”页框满足供给。所谓”窃取”,指的是:将用户进程内存空间对应占用的页框中的数据swap out至磁盘(称为交换区),或者将OS页面缓存中的内存页(还有用户进程mmap()的内存页)flush(同步fsync())至磁盘设备。PS:如果你观察到因为RAM不足导致系统病态式般慢,通常都是因为缺页异常处理,以及PFRA在”盗页”。我们从以下几个方面了解PFRA。

候选页框:找出哪些页框是可以被回收?

进程内存空间占用的页框,比如数据段中的页(Heap, Data),还有在Heap与Stack之间的匿名映射页(比如由malloc()分配的大内存)。但不包括Stack段中的页。

进程空间mmap()的内存页,有映射文件,非匿名映射。

缓存在页面缓存中Buffer/Cache占用的页框。也称OS Page Cache。

页框回收策略:确定了要回收的页框,就要进一步确定先回收哪些候选页框

尽量先回收页面缓存中的Buffer/Cache。其次再回收内存空间占用的页框。

进程空间占用的页框,要是没有被锁定,都可以回收。所以,当某进程睡眠久了,占用的页框会逐渐地交换出去至交换区。

使收LRU置换算法,将那些久而未用的页框优先被回收。这种被放在LRU的unused链表的页,常被认为接下来也不太可能会被引用。

相对回收Buffer/Cache而言,回收进程内存页,昂贵很多。所以,Linux默认只有swap_tendency(交换倾向值)值不小于100时,才会选择换出进程占用的RES。其实交换倾向值描述的是:系统越忙,且RES都被进程占用了,Buffer/Cache只占了一点点的时候,才开始回收进程占用页框。PS:这正表明了,某些DBA提议将MySQL InnoDB服务器vm.swappiness值设置为0,以此让InnoDB Buffer Pool数据在RES呆得更久。

如果实在是没有页框可回收,PFRA使出最狠一招,杀掉一个用户态进程,并释放这些被占的页框。当然,这个被杀的进程不是胡乱选的,至少应该是占用较多页框,运行优选级低,且不是root用户的进程。

激活回收页框:什么时候会回收页框?

紧急回收。系统内核发现没有够用的页框分配,供给读文件和内存缺页处理的时候,系统内核开始”紧急回收页框”。唤醒pdflush内核线程,先将1024页脏页从页面缓存写回磁盘。然后开始回收32页框,若反复回收13次,还收不齐32页框,则发狠杀一个进程。

周期性回收。在紧急回收之前,PFRA还会唤醒内核线程kswapd。为了避免更多的“紧急回收”,当发现空闲页框数量低于设置的警告值时,内核线程kswapd就会被唤醒,回收页框。直到空闲的页框的数量达到设定的安全值。PS:当RES资源紧张的时候,你可以通过ps命令看到更多的kswapd线程被唤醒。

OOM。在高峰时期,RES高度紧张的时候,kswapd持续回收的页框供不应求,直到进入”紧急回收”,直到 OOM。

Paging 和Swapping

这二个关键字在很多地方出现,译过来应该是Paging(调页),Swapping(交换)。PS:英语里面用得多的动词加上ing,就成了名词,比如building。咬文嚼字,实在是太难。看二图

Swapping的大部分时间花在数据传输上,交换的数据也越多,意味时间开销也随之增加。对于进程而言,这个过程是透明的。由于RAM资源不足,PFRA会将部分匿名页框的数据写入到交换区(swap area),备份之,这个动作称为so(swap out)。等到发生内存缺页异常的时候,缺页异常处理程序会将交换区(磁盘)的页面又读回物理内存,这个动作称为si(swap in)。每次Swapping,都有可能不只是一页数据,不管是si,还是so。Swapping意味着磁盘操作,更新页表等操作,这些操作开销都不小,会阻塞用户态进程。所以,持续飚高的si/so意味着物理内存资源是性能瓶颈。

Paging,前文我们有说过Demand Paging。通过线性地址找到物理地址,找到页框。这个过程,可以认为是Paging,对于进程来讲,也是透明的。Paging意味着产生缺页异常,也有可能是大缺页,也就意味着浪费更多的CPU时间片资源。

总结

1.用户进程内存空间分为5段,Text, DATA, BSS, Heap, Stack。其中Text只读可执行,DATA全局变量和静态变量,Heap用完就尽早free(),Stack里面的数据是临时的,退出函数就没了。

2.glibc malloc()动态分配内存。使用brk()或者mmap(),128Kbytes是一个临界值。避免内存泄露,避免野指针。

3.内核会尽量延后Demand Paging。主缺页是昂贵的。

4.先回收Buffer/Cache占用的页框,然后程序占用的页框,使用LRU置换算法。调小vm.swappiness值可以减少Swapping,减少大缺页。

5.更少的Paging和Swapping

6.fork()继承父进程的地址空间,不过是只读,使用cow技术,fork()函数特殊在于它返回二次

Linux内存点滴 用户进程内存空间的更多相关文章

  1. Linux内存点滴:用户进程内存空间

    原文出处:PerfGeeks 经常使用top命令了解进程信息,其中包括内存方面的信息.命令top帮助文档是这么解释各个字段的.VIRT , Virtual Image (kb)RES, Residen ...

  2. linux上限制用户进程数、cpu占用率、内存使用率

    限制进程CPU占用率的问题,给出了一个shell脚本代码如下: renice +10 `ps aux | awk '{ if ($3 > 0.8 && id -u $1 > ...

  3. 放开Linux内核对用户进程可打开文件数和TCP连接的限制

    一. 检查linux内核uname -alsb_release -a 二. 用户进程可打开文件数限制1) vim /etc/security/limits.conf*       -      nof ...

  4. Linux进程地址空间 && 进程内存布局[转]

    一 进程空间分布概述       对于一个进程,其空间分布如下图所示: 程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码. 初始化过的数据(Data):在程序运行初已经对变量进行初始 ...

  5. linux进程用户内存空间和内核空间

    When a process running in user mode requests additional memory, pages are allocated from the list of ...

  6. (转)Linux内核本身和进程的区别 内核线程、用户进程、用户线程

    转自:http://blog.csdn.net/adudurant/article/details/23135661 这个概念是很多人都混淆的了,我也是,刚开始无法理解OS时,把Linux内核也当做一 ...

  7. linux内核线程,进程,线程

    http://blog.csdn.net/dyllove98/article/details/8917197 Linux对于内存的管理涉及到非常多的方面,这篇文章首先从对进程虚拟地址空间的管理说起.( ...

  8. Linux虚拟地址空间布局以及进程栈和线程栈总结【转】

    转自:http://www.cnblogs.com/xzzzh/p/6596982.html 原文链接:http://blog.csdn.net/freeelinux/article/details/ ...

  9. Linux虚拟地址空间布局以及进程栈和线程栈总结

    原文链接:http://blog.csdn.net/freeelinux/article/details/53782986[侵删] 本文转自多个博客,以及最后有我的总结.我没有单独从头到尾写一个总结的 ...

随机推荐

  1. testlink 下载地址

    testlink 下载地址 https://sourceforge.net/projects/testlink/files/TestLink%201.9/

  2. XAMPP安装及配置注意事项

    1.下载对应版本以后,解压安装 2.设置环境变量中的path,即D:\xampp\mysql\bin 3.设置监听端口 4.解决端口冲突问题 5.各种测试网址注意事项 由于很晚了,先记录下来,明天补充 ...

  3. javascript实现的手风琴折叠菜单效果

    演示地址:http://codepen.io/anon/pen/pJERMq 实现效果: HTML代码: <!DOCTYPE html> <html lang="en&qu ...

  4. 关于C#中的弱引用

    本文前部分来自:http://www.cnblogs.com/mokey/archive/2011/11/24/2261605.html 分割线后为作者补充部分. 一:什么是弱引用 了解弱引用之前,先 ...

  5. HDU2111 Saving HDU 【贪心】

    Saving HDU Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total ...

  6. JMeter 参数化、检查点、集合点

      参数化:简单的来理解一下,我们录制了一个脚本,这个脚本中有登录操作,需要输入用户名和密码,假如系统不允许相同的用户名和密码同时登录,或者想更好的模拟多个用户来登录系统. 这个时候就需要对用户名和密 ...

  7. [Cycle.js] Making our toy DOM Driver more flexible

    Our previous toy DOM Driver is still primitive. We are only able to sends strings as the textContent ...

  8. 【Android 应用开发】 ActionBar 样式详解 -- 样式 主题 简介 Actionbar 的 icon logo 标题 菜单样式修改

    作者 : 万境绝尘 (octopus_truth@163.com) 转载请著名出处 : http://blog.csdn.net/shulianghan/article/details/3926916 ...

  9. Dynamics CRM 2016 使用Plug-in Trace Log调试插件

    1.写插件 首先,让我们写一个简单的插件来测试新插件跟踪日志功能.请注意,在下面的示例代码中,我们增加ITracingService的一个实例,以及记录有关插件的执行信息记录的一些键值: 2.注册插件 ...

  10. OC特有语法-分类(category)

    本文转载Keefo. Objective-C的Object-oriented programming特性提供subclass和category这2个比较非常重要的部分.subclass应该反复被各种编 ...