Normal Erlang code has a fixed number of reductions (function calls) before it must yield to a scheduler. Processes also have their own stacks and heaps and run garbage collection independently. The result is that no single process can stop the whole system by monopolizing CPU or managing shared memory.
The Erlang runtime can start a scheduler for every core on a machine and, since processes are independent, concurrency can be achieved by spawning additional processes. Processes communicate by passing messages which are copied from the sender into the mailbox of the receiver.
As an application programmer all of your code will run within a process and passively benefit from these properties. The tradeoff is that concurrency is on by default and single threaded performance can suffer. There are escape hatches to run native code, but it is more painful than writing concurrent code in a single-threaded by default language. The fundamental assumption of Erlang is that it is much more likely that you will need concurrency and fault tolerance than maximum single thread performance.