<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dhananjay Jadhav</title>
    <description>The latest articles on DEV Community by Dhananjay Jadhav (@dhananjay_j).</description>
    <link>https://dev.to/dhananjay_j</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3994302%2F5709de15-dcfe-4637-a176-0ab6bb7db92f.png</url>
      <title>DEV Community: Dhananjay Jadhav</title>
      <link>https://dev.to/dhananjay_j</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dhananjay_j"/>
    <language>en</language>
    <item>
      <title>Deconstructing the ecall: From C User-Space to RISC-V Bare Metal</title>
      <dc:creator>Dhananjay Jadhav</dc:creator>
      <pubDate>Sat, 11 Jul 2026 21:30:40 +0000</pubDate>
      <link>https://dev.to/dhananjay_j/deconstructing-the-ecall-from-c-user-space-to-risc-v-bare-metal-l0a</link>
      <guid>https://dev.to/dhananjay_j/deconstructing-the-ecall-from-c-user-space-to-risc-v-bare-metal-l0a</guid>
      <description>&lt;p&gt;For most application developers, system call are just the necessary black-box API boundary. You invoke the read(), write(), open() etc.. accept the context switching latency and trust the operating system to handle the hardware. But when you are the building and designing the systems where every nanosecond overhead matters like the High Frequency Trading engine, optimizing the low level device drivers or saturating AI hardware accelerators with zero-copy data streams treating the API of OS as a boundaries can't afford the developers and the companies.&lt;/p&gt;

&lt;p&gt;For writing performance first software you have to understand what happens to the CPU pipeline when user-space execution halts and then only you can optimize it and maximize the performance. To understand the insides of the operating system, how data is transfer from user-space to the kernel-space and how handle that data on minimal resources without compromising the performance I am using the OSTEP book and the MIT 6.1810 xv6 course labs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crossing the Boundary: The Anatomy of the RISC-V Trap
&lt;/h2&gt;

&lt;p&gt;To understand the cost of context switch, we have to start at the exact moment the illusion of the operating system breaks. When developer write read(fd, buffer, size) in C, compiler doesn't link this to the standard function read in kernel. User-Space code cannot go directly into the kernel memory, doing so would trigger the fatal CPU exception due to hardware privilege restriction. Instead that C function is a hollow shell - an assembly stub.&lt;/p&gt;

&lt;p&gt;In a RISC-V architecture (backbone of the xv6) this stub have one job prepare the hardware for trap. It does this by packing the argument into the specific CPU registers (typically a0 to a5) and loading unique system call number into the a7 register. This system call index is critical number that kernel will use later to route the request. Once the registers are loaded the stub execute the ecall instruction, this is where the magic and performance overhead happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the ecall ? what does it?
&lt;/h2&gt;

&lt;p&gt;ecall is not a standard jump or branch instruction; it is hardware-enforced context switching. The moment silicon reads the ecall, the CPU forcefully suspend user space execution. It elevates the processor's privilege level from user mode to Supervisor mode(kernel mode), save the current program counter so remember where to return later, and blindly jump into the kernel space memory defined at the time of boot by kernel. At this exact nanosecond user-space lost all the control, the kernel is now awake, but it is operating in highly volatile state. It has to safely save user program registers without overwriting them validate the memory pointer passed in and execute the actual logic before handover control back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Waking the kernel: The Dispatcher and Trapframe
&lt;/h2&gt;

&lt;p&gt;Once the ecall instruction fires and hardware drops into the Supervisor mode, the kernel wakes up in a specialized handler routine (in xv6 it wakes up in trampoline.S and moves to the usertrap()). But kernel doesn't instantly know what the user wants it only knows that trap occurred.&lt;/p&gt;

&lt;p&gt;To figure out why it was occurred the kernel look at the preserved snapshot of CPU's state called the trap frame. Inside the trapframe the kernel check the a7 register this register hold the unique syscall number. The dispatcher (in xv6 inside of the syscall.c) take this ID (unique syscall number) and use it as an index into an array of function pointer. Example: If a7 holds the 22 (ID we might assign to the trace() or the sysinfo or any syscall), the dispatcher routes the execution to the corresponding kernel function like sys_trace() or sys_sysinfo().&lt;/p&gt;

&lt;h2&gt;
  
  
  The Danger of User pointers
&lt;/h2&gt;

&lt;p&gt;Routing the syscall is just half of battle. The real engineering challenge is handling handle arguments safely.&lt;/p&gt;

&lt;p&gt;When building observability tool like system call to extract number of running process or the bytes of free memory you have to pass data back to the user-space. But the kernel cannot blindly trust pointer passed from the user-space in register a0 through a5. If malicious or buggy program pass the pointer into the protected kernel address, and the kernel blindly writes to it, the entire system is compromised.&lt;/p&gt;

&lt;p&gt;This is why the kernel use the specialized helper function (like argint(), argaddr(), and copyout() ). Before a custom sysinfo() call can populate a struct with memory statistics and send it back, the kernel must manually translate the users virtual address into a physical address verify the user has write permissions for that specific page and safely copy the bytes across the boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Giving the each process a memory
&lt;/h2&gt;

&lt;p&gt;Routing a syscalls and safely reading the arguments from user solves one problem, but system call like trace introduce new requirement that neither of two step covers that is it has to be remembered.&lt;/p&gt;

&lt;p&gt;When a user call trace(mask), it is not asking kernel to execute the one of task, It is instructing the kernel to change how it behaves for every subsequent syscall that process makes for the rest of its life. That means the trace mask cannot live in a local variable inside sys_trace(), it would vanish the moment the function returns. It has to live somewhere that persists for the process itself.&lt;/p&gt;

&lt;p&gt;In xv6 that place is struct proc, defined in kernel/proc.h. This is the kernel core bookkeeping structure. There is one per process holding everything the kernel needs to know about it:- its page table, its execution state (running, sleeping, zombie, runnable etc..), its PID, and its open files. Adding trace support means adding one more field an integer like tracemask so every process now carries its own independent trace state.&lt;/p&gt;

&lt;p&gt;But adding the field to the struct is not enough on its own. The struct proc slots in xv6 are allocated from a fixed-size array, reused constantly as old processes die and new ones are created. A slot that held a previous process's data does not get wiped clean by default, it just sits there with whatever volatile values the last occupant left behind. If you do not explicitly initialize the tracemask to 0 for every new process, a freshly created process could inherit a stale, garbage trace mask from a completely unrelated program that used to occupy that memory address.&lt;/p&gt;

&lt;p&gt;To fix fix this in allocproc() inside kernel/proc.c the function that runs every single time the kernel needs a fresh process slot whether during boot or from a fork(). Right alongside where xv6 already resets the PID, state, and zeroes out the trapframe, you must explicitly clear the tracemask. It is a one line addition in code, but it is the difference between a syscall that works deterministically and one that occasionally, mysteriously misbehaves due to memory staleness the absolute worst kind of bug to chase in systems programming (I think this is the worst kind of bug in system programming).&lt;/p&gt;

&lt;p&gt;There is one more detail worth calling out because it is easy to miss, when a process calls fork() should the child inherit the parent's trace mask? xv6 fork() implementation copies most of the parent's struct proc state into the child. Deciding whether the tracemask should be part of that copy is a deliberate engineering design decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeing It Work
&lt;/h2&gt;

&lt;p&gt;All of this plumbing the hardware enforced trap, the dispatcher, the trapframe, the argument safety checks, and the per-process mask exists to support one visible behavior.&lt;/p&gt;

&lt;p&gt;Once the tracemask is set, the xv6 syscall() dispatcher checks it on every subsequent trap that process triggers. After the target function runs, if the corresponding bit is set in the mask, the kernel intercepts the flow to print a line identifying which syscall just ran, for which process, and what it returned.&lt;/p&gt;

&lt;p&gt;Running the grader against my implementation confirms all tests pass:-&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9jkalx5smpvmlq52klmy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9jkalx5smpvmlq52klmy.png" alt=" " width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Every one of these steps the hardware-enforced trap, the trapframe save, the dispatcher lookup, the argument safety checks, the per-process state happens on every single syscall a program makes. read(), write(), open()  all of it. For most software, this cost is invisible, amortized across enough useful work that nobody notices.&lt;/p&gt;

&lt;p&gt;But at the scale HFT trading engines, custom device drivers or AI accelerator pipelines operate at where a single function might be called millions of times a second, and every nanosecond of latency compounds this is no longer a cost you can afford to treat as a black box. It's exactly why technologies like io_uring and kernel-bypass networking exist, not as exotic tricks, but as direct engineering responses to the real, measurable cost of the boundary this article just walked through. You can't optimize a cost you don't understand the shape of.&lt;/p&gt;

&lt;p&gt;That is the real reason I am working through OSTEP and xv6 labs instead of only reading about operating systems implementing trace and sysinfo by hand forced me to see this boundary as a sequence of concrete, costly steps, not an abstraction. Lab 3 (page tables) is next, and if virtual memory works anything like this lab did, there's a lot more to learn about exactly where performance goes when software crosses from user space into the kernel.&lt;/p&gt;

</description>
      <category>kernel</category>
      <category>systemprogramming</category>
      <category>linux</category>
      <category>c</category>
    </item>
    <item>
      <title>Why Your Pipe Code Deadlocks - A Lesson from MIT xv6</title>
      <dc:creator>Dhananjay Jadhav</dc:creator>
      <pubDate>Fri, 26 Jun 2026 20:19:30 +0000</pubDate>
      <link>https://dev.to/dhananjay_j/why-your-pipe-code-deadlocks-a-lesson-from-mit-xv6-33pa</link>
      <guid>https://dev.to/dhananjay_j/why-your-pipe-code-deadlocks-a-lesson-from-mit-xv6-33pa</guid>
      <description>&lt;h2&gt;
  
  
  Introduction:
&lt;/h2&gt;

&lt;p&gt;I was working on MIT's xv6 OS lab - the pingpong program. The idea was simple: parent sends a byte to child through a pipe, child reads it and prints "received ping", sends a byte back, parent reads it and prints "received pong".&lt;/p&gt;

&lt;p&gt;I wrote the code. Ran it manually inside QEMU. Worked perfectly.&lt;br&gt;
Then I ran the automated test. It just hung. No output. No error.Just silence until timeout.&lt;br&gt;
&amp;nbsp;&lt;br&gt;
Two hours later I found the problem. It was two lines.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is a Pipe:
&lt;/h2&gt;

&lt;p&gt;A pipe is a one way communication channel between two processes. In Unix every pipe has two ends:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// p[0] → read end (you read from here)&lt;/span&gt;
&lt;span class="c1"&gt;// p[1] → write end (you write here)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parent writes into p[1], Child reads from p[0]. Simple.&lt;/p&gt;

&lt;p&gt;For two way communication like pingpong you need two pipes, one for each direction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// parent → child&lt;/span&gt;
 &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// child → parent&lt;/span&gt;
 &lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What is a Deadlock:
&lt;/h2&gt;

&lt;p&gt;A deadlock is when two processes are both waiting for each other and neither can move forward.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqhysrw0st6e1u7m3qlbv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqhysrw0st6e1u7m3qlbv.png" alt="Automated test timing out" width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Think of it like two people standing in a narrow corridor. Each one is waiting for the other to move first. Nobody moves. Forever.&lt;/p&gt;

&lt;p&gt;In pipes it happens when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Process A is waiting for Process B to finish&lt;/li&gt;
&lt;li&gt;Process B is waiting for Process A to send data&lt;/li&gt;
&lt;li&gt;Neither moves&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My Mistake - wait() in the Wrong Place:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6t3svrzu333hxayg7dg3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6t3svrzu333hxayg7dg3.png" alt="Wrong code in Neovim" width="800" height="957"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was parent code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ← waiting for child to finish FIRST&lt;/span&gt;
 &lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And my child code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
 &lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ← waiting for parent to send data&lt;/span&gt;
 &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the problem?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parent calls wait(0) - it stops and waits for child to exit&lt;/li&gt;
&lt;li&gt;Child calls read(p[0]) - it stops and waits for parent to send data&lt;/li&gt;
&lt;li&gt;Parent is waiting for child. Child is waiting for parent.&lt;/li&gt;
&lt;li&gt;Nobody moves. Forever.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a deadlock. The process just hangs silently until the test runner kills it with a timeout.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Second Mistake - Not Closing Pipe Ends:
&lt;/h2&gt;

&lt;p&gt;Every process gets both ends of the pipe after fork(). If you don't close the ends you don't use, read() never sees EOF and hangs waiting for more data even after the writer is done.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Wrong - child keeps both ends open&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
 &lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Correct - child closes the end it doesn't use&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
 &lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// child doesn't write to p&lt;/span&gt;
 &lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rule: every process must close every pipe end it doesn't use. Always.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix:
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
 &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
 &lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// parent writes, child reads&lt;/span&gt;
 &lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// child writes, parent readsint id = fork();&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
   &lt;span class="c1"&gt;// child&lt;/span&gt;
   &lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// child doesn't write to p&lt;/span&gt;
   &lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// child doesn't read from p1char c;&lt;/span&gt;
   &lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;           &lt;span class="c1"&gt;// receive from parent&lt;/span&gt;
   &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%d: received ping&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;getpid&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
   &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;         &lt;span class="c1"&gt;// send back to parentclose(p[0]);&lt;/span&gt;
   &lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
   &lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="c1"&gt;// parent&lt;/span&gt;
   &lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// parent doesn't read from p&lt;/span&gt;
   &lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// parent doesn't write to p1char c = 'x';&lt;/span&gt;
   &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// send to child&lt;/span&gt;
   &lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// receive from child&lt;/span&gt;
   &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%d: received pong&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;getpid&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
   &lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
   &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// wait AFTER all pipe I/O is done&lt;/span&gt;
   &lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb7hpxoxhfuqwcnnsus0u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb7hpxoxhfuqwcnnsus0u.png" alt="All tests passing" width="785" height="82"&gt;&lt;/a&gt;&lt;br&gt;
Two things changed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wait(0) moved to after all pipe communication is done&lt;/li&gt;
&lt;li&gt;Every unused pipe end is closed immediately after fork&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Rules That Stuck With Me:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Always close pipe ends you don't use → otherwise read() hangs waiting for EOF forever&lt;/li&gt;
&lt;li&gt;wait() always comes after pipe I/O, never before → otherwise parent and child wait for each other forever&lt;/li&gt;
&lt;li&gt;pipe[0] is always read end, pipe[1] is always write end → never mix these up&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What I'm Building Next:&lt;/strong&gt;&lt;br&gt;
This was Lab 1 of MIT 6.1810 - the xv6 RISC-V operating systems course. I'm working through all the labs on Arch Linux with a RISC-V cross compiler and QEMU.&lt;/p&gt;

&lt;p&gt;Next up is Lab 2 - system calls. I'll be adding new syscalls directly into the xv6 kernel.&lt;/p&gt;

&lt;p&gt;All my code is on &lt;strong&gt;GitHub: github.com/Dj-pages/xv6-labs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're learning systems programming, MIT xv6 is the best hands-on resource out there. Everything breaks, and that's exactly the point.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>c</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
