本文存在几个bug以及不规范的地方,已在创建设备节点问题进行相关的说明,请结合两篇文章一起看。

用户空间通常是打开某一特定的设备节点,然后通过write()/read()/ioctl()方法向内核空间进行数据的交换。
https://www.ibm.com/developerworks/cn/linux/l-kerns-usrs2/

一、sysfs介绍

在 documentation/filesystems/sysfs.txt 对sysfs的介绍中,一上来就说:

sysfs is a ram-based filesystem initially based on ramfs. It provides
a means to export kernel data structures, their attributes, and the
linkages between them to userspace.

sysfs is tied inherently to the kobject infrastructure. Please read
Documentation/kobject.txt for more information concerning the kobject
interface.

sysfs是基于ramfs,提供了到处内核数据结构、属性和与用户空间关联的方法。sysfs与kobject紧紧相关联。简而言之,sysfs就是将系统中的设备组成层次结构,并向用户提供详细的内核数据结构等信息。这是一个文件系统,实时显示当前设备的情况,用户可以通过设备文件与内核进行数据的交互。
本文不对sysfs的原理进行说明,只站在用户的角度去使用它。

二、sys目录结构

victor@ubuntu:/sys$ ll
total 4
dr-xr-xr-x  13 root root    0 Nov 20 14:28 ./
drwxr-xr-x  23 root root 4096 Oct 16 11:23 ../
drwxr-xr-x   2 root root    0 Nov 20 14:28 block/
drwxr-xr-x  36 root root    0 Nov 20 14:28 bus/
drwxr-xr-x  60 root root    0 Nov 20 14:28 class/
drwxr-xr-x   4 root root    0 Nov 20 14:28 dev/
drwxr-xr-x  12 root root    0 Nov 20 14:28 devices/
drwxr-xr-x   5 root root    0 Nov 20 14:28 firmware/
drwxr-xr-x   7 root root    0 Nov 20 14:28 fs/
drwxr-xr-x   2 root root    0 Nov 20 14:28 hypervisor/
drwxr-xr-x  10 root root    0 Nov 20 14:28 kernel/
drwxr-xr-x 138 root root    0 Nov 20 14:28 module/
drwxr-xr-x   2 root root    0 Nov 20 14:28 power/
victor@ubuntu:/sys$ 

关于这几个目录的详细介绍,参照了linux内核sysfs详解

block目录:包含所有的块设备;
devices目录:包含系统所有的设备,并根据设备挂接的总线类型组织成层次结构;
bus目录:包含系统中所有的总线类型;
drivers目录:包括内核中所有已注册的设备驱动程序;
class目录:系统中的设备类型(如网卡设备,声卡设备等) ;

sys下面的目录和文件反映了整台机器的系统状况。比如bus,
victor@ubuntu:/sys$ ll bus/
i2c/ ide/ pci/ pci express/ platform/ pnp/ scsi/ serio/ usb/
里面就包含了系统用到的一系列总线,比如pci, ide, scsi, usb等等。比如你可以在usb文件夹中发现你使用的U盘,USB鼠标的信息。

三、file_operation

注意:以下代码写法存在多种问题,现已在另外两篇文章创建设备节点问题poll() 的用法当做反面教材进行说明了,请参照。

1、新建一个驱动

#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/sched.h>

//#define DEBUG_SAMPLE

#ifdef DEBUG_SAMPLE
#define sample_dbg(format, arg...) printk(format, ##arg)
#else
#define sample_dbg(fmt, ...)       do{ }while(0)
#endif

#define SAMPLE_IOC_MAGIC                's'
#define IOC_SAMPLE_CMD0              _IOW(SAMPLE_IOC_MAGIC, 0, __u8)
#define IOC_SAMPLE_CMD1              _IOW(SAMPLE_IOC_MAGIC, 1, __u8)

struct sample {
    char buf[256];
    int count;
    int sample_major;
    wait_queue_head_t   read_queue;
    wait_queue_head_t   write_queue;
    bool    completed_in_req;
    bool    completed_out_req;
};

static struct class *sample_class;
static struct device *sample_device;
static struct sample *sample_dev;

int sample_open(struct inode * inode, struct file * filp)
{
    sample_dbg("[sample] %s line = %d\n", __func__, __LINE__);
    return 0;
}

int sample_release(struct inode *node, struct file *filp)
{
    sample_dbg("[sample] %s line = %d\n", __func__, __LINE__);
    return 0;
}

static int sample_write(struct file * file, const char __user * buf, size_t count, loff_t *f_pos)
{
    int ret = count;
    sample_dbg("[sample] %s line = %d: count = %d, f_pos = %d\n", __func__, __LINE__, count, *f_pos);

    if (copy_from_user(sample_dev->buf+*f_pos, buf, count)) {
        sample_dbg("[sample] copy from user error\n");
        ret = -EFAULT;
    }
    sample_dbg("[sample] write:buf = %s\n", sample_dev->buf+*f_pos);

    // 写完之后可以读,但不能写,唤醒读
    sample_dev->completed_in_req = 1;
    sample_dev->completed_out_req = 0;
    wake_up_interruptible(&sample_dev->read_queue);
    return ret;
}

static int sample_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    int ret = count;
    sample_dbg("[sample] %s line = %d: count = %d, f_pos = %d\n", __func__, __LINE__, count, *f_pos);
    sample_dbg("[sample] read:buf = %s\n", sample_dev->buf);
    if (copy_to_user(buf, sample_dev->buf, count)) {
        ret = -EFAULT;
        sample_dbg("[sample] copy to user error\n");
    }

    // 读完之后可以写,但不能读,唤醒写
    sample_dev->completed_in_req = 0;
    sample_dev->completed_out_req = 1;
    wake_up_interruptible(&sample_dev->write_queue);

    return ret;
}

long sample_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    switch(cmd) {
        case IOC_SAMPLE_CMD0:
            break;

        case IOC_SAMPLE_CMD1:
            break;

        default:
            break;
    }
    return 0;
}

static unsigned int sample_poll(struct file *fd, poll_table *wait)
{
    unsigned int    ret = 0;
    sample_dbg("[sample] %s line = %d\n", __func__, __LINE__);

    poll_wait(fd, &sample_dev->read_queue, wait);
    poll_wait(fd, &sample_dev->write_queue, wait);

    if(sample_dev->completed_out_req)
        ret |= POLLOUT | POLLWRNORM;

    if (sample_dev->completed_in_req)
        ret |= POLLIN | POLLRDNORM;

    return ret;
}


static const struct file_operations sample_fops = {
    .owner      = THIS_MODULE,
    .open       = sample_open,
    .release    = sample_release,
    .write      = sample_write,
    .read       = sample_read,
    .unlocked_ioctl = sample_ioctl,
    .poll       = sample_poll,
};


static int sample_init(void)  
{  

    /* 初始化 sample_dev 结构体 */
    sample_dev = kzalloc(sizeof(struct sample), GFP_KERNEL);
    if (!sample_dev)
        return ERR_PTR(-ENOMEM);

    /* 注册字符设备,主设备号设置为0表示由系统自动分配主设备号 */
    sample_dev->sample_major = register_chrdev(0, "sample", &sample_fops);

    /* 创建sample_class类 */
    sample_class = class_create(THIS_MODULE, "sample_class");

    /* 在sample_class类下创建sample_dev设备,这之后可以生成 /dev/sample_dev 的设备节点 */
    sample_device = device_create(sample_class, NULL, MKDEV(sample_dev->sample_major, 0), NULL, "sample_dev");

    init_waitqueue_head(&sample_dev->write_queue);
    init_waitqueue_head(&sample_dev->read_queue);

    sample_dev->completed_in_req = 1; // 一上来标记可以写

    return 0;
}


static void sample_exit(void)  
{
    unregister_chrdev(sample_dev->sample_major, "sample");
    device_unregister(sample_device);
    class_destroy(sample_class);
}  

module_init(sample_init);
module_exit(sample_exit);

MODULE_AUTHOR("Victor");
MODULE_DESCRIPTION("Just for sample demon");
MODULE_LICENSE("GPL");

register_chrdev大致作用:向内核注册cdev结构体,当在用户空间打开设备文件时内核可以根据设备号快速定位此设备文件的cdev->file_operations结构体,
从而调用驱动底层的open,close,read,write,ioctl等函数,当我们在用户空间open字符设备文件时,
首先调用def_chr_fops->chrdev_open()函数(所有字符设备都调用此函数),
chrdev_open会调用kobj_lookup函数找到设备的cdev->kobject,从而得到设备的cdev,进而获得file_operations.
要想通过kobj_lookup找到相应的cdev,必需调用register_chrdev()函数。向内核注册。

一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,
这个类存放于sysfs下面,一旦创建好了这个类,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。
这样,加载模块的时候,用户空间中的udev会自动响应device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。

与之对应的应用层测试程序:

// sample_test.c

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
#include <poll.h>

#define FILE_PATH "/dev/sample_dev"

int main(int argc, char **argv)
{
    int ret = 0;
    int fd;
    char *wr_buf = "aaaaa";
    char rd_buf[10];
    struct pollfd Events;

    memset(&Events, 0 ,sizeof(Events));

    fd = open(FILE_PATH, O_RDWR | O_NDELAY);
    if (fd < 0)
    {
        printf("[sample_test] open %s failed!!!!\n", FILE_PATH);
        return -1;
    }

    Events.fd = fd;
    Events.events = POLLOUT | POLLWRNORM;
    ret = poll(&Events, 1, 1000);
    if (ret < 0) {
        printf("[sample_test] write POLL ERROR");
    } else if (ret == 0) {
        printf("[sample_test] write POLL timeout");
    } else {
        ret = write(fd, wr_buf, sizeof(wr_buf));
    }

    Events.fd = fd;
    Events.events = POLLIN | POLLRDNORM;
    ret = poll(&Events, 1, 1000);
    if (ret < 0) {
        printf("[sample_test] read POLL ERROR");
    } else if (ret == 0) {
        printf("[sample_test] read POLL timeout");
    } else {
        ret = read(fd, rd_buf, 4);
    }
    close(fd);
    return 0;

}

四、DEVICE_ATTR

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

ssize_t store_simple(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);

static DEVICE_ATTR(simple, S_IWUSR | S_IRUGO, show_simple, store_simple);

static struct attribute *dev_attrs[] = {
    &dev_attr_simple.attr,
    NULL,
};

static struct attribute_group dev_attr_grp = {
    .attrs = dev_attrs,
};

// 读接口
ssize_t show_simple(struct device *dev, struct device_attribute *attr, char *buf)
{
    return 0;
}

// 写接口
ssize_t store_simple(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    return count; // 一定要return count,因为应用层通常是判断写的个数与返回的个数是否相等来判断是否写成功的
}

probe()函数创建sys节点:

sysfs_create_group(&pdev->dev.kobj, &dev_attr_grp);

其中,相关的宏定义如下:

#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

#define __ATTR(_name, _mode, _show, _store) {                           \
        .attr = {.name = __stringify(_name),                            \
                 .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },             \
        .show   = _show,                                                \
        .store  = _store,                                               \
}

struct device_attribute {
        struct attribute        attr;
        ssize_t (*show)(struct device *dev, struct device_attribute *attr,
                        char *buf);
        ssize_t (*store)(struct device *dev, struct device_attribute *attr,
                         const char *buf, size_t count);
};
GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐