SnowKagura‘s blog - 运行
type: channel
link: https://www.snowkagura.com/tag/%E8%BF%90%E8%A1%8C/
items: 操作系统学习笔记（5）——几种操作系统的运作方式
items: 函数调用实现原理小结+RISC-V架构学习笔记
items: 操作系统学习笔记（4）—— 操作系统的特征
items: 操作系统学习笔记（3）——操作系统抽象
items: 操作系统学习笔记（2）——系统调用接口
items: 操作系统学习笔记（1）——什么是操作系统
items: 从零开始搭建react开发环境（进阶篇）
items: 从零开始构建react+typescript开发环境
items: 因为Flash停止服务后学校的平台仍使用flash，我打包了一个可用flash的chromium浏览器

操作系统学习笔记（5）——几种操作系统的运作方式
type: item
date: 2022-05-08T14:49:00+08:00
description: 应用程序基本执行环境在这种执行环境下，操作系统与应用程序的界限并不明显，应用程序即是操作系统的组成部分，均在内核态下运行，一旦应用程序出错，便会导致整个操作系统的崩溃批处理操作系统从批处理操作系统开始，用户程序与操作系统有了明显的界限，用户程序运行在用户态下，对于一些操作需要通过特权级的切换来完成（系统调用），这时，在操作系统与用户程序间建立了系统调用接口（syscall）。通过这种方式，也实现了用户栈与内核栈的分离，也即保证了用户程序的执行环境是独立于操作系统的执行环境的，进一步提高了操作系统的可靠性。批处理系统的特点是，一次只加载一个应用程序，当应用执行 出错或完成 后，操作系统会自动 加载 下一个需要执行的应用程序。多道程序的运行方式在内存中尽量同时驻留多个应用，这样处理器的利用率就会提高。但只有一个程序执行完毕后或主动放弃执行，处理器才能执行另外一个程序。这种运行方式称为 多道程序（Multiprogramming） 。协作式操作系统在 多道程序 运行方式下，一个程序如果不让出处理器，其他程序是无法执行的。如果一个应用由于 I/O 操作让处理器空闲下来或让处理器忙等，那其他需要处理器资源进行计算的应用还是没法使用空闲的处理器资源。于是就想到，让应用在执行 I/O 操作或空闲时，可以主动 释放处理器 ，让其他应用继续执行。当然执行 放弃处理器 的操作算是一种对处理器资源的直接管理，所以应用程序可以发出这样的系统调用，让操作系统来具体完成。这样的操作系统就是支持 多道程序 或 协作式多任务 的协作式操作系统。抢占式操作系统上面提到的协作式操作系统，在对于编写用户程序的人群要求十分高。他们不一定有友好互助的意识，也不了解其它程序的执行情况，很难（也没必要）有提高整个系统利用率上的大局观。在他们的脑海里，整个计算机就应该是为他们自己的应用准备的，不用考虑其他程序的运行。这导致应用程序员在编写程序时，无法做到在程序的合适位置放置 放弃处理器的系统调用请求 ，这样系统的整体利用率还是无法提高。所以，站在系统的层面，还是需要有一种办法能强制打断应用程序的执行，来提高整个系统的效率，让在整个系统中执行的多个程序之间占用计算机资源的情况相对公平一些。根据计算机系统的硬件设计，为提高 I/O 效率，外设可以通过硬件中断机制来与处理机进行 I/O 交互操作。这种硬件中断机制可随时打断应用程序的执行，并让操作系统来完成对外设的 I/O 响应。而操作系统可进一步利用某种以固定时长为时间间隔的外设中断（比如时钟中断）来强制打断一个程序的执行，这样一个程序只能运行一段时间（可以简称为一个时间片, Time Slice）就一定会让出处理器，且操作系统可以在处理外设的 I/O 响应后，让不同应用程序分时占用处理器执行，并可通过统计程序占用处理器的总执行时间，来评估运行的程序对处理器资源的消耗。这种运行方式称为 分时共享（Time Sharing） 或 抢占式多任务（Multitasking） ，也可合并在一起称为 分时多任务 。可以把一个程序的一次完整执行过程称为一次 任务 (Task)，把一个程序在一个时间片（Time Slice）上占用处理器执行的过程称为一个 任务片 (Task Slice)。操作系统对不同程序的执行过程中的 任务片 进行调度和管理，即通过平衡各个程序在整个时间段上的任务片数量，就能达到一定程度的系统公平和高效的系统效率。在一个包含多个时间片的时间段上，会有属于不同程序的多个任务片在轮流占用处理器执行，这样的操作系统就是支持 分时多任务 或 抢占式多任务 的抢占式操作系统。

函数调用实现原理小结+RISC-V架构学习笔记
type: item
date: 2022-04-07T00:55:00+08:00
description: 从汇编语言的视角来看在函数调用的时候，需要有一条指令跳转到被调用函数的位置，这个看起来和其他控制结构没什么不同；但是在被调用函数返回的时候，我们却需要返回那条跳转过来的指令的下一条继续执行。这次用来返回的跳转究竟跳转到何处，在对应的函数调用发生之前是不知道的。比如，我们在两个不同的地方调用同一个函数，显然函数返回之后会回到不同的地址。这是一个很大的不同：其他控制流都只需要跳转到一个 编译期固定下来 的地址，而函数调用的返回跳转是跳转到一个 运行时确定 （确切地说是在函数调用发生的时候）的地址。对此，指令集必须给用于函数调用的跳转指令一些额外的能力，而不只是单纯的跳转。对于8086汇编来说，有一条伪指令符合该特征，也就是 call 伪指令，它会使得 sp 寄存器 减少对应的字节数，然后将当前的 IP 保存到栈顶（在执行当前指令之前，IP 就已经指向下一条指令了），然后使得 IP 指向被调用的函数地址。而 ret 伪指令则是该过程的逆过程，即将保存在栈内的返回地址出栈放入 IP 内。而在 RISC-V 架构上，有两条指令即符合这样的特征：指令指令功能$jal\ rd,\ imm[20:1]$$rd\leftarrow pc+4$$pc\leftarrow pc+imm$$jalr\ rd,\ (imm[11:0])rs$$rd\leftarrow pc+4$$pc\leftarrow rs+imm$[scode type="blue" size=""]RISC-V 指令各部分含义在大多数只与通用寄存器打交道的指令中， rs 表示 源寄存器 (Source Register)， imm 表示 立即数 (Immediate)，是一个常数，二者构成了指令的输入部分；而 rd 表示 目标寄存器 (Destination Register)，它是指令的输出部分。rs 和 rd 可以在 32 个通用寄存器 x0~x31 中选取。但是这三个部分都不是必须的，某些指令只有一种输入类型，另一些指令则没有输出部分。[/scode]从中可以看出，这两条指令在设置 pc 寄存器完成跳转功能之前，还将当前跳转指令的下一条指令地址保存在 rd 寄存器中，即 $rd\leftarrow pc+4$ 这条指令的含义。（这里假设所有指令的长度均为 4 字节）在 RISC-V 架构中，通常使用 ra 寄存器（即 x1 寄存器）作为其中的 rd 对应的具体寄存器，因此在函数返回的时候，只需跳转回 ra 所保存的地址即可。事实上在函数返回的时候我们常常使用一条 伪指令 (Pseudo Instruction) 跳转回调用之前的位置： ret 。它会被汇编器翻译为 jalr x0, 0(x1)，含义为跳转到寄存器 ra 保存的物理地址，由于 x0 是一个恒为 0 的寄存器，在 rd 中保存这一步被省略。总结一下，在进行函数调用的时候，我们通过 jalr 指令保存返回地址并实现跳转；而在函数即将返回的时候，则通过 ret 伪指令回到跳转之前的下一条指令继续执行。这样，RISC-V 的这两条指令就实现了函数调用流程的核心机制。可以发现，汇编指令在实现函数调用流程的核心机制大差不差，其思想都是在调用函数前保存返回地址，在函数调用结束时，恢复到之前的地址。在栈内保存函数地址还好办，但是像 RISC-V 架构这样，指令将地址保存在 ra 寄存器中，我们就得保证 ra 寄存器在函数调用过程中不被篡改了，单次调用还好说，但是在现实应用中，我们离不开函数 多层嵌套调用 的情形。我们很容易想象，如果函数不支持嵌套调用，那么编程将会变得多么复杂。如果我们试图在一个函数 f 中调用一个子函数，在跳转到子函数 g 的同时，ra 会被覆盖成这条跳转指令的下一条的地址，而 ra 之前所保存的函数 f 的返回地址将会 永久丢失 。因此，若想正确实现嵌套函数调用的控制流，我们必须通过某种方式保证：在一个函数调用子函数的前后，ra 寄存器的值不能发生变化。但实际上，这并不仅仅局限于 ra 一个寄存器，而是作用于所有的通用寄存器。这是因为，编译器是独立编译每个函数的，因此一个函数并不能知道它所调用的子函数修改了哪些寄存器。而站在一个函数的视角，在调用子函数的过程中某些寄存器的值被覆盖的确会对它接下来的执行产生影响。因此这是必要的。我们将由于函数调用，在控制流转移前后需要保持不变的寄存器集合称之为 函数调用上下文 (Function Call Context) 。由于每个 CPU 只有一套寄存器，我们若想在子函数调用前后保持函数调用上下文不变，就需要物理内存的帮助。确切的说，在调用子函数之前，我们需要在物理内存中的一个区域 保存 (Save) 函数调用上下文中的寄存器；而在函数执行完毕后，我们会从内存中同样的…

操作系统学习笔记（4）—— 操作系统的特征
type: item
date: 2022-03-24T21:14:00+08:00
description: 操作系统的特征从总体上看，操作系统具有五个方面的特征：虚拟化 (Virtualization)、并发性 (Concurrency)、异步性、共享性和持久性 (Persistency)。操作系统的虚拟化可以理解为它对内存、CPU 的抽象和处理；并发性和共享性可以理解为操作系统支持多个应用程序“同时”运行；异步性可以从操作系统调度、中断处理对应用程序执行造成的影响等几个方面来理解；持久性则可以从操作系统中的文件系统支持把数据方便地从磁盘等存储介质上存入和取出来理解。虚拟性内存虚拟化程序员在写应用程序的时候，不用考虑其程序的起始内存地址要放到计算机内存的具体某个位置，而是用字符串符号定义了各种变量和函数，直接在代码中便捷地使用这些符号就行了。是由于操作系统建立了一个 地址固定 ， 空间巨大 的虚拟内存给应用程序来运行，这是 内存虚拟化 ，其实是一种 “空间虚拟化” ， 可进一步细分为 内存地址虚拟化 和 内存大小虚拟化 。这里的每个符号在运行时是要对应到具体的内存地址的。这些内存地址的具体数值是什么？程序员不用关心。为什么？因为编译器会自动帮我们把这些符号翻译成地址，形成可执行程序。程序使用的内存是否占得太大了？在一般情况下，程序员也不用关心。[scode type="blue" size=""]实际上，编译器 (Compiler，比如 gcc) 和链接器 (linker，比如 ld) 也不知道程序每个符号对应的地址应该放在未来程序运行时的哪个物理内存地址中。所以，编译器的一个简单处理办法就是，设定一个固定地址（比如 0x10000）作为起始地址开始存放代码，代码之后是数据，所有变量和函数的符号都在这个起始地址之后的某个固定偏移位置。假定程序每次运行都是位于一个不会变化的起始地址。这里的变量指的是全局变量，其地址在编译链接后会确定不变。但局部变量是放在堆栈中的，会随着堆栈大小的动态变化而变化。这里编译器产生的地址就是虚拟地址。这里，编译器和链接器图省事，找了一个适合它们的解决办法。当程序要运行的时候，这个符号所对应的虚拟内存地址到计算机的物理内存地址的映射必须要解决了，于是这个任务便推到了操作系统身上。操作系统会把编译器和链接器生成的执行代码和数据放到空闲的物理内存中，并建立虚拟地址到物理地址的映射关系。由于物理内存中的空闲区域是动态变化的，这导致虚拟地址到物理地址的映射关系也是动态变化的，需要操作系统来维护好可变的映射关系，确保编译器“固定起始地址”的假设成立。只有操作系统维护好了这个映射关系，才能让程序员只需写一些易于人理解的字符串符号来代表一个内存空间地址，且编译器只需确定一个固定地址作为程序的起始地址就可以不用考虑将来这个程序要在哪里运行的问题，从而实现了 内存地址虚拟化 。在这里，虚拟地址到物理地址的转换关系由操作系统与 MMU(Memory Management Unit) 进行交互从而处理映射关系。[/scode]应用程序在运行时不用考虑当前物理内存是否够用。如果应用程序需要一定空间的内存，但由于在某些情况下，物理内存的空闲空间可能不多了，这时操作系统通过把物理内存中最近没使用的空间（不是空闲的，只是最近用得少）换出（就是“挪地”）到硬盘上暂时缓存起来，这样空闲空间就大了，就可以满足应用程序的运行时内存需求了，从而实现了 内存大小虚拟化 。[scode type="green" size=""]这里其实就涉及到了内存页的概念，通过将内存按页面大小的映射，从而实现了方便的装载与卸载，等到需要使用时再重新建立映射关系[/scode]CPU虚拟化不同的应用程序可以在内存中并发运行，相同的应用程序也可有多个拷贝在内存中并发运行。而每个程序都“认为”自己完全独占了 CPU 在运行，这是 “CPU虚拟化” ，也是一种 “时间虚拟化” 。操作系统给了运行的应用程序一个幻象，即操作系统把时间分成小段，每个应用程序占用其中一小段时间片运行，用完这一时间片后，操作系统会切换到另外一个应用程序，让它运行。由于时间片很短，操作系统的切换开销也很小，应用程序或使用应用程序的用户基本上是看不出的，反而感觉到多个程序各自在独立“并行”执行，从而实现了 CPU虚拟化 。[scode type="yellow" size=""]并行 (Parallel) 是指两个或者多个事件在同一时刻发生；并发 (Concurrent) 是指两个或多个事件在同一时间间隔内发生。对于基于单 CPU 的计算机而言，各个“同时”运行的程序其实是串行分时复用一个 CPU ，任一个时刻点上只有一个程序在 CPU 上运行。 这些虚拟性的特征给应用程序的开发和执行提供了非常方便的执行环境，但也给操作系统的设计与实现提出了很多挑战。[/scode]并发性操作系统为了能够让 CPU 充…

操作系统学习笔记（3）——操作系统抽象
type: item
date: 2022-03-20T12:14:00+08:00
description: 执行环境执行环境 (Execution Environment) 是一个内涵很丰富且有一定变化的术语，它主要负责给在其上执行的软件提供相应的功能与资源，并可在计算机系统中形成多层次的执行环境。对于现在直接运行在裸机硬件 （Bare-Metal） 上的操作系统，其执行环境是 计算机的硬件 。在早期的计算机系统中，还没有操作系统，所以对于直接运行在裸机硬件上的应用程序而言，其执行环境也是 计算机的硬件 。后来随着计算机技术的发展，应用程序下面形成了一层比较通用的函数库，这使得应用程序不需要直接访问硬件了，它所需要的功能（比如显示字符串）和资源（比如一块内存）都可以通过函数库的函数来帮助完成。在第二个阶段，应用程序的执行环境就变成了 函数库 -> 计算机硬件 ，而这时函数库的执行环境就是计算机的硬件。再进一步，操作系统取代了函数库来访问硬件，函数库通过访问操作系统的系统调用服务来进一步给应用程序 提供丰富的功能和资源。在第三个阶段，应用程序的执行环境就变成了 函数库 -> 操作系统内核 -> 计算机硬件 。在后面又出现了基于 Java 语言的应用程序，在函数库和操作系统之间，多了一层 Java 虚拟机，此时 Java 应用 程序的执行环境就变成了 函数库 -> Java 虚拟机 -> 操作系统内核 -> 计算机硬件 。在云计算时代，在传统操作系统与 计算机硬件之间多了一层 Hypervisor/VMM ，此时应用程序的执行环境变成了 函数库 -> Java 虚拟机 -> 操作系统内核 -> Hypervisor/VMM -> 计算机硬件 。这里可以看到，随着软件需求的多样化和复杂化， 执行环境的层次 也越来越多。另外，CPU 在执行过程中，可以在不同层次的执行环境之间切换，这称为 执行环境切换 。执行环境切换主要是通过特定的 API 或 ABI 来完成的，这样不同执行环境的软件就能实现数据交换与互操作，而且还保证了彼此之间有清晰的隔离。[scode type="blue"]执行环境切换，比如说docker容器内的应用与物理机之间的交互，虚拟机与物理机之间的交互[/scode]对于应用程序的执行环境而言，应用程序只能看到执行环境直接提供给它的接口（API 或 ABI），这使得应用程序所能得到的服务取决于执行环境提供给它的访问接口。所以，操作系统可以看成是应用程序执行环境，其形态可以是一个库，也可以是一个虚拟机等，或者它们的某种组合形式。比如， 手机上的 Android操作系统 是Android应用程序的执行环境，它包括了库形态的Framework层，执行Java程序的虚拟机层，与操作系统交互的系统类C库和Linux kernel。执行环境基本定义根据上面的介绍，可以得出对于应用程序的执行环境的一个基本的定义：执行环境是应用程序正确运行所需的服务与管理环境，用来完成应用程序在运行时的数据与资源管理、应用程序的生存期等方面的处理，它定义了应用程序有权访问的其他数据或资源，并决定了应用程序的行为限制范围。普通控制流各种应用程序在执行环境中执行其功能，而具体如何执行，取决于 程序的控制流 。程序的控制流 （Flow of Control or Control Flow） 是指以一个程序的指令、语句或基本块为单位的执行序列。而处理器的控制流是指处理器中程序计数器的控制转移序列。最简单的一种控制流（没有异常或中断产生的前提下）是一个“平滑的”序列，其中每个要执行的指令地址在内存中都是相邻的。如果站在程序员的角度来看控制流，会发现控制流是程序员编写的程序的执行序列，这些序列是程序员预设好的。程序运行时能以多种简单的控制流（顺序、分支、循环结构和多层嵌套函数调用）组合的方式，来一行一行的执行源代码（以编程语言级的视角），也是一条一条的执行汇编指令（以汇编语言级的视角）。对于上述的不同描述，我们可以统称其为 普通控制流 （CCF，Common Control Flow，简称 控制流） 。在应用程序视角下，它只能接触到它所在的执行环境，不会跳到其他执行环境，所以应用程序执行基本上是以普通控制流的形式完成整个运行的过程。异常控制流应用程序在执行过程中，如果发出系统调用请求，或出现外设中断、CPU 异常等情况，处理器执行的前一条指令和后一条指令将会位于两个完全不同的位置，即不同的执行环境 。比如，前一条指令还在应用程序的代码段中，后一条指令就跑到操作系统的代码段中去了，这就是一种控制流的“突变”，即控制流脱离了其所在的执行环境，并产生 执行环境的切换。 我们把这种“突变”的控制流称为 异常控制流 （ECF, Exceptional Control Flow） 。应用程序 感知 不到这种异常的控制流情况，这主要是由于操作系统把这种情况 透明 地进行了执行环境的…

操作系统学习笔记（2）——系统调用接口
type: item
date: 2022-03-19T19:18:00+08:00
description: 站在用户和应用程序的角度看操作系统操作系统内核是一个需要提供各种服务的软件，其服务对象是应用程序，而用户（这里可以理解为一般使用计算机的人）是通过应用程序的服务间接获得操作系统的服务的，因此操作系统内核藏在一般用户看不到的地方。但应用程序需要访问操作系统获得操作系统的服务，这就需要通过操作系统的接口才能完成。操作系统的接口形式就是应用程序二进制接口 (ABI, Application Binary Interface)。操作系统不能只提供面向单一编程语言的函数库的编程接口 (API, Application Programming Interface) ，它的接口需要考虑对基于各种编程语言的应用支持，以及访问安全等因素，使得应用软件不能像访问函数库一样的直接访问操作系统内部函数，更不能直接读写操作系统内部的地址空间。为此，操作系统设计了一套安全可靠的接口，我们称为系统调用接口 (System Call Interface)。系统调用接口通常面向应用程序提供了API的描述，但在具体实现上，还需要提供ABI的接口描述规范。[scode type="blue"]一些思考：提供ABI规范是便于底层开发人员进行驱动程序的开发，以及进行汇编级别的优化和高级编程语言运行库的开发等操作[/scode]在现代处理器的安全支持（特权级隔离，内存空间隔离等）下，应用程序就不能直接以函数调用的方式访问操作系统的函数，以及直接读写操作系统的数据变量。[scode type="red"]这里的不能直接以函数调用的方式访问操作系统的函数，我认为指的是操作系统内部使用的函数而非暴露出来的系统调用接口[/scode]不同类型的应用程序可以通过符合操作系统规定的ABI规范的系统调用接口，发出系统调用请求，来获得操作系统的服务。操作系统提供完服务后，返回应用程序继续执行。[scode type="share"]API 与 ABI 的区别应用程序二进制接口 ABI 是不同二进制代码片段的连接纽带。ABI 定义了二进制机器代码级别的规则，主要包括基本数据类型、通用寄存器的使用、参数的传递规则、以及堆栈的使用等等。ABI 与处理器和内存地址等硬件架构相关，是用来约束链接器 (Linker) 和汇编器 (Assembler) 的。在同一处理器下，基于不同高级语言编写的应用程序、库和操作系统，如果遵循同样的 ABI 定义，那么它们就能正确链接和执行。应用程序编程接口 API 是不同源代码片段的连接纽带。API 定义了一个源码级（如 C 语言）函数的参数，参数的类型，函数的返回值等。因此 API 是用来约束编译器 (Compiler) 的：一个 API 是给编译器的一些指令，它规定了源代码可以做以及不可以做哪些事。API 与编程语言相关，如 libc 是基于 C 语言编写的标准库，那么基于 C 的应用程序就可以通过编译器建立与 libc 的联系，并能在运行中正确访问 libc 中的函数。简单来说，API是编译器级别的，ABI是汇编级别的。[/scode]系统调用接口的一些比较重要的功能与相应的抽象对于系统调用接口提供的功能，下面列出了一些值得了解的功能：一个运行的程序可以创建另一个程序的实例吗？需要等待另外一个程序执行完成吗？操作系统能停止或恢复另一个正在运行的程序吗？一个运行的程序如何与连接到计算机的设备通信并通过它们与物理世界通信？多个运行的程序如何同步互斥地对共享资源进行访问？一个运行的程序可以要求更多（或更少）的内存空间吗？一个运行的程序如何持久地存储用户数据？一个运行的程序如何能输出字符信息？如何能获得输入字符信息？下面列出了一些相对比较重要的操作系统接口或抽象：进程（即程序运行过程）管理：复制创建进程 fork 、退出进程 exit 、执行进程 exec 等。线程管理：线程（即程序的一个执行流）的创建、执行、调度切换等。线程同步互斥的并发控制：互斥锁 mutex 、信号量 semaphore 、管程 monitor 、条件变量 condition variable 等。进程间通信：管道 pipe 、信号 signal 、事件 event 等。虚存管理：内存空间映射 mmap 、改变数据段地址空间大小 sbrk 、共享内存 shm 等。文件I/O操作：对存储设备中的文件进行读 read 、写 write 、打开 open 、关闭 close 等操作。外设I/O操作：外设包括键盘、显示器、串口、磁盘、时钟 … 但接口均采用了文件 I/O 操作的通用系统调用接口。[scode type="share"]上述表述在某种程度上说明了操作系统对计算机硬件重要组成的抽象和虚拟化，使得应用程序只需基于对简单的抽象概念的访问来达到对计算机系统资源的使用：文件 (File) 是外设的…

操作系统学习笔记（1）——什么是操作系统
type: item
date: 2022-03-19T16:36:00+08:00
description: 前言最近找到一个好文档，关于如何用Rust编写一个操作系统，里面系统的讲述了操作系统的相关知识和源代码等等，所以就打算跟着这个文档开始学习操作系统相关的知识(之后就可以去读CSAPP了从认识来看一个操作系统（OS）是一个软件，它帮助用户和应用程序使用和管理计算机的资源。操作系统可能对最终用户不可见，但控制着嵌入式设备、更通用的系统（如智能手机、台式计算机和服务器）以及巨型机等各种计算机系统。从功能开始操作系统主要做的事情就只有两件：一是向下管理计算机硬件和各种外设，二是向上给应用软件提供各种服务帮助。进一步描述操作系统是一个可以管理CPU、内存和各种外设，并管理和服务应用软件的系统软件。[scode type="share"]系统软件是在一个计算机系统范围内使用的软件，可涉及到整个计算机系统。文本编辑器这样的软件只涉及到对文本文件的编辑，并不能被叫做系统软件。而在计算机中安装的 C 标准库 libc（类似的有 Rust 标准库 等）可以算是一个，因为它提供了与OS交互的系统调用接口，其功能覆盖了整个计算机系统，会被许多不同的软件访问。[/scode]为了完成这些工作，操作系统需要知道如何与硬件打交道，如何更好地给应用软件提供服务，这就有一系列操作系统相关的理论、抽象、设计等来支持如何做和做得好的需求。[scode type="blue"]在不同的应用场景下，操作系统的边界也是不同的，我们可以把运行时库、图形界面支持库等这些可支持不同应用的系统软件 (System Software) 也看成是操作系统的一部分。比如说嵌入式环境，一般操作系统以运行时库的形式与应用程序紧密结合在一起，形成一个可在嵌入式硬件上单一执行的嵌入式应用。而 Android 应用运行在 ARM 处理器上 Android 操作系统的执行环境中。[/scode]简化描述站在应用程序的角度来看，我们可以发现常见的应用程序其实是运行在由硬件、操作系统内核、运行时库、图形界面支持库等所包起来的一个 执行环境 (Execution Environment) 中，应用程序只需根据与系统软件约定好的应用程序二进制接口 (ABI, Application Binary Interface) 来请求执行环境提供的各种服务或功能，从而完成应用程序自己的功能。于是我们可以将操作系统的定义简化成：应用程序的软件执行环境。一直在变化的定义操作系统的内涵和外延随着历史的发展也一直在变化，并没有类似于“1+1=2”这样的明确定义。如今在二十一世纪初期的大众眼中，操作系统就是他们的手机/终端上的软件系统，包括各种应用程序集合，图形界面和网络浏览器是其中重要的组成部分。但无论操作系统的内在实现和具体目标如何变化，其管理计算机硬件，给应用提供服务的核心定位没有变化。

从零开始搭建react开发环境（进阶篇）
type: item
date: 2022-02-14T18:05:00+08:00
description: [scode type="share"]上篇文章写的有些简略，而且有些地方在实际开发中还是有一定问题存在的，所以就有了这个进阶篇，这篇文章也是因为最近在搞前端开发，所以顺便总结一下[/scode]大纲首先我先说明一下这次使用的技术栈：less css预处理器，知名的还有sass、scss、stylus等react 主要的开发依赖库typescript 提供静态类型等诸多好处webpack 打包工具webpack-dev-server 开发用服务器react-router 路由管理库axios 网络请求库babel es新特性等代码的polyfill以这些作为主要的开发工具和生产依赖，除此之外还有一些关于webpack打包的插件，babel的插件之类的都会在后续进行一一说明步骤首先，我们从新建文件夹开始mkdir my-app \ && cd my-app \ && npm init -y然后我们就开始装依赖了npm install react react-dom react-router-dom axios --save-exact以上是几个生产环境所需要的依赖，所以放在一起进行安装，这里的--save-exact是为了把下载下来的依赖库版本固定，从而避免不同人开发时因为版本更新导致的诸多问题现在我们开始安装开发方面的依赖库，这里要使用一个参数--save-dev，说明这些依赖只在开发时使用npm install @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/preset-react @babel/runtime @babel/preset-typescript --save-dev \ && npm install webpack webpack-cli webpack-dev-middleware webpack-dev-server webpack-merge --save-dev \ && npm install typescript @types/react @types/react-dom @types/webpack @types/node ts-node --save-dev \ && npm install less less-loader css-loader babel-loader style-loader --save-dev \ && npm install clean-webpack-plugin css-minimizer-webpack-plugin fork-ts-checker-webpack-plugin html-webpack-plugin mini-css-extract-plugin --save-dev这里可以看到，有非常多的开发方面的依赖，在这里我一行一行的进行解释首先第一行，我们可以看到都是关于babel方面的一些库，下面是它们的作用@babel/core babel的核心库@babel/plugin-transform-runtime 和 @babel/runtime 提供对async/await功能的支持@babel/preset-env 提供对ES规范新功能的支持@babel/preset-react 提供对react相关代码规范的支持@babel/preset-typescript 提供对typescript代码的支持babel主要的作用就是为了提供对一些浏览器不支持的语法进行降级转换的作用，使得其可以在浏览器运行然后是第二行，关于webpack的一些功能模块webpack 和 webpack-cli 一个是核心库，一个是命令行支持webpack-dev-middleware 和 webpack-dev-server 对开发环境提供热替换开发服务器功能webpack-merge 用于对配置文件的merge，方便进行配置文件的区分管理（ts原生也可以合并，但是这个比较智能）第三行，关于typescript方面的一些模块声明文件还有typescript自身，这里就不做详细解释了第四行，提供了less支持和一些webpack读取相关文件的相应loader第五行，关于webpack的一些插件clean-webpack-plugin 在打包生成文件时会对之前的文件进行相应的清理操作，主要是为了修复webpack-dev-middleware在启用writeToDisk功能时，webpack原生配置启用自动清理却无法清理的bugcss-minimizer-webpack-plugin 对打包生成的css进行最小化压缩处理fork-ts-checker-webpack-plugin 提供在…

从零开始构建react+typescript开发环境
type: item
date: 2022-02-03T15:55:00+08:00
description: ps：很久没写博客，最近学了一些前端，觉得还是需要沉淀一些东西，于是便有了从零开始构建react开发环境的这篇文章开始初始化项目新建一个my-app文件夹mkdir my-app进入my-app文件夹cd my-app然后在my-app目录下创建src文件夹用来存放React代码mkdir src使用npm初始化项目npm init -y 此时的项目架构如下：安装typescript和react安装typescript全局安装是为了使用tsc命令，开发安装是必须的npm install -g typescript # 全局安装 npm install typescript --save-dev[scode type="blue"]注意：在这里简单介绍一下npm install命令的参数 --save-dev 和 --save的区别，一般来说使用--save-dev参数安装的npm包在最终打包的时候不会被包括到源码里去，所以类似babel和webpack这种进行项目工程构建或者代码编译的库应该用--save-dev来安装，而--save则是安装代码运行必须的库，比如react等。[/scode]配置typescript安装好之后输入tsc --init打开 tsconfig.json，进行以下配置。这将告诉 TypeScript 在哪找到我们的代码。{ "compilerOptions": { "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ "lib": ["es5", "es6", "dom"], /* Specify library files to be included in the compilation. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ "sourceMap": true, /* Generates corresponding '.map' file. */ "outDir": "./dist/", /* Redirect output structure to the directory. */ "removeComments": true, /* Do not emit comments to output. */ "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ "preserveConstEnums": true }, "include": [ "./src/**/*" ] }安装react和react的typescript支持npm install react react-dom --save && npm install @types/react --save-dev && npm install @types/react-dom --save-dev安装webpack在终端输入以下指令npm install webpack --save-dev && npm install webpack-cli --save-dev && npm install webpack-dev-s…

因为Flash停止服务后学校的平台仍使用flash，我打包了一个可用flash的chromium浏览器
type: item
date: 2021-03-06T18:35:00+08:00
description: 可运行flash的chrome使用方法解压压缩包到一个文件夹中，创建chrome.exe的快键方式，在目标栏中，引号的后面空一格输入--ppapi-flash-path="解压的路径\pepflashplayer64_34_0_0_465.dll" --ppapi-flash-version="34.0.0.465" --allow-outdated-plugins弄完之后，打开该快捷方式，在导航栏输入chrome://version，如果出现以下字样则说明安装成功[button color="success" icon="iconfont icon-links" url="https://wws.lanzoui.com/iDnDepanxyf"]下载 提取码33ur[/button]更新日志：2021/5/20 替换flash内核 34.0.0.105->34.0.0.465