備份好文章
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。
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);void FD_CLR(int fd, fd_set *set);int FD_ISSET(int fd, fd_set *set);void FD_SET(int fd, fd_set *set);void FD_ZERO(fd_set *set);
還有一個和 select()很類似的 poll()系統呼叫,以基本功能來說,select()與poll()都一樣,
但指定的 file handler的方式不一樣,且指定多個 file handler 的時候,poll()走訪所有
file handler的速度比較快。 可參考 man 2 poll。
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
應用程式想同時執行多個同步 I/O 工作時,可使用的系統呼叫有好幾個,
但在驅動程式,只需要準備一個函式即可。
不管 user process 用了哪個系統呼叫,kernel 都只會呼叫驅動程式提供的這個函式。
Character 類型的裝置想支援同時執行多個同步 I/O 工作的話,只要在驅動程式準備 poll方法即可。
poll方法會收到 file_operations 結構。
unsigned int (*poll) (struct file *, struct poll_table_struct *);
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
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p);// filp:執行操作的設備文件指針// wait_address:設備驅動的等待隊列頭// p:內核傳遞的輪詢表指針 調用poll_wait()函數將等待隊列添加到poll_table輪詢表 (出處參考)
在呼叫同時執行多個同步 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
POLLIN|POLLRDNORM 可讀取POLLOUT|POLLWRNORM 可寫入POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM 可讀寫POLLERR 發生錯誤POLLHUP 裝置離線(EOF)
說了那麼多,還倒不如寫個程式比較好了解。
poll操作一般結構:
xxx_poll() { unsigned int mask = 0;//定義返回值 ... //調用poll_wait()把進程添加到輪詢表 ... if(device is ready for read){ mask = POLLIN | POLLRDNORM; } ... return mask; }
剛試了一下,果然新舊版核心還是有差,幾個問題,稍為提出來討論一下:
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 原始碼如下:
/*****************************************************************************/#include #include #include #include #include #include #include #include #include #include #include #define DRIVER_NAME "test_select"static unsigned int test_select_major = 0;static unsigned int num_of_dev = 1;static struct cdev test_select_cdev;static unsigned int timeout_value = 10;struct test_select_data { struct timer_list timeout; spinlock_t lock; wait_queue_head_t read_wait; int timeout_done; struct semaphore sem;};unsigned int test_select_poll(struct file *filp, poll_table *wait){ struct test_select_data *data = filp->private_data; unsigned int mask = POLLOUT|POLLWRNORM; printk(KERN_ALERT "Call test_select_poll.\n"); if (data == NULL) return -EBADFD; down(&data->sem); poll_wait(filp, &data->read_wait, wait); if (data->timeout_done == 1) { /* readable */ mask |= POLLIN|POLLRDNORM; } up(&data->sem); printk(KERN_ALERT "%s returned (mask 0x%x)\n", __func__, mask);}static void test_select_timeout(unsigned long arg){ struct test_select_data *data = (struct test_select_data*)arg; unsigned long flags; printk(KERN_ALERT "Call test_select_timeout.\n"); spin_lock_irqsave(&data->lock, flags); data->timeout_done = 1; wake_up_interruptible(&data->read_wait); spin_unlock_irqrestore(&data->lock, flags);}ssize_t test_select_write (struct file *filp, const char __user *buf, size_t count, loff_t *pos){ return -EFAULT;}ssize_t test_select_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos){ struct test_select_data *data = filp->private_data; int i; unsigned char val; int retval; if (down_interruptible(&data->sem)) return -ERESTARTSYS; if (data->timeout_done == 0) { /* no read */ up(&data->sem); if (filp->f_flags & O_NONBLOCK) /* non-blocking mode */ return -EAGAIN; do { retval = wait_event_interruptible_timeout( data->read_wait, data->timeout_done == 1, 1*HZ); if (retval == -ERESTARTSYS) return -ERESTARTSYS; } while (retval == 0); /* timeout elapsed */ if (down_interruptible(&data->sem)) return -ERESTARTSYS; } val = 0xff; for (i = 0; i < count; i++) { if (copy_to_user(&buf[i], &val, 1)) { retval = -EFAULT; goto out; } } retval = count;out: data->timeout_done = 0; /* restart timer */ mod_timer(&data->timeout, jiffies + timeout_value*HZ); up(&data->sem); return retval;}static int test_select_close(struct inode *inode, struct file *filp){ struct test_select_data *data = filp->private_data; printk(KERN_ALERT "Call test_select_close.\n"); if (data) { del_timer_sync(&data->timeout); kfree(data); } return 0;}static int test_select_open(struct inode *inode, struct file *filp){ struct test_select_data *data; printk(KERN_ALERT "Call test_select_open.\n"); data = kmalloc(sizeof(struct test_select_data), GFP_KERNEL); if (data == NULL) return -ENOMEM; /* initialize members */ spin_lock_init(&data->lock); init_waitqueue_head(&data->read_wait); // init_MUTEX(&data->sem); /* 新版 kernel 已不適用 */ sema_init(&data->sem, 1); /* 改用 sema_init */ init_timer(&data->timeout); data->timeout.function = test_select_timeout; data->timeout.data = (unsigned long)data; filp->private_data = data; /* start timer */ data->timeout_done = 0; mod_timer(&data->timeout, jiffies + timeout_value*HZ); return 0;}struct file_operations fops = { .owner = THIS_MODULE, .open = test_select_open, .release = test_select_close, .read = test_select_read, .write = test_select_write, .poll = test_select_poll,};static int test_select_init(void){ dev_t dev = MKDEV(test_select_major, 0); int alloc_ret = 0; int cdev_ret = 0; alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME); if (alloc_ret) goto error; test_select_major = MAJOR(dev); cdev_init(&test_select_cdev, &fops); cdev_ret = cdev_add(&test_select_cdev, dev, num_of_dev); if (cdev_ret) goto error; printk(KERN_ALERT "%s driver(major: %d) installed.\n", DRIVER_NAME, test_select_major); return 0;error: if (cdev_ret == 0) cdev_del(&test_select_cdev); if (alloc_ret == 0) unregister_chrdev_region(dev, num_of_dev); return -1;}static void test_select_exit(void){ dev_t dev = MKDEV(test_select_major, 0); cdev_del(&test_select_cdev); unregister_chrdev_region(dev, num_of_dev); printk(KERN_ALERT "%s driver removed\n", DRIVER_NAME);}module_init(test_select_init);module_exit(test_select_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Wang Chen Shu");MODULE_DESCRIPTION("This is test_select module.");/*****************************************************************************/
Makefile 如下:
/*****************************************************************************/KDIR="/opt/linux-source-2.6.38"PWD=$(shell pwd)obj-m := test_select.oall: $(MAKE) -C ${KDIR} M=${PWD} modulesclean: $(MAKE) -C ${KDIR} M=${PWD} clean
/*****************************************************************************/
test.c 如下:
/*****************************************************************************/
#include #include #include #define DEVFILE "/dev/test_select0"int main(){ int fd; fd_set rfds; struct timeval tv; int retval; unsigned char buf; ssize_t sz; int i; fd = open(DEVFILE, O_RDWR); if (fd == -1) { perror("open"); return -1; } do { FD_ZERO(&rfds); FD_SET(fd, &rfds); tv.tv_sec = 5; tv.tv_usec = 0; printf("select() ...\n"); retval = select(fd + 1, &rfds, NULL, NULL, &tv); if (retval == -1) { perror("select"); break; } if (retval) { break; } } while (retval == 0); /* timeout elapsed */ if (FD_ISSET(fd, &rfds)) { printf("read() ...\n"); sz = read(fd, &buf, 1); printf("read() %d\n", sz); printf("%02x ", buf); printf("\n"); } close(fd); return 0;}
/*****************************************************************************/
執行結果如下:
# 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驅動程式設計由平田豐著的這本書。