一、mmap原理

在虚拟内存空间那一节,我们知道,每一个进程都有一个列表vm_area_struct,指向虚拟地址空间的不同内存块,这个变量名字叫mmap

struct mm_struct {
struct vm_area_struct *mmap; /* list of VMAs */
......
} struct vm_area_struct {
/*
* For areas with an address space and backing store,
* linkage into the address_space->i_mmap interval tree.
*/
struct {
struct rb_node rb;
unsigned long rb_subtree_last;
} shared; /*
* A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
* list, after a COW of one of the file pages. A MAP_SHARED vma
* can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack
* or brk vma (with NULL file) can only be in an anon_vma list.
*/
struct list_head anon_vma_chain; /* Serialized by mmap_sem &
* page_table_lock */
struct anon_vma *anon_vma; /* Serialized by page_table_lock */ /* Function pointers to deal with this struct. */
const struct vm_operations_struct *vm_ops;
/* Information about our backing store: */
unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE
units */
struct file * vm_file; /* File we map to (can be NULL). */
void * vm_private_data; /* was vm_pte (shared mem) */

其实内存映射不仅仅是物理内存和虚拟内存之间的映射,还包括将文件中的内容映射到虚拟内存空间,这个时候访问内存空间就能够访问到文件里面的数据。

而仅有物理内存和虚拟内存的映射是一种特殊情况

1、mmap系统调用

1、如何分配一大块内存

如果申请一大块内存就用mmap,mmap是映射内存空间到物理内存

另外,如果一个进程想映射一个文件到自己的虚拟内存空间,也要通过mmap系统调用这个时候mmap是映射内存空间到物理内存再到文件。可见mmap这个系统调用时核心,

2、我们现在来看mmap这个系统调用

SYSCALL_DEFINE6(mmap, unsigned long, addr, unsigned long, len,
unsigned long, prot, unsigned long, flags,
unsigned long, fd, unsigned long, off)
{
......
error = sys_mmap_pgoff(addr, len, prot, flags, fd, off >> PAGE_SHIFT);
......
} SYSCALL_DEFINE6(mmap_pgoff, unsigned long, addr, unsigned long, len,
unsigned long, prot, unsigned long, flags,
unsigned long, fd, unsigned long, pgoff)
{
struct file *file = NULL;
......
file = fget(fd);
......
retval = vm_mmap_pgoff(file, addr, len, prot, flags, pgoff);
return retval;
}

如果映射到文件,fd会传进来一个文件描述符,并且mmap_pgoff里面通过fget函数,根据文件描述符获得struct file、struct file表示打开一个文件

接下来的调用链是:

这里主要干了两件事情

1、调用 get_unmapped_area 找到一个没有映射的区域

2、调用 mmap_region 映射这个区域。

3、我们先来看 get_unmapped_area 函数。

unsigned long
get_unmapped_area(struct file *file, unsigned long addr, unsigned long len,
unsigned long pgoff, unsigned long flags)
{
unsigned long (*get_area)(struct file *, unsigned long,
unsigned long, unsigned long, unsigned long);
......
get_area = current->mm->get_unmapped_area;
if (file) {
if (file->f_op->get_unmapped_area)
get_area = file->f_op->get_unmapped_area;
}
......
}

const struct file_operations ext4_file_operations = {
......
.mmap = ext4_file_mmap
.get_unmapped_area = thp_get_unmapped_area,
}; unsigned long __thp_get_unmapped_area(struct file *filp, unsigned long len,
loff_t off, unsigned long flags, unsigned long size)
{
unsigned long addr;
loff_t off_end = off + len;
loff_t off_align = round_up(off, size);
unsigned long len_pad;
len_pad = len + size;
......
addr = current->mm->get_unmapped_area(filp, 0, len_pad,
off >> PAGE_SHIFT, flags);
addr += (off - addr) & (size - 1);
return addr;
}

 4、我们再来看 mmap_region 函数,看它如何映射这个虚拟内存区域

unsigned long mmap_region(struct file *file, unsigned long addr,
unsigned long len, vm_flags_t vm_flags, unsigned long pgoff,
struct list_head *uf)
{
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma, *prev;
struct rb_node **rb_link, *rb_parent; /*
* Can we just expand an old mapping?
*/
vma = vma_merge(mm, prev, addr, addr + len, vm_flags,
NULL, file, pgoff, NULL, NULL_VM_UFFD_CTX);
if (vma)
goto out; /*
* Determine the object being mapped and call the appropriate
* specific mapper. the address has already been validated, but
* not unmapped, but the maps are removed from the list.
*/
vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
if (!vma) {
error = -ENOMEM;
goto unacct_error;
} vma->vm_mm = mm;
vma->vm_start = addr;
vma->vm_end = addr + len;
vma->vm_flags = vm_flags;
vma->vm_page_prot = vm_get_page_prot(vm_flags);
vma->vm_pgoff = pgoff;
INIT_LIST_HEAD(&vma->anon_vma_chain); if (file) {
vma->vm_file = get_file(file);
error = call_mmap(file, vma);
addr = vma->vm_start;
vm_flags = vma->vm_flags;
}
......
vma_link(mm, vma, prev, rb_link, rb_parent);
return addr;
.....

1、还记得咱们刚找到了虚拟内存区域的前一个 vm_area_struct,我们首先要看,是否能够基于它进行扩展,也即调用 vma_merge,和前一个 vm_area_struct 合并到一起。

2、如果不能,就需要调用 kmem_cache_zalloc,在 Slub 里面创建一个新的 vm_area_struct对象,设置起始和结束位置,将它加入队列。如果是映射到文件,则设置 vm_file 为目标文件,

  调用 call_mmap。其实就是调用 file_operations 的 mmap 函数

3、对于 ext4 文件系统,调用的是 ext4_file_mmap。从这个函数的参数可以看出,这一刻文件和内存开始发生关系了。这里我们将vm_area_struct 的内存操作设置为文件系统操作,也就是说,

  读写内存其实就是读写文件系统。

static inline int call_mmap(struct file *file, struct vm_area_struct *vma)
{
return file->f_op->mmap(file, vma);
} static int ext4_file_mmap(struct file *file, struct vm_area_struct *vma)
{
......
vma->vm_ops = &ext4_file_vm_ops;
......
}

5、我们再回到 mmap_region 函数。

最终,vma_link 函数将新创建的 vm_area_struct 挂在了 mm_struct 里面的红黑树上。

这个时候,从内存到文件的映射关系,至少要在逻辑层面建立起来。那从文件到内存的映射关系呢?vma_link 还做了另外一件事情,就是 __vma_link_file。这个东西要用于建立这层映射关系。

对于打开的文件,会有一个结构 struct file 来表示。它有个成员指向 struct address_space 结构,这里面有棵变量名为 i_mmap 的红黑树,vm_area_struct 就挂在这棵树上。

struct address_space {
struct inode *host; /* owner: inode, block_device */
......
struct rb_root i_mmap; /* tree of private and shared mappings */
......
const struct address_space_operations *a_ops; /* methods */
......
} static void __vma_link_file(struct vm_area_struct *vma)
{
struct file *file; file = vma->vm_file;
if (file) {
struct address_space *mapping = file->f_mapping;
vma_interval_tree_insert(vma, &mapping->i_mmap);
}

到这里,内存映射的内容要告一段落,你可能会困惑,好像还没有和物理内存法神过任何关系、还是在虚拟内存里面折腾呀?对的,因为到目前为止,我们还没有开始真正访问内存呀!

这个时候,内存管理并不直接分配物理内存,因为物理内存相对于虚拟地址空间太宝贵了,只要等你真正用的那一刻才会开始分配

二、用户态缺页异常

一旦开始访问虚拟内存的某个地址,如果我们发现,并没有对应的物理页,那就出发缺页中断,调用do_page_fault

dotraplinkage void notrace
do_page_fault(struct pt_regs *regs, unsigned long error_code)
{
unsigned long address = read_cr2(); /* Get the faulting address */
......
__do_page_fault(regs, error_code, address);
......
} /*
* This routine handles page faults. It determines the address,
* and the problem, and then passes it off to one of the appropriate
* routines.
*/
static noinline void
__do_page_fault(struct pt_regs *regs, unsigned long error_code,
unsigned long address)
{
struct vm_area_struct *vma;
struct task_struct *tsk;
struct mm_struct *mm;
tsk = current;
mm = tsk->mm; if (unlikely(fault_in_kernel_space(address))) {
if (vmalloc_fault(address) >= 0)
return;
}
......
vma = find_vma(mm, address);
......
fault = handle_mm_fault(vma, address, flags);
......

1、在do_page_fault里面,先要判断缺页中断是否发生在内核,如果发生在内核则调用vmalloc_fault,这就是和咱们前面学过的虚拟内存的布局对应上了

2、在内核里面,vmalloc区域需要内核页表映射到物理页,咱们这里把内核的这部分放放,接着看用户空间的部分

3、接下来在用户空间里面,找到你访问的那个地址所在的区域 vm_area_struct,然后调用 handle_mm_fault 来映射这个区域。

static int __handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
unsigned int flags)
{
struct vm_fault vmf = {
.vma = vma,
.address = address & PAGE_MASK,
.flags = flags,
.pgoff = linear_page_index(vma, address),
.gfp_mask = __get_fault_gfp_mask(vma),
};
struct mm_struct *mm = vma->vm_mm;
pgd_t *pgd;
p4d_t *p4d;
int ret; pgd = pgd_offset(mm, address);
p4d = p4d_alloc(mm, pgd, address);
......
vmf.pud = pud_alloc(mm, p4d, address);
......
vmf.pmd = pmd_alloc(mm, vmf.pud, address);
......
return handle_pte_fault(&vmf);
}

到这里,终于看到了我们熟悉的 PGD、P4G、PUD、PMD、PTE,这就是前面讲页表的时候,讲述的四级页表的概念,因为暂且不考虑五级页表,我们暂时忽略P4G

1、pgd_t 用于全局页目录项,pud_t 用于上层页目录项,pmd_t 用于中间页目录项,pte_t 用于直接页表项。

2、每个进程都有独立的地址空间,为了这个进程独立完成映射,每个进程都有独立的进程页表,这个页表的最顶级的 pgd 存放在 task_struct 中的 mm_struct 的 pgd变量里面

3、在一个进程新创建的时候,会调用 fork,对于内存的部分会调用 copy_mm,里面调用 dup_mm

/*
* Allocate a new mm structure and copy contents from the
* mm structure of the passed in task structure.
*/
static struct mm_struct *dup_mm(struct task_struct *tsk)
{
struct mm_struct *mm, *oldmm = current->mm;
mm = allocate_mm();
memcpy(mm, oldmm, sizeof(*mm));
if (!mm_init(mm, tsk, mm->user_ns))
goto fail_nomem;
err = dup_mmap(mm, oldmm);
return mm;
}

在这里,除了创建一个新的 mm_struct,并且通过 memcpy 将它和父进程的弄成一模一样之外,我们还需要调用 mm_init 进行初始化。接下来,

mm_init 调用 mm_alloc_pgd,分配全局、页目录项,赋值给 mm_struct 的 pdg 成员变量。

static inline int mm_alloc_pgd(struct mm_struct *mm)
{
mm->pgd = pgd_alloc(mm);
return 0;
}

pgd_alloc 里面除了分配 PDG 之外,还做了很重要的一个事情,就是调用 pgd_ctor

static void pgd_ctor(struct mm_struct *mm, pgd_t *pgd)
{
/* If the pgd points to a shared pagetable level (either the
ptes in non-PAE, or shared PMD in PAE), then just copy the
references from swapper_pg_dir. */
if (CONFIG_PGTABLE_LEVELS == 2 ||
(CONFIG_PGTABLE_LEVELS == 3 && SHARED_KERNEL_PMD) ||
CONFIG_PGTABLE_LEVELS >= 4) {
clone_pgd_range(pgd + KERNEL_PGD_BOUNDARY,
swapper_pg_dir + KERNEL_PGD_BOUNDARY,
KERNEL_PGD_PTRS);
}
......
}

1、pgd_ctor干了什么

2、load_new_mm_cr3 为什么要将虚拟地址转换为物理地址?

因为cr3存放的是物理地址,只有将虚拟地址转换为物理地址才能加载到 cr3 里面去

3、load_new_mm_cr3将虚拟地址转换为虚拟地址的调用链

4、地址转换的过程无需进入内核态

5、触发缺页异常调用链

只有访问内存的时候发现没有映射多物理内存,页表也没有创建过,才触发缺页异常

绕了一大圈,终于将页表整个机制的各个部分串了起来。但是咱们的故事还没讲完,物理的内存、还没找到。我们还得接着分析 handle_pte_fault 的实现。

static int handle_pte_fault(struct vm_fault *vmf)
{
pte_t entry;
......
vmf->pte = pte_offset_map(vmf->pmd, vmf->address);
vmf->orig_pte = *vmf->pte;
......
if (!vmf->pte) {
if (vma_is_anonymous(vmf->vma))
return do_anonymous_page(vmf);
else
return do_fault(vmf);
} if (!pte_present(vmf->orig_pte))
return do_swap_page(vmf);
......
}

匿名页调用

这个函数你还记得吗?就是咱们伙伴系统的核心函数,专门用来分配物理页面的。do_anonymous_page 接下来要调用 mk_pte,将页表项指向新分配的物理页,set_pte_at 会将页表项塞到页表里面。

static int do_anonymous_page(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
struct mem_cgroup *memcg;
struct page *page;
int ret = 0;
pte_t entry;
......
if (pte_alloc(vma->vm_mm, vmf->pmd, vmf->address))
return VM_FAULT_OOM;
......
page = alloc_zeroed_user_highpage_movable(vma, vmf->address);
......
entry = mk_pte(page, vma->vm_page_prot);
if (vma->vm_flags & VM_WRITE)
entry = pte_mkwrite(pte_mkdirty(entry)); vmf->pte = pte_offset_map_lock(vma->vm_mm, vmf->pmd, vmf->address,
&vmf->ptl);
......
set_pte_at(vma->vm_mm, vmf->address, vmf->pte, entry);
......
}

第二种情况映射到文件 do_fault,最终我们会调用 __do_fault

int swap_readpage(struct page *page, bool do_poll)
{
struct bio *bio;
int ret = 0;
struct swap_info_struct *sis = page_swap_info(page);
blk_qc_t qc;
struct block_device *bdev;
......
if (sis->flags & SWP_FILE) {
struct file *swap_file = sis->swap_file;
struct address_space *mapping = swap_file->f_mapping;
ret = mapping->a_ops->readpage(swap_file, page);
return ret;
}
......
}

这里调用了struct vm_operations_struct vm_ops的fault函数,还记得咱们上面用mmap映射文件的时候,对于ext4文件系统,vm_ops指向了ext4_file_vm_ops也就是调用了函数ext4_filemap_fault

static const struct vm_operations_struct ext4_file_vm_ops = {
.fault = ext4_filemap_fault,
.map_pages = filemap_map_pages,
.page_mkwrite = ext4_page_mkwrite,
}; int ext4_filemap_fault(struct vm_fault *vmf)
{
struct inode *inode = file_inode(vmf->vma->vm_file);
......
err = filemap_fault(vmf);
......
return err;
}

ext4_filemap_fault里面的逻辑我们很容易就能读懂,vm_file就是咱们当时mmap的时候映射的那个文件,然后我们需要调用filemap_fault

对于文件映射来说,一般这个文件会在物理内存里面有页面作为它的缓存,find_get_page就是找那个页,如果找到了,就调用,预读一些数据到内存里面;如果没有,就跳到no_cached_page

int filemap_fault(struct vm_fault *vmf)
{
int error;
struct file *file = vmf->vma->vm_file;
struct address_space *mapping = file->f_mapping;
struct inode *inode = mapping->host;
pgoff_t offset = vmf->pgoff;
struct page *page;
int ret = 0;
......
page = find_get_page(mapping, offset);
if (likely(page) && !(vmf->flags & FAULT_FLAG_TRIED)) {
do_async_mmap_readahead(vmf->vma, ra, file, page, offset);
} else if (!page) {
goto no_cached_page;
}
......
vmf->page = page;
return ret | VM_FAULT_LOCKED;
no_cached_page:
error = page_cache_read(file, offset, vmf->gfp_mask);
......
}

1、如果没有物理内存中的缓存页

2、那我们就调用 page_cach—_read

3、在这里显示分配一个缓存页

4、将这一页加到 lru 表里面

5、然后在 address_space 中调用 aaddress_space_operations 的readpage 函数,将文件内容读到内存中。address_space 的作用咱们上面也介绍过了。

static int page_cache_read(struct file *file, pgoff_t offset, gfp_t gfp_mask)
{
struct address_space *mapping = file->f_mapping;
struct page *page;
......
page = __page_cache_alloc(gfp_mask|__GFP_COLD);
......
ret = add_to_page_cache_lru(page, mapping, offset, gfp_mask & GFP_KERNEL);
......
ret = mapping->a_ops->readpage(file, page);
......
}

struct address_space_operations对于 ext4 文件系统的定义如下所示。这么说来,

上面的 readpage 调用的其实是 ext4_readage。因为我们还没讲到文件系统,这里我们不详细介绍

ext4_readpage 具体干了什么。你只要知道,最后会调用 ext4_read_inline_page,这里面有部分逻辑和内存映射有关就行了。

static const struct address_space_operations ext4_aops = {
.readpage = ext4_readpage,
.readpages = ext4_readpages,
......
}; static int ext4_read_inline_page(struct inode *inode, struct page *page)
{
void *kaddr;
......
kaddr = kmap_atomic(page);
ret = ext4_read_inline_data(inode, kaddr, len, &iloc);
flush_dcache_page(page);
kunmap_atomic(kaddr);
......
}

1、为什么要在内核里面映射一把?

1、在 ext4_read_inline_page 函数里,我们需要先调用 kmap_atomic,将物理内存映射到内核的虚拟地址空间,得到内核中的地址kaddr

2、kaddr它是用来做临时内核映射的。本来把物理内存映射到用户虚拟地址空间,不需要在内核里面映射一把。

但是,现在因为要从文件里面读取数据并写入这个物理页面,又不能使用物理地址,

我们只能使用虚拟地址,这就需要在内核里面临时映射一把。临时映射后,ext4_read_inline_data 读取文件到这个虚拟地址。读取完毕后,我们取消这个临时映射 kunmap_atomic 就行了。

我们再来看第三种情况,do_swap_page。之前我们讲过物理内存管理,你这里可以回忆一下。如果长时间不用,就要换出到硬盘,

也就是 swap,现在这部分数据又要访问了,我们还得想办法再读到内存中来。

int do_swap_page(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
struct page *page, *swapcache;
struct mem_cgroup *memcg;
swp_entry_t entry;
pte_t pte;
......
entry = pte_to_swp_entry(vmf->orig_pte);
......
page = lookup_swap_cache(entry);
if (!page) {
page = swapin_readahead(entry, GFP_HIGHUSER_MOVABLE, vma,
vmf->address);
......
}
......
swapcache = page;
......
pte = mk_pte(page, vma->vm_page_prot);
......
set_pte_at(vma->vm_mm, vmf->address, vmf->pte, pte);
vmf->orig_pte = pte;
......
swap_free(entry);
......
}

1、do_swap_page函数会先查找 swap 文件有没有缓存页。

2、如果没有,就调用swapin_readahead,将 swap 文件读到内存中来,形成内存页,并通过 mk_pte 生成页表项。

3、set_pte_at 将页表项插入页表,将 swap 文件清理。因为重新加载回内存了,不再需要 swap 文件了。

4、swapin_readahead 会最终调用 swap_readpage,在这里,我们看到了熟悉的readpage 函数,也就是说读取普通文件和读取 swap 文件,

过程是一样的,同样需要用 kmap_atomickmap_atomic 做临时映射。

int swap_readpage(struct page *page, bool do_poll)
{
struct bio *bio;
int ret = 0;
struct swap_info_struct *sis = page_swap_info(page);
blk_qc_t qc;
struct block_device *bdev;
......
if (sis->flags & SWP_FILE) {
struct file *swap_file = sis->swap_file;
struct address_space *mapping = swap_file->f_mapping;
ret = mapping->a_ops->readpage(swap_file, page);
return ret;
}
......
}

通过上面复杂的过程,用户缺页异常处理完毕了,物理内存中有了页面,页表也建立好了映射,接下来用户程序在虚拟内存空间里面,可以通过虚拟地址顺利经过页表映射的访问物理页面上的数据了

为了加快映射速度,我们不需要每次从虚拟地址到物理地址都转换走一遍页表

1、页表一般都很大,只能存放在内存中,操作系统每次访问内存要折腾两步

1、先通过查询页表得到物理地址

2、然后访问该物理地址读取指令、数据

2、TLB 页表的 Cache

为了提高映射速度,我们引入了TLB(Translation Lookaside Buffer)我们经常称为快表,专门用来做地址映射的硬件设备。

它不在内存中、可存储的数据比较少,但是比内存要快。所以,我们可以想象,TLB 就是页表的 Cache,其中存储了当前最可能被访问到的页表项,其内容是部分页表项的一个副本。

3、有了 TLB 之后,地址映射的过程就像图中画的

1、我们先查块表,块表中有映射关系,然后直接转换为物理地址。
2、如果在 TLB 查不到映射关系时,才会到内存中查询页表。

总结时刻

用户态的内存映射机制,我们解析的差不多了,我们来总结一下,用户态的内存映射机制包含以下几个部分
用户态内存映射函数 mmap,包括用它来做匿名映射和文件映射...
用户态的页表结构,存储位置在 mm_struct 中。
在用户态访问没有映射的内存会引发缺页异常,分配物理页表,补齐页表。如果是匿名映射则
分配物理内存;如果是 swap,则将 swap 文件读入;如果是文件映射,则将文件读入

趣谈Linux操作系统学习笔记:第二十五讲的更多相关文章

  1. 深挖计算机基础:趣谈Linux操作系统学习笔记

    参考极客时间专栏<趣谈Linux操作系统>学习笔记 核心原理篇:内存管理 趣谈Linux操作系统学习笔记:第二十讲 趣谈Linux操作系统学习笔记:第二十一讲 趣谈Linux操作系统学习笔 ...

  2. 趣谈Linux操作系统学习笔记:第二十讲

    一.引子 1.计算两方面的原因 2.内存管理机制 二.独享内存空间的原理 1.会议室和物理内存的关系 和会议室一样,内存都被分成一块块儿的,都编号了号,例如3F-10就是三楼十号会议室.内存页有这样一 ...

  3. 趣谈Linux操作系统学习笔记:第二十四讲

    一.小内存的分配基础 1.kmem_cache_alloc_node的作用 通过这段代码可以看出,它调用了kmem_cache_alloc_node函数,在task_struct的缓存区域task_s ...

  4. 趣谈Linux操作系统学习笔记:第二十六讲

    一.内核页表 和用户态页表不同,在系统初始化的时候,我们就要创建内核页表了 我们从内核页表的根swapper_pg_dir开始找线索,在linux-5.1.3/arch/x86/include/asm ...

  5. 趣谈Linux操作系统学习笔记:第二十八讲

    一.引子 磁盘→盘片→磁道→扇区(每个 512 字节) ext* 定义文件系统的格式 二.inode 与块的存储 1.块 2.不用给他分配一块连续的空间 我们可以分散成一个个小块进行存放 1.优点 2 ...

  6. 趣谈Linux操作系统学习笔记:第二十九讲

    一.引子 在这之前,有一点你需要注意.解析系统调用是了解内核架构最有力力的一把钥匙,这里我们只要重点关注这几个最重要的系统调用就可以了 1.mount 系统调用用于挂载文件系统:2.open 系统调用 ...

  7. 趣谈Linux操作系统学习笔记:第二十七讲

    一.文件系统的功能规划 1.引子 咱们花了这么长的时间,规划了会议室管理系统,这样多个项目执行的时候,隔离性可以得到保证. 但是,会议室里面被回收,会议室里面的资料就丢失了.有一些资料我们希望项目结束 ...

  8. 趣谈Linux操作系统学习笔记:第二十一讲

    一.分段机制 1.分段机制的原理图 2.段选择子 3.段偏移量 例如,我们将上面的虚拟空间分成以下 4 个段,用 0-3 来编号.每个段在段表中有一个项,在物理空间中,段的排列如下图的右边所示. 4. ...

  9. 《趣谈 Linux 操作系统》学习笔记(一):为什么要学 Linux 及学习路径

    前言:学习的课程来自极客时间的专栏<趣谈 Linux 操作系统>,作者用形象化的比喻和丰富的图片让课程变得比较易懂,为了避免知识看过就忘,打算通过写学习笔记的形式记录自己的学习过程. Li ...

随机推荐

  1. JS PopupAlert

    JS PopupAlert 可以在 JavaScript 中创建三种消息框:警告框.确认框.提示框. 警告框 警告框经常用于确保用户可以得到某些信息. 当警告框出现后,用户需要点击确定按钮才能继续进行 ...

  2. 从0系统学Android--3.7 聊天界面编写

    从0系统学Android--3.7 聊天界面编写 本系列文章目录:更多精品文章分类 本系列持续更新中.... 3.7 编写界面的最佳实践 前面学习了那么多 UI 开发的知识,下面来进行实践,做一个美观 ...

  3. 12.2 中的Data Guard Standby 密码文件自动同步 (Doc ID 2307365.1)

    Data Guard Standby Automatic Password file Synchronization in 12.2 (Doc ID 2307365.1) APPLIES TO: Or ...

  4. 表单生成器(Form Builder)之mongodb表单数据查询——统计查询求和

    上一篇笔记仅是记录了一下简单的关联查询,根据笔记中的场景:将某一车辆关联的耗损记录全部放在了一个字段当中.不知道现在中有没有这种场景,我们的应用中没有类似的场景,可能我们更关注的是某车辆的总耗损金额和 ...

  5. Centos7 基于SVN+Apache+IF.svnadmin实现web管理

    1.简单介绍: iF.SVNAdmin应用程序是您的Subversion授权文件的基于Web的GUI.它基于PHP 5.3,需要安装一个Web服务器(Apache).该应用程序不需要数据库后端或任何类 ...

  6. python爬取网页数据

    一.利用webbrowser.open()打开一个网站: ? 1 2 3 >>> import webbrowser >>> webbrowser.open('ht ...

  7. 利用Python进行数据分析-Pandas(第四部分-数据清洗和准备)

    在数据分析和建模的过程中,相当多的时间要用在数据准备上:加载.清理.转换以及重塑上.这些工作会占到分析时间的80%或更多.有时,存储在文件和数据库中的数据的格式不适合某个特定的任务.研究者都选择使用编 ...

  8. java获取当前年份、月份和日期字符串等

    Java获取当前年份.月份和日期是通过Calendar类的实例对象来获取的. 首先创建一个Calendar类的实例对象,Calendar类属于java.util包. Calendar calendar ...

  9. Java描述设计模式(06):建造者模式

    本文源码:GitHub·点这里 || GitEE·点这里 一.生活场景 基于建造者模式,描述软件开发的流程. 1.代码实现 /** * 基于建造者模式描述软件开发 */ public class C0 ...

  10. MVC邮箱验证

    post请求 [HttpPost] public void Email(Models.Email m,string Txt) { if (Txt!= Session["yzm"]. ...