坚持思考,就会很酷
终于到了动手的环节,今天我们直接搞起一个叫做 hello world 的文件系统,附上全部代码实现,且可以体验测试。
环境准备:
确认 Linux 内核支持 FUSE
root@ubuntu:~# modprobe fuse
命令没有报错的话,就说明内核支持 fuse ,并且已经加载。
在 02 FUSE 框架篇我们介绍了 FUSE 协议,说到了 FUSE 框架的 3 组件:内核 fuse 文件系统,用户态 libfuse 库,fusermount 工具。
内核的 fuse 文件系统只有一份,用于承接 vfs 请求,封装成 FUSE 协议包,走 /dev/fuse
建立起来的通道转发用户态。用户态的任务就是把 FUSE 协议包解析出来并且处理,然后把请求响应按照 FUSE 协议封装起来,走 /dev/fuse
通道传回内核,由 vfs 传回用户。
所以,我们看到用户态 libfuse 库这个东西其实就只是 FUSE 协议解析和封装用的。童鞋们,注意啦,重点来了。划重点:只要是数据协议
,就有一个特点:和具体语言无关
。数据协议格式只不过是对字节流的分析方式而已
FUSE 的协议也是如此,libfuse 这个是用 c 语言实现的 FUSE 协议库,官方的 Github 地址是:https://github.com/libfuse/libfuse/ 。Go 语言不能直接用 libfuse 库,因为 libfuse 库全都是封装成了 c 的结构体。
这是我们要迈过的第一道关,就是用 Go 语言来解析 FUSE 协议。好吧,准备开始啦。首先看一下 FUSE 的数据包格式:
先思考一个问题:libfuse 做了哪些事情?
然后,要和建立 /dev/fuse
通道;
然后,要实现一个 Server 服务端,监听这个通道,这样就和内核 fuse 建立了联系,接收和发送消息;
然后,对不同的请求做不同的解析;
比如 read 的 Opcode 是,write 的 Opcode 是 ;
write 请求携带用户数据,其他请求不携带;
然后,把解析好的 FUSE IO 请求转发给用户态文件系统;
然后,接收用户态文件系统的 IO 响应,封装成 FUSE 响应格式;
然后,不同的请求有不同的响应,做不同的处理,算了吧,太麻烦了,
read 请求的响应携带用户数据,其他请求则不同;
Go 的 FUSE 协议库也要做以上这些东西。其实,任何一种协议数据格式的解析从来都是索然无味的,因为代码实现的逻辑功能是确定的。这里推荐一个 Go 的 FUSE 库:bazil/fuse,这是一个纯 Go 写的 FUSE 协议解析库,作用和 libfuse 这个纯 c 语言写的库作用完全一样。
bazil.org/fuse
is a Go library for writing FUSE userspace filesystems.
有了这个 Go FUSE 协议解析库,就可以开始写文件系统的程序了。我们自己能参与创造的部分才是真正感兴趣的。
我们下面写了一个 helloworld
的文件系统,首先说结论,hellofs
实现了以下功能:
hello
的文件(注意:不需要用户创建哦,直接挂载之后就有了);cat
这个 hello
将会返回 hello, world
的内容;hello
文件的属性:inode 为 20210606,mode 为 444;跟我一起创建出一个叫做 helloword.go 的文件,写入下面的代码:
// 实现一个叫做 hellfs 的文件系统
package main
import (
"context"
"flag"
"log"
"os"
"syscall"
"bazil.org/fuse"
"bazil.org/fuse/fs"
_ "bazil.org/fuse/fs/fstestutil"
)
func main() {
var mountpoint string
flag.StringVar(&mountpoint, "mountpoint", "", "mount point(dir)?")
flag.Parse()
if mountpoint == "" {
log.Fatal("please input invalid mount point\n")
}
// 建立一个负责解析和封装 FUSE 请求监听通道对象;
c, err := fuse.Mount(mountpoint, fuse.FSName("helloworld"), fuse.Subtype("hellofs"))
if err != nil {
log.Fatal(err)
}
defer c.Close()
// 把 FS 结构体注册到 server,以便可以回调处理请求
err = fs.Serve(c, FS{})
if err != nil {
log.Fatal(err)
}
}
// hellofs 文件系统的主体
type FS struct{}
func (FS) Root() (fs.Node, error) {
return Dir{}, nil
}
// hellofs 文件系统中,Dir 是目录操作的主体
type Dir struct{}
func (Dir) Attr(ctx context.Context, a *fuse.Attr) error {
a.Inode = 20210601
a.Mode = os.ModeDir | 0555
return nil
}
// 当 ls 目录的时候,触发的是 ReadDirAll 调用,这里返回指定内容,表明只有一个 hello 的文件;
func (Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
// 只处理一个叫做 hello 的 entry 文件,其他的统统返回 not exist
if name == "hello" {
return File{}, nil
}
return nil, syscall.ENOENT
}
// 定义 Readdir 的行为,固定返回了一个 inode:2 name 叫做 hello 的文件。对应用户的行为一般是 ls 这个目录。
func (Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
var dirDirs = []fuse.Dirent{{Inode: 2, Name: "hello", Type: fuse.DT_File}}
return dirDirs, nil
}
// hellofs 文件系统中,File 结构体实现了文件系统中关于文件的调用实现
type File struct{}
const fileContent = "hello, world\n"
// 当 stat 这个文件的时候,返回 inode 为 2,mode 为 444
func (File) Attr(ctx context.Context, a *fuse.Attr) error {
a.Inode = 20210606
a.Mode = 0444
a.Size = uint64(len(fileContent))
return nil
}
// 当 cat 这个文件的时候,文件内容返回 hello,world
func (File) ReadAll(ctx context.Context) ([]byte, error) {
return []byte(fileContent), nil
}
简单说下上面做了什么事情:
readdir
和 getattr
的行为回调;readall
和 getattr
的行为回调;好,激动人心的时候到了,我们先编译出这个程序,然后跑起来就是可用一个极简的文件系统了。麻雀虽小,五脏俱全。
root@ubuntu:~/gopher/src# go build -gcflags "-N -l" ./helloworld.go
成功编译,获得二进制文件 helloworld
。
创建一个空目录当做挂载点,笔者是在 /mnt
目录下创建了一个叫做 myfs
的目录。
root@ubuntu:~# mkdir /mnt/myfs/
好,现在我们用户文件系统程序准备好了,挂载点也准备好了,万事俱备了,可以运行了。命令如下:
root@ubuntu:~/gopher/src# ./helloworld --mountpoint=/mnt/myfs --fuse.debug=true
参数说明:
mountpoint
:指定挂载点目录,也就是上面创建的空目录 /mnt/myfs/
;fuse.debug
:为了更好的理解用户文件系统,可以把这个开关设置成 true
,这样用户发送的请求对应了后端什么逻辑就一目了然了;测试跑起来之后,如果没有任何异常,helloworld
就是作为一个守护进程,卡主执行,没有任何日志。直到收到请求。
这个时候,我们这个终端窗口就不要动了(待会可以看日志),再新开一个终端用来测试。
现在我们从多个角度测试下 hellofs ,感受下自己做的第一个用户文件系统是什么样子的。
首先,文件系统一定要挂载才能用,所以 df
命令可以看到挂载情况:
root@ubuntu:~# df -aTh|grep hello
helloworld fuse.hellofs 0.0K 0.0K 0.0K - /mnt/myfs
看到了不?有一个叫做 helloworld
,类型为 fuse.hellofs
的文件系统。这两个名字都是代码里指定的。
然后,如果挂载了 fusectl 文件系统(内核 fuse 文件系统),那么还可以在 /sys/fs/fuse/connections
看到比以前多一个数字命名的目录。
我们通过 ls,stat,cat 等命令对 hellofs 文件系统探测一下。
第一个问题:stat
查看一下挂载点 stat /mnt/myfs
?能得到什么数据?
root@ubuntu:~# stat /mnt/myfs/
File: '/mnt/myfs/'
Size: 0 Blocks: 0 IO Block: 4096 directory
Device: 29h/41d Inode: 20210601 Links: 1
Access: (0555/dr-xr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2021-06-06 13:49:02.463926775 +0800
Modify: 2021-06-06 13:49:02.463926775 +0800
Change: 2021-06-06 13:49:02.463926775 +0800
Birth: -
我们看到特殊的 inode:20210601,权限:555 (回忆下上面的代码实现,目录的 inode 为 20210601 就是我们指定的)。如下:
注意,在 stat /mnt/myfs
的同时,用户文件系统会打印出日志:
root@ubuntu:~/code/gopher/src/myfs# ./helloworld --mountpoint=/mnt/myfs --fuse.debug=true
2021/06/06 13:49:04 FUSE: <- Getattr [ID=0x2 Node=0x1 Uid=0 Gid=0 Pid=891] 0x0 fl=0
2021/06/06 13:49:04 FUSE: -> [ID=0x2] Getattr valid=1m0s ino=20210601 size=0 mode=dr-xr-xr-x
这个日志明确的告诉了我们,先收到了一个 Getattr
的请求,请求参数是什么,然后 hellofs
处理完成之后,返回了什么样的响应。
第二个问题:ls /mnt/myfs
的反应呢?
root@ubuntu:~# ls -l /mnt/myfs/
total 0
-r--r--r-- 1 root root 13 Jun 6 13:49 hello
我们看到了一个 hello 的文件(体会一下,我们没有创建过这个文件哦)。
那么,stat /mnt/myfs/hello
会得到什么?
root@ubuntu:~# stat /mnt/myfs/hello
File: '/mnt/myfs/hello'
Size: 13 Blocks: 0 IO Block: 4096 regular file
Device: 29h/41d Inode: 20210606 Links: 1
Access: (0444/-r--r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2021-06-06 13:49:02.463926775 +0800
Modify: 2021-06-06 13:49:02.463926775 +0800
Change: 2021-06-06 13:49:02.463926775 +0800
Birth: -
我们看到了特殊的 inode 20210606。
第三个问题:cat /mnt/myfs/hello
这个文件?
root@ubuntu:~# cat /mnt/myfs/hello
hello, world
我们看到 hello,world 的返回,虽然你从来没写过这个文件。
第四个问题:请大家体会一下 hello
这个文件,这个文件和你平时见的文件有什么区别呢?
这个是一个额外的问题,也是要读者朋友重点思考的一个问题。思考 hello 这个文件的特殊性:
以上的问题你想通了吗?可以先思考下,或者找我交流。
请记住,文件这个概念从来都是一个逻辑的对象。是文件系统给你的一个抽象的对象。换句话说,文件表现的任何信息都只是文件系统想要展现给你的而已。
你看到的只是 FS 想要你看到的而已!!!
libfuse
是纯 c 实现的 FUSE 协议解析库,如果你想用 c 语言实现一个用户文件系统,那么选它就对了;bazil.org/fuse
是纯 Go 实现的 FUSE 协议库,我们用 Go 语言实现用户文件系统,那么选它就对了;hello,world
的文件系统;这次实现了一个完整的 helloworld 用户态文件系统,最后留个思考题?实现 hellofs 之后,你理解“文件”是什么呢?
有了这一次的基础,下一次我们实现一个更复杂的文件系统:加密的分布式文件系统,敬请期待。
~完~
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/Yf6yBoEQe6ijMlPgZ6P2sA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。