用AI给女儿画了一本绘本,故事的主角就是她自己--完整教程--附完整prompt

给AI女儿画一本绘本的想法产生很久了,之前也做过一些尝试。但是在Midjourney v6之前,画出的绘本效果一直不能让我满意。主要是角色一致性的调校很困难,比如第一幅图中的女孩和第二幅图中的女孩长得不一样。

在Midjourney v6版本引入–cref和–sref参数后,保持画面一致性便利了许多。

这个小假期终于把它付诸实践。以下记录生成这个绘本的完整过程。

Midjourney生成主角参考图

基于某张照片生成主角参考图,这张图片,会在后续的绘图中不断引用,用于保持不同画面间主角的一致性。生成的图片要求:背景干净,最好是纯色(提示词中,加入”pure background”),普通站姿,不要有过份夸张的动作。

Midjourney的prompt:

1
[原始照片url] a 3-year-old cute Chinese girl in a red one-piece dress, full length portrait, pure background, children’s story book illustration, picture book style, --chaos 20

Chatgpt生成绘本故事

这步简单,让chatgpt帮你生成故事就好,如果不满意,可以让它修改指定的部分。

chatgpt参考提示词:

1
2
请你扮演一个儿童绘本作家,并且精通用midjourney生成图画的方法。请你写一个适合于3岁女孩的儿童绘本故事。请分画面撰写,每幅画面中,都包含“插画描述”和“旁白文本”两部分。
请你改写第6页之后的部分,让故事更加曲折一些。

请Chatgpt将绘本故事转为midjourney画图提示词

我用的ChatGPT提示词:

1
请把每一页的插画描述,转译为midjourney prompt,以英文撰写。

这步实测,chatgpt还没有想象中的聪明,生成的提示不能直接满足midjourney的绘图要求。需要我大量的修正。(可能是我使用方式不对,大神们可以指导一下)
这步由chatgpt生成的midjourney prompt的几个问题是:
chatgpt把很多对绘图无关的信息纳入到提示词中。例如:Chatgpt给出的midjourney提示词之一是:“A girl creatively arranging colorful flowers on the ground to form a large rainbow in celebration of the moment.”。这句中,”creatively”和”in celebration of the moment”对Midjourney而言都是无效信息。对Midjourney而言,如果想要精确地控制图片的输出,只需要直接告诉它“要画什么”,而不必告诉它“画这个表达了什么”
没有带上画面风格相关的提示词和参数。这对Midjourney绘图很关键。

使用绘本提示词绘图

根据第3步给出的参考,修订,准备好midjourney提示词喂给它。
midjourney在当前版本之前,最被人诟病的就是不易保持画面的一致性(比如,第一幅图的女孩和第二幅画的女孩长得不一样)。在v6版本引入–cref和–sref参数后,保持画面一致性便利了许多。
一些保持风格一致的小技巧:

  • 所有画面,使用统一的风格相关提示词,这是我用的风格提示词:“colored ink drawing, giving the scene a magical, dreamlike, and engaging atmosphere, A heartwarming children’s book illustration.”。在每幅画面的提示词中,都加入这段。
  • 使用–cref参数来保持角色的一致性,所有画面的的–cref参数,都引用由第一步生成的那幅主角参考图。
  • 使用–sref参数来保持画面质感的连续性,可以将上一幅画面的输出,作为下一幅画面的–sref参数输入。
  • 多尝试。不要高估你的表达能力和MJ的理解能力。在我的这些绘本画中,画得最快的一幅,也经过了4次的提示词调整才画出满意的画面,最多的一幅,试了20组左右的提示词。

把旁白加入图画中

这步简单,随便用一个你熟悉的绘图工具(PS、美图秀秀)都可以。我用的是win10自带的“画图”程序。将旁白贴入图片中。

成果

来看看成果吧:

封面

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Midjourney Prompt

以下把我用到的完整prompt开放给大家参考:

主角参考图:

1
[主角照片url] a 3-year-old cute Chinese girl in a red one-piece dress, full length portrait, children’s story book illustration, picture book style, --chaos 20 

图1

1
A joyful three-year-old girl surrounded by colorful balloons and toys in her room, with sparkling eyes full of imagination. ink drawing, giving the scene a magical, dreamlike, and engaging atmosphere, A heartwarming children's book illustration. --cref [主角参考图url] --cw 50 --chaos 30

图2

1
[网络上找的一张喜欢的风格的照片的url] A little girl stepping out of her house to the yard with trees and flowers, The sun is shining brightly. colored ink drawing, giving the scene a magical, dreamlike, and engaging atmosphere, A heartwarming children's book illustration. --cref [主角参考图url] --cw 50 --chaos 50 

图3

1
[图2 url] a curious girl observing dew drops closely, the dew drops shining in the sun. colored ink drawing, giving the scene a magical, dreamlike, and engaging atmosphere, A heartwarming children's book illustration. --cref [主角参考图url] --cw 10 --chaos 20 --v 6.0

图4

1
[图3 url] A curious little girl touching a dewdrop on a leaf, imagining it turning into a miniature rainbow. Rainbow is clearly painted in the picture. colored ink drawing, giving the scene a magical, dreamlike, and engaging atmosphere, A heartwarming children's book illustration. --cref [主角参考图url] --cw 10 --chaos 40 --v 6.0

图5

1
[图4 url] A girl running following a butterfly through the garden as dark clouds gather and the wind starts to pick up, signaling an approaching storm. colored ink drawing, giving the scene a magical, dreamlike, and engaging atmosphere, A children's book illustration. --cref [主角参考图url] --cw 30 --chaos 40 --v 6.0

图6

1
In heavy rain, some kittens, puppies and a littyle girl, are running at full speed, looking for shelter in the garden, dark clouds gather and the wind starts to pick up. colored ink drawing, giving the scene a magical, dreamlike, and engaging atmosphere, A children's book illustration. --cref [主角参考图url] --cw 20 --sref [图5 url] --chaos 40 --v 6.0 --no house

图7

1
a little girl, some kittens and puppies curl up to get sheltered from a tree hole, they are sad. it is rainy outside, the view from the tree hole inside out. colored ink drawing, A children's book illustration. --chaos 40 --v 6.0 --cref [主角参考图url] --cw 10 --sref [图6 url] --sw 50

图8

1
a little girl and some kittens, puppies, are running out of a flooding tree hollow, determination on their faces, rainy and adventurous setting. colored ink drawing, giving the scene a magical, dreamlike, and engaging atmosphere, A children's book illustration. --chaos 40 --v 6.0 --cref [主角参考图url] --cw 30 --sref [图7url]

图9

1
a little girl and some kittens, puppies are rushing to the eaves of a cabin, in heavy rain, behind view, colored ink drawing, giving the scene a magical, dreamlike, and engaging atmosphere, A children's book illustration. --chaos 40 --v 6.0 --cref [主角参考图url] --cw 20 --sref [图8 url]

图10

1
a little girl, and some kittens and puppies are standing inside the window of a cottage, looking up at a cloudy sky, colored ink drawing, giving the scene a magical, dreamlike, and engaging atmosphere, A heartwarming children's book illustration. --chaos 40 --v 6.0 --cref [主角参考图url]  --cw 20 --sref [图5 url]

图11

1
Sun breaking through clouds, a magnificent rainbow appear in the sky after the rain. colored ink drawing, giving the scene a magical, dreamlike, and engaging atmosphere, A heartwarming children's book illustration. --chaos 20 --v 6.0 --sref [图2 url]

图12

1
a little girl, some kittens and puppies are standing at the doorway of a cottage, looking up at a rainbow with surprise and admiration, behind view. colored ink drawing, giving the scene a magical, dreamlike, and engaging atmosphere, A heartwarming children's book illustration. --chaos 40 --v 6.0 --cref [主角参考图url] --cw 20 --sref [图11 url]

图13

1
A little girl, some kittens and puppies are running on a rainbow, colored ink drawing, giving the scene a magical, dreamlike, and engaging atmosphere, A children's book illustration. --chaos 30 --v 6.0 --cref [主角参考图 url] --cw 50 --sref [图11 url]

图14

1
A little girl, some kittens and puppies are sitting on ground, they are a little sad,  colored ink drawing, giving the scene a magical, dreamlike, and engaging atmosphere, A children's book illustration. --chaos 30 --v 6.0 --cref [主角参考图url] --cw 50 --sref [图16 url (是的,我先画的图16,后画的图14)]

图15

1
One picture shows a little girl with a dialogue box painted on her head to express her thoughts, and inside the dialogue box is a small rainbow. colored ink drawing, giving the scene a magical, dreamlike, and engaging atmosphere, A children's book illustration. --chaos 30 --v 6.0 --cref [主角参考图 url] --cw 50 --sref [图1 url]

图16

1
A girl is arranging colorful stones in the shape of a rainbow on the ground. some kittens and puppies are sitting beside her. the view from above. the girl is quite small compared to the on-ground rainbow. colored ink drawing, giving the scene a magical, dreamlike, and engaging atmosphere, A children's book illustration. --chaos 30 --v 6.0 --cref [主角参考图 url] --cw 20 --sref [图11 url] --sw 50

最后以一张Midjourney犯傻的图结尾:

Midjourney犯傻图

STUN原理

在点对点(P2P)网络环境中,参与者需能够互相直接连接,但网络地址转换(NAT)的存在常常阻碍了这种直接连接的建立,这对P2P应用的正常运作构成了挑战。会话穿越工具协议(STUN)是一种广泛采用的技术,旨在解决NAT对P2P应用的影响。STUN协议使得网络设备能够识别它们经由NAT设备映射后的公网IP地址和端口号,并利用这些信息在两个P2P节点间建立一个数据通道,使它们能够跨越NAT设备进行通信。

为何STUN至关重要?

随着IPv4地址资源的日益紧张,NAT的部署变得更加普遍。NAT在解决IP地址不足的同时,也增强了网络安全,通过阻止外部网络的未经请求的流量进入内部网络。然而,NAT同时也打破了P2P网络的点对点通信模型,因为P2P通信要求每个节点都能够主动建立连接。

为了克服NAT对P2P网络的限制,开发了多种NAT穿越技术,包括反向链接、应用层网关(ALG)、打洞技术以及中间件方法。

STUN(Session Traversal Utilities for NAT)是一种在RFC标准中定义的协议,它能够探测NAT的存在,并帮助获取经NAT映射的IP地址和端口号。通过STUN,可以在两个P2P节点间建立一条穿越NAT的连接,这个过程也常被称作“打洞”。STUN的优势在于它不要求对现有的NAT设备进行任何修改,仅需要在网络中部署一个STUN服务器即可轻松实现。

STUN服务器/客户端

STUN遵循客户端/服务器模型,包括STUN服务器和STUN客户端两部分:

STUN服务器:这是一个在公网上运行的实体,例如路由器或者专用服务器,负责接收STUN绑定请求并发送STUN绑定响应。
STUN客户端:这是一个在私网内运行的实体,负责发送STUN绑定请求并接收STUN绑定响应。

STUN典型组网

STUN的工作机制

STUN协议通过在STUN客户端和STUN服务器之间交换消息来探测NAT,并建立数据通道。STUN的消息交换过程包括NAT探测和打洞两个阶段,具体如下:

NAT探测阶段:

  • STUN客户端向STUN服务器发送绑定请求(Binding Request)。
  • STUN服务器接收请求并发送绑定响应(Binding Response),携带MAPPED-ADDRESS、XOR-MAPPED-ADDRESS和RESPONSE-ORIGIN等属性。
  • STUN客户端通过比较绑定响应中的地址属性和原始请求的源地址,确定是否存在NAT,并将结果反馈给应用。

打洞阶段:

  • STUN客户端向服务器请求其它STUN客户端的接口信息。通过交换NAT映射的地址信息来尝试建立直接的P2P连接。
  • 客户端交替发送绑定请求,以在各自的NAT设备上创建会话表项。
  • 当两端的NAT设备都建立了会话表项后,P2P通信可以直接进行,无需通过STUN服务器。
  • 这种方法允许P2P节点之间在NAT的干扰下也能够建立稳定的通信链路,从而使P2P应用能够在各种网络环境下正常运作。

爸爸的抗癌日记

在生命的旅程中,我们常常会遭遇无法预料的挑战,这些挑战考验着我们的意志和勇气。对许多人来说,陪伴至亲在抗癌的征途上是一段艰难而又无比珍贵的经历。我想通过这些日记,记录下我与父亲共度的两年半,那段艰辛而又充满爱的时光。这是一段关于坚韧、爱与失落的故事,也是我对父亲深深的怀念和珍视的见证。

2021/7/9

浑浑噩噩的一周过去。从第一次得知爸爸可能得肝癌,到今天,一周过去。颓废了一周,眼泪不知流了多少回。以此记为证,我要振作。

以此文记录我和爸爸一起抗癌的日子。

上周六,爸爸腰疼难忍。到人民医院想看看医生,无奈没有提前挂号,无医可看。到周日时终于看上医生,开单做了检查。肾脏和脊椎都没有问题,还以为只是小病。休息一下就好。

周一时还有一项检查,做了腹部彩超,发现了病灶。肝部有一块11cm*11cm的肿块。请省立医院的同学帮忙看了一下,她说大概率是癌。连忙带爸爸去省立做了核磁共振等一系列检查。在等待检查结果时,同学已提醒我要有心理准备。

周二下午,结果出来,确诊了。我看着报告哭成泪人。爸爸还不知道,我在爸爸面前还要保持镇定,但回到房间躺下后,眼泪不能自已。彻夜未眠。

周三,到医院取了报告。爸爸执意要看报告,知道瞒不了。一边看着,一家人抱在一起哭成一团。

在我眼里爸爸的身体还算硬朗,在这次腰疼之前,也没有任何症状。这样的打击突如其来。陪爸爸说了很多话,无非是鼓励之类的。但爸爸是个明白人,他自己知道肝癌的凶险,不停地和我说只有几个月的生命了。

到傍晚时,爸爸把房产证和保险单都交到我手上,神态还算镇定。但越是这样,我越是心疼。

周四,把爸爸安顿住进了医院。我们着急,想请医生快点开始治疗。但医生说要先做检查,再请各科会诊治疗方案。我们还是得等。晚上带女儿和爸爸视频聊天,爸爸看到孙女很开心。但我知道他心里的苦涩。

本都规划好他的退休生活了,带孙女上幼儿园,教孙女识字画画。倒头来一场病把所有规划化为泡影。

周五,2021/7/9,也就是今天。本想去公司把耽搁的工作处理一下,到公司楼下,情绪又崩溃了。只能掉头回家。

现在是中午13:00,我开始写下这篇日记。我期待这个系列的日记能有个美好的结局。就算没有好的结局,也希望日记越长越好。

颓废一周时间了,我要振作。不光爸爸需要我,妻女也需要我。

后面还有很多挑战,要手术,要化疗,要陪护。这些都需要我,我不能倒下。

爸爸,加油!静待奇迹发生。

2021/7/17

本周各项检查的结果陆续出来。有一些不幸中的万幸:1)癌细胞没有转移和扩散。2)肿瘤包膜完整。方便手术。

严主任的看诊给了我们很大的信心。看诊完,爸爸给严主任敬了个礼。看得出,他找到了希望。

爸爸从消化内科转到的肝胆外科,准备手术。

在准备手术的几天里,好多亲友来看他,给了他鼓励。爸爸精神还不错。有些时候,感觉是他在给我信心。

2021/7/20

现在,爸爸正在手术室里,做右肝切除的手术。今天来了好多亲人。给了爸爸好多鼓励。

昨天医生给我们讲解手术方案,最好的和最坏的结果都讲了。最好的结果:就是切除右半肝后,没有任何癌细胞残留。我祈祷着好事能够在爸爸身上发生。

爸爸从小到大,吃的苦太多了。我希望幸运女神能够眷顾我们一次。

昨天爸爸写了一份遗嘱,爸爸说会好好治疗,写遗嘱是为了以防万一,毕竟手术都有风险。

在59岁的年纪,一个准备享受退休后天伦之乐的年纪,爸爸就写好了遗嘱。让我怎能不心疼。

2021/7/21

手术持续了6个小时,一做做到凌晨,从手术结果还说还算顺利。该切的都切了。

身上插满了管子回到病房。

亲戚们也一直在病房外坚守到了凌晨,甚至被护士长赶了好几次,都不愿意走。

2021/8/1

爸爸手术后恢复得挺快的。这周出院了。更确切地说,是被医生赶出医院的。医院床位很紧张,把床位资源留给最需要的病人。

出院后,对我们来说,最麻烦的是给爸爸的手术伤口换药。术后腹水多,出院的第一天,爸爸的腹水一次又一次地把伤口上的纱布浸湿。一天就换药了七八次。

在家附近,我们跑了4家社区卫生站。听到是大手术后的伤口,都不愿意上门服务,都怕承担风险。最后不得已,只能我们自己给爸爸换药。

今天出院第五天,腹水已经少多了,爸爸的精神不错。有空就看奥运会。

今天周末,把女儿抱给爸爸看。爸爸一个月没看到孙女了,开心得很。还是家里好!

医生说一个月后再进医院去,可能要做介入治疗。可能又要吃苦了,不管怎么样,先快乐一个月吧。

2021/8/21

爸爸连续低烧不退,我又开始担心了

2021/8/25

带老爸来复查,这次严主任说要做介入,看得出来,爸爸又沮丧了。

早该预想到,这病并不能这般顺利地治好。我已做好了打持久战的准备。

没有床位住院,等两天吧。

愿一切顺利。

2021/9/4

爸爸又住院了一个星期,今天出院了。这次住院主要是做了介入治疗。

做介入治疗前一天,隔壁床做相同手术的病友,疼得嗷嗷叫。把爸爸吓得够呛。

万幸的是,爸爸的治疗,不良反应并没有这么大。从手术记录来看,挺顺利的。

同病房的病友,基本上都是癌症,出院时相互问候,祝福活到百岁,很温暖。

最后马上离开医院时,在医院的走廊上,看到一位姐姐哭得撕心裂肺。能理解他,住到这个科室的人,或多或少都要考虑面对亲人的离开。人类在疾病面前,显得多么渺小。无论如何,希望她坚强。大家一起坚强,一起勇敢面对。

医生说两个月后复查。爸爸可以在家里好好休息一下、享受退休生活了。

因为肺部积液,出院时爸爸还插着个引流管,问题不大。两天后来拆管。

2021/11/6

这两个月爸爸过得挺好的,看起来与正常人也无异。每天散步15000步,比大多数年轻人的运动量都大了。真希望能一直这么好下去。

但是,最新的检查报告,发现了肺转移!

是我想得太简单了,癌症这个病,很难就这么顺利解决掉。

丢掉幻想,做好打持久战的准备。

2021/11/17

肺转移无疑了,接受现实。开始治疗。

医生给了靶向+PD1的治疗方案。了解到这是《治疗指南》推荐的一线方案,按此方案治疗。

爸爸看起来精神状态还不错,每天运动10000多步。不知道的人,完全看不出他是个病人。

不知他是不是只是在我面前装作坚强。至少,我在他面前,是装作坚强。

2022/01/06

今天复查,肺里的癌细胞还在继续进展。仑伐替尼+pd1的方案似乎没起到期望的作用。难过。预感今年的年会不好过了。

本来还期待靶向药能起作用,让肺里的瘤消下去。下周找严主任,可能得换个方案了。

2022/01/12

严主任给出了两种选择:1)激进一些,加一种靶向药吉非替尼,但是是没进入《指南》的,实验性质的,可能没效果,但也有可能有奇效。2)有一定概率癌细胞是假性进展,可以再观察一个月再看。春节临近,爸爸不想再折腾,也怕新靶向药的副作用。选择了继续当前的方案(仑伐替尼+PD1)一个月。

希望能过个好年。

2022/02/06

过年了!

这个年过得还不错,爸爸参加了很多聚会,除了有些忌口外,也没有什么不舒服的地方。

似乎在亲戚间达成了一种默契,不去触碰这个谁也不愿提及的话题。我也一样。就像没得病一样。

爸爸倒也开朗乐观,也几次听到他自己吹起了口哨。

今年我给家人们多包了红包,2021年,他们过得太辛苦了。

过年期间,我通过各种渠道收集了最新的癌症治疗进组实验的联系手段,也准备了几个可以远程问诊的专家的手段,以备将来不时之需。

2022/2/17

过完年后的第一次复查,肿瘤没有长大,保持原有的大小。算是有控制住。但异常凝血酶源还在翻倍地涨。

严主任说这个结果,还行,继续仑伐替尼+PD1治疗。

向爸爸提出到其它医院问问其它医生,爸爸不愿去。爸爸还是这么个怕麻烦的性格。

女儿长到1岁零4个月了,越发可爱。每周爸爸最开心的事就是和孙女玩。每次带女儿回家,爸爸都从家走到楼下来接,抱在怀里爱不释手。好温馨的场面。

2022/3/9

又到了复查的时候,祈求有个好结果。

2022/3/19

这次的复查,肺部的肿瘤有很小程度的长大,最大的有1.4cm,肝部没有复发。异常凝血酶源降了500。

当前的方案,算是有控制住肿瘤的进展。

希望耐药能来得晚一些,再晚一些。

2022/4/22

从查出肺转移至今,已经半年了。半年里,肺里最大的那个肿瘤,长大了2mm。

从患者的角度,当然是希望肿瘤能变小甚至消失。但是医生说,半年才长大2mm,算是控制得很好的了。

今天医生给爸爸换了个靶向药,换成多纳菲尼,也许是从防止耐药的角度来考虑吧。

加油啊,爸爸。保持乐观,过好当下。

2022/5/25

换药成多纳菲尼后,这一个月,副作用很大。爸爸消瘦了,头发也掉了很多,关节轮着疼。然而,甲胎蛋白和异常凝血酶原还是在噌噌往上涨。多纳菲尼对爸爸没什么效果。

今天医生改了方案:仑伐替尼+PD-L1,每个月2W+的药费。

希望PD-L1有效吧。如果钱能解决问题,卖房治病都值得。

2022/6/12

本周爸爸的身体差了很多,反复地发烧,身体也没什么力气。不知是不是PDL1的副作用。

但看的到孙女时候还是很高兴。

马上又要去检查了,希望PDL1能真的有效。

2022/7/3

出来石家出差了一个星期,这次检查的结果,并不太好。肺部的肿瘤有所控制,但肝部又有复发,肾部也有新的转移。

我是已经到石家庄了,爸爸的检查结果才出来的。爸爸在马上又要住院。我远在石家庄,有一种无力感,无能感。想做点什么,又不知道能做什么。

希望能早点回福州,陪一陪爸爸。

2022/7/10

我回福州了,爸爸这次住院时间不长,已经出院了。总共在医院的时间才不到48小时。

住院主要是做了介入手术,控制肝部和肾部的肿瘤。手术 还算顺利。出院后爸爸还是觉得手术部位能受,但又强撑着不吃止痛药。

今天带女儿回家了,爸爸难受都不能抱孙女。希望下周能好点。

医生推荐了新的方案,T+A,但是有点贵。爸爸有点舍不得。但我舍得,我还是出得起的。下次再去医院问问医生,这个方案的利弊。

2022/8/8

爸爸出院后的这一个月,恢复得还不错,精神状态好了很多。看不出像个病人。

医生认为PDL1虽然没有让肿瘤变小,但还算控制得住,暂时没有换药。

T+A的方案与当前的方案,医生也很难权衡出利弊。并不是越贵的药越好。

2022/9/5

这个月的的检查结果,有一些好消息。

肝部的新发肿瘤,比介入手术前小了,肾部的转移瘤,医生说,可能没有活性。

甲胎蛋白还在涨,但是异常凝血酶原有降低。

爸爸的精神状态还算好。

昨天回老家扫墓了,爸爸说明年也要给自己找基地了。好心酸,晚上睡不着觉,我真的无法想象没有爸爸的日子。

回老家和亲戚们聊天,爸爸说,如果他自己没生病,一家人的生活应该是很幸福富足的。

是的,确实是如此,我现在也多么希望爸爸没有生病。

不过,话说回来,在爸爸没生病之前,我们似乎也没感受到生活有多么的幸福。

身在福中之时,我们并不知福。幸福,还是要自己去寻找和体会。

2022/10/27

这次复查,异常凝血酶原降低了一半,肺部最大的的转移瘤缩小,肝部的复发瘤消失。

好久没有好消息了,总算给盼来了。开心。

但有不那么好的消息,医院里没有仑伐替尼了,得到外面的药店自费买。自费的话,进口药费用是无法承担的,换了国产的。国产药,别让我失望。

2023/1/10

爸爸刚刚经历了新冠阳康,一开始十分担心他有基础病会很难熬。结果万幸他的病程与大多人也都差不多,一个星期左右就康复了。

刚刚复查了一次。这次的结果太糟了。甲胎蛋白翻倍,异常凝血酶原翻3倍。但是从CT的MRI的结果看,肿瘤并没有太多的进展。爸爸沮丧了,我都不知道要怎么安慰他。

马上过年了,今年能过个好年吗?

2023/2/4

马上元宵节了,这个年,顺利地过了。大多数人从新冠中恢复过来,爸爸也一样,参加了几场聚会,精神状态还行。

最新的检查结果,甲胎蛋白和异常凝血酶原都升高了好多。我猜多半是耐药了,但又不敢和爸爸明说,怕他失去希望。

下周去找严主任,或许该换药了。

最近经常失眠,每每夜深人静时,我常常想起小时候与爸爸的点点滴滴,辗转反侧。可以预见,今年会挺难的。

加油啊,爸爸。

2023/3/12

是的,这次的检查又不理想,甲胎蛋白和异常凝血酶原还是长得很快。肺部和肾上的转移瘤,都有不同程度的增大。

针对肾上的转移瘤,明天开始做放疗。这是第一次做放疗,不知道情况会怎样。

加油吧。

2023/3/15

放疗了几次了,幸运的是没有出现原以为会有的副作用,爸爸的日常生活也没有受到影响。

陪父亲去了几次医院,在放疗区外等待的患者和家属们普遍低着头看着手机,就像来摔伤了来医院换药一样的平静。但我们互相都知道,走到个治疗室里的人,各有各的苦难。

最近公司里挺忙的,在公司里受的委屈也挺多的,每天急急忙忙的。医院的放疗室外的等待区,反而让我感到平静。癌症都面对了,公司里那点破事又算得了什么呢?

2023/5/1

最新的检查结果出来,肿瘤标志物都有一定的下降,放疗还是有效果。肾部的转移瘤缩小了,肺部的有一点增大。

爸爸的体力有点不行了,多走几步就累了,但生活还能自理。去药店领药,店员还说爸爸看起来不像患者,他还高兴了好一会儿。

刚刚过去的4月份,爸爸给自己挑好了墓地,我和他一起去买下来,算是了却爸爸一桩心事。他在为离开做准备了。

2023/5/21

刚刚过去了一周,爸爸肺里最大的转移瘤做了射频消融,医生说转移得太多了,做消融只是估息治疗,无法根治。

给自己降低一下思想负担吧。

2023/7/16

爸爸昨天在家里晕倒了。据爸爸说,当时早起没吃饭,走到客厅一阵眩晕就倒下了,砸坏了电风扇。

后来自己醒了。我估计是最近胃口不好导致的低血糖。

一个星期前,爸爸刚刚在协和医院做了粒子植入治疗,处理了肺部另一个较大的转移瘤。这次治疗是严主任的建议。

我现在也无法判断这是估息治疗还是真的有用。但处理掉大的转移瘤,也确实从心理层面降低了一些负担。

三月份的放疗后,爸的胃口都很差,饭量少了许多,这可能也是导致晕例的原因之一。

但我能做的真的很有限,每到夜深人静,这种无力感总会涌上心头。

2023/8/25

最新的检查结果,粒子植入治疗的效果还是很好,这是个好消息。

但另外一颗原来挺小的肿瘤迅速地长大了。还要再住院去做一次粒子植入。

爸爸现在很排斥住院,说了好几次今年不想再住院了。我心疼但也只能鼓励他不要灰心。

这颗转移瘤长得这么快,多半是PDL1和仑伐耐药了,协和医院的黄主任推荐了一种30万/年的换药方案,但不一定有效。

爸爸心疼钱,也怕那些严重的副作用,还在纠结要不要换。

2023/10/10

爸爸又住院了,这次不是因为肿瘤。是因为胃疼。

前两天做了胃镜,十二指肠有个很严重的溃疡。可能是仑伐替尼的副作用导致的。

困扰爸爸几个月的消化系统的问题总算找到病因了,做胃镜做得太晚了。

但也因为胃部的病,肺部的粒子植入治疗又要推迟了。

2023/11/5

今天我过农历生日了,家里给了我一个惊喜,刚到家,熊猫不走的熊猫就在给我跳舞。

对我这种社恐来说,有点尴尬,但还是很开心啊。

当然,最开心的是爸爸出院了。他10月份住院了两次。第一次因为胃病,第二次为了为肺部的转移瘤做了消融手术。一次消融了四颗,把爸爸折腾得够呛。

爸爸恢复得还行,虽然还没彻底恢复,但精神头还不错。

2023/12/13

上周开始,爸爸右下腹开始隐隐作痛。到上周末时甚至晚上都疼得晚不着。精神也差了好多。

我看着真的好揪心。

两个月前的腹部CT并没有什么异常,这两个月会有这么大的病情进展吗?

明天会去做腹部彩超和胃镜,希望能找出病因。

2023/12/15

检查结果出来,腹部并没有任何肿瘤复发的迹像。十二指肠的溃疡还没好,腹部的疼痛可能是十二指肠的溃疡引起的。

医生说了好多忌口,开了一些中药配合西花一起吃,爸爸要好好管住嘴了。

消化系统的毛病,多半是仑伐替尼的副作用。为了治肝癌,消化系统又遭罪了。

如果疼痛没好转,下周又要住院了。

2023/12/20

爸爸又住院了,被腹痛折磨了12天,每天精神很差,睡眠也很糟,希望在医院里能给他一些缓解。

但似乎未能如愿,消化科针对十二指肠的溃疡的治疗并没有缓解他的疼痛,同时做了好多检查,也又没有找出疼痛病因。

虽然在爸爸面前,我总是鼓励他保持信心,找到办法,但每每从医院出来,心中总会升起无力感。

我越发地担心失去爸爸了,这种让人恐慌的场景时不时冲进脑门,眼泪总在眼框里打转。

2024/1/5

12月份以来,爸爸的状态越来越差了,昨天已经走不稳路了,意识已不太清醒。也开始糊涂了。记不住做检查的时间,记不住有没有吃过饭

爸爸没生病前是多么聪明啊,现在怎么变成这样。

昨天晚上高烧41度,说了一些胡话。半夜挂急诊,也没查出原因,只开了一些退烧药。

2024/1/6

爸爸一早就双腿无力,在卫生间摔倒了,送到医院时休克了,送到抢救室急救。

现在生命体征暂时稳定。

医生诊断为,腹部的转移瘤侵犯十二指肠导致穿孔,消化道食物残渣流入腹部导致感染。原来所谓的十二指肠溃疡,实际上是转移瘤侵犯,之前所有的检查都没有提及这点。

医生让我做好最坏的准备了。因为感染严重,最好的药物可能也无法把感染压下来。

先把感染压下来,才有可能做进一步治疗。

浅浅睡下,突然一阵恐惧的感觉冲上脑门,一下子冲醒了。那种感觉,是一种不可思议的“非现实感”。

我好怕,好怕失去他。

2024/1/13

这一个星期,我不知道是怎么过来的。

爸爸的神智状态越来越差,70%的时间里是不认识我的。

一个不愿意接受又不得不接受的事实:爸爸已经处于临终状态了。

除了急诊科,省立医院没有其他科室愿意接收爸爸去到病房,只能一直在抢救室里。抢救室里24小时都是嘈杂的声音,病人和陪护都休息不好。但又无处可去。

医生说,如果三四天没把炎症压下去,就没什么希望了,现在七天了,还是看不到希望。

爸爸也好痛苦,止疼针注射的频率越来越高。看得我好难受。

我是不是该放手了?爸爸多活一天,就多痛苦一天。即使奇迹发生,生存质量肯定也无法得到保障。

2024/1/14

在市一医院找到了一个可以接收的病房,终于可以不用住在抢救室里了,住院环境可以好些了。

爸爸住院这段时间,得到了非常多亲友的支持和帮助:在医院陪护,值夜,甚至处理排泄物,如果没有他们的帮助,我肯定扛不过这段艰难的时间。我感受到有一个温暖的大家庭有多么重要。

是不是要考虑二胎了?

2024/1/18

这可能是我这辈子做出的最艰难的决定吧,放手了,带爸爸回家。

在家里陪爸爸走最后一段路吧。

从1/6到今天,感染指标未见改善,又比入院之前新增了肺部的感染,CT显示整个肺部都白了。身上都插满了管子,不能说话。

在医院里打了安定,爸爸一直在沉睡,他应该感受不到痛苦。回家后,待安定的药效过后,希望他不要有太大的痛苦。

为了安定药过后,爸爸能好受一些,今天与省立的宁养院取得了联系,他们可以提供一些临终关怀和止痛药的服务。服务很周到,态度很耐心。对于临终病人家属来说,宁养院的支持给了我们些许安慰。

2024/1/19

今天,爸爸永远地离开了我。

爸爸走的时候没什么痛苦,很安详,甚至准备好的止痛药都没用上,这给了我些许安慰。

人生这趟列车,很少人能够从开始陪伴我们一直到最后。爸爸到站了,先下车了。离别的车站,让人伤感,但也只能挥挥手,道一声珍重,感谢36年的养育和陪伴。我会永远怀念这段36年的旅程。

OpenWrt:ucode开发环境下的LuCI HelloWorld应用

在OpenWrt 23.05中,基于lua的LuCI开发环境默认不被预装,取而代之的是基于ucode的开发环境。因此,默认不能使用官方教程https://github.com/openwrt/luci/wiki/Modules中的lua接口来新增请求了。

本文目标:通过举例,说明在没有lua的LuCI开发环境中,一条配置项,是如何从前端下发到路由器上的。

开发环境

在开始之前,你需要准备一套环境,满足以下要求:

  • OpenWrt系统已经启动并设置了SSH登陆
  • 确保LuCI已安装并可以在浏览器中访问
  • 推荐安装openssh-sftp-server软件包,以便通过SFTP上传下载文件(opkg update; opkg install openssh-sftp-server)。

LuCI简介

OpenWrt的前端界面使用LuCI(Lua Configuration Interface),它是一个基于Lua语言编写的轻量级Web管理界面。LuCI提供了一个直观和易于使用的图形用户界面,使用户能够通过Web浏览器对OpenWrt路由器进行配置和管理。

环境常用路径

  • /www/luci-static/resources/: luci前端需要的资源文件,如图片,js,css,html等。
  • /usr/share/ucode/luci/controller:controller ucode代码。
  • /usr/share/ucode/luci/template:template ucode代码。
  • /usr/share/luci/menu.d:http请求uri注册用的json文件

HelloWorld

注册请求

/usr/share/luci/menu.d目录下,新增一个hello.js文件,文件内容为:

1
2
3
4
5
6
7
8
9
10
{
"admin/hello": {
"title": "hello",
"order": 1,
"action": {
"type": "view",
"path": "hello"
}
},
}

resources文件更新

/www/luci-static/resources/view中,新增hello.js文件,文件内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'use strict';
'require view';
'require fs';
'require ui';
return view.extend({
load: function() {
return "Hello World!"
},
render: function(helloString) {
return "<h1>" + helloString + "</h1>";
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

这里的loadrender会依次调用,load函数的返回值,会作为render函数的输入参数。render的返回值,会作为前端view部分的html代码。

验证

在浏览器中访问http://[IP]/cgi-bin/luci/admin/hello,可以看到Hello World页面。

LuCI Helloworld

给HelloWorld添加uci配置功能

接下来我们给HelloWorld赋予更为丰富的功能,支持配置的的查询和修改。这就需要用到luci uci api

修改hello.js

我们通过uci api读取ntp的配置,每次刷新页面,ntp的配置就被修改一次(0改为1,1改为0)。将上述hello.js文件修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
'use strict';
'require view';
'require uci';
'require form';

return view.extend({
load: function() {
//return 'Hello World';
return Promise.all([uci.load('system')]);
},
render: function() {
let ntp_enabled = uci.get('system', 'ntp', 'enabled');
let change_to = ntp_enabled=='1' ? '0' : '1'
uci.set('system', 'ntp', 'enabled', change_to);
uci.apply();
uci.save();
return "<h1>NTP Enabled: " + change_to + "</h1>";
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

以上程序,在load函数中,通过uci.load加载配置。在render中,通过uci.get读取ntp配置,并通过uci.set修改。最后通过uci.applyuci.save保存配置。

验证

在浏览器中,打开http://[IP]/cgi-bin/luci/admin/hello并不断刷新页面,可以见到,每次页面刷新,ntp的开关配置就被修改一次。

LuCI uci Helloworld

参考资料

OpenWrt:编译自己的HelloWorld应用插件

本文说明如何一步一步地编译出自己的Hello World应用(插件),并部署到OpenWrt设备上。

准备工作

编译环境

准备编译环境,准备一个用于编译插件的根目录(我的环境中目录名是~/openwrt_plugins),在此目录下,下载OpenWrt工程到一个名为source的目录,并切换到最新的稳定版分支。

1
2
3
4
5
git clone https://github.com/openwrt/openwrt.git source
cd source
git branch -a
git tag
git checkout v23.05.0

下载软件包

运行./scripts/feeds update -a命令,下载或更新在feeds.conf/feeds.conf.default中定义的所有最新软件包。

受限于国内的访问国际互联网的连接环境,feeds update这一步特别容易失败。可以通过以下几条方式来提高成功率:
git config --global http.postBuffer 524288000
git config --global http.lowSpeedLimit 1000
git config --global http.lowSpeedTime 600
以上配置的含义为:配置git缓冲区为500M,配置git访问超时的条件为:速率小于1KB/s,且持续600秒

运行./scripts/feeds install -a命令使安装上述软件包在后续的make menuconfig中生效。

配置编译选项

使用已有固件的编译配置

网络上已编译出的固件通常都会把编译配置一并提供(config.buildinfo或config.seed),可以直接使用。

我的目标机是一台小米WR30U,使用mtk的filogic芯片方案,从OpenWrt官网找到对应的编译配置并下载,置于OpenWrt工程根目录下的.config文件中:

1
wget https://downloads.openwrt.org/releases/23.05.0/targets/mediatek/filogic/config.buildinfo -O .config

但这份配置中包含了filogic芯片方案的所有设备的配置,还需进行裁剪和修改。

在以上配置的基础上,运行make menuconfig命令来完成进一步的自定义配置。完成后配置会更新至.config文件中。

在Target Profile中,仅保留Xiaomi WR30U的设备支持(我的设备是Xiaomi Mi Router WR30U (112M UBI with NMBM-Enabled layout)),把其它设备的支持删除。

menuconfig

编译工具链

根据以上的配置,编译出适配指定设备的编译工具链。

1
make toolchain/install

为了更高效地使用工具链中的各类工具,将工具链的输出目录加入PATH路径。

1
export PATH=~/openwrt_plugins/source/staging_dir/host/bin:$PATH

创建自己的OpenWrt应用

构建包(package)

OpenWrt构建系统在很大程度上围绕着“包”(packages)的概念展开。它们是系统的核心要素。不论是什么软件,几乎总会有相应的包。这适用于系统中的几乎所有内容,无论是与目标独立的工具、交叉编译工具链、目标固件的Linux内核、与内核模块,还是将安装到目标固件的根文件系统的各种应用程序。

因此,对于“Hello, World!”应用程序来说,也要遵循这种基于“包”的构建逻辑。

包源(package feed)是一个包的仓库,它包含包的配置项,可包含在最终固件中。包的仓库可以位于本地目录上,也可以位于网络共享上,或者可以位于GitHub之类的版本控制系统上。

下面,我会从源码构建出一个名为“helloworld_package”的包。

准备编译目录和源码

准备编译目录和代码,这里只准备一个最简单的Hello World程序。把以下代码存放至~/openwrt_plugins/helloworld_package/helloworld/source/helloworld.c文件中。

1
2
3
4
5
6
7
#include <stdio.h>

int main(void)
{
printf("\nHello, world!\n\n");
return 0;
}

makefile存放至~/openwrt_plugins/helloworld_package/helloworld/source/makefile

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
# Global target; when 'make' is run without arguments, this is what it should do
all: helloworld

# These variables hold the name of the compilation tool, the compilation flags and the link flags
# We make use of these variables in the package manifest
CC = $(TARGET_CC)
CFLAGS = $(TARGET_CFLAGS)
LDFLAGS = $(TARGET_LDFLAGS)

# This variable identifies all header files in the directory; we use it to create a dependency chain between the object files and the source files
# This approach will re-build your application whenever any header file changes. In a more complex application, such behavior is often undesirable
DEPS = $(wildcard *.h)

# This variable holds all source files to consider for the build; we use a wildcard to pick all files
SRC = $(wildcard *.c)

# This variable holds all object file names, constructed from the source file names using pattern substitution
OBJ = $(patsubst %.c, %.o, $(SRC))

# This rule builds individual object files, and depends on the corresponding C source files and the header files
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)

# To build 'helloworld', we depend on the object files, and link them all into a single executable using the compilation tool
# We use automatic variables to specify the final executable name 'helloworld', using '$@' and the '$^' will hold the names of all the
# dependencies of this rule
helloworld: $(OBJ)
$(CC) -o $@ $^ $(LDFLAGS)

# To clean build artifacts, we specify a 'clean' rule, and use PHONY to indicate that this rule never matches with a potential file in the directory
.PHONY: clean

clean:
rm -f helloworld *.o

清单文件,清单文件也是Makefile文件,用于指定包编译的各类信息:~/openwrt_plugins/helloworld_package/helloworld/Makefile

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
include $(TOPDIR)/rules.mk

# 包名和版本号,$(PKG_BUILD_DIR)变量会由这些包名和版本号组成
PKG_NAME:=helloworld
PKG_VERSION:=1.0
PKG_RELEASE:=1

# 源码目录
SOURCE_DIR:=./source

include $(INCLUDE_DIR)/package.mk

# 包定义; 定义了在配置目录中,配置选项放置于什么位置 (make menuconfig)
define Package/helloworld
SECTION:=examples
CATEGORY:=Examples
TITLE:=Hello, World!
endef

# 包描述信息:更详细的说明
define Package/helloworld/description
A simple "Hello, world!" -application.
endef

# 准备编译指令:创建目录并拷贝源码
# $(Build/Patch)是为了兼容补丁机制
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
cp $(SOURCE_DIR)/* $(PKG_BUILD_DIR)
$(Build/Patch)
endef

# 包构建指令:调用交叉编译工具链编译源码,生成可执行文件。
define Build/Compile
@echo 'start compiling'
$(MAKE) -C $(PKG_BUILD_DIR) \
CC="$(TARGET_CC)" \
CFLAGS="$(TARGET_CFLAGS)" \
LDFLAGS="$(TARGET_LDFLAGS)"
@echo 'end compiling'
endef

# 包安装指令:创建安装目录,拷贝可执行文件至目录中。
define Package/helloworld/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/helloworld $(1)/usr/bin
endef

# 真正执行构建指令(以上defile都只是在定义,未执行)
$(eval $(call BuildPackage,helloworld))

feeds准备、更新、安装

在OpenWrt工程源码目录下~/openwrt_plugins/source,创建feeds.conf,文件内容如下:

1
src-link helloworld_package /home/user_name/openwrt_plugins/helloworld_package

注意,src-link这里不能写~/openwrt_plugins/helloworld_package,要写绝对路径。

feeds更新与安装:

1
2
3
cd ~/openwrt_plugins/source
./scripts/feeds update helloworld_package
./scripts/feeds install -a -p helloworld_package

打印出这句话,就是安装成功了。

1
Installing package 'helloworld' from helloworld_package

编译package

通过make menuconfighelloworld包给使能(位置在Examples/Hello, World!),并将配置保存。
然后执行以下命令编译:

1
make package/helloworld/compile

编译成功后,在bin/packages/<arch>/helloworld_package/目录下,可以看到ipk文件,就是编译出来的包了。

部署和应用

将编译出的ipk文件传输到设备上,通过opkg命令安装。

1
opkg install /tmp/helloworld_1.0-1-arch>.ipk

安装成功后,执行它。

1
2
3
root@OpenWrt:~# /usr/bin/helloworld

Hello, world!

至此,一个最简单的OpenWrt应用就编译部署完成了。

OpenWrt系列教程

OpenWrt:从源码到固件,编译自己的OpenWrt系统

本文说明如何一步一步地从源码编译出自己的OpenWrt镜像,并升级到设备上。

准备工作

准备一个GNU/Linux, BSD 或 MacOSX 操作系统。并且,在环境中准备好以下官方教程中要求的工具:

https://openwrt.org/docs/guide-developer/toolchain/install-buildsystem

我的环境是Ubuntu22,执行

1
2
sudo apt update 
sudo apt install build-essential clang flex bison g++ gawk gcc-multilib g++-multilib gettext git libncurses-dev libssl-dev python3-distutils rsync unzip zlib1g-dev file wget

编译系统

下载源码

下载代码:

1
2
3
git clone https://github.com/openwrt/openwrt.git
cd openwrt
git pull

切换分支,一般选择最新的稳定版本:

1
2
3
git branch -a
git tag
git checkout v23.05.0

下载软件包

运行./scripts/feeds update -a命令,下载或更新在feeds.conf/feeds.conf.default中定义的所有最新软件包。

受限于国内的访问国际互联网的环境中,feeds update这一步特别容易失败。可以通过以下几条方式来提高成功率:
git config --global http.postBuffer 524288000
git config --global http.lowSpeedLimit 1000
git config --global http.lowSpeedTime 600
以上配置的含义为:配置git缓冲区为500M,配置git访问超时的条件为:速率小于1KB/s,且持续600秒

运行./scripts/feeds install -a命令使安装上述软件包在后续的make menuconfig中生效。

配置编译选项

使用已有固件的编译配置

网络上已编译出的固件通常都会把编译配置一并提供(config.buildinfo或config.seed),可以直接使用。

我的目标机是一台小米WR30U,使用mtk的filogic芯片方案,从OpenWrt官网找到对应的编译配置并下载,置于OpenWrt工程根目录下的.config文件中:

1
wget https://downloads.openwrt.org/releases/23.05.0/targets/mediatek/filogic/config.buildinfo -O .config

但这份配置中包含了filogic芯片方案的所有设备的配置,还需进行裁剪和修改。

在以上配置的基础上,运行make menuconfig命令来完成进一步的自定义配置。完成后配置会更新至.config文件中。

在Target Profile中,仅保留Xiaomi WR30U的设备支持(我的设备是Xiaomi Mi Router WR30U (112M UBI with NMBM-Enabled layout)),把其它设备的支持删除。

menuconfig

编译

使用make download预先下载编译过程中需要的代码和依赖项等资源。

. . . . . . . 经过漫长的等待下载完成。到此为止,我们已做好了所有的编译准备。

正式开始编译吧,运行make命令来构建固件。该命令将下载所有源代码和依赖项(即使之前已经make download,也还有其它包需要下载),构建交叉编译工具链,然后为目标系统交叉编译出OpenWrt内核和应用程序。

1
make -j4 V=s

编译选项说明

  • -jN: make命令可以加上-j参数用于指定使用多少cpu核编译,可以加速编译过程。例如:make download -j4make -j5
  • V=s:make命令可以加上V=s可以输出更多的编译错误信息。

如果顺利的话,这里make完就可以编译出镜像了。但是实际不太可能完全顺利。每个人遇到的问题可能不一样。我遇到的问题和修复方法,我放在后面附录中。

编译输出

. . . . . . . 经过漫长的编译过程(我的环境中编了5个小时),编译结果存放于openwrt/bin/targets/目录下。几类编译产出的镜像说明如下:

  1. factory:用于替换厂商的原厂固件,兼容原厂的安装包格式。通常使用原厂的web GUI进行升级。
  2. sysupgrade:用于升级替换已有的OpenWrt版本,这是最常用的镜像。
  3. initramfs-kernel:用于开发或特殊情况下的一次性引导,作为安装常规sysupgrade版本的过渡步骤。由于initramfs版本完全运行在RAM中,不会在闪存中存储任何设置,因此不适合用于操作性使用。

升级固件

主要参考刷机教程中提及的办法,在路由器断电后,用针按住 reset 不放,再接上电源,等待 10s 左右松开,就能进入 uboot。电脑用网线和 wr30u 的网口1连接,电脑在网络设置里将以太网设置为静态。IP地址:192.168.1.2,子网掩码:255.255.255.0,浏览器打开192.168.1.1访问uboot后台。

uboot下上传固件

现在选择编译出的openwrt主程序(openwrt-23.05.0-mediatek-filogic-xiaomi_mi-router-wr30u-112m-nmbm-squashfs-sysupgrade.bin),upload 后 update 更新即可。

升级完成后,把网络连到LAN口上(非1口),在控制台上用ssh root@192.168.1.1登陆,可见已成功升级至编译出的OpenWrt固件。

ssh登陆OpenWrt设备

也可以通过浏览器访问192.168.1.1,进入web管理页面,默认用户名密码是root/password

OpenWrt LuCI首页

将自己编译的固件与从官方网站上下载的固件https://downloads.openwrt.org/releases/23.05.0/targets/mediatek/filogic/openwrt-23.05.0-mediatek-filogic-xiaomi_mi-router-wr30u-112m-nmbm-squashfs-sysupgrade.bin 升级后的结果做了一下对比,路由器功能完全一致。

附录

参考资料

编译失败记录

内核编译Warning

默认情况下,内核编译有加上-Werror选项,因此遇到Warning就会停下来。例如编译失败日志:

1
2
/home/spencer/openwrt/build_dir/target-aarch64_cortex-a53_musl/linux-mediatek_filogic/dmx_usb_module-19.12.1/dmx_usb.c: In function 'dmx_usb_write':
./include/linux/kern_levels.h:5:25: error: format '%d' expects argument of type 'int', but argument 4 has type 'size_t' {aka 'long unsigned int'} [-Werror=format=]

于是找到-Werror添加的地方,删除掉。即在openwrt/build_dir/target-aarch64_cortex-a53_musl/linux-mediatek_filogic/linux-5.15.132/Makefile中,把以下这行注释掉:

1
#KBUILD_CFLAGS-$(CONFIG_WERROR) += -Werror

acl编译失败

遇到acl编译失败,到社区上搜索,已有高人给出了答案:

https://github.com/openwrt/packages/issues/21051
https://github.com/openwrt/packages/pull/21031/commits/dcc6d70f735474d49345d8d4b43a8098bb217220

cjdns编译失败

1
2
3
4
5
6
7
cc1: note: someone does not honour COPTS correctly, passed 16 times
net/SwitchPinger_admin.c: In function 'adminPing':
net/SwitchPinger_admin.c:103:133: error: dangling pointer 'err' to an unnamed temporary may be used [-Werror=dangling-pointer=]
103 | Dict d = Dict_CONST(String_CONST("error"), String_OBJ(err), NULL);
| ^
net/SwitchPinger_admin.c:82:26: note: unnamed temporary defined here
82 | err = String_CONST("path was not parsable.");

解决办法:
https://github.com/coolsnowwolf/lede/issues/10817

OpenWrt系列教程

OpenWrt:刷机小米WR30U(AX3000T)

硬件配置

小米WR30U是小米的联通专供路由器,不支持售后。也可以购买它的零售版的AX3000T,硬件配置完全一样。

主芯片:MT7981BA
射频芯片:MT7976CN
交换芯片:MT7931AE
存储:RAM 256MB ROM 128MB
开发平台:Flogic 820

准备工作

  • 小米WR30U(AX3000T)一台
  • Windows 10 PC 一台
  • 网线一根
  • MobaXterm等控制台工具

刷机过程

初始配置

路由器上电,PC通过wifi连接上路由器放出的信号(我这里是xiaomi_0342和xiaomi_0342_5G)。

在浏览器中登陆:http://192.168.31.1/init.html#/home,初始配置向导随意配置。

在“上网配置”中,做以下配置:

  • 上网配置选择“DHCP”
  • 使能“启动与智能网关无线配置同步”(会重启)
  • WAN口选择,改为“固定WAN口”,1口为固定WAN口。

在PC上,打开控制面板-网络和 Internet-网络和共享中心-选择WLAN-点击属性-共享-勾选第一个允许-确认。这个时候路由器应该能连接网络,面板上的网络灯也会从黄灯变为蓝色。

设置网络共享

通过以上方式,路由器可以通过PC的网络来访问因特网,可以直接从网络上下载一些资源。但有时有一些鸡肋。原因是:PC要通过ssh来操作WR20U,就只能通过192.168.31.1这一wifi空口IP(ssh server只在这个接口上监听),但PC又同是要访问大网,wifi又不能连到WR20U放出的信号上,应该连到其它可访问大网的信号上。

解锁SSH

在PC上,安装pycryptodome工具,这是一个加解密库。

1
pip install pycryptodome

运行server_emulator.py脚本以解锁ssh

1
python server_emulator.py

出现Device informationfinish 就完成了,现在 wr30 就打开了 ssh,默认的用户名是 root 密码是 admin。

此时断开网线,用wifi连接WR30U。用ssh root@192.168.31.1连接路由器。出现“ARE U OK”就算连接上了。

ssh连接xiaomi WR30U

然后就可以通过ssh控制台操作设备了。

如果需要固化 ssh 可以执行以下命令(wr30u 需要联网)

curl -O https://cdn.jsdelivr.net/gh/lemoeo/AX6S@main/auto_ssh.sh
chmod +x auto_ssh.sh
uci set firewall.auto_ssh=include
uci set firewall.auto_ssh.type='script'
uci set firewall.auto_ssh.path='/data/auto_ssh/auto_ssh.sh'
uci set firewall.auto_ssh.enabled='1'
uci commit firewall``` 

刷写uboot

使用WinSCP把mt7981_xiaomi_wr30u-u-boot.fip文件拷贝到/tmp目录下,并执行:mtd write mt7981_xiaomi_wr30u-u-boot.fip FIP

烧写uboot命令

刷写主程序

路由器断电后,用针按住 reset 不放,再接上电源,等待 10s 左右松开,就能进入 uboot。电脑用网线和 wr30u 的网口1连接,电脑在网络设置里将以太网设置为静态。IP地址:192.168.1.2,子网掩码:255.255.255.0,浏览器打开192.168.1.1访问uboot后台。

uboot下上传固件

需要注意一点,uboot下的默认IP 192.168.1.1与许多家庭网络的网关一致,因此,为了避免冲突,连接WR30U的uboot下,建议断开大网连接,并使用浏览器的无痕模式。

现在选择下载好的openwrt主程序,upload 后 update 更新即可,刷写完成系统会重启进入 openwrt 的系统。

主程序web界面

至此,xiaomi WR30U就刷机完成了。

新的系统默认 WiFi 是 QWRT,没有密码,后台是 192.168.1.1,默认用户名:root,密码:password。

默认IP 192.168.1.1与许多家庭网络的网关一致,因此,为了避免冲突,建议修改本地IP为192.168.8.1 。(网络-接口-LAN修改)

修改IP后,ssh root@192.168.8.1,密码password。可以进入openwrt后台环境。

主程序界面

资源下载

参考资料

OpenWrt系列教程

VPP(2):软件架构,VPP-infra,VLIB

VPP软件架构

VPP软件从下至上分为VPP infra, VLIB, VNET, Plugins四层。

报文处理图

VPP-Infra

VPP基础设施层。是一个基础服务集合。提供内存访问,向量、环、哈希表等数据结构接口,以及报文处理图节点、定时器等。VPP infra已经完成近20年,不会有经常的变更。包含:

  • 向量
  • 位图
  • 池:结合向量和位图,以便于快速分配定长的数据结构
  • 哈希表
  • 定时器
  • 单调时钟
  • 字符串format,unformat

VLIB

矢量处理库。vlib层处理各种应用程序管理功能:缓存、内存和报文处理图节点管理。维护计数器,轻量级的多任务线程,数据包跟踪。并实现了CLI。main函数也在此层。

初始化

使用 VLIB_INIT_FUNCTION (my_init_function) macros宏来定义初始化函数。默认情况下,所有初始化函数的执行顺序不定。

可以通过以下手段来约束初始化函数的执行顺序:

1
2
3
4
5
6

VLIB_INIT_FUNCTION(my_init_function) =
{
.runs_before = VLIB_INITS("we_run_before_function_1", "we_run_before_function_2"),
.runs_after = VLIB_INITS("we_run_after_function_1", "we_run_after_function_2),
};

报文处理图初始化

报文处理图定义了一系列的图节点用于处理报文。框架支持在运行时往图中添加节点,不支持移除节点。vlib提供了多种类型的节点,按分发行为来分类(vlib_node_registration_t ):

  • VLIB_NODE_TYPE_PRE_INPUT:在其它节点类型之前执行
  • VLIB_NODE_TYPE_INPUT:在pre_input节点之后,尽快执行
  • VLIB_NODE_TYPE_INTERNAL:只在针对挂起的报文,显式配置运行态时执行。
  • VLIB_NODE_TYPE_PROCESS:只在显式配置运行态时执行,在多任务线程中执行。节点会在挂起一段时间后被执行。

报文处理图节点分发

报文处理图节点分发的函数入口是:./src/vlib/main.c:vlib_main_loop.。报文分发的流程很简单,但是可能会很难理解:分发器将向量发入处理图中,如果需要的话,把它切分成更小的向量,直至原始向量中的所有工作都被处理完。

这一方案需要在向量中的报文个数上做一个权衡:如果向量中的报文个数增加,每个报文的处理时间减小。是因为:节点处理的第一个分片报文的处理过程中,把处理所需的指令加载到L1 I-cache中,后续所有报文都会此因受益,可以使用I-cache中已经加载好的指令。

参考资料

VPP(1):什么是VPP,为什么叫VPP

什么是VPP

VPP(Vector Packet Processing)是思科旗下的一款可拓展的开源框架,提供用用的、高质量交换、路由功能的应用框架。是网络协议领域常用的开源框架。

为什么叫VPP

VPP全称Vector Packet Processing(矢量报文处理)。VPP的精髓都包含在名字里了,即这个Vector。矢量报文处理与常规的报文处理有什么差别?

标量报文处理

一个标量报文处理流程的典型过程是:一次仅处理一个报文,一个报文中接口收到后,经过若干个功能模块的依次处理。每个功能模块的处理结果,通常有三类,1)丢弃;2)不作处理投递给下一功能模块;3)改写报文/转发。

1
2
3
4
+---> fooA(packet1) +---> fooB(packet1) +---> fooC(packet1)
+---> fooA(packet2) +---> fooB(packet2) +---> fooC(packet2)
...
+---> fooA(packet3) +---> fooB(packet3) +---> fooC(packet3)

标量报文处理存在以下问题:

  1. I-cache抖动:每个报文都要完整地执行完所有功能模块(foo)的处理,程序频繁地替换I-cache(instruction cache)缓存中的数据,导致缓存利用率降低,从而影响计算机性能。
  2. 每个报文处理过程中的调用栈信息随功能模块(foo)而变化,导致调用栈存放的D-cache的存取压力大。

矢量报文处理

矢量报文处理把报文组成一个“报文矢量”(可理解为报文组)。每一个功能模块(foo),都对报文矢量中的报文做一次性处理。

这样的设计:

  1. 减少了I-cache抖动,因为每个instruction重复应用于处理报文组的每个报文,提高了I-cache命中率。
  2. 调用栈信息重复应用于报文组的每个报文,D-cache的存取压力随之降低。
1
2
3
4
+---> fooA([packet1, +---> fooB([packet1, +---> fooC([packet1, +--->
packet2, packet2, packet2,
... ... ...
packet256]) packet256]) packet256])

报文处理图

VPP中的关键设计是Packet Process Graph(报文处理图)。使VPP具有以下特性:

  • 可伸缩,可插件扩展
  • 成熟的图节点架构
  • 可定制的流水线
  • 插件平等

在处理图中,开发人员可以定制插入新的处理节点,这让VPP易于扩展且可以用于定制化地对报文进行特定意途的处理。

报文处理图

VPP平台从接口上接收报文,组合成报文向量,然后把报文向量“喂”给报文处理图,图中的所有节点对报文向量进行逐一处理。

VPP插件都是在运行时加载的共享库。一个插件可以引入新的报文处理图节点或重新组织报文处理图。你可以生成一个完全独立于VPP原生的报文处理图。

参考资料

Linux CFS调度器

Linux CFS

CFS(Completely Fair Scheduler)是Linux 2.6.23之后的内核默认的线程调度器,它提供了一种相对公平的线程调度策略。它提供了多种调度算法,大致分为两类。

  • 普通调度策略
  • 实时调度策略

普通调度

普通调度策略,即最小vruntime调度。包含SCHED_OTHER, SCHED_IDLE, SCHED_BATCH这几类。在普通调度策略下,优先级(sched_priority)字段总是为0,并没有参与到调度策略的决策中。

注意:这里说的sched_priority与ps或top命令中看到的pri字段,并不是同一个含义。

vruntime

vruntime是一个线程在cpu上运行的累计时间经过归一校正计算后的结果 ,调度器会优先调度vruntime较小的线程。

理想情况下,所有线程的vruntime大小应该一致,即各线程得到了公平的调度。

线程切换原则

原则很简单:总是选择vruntime最小的线程进行调度。

线程A在执行一段时间后,它的vruntime逐渐累积。一旦A的vruntime超过另一个B的vruntime,CFS则做出线程切换,调度线程B执行。

红黑树

所有处于runnable状态的线程,基于vruntime值,组织一棵红黑树(CFS red-black tree)。以此方便地插入新的线程,或取出vruntime最小的线程。

基于nice值的动态优先级

nice值通过对vruntime的校正,进而影响线程被分配到的时间片的多少。nice的取值范围是-20(高优先级)~19(低优先级)。nice会影响线程占用CPU的时间,nice值越小被分配到的时间片越多。nice值仅会影响SCHED_OTHERSCHED_BATCH策略。

下面简要解释算法:

每一个nice值都对应一个权重系数。大约为: weight = 1024 * (1.25)^(-nice)

可以简要地理解为,权重系数为(1.25)^(-nice)

  • 当nice为0,权重系数=1,vruntime即是线程在cpu上运行的实际时间。
  • 当nice>0,权重系数>1,vruntime会比线程在cpu上运行的实际时间更长。
  • 当nice<0,权重系数<1,vruntime会比线程在cpu上运行的实际时间更短。

调度策略

SCHED_OTHER

SCHED_OTHER仅用于sched_priority为0的线程,在sched_priority为0的所有SCHED_OTHER线程中,使用CFS默认的调度策略(最小vruntime调度),上面已经描述了具体算法。

SCHED_BATCH

SCHED_BATCHSCHED_OTHER类似,仅用于sched_priority为0的线程。其不同之处在于:SCHED_BATCH会假设所有线程都是对cpu都是贪婪的,它会在线程的每一次调度之后,对线程施加一点“小惩罚”。这种策略通常应用于占用cpu较多,但又没有用户交互的线程中。

SCHED_IDLE

SCHED_IDLE仅用于sched_priority为0的线程,nice值对它也不起作用。此调度策略被应用于极低优先级的线程,仅当系统空闲时线予调度。

实时调度

SCHED_FIFOSCHED_RR都属于实时线程调度策略。

sched_priority在实时调度策略中被使用,它的取值范围是1~99。调度器针对每一个sched_priority级别,都维护一个待执行的线程的队列。调度器会从最高优先级的非空队列中,选择队头节点的线程来执行。

SCHED_FIFO

当一个SCHED_FIFO达到可执行状态,它总是会抢占SCHED_OTHER, SCHED_IDLE, SCHED_BATCH的线程。SCHED_FIFO并没有把cpu时间切分时间片,一旦开始执行,就会一直执行,除非以下状况:

  • 线程主动挂起(I/O等)。
  • 被更高的优先级线程抢占。

它遵循以下规则:

  • 正在被执行的SCHED_FIFOA线程,如果被更高优先级的B线程抢占,它会放在队列队头中。一旦B线程执行完成,A线程会马上被恢复调度。
  • SCHED_FIFO的线程从不可执行转为可执行状态,它会被加入到对应sched_priority的队尾。
  • SCHED_FIFO线程的优先级被调整时,如果是提高优先级,会被放入新优先级队列的队尾;如果是降低优先级,会被放在新优先级队列的队头。

SCHED_RR

RR(Round-robin)调度是针对FIFO调度的增强。在以上FIFO调度的策略的基础上,添加了以下规则:可以配置线程单次执行的最大时间片长度。一旦时间片被使用完,线程停止执行并放置到对应优先级队列的队尾。

参考文献