[轉]基礎 Linux Device Driver 驅動程式#10 (select/poll)

備份好文章
http://csw-dawn.blogspot.tw/2012/01/linux-device-driver-10-selectpoll.html


基礎 Linux Device Driver 驅動程式#10 (select/poll)

相信各位都有在Linux上寫程式的經驗,
當您程式裡呼叫 open 時,Linux 預設會以 blocking mode的方式開啟,
block 是指當 process 為了等待某件事的發生,而進入 sleep 狀態的情形。

像 read 就是其中一種,當沒資料讀取時,process 就會被 block。
write 也是一樣,在寫入資料時,寫入對象還無法處理資料時,一樣會 block。

對某些程式來說,如果 read 系統呼叫被 block 的話,有時就會有設計上的問題,
所以為了避免這種問題發生,Linux 就準備了以下方式。

1. Non-blocking 模式

啟用 non-blocking 模式後,不管是 read 還是 write 就不會被 block住。但會傳回 errno 錯誤碼,
這時就必須自已再做讀寫的動作。
想使用 non-blocking 模式的話,可在 open() 開檔時指定 O_NONBLOCK。

2. 同時執行多個同步 I/O 工作

同時執行多個同步 I/O 工作 指的是使用 select 系統呼叫的做法。
select 系統呼叫本身會被 block, 但可指定 timeout。

使用 select() 的時候,有幾個常用的函式巨集,可參考 man 2 select。

還有一個和 select()很類似的 poll()系統呼叫,以基本功能來說,select()與poll()都一樣,
但指定的 file handler的方式不一樣,且指定多個 file handler 的時候,poll()走訪所有
file handler的速度比較快。 可參考 man 2 poll。

應用程式想同時執行多個同步 I/O 工作時,可使用的系統呼叫有好幾個,
但在驅動程式,只需要準備一個函式即可。
不管 user process 用了哪個系統呼叫,kernel 都只會呼叫驅動程式提供的這個函式。

Character 類型的裝置想支援同時執行多個同步 I/O 工作的話,只要在驅動程式準備 poll方法即可。
poll方法會收到 file_operations 結構。

poll方法會在kernel 處理 select 與 poll 之類的系統呼叫時用到。它必須的執行工作如下:
1. 在 wait queue 豋記。
2. 傳回目前可以操作的狀態。

在呼叫同時執行多個同步 I/O 工作的系統呼叫時,block 直到狀態變化的動作,指的是在 kernel裡面 sleep。
如果要 sleep的話,要先準備 wait queue(wait_queue_head_t),這個由驅動程式負責提供。
豋記 wait queue 的工作可透過 poll_wait()完成,它定義在 include/linux/poll.h

在呼叫同時執行多個同步 I/O 工作的系統呼叫時,解除 block 的時機,是驅動程式透過傳給 poll_wait()
的 wait queue 被喚醒的時候。Kernel 在被 wait queue 喚醒之後,會再次呼叫驅動程式的 poll()
確認是否成為等待中的狀態(可寫入或可讀出),如果是的話,FD_ISSET 巨集就會成立。
想判斷是不是這種狀態的話,還是需要驅動程式提供資訊才行,這個資訊就是透過poll()方法的傳回值來表示。
傳回值要透過 include/linux/poll.h 定義的巨集 OR 起來表示,下面是常用到的組合。
數值定義於 android/bionic/libc/kernel/arch-arm/asm/poll.h

說了那麼多,還倒不如寫個程式比較好了解。

poll操作一般結構:

剛試了一下,果然新舊版核心還是有差,幾個問題,稍為提出來討論一下:
1. 要用 kmalloc 及 kfree 的話,要 include 註:其實在上一個示範程式就已有這問題了。
請參考: 基礎 Linux Device Driver 驅動程式#9 (IOCTL)
2. void init_MUTEX (struct semaphore *sem); /* 新版 kernel 已不適用 */
改用
void sema_init (struct semaphore *sem, int val);

test_select.c 原始碼如下:

Makefile 如下:

/*****************************************************************************/

test.c 如下:
/*****************************************************************************/

/*****************************************************************************/

執行結果如下:

# ls
Makefile test_code test_select.c
# make
make -C “/opt/linux-source-2.6.38” M=/opt/test_driver/my_driver/test_select modules
make[1]: Entering directory /opt/linux-source-2.6.38'
CC [M] /opt/test_driver/my_driver/test_select/test_select.o
/opt/test_driver/my_driver/test_select/test_select.c: In function ‘test_select_poll’:
/opt/test_driver/my_driver/test_select/test_select.c:44:1: warning: control reaches end of non-void function
Building modules, stage 2.
MODPOST 1 modules
CC /opt/test_driver/my_driver/test_select/test_select.mod.o
LD [M] /opt/test_driver/my_driver/test_select/test_select.ko
make[1]: Leaving directory
/opt/linux-source-2.6.38′
# ls
Makefile Module.symvers test_select.c test_select.mod.c test_select.o
modules.order test_code test_select.ko test_select.mod.o
# cd test_code/
# ls
test.c
# gcc test.c -o test
# ls
test test.c
# cd ..
# insmod ./test_select.ko
test_select driver(major: 250) installed.
# mknod /dev/test_select0 c 250 0
# ./test_code/test
select() …
read() …
… 經過 10秒 …
read() 1
ff
# dmesg | tail
… 以上略過 …
Call test_select_open.
Call test_select_poll.
test_select_poll returned (mask 0x104)
Call test_select_timeout.
Call test_select_close.

# rm -rf test_code/test
# rm /dev/test_select0
# rmmod test_select
# make clean
make -C “/opt/linux-source-2.6.38” M=/opt/test_driver/my_driver/test_select clean
make[1]: Entering directory /opt/linux-source-2.6.38'
CLEAN /opt/test_driver/my_driver/test_select/.tmp_versions
CLEAN /opt/test_driver/my_driver/test_select/Module.symvers
make[1]: Leaving directory
/opt/linux-source-2.6.38′

OK, 一切就是如此的順利,世界就是如此的美好 ^^

註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。

發表迴響