【NJU OS】12 进程的地址空间
进程的地址空间
char *p 可以和 intptr_t 互相转换
- 可以指向 “任何地方”
- 合法的地址 (可读或可写)
- 代码 (main, %rip 会从此处取出待执行的指令),只读
- 数据 (static int x),读写
- 堆栈 (int y),读写
- 运行时分配的内存 (???),读写
- 动态链接库 (???)
- 非法的地址
- NULL,导致 segmentation fault
外挂:攻与防
控制/数据流完整性
- 保护进程的完整性
- 独立的进程/驱动做完整性验证
- 保护隐私数据不被其他进程读写
- 拦截向本进程的 ReadProcessMemory 和 WriteProcessMemory,发现后立即拒绝执行
- 例子
- Denuvo Anti-Cheat, Epic Anti-Cheat Interface
其他解决方法
- AI 监控/社会工程学:如果你强得不正常,当然要盯上你
- 云/沙盒 (Enclave) 渲染:“计算不再信任操作系统”
总结
- 进程的地址空间
- 能文件关联的、带有访问权限的连续内存段
- a.out, ld.so, libc.so, heap, stack, vdso
- 能文件关联的、带有访问权限的连续内存段
- 进程地址空间的管理 API
- mmap
【NJU OS】11 操作系统上的进程
操作系统启动后到底做了什么?
从系统启动到第一个进程
操作系统会加载 “第一个程序”
- RTFSC (latest Linux Kernel)
- 如果没有指定启动选项 init=,按照 “默认列表” 尝试一遍
- 从此以后,Linux Kernel 就进入后台,成为 “中断/异常处理程序”
fork()
做一份状态机完整的复制 (内存、寄存器现场)
execve()
_exit()
结束程序执行的三种方法
exit 的几种写法 (它们是不同)
- exit(0) - stdlib.h 中声明的 libc 函数
- 会调用 atexit
- _exit(0) - glibc 的 syscall wrapper
- 执行 “exit_group” 系统调用终止整个进程 (所有线程) 细心的同学已经在 strace 中发现了
- 不会调用 atexit
- syscall(SYS_exit, 0)
- 执行 “exit” 系统调用终止当前线程
- 不会调用 atexit
总结
- 对 “操作系统” 的完整理解
- CPU Reset → Firmware → Loader → Kernel _start() → 执行第一个程序 /bin/init → 中断/异常处理程序
- 一个最小的 Linux 系统的例子
- 进程管理 API
- fork, execve, exit: 状态机的复制、重置、销毁
- 理论上就可以实现 “各种功能” 了!
【前端】React笔记
React基础
什么是JSX?
- JavaScripts + XML: 表示在JS代码中编写HTML模版结构,它是React中编写UI模版的方式。
- JSX并不是标准的JS语法,它是JS的语法扩展,浏览器本身不能识别,需要通过解析工具(BABEL)做解析之后才能在浏览器中运行
在JSX中使用JS表达式
在JSX中可以通过 大括号语法{}
识别JavaScript中的表达式
- 使用引号传递字符串
- 使用JavaScript变量
- 函数调用和方法调用
- 使用JavaScript对象
React 事件绑定
- React 基础事件绑定:on + 事件名称 = { 事件处理程序 },整体上遵循驼峰命名法
- 使用事件对象参数:在事件回调函数中设置形参e
- 传递自定义参数:事件绑定的位置改造成箭头函数的写法
- 同时传递事件对象和自定义参数:在事件绑定的位置传递事件实参e和自定义参数,clickHandler中声明形参,注意顺序对应
React 组件
在React中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图UI, 渲染组件只需要把组件当成标签书写即可
- 状态不可变
在React中,状态被认为是只读的,我们应该始终替换它而不是修改它,直接修改状态不能引发视图更新
- 修改对象状态
对于对象类型的状态变量,应该始终传给set方法一个全新的对象来进行修改
React 副作用管理
useEffect
useEffect是一个React Hook函数,用于在React组件中创s建不是由事件引起而是由渲染本身引起的操作(副作用), 比 如发送AJAX请求,更改DOM等等
useEffect依赖说明
useEffect副作用函数的执行时机存在多种情况,根据传入依赖项的不同,会有不同的执行表现
没有依赖项 组件初始渲染 + 组件更新时执行
空数组依赖 只在初始渲染时执行一次
添加特定依赖项 组件初始渲染 + 依赖项变化时执行
React 路由
1 | npm create-react-app react-router-pro # 使用CRA创建项目 |
路由导航
声明式导航
声明式导航是指通过在模版中通过 组件描述出要跳转到哪里去,比如后台管理系统的左侧菜单通常使用这种方式进行
编程式导航
编程式导航是指通过 useNavigate 钩子得到导航方法,然后通过调用方法以命令式的形式进行路由跳转,比如想在登录请求完毕之后跳转就可以选择这种方式,更加灵活
导航传参
嵌套路由配置
什么是嵌套路由?
在一级路由中又内嵌了其他路由,这种关系就叫做嵌套路由,嵌套至一级路由内的路由又称作二级路由,例如:
嵌套路由配置
- 使用 children属性配置路由嵌套关系
- 使用
组件配置二级路由渲染位置
craco
- 配置‘@’全局路径
Mock
前端在没有后端接口支持下进行接口数据模拟
插件
- lodash
- classnames:classnames是一个简单的JS库,可以非常方便的通过条件动态控制class类名的显示
- day.js
- craco
- 配置‘@’全局路径
【技术】HTTP常用知识
HTTP常见状态码
1XX:消息
- 101 Switch Protocol:升级协议,如从 http 到 ws,此时需要反向代理支持,如 Nginx。
2XX:成功
- 200 Ok: 成功
3XX:重定向
- 301 Moved Permanently:永久重定向
- 302 Found:临时重定向
- 304 Not Modified:自上次请求,未修改的文件
4XX:客户端错误
- 400 Bad Request:错误的请求
- 401 Unauthorized:未被授权,需要身份验证,例如token信息等等
- 403 Forbidden:请求被拒绝
- 404 Not Found:资源缺失,接口不存在,或请求的文件不存在等等
5XX:服务端错误
- 500 Internal Server Error:服务器端未知错误,一般不允许出现。
- 502 Bad Gateway:从上游应用层未返回响应,上游应用层挂了
- 503 Service Unavailable:服务暂时无法使用
- 504 Gateway Timeout:网关超时,上游应用层迟迟未响应
HTTP报文
GET和POST差异
- GET在浏览器回退时是无害的,而POST会再次提交请求。
- GET产生的URL地址可以被Bookmark,而POST不可以。
- GET请求会被浏览器主动cache,而POST不会,除非手动设置。
- GET请求只能进行url编码,而POST支持多种编码方式。
- GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
- GET请求在URL中传送的参数是有长度限制的,而POST没有。
- 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
- GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
- GET参数通过URL传递,POST放在Request body中。
- GET产生一个TCP数据包;POST产生两个TCP数据包。
HTTP 缓存
Http 缓存分为以下两种,两者都是通过 HTTP 响应头控制缓存
- 强制缓存
再次请求时无需再向服务器发送请求
- 协商缓存
再次请求时,需要向服务器校验新鲜度,如果资源是新鲜的,返回 304,从浏览器获取资源
缓存策略
关于 http 缓存配置的最佳实践为以下两条:
文件路径中带有 hash 值:一年的强缓存。因为该文件的内容发生变化时,会生成一个带有新的 hash 值的 URL。前端将会发起一个新的 URL 的请求。配置响应头 Cache-Control: public,max-age=31536000,immutable
文件路径中不带有 hash 值:协商缓存。大部分为 public 下文件。配置响应头 Cache-Control: no-cache 与 etag/last-modified
HTTP Header
gzip 配置
一句话:gzip 的核心是 Deflate,而它使用了 LZ77 算法与 Huffman 编码来压缩文件,重复度越高的文件可压缩的空间就越大。
因此 gzip 用于 HTTP 文件传输中,比如 JS、CSS 等,但一般不会压缩图片。在 HTTP Response 报文中,用 Content-Encoding 指明使用 gzip 压缩,而以下响应头在大部分生产环境的响应报文中都可以看到。
跨域
HTTPS
HTTPS中如何保证证书是可信任的
数字签名: 数字签名的原理其实很简单,就是把公钥私钥的用法反过来,之前是公钥加密、私钥解密,现在是私钥加密、公钥解密。但又因为非对称加密效率太低,所以私钥只加密原文的摘要,这样运算量就小的多,而且得到的数字签名也很小,方便保管和传输。
数字证书和CA: 因为公钥是任何人都可以发布的,所以我们需要引入第三方来保证公钥的可信度,这个“第三方”就是我们常说的 CA(Certificate Authority,证书认证机构),CA 对公钥的签名认证也是有格式的,要包含公钥的序列号、用途、颁发者、有效时间等等,把这些打成一个包再签名,完整地证明公钥关联的各种信息,形成“数字证书”(Certificate)。
小一点的 CA 可以让大 CA 签名认证,但链条的最后,也就是 Root CA,就只能自己证明自己了,这个就叫“自签名证书”(Self-Signed Certificate)或者“根证书”(Root Certificate)。你必须相信,否则整个证书信任链就走不下去了。
HTTP2
http2与http1.1有什么改进?
- 二进制分帧
- 请求多路复用 (Stream/Frame)
- 头部压缩: (HPack),其中原理是哈夫曼编码及索引表
- 服务端推送: (Server Push)
http2 中 Stream 与 Frame 是什么关系?
- Stream 为 Request/Response 报文的双向通道,一个完整资源的请求与相应是一个 stream,特殊的 stream 作为 Settings、Window_Update 等 Frame 发送的通道
- Frame 为 http2 通信的最小单位,有 Data、Headers 等,一个 Stream 包含多个 Frame,如一条 http 请求包含 Header、Data Frame 等
HTTP3
http3 基于 UDP 协议,这是与以前版本的 http 最大的不同,可以解决 http2 TCP 连接阻塞的问题。
【前端】基于Vue3的PC电商前台项目实现
前言
初学Vue的常规项目,不涉及后端接口。
项目介绍
基于Vue3和ElementPlus搭建的某服装电商前台。
技术栈
- Vue3
- Vue-Router
- Vite
- Pinia
- ElementPlus
内容
使用Vite进行项目构建,根据甲方需求,精确还原UI设计。
对Axios进行简单二次封装,如请求拦截器和响应拦截器。
进行路由设计,实现基于业务逻辑的组件拆分。
实现购物车组件及SKU组件的封装及优化。
使用Pinia进行集中状态管理,并处理用户数据路由缓存问题。
实现长页面吸顶交互、图片懒加载、面包屑、放大镜、跳转第三方支付等功能,提高用户交互体验。
心得记录
用别人的组件时需注意
- 看文档
- 没有文档时,看props和emit
全局注册
- 目前Vue3中vue component文件夹下的组件默认注册
解决路由缓存问题
- 一级分类的切换正好满足上面的条件,组件实例复用,导致分类数据无法更新
- 解决思路:1. 让组件实例不复用,强制销毁重建 2. 监听路由变化,变化之后执行数据更新操作
- 可以给router-view添加key,也可以使用beforeRouteUpdate导航钩子。在意性能问题,选择onBeforeUpdate, 精细化控制;不在意性能问题,选择key,简单粗暴
持久化用户数据
- Token
【NJU OS】10 状态机模型的应用
状态机模型:理解编译器和现代 CPU
编译器:源代码 S (状态机) → 二进制代码 C (状态机)
C=compile(S)
编译 (优化) 的正确性 (Soundness):
- S 与 C 的可观测行为严格一致
- system calls; volatile variable loads/stores; termination
超标量 (superscalar)/乱序执行处理器
- 允许在状态机上 “跳跃”
- ilp-demo.c
查看状态机执行
Trace 和调试器
程序执行 = 状态机执行
- 我们能不能 “hack” 进这个状态机
- 观察状态机的执行
- strace/gdb
- 甚至记录和改变状态机的执行
- 观察状态机的执行
应用 (1): Time-Travel Debugging
应用 (2): Record & Replay
总结
- 编程 (状态机) 就是全世界
- 状态机可以帮我们
- 建立物理世界的公理体系
- 理解调试器、Trace, profiler
- 自动分析程序的执行 (model checker)
【NJU OS】09 操作系统的状态机模型
动手写操作系统
大学的真正意义
将已有的知识和方法重新消化,为大家建立好 “台阶”,在有限的时间里迅速赶上数十年来建立起的学科体系。
“专业世界观” 的学习方法
- 经典研究论文 (OSDI, SOSP, ATC, EuroSys, …)
- 久经考验的经典教学材料 (xv6, OSTEP, CSAPP, …)
- 海量的开源工具 (GNU 系列, qemu, gdb, …)
- 第三方资料,慎用 (tutorials, osdev wiki, …)
硬件和软件的桥梁
Bare-metal 与程序员的约定
为了让计算机能运行任何我们的程序,一定存在软件/硬件的约定
- CPU reset 后,处理器处于某个确定的状态
- PC 指针一般指向一段 memory-mapped ROM
- ROM 存储了厂商提供的 firmware (固件)
- 处理器的大部分特性处于关闭状态
- 缓存、虚拟存储、……
- PC 指针一般指向一段 memory-mapped ROM
- Firmware (固件,厂商提供的代码)
- 将用户数据加载到内存
- 例如存储介质上的第二级 loader (加载器)
- 或者直接加载操作系统 (嵌入式系统)
- 将用户数据加载到内存
x86 Family: CPU Reset 行
CPU Reset 之后:发生了什么?
- 从 PC (CS:IP) 指针处取指令、译码、执行……
- 从 firmware 开始执行
- ffff0 通常是一条向 firmware 跳转的 jmp 指令
Firmware: BIOS vs. UEFI
- 都是主板/主板上外插设备的软件抽象
- 支持系统管理程序运行
- Legacy BIOS (Basic I/O System)
- UEFI (Unified Extensible Firmware Interface)
Legacy BIOS: 约定
Firmware 必须提供机制,将用户数据载入内存
Legacy BIOS 把第一个可引导设备的第一个扇区加载到物理内存的 7c00 位置
此时处理器处于 16-bit 模式
规定 CS:IP = 0x7c00, (R[CS] << 4) | R[IP] == 0x7c00
可能性1:CS = 0x07c0, IP = 0
可能性2:CS = 0, IP = 0x7c00
其他没有任何约束
调试 QEMU: 确认 Firmware 的行为
鸡和蛋的问题解决
有个原始的鸡:Firmware
- 代码直接存在于硬件里
- CPU Reset 后 Firmware 会执行
- 加载 512 字节到内存 (Legacy Boot)
- 然后功成身退
Firmware 的另一用处
- 放置一些 “绝对安全的代码”
- BIOS 中断 (Hello World 是如何被打印的)
- ARM Trusted Firmware
- Boot-Level 1, 2, 3.1, 3.2, 3.3
- U-Boot: the universal boot loader
今天的 Firmware: UEFI
UEFI 上的操作系统加载
操作系统的状态机模型(需复习)
“操作系统” 的状态机已经启动
操作系统:是个 C 程序
总结
- 一切皆可调试 (包括 firmware)
- 理解操作系统是如何被启动的
- 学会使用 gdb (必备生存技能)
- 操作系统也是程序
- AbstractMachine 扩展了程序的语义,仅此而已
【NJU OS】08 并发 Bug 和应对
应对Bug的方法
基本思路:否定你自己
虽然不太愿意承认,但始终假设自己的代码是错的。
Bug 多的根本原因:编程语言的缺陷
软件是需求 (规约) 在计算机数字世界的投影。
更实在的方法:防御性编程
把程序需要满足的条件用 assert 表达出来。
并发 Bug:死锁 (Deadlock)
AA-Deadlock
ABBA-Deadlock
避免死锁
死锁产生的四个必要条件 (Edward G. Coffman, 1971):
- 互斥:一个资源每次只能被一个进程使用
- 请求与保持:一个进程请求资阻塞时,不释放已获得的资源
- 不剥夺:进程已获得的资源不能强行剥夺
- 循环等待:若干进程之间形成头尾相接的循环等待资源关系
并发 Bug:数据竞争 (Data Race)
数据竞争
不同的线程同时访问同一段内存,且至少有一个是写。
Peterson 算法告诉大家:
你们写不对无锁的并发程序,所以事情反而简单了
用互斥锁保护好共享数据,消灭一切数据竞争
其他类型并发Bug
原子性违反(AV)
顺序违反(Ov)
应对并发Bug的方法
Lockedep
ThreadSanitizer
更多的检查:动态程序分析
动态分析工具:Sanitizers
总结
- 常见的并发 bug
- 死锁、数据竞争、原子性/顺序违反
- 不要盲目相信自己:检查、检查、检查
- 防御性编程:检查
- 动态分析:打印 + 检查