I don't know if this helps, but there's a straightforward way to understand
the IO monad in terms of continuation passing.
You can think of a value of type IO a as being a CPS expression with a hole
in it; the hole is to be filled with a continuation which expects a value of
type a. The only way to fill the hole is by using >>=, whose second argument
is a continuation with another (nested) hole in it. So effectively with >>=
you build a CPS expression from the outside in. The final continuation,
which takes () and aborts the program, is ultimately filled in by the
runtime system.
This viewpoint doesn't work for other monads, since they always provide some
sort of destructuring operations on monadic values, e.g. runState or the
standard list deconstructors. But it works fine for IO provided you ignore
the existence of unsafePerformIO and friends.
-- Ben