well, you might have only queues that process can write to present in one process (plus queues it can read, but this time in read-only mode)... conceptually, that's still shared memory but a bit more "managed".
Also, you might insist on the fact that if N process access to the same destination, they'll each have
their own queue, so that they won't mess with other process'queues.
The "real" problem comes from synchronization: you say "i'll call kernel only if queue is empty" ... that's nice, but what tells you noone (on another CPU, or due to an interrupt) isn't filling your queue right at the moment you're about to call kernel to sleep ? and if that's the case, isn't there a risk that you never get noticed of the new item's presence ?
Esp. if your sender code looks like "on message_add, if queue_is_found_empty then wakeup_queue_owner", it may not notice the queue was empty, or send you a "wakeup" before you sleep