I think this might be a difference in design. I have one kernel stack per task. That way, I can leave one task sleeping while the IO manager fetches the data. When the IO manager is finished fetching the data, it just wakes the original thread. Which can then return to userspace with the data.
Code:
ssize_t read(int fd, void* __user buf, size_t len) {
if ((unsigned)fd >= current->process->fdlim || !current->process->files[fd])
return -EBADF;
[a few layers further down]
while (!(p = find_in_disk_buffer(disk, lba))) {
current->flags |= TIF_SLEEP_UNINT;
ioman_request_read(disk, lba, current);
schedule();
}
[and back up]
return copy_to_user(buf, p, len);
}
The schedule() function stops the calling thread until it is scheduled again. And the TIF_SLEEP_UNINT flag means the thread can never be scheduled again. Which is why the ioman has to know about the calling thread, so it can wake it (by removing that task flag; the scheduler takes care of the rest).