Erlang spreads processes across OS threads, like most other green-threading impls, and its not always great at it. (I don't know what you mean by "across OS processes." Processes on different Erlang VMs can communicate but the scheduler isn't going to move processes between them)
Calling into native code isn't an easy problem in Erlang either. NIF calls will block a scheduler thread, but the scheduler knows nothing about how long a NIF call is expected to take and will happily queue up processes to be run on a thread that is blocked inside a NIF call.
"Regular" erlang I/O is done by queueing up requests to be fulfilled by .. you guessed it, a pool of threads that spend most of their time sleeping in blocking i/o calls.
http://en.wikipedia.org/wiki/Green_threads#Green_threads_in_...
- you have to balance load across true OS threads to take advantage of multiple CPUs. (You often pin an OS thread per CPU.)
- if you make a call to a blocking OS function you have no way to pre-empt your lightweight thread.
HTH.
Update: "you have to balance load" --> I mean the green thread library implementer. Users of a lightweight threading library typically don't concern themselves with this, though they might if performance becomes an issue.