Why doesn't this call to `poll` block correctly on a sysfs device attribute file?
Asked Answered
H

1

11

I have a simple sysfs device attribute which shows up under my sysfs directory, and on a call to read returns the value of a kernelspace variable. I want to call poll on this attribute to allow my userspace thread to block until the value shown by the attribute changes.

My problem is that poll doesn't seem to block on my attribute -- it keeps returning POLLPRI even though the value shown by the attribute does not change. In fact, I have no calls at all to sysfs_notify in the kernel module, yet the userspace call poll still does not block.

Perhaps I should be checking for a return value of something other than POLLPRI -- but according to the documentation in the Linux kernel, sysfs_poll should return POLLERR|POLLPRI:

/* ... When the content changes (assuming the
 * manager for the kobject supports notification), poll will
 * return POLLERR|POLLPRI ...
 */

Is there something I'm forgetting to do with poll?


  1. The device attribute is located at: /sys/class/vilhelm/foo/blah.

  2. I load a kernel module called foo which registers a device, and creates a class and this device attribute.

  3. The userspace application called bar spawns a thread that calls poll on the device attribute, checking for POLLPRI.

    • If poll returns a positive number, POLLPRI has been returned.
    • Use fopen and fscan to read the value from the device attribute file.
    • If the value is 42, print FROM THREAD!!!.

The problem is that the message is printed nonstop when I'm expecting the call to poll to block indefinitely. The problem must lie with poll (the other calls successfully acquire the correct value of 42 from the device attribute).


userspace app - bar.c:

#include <stdio.h>

#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <unistd.h>

static void handle_val(unsigned val, FILE *fp);
void * start_val_service(void *arg);

int main(void){
        pthread_t val_serv;
        pthread_create(&val_serv, NULL, &start_val_service, NULL);

        pthread_exit(NULL);
        return 0;

}

static void handle_val(unsigned val, FILE *fp){
        switch(val){
                case 42:
                {
                        printf("FROM THREAD!!!\n");
                        break;
                }

                default:
                        break;
        }
}


void * start_val_service(void *arg){
        struct pollfd fds;
        fds.fd = open("/sys/class/vilhelm/foo/blah", O_RDONLY);
        fds.events = POLLPRI;

        do{
                int ret = poll(&fds, 1, -1);
                if(ret > 0){
                        FILE *fp = fopen("/sys/class/vilhelm/foo/blah", "r");

                        unsigned val;
                        fscanf(fp, "%u", &val);
                        
                        handle_val(val, fp);

                        fclose(fp);
                }
        }while(1);

        close(fds.fd);

        pthread_exit(NULL);
}

kernel module - foo.c:

#include <linux/device.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/kernel.h>

static dev_t foo_dev;
static struct class *vilhelm;

static unsigned myvar = 42;

static ssize_t unsigned_dev_attr_show(struct device *dev, struct device_attribute *attr, char *buf);

struct unsigned_device_attribute{
        struct device_attribute dev_attr;
        unsigned *ptr;
};

static struct unsigned_device_attribute unsigned_dev_attr_blah = {
        .dev_attr = __ATTR(blah, S_IRUGO, unsigned_dev_attr_show, NULL)
};

static int __init foo_init(void){
        int retval = 0;

        printk(KERN_INFO "HELLO FROM MODULE 1");
        
        if(alloc_chrdev_region(&foo_dev, 0, 1, "vilhelm") < 0){
                printk(KERN_ERR "foo: unable to register device");
                retval = -1;
                goto out_alloc_chrdev_region;
        }
        
        vilhelm = class_create(THIS_MODULE, "vilhelm");
        if(IS_ERR(vilhelm)){
                printk(KERN_ERR "foo: unable to create device class");
                retval = PTR_ERR(vilhelm);
                goto out_class_create;
        }
        
        struct device *foo_device = device_create(vilhelm, NULL, foo_dev, NULL, "foo");
        if(IS_ERR(foo_device)){
                printk(KERN_ERR "foo: unable to create device file");
                retval = PTR_ERR(foo_device);
                goto out_device_create;
        }

        unsigned_dev_attr_blah.ptr = &myvar;
        retval = device_create_file(foo_device, &unsigned_dev_attr_blah.dev_attr);
        if(retval){
                printk(KERN_ERR "foo: unable to create device attribute files");
                goto out_create_foo_dev_attr_files;
        }

        return 0;

        out_create_foo_dev_attr_files:
                device_destroy(vilhelm, foo_dev);
        out_device_create:
                class_destroy(vilhelm);
        out_class_create:
                unregister_chrdev_region(foo_dev, 1);
        out_alloc_chrdev_region:
                return retval;
}

static void __exit foo_exit(void){
        printk(KERN_INFO "BYE FROM MODULE 1");

        device_destroy(vilhelm, foo_dev);
        class_destroy(vilhelm);
        unregister_chrdev_region(foo_dev, 1);
}

static ssize_t unsigned_dev_attr_show(struct device *dev, struct device_attribute *attr, char *buf){
        struct unsigned_device_attribute *tmp = container_of(attr, struct unsigned_device_attribute, dev_attr);

        unsigned value = *(tmp->ptr);

        return scnprintf(buf, PAGE_SIZE, "%u\n", value);
}

module_init(foo_init);
module_exit(foo_exit);

MODULE_LICENSE("GPL");

See also

Using the Linux sysfs_notify call

Hunan answered 8/5, 2013 at 14:10 Comment(0)
J
10

To quote some more from the comment you quoted:

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.

But you do nothing with fds.fd.

Also, do a dummy read() before calling poll(); any newly opened file is considered changed.

Junia answered 13/5, 2013 at 14:15 Comment(6)
I added close(fds.fd) above the fopen line, and called open again after the fclose call, but no change. Using lseek(fds.fd, 0, SEEK_SET) yielded no results as well.Hunan
You must read the current value from the polled fd before calling poll.Junia
The dummy read is what solved it. I created a char variable called dummybuf and inserted the following line immediately after open, and also after fclose: read(fds.fd, &dummybuf, 1);Hunan
Though I do have followup question: is it normal for POLLPRI to be returned when a file is opened (thus warranting the need for a dummy read)?Hunan
For sysfs files, it's normal. (POLLIN indicates that the file is readable, which is always true.)Junia
Dummy read because newly-opened-is-considered-changed was also the (undocumented!) missing piece that I needed to poll the cgroups.events file.Alanealanine

© 2022 - 2024 — McMap. All rights reserved.