八、用户进程:TSS、用户进程的创建、用户进程的执行(通过调度函数)
来源:互联网 发布:python 安卓 编辑:程序博客网 时间:2024/05/16 19:51
TSS
现代操作系统采用的任务切换
实现用户进程
uint32_t* pgdir;//进程自己页表的虚拟地址struct virtual_addr userprog_vaddr;//用户进程的虚拟地址池页表使用虚拟地址是因为页目录表本身也要占用内存来存储,我们在为进程创建页目录表,肯定要为页目录表申请内存,内存管理系统返回的地址肯定都是虚拟地址,不可能返回物理地址。
为进程创建页表和3特权级栈
static void* vaddr_get(enum pool_flags pf, uint32_t pg_cnt) { int vaddr_start = 0, bit_idx_start = -1; uint32_t cnt = 0; if (pf == PF_KERNEL) { // 内核内存池 bit_idx_start = bitmap_scan(&kernel_vaddr.vaddr_bitmap, pg_cnt); if (bit_idx_start == -1) { return NULL; } while(cnt < pg_cnt) { bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 1); } vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start * PG_SIZE; } else { // 用户内存池 struct task_struct* cur = running_thread(); bit_idx_start = bitmap_scan(&cur->userprog_vaddr.vaddr_bitmap, pg_cnt); if (bit_idx_start == -1) { return NULL; } while(cnt < pg_cnt) { bitmap_set(&cur->userprog_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 1); } vaddr_start = cur->userprog_vaddr.vaddr_start + bit_idx_start * PG_SIZE; /* (0xc0000000 - PG_SIZE)做为用户3级栈已经在start_process被分配 */ ASSERT((uint32_t)vaddr_start < (0xc0000000 - PG_SIZE)); } return (void*)vaddr_start;}
void* get_a_page(enum pool_flags pf, uint32_t vaddr) { struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool; lock_acquire(&mem_pool->lock); /* 先将虚拟地址对应的位图置1 */ struct task_struct* cur = running_thread(); int32_t bit_idx = -1;/* 若当前是用户进程申请用户内存,就修改用户进程自己的虚拟地址位图 */ if (cur->pgdir != NULL && pf == PF_USER) { bit_idx = (vaddr - cur->userprog_vaddr.vaddr_start) / PG_SIZE; ASSERT(bit_idx > 0); bitmap_set(&cur->userprog_vaddr.vaddr_bitmap, bit_idx, 1); } else if (cur->pgdir == NULL && pf == PF_KERNEL){/* 如果是内核线程申请内核内存,就修改kernel_vaddr. */ bit_idx = (vaddr - kernel_vaddr.vaddr_start) / PG_SIZE; ASSERT(bit_idx > 0); bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx, 1); } else { PANIC("get_a_page:not allow kernel alloc userspace or user alloc kernelspace by get_a_page"); } void* page_phyaddr = palloc(mem_pool); if (page_phyaddr == NULL) { return NULL; } page_table_add((void*)vaddr, page_phyaddr); lock_release(&mem_pool->lock); return (void*)vaddr;}
如何从特权级0进入特权级3
刚开始我们的系统一直运行在kernel中,特权级一直是0,所以我们要从0特权级进入3特权级。我们的中断从3特权级进入0特权级,然后是通过 iret 返回,所以我们这里也假装通过 iret 返回,所以要经过 intr_exit,即一大堆的 pop 指令,将3特权级的寄存器资源弹出去。从中断返回时候,要弹出之前的中断前的寄存器资源,所以我们要进入3特权级,要提前在 intr_exit中设置好3特权级的资源。CPU通过中断栈中CS的RPL知道了将会返回哪个特权级,所以RPL必须为3。
1,假装从中断返回,用过 iret 指令,就要经过 intr_exit 的一系列 pop
2,要提前在中断栈中设置好3特权级的环境:cs和eip、ss 和 esp 等,以便借着pop可以弹出
3,中断栈中CS中的RPL 必须为3
4,相应的在用户模式下,只能访问特权级为3的代码段和数据段,因此中断栈中的相应的段寄存器中的选择子指向的段描述符的DPL也要为3
5,在进入中断时候,eflags 的 IF 位 为0,不能响应中断,在退出中断后要置1,因此中断栈中的IF要置1
6,中断栈中的 eflags 的IOPL 位为1 ,不允许用户进程直接访问硬件。
用户进程创建流程
进程首先要先创建好,然后才能运行。进程创建由函数 process_execute()来完成:包括创建进程使用的PCB(一页),然后完善这个PCB:初始化好struck task_stack:虚拟地址池和页表、创建好 thread_stack:注意是进程的函数了、创建好 intr_stack:进程的上下文环境、然后将进程加入全部队列和就绪队列
图中的 init_thread() 是创建 task_stack 的,thread_create() 是创建 thread_stack的:首次运行是调用 kernek_thread(start_process,user_prog),然后调用 start_process(user_prog)的。
创建好后开始执行,通过 时钟中断后时间片到了然后调用 schedule()来进行调度就行进程队列,pop 出用户进程开始执行的。首先激活页表,创建时候将页表的内容都已经写好, 但是并没有给CR3赋值,所以。在shedule()中要将页表激活,就是给CR3赋值。然后通过 ret 弹到 kernel_thread(start_process,user_porg)-->start_process(user_prog)。start_process(user_prog)函数功能:用来构建用户进程的上下文,也就是在 intr_stack 附上合适的值,然后调用 intr_eixt,pop 出一系列值,进入到用户进程中CPU开始执行。
注意:可以看出1,进程是在线程的基础上执行的,很多函数都是通用的。2,线程在执行到 kernel_thread( function,arg)--》function(arg),这个函数功能就是直接执行进程代码了。但是进程到这一步后的函数功能是:先初始化 intr_stack,然后调用 intr_exit,pop 出一系列的值,然后执行进程的代码。
c程序的内存分布
实现用户进程
操作系统是为用户进程服务的,它提供了各种系统功能供用户进程调用。为了用户进程可以访问到内核服务,必须确保用户进程在自己的地址空间中能够访问到内核才行。虚拟地址空间由页表来控制,页表由操作系统来管理,所以用户空间的虚拟空间是由操作系统分配的。每个用户都有4GB的虚拟空间,操作系统把4GB的用户空间和内核空间,操作系统占了3G-4G,用户空间是0G-3G。用户进程占据了页目录表的 0-767 个页目录项,内核占据页目录表的 768-1023个目录项。
操作系统在物理内存中的位置是固定的,内核进程也只有一套页表,也是固定的。我们要想内核被所有的用户进程都可以访问到,我们只需要把每个用户进程页目录项用内核页目录表的第768-1023个页目录项代替即可。因为我们的内核页表也是3GB-4GB的,这样当我们的用户进程的3GB-4GB的一个地址来访问时候,就会用虚拟地址的10+10+12来访问物理地址。我们只复制页目录项即可,页表项不需要复制,因为1024个页表在内核的页表地址中已经存在了,用户进程中直接转到内核页表项中去找了。
//功能:创建页表,注意页表项都应该填入物理地址,CR3中应该填入的也是物理地址
//实现:在内核物理池中申请一页,4k/4=1024项,我们将后四分之一复制成内核的。
uint32_t* create_page_dir(void){uint32_t* page_dir_vaddr = get_kernel_pages(1);//用户进程的页表不能让用户直接访问到,所以在内核空间来申请 if (page_dir_vaddr == NULL){console_put_str("create_page_dir: get_kernel_page failed!");return NULL;}//1 先复制页表 memcpy((uint32_t*)((uint32_t)page_dir_vaddr + 0x300 * 4), (uint32_t*)(0xfffff000 + 0x300 * 4), 1024);// page_dir_vaddr + 0x300*4 是内核页目录的第768项 //2 更新页目录地址 uint32_t new_page_dir_phy_addr = addr_v2p((uint32_t)page_dir_vaddr);page_dir_vaddr[1023] = new_page_dir_phy_addr | PG_US_U | PG_RW_W | PG_P_1;return page_dir_vaddr;}
用户进程有自己的堆和栈,因此必须要有方法跟踪内存的分配情况。和内核一样,用户进程也是用位图来管理地址分配的,每个进程有自己单独的位图。用户进程被加载到内存后,剩余未用的高地址都被作为堆和栈的共享空间。虚拟地址池结构包括虚拟起始地址和虚拟地址的位图。我们选择 0xc0804_8000 为用户程序的起始虚拟地址,即0x0804_8000 --0xc000_0000为用户进程的虚拟地址。我们还是用位图的一位表示4K,那么我们的虚拟地址需要的位图页数就要从内核物理池中申请。
//功能:创建用户进程虚拟地址位图
//实现:赋值用户进程虚拟地址的起始地址、赋值用户进程虚拟地址位图的起始地址和位图字节长度。
void create_user_vaddr_bitmap(struct task_struct* user_prog) { user_prog->userprog_vaddr.vaddr_start = USER_VADDR_START; uint32_t bitmap_pg_cnt = DIV_ROUND_UP((0xc0000000 - USER_VADDR_START) / PG_SIZE / 8 , PG_SIZE); user_prog->userprog_vaddr.vaddr_bitmap.bits = get_kernel_pages(bitmap_pg_cnt); user_prog->userprog_vaddr.vaddr_bitmap.btmp_bytes_len = (0xc0000000 - USER_VADDR_START) / PG_SIZE / 8; bitmap_init(&user_prog->userprog_vaddr.vaddr_bitmap);}//功能:创建用户进程:在内核中申请PCB,然后初始化各个栈,主要是为 task_stack 中的页表和虚拟地址池赋值。
void process_execute(void* filename, char* name) { /* pcb内核的数据结构,由内核来维护进程信息,因此要在内核内存池中申请 */ struct task_struct* thread = get_kernel_pages(1); //为进程申请一个pcb页。 init_thread(thread, name, default_prio); //初始化task_struck create_user_vaddr_bitmap(thread);//为task_struck中的虚拟地址赋值 thread_create(thread, start_process, filename);//初始化thread_stack,即第一次运行时候的函数 thread->pgdir = create_page_dir();//页表 enum intr_status old_status = intr_disable(); ASSERT(!elem_find(&thread_ready_list, &thread->general_tag)); list_append(&thread_ready_list, &thread->general_tag); ASSERT(!elem_find(&thread_all_list, &thread->all_list_tag)); list_append(&thread_all_list, &thread->all_list_tag); intr_set_status(old_status);}
//功能:激活进程:包括激活页表和更新tss中的0特权级栈。激活页表是将页表物理地址赋予CR3。更新tss是将
void process_activate(struct task_struct* p_thread) { ASSERT(p_thread != NULL); /* 击活该进程或线程的页表 */ page_dir_activate(p_thread); /* 内核线程特权级本身就是0,处理器进入中断时并不会从tss中获取0特权级栈地址,故不需要更新esp0 */ if (p_thread->pgdir) { /* 更新该进程的esp0,用于此进程被中断时保留上下文 */ update_tss_esp(p_thread); }}
//功能:创建用户进程上下文。当通过switch()函数开始执行用户进程时候首先要先设置用户上下文环境,然后 iret 出去。
void start_process(void* filename_) { void* function = filename_; struct task_struct* cur = running_thread(); cur->self_kstack += sizeof(struct thread_stack); struct intr_stack* proc_stack = (struct intr_stack*)cur->self_kstack; proc_stack->edi = proc_stack->esi = proc_stack->ebp = proc_stack->esp_dummy = 0; proc_stack->ebx = proc_stack->edx = proc_stack->ecx = proc_stack->eax = 0; proc_stack->gs = 0; // 用户态用不上,直接初始为0 proc_stack->ds = proc_stack->es = proc_stack->fs = SELECTOR_U_DATA; proc_stack->eip = function; // 待执行的用户程序地址 proc_stack->cs = SELECTOR_U_CODE; proc_stack->eflags = (EFLAGS_IOPL_0 | EFLAGS_MBS | EFLAGS_IF_1); proc_stack->esp = (void*)((uint32_t)get_a_page(PF_USER, USER_STACK3_VADDR) + PG_SIZE) ; proc_stack->ss = SELECTOR_U_DATA; asm volatile ("movl %0, %%esp; jmp intr_exit" : : "g" (proc_stack) : "memory");}
用户进程的特征
用户进程的特征为:3特权级、单独的页表和3特权级的栈。无论代码在内核空间还是在用户空间,代码都是一段指令而已,CPU并不能分清这段代码是用户代码还是内核代码。处理器关注的是CPL:当前特权级,即cs中的RPL。
- 八、用户进程:TSS、用户进程的创建、用户进程的执行(通过调度函数)
- 创建特定用户的进程
- 进程的用户身份
- system 用户创建的进程创建当前用户(如Administrator)的进程。
- 用户进程中执行的操作系统
- 通过枚举进程获取用户的TOKEN
- Linux下的进程类别(内核线程、轻量级进程和用户进程)以及其创建方式--Linux进程的管理与调度(四)
- 浅析内核中用户进程的创建
- "SYSTEM"用户创建进程
- 通过API HOOK 创建SYSTEM用户进程
- 进程的有效用户和实际用户
- 取进程的用户(所有进程)
- 进程的用户ID探究
- 进程的用户ID探究
- 进程的用户ID探究
- 进程的用户ID探究
- 进程的用户ID探究
- linux进程的用户ID
- 阿里云服务器Tomcat无法从外部访问
- 计蒜客 统计字符个数 --Python
- TakePhotoV2.0.0新版隆重发布
- Spring boot源码分析-ConfigurationProperties
- 如何使用Journalctl查看并操作Systemd日志
- 八、用户进程:TSS、用户进程的创建、用户进程的执行(通过调度函数)
- Java简单文件读写
- 块设备驱动
- Spring boot源码分析-Conditional(12)
- 软件工程学习总结
- 谁拿到了最多奖学金
- Android issue: adb install … 发生异常 [INSTALL_FAILED_TEST_ONLY: installPackageLI
- Java回调
- 接口切换