fuse实现webfs

fuse是一个用户空间级别的文件系统接口,大家都知道文件系统通常在内核中实现,但是因为一些原因,有一些文件系统不方便在在内核中实现,比如版权的原因,或者开发者不想写C,于是fuse诞生了,总之fuse就是一个让你在用户空间实现文件系统的接口,虽然是在用户空间实现的,但是文件系统该有的接口,fuse也基本不拉,不过,看用户的需求,如果你只要实现一个很简单的功能,那么,也许一个只需要实现4-5个就可以满足你的需求,接下来我们来实现一个基于web的文件系统。
在linux下,我们跨机器传输文件经常会用到如下这个命令:

python -m SimpleHTTPServer

这个命令会在本地起一个端口,然后从另一个机器的访问这个http端口,就可以访问文件并且下载,相信很多同学对这个不陌生。
那么,有没有办法把这种类似的接口挂载到本地,像访问文件夹一样cp呢?
那我们用fuse就可以实现这个功能,基本的我们实现如下几个接口就可以:
getattr:获得文件属性,是普通文件还是目录,文件权限,在我们常见下文件都为444,文件夹都是555,因为要保证进入文件夹,需要x权限
read:读取文件内容的接口,需要支持offset
readdir:读取文件夹下的子文件,返回是一个列表
具体的代码如下:

    def getattr(self, path, fh=None):
        wh = self.getwebheader(path)
        if "Content-Length" in wh:
            st_size = int(wh["Content-Length"])
        else:
            st_size = 0
        tplist = path.split("/")
        ppath = "/".join([i for i in tplist if i != ""][0:-1]) + "/"
        w = self.getweb(ppath)
        if re.search('a href="' + path.lstrip("/") + '/"', w) or path == "/":
            st_mode = 0o40555
        else:
            st_mode = 0o100444
        attr_dict = {
            "st_mode": st_mode,
            "st_size": st_size,
                 }
        return attr_dict

    getxattr = None
    listxattr = None

    def open(self, path, flags):
        self.fd += 1
        return self.fd

    def read(self, path, size, offset, fh):
        w = self.getweb(path, size, offset)
        return w

    def readdir(self, path, fh):
        nulldir = ['.', '..']
        w = self.getweb(path)
        allfiles = re.findall('a href="(.*)"', w)
        dirs = [fs[0:-1] for fs in allfiles if fs.endswith("/")]
        files = [fs for fs in allfiles if not fs.endswith("/")]
        rsdir = files + dirs + nulldir
        return rsdir

    def getweb(self, path, size=None, offset=None):
        if size != None:
            headers = {"Range": "bytes=%d-%d" % (offset, offset + size - 1)}
        else:
            headers = {}
        r = requests.get(self.url + path, headers=headers)
        return r.content

    def getwebheader(self, path):
        r = requests.head(self.url + path)
        return r.headers

这个文件完整版本在这里:https://github.com/lishuai860113/fusepy/blob/master/examples/webfs.py
这样我们在远端起一个SimpleHTTPServer接口,然后本地这样挂载一下:

python webfs.py http://10.20.40.170:8000 /mnt/tmp

就可以在/mnt/tmp下读取到你web上的内容了。
那这样有一点美中不足的地方,在我们cp大文件的时候,会报错,这里是因为SimpleHTTPServer不支持下载的时候指定range,我们read的时候,一般都是按照一个block 4096去读取的,所以一定要web服务器支持range才可以,那我们也准备了一个支持range的server :https://github.com/lishuai860113/fusepy/blob/master/examples/RangeHTTPServer.py
用这个就没有问题了,另外go也写了一个简单的接口:
https://github.com/lishuai860113/UploadFileServer
go这个库原生是支持range的,所以没有问题的。
这样,就可以做出自己想要的文件系统了,祝玩的开心!

发表在 python, 好玩的linux | 留下评论

centos6.6 pidns_get空指针问题

某天数据库的同学抱怨服务器不断重启,登录发现均有crash的记录,查看之后发现是最近某支撑部门部署的agent导致,而且集中在centos6.6的系统中,crash中dmesg中有如下信息:

PID: 4361   TASK: ffff881024318aa0  CPU: 23  COMMAND: "java"
……
    [exception RIP: kref_get+12]
    RIP: ffffffff8128f42c  RSP: ffff881022f61e38  RFLAGS: 00010292
    RAX: 0000000000000000  RBX: 0000000000000000  RCX: 00000000fffffff3
    RDX: 0000000000000000  RSI: 0000000000000000  RDI: 0000000000000000
    RBP: ffff881022f61e48   R8: 0000000000000000   R9: 0000000000000000
    R10: 0000000000000000  R11: 0000000000000001  R12: ffffffff8161b040
    R13: 0000000000001001  R14: 00007f1bf4dfd2a0  R15: 0000000000000000
    ORIG_RAX: ffffffffffffffff  CS: 0010  SS: 0018
#9 [ffff881022f61e50] pidns_get at ffffffff810d69f6
#10 [ffff881022f61e70] proc_ns_readlink at ffffffff81203558

这里截取了关键的部分,发现是在pidns_get中调用了kref_get,但是kref_get获取到的参数,是空的指针,所以导致出错,我们查看了pidns_get的定义:

static void *pidns_get(struct task_struct *task)
{
        struct pid_namespace *ns;

        rcu_read_lock();
        ns = get_pid_ns(task_active_pid_ns(task));
        rcu_read_unlock();

        return ns;
}

这里因为task_active_pid_ns(task)可能会返回NULL,但是get_pid_ns是这么定义的:

static inline struct pid_namespace *get_pid_ns(struct pid_namespace *ns)
{
        if (ns != &init_pid_ns)
                kref_get(&ns->kref);
        return ns;
}

这里kref_get 去拿&ns->kref的时候,因为ns是NULL,所以会发生空指针访问异常。
正常是需要判断值为NULL的逻辑的,但是这里并没有,我们对比观察下没有问题的centos6.8系统上的代码:

static void *pidns_get(struct task_struct *task)
{
        struct pid_namespace *ns;

        rcu_read_lock();
        ns = task_active_pid_ns(task);
        if (ns)
                get_pid_ns(ns);
        rcu_read_unlock();

        return ns;
}

发现这里判断了当ns不为NULL的时候,才会去执行get_pid_ns(),这样自然不会有问题。

发表在 好玩的linux | 留下评论

linux glob_filename 解析超长字符串致夯死问题

0.背景

大清早的同事过来找,说有个机器无法登录之后无法通过sudo su -切换到root,上去看了一下,发现确实如此,于是开始愉(dan)快(teng)的排障之旅。

1.思路

确定应该不是shell本身的原因导致,因为普通权限的用户可以登录,并且没有问题,但是切换到root的时候会卡住,于是怀疑bash的rc文件或者profile文件导致问题,重点排查这个环节

2.步骤

2.0

首先,现在切不到root,很多不方便的地方,虽然root bash不可以用,但是还有其他很多shell哦,没有装?装一个,sudo yum install csh,然后sudo csh,可以获得root权限,后续就方便很多了

2.1

开启另一个终端做调试,执行sudo su – ,夯住:

htop查看下负载,单核跑满:

确认是bash进程跑满单核,并且内存占用会逐步上升

2.2

查看了bashrc跟profile.d下面所有文件,并未发现异常于其他机器的,开始怀疑特殊的场景,诱发了bash,或者某些rc文件的bug,尝试用perf top -p pid 分析下,查看下函数cpu占用情况:

2.3

搜索资料发现,上面三个占用cpu最高的函数,是处理字符串的函数,怀疑某种特殊字符的字符串导致bash僵死,后来证明这个猜测是错误的,不过还好方向没走太偏。

要找到bash内部的问题,目前看来只能尝试gdb去跟踪了,先sudo gdb bash跟踪了一次,发现输出都是汇编,还是很头疼的,为了将汇编跟源代码对应起来,需要安装debuginfo的包,安装的debuginfo包方法在这里:

https://www.jianshu.com/p/5b4ef8112b97

在这个场景,我们需要安装的是bash,glibc的debuginfo包。

装好之后,gdb走起

2.4

sudo gdb bash 执行之后,run一下,hang住之后,等一段时间,大约10s,ctrl c掉

然后执行bt查看下栈信息,装了debuginfo终于可以看到源码跟参数信息了,泪目。上下翻下栈的详细信息

可以看到,栈深度已经到达49,正常不会有这么多的,事后复现发现,栈深度也会持续增加,从第1个栈帧到34个,基本都是雷同的内容,猜测有递归调用,而其中的内容,应该是应用层的内容,开始排查profile.d 下面的各个文件发现有一行:

这段逻辑是要把history中上一条执行记录提取出来,发送到日志中,但是在echo $y的时候,命令行中的参数会被解释!

2.5

把这个文件移走之后,发现可以正常切换了,断定问题根源在这个文件

3.复现

3.0

原理,linux在shell中遇到形如 /dir/* 的参数的时候,会解析展开其中的内容如:/dir/f1 /dir/f2 /dir/f3 ……

如果某个目录下文件太多,那会导致这个解析过程处理的字符串过大,由于解析逻辑中有递归的过程,也导致调用不断增加,内存不断增加,最后导致夯住,持续下去,进程也会耗光内存,导致OOM

3.1

如果希望复现,我们可以构建一个较长的目录名称,然后在这个目录下创建大量文件:

mkdir /tmp/123456789012345678901234567890123456789012345678901234567890
cd /tmp/123456789012345678901234567890123456789012345678901234567890
for i in `seq 1 10000`
do
touch $i
done

这样,我们在执行如下命令的时候:

echo /tmp/123456789012345678901234567890123456789012345678901234567890/* >/dev/null

参数就会被解析,最终会展开一个几十万byte的字符串

以上这个场景,bash会处理十几分钟,最后会报错太长参数退出。

大家可以尝试构建更多的文件,夯更久一些,直到OOM,玩的开心

4.总结

总体来说,排障的思路还是没有走入太多的歪路,gdb还是神器,后续可能还要继续了解使用下,后续类似这种问题,其实简单来说可以把profile.d跟bashrc移走先,基本可以快速定位问题文件。其中也使用strace以及bash -x 也可以发现一些端倪,如果思路清晰对位,最终一定能找到真正的问题所在。

发表在 debug, 好玩的linux | 留下评论

MTU导致通信不正常问题

其实这个问题一看到题目就知道了,但是分析起来还是有点绕弯弯,虽然是一个貌似很常见的问题,但是职业生涯还是第一次遇到,记录一下。

下午业务跟网络的同学反馈,某云主机访问自建IDC个别服务不正常,拿过一份抓包文件看了下,看到有重传,但是并没有特别的明显标示,于是建议两端转包,然后拿到了如下两份抓包结果:

服务端:

客户端:

从图上看出,服务器收到客户端的get请求之后,回应了两个个2962+140的包,当然,因为打开了网卡拆分tcp选项,所以2962的包到网络必然会被拆成两个包,但是从对方来看,并没有收到2962的包内容,然后客户端回了一个dup ack,表示中间丢包了,服务器端连续发了7个包,客户端都未收到,然后客户端在1分钟后发送keep alive信息,服务端发送rst,会话终止,注意其中服务端发送的多个包,客户端未收到,总结下来,看起来较大的包,客户端都没收到,怀疑是MTU问题,拿到云上环境其他主机看了下,确实MTU是1400,比我方的1500小,但是注意截图中红框的部分,客户端TCP通告的MTU是1460,那么说明对方服务器配置的MTU可能仍然是1500,因为MSS通常是 网卡MTU-ip包头20-tcp包头20算出来的,登录上对方的服务器检查,发现MTU确实跟其他机器不一致,是1500。

那么,对方正常云主机MTU配置都是1400,应当是有依据的,因为linux默认是1500,应该是对方基础网络设备上的限制,必须要1400,大一些的可能通不过部分网络设备,所以,即使对方服务器配置1500,通告到我方之后,我方按较大的报文发送,到了对方网络环境当中某些节点,可能还是会被丢弃,这就是问题的原因。跟对方运维负责人沟通后,也验证了这一问题。

发表在 好玩的linux, 网络 | 留下评论

linux intel KPTi 补丁对性能影响

2018年最火的是什么,当然是intel的漏洞大礼包了,几十年来cpu全部中招,rhel也马上推出了补丁,补丁对性能的影响,大家也是看法不一,有的说2%,有的说20%到30%之间,那么具体的影响有多少,为什么性能影响上有很大分歧呢,这里简要的介绍一下。

根据漏洞描述,此补丁影响的是linux syscall跟进程切换的开销,如果是纯计算型的应用,基本不受影响,正好sysbench有一个线程创建的测试,我们通过如下命令:

sysbench --test=threads --num-threads=32 --thread-yields=100000 --thread-locks=80 run

同样的命令,在打过补丁跟没打过补丁的场景来看,打过补丁的机器需要80s跑完,而没打过补丁的场景,只需要30s即可跑完,那么从这个场景来看,性能的损失,大约有60%,大概算是影响较大的一个场景了,虽然正常的应用,不应该有很频繁的切换跟调用,但是,补丁对于这个切换跟调用下的性能影响,绝对不容小觑。

 

发表在 好玩的linux | 留下评论