阅读视图

2025年终总结

时间 等不了人
生活中 一不留神
转个眼秒杀了
随秒针蒸发了
昨天变成了今天
过去变成了现在
未来刚才来
它从何而来
岁月 眨眼就过了
瞬间 就那样默默的
上一次看 才二十三

属于懒癌晚期了,12 月想着写年终总结,结果一直拖到 2 月底、、、

此外,真的感觉一年比一年过得快啊有木有

眨眼就24 岁了,到了本命年了,然而上一次 12 岁过年的记忆却一点也没有。先祝大家新年快乐吧~

2025 也算是人生中的一次转折点吧。换了一座城市,重新开始另一段求学之旅。

上半年依旧是在无聊且负收益的实习中度过,一直到 5 月底开始毕设答辩才结束。6 月基本上是最幸福的时候了,在学校的最后一个月,整体躺在宿舍打游戏,享受最后的大学时光,

暑假一晃而过,就到了开学的时候。下半年开始适应新的环境,结果吐槽到学期结束。

学校的宿舍满满的叙利亚风格,堪比战犯营,这一点是最受不了的。此外学院为了阻止学生出去实习,修改了奖学金政策,除了绩点,还需要去参加各种文娱活动、学术会议等,就连入学奖学金也降低了,属实是恶心到家了。

不过北京毕竟寸土寸金的,能有宿舍也算不错了。本来想着北京好找实习,特地选了这,结果学校放实习的导师寥寥无几,不过导师虽然研三才放人,但是平时对人也挺好,也算是不信中的万幸了。平时除了上课,就是在工位学点东西,顺便帮导师干干小活。每天中午起,健个身,下午去工位,凌晨回宿舍。每次感慨一天怎么过的这么快,仔细想想起床时候已经过了半天了。

期间一直在寻找就业方向,虽然一直打算干嵌入式,但是细分却不知道去做什么,后面独自摸索打算走驱动方向,并且跟着正点原子的教程学了下去,现在差不多也完结了。

后来想偷偷出去实习,可惜简历实在稀烂,回复基本寥寥无几,而且时间基本要求长期,本打算加上寒假实习 3 个月,结果一听实习时长直接拒了。此外,虽然北京互联网厂很多,但是电子类感觉还是南方深圳岗位多点,而且周围学历全是 211 起步,属实拼不过别人,哎。后面就妥协了,既然没法实习,就继续深耕,做点有含金量的项目吧。

这一年大模型的发展让我见识到了什么才叫 人工智能。这学期的课设等所有代码项目全部靠大模型完成。Gemini、Copilot、Chagpt 是用的最多的,幸亏前两个有学生认证优惠,也属于薅到羊毛了。现在如果离了这些,估计一点代码也写不出来了。

除了学习外,也开始探索北京这座城市,去了雍和宫、什刹海、圆明园、颐和园等。寒假前趁着放假人少,也和室友成功约上了清华,去参观了下,小时候的梦也算实现了一半,哈哈哈哈。北京确实是一个让人向往的地方,也难怪这么多北漂,不知道以后会不会成为里面的一员。

就这样,一年也就过去了,也到了我的本命年,不过本命年好像犯太岁,希望今年可以一帆风顺点。

  •  

实习小记

实习

从实习到现在,差不多两个月了。每天早上7点准时起来,然后挤地铁,八点多到医院。中午和同学一起吃饭,下午 4.30 准时下班走人。

日复一日,期间看完了python基础,做了个基于Django框架的网站项目,不过也是看着书照葫芦画瓢,了解了下基本框架。想着用于考研复试,可惜有没有机会都不知道。

2月4号回到医院,一个月轮一个岗,好在目前都挺闲,想着如果考不上,就直接找工作,于是看了本《C++ Primer》。然而随着考研出分日期越来越接近,已经完全看不下去了,就像一个等待行刑的罪犯一样,一点学习的念头都没有。

 

韩剧

周末无聊的刷着抖音,突然蹦出来一个04年的古着韩剧,名字叫《对不起,我爱你》

剧情简介:

车武赫(苏志燮饰)似乎是一个注定被上帝遗忘的人。刚出生就被父母抛弃的他从小受尽澳大利亚养父的虐待,直到流浪街头与韩裔女孩智英(崔汝珍饰)相遇后才以为终于找到了真情。虚伪的智英最后为了钱弃他而去,为了保护智英,一颗子弹却永远地留在了武赫的脑袋里。回到韩国后,武赫看到亲生母亲(李慧英饰)和弟弟崔允(郑敬淏饰)过着与自己截然不同的富裕生活,于是复仇之火在武赫心中熊熊燃起。在复仇的过程中,武赫爱上了与弟弟青梅竹马的恩彩(林秀晶饰),并为了她逐渐放弃了复仇的计划。最后为了拯救出车祸的弟弟,武赫不惜牺牲自己的生命捐出心脏,而失去此生最爱的恩彩最终也选择在武赫的墓前结束自己的生命。

看讲解挺有意思的,于是特地搜了下,发现全网都是战损画质,毕竟是04年,我也就早出生两岁。后来翻评论,得知B站有4K修复版,链接贴在这里了:【【4K】对不起我爱你】 https://www.bilibili.com/video/BV1QV1LYEErF/?share_source=copy_web&vd_source=9ab026981a5f26dc9103f8ba6b252720

怪不得女生都喜欢看韩剧,尤其是零几年的时候,现在终于是感同身受了。

剧情现在听起来挺狗血的,但是后劲还是挺大的,尤其是女主最后殉情的时候,害。 ps:恩彩太好看了吧

剧中的歌曲也非常nice,中日韩三个版本都有,这里贴个剧中的吧:https://music.163.com/#/song?id=5295207

现在真恨自己手贱,为什么特地找来看看,以至于现在都有点恍惚。在这里推荐下这部剧,如果太闲了,一定要看下~

 

健身

考研一年胖了十斤,也是这一年为数不多的收获了。由于天生的外胚体质,吸收不好,消耗还多,从小瘦到现在。

考研前也一直有想要健身的念头,奈何没有坚定的决心,不好意思在宿舍锻炼,也怕没有时间。更没有勇气走进健身房,毕竟细胳膊细腿的,害怕别人的眼光。

考研这一年,学校特地找了个大的自习室,装修成了健身房。正好考完研,于是踏出了人生中的第一步,去了学校健身房开始健身。

奈何好景不长,学生们陆续考完试,回家了,健身房开门时间也变短了,正好在我下班后关门。于是便在宿舍举起了哑铃,跟着网上三分化计划进行训练。期间买了米糊,用来补充碳水,进行增重。过年后又买了桶蛋白粉,补充蛋白质摄入。

记得第一次进入健身房,便开始猛练,当天还没啥反应,结果第二天变成了螳螂臂,手都伸不直,休息了四天才好。后来再去就好多了。期间右手腕也伤着了,一用力手筋就疼,于是便买了个护腕。幸运的是,付出有了点回报,手臂终于有了点形状。

 

吐槽

现在每天浑浑噩噩的,上班就是睡觉、刷抖音。唯一有点期望的就是回到宿舍进行健身了,然后就是趴在床上进行emo了。

不知道是因为受了那部韩剧的影响,还是因为要出分了,又或者想起某人了,最近总感觉一点精神都没有,有点人生没有意义的感觉。

不过这些应该也是正常现象吧,毕竟濒临毕业,马上结束仅剩的校园时光,正常人都会有的迷茫时刻。想起易中天形容刘备的那句话:

前途光明我看不见,道路曲折我走不完

哈哈,记得高中时候捧着太宰治的《人间失格》,看完后却也感觉不出什么。就如辛弃疾所说:

少年不识愁滋味,爱上层楼。爱上层楼,为赋新词强说愁。
而今识尽愁滋味,欲说还休。欲说还休,却道“天凉好个秋”!

尽管现在也是不识愁滋味,但是每个时间段,总有特定的困惑,对吧。

由于太过无聊,又想着博客好久没更新了,于是便写篇实习小记,正好又水了一篇文章

 

  •  

2024年终总结

又到了一年一度的年终总结时候了,博客最近的一次文章还是去年的年终总结,害,这一年过的太快了~

前言

今年发生许多事,但是又感觉啥事没有(废话文学)。如果将这一年形容为打游戏的话,主线就是考研,支线就是旅途上的各种趣事了。

下面就开始流水账了,也算是给这一年的交代

博客

今年一直忙着考研,基本没怎么打理博客,虽然也没啥好打理的。唯一一个让人心烦的就是又拍云的cdn被刷了两次吧。

一次是群体性的,好多博主被刷了,当时损失还小,几十块钱吧。经过这件事后长了记性,让又拍云客服设置了每日额度提醒。结果后来又被刷了一次,两百多,第二天才有提醒。找了又拍云客服算帐,最后给了二百多代金劵,但是欠的费用必须自己充钱补了。

经过这件事后,我把图片以及字体全部托管到github上了,速度堪忧,所以有的文章图片会显示失败,现在也还没想到什么有效的方法,估计后面可能把图片全托管在服务器上了。

由于忙着考研,也有单纯懒的原因,关于主题博客等相关问题,除了一开始回复了下,后面基本没回复过了,不过评论区也有好心人会回复这些咨询,这里感谢下他们。

除了这些外,一直有个想法,就是换个框架,把文章迁移到typecho上。后来由于迁移中出错,以及觉得太麻烦了,就放弃了,不知道后面会不会实现。

考研

今年考研人数继续下降,反之,考公人数再次增长。这个时代,似乎到处都是路,但是每条路上都挤满了人。

下面这些话属于我的无病呻吟了,哈哈哈,见谅~

为了缓解就业压力,研究生再次扩招,可能很多人觉得现在考研变简单了,但是却很少人知道扩招的名额基本给了保研。除了这个以外,扩招的学校基本都是些学院或者刚改为大学的学校。关于读双非研究生到底有没有用,谁也给不了正确的答案。

有人觉得考研就是逃避就业,继续呆在温室里。但是,如果有好的工作的话,谁会去考研呢~(学术追求等除外)。我想大部分人考研应该是为了能找个更好的工作吧。现在的就业形势,学历越来越不够用了。公司招的要么就是三年经验起步,要么就是非92不要,可能会略显夸张。

谈谈我的考研历程吧,这次纯是流水账,诉说下自己的苦,可以跳过~

寒假时候在家里背了会单词书,看了点数据结构,正式开始应该是从3月开始备考。当时每天课很多,基本都是找零碎时间去自习室学习,有时候还会没有位置。期间还忙里偷闲爬了泰山。

四月末,突然群体改考408(计算机专业统考),虽然我心仪学校并未改考,但是自命题的竞争压力前所未有的增大,为了缓解我永远学不好的数学,我决定学408。专业课从一本书换成了四本。所幸本科有学过这些科目,虽然是浅尝辄止,但也还算有所了解。五一貌似整个假期都是在自习室过的,以及一起备考的室友和朋友。

后来开始放暑假了,由于学校不给留校,本来打算和室友一起在外面租个房子,可惜没啥好的地方可租,以及考虑到房租费,于是作罢,将书全部带回家,开始了两个月的自学。暑假最快乐的时候应该就是学累了打nba2k的时候,可以暂时忘掉考研的压力。记得当时学习时偷偷刷会抖音都会有股罪恶感,哈哈哈。

九月回校时感冒了一星期,和阳了差不多,当时黑神话正火,于是室友和我在宿舍玩了四天的黑神话,当时也是因为来得早,自习室也没啥人,于是就松懈了会。感冒好了就开足马力学习了。这个月应该是我学习效率最高的时候,整天都沉浸在知识里,哈哈,有一点点夸张。

期间基本没啥课了,每天8:30到自习室,晚上10点回宿舍,偶尔休息一会,也只敢休息一上午。

到了十月,本想在自习室学足7天,后来朋友来找我玩,以及学的确实太累了,便开始三天打鱼,两天晒网。后面就是考研预报名了,已经感觉到时间的紧迫了,但是还是一点不慌张,一直到网上确认,仍然是乐呵呵的心态。甚至11月底还抽空去了洛阳,爬了老君山。

接着就是十二月了,要上战场了,看到到手的肖四,开始慌张起来,一切计划都打乱了。开始早起到自习室背书,一背就是一上午。晚上回宿舍后还要在楼梯口背会,虽然没啥效果,只是感动自己罢了。

由于在其他学校考,20号便带着行李到了订的酒店,幸亏订的早,不然这价格都不用考研了,两晚能够炒到1k,真够离谱的。到酒店后全是兴奋感,不知道是要解放了,还是啥,一下午就那样过去了。晚上看着记不住的肖四,一筹莫展,一直背到12点才上床,结果1点多才睡着。早上5点又起床背肖四,直到进考场。

第一场考政治,这时候身体开始分泌物质了,也点也不困,只是做完选择,浏览完大题后,突然感觉这些天努力白费了。背的全在材料里原来不是梗。也不能怪肖四肖八没用,只能说命题组你做个人吧。对着材料换着花样抄,最后也就憋出一点字就交卷了。

下午就是英二了,先写的作文,感觉还挺简单,等到做阅读理解的时候,开始犯困了,而且文章越看越懵,和往年真题卷做的感觉一点也不一样。最后交卷时候改了三四个选择。回到酒店后开始思考人生了,感觉这次是完蛋了,哎,想哭

第二天上午考的数二,考完都说简单,对比往年确实,但是打小数学就不好,出考场后就发现积分算错了,这下好了,本来想靠英语拉下数学的,结果双双坠落了,中午一点饭没吃,默默看着专业课,祈祷能拯救自己。

下午的专业课就是中规中矩了,还是有许多不会,已经麻木了,不知不觉就交卷了,整个考研也结束了。

回顾备考之路,总有说不出道不明的感觉,没人会关心你苦不苦,只有结果能说明,只希望我的结果能好一点。

努力了吗?确实努力了。只是面对考试,还是有股力不从心,一切白干的感觉。

不过这一年也收获了许多,在自习室认识了许多很好的人,收到了许多小零食,哈哈哈哈。这里还是很感谢考研这条路,让我走进了自习室,收获了比以往更多的东西。

偷闲

考研期间一共爬了两座山,分别是泰山和老君山。

泰山是三月爬的,当时刚开始准备考研。干大事前总得有个标志性开始,于是便决定爬个泰山,来表决考研的决心。

太多细节已经记不清了,由于cdn欠费,也只能扔一张图片了~

接着就是11月底的老君山。当时刚从自习室回来,吃个晚饭再继续学。结果室友刷到了老君山下雪的视频。以及大学生免门票,便来了场说走就走的旅行。结果路上发现人太多,已经取消了,真够离谱的。

决定去爬后,当场定了凌晨的火车,回自习室把书收了,就开始收拾东西,准备出发。

坐了7个小时的硬座,终于到了洛阳站。不过硬座是真的没有体验,太闹腾了。后来发现还要再坐两个半小时的大巴,才能到老君山,差点猝死过去。

到了老君山后,先做了一级索道,然后开始爬山。雪还没有化,路有点滑,全是冰雪,不过风景独美。到达山顶时,全是雾,以为要带着失望回去了,结果出太阳了,完整的见证了整个规程,太壮观了,照片确实拍不出风景的美。

202402

勇敢的人,先享受世界

等到下山的时候,已经快6点了,还要再坐大巴回市里。此时已经又累又虚,本来打算坐高铁,结果赶不上了,便买了卧铺回去。不过也幸亏没赶上,便去了洛阳城楼那游玩了一番。

小姐姐很飒,哈哈哈

实习

按照学校安排,考完研后就开始没有工资的实习了,预计到5月底才结束。期间还得忙毕设以及复试(如果有希望的话)

每天7点起,匆忙洗漱,买点早饭就赶地铁了。不过好在实习不是很忙,老师都挺好,可以忙些其他的。有时间得考虑找个兼职,赚点钱,哎~。都要大四毕业了,还没开始赚钱,太悲哀了

且行且珍惜,今年失去了非常好的人,原因是我,没办法,我就是一个很矛盾的人,希望大家要珍惜眼前人

预计今年6月毕业,写个大学四年的终结吧。总感觉大一开学还在昨天,然而现在宿舍已经走了两个人了。

此外,在自习室遇到许多朋友,被投喂了好多,哈哈哈。

祝6号楼自习室的朋友们,期末考试顺利,早日解放。研友们成功上岸,金榜题名!

希望3月份,我们还能在自习室相遇~

  •  

2023年终总结

2024

又到了写年终总结的时候了。

每天一记的日记我肯定坚持不下去,但是每年一次的年终总结,我应该能坚持下。

万卷古今消永日,一窗昏晓送流年

在万卷书中消磨自己整日时光,南窗下让自己生命的河流静静地流过。

这首诗句是陆游写的,用来感叹读书对时间的消磨以及对内心的充实。

非常贴合2023年我的校园生活(并不是指读了许多书,hh)

2023年,应该算是大学中最稳定的一年了,也是最充实的一年。

没有初入校园的懵懂,也没有大三后期对人生该驶向何处的迷茫。

我可以拥有一定的时间去探索感兴趣的方向,也可以选择去一些风景名地来开阔自己的内心。

青春没有售价,一切皆在脚下

 

生活

其实今年并没有去了很多地方,因为实在做不到真正的穷游,哈哈。

唯一的一次远门,也就是做了两小时高铁的南京,体验了下疫情解封后的疯狂。

解封后的生活 – Echo小窝 (liveout.cn)

也就是上面这篇文章里描述的那样,其他时候基本都是在当地以及附近玩玩了。

除了这个外,就是基本的三点一线了,偶尔出去下趟馆子,也就是下面这篇文章所述

九月 – Echo小窝 (liveout.cn)

没办法,今年发生的能够谈谈的基本都被写出来了,我也没法用来凑字数了。

 

博客

如今,一到年底,各大APP都会争先推送各种总结报告,既然如此,我也给博客写一份年总报告吧。

在2023年这一年中,小E的小窝一共更新了 27篇文章,其中大部分都是技术文章。

相比文章,碎碎念 达到了惊人的 38条 ,平均每个月吐槽3次。果然,吐槽是人的本质。

不过好在文章数和碎碎念数量一样,说明我更新的也是挺频繁的。

除此之外,访客量达到了 349771,点击量则是 761959,emm,除去友链经常互访的小伙伴们,数据还是不错的,哈哈。

其实一开始也没想到会有这么多人访问,毕竟只是一个私人博客,也没啥营养东西。不过友链和B站教程视频倒是引流了不少,也算是一个不错的礼物——送给自己的博客。

因为博客是一时兴起搭建的,许多地方设置的不是很合理。

一开始我是打算用来记录生活和学习笔记等文章,确实做到了这一步。不过因为各种文章都发布在这一个博客中,导致比较杂乱。因此,我打算后面将学习记录以及教程文章全部搬迁到其他地方,而这个博客单纯当作一个记录和吐槽生活的地方。至于什么时候,应该得明年吧(确实是明年,hh)。

 

抉择

下面就是最重要的地方了。

每次写年终总结时都会觉得时间过得飞快,现在也一样。

刚搭建博客时候是大一暑假,现在已经到了大三寒假了。工作 or 考研是一个不得不面对的问题。这也是一个使人成长的问题。

友链里面的朋友们已经都开始实习了,而我现在却一点准备也没有,除了一个破简历,总结了大学所学的所有知识。

不过现如今,我的学历已经不够看了,因此,我选择了考研。当然,考研也是面向工作。

至于考公,一直从未出现在我的选择里。因为我也有一个年薪百万的IT梦,哈哈,开个玩笑。

今年考公的人数也在增加,恰巧是考研减少的人数。考公的难度和考研的难度,对我来说,应该是一样的,都很难。

既如此,不如先考研,如果运气好,考上了,到时候再考虑是考公或者工作。如果考不上,那么我估计会二战。如果运气好点,能够找到一个凑活的工作,那么便是边工作边二战了。

既然做了选择,下面就该努努力了。博客也该放一放了,毕竟没啥文章能写。有可能下一篇文章就是2024年的年终总结了,哈哈。希望一战上岸吧(虽然上岸后还有无数个岸)

 

  •  

Git命令汇总

前言

单纯的对一些经常用的 git 命令进行总结,方便以后查询使用,没啥营养

1. 基础

git add <file>        	   # 提交到 暂存区

git commit -m "commnet"   	   # 提交到 版本库

git branch -M main          	# 重新命名分支

git remote add origin 		  	# 添加远程仓库

git pull origin master			# 从名为 origin 的远程仓库的 master 分支拉取最新的提交,并将其合并到当前分支

git push origin main   		   # 将本地仓库的文件push到远程仓库(若 push 不成功,可加 -f 进行强推操作)

git diff read.txt				# 查看文件变化

2. 版本回退

git reset --hard HEAD^			# 恢复到上一个版本
git reset --hard HEAD~10		# 恢复到网上10个版本
git reset --hard commitID		# 恢复到指定commit版本

3. 撤销修改

git restore <file>				# 工作区
git restore --staged <file>		# 暂存区,工作区需要执行上一步	add
git reset --hard HEAD^			# 工作区、暂存区、本地仓库都回退	commit

4. 删除文件

rm file	  
git add file	
git commit -m ""

5. 分支操作

git branch test				# 创建分支 test
git branch					# 查看当前分支
git switch -c test			# 创建test分支,然后切换到test分支	git branch test 	git checkout test
git switch master			# 切换
git merge test				# 合并指定分支到当前分支
git merge --no-ff -m "no-ff" test	# 禁用快进(fast-forward)合并,强制创建一个新的合并提交
git branch -d test			# 删除分支
git branch -D test			# 强制删除
git log --graph				# 查看分支合并图

6. 保存和恢复

git stash save "Your stash message"		# 保存工作进度
git stash list							# 查看 stash 列表
git stash apply [<stash-index>]			# 应用 
git stash drop [<stash-index>]			# 删除
git stash pop [<stash-index>]			# 应用并删除

git cherry-pick							# 将一个或多个提交从一个分支应用到另一个分支

7. 多人协作

git remote -v					# 查看远程库的信息
git switch -c dev origin/dev	# 本地创建一个新分支 dev,并将其设置为跟踪远程仓origin/dev 分支
git branch -u origin/dev dev	# 本地分支 dev 设置为跟踪远程仓库的 origin/dev 分支
git push origin master   	   # 将本地仓库文件push到远程(若push不成功,可加-f进行强推)
git pull origin master			# 从远程仓库origin的master分支拉取最新提交,并将其合并到当前分支
git rebase						# 把本地未push的分叉提交历史整理成直线		

8. 标签

标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。

 git tag v1.0								# 打一个新标签V1.0,默认是打在最新提交的commit上
 git log --pretty=oneline --abbrev-commit	# 每一行包含了一个提交的简略哈希和提交信息
 git tag v0.9 f52c633						# 在特点commit上打标签
 git tag -a v0.1 -m "v0.1 released" 1094adb	# 创建带有说明的标签,用-a指定标签名,-m指定说明文字
 git tag									# 查看所有标签
 git show v0.9								# 查看标签信息,标签不是按时间顺序列出,而是按字母排序
 
 git tag -d v0.1					# 删除标签
 git push origin v1.0				# 推送标签到远程
 git push origin --tags				# 一次性推送全部尚未推送到远程的本地标签
 git push origin -d tag tagName	# 删除一个远程标签
 git ls-remote --tags origin 		# 查看删除远程tags执行效果

 

 

  •  

讯飞免费星火大模型部署教程

前言

发现两个多月没写文章了,不过考试月也没啥好写的。

最近大模型这么火,正好有个项目用到,于是便水一篇教程吧。

此篇教程为 科大讯飞的星火大模型 部署教程,部署完成后即可与智能助手进行聊天。

这里是关于部署到服务器端,如果有其他需求可以查看官方文档。

 

大模型简介

大语言模型 (英语:large language model,LLM) 是一种语言模型,由具有许多参数(通常数十亿个权重或更多)的人工神经网络组成,使用自监督学习半监督学习对大量未标记文本进行训练[1]。大型语言模型在2018年左右出现,并在各种任务中表现出色[2]

尽管这个术语没有正式的定义,但它通常指的是参数数量在数十亿或更多数量级的深度学习模型[3]。大型语言模型是通用的模型,在广泛的任务中表现出色,而不是针对一项特定任务(例如情感分析、命名实体识别或数学推理)进行训练[2]

尽管在预测句子中的下一个单词等简单任务上接受过训练,但发现具有足够训练和参数计数的神经语言模型可以捕获人类语言的大部分句法和语义。 此外大型语言模型展示了相当多的关于世界的常识,并且能够在训练期间“记住”大量事实[2]

参考资料:llm - 搜索 (wikipedia.org)

 

部署

API领取

首先前往科大讯飞的星火大模型官网 讯飞星火认知大模型-AI大语言模型-星火大模型-科大讯飞 (xfyun.cn)

进行注册,然后领取大模型的API

这里选择 API免费试用 ,然后进入如下页面

选择第一个 个人免费包免费试用 。等到领取成功后,后台会有如下界面

这里的 APPIDAPISecretAPIKey 就是接口信息,后面会用到。

 

Linux SDK 下载

进入上面页面后,点击 Linux SDK 右边的下载按钮。将会下载SDK包,感兴趣的也可以点击文档查看使用教程

下载完成后传到服务器,使用解压命令解压包

unzip Spark3.0_Linux_SDK_v1.1.zip

然后进入解压出来的包 Spark3.0_Linux_SDK_v1.1 , 里面应该包含如下文件

root@echofree:/opt# cd Spark3.0_Linux_SDK_v1.1/
root@echofree:/opt/Spark3.0_Linux_SDK_v1.1# ls
build  include  lib  src

 

动态库配置

进入 lib 目录,里面会有一个相关的调用库

root@echofree:/opt/Spark3.0_Linux_SDK_v1.1# cd lib/
root@echofree:/opt/Spark3.0_Linux_SDK_v1.1/lib# ls
libSparkChain.so

这里为了方便点,直接采用暴力方法,将库文件 libSparkChain.so 复制到 /usr/lib

cp libSparkChain.so /usr/lib

 

API接口配置

进入 src 目录,会有一个 demo.cpp 文件,进入此文件,修改如下信息

int initSDK()
{
    // 全局初始化
    SparkChainConfig *config = SparkChainConfig::builder();
    config->appID("appID")        // 你的appid
        ->apiKey("apiKey")        // 你的apikey
        ->apiSecret("apiSecret"); // 你的apisecret
        // ->logLevel(0)
        // ->logPath("./aikit.log");
    int ret = SparkChain::init(config);
    printf(RED "\ninit SparkChain result:%d" RESET,ret);
    return ret;
}

将这里的 三个API配置信息改为自己的即可。

 

demo测试

配置完成后就要测试连接了,使用 GNU 编译套件进行编译,命令如下

g++ -Iinclude src/demo.cpp -o demo -lSparkChain -lstdc++ -lpthread

如果你会 Makefile 的话,也可以复制下面的进行编译

CC = g++
CFLAGS = -Iinclude
LIBS = -lSparkChain -lstdc++ -lpthread
SRC = src/server.cpp
OUTPUT = demo

all: $(OUTPUT)

$(OUTPUT): $(SRC)
	$(CC) $(CFLAGS) -o $@ $^ $(LIBS)

clean:
	rm -f $(OUTPUT)

如果一切正常,文件夹下会生成一个可执行文件 demo

root@echofree:/opt/Spark3.0_Linux_SDK_v1.1# ls
build  demo  files  include  lib  src

运行看看

root@echofree:/opt/Spark3.0_Linux_SDK_v1.1# ./demo

######### llm Demo #########

init SparkChain result:0
######### 同步调用 #########

syncOutput: assistant:Hello

syncOutput: assistant:こんにちは

######### 异步调用 #########
0:assistant:Hello:myContext
2:assistant::myContext
tokens:1 + 5 = 6
0:assistant:こ:myContext
1:assistant:んに:myContext
1:assistant:ちは (:myContext
1:assistant:Konnichi:myContext
2:assistant:wa):myContext
tokens:12 + 10 = 22

很好,配置完成!

 

加点互动

下面就要给他加互动功能了,毕竟大模型不能进行交互聊天,那还要他做什么

修改 demo.cpp 文件,内容如下

记得修改下 API信息哦

#include "../include/sparkchain.h"
#include <iostream>
#include <string>
#include <atomic>
#include <unistd.h>
#include <regex>

#define GREEN "\033[32m"
#define YELLOW "\033[33m"
#define RED "\033[31m"
#define RESET "\033[0m"

using namespace SparkChain;
using namespace std;

// async status tag
static atomic_bool finish(false);
// result cache
string final_result = "";

class SparkCallbacks : public LLMCallbacks
{
    void onLLMResult(LLMResult *result, void *usrContext)
    {
        int status = result->getStatus();
        printf(GREEN "%d:%s:%s:%s \n" RESET, status, result->getRole(), result->getContent(), usrContext);
        final_result += string(result->getContent());
        if (status == 2)
        {
            printf(GREEN "tokens:%d + %d = %d\n" RESET, result->getCompletionTokens(), result->getPromptTokens(), result->getTotalTokens());
            finish = true;
        }
    }

    void onLLMEvent(LLMEvent *event, void *usrContext)
    {
        printf(YELLOW "onLLMEventCB\n  eventID:%d eventMsg:%s\n" RESET, event->getEventID(), event->getEventMsg());
    }

    void onLLMError(LLMError *error, void *usrContext)
    {
        printf(RED "onLLMErrorCB\n errCode:%d errMsg:%s \n" RESET, error->getErrCode(), error->getErrMsg());
        finish = true;
    }
};

int initSDK()
{
    // 全局初始化
    SparkChainConfig *config = SparkChainConfig::builder();
    config->appID("appID")        // 你的appid
        ->apiKey("apiKey")        // 你的apikey
        ->apiSecret("apiSecret"); // 你的apisecret
        // ->logLevel(0)
        // ->logPath("./aikit.log");
    int ret = SparkChain::init(config);
    printf(RED "\ninit SparkChain result:%d" RESET,ret);
    return ret;
}

void syncLLMTest()
{
	cout << "\n######### 同步调用 #########" << endl;
	// 配置大模型参数
	LLMConfig *llmConfig = LLMConfig::builder();
	llmConfig->domain("generalv3");
	llmConfig->url("ws(s)://spark-api.xf-yun.com/v3.1/chat");

	Memory* window_memory = Memory::WindowMemory(5);
	LLM *syncllm = LLM::create(llmConfig, window_memory);

	// Memory* token_memory = Memory::TokenMemory(500);
	// LLM *syncllm = LLM::create(llmConfig,token_memory);

	int i = 0;
	//const char* input = "";
	while (1)
	{
		char input[256]; // 定义一个足够大的字符数组来接收用户输入

		printf("请输入问题 (输入 'q' 退出):");
		scanf("%s", input);

		if (strcmp(input, "q") == 0) {
			break; // 如果输入是 'q',则退出循环
		}

		// 同步请求
		LLMSyncOutput *result = syncllm->run(input);
		if (result->getErrCode() != 0)
		{
			printf(RED "\nsyncOutput: %d:%s\n\n" RESET, result->getErrCode(), result->getErrMsg());
			continue;
		}
		else
		{
			printf(GREEN "\nsyncOutput: %s:%s\n" RESET, result->getRole(), result->getContent());
		}

	}
	// 垃圾回收
	if (syncllm != nullptr)
	{
		LLM::destroy(syncllm);
	}
}


void uninitSDK()
{
    // 全局逆初始化
    SparkChain::unInit();
}

int main(int argc, char const *argv[])
{
    cout << "\n######### llm Demo #########" << endl;
    // 全局初始化
    int ret = initSDK();
    if (ret != 0)
    {
        cout << "initSDK failed:" << ret << endl;
        return -1;
    }

    syncLLMTest(); // 同步调用
   

    // 退出
    uninitSDK();

    return 0;
}

如果你仔细观察,会发现少了一部分代码。

星火大模型的接口调用给了两种方式,一种是同步,一种是异步

 

这里我用的是同步,所有文字都输出完,才会打印在终端。

正常的大模型,应该都是异步调用,即慢慢打印出来,这里留给读者自己修改了。

下面看下运行效果

root@echofree:/opt/Spark3.0_Linux_SDK_v1.1# ./demo

######### llm Demo #########

init SparkChain result:0
######### 同步调用 #########
请输入问题 (输入 'q' 退出):徐州天气怎么样

syncOutput: assistant:今天徐州市的天气是多云,气温在3℃到11℃之间,有点冷。东风4-5级,湿度为53%。空气质量良好,PM2.5指数为60。在这样的天气条件下,适宜旅游、钓鱼和户外运动,但要注意保暖。同时,感冒较易发生,请注意保持干净整洁的环境和清新流通的空气。
请输入问题 (输入 'q' 退出):你是什么

syncOutput: assistant:您好,我是科大讯飞研发的认知智能大模型,我的名字叫讯飞星火认知大模型。我可以和人类进行自然交流,解答问题,高效完成各领域认知智能需求。
请输入问题 (输入 'q' 退出):q
root@echofree:/opt/Spark3.0_Linux_SDK_v1.1#

效果还是不错的。

 

整点花活

既然一切都配置ok了,那肯定得进行应用开发了,这里来个小demo

这里是使用 Qt 开发的一个非常质朴的聊天界面,也就是开头所说的项目中正好用到大模型的地方。由于时间紧张,技术能力有限,就直接搬上去了。

具体原理就是使用 Linux的 socket多线程 与界面进行通信。服务器端负责接收客户端的信息并进行回复。

后续

由于技术有限,并且考试月繁忙,等到有空闲时间了,再写个单独的交互界面。

  •  

九月

九月小记

糊弄着,糊弄着,开学已经一个月了。

看着还差两条数量就要追上文章数的 碎碎念 ,我觉得不能摆烂下去了。

不过写文章是真的费时间,除非是学习笔记,直接上传发布,可惜最近没啥笔记,就算有,也没有啥价值。emm,文章也没啥价值,不过最起码以后还能看看,回忆下(如果博客继续存在下去)。

所以,我做了一个......的决定,水篇文章,hh

大三

虽然现在已经大三了,但是还是像打车一样,在生活的旅途中,找不到自己的定位。

开学时踌躇满志,想要好好拼搏下,可惜药效不够,过几天就原形毕现了,实在是不知道做什么。不是事情太少,无事可做,而是事情一大堆,找不清主线了。就像打剧情游戏一样,一堆游戏线路需要去做。不过游戏还好,最起码给你标了主线,给了指引和线索。

好在没过几天,全国计算机等级考试来了,索性报了个三级。明知就业没啥用,但还是报了。还记得之前学院开会,统计二级证书人数,结果寥寥无几,于是便让我们去报名考试。

网上搜寻一番,发现都说网络技术简单,便火速下单了未来教育的习题。之前的二级考试,因为太过简单,直接白嫖学姐用过的。这次好歹是三级,打算认真刷题,好好准备。可惜看了一下,发现基本都是背诵的知识,唯一需要计算的就是ip地址内容了。

这下好了,又没有事情做了,emm,于是买了本《C++ Prime Plus》看看。之前买的《深入理解计算机系统》由于二进制内容对我来说太过枯燥,便舍弃了一会。

于是整个9月都在游戏和书籍中度过。对了,还有实验课。

数电的实验课,线子越插越多,脑子转速越来越慢。好在勉强还能做出来,然后就是万恶的实验报告了。

而且数电老师讲课速度是真的快,短短一个月就讲了一半内容,我是真的赶不上了,还是等考试前突击吧。

实验课下课拍的学长(也可能是学姐)。

除此之外,在连烧了两块开发板后,再次忍痛买了两个ESP32,也终于调库成功,连上了阿里云的物联网平台

_

 

把子肉

疫情过去了,两年没走过的南门也正常开放了。于是下午课上完,便和室友走一公里路,去旁边的万达广场吃把子肉,还有隆江猪脚饭~

虽然这个图片里没有猪脚,也没有把子肉。

从室友那偷的学校晚霞图,当时正好下课,准备出去吃一顿,然后走回学校,也是很惬意的。毕竟人生偷得几时闲。

 

三级考试

在考试的前一晚,学校在操场举行了迎新晚会,哈哈,没办法,太穷了,555

人倒是挺多的,他们拍表演,我只能拍个手机。

 

第二天考试,梦回我多年未见的母校

之间还有个小插曲。由于学校不开设考试点(狠狠表扬了),于是只能报名矿大了~

矿大,嗯,很大,哈哈。有山有水有树林,风景很美,可惜没吃到矿大的饭。

还记得上次蓝桥杯也是在这考的,当时做了好长时间,可惜没出好成绩,考完心情也不咋地。这次三级就不一样了,果然如预料的一样,非常简单,考试前一星期背背题就行了。

也不知道下次四级报不报。想报软考,可惜11月就考了,时间来不及了。至于教资考试,已经过去了,而且也从来没想过。

考完试的晚上,也就是昨晚,顺便买了杯茶百道犒劳下自己(虽然没动啥脑子)

一个小彩蛋。才发现茶百道的包装纸有学校的曲艺团标志,哈哈。

突然想起了之前友链里看过的一篇文章,有关奶茶的,在这推荐下:饮者杂记(Ⅰ) - 庭院一角 (harrypan.cn)

虽然作为店员的我自然要满足顾客的需求,然而我内心中则是十分认同清玉的那个搞怪广告:“调你妈,黄金比例最好喝!” 都买奶茶了,还要想东想西,瞻前顾后,连片刻的休憩都要充满对身材和健康的焦虑。 ———— 此文摘抄

 

秋招

离毕业也快了,看了下牛客秋招,发现是诸神之战,hc越来越少,简历越来越多。

连全国城镇调查失业率都暂停发布了,233,还能咋办呢,对吧

国家统计局:8月起,全国青年人等分年龄段的城镇调查失业率将暂停发布新闻频道央视网(cctv.com)

这里又想推荐一篇友链的相关文章了:对当下高等教育的一点思考 – Abin的个人博客-爱上小树枝 (xiaoshuzhi.love)

至于考研,只能说缓解就业了,而且还不一定能考上。在这学历贬值的时代,考研的人数已经到达了450万了。

当然了,还有考公,也是千军万马,不容易。

不过也不能这么悲观,还是得发现生活的美好的一面。

 

  •  

外设篇01:那些年用过的键盘

前言

转眼已经大三了,也买了四个键盘,谁让我喜欢电子产品呢。除此之外,天天敲代码,对键盘使用度极高。

换句话说,键盘已经替代了高中的黑笔,成为了我新的实现理想的工具。于是趁着开学之际,便想写篇文章介绍下用过的几种键盘。

 

1. 达尔优EK815

 

高中毕业的暑假,对于电脑以及外设还处于一问三不知的状态,在知乎搜了几篇文章,只知道买这些东西走京东自营店比较好,于是便下载了京东(京东快打钱)。

当时在京东的键盘销售榜上,这个键盘,也就是 达尔优EK815机械合金版,好像排在了第一名,而且价格也挺便宜的,149 元,有着可调的RGB灯光,还是机械的,对那时候没有接触过键盘的我来说,十分具有吸引力,于是光速下单买了蓝白色。

与此同时,也买了第一个steam游戏,战地5,整个暑假都在这个游戏中度过。虽然每次都被打成薯条,但也不亦乐乎。

还有一点是,用这个键盘的人太多了,高中同学就有几个撞了。

 

2. 黑峡谷X3Pro

 

 

到了大一暑假,经历一个学年的洗礼,对键盘的知识也增加了不少,于是开始寻找更高配置的键盘。

因为第一个键盘是有线的,有时候使用起来极其不方便,这次便想买个三模的。经过多次搜寻,最终目光落在了两款键盘上:RK98黑峡谷X3Pro。到了最后还是买了黑峡谷的 黑森林慕斯色

没办法,黑白色对我的吸引力还是很大的,还送了个掌托(虽然从来没用过)。选定的轴体为流沙金,价格 389,还是很贵的,不过体验感完虐之前的达尔优,果然是一分钱一分货。后来又发现隔壁宿舍两个和我一样的,哈哈,这个键盘销量也挺高的。

不过很气人的是,今年暑假直接降价了100,虽然电子产品是理财产品,但还是很离谱。

 

3. 华为蓝牙键盘

 

这款小键盘则是从舍友那收过来的,因为有时候需要便携操作,正好有个平板,缺个小键盘,于是打个折,花费了 111.11 大洋买了下来。

虽然想法很不错,不过用的时间几乎可以不计,一般当个备用键盘或者静音键盘使用。

 

4. Cherry KC1000

 

55元 极致性价比,一款非常经典的薄膜键盘。因为之前的黑峡谷声音有点大,所以开学就买了这个,切换着用。

使用手感和学校机房的已经沾满油的薄膜键盘基本一样,声音很小,不过如果快速敲代码声音还是有点大的。

相比于这个,我还是更喜欢没有小数字的87键,打字速度更快,但也失去了双人4399的快乐~

 

总结

总的来说,黑峡谷的X3Pro是我用的最舒服的一个。

Cherry的薄膜键盘由于刚到,还没有捂热,而且87键机械键盘用惯了,导致手感不是很好。

华为的蓝牙键盘比较小巧,适合平板办公。

至于达尔优那款,如果经费有限,又想体验RGB+机械轴,那么可以一试。

  •  

RSS阅读器安利:Fluent Reader

前言

由于之前在网上搜寻到RSS订阅工具都差强人意,今天闲来无事,偶然发现了一款颜值非常高的开源免费RSS订阅器:Fluent Reader , 于是便想写一篇文章安利下

如果想要使用,微软商店直接搜索下载即可,也可以前往 GitHub 上下载,此仓库包含了APP端软件包

至于更多RSS订阅源可跳转相关链接处

 

RSS 介绍

RSS(Really Simple Syndication,真正简单的分发)是一种用于发布和订阅网站内容的数据格式和协议。它通过简单的 XML 格式来传递网站的文章、新闻、博客等信息,允许用户通过订阅器(RSS Reader)获取网站内容的最新更新,而无需直接访问网站。

RSS 最早出现在 1999 年,由 Netscape 公司创立。随后,RSS 标准逐渐发展,演变为不同的版本和格式。常见的 RSS 版本包括 RSS 0.9x、RSS 1.0、RSS 2.0 和 Atom 等。

RSS 的工作原理如下:

  1. 网站创建并维护 RSS 文件:网站管理员会将网站的文章、新闻、博客等内容整理成 XML 格式的 RSS 文件,并将其发布在网站的特定位置,通常是一个预定义的 RSS Feed URL。
  2. 用户使用订阅器订阅 RSS Feed:用户可以使用 RSS 订阅器(也称为 RSS Reader 或 Feed Reader)来订阅感兴趣的网站的 RSS Feed。订阅器会定期检查订阅的 RSS Feed,以获取其中的更新。
  3. 订阅器获取更新:当订阅的网站有新的文章或内容发布时,RSS Feed 文件会被更新。订阅器会检测到这些更新,并将最新的内容显示在用户的订阅列表中。

RSS 的优点包括:

  • 方便获取信息:用户可以一站式地收集和查看多个网站的内容更新,无需频繁访问每个网站。
  • 自动化更新:订阅器会定期检查更新,用户不需要手动去查看是否有新的内容发布。
  • 隐私保护:RSS 订阅不需要提供个人信息,保护用户的隐私。

随着社交媒体和其他内容分发平台的兴起,RSS 的使用逐渐减少。然而,RSS 仍然被许多网站和博客用于提供内容更新,并且一些专门的订阅器应用程序仍然广泛使用,满足了一部分用户对于个性化内容订阅的需求。

 

软件截图

 

 

 

 

相关链接

GitHub地址:Fluent Reader

RSS入门指南:高效获取信息,你需要这份 RSS 入门指南 - 少数派 (sspai.com)

使用体验:Windows平台最美RSS阅读器-Fluent Reader上手体验 - 知乎 (zhihu.com)

友情提醒:卸载软件时,记得导出相关订阅源进行备份

  •  

Linux系统编程02:文件系统

前言

此篇文章为学习 Linux系统编程02:文件系统 部分的笔记

1. 文件存储

1.1 inode

inode 是 Linux 和 Unix 操作系统中的一个重要概念,它是文件系统中的一个 数据结构,用于存储文件的元数据。每个文件和目录都有一个对应的 inode 来描述其属性和位置信息。

root@freecho:/opt/C/gcc/code# stat hello.c
  File: hello.c
  Size: 373             Blocks: 8          IO Block: 4096   regular file
Device: b301h/45825d    Inode: 1314593     Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2023-07-26 21:29:11.655510006 +0800
Modify: 2023-07-26 21:29:11.655510006 +0800
Change: 2023-07-26 21:29:11.655510006 +0800
Birth: 2023-07-26 21:29:11.655510006 +0800

inode 结构包含了以下信息:不包含文件名

  1. 文件类型:标识文件是普通文件、目录、符号链接等类型。
  2. 文件权限:文件的读、写、执行权限。
  3. 文件所有者和所属组:标识文件的所有者和所属的用户组。
  4. 文件大小:文件的大小,以字节为单位。
  5. 文件时间戳:记录文件的创建时间、修改时间和访问时间等。
  6. 文件链接数:记录文件的硬链接数目。
  7. 数据块指针:指向文件存储数据的数据块。

当系统中创建一个新文件时,会为该文件分配一个唯一的 inode,然后将文件的实际数据存储在数据块中,并将 inode 中的数据块指针指向这些数据块。在文件被访问或修改时,通过 inode 可以快速定位文件的数据块,而不需要遍历整个文件系统。

通过 ls -i 命令可以查看文件的 inode 号码。每个文件和目录在同一文件系统中具有唯一的 inode 号码。

inode 在文件系统的性能和管理中起着关键作用,它使得文件系统能够高效地管理文件和目录,并支持硬链接的使用。

1.2 dentry

dentry , (即 directory entry 目录项)是 Linux 文件系统中的一个重要概念,用于管理文件系统中的目录。dentryinode 相关联,共同组成了 Linux 文件系统中的目录项。其本质依然是结构体,重要成员变量有两个 {文件名,inode,...},而文件内容(data)保存在磁盘盘块中

每个目录都包含一个或多个 dentry,每个 dentry 表示一个目录中的文件或子目录。dentry 记录了文件或目录的名称、文件类型和对应的 inode 号码等信息。当用户访问文件时,Linux 文件系统会通过 dentry 来快速定位文件的 inode,从而访问文件的实际数据。

在 Linux 文件系统中,dentry 会被缓存在内存中,以提高文件系统的性能。当用户访问文件时,系统首先会查找该文件对应的 dentry 是否已经缓存,如果已经缓存,则直接从 dentry 中获取 inode 信息,避免了不必要的磁盘访问。如果文件对应的 dentry 不在缓存中,系统会通过目录索引进行查找,并将找到的 dentry 缓存起来,以便下次快速访问。

dentry 与目录层次结构一起形成了文件系统的层次结构,通过 dentry 可以在文件系统中快速定位文件和目录,提高了文件系统的访问效率和性能。同时,dentry 的缓存机制也减少了不必要的磁盘访问,提高了整个文件系统的效率。

1.3 文件系统

文件系统是一组规则,规定对文件的存储及读取的一般方法。文件系统在磁盘格式化过程中指定。

以下为常见文件系统:

  1. FAT32(File Allocation Table 32):FAT32是一种较旧的文件系统,广泛应用于可移动介质(如USB闪存驱动器、SD卡等)。它是Windows系统和其他操作系统的通用文件系统。
  2. NTFS(New Technology File System):NTFS是Windows操作系统中使用的主要文件系统。它支持大文件和文件系统,并提供更高级的权限控制和数据安全性。
  3. exFAT(Extended File Allocation Table):exFAT是FAT32文件系统的改进版本,特别设计用于支持更大的文件和分区。它通常在移动存储设备和外部驱动器中使用。
  4. ext2(Second Extended File System):ext2是Linux系统早期的文件系统,不具备日志功能。虽然现在很少使用,但仍然是一些老旧系统的选择。
  5. ext3(Third Extended File System):ext3是ext2文件系统的改进版本,具有日志记录功能,可提供更好的数据完整性和恢复能力。
  6. ext4(Fourth Extended File System):ext4是Linux系统中目前最常用的文件系统,它是ext3文件系统的进一步改进,提供更高的性能和可靠性。ext4支持更大的文件和文件系统,并具备更高级的特性。

1.4 硬链接、软连接

硬链接软链接 (又称软连接)是 Linux 文件系统中两种不同类型的链接方式,用于在文件系统中创建文件或目录之间的关联。

  1. 硬链接(Hard Link):


    • 硬链接是目录项(dentry)中指向相同 inode 号的不同目录项。
    • 通过硬链接,多个文件名可以指向同一个数据块,实际上是同一个文件的不同访问入口。
    • 硬链接创建后,可以像普通文件一样操作,读写内容,删除等,但是不能对目录进行硬链接。
    • 硬链接不能跨文件系统创建,即硬链接必须位于同一个文件系统。
  2. 软链接(Symbolic Link / Soft Link):


    • 软链接是一个特殊的文件,它包含了指向另一个文件或目录的路径名。
    • 软链接类似于 Windows 系统的快捷方式,它只是一个指向目标的快捷方式而已。
    • 软链接可以跨文件系统创建,因为它只保存了目标文件或目录的路径名。
    • 删除软链接并不会影响目标文件或目录,但如果目标文件或目录被删除,软链接将变为"断链"。

对比:

  • 硬链接是多个目录项指向同一个 inode,它们是文件系统中同一个文件的不同名字,文件大小和权限都是相同的。
  • 软链接是一个特殊的文件,它保存了指向目标文件或目录的路径名,它是目标文件或目录的"快捷方式",不占用实际数据块。

注意事项:

  • 删除硬链接或软链接并不会删除目标文件本身。
  • 硬链接不能跨文件系统创建,而软链接可以。

示例:

$ echo "Hello, hard link!" > original.txt
$ ln original.txt hard_link.txt    # 创建硬链接
$ ln -s original.txt soft_link.txt # 创建软链接

$ ls -l
-rw-r--r--  2 user user 18 May 18 2023 hard_link.txt
lrwxrwxrwx  1 user user 13 May 18 2023 soft_link.txt -> original.txt

$ cat hard_link.txt  # 输出:"Hello, hard link!"
$ cat soft_link.txt  # 输出:"Hello, hard link!"

$ rm original.txt   # 删除原始文件
$ cat hard_link.txt  # 输出:"Hello, hard link!",硬链接仍然存在
$ cat soft_link.txt  # 输出:"cat: soft_link.txt: No such file or directory",软链接断链

 

2. 文件操作

2.1 stat、lstat 函数

概念

statlstat函数都用于获取文件或目录的信息,但在处理符号链接时有所不同。

  1. stat函数:


    • 函数原型:int stat(const char *path, struct stat *buf);
    • 描述:stat函数通过指定的文件路径获取文件信息,并将结果存储在struct stat类型的结构体buf中。如果path是一个符号链接,stat函数将会获取符号链接指向的文件的信息。
    • 返回值:成功时返回0,失败时返回-1。
  2. lstat函数:


    • 函数原型:int lstat(const char *path, struct stat *buf);
    • 描述:lstat函数与stat函数类似,也用于获取文件信息。不同之处在于,lstat函数不会跟随符号链接,而是获取符号链接本身的信息,而不是它所指向的文件的信息。
    • 返回值:成功时返回0,失败时返回-1。

这两个函数对于获取文件的权限、大小、时间戳等信息非常有用,而在处理符号链接时,使用lstat函数可以避免不必要的问题。在编写程序时,需要根据具体需求选择使用stat函数还是lstat函数。

buf.st_size  // 获取文件大小
buf.st_mode  // 获取文件类型
buf.st_mode  // 获取文件权限
符号穿透:stat 会  lstat 不会

代码

stat.c :查看文件大小

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>

int main(int argc, char *argv[])
{
    struct stat sbuf;

    int ret = stat(argv[1], &sbuf);
    if (ret == -1) {
        perror("stat error");
        exit(1);
    }

    printf("file size: %ld\n", sbuf.st_size);

    return 0;
}

lstat.c : 查看文件属性

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>

int main(int argc, char *argv[])
{
    struct stat sbuf;

    int ret = lstat(argv[1], &sbuf);
    if (ret == -1) {
        perror("stat error");
        exit(1);
    }

    if (S_ISREG(sbuf.st_mode)) {
        printf("It's a regular\n");
    } else if (S_ISDIR(sbuf.st_mode)) {
        printf("It's a dir\n");
    } else if (S_ISFIFO(sbuf.st_mode)) {
        printf("It's a pipe\n");
    } else if (S_ISLNK(sbuf.st_mode)) {
        printf("It's a soft link\n");
    }

    return 0;
}

2.2 link、unlink 函数

概念

link函数和unlink函数用于创建硬链接和删除文件链接(硬链接或符号链接)。

  1. link函数: link函数用于创建硬链接。硬链接是指在文件系统中创建一个新的链接指向同一个文件,这个新链接和原文件具有相同的inode号和数据块,但是在目录中显示为一个新的文件名。它的原型为:

    int link(const char *oldpath, const char *newpath);
    

    参数说明:


    • oldpath:源文件路径名,即要创建硬链接的文件。
    • newpath:目标文件路径名,即新创建的硬链接的文件名。

    返回值:


    • 如果成功创建硬链接,返回0。
    • 如果出现错误,返回-1,并设置errno来指示错误类型。
  2. unlink函数: unlink函数用于删除一个文件链接。如果删除的是硬链接,只会删除该链接,而不会删除原文件;如果删除的是符号链接,会删除链接指向的原文件。它的原型为:

    cint unlink(const char *pathname);
    

    参数说明:


    • pathname:要删除的文件路径名,可以是硬链接或符号链接。

    返回值:


    • 如果成功删除文件链接,返回0。
    • 如果出现错误,返回-1,并设置errno来指示错误类型。

这两个函数在Linux系统编程中经常用于文件链接的创建和删除操作。需要注意的是,link函数只能用于同一个文件系统内的文件,而不能跨文件系统创建硬链接。对于跨文件系统的文件链接,可以使用符号链接(符号链接是指在文件系统中创建一个新的文件,它指向另一个文件的路径)来实现。

思考,为什么目录项要游离于 inode 之外,画蛇添足般的将文件名单独存储呢?这样 的存储方式有什么样的好处呢? 其目的是为了实现文件共享。

Linux 允许多个目录项共享一个 inode,即共享盘块(data)。 不同文件名,在人类眼中将它理解成两个文件,但是在内核眼里是同一个文件

link 函数,可以为已经存在的文件创建目录项(硬链接)。unlink 函数则是删除一个文件的目录项

mv 命令即是修改了目录项,而并不修改文件本身。

代码

注意 Linux 下删除文件的机制:不断将 st_nlink -1,直至减到 0 为止。无目录项对应的 文件,将会被操作系统择机释放。(具体时间由系统内部调度算法决定) 因此,我们删除文件,从某种意义上说,只是让文件具备了被释放的条件。

unlink 函数的特征:清除文件时,如果文件的硬链接数到 0 了,没有 dentry 对应,但该 文件仍不会马上被释放。要等到所有打开该文件的进程关闭该文件,系统才会挑时间将该文 件释放掉。

mymv.c :编程实现 mv 命令的改名操作

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    link(argv[1], argv[2]);

    unlink(argv[1]);

    return 0;
}

unlink.c:通过观察临时文件 temp.txt 存在情况,了解unlink函数以及删除文件机制

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    int fd;
    int ret;
    char *p = "test of unlink\n";
    char *p2 = "after write something.\n";

    fd = open("temp.txt", O_RDWR | O_CREAT | O_TRUNC, 0644); // 临时文件,程序结束销毁
    if (fd < 0) {
        perror("open temp error");
        exit(1);
    }

    ret = unlink("temp.txt"); // // 出现段错误时,temp.txt依然销毁
    if (ret < 0) {
        perror("unlink error");
        exit(1);
    }

    ret = write(fd, p, strlen(p));
    if (ret == -1) {
        perror("--------write error");
    }

    printf("hi! I'm printf\n");
    ret = write(fd, p2, strlen(p2));
    if (ret == -1) {
        perror("--------write error");
    }

    p[3] = 'H';  // 发送段错误

    printf("Enter anykey continue\n");
    getchar();

    close(fd);

    /*
    ret = unlink("temp.txt"); // 出现段错误时,temp.txt无法销毁
    if (ret < 0) {
        perror("unlink error");
        exit(1);
    }
    */

    return 0;
}

2.3 隐式回收

当进程结束运行时,所有该进程打开的文件会被关闭,申请的内存空间会被释放。系统的这一特性称之为隐式回收系统资源。

写程序时一定不能忘记关闭文件

2.4 其他函数

readlink 函数

在Linux中,readlink是一个命令行工具和系统调用,用于读取符号链接(Symbolic Link)所指向的目标路径。

  1. 命令行工具: readlink命令用于查看符号链接的目标路径。使用方法如下:

    readlink [OPTIONS] LINK_PATH
    

    其中,LINK_PATH是符号链接的路径。readlink会输出该符号链接所指向的目标路径。

  2. 系统调用readlink也是一个系统调用,用于在C/C++程序中访问符号链接的目标路径。

    函数原型:

    #include <unistd.h>
    ssize_t readlink(const char *path, char *buf, size_t bufsiz);
    

    参数说明:


    • path:符号链接的路径。
    • buf:用于存储目标路径的缓冲区。
  • bufsiz:缓冲区的大小,应该足够大以容纳目标路径的字符。

返回值:

  • 成功时,返回读取的目标路径的长度(不包括终止空字符),如果目标路径长度大于bufsiz,则返回-1
  • 失败时,返回-1,并设置errno来指示错误类型。

rename 函数

rename函数是一个C标准库函数,用于对文件或目录进行重命名。它在 <stdio.h> 头文件中声明,并且是一个较为简单的文件操作函数。

函数原型:

#include <stdio.h>
int rename(const char *old_path, const char *new_path);

参数说明:

  • old_path:旧的文件名或目录名。
  • new_path:新的文件名或目录名。

返回值:

  • 如果重命名成功,则返回0。
  • 如果重命名失败,则返回-1,并设置errno来指示错误类型。

 

3. 目录操作

3.1 getcwd、chdir 函数

getcwd 函数

获取进程当前工作目录 (卷 3,标库函数)

char *getcwd(char *buf, size_t size); 

成功:buf 中保存当前进程工作目录位置

失败: NULL

chdir 函数

改变当前进程的工作目录

 int chdir(const char *path); 

成功:0

失败:-1 设置 errno 为相应值

3.2 文件、目录权限

注意:目录文件也是“文件”。其文件内容是该目录下所有子文件的目录项 dentry。 可以尝试用 vim 打开一个目录。

 rwx
文件文件的内容可以被查看内容可以被修改可以运行产生一个进程
 cat、more、less…vi、> …./文件名
目录目录可以被浏览创建、删除、修改文件可以被打开、进入
 ls、tree…mv、touch、mkdir...cd

目录设置黏住位:若有 w 权限,创建不变,删除、修改只能由 root、目录所有者、文件所 有者操作。

3.3 目录函数

opendir 函数

返回 :根据传入的目录名打开一个目录 (库函数) DIR * 类似于 FILE *

DIR *opendir(const char *name); 

返回 :成功返回指向该目录结构体指针,失败返回 NULL

参数支持相对路径、绝对路径两种方式

例如:打开当前目录:

  1. getcwd() , opendir()
  2. opendir(".");

closedir 函数

作用:关闭打开的目录

int closedir(DIR *dirp); 

返回 :成功:0; 失败:-1 设置 errno 为相应值

readdir 函数

作用:读取目录 (库函数)

struct dirent *readdir(DIR *dirp); 

返回 :成功返回目录项结构体指针;失败返回NULL设置errno 为相应值

需注意返回值,读取数据结束时也返回 NULL 值,所以应借助 errno 进一步加以区分。

struct 结构体

 struct dirent { 
     ino_t d_ino;            // inode 编号 
     off_t d_off;  
     unsigned short d_reclen; // 文件名有效长度
     unsigned char d_type;   // 类型(vim 打开看到的类似@*/等)
	 char d_name[256];        // 文件名
 };

代码

myls.c:通过以上函数实现 ls 命令

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>

int main(int argc, char *argv[])
{
    DIR *dp;
    struct dirent *sdp;

    dp = opendir(argv[1]);
    if (dp == NULL) {
        perror("open dir error");
        exit(1);
    }

    while ((sdp = readdir(dp)) != NULL) {
        if (strcmp(sdp->d_name, ".") == 0)
            continue;
        if (strcmp(sdp->d_name, "..") == 0)
            continue;
        printf("%s\t", sdp->d_name);
    }

    printf("\n");

    closedir(dp);

    return 0;
}

 

4. 递归遍历目录

查询指定目录,递归列出目录中文件,同时显示文件大小

4.1 思路

  1. 判断命令行参数,获取用户要查询的目录名 argv[1]

    argvc == 1 ---> ./

  2. 判断用户指定的是否是目录。不是则打印文件名

    stat S_ISDIR() ---> 封装函数 isFile

  3. 读目录:

    opendir()   
    while (readdir()) {
        普通文件:直接打印
        目录:
            拼接目录访问绝对路径   sprintf(path, "%s/%s", dir, d_name)
            递归调用自己
    } 
    closedir()
    

4.2 代码

ls-R.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>

void isFile(char *name);

// 打开目录,读取目录,处理目录
void read_dir(char *dir, void (*func)(char *))
{
    char path[256];
    DIR *dp;
    struct dirent *sdp;

    dp = opendir(dir);
    if (dp == NULL) {
        perror("opendir error");
        return;
    }

    // 读取目录项
    while ((sdp = readdir(dp)) != NULL) {
        if (strcmp(sdp->d_name, ".") == 0 || strcmp(sdp->d_name, "..") == 0) {
            continue;
        }
        // 目录项本身不可访问,拼接 目录/目录项
        sprintf(path, "%s/%s", dir, sdp->d_name);
        // 判断文件类型,目录递归进入,文件显示名字、大小
        //isFile(path);
        func(path);
    }

    closedir(dp);

    return;
}

void isFile(char *name)
{
    int ret = 0;
    struct stat sbuf;

    // 获取文件属性,判断文件类型
    ret = stat(name, &sbuf);
    if (ret == -1) {
        perror("stat error");
        return;
    }

    // 目录文件,进入目录函数
    if (S_ISDIR(sbuf.st_mode)) {
        read_dir(name, isFile);
    }
    // 普通文件,显示文件名、大小
    printf("%10s\t\t%ld\n", name, sbuf.st_size);

    return;
}


int main(int argc, char *argv[])
{
    // 判断命令行参数
    if (argc == 1) {
        isFile(".");
    } else {
        isFile(argv[1]);
    }

    return 0;
}

5. 重定向

dupdup2 是 Linux 系统中用于复制文件描述符的函数,它们都是 C 语言的系统调用函数。它们的作用是创建一个新的文件描述符,该文件描述符是现有文件描述符的副本,指向同一个文件。

5.1 dup 函数

int dup(int oldfd);

dup 函数会复制参数 oldfd 所指向的文件描述符,并返回一个新的文件描述符,该新的文件描述符是系统中当前可用的最小的未使用的文件描述符。

如果复制成功,则返回新的文件描述符;如果复制失败,则返回 -1。

代码

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    int fd = open(argv[1], O_RDONLY);
    if (fd == -1) {
        perror("Error opening file");
        return 1;
    }

    // 复制文件描述符
    int new_fd = dup(fd);

    printf("Original file descriptor: %d\n", fd);
    printf("New file descriptor: %d\n", new_fd);

    close(fd); // 注意:关闭原文件描述符不会影响新的文件描述符

    // 使用新的文件描述符读取文件内容
    char buffer[10];
    ssize_t bytes_read = read(new_fd, buffer, sizeof(buffer) - 1);
    buffer[bytes_read] = '\0';
    printf("Content: %s\n", buffer);

    close(new_fd);
    return 0;
}

5.2 dup2 函数

int dup2(int oldfd, int newfd);

dup2 函数与 dup 函数类似,但是它可以指定新的文件描述符的数值。如果 newfd 已经是一个打开的文件描述符,那么 dup2 将首先关闭 newfd,然后将 oldfd 复制到 newfd,确保 newfdoldfd 指向相同的文件。

成功:返回一个新文件描述符; 如果 oldfd 有效,则返回的文件描述符与 oldfd 指向同一文件。

失败:如果 oldfd 无效,调用失败,关闭 newfd。返回-1,同时设置 errno 为相应值

代码

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    int fd = open(argv[1], O_RDONLY);
    if (fd == -1) {
        perror("Error opening file");
        return 1;
    }

    // 复制文件描述符到新的文件描述符 100
    int new_fd = dup2(fd, 100);

    printf("Original file descriptor: %d\n", fd);
    printf("New file descriptor: %d\n", new_fd);

    close(fd); // 注意:关闭原文件描述符不会影响新的文件描述符

    // 使用新的文件描述符读取文件内容
    char buffer[100];
    ssize_t bytes_read = read(new_fd, buffer, sizeof(buffer) - 1);
    buffer[bytes_read] = '\0';
    printf("Content: %s\n", buffer);

    close(new_fd);
    return 0;
}

5.3 小结

  • dup 复制文件描述符,返回一个新的文件描述符,值为系统中当前可用的最小未使用的文件描述符。
  • dup2 复制文件描述符到指定的新文件描述符,如果新文件描述符已经打开,则先关闭新文件描述符再复制。
  • 这两个函数在多线程环境下可能会存在竞态条件,使用时需要注意线程安全性。

记忆方法两种:

  1. 文件描述符的本质角度理解记忆。
  2. 从函数原型及使用角度,反向记忆。

练习:借助 dup 函数编写 mycat 程序,实现 cat file1 > file2 命令相似功能

mycat.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd1, fd2;
    int fdret, ret;

    fd1 = open(argv[1], O_RDWR);
    fd2 = open(argv[2], O_RDWR);

    fdret = dup2(fd1, fd2);  // 返回 新文件描述符fd2
    printf("fdret = %d\n", fdret);

    ret = write(fd2, "1234567", 7); // 写入 fd1 指向的文件
    printf("ret = %d\n", ret);

    dup2(fd1,STDOUT_FILENO); // 将屏幕输入,重定向给 fd1 所指向的文件

    printf("-----------------------886");

    close(fd1);
    close(fd2);

    return 0;
}

5.4 fcntl 实现 dup

当 fcntl 的第二个参数为 F_DUPFD 时, 它的作用是根据一个已有的文件描述符,复制生成一个新的文件描述符。此时,fcntl 相当于 dup 和 dup2 函数。

参 3 指定为 0 时,因为 0 号文件描述符已经被占用。所以函数自动用一个最小可用文件描述符。

参 3 指定为 9 时,如果该文件描述符未被占用,则返回 9。否则,返回大于 9 的可用文件描述符。

fcntl_dup.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    int fd1 = open(argv[1], O_RDWR);

    printf("fd1 = %d\n", fd1);

    int newfd = fcntl(fd1, F_DUPFD, 0); // 0被占用,fcntl使用文件描述符表中可用的最小文件描述符返回
    printf("newfd = %d\n", newfd);

    int newfd2 = fcntl(fd1, F_DUPFD, 7); // 7,未被占用,返回 >= 7 的文件描述符
    printf("newfd2 = %d\n", newfd2);

    int ret = write(newfd2, "YYYYYYY", 7);
    printf("ret = %d\n", ret);

    close(fd1);

    return 0;
}

 

相关链接

教程视频:Linux系统编程哔哩哔哩bilibili

Linux系列文章:Linux – Echo (liveout.cn)

GCC、GDB、Makefile:GCC、GDB、Makefile学习笔记 – Echo (liveout.cn)

Linux系统编程1:文件I/O笔记:Linux系统编程1:文件I/O

GitHub仓库,包含教程讲义、代码以及笔记:https://github.com/PGwind/LinuxSystem

  •