xv6源码剖析 008

xv6源码剖析 008

其实我发现顺序好像错了,应该先更新kalloc.c的,因为进程也是来自于内核的,但是更都更了,先写完proc.hproc.c先。

直接开始。

全局变量

struct cpu cpus[NCPU];

struct proc proc[NPROC];

struct proc *initproc;

int nextpid = 1;
struct spinlock pid_lock;

procinit(void)

这个函数是在内核的main函数中调用的,用来初始化进程表(process table)。

这里补充一点,proc::lock是进程锁,上一期因为着急追剧,所以没有看到这个属性在最上面。

void
procinit(void)
{
  struct proc *p;
  
  initlock(&pid_lock, "nextpid");
  for(p = proc; p < &proc[NPROC]; p++) {
      // 初始化进程的进程锁
      initlock(&p->lock, "proc");

      // Allocate a page for the process's kernel stack.
      // Map it high in memory, followed by an invalid
      // guard page.
      // 每个进程的内核栈,
      // xv6的进程的内核栈是有固定的物理地址的。
      // 能够通过内核代码提供的一个宏函数算出来
      char *pa = kalloc();
      if(pa == 0)
        panic("kalloc");
      uint64 va = KSTACK((int) (p - proc));
      kvmmap(va, (uint64)pa, PGSIZE, PTE_R | PTE_W);
      p->kstack = va;
  }
  // 这个函数在vm.c中,用来设置寄存器stap值
  // stap -> kernelpage
  // 并且允许分页
  kvminithart();
}

cpuid()

这个函数用来获取当前cpu的编号,这个函数通过调用一个内联汇编函数来从寄存器 tp 中获取对应的值。因为每个cpu都有自己的一套寄存器,所以不同的cpu调用这个函数获取到的值是不一样的。

int
cpuid()
{
  int id = r_tp();
  return id;
}

struct cpu* mycpu(void)

获取对cpu进行抽象后的一个结构体,struct cpu

struct cpu*
mycpu(void) {
  int id = cpuid();
  struct cpu *c = &cpus[id];
  return c;
}

struct proc* myproc(void)

获取当前的进程的结构体

struct proc*
myproc(void) {
  push_off();
  struct cpu *c = mycpu();
  struct proc *p = c->proc;
  pop_off();
  return p;
}

pop_off()是定义在spinlock.c中函数,与之对应的是push_off(),两者都是通过调用内联的汇编函数来打开或者禁止设备中断(device interrupts),因为xv6在自旋锁创建的临界区之间是不允许中断的,因为这有可能会产生死锁;在小书中说到,xv6坚持的是从简的原则,所以直接一棒子打死了;而睡眠锁(sleep lock)则允许进程或线程在睡眠的时候获取锁(具体细节我忘了,到时候再看看)。

int allocpid()

获取一个进程id

int
allocpid() {
  int pid;
  
  acquire(&pid_lock);
  pid = nextpid;
  nextpid = nextpid + 1;
  release(&pid_lock);

  return pid;
}

下面这个函数很重要,我们在页表实验的时候也会涉及到

static struct proc* allocproc(void)

简述一下作用:从进程表中找到一个处于UNUSED状态的进程,并重用该进程。

static struct proc*
allocproc(void)
{
  struct proc *p;
    
  // 遍历进程表,并试图找到一个UNUSED进程
  for(p = proc; p < &proc[NPROC]; p++) {
	// 获取进程锁,互斥访问
    acquire(&p->lock);
    if(p->state == UNUSED) {
      // 如果找到了UNUSED进程
      goto found;
    } else {
      // 释放进程锁
      release(&p->lock);
    }
  }
  return 0;

found:
  // 分配一个进程id
  p->pid = allocpid();
    
  // 获取一个trapframe page
  // 这个page在我们进行trap的时候发挥着关键的作用
  // Allocate a trapframe page.
  if((p->trapframe = (struct trapframe *)kalloc()) == 0){
    release(&p->lock);
    return 0;
  }

  // An empty user page table.
  // 获取一个空的用户页表
  p->pagetable = proc_pagetable(p);
  if(p->pagetable == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }

  // Set up new context to start executing at forkret,
  // which returns to user space.
  // 设置一个新的上下文
  memset(&p->context, 0, sizeof(p->context));
  // 一般需要产生新进程都是由于fork系统调用
  // 所以设置返回地址返回到调用fork的上下文
  p->context.ra = (uint64)forkret;
  // 将sp寄存器指向栈底,因为栈是从
  // 高地址向低地址生长的
  p->context.sp = p->kstack + PGSIZE;

  return p;
}

static void freeproc(struct proc *p)

回收一个进程

流程大概是:清空trapframe page 的映射,因为这个一个共享的页,如果释放了,内核会崩掉。设置进程的状态为UNUSED,将进程的其他的标志也清空。

static void
freeproc(struct proc *p)
{
  if(p->trapframe)
    kfree((void*)p->trapframe);
  p->trapframe = 0;
  if(p->pagetable)
    proc_freepagetable(p->pagetable, p->sz);
  p->pagetable = 0;
  p->sz = 0;
  p->pid = 0;
  p->parent = 0;
  p->name[0] = 0;
  p->chan = 0;
  p->killed = 0;
  p->xstate = 0;
  p->state = UNUSED;
}

pagetable_t proc_pagetable(struct proc *p)

获取一个进程页表。

pagetable_t
proc_pagetable(struct proc *p)
{
  pagetable_t pagetable;

  // 获取一个空页表
  pagetable = uvmcreate();
  if(pagetable == 0)
    return 0;

  // 创建tramframe 和 trampoline 的映射
  if(mappages(pagetable, TRAMPOLINE, PGSIZE,
              (uint64)trampoline, PTE_R | PTE_X) < 0){
    uvmfree(pagetable, 0);
    return 0;
  }

  // map the trapframe just below TRAMPOLINE, for trampoline.S.
  if(mappages(pagetable, TRAPFRAME, PGSIZE,
              (uint64)(p->trapframe), PTE_R | PTE_W) < 0){
    uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    uvmfree(pagetable, 0);
    return 0;
  }

  return pagetable;
}

void proc_freepagetable(pagetable_t pagetbale, uint64 sz)

清空一个页表的映射资源

void
proc_freepagetable(pagetable_t pagetable, uint64 sz)
{
  uvmunmap(pagetable, TRAMPOLINE, 1, 0);
  uvmunmap(pagetable, TRAPFRAME, 1, 0);
  uvmfree(pagetable, sz);
}

下面这个有点抽象,是一段机器码,我看不懂。所以把注释附上去了,哈哈

// a user program that calls exec("/init")
// od -t xC initcode
uchar initcode[] = {
  0x17, 0x05, 0x00, 0x00, 0x13, 0x05, 0x45, 0x02,
  0x97, 0x05, 0x00, 0x00, 0x93, 0x85, 0x35, 0x02,
  0x93, 0x08, 0x70, 0x00, 0x73, 0x00, 0x00, 0x00,
  0x93, 0x08, 0x20, 0x00, 0x73, 0x00, 0x00, 0x00,
  0xef, 0xf0, 0x9f, 0xff, 0x2f, 0x69, 0x6e, 0x69,
  0x74, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00
};

void userinit(void)

这个函数跟本篇的第一个函数procinit一样,也是在内核的main函数中调用的,它初始化内核的第一个用户进程,这个进程作为这个操作系统进程树(process tree)的根节点(root)。

这个进程的主要的作用是:管理孤儿进程。什么是孤儿进程呢?我们直接看看gpt的回答

操作系统处理孤儿进程的方式如下:

  1. 识别孤儿进程: 当一个进程(父进程)非正常终止(如崩溃、被杀死或正常退出但未正确清理子进程)时,其任何仍在运行的子进程将变为孤儿进程。操作系统通过监测进程间关系来识别这种情况:当一个进程终止时,系统检查其是否有尚未终止的子进程。
  2. 收养孤儿进程: 一旦检测到孤儿进程,操作系统会立即采取行动,通常由一个特殊进程——init进程(进程ID为1,是所有进程的始祖)来“收养”这些孤儿进程。init进程接管孤儿进程,成为它们新的父进程。这意味着孤儿进程的父进程PID(PPID)会被更新为1。
  3. 资源回收: 对于孤儿进程而言,虽然它们失去了原始父进程,但由于被init进程及时收养,它们不会像僵尸进程那样长时间停留在系统中。init进程是一个设计上能够正确管理子进程生命周期的进程,它会定期调用wait()waitpid()系统调用来等待其子进程(包括收养的孤儿进程)结束,并收集它们的退出状态信息。这样,孤儿进程在终止后能够立即释放其在内核中占用的资源(如进程控制块、打开的文件描述符等),不会造成资源泄漏。
  4. 确保孤儿进程继续运行或正常终止: 收养过程不影响孤儿进程自身的运行状态。孤儿进程将继续执行直到其自然结束,或者接收到终止信号。由于已经成为init进程的子进程,孤儿进程现在遵循init进程的信号处理规则,确保即使原父进程未能正确处理子进程的退出,孤儿进程也能得到妥善处理。

总结来说,操作系统通过快速识别孤儿进程、将其交给init进程收养、确保资源及时回收以及维持孤儿进程的正常运行或终止流程,有效地管理孤儿进程,避免了因父进程异常导致的子进程管理问题和系统资源浪费。这样,孤儿进程虽然失去了原始父进程,但其生命周期得到了完整且有效的管理,不会对系统稳定性和资源利用率造成负面影响。

void
userinit(void)
{
  struct proc *p;

  p = allocproc();
  initproc = p;
  
  // allocate one user page and copy init's instructions
  // and data into it.
  uvminit(p->pagetable, initcode, sizeof(initcode));
  p->sz = PGSIZE;

  // prepare for the very first "return" from kernel to user.
  p->trapframe->epc = 0;      // user program counter
  p->trapframe->sp = PGSIZE;  // user stack pointer

  safestrcpy(p->name, "initcode", sizeof(p->name));
  p->cwd = namei("/");

  p->state = RUNNABLE;

  release(&p->lock);
}

函数的实现也是比较简单,但是确实比较有学问在里面。

今天就先到这了,晚安玛卡巴卡。

  • 22
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值