一个小bug,引出对linux启动机制systemd的代码分析(代码片段)

beyondma beyondma     2023-03-07     307

关键词:

最近我在生产上遇到一个非常有意思的问题,在Cent OS7以上的操作系统中,VG卷组一激活其默认对应的文件系统也一并挂载上了,而且这还不是红帽和CentOS的特有问题,如果fstab配置default参数的话,其它Linux发行版也有同样的问题。

一般来说用户只需要在启动的时候自动挂载文件系统,在日常使用中激活卷组后,用户很可能希望把这个卷组挂载到其它位置上,而非默认位置,Systemd和fstab这样的联动操作其实给用户日常的使用带来了不少的困扰。

这其实应该是systemd与fstab配合的一个bug,为了说清这个问题,我们先来看,fstab的使用方法,/etc/fstab 文件包含了如下字段,通过空格或 Tab 分隔:

<file system> <dir> <type> <options> <dump> <pass>

  • <file systems>:要挂载的分区或存储设备.
  • <dir>:文件系统的挂载位置。
  • <type>要挂载设备或是分区的文件系统类型,
  • <options>挂载时使用的参数,这个是我们重点要说的。

其中auto在启动时时自动挂载。

defaults使用文件系统的默认挂载参数,默认参数为:rwsuiddevexecautonouserasync也就是包含了auto这个参数

正如前文所说按照常理理解,auto应该是单指启动时的自动挂载。

初识systemd

在Cent os 7版本之前,红帽系的Linux一直采用init机制来进行系统初始化,现在还有很多经典书籍在介绍Linux启动时还是会详细说明0号init进程的由来,总体来说systemd之前的sysvinitupstart没有太大区别,upstart只是一个支持USB启动的并行版sysvinit

systemd的出现颇有后来者居上的气势,目前已经基本统一了linux初始化工具的江湖,它克服 sysvinit串行执行启动步骤的,大幅提高系统的启动速度。凭借着优异的表现目前upstart的拥趸Ubuntu也开始在最新版本中使用systemd了。

systemd提供了和 sysvinit 兼容的特性原先版本系统中已经存在的服务和进程无需修改。这大幅降低了用户的升级成本,使得 systemd的升级替换相对比较平滑。而且systemd 提供了比 upstart 更优秀的并行启动能力,采用了 socket / D-Bus activation 等技术启动服务。并取得了更快的启动速度我们明显可以感受到在相同配置的配置下,CentOS7比6启动速度要快

但是因为systemd过于激进了,这其实也是造成前文那个BUG的直接原因。

systemd如何了解系统启动情况

systemd在进行启动任务编排并控制系统其它服务(service)时,需要详细了解系统当前的状态,我们看到systemd使用的技术基于inotify的钩子机制进行的。

比如在https://github.com/systemd/systemd/basic/fs-util.c下的inotify_add_watch_fd函数就是一个添加监视点的入口。

int inotify_add_watch_fd(int fd, int what, uint32_t mask) 

        int wd;



        /* This is like inotify_add_watch(), except that the file to watch is not referenced by a path, but by an fd */

        wd = inotify_add_watch(fd, FORMAT_PROC_FD_PATH(what), mask);

        if (wd < 0)

                return -errno;



        return wd;

而再翻开Linux下inotify的代码,你会发现添加监控点及通知操作的代码当中都是有加锁动作的,尤其是在新建监控点时甚至直接上了自旋锁。

https://github.com/torvalds/linux/blob/f8fbb47c6e86c0b75f8df864db702c3e3f757361/fs/notify/inotify/inotify_user.c

static int inotify_new_watch(struct fsnotify_group *group,

     struct inode *inode,

     u32 arg)



struct inotify_inode_mark *tmp_i_mark;

__u32 mask;

int ret;

struct idr *idr = &group->inotify_data.idr;

spinlock_t *idr_lock = &group->inotify_data.idr_lock;//直接上spinlock



mask = inotify_arg_to_mask(inode, arg);



tmp_i_mark = kmem_cache_alloc(inotify_inode_mark_cachep, GFP_KERNEL);

if (unlikely(!tmp_i_mark))

return -ENOMEM;



fsnotify_init_mark(&tmp_i_mark->fsn_mark, group);

tmp_i_mark->fsn_mark.mask = mask;

tmp_i_mark->wd = -1;



ret = inotify_add_to_idr(idr, idr_lock, tmp_i_mark);

if (ret)

goto out_err;



/* increment the number of watches the user has */

if (!inc_inotify_watches(group->inotify_data.ucounts)) 

inotify_remove_from_idr(group, tmp_i_mark);

ret = -ENOSPC;

goto out_err;





/* we are on the idr, now get on the inode */

ret = fsnotify_add_inode_mark_locked(&tmp_i_mark->fsn_mark, inode, 0);

if (ret) 

/* we failed to get on the inode, get off the idr */

inotify_remove_from_idr(group, tmp_i_mark);

goto out_err;







/* return the watch descriptor for this new mark */

ret = tmp_i_mark->wd;



out_err:

/* match the ref from fsnotify_init_mark() */

fsnotify_put_mark(&tmp_i_mark->fsn_mark);



return ret;

我们知道加锁区域内的代码一定要尽力的简洁,耗时操作是严格禁止的,只有这样才能提升内核的运转效率,尤其是自旋锁会将所有CPU全部阻塞住,更是要加小心,因此systemd在接收到inotify的通知时必须要以最快速度,运行完成全部代码。

VG一激活文件系统就挂载的问题到底能不能改

对于VG到底是在启动时被激活的,还是由用户手工激活的,systemd完全可以判断出来,但是这个判断是有代价的,由于inotify的api当中并没有提供一个标志是否为启动时调用的onboot标志,因此systemd只能自行判断系统是否处在启动态,而加这个判断无论是想取得系统的运行时间,还是要取得用户登陆状态,对于systemd这种优化到极致的软件来说,可能都是不能接受的。

为了验证上述是观点,我们可以在实测中尝试在通知处理函数中加入一些耗时操作来观察对于系统的影响,由于inotify可以改变原系统调用(syscall)行为的,
受试验实验环境所限,我手头机器的硬件水平不足以支持我搭建一个虚拟机平台任意测试的要求,因此模拟当中我们使用了另一个监测机制kprobe来进行,和inotify相比,kprobe属于旁路监测重量级较轻,我自行注册了一个监测文件变化的探针,然后在处理事件时加入延时操作,观察对于系统IO的影响。

 

结果测试发现在我所在的4核/8G的平台上,每秒钟文件打开、关闭文件操作的次数,由每秒867次的锋值下降到了72次,90%以上的下降。因此这个在systemd项目下开了近三年的ISSUE似乎没有好的解法,无论是sysvinit的0号init进程机制,还是在inotify的处理函数中加入系统运行状态的判断,都不是好的办法。如果各位读者有什么好的建议或者思路可以留言提供一下,咱们共同探讨!

由一个bug引出java包装类型

    工作中遇到过一个bug,用两个POJO的Integer字段做==判断,明明“数值”相等结果返回false。检查代码,调试,看源码搞了好久,才知道是Java包装类理解不够惹的祸。 为了弄清楚其中的本质,先上一段代码:1i... 查看详情

升级springboot版本,引出了一个大bug(代码片段)

...本,由之前的2.0.4升级到最新版本2.7.5,却引出了一个大Bug。到底是怎么回事呢?1.案发现场有一天,项目组的同事反馈给我说,我之前有个接口在新的测试环境报错了,具体异常是:Missingargmentlevelformet... 查看详情

java小程序的一个bug

...pac.java:13)第13行就是我写的那个最后一行,主函数就4行,一个列表的程序。请问报错哪行怎么了intarray[]=4,3,2,1;System.out.printf("%s%8s\n","manu","count");for(intcounter=0;counter<array.length;counter++)System.out.printf("%3... 查看详情

vmwareesxi6.5的一个小bug-原有linux中vmwaretools无法更新

测试过程中已经发现个小bug:单台的ESXi6.5上,如果是旧有的Linux机器,无法顺利更新VMwareTools,会报错,但如果VMwareTools不更新,在Webclient段调整Linux桌面的分辨率就会造成VM无法访问,无法通过console控制,桌面端和网页端都不行... 查看详情

记录一次bug解决过程:eclipseinstalledjres配置引出的问题

一总结eclipseInstalledJREs配置引出的问题:编译以来JDK,不是JREspringboot内嵌tomcat运行程序,tomcat:run二Bug描述:eclipseInstalledJREs配置引出的问题  刚新鲜检索出的代码,同学们编译都ok的,自己编译总是出错。原因在于eclipseInstall... 查看详情

watchdog机制概述(代码片段)

...序“跑飞”,造成整个系统无法正常工作,因此,引入了一个“看门狗”,对单片机的运行状态进行实时监测,针对运行故障做一些保护处理,譬如让系统重启。这种Watchdog属于硬件层面,必须有硬件电路的支持。Linux也引入了Wa... 查看详情

linux下的各个目录

...,而且名字都是系统命令的名字,其实每个系统命令都是一个小的可执行的文件,这些命令都存放在bin目录中。2./boot(启动),启动目录,对嵌入式非常重要,System.map和vmlinux内核文件都在该目录下。3./dev(device,设备) 查看详情

有一个反射的小程序引出的问题

publicclasssecond{publicstaticvoidmain(String[]args){try{Methodsqrt=Math.class.getMethod("sqrt",double.class);rr(3,sqrt);}catch(Exceptione){};}privatestaticvoidrr(doublec,Methodf){try{doublex=(double) 查看详情

微信小程序启动更新机制(代码片段)

小程序启动小程序启动会有两种情况,一种是「冷启动」,一种是「热启动」。热启动:假如用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时无需重新启动,只需将后台态的小程序切换到前台,这个过程就... 查看详情

pycharm小bug集合

最近使用pycharm发现一个有趣的小bug:暂时命名为pycharm多行注释发黄小bug:根据pep8规则,pycharm如果已经存在了一对多行注释,不支持第二对多行注释分两行2.没有报错信息,也不会影响使用,只是会发黄如下图:解决方案是:1.... 查看详情

为啥linux发行版本的开发周期这么短?

半年就一个版本.这样不好吧?参考技术ALinux更新快,不是淘汰快!更新不过是对系统软件新发现的bug进行修改而已,对现在系统非常满意的话,可以选择不更新,如果不能容忍bug或软件有了新特性再选择更新也不迟。开发周期短... 查看详情

不同js环境usestrict对反复属性处理的差异

昨天改一个Bug不小心属性名反复了,引出了一个非常有意思的小Bug。导致Bug产生的是一段JS对象声明的代码。其结构与例如以下代码等价。varfn=function(){‘usestrict‘;varobj={a:1,a:2//由于声明的属性比較多,后面加入的属性不小心与... 查看详情

reflection反射机制原理使用场景及缺陷(代码片段)

(目录)反射一个需求引出反射需求如下:根据配置文件re.properties中的指定信息,创建Cat对象并调用方法hi代码如下:publicclassCatprivateStringname;publicintage;publicStringsex;//无参构造器publicCat()privateCat(Stringname)this.name=name;publicCat(Stringname,i... 查看详情

idea关于前端调试的一个小bug(代码片段)

今天在做springboot项目时,直接用的bootstrap模板,将bootstrap下载好并将其中的css、font、js文件夹导入到工程的static目录下后,在template/index.html中引用出了问题。出现问题的原因是,当直接把文件拖入时idea会自动给你把路径填好,... 查看详情

java类加载机制

...  当调用java命令来运行某个Java程序时,该命令将会启动一个JVM进程.同一个JVM中的所有线程,变量都处于同一个进程中,共享该JVM的内存区域.当出现以下情况是,JVM会退出:1):程序正常执行结束.2):使用System.exit(0)方法;3):出现异常时,... 查看详情

深入理解cookie和session机制

首先为了让我们能够对Cookie和Session有一个初步的理解,先给出一个问题和一个例子来引出Cookie和Session,然后随后带着问题再去思考Cookie和Session二者的机制。问题1:为什么我们在浏览网页的时候,会发现它会自动的给你推送一... 查看详情

微信小程序运行机制和生命周期(代码片段)

一.运行机制首先了解下小程序的运行机制,小程序从启动到最终被销毁,会经历很多不同的状态,小程序在不同状态下会有不同的表现。大致运行机制如下图。小程序生命周期图接下来我们是图中概念讲解,项目... 查看详情

微信小程序运行机制和生命周期(代码片段)

一.运行机制首先了解下小程序的运行机制,小程序从启动到最终被销毁,会经历很多不同的状态,小程序在不同状态下会有不同的表现。大致运行机制如下图。小程序生命周期图接下来我们是图中概念讲解,项目... 查看详情