What is Partitioning Processes And Threads in Linux-Free Embedded Linux Course

Partitioning Processes And Threads
Design rules for splitting embedded Linux workloads between processes and threads — from EmbeddedPathashala’s free Linux device drivers course

Knowing the pthread API is only half the job. The harder question, especially in embedded Linux systems, is deciding which pieces of your system should be separate processes and which should just be threads inside one process. This lecture, part of EmbeddedPathashala’s free Linux kernel development course and free embedded systems course, walks through a practical set of design rules and a real-world case study — Android’s process model — to make that decision concrete.

Key Concepts Covered
process isolation fault containment UID/GID access control worker thread pools Android Binder

What You Will Learn

  • Six practical rules for splitting work between processes and threads
  • Why fault isolation is a process-level property, not a thread-level one
  • How Android’s process model applies these rules in production
  • A worked example structuring a small embedded system

Prerequisites

Before You Start

This lecture assumes you already understand how mutexes and condition variables work, covered in the previous lecture in this chapter, and that you know the basic difference between fork() and pthread_create().

Why the Split Matters

Threads and processes solve overlapping but distinct problems. Threads share an address space, which makes communication cheap but means one thread’s bug — a wild pointer write, a buffer overrun — can corrupt data belonging to every other thread in the process. Processes get a protected, private address space enforced by the MMU, so when a process crashes, the kernel reclaims its memory and file descriptors and every other process keeps running untouched. That protection is the single biggest reason to reach for a separate process instead of another thread, and it is the theme running through every rule below.

Six Rules for Partitioning a System

Rule 1 — Group Tightly Coupled Work Together

Threads that interact constantly and share data structures heavily belong in the same process. Putting them in separate processes would force every interaction through IPC, adding latency and serialization overhead for no real benefit.

Rule 2 — Isolate Loosely Coupled Components

Components that interact only occasionally are good candidates for separate processes. The isolation buys resilience: a crash in one component does not take down the other, and the two can be developed, updated, and restarted independently.

Rule 3 — Never Mix Critical and Non-Critical Work

This is an amplification of Rule 2. If part of your system is safety- or mission-critical — a motor control loop, a watchdog handler — keep it in its own process, written more conservatively than the rest, so it survives even if a less-critical process misbehaves or crashes. Real-time threads, by definition, should almost always get a dedicated process.

Rule 4 — Keep Threads Modular

Because threads share memory so easily, it is tempting to let their code and variables blur together. Resist it. Threads with well-defined interfaces between them are easier to reason about, test, and eventually split into separate processes if requirements change later.

Rule 5 — Threads Are Not Free

Every additional thread adds scheduling overhead and, more importantly, adds synchronization surface area — more shared state that needs a mutex, more places a race condition can hide. Creating a thread is cheap computationally; coordinating it correctly is the real cost.

Rule 6 — Use Threads for Real Parallelism

On a multi-core embedded SoC, one thread per core is a legitimate way to extract real throughput from CPU-bound work. Prefer an established library such as OpenMP or a thread-pool abstraction over hand-rolling your own parallel algorithm from scratch — the synchronization bugs in a hand-rolled parallel loop are rarely worth the saved dependency.

Case Study: Android’s Process Model

Android is one of the most widely deployed real-world applications of these rules. Every Android app runs as its own separate Linux process — the platform explicitly maps “application” to “process” rather than to “thread.” This gives Android two things at once: memory isolation, so one app’s memory corruption cannot touch another app’s data, and fault containment, so one app crashing does not bring down the device or other running apps.

The same process boundary doubles as an access-control boundary. Each app process runs under its own Linux UID and set of GIDs, and the kernel enforces file and resource permissions purely based on that UID/GID — the same mechanism any Linux system uses for user isolation, repurposed as an app sandbox.

Threads Inside a Typical Android App Process
Android App Process (own UID, own address space) | |– UI thread (handles user interface updates) |– Signal handler thread (handles OS-delivered signals) |– GC / heap threads (manage dynamic memory, free unused objects) |– Binder worker pool (>= 2 threads, receive IPC messages | from other app/system processes)

Inside that one process, Android still uses several threads, which is exactly Rule 1 in action: the UI update logic, signal handling, garbage collection bookkeeping, and a worker pool for receiving cross-process messages over the Binder protocol are all tightly coupled to that specific app’s runtime state, so they live together as threads rather than as separate processes. Cross-app communication, by contrast, goes over Binder IPC between processes — because different apps are exactly the loosely-coupled, fault-isolated components Rule 2 and Rule 3 describe.

Worked Example: Structuring a Small Embedded System

Consider a small embedded device running a sensor read loop, a network reporting client, and a local logging/diagnostics service. Applying the six rules gives a concrete structure:

ComponentProcess or Thread?Reasoning
Sensor sampling loopDedicated real-time processRule 3 — time-critical, must survive other components failing
Buffer manager thread inside sensor processThread, same process as sampling loopRule 1 — tightly coupled, shares the sample buffer directly
Network reporting clientSeparate processRule 2 — network stack faults or hangs should not affect sampling
Logging/diagnostics serviceSeparate processRule 2 and 3 — non-critical, isolated from the critical sampling path
$ ps -eLo pid,tid,comm,pri | grep ep_
  1042  1042 ep_sensor_proc    -5   <- real-time process
  1042  1043 ep_buffer_thread  -5   <- thread inside same process
  1088  1088 ep_net_proc       20   <- separate, normal-priority process
  1102  1102 ep_log_proc       20   <- separate, normal-priority process

Common Mistakes and Troubleshooting

Putting everything in one process “to keep it simple”: This is the most common mistake in small embedded projects. It feels simpler at first, but it means any single bug — even in the least-critical component — can take down the entire system, including the safety-critical parts.
Splitting into too many processes: The opposite mistake also happens. If two components interact on every iteration of a hot loop, forcing that interaction through IPC (pipes, sockets, shared memory) instead of a shared-memory thread call adds real, measurable latency for no isolation benefit, since the two components fail together anyway.

Best Practices

  • Draw the process/thread boundary along fault-tolerance lines first, and performance lines second.
  • Give real-time or safety-critical work its own process, always.
  • Reuse UID/GID-based permissions as a cheap access-control layer between your own processes, the same way Android does between apps.
  • Revisit the split as a system grows — a thread that started tightly coupled can outgrow that coupling and deserve its own process later.

Security Considerations

Process boundaries are also security boundaries on Linux. Running less-trusted or network-facing components (like a reporting client that parses external data) in their own process, under a restricted UID with minimal file permissions, limits the blast radius if that component is ever compromised — a compromised thread inside your main process has full access to everything else in that address space, while a compromised separate process is constrained by whatever the kernel’s UID/GID and capability rules allow it to touch.

Summary and Key Takeaways

  • Processes give memory protection and fault isolation; threads give cheap, fast sharing.
  • Group tightly coupled work as threads; isolate loosely coupled or critical work as separate processes.
  • Never mix critical and non-critical work in the same process.
  • Android’s one-process-per-app model is a real production example of these same rules applied at scale.
  • Revisit your process/thread split as the system’s requirements evolve.

Conclusion

Choosing between a thread and a process is a design decision, not a performance micro-optimization — it determines what happens to the rest of your system when one part of it fails. The six rules covered here, and the Android case study that puts them into practice, give you a repeatable way to make that call on any embedded Linux project. Together with the mutex and condition variable primitives from the previous lecture, you now have both the low-level tools and the high-level design judgment this chapter set out to cover as part of EmbeddedPathashala’s free Linux kernel development course.

Frequently Asked Questions

Why does Android run every app as a separate process instead of a thread?

To get memory isolation and fault containment: a bug or crash in one app’s process cannot corrupt another app’s memory or bring down the rest of the system, and the kernel can enforce per-app permissions using standard UID/GID access control.

How many threads does a typical Android app process use?

At minimum it includes a UI thread, a signal-handling thread, threads for memory management/garbage collection, and a worker pool of at least two threads for receiving Binder IPC messages from other processes.

Should real-time code ever share a process with non-real-time code?

No — Rule 3 is specifically about this. Real-time or safety-critical threads should go into their own process so they keep running correctly even if a non-critical process in the system fails or misbehaves.

Is it always faster to use threads instead of separate processes?

Not necessarily, and it is the wrong question to lead with. Threads reduce IPC overhead for tightly coupled work, but the more important factor is fault isolation — loosely coupled or critical components should be separate processes even when threads would be marginally faster.

What is Binder in the context of this lecture?

Binder is Android’s inter-process communication mechanism. It is the channel apps and system services use to talk to each other across the process boundaries this lecture recommends keeping in place, rather than merging everything into one process.

How do I decide the cutoff between “tightly coupled” and “loosely coupled” components?

A practical test: if two components need to exchange data on every iteration of a hot loop and sharing memory directly would meaningfully reduce latency, they are tightly coupled and belong together as threads. If they exchange data occasionally, in bursts, or only need to know about each other’s high-level state, they are loosely coupled and are good candidates for separate processes.

Continue the Free Linux Device Drivers Course

Explore more chapters of EmbeddedPathashala’s free Linux kernel and embedded systems curriculum, covering everything from bootloaders to device drivers.

Browse the Full Course Next Lecture

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *