MIT 6.S081 (BOOK-RISCV-REV1)教材第二章内容

news2024/10/6 6:03:43

MIT 6.S081 第二章内容

  • 引言
  • 操作系统架构
    • 抽象系统资源
    • 用户态,核心态,以及系统调用
    • 内核组织
    • 代码(XV6架构篇)
    • 进程概述
    • 代码(启动XV6和第一个进程)
    • 真实世界


引言

MIT 6.S081 2020 操作系统

本文为MIT 6.S081课程第二章教材内容翻译加整理。

本课程前置知识主要涉及:

  • C语言(建议阅读C程序语言设计—第二版)
  • RISC-V汇编
  • 推荐阅读: 程序员的自我修养-装载,链接与库

操作系统架构

操作系统的一个关键要求是同时支持多个活动。

  • 例如,使用第1章中描述的系统调用接口,一个进程可以用fork启动新进程。

操作系统必须在这些进程之间分时使用计算机资源。

  • 例如,即使进程比硬件处理器多,操作系统也必须确保所有进程都有机会执行。

操作系统还必须安排进程之间的隔离。

  • 也就是说,如果一个进程有错误和故障,它不应该影响其他进程。

然而,完全隔离又太过头了,进程之间应当可以进行刻意为之的交互;

  • 管道就是一个例子。

因此,操作系统必须满足三个要求:多路复用、隔离和交互。

本章概述了如何组织操作系统来实现这三个要求。事实证明,有很多方法可以做到这一点,但是本文侧重于以宏内核为中心的主流设计,许多Unix操作系统都使用这种内核。本章还概述了xv6进程(它是xv6中的隔离单元)以及xv6启动时第一个进程的创建。

Xv6运行在多核RISC-V微处理器上,它的许多低级功能(例如,它的进程实现)是特定于RISC-V的。

  • RISC-V是一个64位的中央处理器,xv6是用基于“LP64”的C语言编写的,这意味着C语言中的long(L)和指针(P)变量都是64位的,但int是32位的。
  • 这本书假设读者已经在一些架构上做了一些机器级编程,并将在出现时介绍RISC-V特定的想法。
  • RISC-V的一个有用的参考文献是《The RISC-V Reader: An Open Architecture Atlas》。用户级ISA和特权指令架构均是官方规范。

完整计算机中的CPU被支撑硬件包围,其中大部分是以I/O接口的形式。Xv6是以qemu的“-machine virt”选项模拟的支撑硬件编写的。这包括RAM、包含引导代码的ROM、一个到用户键盘/屏幕的串行连接,以及一个用于存储的磁盘。


抽象系统资源

当谈及操作系统时,人们可能会问的第一个问题是为什么需要它?

也就是说,我们可以将系统调用实现为一个库,应用程序可以与之链接。

  • 在此方案中,每个应用程序甚至可以根据自己的需求定制自己的库。
  • 应用程序可以直接与硬件资源交互,并以应用程序的最佳方式使用这些资源(例如,实现高性能或可预测的性能)。
  • 一些嵌入式设备或实时系统的操作系统就是这样组织的。

这种库函数方法的缺点是,如果有多个应用程序在运行,这些应用程序必须表现良好。

  • 例如,每个应用程序必须定期放弃中央处理器,以便其他应用程序能够运行。
  • 如果所有应用程序都相互信任并且没有错误,这种协同操作的分时方案可能是可以的。
  • 然而更典型的情况是, 应用程序互不信任且存在bug,所以人们通常希望提供比合作方案更强的隔离。

为了实现强隔离, 最好禁止应用程序直接访问敏感的硬件资源,而是将资源抽象为服务。

  • 例如,Unix应用程序只通过文件系统的open、read、write和close系统调用与存储交互,而不是直接读写磁盘。
  • 这为应用程序提供了方便实用的路径名,并允许操作系统(作为接口的实现者)管理磁盘。
  • 即使隔离不是一个问题,有意交互(或者只是希望互不干扰)的程序可能会发现文件系统比直接使用磁盘更方便。

同样,Unix在进程之间透明地切换硬件处理器,根据需要保存和恢复寄存器状态,这样应用程序就不必意识到分时共享的存在。这种透明性允许操作系统共享处理器,即使有些应用程序处于无限循环中。

另一个例子是,Unix进程使用exec来构建它们的内存映像,而不是直接与物理内存交互。

  • 这允许操作系统决定将一个进程放在内存中的哪里;如果内存很紧张,操作系统甚至可以将一个进程的一些数据存储在磁盘上。exec还为用户提供了存储可执行程序映像的文件系统的便利。

Unix进程之间的许多交互形式都是通过文件描述符实现的。

  • 文件描述符不仅抽象了许多细节(例如,管道或文件中的数据存储在哪里),而且还以简化交互的方式进行了定义。
  • 例如,如果流水线中的一个应用程序失败了,内核会为流水线中的下一个进程生成文件结束信号(EOF)。

系统调用接口是精心设计的,既为程序员提供了便利,又提供了强隔离的可能性。Unix接口不是抽象资源的唯一方法,但它已经被证明是一个非常好的方法。


用户态,核心态,以及系统调用

Note

  • 用户态=用户模式=目态

  • 核心态=管理模式=管态

强隔离需要应用程序和操作系统之间的硬边界,如果应用程序出错,我们不希望操作系统失败或其他应用程序失败,相反,操作系统应该能够清理失败的应用程序,并继续运行其他应用程序,要实现强隔离,操作系统必须保证应用程序不能修改(甚至读取)操作系统的数据结构和指令,以及应用程序不能访问其他进程的内存。

CPU为强隔离提供硬件支持。例如,RISC-V有三种CPU可以执行指令的模式:机器模式(Machine Mode)、用户模式(User Mode)和管理模式(Supervisor Mode)。在机器模式下执行的指令具有完全特权;CPU在机器模式下启动。机器模式主要用于配置计算机。Xv6在机器模式下执行很少的几行代码,然后更改为管理模式。

在管理模式下,CPU被允许执行特权指令:

  • 例如,启用和禁用中断、读取和写入保存页表地址的寄存器等。

如果用户模式下的应用程序试图执行特权指令,那么CPU不会执行该指令,而是切换到管理模式,以便管理模式代码可以终止应用程序,因为它做了它不应该做的事情。应用程序只能执行用户模式的指令(例如,数字相加等),并被称为在用户空间中运行,而此时处于管理模式下的软件可以执行特权指令,并被称为在内核空间中运行。在内核空间(或管理模式)中运行的软件被称为内核。

想要调用内核函数的应用程序(例如xv6中的read系统调用)必须过渡到内核。CPU提供一个特殊的指令,将CPU从用户模式切换到管理模式,并在内核指定的入口点进入内核(RISC-V为此提供ecall指令)。一旦CPU切换到管理模式,内核就可以验证系统调用的参数,决定是否允许应用程序执行请求的操作,然后拒绝它或执行它。由内核控制转换到管理模式的入口点是很重要的;如果应用程序可以决定内核入口点, 那么恶意应用程序可以在跳过参数验证的地方进入内核。


内核组织

一个关键的设计问题是操作系统的哪些部分应该以管理模式运行。一种可能是整个操作系统都驻留在内核中,这样所有系统调用的实现都以管理模式运行。这种组织被称为宏内核(monolithic kernel)

在这种组织中,整个操作系统以完全的硬件特权运行。这个组织很方便,因为操作系统设计者不必考虑操作系统的哪一部分不需要完全的硬件特权。此外,操作系统的不同部分更容易合作。例如,一个操作系统可能有一个可以由文件系统和虚拟内存系统共享的数据缓存区。

宏组织的一个缺点是操作系统不同部分之间的接口通常很复杂(正如我们将在本文的其余部分中看到的),因此操作系统开发人员很容易犯错误。在宏内核中,一个错误就可能是致命的,因为管理模式中的错误经常会导致内核失败。如果内核失败,计算机停止工作,因此所有应用程序也会失败。计算机必须重启才能再次使用。

为了降低内核出错的风险,操作系统设计者可以最大限度地减少在管理模式下运行的操作系统代码量,并在用户模式下执行大部分操作系统。这种内核组织被称为微内核(microkernel)
在这里插入图片描述

图2.1说明了这种微内核设计。在图中,文件系统作为用户级进程运行。作为进程运行的操作系统服务被称为服务器。为了允许应用程序与文件服务器交互,内核提供了允许从一个用户态进程向另一个用户态进程发送消息的进程间通信机制。例如,如果像shell这样的应用程序想要读取或写入文件,它会向文件服务器发送消息并等待响应。

TIPS

  • 由于客户/服务器(Client/Server)模式,具有非常多的优点,故在单机微内核操作系统中几乎无一例外地都采用客户/服务器模式,将操作系统中最基本的部分放入内核中,而把操作系统的绝大部分功能都放在微内核外面的一组服务器(进程)中实现。

在微内核中,内核接口由一些用于启动应用程序、发送消息、访问设备硬件等的低级功能组成。这种组织允许内核相对简单,因为大多数操作系统驻留在用户级服务器中。

像大多数Unix操作系统一样,Xv6是作为一个宏内核实现的。因此,xv6内核接口对应于操作系统接口,内核实现了完整的操作系统。由于xv6不提供太多服务,它的内核可以比一些微内核还小,但从概念上说xv6属于宏内核。


代码(XV6架构篇)

XV6的源代码位于kernel/*子目录中,源代码按照模块化的概念划分为多个文件,图2.2列出了这些文件,模块间的接口都被定义在了def.h*(*kernel/defs.h*)。

文件描述
*bio.c*文件系统的磁盘块缓存
*console.c*连接到用户的键盘和屏幕
*entry.S*首次启动指令
*exec.c*exec()系统调用
*file.c*文件描述符支持
*fs.c*文件系统
*kalloc.c*物理页面分配器
*kernelvec.S*处理来自内核的陷入指令以及计时器中断
*log.c*文件系统日志记录以及崩溃修复
*main.c*在启动过程中控制其他模块初始化
*pipe.c*管道
*plic.c*RISC-V中断控制器
*printf.c*格式化输出到控制台
*proc.c*进程和调度
*sleeplock.c*Locks that yield the CPU
*spinlock.c*Locks that don’t yield the CPU.
*start.c*早期机器模式启动代码
*string.c*字符串和字节数组库
*swtch.c*线程切换
*syscall.c*Dispatch system calls to handling function.
*sysfile.c*文件相关的系统调用
*sysproc.c*进程相关的系统调用
*trampoline.S*用于在用户和内核之间切换的汇编代码
*trap.c*对陷入指令和中断进行处理并返回的C代码
*uart.c*串口控制台设备驱动程序
*virtio_disk.c*磁盘设备驱动程序
*vm.c*管理页表和地址空间

图2.2:XV6内核源文件


进程概述

Xv6(和其他Unix操作系统一样)中的隔离单位是一个进程。进程抽象防止一个进程破坏或监视另一个进程的内存、CPU、文件描述符等。它还防止一个进程破坏内核本身,这样一个进程就不能破坏内核的隔离机制。内核必须小心地实现进程抽象,因为一个有缺陷或恶意的应用程序可能会欺骗内核或硬件做坏事(例如,绕过隔离)。内核用来实现进程的机制包括用户/管理模式标志、地址空间和线程的时间切片。

为了帮助加强隔离,进程抽象给程序提供了一种错觉,即它有自己的专用机器。进程为程序提供了一个看起来像是私有内存系统或地址空间的东西,其他进程不能读取或写入。进程还为程序提供了看起来像是自己的CPU来执行程序的指令。

Xv6使用页表(由硬件实现)为每个进程提供自己的地址空间。RISC-V页表将虚拟地址(RISC-V指令操纵的地址)转换(或“映射”)为物理地址(CPU芯片发送到主存储器的地址)。

img

Xv6为每个进程维护一个单独的页表,定义了该进程的地址空间。如图2.3所示,以虚拟内存地址0开始的进程的用户内存地址空间。首先是指令,然后是全局变量,然后是栈区,最后是一个堆区域(用于malloc)以供进程根据需要进行扩展。有许多因素限制了进程地址空间的最大范围: RISC-V上的指针有64位宽;硬件在页表中查找虚拟地址时只使用低39位;xv6只使用这39位中的38位。因此,最大地址是2^38-1=0x3fffffffff,即MAXVA(定义在*kernel/riscv.h*:348)。在地址空间的顶部,xv6为trampoline(用于在用户和内核之间切换)和映射进程切换到内核的trapframe分别保留了一个页面,正如我们将在第4章中解释的那样。

xv6内核为每个进程维护许多状态片段,并将它们聚集到一个proc(*kernel/proc.h*:86)结构体中。一个进程最重要的内核状态片段是它的页表、内核栈区和运行状态。我们将使用符号p->xxx来引用proc结构体的元素;例如,p->pagetable是一个指向该进程页表的指针。

每个进程都有一个执行线程(或简称线程)来执行进程的指令。一个线程可以挂起并且稍后再恢复。为了透明地在进程之间切换,内核挂起当前运行的线程,并恢复另一个进程的线程。线程的大部分状态(本地变量、函数调用返回地址)存储在线程的栈区上。每个进程有两个栈区:一个用户栈区和一个内核栈区(p->kstack)。当进程执行用户指令时,只有它的用户栈在使用,它的内核栈是空的。当进程进入内核(由于系统调用或中断)时,内核代码在进程的内核堆栈上执行;当一个进程在内核中时,它的用户堆栈仍然包含保存的数据,只是不处于活动状态。进程的线程在主动使用它的用户栈和内核栈之间交替。内核栈是独立的(并且不受用户代码的保护),因此即使一个进程破坏了它的用户栈,内核依然可以正常运行。

一个进程可以通过执行RISC-V的ecall指令进行系统调用,该指令提升硬件特权级别,并将程序计数器(PC)更改为内核定义的入口点,入口点的代码切换到内核栈,执行实现系统调用的内核指令,当系统调用完成时,内核切换回用户栈,并通过调用sret指令返回用户空间,该指令降低了硬件特权级别,并在系统调用指令刚结束时恢复执行用户指令。进程的线程可以在内核中“阻塞”等待I/O,并在I/O完成后恢复到中断的位置。

p->state表明进程是已分配、就绪态、运行态、等待I/O中(阻塞态)还是退出。

p->pagetable以RISC-V硬件所期望的格式保存进程的页表。当在用户空间执行进程时,Xv6让分页硬件使用进程的p->pagetable。一个进程的页表也可以作为已分配给该进程用于存储进程内存的物理页面地址的记录。


代码(启动XV6和第一个进程)

为了使xv6更加具体,我们将概述内核如何启动和运行第一个进程。接下来的章节将更详细地描述本概述中显示的机制。

当RISC-V计算机上电时,它会初始化自己并运行一个存储在只读内存中的引导加载程序。引导加载程序将xv6内核加载到内存中。然后,在机器模式下,中央处理器从_entry (kernel/entry.S:6)开始运行xv6。Xv6启动时页式硬件(paging hardware)处于禁用模式:也就是说虚拟地址将直接映射到物理地址。

加载程序将xv6内核加载到物理地址为0x80000000的内存中。它将内核放在0x80000000而不是0x0的原因是地址范围0x0:0x80000000包含I/O设备。

在这里插入图片描述
_entry的指令设置了一个栈区,这样xv6就可以运行C代码。Xv6start. c (kernel/start.c:11)文件中为初始栈stack0声明了空间。由于RISC-V上的栈是向下扩展的,所以_entry的代码将栈顶地址stack0+4096加载到栈顶指针寄存器sp中。现在内核有了栈区,_entry便调用C代码start(kernel/start.c:21)

在这里插入图片描述
函数start执行一些仅在机器模式下允许的配置,然后切换到管理模式。RISC-V提供指令mret以进入管理模式,该指令最常用于将管理模式切换到机器模式的调用中返回。而start并非从这样的调用返回,而是执行以下操作:它在寄存器mstatus中将先前的运行模式改为管理模式,它通过将main函数的地址写入寄存器mepc将返回地址设为main,它通过向页表寄存器satp写入0来在管理模式下禁用虚拟地址转换,并将所有的中断和异常委托给管理模式。

在进入管理模式之前,start还要执行另一项任务:

  • 对时钟芯片进行编程以产生计时器中断。

清理完这些“家务”后,start通过调用mret“返回”到管理模式。这将导致程序计数器(PC)的值更改为main(kernel/main.c:11)函数地址。

// entry.S needs one stack per CPU.
__attribute__ ((aligned (16))) char stack0[4096 * NCPU];

// entry.S jumps here in machine mode on stack0.
void
start()
{
  // set M Previous Privilege mode to Supervisor, for mret.
  //设置mstatus的MPP位为Supervisor态
  unsigned long x = r_mstatus();
  x &= ~MSTATUS_MPP_MASK;
  x |= MSTATUS_MPP_S;
  w_mstatus(x);

  // set M Exception Program Counter to main, for mret.
  // requires gcc -mcmodel=medany
  //设置mepc寄存器指向main函数地址
  w_mepc((uint64)main);

  // disable paging for now.
  //启动阶段禁用分页功能,意味着取消了地址翻译和保护机制,所有的内存访问将直接使用物理地址而不经过虚拟地址到物理地址的映射
  w_satp(0);

  // delegate all interrupts and exceptions to supervisor mode.
  //将所有中断和异常委托给 Supervisor 模式处理
  w_medeleg(0xffff);
  w_mideleg(0xffff);
  //设置SIE寄存器相关位,从而开启S态下的外部中断,时钟中断和软件中断
  w_sie(r_sie() | SIE_SEIE | SIE_STIE | SIE_SSIE);

  // ask for clock interrupts.
  //初始化硬件定时器模块
  timerinit();

  // keep each CPU's hartid in its tp register, for cpuid().
  // 获取当前hart id
  int id = r_mhartid();
  //在 Machine 模式下,tp 寄存器存储当前硬件线程的唯一标识符(hartid),可用于识别不同的处理器核心
  w_tp(id);

  // switch to supervisor mode and jump to main().
  //执行一条汇编指令: mret
  //1.特权级别切换到S态
  //2.跳转到main函数入口地址处执行
  asm volatile("mret");
}
  • 下面展示的是硬件定时器模块初始化函数的代码
// scratch area for timer interrupt, one per CPU.
//存放每个hart对应的时钟中断上下文环境---中断上下文环境占用32*uint64大小
uint64 mscratch0[NCPU * 32];

// assembly code in kernelvec.S for machine-mode timer interrupt.
extern void timervec();

// set up to receive timer interrupts in machine mode,
// which arrive at timervec in kernelvec.S,
// which turns them into software interrupts for
// devintr() in trap.c.
void
timerinit()
{
  // each CPU has a separate source of timer interrupts
  int id = r_mhartid();

  // ask the CLINT for a timer interrupt.
  // 设置时钟中断间隔大约为1毫秒发生一次 --> 硬件1000000次tick大约为1毫秒(qemu模拟出来的)
  int interval = 1000000; // cycles; about 1/10th second in qemu.
  //初始化MTIMECMP寄存器的值=MTIME+1毫秒间隔 ---> 设置下一次时钟中断发生在1毫秒后
  *(uint64*)CLINT_MTIMECMP(id) = *(uint64*)CLINT_MTIME + interval;

  // prepare information in scratch[] for timervec.
  // scratch[0..3] : space for timervec to save registers.
  // scratch[4] : address of CLINT MTIMECMP register.
  // scratch[5] : desired interval (in cycles) between timer interrupts.
  //mscratch0数组存放所有hart的时钟中断上下环境
  uint64 *scratch = &mscratch0[32 * id];
  //获取当前hart对应的MTIMECMP寄存器的地址
  scratch[4] = CLINT_MTIMECMP(id);
  //存放当前hart时钟中断对应的间隔
  scratch[5] = interval;
  //当前hart的mscratch寄存器指向当前hart的scratch区域,该区域存放当前hart时钟中断上下文环境
  w_mscratch((uint64)scratch);

  // set the machine-mode trap handler.
  //设置mvetc指向时钟中断处理函数地址
  w_mtvec((uint64)timervec);

  // enable machine-mode interrupts.
  // 开启M态下的全局中断
  w_mstatus(r_mstatus() | MSTATUS_MIE);

  // enable machine-mode timer interrupts.
  // 开启M态的时钟中断
  w_mie(r_mie() | MIE_MTIE);
}

TIPS

  • 注:mret执行返回,返回到先前状态,由于start函数将前模式改为了管理模式且返回地址改为了main,因此mret将返回到main函数,并以管理模式运行

main(kernel/main.c:11)初始化几个设备和子系统后,便通过调用userinit (kernel/proc.c:212)创建第一个进程,第一个进程执行一个用RISC-V程序集写的小型程序:initcode. S (user/initcode.S:1),它通过调用exec系统调用重新进入内核。
在这里插入图片描述
正如我们在第1章中看到的,exec用一个新程序(本例中为 /init)替换当前进程的内存和寄存器。一旦内核完成exec,它就返回/init进程中的用户空间。
在这里插入图片描述

如果需要,init(user/init.c:15)将创建一个新的控制台设备文件,然后以文件描述符0、1和2打开它。然后它在控制台上启动一个shell。系统就这样启动了。
在这里插入图片描述


真实世界

在现实中,人们可以同时看到宏内核和微内核。许多Unix都采用宏内核。例如,尽管Linux的一些操作系统功能作为用户级服务器运行(例如窗口系统),但它是宏内核架构。而如L4、Minix和QNX的内核都被组织成一个带有多个服务器的微内核,微内核在嵌入式设备中得到了广泛的应用。

大多数操作系统都采用了进程的概念,并且大多数操作系统的进程看起来与xv6相似。然而,现代操作系统支持在一个进程中创建多个线程,使得一个进程能够利用多个处理器。在一个进程中支持多个线程涉及许多XV6缺乏的机制,包括潜在的接口更改(例如,Linux下fork的变体clone),以控制进程线程共享哪些内容。


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

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

相关文章

用饭店来形象比喻线程池的工作原理

一、线程池解决的问题? 使用线程池主要解决在程序中频繁创建和销毁线程导致的资源浪费,线程池可以维护一定量的线程来执行所需要的任务,维护的线程也可以重复使用。 二、用形象的饭店来解释工作原理 线程池就相当于一家饭店, 任…

SpringBoot框架的学生宿舍管理系统

项目介绍 主要功能: 管理员登录权限: ①学生管理:根据编号姓名搜索、可以新增修改删除、导入导出 ②楼宇管理:根据楼宇搜索、可以新增修改删除、导入导出 ③宿舍管理:根据宿舍编号搜索、可以新增修改删除、导入导出 ④…

HCIA-RS实验-配置FTP 业务

FTP简单说明 FTP(File Transfer Protocol)是一种用于文件传输的协议,可以在计算机之间进行文件的上传和下载。FTP使用客户端-服务器模型,客户端通过FTP客户端软件连接到服务器端的FTP服务端口,进行文件传输和管理。 F…

在fpga上开发音视频是一种什么体验?

前言: 今天周末回公司解决了解码播放问题,最近周末也没啥事情,一般周六都会过去公司学习音视频开源项目(过去公司,主要是住的近,所以很方便!),待在家里也是无聊,所以就回去看开源项目…

硬件设计电源系列文章-LDO设计

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 本文主要分享LDO的相关设计,尤其是LDO的并联设计 整体架构流程 提示:这里可以添加技术整体架构 主要是讲述LDO的并联;并联以增加输出驱动能力,其具体框架如下&#x…

详解Ribbon

目录 1.概述 2.使用 2.1.引入 2.2.启用 2.3.切换负载均衡算法 3.负载均衡源码分析 3.1.接口 3.2.抽象类 3.3.选择服务器 3.4.原子性 4.自定义负载均衡算法 1.概述 Ribbon是Netflix开源的一个客户端负载均衡库,也是Spring Cloud Netflix项目的核心组件之…

常见网络服务器并发模型

近些年,随着互联网的大发展,高并发服务器技术也快速进步,从简单的循环服务器模型处理少量网络并发请求,演进到解决C10K,C10M问题的高并发服务器模型。本文主要以TCP为例,总结了几种常见的网络服务器模型的实…

巧用文件批量改名高手删除子文件夹一例

比如有很多商品文件夹,里面又分出主图、细节图等,现在因工作需要把主图、细节图这些子文件夹去掉,把子文件夹里面的文件放在商品名称的父文件夹中,如图: 打开主图文件夹,我们可以看到文件名结构为数字编号的…

Git学习 - 2023-06-08

2023暑期学习 Git基础Git Fetch VS Git Pullgit pull --rebase VS git pull几种merge的方法Fork VS Clone CS Branch如何把master的内容更新到分支上详尽介绍 git fetch VS git pull其他命令 Git基础 git branch branch-name # 创建一个新的分支git checkout branch-name # 切…

Golang | Web开发之Gin框架快速入门基础实践

欢迎关注「全栈工程师修炼指南」公众号 点击 👇 下方卡片 即可关注我哟! 设为「星标⭐」每天带你 基础入门 到 进阶实践 再到 放弃学习! 专注 企业运维实践、网络安全、系统运维、应用开发、物联网实战、全栈文章 等知识分享 “ 花开堪折直须折&#xf…

Learning C++ No.30 【lambda表达式实战】

引言: 北京时间:2023/6/9/9:13,今天8:15起床,可能是最近课非常少,导致写博客没什么压力,什么时间都能写,导致7点起不来,哈哈哈,习惯睡懒觉了,但是问题不大&a…

【二十七】springboot之通过threadLocal+参数解析器实现同session一样保存当前登录信息的功能

springboot篇章整体栏目: 【一】springboot整合swagger(超详细 【二】springboot整合swagger(自定义)(超详细) 【三】springboot整合token(超详细) 【四】springboot整合mybatis…

绿豆影视系统5.1.8反编译版源码:PC+WAP+APP端【附搭建教程+软件】

简介: 绿豆影视系统5.1.8反编译版源码:PCWAPAPP端【附搭建教程软件】 优化内容 1.专题类目,在后台进行设置 2.短视频类目 ,需要有信天翁id 3.优化首页栏目不显示问题 4.去除我的页面 不常用功能 5.修复自定义密码只能输入数字的…

二阳竟然是这样的~

今天周六,家人都出去玩了,把我自己扔家里了,因为我2阳了,出门都不带我玩了。 正好趁这个时间简单写下2阳的症状,和1阳有什么不一样。 先说结论: 我的亲身感受是2阳比1阳轻的多,没有浑身关节疼&a…

MySQL-索引详解(三)

♥️作者:小刘在C站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维课堂笔记,努力不一定有回报,但一定会有收获加油!一起努力,共赴美好人生! ♥️树高千尺,落叶归根人生不易&…

第64篇:史上最严重的APT供应链攻击事件,借助Solarwinds攻击欧美的流程图梳理和分析(上篇)...

Part1 前言 大家好,我是ABC_123,公众号正式更名为”希潭实验室”,敬请关注。本期分享一个堪称史上影响最大、危害最大的供应链攻击APT案例——Solarwinds供应链攻击事件,SolarWinds的旗下有数万家客户公司,包括了”财…

h.264 h.265 协议基本概念记录

区分一些概念 MPEG-4 是一套用于音频、视频信息的压缩编码标准H.264,AVC,编码格式,是MPEG-4 的第10部分H.265,HEVC,编码格式,是MPEG-H的第2部分mp4,rmvb,mkv,avi是容器&…

【ChatGPT】数据科学 ChatGPT Cheat Sheet 书籍分享(阿里云盘下载)

封皮 以下为书中部分内容的机器翻译 我们的重要提示指南 1. 以 AI 角色的描述开始提示。 例如,“你是{x}”或“我希望你扮演{x}”。如果您不确定,请尝试“你是一个有帮助的助手”。 例如,您是 OpenAI 的数据科学家,您正在研究大型…

测量项目总结

和朋友合作开发一个测量机产品, 用于测量汽车零件形位公差, 客户的客户是电动汽车第一品牌, 我负责上位机开发, 历时2个月, 完成上百次的commit. 时间虽紧, 但代码质量上没有妥协, 软件层次划分合理, 后续考虑做成系列产品. 开发利器 感恩这个时代, 现在的软件开发开发体验真好…

git hook

hook hook 翻译为钩子,简单说就是监听某个事件(操作),然后触发自定义逻辑 在 git 中可以监听 commit,push 等操作,在操作之前或之后触发对应的 hook,在 hook 中写自定义的逻辑,比如…