first commit.
22
.gitignore
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
docs/.vitepress/cache
|
||||
docs/.vitepress/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
1
.prettierrc.toml
Normal file
@@ -0,0 +1 @@
|
||||
semi = false
|
||||
55
docs/.vitepress/config.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { defineConfig } from "vitepress"
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
lang: "zh-Hans-CN",
|
||||
title: "徐越的编程书",
|
||||
markdown: {
|
||||
container: {
|
||||
tipLabel: '提示',
|
||||
warningLabel: '警告',
|
||||
dangerLabel: '危险',
|
||||
infoLabel: '信息',
|
||||
detailsLabel: '详细信息'
|
||||
}
|
||||
},
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
nav: [
|
||||
{ text: "首页", link: "/" },
|
||||
{
|
||||
text: "Python",
|
||||
items: [
|
||||
{ text: "基础知识", link: "/python/index.md" },
|
||||
{ text: "办公自动化", link: "/oa/index.md" },
|
||||
{ text: "网络爬虫", link: "/crawler/index.md" },
|
||||
],
|
||||
},
|
||||
{ text: "计算机科学", link: "/cs/00/index.md" },
|
||||
],
|
||||
sidebar: {
|
||||
"/python": [{}],
|
||||
"/oa": [{}],
|
||||
"/crawler": [{}],
|
||||
"/cs": [
|
||||
{ text: "简介", link: "/cs/00/index.md" },
|
||||
{ text: "计算机早期历史", link: "/cs/01/index.md" },
|
||||
{ text: "电子计算机", link: "/cs/02/index.md" },
|
||||
{ text: "布尔逻辑和逻辑门", link: "/cs/03/index.md" },
|
||||
{ text: "二进制", link: "/cs/04/index.md" },
|
||||
{ text: "算术逻辑单元", link: "/cs/05/index.md" },
|
||||
{ text: "寄存器和内存", link: "/cs/06/index.md" },
|
||||
{ text: "中央处理器", link: "/cs/07/index.md" },
|
||||
{ text: "指令和程序", link: "/cs/08/index.md" },
|
||||
{ text: "高级 CPU 设计", link: "/cs/09/index.md" },
|
||||
{ text: "早期的编程方式", link: "/cs/10/index.md" },
|
||||
{ text: "编程语言发展史", link: "/cs/11/index.md" },
|
||||
{ text: "编程原理 - 语句和函数", link: "/cs/12/index.md" },
|
||||
{ text: "算法入门", link: "/cs/13/index.md" },
|
||||
{ text: "......", link: "/cs/14/index.md" },
|
||||
],
|
||||
},
|
||||
outlineTitle: '目录',
|
||||
docFooter: { prev: '上一篇', next: '下一篇' }
|
||||
},
|
||||
})
|
||||
110
docs/.vitepress/theme/codemirror/createTheme.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { EditorView } from "@codemirror/view"
|
||||
import { Extension } from "@codemirror/state"
|
||||
import {
|
||||
HighlightStyle,
|
||||
TagStyle,
|
||||
syntaxHighlighting,
|
||||
} from "@codemirror/language"
|
||||
|
||||
interface Options {
|
||||
/**
|
||||
* Theme variant. Determines which styles CodeMirror will apply by default.
|
||||
*/
|
||||
variant: Variant
|
||||
|
||||
/**
|
||||
* Settings to customize the look of the editor, like background, gutter, selection and others.
|
||||
*/
|
||||
settings: Settings
|
||||
|
||||
/**
|
||||
* Syntax highlighting styles.
|
||||
*/
|
||||
styles: TagStyle[]
|
||||
}
|
||||
|
||||
type Variant = "light" | "dark"
|
||||
|
||||
interface Settings {
|
||||
/**
|
||||
* Editor background.
|
||||
*/
|
||||
background: string
|
||||
|
||||
/**
|
||||
* Default text color.
|
||||
*/
|
||||
foreground: string
|
||||
|
||||
/**
|
||||
* Caret color.
|
||||
*/
|
||||
caret: string
|
||||
|
||||
/**
|
||||
* Selection background.
|
||||
*/
|
||||
selection: string
|
||||
|
||||
/**
|
||||
* Background of highlighted lines.
|
||||
*/
|
||||
lineHighlight: string
|
||||
|
||||
/**
|
||||
* Gutter background.
|
||||
*/
|
||||
gutterBackground: string
|
||||
|
||||
/**
|
||||
* Text color inside gutter.
|
||||
*/
|
||||
gutterForeground: string
|
||||
|
||||
gutterBorderRight: string
|
||||
}
|
||||
|
||||
export const createTheme = ({
|
||||
variant,
|
||||
settings,
|
||||
styles,
|
||||
}: Options): Extension => {
|
||||
const theme = EditorView.theme(
|
||||
{
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
"&": {
|
||||
backgroundColor: settings.background,
|
||||
color: settings.foreground,
|
||||
},
|
||||
".cm-content": {
|
||||
caretColor: settings.caret,
|
||||
},
|
||||
".cm-cursor, .cm-dropCursor": {
|
||||
borderLeftColor: settings.caret,
|
||||
},
|
||||
"&.cm-focused .cm-selectionBackgroundm .cm-selectionBackground, .cm-content ::selection":
|
||||
{
|
||||
backgroundColor: settings.selection,
|
||||
},
|
||||
".cm-activeLine": {
|
||||
backgroundColor: settings.lineHighlight,
|
||||
},
|
||||
".cm-gutters": {
|
||||
backgroundColor: settings.gutterBackground,
|
||||
borderRight: settings.gutterBorderRight,
|
||||
color: settings.gutterForeground,
|
||||
},
|
||||
".cm-activeLineGutter": {
|
||||
backgroundColor: settings.lineHighlight,
|
||||
},
|
||||
},
|
||||
{
|
||||
dark: variant === "dark",
|
||||
},
|
||||
)
|
||||
|
||||
const highlightStyle = HighlightStyle.define(styles)
|
||||
const extension = [theme, syntaxHighlighting(highlightStyle)]
|
||||
|
||||
return extension
|
||||
}
|
||||
149
docs/.vitepress/theme/codemirror/oneDark.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { EditorView } from "@codemirror/view"
|
||||
import { Extension } from "@codemirror/state"
|
||||
import { HighlightStyle, syntaxHighlighting } from "@codemirror/language"
|
||||
import { tags as t } from "@lezer/highlight"
|
||||
|
||||
// Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors
|
||||
|
||||
const chalky = "#e5c07b",
|
||||
coral = "#e06c75",
|
||||
cyan = "#56b6c2",
|
||||
invalid = "#ffffff",
|
||||
ivory = "#abb2bf",
|
||||
stone = "#7d8799", // Brightened compared to original to increase contrast
|
||||
malibu = "#61afef",
|
||||
sage = "#98c379",
|
||||
whiskey = "#d19a66",
|
||||
violet = "#c678dd",
|
||||
darkBackground = "#26262a",
|
||||
highlightBackground = "#2c313a",
|
||||
background = "#101014", // naive-ui
|
||||
tooltipBackground = "#353a42",
|
||||
selection = "#3E4451",
|
||||
cursor = "#528bff"
|
||||
|
||||
/// The editor theme styles for One Dark.
|
||||
const oneDarkTheme = EditorView.theme(
|
||||
{
|
||||
"&": {
|
||||
color: ivory,
|
||||
backgroundColor: background,
|
||||
},
|
||||
|
||||
".cm-content": {
|
||||
caretColor: cursor,
|
||||
},
|
||||
|
||||
".cm-cursor, .cm-dropCursor": { borderLeftColor: cursor },
|
||||
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":
|
||||
{ backgroundColor: selection },
|
||||
|
||||
".cm-panels": { backgroundColor: darkBackground, color: ivory },
|
||||
".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
|
||||
".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" },
|
||||
|
||||
".cm-searchMatch": {
|
||||
backgroundColor: "#72a1ff59",
|
||||
outline: "1px solid #457dff",
|
||||
},
|
||||
".cm-searchMatch.cm-searchMatch-selected": {
|
||||
backgroundColor: "#6199ff2f",
|
||||
},
|
||||
|
||||
".cm-activeLine": { backgroundColor: "#6699ff0b" },
|
||||
".cm-selectionMatch": { backgroundColor: "#aafe661a" },
|
||||
|
||||
"&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": {
|
||||
backgroundColor: "#bad0f847",
|
||||
},
|
||||
|
||||
".cm-gutters": {
|
||||
backgroundColor: background,
|
||||
color: stone,
|
||||
border: "none",
|
||||
},
|
||||
|
||||
".cm-activeLineGutter": {
|
||||
backgroundColor: highlightBackground,
|
||||
},
|
||||
|
||||
".cm-foldPlaceholder": {
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
color: "#ddd",
|
||||
},
|
||||
|
||||
".cm-tooltip": {
|
||||
border: "none",
|
||||
backgroundColor: tooltipBackground,
|
||||
},
|
||||
".cm-tooltip .cm-tooltip-arrow:before": {
|
||||
borderTopColor: "transparent",
|
||||
borderBottomColor: "transparent",
|
||||
},
|
||||
".cm-tooltip .cm-tooltip-arrow:after": {
|
||||
borderTopColor: tooltipBackground,
|
||||
borderBottomColor: tooltipBackground,
|
||||
},
|
||||
".cm-tooltip-autocomplete": {
|
||||
"& > ul > li[aria-selected]": {
|
||||
backgroundColor: highlightBackground,
|
||||
color: ivory,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ dark: true },
|
||||
)
|
||||
|
||||
/// The highlighting style for code in the One Dark theme.
|
||||
const oneDarkHighlightStyle = HighlightStyle.define([
|
||||
{ tag: t.keyword, color: violet },
|
||||
{
|
||||
tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
|
||||
color: coral,
|
||||
},
|
||||
{ tag: [t.function(t.variableName), t.labelName], color: malibu },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey },
|
||||
{ tag: [t.definition(t.name), t.separator], color: ivory },
|
||||
{
|
||||
tag: [
|
||||
t.typeName,
|
||||
t.className,
|
||||
t.number,
|
||||
t.changed,
|
||||
t.annotation,
|
||||
t.modifier,
|
||||
t.self,
|
||||
t.namespace,
|
||||
],
|
||||
color: chalky,
|
||||
},
|
||||
{
|
||||
tag: [
|
||||
t.operator,
|
||||
t.operatorKeyword,
|
||||
t.url,
|
||||
t.escape,
|
||||
t.regexp,
|
||||
t.link,
|
||||
t.special(t.string),
|
||||
],
|
||||
color: cyan,
|
||||
},
|
||||
{ tag: [t.meta, t.comment], color: stone },
|
||||
{ tag: t.strong, fontWeight: "bold" },
|
||||
{ tag: t.emphasis, fontStyle: "italic" },
|
||||
{ tag: t.strikethrough, textDecoration: "line-through" },
|
||||
{ tag: t.link, color: stone, textDecoration: "underline" },
|
||||
{ tag: t.heading, fontWeight: "bold", color: coral },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey },
|
||||
{ tag: [t.processingInstruction, t.string, t.inserted], color: sage },
|
||||
{ tag: t.invalid, color: invalid },
|
||||
])
|
||||
|
||||
/// Extension to enable the One Dark theme (both the editor theme and
|
||||
/// the highlight style).
|
||||
export const oneDark: Extension = [
|
||||
oneDarkTheme,
|
||||
syntaxHighlighting(oneDarkHighlightStyle),
|
||||
]
|
||||
83
docs/.vitepress/theme/codemirror/smoothy.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { tags as t } from "@lezer/highlight"
|
||||
import { createTheme } from "./createTheme"
|
||||
|
||||
// Author: Kenneth Reitz
|
||||
export const smoothy = createTheme({
|
||||
variant: "light",
|
||||
settings: {
|
||||
background: "#FFFFFF",
|
||||
foreground: "#000000",
|
||||
caret: "#000000",
|
||||
selection: "#FFFD0054",
|
||||
gutterBackground: "#FFFFFF",
|
||||
gutterForeground: "#00000070",
|
||||
gutterBorderRight: "none",
|
||||
lineHighlight: "#00000008",
|
||||
},
|
||||
styles: [
|
||||
{
|
||||
tag: t.comment,
|
||||
color: "#CFCFCF",
|
||||
},
|
||||
{
|
||||
tag: [t.number, t.bool, t.null],
|
||||
color: "#E66C29",
|
||||
},
|
||||
{
|
||||
tag: [
|
||||
t.className,
|
||||
t.definition(t.propertyName),
|
||||
t.function(t.variableName),
|
||||
t.labelName,
|
||||
t.definition(t.typeName),
|
||||
],
|
||||
color: "#2EB43B",
|
||||
},
|
||||
{
|
||||
tag: t.keyword,
|
||||
color: "#D8B229",
|
||||
},
|
||||
{
|
||||
tag: t.operator,
|
||||
color: "#4EA44E",
|
||||
fontWeight: "bold",
|
||||
},
|
||||
{
|
||||
tag: [t.definitionKeyword, t.modifier],
|
||||
color: "#925A47",
|
||||
},
|
||||
{
|
||||
tag: t.string,
|
||||
color: "#704D3D",
|
||||
},
|
||||
{
|
||||
tag: t.typeName,
|
||||
color: "#2F8996",
|
||||
},
|
||||
{
|
||||
tag: [t.variableName, t.propertyName],
|
||||
color: "#77ACB0",
|
||||
},
|
||||
{
|
||||
tag: t.self,
|
||||
color: "#77ACB0",
|
||||
fontWeight: "bold",
|
||||
},
|
||||
{
|
||||
tag: t.regexp,
|
||||
color: "#E3965E",
|
||||
},
|
||||
{
|
||||
tag: [t.tagName, t.angleBracket],
|
||||
color: "#BAA827",
|
||||
},
|
||||
{
|
||||
tag: t.attributeName,
|
||||
color: "#B06520",
|
||||
},
|
||||
{
|
||||
tag: t.derefOperator,
|
||||
color: "#000",
|
||||
},
|
||||
],
|
||||
})
|
||||
36
docs/.vitepress/theme/components/Author.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="author">
|
||||
<img class="photo" :src="'/avatars/' + name + '.svg'" alt="avatar" />
|
||||
<div class="intro">
|
||||
<span class="name">{{ name }}</span>
|
||||
<span class="title">{{ title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
name: string
|
||||
title: string
|
||||
}
|
||||
defineProps<Props>()
|
||||
</script>
|
||||
<style scoped>
|
||||
.author {
|
||||
display: inline-flex;
|
||||
margin: 2rem 4rem 0 0;
|
||||
}
|
||||
|
||||
.photo {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.intro {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.name {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
37
docs/.vitepress/theme/components/BVideo.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div class="video">
|
||||
<iframe :src="url"></iframe>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue"
|
||||
interface Props {
|
||||
src: string
|
||||
p?: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const url = computed(() => {
|
||||
let url = `https://player.bilibili.com/player.html?bvid=${props.src}`
|
||||
if (props.p) {
|
||||
url += `&page=${props.p}`
|
||||
}
|
||||
return url
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
.video {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-bottom: calc(56.25% + 68px);
|
||||
}
|
||||
|
||||
.video > iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
83
docs/.vitepress/theme/components/CodeEditor.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue"
|
||||
import { useData } from "vitepress"
|
||||
import { Codemirror } from "vue-codemirror"
|
||||
import { cpp } from "@codemirror/lang-cpp"
|
||||
import { python } from "@codemirror/lang-python"
|
||||
import { EditorView } from "@codemirror/view"
|
||||
import { VPButton } from "vitepress/theme"
|
||||
import { oneDark } from "../codemirror/oneDark"
|
||||
import { smoothy } from "../codemirror/smoothy"
|
||||
import { asyncRun } from "./py"
|
||||
|
||||
interface Props {
|
||||
modelValue: string
|
||||
lang?: "python" | "c"
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
lang: "python",
|
||||
})
|
||||
|
||||
const { isDark } = useData()
|
||||
|
||||
const lang = computed(() => {
|
||||
if (props.lang === "python") {
|
||||
return python()
|
||||
}
|
||||
return cpp()
|
||||
})
|
||||
|
||||
const styleTheme = EditorView.baseTheme({
|
||||
"& .cm-scroller": {
|
||||
"font-family": "Consolas",
|
||||
},
|
||||
"&.cm-editor.cm-focused": {
|
||||
outline: "none",
|
||||
},
|
||||
})
|
||||
|
||||
const input = ref("")
|
||||
const output = ref("")
|
||||
const code = ref(props.modelValue)
|
||||
|
||||
async function run() {
|
||||
const ev = await asyncRun(code.value, input.value)
|
||||
output.value = ev.result
|
||||
}
|
||||
|
||||
function reset() {
|
||||
code.value = props.modelValue
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<p>代码编辑区</p>
|
||||
<Codemirror
|
||||
v-model="code"
|
||||
indentWithTab
|
||||
:extensions="[styleTheme, lang, isDark ? oneDark : smoothy]"
|
||||
:tabSize="4"
|
||||
/>
|
||||
<p>输入框</p>
|
||||
<Codemirror
|
||||
v-model="input"
|
||||
indentWithTab
|
||||
:extensions="[styleTheme, isDark ? oneDark : smoothy]"
|
||||
:tabSize="4"
|
||||
/>
|
||||
<p>结果</p>
|
||||
<p>{{ output }}</p>
|
||||
<div :class="$style.actions">
|
||||
<VPButton :class="$style.run" @click="run" text="运行"></VPButton>
|
||||
<VPButton @click="reset" theme="alt" text="重置"></VPButton>
|
||||
</div>
|
||||
</template>
|
||||
<style module>
|
||||
.actions {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.run {
|
||||
margin-right: 20px;
|
||||
}
|
||||
</style>
|
||||
24
docs/.vitepress/theme/components/py.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
const worker = new Worker("/worker.js")
|
||||
|
||||
const callbacks = {}
|
||||
|
||||
worker.onmessage = (event) => {
|
||||
const { id, ...data } = event.data
|
||||
const onSuccess = callbacks[id]
|
||||
delete callbacks[id]
|
||||
onSuccess(data)
|
||||
}
|
||||
|
||||
const asyncRun = (() => {
|
||||
let id = 0 // identify a Promise
|
||||
return (python: string, input: string) => {
|
||||
// the id could be generated more carefully
|
||||
id = (id + 1) % Number.MAX_SAFE_INTEGER
|
||||
return new Promise<{ result: string; error: string }>((onSuccess) => {
|
||||
callbacks[id] = onSuccess
|
||||
worker.postMessage({ python, input, id })
|
||||
})
|
||||
}
|
||||
})()
|
||||
|
||||
export { asyncRun }
|
||||
14
docs/.vitepress/theme/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { Theme } from "vitepress"
|
||||
import DefaultTheme from "vitepress/theme"
|
||||
import BVideo from "./components/BVideo.vue"
|
||||
import Author from "./components/Author.vue"
|
||||
import CodeEditor from "./components/CodeEditor.vue"
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
async enhanceApp({ app }) {
|
||||
app.component("bvideo", BVideo)
|
||||
app.component("author", Author)
|
||||
app.component("code-editor", CodeEditor)
|
||||
},
|
||||
} satisfies Theme
|
||||
1
docs/crawler/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# python
|
||||
BIN
docs/cs/00/cc.png
Normal file
|
After Width: | Height: | Size: 434 KiB |
BIN
docs/cs/00/cs.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
15
docs/cs/00/index.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# 简介
|
||||
|
||||

|
||||
|
||||
CrashCourse 是网上非常著名的在线速成课程,课程覆盖文化、历史、哲学、物理、化学、生物、天文、商业、经济、计算机、艺术、游戏、建筑、神秘学等。
|
||||
|
||||
计算机科学是一门系统性研究信息和计算的理论基础以及它们在计算机系统中如何实现与应用的实用技术的学科。虽然叫做计算机科学,但是其相当一部分领域都不涉及计算机本身的研究,更多的与数学、工程学、认知科学和经济学有交叉。
|
||||
|
||||

|
||||
|
||||
CrashCourse 的这一套 [**计算机科学速成课**](https://www.bilibili.com/video/BV1EW411u7th) 帮助我们快速学习计算机科学,每个视频十几分钟,一共四十课时。跟着主讲人 Carrie Anne 学完整个课程,会让我们对计算机科学有大致了解。
|
||||
|
||||
我会把原视频和自己的笔记一起放在网站上方便大家阅读,同学们可以跟我一样边看视频边记笔记,由于原视频是英文的,也感谢网友们自发翻译成中文,他们是知识的传播者。
|
||||
|
||||
好了,我们一起进入第一课时的学习:了解计算机的早期历史。
|
||||
BIN
docs/cs/01/Ada_Lovelace_portrait.jpg
Normal file
|
After Width: | Height: | Size: 310 KiB |
BIN
docs/cs/01/IBM.png
Normal file
|
After Width: | Height: | Size: 735 KiB |
78
docs/cs/01/index.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# 计算机早期历史
|
||||
|
||||
:::tip
|
||||
[前去观看第一集](https://bilibili.com/BV1EW411u7th?p=1)
|
||||
:::
|
||||
|
||||
## 计算机对世界的影响
|
||||
|
||||
计算机改变了我们生活几乎所有方面,计算机对我们社会的重要性不言而喻。
|
||||
|
||||
类比与「工业革命」使人民的生产水平大大提高,计算机的出现与发展通过使世界进步飞速,也许未来人民会将计算机的出现与发展,称作「信息革命」。
|
||||
|
||||
## 计算机的实质
|
||||
|
||||
**通过一层层的抽象,来做出复杂操作**,本课程会从最底层的 0 和 1,到逻辑门,CPU,操作系统,整个互联网等一层层讲解。
|
||||
|
||||
## 计算机的起源
|
||||
|
||||
### 算盘
|
||||
|
||||
最早的计算设备是算盘,它是手动计算器,用来帮助加减数字,集存储和计算为一体,它的出现是因为社会的的规模已经超出个人心算的能力(视频中举例的最基本的一种算盘,和我们日常中见到的不一样,和小学的教具类似)
|
||||
|
||||

|
||||
|
||||
::: info
|
||||
在演示算盘的用法的时候,很像是之后会学到的**进制计算**
|
||||
:::
|
||||
|
||||
人类发明的各种巧妙的计算设备:
|
||||
|
||||
- 星盘:让船只可以在海上计算纬度
|
||||
- 计算尺:帮助计算乘法和除法
|
||||
- 上百种的时钟:计算日出、潮汐、天体位置、或者纯粹计时
|
||||
|
||||
这些设备让原先费力的计算变得更快、更简单、更精确。**降低门槛,加强我们的能力。**
|
||||
|
||||
### 步进计算器
|
||||
|
||||
在以前 Computer 并不是指计算机,而是指负责计算的人,这个职业在 1600 年至 1800 年存在。
|
||||
|
||||

|
||||
|
||||
后面,计算机才逐渐指机器,其中以**步进计算器**最为有名。
|
||||
|
||||
步进计算器是第一台能自动进行加减乘除四则运算的机器,它逢十自动进一,主要利用齿轮间的转到进行进位,乘法和除法实际上是多个加法和减法。
|
||||
|
||||

|
||||
|
||||
但即使有机械计算器,算一个结果可能要几小时甚至几天,而且机器昂贵,所以出现了预先算好的「计算表」,需要结果只要查表就可以了。
|
||||
|
||||
:::info
|
||||
二战的射程表,用于计算炮弹飞行轨迹和落点
|
||||
:::
|
||||
|
||||
### 差分机、分析机
|
||||
|
||||
后来,由于生活和战争的需要,开发一个先进的计算机器越来越重要。这时,一个很跨时代的概念出现了 —— 分析机,这个分析机不仅能完成加减乘除运算,还能对数据和事件进行一系列操作,不过由于观念太超前,当时无法做出来。但是,这种通用的自动的计算机器的概念,给时代带来了新的思想。
|
||||
|
||||
英国数学家 [Ada Lovelace(阿达·洛芙莱斯)](https://baike.baidu.com/item/%E9%98%BF%E8%BE%BE%C2%B7%E6%B4%9B%E8%8A%99%E8%8E%B1%E6%96%AF) 给分析机写了假象的程序,她说:「未来会诞生一门全新的、强大的、专为分析所用的语言」,因此 Ada 被认为是世界上第一位程序员。
|
||||

|
||||
|
||||
### 打孔卡片制表机
|
||||
|
||||
1890 年的美国人口普查,如果要用人力来算,预计要 13 年才能完成,但是人口普查是 10 年一次,所以美国政府找来了 Herman Hollerith(赫尔曼·何乐礼),他为政府开发了一个**机器打孔制表机**,通过打孔来记录数据,能够更快地记录人口情况。这一发明大大提升了人口普查的效率。
|
||||
|
||||

|
||||
|
||||
:::info
|
||||
机器打孔机是手动的 10 倍左右,使得人口普查两年半就完成,节省上百万美元。
|
||||
:::
|
||||
|
||||
后来,由于社会发展,利用机器来代替人类手工来提升效率的需求增加,Herman Hollerith 成立了制表机器公司。这家公司后来与其他机械公司合并,成为了 **International Business Machines Corporation 国际商业机器公司**,简称 [IBM](https://baike.baidu.com/item/ibm)。
|
||||
|
||||

|
||||
|
||||
## 接下来
|
||||
|
||||
这些电子机械的「商业机器」取得巨大成功,改变了商业和政府。随着人口爆炸和经济贸易的兴起,要求更快更灵活的工具来处理数据,这就来到了电子计算机时代。
|
||||
BIN
docs/cs/01/机器.png
Normal file
|
After Width: | Height: | Size: 427 KiB |
BIN
docs/cs/01/步进计算器.png
Normal file
|
After Width: | Height: | Size: 773 KiB |
BIN
docs/cs/01/算盘.png
Normal file
|
After Width: | Height: | Size: 339 KiB |
BIN
docs/cs/01/负责计算的人.png
Normal file
|
After Width: | Height: | Size: 893 KiB |
BIN
docs/cs/02/IBM608.jpg
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
docs/cs/02/bug.jpg
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
docs/cs/02/eniac1.jpg
Normal file
|
After Width: | Height: | Size: 567 KiB |
BIN
docs/cs/02/eniac2.jpg
Normal file
|
After Width: | Height: | Size: 439 KiB |
BIN
docs/cs/02/eniac3.jpg
Normal file
|
After Width: | Height: | Size: 57 KiB |
93
docs/cs/02/index.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# 电子计算机
|
||||
|
||||
:::tip
|
||||
[前去观看第二集](https://bilibili.com/BV1EW411u7th?p=2)
|
||||
:::
|
||||
|
||||
20 世纪人口暴增,科学与工程进步迅速,航天计划成形。以上导致数据的复杂度急剧上升、计算量暴增,对于计算的自动化、高速有迫切的需求。
|
||||
|
||||
## 继电器
|
||||
|
||||
1944 年 IBM 建造的**哈佛马克一号**是当时最大的机电计算机,体积非常巨大,这台机器是为了给「曼哈顿计划」跑模拟数据的,它拥有大约 3500 个**继电器**。
|
||||
|
||||

|
||||
|
||||
继电器是用电控制的机械开关。继电器有根「控制线路」,控制电路是开还是关。当螺线圈通过电时,其会产生磁场,吸引上面的线闭合,达到连通的目的。这个继电器可以用于机器进行控制。
|
||||
|
||||
糟糕的是,继电器开关有一定质量,这会影响其闭合。其一秒能够闭合 50 次,导致计算机的运算非常慢。
|
||||
|
||||
:::info
|
||||
哈佛马克一号 1 秒能做 3 次加减运算,1 次乘法要 6 秒,除法要 15 秒,更复杂的操作,比如三角函数,可能要一分钟以上。
|
||||
:::
|
||||
|
||||
除了速度慢,另一个限制是齿轮磨损。随着机器的不断运行,机械器件的磨损不可避免
|
||||
|
||||
:::info
|
||||
哈佛马克一号的任何一个继电器发生故障,就会导致计算出错,而一个计算可能耗时好几天,这是非常致命的。
|
||||
:::
|
||||
|
||||
此外,巨大、黑色、温暖的机器也会吸引虫子。虫子的英文是 Bug。虫子附着在大型计算机的组件上,会导致其运行出错。那么,机器故障(Bug)的来源也是此。
|
||||
|
||||

|
||||
|
||||
要进一步提高计算能力,我们需要更快更可靠的东西来替代继电器
|
||||
|
||||
## 电子管
|
||||
|
||||
一个新的电子组件出现了 —— 「热电子管」。这种电子管只能运行电流**单向运动**,当电流反向时,电子管不能发光。这种管叫「二极管」。
|
||||
|
||||
但是我们需要的是一个可以控制开和关的元件,在 1906 年美国的李·德弗雷斯特在二极管中的两个电极之间加入了第三个控制电极,从而发明了**真空三极管**,真空三极管和继电器有着相同的功能,但是由于没有机械部件的物理移动,所以它的磨损很少,开闭速度可以达到每秒上千次。
|
||||
|
||||
真空三极管在无线电、长途电话等电子设备中大量运用,持续了近半个世纪。
|
||||
|
||||

|
||||
|
||||
刚开始时,这种三极管造价昂贵,而且一个计算机需要上千个电气开关。不过随着时间推移,一些政府部门可以承担这个价格。这标志着计算机从机电转向电子。
|
||||
|
||||
:::info
|
||||
第一个大规模使用真空管的计算机是 1943 年的「巨人一号」,用于破解纳粹通信,它有 1600 个真空管,被认为是第一个可编程的电子计算机,编程方法是把几百根电线插入插板,但是需要**特别的**配置。
|
||||
:::
|
||||
|
||||
## 电子计算机
|
||||
|
||||
世界上第一个真正的通用的可编程计算机是 ENIAC(电子数值积分计算机)。1946 年在美国宾夕法尼亚大学建造成功。ENIAC 每秒可执行 5000 次加减法,不过由于真空管很多,它几乎半天就会出现一次故障。
|
||||
|
||||
ENIAC(埃尼阿克)是世界上**第一台**通用计算机,和一个房间一样大。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
ENIAC 的四个面板和一个函数表
|
||||
|
||||

|
||||
|
||||
## 晶体管
|
||||
|
||||
为了降低成本和大小,同时提高可靠性和速度,我们需要一种新的电子开关。1947 年,贝尔实验室发明了**晶体管**(晶体三极管),一个全新的计算机时代来临了。
|
||||
|
||||
晶体管有两端电极叫做「源极」和「漏极」,中间的材料叫「栅极」,栅极使用半导体材料(有时候导电,有时候绝缘)
|
||||
|
||||
晶体管每秒开关一万次,相比较体型巨大且玻璃易碎的真空管,固态的晶体管占尽优势。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
固体管和晶体管大小的比较
|
||||
|
||||

|
||||
|
||||
由于晶体管的体积很小而且价格便宜,这样我们就能制造出更小更便宜的计算机。
|
||||
|
||||
|
||||
1957 年发布的 IBM 608 是第一款完全使用晶体管且消费者可以买到的计算机,它有 3000 个晶体管,每秒可以执行 4500 次加减法、80 次的乘除法
|
||||
|
||||

|
||||
|
||||
|
||||
如今,晶体管小至几十纳米,运算次数达到几十上百万,并且有几十年的寿命。
|
||||
|
||||
## 接下来
|
||||
|
||||
从继电器到电子管到晶体管,计算机的体积越变越小,速度越变越快,如何利用晶体管进行计算呢?
|
||||
BIN
docs/cs/02/三极管.jpg
Normal file
|
After Width: | Height: | Size: 127 KiB |
BIN
docs/cs/02/晶体管.jpg
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
docs/cs/02/晶体管2.jpg
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
docs/cs/02/晶体管3.jpg
Normal file
|
After Width: | Height: | Size: 149 KiB |
BIN
docs/cs/02/目录.jpg
Normal file
|
After Width: | Height: | Size: 178 KiB |
BIN
docs/cs/02/继电器.jpg
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
docs/cs/03/AND.png
Normal file
|
After Width: | Height: | Size: 347 KiB |
BIN
docs/cs/03/NOT.png
Normal file
|
After Width: | Height: | Size: 823 KiB |
BIN
docs/cs/03/OR.png
Normal file
|
After Width: | Height: | Size: 411 KiB |
BIN
docs/cs/03/XOR.png
Normal file
|
After Width: | Height: | Size: 425 KiB |
89
docs/cs/03/index.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# 布尔逻辑和逻辑门
|
||||
|
||||
<author name="胡杰" title="20 实验 2 班"/>
|
||||
|
||||
:::tip
|
||||
[前去观看第三集](https://bilibili.com/BV1EW411u7th?p=3)
|
||||
:::
|
||||
|
||||
## 二进制
|
||||
|
||||
### 什么是二进制
|
||||
|
||||
电路闭合,电流流过,代表 **真** ,电路断开,无电流流过,代表 **假** 。
|
||||
|
||||
二进制也可以写成 **0** 和 **1** 而不是 **True** 和 **False** ,只是不同的表达方式罢了
|
||||
|
||||
### 为什么用二进制
|
||||
|
||||
晶体管可以不是单纯的 **开和关** ,还可以让不同大小的电流通过。
|
||||
|
||||
**第一个原因** :一些早期的计算机是三进制的,有三种状态,甚至五进制,有五种状态。状态越多,越难以区分信号。所以把两种信号尽可能分开,只用 **开和关** ,可以尽可能的减少这类问题。
|
||||
|
||||
例如手机快没电了,或者附近有电噪音,信号可能会混在一块,而每次百万次变化的晶体管会让这个问题变得更糟
|
||||
|
||||

|
||||
|
||||
**第二个原因** :有整一个数学分支存在,专门处理「真」和「假」,它已经解决了所有法则和运算,叫 **布尔代数** 。**乔治·布尔(George Boole)** 是布尔二字的由来。
|
||||
|
||||
### 布尔逻辑
|
||||
|
||||
在常规代数中,变量的值是数字,可以进行加法或乘法之类的操作。但在布尔代数中,变量的值是 **True** 和 **False** 能进行逻辑操作。
|
||||
|
||||
## NOT、AND、OR
|
||||
|
||||
### NOT GATES(非门)
|
||||
|
||||
NOT 操作把布尔值反转,把 True 进行 NOT 后就会变成 False,反之亦然。
|
||||
|
||||
| INPUT(输入) | OUTPUT (输出) |
|
||||
| :-----------: | :-------------: |
|
||||
| TRUE | FALSE |
|
||||
| FALSE | TRUE |
|
||||
|
||||
用晶体管可以轻松实现这个逻辑。晶体管只是电控制的开关,有三根线:两根电极和一根控制线,控制线通电时,电流就可以从一个电极流到另一个电极。
|
||||
|
||||
### AND GATES(与门)
|
||||
|
||||
AND 操作有两个输入和一个输出。如果两个输入都是 True,输出才是 True;两个输入有一个为 True,输出为 False;两个输入都是 False,输出还是 False。
|
||||
|
||||
| INPUT A(输入 A) | INPUT B(输入 B) | OUTPUT(输出) |
|
||||
| :---------------: | :---------------: | :------------: |
|
||||
| TRUE | TRUE | TRUE |
|
||||
| FALSE | TRUE | FALSE |
|
||||
| TRUE | FALSE | FALSE |
|
||||
| FALSE | FALSE | FALSE |
|
||||
|
||||
要实现 AND 操作,需要两个晶体管连在一起,得到两个输入一个输出。只有当两个开关都是开时,电流才会通;其中任何一个没有通,电流则不通。
|
||||
|
||||
### OR GATES(或门)
|
||||
|
||||
OR 操作也有两个输入和一个输出。但是它只需要满足一方为 True,结果就为 True;只有双方都为 False 时,结果才是 False。
|
||||
|
||||
| INPUT A(输入 A) | INPUT B(输入 B) | OUTPUT(输出) |
|
||||
| ----------------- | ----------------- | -------------- |
|
||||
| TRUE | TRUE | TRUE |
|
||||
| FALSE | TRUE | TRUE |
|
||||
| TRUE | FALSE | TRUE |
|
||||
| FALSE | FALSE | FALSE |
|
||||
|
||||
要实现 OR 操作,需要两个晶体管并联起来。任意一处的开关为开时,电流都能通过。
|
||||
|
||||

|
||||
|
||||
### NOT、AND、OR 的电路符号
|
||||
|
||||

|
||||
|
||||
## XOR 异或
|
||||
|
||||
除了与或非三门之外,还有一个布尔操作叫做 **异或(XOR)**。XOR 就像普通 OR,但有个区别:如果两个输入都为 True,但结果为 False。想要输出 True,其中一个输入必须是 True,另一个输入为 False,这样结果才会是 True;俩输入均为 False 时,结果与 OR 一样。
|
||||
|
||||
| INPUT A(输入 A) | INPUT B(输入 B) | OUTPUT(输出) |
|
||||
| :---------------: | :---------------: | :------------: |
|
||||
| TRUE | TRUE | FALSE |
|
||||
| FALSE | TRUE | TRUE |
|
||||
| TRUE | FALSE | TRUE |
|
||||
| FALSE | FALSE | FALSE |
|
||||
|
||||
实现 XOR 的电路图。
|
||||
BIN
docs/cs/03/为什么用二进制.png
Normal file
|
After Width: | Height: | Size: 470 KiB |
BIN
docs/cs/03/目录.png
Normal file
|
After Width: | Height: | Size: 566 KiB |
BIN
docs/cs/03/符号.png
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
docs/cs/04/image1.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
docs/cs/04/image2.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
docs/cs/04/image3.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
77
docs/cs/04/index.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# 二进制
|
||||
|
||||
<author name="虞嘉乐" title="21 计算机 4 班 "/>
|
||||
|
||||
:::tip
|
||||
[前去观看第四集](https://bilibili.com/BV1EW411u7th?p=4)
|
||||
:::
|
||||
|
||||
## 二进制的计算
|
||||
|
||||
### 二进制的位数关系
|
||||
|
||||
二进制,是基于 2 的表示法,因为二进制中只有 0 和 1。因此,在二进制的计算中,相邻的两位中,左一位是右一位的两倍。比如在二进制中,101 就意味着有 1 个 4,0 个 2,1 个 1。
|
||||
|
||||

|
||||
|
||||
### 二进制转换十进制
|
||||
|
||||
当你知道了二进制每一位所代表的数字后,相加就可以得到十进制的数字。继续拿二进制的 101 作为例子,得出 4+0+1=5,故二进制中的 101 代表的是十进制中的 5
|
||||
|
||||
### 二进制之间的加减
|
||||
|
||||
二进制的加减与十进制没有太大的区别,二进制也是从最小的一位开始相加。区别在于,二进制是满二进一,十进制是满十进一,如下图
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 字节与位
|
||||
|
||||
### 位
|
||||
|
||||
在二进制中,一个 1 或 0,叫一"位"(bit)
|
||||
|
||||
### 字节
|
||||
|
||||
在计算机中,8 位是十分常见的内容,因此有了专门的名字:字节(byte)。
|
||||
|
||||
### 千字节与字节的换算
|
||||
|
||||
在十进制的算法中,1 千字节 = 1000 字节就如同 1 千克 = 1000 克一样。但是在二进制中,1 千字节并不是这么计算的。在二进制的算法中,1 千字节 = 2 的 10 次方 = 1024 字节,所以,不论是 1 千字节等于 1000 字节或者等于 1024 字节都是正确的。这也正是你在购买硬盘时商家显示的大小与实际上的大小并不相同的原因,商家是十进制,计算机是二进制。
|
||||
|
||||
### 常用的计算机位数
|
||||
|
||||
一般来讲,我们使用的计算机都是 32 位或 64 位,所以我们的计算机才能拥有如此丰富的色彩与能力。
|
||||
|
||||
## 二进制如何表现出各种类型的数据
|
||||
|
||||
### 二进制如何表达出数字的正负
|
||||
|
||||
大部分的计算机都是以第一位来代表数字的正负,1 代表负,0 代表正,而剩下的用来表示十进制数字
|
||||
|
||||
### 二进制如何表示浮点数
|
||||
|
||||
在计算机中,有多种方式表达浮点数,其中最常见的是 IEEE 754 标准。它用类似科学计数法的方法来存十进制值,例如,625.9 可以写成 0.6259\*10^3,其中,0.6259 是有效位数,3 是指数。在 32 位中,第一位代表数字的正负,接下来的 8 位代表着指数,而剩下的 23 位则是有效数字。
|
||||
|
||||
### 二进制表示文字
|
||||
|
||||
#### 1 最早使用编号表示的方法
|
||||
|
||||
与其使用特殊方式来表示字母,倒不如让计算机用数字来表示,而最简单的方法就是给字母标上编号。著名英国作家佛朗西斯·培根(Francis Bacon)曾用 5 位序列来编码英文的 26 个字母。5 位对于 26 个字母是绰绰有余,但无法表示标点符号、数字和大小写字母。
|
||||
|
||||
#### 2 ASCII 的诞生
|
||||
|
||||
由于佛朗西斯·培根的方法无法表示标点符号、数字和大小写字母,因此,ASCII(美国信息交换标准代码)在 1963 年诞生。ASCII 是 7 位代码,足以表达大小写字母、标点符号、数字、以及一些特殊的符号。由于 ASCII 是很早的标准,所以被广泛应用,让不同的公司制作的计算机能够互相交换数据,这种通用交换信息的能力叫 "互用性"。但 ASCII 本身是为了英文而存在的,所以其他国家很难使用。幸运的是,一个字节有 8 位,128 到 255 的字符留给了各个国家自己补充。
|
||||
|
||||
#### 3 统一所有编码的标准
|
||||
|
||||
尽管留有的字符对于大部分西方国家都足够使用,但如果在一个国家的电脑上打开用另一个语言写的电子邮件会造成乱码,而且中日等亚洲国家的语言有着数千个字符,无法用 8 位表示所有的字符。日本人更是因为编码的问题,以至于专门有词称呼:乱码(mojibake)。好在,于 1992 年,Unicode 诞生,统一了所有编码的标准。最常见的 Unicode 是 16 位,有超过一百万个位置,对于所有语言都足够了,甚至还能放点数学符号、emoji 等。
|
||||
|
||||
### 补充
|
||||
|
||||
除了以上谈到的这些之外,声音也好,色彩也好,画面也好,都是通过二进制表达的,而我们的计算机,网页、短信、视频甚至操作系统也都是一连串的 0 和 1。
|
||||
|
||||
## 接下来
|
||||
|
||||
让我们看看电脑如何操作二进制
|
||||
BIN
docs/cs/04/目录.jpg
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
docs/cs/05/image1.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
docs/cs/05/image2.png
Normal file
|
After Width: | Height: | Size: 221 KiB |
BIN
docs/cs/05/image3.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
docs/cs/05/image4.png
Normal file
|
After Width: | Height: | Size: 310 KiB |
BIN
docs/cs/05/image5.png
Normal file
|
After Width: | Height: | Size: 196 KiB |
BIN
docs/cs/05/image6.png
Normal file
|
After Width: | Height: | Size: 299 KiB |
BIN
docs/cs/05/image7.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
101
docs/cs/05/index.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# 算术逻辑单元
|
||||
|
||||
<author name="李云豪" title="21 计算机 3 班"/>
|
||||
|
||||
:::tip
|
||||
[前去观看第五集](https://bilibili.com/BV1EW411u7th?p=5)
|
||||
:::
|
||||
|
||||
## 什么是 ALU
|
||||
|
||||
上一节课我们提到如何用二进制表示数据,如 010 表示 2 等。我们知道,**表示和存储数据**是计算机的重要功能。但真正的目标是计算处理有意义的数据,这些操作由计算机的“算术逻辑单元”处理,简称 ALU。
|
||||
|
||||
ALU 有 2 个单元:**算数单元**和**逻辑单元**
|
||||
|
||||
## 算数单元
|
||||
|
||||
它负责计算机里所有的数字操作,比如加减法,或者给某个数+1(这个叫增量运算),不过今天我们重点要理解的是加法运算。
|
||||
|
||||
设计时,我们不在晶体管层次出发,而是用更高层的抽象 —— **逻辑门**。我们需要用到 AND,OR,NOT 和 XOR 逻辑门。
|
||||
|
||||
### 半加器
|
||||
|
||||
对于二进制加法,1+0=1,0+1=1,0+0=0,1+1=10。如下图:
|
||||
|
||||

|
||||
|
||||
因为 XOR 门只能存放 1 bit 所以当计算 1+1 时,我们需要一根额外的线代表"进位"
|
||||
|
||||

|
||||
|
||||
AND 门,只有当两个输出的结果都为 TRUE 时则为 TRUE
|
||||
|
||||
它只能处理一位计算,我们称为**半加器**
|
||||
|
||||
### 全加器
|
||||
|
||||
如果想要处理超过 1+1 的运算,我们需要**全加器**。
|
||||
|
||||
半加器输出了进位,着意味着,我们处理时,还需要将进位考虑进去,才能设计全加器。
|
||||
|
||||

|
||||
|
||||
全加器要考虑三位数字的和,全加器有三输入(这里是 A B C),两输出(进位和总和)。"总和"和"进位"要两条输出线
|
||||
|
||||
先用半加器将 A 和 B 相加 然后把 C 输入到第二个半加器 最后用一个 OR 门检查进位是不是 TRUE
|
||||
|
||||
### 8 位加法器
|
||||
|
||||
全加器会把 A B C 三个输入加起来输出"总和"和"进位"
|
||||
|
||||
有了全加器,我们可以进行多位加法器设计。这主要是利用半加器和全加器进行,刚开始因为没有高位的进位,我们使用半加器,接下来我们全部使用全加器,因为需要考虑进位。
|
||||
|
||||
下图是相加两个 8 位数字
|
||||
|
||||

|
||||
|
||||
第 1 次数字相加 无进位 所以用一个半加器 之后每一个进位用全加器来表示(以此类推)
|
||||
|
||||
### 溢出怎么办?
|
||||
|
||||
两者数字太大了 超过了用来表示的位数 比如 8 位加法器的第 9 位糖豆人的 BUG
|
||||
|
||||
可以通过扩大表示位数来减少溢出(缺点是会用到更多逻辑门且每次的进位都需要时间),所以,现代电路用的加法器不同,叫**超前进位加法器**。
|
||||
|
||||
## 逻辑单元
|
||||
|
||||
逻辑单元执行逻辑操作 比如 AND OR NOT
|
||||
|
||||
### 检测数字是否为 0 的电路
|
||||
|
||||
例如下图就是检查 ALU 输出是不是 0 的电路,很简单,多个或操作,然后最后取反便可,因为只有输出位数全为 0,结果才为 0。
|
||||
|
||||

|
||||
|
||||
## 一个抽象的 ALU 的执行过程
|
||||
|
||||

|
||||
|
||||
### 1. 输入
|
||||
|
||||
两个输入 A 和 B,都是 8 位的
|
||||
|
||||
### 2. 操作码
|
||||
|
||||
还需要告诉 ALU 执行什么操作,例如加法或者减法,所以用 4 位操作代码 简而言之:"1000"可能代表加法命令,"1100"代表减法命令
|
||||
|
||||
### 3. 输出
|
||||
|
||||
输出结果还是 8 位的,ALU 还输出一堆标志(Flag)。一些操作介绍如下:
|
||||
|
||||
#### 标志(Flag)
|
||||
|
||||
- ZERO:若 ALU 输出是 0,那么 ZERO 标志就变成 1。
|
||||
- NEGATIVE:我们可以用 ALU 做减法,然后用 NEGATIVE 判断其是不是小于 0,从而进行比较大小。
|
||||
- OVERFLOW:ALU 还有溢出单元,判断有没有进位。
|
||||
|
||||
ALU 有很多 Flag,这三个是最常用的。
|
||||
|
||||
## 接下来
|
||||
|
||||
现在,我们知道了计算机是怎样在没有齿轮或者杠杆的情况下进行运算的,接下来,我们讲如何利用 ALU 做一个 CPU。
|
||||
BIN
docs/cs/06/256内存.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
198
docs/cs/06/index.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# 寄存器和内存
|
||||
|
||||
<author name="陈登凯" title="21 计算机 4 班"/>
|
||||
|
||||
:::tip
|
||||
[前去观看第六集](https://bilibili.com/BV1EW411u7th?p=6)
|
||||
:::
|
||||
|
||||
|
||||
## 计算机中的存储器
|
||||
|
||||
当我们在使用电脑时,突然断开计算机电源时,再次打开电脑,会发现,之前所有未保存的进度全部丢失了。
|
||||
|
||||
这是因为电脑使用的是 **随机存取存储器** 简称 **RAM** 。
|
||||
|
||||
它只会在通电的情况下才会存储东西,一旦断电,存储在 **RAM** 中的数据将会 **全部清除**!
|
||||
|
||||
另一种存储叫 **持久存储**(如硬盘,U 盘,光盘等),即使断开电源,存储的数据也不易丢失
|
||||
|
||||
## 存储器的原理与制作
|
||||
|
||||
### 存 1 电路
|
||||
|
||||
目前为止,我们所说的电路都是单向的,比如 [8 位加法器](https://book.hyyz.izhai.net/cs/how-computers-calculate-the-alu/#8-%E4%BD%8D%E5%8A%A0%E6%B3%95%E5%99%A8),但是,我们也可以把输出连回输入,如下图所示:
|
||||
|
||||

|
||||
|
||||
当 A 端输入 0 时,将会输出 0,B 端将会输入 A 端的值。
|
||||
|
||||

|
||||
|
||||
当 A 输入 1 时,将会输出 1,B 端这时也会输入 A 端的值。
|
||||
|
||||

|
||||
|
||||
但如果这时再次输入一个 0,结果将会是输出 1,B 端这时输入的值为 1。
|
||||
|
||||

|
||||
|
||||
但是问题是,即使尝试再多次,也无法将 1 变回 0!
|
||||
|
||||
这是一个 [或门](https://book.hyyz.izhai.net/cs/boolean-and-logic-gates/#or-gates%E6%88%96%E9%97%A8) 电路。
|
||||
|
||||
### 存 0 电路
|
||||
|
||||
如下图所示,当把 [或门](https://book.hyyz.izhai.net/cs/boolean-and-logic-gates/#or-gates%E6%88%96%E9%97%A8) 换成 [与门](https://book.hyyz.izhai.net/cs/boolean-and-logic-gates/#and-gates%E4%B8%8E%E9%97%A8) 时,当 A 端与 B 端相同时将会输出结果,如果当 A 端与 B 端都为 1 时,则会输出 1,当 A 端与 B 端都为 0 时,将会输出 0,如下图所示:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
这时如果将 A 端的值设置为 1,B 端的值设置为 0 时,将会是 0,如下图所示:
|
||||
|
||||

|
||||
|
||||
## 锁存器与门锁
|
||||
|
||||
### 锁存器
|
||||
|
||||
现在,我们有了存 0 锁存器和存 1 锁存器,我们可以把它利用起来,相结合成为「AND-OR 锁存器」
|
||||
|
||||
如下图所示:
|
||||
|
||||

|
||||
|
||||
上方的是 **SET** 输入,下方的是 **RESET** 输入:
|
||||
|
||||
当 SET=1、RESET=0,就能将输出设置为 1。
|
||||
|
||||
当 SET=1、RESET=1,就能将输出设置为 0。
|
||||
|
||||
当 SET=0、RESET=0,则会输出 **最后放入的内容**。
|
||||
|
||||
这就是说它存住了一位的信息,这就叫「锁存」,因为它锁住了一个值。
|
||||
|
||||
放入数据的操作叫做「写入」,拿出数据的操作叫「读取」。
|
||||
|
||||
:::tip
|
||||
当 OR 门其中 **一个** 输入为 0 或 AND 门其中 **一个** 输入为 1,则相当于另外一个输入会直接被输出。
|
||||
|
||||
当 OR 门其中一个输入为 1,则 **直接输出 1**。
|
||||
|
||||
当 AND 门其中一个输入为 0,则 **直接输出 0**。
|
||||
:::
|
||||
|
||||
### 门锁
|
||||
|
||||
用 **两条** SET 和 RESET **输入线来控制**,有点难理解,为了更容易用,我们希望 **只有一条输入线**。
|
||||
|
||||
因此 **将两条线** SET 和 RESET 变为 **允许写入线** 和 **数据写入线**,变为仅仅 **一条线控制,另一条线输入数据**,因此需要 **添加其他门控单元**,可以得到一个 **门锁**
|
||||
|
||||

|
||||
|
||||
通过门锁,将值锁起来。
|
||||
|
||||
门锁关闭,锁定原有值,当输入数据发生改变时,输出值不再发生变化。
|
||||
|
||||
门锁打开,输入值会写入到输出值。
|
||||
|
||||
如下图所示:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 寄存器
|
||||
|
||||
虽然一个门锁只能存储一位数字,但是我们并排放 8 个锁存器,就可以存储 8 位信息,一组这样的锁存器叫「寄存器」,寄存器能够 **存一个数字**,这个数字的位数就称为寄存器的 **位宽**
|
||||
|
||||
写入寄存器之前,要先 **启动里面所有的锁存器**,可以将所有的锁存器的允许写入线 **都连接在一起,并把它设为 1**,然后用 8 条数据线 **发送数据**,然后将允许写入线 **设为 0**,就能将 8 位数据 **存储在寄存器中**
|
||||
|
||||

|
||||
|
||||
## 矩阵网络优化门锁放置
|
||||
|
||||
如果只有 **很少的位**,把锁存器并排放置,也 **勉强够用了**。
|
||||
|
||||
如果是 64 位寄存器,那么要 64 根数据线和 64 根连到输出端的线,以及 1 根启用寄存器的线,那么这样一个 64 位寄存器就需要 129 根线,256 位寄存器就需要 513 根线了,非常耗材。
|
||||
|
||||
可以通过 **矩阵形式排列来进行化简**。在矩阵中,不并列排放锁存器,而是做成表格,存 256 位,我们用 16\*16 的网格锁存器,有 16 行 16 列,要 **启用某个锁存器**,就打开 **相应的行线和列线**
|
||||
|
||||

|
||||
|
||||
**打开交叉处,更多的细节如下:**
|
||||
|
||||
用一根行线和列线相并,只有他们都为 1 时才有效,AND 门才会输出 1,我们可以使用 **单个锁存器**。
|
||||
|
||||
这种排列法,用一根 **允许写入线(WRITE ENABLE)** 连所有锁存器。为了让锁存器变成「允许写入」,行线、列线和「允许写入线」都必须是 1。每次只有 1 个锁存器会这样,代表我们可以只用一根「数据线」连所有锁存器来传数据。其他锁存器会忽略数据线上的值,因为没有「允许写入」。
|
||||
|
||||
设置 WRITE ENABLE 和 READ ENABLE 才有用,否则这两条线的经过 AND 门的输出始终为 0。
|
||||
|
||||
当 WRITE ENABLE=1 时,输入数据就会 **保存在门锁中**。
|
||||
|
||||
当 READ ENABLE=1 时,就会连通晶体管,将 **存储在门锁中的数据输出**。
|
||||
|
||||
这里添加了一个 READ ENABLE 线来 **控制读取**,同时将 **输入输入和数据输出线合并**,从 3 条线减少为 2 条。
|
||||
|
||||
并且由于 **每次能够控制唯一一个锁存器**,所以所有的数据线可以合并成一条。也就是说,对于 256 位存储,只需要一条数据线,一条允许写入线,一条允许读取线和 16 行 16 列的线用来选择锁存器,一共 35 条线。
|
||||
|
||||

|
||||
|
||||
## 多路复用器
|
||||
|
||||
为了将 **地址** 转换成 **行和列**,需要用到 **多路复用器**,它能够连通输入对应的线,若输入「0001」那么多路复用器就会把第 1 路连通,达到选路的目的。
|
||||
|
||||
用 **一个多路复用器** 处理 **行**,**一个多路复用器** 处理 **列**,由此通过输入行和列的坐标就能定位到对应的锁存器了。
|
||||
|
||||

|
||||
|
||||
## 256 位寄存器
|
||||
|
||||
可以对 **256 位寄存器** 进行封装,他输入的是一个 8 位的地址:
|
||||
|
||||
4 位表示行,4 位表示列。
|
||||
|
||||
同时需要允许写入线和允许读取线,
|
||||
|
||||
然后需要一根数据线用于读写数据。
|
||||
|
||||

|
||||
|
||||
再进一步拓展,可以将 8 个 256 位内存拼在一起,这样就**能够一次读写 8bit 数据**也就是**一个字节数据**
|
||||
|
||||
由于每个 256 位内存都使用相同的 8 位数据线,因此 8 位数据会存在每个 256 位内存的相同地址中,并且第一个 256 位内存存放第一位,第二个 256 位内存存放第二位,以此类推。
|
||||
|
||||
**这个模块可以在 256 个地址中存储 256 个字节。**
|
||||
|
||||

|
||||
|
||||
可以对其 **进行抽象**,看成一个整体的 **可寻址内存**,其中有 256 个地址,**每个地址** 能读写一个字节的值。
|
||||
|
||||

|
||||
|
||||
## 寻址地址
|
||||
|
||||
现代计算机可以在此基础上将内存扩展到 MB 和 GB 级别。
|
||||
|
||||
我们这里使用 **8 个 16x16 门锁矩阵** 可以得到 **256 字节的内存**,然后可以用 4 位表示行 **4 位表示列来进行寻址。**
|
||||
|
||||
随着内存地址增多,内存地址也必须增长。8 位最多能代表 256 个内存地址
|
||||
|
||||
> 0000 0000 ~ 1111 1111(0~255)一共 256 个数字
|
||||
|
||||
要给千兆或者十亿字节的内存寻址,需要 32 位的地址
|
||||
|
||||
## 随机存储器
|
||||
|
||||
内存的一个 **重要特性** 是:能够 **随时访问任何位置**,所以称为 **随机存取寄存器(RAM)**
|
||||
|
||||
**RAM 只记录当前在干什么**
|
||||
|
||||
> RAM 中存储的数据会随着断电而消失!
|
||||
|
||||
这次,我们做了一个 SRAM(静态随机存取存储器),还有其他比如 DRAM、闪存等等,他们在功能上与 SRAM 相似,但用不同的电路存单个位,比如用不同的逻辑门、电容器、电荷捕获或忆阻器等等。但根本上,这些技术都是矩阵层次嵌套,来存储大量信息。
|
||||
|
||||
## 接下来
|
||||
|
||||
了解了寄存器和内存,那么我们就离计算机的核心 —— CPU 不远了。
|
||||
BIN
docs/cs/06/与或门.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
docs/cs/06/与门.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
docs/cs/06/与门1.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
docs/cs/06/与门2.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
docs/cs/06/内存2.png
Normal file
|
After Width: | Height: | Size: 250 KiB |
BIN
docs/cs/06/内存3.png
Normal file
|
After Width: | Height: | Size: 139 KiB |
BIN
docs/cs/06/复用器.png
Normal file
|
After Width: | Height: | Size: 337 KiB |
BIN
docs/cs/06/寄存器.png
Normal file
|
After Width: | Height: | Size: 258 KiB |
BIN
docs/cs/06/或门.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
docs/cs/06/或门1.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
docs/cs/06/或门2.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
docs/cs/06/或门3.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
docs/cs/06/目录.jpg
Normal file
|
After Width: | Height: | Size: 177 KiB |
BIN
docs/cs/06/矩阵网络.png
Normal file
|
After Width: | Height: | Size: 227 KiB |
BIN
docs/cs/06/矩阵网络1.png
Normal file
|
After Width: | Height: | Size: 128 KiB |
BIN
docs/cs/06/门锁.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
docs/cs/06/门锁1.png
Normal file
|
After Width: | Height: | Size: 136 KiB |
BIN
docs/cs/06/门锁总.png
Normal file
|
After Width: | Height: | Size: 185 KiB |
115
docs/cs/07/index.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# 07 中央处理器
|
||||
|
||||
<author name="胡杰" title="20 实验 2 班 "/>
|
||||
<author name="虞嘉乐" title="21 计算机 4 班 "/>
|
||||
|
||||
:::tip
|
||||
[前去观看第七集](https://bilibili.com/BV1EW411u7th?p=7)
|
||||
:::
|
||||
|
||||
|
||||
## 计算机的心脏 —— 中央处理单元,简称「CPU」
|
||||
|
||||
CPU 负责执行 **程序**,例如 Office,浏览器,游戏《半条命 2》
|
||||
|
||||
程序由一个个操作组成,这些操作叫做**「指令(Instruction)」**,指令用来告诉计算机要做什么。
|
||||
|
||||
## CPU 的组成
|
||||
|
||||
中央处理单元 —— CPU,计算机的心脏,由 **ALU**(**算术逻辑单元**)、**寄存器** 和 **RAM**(**随机存取存储器**) 组合在一起。
|
||||
|
||||
## 指令
|
||||
|
||||
CPU 负责执行程序,而程序是由 **指令** 组成的,指令负责指示计算机做什么。
|
||||
|
||||
打个比方,如果是数学指令(比如加减乘除),CPU 会让 ALU 进行数学计算。如果是内存指令,CPU 会和内存通信,然后读/写值。
|
||||
|
||||
## CPU 的搭建与指令的三个阶段
|
||||
|
||||
CPU 有很多的组件,但这次的重点放在功能,而不是线具体怎么连。所以接下来,当我们用一条线连接两个组件的时候,这条线只是必须线路的一个表现形式。
|
||||
|
||||
那么,接下来就让我们正式的开始搭建吧。
|
||||
|
||||
首先,我们需要一些内存,把上一篇做的 RAM 拿来就可以了。为了保持简单,我们假设它只有 16 个位置,每位置有 8 位。之后再来 4 个 8 位的寄存器 A、B、C、D 用来 **临时存数据** 和 **操作数据**。
|
||||
|
||||
我们可以给 CPU 支持的所有指令一个 ID。在下图假设的例子中,我们用前四位存操作码,后四位数据则来自地址或寄存器。所以我们还需要俩个寄存器来完成 CPU.一个寄存器存当前指令的内存地址,叫 **指令地址寄存器**,另一个存当前指令,叫 **指令寄存器**。
|
||||

|
||||
|
||||
### 指令的第一个阶段 —— 取指令
|
||||
|
||||
当我们启动计算机时,所有的寄存器从 0 开始,为了更好理解,我们过一遍在 RAM 内放的一个程序。
|
||||
|
||||
首先,将指令地址寄存器连到 RAM,因为寄存器的值为 0,因此 RAM 返回地址 0 的值,也就是 0010 1110。
|
||||
|
||||
0010 1110 被复制到了指令寄存器里。现在,指令拿到了,应该去处理它了,这就是 CPU 的第二个阶段 —— **解码阶段**。
|
||||
|
||||

|
||||
|
||||
### 指令的第二个阶段 —— 解码
|
||||
|
||||
指令寄存器中,前 4 位 0010 是 LOAD_A 的指令,意思是把 RAM 的值放入寄存器 A 中。
|
||||
|
||||

|
||||
|
||||
后四位 1110 是 RAM 的地址,转换成十进制是 14.
|
||||
|
||||
接下来,指令由**控制单元**来解码。就像之前的所有东西一样,控制单元也是由逻辑门组成(忘记逻辑门是什么的,罚你再去看一遍[第三课](https://book.hyyz.izhai.net/cs/boolean-and-logic-gates/)).
|
||||
|
||||
比如,为了识别 LOAD_A 的指令,我们需要一个电路,检查操作码是不是 0010,我们可以用很少的逻辑门来实现。
|
||||
|
||||

|
||||
|
||||
好了,现在已经知道了什么是指令,接下来就让我们开始执行指令。
|
||||
|
||||
### 指令的第三个阶段 —— 执行
|
||||
|
||||
用检查是否是 LOAD_A 的电路打开 RAM 的 **允许读取线**,把地址 14 传过去,RAM 拿到了值 0000 0011,也就是十进制 3。因为我们用到的是 LOAD_A 的指令,所以我们只把这个值存在寄存器 A,其他寄存器不受影响,所以我们要用一根线,把 RAM 连到 4 个寄存器,用电路启用寄存器 A 的 **允许写入线**,这样我们就把 RAM 地址 14 的值让在了寄存器 A 中.既然指令完成了,我们可以关掉所有线路,去拿下一个指令了。我们把指令地址寄存器加 1,执行阶段就此结束
|
||||
|
||||

|
||||
|
||||
### 一个小总结
|
||||
|
||||
LOAD_A 只是 CPU 可执行的指令中的其中一个,不同的指令有不同的逻辑电路解码,这些逻辑电路会配置 CPU 内的组件来执行对应操作,而具体分析太过繁琐,所以有把控制单元包成一个整体,这样更加简洁。
|
||||

|
||||
|
||||
## 第 3 个指令:1000 0100
|
||||
|
||||
第 2 个指令与第 1 个指令大同小异,只要按部就班的走就能完成一次循环。但 RAM 的第 3 个指令和第 4 个指令与前两个稍有区别。
|
||||
|
||||
第三个指令(ADD)的后 4 位不是 RAM 地址,而是 2 个(数量) 2 位 (单位) 分别代表 2 个寄存器。
|
||||
|
||||
第一个地址是 01,代表寄存器 B,第二个是 00,代表寄存器 A,因此,1000 0100 代表着把 B 的值加到 A 里,为了执行,我们用到了 ALU(忘了的,罚你去看[第 5 课](https://book.hyyz.izhai.net/cs/how-computers-calculate-the-alu/))。
|
||||
|
||||
在 ADD 指令中,先启用寄存器 B 作为 ALU 的第一个输入,然后 A 做第二个输入。之后,控制单元传递 ADD 操作码告诉 ALU 要做什么。最后的结果应当在寄存器 A 中,但不能直接放入,这回当值新值进入 ALU,不断和自己相加,所以控制单元会用自己的一个寄存器暂时保存结果,关闭 ALU 后再写入正确的寄存器中。当然了,最后还是要把指令地址加一,这样又就完成了一个循环
|
||||
|
||||

|
||||
|
||||
## 最后一个指令:0100 1101
|
||||
|
||||
这个指令是 STORE_A 指令(把寄存器 A 的值放入内存),它的后四位是 RAM 地址,但要做的不是允许读取,而是允许写入,同时打开寄存器 A 的允许读取,将 A 的值纯给 RAM。
|
||||
|
||||
至此,我们运行了第一个电脑程序,恭喜!
|
||||
|
||||
## 时钟
|
||||
|
||||
**时钟**,是负责管理 CPU 节奏的,它以一定的间隔发电信号,控制单元会用这个信号推进 CPU 的内部操作。当然了,节奏不能太快,因为就算是电也要一定时间来传输。
|
||||
|
||||
## 时钟速度
|
||||
|
||||
每一次指令走完三个阶段的速度叫**时钟速度**,单位是赫兹,表示频率的单位,一赫兹代表一秒一个周期。如今的处理器已经达到了几千兆赫兹。
|
||||
|
||||
## 超频
|
||||
|
||||
超频即使修改时钟速度,加快 CPU 的速度,但注意,超频太多会让 CPU 过热或产生乱码,因为信号跟不上时钟。
|
||||
|
||||
## 降频
|
||||
|
||||
当然了,既然有超频,自然有降频。降频一般用在跑新能要求较低的程序或者人已经离开时,降频能够更加省电。
|
||||
|
||||
为了尽可能省电,现在的处理器可以按需求加快或减慢时钟速度,这叫做动态调整频率
|
||||
|
||||
最后,让我们把时钟加入到我们组成的 CPU 中,这样我们的 CPU 就完整啦!现在可以放到盒子里,拿出去和你的朋友大肆炫耀了!
|
||||
|
||||
## 接下来
|
||||
|
||||
下一课,我们要加强 CPU,给它拓展更多的指令,同时开始讲软件。
|
||||
BIN
docs/cs/07/微信截图_20221004140939.png
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
docs/cs/07/微信截图_20221004142602.png
Normal file
|
After Width: | Height: | Size: 249 KiB |
BIN
docs/cs/07/微信截图_20221004143940.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/cs/07/微信截图_20221004144647.png
Normal file
|
After Width: | Height: | Size: 198 KiB |
BIN
docs/cs/07/微信截图_20221004145957.png
Normal file
|
After Width: | Height: | Size: 203 KiB |
BIN
docs/cs/07/微信截图_20221004150501.png
Normal file
|
After Width: | Height: | Size: 199 KiB |
BIN
docs/cs/07/微信截图_20221004153642.png
Normal file
|
After Width: | Height: | Size: 221 KiB |
BIN
docs/cs/07/目录.jpg
Normal file
|
After Width: | Height: | Size: 284 KiB |
75
docs/cs/08/index.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# 指令和程序
|
||||
|
||||
<author name="虞嘉乐" title="21 计算机 4 班 "/>
|
||||
|
||||
:::tip
|
||||
[前去观看第八集](https://bilibili.com/BV1EW411u7th?p=8)
|
||||
|
||||
观前提醒:在本篇文章中,我们所用到的 RAM 依旧是上一篇文章中用到的那个。
|
||||
:::
|
||||
|
||||
## 更多指令
|
||||
|
||||
### 加法的对立面:SUB
|
||||
|
||||
SUB 是减法,与 ADD(加法)指令相同,也要用两个寄存器
|
||||
|
||||
### 跳跳跳:JUMP
|
||||
|
||||
如果你想改变指令顺序,或者跳过一些指令,那 JUMP 就是你的不二之选。JUMP 的作用是让程序跳转到新的位置。
|
||||
|
||||
JUMP 的底层实现方式:用指令后 4 位代表内存地址的值覆盖掉指令地址寄存器中的值(指令地址寄存器可以理解为记录当前内存地址的值的记事本,每完成一次指令,指令地址寄存器都会加一)
|
||||
|
||||
#### JUMP 的好兄弟们
|
||||
|
||||
JUMP 的其中一个好兄弟,叫 JUMP_NEGATIVE。它只在 ALU 的负数标志为真时才进行跳转。
|
||||
|
||||
除此之外,还有 JUMO IF EQUAL(如果相等)、JUMP IF GREATER(如果更大)
|
||||
|
||||
### 请停下来:HALT
|
||||
|
||||
HALT 指令是停止,如果没有它的存在,电脑会崩掉,快说谢谢 HALT 哥。
|
||||
|
||||
**HALT 很重要,能区分指令和数据,还能避免 CPU 不停运行下去后,去处理 STORE_A13 之后不是操作码的 0 而崩掉**
|
||||
|
||||
## 指令的综合运用
|
||||
|
||||
### 无限的循环~ 循环~ 循环~
|
||||
|
||||
在运行之前,我们要先把内存中的 3 和 14 两个数字都改成 1。
|
||||
|
||||
首先,把 LOAD_A 14 和 LOAD_B 15 中的两个 1 分别存入寄存器 A 和 B 中,然后 ADD B A 把寄存器 B 和 A 相加,结果放在寄存器 A 中。之后,使用 ATORE_A 13 的指令,把结果存在内存地址 13 中。接下来,就是本次主角的隆重登场——JUMP2!这条指令的意思是把指令地址寄存器的值改成 2,那么,我们就在一次的回到了 ADD B A,我们又一次的将 A 和 B 的值相加,然后又一次的遇到了 JUMP2。就这样,不断地循环且无法触发 HALT,这就是无限循环。
|
||||
|
||||

|
||||
|
||||
### 如何利用 JUMP 的兄弟逃出循环
|
||||
|
||||
这一次,我们进行了微小的更改,比如说将 A 的值改成了 11,B 改成了 5,用 SUB 代替了 ADD,用到了 JUMP_NEGATIVE 指令,具体的更改如下图
|
||||
|
||||

|
||||
|
||||
现在,让我们跟着 CPU 走一遍。到了 SUB B A,用 A 减 B 等于 6,将结果存在 A 中。因为 6 不符合 JUMP_NEGATIVE 的跳转条件,故进行下一条指令。下一条是 JUMP2,那就再次跳回 SUB,再用 A 减去 B 结果为 1,依旧不符合,继续重复。这一次有了改变,A 减 B 结果是-4,符合了 JUMP_NEG 5 的跳转条件,接下来直接跳转到第五条指令 ADD B A,将 A B 的结果相加并存到寄存器 A 中,再根据下一条的指令,把 A 的值存在内存地址 13 中。最后,碰到了 HALT。现在,我们的程序终于可以休息了。
|
||||
|
||||
## 现代如何使用更多的指令
|
||||
|
||||
我们在这里假设的 CPU 都很基础,只有 4 位,只能操作 16 个地址。而现代的 CPU 有两种策略来增加能使用的指令。
|
||||
|
||||
### 最粗暴的方法 —— 更多的位
|
||||
|
||||
第一种方法十分简单粗暴,直接用更多的位来代表指令,现代常有的是 32 位和 64 位。
|
||||
|
||||
### 麻烦的技巧型 —— 可变指令长度
|
||||
|
||||
举个栗子,比如某个 CPU 用 8 位长度的操作码,如果看到 HALT 指令,HALT 不需要额外数据,那么会马上执行;看到 JUMP,它得知道位置值,这个值在 JUMP 后面,这叫**立即值**
|
||||
|
||||
这样设计,指令可以是任意长度,但会让读取阶段麻烦一些。
|
||||
|
||||
当然,这里要说明一下,我们拿来距离的 CPU 和指令集都是假设的,是为了展示核心原理。
|
||||
|
||||
最后再来展示一下我们的小美人 —— 4004 处理器
|
||||
|
||||

|
||||
|
||||
## 接下来
|
||||
|
||||
下一章,我们会讲到 CPU 更多的功能
|
||||
BIN
docs/cs/08/微信截图_20221011201840.png
Normal file
|
After Width: | Height: | Size: 152 KiB |
BIN
docs/cs/08/微信截图_20221011205418.png
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
docs/cs/08/微信截图_20221011212254.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
91
docs/cs/09/index.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# 高级 CPU 设计
|
||||
|
||||
<author name="虞嘉乐" title="21 计算机 4 班 "/>
|
||||
|
||||
:::tip
|
||||
[前去观看第九集](https://bilibili.com/BV1EW411u7th?p=9)
|
||||
:::
|
||||
|
||||
## 最初的提速方式
|
||||
|
||||
随着时代的发展,人类对计算机速度的要求越来越高,这个时候我们就需要给 CPU 提提速。
|
||||
|
||||
在早期,人们并没有如今这么好用的提速方法,他们选择减少晶体管的切换时间(**晶体管组成了逻辑门、ALU 以及前几章提到的其他组件**)。
|
||||
|
||||
当然,绝大多数的情况下,越新的东西越好,让 CPU 提速的方法也是一样的。如今使用的新技术,不仅能让简单的指令更快速的执行,也能让它进行更复杂的计算。
|
||||
|
||||
## 如何高效的实施指令
|
||||
|
||||
如果我们用自己搭建的那个 CPU 来算一道 16/4,会占用大量的时钟周期,十分的低效。因为我们搭建的 CPU 并没有除法相关的指令,它计算 16/4,会用 16 一直减 4,直至 0 或负数。所以现代 CPU 直接在硬件上设计了除法,可以直接给 ALU 除法指令。
|
||||
|
||||
再比如,现代的 CPU 会有专门的电路来处理图形操作、解码压缩视频、加密文档等等,如果这些也用标准操作来实现,那会用非常多的时钟周期。
|
||||
|
||||
(小科普:某些 CPU 有 MMX、3DNOW、SSE,它们有额外的电路能够做更加复杂的操作,用于游戏和加密等场景)
|
||||
|
||||
随着指令的不断增加,人们也越来越习惯了这些方便的指令,这也让 CPU 中的指令越来越多。从最初的 46 条到如今的上千条指令,CPU 也越来越复杂,越来越厉害了。
|
||||
|
||||
## 为何要快速的把数据传递给 CPU
|
||||
|
||||
就像你再怎么能吃,一张嘴的容量也限制了每次吃下的量。RAM 就是这张嘴,它是 CPU 之外的独立组件 (_当然,你的嘴是你身上的_) ,这也就意味着数据要通过一根线互相传递。这根线叫总线,它可能只有几厘米长,而且电信号传输速度接近光速,但是 CPU 每秒可以处理上亿条指令,而 RAM 要需要时间找地址、取数据、配置、输出数据,这就导致了 CPU 必须等待 RAM 处理完后将数据输出,而这可能会是多个时钟周期的时间。
|
||||
|
||||
## 如何将数据快速传到 CPU
|
||||
|
||||
### 只要多存点数据在自己这里,不就比之前快了吗 —— 缓存
|
||||
|
||||
解决这种延迟的方法之一是给 CPU 加点 RAM——叫"**缓存**"。
|
||||
|
||||
因为 CPU 里空间不大,所以缓存一般只有 KB 或者 MB,而 RAM 都是 GB 起步。自此,CPU 从 RAM 拿数据时,RAN 可以不用只传一个,可以传一批了,虽然花的时间久了点,但数据可以存在缓存。
|
||||
|
||||
因为数据是一个接着一个处理的,这就导致数据存在缓存里十分好用,不用再去 RAM 里拿数据。打个比方,就好像是两个人进行大胃王比赛,有缓存的就像有人直接拿一大堆的菜依次的摆在你的桌前,吃完直接换下一道菜,不用让服务员去拿,而没有缓存的需要在吃完后等待服务员拿出下一道菜。
|
||||
|
||||
说完了处理方式,那就得提一嘴这种方式的速度。因为缓存里 CPU 很近,所以只要用一个时钟周期 CPU 就可以拿到数据,咱们的孤寡老 CPU 终于不用空等。
|
||||
|
||||
(小贴士:如果想要的数据已经在缓存里了,那么这部分的数据叫"**缓存命中**",不在就叫"**缓存未命中**")
|
||||
|
||||
#### 脏位
|
||||
|
||||
缓存除了可以让数据更快的被 CPU 使用外,还可以当一个临时空间。举个栗子,就像你设定了今年想要达成的目标并写在了记事本上。
|
||||
|
||||
与之前的相同,数据不是直接存在 RAM 中,而是在缓存里,这样不仅存起来快,而且改起来也快(就好像你改了又改的择偶标准 —— 从找个漂亮贤惠的老婆,到是个女的都行,再到是个人就行(欸嘿))
|
||||
|
||||
由于缓存和 RAM 内的数据不一样了,这种不一致必须记录下来,之后需要同步,因此缓存内的每块空间都有一个特殊标记,那就是这一段的绝对主角 —— 脏位。一般来讲,计算机同步数据是在 CPU 内缓存的数据满了,而 CPU 有需要空间来储存,这个时候就需要清理缓存内的空间了。在清理缓存的时候,会先检查脏位,如果是"脏"的,那会在加载新数据之前把数据写回 RAM。
|
||||
|
||||
### 左右互博之术 —— 指令流水线
|
||||
|
||||
第 7 章中讲到,CPU 按"取址 —— 解码 —— 执行"的顺序处理,这种设计使 CPU 需要三个时钟周期才能执行一条指令,但是每一个步骤都是用到 CPU 不同的位置,那我们不选择让 CPU 学学"左右互搏之术"呢?
|
||||
|
||||
在 CPU 执行指令时,同时开始解码下一条指令,并且取下下条指令的地址,这样的流水线,可以每个时钟周期执行一个指令,这样,效率就是之前的三倍啦!
|
||||
|
||||

|
||||
|
||||
#### 问题之一:指令之间的依赖关系
|
||||
|
||||
什么指令之间的依赖性呢?举个栗子,你正在执行的指令会改变你正在读取的那个指令的数据。
|
||||
|
||||
所以,流水线处理器必须先弄清楚指令之间的依赖性,必要时停止流水线,以防出现问题。而高端的 CPU 会动态的排序有依赖关系的指令,最小化的停止时间,这叫"**乱序执行**"。这种电路非常复杂,但它实在是太高效了,几乎所有现代处理器都有流水线。
|
||||
|
||||
#### 问题之二:条件跳转
|
||||
|
||||
跳转类型的指令会改变程序的执行流,低级的流水线处理器,看到 jump 指令会停一会等待条件值定下,但这会造成一定的延迟。
|
||||
|
||||
而高端的流水线处理器就不一样了,它们会猜,对了就直接使用,错了就当场清空,这种方法叫"**分支预测**"。当让,不必因为"猜"这个字而感到不信任,因为高端的流水线处理器的成功率超过了 90%,除非你是那种喝凉水都能塞牙的倒霉孩子,不然几乎不会出问题。
|
||||
|
||||
#### 超标量处理器
|
||||
|
||||
它的存在会让流水线彻底进入 007 的时代,它不会给流水线的任意一个工作区域一点休息的时间,这使流水线的工作效率大大提升,一个时钟周期可以完成多个指令了。
|
||||
|
||||
### 三个臭皮匠,抵个诸葛亮 —— 多核处理器
|
||||
|
||||
多核处理器,意思是一个 CPU 芯片里,有多个独立处理单元,像多个独立 CPU,但可以共享一些资源,合作运算。
|
||||
|
||||

|
||||
|
||||
## 中国的超级计算机 —— 神威·太湖之光
|
||||
|
||||
如果多核处理器无法满足的时候,就可以加几个 CPU。而为了满足一些怪物级的运算,就需要超级计算机,它们需要更多的 CPU。神威·太湖之光就是一台这样的超级计算机,它有 40960 个 CPU,每个 CPU 高达 256 个核心,每秒可以进行 9.3 亿亿次浮点数运算,是当之无愧的怪物级的超级计算机,在世界超级计算机排行榜上也有其大名。
|
||||
|
||||

|
||||
|
||||
## 接下来
|
||||
|
||||
下一章,我们会讲到编程。
|
||||
BIN
docs/cs/09/微信截图_20221018213210.png
Normal file
|
After Width: | Height: | Size: 168 KiB |
BIN
docs/cs/09/微信截图_20221018221445.png
Normal file
|
After Width: | Height: | Size: 198 KiB |
BIN
docs/cs/09/微信截图_20221018221548.png
Normal file
|
After Width: | Height: | Size: 222 KiB |
68
docs/cs/10/index.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# 早期的编程方式
|
||||
|
||||
<author name="虞嘉乐" title="21 计算机 4 班 "/>
|
||||
|
||||
:::tip
|
||||
[前去观看第十集](https://bilibili.com/BV1EW411u7th?p=10)
|
||||
:::
|
||||
|
||||
## 最初的编程
|
||||
|
||||
人们对于编程的需求远在计算机出来之前,而当中最著名的例子来自于纺织业。如果只是纯色的纺织品非常简单,但想要有图案的纺织品是,工人们就要每隔一会就调整一次织布机,这当中有着非常大的劳动力需求。正因如此,以前带有图案的纺织品价格十分昂贵。
|
||||
|
||||
因此, **约瑟夫·玛丽·雅卡尔** 在 1801 年发明了世界上最早的编程——可编程纺织机。
|
||||

|
||||
|
||||
## 早期如何编程
|
||||
|
||||
最初的编程用到的是可穿孔纸卡,通过特定位置是否有孔来确定图案。后来,可穿孔纸卡也在 1890 年用于美国的人口普查,但要注意,汇总机并不算计算机,它只做到了汇总数据,而不能编程,在几十年后,它们才可以做到一些减乘除,一些小决定。
|
||||
|
||||
## 控制面板的过去
|
||||
|
||||
早期的面板有很多小插孔,程序员可以插电线,让机器的不同部分互相传数据和信号,正是这些特性,早期的面板就有一个十分相称的名字 —— 插线板。
|
||||

|
||||
|
||||
但插线板有一个“**小小**”的缺点,那就是运行不同程序是,要重新接线。所以到了 1920 年代,控制面板变成了可插拔。
|
||||
|
||||
可插拔的控制面板让编程更方便,现在可以给机器插入不同的程序,而不用重新接线了。举个栗子,一个插线板用来算税,另一个算工资账单。
|
||||
|
||||
但给插线板编程很复杂,下图中乱成一团的线就足以说明。而这,在 1940 年代十分流行。(_早期的程序员看这个真的不会头秃吗_)
|
||||

|
||||
|
||||
## 存储程序计算机
|
||||
|
||||
用插线板编程,不只在机电计算机中流行,于 1946 年完成的第一台通用电子计算机用的也是插线板编程。但是程序的更换需要非常大量的时间,最多可能要花三个星期,由于早期的计算机十分昂贵,停机几周只是为了换程序,完全无法接受,所以在 1940 年代晚期到 1950 年代出,人们发明了内存。通过把程序储存在内存中,使程序易于修改,方便 CPU 快速读取,这类机器叫“**存储程序计算机**”。
|
||||
|
||||
## 冯诺依曼结构
|
||||
|
||||
一个名为*约翰·冯·诺伊曼*的人提出了一种结构——程序与数据都存在一个地方,而这,就叫做“**冯诺依曼结构**”。冯诺依曼结构的计算机的标志是有一个处理器(有算术逻辑单元的)+ 数据寄存器 + 指令寄存器 + 指令地址寄存器 + 内存(存数据和指令)。看到这里,你是否感到有一丝丝的眼熟呢?没错,在第 7 章,我们造的就是冯诺依曼结构的计算机。
|
||||

|
||||
|
||||
而这种结构的计算机由曼彻斯特大学与 1948 年建造完成,绰号“宝宝”。如今,我们所用到的计算机也在用一样的结构。
|
||||

|
||||
|
||||
## 穿孔纸卡的再就业
|
||||
|
||||
虽然有了内存,但程序和数据及就需要某种方式输入进去。这个时候,穿孔纸卡就有了就业空间。到 1980 年代,几乎所有计算机都有穿孔纸卡读取器,将一叠卡片放进去,读取器会一个个写入内存,直到写入完毕,电脑开始执行。
|
||||
|
||||
:::info
|
||||
用纸卡的最大程序使美国空军的 SAGE 防空系统,与 1955 年完成,据称顶峰时期雇佣了世上 20%的程序员。主控制程序用了 62500 张纸卡,等同于如今的 5MB
|
||||
:::
|
||||
|
||||
## 面板编程
|
||||
|
||||
在 1980 年代之前,还有一种常见的编程方式 —— **面板编程**。
|
||||
|
||||
面板编程用大量的开关和按钮做到和插线板一样的效果,在面板上有指示灯,代表各种函数状态和内存中的值。在 50 年代和 60 年代的计算机一般都有这么巨大的控制台。
|
||||

|
||||
|
||||
早期的家用计算机使用了大量的开关,因为大多数家庭都负担不起昂贵的外围设备,如穿孔纸卡读取器。这种计算机要编程,需要多次的拨动面板上的开关,最后按下运行按钮。
|
||||

|
||||
|
||||
## 总结
|
||||
|
||||
在早期,不管是用什么进行编程,都需要非常了解底层硬件,这让编程变得十分困难。于是,人们发明出了更简单的编程方式 —— **编程语言**。
|
||||
|
||||
## 接下来
|
||||
|
||||
下一章,我们开始学习程序语言的发展史。
|
||||
BIN
docs/cs/10/微信截图_20221113194322.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
docs/cs/10/微信截图_20221113194808.png
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
docs/cs/10/微信截图_20221113195538.png
Normal file
|
After Width: | Height: | Size: 354 KiB |
BIN
docs/cs/10/微信截图_20221113201633.png
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
docs/cs/10/微信截图_20221113201716.png
Normal file
|
After Width: | Height: | Size: 368 KiB |
BIN
docs/cs/10/微信截图_20221113203315.png
Normal file
|
After Width: | Height: | Size: 223 KiB |
BIN
docs/cs/10/微信截图_20221113203640.png
Normal file
|
After Width: | Height: | Size: 152 KiB |