diff --git a/.github/workflows/netlify.yml b/.github/workflows/netlify.yml new file mode 100644 index 0000000..563d4e0 --- /dev/null +++ b/.github/workflows/netlify.yml @@ -0,0 +1,19 @@ +# This is a basic workflow to help you get started with Actions + +name: Trigger Netlify + +# Controls when the workflow will run +on: + issues: + types: [opened,edited,deleted,transferred,pinned,unpinned,closed,reopened,assigned,unassigned,labeled,unlabeled,locked,unlocked,milestoned,demilestoned] + +jobs: + start: + name: trigger webhook + if: ${{ !github.event.issue.pull_request }} + runs-on: ubuntu-latest + steps: + - run: | + curl -X POST -d {} https://api.netlify.com/build_hooks/5db8f7db6051f5b810798d74 -vvv + env: + NUMBER: ${{ github.event.issue.number }} diff --git a/README.md b/README.md new file mode 100644 index 0000000..8665c86 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +嗨嗨嗨 + +这是由机器人自动生成的issue存档😜 diff --git a/_posts/.gitkeep b/_posts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git "a/_posts/10-\345\246\202\344\275\225\344\274\230\351\233\205\345\234\260\344\275\277\347\224\250Git\345\220\210\345\271\266\345\244\232\344\270\252commits\357\274\237.md" "b/_posts/10-\345\246\202\344\275\225\344\274\230\351\233\205\345\234\260\344\275\277\347\224\250Git\345\220\210\345\271\266\345\244\232\344\270\252commits\357\274\237.md" new file mode 100644 index 0000000..fc529f9 --- /dev/null +++ "b/_posts/10-\345\246\202\344\275\225\344\274\230\351\233\205\345\234\260\344\275\277\347\224\250Git\345\220\210\345\271\266\345\244\232\344\270\252commits\357\274\237.md" @@ -0,0 +1,182 @@ +--- +date: 2019-09-13 +updated: 2019-10-31 +issueid: 10 +tags: +title: 如何优雅地使用Git合并多个commits? +--- +你可能有过下面的经历 + +自己在本地开发,由于 `Github` 配置了CI,所以需要将新的代码 push到 github 来测试 + +所以你的 `commit` 上会有大量无用的 `commit` 与 `commit message` + +比如: +update, done, fix,update,update ... + +当你测试通过之后,你想把上面的 `commit` 合并成一个显示。 + +如果这是你自己的仓库还好,如果是 fork 的仓库,在开发完成之后你想要 pull request,留着那么多的 commits 肯定不合适 + +**那如何将多个 `commits` 记录合并成一个 `commit` 并且保留 `commit` 的代码呢?** + +先创建几个 `commit` 记录 + +``` +mkdir test +cd test + +git init + +echo 0 > a.md +git add . +git commit -m "add 0" + +echo 1 >> a.md +git add . +git commit -m "add 1" + +echo 2 >> a.md +git add . +git commit -m "add 2" + +echo 3 >> a.md +git add . +git commit -m "add 3" +``` + +现在我们看一下 `commit` 记录 + +``` +git log +``` + +``` +commit 338fe65506a59f02e79badce0ff2c4dd77f08f8c (HEAD -> master) +Author: iamwwc <**@gmail.com> +Date: Thu Sep 12 22:57:19 2019 +0800 + + add 3 + +commit e693fb400991d69005caa026b4f0f2fc8dbce5a8 +Author: iamwwc <**@gmail.com> +Date: Thu Sep 12 22:57:19 2019 +0800 + + add 2 + +commit 278713a3b6eb0e5b72670f007bdbc3bb0a1f9aa1 +Author: iamwwc <**@gmail.com> +Date: Thu Sep 12 22:57:19 2019 +0800 + + add 1 + +commit 3011974bb76dbc5ccc75abf9a528605c60cbb51f +Author: iamwwc <**@gmail.com> +Date: Thu Sep 12 22:57:18 2019 +0800 + + add 0 +``` + +你现在想把 `add3`, `add2` 合并到 `add1` 并且保留全部的内容,并将这个新的commit的记录改写为 `add1,2,3` + +有如下两种方式 + +#### Git rebase + +``` +git rebase -i HEAD~3 +// -i 是 --interactive,交互式,Git 会打开操作窗口 +// 当然,你也可以 `git rebase -i 3011974bb76dbc5ccc75abf9a528605c60cbb51f` +``` + +这时 `git` 会为你打开 `rebase` 的对话框 + +你会看到如下的内容。 + +``` +pick 278713a add 1 +pick e693fb4 add 2 +pick 338fe65 add 3 +``` + +而对应着,`git rebase` 有如下命令 + +``` +# p, pick = use commit +# r, reword = use commit, but edit the commit message +# e, edit = use commit, but stop for amending +# s, squash = use commit, but meld into previous commit +# f, fixup = like "squash", but discard this commit's log message +# x, exec = run command (the rest of the line) using shell +# d, drop = remove commit +``` + +我们这里使用 `fixup`,丢弃掉 `log message` + +``` +r 278713a add1,2,3 +f e693fb4 add 2 +f 338fe65 add 3 +``` + +关闭对话框之后进入到 `commit message` 的界面,这时填写 `add1,2,3`并关闭 + +再次 `git log` + +``` +commit 74d943e839c96fe256bd3324a6da0a5e6fbeafa1 (HEAD -> master) +Author: iamwwc <**@gmail.com> +Date: Thu Sep 12 22:57:19 2019 +0800 + + add1,2,3 + +commit 3011974bb76dbc5ccc75abf9a528605c60cbb51f +Author: iamwwc <**@gmail.com> +Date: Thu Sep 12 22:57:18 2019 +0800 + + add 0 +``` + +完成修改 :) + +#### Git reset --soft + +`--soft` 保证你的 `commit` 不丢失,内容不会被删除,仅仅移动了 `HEAD` + +我们将 HEAD 移动到 add1 之前(也就是 `add0`) + +``` +git reset --soft 3011974bb76dbc5ccc75abf9a528605c60cbb51f +``` + +使用 `git log`你会发现只剩下 `add0` 的提交 + +下面添加全部的文件 + +``` +git add . +git commit -m "add1,2,3" +``` + +完成 + +之后往 GitHub 提交时由于 `commit` 的变更你会推送失败 + +可以 `force-with-lease` 更新 +``` +git push --force-with-lease +``` + +不推荐使用 `-force` + +比如A,B一起在 `master` 分支开发,B开发完成之后将代码 `push` 到了 `master` + +A在本地 `master` 分支进行了 `rebase` 操作。这时A与 `remote/master` 已经冲突了 + +如果A 使用 `git push --force`,则会覆盖掉 B 推送到 `master` 上的代码 + +`https://stackoverflow.com/a/37460330/7529562` + +参见 + +`https://github.com/Jisuanke/tech-exp/issues/13` \ No newline at end of file diff --git "a/_posts/11-\344\270\244\344\270\252\351\223\276\350\241\250\347\233\270\345\212\240-leetcode2.md" "b/_posts/11-\344\270\244\344\270\252\351\223\276\350\241\250\347\233\270\345\212\240-leetcode2.md" new file mode 100644 index 0000000..5743142 --- /dev/null +++ "b/_posts/11-\344\270\244\344\270\252\351\223\276\350\241\250\347\233\270\345\212\240-leetcode2.md" @@ -0,0 +1,71 @@ +--- +tags: +- 算法与数据结构 +- Leetcode +title: 两个链表相加-leetcode2 +date: 2019-09-13 +updated: 2019-09-13 +issueid: 11 +--- +[原题在这里](https://leetcode.com/problems/add-two-numbers/) + +### 介绍 + +给你两个非空单向链表 `l1, l2`,将链表按照 `个十百千` 的顺序相加。 + +比如 + +``` +输入: (1 -> 2 -> 3) + (4 -> 5 -> 6) + +返回: (5 -> 7 -> 9) +``` + +这里最需要注意的是进位。 + +### 实现 + +```java +public ListNode addTwoNumbers(ListNode l1, ListNode l2) { + int carry = 0; + ListNode origin = new ListNode(0), tmp = origin; + + // 遍历最大长度的链表 + while(!(l1 == null && l2 == null && carry == 0)) { + // 如果 l1 或者 l2 为 null,那么值始终为 0 + int v1 = l1 == null ? 0 : l1.val; + int v2 = l2 == null ? 0 : l2.val; + int num = v1 + v2 + carry; + tmp.next = new ListNode(num % 10); + // 确定是否进位 + carry = num / 10; + carry = num / 10; + tmp = tmp.next; + if (l1 != null) l1 = l1.next; + if (l2 != null) l2 = l2.next; + } + return origin.next; +} +``` + +这道题和我原来的思路不同点在于 + +我往往会去遍历最小的链表,然后 `if` 分开处理 + +```java +while(l1 != null && l2 != null) { + +} + +if (l1 == null ){ + +} +if (l2 == null ){ + +} + +``` + +却没有想到遍历最长链表,如果短链表为 `null`,那直接返回0就可以 + +写算法往往带来的是新的思考方式 \ No newline at end of file diff --git "a/_posts/12-\346\234\200\351\225\277\346\227\240\351\207\215\345\244\215\345\255\227\344\270\262-leetcode3.md" "b/_posts/12-\346\234\200\351\225\277\346\227\240\351\207\215\345\244\215\345\255\227\344\270\262-leetcode3.md" new file mode 100644 index 0000000..0333db2 --- /dev/null +++ "b/_posts/12-\346\234\200\351\225\277\346\227\240\351\207\215\345\244\215\345\255\227\344\270\262-leetcode3.md" @@ -0,0 +1,51 @@ +--- +title: 最长无重复字串-leetcode3 +date: 2019-09-13 +updated: 2019-09-13 +issueid: 12 +tags: +- 算法与数据结构 +- Leetcode +--- +题目来自 + +https://leetcode.com/problems/longest-substring-without-repeating-characters/ + + +### 题目描述 + +给你一个字符串s,输出 最长且不重复的字串 的长度 + +比如输入 `tmsmfdut`,因为最长的不重复字串是 `smfdut`,所以输出 `6` + +### 解决方案 + +既然是最大长度,那肯定需要记录 `max`,使用双索引,i用来遍历s, `j` 为不重复字串的起始索引,我们将字符作为 `key`,下标索引作为 `value`作为索引存放到 `hashmap`中。在后面遍历s时,如果字符存在于 `map` 中,那去 当前距离 `i - j + 1` 与 `max` 的最大值。 + +代码如下 +```java +public int lengthOfLongestSubstring(String s) { + // 存放 character 与 下标索引 + HashMap map = new HashMap(); + int max = 0; + for (int i = 0, j = 0; i < s.length(); i ++) { + if(map.containsKey(s.charAt(i))) { + j = Math.max(j,map.get(s.charAt(i)) + 1); + } + map.put(s.charAt(i),i); + max = Math.max(max, i - j + 1); + } + return max; +} +``` + +为什么 `j = Math.max(j,map.get(s.charAt(i)) + 1);`中需要 `+1`? + +看下图,这时我们 j 为1,i为3,hashmap中存放着第一个m, +我们通过 `map.get(s.charAt(i))`获得的是m对应的下标1 + +如果 `j` 等于 `1` ,那么会直接与下标为 `3` 的 `m` 重复,所以我们需要 `+1`,将 `j` 指向 `map` 中 `m` 对应的下标索引的后一位,也就是 `s` 对应的 `2` + +![image](https://user-images.githubusercontent.com/24750337/64864439-ae8e9200-d669-11e9-8774-ecec70544a14.png) + + diff --git "a/_posts/13-\345\210\244\346\226\255\346\225\260\347\273\204\345\205\203\347\264\240\347\273\217\345\217\230\345\214\226\344\271\213\345\220\216\346\230\257\345\220\246\347\233\270\347\255\211.md" "b/_posts/13-\345\210\244\346\226\255\346\225\260\347\273\204\345\205\203\347\264\240\347\273\217\345\217\230\345\214\226\344\271\213\345\220\216\346\230\257\345\220\246\347\233\270\347\255\211.md" new file mode 100644 index 0000000..0e1b41b --- /dev/null +++ "b/_posts/13-\345\210\244\346\226\255\346\225\260\347\273\204\345\205\203\347\264\240\347\273\217\345\217\230\345\214\226\344\271\213\345\220\216\346\230\257\345\220\246\347\233\270\347\255\211.md" @@ -0,0 +1,95 @@ +--- +issueid: 13 +tags: +- 算法与数据结构 +title: 判断数组元素经变化之后是否相等 +date: 2019-09-14 +updated: 2019-09-14 +--- +题目来自 + +https://www.nowcoder.com/questionTerminal/c55f4f15cc3f4ff0adede7f9c69fa0c1 + +这是小米面试时考官给出的一道题。面试结束之后想了下,这不就是判断一个数是否是 2 的次方倍吗。我之前还做过判断关于指数幂的问题。 + +### 题目介绍 +``` +牛牛有一个数组,里面的数可能不相等,现在他想把数组变为:所有的数都相等。问是否可行。 +牛牛可以进行的操作是:将数组中的任意一个数改为这个数的两倍。 +这个操作的使用次数不限,也可以不使用,并且可以对同一个位置使用多次。 + +输入描述: +输入一个正整数N (N <= 50) +接下来一行输入N个正整数,每个数均小于等于1e9. + + +输出描述: +假如经过若干次操作可以使得N个数都相等,那么输出 `"YES"`, 否则输出 `"NO"` + +示例1 +输入 +2 +1 2 +输出 + +YES +``` + +### 题目分析 + +既然最后全部的数都相等,那说明数组中的任意两个数A,B(A >= B),一定满足 + +``` +A % B == 0 可以被整除 +(A / B) & (A / B - 1) == 0 商为2的倍数 +``` + +![image](https://user-images.githubusercontent.com/24750337/64867060-32974880-d66f-11e9-9bc7-7b3f10a07aab.png) + +那如何判断 B 是 A 的二次倍数呢? + +我们利用二进制位运算的特点 + +``` +// 等于 0 则说明是2的幂方 +// 比如 +// 4 100 +// & +// 2 010 +// = 000 +n & (n - 1) == 0 +``` + +我们只需要每次对 `两个数` 进行判断,如果成立,则返回 `YES` + +代码如下 + +```java + +public boolean yesOrNot(int [] intarrs) { + for ( int i = 0 ; i < intarrs.length - 1; i ++) { + if(!isOk(intarrs[i],intarrs[i + 1])) { + System.out.println("NO"); + return false; + } + } + System.out.println("YES"); + return true; +} + +public boolean isOk(int num1, int num2) { + int more = num1, less = num2 ; + if (num1 < num2){ + more = num2; + less = num1; + } + // 4 100 + // 3 011 + // 2 010 + + if (more % less == 0 && ((more / less) & (more / less - 1)) == 0) { + return true; + } + return false; +} +``` \ No newline at end of file diff --git "a/_posts/14-\346\234\200\351\225\277\345\233\236\346\226\207\345\255\227\344\270\262-leetcode5.md" "b/_posts/14-\346\234\200\351\225\277\345\233\236\346\226\207\345\255\227\344\270\262-leetcode5.md" new file mode 100644 index 0000000..2a3c3c2 --- /dev/null +++ "b/_posts/14-\346\234\200\351\225\277\345\233\236\346\226\207\345\255\227\344\270\262-leetcode5.md" @@ -0,0 +1,184 @@ +--- +issueid: 14 +tags: +- 算法与数据结构 +- Leetcode +title: 最长回文字串-leetcode5 +date: 2019-09-14 +updated: 2019-09-14 +--- +题目来自 + +https://leetcode.com/problems/longest-palindromic-substring/ + +### 题目介绍 + +给定一个字符串 `s` ,输出 `s` 对应的最长回文字串 + +比如 + +``` +Input: s = "babad" + +Output: s = "bad" +``` + +解释: + +`bad` 是 `babad` 的最长回文字串 + +### 解决方案 + +之前碰到过类似的一道题,判断 s 是否回文,其中之一的方法是从两边开始向里扫描。但对于这道题,求最长回文字串向里扫描不适用。 + +我们采用中间向外扫描, 不断比较记录最大的长度, 跳过肯定是回文的重复字符序列。 + +然后利用双指针左右移动。 + +```java +public String longestPalindrome(String s) { + if (s.length() < 2) { + return s; + } + int max = 0 , start = 0; + /* + * i 是移动的起始点 + * 比如 s.length() == 8 + * 现在max == 6 + * i只需要到 (8 - 6 / 2 ) == 5 就可以,没必要继续向后扫描 + */ + for(int i = 0 ; i < s.length() - max / 2; i ++) { + int j = i; + int k = i; + // 跳过一定是回文的重复字符 + while(k < s.length() - 1 && s.charAt(k) == s.charAt(k + 1)) { + k ++; + } + // 从重复序列的左右同时开始移动 + while(j > 0 && k < s.length() - 1 && s.charAt(j - 1) == s.charAt(k + 1)) { + j --; + k ++; + } + int len = k - j + 1; + if (max <= len){ + start = j; + max = len; + } + } + return s.substring(start, start + max); +} +``` +### 再度优化 + +上面代码在 leetcode 上运行使用了 `18ms`,我们接下来继续优化,让它逼近 `1ms` + +首先要做的是 `String` 转化为 `char[]` 直接索引,并且数组长度也存入变量,不再调用 `s.length()` + + +```java +public String longestPalindrome(String s) { + if (s.length() < 2) { + return s; + } + char[] chars = s.toCharArray(); + int charlen = chars.length; + int max = 0 , start = 0; + for(int i = 0 ; i < charlen - max / 2; i ++) { + int k = i; + int j = i; + while(k < charlen - 1 && chars[k] == chars[k + 1]) { + k ++; + } + while(j > 0 && k < charlen - 1 && chars[j - 1] == chars[k + 1]) { + j --; + k ++; + } + int len = k - j + 1; + if (max <= len){ + start = j; + max = len; + } + } + return s.substring(start, start + max); +} +``` + +以上代码运行占用时间 `10ms` + +##### 继续优化 + +```java +public String longestPalindrome(String s) { + if (s.length() < 2) { + return s; + } + char[] chars = s.toCharArray(); + int charlen = chars.length; + int max = 0 , start = 0; + for(int i = 0 ; i < charlen - max / 2; i ++) { + int k = i; + int j = i; + while(k < charlen - 1 && chars[k] == chars[k + 1]) { + k ++; + } + i = k; + while(j > 0 && k < charlen - 1 && chars[j - 1] == chars[k + 1]) { + j --; + k ++; + } + int len = k - j + 1; + if (max <= len){ + start = j; + max = len; + } + } + return s.substring(start, start + max); +} + +``` + +这次我们就加一行代码,`i = k;`,当 `while` 搜寻完重复字符之后,那 `i` 也没必要在从这里继续检索了。 + +所以直接跳过重复字符,从其后面继续检索。 + +以上代码运行占用时间 `2ms` + +优化结束 :) + +### 感想 + +到现在也是做了好多的 `leetcode` 题目了,感觉对代码的细节控制更深入了。 + +比如这个代码片段 + +```java +while(j > 0 && k < s.length() - 1 && s.charAt(j - 1) == s.charAt(k + 1)) { + j --; + k ++; +} +``` + +如果按我之前的写法,我会写成下面这样 + +```java +while(j >= 0 && k < s.length() && s.charAt(j) == s.charAt(k)) { + j --; + k ++; +} +``` + +看起来没什么问题,但 `j` 可能为 `-1`,而 `k` 可能为 `s.length()`,已经索引溢出了。 + +为了解决这个问题,我会在后面加上更臃肿的代码 + +```java +if (j == -1) { + j ++; +} + +if (k == s.length()) { + k --; +} +``` + +算法题写多了,感觉自己更灵光了 :) \ No newline at end of file diff --git "a/_posts/15-go\347\232\204\347\272\277\347\250\213\344\275\225\346\227\266\344\274\232\351\230\273\345\241\236.md" "b/_posts/15-go\347\232\204\347\272\277\347\250\213\344\275\225\346\227\266\344\274\232\351\230\273\345\241\236.md" new file mode 100644 index 0000000..162e49c --- /dev/null +++ "b/_posts/15-go\347\232\204\347\272\277\347\250\213\344\275\225\346\227\266\344\274\232\351\230\273\345\241\236.md" @@ -0,0 +1,115 @@ +--- +title: go的线程何时会阻塞 +date: 2019-09-16 +updated: 2021-05-13 +issueid: 15 +tags: +- Go +--- +### 什么是协程 + +OS 并不理解 协程,协程是在 `userspace` 模拟出来的调度,协程运行在线程之上,所以协程没有上下文切换消耗。 + +### Go什么时候会阻塞调用线程? + +太长不看版: + +> Go 进行系统调用时,如果 OS 对于 socket,file 的 文件描述符fd 不支持 IO multiplxing,Go 会阻塞调用线程。 + +我们都知道,`goroutine` 是在线程上模拟调度出来的,那在什么情况下,go runtime 会再创建新的线程? + +我们从最基础的概念开始。 + +进程的系统调用是以线程为单位的,而 goroutine 是在线程之上,`user space` 模拟出的调度。当 Go code 进行系统调用时,`code` 当前运行的线程会进行 `syscall`,从而使得调用线程被阻塞。操作系统不理解 `goroutine`,只能感知到 线程,所以可以理解为,当进行系统调用时,线程就是 `Go` 押在操作系统的人质(不押韵) + +对于 `socket` 的读写操作都属于系统调用。 + +设想一下这个场景,我们开发了一个 http server, 每个连接都使用新的 `goroutine` 去处理,那当我们有 1000 个 `connections`,线程数应到 1000+(并且大部分都阻塞),但这不合实际。实际上,Go 对于 `socket` 实现了高效了 IO多路复用。 + +将 `blocking thread` 转换为 `blocking goroutine` 的设计叫做 [netpoller](https://stackoverflow.com/a/36117724/7529562) + +`netpoller` 从 `goroutine` 接受事件,然后将其分发到操作系统提供的IO处理器。 + +在 `Linux` 中使用 `epoll`,`BSD, Drawin` 下使用 `kqueue`,`Windows`下使用 `IOCP` + +https://morsmachine.dk/netpoller + +但 `netpoll` 是为 `socket`,那对于普通的文件呢? + +再次设想一下: + +我们对磁盘中 `10000` 个文件进行读写,如果不使用 `poll` 技术,我们需要阻塞至少 `10000` 个线程,这简直无法接受。 + +这个问题在 `Github Issues` 上有很多的讨论。 + +你可以在最后的参考中找到链接 + +为什么 Go 只为 `socket` 使用 `poll` 技术呢? + +Windows 下的 IOCP 支持 `socket, file` + +Linux 下的 `epoll` [只支持](https://stackoverflow.com/questions/8057892/epoll-on-regular-files) `socket` + +所以 正是由于不同平台对于 `poll` 技术支持特性的不同,使得 Go 在读写文件时会采用阻塞线程的无奈之举。 + +直到反应的人越来越多,`Go` 最终在 [这个PR](https://github.com/golang/go/commit/c05b06a12d005f50e4776095a60d6bd9c2c91fac) 中完善了处理 + +那如何解决的呢? + +我们用读取文件举例子。 + +`windows` 下 `ReadFile` 实现 `runtime/syscall_windows.go` + +```go +func ReadFile(handle Handle, buf []byte, done *uint32, overlapped *Overlapped) (err error) { + var _p0 *byte + if len(buf) > 0 { + _p0 = &buf[0] + } + // 这里实际上调用了 ReadFile 这个 Windows 的API + r1, _, e1 := Syscall6(procReadFile.Addr(), 5, uintptr(handle), uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), uintptr(unsafe.Pointer(done)), uintptr(unsafe.Pointer(overlapped)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = EINVAL + } + } + return +} +``` + +[Windows下关于 ReadFile API 的文档](https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile) + +``` +This function(ReadFile) is designed for both synchronous and asynchronous operations. +``` + +现在 `Go` 统一对 `socket,file` 的 文件描述符 `fd` 做了异步处理。 + +在 `Linux` 下,由于 `epoll` 不支持 对于文件的读写,所以 `Go` 像之前那样阻塞调用线程。 + + +如果 `goroutine` 进行了阻塞式系统调用,那么当前线程会被阻塞,当 `go` 调度器没有足够的线程可以支持其余的 `goroutine` 时,调度器会创建额外的线程 + +### runtime.LockOSThread 是做什么呢? + +还是到了 `goroutine` 与 `thread` 的区别。 + +操作系统并不理解协程,协程是在 `userspace` 中模拟调度出来的。 + +既然 `OS` 不理解协程,`Go` 进行系统调用时,必须以线程为质押。 + +`go scheduler` 并不保证 `goroutine` 始终在同一线程运行。所以为了确保系统调用能够成功,进行 `syscall` 的 `goroutine(A)` 必须固定在某个线程运行,调用 `LockOSThread` 之后,其余的 `goroutine` 不能在这个线程运行。除非 `A` 调用 `UnlockOSThread` + +https://stackoverflow.com/questions/25361831/benefits-of-runtime-lockosthread-in-golang/25362395#25362395 + + +参考来源: + +1. https://github.com/golang/go/commit/c05b06a12d005f50e4776095a60d6bd9c2c91fac +2. https://github.com/golang/go/issues/18507 +3. https://github.com/golang/go/issues/6222 +4. https://github.com/golang/go/issues/6817 +5. https://stackoverflow.com/questions/8057892/epoll-on-regular-files +6. https://morsmachine.dk/netpoller \ No newline at end of file diff --git "a/_posts/16-Go\344\270\216GC.md" "b/_posts/16-Go\344\270\216GC.md" new file mode 100644 index 0000000..f261d3e --- /dev/null +++ "b/_posts/16-Go\344\270\216GC.md" @@ -0,0 +1,64 @@ +--- +title: Go与GC +date: 2019-09-16 +updated: 2019-09-21 +issueid: 16 +tags: +- Go +--- +### 常见的 GC 算法 + +#### 引用计数 + +每个对象分别引用一个计数器 `count`,被引用则 `count + 1`,被释放则 `count - 1`,当 `count` 为 `0` 时,该对象可以被清除 + +缺点是对象之间存在循环引用的问题 + +#### 标记-清除算法 + +##### 基于追踪的垃圾回收算法 + +算法分为两部分:标记(mark),清除(sweep) + +1. 从 `root` 开始遍历,将所有访问到的对象都标记为可达对象 + +![image](https://user-images.githubusercontent.com/24750337/64919186-6e025600-d7da-11e9-9dd4-7b6590b7b315.png) + +2. 清除不可达对象 + +进行以上两个过程需要 `stop the world`,代码的执行会被暂停,保证在清除过程中内存的状态不变,这个过程又被称作 STW,可见 STW 所用时间将直接决定 GC 性能,优点是解决了 引用计数 问题,确定是需要 STW + +##### 三色标记算法 + +白色集合: 初始状态,全部的对象,其中部分可以被回收。 +灰色集合: root 中直接引用的对象,可能引用白色对象,不能被回收,会被移动到黑色集合。 +黑色集合: 其中的对象 **不引用** 白色集合中的对象。 + +###### 清理过程 + +1. 初始状态下对象都在白色集合 +2. 将被 `root` 直接引用的对象移动到灰色集合 +3. 从灰色集合中取出一个对象,将其放入黑色集合,将其子对象放入灰色集合,重复以上过程,直至灰色集合为空 + +此时黑色集合包含被直接(或间接)引用的对象,白色集合包含未被引用的对象 + +1. 将白色集合的对象全部清除 + +三色标记法与标记-清除算法相比,优越性体现在哪? + +三色标记法只需要保证 灰色集合为空,黑色不引用白色对象 的条件,那么白色对象就可以被释放 + +三色标记的目的,主要是用来做增量垃圾回收,如果只有两种状态,那么标记和清除不能被打断,期间用户程序不能执行。 + +而使用三色标记,即使在标记过程中对象的引用发生了变化,只要满足黑色不引用白色这个约束条件,垃圾回收器就可以继续工作,将整个回收过程打散,每次回收一部分,实现真正的增量回收。 + +但并发执行存在一个问题: + +![image](https://user-images.githubusercontent.com/24750337/65370152-4999f400-dc88-11e9-8d26-8f5d67befd8e.png) + +A为根对象,GC遍历到A,A变黑,B变灰,这时用户程序将A->B 的引用改为 A -> C 的引用,GC遍历到B,B没有引用,B变黑,灰集合为空,C被错误地释放。 + +为了解决并发带来地引用修改的问题, Go 引用了写屏障,GC开始之后可以感知内存的引用变化,如果黑色对象A引用了白色对象C,那么C变为灰色,如果新创建了对象,那么直接移入黑色,等待下一次处理 + +https://blog.wangriyu.wang/2019/04-Golang-GC.html# +https://making.pusher.com/golangs-real-time-gc-in-theory-and-practice/ diff --git "a/_posts/17-Git\345\270\270\347\224\250\345\221\275\344\273\244.md" "b/_posts/17-Git\345\270\270\347\224\250\345\221\275\344\273\244.md" new file mode 100644 index 0000000..76f2600 --- /dev/null +++ "b/_posts/17-Git\345\270\270\347\224\250\345\221\275\344\273\244.md" @@ -0,0 +1,50 @@ +--- +date: 2019-09-16 +updated: 2019-09-16 +issueid: 17 +tags: +title: Git常用命令 +--- +这里收集着我平时用到的Git操作 :) + +### 推送到不同的分支 + +将本地的 `master` 分支推送到远程仓库的 `fixreadme` 分支 + +`origin` 代表着你的远程仓库地址 + +``` +git push origin master:fixreadme +``` + +### 重新修改已经commit 的message + +``` +git commit --amend -m "re-commit" +``` + +### 删除远程branch + +``` +git push origin --delete iambranch +``` + +### 删除本地branch + +安全删除,检查iambranch合并状态 + +``` +git branch -d iambranch +``` + +强制删除 + +``` +git branch -D iambranch +``` + +### 查看全部分支的提交 commit 状态 + +``` +git log --all --oneline --decorate --graph +``` diff --git "a/_posts/18-\347\275\227\351\251\254\346\225\260\345\255\227\346\261\202\345\222\214-leetcode13.md" "b/_posts/18-\347\275\227\351\251\254\346\225\260\345\255\227\346\261\202\345\222\214-leetcode13.md" new file mode 100644 index 0000000..96f61d5 --- /dev/null +++ "b/_posts/18-\347\275\227\351\251\254\346\225\260\345\255\227\346\261\202\345\222\214-leetcode13.md" @@ -0,0 +1,61 @@ +--- +title: 罗马数字求和-leetcode13 +date: 2019-09-17 +updated: 2019-09-17 +issueid: 18 +tags: +- Leetcode +--- +题目来源 + +https://leetcode.com/problems/roman-to-integer/ + +### 题目描述 + +给定一个字符串 s,计算得出 s 对应的值。 + +``` +Input: "MCMXCIV" +Output: 1994 +Explanation: M = 1000, CM = 900, XC = 90 and IV = 4. +``` + +### 题目分析 + +罗马数字计算最关键的是 + +1. 通常数字大小从左到右依次递减。 +2. 如果 s[i] < s[i + 1],说明这两个数字可以组合在一起,且值为 s[i + 1] - s[i]。 +3. 如果 s[i] >= s[i + 1],则值为 s[i] + +### 实现 + +````java +class Solution { + public int romanToInt(String s) { + char[] arrs = s.toCharArray(); + int result = 0; + HashMap map = new HashMap<>(); + map.put('I',1); + map.put('V',5); + map.put('X',10); + map.put('L',50); + map.put('C',100); + map.put('D',500); + map.put('M',1000); + for(int i = 0 ; i < arrs.length; i ++) { + int current = map.get(arrs[i]); + // 这里需要处理数组索引溢出 + // '1'表明溢出,我们用 0 代替 + int next = map.getOrDefault(i < arrs.length - 1 ? arrs[i + 1] : '1',0); + if (next > current) { + result += next - current; + i += 1; + continue; + } + result += current; + } + return result; + } +} +``` \ No newline at end of file diff --git "a/_posts/19-\346\210\221\346\211\200\350\256\244\344\270\272\347\232\204\346\234\200\345\245\275\344\272\214\345\217\211\346\240\221\344\270\211\345\272\217\351\201\215\345\216\206.md" "b/_posts/19-\346\210\221\346\211\200\350\256\244\344\270\272\347\232\204\346\234\200\345\245\275\344\272\214\345\217\211\346\240\221\344\270\211\345\272\217\351\201\215\345\216\206.md" new file mode 100644 index 0000000..5bb46da --- /dev/null +++ "b/_posts/19-\346\210\221\346\211\200\350\256\244\344\270\272\347\232\204\346\234\200\345\245\275\344\272\214\345\217\211\346\240\221\344\270\211\345\272\217\351\201\215\345\216\206.md" @@ -0,0 +1,89 @@ +--- +updated: 2019-09-17 +issueid: 19 +tags: +- 算法与数据结构 +- Leetcode +title: 我所认为的最好二叉树三序遍历 +date: 2019-09-17 +--- +在此感谢 leetcode 网友提供的遍历方法 + +https://leetcode-cn.com/problems/binary-tree-inorder-traversal/solution/yan-se-biao-ji-fa-yi-chong-tong-yong-qie-jian-ming/ + +二叉树的遍历是老生常谈的话题,递归方法最容易理解,但空间复杂度极高,由此衍生出了迭代遍历,但思维量巨大,每种遍历都需要写不同风格的代码。 + +我最近重写遍历时发现 网友 独创的遍历方式。 + +采用颜色标记法,未访问的用白色标记,访问的用黑色标记。 + +我们针对不同的遍历,只需要修改添加到数组时的顺序即可。 + +三种遍历方式代码风格极其相似,你还会怕写不出迭代遍历的代码吗? + +前序遍历 + +``` +var preorderTraversal = function(root) { + let white = 0, black = 1 + let stack = [[white, root]] + let result = [] + while(stack.length > 0) { + let [color, node] = stack.pop() + if(!node) continue + if (color == white) { + stack.push([white, node.right]) + stack.push([white, node.left]) + stack.push([black, node]) + }else{ + result.push(node.val) + } + } + return result +}; +``` + +中序遍历 +``` +var inorderTraversal = function(root) { + let white = 0, black = 1 + let stack = [[white, root]] + let result = [] + while(stack.length > 0) { + let [color , node] = stack.pop() + if (!node) { + continue + } + if(color == white) { + stack.push([white, node.right]) + stack.push([black, node]) + stack.push([white, node.left]) + }else{ + result.push(node.val) + } + } + return result +}; +``` + +后序遍历 +``` +var postorderTraversal = function(root) { + let white = 0, black = 1 + let stack = [[white, root]] + let result = [] + while(stack.length > 0) { + let [color,node] = stack.pop() + + if (!node) continue + if (color == white) { + stack.push([black,node]) + stack.push([white, node.right]) + stack.push([white, node.left]) + }else { + result.push(node.val) + } + } + return result +}; +``` \ No newline at end of file diff --git "a/_posts/20-Go\345\271\266\345\217\221\346\211\223\345\215\260\346\225\260\345\255\227.md" "b/_posts/20-Go\345\271\266\345\217\221\346\211\223\345\215\260\346\225\260\345\255\227.md" new file mode 100644 index 0000000..0eaf293 --- /dev/null +++ "b/_posts/20-Go\345\271\266\345\217\221\346\211\223\345\215\260\346\225\260\345\255\227.md" @@ -0,0 +1,46 @@ +--- +title: Go并发打印数字 +date: 2019-09-17 +updated: 2019-09-17 +issueid: 20 +tags: +- Go +--- +其实说白了就是流程控制,现在有三个 `goroutine`,如果控制他们顺序呢? + +具体细节忘了,但好像美团面试官就是这么问的 + +当时没说明白,要是手写一下让面试官看下,说不定就不会挂掉了... + +```go +package main + +import ( + "fmt" + "sync" +) + +func main() { + c1 := make(chan struct{}) + c2 := make(chan struct{}) + group := sync.WaitGroup{} + group.Add(1) + go func() { + fmt.Println("g1") + c1 <- struct{}{} + }() + + go func() { + <- c1 + fmt.Println("g2") + c2 <- struct{}{} + }() + + go func() { + <- c2 + fmt.Println("g3") + group.Done() + }() + group.Wait() +} +``` \ No newline at end of file diff --git "a/_posts/21-\346\265\205\345\205\245\345\223\210\345\270\214\350\241\250.md" "b/_posts/21-\346\265\205\345\205\245\345\223\210\345\270\214\350\241\250.md" new file mode 100644 index 0000000..f8d3c88 --- /dev/null +++ "b/_posts/21-\346\265\205\345\205\245\345\223\210\345\270\214\350\241\250.md" @@ -0,0 +1,55 @@ +--- +title: 浅入哈希表 +date: 2019-09-17 +updated: 2019-09-19 +issueid: 21 +tags: +- 基础 +--- +### 为什么人们都说 Hash 表查找的时间复杂度是 O(1)? + +下面是我在 Stackoverflow 上看到的回答 + +https://stackoverflow.com/questions/730620/how-does-a-hash-table-work + +我翻译了一部分,又对一些缺少的概念添加了解释 + +哈希表经常通过数组与链表实现。我们设想一张存储姓名的表,在经过几次的插入之后,他可能会在内存中有如下形式。 + +`()` 中代表的是姓名的 哈希值 + +``` +bucket# bucket content / linked list + +[0] --> "sue"(780) --> null +[1] null +[2] --> "fred"(42) --> "bill"(9282) --> "jane"(42) --> null +[3] --> "mary"(73) --> null +[4] null +[5] --> "masayuki"(75) --> "sarwar"(105) --> null +[6] --> "margaret"(2626) --> null +[7] null +[8] --> "bob"(308) --> null +[9] null +``` + +关键的几点: + +1. 每一个数组元素都是一个`桶(bucket)`,刚开始是空的,引用着链表,其中包含着值`(在这个例子中是名字)` +2. 每个值 (比如 `fred` ,哈希值是 42)都连接着 `[hash % 桶的数量]` 这个桶,比如 `42 % 10 == [2]`, `%`是 `取余操作符`,余数是桶的编号 +3. 在同一个桶中的多个值的值可能冲突`(相同)`,这往往是由于他们取余之后的 `hash` 值相同导致的`(比如 42 % 10 == 2, 9282 % 10 == 2)`。 + - 大多数的 哈希表 都会通过比较早己经插入桶中的完整的值来处理冲突`(虽然这会降低性能,却保证了没有逻辑上的错误)` + +#### 链表的长度与负载系数(load factor)相关,与值的数量无关 + +当存储的元素增多时,表会自动扩大(创建一个更大的数组,存放更多的桶,删除旧的数组),以此来保证值的数量与桶的数量比例在 0.5 到 1.0 之前。 + +对于负载系数为1的加密级哈希函数, `1/e (~36.8%)` 的桶是空的 另外 `1/e (~36.8%)`的桶有一个元素, `1/(2e)` 或者 `~18.4%` 的桶有连个元素, `6.1%` 的桶有三个元素,`1.5%` 的桶有四个元素 `3%` 的桶有五个元素,不管元素数量多少,链表的平均长度大约是 `1.58`(100 个元素对应着 100 个桶,或者一亿个元素对应着一亿个桶),这也就是为什么我们 `查找/插入/删除` 是 `O(1)` 常数的时间复杂度 + +#### 什么是 负载系数(load factor)? + +哈希表中的元素超过某一个限度时会自动扩张,扩张之后的数组更大,同时每个链表的长度也会更短,那么我们的 `查找/插入/删除` 操作就越接近与常数 + +更大的负载系数`(0 < load factor <= 1)`带来空间占用的降低,但却提高了 `查找/插入/删除` 的时间花费 + +`Java` 的 `HashMap` 的默认负载系数是 `0.75`,作为空间和时间的折中。 diff --git "a/_posts/22-\345\246\202\344\275\225\344\275\277\344\270\200\344\270\252\345\256\271\345\231\250\350\277\220\350\241\214\345\244\232\344\270\252\350\277\233\347\250\213\357\274\237.md" "b/_posts/22-\345\246\202\344\275\225\344\275\277\344\270\200\344\270\252\345\256\271\345\231\250\350\277\220\350\241\214\345\244\232\344\270\252\350\277\233\347\250\213\357\274\237.md" new file mode 100644 index 0000000..599fb01 --- /dev/null +++ "b/_posts/22-\345\246\202\344\275\225\344\275\277\344\270\200\344\270\252\345\256\271\345\231\250\350\277\220\350\241\214\345\244\232\344\270\252\350\277\233\347\250\213\357\274\237.md" @@ -0,0 +1,121 @@ +--- +tags: +- 云计算 +title: 如何使一个容器运行多个进程? +date: 2019-09-21 +updated: 2019-10-31 +issueid: 22 +--- +本文翻译自 `https://docs.docker.com/config/containers/multi-service_container/` + + +我们都知道,所谓的容器就是一个被隔离到不同环境的进程而已,那我们如果在一个容器内运行多个进程呢? + +一个容器的主进程是 `ENTRYPOINT` 或者在 `Dockerfile` 最后的 `CMD` , + +容器的主进程负责管理它启动的全部进程。 + +在某些条件下,主进程由于出错而退出,容器退出时,主进程并不能优雅地处理停止的子进程。 + +如果你的进程会出现上述情况,你启动容器时可以使用 `--init` 标志,在容器内插入一个简易的初始化进程作为主进程,容器退出时`INIT` +处理这些停止的子进程 ---- 子进程作为孤儿进程会被托管到 INIT 进程 + +用上述方式管理容器的生命周期比使用笨重的 `sysinit`, `upstart`, 或者 `systemd` 更优雅 + +如果需要在一个容器内运行多个服务,你可以通过下面几种不同方式解决。 + +- 将全部的命令都封装到脚本中,并将脚本作为 CMD 执行。这是一个非常原生的做法。 + 比如 + ``` + #!/bin/bash + + # Start the first process + ./my_first_process -D + status=$? + if [ $status -ne 0 ]; then + echo "Failed to start my_first_process: $status" + exit $status + fi + + # Start the second process + ./my_second_process -D + status=$? + if [ $status -ne 0 ]; then + echo "Failed to start my_second_process: $status" + exit $status + fi + + # Naive check runs checks once a minute to see if either of the processes exited. + # This illustrates part of the heavy lifting you need to do if you want to run + # more than one service in a container. The container exits with an error + # if it detects that either of the processes has exited. + # Otherwise it loops forever, waking up every 60 seconds + + while sleep 60; do + ps aux |grep my_first_process |grep -q -v grep + PROCESS_1_STATUS=$? + ps aux |grep my_second_process |grep -q -v grep + PROCESS_2_STATUS=$? + # If the greps above find anything, they exit with 0 status + # If they are not both 0, then something is wrong + if [ $PROCESS_1_STATUS -ne 0 -o $PROCESS_2_STATUS -ne 0 ]; then + echo "One of the processes has already exited." + exit 1 + fi + done + ``` + + Dockerfile + + ``` + FROM ubuntu:latest + COPY my_first_process my_first_process + COPY my_second_process my_second_process + COPY my_wrapper_script.sh my_wrapper_script.sh + CMD ./my_wrapper_script.sh + ``` + +- 如果你先启动主进程但暂时需要在运行其他的进程(比如与主进程交互),那你可以使用 bash 的任务控制解决。 + + ```bash + #!/bin/bash + + # turn on bash's job control + set -m + + # Start the primary process and put it in the background + ./my_main_process & + + # Start the helper process + ./my_helper_process + + # the my_helper_process might need to know how to wait on the + # primary process to start before it does its work and returns + + + # now we bring the primary process back into the foreground + # and leave it there + fg %1 + ``` + + Dockerfile + + ``` + FROM ubuntu:latest + COPY my_main_process my_main_process + COPY my_helper_process my_helper_process + COPY my_wrapper_script.sh my_wrapper_script.sh + CMD ./my_wrapper_script.sh + ``` + +- 使用一个像 `supervisord` 一样的进程管理器。这是一个重量级的方法,需要将 `supervisord` 和它的配置文件打包到镜像(或者直接使用基于 `supervisord`的镜像),然后启动supervisor,它会根据配置文件代你管理进程,下面是一个使用 supervisord 的 Dockerfill,我们假定已经预先写好了 `supervisord.conf`,`my_first_process`, `my_second_process`,并放到与 `Dockerfile` 同级的目录 + + ``` + FROM ubuntu:latest + RUN apt-get update && apt-get install -y supervisor + RUN mkdir -p /var/log/supervisor + COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf + COPY my_first_process my_first_process + COPY my_second_process my_second_process + CMD ["/usr/bin/supervisord"] + ``` diff --git "a/_posts/23-\345\256\232\347\202\271\346\225\260\344\270\216\346\265\256\347\202\271\346\225\260\347\232\204\345\214\272\345\210\253.md" "b/_posts/23-\345\256\232\347\202\271\346\225\260\344\270\216\346\265\256\347\202\271\346\225\260\347\232\204\345\214\272\345\210\253.md" new file mode 100644 index 0000000..493baba --- /dev/null +++ "b/_posts/23-\345\256\232\347\202\271\346\225\260\344\270\216\346\265\256\347\202\271\346\225\260\347\232\204\345\214\272\345\210\253.md" @@ -0,0 +1,34 @@ +--- +tags: +- 基础 +title: 定点数与浮点数的区别 +date: 2019-10-22 +updated: 2019-10-22 +issueid: 23 +--- +小数点在数制中代表一种对齐方式,比如说你要比较1000和200哪个比较大你应该怎么做呢?你必须把他们右对齐: + +``` +1000 + 200 +``` +然后发现1比0(前面补零)大,所以1000比较大。那么如果是比较1000和200.01呢?这时候就不是右对齐了,而是对应位对齐,也就是小数点对齐: +``` +1000 + 200.01 +``` +小数点位置在进制表示中是至关重要的,位置差一位整体就要差进制倍(十进制就是十倍)。 + +在计算机中也是这样,虽然计算机使用二进制,但在处理非整数时,也需要考虑小数点位置的问题,无法对齐小数点就无法做加法、减法比较这样的操作。我们说小数点位置,永远是说相对于我们存储的数位来说的,比如说我们存储了01001001,然后小数点在第三位之后,也就是010.01001了。 + +在计算机中处理小数点位置有浮点和定点两种,定点就是小数点永远在固定的位置上,比如说我们约定一种32位无符号定点数,它的小数点永远在第5位后面,这样最大能表示的数就是 11111.111111111111111111111111111,它是32 - 2^-27,最小非零数是2^-27。定点数是提前对齐好的小数,整数是一种特殊情况,小数点永远在最后一位之后。 + +定点数的优点是很简单,大部分运算实现起来和整数一样或者略有变化,但是缺点则是表示范围,比如我们刚才的例子中,最大只能表示32;而且在表示很小的数的时候,大部分位都是0,精度很差,不能充分运用存储单元。浮点数就是设计来克服这个缺点的,它相当于一个定点数加上一个阶码,阶码表示将这个定点数的小数点移动若干位。由于可以用阶码移动小数点,因此称为浮点数。 + +> 作者:灵剑 +> +> 链接:https://www.zhihu.com/question/19848808/answer/120393769 +> +> 来源:知乎 +> +> 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 diff --git "a/_posts/24-\350\275\254\350\275\275-\347\276\216\345\233\275\344\270\272\344\275\225\350\200\214\346\210\230.md" "b/_posts/24-\350\275\254\350\275\275-\347\276\216\345\233\275\344\270\272\344\275\225\350\200\214\346\210\230.md" new file mode 100644 index 0000000..61a5211 --- /dev/null +++ "b/_posts/24-\350\275\254\350\275\275-\347\276\216\345\233\275\344\270\272\344\275\225\350\200\214\346\210\230.md" @@ -0,0 +1,191 @@ +--- +tags: +- 杂七杂八 +title: 转载-美国为何而战 +date: 2019-10-28 +updated: 2019-10-28 +issueid: 24 +--- +一、中国周边形势与美元指数周期1史上第一个金融帝国的出现 + +  这个问题,我相信在座的搞经济搞金融的同志比我更适合谈论它,有所不同的是我把它和战略问题结合起来谈。从1971年8月15日,美元与黄金脱钩之后,美元这艘大船就摘掉了它的锚,这个锚就是黄金。让我们向前追溯一下,1944年7月,美国为了从大英帝国手中接过货币霸权,由罗斯福总统推动建立了三个世界体系,一个是政治体系——联合国;一个是贸易体系——关贸总协定,也就是后来的WTO;一个是货币金融体系,也就是布雷顿森林体系。 + + +  布雷顿森林体系按照美国人的愿望,是确立美元的霸权地位。但是实际上经过20多年的实践,从1944年到1971年,整整27年,却并没有真正让美国人拿到霸权。什么东西阻挡了美元的霸权?就是黄金。布雷顿森林体系建立之初,为了确立美元的霸权,美国人曾经对全世界做出承诺,就是要各国的货币锁定美元,而美元锁定黄金。怎么锁定呢?每35美元兑换1盎司黄金。有了美元对全世界的这个承诺,美国人就不可能为所欲为。说的简单点,35美元兑换1盎司黄金,意味着美国人不能随便地滥印美元,你多印35美元,你的金库里就要多储备1盎司黄金。 + + + + +  美国之所以有底气对全世界做出这样的承诺,是因为它当时手中掌握了全球80%左右的黄金储备。美国人认为,我有这么多黄金在手,用它去支撑美元的信用是没有问题的。但是情况并不像美国人想的那么简单。美国在二战之后连续愚蠢地卷入了朝鲜战争和越南战争。这两场战争使美国耗费巨大,尤其是越南战争。越南战争期间美国差不多打掉了八千亿美元的军费。随着战争花费越来越大,美国有点吃不住劲了。因为按照美国的承诺,每35美元的流失就意味着1盎司黄金的流失。 + + +  到1971年8月,美国人手里的黄金大概还有8800多吨,这时美国人知道有点麻烦了,与此同时有些人还在给美国人制造新的麻烦。比如说法国总统戴高乐,他不相信美元,他找来法国财政部长和央行行长,要他们看一下法国有多少美元储备,得到的答案是大概有22亿—23亿美元。戴高乐说,一分都不剩全部提出来交给美国人,换成黄金拿回来。法国人对美国人的这一击,对其他国家产生了示范效应,其他一些外汇盈余的国家纷纷向美国人表示,我们也不要美元,我们要黄金。这样就逼得美国人无路可走。 + + +  于是,在1971年8月15日,时任美国总统尼克松宣布关闭黄金窗口,美元与黄金脱钩。这就是布雷顿森林体系瓦解的开始,也是美国人对世界的一次背信弃义。但是对于整个世界来讲,当时人们还不能完全理清楚头绪。原来我们相信美元是因为美元背后有黄金,美元成为国际流通货币、结算货币、储备货币已经实行了20多年了,人们已经习惯使用美元。现在美元突然刹车,它的背后不再有黄金,从理论上讲,它变成了一张纯粹的绿纸,这个时候我们还要使用它吗?你可以不使用它,但在国际间结算时用什么对商品的价值进行衡量?因为货币是价值尺度,所以如果不使用美元,难道还能信任别的货币?比如人民币和卢布之间,俄罗斯人(当时的苏联人)如果不认人民币,我们不认卢布的话,就只能继续拿美元做为我们之间的交换介质。 + + +  所以,美国人就利用世人的惯性和无奈,在1973年10月迫使石油输出国组织(欧佩克)接受了美国人的条件:全球的石油交易必须用美元结算。在此之前,全球的石油交易可以用各种国际流通货币结算,但是从1973年10月以后,一切改变了,欧佩克宣布,必须用美元对全球的石油交易进行结算。这样,美国人在使美元与黄金贵金属脱钩之后又与大宗商品石油挂钩。为什么?因为美国人看的很清楚,你可以不喜欢美元,但你不可以不喜欢能源,你可以不使用美元,但你能不使用石油?任何国家要发展,都要消耗能源,所有国家都需要石油,在这种情况下,你需要石油就等于需要美元,这是美国人非常高明的一招。从1973年开始美元与石油挂钩以后,其实是从1971年美元与黄金脱钩之后,美元就伴随美国开始了一个新的历程。 + + +  整个世界当时并没有几个人清晰的看到这一点,包括很多经济学家、金融专家,他们不能够非常清晰地指出,20世纪最重要的事件不是别的,不是一战、二战,也不是苏联的解体,20世纪最重要的事件是1971年8月15日美元与黄金脱钩,从此之后,人类真正看到了一个金融帝国的出现,而这个金融帝国把整个人类纳入到它的金融体系之中。实际上所谓美元霸权的建立是从这个时刻开始的。到今天大约40年的时间。而从这一天之后,我们进入到一个真正的纸币时代,在美元的背后不再有贵金属,它完全以政府的信用做支撑并从全世界获利。简单地说:美国人可以用印刷一张绿纸的方式从全世界获得实物财富。人类历史上从来没有过这样的事情。人类历史上获得财富的方式很多,要么用货币交换,你要么黄金或者白银;要么用战争的方式去掠夺,但是战争的成本非常巨大。而当美元变成一张绿纸出现后,美国获利的成本可以说极其的低廉。 + + + + +  因为美元与黄金脱钩,黄金不再拖美元的后腿,美国可以随意印刷美元,这时如果大量美元留在美国国内,将造成美国的通胀;如果美元输出去,那就意味着全世界替美国消化通胀,这就是美元通胀率不高的主要原因。换句话说,美国向全球输出美元,也就稀释了它的通胀。但是美元向全球输出之后,美国人手里就没有钱了,这个时候如果美国人继续印刷货币,美元就不断贬值,这对美国没有好处。所以说美联储并不像有些人所想象得那样,是一个滥印货币的中央银行。美联储实际上懂得什么叫克制。从1913年美联储成立到2013年100年,美联储一共发行了多少美元?大约10万亿。 + + +  这样一比较,有人开始指责中国的央行。为什么呢?我们的央行从1954年发行新货币——新人民币到现在,已经发行了120多万亿人民币。如果按汇率6.2跟美元折算的话,我们大概发行了20万亿美元。但是这同样并不意味着中国乱印货币,因为中国在改革开放以后挣到了大量美元,同时这期间还有大量的美元做为境外投资进入中国。但由于外汇管制,美元不能在中国流通,所以央行就必须发行与进入中国的美元及其他外币相应的人民币,然后以人民币在国内流通。可是国外的投资在中国挣到了钱以后,可能就撤走了;与此同时,我们还会拿出大量外汇,从境外够买资源、能源、产品和技术,如此一来,大量的美元走了,人民币留了下来,你又不可能将相应数额的人民币销毁,只能让人民币留在中国继续流通,所以我国人民币的存量必然大于美元。这也反过来佐证了这30多年中国经济的惊人发展。中国央行承认近年来大概超发了20多万亿人民币。巨量的超发最后全都留在了中国,这就牵扯到我后面将要谈到的问题——人民币为什么要国际化。 + + +2美元指数周期律与全球经济的关系 +  美国之所以没有通胀很大程度上就在于美元的全球流通。但是美国又不能无节制地发行美元,让美元不断贬值。所以要节制。可节制后手中没有美元了怎么办?美国人有另外一套办法解决这个问题,那就是发行国债,通过发行国债又让输出去的美元重新回到美国。但输出去的通过债务资本重新回到美国,美国人开始玩起一手印钱,一手借债的游戏,印钞能赚钱,借债也能赚钱,以钱生钱,金融经济比实体经济赚钱来得痛快多了,谁还愿意出大力流大汗去干附加值低的制造业、加工业实体经济?1971年8月15日之后,美国人逐渐放弃了实体经济而转向虚拟经济,渐渐变成一个空心化的国家。今天美国的GDP已经达到18万亿美元,实体经济为其GDP的贡献不超5万亿,剩下的大部分全都是虚拟经济带来的。美国通过发行国债,让大量在海外流通的美元重新回到美国,进入美国的三大市——期货市场、国债市场和证券市场。美国人通过这个方式钱生钱,然后再向海外输出,这样循环往复地生利,美国由此变成一个金融帝国。美国把全世界纳入它的金融体系之中。很多人认为在大英帝国衰落之后,殖民的历史基本就结束了。其实不然,因为美国成为金融帝国之后,开始用美元进行隐性的殖民扩张,通过美元隐蔽地控制各国经济,从而把世界各个国家变成它的金融殖民地。今天我们看到很多主权独立的国家包括中国在内,你尽可以有主权、有宪法、有政府,但是你脱离不开美元,你的一切最后都会通过各种方式用美元来表达,并最终让你的实物财富通过与美元的兑换源源不断地进入美国。 + + + + +  这一点,通过40年来美元指数周期图表,我们可以看得非常清楚。1971年8月15日美元跟黄金脱钩,意味着美国人摆脱了黄金的束缚,可以随意地印刷美元了,美元发行量大增,美元指数自然要走低。从1971年特别是1973年石油危机之后美元指数就一直在走低,这就说明美元印多了。如此这般大约持续了近10年时间。美元指数走低对于世界经济来讲并不完全是坏事,因为这意味着美元的供应量增大,也就意味着资本的流量增大,大量的资本不留在美国,要向国外出溢出。第一次美元指数走低之后,大量美元去了拉丁美洲,给拉丁美洲带去了投资拉动,也带来了繁荣,这就是70年代拉美的经济繁荣。 + + +  美元泄洪期大约持续了近10年左右的时间,直到1979年,美国人决定关掉泄洪闸。美元指数走低相当于美国人开闸放水,而关闸实际上就是减少美元的流动性。1979年美元指数开始走强,意味着向其他地方输送美元减少。拉丁美洲本来因为获得了大量的美元投资,正在欣欣向荣的发展,突然间投资减少了,流动性枯竭了、资金链条断裂了,经济能不出现麻烦吗? + + +  遇到麻烦的拉美国家纷纷开始想办法自救。比如阿根廷,阿根廷的人均GDP一度已经迈进了发达国家的行列。但拉美经济危机一出现,阿根廷却率先进入衰退。解决衰退的办法有多种,但不幸的是,当时的阿根廷政府是通过政变上台的军政府,总统是加尔铁里,他完全没有经济头脑。作为军人的加尔铁里唯一的想法就是战争,他希望通过战争来脱困。他把目光瞄向了离阿根廷600公里远的马尔维纳斯群岛,英国人把它叫做福克兰群岛。这个群岛已经被英国人统治了100多年,加尔铁里决定把它夺回来。但阿根廷是南美洲国家,南美一向被视作美国的后院。在美国后院打仗不能不请示美国。于是加尔铁里让人给美国总统里根带话,看看美国的态度。里根明明知道加尔铁里打这一仗,会导致一场和英国的更大规模战争,但他却轻描淡写地表态说,这是你们与英国之间的事情,与美国无关,我们不持立场,我们保持中立。加尔铁里以为这是美国总统对他的默许,便发动了马岛战争,轻松收回了马岛。阿根廷上下一片欢呼,热烈的像过狂欢节。但英国首相撒切尔夫人宣称绝不接受这个结局,还逼着美国总统必须表态。这时的里根立刻撕下中立的面具,发表声明强烈谴责阿根廷的侵略行径,坚决站在英国一边。随后,英国派出一支航母特混舰队,劳师远征8000海里,一举拿下了马岛。 + + +  与此同时,美元走势开始走强,国际资本按照美国的意愿回到美国。因为当马岛战争打响后,全球的投资人立刻判断,拉美的地区性危机出现了,拉美的投资环境恶化了,于是纷纷从拉美撤资。美联储看到时机已到,立刻宣布美元加息,加息后的美元加快了资本撤出拉美的步伐。拉美的经济一片狼藉。从拉美撤出的资本几乎全到了美国,去追捧美国的三大市(债市、期市、股市),给美国带来了美元与黄金脱钩后的第一个大牛市,让美国人赚得钵满盆满。当时美元指数从弱势时的60多点一口气蹿升到120多点,上升了100%。美国人在自己的三大市牛市后并不收手,有趁势拿着赚到的钱,重新回到拉美去购买那些此时价格已跌成地板价的优质资产,狠狠剪了一次拉美经济的羊毛,这是美元指数第一次走强后的情况。 + + +  如果这种事情只发生一次,那它就是小概率事件;如果它反复出现,那它就一定是规律。当第一次“十年美元走弱、六年美元走强”之后,人们并不确定它是不是规律。从拉美金融危机这个高峰之后,美元指数从1986年又开始一路下跌。其间经历了日本金融危机、欧洲货币危机,美元指数仍然在走低,大约走了10年,10年之后的1997年美元指数再一次走强。美元指数这一次走强之后也是持续了6年。这就很有意思了,我们看到美元指数差不多呈现出这样一个规律性——10年的走弱、6年的走强,再一个10年的走弱、接下来又一个6年的走强。 + + + + +  在1986年美元指数第二次开始走弱之后,长达10年的时间内,美元又像洪水一样向世界倾泄。这次主要的泄洪区是亚洲。上个世纪80年代最火的是什么概念?“亚洲四小龙”、“亚洲雁阵”等等。当时很多人都认为亚洲的繁荣是由亚洲人的辛勤劳动、亚洲人的聪敏智慧带来的,实际上很大原因是因为亚洲国家获得了充足的美元、获得了充足的投资。当亚洲的经济欣欣向荣到差不多的时候,美国人觉得又应该到剪羊毛的时候了,于是,1997年,也就是美元指数整整走低10年之后,美国人通过减少对亚洲的货币供应,使美元指数反转走强,亚洲大多数国家的企业和行业遭遇流通性不足,有的甚至干脆资金链条断裂,亚洲出现了经济危机和金融危机的征兆。 + + +  这时候一锅水已烧到99度还差1度才能烧开,差哪一度呢,差地区性危机出现。那么是不是也像阿根廷人那样打一仗呢?未必。制造地区性危机,不一定只有打仗一途。既然制造地区性危机就是为了撵出资本,那么不打仗照样有制造地区性危机的办法?于是我们看到那个名叫索罗斯的金融投机家,带着他的量子基金和全世界上百家的对冲基金,开始群狼般攻击亚洲经济最弱的国家——泰国,攻击泰国的货币——泰铢。一个星期左右,然后由此开始的泰铢危机,立刻产生传导效应,一路向南,陆续传导到马来西亚、新加坡、印尼、菲律宾,然后北上传导中国台湾、香港,日本,韩国,一直传导到俄罗斯,东亚金融危机全面爆发。这个时候水已烧开。全球的投资人判断亚洲的投资环境恶化,便纷纷从亚洲撤出自己的资本。而美联储则又一次不失时机地吹响了加息的号角。跟着号音从亚洲撤出的资本又一次到美国去追捧美国的三大市,给美国带来了第二个大牛市。当美国人挣够了钱以后,仍像在拉丁美洲那样,拿着他们从亚洲金融危机赚到的大把的钱又回到了亚洲,去购买亚洲跌到地板价上的优质资产。此时亚洲经济已经被这次金融危机冲得稀里哗啦,毫无招架之功,更无还手之力,这次唯一的幸运者是中国。 + + +3现在,轮到中国了 +  此后,如潮汐一样准确,美元指数经过6年的走强,到2002年,再一次开始走弱,然后,又是10年时间,到2012年,美国人又开始为美元指数即将由弱转强做准备。办法还是老一套:给别人制造地区性危机。于是,我们就先后看到,在中国周边陆续出现天安号事件,钓鱼岛争端,黄岩岛争端。几乎全在这一时期密集出现。但是很不巧,美国在2008年自己玩火玩大了,自己先遭遇了金融危机,结果使美元指数走强的时间被迫向后推延。中菲黄岩岛争端和中日钓鱼岛争端,看似和美元指数走强没有太大的关系,但是真的没有关系吗?为什么恰恰出现在美元指数第三次走弱之后的第10个年头?很少有人对这个问题进行过探究,但是这个问题确实值得我们深思。 + + + + + +  如果我们承认从1971年美元与黄金脱钩之后,确实存在着一个美元指数周期率,那么,根据这个周期率及美国人借机剪别国羊毛的手法,我们可以断定,现在轮到中国了。为什么这么说?因为眼下中国已经成了从全球吸引和获得投资最多的国家,大量国际资本由于看好中国经济进入中国。从经济规律上讲,不能仅仅把中国看成是一个国家。一个中国的经济规模就相当于整个拉美,甚至比拉美的经济总量还要大;和东亚经济比,也可以说中国经济相当于整个东亚。而过去十年里,大量资本进入中国,使中国的经济总量,以令人垂涎的速度增长到全球第二,如此一来,美国把第三次剪羊毛的目标瞄准中国,一点不奇怪。 + + +  如果这一判断成立,那么,从2012年中日钓鱼岛争端、中菲黄岩岛争端之后,中国周边的事情层出不穷,一直到去年中越“981”钻井平台冲突,再到后来的香港“占中”事件。这些事件还能看成是偶然事件吗?去年5月,我陪国防大学的政委刘亚洲将军到香港去考察时,已经得知“占中”行动正在酝酿中,可能在5月底就会发生。但是5月底没有发生,6月底没有发生,7月还是没有发生,到了8月仍然没有发生。什么原因?这个酝酿的“行动”在等什么?让我们对比另一事件的时间表:美联储退出QE时间表。去年年初,美国就说要退出QE(量化宽松),4月、5月、6月、7月、8月,一直没有退出。只要不退出QE,就意味着美元还在超量发行,美元指数就不能走强,香港的“占中”也就一直没有出现,二者在时间表上完全重合。直到去年9月底,美联储终于宣布美国退出QE,美元指数开始掉头走强后,10月初,香港“占中”爆发。其实,中日钓鱼岛、中菲黄岩岛、981钻井平台、香港“占中”,这四个点都是炸点,任何一个点引爆成功,都会引发地区性金融危机,也就意味着中国周边投资环境恶化,从而满足“美元指数走强时,其他地区必须相应出现地区性危机,使该地区投资环境恶化,迫使投资人大量撤出资本”,这一美元获利模式的基本条件。但是对美国人来讲很不幸的是,这回它碰到的对手是中国。中国人用打太极的方式,一次次化解了周边危机,结果直到现在,美国人最希望的在99度水温时出现的最后1度,始终没能出现,水,也就一直没有烧开。 + + +  水没烧开,美联储举着加息的号角就迟迟不能吹响。看来,美国知道想剪中国的羊毛没那么容易,所以也就没打算就在一棵树上吊死。在推动香港占中的同时,美国多管齐下,在其他地区同时下手,在哪儿?乌克兰。欧盟与俄罗斯的接合部。亚努科维奇领导下的乌克兰,当然不是没缝的鸡蛋,所以,才会有让苍蝇下蛆的机会。但美国盯上了乌克兰并不仅仅是因为它是一只有缝的鸡蛋,而是它是足以既打击亚努科维奇这个不听话的政客,又阻断欧俄走近,也能造成欧洲投资环境恶化,一桃杀三士的理想目标。于是,一场貌似乌克兰人自发的“颜色革命”爆发了,美国人的目的以出乎美国人和地球人意料的方式实现了:俄罗斯强人普京趁势借机收回了克里米亚,此举虽不在美国人计划之内,但却正好让美国人更有理由向欧盟还有日本施压,迫使他们与美国一起制裁俄罗斯,给俄罗斯更给欧洲经济带来巨大的压力。 + + + + + +  美国人为什么要这样做,人们往往容易从地缘政治角度,而不是从资本的角度去看这个问题。乌克兰出现危机之后,欧美与俄罗斯的关系迅速恶化,但整个西方世界一起制裁俄罗斯的结果,却直接使欧洲的投资环境恶化,导致资本从这里撤出。据有关数据显示,大约有上万亿的资本离开了欧洲。美国人的两手设计得逞了。这就是:如果不能让资本从中国撤出去追捧美国的话,那就起码让欧洲的资本撤出来回流美国。这第一步,以戏剧性的乌克兰变局实现了,但第二步,却未能如美国所愿。因为从欧洲撤出的资本,并没有去美国,另有数据显示,它们大部分来到了香港。这意味着全球投资人仍然不看好美国经济的复苏。而宁愿看好虽已处在经济下行线上,但仍保持着全球第一增长率的中国。 + + +  这是其一。其二是中国政府在去年宣布了要实现“沪港通”,全球的投资人都热切地希望通过“沪港通”,在中国捞一把。过去西方资本不敢进入中国股市,很重要的一个原因,就是中国进行严格的外汇管制,宽进严出,你可以随意进来,但是你不能随意出去,所以说他们一般不敢到中国来投资中国的股市。“沪港通”之后,他们可以很轻松地在香港投资上海的股市,挣到钱后可以转身就走,于是上万亿的资本滞留在了香港。这就是去年9月之后,也就是香港“占中”开始直到今天,“占中”势力及其幕后推手始终不肯罢休,总想卷土重来的一个很重要的原因。因为美国人需要制造一次针对中国的地区性危机,让滞留香港的资本撤出中国,去追捧美国经济。 + + +  美国经济为什么这么强烈地需要并依赖国际的资本回流?原因是,从1971年8月15日美元与黄金脱钩之后,美国经济逐渐放弃实物生产,脱离开实体经济。美国人把实体经济的低端制造业、低附加值产业叫做垃圾产业或者叫做夕阳产业,逐渐向发展中国家转移,尤其是向中国转移。而美国除了留下所谓高端的产业,IBM、微软等企业外,70%左右的就业人口都陆续转向了金融和金融服务业。这时的美国已经变成了一个产业空心化国家,它已经没有多少实体经济可以为全球投资人带来丰厚的利润。在这种情况下美国人不得不打开另一扇门,就是虚拟经济的大门。虚拟经济就是它的三大市。它只能通过让国际资本进入三大市的金融池子中,为自己钱生钱。然后,再拿挣到的钱去剪全世界的羊毛,美国人现在只有这么一个活法了。或者我们称之为美国的国家生存方式。这个方式就是,美国需要大量的资本回流来支撑美国人的日常生活和美国经济,在这种情况下谁阻挡了资本回流美国,谁就是美国的敌人。我们一定要把这件事弄明白想清楚。 + + +二、中国快速兴起动了谁的奶酪1欧元的诞生为什么会招来一场战争 + + + +  1999年1月1日,欧元正式诞生。三个月之后科索沃战争爆发。很多人以为科索沃战争是美国和北约联手打击米洛舍维奇政权,因为米洛舍维奇政权在科索沃地区屠杀阿族人,制造了骇人听闻的人道主义灾难。战争结束之后,这个谎言迅速戳破,美国人承认这是中情局与西方的媒体联手做的一个局,目的是打击南联盟政权。但是,科索沃战争真的是为了打击南联盟政权吗?欧洲人开始一边倒地认为就是这个目的,但是打完这场72天的战争之后,欧洲人才发现自己上当了,为什么? + + +  欧元启动之初,欧洲人信心满满。他们给欧元的定价是与美元比值1:1.07。科索沃战争爆发后,欧洲人参与北约行动,全力以赴支持美国攻打科索沃,72天的狂轰乱炸,米洛舍维奇政权垮台,南联盟屈服。可接下来一盘点,欧洲人发现不对头了,欧元,就在这70天里,居然被这场战争打残了。战争结束时,欧元直线下跌30%,0.82美元兑换一欧元。这时欧洲人才恍然大悟,原来是别人把你卖了,你还在替别人数钱。这一来欧洲人才开始醒悟。这就是为什么到后来当美国要打伊拉克的时候,法国和德国这两个欧盟的轴心国家,坚决反对这场战争。 + + +  有人说,西方民主国家之间不打仗,到现在为止,西方国家之间,在二战之后确实没有直接发生过战争,但是不等于没有发生军事战争,不等于他们之间不发生经济战争或金融战争。科索沃战争就是美国人对欧元的间接金融战争,结果打的是南联盟,疼的是欧元。因为欧元的诞生动了美元的奶酪。在欧元诞生之前全世界的流通货币是美元,美元在全球的结算率一度高达80%左右,即使到现在也在60%左右。欧元的出现立刻切走了美国的一大块奶酪。欧盟是一个27万亿美元的经济体,它的出现一下子就盖过了当时世界上最大的经济体北美自贸区(24万亿美元—25万亿美元规模)。做为如此大规模的经济体,欧盟当然不甘心用美元来结算它内部的贸易,于是欧洲人决定推出自己的货币——欧元。欧元的出现切走了美元三分之一的货币结算量,到现在世界上23%的贸易结算已使用的是欧元而不是美元。美国人在欧洲一开始谈论欧元时对此警惕不足,到后来发现欧元一出现就对美元的霸权地位构成挑战时,已经有点来不及了。所以,美国要接受这个教训,一方面要摁住欧盟和欧元,另一方面要摁住其他的挑战者。 + + +2“亚太战略在平衡”要平衡什么? +  中国的兴起,让我们成了新的挑战者。2012年钓鱼岛争端、黄岩岛争端,就是美国成功打压挑战者的最新尝试。这两个发生在中国周边的地缘政治事件,虽没能造成资本从中国大量外流,但却起码部分达到了美国人的目标,直接导致两件事情胎死腹中。2012年年初,中日韩关于东北亚自贸区的谈判接近成功;4月,中日货币互换和中日之间互相持有对方国债也初步达成协议。但此时,钓鱼岛争端,黄岩岛争端相继出现,一下子把东北亚自贸区谈判,中日货币互换一风吹了。几年后的现在,我们才勉强完成了中韩两国的双边自贸区的谈判,这已经意义不大了,因为它和中日韩东北亚自贸区的意义完全不可同日而语,为什么这么说?因为中日韩自贸区谈判一旦成功,一定是包括中、日、韩、港、澳、台的整个东北亚自贸区。东北亚自贸区一经形成就意味着世界规模大约20多万亿美元的第三大经济体出现。但是,东北亚自贸区一旦出现就不会止步,它会迅速南下与东南亚自贸区整合,形成东亚自贸区,东亚自贸区的产生意味着30多万亿美元规模的世界第一大经济体出现,将超越欧盟和北美。接下来我们还可以继续推想,东亚自贸区出现,依然不会止步,它会向西整合印度和南亚,然后向北整合中亚五国,再然后继续向西,整合中东部分的西亚。这样整个亚洲自贸区,规模将超过50万亿美元,将比欧盟和北美加起来还要大,这么庞大的一个自贸区出现,难道她会愿意用欧元或美元结算他们内部的贸易吗?当然不会。这就意味着亚元可能诞生。 + + + + +  但我认为,如果真的出现亚洲自贸区,我们只能推进人民币国际化,让人民币成为亚洲的主导货币,就像美元先成为北美的硬流通货币,再成为全世界的硬流通货一样。人民币国际化的意义远远不止我们所说的人民币走出去,在“一带一路”中发挥作用等等,它将与美元、欧元一起三分天下。中国人能看到这一点,美国人就看不到这一点吗?当美国人宣布战略重心东移,推动日本在钓鱼岛跟中国扯皮、推动菲律宾在黄岩岛跟中国对峙的时候,如果我们还目光短浅地以为,钓鱼岛争端是日本右翼鼓动“购岛”后与中国的冲突,黄岩岛争端是菲律宾总统阿基诺昏了头找中国的麻烦。而看不出这是美国人的深谋远虑,是美国人在阻止人民币成为美元的有一个挑战者,而美国人则非常清楚地知道自己在干什么,所以他们一定不能让这样的事情再次出现。因为东北亚自贸区一旦形成就会产生连锁反应,也就意味着世界货币三分天下成为现实,想一想看,手中只剩三分之一货币霸权的美元,还叫货币霸权吗?而今天一个产业空心化的美国,假如再没有了货币霸权,美国还能算世界霸主吗?想明白这点,就知道为什么今天中国遇到的所有麻烦背后,都有美国的影子。是因为美国比我们想得远,看得深,才为了防中国之“患”于未然,处处给我们制造麻烦。这也就是美国为什么要实施亚太战略再平衡的根本原因。它究竟要平衡什么?它真的要在中国与日本、中国与菲律宾,中国与其他有争端的国家之间,实现一种微妙平衡,扮演平衡者的角色吗?当然不是,它的目标就是一个,平衡掉中国今天大国崛起的势头。 + + +三、美军为美元而战的秘密1伊拉克战争与石油交易用谁的货币结算 +  人们都说,美国这个国家的强大,是由于有三大支柱——货币、科技、军事。实际上今天我们可以看到,真正支撑美国的是货币和军事,而支撑货币的则是美国的军事力量。全世界所有国家军队的打仗都是烧钱,但美军打仗虽然也烧钱。但却能一边烧钱,一边为美国挣钱,这一点,其他国家都做不到。只有美国,可以通过打仗获得巨大的利益,尽管美国也有失手的时候。 + + + + + +  美国人为什么要打伊拉克?大部分人心里想到的是两个字——石油。美国人真的是为石油而战吗?非也。美国人如果为石油而战的话,那么,美国人在打下伊拉克之后,为什么不从伊拉克拉走一桶石油?而且,油价从战前的38美元一桶,一路飙升到战后的149美元一桶,美国老百姓并没有因为美军占领了伊拉克这样的产油国而享受低油价。所以说,美国打伊拉克不是为了石油,而是为了美元。 + + +  为什么这么说?道理非常简单。由于为了控制世界,美国需要全世界都使用美元。为了让全世界都使用美元,美国人在1973年下了一步高明的先手棋:让美元与石油挂钩,通过胁迫欧佩克的主导国家沙特阿拉伯,实现了全球的石油交易用美元结算。如果你理解了全球石油交易用美元结算,你就能理解美国人为什么要在产油国打仗。在产油国打仗的一个直接后果就是油价飙升,油价一飙升就意味着美元的需求量也提高了。比如说战争之前,你手里有38美元,理论上讲,你就可以从油商手里买走一桶石油。现在这场战争把油价打高了4倍多,达到149美元,你手里的38美元就只够买1/4桶石油,剩下3/4桶意味着你还差100多美元。怎么办?你只能去找美国人,拿出自己的产品和资源去换美国人手中的美元。而这时美国政府就可以理直气壮、光明正大、名正言顺地印美元。这就是通过战争,通过在产油国打仗打高油价,打出美元需求的秘密。 + + +  我想告诉大家的是,美国人在伊拉克打仗,还不止是这一个目标。它同时也是在维护美元的霸权地位。当年小布什为什么一定要打伊拉克?现在我们已经看的很清楚,萨达姆没有支持恐怖主义,没有支持基地组织,也没有大规模杀伤性武器,但为什么萨达姆却最终走上了绞刑架?因为萨达姆自以为聪敏,想在在大国之间玩火。1999年欧元正式启动,萨达姆以为抓住了在美元和欧元,美国和欧盟之间玩火的机会,于是他迫不及待地宣布伊拉克的石油交易将用欧元结算。这一下惹火了美国人,尤其是它产生一连串的示范效应,俄罗斯总统普京,伊朗总统内贾德,委内瑞拉总统查韦斯,纷纷宣布自己国家的石油出口结算也用欧元结算。这还了得?这不是往美国人胸口捅刀子么?所以说这场伊拉克战争非打不可,有些人认为这么说太牵强了。那么请他看一看,美国人打下伊拉克之后干了些什么?还没等抓住萨达姆,美国人就迫不及待地成立了伊拉克临时政府,临时政府发布的第一道法令,就是宣布伊拉克的石油出口,从欧元结算改回用美元结算。这就是为什么说美国人是在为美元作战。 + + +2阿富汗战争与美国资本项目顺差 + + + +  也许有人会说,伊拉克战争为美元而战可以理解,阿富汗不是产油国,那么美国大打阿富汗战争总不会是为了美元吧?何况阿富汗战争是在“9·11”发生之后,全世界的人都看得很清楚,美国是为了对基地组织的报复和对支持基地组织的塔利班的惩罚,才发动了阿富汗战争。但事实真的如此吗?阿富汗战争是在“9·11”之后一个多月打响的,应该说打得很仓促,打到一半时美军就把巡航导弹打光了,而战争还在继续,美国防部不得不下命令打开核武器库,取出1000枚核巡航导弹,摘下核弹头,换上常规弹头,又打了900多枚才把阿富汗打下来。这明摆着证明这一场准备得非常不充分,既然如此,美国人为什么硬要仓促上阵呢? + + +  因为美国人已经等不及了,更因为美国人的日子过不下去了。21世纪初期,美国作为一个产业空心化的国家,每年都大约需要有7000亿美元的净流入,才能过日子。可是“9·11”之后一个月内,全球投资人对美国的投资环境恶化,表示出了从未有过担心和忧虑:如果强大如美国对自己的安全都不能保证,怎么能够保证投资人的资金安全?结果,3000多亿的热钱离开了美国。这就迫使美国必须尽快打一仗,这一仗不仅是要惩罚塔利班和基地组织,还要给全球投资人一个信心。随着第一枚巡航导弹在喀布尔炸响,道琼斯指数迅速回升,一天之内回升600点,流出的资本开始回流美国,到年底,大约有4000多亿美元回流美国。这不正说明,阿富汗战争同样是为美元而战,是为资本而战。 + + +3航母为何让位于全球快速打击系统 +  不少人对中国的航母充满了期待,因为他们都看到了在航母历史上的作为,也热切地盼望中国有自己的航母,而辽宁号的出现,也确实让我们中国赶上航母的末班车。虽然航母在今天仍然是一个大国的标志,但是它更多的就是个标志。因为在全球经济越来越被金融化之后的今天,航母的作用将逐渐式微。因为在历史上航母是物流时代的产物。大英帝国兴盛时,要推动全球贸易,将它的产品推向全球,然后再把资源拿回来,所以它需要强大的海军保证海上通道的畅通。直到后来发展到航母的出现,都是为了控制海洋,保证海上通道的安全。因为当时是资源和产品“物流为王”的时代,谁控制了海洋,谁就能控制了全球财富的流动。但是今天世界已是“资本为王”的时代,成百亿、上千亿乃至上万亿的资本,从一个地方流到另一个地方,只要在电脑上敲几个键,几秒钟之内就能完成,在大洋上航行的航母能跟随物流的速度,却无法跟上资本流的速度,当然也就无法控制全球资本。 + + +  那么今天,有什么办法?可以跟上被互联网支持的全球资本的流向、流量和流速?美国人正在开发庞大的全球快速打击系统,用弹道导弹、超音速飞机,5倍甚至十几倍于超音速的巡航导弹,就可以迅速打击任何资本云集的地区。现在美国号称可以28分钟打遍全球,不管资本在全球任何一个地方云集,只要美国不想让资本在那个地方落脚,导弹就可以在28分钟后赶到那里。而当导弹落下去的时候,资本就会乖乖撤出。这就是全球快速打击系统必将取代航母的原因。当然,航母在未来仍会有它不可替代的作用,诸如保障海上通道的海上安全,或者执行人道主义使命等等,因为航母是非常不错的海上平台。但是作为控制未来资本流动的武器,它已经远不如全球快速打击系统。 + + +四、“空海一体战”难解美国之困 + + +  美国人在考虑用军事手段应对中国崛起的问题上,提出了一个概念,叫做“空海一体战”。我认为“空海一体战”仍然难解美国的困局。“空海一体战”是2010年美空军和海军首脑联合提出的针对中国的作战概念。提出“空海一体战”,实际上首先就反映出美军今天正在走弱。美军过去以为,它用空袭可以打击中国,用海军也可以打击中国。现在美国发现自己的力量无论是空军还是海军单独使用,都不可能对中国构成优势,必须空海联合才能对中国构成一定的优势,这就是空海一体战的来由。但是空海一体战从2010年年初提出到现在不过4年多一点,突然美国人给它改了个名称,叫做“全球公域介入与机动联合概念”。 + + +  在这个空海联合行动构想中,美国人认为10年内,中美之间不会发生战争。因为美国人研究中国今天的军力发展后,认为以美军现有能力,不足以确保抵消中国军队已建立的一些对美优势。如攻击航母的能力和摧毁太空系统的能力,所以,美国必须再拿出10年时间发展更先进的作战系统,以抵消中国的某种优势。这意味着美国人可能的战争时刻表拨到了10年后。虽然10年后战争也仍可能不会发生,但我们都必须对此做好准备。中国要想让10年后也不会发生战争,就需要在这10年内把我们自己的事情做得更好,包括军事和战争的准备。 + + +五、“一带一路”的战略意义 + + + + +  让我们看看在美国受人追捧的运动,第一是篮球、第二是拳击。拳击这项运动典型地反映出了美国人崇尚实力的风格,直来直去,重拳出击,最好KO(击倒获胜)对手,一切都很明确;而中国人则相反,喜欢模糊,以柔克刚,我也不追求KO你,但我要把你所有的动作都化解掉。中国人喜欢打太极,而太极确实是一门比拳击更高的艺术。 + + +  “一带一路”就反映了这种思路。历史上所有的大国在崛起过程中,都有围绕它的崛起展开的全球化运动。这意味着全球化不是一个从历史到今天一以贯之的过程,而是各有各的全球化。罗马帝国有罗马帝国的全球化,大秦帝国有大秦帝国的全球化。每一次全球化都是被每一个崛起的帝国推动的;每一个帝国都有与它相关的一段全球化,在它的上升期到它的鼎盛期,全球化达到一个巅峰。而这个全球化同时会被它自身的力量所限制,这就是它的能力所能达到的最大范围和它的交通工具所能到达的最远点,那也就是它全球化的终点。所以,无论是古罗马全球化,还是大秦帝国的全球化,今天看来,都只能算是一种帝国扩张的区域化过程。真正的近现代史上的全球化,是从大英国帝国开始的,大英帝国的全球化是贸易的全球化。美国秉承了大英帝国的衣钵之后延续了一段贸易全球化,而真正具有美国特色的全球化,是美元的全球化。这也是我们今天正经历的全球化。但我不同意说中国今天的“一带一路”,是和全球经济一体化接轨,那等于说是要继续和美元的全球化接轨,这样的理解是不对的。作为一个崛起中的大国,“一带一路”是中国全球化的初始阶段,也就中国的全球化。作为一个大国,在崛起过程中必须推进环绕你展开的全球化。 + + +  “一带一路”应该说是中国迄今为止能提出的最好的大国战略。因为它是跟美国战略东移的一次对冲。有些人会对此提出疑问,对冲应该是相向而行,你还能有背向而行的对冲吗?对了,“一带一路”就是中国对美国东移战略的一次背向对冲,我拿背朝向你。你不是压过来了吗?我往西走,既不是避让你,也不是畏惧你,而是非常巧妙地化解你由东向我压来的这种压力。 + +“一带一路”并非两线并行战略,而应有主次之分。鉴于海上力量至今是中国的短板,“一带一路”首先应该选择从陆上完成,也就是说“一路”应该是辅攻方向,而“一带”应该成为主攻方向。“一带”成为主攻方向,意味着我们必须重新认识陆军的作用。有人说中国陆军天下无敌,这话放在在中国的国土范围内说,没错,中国陆军所向无敌,谁也别想再踏上中国的领土来打大规模的仗,问题是中国陆军有远征能力吗? + + +  我在去年年底《环球时报》的年会上谈到这样一个问题,我说美国人选中国作对手,打压中国,是选错了对手、选错了方向。因为未来真正对美国构成挑战的根本不是中国,是美国自己,美国将自己埋葬自己。因为它没有意识到,一个大时代正在到来,这个时代将会把它所代表的金融资本主义推到最高阶段之际,让美国从巅峰跌落,因为一方面,美国通过虚拟经济,已经把资本主义的红利吃尽了。另一方面,美国又通过它引以为傲的领先全球的科技创新、把互联网、大数据、云计算推到了极致,而这些工具最终将成为埋葬以美国为代表的金融资本主义的最主要的推手。 + + + + + +  阿里巴巴在去年“双11”这天,其淘宝网、天猫网的网购销售额一天达到507亿人民币,而在相隔不久的感恩节三天的假期里,美国网上销售和地面上的商场销售总额才相当于407亿人民币,不及阿里巴巴一家。而中国还没有算上网易、腾讯、京东,更没有算其他商场的营业额。这意味着一个新的时代已经悄悄到来,而美国人面对这个时代仍然迟钝。阿里巴巴的交易,全是用支付宝的方式完成的,支付宝意味什么?意味着货币已经退出交易舞台,而美国人的霸权是建立在美元基础上的。美元是什么?美元是货币。未来当我们越来越多的不再使用货币结算的时候,传统意义上的货币就将成为无用的东西。当货币成为无用的东西时,建立在货币之上的帝国还会存在吗?这才是美国人要考虑的问题。 + + +  3D打印机同样也代表了一个未来方向,将使人类社会今天的生产方式发生根本性变化。由于生产方式在改变,交易方式在改变,世界就必然要发生根本性变化,而历史证明,真正能导致社会性质发生变化的原因,就是因为这两者的致变,而不是其他因素。中国从秦末秦二世时期,开始有人造反,陈胜吴广揭竿而起,到辛亥革命,2000多年的历史上有发生过多少次起义、造反、战争、革命?解决问题吗?不解决问题,一直是改朝换代,一直是低水平循环。因为这些来复式运动改变不了农耕社会的本质,既没有改变生产方式又没有改变交易方式,所以只能一直改朝换代。西方也是如此,拿破仑携法国大革命的雄风,带领一支崭新的被大革命洗礼过的军队横扫欧洲,把一顶顶王冠扫落在地,但等到滑铁卢一仗失败,拿破仑下台,欧洲的帝王们一个个复辟,立刻重回封建社会。直到英国的蒸汽机来了,工业革命来了,使人类的产能大大的提升,大量剩余产品出现,有了剩余产品才会有剩余价值,然后才会有资本,然后才会有资本家,然后资本主义社会就到来了。 + + +  那么今天当资本有可能随着货币的消失而消失,当生产的方式也将随着3D打印机的出现而改变时,人类即将跨入一个新的社会门槛,这时的中国和美国站在同一条起跑线上,都站在互联网、大数据和云计算的起跑线上。那么这时我们要比的就是谁先迈入这个时代,而不是谁把谁打压下去。我就是从这个意义上讲,美国选错了对手。美国真正的对手是它自己,是这个时代。而美国人恰恰在这一点上,显示出惊人的迟钝。因为它太渴望保住自己的霸权地位,而从未想过与别的国家分享权力,共同迈过新社会时代那道今天对我们来说,还有很多未知领域和不确定性的门槛。 \ No newline at end of file diff --git "a/_posts/25-\344\272\272\346\260\221\345\270\201\344\275\234\344\270\272\347\237\263\346\262\271\345\207\272\345\217\243\347\232\204\347\273\223\347\256\227\350\264\247\345\270\201\346\204\217\345\221\263\347\235\200\344\273\200\344\271\210\357\274\237.md" "b/_posts/25-\344\272\272\346\260\221\345\270\201\344\275\234\344\270\272\347\237\263\346\262\271\345\207\272\345\217\243\347\232\204\347\273\223\347\256\227\350\264\247\345\270\201\346\204\217\345\221\263\347\235\200\344\273\200\344\271\210\357\274\237.md" new file mode 100644 index 0000000..fc2fd0f --- /dev/null +++ "b/_posts/25-\344\272\272\346\260\221\345\270\201\344\275\234\344\270\272\347\237\263\346\262\271\345\207\272\345\217\243\347\232\204\347\273\223\347\256\227\350\264\247\345\270\201\346\204\217\345\221\263\347\235\200\344\273\200\344\271\210\357\274\237.md" @@ -0,0 +1,141 @@ +--- +title: 人民币作为石油出口的结算货币意味着什么? +date: 2019-10-28 +updated: 2019-10-28 +issueid: 25 +tags: +- 杂七杂八 +--- +可能有的人不太理解,这件事是个啥?那么就由我来给大家总结一下。 + +这其实是一个巨大的历史性事件,因为它意味着咱们的人民币正式打响了针对美元的第一枪! + +为了清楚明白为什么这么说,就要先了解一下美元霸权是怎么回事,还有美元有多厉害! + +有人说过:美国在世界上的霸权地位靠的是三样东西:美元、石油、和航母 + + +大家都知道世界上最厉害的国家是美国,第二就是咱中国。而国家发展都需要一个核心资源——石油 + +很多人可能认为石油只是开车时候用的燃油,其实上石油的作用远远不止如此 + +我们穿衣服用的涤纶、腈纶、锦纶、给衣服染色的染料、平时用的塑料袋、农作物用的化肥农药、铺路用的沥青、生活中用的橡胶制品等等!全都是直接或间接由石油产生的! + +可以说石油是现在地球上最重要的战略资源,而美国,虽然不是石油出口大国,但它想方设法想要“控制”石油! + + +我们都知道在二战之后,美元曾经通过和黄金挂钩达成了著名的“布雷顿森林体系”然而,到了1971年美国经济实力下降,布雷顿森林体系难以维持! + +于是,在1971年,眼看美元作为通用货币的地位摇摇欲坠眼之时,为了保持美元的地位必须让美元同一件全村人都得用的东西挂钩起来,于是美国人就想到了——石油 + + +美国人找到了产油户沙特,多次商讨之下跟沙特签了个协议:美国承诺对沙特提供军火保护,作为交换沙特在卖石油的时候!只能用美元交易美元就这样同石油绑定了! + +之后,因为沙特是石油卖得最多的,他一带头,其他卖石油的比如伊拉克啊、伊朗、利比亚啊慢慢也就跟着沙特一起都用美元买卖石油了! + +就这样本来要扔掉美元的国家,现在要建设,建设却离不开石油,但是因为石油只能用美元买卖,咱手里又没有美元,怎么办? + + +只能跑到美利坚那里,先用货和美国人换美元,然后再用换来的美元去买石油! + +全世界人民都要用石油,所以大家都得买美元!紧接着大家为了随时都有石油买,就出现在手里囤一些美元以防不时之需情况! + +但是这些囤着的美元买石油不一定用的完,会有剩余!就有国家想着:这些美元留着也是留着,不如拿出来用!于是在和其他国家买卖其他货物的时候也干脆通通用美元交易! + +于是,美元就顺理成章维持住了通用货币的地位,全村人都接受美元买东西有多少要多少!其他国家发行的货币就不行了! + + +就这样美元成为全村通用货币,其他国家如果想买石油,必须拿货跟美国换美元!再用美元买石油,对于国家来说相当于用货换了石油回来用 + +但是对于美国来说呢?美元不过是一张纸钞啊!而且这张纸印多少、啥时候印完全是他一个人说了算啊!也就是说他只付出了一张印了数字的绿纸就能换实打实的货物回来! + + +而且,因为美元太多了有些人拿着没处花!就问美国,美元还能干啥啊?这时候,美国跳出来告诉其他国家:拿美元买我的国债啊!我用我美利坚的信誉担保 + +钱一定会还你的!我地球头号强人扛把子还能欠钱不还吗? + +于是,其他国家就都拿这些美元跑去买美国的国债了! + + +国债是什么?你可以理解为美国打的借条用来跟其他国家借钱,而借到了钱之后,美国再用借来的钱,去向其他国家买东西!也就是说,他全程没有付出什么! + +那等到要还钱的时候没钱怎么办呢?美国就用新债来还旧债!今天中国的钱到期了!我就跟俄罗斯借一些钱先还上,至于俄罗斯的钱怎么还?不着急到期了再想办法! + +这就相当于美国是一个整天刷爆信用卡的卡奴用东家的卡补西家的债。但是呢,因为他国力强盛大家就是愿意去买他的国债,等大家一回头发现:哎呀,美国竟然已经欠我这么多钱了!所以说:美国其实就是全世界最大的老!赖! + + +就这样,因为美元村里通用货币的地位,美利坚一边欠着巨额债务,一边大笔支出,而辛辛苦苦累死累活干活生产的国家们只拿到了一手白条! + +这么负债下来美国欠了多少钱呢?根据《华尔街日报》的调查全村所有的企业、家庭、政府欠的钱加一块儿是223.3万亿美元,美国欠了60万亿美元,占26.86% + + +(各产油国持有的美国国债) + +可以说,美国就是个恶霸,天天都吃霸王餐!有人说欠钱了咋了?拖得过初一,拖不过十五他不早晚要还的嘛!难道欠钱的还能成了大爷?没错,对于美国来说欠钱的就是大爷,为什么呢? + +因为大家都用他家的钱但是印钞机是他家的,这钱他想印多少就印多少,到他还钱的时候他手上没钱还了,他就耍赖开动印钞机印个300的钞票还你!乍一看之下好像没问题,但是,某国里本来只有2万美元,美国耍赖多印了300,美元总数多了,美元就贬值了! + + +其他国家拿到的虽然还是300美元,但是他再拿这300去买东西,可能就买200的东西而已!钱就这样缩水了! + +就这样,美国白白赖了账其他国家还一点办法都没有!不过美国也知道这样下去,是要遭公愤的要是其他国家不高兴了!哪天突然集体要我还钱,那我不是要扑街? + +于是,他就用向村民借来的钱造了一堆军火把自己房子打造得铁桶一样,航母整天开来开去,美国的航母越多就越没有村民敢要他还钱! + +这就是为什么美国人不像其他国家一样辛苦干活但是美国人的海军却比世界全部国家加起来还要强大,因为美国这些航母等于是所有村民一起给他打工造出来的! + + +所以,对于美国来说形成了军事力量维护美元霸权,美元霸权又反哺军事让美国军事力量更强大的循环之后美国的生活就变得非常简单——躺在印钞机上吃喝拉撒就行了! + +美国不需要像其他国家一样进行辛苦的生产劳动开店挣钱,要汽车,印钞!要粮食,印钞!要手机,印钞!还白条,印钞!没有什么是印个一万张钞票搞不定的!如果有,就再印它一万张! + + +这一切正如美国前国务卿基辛格所说的那样:如果你控制了石油,你就控制住了所有世界经济!如果你控制了货币,你就控制住了整个世界! + +最过分的是美国整天花天酒地账目出现问题钱不够用了他还会拉全世界下水 + +让世界人民给他买单!08年,因为美国爆发了次贷危机,面对这个烂摊子美国全速开动疯狂印钞,整个过程中美国印了近4万亿张钞票!全世界的美元一下子堆成了山美元贬值得厉害!他的借条就更不值钱了其他国家拿着一把美元都买不了一件货了! + + +就这样,美国通过疯狂印钞把自己花天酒地犯下的错转移给了全世界其他国家!结果,次贷危机结束后罪魁祸首美国毫发无损其他村民却元气大伤咱们中国是村子里最能吃苦也最大的店手里美国的借条最多也因此被美国人坑得最惨! + +幸好咱中国人吃苦耐劳还喜欢存钱虽然亏了一大笔钱但是还维持的住,而欧洲和日本要么破产就剩下了一个壳子了 + +其实,其他国家都知道这样被美国控制货币是不行的咱天天累死累活做生意 + + +就这小子啥都不做天天印钞就行了!但是,美国家里的军火太多了!航母还老是晃悠,只要有其他国家敢于破坏这个体系美国就会拿枪拿炮打杖! + +比如,2000年9月26日伊拉克总统萨达姆对美国人老在自己店里赊账不爽了他宣布老子不用美元买卖石油了你们拿着欧洲联盟用的欧元也可以来我这里买石油这一下美国就不高兴了! + +于是,美国找了个伊拉克在研究化学武器的借口发动伊拉克战争把伊拉克揍了个七荤八素萨达姆被打死了导致现在伊拉克成了现在这个样子! + + +利比亚的卡扎菲也是想让石油摆脱美元的控制结果被美国挑动利比亚战争 + +被手底下一个反对他的恐怖分子消灭了! + +而这一回中国的原油期货正式挂牌交易了用人民币结算啥意思? + +就是说——这次,轮到我们挑战美国了 + +咱中国攒了很久的钱,家底非常厚实库房满满当当都是金子!咱手里家伙也好、咱的航母也开始转悠了 + +于是,咱们觉得时机成熟了宣布我们中国开了一个原油市场大家都来买卖啊咱这里可以不用美元用我家的人民币买卖就行还可以转换成黄金啊! + + +其他国家吃够了美元的苦早就后悔上了美国的当了尤其是沙特满手的美国借条 + +却根本不知道美国啥时候能还钱 + +现在,沙特的石油不是只能卖给美国换一手借条了而是可以兑换成可靠的黄金 + +小沙特非常开心!于是,其他已经有一部分人开始存人民币了!其他国家手里的人民币拿得多了美元自然就少了,美国赖以控制世界的筹码就被我们抽走了这,就是在挑战美元的统治地位! + +这就是中国开放原油期货的意义! + +> 转载自 +> 如何看待中国宣布任何接受人民币作为石油结算的石油出口国,都可以在上海黄金交易所将石油兑换成黄金? +> 乌鸦校尉的回答 - 知乎 +https://www.zhihu.com/question/66043558/answer/375816896 +> 题目有修改 diff --git "a/_posts/26-\344\270\255\345\233\275\344\270\272\344\273\200\344\271\210\350\246\201\350\277\233\350\241\214\350\265\204\346\234\254\347\256\241\345\210\266\357\274\237.md" "b/_posts/26-\344\270\255\345\233\275\344\270\272\344\273\200\344\271\210\350\246\201\350\277\233\350\241\214\350\265\204\346\234\254\347\256\241\345\210\266\357\274\237.md" new file mode 100644 index 0000000..bc79628 --- /dev/null +++ "b/_posts/26-\344\270\255\345\233\275\344\270\272\344\273\200\344\271\210\350\246\201\350\277\233\350\241\214\350\265\204\346\234\254\347\256\241\345\210\266\357\274\237.md" @@ -0,0 +1,72 @@ +--- +title: 中国为什么要进行资本管制? +date: 2019-10-29 +updated: 2019-10-29 +issueid: 26 +tags: +- 杂七杂八 +--- +美国和日本指责中国政府干预外汇自由,要求中国实行浮动汇率,放开资本管制。而中国则坚定或者委婉地予以拒绝。要说清楚这个问题,得首先从黄金谈起。大家知道,人类最初的货币是黄金和白银。黄金和白银由于不易变质,产量又不是很多,易于切割,成为了所有国家都接受的财富符号。当时欧洲殖民主义者对黄金产生狂热的迷恋,他们寻找新大陆的重要目的就是寻找黄金。后来的不少文献在谈到这段历史时,大都指责资本主义赤裸裸的掠夺财富,进行资本原始积累的罪恶本性。事实上,如果把当时的欧洲看作是相对孤立的整体的话,也就是说,假如掠夺来的黄金主要只在欧洲内部使用,那么这些黄金对于欧洲来说,谈不上什么原始资本积累,而只是相当于货币扩张而已。简单地说,欧洲黄金的增多,就跟今天的多印钞票一样,轻度的黄金扩张可以刺激经济,而过分的黄金扩张只会让经济发疯。 + +当然,黄金总量如果不增长,就跟今天的中央银行停止发行钞票一样,经济也会因为缺乏足够的货币而停滞萧条。黄金就其质地来说,非常适合作为货币。但是它并不是具备货币的所有要求。这体现在它的供应上。黄金的供应是自然决定的,有时候多有时候少。而国家经济的发展往往需要稳定的货币供应。特别是世界经济发展到一定总量时,黄金就再也跟不上经济的增长,而成为经济的制约因素。 + +在货币的进化过程中,开始出现纸钞。最开始的纸钞就是借条。银庄(相当于今天的私人银行)发出纸钞,拿着纸钞的人可以在银庄的各个分庄换取银子或者黄金。由于发出去的钞票随时都可能换成黄金或者银子,所以各个银庄必须在有等量的黄金或者银子时,才敢发钞票。这就是大家常常听说的:发放钞票要以黄金外汇等储备为基础。当然,这种说法反映的是当时的现实。有些学经济的还用这句话来解释当今的货币政策,就是胡扯了。 + +在银庄发放钞票的过程中,发现自己可以比黄金或者银子拥有量更多的发钞票??因为钞票不可能立刻要兑换成黄金。特别是对于那些大银庄,他们的钞票因为信誉好,甚至可以直接像黄金白银那样来买东西,人们也就不在乎是不是一定要兑换成黄金白银了。这样,银庄发出的钞票远远超过了其黄金白银拥有量。这就是信誉货币的始端。它意味着,货币可以凭空的印刷出来。但是无论过去还是现在,这种扩张都是通过信贷完成的。因此不少经济学者坚持:即便货币可以凭空创造,但是也只能以信贷的方式进行扩张。这种说法完全没有理论依据,我可以预言,如果说货币财政政策要进行改革,那么首先将会在货币的发行方式上对传统理论的突破。 + +由于银庄发放钞票可以超过它实际拥有的财富,只要不发生挤兑,它就实质上是占有了这些多余的财富。这就类似于现代国家发行钞票的货币税。同时钞票由私人发行,也不利于国家对经济的宏观调控。所以在货币的演化过程中,国家开始集中发钞权。国家发行钞票,最初也是以黄金作为本位,也就是国家要拥有多少黄金才发多少钞票,钞票可以自由兑换黄金。但是后来随着经济的发展,货币总量需求增大,黄金开始紧缺,国家不得不在黄金储量之外发行过量的钞票。此时金本位崩溃。再后来黄金成为国际间的重要支付手段,国家感觉有必要自己进行掌握,所以国家就开始终止钞票和黄金的自由兑换,而把有限的黄金用于国家控制。这样就进入彻底的信用货币时代,黄金作为一国内的支付手段退出流通领域。国家发行钞票完全根据自己的需要,并获取发行钞票的巨大利润。因为他们付出的是纸,印出的却是钱。 + +基于同样的原理,国际外汇市场也走在同样的演化路径上。 + +二战过后,各国把黄金交放到美国央行,美国按照与黄金的一定比例投放美元。各国都接受美元为世界通用的货币。这个过程相对是比较公平的,因为美国要严格按照其黄金储备发放美元,而且各国凭借美元可以自由提取美国的黄金,因此不存在剥削他国财富之说。但是很快,由于世界经济的迅速发展,不要说美国的黄金储备,就是世界的黄金总产量都跟不上经济的发展,美国要满足各国把美元作为外汇储备以及世界货币的需要,不得不开始超出黄金储量来印刷美元。在谈到这段历史时,要纠正一个错误,就是美国大量印刷美元,来解决它的外汇逆差,并不一定是因为美国经济的衰退造成的。即便美国的经济不衰退,世界经济的发展也将促使它在黄金储备以外发行美元。特别是,美国自身的经济发展越快,就越有多印美元的需求。如果美国的经济长期处于顺差,则美元就无法发到世界上去,也就成为不了世界货币。 + +由于美元发行的总量远远超过了美国的黄金储备,美元相对黄金就再也无法维持以前的比率,美元不得不进行贬值。此时世界掀起抛售美元,提取黄金的热潮,如果放任下去,美国央行的黄金可能被耗尽,于是美国政府宣布,停止美元与黄金的兑换,美元与黄金脱钩。这就是布雷顿森林体系的垮台。 + +布雷顿森林体系的垮台,不是由于哪个国家的经济衰退引起的,而是世界经济发展的必然历史趋势。 + +虽然布雷顿森林体系垮台,美元凭借其历史惯例和强大的经济实力,作为世界货币的地位仍然不可动摇。而且这个时候美元的霸权开始显现出来,美元开始作为美国攫取其他国家财富的重要手段而存在。 + +形象一些说,此时美国类似世界央行。美国发行的美元已经成为信用本位的货币。它可以凭空印刷美元,用于购买它国生产的实物财富。也就是说,美国可以用白纸来换取它国的原材料、以及其它实物产品。有人会说,当美国用美元购买它国产品时,其它国家也就获得了美元,其它国家可以持美元反过来购买美国产品,因此不存在说美国霸权不霸权的问题。这个问题涉及到两个方面。一个方面与目前美元贬值的问题密切相关,这个在后面我会阐述。这里我谈它的第一方面,就是美元的货币税问题。 + +虽然拥有美元的国家可以向美国购买产品,从而使得与美国的交换成为等价交换,但是一方面它是各个国家的重要外汇储备手段,随着经济的增长,储备也会逐渐增多;另一方面美元作为世界货币,它必定要在世界其他各国流通,而且世界经济发展越快,在各国流通的总量也就增长越快。因此从局部看,不断有美元流出或者流进美国,但总体上,还是以美国向外输出美元,向内输入实物财富为主。这就是大家看到为什么往往都是美国逆差的真正原因。这长期停滞在美国之外的美元,就是美国向各国收取的货币税。美国凭借货币税的特权,直接无偿掠夺别国财富。因此大家再看到美国对世界上其它国家进行援助时,应当知道它可能仅仅归还了从这些国家掠夺过来的部分财富而已。同时大家再看到美国逆差,而且美国拼命指责其他国家造成它的逆差时,千万不要轻易得出美国真的吃亏的结论,它是在贼喊捉贼,流血的说不定恰好是你自己。 + +的确,如果美国美元输出过多,这些输出的美元如果反过来购买美国的产品,无疑是美国财富的损失,其收的货币税就少了。所以,美国采取了另外一个措施:贬值。我个人相信,美元保持一定时期的稳定,然后贬值,然后再保持一定时期的稳定,然后再贬值,是美国的一项长期既定战略而不是什么突发危机使然。当其他国家都储存太多的美元储备后,例如中国这样的国家不吃不喝勒紧裤腰带的积累了3000多亿美元的外汇储备,美国就开始考虑贬值了。美国贬值货币主要通过降低利率,扩张财政支出,以及央行干预来完成。通过这些措施,使得美元供应量增大,美元贬值。美国的降低利率和扩张财政支出,对本国经济也要造成影响,譬如经济可能过热。但是美国对于国内经济,虽然我并不能断定它是有意为之还是对经济运行还不是太熟悉,总之在它执行美元贬值手段时,严格的控制了国内的需求,也就是说通过供需失衡,使得美元利率下调,美元贬值,同时美国的经济还不过热。我已经声明,不排除是美国对经济还不熟悉的原因导致其供需失衡。但是,如果换上是我,如果对经济比较了解的话,不排除不采取某些相似手段。特别是美国刚刚完成一场战争,也给贬值造成了有利条件。 + +假使美元贬值10%,则中国外汇储备硬生生损失300-350亿美元。放之世界,各国又要损失多少?美国又要掠夺多少? + +当然,美国虽然容许一定的逆差,但是逆差太大,给它带来的影响也是不利的。美国一方面通过贬值减少逆差,另一方面,特别是小布什执政,削弱福利,经济萎缩,失业增加,因此增加出口外贸需求就成为拉动经济的重点。美国出口产品多是附加值高的高科技产品,出口再多,对于美国的实物财富减少也影响很小。所以增加出口,并以出口获取的财富用来进口低附加值的实物财富,也是美国经济的重点。而美元贬值,降低了其出口价格,增加了出口。 + +以美国和中国两国而言,美元贬值,人民币升值,对于今后的进出口影响自不必说,最狠的一招还在后面。 + +美国和日本指责中国政府干预外汇市场。其要求主要有两个,一个是人民币要升值,一个是资本市场管制要放开,也就是说资本可以自由兑换。这两个要求足以对中国经济构成致命的杀伤力。 + +一种货币的贬值可以有多种手段。例如美国,是通过调节美元利率等国内经济政策手段来实现。但是这种方法运用不当的话,往往会伤人一千,自伤八百,因为这些国内经济政策不但影响外汇市场,还威胁本国国内经济。坦率的说,这也是西方经济理论本身的缺陷导致的。而中国采取的是另外一种方式,就是直接通过央行的买进卖出,来保证外汇市场人民币的汇率。同时中国还采取资本管制,资本并不能自由进出中国市场。因此中国可以在相对屏蔽本国经济的情况下对外汇市场进行干预。但无论如何,货币是否贬值,大多都是由政府政策决定的。无论美国也好还是日本也好,其货币的贬值都是政府干预的结果,而不是什么他们口口声声说的市场、自由。日本有官员说虽然利率由政府决定,但是汇率应当由市场决定的说法简直是搞笑,这是典型的话语霸权。都是干预手段,都导致货币贬值的后果,难道惟独你那种干预是市场经济,我这种干预就是政府干预?说句实话,目前西方通行的外汇体系本身就有很多缺陷,难道弥补这些缺陷,不跟你犯同样的错误就是政府干预? + +他们的真正目的,除了上面说的以外,还在于一个更加阴险的措施。 + +在他们要求人民币升值之时或者之前,美国或者日本的金融投机家开始把大量资金暗地流入中国。如果中国资本市场开放,这种流入就更加容易。资金流入中国后,兑换成人民币,等着人民币升值。人民币升值后,这些金融大鳄就开始抛出人民币,购买美元。中国央行不可能也跟着抛出人民币购买美元,如果那样的话人民币的汇率就会下跌。中国央行为了维持人民币汇率,必须购买人民币,抛出美元等外汇储备。本来中国的人民币之所以表面上看要升值,是因为中国经济失衡,需求不足,物价低迷,大量人民币游移于市场之外的结果,特别是近年来大量资金外逃,据统计外逃资金达3万亿之巨,并不是人民币真的就该升值了。 + +如果那时资本市场开放,外逃资金加上中国本土藏于地下的人民币倾巢而出,再加上金融大鳄利用金融杠杆,以一元资金调动十元的比率,来冲击人民币,可能最终有两个结果。比较乐观的结果是中国央行外汇储备足够,最终咬牙撑住了人民币汇率,但是此时由于已经大量的抛出美元,所以外汇储备也是损失殆尽,国家经济倒退几年乃至十多年。第二个结果更为恐怖,就是央行发现无法支撑,最终放任人民币汇率变化。此时人民币汇率将一路下跌,人民币越下跌,人们越是抛出人民币,越抛出人民币,人民币汇率越下跌,直到最后崩溃为止。这就是东南亚金融危机的翻版。到那个时候,中国彻底萎靡不振。 + +这是人民币升值可能带来的最严重的后果。即便中国仍然在资本管制,但目前各种投机资本仍然通过各种途径进入中国200多亿美元就是迹象。 + +那么,可不可以人民币不升值,但资本放开管制呢?如张五常主张的那样?相形而言,我宁愿人民币浮动一下汇率,同时不开放资本兑换,也不愿意固定人民币汇率下开放资本市场。因为浮动汇率,游资要来冲击人民币,至少我可以通过行政手段加强管制,而开放资本市场,在目前人民币总体上看可能反而是贬值的情况下看,如果国外金融大鳄连同地下人民币狙击中国,即便人民币不升值,也可能耗去大量外汇储备,经济遭受重大损失。 + +从另一方面,我们又不禁要问,即便美国不耍手腕来抢夺中国手中的外汇,外汇对于中国就有什么意义吗? + +长期以来,中国由于教育、社保福利等政策上的失误,以及中西部经济差距、城乡经济差距的进一步增大,致使内需严重不足。内需的不足又导致工资进一步降低,反过来又致使内需进一步下降。所以中国把经济的发展寄托于外需,希求通过外贸把产品卖出去。但是中国的产品多是低级产品,在国际市场上的竞争主要是靠廉价。而这种廉价又主要依靠中国的廉价劳动力来完成的。这是一个非常严重的问题:如果长久的压低劳动力的工资,则国内内需将没有启动的可能,中国会进一步依赖外需;而依赖外需又需要再压低劳动力的工资,以在国际市场上竞争,而压低劳动力工资又将继续压抑内需。 + +现在抽出身来回看我们的国内经济,当真是一幅奇观: + +我们不断的生产大量产品源源不断的输往国外,换回大量的外币,国内人民却无法消费。换回的大量外币又被国家通过购买美国等外国的国债等方式,流回美国等国家。这样,中国以纸币标志的GDP不断增加,但是用于国内消费的财富却不见增长甚至可能下降。又由于内需不振,外需就占了总需求的相当比重,所以人们就以为外需更加重要,进一步不惜抑制内需来扩张外需,于是国家经济进一步走向歧形。其表现形式是外汇出现大量顺差,国内需求萎靡不振。 + +一国经济,根本应依赖于物质财富的消费,此种消费是下一轮物质财富再生产的源泉,怎么能守着几张花花绿绿的废纸来富强邦国。美国常年保持外汇逆差,却成为了经济超级大国,非常重要的一点便在此。中国外贸所得外汇,不用于购买国外相应物质财富以供国内发展,别说4千亿美元储备,就算有4万亿美元又与废纸何异?可叹了枉自紧缩了国内需求,却是与他人作嫁衣裳。 + +外贸之输赢,不在你逆差还是顺差,而在你产品的附加值是否高。倘若产品的附加值低,你顺差越多,国力便越是衰退。不过此经济分析,不是此文内容,放作以后分析。目前中国央行正竭力抑制人民币升值,其采取的干预方式乃是标准的西方宏观货币政策,即买进美元,抛出人民币。为防止抛出人民币导致通货膨胀,另一方面又紧缩银根,削弱正常途径的货币投放数量,以求达到一国货币总量的稳定。这种方法非常危险,因为虽然好象总量上货币稳定,但是加强了经济的不均衡,正常的商业运作被紧缩性的货币政策限制,抛出的人民币却大量的游移于市场之外。结果中国的经济将处于这样一个两难境地:经济继续紧缩,需求继续疲惫,而大量人民币成为地上悬河,随时可能崩溃。因此正常经济一旦稍有起色,央行就不得不担惊受怕,怕这悬河垮了,造成通货膨胀,所以收紧钱口袋。而收紧钱口袋,经济便没有起色。所以两派经济学家吵架,一派说中国经济要通货膨胀,一派说中国还在通货紧缩,都有道理又都没有道理。关键怕的就是这道悬河,是悬在头顶上的一柄剑。如果没有这道悬河,中国经济究竟是紧缩还是膨胀,局势就会明朗,争论就会少得多。 + +中国经济要正常运转,必须削掉这道悬河。加强对垄断利润的税收,加强打击资金外逃,实行财产登记制度,增设遗产税。加大教育投入,加大社保医疗等公共福利设施投入。切实保障工人权利,提高我国劳动力素质,不要老是以廉价劳动力为荣。把外向型经济转为内向型经济。以上措施,可以相当于中央银行紧缩银根给投资造成的影响一样,但是却可以刺激投资的积极性和增加消费,削去悬河,因此与仅仅紧缩银根不可同日而语。对于目前的外汇储备,除了保留必要的用来稳定市场的部分外,应当用以购买战略资源或者技术,通过这种方式把美元抛出去,以绝国际社会之话柄,并同时让外汇切切实实地用来建设中国,而非像守财奴一般抱着外汇压箱底。要是中国以后被迫抛出美元之时,是通过吸纳人民币来抛出美元,中国央行还自以为是执行外汇调控政策,则中国数十年之积累,转眼便灰飞烟灭,一场空矣! + +> 作者:sophie + 链接:https://www.zhihu.com/question/21361525/answer/92678484 + 来源:知乎 + 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 \ No newline at end of file diff --git "a/_posts/27-\346\210\220\344\270\272\344\270\200\345\256\266\345\205\254\345\217\270\347\232\204\347\273\235\345\257\271\346\216\247\350\202\241\350\202\241\344\270\234\357\274\214\345\205\266\344\273\226\350\202\241\344\270\234\346\230\257\344\270\215\346\230\257\345\260\261\345\244\261\345\216\273\350\257\235\350\257\255\346\235\203\344\272\206\357\274\237.md" "b/_posts/27-\346\210\220\344\270\272\344\270\200\345\256\266\345\205\254\345\217\270\347\232\204\347\273\235\345\257\271\346\216\247\350\202\241\350\202\241\344\270\234\357\274\214\345\205\266\344\273\226\350\202\241\344\270\234\346\230\257\344\270\215\346\230\257\345\260\261\345\244\261\345\216\273\350\257\235\350\257\255\346\235\203\344\272\206\357\274\237.md" new file mode 100644 index 0000000..54bb2dc --- /dev/null +++ "b/_posts/27-\346\210\220\344\270\272\344\270\200\345\256\266\345\205\254\345\217\270\347\232\204\347\273\235\345\257\271\346\216\247\350\202\241\350\202\241\344\270\234\357\274\214\345\205\266\344\273\226\350\202\241\344\270\234\346\230\257\344\270\215\346\230\257\345\260\261\345\244\261\345\216\273\350\257\235\350\257\255\346\235\203\344\272\206\357\274\237.md" @@ -0,0 +1,72 @@ +--- +title: 成为一家公司的绝对控股股东,其他股东是不是就失去话语权了? +date: 2019-11-02 +updated: 2019-11-02 +issueid: 27 +tags: +- 杂七杂八 +- 转载 +--- +看来大家对于如何控制一家公司很感兴趣啊,那我接着再说一点,并且修正一些错误。 + +首先,一家公司最重要的是什么? + +是 CEO 吗?是董事长吗?是控股股东吗? + +都不是 + +是章程。就好像国家最重要的是宪法 + +起初所有的权利都是属于全体股东的,全体股东签订了章程,把所有的权利都授予了章程,章程规定了公司的名字住址注册资本等等,然后把权力分给股东会,董事会,并依照章程选举了董事会成员和监事会成员,董事长,监事长,并聘用总经理等高管,并授予他们一定的权力。 + +题主一定看电视剧里,只要占股超过51%,就没有人可以投票超过你,你就可以独霸整个公司了呢? + +然而并不是,章程里事无巨细地划分每一项权利,任何一项事物需要经过股东会或者董事会或者是总经理,并且规定相应的机制。 + +假如,我是公司的董事长,如果章程里规定了,更改董事长需要全体股东同意,但如果我手里恰好有那么万分之一的股份,那么你有另外99.99%的股份也是没有用的。 + +但是如果章程里又规定了,经过股东会合计占三分之二以上表决权的股东同意,可以修改章程。(原答案有错误,公司法第一百零三条 股东出席股东大会会议,所持每一股份有一表决权。但是,公司持有的本公司股份没有表决权。股东大会作出决议,必须经出席会议的股东所持表决权过半数通过。但是,股东大会作出修改公司章程、增加或者减少注册资本的决议,以及公司合并、分立、解散或者变更公司形式的决议,必须经出席会议的股东所持表决权的三分之二以上通过) + +(但是关于修改公司章程,不同国家所要求的投票权是不一样,例如美国要求的比例就是75%,具体参照当地法律) + +所以如果你占有了三分之二以上的股权你就可以修改章程,让我走人啦。 + +懂了吗?这全部最终要落实到章程是怎么设计的,权力是如何分配。(也要遵守当地法律) + +另外,在评论里大家都提到一件事就是,投票权和股权的割裂,注意:中国大陆和香港的法律都坚持同股同权,上面公司法第一百零三条就有阐述,这也是阿里巴巴没有在香港上市成功的原因(第二次上市),在美国可以同股不同权。 + +其二,公司法里也有保护小股东的条例。 + +虽然章程很重要,你也要考虑到国家法律的感受啊! + +公司法保护小股东权益具体可以从以下几点说起 + +1、 公司人格否认制 +2、 表决权代理制 +3、 累积投票制 +4、 股东诉讼制度 +5、异议股东股权收买请求权制度(退股权) +6、 股东大会召集请求权制度 +7、 股东提案权 +8、 解散公司权利的行使 +9、 表决权回避制度 +10、 股东的知情权 + +你是不是以为我将会长篇大论的说下去? + +不,你自己去查吧 + +当然,评论里也提到,这是正规地控制一家公司的做法,但是实际上在神奇的中华大地上,充满了太多不正规。 + +那么如何不正规地控制一家公司呢? + +我当然是不可能告诉你的啊,但是我可以稍微告诉你一下公司有哪些非常重要的东西,你需要保管好, + +公章,法人章,财务章(如果有其他章,也很重要的),法人亲笔签字,法人身份证,银行密码,银行u key,法人一证通,帐本 + +> 成为一家公司的绝对控股股东(刚好51%的股权),其他股东是不是就失去话语权了? - 知乎 +https://www.zhihu.com/question/66669554/answer/246486435 +有删改 + +> 腾讯公司 章程 +> https://www.tencent.com/attachments/MemorandumAndArticles.pdf \ No newline at end of file diff --git "a/_posts/28-\345\205\254\345\217\270\346\263\250\345\206\214\350\265\204\346\234\254\345\244\232\345\260\221\346\230\257\344\273\200\344\271\210\346\246\202\345\277\265\357\274\237.md" "b/_posts/28-\345\205\254\345\217\270\346\263\250\345\206\214\350\265\204\346\234\254\345\244\232\345\260\221\346\230\257\344\273\200\344\271\210\346\246\202\345\277\265\357\274\237.md" new file mode 100644 index 0000000..a768ab1 --- /dev/null +++ "b/_posts/28-\345\205\254\345\217\270\346\263\250\345\206\214\350\265\204\346\234\254\345\244\232\345\260\221\346\230\257\344\273\200\344\271\210\346\246\202\345\277\265\357\274\237.md" @@ -0,0 +1,43 @@ +--- +title: 公司注册资本多少是什么概念? +date: 2019-11-02 +updated: 2019-11-02 +issueid: 28 +tags: +- 杂七杂八 +- 转载 +--- +公司注册资本多少是什么概念 + +1. 注册资本制度大概经历了三个标志性的阶段:1)你说注册资本写100万,你一分不少拿出来证明一下你有能力拿100万;2)你不用一次性拿100万了,但是要先拿个二三十万证明一下你有能力,后续款项也必须在几年内缴清;3)你自己和一起开公司的人商量好,什么时候把100万交到公司,一开始可以一分钱不拿,也不用证明你有这个能力拿了。但是你们的约定要以书面形式写出来,并且到工商局备案。 +2. 2014年后的新《公司法》,对应的就是上述的第三个阶段。以下内容针对2014年出的《公司法》适用。 +3. 实际操作中,一开始不可能一分钱不拿出来,你还是要往公司账户里打个几万块钱用于一些开支的。不然税务法方面会有些麻烦。 +4. 你执照上写的「注册资本」的金额,就是你打算交给公司的钱,也称「认缴资本」;你实际交给公司的钱,叫做「实缴资本」。 +5. 公司以前每年要年检,现改为企业公示。公示时你要面临填写一个出资信息。其中「认缴资本」金额写你准备交给公司的钱,有多个股东,就相应的写各自准备交的金额。 +6. 认缴资本的出资日期为你们约定好的,写到《公司章程》里的那一天,不是公司成立的那一天。比如我2014年1月1日开公司,约定好2024年1月1日交给公司100万,那么认缴出资日期为2024年的1月1日。 +7. 实缴出资金额和时间按照要求发生后20天公示。但实际操作一般不写,等到出资全部到位才公示。这就是第3点说的,你写个注册资本100万,结果你每个月往公司账户上打1万,要是你每个月到公示系统写下1万的出资情况,那公示页面好看了·~ +8. 认缴资本未实际缴清,公司要注销,剩下的钱不用补交。因为第一票答案没写清楚,回答里写清楚了,所以我还是要强调下。别怕。不是注册资本写1000万,过了3个月觉得开公司没意思,又一分钱没交给公司,想注销公司的话,政府不会让你交1000万才能销掉公司的。除非你发生法人债务,也就是以公司的名义欠了钱,那就是高票答案说的处理方式了:欠999万补交999万;欠1100万补交1000万。当然,实际发生欠债情况时,根据实际情况不同处理方式不同,不会是这里说的这么简单的计算方式。 +9. 注册资本缴清后才在年末的账簿上贴印花税。所以注册资本写高了,虽然认缴,但要考虑下印花税成本,印花税就是要往税务局交一笔钱,根据你注册资本来的,写的越多会导致交的越多。税率是万分之五。 +10. 总的来说,你可以吹牛皮办了1000万注册资本的公司,但就是别欠债。不想搞了销掉,别怕。欠债另说了。 + +> 公司注册资本多少是什么概念? - 梦想家的回答 - 知乎 +https://www.zhihu.com/question/20600038/answer/46038490 + +------------------------------------------ + +注册资本对于外人来说,可以理解为公司最开始设立的时候有这么多资产。 +比如注册资本5000万元,大体上可以认为这家公司最初设立的时候账上就有5000万元的东西。 + +注册资本的大小,在一定程度上影响着商业对手的合作意愿和信任度。 +大家都更愿意和注册资本比较大的公司合作,因为这样的公司账上东西多,不可能抬腿溜了。如果是注册资本10万的企业,你放心让它做1000万的项目吗,是不是很自然的怕该公司卷款逃跑了? + +这5000万元在公司注册的时候存到公司账户里,由会计师验证,并向工商局出具验资报告。工商局确认之后,就会登记这家公司的注册资本有5000万元。 +所以理论上这笔钱最开始应该在公司账上。 + +被追问到实际情况。 +实际情况是注册资本注入之后,大股东有机会把钱再转走,留下一个空壳公司,这个我们叫做抽逃注册资本。 +但是抽逃注册资本一是有民事责任,被发现的话会要求赔偿;二是可能还涉及刑事责任。注册资本金额越高,抽逃的法律后果就越严重,股东就会更加仔细的掂量。 +从这个角度来说,注册资本总有抽逃的风险,但是注册资本金额大的,股东的法律责任更大,抽逃的风险相对会小一些。 + +> 公司注册资本多少是什么概念? - Luo Patrick的回答 - 知乎 +https://www.zhihu.com/question/20600038/answer/15671728 \ No newline at end of file diff --git "a/_posts/29-\345\217\231\345\210\251\344\272\232\346\210\230\344\272\211\346\262\211\346\200\235\345\275\225\357\274\232\350\203\214\345\220\216\347\232\204\347\276\216\344\277\204\350\203\275\346\272\220\346\240\274\345\261\200\345\244\247\350\276\203\351\207\217.md" "b/_posts/29-\345\217\231\345\210\251\344\272\232\346\210\230\344\272\211\346\262\211\346\200\235\345\275\225\357\274\232\350\203\214\345\220\216\347\232\204\347\276\216\344\277\204\350\203\275\346\272\220\346\240\274\345\261\200\345\244\247\350\276\203\351\207\217.md" new file mode 100644 index 0000000..06a6f62 --- /dev/null +++ "b/_posts/29-\345\217\231\345\210\251\344\272\232\346\210\230\344\272\211\346\262\211\346\200\235\345\275\225\357\274\232\350\203\214\345\220\216\347\232\204\347\276\216\344\277\204\350\203\275\346\272\220\346\240\274\345\261\200\345\244\247\350\276\203\351\207\217.md" @@ -0,0 +1,75 @@ +--- +tags: +- 转载 +title: 叙利亚战争沉思录:背后的美俄能源格局大较量 +date: 2019-11-17 +updated: 2019-11-19 +issueid: 29 +--- +美国企图通过出兵叙利亚,以完全控制俄油气资源出口,进而将俄排挤出国际油气市场,以达到彻底摧毁和控制俄国的目的。 + +一、美国试图控制俄油气出口欧洲的管线 + +近年来,俄国家收入70%来自石油天然气出口,其中一大半出口欧洲。控制了俄油气出口欧洲的管线管线,基本就能实现控制俄国一半的目的。 + +美国牵头建设绕开俄向欧洲输送石油的管道系统。在美国的牵头支持和直接投资下,1999年11月,阿塞拜疆、格鲁吉亚和土耳其签署了关于巴库-第比利斯-杰伊汉石油管道计划,将里海原油从阿塞拜疆经格鲁吉亚运到土耳其,里海的石油不再经过俄罗斯的管道出口欧洲。2006年,该管道正式投产,年出口量达到5000万吨,从而打破了俄对里海石油外运欧洲的垄断地位。 + +![image](https://user-images.githubusercontent.com/24750337/69002430-522a4600-092a-11ea-8a1b-0cebf3321849.png) + + + +美国力主建立的巴库-第比利斯-杰伊汉石油管道绕开了俄 + +俄出口欧洲60%左右的天然气须过境乌克兰的管网。乌克兰危机后,美国控制了乌克兰的管线,严重威胁俄油气出口欧洲,但鉴于切断该管线将极大恶化欧美关系,所以美国不敢轻易切断这些管道,但俄开始积极筹建绕开乌克兰的油气管线。俄计划到2020年,过境乌克兰的天然气减少到每年100-150亿立方米,占其总出口量的5%-8%左右。 + +![image](https://user-images.githubusercontent.com/24750337/69002437-6f5f1480-092a-11ea-9a93-b0f8590b4e2c.png) + + + +俄出口欧洲的主要天然气管线 + +“亚马尔-欧洲”天然气管道不容乐观。该管道绕开乌克兰,但但在2015年7月,波兰掌握了管道52%的股份。在波兰与俄关系持续紧张的情况下,该管道安全性不容乐观。 + +绕开乌克兰的“南溪”管线遭美国阻挠而流产。从2012年,俄开始建设从经黑海海底到保加利亚,然后通过两条支线分别通达奥地利、意大利等国家的“南溪”管线。按照规划,该管道2015年年底投入运用,年输气量达600多亿立方米,可绕道乌克兰从南部进入欧洲,由俄气负责全线建设。2014年6月8日,美国老牌参议员约翰·麦凯恩等三名美国议员与保加利亚总理举行闭门会议,会后保加利亚突然宣布暂停“南溪”管道建设。2014年6月24日,普京宣布放弃“南溪”管线。 + +![image](https://user-images.githubusercontent.com/24750337/69002442-8998f280-092a-11ea-92b8-79671897832d.png) + +夭折的“南溪”管线 + +“土耳其流”管线一波三折。“南溪”项目失败后,2014年12月1日,俄与土耳其签署了修建跨黑海通向土耳其的“土耳其流”天然气管道,并向西延伸到意大利,作为“南流”管道替代方案,该管道年供气总量可达300亿立方米,部分线路可向东南欧国家供气,原计划2018动工、2019年12月投入使用。在2015年底土耳其击落俄战机后,该项目暂停。后随俄土关系恢复,2016年8月9日,俄土恢复该项目,但建成还远需时日,而且还将受到俄土关系的影响力。 + +俄经过黑海向土耳其供气的管道项目 + +绕开乌克兰的“北溪”管线前景堪忧。2012年,俄建成通过波罗的海海底直接连接德国的“北溪”管线,由俄、德、法共同投资建成,管道从俄罗斯维堡出发,经波罗的海海底通向德国港口格赖夫斯瓦尔德,此管道终结了俄输欧天然气必须经第三国中转的历史(该项目由俄天然气工业公司(占股份51%),德国Wintershall和E.ON Ruhrgas(各15.5%),法国GDF Suez和荷兰Gasunie(各9%)共同投资建设,德国前总理施罗德担任项目股东委员会主席。)。欧盟和俄计划于2018年第二季度在“北溪”管道旁开始建设“北溪-2”号天然气管道,但这一项目因遭2017年8月美国对俄经济制裁而岌岌可危,对“北溪”项目融资的欧洲银行和企业将受到美国制裁。根据欧盟评估,美国相关制裁措施一旦生效,欧俄间仅在能源合作领域就会有8个重大项目受到波及。除了“北溪—2号”项目外,包括“北溪-1号”、“萨哈林-2号”、“土耳其流”等在内的诸多欧俄能源合作将面临制裁与停滞。 + +![image](https://user-images.githubusercontent.com/24750337/69002451-9ae1ff00-092a-11ea-9d9b-1441faf08c1a.png) + + +北流天然气管道2线项目示意图(蓝色,黄色为1线)。 + +二、美国企图控制中东油气资源和出口管线 + +美国还计划彻底控制中东油气资源和管线,将中东丰富的油气资源输送到欧洲和世界,就能控制俄油气出口市场,从而实现彻底遏制俄的目的。 + +当前美国已经控制了中东一半以上的油气资源。沙特以石油美元的形式换取美国的安全承诺,其石油完全被美国控制,石油美元为美国霸权奠定了坚实的基础。此外,美国还控制了伊拉克和卡特尔的油气资源。作为另一个中东能源大户的伊朗,其油气出口有东南西北四条通道。南部通过波斯湾向其他国家出口,但是这里有美国第五舰队把守,伊朗的油气田都在美国海军航空兵攻击范围内。北部地区可以通过与土库曼斯坦、俄罗斯等国的合作将油气资源出口到中国,但是这条管线受到驻阿富汗美军的威胁。东部地区倒是可以通过巴基斯坦的中巴经济走廊出口中国新疆,但这里极端分子遍布,安全问题堪忧。伊朗能源出口唯一能够突围的地方就是西部的叙利亚了。 + +在控制中东大部分油气资源后,美国开始着手控制中东重要的油气管线,其中最重要的是什叶派管线和逊尼派管线。原有的什叶派管线是以伊拉克和叙利亚的基尔库克-巴尼亚斯原油管道为基础。该管线将伊拉克的基尔库克油田连接到叙利亚港口城市巴尼亚斯,另外基该管线在叙利亚霍姆斯地区辟出了一条支线,一直穿越黎叙边界,到达黎巴嫩沿海的的黎波里。因为沿途都什叶派国家,所以又称为什叶派管线。该管道于1952年投产,长达800公里,每天运输量可达30万桶。2003年伊拉克战争爆发之后,美国迫不及待的将基尔库克巴尼亚斯管道的伊拉克段炸毁,美国发动伊拉克战争的一个重要原因就是因为萨达姆想把这条管线的石油输出结算变更为欧元。2009年,叙利亚和伊拉克两国计划修复该管线。在叙利亚内战爆发前夕,两国还签署了一项关于在现有管线再建造两条原油管道的初步协议,随后叙利亚内战全面爆发,管线又被搁置了。为阻止什叶派管线的修复,美国联合约旦、以色列规划了从伊拉克的基尔库克,经约旦、以色列出海的海法-基尔库克管线,但该管线由于种种原因,一直没有规划实施。 + +![image](https://user-images.githubusercontent.com/24750337/69002455-a9301b00-092a-11ea-85c4-2a8f7b5e916f.png) + + +途中绕开叙利亚的海法-基尔库克管线 + +2011年7月25日,伊朗、伊拉克和叙利亚签署建造什叶派管线协议,准备将伊朗的南帕尔斯油气田经过原有的基尔库克-巴尼亚斯管道到达叙利亚东部港口的石油管线,该油气田是世界上最大的油气田,为伊朗和卡塔尔共有。该管线得到了俄的大力支持,俄也将联合上述国家共同修建该管线,最重要的是该管线建成后,将抛弃石油美元,改用各国本币或人民币结算,这对美国的石油美元霸权构成直接威胁。所以协议签署后不久,叙利亚内战全面爆发(南帕尔斯气田是世界上最大的气田,位于波斯湾的伊朗和卡塔尔交界处,为两国共有。该气田占世界天然气储量的19%,达到50万亿立方米,相当于世界前20的气田储量总和。气田覆盖面积9700平方千米,伊朗占3700平方千米。)。 + +美国规划逊尼派管线。美国规划了把卡塔尔南帕尔斯油气田的石油天然气,绕过伊朗,经伊拉克、叙利亚、土耳其送往欧洲。所以,美国必须推翻巴沙尔政权,控制住叙利亚,就将这条什叶派管线改为逊尼派管线,就可以联手沙特、卡塔尔等国与俄发动石油战,压低石油价格,将俄彻底逐出国际能源市场,同时让俄在低油价中自动崩溃瓦解,打通逊尼派石油管线的巨大利益也使得逊尼派国家牢牢绑定在美国战车上,铁了心要推翻巴沙尔的原因。 + +![image](https://user-images.githubusercontent.com/24750337/69002460-bf3ddb80-092a-11ea-8128-05cc165f881a.png) + +什叶派管线和逊尼派管线 + +无论什叶派管线和逊尼派管线,都要经过中东库尔德人地区,所以在叙利亚境内接连失败后,美国当前极力控制中东库尔德人地区,仿照当年建立以色列来分裂阿拉伯人一样。在控制了库尔德地区油气资源以后,西方开始鼓动伊拉克库尔德人独立,以色列和沙特均表示支持(库尔德地区石油天然气开采权主要掌握在两个公司手里,一个是吉尼尔能源公司,它的最大投资者是金融世家后裔纳撒尼尔·罗斯柴而德。另外一个是伊拉克国际资源公司,但2017年5月12日这家伊拉克公司已被美国佳洁士投资公司全额收购私有化。)。库尔德人分布区如果完全独立出去就能够切断什叶派管线,什叶派管线从伊朗到伊拉克这段正好通过伊朗库尔德斯坦省到伊拉克库尔德人控制的苏莱曼尼亚省。向北库尔德人控制区靠近阿塞拜疆的巴库油田,直接染指里海。向东直插地中海沿岸,完全具备了取代叙利亚和土耳其四海战略位置的潜力。但支持库尔德人独立,目前而言只是个梦想而已。 + +> 叙利亚战争中的油气争夺战,作者:况腊生,国防大学博士后,本文节选自《叙利亚战争沉思录——二十一世纪的微型世界战争》,人民出版社2018年3月出版。 +> +> 转自 http://news.ifeng.com/a/20180428/57950951_0.shtml \ No newline at end of file diff --git a/_posts/3-HTTP-pipelining-multiplexing.md b/_posts/3-HTTP-pipelining-multiplexing.md new file mode 100644 index 0000000..dcd0d18 --- /dev/null +++ b/_posts/3-HTTP-pipelining-multiplexing.md @@ -0,0 +1,53 @@ +--- +issueid: 3 +tags: +title: HTTP-pipelining-multiplexing +date: 2019-09-11 +updated: 2020-10-06 +--- +![](https://webcdn.chaochaogege.net/images/20190321152954.png) + +#### HTTP/1.1 + +可以将请求一股脑发送出去,然后 `client` 等待服务器回应,如图二,但第一个请求如果被阻塞,那么后面的请求都没办法处理 +缺点: + 1. 对服务器负担很大 + 2. http request 级别的 队首阻塞 + +#### HTTP/2 + +h2 通过对请求进行分帧,一个 HTTP 请求,回应对应着 `Stream` + +逻辑上 +![image](https://user-images.githubusercontent.com/24750337/95217161-75513300-0825-11eb-83d2-6c834202697b.png) + +实际上 +![image](https://user-images.githubusercontent.com/24750337/95217278-974ab580-0825-11eb-952d-34f4ebce39a8.png) + + +通过将一个请求打散,h2 解决了 `http request` 级别的阻塞,但使用同一个 `TCP` ,全部的请求遵守同一个流量控制,只要前方有一个帧遭到了阻塞(丢失),后面的请求始终不能处理( `TCP` 不会上上层交付) + +#### Quic (Quick UDP Internet Connections) + +Google开发的使用 `UDP` 模拟 `TCP`,集成了流量控制与拥塞控制,由于UDP面向数据报,所以从根本上解决了单个 `TCP` 造成的拥塞问题 + +TCP 使用 四元组(src port,src ip, dst port, dst ip)来唯一标识连接 +一个 TCP 连接建立需要 1.5RTT, +当从wifi切换到移动网络,由于 IP 更改,导致原来的连接丢失,所以会出现短暂的掉线现象 + +而Quic 使用一个由 client 生成的8字节的id来标识 连接,即使网络环境改变,也能使用id来进行RTT为0的重连 +https://docs.google.com/document/d/1gY9-YNDNAB1eip-RTPbqphgySwSNSDHLq9D5Bty4FSU/edit + +#### HTTP/3 + +IETF(互联网工程任务组) 觉着 Quic 是个好东西,希望能用来承载别的应用层协议 + +最终分层 + +UDP -> Quic -> Application Layer + +使用 `Quic` 来承载 `HTTP` 流量 + + +参考 +> https://liudanking.com/arch/what-is-head-of-line-blocking-http2-quic/ diff --git "a/_posts/30-\345\215\227\346\265\267\351\227\256\351\242\230\350\203\214\345\220\216\347\232\204\347\273\217\346\265\216\345\215\232\345\274\210.md" "b/_posts/30-\345\215\227\346\265\267\351\227\256\351\242\230\350\203\214\345\220\216\347\232\204\347\273\217\346\265\216\345\215\232\345\274\210.md" new file mode 100644 index 0000000..69ddb7d --- /dev/null +++ "b/_posts/30-\345\215\227\346\265\267\351\227\256\351\242\230\350\203\214\345\220\216\347\232\204\347\273\217\346\265\216\345\215\232\345\274\210.md" @@ -0,0 +1,123 @@ +--- +tags: +- 转载 +title: 南海问题背后的经济博弈 +date: 2019-11-19 +updated: 2019-11-19 +issueid: 30 +--- +很长时间以来金融一直不被人待见,被认为挤占实体经济过度,殊不知金融才是武器、是战场。 + +自从去年下半年美国派出包括航空母舰在内的大量的水面舰艇在南海对中国进行挑衅以来,南海似乎终于要迎来了一个靴子的落地,虽然我不知道这个落地的靴子意味着结束,还是另一个开端。 + +已故的新加坡独裁者李光耀就曾说过:"这个世界上没有所谓的台湾问题,只有中美问题。"如果把这个结论用在南海,我想同样是适用的。南海问题,本来就是一个可大可小的问题,把它放到南海,它是一个大问题,可是当把南海问题放到中美之间,你会发现这亦不过是两个重量级对手在博弈的时候其中一方布下的一个小障碍。 + +把目光放在南海的人,永远看不清整个事情的图景。 + +如果你不能把视角拉到整个世界,那么至少要先把视野拉到整个东亚。 + + + +意外增加的外储与萨德入韩 7月8号,当韩联社及韩国《中央日报》确认美韩决定在韩国部署萨德反导系统,并预计在2017年末会投入使用的时候,很多人是被突然震惊到了。事出不常必有因,作为一个涉及到韩国国家安全甚至有可能会使韩国直接置于中国战略核打击力量下的重大决策,我不觉得会是韩美之间拍脑袋下的决定,我也不相信只是为了对付区区一个尚处于封建社会的北朝鲜。 + +在各种纷乱的解释和探讨中,我只对一个东西感兴趣,那就是动机。任何一个罪犯在实施犯罪的时候,总有一个动机,对于我来说,我觉得很多人可能忽略掉了一条非常重要的信息,而这条信息与任何政治或者军事都没有关系。这条信息是什么?是7月8号中国外汇管理局发布的一个数据,这条数据简单到只有一句话:上月中国外汇储备意外上涨130亿美元达到3.21万亿美元,创逾一年来最大涨幅。 + + + +我知道国内有太多鞋底抹了油的人无时无刻不在想着怎么把自己的财产或者帮别人把财产转移出去,那如何解释在这群见缝插针的家伙们的努力下,外汇储备反而会增加呢?中国的外汇储备增加,意味着钱肯定是从某个地方流向中国,这些流进来的外汇,来源会是哪里呢? + +世界地图无非就那么大,主要经济体不用五个手指都可以数过来,中国,美国,日本,欧洲,你们觉得答案会是哪个?我觉得很明显已经不言而喻了。 + + + +一切争端最初的起源与最终的解 在整个21世纪里,全球争夺的是什么?表面上看是资源的争夺,能源的争夺,地缘的争夺,但是实际上最重要只有一个东西:对全球资本的争夺。谁能吸引全球最大量的资本,谁就能获得发展的机遇。哪个国家能获得资本的追捧,哪个国家就可以建立起对其它国家的不对称优势。 + +所以美国很急切地需要中国的领土吗?我并不觉得。实际上,美国现在要跟全球其它地区争夺的既不是能源,也不是资源,更不是地缘,美国让日本、菲律宾对中国挑起领土之争,并不是它想要这些地区和领土,作为一个金融帝国,对它来说只有一样东西是最重要的,那就是维系整个帝国运行的血液:资本。 + +理解了这点,就不难理解为什么在中国的外储意外增加之后,萨德系统会在第二天立马宣布入韩。军事从来都不服务于军事,军事从来只服务于政治,而政治,本质上只服务于经济。不管是东北亚,还是钓鱼岛,又或者南海,在这些红灯不断亮起闪烁的热点背后,都只有一条主线,那就是对资本流动方向的驱赶和引导。 + + + +所以本质上并没有什么南海问题,只有中美对全球资本的争夺问题。 + +英国脱欧只是整个欧元区解体的一个开端,而我看不到任何人或组织能够阻挡整个欧元区在未来20年逐步滑向解体的趋势。曾经有一句老话说:"如果你在森林里遇到了熊,那么赶紧蹲下来系鞋带。因为你未必快得过熊,但你只要比身边的人更快就够了。"很明显欧洲现在就是那个躺到在血泊里被熊大快朵颐的家伙,欧洲的血流到哪里,就会给哪里带来局部的滋润。 + +在此之前,资本只有一个选择,那就是把美国当成避险的地方,但现在,明显多了一个选择。对于美国而言,这可不是什么值得庆祝的消息。而且资本不单止追求收益,更追求安全。 + +如果以后的世界变得越来越动荡,中国现在这种相对保守但对社会管控能力更为强大,可以带来一个更稳定更安全社会的国家,搞不好会越来越显示出自己的"制度性优势"。如果越来越多的资本逐步意识到这点,那么作为一个靠对外输出巨额逆差,再依靠其它国家流入大量资本购买国债来输入流动性维持整个循环的国家而言,无异于像看见了一个逐步逼近自己脖子的绞索。 + +中国的核心利益 美国海军把60%的力量部署到亚太地区,并不是为了跟中国海军来一场你死我活的灭国之战,而是通过不断的摩擦和制造冲突,来恶化中国周边的安全环境,挑起中国跟东南亚和日本甚至韩国的矛盾,从而在政治和货币上孤立中国。 + +对于中国来说,真正严峻的形势不是海洋权益被人蚕食,不是岛礁岛屿被人蚕食,而是中国的发展可能因为我们自己急于去解决这些眼前利益,而中断发展进程,这才是我们面临的真正严峻的形势。别人占一个岛一个礁并不能决定国家的生死存亡,但如果处理不当,却完全有可能让自己陷入生死存亡的危机。 + +中国的核心利益从来就不在哪个小岛,而是如何通过不断升级自己的核心产业,吸引全球资本,同时向外扩散人民币的影响力,从东北亚到东南亚甚至到中东和非洲,把美元的影响力挤出去。 + +一个投机者的觉悟 在中国有实力去扩散自己的货币之前,中国政府先要确保的是如何使自己国内的资本不会进入到一个无法遏制的外流趋势中。在今年一月份的时候我曾一度非常困惑于资本的大量外流与一线城市房价的迅猛增长,这是一个极其矛盾的现象,直到我明白抑制房价与阻挡资本外流之间的轻重,一切才如拨云散雾般清晰。 + +要控制资本外流,单靠关闭国内的出入渠道是不够的,金融的世界从来都不是拼蛮力,而是巧力。关闭渠道只是一个方面,更重要的是要为这些资本描绘出一个能带来更高收益率的地方,这也意味必须在各个局部制造出一场场小泡沫。 + +一线房产,螺纹钢,黄金,白银,股市……不管把资本引向哪个标的锚,都不能让它们涌向美元,这,是底牌。 + +美国的策略是制造出区域性冲突逼走中国的资本,而一旦中国已经证明自己能有效遏制住资本外流的趋势之后,那些被美国利用来跟中国制造冲突的国家,实质上已经处于非常危险的境地。 + +坐等菲律宾作死 如果你认同我前文说的南海问题的实质是中美围绕全球资本争夺的结论,那么接下来的另一个结论也就不难得出了。围绕资本的争夺,美国所需要的是中国周边地区的冲突,但是作为新兴的大国,中国如果不能向全球证明自己能有效确保资本在中国及中国影响范围的安全,那么对中国来说同样是不可接受的。 + + + +中国要证明自己,同时要避免跟美国之间的全面战争,那么通过打击一个美国同盟的第三方弱小国家来间接打击美国,将是中国代价和风险最小的战争策略。 + +即使到了最坏的情况,即中美之间最后不得不进行冲突,也一定不会是采取在中国或美国领土上直接进行的方式,而同样会选择相对实力较弱小的第三国作为主战场。美国东亚盟国中相对实力最强大的日本,其次是韩国,所以在短时间内这两个国家不会是中国考虑的对象,主战场同样也不会发生在台湾,因为这是中国非常不希望看到的内战模式。同时新加坡太远,并且块头太小,即使在这里打败了美国,对美国在亚洲的军事实力影响也无伤大雅,况且中国也很难有理由与美国在新加坡开战。 + +排除掉这几个选项之后,只有一个答案呼之欲出。更何况,这个国家明天向国际法庭申请的仲裁会出结果。这个仲裁本来就是被操纵的,难道还会有什么意外的结果吗?而还有什么比一个对中国不利的结果更适合当借口的? + +菲律宾争取自己的利益可以理解,但其上一任国家领导者把自己本国的利益诉求完全变成他人利益争端的工具和借口,则是我一个正常人所无法理解的。 + +我要是一个菲律宾人,此刻我要担心的,肯定不是我香蕉卖不出去的问题了。 + +延伸阅读:南海对抗中美真的会爆发战争吗? + +看问题要前后联系的看,才能发现内在的规律,在南海之争爆发前,英国退欧产生的动荡事实上帮助美联储成功加息一次。美国股市重回一万八千点,美国十年期国债收益率被同步压低,美国呈现股债双牛的格局。美国强拉金融资产价格最突出的表现,是上周五公布的非农数据,五月的数据是就业人数三点八万,创二零一二年以来最低数据,到了六月份的数据就暴增七倍到二十八万,这个就业数据不要说国际投资者,估计华尔街自己都不信。盲目的数据造假显示美国已经有点顾头不顾腚了。只要能为舆论鼓吹美国经济复苏提供足够的支撑,编造点数据又有何不可呢? + + + +那么美国为什么如此着急着制造美国经济复苏的假象呢?血饮认为,原因有两个,美债收益率扁平化趋势在强化以及美国国债偿还高峰期到来。第一个原因是短期紧迫的,债市收益率扁平化的原因,在于美国提高短期国债票面收益率,用出售的钱购买长期国债,结果是压低长期国债收益率的同时,短期国债收益率上涨,一降一升之间,导致长短期国债收益率扁平化。要改变这种趋势,最好的办法就是缩表,也就是出售美联储手里持有的美债从而获得流动性。这样才能缓解国债收益率扁平化带来的风险。 + +目前看来,这方面美国不太乐观。高盛等投行据此认为美国经济衰退将会在十二个月以后发生。十二个月以后就是明年这个时候了,这同时也是美国在零八年金融危机期间发行的十年期国债集中到期的日子。所以,美国的核心问题就是没钱维持整个金融市场的高位运行!这是美国所有反常行为背后的根本原因。美国在南海强硬的同时,其实脖子下面的神经都快崩断了。 + +美国南海施压中国所要达到的目的有两层含义,最好的结果是逼迫中国像零八年那样开启四万亿,同时大量购买美国国债和股票,为美国金融市场接盘。再不济,用中美冲突加剧逼迫中国境内资本出逃也是可以接受的。目前网络上很多鼓励换美元的,大致都是被美国收买造势的。如果说零八年中国购买美债和美股是有美国国家主权信誉做保障的话,那么量化宽松后,将美国纳税人未来几十年收入都已经抵押出去的美国,已经完全没有了合格抵押品能让中国信服。美国使用武力逼迫中国也不会达到自己想要的目的。 + + + +具体的操作步骤上,英国退欧导致大西洋两岸资本向美国汇集,此时美国在南海围堵中国逼迫中国接受南海仲裁,无疑则是乘胜追击。在推高美元资产价格的同时,将金融炮口对准中国,如果能用军事施压逼迫中国妥协或者是摩擦对抗中中方失利,那么东亚地区出逃的资本将会恐慌性出逃,避险美元资产以后,就会大幅吸收美国股债市的泡沫。美国高企的一万八千点股市和被急速压低的债市,就会成为吸收中国三十年改革开放成果的黑洞。在出逃的过程中,国内抛售人民币资产避险美元的行为将会是观察的重要窗口。所以血饮还是建议各位不要跟风挤兑美元,金融防火墙的破裂会比长江大堤决口带来的危害更大。 + +看过金庸武侠剧的人都知道吸星大法这门武功,现在美国的做法就类似于这个,用地缘冲突扣紧中国的脉门,在打穿穴道以后将中国的财富全部抽离,这是一种非常恶毒的做法。配合金融攻击的军事和地缘施压美国也几乎把能动用的手段全部使出来了。从韩国部署萨德,到安倍修宪,到台独,到航母进南海,这次施压的力度和强度是二十年来最大的。所有的政治军事行为归根到底是为国家的经济利益服务的。美国加紧部署萨德系统,和中国南海扩岛都可以看做是中美争夺日韩的外在表现。部署萨德同时,与日本的标准三、爱国者和海基远程预警雷达组网,是美国从军事上绑死日韩的具体动作。中国志在完全控制南海水域,则是着眼于掐断日韩海上贸易通道。部署萨德系统美国说不针对中俄,中国南海扩岛说不阻碍航行自由,中美的说法本质上都是自说自话。中国将部署的东风三十一前出到东北地区,美国将四艘航母开进南海,是相互威慑对方。 + +不同的是,美国更多的借助地区的盟友发力,而中国则要在家门口对敌。以中国领海线作为楚河汉界,中美全面排兵布阵。这场冲突要远远大于二零一四年美俄航母在地中海的那次对峙。中国在家门口作战已经无路可退,美国出动四艘航母威慑中国,也将会是奥巴马任内最后一次军事冒险。中国人在国家安全遭受威胁的时候从来就没有退缩过,美国用一个南海仲裁就想压中国接受,可能美国人还以为中国是一百二十年的清政府。在很多人关注的问题中,萨德系统的危害被反复提及,中国外交部也明确表达了反对的态度。朴槿惠在四月份韩国国会选举中失利,代表内部制约韩国右翼的力量被削弱,萨德的部署客观上让韩国在经济和安全上双重得利,在签署了中韩自贸区协定后韩国即翻脸,这是对中国朝鲜半岛政策的最大讽刺。 + +长期以来,中国在东亚的政策可以说是自相矛盾的,对韩日主要以经济利诱为主,妄图以经济利益的关联来构建中日韩经济的一体化。这个政策成果最高的时候是二零零八年到二零零九年,韩国总统卢武铉和日本首相均为亲华派。中韩和中日自贸区谈判以及货币互换等加速推进,如果继续下去的话,中日韩确实有希望一体化,但是卢武铉被逼自杀和鸠山被美国拿下后,东亚局势完全逆转。其实从那个时候看,中国的这条道路就已经走到头了,但是中国的政策却没有改弦易张,反而在这条道路上继续滑行。在这个过程中,中国不断以配合美日制裁朝鲜换取西方的让步。 + +美日则得寸进尺,不仅日本修宪没有停止而且韩国的萨德部署也即将成为现实。美国是朝鲜半岛局势动荡的始作俑者这句话,今年才由王毅说出来。对美国这样的无赖国家,不论是在哪条战线中国都应该以斗争为主。单纯以经济一体化推动韩日脱离美国控制,这只是中国的一厢情愿,本身就忽视了韩日是美国殖民地的现实。在发展经济的时候要有能力保护国家安全,这个原则什么时候都不能放弃。经过萨德事件,中国应该全面反思和扭转半岛外交政策,以斗争求团结,而不是为为了表面团结放弃斗争。 + +那么韩国部署萨德系统以后,中国该如何反击呢?东亚地区有两个国家和地区是完全经受不住中国制裁的,他们分别是韩国和台湾。二者同时也是对华贸易顺差最大的两个,而且经济结构都是出口型的,对中国市场极度依赖。韩国部署萨德后,中国可以对支持部署该系统的韩国公司和个人进行严厉制裁。昨天负责投资风险事务的亚洲基础设施投资银行副总裁洪起泽在进入长期休假以后,该职位级别被强降至局长级。即,韩国事实上丢失了向亚投行缴纳四点三万亿韩元后取得的副总裁职位,这是中国对韩国进行点名式警告。 + +公司方面,华为已经发动对三星的知识产权侵权诉讼,未来韩国部署萨德以后,中国还可以追加制裁韩国企业和个人。对于韩国的做法,只有一个大大的"蠢"字能够形容,索罗斯已经准备做空韩元,金融上韩国吃美国的亏已经很多了,在未来的金融大对决中,人民币是韩国唯一的避风港。韩元和人民币已经能直接交易,韩国出口型的经济和中国内需市场结合,将会为韩国带来光明的前景,萨德的部署将会使韩国之前的努力功亏一篑,效果类似钓鱼岛争端打断中日货币直接兑换一样。因为中韩有货币直接交易,犹太华尔街可以通过做空韩元间接做空人民币,从而达到从中国楼市解套的目的,这是一个非常隐蔽的攻击。 + +危害已经产生,那么我们该如何应对呢?韩国的萨德系统对中国华北东北国防安全构成威胁,我们可以借助朝鲜之手对萨德的雷达系统进行干扰。萨德系统要想最大范围的探测中国腹地,其部署位置必然要靠近三八线,与朝鲜几乎是面对面。那么中国有干扰萨德系统的技术吗?答案是有,且已经在实际应用中取得很好的效果。二零一四年,台湾部署了从美国进口的探测距离三千公里的铺路爪远程预警系统,这种雷达被台湾吹嘘为"能够发现3000公里上飞行的高尔夫球"但是该雷达在投入使用一年以后成为瞎子。原因在于大陆在与之隔海相望的福建惠安部署了针对"铺路爪"进行干扰的一部大型相控阵雷达,两者距离仅240公里。萨德系统使用的X波段雷达因为靠近三八线,所以由中方提供设备对其进行干扰是完全可以的。战术上,中国可以在辽东和山东半岛部署东风二十一导弹直接瞄准萨德系统。 + +当然我们都知道这一切的幕后黑手是美国。对付美国,血饮认为可采取的措施很多,金融上果断抛售美国国债。去年八月份中国单月抛售美债一千亿美元,这对美国债市施加巨大的影响。其次中国还可以果断的抛售美国上市公司股票,到今年四到五月份中国累计减持一千多亿美元美股,在美国股市冲过一万八千点的时候抛售也是极其合适的。在南海,中国可以顺势推动南海防空识别区的建立,同时将中程预警雷达以及反舰导弹等武器装备部署于南海岛礁,构建完整的防御体系。 + +也可以模仿俄罗斯向美国夏威夷海域发射洲际弹道导弹的做法。同时加强与朝鲜和俄罗斯的战略协同,可以事实上解除对于朝鲜施加的制裁,加强与俄罗斯在反导防空领域的合作。同时为了反击犹太资本集团,中国可以加大对叙利亚的支持,同时启动将伊朗纳入上海合作组织的进程。在美以找到新代理人之前打通伊朗至叙利亚的陆地走廊。这会严重威慑以色列,警告可萨犹太集团在对付中国方面悠着点。 + +有人会问中美激烈碰撞会发生战争吗?答案是不会!首先,打仗需要钱,特别是现代战争更是耗费金钱无数。美国和中国打仗他有钱吗?有钱的话,何必动用航母来勒索中国呢?美国打伊拉克战争直接耗费两万亿美元,间接负债七万亿美元。和军事实力位居世界第二,战争动员能力世界第一的中国开战,美国有把握赢吗?美国准备为此花费多少金钱呢?二十万亿美元肯定不够。把现在债台高筑的美国卖了值几个二十万亿?更何况在中国家门口打仗,南海是中国核潜艇唯一一个安全的水下发射阵地,中国必然死守。 + +如果打成持久战,美国的国内经济危机还能压制住吗?中美开战美国本土必然无法幸免,到时候危机传导美国的股市和债市,他们还能保证高位运行吗?不得不说,这次美国这次的总体战发动的有点匆忙,上来就出动四艘航母玩王炸,本身就有上来就梭哈的架势。南海仲裁后,中美在南海爆发战争的可能性几乎为零。战争无法发动但是可以有激烈的摩擦,狭路相逢最终比拼的就是国家意志,这方面血饮对解放军有十二分的信心。反过来看美军,他们的战斗意志只能用战五渣来形容,去年美俄黑海摩擦,当俄罗斯的苏二四俯冲略过美舰,美国士兵的心理险要崩溃。今年初伊朗扣留美国士兵,美国大兵跪地投降的一幕更是让美军颜面扫地。从朝鲜战争到现在美国的纸老虎本质从未改变,但是解放军却已经武装到牙齿。 + +既然打不起来,那么美国会不会对中国进行金融制裁呢?目前网络上很多人都有这个担心,其实美国攻击别的一向是先经济制裁后军事的,这次美国跳过金融制裁的环节直接军事施压,这正好说明美国不敢制裁中国。在美国国内,有很多人主张对华完全赖账,电视节目中也有很多跳梁对此大为赞同并沾沾自喜。实际上,美国不仅不敢制裁中国,还要保护中国在美投资。账面上,中国虽然持有一点四五万亿美元资产,但是美国在华投资金额更大。零七年到零八年通过地下钱庄和虚假贸易流入中国的热钱数以万亿美元。那个时期金融界的热门词汇就是热钱,而零八年到一二年之间美国量化宽松出来的钱也在大量流入中国境内。粗略统计,这部分钱加上十年来积累的利润保守估计总额在五万亿美元左右。这里说下外国投资在中国市场产生的留存利润问题。零五年汇改到现在,热钱投资中国房地产、地方政府基建项目产生了巨大的利润,这部分利润一直没有汇出中国境内,而是以人民币的形势进行了再投资。长达十年的利润累计他们每年的获利净值已经远远超过每年的外商对华直接投资总额。所以美国在华投资的规模是非常巨大的,其规模远超我们的认知。 + +假设美国对中国进行金融制裁,完全抹平中国持仓美债的话,对等的中国会对美资进行金融管制甚至是直接没收,这里面谁的损失更大一目了然。目前中国市场是美日企业利润的主要来源地。美国对华制裁也等于是彻底关闭了中国市场,去其他地方投资哪有中国市场获利巨大呢?其次从技术上说,如果中美翻脸的话,中国可以通过英国和比利时卢森堡的账户在欧洲债券市场上迅速抛售美债,欧洲市场的债券交易量数以万亿计,中国的美债持仓完全足够市场吸纳。 + +所以对于中美南海之争,中国依旧可以稳坐钓鱼台有条不紊的应对。至于中美两军发生摩擦,这是大国博弈中的正常碰撞。对比美俄自冷战到现在的各种摩擦,中国更要习惯这种摩擦会成为中美对抗中的常态。南海之争是中国崛起为世界强权的成人礼,不远的将来这样的摩擦还会出现在夏威夷、出现在加勒比海或者是美国西海岸。对于南海争端不必过于惊慌,一百年以后南海还在那里,美国在哪里就说不准了。中美鏖战南海,我相信中国会最终取得胜利,在吸血中国无果的情况下,通过输入通缩对中国进行软杀伤就成为美国的不二选择。 + +> 转自 https://zhuanlan.zhihu.com/p/21783705 +> +> 题目有修改 diff --git "a/_posts/31-\344\273\200\344\271\210\346\230\257\346\234\237\350\264\247\344\272\244\346\230\223\357\274\237.md" "b/_posts/31-\344\273\200\344\271\210\346\230\257\346\234\237\350\264\247\344\272\244\346\230\223\357\274\237.md" new file mode 100644 index 0000000..9f3daf3 --- /dev/null +++ "b/_posts/31-\344\273\200\344\271\210\346\230\257\346\234\237\350\264\247\344\272\244\346\230\223\357\274\237.md" @@ -0,0 +1,68 @@ +--- +title: 什么是期货交易? +date: 2019-11-23 +updated: 2019-11-23 +issueid: 31 +tags: +- 转载 +--- +什么是期货交易,期货和股票有什么不同,和炒黄金、外汇乃至邮票、文玩、比特币比起来,哪个更容易赚钱?刚刚接触期货概念的投资者往往会有一堆问题,作为量巢量化学院第一篇,本文首先给大家解释一下期货交易是怎么一回事。 + +一、期货交易的雏形--远期交易 +大家在生活中最常见的交易方式是"一手交钱、一手交货",例如去楼下水果店买苹果,5元一斤,你给老板10元钱,老板递给你两斤苹果,当场钱货两讫,交易完成,我们称之为现货交易。类似的,小明去Apple Store买iPhone X,给店员9688元钱,同时拿到一部苹果手机,这也是现货交易。 + +接下来我们把场景做一点变化,假设18年秋天苹果会发布新一代手机iPhone XI,小明作为发烧友想要第一时间拿到,他找到黄牛A说,"2018年10月1日,我从你这儿以12000元买1台iPhone XI,256G版本。"。这个口头协议约定了在未来某一天将要发生的交易细节(时间、价格、数量、标的、规格),我们称之为远期交易。 +![image](https://user-images.githubusercontent.com/24750337/69477228-ab104780-0e1e-11ea-9f34-1aefdc421841.png) + +知道了什么是远期交易,就离期货交易只有一步之遥了,不过我们先在这停顿,想一想远期交易的意义是什么,这种交易需求是如何产生的,以及人们可以用这种交易方式达到什么目的。在上面的例子中,小明在3月份与黄牛A达成了一致,之后半年便可以不再操心这件事,10月1日肯定能买到手机;而黄牛A在3月份与小明握手之后,也确定了10月1日一定能卖出手机。等真正到了10月,Tim Cook发布新一代iPhone手机后,可能加入了非常划时代的新功能,引发抢购热潮,造成供不应求,黑市上价格炒到2万,则小明高高兴兴地按照规定价格12000元从黄牛A那里买来后者通宵排队七天拿到的手机;另一种可能则是富士康大量招工,提高生产效率,供货充足,则黄牛轻轻松松以不到万元的价格从Apple官方店购入,出门转手加价至12000给小明接盘。 + +回到苹果的例子,果汁生产商在三月份做年度计划时,知道自己今年需要采购苹果作为生产原料,但是如果现在立刻购买所需的全部苹果,则需要付出资金,操心仓储,并且到下半年生产时苹果也不够新鲜,更为合理的采购时机应该是等秋天苹果丰收时进行,但由于无法预测那个时候的苹果价格,故而没法准确地为其果汁产品进行定价,定价高了导致滞销,定价低了会造成亏损。因此,厂家找到果农甲,签订远期交易合同,约定好将在10月份以600元一吨的价格买入100吨苹果,并且依据这个原材料成本来安排年度的生产计划,产品定价乃至市场营销策略,使得整个商业流程能可控的执行。另一方面,从果农甲的角度来看,他很难预测今年是风调雨顺还是会发生自然灾害,也无法判断市场的供求关系,苹果到九月份是畅销还是滞销,价格是高涨还是低落,而在三月份就提前签订合同售出10月份的苹果,帮他确定了这笔交易,保证了6万元的收入。 + +等真正到了10月,如果苹果市场价格达到700元一吨,则果汁生产商由于之前明智的决定而降低了自己的成本价格;若苹果价格回落至500元一吨,则果农甲能够以高于市场价格100元的水平达成交易,获得更多利润。同时我们也应该认识到,果汁生产商虽然每吨损失了100元,但其在三月份的所有生产销售和市场行为都是围绕600元的材料成本进行规划与执行的,并非突然蒙受损失而被打个措手不及。 + +小结一下,远期交易这种方式,让买卖双方能够提前确立日后的一笔交易,同时也锁定了交易的价格,明确了成本或收入,消弭了未来一段时间由价格波动所带来的不确定性,降低了价格可能向不利方向变化所带来的未知风险,同时也让渡了价格可能向有利方向变化所带来的潜在收益。 + +二、远期交易的难点 +之前的例子之中,还存在一个漏洞,如何保证交易双方都能诚实守信的按照事先的约定完成交易,即使该交易对自己是不利的?在苹果的例子中,假设10月份价格回落到500元,如何保证厂商仍旧以600元从果农甲手中购买,而不是翻脸不认账,自行在市场上以500元的价格交易?有的读者可能想到了让厂商在三月份就每吨交出100元预付款,但如果价格最终不是500而是400,即使提前交了100*100计一万元押金,果汁厂商仍然有充分的理由放弃这笔押金,执意毁约,以比事先合约价便宜200的水平从市场购买。并且,缴纳押金只对厂商做出了一定程度的约束,当市价高于600元时,果农甲作为买方很可能毁约--甚至揣着果汁厂商预付的押金携款而逃。 + +因此,一般的远期交易,双方都会在合同执行方式上加入各种约束机制,缴纳预付款、质押品,引入第三方金融机构、法务机构,增大双方的违约成本,想方设法保证交易能够执行。厂商要找果农甲、果农乙、果农丙逐一商讨交货日期、价格、方式以及各项保证交易实施的条款方案,最终确定合适的供货商;果农甲也面临着与厂商A、B、C进行谈判,挑选自己认为合理的远期交易合同条款细节,饶是如此,最终仍然免不了出现违约,双方对簿公堂或者一拍两散的情况发生,即使交易成功,到19年继续签订新合同时,这些条款又需要重新逐项商议和调整,达成交易所需的人力和经济成本都颇高。 +![image](https://user-images.githubusercontent.com/24750337/69477236-b82d3680-0e1e-11ea-85d0-1f348f5de51f.png) + +三、期货交易 +远期交易这种交易形式需求旺盛,意义明显,于是人们在长期的贸易实践中逐步总结出了可行的实施方式,为交易频繁、供需双方参与数量足够大的交易标的物,设定了规范交易流程和交易场所,买卖双方不用再找到对方进行冗长的谈判,商议签订复杂的、定制化的合同条款,而是直接使用被大家认可的标准合约进行交易。 + +中国的期货交易所共有四个,即: + +上海期货交易所,主要交易品种是铜铝铅锌等金属原材料,也有天然油、橡胶、沥青等化工品 +郑州商品交易所,主要交易品种是小麦棉花白糖等农产品,之前提到的苹果也在17年正式成为郑商所支持的交易品种。PS: 上文提到的iPhone并不是真正的期货品种…… +大连商品交易所,主要交易品种有玉米大豆鸡蛋等农产品,也有聚乙烯、焦炭、焦煤等化工品 +中国金融期货交易所,主要交易品种为沪深300股指和5年国债期货等金融衍生品 + +期货交易所作为国家批准设立并监管运行的交易场所,制定了各个具体期货交易品种的合约规范、交易费率和交易方式。下面仍以买卖苹果为例,详细介绍交易流程,其中保证金的收取和盈亏的结算机制很好的保障了双方持有期货合约所应该履行的义务及享受的权利: + +0). 假设果汁厂商进行期货交易的账户初始资金为10000元。 + +1). 3月1日,果汁厂商看到10月交货的苹果期货合约(代码:AP810,AP是apple的缩写,810表示18年10月份交货)价格为6000元/手(一手是10吨),于是下单买入10手该合约;另一方面,有一个对手(例如果农甲)选择了以6000元/手的价格卖出10手该合约。交易所会撮合这两个价格匹配,一买一卖的报价,产生成交,并冻结双方各7%的保证金,即双方为这笔交易各付出了6000*10*7%,计4200元,作为约束他们执行交易的保证金,通俗的说就是押金。 + +2). 3月2日,假设AP810的价格上涨至7000元/手(实际交易有涨跌停限制,一天的价格波动不会这么大),交易所在日终结算时,会计算出买入苹果合约的果汁厂商每手合约相较昨天的6000元/手,获利1000元,10手共计10000元,并结算至其账户;相应的,果农甲则由于每手亏损1000元,而被扣除共10000元。同时,根据新的结算价7000元/手,冻结资金应为4900元(真实交易里冻结资金是在盘中实时计算的,而非盘后结算),此时果汁厂商的可用资金为15100元。 + +3). 3月3日,假设AP810的价格下跌至5500元/手,交易所在日终结算时,会判定果汁厂商所持有的合约相较于昨天的7000元/手,亏损1500元,10手共计扣除15000元,剩余5000元,并且冻结资金被调整至5500*10*7%,即3850元,可用资金所剩不多,只有1150元。这时期货经纪公司便会通知果汁厂商,要求其追加资金,否则将会强制卖出其持有的10手合约。顺便说一句,这种要求合约持有者追加资金以保证自己有足够余额作为保证金(Margin)的通知一般是以打电话形式执行的,凯文·史派西在2011年主演的影片《Margin Call》便展现了金融交易的紧张刺激。 + +4). 3月4日,果汁厂商依照要求追加转入10000元资金,而AP810继续下跌至5000元/手,日终结算时,每手较昨天又亏损500元,共计5000元,总资金变动至10000元,其中冻结部分3500元,可用资金6500元。 + +![image](https://user-images.githubusercontent.com/24750337/69477242-c4b18f00-0e1e-11ea-9d60-6c846c6ac0c5.png) + +由上表可见,随着AP810合约价格的变动,期货交易所每天都会重新计算买方和卖方的账户余额,赚钱和亏钱当天就反应在账户数字之中,而不像股票那样等到卖出时才进行计算,这种结算方式就叫做每日无负债结算制度。由于价格波动引起的盈亏及时地反应在双方的账户余额中,因此双方没有了违约的动机。每天结算时,如果当天价格高于昨天,对于果汁厂商而言,日终时交易所已经把价差带来的盈利结算至账户,果农由价差导致的亏损也被交易所扣除。 + +读者可以直接理解为由于价格上涨,交易所仲裁果农支付给了果汁厂商这笔价差,这样一来,即使果农违不肯交出或者交不出苹果,果汁厂商已经拿到了价差,自己去市场上买也不会蒙受损失。反之,若当天价格低于昨天,则可以认为交易所会让果汁厂商支付价差给果农,如是,即使果汁厂商反悔不买了,果农已经拿到价差,手上的苹果买给市场其它买家也不吃亏。 + +因此双方所持有的合约,虽然价格一直在发生波动,但是交易所在其中调节计算,始终保持着两者在3月1号达成一致的6000元/手这一约定所应有的盈亏,而无法通过违约行为获利。随着时间的流逝,双方期货投资账户的余额依照着苹果期货合约价格的变动而变化着,持有该期货合约至10月份的交货日(标准术语叫交割日),交易所将完成最终的实物交割,果农将苹果存入交易所指定仓库,厂商按当日交割价格付款,并获得相应数量的苹果仓库提货单,交易完成。 + +四、总结 +相信通过前面数个例子的解释,大家对于相关概念有了一个大致的认识。这里再贴一段教科书般的定义,以求对期货合约这一概念有更加完备和精准的了解:"远期合约和期货合约都是交易双方约定在未来某一特定时间、以某一特定价格、买卖某一特定数量和质量资产的交易形式。期货合约是期货交易所制定的标准化合约,对合约到期日及其买卖的资产的种类、数量、质量作出了统一规定。” + +因此,对于交易所支持的标准化期货合约品种,交易参与者可以预先确定在未来某一时刻希望达成的交易,从而提前锁定交易价格,降低今后价格波动可能带来的风险。而期货交易所制定的交易规则,如合约定义、保证金结算制度、交割流程等,保证了这种远期交易能够以较小的成本达成。 + +> 什么是期货 (Futures)? - 肖洋的回答 - 知乎 +> +> https://www.zhihu.com/question/23512883/answer/335943570 diff --git "a/_posts/32-ABI\357\274\210Application Binary Interface\357\274\211\346\230\257\344\273\200\344\271\210\357\274\237.md" "b/_posts/32-ABI\357\274\210Application Binary Interface\357\274\211\346\230\257\344\273\200\344\271\210\357\274\237.md" new file mode 100644 index 0000000..7c50b11 --- /dev/null +++ "b/_posts/32-ABI\357\274\210Application Binary Interface\357\274\211\346\230\257\344\273\200\344\271\210\357\274\237.md" @@ -0,0 +1,20 @@ +--- +title: ABI(Application Binary Interface)是什么? +date: 2020-02-28 +updated: 2020-02-28 +issueid: 32 +tags: +- 基础 +--- +ABI是一组决定调用转换(calling convention),放置结构的规则。 + +Pascal利用栈,按照与C语言相反的顺序传递参数,所以Pascal和C不能编译成相同相同的ABI。 + +**比如,对于函数调用参数传递,C语言参数从右向左压栈,而Pascal与其相反** + +C和Pascal编译器各自的标准也隐藏着却是如此(最终编译出来的ABI不兼容) + +C++编译器并不能定义“标准的”方式来管理变量,因为C++并没有定义这个标准。C++的变量管理在Windows上的编译器与GNU编译器之间并不兼容,所以这两个编译器产生的代码并不是ABI兼容的。 + +> https://stackoverflow.com/questions/2171177/what-is-an-application-binary-interface-abi +> *看英文能明白是什么意思,结果翻译过来那个味就变了 :)* \ No newline at end of file diff --git "a/_posts/33-Ubuntu\345\246\202\344\275\225\345\274\200\345\220\257\350\207\252\346\214\202\345\234\250 Vmware shared folder?.md" "b/_posts/33-Ubuntu\345\246\202\344\275\225\345\274\200\345\220\257\350\207\252\346\214\202\345\234\250 Vmware shared folder?.md" new file mode 100644 index 0000000..3eb50a5 --- /dev/null +++ "b/_posts/33-Ubuntu\345\246\202\344\275\225\345\274\200\345\220\257\350\207\252\346\214\202\345\234\250 Vmware shared folder?.md" @@ -0,0 +1,17 @@ +--- +title: Ubuntu如何开启自挂在 Vmware shared folder? +date: 2020-03-03 +updated: 2020-03-03 +issueid: 33 +tags: +- 杂七杂八 +--- +我想要将Windows整个D盘挂载到 Ubuntu 的 `/root/d` 下,使用 `vmhgfs` 一直提示我 `Error: cannot canonicalize mount point: No such file or directory`,查了好久,之后发现 `vmhgfs`已经 `out of date`,对于我 Ubuntu 18.04版本应该使用 `vmhgfs-fuse` + +下面这个回答解决了问题 + +https://askubuntu.com/a/1051620/956906 + +另外参考 + +https://github.com/vmware/open-vm-tools/issues/248 \ No newline at end of file diff --git "a/_posts/35-Nginx\351\205\215\347\275\256Oneindex.md" "b/_posts/35-Nginx\351\205\215\347\275\256Oneindex.md" new file mode 100644 index 0000000..d2cfb27 --- /dev/null +++ "b/_posts/35-Nginx\351\205\215\347\275\256Oneindex.md" @@ -0,0 +1,46 @@ +--- +title: Nginx配置Oneindex +date: 2020-03-12 +updated: 2020-03-12 +issueid: 35 +tags: +--- +每一个 `server` 都是一个虚拟主机,通过 `http` 的 `host` 字段区分不同的目录 + +这个 `host` 字段对应着`nginx`的`server_name` + +`oneindex`需要 `php-fpm` 来处理请求,`php-fpm` 默认使用`unix socket`,需要在 `/etc/php/7.0/fpm/pool.d/`下的配置文件中添加 + +``` +listen = 127.0.0.1:9000 +listen = /run/php/php7.0-fpm.sock +``` +使得 `php-fpm` 监听 `9000` 端口 + +nginx根目录为 `/var/www/html`,我将 `oneindex` 放在了 `/var/www/html/oneindex` + +如下为配置文件 + +``` +server { + listen 80; + # 指定我使用的域名 + server_name drive.chaochaogege.com; + index index.php; + # 虚拟主机的根目录 + root /var/www/html/oneindex; + location / { + index index.html; + #Implementing PHP pseudo static + try_files $uri /index.php?$args; + } + location ~ \.php$ { + fastcgi_pass localhost:9000; + fastcgi_index index.php; + include fastcgi_params; + #document_root 与上面的root相当,指的是请求的根目录 + fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name; + # fastcgi_param QUERY_STRING $query_string; + } +} +``` \ No newline at end of file diff --git "a/_posts/36-Nginx\345\246\202\344\275\225\345\244\204\347\220\206\350\257\267\346\261\202\357\274\237--\347\277\273\350\257\221.md" "b/_posts/36-Nginx\345\246\202\344\275\225\345\244\204\347\220\206\350\257\267\346\261\202\357\274\237--\347\277\273\350\257\221.md" new file mode 100644 index 0000000..0c8eecd --- /dev/null +++ "b/_posts/36-Nginx\345\246\202\344\275\225\345\244\204\347\220\206\350\257\267\346\261\202\357\274\237--\347\277\273\350\257\221.md" @@ -0,0 +1,174 @@ +--- +issueid: 36 +tags: +- 翻译 +title: Nginx如何处理请求?--翻译 +date: 2020-03-13 +updated: 2020-03-18 +--- +> 本文翻译自: +> +> http://nginx.org/en/docs/http/request_processing.html#simple_php_site_configuratio + +### 基于域名的虚拟服务器 + +nginx首先确定使用哪个server来处理请求。让我们看下面简单的配置,这三个server都监听80端口 + + + +``` +server { + listen 80; + server_name example.org www.example.org; +... +} + +server { + listen 80; + server_name example.net www.example.net; +... +} + +server { + listen 80; + server_name example.com www.example.com; +... +} +``` + +在这个配置文件中,nginx只通过检测请求header的Host字段来决定哪个server处理请求。如果Host的值没有匹配到任何一个server_name,或者请求根本不包含Host字段,那么nginx会将请求路由到这个端口的默认server。在上面的配置文件中,nginx的default server是第一个server块。当然,默认server也可以明确被确定,只需要在listen指令中添加default_server参数。 + +``` +server { + listen 80 default_server; + server_name example.net www.example.net + ... +} +``` + +*default server参数从0.8.21版本开始提供。在早期的版本中,应该使用default参数* + +记住,default server是监听端口的,而不是server_name的属性。后面还会提到。 + +### 如果丢弃掉没有Host字段的请求? + +没有Host字段的请求是非法请求,server可以通过定义如下server来拒绝。 + +``` +server { + listen 80; + server_name ""; + return 444; +} +``` + +空server_name将会被用来匹配没有Host字段的请求,我们直接返回nginx的444代码。 + +*从0.8.48版本开始,这已经是server name的默认设置的,所以 “” 可以省略不写。早期版本中默认server name将会使用操作系统的hostname* + +### 混合使用基于域名和基于IP的虚拟主机 + +让我们看一个更复杂的配置。下面的虚拟主机监听的不同的端口。 + +``` +server { + listen 192.168.1.1:80; + server_name example.org www.example.org; +... +} + +server { + listen 192.168.1.1:80; + server_name example.net www.example.net; +... +} + +server { + listen 192.168.1.2:80; + server_name example.com www.example.com; +... +} +``` + +在这个配置文件中,nginx首先检测请求的IP地址和端口并与server块中的listen指令对比。然后将请求的Host字段与server_name指令对照。如果找不到server_name,那么请求将会由默认server处理。比如,192.168.1.1:80端口收到的www.example.com的请求将会被192.168.1.1:80端口的默认server处理,这是因为www.example.com server_name没有在这个端口定义。 + +正如早已指出的,default_server是listen port的属性,我们可以在不同的端口定义不同的default server。 + +``` +server { + listen 192.168.1.1:80; + server_name example.org www.example.org; +... +} + +server { + listen 192.168.1.1:80 default_server; + server_name example.net www.example.net; +... +} + +server { + listen 192.168.1.2:80 default_server; + server_name example.com www.example.com; +... +} +``` + +### 一个简单的PHP网站配置 + +现在让我们看下nginx如果选择location字段去处理一个典型的请求,如下是一个简易的PHP网站。 + +``` +server { + listen 80; + server_name example.org www.example.org; + root /data/www; + + location / { + index index.html index.php; + } + + location ~* \.(gif|jpg|png)$ { + expires 30d; + } + + location ~ \.php$ { + fastcgi_pass localhost:9000; + fastcgi_param SCRIPT_FILENAME + $document_root$fastcgi_script_name; + include fastcgi_params; + } +} +``` + +nginx首先搜索到最具体的location前缀而不会在意location出现的顺序。在上面的配置文件中,只有 `location /`被匹配,这是由于`/`可以匹配任何一个请求,但他会最后被使用。然后nginx按照个给出的顺序检测由正则表达式给出的`location`。第一个匹配的location会中断nginx的搜索,然后nginx会直接使用这个location,如果没有正则表达式匹配这个请求,那么nginx会使用最普适的location前缀,这里会使用刚才搜索到的`location /` + +记住location字段仅仅检测URI部分,忽略query arguments。这样是由于query string会以多种不同的方式呈现。 + +``` +/index.php?user=john&page=1 +/index.php?page=1&user=john +``` + +除此之外,任何人都能够在query string里面添加任何信息 + +``` +/index.php?page=1&something+else&user=john +``` + +现在让我们看下请求如何按照上面的配置文件处理 + +的 + +- 请求 `/logo.git`被前缀 / 首先匹配,然后被正则表达式 `\. (git|jpg|png)$`匹配。指令 `root /data/www`将 对于 `/logo.gif`映射成 `/data/www/logo.git`,于是文件发给了客户端 + +- 请求 `/index.php`也首先被 / 匹配,然后被 `\. (php)$`匹配。因此请求被之后的location处理,请求会被传递给监听在 `localhost:9000`的Fast CGI`。 fastcgi_param`指令将`SCRIPT_FILENAME`设置为 `/data/www/index.php`,然后 `FastCGI`服务器执行 `/data/www/index.php`文件。 `$document_root`相当于 `root`指令,变量 `$fastcgi_script_name`相当于请求的URI(/index.php) 。 + +- 请求 `about.html`仅仅被 / 匹配,因此它会被这个location处理,指令 `root /data/www`映射成了 `/data/www/about.html`,最后文件被发送给了客户端。 + +- 对于 / 的请求却更加复杂。它仅被 `/ location`匹配,因此它被这个location匹配。然后 `index`指令根据它的参数测试 index 文件是否存在。如果 `/data/www/index.html`文件不存在,但 `/data/www/index.php`文件存在,那么指令将会进行一次内部重定向,将请求定向到了 `/index.php`,nginx搜索再次搜索location块,这个请求就好像是由客户端发起的一样。正如我们之前看到了,被内部重定向的请求将会最终别 FastCGI服务处理。 + +译者注: + +> 其实最后一种情况就是我们所说的伪静态,将 php 文件伪装成了 html 文件,内部重定向客户端察觉不到,可使用 `rewrite`指令完成伪静态。 + diff --git "a/_posts/38-\350\213\261\350\257\255\346\200\235\347\273\264-\344\270\215\346\226\255\346\233\264\346\226\260.md" "b/_posts/38-\350\213\261\350\257\255\346\200\235\347\273\264-\344\270\215\346\226\255\346\233\264\346\226\260.md" new file mode 100644 index 0000000..fc3dea3 --- /dev/null +++ "b/_posts/38-\350\213\261\350\257\255\346\200\235\347\273\264-\344\270\215\346\226\255\346\233\264\346\226\260.md" @@ -0,0 +1,37 @@ +--- +date: 2020-04-05 +updated: 2020-04-05 +issueid: 38 +tags: +- 杂七杂八 +title: 英语思维-不断更新 +--- +关于英语思维 + +我理解英文句子的时候总是有点不对劲,但不知道具体是哪里 + +翻译英文句子的时候总是对照着中文翻译,但这样的英文读起来就是中国式英语。 + +英语中介词短语和从句应用非常广泛,往往一个句子首先出现的是主谓宾,然后通过whoi,where等从句进一步解释 + +我需要熟练上述英语的表达方式才行 + +举个例子 + + + +农产品的价格飞涨 + +第一要表达的是价格,所以 + +*The price* + +而后对 price 进一步解释 + +*The price of agricultural commodity* + +之后加上谓语和宾语 + +*The price of agricultural commodity were soaring* + +> https://www.zhihu.com/answer/951072172 \ No newline at end of file diff --git "a/_posts/39-\350\247\206\351\242\221\346\240\274\345\274\217\345\237\272\347\241\200\347\237\245\350\257\206\357\274\232\350\256\251\344\275\240\344\272\206\350\247\243MKV\343\200\201MP4\343\200\201H.265\343\200\201\347\240\201\347\216\207\343\200\201\350\211\262\346\267\261\347\255\211\347\255\211.md" "b/_posts/39-\350\247\206\351\242\221\346\240\274\345\274\217\345\237\272\347\241\200\347\237\245\350\257\206\357\274\232\350\256\251\344\275\240\344\272\206\350\247\243MKV\343\200\201MP4\343\200\201H.265\343\200\201\347\240\201\347\216\207\343\200\201\350\211\262\346\267\261\347\255\211\347\255\211.md" new file mode 100644 index 0000000..f69249e --- /dev/null +++ "b/_posts/39-\350\247\206\351\242\221\346\240\274\345\274\217\345\237\272\347\241\200\347\237\245\350\257\206\357\274\232\350\256\251\344\275\240\344\272\206\350\247\243MKV\343\200\201MP4\343\200\201H.265\343\200\201\347\240\201\347\216\207\343\200\201\350\211\262\346\267\261\347\255\211\347\255\211.md" @@ -0,0 +1,302 @@ +--- +title: 视频格式基础知识:让你了解MKV、MP4、H.265、码率、色深等等 +date: 2020-04-11 +updated: 2020-04-11 +issueid: 39 +tags: +- 转载 +--- +> 转载自 +> +> https://www.4k123.com/thread-8194-1-1.html + +1、封装格式(MP4/MKV…) vs 媒体格式(H.264/FLAC/AAC…) + +MP4+MKV是你下载的视频文件最常见的种类。这些文件其实类似一个包裹,它的后缀则是包裹的包装方式。这些包裹里面,包含了视频(只有图像),音频(只有声音),字幕等。当播放器在播放的时候,首先对这个包裹进行拆包(专业术语叫做分离/splitting),把其中的视频、音频等拿出来,再进行播放。 + + +既然它们只是一个包裹,就意味着这个后缀不能保证里面的东西是啥,也不能保证到底有多少东西。包裹里面的每一件物品,我们称之为轨道(track),一般有这么些: + +视频(Video): 一般来说肯定都有,但是也有例外,比如mka格式的外挂音轨,其实就是没视频的mkv。注意我们说到视频的时候,是不包括声音的。 + +音频(audio):一般来说也肯定有,但是有些情况是静音的,就没必要带了。 + +章节(Chapter): 蓝光原盘中自带的分段信息。如果文件带上了,那么你可以在播放器中看到带章节的效果: + +.potplayer右键画面,选项-播放-在进度条上显示书签/章节标记 + +.mpc-hc 右键画面,选项-调节-在进度条显示章节标记 + +字幕(Subtitles):有些时候文件自带字幕,并且字幕并非是直接整合于视频的硬字幕,那么就是一起被打包在封装容器中。 + +其他可能还有附件等,不一一列举。每个类型也不一定只有一条轨道,比如经常见到带多音轨的MKV。 + + +每个轨道,都有自己的格式。比如大家常说的,视频是H.264,音频是AAC,这些就是每个轨道的格式。 + +视频的格式,常见的有H.264(可以细分为8bit/10bit),H.265(当前也有8bit/10bit之分),RealVideo(常见于早期rm/rmvb),VC-1(微软主导的,常见于wmv)。基本上,H.264=AVC=AVC1, H.265=HEVC。 + +音频的格式,常见的有 FLAC/ALAC/TrueHD/DTS-HD MA这四种无损,和AAC/MP3/AC3/DTS(Core)这四种有损。 + + + + + +MKV vs MP4,主要的区别在于: + + +MKV支持封装FLAC作为音频,MP4则不支持。但是MP4也可以封装无损音轨(比如说ALAC,虽然普遍认为ALAC的效率不如FLAC优秀) +MKV支持封装ASS/SSA格式的字幕,MP4则不支持。一般字幕组制作的字幕是ASS格式,所以内封字幕多见于MKV格式 +MP4作为工业标准,在视频编辑软件和播放设备上的兼容性一般好于MKV。这也是vcb-s那些为移动设备优化的视频基本上选择MP4封装的原因。 +除此之外,这两个格式很大程度上可以互相代替。比如它们都支持封装AVC和HEVC,包括8bit/10bit的精度。所以MP4画质不如MKV好,这种论断是非常无知的——它们完全可以封装一样的视频。 + +为什么会有这样的分歧,就是历史原因了。MKV是民间研发,为了代替古老的AVI,从而更好地支持H264,它开发和修改的灵活度使得它可以兼容flac/ass这类非工业标准的格式;而MP4则是出生豪门,作为工业标准,替代更古老的MPG,作为新一代视频/音频封装服务的。 + + + + + + + + + +2、视频的基础参数:分辨率,帧率和码率。 + +视频是由连续的图像构成的。每一张图像,我们称为一帧(frame)。图像则是由像素(pixel)构成的。一张图像有多少像素,称为这个图像的分辨率。比如说1920×1080的图像,说明它是由横纵1920×1080个像素点构成。视频的分辨率就是每一帧图像的分辨率。 + + +一个视频,每一秒由多少图像构成,称为这个视频的帧率(frame-rate)。常见的帧率有24000/1001=23.976, 30000/1001=29.970, 60000/1001=59.940, 25.000, 50.000等等。这个数字是一秒钟内闪过的图像的数量。比如23.976,就是1001秒内,有24000张图像。视频的帧率是可以是恒定的(cfr, Const Frame-Rate),也可以是变化的(vfr, Variable Frame-Rate) + + +码率的定义是视频文件体积除以时间。单位一般是Kbps(Kbit/s)或者Mbps(Mbit/s)。注意1B(Byte)=8b(bit)。所以一个24分钟,900MB的视频: + +体积:900MB = 900MByte = 7200Mbit + +时间:24min = 1440s + +码率:7200/1440 = 5000 Kbps = 5Mbps + + +当视频文件的时间基本相同的时候(比如现在一集番大概是24分钟),码率和体积基本上是等价的,都是用来描述视频大小的参数。长度分辨率都相同的文件,体积不同,实际上就是码率不同。 + +码率也可以解读为单位时间内,用来记录视频的数据总量。码率越高的视频,意味着用来记录视频的数据量越多,潜在的解读就是视频可以拥有更好的质量。(注意,仅仅是潜在,后文我们会分析为什么高码率不一定等于高画质) + + +3、色深 + +色深(bit-depth),就是我们通常说的8bit和10bit,是指每个通道的精度。8bit就是每个通道用一个8bit整数(0~255)代表,10bit就是用10bit整数(0~1023)来显示。16bit则是0~65535 + +(注意,上文的表述是不严谨的,视频在编码的时候,并非一定能用到0~255的所有范围,而是可能有所保留,只用到一部分,比如16~235。这我们就不详细展开了) + + +你的显示器是8bit的,代表它能显示RGB每个通道0~255所有强度。但是视频的色深是YUV的色深,播放的时候,YUV需要通过计算转换到RGB。因此,10bit的高精度是间接的,它使得运算过程中精度增加,以让最后的颜色更细腻。 + + +如何理解8bit显示器,播放10bit是有必要的呢: + +一个圆的半径是12.33m, 求它的面积,保留两位小数。 + +半径的精度给定两位小数,结果也要求两位小数,那么圆周率精度需要给多高呢?也只要两位小数么? +取pi=3.14, 面积算出来是477.37平方米 +取pi=3.1416,面积算出来是477.61平方米 +取pi精度足够高,面积算出来是477.61平方米。所以取pi=3.1416是足够的,但是3.14就不够了。 + + +换言之,即便最终输出的精度要求较低,也不意味着参与运算的数字,以及运算过程,可以保持较低的精度。在最终输出是8bit RGB的前提下,10bit YUV比起8bit YUV依旧具有精度优势的原因就在这里。事实上,8bit YUV转换后,覆盖的精度大概相当于8bit RGB的26%,而10bit转换后的精度大约可以覆盖97%——你想让你家8bit显示器发挥97%的细腻度么?看10bit吧。 + +8bit精度不足,主要表现在亮度较低的区域,容易形成色带: + +![image](https://user-images.githubusercontent.com/24750337/79033044-a554ac00-7bdd-11ea-884e-8aa82ed4f053.png) + +注意这图右边那一圈圈跟波浪一样的效果。这就是颜色精度不足的表现。 + +10bit的优势不只在于显示精度的提高,在提高视频压缩率,减少失真方面,相对8bit也有优势。这方面就不展开了。 + +4、图像的表示方法:RGB模型 vs YUV模型 + +光的三原色是红(Red)、绿(Green)、蓝(Blue)。现代的显示器技术就是通过组合不同强度的三原色,来达成任何一种可见光的颜色。图像储存中,通过记录每个像素红绿蓝强度,来记录图像的方法,称为RGB模型 (RGB Model) + +常见的图片格式中,PNG和BMP这两种就是基于RGB模型的。 + + + +比如说原图: + +![image](https://user-images.githubusercontent.com/24750337/79033054-b69db880-7bdd-11ea-8172-84407d0bdcdd.png) +![image](https://user-images.githubusercontent.com/24750337/79033055-b9001280-7bdd-11ea-8c57-0788d3b9e1db.png) +![image](https://user-images.githubusercontent.com/24750337/79033057-bbfb0300-7bdd-11ea-9136-374a6ffad98c.png) +![image](https://user-images.githubusercontent.com/24750337/79033060-bdc4c680-7bdd-11ea-9091-8a9db70aeb00.png) + + +三个通道下,信息量和细节程度不一定是均匀分布的。比如说可以注意南小鸟脸上的红晕,在3个平面上的区分程度就不同——红色平面下几乎无从区分,造成区别的主要是绿色和蓝色的平面。外围白色的脸颊,三色都近乎饱和;但是红晕部分,只有红色饱和,绿色和蓝色不饱和。这是造成红色凸显的原因。 + + +除了RGB模型,还有一种广泛采用的模型,称为YUV模型,又被称为亮度-色度模型(Luma-Chroma)。它是通过数学转换,将RGB三个通道,转换为一个代表亮度的通道(Y,又称为Luma),和两个代表色度的通道(UV,并成为Chroma)。 + + +举个形象点的例子:一家养殖场饲养猪和牛,一种记数方式是:(猪的数量,牛的数量) + +但是也可以这么记录:(总数量=猪的数量+牛的数量,相差=猪的数量-牛的数量)。两种方法之间有数学公式可以互转。 + + +YUV模型干的是类似的事儿。通过对RGB数据的合理转换,得到另一种表示方式。YUV模型下,还有不同的实现方式。举个用的比较多的YCbCr模型:它把RGB转换成一个亮度(Y),和 蓝色色度(Cb) 以及 红色色度(Cr)。转换背后复杂的公式大家不需要了解,只需要看看效果: + + +只有亮度通道: + +![image](https://user-images.githubusercontent.com/24750337/79033065-c4ebd480-7bdd-11ea-8bc6-08f574e2d4e5.png) + + +![image](https://user-images.githubusercontent.com/24750337/79033068-c7e6c500-7bdd-11ea-800c-bb1be54b75ce.png) + + +![image](https://user-images.githubusercontent.com/24750337/79033070-ca491f00-7bdd-11ea-8f6a-73c4932ecab7.png) + + +在图像视频的加工与储存中,YUV格式一般更受欢迎,理由如下: + + +1、人眼对亮度的敏感度远高于色度,因此人眼看到的有效信息主要来自于亮度。YUV模型可以将绝大多数的有效信息分配到Y通道。UV通道相对记录的信息少的多。相对于RGB模型较为平均的分配,YUV模型将多数有效信息集中在Y通道,不但减少了冗余信息量,还为压缩提供了便利 + +2、保持了对黑白显示设备的向下兼容 + +3、图像编辑中,调节亮度和颜色饱和度,在YUV模型下更方便。 + +几乎所有的视频格式,以及广泛使用的JPEG图像格式,都是基于YCbCr模型的。播放的时候,播放器需要将YCbCr的信息,通过计算,转换为RGB。这个步骤称为渲染(Rendering) + +每个通道的记录,通常是用整数来表示。比如RGB24,就是RGB各8个bit,用0~255 (8bit的二进制数范围)来表示某个颜色的强弱。YUV模型也不例外,也是用整数来表示每个通道的高低。 + + +5、色度半采样 + +在YUV模型的应用中,Y和UV的重要性是不等同的。图像视频的实际储存和传输中,通常将Y以全分辨率记录,UV以减半甚至1/4的分辨率记录。这个手段被称为色度半采样(Chroma Sub-Sampling)。色度半采样可以有效减少传输带宽,和加大UV平面的压缩率,但是不可避免的会损失UV平面的有效信息。 + + +我们平常的视频,最常见的是420采样。配合YUV格式,常常被写作yuv420。这种采样是Y保留全部,UV只以(1/2) x (1/2)的分辨率记录。比如说1920×1080的视频,其实只有亮度平面是1920×1080。两个色度平面都只有960×540的分辨率。 + +当然了,你也可以选择不做缩减。这种称为444采样,或者yuv444。YUV三个平面全是满分辨率。 + + +在做YUV->RGB的时候,首先需要将缩水的UV分辨率拉升到Y的分辨率(madVR中允许自定义算法,在Chroma Upscaling当中),然后再转换到RGB。做RGB->YUV的转换,也是先转换到444(YUV的分辨率相同),再将UV分辨率降低。 + + +一般能拿到的片源,包括所有蓝光原盘,都是420采样的。所以成品一般也保留420采样。所以yuv420就表示这个视频是420采样的yuv格式。 + +将420做成444格式,需要自己手动将UV分辨率拉升2×2倍。在今天madVR等渲染器可以很好地拉升UV平面的情况下,这种做法无异于毫无必要的拉升DVD做成伪高清。 + +当然了,有时候也需要在444/RGB平面下做处理和修复,常见的比如视频本身RGB平面不重叠(比如摩卡少女樱),这种修复过程首先要将UV分辨率拉升,然后转RGB,做完修复再转回YUV。修复后的结果相当于全新构图,这种情况下保留444格式就是有理由,有必要的。 + + +H264格式编码444格式,需要High 4:4:4 Predictive Profile(简称Hi444pp)。所以看到Hi444pp/yuv444 之类的标示,你就需要去找压制者的陈述,为什么他要做这么个拉升。如果找不到有效的理由,你应该默认作者是在瞎做。 + + +6、空间上的低频与高频:平面,纹理和线条 + +在视频处理中,空间(spatial)的概念指的是一帧图片以内(你可以认为就是一张图所呈现的二维空间/平面)。跟时间(temporal)相对;时间的概念就强调帧与帧之间的变换。 + + +于是我们重新来看这张亮度的图: + +![image](https://user-images.githubusercontent.com/24750337/79033073-d208c380-7bdd-11ea-81a5-d6d454e7aa3d.png) + + +亮度变化较快,变动幅度大的区域,我们称之为高频区域。否则,亮度变化缓慢且不明显的区域,我们称为低频区域。 + + +图中的蓝圈就是一块典型的低频区域,或者就叫做平面(平坦的部分)。亮度几乎没有变化 + +绿圈中,亮度呈现跳跃式的突变,这种高频区域我们称之为线条。 + +红圈中,亮度频繁变化,幅度有高有低,这种高频区域我们称为纹理。 + + +有时候,线条和纹理(高频区域)统称为线条,平面(低频区域)又叫做非线条。 + + +这是亮度平面。色度平面,高频低频,线条等概念也同样适用,就是描述色度变化的快慢轻重。一般我们所谓的“细节”,就是指图像中的高频信息。 + +一般来说,一张图的高频信息越多,意味着这张图信息量越大,所需要记录的数据量就越多,编码所需要的运算量也越大。如果一个视频包含的空间性高频信息很多(通俗点说就是每一帧内细节很多),意味着这个视频的空间复杂度很高。 + +记录一张图片,编码器需要决定给怎样的部分多少码率。码率在一张图内不同部分的分配,叫做码率的空间分配。分配较好的时候,往往整幅图目视观感比较统一;分配不好常见的后果,就是线条纹理尚可,背景平面区域出现大量色带色块(码率被过分的分配给线条);或者背景颜色过渡自然,纹理模糊,线条烂掉(码率被过分的分配给非线条)。 + + + +7、时间上的低频与高频:动态 + +在视频处理中,时间(temporal)的概念强调帧与帧之间的变换。跟空间(spatial)相对。 + +动态的概念无需多解释;就是帧与帧之间图像变化的强弱,变化频率的高低。一段视频如果动态很高,变化剧烈,我们称为时间复杂度较高,时域上的高频信息多。否则如果视频本身舒缓多静态,我们称为时间复杂度低,时域上的低频信息多。 + + +一般来说,一段视频的时域高频信息多,动态的信息量就大,所需要记录的数据量就越多,编码所需要的运算量也越大。但是另一方面,人眼对高速变化的场景,敏感度不如静态的图片来的高(你没有时间去仔细观察细节),所以动态场景的优先度可以低于静态场景。如何权衡以上两点去分配码率,被称为码率的时间分配。分配较好的时候,看视频无论动态还是静态效果都较好;分配不好的时候往往是静态部分看着还行,动态部分糊烂掉;或者动态部分效果过分的好,浪费了大量码率,造成静态部分欠码,瑕疵明显。 + + +很多人喜欢看静止的截图对比,来判断视频的画质。从观看的角度,这种做法其实并不完全科学——如果你觉得比较烂的一帧其实是取自高动态场景,那么这一帧稍微烂点无可厚非,反正观看的时候你注意不到,将码率省下来给静态部分会更好。 + + +8、清晰度与画质简述 + +我们经常讨论,一个视频清晰度如何,画质好不好。但是如何给这两个术语做定义呢? + + +经常看到的说法:“这个视频清晰度是1080p的”。其实看过上文你就应该知道,1080p只是视频的分辨率,它不能直接代表清晰度——比如说,我可以把一个480p的dvd视频拉升到1080p,那又怎样呢?它的清晰度难道就提高了么? + + +一个比较接近清晰度的概念,是上文所讲述的,空间高频信息量,就是一帧内的细节。一张图,一个视频的细节多,它的清晰度就高。分辨率决定了高频信息量的上限;就是它最清晰能到什么地步。1080p之所以比480p好,是因为它可以允许图像记录的高频信息多。这个说法看样子很靠谱,但是,有反例: + +![image](https://user-images.githubusercontent.com/24750337/79033075-d92fd180-7bdd-11ea-9f96-131d3d0779a8.png) + + +右图的高频信息远比左图多——它的线条很锐利,有大量致密的噪点(注意噪点完全符合高频信息的定义;它使得图像变化的非常快) +但是你真的觉得右图清晰度高么? +事实上,右图完全是通过左图加工而来。通过过度锐化+强噪点,人为的增加无效的高频信息。 + + + + +所以清晰度的定义我更倾向于这样一个说法:图像或视频中,原生、有效的高频信息。 +原生,强调这种清晰度是非人工添加的;有效;强调细节本身有意义,而不是毫无意义的噪点特效。 + + +值得一提的是,人为增加的高频信息不见得完全没有帮助。有的时候适度锐化的确能够起到不错的目视效果: + +![image](https://user-images.githubusercontent.com/24750337/79033079-df25b280-7bdd-11ea-9e72-5ca7f750fc64.png) + +这是一幅适度锐化后的效果。如果有人觉得右图更好,至少某些部分更好,相信我,你不是一个人。所以适度锐化依旧是视频和图像处理中,可以接受的一种主观调整的手段,一定的场合下,它确实有助于提高目视效果。 + + +以上是清晰度的概述。注意,清晰度只是空间方面(就是一帧以内)。如果再考虑到动态效果的优秀与否(视频是不是那种一动起来就糊成一团的,或者动起来感觉卡顿明显的,常见于早起RMVB),空间和时间上优秀的观看效果共同定义了画质。所以我们说madVR/svp那些倍帧效果有助于提高画质,实际上它们增强了时间上的观看效果。 + + + +好的画质,是制作者和观众共同追求的。怎么样的视频会有好的画质呢?是不是码率越高的视频画质越好呢?真不见得。视频的画质,是由以下几点共同决定的: + + +1、源的画质。 +俗话说的好,上梁不正下梁歪。如果源的画质本身很差,那么再如何折腾都别指望画质好到哪去。所以压制者往往会选择更好的源进行压制——举个栗子,BDRip一般都比TVRip来的好,哪怕是720p。蓝光也分销售地区,一般日本销售的日版,画质上比美版、台版、港版啥的都来得好,所以同样是BDRip,选取更好的源,就能做到画质上优先一步。 + + + +2、播放条件。 +观众是否用了足矣支持高画质播放的硬件和软件。这就是为啥我们在发布Rip的同时大力普及好的播放器;有时候一个好的播放器胜过多少在制作方面的精力投入。 + + + +3、码率投入vs编码复杂度。 +视频的时间和空间复杂度,并称为编码复杂度。编码复杂度高的视频,往往细节多,动态高(比如《魔法少女小圆剧场版 叛逆的物语》),这样的视频天生需要较高的码率去维持一个优秀的观看效果。 +相反,有些视频编码复杂度低(比如《请问今天要来点兔子么》,动态少,线条细节柔和),这种视频就是比较节省码率的。 + + + +4、码率分配的效率和合理度。 +同样多的码率,能起到怎样好的效果,被称为效率。比如H264就比之前的RealVideo效率高;10bit比8bit效率高;编码器先进,参数设置的比较合理,编码器各种高端参数全开(通常以编码时间作为代价),码率效率就高。 +合理度就是码率在时空分配方面合理与否,合理的分配,给观众的观看效果就比较统一协调。 码率分配的效率和合理度,是对制作者的要求,要求制作者对片源分析,参数设置有比较到位的理解。 + + + +这里再多提一句,至少在这个时间点,也就是此文发布的2014年年底,HEVC相对于AVC可以提高50%的效率,依旧是一个纸面上的理论值。实际操作中,因为HEVC编码器的成熟度远不及经过了十几年发展的AVC编码器,导致现在HEVC的潜力远没有能发挥出来,特别是高画质下甚至不如。 + + +对于目前主流的,定位收藏画质的BDRip,同样码率下x265的画质相对于x264没有优势;所以在近期,大家不用优先的去下载HEVC版作为收藏目的,更不必迷信什么“码率降低一半”。再强调一次,这个时间点;如果一年后以上陈述被不断进步的HEVC编码器推翻,我毫不惊讶。就比如目前4K就开始使用改编码方式了。 + + diff --git "a/_posts/4-Vue VNode Patch \345\210\206\346\236\220.md" "b/_posts/4-Vue VNode Patch \345\210\206\346\236\220.md" new file mode 100644 index 0000000..2d0d669 --- /dev/null +++ "b/_posts/4-Vue VNode Patch \345\210\206\346\236\220.md" @@ -0,0 +1,163 @@ +--- +date: 2019-09-11 +updated: 2019-09-11 +issueid: 4 +tags: +title: Vue VNode Patch 分析 +--- +这里记录一下Vue的 Virtual DOM 比较过程 +来自于 `cn.vuejs.org` `patchVnode` 函数断点 + +当我们对于data进行修改之后会产生新的 VDOM 集合 +这里是 vnode + +oldVnode则代表修改之前的 VDOM + +```js +function patchVnode ( + oldVnode, + vnode, + insertedVnodeQueue, + ownerArray, + index, + removeOnly + ) { + if (oldVnode === vnode) { + return + } + + if (isDef(vnode.elm) && isDef(ownerArray)) { + // clone reused vnode + vnode = ownerArray[index] = cloneVNode(vnode) + } + + const elm = vnode.elm = oldVnode.elm + + if (isTrue(oldVnode.isAsyncPlaceholder)) { + if (isDef(vnode.asyncFactory.resolved)) { + hydrate(oldVnode.elm, vnode, insertedVnodeQueue) + } else { + vnode.isAsyncPlaceholder = true + } + return + } + + // reuse element for static trees. + // note we only do this if the vnode is cloned - + // if the new node is not cloned it means the render functions have been + // reset by the hot-reload-api and we need to do a proper re-render. + if (isTrue(vnode.isStatic) && + isTrue(oldVnode.isStatic) && + vnode.key === oldVnode.key && + (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) + ) { + vnode.componentInstance = oldVnode.componentInstance + return + } + + let i + const data = vnode.data + if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { + i(oldVnode, vnode) + } + + const oldCh = oldVnode.children + const ch = vnode.children + if (isDef(data) && isPatchable(vnode)) { + for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode) + if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode) + } + if (isUndef(vnode.text)) { + if (isDef(oldCh) && isDef(ch)) { + if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) + } else if (isDef(ch)) { + if (process.env.NODE_ENV !== 'production') { + checkDuplicateKeys(ch) + } + if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '') + addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) + } else if (isDef(oldCh)) { + removeVnodes(elm, oldCh, 0, oldCh.length - 1) + } else if (isDef(oldVnode.text)) { + nodeOps.setTextContent(elm, '') + } + } else if (oldVnode.text !== vnode.text) { + nodeOps.setTextContent(elm, vnode.text) + } + if (isDef(data)) { + if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode) + } + } +``` + +Vue 会在 `updateChildren` 中比较 `VNode` +每次调用`sameVnode`,当节点相同时调用 `patchVNode` +`patchVNode`中会再次调用 `updateChildren` 进行更新 + +这是一个间接的递归 + +有个疑问 +Vue一直再调用 updateChildren + +既然是diff,应该会生成一个差异补丁 +但 updateChildren 并未有返回值,那么patch之后最终如何渲染到UI上的? + +---------------------------------------- + +Vue在模板编译完成之后会生成组件的渲染函数 + +这个渲染函数调用之后会返回VNode节点 + +```js +vnode = render.call(vm._renderProxy, vm.$createElement); +``` + + +```js +updateComponent = function () { + vm._update(vm._render(), hydrating); +}; +``` + +`vm_render()` 调用render function 获得VNode,然后进行_update,根据VNode将DOM渲染到HTML中 + +也即,`render` 获得 `VDOM`, `_update` 根据 `VDOM` 来渲染 `DOM` + +----------------------------------- + +如果没记错的话 target stack 是 Vue2 中才引入的机制,而 Vue1 中则是仅靠 Dep.target 来进行依赖收集的。根据我自己对 Vue1 和 Vue2 差异的理解,引入 target stack 的原因在于 Vue2 使用了新的视图更新方式。 + +具体来说,vue1 视图更新采用的是细粒度绑定的方式,而 vue2 采取的是 virtual DOM 的方式。举个例子来说可能比较容易理解,对于下面的模版: +``` + +
+{{ a }} + +{{ c }} +
+ + +{{ b }} +``` + +Vue1 的处理方式可以简化理解为: + +``` +watch(for a) -> directive(update {{ a }}) +watch(for b) -> directive(update {{ b }}) +watch(for c) -> directive(update {{ c }}) +``` +由于是数据到 DOM 操作操作指令的细粒度绑定,所以不论是指令还是 watcher 都是原子化的。对于上面的模版,在处理完 {{ a }} 的视图绑定后,创建新的 vue 实例 my 并且处理 {{ b }} 的视图绑定,随后继续处理 {{ c }}的绑定。 + +而在 Vue2 中情况就完全不同,视图被抽象为一个 render 函数,一个 render 函数只会生成一个 watcher,其处理机制可以简化理解为: + +``` +renderRoot () { + ... + renderMy () + ... +} +``` +可以看到在 Vue2 中组件数的结构在视图渲染时就映射为 render 函数的嵌套调用,有嵌套调用就会有调用栈。当 evaluate root 时,调用到 my 的 render 函数,此时就需要中断 root 而进行 my 的 evaluate,当 my 的 evaluate 结束后 root 将会继续进行,这就是 target stack 的意义。 + +未完待续。。 \ No newline at end of file diff --git a/_posts/41-microsoft pinyin horizontal or vertical switcher bug fix.md b/_posts/41-microsoft pinyin horizontal or vertical switcher bug fix.md new file mode 100644 index 0000000..b621a0b --- /dev/null +++ b/_posts/41-microsoft pinyin horizontal or vertical switcher bug fix.md @@ -0,0 +1,39 @@ +--- +updated: 2020-04-23 +issueid: 41 +tags: +- 杂七杂八 +title: microsoft pinyin horizontal or vertical switcher bug fix +date: 2020-04-23 +--- +如果你使用`Windows 2004H`版本的`pinyin` 输入法,那么有一定概率 `horizontal/vertical` 失效 + +如果下图 `vertical` 模式,你不管点击多少次,永远不能切换成 `horizontal` 模式 +![image](https://user-images.githubusercontent.com/24750337/80048104-cf915c80-8541-11ea-89c9-2c7dc10c907e.png) + +在网上英文中文,`windows feedback`都查了个遍,反馈Bug也没人搭理,无果,只能靠自己的知识来解决了 + +首先按照常理,`windows`下系统自带软件的配置往往存放在注册表,而非本地配置文件,我们借助 `procmon`这个软件来监控 `SystemSettings.exe`进程对于注册表的操作,打开 `Settings` 然后点到输入法界面随便进行几点操作,发现对 `InputMethod`进行的操作 + +![image](https://user-images.githubusercontent.com/24750337/80048321-74139e80-8542-11ea-9e06-e8abddd27172.png) + +欣喜若狂,打开注册表定位到具体的键值,发现Windows的输入法配置都存放在这里,根据上面英文单词的含义,我们找到了`Computer\HKEY_CURRENT_USER\SOFTWARE\Microsoft\InputMethod\Settings\CHS`文件夹, + +*我这里没有CHS是因为我通过删除注册表来进行重制,所以CHS文件夹没了* +![image](https://user-images.githubusercontent.com/24750337/80048558-1895e080-8543-11ea-9294-a64e2e460461.png) + +但删除之前我做了备份,CHS大体内容如下 + +![image](https://user-images.githubusercontent.com/24750337/80048553-16338680-8543-11ea-8a8e-b32ab02b6392.png) + +为了确保删除不出岔子,我提前对`InputMethod`文件夹导出了注册表项,做备份 + +这时候直接删除CHS,然后输入框会直接消失不见,在 `Settings`下连续开关多次 ·IME toolbar· + +![image](https://user-images.githubusercontent.com/24750337/80048660-66aae400-8543-11ea-868f-ee96fad2194b.png) + +最终成功还原为`IME horizontal` + +![image](https://user-images.githubusercontent.com/24750337/80048681-74f90000-8543-11ea-9480-7e12d66575ef.png) + +*自己动手丰衣足食,如果自己不爱鼓捣计算机,平常不练习 ProcMon 这些系统管理软件的使用,那么碰到这个问题只能抓瞎,这个问题困扰我很久了,今天实在看不习惯这个vertical布局,才下决心解决这个问题* diff --git "a/_posts/42-\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204React\346\250\241\346\235\277\344\273\245\345\217\212\344\270\200\344\272\233React\350\277\220\344\275\234\346\265\201\347\250\213\347\232\204\347\256\200\347\237\255\345\210\206\346\236\220.md" "b/_posts/42-\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204React\346\250\241\346\235\277\344\273\245\345\217\212\344\270\200\344\272\233React\350\277\220\344\275\234\346\265\201\347\250\213\347\232\204\347\256\200\347\237\255\345\210\206\346\236\220.md" new file mode 100644 index 0000000..d93c408 --- /dev/null +++ "b/_posts/42-\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204React\346\250\241\346\235\277\344\273\245\345\217\212\344\270\200\344\272\233React\350\277\220\344\275\234\346\265\201\347\250\213\347\232\204\347\256\200\347\237\255\345\210\206\346\236\220.md" @@ -0,0 +1,26 @@ +--- +issueid: 42 +tags: +title: 一个简单的React模板以及一些React运作流程的简短分析 +date: 2020-04-25 +updated: 2020-04-26 +--- +我知道你看不懂的 😂 + +

+ See the Pen + Template of React by csstry (@tryfrontend) + on CodePen. +

+ + + + +比如React,点击一个button,从触发事件到virtual dom修改都是同步进行 + + + +以及Vue + + +都是触发渲染之后diff出来Node不同之后就patch这个Node,不存在diff出来差别之后异步等到下一个周期才更新,也不存在diff完node之后统一patch,都是找出来不同就直接修改,树的深度优先遍历 \ No newline at end of file diff --git "a/_posts/44-\345\244\215\344\271\240\345\244\247\345\255\246\347\256\227\346\263\225\350\257\276.md" "b/_posts/44-\345\244\215\344\271\240\345\244\247\345\255\246\347\256\227\346\263\225\350\257\276.md" new file mode 100644 index 0000000..0289491 --- /dev/null +++ "b/_posts/44-\345\244\215\344\271\240\345\244\247\345\255\246\347\256\227\346\263\225\350\257\276.md" @@ -0,0 +1,42 @@ +--- +title: 复习大学算法课 +date: 2020-04-26 +updated: 2020-04-26 +issueid: 44 +tags: +- 算法与数据结构 +--- +挺多东西时间长了不接触都忘记了 + +`10÷6=1...4` +10是被除数,6是除数,1是商,4是余数 + +`4 == 10 % 6` + +两数相除,除数变被除数,余数变除数 + + +设余数为x + +``` + n + ----> x + m % n = + -> n + <-----------x +``` + +```js +!function() { + const log = console.log + function gcd(m,n) { + while(n > 0){ + let x = n + n = m % n + m = x + } + return m + } + log(gcd(12,16)) +}() +``` diff --git "a/_posts/45-\350\257\273Go\346\263\233\345\236\213\346\217\220\346\241\210\346\234\211\346\204\237.md" "b/_posts/45-\350\257\273Go\346\263\233\345\236\213\346\217\220\346\241\210\346\234\211\346\204\237.md" new file mode 100644 index 0000000..80006a0 --- /dev/null +++ "b/_posts/45-\350\257\273Go\346\263\233\345\236\213\346\217\220\346\241\210\346\234\211\346\204\237.md" @@ -0,0 +1,121 @@ +--- +title: 读Go泛型提案有感 +date: 2020-06-25 +updated: 2020-06-25 +issueid: 45 +tags: +- Go +- 基础 +--- +原本我以为Go添加泛型就加个type注释就可以,刚读了一遍Go generic proposal,发现要考虑的很多 + +提案里用C++类比,很久没写,不怎么熟悉,我用Java举例子 + +如下Go代码 + +``` +// This function is INVALID. +func Stringify(type T)(s []T) (ret []string) { + for _, v := range s { + ret = append(ret, v.String()) // INVALID + } + return ret +} +``` + +这份代码问题在于,v只是T类型,编译系统无法确定T类型含有String()方法,在Go中,全部的字段都会在编译时进行解析绑定,所以Go不允许上面的写法 + +这是泛型系统普遍存在的问题 + +如下Java代码 +``` +public static String[] Stringify1(T[] s) { + String[] ret = null; + for(int idx = 0 ; idx <= s.length; idx ++) { + ret[idx] = s[idx].String(); + } + return ret; +} +``` +你的String()方法根本过不了编译,无法解析String字段 +那Java是如何解决这个问题的呢?有界类型参数(Bounded Type Parameters) + +``` +class StringClass extends ArrayList { + public String String() { + return null; + } +} + +public class App { + public static > String[] Stringify(T[] s) { + String[] ret = null; + for(int idx = 0 ; idx <= s.length; idx ++) { + ret[idx] = s[idx].String(); + } + return ret; + } +} +``` + +通过extends硬性限定T的类型范围,这样调用String()时可正确解析。 +如果Go支持泛型,那必须顺带着解决 Bounded Type Parameters 的问题,标准的解决问题引入问题解决问题循环 + +Go借助现有的interface来做类型约束(type constraint) + +``` +type Stringer interface { + String() string +} +func Stringify(type T Stringer)(s []T) (ret []string) { + for _, v := range s { + ret = append(ret, v.String()) + } + return ret +} +``` + +2. 运算符约束 + +``` +由于T是不定类型,所以无法比较 +// This function is INVALID. +func Smallest(type T)(s []T) T { + r := s[0] // panic if slice is empty + for _, v := range s[1:] { + if v < r { // INVALID + r = v + } + } + return r +} +``` +那Java呢?根据我所学,Java禁止不定类型的比较,C++支持,但需要运算符重载(operator overloading) + +Java中原始类型(primitive type)都被封装到对象中,就连Integer比较都需要拆箱来提取int,更何谈直接比较Object + +Go从设计上就没有OOP,struct传递完全可以用pointer这个生值,而这些值天生就是可以被比较的,不能禁止 + +这个要说一下 + +这个代码s也是传递引用,我在这里不想讨论值传递和引用传递,明白的人自然能看明白 +``` +public void print(String s) {} +``` +也就是s也是一个数值,不是对象,不过Java在你调用s之前会解引用(*s).balabalabala + + +```go +type s struct { + +} +func (this *s) doSth(){} +func print(this *s)){ + // Go这里能调用,是由于编译器发现没有歧义,会帮助你转换成(*this).balabala调用 + this.doSth() +} +``` + +#### Go官方FAQ +> Why not use the syntax F like C++ and Java? +> When parsing code within a function, such as v := F, at the point of seeing the < it's ambiguous whether we are seeing a type instantiation or an expression using the < operator. Resolving that requires effectively unbounded lookahead. In general we strive to keep the Go parser efficient. \ No newline at end of file diff --git "a/_posts/46-\346\210\221\347\234\274\344\270\255\347\232\204Redux.md" "b/_posts/46-\346\210\221\347\234\274\344\270\255\347\232\204Redux.md" new file mode 100644 index 0000000..d09c94c --- /dev/null +++ "b/_posts/46-\346\210\221\347\234\274\344\270\255\347\232\204Redux.md" @@ -0,0 +1,16 @@ +--- +updated: 2020-08-24 +issueid: 46 +tags: +- 前端 +title: 我眼中的Redux +date: 2020-08-24 +--- +现代大型React应用必须要用一个中央数据存储便于不同组件之间共享数据 + +奈何本人水平太菜,接触Redux时扑面而来一堆概念,明白特地花了一张图搞明白它~~ 今后有机会一定持续更新🤣 + +*图中部分代码来自Redux官网 https://redux.js.org/basics/actions* + +![image](https://user-images.githubusercontent.com/24750337/91031929-d6d18f80-e633-11ea-908b-b38cd0d3ba4a.png) + diff --git "a/_posts/47-Redux-thunk\344\273\243\347\240\201\345\210\206\346\236\220.md" "b/_posts/47-Redux-thunk\344\273\243\347\240\201\345\210\206\346\236\220.md" new file mode 100644 index 0000000..ce2c8fc --- /dev/null +++ "b/_posts/47-Redux-thunk\344\273\243\347\240\201\345\210\206\346\236\220.md" @@ -0,0 +1,83 @@ +--- +title: Redux-thunk代码分析 +date: 2020-08-25 +updated: 2020-08-25 +issueid: 47 +tags: +- 前端 +--- +下面是redux的applyMiddleware代码 + +``` +export default function applyMiddleware( + ...middlewares: Middleware[] +): StoreEnhancer { + return (createStore: StoreEnhancerStoreCreator) => ( + reducer: Reducer, + preloadedState?: PreloadedState + ) => { + const store = createStore(reducer, preloadedState) + let dispatch: Dispatch = () => { + throw new Error( + 'Dispatching while constructing your middleware is not allowed. ' + + 'Other middleware would not be applied to this dispatch.' + ) + } + + const middlewareAPI: MiddlewareAPI = { + getState: store.getState, + dispatch: (action, ...args) => dispatch(action, ...args) + } + const chain = middlewares.map(middleware => middleware(middlewareAPI)) + dispatch = compose(...chain)(store.dispatch) + + return { + ...store, + dispatch + } + } +} +``` + +下面就是Thunk这个中间件的代码 + +``` +function createThunkMiddleware(extraArgument) { + return ({ dispatch, getState }) => (next) => (action) => { + if (typeof action === 'function') { + return action(dispatch, getState, extraArgument); + } + + return next(action); + }; +} + +const thunk = createThunkMiddleware(); +thunk.withExtraArgument = createThunkMiddleware; + +export default thunk; +``` + +这个中间件返回了嵌套的函数闭包,其中下面的函数就是applyMiddlewares的 `store.dispatch` + +``` +(action) => { + if (typeof action === 'function') { + return action(dispatch, getState, extraArgument); + } + + return next(action); + }; +} +``` + +回忆一下,我们 `connect` 之后的组件通过 `dispatch` 调用,传递的 + +``` +{ +type: SET_MESSAGE, +payload: 'hello' +} +``` + +就是 `action` 的参数结构 \ No newline at end of file diff --git "a/_posts/48-Actor Model \347\274\226\347\250\213\346\250\241\345\236\213\346\265\205\350\260\210.md" "b/_posts/48-Actor Model \347\274\226\347\250\213\346\250\241\345\236\213\346\265\205\350\260\210.md" new file mode 100644 index 0000000..6fb8cca --- /dev/null +++ "b/_posts/48-Actor Model \347\274\226\347\250\213\346\250\241\345\236\213\346\265\205\350\260\210.md" @@ -0,0 +1,194 @@ +--- +title: Actor Model 编程模型浅谈 +date: 2020-09-15 +updated: 2020-09-15 +issueid: 48 +tags: +- 转载 +--- +> 本文转载自 http://jiangew.me/actor-model/ + +* [Actor 模型背景](#actor-%E6%A8%A1%E5%9E%8B%E8%83%8C%E6%99%AF) +* [Actor 模型特点](#actor-%E6%A8%A1%E5%9E%8B%E7%89%B9%E7%82%B9) +* [Akka vs Netty](#akka-vs-netty) +* [Actor Model](#actor-model) + * [What: Actor Model](#what-actor-model) + * [How: Actor Model](#how-actor-model) + * [Who: Actor Model](#who-actor-model) + * [What: Akka](#what-akka) + * [Akka 实现了独特的混合模型](#akka-%E5%AE%9E%E7%8E%B0%E4%BA%86%E7%8B%AC%E7%89%B9%E7%9A%84%E6%B7%B7%E5%90%88%E6%A8%A1%E5%9E%8B) + * [What: Vert.x](#what-vertx) + * [Actor Arch](#actor-arch) + * [EventBus](#eventbus) + * [并发编程框架](#%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E6%A1%86%E6%9E%B6) + * [异步IO编程框架](#%E5%BC%82%E6%AD%A5io%E7%BC%96%E7%A8%8B%E6%A1%86%E6%9E%B6) + * [响应式编程](#%E5%93%8D%E5%BA%94%E5%BC%8F%E7%BC%96%E7%A8%8B) + * [集群](#%E9%9B%86%E7%BE%A4) + * [How: Vert.x Actor Model](#how-vertx-actor-model) + * [What: Vert.x EventBus](#what-vertx-eventbus) + * [Resources](#resources) + +Actor 模型背景 +---------- + +Actor 模型(Actor model)首先是由Carl Hewitt在1973定义, 由Erlang OTP(Open Telecom Platform)推广,其消息传递更加符合面向对象的原始意图。Actors属于并发组件模型,通过组件方式定义并发编程范式的高级阶段,避免使用者直接接触多线程并发或线程池等基础概念。 + +流行语言并发是基于多线程之间的共享内存,使用同步方法防止写争夺,Actors使用消息模型,每个Actors在同一时间处理最多一个消息,可以发送消息给其他Actors,保证了[单独写原则](https://www.jdon.com/performance/singlewriter.html)。从而巧妙避免了多线程写争夺。 + +Actor 模型特点 +---------- + +* 隔离计算实体 +* Share Nothing +* 没有任何地方同步 +* 异步消息传递 +* 不可变的消息 消息模型类似 MailBox / Queue + +AKKA框架是一个实现Actors模型的平台,灵感来自Erlang,能更轻松地开发可扩展,实现多线程安全应用。支持Java和Scala。 + +Actors是一个轻量级的对象,通过发送消息实现交互。每个Actors在同一时间处理最多一个消息,可以发送消息给其他Actors。在同一时间可以于一个Java虚拟机存在数以百万计的参与者,架构是一个父子层结构,其中父层监控子层的行为。还可以很容易地扩展Actor运行在集群中各个节点之间,无需修改代码。每个演员都可以有内部状态(字段/变量),但通信只能通过消息传递,不会有共享数据结构(计数器,队列)。 + +Akka vs Netty +------------- + +Akka是一个建立在Actors概念和可组合Futures之上的并发框架,Akka设计灵感来源于Erlang,Erlang是基于Actor模型构建的。它通常被用来取代阻塞锁如同步、读写锁及类似的更高级别的异步抽象。 + +Netty是一个异步网络库,使JAVA NIO的功能更好用。 + +Akka针对IO操作有一个抽象,这和Netty类似。使用Akka可以用来部署计算集群,Actor在不同的机器之间传递消息。从这个角度来看,Akka相对于Netty来说,是一个更高层次的抽象。 + +Actor Model +----------- + +### What: Actor Model + +每个Actor可认为一个EventLoop可简单理解为线程(实际可能多个Actor共享线程)。它有以下组件: + +* 事件信箱 +* 内部状态 + +Actor EventLoop 的状态有: + +* 等待信箱事件 +* 处理事件中 + +研发会使用异步IO编程来实现具体的Actor。即,具体Actor关心的IO读写事件也会放入Actor自己的信箱。处于【处理事件中】的事件消费程序,不会有IO引起的Block等待。 + +### How: Actor Model + +* 简化多线程编程:单线程的Actor程序,可以保证内部状态不并发访问。不再需要考虑多线程共享状态下的各种问题。 +* 天然分布式设计:Actor间基于消息的通讯,天然地支持分布式的部署,如果使用合适的Actor容器,多Actor实例的管理等,天然地实现了“微服务”架构。 +* HA:Actor容器可以用统一的健康探测消息,来测试Actor的健康情况。需要时可以重启新的Actor。 +* 其它 Event Driven Arch(EDA)的好处。 + +### Who: Actor Model + +* Erlang +* Akka +* Vert.x +* … + +### What: Akka + +Akka支持可扩展的实时事务处理。我们相信编写出正确的、具有容错性和可扩展性的并发程序太困难了。这多数是因为使用了错误的工具和错误的抽象级别。Akka就是为了改变这种状况而生的。通过使用Actor模型提升了抽象级别,为构建可扩展的、有弹性的响应式并发应用提供了一个更好的平台,详见[响应式宣言](https://www.reactivemanifesto.org/)。在容错性方面Akka采用了“let it crash”(让它崩溃)模型,该模型已经在电信行业构建出“自愈合”的应用和永不停机的系统,取得了巨大成功。Actor还为透明的分布式系统以及真正的可扩展高容错应用的基础进行了抽象。 + +一个Actor是一个容器,它包含了状态,行为,一个邮箱,子Actor和一个监管策略。所有这些封装在一个Actor引用里。最终在Actor终止时,会有这些发生。 + +Actor使你能够进行服务失败管理(监控),负载管理(缓和策略、超时和处理隔离),以及水平和垂直方向上的可扩展性(增加cpu核数和/或增加更多的机器)管理。 + +#### Akka 实现了独特的混合模型 + +Actors + +* 对并发/并行程序的简单的、高级别的抽象; +* 异步、非阻塞、高性能的事件驱动编程模型; +* 非常轻量的事件驱动处理(1G内存可容纳数百万个actors); + +容错性 + +* 使用“let-it-crash”语义的监控层次体系; +* 监控层次体系可以跨越多个JVM,从而提供真正的容错系统; +* 非常适合编写永不停机、自愈合的高容错系统; + +位置透明性 + +* Akka的所有元素都为分布式环境而设计:所有actor只通过发送消息进行交互,所有操作都是异步的。 + +持久性 + +* Actor接收到的消息可以选择性的被持久化,并在actor启动或重启的时候重放。这使得actor能够恢复其状态,即使是在JVM崩溃或正在迁移到另外节点的情况下。 + +### What: Vert.x + +有人说它是java界的Node.js。但它自称性能上,多语言上超越Node.js。他是个异步框架,Web框架,有EventBus,支持Actor模式,支持微服务管理和发现等,听起来十分牛逼的样子。 + +#### Actor Arch + +Actor 在 Vert.x 中叫 [Verticle](http://vertx.io/docs/vertx-core/java/#_verticles) + +#### EventBus + +事件总线,支持事件广播、点到点发送、点对点发送与回复 [EventBus](http://vertx.io/docs/vertx-core/java/#event_bus) + +#### 并发编程框架 + +* inline异步任务 \[Verticle中异步调用阻塞代码的类似ForkJoinPool\] +* Future的串并联使用,异步任务与结果的封装 +* 好莱坞原则的异步Callback编程 \[Hollywood principle:”Don’t call me; I’ll call you.\] + +#### 异步IO编程框架 + +![image](https://user-images.githubusercontent.com/24750337/93199210-af786900-f780-11ea-8d12-1105038e6fb7.png) + +![image](https://user-images.githubusercontent.com/24750337/93199238-bacb9480-f780-11ea-8cac-c8f507b518c1.png) + + +#### 响应式编程 + +如果你认为OOP或好莱坞原则的异步Callback编程已经OUT了,或者已经被Callback Hell坑过。那么可以试试Reactive。Vert.x支持 [RxJava](https://vertx.io/docs/vertx-rx/java/) + +#### 集群 + +跨JVM的集群EventBus,基于hazelcast来实现集群的发现和消费订阅的注册功能。[Vert.x Cluster Manager](http://vertx.io/docs/vertx-core/java/#_cluster_managers) ![Vert.x Cluster](../assets/images/post/20190116/cluster-01.png) + +### How: Vert.x Actor Model + +Actor模式在Vert.x中是这样体现的: + +* Actor –> Verticle +* 信箱 –> 一般的事件,如EventBus消息到达池。还有其它事件,如Http/Websocket/异步JDBC请求或事件池、inline异步任务(Verticle中异步调用阻塞代码的类似ForkJoinPool)的结果池。 +* EventLoop –> Actor的信箱消费线程 + +![image](https://user-images.githubusercontent.com/24750337/93199271-c4ed9300-f780-11ea-9546-220081d0c52e.png) + +![image](https://user-images.githubusercontent.com/24750337/93199296-cae37400-f780-11ea-84ca-e14bddf35f82.png) + +### What: Vert.x EventBus + +Vert.x EventBus是个普通的无中心消息框架: + +* 事件广播; +* 点到点发送; +* 点对点发送与回复\[ACK\]; +* 事件的发生源模块,事件的监听模块完全解藕; +* 事件可以支持广播、路由、点对点等方法传递。生产者不关心订阅者的数量、实现逻辑; +* 生产者与订阅者可以关注自己的Subject。生产者与订阅者间没有相互的依赖关系; +* SLA: 消息没有持久化,可能丢失。消息的投递以最大努力为原则,即不保证成功; +* 支持机器间EventBus的集群共享(可选基于hazelcast),消息和Subject可以在集群中传播; +* 在分布式部署架构下支持功能和服务热插拔,HA要求的情况下可以灵活支撑; +* 集群自治,同时支持集群成员的注册和发现、异常容错; + +Vert.x EventBus是个打通到客户端的消息框架,是个All in one工具集。由于它包含一个 EventBus SockJS Bridge 模块,浏览器可以通过它提供的JS库,使用sockjs协议(Server-Sent Events/Websocket/HTTP poll)来接入EventBus。后端的事件可以实时通知到浏览器(提供event bus javascript库),实时推浏览器的广播变得相对容易实现。想想一个长后台任务完成,或监控事件触发时,一切都变得实时响应到用户端。浏览器也可以向后端发送异步指令和请求。[EventBus Bridge](http://vertx.io/docs/vertx-web/java/#_sockjs_event_bus_bridge) + +### Resources + +* [PayPal 如何使用8个VM每天支撑数十亿个事务](https://www.jdon.com/48257) +* [PayPal 如何使用8个VM每天支撑数十亿个事务 原文](http://highscalability.com/blog/2016/8/15/how-paypal-scaled-to-billions-of-transactions-daily-using-ju.html) +* [PayPal Akka Streams & Akka HTTP for Large-Scale Production Deployments](http://paypal.github.io/squbs/) +* [Lightbend Platform Tech Hub](https://developer.lightbend.com/guides/?_ga=2.3489849.888347207.1547346978-1017804577.1547100744) + +#### Related Posts + +* [Vert.x Service Proxy](https://jiangew.github.io/vertx-service-proxy/) +* [Vert.x 异步 RPC 实现解读](https://jiangew.github.io/vertx-async-rpc/) +* [Vert.x in Action](https://jiangew.github.io/vertx-in-action/) \ No newline at end of file diff --git "a/_posts/49-React Hook\344\275\277\347\224\250\345\260\217\350\256\260\345\275\225.md" "b/_posts/49-React Hook\344\275\277\347\224\250\345\260\217\350\256\260\345\275\225.md" new file mode 100644 index 0000000..c84af7c --- /dev/null +++ "b/_posts/49-React Hook\344\275\277\347\224\250\345\260\217\350\256\260\345\275\225.md" @@ -0,0 +1,167 @@ +--- +title: React Hook使用小记录 +date: 2020-10-11 +updated: 2020-10-11 +issueid: 49 +tags: +- 前端 +--- +# UseMemo 的使用 + +FP 组件每次更新渲染都会被调用,我们希望更新时保存上次渲染的状态 +比如如下组件 onChange触发之后组件更新,结果输入的值又没了 + +```jsx +const [formData, setFormData] = useState(""); +const [formSchema, setFormSchema] = useState(defaultSchema); +return ( + + +
+ {}} + > +
+ + +
+ { + setFormData(JSON.stringify(e.formData)); + }} + > + + +
+ + +
+ {}} + > +
+ +
+); +``` + +而使用useMemo改造后,只要依赖数组不变化,那组件不会被重新渲染,状态还会被保留 + +```jsx +const [formData, setFormData] = useState(""); +const [formSchema, setFormSchema] = useState(defaultSchema); +return ( + + +
+ {}} + > +
+ + +
+ {useMemo( + () => ( + { + setFormData(JSON.stringify(e.formData)); + }} + > + + + ), + [formSchema] + )} +
+ + +
+ {useMemo( + () => ( + {}} + > + ), + [formData] + )} +
+ +
+); +``` +## useRef + +函数组件每次调用函数都是新的引用,如果这个函数你传给了子组件,子组件不重新渲染那拿不到最新的函数,可以利用useRef保存函数状态 + +![image](https://user-images.githubusercontent.com/24750337/94576173-a4a4f480-02a7-11eb-838e-ddb4422a2136.png) + +## useEffect + +#### 最佳实践 + +看下面的代码,我们希望 createItem 创建完刷新页面,拉取最新的数据。 +```js + const [pattern, setPattern] = useState({}); +useEffect(() => { + (async () => { + const items_temp = await api.items.getAllItems(key_id); + const key_temp = await api.keys.getOne(key_id); + setItems(items_temp); + setKey(key_temp); + })(); +}, []); +``` + +如果我们将API请求和 setXXX 放到 `componentDidUpdate` 里会触发React无限更新 + +```js +const [x, SetX] = useState([]); +useEffect(() => { + (async () => { + const x = await api.get(id); + // 这样会触发无限更新! + setX(x); + })(); +}) +``` + +解决办法是把API和SetX做成单独的函数,不要放到 `useEffect` 里,需要的时候调用一下就好了 + + +```js +useEffect(() => { + (async () => { + fetchList(); + })(); +}, []); +async function fetchList() { + const items_temp = await api.items.getAllItems(key_id); + const key_temp = await api.keys.getOne(key_id); + setItems(items_temp); + setKey(key_temp); +} +``` + +需要的拉取新数据时就调用一下 + +```js +try { + await api.items.createItem(key_id, { + Value: value, + ...toUpperCase(data) + }); +} catch (e) { + console.log(e); +} finally { + setLoading(false); + setModalVisiable(false); + fetchList(); +} +``` \ No newline at end of file diff --git "a/_posts/5-Golang\344\275\277\347\224\250ProtoBuf.md" "b/_posts/5-Golang\344\275\277\347\224\250ProtoBuf.md" new file mode 100644 index 0000000..aa914d7 --- /dev/null +++ "b/_posts/5-Golang\344\275\277\347\224\250ProtoBuf.md" @@ -0,0 +1,141 @@ +--- +updated: 2019-09-11 +issueid: 5 +tags: +title: Golang使用ProtoBuf +date: 2019-09-11 +--- +两个例子都使用了`Golang`最新的`module feature` +第一个例子还是放到了`$GOPATH`下 + + +#### go.mod >> module chaochaogege.com/filecatcher + +如果我的域名`chaochaogege.com` + +路径 `$GOPATH/src/chaochaogege.com/` + +`chaochaogege.com`里面有个`project`,`filecatcher` + +我现在有两个`proto`文件都处于 `chaochaogege.com/filecatcher/common/` + +- TaskInfo.proto +- ChunkInfo.proto + + + +我们需要指定`proto`文件路径 + +比如我们当前目录是`$GOPATH/src/` +那么运行如下命令可以编译成功 + +``` +protoc --proto_path=./ --go_out=chaochaogege.com/common/ chaochaogege.com/common/TaskInfo.proto +``` + +- `--proto_path`: 指定从哪里寻找`proto`文件 +- `--go_out`: 生成的`pb.go`目录 +- `chaochaogege.com/common/TaskInfo.proto`: 要编译的源文件 + +下面是两个`proto`文件 + +*ChunkInfo.proto* + +``` +syntax = "proto3"; +package chaochaogege.filecatcher.common; + +option go_package = "chaochaogege.com/filecatcher/common"; + +message ChunkInfo{ +uint32 startBytes = 1; +uint32 endBytes = 2; +uint32 totalBytes = 3; +uint32 downloaded = 4; +uint32 orderId = 5;// 间隔1000,便于中间切分 + +} +``` + +*TaskInfo.proto* + +``` +syntax = "proto3"; +package chaochaogege.filecatcher.common; + +option go_package = "chaochaogege.com/filecatcher/common"; + +import "chaochaogege.com/filecatcher/common/ChunkInfo.proto"; + +message TaskInfo{ +string name = 1; +string storePath = 2; +uint32 workersNum = 3; +uint32 totalSize = 4; +repeated chaochaogege.filecatcher.common.ChunkInfo chunkInfo = 5; +} +``` + +`TaskInfo.proto`里面的`go_package`指定的生成路径指的是当前目录下的路径 + +因为处于`$GOPATH/src/`下面,所以最后生成的路径正好是`$GOPATH/src/ + chaochaogege.com/filecatcher/common/` + +-------------------------------------------------------------------------------- + +#### go.mod >> module filecatcher + +因为使用了`module`特性之后我们就不想全部的project都挂载到域名之下,所以现在去掉`chaochaogege.com` + +*ChunkInfo.proto* + +``` +syntax = "proto3"; +package filecatcher.common; + +option go_package = "filecatcher/common"; + +message ChunkInfo{ +uint32 startBytes = 1; +uint32 endBytes = 2; +uint32 totalBytes = 3; +uint32 downloaded = 4; +uint32 orderId = 5;// 间隔1000,便于中间切分 + +} +``` + +*TaskInfo.proto* + +``` +syntax = "proto3"; +package filecatcher.common; + +option go_package = "filecatcher/common"; +import "filecatcher/common/ChunkInfo.proto"; + +message TaskInfo{ +string name = 1; +string storePath = 2; +uint32 workersNum = 3; +uint32 totalSize = 4; +repeated filecatcher.common.ChunkInfo chunkInfo = 5; +} +``` + +**第一个例子中go.mod的module是`chaochoagege.com/filecatcher`,既然要去掉域名,那么不要忘记改成`module filecatcher`** + +这一次我们不需要到`$GOPATH/src/`目录下了, + +我在[官方Github文档](https://github.com/golang/protobuf)上找到一个新的指令格式 + +注意看我们的`go_package`, 为了运行下面的命令,我们只需要将当前目录置为`filecatcher`的上层 + +``` +protoc --proto_path=. --go_out=paths=source_relative:. filecatcher/common/TaskInfo.proto +``` + +----------------------------------------------------------------------------------------- + +之前没有`go module`的时候代码都要放到`$GOPATH`,并且引用自己写的代码还要加上域名和一堆的前缀 + +现在使用`module`,只需要在`go.mod`里面指定模块名字,之后就可以用`模块名加包名`引用了 \ No newline at end of file diff --git "a/_posts/50-Redux\344\270\272\344\273\200\344\271\210\351\234\200\350\246\201\344\270\255\351\227\264\344\273\266\346\235\245\345\244\204\347\220\206\345\274\202\346\255\245\346\225\260\346\215\256\346\265\201\357\274\237.md" "b/_posts/50-Redux\344\270\272\344\273\200\344\271\210\351\234\200\350\246\201\344\270\255\351\227\264\344\273\266\346\235\245\345\244\204\347\220\206\345\274\202\346\255\245\346\225\260\346\215\256\346\265\201\357\274\237.md" new file mode 100644 index 0000000..3c50c9d --- /dev/null +++ "b/_posts/50-Redux\344\270\272\344\273\200\344\271\210\351\234\200\350\246\201\344\270\255\351\227\264\344\273\266\346\235\245\345\244\204\347\220\206\345\274\202\346\255\245\346\225\260\346\215\256\346\265\201\357\274\237.md" @@ -0,0 +1,58 @@ +--- +issueid: 50 +tags: +- 前端 +title: Redux为什么需要中间件来处理异步数据流? +date: 2020-10-11 +updated: 2020-10-11 +--- +我们明明可以在async之后直接调用dispatch,为什么又要多此一举引入中间件呢? + +[为什么需要中间件来处理Redux异步数据流?](https://stackoverflow.com/questions/34570758/why-do-we-need-middleware-for-async-flow-in-redux) + +## 如果异步之后直接Dispatch + +```javascript +// action creator +function loadData(dispatch, userId) { // needs to dispatch, so it is first argument + return fetch(`http://data.com/${userId}`) + .then(res => res.json())f + .then( + data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }), + err => dispatch({ type: 'LOAD_DATA_FAILURE', err }) + ); +} + +// component +componentWillMount() { + loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch +} +``` + + + +## 如果使用 `react-thunk` +```js +// action creator +function loadData(userId) { + return dispatch => fetch(`http://data.com/${userId}`) // Redux Thunk handles these + .then(res => res.json()) + .then( + data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }), + err => dispatch({ type: 'LOAD_DATA_FAILURE', err }) + ); +} + +// component +componentWillMount() { + this.props.dispatch(loadData(this.props.userId)); // dispatch like you usually do +} +``` + +可以看到,`thunk` 只是更改了你的写法。因为不使用 `thunk` 你需要每次传递给异步函数 `dispatch`,使用 `thunk` 后在组件中就可以调用 `dispatch`,组件不再需要关注 `dispatch` 派发的函数是不是异步,组件只是发出一个请求而已。组件connect的数据通过 `dispatch => reducer` 流程最后会触发更新。 + +## Redux-thunk出现是为了解决什么问题? +优雅地进行异步调用,但thunk并不能终止请求,比如component unmounted 之后API调用结束这时会产生副作用,容易导致内存泄漏。 +`react saga`就是为解决上述问题而生的,当然,后面再说 :) + +[How to dispatch a Redux action with a timeout?](https://stackoverflow.com/questions/35411423/how-to-dispatch-a-redux-action-with-a-timeout/35415559#35415559) \ No newline at end of file diff --git "a/_posts/51-Typescript\345\212\250\346\200\201\347\261\273\345\236\213\346\216\250\346\226\255.md" "b/_posts/51-Typescript\345\212\250\346\200\201\347\261\273\345\236\213\346\216\250\346\226\255.md" new file mode 100644 index 0000000..22a74a3 --- /dev/null +++ "b/_posts/51-Typescript\345\212\250\346\200\201\347\261\273\345\236\213\346\216\250\346\226\255.md" @@ -0,0 +1,118 @@ +--- +title: Typescript动态类型推断 +date: 2020-10-12 +updated: 2020-10-12 +issueid: 51 +tags: +- 前端 +--- +### 实现一个功能,根据第一个字段输入的值来动态更改其余字段的类型 + +比如如下代码 + +```ts +interface InputProps { + label: string; +} +interface SelectProps { + name: string; +} +export interface InputType { + input: InputProps; + select: SelectProps; +} +export type FormItemDefinition = { + type: 'input' | 'select'; + componentProps?: any +} +``` + +我希望当 `type === 'input'` 时 `componentProps` 为 `InputProps`,同理当为 `'select'`时 `componentProps` 为 `'SelectProps'` + +#### 初步实现我们的需求 + +```ts +type InputProps = { + label: string; +} +type SelectProps = { + name: string; +} +export type InputType = { + input: InputProps; + select: SelectProps; +} +type FormItemDefinition = { + type: T; + componentProps?: InputType[T]; +} +const a: FormItemDefinition = { + type: 'input', + componentProps: { + + } +} +export const ItemCreateList: FormItemDefinition[] = [ + { + type: 'input', + componentProps: { + + } + } +] +``` + +上述代码有个问题,componentProps包含了全部的类型 + +[具体看这个Playground](https://www.typescriptlang.org/play?noImplicitAny=false&alwaysStrict=false#code/C4TwDgpgBAkgdmArsACgJwPZgM5QLxQDeAUFGVADYCGARhBQFxTbBoCWcA5gNzEC+xUJCgBlehADGqTDnxFS5OFQC2EJi3ZdeAiAA8wGNMChDo8JMAAq4aARLkoHC03PJ0WbLwfZxUpmIpJaQ9tQRsoADFDZRhgCGUAEQgAMw42YDYMOAAeSyg9OLgAE1wAawgQDGTYBGRrYQJyyurXKxsAPjl7clMmSy9yCQxlAzgIOGCcAH4XWrbIAG1LAF1QobgWKComKLQYuMSUtIysroUHMl6oAHInZGuAGnOLoZGs8cnsJm6Li+eHAQA4h6AxGKDrTaxeIAYTQECocQAMmwWDtolDDqk4OlMnAFss5Atnj8LldbnNHv9BsNRh93DhvlTfkyoIDyAJlsQgA) + +```ts +componentProps?: InputProps | SelectProps | undefined +``` +而目标是 + +```ts +componentProps?: InputProps | undefined +``` + +#### 进一步优化 + +```ts +type InputProps = { + label: string; +} +type SelectProps = { + name: string; +} +export type InputType = { + input: InputProps; + select: SelectProps; +} +// T是一个类型而keyof InputType是字符串,所以要用extends产生新类型 +// 如果是 T = keyof InputType则会出现 Type 'T' cannot be used to index type 'InputType'.(2536) 错误 +type FormItemDefinition = { + type: T; + componentProps?: InputType[T]; +} +// 最关键在这,定义一个数组,这个数组类型是 T extends keyof InputType ? FormItemDefinition : any,如果T是InputType的其中一个最后类型就是 FormItemDefinition +export type FormItems = (T extends keyof InputType ? FormItemDefinition : any)[] + +export const ItemCreateList: FormItems = [ + { + type: 'input', + componentProps: { + label:'' + }, + }, + { + type: 'select', + componentProps: { + name: '' + }, + }, +]; +``` + +[以上代码可以在这里找到](https://www.typescriptlang.org/play?noImplicitAny=false&alwaysStrict=false&ssl=11&ssc=27&pln=11&pc=34#code/C4TwDgpgBAkgdmArsACgJwPZgM5QLxQDeAUFGVADYCGARhBQFxTbBoCWcA5gNzEC+xUJCgBlehADGqTDnxFS5OFQC2EJi3ZdeAiAA8wGNMChDo8JMAAq4aARLkoHC03PJ0WbLwfZxUpmIpJaQ9tQRsoADFDZRhgCGUAEQgAMw42YDYMOAAeSyg9OLgAE1wAawgQDGTYBGRrSAA+OXtyUyZLL3IJDGUDOAg4YJwAfhdaqxsAbUsAXVC9AyMTcKi0GLjlbFy5csrq1wnGuQAKPIKBkqhdqpqLeuhhyOjY+KTUuHTMnMsmpio4EAASkmM2IxAWhmM3TgLFgGwAwmgIFQ4gAZNgsJirdbxXAESYKMgtBxkNpQADkTmQ5IANISSd1elkBkNsExiSSHNQ6IxyeT6Q4+HTBcLyByHGTyT5AlJaQKuj0+iz3Dh2fLOUpVEw+eqoEL6fq5kA) + +`FormItemDefinition` 不能设置联合类型的默认值,要不然 `componentProps` 也都是联合类型了,就无法区分了,把默认值提取出去,然后逐个给 `FormItemDefinition` 限制对应类型的泛型,就是不同类型的集合了 \ No newline at end of file diff --git "a/_posts/52-\346\225\260\346\215\256\350\241\250\347\232\204\350\256\276\350\256\241.md" "b/_posts/52-\346\225\260\346\215\256\350\241\250\347\232\204\350\256\276\350\256\241.md" new file mode 100644 index 0000000..368f1c0 --- /dev/null +++ "b/_posts/52-\346\225\260\346\215\256\350\241\250\347\232\204\350\256\276\350\256\241.md" @@ -0,0 +1,54 @@ +--- +title: 数据表的设计 +date: 2020-10-21 +updated: 2020-10-21 +issueid: 52 +tags: +- 基础 +- 数据库 +--- +比如我们有个表(UserLiked)记录用户喜欢的 workspace 内容,且一个用户可以喜欢多个 workspace + +前端的 UI 展现是用户点击 like 界面显示出用户全部喜欢的 workspace + +现在有下面两种表结构,你觉着哪种不错? + +**注意,MYSQL并不支持Array类型,这里只是比较表设计。** + +**Mongodb支持Array,文档数据库表结构灵活,不要滥用Array,不要过早优化** + +A 表 + +| ID | UserID | LikedIds | +| --- | ------ | ------------- | +| int | int | Array\ | + +又或者 B 表 +| ID | UserID | workspaceID | +| --- | ------ | ----------- | +| int | int | int,外健 | + +A 表可以保证只存在唯一一个 UserId,这时 UserID 可以作为主键 + +B 表 UserID 可以存在多个,UserID, workspaceID 共同作为主键 + +为了拿到更细度的 workspace 数据两个数据表都需要 Join 联查 + +### 你真的需要存储数组吗? + +https://stackoverflow.com/questions/17371639/how-to-store-arrays-in-mysql + +https://stackoverflow.com/a/17371788/7529562 +上文 A 表结构要通过数组实现,但 MYSQL 并不支持数组,为什么不支持? + +设想我们还有一张记录用户全部的手机号 UserPhones 表。按照 UserLiked 的设计我们会在 User 表中添加 Phones 数组,现在表成了这样 + +| UserID | Phones | Liked | +| ------ | ----------- | ----------- | +| int | Array\ | Array\ | + +那如果今后还有相似的需求呢?难道总是通过加数组解决吗? + +最好的解决办法是像 B 表,将两张表分裂成三张表 + +User 表只存储用户信息, Phone 表只存储手机号码信息,如果两表有关联应使用新的表记录两者的关联,并通过 JOIN 多表联查,这就是关系数据库。 \ No newline at end of file diff --git "a/_posts/53-Rust\346\265\205\345\260\235.md" "b/_posts/53-Rust\346\265\205\345\260\235.md" new file mode 100644 index 0000000..e9399fd --- /dev/null +++ "b/_posts/53-Rust\346\265\205\345\260\235.md" @@ -0,0 +1,1233 @@ +--- +title: Rust浅尝 +date: 2020-11-01 +updated: 2022-09-12 +issueid: 53 +tags: +- 编程语言 +- Rust +--- +# Rust 基本语法学习 + +## 为什么会有此文 + +目标是给 Deno 项目贡献代码,结果发现我还不会 Rust ......,所以要先啃 Rust + +我发现 rust,ruby 和掌握的 C 系风格语言很不同。 + +我从 C# 转 Java 时根本没花时间『特地』学习语法,因为他们两个太像了,直接找了一份开源代码对着抄,边抄边查文档就会了。 + +然而当我开始学 Rust 时我看到这语法就蒙蔽了。 + +你很习惯用之前的经验来套,结果发现,卧槽,你 `return` 哪去了, for 循环呢? `'static`又是个啥,好不容易看到个 \ 以为碰到泛型了,结果后面的 where 又是个啥,搜完才发现类似于 T extends String。 + +语法都看不懂何谈抄? + +所以决定将我 抄 官方文档的 example 和开源代码的过程作为笔记沉淀下来 :) + +开始正文 + +学了这么多语言,我也有自己的学习流程 + +1. 模块系统,如何引入类(相对,绝对路径),模块的最小单元是由约束,关键词?文件? +2. 类型系统,是否区分 primitive 和 Object, pass by value or reference? +3. GC系统 +4. 生态系统,有包管理器吗,如何安装第三方包 +5. 线程模型,1:1 还是 M:N,是否实现了 yield, await, async, Promise, Future 等等异步模型 +6. 语言特性,Green Thread,Ownership等等 +7. 关键字,预处理,宏定义等杂项 + +上述七步互相穿插并持续配合官方文档,知乎文章,搜索引擎 + +## 模块引用-模块系统 + +这是某个文件的内容 + +``` +use crate::{event, sys, Events, Interest, Token}; +use log::trace; +#[cfg(unix)] +use std::os::unix::io::{AsRawFd, RawFd}; +use std::time::Duration; +use std::{fmt, io}; +``` + +可以看到,对于自己包使用 use crate,当引用外部包时使用 use std:: 或者 use log::trace +std:: 是 rust 的标准库 + +rust的模块系统是个树形结构,全部模块都挂载到 crate 这个root上,而以 `crate::xxx` 的方式引用模块被称为 绝对路径 引用 + +``` +crate + └── front_of_house + ├── hosting + │ ├── add_to_waitlist + │ └── seat_at_table + └── serving + ├── take_order + ├── serve_order + └── take_payment +``` + +``` +use crate::{event, sys}; +``` + +``` +src + └── event + ├── mod.rs + │ + │ + sys + ├─ mod.rs +``` + +引用自己的代码用 crate 开头,比如 use crate::{ event }; + +如果是外部库比如 log,就使用 use log; + +use park; + +1. 首先查找 park.rs,找到停止 +2. 查找park/mod.rs文件 + +上述查找算法不运行同时拥有 park/mod.rs 和 park.rs + +(pub)mod park;(向外)声明模块,使用上述查找算法 + +**注意** + +Rust中不以文件,而以 mod 关键字定义模块,如果你的文件没有 mod 关键词,那你的代码就挂载在父文件夹所在的模块 + +mod event; 向编译器声明一个模块,这样编译器才知道这个模块的存在并将其引入到 module tree 里,如下图所示 + +![image](https://user-images.githubusercontent.com/24750337/97392445-bddba800-191c-11eb-8991-ff8cc2d155b9.png) + +我觉着对于模块,下面这句话**最精髓** + +**mod 串成的树可以让编译器找到你全部的代码并编译** + +**而 pub 可以让你显式将符号向其他模块导出** + +如果你向接近一个符号,那从这个符号的最左侧一直到最右侧都必须 accessable + +```rust +// src/event/event.rs +pub struct Event { + inner: sys::Event, +} + +// 这点和 js,python很不同 +// js 使用文件路径直接就可以查找,而不需要先声明存在这个模块 +// src/event/mod.rs +mod event; +mod events; +mod source; + +pub use self::event::Event; +pub use self::events::{Events, Iter}; +pub use self::source::Source; + +``` + +> use crate::event::Event; + +1. 找到src/ +2. 找到event/mod.rs,发现定义了 event 模块(mod event;) +3. event.rs 中 pub 将Event从 event.rs 模块导出 +4. pub use self::events::Event 先将模块导入 mod.rs 之后利用 pub 二次导出 +5. 于是可以访问 Event struct + +struct 只是个Item,可以替换为 function, trait + +pub 会将 item 挂载到上一级 mod,如果直接在src/ 目录下,那就挂载到 crate 这个 Root module + +### 约定优于配置 + +我们的代码往往有两种用途 + +##### 作为 binary + +rust约定当 src/main.rs 存在时rust可以生成可执行文件 + +#### 作为 library + +rust约定当 src/lib.rs 存在时 rust作为库文件使用,引用这个库时 rust 会读取 src/lib.rs 的信息 + +src/main.rs 和 src/lib.rs 可以共存,这时项目既可作为 binary 又可为 library,你引用库时需要使用库名 + +```toml +[package] +name = "minigrep" +version = "0.1.0" +authors = [""] +edition = "2018" +``` + +在main.rs引用lib时按照下面方式 + +``` +use minigrep::print; +``` + +这很好理解,当开发者用你的库时也是以库名开头,这样 rust 才知道你引用的是谁,所以使用库名引用自己库代码时可提供一致的体验。 + +##### mod utils和 use utils的区别是 + +``` +mod utils; +use utils::print; +``` + +mod告诉 rust 这里存在一个 module utils,请去找到 `utils/lib.rs` 或者 `utils.rs` 并挂载到 rust 的模块树 + +use 是将**已经**挂载到 模块树 的符号引入当前 scope,所以不要看到 use 就想到 name lookup 和 图 理论。 + +所以我们在 main 里为了使用自己的 mod,需要先 mod 声明挂载到模块树,然后 use 将符号引入 scope,main是入口点,main 之前并没有引入别的mod,所以我们要先 mod xxx 声明。 + +Rust 会从 main.rs 或者 lib.rs 这个Root开始寻找 mod xxx 来进行模块查找并挂载到 crate root + +这和 JS 很不同, import 会 **查找并将这个符号引入**,rust 拆成了两步 + +和 Java 也不同, import com.chaochaogege.utils; 会 **查找并引入** 这个 class + +https://github.com/rust-lang/book/issues/460 + +--- + +`use a::b::c` 中的 a 一定要是 lib名字 + +比如 + +```rust +// 自己库 error 下的 bad_resource_id +// 这里查找到了 src/error.rs 文件下的 bad_resource_id +use crate::error::bad_resource_id; +//第三方库 serde_json 下的 json +use serde_json::json; +// 标准库 std 下的 pin::Pin +use std::pin::Pin; +``` + +对于自己编写的 crate 必须**遵循先声明后使用** + +```rs +// lib.rs +// 必须先声明存在 mod编译器才会去寻找 unix +// 因为下面 Selector 已经导入 unix/mod.rs 所以直接能从 unix:: Selector 引入 +mod unix; +use crate::unix::Selector; + +// src/unix/mod.rs +// 先声明当前目录下存在 selector 模块 +mod selector; + +// 将 selector 模块引入到 当前mod.rs并导出 +pub(crate) use self::selector::{Selector}; + +// src/unix/selector.rs +pub struct Selector { + id: usize, +} +``` + +如果使用第三方 crate 无须声明直接用 crate 名字引入 + +```rs +// 使用名字直接引入cargo.toml 中的 ntapi 依赖 +use ntapi::ntrtl::RtlNtStatusToDosError; +``` + +#### C++ 的模块系统 + +我想尝试理解 rust 的模块系统为什么这么设计,所以借这个机会谈一下 CPP 模块系统 + +我们都有个习惯,http.h 存放 http 函数的声明,而http.cc 包含着 http 具体的实现逻辑 + +当你在main函数引用 http 的实现时会这样 + +```cpp +#include "http.h" +``` + +```cpp +// http.cc +#include "http.h" +int run(){ + return 0; +} + +``` + +我们并没有在任何地方引用 http.cc ,那最后编译器如何将你的实现引入呢? + +关键点在编译你的项目时你需要 gcc main.cpp http.cc -o main + +编译器看到 main 中引用了 http.h,他就会在符号表记录这个符号,但他并不知道这个符号到底在哪,所以我们需要主动将这个 http.cc 文件编译进去,生成 http.o,编译器发现 http.o 中的 run 在 http.h 对应后,会将符号表中对应的符号地址替换成 run。 + +当然,我们编译大型项目时往往借助 makefile,makefile中指定你想要编译的全部 xx.cc 文件,linker会将这些 xxx.o 连接。 + +这和 rust 有什么相似点呢? + +mod xxx; 向编译器声明这个模块的存在,xxx + +use xxx; 编译器会查找这个模块,这样不需要我们和 cpp 一样指定全部要编译的文件。 + +## 函数返回 + +语句结束要加分号,如果没有分号表示 return + +```rust +fn multiple(first_number_str: &str, second_numer_str: &str) -> Result { + // 由于 parse 可能发生异常,这里返回Result,OK可以将如果成功的值解析出来 + let first_number = first_number_str.parse::()?; + let second_number = second_numer_str.parse::()?; + // 这里没分号,返回了 + Ok(first_number * second_number) +} +``` + +如果 + +## Traits 约束 - 接口 + +就是接口,比如下面代码, println? 默认并不支持输出 struct,就像 Java 重写 toString 后才支持输出 class, rust 中需要实现 std::fmt::display 这个 trait(接口)。 + +derive(Debug) 实现 Debug 接口,而 Debug 这个 trait 实现了 std::fmt::Display 的 fmt 接口 + +{:?} 是以 Debug 输出,需要配合 derive(Debug) + +```rust +#[derive(Debug)] +struct Rectangle { + width: u32, + height: u32, +} + +fn main() { + let rect1 = Rectangle { + width: 30, + height: 50, + }; + println!("rect1 is {:?}", rect1); +} +``` + +下面的 trait 的声明和定义,你可以看到 trait 和 generic 很像 + +```rust +trait Add { + // 类似 typescript 的 type x = () => void; 语法 + // 定义一个Output便于函数使用 + // 由于 Trait 不能被直接实例化所以 Output 并没有绑定到具体的类型 + type Output; + + fn add(self, rhs: Rhs) -> Self::Output; +} +use std::ops::Add; + +struct Millimeters(u32); +struct Meters(u32); + +impl Add for Millimeters { + // 我们实现 Add 这个trait时将 Output 定为 Millimeter 类型 + type Output = Millimeters; + + fn add(self, other: Meters) -> Millimeters { + Millimeters(self.0 + (other.0 * 1000)) + } +} +``` + +### Trait 和 范型区别 + +这是 Trait + +```rust +struct Counter { + count: u32, +} + +impl Counter { + fn new() -> Counter { + Counter { count: 0 } + } +} + +impl Iterator for Counter { + type Item = u32; + + fn next(&mut self) -> Option { + // --snip-- + if self.count < 5 { + self.count += 1; + Some(self.count) + } else { + None + } + } +} + +fn main() {} +``` + +这是范型,调用范型函数时每次调用都需指明范型参数的具体类型 T。 + +Trait 可理解为 具体的范型,trait 只能实现一次且实现时已经指定为哪个类型实现。 +当我们调用 trait 方法时不再需要显式传递范型参数 + +https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#specifying-placeholder-types-in-trait-definitions-with-associated-types + +```rust +pub trait Iterator { + fn next(&mut self) -> Option; +} +``` + +## 函数签名推断 + +有人会有疑问,为什么函数上要加类型约束,难道不能从调用方传递过来的类型推断吗 + +事实上函数可能会单独存放,它并不知道谁会调用它,函数需要自身携带有关的参数信息便于编译器进行调用参数校验。 + +## where keyword + +```rust +pub fn deregister(&self, source: &mut S) -> io::Result<()> +where +S: event::Source + ?Sized, +{ +trace!("deregistering event source from poller"); +source.deregister(self) +} +``` + +前面说了 trait 实现接口,当涉及到泛型时还会有泛型参数约束 + +比如 Java 中 `` 要求 T 必须继承自 String,rust 可以对泛型参数 S 进行约束,event::Source 和 Sized 的合体类型 + +--- + +## 移动语义 + +```rust +let args: Vec = env::args().collect(); +// 这是正确的 +let query = &args[1]; + +// 下面代码就会出现 cannot move 的错误,究其原因是 String 类型不是 Copy 类型,String 对象内管理着一些内存,这些内存只能转移出去(move out)或者 Clone +// 是不是和 C++ 中的 Copy, Move语义很相似? +// https://stackoverflow.com/a/40075101/7529562 +let query = args[1]; +``` + +--- + +再看一个 + +```rust +let contents = fs::read_to_string(config.filename).expect("Something went wrong reading the file"); +println!("{} contends are {}",config.filename, contents); +``` + +上面代码运行会出这个错误: + +``` +error[E0382]: borrow of moved value: `config.filename` + --> src\main.rs:10:35 + | +8 | let contents = fs::read_to_string(config.filename) + | --------------- value moved here +9 | .expect("Something went wrong reading the file"); +10 | println!("{} contends are {}",config.filename, contents); + | ^^^^^^^^^^^^^^^ value borrowed here after move + | + = note: move occurs because `config.filename` has type `std::string::String`, which does not implement the ` +Copy` trait +``` + +原因是 config.filename 所有(ownership) 转移到 read_to_string,println! 时没有所有权而报错 + +改为 fs::read_to_string(&config.filename),以借用 borrow 方式解决 + +### 函数闭包 + +```rust +let expensive_closure = |num| { + println!("calculating slowly..."); + thread::sleep(Duration::from_secs(2)); + num +}; +``` + +没错,上面这个就是 函数闭包,我知道你以为的闭包是这样子 + +```js +let expensive_closure = function(num) { + // xxx +} +``` + +rust 在文档里也解释为什么用 |num| 的形式 + +> this syntax was chosen because of its similarity to closure definitions in Smalltalk and Ruby. + +说白了,就是从 Ruby 借鉴(抄)过来的嘛!虽然我觉着这语法挺难看的 :) + +下面比较几个 rust 语法定义 + +```rust +// 函数定义 +fn add_one_v1 (x: u32) -> u32 { x + 1 } + +// 添加上 类型约束 的闭包函数定义 +let add_one_v2 = |x: u32| -> u32 { x + 1 }; + +// 去掉 类型约束 的闭包函数定义 +let add_one_v3 = |x| { x + 1 }; + +// 由于函数体只有一个表达式,所以 {} 也直接去掉 +// 和 Javascript 的 let add_one_v4 = x => x + 1 很像 +let add_one_v4 = |x| x + 1 ; +``` + +### 静态生存期 (static Lifetime) + +```rust +let s: &'static str = "I have a static lifetime."; +``` + +## 所有权 + +GC 语言不需要手动清理引用,cpp 没有 GC 需要你显式调用 free,但 free 一次分配只能调用一次,多次调用会出现不可预料的问题。 + +GC 语言的问题是当引用类型作为参数传递给函数时在函数的 scope 中会有一个新的变量来承接传递过来的引用,这时同一块内存就有了两个变量引用。问题出现了,我们怎么知道什么时候才能释放这块内存呢? + +GC 语言不需要程序员关注,因为 GC 会帮你标记引用次数并使用算法感知到什么时候能释放。 + +Rust 如果没有所有权的概念那就和 CPP 一样需要程序员手动释放内存。 + +所有权就是引用类型在传递给函数或者其他的变量时上一个变量就不允许再访问原来的内存,这时内存的所有权给了新变量。rust 通过这种方式可以确保某一时刻始终只有一个变量 hold 这块内存,当这个变量离开属于它的 scope 时就可以调 drop。 + +**核心点是只有一个变量持有引用**,如果多个变量都持有,那在分别离开自己 scope 后多次调用 free释放同一块内存会导致 `memory corruption` + +但多个变量都持有同一块内存的引用是非常有用的,Rust也提供了Rc,Arc来实现 counted references + +Rust 中的基本数据类型Copy 速度会很快,所以允许旧变量赋值之后本身可用 + +```rs +// x 给y之后x还可用 +let x = 5; +let y = x; + +println!("x = {}, y = {}", x, y); +``` + +其他复杂类型赋值= 时会引用所有权系统,但自实现的 Copy Trait 除外(Rust不允许同时实现Copy和Drop方法) +https://kaisery.github.io/trpl-zh-cn/ch04-01-what-is-ownership.html#%E5%8F%AA%E5%9C%A8%E6%A0%88%E4%B8%8A%E7%9A%84%E6%95%B0%E6%8D%AE%E6%8B%B7%E8%B4%9D + +看 rust 手册时还看到了 move,copy 的概念,这个就和 cpp 差不多了,本质是一种接口的设计,程序员可以实现这些接口来规定变量赋值时到底采取什么行为,cpp 中有几个概念:移动构造函数,拷贝构造函数 + +以及右值引用 + +如果你在调用函数结束需要继续使用 s1 ,那函数必须以引用 & 的方式,函数结束后所有权会还给 caller。 假如你有一个苹果,你需要别人借用之后返回给你,你可以用一个盒子把你的苹果包装起来,将这个盒子的所有权借给他(function),等借用完成盒子被销毁但你的苹果还在,其实引用(&)就是一个拦截器,可以拦截 function 对于内部变量的操作。 + +```rust +let s1 = String::from("hello"); +let len = calculate_length(&s1); + +fn calculate_length(s: &String) -> usize { // s is a reference to a String + s.len() +} // H +``` + +```rust +let s1 = String::from("hello"); +let len = calculate_length(s1); + +// calculate_length 的 s 参数没有 &,那 s1 传递给 calculate_length 时值会被move +// 原理是 calculate_length 中创建一个临时变量来承接 s1 的内容那 s1 就不能引用自己原来的内容了 +fn calculate_length(s: String) -> usize { // s is a reference to a String + s.len() +} // H +``` + +看起来 rust 隐藏了 GC 的概念,但隐藏的代价是开发者不能想使用 GC 语言一样随意的返回引用、对变量进行操作。其实习惯也没什么感知了。 + +rust 中的 & 叫做 reference,你就可以认为是 cpp 中的 pointer。 + +rust 中解引用用 * 获取pointer值向的值 + +& 在rust中还有一个含义,借用(borrow),上面说了,所有权关键点最终某一个时刻只应有一个量持有引用的内存 + +但其实 Rust 还允许同时存在多个不可变引用,或者只存在一个可变引用 + +```rust +let a = Box::new(1); +let b = &a; +let c = &a; +let d = &a; +let mut e = &a; +``` + +#### 如何形象描述 Rust 引用呢? + +![Rust引用结构](https://user-images.githubusercontent.com/24750337/97800016-834f7380-1c6c-11eb-891e-ed3054b11a38.png) + +上图说明 s 只是简单存放了指向 s1 的 pointer,s 对最右侧操作需要二次寻址。二次寻址确保了当 s 不再使用可以安全 drop 掉而不影响 s1 + +理论上能获取到pointer就获得了对数据修改的能力, 但 Rust 中引用默认是 immutable,想要修改需添加 mut 修饰。 + +mut 有个限制,在同一个 scope 只允许一个变量持有 mut 的引用 + +https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#mutable-references + +下面代码编译不通过。 + +```rust +fn main() { + let mut s = String::from("hello"); + + let r1 = &mut s; + let r2 = &mut s; + + println!("{}, {}", r1, r2); +} +``` + +``` +$ cargo run + Compiling ownership v0.1.0 (file:///projects/ownership) +error[E0499]: cannot borrow `s` as mutable more than once at a time + --> src/main.rs:5:14 + | +4 | let r1 = &mut s; + | ------ first mutable borrow occurs here +5 | let r2 = &mut s; + | ^^^^^^ second mutable borrow occurs here +6 | +7 | println!("{}, {}", r1, r2); + | -- first borrow later used here +``` + +引用总结 + +> 1. 在某一时刻,你只被允许只有 一个可变引用(mut) 或多个不可变引用 +> 2. 引用必须总是合法,即:引用变量必须存活到被引用变量之后 + +--- + +下面代码编译不通过在于:对于引用类型的赋值不像其他语言的shadow copy一般,rust 会直接将 s1 持有的全部数据转移到 s2 中,从s2往后s1就不被设置为非法,不被允许访问原来的内存,这就是 Move。 + +```rust +let s1 = String::from("hello"); +let s2 = s1; + +println!("{}, world!", s1); +``` + +借用不会move 掉 s1 的内存,而暂时允许 s2 持有 s1 引用的内存。通过 & 的标记 Rust 会确保 s2 离开自己的scope时不会被调用 drop,因为 s2 的内存最终属于 s1 + +```rust +let s1 = String::from("hello"); +let s2 = &s1; + +println!("{}, world!", s1); +``` + +--- + +基本数据类型大小在编译时就可确认,stack中的数据复制性能远高于存放于 heap 的String类型,这时数据的赋值就是Copy + +x 和 y 相等但不相同 + +```rust +let x = 5; +let y = x; + +println!("x = {}, y = {}", x, y); +``` + +我们再想一下,为什么会有所有权的概念?GC存在的意义是正确释放不再使用的 heap 内存。 + +函数调用需要调用栈 stack 的大小必须可被计算,所以我们不能将大小可变的数据存到栈 + +Rust有四种基本类型(Integer, Floating, Boolean, Character),两种组合类型(Tuple, Array) + +Tuple,Array长度固定,也即编译时就必须可确认大小,**上面六种类型都会分配到 stack** + +所有权关注的是分配到 heap 的数据类型, + +看下面的代码,既然数组是基本类型,那函数调用时就会复制整个数组,你对 array_b 的修改不会反映到 array_a + +这里不存在所有权转移,array_b 本质是复制了 array_a 而不是**偷走**了 array_a 的内存 + +为什么不偷走呢?因为 Array 类型 **size fixed**,编译时就可以分配好栈大小 + +```rust +#[test] +fn p () { + let mut array_a = [1,2,3]; + c(array_a); + fn c (mut array_b: [i32;3]) { + array_b[1] = 2; + } + // [1, 2, 3] + println!("{:?}",array_a) +} +``` + +想要在 p 里修改 array_a 就需要传递指针 & 并标记为可变 mut + +```rust +#[test] +fn p () { + let mut array_a = [1,2,3]; + c(&mut array_a); + fn c (mut array_b: &mut [i32; 3]) { + array_b[1] = 6; + } + // [1, 6, 3] + println!("{:?}",array_a) +} +``` + +下面 s 是个 &str 类型,因为 HelloWorld 被存到 .data 段, s 是指向 .data 段某个地址的不可变引用 + +```rs +let s = "Hello, world!"; +``` + +考虑 所有权 的关键点在于明确数据分配在 heap 中,这里为什么说是数据而不说变量呢?因为数据囊括可变和不可变,这两种都可以分配到 heap + +具体可以读读下面链接,我记录的往往都是官方文档给的启发。 + +https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html + +#### move + +下面闭包孵化了新的线程,但线程运行的时间可能长于调用函数。 + +需要明确闭包函数对外部变量的所有权归属,改为 `let thread = thread::spawn(move || loop {` + +后闭包会强行 take ownership + +```rust +fn new (id: usize, receiver: Arc>>) -> Worker { + let thread = thread::spawn(|| loop { + let job = receiver.lock().unwrap().recv().unwrap(); + println!("Worker {} got a job; executing.", id); + job(); + }); + Worker{ + id, thread + } +} +``` + +### 生命周期 + +2020/11/19 的思考 + +Rust始终在解决一个问题:如何保证内存安全? + +所以对于包含 引用 的 `struct` 结构体,Rust 必须确保引用在 `struct` 实例存活过程中始终有效,不会出现野指针 +在编译器无法确定引用和 struct 自身存活时间孰短孰长时,开发者必须通过生命周期标识 显式 保证 引用 存活时长。 + +```rs +struct User { + username: &str, + email: &str, + sign_in_count: u64, + active: bool, +} + +fn main() { + let user1 = User { + email: "someone@example.com", + username: "someusername123", + active: true, + sign_in_count: 1, + }; +} +``` + +上面代码编译器会抛出如下问题 + +``` +error[E0106]: missing lifetime specifier + --> + | +2 | username: &str, + | ^ expected lifetime parameter + +error[E0106]: missing lifetime specifier + --> + | +3 | email: &str, + | ^ expected lifetime parameter +``` + +看完了文档总结我的理解: + +生命周期告诉 Rust 两个变量存活的时间长度 + +比如下面的代码,返回的 &'a str 一定是 x **或** y,或者说,&'a str 一定依赖了 x **或** y, + +那通过生命周期标注 `'a` 告诉 Rust 编译器返回的值生命周期是 x **和** y **最小值** + +```rust +fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { + if x.len() > y.len() { + x + } else { + y + } +} +``` + +不能在函数中返回局部变量的引用,函数调用完成 result 就被drop掉,所以返回的引用也是不合法的 + +这段代码过不了编译是因为 str 属于基本数据类型,调用结束函数栈就被pop掉,对于被pop掉的栈变量引用是不合法的。 + +```rust +fn longest<'a>(x: &str, y: &str) -> &'a str { + let result = String::from("really long string"); + result.as_str() +} +``` + +当然,你可以返回 String + +```rust +fn main() { + let a = calculate_length(); + println!("{}",a) +} +fn calculate_length() -> String { // s is a reference to a String + String::from("123") +} // H +``` + +#### 'static 生命周期 + +简单介绍一个语句 + +```rust +// 定义 Job 类型, dyn FnOnce 是多态函数,只允许被调用一次,实现了 Send trait,'static生命周期 +// Box 要求编译器将 Job 类型放到 heap 中 +type Job = Box; +``` + +#### 生命周期参数 + +你调用函数时编译器只能知道函数的参数类型,但并不知道传递给函数的变量要存活多久,如果函数永远不返回那传递的参数就没法释放,所以需要函数标注生命周期来确定传递的参数可以和xxx存活时间一样长。 + +https://www.reddit.com/r/rust/comments/bltnfv/simplest_best_explanation_of_lifetimes/emth1hk?utm_source=share&utm_medium=web2x&context=3 +https://www.reddit.com/r/rust/comments/bltnfv/simplest_best_explanation_of_lifetimes/emrev4p?utm_source=share&utm_medium=web2x&context=3 + +### Async, Await 语义 + +再说 Rust 之前先用我最熟悉的 Javascript 举例子 + +```js +async function readFileFromDisk(path) { + return await fs.readFile(path); +} +const pwd = await readFromDisk("/etc/passwd"); + +// 上面的 async function 会被翻译成这样 +function readFileFromDisk(path) { + return fs.readFile(path); +} +readFromDisk("/etc/passwd").then((d) => { + const pwd = d; +}); +``` + +JS 的例子是将异步 IO 转换成 Promise,这个 Promise 对应的 Rust 中的 Future,Java 中的 CompletableFuture(Java 中的 Future 是个同步调用,override 掉 get 方法) + +理解这样概念时要明白怎么实现的,OS 只认识线程 + +如果 FD(File descriptor) 被设置为 block,那 Read 时没有数据就会被阻塞,既然阻塞当前调用线程就会被挂起,很好理解,所谓的协程, green thread 只不过是用户空间构造出来的概念,OS 只认线程,所以 IO 调用被阻塞线程就作为人质被挂起 + +如果 FD 是 setBlocking(false),那就引出现在主流的异步实现方案 + +这个方案借助了 OS 提供的 IO 等待阻塞原语 IO-aware system blocking primitive,当 Read 没有数据读时会直接返回,可以将 FD 注册给 OS 然后 poll 等待,等数据读时 OS 会唤醒你的调用线程 + +poll 有两个实现,单线程/多线程池 + +Rust 中的 mio 是一个单线程实现,Read,Write 都在一个线程 + +Tokio 借助 mio 实现了线程池方案 + +问题是常用的 select 方案对 File 支持不友好 + +https://www.reddit.com/r/rust/comments/dh8ook/mio_for_file_operations/ + +现有的解决办法是开新的线程阻塞读取 + +两种阻塞方案 +socket 的 read 不一定会有数据,epoll 在这种情况下可以封装事件循环 + +但如果你读取本地文件 read 一定会马上读取,如果对 file 使用 epoll,当调用 epoll_await 时马上会返回,因为文件这时已经就绪可读了 + +read 一定要放到某个 thread 才行,Linux 有的 AIO 了解一下 + +### 智能指针 + +#### Rc - Reference Counted + +具有引用计数功能的指针,只能用于单线程 + +A,B,C,D 同时引用一块内存,如果知道 A 最长那可以将A设置为 ownership。但实际上不能确认谁的生命周期最长。Rc就是为了解决这个问题,它会在 count = 0 时释放内存。 + +### 无惧并发 + +下面代码线程捕获了 v,但 Rust 并不能确定线程运行多长时间,也就不能明确释放时机 + +```rust +use std::thread; + +fn main() { + let v = vec![1, 2, 3]; + + let handle = thread::spawn(|| { + println!("Here's a vector: {:?}", v); + }); + + drop(v); // oh no! + + handle.join().unwrap(); +} +``` + +所以需要move语义,将线程引用的外层变量全部 take ownership,转移到线程上下文中。 +https://doc.rust-lang.org/book/ch16-01-threads.html + +#### Channel + +```rust +use std::sync::mpsc; +use std::thread; + +fn main() { + let (tx, rx) = mpsc::channel(); + + thread::spawn(move || { + let val = String::from("hi"); + tx.send(val).unwrap(); + }); + + let received = rx.recv().unwrap(); + println!("Got: {}", received); +} +``` + +### 语法杂项 + +#### 函数调用返回 + +语句结束要加分号,如果没有分号表示 return + +```rust +fn multiple(first_number_str: &str, second_numer_str: &str) -> Result { + // 由于 parse 可能发生异常,这里返回Result,OK可以将如果成功的值解析出来 + let first_number = first_number_str.parse::()?; + let second_number = second_numer_str.parse::()?; + // 这里没分号,返回了 + Ok(first_number * second_number) +} +``` + +main 函数没有返回值,所以最后一行要添加分号 + +#### 闭包(closure) 和 block + +看下面的代码, async 块并不是个 闭包 函数,而只是简单的 语法块,首先执行 async {},然后返回的参数再传递给 `spawn`,那 spawn 拿到的就是个 Future 而不是 函数 + +```rust +mini_tokio.spawn(async { + let when = Instant::now() + Duration::from_millis(10); + let future = Delay { when }; + + let out = future.await; + assert_eq!(out, "done"); +}); +``` + +#### Marco 宏 - 元编程 + +Marco 可以理解为模式匹配,又或者为 正则表达式捕获组,匹配出特定模式的 token 来动态生成代码 + +test 宏支持重载,有两个写法。 expr 要求 left, right 必须为表达式(expression) + +=> () 中的内容是 marco 展开的 block + +```rs +macro_rules! test { + ($left: expr; and $right: expr) => ( + println!("{:?} and {:?} is {:?}",stringify!($left),stringify!($right), $left && $right) + ); + ($left: expr; or $right: expr) => ( + println!("{:?} or {:?} is {:?}", stringify!($left), stringify!($right), $left || $right); + ) +} + +fn main() { + test!( 1i32 + 1 ==2i32;and 2i32 * 2 == 4i32); + // 这个也可以运行是由于Rust中 块(block) 也算是表达式 + test!( 1i32 + 1 ==2i32;and { + let mut a = 1 + 1; + 2i32 * 2 == 4i32 + }); + test!( 1 + 2 == 3;or 2 + 3 == 4); +} +``` + +rust 中表达式的含义更广泛,https://doc.rust-lang.org/reference/expressions.html + +看个更复杂的, ident 是个约束,要求 fn 必须是函数,右侧括号里是个正则, * 允许 0 到无穷多个,所以我在使用的时候添加,,,,,,编译也通过。 + +这里 + +```rs +{/**/{}/**/} +``` + +是个表达式,外层 {} 是宏定义外侧,内层的 {} 是块表达式 + +因为 Hexo 编译 markdown 会出错,所以用 /**/ 隔断下 + +```rs +=> {/**/{ // +// +}/**/} +``` + +```rs +#[allow(unused_macros)] +macro_rules! syscall { + ($fn: ident ( $($arg: expr),* $(,)* ) ) => {/**/{ // + let res = unsafe { libc::$fn($($arg, )*) }; + if res == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(res) + } + }/**/}; +} +fn main() { + let ep = syscall!(epoll_create(19 + 1,,,,,,,,,,,)).map(|ep| ep).unwrap(); +} +``` + +#### struct + +没想到一个 struct 我竟然要单独学一些 + +已经习惯了如下写法 + +```rust +struct Point { + name: str, +} +``` + +结果看 mio 时发现还有这样的 struct,我没想到 Token 之后可以直接括号,一直看不懂什么意思,也不好检索。 + +最后没办法了只能看下 RFC,结果发现 struct Token(pub usize) 叫做 tuple struct,可以认为这种struct 的字段是 0 + +RFC在此 + +https://doc.rust-lang.org/reference/items/structs.html + +struct 只是约束了存储空间,比如下面 + +```rs +// struct 是定义符号,即使写法不一样但还是根据存的类型来分配空间 +struct Structure_Bool(bool); +struct Structure_Integer(i32); +assert_eq!(1,std::mem::size_of::()); +assert_eq!(4,std::mem::size_of::()); +``` + +这个过程告诉我们: + +语言的语法其实没什么技术含量,看不懂这个语法可以看 RFC 学习,关键在于理解语法的内在含义,而不是表征 + +```rust +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Token(pub usize); + +impl From for usize { + fn from(val: Token) -> usize { + val.0 + } +} +``` + +上面的 struct 可以解释如下 + +```rs +pub struct Token { + 0: usize, +} +``` + +#### enum 枚举 + +大一学过 C 语言的 union,rust中的枚举类型和其差不多, 存储空间有最大的类型决定并保证内存对齐 +https://en.wikipedia.org/wiki/Tagged_union + +> 枚举和 struct 一样可以定义方法。方法、函数只是一堆的指令,操作的数据才是关键。既然枚举也有数据,那就可以为其定义操作自身数据的方法。 +> 只不过这 方法 和 类方法 一样其 self 都绑定到了要操作的内存 + +```rs +#![allow(unused)] +fn main() { + enum Message { + Quit, + Move { x: i32, y: i32 }, + Write(String), + ChangeColor(i32, i32, i32), + } + + impl Message { + fn call(&self) { + // 在这里定义方法体 + } + } + + let m = Message::Write(String::from("hello")); + m.call(); +} +``` + +#### Option 与 match + +Rust调用返回 Option,你想消费 Some 值必须要处理 Some 和None(除非使用unwrap) + +### extern crate + +声明外部依赖,这个依赖将会在编译时指定 soname,然后 linker 会确保运行时这个 so 被加载到进程。 + +### pub 声明模块可见性 + +#### pub (crate) + +声明只在当前 crate 内可见,意思是只在当前库可见(有cargo.toml的文件夹构成了一个crate) + +### QA + +#### String 和 str 区别 + +String是个对象,存放在stack上的数据大小固定 + +str 是常量,但编译时不能判断具体的长度,所以 str 的内容存放在 静态存储区,通过 &str 获取数据 + +#### #![no_std] 宏 + +Rust 的 std 标准库提供了许多特性,Vec,TCP Socket 假设代码运行在OS上,并且存在内存分配器 memory allocator 或者 network stack。 + +但如果开发自己的OS,这时 memory allocator是不存在的,那 std 库就不能使用,所以 no_std 告诉 Rust 编译器不要将标准库代码link进来。 + +https://www.reddit.com/r/rust/comments/9eyc21/noob_what_exactly_is_no_std_and_why_is_it_so/ + +经常和 std 比较的是 core 库 + +这是 Rust 中最纯净的,没有依赖的库,core不和任何平台绑定,只实现平台无关的逻辑 + +列举 core 中的两个函数就明白了 + +```rs +use core::cmp::min; // 计算最小值 +use core::mem::size_of; // 计算类型占用的字节数 +``` + +#### FnOnce() + +一次性函数,只能调用一次。往往是匿名函数(闭包函数) + +#### Rust 为什么只允许一个变量持有 mut 变量? + +mut变量可以被修改也隐含着变量不可控,多个变量持有同一个 mut 引用在多线程情况下及其容易出现 race condition。 + +rust 推荐如下方式处理多线程变量共享 + +1. 同一个变量多线程通过Mutex共享 +2. channel +3. Arc + +#### 到底什么是移动语义 + +我以为的移动语义 + +![image](https://user-images.githubusercontent.com/24750337/99526121-cd0ebc80-29d5-11eb-93af-489db17ccae4.png) + +但实际**可能**的移动语义 + +![image](https://user-images.githubusercontent.com/24750337/99526133-d26c0700-29d5-11eb-9d43-782748cf08f4.png) + +移动指的是物理内存上的地址没有变化,堆内数据 还在那里 + +而进程中虚拟地址却变化了,移动之后原来的pointer(虚线)指向不存在的内存空间 + +下面的Foo在函数中会分配到 stack,如果我们将实例化之后的 Foo 移动给别的函数会调用 memcopy 复制 Foo + +问题是 Foo中含有 ptr 指针引用到原来的内存,如果prt指向 caller 的 stack 变量极其容易造成悬空指针,所以需要一种方式固定住这块内存 + +```rs +struct Foo { + array: [Bar; 10], + ptr : &'array Bar, +} +``` + +### 标准库解读 + +#### Arc + +> A thread-safe reference-counting pointer. 'Arc' stands for 'Atomically Reference Counted'. + +```rust +let (sender, receiver) = mpsc::channel(); +let receiver = Arc::new(Mutex::new(receiver)); +``` + +想再说下 [WeakMap](https://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap),拿 Javascript举例。 + +可以理解为 WeakMap 里存放的是弱引用 + +是个引用:可以找到你存放的Object +又不完全是个引用,GC不会在乎 WeakMap 里的引用,如果没其他对象引用即使WeakMap存在引用也会被GC,相较于Map,WeakMap可以一定程度防止内存泄漏 + +```js +var map = new Map(); +function useObj(obj) { + doSomethingWith(obj); + var called = map.get(obj) || 0; + called ++; + if (called > 10) report(); + // map 持有全部obj的引用永不释放 + map.set(obj, called); +} +``` + +如果没有其他引用持有 obj们,那 GC 会自动清除 obj 且 map 中对原obj的引用也会被删除 + +```js +var map = new WeakMap(); +function useObj(obj) { + doSomethingWith(obj); + var called = map.get(obj) || 0; + called ++; + if (called > 10) report(); + map.set(obj, called); +} +``` + +那 WeakMap 有啥引用场景呢? + +最简单的是持有 DOM,一般而言 DOM 从DOM树上删除后就没用了,如果使用Map会造成 memory leak,这里使用 WeakMap 合适 diff --git "a/_posts/54-Rust-Pin\346\217\220\345\207\272\347\232\204\345\277\205\350\246\201\346\200\247-\344\273\245\345\217\212\346\210\221\345\257\271Pin\347\232\204\350\256\244\350\257\206.md" "b/_posts/54-Rust-Pin\346\217\220\345\207\272\347\232\204\345\277\205\350\246\201\346\200\247-\344\273\245\345\217\212\346\210\221\345\257\271Pin\347\232\204\350\256\244\350\257\206.md" new file mode 100644 index 0000000..0603a7e --- /dev/null +++ "b/_posts/54-Rust-Pin\346\217\220\345\207\272\347\232\204\345\277\205\350\246\201\346\200\247-\344\273\245\345\217\212\346\210\221\345\257\271Pin\347\232\204\350\256\244\350\257\206.md" @@ -0,0 +1,517 @@ +--- +title: Rust-Pin提出的必要性-以及我对Pin的认识 +date: 2021-06-08 +updated: 2021-07-13 +issueid: 54 +tags: +- Rust +--- +## 我对Pin 的整体理解 - 为了解决unsafe场景下move问题 + +### 提出的必要性 - 不依赖Pin能否做到希望的 不被move ? + +Pin的作用是防止move,但如果程序员小心处理,那就不会出错。为什么还需要Pin呢? + +既然在某些场景下move是错的,那作为安全的编程语言,就需要显式限制这些场景。**不能将安全交给人的直觉**保证,**这是Rust编译器的责任,不是人的**。 + +反面例子是C++,程序员犯错的时候还少吗? + +明确一点,即使有Pin,你如果想出 UB,通过 unsafe **一定可以** 做到,我只是想说明,Pin不是银弹,万能药。 + +即使别人眼中UB是错的,但目的不同,对正确的理解不同。如果想要的就是UB的结果,那这UB对你而言是正确的,但这种超出语言的范畴,Pin也不是为这种场景(人的主观)而生的。 + +在代码整个链路不使用任何unsafe情况下,哪怕没有Pin,你也不会写出UB的代码。编译器的静态检查会教你做人。 + +unsafe 作为 rust 的一部分,其存在是有意义的,Pin也主要针对这场景。 (我写的,有点像三段论。。。 + +Pin的意义在于,当你不想出错时,通过 Pin 藏起 `&mut T`,从语法上一定程度限制 unsafe 的危险性。 + +为什么限定unsafe,在safe rust中,构造自引用并且用起来都十分困难,UB受到 borrow checker的严格限制。不会由于move特性而导致错误。 + +一个例子: +> +> 小明你是库tokio的提供者,小红调用小明的函数,该函数利用 unsafe 返回给小红一个结构 X。X的字段 Y 自引用了 &X +> +> (利用unsafe构造出自引用结构。当然,只是用自引用结构举例子,Pin并不只是用于自引用,比如 tokio-executor 获取 X(task) 的地址,用于 poll 之后的 wake,Pin只是表达不能move)。 +> +> 小红之后使用X,进行了它认为正确的move(move在rust中很常见,而且用的是safe的函数,在小红眼里是正确的)。但这时 Y 错误引用到原来的 &X。 +> +> 那怎么解决呢? +> +> 小明可以不返回X,而是返回 `Pin` +> + +所以Pin是一种规范,一种交流的**协议**。 + +如果全代码链路都可小心翼翼处理move,不出现因为move导致的UB,那 Pin 就不是必须 + +还是上面的例子 + +小红并没有调用任何 unsafe 的代码,是小明内部使用unsafe,导致了UB + +Pin是为了增强鲁棒性(指控制系统在**一定**(结构,大小)的参数摄动下,维持其它某些性能的特性。**一定**也从侧面佐证上文说的,Pin 不是银弹,万能药,只是从语法上一定程度限制 unsafe 的危险性) + +任何 **非unsafe** 的代码和 `Pin` 一样,都会被编译器进行静态检查 + + +> 就比如 future-rs,为什么需要使用 Pin? +> +> future 的 API 内部完全可以保证不会 move 导致 UB,但是 Future 需要给广大开发者使用,广大开发者很容易在 poll 里面把你的 Future 自引用结构体 move 掉 + + +---------------------------- + +关于 `self referential part` + +编写自引用结构,并不是一定需要 `Pin`。 + +例子,你将整个结构都放到 heap 上,用户 move 只是 move 了 pointer,并不会破坏任何东西(doesn't break anything)。 + +------------------------------------------ + +下面是我(nuclearwwc)和alice 关于Pin的对话 + +> nuclearwwc — Today at 11:00 PM +> +> Hi, Alice! some streams contain unsafe code that would be incorrect if the stream is moved. So I have a question about this. If a developer can write code to avoid move very carefully. Does that mean we don't need the Pin? From my understand, as a safety language, Rust should **explicit** to restrict developer's code of conduct instead of rely on them intuition. That't why we still need Pin even though in unsafe scope. right? +> +> Alice Ryhl — Today at 11:01 PM +> +> Sure. The Pin type is only necessary if you can't trust the user to not move it. +> And generally when you write unsafe code and expose it in a library, then the library is generally considered incorrect if there is a way to use that library to invoke undefined > behavior without having the user of the library use unsafe code. +> Being robust against any type of non-unsafe code requires compile-time checks such as Pin +> There are some cases where you don't need Pin to correctly write a self-referential struct. This can happen if the self-referential part is on the heap, since then the user moving the > struct doesn't break anything. + + +https://discord.com/channels/500028886025895936/500336333500448798/847489538414608444 + + +## Pin用于防止变量被误 move + +其中之一应用场景是解决async中 Future 自身被move,导致executor唤醒失败的问题。 + + +本小结来自以下文章 + +> https://fasterthanli.me/articles/pin-and-suffering + +最常见的Pin方式是 `Box::pin(x)` 或者 `Pin::new(x)`,但后者要求 `x: Unpin` + +对于一个结构体,当每个成员都是 `Unpin` 时,整个 `struct` 就是 `Unpin` + +rust中默认类型都是Unpin的,也即,即使被Pin住,可以move + +当需要开发自引用结构时,可以将 struct 某个自引用字段用Pin包裹,这样struct就是 !Unpin,想要Pin住这种结构常规用 `Pin::new(x)`不行了。必须 Box::pin(x); + +典型的自引用结构是 linked_list + +https://github.com/tokio-rs/tokio/blob/6b9bdd5ca25bd3f30589506de911db73f7dbf8b4/tokio/src/time/driver/entry.rs#L299 + + +TimerEntry -> TimerShared -> TimerSharedPadded#pointers -> LinkedList -> TimerShared + +由于TimerShared结构间接自引用,所以整个链条都需要通过 PhantomPined 来 !Unpin + +--------------------- + +## Unpin + +Unpin是指 **即使 struct 被Pin住,struct 也可以被移动**,并不是指不能被移动! + +Types that can be safely moved **after** being pinned. + +https://doc.rust-lang.org/std/marker/trait.Unpin.html + +x.await要防止上一行存的引用在下一行被move,所以poll future时临时Pin住 + +Future是编译器翻译成的状态机,编译器会将await点用到的变量保存到struct,默认赋值这些变量时通过 `*Self = xxx`,由于Self永远是自身,所以move是安全的(是通过Self相对引用)。 + +但如果我们future的代码里获取了绝对地址,并且用在await,编译器会将这地址保存到struct,这时调用poll()被move就会出问题。为了兼容这情况,poll的Self就必须被Pin住。 + + +```rs +async fn pin_example() -> i32 { + let array = [1, 2, 3]; + // async第一次执行后 + // 获取array里面的绝对地址 + // 这些代码就将 Future 变成了自引用结构 + // Future通过poll时禁止move来避免move后引用绝对地址导致的 UB + let element = &array[2]; + async_write_file("foo.txt", element.to_string()).await; + *element +} +``` + +也为了兼容这情况,Future结构是 !Unpin + +https://os.phil-opp.com/async-await/#pinning + +Future在第一次poll之前 move 是没问题的,因为async函数里的代码被封装进 Future#poll 里,只要还从未调用过这函数,poll里可能存在的生成自引用的代码就不会被执行,那这时移动就没问题 + +**It is worth noting that moving futures before the first poll call is fine**. This is a result of the fact that **futures are lazy and do nothing until they're polled for the first time**. The start state of the generated state machines therefore only contains the function arguments, but no internal references + +-------------------- + +这两行 tokio::pin! 如果注释掉会出问题 +https://github.com/willdeeper/shadowsocks-rust/blob/6ff4f5b04b8e22d36cd54477cfcadf8cb403de21/bin/ssserver.rs#L308 + +究其原因,是由于 select 要求 Unpin,而 server 是 Future,对Future本身是 !Unpin(注意,!Unpin是编译器不会为其实现Unpin),由于server和abort_signal会被状态机放到 struct 保存状态,所以 tokio::pin 只是简单检查了是 ownership,就Pin::new_unchecked获取Pin + + +关于状态机和 !Unpin,可以看我写的 `async是如何使用状态机实现的`(本地文档库 计算机理论/),搜索!Unpin,会有详细介绍。 + +## 如果不Pin住实现Future的struct会有可能发生Panic + +> 左侧顶部三个点的按钮可以开启RUST_BACKTRACE 看到栈回溯 + +https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8fde70c2583dbcb2a4cf3d929a638b8b + +```rs +use futures::Future; +use std::{ + mem::swap, + pin::Pin, + task::Poll, + time::Duration +}; +use tokio::{ + macros::support::poll_fn, + time::sleep +}; + +#[tokio::main] +async fn main() { + let mut sleep1 = sleep(Duration::from_secs(1)); + let mut sleep2 = sleep(Duration::from_secs(1)); + + { + let mut sleep1 = unsafe { Pin::new_unchecked(&mut sleep1)}; + poll_fn(|cx| { + let _ = sleep1.as_mut().poll(cx); + Poll::Ready(()) + }).await; + } + swap(&mut sleep1, &mut sleep2); + sleep1.await; + sleep2.await; +} +``` + +上面代码会panic,原因就是没有真正Pin住,被swap出去,导致tokio唤醒了错误的sleep future + +``` +$ RUST_BACKTRACE=1 cargo run --quiet +thread 'tokio-runtime-worker' panicked at 'assertion failed: cur_state < STATE_MIN_VALUE', /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/time/driver/entry.rs:174:13 +stack backtrace: + 0: rust_begin_unwind + at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b/library/std/src/panicking.rs:493:5 + 1: core::panicking::panic_fmt + at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b/library/core/src/panicking.rs:92:14 + 2: core::panicking::panic + at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b/library/core/src/panicking.rs:50:5 + 3: tokio::time::driver::entry::StateCell::mark_pending + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/time/driver/entry.rs:174:13 + 4: tokio::time::driver::entry::TimerHandle::mark_pending + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/time/driver/entry.rs:591:15 + 5: tokio::time::driver::wheel::Wheel::process_expiration + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/time/driver/wheel/mod.rs:251:28 + 6: tokio::time::driver::wheel::Wheel::poll + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/time/driver/wheel/mod.rs:163:21 + 7: tokio::time::driver::::process_at_time + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/time/driver/mod.rs:269:33 + 8: tokio::time::driver::::process + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/time/driver/mod.rs:258:9 + 9: tokio::time::driver::Driver

::park_internal + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/time/driver/mod.rs:247:9 + 10: as tokio::park::Park>::park + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/time/driver/mod.rs:398:9 + 11: as tokio::park::Park>::park + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/park/either.rs:30:29 + 12: ::park + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/driver.rs:198:9 + 13: tokio::runtime::park::Inner::park_driver + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/park.rs:205:9 + 14: tokio::runtime::park::Inner::park + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/park.rs:137:13 + 15: ::park + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/park.rs:93:9 + 16: tokio::runtime::thread_pool::worker::Context::park_timeout + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/thread_pool/worker.rs:422:13 + 17: tokio::runtime::thread_pool::worker::Context::park + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/thread_pool/worker.rs:398:20 + 18: tokio::runtime::thread_pool::worker::Context::run + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/thread_pool/worker.rs:328:24 + 19: tokio::runtime::thread_pool::worker::run::{{closure}} + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/thread_pool/worker.rs:303:17 + 20: tokio::macros::scoped_tls::ScopedKey::set + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/macros/scoped_tls.rs:61:9 + 21: tokio::runtime::thread_pool::worker::run + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/thread_pool/worker.rs:300:5 + 22: tokio::runtime::thread_pool::worker::Launch::launch::{{closure}} + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/thread_pool/worker.rs:279:45 + 23: as core::future::future::Future>::poll + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/blocking/task.rs:42:21 + 24: tokio::runtime::task::core::CoreStage::poll::{{closure}} + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/task/core.rs:235:17 + 25: tokio::loom::std::unsafe_cell::UnsafeCell::with_mut + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/loom/std/unsafe_cell.rs:14:9 + 26: tokio::runtime::task::core::CoreStage::poll + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/task/core.rs:225:13 + 27: tokio::runtime::task::harness::poll_future::{{closure}} + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/task/harness.rs:422:23 + 28: as core::ops::function::FnOnce<()>>::call_once + at /home/amos/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:322:9 + 29: std::panicking::try::do_call + at /home/amos/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:379:40 + 30: __rust_try + 31: std::panicking::try + at /home/amos/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:343:19 + 32: std::panic::catch_unwind + at /home/amos/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:396:14 + 33: tokio::runtime::task::harness::poll_future + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/task/harness.rs:409:19 + 34: tokio::runtime::task::harness::Harness::poll_inner + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/task/harness.rs:89:9 + 35: tokio::runtime::task::harness::Harness::poll + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/task/harness.rs:59:15 + 36: tokio::runtime::task::raw::poll + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/task/raw.rs:104:5 + 37: tokio::runtime::task::raw::RawTask::poll + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/task/raw.rs:66:18 + 38: tokio::runtime::task::Notified::run + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/task/mod.rs:171:9 + 39: tokio::runtime::blocking::pool::Inner::run + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/blocking/pool.rs:278:17 + 40: tokio::runtime::blocking::pool::Spawner::spawn_thread::{{closure}} + at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.4.0/src/runtime/blocking/pool.rs:258:17 +note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. +``` + + +> Well, precisely because that's a **heap allocation**! If you're holding a Sleep on the stack, and you pass it around to another function, or store it in a struct, or whatever, it actually moves — its address changes. +> +> But if you're holding a Box, well, then you're only holding a pointer to a Sleep that lives somewhere in heap-allocated memory. That somewhere will never change, ie. **the Sleep itself will never move**. The pointer to Sleep can be passed around, and everything is fine. + +-------------- + + +## Tokio#spawn要求Future必须是Pin + +https://github.com/tokio-rs/tokio/blob/6b9bdd5ca25bd3f30589506de911db73f7dbf8b4/tokio/src/runtime/task/core.rs#L221 + +整个Task都被分配到heap,所以不会move,需要poll future时通过下面 + +```rs + +// Safety: The caller ensures the future is pinned. +let future = unsafe { Pin::new_unchecked(future) }; +``` + +可获得Pin + +## Pin on stack + +为什么要记录下这个呢?是因为看 shadowsocks-rust时使用的 pin! 里面竟然是 `new_unchecked()` + +https://github.com/shadowsocks/shadowsocks-rust/blob/49b7004d7565de98a24b49657443b19c03365a8f/bin/ssserver.rs#L304 + +所以就好奇,为什么可以直接用 `new_unchecked` 函数。 + +tokio::pin!用的 new_unchecked 并不在乎到底Pin的是在stack还是heap,如果指针指向stack,就是pin在stack,指向heap,就是pin在heap。 + +tokio::pin!有意思的点是,`let mut $x = $x` 先shadow掉原来的 $x,确保 $x 不会被别的变量持有。 + +最后Pin住。Pin 根本就不在乎是 stack 还是 heap,Pin只是代表不能被 move, + +> pinning makes sense anywhere +> pinning just means it doesn't move + +相当于,你给我 T,最后我返还给你 `Pin<&mut T>`,由于 Pin<&mut T> 限制地更严,所以安全。(从宽松到严格往往是容易的) + +tokio::pin好处是,你完全可以 safely 使用 pin。 而不需要你自己的代码写 unsafe + + +https://discord.com/channels/500028886025895936/500336333500448798/846016916957954058 + +```rs +#[macro_export] +macro_rules! pin { + ($($x:ident),*) => { $( + // Move the value to ensure that it is owned + // 我们是 ownership + let mut $x = $x; + // Shadow the original binding so that it can't be directly accessed + // ever again. + // 原来的 x 被我们 shadow 掉了,x 变成 Pin + #[allow(unused_mut)] + let mut $x = unsafe { + $crate::macros::support::Pin::new_unchecked(&mut $x) + }; + )* }; + ($( + let $x:ident = $init:expr; + )*) => { + $( + let $x = $init; + $crate::pin!($x); + )* + }; +} +``` + +## Pin projection 和 structural Pinning + +大体意思是,对于一个结构体Struct,当Struct被Pin住时,这个Pin的约束对哪个字段起作用? +如果某个字段被move并不会引起问题,那 `Pin<&mut Map>` 可以转换为 `&mut f` +如果某个字段 future 必须被Pinned,那 `Pin<&mut Map>`就是约束他(future)的,而非其他字段。 + +其他字段是否被move,并不会引起unsound。 + +```rs +struct Map { + future: Fut, + f: Option +} + +// 默认每个字段都必须是Unpin,Map才会是Unpin +// 我们主动为Map实现Unpin +// 当 Fut 是Unpin时,整个结构就是Unpin +// 为什么不在乎f Unpin与否? +// 因为f是否Unpin,和是否被move,并不会导致future#poll出问题,可以忽略f +// +impl Unpin for Map {}; + +impl Future for Map +where Fut: Future, + F: FnOnce(Fut::Output) -> T +{ + unsafe_pinned!(future: Fut); + unsafe_unpinned!(f: Option); + + type Output = T; + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.as_mut().future().poll(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(output) => { + let f = self.f().take().expect("take must success"); + Poll::Ready(f(output)) + } + } + } +} +``` + +pin_project crate 就是做这个的,通过在字段上标注 `#[pin]`进行局部Pin,struct是Pin的,有 `#[pin]`的字段也是Pin的,其他字段是否被move没有问题 + + +当字段被Pin住,成为 `Pin<&mut Field>`,就不应该将 Field 移出去。这里要说明的是,移出去不一定错,Pin从设计上说不能move而已。只要将他假设为,虽然被Pin住,但还是可以被move。 + + +## 翻译 https://doc.rust-lang.org/nightly/core/pin/index.html#projections-and-structural-pinning + +### Projections and Structural Pinning 映射和结构化Pinning + +名词解释 + +1. projection:映射,投影。提供一个方法将 `Pin<&mut Struct>` 转换成 `Pin<&mut Field>` +2. structural pinning: 结构pinning。如果整个struct是Pin,那每个成员也都是Pin。比如现存在 `Pin<&mut Struct>`类型,则此刻 Struct 里的每个字段都必须是 `Pin<&mut FieldABCDEFG>` + + +当使用pinned结构时,我们如果获取方法参数中类型是 `Pin<&mut Struct>`的内部结构成员?通常办法是写一个工具函数(叫做 projection),将 `Pin<&mut Struct>`转换成对内部字段的引用。但这引用应该是 `Pin<&mut Field>` 还是 `&mut Field`?实际上当获取Enum,或容器类型(`Vec`, `Box`, `RefCell`)内部字段的引用时,也会有上面的疑问。(这疑问涉及到可变和共享引用两者,我们只是单拎出来可变引用加以说明)。 + +实际上是否将某个字段的pinned projection转换为 `Pin<&mut Struct>`转换成 `Pin<&mut Field>` 还是 `&mut Field` 取决于 Struct 结构体的提供者。这种转化是有约束的,最重要的约束时一致性。Struct的每个字段两个取其一,只能被映射成为 pinned reference 或者 &mut Field。如果同时映射为上述两者,那可能是 unsound. + +作为数据结构的作者,你必须决定每个字段是否能从 `Pin<&mut Struct>`转换为 `Pin<&mut Field>`。pinning的传递(即从 `Pin<&mut Struct>`转换为 `Pin<&mut Field>`)称为结构化(structural)。因为他衍随了原来外层的类型(我自己的理解,`Pin<&mut Struct>`=> `Pin<&mut Field>`,只是内层T变化,作为最外层的 `Pin`不变,也即,始终是Pin的)。在下面的章节,我们分别解释两种选择的考量 + +> 哪两者?分别是 +> 1. `Pin<&mut Struct>` => `&mut Field`,称为 not structural(Pinning is not structural for `field`) +> 2. `Pin<&mut Struct>` => `Pin<&mut Field>`,称为 structural(Pinning is structural for `field`) + +#### Pinning is not structural for `field` + +一个被Pin住的Struct `Pin<&mut Struct>`,其内部字段竟然不是Pinned,看着很反直觉,但通常这是最简单的选择,如果`Pin<&mut Field>`从未被创建,那绝对不可能出错!所以,如果你认为某些字段没必要有 `结构化pinning约束(structural pinning)`,你需要确保的是,你自始至终都不会创建对这些字段的 pinned reference,也即,不会创建 `Pin<&mut Field>`。 + +没有结构化pinning的字段可以通过映射方法,将 `Pin<&mut Struct>`转换为 `&mut Field`。 + +```rs +impl Struct { + fn pin_get_field(self: Pin<&mut Self>) -> &mut Field { + // This is okay because `field` is never considered pinned. + unsafe { &mut self.get_unchecked_mut().field } + } +} +``` + +你设置可以 `impl Unpin for Struct` 即使字段 field 不是 Unpin. + +> What that type thinks about pinning is not relevant when no Pin<&mut Field> is ever created. +> 这句话我不会翻译,但我这么理解:一定要区分开约束是编译期(是编译期,不是编译器,没打错)的施加还是不这样做一定会出错 +> Pin只是编译期的约束,是为了防止出错,详细介绍看本文上部分 `Pin的作用是防止move,但如果程序员小心处理,那就不会出错。为什么还需要Pin呢?xxxx`。 + +> 解释,由于 field 被Pin与否都不会影响逻辑,所以其他需要被Pin的都实现了Unpin,那整个结构从原则上就是 Unpin,但编译器只会对全部字段都是Unpin的 Struct才会自动实现Unpin,所以这种情况下需要开发者手动声明 Struct 为 Unpin + +#### Pinning is structural for `field` + +另一个选择是决定 pinning对字段是结构化的,这意味着,如果struct被pinned,field也会。 + +通过这可以编写映射创建 `Pin<&mut Field>`,如下所示,field是pinned + +```rs +impl Struct { + fn pin_get_field(self: Pin<&mut Self>) -> Pin<&mut Field> { + // This is okay because `field` is pinned where `self` is. + unsafe { self.map_unchecked_mut(|s| &mut s.field) } + } +} +``` + +然而,为了结构化pinning,需要满足几个条件: + +1. 如果全部结构化field都是Unpin,那struct这容器才会是Unpin。这其实是编译器默认的行为,但Unpin是safe trait,所以编写 struct 的作者需确保不添加 `impl Unpin for Struct`(值得注意的是,像上面 `pin_get_field`添加将`Pin<&mut Self>` 映射为 `Pin<&mut Field>`需要unsafe代码块,但实现Unpin本身是safe的,如果你通过unsafe完成上述映射,就必须仔细考虑Unpin的添加与否。) + +> 为什么要特别强调Unpin? +> Unpin会逃脱 Pin 的限制,从而获取 `&mut Field`,将 `Pin<&mut Self>`转换成 `Pin<&mut Field>`的过程中蕴含着一层假设,即Self不会被move。但对Struct实现Unpin后会使得Self 成为Unpin,Unpin可以逃离Pin的限制,这很危险(只是危险,不一定会出错,如果你足够牛逼,仔细处理各种边界case也没问题,只不过没有编译器约束你罢了) + + +2. struct的析构函数不能将结构化pin的字段move出去。这是[上一节](https://doc.rust-lang.org/nightly/core/pin/index.html#drop-implementation)着重陈述的一点: `drop` 接受 `&mut self`参数,但 struct和他内部的字段可能刚才被Pin住了。你必须保证 Drop 实现中不会move内部字段。尤其上面介绍的,这意味着你的struct一定不能是`#[repr(packed)]`。浏览[上一节](https://doc.rust-lang.org/nightly/core/pin/index.html#drop-implementation) 学习如何编写 drop,让编译器帮助你别意外违反 pinning. + + +3. 你必须确保遵守 [drop 保证*(drop guarantee)](https://doc.rust-lang.org/nightly/core/pin/index.html#drop-guarantee)。一旦你struct被pinned,在没调用struct析构函数之前,struct的那片内存必须不被覆盖,不会释放(否则就是UB,这比较好理解)。这可能会很难办,正如 `VecDeque`遇到的那样: 如果容器内的元素的析构函数panic,那 `VecDeque` 的析构函数调用就会失败。这违反了 `drop` 保证(drop guarantee),因为这会导致容器内元素明明析构函数未被调用,但他们的内存却被释放(deallocated)。所幸`VecDeque`并没有 pinning 映射(projections),所以上述情况不会导致 `unsoundness` + +4. 你一定不能在struct被pinned期间,提供任何可能导致 structural field 被move出去的方法。例如,如果 struct 包含 `Option`,恰好有个take方法,其函数形参为 `fn(Pin<&mut Self>) -> Pin<&mut T>`,这 take 方法可被用来将 T 从 pinned 的 `Struct` move出去 + +这意味着 pinning 不能用于 结构化pin住 持有数据的这个字段 +> 上面这句话有些生硬。换成英文感受下 +> +> which means pinning cannot be structural for the field holding this data. +> 意思是,Struct 的字段明明被 pinning 了,结果你提供方法将 `Pin<&mut Struct>` 转换为 `Option`,使得 T 逃脱了 Pin 的约束,这显然是**错误**的 + +再举一个更复杂的,将数据从已经被 pinned 的结构 move 出去的例子。想象下如果 `RefCell`有个 `fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T>`方法,那我们可以这么玩 + +```rs +fn exploit_ref_cell(rc: Pin<&mut RefCell>) { + { let p = rc.as_mut().get_pin_mut(); } // 将 Pin<&mut RefCell> 转换为 Pin<&mut T> + let rc_shr: &RefCell = rc.into_ref().get_ref(); // Pin<&mut T> => &RefCell + let b = rc_shr.borrow_mut(); // RefCell本身有 borrow_mut() 方法,可以通过 &self 获得 &mut self。 + // https://doc.rust-lang.org/std/cell/struct.RefCell.html#method.borrow_mut + // RefCell借助UnsafeCell,将 &self 转为 &mut self + let content = &mut *b; // reborrow下,最终获取 &mut T + // 通过上述 "努力" ,将 Pin<&mut Ref> 一步步转化成 &mut T,逃脱了Pin的限制 + // 获得 &mut T后,就能将 T 从 Pin<&mut RefCell> move出去 +} +``` + +**翻译完上面之后,总结一句话** + +> Pin作为rust zero cost的其中一例,只是编译期的约束。而如果你足够牛逼,仔细处理各种边界case,哪怕Pin住,通过unsafe随便玩,随便move都没问题。但为了绝对正确性,我们认为在 `Pin` 期间利用各种手段将 T move 出去,是**绝对错误**的 + +---------------------------- + +重点看下面这连接,最后两个作为补充即可 + +> https://stackoverflow.com/questions/56058494/when-is-it-safe-to-move-a-member-value-out-of-a-pinned-future + + +https://github.com/rust-lang/futures-rs/blob/0.3.0-alpha.15/futures-util/src/future/map.rs#L36-L45 + +https://doc.rust-lang.org/nightly/core/pin/index.html#projections-and-structural-pinning \ No newline at end of file diff --git "a/_posts/55-Async\347\212\266\346\200\201\346\234\272\345\256\236\347\216\260.md" "b/_posts/55-Async\347\212\266\346\200\201\346\234\272\345\256\236\347\216\260.md" new file mode 100644 index 0000000..aee9291 --- /dev/null +++ "b/_posts/55-Async\347\212\266\346\200\201\346\234\272\345\256\236\347\216\260.md" @@ -0,0 +1,328 @@ +--- +title: Async状态机实现 +date: 2021-06-18 +updated: 2021-06-18 +issueid: 55 +tags: +- Rust +--- +## Rust中的状态机实现 + +我概括一下,引入 Future 必须解决自引用被swap的问题 + +Pin通过 提供 &Self 来让编译器进行入参检查,mem::swap(& mut self) 需要 mut 类型,所以编译器类型检查失败而退出 + +[](https://cloud.tencent.com/developer/article/1628311) + +https://rust-lang.github.io/async-book/04_pinning/01_chapter.html + +Future 任务会跨越线程执行,我们知道Future可以编译成状态机yield执行,那在从Future Pending 到 Done 的过程中需要将全部的状态统一保存起来。 + +这里最好的办法是 struct,也即 async 中所有的变量都在 struct 上分配,并通过self引用。 + +整个 Future 执行期间全部的变量都通过 enum 进行保存。 + + +## Future 是如何保存执行状态的? + +编译之前 + +```rs +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; +use std::time::{Duration, Instant}; + +struct Delay { + when: Instant, +} + +impl Future for Delay { + type Output = &'static str; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) + -> Poll<&'static str> + { + if Instant::now() >= self.when { + println!("Hello world"); + Poll::Ready("done") + } else { + // Ignore this line for now. + cx.waker().wake_by_ref(); + Poll::Pending + } + } +} + +#[tokio::main] +async fn main() { + let when = Instant::now() + Duration::from_millis(10); + let future = Delay { when }; + + let out = future.await; + assert_eq!(out, "done"); +} +``` + +编译之后 +```rs +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; +use std::time::{Duration, Instant}; +// 每次返回的Future都会存放到 enum 结构体中。这样在Future未结束之前Future会在 enum 持久化 +enum MainFuture { + // Initialized, never polled + State0, + // Waiting on `Delay`, i.e. the `future.await` line. + State1(Delay), + // The future has completed. + Terminated, +} + +impl Future for MainFuture { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) + -> Poll<()> + { + use MainFuture::*; + + loop { + match *self { + State0 => { + // 这些代码并不会执行多次 + // https://discord.com/channels/500028886025895936/500336333500448798/854955299522084914 + let when = Instant::now() + + Duration::from_millis(10); + // 看编译前的代码 + // 由于when在.await使用,编译器自动将when保存到enum + // 所以不用担心async function调用返回后stack里的变量没了 + // 编译器会分析出来,帮你处理 + let future = Delay { when }; + *self = State1(future); + } + State1(ref mut my_future) => { + match Pin::new(my_future).poll(cx) { + Poll::Ready(out) => { + assert_eq!(out, "done"); + *self = Terminated; + return Poll::Ready(()); + } + Poll::Pending => { + return Poll::Pending; + } + } + } + Terminated => { + panic!("future polled after completion") + } + } + } + } +} +``` + +比如如下async代码块 + +```rs +async { + let mut x = [0; 128]; + let read_into_buf_fut = read_into_buf(&mut x); + read_into_buf_fut.await; + println!("{:?}", x); +} +``` + +下面是desugar的代码 + +```rs +struct AsyncFuture { + x: [u8; 128], + read_into_buf_fut: ReadIntoBuf + state: State, +} + +enum State { + Start, + AwaitingReadIntoBuf, + Done +} + +struct ReadIntoBuf { + buf: &'a mut [u8] // 可能被实现为原始指针,这儿只是为了方便描述 +} +``` +会被rustc编译成Future对象,并且跨.await的变量被封装到struct保存,通过生成state维护状态机的状态 + +并不是全部的local variable 都会被保存,rustc只会保存await需要的变量,这些变量特点是await返回后就会被销毁,所以需要保存到struct +https://discord.com/channels/500028886025895936/500336333500448798/854955299522084914 + +由于保存变量状态, poll 中获得这状态通过变量引用访问,所以struct往往会保存变量的引用,这就使得 future 不能被移动,所以编译器会为生成的 future 状态机增加 PhantomPinned,使得 future 是 !Unpin + +https://os.phil-opp.com/async-await/#pinning-and-futures + +也正是由于 future !Unpin,为了再次 poll 这个future,必须重新 Pin 住 + +所以 shadowsocks-rust 这里重新Pin住 + +https://github.com/willdeeper/shadowsocks-rust/blob/6ff4f5b04b8e22d36cd54477cfcadf8cb403de21/bin/sslocal.rs#L463 + +虽然看着 server 是 stack local,但 async 会将其保存到 struct,确保pin的整个过程中server始终存在。 + +https://os.phil-opp.com/async-await/#stack-pinning-and-pin-mut-t + +> 实际上,我并没有找到具体文档说,rustc实现的 future 一定会 opt out Unpin,使其 !Unpin。 +> 但根据我的理解,既然 future 翻译成的状态机是自引用结构,那就不允许move。自然就需要为其 !Unpin +> 从下面的回答可证明是自引用结构 +> https://users.rust-lang.org/t/is-it-possible-to-de-sugar-async-with-normal-rust-syntax/37789/3 +> 至于说的 !Unpin 是我的推论,不过我认为我推论是正确的。 + +---------------- +https://os.phil-opp.com/async-await/#saving-state-1 + +> Language-supported implementations of cooperative tasks are often even able to backup up the required parts of the call stack before pausing. As an example, **Rust's async/await implementation stores all local variables that are still needed in an automatically generated struct** +----------- + +Rust没找到在线编辑器看编译后的代码,但stackless理论中,js和rust最接近,后面不不明白,可以看js编译后是如何利用函数闭包保存状态的 + +> https://babeljs.io/repl#?browsers=ie%206%0A&build=&builtIns=false&corejs=3.6&spec=false&loose=false&code_lz=NwCghgzgngdgxgAgGYFd4BcCWB7GCQCUCA3gFACQqGOeEANgKYMAOIAthEWQj7wE4N0KPnhgMA7ggAKfbG0wQGIARGx0AbgwQBeAHwJF6ACqY2DbCnTKGqjQwA0CDgQLBSvBAF9S73pFiIVHBYuAgAJgxs2IQkvh5wuLYMAHR02ADmIABEHOkAjFmucbwJMBDoCGA6CACsbh48jBUARtVg4mCYFfRMrHkADINFDQgA9KMIgBexlR1dCICo-oAUroBhcmDFPOMIgNDugGj-gIvRgF-KgCFugMdygIAegEAMVYDVEYC3qYCgyoCAOoAU6oC_ioAOpoAA5oBQcoBCOutjCaATfjAOwWgHvlQCncoBd-UAGtqANqdAIAGgEP5QD2BgDNoBlfQ-gFjFQCE1tdAMAxi3RE0AWPKAfdjAPFpBnEXTgAAsEIBt-MApoqAaDlAABygCo5REkhDPQAuplzKtB4AgPoBaOUAMP-AXzDAGyOgCxNXFEmadCqAf3lAOn6_jF5TAcAA1oAV-MAe2p8wBi8ojANOagAm_QC_Cc8giEYEcHhLYXy2YAjdKBgFPlQC70YBAyMADdGAeXk-TrEHrDYA3PUAsHKAejNAIhGgBu5QAceoAX6KOHyJgAgLKWATgtADTmgEYdRM54kjTb7QAWaqTAHYeUP2x3OsLe7wegEAGBgwFBsTvoPgoYIA0pJVIZbK5ABMAC4EFkEABqSrDXjeYrtNXhSLRIqeAiEYBAA&debug=false&forceAllTransforms=true&shippedProposals=false&circleciRepo=&evaluate=true&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=es2017%2Ces2015-loose&prettier=false&targets=&version=7.14.1&externalPlugins= + + +## 为什么说 Pin 是零开销? + +Pin! 的内容会分配到堆上防止移动,对于自引用结构为了防止移动一定会内存**分配到堆上**,零开销的意思是不会进行**多余的**堆分配。 + +mem::swap 要求 参数为 `&mut T`,只要我们保证不泄漏出 mut 就可保证用户代码不会 move 内存造成**不安全编程** + +除非显式 !Unpin, 否则编译器会自动实现Unpin,也就是Rust里默认变量是可以移动的 + +比如下面这代码 +https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=636b71d4acef346a9ce810b735908cb1 + +https://stackoverflow.com/a/49917903/7529562 + +## Javascript Promise 和 Rust Future 的不同 +JavaScript 的 Promise 调用可能会返回新的 Promise,而Rust的Future只会返回 Pending 或 Ready,并不会返回新的 Future + +Future在Rust中是块写法 + +```rs +thread::spawn(async { + +}); +``` + +这 async 编译为 Future,Future::poll返回 `Pending / Ready`,想孵化新的异步任务需调用运行时提供的孵化方法,而不是通过返回 Future + +```rs +tokio::spawn(async { + // spawn 孵化新的Future + let join_hander = tokio::spawn(async { + + }); + // future#poll 会先返回 join_hander 的完成状态 + join_hander.await?; +}); +``` +## JS中的状态机 + +我将用 Javascript 来演示 async 如何实现。 async 原理类似 C# 的 async Task,Rust中的 ?. Future + + +async 本质是依赖 回调函数 + +```js +async function fetchDataFromRemote(path) { + const url = 'https://' + path; + const r1 = await api.get(url) + const r2 = await api.getFrom(url) + return [r1,r2] +} + +const data = await fetchDataFromRemote('api.github.com/repos/tokio-rs/mini-redis/languages') +``` + +```js +fetchDateFromRemote() { + const url = 'https://' + path; + let state = 0; + let finalState = 2; + let waitFor = null + switch(state) { + case 0: { + waitFor = api.get(url); + return waitFor; + } + } +} +``` + +又或者下面从babel拷贝过来的代码 + +```js +async function getActive(keyId: string) { + if (typeof itemId === 'string') { + const r = await (await api.items.get(keyId, itemId)).data; + setDefault(r.value); + setFields({ + key: 'value', + value: r.value + }); + return; + } + const k = await (await api.keys.get(keyId)).data; + if (Number.parseInt(k?.active) !== -1) { + const active_item = await (await api.items.get(keyId, itemId)).data + + setDefault(active_item.value); + setFields({ + key: 'value', + value: active_item.value + }); + } +} +``` + +```js +function getActive(keyId) { + return __awaiter(this, void 0, void 0, function () { + var r, k, active_item; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!(typeof itemId === 'string')) return [3 /*break*/, 3]; + return [4 /*yield*/, _api__WEBPACK_IMPORTED_MODULE_5__["default"].items.get(keyId, itemId)]; + case 1: return [4 /*yield*/, (_a.sent()).data]; + case 2: + r = _a.sent(); + setDefault(r.value); + setFields({ + key: 'value', + value: r.value + }); + return [2 /*return*/]; + case 3: return [4 /*yield*/, _api__WEBPACK_IMPORTED_MODULE_5__["default"].keys.get(keyId)]; + case 4: return [4 /*yield*/, (_a.sent()).data]; + case 5: + k = _a.sent(); + debugger; + if (!(Number.parseInt(k === null || k === void 0 ? void 0 : k.active) !== -1)) return [3 /*break*/, 8]; + return [4 /*yield*/, _api__WEBPACK_IMPORTED_MODULE_5__["default"].items.get(keyId, itemId)]; + case 6: return [4 /*yield*/, (_a.sent()).data]; + case 7: + active_item = _a.sent(); + setDefault(active_item.value); + setFields({ + key: 'value', + value: active_item.value + }); + _a.label = 8; + case 8: return [2 /*return*/]; + } + }); + }); + } +``` + + diff --git "a/_posts/56-Rust\347\224\237\345\221\275\345\221\250\346\234\237.md" "b/_posts/56-Rust\347\224\237\345\221\275\345\221\250\346\234\237.md" new file mode 100644 index 0000000..206a65f --- /dev/null +++ "b/_posts/56-Rust\347\224\237\345\221\275\345\221\250\346\234\237.md" @@ -0,0 +1,204 @@ +--- +tags: +- Rust +title: Rust生命周期 +date: 2021-07-01 +updated: 2021-07-03 +issueid: 56 +--- +# 生命周期的抽象 + +**将LT想象成scope不太容易理解,可以将其想象成链。标注同一个的引用必须共存亡。通过 'a, 多个引用链在一起。** +![将引用比作绳子]](https://user-images.githubusercontent.com/24750337/114650520-fb8a8c80-9d14-11eb-93a0-3ee191ff4938.png) + +https://doc.rust-lang.org/stable/book/ch10-03-lifetime-syntax.html +https://www.zhihu.com/question/30861652/answer/132841992 + +# 为什么生命周期被如此设计 + +跨函数的变量生存期分析及其复杂,要分析完成各种条件语句,且需要的值只有在运行时才能确认,这就加大了编译器分析的复杂度(又可认为不可能进行分析),Rust通过在函数,结构体上进行生命周期标注,将分析的范围限定到函数内部,从而完成整个分析的过程。这就是为何生命之后需要在 函数, 结构体 上进行 `'a` 标注 + +[官方文档上有过类似的解释](https://doc.rust-lang.org/stable/book/ch10-03-lifetime-syntax.html) +> lifetime syntax is about connecting the lifetimes of various parameters and return values of functions. Once they’re connected, Rust has enough information to allow memory-safe operations and disallow operations that would create dangling pointers or otherwise violate memory safety. + +## 生命周期和临时借用 + +### 复现 + +```rs +struct NumRef<'a> (&'a i32); + +impl<'a> NumRef<'a> { + fn some_method(&'a mut self) { + + } +} + +fn main() { + let mut num_ref = NumRef(&5); + num_ref.some_method(); // #1 + num_ref.some_method(); // #2 + println!("{:?}", num_ref); +} +``` + +上面将 some_method 的 self 和整个 NumRef的 'a 关联,这意味着 some_method只能被调用一次。 +rustc调用 #1 时借用了 self,由于 `&'a mut self`,所以 #1 调用完后也不能释放掉这次借用,第二次调用 some_method 出现第二次借用。 + +### 纠正 + +```rs +struct NumRef<'a> (&'a i32); + +impl<'a> NumRef<'a> { + fn some_method(&mut self) {} + + // 上面省略了LT,desugar 后是 + fn some_method_desugared<'b>(&'b mut self){} +} + +fn main() { + let mut num_ref = NumRef(&5); + num_ref.some_method(); + num_ref.some_method(); + println!("{:?}", num_ref); +} +``` +'b 和整个 struct 的 'a 无关,所以some_method的可变借用不需要和整个struct对齐,第二次调用可重新借用。 + +通过上面例子说明,代码能编译通过只是确保内存安全,生命周期的标注不一定是对的。 + +以上来自 + +> https://github.com/pretzelhammer/rust-blog/blob/master/posts/common-rust-lifetime-misconceptions.md#5-if-it-compiles-then-my-lifetime-annotations-are-correct + +## 生命周期省略 '_ + +https://doc.rust-lang.org/stable/book/ch10-03-lifetime-syntax.html#lifetime-elision + +> The first rule is that each parameter that is a reference gets its own lifetime parameter. In other words, a function with one parameter gets one lifetime parameter: fn foo<'a>(x: &'a i32); a function with two parameters gets two separate lifetime parameters: fn foo<'a, 'b>(x: &'a i32, y: &'b i32); and so on. +>The second rule is if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters: fn foo<'a>(x: &'a i32) -> &'a i32. +>The third rule is if there are multiple input lifetime parameters, but one of them is &self or &mut self because this is a method, the lifetime of self is assigned to all output lifetime parameters. This third rule makes methods much nicer to read and write because fewer symbols are necessary. + +### 生命周期何时可以省略呢? +**首先明确一点,Rust中每个引用都必须有生命周期,如果没写且编译通过,那属于生命周期省略** + +生命周期下面简称 LT +1. 先将每个函数参数都标注唯一的 LT, 'a, 'b, 'c, 'd +2. 如果入参只有一个LT,显而易见, 出参引用的生命周期一定会个 入参的 LT一致,也即,一个 入参引用 和 全部的出参 LT一样 +3. 如果入参多个LT,且是成员函数(&self 或 &mut self),那全部的出参LT都和(&self 或 &mut self)一致。 + +上述三个规则结束后,如果还有 出参 的LT没被确定,就必须**显式**添加LT标注 + +如果只有入参有引用,返回类型没引用,不需要标注生命周期。为什么呢?因为LT存在是防止空指针引用,如果返回值没引用函数内部的内存,就不会出现这个问题(单线程下)。 + +这个是官方的例子 +https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5c4baf83eac0666c47ef3b7b2ec027d9 + +下面的是我改的 +https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3ae5b85c9f35375f866ad3ddfa3bee47 + + +```rs +struct Spawner{ + +} + +impl fmt::Debug for Spawner { + // fmt::Formatter 结构体又生命周期标注 + // struct Formatter<'a> + // '_ 意思是我传递一个生命周期,这个周期以你Formatter<'a>为准 + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("Spawner").finish() + } +} +``` + +## 'static 生命周期 +当 struct 引用其他内存时需要生命周期标识 + +'static 告诉编译器,这个 name 生命周期比 Person 结构体 无限长。 + +https://www.reddit.com/r/rust/comments/o9w6rl/rust_traits_and_static/h3dszio?utm_source=share&utm_medium=web2x&context=3 + +生命周期只是个标注,编译器根据标注进行生命周期推导,如果发现对不上还是会报错。 + +```rs +struct Person { + name: &'static str, +} + +impl Person { + fn new(name: &'static str) -> Person { + Person { name: name } + } +} +fn main() { + let nobody: Person = Person::new("nobody"); +} +``` + +曾以为生命周期随便写也没关系。当你创建或者返回具有生命周期标识的struct时也必须在创建、赋值、返回时指明声明长度让编译器推导 + +比如下面 +```rs +pub(crate) struct BasicScheduler { + /// Inner state guarded by a mutex that is shared + /// between all `block_on` calls. + inner: Mutex>>, + + /// Notifier for waking up other threads to steal the + /// parker. + notify: Notify, + + /// Sendable task spawner + spawner: Spawner, +} +struct InnerGuard<'a, P: Park> { + inner: Option>, + basic_scheduler: &'a BasicScheduler

, +} +impl BasicScheduler

{ + // InnerGuard struct有周期标注,所以你返回时也必须进行标注 + // 这里还特殊的一点是,P 是个泛型,实现 Park 这个接口(trait) + // <> 里同时带了 生命周期标注 和 泛型参数 + fn take_inner(&self) -> Option> { + Some(InnerGuard { + inner: Some(inner), + basic_scheduler: &self, + }) + } +} +``` + +生命周期只是个注解Annotation,告诉编译器你的引用存活到什么时间,但如果编译器推导出的生命长度与你的标注不同,编译器并不会帮你延长变量的存活期,而是会complain,编译出错让你再检查 +https://www.zhihu.com/question/30861652/answer/132841992 + +## https://doc.rust-lang.org/stable/book/ch10-03-lifetime-syntax.html#lifetime-annotations-in-function-signatures + +当在函数上标注生命周期时,总是在函数签名,而不是函数体标注。在函数内部的变量编译器可以很轻松的分析,但对于引用的外部的变量,或者从函数参数传进来的变量,编译器无法自己分析,需要手动标注生命周期。 + +> When annotating lifetimes in functions, the annotations go in the function signature, not in the function body. Rust can analyze the code within the function without any help. However, when a function has references to or from code outside that function, it becomes almost impossible for Rust to figure out the lifetimes of the parameters or return values on its own. The lifetimes might be different each time the function is called. This is why we need to annotate the lifetimes manually. + + +你如何标注生命周期取决于你的函数在做什么。如果我们将 longest 函数改为总是返回第一个入参,那我们不需要在y上标注。 + +比如 + +```rs +// 返回参数只和 x 有关 +fn longest<'a> (x: &'a str, y: &str) -> &'a str { + x +} +``` +在上面例子中,只为 x 和返回值标注生命周期参数,这是因为 y 和 x,返回值的生命周期没有任何关系 + +## 下面这段话很重要 + +当从函数中返回引用时,返回值的生命周期参数**必须匹配**函数入参的其中之一。如果返回的引用和任何一个入参都没有关系,那返回值一定引用了在函数内创建的变量,这非常危险,因为离开函数作用域之后被引用的值会被销毁 + +> When returning a reference from a function, the lifetime parameter for the return type needs to match the lifetime parameter for one of the parameters. If the reference returned does not refer to one of the parameters, it must refer to a value created within this function, which would be a dangling reference because the value will go out of scope at the end of the function. Consider this attempted implementation of the longest function that won’t compile: + +编写函数,结构体时,最简单的生命周期标注是给全部的引用都加上相同生命周期,这样最起码可以确保万无一失 + +如果函数某个引用&c并不和其他引用有'a的关系,可以给&c添加 'b,不过这种情况很少出现 \ No newline at end of file diff --git "a/_posts/57-\350\256\260\345\275\225 Tun \351\200\217\346\230\216\344\273\243\347\220\206\347\232\204\345\244\232\347\247\215\345\256\236\347\216\260\346\226\271\345\274\217\357\274\214\344\273\245\345\217\212\345\246\202\344\275\225\351\201\277\345\205\215 routing loop.md" "b/_posts/57-\350\256\260\345\275\225 Tun \351\200\217\346\230\216\344\273\243\347\220\206\347\232\204\345\244\232\347\247\215\345\256\236\347\216\260\346\226\271\345\274\217\357\274\214\344\273\245\345\217\212\345\246\202\344\275\225\351\201\277\345\205\215 routing loop.md" new file mode 100644 index 0000000..f44d1eb --- /dev/null +++ "b/_posts/57-\350\256\260\345\275\225 Tun \351\200\217\346\230\216\344\273\243\347\220\206\347\232\204\345\244\232\347\247\215\345\256\236\347\216\260\346\226\271\345\274\217\357\274\214\344\273\245\345\217\212\345\246\202\344\275\225\351\201\277\345\205\215 routing loop.md" @@ -0,0 +1,484 @@ +--- +date: 2021-08-01 +updated: 2023-09-13 +issueid: 57 +tags: +- net +title: 记录 Tun 透明代理的多种实现方式,以及如何避免 routing loop +--- +从tun读出来后再写入tun,下次读还会将自己刚写入的packet读出来,如果设置默认路由是tun网卡,会导致死循环。下文会介绍解决routing loop的多种方法 + + + +> How does Virtual network device actually work ? +> Virtual network device can be viewed as a simple Point-to-Point or +> Ethernet device, which instead of receiving packets from a physical +> media, receives them from user space program and instead of sending +> packets via physical media sends them to the user space program. +> +> Let's say that you configured IPv6 on the tap0, then whenever +> the kernel sends an IPv6 packet to tap0, it is passed to the application +> (VTun for example). The application encrypts, compresses and sends it to +> the other side over TCP or UDP. The application on the other side decompresses +> and decrypts the data received and writes the packet to the TAP device, +> the kernel handles the packet like it came from real physical device. + +一共两个方法, read 和 write + +read from tun读取数据包 + +write将数据包写入tun,tun直接将userspace的packet注入内核,就好像内核刚从物理网卡读取出来一样 + + + + + +**搞这些东西最好在虚拟机,搞坏了还能还原。** + +## NAT, 转发到socks server + +1. tun读取ip packet,本地启动TcpServer/UduServer +2. 解析出ip src, dest 信息 +3. 通过fake by ip,利用NAT,获取一个内部网段的唯一ip +4. 将此数据包的ip dest改为自身ip,并将dest port改为TcpServer 监听的端口 +5. TcpServer/UDPServer收到请求,根据预设的规则进行判断,最后决定将数据发给 socks server还是直连 + +这里的关键是ip packet的 dest port 指向了 TcpServer/UdpServer 监听的端口,通过这种方式,让 IP packet 重新走了一遍OS自身的 tcp/ip stack(好处是借用OS自己的协议栈 ),同时获取到 src ip 和 dest ip + +实现往往是通过 session 实现类似NAT的功能,packet走完OS自己的协议栈后,能够还原出original destination。 + +看下面 Go 和 Rust 的代码,代码是不是非常像? + + + + +类似还有最近(2021/08/10) shadowsocks-rust 搞的tun模式 + +随便在tun网段bind某个端口 tcp_daddr + + +拦截流量后查找connection,找到就将 dest 改为 tcp_daddr,这样我们不再需要自己的userspace tcp/ip stack,而是复用OS自身的网络栈。 + + +## 直接从Ip packet构建TcpListener + +leaf使用自己修改的lwip,从ip packet直接封装出 TcpListener,这里的难点是,TcpListener往往dest port是自身,而无法获取真正的 dest port,另外TcpServer只能收到bind的端口的请求。 + +leaf修改lwip代码后,使得任意端口的流量都会流经TcpListener + + + +现在clash和其他很多项目都用到leaf作者封装的lwip,开发出来的go-tun2socks + +leaf也有nat,但只给UDP用到,主要是fake by ip + + +为什么leaf只有UDP用到nat_manager.rs? + +应该只是觉着没必要罢了,修改的 lwip 已经能对 TcpListener 提供很好的支持,所以只对UDP做了 fake by ip,用到nat。这有区别上面说的 `NAT,转发到 socks server,leaf 不依赖 OS 自己的 tcp/ip 重组报文,也就不需要维护 session 来进行 NAT + +## 如何避免重新读出刚写入tun设备的数据包 + +> 请问,如果自己设置默认路由全部流量都发到tun设备,自己read出来后交给userspace网络栈处理,再将处理后的数据包写入tun,之后的read会不会重新read出来自己刚写入的包呢? +> +> 我理解会,内核根据默认路由会重新送给tun,但那样死循环了呀 +> +> 我看这些利用tun的transparent proxy从未遇到过这问题,就好奇是什么原理 + +seeker有这句话 + +> == 指定 IP 或某网段走代理 +> 修改路由表,将希望走代理的 IP 或者网段路由到虚拟网卡。如果使用了本机 socks5 代理,则必须确保 socks5 不会直连加入路由表的网段,否则会死循环。 + +设置默认路由后绝大多数流量都会到tun,为了防止自己刚写到tun包的走默认路由又回来,需要再设置条更短的路由表,发包的ip dest 用这个ip网段,这样就不会再回来。 + +------------------------------ + +> Active Routes: +> +> Network Destination Netmask Gateway Interface Metric +> +> 0.0.0.0 0.0.0.0 192.168.3.1 192.168.3.21 25 +> 0.0.0.0 0.0.0.0 On-link 198.18.0.1 0 +> 127.0.0.0 255.0.0.0 On-link 127.0.0.1 331 +> 198.18.0.0 255.255.0.0 On-link 198.18.0.1 + +clash为默认网关,clash使用wireguard,在windows上使用 SO_UNICAST_IP 绑定 outgoing 网卡流量。不会再走routing decision + +透明代理的tcp listener都是寄生在lwip之上,从tun设备收到数据写入userspace stack,自己的socks代理直接从lwip处理后的数据里就能解析出socket,leaf就这么做的。 + +net stack写入tun + + + +tun写入 net stack + + + + +```bash +# ip +CMD=$1 +# tun gateway +TUN_GW=$2 +# 原来的默认网关 +ORIG_GW=$3 +# 原来的默认网关发给的接口 +# https://github.com/mellow-io/mellow/blob/f71f6e54768ded3cfcc46bebb706d46cb8baac08/src/main.js#L866 +ORIG_GW_SCOPE=$4 +# send through 策略路由 +# https://man7.org/linux/man-pages/man8/ip-rule.8.html +# https://github.com/mellow-io/mellow/blob/f71f6e54768ded3cfcc46bebb706d46cb8baac08/src/main.js#L719 +ORIG_ST=$5 + +"$CMD" route del default table main +# 注意,这加到了 main table,是OS默认使用的路由表 +"$CMD" route add default via $TUN_GW table main +# 注意,这里加到default table,default table是默认路由表,main路由表未匹配到会使用default +"$CMD" route add default via $ORIG_GW dev $ORIG_GW_SCOPE table default +# 策略路由,从原来的default interface 接口来的ip数据包转发到default表,这样就能送出去流量,不会成环了 +"$CMD" rule add from $ORIG_ST table default +``` + +~~看来 wireguard 写入 tun 后并不会出现routing loop,SO_NOTOIF opt 确保不会重新读出刚才写入的数据包 +~~ + +wireguard 之前想添加 `SO_NOTOIF` 来避免routing loop, +但看来维护者认为 ip rule 就够用,不应该在内核做太多magic的功能(直接不走路由)。最后不了了之 + +wireguard实现 + + + + + + +## 防止routing loop的方式 + +### 为需要直连的ip设置单独的路由(删除掉默认路由) + +> 同学,我又来了[无辜笑]。我看seal的全局模式可以转发整个系统的流量,最近自己在搞一个类似的透明代理工具,有个问题想请教下 +> +> 如果自己设置默认路由全部流量都发到tun设备,自己read出来后交给userspace网络栈处理,再将处理后的数据包写入tun,之后的read会不会重新read出来自己刚写入的包呢 +> ⁣ +> ⁣我理解会,内核根据默认路由会重新送给tun,但那样死循环了呀 +> ⁣ +> ⁣但我看seal的全局模式transparent proxy从未遇到过这问题,这是怎么做到的呢 +> +> 回复: read出来不会直接写入tun,是重新封包发给VPN server +> +> vpn server是单独加了一条路由,而且发送包不是直接写tun包,是直接通过tcp/udp发出,socket可以绑定指定网卡发送 + +> ip route del default +> ip route add default dev wg0 +> ip route add 163.172.161.0/32 via 192.168.1.1 dev eth0 +[The Classic Solutions](https://www.wireguard.com/netns/#routing-all-your-traffic) + +### 为直连ip设置单独路由(不删除默认路由,只覆盖) + +默认路由的作用是没有匹配到时走default,通过设置 `0.0.0.0/1`,让这条路由总是先于 default 命中。 +再对要直连的 ip(这里是 163.172.161.0) 设置单独的路由,不需要删除原来的默认路由 + +> ip route add 0.0.0.0/1 dev wg0 +> ip route add 128.0.0.0/1 dev wg0 +> ip route add 163.172.161.0/32 via 192.168.1.1 dev eth0 + +这种也叫做 `0/1 128/1 trick`。 + +但这trick有局限[搜0/1](https://lists.openwall.net/netdev/2016/02/02/222) + +推荐阅读 + +[Overriding The Default Route](https://www.wireguard.com/netns/#routing-all-your-traffic) + +### iptables + +#### iptables REDIRECT + + +#### iptables TPROXY + +#### iptables with fwmark + +iptables 配合 fwmark。 + + +### 策略路由 (ip rule with fwmark) + +#### 使用 ip rule 排除掉某个网卡流量 + +Rule-based Routing + +ip rule + + + +#### 或者 rule 还有更强大的终止routing decision + +这方法没用过,但看着能行 + + + +### namespace solution + + + +[The New Namespace Solution](https://www.wireguard.com/netns/#routing-all-your-traffic) + +### bind before connect + +bind之后connect,routing 不会起作用,这样就能解决设置默认网关后导致的 routing loop + +*通过调试 [leaf](https://github.com/willdeeper/leaf),我已经能够十分确认上面这句话的正确性* + +> If I bind an interface before to connect, Does that mean the connect for outgoing traffic will use that interface I bind without follow the routing decision? +> +> @nuclear yes, if you bind() to an interface before connect()'ing, the connection will go out through that interface. That is the whole point. +> + +listen之前需要bind,决定listen到哪个网卡。 +如果作为client去connect,在调用connect时bind会隐式发生。你也可以主动bind before connect,绕过路由选择,强迫出流量使用某个network interface + + + +### windows 平台 +#### IP_UNICAST_IF +wireguard也依赖tun device,但这tun和Unix上的tun不太像。 + +windows并没有tun的概念,为弥补这空缺,wintun 既是个 windows 内核驱动,也是个userspace tunnel,前者从内核拉取、向内核注入packet,后者将前者的数据包传给 userspace 处理 。 + +windows 并没有策略路由,所以通过 bind interface IP_UNICAST_IF 来避免routing loop(从侧面印证了 bind before connect 可以解决routing loop) + +以下三封邮件解释了为什么使用 bind。 + + + + + +其中 谈到的 [IP_UNICAST_IF](https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options) [IPV6_UNICAST_IF](https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ipv6-socket-options) 可理解为指定 outgoing 流量的网络接口。 + +而里面说的 WFP 是 [Windows Filtering Platform](https://docs.microsoft.com/en-us/windows/win32/fwp/windows-filtering-platform-start-page),看起来是 windows 平台内核级别的流量过滤API(类似 iptables) + +windows 没有Linux 的类似 ip rule 这种策略路由。 wireguard-windows 用来 IP_UNICAST_IF 来开发 + +下面是 wireguard-go 在 windows 上用 IP_UNICAST_IF 的实现 + +wireguard-go IP_UNICAST_IF的实现 + + + +31是windows header里定义的 + +] + +此外,wireguard 会帮你自动设置路由,bind 来避免routing loop + + + +wireguard 对自己使用 IP_UNICAST_IF 的解释 + + + +#### VPN service 直接支持设置绕过VPN的路由 +> https://docs.microsoft.com/en-us/uwp/api/windows.networking.vpn.vpnrouteassignment.ipv4inclusionroutes?view=winrt-22621 + +### Linux 平台 + +通过 setsockopt syscall 时传递 SO_BINDTODEVICE + + + + + +阅读leaf代码时确认使用过上述选项 + + + +![image](https://user-images.githubusercontent.com/24750337/127586791-76ad6390-d023-481a-af46-765dee5167f7.png) + + + +shadowsocks-rust 新添加的 tun mode 也使用了 + +``` +https://github.com/shadowsocks/shadowsocks-rust/blob/4287c2aab8f1826446b68888f58462b4342a0a53/bin/sslocal.rs#L116 + +https://github.com/shadowsocks/shadowsocks-rust/pull/586#issuecomment-899196940 +``` + +------- +经测试(leaf只bind,不添加 ip rule),bind SO_BINDTODEVICE 之后再将数据写入socket,数据会直接到达网卡的发送队列,不会再次走routing decision。 + +路由选择的核心在于找到一个网卡,并 bind SO_BINDTODEVICE 直接绕过了这一步。 + +对于从外界接收数据, + +``` +user mode tcp/ip stack <=> Application + ^ + | default routing +outside => routing decision => NIC adaptor => tcp/ip stack behind NIC + \ + Application + (ARP) / +outside <= NIC adaptor <= routing decision <= tcp/ip stack behind NIC + ^ + |bind SO_BINDTODEVICE +user mode tcp/ip stack <=> Application + +``` + +当 leaf 使用bind到默认网卡时,不需要ip rule策略路由。 + +------ + +即使socks代理在本地,也不会导致routing loop。 + +这是因为路由表中 local 表优先级最高,我们修改的是main和default表。 + +配置 socks outbound 为其他机器时正常工作,是因为流量到了原始网卡直接发离机器了。 + +但如果配置的socks outbound 为本地环路地址会又问题 + +例子 + +> 本地开clash,listen 7890,leaf配置`socks outbound` 127.0.0.1:7890 +> socket SO_BINDTODEVICE 到 eth0,用这 socket dial to 127.0.0.1:7890 +> 在vmm8,也就是虚拟机的默认网卡上抓到 ip dst 为 127.0.0.1 的流量,127.0.0.1属于环路地址,不应该在以太网卡上抓到。所以连接失败。 +> +> ![image](https://user-images.githubusercontent.com/24750337/127739003-77a92416-6c4f-4213-8659-20cab3255d79.png) + +clash这issue 谈到了bind,listen的问题 + + + +### Mac + +#### Network Extension + +excludedRoutes + + + +wireguard 就使用到了 includedRoutes + + + + +#### 设置路由表 + +#### Android + +) + +``` +Protect a socket from VPN connections. After protecting, data sent through this socket will go directly to the underlying network, so its traffic will not be forwarded through the VPN. This method is useful if some connections need to be kept outside of VPN. For example, a VPN tunnel should protect itself if its destination is covered by VPN routes. Otherwise its outgoing packets will be sent back to the VPN interface and cause an infinite loop. This method will fail if the application is not prepared or is revoked. +``` + +每个OS都有自己的特色,Linux下提供 SO_BINDTODEVICE。而Mac,Android则提供高级的API,用于解决routing loop这个问题。核心都是绕过了routing decision这项 + +------------- +## 总结 + +每个平台都有特殊的实现方式, + +Linux + +- iptables或者配合fwmark: 很多软件 +- ip rule: 很多软件 + +Mac + +- network extension: wireguard-apple +- 为出proxy单独设置路由: 经典VPN实现,VPN server往往有公网IP + +Android + +- VpnService#protect: shadowsocks-android + +Windows + +- IP_UNICAST_IF: wireguard-go +- Windows.Networking.Vpn: [Maple](https://github.com/YtFlow/Maple/blob/884b16616ab69e68f727cbe0db61f2849f6560e2/Maple.Task/VpnPlugin.cpp#L30) + +而又有最后相似的,古典的(classic)处理方式: +为proxy创建单独的route,避免 routing loop. + +## 参考的开源项目 + +C实现的tun2socks + + +Go实现的tun2socks,支持windows + + +Rust实现的透明代理工具 + + + + +Tcp connection 使用特定interface送出去流量 +或者使用**策略路由** + + +还是这张图 + +``` +user mode tcp/ip stack <=> Application + ^ + | default routing +outside => routing decision => NIC adaptor => tcp/ip stack behind NIC + \ + Application + (ARP) / +outside <= NIC adaptor <= routing decision <= tcp/ip stack behind NIC + ^ + |bind SO_BINDTODEVICE +user mode tcp/ip stack <=> Application + +``` + +通过bind可以将数据送给eth0(Mac地址mac0),现在eth0怎么往下送呢? + +如果ip dest(ip0)是本机另一个网卡 eth1(Mac地址mac1) ,怎么才能送过去呢? + +这就涉及到ARP,写入eth0后,发送ARP确定 ip dest对应的链路层地址 + +``` +who has ip0 tell mac0 +ip0 is at mac1 +``` + +这时 eth1 告诉 eth0 MAC 地址,然后 eth0 就能送过去了。 + +首先确定 dest ip 是否和 src ip 为一个网段 + +1. 是一个网段,直接发广播ARP,链路层Target Mac全为0 +2. 不是一个网段,链路层 Target Mac为网关Mac,网关收到数据包后广播再次发Arp,寻找目标地址,找到后发给他 + + + +![image](https://user-images.githubusercontent.com/24750337/127967254-038f835a-259d-48d4-97c1-9b5065f8fd5b.png) + +如果是本地另一个网段的地址呢? + +Windows 的route table 有On-Link,Linux应该也差不多。多个虚拟网卡都在内核注册过,所以内核知道 dest ip 的 Target Mac应该是哪个网卡的Mac,不需要额外发ARP,直接内核中转过去了。 + +这也解释了,为什么没有抓到查询另一个本地网卡的ARP请求,也解释了,从一个网卡发出去,并不是一定是发出本地机器,有可能在本地。所以leaf中bind即使是eth0,该去另一个网卡的流量还是会过去。 + + + +我很有必要区分出物理网卡,虚拟网卡。 + +物理网卡负责将数据从网线送出去,并没有ip地址的概念,因为不同的笔记本IP是可自由配置的,所以无法给物理网卡固定的ip。 + +但物理网卡是有Mac地址的。 + +附录 + +wireguard 架构 + + +wireguard white paper 搜 routing loop + + diff --git "a/_posts/58-shadowsocks \345\215\217\350\256\256\345\210\206\346\236\220\345\217\212\345\257\206\347\240\201\345\255\246\345\237\272\347\241\200.md" "b/_posts/58-shadowsocks \345\215\217\350\256\256\345\210\206\346\236\220\345\217\212\345\257\206\347\240\201\345\255\246\345\237\272\347\241\200.md" new file mode 100644 index 0000000..467a292 --- /dev/null +++ "b/_posts/58-shadowsocks \345\215\217\350\256\256\345\210\206\346\236\220\345\217\212\345\257\206\347\240\201\345\255\246\345\237\272\347\241\200.md" @@ -0,0 +1,341 @@ +--- +date: 2022-05-24 +updated: 2022-05-25 +issueid: 58 +tags: +- net +title: shadowsocks 协议分析及密码学基础 +--- +# shadowsocks 协议分析 + +## 基础知识 + +### nonce 和 salt 区别 + +salt 只用一次,用于生成 cipher,和 salt 类似的概念叫initial vector(初始化向量),可认为 salt 和 iv 是等价的概念。 + +nonce 每次 encrypt,decrypt都需要,而且 decrypt 时使用的nonce必须和那次encrypt的nonce一致。由于加解密的nonce必须一一对应,所以nonce往往采用双方提前约定的生成规则,一般都是每次使用完自加1,为下次使用做准备 + +### 信息完整性校验 + +针对 AEAD 加密,密文加密生成 HMAC tag。解密需提供 校验tag 用于数据完整性校验。所以加密方负责将 tag 传给解密方。一般来说,tag会附加到密文之后。 + +## shadowsocks 协议格式 + +解密的首个数据包头会携带 salt 用于接下来计算解密key + +``` +[salt][encrypted length][tag][encrypted payload][tag] +``` + +之后的数据流格式为 + +``` +[encrypted length][tag][encrypted payload][tag] +``` + +由于key创建时需 salt,所以 shadowsocks-server 的 decryptor 的 key 是在收到首次数据包后才能创建出来的 + +加解密的函数式表达 + +psk 的推导需要 user_password,核心是OpenSSL的 [EVP_BytesToKey](https://www.openssl.org/docs/man1.0.2/man3/EVP_BytesToKey.html) + +我贴下原文,D_i 代表第 i 轮,每一轮的计算为:`[上次计算的结果 + data + salt]`,直到 digest 之后的结果符合key长度要求 + +``` +KEY DERIVATION ALGORITHM +The key and IV is derived by concatenating D_1, D_2, etc until enough data is available for the key and IV. D_i is defined as: + + D_i = HASH^count(D_(i-1) || data || salt) +where || denotes concatentaion, D_0 is empty, HASH is the digest algorithm in use, HASH^1(data) is simply HASH(data), HASH^2(data) is HASH(HASH(data)) and so on. +``` + +以下是其他语言针对 psk 计算的实现 + +``` +https://github.com/shadowsocks/shadowsocks/blob/master/shadowsocks/cryptor.py#L54 +https://github.com/shadowsocks/shadowsocks-org/issues/98 +``` + +``` + +// key_length 为加密算法要求的key length +// 比如 AES-128-GCM 要求 key length 128 bits,即 16 字节, derive_pre_shared_key 必须保证 psk.len() == 16 +// psk length 与 key length 相等 +derive_pre_shared_key(user_password, key_length) => psk +derive_key(psk, salt) => key + +// message 为原文 +// tag 为完整性检验数据 +AE_Encrypt(key, message, nonce) => (ciphertext, tag) + +// ciphertext 密文 +AE_Decrypt(key, ciphertext, nonce, tag) => message +``` + +对于 shadowsocks#inbound, 第一个 encrypted payload 是目标地址,之后的则是 要转发的数据 + +``` +[target_address][payload length][payload] +``` + +对于 shadowsocks#outbound + +首次数据包构造中,第一个 encrypted payload 必须是 `target_address`,之后则是要转发的数据 + +加解密只是真实的传递双方原始的数据,针对 shadowsocks#inbound + +我们设定 EncryptedChunk 格式如下 + +``` +[encrypted length][tag][encrypted payload][tag] +``` + +则原始协议对应如下 + +``` +| target_address | payload length | payload | +| ----------------- | ----------------- | ----------------- | +| variable length | 2bytes | variable length | +| ----------------- | ----------------- | ----------------- | +| EncryptedChunk | EncryptedChunk | EncryptedChunk | +| ----------------- | ----------------- | ----------------- | +| | <--------------无限重复--------------->| +``` + +加密后的 shadowsocks 数据流格式为 + +``` +[salt]([Encrypted Chunk]) * n +``` + +target_address 为 socks5的格式,定义如下 + +| ATYP | Target | Port | +| ----- | --------------- | ------ | +| 1byte | variable length | 2bytes | + +shadowsocks-client => shadowsocks-server\ +shadowsocks-client <= shadowsocks-server +连接都以 salt 开头 + +client => server 在 salt 之后的第一个 EncryptedChunk 为 target_address + +server => client 在 salt 之后的 EncryptedChunk 则都为要转发的数据 + +### 密钥导出 + +#### pre shared master key(PSK) 生成 + +user passwd 加 md5计算而成 + + +#### session key 生成(真正用于加解密的key) + +整个流程可简述为 + +HKDF-SHA1(key, salt, info) => subkey + +HKDF 包含两个阶段,extract,expand + +``` +HKDF extract(algorithmName, salt, key) => HKDF-struct + +HKDF-struct#expand(info, key_len) => session key +``` + + + +```cs +// masterKey 就是 PSK,sessionKey则为生成结果,salt 从shadowsocks server的第一个chunk的头取,InfoBytes 始终为 ss-subkey 的 bytes 表示 +//HKDF 包含 extract,expand 两个阶段,下面代码是两个阶段一起都做了 +HKDF.DeriveKey(HashAlgorithmName.SHA1, masterKey, sessionKey, salt, InfoBytes); +``` + +上面的 `HKDF.DeriveKey` 同时包含extract, expand + +如果是分两步进行,大致下面模样 + +```rs +pub fn hkdf_sha1(key: &[u8], salt: &[u8], info: Vec, size: usize) -> Result> { + let (_, h) = Hkdf::::extract(Some(salt), key); + let mut okm = vec![0u8; size]; + h.expand(&info, &mut okm) + .map_err(|_|anyhow!("hkdf expand failed"))?; + Ok(okm.to_vec()) +} +``` + +这里要说明下,由于用户输入的密码往往是简单的字符串,所以大多数实现并不直接使用 password 作为 key,而是先进行 KDF(key derivation function),将KDF得到的值作为key, + +常见的 KDF 算法有 PBKDF2, HKDF,这些算法的特点是,都可以生成指定长度的 key + +-------- +拿 `AES-128-GCM` 举个例子 + +128 bit 的 key 长度,也就是 16 字节 + +其他的 salt, nonce 长度并未有强制性要求,有些实现库支持 variable length + + + +具体长度则由应用自行决定 + +shadowsocks 要求如下 + +| Name | Alias | Key Size | Salt Size | Nonce Size | Tag Size | +| ---------------------- | ---------------------- | -------- | --------- | ---------- | -------- | +| AEAD_CHACHA20_POLY1305 | chacha20-ietf-poly1305 | 32 | 32 | 12 | 16 | +| AEAD_AES_256_GCM | aes-256-gcm | 32 | 32 | 12 | 16 | +| EAD_AES_128_GCM | aes-128-gcm | 16 | 16 | 12 | 16 | + +不同的加密算法所要求的 key size 不同,这就要求我们能任意生成不同长度的key + +举个例子,HKDF 分extract,expand 两个阶段。extract 之后会得到一串伪随机数,expand 阶段可以指定长度,从而得到任意长度的 key。 + +# 密码学基础 + +## MAC (Message authentication code) + +消息验证码,是附加到原文之后的数据,用于持有密钥的接收方进行校验,可察觉数据是否被篡改,只是一个概念,并不对应具体的实现算法。 + +为什么 mac 需要一个key呢?直接做一边hash当成tag不就可以了吗? + +假设 + +``` +小明 => 小红,小明用原文 A 做hash后得到 tag A 发给小红 + +hacker 劫持后,用伪造的原文B做hash后得到 tag B,同时将 原文A,tagA替换掉发给小红,小红获取 原文A(实际被替换成原文B)计算后与 tagA(实际被替换为tagB)比较,发现相同,认为数据没有篡改 !! + +所以必须引入一个只有小红,小明才知道的key +``` + +新阶段为 + +``` +小明 + +mac(key, textA) => tagA + +由于 hacker不知道 key,假设随便使用一个 key1,伪造 textB + +mac(key1, textB) => tagB + +当小红 +mac(key, textB) => tagC != tagB + +则小红会发现数据被篡改 +``` + +## HMAC (hash based message authentication code) + +是 MAC 的一种实现算法,底层使用 hash 函数生成 mac + +所以HMAC也需要一个key + +HMAC(key, text) => tag + +## HKDF (HMAC-based Extract-and-Expand Key Derivation Function) + +将 HMAC 作为底层实现的密钥派生算法 + +``` +// https://datatracker.ietf.org/doc/html/rfc5869 +A key derivation function (KDF) is a basic and essential component of +cryptographic systems. Its goal is to take some source of initial +keying material and derive from it one or more cryptographically +strong secret keys. +``` + +KDF,即密钥派生算法,旨在通过一些初始的密码材料,派生出一个至多个密码学意义上,足够健壮的 密钥 + +HKDF 包含两个阶段,extract, expand + +extract(salt, user_weak_password) => HKDF_struct + +// If you don't have any info to pass, use an empty slice. +HKDF_struct.expand(info) => out_key + +以下引用自 , 我非常建议阅读,十分简单明了的RFC文档,介绍了每个数据的作用 + +``` + +In many applications, the input keying material is not necessarily +distributed uniformly, and the attacker may have some partial +knowledge about it (for example, a Diffie-Hellman value computed by a +key exchange protocol) or even partial control of it (as in some +entropy-gathering applications). Thus, the goal of the "extract" +stage is to "concentrate" the possibly dispersed entropy of the input +keying material into a short, but cryptographically strong, +pseudorandom key. In some applications, the input may already be a +good pseudorandom key; in these cases, the "extract" stage is not +necessary, and the "expand" part can be used alone. + +The second stage "expands" the pseudorandom key to the desired +length; the number and lengths of the output keys depend on the +specific cryptographic algorithms for which the keys are needed. + +2.2. Step 1: Extract + + HKDF-Extract(salt, IKM) -> PRK + + Options: + Hash a hash function; HashLen denotes the length of the + hash function output in octets + + Inputs: + salt optional salt value (a non-secret random value); + if not provided, it is set to a string of HashLen zeros. + IKM input keying material + + Output: + PRK a pseudorandom key (of HashLen octets) + + The output PRK is calculated as follows: + + PRK = HMAC-Hash(salt, IKM) + +2.3. Step 2: Expand + + HKDF-Expand(PRK, info, L) -> OKM + + Options: + Hash a hash function; HashLen denotes the length of the + hash function output in octets + + + + + + + +Krawczyk & Eronen Informational [Page 3] + +RFC 5869 Extract-and-Expand HKDF May 2010 + + + Inputs: + PRK a pseudorandom key of at least HashLen octets + (usually, the output from the extract step) + info optional context and application specific information + (can be a zero-length string) + L length of output keying material in octets + (<= 255*HashLen) + + Output: + OKM output keying material (of L octets) + + The output OKM is calculated as follows: + + N = ceil(L/HashLen) + T = T(1) | T(2) | T(3) | ... | T(N) + OKM = first L octets of T + + where: + T(0) = empty string (zero length) + T(1) = HMAC-Hash(PRK, T(0) | info | 0x01) + T(2) = HMAC-Hash(PRK, T(1) | info | 0x02) + T(3) = HMAC-Hash(PRK, T(2) | info | 0x03) + ... +``` diff --git "a/_posts/59-Nginx \344\273\243\347\240\201\345\210\206\346\236\220.md" "b/_posts/59-Nginx \344\273\243\347\240\201\345\210\206\346\236\220.md" new file mode 100644 index 0000000..bde2216 --- /dev/null +++ "b/_posts/59-Nginx \344\273\243\347\240\201\345\210\206\346\236\220.md" @@ -0,0 +1,1554 @@ +--- +title: Nginx 代码分析 +date: 2022-08-18 +updated: 2023-10-26 +issueid: 59 +tags: +- 基础 +- net +- C/C++ +--- +工作中涉及Nginx module开发,分析Nginx/tengine代码 + +## nginx modules初始化 + +每个 module 必须有 ngx_module_t,且在module不同生命周期都支持 hook 函数 + +```c +struct ngx_module_s { + ngx_uint_t ctx_index; + ngx_uint_t index; + + char *name; + + ngx_uint_t spare0; + ngx_uint_t spare1; + + ngx_uint_t version; + const char *signature; + + void *ctx; + ngx_command_t *commands; + ngx_uint_t type; + + ngx_int_t (*init_master)(ngx_log_t *log); + + ngx_int_t (*init_module)(ngx_cycle_t *cycle); + + ngx_int_t (*init_process)(ngx_cycle_t *cycle); + ngx_int_t (*init_thread)(ngx_cycle_t *cycle); + void (*exit_thread)(ngx_cycle_t *cycle); + void (*exit_process)(ngx_cycle_t *cycle); + + void (*exit_master)(ngx_cycle_t *cycle); + + uintptr_t spare_hook0; + uintptr_t spare_hook1; + uintptr_t spare_hook2; + uintptr_t spare_hook3; + uintptr_t spare_hook4; + uintptr_t spare_hook5; + uintptr_t spare_hook6; + uintptr_t spare_hook7; +}; +``` + +由于 ngx_module_t 为基础类型,对于http和stream,通过 ngx_module_t#ctx进行了扩展,用于放置 http/stream module相关的 hook 函数 + +比如http module中,ctx 为 ngx_http_module_t 类型 + +```c +typedef struct { + ngx_int_t (*preconfiguration)(ngx_conf_t *cf); + ngx_int_t (*postconfiguration)(ngx_conf_t *cf); + + void *(*create_main_conf)(ngx_conf_t *cf); + char *(*init_main_conf)(ngx_conf_t *cf, void *conf); + + void *(*create_srv_conf)(ngx_conf_t *cf); + char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf); + + void *(*create_loc_conf)(ngx_conf_t *cf); + char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf); +} ngx_http_module_t; +``` + +stream module 对 module ctx 扩展 + +```c +typedef struct { + ngx_int_t (*preconfiguration)(ngx_conf_t *cf); + ngx_int_t (*postconfiguration)(ngx_conf_t *cf); + + void *(*create_main_conf)(ngx_conf_t *cf); + char *(*init_main_conf)(ngx_conf_t *cf, void *conf); + + void *(*create_srv_conf)(ngx_conf_t *cf); + char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, + void *conf); +} ngx_stream_module_t; + +``` + +nginx cycle 是最上层的大类,存放全部nginx运行上下文,其中 +cycle->modules 存放全部的module,包含http_core modules,第三方http modules, event modules 等 + +```c +ngx_int_t +ngx_cycle_modules(ngx_cycle_t *cycle) +{ + /* + * create a list of modules to be used for this cycle, + * copy static modules to it + */ + + + cycle->modules = ngx_pcalloc(cycle->pool, (ngx_max_module + 1) + * sizeof(ngx_module_t *)); + if (cycle->modules == NULL) { + return NGX_ERROR; + } + // 将全部的modules都放到cycle->modules中 + // ngx_modules 是编译后自动生成的文件,里面引用了全部modules + ngx_memcpy(cycle->modules, ngx_modules, + ngx_modules_n * sizeof(ngx_module_t *)); + + cycle->modules_n = ngx_modules_n; + + return NGX_OK; +} +``` + +### 各个hook的调用时机 + + + +#### init_module + +master 进程在每次配置初始化之后调用 + +#### init_process + +master进程创建完worker后,worker中调用。所以init_process在worker的上下文中 + +#### exit_process + +worker 退出时调用 + +#### exit_master + +master退出时调用 + +## module自身配置的创建与解析module指令command + +```c +// https://github.com/iamwwc/nginx-debugable/blob/8a30c4b12226c92cd317db614172cc6de2795768/src/core/ngx_cycle.c#L238 + +// 遍历全部的模块 +for (i = 0; cycle->modules[i]; i++) { + if (cycle->modules[i]->type != NGX_CORE_MODULE) { + continue; + } + + module = cycle->modules[i]->ctx; + + if (module->create_conf) { + // init_cycle 阶段调用 module#create_conf 创建每个module初始状态的配置文件 + rv = module->create_conf(cycle); + if (rv == NULL) { + ngx_destroy_pool(pool); + return NULL; + } + // cycle 下 + cycle->conf_ctx[cycle->modules[i]->index] = rv; + } +} +``` + +**我发现 nginx 遍历全部modules的代码挺多的,为了实现某个功能,经常要遍历全部modules** + +```c +// https://github.com/iamwwc/nginx-debugable/blob/8a30c4b12226c92cd317db614172cc6de2795768/src/core/ngx_conf_file.c#L447 +// 在create_conf 已经创建好了 module conf,并存放到了 cf->ctx数组,下标是module的index +// 由于这是配置文件解析阶段,需要初始化每个module 的配置,所以调用module#ngx_command_t#set 方法,交由module处理每个directive(command) + + +for (i = 0; cf->cycle->modules[i]; i++) { + // 遍历全部 modules + + cmd = cf->cycle->modules[i]->commands; + if (cmd == NULL) { + continue; + } + + for ( /* void */ ; cmd->name.len; cmd++) { + // 处理每个module的ngx_command_t[] + + if (name->len != cmd->name.len) { + continue; + } + // 寻找name相同的command + // 比如 http block 的command#name是 http + // static ngx_command_t ngx_http_commands[] = { + + // { ngx_string("http"), + // NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, + // ngx_http_block, + // 0, + // 0, + // NULL }, + + // ngx_null_command + // }; + if (ngx_strcmp(name->data, cmd->name.data) != 0) { + continue; + } + + found = 1; + + if (cf->cycle->modules[i]->type != NGX_CONF_MODULE + && cf->cycle->modules[i]->type != cf->module_type) + { + continue; + } + + /* is the directive's location right ? */ + + if (!(cmd->type & cf->cmd_type)) { + continue; + } + + if (!(cmd->type & NGX_CONF_BLOCK) && last != NGX_OK) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "directive \"%s\" is not terminated by \";\"", + name->data); + return NGX_ERROR; + } + + if ((cmd->type & NGX_CONF_BLOCK) && last != NGX_CONF_BLOCK_START) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "directive \"%s\" has no opening \"{\"", + name->data); + return NGX_ERROR; + } + + /* is the directive's argument count right ? */ + + if (!(cmd->type & NGX_CONF_ANY)) { + + if (cmd->type & NGX_CONF_FLAG) { + + if (cf->args->nelts != 2) { + goto invalid; + } + // 指令要求至少一个参数 + } else if (cmd->type & NGX_CONF_1MORE) { + + if (cf->args->nelts < 2) { + goto invalid; + } + // 指令要求至少两个参数 + } else if (cmd->type & NGX_CONF_2MORE) { + + if (cf->args->nelts < 3) { + goto invalid; + } + + } else if (cf->args->nelts > NGX_CONF_MAX_ARGS) { + + goto invalid; + + } else if (!(cmd->type & argument_number[cf->args->nelts - 1])) + { + goto invalid; + } + } + + /* set up the directive's configuration context */ + + conf = NULL; + + if (cmd->type & NGX_DIRECT_CONF) { + // 将conf赋为上文调用create_conf初始化好的数组地址 + conf = ((void **) cf->ctx)[cf->cycle->modules[i]->index]; + + } else if (cmd->type & NGX_MAIN_CONF) { + // 将conf赋为上文调用create_conf初始化好的数组地址 + conf = &(((void **) cf->ctx)[cf->cycle->modules[i]->index]); + } else if (cf->ctx) { + // 举个例子 + // 在 ngx_stream block解析过程中,cf->ctx 指向 ngx_stream_conf_ctx_t + // 如果 cmd->conf 为 NGX_STREAM_SRV_CONF_OFFSET(8) + // 则 (char *) cf->ctx + cmd->conf 计算后指向 ngx_stream_conf_ctx_t#srv_conf 字段. + // 此种情况下,下面的 conf = confp[cf->cycle->modules[i]->ctx_index]; 计算为此module在stream module 的 srv 配置结构,也就是module#create_srv_conf hook 返回的结构 + confp = *(void **) ((char *) cf->ctx + cmd->conf); + + if (confp) { + // 将conf赋为上文调用create_conf初始化好的数组地址 + conf = confp[cf->cycle->modules[i]->ctx_index]; + } + } + // 调用 set,module接管directive处理 + // 如果cmd->name == "http" && NGX_CONF_BLOCK + // 则会走到 + // https://github.com/iamwwc/nginx-debugable/blob/1db517fb71aed6d6fffc8347086f89eb29b83dea/src/http/ngx_http.c#L122 + // http block 创建用于http的配置格式 + // 为什么 ngx_conf_t 里多是 void* p + // 是因为不同的module要求的配置格式不尽相同,所以留给module自定义 + rv = cmd->set(cf, cmd, conf); + + if (rv == NGX_CONF_OK) { + return NGX_OK; + } + + if (rv == NGX_CONF_ERROR) { + return NGX_ERROR; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%s\" directive %s", name->data, rv); + + return NGX_ERROR; + } +} + +``` + +conf 字段决定set函数的第三个参数传递什么,如果是 NGX_STREAM_SRV_CONF_OFFSET,则nginx会将此module的srv_conf作为 void *conf 传递 + +我们拿一个set函数举例 + +```c +static char * +ngx_set_worker_processes(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_str_t *value; + ngx_core_conf_t *ccf; + + // 转换为module自己的conf类型 + ccf = (ngx_core_conf_t *) conf; + + if (ccf->worker_processes != NGX_CONF_UNSET) { + return "is duplicate"; + } + + value = cf->args->elts; + + // 设置配置 + if (ngx_strcmp(value[1].data, "auto") == 0) { + ccf->worker_processes = ngx_ncpu; + return NGX_CONF_OK; + } + + ccf->worker_processes = ngx_atoi(value[1].data, value[1].len); + + if (ccf->worker_processes == NGX_ERROR) { + return "invalid value"; + } + + return NGX_CONF_OK; +} +``` + +ngx_conf_t是在nginx启动后创建的变量,用于配置解析和调用module hook时记录当前配置解析的状态上下文。 + +```c +// https://github.com/willdeeper/tengine/blob/f9a582f73a2d6bd4fb73270cb6636e3490fc943f/src/core/ngx_conf_file.c#L761 +if (found) { + // ... + // 配置解析过程中,会将指令后的Args存入 cf->args + // 调用 module command[] 初始化command值时传入 + // char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + word = ngx_array_push(cf->args); + + if (word == NULL) { + return NGX_ERROR; + } + + word->data = ngx_pnalloc(cf->pool, b->pos - 1 - start + 1); + if (word->data == NULL) { + return NGX_ERROR; + } + // ... +} +``` + +![image](https://user-images.githubusercontent.com/24750337/186622825-8cc33160-bc89-4dc0-98ff-6307df27d683.png) + + + +### main_conf vs srv_conf vs loc_conf + +对于http 模块 + +main_conf: 应用于整个http block,属于module的全局http配置 +srv_conf: 应用于单独一个server block,属于单独某个server块的配置 +loc_conf: 应用于单独一个location,属于单独某个location块的配置 + +所以 main_conf 只有一个,而 srv_conf, loc_conf 都有多个。 + +所以 create_main_conf 只被调用一次, create_srv, create_loc 可被调用多次,取决于配置中有多少个 server,location block + +对于 stream 模块 + +main_conf: 应用于整个stream block,属于module的全局stream配置 +srv_conf: 应用于单独一个server block,属于单独某个server块的配置 + +所以 main_conf 只有一个,而 srv_conf都有多个。 + +所以 create_main_conf 只被调用一次, create_srv_conf 可被调用多次,取决于配置中有多少个 server block。 + +比如 + +``` +stream { + upstream foo { + server 1; + server 2; + } + server { + + } + upstream foo { + server 1; + server 2; + } +} +``` + +```c +static ngx_command_t ngx_stream_upstream_dynamic_command[] = { + {ngx_string("consul"), NGX_STREAM_UPS_CONF | NGX_CONF_1MORE, + ngx_stream_dynamic_upstream_set_consul_slot, 0, 0, NULL}, + ngx_null_command}; + +``` + +由于有 `NGX_STREAM_UPS_CONF` 标记,配置解析到foo,bar时,分别都会调用module的create_srv_conf + +## 配置merge + + + +以 `create_loc_conf` 为例,对于module有 create_conf和merge_conf的hook,nginx首先调用 create_conf,此时module创建一个空conf返回给nginx,在merge阶段nginx调用 merge_conf hook,将parent context conf传过来,module就可以拿到配置文件初始化 + +比如下面的合并server block,`cscfp[s]->ctx->srv_conf[ctx_index]` 是新创建的 conf,`saved.srv_conf[ctx_index]`是 + +```c +if (module->merge_srv_conf) { + // https://developer.huawei.com/consumer/cn/forum/topic/0201187160465810231?fid=26 + // 这里主要是进行配置的合并,saved.srv_conf[ctx_index]指向的是http块中解析出的SRV类型的配置, + // cscfp[s]->ctx->srv_conf[ctx_index]解析的则是当前server块中的配置,合并的一般规则是通过对应 + // 模块实现的merge_srv_conf()来进行的,**决定使用http块中的配置,还是使用server块中的配置**, + // 或者说合并两者,则是由具体的实现来决定的。这里合并的效果本质上就是对 + // cscfp[s]->ctx->srv_conf[ctx_index]中的属性赋值,如果该值不存在,则将http块对应的值写到 + // 该属性中 + rv = module->merge_srv_conf(cf, saved.srv_conf[ctx_index], + cscfp[s]->ctx->srv_conf[ctx_index]); + if (rv != NGX_CONF_OK) { + goto failed; + } +} +``` + +对于http block,最关键的是 + +```c +typedef struct { + void **main_conf; + void **srv_conf; + void **loc_conf; +} ngx_http_conf_ctx_t; + + +typedef struct { + /* array of the ngx_http_server_name_t, "server_name" directive */ + ngx_array_t server_names; + + /* server ctx */ + // 每个server块持有 ngx_http_conf_ctx_t 结构,承上启下,server块记录属于哪个http,由记录其下全部的loc_conf + ngx_http_conf_ctx_t *ctx; + + u_char *file_name; + ngx_uint_t line; + + ngx_str_t server_name; + + size_t connection_pool_size; + size_t request_pool_size; + size_t client_header_buffer_size; + + ngx_bufs_t large_client_header_buffers; + + ngx_msec_t client_header_timeout; + + ngx_flag_t ignore_invalid_headers; + ngx_flag_t merge_slashes; + ngx_flag_t underscores_in_headers; + + unsigned listen:1; +#if (NGX_PCRE) + unsigned captures:1; +#endif + + ngx_http_core_loc_conf_t **named_locations; +} ngx_http_core_srv_conf_t; +``` + +http block 中开始初始化 http module, + +```c +// https://github.com/iamwwc/nginx-debugable/blob/1db517fb71aed6d6fffc8347086f89eb29b83dea/src/http/ngx_http.c#L154 + +/* the http main_conf context, it is the same in the all http contexts */ + +// 初始化 main_conf +ctx->main_conf = ngx_pcalloc(cf->pool, + sizeof(void *) * ngx_http_max_module); +if (ctx->main_conf == NULL) { + return NGX_CONF_ERROR; +} + + +/* + * the http null srv_conf context, it is used to merge + * the server{}s' srv_conf's + */ +// 初始化 srv_conf +ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); +if (ctx->srv_conf == NULL) { + return NGX_CONF_ERROR; +} + + +/* + * the http null loc_conf context, it is used to merge + * the server{}s' loc_conf's + */ +// 初始化 loc_conf +ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); +if (ctx->loc_conf == NULL) { + return NGX_CONF_ERROR; +} + + +/* + * create the main_conf's, the null srv_conf's, and the null loc_conf's + * of the all http modules + */ + +for (m = 0; cf->cycle->modules[m]; m++) { + // 只处理 http module + if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) { + continue; + } + + // 从cycle中取出 ngx_module_t#ctx void* p 指针 + module = cf->cycle->modules[m]->ctx; + mi = cf->cycle->modules[m]->ctx_index; + + // 调用 http module hook + if (module->create_main_conf) { + ctx->main_conf[mi] = module->create_main_conf(cf); + if (ctx->main_conf[mi] == NULL) { + return NGX_CONF_ERROR; + } + } + // 调用 http module hook + if (module->create_srv_conf) { + ctx->srv_conf[mi] = module->create_srv_conf(cf); + if (ctx->srv_conf[mi] == NULL) { + return NGX_CONF_ERROR; + } + } + // 调用 http module hook + if (module->create_loc_conf) { + ctx->loc_conf[mi] = module->create_loc_conf(cf); + if (ctx->loc_conf[mi] == NULL) { + return NGX_CONF_ERROR; + } + } +} + +// 注意这里,pcf 不是pointer,是clone了一份 cf +// 而cf->ctx 被覆盖成 ngx_http_conf_ctx_t 结构 +// ngx_conf_t 的ctx应该指向 modules的数组,这里临时修改了指向hack了下。主要是为了http postconfiguration 和 config merge +// 在此函数最后 *cf = pcf +pcf = *cf; +cf->ctx = ctx; + +for (m = 0; cf->cycle->modules[m]; m++) { + if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) { + continue; + } + + module = cf->cycle->modules[m]->ctx; + + if (module->preconfiguration) { + if (module->preconfiguration(cf) != NGX_OK) { + return NGX_CONF_ERROR; + } + } +} + +/* parse inside the http{} block */ + +cf->module_type = NGX_HTTP_MODULE; +cf->cmd_type = NGX_HTTP_MAIN_CONF; +rv = ngx_conf_parse(cf, NULL); + +if (rv != NGX_CONF_OK) { + goto failed; +} + +/* + * init http{} main_conf's, merge the server{}s' srv_conf's + * and its location{}s' loc_conf's + */ + +cmcf = ctx->main_conf[ngx_http_core_module.ctx_index]; +cscfp = cmcf->servers.elts; + +for (m = 0; cf->cycle->modules[m]; m++) { + if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) { + continue; + } + + module = cf->cycle->modules[m]->ctx; + mi = cf->cycle->modules[m]->ctx_index; + + /* init http{} main_conf's */ + + if (module->init_main_conf) { + rv = module->init_main_conf(cf, ctx->main_conf[mi]); + if (rv != NGX_CONF_OK) { + goto failed; + } + } + + rv = ngx_http_merge_servers(cf, cmcf, module, mi); + if (rv != NGX_CONF_OK) { + goto failed; + } +} + + +/* create location trees */ + +for (s = 0; s < cmcf->servers.nelts; s++) { + + clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index]; + + if (ngx_http_init_locations(cf, cscfp[s], clcf) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) { + return NGX_CONF_ERROR; + } +} +if (ngx_http_init_phases(cf, cmcf) != NGX_OK) { + return NGX_CONF_ERROR; +} + +if (ngx_http_init_headers_in_hash(cf, cmcf) != NGX_OK) { + return NGX_CONF_ERROR; +} + + +for (m = 0; cf->cycle->modules[m]; m++) { + if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) { + continue; + } + + module = cf->cycle->modules[m]->ctx; + + if (module->postconfiguration) { + if (module->postconfiguration(cf) != NGX_OK) { + return NGX_CONF_ERROR; + } + } +} + +if (ngx_http_variables_init_vars(cf) != NGX_OK) { + return NGX_CONF_ERROR; +} + +/* + * http{}'s cf->ctx was needed while the configuration merging + * and in postconfiguration process + */ + +// **看这里** +// 在 ngx_http_merge_servers 和 postconfiguration 之后,ctx又还原重新指向 modules 指针数组 +*cf = pcf; +``` + +### merge_loc_conf + +需要location merge是因为 location 可以嵌套 + +```txt +# main context + +server { + + # server context + + location /match/criteria { + + # first location context + + } + + location /other/criteria { + + # second location context + + location nested_match { + + # first nested location + + } + + location other_nested { + + # second nested location + + } + + } + +} +``` + +## Http 请求处理 + +### Phase + +Nginx将请求分成如下几个阶段,请求到来后,按照顺序分别执行每个阶段的handlers + +```c +typedef enum { + NGX_HTTP_POST_READ_PHASE = 0, + + NGX_HTTP_SERVER_REWRITE_PHASE, + + NGX_HTTP_FIND_CONFIG_PHASE, + NGX_HTTP_REWRITE_PHASE, + NGX_HTTP_POST_REWRITE_PHASE, + + NGX_HTTP_PREACCESS_PHASE, + + NGX_HTTP_ACCESS_PHASE, + NGX_HTTP_POST_ACCESS_PHASE, + + NGX_HTTP_PRECONTENT_PHASE, + + NGX_HTTP_CONTENT_PHASE, + + NGX_HTTP_LOG_PHASE +} ngx_http_phases; +``` + +由于nginx支持module插件的形式,必然要有一种机制,当请求来后遍历执行插件,可以在中间某个插件停止处理 + +Koa 使用洋葱模型,nginx则while迭代handler,handler里决定是否 `r->phase_handler++`指向下一个handler,如果handler返回 `NGX_OK`,则请求处理结束 + +```c +// https://github.com/iamwwc/nginx-debugable/blob/8a30c4b12226c92cd317db614172cc6de2795768/src/http/ngx_http_core_module.c#L875 +while (ph[r->phase_handler].checker) { + + rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]); + + if (rc == NGX_OK) { + return; + } +} +``` + +http core module 初始化阶段会将不同module挂载到不同phase上,如下 + +```c +// https://github.com/iamwwc/nginx-debugable/blob/8a30c4b12226c92cd317db614172cc6de2795768/src/http/ngx_http.c#L486 +for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) { + h = cmcf->phases[i].handlers.elts; + + switch (i) { + // 内置module 通过for loop 放到不同phase + case NGX_HTTP_SERVER_REWRITE_PHASE: + if (cmcf->phase_engine.server_rewrite_index == (ngx_uint_t) -1) { + cmcf->phase_engine.server_rewrite_index = n; + } + checker = ngx_http_core_rewrite_phase; + + break; + + case NGX_HTTP_FIND_CONFIG_PHASE: + find_config_index = n; + + ph->checker = ngx_http_core_find_config_phase; + n++; + ph++; + + continue; + + case NGX_HTTP_REWRITE_PHASE: + if (cmcf->phase_engine.location_rewrite_index == (ngx_uint_t) -1) { + cmcf->phase_engine.location_rewrite_index = n; + } + checker = ngx_http_core_rewrite_phase; + + break; + + case NGX_HTTP_POST_REWRITE_PHASE: + if (use_rewrite) { + ph->checker = ngx_http_core_post_rewrite_phase; + ph->next = find_config_index; + n++; + ph++; + } + + continue; + + case NGX_HTTP_ACCESS_PHASE: + checker = ngx_http_core_access_phase; + n++; + break; + + case NGX_HTTP_POST_ACCESS_PHASE: + if (use_access) { + ph->checker = ngx_http_core_post_access_phase; + ph->next = n; + ph++; + } + + continue; + + case NGX_HTTP_CONTENT_PHASE: + checker = ngx_http_core_content_phase; + break; + + default: + checker = ngx_http_core_generic_phase; + } + + n += cmcf->phases[i].handlers.nelts; + + for (j = cmcf->phases[i].handlers.nelts - 1; j >= 0; j--) { + ph->checker = checker; + ph->handler = h[j]; + ph->next = n; + ph++; + } +} + +``` + +## Stream module hook 调用顺序 + +先看一个第三方 stream module的 ctx 结构 + +```c +typedef struct { + ngx_int_t (*preconfiguration)(ngx_conf_t *cf); + ngx_int_t (*postconfiguration)(ngx_conf_t *cf); + + void *(*create_main_conf)(ngx_conf_t *cf); + char *(*init_main_conf)(ngx_conf_t *cf, void *conf); + + void *(*create_srv_conf)(ngx_conf_t *cf); + char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, + void *conf); +} ngx_stream_module_t; + +static ngx_stream_module_t ngx_stream_upsync_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + ngx_stream_upsync_create_main_conf, /* create main configuration */ + ngx_stream_upsync_init_main_conf, /* init main configuration */ + + ngx_stream_upsync_create_srv_conf, /* create server configuration */ + NULL, /* merge server configuration */ +}; +``` + +调用顺序 + +1. create_main_conf:创建module自己的配置文件struct并返回 +2. create_srv_conf + 创建server块的module 配置。举个例子:stream block下会有server block,此时会调用 create_srv_conf,放到 srv_conf 下的module对应的数组索引中。upstream block下也有 server block,也会调用 create_srv_conf,放到 srv_conf 下的module对应的数组索引中。所以 create_srv_conf 会在stream block和upstream block都调用。 +3. preconfiguration: 配置创建之前 +4. postconfiguration: 配置创建之后,但还未基于配置进行其他的行为。http request handler就放这里。nginx会将handler放入phase_engine,再往下放handler已经晚了 + 到此 nginx 已经完成 upstream,server 块、module 的command/directive 解析。由于每个command都有set hook,set里都已经将value放到了之前创建的 conf 中 +5. create_main_conf +6. create_srv_conf:server块配置初始化 + // 配置初始化完成后,下面开始调用 ngx_module_t 上的 module 级别的hook +7. create_loc_conf +8. init_main_conf + nginx 将 module 自己创建好的 conf 传进来,module 初始化自身配置 +9. merge_srv_conf: 有些指令可以在main block,也可以在 srv block,merge_srv_conf 的hook决定srv block的conf是否需要覆盖main block的。由于传递的是指针,也可以顺带修改main block的指令参数 +10. init_module: + 每次配置文件reload,init_module都会被重新掉用,传进来 cycle,cycle->old_cycle,用于生成一个新cycle。最后创建好的cycle放在全局变量中,fork 新worker process后,cycle 会自动映射到worker process 地址空间,访问时写时拷贝。 +11. init_process + 只在 worker process 调用。请求不经过master直接到worker,此hook可以执行module的业务逻辑,初始化处理请求相关的配置。比如 `nginx-stream-upsync-module`,在 init_process 里请求consul,etcd,获取下游ipport列表,放在worker自己的变量中 +12. exit_process + worker退出调用 +13. exit_master + master退出时掉用,优雅退出 + +## Stream 请求处理 + +### Phase + +如下几个阶段,顺序执行每个阶段下的handlers + + +```c +typedef enum { + NGX_STREAM_POST_ACCEPT_PHASE = 0, + NGX_STREAM_PREACCESS_PHASE, + NGX_STREAM_ACCESS_PHASE , + NGX_STREAM_SSL_PHASE, + NGX_STREAM_PREREAD_PHASE, + NGX_STREAM_CONTENT_PHASE, + NGX_STREAM_LOG_PHASE +} ngx_stream_phases; + +``` + +```c +// https://github.com/iamwwc/nginx-debugable/blob/8a30c4b12226c92cd317db614172cc6de2795768/src/stream/ngx_stream.c#L352 +for (i = 0; i < NGX_STREAM_LOG_PHASE; i++) { + h = cmcf->phases[i].handlers.elts; + + switch (i) { + // stream module 将内置handlers放到不同的phase + case NGX_STREAM_PREREAD_PHASE: + checker = ngx_stream_core_preread_phase; + break; + + case NGX_STREAM_CONTENT_PHASE: + ph->checker = ngx_stream_core_content_phase; + n++; + ph++; + + continue; + + default: + checker = ngx_stream_core_generic_phase; + } + + n += cmcf->phases[i].handlers.nelts; + + for (j = cmcf->phases[i].handlers.nelts - 1; j >= 0; j--) { + ph->checker = checker; + ph->handler = h[j]; + ph->next = n; + ph++; + } +} +``` + +## stream upstream module 分析与负载均衡逻辑 + +upstream作为nginx的LB模块,承担着在server中流量均分的重任。 + +分析upstream module中,必然绕不开以下几个结构 + +```c +ngx_stream_upstream_server_t *server = NULL; +// rr 为 round robin 简写 +ngx_stream_upstream_rr_peer_t *peer = NULL; +ngx_stream_upstream_rr_peers_t *peers = NULL; +ngx_stream_upstream_srv_conf_t *uscf; +``` + +通过 upstream block 对齐到上面类型 + +```nginx +stream { + # ngx_stream_upstream_srv_conf_t + # ngx_stream_upstream_rr_peers_t + upstream a.b.c{ + # ngx_stream_upstream_server_t + # ngx_stream_upstream_rr_peer_t + server 127.0.0.1:12345 max_fails=3 fail_timeout=30s; + # ngx_stream_upstream_server_t + # ngx_stream_upstream_peer_t + server unix:/tmp/backend3; + server 1.2.3.4:8080 backup; + server 5.6.7.8:8080 backup; + } + + server { + listen 12345; + proxy_connect_timeout 1s; + proxy_timeout 3s; + proxy_pass backend; + } +} +``` + +ngx_stream_upstream_srv_conf_t 存放 upstream block 级别的相关配置 +ngx_stream_upstream_server_t 存放 upstream 单独一个server block配置,含有 `weight, max_fails` 等配置 + +`ngx_stream_upstream_rr_peers_t`, `ngx_stream_upstream_rr_peer_t` 与 round robin 相关 + +peer_t 记录LB相关参数,比如已经连接到此下游的连接数,下游响应时间,用于进行LB策略 +peers_t#next参数和upstream 的backup指令有关,记录此peers_t的backup servers + +当peer_t down,peers_t通过next找到此peer_t的backup peers,再通过 backup peers 找到 backup peer_t + +ngx_stream_upstream_rr_peers_s 结构注释 + +``` +struct ngx_stream_upstream_rr_peers_s { + ngx_uint_t number; // 下游服务器数量 + +#if (NGX_STREAM_UPSTREAM_ZONE) + ngx_slab_pool_t *shpool; + ngx_atomic_t rwlock; + ngx_stream_upstream_rr_peers_t *zone_next; +#endif + + ngx_uint_t total_weight; // 所有服务器总权重 + + unsigned single:1; // 此upstream只有一台下游 + unsigned weighted:1; + + ngx_str_t *name; + + ngx_stream_upstream_rr_peers_t *next; // 存放全部backups下游 + + ngx_stream_upstream_rr_peer_t *peer; // 存放全部下游 +}; +``` + +ngx_stream_upstream_rr_peer_s 结构注释 + +```c +struct ngx_stream_upstream_rr_peer_s { + struct sockaddr *sockaddr; // 后端地址 + socklen_t socklen; // 后端地址长度 + ngx_str_t name; // 后端名称 + ngx_str_t server; + + ngx_int_t current_weight; // 当前权重,运行中动态调整 + ngx_int_t effective_weight; + ngx_int_t weight; // 配置的权重 + + ngx_uint_t conns; + ngx_uint_t max_conns; + + ngx_uint_t fails; + time_t accessed; + time_t checked; + + ngx_uint_t max_fails; // 最大失败次数 + time_t fail_timeout; // 多长时间内出现max_fails次失败,认为后端down + ngx_msec_t slow_start; + ngx_msec_t start_time; + + ngx_uint_t down; // 指定此后端down + + void *ssl_session; + int ssl_session_len; + +#if (NGX_STREAM_UPSTREAM_ZONE) + ngx_atomic_t lock; +#endif + + ngx_stream_upstream_rr_peer_t *next; + + NGX_COMPAT_BEGIN(25) + NGX_COMPAT_END +}; +``` + +![image](https://user-images.githubusercontent.com/24750337/188052512-c07e011e-760d-42bb-9e3e-194c90f1e3e1.png) + +## 部分nginx struct剖析 + +### ngx_command_t + +```c +struct ngx_command_s { + ngx_str_t name; + ngx_uint_t type; + char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + ngx_uint_t conf; + ngx_uint_t offset; + void *post; +}; + +// example + +static ngx_command_t ngx_http_dubbo_commands[] = { + + { ngx_string("dubbo_pass"), // name + NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE4, // type + ngx_http_dubbo_pass, // set + NGX_HTTP_LOC_CONF_OFFSET, // conf + 0, // offset + NULL /* post */}, + ngx_null_command +}; +``` + +nginx回调set(ngx_http_dubbo_pass),并根据 conf(NGX_HTTP_LOC_CONF_OFFSET),设置 cf->ctx + +offset 只有和set配合才有意义,由于nginx有很多需要将配置项存放到结构体的需求,所以nginx提供了形如 ngx_conf_set_foo_slot 的函数,用于批量解决此需求。 + +这些ngx_conf_set_foo_slot需要知道将value放到结构体的哪个位置,因此在ngx_command_t上就须填写offset,由nginx计算位置并存放。 + +当然你完全可以将offset置为0,但这意味着需要针对每条directive都编写set,将 `void * conf` 转换成具体的结构体类型,通过 `a->b`设置。 + +比如nginx提供的一个函数,通过`cmd->offset`获取要set的struct成员并赋值 + +```c +char * +ngx_conf_set_num_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *p = conf; + + ngx_int_t *np; + ngx_str_t *value; + ngx_conf_post_t *post; + + + np = (ngx_int_t *) (p + cmd->offset); + + if (*np != NGX_CONF_UNSET) { + return "is duplicate"; + } + + value = cf->args->elts; + *np = ngx_atoi(value[1].data, value[1].len); + if (*np == NGX_ERROR) { + return "invalid number"; + } + + if (cmd->post) { + post = cmd->post; + return post->post_handler(cf, post, np); + } + + return NGX_CONF_OK; +} + +``` + +### ngx_conf_t + +```c +struct ngx_conf_s { + char *name; + ngx_array_t *args; + + ngx_cycle_t *cycle; + ngx_pool_t *pool; + ngx_pool_t *temp_pool; + ngx_conf_file_t *conf_file; + ngx_log_t *log; + + void *ctx; + ngx_uint_t module_type; + ngx_uint_t cmd_type; + + ngx_conf_handler_pt handler; + void *handler_conf; +#if (NGX_SSL && NGX_SSL_ASYNC) + ngx_flag_t no_ssl_init; +#endif +}; + +// example module +ngx_module_t ngx_stream_upsync_module = { + NGX_MODULE_V1, + &ngx_stream_upsync_module_ctx, /* module context */ + ngx_stream_upsync_commands, /* module directives */ + NGX_STREAM_MODULE, /* module type */ + NULL, /* init master */ + ngx_stream_upsync_init_module, /* init module */ + ngx_stream_upsync_init_process, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + ngx_stream_upsync_clear_all_events, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; +``` + +重点说下 `void* ctx` +ngx_conf_s 在配置解析过程中使用,module type定义了 ctx 存放的类型。上述 ngx_stream_upsync_module 为 stream module,所以解析stream block时,包括 stream upstream 下,ngx_conf_t#ctx总是指向 stream 的 ctx 配置 `ngx_stream_conf_ctx_t` + +### ngx_str_t + +```c +typedef struct { + size_t len; + u_char *data; +} ngx_str_t; + +``` + +len的长度不一定包含 `\0`,如果`ngx_str_t`由字符串常量初始化而来,比如`ngx_string("hello")`, `sizeof("hello")`为 5+1,而此时len却是5,确保一定是null结尾,方便传给syscall + + +```c +var->len = 2; + var->data = (u_char *) "TZ"; +``` + +〉 The `len` field holds the string length and `data` holds the string data. The string, held in `ngx_str_t`, may or may not be null-terminated after the `len` bytes. In most cases it’s not. However, in certain parts of the code (for example, when parsing configuration), `ngx_str_t` objects are known to be null-terminated, which simplifies string comparison and makes it easier to pass the strings to syscalls + +## Module如何注册到Phase,参与请求的处理 + +上文说了 http core module 会在http module初始化中注册到phase,而第三方module则会在 + + +```c +static ngx_http_module_t ngx_http_foo_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_foo_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +static ngx_int_t +ngx_http_foo_handler(ngx_http_request_t *r) +{ + ngx_str_t *ua; + + ua = r->headers_in->user_agent; + + if (ua == NULL) { + return NGX_DECLINED; + } + + /* reject requests with "User-Agent: foo" */ + if (ua->value.len == 3 && ngx_strncmp(ua->value.data, "foo", 3) == 0) { + return NGX_HTTP_FORBIDDEN; + } + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_http_foo_init(ngx_conf_t *cf) +{ + ngx_http_handler_pt *h; + ngx_http_core_main_conf_t *cmcf; + + cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); + // http://nginx.org/en/docs/dev/development_guide.html#http_phases + // 将handler放到对应的phase list中 + h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers); + if (h == NULL) { + return NGX_ERROR; + } + + *h = ngx_http_foo_handler; + + return NGX_OK; +} +``` + +## 指令directive + + + +## master发送signal + + + +```c +for ( ;; ) { + // 省略无关代码... + // block 在 signal syscall + sigsuspend(&set); + + if (ngx_terminate) { + if (delay == 0) { + delay = 50; + } + + if (sigio) { + sigio--; + continue; + } + + sigio = ccf->worker_processes + 2 /* cache processes */; + + // signal 属于进程间通信 + // 将 signal 发送给worker process + if (delay > 1000) { + ngx_signal_worker_processes(cycle, SIGKILL); + } else { + ngx_signal_worker_processes(cycle, + ngx_signal_value(NGX_TERMINATE_SIGNAL)); + } + + continue; + } + // 省略无关代码... +} +``` + +## worker process 接受 master process signal + +master, worker signal 处理代码都在一起 + +```c +// https://github.com/iamwwc/nginx-debugable/blob/8a30c4b12226c92cd317db614172cc6de2795768/src/os/unix/ngx_process.c#L319 + +switch (ngx_process) { + +case NGX_PROCESS_MASTER: +case NGX_PROCESS_SINGLE: + switch (signo) { + + case ngx_signal_value(NGX_SHUTDOWN_SIGNAL): + ngx_quit = 1; + action = ", shutting down"; + break; + + case ngx_signal_value(NGX_TERMINATE_SIGNAL): + case SIGINT: + ngx_terminate = 1; + action = ", exiting"; + break; + + case ngx_signal_value(NGX_NOACCEPT_SIGNAL): + if (ngx_daemonized) { + ngx_noaccept = 1; + action = ", stop accepting connections"; + } + break; + + case ngx_signal_value(NGX_RECONFIGURE_SIGNAL): + ngx_reconfigure = 1; + action = ", reconfiguring"; + break; + + case ngx_signal_value(NGX_REOPEN_SIGNAL): + ngx_reopen = 1; + action = ", reopening logs"; + break; + + case ngx_signal_value(NGX_CHANGEBIN_SIGNAL): + if (ngx_getppid() == ngx_parent || ngx_new_binary > 0) { + + /* + * Ignore the signal in the new binary if its parent is + * not changed, i.e. the old binary's process is still + * running. Or ignore the signal in the old binary's + * process if the new binary's process is already running. + */ + + action = ", ignoring"; + ignore = 1; + break; + } + + ngx_change_binary = 1; + action = ", changing binary"; + break; + + case SIGALRM: + ngx_sigalrm = 1; + break; + + case SIGIO: + ngx_sigio = 1; + break; + + case SIGCHLD: + ngx_reap = 1; + break; + } + + break; + +case NGX_PROCESS_WORKER: +case NGX_PROCESS_HELPER: + switch (signo) { + + case ngx_signal_value(NGX_NOACCEPT_SIGNAL): + if (!ngx_daemonized) { + break; + } + ngx_debug_quit = 1; + /* fall through */ + case ngx_signal_value(NGX_SHUTDOWN_SIGNAL): + // worker, helper process 收到 shutdown signal后会设置ngx_quit + // 而此时 worker 一直在 ngx_worker_process_cycle 函数中, + // 当检测到 ngx_quit = 1后会关闭listening socket优雅退出 + // https://github.com/iamwwc/nginx-debugable/blob/f02e2a734ef472f0dcf83ab2e8ce96d1acead8a5/src/os/unix/ngx_process_cycle.c#L728 + ngx_quit = 1; + action = ", shutting down"; + break; + + case ngx_signal_value(NGX_TERMINATE_SIGNAL): + case SIGINT: + ngx_terminate = 1; + action = ", exiting"; + break; + + case ngx_signal_value(NGX_REOPEN_SIGNAL): + ngx_reopen = 1; + action = ", reopening logs"; + break; + + case ngx_signal_value(NGX_RECONFIGURE_SIGNAL): + case ngx_signal_value(NGX_CHANGEBIN_SIGNAL): + case SIGIO: + action = ", ignoring"; + break; + } + + break; +} +``` + +## worker listen socket 解决listen冲突 + +Linux Kernel 支持 [SO_REUSEPORT](https://github.com/iamwwc/nginx-debugable/blob/8a30c4b12226c92cd317db614172cc6de2795768/src/core/ngx_connection.c#L297),允许多process accept 同一个socket,socket将按照一定的算法传给不同process + +## 内存池 + +pool并不是说需要内存都从pool里申请,而是在进行一系列操作的时候发现需要频繁申请内存,这时候可以 `ngx_create_pool` 申请pool,之后这些操作都在此pool中进行。 + +全部操作都完成后,调用 `ngx_destroy_pool`就可以。所以 ngx_pfree 并不会释放小内存块 + +比如我们有个逻辑,heap上存了指向其他heap的pointer,如果为了释放这些内存,就需要每个字段都调用free,如果后期涉及到这里的更改,就必须添加上心字段的free,维护成本很高 + +而如果采用pool,逻辑都从pool里分配,只需要在逻辑开头create pool,在逻辑结束后destroy pool就可以,**非常好用** + +## http请求运行流程 + + + +```c +// 向 cycle->listening 添加 `ngx_listening_t*`结构,并注册 ngx_http_init_connection 回调函数 +ls->handler = ngx_http_init_connection; +``` + + + +```c +rev = c->read; +// 注册 socket on read handler, 用于解析 http request +rev->handler = ngx_http_wait_request_handler; +// 暂时没有要写的,先置空 +c->write->handler = ngx_http_empty_handler; +``` + +ngx_http_wait_request_handler 函数中从socket读取request + + +```c +// ngx_http_wait_request_handler +n = c->recv(c, b->last, size); +// 省略代码..... +// request读取完成,创建 ngx_http_request_t +c->data = ngx_http_create_request(c); +if (c->data == NULL) { + ngx_http_close_connection(c); + return; +} + +rev->handler = ngx_http_process_request_line; +// 开始处理 http 协议 +ngx_http_process_request_line(rev); + +``` + +```c +// ngx_http_process_request_line +// 处理 header +rev->handler = ngx_http_process_request_headers; +ngx_http_process_request_headers(rev); +``` + +http 调用栈 + +```c +ngx_http_core_run_phases (/data00/codes/tengine/src/http/ngx_http_core_module.c:941) + +// 走nginx http phase +ngx_http_handler (/data00/codes/tengine/src/http/ngx_http_core_module.c:930) + +// handler处理结束,之后就是body,request解析完成 +ngx_http_process_request (/data00/codes/tengine/src/http/ngx_http_request.c:2227) + +处理request header +ngx_http_process_request_headers (/data00/codes/tengine/src/http/ngx_http_request.c:1622) + +// 处理 request line +ngx_http_process_request_line (/data00/codes/tengine/src/http/ngx_http_request.c:1283) +ngx_http_wait_request_handler (/data00/codes/tengine/src/http/ngx_http_request.c:535) +ngx_epoll_process_events (/data00/codes/tengine/src/event/modules/ngx_epoll_module.c:972) +ngx_process_events_and_timers (/data00/codes/tengine/src/event/ngx_event.c:260) +ngx_single_process_cycle (/data00/codes/tengine/src/os/unix/ngx_process_cycle.c:346) +main (/data00/codes/tengine/src/core/nginx.c:415) +__libc_start_main (@__libc_start_main:61) +_start (@_start:14) +``` + +从socket一次recv可能不是完成的http request,就需要暂存已有buffer,并返回,下一次recv从上一次结束处重新开始 + +当涉及到 NGX_AGAIN,nginx往往都会创建一个struct,将buf保存起来,并保证此函数重入是正确的 + +比如下面代码 + +```c +static void +ngx_http_process_request_line(ngx_event_t *rev) +{ + ssize_t n; + ngx_int_t rc, rv; + ngx_str_t host; + ngx_connection_t *c; + ngx_http_request_t *r; + + c = rev->data; + r = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "http process request line"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + c->timedout = 1; + ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + rc = NGX_AGAIN; + + for ( ;; ) { + + if (rc == NGX_AGAIN) { + // NGX_AGAIN表示数据不完整,返回,等此函数重新被调用 + n = ngx_http_read_request_header(r); + + if (n == NGX_AGAIN || n == NGX_ERROR) { + break; + } + } + } +} + +``` diff --git "a/_posts/6-Cgroup\351\231\220\345\210\266\345\206\205\345\255\230\344\270\216\350\212\202\347\202\271\347\232\204\345\210\240\351\231\244.md" "b/_posts/6-Cgroup\351\231\220\345\210\266\345\206\205\345\255\230\344\270\216\350\212\202\347\202\271\347\232\204\345\210\240\351\231\244.md" new file mode 100644 index 0000000..79162ab --- /dev/null +++ "b/_posts/6-Cgroup\351\231\220\345\210\266\345\206\205\345\255\230\344\270\216\350\212\202\347\202\271\347\232\204\345\210\240\351\231\244.md" @@ -0,0 +1,113 @@ +--- +title: Cgroup限制内存与节点的删除 +date: 2019-09-11 +updated: 2019-09-11 +issueid: 6 +tags: +--- +首先不限制内存,让我们常见一个进程 + +``` +stress --vm-bytes 200m --vm-keep -m 1 +``` + +如下图,机器 `2G Mem` +共占用 `10%`, `200mb` + +用 `top` 命令观察 + +![](https://raw.githubusercontent.com/iamwwc/noterepo/master/images/image_10.png) + + +现在利用 `Cgroup` 限制下内存 + +``` +mkdir /sys/fs/cgroup/memory/testmem -p +cd /sys/fs/cgroup/memory/testmem +# 将当前bash pid 写入 tasks +echo $$ > tasks +echo 100m > memory.limit_in_bytes +``` + +然后在**当前bash**中启动 + +``` +stress --vm-bytes 200m --vm-keep -m 1 +``` + +另开一个shell, `top` 观察 + +![](https://raw.githubusercontent.com/iamwwc/noterepo/master/images/image_11.png) + +可以看到,第二个进程的内存被限制到了 `100m` + +### 删除 Cgroup + +由于每个 Cgroup 都是完整的文件夹,所以我当时的方法是直接递归删除文件 + +``` +rm -rf ./testm +``` + +结果报了成堆的如下错误。 + +``` +rm: cannot remove 'testm/cgroup.procs': Operation not permitted +``` + +后来查了下 + +`http://blog.tinola.com/?e=21` + +我们无法删除这些文件,但可以删除文件夹 + +``` +rmdir ./testm +``` + +但由于当前 `bash` 的 `pid` 已经写入了 `testm/tasks` + +在删除这个文件夹之前需要将pid移到 包含 testm 的文件夹的 `tasks` 当中 + +``` +cd /sys/fs/cgroup/memory/ +echo $$ >> tasks +``` + +最后执行删除操作 + +``` +rmdir ./testm +``` + +还有一个更方便的 `cmd` + +他会帮你将子 `group` 下 `tasks` 当中的 `pid` 全部移动到 `root` 当中 + +先创建嵌套 `cgroup` + +``` +cd /sys/fs/cgroup/memory/ +mkdir wwc/w1 +echo $$ > wwc/w1/tasks +``` + +如果我们直接 + +``` +rmdir wwc/ +``` + +会直接报错 + +``` +rmdir: failed to remove 'wwc': Device or resource busy +``` + +手动移除需要我们将嵌套 `cgroup` 中的 `pid` 全部移动到 `memory/tasks` + +这时就可以借助 + +``` +cgdelete -r memory:wwc +``` \ No newline at end of file diff --git "a/_posts/60-mt7921-usb\351\251\261\345\212\250\346\263\250\345\206\214\345\210\206\346\236\220.md" "b/_posts/60-mt7921-usb\351\251\261\345\212\250\346\263\250\345\206\214\345\210\206\346\236\220.md" new file mode 100644 index 0000000..7e2259f --- /dev/null +++ "b/_posts/60-mt7921-usb\351\251\261\345\212\250\346\263\250\345\206\214\345\210\206\346\236\220.md" @@ -0,0 +1,180 @@ +--- +date: 2023-11-03 +updated: 2023-11-14 +issueid: 60 +tags: +- Kernel +title: mt7921-usb驱动注册分析 +--- +以联发科 `mt7921` 无线网卡驱动为例 + +> kernel `mt7921/` 下有pci,usb接口的驱动,usb接口驱动入口是`usb.c`。手里有mt7921芯片的USB无线网卡,对应的驱动为mt7921u + +pci驱动为 `mt7921e` +USB接口 MT7921 外观 +![usb](/assets/2023-10-10-15-15-36.png) + +PCI接口的MT7921网卡外观 + +![pci-e](/assets/2023-10-10-15-16-09.png) + +驱动采用USB总线注册到kernel,使用kernel提供的 `module_usb_driver(mt7921u_driver);`,调用 `usb_register_driver`进行注册 + +![](/assets/2023-10-10-15-17-27.png) + +kernel驱动采用callback式,,linux为各种接口设备提供helper + +驱动使用kernel `module_usb_driver`进行注册 + +```c +#define module_usb_driver(__usb_driver) \ + module_driver(__usb_driver, usb_register, \ + usb_deregister) +``` + +经过usb的一系列宏替换,`module_init`调用usb的通用init函数 `usb_register` 初始化,最终调用 `struct usb_driver#probe` 开始驱动初始化 + +```c +#define module_driver(__driver, __register, __unregister, ...) \ +static int __init __driver##_init(void) \ +{ \ + return __register(&(__driver) , ##__VA_ARGS__); \ +} \ +module_init(__driver##_init); \ +static void __exit __driver##_exit(void) \ +{ \ + __unregister(&(__driver) , ##__VA_ARGS__); \ +} \ +module_exit(__driver##_exit); + +``` + +usb 是接口类型,usb接口探测完成后,驱动调用kernel 80211一系列helper,在kernel中注册为无线网卡。 + +![call stack](/assets/2023-10-10-15-16-58.png) + +驱动的80211回调。有数据需要发送kernel则调用`.tx` + +```c +const struct ieee80211_ops mt7921_ops = { + .tx = mt792x_tx, + .start = mt7921_start, + .stop = mt7921_stop, + .add_interface = mt7921_add_interface, + .remove_interface = mt792x_remove_interface, + .config = mt7921_config, + .conf_tx = mt792x_conf_tx, + .configure_filter = mt7921_configure_filter, + .bss_info_changed = mt7921_bss_info_changed, + .start_ap = mt7921_start_ap, + .stop_ap = mt7921_stop_ap, + .sta_state = mt7921_sta_state, + .sta_pre_rcu_remove = mt76_sta_pre_rcu_remove, + .set_key = mt7921_set_key, + .sta_set_decap_offload = mt7921_sta_set_decap_offload, +#if IS_ENABLED(CONFIG_IPV6) + .ipv6_addr_change = mt7921_ipv6_addr_change, +#endif /* CONFIG_IPV6 */ + .ampdu_action = mt7921_ampdu_action, + .set_rts_threshold = mt7921_set_rts_threshold, + .wake_tx_queue = mt76_wake_tx_queue, + .release_buffered_frames = mt76_release_buffered_frames, + .channel_switch_beacon = mt7921_channel_switch_beacon, + .get_txpower = mt76_get_txpower, + .get_stats = mt792x_get_stats, + .get_et_sset_count = mt792x_get_et_sset_count, + .get_et_strings = mt792x_get_et_strings, + .get_et_stats = mt792x_get_et_stats, + .get_tsf = mt792x_get_tsf, + .set_tsf = mt792x_set_tsf, + .get_survey = mt76_get_survey, + .get_antenna = mt76_get_antenna, + .set_antenna = mt7921_set_antenna, + .set_coverage_class = mt792x_set_coverage_class, + .hw_scan = mt7921_hw_scan, + .cancel_hw_scan = mt7921_cancel_hw_scan, + .sta_statistics = mt792x_sta_statistics, + .sched_scan_start = mt7921_start_sched_scan, + .sched_scan_stop = mt7921_stop_sched_scan, + CFG80211_TESTMODE_CMD(mt7921_testmode_cmd) + CFG80211_TESTMODE_DUMP(mt7921_testmode_dump) +#ifdef CONFIG_PM + .suspend = mt7921_suspend, + .resume = mt7921_resume, + .set_wakeup = mt792x_set_wakeup, + .set_rekey_data = mt7921_set_rekey_data, +#endif /* CONFIG_PM */ + .flush = mt792x_flush, + .set_sar_specs = mt7921_set_sar_specs, + .remain_on_channel = mt7921_remain_on_channel, + .cancel_remain_on_channel = mt7921_cancel_remain_on_channel, + .add_chanctx = mt7921_add_chanctx, + .remove_chanctx = mt7921_remove_chanctx, + .change_chanctx = mt7921_change_chanctx, + .assign_vif_chanctx = mt792x_assign_vif_chanctx, + .unassign_vif_chanctx = mt792x_unassign_vif_chanctx, + .mgd_prepare_tx = mt7921_mgd_prepare_tx, + .mgd_complete_tx = mt7921_mgd_complete_tx, +}; +``` + +追踪 `.tx`调用路径 +![](/assets/2023-10-10-15-16-34.png) + +## 收包送到kernel协议栈路径 + +![](/assets/2023-10-10-15-16-46.png) + +调用路径有两个,pci 接口走的DMA,而USB接口则是kthread polling + +```c +int __mt76_worker_fn(void *ptr) +{ + struct mt76_worker *w = ptr; + + while (!kthread_should_stop()) { + set_current_state(TASK_INTERRUPTIBLE); + + if (kthread_should_park()) { + kthread_parkme(); + continue; + } + + if (!test_and_clear_bit(MT76_WORKER_SCHEDULED, &w->state)) { + schedule(); + continue; + } + + set_bit(MT76_WORKER_RUNNING, &w->state); + set_current_state(TASK_RUNNING); + // kthread 持续调用 mt76u_rx_worker + w->fn(w); + cond_resched(); + clear_bit(MT76_WORKER_RUNNING, &w->state); + } + + return 0; +} + +// https://github.com/torvalds/linux/blob/v6.6/drivers/net/wireless/mediatek/mt76/usb.c#L628 +static void mt76u_rx_worker(struct mt76_worker *w) +{ + struct mt76_usb *usb = container_of(w, struct mt76_usb, rx_worker); + struct mt76_dev *dev = container_of(usb, struct mt76_dev, usb); + int i; + + rcu_read_lock(); + // 遍历每个队列处理rx数据 + mt76_for_each_q_rx(dev, i) + mt76u_process_rx_queue(dev, &dev->q_rx[i]); + rcu_read_unlock(); +} +``` + +## qemu绑定mt7921u网卡,分析驱动调用栈 + +```bash +qemu-system-x86_64 --kernel ./arch/x86/boot/bzImage -initrd ./rootfs.cpio -device e1000,netdev=eth0 -netdev user,id=eth0,hostfwd=tcp::5555-:22,net=192.168.76.0/24,dhcpstart=192.168.76.9 -append "nokaslr console=ttyS0" -S -nographic -gdb tcp::1234 -virtfs local,path=/,security_model=none,mount_tag=guestroot -usb -device usb-host,vendorid=0x0e8d,productid=0x7961 +``` + +![mt7921 probe调用栈/也是USB module调用栈](/assets/2023-10-17-20-30-16.png) diff --git a/_posts/61-Kernel-Net.md b/_posts/61-Kernel-Net.md new file mode 100644 index 0000000..7aa8d44 --- /dev/null +++ b/_posts/61-Kernel-Net.md @@ -0,0 +1,623 @@ +--- +title: Kernel-Net +date: 2023-11-03 +updated: 2023-11-03 +issueid: 61 +tags: +- Kernel +--- +## WI-FI 如何传输数据 + +无线通讯通过电磁波完成,发送端通过电流转换成电磁场,电磁场产生无线电波在空间中传递。利用这现象,将信息调制到无线电波上发送。计算机领域中,信息就是 0/1,通过电磁波的带宽表示(也可以用电磁波其他的概念组合表示,不一定是是带宽) + +接收端利用电磁感应原理,通过解调还原成最初的数据,所以 WI-FI 设备都带天线,[天线](https://zh.wikipedia.org/wiki/%E5%A4%A9%E7%BA%BF) + +> 数据在传输中,发送器会在天线上施加电流,施加的时变电压或时变电流而产生辐射的电磁场,使得电流的能量转变成无线电波。在接收时,天线会由于电场的感应,而在天线内部产生时变电流,并在其终端产生时变电压,产生电讯号经过处理之后,可以在接收器中观察或收听。天线被广泛应用于广播、点对点无线电通讯、雷达和太空探索等通讯系统。天线是无线电通讯系统中的必需组件。 + +维基百科对无线电通讯有个大体介绍 + +> [无线电 - 维基百科,自由的百科全书](https://zh.wikipedia.org/wiki/%E6%97%A0%E7%BA%BF%E7%94%B5) + +### 调制解调 +WI-FI通过电磁波发送数据,不同的WI-FI版本,a/b/ax/n就是在研究如何调制解调,如何将收到的无线电波解码成多个原始电波 +> [调变 - 维基百科,自由的百科全书](https://zh.wikipedia.org/zh-hans/%E8%AA%BF%E8%AE%8A) +> [正交频划分多路复用](https://en.wikipedia.org/wiki/Orthogonal_frequency-division_multiplexing) +![](/assets/2023-10-30-1.png) + +## device driver 和 firmware 区别 + +设备驱动运行在软件层面,OS 级别。驱动是内核相关,不同的内核由于有不同的架构,所以驱动要单独开发 + +固件运行在具体的外部硬件上,有独立的板载 CPU/RAM/闪存。负责执行 OS 驱动通过 PCIE/GPIO 传递过来的指令,转换为电路表示。由于固件绑定在硬件,所以固件只有一套,始终跟随此硬件,不像驱动,不同的 OS 有多套 + +[Why do firmware and drivers have to be separate? - Super User](https://superuser.com/questions/1758062/why-do-firmware-and-drivers-have-to-be-separate/1758177#1758177) + +不是全部的硬件都有固件,有些硬件逻辑很简单,完全是电路表达,所以不需要固件。一些复杂硬件,比如无线网卡才会单独设计固件,加上微控制器(类似 CPU)执行加减乘除,数据 mov 指令。 + +固件和硬件绑定,并不会独立向外提供,大多数固件都是闭源的。固件代码需要运行到微控制器,编译器要将代码编译到适合的架构上,比如 ARM/X86,如果微控制器是新的架构,编译器不支持,又需要为编译器支持新的后端 + +再额外说一句,主板总线可以连接 CPU,RAM,网卡,还有一种工艺是将这些设备都封装进类似 CPU 那种芯片中,被称为 Soc(System on chip) + +> SoCs are in contrast to the common traditional motherboard-based PC architecture, which **separates components based on function and connects them through a central interfacing circuit board**. Whereas a motherboard houses and connects detachable or replaceable components, SoCs integrate all of these components into a single integrated circuit.**An SoC will typically integrate a CPU, graphics and memory interfaces,[nb 2] secondary storage and USB connectivity, I/O interfaces on a single chip**, whereas a motherboard would connect these modules as discrete components or expansion cards. +> +> An **SoC integrates a microcontroller, microprocessor or perhaps several processor cores with peripherals like a GPU, Wi-Fi and cellular network radio modems, and/or one or more coprocessors**. Similar to how a microcontroller integrates a microprocessor with peripheral circuits and memory, an SoC can be seen as integrating a microcontroller with even more advanced peripherals. For an overview of integrating system components, see system integration. + +固件也可以从文件系统加载:驱动程序通过 `request_firmware`向 kernel 找到所需固件后,再传递给硬件。可以做到对固件的 patch + +所以 kernel 要控制硬件,光有 driver 不够,还需要对应的固件程序 + +> + +## 总线接口 + +计算机就是个电路,如何将不同的硬件连接到一起涉及到总线和接口 + +PCIe 属于内部总线接口/架构,通过 PCIE 接口,外部硬件可以直连 CPU,提升了性能。 + + +USB 是外部接口,连接计算机外的硬件 + + +> 当然,这些不过是架构/接口类型,核心在传递数据,主板增加 USB 接口也不是做不到 + + + + +请问下 linux 下的无线网卡驱动开发? - 纸飞机的回答 - 知乎 + + +一个简单的内核 wireless 驱动 + + +传统主板结构:分南北桥 + +![motherboard block diagram](/assets/2023-10-12-11-28-41.png) + +上图可以看到,内存在北桥直接和CPU连接,而外部硬件设备则在南桥,通过PCIE/ISA等总线控制器与CPU间接互联 + +SOC总线结构将大多数功能都集成在一块芯片,所以 SOC 主板大多数都直连芯片 + +![soc motherboard arch](/assets/2023-10-12-11-27-04.png) + +> 软件依赖硬件,看明白总线结构,也就明白了很多软件的概念 + +## 中断 + +内核是软硬件交互的接口,驱动需要响应硬件中断。 + +之前理解外部设备发送中断需要有单独的中断 pin 脚,挂载到总线中断 pin 才能将中断发送给 CPU。局限是 CPU 的 pin 脚数量是固定的,能发起的中断号数量也固定,不能无限制分配。为了支持更多的中断,总线就需要更多的 pin 用于中断。有些设备还需要共享同一个中断,中断触发后驱动轮巡共享中断的设备,设备都去检查自身的状态看中断是不是属于自己的。如果用不了这么多中断号,那多余的 pin 就是浪费。 + +使用 PCI-E 总线结构的硬件设备支持 MSI,不需要 pin 脚。中断发生后,硬件设备将中断号写入中断控制器的中断寄存器,或者其他 MMIO 的地址。中断控制器再将中断信息通过内存送到 CPU + + + +开发驱动需要向内核申请中断号并注册中断回调 + +```c +retval = request_irq(priv->pdev->irq, adm8211_interrupt, + IRQF_SHARED, "adm8211", dev); +``` + +[Difference between request_irq and \_\_interrupt](https://stackoverflow.com/a/6246352/7529562) + +```c +// 向内核申请中断号 +platform_get_irq(..) +platform_get_irq_optional(..) +// 注册中断回调 +request_threaded_irq(..) +// this function takes the +// same arguments and performs the same function as +// request_threaded_irq(). IRQs requested with this function will be +// automatically freed on driver detach. +devm_request_threaded_irq(..) +``` + +并不是每个驱动都要先申请中断号,有些硬件会写死中断号,驱动直接注册中断回调 + +## 驱动如何作为内核模块被加载 + +内核提供 kernel module 来扩展内核能力,驱动大多数是以 module 来开发,下面分析下 driver module 如何注册到内核 + +经典的 kernel module 注册使用 +内核调用 init 初始化内核模块 + +```c +#include +module_init(..) +module_exit(..) +``` + +```c +#include +module_platform_driver(..) +// or +#include +module_pci_driver(..) +``` + +驱动类型不同,注册的方式也不同,但内部都调用`module_driver(__pci_driver, pci_register_driver, pci_unregister_driver)`宏 + +驱动懒加载运行。硬件设备插入内核后,驱动代码才会运行,所以 driver module 将 module_init 导向内核驱动 register 函数 + +```c +// https://github.com/torvalds/linux/blob/63b823d7d3cd275c3347233f95bdf966a595dbc8/drivers/base/driver.c#L222 +/** + * driver_register - register driver with bus + * @drv: driver to register + * + * We pass off most of the work to the bus_add_driver() call, + * since most of the things we have to do deal with the bus + * structures. + */ +int driver_register(struct device_driver *drv){ + //... +} +``` + +## 驱动加载流程 + +### PCI-e 总线结构为例 + +硬件插入 PCI-E 总线后内核遍历 PCIE slot,读取设备信息,包含硬件 name,vendor,想使用的中断号 + +从 PCIE controller 读取插在 pcie slot 上的设备的代码调用栈 + +![call stack](/assets/2023-10-10-15-13-36.png) + +```txt +https://github.com/torvalds/linux/blob/69c42d493db452ea87c1ac56e83c978512f4e6ec/arch/x86/pci/direct.c#L21-L50 +``` + + + + + +```c +// 代码有删减 +// https://github.com/torvalds/linux/blob/d0b7b3a422f1f550a1bac9fc3404196c10232d14/drivers/pci/probe.c#L1830 +int pci_setup_device(struct pci_dev *dev) +{ + u32 class; + u16 cmd; + u8 hdr_type; + int err, pos = 0; + struct pci_bus_region region; + struct resource *res; + + hdr_type = pci_hdr_type(dev); +// 初始化dev结构,此结构就是驱动加载后的dev + dev->sysdata = dev->bus->sysdata; + dev->dev.parent = dev->bus->bridge; + dev->dev.bus = &pci_bus_type; + dev->hdr_type = hdr_type & 0x7f; + dev->multifunction = !!(hdr_type & 0x80); + dev->error_state = pci_channel_io_normal; + set_pcie_port_type(dev); + + /* + * Assume 32-bit PCI; let 64-bit PCI cards (which are far rarer) + * set this higher, assuming the system even supports it. + */ + dev->dma_mask = 0xffffffff; + + dev_set_name(&dev->dev, "%04x:%02x:%02x.%d", pci_domain_nr(dev->bus), + dev->bus->number, PCI_SLOT(dev->devfn), + PCI_FUNC(dev->devfn)); + + + /* Need to have dev->cfg_size ready */ + set_pcie_thunderbolt(dev); + + set_pcie_untrusted(dev); + + /* "Unknown power state" */ + dev->current_state = PCI_UNKNOWN; + + /* Early fixups, before probing the BARs */ + pci_fixup_device(pci_fixup_early, dev); + + pci_set_removable(dev); + + // 从PCI设备读取硬件中断号 + pci_read_irq(dev); + pci_read_bases(dev, 1, 0); + pci_read_config_word(dev, PCI_CB_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor); + pci_read_config_word(dev, PCI_CB_SUBSYSTEM_ID, &dev->subsystem_device); + + /* We found a fine healthy device, go go go... */ + return 0; +} +``` + +设备信息初始化完成后,依次调用 + +```txt +pci_device_add +device_add +``` + +最终调用到内核驱动模块的回调 + +## 数据交互的几种方式 + +1. 通过 pin 脚,pinout 的组合数量决定了能够传输的数据量,此种方式要想增加传输的数据量,就需要加更多的 pin +2. pin 高电压保持的时间:较少的 pinout 能传递更多的数据,比如约定 pin 1 保持至少 10ns 的高电压则表示 1。局限:传递速度会慢 +3. 约定数据存放的位置:PCIE 设备会将硬件信息存放到固定的某个地址开始的 256 字节,内核要获取信息只需要读固定位置 +4. 内存传递:少量 pin 脚标识状态,而将更多的数据存放到约定的地址字段 + +## initcall - kernel init 初始化过程 + +追踪USB初始化路径时发现 `usb_init` 并没有显式调用,而是使用宏在 elf 的 `.section` 新增一条记录 + +```c +static void __exit usb_exit(void) +{ + /* This will matter if shutdown/reboot does exitcalls. */ + if (usb_disabled()) + return; + + usb_release_quirk_list(); + usb_deregister_device_driver(&usb_generic_driver); + usb_major_cleanup(); + usb_deregister(&usbfs_driver); + usb_devio_cleanup(); + usb_hub_cleanup(); + class_unregister(&usbmisc_class); + bus_unregister_notifier(&usb_bus_type, &usb_bus_nb); + bus_unregister(&usb_bus_type); + usb_acpi_unregister(); + usb_debugfs_cleanup(); + idr_destroy(&usb_bus_idr); +} + +subsys_initcall(usb_init); +``` + +kernel经常会使用 elf 的 `.section` 进行标记 + +usb_exit属于initcall的`subsys_initcall`,在kernel初始化阶段调用。kernel的module有些会依赖kernel子系统初始化完成,所以 initcall 来得比 `module_init` 更早 + +> + +initcall在booting时调用,而 `module_init` 在 `load_module`调用 + +```c +/* + * Allocate and load the module: note that size of section 0 is always + * zero, and we rely on this for optional sections. + */ +static int load_module(struct load_info *info, const char __user *uargs, + int flags) +{ + // ... +} + +``` + +kernel 并不是一定从内存`0x0000`地址加载,以uboot为例,kernel*可以*从 `0x10000000`地址启动 + +```text +## Booting kernel from Legacy Image at 02004000 ... + Image Name: Linux-4.9.0-xilinx + Image Type: ARM Linux Kernel Image (uncompressed) + Data Size: 4670720 Bytes = 4.5 MiB + Load Address: 10000000 + Entry Point: 10000000 +``` + +elf的地址从0开始,kernel计算 `启动地址 + .section initcall相对于0的偏移地址` 就可以获得 initcall 的实际地址,从而调用initcall初始化 + +netlink initcall 调用栈 + +![initcall example](/assets/2023-10-14-13-45-03.png) + +> [initcall](/assets/2020_ELCE_initcalls_myjosserand.pdf) + +## 虚拟内存与缺页中断(page fault) + +Q: 之前有个疑问,CPU执行process代码采用的是绝对地址,本身汇编访存并没有进行虚拟内存到物理内存转换,CPU是如何发现地址不存在的? + +A: 虚拟内存到物理内存的映射不是OS做的,而是CPU内部的内存管理单元(MMU)负责 + +MMU在CPU内部,kernel和MMU的交互属于 arch-related + +在linux源码中,`linux/arch/*/mm`存放kernel不同架构和MMU的交互逻辑 + +CPU负责地址映射,需要的地址在页表查不到,会产生 page fault interupt,kernel ISR(interupt service routine)检查缺页中断,将内存页从磁盘复制到内存,将调入的内存块在MMU中建立映射 + +> +> +> +> +> 构建不需要MMU,进程使用物理地址访问内存的kernel + +内核和用户进程一样也使用虚拟内存,内核分配函数分配的也是虚拟内存。和用户进程不同的是,内核还控制页表,当内存找不到访问页,kernel负责页换入/换出 + +> +> +> The kernel normally uses virtual addresses. Any address returned by +kmalloc(), vmalloc(), and similar interfaces is a virtual address and can +be stored in a ``void *``. + +## DMA(直接内存访问) + +> + +IOMMU + +![MMU vs IOMMU](https://upload.wikimedia.org/wikipedia/commons/thumb/d/d6/MMU_and_IOMMU.svg/1920px-MMU_and_IOMMU.svg.png) + +> +> +> If the device supports DMA, the driver sets up a buffer using kmalloc() or +> a similar interface, which returns a virtual address (X). The virtual +> memory system maps X to a physical address (Y) in system RAM. The driver +> can use virtual address X to access the buffer, but the device itself +> cannot because DMA doesn't go through the CPU virtual memory system. +> +> In some simple systems, the device can do DMA directly to physical address +> Y. But in many others, there is IOMMU hardware that translates DMA +> addresses to physical addresses, e.g., it translates Z to Y. This is part +> of the reason for the DMA API: **the driver can give a virtual address X to +> an interface like dma_map_single(), which sets up any required IOMMU +> mapping and returns the DMA address Z. The driver then tells the device to +> do DMA to Z, and the IOMMU maps it to the buffer at address Y in system +> RAM.** + +DMA访问不经过CPU,自然就不会经过CPU内部的MMU。IO设备通过虚拟内存访问内存数据就无法进行,为了解决此问题,在总线上引入了IOMMU,作为MMU的替代负责虚拟内存到物理内存的转换 + +在一些架构上,`dma_address`可以是物理地址,但大多数情况下,都是bus address。设备需要IOMMU进行地址变换,才能访问到真正的RAM DMA地址。注意,bus address不是虚拟地址,可能是其他类型的地址,因为IOMMU负责地址变化,谁知道它怎么映射的呢,没准是任意的地址 + +kernel 使用物理地址记录外围设备的io地址,驱动无法使用,它需要 `ioremap()` 成虚拟地址,变成 `memory mapped io address` + +IOMMU和DMA控制器可以集成在主板,也可以集成到SOC,比如 H616 SOC + +![image](/assets/h616.png) + +驱动和设备通过DMA descriptors ring buffer通讯。device控制head,driver控制tail + +每个buffer存放指向 data 的指针,device将收到的数据放到data,driver从ring buffer取走(如果是net driver,创建skb_buff push到内核协议栈),创建新的buffer,加到descriptor ring buffer 的tail + +![](/assets/2023-10-10-15-15-09.png) + +[ixy-writing-user-space-network-drivers](/assets/ixy-writing-user-space-network-drivers.pdf) + +> 其他的userspace driver实现,不一定对齐Linux +> +> Both receive and transmit rings work in a similar way: the +driver programs a physical base address and the size of the +ring. It then fills the memory area with DMA descriptors, +i.e., pointers to physical addresses where the packet data is +stored with some metadata. Sending and receiving packets is +done by passing ownership of the DMA descriptors between +driver and hardware via a head and a tail pointer. The driver +controls the tail, the hardware the head. Both pointers are +stored in device registers accessible via MMIO +> +> Receiving Packets. The driver fills up the ring buffer +with physical pointers to packet buffers in start_rx_queue() +on startup. Each time a packet is received, the correspond- +ing buffer is returned to the application and we allocate a +new packet buffer and store its physical address in the DMA +descriptor and reset the ready flag. We also need a way to +translate the physical addresses in the DMA descriptor found +in the ring back to its virtual counterpart on packet reception. +This is done by keeping a second copy of the ring populated +with virtual instead of physical addresses, this is then used +as a lookup table for the translation. + +DMA desc ring + +![](/assets/2023-10-10-15-32-23.png) + +虽说一直提到DMA ring buffer,但DMA并不一定需要ring buffer。DMA提供双方互相访问的内存。DMA对双方在内存中以何种数据结构进行通讯没有强制要求,你可以用DMA内存实现ring buffer,将DMA address写到硬件。也可以用其他结构。 + +ringbuffer好处是容易实现 lockfree list,硬件和驱动分别从头和尾取/放 buffer + +总而言之,DMA只是硬件和驱动直接沟通的媒介,怎么沟通那是两方的事。 + +## 虚拟地址,物理地址,总线地址 + +物理地址:CPU物理地址 + +总线地址:host bridge的整个地址空间,外围设备的地址(IO地址)都在总线地址上 + +![](/assets/2023-10-10-15-14-59.png) + +虚拟地址:CPU虚拟地址 + +CPU是如何分别访问内存地址和外围设备的IO地址的? + +CPU 有单独的引脚标识此次内存访问是RAM,还是IO地址 +如果引脚高电压,访问的地址是IO地址,否则为RAM地址 + +总线控制器负责将访问重定向 + +> + +重新思考就会发现,内存访问不单单是访问 `0xffff` 那么简单,中间必然夹杂着其他协议。比如PCI-E 总线,访问地址时会涉及到PCIE总线通讯协议 + +## 网卡接受/发送队列 + +网卡有多个接受/发送队列,收到/发送的数据按照一定算法分配到不同的队列,充分利用CPU多核心的特性,不同核心从不同DMA buffer取数据。网卡有网卡芯片和ROM中存放的机器指令 + +```c +// https://github.com/torvalds/linux/blob/v6.6/drivers/net/wireless/mediatek/mt76/usb.c#L628 +static void mt76u_rx_worker(struct mt76_worker *w) +{ + struct mt76_usb *usb = container_of(w, struct mt76_usb, rx_worker); + struct mt76_dev *dev = container_of(usb, struct mt76_dev, usb); + int i; + + rcu_read_lock(); + // 遍历每个队列处理rx数据 + mt76_for_each_q_rx(dev, i) + mt76u_process_rx_queue(dev, &dev->q_rx[i]); + rcu_read_unlock(); +} +``` + +### GRO(Generic Receive Offload) + +和GRO相似的是LRO(large receive offload) + +核心原理: +将小包组装成大包再交给协议栈,减少协议栈处理包的数量,降低处理压力 +LRO会合并看到的全部包。但小包的TCPHeader可能不尽相同,如果合并成一个TCP包肯定有header要被丢弃,造成数据失真,甚至有些数据包及其依赖TCP header trick,合并会破坏原本的通信。 + +GRO是LRO的升级版。 +原理: + +为每种合并都定义合并规则,只有当这些数据字段相同才合并。 +所以GRO有很多类型,针对IP_GRO,TCP_GRO,VxLAN-GRO。 + +拿IP举例,如果涉及packet reordering,合并也会取消 + +合并规则如下 + +> + +## NAPI + +基于已有thread_struct封装的新的任务调度库 + +kernel thread通过thread_struct调度,napi本身封装了thread_struct,内部有kernel thread + +驱动初始化代码里创建napi结构,kernel会创建对应的调度上下文,napi 被 kernel调度执行,最后回调驱动代码 + +![](/assets/2023-10-10-15-14-23.png) + +为什么NAPI收到第一个包需要关闭中断? + +> 每收到一个包就触发中断会导致极高的CPU占用。NAPI模型采用polling方式避免了大量的中断触发 + +## RCU(read copy update) + +基于一点,对指针长度的内存数据写入是原子的。只有两个值,新值或旧值,没有中间态 + +如果地址总线长度32,那么针对对齐后的4字节写入是原子的 + +> [c - Does a CPU assigns a value atomically to memory? - Stack Overflow](https://stackoverflow.com/questions/2892899/does-a-cpu-assigns-a-value-atomically-to-memory) + +如果32长度,写入64长度数据,CPU不得不拆分成两次就不是原子写入 + +指针长度往往等于地址总线长度。可以认为:现代CPU中,对齐后的指针写入是原子操作 + +> https://www.kernel.org/doc/html/next/RCU/whatisRCU.html +> +> In contrast, RCU-based updaters typically take advantage of the fact that writes to **single aligned pointers are atomic on modern CPUs**, allowing atomic insertion, removal, and replacement of data items in a linked structure without disrupting readers. + +但还是需要 atomic interger + +CPU存在缓存,读取没有中间态,但读写有新旧之分,多核对同一内存值相加,结果不一定符合预期 + +> [If aligned memory writes are atomic, why do we need the sync/atomic package? | Dave Cheney](https://dave.cheney.net/2018/01/06/if-aligned-memory-writes-are-atomic-why-do-we-need-the-sync-atomic-package) + +参考 + +> [kernel.org/doc/Documentation/RCU/checklist.txt](https://www.kernel.org/doc/Documentation/RCU/checklist.txt) +> [pdos.csail.mit.edu/6.828/2023/lec/rcu-faq.txt](https://pdos.csail.mit.edu/6.828/2023/lec/rcu-faq.txt) +> [READ-COPY UPDATE: USING EXECUTION HISTORY TO SOLVE +CONCURRENCY PROBLEMS](/assets/2023-10-26-5.pdf) + +总结: + +`volatile int foo;`:最初给IO内存使用,IO内存访问有严格的顺序,确保foo的访问不会被reorder,每次访问都生成访存指令,修改后的值对其他CPU可见,编译器可以reorder其他 `non-volatile`值的访问。 + +如果希望foo的访问在bar之后,光volatile不够,还需要加内存屏障 +`__memory_barrier/__sync_synchronize` + +> [Compile-time memory barrier implementation](https://en.wikipedia.org/wiki/Memory_ordering#Compile-time_memory_barrier_implementation) +> [Runtime memory ordering](https://en.wikipedia.org/wiki/Memory_ordering#Runtime_memory_ordering) +> [Why is compiler allowed to reorder instructions around system calls? : r/cpp](https://www.reddit.com/r/cpp/comments/dh3hle/comment/f3n4lpq/?utm_source=share&utm_medium=web2x&context=3) + +原则上 volatile 和memory barrier是两个东西,现在的编译器提供的内置sync函数默认都会添加full memory barrier + +比如 +> GCC [\_\_sync\_bool\_compare\_and\_swap - IBM Documentation](https://www.ibm.com/docs/en/xl-c-and-cpp-aix/16.1?topic=functions-sync-bool-compare-swap#:~:text=A%20full%20memory%20barrier%20is%20created%20when%20this%20function%20is%20invoked.) +> Clang [Clang Language Extensions — Clang 18.0.0git documentation](https://clang.llvm.org/docs/LanguageExtensions.html#:~:text=The%20__sync_swap()%20builtin%20is%20a%20full%20barrier.) + + +atomic算是volatile和memory_barrier的合体。store/load 接收ordering参数,能够更细粒度控制 memory ordering,依赖CPU架构提供的指令(x86的LOCK_总线同步) + + +## 硬件中断/软中断/tasklet/threaded irq + +硬件中断优先级最高,内核需要马上停止其他工作来执行硬件中断handler。handler执行时间过长会影响系统稳定性,于是硬件中断handler将其他的工作以软中断的方式进行,tasklet就是依赖软中断的一个系统。软中断并没有自己的上下文,直接运行在kernel上下文,所以无法被像thread一样调度,执行时间长会影响稳定性。 + +现在逐渐废弃 tasklet,将handler都变为thread,由kernel调度器统一执行调度 + +如果全部软中断执行的时间太长,每个CPU单独的ksoftirqd会负责执行其他的软中断work。ksoftirqd会通过线程来执行软中断 + +> [Heuristics for software-interrupt processing [LWN.net]](https://lwn.net/Articles/925540/) + +## 中断处理程序(ISR)运行在哪个 CPU core? + +结论;中断控制器决定哪个core运行 ISR,当然,事无绝对,system boot阶段如果有中断会默认发送core0。 +x86有APIC,APIC通过总线连接CPU进行通讯 + +还是那句话,**计算机是人造的,解决问题的方法千千万,现在使用的方法清楚就行,不要抱着“为什么是这种方式,而不是那种”,就是选了这种方式罢了** + +![](/assets/2023-10-10-15-14-47.png) + +> [x86 - How does the CPU decide what core should handle a hardware interrupt? - Stack Overflow](https://stackoverflow.com/questions/58054305/how-does-the-cpu-decide-what-core-should-handle-a-hardware-interrupt/58054373#58054373) +> [multiprocessing - Multi-core CPU interrupts - Stack Overflow](https://stackoverflow.com/questions/49379899/multi-core-cpu-interrupts/49414183#49414183) +> [multiprocessing - Multi-core CPU interrupts - Stack Overflow](https://stackoverflow.com/questions/49379899/multi-core-cpu-interrupts) + +## 根文件系统 + +initial-rootfs是只读的,只用于kernel启动的初始化过程。有kernel Image,必要的二进制,比如 /sbin/init + +初始rootfs必须能被bootloader访问,可能是rootfs自身,又或者放在引导分区内 + +bootloader才能获得kernel image和初始rootfs,并将他们放到内存并启动kernel + +kernel启动后判断初始rootfs格式,选择挂载(ext2),还是先解压再挂载(cpio),可以从userspace系统获取必要的驱动,模块,初始化程序。 + +> [identify_ramdisk_image](https://github.com/torvalds/linux/blob/9aa5db95729cc6936eeb5b51868a57602f52bd3d/init/do_mounts_rd.c#L58) +> + +kernel内核态初始化完成后结尾调用 sbin/init 初始化用户进程。init进程根据 fstab(或者其他配置) 对 `/` 进行remount,重新挂载(remount)为磁盘憋处的真正文件系统(比如ext4) + +> [rootfs](https://en.wikipedia.org/wiki/Initial_ramdisk#Implementation) + +想想就行了,如果全流程都用initial rootfs,你修改内容每次都要重新更新它,或者再其上添加overlay + +现在debian也只会在某些情况,比如升级kernel版本,才会调用 `update-initramfs` 更新initial rootfs,给kernel启动时用 + +### initrd vs initramfs + +initrd: initial ram disk +initramfs: 就叫 initramfs + +> [initrd vs initramfs](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/filesystems/ramfs-rootfs-initramfs.rst) + +## 内存对齐 + +内存是线性的,但是逻辑上的线性,和实际的存储方式并不关联。线性内存方便从软件角度理解内存 + +内存条结构如下 +![memory-bank](/assets/2023-10-20-14-39-55.png) + +![memory-bank](/assets/2023-10-20-14-47-30.png) + +内存访问结构如下。 + +![内存单元通过矩阵排列,并不是线性一条线](/assets/2023-10-20-14-43-21.png) + +32位地址总线,CPU是32位寄存器。一次至多传递4字节数据。矩阵式排列最适合逐行扫描,一次性访问4字节,控制器一次就可以扫描完成 + +非对齐访问需要跨行访问,控制器先扫描第一行,将前两字节数据放到寄存器的低两字节,再扫描第二行放到寄存器高两字节,同样读取两字节,非对齐需要两次操作 + +![](/assets/2023-10-20-15-23-35.png) + +> https://stackoverflow.com/a/12881322/7529562 + +## 引用 + +[MHVLUG_2017-04_Network_Receive_Stack.pdf](/assets/MHVLUG_2017-04_Network_Receive_Stack.pdf) + +> +> +> . diff --git "a/_posts/62-\345\217\221\345\214\205callstack\345\210\206\346\236\220.md" "b/_posts/62-\345\217\221\345\214\205callstack\345\210\206\346\236\220.md" new file mode 100644 index 0000000..1f806b8 --- /dev/null +++ "b/_posts/62-\345\217\221\345\214\205callstack\345\210\206\346\236\220.md" @@ -0,0 +1,61 @@ +--- +updated: 2023-11-03 +issueid: 62 +tags: +- Kernel +title: 发包callstack分析 +date: 2023-11-03 +--- +## 内核发包路径分析 + +触发方式 + +```bash +curl http://baidu.com +``` + +### udp dns callstack + +![发包](/assets/2023-10-19-15-55-22.png) + +### tcp callstack + +![](/assets/2023-10-24-140558769.png) + +``` +__dev_xmit_skb (\root\codes\kernel-dev\linux\net\core\dev.c:3779) +__dev_queue_xmit (\root\codes\kernel-dev\linux\net\core\dev.c:4310) +dev_queue_xmit (\root\codes\kernel-dev\linux\include\linux\netdevice.h:3082) +neigh_hh_output (\root\codes\kernel-dev\linux\include\net\neighbour.h:529) +neigh_output (\root\codes\kernel-dev\linux\include\net\neighbour.h:543) +ip_finish_output2 (\root\codes\kernel-dev\linux\net\ipv4\ip_output.c:238) +__ip_finish_output (\root\codes\kernel-dev\linux\net\ipv4\ip_output.c:0) +NF_HOOK_COND (\root\codes\kernel-dev\linux\include\linux\netfilter.h:293) +ip_output (\root\codes\kernel-dev\linux\net\ipv4\ip_output.c:439) +dst_output (\root\codes\kernel-dev\linux\include\net\dst.h:458) +ip_local_out (\root\codes\kernel-dev\linux\net\ipv4\ip_output.c:128) +__ip_queue_xmit (\root\codes\kernel-dev\linux\net\ipv4\ip_output.c:543) +ip_queue_xmit (\root\codes\kernel-dev\linux\net\ipv4\ip_output.c:557) +__tcp_transmit_skb (\root\codes\kernel-dev\linux\net\ipv4\tcp_output.c:1415) +tcp_ack_snd_check (\root\codes\kernel-dev\linux\net\ipv4\tcp_input.c:5616) +tcp_rcv_state_process (\root\codes\kernel-dev\linux\net\ipv4\tcp_input.c:6738) +tcp_v4_do_rcv (\root\codes\kernel-dev\linux\net\ipv4\tcp_ipv4.c:1751) +__release_sock (\root\codes\kernel-dev\linux\net\core\sock.c:2983) +release_sock (\root\codes\kernel-dev\linux\net\core\sock.c:3520) +tcp_sendmsg (\root\codes\kernel-dev\linux\net\ipv4\tcp.c:1337) +inet_sendmsg (\root\codes\kernel-dev\linux\net\ipv4\af_inet.c:840) +sock_sendmsg_nosec (\root\codes\kernel-dev\linux\net\socket.c:730) +__sock_sendmsg (\root\codes\kernel-dev\linux\net\socket.c:745) +__sys_sendto (\root\codes\kernel-dev\linux\net\socket.c:2194) +__do_sys_sendto (\root\codes\kernel-dev\linux\net\socket.c:2206) +__se_sys_sendto (\root\codes\kernel-dev\linux\net\socket.c:2202) +__x64_sys_sendto (\root\codes\kernel-dev\linux\net\socket.c:2202) +do_syscall_x64 (\root\codes\kernel-dev\linux\arch\x86\entry\common.c:50) +do_syscall_64 (\root\codes\kernel-dev\linux\arch\x86\entry\common.c:80) +entry_SYSCALL_64 (\root\codes\kernel-dev\linux\arch\x86\entry\entry_64.S:120) +48 (@48..88:3) +``` + + +## 参考 +> [networking:kernel\_flow [Wiki]](https://wiki.linuxfoundation.org/networking/kernel_flow) \ No newline at end of file diff --git "a/_posts/63-e1000\346\224\266\345\214\205callstack\345\210\206\346\236\220.md" "b/_posts/63-e1000\346\224\266\345\214\205callstack\345\210\206\346\236\220.md" new file mode 100644 index 0000000..8269e26 --- /dev/null +++ "b/_posts/63-e1000\346\224\266\345\214\205callstack\345\210\206\346\236\220.md" @@ -0,0 +1,718 @@ +--- +title: e1000收包callstack分析 +date: 2023-11-03 +updated: 2023-11-03 +issueid: 63 +tags: +- Kernel +--- + +## 驱动层 + +驱动向内核注册 softirq,里面包含回调函数。驱动收到数据触发中断,kernel读取 + +```c +__netif_receive_skb_list_ptype(struct net_device * orig_dev, struct packet_type * pt_prev, struct list_head * head) (/data00/codes/linux/net/core/dev.c:5533) +__netif_receive_skb_list_core(struct list_head * head, bool pfmemalloc) (/data00/codes/linux/net/core/dev.c:5582) +__netif_receive_skb_list(struct list_head * head) (/data00/codes/linux/net/core/dev.c:5634) +netif_receive_skb_list_internal(struct list_head * head) (/data00/codes/linux/net/core/dev.c:5725) +gro_normal_list(struct napi_struct * napi) (/data00/codes/linux/include/net/gro.h:433) +gro_normal_list(struct napi_struct * napi) (/data00/codes/linux/include/net/gro.h:429) +napi_complete_done(struct napi_struct * n, int work_done) (/data00/codes/linux/net/core/dev.c:6065) +e1000_clean(struct napi_struct * napi, int budget) (/data00/codes/linux/drivers/net/ethernet/intel/e1000/e1000_main.c:3811) +__napi_poll(struct napi_struct * n, bool * repoll) (/data00/codes/linux/net/core/dev.c:6496) +napi_poll(struct list_head * repoll, struct napi_struct * n) (/data00/codes/linux/net/core/dev.c:6563) +net_rx_action(struct softirq_action * h) (/data00/codes/linux/net/core/dev.c:6696) +__do_softirq() (/data00/codes/linux/kernel/softirq.c:571) +do_softirq() (/data00/codes/linux/kernel/softirq.c:472) +do_softirq() (/data00/codes/linux/kernel/softirq.c:459) +``` + +### NAPI + +基于已有thread_struct封装的新的任务调度库 + +kernel thread通过thread_struct调度,napi本身封装了thread_struct,内部有kernel thread + +驱动初始化代码里创建napi结构,kernel会创建对应的调度上下文,napi 被 kernel调度执行,最后回调驱动代码 + +![](/assets/2023-10-10-15-14-23.png) + +为什么NAPI收到第一个包需要关闭中断? + +> 每收到一个包就触发中断会导致极高的CPU占用。NAPI模型采用polling方式避免了大量的中断触发 + +## 内核网络层 + +`receive_skb*` 有list和非list版本。list可以做GRO合并,根据条件合并多个skb,减少重复处理次数。以list版本继续分析 + +调用次序 + +1. [`netif_receive_skb_list`](https://elixir.bootlin.com/linux/v6.6-rc6/source/net/core/dev.c#L5803) +2. [`__netif_receive_skb_list_core`](https://elixir.bootlin.com/linux/v6.6-rc6/source/net/core/dev.c#L5603) +3. [`__netif_receive_skb_core`](https://elixir.bootlin.com/linux/v6.6-rc6/source/net/core/dev.c#L5315) +4. [`deliver_skb`](https://elixir.bootlin.com/linux/v6.6-rc6/source/net/core/dev.c#L2208) +回调 packet_type 指向的handler + +```c +static inline int deliver_skb(struct sk_buff *skb, + struct packet_type *pt_prev, + struct net_device *orig_dev) +{ + if (unlikely(skb_orphan_frags_rx(skb, GFP_ATOMIC))) + return -ENOMEM; + refcount_inc(&skb->users); + return pt_prev->func(skb, skb->dev, pt_prev, orig_dev); +} +``` + +### IP 层 + +ipv4 的handler在init阶段注册[inet_init](https://elixir.bootlin.com/linux/v6.6-rc6/source/net/ipv4/af_inet.c#L2051) + +deliver_skb -> [ip_rcv](https://elixir.bootlin.com/linux/v6.6-rc6/source/net/ipv4/ip_input.c#L560),继续ipv4 流程处理 + + +```c +/* + * IP receive entry point + */ +int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, + struct net_device *orig_dev) +{ + struct net *net = dev_net(dev); + + skb = ip_rcv_core(skb, net); + if (skb == NULL) + return NET_RX_DROP; + + return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, + net, NULL, skb, dev, NULL, + ip_rcv_finish); +} +``` + +ip_rcv 逻辑很少,在计数,包检查后交给netfilter hook处理,处理完调用 `ip_rcv_finish` + +skb刚进入IP层第一时间调用 `PRE_ROUTING` hook + +5. netfilter `PRE_ROUTING` hook + +netfilter hook 执行过程后面会详细讲解 + +6. ip_rcv_finish +```c +static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb) +{ + struct net_device *dev = skb->dev; + int ret; + + /* + * if ingress device is enslaved to an L3 master device pass the + * skb to its handler for processing + */ + // 未开启CONFIG_NET_L3_MASTER_DEV时 + // l3mdev_ip_rcv do nothing + skb = l3mdev_ip_rcv(skb); + if (!skb) + return NET_RX_SUCCESS; + // 查找路由表 + ret = ip_rcv_finish_core(net, sk, skb, dev, NULL); + if (ret != NET_RX_DROP) + ret = dst_input(skb); + return ret; +} +``` + + +7. `ip_rcv_finish_core`: 查路由表,详细路由查询在 [路由选择](#路由选择) +8. [`ip_route_input_slow`](https://elixir.bootlin.com/linux/v6.6-rc6/source/net/ipv4/route.c#L2223):路由查询入口函数 + +单就路由而言,路由是根据目的地IP进行选择的过程。而 PBR(policy-based routing)的引入使得路由结果还受 source ip address, skb->mark, tcp/udp source/destination port 等变量影响。`struct flowi4` 包含了上述可以被参考的字段值。 + +`struct flowi4` +`fib_lookup`/`fib_table_lookup` 需 `struct flowi4` 和 `struct fib_result` 参数。 +给接下来的路由选择准备所需参数。 +- 初始化 `struct flowi4`:给PBR提供除 destination ip 之外的参考信息 +- `struct fib_result`:fib_lookup 的 in_out 返回结果。 +- [ip_mkroute_input](https://elixir.bootlin.com/linux/v6.6/source/net/ipv4/route.c#L2337):根据 `fib_lookup` 返回的 `struct fib_result` 构造route cache + - [skb_dst_set](https://elixir.bootlin.com/linux/v6.6/source/net/ipv4/route.c#L1759):将cache写入skb `struct dst_entry`字段 + +调用[`fib_lookup`](https://elixir.bootlin.com/linux/v6.6-rc6/source/include/net/ip_fib.h#L310)开始路由选择 + +![](/assets/2023-11-01-41.png) + + +#### 路由选择 + +路由选择总览 +![](/assets/2023-10-30.png) + +fib(forwarding information base) +kernel kbuild有[IP_MULTIPLE_TABLES](https://elixir.bootlin.com/linux/v6.6/source/net/ipv4/Kconfig#L63)选项。关闭则只有一张main表 + +使用如下 fib_lookup 函数 + +```c +static inline int fib_lookup(struct net *net, const struct flowi4 *flp, + struct fib_result *res, unsigned int flags) +{ + struct fib_table *tb; + int err = -ENETUNREACH; + + rcu_read_lock(); + + tb = fib_get_table(net, RT_TABLE_MAIN); + if (tb) + err = fib_table_lookup(tb, flp, res, flags | FIB_LOOKUP_NOREF); + + if (err == -EAGAIN) + err = -ENETUNREACH; + + rcu_read_unlock(); + + return err; +} +``` + +开启则引入多路由表,代码如下 + +1. [`fib_lookup`](https://elixir.bootlin.com/linux/v6.6-rc6/source/include/net/ip_fib.h#L310) +```c +static inline int fib_lookup(struct net *net, struct flowi4 *flp, + struct fib_result *res, unsigned int flags) +{ + struct fib_table *tb; + int err = -ENETUNREACH; + + flags |= FIB_LOOKUP_NOREF; + if (net->ipv4.fib_has_custom_rules) + // 如果有策略路由(Policy-based routing (PBR)) + // 进行PBR匹配 + // 下面的fib_table_lookup查找的是 ip route show 展示的路由 + // 也就是PBR的main routing table + // 所以不需要再调用fib_table_lookup + // ip rule list + // Priority: 32766, Selector: match anything, Action: lookup + // routing table main (ID 254). The main table is the normal + // routing table containing all non-policy routes + return __fib_lookup(net, flp, res, flags); + + rcu_read_lock(); + + res->tclassid = 0; + // 默认三张表 + // local main default + // 这里只查了 main 和 default表,没有 local + // 如果开启策略路由,在上面调用 __fib_lookup 并返回 + // 否则 local 合并到 main table 查询 + // 好处:代码尽量复用,如果用户不开启自定义rule,速度会有提升 + // 开启 RPDB ,kernel调用 fib_trie_unmerge(),将 local 从 main 剥离 + // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0ddcf43d5d4a03ded1ee3f6b3b72a0cbed4e90b1 + tb = rcu_dereference_rtnl(net->ipv4.fib_main); + if (tb) + err = fib_table_lookup(tb, flp, res, flags); + + if (!err) + goto out; + // 查default表 + tb = rcu_dereference_rtnl(net->ipv4.fib_default); + if (tb) + err = fib_table_lookup(tb, flp, res, flags); + +out: + if (err == -EAGAIN) + err = -ENETUNREACH; + + rcu_read_unlock(); + + return err; +} +``` + +使用 ip rule 列出/添加/删除 PBR + +```txt +$ ip rule list +# PRIORITY SELECTOR ACTION +0: from 127.0.0.1 iif lo ipproto tcp lookup 127 +0: from 127.0.0.1 iif lo ipproto udp lookup 127 +0: from all iif lo ipproto tcp lookup 128 +0: from all iif lo ipproto udp lookup 128 +0: from all lookup local +32766: from all lookup main +32767: from all lookup default +``` + +每个规则包含三部分,优先级,选择器,执行结果。优先级从小到大是规则被匹配的顺序。选择器匹配包信息,ACTION 执行具体的操作。 + +`from all`:任意source ip数据包都命中,执行action + +`lookup local`: 到local表继续查找 +`lookup main`: 到main表继续查找 +`lookup default`: 到default表继续查找 +local, main, default 表的查找顺序也对应了 `fib_lookup` 的表查找顺序 + +如果 ip rule 过程有一条规则命中,查找马上停止,并使用此规则作为最后的路由规则。 + +![](/assets/2023-11-01-40.png) + +fib_lookup +=> [`__fib_lookup`](https://elixir.bootlin.com/linux/v6.6-rc6/source/net/ipv4/fib_rules.c#L81):策略路由查找 +=> fib_table_lookup: 路由查找算法实现 +```c +// 路由查找算法 +// default via 10.0.2.2 dev enp0s3 +// 10.0.2.0/24 dev enp0s3 proto kernel scope link src 10.0.2.15 +// 最长前缀匹配 +int fib_table_lookup(struct fib_table *tb, const struct flowi4 *flp, + struct fib_result *res, int fib_flags) +{ + /* Step 1: Travel to the longest prefix match in the trie */ + for (;;) { + } + + /* Step 2: Sort out leaves and begin backtracing for longest prefix */ + for (;;) { + } + +found: + /* Step 3: Process the leaf, if that fails fall back to backtracing */ + // ... +} +``` +#### struct rtable + +代表一条路由规则,路由结束绑定到[`skb#_skb_refdst`](https://elixir.bootlin.com/linux/v6.6-rc6/source/include/linux/skbuff.h#L882) + +- `struct dst_entry dst` +下面详细介绍 +- `_u16 rt_type` + +本路由类型 + +| 字段 | 值 | ip route | 含义 | +| ----------------- | --- | ------------- | :-------------------------------------------------- | +| `RTN_UNICAST` | 1 | `unicast` | 默认值,如果`skb#_skb_refdst`是此值,数据需要被转发 | +| `RTN_LOCAL` | 2 | `local` | 本机路由 | +| `RNT_BROADCAST` | 3 | 广播 | 需要转发 | +| `RTN_MULTICAST` | 5 | 多播 | 多播 | +| `RNT_UNREACHABLE` | 7 | `unreachable` | 丢弃,返回 ICMP network unreachable | +- `__u8 rt_uses_gateway` + - bool,路由的下一跳是网关(ip route 返回类似 `via 10.0.0.1` 的格式),那么`rt_gw4`包含网关IP地址。 +- `u8 rt_gw_family` + - 如果 `rt_uses_gateway` 是0,那 `rt_gw_family` 是0。如果网关地址是IPV4,=AF_INET。IPV6, =AF_INET6 +- `union {__be32 rt_gw4; struct in6_addr rt_gw6;}` + - 如果是网关,根据IP类型使用 `rt_gw4` 或 `rt_gw6` 字段 + +#### struct dst_entry + +- `struct net_device *dev` + - 发送数据的网络设备,数据包最终从此设备发送 + +- `struct xfrm_state *xfrm` + - 和IPsec 相关,一般是NULL + +- `int (_input)(struct sk_buff_skb)` + - 根据路由结果,input是不同值。决定数据包接下来如果处理。dest ip本机则继续向上传递。不是本机的ip则转发 + + | 可选值 | 备注 | + | ---------------- | --------------------------------------------------------------------------------------------------------------------------------- | + | dst_discard | 默认值 | + | ip_local_deliver | 目的地址是本机,或者广播数据包 | + | ip_forward | 单播,但不是本机,需要转发 | + |ip_mr_input| 收到多播包处理 [ip_mr_input](https://elixir.bootlin.com/linux/v6.6/source/net/ipv4/route.c#L1754) | + | ip_error | 没有找到匹配路由,`unreachable`,数据包丢弃,返回ICMP host unreachable | + | lwtunnel_input | [Kconfig - net/Kconfig - Linux source code (v5.14.7) - Bootlin](https://elixir.bootlin.com/linux/v5.14.7/source/net/Kconfig#L390) | + +- `int (_output)(struct net_net, struct sock _sk, struct sk_buff_skb);` + + - 根据路由结果 output 指向不同函数。决定数据包如何发送 + + | 可选值 | 备注 | + | ----------------- | ------------------------------------- | + | dst_discard_out() | 默认值 | + | ip_output | 本机生成,需要发送的单播包 | + | ip_rt_bug() | bug? | + | xfrm4_output | 要转发的数据包 | + | ip_mc_output | 本机生成,需发送的多播(multicast)包 | + + + + +```c +/* + * Deliver IP Packets to the higher protocol layers. + */ +int ip_local_deliver(struct sk_buff *skb) +{ + /* + * Reassemble IP fragments. + */ + struct net *net = dev_net(skb->dev); + // 如果skb对应的ip包被链路层分片 + if (ip_is_fragment(ip_hdr(skb))) { + // 尝试重组 + if (ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER)) + return 0; + } + + return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, + net, NULL, skb, skb->dev, NULL, + ip_local_deliver_finish); +} +``` + + +尝试重组skb。如果此skb分片包的最后一个那么`ip_defrag`返回0,获得完成的ip数据包。 + +![ip_local_deliver](/assets/2023-10-26.png) +*ip_local_deliver call stack* + +`dst_input` 是很薄的中间函数 + +```c +static inline int dst_input(struct sk_buff *skb) +{ + return INDIRECT_CALL_INET(skb_dst(skb)->input, + ip6_input, ip_local_deliver, skb); +} +``` + +展开 `INDIRECT_CALL_INET` + +```c +#define INDIRECT_CALL_INET(f, f2, f1, ...) \ + INDIRECT_CALL_2(f, f2, f1, __VA_ARGS__) + +// Expands to +({ + __builtin_expect(!!(skb_dst(skb)->input == ip6_input), 1) ? + ip6_input(skb) : + ({ + __builtin_expect(!!(skb_dst(skb)->input == + ip_local_deliver), + 1) ? + ip_local_deliver(skb) : + skb_dst(skb)->input(skb); + }); +}) +``` + +如果 input 不等于 `ip_local_deliver` ,调用 `skb_dst(skb)->input(skb);`,否则调用 `ip_local_deliver` + +这样就包含多条路径,这些路径的 input 都在路由选择阶段赋值。 +如果 input == `ip_forward` 说明数据包需要转发到本机外 + +#### ip_forward + +![ip_forward](/assets/2023-10-26-1.png) +*ip forward 调用栈* + +1. ip_forward +调用 *netfilter forward hook* +```c +int ip_forward(struct sk_buff *skb) { + // ... + return NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD, + net, NULL, skb, skb->dev, rt->dst.dev, + ip_forward_finish); +} +``` +2. ip_forward_finish +3. dst_output +调用网络层output handler,通常会调用`ip_output` + 1. `ip_output`: 利用IP协议发送 + 2. `xfrm4_output`: IPsec送达 + 3. `ip_mc_output`: multicast packets + +```c +/* Output packet to network from transport. */ +static inline int dst_output(struct net *net, struct sock *sk, struct sk_buff *skb) +{ + return INDIRECT_CALL_INET(skb_dst(skb)->output, + ip6_output, ip_output, + net, sk, skb); +} +``` + +4. ip_output + +执行 *Netfilter POSTROUTING HOOK* + +```c +int ip_output(struct net *net, struct sock *sk, struct sk_buff *skb) +{ + struct net_device *dev = skb_dst(skb)->dev, *indev = skb->dev; + + skb->dev = dev; + skb->protocol = htons(ETH_P_IP); + return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING, + net, sk, skb, indev, dev, + ip_finish_output, + !(IPCB(skb)->flags & IPSKB_REROUTED)); +} +``` + + +5. `ip_finish_output` +6. `__ip_finish_output` +7. `ip_finish_output2` + +进入`netghbouring subsystem` ,查找ARP cache,找到链路层 MAC + +#### ip_local_deliver + + +```c +struct rtable *rt_dst_alloc(struct net_device *dev, + unsigned int flags, u16 type, + bool noxfrm) +{ + struct rtable *rt; + + rt = dst_alloc(&ipv4_dst_ops, dev, 1, DST_OBSOLETE_FORCE_CHK, + (noxfrm ? DST_NOXFRM : 0)); + + if (rt) { + rt->rt_genid = rt_genid_ipv4(dev_net(dev)); + rt->rt_flags = flags; + rt->rt_type = type; + rt->rt_is_input = 0; + rt->rt_iif = 0; + rt->rt_pmtu = 0; + rt->rt_mtu_locked = 0; + rt->rt_uses_gateway = 0; + rt->rt_gw_family = 0; + rt->rt_gw4 = 0; + + rt->dst.output = ip_output; + if (flags & RTCF_LOCAL) + // 送达本机上层协议栈 + rt->dst.input = ip_local_deliver; + } + + return rt; +} +``` + +ip_local_deliver 在创建 `dst_entry`时初始化。如果ip packet目的地为本机,继续传递到上层协议栈 + +假设packet送往本机,分析 ip_local_deliver + +继续调用 +1. ip_local_deliver +1. [`ip_local_deliver_finish`](https://elixir.bootlin.com/linux/v6.6-rc6/source/net/ipv4/ip_input.c#L227) +2. [`ip_protocol_deliver_rcu`](https://elixir.bootlin.com/linux/v6.6-rc6/source/net/ipv4/ip_input.c#L187) +```c +void ip_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int protocol) +{ + const struct net_protocol *ipprot; + int raw, ret; + +resubmit: + raw = raw_local_deliver(skb, protocol); + // CC-NET 根据ip协议字段找到handler处理 + ipprot = rcu_dereference(inet_protos[protocol]); + if (ipprot) { + if (!ipprot->no_policy) { + if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) { + kfree_skb_reason(skb, + SKB_DROP_REASON_XFRM_POLICY); + return; + } + nf_reset_ct(skb); + } + // call 入传输层 + ret = INDIRECT_CALL_2(ipprot->handler, tcp_v4_rcv, udp_rcv, + skb); + if (ret < 0) { + protocol = -ret; + goto resubmit; + } + __IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS); + } else { + if (!raw) { + if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) { + __IP_INC_STATS(net, IPSTATS_MIB_INUNKNOWNPROTOS); + icmp_send(skb, ICMP_DEST_UNREACH, + ICMP_PROT_UNREACH, 0); + } + kfree_skb_reason(skb, SKB_DROP_REASON_IP_NOPROTO); + } else { + __IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS); + consume_skb(skb); + } + } +} +``` + + +### 传输层 + +[tcp\_ipv4.c](https://elixir.bootlin.com/linux/v6.6-rc6/source/net/ipv4/tcp_ipv4.c#L1982) + +```c +// [af_inet.c ](https://elixir.bootlin.com/linux/v6.6-rc6/source/net/ipv4/af_inet.c#L1741) +static const struct net_protocol tcp_protocol = { + .handler = tcp_v4_rcv, + .err_handler = tcp_v4_err, + .no_policy = 1, + .icmp_strict_tag_validation = 1, +}; +``` + +注册 tcp 回调,IP层根据protocol type回调传输层handler + +## udp send +![](/assets/2023-10-31-21.png) +## udp receive + +UDP sock receive +![](/assets/2023-10-23-16-51-11.png) + +## tcp send +tcp connect +![](/assets/2023-10-31-22.png) + +tcp sendmsg +![](/assets/2023-10-31-23.png) + +## tcp receive +tcp rev + tcp syn send + +``` +__dev_xmit_skb (\root\codes\kernel-dev\linux\net\core\dev.c:3760) +__dev_queue_xmit (\root\codes\kernel-dev\linux\net\core\dev.c:4310) +dev_queue_xmit (\root\codes\kernel-dev\linux\include\linux\netdevice.h:3082) +neigh_hh_output (\root\codes\kernel-dev\linux\include\net\neighbour.h:529) +neigh_output (\root\codes\kernel-dev\linux\include\net\neighbour.h:543) +ip_finish_output2 (\root\codes\kernel-dev\linux\net\ipv4\ip_output.c:238) +__ip_finish_output (\root\codes\kernel-dev\linux\net\ipv4\ip_output.c:0) +NF_HOOK_COND (\root\codes\kernel-dev\linux\include\linux\netfilter.h:293) +ip_output (\root\codes\kernel-dev\linux\net\ipv4\ip_output.c:439) +dst_output (\root\codes\kernel-dev\linux\include\net\dst.h:458) +ip_local_out (\root\codes\kernel-dev\linux\net\ipv4\ip_output.c:128) +__ip_queue_xmit (\root\codes\kernel-dev\linux\net\ipv4\ip_output.c:543) +ip_queue_xmit (\root\codes\kernel-dev\linux\net\ipv4\ip_output.c:557) +__tcp_transmit_skb (\root\codes\kernel-dev\linux\net\ipv4\tcp_output.c:1415) +__tcp_send_ack (\root\codes\kernel-dev\linux\net\ipv4\tcp_output.c:4082) +tcp_send_ack (\root\codes\kernel-dev\linux\net\ipv4\tcp_output.c:4088) +tcp_rcv_synsent_state_process (\root\codes\kernel-dev\linux\net\ipv4\tcp_input.c:6363) +tcp_rcv_state_process (\root\codes\kernel-dev\linux\net\ipv4\tcp_input.c:6539) +tcp_v4_do_rcv (\root\codes\kernel-dev\linux\net\ipv4\tcp_ipv4.c:1751) +tcp_v4_rcv (\root\codes\kernel-dev\linux\net\ipv4\tcp_ipv4.c:2150) +ip_protocol_deliver_rcu (\root\codes\kernel-dev\linux\net\ipv4\ip_input.c:205) +ip_local_deliver_finish (\root\codes\kernel-dev\linux\net\ipv4\ip_input.c:233) +NF_HOOK (\root\codes\kernel-dev\linux\include\linux\netfilter.h:304) +ip_local_deliver (\root\codes\kernel-dev\linux\net\ipv4\ip_input.c:254) +dst_input (\root\codes\kernel-dev\linux\include\net\dst.h:468) +ip_sublist_rcv_finish (\root\codes\kernel-dev\linux\net\ipv4\ip_input.c:580) +ip_list_rcv_finish (\root\codes\kernel-dev\linux\net\ipv4\ip_input.c:631) +ip_sublist_rcv (\root\codes\kernel-dev\linux\net\ipv4\ip_input.c:639) +ip_list_rcv (\root\codes\kernel-dev\linux\net\ipv4\ip_input.c:674) +__netif_receive_skb_list_ptype (\root\codes\kernel-dev\linux\net\core\dev.c:5570) +__netif_receive_skb_list_core (\root\codes\kernel-dev\linux\net\core\dev.c:5618) +__netif_receive_skb_list (\root\codes\kernel-dev\linux\net\core\dev.c:5670) +netif_receive_skb_list_internal (\root\codes\kernel-dev\linux\net\core\dev.c:5761) +gro_normal_list (\root\codes\kernel-dev\linux\include\net\gro.h:439) +napi_complete_done (\root\codes\kernel-dev\linux\net\core\dev.c:6101) +e1000_clean (\root\codes\kernel-dev\linux\drivers\net\ethernet\intel\e1000\e1000_main.c:3811) +__napi_poll (\root\codes\kernel-dev\linux\net\core\dev.c:6531) +napi_poll (\root\codes\kernel-dev\linux\net\core\dev.c:6598) +net_rx_action (\root\codes\kernel-dev\linux\net\core\dev.c:6731) +__do_softirq (\root\codes\kernel-dev\linux\kernel\softirq.c:553) +invoke_softirq (\root\codes\kernel-dev\linux\kernel\softirq.c:427) +__irq_exit_rcu (\root\codes\kernel-dev\linux\kernel\softirq.c:632) +irq_exit_rcu (\root\codes\kernel-dev\linux\kernel\softirq.c:644) +sysvec_apic_timer_interrupt (\root\codes\kernel-dev\linux\arch\x86\kernel\apic\apic.c:1074) +``` +![](/assets/2023-10-23-16-52-49.png) + +## netfilter hook + +入口点 + +```c +static inline int +NF_HOOK(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk, struct sk_buff *skb, + struct net_device *in, struct net_device *out, + int (*okfn)(struct net *, struct sock *, struct sk_buff *)) +{ + // 调用netfilter预定义的hook,如果ret时,回调下一阶段的函数(即允许包通过) + int ret = nf_hook(pf, hook, net, sk, skb, in, out, okfn); + if (ret == 1) + ret = okfn(net, sk, skb); + return ret; +} +``` + + +## 数据包流 + +通常在协议栈有很多的决策点,根据不同的条件调用C的各种函数回调,将包送到不同的路径。 + +绿色部分是和路由相关的两个分流点。 +1. dst_input: 根据routing结果决定packet是local还是forward +2. dst_output: 根据`skb_dst(skb)->output`不同值,决定如何调用三层协议发送 +```c +/* Output packet to network from transport. */ +static inline int dst_output(struct net *net, struct sock *sk, struct sk_buff *skb) +{ + return INDIRECT_CALL_INET(skb_dst(skb)->output, + ip6_output, ip_output, + net, sk, skb); +``` + +下图还针对收包展示了两个分流点。 +1. `deliver_skb`: 根据链路层Type字段区分三层协议,IPv4, IPv6, ARP等等。 +2. 如果三层是IP协议,通过`iphdr->protocol`字段区分传输层协议TCP,UDP + [回调handler](https://github.com/torvalds/linux/blob/v6.6-rc6/net/ipv4/ip_input.c#L205) + +```c +ret = INDIRECT_CALL_2(ipprot->handler, tcp_v4_rcv, udp_rcv, + skb); +``` + +![](/assets/2023-10-31-18.png) +> 追踪`skb->_skb_refdst`代表的 `dst_entry *` 结构变化,展示packet收到后的处理流程 + +从图的左下角看起 + +数据包可以一个个处理 + +> `__netif_receive_skb` -> `deliver_skb` + +也可以作为lists一同处理 + +> `__netif_receive_skb_list` -> `netif_receive_skb_list_ptype` + +![](/assets/2023-10-31-19.png) + +两种处理方式最后都回调上层 non-list 或者 list 的 handler + +ipv4 的 handler 在init阶段注册[inet_init](https://elixir.bootlin.com/linux/v6.6-rc6/source/net/ipv4/af_inet.c#L2051) + +```c +ipfrag_init(); + +dev_add_pack(&ip_packet_type); + +ip_tunnel_core_init(); +``` + +协议分发利用[EtherType - Wikipedia](https://en.wikipedia.org/wiki/EtherType)字段,通过hashtable查找handle,ipv4的两个回调函数分别为 `ip_rcv`和 `ip_list_rcv`。 + +一到达网络层,首先进行 *Netfilter Prerouting*,如果没有被丢弃,继续调用 +1. `ip_rcv_finish/ip_list_rcv_finish` +2. `ip_rcv_finish_core` + 没有list版本,每个skb单独调用。开始routing +![](/assets/2023-10-31-20.png) + + + +## 参考 + +> [Routing Decisions in the Linux Kernel - Part 1: Lookup and packet flow](https://thermalcircle.de/doku.php?id=blog:linux:routing_decisions_in_the_linux_kernel_1_lookup_packet_flow) +>[networking:kernel\_flow [Wiki]](https://wiki.linuxfoundation.org/networking/kernel_flow#receive-flow) +>[Linux 网络栈接收数据(RX):原理及内核实现(2022)](https://arthurchiao.art/blog/linux-net-stack-implementation-rx-zh) \ No newline at end of file diff --git "a/_posts/64-epoll\350\247\246\345\217\221\346\234\272\345\210\266\345\210\206\346\236\220.md" "b/_posts/64-epoll\350\247\246\345\217\221\346\234\272\345\210\266\345\210\206\346\236\220.md" new file mode 100644 index 0000000..b7e3fe2 --- /dev/null +++ "b/_posts/64-epoll\350\247\246\345\217\221\346\234\272\345\210\266\345\210\206\346\236\220.md" @@ -0,0 +1,123 @@ +--- +tags: +- Kernel +title: epoll触发机制分析 +date: 2023-11-07 +updated: 2023-11-16 +issueid: 64 +--- +目标: + + 分析 kernel 驱动收到后skb + + 1. 如何唤醒epoll callback? + 2. accpet syscall 如何从listener socket spawn 子 socket? + +一个极简epoll server程序(无法编译,只说明流程) + +```c +// 创建listener socket +int listener_fd = socket(); +bind(listener_fd); +listen(listener_fd); +set_socket_non_blocking(listener_fd); + +// 创建socket +ep = epoll_create1(0); +struct epoll_event event; +event.data.fd = listener_fd; +event.events = EPOLLIN | EPOLLET; +// 将listener socket注册进epoll +s = epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event); +for ( ;; ) { + // eventloop + int n = epoll_wait(ep, &events, 1024, -1); + for (itn i = 0; i < n ; i ++) { + if(events[i].data.fd == listener_fd) { + // new incoming connection + int child = accept(listener_fd, &addr, &addr_len); + // 将子连接socket加入epoll eventloop + make_socket_non_blocking(infd); + struct epoll_event; + event.data.fd = infd; + event.events = EPOLLIN | EPOLLET; + s = epoll_ctl(ep, EPOLL_CTL_ADD, child, &event); + } + } +} +``` + + +## epoll 注册 wake callback + +直接看追踪的call stack + +1. `epoll_ctl(ep, EPOLL_CTL_ADD, child, &event)`: called from epoll_ctl syscall +2. [ep_insert](https://elixir.bootlin.com/linux/v6.6/source/fs/eventpoll.c#L1552): +```c +// ep_insert +epq.epi = epi; +init_poll_funcptr(&epq.pt, ep_ptable_queue_proc); +``` + +把 `ep_ptable_queue_proc` 放入 `_qproc`,在接下来的 `poll_wait` 调用 + ```c +static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc) +{ + pt->_qproc = qproc; + pt->_key = ~(__poll_t)0; /* all events enabled */ +} + +``` + + +3. ep_item_poll + 1. 如果fd不是file epoll,那么通过vfs定向给具体实现 +4. vfs_poll: virtual fs poll,call `file->f_op->poll(file, pt);` + 1. sock poll: 文件底层对应的是sock + 2. unix_poll: address family unix poll +5. sock_poll: socket 文件类型的poll +6. tcp_poll: tcp socket poll 实现 +7. sock_poll_wait +8. poll_wait +9. [ep_ptable_queue_proc](https://elixir.bootlin.com/linux/v6.6/source/fs/eventpoll.c#L1288) + 1. ep_poll_callback 添加到 wait queue +![](assets/2023-11-07-3.png) + +## struct file 转换成 struct sock + +`struct sock` 有最核心弄明白的点在于skb buff 如何转换成 `struct socket` 结构 + +```c +static __poll_t sock_poll(struct file *file, poll_table *wait) +{ + // 关键点 + struct socket *sock = file->private_data; + const struct proto_ops *ops = READ_ONCE(sock->ops); + __poll_t events = poll_requested_events(wait), flag = 0; + + if (!ops->poll) + return 0; + + if (sk_can_busy_loop(sock->sk)) { + /* poll once if requested by the syscall */ + if (events & POLL_BUSY_LOOP) + sk_busy_loop(sock->sk, 1); + + /* if this socket can poll_ll, tell the system call */ + flag = POLL_BUSY_LOOP; + } + + return ops->poll(file, sock, wait) | flag; +} +``` + +sock_poll将 `file->private_data` 转换成 `struct sock`,从而调用到epoll的socket实现 + +[Why does the Linux kernel have \`struct sock\` and \`struct socket\`? - Stack Overflow](https://stackoverflow.com/a/46415123/7529562) + +## sock wake epoll callback + +唤醒`ep->wq`[ep_poll_callback](https://elixir.bootlin.com/linux/v6.6/source/fs/eventpoll.c#L1217) + +![](/assets/2023-11-07-2.png) \ No newline at end of file diff --git "a/_posts/65-tcp-ip-rcv-skb-\346\265\201\347\250\213\345\210\206\346\236\220.md" "b/_posts/65-tcp-ip-rcv-skb-\346\265\201\347\250\213\345\210\206\346\236\220.md" new file mode 100644 index 0000000..bfa6120 --- /dev/null +++ "b/_posts/65-tcp-ip-rcv-skb-\346\265\201\347\250\213\345\210\206\346\236\220.md" @@ -0,0 +1,250 @@ +--- +title: tcp-ip-rcv-skb-流程分析 +date: 2023-11-16 +updated: 2023-11-16 +issueid: 65 +tags: +- net +- Kernel +--- +tcp rev + tcp syn send + +``` +__dev_xmit_skb (\root\codes\kernel-dev\linux\net\core\dev.c:3760) +__dev_queue_xmit (\root\codes\kernel-dev\linux\net\core\dev.c:4310) +dev_queue_xmit (\root\codes\kernel-dev\linux\include\linux\netdevice.h:3082) +neigh_hh_output (\root\codes\kernel-dev\linux\include\net\neighbour.h:529) +neigh_output (\root\codes\kernel-dev\linux\include\net\neighbour.h:543) +ip_finish_output2 (\root\codes\kernel-dev\linux\net\ipv4\ip_output.c:238) +__ip_finish_output (\root\codes\kernel-dev\linux\net\ipv4\ip_output.c:0) +NF_HOOK_COND (\root\codes\kernel-dev\linux\include\linux\netfilter.h:293) +ip_output (\root\codes\kernel-dev\linux\net\ipv4\ip_output.c:439) +dst_output (\root\codes\kernel-dev\linux\include\net\dst.h:458) + ip_local_out (\root\codes\kernel-dev\linux\net\ipv4\ip_output.c:128) +__ip_queue_xmit (\root\codes\kernel-dev\linux\net\ipv4\ip_output.c:543) +ip_queue_xmit (\root\codes\kernel-dev\linux\net\ipv4\ip_output.c:557) +__tcp_transmit_skb (\root\codes\kernel-dev\linux\net\ipv4\tcp_output.c:1415) +__tcp_send_ack (\root\codes\kernel-dev\linux\net\ipv4\tcp_output.c:4082) +tcp_send_ack (\root\codes\kernel-dev\linux\net\ipv4\tcp_output.c:4088) +tcp_rcv_synsent_state_process (\root\codes\kernel-dev\linux\net\ipv4\tcp_input.c:6363) +tcp_rcv_state_process (\root\codes\kernel-dev\linux\net\ipv4\tcp_input.c:6539) +tcp_v4_do_rcv (\root\codes\kernel-dev\linux\net\ipv4\tcp_ipv4.c:1751) +tcp_v4_rcv (\root\codes\kernel-dev\linux\net\ipv4\tcp_ipv4.c:2150) +ip_protocol_deliver_rcu (\root\codes\kernel-dev\linux\net\ipv4\ip_input.c:205) +ip_local_deliver_finish (\root\codes\kernel-dev\linux\net\ipv4\ip_input.c:233) +NF_HOOK (\root\codes\kernel-dev\linux\include\linux\netfilter.h:304) +ip_local_deliver (\root\codes\kernel-dev\linux\net\ipv4\ip_input.c:254) +dst_input (\root\codes\kernel-dev\linux\include\net\dst.h:468) +ip_sublist_rcv_finish (\root\codes\kernel-dev\linux\net\ipv4\ip_input.c:580) +ip_list_rcv_finish (\root\codes\kernel-dev\linux\net\ipv4\ip_input.c:631) +ip_sublist_rcv (\root\codes\kernel-dev\linux\net\ipv4\ip_input.c:639) +ip_list_rcv (\root\codes\kernel-dev\linux\net\ipv4\ip_input.c:674) +__netif_receive_skb_list_ptype (\root\codes\kernel-dev\linux\net\core\dev.c:5570) +__netif_receive_skb_list_core (\root\codes\kernel-dev\linux\net\core\dev.c:5618) +__netif_receive_skb_list (\root\codes\kernel-dev\linux\net\core\dev.c:5670) +netif_receive_skb_list_internal (\root\codes\kernel-dev\linux\net\core\dev.c:5761) +gro_normal_list (\root\codes\kernel-dev\linux\include\net\gro.h:439) +napi_complete_done (\root\codes\kernel-dev\linux\net\core\dev.c:6101) +e1000_clean (\root\codes\kernel-dev\linux\drivers\net\ethernet\intel\e1000\e1000_main.c:3811) +__napi_poll (\root\codes\kernel-dev\linux\net\core\dev.c:6531) +napi_poll (\root\codes\kernel-dev\linux\net\core\dev.c:6598) +net_rx_action (\root\codes\kernel-dev\linux\net\core\dev.c:6731) +__do_softirq (\root\codes\kernel-dev\linux\kernel\softirq.c:553) +invoke_softirq (\root\codes\kernel-dev\linux\kernel\softirq.c:427) +__irq_exit_rcu (\root\codes\kernel-dev\linux\kernel\softirq.c:632) +irq_exit_rcu (\root\codes\kernel-dev\linux\kernel\softirq.c:644) +sysvec_apic_timer_interrupt (\root\codes\kernel-dev\linux\arch\x86\kernel\apic\apic.c:1074) +``` +![](/assets/2023-10-23-16-52-49.png) + + +```c +static inline struct sock *__inet_lookup(struct net *net, + struct inet_hashinfo *hashinfo, + struct sk_buff *skb, int doff, + const __be32 saddr, const __be16 sport, + const __be32 daddr, const __be16 dport, + const int dif, const int sdif, + bool *refcounted) +{ + u16 hnum = ntohs(dport); + struct sock *sk; + + sk = __inet_lookup_established(net, hashinfo, saddr, sport, + daddr, hnum, dif, sdif); + *refcounted = true; + if (sk) + return sk; + *refcounted = false; + return __inet_lookup_listener(net, hashinfo, skb, doff, saddr, + sport, daddr, hnum, dif, sdif); +} +``` +到TCP层后,skb需要转换成 `struct sock`,从 kernel 协议栈查找skb -> struct sk 映射 + +```c +struct sock *__inet_lookup_established(struct net *net, + struct inet_hashinfo *hashinfo, + const __be32 saddr, const __be16 sport, + const __be32 daddr, const u16 hnum, + const int dif, const int sdif) +{ + INET_ADDR_COOKIE(acookie, saddr, daddr); + const __portpair ports = INET_COMBINED_PORTS(sport, hnum); + struct sock *sk; + const struct hlist_nulls_node *node; + /* Optimize here for direct hit, only listening connections can + * have wildcards anyways. + */ + // hashtable查询 + unsigned int hash = inet_ehashfn(net, daddr, hnum, saddr, sport); + // 找到hashtable桶 + unsigned int slot = hash & hashinfo->ehash_mask; + struct inet_ehash_bucket *head = &hashinfo->ehash[slot]; + +begin: + sk_nulls_for_each_rcu(sk, node, &head->chain) { + if (sk->sk_hash != hash) + continue; + if (likely(inet_match(net, sk, acookie, ports, dif, sdif))) { + if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt))) + goto out; + if (unlikely(!inet_match(net, sk, acookie, + ports, dif, sdif))) { + sock_gen_put(sk); + goto begin; + } + goto found; + } + } + /* + * if the nulls value we got at the end of this lookup is + * not the expected one, we must restart lookup. + * We probably met an item that was moved to another chain. + */ + if (get_nulls_value(node) != slot) + goto begin; +out: + sk = NULL; +found: + return sk; +} +``` + +sock#sk_data_ready 回调通知 + +![](/assets/2023-11-07-4.png) + +注意看左侧callstack,协议栈收到数据后 + +1. tcp_rcv_established + 1. syn rcv 后进入 established 状态 + 2. tcp_queue_rcv + 1. \_\_skb_queue_tail + 3. tcp_data_ready +1. tcp_data_queue + 1. tcp_queue_rcv + 1. \_\_skb_queue_tail + 2. tcp_data_ready + 1. `struct sock#sk_data_ready`默认是[sock_def_readable](https://elixir.bootlin.com/linux/v6.6/source/net/core/sock.c#L3447) + + 唤醒 + + ```c + void sock_def_readable(struct sock *sk) + { + struct socket_wq *wq; + + trace_sk_data_ready(sk); + + rcu_read_lock(); + wq = rcu_dereference(sk->sk_wq); + // CC-NET-TCP 唤醒 sk 的 waiters + if (skwq_has_sleeper(wq)) + wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN | EPOLLPRI | + EPOLLRDNORM | EPOLLRDBAND); + sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN); + rcu_read_unlock(); + } + ``` + +### 如何唤醒accept syscall + +tcp server运行过程 +> bind: 分配 port +> listen: Move a socket into listening state. +> blocking accept: thread blocking to require new socket + + +```c +bind(); +listen(); +for(;;) { + connfd = accept(sockfd, (SA*)&cli, &len); +} +``` + +```c +const struct proto_ops inet_stream_ops = { + .family = PF_INET, + .owner = THIS_MODULE, + .release = inet_release, + .bind = inet_bind, + .connect = inet_stream_connect, + .socketpair = sock_no_socketpair, + // accept syscall 调用到 tcp proto_ops 回调 inet_accept + .accept = inet_accept, + .getname = inet_getname, + .poll = tcp_poll, + .ioctl = inet_ioctl, + .gettstamp = sock_gettstamp, + // tcp listen + .listen = inet_listen, + .shutdown = inet_shutdown, + .setsockopt = sock_common_setsockopt, + .getsockopt = sock_common_getsockopt, + .sendmsg = inet_sendmsg, + .recvmsg = inet_recvmsg, +#ifdef CONFIG_MMU + .mmap = tcp_mmap, +#endif + .splice_eof = inet_splice_eof, + .splice_read = tcp_splice_read, + .read_sock = tcp_read_sock, + .read_skb = tcp_read_skb, + .sendmsg_locked = tcp_sendmsg_locked, + .peek_len = tcp_peek_len, +#ifdef CONFIG_COMPAT + .compat_ioctl = inet_compat_ioctl, +#endif + .set_rcvlowat = tcp_set_rcvlowat, +}; +``` + +注册唤醒回调 +accept blocking +[inet_csk_accept](https://elixir.bootlin.com/linux/v6.6/source/net/ipv4/inet_connection_sock.c#L683) +[prepare_to_wait_exclusive](https://elixir.bootlin.com/linux/v6.6/source/net/ipv4/inet_connection_sock.c#L609) +```c +bool +prepare_to_wait_exclusive(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry, int state) +{ + unsigned long flags; + bool was_empty = false; + + wq_entry->flags |= WQ_FLAG_EXCLUSIVE; + spin_lock_irqsave(&wq_head->lock, flags); + if (list_empty(&wq_entry->entry)) { + was_empty = list_empty(&wq_head->head); + __add_wait_queue_entry_tail(wq_head, wq_entry); + } + set_current_state(state); + spin_unlock_irqrestore(&wq_head->lock, flags); + return was_empty; +} +``` + + +### 如何唤醒epoll等异步回调机制 + +未完待续... \ No newline at end of file diff --git "a/_posts/66-LVS\346\265\201\351\207\217\350\275\254\345\217\221\346\250\241\345\274\217.md" "b/_posts/66-LVS\346\265\201\351\207\217\350\275\254\345\217\221\346\250\241\345\274\217.md" new file mode 100644 index 0000000..8e3b616 --- /dev/null +++ "b/_posts/66-LVS\346\265\201\351\207\217\350\275\254\345\217\221\346\250\241\345\274\217.md" @@ -0,0 +1,45 @@ +--- +issueid: 66 +tags: +- net +title: LVS流量转发模式 +date: 2023-12-14 +updated: 2023-12-14 +--- +LVS(linux virtual server) 做网关的几种转发模式 + +## DR(direct routing) +路由收包后先给到同vlan的LB,LB根据负载均衡策略选择后端X,将mac改为X的mac地址并发送 + +优势: +- X的response不需要过LB,X的response发给路由,路由送给client +- LB只是作为指路人,修改destination mac,并没有其他逻辑,所以性能很高 +劣势 +- LB和X必须处于同vlan +- 必须依赖ARP发现X的mac +- 修改MAC层,不对IP/TCP操作,所以不支持端口映射 + +## NAT +地址变换,LVS向后端转发时修改src-ip和dest-ip,确保X的response回包dest-ip是LVS +![](/assets/2023-12-14-1.png) +注意看第三个, S 是 200.200.200.2,对应第一张图的S,说明LB NAT向后端转发时,只修改了dest-ip。dest-ip指向X。 + +为了确保第四步response能够改回正确的src-ip,LB内部需要维护 (10.10.10.2, 200.200.200.2) 到 (200.200.200.1, 200.200.200.2)的映射,收到(10.10.10.2, 200.200.200.2)后查表改成(200.200.200.1, 200.200.200.2) + +优势 +- 支持端口映射 +- 配置简单 +劣势 +- LVS和X必须同子网,并且LVS必须是网关,才能修改src-ip。不修改src-ip直接发给C,C四元组找不到对应的连接,TCP reset +## FULL-NAT +和NAT大体相同,如果说上面的NAT是DNAT(destination-NAT),那么FULL-NAT更像是 SD-NAT(source-destination-NAT。同时修改src-ip和dest-ip,dest-ip依旧是X的rip(real ip),src-ip是 LB。X和LB就算不同vlan,依旧可以经路由转发给LB,LB最后发给cip + +优势 +- LB 和 X 可以不同vlan +劣势 +- FULL-NAT同时修改src和dest ip,会导致X拿不到真正的 cip,一些依赖cip的后端服务会出问题 +解决办法 +cip通过tcp option字段传递,后端服务器安装TOA模块,从option读出cip再给上层应用 + +## reference +> [Load Balancing - Linux Virtual Server (LVS) and Its Forwarding Modes](https://www.alibabacloud.com/blog/load-balancing---linux-virtual-server-lvs-and-its-forwarding-modes_595724) \ No newline at end of file diff --git "a/_posts/7-2018\345\271\264\347\273\210\346\200\273\347\273\223.md" "b/_posts/7-2018\345\271\264\347\273\210\346\200\273\347\273\223.md" new file mode 100644 index 0000000..004776c --- /dev/null +++ "b/_posts/7-2018\345\271\264\347\273\210\346\200\273\347\273\223.md" @@ -0,0 +1,224 @@ +--- +updated: 2019-09-11 +issueid: 7 +tags: +- 年终总结 +title: 2018年终总结 +date: 2019-09-11 +--- +现在是 `2018-12-30 16:40` 图书馆,等待`Windows`更新`1809版本`也没什么事,干脆就开始写了 + +之前没有写总结的习惯,打算从现在开始每年都写一篇,希望能守信 + +从大一到大三过了三年,所以趁这个机会把三年都写一遍,借这个机会审视一下过去的经历 + + + +#### 大一 + +从高中上大学的时候发奋要好好学习,上课听讲,也算是一个新的起点 + +所以大一上学期几乎每个周末都和舍友去学习高数,当时一道题不会竟然还很不爽。。考试老师分给的少了还不满 + +编程方面,第一次接触C语言,上课看到老师使用`printf`,在控制台输出 `Hello,World`的时候真的是大叫 `WowWow!` + +每次老师布置下课作业马上回到宿舍就写完了,然后给组员抄。。 + +当时写完了塞到U盘里面,感觉里面塞满了整个世界,等着上课举手上去炫技,展示自己写的代码 + +因为自己的编程基础当时几乎为0,所以按部就班的学习,C语言用了一个上学期才学完(后来发现C语言只教了一部分,剩下的又是自学完) + +最后的课程设计,我记得当时17(车位管理系统) 和 18(火车票啥玩意) 题算是难题,然后我们整个宿舍都做了 17 题,那是我第一次写了 1000 行的代码。`1000行`对当时的来说难度很大,因为提前两三个周准备,所以最后集体课设的 4 天基本上都在教别人了 + +*吐槽一句,现在动不动就是 `****系统`,订票系统,酒店系统,一听到这些词就没了兴致* + +当时觉着自己很厉害,因为一直有一个梦想,就是写一个 操作系统 内核, 把这个想法告诉C语言老师之后,老师很感觉的说了一句 `"你不行"`(原话) + +当时的我没有足够的认识,以为学会了C语言就能写一些很酷炫的东西,但后来证明,这个想法是错误的 + +博客方面 + +博客是从大一上学期开始的,当时用的是Wordpress + +第一次接触Linux,完全就是对着教程一步步复制粘贴 shell,出了好多的错,一遍一遍复制到百度上查 + +需要配置Nginx, PHP 和 MySQL,当时的我对这三个都不清楚,不知道Nginx的静态资源服务器,PHP 是 server side 脚本, 哦, Mysql之前听说过,也仅仅限于听说 + +我记着比较清楚的是上学期下午,舍友都出去了,我就在宿舍重复敲那些CMD,就几行我硬是重复了一下午,出错了重新启动,查教程再来 + +反正最后鼓捣好了,选了一个看着不错的主题糊上了,因为当时没有写东西的习惯,再加上没什么能写了,最后就长草了。服务器最后回滚,之前写的全都没了 + +之前买的域名也在今年过期了,因为现在觉着那域名 `turntofuture.com`太幼稚了,就扔掉了 + +买了许多的书,现在发现计算机的书加起来能有 `1000+` , `Windows程序设计,编译原理` 这些书买了拆开封就放那了。 + +当时不知道到底什么是 `服务器`,看了`深入理解计算机系统`之后还打算写个服务器玩玩(主要是不知道自己要做什么),后来也没写 + +这是我开始迷茫的开始 + +大一上学期才学会用代理,当时看了百度上关于FQ的帖子,说是违反了法律法规之类的,吓得我也不敢FQ。。 + +第一次用是蓝灯,一个月800MB流量,因为没多少流量,所以也不常用,配置环境出现问题就是百度 + +寒假的时候第一次用Shadowsocks,以为软件都是要有官网的,我也没有开源软件这个概念,糊里糊涂的花 150 买个一年的代理,也是第一次完整意义上的FQ + + +开始接触Google,阅读英文文档,因为高中自己英语还好,但为了尽量适应英文环境,把 Windows 和 Android 都换成了英文(这样做不是为了学英文,而是看到英文不抵触) + +*开始更新Windows了,正好出去吃饭,17:07,回来继续* + +*18:28更新完了,继续* + +稀里糊涂大一结束了,从现在看来,大一除了学会了C语言之外,最重要的就是用Google了,认识也开始提升。 + +#### 大二 + +大一下和大二上也经历了许多人的迷茫,我也不知道我要做什么,也不知道能做什么 + +当时就是乱撞 + +照着 第一行代码 写过Android开发 + +照着书做过 Win32 开发 + +在 52pojie 上学过破解 +也再看雪学过汇编搞过逆向 + +有点网络技术就试过走安全方向 + +上面的这些都无疾而终 + +大二上学期考试临阵突击,两天看完一本书,最后只有离散躺枪。。 + +哦,对了,还有,我觉着大学给人留下的印象很不好吧,有时候听到一些老师违背常理胡扯淡,还站起来怼。 + +但实在是不想看到教师误导人。 + +大二寒假 + +自己在家学Python,当时听别人说爬虫挺好玩的,之前也没学过前端,也不知道DOM指的是什么。最后因为Python不会声明 `class` (我当时是真的不会。。跟着网上的教程写但就是不会) 就放弃了 + +因为大一想要做Win32开发,所以学过C#,在家写 .Net 突然有天恍惚间觉着自己不想被困死在 .Net 平台上,果断就转了Java,做跨平台开发 + +*当时的我并不知道 .Net Core 这个东西,而且那是 Netcore 还不成熟,就算知道了也不会选择它* + +在家 用 Java 写了一个简单的 网络代理,手动提取 HTTP host,来一个TCP连接就跑一个线程,结果在 任务管理器 上显示我开到了 80+ 线程,直接卡死了。。 + +大二下 + +到了大学真正拐角点,在知乎上看到了GSOC,是Google为大学生准备的代码活动 + +被选中的可以和一些开源社区的成员一起为社区做贡献 + +我是非常想参加这个项目,然后去社区用英文套近乎 + +中间的过程很复杂,最后结果是失败了。。 + +不过真正影响我的不是参加这个项目,而是我为了准备,把我打算参加的 project 的 代码全都读了一遍 + +那时候我开始真正去理解别人的代码 + +也正是长时间看别人的代码,我自身的编码水平也获得了巨幅提升 + +code base中使用的框架和库也为我今后对于异步思想的理解提供的帮助 + +熟练了HTTP, TCP, 以及 单元测试, 集成测试, + +大二下是代码水平急速提升的阶段,我开发了4个不一样的代理软件 + +现在的自己的 Github 仓库也有了点看得过去的代码 + +因为自己已经养成了阅读别人源代码的习惯,仿写Shadowsocks设计,学着自己封装 EventLoop,因为熟练了 Java 的 `select`,所以后面使用 `Netty` +的时候能够从框架本身的视角去看待问题 + +那时起开始用各种框架和库来创造一些不一样的玩具,也逐渐找到了自己的方向,有了目标,不再迷茫 + +大二暑假在家自学了Web前端,因为自己之前都是写Web接口,突然有一天发现没有前端的View,我就算写再多的API接口也没用,加上我这样的程序员天生内向的特点,也不知道谁会去做Web前端,没办法,只能自己上了,大二暑假天天在写代码,出去有空闲时间了就看文档 + +说实话,我从未想过前端这么杂乱,这也不是技术帖,所以不想在这留技术细节,总之,碰到一个坑我能栽进去三到四天不止 + +因为没有人能给我指导,我最初就是操作DOM,写了一个暑假,最后想把Windows装成Linux,结果忘了保存代码,临近大三开学的时候我亲手格式化掉了我一个暑假的成果。 + +由于代码被误删,当时已经绝望了,但如果不是这个事,也不会接触到前端框架 + +代码被删,只能重来,因为已经体验到了操作DOM的痛苦,所以我开始寻找别的解决方案 + +在大二下做数据库课设的时候接触过Angularjs和Vuejs,因为Vuejs中文文档写得比较不错,所以最后选择了它 + +Vuejs自带的脚手架让我见识到了前端的工程化打包,以及本身的数据驱动的思想,使得自己对于整个前端的理解更进一层 + +当然,前端基础那么差就直接用框架,肯定不免踩地雷,开始使用框架的前一段时间堪称黑暗,不知道摔进去最后怎么爬出来了,也不想再回忆那段时期 + +总之,经历了那段痛苦之后,现在的我已经完全理解的前端,对于整个Web的未来也有了自己的看法 + +#### 大三 + +*2018-12-31 19:52* + +突然发现写到 大三了,真的好快 + +时间拉得太长,对于大一和大二的经历已经开始遗忘了 + +现在这个网站的主题就是我写的,不过因为纯写CSS和Js没什么技术含量,决定重写 + + +现在自己的博客托管在 Github Page ,因为之前放到自己的VPS上懒得收拾,但 静态网站 用起来不灵活,加上自己本身就是 Web开发方向,所以最近忙着写 + +自己的博客系统。 + +这一次将 后台和 前端界面全部自己来做,代码已经完成了 85%, + +自己深知部署到生产环境的麻烦,所以决定再次使用 容器方案,初步设定了使用五个容器来做打包 + +至于爬虫,我停止学Python之后就再也没碰过,不过现在的话,抓取网页数据已经没有问题了,也算是前端学成之后获得了额外礼物吧 + +现在想想,自己真是变了好多,大一的时候对大学教育饱经期待,到现在对于大学教育的失望 + +我总觉着现在大学培养的就是机器人,一堆的课对学生一点提高都没有,还有恶心人的点名签到 + +我舍友去了也是玩手机,睡觉,这样去的意义是什么?还不如呆在图书馆写代码 + +每次上一门编程课,教师第一节教的就是如何配置开发环境。。。 + +现在复习周,要准备期末考试了,分散了我大多的精力 + +要不是为了准备考试,现在就已经部署完成了 + +再说说编程 + +我不属于任何语言宗教,我觉着一个合格的开发者应该默认什么语言都会,只需要给他点时间看看文档,抄抄代码 + +但是经常爱看一些 `Python VS Golang`之类的比较,虽然自己两个语言都会,感觉挺矛盾的 :) + +许多的概念或者类库没必要去记,知道有这个东西就可以,把常用的记住了,实际写的时候碰到不会的直接Google搜就可以 + +编程语言不是最重要的,重要的是那种思想,比如网络,有价值的网络思想是从实际中总结的,课本上的就算学会了不去实践还是没用 + +那种思想本身就是跨平台跨语言的 + +大一和舍友学C++,天天背着一本800页的 c++ primer 去啃,到现在都忘了,为了学而学,不和实践结合,学而不用等于不会 + +大三寒假初步打算把这个博客重写完,期间穿插着算法,被我丢掉许久的算法终于要重新拾起来了 + +因为自己不考研,所以下学期开始试试提前批,如果面试成功和老师请假就去实习, + +后面还有秋招等着。 + +也算最后给大学加上了句号。 + + +------------------------------------------------------------- + +后面会把总结单独来一个栏目,不在和日常推送混到一起了 + +--------------------------------------- + +写完之后发现写成了流水账,之前打算写完之后扔到印象笔记算了,没什么干货就不公开了 + +但当时写文章就是为了分享,所以还是推送上来了,毕竟没人认识我 ;) + +写完了 + +2018-12-31 22:20 + +Hello, 2019 :) \ No newline at end of file diff --git "a/_posts/8-TypeScript\344\270\255import=require \345\222\214es6 module import\345\214\272\345\210\253.md" "b/_posts/8-TypeScript\344\270\255import=require \345\222\214es6 module import\345\214\272\345\210\253.md" new file mode 100644 index 0000000..5dbdfde --- /dev/null +++ "b/_posts/8-TypeScript\344\270\255import=require \345\222\214es6 module import\345\214\272\345\210\253.md" @@ -0,0 +1,111 @@ +--- +title: TypeScript中import=require 和es6 module import区别 +date: 2019-09-11 +updated: 2019-11-20 +issueid: 8 +tags: +--- +这个是在Github 看到的评论,感觉写的不错,就直接搬过来了 + +比较 + +`export default` 和 `import x = require('')`区别 + +### export default ... ([Default Export](https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export#Description)) + +```typescript +// calculator.ts // compiled.js +// ============= // =========== +export default class Calculator { // var Calculator = /** @class */ (function () {' +' public add(num1, num2) { // function Calculator() {} + return num1 + num2; // Calculator.prototype.add = function (num1, num2) { + } // return num1 + num2; +} // }; + // return Calculator; + // }()); + // exports["default"] = Calculator; +``` + +### import ... from "module"; + +```typescript +// importer.ts // compiled.js +// =========== // =========== +import Calculator from "./calculator"; // exports.__esModule = true; + // var calculator = require("./calculator"); +let calc = new Calculator(); // var calc = new calculator["default"](); + // console.log(calc.add(2, 2)); +console.log(calc.add(2, 2)); // +``` + +##### Notes: + +* A default export can be imported with any name. +* Functionally equivalent to `import * as Calculator from "./calculator";` and then instantiating it using `new Calculator.default()`. + +--- + +### export = ... + +```typescript +// calculator.ts // compiled.js +// ============= // =========== +export = class Calculator { // module.exports = /** @class */ (function () { + public add(num1, num2) { // function Calculator() {} + return num1 + num2; // Calculator.prototype.add = function (num1, num2) { + } // return num1 + num2; +} // }; + // return Calculator; + // }()); +``` + +### import ... = require("module"); + +```typescript +// importer.ts // compiled.js +// =========== // =========== +import Calculator = require("./calculator"); // exports.__esModule = true; + // var Calculator = require("./calculator"); +let calc = new Calculator(); // var calc = new Calculator(); + // console.log(calc.add(2, 2)); +console.log(calc.add(2, 2)); // +``` + +##### Notes: + +* This syntax is only used when importing a CommonJS module. + +--- + +### export ... ([Named Export](https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export#Description)) + +```typescript +// calculator.ts // compiled.js +// ============= // =========== +export class Calculator { // exports.__esModule = true; + public add(num1, num2) { // var Calculator = /** @class */ (function () { + return num1 + num2; // function Calculator() {} + } // Calculator.prototype.add = function (num1, num2) { +} // return num1 + num2; + // }; + // return Calculator; + // }()); + // exports.Calculator = Calculator; +``` + +### import { ... } from "module"; + +```typescript +// importer.ts // compiled.js +// =========== // =========== +import { Calculator } from "./calculator"; // exports.__esModule = true; + // var calculator = require("./calculator"); +let calc = new Calculator(); // var calc = new calculator.Calculator(); + // console.log(calc.add(2, 2)); +console.log(calc.add(2, 2)); // +``` + +##### Notes: + +* Named exports are useful to export several values. +* During the import, you must use the same name of the corresponding object. diff --git "a/_posts/9-\347\245\236\345\245\207\347\232\204\344\274\230\345\205\210\351\230\237\345\210\227.md" "b/_posts/9-\347\245\236\345\245\207\347\232\204\344\274\230\345\205\210\351\230\237\345\210\227.md" new file mode 100644 index 0000000..0805fe5 --- /dev/null +++ "b/_posts/9-\347\245\236\345\245\207\347\232\204\344\274\230\345\205\210\351\230\237\345\210\227.md" @@ -0,0 +1,150 @@ +--- +title: 神奇的优先队列 +date: 2019-09-12 +updated: 2019-09-12 +issueid: 9 +tags: +- 算法与数据结构 +--- +### 起因 + +美团面试时面试官出了一道算法题 + +https://leetcode.com/problems/ugly-number-ii/ + +面试时没解决 :( + +### 解决 + +丑数的特点: +1. 每个丑数都是前面数 x (2 | 3 | 5)其中之一 +2. 丑数按照从小到大的顺序排序 + +#### 动态规划 + +```java +public int nthUglyNumber(int input) { + if(input == 0 || input == 1) { + return input; + } + int[] arr = new int[input]; + int t2 = 0, t3 = 0, t5 = 0; + arr[0] = 1; + for(int i = 1; i < input ; i ++) { + arr[i] = Integer.min(arr[t2] * 2,Integer.min(arr[t3] * 3,arr[t5] * 5)); + if (arr[i] == arr[t2] * 2) t2 ++; + if (arr[i] == arr[t3] * 3) t3 ++; + if (arr[i] == arr[t5] * 5) t5 ++; + } + return arr[input - 1]; +} +``` + +我们有三个丑数,1,2,3,那下一个是多少?根据定义,下一个数是 2*(1,2,3), 3*(1,2,3), 5*(1,2,3) 中最小的一个数,这里显然是2,但2早已经在集合里面了。 + +所以更确切的说,下一个丑数是 2*(1,2,3), 3*(1,2,3), 5*(1,2,3) 中最小的且不在集合中的数。 + +我们可以假设集合中最初只有1,那么下一个数是 2* 1,再下一个数是 2 * 2,3 * 1,5 * 1 中最小的一个。 + +于是我们维护三个pointer,pointer与质因数(2 | 3 | 5)相乘,得到了(2 | 3 | 5)中与质因数相乘最小的丑数。 + +#### 最大堆-优先队列 + +解决这个问题可以使用Java提供的两种数据结构。 + +1. PriorityQueue(优先队列) +2. TreeSet + +首先弄明白几个概念: + +##### 优先队列和TreeSet的关系 + +PriorityQueue 使用数组存储,按顺序排列,作为队列,你只能获得他的最大,最小值,可以存储重复的值。 + +TreeSet可以保证数据全部按照某个顺序排列,不允许重复,可获得其中的任意值,提供比优先队列更多的特性,但同样意味着需要进行更多的计算。 + +##### 优先队列和堆的关系 + +优先队列是一种抽象概念,描述了接口和它的行为,并不关联底层的具体实现 + +堆是一种数据结构,它可以通过某种方式来使得数据的存取高效。 + +巧合是的,优先队列使用堆这个数据结构来实现优先队列本身的抽象特点。 + +##### 设计步骤 + +我们需要将计算出来的丑数存放到数组中,数组元素必须按照升序排列,最后的元素就是小于n的最大的丑数。 + +我们先用优先队列来解决这个问题 + +###### 优先队列 + +```java +public int nthUglyNumV2(int input) { + // Obviously, input must great than or equals to zero + if (input <= 1) { + return input; + } + + PriorityQueue queue = new PriorityQueue<>(); + queue.offer(1L); + for ( int i = 1 ; i < input ; i ++) { + Long num = queue.poll(); + // 如果最小的数有重复,那么就去重 + while(!queue.isEmpty() && queue.peek().equals(num)) num = queue.poll(); + queue.offer(num * 2); + queue.offer(num * 3); + queue.offer(num * 5); + } + return queue.poll().intValue(); +} +``` + +Java的优先队列默认使用最小堆,也即,`poll` 获得的是最小的值 + +每一次 `for` 都会将最小的值放在堆顶,从 1 开始,只需要再循环 `n - 1` 次,最后 poll 拉去的就是第 n 个丑数 + +由于使用队列时,数字会有重复,我们可以采用TreeSet来解决。 + +###### TreeSet + +`TreeSet`,顾名思义,首先是 Set,意味着自动去重。 + +而根据 docs + +``` +The elements are ordered using their natural ordering, or by a Comparator provided at set creation time, depending on which constructor is used. +``` + +这里 `TreeSet` 会将整数按照升序排列 + +那我们的代码和上面的优先队列其实很相似,只不过不需要手动 `while` 去重了 + +```java +public int nthUglyNumV3(int input) { + // obviously, input must great than zero + if (input <= 1) { + return input; + } + + TreeSet set = new TreeSet(); + set.add(1L); + for(int i = 1 ; i < input; i ++){ + Long num = set.pollFirst(); + set.add(num * 2); + set.add(num * 3); + set.add(num * 5); + } + return set.pollFirst().intValue(); +} +``` + +### 感想 + +每一道算法题都是独特的,最关键的是寻找出这道题的特点。 + +算法题往往涉及到某个数据结构,利用其特点对结果进行处理。 + +之前挺不喜欢刷题的,但最近看了好多算法,虽然平常几倍的性能差距没那么直观,但当量级逐渐上升之后,算法高效的优势就会显露无疑 + +这里侧重于解决算法问题,而不会手动实现数据结构,后面我会另起一篇文章来手写一个heap。 \ No newline at end of file diff --git a/about/index.md b/about/index.md new file mode 100644 index 0000000..d22e714 --- /dev/null +++ b/about/index.md @@ -0,0 +1,13 @@ +--- +title: 关于我 +date: 2019-09-20 08:34:13 +--- + +『给互联网留下点什么,证明我来过』 +联系我 +> qaq1362211689\.com + + + diff --git a/assets/2020_ELCE_initcalls_myjosserand.pdf b/assets/2020_ELCE_initcalls_myjosserand.pdf new file mode 100644 index 0000000..431af68 Binary files /dev/null and b/assets/2020_ELCE_initcalls_myjosserand.pdf differ diff --git a/assets/2023-10-10-15-13-36.png b/assets/2023-10-10-15-13-36.png new file mode 100644 index 0000000..b426300 Binary files /dev/null and b/assets/2023-10-10-15-13-36.png differ diff --git a/assets/2023-10-10-15-14-07.png b/assets/2023-10-10-15-14-07.png new file mode 100644 index 0000000..1bb725c Binary files /dev/null and b/assets/2023-10-10-15-14-07.png differ diff --git a/assets/2023-10-10-15-14-23.png b/assets/2023-10-10-15-14-23.png new file mode 100644 index 0000000..af35148 Binary files /dev/null and b/assets/2023-10-10-15-14-23.png differ diff --git a/assets/2023-10-10-15-14-47.png b/assets/2023-10-10-15-14-47.png new file mode 100644 index 0000000..eef3476 Binary files /dev/null and b/assets/2023-10-10-15-14-47.png differ diff --git a/assets/2023-10-10-15-14-59.png b/assets/2023-10-10-15-14-59.png new file mode 100644 index 0000000..1abca0e Binary files /dev/null and b/assets/2023-10-10-15-14-59.png differ diff --git a/assets/2023-10-10-15-15-09.png b/assets/2023-10-10-15-15-09.png new file mode 100644 index 0000000..e64883f Binary files /dev/null and b/assets/2023-10-10-15-15-09.png differ diff --git a/assets/2023-10-10-15-15-36.png b/assets/2023-10-10-15-15-36.png new file mode 100644 index 0000000..e667b9e Binary files /dev/null and b/assets/2023-10-10-15-15-36.png differ diff --git a/assets/2023-10-10-15-16-09.png b/assets/2023-10-10-15-16-09.png new file mode 100644 index 0000000..fade32c Binary files /dev/null and b/assets/2023-10-10-15-16-09.png differ diff --git a/assets/2023-10-10-15-16-34.png b/assets/2023-10-10-15-16-34.png new file mode 100644 index 0000000..51c1825 Binary files /dev/null and b/assets/2023-10-10-15-16-34.png differ diff --git a/assets/2023-10-10-15-16-46.png b/assets/2023-10-10-15-16-46.png new file mode 100644 index 0000000..be67839 Binary files /dev/null and b/assets/2023-10-10-15-16-46.png differ diff --git a/assets/2023-10-10-15-16-58.png b/assets/2023-10-10-15-16-58.png new file mode 100644 index 0000000..aed8cc5 Binary files /dev/null and b/assets/2023-10-10-15-16-58.png differ diff --git a/assets/2023-10-10-15-17-19.png b/assets/2023-10-10-15-17-19.png new file mode 100644 index 0000000..2a72fb1 Binary files /dev/null and b/assets/2023-10-10-15-17-19.png differ diff --git a/assets/2023-10-10-15-17-27.png b/assets/2023-10-10-15-17-27.png new file mode 100644 index 0000000..e9f892d Binary files /dev/null and b/assets/2023-10-10-15-17-27.png differ diff --git a/assets/2023-10-10-15-18-25.png b/assets/2023-10-10-15-18-25.png new file mode 100644 index 0000000..d9a4bb5 Binary files /dev/null and b/assets/2023-10-10-15-18-25.png differ diff --git a/assets/2023-10-10-15-18-38.png b/assets/2023-10-10-15-18-38.png new file mode 100644 index 0000000..56961c6 Binary files /dev/null and b/assets/2023-10-10-15-18-38.png differ diff --git a/assets/2023-10-10-15-32-23.png b/assets/2023-10-10-15-32-23.png new file mode 100644 index 0000000..427e53b Binary files /dev/null and b/assets/2023-10-10-15-32-23.png differ diff --git a/assets/2023-10-12-11-27-04.png b/assets/2023-10-12-11-27-04.png new file mode 100644 index 0000000..d42173c Binary files /dev/null and b/assets/2023-10-12-11-27-04.png differ diff --git a/assets/2023-10-12-11-28-41.png b/assets/2023-10-12-11-28-41.png new file mode 100644 index 0000000..c63a68f Binary files /dev/null and b/assets/2023-10-12-11-28-41.png differ diff --git a/assets/2023-10-14-10-12-16.png b/assets/2023-10-14-10-12-16.png new file mode 100644 index 0000000..5a8618e Binary files /dev/null and b/assets/2023-10-14-10-12-16.png differ diff --git a/assets/2023-10-14-10-20-00.png b/assets/2023-10-14-10-20-00.png new file mode 100644 index 0000000..3332673 Binary files /dev/null and b/assets/2023-10-14-10-20-00.png differ diff --git a/assets/2023-10-14-11-41-09.png b/assets/2023-10-14-11-41-09.png new file mode 100644 index 0000000..0da79eb Binary files /dev/null and b/assets/2023-10-14-11-41-09.png differ diff --git a/assets/2023-10-14-13-45-03.png b/assets/2023-10-14-13-45-03.png new file mode 100644 index 0000000..c7f533f Binary files /dev/null and b/assets/2023-10-14-13-45-03.png differ diff --git a/assets/2023-10-17-20-30-16.png b/assets/2023-10-17-20-30-16.png new file mode 100644 index 0000000..46a0471 Binary files /dev/null and b/assets/2023-10-17-20-30-16.png differ diff --git a/assets/2023-10-19-15-55-22.png b/assets/2023-10-19-15-55-22.png new file mode 100644 index 0000000..7164978 Binary files /dev/null and b/assets/2023-10-19-15-55-22.png differ diff --git a/assets/2023-10-20-14-39-55.png b/assets/2023-10-20-14-39-55.png new file mode 100644 index 0000000..2dd701e Binary files /dev/null and b/assets/2023-10-20-14-39-55.png differ diff --git a/assets/2023-10-20-14-43-21.png b/assets/2023-10-20-14-43-21.png new file mode 100644 index 0000000..2221d17 Binary files /dev/null and b/assets/2023-10-20-14-43-21.png differ diff --git a/assets/2023-10-20-14-47-30.png b/assets/2023-10-20-14-47-30.png new file mode 100644 index 0000000..4194eb9 Binary files /dev/null and b/assets/2023-10-20-14-47-30.png differ diff --git a/assets/2023-10-20-15-02-34.png b/assets/2023-10-20-15-02-34.png new file mode 100644 index 0000000..5f990d5 Binary files /dev/null and b/assets/2023-10-20-15-02-34.png differ diff --git a/assets/2023-10-20-15-16-02.png b/assets/2023-10-20-15-16-02.png new file mode 100644 index 0000000..217d385 Binary files /dev/null and b/assets/2023-10-20-15-16-02.png differ diff --git a/assets/2023-10-20-15-23-35.png b/assets/2023-10-20-15-23-35.png new file mode 100644 index 0000000..da55bbf Binary files /dev/null and b/assets/2023-10-20-15-23-35.png differ diff --git a/assets/2023-10-23-16-51-11.png b/assets/2023-10-23-16-51-11.png new file mode 100644 index 0000000..e5d93f4 Binary files /dev/null and b/assets/2023-10-23-16-51-11.png differ diff --git a/assets/2023-10-23-16-52-11.png b/assets/2023-10-23-16-52-11.png new file mode 100644 index 0000000..1d45bbc Binary files /dev/null and b/assets/2023-10-23-16-52-11.png differ diff --git a/assets/2023-10-23-16-52-49.png b/assets/2023-10-23-16-52-49.png new file mode 100644 index 0000000..6f9c1c7 Binary files /dev/null and b/assets/2023-10-23-16-52-49.png differ diff --git a/assets/2023-10-24-1.png b/assets/2023-10-24-1.png new file mode 100644 index 0000000..b547042 Binary files /dev/null and b/assets/2023-10-24-1.png differ diff --git a/assets/2023-10-24-140554105.png b/assets/2023-10-24-140554105.png new file mode 100644 index 0000000..b547042 Binary files /dev/null and b/assets/2023-10-24-140554105.png differ diff --git a/assets/2023-10-24-140556211.png b/assets/2023-10-24-140556211.png new file mode 100644 index 0000000..b547042 Binary files /dev/null and b/assets/2023-10-24-140556211.png differ diff --git a/assets/2023-10-24-140558764.png b/assets/2023-10-24-140558764.png new file mode 100644 index 0000000..b547042 Binary files /dev/null and b/assets/2023-10-24-140558764.png differ diff --git a/assets/2023-10-24-140558765.png b/assets/2023-10-24-140558765.png new file mode 100644 index 0000000..b547042 Binary files /dev/null and b/assets/2023-10-24-140558765.png differ diff --git a/assets/2023-10-24-140558766.png b/assets/2023-10-24-140558766.png new file mode 100644 index 0000000..b547042 Binary files /dev/null and b/assets/2023-10-24-140558766.png differ diff --git a/assets/2023-10-24-140558767.png b/assets/2023-10-24-140558767.png new file mode 100644 index 0000000..b547042 Binary files /dev/null and b/assets/2023-10-24-140558767.png differ diff --git a/assets/2023-10-24-140558768.png b/assets/2023-10-24-140558768.png new file mode 100644 index 0000000..b547042 Binary files /dev/null and b/assets/2023-10-24-140558768.png differ diff --git a/assets/2023-10-24-140558769.png b/assets/2023-10-24-140558769.png new file mode 100644 index 0000000..79ee16a Binary files /dev/null and b/assets/2023-10-24-140558769.png differ diff --git a/assets/2023-10-24.png b/assets/2023-10-24.png new file mode 100644 index 0000000..b547042 Binary files /dev/null and b/assets/2023-10-24.png differ diff --git a/assets/2023-10-25-1.pdf b/assets/2023-10-25-1.pdf new file mode 100644 index 0000000..01cb702 Binary files /dev/null and b/assets/2023-10-25-1.pdf differ diff --git a/assets/2023-10-25.pdf b/assets/2023-10-25.pdf new file mode 100644 index 0000000..7b4ca05 Binary files /dev/null and b/assets/2023-10-25.pdf differ diff --git a/assets/2023-10-26-1.pdf b/assets/2023-10-26-1.pdf new file mode 100644 index 0000000..1c58558 Binary files /dev/null and b/assets/2023-10-26-1.pdf differ diff --git a/assets/2023-10-26-1.png b/assets/2023-10-26-1.png new file mode 100644 index 0000000..bed3516 Binary files /dev/null and b/assets/2023-10-26-1.png differ diff --git a/assets/2023-10-26-2.pdf b/assets/2023-10-26-2.pdf new file mode 100644 index 0000000..8919899 Binary files /dev/null and b/assets/2023-10-26-2.pdf differ diff --git a/assets/2023-10-26-3.pdf b/assets/2023-10-26-3.pdf new file mode 100644 index 0000000..c96e2d4 Binary files /dev/null and b/assets/2023-10-26-3.pdf differ diff --git a/assets/2023-10-26-4.pdf b/assets/2023-10-26-4.pdf new file mode 100644 index 0000000..c96e2d4 Binary files /dev/null and b/assets/2023-10-26-4.pdf differ diff --git a/assets/2023-10-26-5.pdf b/assets/2023-10-26-5.pdf new file mode 100644 index 0000000..a8cbcac Binary files /dev/null and b/assets/2023-10-26-5.pdf differ diff --git a/assets/2023-10-26.pdf b/assets/2023-10-26.pdf new file mode 100644 index 0000000..37be790 Binary files /dev/null and b/assets/2023-10-26.pdf differ diff --git a/assets/2023-10-26.png b/assets/2023-10-26.png new file mode 100644 index 0000000..3f18b56 Binary files /dev/null and b/assets/2023-10-26.png differ diff --git a/assets/2023-10-30-1.png b/assets/2023-10-30-1.png new file mode 100644 index 0000000..7835ee1 Binary files /dev/null and b/assets/2023-10-30-1.png differ diff --git a/assets/2023-10-30.png b/assets/2023-10-30.png new file mode 100644 index 0000000..f39a6de Binary files /dev/null and b/assets/2023-10-30.png differ diff --git a/assets/2023-10-31-17.png b/assets/2023-10-31-17.png new file mode 100644 index 0000000..a802933 Binary files /dev/null and b/assets/2023-10-31-17.png differ diff --git a/assets/2023-10-31-18.png b/assets/2023-10-31-18.png new file mode 100644 index 0000000..6703502 Binary files /dev/null and b/assets/2023-10-31-18.png differ diff --git a/assets/2023-10-31-19.png b/assets/2023-10-31-19.png new file mode 100644 index 0000000..6f3a00a Binary files /dev/null and b/assets/2023-10-31-19.png differ diff --git a/assets/2023-10-31-20.png b/assets/2023-10-31-20.png new file mode 100644 index 0000000..b4b37e3 Binary files /dev/null and b/assets/2023-10-31-20.png differ diff --git a/assets/2023-10-31-21.png b/assets/2023-10-31-21.png new file mode 100644 index 0000000..f078921 Binary files /dev/null and b/assets/2023-10-31-21.png differ diff --git a/assets/2023-10-31-22.png b/assets/2023-10-31-22.png new file mode 100644 index 0000000..9c8d234 Binary files /dev/null and b/assets/2023-10-31-22.png differ diff --git a/assets/2023-10-31-23.png b/assets/2023-10-31-23.png new file mode 100644 index 0000000..fd73102 Binary files /dev/null and b/assets/2023-10-31-23.png differ diff --git a/assets/2023-10-31-7.png.md b/assets/2023-10-31-7.png.md new file mode 100644 index 0000000..e69de29 diff --git a/assets/2023-10-31.png b/assets/2023-10-31.png new file mode 100644 index 0000000..03a424d Binary files /dev/null and b/assets/2023-10-31.png differ diff --git a/assets/2023-1024-14-0502993.png b/assets/2023-1024-14-0502993.png new file mode 100644 index 0000000..b547042 Binary files /dev/null and b/assets/2023-1024-14-0502993.png differ diff --git a/assets/2023-1024-14-0528008.png b/assets/2023-1024-14-0528008.png new file mode 100644 index 0000000..b547042 Binary files /dev/null and b/assets/2023-1024-14-0528008.png differ diff --git a/assets/2023-11-01-2.png b/assets/2023-11-01-2.png new file mode 100644 index 0000000..cbdadf3 Binary files /dev/null and b/assets/2023-11-01-2.png differ diff --git a/assets/2023-11-01-40.png b/assets/2023-11-01-40.png new file mode 100644 index 0000000..3431024 Binary files /dev/null and b/assets/2023-11-01-40.png differ diff --git a/assets/2023-11-01-41.png b/assets/2023-11-01-41.png new file mode 100644 index 0000000..9dc8491 Binary files /dev/null and b/assets/2023-11-01-41.png differ diff --git a/assets/2023-11-01.png b/assets/2023-11-01.png new file mode 100644 index 0000000..89cc196 Binary files /dev/null and b/assets/2023-11-01.png differ diff --git a/assets/2023-11-05-1.png b/assets/2023-11-05-1.png new file mode 100644 index 0000000..0d72996 Binary files /dev/null and b/assets/2023-11-05-1.png differ diff --git a/assets/2023-11-06-1.png b/assets/2023-11-06-1.png new file mode 100644 index 0000000..0dbe442 Binary files /dev/null and b/assets/2023-11-06-1.png differ diff --git a/assets/2023-11-06-2.png b/assets/2023-11-06-2.png new file mode 100644 index 0000000..277a79c Binary files /dev/null and b/assets/2023-11-06-2.png differ diff --git a/assets/2023-11-06-27.png b/assets/2023-11-06-27.png new file mode 100644 index 0000000..0eabb74 Binary files /dev/null and b/assets/2023-11-06-27.png differ diff --git a/assets/2023-11-06-3.png b/assets/2023-11-06-3.png new file mode 100644 index 0000000..919a784 Binary files /dev/null and b/assets/2023-11-06-3.png differ diff --git a/assets/2023-11-06.png b/assets/2023-11-06.png new file mode 100644 index 0000000..0dbe442 Binary files /dev/null and b/assets/2023-11-06.png differ diff --git a/assets/2023-11-07-1.png b/assets/2023-11-07-1.png new file mode 100644 index 0000000..d0450a0 Binary files /dev/null and b/assets/2023-11-07-1.png differ diff --git a/assets/2023-11-07-12.png.md b/assets/2023-11-07-12.png.md new file mode 100644 index 0000000..e69de29 diff --git a/assets/2023-11-07-3.png b/assets/2023-11-07-3.png new file mode 100644 index 0000000..405af9a Binary files /dev/null and b/assets/2023-11-07-3.png differ diff --git a/assets/2023-11-07-4.png b/assets/2023-11-07-4.png new file mode 100644 index 0000000..9cca467 Binary files /dev/null and b/assets/2023-11-07-4.png differ diff --git a/assets/2023-11-07.png b/assets/2023-11-07.png new file mode 100644 index 0000000..405af9a Binary files /dev/null and b/assets/2023-11-07.png differ diff --git a/assets/2023-11-14-1.png b/assets/2023-11-14-1.png new file mode 100644 index 0000000..13898d0 Binary files /dev/null and b/assets/2023-11-14-1.png differ diff --git a/assets/2023-11-14-2.png b/assets/2023-11-14-2.png new file mode 100644 index 0000000..3c4ccf7 Binary files /dev/null and b/assets/2023-11-14-2.png differ diff --git a/assets/2023-11-14.png b/assets/2023-11-14.png new file mode 100644 index 0000000..fa64c2b Binary files /dev/null and b/assets/2023-11-14.png differ diff --git a/assets/2023-11-16.png b/assets/2023-11-16.png new file mode 100644 index 0000000..f6fc393 Binary files /dev/null and b/assets/2023-11-16.png differ diff --git a/assets/2023-11-17.png b/assets/2023-11-17.png new file mode 100644 index 0000000..9d4a24f Binary files /dev/null and b/assets/2023-11-17.png differ diff --git a/assets/2023-11-20.png b/assets/2023-11-20.png new file mode 100644 index 0000000..d0450a0 Binary files /dev/null and b/assets/2023-11-20.png differ diff --git a/assets/2023-12-03-1.png b/assets/2023-12-03-1.png new file mode 100644 index 0000000..143b5ee Binary files /dev/null and b/assets/2023-12-03-1.png differ diff --git a/assets/2023-12-14-1.png b/assets/2023-12-14-1.png new file mode 100644 index 0000000..2dbce97 Binary files /dev/null and b/assets/2023-12-14-1.png differ diff --git a/assets/20231024-1.png b/assets/20231024-1.png new file mode 100644 index 0000000..b547042 Binary files /dev/null and b/assets/20231024-1.png differ diff --git a/assets/20231024.png b/assets/20231024.png new file mode 100644 index 0000000..b547042 Binary files /dev/null and b/assets/20231024.png differ diff --git "a/assets/80211\346\224\266\345\214\205\346\211\247\350\241\214\350\267\257\345\276\204\345\210\206\346\236\220-1.png" "b/assets/80211\346\224\266\345\214\205\346\211\247\350\241\214\350\267\257\345\276\204\345\210\206\346\236\220-1.png" new file mode 100644 index 0000000..b547042 Binary files /dev/null and "b/assets/80211\346\224\266\345\214\205\346\211\247\350\241\214\350\267\257\345\276\204\345\210\206\346\236\220-1.png" differ diff --git "a/assets/80211\346\224\266\345\214\205\346\211\247\350\241\214\350\267\257\345\276\204\345\210\206\346\236\220-2.png" "b/assets/80211\346\224\266\345\214\205\346\211\247\350\241\214\350\267\257\345\276\204\345\210\206\346\236\220-2.png" new file mode 100644 index 0000000..b547042 Binary files /dev/null and "b/assets/80211\346\224\266\345\214\205\346\211\247\350\241\214\350\267\257\345\276\204\345\210\206\346\236\220-2.png" differ diff --git "a/assets/80211\346\224\266\345\214\205\346\211\247\350\241\214\350\267\257\345\276\204\345\210\206\346\236\220-3.png" "b/assets/80211\346\224\266\345\214\205\346\211\247\350\241\214\350\267\257\345\276\204\345\210\206\346\236\220-3.png" new file mode 100644 index 0000000..b547042 Binary files /dev/null and "b/assets/80211\346\224\266\345\214\205\346\211\247\350\241\214\350\267\257\345\276\204\345\210\206\346\236\220-3.png" differ diff --git "a/assets/80211\346\224\266\345\214\205\346\211\247\350\241\214\350\267\257\345\276\204\345\210\206\346\236\220-4.png" "b/assets/80211\346\224\266\345\214\205\346\211\247\350\241\214\350\267\257\345\276\204\345\210\206\346\236\220-4.png" new file mode 100644 index 0000000..b547042 Binary files /dev/null and "b/assets/80211\346\224\266\345\214\205\346\211\247\350\241\214\350\267\257\345\276\204\345\210\206\346\236\220-4.png" differ diff --git "a/assets/80211\346\224\266\345\214\205\346\211\247\350\241\214\350\267\257\345\276\204\345\210\206\346\236\220.pdf" "b/assets/80211\346\224\266\345\214\205\346\211\247\350\241\214\350\267\257\345\276\204\345\210\206\346\236\220.pdf" new file mode 100644 index 0000000..5f74b39 Binary files /dev/null and "b/assets/80211\346\224\266\345\214\205\346\211\247\350\241\214\350\267\257\345\276\204\345\210\206\346\236\220.pdf" differ diff --git "a/assets/80211\346\224\266\345\214\205\346\211\247\350\241\214\350\267\257\345\276\204\345\210\206\346\236\220.png" "b/assets/80211\346\224\266\345\214\205\346\211\247\350\241\214\350\267\257\345\276\204\345\210\206\346\236\220.png" new file mode 100644 index 0000000..b547042 Binary files /dev/null and "b/assets/80211\346\224\266\345\214\205\346\211\247\350\241\214\350\267\257\345\276\204\345\210\206\346\236\220.png" differ diff --git a/assets/Excalidraw/Drawing 2023-10-31 16.53.50.excalidraw.md b/assets/Excalidraw/Drawing 2023-10-31 16.53.50.excalidraw.md new file mode 100644 index 0000000..56d9ded --- /dev/null +++ b/assets/Excalidraw/Drawing 2023-10-31 16.53.50.excalidraw.md @@ -0,0 +1,186 @@ +--- + +excalidraw-plugin: raw +tags: [excalidraw] + +--- +==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== + + +# Text Elements + +# Embedded files +9390a7da529755403fcd0992dca949e226b6bd4b: [[assets/Pasted Image 20231031175353_420.png]] + +%% +# Drawing +```json +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.9.27", + "elements": [ + { + "type": "rectangle", + "version": 232, + "versionNonce": 71742523, + "isDeleted": false, + "id": "XbIeoET18-VjvTLZXZabk", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -243.20009792242877, + "y": -363.63117899263943, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 321.27734375, + "height": 418.78125, + "seed": 1627052891, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1698743008891, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 48, + "versionNonce": 1122082683, + "isDeleted": false, + "id": "auXdpe0Xs1k2xMCB27bkq", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -477.8055462078108, + "y": -394.4084679454253, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 195.5554184054388, + "height": 363.44892340896513, + "seed": 2116106779, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1698743012623, + "link": null, + "locked": false + }, + { + "id": "NVcMt-2_QUW2WxYFuAFtg", + "type": "image", + "x": -258.8234429532778, + "y": -237.5227017585061, + "width": 63.46100022301515, + "height": 39.55139802631578, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1962274837, + "version": 133, + "versionNonce": 1045460795, + "isDeleted": false, + "boundElements": null, + "updated": 1698746032637, + "link": null, + "locked": false, + "status": "pending", + "fileId": "9390a7da529755403fcd0992dca949e226b6bd4b", + "scale": [ + 1, + 1 + ] + }, + { + "id": "M8_u6lQ4F4w6wkxuv7kVw", + "type": "image", + "x": -294.87114032169876, + "y": -289.6333103111377, + "width": 41.54834351503767, + "height": 28.513569078947423, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 848671093, + "version": 217, + "versionNonce": 1084402299, + "isDeleted": true, + "boundElements": null, + "updated": 1698746017283, + "link": null, + "locked": false, + "status": "pending", + "fileId": "b98aceba1224ca2fe39d249d5b3c191d7b2df20f", + "scale": [ + 1, + 1 + ] + } + ], + "appState": { + "theme": "light", + "viewBackgroundColor": "#ffffff", + "currentItemStrokeColor": "#1e1e1e", + "currentItemBackgroundColor": "transparent", + "currentItemFillStyle": "solid", + "currentItemStrokeWidth": 2, + "currentItemStrokeStyle": "solid", + "currentItemRoughness": 0, + "currentItemOpacity": 100, + "currentItemFontFamily": 1, + "currentItemFontSize": 20, + "currentItemTextAlign": "left", + "currentItemStartArrowhead": null, + "currentItemEndArrowhead": "arrow", + "scrollX": 512.6273080848566, + "scrollY": 411.4918629427166, + "zoom": { + "value": 1.9000000000000001 + }, + "currentItemRoundness": "round", + "gridSize": null, + "gridColor": { + "Bold": "#C9C9C9FF", + "Regular": "#EDEDEDFF" + }, + "currentStrokeOptions": null, + "previousGridSize": null, + "frameRendering": { + "enabled": true, + "clip": true, + "name": true, + "outline": true + } + }, + "files": {} +} +``` +%% \ No newline at end of file diff --git a/assets/Excalidraw/drawing-2023-10-31 17.06.57.excalidraw.md b/assets/Excalidraw/drawing-2023-10-31 17.06.57.excalidraw.md new file mode 100644 index 0000000..2149c0c --- /dev/null +++ b/assets/Excalidraw/drawing-2023-10-31 17.06.57.excalidraw.md @@ -0,0 +1,15 @@ +--- + +excalidraw-plugin: parsed +tags: [excalidraw] + +--- +==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== + + +%% +# Drawing +```json +{"type":"excalidraw","version":2,"source":"https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.9.27","elements":[],"appState":{"gridSize":null,"viewBackgroundColor":"#ffffff"}} +``` +%% \ No newline at end of file diff --git a/assets/Excalidraw/drawing-2023-10-31 17.07.01.excalidraw.md b/assets/Excalidraw/drawing-2023-10-31 17.07.01.excalidraw.md new file mode 100644 index 0000000..2149c0c --- /dev/null +++ b/assets/Excalidraw/drawing-2023-10-31 17.07.01.excalidraw.md @@ -0,0 +1,15 @@ +--- + +excalidraw-plugin: parsed +tags: [excalidraw] + +--- +==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== + + +%% +# Drawing +```json +{"type":"excalidraw","version":2,"source":"https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.9.27","elements":[],"appState":{"gridSize":null,"viewBackgroundColor":"#ffffff"}} +``` +%% \ No newline at end of file diff --git a/assets/First.canvas b/assets/First.canvas new file mode 100644 index 0000000..997b996 --- /dev/null +++ b/assets/First.canvas @@ -0,0 +1,7 @@ +{ + "nodes":[ + {"id":"a058c5ee0a4a3374","type":"file","file":"assets/2023-10-10-15-17-19.png","x":-420,"y":-513,"width":400,"height":319}, + {"id":"56f39412b5a0f385","type":"file","file":"Pasted image 20231024105608.png","x":-880,"y":-411,"width":400,"height":217} + ], + "edges":[] +} \ No newline at end of file diff --git a/assets/MHVLUG_2017-04_Network_Receive_Stack.pdf b/assets/MHVLUG_2017-04_Network_Receive_Stack.pdf new file mode 100644 index 0000000..8902340 Binary files /dev/null and b/assets/MHVLUG_2017-04_Network_Receive_Stack.pdf differ diff --git a/assets/NET-2022-01-1_11.pdf b/assets/NET-2022-01-1_11.pdf new file mode 100644 index 0000000..bea9444 Binary files /dev/null and b/assets/NET-2022-01-1_11.pdf differ diff --git a/assets/Pasted Image 20231031175308_401.png b/assets/Pasted Image 20231031175308_401.png new file mode 100644 index 0000000..75e4357 Binary files /dev/null and b/assets/Pasted Image 20231031175308_401.png differ diff --git a/assets/Pasted Image 20231031175353_420.png b/assets/Pasted Image 20231031175353_420.png new file mode 100644 index 0000000..c9cdd42 Binary files /dev/null and b/assets/Pasted Image 20231031175353_420.png differ diff --git a/assets/Pasted image 20231024135446.png b/assets/Pasted image 20231024135446.png new file mode 100644 index 0000000..b547042 Binary files /dev/null and b/assets/Pasted image 20231024135446.png differ diff --git a/assets/Pasted image 20231024135742.png b/assets/Pasted image 20231024135742.png new file mode 100644 index 0000000..b547042 Binary files /dev/null and b/assets/Pasted image 20231024135742.png differ diff --git a/assets/Pasted image 20231024142355.png b/assets/Pasted image 20231024142355.png new file mode 100644 index 0000000..b547042 Binary files /dev/null and b/assets/Pasted image 20231024142355.png differ diff --git a/assets/canvas/Linux.canvas b/assets/canvas/Linux.canvas new file mode 100644 index 0000000..ea14b45 --- /dev/null +++ b/assets/canvas/Linux.canvas @@ -0,0 +1,15 @@ +{ + "nodes":[ + {"id":"af74362181736e04","x":-560,"y":-4400,"width":250,"height":60,"type":"text","text":"Linux"}, + {"id":"7e293a6eb688b347","x":-560,"y":-4160,"width":250,"height":60,"type":"text","text":"NET"}, + {"id":"725e4c9d7de3e296","x":-1120,"y":-3960,"width":444,"height":400,"type":"file","file":"计算机/Linux/源码分析/发包callstack分析.md"}, + {"id":"0034e0aff52a1eed","x":-635,"y":-3960,"width":400,"height":400,"type":"file","file":"计算机/Linux/源码分析/kernel-net.md"}, + {"id":"01dbd600e90ae92f","x":-200,"y":-3960,"width":400,"height":400,"type":"file","file":"计算机/Linux/源码分析/e1000收包callstack分析.md"} + ], + "edges":[ + {"id":"f40bf9fc86da7027","fromNode":"af74362181736e04","fromSide":"bottom","toNode":"7e293a6eb688b347","toSide":"top"}, + {"id":"a38c74d4daf79e89","fromNode":"7e293a6eb688b347","fromSide":"bottom","toNode":"725e4c9d7de3e296","toSide":"top"}, + {"id":"5b00b5ee52ec64f7","fromNode":"7e293a6eb688b347","fromSide":"bottom","toNode":"0034e0aff52a1eed","toSide":"top"}, + {"id":"020a39ab0026cb22","fromNode":"7e293a6eb688b347","fromSide":"bottom","toNode":"01dbd600e90ae92f","toSide":"top"} + ] +} \ No newline at end of file diff --git "a/assets/clash-tun\351\273\230\350\256\244\351\205\215\347\275\256-20231031161557644.png" "b/assets/clash-tun\351\273\230\350\256\244\351\205\215\347\275\256-20231031161557644.png" new file mode 100644 index 0000000..4511f0f Binary files /dev/null and "b/assets/clash-tun\351\273\230\350\256\244\351\205\215\347\275\256-20231031161557644.png" differ diff --git a/assets/debug-mt7921.png b/assets/debug-mt7921.png new file mode 100644 index 0000000..3e5d0b5 Binary files /dev/null and b/assets/debug-mt7921.png differ diff --git a/assets/h616.png b/assets/h616.png new file mode 100644 index 0000000..c872c03 Binary files /dev/null and b/assets/h616.png differ diff --git a/assets/ixy-writing-user-space-network-drivers.pdf b/assets/ixy-writing-user-space-network-drivers.pdf new file mode 100644 index 0000000..42ee1a2 Binary files /dev/null and b/assets/ixy-writing-user-space-network-drivers.pdf differ diff --git "a/assets/mt7921-usb\351\251\261\345\212\250\346\263\250\345\206\214\345\210\206\346\236\220.png" "b/assets/mt7921-usb\351\251\261\345\212\250\346\263\250\345\206\214\345\210\206\346\236\220.png" new file mode 100644 index 0000000..b547042 Binary files /dev/null and "b/assets/mt7921-usb\351\251\261\345\212\250\346\263\250\345\206\214\345\210\206\346\236\220.png" differ diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..dc4333a Binary files /dev/null and b/favicon.ico differ diff --git a/images/about.png b/images/about.png new file mode 100644 index 0000000..85d3ac9 Binary files /dev/null and b/images/about.png differ diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..5b07bdc --- /dev/null +++ b/robots.txt @@ -0,0 +1,3 @@ +User-agent: * +Allow: / +Sitemap: https://www.chaochaogege.com/sitemap.xml \ No newline at end of file diff --git a/tags/index.md b/tags/index.md new file mode 100644 index 0000000..de7cc45 --- /dev/null +++ b/tags/index.md @@ -0,0 +1,5 @@ +--- +title: tags +date: 2023-09-12 13:46:34 +type: "tags" +---