一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

【原創(chuàng)】Linux select/poll機(jī)制原理分析

 頭號(hào)碼甲 2022-07-09 發(fā)布于北京

前言

  • Read the fucking source code! --By 魯迅
  • A picture is worth a thousand words. --By 高爾基

1. 概述

Linux系統(tǒng)在訪問(wèn)設(shè)備的時(shí)候,存在以下幾種IO模型:

  1. Blocking IO Model,阻塞IO模型;
  2. Nonblocking I/O Model,非阻塞IO模型;
  3. I/O Multiplexing Model,IO多路復(fù)用模型;
  4. Signal Driven I/O Model,信號(hào)驅(qū)動(dòng)IO模型;
  5. Asynchronous I/O Model,異步IO模型;

今天我們來(lái)分析下IO多路復(fù)用機(jī)制,在Linux中是通過(guò)select/poll/epoll機(jī)制來(lái)實(shí)現(xiàn)的。

先看一下阻塞IO模型與非阻塞IO模型的特點(diǎn):

  • 阻塞IO模型:在IO訪問(wèn)的時(shí)候,如果條件沒(méi)有滿足,會(huì)將當(dāng)前任務(wù)切換出去,等到條件滿足時(shí)再切換回來(lái)。
    • 缺點(diǎn):阻塞IO操作,會(huì)讓處于同一個(gè)線程的執(zhí)行邏輯都在阻塞期間無(wú)法執(zhí)行,這往往意味著需要?jiǎng)?chuàng)建單獨(dú)的線程來(lái)交互。
  • 非阻塞IO模型:在IO訪問(wèn)的時(shí)候,如果條件沒(méi)有滿足,直接返回,不會(huì)block該任務(wù)的后續(xù)操作。
    • 缺點(diǎn):非阻塞IO需要用戶一直輪詢操作,輪詢可能會(huì)來(lái)帶CPU的占用問(wèn)題。

對(duì)單個(gè)設(shè)備IO操作時(shí),問(wèn)題并不嚴(yán)重,如果有多個(gè)設(shè)備呢?比如,在服務(wù)器中,監(jiān)聽(tīng)多個(gè)Client的收發(fā)處理,這時(shí)候IO多路復(fù)用就顯得尤為重要了,來(lái)張圖:

如果這個(gè)圖,讓你有點(diǎn)迷惑,那就像個(gè)男人一樣,man一下select/poll函數(shù)吧:

  • select:

  • poll

簡(jiǎn)單來(lái)說(shuō),select/poll能監(jiān)聽(tīng)多個(gè)設(shè)備的文件描述符,只要有任何一個(gè)設(shè)備滿足條件,select/poll就會(huì)返回,否則將進(jìn)行睡眠等待。
看起來(lái),select/poll像是一個(gè)管家了,統(tǒng)一負(fù)責(zé)來(lái)監(jiān)聽(tīng)處理了。

已經(jīng)迫不及待來(lái)看看原理了,由于底層的機(jī)制大體差不多,我將選擇select來(lái)做進(jìn)一步分析。

2. 原理

2.1 select系統(tǒng)調(diào)用

select的系統(tǒng)調(diào)用開(kāi)始:

  • select系統(tǒng)調(diào)用,最終的核心邏輯是在do_select函數(shù)中處理的,參考fs/select.c文件;
  • do_select函數(shù)中,有幾個(gè)關(guān)鍵的操作:
    1. 初始化poll_wqueues結(jié)構(gòu),包括幾個(gè)關(guān)鍵函數(shù)指針的初始化,用于驅(qū)動(dòng)中進(jìn)行回調(diào)處理;
    2. 循環(huán)遍歷監(jiān)測(cè)的文件描述符,并且調(diào)用f_op->poll()函數(shù),如果有監(jiān)測(cè)條件滿足,則會(huì)跳出循環(huán);
    3. 在監(jiān)測(cè)的文件描述符都不滿足條件時(shí),poll_schedule_timeout讓當(dāng)前進(jìn)程進(jìn)行睡眠,超時(shí)喚醒,或者被所屬的等待隊(duì)列喚醒;
  • do_select函數(shù)的循環(huán)退出條件有三個(gè):
    1. 檢測(cè)的文件描述符滿足條件;
    2. 超時(shí);
    3. 有信號(hào)要處理;
  • 在設(shè)備驅(qū)動(dòng)程序中實(shí)現(xiàn)的poll()函數(shù),會(huì)在do_select()中被調(diào)用,而驅(qū)動(dòng)中的poll()函數(shù),需要調(diào)用poll_wait()函數(shù),poll_wait函數(shù)本身很簡(jiǎn)單,就是去回調(diào)函數(shù)p->_qproc(),這個(gè)回調(diào)函數(shù)正是poll_initwait()函數(shù)中初始化的__pollwait();

所以,來(lái)看看__pollwait()函數(shù)嘍。

2.2 __pollwait

  • 驅(qū)動(dòng)中的poll_wait函數(shù)回調(diào)__pollwait,這個(gè)函數(shù)完成的工作是向struct poll_wqueue結(jié)構(gòu)中添加一條poll_table_entry
  • poll_table_entry中包含了等待隊(duì)列的相關(guān)數(shù)據(jù)結(jié)構(gòu);
  • 對(duì)等待隊(duì)列的相關(guān)數(shù)據(jù)結(jié)構(gòu)進(jìn)行初始化,包括設(shè)置等待隊(duì)列喚醒時(shí)的回調(diào)函數(shù)指針,設(shè)置成pollwake;
  • 將任務(wù)添加到驅(qū)動(dòng)程序中的等待隊(duì)列中,最終驅(qū)動(dòng)可以通過(guò)wake_up_interruptile等接口來(lái)喚醒處理;

這一頓操作,其實(shí)就是驅(qū)動(dòng)向select維護(hù)的struct poll_wqueue中注冊(cè),并將調(diào)用select的任務(wù)添加到驅(qū)動(dòng)的等待隊(duì)列中,以便在合適的時(shí)機(jī)進(jìn)行喚醒。所以,本質(zhì)上來(lái)說(shuō),這是基于等待隊(duì)列的機(jī)制來(lái)實(shí)現(xiàn)的。

是不是還有點(diǎn)抽象,來(lái)看看數(shù)據(jù)結(jié)構(gòu)的組織關(guān)系吧。

2.3 數(shù)據(jù)結(jié)構(gòu)關(guān)系

  • 調(diào)用select系統(tǒng)調(diào)用的進(jìn)程/線程,會(huì)維護(hù)一個(gè)struct poll_wqueues結(jié)構(gòu),其中兩個(gè)關(guān)鍵字段:
    1. pll_table:該結(jié)構(gòu)體中的函數(shù)指針_qproc指向__pollwait函數(shù);
    2. struct poll_table_entry[]:存放不同設(shè)備的poll_table_entry,這些條目的增加是在驅(qū)動(dòng)調(diào)用poll_wait->__pollwait()時(shí)進(jìn)行初始化并完成添加的;

2.4 驅(qū)動(dòng)編寫啟示

如果驅(qū)動(dòng)中要支持select的接口調(diào)用,那么需要做哪些事情呢?
如果理解了上文中的內(nèi)容,你會(huì)毫不猶豫的大聲說(shuō)出以下幾條:

  1. 定義一個(gè)等待隊(duì)列頭wait_queue_head_t,用于收留等待隊(duì)列任務(wù);
  2. struct file_operations結(jié)構(gòu)體中的poll函數(shù)需要實(shí)現(xiàn),比如xxx_poll();
  3. xxx_poll()函數(shù)中,當(dāng)然不要忘了poll_wait函數(shù)的調(diào)用了,此外,該函數(shù)的返回值mask需要注意是在條件滿足時(shí)對(duì)應(yīng)的值,比如EPOLLIN/EPOLL/EPOLLERR等,這個(gè)返回值是在do_select()函數(shù)中會(huì)去判斷處理的;
  4. 條件滿足的時(shí)候,wake_up_interruptible喚醒任務(wù),當(dāng)然也可以使用wake_up,區(qū)別是:wake_up_interruptible只能喚醒處于TASK_INTERRUPTIBLE狀態(tài)的任務(wù),而wake_up能喚醒處于TASK_INTERRUPTIBLETASK_UNINTERRUPTIBLE狀態(tài)的任務(wù);

2.5 select/poll的差異

  • selectpoll本質(zhì)上基本類似,其中select是由BSD UNIX引入,pollSystemV引入;
  • selectpoll需要輪詢文件描述符集合,并在用戶態(tài)和內(nèi)核態(tài)之間進(jìn)行拷貝,在文件描述符很多的情況下開(kāi)銷會(huì)比較大,select默認(rèn)支持的文件描述符數(shù)量是1024;
  • Linux提供了epoll機(jī)制,改進(jìn)了selectpoll在效率與資源上的缺點(diǎn),未深入了解;

3. 示例代碼

3.1 內(nèi)核驅(qū)動(dòng)

示例代碼中的邏輯:

  1. 驅(qū)動(dòng)維護(hù)一個(gè)count值,當(dāng)count值大于0時(shí),表明條件滿足,poll返回正常的mask值;
  2. poll函數(shù)每執(zhí)行一次,count值就減去一次;
  3. count的值可以由用戶通過(guò)ioctl來(lái)進(jìn)行設(shè)置;
#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 測(cè)試代碼

測(cè)試代碼邏輯:

  1. 創(chuàng)建一個(gè)設(shè)值線程,用于每隔2秒來(lái)設(shè)置一次count值;
  2. 主線程調(diào)用select函數(shù)監(jiān)聽(tīng),當(dāng)設(shè)值線程設(shè)置了count值后,select便會(huì)返回;
#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;
}

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    九九久久精品久久久精品| 99久久国产综合精品二区| 大香蕉久草网一区二区三区| 久久久精品日韩欧美丰满| 中国日韩一级黄色大片| 日本人妻精品有码字幕| 激情五月天深爱丁香婷婷| 91人妻丝袜一区二区三区| 97人妻精品一区二区三区男同| 麻豆欧美精品国产综合久久| 欧美日韩精品视频在线| 亚洲男女性生活免费视频| 中文字幕日韩精品人一妻| 久草国产精品一区二区| 好吊妞视频只有这里有精品| 青青操日老女人的穴穴| 久久综合亚洲精品蜜桃| 日韩免费av一区二区三区| 东京热一二三区在线免| 91插插插外国一区二区婷婷| 99秋霞在线观看视频| 91麻豆视频国产一区二区| 美国欧洲日本韩国二本道| 亚洲成人免费天堂诱惑| 亚洲国产精品一区二区毛片| 91偷拍视频久久精品| 精品国产亚洲av成人一区| 少妇肥臀一区二区三区| 亚洲三级视频在线观看免费| 国产欧美日韩一级小黄片| 亚洲一区二区精品国产av| 99国产精品国产精品九九| 久久亚洲精品成人国产| 成人午夜在线视频观看| 免费观看日韩一级黄色大片| 91爽人人爽人人插人人爽| 欧美精品一区二区水蜜桃| 日韩少妇人妻中文字幕| 欧美国产日产综合精品| 视频一区二区三区自拍偷| 欧美日韩精品综合一区|