Living@Greatwall

How to use Pelican to bulid blog on github.io

一直想建立一个自己的blog,以前用过csdn,不过自从用了云笔记,就很久没更新blog了。看现在很流行用github搭建blog,所以决定玩一下,加上学过一点python,就选择了pelican这个python写的工具,顺便练练python。

1. virtualenv的使用

python开发的常用工具,使用这个工具可以隔离本项目依赖的环境设置,这样就能避免干扰其他项目的环境变量设置。virtualenv的使用参考:http://docs.python-guide.org/en/latest/dev/virtualenvs/. 常用命令:

  • virtualenv venv:建立一个虚拟环境,会在当前目录中建立一个venv的目录,里面包含了这个虚拟环境所有的配置信息;
  • virtualenv -p /usr/bin/python2.7 venv:指定venv环境使用python解释器;
  • source venv/bin/activate:激活该虚拟环境,每次使用该命令进入虚拟环境;
  • deactivate:退出虚拟环境;

2. 安装pelican

这个可在虚拟环境下安装,也可以就在本机环境下安装,

$: sudo …

多进程IPC机制实现

这是lab4最后一个任务,实现进程间的通信IPC,主要完成kern/syscall.c里面的两个API: sys_ipc_recv和sys_ipc_try_send,以及lib/syscall.c的提供给用户的系统调用:ipc_recv和ipc_send。实现代码如下:

static int
sys_ipc_try_send(envid_t envid, uint32_t value, void *srcva, unsigned perm)
{
    // LAB 4: Your code here.
    struct Env *env = NULL;
    struct PageInfo *page = NULL;
    int r;

    if ((r = envid2env(envid, &env, 0)) < 0) {
        return r;
    }
    /* make sure that dst …

spinlock的实现

spinlock在多核多线程的场景中使用非常广泛,采用busy_wait_loop忙等待的方式,与信号量相比的优点在于不会进程上下文的切换(进程的调度),节省了进程切换带来的敖贵的系统开销。一般用spinlock时,要保证临界区的代码短小,等待的时间较短。linux内核中很多地方都使用了spinlock,如中断上下文中使用(多核),用户态多进程pthread提供了spinlock的API。spinlock的实现一般采用cpu提供的原子指令(atomic)来实现,如Test-and-Set,Compare-and-Swap。 JOS内核提供了spinlock,采用CAS(使用x86的xchg指令)方法实现。 spinlock的定义如下:

// Mutual exclusion lock.
struct spinlock {
    unsigned locked;       // Is the lock held?

#ifdef DEBUG_SPINLOCK
    // For debugging:
    char *name;            // Name of lock.
    struct CpuInfo *cpu;   // The CPU holding the lock …

lab4:MultiProcessor支持、fork实现

一、多核启动

lab4将JOS扩展到多核上,让JOS支持SMP(symmetric multiprocessing,一个multiprocessor模型)。 关于multiprocessor与multicore的区别请看:https://software.intel.com/en-us/blogs/2008/04/17/the-difference-between-multi-core-and-multi-processing。 multiprocessor(multiCPUS)中每个cpu属于不同的chip,然后插在同一个主板上,cpu之前通过总线通信;而multicore是所有cpus都在同一个chip上,这样cpu之间的通信时延更低,更省电; 在SMP系统中,正常运行时所有cpu功能一样,但在启动时,cpu分为两类: 1)Bootstrap processor(BSP):负责初始化并启动系统; 2)Application processors(AP):os启动后被BSP激活。

在SMP系统中,每个cpu有一个APIC(LAPIC:高级可编程中断管理器)单元,专门负责传送中断信号,见kern/lapic.c,LAPIC给每个cpu分配一个唯一的ID …

lab3:user mode与kernel mode的切换

两个模式切换涉及两个很重要的数据结构:Taskstate和TrapFrame,Taskstate前面已提到,这个在cpu初始化时会调用trap_init_percpu将设置这个结构体中的栈寄存器和段寄存器: tss->ts_esp0 = 每个cpu的栈top; tss->ts_ss0 = GD_KD; 内核的数据段选择子

每个cpu都有一个这样的结构:thiscpu->cpu_ts,这个是为了当从用户态切换到内核态时,让cpu能找到内核栈和数据段的地址。

TrapFrame用在内核态与用户态相互切换时,当从user mode切换到kernel mode时,cpu会将当前进程的状态信息以TrapFrame的形式 保存在cpu的内核栈上(内核栈地址由上面的tss给出),而当cpu从kernel mode返回时,会从内核栈上pop出进程的状态信息,然后继续进程的运行。 下面从两种模式切换时trapframe的作用。

1) 从user mode到kernel mode

发生这种切换时,内核也必须保存当前用户进程的状态,以便返回时可以继续执行,用户进程的状态就保存在TrapFrame结构中, 那么cpu是怎么做的呢? 先看TrapFrame结构:

struct PushRegs {
    /* registers as pushed by pusha */
    uint32_t reg_edi;
    uint32_t reg_esi;
    uint32_t …

lab3: Interrupts, Exceptions, Traps, Syscall

x86有两种类型的中断:

中断interrupt:主要指外设的中断; 异常exception:包括faults,traps,aborts,还有软中断(注意软中断也被当做异常处理); x86使用下面两种机制来实现中断处理机制。

1. 中断描述符表IDT

x86允许256个中断,每个中断有一个中断向量interrupt vector,这个vector值在[0, 255]之间。当中断发生时,cpu使用这个vector来索引IDT,IDT与GDT类似,IDT中每一项都是一个描述符,叫IDT descriptors,有三种类型: task gates:用于hardware支持的任务切换; interrupt gates; trap gates; 每个描述符长为8字节,结构如下(由于task gate没用到,就不说了):

那么中断的过程如下图:

当中断产生时,cpu使用vector来索引IDT,找到相应的描述符,从描述符中获取下面的东西: 将描述符里面值赋给eip,也就是将eip指向了中断处理服务例程的指令(就是讲上上面的图中selector和offset组成得到eip); 将描述符里面的值赋给cs寄存器,包括特权级DPL,一般中断处理这个都为0,内核态处理 …

lab3:加载用户进程

该练习主要完成内核加载用户进程,包括用户进程环境的初始化,创建用户进程,加载用户进程等。 主要完成以下几个函数的实现: * env_init():初始化用户进程,配置每个段的优先级特权级;

  • env_setup_vm():给每个进程分配一个页目录,并初始化用户地址空间的内核部分;

  • region_alloc():给用户进程分配物理内存并将物理地址映射到进程的虚拟地址空间;

  • load_icode():解析用户进程elf文件,并加载到用户地址空间;

  • env_create():调用env_alloc和load_icode;

  • env_run():在用户模式下执行进程;

由于此时还没有文件系统,此时是将用户的进程elf文件嵌入到内核的image中,最后生成的 kernel.sym包含一些特殊的符号,这个符号实际上就是用户程序的elf文件,最后由内核加载 并执行。

每个用户进程都有一个相应的结构体:

struct Env {
    struct Trapframe env_tf;    // Saved registers
    struct Env *env_link;       // Next free Env
    envid_t env_id;         // Unique environment identifier
    envid_t env_parent_id;      // env_id …

lab2-exercise1:physical memory management

这部分内容主要是完成物理页的初始化,完成物理页pages数组的映射,并初始化page_free_list。exercise1的内存布局如图, 前面(entry.S)提到内核的c代码被链接到了到0xF0100000处(下图中KERNBASE=0xF0000000),而entry.S最后使能了分页机制, 也就是进入内核的c代码后,所有的地址都是虚拟地址,也就是左边的地址空间,但是内核的c代码的物理内存仍在0x100000处, 所以要建立一个映射,也就是图中的映射。该练习主要完成一下几个函数:

boot_alloc()

mem_init() (only up to the call to check_page_free_list(1))

page_init()

page_alloc()

page_free()

1. boot_alloc

是开始阶段的内存分配,注意它是从end(内核所占空间的结尾)开始分配内存,后面的虚拟地址都是可用的,这个函数是以页为分配单位,就算n不足4KB,也会分配一页。

static void *
boot_alloc(uint32_t n)
{
    static char *nextfree …

lab2-exercise2:页表

lab2的exercise1主要完成物理页的管理,建立一个简单的内存管理机制,后面的练习需要用到内存分配都是这里完成的,小结一下exercise1的要点: 完成物理内存的管理机制:主要是建立pages[]与物理页的一一映射(包括设置可用和不可用的物理页,pages数组的index就是物理内存地址pa的高22位); exercise2主要内容是完成y页目录和页表的建立,从而建立起虚拟地址空间与物理地址空间的映射,主要是实现以下几个函数:

  • pgdir_walk()
  • boot_map_region()
  • page_lookup()
  • page_remove()
  • page_insert()

1. pgdir_walk

给定一个页目录,该函数返回一个虚拟地址对应的在页目录中的页表项page table entry(PTE)的地址。 当该虚拟地址对应的页表不存在时,会创建一个页表。后面几个函数的实现都会依赖该函数.

pte_t *
pgdir_walk(pde_t *pgdir, const void *va, int create)
{
    // Fill this function in
    pde_t pde;
    pte_t *pte;
    struct PageInfo *page;

    pde  = pgdir …