内核管理-之进程虚拟内存-基于linux3.10

2023-11-09

关于启动过程内存管理见《内存管理-之启动

关于内核空间内存管理见《内存管理-之内核内存管理

如果需要,内存管理五章整理成pdf了,下载地址http://download.csdn.net/detail/shichaog/8662135

进程的虚拟地址空间和内核的虚拟地址管理方法不一样,不论应用程序如何切换,内核始终是一个并且其一直驻留在内存中,而进程则不同,可以有多个进程同时驻留在内存中,并且从各个进程的角度来看,呈现的系统是一样的,并且它们并不会彼此干扰。

有一篇文章,《linux应用程序如何运行》分析的是应用程序调用execve()执行系统调用时发生的一些事,该文章有助于理解本章内容,图5.1的右下角给出了execve的主要功能。

 5.1 进程准备知识

各进程的虚拟地址空间从0开始,最大到TASK_SIZE-1,这一范围就是通常所述的3G,从3G~4G-1是内核的地址空间,一个进程通常包括如下几个部分:

l  二进制可执行代码,比如hello.c编译得到的可执行代码,内核中将其称之为text。

l  程序使用的动态库,.so结尾的文件,该库是对系统调用的封装。

l  堆,存储全局和动态产生的数据。

l  栈,局部变量以及函数调用时对寄存器和返回地址的保存。

l  环境变量和命名参数

l  将文件内容映射到虚拟地址空间。

来看一个小例子:

hello.c
#include <stdio.h>
#include <unistd.h>

void main(void)
{
        printf("\nHello, world ~!\n");
        sleep(15);
        printf("Goodby, world ~!\n");
}

编译

gcc –o hello hello.c

执行及输出结果

ge@u:~$ ./hello &
[1] 7931
ge@u:~$ 
Hello, world ~!
Goodby, world ~!
[1]+  Exit 17                 ./hello
ge@u:~$

进程的内存使用情况,也可以使用pmapPID命令查看

ge@u:~$ cat /proc/7931/maps
08048000-08049000 r-xp 00000000 08:01 1447992    /home/ge/hello
08049000-0804a000 r--p 00000000 08:01 1447992    /home/ge/hello
0804a000-0804b000 rw-p 00001000 08:01 1447992    /home/ge/hello
b7545000-b7546000 rw-p 00000000 00:00 0 
b7546000-b76ef000 r-xp 00000000 08:01 411089     /lib/i386-linux-gnu/libc-2.19.so
b76ef000-b76f0000 ---p 001a9000 08:01 411089     /lib/i386-linux-gnu/libc-2.19.so
b76f0000-b76f2000 r--p 001a9000 08:01 411089     /lib/i386-linux-gnu/libc-2.19.so
b76f2000-b76f3000 rw-p 001ab000 08:01 411089     /lib/i386-linux-gnu/libc-2.19.so
b76f3000-b76f6000 rw-p 00000000 00:00 0 
b770c000-b770f000 rw-p 00000000 00:00 0 
b770f000-b7710000 r-xp 00000000 00:00 0          [vdso]
b7710000-b7730000 r-xp 00000000 08:01 411098     /lib/i386-linux-gnu/ld-2.19.so
b7730000-b7731000 r--p 0001f000 08:01 411098     /lib/i386-linux-gnu/ld-2.19.so
b7731000-b7732000 rw-p 00020000 08:01 411098     /lib/i386-linux-gnu/ld-2.19.so
bf8e7000-bf908000 rw-p 00000000 00:00 0          [stack]

每个应用程序的起始地址都是一样的0x08048000(IA-32架构),如下查看cat进程的内存布局。

ge@u:~$ cat /proc/self/maps 
08048000-08053000 r-xp 00000000 08:01 1966103    /bin/cat
08053000-08054000 r--p 0000a000 08:01 1966103    /bin/cat
08054000-08055000 rw-p 0000b000 08:01 1966103    /bin/cat
089c0000-089e1000 rw-p 00000000 00:00 0          [heap]
b7223000-b7388000 r--p 001c8000 08:01 1188875    /usr/lib/locale/locale-archive
b7388000-b7588000 r--p 00000000 08:01 1188875    /usr/lib/locale/locale-archive
b7588000-b7589000 rw-p 00000000 00:00 0 
b7589000-b7732000 r-xp 00000000 08:01 411089     /lib/i386-linux-gnu/libc-2.19.so
b7732000-b7733000 ---p 001a9000 08:01 411089     /lib/i386-linux-gnu/libc-2.19.so
b7733000-b7735000 r--p 001a9000 08:01 411089     /lib/i386-linux-gnu/libc-2.19.so
b7735000-b7736000 rw-p 001ab000 08:01 411089     /lib/i386-linux-gnu/libc-2.19.so
b7736000-b7739000 rw-p 00000000 00:00 0 
b774f000-b7750000 r--p 00855000 08:01 1188875    /usr/lib/locale/locale-archive
b7750000-b7752000 rw-p 00000000 00:00 0 
b7752000-b7753000 r-xp 00000000 00:00 0          [vdso]
b7753000-b7773000 r-xp 00000000 08:01 411098     /lib/i386-linux-gnu/ld-2.19.so
b7773000-b7774000 r--p 0001f000 08:01 411098     /lib/i386-linux-gnu/ld-2.19.so
b7774000-b7775000 rw-p 00020000 08:01 411098     /lib/i386-linux-gnu/ld-2.19.so
bfefe000-bff1f000 rw-p 00000000 00:00 0          [stack]

它们对应的内存布局情况如图5.1所示。Vdso(virtualDynamic Shared Object)是一个比较小的共享库,内核动态将该库映射到所有应用程序的地址空间中。这主要是因为glibc更新太慢导致的,其详细的简介请看http://www.man7.org/linux/man-pages/man7/vdso.7.html

图5.1 虚拟内存布局

每个进程都由一个structtask_struct结构体表示,其内存信息保存在struct mm_struct成员中,下面的结构体的部分成员的意义结合图5.1看更为清晰。

struct mm_struct {
	struct vm_area_struct * mmap;		/* 虚拟内存区域列表 */
	struct rb_root mm_rb;
	struct vm_area_struct * mmap_cache;	/* 最近一次find_vma的结果*/
#ifdef CONFIG_MMU
	unsigned long (*get_unmapped_area) (struct file *filp,
				unsigned long addr, unsigned long len,
				unsigned long pgoff, unsigned long flags);
	void (*unmap_area) (struct mm_struct *mm, unsigned long addr);
#endif
	unsigned long mmap_base;		/*Memory Mapping Segment起始地址*/
	unsigned long task_size;		/*进程虚拟地址空间大小,通常是3G */
	/* if non-zero, the largest hole below free_area_cache */
	unsigned long cached_hole_size;
/* first hole of size cached_hole_size or larger */
	unsigned long free_area_cache;		
	unsigned long highest_vm_end;		/* highest vma end address */
	pgd_t * pgd;
	atomic_t mm_users;			/* How many users with user space? */
	atomic_t mm_count;			/*对该结构体的引用计数 */
	int map_count;				/* number of VMAs */

	spinlock_t page_table_lock;		/* Protects page tables and some counters */
	struct rw_semaphore mmap_sem;

	struct list_head mmlist;		/* List of maybe swapped mm's.	These are globally strung
						 * together off init_mm.mmlist, and are protected
						 * by mmlist_lock
						 */


	unsigned long hiwater_rss;	/* High-watermark of RSS usage */
	unsigned long hiwater_vm;	/* High-water virtual memory usage */

	unsigned long total_vm;		/* Total pages mapped */
	unsigned long locked_vm;	/* Pages that have PG_mlocked set */
	unsigned long pinned_vm;	/* Refcount permanently increased */
	unsigned long shared_vm;	/* Shared pages (files) */
	unsigned long exec_vm;		/* VM_EXEC & ~VM_WRITE */
	unsigned long stack_vm;		/* VM_GROWSUP/DOWN */
	unsigned long def_flags;
	unsigned long nr_ptes;		/* Page table pages */
//见图5.1右侧,start_code、end_code代码段起止,start_data、end_data数据段起止。
	unsigned long start_code, end_code, start_data, end_data;
//堆
	unsigned long start_brk, brk, start_stack;
//参数和环境变量
	unsigned long arg_start, arg_end, env_start, env_end;

	unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */

	/*
	 * Special counters, in some configurations protected by the
	 * page_table_lock, in other configurations by being atomic.
	 */
	struct mm_rss_stat rss_stat;

	struct linux_binfmt *binfmt;

	cpumask_var_t cpu_vm_mask_var;

	/* Architecture-specific MM context */
	mm_context_t context;

	unsigned long flags; /* Must use atomic bitops to access the bits */

	struct core_state *core_state; /* coredumping support */

	/* store ref to file /proc/<pid>/exe symlink points to */
	struct file *exe_file;
	struct uprobes_state uprobes_state;
};

mm_struct 和VMA的关系如5.2所示。

图5.2 进程管理的vm_area_struct与进程虚拟地址空间的关联

struct vm_area_struct {
	/* 以下两个成员的信息可用于遍历VMA树,其存储在第一缓存行中*/
	unsigned long vm_start;		/* Our start address within vm_mm. */
	unsigned long vm_end;		/* 在vm_mm内结束地址之后的一个字节*/
	/* 各进程的VM(虚拟内存)链表,按地址升序排列,vm_rb是红黑树的管理*/
	struct vm_area_struct *vm_next, *vm_prev;
	struct rb_node vm_rb;

	/*
	 *该虚拟内存区的左侧最大空闲内存间隙,该间隙要么是VMA和vma->prev之间的间隙,要么就是该VMA之下的VMA去的红黑树和其自身的vm-prev的间隙,其作用是帮助get_unmapped_area 找到一个大小合适空闲的空间。*/
	unsigned long rb_subtree_gap;

	/* 第二个cache的缓存行存储从此开始. */

	struct mm_struct *vm_mm;	/* vm_area_struct属于的地址空间*/
	pgprot_t vm_page_prot;		/* Access permissions of this VMA. */
	unsigned long vm_flags;		/* Flags, see mm.h. */

	/*对于有地址空间和后备存储器的区域将被链接到address_space->i_mmap优先树或者被链接到address_space->i_mmap_nonlinear链表上 */
	union {
		struct {
			struct rb_node rb;
			unsigned long rb_subtree_last;
		} linear;
		struct list_head nonlinear;
	} shared;

	/*在对文件的某一页执行了COW(copy on write)操作之后,一个文件的MAP_PRIVATE虚拟内存区可能在 i_mmap 树和anon_vma链表这两个地方,一个MAP_SHARED类型的虚拟内存区只能映射到i_mmap树。一个匿名MAP_PRIVATE虚拟内存区,栈或者堆只能被映射到anon_vma链表。*/
	struct list_head anon_vma_chain; /* mmap_sem & page_table_lock 确保存取串行*/
	struct anon_vma *anon_vma;	/* page_table_lock确保存取串行(锁防止并发) */

	/* 处理这个结构体的函数操作集,缺页异常时的处理函数,就是该操作集的fault指针*/
	const struct vm_operations_struct *vm_ops;

	/*后备存储器的信息*/
	unsigned long vm_pgoff;		/* Offset (within vm_file) in PAGE_SIZE
					   units, *not* PAGE_CACHE_SIZE */
	struct file * vm_file;		/* 指向映射的文件,如果没有则为NULL*/
	void * vm_private_data;		/* was vm_pte (shared mem) */

	struct vm_region *vm_region;	/* NOMMU mapping region */
};

5.2 文件和虚拟内存

图5.3文件和虚拟地址空间的映射关系

structaddress_space的i_mmap成员指向的是私有和共享映射的红黑树,而i_mmap_nonlinear则是VM_NONLINEAR映射的链表,它们链表的指向的成员对象均是vm_area_struct,

5.3 虚拟内存区操作

由5.1和5.2节可以看出,虚拟内存和structvm_area_struct关系还是挺大的,这节就来看看和vm_area_struct相关的一些操作。

 5.3.1 find_vma

在5.1节提到structmm_struct的mmap_cache成员存放的是最近一次find_vma的结果。该函数用于查找用户空间地址中结束地址在给定地址之后的第一个区域,即满足addr>vm_area_struct->vm_end条件的第一个区域。

struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr)
{
	struct vm_area_struct *vma = NULL;

	/* 首先查找查找cache,命中率约35%*/
	vma = ACCESS_ONCE(mm->mmap_cache);
	if (!(vma && vma->vm_end > addr && vma->vm_start <= addr)) {
//cache没有命中,则执行下面的查找。
		struct rb_node *rb_node;
//获得红黑树根节点
		rb_node = mm->mm_rb.rb_node;
		vma = NULL;

		while (rb_node) {
//不要陌生,linux内核的c文件早就可以在使用一个变量时再定义了
			struct vm_area_struct *vma_tmp; 
			vma_tmp = rb_entry(rb_node,
					   struct vm_area_struct, vm_rb);
//如果获得的vma去的结束地址大于指定的地址,这就意味着找到了合适的vma区,否则决定是从左子树还是右子树开始查找,有点类似于二分查找。
			if (vma_tmp->vm_end > addr) {
				vma = vma_tmp;
				if (vma_tmp->vm_start <= addr)
					break;
				rb_node = rb_node->rb_left;
			} else
				rb_node = rb_node->rb_right;
		}
		if (vma)
			mm->mmap_cache = vma;
	}
	return vma;
}

5.3.2 vma_merge

vma_merge用于区域合并,当新插入的区域可以和一个或多个区域合并时,合并操作将被执行。给定一个映射需求(addr,end,vm_flags,file,pgoff),判断是否可以进行区域合并。绝大多数情况下,当调用mmap,brk或者mremap时,vma_merged地址范围参数[addr,end)肯定还没被映射;但是当调用mprotect时,这段区域肯定被映射了。prev是紧邻着新区域之前的区域,addr、end以及vm_flags分别是新区域的开始地址、结束地址以及标志。如果是一个文件映射,file还会指向struct file实例,最后pgoff反映的是映射在文件数据内的偏移量。先来说明这一过程,首先截取图5.2的一部分。

图5.4 VMA和插入VMA关系

根据原VMA和插入VMA的关系分为两大类,共八中情况。A表示要插入的VMA区,P表示在要插入的VMA区的前面,N则表示位置处在要插入的VMA区的后面。

在所有情况中,能够合并的分为两大类,一类是前驱合并,即case1/2/3,对于case1,P区、插入的VMA区,以及N区,可连接组合和一个整块VMA,case2插入的VMA区和P可以合并其它情况依次类推。X意思是不关心的区域,因为这时添加进入的VMA区的end和next->vm_end是相等的,即它们是重叠的区域,所以会将N区后移,找N的后驱作为真正的后驱。

mm/mmap.c
struct vm_area_struct *vma_merge(struct mm_struct *mm,
			struct vm_area_struct *prev, unsigned long addr,
			unsigned long end, unsigned long vm_flags,
		     	struct anon_vma *anon_vma, struct file *file,
			pgoff_t pgoff, struct mempolicy *policy)
{
	pgoff_t pglen = (end - addr) >> PAGE_SHIFT;
	struct vm_area_struct *area, *next;
	int err;
//如果紧邻新区域之前的区域非空,将获得next成员,这有点类似断开链表插入一个元素,否则获得该mm的第一个VMA元素
	if (prev)
		next = prev->vm_next;
	else
		next = mm->mmap;
	area = next;
//如果存在next存在,并且vma的结束地址和给定的结束地址相同,则有重叠,需要调整vma
	if (next && next->vm_end == end)		/* cases 6, 7, 8 */
		next = next->vm_next;

	/*
	 * 是否和前一个vma进行合并?
前一个vma存在(prev非空)并且其结束地址等于新区域的开始地址。两者的struct mempolicy(NUMA使用)属性相同,另外还有vm_flags以及映射同一个文件等属性也要一致。前驱合并
	 */
	if (prev && prev->vm_end == addr &&	mpol_equal(vma_policy(prev), policy) &&
			can_vma_merge_after(prev, vm_flags,//如果可以合并返回true。
						anon_vma, file, pgoff)) {
		/*
		 * 代码到这里,说明可以合并。现在查看是否可以对后一个vma也进行合并  
		 */
		if (next && end == next->vm_start &&	can_vma_merge_before(next, vm_flags,
			anon_vma, file, pgoff+pglen) &&is_mergeable_anon_vma(prev->anon_vma,
						      next->anon_vma, NULL)) {
							/* cases 1, 6 */
			err = vma_adjust(prev, prev->vm_start,
				next->vm_end, prev->vm_pgoff, NULL);
		} else					/* cases 2, 5, 7 */
			err = vma_adjust(prev, prev->vm_start,
				end, prev->vm_pgoff, NULL);
		if (err)
			return NULL;
		khugepaged_enter_vma_merge(prev);
		return prev;
	}

	/*如果前面的失败,则从后面的vma开始进行和上面类似的合并操作。后驱合并
	 * Can this new request be merged in front of next?
	 */
	if (next && end == next->vm_start &&
 			mpol_equal(policy, vma_policy(next)) &&
			can_vma_merge_before(next, vm_flags,
					anon_vma, file, pgoff+pglen)) {
		if (prev && addr < prev->vm_end)	/* case 4 */
			err = vma_adjust(prev, prev->vm_start,
				addr, prev->vm_pgoff, NULL);
		else					/* cases 3, 8 */
			err = vma_adjust(area, addr, next->vm_end,
				next->vm_pgoff - pglen, NULL);
		if (err)
			return NULL;
		khugepaged_enter_vma_merge(area);
		return area;
	}

	return NULL;
}

vma_adjust用于完成实际的合并操作。

5.3.3 insert_vm_struct

该函数用于将vm结构体插入按地址排列的进程链表和inode的i_mmap树中,如果vm_file非空,则需要获得i_mmap_mutex互斥信号量。属于红黑树的插入。

int insert_vm_struct(struct mm_struct *mm, struct vm_area_struct *vma)
{
	struct vm_area_struct *prev;
	struct rb_node **rb_link, *rb_parent;

	if (!vma->vm_file) {
		BUG_ON(vma->anon_vma);
		vma->vm_pgoff = vma->vm_start >> PAGE_SHIFT;
	}
	if (find_vma_links(mm, vma->vm_start, vma->vm_end,
			   &prev, &rb_link, &rb_parent))
		return -ENOMEM;
	if ((vma->vm_flags & VM_ACCOUNT) &&
	     security_vm_enough_memory_mm(mm, vma_pages(vma)))
		return -ENOMEM;

	vma_link(mm, vma, prev, rb_link, rb_parent);
	return 0;
}

5.3.4 创建区域

在插入新分配的内存区域之前,内核必须确认虚拟地址空间中是否有足够的空闲空间用于映射给定长度的区域。这一工作由get_unmapped_area函数完成,该函数是和体系结构是息息相关,具体体系结构实现的细节这里就不关心了。

5.4 创建地址区间

mmap,do_mmap

         内核在创建一个新的VMA区时,会判断和一个已存在的VMA区是否可以合并,如果可以合并,则会合并成一个区域,而不创建一个一个新的VMA,但如果无法合并,则会创建一个新的VMA区。

         do_mmap会调用do_mmap_pgoff完成实际的创建过程,该函数首先调用get_unmapped_area在虚拟地址空间中找到一个适当的区域用于映射。而calc_vm_prot_bits则是计算新VMA的权限标识。准备工作做好后,开始进行实际的区域映射工作,其由mmap_region来实现,mmap_region首先检查资源是否超出进程的资源限制,如果超出限制并且是固定映射则返回,否则计算可以映射多少VMA区,然后调用find_vma_links查找是否已有现存区域,如果有则需要调用do_munmap将该区域腾出来给新的VMA区。然后尝试不创建新的vm_area_struct实例,即使用vma_merge方法尝试将新的VMA区域合并到已有的vm_area_struct实例里去,如果合并不成功,则需要创建vm_area_struct实例,如果是文件映射,则file->f_op->mmap将文件和新创建的VMA区关联起来。最后调用vma_link将新的VMA区插入到线性虚拟地址空间去。

图5.5 创建新区域核心流程

5.5 删除地址区间

mumap,do_munmap()

用于从特定的进程地址空间中删除指定的VMA区,

mm/map.c

intdo_munmap(struct mm_struct *mm, unsigned long start, size_t len)

         第一个参数用于删除所在的地址空间,第二个参数指定删除的VMA的起始地址,第三个参数指定删除的长度。

mm/map.c
int vm_munmap(unsigned long start, size_t len)
{
	int ret;
	struct mm_struct *mm = current->mm;

	down_write(&mm->mmap_sem);
	ret = do_munmap(mm, start, len);
	up_write(&mm->mmap_sem);
	return ret;
}
EXPORT_SYMBOL(vm_munmap);

SYSCALL_DEFINE2(munmap, unsigned long, addr, size_t, len)
{
	profile_munmap(addr);
	return vm_munmap(addr, len);
}

删除一个VMA区的流程如图5.6所示。该函数首先调用find_vma查找和要删除VMA的start地址重叠的VMA区,如果没有重叠VMA区,则直接返回,如果需要分离VMA区,则调用__split_vma分离VMA区,然后调用find_vma判断删除区域的end是否也有重叠的VMA区,有也要分离。detach_vmas_to_be_unmapped用于将要删除的VMA解映射掉,其遍历所有的vm_area_struct实例线性表,直到要解除映射的地址范围已经全部涵盖在内,然后调用unmap_region从表页中删除与映射相关的所有项,同时内核会刷新相关的TLB项。最后调用remove_vma_list使用vm_area_struct实例占用的空间。


图5.6 删除VMA区流程

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

内核管理-之进程虚拟内存-基于linux3.10 的相关文章

随机推荐

  • 行式数据库与列式数据库的对比

    导语 随着大数据的发展 现在出现的列式存储和列式数据库 它与传统的行式数据库有很大区别的 正文 行式数据库是按照行存储的 行式数据库擅长随机读操作不适合用于大数据 像SQL server Oracle mysql等传统的是属于行式数据库范畴
  • C语言在控制台上实现鼠标操作的方法

    文章目录 了解windows库函数 了解句柄 实现思路与代码 在制作面向用户系统时 我们往往需要设置除输入参数外更为灵活的操作方式 例如鼠标点击 按键按下 无阻塞输入 等 同时 我们需要制作更为精美的 UI而不是简陋的黑白界面 然而 纯C语
  • 解决docker下nginx的多个站点互通问题

    系统为MAC 环境为docker mysql php nginx 问题描述 1 调试本地接口 方式为curl 请求 返回拒绝 业务上通过curl访问报错 Couldn t connect to server Failed to connec
  • git bash配置ssh 登录 Linux

    1 首先在 Linux 服务器上生成公钥和私钥文件 默认的存放目录在 ssh下 ssh keygen 可以将密码留空 这样之后就可以免密码登录 2 将私钥文件拷贝到本机 scp root 192 168 1 168 root ssh id
  • Ubuntu安装Protobuf,指定版本

    参考 https github com protocolbuffers protobuf readme https github com protocolbuffers protobuf blob v3 20 3 src README md
  • VirtualBox+WinDbg+Win7调试环境配置

    VirtualBox WinDbg Win7调试环境配置 火苗999 的博客 1 配置虚拟机串口如图 勾选启用串口 gt 端口选择COM1 gt 端口模式选择主机管道 gt 勾选创建管道 gt 端口文件位置输入 pipe com1 2 配置
  • 极简光线追踪入门

    阅读本文不需要任何图形学基础 这里抛砖引玉 希望勾起读者对光线追踪的兴趣 前言 光线追踪原理简单 并且可以抛开图形学的一堆理论 单独提出来讲 本文不需要任何图形学基础 看完本文后 将能够实现以下效果 图1 光线追踪效果 原理 光线追踪算法由
  • linux文件夹大小4096,Linux文件系统之文件、分区大小限制

    各种文件系统的限制 NTFS Windows 支持最大分区2TB 最大文件2TB FAT16 Windows 支持最大分区2GB 最大文件2GB FAT32 Windows 支持最大分区128GB 如果使用磁盘管理进行分区 最大为32GB
  • Docker自定义network搭建kafka

    昨天在搞spring cloud bus 想到用kafka实现消息总线 然后就用docker起了一个zookeeper和kafka 本人docker版本是17 06 采用机器是Ubuntu18版本 详细安装kafka的过程如下 1 dock
  • 反病毒工具-VirtualKD

    KD Kernel Debug 简介 它是一款 虚拟机辅助调试开源工具 版本支持情况 当前2 7版本支持Win8及以前系统 官网 http virtualkd sysprogs org 当你需要高效地调试一台虚拟机你需要它 双机调试的时候
  • VC中GDI绘图技术基础知识:hdc设备环境句柄,坐标系

    VC中GDI绘图技术 通过HDC设备环境句柄绘图有三种方式 标准客户区绘图 临时客户区绘图 非客户区绘图 1 标准客户区绘图 是在WM PAINT消息回调时执行 调用BeginPaint函数 EndPaint函数 2 临时客户区绘图 是在任
  • JSP+MVC开发模式 +EL表达式+ JSTL标签

    今日内容 1 JSP 1 指令 2 注释 3 内置对象 2 MVC开发模式 3 EL表达式 4 JSTL标签 5 三层架构 JSP 1 指令 作用 用于配置JSP页面 导入资源文件 格式 分类
  • ESP32 之 ESP-IDF 教学(十四)——虚拟文件系统(VFS)

    本文章 来自原创专栏 ESP32教学专栏 基于ESP IDF 讲解如何使用 ESP IDF 构建 ESP32 程序 发布文章并会持续为已发布文章添加新内容 每篇文章都经过了精打细磨 通过下方对话框进入专栏目录页 CSDN 请求进入目录 O
  • 安卓上基于透明代理抓包

    前言 在安卓上基于透明代理对特定APP抓包中使用的是redsocks2 本文演示如何使用clash实现同样的效果 你以为是Clash For Android 错 这里使用的是Core版本 就原理上来说 和前一篇文章并无区别 Clash的优势
  • Linux网络协议栈(五) -- 数据包的发送(based in 2.6.32)

    一 关键数据结构 对于输出封包 设备的数据结构主要包括两个 输出队列 queue 和输出队列规则 queue discipline 我们首先来看输出队列 2 6 18内核中无该结构体 struct netdev queue structne
  • 谈谈App混合开发

    混合开发的App Hybrid App 就是在一个App中内嵌一个轻量级的浏览器 一部分原生的功能改为Html 5来开发 这部分功能不仅能够在不升级App的情况下动态更新 而且可以在Android或iOS的App上同时运行 让用户的体验更好
  • 技术人修炼之道阅读笔记(六)解决对抗性思维方法

    技术人修炼之道阅读笔记 六 解决对抗性思维方法 网上经常看见产品经理和开发人员的段子 例如 杀死一个开发人员不用枪 只需要改三次需求 技术人开会 产品经理能安然无恙坐在这里 是因为门外有保安 等 这些都说明了产品经理和开发人员的矛盾 那么问
  • qt检查文件夹是否有写权限

    Qt 使用如下函数能够判断路径或者文件是否可写 bool QFileInfo isWritable const 对于win10系统实测 结果不准确 继续排查 官方文档描述 a 如果未启用 NTFS 权限检查 Windows 上的结果将仅反映
  • IDEA 程序包不存在,找不到符号但是明明存在对应的jar包 的解决方案

    注 本人刚开始是使用 Settings gt Build gt Build Tools gt Maven gt Runner gt 勾选上Delegagte IDE build run actions to Maven 这种办法 成功解决了
  • 内核管理-之进程虚拟内存-基于linux3.10

    关于启动过程内存管理见 内存管理 之启动 关于内核空间内存管理见 内存管理 之内核内存管理 如果需要 内存管理五章整理成pdf了 下载地址http download csdn net detail shichaog 8662135 进程的虚