SylixOS字符设备驱动开发(十二)

gewenbin
gewenbin
gewenbin
188
文章
15
评论
2020年12月17日23:15:16 评论 1,572

SylixOS设备操作之select

通过前面的学习我们知道了SylixOS下操作设备是通过设备文件的方式来进行的,先open一个文件获取到一个文件描述符,然后通过文件描述符进行操作文件。有时候我们在程序里可能需要对多个文件描述符进行操作,比如有一个程序我们需要读取设备数据,如果没有数据准备好程序需要阻塞,同时我们还要检测用户在命令行的输入,如果输入'quit'程序需要退出,程序该如何设计?大家首先想到的接受输入用scanf不就行了么,但是scanf这个函数是阻塞型的,如果用户没有输入东西程序就会一直阻塞,这样即使设备有数据准备好程序也不能及时去处理。

1. select应用层用法简介

解决这类问题的办法就是使用多路IO复用接口select,函数原型:

LW_API INT     select(INT               iWidth, 
                      fd_set           *pfdsetRead,
                      fd_set           *pfdsetWrite,
                      fd_set           *pfdsetExcept,
                      struct timeval   *ptmvalTO);

select可以检测一系列的文件描述符集合,检测的方向为三个,分别为可读,可写,有异常,并且还可以带有超时检测机制,这里我们以检测描述符可读为例进行讲解。

在本节demo中,我们设计检测两个描述符是否可读,一个是标准输入描述符,一个是demo设备文件描述符。我们使用fd_set 数据结构来记录描述符的集合,在使用select之前需要初始化这个描述符集合:

FD_ZERO (&fds);
FD_SET (STD_IN, &fds);
FD_SET (fd, &fds);

首先使用FD_ZERO 将数据结构清零,然后使用FD_SET 依次设置想要检测的描述符,STD_IN 表示标准输入文件描述符,每一个进程都会默认打开标准输入、标准输出、标准出错三个文件描述符,在SylixOS下,这三个文件描述符分别用STD_INSTD_OUTSTD_ERR 来表示:

/*********************************************************************************************************
  low-level I/O input, output, error fd's
*********************************************************************************************************/

#define STD_IN                      0
#define STD_OUT                     1
#define STD_ERR                     2

初始化完描述符集合后就可以调用select来检测:

ret = select (fd + 1, &fds, NULL, NULL, NULL);

select第一个参数为要检测的文件描述符中最大的那个值再加一,当描述符可读后还需要调用FD_ISSET 来确定是哪个描述符准备好了:

if (FD_ISSET(fd, &fds)) {
    printf("data readable.\r\n");
}

if (FD_ISSET(0, &fds)) {
    printf("app demo quit.\r\n");
    break;
}
2. 驱动层支持select

SylixOS中select是通过底层驱动ioctl来实现的,select实现伪代码表示的基本流程:

        select
           |
 ioctl调用FIOSELECT命令
           |
        线程睡眠
           |
ioctl调用FIOUNSELECT命令   
           |
          结束

驱动里select方法的阻塞和唤醒是通过select链表来实现的,首先在demo_dev_t 数据结构中添加select链表wakeup_list

typedef struct _demo_dev {
    LW_DEV_HDR dev;
    int flag;
    LW_SEL_WAKEUPLIST wakeup_list;
    void *priv;
} demo_dev_t;

然后在驱动加载时通过SEL_WAKE_UP_LIST_INIT 来初始化这个select链表:

SEL_WAKE_UP_LIST_INIT(&demo_dev[0].wakeup_list);
SEL_WAKE_UP_LIST_INIT(&demo_dev[1].wakeup_list);

如果驱动程序中数据还没准备好,就需要将线程睡眠,这是通过将当前驱动的等待结点加入到等待链表中来实现的:

PLW_SEL_WAKEUPNODE pselnode;

pselnode = (PLW_SEL_WAKEUPNODE)arg;
SEL_WAKE_NODE_ADD(&dev->wakeup_list, pselnode);

等待结点pselnode 是select在调用驱动的ioctl之前就准备好的,并作为ioctl的arg参数传进来,驱动层只需要转换成PLW_SEL_WAKEUPNODE 类型指针即可使用。

注意:SEL_WAKE_NODE_ADD只是将等待结点加入到等待链表中,此接口并不会阻塞,线程睡眠是通过线程TCB中的一个二进制信号量来实现的。

如果数据准备好了,就可以通过SEL_WAKE_UP 或者SEL_WAKE_UP_ALL 来唤醒线程,其中SEL_WAKE_UP_ALL 可以通过指定唤醒类型来进行更精确的控制。在实际的设备驱动中,一般是在中断处理中来调用此接口进行唤醒线程。在本例中,我们将唤醒调用放在虚拟设备的定时处理线程中:

static LW_SEL_WAKEUPLIST *wakeup_list;

static void *vdev_thread (void *sarg)
{
    while (1) {
        sleep(READ_PERIOD);

        data++;
        readable = TRUE;
        API_SemaphoreBPost(read_sync);
        if (wakeup_list) {
            SEL_WAKE_UP_ALL(wakeup_list, SELREAD);
        }
    }

    return NULL;
}

wakeup_list 这个全局变量在驱动调用vir_device_wakeup_register 接口时初始化:

int vir_device_wakeup_register (LW_SEL_WAKEUPLIST *list)
{
    wakeup_list = list;
    return 0;
}

线程唤醒后需要将之前的等待结点从等待链表里删除,这是通过SEL_WAKE_NODE_DELETE 来实现的:

PLW_SEL_WAKEUPNODE pselnode;

pselnode = (PLW_SEL_WAKEUPNODE)arg;
SEL_WAKE_NODE_DELETE(&dev->wakeup_list, pselnode);

通过前面的介绍,我们可以写出demo驱动ioctl中FIOSELECTFIOUNSELECT 命令相对应的处理:

case FIOSELECT:
    pselnode = (PLW_SEL_WAKEUPNODE)arg;

    if (SEL_WAKE_UP_TYPE(pselnode) == SELREAD) {
        if (vir_device_readable()) {
            SEL_WAKE_UP(pselnode);
        } else {
            SEL_WAKE_NODE_ADD(&dev->wakeup_list, pselnode);
            vir_device_wakeup_register(&dev->wakeup_list);
        }
    } else {
        return (PX_ERROR);
    }

    break;

case FIOUNSELECT:
    pselnode = (PLW_SEL_WAKEUPNODE)arg;

    if (SEL_WAKE_UP_TYPE(pselnode) == SELREAD) {
        SEL_WAKE_NODE_DELETE(&dev->wakeup_list, pselnode);
    } else {
        return (PX_ERROR);
    }

    break;

当调用select时驱动已经有准备好的数据时,需要调用SEL_WAKE_UP 告知系统已经有数据准备好,线程不需要阻塞。

3. 实际运行效果

加载驱动后,运行程序,终端输出打印:

[root@sylixos:/apps/app_demo12]# ./app_demo12
demo_open .
open read/write.
file permission: 644.
demo_ioctl 1c.
demo_ioctl 1d.
data readable.
demo_read.
read data is: 3162.
demo_ioctl 1c.
demo_ioctl 1d.
data readable.
demo_read.
read data is: 3163.
demo_ioctl 1c.
demo_ioctl 1d.
data readable.
demo_read.
read data is: 3164.
demo_ioctl 1c.
quit
demo_ioctl 1d.
app demo quit.
demo_close.
[root@sylixos:/apps/app_demo12]#

可以看出在没有输入时,程序依然是每隔一段时间读取数据打印输出,如果命令行有输入,比如输入'quit'后,程序响应输入直接退出结束。

附源码

driver_demo12.c源码:

#define  __SYLIXOS_KERNEL
#include <SylixOS.h>
#include <module.h>
#include <string.h>
#include "driver.h"
#include "vir_peripheral_device.h"

typedef struct _demo_dev {
    LW_DEV_HDR dev;
    int flag;
    LW_SEL_WAKEUPLIST wakeup_list;
    void *priv;
} demo_dev_t;

int drv_index;
demo_dev_t demo_dev[2];

static long demo_open(LW_DEV_HDR *dev, char *name, int flag, int mode)
{
    demo_dev_t *device = (demo_dev_t *)dev;
    device->priv = NULL;
    device->flag = 0;

    printk("%s %s.\r\n", __func__, name);

    if (flag & O_NONBLOCK) {
        printk("open nonblock.\r\n");
        device->flag |= O_NONBLOCK;
    }

    if ((flag & O_ACCMODE) == O_RDONLY) {
        printk("open read only.\r\n");
    } else if ((flag & O_ACCMODE) == O_WRONLY) {
        printk("open write only.\r\n");
    } else {
        printk("open read/write.\r\n");
    }

    printk("file permission: %o.\r\n", mode);

    LW_DEV_INC_USE_COUNT(&device->dev);

    return (long)device;
}

static int demo_close(demo_dev_t *dev)
{
    printk("%s.\r\n", __func__);

    LW_DEV_DEC_USE_COUNT(&dev->dev);

    return ERROR_NONE;
}

static int demo_ioctl(demo_dev_t *dev, int cmd, long arg)
{
    PLW_SEL_WAKEUPNODE pselnode;
    struct stat *pstat;
    struct data *data;

    printk("%s %x.\r\n", __func__, cmd);

    switch (cmd) {
    case FIOFSTATGET:
        pstat = (struct stat *)arg;
        if (!pstat) {
            return  (PX_ERROR);
        }

        pstat->st_dev     = LW_DEV_MAKE_STDEV(&dev->dev);
        pstat->st_ino     = (ino_t)0;
        pstat->st_mode    = (S_IRWXU | S_IRWXG | S_IRWXO | S_IFCHR);
        pstat->st_nlink   = 1;
        pstat->st_uid     = 0;
        pstat->st_gid     = 0;
        pstat->st_rdev    = 0;
        pstat->st_size    = 0;
        pstat->st_blksize = 0;
        pstat->st_blocks  = 0;
        pstat->st_atime   = API_RootFsTime(LW_NULL);
        pstat->st_mtime   = API_RootFsTime(LW_NULL);
        pstat->st_ctime   = API_RootFsTime(LW_NULL);
        break;

    case FIONBIO:
        if (*(int *)arg) {
            dev->flag |= O_NONBLOCK;
        } else {
            dev->flag &= ~O_NONBLOCK;
        }
        break;

    case FIOSETFL:
        if ((int)arg & O_NONBLOCK) {
            dev->flag |= O_NONBLOCK;
        } else {
            dev->flag &= ~O_NONBLOCK;
        }
        break;

    case FIOSELECT:
        pselnode = (PLW_SEL_WAKEUPNODE)arg;

        if (SEL_WAKE_UP_TYPE(pselnode) == SELREAD) {
            if (vir_device_readable()) {
                SEL_WAKE_UP(pselnode);
            } else {
                SEL_WAKE_NODE_ADD(&dev->wakeup_list, pselnode);
                vir_device_wakeup_register(&dev->wakeup_list);
            }
        } else {
            return (PX_ERROR);
        }

        break;

    case FIOUNSELECT:
        pselnode = (PLW_SEL_WAKEUPNODE)arg;

        if (SEL_WAKE_UP_TYPE(pselnode) == SELREAD) {
            SEL_WAKE_NODE_DELETE(&dev->wakeup_list, pselnode);
        } else {
            return (PX_ERROR);
        }

        break;

    case CMD_GET_VERSION:
        data = (struct data *)arg;
        data->version = 3;
        break;

    default:
        return  (PX_ERROR);
    }

    return ERROR_NONE;
}

static ssize_t demo_read(demo_dev_t *dev, char *buf, size_t size)
{
    int ret = -1;
    BOOL non_block = FALSE;

    printk("%s.\r\n", __func__);

    if (dev->flag & O_NONBLOCK) {
        non_block = TRUE;
    } else {
        non_block = FALSE;
    }

    ret = vir_device_read(non_block);
    if (ret > 0)
        memcpy(buf, &ret, sizeof(int));

    return ret;
}

static ssize_t demo_write(demo_dev_t *dev, char *buf, size_t size)
{
    char wdata[30] = {0};
    int wdata_len = 30;

    printk("%s.\r\n", __func__);

    wdata_len = (size < wdata_len) ? size : wdata_len;
    memcpy(wdata, buf, size);

    printk("write data %s success.\r\n", wdata);

    return wdata_len;
}

static struct file_operations demo_fops = {
    .owner    = THIS_MODULE,
    .fo_open  = demo_open,
    .fo_close = demo_close,
    .fo_ioctl = demo_ioctl,
    .fo_read  = demo_read,
    .fo_write = demo_write,
};

int module_init (void)
{
    int ret = 0;

    drv_index = iosDrvInstallEx(&demo_fops);
    if (drv_index < 0) {
        printk("driver install fail.\r\n");
        return -1;
    }

    DRIVER_LICENSE(drv_index, "GPL->Ver 2.0");
    DRIVER_AUTHOR(drv_index, "GeWenBin");
    DRIVER_DESCRIPTION(drv_index, "demo driver.");

    ret = iosDevAdd(&demo_dev[0].dev, "/dev/demo0", drv_index);
    if (ret != ERROR_NONE) {
        printk("device add fail.\r\n");
        iosDrvRemove(drv_index, TRUE);
        return -1;
    }

    ret = iosDevAdd(&demo_dev[1].dev, "/dev/demo1", drv_index);
    if (ret != ERROR_NONE) {
        printk("device1 add fail.\r\n");
        iosDevDelete(&demo_dev[0].dev);
        iosDrvRemove(drv_index, TRUE);
        return -1;
    }

    SEL_WAKE_UP_LIST_INIT(&demo_dev[0].wakeup_list);
    SEL_WAKE_UP_LIST_INIT(&demo_dev[1].wakeup_list);

    vir_device_init();

    return 0;
}

void module_exit (void)
{
    iosDevDelete(&demo_dev[0].dev);
    iosDevDelete(&demo_dev[1].dev);
    iosDrvRemove(drv_index, TRUE);
}

vir_peripheral_device.c源码:

/*
 * vir_peripheral_device.c
 *
 *  Created on: Nov 28, 2020
 *      Author: Administrator
 */
#define  __SYLIXOS_KERNEL
#include <SylixOS.h>
#include "vir_peripheral_device.h"

#define READ_PERIOD (3)
#define MUTEX_CREATE_DEF_FLAG        \
        LW_OPTION_WAIT_PRIORITY    | \
        LW_OPTION_INHERIT_PRIORITY | \
        LW_OPTION_DELETE_SAFE      | \
        LW_OPTION_OBJECT_GLOBAL

static LW_SEL_WAKEUPLIST *wakeup_list;
static LW_HANDLE tid;
static LW_HANDLE read_sync;
static BOOL readable = FALSE;
static int data = 1;

static void *vdev_thread (void *sarg)
{
    while (1) {
        sleep(READ_PERIOD);

        data++;
        readable = TRUE;
        API_SemaphoreBPost(read_sync);
        if (wakeup_list) {
            SEL_WAKE_UP_ALL(wakeup_list, SELREAD);
        }
    }

    return NULL;
}

int vir_device_init (void)
{
    LW_CLASS_THREADATTR threadattr;

    API_ThreadAttrBuild(&threadattr,
                        4 * LW_CFG_KB_SIZE,
                        LW_PRIO_NORMAL,
                        LW_OPTION_THREAD_STK_CHK,
                        LW_NULL);
    tid = API_ThreadCreate("t_vdev", vdev_thread, &threadattr, LW_NULL);
    if (tid == LW_OBJECT_HANDLE_INVALID) {
        return (PX_ERROR);
    }

    read_sync = API_SemaphoreBCreate("count_lock",
                                0,
                                LW_OPTION_OBJECT_LOCAL,
                                LW_NULL);
    if (read_sync == LW_OBJECT_HANDLE_INVALID) {
        API_ThreadDelete(&tid, NULL);
        printk("semaphore create failed.\n");
        return (PX_ERROR);
    }

    return ERROR_NONE;
}

int vir_device_deinit (void)
{
    API_ThreadDelete(&tid, NULL);

    return ERROR_NONE;
}

int vir_device_read (BOOL non_block)
{
    if (non_block) {
        if (readable) {
            goto return_data;
        } else {
            return 0;
        }
    }

    API_SemaphoreBPend(read_sync, LW_OPTION_WAIT_INFINITE);

return_data:
    readable = FALSE;
    return data;
}

BOOL vir_device_readable (void)
{
    return readable;
}

int vir_device_wakeup_register (LW_SEL_WAKEUPLIST *list)
{
    wakeup_list = list;
    return 0;
}

vir_peripheral_device.h源码:

/*
 * vir_peripheral_device.h
 *
 *  Created on: Nov 28, 2020
 *      Author: Administrator
 */

#ifndef SRC_VIR_PERIPHERAL_DEVICE_H_
#define SRC_VIR_PERIPHERAL_DEVICE_H_

int vir_device_init(void);
int vir_device_deinit(void);
int vir_device_read(BOOL non_block);
BOOL vir_device_readable(void);
int vir_device_wakeup_register(LW_SEL_WAKEUPLIST *list);

#endif /* SRC_VIR_PERIPHERAL_DEVICE_H_ */

app_demo12源码:

#include <stdio.h>
#include <string.h>

int main (int argc, char **argv)
{
    int fd;
    int ret = 0;
    int data = -1;
    fd_set fds;

    fd =  open("/dev/demo0", O_RDWR);
    if (fd < 0) {
        printf("open file fail.\r\n");
        return -1;
    }


    while (1) {
        FD_ZERO (&fds);
        FD_SET (STD_IN, &fds);
        FD_SET (fd, &fds);

        ret = select (fd + 1, &fds, NULL, NULL, NULL);
        if (ret < 0) {
            printf("select fail.\r\n");
            close(fd);
            return -1;
        } else if (ret == 0){
            printf("select timeout.\r\n");
        } else {
            if (FD_ISSET(fd, &fds)) {
                printf("data readable.\r\n");
            }

            if (FD_ISSET(STD_IN, &fds)) {
                printf("app demo quit.\r\n");
                break;
            }
        }

        read(fd, &data, 4);
        printf("read data is: %d.\r\n", data);
    }

    close(fd);

    return  (0);
}
gewenbin
  • 本文由 发表于 2020年12月17日23:15:16
  • 转载请务必保留本文链接:http://www.databusworld.cn/8955.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: