Linux用戶空間與內核空間交互方法

本文存在幾個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);
};
發佈了64 篇原創文章 · 獲贊 109 · 訪問量 43萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章