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.
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.
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:
| Component | Process or Thread? | Reasoning |
|---|---|---|
| Sensor sampling loop | Dedicated real-time process | Rule 3 — time-critical, must survive other components failing |
| Buffer manager thread inside sensor process | Thread, same process as sampling loop | Rule 1 — tightly coupled, shares the sample buffer directly |
| Network reporting client | Separate process | Rule 2 — network stack faults or hangs should not affect sampling |
| Logging/diagnostics service | Separate process | Rule 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
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