stdio provides formatted I/O, buffering, stream state, and position management via FILE objects. Buffering can coalesce many small operations, but it does not guarantee that every fwrite avoids entering the kernel, nor does it guarantee that each API call corresponds to exactly one system call when unbuffered. To determine where data has reached, you must distinguish between library interfaces, low-level I/O, and persistence layers.
FILE and File Descriptors
An fd is an integer index in the process descriptor table, while a FILE also maintains a buffer cursor, EOF/error state, direction, and locks. fread may return directly from a prefetched user buffer, or it may fill the buffer or perform larger low-level reads; fwrite similarly can take the buffered path or write directly.
The _IO_FILE fields in glibc aid in reading the implementation but are not a portable interface across different libc implementations and should not be modified in application code. A FILE does not necessarily correspond to a regular file; for example, memory streams may have different underlying operations.
Three Buffering Strategies
Mode
Typical Use
Understanding of Flush Timing
_IOFBF Fully Buffered
Regular files, large amounts of small output
Flushes when full, on explicit flush, on close, etc.; not only when full
_IOLBF Line Buffered
Interactive terminal output
Flushes on newline or other conditions, or on explicit flush
_IONBF Unbuffered
Short outputs needing immediate push
Does not accumulate regular output buffers, but offers no write-to-disk guarantees
The common Linux libc defaults to line buffering for terminal stdout, fully buffered for regular file output, and unbuffered for stderr. After redirection to a file or pipe, stdout behavior may change; thus, it is not contradictory for the same program to output promptly in a terminal but delay output in a pipeline.
setvbuf should be called after opening the stream and before performing other operations on it, and its return value should be checked. BUFSIZ is a macro provided by the implementation; not all FILE buffers are guaranteed to be the same size, and you should not hardcode a specific glibc value as a portable constant. setvbuf
FILE *fp =tmpfile();if(fp ==NULL)return1;char storage[4096];if(setvbuf(fp, storage, _IOFBF,sizeof storage)!=0){fclose(fp);return1;}/* Use fp; fclose(fp) must be called before storage goes out of scope. */if(fclose(fp)== EOF)return1;
This is a snippet within a function body and requires <stdio.h>. Custom buffers must remain alive until the stream is closed; passing a function-local array to a FILE that will be returned leaves a dangling reference.
fwrite, fflush, and fsync Guarantees Differ
fwrite returning the full count may only indicate that the data was accepted by the library; fflush succeeding means the corresponding user buffer has been submitted via a low-level write operation, but it does not guarantee persistence against power loss. For regular files requiring specific persistence guarantees, you typically call fflush first, then fsync on fileno(fp), checking errors for each separately. fflush(3)
fsync operates on kernel file state and cannot flush out data still sitting in the FILE buffer. Crash consistency for newly created or renamed files may also require syncing the directory containing them, which differs from syncing individual file content. fsync(2)
The kernel cache also does not guarantee that any 1000 write calls will be merged into exactly one physical I/O operation. Writeback policies, filesystems, block sizes, and device queues all play a role. Avoid hardcoding numbers like "fixed 100x speedup" or "system calls definitely take 200 ns."
Why can't fsync flush data inside FILE?
Append three bytes, then call fsync directly: the kernel doesn't know about these three bytes yet, so the sync guarantee won't cover them. Call fflush first, then fsync, and the three layers of accounting align. The underlying file retains committed content; don't depict sync as "moving and clearing" data from the kernel, because the file cache may still hold these bytes.
Preparing the visual
Why can't fsync flush data inside FILE?
fflush submits the user buffer to the underlying file; fsync syncs the kernel's known file state. Calling fsync before fflush does not add persistence guarantees for the subsequent submission.
{"id":"stdio-durability-boundaries","title":"Why can't fsync flush data inside FILE?","summary":"fflush submits the user buffer to the underlying file; fsync syncs the kernel's known file state. Calling fsync before fflush does not add persistence guarantees for the subsequent submission.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"append\" data-i18n=\"append\"></button><button id=\"flush\" data-i18n=\"flush\"></button><button id=\"sync\" data-i18n=\"sync\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><div class=\"track-row\"><span data-i18n=\"user\"></span><div id=\"user\" class=\"track\"></div></div><div class=\"track-row\"><span data-i18n=\"kernel\"></span><div id=\"kernel\" class=\"track\"></div></div><div class=\"track-row\"><span data-i18n=\"durable\"></span><div id=\"durable\" class=\"track\"></div></div><div class=\"metrics\"><div><span data-i18n=\"pending\"></span><strong id=\"pending\"></strong></div><div><span data-i18n=\"safe\"></span><strong id=\"safe\"></strong></div></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n#user,#kernel,#durable{display:grid;grid-template-columns:repeat(8,minmax(0,1fr))}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let total=0,submitted=0,synced=0;function draw(){for(const [id,lo,hi] of [['user',submitted,total],['kernel',0,submitted],['durable',0,synced]])cells(id,Array.from({length:8},(_,i)=>({text:i>=lo&&i<hi?'ABCDEFGH'[i]:'—',cls:i>=lo&&i<hi?(id==='durable'?'gold':'on'):'empty'})));set('pending',total-submitted);set('safe',synced);$('#append').disabled=total===8;$('#kernel').dataset.count=submitted;}$('#append').onclick=()=>{total++;if(total-submitted===8)submitted=total;draw()};$('#flush').onclick=()=>{submitted=total;draw()};$('#sync').onclick=()=>{synced=submitted;draw()};$('#reset').onclick=()=>{total=submitted=synced=0;draw()};draw();","audio":false,"strings":{"append":"Append one byte","durable":"Prefix with sync guarantees","flush":"fflush","heading":"Why can't fsync flush data inside FILE?","intro":"Perform append, fflush, and fsync in various orders, comparing which layer holds the data.","kernel":"File content accepted by kernel","pending":"Still in user buffer","reset":"Reset","safe":"Has sync guarantee","scope":"Existing regular file, capacity for 8 example bytes; no automatic write before full buffer; all I/O succeeds. The durable layer represents the prefix with sync guarantees, not predicting actual background writeback.","scopeLabel":"Model Scope and Conventions","sync":"fsync","user":"Pending submission in FILE"}}
Correctly Interpreting Return Values and Errors
fread/fwrite return the number of elements completed; the count equals the byte count only when size=1. To handle trailing bytes byte-by-byte, using fread(buf, 1, capacity, fp) more clearly expresses how many bytes were obtained.
A short read may be EOF or an error; distinguish using feof and ferror. The EOF flag is set only after an attempt to read discovers the end, so while (!feof(fp)) is not a correct read loop condition. fread(3)
The following function copies two streams opened by the caller and flushes the output on successful completion; it does not close the streams and provides no persistence guarantees:
Write errors may occur at the time of fwrite, or they may be deferred until fflush or fclose. Even if an error is reported on close, the caller cannot continue to treat the original FILE as a valid object to retry fclose. Whether to retry after clearerr on an error stream should be designed based on the cause and position semantics, not blindly loop infinitely.
Errors May Still Appear During Flush After Write Acceptance
Linux's /dev/full returns ENOSPC for writes, which can be used to demonstrate how the library buffer defers errors to fflush without filling a real disk. The following is limited to glibc/Linux: place three characters in a fully buffered stream, then explicitly flush. full(4)
Save as delayed-error.c, compile, and run. The first four boolean results should be 1. The close result is shown separately and is not required to repeat the same flush error. You cannot ignore previous failures by only looking at the fclose return value at the end, nor can you declare data written successfully just because three fputc calls succeeded.
Mixing Reads, Writes, and Low-level fd Operations
Switching an update stream (e.g., r+, w+) from output to input requires fflush or a positioning operation; switching from input to output usually requires a positioning operation, unless the previous input encountered EOF. Use fseek/fsetpos etc. to explicitly synchronize direction and position. fopen(3)
stdio's lookahead can cause the underlying fd offset to advance ahead of the application's consumption position. Calling read directly on fileno(fp) may bypass unconsumed buffers; mixing write may also alter output order. Prefer unified I/O layers on a single object; if mixing is necessary, adhere to POSIX active handle coordination rules.
fflush(stdin) is not a universal method for clearing keyboard input. POSIX defines buffer discarding behavior for seekable input streams, but pipes and terminals are not in the same category.
Checking Position, Direction, and EOF in the Same Update Stream
Save as update-stream.c; tmpfile creates a binary update stream. Write abcdef, then read the first three bytes; use a positioning operation to coordinate the switch from read to write, replace d at the current position with X, and finally read back abcXef.
#include<stdio.h>#include<string.h>intmain(void){
FILE *fp =tmpfile();if(fp ==NULL)return1;int failed =1;char data[7]={0};if(fwrite("abcdef",1,6, fp)!=6)goto done;if(fseek(fp,0, SEEK_SET)!=0)goto done;if(fread(data,1,3, fp)!=3||memcmp(data,"abc",3)!=0)goto done;if(fseek(fp,0, SEEK_CUR)!=0)goto done;if(fputc('X', fp)== EOF)goto done;if(fseek(fp,0, SEEK_SET)!=0)goto done;if(fread(data,1,6, fp)!=6||memcmp(data,"abcXef",6)!=0)goto done;if(feof(fp)||ferror(fp))goto done;if(fgetc(fp)!= EOF ||!feof(fp)||ferror(fp))goto done;clearerr(fp);if(feof(fp)||ferror(fp))goto done;/* Clearing the flag does not move the file position; reading again will still hit EOF. */if(fgetc(fp)!= EOF ||!feof(fp))goto done;if(fseek(fp,0, SEEK_SET)!=0||feof(fp))goto done;if(fgetc(fp)!='a')goto done;
failed =0;puts("update and EOF transitions passed");done:if(fclose(fp)== EOF) failed =1;return failed;}
Compile and run: cc -Wall -Wextra update-stream.c -o update-stream, then ./update-stream. When exactly 6 bytes are read, the application's request is satisfied, and no additional read has occurred to discover the end; the next fgetc sets EOF. clearerr only clears the state, not the file position, while a successful fseek both positions and clears EOF. fseek(3), Stream State
The goto here uniformly closes the FILE, avoiding resource leaks in each check branch. It does not automatically recover from failed I/O; if you want to report specific reasons, save the error information before jumping.
fork Copies Unflushed User Buffers
After fork, parent and child processes each have copies of FILE states and user buffers, while the underlying open file descriptors may be shared. If both sides flush normally via exit, the same unflushed content may be written twice.
Even if printf("before fork\n") contains a newline, stdout redirected to a fully buffered context may not have flushed yet. Single-threaded programs can explicitly fflush at appropriate points and check the result; you cannot infer safety across all output environments based on "having a newline."
After fork in multithreaded programs, the child process is still subject to async-signal-safety restrictions before exec and cannot arbitrarily call stdio to tidy up lock states. The exec failure path typically uses appropriate low-level reporting and _exit, avoiding repeated flushing of inherited buffers. _exit does not flush stdio, unlike normal exit.
Order, Atomicity, and Performance
Writing to stdout and stderr separately, even if both are flushed, does not guarantee a unified global display order in all external collectors. For strict ordering, use the same output channel or a logging protocol with sequence numbers.
stdio single calls usually have stream locks, but a log composed of multiple calls does not automatically become a transaction. When multiple processes write to a pipe, you must also consider the actual length of low-level write and PIPE_BUF; do not treat "one fprintf" as "one atomic pipe message."
Using strace to observe the number of low-level write calls can verify batching effects; evaluate buffer sizes using actual throughput, latency, and error behavior. Non-blocking network event loops usually manage fds and application buffers directly; see epoll and Event-Driven Programming for details.