(done) 补充:xv6 的一个用户程序 init 是怎么启动的 ?它如何启动第一个 bash ?

news2025/5/13 9:29:47

先看 main.c
从函数名来看,比较相关的就 userinit() 和 scheduler()

#include "types.h"
#include "param.h"
#include "memlayout.h"
#include "riscv.h"
#include "defs.h"

volatile static int started = 0;

// start() jumps here in supervisor mode on all CPUs.
void
main()
{
  if(cpuid() == 0){
    consoleinit();
    printfinit();
    printf("\n");
    printf("xv6 kernel is booting\n");
    printf("\n");
    kinit();         // physical page allocator
    kvminit();       // create kernel page table
    kvminithart();   // turn on paging
    procinit();      // process table
    trapinit();      // trap vectors
    trapinithart();  // install kernel trap vector
    plicinit();      // set up interrupt controller
    plicinithart();  // ask PLIC for device interrupts
    binit();         // buffer cache
    iinit();         // inode table
    fileinit();      // file table
    virtio_disk_init(); // emulated hard disk
    userinit();      // first user process
    __sync_synchronize();
    started = 1;
  } else {
    while(started == 0)
      ;
    __sync_synchronize();
    printf("hart %d starting\n", cpuid());
    kvminithart();    // turn on paging
    trapinithart();   // install kernel trap vector
    plicinithart();   // ask PLIC for device interrupts
  }

  scheduler();        
}

看 userinit
1.使用 allocproc() 分配一个进程 (分配内核栈,以及 scheduler 调度后跳转到的地址 – forkret)
2.使用 uvmfirst 为 proc 映射一个内存页,该内存页储存 initcode (initcode.S 的汇编结果)。映射到用户空间虚拟地址 0 上
3.设置进程当前目录为 根目录 “/” (该目录在 mkfs.c 中创建)
4.设置进程状态为 RUNNABLE,方便后续调度

// a user program that calls exec("/init")
// assembled from ../user/initcode.S
// od -t xC ../user/initcode
// 汇编自 initcode.S 的二进制指令
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
};

// Load the user initcode into address 0 of pagetable,
// for the very first process.
// sz must be less than a page.
// 分配一页内存,映射到页表里,再把 src 的内容拷贝进那页内存
void
uvmfirst(pagetable_t pagetable, uchar *src, uint sz)
{
  char *mem;

  if(sz >= PGSIZE)
    panic("uvmfirst: more than a page");
  mem = kalloc();
  memset(mem, 0, PGSIZE);
  mappages(pagetable, 0, PGSIZE, (uint64)mem, PTE_W|PTE_R|PTE_X|PTE_U);
  memmove(mem, src, sz);
}

// Set up first user process.
void
userinit(void)
{
  struct proc *p;

  p = allocproc();
  initproc = p;
  
  // allocate one user page and copy initcode's instructions
  // and data into it.
  // 分配一页内存,映射到页表里,再把 initCode 的内容拷贝进那页内存
  uvmfirst(p->pagetable, initcode, sizeof(initcode));
  p->sz = PGSIZE;

  // prepare for the very first "return" from kernel to user.
  // 初始化用户程序的栈顶和 pc
  p->trapframe->epc = 0;      // user program counter
  p->trapframe->sp = PGSIZE;  // user stack pointer

  // 给第一个进程命名
  safestrcpy(p->name, "initcode", sizeof(p->name));
  // 名为 "/" 的 inode 是在 mkfs.c 中被创建的
  p->cwd = namei("/");

  // 标记该进程状态为 RUNNABLE
  p->state = RUNNABLE;

  release(&p->lock);
}

看 initcode.S
从汇编源码来看,就做两个事情:
1.调用系统调用 exec(“/init”, “/init”, NULL)
2.调用系统调用 exit(0)

# Initial process that execs /init.
# This code runs in user space.

#include "syscall.h"

# exec(init, argv)
.globl start
start:
        la a0, init
        la a1, argv
        li a7, SYS_exec
        ecall

# for(;;) exit();
exit:
        li a7, SYS_exit
        ecall
        jal exit

# char init[] = "/init\0";
init:
  .string "/init\0"

# char *argv[] = { init, 0 };
.p2align 2
argv:
  .long init
  .long 0

接下来看 scheduler
1.开中断
2.查找 RUNNABLE process
3.上下文切换过去

void
scheduler(void)
{
  struct proc *p;
  struct cpu *c = mycpu();

  c->proc = 0;
  for(;;){
    // The most recent process to run may have had interrupts
    // turned off; enable them to avoid a deadlock if all
    // processes are waiting.
    intr_on();

    for(p = proc; p < &proc[NPROC]; p++) {
      acquire(&p->lock);
      if(p->state == RUNNABLE) {
        // Switch to chosen process.  It is the process's job
        // to release its lock and then reacquire it
        // before jumping back to us.
        p->state = RUNNING;
        c->proc = p;
        swtch(&c->context, &p->context);

        // Process is done running for now.
        // It should have changed its p->state before coming back.
        c->proc = 0;
      }
      release(&p->lock);
    }
  }
}

根据 allocproc() 的代码,我们知道所有内核进程一开始跳转到的函数是 forkret,来看看 forkret
如果 forkret 是被第一次调用的,那么它会执行 fsinit(ROOTDEV) 初始化内核的文件系统。否则,调用 usertrapret

// A fork child's very first scheduling by scheduler()
// will swtch to forkret.
void
forkret(void)
{
  static int first = 1;

  // Still holding p->lock from scheduler.
  release(&myproc()->lock);

  if (first) {
    // File system initialization must be run in the context of a
    // regular process (e.g., because it calls sleep), and thus cannot
    // be run from main().
    fsinit(ROOTDEV);

    first = 0;
    // ensure other cores see first=0.
    __sync_synchronize();
  }

  usertrapret();
}

看 usertrapret
这里会做一系列设置,但最终会使用

  uint64 trampoline_userret = TRAMPOLINE + (userret - trampoline);
  ((void (*)(uint64))trampoline_userret)(satp);

这种方式跳转到 trampoline.S 汇编代码里,这个代码最后一个指令是 sret,也就是跳转到用户态,同时 PC 指向之前在 userinit() 中设置的 0x0

//
// return to user space
//
void
usertrapret(void)
{
  struct proc *p = myproc();

  // we're about to switch the destination of traps from
  // kerneltrap() to usertrap(), so turn off interrupts until
  // we're back in user space, where usertrap() is correct.
  intr_off();

  // send syscalls, interrupts, and exceptions to uservec in trampoline.S
  uint64 trampoline_uservec = TRAMPOLINE + (uservec - trampoline);
  w_stvec(trampoline_uservec);

  // set up trapframe values that uservec will need when
  // the process next traps into the kernel.
  p->trapframe->kernel_satp = r_satp();         // kernel page table
  p->trapframe->kernel_sp = p->kstack + PGSIZE; // process's kernel stack
  p->trapframe->kernel_trap = (uint64)usertrap;
  p->trapframe->kernel_hartid = r_tp();         // hartid for cpuid()

  // set up the registers that trampoline.S's sret will use
  // to get to user space.
  
  // set S Previous Privilege mode to User.
  unsigned long x = r_sstatus();
  x &= ~SSTATUS_SPP; // clear SPP to 0 for user mode
  x |= SSTATUS_SPIE; // enable interrupts in user mode
  w_sstatus(x);

  // set S Exception Program Counter to the saved user pc.
  w_sepc(p->trapframe->epc);

  // tell trampoline.S the user page table to switch to.
  uint64 satp = MAKE_SATP(p->pagetable);

  // jump to userret in trampoline.S at the top of memory, which 
  // switches to the user page table, restores user registers,
  // and switches to user mode with sret.
  uint64 trampoline_userret = TRAMPOLINE + (userret - trampoline);
  ((void (*)(uint64))trampoline_userret)(satp);
}

最后回顾 initcode.S,它调用了系统调用 exec(/init, /init, NULL)。我们来看看文件 /init 是啥。
在 Makefile 中,可以看到 _init,说明它以文件形式存在于 xv6 文件系统里
在这里插入图片描述

看 user/init.c
从代码来看,它打开 console,随后调用 fork 来启动 sh 程序,接着等待 sh 程序退出,若 sh 程序退出,则再次开启一个新的 sh

// init: The initial user-level program

#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/spinlock.h"
#include "kernel/sleeplock.h"
#include "kernel/fs.h"
#include "kernel/file.h"
#include "user/user.h"
#include "kernel/fcntl.h"

char *argv[] = { "sh", 0 };

int
main(void)
{
  int pid, wpid;

  if(open("console", O_RDWR) < 0){
    mknod("console", CONSOLE, 0);
    open("console", O_RDWR);
  }
  dup(0);  // stdout
  dup(0);  // stderr

  for(;;){
    printf("init: starting sh\n");
    pid = fork();
    if(pid < 0){
      printf("init: fork failed\n");
      exit(1);
    }
    if(pid == 0){
      exec("sh", argv);
      printf("init: exec sh failed\n");
      exit(1);
    }

    for(;;){
      // this call to wait() returns if the shell exits,
      // or if a parentless process exits.
      wpid = wait((int *) 0);
      if(wpid == pid){
        // the shell exited; restart it.
        break;
      } else if(wpid < 0){
        printf("init: wait returned an error\n");
        exit(1);
      } else {
        // it was a parentless process; do nothing.
      }
    }
  }
}

再看 sh.c
sh.c 的代码复杂很多,但核心代码就下面几行,循环读取用户输入的命令,随后调用 fork + exec 去执行 (runcmd 是基于 exec 实现的)

  // Read and run input commands.
  while(getcmd(buf, sizeof(buf)) >= 0){
    if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){
      // Chdir must be called by the parent, not the child.
      buf[strlen(buf)-1] = 0;  // chop \n
      if(chdir(buf+3) < 0)
        fprintf(2, "cannot cd %s\n", buf+3);
      continue;
    }
    if(fork1() == 0)
      runcmd(parsecmd(buf));
    wait(0);
  }

到这里,就知道 xv6 第一个程序是 init.c,以及它是怎么启动第一个 bash 的了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2374552.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

超详细讲解C语言转义字符\a \b \r \t \? \n等等

转义字符 C语言有一组字符很特殊&#xff0c;叫做转义字符&#xff0c;顾名思义&#xff0c;改变原来的意思的字符。 1 \? ??)是一个三字母词&#xff0c;在以前的编译器它会被编译为] (??会被编译为[ 因此在以前输入(are you ok ??)就会被编译为are you ok ] 解决这个…

SpringBoot校园失物招领信息平台

SpringBoot校园失物招领信息平台 文章目录 SpringBoot校园失物招领信息平台1、技术栈2、项目说明2.1、登录注册2.2、管理员端截图2.3、用户端截图 3、核心代码实现3.1、前端首页3.2、前端招领广场3.3、后端业务处理 1、技术栈 本项目采用前后端分离的架构&#xff0c;前端和后…

rust 全栈应用框架dioxus server

接上一篇文章dioxus全栈应用框架的基本使用&#xff0c;支持web、desktop、mobile等平台。 可以先查看上一篇文章rust 全栈应用框架dioxus&#x1f448; 既然是全栈框架&#xff0c;那肯定是得有后端服务的&#xff0c;之前创建的服务没有包含后端服务包&#xff0c;我们修改…

西安交大多校联训NOIP1模拟赛题解

西安交大多校联训NOIP1模拟赛题解 T1 秘境形式化题意思路代码&#xff08;丑陋&#xff09; T2 礼物形式化题意思路代码&#xff08;实现&#xff09; T3 小盒子的数论形式化题意思路代码&#xff08;分讨&#xff09; T4 猫猫贴贴(CF997E)形式化题意思路代码&#xff08;深奥&…

数据结构(三)——栈和队列

一、栈和队列的定义和特点 栈&#xff1a;受约束的线性表&#xff0c;只允许栈顶元素入栈和出栈 对栈来说&#xff0c;表尾端称为栈顶&#xff0c;表头端称为栈底&#xff0c;不含元素的空表称为空栈 先进后出&#xff0c;后进先出 队列&#xff1a;受约束的线性表&#xff0…

若依定制pdf生成实战

一、介绍 使用 Java Apache POI 将文字渲染到 Word 模板是一种常见的文档自动化技术&#xff0c;广泛应用于批量生成或定制 Word 文档的场景。使用aspose可以将word转成pdf从而达到定制化pdf的目的。 参考文档&#xff1a;java实现Word转Pdf&#xff08;Windows、Linux通用&a…

c++STL-vector的模拟实现

cSTL-vector的模拟实现 vector的模拟实现基本信息构造函数析构函数返回容量&#xff08;capacity&#xff09;返回元素个数&#xff08;size&#xff09;扩容&#xff08;reserve和resize&#xff09;访问&#xff08;[]&#xff09;迭代器&#xff08;**iterator**&#xff09…

在 Elasticsearch 中连接两个索引

作者&#xff1a;来自 Elastic Kofi Bartlett 解释如何使用 terms query 和 enrich processor 来连接 Elasticsearch 中的两个索引。 更多有关连接两个索引的查询&#xff0c;请参阅文章 “Elastic&#xff1a;开发者上手指南” 中的 “丰富数据及 lookup” 章节。 Elasticsea…

使用 Watt toolkit 加速 git clone

一、前言 Watt toolkit 工具是我经常用于加速 GitHub 网页和 Steam 游戏商店访问的工具&#xff0c;最近想加速 git clone&#xff0c;发现可以使用 Watt toolkit 工具的代理实现。 二、查看端口 我这里以 Ubuntu 为例&#xff0c;首先是需要将加速模式设置为 System&#xff1…

应急响应靶机——WhereIS?

用户名及密码&#xff1a;zgsf/zgsf 下载资源还有个解题.exe: 1、攻击者的两个ip地址 2、flag1和flag2 3、后门程序进程名称 4、攻击者的提权方式(输入程序名称即可) 之前的命令&#xff1a; 1、攻击者的两个ip地址 先获得root权限&#xff0c;查看一下历史命令记录&#x…

Docke容器下JAVA系统时间与Linux服务器时间不一致问题解决办法

本篇文章主要讲解&#xff0c;通过docker部署jar包运行环境后出现java系统内时间与服务器、个人电脑真实时间不一致的问题原因及解决办法。 作者&#xff1a;任聪聪 日期&#xff1a;2025年5月12日 问题现象&#xff1a; 说明&#xff1a;与实际时间不符&#xff0c;同时与服务…

【MCP】其他MCP服务((GitHub)

【MCP】其他MCP服务&#xff08;&#xff08;GitHub&#xff09; 1、其他MCP服务&#xff08;GitHub&#xff09; MCP广场&#xff1a;https://www.modelscope.cn/mcp 1、其他MCP服务&#xff08;GitHub&#xff09; 打开MCP广场 找到github服务 访问github生成令牌 先…

内存 -- Linux内核内存分配机制

内存可以怎么用&#xff1f; kmalloc&#xff1a;内核最常用&#xff0c;用于频繁使用的小内存申请 alloc_pages&#xff1a;以页框为单位申请&#xff0c;物理内存连续 vmalloc&#xff1a;虚拟地址连续的内存块&#xff0c;物理地址不连线 dma_alloc_coherent&#xff1a;常…

关于读写锁的一些理解

同一线程的两种情况&#xff1a; 读读&#xff1a; public static void main(String[] args) throws InterruptedException {ReentrantReadWriteLock lock new ReentrantReadWriteLock();Lock readLock lock.readLock();Lock writeLock lock.writeLock();readLock.lock();S…

C++修炼:模板进阶

Hello大家好&#xff01;很高兴我们又见面啦&#xff01;给生活添点passion&#xff0c;开始今天的编程之路&#xff01; 我的博客&#xff1a;<但凡. 我的专栏&#xff1a;《编程之路》、《数据结构与算法之美》、《题海拾贝》、《C修炼之路》 欢迎点赞&#xff0c;关注&am…

android-ndk开发(10): use of undeclared identifier ‘pthread_getname_np‘

1. 报错描述 使用 pthread 获取线程名字&#xff0c; 用到 pthread_getname_np 函数。 交叉编译到 Android NDK 时链接报错 test_pthread.cpp:19:5: error: use of undeclared identifier pthread_getname_np19 | pthread_getname_np(thread_id, thread_name, sizeof(thr…

UI自动化测试框架:PO 模式+数据驱动

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1. PO 设计模式简介 什么是 PO 模式&#xff1f; PO&#xff08;PageObject&#xff09;设计模式将某个页面的所有元素对象定位和对元素对象的操作封装成…

Java笔记4

第一章 static关键字 2.1 概述 以前我们定义过如下类&#xff1a; public class Student {// 成员变量public String name;public char sex; // 男 女public int age;// 无参数构造方法public Student() {}// 有参数构造方法public Student(String a) {} }我们已经知道面向…

2025年渗透测试面试题总结-渗透测试红队面试八(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 渗透测试红队面试八 二百一十一、常见中间件解析漏洞利用方式 二百一十二、MySQL用户密码存储与加密 …

MiniMind:3块钱成本 + 2小时!训练自己的0.02B的大模型。minimind源码解读、MOE架构

大家好&#xff0c;我是此林。 目录 1. 前言 2. minimind模型源码解读 1. MiniMind Config部分 1.1. 基础参数 1.2. MOE配置 2. MiniMind Model 部分 2.1. MiniMindForCausalLM: 用于语言建模任务 2.2. 主干模型 MiniMindModel 2.3. MiniMindBlock: 模型的基本构建块…