LINUX.ORG.RU

Странное поведение splice для блочных устройств на ядрах > 3.6

 ,


1

3

Нашел странность в поведении splice на ядрах новее, чем 3.6. Берем такой файл:

#define _GNU_SOURCE
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<errno.h>
#include<stdio.h>

int main(void) {
        int fdi = STDIN_FILENO;
        int fdo = STDOUT_FILENO;
        size_t r;
        fprintf(stderr, "Seeking to %zd\n", lseek(fdo, 0, SEEK_END));
        r = splice(fdi, NULL, fdo, NULL, BYTES_TO_SPLICE, 0);
        fprintf(stderr, "Result: %zd %d\n", r, errno);
        perror("Splice");
        return 0;
}

Он пытается вызвать splice для записи за конец файла в stdout. Создаем блочное устройство:

dd if=/dev/zero of=myfile bs=1M count=100
losetup /dev/loop7 myfile

Пытаемся записать после его конца при разных значениях объема сплайса (BYTES_TO_SPLICE) на ядре 3.6:

# gcc -Wall -DBYTES_TO_SPLICE=4096 tspl.c
# cat /dev/zero | ./a.out > /dev/loop7
Seeking to 104857600
Result: -1 5
Splice: Input/output error
# gcc -Wall -DBYTES_TO_SPLICE=4095 tspl.c
# cat /dev/zero | ./a.out > /dev/loop7
Seeking to 104857600
Result: -1 5
Splice: Input/output error

Казалось бы, все логично. Писать за конец блочного устройства нельзя. Однако возьмем ядро 3.10....

# gcc -Wall -DBYTES_TO_SPLICE=4095 tspl.c
# cat /dev/zero | ./a.out > /dev/loop7
Seeking to 104857600
Result: -1 5
Splice: Input/output error
# gcc -Wall -DBYTES_TO_SPLICE=4096 tspl.c
# cat /dev/zero | ./a.out > /dev/loop7
Seeking to 104857600
Result: 4096 0
Splice: Success

При размере 4096 и больше об ошибке не сообщается. Куда уходят данные, тоже непонятно.

Есть у меня подозрение, что изменение поведения связано вот с этим коммитом.

Хотелось бы, чтобы какой-нибудь грамотный kernel hacker провел этот тест у себя на ядре поновее и привел свои соображения. Если это баг в ядре, то лучше о нем сообщить.



Последнее исправление: Vadim_Z (всего исправлений: 1)

Я не kernel hacker, но это действительно смахивает на баг. Я потестил на ядре 3.14.4: если размер меньше 4096, то как и положено splice() возвращает ошибку, а в dmesg сваливается:

[3238995.053449] attempt to access beyond end of device
[3238995.053458] loop7: rw=0, want=204808, limit=204800
При большем размере ни ошибки, ни новых сообщений в логе я не наблюдаю. Причём это проявляется не только с loop, но и с другими блочными устройствами.

Deleted
()

пишите мантейнеру

quest ★★★★
()

Автоматизация git bisect на примере...

Я решил ради интереса попробовать git bisect для поиска коммита, связанного с этой проблемой, и в процессе описать как и что. Краткая справка: git bisect позволяет методом двоичного поиска между точно работающим правильно срезом исходников и точно сломанным найти проблемный коммит. Сначала мы указываем bisect'у где точно всё плохо (v3.10 в нашем случае) и где точно всё хорошо (v3.6). Затем он автоматически подсовывает нам коммиты, мы их тестим и отмечаем как хорошие (проблема не воспроизводится), плохие (проблема есть) и те, которые нужно пропустить (исходники совсем не собираются или сломано что-то другое). В конце bisect выдаёт нам хеш проблемного коммита. Вместо тестирования коммитов вручную, процесс можно польностью автоматизировать при помощи git bisect run и небольшого скрипта, который и будет выдавать bisect'у результат - «хорошо», «плохо» или «пропустить».

Итак, сначала нужно немного модифицировать тест топикстартера, чтобы он возвращал результат, который мы можем легко получить и обработать:

test.c

#define _GNU_SOURCE
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<errno.h>
#include<stdio.h>

int main(void) {
        int fdi = STDIN_FILENO;
        int fdo = STDOUT_FILENO;
        size_t r;
        fprintf(stderr, "Seeking to %zd\n", lseek(fdo, 0, SEEK_END));
        r = splice(fdi, NULL, fdo, NULL, BYTES_TO_SPLICE, 0);
        fprintf(stderr, "Result: %zd %d\n", r, errno);
        perror("Splice");
        return (r == -1)? 0 : 1;
}

К этому тесту нам нужен скрипт, который будет запускаться внутри виртуалки (qemu) в качестве инита, запускать тест, выдавать результат работы «во вне» и завершать работу системы. Как выдавать результат из qemu? Я выбрал самый простой и топорный вариант: подсовывать qemu ещё один лишний файл в качестве блочного устройства и просто делать туда echo. Вот что вышло:

run-test

#!/bin/sh -eux

if cat /dev/zero | /root/test >/dev/sdb; then
    echo 0 >/dev/sdc
else
    echo 1 >/dev/sdc
fi

sync
mount -t proc proc /proc
echo 1 >/proc/sys/kernel/sysrq
echo o >/proc/sysrq-trigger
exec cat

/root/test - это статически собранный тестовый бинарник, /dev/sdb - блочное устройство с пустым образом (вместо loop), /dev/sdc - костыль для передачи результата.

Также нам потребуется корневая ФС, в которой будет как минимум /bin/sh с зависимостями, чтобы наш скрипт заработал. Для генерации ФС я написал ещё один небольшой скрипт, использующий debootstrap:

create-fs

#!/bin/sh -eux

BASE="/home/ivan.mironov/projects/test/splice-block-test"
FS_IMAGE="${BASE}/fs.ext4"
SIZE_MB=1024
MOUNT_POINT="${BASE}/mount"
SRC="${BASE}/test.c"
SCRIPT="${BASE}/run-test"

dd if=/dev/null of="${FS_IMAGE}" bs=1M count=0 seek=$SIZE_MB
mkfs.ext4 -F "${FS_IMAGE}"

sudo mkdir -v "${MOUNT_POINT}"
sudo mount -o loop "${FS_IMAGE}" "${MOUNT_POINT}"

sudo debootstrap --arch "amd64" "wheezy" "${MOUNT_POINT}" "http://http.debian.net/debian/"
sudo mknod "${MOUNT_POINT}/dev/sda" b 8 0 || true
sudo mknod "${MOUNT_POINT}/dev/sdb" b 8 16 || true
sudo mknod "${MOUNT_POINT}/dev/sdc" b 8 32 || true

sudo chmod a+rwX "${MOUNT_POINT}/root"
gcc -Wall -static -DBYTES_TO_SPLICE=4096 -o "${MOUNT_POINT}/root/test" "${SRC}"
cp -v "${SCRIPT}" "${MOUNT_POINT}/root/"

sudo umount "${MOUNT_POINT}"
sudo rmdir -v "${MOUNT_POINT}"

Остался последний скрипт, который будет запускаться git bisect'ом, собирать ядро, запускать его под qemu и возвращать bisect'у результат:

bisect

#!/bin/sh -eux

BASE="/home/ivan.mironov/projects/test/splice-block-test"
FS_IMAGE="${BASE}/fs.ext4"
TEST_IMAGE="${BASE}/sdb.block"
RES_IMAGE="${BASE}/sdc.block"

dd if=/dev/null of="${TEST_IMAGE}" bs=1M count=0 seek=100
echo "   " >"${RES_IMAGE}"

make distclean || exit 125
make defconfig || exit 125
make -j 10 || exit 125

qemu-kvm \
        -nographic \
        -kernel "./arch/x86/boot/bzImage" \
        -append "root=/dev/sda ro init=/root/run-test console=ttyS0" \
        -hda "${FS_IMAGE}" \
        -hdb "${TEST_IMAGE}" \
        -hdc "${RES_IMAGE}" || exit 125

result=$( tr -d "\0\n" <"${RES_IMAGE}" )
echo "RESULT: $result"
[ $result -eq 1 ] && exit 1
exit 0

Код 0 - хороший коммит, код 125 - пропустить коммит, 1 (любой код от 1 до 127 включительно, кроме 125) - плохой коммит. Любой другой код остановит git bisect, у меня это не используется.

Deleted
()
Ответ на: Автоматизация git bisect на примере... от Deleted

Автоматизация git bisect на примере... [part 2]

Осталось это всё запустить... Переходим в директорию с гит-клоном исходников ядра и...

$ git bisect start v3.10 v3.6
$ git bisect run ~/projects/test/splice-block-test/bisect
Можно идти пить чай. На сосднем терминале можно периодически запускать «git bisect log» и смотреть как продвигается дело.

В конце в нашем случае git bisect выдаст:

bbec0270bdd887f96377065ee38b8848b5afa395 is the first bad commit
commit bbec0270bdd887f96377065ee38b8848b5afa395
Author: Linus Torvalds <torvalds@linux-foundation.org>
Date:   Thu Nov 29 12:31:52 2012 -0800

    blkdev_max_block: make private to fs/buffer.c

    We really don't want to look at the block size for the raw block device
    accesses in fs/block-dev.c, because it may be changing from under us.
    So get rid of the max_block logic entirely, since the caller should
    already have done it anyway.

    That leaves the only user of this function in fs/buffer.c, so move the
    whole function there and make it static.

    Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

:040000 040000 3d7d039b5b712f8c57d0daf56f3dda2168d43ef9 8a465dc6afd0b301505b67b484a8ff24404b0bce M	fs
:040000 040000 69c3007818393029f8c8ba7838f63ff7bcc443a5 9bedfa148e484ee751751a6c78272952ac908b59 M	include
bisect run success
Тот же результат, что и у топикстартера. Скорее всего он тоже нашёл этот коммит бисектом. «git bisect log» при этом выдаёт историю поиска:
# bad: [8bb495e3f02401ee6f76d1b1d77f3ac9f079e376] Linux 3.10
# good: [a0d271cbfed1dd50278c6b06bead3d00ba0a88f9] Linux 3.6
git bisect start 'v3.10' 'v3.6'
# bad: [1b22e382ab40b0e3ee5abb3e310dffb16fee22aa] tracing: Let tracing_snapshot() be used by modules but not NMI
git bisect bad 1b22e382ab40b0e3ee5abb3e310dffb16fee22aa
# good: [15ffde4d36c30d81cd04a154960608486a1464f4] staging: et131x: Refactor et131x_isr() to remove indenting
git bisect good 15ffde4d36c30d81cd04a154960608486a1464f4
# bad: [66cdd0ceaf65a18996f561b770eedde1d123b019] Merge tag 'kvm-3.8-1' of git://git.kernel.org/pub/scm/virt/kvm/kvm
git bisect bad 66cdd0ceaf65a18996f561b770eedde1d123b019
# bad: [cf4af01221579a4e895f43dbfc47598fbfc5a731] Merge tag 'boards' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
git bisect bad cf4af01221579a4e895f43dbfc47598fbfc5a731
# bad: [c6bd5bcc4983f1a2d2f87a3769bf309482ee8c04] Merge tag 'tty-3.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty
git bisect bad c6bd5bcc4983f1a2d2f87a3769bf309482ee8c04
# bad: [b58ed041a360ed051fab17e4d9b0f451c6fedba7] Merge tag 'devicetree-for-linus' of git://git.secretlab.ca/git/linux-2.6
git bisect bad b58ed041a360ed051fab17e4d9b0f451c6fedba7
# good: [7e5530af11be68f3109672aed59243f82e1272f0] Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
git bisect good 7e5530af11be68f3109672aed59243f82e1272f0
# bad: [a8936db7c2d9ef7f8e080d629301e448291f3b75] Merge tag 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging
git bisect bad a8936db7c2d9ef7f8e080d629301e448291f3b75
# bad: [29594404d7fe73cd80eaa4ee8c43dcc53970c60e] Linux 3.7
git bisect bad 29594404d7fe73cd80eaa4ee8c43dcc53970c60e
# bad: [df2fc246c8ee8b6067af1fa55d3bc23107457f61] Merge branch 'fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/rusty/linux
git bisect bad df2fc246c8ee8b6067af1fa55d3bc23107457f61
# bad: [4ba0032984fd2626209c6aef0ee025912875bf9f] Merge branch 'v4l_for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media
git bisect bad 4ba0032984fd2626209c6aef0ee025912875bf9f
# good: [d2a0db1ee01aea154ccc460e45a16857e32c4427] [media] s5p-mfc: Handle multi-frame input buffer
git bisect good d2a0db1ee01aea154ccc460e45a16857e32c4427
# bad: [bbec0270bdd887f96377065ee38b8848b5afa395] blkdev_max_block: make private to fs/buffer.c
git bisect bad bbec0270bdd887f96377065ee38b8848b5afa395
# good: [1e8b33328a5407b447ff80953655a47014a6dcb9] blockdev: remove bd_block_size_semaphore again
git bisect good 1e8b33328a5407b447ff80953655a47014a6dcb9
# good: [ab73857e354ab9e317613cba7db714e2c12c6547] direct-io: don't read inode->i_blkbits multiple times
git bisect good ab73857e354ab9e317613cba7db714e2c12c6547
# first bad commit: [bbec0270bdd887f96377065ee38b8848b5afa395] blkdev_max_block: make private to fs/buffer.c

Deleted
()
Ответ на: Автоматизация git bisect на примере... [part 2] от Deleted

Автоматизация git bisect на примере... [part 3]

В master'е эта проблема решена, и мы можем аналогичным способом найти патч, решающий проблему.

Для начала - кардинально поменяем представления о добре и зле!

diff --git a/bisect b/bisect
index 53b5de5..d815141 100755
--- a/bisect
+++ b/bisect
@@ -22,5 +22,5 @@ qemu-kvm \

 result=$( tr -d "\0\n" <"${RES_IMAGE}" )
 echo "RESULT: $result"
-[ $result -eq 1 ] && exit 1
-exit 0
+[ $result -eq 1 ] && exit 0
+exit 1
Затем запускаем наоборот:
git bisect start origin/master v3.10
git bisect run ~/projects/test/splice-block-test/bisect
На этот раз в моём случае процесс шёл дольше, так как попалось много срезов, которые вообще не собирались. Но искомый патч (решающий проблему) найти всё-таки удалось:
8d0207652cbe27d1f962050737848e5ad4671958 is the first bad commit
commit 8d0207652cbe27d1f962050737848e5ad4671958
Author: Al Viro <viro@zeniv.linux.org.uk>
Date:   Sat Apr 5 04:27:08 2014 -0400

    ->splice_write() via ->write_iter()
    
    iter_file_splice_write() - a ->splice_write() instance that gathers the
    pipe buffers, builds a bio_vec-based iov_iter covering those and feeds
    it to ->write_iter().  A bunch of simple cases coverted to that...
    
    [AV: fixed the braino spotted by Cyrill]
    
    Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

:040000 040000 a5308b970d532be09888bc1304caf2ffbf3a7638 f70464499ea87208dfaac32cf6c2d378ff46856d M	fs
:040000 040000 bb6dfd94f695a405b40f078dd82aeca89d12d4eb 770ad6bc1e9ba732e0a36191dff52462e5a34e26 M	include
bisect run success

Может кому-нибудь всё это пригодится.

Deleted
()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: Автоматизация git bisect на примере... [part 3] от Deleted

Да, спасибо за помощь и информацию. Коммит я нашел не bisect-ом. а прикинул логику работы ядра.

Похоже, что указанный последний патч решает проблему, т.е. они сделали более правильную логику для сплайса.

Vadim_Z
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.