Read the fucking source code!
--By 魯迅A picture is worth a thousand words.
--By 高爾基Linux系統在訪問設備的時候,存在如下幾種IO模型:node
Blocking IO Model,阻塞IO模型
;Nonblocking I/O Model,非阻塞IO模型
;I/O Multiplexing Model,IO多路複用模型
;Signal Driven I/O Model,信號驅動IO模型
;Asynchronous I/O Model,異步IO模型
;今天咱們來分析下IO多路複用機制,在Linux中是經過select/poll/epoll
機制來實現的。linux
先看一下阻塞IO模型與非阻塞IO模型的特色:服務器
對單個設備IO操做時,問題並不嚴重,若是有多個設備呢?好比,在服務器中,監聽多個Client的收發處理,這時候IO多路複用就顯得尤其重要了,來張圖:數據結構
若是這個圖,讓你有點迷惑,那就像個男人同樣,man
一下select/poll
函數吧:異步
select
:
函數
poll
oop
簡單來講,select/poll
能監聽多個設備的文件描述符,只要有任何一個設備知足條件,select/poll
就會返回,不然將進行睡眠等待。
看起來,select/poll
像是一個管家了,統一負責來監聽處理了。測試
已經火燒眉毛來看看原理了,因爲底層的機制大致差很少,我將選擇select
來作進一步分析。線程
從select
的系統調用開始:3d
select
系統調用,最終的核心邏輯是在do_select
函數中處理的,參考fs/select.c
文件;do_select
函數中,有幾個關鍵的操做:
poll_wqueues
結構,包括幾個關鍵函數指針的初始化,用於驅動中進行回調處理;f_op->poll()
函數,若是有監測條件知足,則會跳出循環;poll_schedule_timeout
讓當前進程進行睡眠,超時喚醒,或者被所屬的等待隊列喚醒;do_select
函數的循環退出條件有三個:
poll()
函數,會在do_select()
中被調用,而驅動中的poll()
函數,須要調用poll_wait()
函數,poll_wait
函數自己很簡單,就是去回調函數p->_qproc()
,這個回調函數正是poll_initwait()
函數中初始化的__pollwait()
;因此,來看看__pollwait()
函數嘍。
__pollwait
poll_wait
函數回調__pollwait
,這個函數完成的工做是向struct poll_wqueue
結構中添加一條poll_table_entry
;poll_table_entry
中包含了等待隊列的相關數據結構;pollwake
;wake_up_interruptile
等接口來喚醒處理;這一頓操做,其實就是驅動向select
維護的struct poll_wqueue
中註冊,並將調用select
的任務添加到驅動的等待隊列中,以便在合適的時機進行喚醒。因此,本質上來講,這是基於等待隊列的機制來實現的。
是否是還有點抽象,來看看數據結構的組織關係吧。
select
系統調用的進程/線程,會維護一個struct poll_wqueues
結構,其中兩個關鍵字段:
pll_table
:該結構體中的函數指針_qproc
指向__pollwait
函數;struct poll_table_entry[]
:存放不一樣設備的poll_table_entry
,這些條目的增長是在驅動調用poll_wait->__pollwait()
時進行初始化並完成添加的;若是驅動中要支持select
的接口調用,那麼須要作哪些事情呢?
若是理解了上文中的內容,你會堅決果斷的大聲說出如下幾條:
wait_queue_head_t
,用於收留等待隊列任務;struct file_operations
結構體中的poll
函數須要實現,好比xxx_poll()
;xxx_poll()
函數中,固然不要忘了poll_wait
函數的調用了,此外,該函數的返回值mask
須要注意是在條件知足時對應的值,好比EPOLLIN/EPOLL/EPOLLERR
等,這個返回值是在do_select()
函數中會去判斷處理的;wake_up_interruptible
喚醒任務,固然也可使用wake_up
,區別是:wake_up_interruptible
只能喚醒處於TASK_INTERRUPTIBLE
狀態的任務,而wake_up
能喚醒處於TASK_INTERRUPTIBLE
和TASK_UNINTERRUPTIBLE
狀態的任務;select/poll
的差別select
與poll
本質上基本相似,其中select
是由BSD UNIX
引入,poll
由SystemV
引入;select
與poll
須要輪詢文件描述符集合,並在用戶態和內核態之間進行拷貝,在文件描述符不少的狀況下開銷會比較大,select
默認支持的文件描述符數量是1024;epoll
機制,改進了select
與poll
在效率與資源上的缺點,未深刻了解;示例代碼中的邏輯:
ioctl
來進行設置;#include <linux/init.h> #include <linux/module.h> #include <linux/poll.h> #include <linux/wait.h> #include <linux/cdev.h> #include <linux/mutex.h> #include <linux/slab.h> #include <asm/ioctl.h> #define POLL_DEV_NAME "poll" #define POLL_MAGIC 'P' #define POLL_SET_COUNT (_IOW(POLL_MAGIC, 0, unsigned int)) struct poll_dev { struct cdev cdev; struct class *class; struct device *device; wait_queue_head_t wq_head; struct mutex poll_mutex; unsigned int count; dev_t devno; }; struct poll_dev *g_poll_dev = NULL; static int poll_open(struct inode *inode, struct file *filp) { filp->private_data = g_poll_dev; return 0; } static int poll_close(struct inode *inode, struct file *filp) { return 0; } static unsigned int poll_poll(struct file *filp, struct poll_table_struct *wait) { unsigned int mask = 0; struct poll_dev *dev = filp->private_data; mutex_lock(&dev->poll_mutex); poll_wait(filp, &dev->wq_head, wait); if (dev->count > 0) { mask |= POLLIN | POLLRDNORM; /* decrease each time */ dev->count--; } mutex_unlock(&dev->poll_mutex); return mask; } static long poll_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct poll_dev *dev = filp->private_data; unsigned int cnt; switch (cmd) { case POLL_SET_COUNT: mutex_lock(&dev->poll_mutex); if (copy_from_user(&cnt, (void __user *)arg, _IOC_SIZE(cmd))) { pr_err("copy_from_user fail:%d\n", __LINE__); return -EFAULT; } if (dev->count == 0) { wake_up_interruptible(&dev->wq_head); } /* update count */ dev->count += cnt; mutex_unlock(&dev->poll_mutex); break; default: return -EINVAL; } return 0; } static struct file_operations poll_fops = { .owner = THIS_MODULE, .open = poll_open, .release = poll_close, .poll = poll_poll, .unlocked_ioctl = poll_ioctl, .compat_ioctl = poll_ioctl, }; static int __init poll_init(void) { int ret; if (g_poll_dev == NULL) { g_poll_dev = (struct poll_dev *)kzalloc(sizeof(struct poll_dev), GFP_KERNEL); if (g_poll_dev == NULL) { pr_err("struct poll_dev allocate fail\n"); return -1; } } /* allocate device number */ ret = alloc_chrdev_region(&g_poll_dev->devno, 0, 1, POLL_DEV_NAME); if (ret < 0) { pr_err("alloc_chrdev_region fail:%d\n", ret); goto alloc_chrdev_err; } /* set char-device */ cdev_init(&g_poll_dev->cdev, &poll_fops); g_poll_dev->cdev.owner = THIS_MODULE; ret = cdev_add(&g_poll_dev->cdev, g_poll_dev->devno, 1); if (ret < 0) { pr_err("cdev_add fail:%d\n", ret); goto cdev_add_err; } /* create device */ g_poll_dev->class = class_create(THIS_MODULE, POLL_DEV_NAME); if (IS_ERR(g_poll_dev->class)) { pr_err("class_create fail\n"); goto class_create_err; } g_poll_dev->device = device_create(g_poll_dev->class, NULL, g_poll_dev->devno, NULL, POLL_DEV_NAME); if (IS_ERR(g_poll_dev->device)) { pr_err("device_create fail\n"); goto device_create_err; } mutex_init(&g_poll_dev->poll_mutex); init_waitqueue_head(&g_poll_dev->wq_head); return 0; device_create_err: class_destroy(g_poll_dev->class); class_create_err: cdev_del(&g_poll_dev->cdev); cdev_add_err: unregister_chrdev_region(g_poll_dev->devno, 1); alloc_chrdev_err: kfree(g_poll_dev); g_poll_dev = NULL; return -1; } static void __exit poll_exit(void) { cdev_del(&g_poll_dev->cdev); device_destroy(g_poll_dev->class, g_poll_dev->devno); unregister_chrdev_region(g_poll_dev->devno, 1); class_destroy(g_poll_dev->class); kfree(g_poll_dev); g_poll_dev = NULL; } module_init(poll_init); module_exit(poll_exit); MODULE_DESCRIPTION("select/poll test"); MODULE_AUTHOR("LoyenWang"); MODULE_LICENSE("GPL");
測試代碼邏輯:
select
函數監聽,當設值線程設置了count值後,select便會返回;#include <stdio.h> #include <string.h> #include <fcntl.h> #include <pthread.h> #include <errno.h> #include <unistd.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/time.h> static void *set_count_thread(void *arg) { int fd = *(int *)arg; unsigned int count_value = 1; int loop_cnt = 20; int ret; while (loop_cnt--) { ret = ioctl(fd, NOTIFY_SET_COUNT, &count_value); if (ret < 0) { printf("ioctl set count value fail:%s\n", strerror(errno)); return NULL; } sleep(1); } return NULL; } int main(void) { int fd; int ret; pthread_t setcnt_tid; int loop_cnt = 20; /* for select use */ fd_set rfds; struct timeval tv; fd = open("/dev/poll", O_RDWR); if (fd < 0) { printf("/dev/poll open failed: %s\n", strerror(errno)); return -1; } /* wait up to five seconds */ tv.tv_sec = 5; tv.tv_usec = 0; ret = pthread_create(&setcnt_tid, NULL, set_count_thread, &fd); if (ret < 0) { printf("set_count_thread create fail: %d\n", ret); return -1; } while (loop_cnt--) { FD_ZERO(&rfds); FD_SET(fd, &rfds); ret = select(fd + 1, &rfds, NULL, NULL, &tv); //ret = select(fd + 1, &rfds, NULL, NULL, NULL); if (ret == -1) { perror("select()"); break; } else if (ret) printf("Data is available now.\n"); else { printf("No data within five seconds.\n"); } } ret = pthread_join(setcnt_tid, NULL); if (ret < 0) { printf("set_count_thread join fail.\n"); return -1; } close(fd); return 0; }