4.2.32 · D2 · HinglishOperating Systems

Visual walkthroughFile operations — open, read, write, seek, close

2,382 words11 min read↑ Read in English

4.2.32 · D2 · Coding › Operating Systems › File operations — open, read, write, seek, close

Hum is exact chain ko follow karenge, har link ke liye ek picture:

open

read

write

lseek

past the end

close

Related vault stops is raaste mein: System calls and the kernel boundary, File descriptor table, File system & inodes, Buffered I/O (fopen/fread).


Step 1 — File ko numbered boxes ki row ke roop mein banao

KYA. Kisi bhi code se pehle, raw material ki picture banao. Ek file koi clever cheez nahi hai: yeh bytes ki ek seedhi line hai, aur ek byte bas ek chhota sa box hai jo 0 se 255 tak koi number rakhta hai. Hum boxes ko 0 se number karte hain. Toh ek 20-byte file mein boxes hain.

YEH picture kyun. Neeche har ek call is baat se define hoti hai ki hum kis box ko point kar rahe hain. Agar pehle yeh agree nahi kiya ki boxes ke addresses hain, toh "offset 16" ya "seek to end" jaisi phrases ka koi matlab nahi. Sab kuch is ruler se tiki hui hain.

PICTURE. Boxes ki row dekho. Har box ke neeche likha number uska address hai (uski position). Sabse pehla byte address hai, na ki — yahi off-by-one wali jagah hai jahan aadhe file bugs rehte hain.


Step 2 — open: ek ticket lo, arrow ko 0 par lagao

KYA. Tumhara program disk ko directly touch nahi kar sakta (woh hardware kernel ka hai — dekho System calls and the kernel boundary). Toh tum poochte ho:

Term by term:

  • naam; kernel isse file ke inode tak follow karta hai.
  • access mode, ek vaada ki tum kya karoge.
  • — returned file descriptor: sabse chhota unused small integer (0,1,2 aam taur par stdin/stdout/stderr ke liye liye hote hain, toh aksar 3 milta hai). Yeh tumhare File descriptor table ko index karta hai.

Returned number kyun? Tumhare paas baad ki har call mein "is open file" ka naam lene ke liye ek chhota handle chahiye. Kernel ek side mein ek open file description box bhi banata hai jo offset store karta hai — aur woh arrow ko par lagata hai.

PICTURE. Baayein taraf ticket fd = 3 ek kernel box ki taraf point karta hai jisme offset = 0 hai, aur woh arrow byte 0 ke bilkul baayein baitha hai.


Step 3 — read: bytes copy karo, phir arrow ko daayein slide karo

KYA. Hum 8 bytes maangte hain:

Kernel arrow se shuru karke bytes tumhare buf mein copy karta hai, phir crucial kaam karta hai:

Arrow n se kyun move karta hai, 8 se kyun nahi. Tumne 8 maange the par n mila. Arrow utna hi aage badhta hai jitna actually deliver hua, toh doosra read theek wahan se continue karta hai jahan pehla ruka — automatic sequential reading, koi manual counting nahi.

n < 8 kyun ho sakta hai? File mein shayad 8 bytes se kam bachi hoon, ya (pipe/socket ke liye) kam data aaya ho. Toh n sachchi baat hai; 8 sirf ek wish thi.

PICTURE. Pehle: arrow 0 par. read bytes 0–7 ko buf mein copy karta hai. Baad mein: arrow byte 8 ke bilkul baayein jump kar gaya. buf ke sirf pehle n boxes valid hain — baaki garbage hai.


Step 4 — write: same arrow, opposite direction

KYA. Writing, reading ka mirror hai — bytes tumhare buf se file mein arrow par flow karte hain:

aur phir arrow daayein n se slide karta hai:

Yahan bhi n check kyun karein? Ek short write (n < count) tab ho sakti hai jab disk bhar jaaye ya koi signal interrupt kare. Toh production code loop karta hai jab tak sab kuch bahar nahi aa jaata:

ssize_t total = 0, n;
while (total < count) {
    n = write(fd, buf + total, count - total);
    if (n < 0) break;      // real error
    total += n;            // apna bookkeeping aage badhao
}

PICTURE. Arrow kisi box se shuru hota hai; write boxes ko daayein overwrite karta hai aur arrow ko utni hi door kheenchta hai. Red arrow ki position ko Step 3 se compare karo — identical motion, bytes bas doosri taraf jaati hain.


Step 5 — lseek: arrow ko ek bhi byte touch kiye bina teleport karo

KYA. Kabhi kabhi tumhe agle byte ki zaroorat nahi hoti — tumhe byte 16 chahiye, ya aakhri 4 bytes. lseek arrow ko pure arithmetic se move karta hai, kuch bhi copy nahi karta:

\texttt{offset} & \texttt{whence = SEEK\_SET} \quad(\text{0 se count karo})\\ \texttt{cur} + \texttt{offset} & \texttt{whence = SEEK\_CUR} \quad(\text{yahan se count karo})\\ \texttt{size} + \texttt{offset} & \texttt{whence = SEEK\_END} \quad(\text{end se count karo}) \end{cases}$$ 20-byte file par classic "last 4 bytes" call ke liye term by term: - $\texttt{whence = SEEK\_END}$ → $\texttt{size} = 20$ se measure karna shuru karo. - $\texttt{offset} = -4$ → **baayein** 4 boxes step karo (negative = baayein). - $\texttt{newpos} = 20 + (-4) = 16$. Ab `read(fd, tail, 4)` boxes 16, 17, 18, 19 cover karta hai — exactly last four. **Yeh kyun exist karta hai.** Files ==random access== allow karti hain: har baar shuru se padhna zaruri nahi. `lseek` woh tool hai jo jawaab deta hai "arrow *wahan* rakh do." **PICTURE.** Teen arrows teen `whence` modes ke liye, sab box 16 par different starting points se land karte hain (0 se, ek beech ke "cur" se, aur daayein end se). Koi bytes shaded nahi — sirf arrow move hua, kuch nahi. > [!mistake] "seek slow hai / disk head ko move karta hai." > *Seek* word mechanical lagta hai, lekin `lseek` bas kernel mein **integer** offset update karta hai. Zero bytes boundary cross karte hain. Yeh practically free hai. --- ## Step 6 — Edge case: arrow ko *end ke baad* push karo, phir write karo **KYA.** Agar hum 20-byte file par offset 25 par `lseek` karein (end ke baad) aur phir ek byte `write` karein toh kya? Arrow `size` se bhi aage legal hai. Wahan likhne se file **stretch** hoti hai: boxes 20–24 ek implicit **hole of zero bytes** ke roop mein create ho jaate hain, aur tumhara byte 25 par land karta hai. **Yeh kyun matter karta hai.** Woh hole bytes `0` read back karte hain lekin **koi disk space nahi lagte** — [[File system & inodes|filesystem]] bas record karta hai "yeh zeros hain." Yeh ek ==sparse file== hai. **PICTURE.** Arrow 25 par baitha hai, purane end (20) se kaafi daayein. Boxes 20–24 khali "phantom" zero boxes ke roop mein draw hain; sirf box 25 (red) mein real data hai. > [!recall]- Hole par `read` karne se kya return hota hai? > Boxes 20–24 ko read karne par `0` ke barabar bytes milte hain ::: haan, holes zeros ke roop mein read back karte hain chahe koi disk block allocate nahi hua ho. --- ## Step 7 — Degenerate case: arrow end par → `read` returns 0 **KYA.** Maan lo arrow `size` par baitha hai (20-byte file par box 20). Daayein aur koi boxes nahi hain. Ab `read(fd, buf, 8)` return karta hai: $$\texttt{n} = 0$$ **0 kyun, −1 kyun nahi.** Zero matlab hai "**kuch bhi nahi bacha, cleanly**" — yeh ==End Of File (EOF)== hai, koi error nahi. Errors $-1$ return karte hain. Yeh ek akela fact hai jo copy loops ko sahi se terminate karta hai: ```c while ((n = read(src, buf, sizeof buf)) > 0) // 0 hamein gracefully rokta hai; -1 alag check hota hai /* n bytes write karo */; ``` **PICTURE.** Arrow box 19 ke baad right wall par parked hai; `read` box khali wapas aata hai bade `n = 0` tag ke saath. > [!mistake] "read == 0 ek error hai." > `0` = clean EOF. `-1` = error. EOF ko error treat karna copy ko ek call pehle rokta hai ya forever loop karwata hai. --- ## Step 8 — `close`: ticket wapas karo **KYA.** Aakhir mein: $$\underbrace{\texttt{ret}}_{0 \text{ ok, } -1 \text{ error}} = \texttt{close}(\texttt{fd})$$ Yeh ticket tumhare [[File descriptor table]] ko wapas karta hai aur open file description par reference count giraata hai. Jab woh count 0 par aata hai, kernel koi bhi pending data **flush** karta hai aur offset arrow ko throw away kar deta hai. **Kyun karna zaruri hai.** Descriptors ek **limited** resource hain (dekho [[ulimit and resource limits]]). Ek long-running program jo `close` bhool jaata hai, woh dhire dhire tickets leak karta hai jab tak naya `open` `EMFILE` ke saath fail nahi ho jaata. (Note: `close` raw fd ke baare mein hai; [[Buffered I/O (fopen/fread)]] layer uske upar apna flush add karti hai.) **PICTURE.** Ticket `3` table mein wapas aata hai, kernel ka offset box gayab ho jaata hai, aur slot 3 ab agle `open` ke reuse ke liye free hai. --- ## Ek-picture summary Ek arrow, ek file, ek hi timeline par poori lifecycle: `open` use 0 par lagata hai, `read`/`write` use `n` se daayein slide karte hain, `lseek` use teleport karta hai (end ke baad sparse hole mein bhi), end par parked `read` `0` deta hai, aur `close` ticket retire karta hai. > [!recall]- Feynman retelling — simple words mein poora walkthrough > Ek file box 0 se shuru hoti numbered boxes ki ek row hai. Tum boxes ko khud touch nahi kar sakte, toh tum OS se poochte ho: file **open** karo. Woh tumhe ek numbered ticket deta hai aur secretly ek chhoti red arrow box 0 par lagata hai — woh arrow *offset* hai, matlab "woh box jo main aage touch karunga." **Read** arrow se boxes ko tumhari bag mein copy karta hai aur arrow ko daayein slide karta hai utna jitna usne actually diya (shayad jitna manga utna nahi). **Write** mirror image karta hai, tumhari bytes boxes mein dalta hai aur arrow usi tarah slide karta hai. Dono *usi* arrow ko share karte hain, toh woh aage badhte rehte hain. **Seek** pure teleportation hai — yeh bas arrow ko arithmetic se re-park karta hai ("shuru se," "yahan se," ya "end se"), zero bytes move karta hai, toh basically free hai. Agar arrow last box ke baad park karo aur write karo, toh gap invisible zeros se bhar jaata hai — sparse file. Agar arrow bilkul end par ho, toh read tumhe `0` deta hai, matlab "sab ho gaya," "toot gaya" nahi. Aakhir mein **close** ticket wapas karta hai taaki koi aur use kar sake. Tickets scarce hain; inhe wapas karna kabhi mat bhulo. > [!mnemonic] Arrow rule > **Har file call yahi hai — "arrow kahan hai, aur kya woh move karti hai?"** — `open` use lagata hai, `read`/`write` use `n` se nudge karte hain, `lseek` use teleport karta hai, `close` use throw away karta hai. --- ## Active recall Successful `read`/`write` ke baad offset kitna advance karta hai? ::: `n` se, actual bytes transferred (requested count nahi). 20-byte file par `lseek(fd, -4, SEEK_END)` calculate karo. ::: newpos = size + offset = 20 + (-4) = 16. Arrow offset = size par baitha hai aur tum `read` karte ho. Kya wapas aata hai? ::: `n = 0`, matlab EOF (clean end), koi error nahi. 20-byte file par 25 par `lseek` karo aur 1 byte write karo. Bytes 20–24 kya hain? ::: Implicit zero bytes ka ek hole — sparse file; naya EOF 26 hai. Kya `lseek` kernel boundary ke across koi bytes transfer karta hai? ::: Nahi — yeh sirf integer offset update karta hai; practically free. Robust `write` loop kyun karta hai? ::: Ek akela `write` `count` se kam store kar sakta hai (short write); loop karo, `total += n` advance karte hue, jab tak kaam na ho jaaye.