備份好文章
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。
1 2 3 4 5 6 7 |
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。
1 |
int poll(struct pollfd *fds, nfds_t nfds, int timeout); |
應用程式想同時執行多個同步 I/O 工作時,可使用的系統呼叫有好幾個,
但在驅動程式,只需要準備一個函式即可。
不管 user process 用了哪個系統呼叫,kernel 都只會呼叫驅動程式提供的這個函式。
Character 類型的裝置想支援同時執行多個同步 I/O 工作的話,只要在驅動程式準備 poll方法即可。
poll方法會收到 file_operations 結構。
1 |
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
1 2 3 4 5 |
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p); // filp:執行操作的設備文件指針 // wait_address:設備驅動的等待隊列頭 // p:內核傳遞的輪詢表指針 調用poll_wait()函數將等待隊列添加到poll_table輪詢表 (<a href="http://blog.csdn.net/waldmer/article/details/18735517" target="_blank" rel="noopener noreferrer">出處</a>參考) |
在呼叫同時執行多個同步 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
1 2 3 4 5 |
POLLIN|POLLRDNORM 可讀取 POLLOUT|POLLWRNORM 可寫入 POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM 可讀寫 POLLERR 發生錯誤 POLLHUP 裝置離線(EOF) |
說了那麼多,還倒不如寫個程式比較好了解。
poll操作一般結構:
1 2 3 4 5 6 7 8 9 10 11 12 |
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 原始碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
/*****************************************************************************/ #include <linux/init.h> #include <linux/module.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/slab.h> #include <linux/poll.h> #include <linux/sched.h> #include <asm/uaccess.h> #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 如下:
1 2 3 4 5 6 7 8 9 10 |
/*****************************************************************************/ KDIR="/opt/linux-source-2.6.38" PWD=$(shell pwd) obj-m := test_select.o all: $(MAKE) -C ${KDIR} M=${PWD} modules clean: $(MAKE) -C ${KDIR} M=${PWD} clean |
/*****************************************************************************/
test.c 如下:
/*****************************************************************************/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
#include <stdio.h> #include <fcntl.h> #include <errno.h> #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'
/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
# 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'
/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
OK, 一切就是如此的順利,世界就是如此的美好 ^^
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。