实验为IPADS《现代操作系统原理与实现》配套实验,慕课上有完整的课程教学视频,以下为个人实验过程和踩坑记录。

Capability

为什么需要有capability?用户进程是不能直接访问内核资源的,因此我们需要借助一种机制,既能让用户进程能告诉操作系统需要操作的资源,同时不能让用户进程直接访问到。而capability就相当于一个key,在系统调用中,用户把这个key作为参数传入(这个key可以是用户进程创建该资源时返回给用户的),操作系统根据key取出对应的资源进行操作。

process.h文件中定义了一个进程的结构,包括了线程的链表和slot_table。这个table就是记录了该进程拥有的资源,包括了线程,进程本身等。

struct process {struct slot_table slot_table;struct list_head thread_list;
};

所有的资源都被object类型所抽象,其中的opaque字段就是指向真正的资源(注意这里是个数组类型,具体原因后面会说)。即若是有一个object对象,那么可以通过其opaque字段取出其指向的真实对象(可以是一个线程,一个pmo等);若是有一个pmo对象或是线程对象,可以通过container_of(obj, struct object, opaque);可以取出其对应的object

而之前提到的cap,它是processslot_table的下标,通过这个下标,我们可以取出一个object_slot,这个结构体中的信息比object稍微丰富一些,包括了权限等,其中一个字段就是指向object,然后通过object,我们就可以用opaque字段拿到其真正管理的资源(线程、pmo等)

struct object {u64 type;u64 size;/* Link all slots point to this object */struct list_head copies_head;/** refcount is added when a slot points to it and when get_object is* called. Object is freed when it reaches 0.*/u64 refcount;u64 opaque[];
};struct object_slot {u64 slot_id;struct process *process;int isvalid;u64 rights;struct object *object;/* link copied slots pointing to the same object */struct list_head copies;
};struct slot_table {unsigned int slots_size;struct object_slot **slots;/** if a bit in full_slots_bmp is 1, corresponding* sizeof(unsigned long) bits in slots_bmp are all set*/unsigned long *full_slots_bmp;unsigned long *slots_bmp;
};

下面可以看几个例子感受一下cap是如何被使用的。

创建object并分配cap

thread_create函数中,我们需要创建线程,然后把线程加入到进程的slot_table中管理起来,同时要返回cap作为索引。其中核心的一句如下:

thread = obj_alloc(TYPE_THREAD, sizeof(*thread));

任何需要通过cap来管理的资源都是通过object来抽象的,所以需要先创建一个object对象。该函数的第二个参数是线程的大小,这是因为我们需要用这个大小来初始化object,使其能容纳我们需要的资源。

仔细看一下这个函数的定义:

void *obj_alloc(u64 type, u64 size)
{u64 total_size;struct object *object;// opaque is u64 so sizeof(*object) is 8-byte aligned.// Thus the address of object-defined data is always 8-byte aligned.total_size = sizeof(*object) + size;object = kmalloc(total_size);if (!object)return NULL;object->type = type;object->size = size;object->refcount = 0;init_list_head(&object->copies_head);return object->opaque;
}

在分配object的时候,kmalloc的大小为sizeof(*object)+size,最后返回的是opaque字段。此时object的内存布局如下:​

object

语句:thread = obj_alloc(TYPE_THREAD, sizeof(*thread));,相当于是thread = malloc(sizeof(struct thread)),并且额外地,我们在头部加上了点别的信息,这样组成了一个object。这样一个函数调用完成了thread空间的分配和object的初始化。

这里最精妙的地方就是这个opaque的类型,是个数组,因此最后返回这个数组名的时候,实际上返回的是指向第一个元素的指针,即第二个参数size分配的额外的内存空间的起始地址。如果换成u64*指针类型的话就没有这种效果了。

然后我们通过cap = cap_alloc(process, thread, 0);,将线程加入到进程的slot_table中,并且返回cap,最终返回给用户,这个函数定义如下:

int cap_alloc(struct process *process, void *obj, u64 rights)
{struct object *object;struct object_slot *slot;int r, slot_id;object = container_of(obj, struct object, opaque);slot_id = alloc_slot_id(process);if (slot_id < 0) {r = -ENOMEM;goto out_unlock_table;}slot = kmalloc(sizeof(*slot));if (!slot) {r = -ENOMEM;goto out_free_slot_id;}slot->slot_id = slot_id;slot->process = process;slot->isvalid = true;slot->rights = rights;slot->object = object;list_add(&slot->copies, &object->copies_head);BUG_ON(object->refcount != 0);object->refcount = 1;install_slot(process, slot_id, slot);return slot_id;out_free_slot_id:free_slot_id(process, slot_id);out_unlock_table:return r;
}

该函数的第二个参数我们传入的是thread的指针,这个是真实的对象,因此我们需要获取它对应的object,这里就用到了前面提到的宏定义。获取到object之后,后面就是加入到slot_table中的一些操作,包括了权限的设置等。

根据cap获取对应的被管理的对象(线程,pmo等)

如何根据线程的cap来获取线程本身?在process_create_root函数中有如下语句:root_thread = obj_get(root_process, thread_cap, TYPE_THREAD);

其中obj_get定义如下:

/* object refenrence */
void *obj_get(struct process *process, int slot_id, int type)
{return get_opaque(process, slot_id, true, type);
}/* local object operation methods */
static void *get_opaque(struct process *process, int slot_id,bool type_valid, int type)
{struct slot_table *slot_table = &process->slot_table;struct object_slot *slot;void *obj;if (!is_valid_slot_id(slot_table, slot_id)) {obj = NULL;goto out_unlock_table;}slot = get_slot(process, slot_id);BUG_ON(slot->isvalid == false);BUG_ON(slot->object == NULL);if (!type_valid || slot->object->type == type) {obj = slot->object->opaque;} else {obj = NULL;goto out_unlock_slot;}atomic_fetch_add_64(&slot->object->refcount, 1);out_unlock_slot:out_unlock_table:return obj;
}

get_opaque方法中,我们根据cap(即slot_id)来查找object_slot,之后取出objectopaque字段返回,即返回了cap对应的真实对象本身(线程)。

get和put一一对应

要注意对于obj_getobj_put的操作要一一对应,因为在get和put的时候会改变refcount的值,因此如果不是一一对应的话可能会造成无法正常释放内存空间或是提前释放。

创建用户进程和线程

加载ELF

ELF format参考wiki:https://en.wikipedia.org/wiki/Executable_and_Linkable_Format

加载ELF的意思就是读取ELF段,然后映射到其指定的虚拟内存地址上。

因此需要:

  1. 正确读取ELF文件的段,包括在ELF文件中的起始位置长度
  2. 正确映射到虚拟地址,包括正确的虚拟地址起始位置长度

需要仔细阅读下面几个字段的含义:

ELF

其中p_offset指的是在img文件中,该段的偏移量,即若是要读取img文件的某一段,那么(void*)bin + elf->p_headers[i].p_offset;就是该段的开头。对应的是1中的起始位置。

p_filesz指的是该段在img文件中的长度,对应的是1中的长度。

p_vaddr指定了该段需要被映射到的虚拟地址开头。要特别注意这个虚拟地址不是页对齐的,也就是说,当我们映射ELF的某一段到虚拟内存的时候,需要注意一下页对齐。对应的是2中虚拟地址的起始位置。

p_memsz是占用的虚拟内存大小,同样需要考虑对齐。对应的是2中的长度。

通过上面四个字段我们就可以完成这个加载函数。要特别注意起始的虚拟地址和虚拟内存长度的对齐,这将会影响到我们如何将ELF文件的段copy到物理内存中,以及如何将物理内存映射到虚拟内存。考虑如下情况:

虚拟地址对齐

上图中竖着的细线都是页对齐的,p_vaddr可能出现在一个页的中间,同样,因为p_memsz也不是页对齐的,因此结束位置也可能会出现在页的中间。这样我们在申请一块物理内存的时候,实际上需要4个页(将第一个和最后一个页补齐)。在映射的时候,我们把申请的4个物理页映射到虚拟地址时,起始页的地址应该是ROUND_DOWN(p_vaddr, PAGE_SIZE)(对应的是最左边的细线),这就导致我们在将ELF文件copy到内存中的时候,也需要考虑这段偏移,即p_vaddr - ROUND_DOWN(p_vaddr, PAGE_SIZE);(对应的是左边粗线到页开头的那一段)。这样才能在访问虚拟地址的时候,正确读取到ELF文件中段的内容。

核心代码如下:

seg_sz = elf->p_headers[i].p_memsz;
p_vaddr = elf->p_headers[i].p_vaddr;seg_map_sz = ROUND_UP(seg_sz + p_vaddr, PAGE_SIZE) - ROUND_DOWN(p_vaddr, PAGE_SIZE);

拷贝时候考虑偏移:

vaddr_t pmo_start_vaddr = (vaddr_t) phys_to_virt(pmo->start);
pmo_start_vaddr += p_vaddr - ROUND_DOWN(p_vaddr, PAGE_SIZE);
char *pos = (void*)bin + elf->p_headers[i].p_offset;
size_t copy_size = elf->p_headers[i].p_filesz;
for(int i = 0; i < copy_size; i++){*((char *)pmo_start_vaddr + i) = *(pos + i);
}

初始化线程上下文

创建完线程之后,通过eret_to_thread(switch_context());来返回,其中eret_to_threadexception_table.S中定义:

/* void eret_to_thread(u64 sp) */
BEGIN_FUNC(eret_to_thread)mov   sp, x0exception_exit
END_FUNC(eret_to_thread)

第一个参数会赋值给sp,之后exception_exit的时候,会把栈中的值复制给寄存器,即恢复了线程执行的上下文,之后通过eret指令切换到用户态就能正确执行了。

因此在初始化线程上下文的时候,只需要在正确的结构体内填入信息即可:

arch_set_thread_stack(thread, stack);
arch_set_thread_next_ip(thread, func);
thread->thread_ctx->ec.reg[SPSR_EL1] = SPSR_EL1_EL0t;

切换线程上下文

switch_context能正确返回刚刚保存的结构体的开头,这样弹栈的时候就能正确赋值给对应寄存器:

return (u64)target_ctx->ec.reg;

单处理机系统的进程调度实验_Chcore -- 上交IPADS操作系统银杏书配套Lab实验笔记 - Lab3进程与异常(一)...相关推荐

  1. 操作系统实验_Chcore -- 上交IPADS操作系统银杏书配套Lab实验笔记 - Lab2内存管理(一)...

    实验为IPADS<现代操作系统原理与实现>配套实验,慕课上有完整的课程教学视频,以下为个人实验过程和踩坑记录. 物理内存布局 当bootloader和内核被加载到内存中的时候,物理内存到底 ...

  2. 单处理机系统的进程调度动态优先_操作系统复习笔记(王道)C2.1

    进程与线程 一.进程特征和概念 前提:允许多个程序并发执行. 1.概念 PCB(process control block)进程控制块,系统利用 PCB 来描述进程的基本情况和运行状 态,进而控制和管 ...

  3. 操作系统算法模拟实例之单处理机系统进程调度

    操作系统算法模拟实例之单处理机系统进程调度 1.实验目的 在多道程序或多任务系统中,系统回时处于就绪态的进程有若干个,为使系统中 的各进程能有条不素地运行,必须选择某种调度策略,以选择一进程占用处理机 ...

  4. 单自由度系统的振动的幅频特性曲线及相频特性曲线及matlab分析,实验四 线性系统的频域分析...

    实验四 线性系统的频域分析 一.实验目的 1.掌握用MA TLAB 语句绘制各种频域曲线. 2.掌握控制系统的频域分析方法. 二.基础知识及MATLAB 函数 频域分析法是应用频域特性研究控制系统的一 ...

  5. 操作系统实验1—实现单处理机下的进程调度程序

    操作系统实验1-实现单处理机下的进程调度程序 文章目录 操作系统实验1-实现单处理机下的进程调度程序 实验描述 设计思路 上机代码 测试结果 心得体会 实验描述 实验内容: 编写一个单处理机下的进程调 ...

  6. linux处理机调度实验报告,模拟Linux操作系统下处理机调度实验报告

    在采用多道系统的设计程序中,往往有若干进程同时处于就绪状态.当就绪状态进程数大于处理机数时,就必须按照某种策略来决定哪些进程优先占用处理机.本实验模拟在单处理机情况下处理机调度 处理机调度 一.实验目 ...

  7. 单自由度振动系统 matlab,单自由度系统的振动及matlab分析

    <单自由度系统的振动及matlab分析>由会员分享,可在线阅读,更多相关<单自由度系统的振动及matlab分析(6页珍藏版)>请在金锄头文库上搜索. 1.单自由度系统的振动及 ...

  8. 处理机调度和进程调度

    调度 处理机调度 高级调度 中级调度 低级调度 进程调度 进程调度的时机 进程调度的方式 进程的切换与过程 调度算法 FCFS 先来先服务 SJF 短作业优先 HRRN 高响应比优先 总结 交互式调度 ...

  9. 处理机调度之进程调度

    3.3进程调度 3.3.1进程调度的任务.机制和方式 1.进程调度任务 任务:分派CPU 主要过程: 1.保存处理机的现场信息 2.按某种算法选取进程 3.把处理器分配给新进程 2.进程调度机制 3. ...

最新文章

  1. html中绝对定位的父级,父元素相对定位,子元素绝对定位
  2. Linux 应用编程
  3. python进制转换内置函数_python数学运算、逻辑运算和进制转化相关的 内置函数...
  4. mikrotik ros ***借线
  5. Maven 项目创建 找不到web.xml
  6. 图的遍历(Java)构造器
  7. VBoxManage: error: Failed to create the host-only
  8. 虚拟机的安装中遇到的问题(WIN10主机)
  9. 幻灯片插入smartArt
  10. python控制小爱同学_小爱同学控制电脑开机 - IT客栈
  11. 麦子学院I2C设备驱动201117
  12. 关于“外接硬盘被写了保护的解决方法”
  13. 修改Linux时间一般涉及到3个命令: date, clock, hwclock
  14. 10.6版本的CodeWarrior 的使用手册
  15. 转至老熊三分地--inside sqlplus prelim
  16. 动态规划解决币值最大化问题
  17. PBX与VoIP:它们之间有什么区别?
  18. 基于Anaconda 搭建 OpenCV for Python 环境(全平台通用)
  19. axure文件如何加密_最全产品设计工具整理,你都掌握了吗?
  20. 颜值绝绝子的 14 款 Chrome 官方主题 !

热门文章

  1. .NET MVC Scripts.Render 上下文不存在问题解决方法
  2. 数据库中,连接有哪些不同类型?请说明这些类型之间的差异,以及为何在某些情形下,某种连接会比较好。...
  3. 查看mysql状态常用命令
  4. ZZULIOJ 1116: 删除元素
  5. 信息学奥赛一本通(2070:【例2.13】数字对调)
  6. 信息学奥赛一本通(1161:转进制)
  7. 信息学奥赛一本通(1141:删除单词后缀)
  8. 扩散(信息学奥赛一本通-T1437)
  9. 逆序对(洛谷-P1908)
  10. 三角形判断(信息学奥赛一本通-T1054)