There are non-portable means in most implementations (via asm extensions). Obviously, this is more work and much less convenient than a volatile keyword that gives you the functionality.
Doesn’t this need volatile itself since the compiler can’t introspect what the assembly is doing?
Ehm, no, once you are in assembly, you do whatever you want to do. There is no compiler to mess things up for you. (Although, it's leaving that to you hehe... :-)
I think the compiler still needs to know that the value might be modified from the outside. Otherwise it might optimize the read operation away from the codepath.
Yes, that is part of creating inline assembly in C or C++ code: