zirias@
Developer
Consider this test program:
It writes 4 times 16kiB worth of zeros to /dev/null and always outputs 4 times
But now, look at truss(1) to see the actual syscalls performed:
Ok, the standard buffer size seems to be just 4kiB, pretty small for today, but you can change it ... and after changing it to 16kiB, the whole chunk is written at once.
But then, with buffering disabled, it only ever writes 1kiB? I would have expected it to try writing the whole buffer passed instead (and resume on short writes).
For comparison, this is what glibc on Linux does (
Does anyone know a reason for this unexpected behavior in FreeBSD's libc.so with buffering disabled (
Background discovering this was a tool where I already have large buffers, so disabling stdio buffering seems like an obvious optimization to avoid needless copying of data, but of course if the price is a vastly increasing number of syscalls, this is pretty bad ...
C:
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
char dummy[16*1024];
int main(void)
{
FILE *nulldev = fopen("/dev/null", "w");
printf("written: %zu\n", fwrite(dummy, 1, sizeof dummy, nulldev));
fclose(nulldev);
nulldev = fopen("/dev/null", "w");
setvbuf(nulldev, 0, _IOFBF, sizeof dummy);
printf("written: %zu\n", fwrite(dummy, 1, sizeof dummy, nulldev));
fclose(nulldev);
nulldev = fopen("/dev/null", "w");
setvbuf(nulldev, 0, _IONBF, 0);
printf("written: %zu\n", fwrite(dummy, 1, sizeof dummy, nulldev));
fclose(nulldev);
int nullfd = open("/dev/null", O_WRONLY);
printf("written: %zu\n", (size_t)write(nullfd, dummy, sizeof dummy));
close(nullfd);
}
It writes 4 times 16kiB worth of zeros to /dev/null and always outputs 4 times
written: 16384
(which is required for fwrite(3) by the C standard if no write error occurs, and is at least expected for raw write(2) as well).But now, look at truss(1) to see the actual syscalls performed:
Code:
mmap(0x0,135168,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON,-1,0x0) = 34924384256 (0x821a7d000)
mprotect(0x2b5e1ab20000,4096,PROT_READ) = 0 (0x0)
issetugid() = 0 (0x0)
sigfastblock(0x1,0x2b5e1ab22840) = 0 (0x0)
open("/etc/libmap.conf",O_RDONLY|O_CLOEXEC,04152000030) = 3 (0x3)
fstat(3,{ mode=-rw-r--r-- ,inode=332418,size=47,blksize=4096 }) = 0 (0x0)
read(3,"# $FreeBSD$\nincludedir /usr/loc"...,47) = 47 (0x2f)
close(3) = 0 (0x0)
open("/usr/local/etc/libmap.d",O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC,0165) = 3 (0x3)
fcntl(3,F_ISUNIONSTACK,0x0) = 0 (0x0)
getdirentries(3,"_\M-e\^Z\0\0\0\0\0\^A\0\0\0\0\0"...,4096,{ 0x0 }) = 104 (0x68)
open("/usr/local/etc/libmap.d/mesa.conf",O_RDONLY|O_CLOEXEC,0165) = 4 (0x4)
fstat(4,{ mode=-rw-r--r-- ,inode=1347155,size=38,blksize=4096 }) = 0 (0x0)
read(4,"libGLX_indirect.so.0 libGLX_mesa"...,38) = 38 (0x26)
close(4) = 0 (0x0)
getdirentries(3,0x821a82008,4096,{ 0x1a15d221 }) = 0 (0x0)
close(3) = 0 (0x0)
open("/var/run/ld-elf.so.hints",O_RDONLY|O_CLOEXEC,010002706) = 3 (0x3)
read(3,"Ehnt\^A\0\0\0\M^@\0\0\0\M^R\^A\0"...,128) = 128 (0x80)
fstat(3,{ mode=-r--r--r-- ,inode=393218,size=530,blksize=4096 }) = 0 (0x0)
pread(3,"/lib:/usr/lib:/usr/lib/compat:/u"...,402,0x80) = 402 (0x192)
close(3) = 0 (0x0)
open("/lib/libc.so.7",O_RDONLY|O_CLOEXEC|O_VERIFY,00) = 3 (0x3)
fstat(3,{ mode=-r--r--r-- ,inode=225660,size=1943128,blksize=131072 }) = 0 (0x0)
mmap(0x0,4096,PROT_READ,MAP_PRIVATE|MAP_PREFAULT_READ,3,0x0) = 34937102336 (0x82269e000)
mmap(0x0,4190208,PROT_NONE,MAP_GUARD,-1,0x0) = 34952810496 (0x823599000)
mmap(0x823599000,532480,PROT_READ,MAP_PRIVATE|MAP_FIXED|MAP_NOCORE|MAP_PREFAULT_READ,3,0x0) = 34952810496 (0x823599000)
mmap(0x82361b000,1351680,PROT_READ|PROT_EXEC,MAP_PRIVATE|MAP_FIXED|MAP_NOCORE|MAP_PREFAULT_READ,3,0x81000) = 34953342976 (0x82361b000)
mmap(0x823765000,40960,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_FIXED|MAP_PREFAULT_READ,3,0x1ca000) = 34954694656 (0x823765000)
mmap(0x82376f000,28672,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_FIXED|MAP_PREFAULT_READ,3,0x1d3000) = 34954735616 (0x82376f000)
mmap(0x823776000,2236416,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_FIXED|MAP_ANON,-1,0x0) = 34954764288 (0x823776000)
munmap(0x82269e000,4096) = 0 (0x0)
close(3) = 0 (0x0)
mprotect(0x823765000,36864,PROT_READ) = 0 (0x0)
sysarch(AMD64_SET_FSBASE,0x820ee1020) = 0 (0x0)
mprotect(0x823765000,36864,PROT_READ|PROT_WRITE) = 0 (0x0)
mprotect(0x823765000,36864,PROT_READ) = 0 (0x0)
readlink("/etc/malloc.conf",0x820ee0710,1024) ERR#2 'No such file or directory'
issetugid() = 0 (0x0)
mmap(0x0,2097152,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON|MAP_ALIGNED(21),-1,0x0) = 34930163712 (0x822000000)
cap_getmode({ 0 }) = 0 (0x0)
open("/dev/hpet0",O_RDONLY|O_CLOEXEC,00) = 3 (0x3)
mmap(0x0,4096,PROT_READ,MAP_SHARED,3,0x0) = 34967486464 (0x824398000)
close(3) = 0 (0x0)
mmap(0x0,2097152,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON|MAP_ALIGNED(12),-1,0x0) = 34937163776 (0x8226ad000)
mmap(0x0,4194304,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON|MAP_ALIGNED(21),-1,0x0) = 34972106752 (0x824800000)
mprotect(0x202000,4096,PROT_READ) = 0 (0x0)
open("/dev/null",O_WRONLY|O_CREAT|O_TRUNC,0666) = 3 (0x3)
fstat(3,{ mode=crw-rw-rw- ,inode=35,size=0,blksize=4096 }) = 0 (0x0)
ioctl(3,TIOCGETA,0x820ee18e0) ERR#25 'Inappropriate ioctl for device'
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,4096) = 4096 (0x1000)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,4096) = 4096 (0x1000)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,4096) = 4096 (0x1000)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,4096) = 4096 (0x1000)
fstat(1,{ mode=crw-rw-rw- ,inode=35,size=0,blksize=4096 }) = 0 (0x0)
ioctl(1,TIOCGETA,0x820ee1500) ERR#25 'Inappropriate ioctl for device'
close(3) = 0 (0x0)
open("/dev/null",O_WRONLY|O_CREAT|O_TRUNC,0666) = 3 (0x3)
fstat(3,{ mode=crw-rw-rw- ,inode=35,size=0,blksize=4096 }) = 0 (0x0)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,16384) = 16384 (0x4000)
close(3) = 0 (0x0)
open("/dev/null",O_WRONLY|O_CREAT|O_TRUNC,0666) = 3 (0x3)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
close(3) = 0 (0x0)
openat(AT_FDCWD,"/dev/null",O_WRONLY,00) = 3 (0x3)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,16384) = 16384 (0x4000)
close(3) = 0 (0x0)
write(1,"written: 16384\nwritten: 16384\n"...,60) = 60 (0x3c)
exit(0x0)
process exit, rval = 0
Ok, the standard buffer size seems to be just 4kiB, pretty small for today, but you can change it ... and after changing it to 16kiB, the whole chunk is written at once.
But then, with buffering disabled, it only ever writes 1kiB? I would have expected it to try writing the whole buffer passed instead (and resume on short writes).
For comparison, this is what glibc on Linux does (
strace
output):
Code:
execve("./writetest", ["./writetest"], 0x7ffccad405e0 /* 19 vars */) = 0
brk(NULL) = 0x55ad5781f000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f129368f000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=26922, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 26922, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f1293688000
close(3) = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\220s\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=1922136, ...}, AT_EMPTY_PATH) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 1970000, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f12934a7000
mmap(0x7f12934cd000, 1396736, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x26000) = 0x7f12934cd000
mmap(0x7f1293622000, 339968, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x17b000) = 0x7f1293622000
mmap(0x7f1293675000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1ce000) = 0x7f1293675000
mmap(0x7f129367b000, 53072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f129367b000
close(3) = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f12934a4000
arch_prctl(ARCH_SET_FS, 0x7f12934a4740) = 0
set_tid_address(0x7f12934a4a10) = 2658583
set_robust_list(0x7f12934a4a20, 24) = 0
rseq(0x7f12934a5060, 0x20, 0, 0x53053053) = 0
mprotect(0x7f1293675000, 16384, PROT_READ) = 0
mprotect(0x55ad56028000, 4096, PROT_READ) = 0
mprotect(0x7f12936c1000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x7f1293688000, 26922) = 0
getrandom("\x28\x30\x1a\x30\x2e\x00\xbd\x7e", 8, GRND_NONBLOCK) = 8
brk(NULL) = 0x55ad5781f000
brk(0x55ad57840000) = 0x55ad57840000
openat(AT_FDCWD, "/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
newfstatat(3, "", {st_mode=S_IFCHR|0666, st_rdev=makedev(0x1, 0x3), ...}, AT_EMPTY_PATH) = 0
ioctl(3, TCGETS, 0x7ffe90d579d0) = -1 ENOTTY (Inappropriate ioctl for device)
write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
newfstatat(1, "", {st_mode=S_IFCHR|0666, st_rdev=makedev(0x1, 0x3), ...}, AT_EMPTY_PATH) = 0
ioctl(1, TCGETS, 0x7ffe90d57400) = -1 ENOTTY (Inappropriate ioctl for device)
close(3) = 0
openat(AT_FDCWD, "/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
newfstatat(3, "", {st_mode=S_IFCHR|0666, st_rdev=makedev(0x1, 0x3), ...}, AT_EMPTY_PATH) = 0
ioctl(3, TCGETS, 0x7ffe90d57a70) = -1 ENOTTY (Inappropriate ioctl for device)
write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
close(3) = 0
openat(AT_FDCWD, "/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
close(3) = 0
openat(AT_FDCWD, "/dev/null", O_WRONLY) = 3
write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
close(3) = 0
write(1, "written: 16384\nwritten: 16384\nwr"..., 60) = 60
exit_group(0) = ?
+++ exited with 0 +++
Does anyone know a reason for this unexpected behavior in FreeBSD's libc.so with buffering disabled (
_IONBF
)? Or is this just some bug?Background discovering this was a tool where I already have large buffers, so disabling stdio buffering seems like an obvious optimization to avoid needless copying of data, but of course if the price is a vastly increasing number of syscalls, this is pretty bad ...