阅读视图

实习小记

实习

从实习到现在,差不多两个月了。每天早上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)

RSS订阅源:RSS Source | RSS订阅源推荐 (rss-source.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

  •  

Linux系统编程01:文件I/O

前言

此篇文章为学习 Linux系统编程01:文件I/O 部分的笔记

1. open/close函数

1.1 open

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

flags

flags 用于指定文件的打开/创建模式,这个参数可由以下三个互斥的常量(定义于 fcntl.h)通过逻辑或(|)连接:

O_RDONLY      只读模式 
O_WRONLY      只写模式 
O_RDWR        读写模式

其他可选常量:

常量含义
O_APPEND每次写操作都写入文件的末尾
O_CREAT如果指定文件不存在,则创建这个文件
O_EXCL如果要创建的文件已存在,则返回 -1,并且修改 errno 的值
O_TRUNC如果文件存在,并且以只写/读写方式打开,则清空文件全部内容
O_NOCTTY如果路径名指向终端设备,不要把这个设备用作控制终端
O_NONBLOCK如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式(nonblocking mode)

以下用于同步输入输出

常量含义
O_DSYNC等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新
O_RSYNCread 等待所有写入同一区域的写操作完成后再进行
O_SYNC等待物理 I/O 结束后再 write,包括更新文件属性的 I/O

 

mode

mode 和 fopen() 函数的 mode 参数相同。mode 指定文件的打开模式:

r = 4,w = 2, x = 1

  • r:只读方式打开一个文本文件(该文件必须存在)
  • r+:可读可写方式打开一个文本文件(该文件必须存在)
  • w:只写方式打开一个文本文件(若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件)
  • w+:可读可写方式创建一个文本文件(若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件)
  • a:追加方式打开一个文本文件(若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留))
  • a+:可读可写追加方式打开一个文本文件(若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。 (原来的EOF符不保留))

a 和 a+ 的区别:a 不能读,a+ 可以读

  • rb:只读方式打开一个二进制文件(使用法则同r)
  • rb+:可读可写方式打开一个二进制文件(使用法则同r+)
  • wb:只写方式打开一个二进制文件(使用法则同w)
  • wb+:可读可写方式生成一个二进制文件(使用法则同w+)
  • ab:追加方式打开一个二进制文件(使用法则同a)
  • ab+:可读可写方式追加一个二进制文件(使用法则同a+)

需要注意的是,当 flags 为 O_CREAT 或 O_TMPFILE 时,必须提供 mode 参数;否则 mode 参数将不起作用。

返回值 open() 的返回值是一个 int 类型的文件描述符,打开失败返回 -1。

 

1.2 close

NAME close – 关闭一个文件描述符

总览

 #include <unistd.h> 
int close(int fd);

描述 close 关闭 一个文件描述符 , 使不在指向任何文件和可以在新的文件操作中被再次使用

任何与此文件相关联的以及程序所拥有的锁, 都会被删除 (忽略那些持有锁的文件描述符)

假如 fd 是最后一个文件描述符与此资源相关联 , 则这个资源将被释放.  
若此描述符是最后一个引用到此文件上的, 则文件将使用 unlink(2) 删除.

返回值

close 返回 0 表示 成功 , 或者 -1 表示 有 错误 发生 .

错误信息

EBADF  fd 不是 一个 有效 的 已 被 打开 的 文件 的 描述符

EINTR  The close() 调用 被 一 信号 中断.

EIO    I/O 有 错误 发生

 

1.3 文件权限

在Linux系统中,文件的最终权限是由文件的创建模式(mode)和用户掩码(umask)共同决定的。

文件的创建模式(mode)是指在使用系统调用如open()creat()创建文件时,通过指定一个八进制数来表示文件的权限。通常使用三个数字来表示权限,分别对应所有者、所属组和其他用户的权限。每个数字由三个位组成,分别代表读(r)、写(w)和执行(x)权限。例如,权限为 rw-r--r-- 的文件模式用八进制表示就是 644

用户掩码(umask)是用来屏蔽(取消)文件创建模式中的某些权限。默认情况下,umask 设置为 022,表示屏蔽掉写权限(w)和执行权限(x)对其他用户。所以如果文件的模式是 644,经过 umask 的处理,其他用户的权限就变为 444,即只有读权限。

计算文件的最终权限可以使用如下公式:

最终权限 = 文件模式(mode) & (~用户掩码(umask))

例如,假设文件的模式是 755,umask 是 022,则最终权限为:

755 & (~022) = 755 & 755 = 755

所以最终权限仍然是 755

另外,需要注意的是,umask 的值通常是用八进制数表示的。在设置 umask 时,一般使用四个数字,例如 umask 022

root@freecho:/opt/C/file_IO/test# umask
0022  # 755

 

1.4 代码样例

open1.c

读取文件,文件存在则以只读 O_RDONLY 方式打开,并且清空文件全部内容 O_TRUNC,如果不存在则创建文件 O_CREAT

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

int main()
{
    int fd;
    // 0644 第一个0表示8进制, 644为权限
    fd = open("./dict.cp", O_RDONLY | O_CREAT | O_TRUNC, 0644); // 644 rw-r--r--

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

    close(fd); // 关闭文件描述符  成功0 失败-1

    return 0;
}

/*
root@freecho:/opt/C/file_IO/test# ./open1
fd = 3
*/
root@freecho:/opt/C/file_IO/test# ./open1
fd = 3

 

open2.c

打开不存在的文件

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

int main()
{
    int fd;
    fd = open("./dict1.cp", O_RDONLY); 

    printf("fd = %d, errno = %d:%s\n", fd, errno, strerror(errno));

    close(fd); // 关闭文件描述符  成功0 失败-1

    return 0;
}
root@freecho:/opt/C/file_IO/test# ./open2
fd = -1, errno = 2:No such file or directory

 

open3.c

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

int main()
{
    int fd;

    fd = open("mydir", O_WRONLY); // 打开文件夹

    printf("fd = %d, errno = %d:%s\n", fd, errno, strerror(errno));

    close(fd); // 关闭文件描述符  成功0 失败-1

    return 0;
}
root@freecho:/opt/C/file_IO/test# ./open3
fd = -1, errno = 21:Is a directory

 

1.5 错误及处理

open 常见错误:

  1. 打开文件不存在
  2. 以写方式打开只读文件(权限问题)
  3. 以只写方式打开目录
  4. 当 open 出错时,程序会自动设置 errno,可以通过 strerror(errno)来查看报错数字的含义

错误处理函数

  1. printf("xxx error: %d\n", errno);
    
  2. char *strerror(int errnum);
    	printf("xxx error: %s\n", strerror(errno));
    
  3. void perror(const char *s);
    	perror("xxx error");
    

 

2. read/write函数

2.1 read

Man手册

NAME read – 在文件描述符上执行读操作

概述

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

描述

read( ) 从文件描述符 fd 中读取 count 字节的数据并放入从 buf 开始的缓冲区中.

如果 count 为零,read( )返回0,不执行其他任何操作. 如果 count 大于SSIZE_MAX,那么结果将不可预料.

返回值

成功时返回读取到的字节数(为零表示读到文件描述符), 此返回值受文件剩余字节数限制

当返回值小于指定的字节数时 并不意味着错误;这可能是因为当前可读取的字节数小于指定的 字节数(比如已经接近文件结尾,或者正在从管道或者终端读取数 据,或者read( )被信号中断)

发生错误时返回-1,并置 errno 为相应值.在这种情况下无法得知文件偏移位置是否有变化.

错误代码

EINTR  在读取到数据以前调用被信号所中断.

EAGAIN 使用 O_NONBLOCK 标志指定了非阻塞式输入输出,但当前没有数据可读.

EIO    输入输出错误.可能是正处于后台进程组进程试图读取其 控制终端,但读操作无效,或者被信号SIGTTIN所阻塞, 或者其进程组是孤儿进程组.也可能执行的是读磁盘或者 磁带机这样的底层输入输出错误.

EISDIR fd 指向一个目录.

EBADF  fd 不是一个合法的文件描述符,或者不是为读操作而打开.

EINVAL fd 所连接的对象不可读.

EFAULT buf 超出用户可访问的地址空间.

也可能发生其他错误,具体情况和 fd 所连接的对象有关.  POSIX 允许  read  在读取了一定量的数据后被信号所中断,并返回  -1(且  errno  被设置为EINTR),或者返回已读取的数据量.

2.2 write

Man手册

NAME write -在一个文件描述符上执行写操作

概述

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

描述 write 向文件描述符 fd 所引用的文件中写入 从 buf 开始的缓冲区中 count 字节的数据.

POSIX规定,当使用了write()之后再使用 read(),那么读取到的应该是更新后的数据. 但请注意并不是所有的文件系统都是 POSIX兼容的.

返回值 成功时返回所写入的字节数(若为零则表示没有写入数据).

错误时返回-1,并置errno为相应值.

若count为零,对于普通文件无任何影响,但对特殊文件将产生不可预料的后果.

错误代码

EBADF  fd 不是一个合法的文件描述符或者没有以写方式打开.

EINVAL fd 所指向的对象不可写.

EFAULT buf 不在用户可访问地址空间内.

EPIPE  fd  连接到一个管道,或者套接字的读方向一端已关闭.此时写进程  将接收到  SIGPIPE  信号;如果此信号被捕获,阻塞或忽略,那么将返回错误EPIPE.

EAGAIN 读操作阻塞,但使用 O_NONBLOCK 指定了非阻塞式输入输出.

EINTR  在写数据以前调用被信号中断.

ENOSPC fd 指向的文件所在的设备无可用空间.

EIO    当编辑一个节点时发生了底层输入输出错误.

可能发生了其他错误,取决于 fd 所连接的对象.

2.3 代码样例

mycp.c

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

int main(int argc, char *argv[])
{
    char buf[1024];

    int n =0;

    int fd1 = open(argv[1], O_RDONLY); // read
    if (fd1 == -1) {
        perror("open argv1 error");
        exit(1);
    }

    int fd2 = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0664); // rw-rw--r
    if (fd1 == -1) {
        perror("open argv2 error");
        exit(1);
    }

    // read 返回为读取到的字节数
    while ((n = read(fd1, buf, 1024)) != 0) { // 0:读到文件结尾结束循环
        if (n < 0) {
            perror("read error");
            break;
        }
        write(fd2, buf, n);
    }

    close(fd1);
    close(fd2);

    return 0;
}
root@freecho:/opt/C/file_IO/rw_test# make
gcc mycp.c -o mycp
root@freecho:/opt/C/file_IO/rw_test# ls
makefile  mycp  mycp.c  test1.txt
root@freecho:/opt/C/file_IO/rw_test# ./mycp test1.txt test2.txt
root@freecho:/opt/C/file_IO/rw_test# ls
makefile  mycp  mycp.c  test1.txt  test2.txt
root@freecho:/opt/C/file_IO/rw_test# cat test2.txt
Hello 这是一个cp测试文件
root@freecho:/opt/C/file_IO/rw_test# ./mycp 123
open argv1 error: No such file or directory

 

2.4 strace命令

strace是一个用于跟踪和分析程序系统调用的工具。它在Linux系统上广泛使用,可用于诊断程序执行时的问题,分析程序与操作系统之间的交互,以及定位程序的错误和性能瓶颈。strace可以帮助开发人员和系统管理员深入了解程序的运行情况,包括系统调用、信号、文件操作、网络通信等。

使用strace命令时,它会启动被跟踪的目标程序,并输出程序执行过程中的系统调用和信号等相关信息。可以用于追踪应用程序、脚本、甚至是其他系统命令的执行。

常见用法:

strace <command>

示例:

strace -o output.txt ls  # 将输出重定向到文件
strace -e trace=open,read,write ls  # 只跟踪指定的系统调用
strace -p <PID>  # 跟踪正在运行的进程

strace的输出会显示程序执行过程中每个系统调用的结果、参数和返回值,以及相应的错误信息。通过分析strace的输出,可以帮助定位程序运行时的问题,识别潜在的错误或性能瓶颈,进而进行适当的优化和调试。

 

2.5 预读入缓输出

通过比较 fgetc/fputcread/write 的执行速度,了解预读入缓输出

库函数: getc_cmp_read.c

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

int main(void)
{
    FILE *fp, *fp_out;
    int n;

    fp = fopen("test.txt", "r");
    if (fp == NULL) {
        perror("fopen error");
        exit(1);
    }

    fp_out = fopen("test.cp", "w");
    if (fp_out == NULL) {
        perror("fopen error");
        exit(1);
    }

    while ((n = fgetc(fp)) != EOF) {
        fputc(n, fp_out);
    }

    fclose(fp);
    fclose(fp_out);

    return 0;
}

系统调用:read_cmp_getc.c

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

#define N 1

int main(int argc, char *argv[])
{
    int fd, fd_out;
    int n;
    char buf[N];

    fd = open("test.txt", O_RDONLY); // read
    if (fd < 0) {
        perror("open test.txt error");
        exit(1);
    }

    fd_out = open("test.cp", O_WRONLY | O_CREAT | O_TRUNC, 0664); // rw-rw--r
    if (fd_out < 0) {
        perror("open test.cp error");
        exit(1);
    }

    // read 返回为读取到的字节数
    while ((n = read(fd, buf, N))) { // 0:读到文件结尾结束循环
        if (n < 0) {
            perror("read error");
            break;
        }
        write(fd_out, buf, n);
    }

    close(fd);
    close(fd_out);

    return 0;
}
strace -o read.txt ./read_cmp_getc
strace -o getc.txt ./getc_cmp_read
  1. readwrite系统调用是无用户缓冲区的,它们直接在用户空间和内核空间之间传递数据,每次操作只处理一个字节或一组字节,需要频繁地在用户空间和内核空间之间切换,因此在处理大量数据时可能会比较慢。
  2. fgetcfputc是C标准库提供的函数,它们使用了用户缓冲区。C标准库在打开文件时会为文件分配一个缓冲区,当使用fgetc读取一个字符或使用fputc写入一个字符时,实际上是将数据读取到用户缓冲区或从用户缓冲区写入数据。当用户缓冲区满了或遇到fflushfclose等情况时,数据才会被传输到内核。
  3. 在大多数情况下,fgetcfputc的性能相对于readwrite会慢一些,因为它们多了一层用户缓冲区的处理。但在某些情况下,特别是频繁读写小量数据时,使用C标准库提供的函数可以减少系统调用的次数,从而提高性能。

综上所述,readwrite是直接的系统调用,没有用户缓冲区,适用于大量数据的读写;而fgetcfputc是C标准库提供的函数,使用了用户缓冲区,适用于频繁读写小量数据。选择使用哪种方法取决于具体的应用场景和性能需求。

3. 文件描述符

此部分主要由 chatGPT 生成讲解

3.1 PCB进程控制块

PCB(Process Control Block,进程控制块)是操作系统中用于管理和维护进程信息的 数据结构。每个运行的进程都对应一个唯一的PCB,PCB记录了进程的状态、标识符、优先级、寄存器值、程序计数器、内存分配信息、打开的文件、CPU占用时间等信息。

PCB是操作系统实现多道程序设计和进程调度的基础。当一个进程被调度执行时,操作系统会将当前进程的上下文(包括寄存器状态等)保存在该进程的PCB中,然后加载要执行的进程的上下文,使得新进程可以继续执行。当操作系统决定暂停或切换进程时,会再次保存当前进程的上下文到其PCB中,同时加载下一个要执行的进程的上下文。

PCB 实际上是一个结构体:struct task_struct { 结构体

PCB 通常包含以下字段:

  1. 进程标识符(Process ID,PID):用于唯一标识进程的整数值。
  2. 程序计数器(Program Counter,PC):指向当前执行指令的地址。
  3. 寄存器值:保存进程的寄存器状态,包括通用寄存器、程序状态字(PSW)等。
  4. 进程状态:表示进程的当前状态,如运行、就绪、阻塞等。
  5. 进程优先级:用于进程调度时确定进程的优先级顺序。
  6. 内存分配信息:记录进程占用的内存地址空间。
  7. 打开的文件列表:记录进程打开的文件及其 文件描述符
  8. CPU占用时间:记录进程在CPU上执行的时间。
  9. 其他控制信息:可能包含信号量、消息队列、进程间通信等信息。

PCB在操作系统中起着重要的作用,它是实现多任务、多进程的基础,也是操作系统对进程进行管理和调度的核心数据结构。每个进程都有一个对应的PCB,当进程从运行态切换到阻塞态或就绪态时,PCB中的信息会被更新和保存,以便在合适的时候重新调度和恢复进程的执行。

 

3.2 文件描述符表

文件描述符

文件描述符是在操作系统中用于标识打开文件的整数值。在Unix-like系统中,包括Linux,每个打开的文件都会被分配一个文件描述符,用于标识该文件。文件描述符是一个非负整数,通常由操作系统管理。

在C语言中,使用int类型来表示文件描述符。文件描述符的值为0、1和2通常有特殊意义:

  • 文件描述符0表示标准输入(stdin),它是程序从终端接收输入数据的文件描述符。
  • 文件描述符1表示标准输出(stdout),它是程序向终端输出数据的文件描述符。
  • 文件描述符2表示标准错误(stderr),它用于向终端输出错误信息。

除了标准输入、标准输出和标准错误外,程序还可以通过系统调用(例如openreadwrite等)打开其他文件,从而得到相应的文件描述符。每个新打开的文件都会获得一个尚未使用的最小的非负整数值作为其文件描述符。

文件描述符的主要作用是用于标识文件,从而在程序中进行文件的读取、写入和关闭等操作。在C语言中,通常使用文件描述符来操作文件,例如使用readwrite来读写文件内容,使用close来关闭文件。文件描述符是一个重要的概念,它使得程序可以方便地管理多个打开的文件,并进行相应的文件操作。

文件描述符表

文件描述符表是操作系统中用于管理进程打开的文件的数据结构。在Unix-like操作系统中,每个进程都有一个文件描述符表,它是一个数组,其中的 每个元素都是一个文件描述符

数组下标可以看成指针,指向文件结构体struct file

struct file {
    
}

操作系统隐藏文件结构体内容,只暴露 数组下标,即文件描述符给用户

文件描述符表的大小是由操作系统预先定义的,并且有一定的限制。在Linux系统中,通常默认情况下,每个进程最多可以打开 1024 个文件,这个限制可以通过修改系统参数来调整。

当进程执行一个新程序时,它会继承原有的文件描述符表。这意味着在新程序中也可以继续使用原有的文件描述符来操作文件。

文件描述符表是操作系统管理进程文件操作的重要机制,它使得进程可以方便地访问和操作打开的文件,从而实现了进程与文件之间的交互和通信。

 

3.3 最大打开文件数

最大打开文件数是操作系统对一个进程能够同时打开的文件数量进行限制的值。这个限制是为了保证系统资源的合理分配和控制,防止某个进程滥用资源导致系统资源耗尽。

在Unix-like系统中,包括Linux和macOS等,最大打开文件数由系统参数ulimit来控制。ulimit命令用于设置或显示用户的资源限制,其中包括最大打开文件数。

要查看当前用户的最大打开文件数,可以在终端中运行以下命令:

ulimit -n

通常情况下,ulimit -n 的默认值为1024,即每个进程最多可以同时打开1024个文件。这个值对于普通用户来说已经足够了,但对于某些特殊应用或服务器程序来说可能会不够。

如果需要增加最大打开文件数的限制,可以使用 ulimit 命令进行设置,但是普通用户通常只能增加到一定的限制,超过系统默认值需要管理员权限。

在Linux系统中,还可以通过修改系统配置文件来增加最大打开文件数的限制。这个配置文件通常是 /etc/security/limits.conf,可以在其中添加类似下面的配置:

*       soft    nofile  65535
*       hard    nofile  65535

这里的 65535 是新的最大打开文件数限制值。设置后需要重新登录或重启系统才能生效。

需要注意的是,增加最大打开文件数的限制会占用更多的系统资源,因此在修改配置时要慎重考虑,确保系统有足够的资源支持。不当的配置可能会导致系统性能下降或资源耗尽问题。

 

3.4 FILE结构体

FILE结构体是C标准库中用于文件操作的重要数据结构。它定义在 stdio.h 头文件中,并由C库提供文件读写函数所使用。

在标准C库中,FILE结构体的定义如下:

typedef struct _IO_FILE FILE;

具体的FILE结构体的定义会因不同的编译器和操作系统而有所不同,但通常会包含用于管理文件操作的各种成员变量。

FILE结构体的主要作用是用于维护文件的状态信息,例如文件指针位置、缓冲区、读写模式等。通过FILE结构体,C库能够实现对文件的高效读写操作,并且对于程序员来说,无需直接处理底层的文件描述符,只需使用FILE指针即可。

C标准库中提供了许多基于FILE结构体的文件读写函数,例如:

  • fopen:打开文件并返回一个FILE指针。
  • fclose:关闭文件。
  • fgetc、getc:从文件中读取一个字符。
  • fgets:从文件中读取一行字符串。
  • fputc、putc:将一个字符写入文件。
  • fputs:将字符串写入文件。
  • fprintf:格式化输出到文件。
  • fread、fwrite:二进制读写数据。
  • fseek、ftell:文件指针定位。

使用这些函数,可以方便地进行文件的读写操作,而无需直接操作文件描述符。当然,底层仍然是通过文件描述符来进行实际的文件读写操作,但这一过程对于程序员来说是透明的。

需要注意的是,在使用标准C库的文件读写函数时,要注意及时关闭文件以释放资源,并检查函数返回值以处理可能出现的错误情况。

 

4. 阻塞、非阻塞

4.1 概念

读常规文件是不会阻塞的,不管读多少字节,read 一定会在有限的时间内返回。从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用 read 读终端设备就会阻塞,如果网络上没有接收到数据包,调用 read 从网络读就会阻塞,至于会阻塞多长时间 也是不确定的,如果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的, 而向终端设备或网络写则不一定。

现在明确一下阻塞(Block)这个概念。当进程调用一个阻塞的系统函数时,该进程被置于睡(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比如网络上接收到数据包,或者调用 sleep 指定的睡眠时间到了)它才有可能继续运行。与睡眠状态相对的是运行(Running)状态,在 Linux 内核中,处于运行状态的进程分为两种情况:

  1. 正在被调度执行:CPU 处于该进程的上下文环境中,程序计数器(eip)里保存着该进 程的指令地址,通用寄存器里保存着该进程运算过程的中间结果,正在执行该进程的指令, 正在读写该进程的地址空间。
  2. 就绪状态:该进程不需要等待什么事件发生,随时都可以执行,但 CPU 暂时还在执行 另一个进程,所以该进程在一个就绪队列中等待被内核调度。系统中可能同时有多个就绪的 进程,那么该调度谁执行呢?内核的调度算法是基于优先级和时间片的,而且会根据每个进 程的运行情况动态调整它的优先级和时间片,让每个进程都能比较公平地得到机会执行,同 时要兼顾用户体验,不能让和用户交互的进程响应太慢。

阻塞、非阻塞是设备文件、网络文件的属性

产生阻塞的场景:读设备文件、读网络文件。(读常规文件无阻塞概念) 补:/dev/tty --- 终端文件

注意,阻塞与非阻塞是对于文件而言的。而不是 read、write 等的属性。read 终端,默认阻塞读。

 

4.2 代码样例

block_readtty.c:阻塞阻塞读终端

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

int main()
{
    char buf[10];
    int n;

    // #define STDIN_FILENO 0  STDOUT_FILENO 1  STDERR_FILENO 2
    n = read(STDIN_FILENO, buf, 10);
    if (n < 0) {
        perror("read STDIN_FILENO");
        // printf("%d", errno);
        exit(1);
    }
    write(STDOUT_FILENO, buf, n);

    return 0;
}

nonblock_readtty.c:非阻塞读终端,通过 open 函数 改变文件状态

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

int main()
{
    char buf[10];
    int fd, n;

    fd = open("/dev/tty", O_RDONLY | O_NONBLOCK); // 设置非阻塞状态
    if (fd < 0) {
        perror("open /dev/tty");
        exit(1);
    }

tryagain:

    n = read(fd, buf, 10);

    // 如果read返回-1,并且 errno = EAGIN 或 EWOULDBLOCK,
    // 说明不是read失败,而是read在以非阻塞方式读一个设备或网络文件,并且文件无数据
    if (n < 0) {
        if (errno != EAGAIN) {        // if (errno != EWOULDBLOCK)
            perror("read /div/tty");
            exit(1);
        } else {
            write(STDOUT_FILENO, "try again\n", strlen("try again\n"));
            sleep(2);
            goto tryagain;
        }
    }

    write(STDOUT_FILENO, buf, n);
    close(fd);

    return 0;
}

nonblock_timeout.c:非阻塞读终端和等待超时

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

#define MSG_TRY "try again\n"
#define MSG_TIMEOUT "time out\n"

int main()
{
    char buf[10];
    int fd, n, i;

    fd = open("/dev/tty", O_RDONLY | O_NONBLOCK); // 设置非阻塞状态
    if (fd < 0) {
        perror("open /dev/tty");
        exit(1);
    }
    printf("open /dev/tty ok ... %d\n", fd);

    for (i = 0; i < 5; i++) {
        n = read(fd, buf, 10);
        if (n > 0) {               // 说明读到了东西
            break;
        }
        if (errno != EAGAIN) {          // if (errno != EWOULDBLOCK)
            perror("read /dev/tty");
            exit(1);
        } else {
            write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
            sleep(2);
        }
    }

    if (i == 5) {
            write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT));
    } else {
        write(STDOUT_FILENO, buf, n);
    }

    close(fd);

    return 0;
}

 

5. fcntl 函数

5.1 概念

fcntl 函数是用于操作文件描述符的系统调用,它可以用来执行各种与文件描述符相关的操作。在C语言中,我们可以通过 fcntl 函数来调用这个系统调用。

函数原型

#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );

参数说明

  • fd:要操作的文件描述符。

  • cmd:指定要执行的操作,可以是下列常量之一:

    • F_DUPFD:复制文件描述符。
    • F_GETFD:获取文件描述符标志。
    • F_SETFD:设置文件描述符标志。
    • F_GETFL:获取文件状态标志。
    • F_SETFL:设置文件状态标志。
    • F_GETOWN:获取异步I/O所有权。
    • F_SETOWN:设置异步I/O所有权。
    • F_GETLK:获取文件锁信息。
    • F_SETLK:设置文件锁。
    • F_SETLKW:设置文件锁,但如果锁不可用,则阻塞。
  • arg:根据操作类型 cmd 的不同,可能需要传入其他参数。

返回值

  • 如果执行成功,则根据操作类型 cmd 的不同,返回值也不同。
  • 如果发生错误,则返回 -1,并设置 errno 来指示错误类型。

 

5.2 代码样例

下面以 F_GETFLF_SETFL 操作为例,演示如何使用 fcntl 函数来获取和设置文件状态标志:

fcntl_1.c

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

int main() {
    int fd = open("example.txt", O_RDONLY);  // 打开文件 example.txt,只读模式

    // 获取文件状态标志
    int flags = fcntl(fd, F_GETFL);
    if (flags == -1) {
        perror("fcntl");
        return 1;
    }

    printf("File flags before modification: %d\n", flags);

    // 添加 O_APPEND 标志
    flags |= O_APPEND;
    
    // 设置文件状态标志
    int result = fcntl(fd, F_SETFL, flags);
    if (result == -1) {
        perror("fcntl");
        return 1;
    }

    printf("File flags after modification: %d\n", flags);

    close(fd);
    return 0;
}

在这个例子中,我们首先打开了一个文件 example.txt,然后使用 fcntl 函数获取文件状态标志,接着添加 O_APPEND 标志,最后再次使用 fcntl 函数将修改后的标志设置回去。运行此程序,可以看到文件状态标志的变化。

下面再来一个例子

fcntl_2.c:将文件改为非阻塞状态

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

#define MSG_TRY "try again\n"

int main(void)
{
    char buf[10];
    int flags, n;

    flags = fcntl(STDIN_FILENO, F_GETFL); // 获取 stdin 属性信息

    if (flags == -1) {
        perror("fcntl error");
        exit(1);
    }

    flags |= O_NONBLOCK; // 添加上非阻塞状态
    int ret = fcntl(STDIN_FILENO, F_SETFL, flags);

    if (ret == -1) {
        perror("fcntl error");
        exit(1);
    }

tryagain:
    n = read(STDIN_FILENO, buf, 10);

    if (n < 0) {
        if (errno != EAGAIN) {
            perror("read /dev/tty");
            exit(1);
        }
        sleep(3);
        write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
        goto tryagain;
    }

    write(STDOUT_FILENO, buf, n);

    return 0;
}

 

6. lseek函数

6.1 概念

lseek 函数用于在打开的文件中定位文件指针的位置。它在 C 语言中是一个系统调用,提供了对文件读写位置的控制。

函数原型

#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

参数说明

  • fd:文件描述符,是文件在进程中的标识符。

  • offset:指定了文件指针的偏移量。这个值可以为正、负或零,具体取决于 whence 参数。

  • whence:用于确定 offset 如何解释,它可以取以下值:

    • SEEK_SET:从文件开头开始偏移。
    • SEEK_CUR:从当前文件指针位置开始偏移。
    • SEEK_END:从文件末尾开始偏移。

返回值

lseek 函数返回新的文件指针位置,若出错则返回 -1,并设置全局变量 errno 来表示错误类型。

特别的:lseek 允许超过文件结尾设置偏移量,文件会因此被拓展。

注意文件“读”和“写”使用同一偏移位置。

 

6.2 代码样例

lseek1.c :文件读写使用同一偏移位置

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

int main()
{
    int fd, n;
    char msg[] = "It's a test for lseek\n";
    char ch;

    fd = open("test1.txt", O_RDWR | O_CREAT, 0644);
    if (fd < 0) {
        perror("open lseek.txt error");
        exit(1);
    }

    write(fd, msg, strlen(msg)); // 使用fd对打开的文件进行写操作,读写位置位于文件结尾处

    lseek(fd, 0, SEEK_SET);   // 修改文件读写位置,位于文件开头

    while ((n = read(fd, &ch, 1))) {
        if (n < 0) {
            perror("read error");
            exit(1);
        }
        write(STDOUT_FILENO, &ch, n); // 将文件内容按字节读出,写出到屏幕
    }

    close(fd);

    return 0;
}

lseek2.c :获取文件大小

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

int main(int argc, char *argv[])
{
    int fd = open(argv[1], O_RDWR);
    if (fd == -1) {
        perror("open error");
        exit(1);
    }

    int lenth = lseek(fd, 0, SEEK_END);
    printf("file size: %d\n", lenth);

    close(fd);

    return 0;
}

lseek3.c :拓展文件大小:要想使文件大小真正拓展,必须引起 IO 操作

也可以使用 truncate函数 直接拓展文件

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

int main(int argc, char *argv[])
{
    int fd = open(argv[1], O_RDWR);
    if (fd == -1) {
        perror("open error");
        exit(1);
    }

    int lenth = lseek(fd, 110, SEEK_END);
    printf("file size: %d\n", lenth);

    write(fd, "\0", 1);


    close(fd);

    return 0;
}

补充:

od -tcx filename # 查看文件的 16 进制表示形式 
od -tcd filename # 查看文件的 10 进制表示形式

 

7. ioctl.c 函数 (嵌入式)

7.1 概念

ioctl 是一个在 UNIX/Linux 系统中用于设备控制的函数。它用于与设备驱动程序进行通信,进行设备的配置、状态查询和控制等操作。

函数原型

#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);

参数解释

  • fd:文件描述符,用于指定要控制的设备。
  • request:一个无符号长整型参数,表示控制请求的编号。通常使用预定义的常量或者自定义的控制码。
  • ...:可选参数,如果 request 需要传递额外的数据,可以使用可变参数传递。

工作原理

ioctl 函数的作用是向设备驱动程序发送特定的控制命令(request),然后设备驱动程序根据接收到的命令执行相应的操作。这些操作可能包括设备的配置、状态查询、性能优化等。不同的设备驱动程序支持的控制命令是不同的,因此在使用 ioctl 函数时,需要参考设备驱动程序的文档或者相关头文件来了解可用的控制命令。

使用场景

ioctl 函数通常用于与特殊设备进行交互,例如硬件设备(如串口、USB 设备等)、网络设备、字符设备、以及其他类型的外设等。通过 ioctl 函数,用户可以向设备驱动程序发送自定义的控制命令,从而实现设备的灵活控制和配置。

注意事项

使用 ioctl 函数需要小心,因为它是一个较为底层的接口,如果使用不当,可能导致系统崩溃或不稳定。在使用 ioctl 函数时,建议查阅相关的文档和资料,了解设备驱动程序支持的控制命令和参数,以及正确的使用方法。

 

7.2 代码样例

ioctl.c

#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>

int main() {
    int fd = open("/dev/ttyS0", O_RDWR); // 打开串口设备文件

    if (fd < 0) {
        perror("open");
        return 1;
    }

    // 设置串口波特率为 9600
    int baud_rate = 9600;
    if (ioctl(fd, TIOCSBRSRATE, &baud_rate) < 0) {
        perror("ioctl");
        return 1;
    }

    // 关闭设备文件
    close(fd);
    return 0;
}

 

8. 传入传出参数

在编程中,函数的参数可以分为 传入参数(Input Parameter)传出参数(Output Parameter)传入传出参数(Input/Output Parameter) 三种类型。

这些参数的区别在于函数如何使用它们以及对它们的修改和返回。

8.1 传入参数

  1. 指针作为函数
  2. 通常有 const 关键字修饰
  3. 指针指向有效区域,在函数内部做读操作

8.2 传出参数

  1. 指针作为函数
  2. 在函数调用之前,指针指向的空间可以无意义,但必须有效
  3. 在函数内部,做写操作
  4. 函数调用结束后,充电函数返回值

8.3 传入传出参数

  1. 指针作为函数
  2. 在函数调用之前,指针指向的空间有实际意义
  3. 在函数内部,先做读操作,后做写操作
  4. 函数调用结束后,充当函数返回值

 

9. 扩展阅读

关于虚拟 4G 内存的描述和解析

此部分内容可以在学到 进程 时观看

一个进程用到的虚拟地址是由内存区域表来管理的,实际用不了 4G。而用到的内存区域,会通过页表映射到物理内存

所以每个进程都可以使用同样的虚拟内存地址而不冲突,因为它们的物理地址实际上是不同的。内核用的是 3G 以上的 1G 虚拟内存地址

其中896M 是直接映射到物理地址的,128M 按需映射 896M 以上的所谓高位内存。各进程使用的是同一个内核。

首先要分清 可以寻址实际使用 的区别。

其实我们讲的每个进程都有 4G 虚拟地址空间,讲的都是“可以寻址”4G,意思是虚拟地址的 0-3G 对于一个进程的用户态和内核态来说是可以访问的,而 3-4G 是只有进程的内核态可以访问的。并不是说这个进程会用满这些空间。

其次,所谓“独立拥有的虚拟地址”是指对于每一个进程,都可以访问自己的 0-4G 的 虚拟地址。虚拟地址是“虚拟”的,需要转化为“真实”的物理地址。

好比你有你的地址簿,我有我的地址簿。你和我的地址簿都有 1、2、3、4 页,但是每 页里面的实际内容是不一样的,我的地址簿第 1 页写着 3 你的地址簿第 1 页写着 4,对于你、 我自己来说都是用第 1 页(虚拟),实际上用的分别是第 3、4 页(物理),不冲突。

内核用的 896M 虚拟地址是直接映射的,意思是只要把虚拟地址减去一个偏移量(3G) 就等于物理地址。同样,这里指的还是寻址,实际使用前还是要分配内存。而且 896M 只是 个最大值。如果物理内存小,内核能使用(分配)的可用内存也小。

 

相关链接

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

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

Linux基础命令:Linux学习资料分享 – Echo (liveout.cn)

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

Vim配置:Linux学习资料分享 – Echo (liveout.cn)

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

  •