MIT-JOS系列5:用户环境(一)

Part A:用户环境和异常处理

用户环境创建

本节中我们将实现一些内核的基本工具来支持受保护的用户进程的运行。我们将增加JOS内核的功能,为它增加一些数据结构来追踪用户进程的一些信息;创建一个单一用户的环境,并在其中加载运行一个程序。我们也会使JOS内核处理用户进程做出的任何系统调用和它导致的任何异常

内核利用ENV数据结构来记录每一个环境的信息。目前我们只创建单一的用户环境,以后再在此基础上设计多用户环境

kern/env.c中,内核维护以下三个关于环境的全局变量:

struct Env *envs = NULL;        // All environments
struct Env *curenv = NULL;      // The current env
static struct Env *env_free_list;   // Free environment list

一旦JOS系统开始运行,envs指针指向一个保存当前系统中所有环境变量的Env结构体数组

  • JOS内核最多同时支持NENV个活跃的环境,并为每一个可能的环境(共NENV个)申请一个Env数据结构,存放在envs数组中
  • JOS内核将所有未使用Env结构体放在env_free_list链表中,以便用户环境的分配和回收
  • curenv指针指向JOS内核中任意时刻正在执行的环境。当内核启动、还没有任何用户环境运行时,它被初始化为NULL

Env数据结构设计如下:

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 of this env's parent
    enum EnvType env_type;      // Indicates special system environments
    unsigned env_status;        // Status of the environment
    uint32_t env_runs;      // Number of times environment has run

    // Address space
    pde_t *env_pgdir;       // Kernel virtual address of page dir
};

env_tf:当内核或其他环境执行时,保存当前未执行的环境的寄存器变量。例如当内核从用户态切换到内核态运行时,用户态的重要寄存器将被保存,以便在回到用户态运行时恢复它们

env_link:指向env_free_list中该环境的下一个未使用的环境。只有在这个结构体也未使用时(也在env_free_list中时)这个域才有用

env_id:标识当前使用此Env结构体的环境的唯一值。当一个环境终止、内核将这个环境的Env重新分配给其他环境后,它们的env_id也不相同

env_parent_id:存放创建此环境的环境的env_id

env_type:用于区分一些特殊环境。对于大多数环境,它的值都是ENV_TYPE_USER

env_status:该域取值为以下之一:

  • ENV_FREE:该Env结构体未被使用,应该被放在env_free_list
  • ENV_RUNNABLE:该Env结构体对应的环境就绪,等待被分配到处理器
  • ENV_RUNNING:该Env结构体对应的环境正在运行
  • ENV_NOT_RUNNABLE:该Env结构体对应的环境处于活跃状态,但此时无法运行:例如他正在等待来自另一个环境的消息
  • ENV_DYING:该Env结构体对应的环境是一个僵尸环境,它将在系统下一次进入内核态时被回收

eng_pgdir:保存该Env结构体对应的环境的页表目录的虚拟地址

与Unix进程相类似,一个JOS环境结合了线程(thread)和地址空间(address space)的概念:线程主要通过保存寄存器的值(env_tf)来定义,地址空间主要通过保存页表目录和页表(eng_pgdir)来定义。要运行一个环境,内核必须为它设置合适的寄存器的值和合适的地址空间

在JOS系统中,环境并没有在内核中拥有各自独立的栈。。因为任意时刻只能有一个环境处于活跃状态,因此JOS内核只需要一个内核栈。

分配环境数组

mem_init()补充对envs数组的内存分配。这个过程与分配页面数组pages是一样的

envs = (struct Env *) boot_alloc(NENV*sizeof(struct Env));
memset(envs, 0, NENV*sizeof(struct Env));

然后需要建立envs的映射关系:虚拟地址为UENVS,权限为用户可读

boot_map_region(kern_pgdir, UENVS, ROUNDUP(envs_size, PGSIZE), PADDR(envs), PTE_U);

创建和运行环境

接下来要编写运行一个用户环境的必要代码。由于现在还没有文件系统,因此JOS将一些用户程序的静态二进制文件作为ELF文件嵌入在内核中,以便被载入和执行

Lab3的GNUmakefileobj/user/目录下生成一系列二进制文件,它们通过-b binary命令作为原始二进制文件被链接到内核中

在读取和运行这些二进制文件前,我们首先完成用户环境的初始化

env_init()

函数功能:初始化所有envs数组中的Env结构体并把它们加入到env_free_list链表中

注意事项:env_free_list中结构体的顺序应与envs数组相同,即第一次调用env_alloc()应该返回envs[0]

代码实现如下:

void
env_init(void)
{
    // Set up envs array
    // LAB 3: Your code here.
    int i;

    for (i=NENV-1; i>=0; i--) {
        envs[i].env_status = ENV_FREE;
        envs[i].env_link = env_free_list;
        env_free_list = &envs[i];
    }

    // Per-CPU part of the initialization
    env_init_percpu();
}

函数最后调用env_init_percpu()配置段式内存管理系统,它所做的事包括

  • 重新载入GDT表
  • 初始化数据段寄存器GS、FS(留给用户数据段使用)、ES、DS、SS(在用户态和内核态切换使用)
  • 初始化内核的代码段寄存器CS
  • 初始化LDT表为0

env_setup_vm()

函数功能:为新的环境分配页表目录,并在新环境的地址空间中初始化与内核相关的部分

相关部分是指:用户和内核一样理论上能访问完整的4G虚拟内存(这里假设能访问到4G)。内核已建立UTOP以上的虚拟地址到物理地址的映射,因此内核拷贝一份这个映射到自己的页表目录中。对应的物理地址用户究竟能不能访问靠页表项中通过权限位控制

代码实现如下:

static int
env_setup_vm(struct Env *e)
{
    int i;
    struct PageInfo *p = NULL;

    // Allocate a page for the page directory
    if (!(p = page_alloc(ALLOC_ZERO)))
        return -E_NO_MEM;

    // Now, set e->env_pgdir and initialize the page directory.
    //
    // Hint:
    //    - The VA space of all envs is identical above UTOP
    //  (except at UVPT, which we've set below).
    //  See inc/memlayout.h for permissions and layout.
    //  Can you use kern_pgdir as a template?  Hint: Yes.
    //  (Make sure you got the permissions right in Lab 2.)
    //    - The initial VA below UTOP is empty.
    //    - You do not need to make any more calls to page_alloc.
    //    - Note: In general, pp_ref is not maintained for
    //  physical pages mapped only above UTOP, but env_pgdir
    //  is an exception -- you need to increment env_pgdir's
    //  pp_ref for env_free to work correctly.
    //    - The functions in kern/pmap.h are handy.

    // LAB 3: Your code here.
    e->env_pgdir = (uintptr_t *)page2kva(p);
    p->pp_ref++;

    // 用户和内核一样能访问到4G的虚拟地址空间,其中
    // UTOP以下的地址空间是用户自己的
    // UTOP以上的地址空间是内核的
    // 赋值一份UTOP以上的内核页表目录给用户目录页表,UTOP以下用户自由发挥
    for (i=PDX(UTOP); i<NPDENTRIES; i++)
        e->env_pgdir[i] = kern_pgdir[i];
    // UVPT maps the env's own page table read-only.
    // Permissions: kernel R, user R
    e->env_pgdir[PDX(UVPT)] = PADDR(e->env_pgdir) | PTE_P | PTE_U;

    return 0;
}

UVPT起的一个页面存放的是当前环境使用的页表的页表目录,因此修改页表目录项e->env_pgdir[PDX(UVPT)]存储的页表基地址为PADDR(e->env_pgdir),并赋予权限用户、内核可读,使访问UVPT起的一个页面等效于访问e->env_pgdir

问:这里能不能用映射来做?

region_alloc()

函数功能:为环境分配len字节的物理内存并映射到用户的虚拟地址空间

  • 不需要对分配的空间初始化
  • 分配的页用户和内核具有写权限
  • 需要对起始地址va和长度len进行4K页面对齐

代码实现如下:

static void
region_alloc(struct Env *e, void *va, size_t len)
{
    // LAB 3: Your code here.
    // (But only if you need it for load_icode.)
    //
    // Hint: It is easier to use region_alloc if the caller can pass
    //   'va' and 'len' values that are not page-aligned.
    //   You should round va down, and round (va + len) up.
    //   (Watch out for corner-cases!)
    uintptr_t start = ROUNDDOWN(va, PGSIZE);
    uintptr_t end = start + ROUNDUP(len, PGSIZE);
    struct PageInfo *p = NULL;
    int i;
    for (i=start; i<end; i+=PGSIZE) {
        p = page_alloc(0);
        if (!p)
            panic("Out of memory!\n");
        if (page_insert(e->env_pgdir, p, i, PTE_U | PTE_W) == -E_NO_MEM)
            panic("Out of memory!\n");
    }
}

load_icode()

函数功能:解析ELF二进制文件,加载其内容到新环境的用户地址空间中

  • 每个用户进程都是一个ELF文件。像boot loader所做的那样,从ELF文件加载用户进程的初始代码区、堆栈和处理器标识位
  • 这个函数仅在内核初始化期间、第一个用户态环境运行前被调用
  • 函数将ELF文件中所有可加载的段载入到用户地址空间中,设置e->env_tf.tf_eip为ELF文件头指示的入口(虚拟地址),以便它之后能从这里开始执行程序
  • 清零bss节
  • 映射程序的初始堆栈到一个页面

代码实现如下:

static void
load_icode(struct Env *e, uint8_t *binary)
{
    // Hints:
    //  Load each program segment into virtual memory
    //  at the address specified in the ELF section header.
    //  You should only load segments with ph->p_type == ELF_PROG_LOAD.
    //  Each segment's virtual address can be found in ph->p_va
    //  and its size in memory can be found in ph->p_memsz.
    //  The ph->p_filesz bytes from the ELF binary, starting at
    //  'binary + ph->p_offset', should be copied to virtual address
    //  ph->p_va.  Any remaining memory bytes should be cleared to zero.
    //  (The ELF header should have ph->p_filesz <= ph->p_memsz.)
    //  Use functions from the previous lab to allocate and map pages.
    //
    //  All page protection bits should be user read/write for now.
    //  ELF segments are not necessarily page-aligned, but you can
    //  assume for this function that no two segments will touch
    //  the same virtual page.
    //
    //  You may find a function like region_alloc useful.
    //
    //  Loading the segments is much simpler if you can move data
    //  directly into the virtual addresses stored in the ELF binary.
    //  So which page directory should be in force during
    //  this function?
    //
    //  You must also do something with the program's entry point,
    //  to make sure that the environment starts executing there.
    //  What?  (See env_run() and env_pop_tf() below.)

    // LAB 3: Your code here.
    struct Elf *elf = (struct Elf*)binary;
    if (elf->e_magic != ELF_MAGIC)
        panic("load_icode() error: Not the ELF file.\n");

    // 设置程序的入口
    e->env_tf.tf_eip = elf->e_entry;

    struct Proghdr *ph, *eph;
    ph = (struct Proghdr*)(binary + elf->e_phoff);
    eph = ph + elf->e_phnum;

    for (; ph<eph; ph++) {
        if (ph->p_type == ELF_PROG_LOAD) {
            if (ph->p_filesz > ph->p_memsz)
                panic("load_icode() error: file length > memory length.\n");
            region_alloc(e, ph->p_va, ph->p_memsz);

            // 将elf文件的段数据直接move到相应的虚拟地址空间
            memmove(ph->p_va, binary+ph->p_offset, ph->p_filesz);
            // 由于filesz<=memsz, 将多出来的memz置0
            memset(ph->p_va+ph->p_filesz, 0, ph->p_memsz-ph->p_filesz);
        }
    }

    // Now map one page for the program's initial stack
    // at virtual address USTACKTOP - PGSIZE.
    region_alloc(e, USTACKTOP-PGSIZE, PGSIZE);
    // LAB 3: Your code here.
}

env_create()

函数功能:调用env_alloc()创建一个新的环境,调用load_icode()向环境中载入ELF文件

代码实现如下:

void
env_create(uint8_t *binary, enum EnvType type)
{
    // LAB 3: Your code here.
    struct Env *env = NULL;
    int err = env_alloc(&env, 0);

    if (err == -E_NO_FREE_ENV)
        panic("env_create() error: no free environment.\n");
    if (err == -E_NO_MEM)
        panic("env_create() error: out of memory.\n");

    env->env_type = type;
    load_icode(env, binary);
}

env_run()

函数功能:运行一个给定的环境

  • 如果是环境切换(即有环境正在运行):

    1. 如果当前环境curenvenv_statusENV_RUNNING,设置它为ENV_RUNNABLE
    2. 设置curenv为新的环境
    3. 设置新的环境的env_statusENV_RUNNING
    4. 更新env_runs计数
    5. 利用lcr3()切换地址空间
  • 利用env_pop_tf()恢复环境的重要寄存器并进入用户模式

代码实现如下:

void
env_run(struct Env *e)
{
    // Step 1: If this is a context switch (a new environment is running):
    //     1. Set the current environment (if any) back to
    //        ENV_RUNNABLE if it is ENV_RUNNING (think about
    //        what other states it can be in),
    //     2. Set 'curenv' to the new environment,
    //     3. Set its status to ENV_RUNNING,
    //     4. Update its 'env_runs' counter,
    //     5. Use lcr3() to switch to its address space.
    // Step 2: Use env_pop_tf() to restore the environment's
    //     registers and drop into user mode in the
    //     environment.

    // Hint: This function loads the new environment's state from
    //  e->env_tf.  Go back through the code you wrote above
    //  and make sure you have set the relevant parts of
    //  e->env_tf to sensible values.

    // LAB 3: Your code here.
    if (curenv != NULL && curenv->env_status == ENV_RUNNING) {
        curenv->env_status = ENV_RUNNABLE;
    }
    curenv = e;
    curenv->env_status = ENV_RUNNING;
    curenv->env_runs++;

    // 设置cr3中的页表目录地址:地址为物理地址
    lcr3(PADDR(curenv->env_pgdir));
    env_pop_tf(&curenv->env_tf);

    // panic("env_run not yet implemented");
}

env_run()这个函数是永不返回的(不是指没有返回值,而是指它跳转去执行别的代码了,而且永远不会回到这里了),它利用env_pop_tf()函数设置新环境的重要寄存器,然后进入用户环境执行代码,因此env_pop_tf()必须放在env_run()的最后一行执行。env_pop_tf()的代码如下:

// Restores the register values in the Trapframe with the 'iret' instruction.
// This exits the kernel and starts executing some environment's code.
//
// This function does not return.
void
env_pop_tf(struct Trapframe *tf)
{
    asm volatile(
        "\tmovl %0,%%esp\n"
        "\tpopal\n"
        "\tpopl %%es\n"
        "\tpopl %%ds\n"
        "\taddl $0x8,%%esp\n" /* skip tf_trapno and tf_errcode */
        "\tiret\n"
        : : "g" (tf) : "memory");
    panic("iret failed");  /* mostly to placate the compiler */
}

env_pop_tf()tf的内容视为栈,从逐个出栈里面的数据,为相应的寄存器赋值

  1. movl %0,%%esp将当前栈指针指向输入变量tf
  2. popal恢复所有r32寄存器,即tf.PushRegs里的东西
  3. pop出tf_es赋值到es
  4. pop出tf_ds赋值到ds
  5. 跳过tf_trapnotf_err,使esp指向tf_eip
  6. 调用iret,跳转到用户进程执行代码

    调用iret时:从esp指向的栈中顺序出栈eip, cs, eflags(标志寄存器), esp, ss赋值到相应寄存器,然后程序跳转到cs:eip处继续执行,因此这个函数正常状态下不会返回

用户环境小结

至此为止,进入运行一个新环境的函数调用如下:

  • start (kern/entry.S)
  • i386_init(kern/init.c)
    • cons_init
    • mem_init
    • env_init
    • trap_init (还未实现)
    • env_create
    • env_run
      • env_pop_tf

启动内核,创建并进入一个环境的过程大约如下:

  1. 启动内核,开启分页,设置栈区(kern/entry.S,之前lab1做的)
  2. 初始化.bss段,初始化一系列硬件(cons_init,之前lab1做的)
  3. 虚拟内存初始化(mem_init,之前lab2做的,lab3刚刚对其进行补充:增加初始化envs数组)
    1. 用CMOS检测可用的物理内存
    2. 为kernel的页表目录分配内存,将页表目录kern_pgdir作为页表插入到页表项UVPT处(UVPT处用于存放当前页表目录,以便内核以外的环境在UVPT处能够查找到自己的页表目录)
    3. 初始化pages数组和envs数组,编写页表分配、释放、映射的相关函数
    4. 完成物理内存前256M的映射,与此同时填写了页表目录和二级页表,并赋予页面相应权限
    5. 设置cr3kern_pgdir的物理地址,并设置cr0的标志
  4. 为所有可能的环境进行初始化(env_init
    1. 初始化envs数组中的每一项状态为ENV_FREE,并放入未使用的环境env_free_list链表中
    2. 加载GDT表和初始化段描述子
  5. 中断设置和异常处理(trap_init,还没做)
  6. 创建一个新环境(env_create
    1. 调用env_alloc()初始化一个环境:这个函数不是由我们动手编写的,但调用了我们编写的一些函数

      • env_free_list拿出一个未被使用的Env结构
      • 利用env_setup_vm()初始化新环境的虚拟地址空间:
        • 为新环境分配一页内存作为页表目录
        • 将其地址填写到新环境的env_pgdir域中
        • 拷贝内核页表目录中UTOP以上部分(内核地址空间映射情况)到新环境的页表目录
        • 将新的页表目录作为页表插入到新环境目录页表项的UVPT
      • 为新环境生成一个唯一标识env_id
      • 初始化新环境的其他域:env_parent_idenv_typeenv_statusenv_runs
      • 初始化新环境的段寄存器env_tf.tf_ds/es/ss/cs关联当前GDT表,初始化栈指针env_tf.tf_esp指向USTACKTOP
      • 修改env_free_list指向下一个未被使用的Env结构
    2. 调用load_icode()为新环境加载可执行二进制文件,这个二进制是进入新环境后执行的程序:
      • 以elf格式读取二进制的程序执行入口e_entry,并用这个入口设置新环境的env_tf.tf_eip,让新环境能够从二进制的入口开始执行程序
      • 利用region_alloc()为类型为ELF_PROG_LOAD的节分配内存并映射到p_va指示的位置
      • 利用memmove()将这些节长度为p_filesz的文件内容移动到p_va指示的位置
      • 由于p_filesz<=p_memsz,利用memset()填充两者之间的空缺
      • 注意:此处操作的p_va是新环境的地址空间,有别于kernel的地址空间,因此需要在操作之前先临时把cr3设置为新环境的页表目录地址e->env_pgdir,操作结束后再恢复到内核的页表目录地址
    3. 为新环境分配一页作为其栈区,映射到虚拟地址USTACKTOP-PGSIZE
      • 这里我寻思着USTACKTOP-PGSIZE是在UTOP底下,也属于用户内存区,也得在用户地址空间下执行?
  7. 进入一个新环境,执行其程序(env_run
    1. 若当前有环境在运行,设置该环境的env_statusENV_RUNNABLE
    2. 设置当前环境指针curenv指向新环境
    3. 修改新环境的运行状态为ENV_RUNNING,运行次数env_runs++
    4. 修改cr3为新环境的页表目录地址
    5. 调用env_pop_tf()进入新环境

代码完成到这里,编译并启动qemu运行后,系统就应该能够顺利进入用户环境并执行hello这个二进制文件。由于目前还没实现对中断和异常的处理,因此它将在hello进行系统调用int $0x30时出错(这行能在hello.asm中找到)。JOS此时尚未设置硬件允许从用户空间转换到内核态,当CPU发现这个它没办法处理这个中断时,它引发一个异常;当它又发现它没办法处理这个异常时,它又引发了一个异常;但它发现它还是没办法处理这个异常,只好放弃,因此最终引发了“三重异常”,在qemu中得到Triple fault的输出。

查看hello.asm,我们可以在中断处设置断点b *0x800b44,如果程序能够执行到这个位置没有异常,就说明之前编写的代码是正确的:

gdb:

The target architecture is assumed to be i8086
[f000:fff0]    0xffff0: ljmp   $0xf000,$0xe05b
0x0000fff0 in ?? ()
+ symbol-file obj/kern/kernel
(gdb) b *0x800b44
Breakpoint 1 at 0x800b44
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x800b44:    int    $0x30

Breakpoint 1, 0x00800b44 in ?? ()
(gdb) 

qemu:

[email protected]:~/1work/MIT-JOS/lab$ make qemu-nox-gdb
***
*** Now run 'make gdb'.
***
qemu-system-i386 -nographic -drive file=obj/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp::26000 -D qemu.log  -S
6828 decimal is 15254 octal!
Physical memory: 131072K available, base = 640K, extended = 130432K
check_page_alloc() succeeded!
check_page() succeeded!
check_kern_pgdir() succeeded!
check_page_installed_pgdir() succeeded!
[00000000] new env 00001000

接下来,在gdb中输入si再往下执行一步,将能在qemu中收到Triple fault

gdb:

Breakpoint 1, 0x00800b44 in ?? ()
(gdb) si
=> 0x800b44:    int    $0x30

Breakpoint 1, 0x00800b44 in ?? ()
(gdb) 

qemu:

[00000000] new env 00001000
EAX=00000000 EBX=00000000 ECX=0000000d EDX=eebfde88
ESI=00000000 EDI=00000000 EBP=eebfde60 ESP=eebfde54
EIP=00800b44 EFL=00000092 [--S-A--] CPL=3 II=0 A20=1 SMM=0 HLT=0
ES =0023 00000000 ffffffff 00cff300 DPL=3 DS   [-WA]
CS =001b 00000000 ffffffff 00cffa00 DPL=3 CS32 [-R-]
SS =0023 00000000 ffffffff 00cff300 DPL=3 DS   [-WA]
DS =0023 00000000 ffffffff 00cff300 DPL=3 DS   [-WA]
FS =0023 00000000 ffffffff 00cff300 DPL=3 DS   [-WA]
GS =0023 00000000 ffffffff 00cff300 DPL=3 DS   [-WA]
LDT=0000 00000000 00000000 00008200 DPL=0 LDT
TR =0028 f018eb60 00000067 00408900 DPL=0 TSS32-avl
GDT=     f011b300 0000002f
IDT=     f018e340 000007ff
CR0=80050033 CR2=00000000 CR3=003bc000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
EFER=0000000000000000
Triple fault.  Halting for inspection via QEMU monitor.

处理中断和异常

见下一篇博客(还没写完)

原文地址:https://www.cnblogs.com/sssaltyfish/p/10689405.html

时间: 2024-08-30 00:30:56

MIT-JOS系列5:用户环境(一)的相关文章

MIT-JOS系列7:用户环境(三)

Part B:缺页异常,断点异常和系统调用 到目前位置我们以及你实现了内核基本的异常处理,现在要在此基础上利用异常处理进行系统调用. 处理缺页异常 缺页异常(page fault,中断向量14)是我们在本实验和往后会大量使用的一个重要例子.当缺页异常发生时,处理器将导致故障的线性地址(虚拟地址)存放在特殊寄存器cr2中.在trap.c中,提供了一个用于缺页异常的函数page_fault_handler() 修改trap_dispatch(),调用page_fault_handler()处理缺页异

MIT 操作系统实验 MIT JOS lab4

MIT JOS lab4 写在前面的碎碎念~ : 经历了LAB 3的洗礼,死磕到了lab 4. 这里还是首先向各位为JOS 实验做过笔记,写过博客,把自己实验代码托管到JOS上面的先行者们致敬! 如果没有这么好的开源环境, 这么好的东西学不来. 珍惜, 不用嘴. Doing is better than saying! -----------------------------------------------------------------------------------------

MIT 操作系统实验 MIT JOS lab3

MIT JOS lab3 Allocating the Environments Array In lab 2, you allocated memory in  mem_init()  for the  pages[]  array, which is a table the kernel uses to keep track of which pages are free and which are not. You will now need to modify  mem_init()  

MIT 操作系统实验 MIT JOS lab2

MIT JOS lab2 在/kern/kdebug.c 里面会看到这段代码,关注下面的__STAB_BEGIN__ 那段代码 __STAB_BEGIN__   __STAB_END__相关定义在/kern/kernel.ld里面 下面我给出了kernel.ld的主要内容 ENTRY(_start) SECTIONS { /* Link the kernel at this address: "." means the current address */ . = 0xF0100000

NS3系列——eclipse + NS3环境搭建

1. 安装NS3 (1)在 ubuntu12.04 中安装 ns3.20,首先要安装一下各种依赖软件: sudo apt-get install gcc g++ pythonsudo apt-get install python-devsudo apt-get install mercurialsudo apt-get install bzrsudo apt-get install gdb valgrindsudo apt-get install gsl-bin libgsl0-dev libg

系统环境变量和用户环境变量

环境变量分为系统环境变量和用户环境变量. 你所说的环境变量是指系统环境变量,对所有用户起作用 而用户环境变量只对当前用户起作用. 例如你要用java,那么你把java的bin目录加入到path变量下面,那么它就是系统环境变量,所用用户登陆,在命令行输入java都会有java的帮助信息出来.而如果你在某个用户的变量下面新建一个变量,那么它就只对这个用户有用,当你以其他用户登陆时这个变量就和不存在一样. 这个问题在linux下面就相对好理解一些,系统变量都在/etc/profile文件里面,而用户的

Center os 用户环境变量

vi ~/.bash_profile进入用户环境变量设置 export JAVA_HOME=/usr/java/jdk1.7.0_76export JAVA_BIN=$JAVA_HOME/binexport PATH=$PATH:$JAVA_HOME/binexport CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jarexport PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH source ~/

【CI】系列二:Ubuntu环境虚拟机安装及配置

好了,做好了初步计划之后,如果可行性没问题,就可以开始实践了. 准备前提:VirtualBox.ubunut镜像 如果没有,可以通过如下地址下载,安装过程此处不做描述. VirtualBox 4.3.12 for Windows hosts:http://download.virtualbox.org/virtualbox/4.3.12/VirtualBox-4.3.12-93733-Win.exe ubuntu-14.04-desktop-amd64.iso:http://mirrors.hu

Bash shell用户环境

介绍 了解Linux的启动顺序,也有助于本内容的理解,加载用户环境是Linux启动的最后一步.不同的shell 会使用不同的配置文件: bash (.profile), ksh (.profile), csh (.login). bash shell 被设计成兼容sh, ksh,  它会在用户主目录依次检查.bash_profile,.bash_login,.profile是否存在,并加载找到的第一个文件.无论文件名字如何,加载用户环境的原理一样,只设定一个配置文件即可.但若有兼容bash,ks