PB158 · Lecture 2
The call you write is not the call the operating system runs. fflush() flushes only the C library's buffer [S3]. The kernel buffer needs a second call [S3]. Four boundaries sit below your code: app to library, library to kernel, kernel to device, and device to medium. A different tool watches each of the first three [V10] [S1] [V30]. No tool here watches the fourth [V5].
fflush()
Last time · Lecture 1
Ondra asserted both in Lecture 1. This hour is where he makes the first one good.
Roadmap
Where we are going
Scope
Today
Not today
Part I
Two programs. The same mebibyte. Predict the ratio before we run them.
Two programs write the same mebibyte to a file. That is 1,048,576 bytes. One uses a buffer. One does not.
Write down a ratio. How much slower is the unbuffered program?
I print no ratio here, on purpose. A benchmark you did not watch me produce is the kind of claim this course teaches you to distrust.
buffering=0
The buffer size is not a constant. It grew sixteen-fold in Python 3.14.
Part II
One answer, stated immediately, and then the instrument that proves it.
The difference is the number of times each program crosses into the operating system.
stat
strace
A tracer runs your program and writes down its system calls [S1] [T3].
How to use it
A trace records the system calls a program made, in order, with arguments and return values.
The same two programs, run again under a counting tracer.
strace -c -e trace=write python3 a.py
write
io.DEFAULT_BUFFER_SIZE
Illustration, not a measurement from this room: a count ratio of 100,000 beside a time ratio of 40 is not a mistake.
time ≈ crossings × fixed cost + bytes × marginal cost [V1]
time ≈ crossings × fixed cost + bytes × marginal cost
A buffer collects adjacent bytes and hands them down in one call.
A buffer holds bytes for consecutive positions. Move elsewhere and those bytes cannot stay in it.
Yes. And a pair of system calls exists to remove it.
lseek
pread
pwrite
The tracer stops your program twice per call. That destroys the timing and leaves the counting intact.
What survives these three rows is the count — the only number on this slide you may use [V13].
NtTrace, this course's Windows tracer, prints no counting summary. Both systems count anyway.
Three limits, none visible in the count.
Part III
We counted the crossings. Now we ask where the bytes actually are.
The verb means one thing here: to hand a buffer's contents to the next level down.
Every level buys a large block from the level below. It sells small pieces to the level above.
Each program uses at most one buffer, and not the same one.
Level four is the uncomfortable one, and skipping it would make the diagram dishonest.
fsync
A demonstration. You have the stack now, so predict this one.
hello
The rule sits in the C standard, not in the kernel.
It proves level one is real. It proves nothing about levels three and four.
ltrace
strace stops at system calls. ltrace stops at calls into shared libraries.
apt-get install ltrace
I promised three of four boundaries. Here is the tool each needs.
Part IV
Now that the levels have a reason to exist, walk down them with one small program.
iodemo.c
Nine lines of C. The first open fails on purpose, so the trace shows an error path.
fwrite
Four arguments, and two of them multiply.
Part V
Read as a comparison. Running the tools is the first lab's job.
It reports filesystem and registry activity [S18], which is a different cut than the system-call boundary.
It watches the native API, "the interface between the application space and the OS kernel" [S10].
Two Windows tools, two layers. One answers this lecture's question.
Process Monitor
NtTrace
ntdll.dll
\??\
You can already read this. The anatomy matches the Linux line, and the vocabulary differs.
First difference, visible in the first argument of almost every line.
Linux
= 3
Windows
FileHandle=0x124
Second difference, and it is the layer boundary showing up where nobody expects it.
NTSTATUS
errno
-1 ENOENT
Third difference, and the other two are symptoms of it.
The tempting conclusion is backwards, and the evidence is a list.
Say the platforms are different [T4]. Never say one is worse.
One program, two systems, traced on one day.
Part VI
One question I will not answer, and the work that follows from it.
write()
I built the whole hour towards this sentence. I leave it unresolved.
The error arrives somewhere other than where you caused it.
fclose
The obvious repair is wrong. Three storage engines dropped the retry in 2018 [V40].
Not new subjects. The same objects and the same calls, wearing different names.
SOCKET
What today's tools cannot see.
And what today's tools cost.
--seccomp-bpf
A successful write() is no guarantee that the data has been committed to disk [S2]. The next lecture starts there, on 22 September.
Closing
Then name the level that holds your bytes, and measure the machine in front of you [S7].
Take it with you
ondrasek.github.io/talks
me@ondrejkrajicek.com