读完一篇有关选择文件系统的文章后 ,“它可以在任何地方,任何地方使用”。 我再次看到其中的抱怨,即Ext4是一个很棒的文件系统,但是在Windows上只有 曲线 专有驱动程序不正确。 但是,我们在几年前倒带了:然后在Habré(以及当时的Giktimes)上发布了有关LibOS的消息,这是试图将Linux内核变成常规的用户模式库。 那里的重点是删除用户空间中的网络堆栈。 一次,我决定看一下该项目是否还活着,在他们的博客上,我看到了一个竞争对手的链接-Linux Kernel Library(LKL)项目。 实际上,这可以说是硬件体系结构“ POSIX / Win32用户模式库”的内核端口。
LKL有什么有趣的? 首先,尽管她生活在世上,但事实并非如此,尽管她不在内核的核心代码库中。 其次,它或多或少对“体系结构”提供了诚实的支持,该体系结构自动使大部分内核可用。 此外,工具包中还cptofs
示例实用程序: cptofs
/ cpfromfs
, fs2tar
和lklfuse
。 在本文中,我们将在主机Linux上测试LKL,查看带有Ext4映像(Btrfs,XFS ...)的文件,该文件没有root和虚拟机,并简要讨论了如何在Windows上进行尝试。
免责声明1:想要尝试-进行备份。 如果要使用包含重要数据的部分来进行此操作-后果自负。 但是,至少在这里驱动程序将是真正的本地驱动程序。
免责声明2:尊重许可。 LKL链接可能使您的GPL程序成为可能 。
初级熟人
LKL存储库(GitHub上的lkl / linux )是常用Linux内核的分支,它增加了对另一种体系结构的支持,主要是我们会在arch/lkl
和tools/lkl
看到它。 让我们克隆存储库,然后尝试按照说明进行组装。 为了进行实验,我将使用一个浅表克隆,它不包含存储库的全部历史记录,而仅包含指定数量的最近提交:
$ git clone https://github.com/lkl/linux.git lkl-linux --depth 10 $ cd lkl-linux $ patch -p 1 <<EOF diff --git a/tools/lkl/lib/hijack/xlate.cb/tools/lkl/lib/hijack/xlate.c index 03ccc6294..75368dcc2 100644 --- a/tools/lkl/lib/hijack/xlate.c +++ b/tools/lkl/lib/hijack/xlate.c @@ -3,6 +3,7 @@ #include <fcntl.h> #include <sys/ioctl.h> #include <sys/socket.h> +#include <linux/sockios.h> #undef st_atime #undef st_mtime #undef st_ctime EOF $ make -C tools/lkl -j4
我不得不修复源代码,但是最后我得到了tools/lkl/lib/liblkl.so
(还有静态tools/lkl/liblkl.a
):
nm -D工具/ lkl / lib / liblkl.so U __assert_fail U bind U calloc U clock_gettime U close w __cxa_finalize 0000000000063b30 T dbg_entrance 0000000000063f30 T dbg_handler U __errno_location U fcntl U fdatasync 0000000000639580 D fd_net_ops U fgets U __fprintf_chk U free U fwrite U getc U getenv w __gmon_start__ U if_nametoindex U inet_pton U ioctl U __isoc99_scanf w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable 0000000000061750 T jmp_buf_longjmp 0000000000061720 T jmp_buf_set 0000000000065470 T jsmn_init 0000000000065060 T jsmn_parse 0000000000065490 T jsmn_strerror 00000000000614c0 T lkl_add_gateway 0000000000061290 T lkl_add_neighbor 00000000000621a0 T lkl_bug 000000000005f070 T lkl_closedir 0000000000639520 D lkl_dev_blk_ops 000000000005fa10 T lkl_dirfd 0000000000062640 T lkl_disk_add 0000000000062780 T lkl_disk_remove 000000000005ec50 T lkl_encode_dev_from_sysfs 000000000005f9f0 T lkl_errdir 000000000005ef80 T lkl_fdopendir 0000000000067f10 T lkl_get_free_irq 000000000005f2c0 T lkl_get_virtio_blkdev 00000000006395c0 D lkl_host_ops 00000000000614b0 T lkl_if_add_gateway 00000000000613e0 T lkl_if_add_ip 00000000000614a0 T lkl_if_add_linklocal 0000000000061520 T lkl_if_add_rule_from_saddr 0000000000061480 T lkl_if_del_ip 0000000000060d70 T lkl_if_down 0000000000060b10 T lkl_ifname_to_ifindex 0000000000061400 T lkl_if_set_ipv4 0000000000061530 T lkl_if_set_ipv4_gateway 0000000000061430 T lkl_if_set_ipv6 00000000000615b0 T lkl_if_set_ipv6_gateway 0000000000060ef0 T lkl_if_set_mtu 0000000000060bf0 T lkl_if_up 0000000000061160 T lkl_if_wait_ipv6_dad 000000000005fba0 T lkl_iomem_access 000000000005fb50 T lkl_ioremap 0000000000067730 T lkl_is_running 0000000000066150 T lkl_load_config_env 0000000000065950 T lkl_load_config_json 0000000000066880 T lkl_load_config_post 0000000000066510 T lkl_load_config_pre 000000000005f470 T lkl_mount_dev 000000000005eae0 T lkl_mount_fs 00000000000642a0 T lkl_netdev_add 00000000000645c0 T lkl_netdev_free 0000000000061030 T lkl_netdev_get_ifindex 0000000000064e70 T lkl_netdev_macvtap_create 0000000000064ed0 T lkl_netdev_pipe_create 0000000000064ce0 T lkl_netdev_raw_create 00000000000644c0 T lkl_netdev_remove 0000000000064c60 T lkl_netdev_tap_create 0000000000064a10 T lkl_netdev_tap_init 000000000005eea0 T lkl_opendir 0000000000062170 T lkl_perror 00000000000620b0 T lkl_printf 0000000000067f90 T lkl_put_irq 0000000000061620 T lkl_qdisc_add 0000000000061630 T lkl_qdisc_parse_add 000000000005f0f0 T lkl_readdir 0000000000063f80 T lkl_register_dbg_handler 0000000000064930 T lkl_register_netdev_fd 000000000005efe0 T lkl_rewinddir 000000000005fa20 T lkl_set_fd_limit 00000000000614e0 T lkl_set_ipv4_gateway 0000000000061500 T lkl_set_ipv6_gateway 0000000000065f60 T lkl_show_config 00000000004f51ad T lkl_start_kernel 0000000000062080 T lkl_strerror 00000000000685f0 T lkl_syscall 0000000000062270 T lkl_sysctl 0000000000062410 T lkl_sysctl_parse_write 0000000000067770 T lkl_sys_halt 00000000000680e0 T lkl_trigger_irq 000000000005f870 T lkl_umount_dev 000000000005edc0 T lkl_umount_timeout 0000000000066ed0 T lkl_unload_config 00000000008186a0 B lkl_virtio_devs U __longjmp_chk U lseek64 U malloc U memchr U memcpy U memset U open U perror U pipe U poll 0000000000064070 T poll_thread U pread64 U __printf_chk U pthread_create U pthread_detach U pthread_exit U pthread_getspecific U pthread_join U pthread_key_create U pthread_key_delete U pthread_mutexattr_init U pthread_mutexattr_settype U pthread_mutex_destroy U pthread_mutex_init U pthread_mutex_lock U pthread_mutex_unlock U pthread_self U pthread_setspecific U puts U pwrite64 U read U readv 00000000008196a0 B registered_devs 000000000005fa90 T register_iomem U sem_destroy U sem_init U sem_post U sem_wait U _setjmp U setsockopt U sigaction U sigemptyset U __snprintf_chk U socket U __stack_chk_fail U stderr U stdin U stpcpy U strchr U strcpy U __strcpy_chk U strdup U strerror U strlen U strncat U __strncat_chk U strncmp U strncpy U strrchr U strtok U strtok_r U strtol U strtoul U syscall U timer_create U timer_delete U timer_settime 000000000005fb00 T unregister_iomem U usleep 0000000000063110 T virtio_dev_cleanup 0000000000062ee0 T virtio_dev_setup 0000000000063100 T virtio_get_num_bootdevs 0000000000062c10 T virtio_process_queue 0000000000062af0 T virtio_req_complete 0000000000062ec0 T virtio_set_queue_max_merge_len U __vsnprintf_chk U write U writev
您问系统调用在哪里。 无需担心,它们被隐藏在公共入口点lkl_syscall
。 这是LKL的syscall
函数的类似物。 在实际情况下,大多数情况下,您将使用类型化的包装器lkl_sys_<name>
。 我们还看到了用于配置“内核”,向其中添加虚拟设备的各种功能,以及常规系统上libc提供的“复杂”系统调用的包装。 例如,有一个这样的系统调用getdents
,但是...“这些不是您感兴趣的接口。”-手册页从阈值告诉我们。 在通常情况下,应该使用标准库函数readdir (3)
,但不要将其与readdir (2)
混淆, readdir (2)
甚至没有在x86_64上实现。 如果使用LKL,则需要lkl_opendir
/ lkl_readdir
/ lkl_closedir
。
让我们尝试写点东西
请记住,请遵守许可证。 Linux内核本身是在GPL2下分发的,是否将为相对公共的LKL接口拉出的程序视为衍生工作-我不知道。
好吧,让我们尝试链接到库。 假定已为变量$LKL
分配了具有已编译LKL的存储库的路径。
#include <stdio.h> #include "lkl_host.h" #include "lkl.h" int main() { // lkl_host_ops // "-" : `printk`, `panic`, ... // , -- lkl_start_kernel(&lkl_host_ops, "mem=128M"); return 0; }
编译:
$ gcc test.c -o test -I$LKL/tools/lkl/include -L$LKL/tools/lkl/lib -llkl
而且有效!
$ ./test ./test: error while loading shared libraries: liblkl.so: cannot open shared object file: No such file or directory $ LD_LIBRARY_PATH=$LKL/tools/lkl/lib ./test [ 0.000000] Linux version 5.3.0+ (trosinenko@trosinenko-pc) (gcc version 9.2.1 20191008 (Ubuntu 9.2.1-9ubuntu2)) #1 Tue Dec 3 14:37:02 MSK 2019 [ 0.000000] memblock address range: 0x7fba8c000000 - 0x7fba93fff000 [ 0.000000] Built 1 zonelists, mobility grouping on. Total pages: 32319 [ 0.000000] Kernel command line: mem=128M [ 0.000000] Dentry cache hash table entries: 16384 (order: 5, 131072 bytes, linear) [ 0.000000] Inode-cache hash table entries: 8192 (order: 4, 65536 bytes, linear) [ 0.000000] mem auto-init: stack:off, heap alloc:off, heap free:off [ 0.000000] Memory available: 129044k/131068k RAM [ 0.000000] SLUB: HWalign=32, Order=0-3, MinObjects=0, CPUs=1, Nodes=1 [ 0.000000] NR_IRQS: 4096 [ 0.000000] lkl: irqs initialized [ 0.000000] clocksource: lkl: mask: 0xffffffffffffffff max_cycles: 0x1cd42e4dffb, max_idle_ns: 881590591483 ns [ 0.000000] lkl: time and timers initialized (irq1) [ 0.000003] pid_max: default: 4096 minimum: 301 [ 0.000019] Mount-cache hash table entries: 512 (order: 0, 4096 bytes, linear) [ 0.000022] Mountpoint-cache hash table entries: 512 (order: 0, 4096 bytes, linear) [ 0.003622] random: get_random_bytes called from _etext+0xbcdb/0x14b05 with crng_init=0 [ 0.003692] printk: console [lkl_console0] enabled [ 0.003707] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604462750000 ns [ 0.003714] xor: automatically using best checksumming function 8regs [ 0.003783] NET: Registered protocol family 16 [ 0.171647] raid6: int64x8 gen() 4489 MB/s [ 0.343119] raid6: int64x8 xor() 3165 MB/s [ 0.514836] raid6: int64x4 gen() 4668 MB/s [ 0.689529] raid6: int64x4 xor() 3256 MB/s [ 0.861155] raid6: int64x2 gen() 6283 MB/s [ 1.032668] raid6: int64x2 xor() 3793 MB/s [ 1.206752] raid6: int64x1 gen() 5185 MB/s [ 1.378219] raid6: int64x1 xor() 2901 MB/s [ 1.378225] raid6: using algorithm int64x2 gen() 6283 MB/s [ 1.378227] raid6: .... xor() 3793 MB/s, rmw enabled [ 1.378229] raid6: using intx1 recovery algorithm [ 1.378333] clocksource: Switched to clocksource lkl [ 1.378427] NET: Registered protocol family 2 [ 1.378516] tcp_listen_portaddr_hash hash table entries: 256 (order: 0, 4096 bytes, linear) [ 1.378521] TCP established hash table entries: 1024 (order: 1, 8192 bytes, linear) [ 1.378527] TCP bind hash table entries: 1024 (order: 1, 8192 bytes, linear) [ 1.378532] TCP: Hash tables configured (established 1024 bind 1024) [ 1.378596] UDP hash table entries: 128 (order: 0, 4096 bytes, linear) [ 1.378618] UDP-Lite hash table entries: 128 (order: 0, 4096 bytes, linear) [ 1.379286] workingset: timestamp_bits=62 max_order=16 bucket_order=0 [ 1.380271] SGI XFS with ACLs, security attributes, no debug enabled [ 1.380864] io scheduler mq-deadline registered [ 1.380872] io scheduler kyber registered [ 1.383396] NET: Registered protocol family 10 [ 1.383763] Segment Routing with IPv6 [ 1.383779] sit: IPv6, IPv4 and MPLS over IPv4 tunneling driver [ 1.384091] Btrfs loaded, crc32c=crc32c-generic [ 1.384223] Warning: unable to open an initial console. [ 1.384237] This architecture does not have kernel memory protection. [ 1.384239] Run /init as init process
您甚至可以从时间戳中看到,内核不仅将这些文本“吐出”到了控制台中,而且还逐渐被漂亮地加载了。 像真实的 。
我们使实验复杂化
现在让我们尝试以某种方式真正使用该库-毕竟,是整个操作系统内核! 让我们尝试在用户空间中干净地从Ext4部分读取文件。 和“本地”驱动程序! 我们以tools/lkl/cptofs.c
,仅实现最必要的工具(为清楚起见):
#undef NDEBUG #include <stdio.h> #include <stdint.h> #include <string.h> #include <assert.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include "lkl_host.h" #include "lkl.h" // , // -- :) int main(int argc, const char *argv[]) { const char * const fsimage = argv[1]; const char * const fstype = argv[2]; const char * const file_to_dump = argv[3]; struct lkl_disk disk; int disk_id, ret; char mpoint[128]; // memset(&disk, 0, sizeof(disk)); disk.fd = open(fsimage, O_RDONLY); assert(disk.fd >= 0); // disk_id = lkl_disk_add(&disk); assert(disk_id >= 0); // lkl_start_kernel(&lkl_host_ops, "mem=128M"); // ret = lkl_mount_dev(disk_id, 0 /* part */, fstype, LKL_MS_RDONLY, NULL, mpoint, sizeof(mpoint)); if (ret < 0) { fprintf(stderr, "lkl_mount_dev failed: %s\n", lkl_strerror(ret)); close(disk.fd); exit(1); } // , ... // ( -libc ) struct lkl_dir *dir = lkl_opendir(mpoint, &ret); struct lkl_linux_dirent64 *dent; while ((dent = lkl_readdir(dir)) != NULL) { fprintf(stderr, "Directory entry: %s\n", dent->d_name); } // : NULL -- ... lkl_closedir(dir); // - // char tmp[256]; uint8_t buffer[65536]; snprintf(tmp, sizeof(tmp), "%s/%s", mpoint, file_to_dump); int fd = lkl_sys_open(tmp, LKL_O_RDONLY, 0); fprintf(stderr, "fd = %d\n", fd); assert(fd >= 0); int count = lkl_sys_read(fd, buffer, sizeof(buffer)); /* */ write(STDERR_FILENO, buffer, count); lkl_sys_close(fd); return 0; }
注意带有LKL_前缀的重命名定义(例如LKL_O_RDONLY
):在Linux主机上,它们很可能与没有前缀的定义重合,但是在其他系统上,这不是事实。
$ mke2fs ext4.img -t ext4 32M $ sudo mount ext4.img /mnt $ echo -e "Hello world\!\nTEST" | sudo tee /mnt/test.txt $ sudo umount /mnt $ LD_LIBRARY_PATH=$LKL/tools/lkl/lib ./read-file ext4.img ext4 test.txt [ 0.000000] Linux version 5.3.0+ (trosinenko@trosinenko-pc) (gcc version 9.2.1 20191008 (Ubuntu 9.2.1-9ubuntu2)) #1 Tue Dec 3 14:37:02 MSK 2019 // ... // [ 1.378960] Warning: unable to open an initial console. [ 1.378975] This architecture does not have kernel memory protection. [ 1.378977] Run /init as init process [ 1.379852] EXT4-fs (vda): mounted filesystem with ordered data mode. Opts: Directory entry: test.txt Directory entry: .. Directory entry: lost+found Directory entry: . fd = 0 Hello world\! TEST
哇,行得通! 还有异国情调吗?
$ mksquashfs test.c read-file.c squashfs.img $ LD_LIBRARY_PATH=$LKL/tools/lkl/lib ./read-file squashfs.img squashfs test.c [ 0.000000] Linux version 5.3.0+ (trosinenko@trosinenko-pc) (gcc version 9.2.1 20191008 (Ubuntu 9.2.1-9ubuntu2)) #1 Tue Dec 3 14:37:02 MSK 2019 // ... // [ 1.378472] This architecture does not have kernel memory protection. [ 1.378474] Run /init as init process lkl_mount_dev failed: No such device
! 虽然,等等,我们可能只是在核心库中不包含SquashFS支持!
配置LKL构建选项
我自己开发了一系列适用于LKL的命令-也许可以简化为传统的make defconfig
, make menuconfig
, make
。
$ make defconfig ARCH=lkl $ make menuconfig ARCH=lkl //// SquashFS $ cp .config arch/lkl/configs/defconfig $ make mrproper $ make -C tools/lkl -j4 #
瞧!
$ gcc read-file.c -o read-file -I$LKL/tools/lkl/include -L$LKL/tools/lkl/lib -llkl $ LD_LIBRARY_PATH=$LKL/tools/lkl/lib ./read-file squashfs.img squashfs test.c [ 0.000000] Linux version 5.3.0+ (trosinenko@trosinenko-pc) (gcc version 9.2.1 20191008 (Ubuntu 9.2.1-9ubuntu2)) #1 Wed Dec 4 12:07:50 MSK 2019 // ... // [ 1.378346] This architecture does not have kernel memory protection. [ 1.378348] Run /init as init process Directory entry: . Directory entry: .. Directory entry: read-file.c Directory entry: test.c fd = 0 #include <stdio.h> #include "lkl_host.h" #include "lkl.h" int main() { lkl_start_kernel(&lkl_host_ops, "mem=128M"); return 0; }
但是,在这种情况下,几乎没有必要重新编译read-file.c
库是动态的。
请问,已承诺的现成程序在哪里?
确实, tools/lkl
包含cptofs.c
, fs2tar.c
以及更多内容,但事实并非如此! 经过Makefile翻遍后,我发现有一个Makefile.autoconf
正在寻找所需的头文件,以及Makefile.conf
,所有这些都写在这里。
因此,有人想要libarchive
,有人想要libarchive
好吧,让我们放下libarchive-dev
, libfuse-dev
(对于Ubuntu而言)并重建。 都一样,它不起作用...而且,如果您删除Makefile.conf
...,糟糕!
那么,我们现在有什么呢? 现在在tools/lkl
我们有cptofs
, fs2tar
和lklfuse
。
首先,将cptofs
复制为cpfromfs
:
$ $LKL/tools/lkl/cptofs --help Usage: cptofs [OPTION...] -t fstype -i fsimage path... fs_path Copy files to a filesystem image -i, --filesystem-image=string path to the filesystem image - mandatory -p, --enable-printk show Linux printks -P, --partition=int partition number -s, --selinux=string selinux attributes for destination -t, --filesystem-type=string select filesystem type - mandatory -?, --help Give this help list --usage Give a short usage message Mandatory or optional arguments to long options are also mandatory or optional for any corresponding short options. $ cp $LKL/tools/lkl/cp{to,from}fs $ $LKL/tools/lkl/cpfromfs --help Usage: cpfromfs [OPTION...] -t fstype -i fsimage fs_path... path Copy files from a filesystem image -i, --filesystem-image=string path to the filesystem image - mandatory -p, --enable-printk show Linux printks -P, --partition=int partition number -s, --selinux=string selinux attributes for destination -t, --filesystem-type=string select filesystem type - mandatory -?, --help Give this help list --usage Give a short usage message Mandatory or optional arguments to long options are also mandatory or optional for any corresponding short options.
俗话说“你叫游艇...”。 我们开始...
$ $LKL/tools/lkl/cpfromfs -t ext4 -i ext4.img test.txt . error processing entry /mnt/0000fe00/test.txt, aborting
嗯...我们必须看到...但是,对于交互式使用它仍然很不方便,因为每次您都必须等待大约一秒钟,直到内核启动。 但是fs2tar
可以正常工作:
$ $LKL/tools/lkl/fs2tar -t ext4 ext4.img ext4.tar $ tar -tf ext4.tar tar: `/' /test.txt /lost+found/
但是我认为这里最有趣的程序是lklfuse
:
$ mkdir mountpoint $ $LKL/tools/lkl/lklfuse -o type=ext4 ext4.img mountpoint/ $ ls mountpoint/ lost+found test.txt $ mount sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime) proc on /proc type proc (rw,nosuid,nodev,noexec,relatime) ... , /dev/fuse on /run/user/1000/doc type fuse (rw,nosuid,nodev,relatime,user_id=1000,group_id=1000) lklfuse on /path/to/mountpoint type fuse.lklfuse (rw,nosuid,nodev,relatime,user_id=1000,group_id=1000) $ echo ABC > mountpoint/ABC.XYZ $ umount mountpoint $ sudo mount ext4.img /mnt $ ls /mnt $ cat /mnt/ABC.XYZ ABC
在我看来,这令人印象深刻:您可以通过FUSE在没有root用户的情况下挂载文件系统(但这取决于系统设置),使用它,然后将其卸载,然后将其连接到主机内核(已经有root用户)并继续进行,就好像什么都没发生一样。
不仅如此, lklfuse
允许普通用户使用常规内核驱动程序挂载分区。 主机核心不必带有对此FS的支持即可构建。 但是,如果它们在OS X上都以相同的方式启动,我不会感到惊讶。
但是,如何从其他操作系统访问Linux FS? 在OS X上,我认为它会更简单:毕竟,它是成熟的UNIX,并且似乎有FUSE支持。 因此,希望它会开始发展。 如果不是,我将朝着检查是否将带有LKL_
前缀的常量传递到LKL系统调用而不是传递给宿主主机的方向。
Windows稍微复杂一些:首先,在UNIX世界中可能没有一些常见的库(例如,用于解析命令行参数)。 其次,您需要了解如何安装到主机文件系统树。 最简单的是-也通过FUSE。 他们说,一旦有了某个Dokan,现在又有了一些东西,但是您需要用Google搜索它。 最主要的是LKL本身是基于Windows构建的,您只需要考虑它就需要64位long
类型才能在64位模式下工作,因此并非每个编译器都可以工作(至少如项目当前自述文件所述)。