阅读视图

死去的回忆

就在刚刚,收到腾讯企业微信的消息,提示公用邮箱空间不够了,为了能够正常收发邮件请及时清理邮件。

在进行邮件批量删除的时候,隐约看到几封邮件从眼前一闪而过,而那几封邮件明显不是邮件管理员。登录到web版本的邮箱才发现,在失踪的日子里,有好多人给自己发过邮件,当然也有其他的邮件。

这些邮件淹没在了这茫茫的退信通知中。也在这时候看到了 云无心 的一封邮件:

一副水墨画,涓涓的小河,这是那小山远没有如此的巍峨。而昔人也已乘黄鹤去。

还有alrclyacevs2borear   等发的关心的邮件,时间太久了,也不回复了,感谢大家的关心和惦念,当然,还有最开始不小心删除的那些,一并谢过了。

对于jeffer.z的咨询问题,看到的有些晚了,也回复了。深表抱歉。

那些没回的消息,不是因为高冷,也不是因为不想回。从来没想到会有人直接回复到这个邮箱,有些出乎意料了。

感谢宝子们的关心,不管如何,我还是会一直向前,爱你们。

  •  

明知山有虎

每当想尽心尽力的去做点事情的时候,总是会有些意外不期而至。这些意外,就一个作用,让自己那仅存的责任心,再少那么一点。

之前自己曾经说过,工作就当成工作就行了,不要当成事业来干。尤其是没有领导关注或者自己依然不在权利中心的时候,稍微的那么一点责任心带来的不是成就感,是无穷无尽的黑锅。如果说把工作当成事业来干,这工作不配,我也不配。

几年前一个烂尾项目,项目经理离职之后,验收无望。连个推进的人都没有,当时自己带着研发去开发的这套系统。之所以验收无望是原来的项目经理与甲方完全没有任何的功能需求交互,开发了一堆毫无实际价值的功能。我安排产品经理去沟通需求的时候,项目经理说,沟通什么需求,完全没必要,我说的就是需求。

在这种所谓的大言不惭的承诺下,系统开发出来,发现完全无法满足甲方的需求。也就在这时候,项目经理离职了,火速交接,第一天听说,第二天人就不见了。这个烂摊子从此就成了无人问津的项目,所有人避之唯恐不及。彼时,自己座位项目的研发总监,想着能交付还是要教父的。于是接下了这个烂摊子,带着产品经理驻场对接需求,所有功能推翻重新开发,两个月之后勉强满足了甲方需求。服务器费用报批一直走不出来,为了上线,自己还垫付了一年的服务器费用两万多,这两万多至今还没给报销。

后来甲方要求进行项目推广,这本来也不是开发方的需求,就是因为前项目经理的口头承诺,自己又带着人自付邮费跑了一个多月进行项目推广。终于在半年之后,项目验收了。在验收前就开始盯着考核验收的问题,每个月扣10个点的绩效。连续扣了三个月,不得不说,自己也是真的傻逼。

半年之后,甲方终于签了验收合同,这件事情才算告一段落。而现在,又开始折腾回款,至于回不回款,那不是我能左右的。严格说来,我们其实是算丙方,乙方与实际使用方签的合同,我们作为分包商拿的这个合同。我们的甲方-项目乙方以甲方没给他们付款为由,不给我们付款。当然,他们说的也是事实。但是这个回款关我屁事?

现在又开始考核这个所谓的回款,又要扣10个点的绩效。当能力成为负担的时候,我还是决定做一个战五渣。

明知山有虎,还向虎山行?

这纯粹就是有病。

明知山有虎,

那就别上明知山!!

  •  

稍纵即逝

五一之前忽然收到二姐的消息问什么时候回去,说老太太的钥匙又丢了。二姐说的钥匙,是那个写字台上钥匙。之前已经丢过好几次了,上次回去有用钢锯把锁给锯开,换上新的锁。不过这把锁只带了两把钥匙,一把留给老太太,另外一把自己带走了,就是怕那天她丢了钥匙,需要再次锯锁。万万没想到,这次丢的这么快。

收到这条消息的时候,正在陪宝子上网球课,我回了一条,不回了吧。回完消息,看到宝子已经打完一筐球了,把手机装进口袋,过去跟宝子一起捡球。

教练问了一句:『明天回老家?啥时候回来?』

『还不大好确定,到时候看看吧』我回道。这种兴趣班之类的,节假日基本都没放假了,正常上课。目的自然也很简单,就是为了挣课时费。

当然,这种答复主要是因为不想假期去上好几节课。毕竟,现在一周两节课,费用的确是有点高,另外一个原因就是可能还想着出去玩玩。每当休息的时候,闲暇的时间,真的并没有想象的那么多。第二天约了眼科医院的检查,带着宝子去查了下视力,同时测了下眼轴,被告知远视储备依然不足了。

对象陪着宝子等待叫号去做检查,自己打伞找了个配钥匙的地方去重新配几把钥匙,回家之后给姐姐留下几把,不至于丢了之后要又打不开抽屉。配一把钥匙2块钱,不到几分钟的时间,重新又配了五把钥匙出来。

回到医院,复诊医生看了下报告,说可以配一副减缓视力衰减的眼镜,这种眼镜很多的小凸点。但是价格,的确也不算便宜,国产三千,进口五千多。一时没想好配还是不配,事情暂时搁置。只是,有时候真的怕如果错过了这个机会,后面近视了,可能还是会后悔。

回家之后,宝子说:『我想回老家。』

她想回去,自然是想和她的小姐姐一起玩。既然没有安排其他的行程,回去自然也不是不可。周三从青岛出发,出了青岛就开始下雨,要么淅淅沥沥,要么大雨倾盆。到了县城雨势才逐渐变小,等到家的时候,甚至偶尔能看到太阳。

到家之后,姐姐们早就到了,开始准备午饭。合适的气温,合适的天气,索性直接在院子里摆好桌子,在院子里吃饭。除了偶尔从树上飘下来的杨树毛,一切都完美无瑕。

两张桌子拼在一起,还是稍显拥挤,只好先让孩子们先吃。毕竟他们吃的快一些,一个孩子吃饱跑了,剩下的也就吃不动了,跟着一起跑掉了。饭后,我又想起来上次回家的时候从阡岭上看到的那几株植物,小时候虽然常吃。但是却不知道学名是什么,上次回家的时候还在花期,现在已经结果了。跟二姐扛着镢头,去刨了几株出来。放到了后备箱里,从老家往回走的时候发现有些蔫了,还是刨的太早了,应该走之前刨出来,可能成活率会更高一些。小的时候,在外面见到一些小树苗,总是刨出来,移植回家,但是却从来没有成才过,过一段时间就死掉了。尽管如此,却还是乐此不疲,想着哪一天这颗幼苗能长大,结出果子来。

回到家,二姐提议,『不如咱们去司马沟吧,哪里有个大的秋千。』

这个司马沟的大秋千,早在过年的时候就建好了,不过但是没去,主要是感觉可能真的挺多的人的。拿出手机看了下,预计半小时之后将会下雨。我提议稍等一下,果然,半小时后狂风大作,院墙外的杨树被吹得大幅摇摆,感觉随时要断掉的感觉。在狂风吹了十几分钟后,忽然听到一声巨响,跑外院内一看,屋顶上掉上了两根碗口粗的树枝。屋顶的瓦,也已然已经有好几块阵亡的。

一个小时之后,风停了,雨停了。这时候自己能做的,不过是把屋顶的树枝拉下来,找了一根细绳,拴上一根木棍。抛了几次之后,终于成功把树枝给拉了下来。至于怎么修复屋顶,这个自己的确无能为力了,只能找专业的人士来处理了。

雨过天晴,空气也变得清冽,空气中的花香不在浓郁。想着宝子的姥姥喜欢吃槐花,虽然现在多数的槐花都已经完全盛开,不过要想找到那种稍微绽开的虽然需要费些功夫,却也不是完全找不到。带着孩子们,沿着村里的小路往东山前进。说是东山,其实并不算高,山腰上是各种新建的以及废弃的厂房。

野外的槐树,挂满了白色的花朵,靠近之后能闻到真真的芬芳,撸一把放进口中,能吃到丝丝的甜味。宝子说,生的比熟的好吃。

沿着厂房的铁栅栏前行,继续寻找尚未完全盛开的槐树,这次搜寻,并未找到合适的槐树。在路的尽头是一个类似水坝的结构,两侧有两排台阶,每级台阶大约四十厘米,宽度大约也有三四十厘米,整个大坝高度大约有七八米。看着这两排台阶,以及缓缓落下的夕阳,忽然内心有个大胆的想法,如果自己爬上去,应该刚好可以拍到夕阳落下的场景。

自己爬到一半的时候,宝子在下面喊:『我也想爬』。

『太陡了,你别上来』我停下来回答。等爬到顶才发现,自己真的没那么大胆,站起来之后,双腿甚至稍微有些发抖,而此刻如果让自己站着掏出手机牌照,感觉的确是有些难度。本来以为大坝两个H型的顶部是完全连着的,到顶才发现,这个H中间的横梁,竟然有个大约一米多的凹槽,完全就是一个凹字形。这四十公分的宽度,加上这一米的深坑,一米多的宽度,自己也不敢跳过去。

真的害怕一不小心就掉到了下面的石堆上,那就嗝屁了。只好蹲下来,沿着原来的台阶,颤颤巍巍的有回到了地面。然而,等自己绕过这个大坝从边上的斜坡到坝顶的时候,太阳已经完全落到地平线以下了。

这稍纵即逝的夕阳,真的没有抓住。只看到远方山顶的风力发电机在夕阳的余晖中旋转。

  •  

在Tor上搭建一个.onion网站

郑重声明与警告

本文旨在科普Tor网络的技术原理与搭建方法,仅供学术研究、个人兴趣及保护合法隐私之用。

请务必知悉:

利用Tor网络从事任何违法违规活动——包括但不限于传播违法信息、侵犯他人权益、进行非法交易等——均属严重违法行为,与本文作者及本博客无关,且将面临严厉的法律制裁。

技术无罪,但使用者有责。请严格遵守您所在地区的法律法规,切勿触碰法律红线。

本文未完待续

1.关于Tor和.onion

Tor这个词就是洋葱路由(The Onion Router)的首字母缩写,洋葱路由的概念始于1990年代中期,由美国海军研究实验室最早构想出tor的原型,tor的目标在于,找到一种尽可能隐私的方式来使用互联网,其想法是通过多个服务器转发流量并在每一步进行加密,数据一层层加密包装像洋葱一样,故名洋葱路由。

tor将这些服务器分为三类:守卫节点guard,中继节点relay和出口节点exit,这些节点服务器由个人或组织捐献给Tor

通过tor访问网站需要依靠tor代理,tor代理发起访问前会向三类节点各选一台,获得三个密钥,每个节点可以用自己签发的密钥对称加解密,后tor代理依次将数据按照从里到外exit=>relay=>guard的顺序,用各自密钥进行加密,在外面形成三个加密层,然后发送给guard,guard解密后发送给relay,relay解密后再发给exit,数据在三个节点间依次传递,其中:

  • exit节点不知道谁在访问,只知道访问目标
  • relay节点既不知道访问目标也不知道谁在访问
  • guard节点知道谁在访问不知道访问目标

这样就实现了难以追踪溯源的访问


通过Tor访问网站可以实现匿名访问,但是普通网站对于外界本身就是透明的,网站被查同样可能暴漏访客信息,或者网站出现某些信息会导致被直接查抄或封锁,因此在Tor上可以使用洋葱服务来实现对网站和访客双向的匿名保护,洋葱服务将网站挡在自己后面,防止网站暴露真实的IP/域名等信息,并只能通过tor网络访问到,同时也无法通过一般搜索引擎搜索到,洋葱服务使用不是实名注册而是通过计算得到一种特殊的.onion域名,这种域名由tor代理向tor进行网站服务器的对应查找实现访问,而不是传统的DNS解析出IP再去访问IP。

洋葱服务因为其双向都不受追踪的特点,成为一些非法网站的温床,故onion又被叫做所谓的暗网,但是作为技术Tor和onion本身是中立的,并不是为这些非法网站而生,而且Tor上面也不像某些营销号说的那样全是非法网站,相反大多数Tor上面的网站都是合法的,很多的表网也有Tor的入口,甚至来自大型公司或组织,例如:

  • BBC bbcnewsd73hkzno2ini43t4gblxvycyac5aw4gnv7t2rccijh7745uqd.onion
  • ProtonMail protonmailrmez3lotccipshtkleegetolb73fuirgj7r4o4vfu7ozyd.onion
  • DuckDuckGo duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion

通过Tor访问onion网站,一般都会用到洋葱浏览器(Tor Browser),当然也可以普通浏览器加上Tor代理实现访问,有些互联网审查地区还需要配合Tor网桥实现接入,使用洋葱浏览器等不是本文重点,也没有技术含量,不再赘述。

2.发布一个.onion网站

很多人觉得在Tor上建站更加复杂,实则相反,比起表网需要购买域名,证书,公网服务器,这些东西onion网站都不需要,因为onion域名不是申请的,而是计算的,tor网络本身就加密,因此为防止被监听的话通常不需要https,至于服务器,实际上,从传输层的角度来看,tor节点不会主动访问onion网站,而是onion网站服务器部署的tor服务不断主动从tor获取请求转发给本地的http服务,所以不需公网服务器,一台能正常联网的计算机理论上就能做onion网站的服务器。

为了安全,做onion网站的服务器反而应该打开防火墙阻断一切连接传入,防止暴漏

前置准备:需要一台服务器,先将网站发布到nginx等web服务器,并仅允许nginx接受来自本机的访问,此处用rocky linux9加nginx为例。

另:服务器肯定要用境外IP的,Tor的节点必然都被GFW拉黑,境内IP的机器就不要想了..

安装tor服务

yum install tor

vim /etc/tor/torrc修改tor默认配置,打开这两行前的注释,使用默认的/var/lib/tor/hidden_service/域名和密钥路径,并将请求转发到127.0.0.1:80的nginx

HiddenServiceDir /var/lib/tor/hidden_service/HiddenServicePort 80 127.0.0.1:80

打开tor服务

systemctl start tor

不指定域名的话,tor服务会为我们自动生成,切换到/var/lib/tor/hidden_service/目录,可以看到tor为我们自动生成的域名和一些密钥信息,hostname文件中的就是生成的默认域名

[root@VM-0-3-rockylinux ~]# cd /var/lib/tor/hidden_service/[root@VM-0-3-rockylinux hidden_service]# lltotal 16drwx------ 2 toranon toranon 4096 May  7 13:08 authorized_clients-rw------- 1 toranon toranon   63 May  7 13:08 hostname-rw------- 1 toranon toranon   64 May  7 13:08 hs_ed25519_public_key-rw------- 1 toranon toranon   96 May  7 13:08 hs_ed25519_secret_key[root@VM-0-3-rockylinux hidden_service]# cat hostname kswxwejpmr3tpi4jhro5o5gzwvawf7u3wyjqzek6264m5jtsinezrdyd.onion

tor服务启动的过程中,会将public_key公钥等信息注册到tor中,用于标识自己是哪个onion域名对应的网站,打开Tor浏览器,访问kswxwejpmr3tpi4jhro5o5gzwvawf7u3wyjqzek6264m5jtsinezrdyd.onion,即可通过tor网络访问到我们的网站页面,是个nginx刚装好的默认欢迎页面。

3.自定义.onion域名

github开源项目mkp224o可以用于计算生成暗网“域名”

切换到/opt目录,先拉取代码

git clone  https://github.com/cathugger/mkp224o.git

编译源码为命令

cd ./mkp224oyum install -y autoconf gcc gcc-c++ make autoconf automake libtool libsodium-devel./autogen.sh ./configuremake

编译好后会生成一个mkp224o命令在当前路径,执行mkp224o -n 1 -d /opt/nana/ nana开始计算,计算完成后,会在/opt/nana/下生成一个开头是nana的onion域名信息的目录:/opt/nana/nanazolamwaqczux2bfjnptwz7fudrv63e5nw3tqr77pevfgk23zx4ad.onion/,目录的名字就是生成的域名,和里面hostname文件的内容是一样的,不过,为了防止层级过深,将这个文件夹下的所有内容全部转移到它的上一级中

自定义的前缀越长,计算时间越久,9位的自定义前缀可能都需要数月数年,因此要酌情选择,计算后生成的secret_key文件是私钥文件,一定不要泄露,否则相当于失去这个域名的控制权

./mkp224o -n 1 -d /opt/nana/ nanacd /opt/nana/nanazolamwaqczux2bfjnptwz7fudrv63e5nw3tqr77pevfgk23zx4ad.onion/mv ./* ../rm -fr /opt/nana/nanazolamwaqczux2bfjnptwz7fudrv63e5nw3tqr77pevfgk23zx4ad.onion/

按照tor的要求,将nana目录本身和转移出来的几个密钥和域名文件设置权限

cd /opt/nana/chown -R toranon:toranon ./chmod 700 ./chmod 600 ./hs_ed25519_secret_keychmod 600 ./hs_ed25519_public_keychmod 600 ./hostname

vim /etc/tor/torrc修改tor配置HiddenServiceDir为/opt/nana/

HiddenServiceDir /opt/nana/HiddenServicePort 80 127.0.0.1:80

再将默认的nginx页面换成Claude Code + DeepSeek生成的炫酷效果页面,重启tor服务,用新的域名访问即可

systemctl restart tor

参考

  1. https://www.torproject.org/zh-CN/about/history/
  2. https://www.youtube.com/watch?v=24JK_b-2V4A&t=530s
  •  

敏感词屏蔽,是一把防护盾,也是一把阉割刀

一次被“禁止词汇”拦下的评论

  某一日,在满心的博客上评论时,提交不了,警报说有“禁止词汇”。

评论遇到禁止词汇

  刚开始没注意到提示语,再次提交时才看清了。我定睛看了看,不觉有异,尝试小改之后再提交,还是不行。疑惑之下,发邮件向博主询问,他很快就回复我了,并且告诉我是因为“中国”两个字,并且沟通后他去掉了这个屏蔽项,真是一位很nice的博主。

邮件沟通

无处不在的屏蔽体验

  由此事,触动我关于敏感词屏蔽的很多记忆。我在常用的几个互联网平台上,几乎都遭遇过不合理的敏感词屏蔽。有时候删除,有时候限读或禁言,有时候某个词能说,有时候又不能说。

  博客小站设置屏蔽,可以理解,很多时候是必要的,主要是防止垃圾广告机器人乱发或网络攻击,与互联网平台的敏感词屏蔽完全不同,不在本文讨论范围。

  我的博客评论区基本上没有设置屏蔽防护,后台检测某些情况下会转入待审核,由人工干预,前台连验证码也去掉了,事实表明,我这个微弱小站的评论区几乎没有出现垃圾评论。

  不知是否有人把敏感词屏蔽和古代的避讳联系起来,但本质不同。古代有“为尊者讳”,为帝王、父母、尊长等隐瞒名讳表示尊敬或敬畏,是下对上的尊,是一种文化传统,就像中国人不能没事直呼父母姓名一样。现在的敏感词屏蔽没有这一层意思,只是一种秩序管理或免责手段。

从“屏蔽脏话”到“屏蔽一切”,以及被扭曲的语言表达

  敏感词屏蔽,在中文互联网平台上,可以说是自古以来的“优良”传统了。最早是在论坛或游戏里面替换掉一些粗鄙骂人的词汇,渐渐的就扩大到不想让你讨论的话题,再到不想你提及的特殊人名或事物,现在已经许多严肃的词汇(比如国家、医院、警察)都可能被屏蔽了。

  看文字内容,如知乎回答或者各平台的文字评论区,经常见到各种莫名其妙的黑话、缩写、拼音、谐音。经常看B站抖音的朋友,必定多次遇见过这样的情形:正常的视频说话过程中,突然刺耳的“哔————”一声,就很难受。

  平台上的直播间,更是重灾区。虽然我不看直播,但会刷到直播切片,看到听到大量的黑话替换,真是不舒服。公务员写成GWY,政府写成ZF,人民警察说成帽子叔叔,医院说成加号,婚姻说成扯证,怀孕说成小西瓜,酒说成八加一,钱说成米,微信说成绿泡泡,淘宝说成某宝,等等。

不透明的规则、普遍的自我审查、表达成本高升

  大多数时候平台对于内容敏感词的处罚是极不透明的,一切尽在不言中,没有公开的具体规则(只有笼统的可以任意解释的),也不会对判罚细节进行公布,也没有高效的复议通道。导致内容发布者必须陷入麻烦的自我审查,自我阉割。

  像赖岳谦这样的爱国学者教授,也直言在B站发视频最累最烦的不是做视频本身,而是反反复复不明原因的审核不通过。马督工这样的非情绪化输出的博主,也会经常遇到视频被毙的情况,以至于视频一发出来,大家就喊着赶紧缓存。

能否寻找平衡

  我见到有些人已经在利用自己微薄的影响力呼吁有关部门重视和管理用词乱象,但那不过是头疼医头脚疼医脚,根源还是审查制度本身。我并不反对内容审查,甚至是支持必要的内容审查。显著的暴力、色情、辱骂,任何人都会同意进行屏蔽、删除、惩罚,但如何避免大量伤及无辜,如何避免矫枉过正,是需要符合当代社会发展的制度设计的。AI新世纪了,还在玩“刑不可知,则威不可测”,那我们的文化政策就还在两千年前的水平。

  我以为,关键是:统一标准、公开透明、公众参与、快速更新。

  如果平台能够检测到发布的内容中的某部分不是显著的恶意违规,只是可能违规,或无意瑕疵,是否可以由平台自动对内容进行打码、标注、提醒,而不是一律审核不通过或删除甚至停号呢?这样对于发布者而言就轻松多了,但平台大概是不愿意多担这样的责任和风险。监管层对于平台的约束也是不透明的,平台也不敢自专。

  如果由国家网信办统一建立审查平台标准库,公布和动态更新用于审查的相关的API和软件包,各互联网平台都与其对接,采用统一的审查标准、审查流程、审查格式、复议流程,避免各家互联网平台自建审核部门或外包第三方内容审核公司,是否更好呢?

  普通老百姓如何参与其中,也是需要考虑的,这是民主的重要体现之一。是否可以从实名申诉库中抽样由随机选取的大量普通老百姓集体投票表决是否违规呢?

现实无力,未来可期

  敏感词屏蔽,本意是一把防护盾,现实中更是一把阉割刀。

  作为防护盾的作用,作用非常有限,对平台和内容创作者都有巨大代价,而对黑灰产或专门做坏事的人来说,很容易变换出绕过屏蔽机制的手段。让我想起手机卡实名制,对于防范电信诈骗的作用,防了个寂寞,但使得黑灰产获得了大量的隐私信息,徒增普通人的烦恼,会被骗的人还是会被骗。

  作为阉割刀的作用,倒是十分显著,汉字之美,汉语之美,就被割了一刀一刀,内容被挖出一个个丑陋的黑色方块,或填入混乱不堪的杂物垃圾。平台自我阉割,内容发布者也自我阉割,每一方都有无辜之处,每一方都有痛点,似乎世间难得双全法,但总要有个前进的方向。

  •  

一个慵懒的五一

感谢订阅陶其的个人博客!

这个五一算是我过过的最放肆的五一了。

之前放假,无论是五一、国庆还是过年,基本上不是去我老家就是去我对象的老家,都是前后两天在路上,然后中间在老家闷着无聊。或者偶尔的出远门玩一下也都是人挤人,到处逛逛不仅人多还特别累挺。

这个五一我们哪儿也没去。

因为五一当天正好是农历十五,我们这边有个说法:初一十五别出门。

正好娃还比较小,我对象就建议咱不要出远门,所以这个五一就没回老家。

第一天

五一当天上午,我爸从老家过来把我妈接回去农忙。

然后就剩我和我对象两个人带娃。

为了放肆一下,我还专门去农贸市场买了几斤小龙虾,炒了个香辣小龙虾吃吃。

第二天

预报当天有雨,所以也没有安排行程。

但是也就中午和下午下了一阵儿小雨。

然后就做了前一天买菜时提前准备的排骨鸡爪煲吃吃。

第三天

预报是没有雨,但是刮起了大风,屋子外面风呜呜的。

自然也是没法带娃出门,又在家呆了一天。

中午做了羊肉粉丝汤吃吃。

下午风小了一些,几个朋友约着聚一下,都带着各自的对象,我和另一家还带了娃,一起又吃了一顿大餐。

第四天

风和丽日,艳阳高照,不能再闷在家里了。

然后我对象就预约了附近的桑葚采摘园,打算带娃去感受一下农场氛围。

桑葚畅吃9块9,桑葚畅吃还能带走两斤的是29.9。园内还有小动物和一些无动力设施都可以免费玩。我对象就团了一个9.9的和一个29.9的。

结果因为头一晚睡得晚,第二天就起得晚,然后又出门前各种准备各种折腾,反正出门的时候都已经10点半了,太阳比较好,所以天已经开始热了。反正等驱车到了桑葚采摘园都已经11点多了。

但是娃正好在这个时候睡着了,没招,就在采摘园停车场驻车等了半个多小时才醒,这才如愿进园。

但是这个时候已经快12点了,大棚里属实是热。

然后我们就轮流一个人抱娃一个人采摘,边吃边摘搞了快1小时才结束。

然后又玩儿了一会儿无动力设施,带娃看了几只小动物然后就回家了。

可能是因为大棚太热了流失了大量的体力,也可能是歇了几天废了,反正我和我对象到家都累坏了。

那时候都2点多了,本来想的是到小区停了车换电动车去旁边的小广场的小餐馆随便吃点,但是天太热桑葚不能放车里,正好要给娃换尿裤就上楼了。

又想着喂完娃饭之后再一起去广场吃饭,但是实在太饿了,我就先骑电车去买饭,我对象在家喂娃,等喂完娃我就买完回来了,还是比点外卖快的。

结果等我们吃完之后,两个人就彻底没劲儿了,然后就抱着娃一起上床休息会儿,结果都累的睡着了。本来想着下午5点再出门去广场零食店逛逛,还定了闹钟结果完全没听到,一觉醒来都6点50了,当天日落时间是7点。

本来想着出去逛逛夜景,顺便把饭解决了,结果我对象的表姐要来家里坐坐,看看娃顺便给娃买了新衣服,后来表姐走的时候也让她带回去一些今天刚采摘的桑葚给她家娃吃,等走了之后都8点多了。

又是我自己骑电车出门,把之前线上买的榴莲给兑换了,又买了份饭和一堆零食,因为后面对象要带着娃去她老家住一段时间,所以提前给她买了一堆零食。

然后因为前些天太懒了,我对象收拾东西的同时,我又洗衣服洗到了半夜。

第五天

今天没有别的事儿,就是收拾一下把我对象和娃送娃姥姥家,然后我回来干会儿活再收拾收拾家里卫生。

结果我那天晚上居然失眠了,又到好晚好晚才睡着。

第二天早上自然又是赖床了。

然后起床做饭吃饭,又简单的收拾了一下。正准备出门,娃又睡了。

然后一看时间快11点了,因为娃白天睡觉有时候最多能睡两个小时,也不知道这次能睡到什么时候,所以索性也不去我对象家吃午饭了,就在家吃过下午再去吧。

不过家里没啥菜了,就又点了外卖。

但是刚点完外卖没多久,娃就醒了。

然后就先做娃的饭,然后就是一起吃饭,吃完我把东西先拿下去一波,顺便把安全座椅给装了(我家车装安全座椅极其费劲,搞了一身汗才弄好)。

然后就是开车过去,结果一路没睡的娃,快到的时候又睡了。

到了之后,把娃抱到床上让我对象看着,我又卸东西,然后把车留在那儿,我又打了个顺风车回城。

但是令我郁闷的是,我打得是1人拼座,如果后续能拼单成功我还能便宜一些,拼不成功就是原价。

结果那辆车除去司机算上我拼座了3个乘客,但是最后下车我手机显示我是1人独享。

后面上车的人当着我的面报的手机尾号司机在手机上输入确认的,我怀疑应该是司机用了3个账号分别接的单,所以她拿到了每个人的独享价。

而且途中还要再接第四个人,司机和人家说自己车上有3个亲友同行问人家介不介意,人家直接拒了,她还不乐意,搁那儿嘟囔,我感觉她有点太不道德了,所以我下车之后直接评价选了私下拼单跨平台拼单。

等我到家都4点多了。

自然打破了我留给自己半天干活和做家务的时间,结果做完工作天就黑了,就只能简单打扫一下作罢。

总结

这个五一没干啥大事儿,也没去什么地方,但其实放假之后能安安稳稳的在家休息几天,啥事儿不干,就吃吃喝喝睡睡,醒了就刷刷手机看看小说啥的。

不仅身体能得到放松,口腹之欲得到满足,精神也能从疲惫的工作状态脱离出来,好好放松放松。

随着年龄上来之后,感觉去景点反而并不歇乏。

不仅一天逛下来身体和双腿酸疼,白天还人挤人,到处都是人声噪音和其他噪音。还有可能就很热啥的。还要走马观花的去看所谓的景点或者风景。

反正一天逛下来,不仅身体没歇乏,精神上更累。

相比之下,我感觉先好好的歇上两三天,然后附近找个不累的景点或者活动以放松状态逛逛玩玩反而更解压。

真正上班时间长之后,才知道大块大块的休息时间是多么的宝贵(没工作被迫休息不算)。

喜欢一个慵懒的五一这篇文章吗?您可以点击浏览我的博客主页 发现更多技术分享与生活趣事。

  •  

Python中的异常

未完待续

1.错误、异常和断言

  • 错误 语法错误,语法检查都过不去
  • 异常 程序语法是正确的,运行到一些地方,也可能会出现错误,运行时检测到的错误就称之为异常
  • 断言 用于判断一个表达式,在表达式条件为false的时候,触发异常

2.异常处理

java中的异常处理包含:try => catch => finally几个步骤,python中的异常处理则是:try => except => else => finally

2.1 try/except

except捕获异常

try:    i = 1 / 0    print("正常执行")except:    print("发生异常了") #发生异常了

2.2 try/except/else

try内没有异常,else块内代码才会执行,else必须放在所有except的后面

__doc__ 是 Python 中每个对象都可能具有的属性,用于存储该对象的文档字符串

try:    result = 3 / 0except ZeroDivisionError as e:    # 打印:异常名 + 异常描述    print(f"1.异常名:{type(e).__name__},异常信息:{e.__doc__}")except (RuntimeError, TypeError, NameError) as e:    print(f"2.异常名:{type(e).__name__},异常信息:{e}")except:    print("Unexpected error")else:    # try内无异常时执行的固定逻辑    print("try内无异常时执行的固定逻辑")    print(result)
1.异常名:ZeroDivisionError,异常信息:Second argument to a division or modulo operation was zero.

如果改为result = 3 / 1,则输出

try内无异常时执行的固定逻辑3.0

2.3 try/except/finally

无论try内是否出现异常,异常是否被捕获,finally块内语句都会执行

try:    result = 3 / 0except ZeroDivisionError as e:    print(f"异常名:{type(e).__name__},异常信息:{e.__doc__}")else:    print(result)finally:    print("finally")
异常名:ZeroDivisionError,异常信息:Second argument to a division or modulo operation was zero.finally

如果改为result = 3 / 1,则输出

3.0finally

⚠️ finally一定会执行,所以finally中要谨慎访问可能因异常导致不存在的变量

try:   result = 3 / 0except ZeroDivisionError as e:   print(f"异常名:{type(e).__name__},异常信息:{e.__doc__}")else:   print(result)finally:   print(result) # ❌ NameError: name 'result' is not defined   print("finally")

2.4 总结

  • try 执行可能产生异常的代码
  • except 发生异常时执行
  • else 没有发生异常时执行
  • finally 有没有异常都会执行

3.异常的产生

3.1 raise抛出

def int_add(x, y):    if isinstance(x, int) and isinstance(y, int):        return x + y    else:        raise TypeError("参数类型错误")print(int_add(1, 2))  # 3print(int_add("1", "2")) # TypeError: 参数类型错误

3.2 assert断言

语法:assert [表达式], "异常信息",表达式一旦我们的断言的预期不符,自动抛出AssertionError

def divide(a, b):    assert b != 0, "断言触发,除数不能为0"    return a / bprint(divide(10, 2))  # 5.0print(divide(10, 0)) # AssertionError: 断言触发,除数不能为0

assert本质上就是简化版的raise,多用于日常开发测试,在生产环境中,应当使用raise,可以通过执行带参数的python -O ***.py命令,屏蔽掉代码中全部的assert语句,直接跳过,不判断不抛异常

4.自定义异常

继承Exception自定义异常,自定义一个属性value

__init__(self, value)会覆盖掉父类Exception的__init__()

class MyError(Exception):    """我是异常doc"""    def __init__(self, value):        self.value = value    def __str__(self):        return repr(self.value)if __name__ == '__main__':    try:        raise MyError('fuck')    except MyError as e:        print(f"触发自定义异常:{type(e).__name__},异常描述:{e.__doc__},异常信息:{e.value}")
触发自定义异常:MyError,异常描述:我是异常doc,异常信息:fuck
  •  

五一假期返乡路

 期待已久的五一假期终于到了,上午把手头着急的工作赶紧处理掉,下午没去公司,中午吃完饭就赶紧出去去买东西了,媳妇昨天就安排好回去要给她带好吃的,直接列了个清单过来,有需求就好说,让我猜着买那才真为难,有目标的采买简单,一个小时采买结束。
 回去的高铁票实在是抢不到,五天假期回去的人应该还是挺多的,就找了个顺风车,刚采买回来顺风车那老乡就喊我过去他那边,我在的位置离他那里十几公里,说是过来接我再回去有一点点不顺,给我发了他的位置,我看他那边离地铁口也挺近,干脆坐个地铁晃悠过去得了,跟他汇合差不多五点半,然后就出发接另外两个乘客,我是没有走线上,联系上之后直接线下交易了,车主那边到手能少扣个几十块钱吧。
 随后接的第二个乘客,我们从市区里面赶到了肥东县去接他,约好的六点半到他那边,结果一等二等,快一个小时才过来,车主给他打了四五个电话,每次都是十分钟十分钟,不守时的人真的很讨厌,然后还带了一窝打鸡仔,放在车里面,全是鸡味,又回来给我熏得不行。。。
回程路上七点的夕阳景色
 结果第三个乘客又回来了市区,我合着跟着车白折腾两三个小时,这车主也不会安排啊,好在第三个女乘客比较守时,不到八点全部接上出发,上来一股香水味,本来挺讨厌浓浓的香水味的,这会能掩盖一下鸡仔的味道,也没那么难闻了。
 出发高速上也是走一路堵一路,赶着晚上回的确实不少,中间路上看着司机好像有点小累,中间还帮着开了三个小时。
 下高速之后又挨个送,我最后回到家已经凌晨三点半了,算了一下时间,从下午五点半坐上车,整个折腾了10个小时,真受罪啊,难受,谁让没抢到高铁票呢。

  •  

旧物堆里的杭州公交卡,揭开十几年前的冤大头记忆

  整理旧物,发现几张早年在杭州的公交卡。

杭州公交卡

  离开杭州十多年,早已对杭州公交没什么印象了,反而是对杭州的公共自行车印象深刻。

  那些年,这公交卡,除了坐公交,还能骑公共自行车,那是古早版的共享单车,官方的,比OFO的出生还早。

杭州公共自行车

  我使用过多次,但老实说,使用体验并不太好。现在习惯了方便的美团单车的人,无法体会当年的公共自行车多不方便。

  首先要有公交卡,且卡内余额要足够,忘了当时好像是要求必须有50或是100的。

  最大的问题是,车位是固定桩的,只能从固定桩位取车,到固定桩位还车。

  这导致几个问题:建设成本高、覆盖不足、调度低效,借车难、还车难。毕竟建设固定桩位的停车网点要开挖施工埋线,费钱费力又占地。网点密度和公交站差不多,但每个网点的桩位非常有限,多的几十个,少的十个左右。我住的那个片区,住了不下千人,也就几十辆公共自行车的桩位。车辆调度效率严重跟不上,多数时候,出发点的桩位空空如也、没车可借,目的地的桩位满满当当、无法还车。我经常要骑着车去附近找还车点,或者在还车点等着别人来骑走一辆车之后,我才能有机会把自己的车还进去。像西湖这样热门的地方,你去的时候,大概率等半小时也难以找到可以停放的空桩。

杭州公共自行车

  我有一次晚上骑车回家,家附近的还车点已经满位了,无法还车,等了一会也没人来骑走其它车。住的居民区,晚上都是回来的人多,车位一般都是满的,即使去附近站点找,也很难找到空车位。于是我先把自行车停在家楼下,想着晚一点再下来看看有没有腾出来的空车位,然后就忘了这事,第二天早上才去还车,卡上被扣了好几十块钱,当了一回冤大头。

  如果要退卡,需要去一个很远的公交卡服务点,来回就得一小时,再加上排队和办理,我实在嫌麻烦,那个押金就算了。以至于旧物堆里还能见到这些旧卡。

  相比而言,现在的美团单车,就先进了100倍。感谢市政公共自行车、OFO、小蓝单车、摩拜等共享自行车先驱/先烈所做的探索。

美团单车

  •  

别再写改名脚本了,一个 Vite 插件搞定压缩、校验、自动哈希命名vite-plugin-pack-orchestrator

别再写改名脚本了,一个 Vite 插件搞定压缩、校验、自动哈希命名vite-plugin-pack-orchestrator,市面上已经有一些 Vite 打包插件,比如 vite-plugin-zip-pack,vite-plugin-compress等,能用,但总差那么点意思 — 大多只支持 ZIP,功能也比较单一。

  •  

Keep Moving

昨天晚上跳绳的时候,终于用的跳绳,另外一根绳子也断掉了。这个跳绳用到现在,也的确是不容易了。

上周的时候就发现摇起来不是很顺滑了,对象说给买新的,这两天应该就能到。然而,等的桃花都谢了,绳依然没到。只好拆掉断掉的部分,重新插回去。不过这么一来,鉴于之前左手一侧的已经断过一次了。这次右手的断了,正好调整完就一样长了。

坚持一项运动,自己也没想到能坚持这么久,甚至让跳绳成为一种习惯。

在跳绳之前,也有几年不曾进行任何的运动。之前,总是有无数的接口,跑步机被卖了,不想出去运动。

等真正的下定决心之后,发现事情似乎也没那么困难,事情的开始,总是有些艰难,有些痛苦,有些抵触。然而,当一切成为习惯之后,发现也没那么困难了。只是有的装备来的稍微晚了点。

前段时间开始买鲨鱼裤,之所以买这个,主要是以前太胖真的穿不了,另外一个原因是,小腿在运动的时候缺少束缚力,运动完之后总是觉得没那么舒服。

然而,这女装啊,尺码跟尺码差距还是有些大。同样是xl,一条穿着非常合适,另外一条就穿着有点大。

xl跟xl还是不一样的,这就挺离谱的。

至于那个跳绳为什么没到,对象发了条消息说快递被拦截了:

这商家的操作,是脑子进水了?

坑爹玩意儿,简直是我减肥路上的绊脚石。

  •  

成绩是 B,口气 A+

  此文记录我和女儿周末生活的一天。

  女儿中考临近,还有半个月就要考英语听力口语了,周天上午我跟她一块去新东方线下教室听了一节英语课。

  教室里为每位同学安排了2个桌位,家长可以坐进来听,但实际上几乎没有家长进来,我是唯一坐进来听课的家长,像个异类。

  教室虽然不大,老师也在用麦克风讲课。老师发音标准,讲的做题技巧也挺实用。从课堂回答问题的状况判断,来听课的学生英语发音都挺不错的,听完短文都能复述关键信息,比我强。

教室

  全程2小时,我听的挺仔细的,本来前一天晚上没睡够,上午还有点困,听了一会儿课反而精神了,但我至少3次看见女儿在打呵欠。

  课后走在回家路上,女儿对课堂的评价是:老师讲的很没意思。

  这是什么?成绩是 B,口气 A+,谦卑不了一点。

  看他们现在的学习条件,是真羡慕啊,不禁想起自己当初学英语,老师提一台磁带机放在讲台上,每播放一句老师要按一下暂停键,学生跟着读,老师再按播放。但大部分课程内容没有录音,只是跟着老师读,课后就没有听录音的机会了。直到大学,我也没有见到英语课堂上直接播放视频的。

  下午她继续肝她的各科作业,一肝就是一下午,再加一晚上,真是慢。

  数学题遇到一个我也不会的题,想了好一会没有头绪,拿笔推演了几分钟也无果。

数学题

  问了豆包,豆包说了一通完全错误,还说缺少条件,再问了DeepSeek,DeepSeek一下就回答正确了,就是比“不绕圈子”的豆包强。看完发现,其实也并不难,只是我已经退化了。恐怕高中阶段,我就完全没有能力跟她探讨习题了,轻松倒是轻松了,但也好像有些微的失落。

  下午六点半我才打开朴朴买菜,菜七点钟送过来,我开始准备煮面。虽然只是煮了个面,但是备菜和煮面一起,我也磨磨唧唧了40分钟,那个青稞面我感觉好难煮啊,中途尝了两次都还是硬心的,不得不加水继续煮。我跟女儿各一满碗面,有瘦肉,有鸡蛋,还有青菜,我感觉略咸了一点,女儿说挺好吃的,可惜不记得拍个照。

  她喜欢跟我Battle古文古诗,动不动来一段《岳阳楼记》和《出师表》,我只记得零散的句子了,被她碾压,碾压我让她挺有成就感吧哈哈,她还给我讲了《岳阳楼记》那几个段落之间的逻辑关系,对我来说挺新鲜,我以前倒是没这样想过。好吧,我要把背诵《岳阳楼记》和《出师表》加入到我的长长的 TODO List 里面去,这是来自老父亲的倔强。

  •  

浅谈前后端分离系统的SEO优化

开发一个系统,不管是从头开始,还是在已有系统上二次开发,从来都不是一蹴而就的事情。在上线以前总觉得已经做够了足够的测试,但是在上线之后还是会出现各种各样的问题。

有的问题,如果是新系统完全可以避免,正是由于是在已有系统上开发的为了兼容wp才会引入一系列的问题,这类问题主要是wp原生的一些机制兼容问题导致的包括但不限于:

1.wp固定连接的兼容

2.shortcode的解析处理

3.wp资源文件与新系统资源文件的路径兼容处理

4.wp启用插件的功能实现,邮件通知、micro-post、邮件发送、邮件模板等等

5.其他的未知问题

也有一部分是新系统天生的缺陷:seo不友好,搜索引擎爬虫无法获取网页内容,毕竟robot不会执行js,这个是前后端分离系统的必然缺陷。

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <link
      rel="icon"
      href="https://zhongxiaojie.cn/wp-content/uploads/2026/01/uugai.com-166111691272754-100x100.png"
      sizes="32x32"
    />
    <link
      rel="icon"
      href="https://zhongxiaojie.cn/wp-content/uploads/2026/01/uugai.com-166111691272754-200x200.png"
      sizes="192x192"
    />
    <link
      rel="apple-touch-icon"
      href="https://zhongxiaojie.cn/wp-content/uploads/2026/01/uugai.com-166111691272754-200x200.png"
    />
    <meta
      name="msapplication-TileImage"
      content="https://zhongxiaojie.cn/wp-content/uploads/2026/01/uugai.com-166111691272754-300x300.png"
    />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta
      name="description"
      content="爱好广泛的女王 独立APP开发者 AI修理师 爬虫砖家 逆向工程师 人工智能 全栈工程师"
    />
    <meta
      name="keywords"
      content="人工智能,机器学习,ml,逆向分析,信息安全,物联网,ida,uniapp,python,爬虫,妹子图,秀人集,java,vue"
    />
    <meta
      name="theme-color"
      content="#ff4f87"
    />
    <link
      rel="manifest"
      href="/manifest.json"
    />
    <link
      rel="stylesheet"
      href="/vendor/enlighterjs.min.css"
    />
    <link
      rel="stylesheet"
      href="/vendor/simple-microblogging.css"
    />
    <title>obaby 𝐢‍𝐧⃝ void - 程序媛 / 独立开发者 / 智商不稳定的女神经</title>
    <script type="module" crossorigin src="/assets/index-DFHpxK1A.js"></script>
    <link rel="stylesheet" crossorigin href="/assets/index-CKljzL1r.css">
  </head>
  <body>
    <div id="app"></div>
    <script
      defer
      src="/vendor/enlighterjs.min.js"
    ></script>
    <script defer src="/vendor/obaby.js"></script>

  </body>
</html>

 

当然有人会比较在意这个东西,不是说这个东西不对。可能是自己没那么在乎吧,之前就曾经收到过数次关于seo友链不显示的问题,上次是搞页面静态化。

其实,在我的博客添加的友链,也并不是全部都不显示,毕竟还有其他的域名,zhongxiaojie.com 以及 oba.by等还是会显示完整的友链信息,这两个域名并没有切换到新的前后端分离的系统。所以,我博客的友链,相当于数个站都给友链做了多次链接,我不知道这个东西对于seo有没有作用,至于是有好处,还是有坏处,我并不清除,我自己并不是那么关注所谓的seo。如果觉得这样反而会出问题的,欢迎反馈,我会及时删除相关链接哈。

当然,这个东西有办法解决吗?答案自然是有,至于解决方法,那就是继续回归服务器渲染。

这解决方案真的是简单粗暴啊,合着这折腾来折腾去,又要弄回服务器渲染,这辛辛苦苦四十年,一夜回到解放前?

采用这种简单粗暴的方法来解决seo问题,显示不是本仙女的作风。既然是针对搜索引擎的,那就直接对搜索引擎做单独的处理就完了。检测ua,如果是收缩引起的ua返回服务器渲染之后的内容,如果是正常浏览(搜索引擎爬虫意外的ua)返回前后端分离的内容。

要实现服务器渲染,基于vue的可以参考nuxt.js(百度百科):

Nuxt.js是由NuxtLabs团队于2016年10月推出的基于Vue.js的开源Web框架,采用MIT License授权。该框架灵感来源于Next.js,Nuxt采用了约定俗成的规范以及一种明确的目录结构,以实现对重复性任务的自动化处理,并使开发人员能够专注于推进新功能的开发。 [2] [5] [8]
Nuxt默认内置服务器端渲染(SSR)功能、支持静态站点生成(SSG)和单页面应用(SPA)三种部署模式,可通过”nuxt generate”命令生成预渲染HTML文件实现静态化部署 [5] [7]。采用模块化架构提供50多个扩展模块,支持TypeScript类型安全、推送和现代化开发工具链 [4] [6]

接下来也就简单了,创建nuxt项目,实现与frontend同样的页面路由和相关的页面文件布局。接口可以直接复用当前的接口,

配置openresty的处理逻辑:

# -----------------------------------------------------------------------------
# Dynamic Rendering(SEO):爬虫 UA → Nuxt SSR;普通用户 → 现有 SPA
# - Nuxt SSR 服务建议监听 127.0.0.1:3000(可按需调整)
# - ?__ssr=1 可强制走 SSR(方便自测/排障)
# - 仅对“页面路由”生效,不影响 /assets、/vendor、/bp-api、WP 后台等
# -----------------------------------------------------------------------------
set $bp_force_ssr 0;
if ($arg___ssr = "1") {
    set $bp_force_ssr 1;
}

set $bp_is_bot 0;
if ($http_user_agent ~* "(googlebot|bingbot|baiduspider|yandexbot|duckduckbot|slurp|sogou|360spider|bytespider|petalbot|facebookexternalhit|twitterbot|rogerbot|ahrefsbot|semrushbot|mj12bot)") {
    set $bp_is_bot 1;
}

location @nuxt_ssr {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Uri $request_uri;
}

# 418 跳转技巧:在页面路由里 return 418 → error_page 转到 @nuxt_ssr
error_page 418 = @nuxt_ssr;

启动之后就可以查看服务器渲染的页面了:

当然,这个实现方法的缺点就是得完全复刻frontend的相关路由和页面,优点就是不用关注原来的系统实现逻辑,哪怕爬虫seo系统出问题也不会影响现有的系统运行。

 

  •  

U-Boot、内核移植与根文件系统构建(BeagleBone Green Gateway&amp;AM335X)

前言

本文档主要记录了基于 BeagleBone Green Gateway 开发板(核心芯片为 TI AM3358)的 U-Boot、内核移植与根文件系统构建全流程。内容涵盖了在 Ubuntu 下搭建交叉编译环境、编译 Linux 4.19 内核U-Boot、设备树 的详细步骤,在移植过程中遇到的编译器兼容性问题(如 GCC 10+ 报错、架构检测失效)及其修复方法。

一:安装交叉编译工具链

关于 安装 ARM 交叉编译工具链 的教程笔记。

  • 宿主机 (Host): 你的电脑 (Ubuntu x86_64)。
  • 目标机 (Target): 开发板 (AM335x ARMv7)。
  • 交叉编译器: 在 x86 上运行,但生成的是能在 ARM 上运行的可执行代码。

方法 A:使用 APT 包管理器 (最简单,推荐)

这是最快的方法,直接从 Ubuntu 软件源安装。

1. 更新源

sudo apt-get update

2. 安装基础构建工具 (Host 工具)
编译内核不仅需要交叉编译器,还需要主机端的 gcc、make、bison 等工具。

sudo apt-get install build-essential bison flex libssl-dev lzop u-boot-tools device-tree-compiler

3. 安装交叉编译器
安装 Hard Float (hf) 版本的编译器,适配 AM335x 的硬件浮点单元。

# gcc (C编译器) 和 g++ (C++编译器)
sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf

方法 B:手动安装 Linaro 版本 (指定版本,进阶)

如果你需要特定版本的 GCC (例如为了避开 GCC 10+ 的语法检查严格问题,或者为了匹配旧内核),可以使用此方法。

1. 下载编译器包
前往 Linaro 官网下载 (例如 GCC 7.5):

  • 文件名示例:gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz

2. 创建目录并解压
通常安装在 /usr/local/arm 目录下。

sudo mkdir -p /usr/local/arm
# 假设压缩包在当前目录
sudo tar -vxf gcc-linaro-7.5.0-*.tar.xz -C /usr/local/arm/

3. 配置环境变量 (PATH)
为了让终端能在任何地方找到这个命令,需要修改 ~/.bashrc

vim ~/.bashrc

在文件最末尾添加:

# 注意将下面的路径换成你实际解压后的文件夹名
export PATH=$PATH:/usr/local/arm/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin

4. 生效配置

source ~/.bashrc

验证安装

无论用哪种方法,安装完成后必须验证。

1. 检查版本

arm-linux-gnueabihf-gcc -v

2. 预期输出

  • 最后一行应显示 gcc version x.x.x ...
  • 如果提示 command not found,说明没安装好或者环境变量没配对。

后缀的区别 (坑点预警)

在下载或安装时,你会看到不同的后缀,千万别选错

后缀名全称含义适用场景
gnueabihfHard Float使用硬件 FPU (浮点单元) 进行运算AM335x (你的板子), Raspberry Pi 等现代 ARM
gnueabiSoft Float使用软件模拟浮点运算 (速度慢)非常古老的 ARM9 或无 FPU 的芯片
aarch64ARM6464位 ARM 架构树莓派4 (64位系统), 手机芯片, RK3399

常用命令别名

为了避免每次编译都要输那一长串前缀,可以在 ~/.bashrc 里加个变量:

# 在 ~/.bashrc 末尾添加
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm

这样以后编译内核只需输:

make zImage  # 甚至不用输 ARCH 和 CROSS_COMPILE,只要 Makefile 支持读取环境变量

二: U-Boot编译和移植

目标:制作一张能引导开发板启动的 SD 卡。

编译 U-Boot

  • 作用:初始化硬件(CPU, DDR),为加载内核做准备。
  • 命令
# 获取
git clone https://github.com/beagleboard/u-boot.git
cd u-boot
git checkout v2021.10-bbb.io-am335x # 切换到对应分支

cd /home/dq/linux/uboot/beagleboard-u-boot

# 清理旧编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
# 配置 (AM335x 通用配置)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- am335x_evm_defconfig
# 编译 (-j4 使用4核加速)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4

产物MLO (一级引导), u-boot.img (二级引导)。

  • ROM Code(芯片出厂自带):寻找 MLO 并加载到 SRAM。
  • MLO:初始化 DDR3,寻找 u-boot.img 并搬运到 DDR3。
  • u-boot.img:运行命令行,加载 Kernel(Linux 内核)。
  • Kernel:挂载 Rootfs(根文件系统),启动用户空间。

SD 卡分区与烧录

  • 作用:将 SD 卡分为 Boot 分区 (FAT32) 和 Rootfs 分区 (EXT4)。
  • 命令

sd卡格式化脚本

#! /bin/sh
# mkcard.sh v0.5
# (c) Copyright 2009 Graeme Gregory <dp@xora.org.uk>
# Licensed under terms of GPLv2
#
# Parts of the procudure base on the work of Denys Dmytriyenko
# http://wiki.omap.com/index.php/MMC_Boot_Format

export LC_ALL=C

if [ -z `which bc` ]; then
    echo "no bc binary found"
    exit 1;
fi

if [ $# -ne 1 ]; then
    echo "Usage: $0 <drive>"
    exit 1;
fi

DRIVE=$1

# 清除 SD 卡头部信息
dd if=/dev/zero of=$DRIVE bs=1024 count=1024

# 获取磁盘大小
SIZE=`fdisk -l $DRIVE | grep Disk | grep bytes | awk '{print $5}'`

echo DISK SIZE - $SIZE bytes

# 计算柱面
CYLINDERS=`echo $SIZE/255/63/512 | bc`

echo CYLINDERS - $CYLINDERS

# 使用 sfdisk 进行分区
# 分区1: 63MiB, 类型 0x0C (FAT32 LBA), 可启动 (*)
# 分区2: 4GiB (或剩余空间), 类型默认 (Linux)
sudo sfdisk $DRIVE << EOF
8192,63MiB,0x0C,*
137216,4GiB,,-
EOF

sleep 1

# 格式化分区 1 为 FAT32 (boot)
if [ -b ${DRIVE}1 ]; then
    umount ${DRIVE}1
    mkfs.vfat -F 32 -n "boot" ${DRIVE}1
else
    if [ -b ${DRIVE}p1 ]; then
        umount ${DRIVE}p1
        mkfs.vfat -F 32 -n "boot" ${DRIVE}p1
    else
        echo "Cant find boot partition in /dev"
    fi
fi

# 格式化分区 2 为 ext4 (rootfs)
if [ -b ${DRIVE}2 ]; then
    umount ${DRIVE}2
    mkfs.ext4 -L "rootfs" ${DRIVE}2
else
    if [ -b ${DRIVE}p2 ]; then
        umount ${DRIVE}p2
        mkfs.ext4 -j -L "rootfs" ${DRIVE}p2
    else
        echo "Cant find rootfs partition in /dev"
    fi
fi
cd /home/dq/linux/tool
# 运行脚本格式化 sdb (注意:你的设备号是 sdb)
sudo ./mkcard.sh /dev/sdb

# 挂载并拷贝引导文件
sudo mount /dev/sdb1 /mnt/boot  # (或依赖自动挂载 /media/dq/boot)
sudo cp ../../linux/uboot/beagleboard-u-boot/MLO /mnt/boot/
sudo cp ../../linux/uboot/beagleboard-u-boot/u-boot.img /mnt/boot/

三:Linux 内核编译与部署

目标:编译内核,并解决 U-Boot 网卡驱动不兼容的问题(采用“SD卡存内核 + NFS存文件系统”的混合策略)。

编译内核与设备树

  • 作用:编译操作系统核心 (zImage) 和硬件描述文件 (.dtb)。
  • 命令
# 获取
git clone https://github.com/beagleboard/linux.git --depth 1 -b 4.19.94-ti-r42

cd /home/dq/linux/tool/linux-4.19.94-ti-r42/
# 配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bb.org_defconfig
# 编译内核、设备树和模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage dtbs modules -j4

生成三个部分

  • zImage: 内核镜像。位于 arch/arm/boot/zImage
  • dtbs: 设备树二进制文件。位于 arch/arm/boot/dts/am335x-bonegreen-gateway.dtb
  • modules: 驱动模块。它们被编译成了分布在各个目录下的 .ko 文件。

TFTP 配置

目的:uboot启动后通过tftp拉取镜像zimag和设备树dtb

  • 无需手动拷贝:Ubuntu 编译生成的产物直接放在 TFTP 根目录,开发板重启时自动从网络拉取。
  • 保护 SD 卡:减少对物理存储介质的读写,避免频繁挂载导致的 FAT 分区损坏。
  • 纯内存运行:内核和设备树直接加载到 RAM,不占用 SD 卡空间。

Ubuntu 配置 TFTP

1. 安装服务程序

使用最稳健的 tftpd-hpa 包:

sudo apt-get update
sudo apt-get install tftpd-hpa tftp-hpa

2. 创建工作目录并赋权 (关键)

默认路径通常在 /var/lib/tftpboot,但为了开发方便,建议修改到用户目录下,并赋予最高权限防止“Permission denied”。

# 1. 创建目录 (建议放在你的工作区)
mkdir -p /home/dq/linux/tftpboot

# 2. 赋予最高权限 (允许任意读写,开发环境图方便)
chmod 777 /home/dq/linux/tftpboot

3. 修改配置文件

编辑 /etc/default/tftpd-hpa,将默认配置修改为指向你的新目录。

sudo vim /etc/default/tftpd-hpa

修改后的完整内容:

# /etc/default/tftpd-hpa

TFTP_USERNAME="tftp"
# 修改 1: 指向你刚才创建的目录
TFTP_DIRECTORY="/home/dq/linux/tftpboot" 
TFTP_ADDRESS=":69"
# 修改 2: 增加 --create 允许上传(可选),保持 --secure
TFTP_OPTIONS="--secure --create"

4. 重启服务并生效

每次修改配置文件后,必须重启服务:

sudo service tftpd-hpa restart

5. 避坑指南:防火墙 (必做)

TFTP 使用 UDP 69 端口,极易被 Ubuntu 防火墙拦截。开发阶段建议直接关闭,或者放行端口。

# 方案 A: 简单粗暴关闭防火墙 (推荐)
sudo ufw disable

# 方案 B: 仅放行 UDP 69
sudo ufw allow 69/udp

6. 本地自测 (验证服务是否存活)

在烧录板子前,先自己连自己测试一下,确保不是 Ubuntu 内部的问题。

# 在 tftpboot 目录下创建一个测试文件
touch /home/dq/linux/tftpboot/test.txt

# 回到家目录尝试下载
cd ~
tftp 127.0.0.1
tftp> get test.txt
tftp> q

# 检查是否下载成功
ls test.txt

如果 ls 能看到文件,说明服务端配置完美!


备选方案 (SD卡部署)

  • 现状:你的 U-Boot 探测不到 PHY 地址 1,网络链路(Link Up)无法建立。
  • 代价:每次修改内核或设备树,必须先进入 Linux,手动挂载 /dev/mmcblk0p1/mnt/sd_boot,然后 cp 覆盖,效率较低
  • 原因:U-Boot 无法通过 TFTP 联网下载,所以直接把内核拷进 SD 卡让它读取。
  • 命令
# 假设 SD 卡 boot 分区挂载在 /media/dq/boot
sudo cp arch/arm/boot/zImage /media/dq/boot/
sudo cp arch/arm/boot/dts/am335x-bonegreen-gateway.dtb /media/dq/boot/
sync  # 同步数据,防止丢失

PS: 后面替换设备树和系统镜像可以通过将sd卡挂载到开发板,然后通过nfs共享文件,将其拷贝到sd卡boot分区


开发板配置

设置产物自动归档
在编译脚本末尾或手动执行,将产物推送到 TFTP 目录(假设为 /home/dq/tftpboot):

cp arch/arm/boot/zImage /home/dq/tftpboot/
cp arch/arm/boot/dts/am335x-bonegreen-gateway.dtb /home/dq/tftpboot/

U-Boot 配置指令 (开发板端)

在 U-Boot 命令行设置网络参数及自动化命令:

  1. 设置网络 IP
setenv ipaddr 192.168.10.13       # 开发板 IP
setenv serverip 192.168.10.200    # Ubuntu 服务器 IP
saveenv
  1. 定义自动化启动脚本 (bootcmd)
# 逻辑:从网络下载 dtb 到 0x88000000 -> 下载 zImage 到 0x82000000 -> 启动内核
setenv loadtftp 'tftp 88000000 am335x-bonegreen-gateway.dtb; tftp 82000000 zImage'
setenv bootcmd 'run loadtftp; bootz 82000000 - 88000000'
saveenv
  1. 正常工作时的现象

当一切配置正确且 PHY 地址匹配时,U-Boot 启动会显示:

link up on port 0, speed 100, full duplex
Using ethernet@4a100000 device
TFTP from server 192.168.10.200; our IP address is 192.168.10.13
Filename 'am335x-bonegreen-gateway.dtb'.
Load address: 0x88000000
Loading: ############  (此处会出现大量井号,表示数据高速传输)
done
Bytes transferred = 56234 (dbaa hex)

四:根文件系统构建以及NFS配置

目标:在 Ubuntu 上建立一个文件夹,让开发板通过网线把它当成“硬盘”来挂载,实现文件实时同步,同时加载根文件系统。

# 根文件系统下载
wget -c https://rcn-ee.com/rootfs/eewiki/minfs/debian-10.3-minimal-armel-2020-02-10.tar.xz

安装模块到根文件系统

❯ pwd
/home/dq/linux/nfs/rootfs/lib/modules/4.19.94

# 生成配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bb.org_defconfig

# 编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules -j4

# 安装
sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=/home/dq/linux/nfs/rootfs/ modules_install

Ubuntu服务端配置

  1. 安装 NFS 服务
sudo apt-get update
sudo apt-get install nfs-kernel-server
  1. 创建并准备根文件系统目录

假设你的根文件系统(Rootfs)存放在 /home/dq/linux/nfs/rootfs

PS: 不同根文件压缩包格式需要不同方式解压

# 1. 创建目录
mkdir -p /home/dq/linux/nfs/rootfs

# 2. 假设你在压缩包所在目录
# -x: 解压, -v: 显示进度, -J: 处理 xz 格式, -f: 指定文件
sudo tar -xvJf debian-10.3-minimal-armel-2020-02-10.tar.xz -C /home/dq/linux/nfs/rootfs/
  1. 配置共享权限 (exports)

NFS 的权限控制通过 /etc/exports 文件管理。

sudo vim /etc/exports

在文件末尾添加以下一行:

/home/dq/linux/nfs/rootfs *(rw,sync,no_root_squash,no_subtree_check)
  • 参数解析
  • rw:可读可写。
  • sync:同步写入(保证数据一致性)。
  • no_root_squash最关键参数。允许板子的 root 用户拥有 Ubuntu 侧对应的 root 权限。如果不加这个,板子无法以 root 身份运行,会导致权限报错。
  • no_subtree_check:禁用子树检查,提高稳定性。
  1. 重启并生效服务
# 重新扫描 exports 文件并使配置生效
sudo exportfs -arv

# 重启 NFS 服务
sudo service nfs-kernel-server restart
  1. 避坑指南:网络与防火墙

NFS 依赖多个端口(RPC),如果防火墙开启,挂载必然失败。

# 直接关闭防火墙(开发环境推荐)
sudo ufw disable

U-Boot设置

直接看第五阶段

五:U-Boot 启动配置

目标:设置 U-Boot 环境变量,启动系统。

TFTP可以使用

即第二阶段tftp开发板的配置

setenv ipaddr 192.168.10.13       # 开发板 IP
setenv serverip 192.168.10.200    # Ubuntu 服务器 IP
saveenv

# 逻辑:从网络下载 dtb 到 0x88000000 -> 下载 zImage 到 0x82000000 -> 启动内核
setenv loadtftp 'tftp 88000000 am335x-bonegreen-gateway.dtb; tftp 82000000 zImage'
setenv bootcmd 'run loadtftp; bootz 82000000 - 88000000'
saveenv

# 设置 NFS 挂载参数 (注意 IP 地址)
setenv bootargs 'console=ttyS0,115200n8 root=/dev/nfs nfsroot=192.168.10.200:/home/dq/linux/nfs/rootfs,v3,tcp ip=192.168.10.13:192.168.10.200:192.168.10.100:255.255.255.0::eth0:off'

# 启动
boot

TFTP无法使用

  • 作用:告诉板子“从 SD 卡读内核,去 192.168.10.200 挂载 NFS”。
  • 命令 (在串口终端执行):
# 1. 从 SD 卡读取内核和 DTB
setenv bootcmd 'load mmc 0:1 0x82000000 zImage; load mmc 0:1 0x88000000 am335x-bonegreen-gateway.dtb; bootz 0x82000000 - 0x88000000'

# 2. 设置 NFS 挂载参数 (注意 IP 地址)
setenv bootargs 'console=ttyS0,115200n8 root=/dev/nfs nfsroot=192.168.10.200:/home/dq/linux/nfs/rootfs,v3,tcp ip=192.168.10.13:192.168.10.200:192.168.10.100:255.255.255.0::eth0:off'

# 启动
boot

六:Hello World

目标:验证开发板镜像是否正常工作。

  1. 编写与静态编译
  2. 问题:动态编译报错 No such file (缺少动态库)。
  3. 解决:使用 -static 静态链接。
  4. 命令 (在 Ubuntu 端):
#include <stdio.h>

int main() {
    printf("--------------------------------------\n");
    printf("  Hello, BBGG! This is Gemini speaking. \n");
    printf("  NFS cross-compile works perfectly!   \n");
    printf("--------------------------------------\n");
    return 0;
}
cd /home/dq/linux/nfs/rootfs/home/debian
# 编写代码
sudo vim hello.c 

# 交叉编译 (静态链接)
sudo arm-linux-gnueabihf-gcc -static hello.c -o hello
  1. 板上运行
  2. 命令 (在串口终端):
cd /home/debian
./hello
  • 结果:成功输出 Hello, BBGG!...

补充

在嵌入式 Linux 开发中,解决 GCC 版本冲突(重定义错误)架构检测失败(ISB 指令错误) 是适配旧内核(如 4.19)的关键步骤。以下是针对这两处修改的笔记整理:


1. 解决主机工具编译冲突 (yylloc 多重定义)

背景:当 Ubuntu 宿主机的 GCC 版本 10 时,默认开启 -fno-common,导致内核自带的 dtc(设备树编译器)工具在链接时报错。

  • 修改文件:顶层 Makefile
  • 具体位置:搜索 KBUILD_HOSTCFLAGS 定义处(通常在 400 行左右)。
  • 修改内容
    在变量末尾手动添加 -fcommon 参数。
# 原代码:
KBUILD_HOSTCFLAGS   := -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 \
                       -fomit-frame-pointer -std=gnu89 $(HOST_LFS_CFLAGS) \
                       $(HOSTCFLAGS)

# 修改后:
KBUILD_HOSTCFLAGS   := -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 \
                       -fomit-frame-pointer -std=gnu89 -fcommon $(HOST_LFS_CFLAGS) \
                       $(HOSTCFLAGS)

2. 解决架构降级导致的汇编错误 (isb 指令不支持)

背景:内核通过 cc-option 测试编译器。在使用硬浮点(Hard-Float)交叉编译器时,由于测试命令缺少 FPU 参数导致测试失败,内核 Makefile 会误认为编译器不支持 ARMv7,从而自动降级到 ARMv5,导致不识别 v7 指令 isb

  • 修改文件arch/arm/Makefile
  • 具体位置:搜索 CONFIG_CPU_32v7 所在行(通常在 65 行左右)。
  • 修改内容
    废除自动检测逻辑,直接硬编码(Hardcode)指定架构为 -march=armv7-a
# 原代码:
arch-$(CONFIG_CPU_32v7)  := -D__LINUX_ARM_ARCH__=7 $(call cc-option,-march=armv7-a,-march=armv5t -Wa$(comma)-march=armv7-a)

# 修改后(删除检测函数,直接指定架构):
arch-$(CONFIG_CPU_32v7)  := -D__LINUX_ARM_ARCH__=7 -march=armv7-a
  •  

难熬的工作日常,风雨之后有彩虹吗

 最近两周实在是过得煎熬,月初假期之后连续几天阴雨连绵,9号上午,我的同事S突然给我发个消息说,我接下来可能要更忙了,然后就发过来几张在医院的检查单,说是由于下雨,在楼梯上摔倒了,导致脚踝两侧两处骨折。
 唉,真是想不到的事,不知道具体怎么摔的,也没好多问,直接摔两处骨折,还是有点严重的,上周几天就各种检查,后来说是要手术,上周二的时候安排了手术,然后观察了几天,上周末让回家修养了,出院之后还要再修养至少一个月。
 这一受伤不当紧,原本我俩干的事情,现在全都压到我头上来了,四五个项目同时进行,实在是有点焦头烂额,上周领导给我安排了个小弟,结果过来只会搞一些简单的文档类工作,我给他整好模板,copy但是能搞,但是这样好像并不会给我分担多少工作量啊,这几周固定都是每周两个操作,也就是要白天要沟通进展,准备方案,晚上要加班操作的话就要凌晨搞到四五点,实在是难顶。
 昨天快下班的时候领导H给我打电话说,下周要安排地市的另外一个兄弟过来支撑一个月,说是我们另外一个领导W跟他讲的我这边事情太多了,让安排一个人过来分担操作,还说我实在忙不过来要跟他讲。我心想,不是当时我跟你讲的时候了,我说我同事S要请假的话我这边一个人忙不过来,结果给我安排一个小白,我当时都讲了,不能分担操作,过来也没啥作用啊,到时候五月份我一周安排三个操作我自己也干不了啊,当时H还跟我讲,先干着,看让他搞一些简单的,现在又问我忙不过来怎么不跟他讲了。。。
 现在整得各地市各专业都人员紧张的一批,降岗降薪,另外还辞退几个,我宿舍这个室友之前通知的让干到三月底,然后四月初开始就和公司谈赔偿问题,来来回回四五个回合也没谈拢,我这个室友说是在这边七年多快八年了,想谈个N+1,公司本来只同意赔偿不到二分之一N,应该是上上周吧,我这个室友往公司寄了个起诉告知函,准备拿起法律武器搏一搏,结果上周末公司领导过来就找他一起又谈了一波,最后双方都让步了一点,说是赔了个½N多点,但是不到N,也算是解决了。
 然后我这个同事最近都在各种投简历面试,准备去往杭州,上海那边找个销售类岗位,一直也没啥进展,确实这年头合适的工作不好找。
 所以即使还在坑位里的牛马已经被压迫的这么狠了,估计也还狠不下心来离职吧,狠不下心来那还是没压迫到位,唉,走一步看一步吧,真到哪天离职了也未尝不是个机会。

  •