创建文件到底发生了什么?

为什么要浅浅探究这个问题?

大家大部分时间的工作其实都是写业务层的”CURD“,而真正陪伴在我们身边的计算机底层原理则不再有那么多好奇心去探究了。这篇文章旨在抛砖引玉,也许大家在空闲的时间里,也能够去探求自己以前一直想知道的原理到底是怎么样的。
如何探究?
这里是使用了自顶向下的方式去探寻。说人话就是从业务层代码入手,逐层探讨,不说废话,直接进入正片部分。

分层:

Java层:

这里放一段简单的创建文件的kotlin代码:
在这里插入图片描述
从理论上来讲,Java本身是在JVM上面运行的,我们的Java层代码是不可能直接利用操作系统中文件系统相关的系统调用来创建文件的。
那么这里要探究的是createNewFile这个方法往下走发生了什么?
File.java:
在这里插入图片描述
这里说明调用了一个Java层的文件系统方法来创建文件。
UnixFileSystem.java:继承自FileSystem抽象类在这里插入图片描述
可以看到,这里确实有一个native方法,说明这个事情是在JDK内的c++底层代码做的。

c++层:

结合JNI和JAVACPP,知道了Java调用CPP代码的文件规则和函数映射规则,所以我们可以很快的从JDK8的源码里面找到对应的native方法在哪里。就在UnixFileSystem_md.c中。
顺利找到了native的表层实现:
在这里插入图片描述
这里插入解释一下几个参数的意思:
第一个参数:path是文件路径
第二个参数:希望做的操作,这里用or标识符说明是希望串行一起做。这里还有很多定义好的枚举,因为不是本文重点,这里不再罗列,感兴趣可以自己搜索学习。比如这里就是想执行:读写or创建or取消(如果失败就继续创建下去的执行链),所以在这里就知道了,当一个路径文件已经存在的时候,就不会再创建了,因为在尝试读写的时候就已经成功了,不会再走创建流程。(会先尝试读写,读写失败再创建)
第三个参数:0666是文件开放权限的编码。会发现这是以0开头的数字,在c语言体系中0开头说明是八进制。
为什么是八进制?这里就跟文件系统的权限分配有关系了,这里简单说明一下:
1.八进制转化为二进制是三位,比如000。众所周知每一个二进制位都可以在约定下携带一个或者一类信息,那么这里是三个八进制数也就可以携带9个信息,那么具体是哪9个信息?
2.第一个八进制代表文件所有者的权限、第二个八进制代表文件用户组的权限、第三个八进制代表其他用户的权限。相当于是对用户权限作管理
3.而八进制内部,则对具体的权限划分作了划分,首先定义文件基本的三种权限:可读、可写、可执行。正好对应三位(我想,设计为八进制也是因为这个)。从高到低以此,赋值为1说明是允许的
那么回过头来看这个0666,就知道是什么意思了,说明无论是哪个用户,都对该文件有可读和可写的权限。这也解答了为什么我用代码创建一个文件后,所有用户都可以任意访问操作的问题了。
表现到我们能看到的命令行界面就是,我们输入ls -l后,会显示在最前面的权限分配情况:
比如这个dubbo堆栈文件,就说明他的权限编码是0644
在这里插入图片描述
再往下就需要我们知道handleOpen做了什么事情:
在这里插入图片描述

可以看到核心就是这个open64函数。(RESTARTBLE函数的作用只是重试而已,本身不跟操作系统交互)
下面的fstat只是为了拿到一些已创建文件的信息而已,也不是研究的重点。

open64函数本身就已经是UNIX系统调用最上层暴露给程序员调用的接口(即系统调用),如果还想往下,就到了系统内核层了。
这里补充一个系统调用的概念:一个完整的系统调用由三部分组成———(1.系统调用参数、2.系统调用执行、3.恢复原程序现场)

这里插一段open函数反汇编的汇编语言,其实大概就知道操作系统做了什么事情了。这里的汇编是ARM汇编。
汇编语言类型涉及到了CPU架构和指令集的区别。主要分为CISC(复杂指令系统计算机)和RISC(精简指令系统计算机)。ARM架构属于RISC主要用于移动端,而我们常用的英特尔、奔腾一般使用X86架构,主要用于PC。我们录播主机应该用的就是X86的架构。
这里可以发现,在libc_open函数中,就会有系统调用痕迹,open系统调用号为5(哪里来的?一般都是计算机架构设计的时候约定的)。
并且也可以发现,系统调用是会触发中断的,这就跟操作系统的执行方式有关系了,只有用户级代码是可以直接执行的,像系统调用这种内核级的命令,是需要触发中断,通知操作系统去执行这一段系统调用程序后,再重新回到原来的用户级代码中执行的。

000082f0 <main>:
    82f0:	e92d4800 	push	{fp, lr}
    82f4:	e28db004 	add	fp, sp, #4	; 0x4
    82f8:	e24dd010 	sub	sp, sp, #16	; 0x10
    82fc:	ebffffd5 	bl	8258 <test>
    8300:	e1a03000 	mov	r3, r0
    8304:	e50b300c 	str	r3, [fp, #-12]
    8308:	e59f002c 	ldr	r0, [pc, #44]	; 833c <main+0x4c>
    830c:	e3a01002 	mov	r1, #2	; 0x2
    8310:	eb002e82 	bl	13d20 <__libc_open> //进入文件操作核心方法
    8314:	e1a03000 	mov	r3, r0
    8318:	e50b3008 	str	r3, [fp, #-8]
    831c:	e59f301c 	ldr	r3, [pc, #28]	; 8340 <main+0x50>

00013d20 <__libc_open>:
   13d20:	e51fc028 	ldr	ip, [pc, #-40]	; 13d00 <___fxstat64+0x50>
   13d24:	e79fc00c 	ldr	ip, [pc, ip]
   13d28:	e33c0000 	teq	ip, #0	; 0x0
   13d2c:	1a000006 	bne	13d4c <__libc_open+0x2c>
   13d30:	e1a0c007 	mov	ip, r7  //把r7原来的数据暂存到ip寄存器
   13d34:	e3a07005 	mov	r7, #5	; 0x5    //open的系统调用号5
   13d38:	ef000000 	svc	0x00000000   //产生软中断
   13d3c:	e1a0700c 	mov	r7, ip  //这里是系统中断后回复现场的代码,说白话就是把ip寄存器本来存的东西还给r7寄存器
   13d40:	e3700a01 	cmn	r0, #4096	; 0x1000
   13d44:	312fff1e 	bxcc	lr
   13d48:	ea0008b0 	b	16010 <__syscall_error>

操作系统层(系统内核):

由于不同操作系统的实现是不同的,这里不对多个操作系统都进行的探讨,仅以linux系统为例分析,因为linux的源码比较好找到。
open函数的宏定义,可以看到,实际上调用的是do_sys_open函数,这才是真正系统调用的函数。
open.c:
在这里插入图片描述

在这里开始执行系统调用,具体的系统调用过程这里不会再展开讨论了。
感兴趣的话有机会可以再重点展开一下,这里再往下会涉及Linux内核操作系统的详细设计,包括文件系统设计、权限设计、安全设计,会很庞大。
这里基本上就是最难啃的一部分了,但至少我们已经摸到内核层了,在之前的流程都能梳理的比较清楚了。
在这里插入图片描述在这里插入图片描述

流程梳理:

基本流程很简洁,其实就是一个三层调用结构而已。
在这里插入图片描述

怎样跟踪系统调用的情况?

从代码层知道了大致的原理,那么如果我想在我的电脑上去跟踪系统调用怎么办?比如我就想知道,我开始执行一行创建文件的Java代码发生了什么,我想知道操作系统做这件事有没有痕迹或记录,怎么办?
很多人由于好奇心或者工作,都有这方面的需要。市面上也有很多跟踪系统调用的工具,很多操作系统本身自带有跟踪命令。这里以Linux系统为例。(MACOS有自己的dtruss和dtrace工具,但是一般都会被安全策略禁用,感兴趣可以自己尝试一下)
介绍一个跟踪命令:strace
strace作为老牌Linux系统调用跟踪工具,是一定要介绍一下的。其他的工具如perf等,可以自己查资料学习。
常用的命令如:strace -e trace=<系统调用名称1>,<系统调用名称2>… -p
解释一下:就是跟踪这个进程上会发生的指定的系统调用集合是怎么样的。
这里用我们自己的服务器抓一下就好了。我这里抓的是进程pid是1,可以理解成我抓的是操作系统全局的系统调用。
在这里插入图片描述

该命令会将系统调用的参数和返回值都打印出来。在Linux中返回值为13说明系统调用成功,反之则是异常情况,需要进行排查。
上面这一段给了我们什么信息呢?
1.我们可以知道,这些系统调用都成功了,如果失败了我们可以以此为依据分析原因。
2.我们窥探到了操作系统到底每天在干什么事情,从这些路径我们可以看出,操作系统需要频繁访问这些文件。这都是些什么文件?比如第一个是磁盘挂载文件信息文件、第二个是PCI通信数据文件、第三个是设备相关标识文件。自此,我们从只能看到简单的CRUD代码,终于能够知道每天在用的操作系统到底在干什么事情的冰山一角了。

意义:

1.终于知道从Java层走到操作系统层,大概发生了什么,尽管还没有全局了解,但已经实现了从0-1过程,之后无论是探求网络socket链接、nio实现等,都知道大概是怎么走到最底层的了。
2.学会了简单的跟踪系统调用,说明系统调用跟踪本身并没有什么难度,只要知道原理和掌握工具,不是什么不可触及的领域。
3.觉得有意思本身就是意义

相关资料:

JDK8源码:https://github.com/openjdk/jdk/blob/master/src/
相关Linux开源内核源码:https://github.com/raspberrypi/linux/

记一篇失踪博主的不定时回归文章吧,daze☆

  • 9
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值