linux poll 机制

目录

1 poll 调用的实现

2 poll () 调用原理:

3 kernel driver 的 .poll 实现

4 kernel driver notify 实现poll 状态上报

4.1 brigntness 节点的写法是调用sysfs_notify()

4.2 brightness_hw_changed 是调用 sysfs_notify_dirent()实现的


1 poll 调用的实现

高通的led代码为例进行分析和学习

/* Event types that can be polled for.  These bits may be set in `events'
   to indicate the interesting event types; they will appear in `revents'
   to indicate the status of the file descriptor.  */
#define POLLIN		0x001		/* There is data to read.  */
#define POLLPRI		0x002		/* There is urgent data to read.  */
#define POLLOUT		0x004		/* Writing now will not block.  */
//@files: kernel/msm-5.4/tools/leds/led_hw_brightness_mon.c

int main(int argc, char const *argv[])
{
	int fd, ret;
	char brightness_file_path[LED_MAX_NAME_SIZE + 11];
	struct pollfd pollfd;
	struct timespec ts;
	char buf[11];

	if (argc != 2) {
		fprintf(stderr, "Requires <device-name> argument\n");
		return 1;
	}

	snprintf(brightness_file_path, LED_MAX_NAME_SIZE,
		 "/sys/class/leds/%s/brightness_hw_changed", argv[1]);

	fd = open(brightness_file_path, O_RDONLY); //创建要监听的fd文件
	if (fd == -1) {
		printf("Failed to open %s file\n", brightness_file_path);
		return 1;
	}

	/*
	 * read may fail if no hw brightness change has occurred so far,
	 * but it is required to avoid spurious poll notifications in
	 * the opposite case.
	 */
	read(fd, buf, sizeof(buf));

	pollfd.fd = fd;
	pollfd.events = POLLPRI;//设定监听的event

	while (1) {
		ret = poll(&pollfd, 1, -1); //poll的入参含义:设定要监听的文件节点和文件节点个数,以及等待的时间
		if (ret == -1) {
			printf("Failed to poll %s file (%d)\n",
				brightness_file_path, ret);
			ret = 1;
			break;
		}

		clock_gettime(CLOCK_MONOTONIC, &ts);

		ret = read(fd, buf, sizeof(buf));
		if (ret < 0)
			break;

		ret = lseek(pollfd.fd, 0, SEEK_SET);//监听的事件有返回则做相应的事情
		if (ret < 0) {
			printf("lseek failed (%d)\n", ret);
			break;
		}

		printf("[%ld.%09ld] %d\n", ts.tv_sec, ts.tv_nsec, atoi(buf));
	}

	close(fd);

	return ret;
}

2 poll () 调用原理:

调用到驱动注册的poll, 在驱动注册的poll 中会进行wait_poll 等event,另一条thread 可以设定event,poll 就可以检测到event就会受到POLLIN or POLLRI 的返回值,对比返回值符合预期就可以进行相关处理操作。

kernel-5.10/fs/select.c 折叠源码
//@files: kernel-5.10/tools/include/nolibc/nolibc.h
static __attribute__((unused))
int sys_poll(struct pollfd *fds, int nfds, int timeout)
{
    return my_syscall3(__NR_poll, fds, nfds, timeout);
}
 
#define my_syscall3(num, arg1, arg2, arg3)                                    \
({                                                                            \
    long _ret;                                                            \
    register long _num  asm("rax") = (num);                               \
    register long _arg1 asm("rdi") = (long)(arg1);                        \
    register long _arg2 asm("rsi") = (long)(arg2);                        \
    register long _arg3 asm("rdx") = (long)(arg3);                        \
                                          \
    asm volatile (                                                        \
        "syscall\n"                                                   \
        : "=a" (_ret)                                                 \
        : "r"(_arg1), "r"(_arg2), "r"(_arg3),                         \
          "0"(_num)                                                   \
        : "rcx", "r8", "r9", "r10", "r11", "memory", "cc"             \
    );                                                                    \
    _ret;                                                                 \
})
 
//@files:kernel-5.10/fs/select.c
SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,
        int, timeout_msecs)
{
    struct timespec64 end_time, *to = NULL;
    int ret;
 
    if (timeout_msecs >= 0) {
        to = &end_time;
        poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC,
            NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC));
    }
// 调用do_sys_poll()
    ret = do_sys_poll(ufds, nfds, to);
 
    if (ret == -ERESTARTNOHAND) {
        struct restart_block *restart_block;
 
        restart_block = ¤t->restart_block;
        restart_block->poll.ufds = ufds;
        restart_block->poll.nfds = nfds;
 
        if (timeout_msecs >= 0) {
            restart_block->poll.tv_sec = end_time.tv_sec;
            restart_block->poll.tv_nsec = end_time.tv_nsec;
            restart_block->poll.has_timeout = 1;
        } else
            restart_block->poll.has_timeout = 0;
 
        ret = set_restart_fn(restart_block, do_restart_poll);
    }
    return ret;
}
 
do_sys_poll()
|-->poll_initwait(&table);//最终table->pt->qproc = __pollwait
|--> do_poll()
    |--> do_pollfd(pfd, pt)
        |--> vfs_poll(f.file, pwait)//@files:kernel-5.10/include/linux/poll.h
             |-->return file->f_op->poll(file, pt)//调用kernel driver 注册的poll
                 //这个函数就跟驱动相关了,猜测调用poll_wait将当前进程放到驱动的等待列表。如果有数据的话,那么设置mask = POLLIN
    |--> schedule_timeout(__timeout);//设置超时时间,进程休眠

return file->f_op->poll(file, pt)//调用kernel driver 注册的poll
//这个函数就跟驱动相关了,猜测调用poll_wait将当前进程放到驱动的等待列表。如果有数据的话,那么设置mask = POLLIN

3 kernel driver 的 .poll 实现

kernfs_fop_poll 折叠源码
const struct file_operations kernfs_file_fops = {
    .read_iter  = kernfs_fop_read_iter,
    .write_iter = kernfs_fop_write_iter,
    .llseek     = generic_file_llseek,
    .mmap       = kernfs_fop_mmap,
    .open       = kernfs_fop_open,
    .release    = kernfs_fop_release,
    .poll       = kernfs_fop_poll,
    .fsync      = noop_fsync,
    .splice_read    = generic_file_splice_read,
    .splice_write   = iter_file_splice_write,
};
 
/*
 * Kernfs attribute files are pollable.  The idea is that you read
 * the content and then you use 'poll' or 'select' to wait for
 * the content to change.  When the content changes (assuming the
 * manager for the kobject supports notification), poll will
 * return EPOLLERR|EPOLLPRI, and select will return the fd whether
 * it is waiting for read, write, or exceptions.
 * Once poll/select indicates that the value has changed, you
 * need to close and re-open the file, or seek to 0 and read again.
 * Reminder: this only works for attributes which actively support
 * it, and it is not possible to test an attribute from userspace
 * to see if it supports poll (Neither 'poll' nor 'select' return
 * an appropriate error code).  When in doubt, set a suitable timeout value.
 */
__poll_t kernfs_generic_poll(struct kernfs_open_file *of, poll_table *wait)
{
    struct kernfs_node *kn = kernfs_dentry_node(of->file->f_path.dentry);
    struct kernfs_open_node *on = kn->attr.open;
//调用poll_wait将当前进程放到驱动的等待列表。如果有数据的话,那么设置mask = POLLIN
    poll_wait(of->file, &on->poll, wait);
 
    if (of->event != atomic_read(&on->event))
        return DEFAULT_POLLMASK|EPOLLERR|EPOLLPRI;
 
    return DEFAULT_POLLMASK;
}
 
static __poll_t kernfs_fop_poll(struct file *filp, poll_table *wait)
{
    struct kernfs_open_file *of = kernfs_of(filp);
    struct kernfs_node *kn = kernfs_dentry_node(filp->f_path.dentry);
    __poll_t ret;
 
    if (!kernfs_get_active(kn))
        return DEFAULT_POLLMASK|EPOLLERR|EPOLLPRI;
 
    if (kn->attr.ops->poll)
        ret = kn->attr.ops->poll(of, wait);
    else
        ret = kernfs_generic_poll(of, wait);
 
    kernfs_put_active(kn);
    return ret;
}

4 kernel driver notify 实现poll 状态上报

本质是用一个workqueue 去等 wake_up_interruptible(&on→poll); 唤醒

4.1 brigntness 节点的写法是调用sysfs_notify()

kernel-5.10/drivers/misc/mediatek/leds/leds-mtk-disp.c

static int led_level_set(struct led_classdev *led_cdev,
                      enum led_brightness brightness)
{

sysfs_notify(&led_cdev->dev->kobj, NULL, "brightness");

sysfs_notify 折叠源码
//@files:kernel-5.10/fs/sysfs/file.c
void sysfs_notify(struct kobject *kobj, const char *dir, const char *attr)
{
    struct kernfs_node *kn = kobj->sd, *tmp;
 
    if (kn && dir)
        kn = kernfs_find_and_get(kn, dir);
    else
        kernfs_get(kn);
 
    if (kn && attr) {
        tmp = kernfs_find_and_get(kn, attr);
        kernfs_put(kn);
        kn = tmp;
    }
 
    if (kn) {
        kernfs_notify(kn);
        kernfs_put(kn);
    }
}
EXPORT_SYMBOL_GPL(sysfs_notify);
 
//@files:/miui-s-matisse-dev/kernel/kernel-5.10/fs/kernfs/file.c
/**
 * kernfs_notify - notify a kernfs file
 * @kn: file to notify
 *
 * Notify @kn such that poll(2) on @kn wakes up.  Maybe be called from any
 * context.
 */
void kernfs_notify(struct kernfs_node *kn)
{
    static DECLARE_WORK(kernfs_notify_work, kernfs_notify_workfn);
    unsigned long flags;
    struct kernfs_open_node *on;
 
    if (WARN_ON(kernfs_type(kn) != KERNFS_FILE))
        return;
 
    /* kick poll immediately */
    spin_lock_irqsave(&kernfs_open_node_lock, flags);
    on = kn->attr.open;
    if (on) {
        atomic_inc(&on->event);
//唤醒poll 进程,这样poll_wait 就可以等到数据了
        wake_up_interruptible(&on->poll);
    }
    spin_unlock_irqrestore(&kernfs_open_node_lock, flags);
 
    /* schedule work to kick fsnotify */
    spin_lock_irqsave(&kernfs_notify_lock, flags);
    if (!kn->attr.notify_next) {
        kernfs_get(kn);
        kn->attr.notify_next = kernfs_notify_list;
        kernfs_notify_list = kn;
        schedule_work(&kernfs_notify_work);
    }
    spin_unlock_irqrestore(&kernfs_notify_lock, flags);
}
EXPORT_SYMBOL_GPL(kernfs_notify);

4.2 brightness_hw_changed 是调用 sysfs_notify_dirent()实现的

void mt_leds_notify_brightness_hw_changed(struct led_conf_info *led_conf,
                           enum led_brightness brightness)
{
    if (WARN_ON(!led_conf->brightness_hw_changed_kn))
        return;
 
    led_conf->brightness_hw_changed = brightness;
//调用到poll 去通知
    sysfs_notify_dirent(led_conf->brightness_hw_changed_kn);
}
EXPORT_SYMBOL_GPL(mt_leds_notify_brightness_hw_changed);

sysfs_notify_dirent 会调用到poll

sysfs_notify_dirent() 折叠源码
//@files:kernel-5.10/include/linux/sysfs.h
static inline void sysfs_notify_dirent(struct kernfs_node *kn)
{
    kernfs_notify(kn);
}
 
//@files:/miui-s-matisse-dev/kernel/kernel-5.10/fs/kernfs/file.c
 
/**
 * kernfs_notify - notify a kernfs file
 * @kn: file to notify
 *
 * Notify @kn such that poll(2) on @kn wakes up.  Maybe be called from any
 * context.
 */
void kernfs_notify(struct kernfs_node *kn)
{
    static DECLARE_WORK(kernfs_notify_work, kernfs_notify_workfn);
    unsigned long flags;
    struct kernfs_open_node *on;
 
    if (WARN_ON(kernfs_type(kn) != KERNFS_FILE))
        return;
 
    /* kick poll immediately */
    spin_lock_irqsave(&kernfs_open_node_lock, flags);
    on = kn->attr.open;
    if (on) {
        atomic_inc(&on->event);
        wake_up_interruptible(&on->poll);
    }
    spin_unlock_irqrestore(&kernfs_open_node_lock, flags);
 
    /* schedule work to kick fsnotify */
    spin_lock_irqsave(&kernfs_notify_lock, flags);
    if (!kn->attr.notify_next) {
        kernfs_get(kn);
        kn->attr.notify_next = kernfs_notify_list;
        kernfs_notify_list = kn;
        schedule_work(&kernfs_notify_work);
    }
    spin_unlock_irqrestore(&kernfs_notify_lock, flags);
}
EXPORT_SYMBOL_GPL(kernfs_notify);

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值