转自:http://blog.csdn.net/boymax2/article/details/52550197

版权声明:本文为博主原创文章,未经博主允许不得转载。

Magenta内核支持虚拟地址的配置,依赖于cpu内的mmu模块。

下面会从以下几个方面对Magenta内核内存管理方面的代码进行分析:

、mmu初始化,也就是硬件mmu的初始化,以底层寄存器操作为主,汇编

、pmm初始化,也就是代码中物理内存结构的初始化

、vmm初始化,也就是代码中虚拟内存结构的初始化

mmu初始化

mmu初始化的代码由汇编完成,其中主要涉及了以下几个结构

TLB:内存映射表,其定义位于c代码中

kernel/arch/arm/arm/mmu.c

[cpp] view plain copy

    uint32_t arm_kernel_translation_table[TT_ENTRY_COUNT] __ALIGNED() __SECTION(".bss.prebss.translation_table");  

以及初始化的内存映射关系,以qemu-virt平台

kernel/platform/qemu-virt/platform.c

[cpp] view plain copy

    struct mmu_initial_mapping mmu_initial_mappings[] = {
/* all of memory */
{
.phys = MEMORY_BASE_PHYS, // 内存物理基地址
.virt = KERNEL_BASE, // 内存虚拟基地址
.size = MEMORY_APERTURE_SIZE,// 虚拟内存大小
.flags = ,
.name = "memory"
}, /* 1GB of peripherals */
{
.phys = PERIPHERAL_BASE_PHYS, // 外设物理基地址
.virt = PERIPHERAL_BASE_VIRT, // 外设虚拟基地址
.size = PERIPHERAL_BASE_SIZE, // 虚拟内存大小
.flags = MMU_INITIAL_MAPPING_FLAG_DEVICE,
.name = "peripherals"
}, /* null entry to terminate the list */
{ }
}; 这两个结构都会在之后的汇编代码中使用。 mmu初始化的汇编代码位于内核的启动文件中,以arm32为例 自己对arm汇编不是很熟悉,在读汇编代码时花费了比较多的时间,希望有错误能指正出来 启动文件中与mmu相关的代码已经提取出来 在其中主要涉及到的操作为以下几个: 、重置mmu相关寄存器 、计算物理地址相对虚拟地址的偏移 、将tlb地址指向空间清零 、遍历mmu_initial_mappings结构,计算后写入tlb 、设置mmu相关寄存器 、跳转至c代码 kernel/arch/arm/arm/start.S [plain] view plain copy #include <asm.h>
#include <arch/arm/cores.h>
#include <arch/arm/mmu.h>
#include <kernel/vm.h> .section ".text.boot"
.globl _start
_start:
b platform_reset
b arm_undefined
b arm_syscall
b arm_prefetch_abort
b arm_data_abort
b arm_reserved
b arm_irq
b arm_fiq
#if WITH_SMP
b arm_reset
#endif .weak platform_reset
platform_reset:
/* Fall through for the weak symbol */ // arm复位处理程序
.globl arm_reset
arm_reset:
/* do some early cpu setup */
// 读SCTLR寄存器,手册P1711
mrc p15, , r12, c1, c0,
/* i/d cache disable, mmu disabled */
// cache位与mmu位置0
bic r12, #(<<)
bic r12, #(<< | <<)
#if WITH_KERNEL_VM
/* enable caches so atomics and spinlocks work */
// cache位与mmu位置1
orr r12, r12, #(<<)
orr r12, r12, #(<<)
#endif // WITH_KERNEL_VM
// 写SCTLR寄存器
mcr p15, , r12, c1, c0, /* calculate the physical offset from our eventual virtual location */
// 计算物理地址相对虚拟地址的偏移,用于之后的转换
.Lphys_offset:
ldr r4, =.Lphys_offset
adr r11, .Lphys_offset
sub r11, r11, r4 ... #if ARM_WITH_MMU
.Lsetup_mmu: /* set up the mmu according to mmu_initial_mappings */ /* load the base of the translation table and clear the table */
// 获取转换表地址
ldr r4, =arm_kernel_translation_table
// 获取转换表物理地址
add r4, r4, r11
/* r4 = physical address of translation table */ mov r5, #
mov r6, # /* walk through all the entries in the translation table, setting them up */
// 遍历转换表结构清零
:
str r5, [r4, r6, lsl #]
add r6, #
cmp r6, #
bne 0b /* load the address of the mmu_initial_mappings table and start processing */
// 获取初始映射地址
ldr r5, =mmu_initial_mappings
// 获取初始映射物理地址
add r5, r5, r11
/* r5 = physical address of mmu initial mapping table */ // 初始映射遍历绑定至转换表
// 转换表的绑定 转换表中元素的高12位为物理基地址下标,低20位为mmu相关flag
.Linitial_mapping_loop:
// 把结构体加载到各个通用寄存器中
ldmia r5!, { r6-r10 }
/* r6 = phys, r7 = virt, r8 = size, r9 = flags, r10 = name */ /* round size up to 1MB alignment */
// 上调size对齐1MB
ubfx r10, r6, #, #
add r8, r8, r10
add r8, r8, #( << )
sub r8, r8, # /* mask all the addresses and sizes to 1MB boundaries */
// 物理地址 虚拟地址 大小 右移20位 取高12位
lsr r6, # /* r6 = physical address / 1MB */
lsr r7, # /* r7 = virtual address / 1MB */
lsr r8, # /* r8 = size in 1MB chunks */ /* if size == 0, end of list */
// 循环边界判断
cmp r8, #
beq .Linitial_mapping_done /* set up the flags */
// 设置mmu相关flag,放置在r10
ldr r10, =MMU_KERNEL_L1_PTE_FLAGS
teq r9, #MMU_INITIAL_MAPPING_FLAG_UNCACHED
ldreq r10, =MMU_INITIAL_MAP_STRONGLY_ORDERED
beq 0f
teq r9, #MMU_INITIAL_MAPPING_FLAG_DEVICE
ldreq r10, =MMU_INITIAL_MAP_DEVICE
/* r10 = mmu entry flags */ :
// 计算translation_table元素的值
// r10:mmu相关flag r6:物理地址高12位
// r12 = r10 | (r6 << 20)
// 高20位为物理地址,低12位为mmu相关flag
orr r12, r10, r6, lsl #
/* r12 = phys addr | flags */ /* store into appropriate translation table entry */
// r4:转换表物理基地址 r7:虚拟地址对应的section
// r12 -> [r4 + r7 << 2]
str r12, [r4, r7, lsl #] /* loop until we're done */
// 准备下一个转换表元素的填充
add r6, #
add r7, #
subs r8, #
bne 0b b .Linitial_mapping_loop .Linitial_mapping_done:
... /* set up the mmu */
bl .Lmmu_setup
#endif // WITH_KERNEL_VM ...
// 跳转至c程序
bl lk_main
b . #if WITH_KERNEL_VM
/* per cpu mmu setup, shared between primary and secondary cpus
args:
r4 == translation table physical
r8 == final translation table physical (if using trampoline)
*/
// 设置mmu相关寄存器
// r4:转换表物理基地址
// mmu相关寄存器 手册P1724
.Lmmu_setup:
/* Invalidate TLB */
mov r12, #
mcr p15, , r12, c8, c7,
isb /* Write 0 to TTBCR */
// ttbcr写0
mcr p15, , r12, c2, c0,
isb /* Set cacheable attributes on translation walk */
// 宏MMU_TTBRx_FLAGS为 (1 << 3) | (1 << 6)
orr r12, r4, #MMU_TTBRx_FLAGS /* Write ttbr with phys addr of the translation table */
// 写入ttbr0
mcr p15, , r12, c2, c0,
isb /* Write DACR */
// 写DACR cache相关
mov r12, #0x1
mcr p15, , r12, c3, c0,
isb /* Read SCTLR into r12 */
// 读SCTLR寄存器,手册P1711
mrc p15, , r12, c1, c0, /* Disable TRE/AFE */
// 禁用TRE和AFE标志位
bic r12, #(<< | <<) /* Turn on the MMU */
// MMU使能标志位
orr r12, #0x1 /* Write back SCTLR */
// 写入SCTLR
// MMU打开
mcr p15, , r12, c1, c0,
isb /* Jump to virtual code address */
// 跳转
ldr pc, =1f
:
... /* Invalidate TLB */
mov r12, #
mcr p15, , r12, c8, c7,
isb /* assume lr was in physical memory, adjust it before returning */
// 计算跳转点的虚拟地址,跳转,之后会调用lk_main
sub lr, r11
bx lr
#endif ... 硬件层的内存管理相关的初始化基本完成后,会跳转到c代码 位于kernel/top/main.c 其中有关内存管理的函数调用顺序为: 、pmm_add_arena 将物理内存加入pmm结构 、vm_init_preheap 堆初始化前的准备工作(钩子) 、heap_init 堆的初始化 、vm_init_postheap 堆初始化后的工作(钩子) 、arm_mmu_init mmu相关的调整 首先要完成pmm初始化工作 pmm初始化主要分为以下几步: 、通过fdt库从bootloader中获取物理内存的长度 、在pmm中加入物理内存 、标记fdt结构的空间 、标记bootloader相关的空间 pmm中比较重要的一个结构体,pmm_arena_t代表着一块物理内存的抽象 kernel/include/kernel/vm.h [cpp] view plain copy typedef struct pmm_arena {
struct list_node node; // 节点,物理内存链表
const char* name; // 名称 uint flags;
uint priority; paddr_t base; // 物理内存基地址
size_t size; // 物理内存长度 size_t free_count; // 空闲的页数 struct vm_page* page_array; // 页结构数组
struct list_node free_list; // 节点,该内存中空闲空间的链表
} pmm_arena_t; 接着以qemu-virt的platform为例,分析pmm初始化的过程 kernel/platform/qemu-virt.c [cpp] view plain copy // 全局物理内存结构体
static pmm_arena_t arena = {
.name = "ram",
.base = MEMORY_BASE_PHYS,
.size = DEFAULT_MEMORY_SIZE,
.flags = PMM_ARENA_FLAG_KMAP,
};
...
// 该函数为平台的早期初始化,在内核启动时调用
void platform_early_init(void)
{
...
/* look for a flattened device tree just before the kernel */
// 获取fdt结构
const void *fdt = (void *)KERNEL_BASE;
int err = fdt_check_header(fdt);
if (err >= ) {
/* walk the nodes, looking for 'memory' and 'chosen' */
int depth = ;
int offset = ;
for (;;) {
offset = fdt_next_node(fdt, offset, &depth);
if (offset < )
break; /* get the name */
const char *name = fdt_get_name(fdt, offset, NULL);
if (!name)
continue; /* look for the properties we care about */
// 从fdt中查找到内存信息
if (strcmp(name, "memory") == ) {
int lenp;
const void *prop_ptr = fdt_getprop(fdt, offset, "reg", &lenp);
if (prop_ptr && lenp == 0x10) {
/* we're looking at a memory descriptor */
//uint64_t base = fdt64_to_cpu(*(uint64_t *)prop_ptr);
// 获取内存长度
uint64_t len = fdt64_to_cpu(*((const uint64_t *)prop_ptr + )); /* trim size on certain platforms */
#if ARCH_ARM
// 如果是32位arm,只使用内存前1GB
if (len > **1024U) {
len = **; /* only use the first 1GB on ARM32 */
printf("trimming memory to 1GB\n");
}
#endif
/* set the size in the pmm arena */
// 保存内存长度
arena.size = len;
}
} else if (strcmp(name, "chosen") == ) {
...
}
}
} /* add the main memory arena */
// 将改内存区域加入到pmm中
pmm_add_arena(&arena); /* reserve the first 64k of ram, which should be holding the fdt */
// 标记fdt区域
pmm_alloc_range(MEMBASE, 0x10000 / PAGE_SIZE, NULL); // 标记bootloader_ramdisk区域
platform_preserve_ramdisk();
...
} 内核在接下来初始化堆之前会在内存中构造出出一个VmAspace对象,其代表的是内核空间的抽象 kernel/kernel/vm/vm.cpp [cpp] view plain copy void vm_init_preheap(uint level) {
LTRACE_ENTRY; // allow the vmm a shot at initializing some of its data structures
// 构造代表内核空间的VmAspace对象
VmAspace::KernelAspaceInitPreHeap(); // mark all of the kernel pages in use
LTRACEF("marking all kernel pages as used\n");
// 标记内核代码所用内存
mark_pages_in_use((vaddr_t)&_start, ((uintptr_t)&_end - (uintptr_t)&_start)); // mark the physical pages used by the boot time allocator
// 标记boot time allocator代码所用内存
if (boot_alloc_end != boot_alloc_start) {
LTRACEF("marking boot alloc used from 0x%lx to 0x%lx\n", boot_alloc_start, boot_alloc_end); mark_pages_in_use(boot_alloc_start, boot_alloc_end - boot_alloc_start);
}
} kernel/kernel/vm/vm_aspace.cpp [cpp] view plain copy void VmAspace::KernelAspaceInitPreHeap() {
// the singleton kernel address space
// 构造一个内核空间单例,因为这个函数只会在启动时调用,所以是这个对象是单例
static VmAspace _kernel_aspace(KERNEL_ASPACE_BASE, KERNEL_ASPACE_SIZE, VmAspace::TYPE_KERNEL,
"kernel");
// 初始化
auto err = _kernel_aspace.Init();
ASSERT(err >= ); // save a pointer to the singleton kernel address space
// 保存单例指针
VmAspace::kernel_aspace_ = &_kernel_aspace;
} VmAspace::VmAspace(vaddr_t base, size_t size, uint32_t flags, const char* name)
: base_(base), size_(size), flags_(flags) { DEBUG_ASSERT(size != );
DEBUG_ASSERT(base + size - >= base); Rename(name); LTRACEF("%p '%s'\n", this, name_);
} status_t VmAspace::Init() {
DEBUG_ASSERT(magic_ == MAGIC); LTRACEF("%p '%s'\n", this, name_); // intialize the architectually specific part
// 标记为内核的空间
bool is_high_kernel = (flags_ & TYPE_MASK) == TYPE_KERNEL;
uint arch_aspace_flags = is_high_kernel ? ARCH_ASPACE_FLAG_KERNEL : ;
// 调用mmu相关的函数
return arch_mmu_init_aspace(&arch_aspace_, base_, size_, arch_aspace_flags);
} kernel/arch/arm/arm/mmu.c [cpp] view plain copy status_t arch_mmu_init_aspace(arch_aspace_t *aspace, vaddr_t base, size_t size, uint flags)
{
LTRACEF("aspace %p, base 0x%lx, size 0x%zx, flags 0x%x\n", aspace, base, size, flags); DEBUG_ASSERT(aspace);
DEBUG_ASSERT(aspace->magic != ARCH_ASPACE_MAGIC); /* validate that the base + size is sane and doesn't wrap */
DEBUG_ASSERT(size > PAGE_SIZE);
DEBUG_ASSERT(base + size - > base); // 初始化内核空间中页的链表
list_initialize(&aspace->pt_page_list); aspace->magic = ARCH_ASPACE_MAGIC;
if (flags & ARCH_ASPACE_FLAG_KERNEL) {
// 设置结构内相关参数,其中转换表的物理内存通过vaddr_to_paddr获取
// 该函数不详细分析了,实质就是通过转换表进行查询得到的物理地址
aspace->base = base;
aspace->size = size;
aspace->tt_virt = arm_kernel_translation_table;
aspace->tt_phys = vaddr_to_paddr(aspace->tt_virt);
} else {
...
} LTRACEF("tt_phys 0x%lx tt_virt %p\n", aspace->tt_phys, aspace->tt_virt); return NO_ERROR;
} 到此内核空间的结构初始化完成 接下来进行内核堆的初始化,Magenta内核中提供了两种堆的实现miniheap以及cmpctmalloc,用户可以自己进行配置。 堆的具体实现方法会在之后进行具体的分析 堆的初始化完成以后,会调用相应的钩子函数,该函数的主要的作用如下: 、在vmm结构中标记内核已使用的虚拟地址 、根据内核使用的地址的区域,分别设置内存的保护 [cpp] view plain copy void vm_init_postheap(uint level) {
LTRACE_ENTRY; vmm_aspace_t* aspace = vmm_get_kernel_aspace(); // we expect the kernel to be in a temporary mapping, define permanent
// regions for those now
struct temp_region {
const char* name;
vaddr_t base;
size_t size;
uint arch_mmu_flags;
} regions[] = {
{
.name = "kernel_code",
.base = (vaddr_t)&__code_start,
.size = ROUNDUP((size_t)&__code_end - (size_t)&__code_start, PAGE_SIZE),
.arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_EXECUTE,
},
{
.name = "kernel_rodata",
.base = (vaddr_t)&__rodata_start,
.size = ROUNDUP((size_t)&__rodata_end - (size_t)&__rodata_start, PAGE_SIZE),
.arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ,
},
{
.name = "kernel_data",
.base = (vaddr_t)&__data_start,
.size = ROUNDUP((size_t)&__data_end - (size_t)&__data_start, PAGE_SIZE),
.arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE,
},
{
.name = "kernel_bss",
.base = (vaddr_t)&__bss_start,
.size = ROUNDUP((size_t)&__bss_end - (size_t)&__bss_start, PAGE_SIZE),
.arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE,
},
{
.name = "kernel_bootalloc",
.base = (vaddr_t)boot_alloc_start,
.size = ROUNDUP(boot_alloc_end - boot_alloc_start, PAGE_SIZE),
.arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE,
},
}; for (uint i = ; i < countof(regions); ++i) {
temp_region* region = &regions[i];
ASSERT(IS_PAGE_ALIGNED(region->base));
status_t status = vmm_reserve_space(aspace, region->name, region->size, region->base);
ASSERT(status == NO_ERROR);
status = vmm_protect_region(aspace, region->base, region->arch_mmu_flags);
ASSERT(status == NO_ERROR);
} // mmu_initial_mappings should reflect where we are now, use it to construct the actual
// mappings. We will carve out the kernel code/data from any mappings and
// unmap any temporary ones.
const struct mmu_initial_mapping* map = mmu_initial_mappings;
for (map = mmu_initial_mappings; map->size > ; ++map) {
LTRACEF("looking at mapping %p (%s)\n", map, map->name);
// Unmap temporary mappings except where they intersect with the
// kernel code/data regions.
vaddr_t vaddr = map->virt;
LTRACEF("vaddr 0x%lx, virt + size 0x%lx\n", vaddr, map->virt + map->size);
while (vaddr != map->virt + map->size) {
vaddr_t next_kernel_region = map->virt + map->size;
vaddr_t next_kernel_region_end = map->virt + map->size; // Find the kernel code/data region with the lowest start address
// that is within this mapping.
for (uint i = ; i < countof(regions); ++i) {
temp_region* region = &regions[i]; if (region->base >= vaddr && region->base < map->virt + map->size &&
region->base < next_kernel_region) { next_kernel_region = region->base;
next_kernel_region_end = region->base + region->size;
}
} // If vaddr isn't the start of a kernel code/data region, then we should make
// a mapping between it and the next closest one.
if (next_kernel_region != vaddr) {
status_t status =
vmm_reserve_space(aspace, map->name, next_kernel_region - vaddr, vaddr);
ASSERT(status == NO_ERROR); if (map->flags & MMU_INITIAL_MAPPING_TEMPORARY) {
// If the region is part of a temporary mapping, immediately unmap it
LTRACEF("Freeing region [%016lx, %016lx)\n", vaddr, next_kernel_region);
status = vmm_free_region(aspace, vaddr);
ASSERT(status == NO_ERROR);
} else {
// Otherwise, mark it no-exec since it's not explicitly code
status = vmm_protect_region(
aspace,
vaddr,
ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE);
ASSERT(status == NO_ERROR);
}
}
vaddr = next_kernel_region_end;
}
}
} 以上代码中涉及到的几个函数,只是做下简单的介绍,不具体分析: vmm_reserve_space:在vmm中标记一块虚拟内存,这块虚拟内存抽象为VmRegion类,拥有自己的底层mmu相关的配置 vmm_protect_region:对某VmRegion对应的虚拟内存设置内存保护的相关参数 mmu相关的调整
mmu相关的调整,由内核新建的bootstrap2线程进行调用arch_init完成 kernel/arch/arm/arm/arch.c [cpp] view plain copy void arch_init(void)
{
...
#if ARM_WITH_MMU
/* finish intializing the mmu */
arm_mmu_init();
#endif
} kernel/arch/arm/arm/mmu.c [cpp] view plain copy void arm_mmu_init(void)
{
/* unmap the initial mapings that are marked temporary */
// 解除具有MMU_INITIAL_MAPPING_TEMPORARY标志的内存映射
struct mmu_initial_mapping *map = mmu_initial_mappings;
while (map->size > ) {
if (map->flags & MMU_INITIAL_MAPPING_TEMPORARY) {
vaddr_t va = map->virt;
size_t size = map->size; DEBUG_ASSERT(IS_SECTION_ALIGNED(size)); while (size > ) {
arm_mmu_unmap_l1_entry(arm_kernel_translation_table, va / SECTION_SIZE);
va += MB;
size -= MB;
}
}
map++;
}
arm_after_invalidate_tlb_barrier(); #if KERNEL_ASPACE_BASE != 0
/* bounce the ttbr over to ttbr1 and leave 0 unmapped */
// 重新设置mmu相关的寄存器,禁用ttbcr0,将原先ttbr0的映射移动到ttbr1
// ttbr1为内核空间使用的寄存器
uint32_t n = __builtin_clz(KERNEL_ASPACE_BASE) + ;
DEBUG_ASSERT(n <= ); uint32_t ttbcr = (<<) | n; /* disable TTBCR0 and set the split between TTBR0 and TTBR1 */ arm_write_ttbr1(arm_read_ttbr0());
ISB;
arm_write_ttbcr(ttbcr);
ISB;
arm_write_ttbr0();
ISB;
#endif
} 至此Magenta内核有关内存管理的初始化完成。

Magenta源代码笔记(3) —— 内存管理【转】的更多相关文章

  1. linux kernel学习笔记-5内存管理_转

    void * kmalloc(size_t size, gfp_t gfp_mask); kmalloc()第一个参数是要分配的块的大小,第一个参数为分配标志,用于控制kmalloc()的行为. km ...

  2. XV6学习笔记(2) :内存管理

    XV6学习笔记(2) :内存管理 在学习笔记1中,完成了对于pc启动和加载的过程.目前已经可以开始在c语言代码中运行了,而当前已经开启了分页模式,不过是两个4mb的大的内存页,而没有开启小的内存页.接 ...

  3. redis 源代码分析(一) 内存管理

    一,redis内存管理介绍 redis是一个基于内存的key-value的数据库,其内存管理是很重要的,为了屏蔽不同平台之间的差异,以及统计内存占用量等,redis对内存分配函数进行了一层封装,程序中 ...

  4. redis源代码解读之内存管理————zmalloc文件

    本文章主要记录本人在看redis源代码的一些理解和想法.由于功力有限,肯定会出现故障,所以.希望高手给出指正. 第一篇就是内存相关的介绍.由于我喜欢先看一些组件的东西,再看总体的流程. 先上一下代码吧 ...

  5. Cocos2D-X2.2.3学习笔记3(内存管理)

    本章节介绍例如以下: 1.C/C++内存管理机制 2.引用计数机制 3.自己主动释放机制 1.C/C++内存管理机制 相信仅仅要懂oop的都知道NEW这个keyword吧,这个通俗点说事实上就是创建对 ...

  6. Linux内核设计笔记12——内存管理

    内存管理学习笔记 页 页是内核管理内存的基本单位,内存管理单元(MMU,管理内存并把虚拟地址转化为物理地址的硬件)通常以页为单位进行处理,从虚拟内存的角度看,页就是最小单位. struct page{ ...

  7. COCOS学习笔记--Cocod2dx内存管理(三)-Coco2d-x内存执行原理

    通过上两篇博客.我们对Cocos引用计数和Ref类.PoolManager类以及AutoreleasePool类已有所了解,那么接下来就通过举栗子来进一步看看Coco2d-x内存执行原理是如何的. / ...

  8. cocos2d-x 源代码分析 : Ref (CCObject) 源代码分析 cocos2d-x内存管理策略

    从源代码版本号3.x.转载请注明 cocos2d-x 总的文件夹的源代码分析: http://blog.csdn.net/u011225840/article/details/31743129 1.R ...

  9. 《代码的未来》读书笔记:内存管理与GC那点事儿

    一.内存是有限的 近年来,我们的电脑内存都有好几个GB,也许你的电脑是4G,他的电脑是8G,公司服务器内存是32G或者64G.但是,无论内存容量有多大,总归不是无限的.实际上,随着内存容量的增加,软件 ...

随机推荐

  1. crontab -e 和/etc/crontab的区别

    /etc/crontab文件和crontab -e命令区别/etc/crontab文件和crontab -e命令区别 1.格式不同 前者 # For details see man 4 crontab ...

  2. 简述在php中 = 、==、 === 的区别(简述在php中 等于 、双等于、 三等于 的区别)

    = 是赋值:就是说给一个变量赋值 == 是轻量级的比较运算,只看值不看类型 === 是重量级的比较运算,既看值,也看类型,要绝对相等才会为true

  3. Swiper.js手动滑动之后,不再自动滑动问题

    var swiper = new Swiper('.swiper-container', {        pagination: '.swiper-pagination',        autop ...

  4. Lake Counting(dfs)

    Description Due to recent rains, water has pooled in various places in Farmer John's field, which is ...

  5. django实现事务

    1.导入模块 from django.db import transaction 2.使用方法 with transaction.atomic(): User.objects.create(name= ...

  6. Jack Straws POJ - 1127 (简单几何计算 + 并查集)

    In the game of Jack Straws, a number of plastic or wooden "straws" are dumped on the table ...

  7. input框中的必填项之取消当前input框为必填项

    html5新增了一个required属性,可以使用这个属性对文本框设置必填项,直接在input文本框上添加required即可 . 效果如图:   

  8. TCP/IP网络编程之网络编程和套接字

    网络编程和套接字 网络编程又称为套接字编程,就是编写一段程序,使得两台连网的计算机彼此之间可以交换数据.那么,这两台计算机用什么传输数据呢?首先,需要物理连接,将一台台独立的计算机通过物理线路连接在一 ...

  9. laravel5.2总结--邮件

    laravel自带SwiftMailer库,集成了多种邮件API,支持多种邮件驱动方式,包括smtp.Mailgun.Maildrill.Amazon SES.mail和sendmail,Mailgu ...

  10. 怎么使用瓦特平台下面的“代码工厂”快速生成BS程序代码

    这里说一下怎么使用瓦特平台下面的“代码工厂”快速生成程序代码 使用平台:windows+"visual studio 2010"+"SqlServer2000+" ...