ブロックを実現するには、まず、「準備ができている・できていない」をはっきりと評価する必要があります。
これはselectでも一緒です。
準備が出来ていない場合には休眠させ、待機させます。
それは
struct wait_queue *q;
interruptible_sleep_on(struct wait_queue ** q);
を使用します。
wait_queue は休眠しているプロセスの情報を起こすときに使えるようにするための変数で、プログラムをつくる場合には struct wait_queue * で変数を宣言し、そのポインタ (&q)を interruptible_sleep_on に渡します。
interruptible がない sleep_on もありますが、違いは signal で目覚めるかどうか、です。
なんらかの要因で、この休眠を解きたい場合には
wake_up_interruptible(struct wait_queue ** q);
を使います。wait_queue には sleep_on したのと同じ変数(のポインタ)を与えます。
このように、なにかを待つために sleep_on して、待っているものを起こすのに wake_up をつかう、その情報は wait_queue * で渡す、というのが、待機の基本です。
select, poll の場合は実際に休眠するかどうかは、カーネルのほうでやってくれます。
そのため、
void poll_wait(struct file * file, struct wait_queue ** wait, poll_table *p);
void select_wait(struct wait_queue ** wait, select_table * p);
を使い、もし、休眠が必要ない場合、 wait_queue * で用意した変数の後始末をしてくれます(結局sleepしなかったときに、間違ってwake_upしても問題にならないように処理してくれるようです)。
起こす点についてはブロックと同じで wake_up_interruptible()を使います。
select などの動作を見やすく/確認しやすくしようとしたら、プログラムがやたらと大きくなってしまいましたが、要は「寝せて」「起こす」です。