/dev/nulll — устройство /dev/null Limited Edition
Содержание
Intro
С устройством /dev/null связано множество компьютерных шуток. Говорят, что информация, направленная в /dev/null, преобразуется процессором в тепловую энергию, и если интенсивно записывать данные в /dev/null, то может наступить перегрев [1]. В редких случаях система может выдать предупреждение пользователю, что он исчерпал 98% места на своём /dev/null и устройство нужно почистить.
Задание
Требуется реализовать внешний модуль для ядра Linux, который создаёт новое символьное устройство под названием /dev/nulll (три буквы L). По смыслу оно должно работать так же, как стандартное нулевое устройство. Но с двумя дополнительными возможностями.
Учёт размера данных
В отличие от обычного /dev/null, новое устройство должно запоминать, сколько байт информации в него было влито.
Возникает вопрос, как эту информацию можно у устройства узнать. К сожалению, стандартные утилиты ls и stat не показывают размер символьных устройств (потому что по смыслу он не определён). Мы будем получать размер в байтах через команду blockdev. Да, она предназначена для блочных устройств, но её легко заставить работать для символьного. Такой вызов
blockdev --getsize64 /dev/nulll
должен возвращать размер в байтах тех данных, которые были отправлены в /dev/nulll. Примечание: число 64 здесь не связано с разрядностью процессора (команда без 64 возвращает размер в блоках, а не в байтах).
На самом деле, внутри blockdev просто делается системный вызов ioctl(). Поэтому указанная команда эквивалентна такому коду на C:
#include <fcntl.h> #include <linux/fs.h> #include <stdio.h> #include <sys/ioctl.h> #include <unistd.h> int main() { int fd; int ret = 0; unsigned long size = 0; fd = open("/dev/nulll", O_RDONLY); if (fd == -1) { perror("open"); return 1; } if (ioctl(fd, BLKGETSIZE64, &size) == -1) { perror("ioctl"); ret = 1; } else { printf("%lu\n", size); } if (close(fd) == -1) { perror("close"); ret = 1; }; return ret; }
При выгрузке и повторной загрузке модуля счётчик «занятого места» должен обнуляться.
Ограничение «места на диске»
Если при загрузке модуля будет задан целочисленный параметр capacity, равный некоторому положительному числу x, то устройство /dev/nulll не даст записать в себя более x байт информации. Последующие записи будут приводить к ошибке ENOSPC ("No space left on device").
Пример
sobols@sobols-VirtualBox:~/hw/nulll$ sudo insmod nulll.ko capacity=12 sobols@sobols-VirtualBox:~/hw/nulll$ printf "hello" > /dev/nulll sobols@sobols-VirtualBox:~/hw/nulll$ blockdev --getsize64 /dev/nulll 5 sobols@sobols-VirtualBox:~/hw/nulll$ printf "world!" > /dev/nulll sobols@sobols-VirtualBox:~/hw/nulll$ blockdev --getsize64 /dev/nulll 11 sobols@sobols-VirtualBox:~/hw/nulll$ printf "more" > /dev/nulll bash: printf: write error: No space left on device sobols@sobols-VirtualBox:~/hw/nulll$ blockdev --getsize64 /dev/nulll 12 sobols@sobols-VirtualBox:~/hw/nulll$ sudo rmmod nulll
Должна поддерживаться конкурентная запись из многих процессов/потоков:
sobols@sobols-VirtualBox:~/hw/nulll$ sudo insmod nulll.ko sobols@sobols-VirtualBox:~/hw/nulll$ seq 10 | xargs -P 10 -I{} dd if=/dev/zero of=/dev/nulll bs=1000 count=1000000 1000000+0 records in 1000000+0 records out 1000000000 bytes (1,0 GB, 954 MiB) copied, 1,79302 s, 558 MB/s 1000000+0 records in 1000000+0 records out 1000000000 bytes (1,0 GB, 954 MiB) copied, 1,90621 s, 525 MB/s 1000000+0 records in 1000000+0 records out 1000000000 bytes (1,0 GB, 954 MiB) copied, 1,92727 s, 519 MB/s 1000000+0 records in 1000000+0 records out 1000000000 bytes (1,0 GB, 954 MiB) copied, 2,00348 s, 499 MB/s 1000000+0 records in 1000000+0 records out 1000000000 bytes (1,0 GB, 954 MiB) copied, 1,92515 s, 519 MB/s 1000000+0 records in 1000000+0 records out 1000000000 bytes (1,0 GB, 954 MiB) copied, 1,91734 s, 522 MB/s 1000000+0 records in 1000000+0 records out 1000000000 bytes (1,0 GB, 954 MiB) copied, 2,07701 s, 481 MB/s 1000000+0 records in 1000000+0 records out 1000000000 bytes (1,0 GB, 954 MiB) copied, 2,04042 s, 490 MB/s 1000000+0 records in 1000000+0 records out 1000000000 bytes (1,0 GB, 954 MiB) copied, 2,01516 s, 496 MB/s 1000000+0 records in 1000000+0 records out 1000000000 bytes (1,0 GB, 954 MiB) copied, 2,06829 s, 483 MB/s sobols@sobols-VirtualBox:~/hw/nulll$ blockdev --getsize64 /dev/nulll 10000000000
Технические детали
Язык программирования — C. Помимо исходного кода, нужно предоставить также Makefile для сборки модуля ядра (ko-файла).
Утверждается, что решение задачи свободно умещается в 100 строк кода.
Проверка будет осуществляться на 64-битной ОС Ubuntu 18.04.1 LTS со стандартным ядром 4.15.0. Поскольку ядро развивается, возможны небольшие нюансы с совместимостью. Например, функция misc_deregister() в ядрах до версии 4.3 возвращала int, затем тип возвращаемого значения был изменён на void [2]. При проверке мелкие ошибки компиляции, вызванные различием версий ядер, я готов поправить самостоятельно.
Литература
- You can be a kernel hacker! — мотивирующий пост от Джулии Эванс (читать по желанию).
- Write Your First Linux Kernel Module — статья про разработку модуля ядра, который создаёт устройство /dev/reverse, призванное менять порядок слов в строках на противоположный.