首页 > 编程知识 正文

数据库系统原理教程,数据库原理及应用教程陈志泊第四版

时间:2023-05-04 11:14:24 阅读:163099 作者:1166

创建页表在Linux启动过程中,要首先初始化内存,必须首先创建页表。 我们知道每个进程都有各自的进程空间,每个进程空间分为内核空间和用户空间。

以arm32为例,每个进程有4G的虚拟空间,其中0-3G属于用户地址空间,3G-4G属于内核地址空间,内核地址空间由所有进程共享,因此内核地址空间的页表

Linux内核中用户进程内存页面表的管理是用一个结构体mm_struct来描述的:

sructmm_struct{.PGD_t*PGD; atomic_t mm_users; /* How many users with user space? */atomic_t mm_count;/* howmanyreferencesto ' struct mm _ struct ' (userscountas1) */atomic_long_t nr_ptes;/* ptepagetablepages */# ifconfig _ pg table _ levels2atomic _ long _ tnr _ pmds;/* pmdpagetablepages */# endifintmap _ count;/* numberofvmas */s pinlock _ tpage _ table _ lock;/* protectspagetablesandsomecounters */struct rw _ semaphore mmap _ SEM; struct list_head mmlist;/* listofmaybeswappedmm ' s.thesearegloballystrung * togetheroffinit _ mm.mm list,andareprotected*bymmlist_lock* 通过上一篇文章的介绍,我们知道pgd页表的条目中包含了下一页表的基址。 这样,您就可以找到PUD/PMD/PTE之后的更多页面表。

用户进程的页表进程页表存储在每个进程的task_struct中。 首先来看task_struct:

include/linux/sched.h :

struct task _ struct { . struct mm _ struct * mm,*active_mm; ……; 在这个mm成员变量中,保存了与那个程序对应的mm_struct构造体数据,据此可以知道对应的程序的页面表。

mmactive_mm用户进程地址空间活跃用户进程地址空间active_mm成员是专门为内核进程部署的,内核进程不需要访问用户地址空间。 也就是说,由于mm成员被设置为NULL,因此为了使内核进程与普通用户进程具有统一的上下文切换方式,内核进程在进行上下文切换时会对内核进程的active_mm进行调度当发现切换进程的是内核进程(线程)时,由于不需要访问用户地址,所以只需借用前一个进程的active mm进行配置即可。 这样做

static _ always _ inlinestructrq * context _ switch (struct rq * rq,struct task_struct *prev,struct task_struct *prev ) mm=next-mm; oldmm=prev-active_mm; /* * For paravirt,thisiscoupledwithanexitinswitch _ toto * combinethepagetablereloadandtheswitchbackendinto * onehypercall into mm ) { next-active_mm=oldmm; atomic_Inc(oldmm-mm_count ); enter_lazy_TLB(oldmm,next ); }elseswitch_mm_irqs_off(oldmm,mm,next ); 从上面的函数中可以看出,如果mm为空,则直接将active_mm设置为prev-active_mm。 这就是设置的内核线程的地址空间。 对于用户进程,active_mm设置为mm。 这个步骤是在福克斯的时候进行的。

staticintcopy _ mm (unsigned long clone _ flags,s

truct task_struct *tsk){ struct mm_struct *mm, *oldmm; int retval; tsk->min_flt = tsk->maj_flt = 0; tsk->nvcsw = tsk->nivcsw = 0;#ifdef CONFIG_DETECT_HUNG_TASK tsk->last_switch_count = tsk->nvcsw + tsk->nivcsw;#endif tsk->mm = NULL; tsk->active_mm = NULL; /* * Are we cloning a kernel thread? * * We need to steal a active VM for that.. */ oldmm = current->mm; if (!oldmm) return 0; /* initialize the new vmacache entries */ vmacache_flush(tsk); if (clone_flags & CLONE_VM) { atomic_inc(&oldmm->mm_users); mm = oldmm; goto good_mm; } retval = -ENOMEM; mm = dup_mm(tsk); if (!mm) goto fail_nomem;good_mm: tsk->mm = mm; tsk->active_mm = mm; return 0;fail_nomem: return retval;}

fork执行的时候是会调用copy_mm函数的,此函数通过oldmm来判断当前执行fork的是内核进程还是用户进程,如果是oldmm为空,代表着要创建的是一个内核进程,此时我们直接返回,如果是一个用户进程,那么最后会设置 tsk->mm = mm; 并且 tsk->active_mm = mm; 。task_struct中的mm成员主要是记录用户地址空间,其中记录的pgd是会最终配置到MMU中的TTBR0寄存器中的。

内核页表

在Linux系统中所有进程的内核页表是共享的同一套,内核页表是存放在swapper_pg_dir,这一套是我们静态定义的页表:

struct mm_struct init_mm = { .mm_rb = RB_ROOT, .pgd = swapper_pg_dir, .mm_users = ATOMIC_INIT(2), .mm_count = ATOMIC_INIT(1), .mmap_sem = __RWSEM_INITIALIZER(init_mm.mmap_sem), .page_table_lock = __SPIN_LOCK_UNLOCKED(init_mm.page_table_lock), .mmlist = LIST_HEAD_INIT(init_mm.mmlist), .user_ns = &init_user_ns, INIT_MM_CONTEXT(init_mm)};

swapper_pg_dir 仅包含内核(全局)映射,而用户空间页表仅包含用户(非全局)映射。CPU在访问一个虚拟内存时,由虚拟地址可以确定到底要访问用户地址还是内核地址,然后选择对应的TTBRx,找到对应的pgd基地址,而swapper_pg_dir 作为共享的内核地址空间,它的地址被写入TTBR1 中,且从不写入 TTBR0。

我们知道了要存放的pgd地址,那么在初始化时,还需要在对应的pgd项中配置上对应的PGD页表项内容才能使能MMU,为了获取内核地址空间的pgd offset,内核中定义了如下宏:

/* to find an entry in a page-table-directory */#define pgd_index(addr) ((addr) >> PGDIR_SHIFT)#define pgd_offset(mm, addr) ((mm)->pgd + pgd_index(addr))/* to find an entry in a kernel page-table-directory */#define pgd_offset_k(addr) pgd_offset(&init_mm, addr)

如下的函数是用来创建内核地址空间映射页表的,它会通过上面的宏定义获取对应的地址,然后在地址上写入要映射的下一级页表的基地址。

/* * Create the page directory entries and any necessary * page tables for the mapping specified by `md'. We * are able to cope here with varying sizes and address * offsets, and we take full advantage of sections and * supersections. */ static void __init create_mapping(struct map_desc *md) { if (md->virtual != vectors_base() && md->virtual < TASK_SIZE) { pr_warn("BUG: not creating mapping for 0x%08llx at 0x%08lx in user regionn", (long long)__pfn_to_phys((u64)md->pfn), md->virtual); return; } if ((md->type == MT_DEVICE || md->type == MT_ROM) && md->virtual >= PAGE_OFFSET && md->virtual < FIXADDR_START && (md->virtual < VMALLOC_START || md->virtual >= VMALLOC_END)) { pr_warn("BUG: mapping for 0x%08llx at 0x%08lx out of vmalloc spacen", (long long)__pfn_to_phys((u64)md->pfn), md->virtual); } __create_mapping(&init_mm, md, early_alloc, false); }

由此以来,我们可以一步一步完成内核的页表配置初始化。另外需要特别注意的是,这个init_mm结构体是会被设置到init_task中的avtive_mm上的,init_task是给swapper进程静态定义的task结构体,此进程是系统中的第一个进程,所以为了以后的进程调度,active_mm的功能是正常的,我们必须要给第一个进程赋值。

#define INIT_TASK(tsk) { INIT_TASK_TI(tsk) .state = 0, .stack = init_stack, .usage = ATOMIC_INIT(2), .flags = PF_KTHREAD, .prio = MAX_PRIO-20, .static_prio = MAX_PRIO-20, .normal_prio = MAX_PRIO-20, .policy = SCHED_NORMAL, .cpus_allowed = CPU_MASK_ALL, .nr_cpus_allowed= NR_CPUS, .mm = NULL, .active_mm = &init_mm, .restart_block = { .fn = do_no_restart_syscall, }, ...... 内核页表是如何在不同进程中共享的?

内核地址空间使用的TTBR1作为页表基地址,而用户地址空间是TTBR0作为页表基地址,这样我们只需要配置内核页表后设置到TTBR1寄存器,后面再各个进程切换时,不对TTBR1做切换,即可共享这段内存配置,而用户空间地址,我们在进程切换是需要进行切换,这个切换是通过task_struct中的mm_struct成员来做的。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。