日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux select/poll机制原理分析

發布時間:2023/12/20 linux 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux select/poll机制原理分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉載一篇文章,講解select和poll機制的,分享給大家。


前言

  • Read the fucking source code! ?--By 魯迅

  • A picture is worth a thousand words. --By 高爾基

1. 概述

Linux系統在訪問設備的時候,存在以下幾種IO模型:

  • 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機制來實現的。

    先看一下阻塞IO模型與非阻塞IO模型的特點:


    • 阻塞IO模型:在IO訪問的時候,如果條件沒有滿足,會將當前任務切換出去,等到條件滿足時再切換回來。

      • 缺點:阻塞IO操作,會讓處于同一個線程的執行邏輯都在阻塞期間無法執行,這往往意味著需要創建單獨的線程來交互。

    • 非阻塞IO模型:在IO訪問的時候,如果條件沒有滿足,直接返回,不會block該任務的后續操作。

      • 缺點:非阻塞IO需要用戶一直輪詢操作,輪詢可能會來帶CPU的占用問題。

    對單個設備IO操作時,問題并不嚴重,如果有多個設備呢?比如,在服務器中,監聽多個Client的收發處理,這時候IO多路復用就顯得尤為重要了,來張圖:


    如果這個圖,讓你有點迷惑,那就像個男人一樣,man一下select/poll函數吧:

    • select:

    • poll

    簡單來說,select/poll能監聽多個設備的文件描述符,只要有任何一個設備滿足條件,select/poll就會返回,否則將進行睡眠等待??雌饋?#xff0c;select/poll像是一個管家了,統一負責來監聽處理了。

    已經迫不及待來看看原理了,由于底層的機制大體差不多,我將選擇select來做進一步分析。

    2. 原理

    2.1 select系統調用

    從select的系統調用開始:


    • 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()函數嘍。

      2.2 __pollwait


      • 驅動中的poll_wait函數回調__pollwait,這個函數完成的工作是向struct poll_wqueue結構中添加一條poll_table_entry;

      • poll_table_entry中包含了等待隊列的相關數據結構;

      • 對等待隊列的相關數據結構進行初始化,包括設置等待隊列喚醒時的回調函數指針,設置成pollwake;

      • 將任務添加到驅動程序中的等待隊列中,最終驅動可以通過wake_up_interruptile等接口來喚醒處理;

      這一頓操作,其實就是驅動向select維護的struct poll_wqueue中注冊,并將調用select的任務添加到驅動的等待隊列中,以便在合適的時機進行喚醒。所以,本質上來說,這是基于等待隊列的機制來實現的。

      是不是還有點抽象,來看看數據結構的組織關系吧。

      2.3 數據結構關系


      • 調用select系統調用的進程/線程,會維護一個struct poll_wqueues結構,其中兩個關鍵字段:

    • pll_table:該結構體中的函數指針_qproc指向__pollwait函數;

    • struct poll_table_entry[]:存放不同設備的poll_table_entry,這些條目的增加是在驅動調用poll_wait->__pollwait()時進行初始化并完成添加的;

    • 2.4 驅動編寫啟示

      如果驅動中要支持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狀態的任務;

    • 2.5 select/poll的差異

      • select與poll本質上基本類似,其中select是由BSD UNIX引入,poll由SystemV引入;

      • select與poll需要輪詢文件描述符集合,并在用戶態和內核態之間進行拷貝,在文件描述符很多的情況下開銷會比較大,select默認支持的文件描述符數量是1024;

      • Linux提供了epoll機制,改進了select與poll在效率與資源上的缺點,未深入了解;

      3. 示例代碼

      3.1 內核驅動

      示例代碼中的邏輯:

    • 驅動維護一個count值,當count值大于0時,表明條件滿足,poll返回正常的mask值;

    • poll函數每執行一次,count值就減去一次;

    • count的值可以由用戶通過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");

      3.2 測試代碼

      測試代碼邏輯:

    • 創建一個設值線程,用于每隔2秒來設置一次count值;

    • 主線程調用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; }

      推薦閱讀:

      專輯|Linux文章匯總

      專輯|程序人生

      嵌入式Linux

      微信掃描二維碼,關注我的公眾號

    總結

    以上是生活随笔為你收集整理的Linux select/poll机制原理分析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。