<?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: Bogdan</title>
    <description>The latest articles on DEV Community by Bogdan (@bogdan_75c1dac33c215a1ba6).</description>
    <link>https://dev.to/bogdan_75c1dac33c215a1ba6</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1960390%2F7a9cf6ae-e185-421e-83dd-d4768d9854d5.png</url>
      <title>DEV Community: Bogdan</title>
      <link>https://dev.to/bogdan_75c1dac33c215a1ba6</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bogdan_75c1dac33c215a1ba6"/>
    <language>en</language>
    <item>
      <title>Writing a tiny PID 1 for containers in pure assembly (x86-64 + ARM64)</title>
      <dc:creator>Bogdan</dc:creator>
      <pubDate>Sat, 06 Dec 2025 18:43:18 +0000</pubDate>
      <link>https://dev.to/bogdan_75c1dac33c215a1ba6/writing-a-tiny-pid-1-for-containers-in-pure-assembly-x86-64-arm64-2h2i</link>
      <guid>https://dev.to/bogdan_75c1dac33c215a1ba6/writing-a-tiny-pid-1-for-containers-in-pure-assembly-x86-64-arm64-2h2i</guid>
      <description>&lt;h3&gt;
  
  
  Most of us don't think much about &lt;code&gt;PID 1&lt;/code&gt; when building Docker images. We just slap a &lt;code&gt;CMD&lt;/code&gt; on the Dockerfile, run the container, and move on
&lt;/h3&gt;

&lt;p&gt;Until one day:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docker stop&lt;/code&gt; hangs forever,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ctrl+c&lt;/code&gt; doesn't terminate your container,&lt;/li&gt;
&lt;li&gt;or you discover a pile of zombie processes inside.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these symptoms point to the same root cause: &lt;strong&gt;your application is running as PID 1 and doesn't behave like an init process.&lt;/strong&gt; In Linux, PID 1 has special semantics around signal handling and zombie reaping, and normal apps rarely implement those correctly.&lt;/p&gt;

&lt;p&gt;Tools like &lt;a href="https://github.com/krallin/tini" rel="noopener noreferrer"&gt;Tini&lt;/a&gt; solved this brilliantly: a tiny process that runs as PID 1, forwards signals to your app, and reaps zombies. Docker even ships with Tini built in via &lt;code&gt;--init&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this post, I'll walk through an alternative implementation: &lt;strong&gt;&lt;code&gt;mini-init-asm&lt;/code&gt;&lt;/strong&gt;, a small PID 1 designed for containers, written entirely in &lt;strong&gt;x86-64 NASM&lt;/strong&gt; and &lt;strong&gt;ARM64 GAS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It's not meant to replace Tini everywhere. Instead, it's:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PGID-first init&lt;/strong&gt; for containers (always uses a separate session and process group),&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pure-assembly implementation&lt;/strong&gt; of the same core ideas,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;a few extra tricks&lt;/strong&gt; like &lt;strong&gt;restart-on-crash&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Design goals
&lt;/h2&gt;

&lt;p&gt;Before writing a single line of assembly, I set a few constraints:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Behave like a responsible PID 1.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Forward termination signals to the &lt;em&gt;whole process group&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Reap zombies, including grandchildren if needed (subreaper mode).&lt;/li&gt;
&lt;li&gt;Exit with a meaningful status (child exit code or &lt;code&gt;128+signal&lt;/code&gt; style).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Be small and auditable.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No libc, no runtime, no hidden magic.&lt;/li&gt;
&lt;li&gt;A single statically-linked binary per architecture.&lt;/li&gt;
&lt;li&gt;Clear, reviewable control flow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Be container-friendly.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy to drop into &lt;code&gt;FROM scratch&lt;/code&gt; images.&lt;/li&gt;
&lt;li&gt;Explicit support for graceful shutdown (grace period + &lt;code&gt;SIGKILL&lt;/code&gt; escalation).&lt;/li&gt;
&lt;li&gt;Optional restart logic, but not a full-blown process manager.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Support amd64 and arm64 from day one.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;x86-64 NASM for the "normal" Docker host.&lt;/li&gt;
&lt;li&gt;ARM64 GAS for modern ARM servers and SBCs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The container PID 1 problem in one picture
&lt;/h2&gt;

&lt;p&gt;When your app runs directly as PID 1, everything inside the container hangs off it:&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.amazonaws.com%2Fuploads%2Farticles%2Fuiq8ogmjozothbroq5tw.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.amazonaws.com%2Fuploads%2Farticles%2Fuiq8ogmjozothbroq5tw.png" alt="Container PID 1 problem" width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;your-app&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ignores &lt;code&gt;SIGTERM&lt;/code&gt;, &lt;code&gt;SIGINT&lt;/code&gt;, etc., &lt;strong&gt;&lt;code&gt;docker stop&lt;/code&gt; won't work properly&lt;/strong&gt;, and k8s will eventually send &lt;code&gt;SIGKILL&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;never calls &lt;code&gt;wait()&lt;/code&gt; / &lt;code&gt;waitpid()&lt;/code&gt;, then exited children become &lt;strong&gt;zombies&lt;/strong&gt; until PID 1 cleans them up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An init like Tini or &lt;code&gt;mini-init-asm&lt;/code&gt; inserts itself as PID 1 and makes your app "just another process" with a normal parent:&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.amazonaws.com%2Fuploads%2Farticles%2Fsdh52j4h87aq7u19ulca.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.amazonaws.com%2Fuploads%2Farticles%2Fsdh52j4h87aq7u19ulca.png" alt="An init like Tini or  raw `mini-init-asm` endraw  inserts itself as PID 1 and makes your app " width="379" height="692"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PID 1 now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;forwards signals to a &lt;strong&gt;process group&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;reaps zombies,&lt;/li&gt;
&lt;li&gt;decides when to exit and with what status.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  High-level architecture of mini-init-asm
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;mini-init-asm&lt;/code&gt; follows a PGID-centric design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Block signals&lt;/strong&gt; in PID 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spawn a child&lt;/strong&gt; under a new session + process group (PGID = child PID).&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;signalfd&lt;/code&gt; listening to &lt;code&gt;HUP, INT, QUIT, TERM, CHLD&lt;/code&gt; plus optional extra signals;&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;timerfd&lt;/code&gt; for the graceful shutdown window;&lt;/li&gt;
&lt;li&gt;an &lt;code&gt;epoll&lt;/code&gt; instance watching both fds.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Run an &lt;strong&gt;event loop&lt;/strong&gt; on &lt;code&gt;epoll_wait&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;on &lt;strong&gt;soft signals&lt;/strong&gt; (&lt;code&gt;TERM/INT/HUP/QUIT&lt;/code&gt;): forward to the whole process group and start the grace timer;&lt;/li&gt;
&lt;li&gt;on &lt;strong&gt;&lt;code&gt;SIGCHLD&lt;/code&gt;&lt;/strong&gt;: reap children with &lt;code&gt;waitpid(-1, WNOHANG)&lt;/code&gt; and track the main child;&lt;/li&gt;
&lt;li&gt;on &lt;strong&gt;timer expiry&lt;/strong&gt;: if the child is still alive, send &lt;code&gt;SIGKILL&lt;/code&gt; to the process group.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;On exit, &lt;code&gt;mini-init-asm&lt;/code&gt; returns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the child's exit status (normal exit), or&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BASE + signal_number&lt;/code&gt; if the child died by a signal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The base is customizable via &lt;code&gt;EP_EXIT_CODE_BASE&lt;/code&gt;, defaulting to &lt;strong&gt;128&lt;/strong&gt; (POSIX shell convention).&lt;/p&gt;

&lt;h2&gt;
  
  
  Sequence: from &lt;code&gt;docker run&lt;/code&gt; to graceful shutdown
&lt;/h2&gt;

&lt;p&gt;Here's the "happy path" when running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mini-init-amd64 &lt;span class="nt"&gt;--&lt;/span&gt; ./your-app &lt;span class="nt"&gt;--flag&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;from &lt;code&gt;docker run&lt;/code&gt; to graceful shutdown:&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.amazonaws.com%2Fuploads%2Farticles%2Fkbzfu2ro1c2oyi7nyr9h.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.amazonaws.com%2Fuploads%2Farticles%2Fkbzfu2ro1c2oyi7nyr9h.png" alt="from  raw `docker run` endraw  to graceful shutdown" width="800" height="795"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the child &lt;strong&gt;ignores&lt;/strong&gt; &lt;code&gt;SIGTERM&lt;/code&gt; and is still alive when the timer expires, &lt;code&gt;mini-init-asm&lt;/code&gt; escalates:&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.amazonaws.com%2Fuploads%2Farticles%2Fejpgqo2w3g8ul6w76gax.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.amazonaws.com%2Fuploads%2Farticles%2Fejpgqo2w3g8ul6w76gax.png" alt="If the child **ignores**  raw `SIGTERM` endraw  and is still alive when the timer expires,  raw `mini-init-asm` endraw  escalates" width="800" height="705"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pure-assembly implementation: structure
&lt;/h2&gt;

&lt;p&gt;The repo is organized to keep the assembly readable and reviewable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/amd64/&lt;/code&gt; - NASM sources (SysV ABI, x86-64).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/arm64/&lt;/code&gt; - GAS sources (AArch64).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;include/syscalls_*.inc&lt;/code&gt; - syscall numbers per arch.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;include/macros*.inc&lt;/code&gt; - small helpers for syscalls / logging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A typical syscall wrapper in NASM looks like (simplified):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;; rax = syscall number
; rdi, rsi, rdx, r10, r8, r9 = args

%macro SYSCALL 0
syscall
cmp rax, 0
jge .ok
; handle -errno in rax if needed...
.ok:
%endmacro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spawning the child in PGID mode is essentially:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;; 1) Fork/clone a child
mov eax, SYS_clone
mov rdi, SIGCHLD ; flags
xor rsi, rsi ; child_stack (unused for simple clone)
xor rdx, rdx ; ...
xor r10, r10
xor r8, r8
xor r9, r9
syscall

cmp rax, 0
je .in_child
jl .fork_error

; ----- Parent (PID 1) -----
; rax = child_pid
mov [child_pid], rax
; continue with signalfd/epoll setup...
jmp .parent_after_fork

.in_child:
; 2) Create new session and PGID
mov eax, SYS_setsid
syscall

; Optionally setpgid(0, 0)
xor rdi, rdi
xor rsi, rsi
mov eax, SYS_setpgid
syscall

; 3) execve() target program
; (argv/envp prepared before jump to child)
mov eax, SYS_execve
mov rdi, [target_path]
mov rsi, [target_argv]
mov rdx, [target_envp]
syscall

; If execve returns, it's an error -&amp;gt; exit(127)
mov edi, 127
mov eax, SYS_exit
syscall
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the ARM64 side, the logic is analogous, just with different calling conventions (&lt;code&gt;x8&lt;/code&gt; for syscall number, &lt;code&gt;x0-x5&lt;/code&gt; for arguments).&lt;/p&gt;

&lt;h2&gt;
  
  
  The epoll + signalfd + timerfd loop
&lt;/h2&gt;

&lt;p&gt;The main event loop is where most of the logic lives. In pseudo-C, the gist is:&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;for&lt;/span&gt; &lt;span class="p"&gt;(;;)&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;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;epoll_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MAX_EVENTS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&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;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;errno&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;EINTR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;for&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;i&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;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&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;events&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;signalfd_fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;signalfd_siginfo&lt;/span&gt; &lt;span class="n"&gt;si&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;signalfd_fd&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;si&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;si&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;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssi_signo&lt;/span&gt;&lt;span class="p"&gt;;&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;is_soft_shutdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;forward_to_pgid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;grace_timer_armed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;arm_timerfd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grace_seconds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;grace_timer_armed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&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;span class="k"&gt;else&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;sig&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;SIGCHLD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;reap_children&lt;/span&gt;&lt;span class="p"&gt;();&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;main_child_exited&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;exit_with_child_status&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;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;forward_to_pgid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sig&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;span class="k"&gt;else&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;events&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;timerfd_fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kt"&gt;uint64_t&lt;/span&gt; &lt;span class="n"&gt;expirations&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;timerfd_fd&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;expirations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expirations&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;main_child_exited&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;kill_process_group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SIGKILL&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;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;The actual code is assembly, but the state machine is very close to this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration knobs
&lt;/h2&gt;

&lt;p&gt;To keep the CLI surface minimal, &lt;code&gt;mini-init-asm&lt;/code&gt; pushes configuration into environment variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Shutdown behavior&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;EP_GRACE_SECONDS&lt;/code&gt; - window between first soft signal and &lt;code&gt;SIGKILL&lt;/code&gt; (default &lt;code&gt;10&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EP_EXIT_CODE_BASE&lt;/code&gt; - base for "killed-by-signal" exit codes (default &lt;code&gt;128&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Signal fan-out&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;EP_SIGNALS&lt;/code&gt; - CSV of extra signals to monitor and forward (e.g. &lt;code&gt;USR1,RT1,RT5&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Reaping and supervision&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;EP_SUBREAPER=1&lt;/code&gt; - enable &lt;code&gt;PR_SET_CHILD_SUBREAPER&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EP_RESTART_ENABLED=1&lt;/code&gt; - restart-on-crash mode.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EP_MAX_RESTARTS&lt;/code&gt; - max restarts (0 = unlimited).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EP_RESTART_BACKOFF_SECONDS&lt;/code&gt; - backoff between restarts.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;On top of that, there are only two CLI flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-v&lt;/code&gt; / &lt;code&gt;--verbose&lt;/code&gt; - log more details.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-V&lt;/code&gt; / &lt;code&gt;--version&lt;/code&gt; - print version.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Compared to Tini
&lt;/h2&gt;

&lt;p&gt;If you're already using Tini, the behavior will feel familiar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;signal forwarding&lt;/strong&gt; and &lt;strong&gt;zombie reaping&lt;/strong&gt; are table stakes for both;&lt;/li&gt;
&lt;li&gt;both can operate in &lt;strong&gt;subreaper&lt;/strong&gt; mode when not PID 1;&lt;/li&gt;
&lt;li&gt;both try to be &lt;strong&gt;transparent&lt;/strong&gt;: your app still sees the usual signals and exit codes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where &lt;code&gt;mini-init-asm&lt;/code&gt; differs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's &lt;strong&gt;PGID-mode by design&lt;/strong&gt; - the target process always runs in a separate session and process group, and signals are sent to the &lt;em&gt;group&lt;/em&gt;, not just a single child.&lt;/li&gt;
&lt;li&gt;The implementation is &lt;strong&gt;pure assembly&lt;/strong&gt; with direct syscalls, so there's no libc and very little hidden behavior.&lt;/li&gt;
&lt;li&gt;It offers a &lt;strong&gt;very small restart-on-crash loop&lt;/strong&gt; controlled by env vars (&lt;code&gt;EP_RESTART_*&lt;/code&gt;), which Tini intentionally doesn't provide.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I still recommend Tini (or Docker's &lt;code&gt;--init&lt;/code&gt;) as the default choice for most workloads. It's widely deployed, packaged, and battle-tested. &lt;code&gt;mini-init-asm&lt;/code&gt; is for people who:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enjoy low-level Linux,&lt;/li&gt;
&lt;li&gt;want a tiny, auditable PID 1 in scratch images,&lt;/li&gt;
&lt;li&gt;or just like reading and hacking assembly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Trying it out in Docker
&lt;/h2&gt;

&lt;p&gt;Here's a minimal Dockerfile example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;debian:stable-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;nasm make binutils ca-certificates &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;make

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; scratch&lt;/span&gt;

&lt;span class="c"&gt;# Copy the tiny init&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /src/build/mini-init-amd64 /mini-init&lt;/span&gt;

&lt;span class="c"&gt;# Copy your app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; your-app /your-app&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/mini-init", "--"]&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["/your-app"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; mini-init-asm-demo &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; mini-init-asm-demo
&lt;span class="c"&gt;# From another shell:&lt;/span&gt;
docker stop &amp;lt;container-id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see your app receive &lt;code&gt;SIGTERM&lt;/code&gt;, run its own shutdown handlers, and exit cleanly,&lt;br&gt;
while &lt;code&gt;mini-init-asm&lt;/code&gt; reaps whatever processes it spawned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status and roadmap
&lt;/h2&gt;

&lt;p&gt;Right now, &lt;code&gt;mini-init-asm&lt;/code&gt; is meant for experimentation and early adopters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The core PGID + signal + timer logic is covered by a test suite (TERM fan-out, escalation, &lt;code&gt;EP_SIGNALS&lt;/code&gt;, exit-code mapping, restart behavior, subreaper edge cases).&lt;/li&gt;
&lt;li&gt;ARM64 is supported and tested via QEMU, but native ARM64 testing will always be more deterministic.&lt;/li&gt;
&lt;li&gt;The focus is on &lt;strong&gt;keeping the code small and understandable&lt;/strong&gt;, not on adding every feature a process supervisor could have.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Future areas I'm interested in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;more real-world testing under high load and high churn scenarios,&lt;/li&gt;
&lt;li&gt;more architectures (e.g. riscv64) if there's interest,&lt;/li&gt;
&lt;li&gt;tooling to visualize and debug the event loop (e.g. trace logging helpers),&lt;/li&gt;
&lt;li&gt;documentation around secure integration with seccomp/cgroups/AppArmor.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;If you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;care about how PID 1 actually works in containers,&lt;/li&gt;
&lt;li&gt;want a tiny init written in assembly you can fully understand and audit,&lt;/li&gt;
&lt;li&gt;or just enjoy reading low-level code,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then &lt;code&gt;mini-init-asm&lt;/code&gt; might be a fun project to explore or contribute to.&lt;/p&gt;

&lt;p&gt;You can find the code, tests, and Docker examples here: &lt;a href="https://github.com/roots666/mini-init-asm" rel="noopener noreferrer"&gt;&lt;strong&gt;GitHub - mini-init-asm&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback, issues, and PRs are very welcome - especially around tricky signal / reaping edge cases you've seen in production.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>linux</category>
      <category>assembly</category>
      <category>containers</category>
    </item>
  </channel>
</rss>
