404频道

学习笔记

Linux虚拟网络设备 - tap/tun

tap/tun常用于隧道通讯,通过一个字符设备来实现用户态和内核态的通讯,字符设备一端连接着用户空间,一端连接着内核空间。

对应的字符设备文件位置:

  • tap: /dev/tap0
  • tun: /dev/net/tun

当应用程序打开字符设备文件时,驱动程序会创建并注册相应的虚拟设备接口,以tunX或tapX命名。应用程序关闭设备文件时,驱动程序会删除tunX和tapX网络虚拟设备,并删除建立起来的路由信息。

两个设备的不同点:

  • tap是一个二层网络设备,只能处理二层的以太网帧
  • tun是一个点对点的三层网络设备,只能处理处理三层的IP数据包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
                ┌──────────────┐
│ │
│ APP │
│ │
└───────┬──────┘






┌────────────▼──────────┐
│ │
─ ─ ─ ─ ─ ─│ /dev/net/tun ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
│ │
└────────────┬──────────┘





┌───────▼──────┐ ┌──────────────┐
│ │ │ │
│ tunX ├────────────────▶│Network Stack │
│ │ │ │
└──────────────┘ └──────────────┘

tun设备应用举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
          ┌──────────────┐         ┌──────────────┐
│ │ │ │
│ APP A │ │ APP B │◀┐
│ │ │ │ │
└───────┬──────┘ └───────┬──────┘ │
│ │ │
│ │ │
1│ │ │
│ 5│ │
│ │ │
─ ─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│─ ─ ─ ─ ┼ ─ ─ ─ ─ ─
│ │ │
│ │ 4│
│ │ │
┌────────────▼────────────────────────▼─────┐ │
│ │ │
│ Network Stack │ │
│ │ │
└────────────┬───────────────────────┬──────┘ │
│ │ │
6│ 3│ │
│ │ │
┌───────▼──────┐ ┌─▼─────────┴──┐
│ │ │ │
10.1.1.11 │ eth0 │ │ tun0 │ 192.168.1.11
│ │ │ │
└───────┬──────┘ └──────────────┘

7│



10.1.1.100 / 192.168.1.100

应用程序A要发送数据到其他物理机192.168.1.100,由于物理网络环境下只有10.1.1.11和10.1.1.100是相互连通的,192.168.1.11和192.168.1.100是不通的,为了192.168.1.11和192.168.1.100能够进行通讯,需要将数据包进行一次封装。

应用程序B是通过打开字符设备文件/dev/net/tun0的方式来打开网络设备

流程如下:

  1. A构造数据包,目的ip为192.168.1.100,并发送给协议栈
  2. 协议栈根据数据包中的ip地址,匹配路由规则,要从tun0出去
  3. 内核协议栈将数据包发送给tun0网络设备
  4. tun0发送应用程序B打开,于是将数据发送给应用程序B
  5. B收到数据包后,在用户态构造一个新的数据包,源IP为eth0的IP 10.1.1.11,目的IP为配置的对端10.1.1.100,并封装原来的数据包
  6. 协议栈根据当前数据包的IP地址选择路由,将数据包发送给eth0

reference

行程:海南藏族自治州黑马河乡(9:30) -> 茶卡盐湖(11:30 - 15:00)-> 柴达木盆地 -> 可鲁克湖(18:00 - 18:30) -> 大柴旦镇(21:00)

路程:500+公里

日期:2019.4.22

今天是整个行程的第五天,第四天晚上到达了青海湖西端的黑马河乡,今天的主要行程是茶卡盐湖,以及横穿几乎整个柴达木盆地。

黑马河日出

黑马河地处青海湖最西端,往东边看去是平静的湖水,完全没有任何遮挡,加上高原的空气通透性特别好,况且地处干旱地带,以晴天为主,是个看日出的绝佳地点。

从黑马河赶往茶卡镇需要经过橡皮山,橡皮山口的海拔在3800米+,是整个行程中的海拔最高点。此时的橡皮山还完全是一座雪山,是整个行程中的第二次到达雪山。同时也是由于这些山脉的原因,才阻隔了青海湖和茶卡盐湖,使其分别成为了只进不出的咸水湖。

茶卡盐湖

茶卡盐湖几乎是青海旅游的毕竟之地,无论是环青海湖小环线,还是青海甘肃大环线,同时也是网红景点,网络上有太多穿着红色裙子的漂亮的小姐姐的照片和视频。

茶卡盐湖地处柴达木盆地,周边地势较低,在离盐湖十几公里的地方湖泊就清晰可见,天上的白云倒影在湖中,宛如一面镜子,因此素有“天空之境”之称。远处的昆仑山脉和祁连山脉的雪山清晰可见,由于天空太过透彻,使常年生活在华北地区的我很难判断出雪山的距离。

采盐船
(图为采盐船)

茶卡盐湖的开发利用相当充分,一方面是用来开发采盐,并建有盐厂,湖面上可以看到开采盐矿的船只。另一方面,通过旅游来进一步最大化其价值,其实整个柴达木盆地有很多的盐湖,但最为大众所熟知的仅此一家。而且旅游跟采盐是完全分离的,互不相干,毕竟大部分游客都是来拍照的,旅游占用的盐湖面积比重相对较小。

茶卡盐湖的含盐量实在太高,大部分地方湖面的水都较浅,仅有薄薄的一层,下面全是沉积的盐矿,我此行并没有看到有数米深的湖水。很多地方是可以穿着靴子进入到里面的,但要小心盐坑,看似平坦的盐矿其实有很多的暗洞,很容易就陷进去,好在游客能去的地方盐洞都给堵住了,没啥安全问题。上图中黄色的网状结构为防止游客陷入的盖子。

死海的含盐量肯定没有盐湖的含量高的,尚且人体可以漂浮在表面,我想茶卡盐湖如果有较深的地方,人体一定是可以漂浮在表面的,可惜盐湖并未提供该项旅游体验服务,要不还真想一试。

茶卡盐湖的最佳拍照时机是清晨和傍晚,中午烈日当头的时候晒得人很不舒服,紫外线相当强烈,尤其是对于我这种没有墨镜装备的近视眼游客。在烈日下,去拍人像也很难拍的漂亮,眼睛是很难睁开的,手机在拍人像时,由于背景实在太亮,人像总是会偏黑一些。对于我而言,是很难做到早起的,也就赶不上清晨的茶卡盐湖,实际上到达盐湖景区的时候已经是中午了。

(盐湖的路都是用盐结晶铺成的,好不奢侈)

(在湖中的沉淀物捞出来随手一攥都能成为一个大疙瘩)

柴达木盆地

游览完茶卡盐湖已经是下午三点钟了,接下来还有400多公里的路程要走,好在高原的天黑时间要到晚上八点半。离开茶卡镇后,就进入了漫长的柴达木盆地。

http://p3.pstatp.com/large/pgc-image/1528418882025edccd91e79


我原以为柴达木盆地是属于新疆地区的,后来一看地图才发现彻头彻尾的属于青海省,夹在昆仑山、祁连山和阿尔金山三大山脉之间,而今天的行程几乎就是横穿整个柴达木盆地。

刚离开茶卡镇,还能看到一些牧民养的成群的牛羊,地上也长有一些枯草,但后来地上随着枯草的减少,牛羊也就看不到了。到后面几乎就是戈壁,看不到任何的动物,完全的荒原,让我想起了《北方的空地》中描述的羌塘无人区中的画面,我感觉无人区中也不过如此罢了。地理课本上说柴达木盆地是“聚宝盆”,矿产资源异常丰富,抛开这些埋藏在地底下的矿产不说,但就感官而言,是极其的荒凉。好在一路上,总有雪山陪伴,也不至于太孤单。一路上有很多动物出没的指示牌,好想看到一个活物,但都以失望告终。

由于地广人稀,这段高速公路的管理也相对简陋,曾看到了一辆当地的三轮车在高速的快车道上逆行,吓得我一脸懵,估计是要按正常的路线行驶要多走出好多路。

可鲁克湖

可鲁克湖位于高速公路旁的大约两公里处,甚至都不用下高速就可以到达,属于3A级景区。到达可鲁克湖时已经是下午6点钟,对于进入景区本不报太大的希望,经询问景区居然营业到晚上八点钟,高原地区果然天黑的晚。景区主要是一个偌大的可鲁克湖,可供游玩的地方特别少,简单拍照走人。如果在这里拍张照,拿给别人看,说是青海湖拍的,没有人能够看得出破绽。如果时间紧迫,该景区完全可以不去参观。

可鲁克湖景区旁边还有个比其更大的托素湖,托素湖是可鲁克湖的三倍大小,可鲁克湖的湖水最终会流入托素湖中。比较有意思的是,可鲁克湖的湖水属于淡水湖,而托素湖却属于典型的内陆咸水湖。咸水湖的形成离不开封闭的环境,托素湖由于没有排水渠道,只能靠蒸发来消耗水分,水分被蒸发后,水分中的盐分却不会被蒸发,日积月累就会形成咸水湖。而可鲁克湖由于会流向托素湖,湖水是流动的,因此没有形成咸水湖。

托素湖离高速较远,要开车过去需要沿着可鲁克湖的湖边,来回至少还得要一个半小时的时间,时间不太允许,且可玩性较差,就直接忽略了该景区。

托素湖旁边还有个外星人遗址,单看景区的名字是我特别感兴趣的类型,网上一搜,景区居然暂未开放!

当天终点 - 大柴旦

又是在晚上的时候到达了当天的终点站大柴旦县。

1
2
3
4
5
6
7
8
天空之镜湖
白雪皑皑山
漫漫柴达木
渺渺无人烟
盆地全是宝
可惜只见草
戈壁连成片
夜宿大柴旦

未完待续…

第四天

行程:西宁市 -> 海南藏族自治州黑马河镇

路程:250公里

日期:2019.4.21

经过了前三天的长途跋涉,今天开始了正式的旅行。早上在西宁市的华润万家采购了一些食物,因为还不太清楚接下来的大环线旅行中沿途的住宿饮食条件是什么样子的,也不太清楚很多情况下是否有足够的时间来饱餐一顿,毕竟每天都要赶很多的路。

第一站 塔尔寺

塔尔寺通常都是大家青海旅游的第一站,因为距离西宁市区比较近,大概有几十公里的路程,开车一个小时即可到达。

塔尔寺属于典型的藏传佛教圣地,藏传佛教是受印度佛教、青藏高原本土的苯波教和汉传佛教影响的共同产物,因此有很多区别于其他佛教分支的特点。唐朝时期的文成公主是信奉佛教的,远嫁松赞干布后,为汉族的佛教向青藏地区的渗透起到了非常大的作用。

塔尔寺在藏传佛教中的地位应该跟五台山差不多,但同时也吸收了一些中原道教的文化,比如寺内能看到有西王母的塑像。寺内建筑宏伟,院落也较多,比起五台山而言,除了整体面积小点,其它方面并不逊色。

未能领悟佛教的我,草草的参观塔尔寺后,明显发现了藏传佛教的很多特色,也带给了我不少的震撼。

好几个大殿的院内或者周围都有虔诚的佛教徒在做周而复始的朝拜,口中还振振有词,木质结构的地板早已磨的光滑无比。那种虔诚、执着非常值得敬佩,虽然以我现在的阅历还很难理解这种宗教行为。

第二站 日月山

从塔尔寺出来后,已经是下午两点了,接下来就要往青海湖进发了。日月山是此行的必经之路,属于祁连山脉的支脉,也是此行中看到的第一个雪山,沿途海拔在3000米以上。

进入日月山之前,阳光普照,高原的阳光相当强烈,隔着车玻璃都能感觉到。一开进日月山,雪山清晰可见,突然下起了冰雹,紧接着又下起了雨夹雪,原本没有积雪覆盖的山上也披上了一层薄薄的银装。这一切仿佛在预示着什么,仿佛在迎接着远道而来的客人,又仿佛是在告诫我不要打扰到了雪山上的神灵。

因为日月山的风景太美,全然没有了赶路的想法,就想驻足体验一下日月山的神奇。主干道是京藏高速,有条公路可以通往日月山风景区,索性直接开往日月山风景区。到了景区门口,雨夹雪渐小,一下车发现空气凉了好多,原本仅穿了单件衣服,感觉立马要穿羽绒服的节奏,穿了件外套仍冻得直打哆嗦。高原的天气就是这般神奇,有阳光和没有阳光完全就是两个世界。

日月山风景区并没有必要进入参观,因为景观在景区外一览无余。

景区旁边能看到很多高原鼠兔,发出吱吱的响声,这玩意特别爱打洞,鼠兔的旁边可以看到几个洞。

之后的行程中在公路边上看到了一只死去的马匹,马的头颅已不知所踪。我想放到东部地区,这种现象是肯定不会出现的,死去的马儿肯定还有它的利用价值。

第三站 青海湖

从日月山出来沿着G109国道继续前行,中间经过了倒淌河风景区,倒淌河水源自日月山,自东向西注入青海湖,故名倒淌河。倒淌河的水量不大,加上本来也很难分清东南西北,就很难体会到倒淌河的精妙。看评价景点比较坑,并没有进入参观。

过不了多久,青海湖就会出现在了眼前,而且接下来的两个多小时会一直沿着青海湖南沿的公路前进,足足有一百公里的距离,不愧为中国最大的湖泊。青海湖有个游客去的较多的二郎剑景区,并没有进入,据说性价比依旧很低,无非就是看湖,青海湖的美从哪个角度都能感受到。

G109国道跟青海湖稍微有一段距离,最近处差不多有一里路的样子,中间都是草原,被牧民给围成了一块一块,山羊绵羊在悠闲的吃着草。找了一处牧民的区域,开车过去,一人10元的门票,即可到青海湖边拍照。

身为游客一定觉得湖边的牧民是真正的逍遥快活,每天在自己的牧场上骑马放牧,住在青海湖畔,远处有雪山作伴,呼吸着几乎没有污染的空气。可是一旦天天都是这样的生活,又有几个游客可以放下尘世间的所有诱惑来到这近乎纯净的地方呢?牧民们也有自己的烦恼,牧民的孩子上学怎么办,牧民们生病了怎么办,各种生活的不便利。虽呼吸着最新鲜的空气,却也承受着杀伤力很强的紫外线。冬天的高原夜晚想想都会瑟瑟发抖,但牧民们也要忍受。

我天真的以为,青海湖最初是真正的大海,后来由于地壳运动,地壳上升,而青海湖变成了一个内陆湖。哈哈,不过这个来源忽悠人还是蛮不错的。后来经查阅资料,青海湖的最初是一个内陆淡水湖,并且注入黄河。后来由于地壳运动,青海湖流入黄河的入口被切断,从而青海湖没有了任何出水口。再加上天气较为干旱,湖水的蒸发量大于湖水的注入量,导致湖水的盐分浓度逐渐增加,从而变成了一个咸水湖。青海境内的咸水湖成因都是因为蒸发量大于注入量,包括比较出名的茶卡盐湖。青海湖的湖水浓度并没有海水的浓度高,湖水帮大家尝过了,确实挺咸的。

青海湖的湖水非常清澈,虽湖水很宽,但空气透彻,一眼可以望到河对面远处的雪山。远处的云朵看起来非常低,跟湖水连在一起,微风吹过,湖水上一层层的波澜。

湖边的小水坑里已经有了青蛙,晚上的青海湖畔温度还是非常低的,不知道这些青蛙夜晚是怎么度过的。当然这些小水坑的水是纯淡水,亲尝无误。

终点站 黑马河镇

黑马河位于青海湖的西端,因为是去往茶卡盐湖的必经之地,且可以看到青海湖,因此顺其自然靠着旅游而生存。早晨起来看日出的是个不错的地方,可以想象一下朝霞倒映在湖水中的场景,很多游客都会选择在黑马河观日出。

到达黑马河已经是晚上八点多了,温度较低,索性换上羽绒服,一点也不觉得热。住宿条件比较一般,但价格却不便宜,是整个行程住的性价比最低的宾馆了。找了一家餐馆就餐,全都是组团的游客,非常不错的食材却做的难吃的不行,上等的牦牛肉放到餐桌上后却嚼也嚼不烂,看着放弃的牦牛肉甚是可惜。这大概就是国内很多因旅游而起的餐馆的真实写照,反正能坑一个是一个,不要回头客,回回都是新客人。做的烂对自己的生意影响不大,但游客在心中对于当地的评价确实难以磨灭的。

未完待续…

第三天

行程:宁夏回族自治区中卫市中宁县 -> 青海省西宁市

路程:500公里

日期:2019.4.20

昨晚到酒店比较晚,早上拉开窗帘,一所学校的操场映入眼帘,令我眼前一亮。离开校园已经太久了,看到同学们身穿运动装在操场上洋溢着青春即亲切又陌生。

第一站 黄河畔

从地图上看中宁县城离黄河比较近,自然要去黄河边上走一走。但与保德县和府谷县不同的是,中宁县并不傍黄河而建,主城区而是离黄河有一公里的距离。我猜测保德县和府谷县边的黄河在地势低洼处,即使河水泛滥也不会殃及县城。但中宁县地势却是非常平坦,不能依靠地势优势阻止泛滥的河水,因此县城的选址离黄河稍有距离。

远处的黄河边上有一个多层的塔,是中宁县枸杞博物馆,中宁县有“天下枸杞出宁夏,中宁枸杞甲天下”的说法,因此建个枸杞博物馆还是比较合适的。由于到枸杞博物馆的路并不好走,而且不能判断此刻博物馆是对外开放,并未前往参观。

黄河水比其下游的府谷县的黄河水要变黄了很多,这也是我一直比较奇怪的地方。河道非常宽,目测有至少1公里的样子,现在是枯水期,河水并不太多。河床上有各式各样的鹅卵石,不知经过了多少次的河水冲刷才形成了现在的形状,我们个体的生命相对鹅卵石的生命实在是太短,挑选了几个像模像样的留作纪念。

在路上

宁夏境内,一路上偶尔会看到种植了一些铺着地膜的农作物,我猜测是土豆的可能性比较大,原因是山东的种植土豆方法就是类似思路。但看下田地中的土质,大部分都是小石块,哪有多少土壤成分呀,倒是拿着石块压着地膜来防止大风非常的方便。

在甘肃和青海接壤地方,道路两侧多了很多树木,,山上的草有很多都绿了起来,生机勃勃的。两天之内满眼都是枯黄色的画面,突然出现鲜绿色还有点不适应。

查看青海省的地图会发现,有好几个地级市都是围绕着青海湖命名的,海东市、海南藏族自治州、海西蒙古族藏族自治州、海北藏族自治州。提到海南,不熟悉的人还以为是海南岛呢。

地图中还有一个比较有意思的地方,海西蒙古族藏族自治州居然有两块区域,中间被玉树给分割开了,完全不接壤。让我立马想到的是在中国版图中另外比较有意思的是河北省的三河市,三河市被北京市和天津市包围,像是河北省的一个孤岛。

青海省有藏族和回族比较容易理解,居然还有蒙古族?要不也就不会叫海西蒙古族藏族自治州了。实际上内蒙古的最西端跟青海省仅隔了甘肃省一段狭长的河西走廊,直线距离上还是特别近的。蒙古族迁徙到青海要从元朝说起,整个中国的版图都是元朝的天下,自然青海省有就了蒙古族,后续又陆陆续续的有蒙古族迁徙都青海省。但青海省的蒙古族还吸收了部分当地藏族的习俗,很难说是蒙古族入侵藏族,还是藏族同化了蒙古族。

青海境内的青藏高速施工,走了很长一段的非高速,跟青海的村庄和县城有了第一次近距离接触的机会。青海省的海东市相对于甘肃而言,植被还是比较茂盛的,树木挺多的,沿路很多梨树,开着洁白的梨花。经过村庄的房屋看起来也不差,很多都是二层的小楼,而且村庄还是比较密集的,大概青海人口都聚集在了东部地区。总体给我外观感受,海东地区要比甘肃东部地区富裕很多。

第二站 西宁市

西宁市属于中国的西部地区,如果放眼到中国的地图中,相同纬度上,西宁算是比较靠中间的城市了,如果不是西部人烟稀少,更像是个中部城市。

晚上八点钟到达西宁市,事先知道青海省的人口只有区区600万,省会应该不咋地,结果还挺另我刮目相看的,城区面积感官上还是挺大的,颇具省会城市的规模,各种品牌应有尽有,夜景也还不错。

晚上吃了一碗当地特色羊肠面,面相不咋地,吃起来味道还不错。

收个尾

1
2
3
4
5
6
人人都说宁夏美
砂石大漠看到尾
甘东地区很荒凉
似乎水源很紧张
夏都街头走一走
车水马龙大高楼

未完待续…

第二天

行程:山西省保德县 -> 陕西省榆林市府谷县 -> 宁夏回族自治区中宁县

路程:600公里

第一站 - 府谷县

第一天留宿的保德县临近黄河,早上醒来后本想去黄河边看下景色,其实最关心的是中段黄河的河水是什么颜色的,可惜黄河边是一条公路沿着黄河蜿蜒,而且局部在修,因为黄河的地势较低,视线受阻严重,连黄河都没看到。

索性直接导航到了黄河对岸的府谷县,山西省保德县跟陕西省榆林市府谷县隔黄河相望,仅一河之遥,可以在跨河大桥上非常方便的在两个省之间通行。跨河大桥并不是特别宽,两车会车是没任何问题的,桥上还有一些行人在通行。上图为连接两省的大桥,在府谷县可以看到“秦晋之好“几个大字。

保德县虽紧挨着黄河,却没有依靠上黄河的任何优势,反而黄河成为了一大劣势。相反,府谷县却非常明智的,楼房离着黄河稍有一段距离,选择在黄河边修建了滨河公园供休闲娱乐。

在府谷县的滨河公园可以清晰看到和对面的山西省保德县城,建筑物的朝向大多都是面向黄河,或者面向对面的府谷县城的。

由于现在是枯水期,人行道离河水还有一段距离,但仍可以看出黄河水还是特别清澈的,跟黄河的淤泥还是有比较鲜明的区别。跟更下游那夹杂的泥沙的黄河水一对比,让人完全联想不到是一条河。后面的行程中在更上游看到了几次黄河,但河水都要比此段的黄河水要黄,至少兰州的黄河水夹杂的泥沙量已经非常大,也就是说此段黄河水是我见到最清澈的。不知道是否我的判断有误,从理论上讲应该越下游黄河水越黄才对的。

在路上

下午一点从府谷县出发,直奔西宁方向而去,最终到达了宁夏回族自治州中卫市中宁县城,最初地貌还是黄土高坡,大概从榆林市之后逐渐沙漠化,地上草还没长出来,在高速上很难判断远处是草原,还是完全的荒漠。

上图为在高速上的简陋厕所,与其说是黄土高坡地貌,我感觉更像是荒漠。

本以为到达宁夏后,地貌会变成接近于草原,因为提到宁夏我最先想到的是黄河冲积而成的河套平原。一路上一直在期盼,但一直也没遇到梦中的草肥水美的河套平原。事后一查资料才发现,即使河套平原最南的“西套”地区也在银川市周边,而我走的高速在银川市的南边大约100多公里的距离,可以说是跟河套平原擦肩而过。从卫星地图上可以清晰的看出河套地区的植被还是比较茂密的。上图中绿色的线为我经过的路线。

终点站 - 中宁县

最终到达了宁夏回族自治州中卫市中宁县城,到达中宁县后,恰巧赶上了一次沙尘暴,停车吃饭的时候,车上已经积了厚厚的一层土。本以为沙尘暴在当地是家常便饭,问下了当地人,说是沙尘暴也不常有,沙尘是从北部的腾格里沙漠吹过来的。从上图中可以看出,中宁县离得腾格里沙漠还是特别近的,刮个北风,沙尘天气还是非常容易出现的。

比较巧合的是,中宁县也恰巧在黄河边上,一天之内,驱车600公里,从黄河“几”字形的右边到达了左边位置。

却道是

1
2
3
4
5
6
7
8
9
10
11
12
日行千余里
胜似的卢骋
朝食保德县
夕塌宁安镇
黄土变沙漠
高山转草原
早在黄河畔
夕亦黄河边
自东向西行
作何现此况
若君心有疑
一览地图觅

未完待续…

近期有一段较为充裕的假期,出去旅游对我而言是再合适不过的选择,带着对远方田野的那份向往。特别有意向的方向有两个:南下和西进。

南方的很多地方都未曾去过, 比如张家界、桂林等,可以从北京出发一路到海南三亚地区,而且当下正巧是烟花三月下江南的好季节。

对于西部地区向往已久,青海、西藏和新疆地区都未曾去过,当下四月份不是西部地区的最佳旅游季节,山基本上还都是枯黄色,草儿还在沉睡中。

但心想南方交通较为便利,后续会去的可能性比较大,而且景区之间跨度较大,可以分开旅游。而西部往往需要拿出一大把的时间来玩耍才能尽兴,很多地方的交通不是特别方便,往往都是需要多个景点一起才最划算。

因此,最终的决定是北京自驾甘肃-青海大环线

此刻的我已经结束了整个行程,行程的时间段为2019.4.18 - 2019.4.30,共计13天时间。起点为北京市,终点为山东省,整个的行程达6000公里。

事先计划的时间为12天,留了一天的buffer,最终用时为13天,没有太大的偏差。由于一直是我一人在开车,相对而言还是比较累的,基本上一天得开500公里,尤其是前面和后面赶路的时间,基本上一天在600公里以上。大部分时间是上午在10点之后出发,下午开车的时间比较多,很多时候需要晚上开一段时间才能满足一天的行程目标。如果时间比较充裕,把整个行程放慢为20天,感觉会比较轻松,会对旅行有更深的体会,只是我时间不太允许。

一路上经过北京、河北、山西、陕西、宁夏、甘肃、青海、河南、山东这些省,地貌包括平原、山脉、黄土高原、戈壁、沙漠、黄河、湖泊、雪山、盆地等地貌。

这一路上遇到的很多地方,之前在历史和地理课本上曾经了解过,很多地方有比较深厚的历史和文化底蕴,自己的认识却是很浅薄,而旅行可以加深对一个地方的地理和历史的认识,正所谓:“纸上得来终觉浅,绝知此事要躬行”。

本系列文章并不打算写成单纯游记的形式,而是更多会将我个人的体会和相关的历史地理知识融合在其中,在这其中查阅相关的资料对我而言也是一种成长。由于才疏学浅,所查资料有限,很多观点不见得是正确的,欢迎指正。

第一天

行程:北京市 -> 山西省忻州市保德县

上午11点从北京出发,驱车700公里,晚上八点半到达山西省忻州市保德县城。

先后经过了华北平原、太行山脉、黄土高原,北京和河北境内大部分属于华北平原,河北和山西省的交界处为太行山脉,期间穿过了多个隧道,基本上是整个的行程中走过的最长的一段隧道了,后续除了祁连山脉、六盘山外隧道就比较少了。

到了山西,以黄土高坡地貌为主,黄土高原也是整个行程中看到最多的地貌了。黄土高原东起太行山脉,西至青海省日月山,南抵秦岭,北达长城,包括了山西、陕西、宁夏、甘肃、青海、河南、内蒙古7个省。比我印象中的要大得多,我印象中主要是在山西境内和陕西的北部地区,一下子刷新了我的认知。

大家都知道,黄土高原是水土流失导致现在的千沟万壑的地貌,也是因此而导致黄河水特别的黄,那么是什么时间段发生的水土流失呢?历史上的黄土高原又是什么样的呢?

黄土高原最初是被植被茂密的森林所覆盖,而且土壤在有充足水源的情况下还是特别肥沃的,并非大家印象中的植被少的可怜,要不然也不可能孕育出华夏文明。相反,正是由于黄土土质疏松易开垦的特点,才容易诞生人类文明,因为最早的人类文明生产工具是特别简陋的,对大自然的改造能力特别有限。黄土高原被破坏的历史时期基本上伴随着华夏文明的发展,到明清时期达到顶峰,建国之后开始逐渐恢复。

很难想象,偌大的黄土高原,竟然因为农耕时代的人类影响变成了如今这幅模样。假如再给现代的人类一次利用自然的机会,我想当下的人类一定会善加利用这片广袤的土地,黄河也许会改成另外一个名字。也就是说,人类不同的文明程度决定了对自然的破坏程度。

但今天的我们殊不知也在对自然进行着更深层次的破坏,如果放到未来来审视我们人类今天的作为,我们今天对这个星球的破坏要大大高于农耕时代对地球的破坏。比如,人类在贪婪的消耗着地球上的石油、煤炭资源,也许在不久的未来,人类会发现在这些燃料资源中还蕴藏着无穷尽的价值,但是这些资源已经被当下愚蠢的人类当做燃料给烧掉了。

一路上雾霾还是比较严重的,尤其是河北保定境内。保定的空气质量是早有心理预期,印象中山西的整体空气还是非常不错的。去年去过一次五台山,穿过几个长长的隧道进入山西境内后,空气会一下子好转,山西省忻州市的空气跟河北省保定的空气质量有着强烈的反差。但这次结果却比较失望,在整个山西境内并未出现晴空万里,而且看上去更像是雾霾,而不是阴天。

山西省境内有那么几段高速,连一辆车都看不到,让我一度怀疑已经抵达大西北的感觉。

服务区已成为乌鸦的聚集地

从服务区鸟瞰村庄

晚上到达保德县,县城傍黄河而建,位于河道东侧。离黄河稍远的地方是黄土高坡,大部分楼房都离建在离黄河较劲的地势平坦处,而黄土高坡上由于地势的原因房屋逐渐变少。一进入县城,道路上到处都是开着远光灯的,仿佛对面一颗颗行进中的太阳,闪的我实在有些难受。有点匪夷所思,明明有路灯,实在想不明白远光灯的用途。真期望每个人作为社会中的一份子,在做事情的时候要多为他人考虑一分,而不是仅顾及自己的感受。

保德县城到处打着扫黑除恶的标语,吓得我开车都格外小心。甚至连小区门口都打着口号,让市民天天回家都可以看到。我深知越是提倡什么,说明越是缺少什么。后来走过好多地方后,发现到处都打着该口号,并不是当地特色。

却道是:

1
2
3
4
5
6
驱车青甘大环线
夜晚留宿保德县
县城建在黄河畔
黄土高坡把城建
黄河对岸府谷县
一桥之隔秦晋缘

未完待续…

最长公共子串及公共子序列问题属于一类问题,都可以使用动态规划的算法来解析,且动态规划方式比较类似,比较容易混淆。

定义

最长公共子串:两个字符串中,相同的最长子串,字符必须是连续的

最长公共子序列:两个字符串中,相同的最长序列,字符不一定是连续的

比如:a[] = “abcde” b[] = “bce”

那么:
最长子串:”bc”
最长子序列:”bce”

解法

假设A、B分别表示两个字符串

最长公共子串

dp[i][j]表示子串A[:i]、B[:j]必须以A[i]、B[j]为结尾的两个字符串的最大子串长度

1
2
3
4
5
if A[i] == B[j] {
dp[i][j] = dp[i-1][j-1]
} else {
dp[i][j] = 0
}

最终dp二维数组中的最大值即为结果

最长公共子序列

dp[i][j]表示子串A[:i]、B[:j]的两个字符串的最大子序列

1
2
3
4
5
if A[i] == B[j] {
dp[i][j] = dp[i-1][j-1]
} else {
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
}

最终dp[i-1][j-1]为结果

leetcode

今天发现vagrant的其中一个虚拟机磁盘空间不够了,需要对其进行磁盘扩容,但不期望是通过增加新硬盘的方式,而是直接增加原磁盘容量的方式来无缝扩容。

修改磁盘文件

进入到vm磁盘文件所在的目录~/VirtualBox VMs/dev_default_1531796361866_92956

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
% vboxmanage showhdinfo  centos-vm-disk1.vmdk
UUID: acbb4ffc-0580-40d6-8627-3ed24cd0beff
Parent UUID: base
State: created
Type: normal (base)
Location: /Users/lvkai/VirtualBox VMs/dev_default_1531796361866_92956/centos-vm-disk1.vmdk
Storage format: VMDK
Format variant: dynamic default
Capacity: 10000 MBytes
Size on disk: 9634 MBytes
Encryption: disabled
In use by VMs: dev_default_1531796361866_92956 (UUID: a153957c-e43f-4dd2-8512-f51d42dee3d3)

# 将之前存储的vmdk格式的文件复制一份vdi格式的文件,由于需要复制文件,该命令需要执行一段时间
% vboxmanage clonehd centos-vm-disk1.vmdk new-centos-vm-disk1.vdi --format vdi

# 将vdi格式的文件修改磁盘空间上限大小为80g,但实际占用磁盘空间仍然为之前的大小
% vboxmanage modifyhd new-centos-vm-disk1.vdi --resize 81920

# 将vdi格式的文件重新转换为vmdk格式,会产生一个新的uuid
% vboxmanage clonehd new-centos-vm-disk1.vdi resized.vmdk --format vmdk
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
Clone medium created in format 'vmdk'. UUID: 7e454b50-0681-494b-b9ca-81700d217c0a

新的硬盘创建完成后,在virtualbox的界面上将对应虚拟机的硬盘更换为resized.vmdk,并将之前旧的centos-vm-disk1.vmdk给删除掉。

使用fdisk创建新的磁盘分区

以上命令执行完成后,开启虚拟机,进入系统,可以看到磁盘空间大小变更为85.9GB,但挂载的磁盘空间大小仍然为8.3G,新增加的磁盘空间仍然处于未分配状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# fdisk -l

Disk /dev/sda: 85.9 GB, 85899345920 bytes, 167772160 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x0000ca5e

Device Boot Start End Blocks Id System
/dev/sda1 * 2048 1026047 512000 83 Linux
/dev/sda2 1026048 20479999 9726976 8e Linux LVM

Disk /dev/mapper/centos-root: 8866 MB, 8866758656 bytes, 17317888 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes


Disk /dev/mapper/centos-swap: 1048 MB, 1048576000 bytes, 2048000 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/centos-root 8.3G 7.9G 386M 96% /
devtmpfs 296M 0 296M 0% /dev
tmpfs 307M 0 307M 0% /dev/shm
tmpfs 307M 4.5M 303M 2% /run
tmpfs 307M 0 307M 0% /sys/fs/cgroup
/dev/sda1 497M 195M 303M 40% /boot
vagrant 466G 390G 77G 84% /vagrant
vagrant_data 466G 390G 77G 84% /vagrant_data
tmpfs 62M 0 62M 0% /run/user/1000

接下来需要将未分配的磁盘空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# fdisk /dev/sda
# 依次输入可创建新的分区
n
p
回车
回车

# 继续输入p,可以看到磁盘的情况,多出了/dev/sda3
# /dev/sda3的System为Linux,而/dev/sda2的System为Linux LVM
Command (m for help): p

Disk /dev/sda: 85.9 GB, 85899345920 bytes, 167772160 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x0000ca5e

Device Boot Start End Blocks Id System
/dev/sda1 * 2048 1026047 512000 83 Linux
/dev/sda2 1026048 20479999 9726976 8e Linux LVM
/dev/sda3 20480000 167772159 73646080 83 Linux

# 依次输入将/dev/sda3更改为LVM格式
t
3
8e
p

Disk /dev/sda: 85.9 GB, 85899345920 bytes, 167772160 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x0000ca5e

Device Boot Start End Blocks Id System
/dev/sda1 * 2048 1026047 512000 83 Linux
/dev/sda2 1026048 20479999 9726976 8e Linux LVM
/dev/sda3 20480000 167772159 73646080 8e Linux LVM

# 输入w后进行保存操作
Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.

WARNING: Re-reading the partition table failed with error 16: Device or resource busy.
The kernel still uses the old table. The new table will be used at
the next reboot or after you run partprobe(8) or kpartx(8)
Syncing disks.

将新创建的磁盘分区添加到LVM分区中

将机器重启后,继续执行如下命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# vgdisplay
--- Volume group ---
VG Name centos
System ID
Format lvm2
Metadata Areas 1
Metadata Sequence No 3
VG Access read/write
VG Status resizable
MAX LV 0
Cur LV 2
Open LV 2
Max PV 0
Cur PV 1
Act PV 1
VG Size 9.27 GiB
PE Size 4.00 MiB
Total PE 2374
Alloc PE / Size 2364 / 9.23 GiB
Free PE / Size 10 / 40.00 MiB
VG UUID cpEmYK-XFew-6ZWT-GEeY-yEou-0vLq-OJiD08

# lvscan
ACTIVE '/dev/centos/swap' [1000.00 MiB] inherit
ACTIVE '/dev/centos/root' [<8.26 GiB] inherit

# pvcreate /dev/sda3
Physical volume "/dev/sda3" successfully created.

# vgextend centos /dev/sda3
Volume group "centos" successfully extended

# lvextend /dev/centos/root /dev/sda3
Size of logical volume centos/root changed from <8.26 GiB (2114 extents) to <78.49 GiB (20093 extents).
Logical volume centos/root successfully resized.

# xfs_growfs /dev/centos/root
meta-data=/dev/mapper/centos-root isize=256 agcount=4, agsize=541184 blks
= sectsz=512 attr=2, projid32bit=1
= crc=0 finobt=0 spinodes=0
data = bsize=4096 blocks=2164736, imaxpct=25
= sunit=0 swidth=0 blks
naming =version 2 bsize=4096 ascii-ci=0 ftype=0
log =internal bsize=4096 blocks=2560, version=2
= sectsz=512 sunit=0 blks, lazy-count=1
realtime =none extsz=4096 blocks=0, rtextents=0
data blocks changed from 2164736 to 20575232

# 最后执行命令可以看到磁盘空间已经增加
# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/centos-root 79G 7.9G 71G 11% /
devtmpfs 296M 0 296M 0% /dev
tmpfs 307M 0 307M 0% /dev/shm
tmpfs 307M 4.5M 303M 2% /run
tmpfs 307M 0 307M 0% /sys/fs/cgroup
/dev/sda1 497M 195M 303M 40% /boot
vagrant 466G 390G 77G 84% /vagrant
vagrant_data 466G 390G 77G 84% /vagrant_data
tmpfs 62M 0 62M 0% /run/user/1000

常用垃圾回收方法

1.引用计数

类似于C++中的智能指针,每个对象维护一个引用计数,当对象被销毁时引用计数减一,当引用计数为0时立即回收对象。

在PHP、Python中使用,适用于内存比较紧张和实时性比较高的系统。

缺点:

  1. 由于频繁更新引用计数,降低了性能。
  2. 循环引用问题。解决办法为避免循环引用。

2.标记(mark)-清除(sweep)

分为标记和清除两个阶段,算法在70年代就提出了,非常古老的算法。

该算法有一个标记初始的root区域,和一个受控堆区。root区域主要是程序当前的栈和全局数据区域。

从root区域开始遍历所有被引用的对象,所有被访问到的对象被标记为“被引用”,标记完成后对未被引用的对象进行内存回收。

缺点:

标记阶段,都会Stop The World,会大大降低性能。

3.分代回收

将堆划分为两个或者多个代空间,新创建的对象存放于新生代,随着垃圾回收的重复执行,生命周期较长的对象会被放到老年代中。新生代和老年代采用不同的垃圾回收策略。

Go垃圾回收机制

Go中采用三色标记算法,本质上还是标记清除算法,但是对标记阶段有改进。

https://upload.wikimedia.org/wikipedia/commons/1/1d/Animation_of_tri-color_garbage_collection.gif

步骤如下:

  1. 开始时,所有的对象均为白色
  2. 从root开始扫描所有可达对象,标记为灰色,放入待处理队列。root包括了全局指针和goroutine栈上的指针。
  3. 从队列取出所有灰色对象,将其引用对象标记为灰色放入队列,自身标记为黑色。
  4. 重复3,直到灰色队列为空。
  5. 将白色对象进行回收

优点:用户程序和标记操作可以并行执行。

详细过程如下图:

通过上图可以看出,STW有两个过程:

  1. gc开始的时候,需要一些准备工作,如开启write barrier
  2. re-scan的过程

每个对象需要一个标记位,go中并未将标记位存放于对象的内存区域中,而是采用非侵入式的标记位。go单独使用了标记位图区域来对应内存中的堆区域。

write barrier: 在运行垃圾回收算法的同时,应用程序在一直执行,上文中提到了写屏障write barrier用于将这些内存的操作记录下来。

GC日志

GC日志对于定位问题还是比较方便的

1.开启GC日志

可以增加环境变量GODEBUG=gctrace=1来开启gc日志,gc日志会打印到标准错误中。例如有如下的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "time"

func main() {
for {
s := make([]int, 10)
for i := 0; i < 10000; i++ {
s = append(s, i)
}
time.Sleep(time.Nanosecond)
}
}

执行GODEBUG=gctrace=1 go run gc.go即可打印gc日志到标准错误中。

2.GC日志的含义

可以参照runtime

1
2
3
4
5
6
7
8
9
gc # @#s #%: #+#+# ms clock, #+#/#/#+# ms cpu, #->#-># MB, # MB goal, # P
where the fields are as follows:
gc # the GC number, incremented at each GC
@#s time in seconds since program start
#% percentage of time spent in GC since program start
#+...+# wall-clock/CPU times for the phases of the GC
#->#-># MB heap size at GC start, at GC end, and live heap
# MB goal goal heap size
# P number of processors used

例子:

gc 11557 @179.565s 2%: 0.018+1.2+0.049 ms clock, 0.14+1.1/2.3/0.90+0.39 ms cpu, 13->14->7 MB, 14 MB goal, 8 P

3.GC的监控

对于线上GC的监控,基本上读取runtime.MemStats结构中的内容,然后存储到时序数据库中。具体有如下两种获取方式:

1
2
3
4
5
6
// 方式1
memStats := &runtime.MemStats{}
runtime.ReadMemStats(memStats)

// 方式2 json格式
expvar.Get("memstats").String()

references

古语有云,一年之计在于春,这句话对于很多植物而言再合适不过。在经历了寒冬之后,很多植物选择将最美好的一面在春天里绽放。

资源

1.PagerDuty

PagerDuty是一家Sass平台厂商,其产品为一款告警处理平台,提供了On-Call管理、告警收敛分组、告警时间报表,并集成了多种告警方式。

2.Fathom

https://github.com/usefathom/fathom/raw/master/assets/src/img/fathom.jpg?v=7

一款开源的简易网站数据分析工具,类似于Google Analytics或者百度分析。

3.Data Structure Visualizations

该网站将常见的计算机数据结构以可视化的形式展示在了界面上,可以在界面上点击按钮完成插入元素、删除元素等操作,对应的数据结构展现会实时发生变化,非常直观。

4.snowflake

https://user-gold-cdn.xitu.io/2018/2/11/16182507bcefae54?imageView2/0/w/1280/h/960/format/webp/ignore-error/1

Twitter开源的分布式算法,常用于分布式id的生成,使用毫秒时间戳、机器id、毫秒内的流水号来生成随机id。采用此种方法生成的id可以保证单机递增,但不能保证是全局递增的。

5.Netlify

很多人喜欢将自己的博客系统利用Github的Pages功能托管到Github上,但Github Pages并不支持对静态页面的构建,只能将构建完成后的页面推动到Github上。Netlify支持对静态网站的持续集成和持续部署,代码可以托管于Github上,Netlify会自动构建和发布,支持免费的https协议和CDN。

但可惜的是,实际测试下来,网站的访问速度在国内不是很理想。

6.996.ICU

全世界最大的同性交友社区Github上异常火爆的声讨996工作制度的项目,两天的时间内已经突破6万多Star,issue的数量也已经破万(截止到2019-03-28 19:55),要知道Github上Star数最多的项目freeCodeCamp也才接近30万Star,全世界运行设备最多的操作系统linux也才7万多Star,这简直创造了Github上Star数增长的奇迹。

很多人在issue中提到了加群交友吐槽、倾诉加班不满,甚至还有过来找男盆友的,活脱脱把issue玩成了贴吧,留言中清一色的汉字,说明基本是中国人在玩。

我个人对于强制996加班的事情不是很赞成,虽然过去三年中我的工作强度应该大于996,但更多的是出于个人自愿和对健康的无视,公司层面并没有强制要求。人生确实有非常多美好的事情可以参与和享受,对于程序员这个群体而言,电脑屏幕之外的世界还很大,还有太多的领域值得去探索和挖掘。但如果确实是因为个人的爱好,在工作中能够获得很好的成就感和满足感,996或者更高强度的加班,我个人觉得都是值得的。

说起ICU,程序员这个群的职业病是颈椎、腰椎、视力等,失眠多梦也是大有人在。我个人也确实身体出过一些问题而住过院,人往往都是在身体好的时候不懂得去重视自己的身体,当身体一旦出毛病的时候才懂得去珍惜。我曾经生病的时候也是鼓励自己要多锻炼和注意身体,但当身体好了之后,当时的愿望又抛到了脑后。

身体出现问题往往不是一朝一夕造成的,而是长期积累的结果,尤其是刚工作的前几年,趁着年轻确实能多加班熬夜,但30岁之后往往体力就跟不上了。还是奉劝各位,在工作的时候多注意休息和加强锻炼,无论是996,还是朝九晚五,都要多注意。

精彩文章

1.互联网运维工作

滴滴运维总监来伟在2017年对运维工作范围的思考,公司处在不同的阶段,运维所能干的事情也有所不同。

2.早点懂这几个道理,就不害怕被裁员了

IT行业中的一些职场规则,程序员在中年时期如果不做好职场转型,会逐步被更有活力更有体力的年轻人给取代。

0%