Когда-то читал статью о выборе файловых систем «чтоб везде-везде работало». В ней в очередной раз увидел жалобы, что Ext4 замечательная файловая система, но на Windows есть только кривые неточные проприетарные драйверы. Но отмотаем ленту ещё на пару лет назад: тогда на Хабре (а в те времена — Гиктаймсе) пролетала новость про LibOS — попытку превратить Linux kernel в обычную библиотеку пользовательского режима. Упор там делался на вынос сетевого стека в user space. Как-то раз я решил поглядеть, а жив ли вообще проект, и в их блоге увидел ссылку на своего рода конкурента — проект Linux Kernel Library (LKL). По сути, это порт ядра, так сказать, на аппаратную архитектуру «библиотека пользовательского режима POSIX / Win32».
Чем интересна LKL? Во-первых тем, что она живёт и здравствует, пусть и не в основной кодовой базе ядра. Во-вторых, это более-менее честная поддержка «архитектуры», автоматически делающая доступной бОльшую часть ядра. Более того, прямо в комплекте идут утилиты-примеры: cptofs/cpfromfs, fs2tar, lklfuse. В этой статье мы протестируем LKL на хостовом Linux, заглянем в файл с образом Ext4 (Btrfs, XFS...) без рута и виртуалок и коротко обсудим, как её можно попробовать на Windows.
DISCLAIMER 1: Захотите попробовать — делайте бекапы. Если вы захотите проделать такое с разделом с важными данными — на свой страх и риск. Впрочем, здесь хотя бы драйверы будут реально родные.
DISCLAIMER 2: Уважайте лицензии. Вероятно, линковка с LKL делает вашу программу GPL'ной.
Первичное знакомство
Репозиторий LKL (lkl/linux на GitHub) представляет из себя форк обычного Linux kernel, в котором добавлена поддержка ещё одной архитектуры, в основном мы будет видеть это в каталогах arch/lkl и tools/lkl. Сделаем клон репозитория и попробуем собрать по инструкции. Для экспериментов я буду использовать shallow clone, который содержит не всю историю репозитория, а лишь указанное количество последних коммитов:
$ 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.c b/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):
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. Это такой аналог функции syscall для LKL. В реальной же ситуации в большинстве случаев вы будете использовать типизированные обёртки lkl_sys_<name>. Также мы видим всякие функции для настройки «ядра», добавления в него виртуальных устройств, а также обёртки над «сложными» системными вызовами, в обычной системе предоставляемые libc. Например, есть такой системный вызов getdents, но… «These are not the interfaces you are interested in.» — с порога говорит нам man-страница. В обычных же случаях предполагается использовать стандартную библиотечную функцию readdir (3), но не путайте её с readdir (2) — древним системным вызовом, который на x86_64 даже не реализован. В случае же работы с LKL вам потребуются обёртки lkl_opendir / lkl_readdir / lkl_closedir.
Попробуем что-нибудь написать
Напоминаю, уважайте лицензии. Сам Linux kernel распространяется под 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
Можно даже видеть по timestamp'ам, что ядро не просто «выплюнуло» в консоль этот текст, а красиво постепенно грузилось как настоящее.
Усложняем эксперимент
Давайте теперь попробуем как-то по-настоящему использовать эту библиотеку — всё-таки целое ядро ОС! Попробуем чисто в user space прочитать файл с 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; }
Обратите внимание на переименованные define'ы с префиксами 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, кто-то libfuse — ну что же, поставим 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, поработать с ней, отмонтировать — а потом подключить к хостовому ядру (уже с рутом) и продолжить, как ни в чём не бывало.
Мало того, что lklfuse позволяет обычному пользователю подмонтировать раздел с помощью штатного драйвера ядра. Хостовое ядро вообще не обязано быть собрано с поддержкой этой ФС. Да что уж там, не удивлюсь, если это всё точно так же заведётся на OS X.
Немного про кросс-платформенность
А что же с доступом к линуксовым ФС из других операционных систем? На OS X, думаю, будет попроще: всё-таки она полноценный UNIX, да и поддержка FUSE, вроде, есть. Так что есть надежда, что оно заведётся с ходу. Если нет, я бы посмотрел в сторону проверки того, везде ли в системные вызовы LKL передаются константы с префиксами LKL_, а не их хостовые аналоги.
С Windows несколько сложнее: во-первых, там банально может не быть некоторых привычных в мире UNIX библиотек (например, для разбора аргументов командной строки). Во-вторых, нужно понять, как подмонтироваться к хостовому дереву файловых систем. Самое простое было бы — так же через FUSE. Говорят, когда-то был некий Dokan, сейчас тоже что-то есть, но нужно гуглить. Главное, что сама LKL на Windows собирается, нужно только учесть, что ей требуется 64-битный тип long для работы в 64-битном режиме, поэтому не каждый компилятор подойдёт (по крайней мере, так написано в текущем readme проекта).
