(done) 补充:xv6 的一个用户程序 init 是怎么启动的 ?它如何启动第一个 bash ?
先看 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 allocatorkvminit(); // create kernel page tablekvminithart(); // turn on pagingprocinit(); // process tabletrapinit(); // trap vectorstrapinithart(); // install kernel trap vectorplicinit(); // set up interrupt controllerplicinithart(); // ask PLIC for device interruptsbinit(); // buffer cacheiinit(); // inode tablefileinit(); // file tablevirtio_disk_init(); // emulated hard diskuserinit(); // first user process__sync_synchronize();started = 1;} else {while(started == 0);__sync_synchronize();printf("hart %d starting\n", cpuid());kvminithart(); // turn on pagingtrapinithart(); // install kernel trap vectorplicinithart(); // 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.// 初始化用户程序的栈顶和 pcp->trapframe->epc = 0; // user program counterp->trapframe->sp = PGSIZE; // user stack pointer// 给第一个进程命名safestrcpy(p->name, "initcode", sizeof(p->name));// 名为 "/" 的 inode 是在 mkfs.c 中被创建的p->cwd = namei("/");// 标记该进程状态为 RUNNABLEp->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, initla a1, argvli a7, SYS_exececall# for(;;) exit();
exit:li a7, SYS_exitecalljal 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.Suint64 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 tablep->trapframe->kernel_sp = p->kstack + PGSIZE; // process's kernel stackp->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 modex |= SSTATUS_SPIE; // enable interrupts in user modew_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); // stdoutdup(0); // stderrfor(;;){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 \nif(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 的了。
相关文章:

(done) 补充:xv6 的一个用户程序 init 是怎么启动的 ?它如何启动第一个 bash ?
先看 main.c 从函数名来看,比较相关的就 userinit() 和 scheduler() #include "types.h" #include "param.h" #include "memlayout.h" #include "riscv.h" #include "defs.h"volatile static int started 0;//…...
Nginx部署前端项目深度解析
在部署Vue前端项目时,Nginx的高效配置直接影响用户体验和性能表现。以下从7个关键维度深度解析部署方案,并提供专业级配置策略: 一、项目构建与基础部署 生产构建 npm run build -- --modern # 现代模式构建生成dist/目录包含:…...

超详细讲解C语言转义字符\a \b \r \t \? \n等等
转义字符 C语言有一组字符很特殊,叫做转义字符,顾名思义,改变原来的意思的字符。 1 \? ??)是一个三字母词,在以前的编译器它会被编译为] (??会被编译为[ 因此在以前输入(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、技术栈 本项目采用前后端分离的架构,前端和后…...
【Qt/C++】深入理解 Lambda 表达式与 `mutable` 关键字的使用
【Qt/C】深入理解 Lambda 表达式与 mutable 关键字的使用 在 Qt 开发中,我们常常会用到 lambda 表达式来编写简洁的槽函数。今天通过一个实际代码示例,详细讲解 lambda 的语法、变量捕获方式,特别是 mutable 的作用。 示例代码 QPushButto…...
扩展:React 项目执行 yarn eject 后的 package.json 变化详解及参数解析
扩展:React 项目执行 yarn eject 后的 package.json 变化详解及参数解析 什么是 yarn eject?React 项目执行 yarn eject 后的 package.json 变化详解1. 脚本部分 Scripts 被替换2. 新增构建依赖 dependencies(部分)3. 新增 Babel …...
slackel系统详解
Slackel 是一个基于 Slackware Linux 和 Salix OS(另一个 Slackware 衍生版)的轻量级 Linux 发行版,主要面向桌面用户。它由希腊开发者 Dimitris Tzemos 创建,目标是结合 Slackware 的稳定性与用户友好的工具,同时优化…...

rust 全栈应用框架dioxus server
接上一篇文章dioxus全栈应用框架的基本使用,支持web、desktop、mobile等平台。 可以先查看上一篇文章rust 全栈应用框架dioxus👈 既然是全栈框架,那肯定是得有后端服务的,之前创建的服务没有包含后端服务包,我们修改…...
CSS Layer 详解
CSS Layer 详解 前言 最近在整理CSS知识体系时,发现Layer这个特性特别有意思。它就像是给样式规则提供了一个专属的「VIP通道」,让我们能更优雅地解决样式冲突问题。今天我就用最通俗的语言,带大家全面了解这个CSS新特性。 什么是CSS Laye…...

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

数据结构(三)——栈和队列
一、栈和队列的定义和特点 栈:受约束的线性表,只允许栈顶元素入栈和出栈 对栈来说,表尾端称为栈顶,表头端称为栈底,不含元素的空表称为空栈 先进后出,后进先出 队列:受约束的线性表࿰…...

若依定制pdf生成实战
一、介绍 使用 Java Apache POI 将文字渲染到 Word 模板是一种常见的文档自动化技术,广泛应用于批量生成或定制 Word 文档的场景。使用aspose可以将word转成pdf从而达到定制化pdf的目的。 参考文档:java实现Word转Pdf(Windows、Linux通用&a…...
RCE联系
过滤 绕过空格 ● 进制绕过 题目练习 数字rce 使用$0执行bash,<<<将后面的字符串传递给左边的命令。 例如: <?php highlight_file(__FILE__); function waf($cmd) { $whiteList [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, \\, \, $, <]; $cmd_ch…...

c++STL-vector的模拟实现
cSTL-vector的模拟实现 vector的模拟实现基本信息构造函数析构函数返回容量(capacity)返回元素个数(size)扩容(reserve和resize)访问([])迭代器(**iterator**)…...
C++核心编程解析:模板、容器与异常处理全指南
文章目录 一、模板1.1 定义1.2 作用1.3 函数模版1.3.1 格式 1.4 类模版1.4.1 格式1.4.2 代码示例1.4.3 特性 二、容器2.1 概念2.2 容器特性2.3 分类2.4 向量vector2.4.1 特性2.4.2 初始化与操作2.4.3 插入删除 2.5 迭代器2.6 列表(list)2.6.1 遍历方式2.…...

在 Elasticsearch 中连接两个索引
作者:来自 Elastic Kofi Bartlett 解释如何使用 terms query 和 enrich processor 来连接 Elasticsearch 中的两个索引。 更多有关连接两个索引的查询,请参阅文章 “Elastic:开发者上手指南” 中的 “丰富数据及 lookup” 章节。 Elasticsea…...
Linux常用命令详解(下):打包压缩、文本编辑与查找命令
一、打包压缩命令 在Linux系统中,打包与压缩是文件管理的核心操作之一。不同的工具适用于不同场景,以下是最常用的命令详解: 1. tar命令 作用:对文件进行打包、解包、压缩、解压。 语法: tar [选项] [压缩包名] […...

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

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

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

【MCP】其他MCP服务((GitHub)
【MCP】其他MCP服务((GitHub) 1、其他MCP服务(GitHub) MCP广场:https://www.modelscope.cn/mcp 1、其他MCP服务(GitHub) 打开MCP广场 找到github服务 访问github生成令牌 先…...
SQL:MySQL函数:日期函数(Date Functions)
目录 时间是数据的一种类型 🧰 MySQL 常用时间函数大全 🟦 1. 获取当前时间/日期 🟦 2. 日期运算(加减) 🟦 3. 时间差计算 🟦 4. 格式化日期 🟦 5. 提取时间部分 Ƿ…...

内存 -- Linux内核内存分配机制
内存可以怎么用? kmalloc:内核最常用,用于频繁使用的小内存申请 alloc_pages:以页框为单位申请,物理内存连续 vmalloc:虚拟地址连续的内存块,物理地址不连线 dma_alloc_coherent:常…...

关于读写锁的一些理解
同一线程的两种情况: 读读: 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大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路! 我的博客:<但凡. 我的专栏:《编程之路》、《数据结构与算法之美》、《题海拾贝》、《C修炼之路》 欢迎点赞,关注&am…...

android-ndk开发(10): use of undeclared identifier ‘pthread_getname_np‘
1. 报错描述 使用 pthread 获取线程名字, 用到 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 模式+数据驱动
🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 1. PO 设计模式简介 什么是 PO 模式? PO(PageObject)设计模式将某个页面的所有元素对象定位和对元素对象的操作封装成…...
大小端的判断方法
大小端(Endianness) 是计算机存储多字节数据(如整数、浮点数)时的两种不同方式,决定了字节在内存中的排列顺序。 1. 大端(Big-Endian) 高位字节存储在低地址,低位字节存储在高地址。…...

Java笔记4
第一章 static关键字 2.1 概述 以前我们定义过如下类: public class Student {// 成员变量public String name;public char sex; // 男 女public int age;// 无参数构造方法public Student() {}// 有参数构造方法public Student(String a) {} }我们已经知道面向…...
DAY22kaggle泰坦尼克号
参考了机器学习实战进阶:泰坦尼克号乘客获救预测_天池notebook-阿里云天池 数据处理省略 直接上模型 5.12.1 一些数据的正则化 这里我们将Age和fare进行正则化: from sklearn import preprocessing scale_age_fare preprocessing.StandardScaler().…...