在Linux中,是否有系统层/脚本来处理文件的打开?

在Linux中,是否有系统层/脚本来处理文件的打开?

在Linux中,是否有一个层/脚本来处理打开文件的程序请求?

就像您在 bash 中打开文件描述符时一样:exec 3 <>/documents/foo.txt或者您的文本编辑器打开/documents/foo.txt

我不敢相信编辑器可以“仅打开一个文件”来自行进行读/写访问。

我宁愿想象这是对“层”的请求(init.d 脚本?),一开始只能打开一定数量的文件,并通过其访问类型、打开文件的进程等来密切关注打开的文件。

答案1

这一层位于 Linux 和其他系统的内核内部,与历史上的 Unix 设计相差不远(大多数非 Unix 操作系统也是如此)。

内核的这一部分称为VFS(虚拟文件系统)层。 VFS的作用是管理打开文件的信息(文件之间的对应关系)文件描述符,打开文件描述和目录条目),解析文件路径(解释和/),并将目录条目上的操作分派到正确的文件系统驱动程序。...

大多数文件系统驱动程序也在内核中,但是保险丝文件系统驱动程序允许将此功能委托给内核外部。如果较低级别的存储这样做,文件系统操作也可能涉及用户域代码,例如,如果磁盘文件系统位于循环装置

答案2

Linux中的文件打开是由内核直接处理的但您可以采取一些措施来影响和研究该过程。


Linux存储堆栈图


系统调用

从顶部开始,您可以看到应用程序用于与文件交互的界面是系统调用

打开,做你期望的事,同时统计数据返回有关文件的信息而不打开它。

您可以使用 strace 研究程序对文件相关系统调用的使用情况:

$ strace -e trace=%file /bin/ls /etc
[...]
stat("/etc", {st_mode=S_IFDIR|0755,  ...}) = 0
openat(AT_FDCWD, "/etc", O_RDONLY...) = 3

这分析了 引起的系统调用ls /etc,显示statopenat是在/etc目录上调用的。

您可能想知道为什么我们要在目录上调用文件操作。在 UNIX 中,目录也是文件。实际上一切都是文件


文件描述符

您可能想知道openat() = 3上面输出中的内容。

在 UNIX 中,打开的文件由文件描述符,它是某个进程打开的文件的唯一表示。文件描述符 0、1 和 2 通常保留给标准流(用户输入/输出),因此第一个打开的文件将为 3。

您可以使用以下命令获取给定进程的打开文件描述符列表lsofstF岛):

$ cat /dev/urandom > /dev/null &
[1] 3242
$ lsof -p 3242
COMMAND  PID      USER   FD   TYPE DEVICE SIZE/OFF   NODE NAME
...
cat     3242 user         0u   CHR  136,0      0t0      3 /dev/pts/0
cat     3242 user         1w   CHR    1,3      0t0   1028 /dev/null
cat     3242 user         2u   CHR  136,0      0t0      3 /dev/pts/0
cat     3242 user         3r   CHR    1,9      0t0   1033 /dev/urandom

FD列显示文件描述符编号以及访问权限。

您还可以使用fuser搜索保存特定文件的进程:

$ fuser /dev/urandom
/dev/urandom:         ...  3242  ...

进程信息伪文件系统-/proc

现在您可能想知道:但怎么办lsof但首先

好吧,让我们来看看吧!

$ strace -e trace=%file lsof -p 3242
...
stat("/proc/3242/", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
openat(AT_FDCWD, "/proc/3242/stat", O_RDONLY) = 4
...
openat(AT_FDCWD, "/proc/3242/fd", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 4
readlink("/proc/3242/fd/0", "/dev/pts/0", 4096) = 10
lstat("/proc/3242/fd/0", {st_mode=S_IFLNK|0700, st_size=64, ...}) = 0
stat("/proc/3242/fd/0", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
openat(AT_FDCWD, "/proc/3242/fdinfo/0", O_RDONLY) = 7
...

所以lsof知道通过...阅读更多文件来打开哪些文件!具体来说,目录/proc/3242/fd.下面的所有内容/proc都是内核保存的“假”文件系统。你可以ls -l通过它来查看它的结构。


影响文件打开

您可以使用多种方法来影响文件打开,尽管它们并不像替换某些脚本那么简单。

如果您希望更改文件的存储或访问方式,例如提供加密、缓存、将其分布在多个磁盘上或类似的方式,那么很可能已经存在一个现有的设备映射器适合您的需求。

如果您想对特定目录/挂载中的文件打开进行细粒度控制,您可以编写一个简单的保险丝文件系统并挂载它。

在程序/进程级别,您可以使用LD_预加载更改 C 库调用并阻止它们执行正常的系统调用。

最困难但最灵活的方法是编写自己的文件系统驱动程序。

答案3

管理对文件的访问是操作系统的第一个也是最重要的功能。 DOS 是个人计算机上最古老的操作系统之一,意思是磁盘操作系统。它允许程序在大多数情况下直接访问硬件,但不能访问文件。程序必须使用 DOS 调用,而 DOS 将管理程序将数据放入和取出的文件。只有磁盘实用程序才能在 DOS 下直接访问硬盘驱动器和文件。

现代保护模式操作系统(如 Linux)像 DOS 一样处理文件访问,但它们也要求对程序本身(或已配置为与之共享内存的任何其他程序)之外的任何内容的每次访问都必须经过内核(Linux 是一个内核)。

Linux 上的程序可能会调用 C 库中的函数来读取文件数据或将数据写入文件。然后,C 库会完成其组织对文件中数据的访问的部分,同时仍然在与程序相同的上下文中运行。然后,C 库将使用正确的函数调用内核 (Linux) 来访问文件,从而将 CPU 切换到环 0 或特权模式。 CPU现在以特权模式运行Linux文件系统驱动程序和硬盘驱动程序软件,直接访问硬件来访问文件。数据被复制到 C 库指示 Linux 放置数据的内存区域,CPU 将切换回用户模式,并具有程序的安全上下文,C 库将恢复并执行其需要执行的任何处理。该数据然后返回执行您的程序。

答案4

简而言之,这就是程序写入文件时发生的情况

  1. 该程序向内核请求open一个由路径给出的文件进行写入。
  2. 内核设置一些内部结构并将打开文件的一些任务委托给特定于文件系统类型的驱动程序。然后内核向程序返回一个文件描述符,它只是一个整数(例如3)。
  3. 程序要求内核将write字节序列(例如字符串)添加到文件描述符所引用的文件中。
  4. 内核再次将工作委托给驱动程序。
  5. 步骤 3 和 4 可能会重复几次。
  6. 程序向内核请求close文件描述符引用的文件。
  7. 内核再次将工作委托给驱动程序,然后销毁内部结构。

这是一个相当简约的汇编程序,其中写入“Hello World!”到文件greeting.txt:

.text
.globl _start

_start:
    # Open and possible create file
    mov $2,             %rax        # syscall 'open'
    mov $path_start,    %rdi        # path
    mov $0101,          %rsi        # create + write
    mov $400,           %edx        # only user gets read permissions
    syscall

    mov %rax,           %r10        # file descriptor

    # Write string to file
    mov $1,             %rax        # syscall 'write'
    mov %r10,           %rdi        # file descriptor
    mov $msg_start,     %rsi        # start of data
    mov $msg_length,    %edx        # length of data
    syscall                         # perform syscall

    # Close file
    mov $3,             %rax        # syscall 'close'
    mov %r10,           %rdi        # file descriptor
    syscall

    # Exit program
    mov $60,            %rax        # syscall 'exit'
    syscall                         # perform syscall


.section .rodata

path_start:
    .string "greeting.txt\0"
path_end:
path_length = path_end - path_start


msg_start:
    .string "Hello World!\n"
msg_end:
msg_length = msg_end - msg_start

将代码保存到 write.s 并使用进行构建

as -o write.o write.s
ld -o write   write.o

然后运行

./write

希望一切顺利。

(注意:我不做任何错误处理。这只是玩具代码。)

相关内容